You are here

Aula 8 e Aula 9

Aula 8/Aula9 - Clarion 1

 

Objetivos

Nesta aula, iniciaremos o estudo da arquitetura Clarion, desenvolvida pelo grupo do Prof. Ron Sun na Universidade de Michigan. Para tanto, será necessário um pequeno estudo teórico do funcionamento da arquitetura, antes do desenvolvimento das atividades práticas. A arquitetura conta com uma home-page onde a grande maioria do material necessário pode ser obtido. Essa home-page pode ser encontrada aqui: Clarion Cognitive Architecture.

Infelizmente, a página de links para artigos desse site está com problemas, apontando os links para lugares errados. Felizmente, os links corretos dos artigos podem ser obtidos aqui.

Existem diversas fontes de informação que são importantes. A mais importante é um texto relativamente antigo, mas que é bastante detalhado nas especificações do Clarion, e que eu recomendo que vocês consultem. É o "R. Sun, A Detailed Specification of CLARION 5.0 . Technical report. 2003". Apesar dele citar o Clarion 5.0, o detalhamento é similar para a versão 6.1.0.7, que utilizaremos em nossas aulas. Uma versão local desse documento, sem as imensas bordas em branco que existem no documento original, e que dificultam a leitura, pode ser encontrada aqui.

Atividades

Nesta aula, seguiremos o tutorial do Clarion, utilizando o material disponibilizado pelos autores da arquitetura. Para tanto, devemos fazer o download da versão mais recente do Clarion no site dos autores: Downloads: Clarion Cognitive Architecture. A versão mais recente até o momento é a versão 6.1.0.7, que utilizaremos nesse curso. Caso a página dos autores apresente algum problema, uma cópia do mesmo pode ser obtida também aqui. Efetue o download da arquitetura e instale os arquivos em sua máquina.

O tutorial está dividido em várias pastas, com diversos arquivos PDF, dentro do diretório Tutorials. Uma versão compactada com todos os tutoriais em um mesmo documento pode ser encontrada aqui.

A versão 6.1.0.7 do Clarion foi desenvolvida em C# em .NET. Em nosso laboratório, utilizaremos o mono, que é uma implementação do .NET para Linux, e o monodevelop, uma ferramenta semelhante ao Netbeans, para desenvolvimento em C#.

Após a instalação da arquitetura, execute o monodevelop no Linux e procure ambientar-se a ele. Para tanto, vamos iniciar executando um Hello World da arquitetura Clarion, utilizando o mono. Os demos estão no diretório Samples.

A idéia é seguirmos todos os tutoriais na aula de hoje. Pode parecer muita coisa, a princípio, mas a maioria dos tutoriais correspondem a apenas pequenos trechos de código. Muitas partes da arquitetura ainda não foram implementadas no Clarion, e correspondem somente a templates pré-preparados para quando essas partes ficarem disponíveis. Existem diversas maneiras de se seguir os tutoriais. Poderíamos pegar um dos sub-sistemas, por exemplo, o ACS, e ir do básico, passando pelo intermediário e depois o avançado, e depois passar para outro subsistema, como MS e MCS. Sugiro que sigam a sequência da versão compactada disponibilizada acima. Entretanto, caso prefiram uma outra sequência, não há problemas.

Durante a leitura do tutorial, haverão vários pontos que podem ficar pouco claros. Para dirimir essas dúvidas, o texto da especificação detalhada, também disponibilizada anteriormente, será o contraponto perfeito. Caso as dúvidas não se solucionem com essa leitura, não hesite em chamar o professor para ajudá-lo. 

Durante as atividades, não se esqueçam de irem documentando os avanços no relatório.

Ao final das atividades do dia, mais ou menos por volta das 17:45, faremos uma avaliação dos avanços de todos, e caso seja necessário mais um dia para a continuidade das atividades, poderemos negociar essa questão durante a aula.

Relatório

O CLARION é uma plataforma formada por um número de subsistemas funcionais subdivididos em duas camadas onde uma primeira é representada de maneira ímplicita e outra de maneira explícita. Esses subsistemas se interagem constatemente. Isso se faz necessário para que a plataforma possa alcançar o processamento cognitivo.

Os quatro subsistemas que compõe o CLARION podem ser notados na figura abaixo.

Figura1: Arquitetura do Clarion

