参数化模型
y ˉ = G ( x , w ) \bar{y} = G(x,w) yˉ=G(x,w)
简单来讲,参数化模型就是依赖于输入和可训练参数的函数。 其中,可训练参数在不同训练样本中是共享的,而输入则因每个样本不同而不同。 在大多数深度学习框架中,参数是隐性的:当函数被调用时,参数不被传递。 如果把模型比作面向对象编程,这些参数相当于被“储存在函数中”。
参数化模型(上文提到的函数)包含一个参数向量,这个模型可以将一个输入转化成输出。 在监督学习中,我们把模型的输出 ( y ˉ ) (\bar{y}) (yˉ)送入代价函数 ( C ( y , y ˉ ) ) (C(y,\bar{y} )) (C(y,yˉ)),并将它与正确答案 ( y ) ({y}) (y) 对比。 图1是这个过程的示意图。
下列是几个参数化模型的例子。
y ˉ = ∑ i w i x i , C ( y , y ˉ ) = ∥ y − y ˉ ∥ 2 \bar{y} = \sum_i w_i x_i, C(y,\bar{y}) = \Vert y - \bar{y}\Vert^2 yˉ=i∑wixi,C(y,yˉ)=∥y−yˉ∥2
y ˉ = arg min k ∥ x − w k , . ∥ 2 \bar{y} = \underset{k}{\arg\min} \Vert x - w_{k,.} \Vert^2 yˉ=kargmin∥x−wk,.∥2
复杂的函数也可以被用到参数化模型中。
损失函数
在训练过程中,我们最小化损失函数。 大体上存在两种损失函数:
逐样本损失函数 - L ( x , y , w ) = C ( y , G ( x , w ) ) L(x,y,w) = C(y, G(x,w)) L(x,y,w)=C(y,G(x,w))
平均损失函数 - 对于任意一个样本的集合 S = { ( x [ p ] , y [ p ] ) ∣ p ∈ { 0 , ⋯ , P − 1 } } S = \lbrace(x[p],y[p]) \mid p \in \lbrace 0, \cdots, P-1 \rbrace \rbrace S={(x[p],y[p])∣p∈{0,⋯,P−1}},
在集合 SS 上的平均损失函数定义为: L ( S , w ) = 1 P ∑ ( x , y ) L ( x , y , w ) = 1 P ∑ P = 0 P − 1 L ( x [ p ] , y [ p ] , w ) L(S,w) = \frac{1}{P} \sum_{(x,y)}L(x,y,w) = \frac{1}{P} \sum_{P=0}^{P-1}L(x[p],y[p],w) L(S,w)=P1∑(x,y)L(x,y,w)=P1∑P=0P−1L(x[p],y[p],w)
在标准的监督学习里,(逐样本)损失函数就是代价函数的输出。 机器学习在很大程度上就是寻找函数的最优解(经常情况下,是寻找某个函数的最小值)。 有的时候(比如在生成对抗网络里),我们学习寻找两个函数的纳什均衡。 我们用一系列和梯度相关的方法(包含但不总是梯度下降)寻找函数的最优解。
假设我们可以很容易地计算一个函数的梯度,我们可以用 梯度相关方法 去寻找这个函数的最小值。 这个方法建立在一个假设上,即,这个函数在大部分区间连续可微分(并不需要在所有地方连续可微分)。
梯度下降背后的逻辑直觉 - 想象在一个大雾弥漫的夜晚,我们在一座山里,想要回到山下的村庄。 因为视野非常有限,我们只能靠观察路面倾斜的角度来判断下山的方向,并且向着那个方向前进。
不同梯度下降的方法:
对整批样本进行梯度下降时的参数更新法则: w ← w − η ∂ L ( S , w ) ∂ w w \leftarrow w - \eta \frac{\partial L(S,w)}{\partial w} w←w−η∂w∂L(S,w)
SGD (随机梯度下降)的参数更新法则:从 { 0 , ⋯ , P − 1 } \lbrace 0, \cdots, P-1 \rbrace {0,⋯,P−1}里选取一个 p p p ,进行参数 w w w的更新
w ← w − η ∂ L ( x [ p ] , y [ p ] , w ) ∂ w w \leftarrow w - \eta \frac{\partial L(x[p], y[p],w)}{\partial w} w←w−η∂w∂L(x[p],y[p],w)
SDG利用了样本的冗余:
其中, w {w} w 表示要进行优化的参数。
在这里, η \eta η 是一个常数,但是在更高级的算法中,它可以是一个矩阵。如果这是一个半正定矩阵,虽然我们确实是在往山下走,但是我们走的方向并不一定是向着最陡的方向走。 事实上,最陡的那个方向并不一定总是我们想要去的方向。
如果我们处理的函数不可微分,比如,函数曲线上有个洞,曲线是阶梯形状,或者干脆是平的,这时候梯度不能提供任何有用信息,我们必须借助其他方法 - 比如0阶方法,或者一些不需要梯度计算的方法。 深度学习说白了就是围绕着梯度相关方法展开的。
其实也存在例外,比如在RL(强化学习)中,我们不直接计算梯度,而是去估计梯度。 举个例子,我们教一个机器人学习骑车,它经常摔来摔去。 这时候我们可以让目标函数是自行车不摔倒的累计时长。 不幸的是,这种目标函数无法计算梯度。 我们的机器人需要自己探索,去找到适合它自己的骑车的方法。
虽然 RL 的代价函数在多数情况下不可微分,神经网络依然可以适用梯度相关的方法去学习。 这是监督学习和强化学习的主要区别之一。 在强化学习里,代价函数 C C C 不可微分。 实际上它是完全未知的。 像一个黑箱子一样,你给它一些输入,它返回一些输出,你并不知道正确答案是什么,只有 y ˉ 和 C \bar{y}和C yˉ和C,能做的只是改变 y ˉ \bar{y} yˉ看C会怎么改变。 这也导致 RL 非常低效,尤其当参数向量维度很高的时候(换言之,算法需要在巨大的空间里探索,寻找解决方案)。
在RL里,有一种非常常用的方法叫做 Actor Critic(演员-评论家)。 其中,评论家模块有一个自己的代价函数C,这个模块是已知并且可训练的。 这个模块是可微分的,人们训练这个模块去近似真正的代价函数或者奖励(reward)函数。 这样以来,人们就可以用可微分的函数去近似代价函数,从而进行反向传播。
在实际操作中,我们利用随机梯度下降算法来计算目标函数在参数上的梯度。 相比于在所有样本上计算目标函数的梯度,再进行平均,随机梯度下降算法每步只选取一个样本,计算损失 L L L,进而获取损失函数在训练参数上的梯度值。 之后,随机梯度下降算法在这个梯度的反方向上移动一步。
w ← w − η ∂ L ( x [ p ] , y [ p ] , w ) ∂ w w \leftarrow w - \eta \frac{\partial L(x[p], y[p],w)}{\partial w} w←w−η∂w∂L(x[p],y[p],w)
在这个公式中,我们用 w w w 减去步长乘以上述的(基于每个样本 x [ p ] , y [ p ] x[p],y[p] x[p],y[p] 的损失计算出的)梯度值,而得到新的 w w w。
因为基于每个样本进行计算,我们得到的轨迹存在非常大的干扰,如图3所示。 我们计算出的损失并不是直指山下,而是看起来非常随机。 每个样本都会把我们的损失拽向各不一样的方向。 不过平均来看,这些小的移动把我们带去了山谷。 虽然看起来有点低效,这种方法其实比起在整批样本上做梯度下降要快得多,尤其是当样本之间有很多重复信息的时候。
在实际中,我们并不用单个样本做随机梯度下降,相反,我们每次更新用一小批的样本(minibatch)。 我们计算一小批样本的平均梯度,然后移动一小步。 我们这么做的唯一原因是因为这样可以更有效地优化算法,让它们更适应当前的硬件(比如GPU,多核CPU)。 批量运算是一种简单的并行化运算方法。
传统的神经网络由互相穿插的线性运算和逐点非线性运算构成。 其中,线性运算就是矩阵-向量乘法,我们将(输入)向量与权值矩阵相乘。 之后,我们将相乘之后的加权和(向量)进行非线性运算(比如 ReLU ( ⋅ ) , tanh ( ⋅ ) , … ) \texttt{ReLU}(\cdot), \tanh(\cdot), …) ReLU(⋅),tanh(⋅),…)
图4所示是一个两层的神经网络,图中可以看到两对线性-非线性层。 也有人称这个网络为三层网络,因为他们包括了输入变量。 注意,当没有中间的非线性层时,这个网络有可能会转化成一个单层网络,因为两个线性函数的乘积依然是一个线性函数。
图5展示了神经网络里,线性和非线性模块是如何堆叠的:
在图中, s [ i ] s[i] s[i]代表了单位 i {i} i 的加权和:
s [ i ] = ∑ j ∈ U P ( i ) w [ i , j ] ⋅ z [ j ] s[i]=\sum_{j \in UP(i)}w[i,j]\cdot z[j] s[i]=j∈UP(i)∑w[i,j]⋅z[j]
其中 U P ( i ) UP(i) UP(i)表示 i i i之前一层的单位, z [ j ] z[j] z[j] 是之前一层的第 j j j个输出。
从而我们计算 z [ i ] z[i] z[i] 用:
z [ i ] = f ( s [ i ] ) z[i]=f(s[i]) z[i]=f(s[i])
其中 f f f 是非线性函数。
具体这里涉及到的微积分的知识可以参照3Blue1Brown的深度学习课程
我们要介绍的第一种方法,是通过非线性函数进行的反向传播。 我们从神经网络里单拿出非线性函数 h h h(即上面的 f f f) ,忽略黑盒子里的其他部分。
这里我们利用链式法则来计算梯度:
g ( h ( s ) ) ′ = g ′ ( h ( s ) ) ⋅ h ′ ( s ) g(h(s))' = g'(h(s))\cdot h'(s) g(h(s))′=g′(h(s))⋅h′(s)
其中, h ’ ( s ) h’(s) h’(s) 是 z z z对 s s s的导数,即 d z d s \frac{\mathrm{d}z}{\mathrm{d}s} dsdz 。 为了更清楚地表示这个导数,我们改写上面的公式为:
d C d s = d C d z ⋅ d z d s = d C d z ⋅ h ′ ( s ) \frac{\mathrm{d}C}{\mathrm{d}s} = \frac{\mathrm{d}C}{\mathrm{d}z}\cdot \frac{\mathrm{d}z}{\mathrm{d}s} = \frac{\mathrm{d}C}{\mathrm{d}z}\cdot h'(s) dsdC=dzdC⋅dsdz=dzdC⋅h′(s)
这样以来,我们只要可以用串联的一系列函数来表示一个神经网络,我们就可以逐层乘以每个 h {h} h函数的导数,从而进行反向传播。
我们也可以用扰动法的角度去想这个问题。 对 s s s添加 d s \mathrm{d}s ds 的扰动,会相应地在 z z z上添加如下扰动:
d z = d s ⋅ h ′ ( s ) \mathrm{d}z = \mathrm{d}s \cdot h'(s) dz=ds⋅h′(s)
从而,这也会在 C C C上添加如下扰动:
d C = d z ⋅ d C d z = d s ⋅ h ’ ( s ) ⋅ d C d z \mathrm{d}C = \mathrm{d}z\cdot\frac{\mathrm{d}C}{\mathrm{d}z} = \mathrm{d}s\cdot h’(s)\cdot\frac{\mathrm{d}C}{\mathrm{d}z} dC=dz⋅dzdC=ds⋅h’(s)⋅dzdC
这样以来,我们得到了和之前一样的公式。
对于线性模块,我们通过加权和进行万象传播。 这次,我们关注变量 z z z和那一系列变量 s s s之间的连接,默认其他的部分是黑盒子。
这次,扰动是一个加权和。 z z z会对多个变量造成影响。 对 z z z施加扰动 d z \mathrm{d}z dz,对相应对 s [ 0 ] s[0] s[0], s [ 1 ] s[1] s[1]和 s [ 2 ] s[2] s[2]施加如下扰动:
d s [ 0 ] = w [ 0 ] ⋅ d z \mathrm{d}s[0]=w[0]\cdot \mathrm{d}z ds[0]=w[0]⋅dz
d s [ 1 ] = w [ 1 ] ⋅ d z \mathrm{d}s[1]=w[1]\cdot \mathrm{d}z ds[1]=w[1]⋅dz
d s [ 2 ] = w [ 2 ] ⋅ d z \mathrm{d}s[2]=w[2]\cdot\mathrm{d}z ds[2]=w[2]⋅dz
相似地,这会对 C C C 施加如下扰动:
d C = d s [ 0 ] ⋅ d C d s [ 0 ] + d s [ 1 ] ⋅ d C d s [ 1 ] + d s [ 2 ] ⋅ d C d s [ 2 ] \mathrm{d}C = \mathrm{d}s[0]\cdot \frac{\mathrm{d}C}{\mathrm{d}s[0]}+\mathrm{d}s[1]\cdot \frac{\mathrm{d}C}{\mathrm{d}s[1]}+\mathrm{d}s[2]\cdot\frac{\mathrm{d}C}{\mathrm{d}s[2]} dC=ds[0]⋅ds[0]dC+ds[1]⋅ds[1]dC+ds[2]⋅ds[2]dC
因此 C C C会受到三个变量的影响而改变,将三个变量对C变化的贡献加起来。
d C d z = d C d s [ 0 ] ⋅ w [ 0 ] + d C d s [ 1 ] ⋅ w [ 1 ] + d C d s [ 2 ] ⋅ w [ 2 ] \frac{\mathrm{d}C}{\mathrm{d}z} = \frac{\mathrm{d}C}{\mathrm{d}s[0]}\cdot w[0]+\frac{\mathrm{d}C}{\mathrm{d}s[1]}\cdot w[1]+\frac{\mathrm{d}C}{\mathrm{d}s[2]}\cdot w[2] dzdC=ds[0]dC⋅w[0]+ds[1]dC⋅w[1]+ds[2]dC⋅w[2]
s k + 1 = w k z k s s_{k+1}=w_kz_ks sk+1=wkzks
z k = h ( s k ) z z_k=h(s_k)z zk=h(sk)z
w k w_k wk 是矩阵; z k z_k zk是向量; h h h 是一个函数,它对输入的每一个元素调用 h {h} h函数。 如图所示是一个三层神经网络,这个神经网络由一系列的线性-非线性函数对组成。 大多数现代神经网络并不像图中所示的例子一样具有这么清晰的结构,它们一般来说要更加复杂。
import torch
from torch import nn
image = torch.randn(3, 10, 20)
d0 = image.nelement()
class mynet(nn.Module):
def __init__(self, d0, d1, d2, d3):
super().__init__()
self.m0 = nn.Linear(d0, d1)
self.m1 = nn.Linear(d1, d2)
self.m2 = nn.Linear(d2, d3)
def forward(self,x):
z0 = x.view(-1) # flatten input tensor
s1 = self.m0(z0)
z1 = torch.relu(s1)
s2 = self.m1(z1)
z2 = torch.relu(s2)
s3 = self.m2(z2)
return s3
model = mynet(d0, 60, 40, 10)
out = model(image)
下面我们展示一个反向传播的例子。
一个功能模块可能具有多个输入和多个输出,也有可能输入是向量输出也是向量。
z g : [ d g × 1 ] , c o l u n m v e c t o r z_g : [d_g\times 1],colunm\ vector zg:[dg×1],colunm vector
z f : [ d f × 1 ] z_f:[d_f\times 1] zf:[df×1]
∂ c ∂ z f = ∂ c ∂ z g ∂ z g ∂ z f \frac{\partial c}{\partial{z_f}}=\frac{\partial c}{\partial{z_g}}\frac{\partial {z_g}}{\partial{z_f}} ∂zf∂c=∂zg∂c∂zf∂zg
[ 1 × d f ] = [ 1 × d g ] × [ d g × d f ] [1\times d_f]= [1\times d_g]\times[d_g\times d_f] [1×df]=[1×dg]×[dg×df]
这是利用链式法则计算 ∂ c ∂ z f \frac{\partial c}{\partial{z_f}} ∂zf∂c的基本公式。 值得注意的是,一个标量对于一个向量的梯度,是一个和这个向量同样形状的一个向量。 我们统一用行向量而非列向量去表示这些向量(梯度)。
对于上面的式子,我们可以进行转置,这样输出的偏导数仍然为列向量,但要注意乘法顺序发生变化了
∂ c ∂ z f T = ∂ z g ∂ z f T ∂ c ∂ z g T \frac{\partial c}{\partial{z_f}}^T=\frac{\partial {z_g}}{\partial{z_f}}^T\frac{\partial c}{\partial{z_g}}^T ∂zf∂cT=∂zf∂zgT∂zg∂cT
[ d f × 1 ] = [ d f × d g ] × [ d g × 1 ] [d_f\times 1]= [d_f\times d_g]\times[d_g\times 1] [df×1]=[df×dg]×[dg×1]
雅可比矩阵(一阶偏导数矩阵)
( ∂ z g ∂ z f ) i j = ( ∂ z g ) i ( ∂ z f ) j \left(\frac{\partial{z_g}}{\partial {z_f}}\right)_{ij}=\frac{(\partial {z_g})_i}{(\partial {z_f})_j} (∂zf∂zg)ij=(∂zf)j(∂zg)i
利用 ∂ z g ∂ z f \frac{\partial {z_g}}{\partial {z_f}} ∂zf∂zg (雅可比矩阵的元),通过已知代价函数对于 z g z_g zg的梯度,我们可以计算代价函数对于 z f z_f zf的梯度。 每一个元 i j ij ij 等于输出向量的第 i i i个元素对于输入向量第 j j j个元素的偏导数。
当网络中有一系列串联的模块,我们可以在每一层用乘雅可比矩阵的方法一路向下,从而得到每个变量的梯度值。
设想一个由多个模块堆叠的神经网络,如图9所示。
在反向传播算法中,我们需要两套梯度:一套是对于状态(网络中的每一个模块)的梯度,另一套是对于权值(某一个模块中所有的参数)的梯度值。 因此,对于每个模块,我们需要两个雅可比矩阵。 这里我们再次利用链式法则完成反向传播。
∂ c ∂ z k = ∂ c ∂ z k + 1 ∂ z k + 1 ∂ z k = ∂ c ∂ z k + 1 ∂ f k ( z k , w k ) ∂ z k \frac{\partial c}{\partial {z_k}}=\frac{\partial c}{\partial {z_{k+1}}}\frac{\partial {z_{k+1}}}{\partial {z_k}}=\frac{\partial c}{\partial {z_{k+1}}}\frac{\partial f_k(z_k,w_k)}{\partial {z_k}} ∂zk∂c=∂zk+1∂c∂zk∂zk+1=∂zk+1∂c∂zk∂fk(zk,wk)
∂ c ∂ w k = ∂ c ∂ z k + 1 ∂ z k + 1 ∂ w k = ∂ c ∂ z k + 1 ∂ f k ( z k , w k ) ∂ w k \frac{\partial c}{\partial {w_k}}=\frac{\partial c}{\partial {z_{k+1}}}\frac{\partial {z_{k+1}}}{\partial {w_k}}=\frac{\partial c}{\partial {z_{k+1}}}\frac{\partial f_k(z_k,w_k)}{\partial {w_k}} ∂wk∂c=∂zk+1∂c∂wk∂zk+1=∂zk+1∂c∂wk∂fk(zk,wk)
接下来我们会考虑一个反向传播的例子,并使用图像来辅助。任意的函数 G ( w ) G(w) G(w)输入到损失函数 C C C中,这可以用一个图来表示。经由雅可比矩阵的乘法操作,我们能将这个图转换成一个反向计算梯度的图。(注意 Pytorch 和 Tensorflow 已经自动地为使用者完成这件事了,也就是说,向前的图自动的被「倒反」来创造导函数的图形以反向传播梯度。)
在这个范例中,右方的绿色图代表梯度的图。跟着图从最上方的节点开始的是:
∂ C ( y , y ˉ ) ∂ w = 1 ⋅ ∂ C ( y , y ˉ ) ∂ y ˉ ⋅ ∂ G ( x , w ) ∂ w \frac{\partial C(y,\bar{y})}{\partial w}=1 \cdot \frac{\partial C(y,\bar{y})}{\partial\bar{y}}\cdot\frac{\partial G(x,w)}{\partial w} ∂w∂C(y,yˉ)=1⋅∂yˉ∂C(y,yˉ)⋅∂w∂G(x,w)
从维度来说, ∂ C ( y , y ˉ ) ∂ w \frac{\partial C(y,\bar{y})}{\partial w} ∂w∂C(y,yˉ)是一个行向量,大小为 1 × N 1\times N 1×N,其中N是w中成员的数量; ∂ C ( y , y ˉ ) ∂ y ˉ \frac{\partial C(y,\bar{y})}{\partial \bar{y}} ∂yˉ∂C(y,yˉ)是个大小 1 × M 1\times M 1×M 的行向量,其中 M M M是输出的维度; ∂ y ˉ ∂ w = ∂ G ( x , w ) ∂ w \frac{\partial \bar{y}}{\partial w}=\frac{\partial G(x,w)}{\partial w} ∂w∂yˉ=∂w∂G(x,w)是个大小 M × N M\times N M×N 的矩阵,其中 M M M是 G G G输出的数量,而 N N N是 w w w 的维度。
注意当图的结构不固定而是依赖于数据时,情况可能更为复杂。比如,我们可以根据输入向量的长度来选择神经网络的模组。虽然这是可行的,当迭代数量过度增加,处理这个变化的难度会增加。
除了习惯的线性和 ReLU 模组,还有其他预先建立的模组。他们十分有用因为他们为了各自的功能被特别的优化过(而非只是用其他初阶模组拼凑而成)。
线性: Y = W ⋅ X Y=W\cdot X Y=W⋅X
d C d X = W ⊤ ⋅ d C d Y d C d W = d C d Y ⋅ X ⊤ \begin{aligned} \frac{dC}{dX} &= W^\top \cdot \frac{dC}{dY} \\ \frac{dC}{dW} &= \frac{dC}{dY} \cdot X^\top \end{aligned} dXdCdWdC=W⊤⋅dYdC=dYdC⋅X⊤
ReLU: y = ( x ) + y=(x)^+ y=(x)+
d C d X = { 0 x < 0 d C d Y otherwise \frac{dC}{dX} = \begin{cases} 0 & x<0\\ \frac{dC}{dY} & \text{otherwise} \end{cases} dXdC={0dYdCx<0otherwise
d C d X = d C d Y 1 + d C d Y 2 \frac{dC}{dX}=\frac{dC}{dY_1}+\frac{dC}{dY_2} dXdC=dY1dC+dY2dC
d C d X 1 = d C d Y ⋅ 1 and d C d X 2 = d C d Y ⋅ 1 \frac{dC}{dX_1}=\frac{dC}{dY}\cdot1 \quad \text{and}\quad \frac{dC}{dX_2}=\frac{dC}{dY}\cdot1 dX1dC=dYdC⋅1anddX2dC=dYdC⋅1
最大值: Y = max ( X 1 , X 2 ) Y=\max(X_1,X_2) Y=max(X1,X2)
因为这个函数也可以写作:
Y = max ( X 1 , X 2 ) = { X 1 X 1 > X 2 X 2 else ⇒ d Y d X 1 = { 1 X 1 > X 2 0 else Y=\max(X_1,X_2)=\begin{cases} X_1 & X_1 > X_2 \\ X_2 & \text{else} \end{cases} \Rightarrow \frac{dY}{dX_1}=\begin{cases} 1 & X_1 > X_2 \\ 0 & \text{else} \end{cases} Y=max(X1,X2)={X1X2X1>X2else⇒dX1dY={10X1>X2else
因此,根据链式法则
d C d X 1 = { d C d Y ⋅ 1 X 1 > X 2 0 else \frac{dC}{dX_1}=\begin{cases} \frac{dC}{dY}\cdot1 & X_1 > X_2 \\ 0 & \text{else} \end{cases} dX1dC={dYdC⋅10X1>X2else
Pytorch中Softmax、Log_Softmax、NLLLoss以及CrossEntropyLoss的关系与区别详解
softmax的log似然代价函数(公式求导)
交叉熵softmax求导简单解释
SoftMax,另一个 Pytorch 模组,是一种方便的方式,可以将一组数字转换为0到1之间的数值,并使它们和为 1。这些数字可以理解为概率分布。因此,它经常用于分类问题。下方等式中的 y i y_i yi是一个记录着每一个类别概率的向量
y i = exp ( x i ) ∑ j exp ( x j ) y_i = \frac{\exp(x_i)}{\sum_j \exp(x_j)} yi=∑jexp(xj)exp(xi)
然而,使用 softmax 使网络容易面临梯度消失。梯度消失是一个问题,因为它会使得随后的权重无法被神经网络改动,进而停止神经网络进一步训练。Logistic sigmoid 函数,就是单一数值的 Softmax 函数,展现出当 s s s很大时, h ( s ) h(s) h(s)是 1 1 1,而当 s s s很小时, h ( s ) h(s) h(s)是 0。因为 sigmoid 函数在 h ( s ) = 0 h(s) = 0 h(s)=0 和 h ( s ) = 1 h(s) = 1 h(s)=1处是平坦的,其梯度为0,造成消失的梯度。
h ( s ) = 1 1 + e x p ( − s ) h(s)= \frac{1}{1+exp(−s)} h(s)=1+exp(−s)1
数学家想到可以用 logsoftmax 来解决 softmax 造成的梯度消失问题。LogSoftMax 是 Pytorch 当中的另一个基本模组。正如下方等式所示,LogSoftMax 组合了 softmax 和对数。
log ( y i ) = log ( exp ( x i ) Σ j exp ( x j ) ) = x i − log ( Σ j exp ( x j ) \log(y_i )= \log\left(\frac{\exp(x_i)}{\Sigma_j \exp(x_j)}\right) = x_i - \log(\Sigma_j \exp(x_j) log(yi)=log(Σjexp(xj)exp(xi))=xi−log(Σjexp(xj)
下方的等式提供同一个等式的另一种观点。下图显示函数中 log ( 1 + exp ( s ) ) \log(1 + \exp(s)) log(1+exp(s))的部份。当s非常小,其值为0,至于s很大时,值是s。如此一来,它不会造成饱和,就避免了梯度消失。
log ( exp ( s ) exp ( s ) + 1 ) = s − log ( 1 + exp ( s ) ) \log\left(\frac{\exp(s)}{\exp(s) + 1}\right)= s - \log(1 + \exp(s)) log(exp(s)+1exp(s))=s−log(1+exp(s))
对于有很多层的网络,ReLU 的效果最好,甚至使其他函数如 sigmoid、hyperbolic tangent tanh ( ⋅ ) \tanh(\cdot) tanh(⋅)相形之下过时了。ReLU 很有效的原因可能是因为它具有的一个尖点使它具有缩放的等变性。
在讲座里前面提到的 Log softmax是交叉熵损失的特例。Pytorch 里,请保证传给交叉熵损失函数时要使用 Log softmax 为输入(而非一般 softmax)。
如同之前所讨论的,小批量使你能更有效率的训练,因为数据中有重复;你不需要每一步对每个观察进行预测、计算损失以估计梯度。
顺序会造成影响。如果模型在一回的训练中只有来自同一类别的样本,它会去直接预测该类别而非学习为何要预测该类别。例如,如果你试着分类 MNIST 数据集的数字而没有打乱数据,那最后一层的偏置易开始总会预测零,接着改成总是预测一、二,依此类推。理想上,每个批量中都应该要有来自每个类别的样本。
不过是否要在每回(epoch)的训练都改变次序仍然存在争论。
训练之前,先归一化每个输入特征,让均值为0、标准差为1是很有用的。使用 RGB 图像数据时,经常会单独取每个通道的均值和标准差,以通道为单位进行归一化。举例而言,取数据集中所有蓝色通道的数值的均值 m b m_b mb 和标准差 σ b \sigma_b σb ,接着归一化每个图像的蓝色通道数值如下:
b [ i , j ] ′ = b [ i , j ] − m b max ( σ b , ϵ ) b_{[i,j]}^{'} = \frac{b_{[i,j]} - m_b}{\max(\sigma_b, \epsilon)} b[i,j]′=max(σb,ϵ)b[i,j]−mb
其中 ϵ \epsilon ϵ 是个任意小的数字,用于避免除以零。对绿色通道和红色通道进行同样操作。这个必要的操作使我们能从不同光线下的图像取得有用的信号;例如日光中的相片有很多红色,但水下的图片则几乎没有。
随着训练持续,学习率应该下降。实际上,大多进阶的模型是用 Adam/Momentum 这些能自我调整学习率的算法训练的,而非学习率固定的单纯 SGD。
机器学习中正则化项L1和L2的直观理解
你可以在损失函数中附上对巨大权重的损失。例如,使用 L2 正则化,我们定义损失为 L L L 并且如下更新权重 w w w,且 R ( w ) = ∥ w ∥ 2 R(w) = \Vert w \Vert^2 R(w)=∥w∥2:
L ( S , w ) = C ( S , w ) + α ∥ w ∥ 2 ∂ R ∂ w i = 2 w i w i = w i − η ∂ C ∂ w i = w i − η ( ∂ C ∂ w i + 2 α w i ) L(S, w) = C(S, w) + \alpha \Vert w \Vert^2\\ \frac{\partial R}{\partial w_i} = 2w_i \\ w_i = w_i - \eta\frac{\partial C}{\partial w_i} = w_i - \eta(\frac{\partial C}{\partial w_i} + 2 \alpha w_i) L(S,w)=C(S,w)+α∥w∥2∂wi∂R=2wiwi=wi−η∂wi∂C=wi−η(∂wi∂C+2αwi)
为了理解为何这称作权重衰减,我们可以将上方的方程式重写来展现我们在更新时把 w i w_i wi 乘以一个小于一的常数。
w i = ( 1 − 2 η α ) w i − η ∂ C ∂ w i w_i = (1 - 2 \eta \alpha) w_i - \eta\frac{\partial C}{\partial w_i} wi=(1−2ηα)wi−η∂wi∂C
L1 正则化(Lasso)是类似的,只不过我们使用 ∑ i ∣ w i ∣ \sum_i \vert w_i\vert ∑i∣wi∣而不是 ∥ w ∥ 2 \Vert w \Vert^2 ∥w∥2 。
本质上,正则化尝试告诉系统要以最短的权重向量来最小化损失函数。L1 正则化会将无用的权重缩减至 0。
权重要被随机的初始化,但它们不能太大或太小,因为输出得要有与输入差不多的方差。Pytorch 有诸多内建的初始化技巧。其中一个适合深层模型的是 Kaiming 初始化:权重的方差与输入数量的平方根成反比。
Dropout 是另一种正则化。它可以当做神经网络的另一层:它接受输入,随机将 n / 2 n/2 n/2 的输入设为零,并且回传这个结果为输出。这迫使系统从所有输入单元获得信息而不是过度依赖少数的输入单元,从而能将资讯分配于一层中的所有单元。这个方法最初是由Hinton et al (2012)提出。
更多技巧参见 LeCun et al 1998.
最后,注意反向传播不只适用于层层堆叠的模型;它可用于任何有向无环图(DAG)只要模组间具有偏序关系,
看看底下的 图 1(a) 。图中的点分布在这个螺旋的旋臂上,并且是在 R 2 \R^2 R2中。每个颜色代表一个类别标记。总共有 K = 3 K = 3 K=3 个不同的类别。数学上由 方程式 1(a) 表示。
图 1(b) 展现了一个类似的螺旋,加上高斯噪声。它的数学表示是 方程式 1(b) 。
在两个例子中,这些点都不是线性可分的。
X k ( t ) = t ( sin [ 2 π K ( 2 t + k − 1 ) ] cos [ 2 π K ( 2 t + k − 1 ) ] ) 0 ≤ t ≤ 1 , k = 1 , . . . , K X_{k}(t)=t\left(\begin{array}{c}{\sin \left[\frac{2 \pi}{K}(2 t+k-1)\right]} \\ {\cos \left[\frac{2 \pi}{K}(2 t+k-1)\right]}\end{array}\right) \\ 0 \leq t \leq 1, \quad k=1, ..., K Xk(t)=t(sin[K2π(2t+k−1)]cos[K2π(2t+k−1)])0≤t≤1,k=1,...,K
X k ( t ) = t ( sin [ 2 π K ( 2 t + k − 1 + N ( 0 , σ 2 ) ) ] cos [ 2 π K ( 2 t + k − 1 + N ( 0 , σ 2 ) ) ] ) 0 ≤ t ≤ 1 , X_{k}(t)=t\left(\begin{array}{c}{\sin \left[\frac{2 \pi}{K}(2 t+k-1 +\mathcal{N}\left(0, \sigma^{2}\right))\right]} \\ {\cos \left[\frac{2 \pi}{K}(2 t+k-1 +\mathcal{N}\left(0, \sigma^{2}\right))\right]}\end{array}\right)\\0 \leq t \leq 1, Xk(t)=t(sin[K2π(2t+k−1+N(0,σ2))]cos[K2π(2t+k−1+N(0,σ2))])0≤t≤1,
进行分类是什么意思呢?请想想看逻辑回归 (LR) 的情况。如果用逻辑回归来分类这个数据,它会创造一系列的线性平面(决策边界)以尝试分离数据到各自的类别。这种解法存在问题,那就是每个区域中,都有属于不同类别的点。因为螺旋的悬臂会跨越决策边界,这不是一个好的解法。
我们该如何解决这个问题?我们可以变换输入空间,使得数据变的线性可分。在训练一个神经网络的过程,它所习得的决策边界会试着符合训练数据的分布。
注意:一个神经总是用由下而上的方式来表示。第一层在最底下,最后一层在顶端。这是因为概念上,输入数据对神经网络处理的任务而言是低阶的特征。当资料向上遍历网络时,每一层会提取更高阶的特征。
上周,我们看到刚初始化的神经网络会任意的变换它的输入。这个变换(起初)对完成眼下的任务没有功效。我们来探索如何用数据来使这个变换具有与手边的任务有关的含义。下列是用来训练的输入数据。
我们将见到什么是一个全连接(FC)网络,以及它如何运作。
考虑图 4所描绘的网络。输入数据, x \boldsymbol x x,受到由 W h \boldsymbol W_h Wh 定义的仿射变换,再经过一个非线性变换。非线性变换的结果表示为 h \boldsymbol h h,代表一个隐藏的输出,也就是一个从网路外部看不见的输出。接下来又有另一个仿射变换( W y \boldsymbol W_y Wy)以及非线性变换,于是产出最终的输出 y ^ \boldsymbol{\hat{y}} y^ 。这个网络可以用下方的方程2来表示,其中 f f f和 g g g都是非线性函数。
h = f ( W h x + b h ) y ^ = g ( W y h + b y ) \begin{aligned} &\boldsymbol h=f\left(\boldsymbol{W}_{h} \boldsymbol x+ \boldsymbol b_{h}\right)\\ &\boldsymbol{\hat{y}}=g\left(\boldsymbol{W}_{y} \boldsymbol h+ \boldsymbol b_{y}\right) \end{aligned} h=f(Whx+bh)y^=g(Wyh+by)
之前看到的这样基础的神经网络只是一连串先仿射变换再非线性运算(挤压)的搭配。常用的非线性函数包含 ReLU、sigmoid、hyperbolic tangent 和 softmax。
上面展示的网络是一个 3 层的网络
1 - 输入神经元
2 - 隐藏神经元
3 - 输出神经元
因此,一个 3层的神经网络有 2个仿射变换。这可以类推至 n 层的网络。
现在让我们看看更复杂的例子。
这个网络有 3 个隐藏层,每一层都是全连接的。图 5 描绘了这个网络。
来考虑第二层的神经元 jj。它的激活值是: a j ( 2 ) = f ( w ( j ) x + b j ) = f ( ( ∑ i = 1 n w i ( j ) x i ) + b j ) ) a^{(2)}_j = f(\boldsymbol w^{(j)} \boldsymbol x + b_j) = f\Big( \big(\sum_{i=1}^n w_i^{(j)} x_i\big) +b_j ) \Big) aj(2)=f(w(j)x+bj)=f((∑i=1nwi(j)xi)+bj)) 其中 w ( j ) \boldsymbol w^{(j)} w(j) 是 W ( 1 ) \boldsymbol W^{(1)} W(1) 的第 j 行。
注意在此例中输入层的激活函数只是恒等函数。隐藏层的激活函数可以是 ReLU、hyperbolic tangent、sigmoid、soft (arg)max 等等。
最后一层的激活函数通常取决于使用情况,这篇 Piazza post 提供了一个解释。
让我们再次考虑两层的神经网络(输入层,隐藏层,输出层),见图 6
这个函数是什么样子的呢?
y ^ = y ^ ( x ) , y ^ : R n → R K , x ↦ y ^ \boldsymbol {\hat{y}} = \boldsymbol{\hat{y}(x)}, \boldsymbol{\hat{y}}: \mathbb{R}^n \rightarrow \mathbb{R}^K, \boldsymbol{x} \mapsto \boldsymbol{\hat{y}} y^=y^(x),y^:Rn→RK,x↦y^
不过,将视隐藏层视觉化会很有帮助,这样,这个映射关系就被扩展为:
y ^ : R n → R d → R K , d ≫ n , K \boldsymbol{\hat{y}}: \mathbb{R}^{n} \rightarrow \mathbb{R}^d \rightarrow \mathbb{R}^K, d \gg n, K y^:Rn→Rd→RK,d≫n,K
这个情况的范例构造长什么样子?在我们的例子里,输入的维度是2(n=2),有一个 1000 维(d = 1000)的隐藏层,以及三个类别(C = 3)。就实际而言,有很多好的理由不在一个隐藏层中放这么多神经元,所以我们可以把这一个隐藏层换成三个各具有 10 个神经元的隐藏层( 1000 → 10 × 10 × 10 1000 \rightarrow 10 \times 10 \times 10 1000→10×10×10)。
那么典型的训练是如何?我们最好用损失(losses)的标准术语来阐述这个过程。
首先,来重新介绍 soft (arg)max 并明确的说:它是一个最后一层经常使用的激活函数,用于搭配 negative log-likelihood loss 处理多类别预测。如同 LeCun 教授在讲座中所说,这是因为它提供比 sigmoid 与均方误差更好的梯度。而且,使用 soft (arg)max 时,你的最后一层已经被归一化(最后一层中所有神经元输出的和为 1),以一种对于梯度方法来说较显性归一化(除以范数)更好的方法。
soft (arg)max 会给你最后一层的 logit,如下:
soft(arg)max ( l ) [ c ] = exp ( l [ c ] ) ∑ k = 1 K exp ( l [ k ] ) ∈ ( 0 , 1 ) \text{soft{(arg)}max}(\boldsymbol{l})[c] = \frac{ \exp(\boldsymbol{l}[c])} {\sum^K_{k=1} \exp(\boldsymbol{l}[k])} \in (0, 1) soft(arg)max(l)[c]=∑k=1Kexp(l[k])exp(l[c])∈(0,1)
必须注意这不是一个闭区间因为指数函数自然会产生正值。
给定预测值的集合 Y ^ \hat{Y} Y^ ,损失函数值是:
L ( Y ^ , c ) = 1 m ∑ i = 1 m ℓ ( y ^ ( i ) , c i ) , ℓ ( y ^ , c ) = − log ( y ^ [ c ] ) \mathcal{L}(\boldsymbol{\hat{Y}}, \boldsymbol{c}) = \frac{1}{m} \sum_{i=1}^m \ell(\boldsymbol{\hat{y}^{(i)}}, c_i), \quad \ell(\boldsymbol{\hat{y}}, c) = -\log(\boldsymbol{\hat{y}}[c]) L(Y^,c)=m1i=1∑mℓ(y^(i),ci),ℓ(y^,c)=−log(y^[c])
L \mathcal{L} L表示的是对所有输入样本,每个样本求cost值的和的平均值; ℓ \ell ℓ表示一个样本的cost; y ^ \hat{y} y^是一个表示神经网络输出的向量(在这个例子中包含了三个元素); y ^ [ c ] \hat{y}[c] y^[c]表示的是这个项链里的第c个元素;这里 c 表示整数的标记而非 one-hot 编码的表示方法。
那么我们来做两个例子,其一的数据被正确分类的数据,另一个不是。
比方
x , c = 1 ⇒ y = ( 1 0 0 ) \boldsymbol{x}, \quad c = 1 \Rightarrow \boldsymbol{y} = {\footnotesize\begin{pmatrix} 1 \\ 0 \\ 0 \end{pmatrix}} x,c=1⇒y=(100)
每一笔数据的损失是多少呢?
如果是接近完美的预测 ( ∼ \sim ∼ 的意思是 大约):
y ^ ( x ) = ( ∼ 1 ∼ 0 ∼ 0 ) ⇒ ℓ ( ( ∼ 1 ∼ 0 ∼ 0 ) , 1 ) → 0 + \hat{\boldsymbol{y}}(\boldsymbol{x}) = {\footnotesize\begin{pmatrix} \sim 1 \\ \sim 0 \\ \sim 0 \end{pmatrix}} \Rightarrow \ell \left( {\footnotesize\begin{pmatrix} \sim 1 \\ \sim 0 \\ \sim 0 \end{pmatrix}} , 1\right) \rightarrow 0^{+} y^(x)=(∼1∼0∼0)⇒ℓ((∼1∼0∼0),1)→0+
因为此时 y ^ [ c ] = y ^ [ 1 ] = 1 \hat{y}[c] = \hat{y}[1] = 1 y^[c]=y^[1]=1,而-log(1) = 0.
如果是几乎完全错误:
y ^ ( x ) = ( ∼ 0 ∼ 1 ∼ 0 ) ⇒ ℓ ( ( ∼ 0 ∼ 1 ∼ 0 ) , 1 ) → + ∞ \hat{\boldsymbol{y}}(\boldsymbol{x}) = {\footnotesize\begin{pmatrix} \sim 0 \\ \sim 1 \\ \sim 0 \end{pmatrix}} \Rightarrow \ell \left( {\footnotesize\begin{pmatrix} \sim 0 \\ \sim 1 \\ \sim 0 \end{pmatrix}} , 1\right) \rightarrow +\infty y^(x)=(∼0∼1∼0)⇒ℓ((∼0∼1∼0),1)→+∞
因为此时 y ^ [ c ] = y ^ [ 1 ] = 0 \hat{y}[c] = \hat{y}[1] = 0 y^[c]=y^[1]=0,而-log(0) = + ∞ \infty ∞.
注意在上面的例子中, ∼ 0 → 0 + ∼ 0 → 0 + a n d ∼ 1 → 1 − \sim 0 \rightarrow 0^{+}∼0→0 +and \sim 1 \rightarrow 1^{-} ∼0→0+∼0→0+and∼1→1− 。为何如此,请稍为思考一下。
补充:有关softmax的内容可以看这个资料
注意:当你使用CrossEntropyLoss,就同时具备了LogSoftMax和 negative loglikelihood NLLLoss,所以不要用了
做训练时,我们集结所有可以训练的参数--权重矩阵和偏置--于一个集合 Θ = { W h , b h , W y , b y } \mathbf{\Theta} = \lbrace\boldsymbol{W_h, b_h, W_y, b_y} \rbrace Θ={Wh,bh,Wy,by}}。所以我们可以把目标函数,也就是损失,写成:
J ( Θ ) = L ( Y ^ ( Θ ) , c ) ∈ R + J \left( \Theta \right) = \mathcal{L} \left( \boldsymbol{\hat{Y}} \left( \Theta \right), \boldsymbol c \right) \in \mathbb{R}^{+} J(Θ)=L(Y^(Θ),c)∈R+
这使得损失取决于网络的输出 Y ^ ( Θ ) \boldsymbol {\hat{Y}} \left( \Theta \right) Y^(Θ),所以我们可以将其转换为一个优化问题。
在图 7当中你可以看到它如何进行, J ( v ) J(\mathcal{v}) J(v) 是我们想最小化的函数。
我们挑选一个随机初始化的点 ϑ 0 \vartheta_0 ϑ0 – 它对应的损失是 J ( ϑ 0 ) J(\vartheta_0) J(ϑ0)。我们可以计算在该点的导数 J ’ ( ϑ 0 ) = d J ( ϑ ) d ϑ ( ϑ 0 ) J’(\vartheta_0) = \frac{\text{d} J(\vartheta)}{\text{d} \vartheta} (\vartheta_0) J’(ϑ0)=dϑdJ(ϑ)(ϑ0)。这个例子里,导数的斜率是正的。我们要向下降最陡的方向走一步。在此是 - d J ( ϑ ) d ϑ ( ϑ 0 ) \frac{\text{d} J(\vartheta)}{\text{d} \vartheta}(\vartheta_0) dϑdJ(ϑ)(ϑ0)
这个迭代重复的过程被称作梯度下降。梯度的方法是训练神经网络的主要工具。
为了计算必要的梯度,我们要使用反向传播。
∂ J ( Θ ) ∂ W y = ∂ J ( Θ ) ∂ y ^ ∂ y ^ ∂ W y ∂ J ( Θ ) ∂ W h = ∂ J ( Θ ) ∂ y ^ ∂ y ^ ∂ h ∂ h ∂ W h \frac{\partial \, J(\mathcal{\Theta})}{\partial \, \boldsymbol{W_y}} = \frac{\partial \, J(\mathcal{\Theta})}{\partial \, \boldsymbol{\hat{y}}} \; \frac{\partial \, \boldsymbol{\hat{y}}}{\partial \, \boldsymbol{W_y}} \quad \quad \quad \frac{\partial \, J(\mathcal{\Theta})}{\partial \, \boldsymbol{W_h}} = \frac{\partial \, J(\mathcal{\Theta})}{\partial \, \boldsymbol{\hat{y}}} \; \frac{\partial \, \boldsymbol{\hat{y}}}{\partial \, \boldsymbol h} \;\frac{\partial \, \boldsymbol h}{\partial \, \boldsymbol{W_h}} ∂Wy∂J(Θ)=∂y^∂J(Θ)∂Wy∂y^∂Wh∂J(Θ)=∂y^∂J(Θ)∂h∂y^∂Wh∂h