三个训练神经网络的建议
(1)一般情况下,在训练集上的目标函数的平均值(cost)会随着训练的深入而不断减小,如果这个指标有增大情况,停下来。
有两种情况:
- 采用的模型不够复杂,以致于不能在训练集上完全拟合;
- 已经训练很好了。
(2)分出一些验证集Validation Set,训练本质目标是在验证集上获取最大识别率。因此训练一段时间后,必须在验证集上测试识别率,保存使验证集上识别率最大的模型参数作为最后的结果。
(3)注意调整学习率Learning Rate,如果刚训练几步损失函数就增加,一般来说是学习率太高;反之如果每次cost变化很小,说明学习率太低。
一点人生的经验:
(1)目标函数可以加入正则项
Minimize E(ω,b)=L(ω,b)+λ/2 ||ω||2
L(ω,b)为原来的目标函数,λ/2 ||ω||2为正则项。λ为权值衰减系数
参考前向传播nn_forward.m
if strcmp(nn.objective_function,'MSE')
nn.cost(s) = 0.5 / m * sum(sum((nn.a{k} - batch_y).^2)) + 0.5 * nn.weight_decay * cost2;
elseif strcmp(nn.objective_function,'Cross Entropy')
nn.cost(s) = -0.5*sum(sum(batch_y.*log(nn.a{k})))/m + 0.5 * nn.weight_decay * cost2;
后向传播nn_backpropagation.m
nn.W_grad{nn.depth-1} = nn.theta{nn.depth}*nn.a{nn.depth-1}'/m + nn.weight_decay*nn.W{nn.depth-1};
nn.b_grad{nn.depth-1} = sum(nn.theta{nn.depth},2)/m;
(2)训练数据归一化
newX=[X-mean(X)]/ std(X)
(3)参数ω和b的初始化
一种比较简单有效的方法:
(ω,b)初始化从区间(-1/sqrt(d),1/sqrt(d))均匀随机取值,其中d为(ω,b)所在层的神经元个数。
可以证明如果X服从均值0方差1的正态分布,且各个维度无关,而(ω,b)是区间(-1/sqrt(d),1/sqrt(d))的均匀分布,则ωTX+b是均值0,方差为1/3的正态分布
nn_create.m
nn.W{k} = 2*rand(height, width)/sqrt(width)-1/sqrt(width);%rand产生伪随机数矩阵,即W权重矩阵初始化
nn.b{k} = 2*rand(height, 1)/sqrt(width)-1/sqrt(width);%b阈值的初始化
避免一开始梯度趋近于0的现象。
(4)BATCH NORMALIZATION
论文:Batch normalization accelerating deep network training by reducing internal covariate shift(2015)
在这可以看:
基本思想:既然我们希望每一层获得的值都在0附近,从而避免梯度消失现象,那么我们为什么不直接把每一层的值做基于均值和方差的归一化呢?
(5)参数的更新策略
ADAGRAD的方法
if strcmp(nn.optimization_method,'AdaGrad')
nn.rW{k}= nn.rW{k}+nn.W_grad{k}.^2;nn.rb{k}= nn.rb{k}+nn.b_grad{k}.^2;
nn.W{k}=nn.W{k}-nn.learning_rate*nn.W_grad{k}./(sqrt(nn.rW{k})+0.001);
nn.b{k}=nn.b{k}-nn.learning_rate*nn.b_qrad{k}./(sqrt(nn.rb{k})+0.001);
解决梯度随机性的问题:引入Momentum
同时结合:Adam-解决梯度绝对值分量不平衡和梯度方向随机性的问题,也引入了逐渐降低梯度搜索步长的机制。

