当前位置: 首页 > news >正文

AI-02a5a4.神经网络-与学习相关的技巧-参数更新

参数的更新

神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。

而(深度)神经网络的参数空间非常大而复杂,无法轻易找到最优解。

在AI-02a5a2.神经网络的学习中,为了找到最优参数,介绍了梯度法,将参数的梯度(导数)作为了线索。

使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称SGD

在这里插入图片描述

SGD

式(6.1), W ← W − η ∂ L ∂ W W \leftarrow W - \eta\frac{\partial L}{\partial W} WWηWL
这里把需要更新的权重参数记为 W W W,把损失函数关于 W W W的梯度记为 ∂ L ∂ W \frac{\partial L}{\partial W} WL η \eta η表示学习率,实际上会取0.01或0.001这些事先决定好的值。式子中的 ← \leftarrow 表示用右边的值更新左边的值。

SGD是朝着梯度方向只前进一定距离的简单方法。

class SGD:"""随机梯度下降法(Stochastic Gradient Descent)lr: 学习率"""def __init__(self, lr=0.01):self.lr = lrdef update(self, params, grads):for key in params.keys():params[key] -= self.lr * grads[key]

在这里插入图片描述

缺点

如果函数的形状非均向(anisotropic),比如呈延伸状,搜索的路径就会非常低效。
SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。

Momentum

式(6.3), v ← α v − η ∂ L ∂ W v \leftarrow \alpha v - \eta \frac{\partial L}{\partial W} vαvηWL

式(6.4), W ← W + v W \leftarrow W + v WW+v
W W W表示要更新的权重参数, ∂ L ∂ W \frac{\partial L}{\partial W} WL表示损失函数关于 W W W的梯度, η \eta η表示学习率。 v v v对应物理上的速度。

式(6.3)表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理法则。
Momentum 方法感觉就像式小球在斜面上滚动,如图6.4所示。

在这里插入图片描述

式(6.3)中有 α v \alpha v αv这一项。在物体不受任何力时,该项承担使物体逐渐减速的任务( α \alpha α设定为0.9之类的值),对应物理上的地面摩擦或空气阻力。

class Momentum:"""Momentum SGD"""def __init__(self, lr=0.01, momentum=0.9):self.lr = lrself.momentum = momentumself.v = Nonedef update(self, params, grads):if self.v is None:self.v = {}for key, val in params.items():                                self.v[key] = np.zeros_like(val)for key in params.keys():self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]params[key] += self.v[key]

实例变量v会保存物体的速度。初始化时, v v v中什么都不保存,但当第一次调用update()时, v v v会以字典型变量的形式保存与参数结构相同的数据。剩余的代码部分就是将式(6.3)、式(6.4)写出来.

在这里插入图片描述

AdaGrad

在神经网络的学习中,学习率(数学式中记为η)的值很重要。学习率过小,会导致学习花费过多时间;反过来,学习率过大,则会导致学习发散而不能正确进行。

在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。

逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。而AdaGrad 进一步发展了这个想法,针对“一个一个”的参数,赋予其“定制”的值。

AdaGrad会为参数的每个元素适当地调整学习率,与此同时进行学习(AdaGrad的Ada来自英文单词Adaptive,即“适当的”的意思)。

式(6.5), h ← h + ∂ L ∂ W ⊙ ∂ L ∂ W h \leftarrow h + \frac{\partial L}{\partial W} \odot \frac{\partial L}{\partial W} hh+WLWL
式(6.6), W ← W − η 1 h ∂ L ∂ W W \leftarrow W - \eta \frac{1}{\sqrt{h}} \frac{\partial L}{\partial W} WWηh 1WL

W W W表示要更新的权重参数, ∂ L ∂ W \frac{\partial L}{\partial W} WL表示损失函数关于 W W W的梯度, η \eta η表示学习率。
变量 h h h ,如式(6.5)所示,它保存了以前的所有梯度值的平方和(式(6.5)中的 ⊙ \odot 表示对应矩阵元素的乘法)。然后,在更新参数时,通过乘以 1 h \frac{1}{\sqrt{h}} h 1,就可以调整学习的尺度。这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。也就是说,可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。

class AdaGrad:"""AdaGrad"""def __init__(self, lr=0.01):self.lr = lrself.h = Nonedef update(self, params, grads):if self.h is None:self.h = {}for key, val in params.items():self.h[key] = np.zeros_like(val)for key in params.keys():self.h[key] += grads[key] * grads[key]params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

这里需要注意的是,最后一行加上了微小值1e-7。这是为了防止当self.h[key]中有0时,将0用作除数的情况。这个微小值也可以设定为参数。

在这里插入图片描述

