SOM自组织映射实战:Python代码实现高维数据聚类与可视化
2026/9/23 21:34:55 网站建设 项目流程

简介:这份资源面向机器学习初学者与需要做数据降维、聚类可视化的开发者,提供自组织映射(SOM)算法的完整Python实现。SOM是一种无监督神经网络方法,可将高维数据映射到二维网格并保持拓扑结构,常用于数据探索与特征可视化。压缩包共31个文件,约3.55MB,以9个py源码文件为核心,涵盖网络初始化、BMU查找、权重更新及高斯、墨西哥帽、气泡等多种邻域函数,并支持指数与线性衰减策略;另有10个png可视化结果、5个csv示例数据集及2个md说明文档,便于直接运行与对照理解。代码内置权重网格、U-Matrix、激活图、训练历史等可视化,并提供量化误差、拓扑误差、邻域保持度等评估指标,示例覆盖螺旋数据聚类、鸢尾花降维与RGB颜色聚类。已有84人学习,适合希望快速上手SOM并复现实验的读者。

1. 从一张二维网格看懂 SOM:它到底在解决什么聚类问题

高维数据聚类最让人头疼的不是算不出来,而是算完了没法看。K-Means 给你一堆标签,PCA 给你两个主成分,但数据的拓扑关系、簇与簇之间的过渡带、离群点落在哪,往往还是黑匣子。自组织映射(Self-Organizing Map,SOM)走的是另一条路:它把高维样本映射到一张二维网格上,让相似样本在网格上靠得近,不相似的被推开,同时保留原始空间的邻域关系。这份 Python 代码包把 SOM 从初始化、BMU 查找、邻域衰减到权重更新整条链路都实现了,还配了权重网格、U-Matrix、激活图、训练历史等可视化,以及量化误差、拓扑误差、邻域保持度、网络利用率四类评估指标。适合两类人:一类是想拿现成代码跑通聚类和降维可视化的从业者,另一类是想拆开看 SOM 内部每一步怎么算、参数怎么调的学习者。压缩包里 main.py、demo.py、examples/basic_example.py 三个入口分别对应完整演示、快速演示和最小验证,data 目录下 circles、blobs、spiral、iris、colors 五份数据覆盖了从线性可分到强非线性缠绕的典型场景。

2. 拆开 som.py:BMU 查找、邻域函数与权重更新怎么落地

2.1 网络初始化与 BMU 查找的代码路径

SOM 的第一步是初始化一张二维权重网格。常见做法有两种:随机初始化,或者从样本里随机抽若干条当初始权重。这份代码在 som.py 里把两种都留了口子,默认走随机初始化。权重矩阵的形状是 (grid_h, grid_w, n_features),每个网格节点是一个和输入同维的向量。

BMU(Best Matching Unit)查找是每次迭代的核心:对每个输入样本,遍历所有网格节点算欧氏距离,取最近的那个。代码里用向量化写法避免 Python 循环,逻辑等价于下面这段:

import numpy as np def find_bmu(weights, sample): # weights: (grid_h, grid_w, n_features) # sample: (n_features,) diff = weights - sample # 广播到每个节点 dist_sq = np.sum(diff ** 2, axis=-1) # 每个节点的平方欧氏距离 bmu_idx = np.unravel_index(np.argmin(dist_sq), dist_sq.shape) return bmu_idx, dist_sq[bmu_idx]

逻辑说明:diff 利用广播把样本向量减到每个网格节点上,dist_sq 在最后一维求和得到每个节点的距离平方,argmin 拿到全局最小值的扁平索引,再用 unravel_index 还原成 (行, 列)。参数上唯一要注意的是 axis=-1,它保证求和发生在特征维而不是网格维,写错 axis 会得到完全错误的结果。

2.2 三种邻域函数与两种衰减策略的选型

BMU 找到之后,要更新它和它邻居的权重。邻居范围由邻域函数决定,这份代码支持高斯邻域、墨西哥帽邻域、气泡邻域三种。高斯邻域是最常用的,权重按距离高斯衰减;气泡邻域是硬截断,半径内权重一样、半径外不更新;墨西哥帽邻域会先正后负,让远邻被轻微推开,适合簇间分离要求高的场景。

邻域半径和邻域内权重的衰减策略由学习率与半径衰减控制,代码提供指数衰减和线性衰减两种:

def decay_exponential(initial, iteration, total, rate=0.05): # 指数衰减:前期下降快,后期趋于平缓 return initial * np.exp(-rate * iteration / total) def decay_linear(initial, iteration, total): # 线性衰减:均匀下降,适合迭代次数不多的场景 return initial * (1 - iteration / total)

逻辑说明:指数衰减的 rate 控制下降陡峭程度,rate 越大前期收缩越快;线性衰减没有额外参数,行为可预测。参数上,学习率初始值一般取 0.5 到 1.0,半径初始值取网格对角线的一半左右,最终都衰减到一个很小的值(学习率约 0.01,半径约 1)。选型上,如果数据簇边界模糊、想让网络更平滑,用高斯邻域配指数衰减;如果迭代轮数少、想每轮都稳定更新,用气泡邻域配线性衰减。

2.3 权重更新与训练循环的完整步骤

权重更新公式是:新权重 = 旧权重 + 学习率 × 邻域权重 × (样本 - 旧权重)。邻域权重由邻域函数根据节点到 BMU 的网格距离算出。完整训练循环如下:

for it in range(total_iter): lr = decay_exponential(lr0, it, total_iter) radius = decay_exponential(r0, it, total_iter) for sample in data: bmu_idx, _ = find_bmu(weights, sample) for i in range(grid_h): for j in range(grid_w): grid_dist = np.sqrt((i - bmu_idx[0])**2 + (j - bmu_idx[1])**2) if grid_dist <= radius: h = np.exp(-(grid_dist**2) / (2 * radius**2)) # 高斯邻域 weights[i, j] += lr * h * (sample - weights[i, j])

逻辑说明:外层控制衰减,内层对每个样本找 BMU 再更新邻域。grid_dist 是网格空间的距离,不是特征空间距离,这点新手容易搞混。参数上,total_iter 一般取 1000 到 10000,数据量大就往上加;radius 必须随迭代收缩,否则后期网络会一直抖动不收敛。实际代码里内层双重循环做了向量化,但理解时按这个朴素版本看最清楚。

3. 跑通 main.py 与 basic_example.py:从安装到出图的完整流程

3.1 环境准备与依赖安装

拿到压缩包后先解压,目录结构里 src 放核心实现(som.py、visualizer.py、utils.py),data 放五份数据集,examples 放示例脚本,output 是出图目录,tests 放单元测试。依赖清单在 requirements.txt 里,主要是 numpy、matplotlib、pandas 这几个。

# 建议先建虚拟环境,避免污染全局 python -m venv som_env source som_env/bin/activate # Windows 用 som_env\Scripts\activate pip install -r requirements.txt

逻辑说明:虚拟环境不是必须,但 SOM 依赖 numpy 版本,全局环境里如果有旧版 numpy 容易冲突。参数上 requirements.txt 没锁死版本,如果安装后报 numpy 相关的 API 错误,常见做法是手动指定 numpy>=1.20。装完先跑最小验证:

python examples/basic_example.py

这个脚本用简单数据跑一遍训练并出图,成功的话 output 目录会多出 simple_data_mapping.png、simple_training_history.png、simple_weights_grid.png、simple_u_matrix.png 四张图。看到图就说明环境通了。

3.2 完整演示 main.py 的四个示例

main.py 会依次跑螺旋数据聚类、鸢尾花降维、RGB 颜色聚类、参数比较分析。螺旋数据是最能体现 SOM 拓扑保持能力的场景,因为螺旋是强非线性缠绕,K-Means 这类基于距离的硬聚类很容易切错,而 SOM 靠邻域更新能把螺旋在网格上摊开。

python main.py

跑完后 output 目录会生成 spiral_data_mapping.png、spiral_som_demo.png、spiral_weights_grid.png、spiral_u_matrix.png、spiral_training_history.png、color_som_demo.png 等图。其中 U-Matrix 值得重点看:它把每个网格节点到邻居的平均距离画成热力图,距离大的地方就是簇边界,颜色深的沟壑把不同簇分开。如果 U-Matrix 上沟壑清晰、簇内颜色均匀,说明训练到位;如果一片糊,多半是迭代不够或学习率衰减太快。

3.3 换自己的数据要改哪几个参数

