<jupyter_start><jupyter_text>Let's set up a Pavolovian conditioning experiment.Agent ASensory input* Vision: 10 bit vector. A specific sparse pattern indicates sight of food.* Taste: 10 bit vector. A specific sparse pattern indicates taste of food.Among uncorrelated sensory activity, the following pattern is hidden.* Taste of food -- time delay t1 --> rewardWould this agent associate high reward prediction for Taste of food?Later, the following pattern is presented.* Sight of food --- time delay t0 --> Taste of food --- time delay t1 --> rewardWould the agent now associate high reward prediction for Sight of food? Will it stop associating Taste with reward (as seen from animal studies)?<jupyter_code>import logging
import numpy as np
import os
import random
import torch
import torch.utils.data
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Bernoulli
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

%matplotlib inline
plt.style.use('classic')

VISION_SIZE = 10
TASTE_SIZE = 5
REWARD_SIZE = 4

BASELINE_ACTIVATION = B = 0.3
HIGH_ACTIVATION = H = 0.8


class SpatialPattern:
  def __init__(self, values):
    self.poisson = Bernoulli(probs=values)

  def sample(self, shape):
    return self.poisson.sample(shape)

class TemporalPattern:
  def __init__(self, spatial_patterns, durations):
    self.spatial_patterns = spatial_patterns
    self.durations = durations
    
  def sample(self):
    all_samples = []
    for i in range(len(self.durations)):
      all_modalities_patterns = self.spatial_patterns[i]
      duration = self.durations[i]
      
      samples = [torch.tensor(all_modalities_patterns[j].sample([duration])) for j in range(len(all_modalities_patterns))]
      samples = torch.cat(samples, 1)
      all_samples.append(samples)
      
    return torch.cat(all_samples, 0)
    
class SensoryModality:      
  def __init__(self, size, baseline_activation):
    self.baseline_pattern = SpatialPattern(torch.ones(size) * baseline_activation)
    
  def baseline_sample(self, shape):
    return self.baseline_pattern.sample(shape)
  
  def create_spatial_pattern(self, values):
    return SpatialPattern(torch.tensor(values))

taste = SensoryModality(TASTE_SIZE, BASELINE_ACTIVATION)
reward = SensoryModality(REWARD_SIZE, BASELINE_ACTIVATION)

taste.create_spatial_pattern([B,B,B,H,H])


experiment1 = TemporalPattern(
  [
      [taste.create_spatial_pattern([B,B,B,H,H]), reward.baseline_pattern],
      [taste.baseline_pattern, reward.baseline_pattern],
      [taste.baseline_pattern, reward.create_spatial_pattern([H,H,H,H])],
      [taste.baseline_pattern, reward.baseline_pattern],
  ],
  [
      200,
      50,
      200,
      300,
  ]
)

data = experiment1.sample()
#print(data)

def trace(data, decay_rate=0.99):
  decay_rate = 0.99

  trace = torch.ones(data.shape[-1]) * B
  data_trace = [trace]

  for i in range(data.shape[0]):
    row = data[i]
    trace = trace * decay_rate + row * (1 - decay_rate)
    data_trace.append(trace)

  data_trace = torch.stack(data_trace)
  #print(data_trace)

  return data_trace

data_trace = trace(data)
plt.plot(data_trace.numpy())
plt.show()


class LocalEnsemble(nn.Module):
  def __init__(self, input_size, ensemble_size):
    super(LocalEnsemble, self).__init__()
    
    self.logger = logging.getLogger(self.__class__.__name__)
    self.logger.setLevel(logging.DEBUG)

    self.input_size = input_size

    self.ensemble = nn.Linear(input_size, ensemble_size)
    self.initialize_sparse_weights(self.ensemble)
    
    self.lateral = nn.Linear(ensemble_size, ensemble_size)
    self.initialize_sparse_weights(self.lateral)
    
    self.previous_activation = torch.zeros(ensemble_size)
    
  def initialize_sparse_weights(self, layer):
    layer.weight.data = F.dropout(layer.weight.data, 0.8)

  def forward(self, x):
    afferent_activation = self.ensemble(x)
    lateral_activation = torch.tanh(self.lateral(self.previous_activation))
    
    activation = torch.sigmoid(afferent_activation + lateral_activation)
    
    self.previous_activation = activation
    return activation

model = LocalEnsemble(data_trace.shape[-1], 10)
print(model.ensemble.weight.data)
model.forward(data_trace) # burner

activations = [model.forward(data_trace) for _ in range(5)]
activations = torch.stack(activations)
# trace(activation.detach())
activations = activations.view(-1, activations.shape[-1])
#plt.plot(trace(activations.detach()).numpy())
plt.plot(activations.detach().numpy())
plt.show()

reward = activations[:, -REWARD_SIZE:].sum(dim=1).unsqueeze(dim=1) / REWARD_SIZE
#plt.plot(trace(reward.detach()).numpy())
plt.plot(reward.detach().numpy())
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10823331227626869, 'fraction_numerical': 0.010317961676142346, 'mean_word_length': 4.459770114942529, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '258503', 'license': 'MIT', 'n_tokens_mistral': 1637, 'n_tokens_neox': 1544, 'n_words': 342, 'path': 'Attractor Learning/01.ipynb', 'repo_name': 'amolk/AGI-experiments', 'size': '4749'}