AdaGrad会记录过去所有梯度的平方和。因此,学习越深入,更新的幅度就越小。实际上,如果无止境地学习,更新量就会变为 0,完全不再更新。为了改善这个问题,可以使用 RMSProp 方法。RMSProp方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。这种操作从专业上讲,称为“指数移动平均”,呈指数函数式地减小过去的梯度的尺度。

class RMSprop:"""RMSprop"""def __init__(self, lr=0.01, decay_rate = 0.99):self.lr = lrself.decay_rate = decay_rateself.h = Nonedef update(self, params, grads):if self.h is None:self.h = {}for key, val in params.items():self.h[key] = np.zeros_like(val)for key in params.keys():self.h[key] *= self.decay_rateself.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

Adam

Adam(Adaptive Moment Estimation)是一种自适应学习率的优化算法,结合了 MomentumRMSprop 的思想。

初始化

  • 学习率:lr
  • 一阶矩衰减率:beta1(通常为 0.9)
  • 二阶矩衰减率:beta2(通常为 0.999)
  • 一阶矩变量:m(初始化为 0)
  • 二阶矩变量:v(初始化为 0)
  • 迭代次数:t(初始化为 0)

更新规则

对于每个参数 θ 和其梯度 g,Adam 的更新步骤如下:

  1. 计算一阶矩(动量)
    m t = β 1 ⋅ m t − 1 + ( 1 − β 1 ) ⋅ g t m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t mt=β1mt1+(1β1)gt

  2. 计算二阶矩(梯度平方的指数移动平均)
    v t = β 2 ⋅ v t − 1 + ( 1 − β 2 ) ⋅ g t 2 v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2 vt=β2vt1+(1β2)gt2

  3. 偏差校正
    由于 m t m_t mt v t v_t vt 在初始阶段偏向于 0,需要进行偏差校正:
    m ^ t = m t 1 − β 1 t \hat{m}_t = \frac{m_t}{1 - \beta_1^t} m^t=1β1tmt
    v ^ t = v t 1 − β 2 t \hat{v}_t = \frac{v_t}{1 - \beta_2^t} v^t=1β2tvt

  4. 更新参数
    θ t = θ t − 1 − lr ⋅ m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \frac{\text{lr} \cdot \hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt1v^t +ϵlrm^t
    其中,ϵ 是一个很小的常数(如 1e-7),用于防止分母为零。


η t = η 1 − β 2 t 1 − β 1 t \eta_t = \eta\frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} ηt=η1β1t1β2t
m ← m + ( 1 − β 1 ) ( ∂ L ∂ W − m ) m \leftarrow m + (1-\beta_1)(\frac{\partial L}{\partial W}-m) mm+(1β1)(WLm)
v ← v + ( 1 − β 2 ) ( ∂ L ∂ W ⊙ ∂ L ∂ W − v ) v \leftarrow v + (1-\beta_2)(\frac{\partial L}{\partial W}\odot\frac{\partial L}{\partial W}-v) vv+(1β2)(WLWLv)
W ← W − η t m v W \leftarrow W - \eta_t \frac{m}{\sqrt{v}} WWηtv m

W W W表示要更新的权重参数, ∂ L ∂ W \frac{\partial L}{\partial W} WL表示损失函数关于 W W W的梯度。
η \eta η 表示学习率,通常是0.001。
β 1 \beta_1 β1 是一阶矩(动量)的衰减率,通常为 0.9。
β 2 \beta_2 β2 是二阶矩(梯度平方的指数移动平均)的衰减率,通常为 0.999。
t t t 是当前迭代次数,从 1 开始计数。
1 − β 2 t \sqrt{1 - \beta_2^t} 1β2t 这是对二阶矩的偏差校正项,由于初始阶段 v v v 偏向于 0,需要通过 1 − β 2 t \sqrt{1 - \beta_2^t} 1β2t 进行校正。
1 − β 1 t 1 - \beta_1^t 1β1t 这是对一阶矩的偏差校正项,由于初始阶段 m m m 偏向于 0,需要通过 1 − β 1 t 1 - \beta_1^t 1β1t 进行校正。
η t \eta_t ηt 这是 自适应学习率,结合了初始学习率、一阶矩和二阶矩的偏差校正项,随着迭代次数 (t) 的增加, η t \eta_t ηt 会逐渐趋于稳定。

class Adam:"""Adam (http://arxiv.org/abs/1412.6980v8)"""def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):self.lr = lrself.beta1 = beta1self.beta2 = beta2self.iter = 0self.m = Noneself.v = Nonedef update(self, params, grads):if self.m is None:self.m, self.v = {}, {}for key, val in params.items():self.m[key] = np.zeros_like(val)self.v[key] = np.zeros_like(val)self.iter += 1lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)        for key in params.keys():# self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]# self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)# unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias# unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias# params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

在这里插入图片描述

对比总结

在这里插入图片描述

