from __future__ import annotations
from typing import List
import torch
import narla
[docs]class MultiAgentNetwork:
"""
A MultiAgentNetwork contains a list of Layers
:param observation_size: Size of the observation which the MultiAgentNetwork will receive
:param number_of_actions: Number of actions available to the MultiAgentNetwork
:param network_settings: Settings for the MultiAgentNetwork
"""
def __init__(
self,
observation_size: int,
number_of_actions: int,
network_settings: narla.multi_agent_network.MultiAgentNetworkSettings,
):
self._network_settings = network_settings
self._history = narla.history.History(storage_size=1_000_000)
self._layers: List[narla.multi_agent_network.Layer] = self._build_layers(
observation_size=observation_size,
number_of_actions=number_of_actions,
network_settings=network_settings,
)
[docs] def act(self, observation: torch.Tensor) -> torch.Tensor:
"""
Take an action based on the observation
:param observation: Observation from the MultiAgentNetwork environment
"""
previous_layer_action = observation
for layer in self._layers:
previous_layer_action = layer.act(previous_layer_action)
return previous_layer_action
@staticmethod
def _build_layers(
observation_size: int,
number_of_actions: int,
network_settings: narla.multi_agent_network.MultiAgentNetworkSettings,
) -> List[narla.multi_agent_network.Layer]:
layers: List[narla.multi_agent_network.Layer] = []
for layer_index in range(network_settings.number_of_layers):
is_last_layer = layer_index == network_settings.number_of_layers - 1
if is_last_layer:
# On the last layer there's just a single output neuron
last_layer_settings = network_settings.layer_settings.clone()
last_layer_settings.number_of_neurons_per_layer = 1
layer = narla.multi_agent_network.Layer(
observation_size=observation_size,
number_of_actions=number_of_actions,
layer_settings=last_layer_settings,
)
else:
layer = narla.multi_agent_network.Layer(
observation_size=observation_size,
number_of_actions=2,
layer_settings=network_settings.layer_settings,
)
observation_size = network_settings.layer_settings.number_of_neurons_per_layer
# The first and last Layers should be fully connected
layer.connectivity = narla.multi_agent_network.Layer.build_connectivity(
observation_size=layer.observation_size,
number_of_neurons=layer.number_of_neurons,
local_connectivity=False if layer_index == 0 or is_last_layer else network_settings.local_connectivity,
)
layers.append(layer)
return layers
[docs] def compute_biological_rewards(self):
"""
Compute BiologicalRewards and distribute to the Neurons
"""
for layer_index, layer in enumerate(self._layers):
for reward_type in self._network_settings.reward_types:
if reward_type not in narla.rewards.RewardTypes.biological_reward_types():
continue
reward = reward_type.to_reward()
rewards = reward.compute(network=self, layer_index=layer_index)
for neuron_index, neuron in enumerate(layer.neurons):
neuron.record(**{reward_type: rewards[0, neuron_index]})
[docs] def distribute_to_layers(self, **kwargs):
"""
Distribute data to the Layers
:param kwargs: Key word arguments to be distributed
"""
for layer in self._layers:
layer.distribute_to_neurons(**kwargs)
@property
def history(self) -> narla.history.History:
"""
Access the History of the MultiAgentNetwork
"""
return self._history
@property
def layers(self) -> List[narla.multi_agent_network.Layer]:
"""
Access the Layers of the MultiAgentNetwork
"""
return self._layers
[docs] def learn(self):
"""
Execute learning phase for Layers
"""
for layer in self._layers:
layer.learn(*self._network_settings.reward_types)
[docs] def record(self, **kwargs):
"""
Record data into the MultiAgentNetwork's History
:param kwargs: Key word arguments to be stored in the History
"""
self._history.record(**kwargs)