O Papel do subsistema Action Centered Subsystem (ACS) é o de controle de acões, não importando se elas estão relacionadas a movimentos externos físicos ou operações mentais internas. Já o Non Action Centered Subsystem (NACS) mantém o conhecimento geral, tanto implícito quanto explícito. O Motivational Subsystem (MS) é responsável pela motivação das percepções, ações e cognições, provendo retroalimentação (por exemplo indicando se uma saída é satisfatória ou não). o Meta-Cognitive Subsystem (MCS) monitora, direciona e modifica as operações do ACS dinamicamente e também as operaçoes dos demais subsistemas.

As figuras abaixo mostram com mais detalhes os subsistemas.
 

Figura2: ACS e NACS

  • ACS

Top Level - Regras explicitas

Bottom Level - Regras implicitas... conhecimentos adquiridos internamente

Inicialmente, regras Top Level sao usadas para gerar conhecimento para o Bottom Level (top down learning). Com o passar do tempo e conhecimentos obtidos implicitamente, as ações passam a ser consideradas utilizando regras do bottom level.

O ciclo de vida desse modulo consiste em escolher ações que supram os critérios do top level e bottom level, e estocásticamente uma delas é escolhida e executada. O resultado é observado e o conhecimento adquirido é reforçado no bottom level (QLearning). RER é utilizado para gerar conhecimentos explicitos baseados nos conhecimentos implicitos (aumento de conhecimento do top level por bottom up learning)

  • NACS

Top Level - Conhecimentos explicitos, regras associadas

Bottom Level - Conhecimentos Implicitos, memórias associadas. 

Os conhecimentos representados nesse subsistema são utilizados nas inferencias sobre o ambiente e estão sob controle do ACS.

Figura3: Motivacional

O subsistema motivacional esta relacionado aos drives e suas interações, ou seja, no mecanismo dos porques o agente faz o que ele faz. Simplificando isso, podemos dizer que o agente será motivado a fazer ações que aumentem o ganho, recompensas e pagamentos

 

  • Drives Primárias
Baixo nível: Buscar comida, buscar bebida, evitar perigo, dormir, reproduzir, evitar saturação, curiosidade, evitar aborrecimento
Alto nível: Buscar proximidade com outros iguais, autoestima, desejo de evolução
  • Drives Secundários
Derivados dos drives primários
Condicionamento, a partir de drives primários
Metas assumidas de outros agentes, por meio de instruções ou o desejo de agradar outros agentes

Figura4: Metacognitivo

O MCS monitora, controla e regula processos cognitivos de forma a melhorar a performance cognitiva do processo. Isso pode acontecer ajustando metas no ACS, ou interrompendo e mudando os processos correntes no ACS e NACS, setando parametros essenciais no ACS e NACS, entre outros.

Durante a execução do tutorial, pudemos ver alguns detalhes da implementação seguindo os seguitest tópicos:

  • Instalação da plataforma
  • Exemplo simples do módulo de ACS
  • Exemplo simples de MS
  • Exemplo intermediário de implementação do ACS
  • Exemplo intermediário de implementação de MS e MCS
  • Exemplo de NACS
Para cada um dos tópicos veremos um pouco mais de detalhes abaixo.
 

Instalação da plataforma

A instalação da plataforma é bem simples. A arquitetura foi gerada e distribuída em uma biblioteca de sistema DLL. Sua implementação é em C# logo tivemos que utilizar um ambiente de desenvolvimento preparado para essa linguagem. Em nosso estudo foi utilizado o Mono em uma distribuição Ubuntu.

Utilizando um dos exemplos do tutorial, pudemos compilar e fazer o link do programa com a biblioteca CLARION.

Após criar um projeto de exemplo e uma vez o link de referencia a DLL estabelecido, nosso ambiente ja esta pronto para utilizar as APIs do CLARION para desenvolvimento.

Os namespaces da biblioteca estão organizados da seguinte maneira:

  • Clarion - base da biblioteca e contém as classes World, AgentInitilizer e ImplicitComponentInitializer
  • Clarion.Framework - contém a maioria das classes necessárias para inicialização e execução das simulações. A grande part das classes desse namespace podem ser correlacionados aos termos e conceitos da teoria do CLARION
  • Clarion.Framework.Core - contém os construtores necessários para as operações do núcleo do sistema
  • Clarion.Framework.Extensions - contém classes de extensões
  • Clarion.Framework.Templates - contém classes abstratas, interfaces e delegates para construção de objetos customizado (por exemplo drives)
  • Clarion.Plugins - prove plugins e ferramentos que podem ser utilizados no ambiente simulado para melhorar compatiblidade e aplicação dos agentes