以 手 写 数 字 识 别 为 例,比 较 前 面 介 绍 的 SGD、Momentum、AdaGrad、Adam这4种方法,并确认不同的方法在学习进展上有多大程度的差异。

一个5层神经网络为对象,其中每层有100个神经元。激活函数使用的是ReLU。结果如下图所示,
在这里插入图片描述

算法梯度计算方式学习率调整机制核心优势
SGD当前梯度固定学习率简单直观,适合凸优化问题
动量法当前梯度 + 历史梯度累积固定学习率加速收敛,缓解震荡
RMSProp当前梯度基于历史梯度平方自适应调整自适应学习率,适合非凸问题
Adam当前梯度 + 一阶动量结合一阶、二阶动量自适应调整综合性能优,低超参数敏感度

multi_layer_net.py

# coding: utf-8import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradientclass MultiLayerNet:"""全连接的多层神经网络Parameters----------input_size : 输入大小(MNIST的情况下为784)hidden_size_list : 隐藏层的神经元数量的列表(e.g. [100, 100, 100])output_size : 输出大小(MNIST的情况下为10)activation : 'relu' or 'sigmoid'weight_init_std : 指定权重的标准差(e.g. 0.01)指定'relu'或'he'的情况下设定“He的初始值”指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”weight_decay_lambda : Weight Decay(L2范数)的强度"""def __init__(self, input_size, hidden_size_list, output_size,activation='relu', weight_init_std='relu', weight_decay_lambda=0):self.input_size = input_sizeself.output_size = output_sizeself.hidden_size_list = hidden_size_listself.hidden_layer_num = len(hidden_size_list)self.weight_decay_lambda = weight_decay_lambdaself.params = {}# 初始化权重self.__init_weight(weight_init_std)# 生成层activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}self.layers = OrderedDict()for idx in range(1, self.hidden_layer_num+1):self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)])self.layers['Activation_function' + str(idx)] = activation_layer[activation]()idx = self.hidden_layer_num + 1self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)])self.last_layer = SoftmaxWithLoss()def __init_weight(self, weight_init_std):"""设定权重的初始值Parameters----------weight_init_std : 指定权重的标准差(e.g. 0.01)指定'relu'或'he'的情况下设定“He的初始值”指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”"""all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size]for idx in range(1, len(all_size_list)):scale = weight_init_stdif str(weight_init_std).lower() in ('relu', 'he'):scale = np.sqrt(2.0 / all_size_list[idx - 1])  # 使用ReLU的情况下推荐的初始值elif str(weight_init_std).lower() in ('sigmoid', 'xavier'):scale = np.sqrt(1.0 / all_size_list[idx - 1])  # 使用sigmoid的情况下推荐的初始值self.params['W' + str(idx)] = scale * np.random.randn(all_size_list[idx-1], all_size_list[idx])self.params['b' + str(idx)] = np.zeros(all_size_list[idx])def predict(self, x):for layer in self.layers.values():x = layer.forward(x)return xdef loss(self, x, t):"""求损失函数Parameters----------x : 输入数据t : 教师标签Returns-------损失函数的值"""y = self.predict(x)weight_decay = 0for idx in range(1, self.hidden_layer_num + 2):W = self.params['W' + str(idx)]weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)return self.last_layer.forward(y, t) + weight_decaydef accuracy(self, x, t):y = self.predict(x)y = np.argmax(y, axis=1)if t.ndim != 1 : t = np.argmax(t, axis=1)accuracy = np.sum(y == t) / float(x.shape[0])return accuracydef numerical_gradient(self, x, t):"""求梯度(数值微分)Parameters----------x : 输入数据t : 教师标签Returns-------具有各层的梯度的字典变量grads['W1']、grads['W2']、...是各层的权重grads['b1']、grads['b2']、...是各层的偏置"""loss_W = lambda W: self.loss(x, t)grads = {}for idx in range(1, self.hidden_layer_num+2):grads['W' + str(idx)] = numerical_gradient(loss_W, self.params['W' + str(idx)])grads['b' + str(idx)] = numerical_gradient(loss_W, self.params['b' + str(idx)])return gradsdef gradient(self, x, t):"""求梯度(误差反向传播法)Parameters----------x : 输入数据t : 教师标签Returns-------具有各层的梯度的字典变量grads['W1']、grads['W2']、...是各层的权重grads['b1']、grads['b2']、...是各层的偏置"""# forwardself.loss(x, t)# backwarddout = 1dout = self.last_layer.backward(dout)layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 设定grads = {}for idx in range(1, self.hidden_layer_num+2):grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.layers['Affine' + str(idx)].Wgrads['b' + str(idx)] = self.layers['Affine' + str(idx)].dbreturn grads

