voxel mountain
学校は最悪なこともある。多くの人がそう思っている。でも、学校は学ぶための最良の方法ではない。伝統的な学校はたった200年前に始まっただけで、人類は何千年も存在していた。学校は個々の興味に合わないことも多い、硬直したカリキュラムを押し付ける。『無駄』なことを強制的に学ばせるのは自然な好奇心を殺す。自分で学ぶことは、自分の情熱や目的を発見する助けになる—学校はむしろその逆をすることが多い。授業で受け身で聞くだけよりも、実際に手を動かして学ぶ方がはるかに効果的。宿題や成績は余計なストレスを生み、学ぶことが義務のように感じられてしまう。好奇心があると、強制的に暗記するよりずっと速く情報を吸収できる。独学で得たスキル(例えばプログラミング!)は充実したキャリアに繋がることが多い。自由に探求することで創造力が育まれる—これは学校ではよく抑えられてしまう。詳しくはこちら:
なぜ遊ぶ本能を解放すると子どもはより幸せで自立し、長く良い学習者になるのか
啓蒙時代後の自己教育
学生の教育の自由の権利
教育に反対する理由
ホームスクーリングに関する研究レビュー
doesn't work, A1p can't predict in correct position. no categories in A1
import torch
from torch import tensor
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('macosx')
import math
lr = 0.01
adaptrate = 0.03
class DenseConnection:
def __init__(self, w,scale):
self.w=w
self.scale = scale
def make(sender,receiver,scale=1.0):
con = DenseConnection(torch.rand(sender.size,receiver.size),scale/sender.size)
receiver.connect(sender,con)
return con
def make2(sender,receiver,scale=1.0):
w=torch.rand(sender.size,receiver.size)
con = DenseConnection(w,scale/sender.size)
con2 = DenseConnection(w.T,scale/receiver.size)
receiver.connect(sender,con)
sender.connect(receiver,con)
return con,con2
def forward(self, x): return [email protected]*self.scale
def updateWeight(self, sender,receiver,adaptAct=True):
change = sender.act[:,None]*receiver.act[None,:]*1.01 - sender.minusPhaseAct[:,None]*receiver.minusPhaseAct[None,:]*0.99
self.w += change*lr
self.w.clamp(0,1)
if adaptAct:
self.w += (torch.where(receiver.targetDiff[None,:]<0.0, 0.0, 1.0) - self.w) * (receiver.targetDiff[None,:].abs()) * adaptrate
def updateWeightSpecific(self, senderMinusPhase,senderPlusPhase,receiver,adaptAct=True):
change = senderPlusPhase[:,None]*receiver.act[None,:]*1.01 - senderMinusPhase[:,None]*receiver.minusPhaseAct[None,:]*0.99
self.w += change*lr
self.w.clamp(0,1)
if adaptAct:
self.w += (torch.where(receiver.targetDiff[None,:]<0.0, 0.0, 1.0) - self.w) * (receiver.targetDiff[None,:].abs()) * adaptrate
class LocalPoolConnection:
"""
Supports different strides, different pool sizes, and different neuron counts
in sender and receiver layers.
Structure:
- Sender: (S_px, S_py, S_neurons)
- Receiver: (R_px, R_py, R_neurons)
- Strides: (stride_x, stride_y) in terms of receiver pools per sender pool.
Actually, it's easier to define:
Sender pool (i, j) connects to Receiver pools in a local neighborhood.
But usually, local connection means Receiver pool (r, c) connects to
Sender pools (r+dx, c+dy).
Let's define strides as:
- Sender stride: distance between sender pool centers in terms of sender pool grid.
- Receiver stride: distance between receiver pool centers.
Actually, standard "local connection" with different strides:
If stride_x=1, receiver pool r connects to sender pools r, r+1, r+2...
If stride_x=2, receiver pool r connects to sender pools r*2, r*2+1, r*2+2...
Let's use:
- Sender grid size: (S_px, S_py)
- Receiver grid size: (R_px, R_py)
- Neurons per sender pool: S_n
- Neurons per receiver pool: R_n
- Neighborhood size: neighborhood_size (e.g., 1 for 3x3, 2 for 5x5)
For each receiver pool (rx, ry), it connects to sender pools:
(rx * stride_x + dx, ry * stride_y + dy) for dx, dy in neighborhood
Where stride_x, stride_y are the strides.
"""
def __init__(self, lw, sw, scale, sender_indices, receiver_indices,
s_px, s_py, s_neurons, r_px, r_py, r_neurons,
stride_x, stride_y, neighborhood_size=1):
"""
Args:
w: 1D tensor of weights
scale: scaling factor
sender_indices: 1D long tensor, sender neuron indices
receiver_indices: 1D long tensor, receiver neuron indices
s_px, s_py: sender grid dimensions
s_neurons: neurons per sender pool
r_px, r_py: receiver grid dimensions
r_neurons: neurons per receiver pool
stride_x, stride_y: strides
neighborhood_size: size of neighborhood
"""
assert float(r_px)*float(stride_x)==float(s_px) and float(r_py)*float(stride_y)==float(s_py), "stride is wrong"
self.lw = lw
self.sw = sw
self.w = lw*sw
self.scale = scale
self.sender_indices = sender_indices
self.receiver_indices = receiver_indices
self.s_px = s_px
self.s_py = s_py
self.s_neurons = s_neurons
self.r_px = r_px
self.r_py = r_py
self.r_neurons = r_neurons
self.stride_x = stride_x
self.stride_y = stride_y
self.neighborhood_size = neighborhood_size
@classmethod
def make(cls, sender, receiver,
stride_x=1, stride_y=1, scale=1.0, neighborhood_size=1):
"""
Creates a local pool connection.
"""
#center_size_x = 1 if stride_x<1 else int(stride_x)
#center_size_y = 1 if stride_y<1 else int(stride_y)
#neighborhood_size_sender_x = neighborhood_size if stride_x<1 else neighborhood_size*stride_x
#neighborhood_size_sender_y = neighborhood_size if stride_y<1 else neighborhood_size*stride_y
offsets = [(dx, dy) for dx in range(-neighborhood_size, neighborhood_size+1)
for dy in range(-neighborhood_size, neighborhood_size+1)]
sender_indices_list = []
receiver_indices_list = []
r_px, r_py, r_neurons = receiver.shape
s_px, s_py, s_neurons = sender.shape
for rx in range(r_px):
for ry in range(r_py):
for dx, dy in offsets:
# Sender pool coordinates
spx = math.floor(rx * stride_x) + dx
spy = math.floor(ry * stride_y) + dy
if 0 <= spx < s_px and 0 <= spy < s_py:
# Connect each neuron in sender pool to each neuron in receiver pool
# This creates a dense local connection within the neighborhood
# But if s_neurons != r_neurons, we need to handle it.
# Let's assume full connectivity between sender and receiver neurons in the neighborhood
# Or pointwise? The original code did pointwise: n sender -> n receiver
# So we connect sender neuron (spx, spy, n) to receiver neuron (rx, ry, n)
for n in range(s_neurons):
# Calculate base indices for the pool connection
base_sender_idx = spx * (s_py * s_neurons) + spy * s_neurons
base_receiver_idx = rx * (r_py * r_neurons) + ry * r_neurons
# Add indices for all receiver neurons for each sender neuron
for r_n in range(r_neurons):
sender_indices_list.append(base_sender_idx + n)
receiver_indices_list.append(base_receiver_idx + r_n)
sender_indices = torch.tensor(sender_indices_list, dtype=torch.long)
receiver_indices = torch.tensor(receiver_indices_list, dtype=torch.long)
w = torch.rand(len(sender_indices_list))
# Calculate scale factor
total_receiver_neurons = r_px * r_py * r_neurons
fan_in = torch.zeros(total_receiver_neurons, dtype=torch.float)
fan_in.index_add_(0, receiver_indices, torch.ones_like(w))
avg_fan_in = fan_in.mean()
scale_factor = scale / avg_fan_in if avg_fan_in > 0 else scale
con = cls(torch.full_like(w,0.5), w, scale_factor, sender_indices, receiver_indices,
s_px, s_py, s_neurons, r_px, r_py, r_neurons,
stride_x, stride_y, neighborhood_size)
receiver.connect(sender,con)
return con
@classmethod
def make2(cls, sender, receiver,
stride_x=1, stride_y=1, scale=1.0, neighborhood_size=1):
#return DenseConnection.make2( s_px* s_py* s_neurons, r_px* r_py* r_neurons,scale)
"""
Creates transposed connection.
"""
conn = cls.make(sender, receiver, stride_x, stride_y, scale, neighborhood_size)
# Transposed: swap sender and receiver
# But we need to adjust strides and grid sizes
# If original: receiver pool rx connects to sender pool rx*stride + dx
# Transposed: sender pool (rx*stride + dx) connects to receiver pool rx
# New receiver (old sender) grid: (s_px, s_py)
# New sender (old receiver) grid: (r_px, r_py)
# New strides: inverse of original strides?
# Actually, let's just swap indices and keep the structure.
# The connectivity graph is reversed.
new_sender_indices = conn.receiver_indices
new_receiver_indices = conn.sender_indices
new_w = conn.w.clone()
# New grid dimensions
new_r_px, new_r_py = conn.s_px, conn.s_py
new_s_px, new_s_py = conn.r_px, conn.r_py
new_r_neurons = conn.s_neurons
new_s_neurons = conn.r_neurons
# New strides:
# If original stride was sx, sy, the transposed stride is 1/sx, 1/sy?
# Not exactly, because the grid sizes are different.
# Let's keep the same neighborhood size and offsets.
# Recalculate scale
total_new_receiver_neurons = new_r_px * new_r_py * new_r_neurons
fan_in = torch.zeros(total_new_receiver_neurons, dtype=torch.float)
fan_in.index_add_(0, new_receiver_indices, torch.ones_like(new_w))
avg_fan_in = fan_in.mean()
scale_factor = conn.scale / (conn.scale / (conn.w.shape[0] / total_new_receiver_neurons if total_new_receiver_neurons > 0 else 1))
# Simpler: use the same scale factor logic
scale_factor = conn.scale / avg_fan_in if avg_fan_in > 0 else conn.scale
con2 = cls(torch.full_like(new_w,0.5), new_w, scale_factor, new_sender_indices, new_receiver_indices,
new_s_px, new_s_py, new_s_neurons, new_r_px, new_r_py, new_r_neurons,
1/conn.stride_x, 1/conn.stride_y, conn.neighborhood_size)
sender.connect(receiver,con2)
return conn,con2
def forward(self, x):
"""
Forward pass.
x: sender activations, shape (S_px * S_py * S_n,)
Returns: receiver activations, shape (R_px * R_py * R_n,)
"""
assert len(x.shape)==1 and x.shape[0]==self.s_px*self.s_py*self.s_neurons, x.shape
sender_values = x[self.sender_indices]
weighted_values = self.w * sender_values
receiver_flat = torch.zeros(self.r_px * self.r_py * self.r_neurons)
receiver_flat.index_add_(0, self.receiver_indices, weighted_values)
return receiver_flat * self.scale
def updateWeight(self, sender, receiver, adaptAct=True):
"""
Update weights.
sender: object with .act, .minusPhaseAct
receiver: object with .act, .minusPhaseAct, .targetDiff
"""
sender_plus = sender.act
sender_minus = sender.minusPhaseAct
receiver_act = receiver.act
receiver_minus = receiver.minusPhaseAct
target_diff = receiver.targetDiff
sender_plus_vals = sender_plus[self.sender_indices]
receiver_act_vals = receiver_act[self.receiver_indices]
sender_minus_vals = sender_minus[self.sender_indices]
receiver_minus_vals = receiver_minus[self.receiver_indices]
change_plus = sender_plus_vals * receiver_act_vals
change_minus = sender_minus_vals * receiver_minus_vals
change = change_plus*1.01 - change_minus*0.99
self.lw += change * lr
self.lw.clamp_(0, 1)
if adaptAct:
target_diff_vals = target_diff[self.receiver_indices]
target_mask = torch.where(target_diff_vals < 0, 0.0, 1.0)
target_update = (target_mask - self.lw) * target_diff_vals.abs() * adaptrate
self.lw += target_update
self.w=self.lw*self.sw
def updateWeightSpecific(self, senderMinusPhase, senderPlusPhase, receiver, adaptAct=True):
sender_plus = senderPlusPhase
sender_minus = senderMinusPhase
receiver_act = receiver.act
receiver_minus = receiver.minusPhaseAct
target_diff = receiver.targetDiff
sender_plus_vals = sender_plus[self.sender_indices]
receiver_act_vals = receiver_act[self.receiver_indices]
sender_minus_vals = sender_minus[self.sender_indices]
receiver_minus_vals = receiver_minus[self.receiver_indices]
change_plus = sender_plus_vals * receiver_act_vals
change_minus = sender_minus_vals * receiver_minus_vals
change = change_plus*1.01 - change_minus*0.99
self.lw += change * lr
self.lw.clamp_(0, 1)
if adaptAct:
target_diff_vals = target_diff[self.receiver_indices]
target_mask = torch.where(target_diff_vals < 0, 0.0, 1.0)
target_update = (target_mask - self.lw) * target_diff_vals.abs() * adaptrate
self.lw += target_update
self.w=self.lw*self.sw
class Layer:
def __init__(self, shape, adaptAct=True, target=None):
self.shape = shape
if isinstance(shape, tuple):
prod = 1
for s in shape: prod *= s
self.size = prod
else:
self.size = shape
self.shape = (shape,)
self.inConnections=[]
self.inConnectionsSender=[]
self.inConnectionsUsePrevAct=[]
self.minusPhaseAct=None
self.act=None
self.prevAct=torch.zeros(self.size)
self.avgAct = torch.zeros(self.size)
self.input = None
self.adaptAct = adaptAct
self.target = target
def connect(self, sender, connection, usePrevAct=False):
if connection in self.inConnections:
i = self.inConnections.index(connection)
self.inConnectionsUsePrevAct[i] = usePrevAct
else:
self.inConnections.append(connection)
self.inConnectionsSender.append(sender)
self.inConnectionsUsePrevAct.append(usePrevAct)
def update(self):
input = None
if self.input is not None: input = self.input
for connection,sender,usePrevAct in zip(self.inConnections,self.inConnectionsSender,self.inConnectionsUsePrevAct):
senderAct = sender.prevAct if usePrevAct else sender.act
if senderAct is not None:
thisInput = connection.forward(senderAct)
if input is None: input = thisInput
else: input += thisInput
assert input is not None
feedforwardInhibition = input.mean()*0.7 + input.amax()*0.3
if len(self.shape)>1 and self.shape[-1]>1:
inPoolDim=len(self.shape)-1
inputView = input.reshape(self.shape)
feedforwardInhibitionPool = inputView.mean(inPoolDim,keepdim=True)*0.7 + inputView.amax(inPoolDim,keepdim=True)*0.3
feedforwardInhibition = torch.maximum(feedforwardInhibition,feedforwardInhibitionPool.expand(self.shape).flatten())
self.feedforwardInhibition=feedforwardInhibition
input = (input - feedforwardInhibition)
self.act = (input*64.0).tanh_().clamp_(0)
return self.act
def updatePlusPhase(self, input):
self.update(input)
#input = (input - self.feedforwardInhibition)
#self.act = (input*64.0).tanh_().clamp_(0)
return self.act
def slowUpdate(self):
self.avgAct += (self.act-self.avgAct)*adaptrate
inPoolDim=len(self.shape)-1
self.avgActTarget = self.avgAct.reshape(self.shape).mean(inPoolDim,keepdim=True).expand(self.shape).flatten()
self.targetDiff = self.avgActTarget - self.avgAct
for connection,sender,usePrevAct in zip(self.inConnections,self.inConnectionsSender,self.inConnectionsUsePrevAct):
if usePrevAct: connection.updateWeightSpecific(sender.prevAct,sender.prevAct,self,self.adaptAct)
else: connection.updateWeight(sender,self,self.adaptAct)
inpLayer=Layer((8,8,3))
V1=Layer((8,8,16))
V2=Layer((8,8,40))
V3=Layer((4,4,40))
V4=Layer((4,4,40))
V1p=Layer(V1.shape, adaptAct=False, target=V1)
V2p=Layer(V1.shape, adaptAct=False, target=V1)
V3p=Layer(V2.shape, adaptAct=False, target=V2)
V4p=Layer(V3.shape, adaptAct=False, target=V3)
MtPos=Layer((8,8,1))
LIP=Layer((8,8,16))
MtPosP=Layer((8,8,1), adaptAct=False, target=MtPos)
A1=Layer((4,4,40))
A2=Layer((4,4,40))
A1ct=Layer(A1.shape)
A2ct=Layer(A2.shape)
A1p=Layer(V4.shape, adaptAct=False, target=V4)
A2p=Layer(V4.shape, adaptAct=False, target=V4)
v1ToV2,V2ToV1=LocalPoolConnection.make2(V1,V2, scale=8.0)
v2ToV3,V3ToV2=LocalPoolConnection.make2(V2,V3, stride_x=2,stride_y=2, scale=8.0)
v3ToV4,V4ToV3=LocalPoolConnection.make2(V3,V4, scale=8.0)
V4p.connect(V4,LocalPoolConnection.make(V4,V4p, scale=8.0),usePrevAct=True)
V3p.connect(V3,LocalPoolConnection.make(V3,V3p, stride_x=0.5,stride_y=0.5, scale=8.0),usePrevAct=True)
V2p.connect(V2,LocalPoolConnection.make(V2,V2p, scale=8.0),usePrevAct=True)
V1p.connect(V1,LocalPoolConnection.make(V1,V1p, scale=8.0),usePrevAct=True)
LocalPoolConnection.make(V1p,V1, scale=0.1*8.0)
LocalPoolConnection.make(V2p,V2, scale=0.1*8.0)
LocalPoolConnection.make(V3p,V3, stride_x=2,stride_y=2, scale=0.1*8.0)
LocalPoolConnection.make(V4p,V4, scale=0.1*8.0)
LocalPoolConnection.make(V4p,V3p, stride_x=0.5,stride_y=0.5, scale=8.0)
LocalPoolConnection.make(V3p,V2p, scale=8.0)
LocalPoolConnection.make(V2p,V1p, scale=8.0)
v1ToMtPos=LocalPoolConnection.make(V1,MtPos, scale=1.0, neighborhood_size=0)
MtPosToLIP=LocalPoolConnection.make(MtPos, LIP, scale=1.0, neighborhood_size=0)
LIPToMtPosP=DenseConnection.make(LIP,MtPosP, scale=8.0)
#MtPosP.connect(LIP,LIPToMtPosP,usePrevAct=True)
MtPosPToLIP=LocalPoolConnection.make(MtPosP,LIP, scale=0.1*1.0, neighborhood_size=0)
LIPToV2=LocalPoolConnection.make(LIP,V2, scale=8.0, neighborhood_size=0)
LIPToV3=LocalPoolConnection.make(LIP,V3, stride_x=2,stride_y=2, scale=8.0, neighborhood_size=0)
LocalPoolConnection.make(MtPos,V1p, scale=8.0)#todo change back to mtposp
LocalPoolConnection.make(MtPos,V2p, scale=8.0)
LocalPoolConnection.make(MtPos,V3p, scale=8.0) # larger neighborhood_size because the connection is not centered currently
LocalPoolConnection.make(MtPos,V4p, stride_x=2,stride_y=2, neighborhood_size=2, scale=8.0)
#LocalPoolConnection.make(MtPos,A1ct, stride_x=2,stride_y=2, neighborhood_size=2, scale=8.0)
v4ToA1,A1ToV4=DenseConnection.make2(V4,A1, scale=8.0) # almost full connection
LocalPoolConnection.make(V1p,A1, stride_x=2,stride_y=2, neighborhood_size=2, scale=0.1*8.0)
A1ct.connect(A1,LocalPoolConnection.make(A1,A1ct, scale=8.0),usePrevAct=True)
A1ct.connect(A1ct,LocalPoolConnection.make(A1ct,A1ct, scale=8.0),usePrevAct=True)
LocalPoolConnection.make(V4p,A1p, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(V3p,A1p, stride_x=2,stride_y=2, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make2(A1ct,A1p, scale=8.0)
LocalPoolConnection.make2(A2ct,A1p, scale=8.0)
LocalPoolConnection.make(A1p,A1, scale=0.1*8.0)
LocalPoolConnection.make(A1ct,V4p, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1ct,V3, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1ct,V3p, stride_x=0.5,stride_y=0.5, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1ct,V2, stride_x=0.5,stride_y=0.5, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1ct,V1p, stride_x=0.5,stride_y=0.5, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1,V3p, stride_x=0.5,stride_y=0.5, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(A1,V2p, stride_x=0.5,stride_y=0.5, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make2(A1,A2, scale=8.0)
LocalPoolConnection.make(V1p,A2, stride_x=2,stride_y=2, neighborhood_size=2, scale=0.1*8.0)
A2ct.connect(A2,LocalPoolConnection.make(A2,A2ct, scale=8.0),usePrevAct=True)
A2ct.connect(A2ct,LocalPoolConnection.make(A2ct,A2ct, scale=8.0),usePrevAct=True)
LocalPoolConnection.make(V4p,A2p, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make(V3p,A2p, stride_x=2,stride_y=2, neighborhood_size=2, scale=8.0)
LocalPoolConnection.make2(A1ct,A2p, scale=8.0)
LocalPoolConnection.make2(A2ct,A1ct, scale=8.0)
i=0
fig, axs = plt.subplots(6,3)
plt.tight_layout(pad=0)
plt.subplots_adjust(wspace=0.05, hspace=0.05)
for ax in axs.flat: ax.tick_params(labelbottom=False, labelleft=False)
def showAt(ax, v, title=None, vmin=0,vmax=1):
ax.clear()
ax.axis('off')
if v is None: return
if len(v.shape)==1: v=v.unsqueeze(0)
if len(v.shape)==3: v=v.flatten(1)
ax.imshow(v, vmin=vmin, vmax=vmax)
if title: ax.set_title(title)
def draw_frame(time_step, grid_dim=100):
# Calculate position using sine for smooth oscillation
x = int((grid_dim / 2) * (1 + torch.sin(time_step * 0.1)))
y = int((grid_dim / 2) * (1 + torch.cos(time_step * 0.07)))
# Create empty frame
frame = torch.zeros(grid_dim, grid_dim, 3)
# Draw single pixel
if 0 <= x < grid_dim and 0 <= y < grid_dim:
frame[y, x, 0] = 1.0
return frame
video_sigs=[]
for j in range(1000):
i+=1
input_image=next_frame(i,moveSpeed=1.0)
v1Input=F.conv2d(
input=input_image,
weight=gabor_filters_tensor,
bias=None,
stride=1,
padding=4 # (9-1)/2 = 4, to keep spatial dimensions same
)
V1.input=F.avg_pool2d(v1Input, kernel_size=4, stride=4).permute(0,2,3,1).flatten() / 32.0
for layer in [V1,V1p,V2,V2p,V3,V3p,V4,V4p,MtPos,MtPosP,LIP,A1,A1ct,A1p,A2,A2ct,A2p]:
layer.act = None
V1.update();MtPos.update();LIP.update()# temp
# update p layers first because they use prevAct
for layer in [A2ct,A1ct,A1p,A1ct,A2ct,A2p,A2ct,MtPosP,V4p,V3p,V2p,V1p]:
layer.update()
#forward
for layer in [V1,V2,V3,V4,MtPos,LIP,A1,A2]:
layer.update()
# update p layers because some connect to super layers which were just updated. again update ct because it depends on lower p layers
for layer in [A2ct,A1ct,A1p,A1ct,A2ct,A2p,A2ct,MtPosP,V4p,V3p,V2p,V1p]:
layer.update()
#spread backward so it spreads more completely
for layer in [A1,V4,V3,V2,V1]:
layer.update()
#forward again
for layer in [V1,V2,V3,V4,MtPos,LIP,A1,A2]:
layer.update()
# do again but clamp p layers
for layer in [V1,V1p,V2,V2p,V3,V3p,V4,V4p,MtPos,MtPosP,LIP,A1,A1ct,A1p,A2,A2ct,A2p]:
layer.minusPhaseAct = layer.act
# also update ct layers without updating p layers
for layer in [A2ct,A1ct,A2ct]:
layer.update()
for layer in [V1p,V2p,V3p,MtPosP,A1p,A2p]:
layer.act = layer.target.act
#forward
for layer in [V1,V2,V3,V4,MtPos,LIP,A1,A2]:
layer.update()
# again update ct because it depends on lower p layers
for layer in [A2ct,A1ct,A2ct]:
layer.update()
#spread backward so it spreads more completely
for layer in [A1,V4,V3,V2,V1]:
layer.update()
#forward again
for layer in [V1,V2,V3,V4,MtPos,LIP,A1,A2]:
layer.update()
#update weight
for layer in [V1,V1p,V2,V2p,V3,V3p,V4,V4p,MtPos,MtPosP,LIP,A1,A1ct,A1p,A2,A2ct,A2p]:
layer.slowUpdate()
#update prevAct
for layer in [V1,V1p,V2,V2p,V3,V3p,V4,V4p,MtPos,MtPosP,LIP,A1,A1ct,A1p,A2,A2ct,A2p]:
layer.prevAct = layer.act
if j>0:
#showAt(axs[0,0], input_image.view(32,32))
#showAt(axs[0,0], inp.view(inpLayer.shape))
#showAt(axs[0,1], inppa.view(inpp.shape))
#showAt(axs[0,2], -10*inpp.targetDiff.view(inpp.shape))
showAt(axs[0,0], V1.minusPhaseAct.view(V1.shape))
showAt(axs[0,1], V1p.minusPhaseAct.view(V1p.shape))
showAt(axs[0,2], (V1.avgActTarget*10.0).view(V1.shape))
showAt(axs[1,0], V2.minusPhaseAct.view(V2.shape))
showAt(axs[1,1], V2p.minusPhaseAct.view(V2p.shape))
showAt(axs[1,2], (V3p.act.view(V3p.shape).amax(dim=2)-V3p.minusPhaseAct.view(V3p.shape).amax(dim=2)), None, -1,1)
showAt(axs[2,0], V3.minusPhaseAct.view(V3.shape))
showAt(axs[2,1], V3p.minusPhaseAct.view(V3p.shape))
showAt(axs[2,2], V4p.minusPhaseAct.view(V4.shape),"v4p")
showAt(axs[3,0], V4.minusPhaseAct.view(V4.shape))
showAt(axs[3,1], A1p.minusPhaseAct.view(A1p.shape),"A1p")
showAt(axs[3,2], A2p.minusPhaseAct.view(A2p.shape),"A2p")
#showAt(axs[4,0], MtPos.minusPhaseAct.view(MtPos.shape),"MtPos")
#showAt(axs[4,2], MtPosP.minusPhaseAct.view(MtPosP.shape),"mtposp")
#showAt(axs[4,1], LIP.minusPhaseAct.view(LIP.shape),"lip")
showAt(axs[5,0], A1.minusPhaseAct.view(A1.shape),"A1")
showAt(axs[5,1], A2.minusPhaseAct.view(A2.shape),"A2")
showAt(axs[5,2], (A1p.act.view(A1p.shape).amax(dim=2)-A1p.minusPhaseAct.view(A1p.shape).amax(dim=2)), None, -1,1)
showAt(axs[4,0], A1ct.minusPhaseAct.view(A1.shape),"A1ct")
showAt(axs[4,1], A2ct.minusPhaseAct.view(A2.shape),"A2ct")
#showAt(axs[5,2], V4.avgAct.view(V4.shape)*5.0)
video_sigs.append(A1.act)
if len(video_sigs)>50:video_sigs.pop(0)
if 1 and len(video_sigs) >= 2:
sigs = torch.stack(video_sigs)
sigs = sigs / sigs.norm(dim=1, keepdim=True).clamp(min=1e-8)
sim = (sigs @ sigs.T).numpy()
axs[4,2]
axs[4,2].imshow(sim, vmin=-1, vmax=1, cmap='RdBu', aspect='auto')
plt.pause(1)
plt.pause(100000)
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
def make_obj(rows):
return torch.tensor(rows, dtype=torch.float32)
objects = [
# Pyramid 1
make_obj([
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,1,1,0,0,0],
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[1,1,1,1,1,1,1,1]
]),
# Round 1
make_obj([
[0,0,0,1,1,0,0,0],
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1],
[0,1,1,1,1,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,0,1,1,0,0,0]
]),
# Box 1
make_obj([
[1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1]
]),
# Horizontal 1
make_obj([
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]
]),
]
def next_frame(i, moveSpeed=1.0):
# Grid for output frame coordinates normalized to [-1, 1]
y_out = torch.linspace(-4, 4, 32)
x_out = torch.linspace(-4, 4, 32)
y_out_grid, x_out_grid = torch.meshgrid(y_out, x_out, indexing='ij')
if True:
objNum = round(i/10-0.5)%len(objects)
obj_bitmap = objects[objNum]
i=torch.tensor(i+objNum*33)
rot = i*0.4
# Map position to [-1, 1]
pos_x_norm = torch.sin(i*0.5*moveSpeed)*2.0
pos_y_norm = torch.sin(i*0.3*moveSpeed)*2.0
# Relative coordinates in frame space
x_rel = x_out_grid - pos_x_norm
y_rel = y_out_grid - pos_y_norm
# Inverse rotation to map frame coordinates to object local space
cos_neg = torch.cos(-rot)
sin_neg = torch.sin(-rot)
x_local = x_rel * cos_neg - y_rel * sin_neg
y_local = x_rel * sin_neg + y_rel * cos_neg
# Normalize local coordinates to [-1, 1] for grid_sample
# Assuming the object bitmap is centered at 0 and scaled to fit [-1, 1]
# Since the bitmap is 8x8, we assume it maps 0..8 to -1..1
x_norm = x_local
y_norm = y_local
grid = torch.stack([x_norm, y_norm], dim=-1).unsqueeze(0)
obj_expanded = obj_bitmap.unsqueeze(0).unsqueeze(0)
frame = F.grid_sample(
obj_expanded,
grid,
mode='bilinear',
padding_mode='zeros',
align_corners=True
)
frame = torch.clamp(frame, 0.0, 1.0)
return frame
plt.imshow(next_frame(100).reshape(32,32))
plt.pause(10000)
import torch
import matplotlib.pyplot as plt
def create_gabor_kernel(length, wavelength, theta, sigma_len, sigma_wd, phase_deg):
"""Generate a Gabor kernel matching the Go code's structure."""
half_length = length // 2
# Create coordinate tensors
x = torch.arange(-half_length, half_length + 1, dtype=torch.float32)
y = torch.arange(-half_length, half_length + 1, dtype=torch.float32)
xx, yy = torch.meshgrid(x, y, indexing='ij')
# Rotation to align with orientation theta
theta=torch.tensor(theta)
cos_theta = torch.cos(theta)
sin_theta = torch.sin(theta)
nx = xx * cos_theta + yy * sin_theta
ny = -xx * sin_theta + yy * cos_theta
# Gaussian envelope
len_norm = 1.0 / (2.0 * sigma_len**2)
wd_norm = 1.0 / (2.0 * sigma_wd**2)
gauss = torch.exp(-(len_norm * nx**2 + wd_norm * ny**2))
# Sinusoidal wave
two_pi_norm = (2.0 * torch.pi) / wavelength
phase_rad = torch.tensor(phase_deg * torch.pi / 180.0) # Convert degrees to radians
sin_wave = torch.sin(two_pi_norm * ny + phase_rad)
val = gauss * sin_wave
return val
filters = []
num_filters = 16
for i in range(num_filters):
kernel = create_gabor_kernel(9,6,i/16.0*torch.pi*2.0,0.3*9,0.2*9,0)
filters.append(kernel)
# Stack into [16, 1, 9, 9]
gabor_filters_tensor = torch.stack(filters).unsqueeze(1)
input_image=next_frame(1)
v1Input=F.conv2d(
input=input_image,
weight=gabor_filters_tensor,
bias=None,
stride=1,
padding=4 # (9-1)/2 = 4, to keep spatial dimensions same
)
plt.imshow(v1Input[0,5])