Nesse tópico também foi visto o relacionamento entre objetos descritivos e funcionais:

  • Descritivos: descreve itens, como por exemplo o ambiente de simulação, agentes, ações, objetivos, conhecimento e estão sob controle da exclusiva da classe World.
  • Funcionais: responsáveis por ações, descrever o processo e mecanismos para o funcionamento de um agente e em geral podem ser referenciados como "Módulos" ou "Componentes"

Exemplo simples do módulo de ACS

O primneiro exemplo visto, SimpleHelloWorld é uma implementação puramente ACS. A idéia é treinar um agente para quando saudado com Oi, responder Oi, quando Tchau, responder Tchau.

Para isso iniciamos o nosso World para conhecer essas informações:

            DimensionValuePair hi = World.NewDimensionValuePair("Salutation", "Hello");
            DimensionValuePair bye = World.NewDimensionValuePair("Salutation", "Goodbye");

O segundo passo é definir as ações que o agente irá executar:

            ExternalActionChunk sayHi = World.NewExternalActionChunk("Hello");
            ExternalActionChunk sayBye = World.NewExternalActionChunk("Goodbye");

Em seguida, iniciamos nosso agente selecionando o funcionamento por Simplified Q Learning backpropagation e definimos os nós de entrada e saída com as informações sensoriais e ações, respectivamente:

            SimplifiedQBPNetwork net = AgentInitializer.InitializeImplicitDecisionNetwork(John, SimplifiedQBPNetwork.Factory);

            net.Input.Add(hi);
            net.Input.Add(bye);
 
            net.Output.Add(sayHi);
            net.Output.Add(sayBye);

Uma vez nosso agente criado, o exemplo consiste em gratificar e penalizar o agente mediante repostas certas e erradas. De tal forma, uma opção aleatória é enviada para o agente e mediante resposta, damos o feedback,

Exercutando vemos que o agente demora algumas rodadas para aprender, porém a partir de um determinado momento, o agente passa a responder precisamente.

Reporting Results for the Simple Hello World Task
John got 9721 correct out of 10000 trials (97%)
At the end of the task, John had learned the following rules:
Condition:
	(Dimension = Salutation, Value = Hello), Setting = False
	(Dimension = Salutation, Value = Goodbye), Setting = True
Action:
	ExternalActionChunk Goodbye:
		DimensionValuePairs:
			(Dimension = SemanticLabel, Value = Goodbye)
Condition:
	(Dimension = Salutation, Value = Hello), Setting = True
	(Dimension = Salutation, Value = Goodbye), Setting = False
Action:
	ExternalActionChunk Hello:
		DimensionValuePairs:
			(Dimension = SemanticLabel, Value = Hello)
 

Exemplo simples de MS

No segundo tópico, adicionamos Goal em nossa lógica, que faz parte do top level do módulo de MS.

No primeiro momento vimos como podemos ver as informações dos Goals de um agente:

//Gets all of the items in the goal structure
IEnumerable<GoalChunk> gsContents =
(IEnumerable<GoalChunk>)John.GetInternals
(Agent.InternalWorldObjectContainers.GOAL_STRUCTURE);
 
//Gets the current goal
GoalChunk currentGoal = John.CurrentGoal;

Como ações, Goals são representados como chunks e inicializados usando a classe World.

Podemos setar dois parametros para ajuste do Goal:

  • Comportamento Lista/Pilha
  • Ativação (atual ou completa)

Exemplificando com código:

 

GoalChunk g = World.NewGoalChunk();

John.MS.Parameters.CURRENT_GOAL_ACTIVATION_OPTION = MotivationalSubsystem.CurrentGoalActivationOptions.FULL;

 
John.MS.Parameters.GOAL_STRUCTURE_BEHAVIOR_OPTION = MotivationalSubsystem.GoalStructureBehaviorOptions.STACK;
 

Para inserir esse Goal no nosso agente, devemos considerar que Goals são parte do SensoryInformation.

