从竞争学习到 Kohonen 网络理解无监督聚类的数学本质开头不需要标签也能学习上一篇我们学习了 PCA它是有监督的降维需要标签来计算方差。今天我们学习自组织映射SOM——一种无监督的学习算法。有监督学习 输入数据 标签 → 学习映射关系 无监督学习 只有输入数据 → 自动发现数据的结构 SOM 的目标 把高维数据映射到 2D 网格 相似的输入映射到相邻的网格位置 → 自动聚类 数据可视化一、竞争学习1.1 胜者通吃Winner-Takes-All竞争学习的基本思想 ═══════════════════════════════════════════════════════════════════ 输入x 神经元w₁, w₂, ..., wₙ 竞争过程 1. 计算每个神经元与输入的距离 2. 距离最近的神经元「获胜」 3. 只有获胜神经元更新权重 d(x, w₁) ||x - w₁|| d(x, w₂) ||x - w₂|| ... d(x, wₙ) ||x - wₙ|| 获胜者w* argmin d(x, wᵢ) 权重更新 w* w* η (x - w*) 只更新获胜的神经元1.2 与感知器的区别特性感知器竞争学习学习方式监督有标签无监督无标签更新规则所有神经元都更新只有获胜神经元更新目标分类聚类二、Kohonen 网络SOM2.1 网络结构SOM 网络结构 ═══════════════════════════════════════════════════════════════════ 输入层x ∈ R^d高维数据 输出层2D 网格通常是矩形或六边形 输入层 输出层2D 网格 x₁ ──→ ┌─────────────┐ x₂ ──→ │ w₁ w₂ w₃ w₄ │ x₃ ──→ │ w₅ w₆ w₇ w₈ │ │ w₉ w₁₀ w₁₁ w₁₂│ └─────────────┘ 每个网格位置有一个权重向量 wᵢ ∈ R^d2.2 自组织的含义自组织Self-Organizing ═══════════════════════════════════════════════════════════════════ 关键特性相邻神经元也更新 普通竞争学习 只有获胜神经元 w* 更新 → 网格结构没有意义 SOM 获胜神经元 w* 更新 相邻神经元也更新但幅度较小 → 相似的输入映射到相邻位置 影响力随距离衰减 influence(d) exp(-d² / 2σ²) d神经元到获胜者的距离 σ邻域半径2.3 拓扑保持拓扑保持Topology Preserving ═══════════════════════════════════════════════════════════════════ 输入空间中的相似性 → 输出空间中的相邻性 输入空间 输出空间 A B C A B C D E F → D E F G H I G H I 相似的样本A, B, D, E在网格中也相邻三、SOM 学习算法3.1 算法步骤SOM 学习算法 ═══════════════════════════════════════════════════════════════════ 输入 X训练数据 grid_size网格大小 n_iterations迭代次数 步骤 1. 初始化权重 for each grid position (i, j): w(i,j) random vector in R^d 2. 对每个迭代 t a. 选择学习率和邻域半径衰减 lr(t) lr₀ * (1 - t/T) σ(t) σ₀ * (1 - t/T) b. 对每个样本 x i. 找到最佳匹配单元BMU bmu argmin ||x - w(i,j)||₂ ii. 更新 BMU 及其邻域的权重 for each neuron (i, j) in 邻域(bmu): w(i,j) w(i,j) lr(t) * influence(d) * (x - w(i,j)) 3. 输出训练好的权重矩阵3.2 关键参数参数含义典型值grid_size网格大小10×10, 20×20lr₀初始学习率0.1 ~ 0.5σ₀初始邻域半径grid_size/2T总迭代次数1000 ~ 10000四、Python 实现 SOM4.1 完整代码importnumpyasnpimportmatplotlib.pyplotaspltclassSOM:自组织映射SOMdef__init__(self,grid_size,input_dim,learning_rate0.1,sigmaNone): 参数: grid_size: 网格大小整数或元组 input_dim: 输入数据维度 learning_rate: 初始学习率 sigma: 初始邻域半径None 则自动计算 ifisinstance(grid_size,int):self.grid_size(grid_size,grid_size)else:self.grid_sizegrid_size self.input_diminput_dim self.lr0learning_rate self.sigma0sigmaifsigmaelsemax(self.grid_size)/2# 初始化权重self.weightsnp.random.randn(self.grid_size[0],self.grid_size[1],input_dim)*0.1# 记录训练过程self.quantization_errors[]def_find_bmu(self,x):找到最佳匹配单元BMUdistancesnp.linalg.norm(self.weights-x,axis2)bmu_idxnp.unravel_index(np.argmin(distances),distances.shape)returnbmu_idxdef_get_neighborhood(self,bmu_idx,sigma):获取邻域内的神经元i,jbmu_idx neighbors[]foriiinrange(self.grid_size[0]):forjjinrange(self.grid_size[1]):distnp.sqrt((ii-i)**2(jj-j)**2)ifdistsigma:neighbors.append((ii,jj,dist))returnneighborsdeffit(self,X,n_iterations1000): 训练 SOM 参数: X: 训练数据形状 (n_samples, input_dim) n_iterations: 迭代次数 n_samplesX.shape[0]self.quantization_errors[]fortinrange(n_iterations):# 衰减学习率和邻域半径lrself.lr0*(1-t/n_iterations)sigmaself.sigma0*(1-t/n_iterations)# 随机选择样本idxnp.random.randint(n_samples)xX[idx]# 找到 BMUbmu_idxself._find_bmu(x)# 更新 BMU 及邻域neighborsself._get_neighborhood(bmu_idx,sigma)forii,jj,distinneighbors:influencenp.exp(-dist**2/(2*sigma**2))self.weights[ii,jj]lr*influence*(x-self.weights[ii,jj])# 记录量化误差ift%1000:qenp.mean([np.min(np.linalg.norm(X-self.weights[i,j],axis1))foriinrange(self.grid_size[0])forjinrange(self.grid_size[1])])self.quantization_errors.append(qe)defpredict(self,X):预测每个样本的 BMU 位置iflen(X.shape)1:XX.reshape(1,-1)bmu_positions[]forxinX:bmu_idxself._find_bmu(x)bmu_positions.append(bmu_idx)returnbmu_positionsdefget_umatrix(self):计算 U-Matrix用于可视化umatrixnp.zeros(self.grid_size)foriinrange(self.grid_size[0]):forjinrange(self.grid_size[1]):distances[]foriiinrange(max(0,i-1),min(self.grid_size[0],i2)):forjjinrange(max(0,j-1),min(self.grid_size[1],j2)):if(ii,jj)!(i,j):distances.append(np.linalg.norm(self.weights[i,j]-self.weights[ii,jj]))umatrix[i,j]np.mean(distances)ifdistanceselse0returnumatrix4.2 测试聚类fromsklearn.datasetsimportmake_blobs# 生成聚类数据X,y_truemake_blobs(n_samples300,centers4,cluster_std0.60,random_state42)# 训练 SOMsomSOM(grid_size10,input_dim2,learning_rate0.5)som.fit(X,n_iterations1000)# 预测bmu_positionssom.predict(X)# 可视化fig,(ax1,ax2)plt.subplots(1,2,figsize(12,5))# 原始数据ax1.scatter(X[:,0],X[:,1],cy_true,cmapviridis,alpha0.6)ax1.set_title(Original Data)# SOM 网格umatrixsom.get_umatrix()ax2.imshow(umatrix,cmapgray)ax2.set_title(SOM U-Matrix)plt.tight_layout()plt.show()4.3 测试高维数据可视化fromsklearn.datasetsimportload_irisfromsklearn.preprocessingimportStandardScaler# 加载鸢尾花数据4 维irisload_iris()Xiris.data yiris.target# 标准化scalerStandardScaler()X_scaledscaler.fit_transform(X)# 训练 SOMsomSOM(grid_size15,input_dim4,learning_rate0.5)som.fit(X_scaled,n_iterations2000)# 可视化fig,axesplt.subplots(1,3,figsize(15,4))# U-Matrixumatrixsom.get_umatrix()axes[0].imshow(umatrix,cmapgray)axes[0].set_title(U-Matrix)# 激活图hit_mapnp.zeros(som.grid_size)bmu_positionssom.predict(X_scaled)forposinbmu_positions:hit_map[pos]1axes[1].imshow(hit_map,cmaphot)axes[1].set_title(Hit Map)# 标签映射label_mapnp.zeros(som.grid_size)count_mapnp.zeros(som.grid_size)forpos,labelinzip(bmu_positions,y):label_map[pos]label count_map[pos]1label_map[count_map0]/count_map[count_map0]axes[2].imshow(label_map,cmapviridis)axes[2].set_title(Label Map)plt.tight_layout()plt.show()五、SOM vs K-Means5.1 对比表特性SOMK-Means拓扑保持✅ 是❌ 否可视化✅ 2D 网格❌ 无计算复杂度高 O(n × grid²)低 O(n × k)参数数量多grid_size, lr, σ少k结果稳定性高低依赖初始化5.2 什么时候用 SOMSOM 适用场景 ═══════════════════════════════════════════════════════════════════ ✅ 适合 SOM - 需要数据可视化 - 需要拓扑保持 - 数据有非线性结构 - 需要交互式探索 ❌ 不适合 SOM - 数据量大计算慢 - 只需要快速聚类用 K-Means - 网格大小难以选择六、工业应用6.1 数据可视化数据可视化 ═══════════════════════════════════════════════════════════════════ 高维数据可视化 - 原始数据 → SOM → 2D 网格 - 每个网格位置代表一类数据 - 颜色表示类别或属性 应用场景 - 文档聚类可视化 - 图像分割可视化 - 客户分群可视化6.2 聚类分析聚类分析 ═══════════════════════════════════════════════════════════════════ SOM 自动发现数据中的模式 - 相似的样本映射到相邻位置 - 不同的簇在网格中分离 优势 - 不需要预设簇数 - 结果可解释 - 支持增量学习6.3 异常检测异常检测 ═══════════════════════════════════════════════════════════════════ 正常样本映射到常见的网格位置 异常样本映射到罕见的网格位置 方法 1. 训练 SOM 2. 记录每个网格位置的激活次数 3. 激活次数少的位置 → 可能是异常七、避坑指南使用 SOM 的 3 个陷阱坑 1网格大小选择不当 → 分辨率不足错误做法网格太小# ❌ 网格太小somSOM(grid_size3,input_dim100)# 只有 9 个神经元无法区分复杂模式正确做法根据数据量选择# ✅ 经验法则网格边长 ≈ 5√nn_samples1000grid_sizeint(5*np.sqrt(n_samples))# ≈ 158# 或者用较小的网格先探索somSOM(grid_size20,input_dim100)坑 2学习率太大 → 不收敛错误做法learning_rate5# ❌ 学习率太大somSOM(grid_size10,input_dim2,learning_rate5)# 权重剧烈震荡无法收敛正确做法从 0.1~0.5 开始# ✅ 合适的学习率somSOM(grid_size10,input_dim2,learning_rate0.5)坑 3邻域太大 → 模糊错误做法sigma100# ❌ 邻域太大somSOM(grid_size10,input_dim2)som.sigma0100# 所有神经元都更新# 没有局部化无法形成清晰的聚类正确做法从 grid_size/2 开始逐渐衰减# ✅ 自动衰减somSOM(grid_size10,input_dim2)# sigma0 默认为 grid_size/2 5# 训练过程中会逐渐衰减到 0八、本篇总结核心要点回顾竞争学习胜者通吃只有获胜神经元更新SOM 结构输入层 2D 网格输出层自组织相邻神经元也更新保持拓扑结构学习算法找 BMU → 更新邻域 → 衰减学习率和邻域半径拓扑保持相似输入映射到相邻输出SOM vs K-MeansSOM 有拓扑保持K-Means 更快下篇预告下一篇我们学习信息论。SOM 用距离度量相似性信息论用信息量度量不确定性。下一篇你将学到Shannon 信息论基础熵、互信息、KL 散度Infomax 原理独立分量分析ICA用 Python 手写 ICA本期互动你对 SOM 有什么看法你用过 SOM 吗在什么场景下你觉得 SOM 和 t-SNE 有什么区别你知道 SOM 的哪些应用欢迎在评论区留言。系列目录篇标题状态01Haykin 精讲开篇从「只会调参」到「理解神经网络的灵魂」✅ 完成02感知器神经网络的「鼻祖」为什么它能「学会」分类✅ 完成03LMS 算法从最小二乘到随机梯度下降工业自适应滤波的核心✅ 完成04反向传播神经网络为什么能「学习」用 NumPy 手写 BP✅ 完成05核方法为什么 SVM 能处理非线性问题理解「升维」的本质✅ 完成06支持向量机最大间隔的「艺术」为什么它是「小数据之王」✅ 完成07正则化为什么模型越复杂越容易过拟合L1/L2/Dropout✅ 完成08PCA为什么降维能「去噪」从特征值分解到核 PCA✅ 完成09SOM无监督学习的「聚类之王」为什么它能「自组织」✅ 当前10信息论为什么「信息最大化」能学特征从熵到 ICA⏳ 下一篇11玻尔兹曼机深度学习的「前世」从统计力学到 RBM⏳ 待写12动态规划强化学习的「数学基础」从 MDP 到值迭代⏳ 待写13Hopfield 网络联想记忆的「鼻祖」为什么它能「回忆」⏳ 待写14卡尔曼滤波为什么它能「预测」从贝叶斯推断到粒子滤波⏳ 待写15Haykin 精讲终篇从感知器到深度学习——一部神经网络的「进化史」⏳ 待写点赞收藏转发是我持续更新的动力
