神经网络参数学习和优化方法的原理

神经网络学习参数和搜索最优超参数的过程

梯度检查

  • 简单的把解析梯度和数值计算梯度进行比较,但实际上操作很复杂
    在使用有限差值近似来计算数值梯度的时候,常见的公式是:
    d f ( x ) d x = f ( x + h ) − f ( x ) h \frac{df(x)}{dx} = \frac{f(x+h)-f(x)}{h} dxdf(x)=hf(x+h)f(x)
    在实践中证明,使用中心化公式效果更好:
    d f ( x ) d x = f ( x + h ) − f ( x − h ) 2 h \frac{df(x)}{dx} = \frac{f(x+h)-f(x-h)}{2h} dxdf(x)=2hf(x+h)f(xh)
    • 使用双精度:单精度会浮点导致即使梯度实现正确,相对误差值也
      会很高
    • 注意Loss函数的不可导点
    • 注意步长
    • 注意正则化项

合理性检查

  • 寻找特定情况的正确损失值 :在使用小参数进行初始化时,确保得到的损失值与期望一致。例如,对于一个跑CIFAR-10的Softmax分类器,一般期望它的初始损失值是2.302,这是因为初始时预计每个类别的概率是0.1(10个类别)如果没看到这样的损失值,那么初始化中就可能有问题。
  • 提高正则化强度时导致损失值变大

检查学习过程

Modeldatasets
“sequential”mnist
Layer (type)Output ShapeParam
flatten (Flatten)(None, 784)0
dense (Dense)(None, 32)25120
dense_1 (Dense)(None, 10)330
optimizerlossepochsbatch_size
‘adam’‘sparse_categorical_crossentropy’

损失函数
  • 在训练的时候,应该跟踪多个重要数值。 这些数值输出的图表是观察训练进程的窗口,帮助你直观理解不同的超参数的设置效果
  • 损失值的震荡程度和批尺寸(batch size)有关当批尺寸为1,震荡会相对较大。当批尺寸就是整个数据集时震荡就会最小
    例:
optimizerlossepochsbatch_size
‘adam’‘sparse_categorical_crossentropy’20512

在这里插入图片描述

optimizerlossepochsbatch_size
‘adam’‘sparse_categorical_crossentropy’201

在这里插入图片描述

训练集和验证集准确率
  • 在训练分类器的时候,需要跟踪的第二重要的数值是验证集和训练集的准确率,这个图表能够展现知道模型过拟合
    的程度
optimizerlossepochsbatch_size
‘adam’‘sparse_categorical_crossentropy’201

在这里插入图片描述

optimizerlossepochsbatch_size
‘adam’‘sparse_categorical_crossentropy’20512

在这里插入图片描述

参数更新

随机梯度下降及各种更新方法

普通更新。最简单的更新形式是沿着负梯度方向改变参数(因为梯度指向的是上升方向,但是我们通常希望最小化损失函数)。假设有一个参数向量x及其梯度dx,那么最简单的更新的形式是:

# 普通更新
x = x ‐ learning_rate * dx

learning_rate是一个超参数,它是一个固定的常量。当在整个数据集上进行计算时,只要学习率足够低,总是能在损失函数上得到非负的进展。

动量(Momentum)更新

# 动量更新
v = mu * v ‐ learning_rate * dx # 与速度融合
x = x + v # 与位置融合

引入了一个初始化为0的变量v和一个超参数mu。说得不恰当一点,这个变量(mu)在最优化的过程中被看做动量(一般值设为0.9),但其物理意义与摩擦系数更一致。这个变量有效地抑制了速度,降低了系统的动能,不然质点在山底永远不会停下来。通过交叉验证,这个参数通常设为[0.5,0.9,0.95,0.99]中的一个。
在这里插入图片描述
在这里插入图片描述

Nesterov动量
Nesterov动量的核心思路是,当参数向量位于某个位置x时,观察上面的动量更新公式可以发现,动量部分(忽视带梯度的第二个部分)会通过mu * v稍微改变参数向量。因此,如果要计算梯度,那么可以将未来的近似位置x +mu * v看做是“向前看”,这个点在我们一会儿要停止的位置附近。因此,计算x + mu * v的梯度而不是“旧”位置x的梯度就有意义了。
在这里插入图片描述