net.Input.Add(g);
 

Agora basta ativar nosso Goal. A forma de se fazer isso é pelo método SetGoal, passando por parametro o Goal e o nível de ativação. Por exemplo John.SetGoal(g, 1).

A desativação do nosso Goal é feita pelo método ResetGoal (John.ResetGoal(g))

Os métodos acima são utilizados na ativação/desativação manual dos Goals. Na plataforma Clarion, isso pode ser feito por diversas maneiras, controladas por outros módulos como ACS e MCS.

Nesse tutorial vemos a utilização pelo módulo ACS. Para tal, devemos utilizar um objeto da classe GoalStructureUpdateActionChunk.

Por exemplo no código abaixo, implementamos um actionChunk que seta o Goal g GoalStructureUpdateActionChunk

 
gAct = World.NewGoalStructureUpdateActionChunk();
gAct.Add(GoalStructure.RecognizedActions.SET, g);
 
Para esse Goal ser ativado pelo modulo ACS, devemos adiciona-lo na camáda pertinente do módulo. Nesse caso na camada de saída do ACS (podemos ver na primeira figura desse relatório a seta saindo do Módulo ACS para o módulo MS).
 
net.Output.Add(gAct);
 

Exemplo intermediário de implementação do ACS

 
No Clarion, os parametros de cada modulo tem valores padrões e geralmente tem boa reposta, porém para termos valores melhores, podemos otimizar os modulos fazendo ajustes nos parametros de cada modulo.
Os parametros do módulos foram implementados de duas formas nessa arquitetura: global e local (classe Parameters).
No exemplo Full Hello World do pacote desse tutorial, podemos ver como alguns parametros foram modificados para melhorar a performance de nossa execução
 
RefineableActionRule.GlobalParameters.SPECIALIZATION_THRESHOLD_1 = -.6;
RefineableActionRule.GlobalParameters.GENERALIZATION_THRESHOLD_1 = -.2;

A classe RefineableActionRule contém, dentre outros métodos, métodos para especialização e generalização. Os parametros alterados no código acima alteram a forma como esses métodos irão atuar de forma a ser mais eficiente em nosso cenário.

Outro exemplo presente no código abaixo desativa vários métodos de aprendizado presentes no module de ACS.

ActionCenteredSubsystem.GlobalParameters.PERFORM_RER_REFINEMENT = false;
ActionCenteredSubsystem.GlobalParameters.PERFORM_RULE_EXTRACTION = false;
ActionCenteredSubsystem.GlobalParameters.PERFORM_TOP_DOWN_LEARNING = false;
ActionCenteredSubsystem.GlobalParameters.PERFORM_BL_LEARNING = false;

Alguns pontos a serem notados é que as mudanças em parametros globais devem ser feitas antes da inicialização dos objetos (caso sejam feitos depois não são aplicados aos objetos anteriores). Outro ponto é que esses parametros respeitam as hierarquias das classes do Clarion. Por exemplo, RefineableActionRule, IRLRule, AssociativeRule, etc são derivados da classe Rule. Caso queira que um parametro tenha efeito em todas as classes, basta mudar o parametro apenas na classe Rule, de outra forma, pode ser alterado apenas nas classes que queiramos.

O exemplo acima é referente a mudanças globais de parametros. Nos casos de mudanças locais, podemos fazer somente durante a instancia do objeto que queremos essa alteração.

John.ACS.Parameters.PERFORM_RER_REFINEMENT = false;

//Retrieves the network from the bottom level of John's ACS
SimplifiedQBPNetwork net = (SimplifiedQBPNetwork)John.GetInternals(Agent.Internals.IMPLICIT_DECISION_NETWORKS).First();
net.Parameters.LEARNING_RATE = .5;

Por fim vimos como lidar com elementos de Working Memory. No Clarion, WM pode conter qualquer tipo de Chunk e suas interações são feitas pela classe Agent.

Pudemos ver como pedar as informações, setar/adicionar e remover elementos conforme demostrado abaixo.

IEnumerable<Chunk> wmContents = (IEnumerable<Chunk>)John.GetInternals(Agent.InternalWorldObjectContainers.WORKING_MEMORY);
 
John.SetWMChunk(ch, 1); //seta-adiciona o chunk na WM pelo agente John
 
John.ResetWMChunk(ch); //remove o chunk na WM pelo agente John
 
