SOM神经网络回归调参疑问:欠拟合有离群点或过拟合贴合原数据
SOM回归拟合x²函数的参数调优与离群点解决思路
我来帮你分析下当前用Somoclu做SOM回归时遇到的问题,以及对应的调优和解决思路:
问题本质拆解
你遇到的两个极端情况其实是SOM回归的典型问题:
- 训练不足:神经元还没充分收敛到数据的真实分布,部分神经元因为拓扑位置(比如二维网格的角落)无法被样本覆盖,就会变成离群点;
- 训练过度:神经元完全贴合原始样本点,失去了SOM原本的插值/泛化能力,变成了样本的“复制”。
而你想要的是能生成插值点的拟合结果,核心是让SOM的神经元既贴合数据分布,又保留连续的拓扑结构,避免极端情况。
一、参数调优方向
1. 调整SOM拓扑结构:改用一维SOM
你现在用的是50×50的二维SOM,但你的任务是拟合单变量函数(x→x²),属于一维回归场景。二维网格的神经元很多处于数据分布的“边缘区域”,很难被样本驱动,自然容易产生离群点。
建议:换成1×N的一维SOM(比如1×100),一维拓扑的神经元是线性排列的,天然对应x轴的连续分布,训练后神经元会按x值顺序排列,既容易拟合曲线,又能避免无意义的离群点。
2. 优化训练策略:用衰减式学习率与邻域半径
固定epochs的训练方式很容易走到欠拟合或过拟合的极端,应该用动态衰减的训练参数:
- 学习率:初始用较大的学习率(比如0.5),让神经元快速向数据分布移动,然后通过指数衰减逐步降低学习率,最后用小学习率精细调整。Somoclu可以通过
learning_rate和decay_function='exponential'实现; - 邻域半径:初始半径设置为覆盖大部分神经元(比如一维100节点的话,初始半径设50),然后逐步衰减到1,这样训练初期神经元整体调整,后期只优化局部,避免过度拟合到单个样本。
3. 调整训练轮次:不用遍历2-100,找最优收敛点
你不需要遍历所有epochs,建议先跑30-50轮衰减式训练,然后观察拟合效果:如果还有离群点,就增加10-20轮;如果已经过度拟合,就减少轮次,或者降低初始学习率。
二、离群点的处理方法
如果坚持用二维SOM,或者训练后还是有离群点,可以试试这些方法:
1. 训练后过滤离群神经元
计算每个神经元到原始样本的最小距离,过滤掉距离过大的点:
from scipy.spatial.distance import cdist # 计算每个codebook点到原始数据的最小欧氏距离 distances = cdist(som_ncodebook, data) min_distances = np.min(distances, axis=1) # 设定阈值(比如取最小距离的95分位数),过滤离群点 threshold = np.percentile(min_distances, 95) filtered_codebook = som_ncodebook[min_distances <= threshold] # 绘制过滤后的结果 plt.plot(filtered_codebook[:,0], filtered_codebook[:,1], 'ob') plt.plot(data[:,0], data[:,1], '.r') plt.show()
2. 约束神经元移动范围
在训练过程中,每隔几轮就把距离样本过远的神经元重置到数据的均值附近,避免它们跑偏:
# 训练过程中加入约束 som = somoclu.Somoclu(n_columns=50, n_rows=50, data=data, initialization='pca') data_mean = np.mean(data, axis=0) for epoch in range(100): som.train(epochs=1, verbose=False) # 检查并重置离群点 distances = cdist(som.codebook.reshape(-1,2), data) min_distances = np.min(distances, axis=1) outlier_idx = np.where(min_distances > np.percentile(min_distances, 99))[0] for idx in outlier_idx: row = idx // 50 col = idx % 50 som.codebook[row, col] = data_mean
三、代码优化建议
你的代码可以简化并优化,比如:
- 替换过时的
as_matrix()为to_numpy(); - 扁平化codebook的逻辑可以简化,不用循环;
- 一维SOM的示例代码:
import numpy as np import matplotlib.pyplot as plt import somoclu import pandas as pd # 读取数据 data = pd.read_csv('10_samples.txt', sep='\t', header=0).to_numpy() # 改用1×100的一维SOM,配置衰减参数 som = somoclu.Somoclu(n_columns=100, n_rows=1, data=data, initialization='pca', learning_rate=0.5, decay_function='exponential', initial_radius=50, radius_decay_function='exponential') som.train(epochs=50) # 扁平化codebook并按x轴排序,方便绘制连续曲线 som_ncodebook = som.codebook.reshape(-1, 2) sorted_codebook = som_ncodebook[np.argsort(som_ncodebook[:, 0])] # 绘图对比 plt.plot(sorted_codebook[:,0], sorted_codebook[:,1], 'ob-', label='SOM拟合曲线') plt.plot(data[:,0], data[:,1], '.r', markersize=10, label='原始样本点') plt.legend() plt.title('一维SOM拟合x²函数') plt.savefig('som_fit_x2.png') plt.close()
这样调整后,你应该能得到更平滑的插值拟合曲线,同时避免离群点和过度拟合的问题。
内容的提问来源于stack exchange,提问作者lelorrain7
相关产品推荐
相关产品推荐