optimizer_compare_naive.py

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
from common.optimizer import *def f(x, y):return x**2 / 20.0 + y**2def df(x, y):return x / 10.0, 2.0*yinit_pos = (-7.0, 2.0)
params = {}
params['x'], params['y'] = init_pos[0], init_pos[1]
grads = {}
grads['x'], grads['y'] = 0, 0optimizers = OrderedDict()
optimizers["SGD"] = SGD(lr=0.95)
optimizers["Momentum"] = Momentum(lr=0.1)
optimizers["AdaGrad"] = AdaGrad(lr=1.5)
optimizers["Adam"] = Adam(lr=0.3)idx = 1for key in optimizers:optimizer = optimizers[key]x_history = []y_history = []params['x'], params['y'] = init_pos[0], init_pos[1]for i in range(30):x_history.append(params['x'])y_history.append(params['y'])grads['x'], grads['y'] = df(params['x'], params['y'])optimizer.update(params, grads)x = np.arange(-10, 10, 0.01)y = np.arange(-5, 5, 0.01)X, Y = np.meshgrid(x, y)Z = f(X, Y)# for simple contour line  mask = Z > 7Z[mask] = 0# plotplt.subplot(2, 2, idx)idx += 1plt.plot(x_history, y_history, 'o-', color="red")plt.contour(X, Y, Z)plt.ylim(-10, 10)plt.xlim(-10, 10)plt.plot(0, 0, '+')#colorbar()#spring()plt.title(key)plt.xlabel("x")plt.ylabel("y")
plt.show()

optimizer_compare_mnist.py

# coding: utf-8
import os
import sys
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve
from common.multi_layer_net import MultiLayerNet
from common.optimizer import *# 0:读入MNIST数据==========(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)train_size = x_train.shape[0]batch_size = 128max_iterations = 2000# 1:进行实验的设置==========
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['AdaGrad'] = AdaGrad()
optimizers['Adam'] = Adam()
# optimizers['RMSprop'] = RMSprop()networks = {}
train_loss = {}for key in optimizers.keys():networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100],output_size=10)train_loss[key] = []    # 2:开始训练==========
for i in range(max_iterations):batch_mask = np.random.choice(train_size, batch_size)x_batch = x_train[batch_mask]t_batch = t_train[batch_mask]for key in optimizers.keys():grads = networks[key].gradient(x_batch, t_batch)optimizers[key].update(networks[key].params, grads)loss = networks[key].loss(x_batch, t_batch)train_loss[key].append(loss)if i % 100 == 0:print( "===========" + "iteration:" + str(i) + "===========")for key in optimizers.keys():loss = networks[key].loss(x_batch, t_batch)print(key + ":" + str(loss))# 3.绘制图形==========
markers = {"SGD": "o", "Momentum": "x", "AdaGrad": "s", "Adam": "D"}
# markers = {"SGD": "o", "Momentum": "x", "RMSprop": "s", "Adam": "D"}
x = np.arange(max_iterations)
for key in optimizers.keys():plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 1)
plt.legend()
plt.show()

相关文章:

  • 【设计模式】GoF设计模式之策略模式(Strategy Pattern)
  • [Linux网络_70] ARP协议 | RARP | DNS | ICMP协议
  • 在 Kotlin 中什么是委托属性,简要说说其使用场景和原理
  • window 显示驱动开发-线性内存空间段
  • kotlin 数据类
  • 2025 年数维杯数学建模 C 题完整论文代码模型:清明时节雨纷纷,何处踏青不误春
  • 最新CDGP单选题(第一章)补充
  • C# 的异步任务中, 如何暂停, 继续,停止任务
  • AKS 支持 Kata Container容器沙盒 -预览阶段
  • 什么是AI写作
  • [QMT量化交易小白入门]-五十一、用Backtrader搭建双均线策略回测平台,年化收益13%
  • Nginx静态资源增加权限验证
  • 计算机二级(C语言)已过
  • Rust 中的 `String`、`str` 和 `str`:深入解析与使用指南
  • Java设计模式之工厂方法模式:从入门到精通
  • 高效管理钉钉收款单数据集成到MySQL的技术方案
  • C——数组和函数实践:扫雷
  • Flutter 3.29.3 花屏问题记录
  • 机器学习第二讲:对比传统编程:解决复杂规则场景
  • CurrentHashMap的整体系统介绍及Java内存模型(JVM)介绍
  • 墨西哥宣布就“墨西哥湾”更名一事起诉谷歌
  • 习近平向中国人民解放军仪仗队致意
  • 洲际酒店:今年第一季度全球酒店平均客房收入同比增长3.3%
  • 人民日报评“组团退演出服”:市场经济诚信原则需全社会维护
  • 美英达成贸易协议,美股集体收涨
  • 马上评|比余华与史铁生的友情更动人的是什么