voxel mountain
学校は最悪なこともある。多くの人がそう思っている。でも、学校は学ぶための最良の方法ではない。伝統的な学校はたった200年前に始まっただけで、人類は何千年も存在していた。学校は個々の興味に合わないことも多い、硬直したカリキュラムを押し付ける。『無駄』なことを強制的に学ばせるのは自然な好奇心を殺す。自分で学ぶことは、自分の情熱や目的を発見する助けになる—学校はむしろその逆をすることが多い。授業で受け身で聞くだけよりも、実際に手を動かして学ぶ方がはるかに効果的。宿題や成績は余計なストレスを生み、学ぶことが義務のように感じられてしまう。好奇心があると、強制的に暗記するよりずっと速く情報を吸収できる。独学で得たスキル(例えばプログラミング!)は充実したキャリアに繋がることが多い。自由に探求することで創造力が育まれる—これは学校ではよく抑えられてしまう。詳しくはこちら:
なぜ遊ぶ本能を解放すると子どもはより幸せで自立し、長く良い学習者になるのか
啓蒙時代後の自己教育
学生の教育の自由の権利
教育に反対する理由
ホームスクーリングに関する研究レビュー
if you want to know more about this, go to https://glub.chat and talk to @aswq
script
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')
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):
return DenseConnection(torch.rand(sender.size,receiver.size),scale/sender.size)
def make2(sender,receiver,scale=1.0):
w=torch.rand(sender.size,receiver.size)
return DenseConnection(w,scale/sender.size),DenseConnection(w.T,scale/receiver.size)
def forward(self, x): return [email protected]*self.scale
def updateWeight(self, sender,receiver):
change = sender.act[:,None]*receiver.act[None,:] - sender.minusPhaseAct[:,None]*receiver.minusPhaseAct[None,:]
self.w += change*lr
self.w.clamp(0,1)
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,:] - senderMinusPhase[:,None]*receiver.minusPhaseAct[None,:]
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, w, 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
"""
self.w = w
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
self.neighborhood_offsets = [(dx, dy) for dx in [-neighborhood_size, neighborhood_size+1] for dy in [-neighborhood_size, neighborhood_size+1]]
# Fix: neighborhood_offsets should be correct
self.neighborhood_offsets = []
for dx in range(-neighborhood_size, neighborhood_size+1):
for dy in range(-neighborhood_size, neighborhood_size+1):
self.neighborhood_offsets.append((dx, dy))
@classmethod
def make(cls, sender, receiver,
stride_x=1, stride_y=1, scale=1.0, neighborhood_size=1):
"""
Creates a local pool connection.
"""
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 = round(rx * stride_x) + dx
spy = round(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
return cls(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)
@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
new_offsets = conn.neighborhood_offsets
return conn,cls(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,
conn.stride_x, conn.stride_y, conn.neighborhood_size)
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):
"""
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 - change_minus
self.w += change * lr
self.w.clamp_(0, 1)
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.w) * target_diff_vals.abs() * adaptrate
self.w += target_update
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 - change_minus
self.w += change * lr
self.w.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.w) * target_diff_vals.abs() * adaptrate
self.w += target_update
class Layer:
def __init__(self, shape):
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.minusPhaseAct=None
self.act=None
self.prevAct=torch.zeros(self.size)
self.avgAct = torch.zeros(self.size)
def update(self, input):
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.target = self.avgAct.reshape(self.shape).mean(inPoolDim,keepdim=True).expand(self.shape).flatten()
self.targetDiff = self.target - self.avgAct
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))
E1=Layer((8,8,1))
A1=Layer((4,4,40))
inpp=Layer(inpLayer.shape)
V1p=Layer(V1.shape)
V2p=Layer(V2.shape)
V3p=Layer(V3.shape)
V4p=Layer(V4.shape)
inpToV1,V1ToInp=LocalPoolConnection.make2(inpLayer,V1, scale=8.0)
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)
v4ToV3p=LocalPoolConnection.make(V4,V3p, scale=8.0)
v3ToV2p=LocalPoolConnection.make(V3,V2p, stride_x=0.5,stride_y=0.5, scale=8.0)
v2ToV1p=LocalPoolConnection.make(V2,V1p, scale=8.0)
v1ToInpp=LocalPoolConnection.make(V1,inpp, scale=8.0)
inppToV1=LocalPoolConnection.make(inpp,V1, scale=0.1*8.0)
v1pToV2=LocalPoolConnection.make(V1p,V2, scale=0.1*8.0)
v2pToV3=LocalPoolConnection.make(V2p,V3, stride_x=2,stride_y=2, scale=0.1*8.0)
v3pToV4=LocalPoolConnection.make(V3p,V4, scale=0.1*8.0)
v1ToE1,E1ToV1=LocalPoolConnection.make2(V1,E1, scale=1.0)
E1ToV1p=LocalPoolConnection.make(E1,V1p, scale=1.0)
v1pToE1=LocalPoolConnection.make(V1p,E1, scale=0.1*1.0)
v4ToA1,A1ToV4=DenseConnection.make2(V4,A1, scale=32.0)
A1ToV4p=DenseConnection.make(A1,V4p, scale=32.0)
v4pToA1=DenseConnection.make(V4p,A1, scale=0.1*32.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):
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(start_dim=1)
ax.imshow(v, vmin=0, vmax=1)
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)
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
)
v1Input=F.avg_pool2d(v1Input, kernel_size=4, stride=4).permute(0,2,3,1).flatten() / 32.0
v1pa=V1p.update(v2ToV1p.forward(V2.prevAct)+E1ToV1p.forward(E1.prevAct))
v2pa=V2p.update(v3ToV2p.forward(V3.prevAct))
V3p.update(v4ToV3p.forward(V4.prevAct))
V4p.update(A1ToV4p.forward(A1.prevAct))
# use layer.act instead of layera in the future
v1a=V1.update(v1Input)
v2a=V2.update(v1ToV2.forward(v1a)+v1pToV2.forward(v1pa))
v3a=V3.update(v2ToV3.forward(v2a)+v2pToV3.forward(v2pa))
V4.update(v3ToV4.forward(V3.act)+v3pToV4.forward(V3p.act))
E1.update(v1ToE1.forward(V1.act)+v1pToE1.forward(V1p.act))
A1.update(v4ToA1.forward(V4.act)+v4pToA1.forward(V4p.act))
#spread backward so it spreads more completely
V4.update(A1ToV4.forward(A1.act)+v3ToV4.forward(V3.act)+v3pToV4.forward(V3p.act))
V3.update(V4ToV3.forward(V4.act)+v2ToV3.forward(V2.act)+v2pToV3.forward(V2p.act))
v2a=V2.update(V3ToV2.forward(V3.act)+v1ToV2.forward(v1a)+v1pToV2.forward(v1pa))
v1a=V1.update(E1ToV1.forward(E1.act)+V2ToV1.forward(v2a)+v1Input)
# do again but clamp p layers
V1.minusPhaseAct = V1.act
V2.minusPhaseAct = V2.act
V3.minusPhaseAct = V3.act
V4.minusPhaseAct = V4.act
E1.minusPhaseAct = E1.act
A1.minusPhaseAct = A1.act
V1p.minusPhaseAct=V1p.act
V2p.minusPhaseAct=V2p.act
V3p.minusPhaseAct=V3p.act
V4p.minusPhaseAct=V4p.act
V1p.act=v1a
V2p.act=v2a
V3p.act=V3.act
V4p.act=V4.act
# use layer.act instead of layera in the future
v1a=V1.update(v1Input)
v2a=V2.update(v1ToV2.forward(v1a)+v1pToV2.forward(v1pa))
v3a=V3.update(v2ToV3.forward(v2a)+v2pToV3.forward(v2pa))
V4.update(v3ToV4.forward(V3.act)+v3pToV4.forward(V3p.act))
E1.update(v1ToE1.forward(V1.act)+v1pToE1.forward(V1p.act))
A1.update(v4ToA1.forward(V4.act)+v4pToA1.forward(V4p.act))
#spread backward so it spreads more completely
V4.update(A1ToV4.forward(A1.act)+v3ToV4.forward(V3.act)+v3pToV4.forward(V3p.act))
V3.update(V4ToV3.forward(V4.act)+v2ToV3.forward(V2.act)+v2pToV3.forward(V2p.act))
v2a=V2.update(V3ToV2.forward(V3.act)+v1ToV2.forward(v1a)+v1pToV2.forward(v1pa))
v1a=V1.update(E1ToV1.forward(E1.act)+V2ToV1.forward(v2a)+v1Input)
#update averages
V1.slowUpdate()
V2.slowUpdate()
V3.slowUpdate()
V4.slowUpdate()
E1.slowUpdate()
A1.slowUpdate()
V1p.slowUpdate()
V2p.slowUpdate()
V3p.slowUpdate()
V4p.slowUpdate()
#update weight
v1ToV2.updateWeight(V1,V2)
V2ToV1.updateWeight(V2,V1)
v2ToV3.updateWeight(V2,V3)
V3ToV2.updateWeight(V3,V2)
v3ToV4.updateWeight(V3,V4)
V4ToV3.updateWeight(V4,V3)
v2ToV1p.updateWeightSpecific(V2.prevAct,V2.prevAct,V1p,adaptAct=False)
v3ToV2p.updateWeightSpecific(V3.prevAct,V3.prevAct,V2p,adaptAct=False)
v4ToV3p.updateWeightSpecific(V4.prevAct,V3.prevAct,V2p,adaptAct=False)
v1pToV2.updateWeight(V1p,V2)
v2pToV3.updateWeight(V2p,V3)
v3pToV4.updateWeight(V3p,V4)
v1ToE1.updateWeight(V1,E1)
E1ToV1.updateWeight(E1,V1)
E1ToV1p.updateWeightSpecific(E1.prevAct,E1.prevAct,V1p,adaptAct=False)
v1pToE1.updateWeight(V1p,E1)
v4ToA1.updateWeight(V4,A1)
A1ToV4.updateWeight(A1,V4)
A1ToV4p.updateWeightSpecific(A1.prevAct,A1.prevAct,V4p,adaptAct=False)
v4pToA1.updateWeight(V4p,A1)
#update prevAct
V1.prevAct = V1.act
V2.prevAct = V2.act
V3.prevAct = V3.act
V4.prevAct = V4.act
E1.prevAct = E1.act
A1.prevAct = A1.act
V1p.prevAct = V1p.act
V2p.prevAct = V2p.act
V3p.prevAct = V3p.act
V4p.prevAct = V4p.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[1,0], v1a.view(V1.shape))
showAt(axs[1,1], v1pa.view(V1p.shape))
showAt(axs[1,2], (V1.target*10.0).view(V1.shape))
showAt(axs[2,0], v2a.view(V2.shape))
showAt(axs[2,1], v2pa.view(V2p.shape))
showAt(axs[3,0], V3.act.view(V3.shape))
showAt(axs[3,1], V3p.act.view(V3p.shape))
showAt(axs[4,0], V4.act.view(V4.shape))
showAt(axs[4,1], V4p.act.view(V4p.shape))
showAt(axs[5,0], E1.act.view(E1.shape))
showAt(axs[5,1], A1.act.view(A1.shape))
showAt(axs[5,2], A1.avgAct.view(A1.shape)*10.0)
video_sigs.append(A1.act)
if len(video_sigs)>50:video_sigs.pop(0)
if False 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):
# 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:
obj_bitmap = objects[round(i/10-0.5)%len(objects)]
i=torch.tensor(i)
rot = i*0.4
# Map position to [-1, 1]
pos_x_norm = torch.sin(i*0.5)*2.0
pos_y_norm = torch.sin(i*0.3)*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])