O uso dessas WM no Clarion é feito por meio da classe WorkingMemoryUpdateActionChunk. Um exemplo de utilização esta disponibilizado abaixo, onde a ação irá setar o chunck ch na WM
 
WorkingMemoryUpdateActionChunk wmAct = World.NewWorkingMemoryUpdateActionChunk();
wmAct.Add(WorkingMemory.RecognizedActions.SET, ch);
 
Por fim definimos um componente do modulo ACS para efetuar essa ação. Para isso basta especificar esse Action Chunk na camada de saída do módulo.
net.Output.Add(wmAct);
 

Exemplo intermediário de implementação de MS e MCS

Nesse exemplo estudamos principalmente o mecanismo de Drives. No Clarion, os Drives usam fatores das informações dos estados internos e externos para definir a "força" do drive, utilizado para sua ativação. Essas informação são recebidas pelo objeto SensoryInformation. As ativações dos drives irão direcionar nosso sistema meta cognitivo a tomar ações que irão direcionar o processamento.

O Clarion ja possui uma implementação para todos os drives primários. O primeiro passo é a inicialização dos drives.

FoodDrive food = AgentInitializer.InitializeDrive(John, FoodDrive.Factory, .5);

Esse código de exemplo inicializa o drive de comida FoodDrive do agente John, definindo fator de inicial e o fator de déficit.
Os drives estão divididos em grupos dentro do baixo nível do módulo de MS conforme abaixo:
  • Drive para aproximar
  • Drive para evitar
  • Drive para aproximar/evitar
  • Drive não específico
Caso queiramos mudar o grupo de um drive, podemos fazer da seguinte forma:
FoodDrive foodDrive = AgentInitializer.InitializeDrive(John, FoodDrive.Factory, .5, MotivationalSubsystem.DriveGroupSpecifications.BOTH);
 
Uma vez iniciado, devemos definir o estimulo do nosso drive.
si = World.NewSensoryInformation(John);
si[typeof(FoodDrive), FoodDrive.MetaInfoReservations.STIMULUS] = 1;
John.Perceive(si);
 
Esse código mostra como especificar o estímulo do drive baseado nas informações do SensoryInformation para o agente John. Com isso temos a camada baixa do módulo de MS própriamente configurados para uso de drive. O próximo passo é a implementação da meta-cognição que irá agir baseado no drive.
 
O processo de configuração do módulo MCS é similar a inicialização de um drive. No tutorial o MCS foi baseado na classe GoalSelectionModule, comumente utilizada.
//inicializando MCS
GoalSelectionModule gsm = AgentInitializer.InitializeMetaCognitiveModule(John, GoalSelectionModule.Factory);
GoalSelectionEquation gse = AgentInitializer.InitializeMetaCognitiveDecisionNetwork(gsm, GoalSelectionEquation.Factory);
 
//relacionando com o Drive, adicionando o drive strenght na camada de entrada do gse
gse.Input.Add(foodDrive.GetDriveStrength());
 
//inicializando o action chunk que seta a meta g e adicionando na camada de saida do gse
GoalStructureUpdateActionChunk gAct = World.NewGoalStructureUpdateActionChunk();
gAct.Add(GoalStructure.RecognizedActions.SET_RESET, g);
gse.Output.Add(gAct);
 
//especificando a relevancia da entrada com a saida
SomeGoalSelectionEquation.SetRelevance(SomeGoalStructureUpdateActionChunk, 
SomeDrive or SomeWorldObject, SomeRelevanceValue);
 
//co-relacionando o drive com o action chunk
gse.SetRelevance(gAct, foodDrive, 1);
 
//aplicando as configurações
gsm.Commit(gse);
John.Commit(gsm);
 

Exemplo de NACS

Para esse modulo estudamos um Simple Reasoner. A lógica consiste no seguinte:

  • Dada uma sequencia de números, retornar a sequencia completa do padrão X e do padrão X+1

Exemplificando, temos os 5 padrões abaixo:

static int [][] patterns = 
{
new int [] {1, 3, 5, 11, 13, 16, 19, 23, 27},
new int [] {3, 6, 7, 8, 12, 15, 20, 21, 26},
new int [] {2, 4, 8, 9, 11, 17, 18, 24, 30},
new int [] {1, 4, 10, 12, 15, 17, 19, 22, 29},
new int [] {3, 5, 8, 10, 14, 18, 20, 25, 28}
};
 
