Spaces:
Sleeping
Sleeping
| """ | |
| L1 稀疏突触微柱 | |
| 随机选择5-10%神经元对建立突触连接 | |
| 支持赫布学习: ΔW = η·(pre·post - λ·W) | |
| 特点: 模拟生物大脑的节能模式 | |
| 参数: ~1K/微柱 | |
| 适用: 感觉/运动/丘脑区 | |
| """ | |
| import numpy as np | |
| from typing import Dict, Optional | |
| class SparseSynapticMicroColumn: | |
| """L1稀疏突触微柱 - 神经元间有稀疏突触连接""" | |
| # 神经元类型比例(按功能柱类型) | |
| NEURON_RATIOS = { | |
| 'sensory': {'E': 0.75, 'I': 0.20, 'M': 0.05}, | |
| 'memory': {'E': 0.80, 'I': 0.15, 'M': 0.05}, | |
| 'detector': {'E': 0.70, 'I': 0.20, 'M': 0.10}, | |
| 'integrator': {'E': 0.70, 'I': 0.15, 'M': 0.15}, | |
| 'selector': {'E': 0.70, 'I': 0.20, 'M': 0.10}, | |
| 'motor': {'E': 0.80, 'I': 0.15, 'M': 0.05}, | |
| 'modulator': {'E': 0.45, 'I': 0.25, 'M': 0.30}, | |
| } | |
| def __init__(self, column_type: str = 'sensory', num_neurons: int = 100, | |
| sparsity: float = 0.08, learning_rate: float = 0.005, | |
| decay_rate: float = 0.001, | |
| receptive_field_size: int = None, | |
| receptive_field_offset: int = 0): | |
| """ | |
| Args: | |
| column_type: 功能类型 | |
| num_neurons: 神经元数量 | |
| sparsity: 突触连接稀疏度(5-10%) | |
| learning_rate: 赫布学习率 | |
| decay_rate: 权重衰减率 | |
| receptive_field_size: 感受野大小(None=全连接, int=只连接部分输入维度) | |
| receptive_field_offset: 感受野起始偏移(分块式: 每个mc负责不同输入区域) | |
| """ | |
| self.column_type = column_type | |
| self.num_neurons = num_neurons | |
| self.sparsity = sparsity | |
| self.learning_rate = learning_rate | |
| self.decay_rate = decay_rate | |
| self.name = f"SparseSynaptic-{column_type}" | |
| # 神经元分组 | |
| ratios = self.NEURON_RATIOS.get(column_type, self.NEURON_RATIOS['sensory']) | |
| self.n_e = int(num_neurons * ratios['E']) | |
| self.n_i = int(num_neurons * ratios['I']) | |
| self.n_m = num_neurons - self.n_e - self.n_i | |
| # 神经元状态 | |
| self.membrane = np.zeros(num_neurons, dtype=np.float32) | |
| self.threshold = np.full(num_neurons, 1.0, dtype=np.float32) | |
| self.refractory = np.zeros(num_neurons, dtype=np.float32) | |
| # === 稀疏突触权重矩阵 === | |
| # 4类突触连接: E→E, E→I, I→E, M→E | |
| self.W_ee = self._create_sparse(self.n_e, self.n_e) | |
| self.W_ei = self._create_sparse(self.n_e, self.n_i) | |
| self.W_ie = self._create_sparse(self.n_i, self.n_e) | |
| self.W_me = self._create_sparse(self.n_m, self.n_e) | |
| # 输入投影矩阵(外部输入→E神经元)— 支持感受野 | |
| self.receptive_field_size = receptive_field_size | |
| self.receptive_field_offset = receptive_field_offset | |
| self.W_input = np.random.randn(self.n_e, num_neurons).astype(np.float32) * 0.1 | |
| # 应用感受野: 随机选择rf_size个输入维度(列) | |
| # 注意: W_input shape=(n_e, num_neurons), 实际输入维度=input_dim(≤num_neurons) | |
| if receptive_field_size is not None and receptive_field_size < num_neurons: | |
| # 用offset做种子,确保可复现但每个mc不同 | |
| rng = np.random.RandomState(receptive_field_offset + 42) | |
| # 只在真实输入维度范围内选择(避免选到padding区域) | |
| effective_input_dim = min(receptive_field_size * 7, num_neurons) # 覆盖约7倍RF大小 | |
| rf_indices = sorted(rng.choice(effective_input_dim, receptive_field_size, replace=False)) | |
| # W_input shape = (n_e, num_neurons), 列=输入维度 | |
| col_mask = np.zeros_like(self.W_input) | |
| col_mask[:, rf_indices] = 1 | |
| self.W_input = self.W_input * col_mask | |
| # 学习统计 | |
| self._forward_count = 0 | |
| self._hebb_updates = 0 | |
| def _create_sparse(self, n_pre: int, n_post: int) -> np.ndarray: | |
| """创建稀疏突触权重矩阵""" | |
| n_synapses = max(1, int(n_pre * n_post * self.sparsity)) | |
| W = np.zeros((n_pre, n_post), dtype=np.float32) | |
| # 随机选择突触位置 | |
| indices = np.random.choice(n_pre * n_post, n_synapses, replace=False) | |
| rows, cols = np.divmod(indices, n_post) | |
| # 初始权重: 兴奋性正, 抑制性负 | |
| W[rows, cols] = np.random.randn(n_synapses).astype(np.float32) * 0.1 | |
| # 记录哪些位置有突触(学习时只更新这些位置) | |
| mask = np.zeros_like(W, dtype=bool) | |
| mask[rows, cols] = True | |
| self._mask_ee = None # 稍后设置 | |
| W[~mask] = 0 # 确保非突触位置为零 | |
| return W | |
| def _create_sparse_with_mask(self, n_pre: int, n_post: int) -> tuple: | |
| """创建稀疏矩阵+掩码""" | |
| n_synapses = max(1, int(n_pre * n_post * self.sparsity)) | |
| W = np.zeros((n_pre, n_post), dtype=np.float32) | |
| indices = np.random.choice(n_pre * n_post, n_synapses, replace=False) | |
| rows, cols = np.divmod(indices, n_post) | |
| W[rows, cols] = np.random.randn(n_synapses).astype(np.float32) * 0.1 | |
| mask = np.zeros((n_pre, n_post), dtype=bool) | |
| mask[rows, cols] = True | |
| return W, mask | |
| def forward(self, inputs: np.ndarray, learn: bool = False) -> np.ndarray: | |
| """前向传播 | |
| Args: | |
| inputs: 外部输入信号 | |
| learn: 是否执行赫布学习(默认False,由learn()显式调用) | |
| """ | |
| frozen = getattr(self, '_frozen', False) | |
| if frozen: | |
| learn = False | |
| x = np.asarray(inputs, dtype=np.float32).flatten() | |
| if len(x) < self.num_neurons: | |
| x = np.pad(x, (0, self.num_neurons - len(x))) | |
| elif len(x) > self.num_neurons: | |
| x = x[:self.num_neurons] | |
| # Step 1: 外部输入 → E神经元 | |
| e_input = self.W_input @ x | |
| # Step 2: 突触传播(2个时间步) | |
| # 时间步1: E→I, E→E | |
| i_input = self.W_ee.T[:self.n_i] @ e_input if self.n_i > 0 else np.zeros(0) | |
| e_lateral = self.W_ee @ e_input | |
| # E神经元激活 — 使用ReLU保留稀疏性和分化 | |
| e_activation = np.maximum(0, e_input + e_lateral) | |
| # I神经元激活 | |
| i_activation = np.maximum(0, i_input) if self.n_i > 0 else np.zeros(0) | |
| # 时间步2: I→E抑制, M→E调节 | |
| e_inhibition = self.W_ie.T @ i_activation if self.n_i > 0 else np.zeros(self.n_e) | |
| e_modulation = self.W_me.T @ np.tanh(x[:self.n_m]) if self.n_m > 0 else np.zeros(self.n_e) | |
| # 最终E输出 — 单层激活(去掉第二层tanh压缩) | |
| e_output = np.maximum(0, e_activation - e_inhibition + e_modulation) | |
| # Winner-Take-All竞争: 只保留top-k激活,其余置零 | |
| k = max(1, int(self.n_e * 0.3)) # 保留前30% | |
| if k < self.n_e: | |
| top_k_idx = np.argpartition(np.abs(e_output), -k)[-k:] | |
| mask = np.zeros(self.n_e, dtype=np.float32) | |
| mask[top_k_idx] = 1.0 | |
| e_output = e_output * mask | |
| # 输出标准化: 归一化到单位球面,保留方向信息 | |
| out_norm = np.linalg.norm(e_output) | |
| if out_norm > 1e-6: | |
| e_output = e_output / out_norm | |
| # Step 3: 赫布学习 — 由learn()显式调用,forward中不自动学习 | |
| # (原设计: if learn: self._hebbian_update(...)) | |
| # 改为: forward只做推理,learn()负责学习,避免forward隐式修改权重 | |
| # 缓存激活用于外部训练 | |
| self._last_pre = e_input.copy() | |
| self._last_post = e_output.copy() | |
| self._last_i_act = i_activation.copy() if self.n_i > 0 else np.zeros(0) | |
| # Step 4: 更新膜电位 | |
| self.membrane[:self.n_e] = e_output | |
| if self.n_i > 0: | |
| self.membrane[self.n_e:self.n_e+self.n_i] = i_activation | |
| if self.n_m > 0: | |
| self.membrane[self.n_e+self.n_i:] = np.tanh(x[:self.n_m]) | |
| # 不应期衰减 | |
| self.refractory = np.maximum(0, self.refractory - 1) | |
| self._forward_count += 1 | |
| return self.membrane.copy() | |
| def _hebbian_update(self, pre: np.ndarray, post: np.ndarray, | |
| i_activation: np.ndarray): | |
| """改进赫布学习: Oja规则 + 去相关 + 行级L2归一化 | |
| 核心改进: | |
| 1. Oja规则 ΔW = η·(pre·post - post²·W) 基础学习 | |
| 2. 反赫布去相关: 推开与最近pattern的相似度,实现分化 | |
| 3. 行级L2归一化防止权重膨胀 | |
| """ | |
| lr = self.learning_rate | |
| # E→E突触: Oja规则 (ΔW_ij = η·(x_i·y_j - y_j²·W_ij)) | |
| n_pre = min(len(pre), self.W_ee.shape[0]) | |
| n_post = min(len(post), self.W_ee.shape[1]) | |
| pre_slice = pre[:n_pre] | |
| post_slice = post[:n_post] | |
| # Oja更新 | |
| post_sq = post_slice ** 2 | |
| oja_term = np.outer(np.ones(n_pre), post_sq) * self.W_ee[:n_pre, :n_post] | |
| delta = lr * (np.outer(pre_slice, post_slice) - oja_term) | |
| # 反赫布去相关: 如果输出与最近历史太相似,推开 | |
| if not hasattr(self, '_output_history'): | |
| self._output_history = [] | |
| self._output_history.append(post_slice.copy()) | |
| if len(self._output_history) > 10: | |
| self._output_history = self._output_history[-10:] | |
| if len(self._output_history) >= 2: | |
| # 计算当前输出与历史均值的相似度 | |
| mean_post = np.mean(self._output_history[:-1], axis=0) | |
| cos_sim = np.dot(post_slice, mean_post) / (np.linalg.norm(post_slice) * np.linalg.norm(mean_post) + 1e-8) | |
| # 相似度越高,去相关力越强 | |
| decorr_strength = lr * 0.5 * max(0, cos_sim) | |
| if decorr_strength > 0: | |
| # 推开: 减弱与历史均值方向的连接 | |
| decorr_delta = decorr_strength * np.outer(pre_slice, mean_post[:n_post]) | |
| delta -= decorr_delta | |
| # 梯度裁剪 | |
| max_delta = 0.1 * np.abs(self.W_ee[:n_pre, :n_post]) + 1e-6 | |
| delta = np.clip(delta, -max_delta, max_delta) | |
| self.W_ee[:n_pre, :n_post] += delta | |
| # 行级L2归一化 | |
| row_norms = np.linalg.norm(self.W_ee, axis=1, keepdims=True) | |
| row_norms = np.maximum(row_norms, 1e-8) | |
| self.W_ee = self.W_ee / row_norms * np.clip(row_norms, 0, 1.5) | |
| # E→I突触: 标准赫布+衰减 | |
| if self.n_i > 0 and self.W_ei.size > 0: | |
| self.W_ei += lr * (np.outer(pre[:self.W_ei.shape[0]], i_activation[:self.W_ei.shape[1]]) - self.decay_rate * self.W_ei) | |
| self.W_ei = np.clip(self.W_ei, -1.5, 1.5) | |
| # I→E突触: 标准赫布+衰减 | |
| if self.n_i > 0 and self.W_ie.size > 0: | |
| self.W_ie += lr * (np.outer(i_activation[:self.W_ie.shape[0]], post[:self.W_ie.shape[1]]) - self.decay_rate * self.W_ie) | |
| self.W_ie = np.clip(self.W_ie, -1.5, 1.5) | |
| self._hebb_updates += 1 | |
| def get_synapse_count(self) -> int: | |
| """获取突触总数""" | |
| count = 0 | |
| for W in [self.W_ee, self.W_ei, self.W_ie, self.W_me]: | |
| count += np.count_nonzero(W) | |
| return count | |
| def get_param_count(self) -> int: | |
| """获取可学习参数总数""" | |
| params = 0 | |
| for W in [self.W_ee, self.W_ei, self.W_ie, self.W_me, self.W_input]: | |
| params += W.size | |
| params += self.threshold.size | |
| return params | |
| def get_config(self) -> Dict: | |
| return { | |
| 'type': self.name, | |
| 'tier': 'L1', | |
| 'column_type': self.column_type, | |
| 'num_neurons': self.num_neurons, | |
| 'neurons': {'E': self.n_e, 'I': self.n_i, 'M': self.n_m}, | |
| 'sparsity': self.sparsity, | |
| 'synapse_count': self.get_synapse_count(), | |
| 'param_count': self.get_param_count(), | |
| 'learning_rate': self.learning_rate, | |
| 'forward_count': self._forward_count, | |
| 'hebb_updates': self._hebb_updates, | |
| } | |
| def reset(self): | |
| """重置状态(保留突触权重)""" | |
| self.membrane = np.zeros(self.num_neurons, dtype=np.float32) | |
| self.refractory = np.zeros(self.num_neurons, dtype=np.float32) | |
| def learn(self): | |
| """执行赫布学习(供v3微柱调用) | |
| 改进:使用真实缓存的输入/输出信号,而非近似值 | |
| """ | |
| # 优先使用真实缓存的输入输出信号 | |
| pre = getattr(self, '_last_pre', None) | |
| post = getattr(self, '_last_post', None) | |
| if pre is not None and post is not None and len(pre) > 0 and len(post) > 0: | |
| # 使用真实信号 | |
| e_input = pre[:self.n_e] if len(pre) >= self.n_e else pre | |
| e_output = post[:self.n_e] if len(post) >= self.n_e else post | |
| else: | |
| # 回退:使用当前膜电位 | |
| if self._forward_count > 0: | |
| e_output = self.membrane[:self.n_e] | |
| e_input = self.W_input @ np.ones(self.num_neurons, dtype=np.float32) * 0.5 | |
| else: | |
| return | |
| i_activation = self.membrane[self.n_e:self.n_e+self.n_i] if self.n_i > 0 else np.zeros(0) | |
| self._hebbian_update(e_input, e_output, i_activation) | |
| def total_params(self) -> int: | |
| """可学习参数数量(突触权重)""" | |
| return sum(w.size for w in [ | |
| self.W_ee, self.W_ei, self.W_ie, self.W_me, self.W_input | |
| ]) | |