我们从数据集 M M M中随机抽取 m m m个小批量样本,通过计算它们梯度均值,可以得到梯度的无偏估计。
换句话讲,就是在模型训练的时候,不是将整个数据集都作为输入来训练模型,而是从数据集中抽取一定的样本来作为输入。此时,参数的更新方式为:
θ = θ − α ∇ L ( f ( θ ; x ) , y ) \theta = \theta - \alpha∇L(f(\theta;x),y) θ=θ−α∇L(f(θ;x),y)
其中 θ \theta θ为模型的参数, α \alpha α为学习率, ∇ L ∇L ∇L为模型的梯度。
线性衰减学习率
在模型训练过程中,我们希望学习率能够随着模型的训练而发现相应的改变,使得模型能够更便于找到最优解。因此就引入了线性衰减学习率。
ϵ k = ( 1 − α ) ϵ 0 + α ϵ τ \epsilon_k=(1-\alpha)\epsilon_0+\alpha\epsilon_\tau ϵk=(1−α)ϵ0+αϵτ
其中, α = k ϵ \alpha={k\over\epsilon} α=ϵk, ϵ k \epsilon_k ϵk表示第 k k k次迭代的学习率, τ \tau τ被设置为需要反复遍历训练集几百次的迭代次数。通常 ϵ τ \epsilon_\tau ϵτ大约设置为 ϵ 0 \epsilon_0 ϵ0的1%。
引入随机梯度下降的优点
随机梯度下降存在学习过程过慢这一问题。而为了解决这一问题,我们引入了动量,记作 v v v,动量算法累计了之前梯度指数衰减的移动平均,并继续沿着该方向移动。引入动量后,参数更新的方式变为了:
v = α v − ϵ ∇ L ( f ( x ; θ ) , y ) θ = θ + v v=\alpha v-\epsilon∇L(f(x;\theta),y) \\ \theta=\theta+v v=αv−ϵ∇L(f(x;θ),y)θ=θ+v
其中 α \alpha α表示之前梯度对衰减的贡献, α ∈ [ 0 , 1 ) \alpha∈[0,1) α∈[0,1), ϵ \epsilon ϵ表示学习率。
使用动量法相当于每次在进行参数更新的时候,会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新(水平方向),那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化(竖直方向),那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少。
Nesterov动量
动量算法存在一个问题,那就是动量 v v v会随着迭代的增加导致 v v v累计过大,导致在遇到最优解时直接“冲出去”,而不是收敛于该点。
Nesterov动量是在动量算法基础上的改进,引进Nesterov后,优化算法可以在“山坡”再次上升之前“减速”。其参数更新的方法为:
v = α v − ϵ ∇ L ( f ( x ; θ − α v ) , y ) θ = θ + v v=\alpha v-\epsilon∇L(f(x;\theta-\alpha v),y) \\ \theta=\theta+v v=αv−ϵ∇L(f(x;θ−αv),y)θ=θ+v
Nesterov动量和动量的区别在于梯度计算上,Nesterov动量的梯度计算是在施加了动量 v v v之后。
AdaGrad算法独立地适应所有模型参数的学习率,缩放每个参数反比于其梯度历史平方值总和的平方根。具有最大偏导的参数相应在参数空间有一个快速下降的学习率,而具有小偏导的参数在学习率上有较小的下降。
AdaGrad参数更新的方式为:
r = r + g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ r=r+g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta r=r+g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
其中
优点:消除了手动调整学习率的需要。
缺点:会导致累计量 r r r在迭代过程中不断增加,最终导致学习率变得无限小,导致参数不再更新。
RMSProp是在AdaGrad的基础上继续的修改,使其在非凸问题中效果更好,它改变梯度累计为指数加权平均的移动平均,该算法使用指数衰减平均以丢弃遥远过去的历史。
RMSProp参数更新的方式为:
r = ρ r + ( 1 − ρ ) g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta r=ρr+(1−ρ)g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
带Nesterov动量的RMSProp参数更新的方式为:
θ ^ = θ + α v g = ∇ L ( f ( x ; θ ^ ) , y ) r = ρ r + ( 1 − ρ ) g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ \hat\theta=\theta+\alpha v \\ g=∇L(f(x;\hat\theta),y) \\ r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta θ^=θ+αvg=∇L(f(x;θ^),y)r=ρr+(1−ρ)g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
Adam更新参数:
其中:
在Adam中, s s s的更新是基于梯度 g g g的 L 2 L_2 L2范数即 ∣ g ∣ 2 |g|^2 ∣g∣2,我们可以将其推广到 L p L_{p} Lp范数,即
s = ρ 1 s + ( 1 − ρ 1 ) ∣ g ∣ p s=\rho_1s+(1-\rho_1)|g|^p s=ρ1s+(1−ρ1)∣g∣p
当 p p p很大时,会导致数值变得不稳定,所以通常采用 L 1 L_1 L1和 L 2 L_2 L2范数,而当 p → ∞ p→∞ p→∞时,通常也会表现为稳定。
所以就有了:
s = ρ 1 ∞ s + ( 1 − ρ 1 ∞ ) ∣ g ∣ ∞ = m a x ( ρ 1 s , ∣ g ∣ ) s=\rho_1^∞s+(1-\rho_1^∞)|g|^∞=max(\rho_1s,|g|) s=ρ1∞s+(1−ρ1∞)∣g∣∞=max(ρ1s,∣g∣)
Adam与Nesterov的结合。
Hessian矩阵:
如果一个函数 F F F的二阶导数都存在,则 F F F的Hessian矩阵为
[ ∂ 2 F ∂ x 1 2 ∂ 2 F ∂ x 1 ∂ x 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x 1 ∂ x n ∂ 2 F ∂ x 2 ∂ x 1 ∂ 2 F ∂ x 2 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x 2 ∂ x n . . . . . . . . . . . . ∂ 2 F ∂ x n ∂ x 1 ∂ 2 F ∂ x n ∂ x 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x n 2 ] \begin{bmatrix} {∂^2F\over∂x_1^2}&{∂^2F\over∂x_1∂x_2}&···&{∂^2F\over∂x_1∂x_n} \\ {∂^2F\over∂x_2∂x_1}&{∂^2F\over∂x_2^2}&···&{∂^2F\over∂x_2∂x_n} \\.&.&.&. \\.&.&.&. \\.&.&.&. \\{∂^2F\over∂x_n∂x_1}&{∂^2F\over∂x_n∂x_2}&···&{∂^2F\over∂x_n^2} \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡∂x12∂2F∂x2∂x1∂2F...∂xn∂x1∂2F∂x1∂x2∂2F∂x22∂2F...∂xn∂x2∂2F⋅⋅⋅⋅⋅⋅...⋅⋅⋅∂x1∂xn∂2F∂x2∂xn∂2F...∂xn2∂2F⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
牛顿法解决优化问题:
对于函数 f ( x ) f(x) f(x),我们要想求得 f ( x ) f(x) f(x)的最小值,实际上就是找到 f ( x ) f(x) f(x)一阶导数为0,二阶导数大于0的点,将这些点记为 x i x_i xi,那么 f m i n ( x ) = m i n ( f ( x i ) ) f_{min}(x)=min(f(x_i)) fmin(x)=min(f(xi))。
那么又如何通过类似于梯度下降算法的迭代算法帮助我们找到 f m i n ( x ) f_{min}(x) fmin(x)呢?
现在我们假设 f ( x ) f(x) f(x)具有二阶偏导数,在迭代第 k k k次时的自变量为 x ( k ) x^{(k)} x(k),那么可将 f ( x ) f(x) f(x)在 x ( k ) x^{(k)} x(k)附近进行二阶泰勒展开:
f ( x ) = f ( x ( k ) ) + g k T ( x − x ( k ) ) + 1 2 ( x − x ( k ) ) T H ( x ( k ) ) ( x − x ( k ) ) {f(x) = f(x^{(k)})+g^T_k(x-x^{(k)})+{1\over2}(x-x^{(k)})^TH(x^{(k)})(x-x^{(k)})} f(x)=f(x(k))+gkT(x−x(k))+21(x−x(k))TH(x(k))(x−x(k))
其中, g k T = ∇ f ( x ( k ) ) g^T_k=∇f(x^{(k)}) gkT=∇f(x(k))是 f ( x ) f(x) f(x)在 x ( k ) x^{(k)} x(k)点处的梯度。 H ( x ( k ) ) H(x^{(k)}) H(x(k))是 f ( x ) f(x) f(x)在 x ( k ) x^{(k)} x(k)点处的Hessian矩阵。
为了找到一阶导数为0,二阶导数大于0的点。我们需要对二阶展开式再次求导,即:
∇ f ( x ) = g k + H k ( x − x ( k ) ) ∇f(x) = g_k+H_k(x-x^{(k)}) ∇f(x)=gk+Hk(x−x(k))
令 ∇ f ( x ) = 0 ∇f(x)=0 ∇f(x)=0,得到 g k + H k ( x − x ( k ) ) = 0 g_k+H_k(x-x^{(k)})=0 gk+Hk(x−x(k))=0,那么 x = x ( k ) − H k − 1 g k x=x^{(k)}-H_k^{-1}g_k x=x(k)−Hk−1gk
于是我们就得到了迭代求解极小值点 x x x的方法,即
x ( k + 1 ) = x ( k ) − H k − 1 g k x^{(k+1)}=x^{(k)}-H_k^{-1}g_k x(k+1)=x(k)−Hk−1gk
牛顿法存在的缺点:
思想:不用二阶偏导数而构造出可以近似Hessian矩阵(或Heissian矩阵的逆)的正定对称矩阵。
def update_parameters(parameters,grads,learning_rate):
"""
使用梯度下降更新参数
:param parameters:包含参数的字典
:param grads:包含梯度的字典
:param learning_rate:学习率,为超参数
:return:
parameters:更新后的参数
"""
L = len(parameters) // 2
for l in range(L):
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * grads["dw" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]
return parameters
def update_parameters_with_momentun(parameters,grads,v,beta,learning_rate):
"""
使用动量更新参数
:param parameters:个字典类型的变量,包含了以下字段:
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
:param grads:一个包含梯度值的字典变量,具有以下字段:
grads["dW" + str(l)] = dWl
grads["db" + str(l)] = dbl
:param v:包含当前速度的字典变量,具有以下字段:
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
:param beta:超参数,动量,实数
:param learning_rate:学习率,实数
:return:
:parameters - 更新后的参数字典
:v - 包含了更新后的速度变量
"""
L = len(parameters) // 2
for l in range(L):
v["dw" + str(l + 1)] = beta * v["dw" + str(l + 1)] + (1 - beta) * grads["dw" + str(l + 1)]
v["db" + str(l + 1)] = beta * v["db" + str(l + 1)] + (1 - beta) * grads["db" + str(l + 1)]
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * v["dw" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * v["db" + str(l + 1)]
return parameters,v
def update_parameters_with_adam(parameters, grads, v, s, t, learning_rate=0.01, beta1=0.9, beta2=0.999, epsilon=1e-8):
"""
使用Adam更新参数
参数:
parameters - 包含了以下字段的字典:
parameters['W' + str(l)] = Wl
parameters['b' + str(l)] = bl
grads - 包含了梯度值的字典,有以下key值:
grads['dW' + str(l)] = dWl
grads['db' + str(l)] = dbl
v - Adam的变量,第一个梯度的移动平均值,是一个字典类型的变量
s - Adam的变量,平方梯度的移动平均值,是一个字典类型的变量
t - 当前迭代的次数
learning_rate - 学习率
beta1 - 动量,超参数,用于第一阶段,使得曲线的Y值不从0开始(参见天气数据的那个图)
beta2 - RMSprop的一个参数,超参数
epsilon - 防止除零操作(分母为0)
返回:
parameters - 更新后的参数
v - 第一个梯度的移动平均值,是一个字典类型的变量
s - 平方梯度的移动平均值,是一个字典类型的变量
"""
L = len(parameters) // 2
v_corrected = {} # 偏差修正后的值
s_corrected = {} # 偏差修正后的值
for l in range(L):
# 梯度的移动平均值,输入:"v , grads , beta1",输出:" v "
v["dw" + str(l + 1)] = beta1 * v["dw" + str(l + 1)] + (1 - beta1) * grads["dw" + str(l + 1)]
v["db" + str(l + 1)] = beta1 * v["db" + str(l + 1)] + (1 - beta1) * grads["db" + str(l + 1)]
# 计算第一阶段的偏差修正后的估计值,输入"v , beta1 , t" , 输出:"v_corrected"
v_corrected["dw" + str(l + 1)] = v["dw" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)
v_corrected["db" + str(l + 1)] = v["db" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)
# 计算平方梯度的移动平均值,输入:"s, grads , beta2",输出:"s"
s["dw" + str(l + 1)] = beta2 * s["dw" + str(l + 1)] + (1 - beta2) * np.square(grads["dw" + str(l + 1)])
s["db" + str(l + 1)] = beta2 * s["db" + str(l + 1)] + (1 - beta2) * np.square(grads["db" + str(l + 1)])
# 计算第二阶段的偏差修正后的估计值,输入:"s , beta2 , t",输出:"s_corrected"
s_corrected["dw" + str(l + 1)] = s["dw" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)
s_corrected["db" + str(l + 1)] = s["db" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)
# 更新参数,输入: "parameters, learning_rate, v_corrected, s_corrected, epsilon". 输出: "parameters".
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * (
v_corrected["dw" + str(l + 1)] / np.sqrt(s_corrected["dw" + str(l + 1)] + epsilon))
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * (
v_corrected["db" + str(l + 1)] / np.sqrt(s_corrected["db" + str(l + 1)] + epsilon))
return (parameters, v, s)