想拿自己的 CSV 跑,最省事的路径是照着 examples/basic_example.py 改。核心就三步:读数据、建 SOM 对象、训练加可视化。

import pandas as pd from src.som import SOM from src.visualizer import SOMVisualizer df = pd.read_csv("data/your_data.csv") data = df.select_dtypes(include=[float, int]).values # 只取数值列 data = (data - data.mean(axis=0)) / data.std(axis=0) # 标准化,别跳过 som = SOM(grid_h=10, grid_w=10, n_features=data.shape[1], neighborhood="gaussian", decay="exponential", learning_rate=0.5, radius=None, iterations=5000) som.train(data) viz = SOMVisualizer(som) viz.plot_u_matrix(save_path="output/my_u_matrix.png") viz.plot_weights_grid(save_path="output/my_weights_grid.png")

逻辑说明:标准化这步别省,SOM 用欧氏距离,量纲差一个数量级就会让某个特征主导整个映射。grid 尺寸按样本量估,常见做法是节点数约为样本数的 5 到 10 倍再开方,比如 1000 条样本用 10×10 到 15×15。参数上 neighborhood 选 gaussian 最稳,decay 选 exponential 配 iterations=5000 起步;radius 留 None 时代码会按网格尺寸自动设初值。n_features 必须和数据列数一致,写错会在训练时抛维度不匹配。

4. 避坑与排查:SOM 训练里最容易翻车的五个点

4.1 现象:所有样本映射到同一个网格节点

原因:学习率或邻域半径初始值太小,或者迭代次数严重不足,权重还没分化开就停了。解决:把 learning_rate 初始值提到 0.5 以上,radius 设为网格对角线的一半,iterations 至少 1000 起步。如果数据维度很高,先做标准化再训练。

4.2 现象:U-Matrix 一片均匀,看不出簇边界

原因:邻域半径衰减太慢,网络被过度平滑,簇之间的差异被抹平。解决:把半径衰减速率调大,或者改用气泡邻域做硬截断。也可以减少网格节点数,节点太密会让每个簇占太多格子,边界反而不明显。

4.3 现象:训练历史曲线后期剧烈震荡不收敛

原因:学习率衰减到底后没有保持一个下限,或者半径衰减到小于 1 之后还在更新远邻。解决:给学习率设一个最小下限(如 0.01),半径衰减到 1 附近就固定住。常见做法是前 80% 迭代衰减,后 20% 保持小学习率微调。

4.4 现象:换自己的数据后报维度不匹配

原因:n_features 写成了原始 CSV 的列数,但实际喂进去的是筛选后的数值列,列数对不上。解决:先打印 data.shape 确认列数,再把这个值传给 n_features。分类列要么做独热编码,要么直接丢掉,别硬塞进 SOM。

4.5 现象:可视化出图是空白或只有坐标轴

原因:matplotlib 后端在无图形界面环境下没切到 Agg,或者 save_path 目录不存在。解决:在脚本开头加 matplotlib.use("Agg"),并确保 output 目录已创建。如果是在服务器上跑,这一步几乎必踩。

5. 用评估指标验证聚类质量:别只看图说话

图好看不代表聚类对。这份代码给了四个指标,量化误差(QE)衡量样本到 BMU 的平均距离,越小说明映射越贴合数据;拓扑误差(TE)衡量 BMU 和第二近节点是否在网格上相邻,越小说明拓扑保持越好;邻域保持度看原始空间近邻在网格上是否还近;网络利用率看有多少节点被激活过,太低说明网格开大了浪费,太高说明网格不够用。

qe = som.quantization_error(data) te = som.topological_error(data) util = som.network_utilization(data) print(f"QE={qe:.4f} TE={te:.4f} 利用率={util:.2%}")

逻辑说明:QE 和 TE 要一起看。QE 低但 TE 高,说明映射贴合但拓扑乱了,多半是邻域半径衰减太快;QE 高但 TE 低,说明拓扑保住了但映射粗糙,多半是迭代不够或网格太小。利用率低于 50% 就把网格缩小,高于 90% 就放大。我一般会先跑一轮看这四个数,再决定要不要调网格尺寸和迭代次数,比反复出图猜要快得多。

从那以后我每次跑 SOM 都强制先看 QE、TE 和利用率这三个数,图只用来做最终确认。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询