微信昵称:WarmIce
这一节首先介绍了过拟合、欠拟合的概念,说明了网络的复杂度设置与网络训练样本数量都很重要,在训练的时候需要进行合理的权衡。有的时候任务简单或者样本数量稀少,使用过于复杂的网络可能造成过拟合;而对于一些较为复杂的任务,使用太简单的网络又会造成欠拟合。需要视情况而定。
对于过拟合,有一些有效的处理方法。
第一个方法就是L2范数正则化。这个很容易理解哈,在学习数值分析的时候,相信大家都应听说过龙格现象,在区间内能够拟合得挺好,出了区间就不行了,而且算出来的参数贼大。神经网络的训练是一样的,只是换了个形式,所以说就直接在loss中加上一个权重项,相当于逼迫权重也不能太大。这里官方给出的公式解释得挺好的。
引用一下:
带有 L 2 L_2 L2范数惩罚项的新损失函数为
ℓ ( w 1 , w 2 , b ) + λ 2 n ∣ w ∣ 2 , \ell(w_1, w_2, b) + \frac{\lambda}{2n} |\boldsymbol{w}|^2, ℓ(w1,w2,b)+2nλ∣w∣2,
其中超参数 λ > 0 \lambda > 0 λ>0。当权重参数均为0时,惩罚项最小。当 λ \lambda λ较大时,惩罚项在损失函数中的比重较大,这通常会使学到的权重参数的元素较接近0。当 λ \lambda λ设为0时,惩罚项完全不起作用。上式中 L 2 L_2 L2范数平方 ∣ w ∣ 2 |\boldsymbol{w}|^2 ∣w∣2展开后得到 w 1 2 + w 2 2 w_1^2 + w_2^2 w12+w22。
有了 L 2 L_2 L2范数惩罚项后,在小批量随机梯度下降中,我们将线性回归一节中权重 w 1 w_1 w1和 w 2 w_2 w2的迭代方式更改为
w 1 ← ( 1 − η λ ∣ B ∣ ) w 1 − η ∣ B ∣ ∑ i ∈ B x 1 ( i ) ( x 1 ( i ) w 1 + x 2 ( i ) w 2 + b − y ( i ) ) , w 2 ← ( 1 − η λ ∣ B ∣ ) w 2 − η ∣ B ∣ ∑ i ∈ B x 2 ( i ) ( x 1 ( i ) w 1 + x 2 ( i ) w 2 + b − y ( i ) ) . \begin{aligned} w_1 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_1^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\\ w_2 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_2^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right). \end{aligned} w1w2←(1−∣B∣ηλ)w1−∣B∣ηi∈B∑x1(i)(x1(i)w1+x2(i)w2+b−y(i)),←(1−∣B∣ηλ)w2−∣B∣ηi∈B∑x2(i)(x1(i)w1+x2(i)w2+b−y(i)).
可见, L 2 L_2 L2范数正则化令权重 w 1 w_1 w1和 w 2 w_2 w2先自乘小于1的数,再减去不含惩罚项的梯度。因此, L 2 L_2 L2范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。
引用完毕。可以看到,使用这个正则化之后,参数的更新就会在原来的基础上再减去一个值,使得不会那么轻易过拟合了。
第二个方法就是DropOut。也就是所谓的丢弃法,这里同样引用官方的文档(实在是写得挺好的):
当对隐藏层使用丢弃法时,该层的隐藏单元将有一定概率被丢弃掉。设丢弃概率为 p p p,那么有 p p p的概率 h i h_i hi会被清零,有 1 − p 1-p 1−p的概率 h i h_i hi会除以 1 − p 1-p 1−p做拉伸。丢弃概率是丢弃法的超参数。具体来说,设随机变量 ξ i \xi_i ξi为0和1的概率分别为 p p p和 1 − p 1-p 1−p。使用丢弃法时我们计算新的隐藏单元 h i ′ h_i' hi′
h i ′ = ξ i 1 − p h i h_i' = \frac{\xi_i}{1-p} h_i hi′=1−pξihi
由于 E ( ξ i ) = 1 − p E(\xi_i) = 1-p E(ξi)=1−p,因此
E ( h i ′ ) = E ( ξ i ) 1 − p h i = h i E(h_i') = \frac{E(\xi_i)}{1-p}h_i = h_i E(hi′)=1−pE(ξi)hi=hi
即丢弃法不改变其输入的期望值。
引用完毕。
刷新我的认知的一点就在于,我一直没想过期望值这一点,也就是说对于没有被丢弃的值要进行一个拉伸的操作。
还有一点,非常重要,你是不是以前以为这个丢弃率丢掉的就一定是对应丢弃率个数的神经元呢?又naive了,实际上,特么就是近似丢弃!你看这个dropout的实现:
def dropout(X, drop_prob):
X = X.float()
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃
if keep_prob == 0:
return torch.zeros_like(X)
mask = (torch.rand(X.shape) < keep_prob).float()
return mask * X / keep_prob
看到那个torch.rand()了吗?我想你应该明白了。
还有一个问题就是什么时候丢弃?不是在非线性激活之前,是在非线性激活函数处理之后紧接着使用dropout的。
不过我这里还有一个问题,就是说pytorch自带的dropout在实现的时候也进行了拉伸吗?我们来深入源码看一下。好的,源码里面也追踪不到,最终可能都编译成可执行文件直接调用去了,md。那我们再来看下官方文档。
During training, randomly zeroes some of the elements of the input tensor with probability p using samples from a Bernoulli distribution. Each channel will be zeroed out independently on every forward call.
This has proven to be an effective technique for regularization and preventing the co-adaptation of neurons as described in the paper Improving neural networks by preventing co-adaptation of feature detectors .
Furthermore, the outputs are scaled by a factor of \frac{1}{1-p} during training. This means that during evaluation the module simply computes an identity function.
应该写得听明白,pytorch里面的dropout也进行了拉伸,这样子在测试的时候直接关掉dropout前向传递就行。
这一节的第二个知识点就是梯度消失和梯度爆炸。
网络层数一变多,模型要么消失要么爆炸,这个大家肯定都遇到过。怎么办呢?随机初始化模型参数是个办法,另外就是BN啊,resnet啊等技术。
一个难以避免的问题就是偏移,比如协变量偏移,P(x)变了,但是P(y|x)没变;比如标签偏移,P(y)与Q(y)不同,但是P(x|y)又是一样的;再比如概念偏移,P(y|x)本身就跟随其他因素改变。
这一节还给了个Kaggle竞赛实战的例子,里面有一点非常值得学习:就是说找最优超参数的时候我们需要划分好训练集和验证集,但是在找到最优参数之后,要使用全部的数据重新训练再进行提交。这个非常重要,学到了。
又是相当硬核的一节课。讲者还是个女的,声音贼尖,听得我耳朵炸。
简单来说,RNN不好训练,如果是过拟合那还行,本身就用了梯度裁剪给你剪一剪,但是欠拟合就很讨厌了,很遗憾,RNN有时候吧过拟合得厉害,有时候又欠拟合得厉害。所以不好训练得很。
这就有了GRU(Gate Recurret Unit,门控循环单元)和LSTM(Long Short-Term Memory,长短时记忆)。
GRU还是只用了一个隐藏状态量,公式为:
R t = σ ( X t W x r + H t − 1 W h r + b r ) Z t = σ ( X t W x z + H t − 1 W h z + b z ) H ~ t = t a n h ( X t W x h + ( R t ⊙ H t − 1 ) W h h + b h ) H t = Z t ⊙ H t − 1 + ( 1 − Z t ) ⊙ H ~ t R_{t} = σ(X_tW_{xr} + H_{t−1}W_{hr} + b_r)\\ Z_{t} = σ(X_tW_{xz} + H_{t−1}W_{hz} + b_z)\\ \widetilde{H}_t = tanh(X_tW_{xh} + (R_t ⊙H_{t−1})W_{hh} + b_h)\\ H_t = Z_t⊙H_{t−1} + (1−Z_t)⊙\widetilde{H}_t Rt=σ(XtWxr+Ht−1Whr+br)Zt=σ(XtWxz+Ht−1Whz+bz)H t=tanh(XtWxh+(Rt⊙Ht−1)Whh+bh)Ht=Zt⊙Ht−1+(1−Zt)⊙H t
实不相瞒,我想知道当时造出来这玩意儿的人怎么想出来这东西的???(黑人问号)
不过你要说这个算复杂的话,那还言之过早,看看LSTM吧,用了俩隐藏状态量:
I t = σ ( X t W x i + H t − 1 W h i + b i ) F t = σ ( X t W x f + H t − 1 W h f + b f ) O t = σ ( X t W x o + H t − 1 W h o + b o ) C ~ t = t a n h ( X t W x c + H t − 1 W h c + b c ) C t = F t ⊙ C t − 1 + I t ⊙ C ~ t H t = O t ⊙ t a n h ( C t ) I_t = σ(X_tW_{xi} + H_{t−1}W_{hi} + b_i) \\ F_t = σ(X_tW_{xf} + H_{t−1}W_{hf} + b_f)\\ O_t = σ(X_tW_{xo} + H_{t−1}W_{ho} + b_o)\\ \widetilde{C}_t = tanh(X_tW_{xc} + H_{t−1}W_{hc} + b_c)\\ C_t = F_t ⊙C_{t−1} + I_t ⊙\widetilde{C}_t\\ H_t = O_t⊙tanh(C_t) It=σ(XtWxi+Ht−1Whi+bi)Ft=σ(XtWxf+Ht−1Whf+bf)Ot=σ(XtWxo+Ht−1Who+bo)C t=tanh(XtWxc+Ht−1Whc+bc)Ct=Ft⊙Ct−1+It⊙C tHt=Ot⊙tanh(Ct)
这帮人脑子怎么想的,这东西为什么想出来应该这样运算的啊?
按照给的文档里面的说法:
在GRU里面,
• 重置⻔有助于捕捉时间序列⾥短期的依赖关系;
• 更新⻔有助于捕捉时间序列⾥⻓期的依赖关系。
而LSTM里面,
遗忘门:控制上一时间步的记忆细胞
输入门:控制当前时间步的输入
输出门:控制从记忆细胞到隐藏状态
记忆细胞:⼀种特殊的隐藏状态的信息的流动
对比来看,GRU是一种LSTM的近似(GRU提出在2014年,而LSTM则是1997年),在硬件成本与时间成本的要求下,GRU会是LSTM的良好替代。
其实,这俩都只是前奏,都是基础部件,不足为提,但是奈何我从来没仔细研究过,只是听过个名儿,因此得好生看看。这要是没弄明白,下边儿的,您呐,想都别想!
有一说一,讲得一般,这个代码还是挺清楚的。PyTorch自带nn.GRU和nn.LSTM的函数可供使用。
下面就是将网络加深。
第一个就是深度循环神经网络,这个网络会有多个隐藏层,除了第一个隐藏层之外,其他的隐藏层的输入是上一个隐藏层输出的隐藏状态。对于这种多个隐藏层的深度循环神经网络,nn.GRU与nn.LSTM都有一个参数控制,就是num_layers参数,该参数可以控制隐藏层的个数(不设置的话默认为1层隐藏层)。还要注意的一点是,nn提供的这些循环神经网络的实现都没有最终的输出层,因此需要额外添加一个输出层,构成网络整体。
第二个就是双向循环神经网络,可以将这个网络看成有两个隐藏层,第一个隐藏层按照句子的顺序依次输入运算得到隐藏层状态,第二个隐藏层按照句子的倒序依次输入因素安得到隐藏层状态,接着,融合同一时间步的两个隐藏层状态后经过线性层输出。对于这个,可以使用bidirectional=True参数控制是否需要进行双向循环。
双向循环网络这边需要重点多谈一些,因为习题中做错了一道题目,是我太想当然了,但是也是讲者的确没有讲透彻的地方。
就是说,对于双向循环网络,我们究竟怎样获得输出呢?
前向和反向运算后,每个时间步长都会计算得到两个隐藏状态(一个是前向传播得到的,另一个是反向传播得到的)。我最开始的想法是,会对这两个隐藏状态分别走全连接层,得到输出后再相加。但是大家想一下,这样的话,就会导致前后运算得到的语义信息没有交互,联系上下文的目的就没有很好地执行。那既然这样不好,更好的方法就是先把两个隐藏状态处理一下,再进行运算得到最终输出。神经网络说起来也没有那么复杂,能够拿得出手的处理方式不外乎相加、相乘以及concat,可以想到,由于两个隐藏状态分别是上下文的信息,使用相加和相乘反而破坏了这里面取出来的信息,因此最好的方法就是使用concat,然后,就能够使用一个全连接层综合考虑上下文的信息得到优良的输出结果。
以上。