在动量(Momentum)更新的基础上:

# Nesterov更新
x_ahead = x + mu * v #在动量的作用下带到的新点x_ahead

# 计算dx_ahead(在x_ahead处的梯度,而不是在x处的梯度)
v = mu * v ‐ learning_rate * dx_ahead
x += v

学习率退火

  • 随步数衰减:每进行几个周期就根据一些因素降低学习率。典型的值是每过5个周期就将学习率减少一半,或者每20个周期减少到之前的0.1。这些数值的设定是严重依赖具体问题和模型的选择的。在实践中可能看见这么一种经验做法:使用一个固定的学习率来进行训练的同时观察验证集错误率,每当验证集错误率停止下降,就乘以一个常数(比如0.5)来降低学习率。
  • 指数衰减。数学公式是:
    a = a 0 e − k t a = a_0 e^{-kt} a=a0ekt
    其中 a 0 、 k a_0、k a0k是超参数 t t t
    是迭代次数(也可以使用周期作为单位.

  • 1/t衰减的数学公式是:
    a = a 0 1 + k t a = \frac{a_0}{1 + kt} a=1+kta0
    其中 a 0 、 k a_0、k a0k是超参数, t t t是迭代次数。
在实践中,我们发现随步数衰减的随机失活(dropout)更受欢迎,因为它使用的超参数(衰减系数和以周期为时间单位的步数)比K更有解释性。
如果训练卡住了,怎么判断是卡在局部最小值点还是鞍点?
核心公式: L ( θ ) ≈ L ( θ ′ ) + ( θ − θ ′ ) T g + 1 2 ( θ − θ ′ ) T H ( θ − θ ′ ) ( L o s s ( θ ) 在 θ ′ 处 泰 勒 开 ) L(\theta) \approx L(\theta^{'}) + (\theta - \theta^{'})^Tg + \frac{1}{2}(\theta - \theta^{'})^TH(\theta - \theta^{'}) (Loss(\theta)在\theta^{'}处泰勒开) L(θ)L(θ)+(θθ)Tg+21(θθ)TH(θθ)(Loss(θ)θ)

在这里插入图片描述

在这里插入图片描述

这里 H f ( x ) Hf(x) Hf(x)Hessian矩阵,它是函数的二阶偏导数的平方矩阵g是梯度向量,这和梯度下降中一样。直观理解上,Hessian矩阵描述了损失函数的局部曲率,从而使得可以进行更高效的参数更新。具体来说,就是乘以Hessian转置矩阵可以让最优化过程在曲率小的时候大步前进,在曲率大的时候小步前进。
但计算(以及求逆)Hessian矩阵操作非常耗费时间和空间。所以很难运用到实际的深度学习应用中去.

逐参数适应学习率方法

Adagrad

在这里插入图片描述

# 假设有梯度和参数向量x
cache = cache + dx**2
x = x ‐ learning_rate * dx / (np.sqrt(cache) + eps)

变量cache的尺寸和梯度矩阵的尺寸是一样的,还跟踪了每个参数的梯度的平方和。平滑的式子eps(一般设为1e-4到1e-8之间)是防止出现除以0的情况。 Adagrad的一个缺点是,在深度学习中单调的学习率被证明通常过于激进且过早停止学习。

RMSProp

这个方法用一种很简单的方式修改了Adagrad方法,让它不那么激进,单调地降低了学习率。具体说来,就是它使用了一个梯度平方的滑动平均:

在这里插入图片描述

cache = decay_rate * cache + (1 ‐ decay_rate) * dx**2
x = x ‐ learning_rate * dx / (np.sqrt(cache) + eps)

decay_rate是一个超参数,常用的值是[0.9,0.99,0.999].
RMSProp和Adagrad不同,其更新不会让学习率单调变小。

Adam

Adam看起来像是RMSProp的动量版。简化的代码是下面这样:

m = beta1*m + (1‐beta1)*dx
v = beta2*v + (1‐beta2)*(dx**2)
x = x ‐ learning_rate * m / (np.sqrt(v) + eps)

这个更新方法看起来真的和RMSProp很像,除了使用的是平滑版的梯度m,而不是用的原始梯度向量dx。论文中推荐的参数eps=1e-8, beta1=0.9, beta2=0.999。在实际操作中,我们推荐Adam作为默认的算法,一般而言跑起来比RMSProp要好一点。但是也可以试试SGD+Nesterov动量。

超参数调优

训练一个神经网络会遇到很多超参数设置。神经网络最常用的设置有:
  • 初始学习率。
  • 学习率衰减方式(例如一个衰减常量)。
  • 正则化强度(L2惩罚,随机失活强度)。

比起交叉验证最好使用一个验证集。在大多数情况下,一个尺寸合理的验证集可以让代码更简单,不需要用几个数据集来交叉验证。你可能会听到人们说他们“交叉验证”一个参数,但是大多数情况下,他们实际是使用的一个验证集。

评价

模型集成

在交叉验证中发现最好的模型。使用交叉验证来得到最好的超参数,然后取其中最好的几个(比如10个)模型来进行集成。这样就提高了集成的多样性,但风险在于可能会包含不够理想的模型。在实际操作中,这样操作起来比较简单,在交叉验证后就不需要额外的训练了。

  • 同一个模型,不同的初始化。使用交叉验证来得到最好的超参数,然后用最好的参数来训练不同初始化条件的模型。这种方法的风险在于多样性只来自于不同的初始化条件。
  • 在交叉验证中发现最好的模型。使用交叉验证来得到最好的超参数,然后取其中最好的几个(比如10个)模型来进行集成。这样就提高了集成的多样性,但风险在于可能会包含不够理想的模型。在实际操作中,这样操作起来比较简单,在交叉验证后就不需要额外的训练了。

总结

训练一个神经网络需要的步骤
  • 利用小批量数据对实现进行梯度检查,还要注意各种错误。
  • 进行合理性检查,确认初始损失值是合理的,在小数据集上能得到100%的准确率。
  • 在训练时,跟踪损失函数值,训练集和验证集准确率,如果愿意,还可以跟踪更新的参数量相对于总参数量的比例(一般在1e-3左右),然后如果是对于卷积神经网络,可以将第一层的权重可视化。
  • 推荐的两个更新方法是SGD+Nesterov动量方法,或者Adam方法。
  • 随着训练进行学习率衰减。比如,在固定多少个周期后让学习率减半,或者当验证集准确率下降的时候。
  • 使用随机搜索(不要用网格搜索)来搜索最优的超参数。
  • 进行模型集成来获得额外的性能提高。

试验代码

import tensorflow as tf
import matplotlib.pyplot as plt
import os

os.environ["CUDA_VISIBLE_DEVICES"] = '0'

(train_image, train_label), (test_image, test_label) = tf.keras.
datasets.mnist.load_data()

print(train_image.shape)

model = tf.keras.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=(28, 28)))
model.add(tf.keras.layers.Dense(32, activation='relu'))
model.add(tf.keras.layers.Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['acc'])
history = model.fit(train_image, train_label, epochs=20, batch_size=1, 
validation_data=(test_image, test_label))

print(model.summary())

# model.save("./model/minist_model.h5")

plt.figure()
plt.plot(history.epoch, history.history.get('loss'), label='loss')
plt.plot(history.epoch, history.history.get('val_loss'), label='val_loss')
plt.show()
plt.figure()
plt.plot(history.epoch, history.history.get('acc'), label='acc')
plt.plot(history.epoch, history.history.get('val_acc'), label='val_acc')
plt.show()

et(‘val_loss’), label=‘val_loss’)
plt.show()
plt.figure()
plt.plot(history.epoch, history.history.get(‘acc’), label=‘acc’)
plt.plot(history.epoch, history.history.get(‘val_acc’), label=‘val_acc’)
plt.show()


'''
参考:
李宏毅老师的机器学习


2021-6-7
'''
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值