Ao informarmos no input as ativações dos valores 1 3 5 11 13 16, o programa retornará {1, 3, 5, 11, 13, 16, 19, 23, 27} e {3, 6, 7, 8, 12, 15, 20, 21, 26}
A implementação segue a seguinte lógica:
  • Inicializa o ambiente com 30 pares dimensão/Valor os 5 padrões especificados com 9 números
static void InitializeWorld()
{
  for (int i = 1; i <= nodeCount; i++)
  {
    dvs.Add(World.NewDimensionValuePair("Dim", i));
  }
  for (int i = 0; i < patterns.Length; i++)
  {
    DeclarativeChunk dc = 
    World.NewDeclarativeChunk(i, addSemanticLabel:false);
    foreach (DimensionValuePair dv in dvs)
    {
      if (patterns[i].Contains(dv.Value)
      {
        dc.Add(dv);
      }
    }
    chunks.Add(dc);
  }
}
  • Adiciona os Chunks declarativos com os 5 padrões no GKS
foreach (DeclarativeChunk dc in chunks)
  reasoner.AddKnowledge(dc);
  • Configura uma rede Hopfield, treinando e checando sua precisão
static void EncodeHopfieldNetwork(HopfieldNetwork net)
{
  double accuracy = 0;
  do
  {
    net.Parameters.TRANSMISSION_OPTION =
    HopfieldNetwork.TransmissionOptions.N_SPINS;
    List<ActivationCollection> sis = new List<ActivationCollection>();
    foreach (DeclarativeChunk dc in chunks)
    {
       ActivationCollection si = ImplicitComponentInitializer.NewDataSet();
       si.AddRange(dc, 1);
       sis.Add(si);
    }
    ImplicitComponentInitializer.Encode(net, sis);
    net.Parameters.TRANSMISSION_OPTION =
    HopfieldNetwork.TransmissionOptions.LET_SETTLE;
    accuracy = ImplicitComponentInitializer.Encode(net, sis, testOnly: true);
  } while (accuracy < 1);
}
  • Inicializa 5 regras associativas no formato: Se padrão X, conclua padrão X+1
static void SetupRules(Agent reasoner)
{
  for (int i = 0; i < chunks.Count - 1; i++)
  {
    RefineableAssociativeRule ar = 
    AgentInitializer.InitializeAssociativeRule(reasoner, 
    RefineableAssociativeRule.Factory, chunks[i + 1]);
    ar.GeneralizedCondition.Add(chunks[i], true);
    reasoner.Commit(ar);
  }
}
  • Executa a ação. Nesse ponto adicionamos ruído de 40% (zerando 40% dos valores) na entrada.
static void DoReasoning(Agent reasoner)
{
  int correct = 0;
  foreach (DeclarativeChunk dc in chunks)
  {
    ActivationCollection si = ImplicitComponentInitializer.NewDataSet();
    int count = 0;
    foreach (DimensionValuePair dv in dvs)
    {
      if (((double)count / (double)dc.Count < (1 - noise)))
      {
        if (dc.Contains(dv))
        {
          si.Add(dv, 1);
          ++count;
        }
        else
          si.Add(dv, 0);
      }
      else
        si.Add(dv, 0);
    }
    Console.WriteLine("Input to reasoner:\r\n" + si);
    Console.WriteLine("Output from reasoner:");
    var o = reasoner.NACS.PerformReasoning(si);
    foreach (var i in o)
    {
      Console.WriteLine(i.CHUNK);
      if (i.CHUNK == dc)
        correct++;
    }
  }
  Console.WriteLine("Retrieval Accuracy: " + 
  (int)(((double)correct / (double)chunks.Count) * 100) + "%");
}

Com isso podemos ver a execução e seu resultado com 100% de precisão nas telas abaixo.

 

 

 

Referencias:

Material do Professor Gudwin: http://faculty.dca.fee.unicamp.br/gudwin/sites/faculty.dca.fee.unicamp.br.gudwin/files/ia006/Clarion.pdf

R. Sun, The motivational and metacognitive control in CLARION. In: W. Gray (ed.), Modeling Integrated Cognitive Systems. Oxford University Press, New York. 2007.

 

 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer