1.12 神经网络与保序回归
很多问题没有现成的公式:给定一堆特征,到底用什么函数关系把它们映射成结果?神经网络给出一个通用答案——用大量加权求和加激活的小单元堆叠出任意复杂函数,再靠数据把参数调出来。本节讲 sklearn.neural_network 的多层感知机(MLP)。而另一类问题要求结果必须单调:价格越高销量越低、年龄越大风险越高。sklearn.isotonic 的保序回归专门拟合这样的单调函数。一节讲万能拟合,一节讲受限拟合。
1.12.1 多层感知机的结构
多层感知机(MLP)由若干层神经元组成:输入层接收特征向量,隐藏层逐层加工,输出层给出预测。每一层的每个神经元把上一层的输出加权求和,再加一个偏置,最后过激活函数:
其中
from sklearn.neural_network import MLPClassifier
# 两个隐藏层,每层 10 个神经元
model = MLPClassifier(hidden_layer_sizes=(10, 10), activation='relu',
max_iter=1000, random_state=0)hidden_layer_sizes=(10, 10) 表示两个隐藏层、每层 10 个神经元。层数越深、每层神经元越多,模型表达能力越强,但越容易过拟合,需要更多数据支撑。
1.12.2 激活函数
激活函数给加权求和的结果引入非线性。如果没有激活函数,多层线性变换可以合并成一次线性变换,网络再深也只是一条直线,无法拟合复杂数据。sklearn 支持三种激活函数:
ReLU(修正线性单元)是默认选项,简单且训练快:
tanh(双曲正切)把输出压缩到
logistic(sigmoid)把输出压缩到
for name in ['relu', 'tanh', 'logistic']:
model = MLPClassifier(hidden_layer_sizes=(10,), activation=name,
max_iter=1000, random_state=0)
model.fit(X_train, y_train)
print(name, model.score(X_test, y_test))1.12.3 求解器与训练过程
训练的本质是梯度下降:定义一个损失函数衡量预测与真实的差距,例如回归用均方误差
其中
sklearn 提供三种求解器:
SGD 是标准随机梯度下降,每次用一小批样本算梯度更新一次,实现简单、省内存,但需要自己调学习率等超参数,收敛较慢。
Adam 是自适应矩估计,自动为每个参数调整学习率,收敛快、对超参数不敏感,实践中通常首选。
LBFGS 是拟牛顿法,利用损失函数的二阶曲率信息,小数据集上收敛快且结果稳定,但不适合样本量很大的情况。
from sklearn.neural_network import MLPRegressor
mlp = MLPRegressor(hidden_layer_sizes=(50,), solver='adam',
alpha=0.01, batch_size=32, max_iter=500, random_state=0)
mlp.fit(X_train, y_train)
print(mlp.score(X_test, y_test))1.12.4 MLPClassifier 与 MLPRegressor 的常用参数
MLPClassifier 处理分类(输出层用 softmax 输出各类概率),MLPRegressor 处理回归(输出层是线性单元,输出连续值)。两者共享一组关键参数:
hidden_layer_sizes 决定网络结构;alpha 是 L2 正则化系数,惩罚过大的权重以抑制过拟合;batch_size 是每次更新所用的样本数,'auto' 时等价于 200;max_iter 是最大迭代次数,训练不足会提前停止并报警告;learning_rate_init 是初始学习率;early_stopping 开启后会在验证集不再变好时提前终止。
# 正则化对比:alpha 越大权重被压得越小,越不容易过拟合
for a in [0.0001, 0.01, 1.0]:
model = MLPClassifier(hidden_layer_sizes=(50,), alpha=a,
max_iter=1000, random_state=0)
model.fit(X_train, y_train)
print(f'alpha={a}', '训练集', round(model.score(X_train, y_train), 3),
'测试集', round(model.score(X_test, y_test), 3))注意神经网络对数据尺度敏感:特征量级相差悬殊时梯度会被大数值特征主导,训练前应做标准化(见预处理章节的 StandardScaler)。
1.12.5 IsotonicRegression 保序回归
保序回归拟合一条单调不减(或单调不增)的曲线,适合**越…越…**型关系。它的目标是在保证
约束要求预测序列单调不减,目标是最小化平方误差。直觉是:如果相邻两点破坏了单调性,就把它们合并成一段,用段内均值代替,反复合并直到整体单调。拟合结果是分段常数的阶梯函数,非参数、不假设任何具体函数形式。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.isotonic import IsotonicRegression
rng = np.random.RandomState(0)
x = np.linspace(0, 5, 200)
y = np.exp(x) + 0.5 * rng.randn(200) # 带噪声的指数关系,本身单调
iso = IsotonicRegression(increasing=True, out_of_bounds='clip')
y_pred = iso.fit_transform(x, y)
plt.scatter(x, y, s=8, alpha=0.5, label='观测')
plt.plot(x, y_pred, 'r-', label='保序回归')
plt.legend()
plt.show()increasing=True 要求单调不减,False 则单调不增;out_of_bounds='clip' 表示对新数据超出训练范围时夹在边界值上。保序回归常用于概率校准、风险评分等需要输出单调可靠的任务。
练习题
第1题 概念理解
说明神经元公式
参考答案
第2题 代码编写
用 make_regression 生成回归数据,用 MLPRegressor 拟合,比较 solver 取 adam 与 lbfgs 时的测试集 IsotonicRegression 拟合,打印拟合序列是否单调不减。
参考答案
import numpy as np
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.isotonic import IsotonicRegression
X, y = make_regression(n_samples=500, n_features=5, noise=10, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
scaler = StandardScaler().fit(X_train)
X_tr, X_te = scaler.transform(X_train), scaler.transform(X_test)
for solver in ['adam', 'lbfgs']:
m = MLPRegressor(hidden_layer_sizes=(30,), solver=solver,
max_iter=2000, random_state=0)
m.fit(X_tr, y_train)
print(solver, round(m.score(X_te, y_test), 3))
rng = np.random.RandomState(0)
x = np.linspace(0, 5, 100)
y = np.exp(x) + 0.5 * rng.randn(100)
iso = IsotonicRegression(increasing=True).fit(x, y)
pred = iso.predict(x)
print('单调不减:', np.all(np.diff(pred) >= 0))第3题 进阶练习
在 make_classification 上比较 MLP 在标准化前与标准化后的测试集准确率;并尝试用网格搜索 hidden_layer_sizes 与 alpha,观察不同组合的过拟合程度(训练集与测试集分数的差距)。
参考答案
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
X, y = make_classification(n_samples=1000, n_features=20, n_informative=12,
random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
m_raw = MLPClassifier(max_iter=1000, random_state=0).fit(X_train, y_train)
print('未标准化:', round(m_raw.score(X_test, y_test), 3))
scaler = StandardScaler().fit(X_train)
X_tr, X_te = scaler.transform(X_train), scaler.transform(X_test)
param_grid = {'hidden_layer_sizes': [(10,), (30,), (30, 10)],
'alpha': [0.0001, 0.01]}
gs = GridSearchCV(MLPClassifier(max_iter=2000, random_state=0),
param_grid, cv=3, scoring='accuracy')
gs.fit(X_tr, y_train)
print('最优参数:', gs.best_params_)
print('测试集:', round(gs.score(X_te, y_test), 3))常见错误
错误 1 · 训练报警告 ConvergenceWarning 或准确率异常低
现象:fit 时出现 ConvergenceWarning,或模型分数很低。
原因:默认 max_iter=200 常常不够,网络还没收敛就停下来了。
解决:调大 max_iter,或开启 early_stopping 配合足够大的 max_iter 让训练自行判断何时停止。
错误 2 · 特征量级差异大时训练效果差
现象:同一份数据,加了 StandardScaler 后分数明显更高。
原因:MLP 对输入尺度敏感,大量级特征会主导梯度,收敛变慢甚至失败。
解决:训练前先标准化特征,再喂给网络。
错误 3 · 隐藏层过大导致严重过拟合
现象:训练集分数接近 1,测试集分数很低。
原因:模型容量远超数据信息量,把噪声也背了下来。
解决:减小 hidden_layer_sizes、增大 alpha 正则化,或增加数据量。
错误 4 · 用 IsotonicRegression 拟合本就不单调的数据
现象:拟合曲线强行变成阶梯状,与真实趋势明显不符。
原因:保序回归的单调约束与数据本身矛盾,约束会硬掰曲线。
解决:先画散点图确认单调性;数据明显非单调时应改用普通回归模型。