📝 算法步骤解释
- Require
- Step size (ϵ,学习率),推荐默认值 0.001。
- Exponential decay rates (ρ1,ρ2):分别控制一阶、二阶动量的衰减速率。推荐默认值 ρ1=0.9,ρ2=0.999。
- Small constant δ:数值稳定常数,防止分母为零,默认 10−8。
- Initial parameters θ:模型初始参数。
- Initialize
- 一阶动量变量 s=0(存放梯度的指数加权平均,类似 Momentum)。
- 二阶动量变量 r=0(存放梯度平方的指数加权平均,类似 RMSProp)。
- 时间步 t=0。
- 循环过程 (直到满足停止条件,例如迭代次数用完或收敛)
- Step A. 采样一个 minibatch
- 从训练集取出一个小批量样本 {x(1),…,x(m)} 和对应标签。
- Step B. 计算梯度
- Step A. 采样一个 minibatch
即小批量平均梯度。
Step C. 时间步递增
t←t+1
Step D. 更新一阶动量(偏置的)
s←ρ1s+(1−ρ1)g
——这是梯度的指数滑动平均(类似 Momentum)。
Step E. 更新二阶动量(偏置的)
r←ρ2r+(1−ρ2)(g⊙g)
——这里 ⊙表示逐元素乘法。即对梯度平方取指数滑动平均(类似 RMSProp)。
Step F. 偏差修正
由于初始化 s=0,r=0,前期会有向零偏移,需要修正:
Step G. 计算更新量
Step H. 更新参数
θ←θ+Δθ
🔑 总结
- sss:梯度的一阶动量(方向 + 平滑)。
- rrr:梯度的二阶动量(幅度 + 自适应缩放)。
- 偏差修正:解决初期 (s,r≈0)的估计偏差问题。
- 更新公式:学习率会根据梯度历史动态调整,每个参数有自己独立的学习率。
Adam 的更新可以理解为:
👉 用 Momentum 决定方向,再 用 RMSProp 决定步长大小。
Python代码示例(一阶动量用 s
,二阶动量用 r
,含偏差修正;并给了一个最小化二次函数的小示例):
import numpy as np
class Adam:
"""
Adam 优化器(Algorithm 8.7)
s: 一阶动量(biased)
r: 二阶动量(biased)
"""
def __init__(self, shape, lr=1e-3, rho1=0.9, rho2=0.999, eps=1e-8):
self.lr = lr # ε (step size)
self.rho1 = rho1 # ρ1
self.rho2 = rho2 # ρ2
self.eps = eps # δ
self.s = np.zeros(shape) # 初始化一阶动量 s=0
self.r = np.zeros(shape) # 初始化二阶动量 r=0
self.t = 0 # 初始化时间步 t=0
def step(self, theta, g):
"""
单次更新:
theta: 参数
g: 当前梯度(对 minibatch 的平均梯度)
return: 更新后的参数
"""
# t ← t + 1
self.t += 1
# Update biased first moment estimate: s ← ρ1 s + (1-ρ1) g
self.s = self.rho1 * self.s + (1.0 - self.rho1) * g
# Update biased second moment estimate: r ← ρ2 r + (1-ρ2) (g ⊙ g)
self.r = self.rho2 * self.r + (1.0 - self.rho2) * (g * g)
# Correct bias:
# ŝ = s / (1 - ρ1^t), r̂ = r / (1 - ρ2^t)
s_hat = self.s / (1.0 - self.rho1 ** self.t)
r_hat = self.r / (1.0 - self.rho2 ** self.t)
# Compute update: Δθ = -ε * ŝ / (sqrt(r̂) + δ)
delta_theta = - self.lr * s_hat / (np.sqrt(r_hat) + self.eps)
# Apply update: θ ← θ + Δθ
theta = theta + delta_theta
return theta
# ================= 示例:最小化 f(θ)=∑ θ_i^2 =================
# 真梯度:∇f(θ)=2θ
np.random.seed(0)
theta = np.random.randn(3) * 5.0 # 初始参数
opt = Adam(shape=theta.shape, lr=1e-2) # 用默认 ρ1=0.9, ρ2=0.999, δ=1e-8
for k in range(1, 501):
g = 2.0 * theta # 计算梯度 (小批量平均梯度在真实任务里替换这里)
theta = opt.step(theta, g) # 按图中流程更新
if k % 100 == 0:
fval = (theta**2).sum()
print(f"iter {k:3d} f(theta)={fval:.6f} theta={theta}")
# 输出会看到 f(θ) 单调下降,θ 收敛到 0 附近
输出:
iter 100 f(theta)=78.124320 theta=[7.84011204 1.09919008 3.93048901]
iter 200 f(theta)=57.552498 theta=[6.91704488 0.49699096 3.07570938]
iter 300 f(theta)=42.152403 theta=[6.0541767 0.17936161 2.33819949]
iter 400 f(theta)=30.556370 theta=[5.25288565 0.05091135 1.72074696]
iter 500 f(theta)=21.872385 theta=[4.51437396 0.01130478 1.22175496]
📊 结果逐行解释
输出是每 100 次迭代打印一次:
Iter 100
f(theta)=78.124320
theta=[7.84011204 1.09919008 3.93048901]
- 初始 θ 很大(一开始是
np.random.randn(3)*5
随机出来的)。 - 经过 100 步更新后,参数值比初始小了一些,但还比较大。目标函数 f(θ) 还在 78 左右。
Iter 200
f(theta)=57.552498
theta=[6.91704488 0.49699096 3.07570938]
- θ 的数值进一步下降了(尤其是第二个分量从 ~1.1 → 0.49)。
- 函数值 f(θ) 从 78 降到了 57,说明 Adam 在往 0 的方向走。
Iter 300
f(theta)=42.152403
theta=[6.0541767 0.17936161 2.33819949]
- 继续下降,f 值变成 ~42。
- 第二个分量(0.179)几乎快收敛到 0 了。
Iter 400
f(theta)=30.556370
theta=[5.25288565 0.05091135 1.72074696]
- 三个分量继续减小,函数值也继续下降。
- 可以看出来参数在逐步往 0 收缩。
Iter 500
f(theta)=21.872385
theta=[4.51437396 0.01130478 1.22175496]
- 此时 f 值还在下降(21),但下降速度变慢了。
- 第二个参数已经基本到 0(0.01),其他两个参数也明显比最开始小了很多。
🔑 总结
- 趋势:函数值从 78 → 57 → 42 → 30 → 21,说明优化器 Adam 确实在不断让目标函数下降。
- 参数收敛:θ 在逐步往 0 收敛(最终最优解)。
- 下降速度:一开始下降快,后面越来越慢,这是正常的,因为越靠近最优点,梯度越小。
- 未到 0:500 步还没完全到 0,是因为学习率比较小(lr=0.01),如果继续迭代或适当调大学习率,θ 会更快逼近 0。
画图验证Adam 能不断减小损失函数,并逐渐收敛到最优解的python代码请到第2页查看👇