Esta aula, irá estudar e experênciar a utilização da arquitetura cognitiva artificial CLARION como um sistema de controle em um ambiente virtual. Para execução desta aula, será utilizado os seguintes códigos fontes de softwares:
O DemoCLARION é um software que implementa o conceito de mente artificial que controla Animats presente no ambiente virtual proposto pelo software WorldServer3D. Ele utiliza-se da arquitetura cognitiva artificial denominada de CLARION, que foi desenvolvida pelo Professor Run Son na Universidade de Columbia em Missouri - EUA. Resumidamente, o CLARION serve como controlador do Animat no mundo virtual.
O Clarion apresenta dois tipos de cognição que são nas formas Implícita e Explícita. A Explicita é projetada/executada através de Regras de Produção e Chunk das mesmas, produzindo assim um resultado esperado daquela regra selecionada. A Implícita é projeta/executada através da utilização das Redes Neurais Artificiais que por sua vez apresentam um treinamento supervisionado através das tecnicas Hopfield e BackPropagation. Desta forma, o Clarion apresenta estrutura capaz de gerar comportamento através do "Action Centered Subsystem" e conhecimento através do "Non-Action Centered Subsystem" que podem ser das cognição Explícita ou Implícita.
Inicialmente, a classe principal do ClarionDEMO é a classe "Main". Portanto, nesta classe onde gera-se os objetos referente à comunicação(WorldServer) com o WorldServer3D, ClarionAgent que é o objeto que implementa a mente artificial usando Clarion. Além das implementações presentes no ClarionDEMO, necessitou-se de desenvolver uma "Thread" para gerar "Thing" dentro no WorldServer3D, que no caso são objetos do tipo "Foods" e "Jewels". Esta é executada a cada 30 segundos, onde gera os objetos através dos métodos seguir: "generateJewels()" e "generateJewels()". Os valores referente á quantidade de objetos e a posição dos objetos são determinados de forma randômica como é demonstrado no código fonte abaixo.
private void Run(object obj){
while (true) {
this.generateJewels ();
this.generateFoods ();
Thread.Sleep (30000);
}
}
private void generateJewels(){
Random random = new Random ();
int count = random.Next (5, 15);
for (int i=0; i < count; i++) {
int posX = random.Next (100, 700);
int posY = random.Next (100, 500);
worldServer.NewJewel (random.Next (0, 6), posX, posY);
}
}
private void generateFoods(){
Random random = new Random ();
int count = random.Next (1, 5);
for (int i=0; i < count; i++) {
int posX = random.Next (100, 700);
int posY = random.Next (100, 500);
worldServer.NewFood(1, posX, posY);
}
}
O método "Run()" demonstrado a cima é a implementação de uma "Thread" que é executada, que por sua vez, gera os objetos "Food" e "Jewel". Pose-se observar que tanto no método "generateFoods()" quanto no "generateJewels()" ambos apresentam a posições randômica dos objetos no WorldServer3D, que são de 100 até 700 para o eixo "X" e de 100 até 500 para o eixo "Y". Isso ocorre porque no WorldServer3D o mapa do ambiente é de 800 por 600 e além do tamanho que restringe as posições dos objetos, o mapa apresenta muros que são gerados na execução do ClarionDEMO. Por padrão, existe também um numero randômico de objetos presentes em cena que para o objeto do tipo "Food" é de 1 até 5 já para objetos do tipo "Jewels" é uma quantidade que varia entre 5 á 15 "Jewels" geradas por ciclo na "Thread" Run. Continuando na Classe "Main", necessitou-se da implementação para novas ações que agente Clarion pode tomar são: DO_NOTHING, ROTATE, MOVE_THING e GET_THING. Essas constantes de decisões representam as possibilidades das tomadas de decisão que o agente pode resultar de seu processo de cognição. Pode-se perceber que para ações a serem executadas utiliza-se do enum "ClarionAgentActionType" que está dentro na classe "ClarionAgent".
void agent_OnNewExternalActionSelected(ClarionAgentActionType externalAction)
{ Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
if (worldServer != null && worldServer.IsConnected)
{
switch (externalAction)
{
case ClarionAgentActionType.DO_NOTHING:
agent.Abort (true);
break;
case ClarionAgentActionType.ROTATE:
double newprad = prad + Math.PI/1000;
while (newprad > Math.PI) newprad -= 2 * Math.PI;
while (newprad < - Math.PI) newprad += 2 * Math.PI;
worldServer.SendSetAngle(creatureId, 0, 0, newprad);
break;
case ClarionAgentActionType.MOVE_THING:
worldServer.SendSetGoTo(creatureId, 1, 1, agent.getCurrentThing().X1, agent.getCurrentThing().Y1);
break;
case ClarionAgentActionType.GET_THING:
if(agent.getCurrentThing().CategoryId == Thing.CATEGORY_JEWEL)
worldServer.SendSackIt(creatureId, agent.getCurrentThing().Name);
else
worldServer.SendEatIt(creatureId, agent.getCurrentThing().Name);
break;
default:
break;
}
}
}
No código demonstrado acima apresenta ações como:
As modificações fundamentais foram desenvolvidas na classe "ClarioAgent". Nas primeiras linhas esta a definição do enum "ClarionAgentActionType", que é utilizado pela classe "Main" como demonstrado anteriormente. Em seguida, são definadas constantes para determinar as dimensões das entradas sensorial do Clarion, juntamente com o obejeto Sack. Mais adiante estão as declarações das entradas sensoriais e as respectivas saídas, conforme o código abaixo.
public enum ClarionAgentActionType
{
DO_NOTHING,
ROTATE,
MOVE_THING,
GET_THING
}
public class ClarionAgent
{
#region Constants
private String SENSOR_VISUAL_DIMENSION = "VisualSensor";
private String ROTATE_MOVE_THING = "MoveRotateThing";
private String GET_THING = "GetThing";
private String NOTHING = "Nothing";
private Thing targetThing = null;
#endregion
..........
public Sack sack;
..........
#region Perception Input
private DimensionValuePair InputNothing;
private DimensionValuePair InputGetThing;
private DimensionValuePair InputMoveRotateThing;
#endregion
#region Action Output
private ExternalActionChunk OutputNothing;
private ExternalActionChunk OutputRotateClockwise;
private ExternalActionChunk OutputMoveThing;
private ExternalActionChunk OutputGetThing;
#endregion
..........
}
Já no construtor da classe, foram instanciados o agente, as entradas e as saidas do agente Clarion juntamente com a Thread que dá o inicio do processo de treinamento e resposta do agente. Resumidamente o treinamento é um processo de apresentação das entradas sensoriais para o agente, consequentemente um processo adaptativo/supervisionado de saidas desejadas e obtenção da resposta correspondente as entradas corrente. Segue o código fonte do construtor da classe.
public class ClarionAgent
{
........
#region Constructor
public ClarionAgent()
{
// Initialize the agent
CurrentAgent = World.NewAgent("Current Agent");
// Initialize Input Information
InputNothing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, NOTHING);
InputMoveRotateThing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, ROTATE_MOVE_THING);
InputGetThing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, GET_THING);
// Initialize Output actions
OutputRotateClockwise = World.NewExternalActionChunk(ClarionAgentActionType.ROTATE.ToString());
OutputMoveThing = World.NewExternalActionChunk (ClarionAgentActionType.MOVE_THING.ToString());
OutputGetThing = World.NewExternalActionChunk (ClarionAgentActionType.GET_THING.ToString());
OutputNothing = World.NewExternalActionChunk(ClarionAgentActionType.DO_NOTHING.ToString());
//Create thread to simulation
runThread = new Thread(RunThread);
Console.WriteLine("Agent started");
}
#endregion
........
}
No projeto, a cognição do Clarion é baseada em chunks de regras fixas. O código a seguir demonstra o método "SetupACS()" que é responsável por instânciar as regras e submete-las ao agente Clarion. No caso desse projeto são 4 regras como: ruleRotate, ruleMoveThing, ruleGetThing e ruleNothing. Pode-se perceber no código que existe somente três entradas para quatro saídas possíveis, que neste caso significa que existe uma entrada que abrange duas sáidas que são "rotate" e "moveThing"(entrada correspondente "InputMoveRotateThing"), quando a entrada está em ativação minima(MIN_ACTIVATION = 0) é realizado o chunk para a regra "ruleRotate", se a ativação for maxíma(MAX_ACTIVATION = 1) é realizado o chunk para "ruleMoveThing". Isso é perceptivel nos métodos "delegate's" que são responsavel por ativar ou não a regra que mesmo pertence.
private void SetupACS()
{
// Create Colission Wall Rule
SupportCalculator avoidRotateSupportCalculator = FixedRuleDelegateToRotate;
FixedRule ruleRotate = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputRotateClockwise, avoidRotateSupportCalculator);
// Commit this rule to Agent (in the ACS)
CurrentAgent.Commit(ruleRotate);
SupportCalculator moveThingSupportCalculator = FixedRuleDelegateToMoveToThing;
FixedRule ruleMoveThing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputMoveThing, moveThingSupportCalculator);
// Commit this rule to Agent (in the ACS)
CurrentAgent.Commit(ruleMoveThing);
SupportCalculator getThingSupportCalculator = FixedRuleDelegateToGetThing;
FixedRule ruleGetThing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputGetThing, getThingSupportCalculator);
// Commit this rule to Agent (in the ACS)
CurrentAgent.Commit(ruleGetThing);
SupportCalculator nothingSupportCalculator = FixedRuleDelegateToNothing;
FixedRule ruleNothing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputNothing, nothingSupportCalculator);
// Commit this rule to Agent (in the ACS)
CurrentAgent.Commit(ruleNothing);
// Disable Rule Refinement
CurrentAgent.ACS.Parameters.PERFORM_RER_REFINEMENT = false;
// The selection type will be probabilistic
CurrentAgent.ACS.Parameters.LEVEL_SELECTION_METHOD = ActionCenteredSubsystem.LevelSelectionMethods.STOCHASTIC;
// The selection of the action will be fixed (not variable) i.e. only the statement defined above.
CurrentAgent.ACS.Parameters.LEVEL_SELECTION_OPTION = ActionCenteredSubsystem.LevelSelectionOptions.FIXED;
// Define Probabilistic values
CurrentAgent.ACS.Parameters.FIXED_FR_LEVEL_SELECTION_MEASURE = 1;
CurrentAgent.ACS.Parameters.FIXED_IRL_LEVEL_SELECTION_MEASURE = 0;
CurrentAgent.ACS.Parameters.FIXED_BL_LEVEL_SELECTION_MEASURE = 0;
CurrentAgent.ACS.Parameters.FIXED_RER_LEVEL_SELECTION_MEASURE = 0;
}
..........
private double FixedRuleDelegateToRotate(ActivationCollection currentInput, Rule target)
{
return ((currentInput.Contains(InputMoveRotateThing, CurrentAgent.Parameters.MIN_ACTIVATION))) ? 1.0 : 0.0;
}
private double FixedRuleDelegateToMoveToThing(ActivationCollection currentInput, Rule target)
{
return ((currentInput.Contains(InputMoveRotateThing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}
private double FixedRuleDelegateToGetThing(ActivationCollection currentInput, Rule target)
{
return ((currentInput.Contains(InputGetThing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}
private double FixedRuleDelegateToNothing(ActivationCollection currentInput, Rule target)
{
return ((currentInput.Contains(InputNothing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}
..........
Os metódos a seguir são primordiais para a execução da captura das "Jewels" presente no leaflet do agent. Os métodos "isCompleted()" e "MakePerceptionFromSensorialInput()" são responsáveis por:
private SensoryInformation MakePerceptionFromSensorialInput(IList sensorialInformation)
{
// New sensory information
SensoryInformation si = World.NewSensoryInformation(CurrentAgent);
//Console.WriteLine(sensorialInformation);
Creature c = (Creature) sensorialInformation.Where(item => (item.CategoryId == Thing.CATEGORY_CREATURE)).First();
bool thingAction = false;
List listOfjewel = sensorialInformation.Where (item => (item.CategoryId == Thing.CATEGORY_JEWEL)).OrderBy(x=>x.DistanceToCreature).ToList();
List listOfFood = sensorialInformation.Where (item => (item.CategoryId == Thing.CATEGORY_NPFOOD)).OrderBy(x=>x.DistanceToCreature).ToList();
c.PrintLeaflet();
double rotateMoveThingActivationValue = 0;
double getThingActivationValue = 0;
double nothingActivationValue = 0;
foreach (Thing thing in listOfjewel) {
thingAction = !isCompleted (c, thing);
if (thingAction) {
targetThing = thing;
break;
}
}
foreach (Thing thing in listOfFood) {
if (thingAction) {
if (targetThing.DistanceToCreature > thing.DistanceToCreature) {
targetThing = thing;
break;
}
} else {
targetThing = thing;
thingAction = true;
break;
}
}
if (thingAction) {
if (targetThing.DistanceToCreature >= 25) {
rotateMoveThingActivationValue = 1;
} else {
getThingActivationValue = 1;
}
}
else {
if (!isCompleted (c))
rotateMoveThingActivationValue = 0;
else
nothingActivationValue = 1;
}
si.Add(InputMoveRotateThing, rotateMoveThingActivationValue);
si.Add(InputGetThing, getThingActivationValue);
si.Add(InputNothing, nothingActivationValue);
return si;
}
public bool isCompleted(Creature c){
string[] colors = {"White", "Green", "Blue", "Red", "Magenta", "Yellow"};
bool result = false;
foreach(string color in colors){
List listLeafletItem = c.leaflets.SelectMany (x => x.items).ToList ().Where (i => i.itemKey == color).ToList ();
LeafletItem leafletItem = new LeafletItem ();
leafletItem.itemKey = color;
foreach (LeafletItem item in listLeafletItem) {
leafletItem.totalNumber += item.totalNumber;
}
result = leafletItem.totalNumber == this.getListOfLeafletCollected ().Where (i => i.itemKey == leafletItem.itemKey).FirstOrDefault ().collected ? true : false;
if(!result)
break;
}
return result;
}
public bool isCompleted(Creature c, Thing jewel){
List listLeafletItem = c.leaflets.SelectMany (x => x.items).ToList ().Where (i => i.itemKey == jewel.Material.Color).ToList ();
LeafletItem leafletItem = new LeafletItem ();
leafletItem.itemKey = jewel.Material.Color;
foreach (LeafletItem item in listLeafletItem) {
leafletItem.totalNumber += item.totalNumber;
}
return leafletItem.totalNumber == this.getListOfLeafletCollected ().Where (i => i.itemKey == leafletItem.itemKey).FirstOrDefault ().collected ?
true : false;
}
public List getListOfLeafletCollected(){
List collected = new List();
collected.Add (new LeafletItem ("Green", 0, sack.green_crystal));
collected.Add (new LeafletItem ("Red", 0, sack.red_crystal));
collected.Add (new LeafletItem ("Blue", 0, sack.blue_crystal));
collected.Add (new LeafletItem ("Yellow", 0, sack.yellow_crystal));
collected.Add (new LeafletItem ("Magenta", 0, sack.magenta_crystal));
collected.Add (new LeafletItem ("White", 0, sack.white_crystal));
return collected;
}
No método "MakePerceptionFromSensorialInput()" são definidas as circunstâncias á qual as ações que a criatura deve tomar. Os três primeiros passos são correspondente á seleção de objetos a capturar. Os demais são respondente á seleção da ação ser tomada. Conforme o algoritmo apresentado acima segue á lógica abaixo:
Na execução dos demos no ambiente WorldServer3D percebeu-se que o DemoCLARION se movimento em busca das "Jewels" que estavam dentro de seu Leaflet. Já o DemoSOAR não conseguiu se movimentar para encontrar os seus "Jewels". Nesta comparação mostrou algumas diferenças entre as arquiteturas propostas. Até esse momento a arquitetura cognitiva SOAR tem uma leve vantagem sobre a CLARION pelo simples fato que o SOAR está mais desenvolvida com relação a sua estrutura complexa. Já o CLARION é menos desenvolvido, porem é de implementação mais fácil em comparação a forma de trabalho do SOAR. O Link abaixo contem o código fonte do EduardoFroesCLARION e em seguida a comparação entre as duas arquiteturas conigtivas no WorldServer3D.
Segue o video da movimentação do SOAR(Primeiro) e CLARION(Segundo).
Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer