{ "cells": [ { "cell_type": "markdown", "id": "e01e06b2e79cccf3", "metadata": {}, "source": [ "# Walkthrough Tutorial: Modelling Emergent Communication\n", "\n", "
\n", "\n", "*This walkthrough tutorial accompanies Section 4.3 of the following paper:*\n", "\n", "Authors. (submitted). PyFCG: Fluid Construction Grammar in Python. \n", "\n", "
\n", "\n", "This tutorial exemplifies the primary use case of FCG: implementing the linguistic capability of autonomous agents in agent-based models of emergent communication. In such experiments, agents start out with an empty grammar and gradually build up their linguistic knowledge as they take part in situated communicative interactions with other agents in the population. This tutorial presents a PyFCG-powered implementation of a neuro-symbolic version of the seminal naming game experiment, in which a population of agents converges on a naming convention for referring to entities that they observe in their environment. The tutorial showcases the integration of PyFCG with other widely used Python libraries, including PyTorch, NumPy and scikit-learn.\n", "\n" ] }, { "cell_type": "markdown", "id": "fc28020f", "metadata": {}, "source": [ "## Initialisation" ] }, { "cell_type": "markdown", "id": "8ab8b066", "metadata": {}, "source": [ "The first time you run this notebook, you might need to install a number of Python modules in your environment:" ] }, { "cell_type": "code", "execution_count": null, "id": "4ff621f74bb05072", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:31:07.234715Z", "start_time": "2025-10-16T07:31:03.440157Z" } }, "outputs": [], "source": [ "# Ensure to upgrade to the latest version of pyfcg\n", "! pip install --upgrade pyfcg\n", "\n", "# Machine learning / neural networks\n", "! pip install numpy\n", "! pip install torch\n", "! pip install scikit-learn\n", "\n", "# Visualisation\n", "! pip install matplotlib\n", "! pip install alive_progress\n", "\n", "# Downloading benchmark data\n", "! pip install kagglehub" ] }, { "cell_type": "markdown", "id": "05860523", "metadata": {}, "source": [ "We can now load Python modules and initialise PyFCG:" ] }, { "cell_type": "code", "execution_count": 1, "id": "18722fd5241cbd52", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:46:50.301336Z", "start_time": "2025-10-16T07:46:50.290366Z" } }, "outputs": [], "source": [ "import pyfcg as fcg\n", "import random\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import torch\n", "from torch import nn\n", "from torch.optim import Adam\n", "from torch.utils.data import TensorDataset\n", "from sklearn.model_selection import train_test_split\n", "import os\n", "import alive_progress\n", "import kagglehub\n", "\n", "# force_download=True ensures that you have the latest version of the underlying fcg-go software,\n", "# (you only need to do this once, afterwards you can set force_download to False)\n", "fcg.init(force_download=False)" ] }, { "cell_type": "markdown", "id": "e32a906a", "metadata": {}, "source": [ "If your device supports `cuda` or `mps`, you can use your GPU for dealing with neural networks. If not, everything will run on your CPU:" ] }, { "cell_type": "code", "execution_count": 2, "id": "dc1c343716fecddf", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:31:41.854119Z", "start_time": "2025-10-16T07:31:41.825319Z" } }, "outputs": [ { "data": { "text/plain": [ "device(type='mps')" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"mps\" if torch.mps.is_available() else \"cpu\")\n", "DEVICE\n", "\n", "# If you have CUDA or MPS, but your graphical card is very limited in memory, you might still need to use 'cpu'Adam\n", "# DEVICE = 'cpu'" ] }, { "cell_type": "markdown", "id": "3c412360", "metadata": {}, "source": [ "## A World of Faces" ] }, { "cell_type": "markdown", "id": "209177d5", "metadata": {}, "source": [ "A first step in setting up a grounded naming game experiment concerns the creation of a world in which the agents will operate. In this case, the world consists of 2000 mugshots of 40 individual people, adopted from the [Augmented Olivetti Faces Dataset](https://www.kaggle.com/datasets/martininf1n1ty/olivetti-faces-augmented-dataset?resource=download)." ] }, { "cell_type": "markdown", "id": "48720485", "metadata": { "vscode": { "languageId": "markdown" } }, "source": [ "We first define a general class `GroundedNGWorld`, and a few methods for sampling and visualising scenes:" ] }, { "cell_type": "code", "execution_count": 3, "id": "37dd242e87cc2061", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:43:35.331202Z", "start_time": "2025-10-16T07:43:35.323889Z" } }, "outputs": [], "source": [ "class GroundedNGWorld:\n", " \"\"\"\n", " A general class for creating worlds that can be used in Grounded Naming Game experiments.\n", " \"\"\"\n", "\n", " def __init__(self, train_data, train_targets, test_data, test_targets):\n", " \"\"\" Initialise train and test data and labels, and create dictionaries with\n", " the labels as keys and the corresponding data instances as values.\"\"\"\n", " self.train_data = train_data\n", " self.train_targets = train_targets\n", " self.test_data = test_data\n", " self.test_targets = test_targets\n", "\n", " self.train_per_individual = sort_data_per_individual(self.train_data, self.train_targets)\n", " self.test_per_individual = sort_data_per_individual(self.test_data, self.test_targets)\n", "\n", " def sample_scene_with_unique_target(self, nr_of_entities=5, train=False):\n", " \"\"\" Sample a new scene consisting of a number of entities\n", " (nr_of_entities), each of a different type, which are draw from the test set (unless train is set to True).\n", " One entity is selected as the target of the conversation, and its index is returned as a second return value.\"\"\"\n", " \n", " scene = []\n", " if train is True:\n", " individuals = self.train_per_individual\n", " else:\n", " individuals = self.test_per_individual\n", "\n", " remaining_individuals = list(individuals.keys())\n", "\n", " target_type = random.choice(remaining_individuals)\n", " target = random.choice(individuals[target_type])\n", " remaining_individuals.remove(target_type)\n", "\n", " \n", "\n", "\n", " for i in range(1, nr_of_entities):\n", " # Randomly pick a type\n", " distractor_type = random.choice(remaining_individuals)\n", " # Randomly pick a data instance for the chosen type\n", " scene.append(random.choice(individuals[distractor_type]))\n", " remaining_individuals.remove(distractor_type)\n", "\n", " scene.append(target)\n", " random.shuffle(scene)\n", "\n", " # Retrieve the target index from the scene:\n", " scene_as_list = [arr.tolist() for arr in scene]\n", " target_index = scene_as_list.index(target.tolist())\n", "\n", " return scene, target_index\n", "\n", "\n", "def sort_data_per_individual(images, labels):\n", " \"\"\"Group images based on their target label.\"\"\"\n", " per_individual = {}\n", " for i in range(0, len(images)):\n", " if labels[i] in per_individual.keys():\n", " per_individual[labels[i].item()].append(images[i])\n", " else:\n", " per_individual[labels[i].item()] = [images[i]]\n", " return per_individual\n", "\n", "\n", "def visualise_image(image):\n", " \"\"\"Show image visually in grayscale.\"\"\"\n", " img = image.squeeze()\n", " plot = plt.imshow(img, cmap='gray')\n", " return plot\n", "\n", "\n", "def visualise_scene(scene):\n", " \"\"\"Show scene visually in grid.\"\"\"\n", " plt.figure(figsize=(11, 18))\n", " for index, image in enumerate(scene):\n", " plt.subplots_adjust(bottom=0.3, right=0.8, top=0.5)\n", " ax = plt.subplot(3, 5, index + 1)\n", " ax.axis('off')\n", " ax.set_title('entity ' + str(index), fontdict={'fontsize': 8})\n", " visualise_image(image)" ] }, { "cell_type": "markdown", "id": "6c7c66f5", "metadata": {}, "source": [ "We download the Olivetti augmented faces dataset from Kaggle:" ] }, { "cell_type": "code", "execution_count": 4, "id": "4f338fbb", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:43:40.255678Z", "start_time": "2025-10-16T07:43:39.894401Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Path to dataset files: /Users/paul/.cache/kagglehub/datasets/martininf1n1ty/olivetti-faces-augmented-dataset/versions/2\n" ] } ], "source": [ "olivetti_dataset_path = kagglehub.dataset_download(\"martininf1n1ty/olivetti-faces-augmented-dataset\")\n", "print(\"Path to dataset files:\", olivetti_dataset_path)" ] }, { "cell_type": "markdown", "id": "df2fe7c7", "metadata": {}, "source": [ "We subclass from `GroundedNGWorld` and implement functionality specifically for dealing with the Olivetti dataset. Essentially, the code below creates train and test splits, and specifies how to represent and train a neural module that classifies mugshots into individuals. " ] }, { "cell_type": "code", "execution_count": 5, "id": "6f8e93c1f9b012aa", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T07:51:36.225279Z", "start_time": "2025-10-16T07:51:36.215631Z" } }, "outputs": [], "source": [ "class OlivettiLgData(GroundedNGWorld):\n", " \"\"\"\n", " Class holding augmented Olivetti dataset for language games.\n", " \"\"\"\n", "\n", " def __init__(self):\n", " \"\"\"Initialise a new Olivetti world of faces for playing language games.\"\"\"\n", " images = np.load(olivetti_dataset_path + '/augmented_faces.npy').reshape(-1, 1, 64, 64)\n", " targets = np.load(olivetti_dataset_path + '/augmented_labels.npy')\n", "\n", " # 80% of the data set is used for training, the remaining 20% for testing:\n", " train_images, test_images, train_labels, test_labels = train_test_split(images, targets, test_size=0.2, random_state=42)\n", "\n", " # Turn numpy arrays into tensors for working with PyTorch:\n", " train_images = torch.tensor(train_images, dtype=torch.float32)\n", " test_images = torch.tensor(test_images, dtype=torch.float32)\n", " train_labels = torch.tensor(train_labels)\n", " test_labels = torch.tensor(test_labels)\n", "\n", " # Call superclass initialisation for the Olivetti dataset:\n", " super().__init__(train_images, train_labels, test_images, test_labels)\n", "\n", "\n", "class DescribeNetOlivetti(nn.Module):\n", " \"\"\"\n", " Neural module that classifies faces into individuals.\n", " \"\"\"\n", "\n", " def __init__(self, input_dim=1,hidden_dim=512,output_dim=40,kernel_size=3,stride=(1,1),pooling_size=2):\n", " super().__init__()\n", "\n", " self.initialize_weights()\n", " \n", " self.conv1 = nn.Conv2d(\n", " in_channels=input_dim,\n", " out_channels=hidden_dim,\n", " kernel_size=kernel_size,\n", " stride=stride,\n", " padding=1,\n", " device=DEVICE,\n", " dtype=torch.float32\n", " )\n", "\n", " self.conv2 = nn.Conv2d(\n", " in_channels=hidden_dim,\n", " out_channels=hidden_dim,\n", " kernel_size=kernel_size,\n", " stride=stride,\n", " padding=1,\n", " device=DEVICE,\n", " dtype=torch.float32\n", " )\n", "\n", " self.conv3 = nn.Conv2d(\n", " in_channels=hidden_dim,\n", " out_channels=hidden_dim,\n", " kernel_size=kernel_size,\n", " stride=stride,\n", " padding=1,\n", " device=DEVICE,\n", " dtype=torch.float32\n", " )\n", "\n", " self.maxpooling = nn.MaxPool2d(\n", " kernel_size=pooling_size,\n", " stride=pooling_size\n", " )\n", "\n", " self.flat = nn.Flatten()\n", " self.relu = nn.ReLU()\n", "\n", " self.dense_layer = nn.Linear(\n", " in_features=hidden_dim * 32 * 32,\n", " out_features=hidden_dim,\n", " device=DEVICE,\n", " dtype=torch.float32\n", " )\n", "\n", " self.output_layer = nn.Linear(\n", " in_features=hidden_dim,\n", " out_features=output_dim,\n", " device=DEVICE,\n", " dtype=torch.float32\n", " )\n", "\n", " def initialize_weights(self):\n", " \"\"\"Randomly initialise the weights of the network.\"\"\"\n", " for layer in self.children():\n", " if isinstance(layer, nn.Conv2d):\n", " nn.init.xavier_uniform_(layer.weight)\n", " if layer.bias is not None:\n", " nn.init.zeros_(layer.bias)\n", " elif isinstance(layer, nn.Linear):\n", " nn.init.xavier_uniform_(layer.weight)\n", " nn.init.zeros_(layer.bias)\n", "\n", " def forward(self, x):\n", " \"\"\"Forward pass through the network.\"\"\"\n", " x = self.conv1(x)\n", " x = self.relu(x)\n", " x = self.conv2(x)\n", " x = self.relu(x)\n", " x = self.conv3(x)\n", " x = self.relu(x)\n", " x = self.maxpooling(x)\n", " x = self.flat(x)\n", " x = self.dense_layer(x)\n", " x = self.relu(x)\n", " x = self.output_layer(x)\n", "\n", " return x\n", "\n", " def _train(self, training_features, training_labels,\n", " validation_features, validation_labels,\n", " nr_of_epochs, batch_size=32, learning_rate=0.00001):\n", " \"\"\"Train the network.\"\"\"\n", "\n", " optimizer = Adam(self.parameters(), lr=learning_rate)\n", " loss_fn = nn.CrossEntropyLoss()\n", "\n", " train_split = TensorDataset(training_features, training_labels)\n", " train_loader = torch.utils.data.DataLoader(train_split, batch_size=batch_size, shuffle=True)\n", " \n", " print('Starting training for new agent.')\n", " \n", " for epoch in range(nr_of_epochs):\n", " # Train on all instances from the train loader:\n", " self.train()\n", " for images, labels in train_loader:\n", " images, labels = images.to(DEVICE), labels.to(DEVICE)\n", " optimizer.zero_grad() # Reset gradients\n", " outputs = self(images) # Forward pass\n", " train_loss = loss_fn(outputs, labels) # Compute loss\n", " train_loss.backward() # Backward pass\n", " optimizer.step() # Update weights\n", "\n", " _, predicted = torch.max(outputs, 1)\n", " correct = (predicted == labels).sum().item()\n", " accuracy = correct / labels.size(0)\n", "\n", " # Evaluate the current model on the validation set:\n", " self.eval()\n", " with torch.no_grad():\n", "\n", " validation_features = validation_features.to(DEVICE)\n", " validation_labels = validation_labels.to(DEVICE)\n", " \n", " validation_ypred = self(validation_features)\n", " val_loss = loss_fn(validation_ypred, validation_labels)\n", "\n", " _, val_predicted = torch.max(validation_ypred, 1)\n", " val_correct = (val_predicted == validation_labels).sum().item()\n", " val_accuracy = val_correct / validation_labels.size(0)\n", "\n", " if epoch % 1 == 0 or epoch == nr_of_epochs - 1:\n", " print(\n", " f\"Epoch {epoch}: Training Loss = {train_loss.item():.4f} | Validation Loss = {val_loss.item():.4f} | Training Acc = {accuracy:.4f} | Validation Acc = {val_accuracy:.4f}\")\n" ] }, { "cell_type": "markdown", "id": "85e3180c", "metadata": {}, "source": [ "## A Population of Agents" ] }, { "cell_type": "markdown", "id": "997a90fb", "metadata": {}, "source": [ "We define our agents to be instances of a new class `GroundedNGAgent` that subclasses from PyFCG's `fcg.Agent` class. The agents are thereby initialised with an empty grammar and inherit a collection of methods for interacting with instances of the `fcg.Grammar` and `fcg.Construction` classes. \n" ] }, { "cell_type": "code", "execution_count": 6, "id": "c617447b73855471", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T08:03:03.061220Z", "start_time": "2025-10-16T08:03:03.048987Z" } }, "outputs": [], "source": [ "class GroundedNGAgent(fcg.Agent):\n", " \"\"\"\n", " Agent subclasses from fcg.Agent.\n", " \"\"\"\n", "\n", " def __init__(self, configuration):\n", " \"\"\"Initialisation of agents upon creation.\"\"\"\n", " self.discourse_role = None\n", " self.task = None\n", " self.configuration = configuration\n", " self.neural_modules = {}\n", " self.utterance = None\n", " self.concept = None\n", " self.communicated_successfully = False\n", " self.applied_cxn = None\n", " self.competitor_cxns = []\n", "\n", " # We call the init method of the fcg.Agent superclass\n", " # to initialise the agent's grammar\n", " super().__init__()\n", "\n", " # We create a mapping for the agent's categories\n", " # so it has its own internal system of categories\n", " agent_id_index = self.id.find(\"-\") + 1\n", " agent_id_number = int(self.id[agent_id_index:])\n", " self.category_mapping = {}\n", " for i in range (0, configuration['nr_of_categories']):\n", " self.category_mapping[i] = i * agent_id_number\n", "\n", " def clear_for_interaction(self):\n", " \"\"\"Initialisation of agents at the start of an interaction. \"\"\"\n", " self.discourse_role = None\n", " self.task = None\n", " self.utterance = None\n", " self.applied_cxn = None\n", " self.concept = None\n", " self.communicated_successfully = False\n", "\n", " def learn_neural_module(self, module_name, i, world):\n", " \"\"\"Learn a neural module for the agent based on the world.\"\"\"\n", "\n", " # Create an instance of the image classifier model\n", " network = eval(module_name + '()').to(DEVICE)\n", " # Train the network for nr_of_epochs epochs with learning_rate\n", " network._train(training_features=world.train_data,\n", " training_labels=world.train_targets,\n", " validation_features=world.test_data,\n", " validation_labels=world.test_targets,\n", " nr_of_epochs=self.configuration['nr_of_epochs'],\n", " learning_rate=self.configuration['learning_rate'])\n", " # Store the learned network in the models directory\n", " os.makedirs('./models/', exist_ok=True)\n", " torch.save(network.state_dict(), './models/' + module_name + '-' + str(i) + '.pt')\n", " # Store the learnt network in the agent's inventory of neural_modules\n", " self.neural_modules[module_name] = network\n", "\n", " def load_neural_module(self, module_name, i):\n", " \"\"\"Load pretrained neural module in agent.\"\"\"\n", "\n", " # Create an instance of the image classifier model\n", " network = eval(module_name +'()').to(DEVICE)\n", " # Load the neural network\n", " network.load_state_dict(torch.load('./models/' + module_name + '-' + str(i) + '.pt', map_location=DEVICE))\n", " # Store the loaded network in the agent's inventory of neural_modules\n", " self.neural_modules[module_name] = network\n", "\n", " def comprehend(self, utterance):\n", " \"\"\" Comprehend utterance, collect meaning, applied_cxn and competitors.\"\"\"\n", "\n", " # Calls PyFCG's comprehend all method to get all possible meanings\n", " # of the utterance according to the agent's grammar\n", " meanings, applied_cxn_names_per_meaning = self.comprehend_all(utterance)\n", " # The agent could not understand the utterance:\n", " if meanings == [None]:\n", " return None\n", " # The agent could understand the utterance, we store the construction it applied and the alternative cxns as competitors\n", " else:\n", " self.applied_cxn = self.find_cxn_by_name(applied_cxn_names_per_meaning[0][0])\n", " self.competitor_cxns = []\n", " for cxn_names in applied_cxn_names_per_meaning[1:]:\n", " self.competitor_cxns.append(self.find_cxn_by_name(cxn_names[0]))\n", " return meanings[0]\n", "\n", " def formulate(self, meaning):\n", " \"\"\"Formulate an utterance given a meaning. Use the highest-scored cxn and collect the competing constructions on the go.\"\"\"\n", "\n", " # Call PyFCG's formulate all method to get all possible ways\n", " # to formulate the meaning according to the agent's grammar\n", " utterances, applied_cxn_names_per_utterance = super().formulate_all(meaning)\n", " # The agent could not formulate the meaning with its grammar:\n", " if utterances == [None]:\n", " return None\n", " # The agent could formulate an utterance, we store the construction it applied and the alternative cxns as competitors\n", " else:\n", " self.utterance = utterances[0][0]\n", " self.applied_cxn = self.find_cxn_by_name(applied_cxn_names_per_utterance[0][0])\n", " self.competitor_cxns = []\n", " for cxn_names in applied_cxn_names_per_utterance[1:]:\n", " self.competitor_cxns.append(self.find_cxn_by_name(cxn_names[0]))\n", " return self.utterance\n", "\n", " def learn(self, form, meaning):\n", " \"\"\" Create a new cxn given a form and a meaning. \"\"\"\n", " new_cxn = fcg.Construction(name=form + '-cxn',\n", " conditional_pole=[[\"?name-unit\",\n", " {\"#meaning\": [[\"concept\", meaning]]},\n", " {\"#form\": [\n", " [\"sequence\", '\\\"' + form + '\\\"', \"?left\", \"?right\"]]}]],\n", " attributes={\"object\": meaning, \"name\": form, \"score\": 0.5})\n", " self.add_cxn(new_cxn)\n", " return new_cxn\n", "\n", " def reward(self):\n", " \"\"\" Reward through lateral inhibition. \"\"\"\n", " inc_delta = CONFIGURATION['cxn_positive_reward']\n", " dec_delta = CONFIGURATION['cxn_negative_reward']\n", " if self.communicated_successfully:\n", " # If success, reward the applied cxn and punish the competitors\n", " self.applied_cxn.increase_score(delta=inc_delta)\n", " for competitor in self.competitor_cxns:\n", " competitor.decrease_score(delta=dec_delta)\n", " # Delete cxns that reach a 0 score\n", " if competitor.get_score() <= 0.0:\n", " self.delete_cxn(competitor)\n", " else:\n", " # If failure, speaker punishes applied cxn\n", " if self.discourse_role == 'speaker':\n", " self.applied_cxn.decrease_score(delta=dec_delta)\n", " # Delete cxns that reach a 0 score\n", " if self.applied_cxn.get_score() <= 0.0:\n", " self.delete_cxn(self.applied_cxn)" ] }, { "cell_type": "markdown", "id": "3d8a0720", "metadata": {}, "source": [ "## Experiment" ] }, { "cell_type": "markdown", "id": "ef2266db", "metadata": {}, "source": [ "We also define a new experiment class `GroundedNGExperiment`. Three methods are associated to this class. The `run_prelinguistic_stage` method engages a prelinguistic stage during which each agent individually learns to tell people apart. Technically, each agent trains a neural network that learns to classify the bitmap images from a training portion of the dataset into 40 classes, i.e. one class per individual in the world. Agents can now quite reliably classify new mugshots in terms of individuals, but the labels that they have learnt to associate to these individuals will differ. For example, mugshots of a given individual might be classified into class 7 by one agent, and into class 34 by another. The labels are not conventional and can thereby not be used by the agents as such for exchanging information about appearances of individuals in their environment.\n", "\n", "After the prelinguistic stage, the agents in the population will participate in a series of situated communicative interactions, as implemented by the `run_series` method, during which they will converge upon a naming convention for communicating about new appearances of individuals. The `run_series` method iteratively calls the `run_interaction` method, which initiates a new communicative interaction as an instance of the `DescribeAndPointInteraction` class, makes the interaction happen by calling its `interact` method, and records its outcome. \n" ] }, { "cell_type": "code", "execution_count": 7, "id": "ed31d15a8c1c202b", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T09:34:10.008716Z", "start_time": "2025-10-16T09:34:10.000345Z" } }, "outputs": [], "source": [ "class GroundedNGExperiment():\n", " \"\"\"\n", " The GroundedNGExperiment class holds the population and world and\n", " defines methods to run (series of) communicative interactions.\n", " \"\"\"\n", "\n", " def __init__(self, configuration={}):\n", " \"\"\"Upon initialisation, the world and population are created.\"\"\"\n", "\n", " # The configuration passed to the experiment is merged with the default configuration.\n", " global CONFIGURATION\n", " self.configuration = fcg.merge_dicts(CONFIGURATION,configuration)\n", "\n", " # World and population are created.\n", " self.world = eval(self.configuration['world'])\n", " self.population = [GroundedNGAgent(self.configuration) for i in range(self.configuration['nr_of_agents'])]\n", " self.current_interaction = None\n", " # With a new experiment, we also reset the monitors\n", " MONITORS = {}\n", "\n", " def run_prelinguistic_stage(self, learn_prelinguistic_categorisation=None):\n", " \"\"\"Initialise neural modules of the agents in the population.\"\"\"\n", " train = self.configuration['learn_prelinguistic_categorisation']\n", " if learn_prelinguistic_categorisation is not None:\n", " train = learn_prelinguistic_categorisation\n", "\n", " for i in range(len(self.population)):\n", " agent = self.population[i]\n", " for module_name in self.configuration['neural_modules']:\n", " if train:\n", " agent.learn_neural_module(module_name, i, self.world)\n", " else:\n", " agent.load_neural_module(module_name, i)\n", "\n", " def run_interaction(self, silent=False):\n", " \"\"\" Create a new interaction, make it happen and record the outcome. \"\"\"\n", "\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

Starting a new interaction

\")\n", " fcg.add_element_to_web_interface(\"
\")\n", "\n", " # Choose 2 interacting agents at random\n", " self.interacting_agents = random.sample(self.population, 2)\n", " speaker = self.interacting_agents[0]\n", " listener = self.interacting_agents[1]\n", " speaker.clear_for_interaction()\n", " listener.clear_for_interaction()\n", " \n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"

Interacting agents:

\")\n", " fcg.add_element_to_web_interface(\"

\")\n", "\n", " # Determine interaction type\n", " interaction_type = self.configuration['interaction'] + '(self, speaker, listener)'\n", " # Create interaction object\n", " interaction = eval(interaction_type)\n", " self.current_interaction = interaction\n", "\n", " # Run actual interaction\n", " interaction.interact(speaker, listener, silent)\n", "\n", " # Record outcome\n", " interaction.record_communicative_success(self.interacting_agents)\n", " interaction.record_lexicon_size(self.population)\n", " interaction.record_conventionality(speaker, listener)\n", "\n", " def run_series(self, nr_of_interactions, silent=True):\n", " \"\"\"Run a series of interactions.\"\"\"\n", " with alive_progress.alive_bar(nr_of_interactions, force_tty=True) as bar:\n", " for i in range(nr_of_interactions):\n", " self.run_interaction(silent=silent)\n", " bar()\n", "\n", " def plot_results(self, window_size=100):\n", " pp = make_plot_points(MONITORS, window_size=window_size)\n", " # for each key in plot points (pp), create a plot\n", " fig, axes = plt.subplots(len(pp.keys()), figsize=(5, 7), sharex=True)\n", "\n", " for i, key in enumerate(pp.keys()):\n", " ax = axes[i]\n", " ax.plot(list(range(len(pp[key]))), pp[key], label=key)\n", " ax.grid()\n", " ax.legend()\n", "\n", " plt.show()" ] }, { "cell_type": "markdown", "id": "a74554b9", "metadata": {}, "source": [ "## Interaction" ] }, { "cell_type": "markdown", "id": "b4384df9", "metadata": {}, "source": [ "At the beginning of a new interaction, two agents are drawn from the population and assigned the roles of speaker and listener. At the same time, three mugshots that were not seen during the prelinguistic stage, and which depict three different people, are sampled from the world. These three images serve as the scene of the interaction and are made visible to both agents. One image from the scene is randomly selected to be the target of the conversation, and is disclosed to the speaker only. The task of the speaker will be to produce an utterance that draws the attention of the listener to the target entity and the task of the listener will be to point at it.\n", "\n", "The speaker first uses the neural network that it trained during the prelinguistic stage to conceptualise the target image in terms of an individual person, represented through one of the agent's internal classes. The speaker then calls its `formulate` method to retrieve its most entrenched construction that maps between this class and a linguistic form. If there exists no construction in the speaker's grammar that associates a linguistic form with this class, as will necessarily be the case at the beginning of the experiment, the speaker will call its `learn` method to invent such a construction. The linguistic form is then passed on to the listener, which calls its `comprehend` method to map the observed form to one of its own internal classes. The listener then uses the neural network that it trained during the prelinguistic stage to identify the corresponding individual in the environment. Finally, the listener points at the image in which it recognised the individual, after which the speaker provides feedback by pointing at the target image. The agents have achieved communicative success if the listener could correctly identify the target entity, and both agents will positively or negatively reward their constructions at the end of the interaction, by adapting the entrenchment scores of their constructions based on whether communicative success was reached or not. If the listener observed a form that was not covered by one of its previously acquired constructions, it will call its `learn` method to create a new construction based on the observed form and the feedback that the speaker provided to the listener through pointing. " ] }, { "cell_type": "code", "execution_count": 8, "id": "2aeffc2edbd03bf9", "metadata": {}, "outputs": [], "source": [ "class Interaction:\n", " \"\"\" General interaction class. \"\"\"\n", "\n", " def record_communicative_success(self, interacting_agents):\n", " \"\"\" Record communicative success of the interaction \"\"\"\n", " success = all([agent.communicated_successfully for agent in interacting_agents])\n", " if success:\n", " notify('communicative_success', 1)\n", " else:\n", " notify('communicative_success', 0)\n", "\n", " def record_lexicon_size(self, population):\n", " \"\"\" Record the average lexicon size across the population. \"\"\"\n", " agents_with_cxns = [agent for agent in population if agent.grammar_size() > 0]\n", " avg_nr_of_cxns = np.mean([agent.grammar_size() for agent in agents_with_cxns])\n", " notify('construction_inventory_size', avg_nr_of_cxns)\n", "\n", " def record_conventionality(self, speaker, listener):\n", " \"\"\" Record the lexicon coherence between speaker and listener \"\"\"\n", " if not speaker.communicated_successfully:\n", " notify('conventionality', 0)\n", " else:\n", " listener_form = listener.formulate([[\"concept\", listener.concept]])\n", " if speaker.utterance == listener_form: ##check!\n", " notify('conventionality', 1)\n", " else:\n", " notify('conventionality', 0)\n", "\n", "\n", "class DescribeAndPointInteraction(Interaction):\n", " \"\"\"\n", " Interaction class for description-and-point interactions.\n", " \"\"\"\n", "\n", " def __init__(self, experiment, speaker, listener):\n", " self.experiment = experiment\n", " self.speaker = speaker\n", " self.listener = listener\n", " self.speaker.discourse_role = \"speaker\"\n", " self.listener.discourse_role = \"listener\"\n", " # Load scene and set tasks\n", " self.scene, target_index = experiment.world.sample_scene_with_unique_target(nr_of_entities = experiment.configuration['nr_of_entities'],\n", " train=False)\n", " speaker.task = DescriptionTask(speaker, self.scene, target_index)\n", " listener.task = PointingTask(listener, self.scene)\n", "\n", " def interact(self, speaker, listener, silent):\n", " \"\"\"Defines the interaction script.\"\"\"\n", "\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"

Scene:

\")\n", " for entity in self.scene:\n", " entity = entity * 255\n", " fcg.render_image_in_web_interface(entity.squeeze().tolist())\n", " fcg.add_element_to_web_interface(\" \")\n", "\n", " # Run description task speaker:\n", " utterance = speaker.task.run_task(silent=silent)\n", "\n", " # Pass the utterance to the listener\n", " listener.utterance = utterance\n", "\n", " # Run the listener side (parsing and possibly adoption)\n", " understood_target_index = listener.task.run_task(utterance, silent=silent)\n", "\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

The speaker agent provides feedback to the listener agent.

\")\n", " fcg.add_element_to_web_interface(\"
\")\n", "\n", " if speaker.task.target_index == understood_target_index:\n", " speaker.communicated_successfully = True\n", " listener.communicated_successfully = True\n", " if silent is not True:\n", " fcg.add_element_to_web_interface('

Communication was successful!.

')\n", " else:\n", " target_entity = listener.task.scene[speaker.task.target_index]\n", " neural_module_name = listener.configuration[\"primitives\"][listener.task.primitive]\n", " listener.concept, _ = classify_image(target_entity, listener.neural_modules[neural_module_name])\n", " mapped_concept = listener.category_mapping[listener.concept]\n", " new_cxn = listener.learn(listener.utterance, mapped_concept) #repeated invention possible!\n", " if silent is not True:\n", " fcg.add_element_to_web_interface('

Communication was unsuccessful!.

')\n", " fcg.add_element_to_web_interface('

The speaker points to the topic entity:

.')\n", " image = target_entity * 255\n", " fcg.render_image_in_web_interface(image.squeeze().tolist())\n", " fcg.add_element_to_web_interface(\"

The listener conceptualises the target in terms of category \" + str(mapped_concept) + \" and creates the following construction:

\")\n", " new_cxn.show_in_web_interface()\n", "\n", " # Reward the agents, positively and/or negatively\n", " speaker.reward()\n", " listener.reward()" ] }, { "cell_type": "code", "execution_count": 9, "id": "7a91615c48e4d7c2", "metadata": {}, "outputs": [], "source": [ "class Task:\n", " pass\n", "\n", "class DescriptionTask(Task):\n", " \"\"\"Given a context of entities, describe a given target entity.\"\"\"\n", " primitive = \"describe\"\n", "\n", " def __init__(self, agent, scene, target_index):\n", " self.agent = agent\n", " self.scene = scene\n", " self.target_index = target_index\n", "\n", " def run_task(self, silent=True):\n", " \"\"\"Run description task: conceptualise target entity (classify image) and formulate utterance.\"\"\"\n", " target_entity = self.scene[self.target_index]\n", "\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

The \" + self.agent.discourse_role + \" agent engages in a description task.

\")\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

Target:

\")\n", " image = target_entity * 255\n", " fcg.render_image_in_web_interface(image.squeeze().tolist())\n", "\n", " neural_module_name = self.agent.configuration[\"primitives\"][self.primitive]\n", " self.agent.concept,_ = classify_image(target_entity, self.agent.neural_modules[neural_module_name])\n", " mapped_concept = self.agent.category_mapping[self.agent.concept]\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"

Conceptualisation:

\")\n", " fcg.add_element_to_web_interface(\"

The speaker conceptualises the target in terms of category \" + str(mapped_concept) + \".

\")\n", " fcg.add_element_to_web_interface(\"

Language production:

\")\n", "\n", " self.agent.utterance = self.agent.formulate([[\"concept\", mapped_concept]])\n", "\n", " if self.agent.utterance is not None and silent is not True:\n", " fcg.add_element_to_web_interface(\"

The speaker applied the following construction(s):

\")\n", " self.agent.applied_cxn.show_in_web_interface()\n", " fcg.add_element_to_web_interface(\"

and utters:

\")\n", " fcg.add_element_to_web_interface('

\"' + self.agent.utterance + '\"

')\n", "\n", " if self.agent.utterance is None:\n", " self.agent.learn(fcg.generate_word_form(), mapped_concept)\n", " self.agent.utterance = self.agent.formulate([[\"concept\", mapped_concept]])\n", " \n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"

The speaker invents the following construction(s):

\")\n", " self.agent.applied_cxn.show_in_web_interface()\n", " fcg.add_element_to_web_interface(\"

and utters:

\")\n", " fcg.add_element_to_web_interface('

\"' + self.agent.utterance + '\"

')\n", "\n", " return self.agent.utterance\n", "\n", "def classify_image(image, neural_net):\n", " \"\"\"Classify image using neural net, and return label and probability of prediction.\"\"\"\n", " global DEVICE\n", " image = image.float().to(DEVICE)\n", " output = neural_net(image.unsqueeze(0))\n", " prediction = torch.argmax(output).item()\n", " softmax = torch.nn.Softmax(dim=1)\n", " probabilities = softmax(output)\n", " prediction_probability = probabilities[0][prediction].item()\n", " return prediction, prediction_probability\n", "\n", "\n", "class PointingTask(Task):\n", " \"\"\"Given an utterance and a context of entities, point to the target entity.\"\"\"\n", "\n", " primitive = \"point\"\n", "\n", " def __init__(self, agent, scene):\n", " self.agent = agent\n", " self.scene = scene\n", "\n", " def run_task(self, utterance, silent=True):\n", " \"\"\"Run pointing task: comprehend utterance and retrieve entity in the scene. Return target index.\"\"\"\n", " target_index = None\n", "\n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

The \" + self.agent.discourse_role + \" agent engages in a pointing task.

\")\n", " fcg.add_element_to_web_interface(\"
\")\n", " fcg.add_element_to_web_interface(\"

Language comprehension:

\")\n", " \n", " listener_comprehension_result = self.agent.comprehend(utterance)\n", "\n", " if listener_comprehension_result is None and silent is not True:\n", " fcg.add_element_to_web_interface(\"

The listener could not comprehend the utterance.

\")\n", "\n", " if listener_comprehension_result:\n", " self.agent.concept = listener_comprehension_result[0][1]\n", " neural_module_name = self.agent.configuration[\"primitives\"][self.primitive]\n", " highest_probability = 0.0\n", " for i in range(0, len(self.scene)):\n", " entity = self.scene[i]\n", " prediction, probability = classify_image(entity, self.agent.neural_modules[neural_module_name])\n", " mapped_prediction = self.agent.category_mapping[prediction]\n", " if mapped_prediction == self.agent.concept:\n", " if probability > highest_probability:\n", " highest_probability = probability\n", " target_index = i\n", " \n", " if silent is not True:\n", " fcg.add_element_to_web_interface(\"

The listener applies the following construction(s):

\")\n", " self.agent.applied_cxn.show_in_web_interface()\n", " fcg.add_element_to_web_interface(\"

The listener looks for an entity of category \" + str(self.agent.concept) + \" in the scene.

\")\n", " fcg.add_element_to_web_interface(\"

The listener points to the entity:

\")\n", " target_entity = self.scene[target_index]\n", " image = target_entity * 255\n", " fcg.render_image_in_web_interface(image.squeeze().tolist())\n", " \n", " return target_index" ] }, { "cell_type": "markdown", "id": "0d2bf806", "metadata": {}, "source": [ "## Monitoring and Plotting" ] }, { "cell_type": "code", "execution_count": 10, "id": "8b442d662a4083cd", "metadata": {}, "outputs": [], "source": [ "from itertools import islice\n", "\n", "MONITORS = {}\n", "\n", "def notify(monitor, value):\n", " \"\"\" Notify will add 'value' to the 'monitor' key in the global variable MONITORS. \"\"\"\n", " global MONITORS\n", " if monitor in MONITORS:\n", " MONITORS[monitor].append(value)\n", " else:\n", " MONITORS[monitor] = [value]\n", "\n", "\n", "def window(seq, n=2):\n", " \"\"\" Returns a sliding window (of width n) over the data from the sequence. \"\"\"\n", " it = iter(seq)\n", " result = tuple(islice(it, n))\n", " if len(result) == n:\n", " yield result\n", " for elem in it:\n", " result = result[1:] + (elem,)\n", " yield result\n", "\n", "\n", "def make_plot_points(monitors, window_size=100):\n", " \"\"\" Creates a new dictionary with the averages of the sliding windows, using the same keys as the monitors. \"\"\"\n", " plot_points = {}\n", " for key in monitors:\n", " plot_points[key] = []\n", " generator = window(monitors[key], n=window_size)\n", " for w in generator:\n", " plot_points[key].append(np.mean(w))\n", " return plot_points" ] }, { "cell_type": "markdown", "id": "a8acf7da", "metadata": {}, "source": [ "## Running an experiment" ] }, { "cell_type": "markdown", "id": "aa79a32d", "metadata": {}, "source": [ "A new experiment can be run by first creating a new instance of the `GroundedNGExperiment` class, passing the desired configuration." ] }, { "cell_type": "code", "execution_count": 11, "id": "eec125deedb70773", "metadata": { "ExecuteTime": { "end_time": "2025-10-16T09:34:16.928371Z", "start_time": "2025-10-16T09:34:16.793363Z" } }, "outputs": [], "source": [ "CONFIGURATION = {'learn_prelinguistic_categorisation': False,\n", " 'nr_of_epochs' : 5,\n", " 'learning_rate' : 0.00001,\n", " 'nr_of_agents' : 5,\n", " 'cxn_positive_reward' : 0.1,\n", " 'cxn_negative_reward' : 0.2,\n", " 'nr_of_entities' : 3,\n", " 'nr_of_categories': 40,\n", " 'world' : 'OlivettiLgData()',\n", " 'interaction' : 'DescribeAndPointInteraction',\n", " 'neural_modules' : ['DescribeNetOlivetti'],\n", " 'primitives' : {'describe': 'DescribeNetOlivetti',\n", " 'point': 'DescribeNetOlivetti'}}\n", "\n", "experiment = GroundedNGExperiment(CONFIGURATION)" ] }, { "cell_type": "markdown", "id": "c8460bc3", "metadata": {}, "source": [ "We now run the prelinguistic stage, where agents learn to tell individuals apart. If you want to save some time and electricity, you can also load pre-trained models (for 5 agents - about 5GB download)): " ] }, { "cell_type": "code", "execution_count": 12, "id": "d552279f", "metadata": {}, "outputs": [], "source": [ "# Download pretrained models\n", "models = fcg.load_resource('groundedngmodels.zip', target_directory=\".\")\n", "if not os.path.isfile('models/DescribeNetOlivetti-4.pt'):\n", " fcg.fcg_go_bridge.unzip(models,\".\")" ] }, { "cell_type": "code", "execution_count": 13, "id": "3ab622c07570548d", "metadata": {}, "outputs": [], "source": [ "# Run prelinguistic stage using pretrained models:\n", "experiment.run_prelinguistic_stage(learn_prelinguistic_categorisation=False)\n", "\n", "# Run prelinguistic stage from scratch:\n", "# experiment.run_prelinguistic_stage(learn_prelinguistic_categorisation=True)" ] }, { "cell_type": "markdown", "id": "6bcfab71", "metadata": {}, "source": [ "We run 5000 situated communicative interactions:" ] }, { "cell_type": "code", "execution_count": 14, "id": "06af9e0d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|████████████████████████████████████████| 5000/5000 [100%] in 5:12.1 (16.02/s) \n" ] } ], "source": [ "experiment.run_series(5000)" ] }, { "cell_type": "markdown", "id": "49ddea10", "metadata": {}, "source": [ "And we trace the 5001st interaction in the web interface:" ] }, { "cell_type": "code", "execution_count": 15, "id": "28f5fb9f", "metadata": {}, "outputs": [], "source": [ "fcg.start_web_interface()\n", "experiment.run_interaction()" ] }, { "cell_type": "markdown", "id": "81767e07", "metadata": {}, "source": [ "The dynamics of an experiment are continuously monitored while running a series of interactions. The outcome of each individual interaction is recorded by the `run_interaction` method in terms of three metrics. The first metric quantifies *communicative success* as a binary measure, reflecting whether the listener agent indeed pointed to the target of the conversation. The second metric records the *average number of constructions* in the grammars of the agents. The third metric quantifies the *conventionality* of the uttered form, reflecting whether the listener agent would have uttered the same form under the same circumstances if it would have been assigned the speaker role. We demonstrate here the use of `matplotlib` as implemented by the `GroundedNGExperiment.plot_results()` method, which plots the the degree of communicative success, degree of conventionality and average number of constructions as a function of the number of interactions that have taken place, using a sliding window of a chosen number of interactions:\n" ] }, { "cell_type": "code", "execution_count": 16, "id": "b5bc938e", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "experiment.plot_results(window_size=100)" ] }, { "cell_type": "markdown", "id": "52e8c108", "metadata": {}, "source": [ "The graphs show that the population indeed converges on a communicatively effective naming convention with one construction for each individual in the world. We can observe the typical language emergence dynamics known from the evolutionary linguistics literature, where the degrees of communicative success and conventionality gradually increase as more interactions take place, while the average grammar size overshoots in the early stages of the experiment, before gradually decreasing as the conventionalisation process further unfolds.\n" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }