闰咄阅 发表于 2026-3-14 21:00:12

深度学习笔记-《动手学习深度学习》

<p>本笔记是作为复试应对面试回答做的一些笔记,后期还要去读一些论文加深理解,参考书为《动手学习深度学习》</p>
<blockquote>
<p>作为一个研究者,我需要去读很多的文章,去总结很多不同的优秀的研究者对这个世界的认识,然后形成自己独特的观点是很重要的</p>
</blockquote>
<h1 id="读论文">读论文</h1>
<ul>
<li>读论文的过程:Abstract、Introduction(讲个故事,我们研究的方向,要做什么,以及后面的几节会讲什么);之后给出实验数据,这里一般是做图表对比,刷刷榜;最后可能有 conclusion(少数有 discussion,如 AlexNet)</li>
</ul>
<h1 id="深度学习基础">深度学习基础</h1>
<h2 id="损失函数">损失函数</h2>
<ul>
<li>\(\hat{y}^{(i)} = \mathbf{w}^\top \mathbf{x}^{(i)} + b\), \(l^{(i)} = \frac{1}{2} (\hat{y}^{(i)} - y^{(i)})^2\), 根据链式法则,可以将其拆分为两步相乘:</li>
</ul>
<p></p>\[\frac{\partial l^{(i)}}{\partial \mathbf{w}} = \frac{\partial l^{(i)}}{\partial \hat{y}^{(i)}} \cdot \frac{\partial \hat{y}^{(i)}}{\partial \mathbf{w}}
\]<p></p><p>第一步:</p>
<p></p>\[\frac{\partial l^{(i)}}{\partial \hat{y}^{(i)}} = 2 \cdot \frac{1}{2} (\hat{y}^{(i)} - y^{(i)}) = \hat{y}^{(i)} - y^{(i)} = \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}
\]<p></p><p>第二步:</p>
<p></p>\[\frac{\partial \hat{y}^{(i)}}{\partial \mathbf{w}} = \frac{\partial (\mathbf{w}^\top \mathbf{x}^{(i)} + b)}{\partial \mathbf{w}} = \mathbf{x}^{(i)}
\]<p></p><p>将上面两步的结果相乘,就得到了完整的梯度表达式:</p>
<p></p>\[\partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{x}^{(i)} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right)
\]<p></p><p>交叉熵损失也是基于 SoftMax:</p>
<p></p>\[\text{softmax}(\mathbf{X})_{ij} = \frac{\exp(\mathbf{X}_{ij})}{\sum_k \exp(\mathbf{X}_{ik})}
\]<p></p><p>进行极大似然估计得出来的公式,交叉熵损失:</p>
<p></p>\[l(\mathbf{y}, \hat{\mathbf{y}}) = - \sum_{j=1}^q y_j \log \hat{y}_j
\]<p></p><ul>
<li>为什么在做线性回归等任务时,我们通常会理所当然地选择"均方误差"作为损失函数<br>
\(y = \mathbf{w}^\top \mathbf{x} + b + \epsilon\), 假设噪音 \(\epsilon\) 符合正态分布,对 \(n\) 个样本的概率连乘,求极大似然估计,把无关紧要的常数项都抛开后,剩下的核心部分:</li>
</ul>
<p></p>\[\sum_{i=1}^n \left( y^{(i)} - \mathbf{w}^\top \mathbf{x}^{(i)} - b \right)^2
\]<p></p><p>这正是我们非常熟悉的均方误差(MSE)公式的内核(即预测值与真实值差值的平方和)。也就是用均方误差去训练模型,本质上就是在做极大似然估计,是在寻找最能解释当前数据的最优参数。</p>
<ul>
<li>做分类任务的时候,采用交叉熵是一个不错的选择。
<ol>
<li>假设我们做一个二分类,模型输出前的逻辑值(Logits)为 \(z\),经过 Sigmoid 激活函数后得到预测概率 \(\hat{y} = \sigma(z)\)。真实标签为 \(y\)。</li>
<li>如果我们使用 MSE:\(L_{\text{MSE}} = \frac{1}{2}(\hat{y} - y)^2\),在分类问题中,我们预测的是各个标签的概率,于是在反向传播阶段,我们对 \(z\) 求导(也就是反向传播时传给上一层的梯度):</li>
</ol>
</li>
</ul>
<p></p>\[\frac{\partial L_{\text{MSE}}}{\partial z} = (\hat{y} - y) \cdot \sigma'(z)
\]<p></p><p>\(\sigma'(z)\) 是 Sigmoid 的导数。<br>
3. 如果模型预测 \(\hat{y}_i \approx 0\),此时 \(z\) 是一个很大的负数,但实际 \(y_i=1\),Sigmoid 在极值处的导数 \(\sigma'(z)\) 会极其接近于 0,那么梯度就接近 0 无法更新权重</p>
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ol start="4">
<li>如果使用交叉熵损失函数:</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p></p>\
\]<p></p><p>同样对 \(z\) 求导:</p>
<p></p>\[\frac{\partial L_{\text{CE}}}{\partial z} = \hat{y} - y
\]<p></p><p>梯度的物理意义变成了:预测值和真实值的单纯差值。</p>
<h2 id="训练与反向传播">训练与反向传播</h2>
<ul>
<li>
<p>分类问题的训练过程:定义一个 net,然后计算出 \(\hat{\mathbf{y}}\),通过损失函数计算出损失,然后反向传播(用于计算前面损失函数的偏导数),之后进行随机小批量梯度下降即可</p>
<ul>
<li>反向传播:\(L-1\) 神经元的输出值会受到应用于它们从 \(L-2\) 接收的输入的权重的影响。因此,我们可以对 \(L-1\) 中的激活函数进行微分,以找到应用于 \(L-2\) 贡献的权重的偏导数。这些偏导数将向我们展示 \(L-2\) 权重的任何变化会如何影响 \(L-1\) 的输出,进而影响 \(L_c\) 的输出值,并因此影响损失函数。递归重复这个过程,直到我们到达输入层。完成之后,我们会得到损失函数的<strong>梯度</strong>:网络中每个权重和偏差参数的偏导数向量。</li>
<li>向下移动(<em>下降</em>)损失函数的梯度将减少损失。由于我们在反向传播期间计算的梯度包含每个模型参数的偏导数,因此我们知道向哪个方向"步进"每个参数以减少损失。</li>
</ul>
</li>
<li>
<p>momentum 是防止个别极端数据会导致优化不平滑,你可以保持保持一个冲量,从过去的那个方向,沿着一个比较平滑的方向往前走</p>
<ul>
<li><strong>须进一步观察</strong></li>
</ul>
</li>
</ul>
<h2 id="激活函数">激活函数</h2>
<ul>
<li>加入激活函数的原因是因为对于 hidden layer 来说,不加入激活函数实际上与线性层一样,考虑:\(\mathbf{H} = \mathbf{X}\mathbf{W}_1 + \mathbf{b}_1\),\(\mathbf{O} = \mathbf{H}\mathbf{W}_2 + \mathbf{b}_2\),我们把隐藏层 \(\mathbf{H}\) 的等式直接代入到输出层矩阵运算中:</li>
</ul>
<p></p>\[\mathbf{O} = (\mathbf{X}\mathbf{W}_1 + \mathbf{b}_1)\mathbf{W}_2 + \mathbf{b}_2
\]<p></p><p>利用矩阵乘法的分配律将其展开:</p>
<p></p>\[\mathbf{O} = \mathbf{X}(\mathbf{W}_1\mathbf{W}_2) + (\mathbf{b}_1\mathbf{W}_2 + \mathbf{b}_2)
\]<p></p><p>最终的等式变成了:</p>
<p></p>\[\mathbf{O} = \mathbf{X}\mathbf{W}' + \mathbf{b}'
\]<p></p><ul>
<li>ReLu 在 AlexNet 首次提出,当时大家觉得求导表现得特别好,其实后来发现都差不多,但是 ReLu 比较简单,就都用了</li>
</ul>
<h2 id="过拟合与欠拟合">过拟合与欠拟合</h2>
<ul>
<li>欠拟合:模型在 train 和 val 的 loss 都很差,误差较小,有理由相信是我们的网络过于简单;</li>
<li>过拟合:模型在 train 的 loss 较小,但是 val 上面表现很差</li>
</ul>
<h2 id="正则化">正则化</h2>
<ul>
<li>
<p>Dropout:也就是对内部层注入噪音,随机的把一些 hidden layer 的 output 变成用固定概率设成零作为输出,能够做到一个<strong>正则</strong>的效果</p>
<ul>
<li>正则是什么?旨在减少学习算法的泛化误差(测试误差),而不是训练误差的任何修改。正则化就是所有用来"防止过拟合"的手段的总称</li>
</ul>
</li>
<li>
<p>权重衰减(weight decay)是最广泛使用的正则化的技术之一,它通常也被称为L2正则化。在我们没有加正则化之前,优化器的唯一目标是让"做题错误率"降到最低:</p>
</li>
</ul>
<p></p>\[L_{\text{total}} = L_{\text{data}}(\mathbf{w}, b)
\]<p></p><p>由于模型非常贪婪,为了让 \(L_{\text{data}}\) 趋近于 0,它会不择手段地调整 \(\mathbf{w}\),甚至把某些权重放大到成千上万,只为了迎合某个微小的噪声点(这就是过拟合)。现在,我们强行给目标函数加了一个"尾巴"(以 \(L_2\) 为例):</p>
<p></p>\[L_{\text{total}} = L_{\text{data}}(\mathbf{w}, b) + \frac{\lambda}{2} ||\mathbf{w}||^2
\]<p></p><p>优化器的目标依然是让整个 \(L_{\text{total}}\) 最小。这时候,博弈就产生了:<br>
* 如果模型依然想把 \(\mathbf{w}\) 设得巨大无比来完美拟合数据(让 \(L_{\text{data}}\) 降为 0),那么后面的惩罚项 \(\frac{\lambda}{2} ||\mathbf{w}||^2\) 就会<strong>爆炸式增长</strong>,导致总分 \(L_{\text{total}}\) 极高。<br>
* 为了拿到最低的总分,优化器被迫做出<strong>妥协</strong>:它宁愿让 \(L_{\text{data}}\) 稍微大一点(放弃完美拟合噪声),也要把权重 \(\mathbf{w}\) 的数值压下来,以此来换取惩罚项的大幅降低。</p>
<p>从微积分求导的过程来看,惩罚的作用更加直接:</p>
<p></p>\[\frac{\partial L_{\text{total}}}{\partial \mathbf{w}} = \frac{\partial L_{\text{data}}}{\partial \mathbf{w}} + \lambda \mathbf{w}
\]<p></p><p></p>\[\mathbf{w} \leftarrow (1 - \eta\lambda)\mathbf{w} - \eta \frac{\partial L_{\text{data}}}{\partial \mathbf{w}}
\]<p></p><p>这就是对权重的衰减</p>
<ul>
<li>\(L_1\) vs \(L_2\) 的行为偏好:\(L_1\) 产生稀疏性,\(L_2\) 产生平滑性
<ul>
<li>\(L_2\) 惩罚(平方)的导数是 \(2\lambda w\):这意味着,惩罚的力度是和权重大小成正比的。当 \(w\) 是 100 的时候,惩罚力度是 \(200\lambda\),逼着它赶紧变小</li>
<li>\(L_1\) 惩罚(绝对值)的导数是 \(\lambda \cdot \text{sign}(w)\):无论你的 \(w\) 是巨大的 100,还是微小的 0.001,只要你不等于 0,惩罚项给你的推力永远是一个常数 \(\lambda\)</li>
</ul>
</li>
</ul>
<h2 id="梯度问题与参数初始化">梯度问题与参数初始化</h2>
<ul>
<li>
<p>梯度消失与梯度爆炸</p>
<ul>
<li>梯度消失:如 <code>sigmoid</code> 函数,值过大或过小都会导致梯度消失,可用 ReLu 解决</li>
<li>梯度爆炸:由于梯度是前 \(l - 1\) 个矩阵与梯度向量的乘积,连乘可能导致爆炸,可用参数随机初始化尝试解决</li>
</ul>
</li>
<li>
<p>初始化有:随机(正态分布)初始化,Xavier 初始化(最常用于搭配 <code>Tanh</code> 或 <code>Sigmoid</code> 这类具有 "S 型曲线" 激活函数的神经网络层中)</p>
<ul>
<li>不要把 Xavier 初始化和 <code>ReLU</code> 激活函数搭配使用</li>
</ul>
</li>
</ul>
<h2 id="批量规范化">批量规范化</h2>
<ul>
<li>Batch Normalization:
<ul>
<li>在深度神经网络中,由于层数很多,底层参数的微小变化,经过层层放大,会导致高层输入数据的分布发生剧烈震荡。高层网络被迫不断去"追赶"这种分布的变化,导致模型训练极慢。</li>
<li>批量规范化就是在一些网络层中对前面的网络层进行标准化,标准化的参数也是可以学习的</li>
<li>提升了训练速度,因为网络层的数据分布被稳定住了,对学习率不敏感了(大学习率也能快速收敛),同样的也<strong>不对初始化敏感了(如果开始初始化导致方差爆炸,BN 也能拉回来)</strong></li>
</ul>
</li>
</ul>
<h1 id="计算机视觉">计算机视觉</h1>
<h2 id="卷积层">卷积层</h2>
<ul>
<li>
<p>卷积是 4 维是因为:如果是 Linear 的话,其实是 5 维,分别是 \([\mathbf{V}]_{i,j,a,b}\):意思是,针对输出图的 \((i,j)\) 这个位置,当偏移量是 \((a,b)\) 时,权重是多少,有 \(c\) 个 <code>RGB</code></p>
<ul>
<li>由于平移不变性,我们意识到原始位置是没用的,这意味着权重张量 \([\mathbf{V}]_{i,j,a,b}\) 里的 \(i\) 和 \(j\) 完全成了摆设,我们可以直接把它俩踢掉,简化成 \([\mathbf{V}]_{a,b,c}\)</li>
<li>局部性:你只需要看它周围一小圈的像素就行了,完全没必要去参考图片角落里距离它几千像素远的背景。它们不能超过一个很小的距离 \(\Delta\)(比如 \(\Delta=1\),也就是只看周围 \(3 \times 3\) 的九宫格)。</li>
<li>剩下的一个维度就是通道了,\([\mathbf{V}]_{a,b,c,d}\)</li>
</ul>
</li>
<li>
<p>卷积核:为了实现"多输入 + 多输出",卷积核(权重张量)必须是一个四维(四阶)的数据结构。这非常关键: 假设输入有 \(c_i\) 个通道,输出想要 \(c_o\) 个通道,卷积核的高和宽分别是 \(k_h\) 和 \(k_w\)。 那么这个卷积核 <code>K</code> 的形状就是:\(c_o \times c_i \times k_h \times k_w\)。</p>
</li>
</ul>
<h2 id="特征映射与感受野">特征映射与感受野</h2>
<ul>
<li>
<p>特征映射其实就是上一层做卷积的时候对应的输出,可以被视为一个输入映射到下一层的空间维度的转换器</p>
</li>
<li>
<p>感受野(receptive field):在卷积神经网络中,对于某一层的任意元素x,其感受野是指在前向传播期间可能影响x计算的所有元素(来自所有先前层)</p>
<ul>
<li>因此,当一个特征图中的任意元素需要检测更广区域的输入特征时,我们可以构建一个更深的网络。</li>
</ul>
</li>
</ul>
<h2 id="汇聚层">汇聚层</h2>
<ul>
<li>汇聚(pooling)层具有双重目的:降低卷积层对位置的敏感性,同时降低对空间采样表示的敏感性。</li>
</ul>
<h2 id="残差网络resnet">残差网络(ResNet)</h2>
<ul>
<li>ResNet 为什么有用?
<ul>
<li>我们想要加深网络来提升性能,但事实上不一定会表现更好,如图,\(f'\) 我们设为最优解,那么 F6 的距离是要比 F3 更远的,而我们确实在向 F6 靠拢,那不如我们说是向右边一样,每次大模型都包含前面的小模型,这样最起码的不会变差![]</li>
<li>ResNet 核心思想就是上述:每个附加层都应该更容易地包含原始函数作为其元素之一。于是,残差块(residual blocks)便诞生了。</li>
</ul>
</li>
</ul>
<h3 id="转置卷积与全卷积网络语义分割">转置卷积与全卷积网络(语义分割)</h3>
<ul>
<li>转置卷积是好理解的,类似于卷积的逆变换,具体一张图其实就可以看懂<br>
![]</li>
<li>对于语义分割,我们考虑先进行卷积神经网络,然后把输出为 <code>1x1</code> 特征图,这个小矩阵的感受野是整张图片的,随后我们再使用转置卷积对其进行放大分割,得到物体边界。</li>
<li>对于转置卷积的初始化,我们使用双线性插值进行对特征图的放大,也就是上采样</li>
<li>单线性插值:这个很简单,我们知道两点 \((x_0,y_0), (x_1, y_1)\),那么给定 \(x\),我们有:</li>
</ul>
<p></p>\[y = \frac{x_1 - x}{x_1 - x_0} y_0 + \frac{x - x_0}{x_1 - x_0} y_1
\]<p></p><ul>
<li>双线性插值法:四个已知顶点(在图像中就是四个相邻的像素点):左上:\(Q_{11} = (x_1, y_1)\),其值为 \(f(Q_{11})\);右上:\(Q_{21} = (x_2, y_1)\),其值为 \(f(Q_{21})\);左下:\(Q_{12} = (x_1, y_2)\),其值为 \(f(Q_{12})\);右下:\(Q_{22} = (x_2, y_2)\),其值为 \(f(Q_{22})\);设目标点位 \(P(x, y)\),对应 \(f(x, y)\),我们对其在 \(x\) 轴做一次线性插值,对 \(y\) 轴再做一次即可(先 \(y\) 后 \(x\) 也可以),矩阵面积:\(S = (x_2 - x_1)(y_2 - y_1)\),那么我们有:</li>
</ul>
<p></p>\[f(x, y) = \frac{(x_2 - x)(y_2 - y)}{S} f(Q_{11}) + \frac{(x - x_1)(y_2 - y)}{S} f(Q_{21}) + \frac{(x_2 - x)(y - y_1)}{S} f(Q_{12}) + \frac{(x - x_1)(y - y_1)}{S} f(Q_{22})
\]<p></p><h1 id="自然语言处理">自然语言处理</h1>
<h2 id="序列模型">序列模型</h2>
<ul>
<li>自回归模型:直接用观测序列 \(x_{t-1} \dots x_{t-\tau}\) 生成预测,这样的好处是参数的数量是不变的
<ul>
<li>每次只根据前面的上文,预测下一个词:</li>
</ul>
</li>
</ul>
<p></p>\[P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}, \ldots, x_1)
\]<p></p><p>实际上就是多个条件概率连乘:</p>
<p></p>\[P(A, B, C) = P(A) \cdot P(B \mid A) \cdot P(C \mid A, B)
\]<p></p><ul>
<li>接着,我们引用马尔科夫链,这里我们只用一阶马尔可夫模型(\(\tau = 1\)),然后我们对其化简:</li>
</ul>
<p></p>\[P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1})
\]<p></p><p>如果我们要去预测下一个(即 \(x_{t+1}\)),那么我们需要把今天所有可能的情况算出来,有:</p>
<p></p>\[P(x_{t+1} \mid x_{t-1})= \sum_{x_t} P(x_{t+1} \mid x_t) P(x_t \mid x_{t-1})
\]<p></p><ul>
<li>隐变量自回归模型:我们总是保留一些对过去观测的总结 \(h_t\),并同时更新预测和总结,这样基于 \(x_t\) 和 \(h_t\) 更新的模型由于 \(h_t\) 从未被观测到,这类模型也被称为隐变量自回归模型</li>
</ul>
<h2 id="文本预处理与基本概念">文本预处理与基本概念</h2>
<ul>
<li>
<p>文本预处理的步骤:</p>
<ol>
<li>将文本作为字符串加载到内存中。</li>
<li>将字符串拆分为词元(如单词和字符)。</li>
<li>建立一个词表,将拆分的词元映射到数字索引。</li>
<li>将文本转换为数字索引序列,方便模型操作。</li>
</ol>
</li>
<li>
<p>一些基本的概念:</p>
<ul>
<li>词元(token):就是数据集中的字或字符</li>
<li>词表(vocabulary):本质是一个哈希表 <code>map<char, int></code>,对它们的唯一词元进行统计,得到的统计结果称之为语料(corpus)。然后根据每个唯一词元的出现频率,为其分配一个数字索引。很少出现的词元通常被移除,这可以降低复杂性。</li>
<li>涉及一个、两个和三个变量的概率公式分别被称为一元语法(unigram)、二元语法(bigram)和三元语法(trigram)模型。(即 <code>Deep</code>,<code>Deep Learning</code>,<code>Deep Learning is</code>)</li>
<li>由于二元和三元词元数量很少,可以用拉普拉斯平滑来计数,但是我们需要存储所有的计数,这完全忽略了单词的意思</li>
</ul>
</li>
</ul>
<h2 id="循环神经网络rnn">循环神经网络(RNN)</h2>
<ul>
<li>循环神经网络(RNN),在时间步 \(t\) 引入了一个叫作隐状的变量 \(\mathbf{H}_t\),负责捕获并保留从序列开头一直到当前时间步的所有历史信息,那么我们更新状态就不是 MLP 那样更新了,而是:</li>
</ul>
<p></p>\[\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h)
\]<p></p><p>\(\phi\) 是激活函数</p>
<ul>
<li>困惑度(Perplexity)用于评估基于循环神经网络的模型:即使模型极好,它生成巨著《战争与和平》的总概率,也必定远小于生成短篇《小王子》的概率。这显然是不公平的。我们需要计算每个词元的平均损失(整个序列的负对数似然(交叉熵损失)取平均):</li>
</ul>
<p></p>\[\text{Average Cross-Entropy}=\frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1)
\]<p></p><p>NLP 科学家为了让其特殊一点,就在这个交叉熵损失上面做了一次指数(吐槽一下哈哈哈):</p>
<p></p>\[\text{Perplexity} = \exp(\text{Average Cross-Entropy})
\]<p></p><ul>
<li>梯度裁剪(Gradient Clipping):为了克服梯度爆炸,最简单的办法就是设定一个速度上限(阈值 \(\theta\)):</li>
</ul>
<p></p>\[\mathbf{g} \leftarrow \min\left(1, \frac{\theta}{\|\mathbf{g}\|}\right) \mathbf{g}
\]<p></p><p>它只改变大小,<strong>不改变方向</strong>,确保了模型依然在朝着正确的方向优化,只是硬生生把步伐给缩小了,从而赋予了模型极大的稳定性。</p>
<ul>
<li>
<p>那么如何克服梯度消失?</p>
<ol>
<li>利用 LSTM 或者 GRU,引入门控单元</li>
<li>利用残差连接将底层的输出直接跨层加到高层的输出上:\(\mathbf{O} = \mathcal{F}(\mathbf{x}) + \mathbf{x}\)</li>
<li>舍弃循环连乘,使用 Attention,因为 Attention 的复杂度不受序列长度影响,可以直接抓取目标位置的信息</li>
</ol>
</li>
<li>
<p>自回归语言模型(包括 ChatGPT 的底层逻辑)生成文本的标准:预热 + 预测</p>
<ul>
<li>Warm-up:由于在生成文本的时候隐状态是空的,所以要更新模型的隐状态,要把序列开头喂给模型,但不理会其输出(即预测)</li>
<li>Predication:刚才预热好的隐状态,加上最后一个字,模型输出预测结果,然后模型自己生成的字作为下一个预测的 \(x_t\)</li>
</ul>
</li>
<li>
<p>时间反向传播(BPTT):</p>
<ul>
<li>在 RNN 中,情况变得极其复杂,因为当前的隐状态 \(h_t\) 不仅依赖于当前的输入,还依赖于上一个时间步的隐状态 \(h_{t-1}\)。</li>
<li>这意味着,当我们要计算权重对当前输出的影响时,根据链式法则,沿着时间轴一步一步往回推导(从时间步 \(T\) 一直反推回时间步 1)。</li>
<li>顺着链式法则把公式完全展开后,发现了一个极其恐怖的现象:隐状态的梯度中包含了一个极其致命的项:\((\mathbf{W}_{hh}^\top)^{T-t}\),这个矩阵需要被自己连续相乘 \((T-t)\) 次,使得模型极其不稳定</li>
<li>于是不再苛求把链条反推到最开始的 \(t=1\) 时刻,而是设定一个步数上限 \(\tau\)。往前推了 \(\tau\) 步之后,我们就强行停止求导。在 Pytorch 中可利用 Detach 函数</li>
</ul>
</li>
</ul>
<h2 id="门控循环单元gru">门控循环单元(GRU)</h2>
<ul>
<li>GRU(门控循环单元),相对于 RNN 新增了两个门控单元:</li>
</ul>
<p></p>\[\mathbf{R}_t = \sigma(\mathbf{X}_t \mathbf{W}_{xr} + \mathbf{H}_{t-1} \mathbf{W}_{hr} + \mathbf{b}_r)
\]<p></p><p></p>\[\mathbf{Z}_t = \sigma(\mathbf{X}_t \mathbf{W}_{xz} + \mathbf{H}_{t-1} \mathbf{W}_{hz} + \mathbf{b}_z)
\]<p></p><p>\(\sigma\) (Sigmoid)激活函数使得这两个门的输出在(0,1)之间,即什么时候放弃,什么时候用之前的记忆是可以学习的,接着我们有:</p>
<p></p>\[\tilde{\mathbf{H}}_t = \tanh(\mathbf{X}_t \mathbf{W}_{xh} + (\mathbf{R}_t \odot \mathbf{H}_{t-1}) \mathbf{W}_{hh} + \mathbf{b}_h)
\]<p></p><p>\(\tilde{\mathbf{H}}_t\) 是候选隐状态,旧记忆 \(\mathbf{H}_{t-1}\) 被重置门 \(\mathbf{R}_t\) 拦截了:\((\mathbf{R}_t \odot \mathbf{H}_{t-1})\) 为什么这里必须使用 Hadamard 积(按元素相乘),而不是矩阵乘法?<br>
* 隐状态 \(\mathbf{H}_{t-1}\) 不是一个单一的数字,而是一个高维向量(如256维度),把这 256 个维度想象成大脑里 256 个独立的神经元,分别记录着不同的信息<br>
* 重置门 \(\mathbf{R}_t\) 也是一个一模一样长度为 256 的向量,里面全是 \((0, 1)\) 之间的数字。<br>
* 当我们做元素相乘 (\(\odot\)) 时,发生的是:\(\mathbf{R}_t\) 的第 1 维只去乘 \(\mathbf{H}_{t-1}\) 的第 1 维,第 10 维只乘第 10 维,让模型拥有了选择性遗忘的能力</p>
<p>对于更新门,我们利用候选隐单元得到当前的隐单元:</p>
<p></p>\[H_t = \mathbf{Z}_t \odot{H_{t-1}} + (1 - \mathbf{Z}_t) \odot \tilde{\mathbf{H}}_t
\]<p></p><p>这些设计可以帮助我们处理循环神经网络中的梯度消失问题,并更好地捕获时间步距离很长的序列的依赖关系![]</p>
<h2 id="长短期记忆lstm">长短期记忆(LSTM)</h2>
<ul>
<li>LSTM(长短期记忆):我们来细化一下长短期记忆网络的数学表达。时间步\(t\)的门被定义如下:输入门是\(\mathbf{I}_t \in \mathbb{R}^{n \times h}\),遗忘门是\(\mathbf{F}_t \in \mathbb{R}^{n \times h}\),输出门是\(\mathbf{O}_t \in \mathbb{R}^{n \times h}\)。它们的计算方法如下:</li>
</ul>
<p></p>\[\begin{aligned}
\mathbf{I}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xi} + \mathbf{H}_{t-1} \mathbf{W}_{hi} + \mathbf{b}_i),\\
\mathbf{F}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xf} + \mathbf{H}_{t-1} \mathbf{W}_{hf} + \mathbf{b}_f),\\
\mathbf{O}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xo} + \mathbf{H}_{t-1} \mathbf{W}_{ho} + \mathbf{b}_o),
\end{aligned}
\]<p></p><p>候选记忆单元为:</p>
<p></p>\[\tilde{\mathbf{C}}_t = \tanh(\mathbf{X}_t \mathbf{W}_{xc} + \mathbf{H}_{t-1} \mathbf{W}_{hc} + \mathbf{b}_c),
\]<p></p><p>输入门\(\mathbf{I}_t\)控制采用多少来自\(\tilde{\mathbf{C}}_t\)的新数据,而遗忘门\(\mathbf{F}_t\)控制保留多少过去的记忆元\(\mathbf{C}_{t-1} \in \mathbb{R}^{n \times h}\)的内容。使用按元素乘法,得出:</p>
<p></p>\[\mathbf{C}_t = \mathbf{F}_t \odot \mathbf{C}_{t-1} + \mathbf{I}_t \odot \tilde{\mathbf{C}}_t.
\]<p></p><p>最后,我们需要定义如何计算隐状态 \(\mathbf{H}_t \in \mathbb{R}^{n \times h}\),在 LSTM 中,它仅仅是记忆元的\(\tanh\)的门控版本。这就确保了\(\mathbf{H}_t\)的值始终在区间\((-1, 1)\)内:</p>
<p></p>\[\mathbf{H}_t = \mathbf{O}_t \odot \tanh(\mathbf{C}_t).
\]<p></p><code>![]
</code>
<ul>
<li>为什么在循环神经网络中,tanh 和 sigmoid 混着用:
<ul>
<li>sigmoid 的作用是起到一个 mask 的作用,就是说我可以选择性的去记忆</li>
<li>tanh,对于 RNN 来说,如果不加 tanh 对于一个梯度更新,连乘会导致 H 的值指数级爆炸;对于 GRU 和 LSTM 这类存在记忆性的来说,tanh 的 \((-1, 1)\) 的范围允许记忆发生正负方向的相互抵消,内容的 \((-1, 1)\) 决定了吸收的信息是起正向促进作用,还是负向抑制作用。并且 tanh 在 backword 的时候也可以让梯度下降更加平滑</li>
</ul>
</li>
</ul>
<h2 id="双向循环神经网络bi-rnn">双向循环神经网络(Bi-RNN)</h2>
<ul>
<li>双向 RNN(Bi-RNN):
<ul>
<li>为了证明双向 RNN 的合理性,我们引出隐马尔可夫模型(HMM),我们有:</li>
</ul>
</li>
</ul>
<p></p>\[P(x_1, \ldots, x_T, h_1, \ldots, h_T) = \prod_{t=1}^T P(h_t \mid h_{t-1})P(x_t \mid h_t)
\]<p></p><p>如果我们利用前面和后面的信息要预测中间某个位置的信息(类似于做完形填空)即求 \(P(x_j \mid x_{-j})\),其中 \(x_{-j}={x_1 \dots x_{j-1},x_{j+1} \dots x_t}\),由于 \(h\) 的信息是隐藏的,所以要枚举出所有情况,这样复杂度达到了 \(O(T \cdot k^2)\),考虑优化,我们首先提出 \(h_1\) 相关的项:</p>
<p></p>\[= \sum_{h_2, \ldots, h_T} \underbrace{\left[ \sum_{h_1} P(h_1)P(x_1 \mid h_1)P(h_2 \mid h_1) \right]}_{\text{定义为 } \pi_2(h_2)} P(x_2 \mid h_2) \prod_{t=3}^T \dots
\]<p></p><p>接着我们提出 \(h_2\):</p>
<p></p>\[= \sum_{h_3, \ldots, h_T} \underbrace{\left[ \sum_{h_2} \pi_2(h_2)P(x_2 \mid h_2)P(h_3 \mid h_2) \right]}_{\text{定义为 } \pi_3(h_3)} P(x_3 \mid h_3) \prod_{t=4}^T \dots
\]<p></p><p>最后有:</p>
<p></p>\[= \sum_{h_T} \pi_T(h_T)P(x_T \mid h_T)
\]<p></p><p>于是我们把向前递归写为:</p>
<p></p>\[\pi_{T+1}(h_{T+1})= \sum_{h_T} \pi_T(h_T)P(x_T \mid h_T)P(h_{T+1}|{h_T})
\]<p></p><p>也可以写成:\(\pi_{T+1}=f(\pi_T,\,x_T)\) 于是我们可以把 \(f\) 看做可学习的函数,反向递归也如此,所以双向 RNN 是合理的<br>
* 双向 RNN 不能用于文本生成,因为我们不知道后面的词元是什么<br>
* 双向循环神经网络的计算速度非常慢。其主要原因是网络的前向传播需要在双向层中进行前向和后向递归,并且网络的反向传播还依赖于前向传播的结果。因此,梯度求解将有一个非常长的链。</p>
<h2 id="编码器-解码器架构">编码器-解码器架构</h2>
<ul>
<li>
<p>编码器解码器:</p>
<ul>
<li>编码器是对输入序列进行特征提取和信息压缩,转化成隐藏状态,称为上下文变量</li>
<li>解码器在没有引入注意力机制之前,我们的解码器只引用编码器的最后一个隐藏状态</li>
</ul>
</li>
<li>
<p>预测过程:</p>
<ul>
<li>基础的序列生成策略是将原句子后面加入 <code><eos></code>,然后填充,喂给编码器,得到最后的隐藏状态</li>
<li>将隐藏状态和 <code><bos></code> 送给解码器,然后得到第一个输出,之后把这个输出当作下次预测的输入,这个叫自回归</li>
</ul>
</li>
</ul>
<h2 id="输出策略与评估">输出策略与评估</h2>
<ul>
<li>
<p>输出策略:</p>
<ul>
<li>最简单的是贪心搜索,即我们在经过 softmax 层之后取最大概率的那一个;</li>
<li>束搜索(beam search)是贪心搜索的一个改进版本,它有一个超参数,名为束宽(beam size)\(k\)。在时间步1,我们选择具有最高条件概率的 \(k\) 个词元![]</li>
</ul>
</li>
<li>
<p>BLEU 通过统计预测序列和真实序列重合的 N 元语法 (n-gram) 片段来打分。</p>
</li>
</ul>
<p></p>\[\text{BLEU} = \exp\left(\min\left(0, 1 - \frac{\text{len}_{\text{label}}}{\text{len}_{\text{pred}}}\right)\right) \prod_{n=1}^k p_n^{1/2^n}
\]<p></p><h2 id="注意力机制">注意力机制</h2>
<ul>
<li>一些基本定义
<ul>
<li>Query (查询 \(Q\)):代表你的“自主意愿”。比如你想找一本书,这个“找书”的念头就是 Query。</li>
<li>Key (键 \(K\)):代表环境中物品的“客观线索/特征”。比如红色的杯子、报纸的排版、书的封面。这是非自主的提示。</li>
<li>Value (值 \(V\)):代表物品的“实质内容”。与 Key 一一对应</li>
</ul>
</li>
<li>注意力汇聚(Attention Pooling)的工作逻辑:系统拿着你的意愿(Query),去和环境中所有物品的特征(Keys)一一比对。匹配度越高的 Key,系统就会给它对应的 Value 分配越大的权重(Attention Weight)。最后把你关注到的所有 Value 按权重加起来,就是模型的输出。</li>
</ul>
<h3 id="bahdanau注意力">Bahdanau注意力</h3>
<p>基于注意力机制的编码器-解码器架构,Decoder 仍然没变,在 Decoder 和 Encoder 中间接入了 Attention 机制, Decoder 在这里干三件事:把自己的输出传入 Attention、传入自己的隐状态(但只用到了最后一层)、传入有效字符长度;我们把要 Decoder 输入的最后一层的隐状态作为 \(Q\);对于 Decoder 的输出,我们称为 <code>enc_outputs</code>, 把其作为 \(K, V\);这里 <code>hidden_state[-1]</code> 作为 \(Q\) 是因为它有前面序列的信息,而它要预测下一个序列![]</p>
<ul>
<li>这里,我们是 Decoder 拿着自己的 Q 去 Encoder 生成的 \(K, V\) 找线索,这个我们称之为交叉注意力</li>
</ul>
<h3 id="自注意力self-attention">自注意力(Self-Attention)</h3>
<ul>
<li>顾名思义就是自己看自己,也就是说,我们想要预测 \(i\) 位置的词 \(x_i\),那么就把其作为 \(Q\),在标准的注意力机制中,我们是:\(f(\text{Query}, (\text{Key}_1, \text{Value}_1), \dots, (\text{Key}_n, \text{Value}_n))\),自注意力要求自己理解自己,于是就有 \(\mathbf{y}_i = f(\mathbf{x}_i, (\mathbf{x}_1, \mathbf{x}_1), \dots, (\mathbf{x}_n, \mathbf{x}_n))\),这里的 \(x_1 \to x_n\) 为句子里的所有词,这种做法可以让模型更加深入的关注自身的句子,通过语境来给 \(x_i\) 一个不同的权重</li>
<li>对于上述的理解,其实就是可以看为,我去抓取一个句子的其他 <code>token</code>,如果我们要预测苹果的真实意思,若句子中出现了 '水果','市场','好吃',等等,那么我们预测该苹果为吃的苹果,若出现了 '股票','芯片'等,那么理解为苹果手机,而这些东西是要自注意力去抓取上下文来理解的</li>
<li>事实上,刚才对 \(x_i\) 作为 \(Q\) 的表述只是为了好理解,对于 '苹果' 一词,我们不想它只是一个定死的意思,它有多个语义,为此通过上述的方式更新它的向量表达</li>
<li>上面提到上下文能力,由于我们是用 \(Q \times K\),然后利用评估函数对 \(V\) 做匹配,所以 \(x_i\) 与其他任何词的距离为 \(O(1)\),这就是自注意力与其他序列模型不同之处</li>
<li>评估函数这里选择的是点积,但不是简单的 \(\text{Score} = X \cdot X_i\),因为这样是存在对称性的,即 \(X \cdot X_i = X_i \cdot X\),所以说我们引入一个可学习的参数,使得:\(\text{Score} = (X W_q) \cdot (X_i W_k)\),还有一个重要的原因,即上面的向量表达,由于 \(X\) 输入序列有很多的向量,我们需要利用可学习的 \(W\) 去进行特征提取以及语义匹配,其中 \(W_q, W_k\) 是特征提取,为了更优的相似度,\(W_v\) 是进行语义匹配</li>
<li>有一个缺点,由于我们直接进行 \(Q\times K\),那么我们发现,其实 \(x_i\) 在哪不重要,因为即使整个句子是乱序的也可以正确得到结果,而位置信息往往比较重要,所以我们引入<strong>位置编码</strong></li>
</ul>
<h3 id="transformer">Transformer</h3>
<ul>
<li>Transformer 也是基于编码器-解码器架构的,与视觉方面类似,引入了块的机制,其架构图如下:![]</li>
<li>对于其编码器,<code>Q,K,V</code> 均来自前一个编码层的输出,其中受到 <code>ResNet</code> 启发,采用了 Residual + LayerNorm,然后接入一个 FFN</li>
<li>对于其解码器,<code>Q</code> 为前一个解码器的输出,<code>K,V</code> 来自编码器的最后一个输出,但是解码器中每个位置只能考虑该位置之前的所有位置(因为不能看到之后的信息),所以保留了自回归的性质</li>
<li>这里的注意力评分机制是采用的点积注意力,为什么不采用加性注意力,原因有两个:
<ol>
<li>点积是矩阵乘法,速度较快,加性注意力要引入额外的权重矩阵和做一次 <code>tanh</code>,而且点积之后用一个标量除法 \(\frac{Q K^T}{\sqrt{d_k}}\) 可以有效地拉回分布方差</li>
<li>加性注意力计算时要用大量的广播机制,速度很慢,广播机制就是两个矩阵虽然形状不一样,但可以对矩阵做拓展使得可以相加</li>
</ol>
</li>
<li>对于位置编码,这里采用了固定位置编码,即三角函数性质的位置编码</li>
<li>对于 LayerNorm,它是对一层做均一化,而 BatchNorm 是对一个小批次,例如对于一个 Batch 的数据 <code>X = [, ]</code>, 那么对于 LayerNorm 来说,我们会得到 <code>X = [[-1, 1], [-1, 1]]</code>, 它对一个 Layer,即 <code>, </code> 做归一化;而 BathNorm 则是从一列,即 <code>[[-1, 1], ]</code>, 很明显的,token 的词向量特征与其他 token 无关,只与自身的向量特征有关,所以我们要做 LayerNorm</li>
<li>FFN(前馈网络)是干什么的?
<ol>
<li>FFN 的作用在作者的 paper 说是类似于 <code>1x1</code> 卷积,对词元进行升降维度;</li>
<li>事实上,FFN 还起到了加入非线性变换的作用,可能的疑问:在计算注意力中有 \(\phi(Q \times K)\),这不就是一个 <code>Softmax</code> 的非线性运算吗?但后面的 \(V\) 是线性的问题没有被解决,所以本质还是引入了非线性变换</li>
<li>可以降低词元向量各向异性</li>
</ol>
</li>
<li>对于语言模型,应该使用 Transformer 的解码器,如果使用编码器(当它看到第 \(t\) 个位置的时候,它可以利用未来的答案来进行作弊)</li>
<li>Transformer 可以应用于对图像的分类,可以先用卷积神经网络进行图像的像素特征提取,之后这些像素块的感受野相当于 token,由 Transformer 来更新注意力权重,理解这些图像特征之间的关系,可以做到更好的分类,这个后面再详细看<strong>VIT</strong></li>
</ul>
<h2 id="bert">BERT</h2>
<ul>
<li>
<p>BERT输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和,其中位置编码是可学习的。其输入形式为 <code><CLS> segment1 <SEP> segment2 <SEP></code></p>
</li>
<li>
<p>进行下一句预测的时候,我们提取出 <code><CLS></code> 作为输入,是因为我们判断两个句子是否连贯,是用宏观的全局语义表示,而 <code><CLS></code> 没有对应 token 语义,它负责吸收整个句子的全局逻辑(通过自注意力,提取全局特征),可以去做分类任务;还有一个原因就是,简洁通俗</p>
</li>
<li>
<p>运用 Bert 的时候,Bert 的参数是 Pretrained(因为比 Random 要好),我们对此进行 fine-tune,从 <code><CLS></code> 开始做预测,之后的那个 Linear 是随机初始化的(如果做分类,评价的话)。</p>
</li>
<li>
<p>Bert 在做填空预测的时候(给定一定的80%的概率 mask,10%的概率 random,10%的概率不变)是非监督的,对 Bert 的 fine-tune 是监督性的,需要打 label,合起来是 semi-supervise</p>
</li>
<li>
<p>Q-A Robot 的输入为 <code><CLS> question <SEP> document <SEP></code>, 输出是答案的起始和终止位置,唯一从头训练的是两个 tensor,尺寸和 Bert 的输出一样,两者分别代表起始位置和终止位置,对 document 做预测,两个 tensor 分别做 inner product 然后分别做 softmax,取 argmax 即可</p>
</li>
<li>
<p>Bert 在做预测(填空)的时候,吃的苹果和苹果电脑这两个完全不同的意思,Bert 在训练完之后,两者的余弦相似度是非常低的,原因可能是 Bert 的 Encoder 毕竟有 Transformer Block,所以可以抓取上下文,比如吃的苹果往往和树,味道相结合,苹果电脑则会和价格,性能结合在一起(但并不一定是对的,参考李宏毅,这个地方就是有待研究的,所以能力不完全在上下文能力中,还有很大研究空间)* 1. 1. 1.</p>
</li>
</ul><br>来源:程序园用户自行投稿发布,如果侵权,请联系站长删除<br>免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 深度学习笔记-《动手学习深度学习》