<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>深度学习 on Zeqiang Fang | 方泽强</title><link>https://zeqiang.fun/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/</link><description>Recent content in 深度学习 on Zeqiang Fang | 方泽强</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sun, 14 Mar 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://zeqiang.fun/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/" rel="self" type="application/rss+xml"/><item><title>胶囊网络 (Capsule Network)</title><link>https://zeqiang.fun/cn/2021/03/capsule-network/</link><pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2021/03/capsule-network/</guid><description><![CDATA[
        <h2 id="cnn-的缺陷">CNN 的缺陷</h2>
<p><a href="/cn/2018/08/cnn/">卷积神经网络（CNN）</a>在图像领域取得了很大的成功，但同时也存在一定的缺陷。卷积层中的卷积核对输入图像利用卷积运算提取其中的特征。卷积核以一个较小的尺寸并以一定的步长在图像上移动得到特征图。步长越大，特征图的尺寸就越小，过大的步长会丢失部分图像中的特征。池化层作用于产生的特征图上，使得 CNN 可以在不同形式的图像中识别出相同的物体，为 CNN 引入了空间不变性。</p>
<p>CNN 最大的缺陷就是忽略了不同特征之间的相对位置，从而无法从图像中识别出姿势、纹理和变化。CNN 中的池化操作使得模型具有空间不变性，因此模型就不具备等变性。以下图为例，CNN 会把第一幅和第二幅识别为人脸，而将第三幅方向翻转的图识别为不是人脸。池化操作造成了部分信息的丢失，因此需要更多的训练数据来补偿这些损失。</p>
<figure>
  <img data-src="/images/cn/2021-03-14-capsule-network/it-is-a-face.png" class="lazyload"/>
  <figcaption><p class="figcaption">图片来源：https://www.spiria.com/en/blog/artificial-intelligence/deep-learning-capsule-network-revolution/</p></figcaption>
</figure>
<h3 id="等变和不变">等变和不变</h3>
<p>对于一个函数 <code>$f$</code> 和一个变换 <code>$g$</code>，如果有：</p>
<p><code>$$ f \left(g \left(x\right)\right) = g \left(f \left(x\right)\right) $$</code></p>
<p>则称 <code>$f$</code> 对变换 <code>$g$</code> 有<strong>等变性</strong>。</p>
<p>例如，变换 <code>$g$</code> 为将图像向左平移若干像素，函数 <code>$f$</code> 表示检测一个人脸的位置。则 <code>$f \left(g \left(x\right)\right)$</code> 表示先将图片左移，我们将在原图的左侧检测到人脸；<code>$g \left(f \left(x\right)\right)$</code> 表示先检测人脸位置，然后将人脸位置左移。这两者的输出结果是一样的，与我们施加变换的顺序无关。CNN 中的卷积操作使得它对平移操作具有等变性。</p>
<p>对于一个函数 <code>$f$</code> 和一个变换 <code>$g: g \left(x\right) = x'$</code>，如果有：</p>
<p><code>$$ f \left(x\right) = f \left(x'\right) = f \left(g \left(x\right)\right) $$</code></p>
<p>则称 <code>$f$</code> 对变换 <code>$g$</code> 有<strong>不变性</strong>。</p>
<p>例如，变换 <code>$g$</code> 为旋转或平移，函数 <code>$f$</code> 表示检测图中是否有黑色，那么这些变换不会对函数结果有任何影响，可以说函数对该变换具有不变性。CNN 中的池化操作对平移操作具有近似不变性。</p>
<h3 id="逆图形">逆图形</h3>
<p>计算机图形学根据几何数据的内部层次结构来构造可视图像，该表示的结构将对象的相对位置考虑在内。软件采用层次的表示方式将其渲染为屏幕上的图像。人类大脑的工作原理则与渲染过程相反，我们称其为逆图形。大脑中对象的表示并不依赖于视角。</p>
<p>例如下图，人眼可以很容易的分辨出是自由女神像，只是角度不同，但 CNN 却很难做到，因为它不理解 3D 空间的内在。</p>
<p><img src="/images/cn/2021-03-14-capsule-network/statue-of-liberty-different-poses.jpg" alt=""></p>
<h2 id="胶囊网络">胶囊网络</h2>
<h3 id="胶囊">胶囊</h3>
<p>在引入“<strong>胶囊</strong>”这个概念的第一篇文献 Transforming Auto-encoders <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 中，Hinton 等人对胶囊概念理解如下：</p>
<blockquote>
<p>人工神经网络不应当追求“神经元”活动中的视角不变性（使用单一的标量输出来总结一个局部池中的重复特征检测器的活动），而应当使用局部的“胶囊”，这些胶囊对其输入执行一些相当复杂的内部计算，然后将这些计算的结果封装成一个包含信息丰富的输出的小向量。每个胶囊学习辨识一个有限的观察条件和变形范围内隐式定义的视觉实体，并输出实体在有限范围内存在的概率及一组“实例参数”，实例参数可能包括相对这个视觉实体的隐式定义的典型版本的精确的位姿、照明条件和变形信息。当胶囊工作正常时，视觉实体存在的概率具有局部不变性——当实体在胶囊覆盖的有限范围内的外观流形上移动时，概率不会改变。实例参数却是“等变的”——随着观察条件的变化，实体在外观流形上移动时，实例参数也会相应地变化，因为实例参数表示实体在外观流形上的内在坐标。</p>
</blockquote>
<p>人造神经元输出单个标量。对于 CNN 卷积层中的每个卷积核，对整个输入图复制同一个内核的权重输出一个二维矩阵。矩阵中每个数字是该卷积核对输入图像一部分的卷积，这个二维矩阵看作是重复特征检测器的输出。所有卷积核的二维矩阵堆叠在一起得到卷积层的输出。CNN 利用最大池化实现不变性，但最大池化丢失了有价值的信息，也没有编码特征之间的相对空间关系。</p>
<p>胶囊将<strong>特征检测的概率作为其输出向量的长度进行编码，检测出的特征的状态被编码为该向量指向的方向</strong>。当检测出的特征在图像中移动或其状态发生变化时，概率仍然保持不变（向量的长度没有改变），但它的方向改变了。</p>
<p>下表总结了胶囊和神经元的不同：</p>
<table>
  <thead>
    <tr>
      <th align="center" colspan="2"></th>
      <th align="center">Capsule</th>
      <th align="center">Traditional Neuron</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid;">
      <td align="center" colspan="2">Input from low-level capsule/neuron</td>
      <td align="center">$\text{vector}\left(\mathbf{u}_i\right)$</td>
      <td align="center">$\text{scalar}\left(x_i\right)$</td>
    <tr/>
    <tr>
      <td align="center" rowspan="8">Operration</td>
      <td align="center">Affine Transform</td>
      <td align="center">$\widehat{\mathbf{u}}_{j \mid i}=\mathbf{W}_{i j} \mathbf{u}_{i}$</td>
      <td align="center">-</td>
    <tr/>
    <tr>
      <td align="center">Weighting</td>
      <td align="center" rowspan="4">$\mathbf{s}_{j}=\sum_{i} c_{i j} \widehat{\mathbf{u}}_{j \mid i}$</td>
      <td align="center" rowspan="4">$a_{j}=\sum_{i} w_{i} x_{i}+b$</td>
    <tr/>
    <tr>
      <td align="center">Sum</td>
    <tr/>
    <tr>
      <td align="center">Nonlinear Activation</td>
      <td align="center">$\mathbf{v}_{j}=\dfrac{\left\|\mathbf{s}_{j}\right\|^{2}}{1+\left\|\mathbf{s}_{j}\right\|^{2}} \dfrac{\mathbf{s}_{j}}{\left\|\mathbf{s}_{j}\right\|}$</td>
      <td align="center">$h_{j}=f\left(a_{j}\right)$</td>
    <tr/>
    <tr style="border-top: 1px solid; border-bottom: 1px solid;">
      <td align="center" colspan="2">Output</td>
      <td align="center">$\text{vector}\left(\mathbf{v}_j\right)$</td>
      <td align="center">$\text{scalar}\left(h_j\right)$</td>
    </tr>
    <tr>
      <td align="right" colspan="3"><img src="/images/cn/2021-03-14-capsule-network/capsule-vs-traditional-neuron-capsule.png"/></td>
      <td align="center"><img src="/images/cn/2021-03-14-capsule-network/capsule-vs-traditional-neuron-traditional-neuron.png"/></td>
    </tr>
  </tbody>
</table>
<p>人造神经元包含如下 3 个计算步骤：</p>
<ol>
<li>输入标量的加权</li>
<li>加权标量的求和</li>
<li>求和标量到输出标量的非线性变换</li>
</ol>
<p>胶囊可以理解为上述 3 个步骤的向量版，同时增加了对输入的仿射变换：</p>
<ol>
<li>输入向量的矩阵乘法：胶囊接受的输入向量编码了低层胶囊检测出的相应对象的概率，向量的方向编码了检测出的对象的一些内部状态。接着将这些向量乘以相应的权重矩阵 <code>$\mathbf{W}$</code>，<code>$\mathbf{W}$</code> 编码了低层特征（例如：眼睛、嘴巴和鼻子）和高层特征（例如：面部）之间的空间关系和其他重要关系。</li>
<li>向量的标量加权：这个步骤同人造神经元对应的步骤类似，但神经元的权重是通过反向传播学习的，而胶囊则使用动态路由。</li>
<li>加权向量的求和：这个步骤同人造神经元对应的步骤类似。</li>
<li>求和向量到输出向量的非线性变换：胶囊神经网络的非线性激活函数接受一个向量，然后在不改变方向的前提下，将其长度压缩到 1 以下。</li>
</ol>
<h3 id="动态路由">动态路由</h3>
<p>胶囊网络使用动态路由算法进行训练，算法过程如下 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>：</p>


<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css">


<div><pre class="pseudocode">
\begin{algorithm}
\caption{Routing 算法}
\begin{algorithmic}
\PROCEDURE{Routing}{$\widehat{\mathbf{u}}_{j | i}, r, l$}
\STATE for all capsule $i$ in layer $l$ and capsule $j$ in layer $\left(l + 1\right)$: $b_{ij} \gets 0$
\FOR{$r$ iterations}
  \STATE for all capsule $i$ in layer $l$: $\mathbf{c}_i \gets \text{softmax} \left(\mathbf{b}_i\right)$
  \STATE for all capsule $j$ in layer $\left(l + 1\right)$: $\mathbf{s}_j \gets \sum_{i} c_{ij} \widehat{\mathbf{u}}_{j | i}$
  \STATE for all capsule $j$ in layer $\left(l + 1\right)$: $\mathbf{v}_j \gets \text{squash} \left(\mathbf{s}_j\right)$
  \STATE for all capsule $i$ in layer $l$ and capsule $j$ in layer $\left(l + 1\right)$: $b_{ij} \gets b_{ij} + \widehat{\mathbf{u}}_{j | i} \cdot \mathbf{v}_j$
\ENDFOR
\RETURN $\mathbf{v}_j$
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}
</pre></div>

<ol>
<li>第 1 行表示算法的输入为：低层 <code>$l$</code> 中所有胶囊的输出 <code>$\widehat{\mathbf{u}}$</code>，以及路由迭代计数 <code>$r$</code>。</li>
<li>第 2 行中的 <code>$b_{ij}$</code> 为一个临时变量，其值在迭代过程中更新，算法运行完毕后其值被保存在 <code>$c_{ij}$</code> 中。</li>
<li>第 3 行表示如下步骤将会被重复 <code>$r$</code> 次。</li>
<li>第 4 行利用 <code>$\mathbf{b}_i$</code> 计算低层胶囊的权重向量 <code>$\mathbf{c}_i$</code>。<code>$\text{softmax}$</code> 确保了所有权重为非负数，且和为一。第一次迭代后，所有系数 <code>$c_{ij}$</code> 的值相等，随着算法的继续，这些均匀分布将发生改变。</li>
<li>第 5 行计算经前一步确定的路由系数 <code>$c_{ij}$</code> 加权后的输入向量的线性组合。该步缩小输入向量并将他们相加，得到输出向量 <code>$\mathbf{s}_j$</code>。</li>
<li>第 6 行对前一步的输出向量应用 <code>$\text{squash}$</code> 非线性函数。这确保了向量的方向被保留下来，而长度被限制在 1 以下。</li>
<li>第 7 行通过观测低层和高层的胶囊，根据公式更新相应的权重 <code>$b_{ij}$</code>。胶囊 <code>$j$</code> 的当前输出与从低层胶囊 <code>$i$</code> 处接收到的输入进行点积，再加上旧的权重作为新的权重。点积检测胶囊输入和输出之间的相似性。</li>
<li>重复 <code>$r$</code> 次，计算出所有高层胶囊的输出，并确立路由权重。之后正向传导就可以推进到更高层的网络。</li>
</ol>
<p>点积运算接收两个向量，并输出一个标量。对于给定长度但方向不同的两个向量而言，点积有几种情况：<code>$a$</code> 最大正值；<code>$b$</code> 正值；<code>$c$</code> 零；<code>$d$</code> 负值；<code>$e$</code> 最小负值，如下图所示：</p>
<p><img src="/images/cn/2021-03-14-capsule-network/capsule-dot-product-1.png" alt=""></p>
<p>我们用紫色向量 <code>$\mathbf{v}_1$</code> 和 <code>$\mathbf{v}_2$</code> 表示高层胶囊，橙色向量表示低层胶囊的输入，其他黑色向量表示接收自其他低层胶囊的输入，如下图所示：</p>
<p><img src="/images/cn/2021-03-14-capsule-network/capsule-dot-product-2.png" alt=""></p>
<p>左侧的紫色输出 <code>$\mathbf{v}_1$</code> 和橙色输入 <code>$\widehat{\mathbf{u}}_{1|1}$</code> 指向相反的方向，这意味着他们的点积是一个负数，从而路由系数 <code>$c_{11}$</code> 减少；右侧的紫色输出 <code>$\mathbf{v}_2$</code> 和橙色输入 <code>$\widehat{\mathbf{u}}_{2|1}$</code> 指向相同的方向，从而路由系数 <code>$c_{12}$</code> 增加。在所有高层胶囊及其所有输入上重复该过程，得到一个路由系数集合，达成了来自低层胶囊的输出与高层胶囊的输出的最佳匹配。</p>
<h3 id="网络架构">网络架构</h3>
<p>胶囊网络由 6 层神经网络构成，前 3 层是编码器，后 3 层是解码器：</p>
<ol>
<li>卷积层</li>
<li>PrimaryCaps（主胶囊）层</li>
<li>DigitCaps（数字胶囊）层</li>
<li>第一全连接层</li>
<li>第二全连接层</li>
<li>第三全连接层</li>
</ol>
<h4 id="编码器">编码器</h4>
<p>编码器部分如下图所示：</p>
<p><img src="/images/cn/2021-03-14-capsule-network/capsule-network-encoder.png" alt=""></p>
<p>卷积层用于检测 2D 图像的基本特征。PrimaryCaps 层包含 32 个主胶囊，接收卷积层检测到的基本特征，生成特征的组合。DigitCaps 层包含 10 个数字胶囊，每个胶囊对应一个数字。</p>
<p>对于 <code>$k$</code> 个类别的数字，我们希望最高层的胶囊当且仅当一个数字出现在图像中时具有一个长的实例化向量。为了允许多个数字，对于每个 DigitCap 使用一个独立的损失函数：</p>
<p><code>$$ L_{k}=T_{k} \max \left(0, m^{+}-\left\|\mathbf{v}_{k}\right\|\right)^{2}+\lambda\left(1-T_{k}\right) \max \left(0,\left\|\mathbf{v}_{k}\right\|-m^{-}\right)^{2} $$</code></p>
<p>DigitCaps 层的输出为 10 个 16 维的向量，根据上面的公式计算每个向量的损失值，然后将 10 个损失值相加得到最终损失。</p>
<p>在损失函数中，当正确的标签与特定 DigitCap 的数字对应时 <code>$T_k$</code> 为 1，否则为 0。加号前一项用于计算正确 DigitCap 的损失，当概率值大于 <code>$m^{+} = 0.9$</code> 时为 0，当概率值小于 <code>$m^{+} = 0.9$</code> 时为非零值；加号后一项用于计算错误 DigitCap 的损失，当概率值小于 <code>$m^{-} = 0.1$</code> 时值为 0，当概率值大于 <code>$m^{-} = 0.1$</code> 时为非零值。公式中的 <code>$\lambda = 0.5$</code> 用于确保训练中数值的稳定性。</p>
<p>简单来说，低层的胶囊用于检测一些特定模式的出现概率和姿态，高层的胶囊用于检测更加复杂的图像，如下图所示：</p>
<p><img src="/images/cn/2021-03-14-capsule-network/two-layer-capsnet.png" alt=""></p>
<h4 id="解码器">解码器</h4>
<p>解码器部分如下图所示：</p>
<p><img src="/images/cn/2021-03-14-capsule-network/capsule-network-decoder.png" alt=""></p>
<p>解码器从正确的 DigitCap 中接收一个 16 维向量，并学习将其解码为数字图像。解码器接收正确的 DigitCap 的输出作为输入，并学习重建一张 <code>$28 \times 28$</code> 像素的图像，损失函数为重建图像与输入图像之间的欧式距离。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Hinton, G. E., Krizhevsky, A., &amp; Wang, S. D. (2011, June). Transforming auto-encoders. In <em>International conference on artificial neural networks</em> (pp. 44-51). Springer, Berlin, Heidelberg.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Sabour, S., Frosst, N., &amp; Hinton, G. E. (2017, December). Dynamic routing between capsules. In <em>Proceedings of the 31st International Conference on Neural Information Processing Systems</em> (pp. 3859-3869).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>隐马尔可夫 (Hidden Markov Model, HMM)，条件随机场 (Conditional Random Fields, CRF) 和序列标注 (Sequence Labeling)</title><link>https://zeqiang.fun/cn/2020/05/hmm-crf-and-sequence-labeling/</link><pubDate>Sat, 02 May 2020 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2020/05/hmm-crf-and-sequence-labeling/</guid><description><![CDATA[
        <h2 id="隐马尔可夫">隐马尔可夫</h2>
<p>隐马尔可夫模型（Hidden Markov Model，HMM）是一个描述包含隐含未知参数的马尔可夫过程的统计模型。马尔可夫过程（Markov Process）是因俄国数学家安德雷·安德耶维齐·马尔可夫（Андрей Андреевич Марков）而得名一个随机过程，在该随机过程中，给定当前状态和过去所有状态的条件下，其下一个状态的条件概率分布仅依赖于当前状态，通常具备离散状态的马尔可夫过程称之为马尔可夫链（Markov Chain）。因此，马尔可夫链可以理解为一个有限状态机，给定了当前状态为 <code>$S_i$</code> 时，下一时刻状态为 <code>$S_j$</code> 的概率，不同状态之间变换的概率称之为转移概率。下图描述了 3 个状态 <code>$S_a, S_b, S_c$</code> 之间转换状态的马尔可夫链。</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/hmm-markov-chain-example.png" class="lazyload"/>
  
</figure>
<p>隐马尔可夫模型中包含两种序列：随机生成的状态构成的序列称之为状态序列（state sequence），状态序列是不可被观测到的；每个状态对应的观测值组成的序列称之为观测序列（observation sequence）。令 <code>$I = \left(i_1, i_2, \cdots, i_T\right)$</code> 为状态序列，其中 <code>$i_t$</code> 为第 <code>$t$</code> 时刻系统的状态值，对应的有 <code>$O = \left(o_1, o_2, \cdots, o_T\right)$</code> 为观测序列，其中 <code>$o_t$</code> 为第 <code>$t$</code> 时刻系统的观测值，系统的所有可能的状态集合为 <code>$Q = \{q_1, q_2, \cdots, q_N\}$</code>，所有可能的观测集合为 <code>$V= \{v_1, v_2, \cdots, v_M\}$</code>。</p>
<p>隐马尔可夫模型主要由三组参数构成：</p>
<ol>
<li>状态转移矩阵：
<code>$$ A = \left[a_{ij}\right]_{N \times N} $$</code>
其中，
<code>$$ a_{ij} = P \left(i_{t+1} = q_j | i_t = q_i\right), 1 \leq i, j \leq N $$</code>
表示 <code>$t$</code> 时刻状态为 <code>$q_i$</code> 的情况下，在 <code>$t+1$</code> 时刻状态转移到 <code>$q_j$</code> 的概率。</li>
<li>观测概率矩阵：
<code>$$ B = \left[b_j \left(k\right)\right]_{N \times M} $$</code>
其中，
<code>$$ b_j \left(k\right) = P \left(o_t = v_k | i_t = q_j\right), k = 1, 2, \cdots, M, j = 1, 2, \cdots, N $$</code>
表示 <code>$t$</code> 时刻状态为 <code>$q_i$</code> 的情况下，观测值为 <code>$v_k$</code> 的概率。</li>
<li>初始状态概率向量：
<code>$$ \pi = \left(\pi_i\right) $$</code>
其中，
<code>$$ \pi_i = P \left(i_1 = q_i\right), i = 1, 2, \cdots, N $$</code>
表示 <code>$t = 1$</code> 时刻，系统处于状态 <code>$q_i$</code> 的概率。</li>
</ol>
<p>初始状态概率向量 <code>$\pi$</code> 和状态转移矩阵 <code>$A$</code> 决定了状态序列，观测概率矩阵 <code>$B$ </code> 决定了状态序列对应的观测序列，因此马尔可夫模型可以表示为：</p>
<p><code>$$ \lambda = \left(A, B, \pi\right) $$</code></p>
<p>对于马尔可夫模型 <code>$\lambda = \left(A, B, \pi\right)$</code>，通过如下步骤生成观测序列 <code>$\{o_1, o_2, \cdots, o_T\}$</code>：</p>
<ol>
<li>按照初始状态分布 <code>$\pi$</code> 产生状态 <code>$i_1$</code>.</li>
<li>令 <code>$t = 1$</code>。</li>
<li>按照状态 <code>$i_t$</code> 的观测概率分布 <code>$b_{i_t} \left(k\right)$</code> 生成 <code>$o_t$</code>。</li>
<li>按照状态 <code>$i_t$</code> 的状态转移概率分布 <code>$\left\{a_{i_t i_{t+1}}\right\}$</code> 产生状态 <code>$i_{t+1}$</code>，<code>$i_{t+1} = 1, 2, \cdots, N$</code>。</li>
<li>令 <code>$t = t + 1$</code>，如果 <code>$t &lt; T$</code>，转步骤 3；否则，终止。</li>
</ol>
<p>马尔可夫模型在应用过程中有 3 个基本问题 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>：</p>
<ol>
<li>概率计算问题。给定模型 <code>$\lambda = \left(A, B, \pi\right)$</code> 和观测序列 <code>$O = \{o_1, o_2, \cdots, o_T\}$</code>，计算在模型 <code>$\lambda$</code> 下观测序列 <code>$O$</code> 出现的概率 <code>$P\left(O | \lambda \right)$</code>。</li>
<li>学习问题。已知观测序列 <code>$O = \{o_1, o_2, \cdots, o_T\}$</code>，估计模型 <code>$\lambda = \left(A, B, \pi\right)$</code> 参数，使得在该模型下观测序列概率 <code>$P\left(X | \lambda \right)$</code> 最大。即用极大似然估计的方法估计参数。</li>
<li>预测问题，也称为解码（decoding）问题。已知模型 <code>$\lambda = \left(A, B, \pi\right)$</code> 和观测序列 <code>$O = \{o_1, o_2, \cdots, o_T\}$</code>，求对给定观测序列条件概率 <code>$P \left(I | O\right)$</code> 最大的状态序列 <code>$I = \{i_1, i_2, \cdots, i_T\}$</code>。即给定观测序列，求最有可能的对应的状态序列。</li>
</ol>
<h3 id="概率计算">概率计算</h3>
<h4 id="直接计算法">直接计算法</h4>
<p>给定模型 <code>$\lambda = \left(A, B, \pi \right)$</code> 和观测序列 <code>$O = \{o_1, o_2, ..., o_T\}$</code>，计算在模型 <code>$\lambda$</code> 下观测序列 <code>$O$</code> 出现的概率 <code>$P\left(O | \lambda \right)$</code>。最简单的办法就是列举出左右可能的状态序列 <code>$I = \{i_1, i_2, ..., i_T\}$</code>，再根据观测概率矩阵 <code>$B$</code>，计算每种状态序列对应的联合概率 <code>$P \left(O, I | \lambda\right)$</code>，对其进行求和得到概率 <code>$P\left(O | \lambda \right)$</code>。</p>
<p>状态序列 <code>$I = \{i_1, i_2, ..., i_T\}$</code> 的概率是：</p>
<p><code>$$ P \left(I | \lambda \right) = \pi_{y_1} \prod_{t = 1}^{T - 1} a_{{i_t}{i_{t+1}}} $$</code></p>
<p>对于固定的状态序列 <code>$I = \{i_1, i_2, ..., i_T\}$</code>，观测序列 <code>$O = \{o_1, o_2, ..., o_T\}$</code> 的概率是：</p>
<p><code>$$ P \left(O | I, \lambda \right) = \prod_{t = 1}^{T} b_{i_t} \left(o_t\right) $$</code></p>
<p><code>$O$</code> 和 <code>$I$</code> 同时出现的联合概率为：</p>
<p><code>$$ \begin{split} P \left(O, I | \lambda \right) &amp;= P \left(O | I, \lambda \right) P \left(I | \lambda \right) \\ &amp;= \pi_{y_1} \prod_{t = 1}^{T - 1} a_{{i_t}{i_{t+1}}} \prod_{t = 1}^{T} b_{i_t} \left(o_t\right) \end{split} $$</code></p>
<p>然后，对于所有可能的状态序列 <code>$I$</code> 求和，得到观测序列 <code>$O$</code> 的概率 <code>$P \left(O | \lambda\right)$</code>，即：</p>
<p><code>$$ \begin{split} P\left(O | \lambda \right) &amp;= \sum_{I} P \left(O | I, \lambda \right) P \left(I | \lambda \right)  \\ &amp;= \sum_{i_1, i_2, \cdots, i_T} \pi_{y_1} \prod_{t = 1}^{T - 1} a_{{i_t}{i_{t+1}}} \prod_{t = 1}^{T} b_{i_t} \left(o_t\right) \end{split} $$</code></p>
<p>但利用上式的计算量很大，是 <code>$O \left(T N^T\right)$</code> 阶的，这种算法不可行。</p>
<h4 id="前向算法">前向算法</h4>
<p><strong>前向概率</strong>：给定马尔可夫模型 <code>$\lambda$</code>，给定到时刻 <code>$t$</code> 部分观测序列为 <code>$o_1, o_2, \cdots, o_t$</code> 且状态为 <code>$q_i$</code> 的概率为前向概率，记作：</p>
<p><code>$$ \alpha_t \left(i\right) = P \left(o_1, o_2, \cdots, o_t, i_t = q_i | \lambda\right) $$</code></p>
<p>可以递推地求得前向概率 <code>$\alpha_t \left(i\right)$</code> 及观测序列概率 <code>$P \left(O | \lambda\right)$</code>，前向算法如下：</p>
<ol>
<li>初值
<code>$$ \alpha_{1}(i)=\pi_{i} b_{i}\left(o_{1}\right), \quad i=1,2, \cdots, N $$</code></li>
<li>递推，对 <code>$t = 1, 2, \cdots, T-1$</code>
<code>$$ \alpha_{t+1}(i)=\left[\sum_{j=1}^{N} \alpha_{t}(j) a_{j i}\right] b_{i}\left(o_{t+1}\right), \quad i=1,2, \cdots, N $$</code></li>
<li>终止
<code>$$ P(O | \lambda)=\sum_{i=1}^{N} \alpha_{T}(i) $$</code></li>
</ol>
<h4 id="后向算法">后向算法</h4>
<p><strong>后向概率</strong>：给定隐马尔可夫模型 <code>$\lambda$</code>，给定在时刻 <code>$t$</code> 状态为 <code>$q_i$</code> 的条件下，从 <code>$t+1$</code> 到 <code>$T$</code> 的部分观测序列为 <code>$o_{t+1}, o_{t+2}, \cdots, o_T$</code> 的概率为后向概率，记作：</p>
<p><code>$$ \beta_{t}(i)=P\left(o_{t+1}, o_{t+2}, \cdots, o_{T} | i_{t}=q_{i}, \lambda\right) $$</code></p>
<p>可以递推地求得后向概率 <code>$\alpha_t \left(i\right)$</code> 及观测序列概率 <code>$P \left(O | \lambda\right)$</code>，后向算法如下：</p>
<ol>
<li>初值
<code>$$ \beta_{T}(i)=1, \quad i=1,2, \cdots, N $$</code></li>
<li>递推，对 <code>$t = T-1, T-2, \cdots, 1$</code>
<code>$$ \beta_{t}(i)=\sum_{j=1}^{N} a_{i j} b_{j}\left(o_{t+1}\right) \beta_{t+1}(j), \quad i=1,2, \cdots, N $$</code></li>
<li>终止
<code>$$ P(O | \lambda)=\sum_{i=1}^{N} \pi_{i} b_{i}\left(o_{1}\right) \beta_{1}(i) $$</code></li>
</ol>
<h3 id="学习算法">学习算法</h3>
<h4 id="监督学习算法">监督学习算法</h4>
<p>假设以给训练数据包含 <code>$S$</code> 个长度相同的观测序列和对应的状态序列 <code>$\left\{\left(O_1, I_1\right), \left(O_2, I_2\right), \cdots, \left(O_S, I_S\right)\right\}$</code>，那么可以利用极大似然估计法来估计隐马尔可夫模型的参数。</p>
<p>设样本中时刻 <code>$t$</code> 处于状态 <code>$i$</code> 时刻 <code>$t+1$</code> 转移到状态 <code>$j$</code> 的频数为 <code>$A_{ij}$</code>，那么转移概率 <code>$a_{ij}$</code> 的估计是：</p>
<p><code>$$ \hat{a}_{i j}=\frac{A_{i j}}{\sum_{j=1}^{N} A_{i j}}, \quad i=1,2, \cdots, N ; \quad j=1,2, \cdots, N $$</code></p>
<p>设样本中状态为 <code>$j$</code> 并观测为 <code>$k$</code> 的频数是 <code>$B_{jk}$</code>，那么状态为 <code>$j$</code> 观测为 <code>$k$</code> 的概率 <code>$b_j \left(k\right)$</code> 的估计是：</p>
<p><code>$$ \hat{b}_{j}(k)=\frac{B_{j k}}{\sum_{k=1}^{M} B_{j k}}, \quad j=1,2, \cdots, N ; \quad k=1,2, \cdots, M $$</code></p>
<p>初始状态概率 <code>$\pi_i$</code> 的估计 <code>$\hat{\pi}_i$</code> 为 <code>$S$</code> 个样本中初始状态为 <code>$q_i$</code> 的频率。</p>
<h4 id="无监督学习算法">无监督学习算法</h4>
<p>假设给定训练数据值包含 <code>$S$</code> 个长度为 <code>$T$</code> 的观测序列 <code>$\left\{O_1, O_2, \cdots, O_S\right\}$</code> 而没有对应的状态序例，目标是学习隐马尔可夫模型 <code>$\lambda = \left(A, B, \pi\right)$</code> 的参数。我们将观测序列数据看做观测数据 <code>$O$</code>，状态序列数据看作不可观测的隐数据 <code>$I$</code>，那么马尔可夫模型事实上是一个含有隐变量的概率模型：</p>
<p><code>$$ P(O | \lambda)=\sum_{I} P(O | I, \lambda) P(I | \lambda) $$</code></p>
<p>它的参数学习可以由 EM 算法实现。EM 算法在隐马尔可夫模型学习中的具体实现为 Baum-Welch 算法：</p>
<ol>
<li>初始化。对 <code>$n = 0$</code>，选取 <code>$a_{i j}^{(0)}, b_{j}(k)^{(0)}, \pi_{i}^{(0)}$</code>，得到模型 <code>$\lambda^{(0)}=\left(A^{(0)}, B^{(0)}, \pi^{(0)}\right)$</code>。</li>
<li>递推。对 <code>$n = 1, 2, \cdots$</code>：
<code>$$ \begin{aligned} a_{i j}^{(n+1)} &amp;= \frac{\sum_{t=1}^{T-1} \xi_{t}(i, j)}{\sum_{t=1}^{T-1} \gamma_{t}(i)} \\ b_{j}(k)^{(n+1)} &amp;= \frac{\sum_{t=1, o_{t}=v_{k}}^{T} \gamma_{t}(j)}{\sum_{t=1}^{T} \gamma_{t}(j)} \\ \pi_{i}^{(n+1)} &amp;= \gamma_{1}(i) \end{aligned} $$</code>
右端各按照观测 <code>$O=\left(o_{1}, o_{2}, \cdots, o_{T}\right)$</code> 和模型 <code>$\lambda^{(n)}=\left(A^{(n)}, B^{(n)}, \pi^{(n)}\right)$</code> 计算，
<code>$$ \begin{aligned} \gamma_{t}(i) &amp;= \frac{\alpha_{t}(i) \beta_{t}(i)}{P(O | \lambda)}=\frac{\alpha_{t}(i) \beta_{t}(i)}{\sum_{j=1}^{N} \alpha_{t}(j) \beta_{t}(j)} \\ \xi_{t}(i, j) &amp;= \frac{\alpha_{t}(i) a_{i j} b_{j}\left(o_{t+1}\right) \beta_{t+1}(j)}{\sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_{t}(i) a_{i j} b_{j}\left(o_{t+1}\right) \beta_{t+1}(j)} \end{aligned} $$</code></li>
<li>终止。得到模型参数 <code>$\lambda^{(n+1)}=\left(A^{(n+1)}, B^{(n+1)}, \pi^{(n+1)}\right)$</code>。</li>
</ol>
<h3 id="预测算法">预测算法</h3>
<h4 id="近似算法">近似算法</h4>
<p>近似算法的思想是，在每个时刻 <code>$t$</code> 选择在该时刻最有可能出现的状态 <code>$i_t^*$</code>，从而得到一个状态序列 <code>$I^{*}=\left(i_{1}^{*}, i_{2}^{*}, \cdots, i_{T}^{*}\right)$</code>，将它作为预测的结果。给定隐马尔可夫模型 <code>$\lambda$</code> 和观测序列 <code>$O$</code>，在时刻 <code>$t$</code> 处于状态 <code>$q_i$</code> 的概率 <code>$\gamma_t \left(i\right)$</code> 是：</p>
<p><code>$$ \gamma_{t}(i)=\frac{\alpha_{t}(i) \beta_{t}(i)}{P(O | \lambda)}=\frac{\alpha_{t}(i) \beta_{t}(i)}{\sum_{j=1}^{N} \alpha_{t}(j) \beta_{t}(j)} $$</code></p>
<p>在每一时刻 <code>$t$</code> 最有可能的状态 <code>$i_t^*$</code> 是：</p>
<p><code>$$ i_{t}^{*}=\arg \max _{1 \leqslant i \leqslant N}\left[\gamma_{t}(i)\right], \quad t=1,2, \cdots, T $$</code></p>
<p>从而得到状态序列 <code>$I^{*}=\left(i_{1}^{*}, i_{2}^{*}, \cdots, i_{T}^{*}\right)$</code>。</p>
<p>近似算法的优点是计算简单，其缺点是不能保证预测的状态序列整体是最有可能的状态序列，因为预测的状态序列可能有实际不发生的部分。事实上，上述方法得到的状态序列中有可能存在转移概率为0的相邻状态，即对某些 <code>$i, j, a_{ij} = 0$</code> 。尽管如此，近似算法仍然是有用的。</p>
<h4 id="维特比算法">维特比算法</h4>
<p>维特比算法（Viterbi Algorithm）实际是用动态规划（Dynamic Programming）解隐马尔可夫模型预测问题，即用动态规划求概率最大路径（最优路径）。这时一条路径对应着一个状态序列。</p>
<p>首先导入两个变量 <code>$\sigma$</code> 和 <code>$\Psi$</code>。定义在时刻 <code>$t$</code> 状态为 <code>$i$</code> 的所有单个路径 <code>$\left(i_1, i_2, \cdots, i_t\right)$</code> 中概率最大值为：</p>
<p><code>$$ \delta_{t}(i)=\max _{i_{1}, i_{2}, \cdots, i_{t-1}} P\left(i_{t}=i, i_{t-1}, \cdots, i_{1}, o_{t}, \cdots, o_{1} | \lambda\right), \quad i=1,2, \cdots, N $$</code></p>
<p>由定义可得变量 <code>$\sigma$</code> 的递推公式：</p>
<p><code>$$ \begin{aligned} \delta_{t+1}(i) &amp;=\max _{i_{1}, i_{2}, \cdots, i_{t}} P\left(i_{t+1}=i, i_{t}, \cdots, i_{1}, o_{t+1}, \cdots, o_{1} | \lambda\right) \\ &amp;=\max _{1 \leqslant j \leqslant N}\left[\delta_{t}(j) a_{j i}\right] b_{i}\left(o_{t+1}\right), \quad i=1,2, \cdots, N ; \quad t=1,2, \cdots, T-1 \end{aligned} $$</code></p>
<p>定义在时刻 <code>$t$</code> 状态为 <code>$i$</code> 的所有单个路径 <code>$\left(i_1, i_2, \cdots, i_{t-1}, i\right)$</code> 中概率最大的路径的第 <code>$t - 1$</code> 个结点为：</p>
<p><code>$$ \Psi_{t}(i)=\arg \max _{1 \leqslant j \leqslant N}\left[\delta_{t-1}(j) a_{j i}\right], \quad i=1,2, \cdots, N $$</code></p>
<p>维特比算法流程如下：</p>
<ol>
<li>初始化
<code>$$ \begin{array}{c} \delta_{1}(i)=\pi_{i} b_{i}\left(o_{1}\right), \quad i=1,2, \cdots, N \\ \Psi_{1}(i)=0, \quad i=1,2, \cdots, N \end{array} $$</code></li>
<li>递推。对 <code>$t = 2, 3, \cdots, T$</code>
<code>$$ \begin{array}{c} \delta_{t}(i)=\max _{1 \leqslant j \leqslant N}\left[\delta_{t-1}(j) a_{j i}\right] b_{i}\left(o_{t}\right), \quad i=1,2, \cdots, N \\ \Psi_{t}(i)=\arg \max _{1 \leqslant j \leqslant N}\left[\delta_{t-1}(j) a_{j i}\right], \quad i=1,2, \cdots, N \end{array} $$</code></li>
<li>终止。
<code>$$ \begin{array}{c} P^{*}=\max _{1 \leqslant i \leqslant N} \delta_{T}(i) \\ i_{T}^{*}=\arg \max _{1 \leqslant i \leqslant N}\left[\delta_{T}(i)\right] \end{array} $$</code></li>
<li>最优路径回溯。对 <code>$t = T - 1, T - 2, \cdots, 1$</code>
<code>$$ i_{t}^{*}=\Psi_{t+1}\left(i_{t+1}^{*}\right) $$</code></li>
</ol>
<p>求的最优路径 <code>$I^{*}=\left(i_{1}^{*}, i_{2}^{*}, \cdots, i_{T}^{*}\right)$</code>。</p>
<h2 id="条件随机场">条件随机场</h2>
<p>概率无向图模型（Probabilistic Undirected Graphical Model）又称为马尔可夫随机场（Markov Random Field），是一个可以由无向图表示的联合概率分布。概率图模型（Probabilistic Graphical Model）是由图表示的概率分布，设有联合概率分布 <code>$P \left(Y\right), Y \in \mathcal{Y}$</code> 是一组随机变量。由无向图 <code>$G = \left(V, E\right)$</code> 表示概率分布 <code>$P \left(Y\right)$</code>，即在图 <code>$G$</code> 中，结点 <code>$v \in V$</code> 表示一个随机变量 <code>$Y_v, Y = \left(Y_v\right)_{v \in V}$</code>，边 <code>$e \in E$</code> 表示随机变量之间的概率依赖关系。</p>
<p><strong>成对马尔可夫性</strong>：设 <code>$u$</code> 和 <code>$v$</code> 是无向图 <code>$G$</code> 中任意两个没有边连接的结点，结点 <code>$u$</code> 和 <code>$v$</code> 分别对应随机变量 <code>$Y_u$</code> 和 <code>$Y_v$</code>。其他所有结点为 <code>$O$</code>，对应的随机变量组是 <code>$Y_O$</code>。成对马尔可夫是指给定随机变量组 <code>$Y_O$</code> 的条件下随机变量 <code>$Y_u$</code> 和 <code>$Y_v$</code> 是条件独立的，即：</p>
<p><code>$$ P\left(Y_{u}, Y_{v} | Y_{O}\right)=P\left(Y_{u} | Y_{O}\right) P\left(Y_{v} | Y_{O}\right) $$</code></p>
<p><strong>局部马尔可夫性</strong>：设 <code>$v \in V$</code> 是无向图 <code>$G$</code> 中任意一个结点，<code>$W$</code> 是与 <code>$v$</code> 有边连接的所有结点，<code>$O$</code> 是 <code>$v$</code> 和 <code>$W$</code> 以外的其他所有结点。<code>$v$</code> 表示的随机变量是 <code>$Y_v$</code>，<code>$W$</code> 表示的随机变量组是 <code>$Y_W$</code>，<code>$O$</code> 表示的随机变量组是 <code>$Y_O$</code>。局部马尔可夫性是指在给定随机变量组 <code>$Y_W$</code> 的条件下随机变量 <code>$Y_v$</code> 与随机变量组 <code>$Y_O$</code> 是独立的，即：</p>
<p><code>$$ P\left(Y_{v}, Y_{O} | Y_{W}\right)=P\left(Y_{v} | Y_{W}\right) P\left(Y_{O} | Y_{W}\right) $$</code></p>
<p>在 <code>$P \left(Y_O | Y_W\right) &gt; 0$</code> 时，等价地：</p>
<p><code>$$ P\left(Y_{v} | Y_{W}\right)=P\left(Y_{v} | Y_{W}, Y_{O}\right) $$</code></p>
<p>局部马尔可夫性如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/local-markov.png" class="lazyload"/>
  
</figure>
<p><strong>全局马尔可夫性</strong>：设结点结合 <code>$A, B$</code> 是在无向图 <code>$G$</code> 中被结点集合 <code>$C$</code> 分开的任意结点集合，如下图所示。结点集合 <code>$A, B$</code> 和 <code>$C$</code> 所对应的随机变量组分别是 <code>$Y_A, Y_B$</code> 和 <code>$Y_C$</code>。全局马尔可夫性是指给定随机变量组 <code>$Y_C$</code> 条件下随机变量组 <code>$Y_A$</code> 和 <code>$Y_B$</code> 是条件独立的，即：</p>
<p><code>$$ P\left(Y_{A}, Y_{B} | Y_{C}\right)=P\left(Y_{A} | Y_{C}\right) P\left(Y_{B} | Y_{C}\right) $$</code></p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/global-markov.png" class="lazyload"/>
  
</figure>
<p><strong>概率无向图模型</strong>定义为：设有联合概率分布 <code>$P \left(Y\right)$</code>，由无向图 <code>$G = \left(V, E\right)$</code> 表示，在图 <code>$G$</code> 中，结点表示随机变量，边表示随机变量之间的依赖关系。如果联合概率分布 <code>$P \left(Y\right)$</code> 满足成对、局部或全局马尔可夫性，就称此联合概率分布为概率无向图模型（Probabilistic Undirected Graphical Model），或马尔可夫随机场（Markov Random Field）。</p>
<p><strong>团与最大团</strong>：无向图 <code>$G$</code> 中任何两个结点均有边连接的结点子集称为团（Clique）。若 <code>$C$</code> 是无向图 <code>$G$</code> 的一个团，并且不能再加进任何一个 <code>$G$</code> 的结点时期成为一个更大的团，则称此 <code>$C$</code> 为最大团（Maximal Clique）。</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/clique.png" class="lazyload"/>
  <figcaption><p class="figcaption">无向图的团和最大团</p></figcaption>
</figure>
<p>上图表示 4 个结点组成的无向图。图中有 2 个结点组成的团有 5 个：<code>$\left\{Y_1, Y_2\right\}$</code>，<code>$\left\{Y_2, Y_3\right\}$</code>，<code>$\left\{Y_3, Y_4\right\}$</code>，<code>$\left\{Y_4, Y_2\right\}$</code> 和 <code>$\left\{Y_1, Y_3\right\}$</code>。有 2 个最大团：<code>$\left\{Y_1, Y_2, Y_3\right\}$</code> 和 <code>$\left\{Y_2, Y_3, Y_4\right\}$</code>。而 <code>$\left\{Y_1, Y_2, Y_3, Y_4\right\}$</code> 不是一个团，因为 <code>$Y_1$</code> 和 <code>$Y_4$</code> 没有边连接。</p>
<p>将概率无向图模型的联合概率分布表示为其最大团上的随机变量的函数的乘积形式的操作，称为概率无向图模型的因子分解。给定无向图模型，设其无向图为 <code>$G$</code>，<code>$C$</code> 为 <code>$G$</code> 上的最大团，<code>$Y_C$</code> 表示 <code>$C$</code> 对应的随机变量。那么概率无向图模型的联合概率分布 <code>$P \left(Y\right)$</code> 可以写作图中所有最大团 <code>$C$</code> 上的函数 <code>$\Psi_C \left(Y_C\right)$</code> 的乘积形式，即：</p>
<p><code>$$ P(Y)=\frac{1}{Z} \prod_{C} \Psi_{C}\left(Y_{C}\right) $$</code></p>
<p>其中，<code>$Z$</code> 是规范化因子：</p>
<p><code>$$ Z=\sum_{Y} \prod_{C} \Psi_{C}\left(Y_{C}\right) $$</code></p>
<p>规范化因子保证 <code>$P \left(Y\right)$</code> 构成一个概率分布。函数 <code>$\Psi_C \left(Y_C\right)$</code> 称为<strong>势函数</strong>，这里要求势函数 <code>$\Psi_C \left(Y_C\right)$</code> 是严格正的，通常定义为指数函数：</p>
<p><code>$$ \Psi_{C}\left(Y_{C}\right)=\exp \left\{-E\left(Y_{C}\right)\right\} $$</code></p>
<p>概率无向图模型的因子分解由这个 Hammersley-Clifford 定理来保证。</p>
<p><strong>条件随机场</strong>（Conditional Random Field）是给定随机变量 <code>$X$</code> 条件下，随机变量 <code>$Y$</code> 的马尔可夫随机场。设 <code>$X$</code> 与 <code>$Y$</code> 是随机变量，<code>$P \left(Y | X\right)$</code> 是给定 <code>$X$</code> 的条件下 <code>$Y$</code> 的条件概率分布。若随机变量 <code>$Y$</code> 构成一个有无向图 <code>$G = \left(V, E\right)$</code> 表示的马尔可夫随机场，即：</p>
<p><code>$$ P\left(Y_{v} | X, Y_{w}, w \neq v\right)=P\left(Y_{v} | X, Y_{w}, w \sim v\right) $$</code></p>
<p>对任意结点 <code>$v$</code> 成立，则称条件概率分布 <code>$P \left(Y | X\right)$</code> 为条件随机场。其中，<code>$w \sim v$</code> 表示在图 <code>$G = \left(V, E\right)$</code> 中与结点 <code>$v$</code> 有边连接的所有结点 <code>$w$</code>，<code>$w \neq v$</code> 表示结点 <code>$v$</code> 以外的所有结点，<code>$Y_v, Y_u$</code> 与 <code>$Y_w$</code> 为结点 <code>$v, u$</code> 和 <code>$w$</code> 对应的随机变量。</p>
<p>定义中并没有要求 <code>$X$</code> 和 <code>$Y$</code> 具有相同的结构，一般假设 <code>$X$</code> 和 <code>$Y$</code> 有相同的图结构，下图展示了无向图的线性链情况，即：</p>
<p><code>$$ G=(V=\{1,2, \cdots, n\}, E=\{(i, i+1)\}), \quad i=1,2, \cdots, n-1 $$</code></p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/linear-crf-1.png" class="lazyload"/>
  <figcaption><p class="figcaption">线性链条件随机场</p></figcaption>
</figure>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/linear-crf-2.png" class="lazyload"/>
  <figcaption><p class="figcaption">X 和 Y 有相同的图结构的线性链条件随机场</p></figcaption>
</figure>
<p>此情况下，<code>$X=\left(X_{1}, X_{2}, \cdots, X_{n}\right), Y=\left(Y_{1}, Y_{2}, \cdots, Y_{n}\right)$</code>，最大团是相邻两个结点的集合。</p>
<p><strong>线性链条件随机场</strong>：设 <code>$X=\left(X_{1}, X_{2}, \cdots, X_{n}\right), Y=\left(Y_{1}, Y_{2}, \cdots, Y_{n}\right)$</code> 均为线性链表示的随机变量序列，若在给定随机变量序列 <code>$X$</code> 的条件下，随机变量序列 <code>$Y$</code> 的条件概率分布 <code>$P \left(Y | X\right)$</code> 构成条件随机场，即满足马尔可夫性：</p>
<p><code>$$ \begin{array}{c} P\left(Y_{i} | X, Y_{1}, \cdots, Y_{i-1}, Y_{i+1}, \cdots, Y_{n}\right)=P\left(Y_{i} | X, Y_{i-1}, Y_{i+1}\right) \\ i=1,2, \cdots, n \quad (\text { 在 } i=1 \text { 和 } n \text { 时只考虑单边 }) \end{array} $$</code></p>
<p>则称 <code>$P \left(Y | X\right)$</code> 为线性链条件随机场。在标注问题中，<code>$X$</code> 表示输入观测序列，<code>$Y$</code> 表示对应的输出标记序列或状态序列。</p>
<p>根据 Hammersley-Clifford 定理，设 <code>$P \left(Y | X\right)$</code> 为线性链条件随机场，则在随机变量 <code>$X$</code> 取值为 <code>$x$</code> 的条件下，随机变量 <code>$Y$</code> 取值为 <code>$y$</code> 的条件概率有如下形式：</p>
<p><code>$$ P(y | x)=\frac{1}{Z(x)} \exp \left(\sum_{i, k} \lambda_{k} t_{k}\left(y_{i-1}, y_{i}, x, i\right)+\sum_{i, l} \mu_{l} s_{l}\left(y_{i}, x, i\right)\right) $$</code></p>
<p>其中，</p>
<p><code>$$ Z(x)=\sum_{y} \exp \left(\sum_{i, k} \lambda_{k} t_{k}\left(y_{i-1}, y_{i}, x, i\right)+\sum_{i, l} \mu_{l} s_{l}\left(y_{i}, x, i\right)\right) $$</code></p>
<p>其中，<code>$t_k$</code> 和 <code>$s_l$</code> 是特征函数，<code>$\lambda_k$</code> 和 <code>$\mu_l$</code> 是对应的权值。<code>$Z \left(x\right)$</code> 是规范化因子，求和是在所有可能的输出序列上进行的。</p>
<p>条件随机场的概率计算，学习算法和预测算法类似隐马尔可夫模型，在此不进行过多赘述，有兴趣的同学可以参见 <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>综上所述，隐马尔可夫模型和条件随机场的主要联系和区别如下：</p>
<ol>
<li>HMM 是概率有向图，CRF 是概率无向图</li>
<li>HMM 是生成模型，CRF 是判别模型</li>
</ol>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/relationship-between-nb-lr-hmm-lcrf-gdm-gcrf.png" class="lazyload"/>
  <figcaption><p class="figcaption">图片来源：An Introduction to Conditional Random Fields</p></figcaption>
</figure>
<p>如上图所示，上面部分为生成式模型，下面部分为判别式模型，生成式模型尝试构建联合分布 <code>$P \left(Y, X\right)$</code>，而判别模型则尝试构建条件分布 <code>$P \left(Y | X\right)$</code>。</p>
<h2 id="序列标注">序列标注</h2>
<p>序列标注（Sequence Labeling）是自然语言处理中的一项重要任务，对于给定的文本序列需要给出对应的标注序列。常见的序列标注任务包含：组块分析（Chunking），词性标注（Part-of-Speech，POS）和命名实体识别（Named Entity Recognition，NER）。</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/pos-ner-demo.png" class="lazyload"/>
  
</figure>
<p>上图为一段文本的词性标注和命名实体识别的结果。</p>
<h3 id="词性标注">词性标注</h3>
<p>词性标注是指为分词结果中的每个单词标注一个正确的词性，即确定每个词是名词、动词、形容词或其他词性的过程。</p>
<p>一些常用中文标注规范如下：</p>
<ol>
<li>北京大学现代汉语语料库基本加工规范 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li>北大语料库加工规范：切分·词性标注·注音 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></li>
<li>计算所汉语词性标记集 3.0（ICTPOS 3.0）<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></li>
<li>The Part-Of-Speech Tagging Guidelines for the Penn Chinese Treebank (3.0) <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li>中文文本标注规范（微软亚洲研究院）<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></li>
</ol>
<h3 id="命名实体识别">命名实体识别</h3>
<p>命名实体识别，又称作“专名识别”，是指识别文本中具有特定意义的实体，主要包括人名、地名、机构名、专有名词等。简单的讲，就是识别自然文本中的实体指称的边界和类别。</p>
<p>常用的标注标准有 IO，BIO，BIOES，BMEWO 和 BMEWO+ 等。（参考自：<a href="https://lingpipe-blog.com/2009/10/14/coding-chunkers-as-taggers-io-bio-bmewo-and-bmewo/">Coding Chunkers as Taggers: IO, BIO, BMEWO, and BMEWO+</a>）</p>
<ol>
<li>IO 标注标准是最简单的标注方式，对于命名实体类别 X 标注为 <code>I_X</code>，其他则标注为 <code>O</code>。由于没有标签界线表示，这种方式无法表示两个相邻的同类命名实体。</li>
<li>BIO 标注标准将命名实体的起始部分标记为 <code>B_X</code>，其余部分标记为 <code>I_X</code>。</li>
<li>BIOES 标注标准将命名实体的起始部分标记为 <code>B_X</code>，中间部分标记为 <code>I_X</code>，结尾部分标记为 <code>E_X</code>，对于单个字符成为命名实体的情况标记为 <code>S_X</code>。</li>
<li>BMEWO 标注标准将命名实体的起始部分标记为 <code>B_X</code>，中间部分标记为 <code>M_X</code>，结尾部分标记为 <code>E_X</code>，对于单个字符成为命名实体的情况标记为 <code>W_X</code>。</li>
<li>BMEWO+ 标注标准在 BMEWO 的基础上针对不同情况的非命名实体标签的标注进行了扩展，同时增加了一个句外（out-of-sentence）标签 <code>W_OOS</code>，句子起始标签 <code>BB_O_OOS</code> 和句子结束标签 <code>WW_O_OOS</code>，如 <a href="http://www.alias-i.com/lingpipe/docs/api/com/aliasi/chunk/HmmChunker.html">下表</a> 所示：</li>
</ol>
<table>
  <thead>
      <tr>
          <th>标签</th>
          <th>描述</th>
          <th>可能上接的标签</th>
          <th>可能下接的标签</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>B_X</code></td>
          <td>命名实体类型 X 的起始</td>
          <td><code>E_Y, W_Y, EE_O_X, WW_O_X</code></td>
          <td><code>M_X, W_X</code></td>
      </tr>
      <tr>
          <td><code>M_X</code></td>
          <td>命名实体类型 X 的中间</td>
          <td><code>B_X, M_X</code></td>
          <td><code>M_X, W_X</code></td>
      </tr>
      <tr>
          <td><code>E_X</code></td>
          <td>命名实体类型 X 的结尾</td>
          <td><code>B_X, M_X</code></td>
          <td><code>B_Y, W_Y, BB_O_X, WW_O_X</code></td>
      </tr>
      <tr>
          <td><code>W_X</code></td>
          <td>命名实体类型 X 的单个字符</td>
          <td><code>E_Y, W_Y, EE_O_X, WW_O_X</code></td>
          <td><code>B_Y, W_Y, BB_O_X, WW_O_X</code></td>
      </tr>
      <tr>
          <td><code>BB_O_X</code></td>
          <td>非命名实体的起始，上接命名实体类型 X</td>
          <td><code>E_X, W_X</code></td>
          <td><code>MM_O, EE_O_Y</code></td>
      </tr>
      <tr>
          <td><code>MM_O</code></td>
          <td>非命名实体的中间</td>
          <td><code>BB_O_Y, MM_O</code></td>
          <td><code>MM_O, EE_O_Y</code></td>
      </tr>
      <tr>
          <td><code>EE_O_X</code></td>
          <td>非命名实体的结尾，下接命名实体类型 X</td>
          <td><code>BB_O_Y, MM_O</code></td>
          <td><code>B_X, W_X</code></td>
      </tr>
      <tr>
          <td><code>WW_O_X</code></td>
          <td>非命名实体，上接命名实体，下接命名实体类型 X</td>
          <td><code>E_X, W_X</code></td>
          <td><code>B_Y, W_Y</code></td>
      </tr>
  </tbody>
</table>
<p>不同标注标准的差别示例如下：</p>
<table>
  <thead>
      <tr>
          <th>字符</th>
          <th>IO</th>
          <th>BIO</th>
          <th>BIOES</th>
          <th>BMEWO</th>
          <th>BMEWO+</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td><code>W_OOS</code></td>
      </tr>
      <tr>
          <td>Yesterday</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>BB_O_OOS</code></td>
      </tr>
      <tr>
          <td>afternoon</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>MM_O</code></td>
      </tr>
      <tr>
          <td>,</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>EE_O_PER</code></td>
      </tr>
      <tr>
          <td>John</td>
          <td><code>I_PER</code></td>
          <td><code>B_PER</code></td>
          <td><code>B_PER</code></td>
          <td><code>B_PER</code></td>
          <td><code>B_PER</code></td>
      </tr>
      <tr>
          <td>J</td>
          <td><code>I_PER</code></td>
          <td><code>I_PER</code></td>
          <td><code>I_PER</code></td>
          <td><code>M_PER</code></td>
          <td><code>M_PER</code></td>
      </tr>
      <tr>
          <td>.</td>
          <td><code>I_PER</code></td>
          <td><code>I_PER</code></td>
          <td><code>I_PER</code></td>
          <td><code>M_PER</code></td>
          <td><code>M_PER</code></td>
      </tr>
      <tr>
          <td>Smith</td>
          <td><code>I_PER</code></td>
          <td><code>I_PER</code></td>
          <td><code>E_PER</code></td>
          <td><code>E_PER</code></td>
          <td><code>E_PER</code></td>
      </tr>
      <tr>
          <td>traveled</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>BB_O_PER</code></td>
      </tr>
      <tr>
          <td>to</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>EE_O_LOC</code></td>
      </tr>
      <tr>
          <td>Washington</td>
          <td><code>I_LOC</code></td>
          <td><code>B_LOC</code></td>
          <td><code>S_LOC</code></td>
          <td><code>W_LOC</code></td>
          <td><code>W_LOC</code></td>
      </tr>
      <tr>
          <td>.</td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>O</code></td>
          <td><code>WW_O_OOS</code></td>
      </tr>
      <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td><code>W_OOS</code></td>
      </tr>
  </tbody>
</table>
<p>不同标准的标签数量如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>标注标准</th>
          <th>标签数量</th>
          <th>N=1</th>
          <th>N=3</th>
          <th>N=20</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>IO</td>
          <td>N+1</td>
          <td>2</td>
          <td>4</td>
          <td>21</td>
      </tr>
      <tr>
          <td>BIO</td>
          <td>2N+1</td>
          <td>3</td>
          <td>7</td>
          <td>41</td>
      </tr>
      <tr>
          <td>BIOES</td>
          <td>4N+1</td>
          <td>5</td>
          <td>13</td>
          <td>81</td>
      </tr>
      <tr>
          <td>BMEWO</td>
          <td>4N+1</td>
          <td>5</td>
          <td>13</td>
          <td>81</td>
      </tr>
      <tr>
          <td>BMEWO+</td>
          <td>7N+3</td>
          <td>10</td>
          <td>24</td>
          <td>143</td>
      </tr>
  </tbody>
</table>
<p>其中，N 为命名实体类型的数量。</p>
<h4 id="bilstm-crf">BiLSTM CRF <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup></h4>
<blockquote>
<p>本小节内容参考和修改自 <a href="https://github.com/createmomo/CRF-Layer-on-the-Top-of-BiLSTM">CRF-Layer-on-the-Top-of-BiLSTM</a>。</p>
</blockquote>
<p>Huang 等人提出了一种基于 BiLSTM 和 CRF 的神经网络模型用于序例标注。整个网络如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/bilstm-crf.png" class="lazyload"/>
  
</figure>
<p>关于模型中的 BiLSTM 部分在此不过多赘述，相关细节可以参见之前的博客：<a href="/cn/2018/09/rnn/">循环神经网络 (Recurrent Neural Network, RNN)</a> 和 <a href="/cn/2020/03/pre-trained-model-for-nlp/">预训练自然语言模型 (Pre-trained Models for NLP)</a>。BiLSTM-CRF 模型的输入是词嵌入向量，输出是对应的预测标注标签，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/bilstm-crf-1.png" class="lazyload"/>
  
</figure>
<p>BiLSTM 层的输出为每个标签的分数，对于 <code>$w_0$</code>，BiLSTM 的输出为 1.5 (<code>B_PER</code>)，0.9 (<code>I_PER</code>)，0.1 (<code>B_ORG</code>)，0.08 (<code>I_ORG</code>) 和 0.05 (<code>O</code>)，这些分数为 CRF 层的输入，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/bilstm-crf-2.png" class="lazyload"/>
  
</figure>
<p>经过 CRF 层后，具有最高分数的预测序列被选择为最优预测结果。如果没有 CRF 层，我们可以直接选择 BiLSTM 层输出分数的最大值对应的序列为预测结果。例如，对于 <code>$w_0$</code>，最高分数为 1.5，对应的预测标签则为 <code>B_PER</code>，类似的 <code>$w_1, w_2, w_3, w_4$</code> 对应的预测标签为 <code>I_PER, O, B_ORG, O</code>，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/bilstm-crf-3.png" class="lazyload"/>
  
</figure>
<p>虽然我们在上例中得到了正确的结果，但通常情况下并非如此。对于如下的示例，预测结果为 <code>I_ORG, I_PER, O, I_ORG, I_PER</code>，这显然是不正确的。</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/bilstm-crf-4.png" class="lazyload"/>
  
</figure>
<p>CRF 层在进行预测时可以添加一些约束，这些约束可以在训练时被 CRF 层学习得到。可能的约束有：</p>
<ul>
<li>句子的第一个词的标签可以是 <code>B_X</code> 或 <code>O</code>，而非 <code>I_X</code>。</li>
<li><code>B_X, I_X</code> 是有效的标签，而 <code>B_X, I_Y</code> 是无效的标签。</li>
<li>一个命名实体的起始标签应为 <code>B_X</code> 而非 <code>I_X</code>。</li>
</ul>
<p>CRF 层的损失包含两部分，这两部分构成了 CRF 层的关键：</p>
<ul>
<li>发射分数（Emission Score）</li>
</ul>
<p>发射分数即为 BiLSTM 层的输出分数，例如 <code>$w_0$</code> 对应的标签 <code>B_PER</code> 的分数为 1.5。为了方便起见，对于每类标签给定一个索引：</p>
<table>
  <thead>
      <tr>
          <th>标签</th>
          <th>索引</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>B_PER</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td><code>I_PER</code></td>
          <td>1</td>
      </tr>
      <tr>
          <td><code>B_ORG</code></td>
          <td>2</td>
      </tr>
      <tr>
          <td><code>I_ORG</code></td>
          <td>3</td>
      </tr>
      <tr>
          <td><code>O</code></td>
          <td>4</td>
      </tr>
  </tbody>
</table>
<p>我们利用 <code>$x_{i y_{j}}$</code> 表示发射分数，<code>$i$</code> 为词的索引，<code>$y_i$</code> 为标注标签的索引。例如：<code>$x_{i=1, y_{j}=2} = x_{w_1, \text{B_ORG}} = 0.1$</code>，表示 <code>$w_1$</code> 为 <code>B_ORG</code> 的分数为 0.1。</p>
<ul>
<li>转移分数（Transition Score）</li>
</ul>
<p>我们利用 <code>$t_{y_i, y_j}$</code> 表示转移分数，例如 <code>$t_{\text{B_PER}, \text{I_PER}} = 0.9$</code> 表示由标签 <code>B_PER</code> 转移到 <code>I_PER</code> 的分数为 0.9。因此，需要一个转移分数矩阵用于存储所有标注标签之间的转移分数。为了使得转移分数矩阵更加鲁棒，需要添加两个标签 <code>START</code> 和 <code>END</code>，分别表示一个句子的开始和结束。下表为一个转移分数矩阵的示例：</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><code>START</code></th>
          <th><code>B-PER</code></th>
          <th><code>I-PER</code></th>
          <th><code>B-ORG</code></th>
          <th><code>I-ORG</code></th>
          <th><code>O</code></th>
          <th><code>END</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>START</code></td>
          <td>0</td>
          <td>0.8</td>
          <td>0.007</td>
          <td>0.7</td>
          <td>0.0008</td>
          <td>0.9</td>
          <td>0.08</td>
      </tr>
      <tr>
          <td><code>B_PER</code></td>
          <td>0</td>
          <td>0.6</td>
          <td>0.9</td>
          <td>0.2</td>
          <td>0.0006</td>
          <td>0.6</td>
          <td>0.009</td>
      </tr>
      <tr>
          <td><code>I_PER</code></td>
          <td>-1</td>
          <td>0.5</td>
          <td>0.53</td>
          <td>0.55</td>
          <td>0.0003</td>
          <td>0.85</td>
          <td>0.008</td>
      </tr>
      <tr>
          <td><code>B_ORG</code></td>
          <td>0.9</td>
          <td>0.5</td>
          <td>0.0003</td>
          <td>0.25</td>
          <td>0.8</td>
          <td>0.77</td>
          <td>0.006</td>
      </tr>
      <tr>
          <td><code>I_ORG</code></td>
          <td>-0.9</td>
          <td>0.45</td>
          <td>0.007</td>
          <td>0.7</td>
          <td>0.65</td>
          <td>0.76</td>
          <td>0.2</td>
      </tr>
      <tr>
          <td><code>O</code></td>
          <td>0</td>
          <td>0.65</td>
          <td>0.0007</td>
          <td>0.7</td>
          <td>0.0008</td>
          <td>0.9</td>
          <td>0.08</td>
      </tr>
      <tr>
          <td><code>END</code></td>
          <td>0</td>
          <td>0</td>
          <td>0</td>
          <td>0</td>
          <td>0</td>
          <td>0</td>
          <td>0</td>
      </tr>
  </tbody>
</table>
<p>转移分数矩阵作为 BiLSTM-CRF 模型的一个参数，随机初始化并通过模型的训练不断更新，最终学习得到约束条件。</p>
<p>CRF 层的损失函数包含两个部分：真实路径分数和所有可能路径的总分数。假设每个可能的路径有一个分数 <code>$P_i$</code>，共 <code>$N$</code> 种可能的路径，所有路径的总分数为：</p>
<p><code>$$ P_{\text {total}}=P_{1}+P_{2}+\ldots+P_{N}=e^{S_{1}}+e^{S_{2}}+\ldots+e^{S_{N}} $$</code></p>
<p>则损失函数定义为：</p>
<p><code>$$ \text{Loss} = \dfrac{P_{\text{RealPath}}}{\sum_{i=1}^{N} P_i} $$</code></p>
<p>对于 <code>$S_i$</code>，共包含两部分：发射分数和转移分数。以路径 <code>START -&gt; B_PER -&gt; I_PER -&gt; O -&gt; B_ORG -&gt; O -&gt; END</code> 为例，发射分数为：</p>
<p><code>$$ \begin{aligned} \text{EmissionScore} = \ &amp;x_{0, \text{START}} + x_{1, \text{B_PER}} + x_{2, \text{I_PER}} \\ &amp;+ x_{3, \text{O}} + x_{4, \text{B_ORG}} + x_{5, \text{O}} + x_{6, \text{END}} \end{aligned} $$</code></p>
<p>其中 <code>$x_{i, y_j}$</code> 表示第 <code>$i$</code> 个词标签为 <code>$y_j$</code> 的分数，为 BiLSTM 的输出，<code>$x_{0, \text{START}}$</code> 和 <code>$x_{6, \text{END}}$</code> 可以设置为 0。转换分数为：</p>
<p><code>$$ \begin{aligned} \text{TransitionScore} = \ &amp;t_{\text{START}, \text{B_PER}} + t_{\text{B_PER}, \text{I_PER}} + t_{\text{I_PER}, \text{O}} \\ &amp;+ t_{\text{O}, \text{B_ORG}} + t_{\text{B_ORG}, \text{O}} + t_{\text{O}, \text{END}} \end{aligned} $$</code></p>
<p>其中 <code>$t_{y_i, y_j}$</code> 表示标注标签由 <code>$y_i$</code> 转移至 <code>$y_j$</code> 的分数。</p>
<p>对于所有路径的总分数的计算过程采用了类似 <a href="/cn/2018/11/computational-complexity-and-dynamic-programming/">动态规划</a> 的思想，整个过程计算比较复杂，在此不再详细展开，详细请参见参考文章。</p>
<p>利用训练好的 BiLSTM-CRF 模型进行预测时，首先我们可以得到序列的发射分数和转移分数，其次用维特比算法可以得到最终的预测标注序列。</p>
<h4 id="lattice-lstm">Lattice LSTM <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup></h4>
<p>Zhang 等人针对中文提出了一种基于 Lattice LSTM 的命名实体识别方法，Lattice LSTM 的结构如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-05-02-hmm-and-crf/lattice-lstm.png" class="lazyload"/>
  
</figure>
<p>模型的基本思想是将句子中的词汇（例如：南京，长江大桥等）信息融入到基于字符的 LSTM 模型中，从而可以显性地利用词汇信息。</p>
<p>模型的输入为一个字符序列 <code>$c_1, c_2, \cdots, c_m$</code> 和词汇表 <code>$\mathbb{D}$</code> 中所有匹配的字符子序列，其中词汇表 <code>$\mathbb{D}$</code> 利用大量的原始文本通过分词构建。令 <code>$w_{b, e}^d$</code> 表示有以第 <code>$b$</code> 个字符起始，以第 <code>$e$</code> 个字符结尾的子序列，例如：<code>$w_{1,2}^d$</code> 表示“南京
”，<code>$w_{7,8}^d$</code> 表示“大桥”。</p>
<p>不同于一般的字符级模型，LSTM 单元的状态考虑了句子中的子序列 <code>$w_{b,e}^d$</code>，每个子序列 <code>$w_{b,e}^d$</code> 表示为：</p>
<p><code>$$ \mathbf{x}_{b, e}^{w}=\mathbf{e}^{w}\left(w_{b, e}^{d}\right) $$</code></p>
<p>其中，<code>$\mathbf{e}^{w}$</code> 为词向量查询表。一个词单元 <code>$\mathbf{c}_{b,e}^w$</code> 用于表示 <code>$\mathbf{x}_{b,e}^w$</code> 的循环状态：</p>
<p><code>$$ \begin{aligned} \left[\begin{array}{c} \mathbf{i}_{b, e}^{w} \\ \mathbf{f}_{b, e}^{w} \\ \widetilde{c}_{b, e}^{w} \end{array}\right] &amp;=\left[\begin{array}{c} \sigma \\ \sigma \\ \tanh \end{array}\right]\left(\mathbf{W}^{w \top}\left[\begin{array}{c} \mathbf{x}_{b, e}^{w} \\ \mathbf{h}_{b}^{c} \end{array}\right]+\mathbf{b}^{w}\right) \\ \mathbf{c}_{b, e}^{w} &amp;=\mathbf{f}_{b, e}^{w} \odot \mathbf{c}_{b}^{c}+\mathbf{i}_{b, e}^{w} \odot \widetilde{c}_{b, e}^{w} \end{aligned} $$</code></p>
<p>其中，<code>$\mathbf{i}_{b, e}^{w}$</code> 和 <code>$\mathbf{f}_{b, e}^{w}$</code> 分别为输入门和遗忘门。由于仅在字符级别上进行标注，因此对于词单元来说没有输出门。</p>
<p>对于 <code>$\mathbf{c}_{j}^c$</code> 来说可能有多条信息流，例如 <code>$\mathbf{c}_7^c$</code> 的输入包括 <code>$\mathbf{x}_7^c$</code>（桥），<code>$\mathbf{c}_{6,7}^w$</code>（大桥）和 <code>$\mathbf{c}_{4,7}^w$</code>（长江大桥）。论文采用了一个新的门 <code>$\mathbf{i}_{b,e}^c$</code> 来控制所有子序列单元 <code>$\mathbf{c}_{b,e}^w$</code> 对 <code>$\mathbf{c}_{j}^c$</code> 的贡献：</p>
<p><code>$$ \mathbf{i}_{b, e}^{c}=\sigma\left(\mathbf{W}^{l \top}\left[\begin{array}{c} \mathbf{x}_{e}^{c} \\ \mathbf{c}_{b, e}^{w} \end{array}\right]+\mathbf{b}^{l}\right) $$</code></p>
<p>则单元状态 <code>$\mathbf{c}_j^c$</code> 的计算变为：</p>
<p><code>$$ \mathbf{c}_{j}^{c}=\sum_{b \in\left\{b^{\prime} | w_{b^{\prime}, j} \in \mathbb{D}\right\}} \boldsymbol{\alpha}_{b, j}^{c} \odot \boldsymbol{c}_{b, j}^{w}+\boldsymbol{\alpha}_{j}^{c} \odot \widetilde{\boldsymbol{c}}_{j}^{c} $$</code></p>
<p>在上式中，<code>$\mathbf{i}_{b,j}^c$</code> 和 <code>$\mathbf{i}_j^c$</code> 标准化为 <code>$\boldsymbol{\alpha}_{b, j}^{c}$</code> 和 <code>$\boldsymbol{\alpha}_{j}^{c}$</code>：</p>
<p><code>$$ \begin{aligned} \boldsymbol{\alpha}_{b, j}^{c} &amp;=\frac{\exp \left(\mathbf{i}_{b, j}^{c}\right)}{\exp \left(\mathbf{i}_{j}^{c}\right)+\sum_{b^{\prime} \in\left\{b^{\prime \prime} | w_{b^{\prime \prime}, j}^{d} \in \mathbb{D}\right\}} \exp \left(\mathbf{i}_{b^{\prime}, j}^{c}\right)} \\ \boldsymbol{\alpha}_{j}^{c} &amp;=\frac{\exp \left(\mathbf{i}_{j}^{c}\right)}{\exp \left(\mathbf{i}_{j}^{c}\right)+\sum_{b^{\prime} \in\left\{b^{\prime \prime} | w_{b^{\prime \prime}, j}^{d} \in \mathbb{D}\right\}} \exp \left(\mathbf{i}_{b^{\prime}, j}^{c}\right)} \end{aligned} $$</code></p>
<h2 id="开放资源">开放资源</h2>
<h3 id="标注工具">标注工具</h3>
<ol>
<li><a href="https://github.com/synyi/poplar">synyi/poplar</a></li>
<li><a href="https://github.com/nlplab/brat">nlplab/brat</a></li>
<li><a href="https://github.com/doccano/doccano">doccano/doccano</a></li>
<li><a href="https://github.com/heartexlabs/label-studio">heartexlabs/label-studio</a></li>
<li><a href="https://github.com/deepwel/Chinese-Annotator">deepwel/Chinese-Annotator</a></li>
<li><a href="https://github.com/jiesutd/YEDDA">jiesutd/YEDDA</a></li>
</ol>
<h3 id="开源模型-框架和代码">开源模型，框架和代码</h3>
<ol>
<li><a href="https://github.com/pytorch/text">pytorch/text</a></li>
<li><a href="https://github.com/flairNLP/flair">flairNLP/flair</a></li>
<li><a href="https://github.com/PetrochukM/PyTorch-NLP">PetrochukM/PyTorch-NLP</a></li>
<li><a href="https://github.com/allenai/allennlp">allenai/allennlp</a></li>
<li><a href="https://github.com/fastnlp/fastNLP">fastnlp/fastNLP</a></li>
<li><a href="https://stanfordnlp.github.io/CoreNLP/index.html">Stanford CoreNLP</a></li>
<li><a href="http://neuroner.com/">NeuroNER</a></li>
<li><a href="https://spacy.io/">spaCy</a></li>
<li><a href="https://www.nltk.org/">NLTK</a></li>
<li><a href="https://github.com/BrikerMan/Kashgari">BrikerMan/Kashgari</a></li>
<li><a href="https://github.com/Hironsan/anago">Hironsan/anago</a></li>
<li><a href="https://github.com/crownpku/Information-Extraction-Chinese">crownpku/Information-Extraction-Chinese</a></li>
<li><a href="https://github.com/thunlp/OpenNRE">thunlp/OpenNRE</a></li>
<li><a href="https://github.com/hankcs/HanLP">hankcs/HanLP</a></li>
<li><a href="https://github.com/jiesutd/NCRFpp">jiesutd/NCRFpp</a></li>
</ol>
<h3 id="其他资源">其他资源</h3>
<ol>
<li><a href="https://github.com/keon/awesome-nlp">keon/awesome-nlp</a></li>
<li><a href="https://github.com/crownpku/Awesome-Chinese-NLP">crownpku/Awesome-Chinese-NLP</a></li>
<li><a href="https://github.com/sebastianruder/NLP-progress">sebastianruder/NLP-progress</a></li>
<li><a href="https://github.com/thunlp/NREPapers">thunlp/NREPapers</a></li>
</ol>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>李航. (2019). <em>统计学习方法（第二版）</em>. 清华大学出版社.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>俞士汶, 段慧明, 朱学锋, &amp; 孙斌. (2002). 北京大学现代汉语语料库基本加工规范. <em>中文信息学报</em>, 16(5), 51-66.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>俞士汶, 段慧明, 朱学锋, 孙斌, &amp; 常宝宝. (2003). 北大语料库加工规范: 切分· 词性标注· 注音. <em>汉语语言与计算学报</em>, 13(2), 121-158.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="http://ictclas.nlpir.org/nlpir/html/readme.htm">http://ictclas.nlpir.org/nlpir/html/readme.htm</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Xia, F. (2000). The part-of-speech tagging guidelines for the Penn Chinese Treebank (3.0). <em>IRCS Technical Reports Series</em>, 38.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Huang, C. N., Li, Y., &amp; Zhu, X. (2006). Tokenization guidelines of Chinese text (v5.0, in Chinese). <em>Microsoft Research Asia</em>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Huang, Z., Xu, W., &amp; Yu, K. (2015). Bidirectional LSTM-CRF models for sequence tagging. <em>arXiv preprint arXiv:1508.01991</em>.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Zhang, Y., &amp; Yang, J. (2018). Chinese NER Using Lattice LSTM. In <em>Proceedings of the 56th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers)</em> (pp. 1554-1564).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>图嵌入 (Graph Embedding) 和图神经网络 (Graph Neural Network)</title><link>https://zeqiang.fun/cn/2020/04/graph-embedding-and-gnn/</link><pubDate>Sat, 11 Apr 2020 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2020/04/graph-embedding-and-gnn/</guid><description><![CDATA[
        <p>图（Graph / Network）数据类型可以自然地表达物体和物体之间的联系，在我们的日常生活与工作中无处不在。例如：微信和新浪微博等构成了人与人之间的社交网络；互联网上成千上万个页面构成了网页链接网络；国家城市间的运输交通构成了物流网络。</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/graph.png" class="lazyload"/>
  <figcaption><p class="figcaption">图片来源：<a href="https://www.allthingsdistributed.com/2019/12/power-of-relationships.html">The power of relationships in data</a></p></figcaption>
</figure>
<p>通常定义一个图 <code>$G = \left(V, E\right)$</code>，其中 <code>$V$</code> 为<strong>顶点（Vertices）<strong>集合，<code>$E$</code> 为</strong>边（Edges）<strong>集合。对于一条边 <code>$e = u, v$</code> 包含两个</strong>端点（Endpoints）</strong> <code>$u$</code> 和 <code>$v$</code>，同时 <code>$u$</code> 可以称为 <code>$v$</code> 的<strong>邻居（Neighbor）</strong>。当所有的边为有向边时，图称之为<strong>有向（Directed）<strong>图，当所有边为无向边时，图称之为</strong>无向（Undirected）<strong>图。对于一个顶点 <code>$v$</code>，令 <code>$d \left(v\right)$</code> 表示连接的边的数量，称之为</strong>度（Degree）</strong>。对于一个图 <code>$G = \left(V, E\right)$</code>，其<strong>邻接矩阵（Adjacency Matrix）</strong> <code>$A \in \mathbb{A}^{|V| \times |V|}$</code> 定义为：</p>
<p><code>$$ A_{i j}=\left\{\begin{array}{ll} 1 &amp; \text { if }\left\{v_{i}, v_{j}\right\} \in E \text { and } i \neq j \\ 0 &amp; \text { otherwise } \end{array}\right. $$</code></p>
<p>作为一个典型的非欧式数据，对于图数据的分析主要集中在节点分类，链接预测和聚类等。对于图数据而言，**图嵌入（Graph / Network Embedding）<strong>和</strong>图神经网络（Graph Neural Networks, GNN）**是两个类似的研究领域。图嵌入旨在将图的节点表示成一个低维向量空间，同时保留网络的拓扑结构和节点信息，以便在后续的图分析任务中可以直接使用现有的机器学习算法。一些基于深度学习的图嵌入同时也属于图神经网络，例如一些基于图自编码器和利用无监督学习的图卷积神经网络等。下图描述了图嵌入和图神经网络之间的差异：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/graph-embedding-vs-graph-neural-networks.png" class="lazyload"/>
  
</figure>
<div class="blockquote" style='border-left: 4px solid #F66E40;'>本文中<strong>图嵌入</strong>和<strong>网络表示学习</strong>均表示 Graph / Network Embedding。</div>
<h2 id="图嵌入">图嵌入</h2>
<blockquote>
<p>本节内容主要参考自：<br>
A Comprehensive Survey of Graph Embedding: Problems, Techniques and Applications <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><br>
Graph Embedding Techniques, Applications, and Performance: A Survey <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup><br>
Representation Learning on Graphs: Methods and Applications <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
</blockquote>
<p>使用邻接矩阵的网络表示存在计算效率的问题，邻接矩阵 <code>$A$</code> 使用 <code>$|V| \times |V|$</code> 的存储空间表示一个图，随着节点个数的增长，这种表示所需的空间成指数增长。同时，在邻接矩阵中绝大多数是 0，数据的稀疏性使得快速有效的学习方式很难被应用。</p>
<p>网路表示学习是指学习得到网络中节点的低维向量表示，形式化地，网络表示学习的目标是对每个节点 <code>$v \in V$</code> 学习一个实值向量 <code>$R_v \in \mathbb{R}^k$</code>，其中 <code>$k \ll |V|$</code> 表示向量的维度。经典的 Zachary&rsquo;s karate club 网络的嵌入可视化如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/karate-graph-embedding.png" class="lazyload"/>
  
</figure>
<h3 id="random-walk">Random Walk</h3>
<p>基于随机游走的图嵌入通过使得图上一个短距的随机游走中共现的节点具有更相似的表示的方式来优化节点的嵌入。</p>
<h4 id="deepwalk">DeepWalk</h4>
<p>DeepWalk <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> 算法主要包含两个部分：一个随机游走序列生成器和一个更新过程。随机游走序列生成器首先在图 <code>$G$</code> 中均匀地随机抽样一个随机游走 <code>$\mathcal{W}_{v_i}$</code> 的根节点 <code>$v_i$</code>，接着从节点的邻居中均匀地随机抽样一个节点直到达到设定的最大长度 <code>$t$</code>。对于一个生成的以 <code>$v_i$</code> 为中心左右窗口为 <code>$w$</code> 的随机游走序列 <code>$v_{i-w}, \dotsc, v_{i-1}, v_i, v_{i+1}, \dotsc, v_{i+m}$</code>，DeepWalk 利用 SkipGram 算法通过最大化以 <code>$v_i$</code> 为中心，左右 <code>$w$</code> 为窗口的同其他节点共现概率来优化模型：</p>
<p><code>$$ \text{Pr} \left(\left\{v_{i-w}, \dotsc, v_{i+w}\right\} \setminus v_i \mid \Phi \left(v_i\right)\right) = \prod_{j=i-w, j \neq i}^{i+w} \text{Pr} \left(v_j \mid \Phi \left(v_i\right)\right) $$</code></p>
<p>DeepWalk 和 Word2Vec 的类比如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>目标</th>
          <th>输入</th>
          <th>输出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Word2Vec</td>
          <td>词</td>
          <td>句子</td>
          <td>词嵌入</td>
      </tr>
      <tr>
          <td>DeepWalk</td>
          <td>节点</td>
          <td>节点序列</td>
          <td>节点嵌入</td>
      </tr>
  </tbody>
</table>
<h4 id="node2vec">node2vec</h4>
<p>node2vec <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> 通过改变随机游走序列生成的方式进一步扩展了 DeepWalk 算法。DeepWalk 选取随机游走序列中下一个节点的方式是均匀随机分布的，而 node2vec 通过引入两个参数 <code>$p$</code> 和 <code>$q$</code>，将<strong>宽度优先搜索</strong>和<strong>深度优先搜索</strong>引入了随机游走序列的生成过程。 宽度优先搜索注重邻近的节点并刻画了相对局部的一种网络表示， 宽度优先中的节点一般会出现很多次，从而降低刻画中心节点的邻居节点的方差， 深度优先搜索反映了更高层面上的节点之间的同质性。</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/node2vec.png" class="lazyload"/>
  
</figure>
<p>node2vec 中的两个参数 <code>$p$</code> 和 <code>$q$</code> 控制随机游走序列的跳转概率。假设上一步游走的边为 <code>$\left(t, v\right)$</code>， 那么对于节点 <code>$v$</code> 的不同邻居，node2vec 根据 <code>$p$</code> 和 <code>$q$</code> 定义了不同的邻居的跳转概率，<code>$p$</code> 控制跳向上一个节点的邻居的概率，<code>$q$</code> 控制跳向上一个节点的非邻居的概率，具体的未归一的跳转概率值 <code>$\pi_{vx} = \alpha_{pq} \left(t, x\right)$</code> 如下所示：</p>
<p><code>$$ \alpha_{p q}(t, x)=\left\{\begin{array}{cl} \dfrac{1}{p}, &amp; \text { if } d_{t x}=0 \\ 1, &amp; \text { if } d_{t x}=1 \\ \dfrac{1}{q}, &amp; \text { if } d_{t x}=2 \end{array}\right. $$</code></p>
<p>其中，<code>$d_{tx}$</code> 表示节点 <code>$t$</code> 和 <code>$x$</code> 之间的最短距离。为了获得最优的超参数 <code>$p$</code> 和 <code>$q$</code> 的取值，node2vec 通过半监督形式，利用网格搜索最合适的参数学习节点表示。</p>
<h4 id="app">APP</h4>
<p>之前的基于随机游走的图嵌入方法，例如：DeepWalk，node2vec 等，都无法保留图中的非对称信息。然而非对称性在很多问题，例如：社交网络中的链路预测、电商中的推荐等，中至关重要。在有向图和无向图中，非对称性如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/asymmetric-proximity.png" class="lazyload"/>
  
</figure>
<p>为了保留图的非对称性，对于每个节点 <code>$v$</code> 设置两个不同的角色：源和目标，分别用 <code>$\overrightarrow{s_{v}}$</code> 和 <code>$\overrightarrow{t_{v}}$</code> 表示。对于每个从 <code>$u$</code> 开始以 <code>$v$</code> 结尾的采样序列，利用 <code>$(u, v)$</code> 表示采样的节点对。则利用源节点 <code>$u$</code> 预测目标节点 <code>$v$</code> 的概率如下：</p>
<p><code>$$ p(v | u)=\frac{\exp (\overrightarrow{s_{u}} \cdot \overrightarrow{t_{v}})}{\sum_{n \in V} \exp (\overrightarrow{s_{u}} \cdot \overrightarrow{t_{n}})} $$</code></p>
<p>通过 Skip-Gram 和负采样对模型进行优化，损失函数如下：</p>
<p><code>$$ \begin{aligned} \ell &amp;= \log \sigma(\overrightarrow{s_{u}} \cdot \overrightarrow{t_{v}})+k \cdot E_{t_{n} \sim P_{D}}[\log \sigma(-\overrightarrow{s_{u}} \cdot \overrightarrow{t_{n}})] \\ &amp;= \sum_{u} \sum_{v} \# \text {Sampled}_{u}(v) \cdot \left(\log \sigma(\overrightarrow{s_{u}} \cdot \overrightarrow{t_{v}}) + k \cdot E_{t_{n} \sim P_{D}}[\log \sigma(-\overrightarrow{s_{u}} \cdot \overrightarrow{t_{n}})]\right) \end{aligned} $$</code></p>
<p>其中，我们根据分布 <code>$P_D \left(n\right) \sim \dfrac{1}{|V|}$</code> 随机负采样 <code>$k$</code> 个节点对，<code>$\# \text{Sampled}_{u}(v)$</code> 为采样的 <code>$\left(u, v\right)$</code> 对的个数，<code>$\sigma$</code> 为 sigmoid 函数。通常情况下，<code>$\# \text{Sampled}_{u}(v) \neq \# \text{Sampled}_{v}(u)$</code>，即 <code>$\left(u, v\right)$</code> 和 <code>$\left(v, u\right)$</code> 的观测数量是不同的。模型利用 Monte-Carlo End-Point 采样方法 <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 随机的以 <code>$v$</code> 为起点和 <code>$\alpha$</code> 为停止概率采样 <code>$p$</code> 条路径。这种采样方式可以用于估计任意一个节点对之间的 Rooted PageRank <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 值，模型利用这个值估计由 <code>$v$</code> 到达 <code>$u$</code> 的概率。</p>
<h3 id="matrix-fractorization">Matrix Fractorization</h3>
<h4 id="grarep">GraRep</h4>
<p>GraRep <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> 提出了一种基于矩阵分解的图嵌入方法。对于一个图 <code>$G$</code>，利用邻接矩阵 <code>$S$</code> 定义图的度矩阵：</p>
<p><code>$$ D_{i j}=\left\{\begin{array}{ll} \sum_{p} S_{i p}, &amp; \text { if } i=j \\ 0, &amp; \text { if } i \neq j \end{array}\right. $$</code></p>
<p>则一阶转移概率矩阵定义如下：</p>
<p><code>$$ A = D^{-1} S $$</code></p>
<p>其中，<code>$A_{i, j}$</code> 表示通过一步由 <code>$v_i$</code> 转移到 <code>$v_j$</code> 的概率。所谓的全局特征包含两个部分：</p>
<ol>
<li>捕获两个节点之间的长距离特征</li>
<li>分别考虑按照不同转移步数的连接</li>
</ol>
<p>下图展示了 <code>$k = 1, 2, 3, 4$</code> 情况下的强（上）弱（下）关系：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/grarep.png" class="lazyload"/>
  
</figure>
<p>利用 Skip-Gram 和 NCE（noise contrastive estimation）方法，对于一个 <code>$k$</code> 阶转移，可以将模型归结到一个矩阵 <code>$Y_{i, j}^k$</code> 的分解问题：</p>
<p><code>$$ Y_{i, j}^{k}=W_{i}^{k} \cdot C_{j}^{k}=\log \left(\frac{A_{i, j}^{k}}{\sum_{t} A_{t, j}^{k}}\right)-\log (\beta) $$</code></p>
<p>其中，<code>$W$</code> 和 <code>$C$</code> 的每一行分别为节点 <code>$w$</code> 和 <code>$c$</code> 的表示，<code>$\beta = \lambda / N$</code>，<code>$\lambda$</code> 为负采样的数量，<code>$N$</code> 为图中边的个数。</p>
<p>之后为了减少噪音，模型将 <code>$Y^k$</code> 中所有的负值替换为 0，通过 SVD（方法详情见参见<a href="/cn/2017/12/evd-svd-and-pca/">之前博客</a>）得到节点的 <code>$d$</code> 维表示：</p>
<p><code>$$ \begin{aligned} X_{i, j}^{k} &amp;= \max \left(Y_{i, j}^{k}, 0\right) \\ X^{k} &amp;= U^{k} \Sigma^{k}\left(V^{k}\right)^{T} \\ X^{k} \approx X_{d}^{k} &amp;= U_{d}^{k} \Sigma_{d}^{k}\left(V_{d}^{k}\right)^{T} \\ X^{k} \approx X_{d}^{k} &amp;= W^{k} C^{k} \\ W^{k} &amp;= U_{d}^{k}\left(\Sigma_{d}^{k}\right)^{\frac{1}{2}} \\ C^{k} &amp;= \left(\Sigma_{d}^{k}\right)^{\frac{1}{2}} V_{d}^{k T} \end{aligned} $$</code></p>
<p>最终，通过对不同 <code>$k$</code> 的表示进行拼接得到节点最终的表示。</p>
<h4 id="hope">HOPE</h4>
<p>HOPE <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 对于每个节点最终生成两个嵌入表示：一个是作为源节点的嵌入表示，另一个是作为目标节点的嵌入表示。模型通过近似高阶相似性来保留非对称传递性，其优化目标为：</p>
<p><code>$$ \min \left\|\mathbf{S}-\mathbf{U}^{s} \cdot \mathbf{U}^{t^{\top}}\right\|_{F}^{2} $$</code></p>
<p>其中，<code>$\mathbf{S}$</code> 为相似矩阵，<code>$\mathbf{U}^s$</code> 和 <code>$\mathbf{U}^t$</code> 分别为源节点和目标节点的向量表示。下图展示了嵌入向量可以很好的保留非对称传递性：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/hope.png" class="lazyload"/>
  
</figure>
<p>对于 <code>$\mathbf{S}$</code> 有多种可选近似度量方法：Katz Index，Rooted PageRank（RPR），Common Neighbors（CN），Adamic-Adar（AA）。这些度量方法可以分为两类：全局近似（Katz Index 和 RPR）和局部近似（CN 和 AA）。</p>
<p>算法采用了一个广义 SVD 算法（JDGSVD）来解决使用原始 SVD 算法计算复杂度为<code>$O \left(N^3\right)$</code> 过高的问题，从而使得算法可以应用在更大规模的图上。</p>
<h3 id="meta-paths">Meta Paths</h3>
<h4 id="matapath2vec">matapath2vec</h4>
<p>matapath2vec <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> 提出了一种基于元路径的异构网络表示学习方法。在此我们引入 3 个定义：</p>
<ol>
<li>**异构网络（(Heterogeneous information network，HIN）**可以定义为一个有向图 <code>$G = \left(V, E\right)$</code>，一个节点类型映射 <code>$\tau: V \to A$</code> 和一个边类型映射 <code>$\phi: E \to R$</code>，其中对于 <code>$v \in V$</code> 有 <code>$\tau \left(v\right) \in A$</code>，<code>$e \in E$</code> 有 <code>$\phi \left(e\right) \in R$</code>，且 <code>$|A| + |R| &gt; 1$</code>。</li>
<li>**网络模式（Network schema）**定义为 <code>$T_G = \left(A, R\right)$</code>，为一个包含节点类型映射 <code>$\tau \left(v\right) \in A$</code> 和边映射 <code>$\phi \left(e\right) \in R$</code> 异构网络的 <code>$G = \left(V, E\right)$</code> 的元模板。</li>
<li>**元路径（Meta-path）**定义为网络模式 <code>$T_G = \left(A, R\right)$</code> 上的一条路径 <code>$P$</code>，形式为 <code>$A_{1} \stackrel{R_{1}}{\longrightarrow} A_{2} \stackrel{R_{2}}{\longrightarrow} \cdots \stackrel{R_{l}}{\longrightarrow} A_{l+1}$</code>。</li>
</ol>
<p>下图展示了一个学术网络和部分元路径：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/metapaths.png" class="lazyload"/>
  
</figure>
<p>其中，APA 表示一篇论文的共同作者，APVPA 表示两个作者在同一个地方发表过论文。</p>
<p>metapath2vec 采用了基于元路径的随机游走来生成采样序列，这样就可以保留原始网络中的语义信息。对于一个给定的元路径模板 <code>$P: A_{1} \stackrel{R_{1}}{\longrightarrow} A_{2} \stackrel{R_{2}}{\longrightarrow} \cdots A_{t} \stackrel{R_{t}}{\longrightarrow} A_{t+1} \cdots \stackrel{R_{l}}{\longrightarrow} A_{l}$</code>，第 <code>$i$</code> 步的转移概率为：</p>
<p><code>$$ p\left(v^{i+1} | v_{t}^{i}, P\right)=\left\{\begin{array}{ll} \dfrac{1}{\left|N_{t+1}\left(v_{t}^{i}\right)\right|} &amp; \left(v_{t}^{i}, v^{i+1}\right) \in E, \phi\left(v^{i+1}\right)=A_{t+1} \\ 0 &amp; \left(v_{t}^{i}, v^{i+1}\right) \in E, \phi\left(v^{i+1}\right) \neq A_{t+1} \\ 0 &amp; \left(v_{t}^{i}, v^{i+1}\right) \notin E \end{array}\right. $$</code></p>
<p>其中，<code>$v^i_t \in A_t$</code>，<code>$N_{t+1} \left(v^i_t\right)$</code> 表示节点 <code>$v^i_t$</code> 类型为 <code>$A_{t+1}$</code> 的邻居。之后，则采用了类似 DeepWalk 的方式进行训练得到节点表示。</p>
<h4 id="hin2vec">HIN2Vec</h4>
<p>HIN2Vec <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup> 提出了一种利用多任务学习通过多种关系进行节点和元路径表示学习的方法。模型最初是希望通过一个多分类模型来预测任意两个节点之间所有可能的关系。假设对于任意两个节点，所有可能的关系集合为 <code>$R = \{\text{P-P, P-A, A-P, P-P-P, P-P-A, P-A-P, A-P-P, A-P-A}\}$</code>。假设一个实例 <code>$P_1$</code> 和 <code>$A_1$</code> 包含两种关系：<code>$\text{P-A}$</code> 和 <code>$\text{P-P-A}$</code>，则对应的训练数据为 <code>$\langle x: P_1, y: A_1, output: \left[0, 1, 0, 0, 1, 0, 0, 0\right] \rangle$</code>。</p>
<p>但实际上，扫描整个网络寻找所有可能的关系是不现实的，因此 HIN2Vec 将问题简化为一个给定两个节点判断之间是否存在一个关系的二分类问题，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/hin2vec.png" class="lazyload"/>
  
</figure>
<p>模型的三个输入分别为节点 <code>$x$</code> 和 <code>$y$</code>，以及关系 <code>$r$</code>。在隐含层输入被转换为向量 <code>$W_{X}^{\prime} \vec{x}, W_{Y}^{\prime} \vec{y}$</code> 和 <code>$f_{01}\left(W_{R}^{\prime} \vec{r}\right)$</code>。需要注意对于关系 <code>$r$</code>，模型应用了一个正则化函数 <code>$f_{01} \left(\cdot\right)$</code> 使得 <code>$r$</code> 的向量介于 <code>$0$</code> 和 <code>$1$</code> 之间。之后采用逐元素相乘对三个向量进行汇总 <code>$W_{X}^{\prime} \vec{x} \odot W_{Y}^{\prime} \vec{y} \odot f_{01}\left(W_{R}^{\prime} \vec{r}\right)$</code>。在最后的输出层，通过计算 <code>$sigmoid \left(\sum W_{X}^{\prime} \vec{x} \odot W_{Y}^{\prime} \vec{y} \odot f_{01}\left(W_{R}^{\prime} \vec{r}\right)\right)$</code> 得到最终的预测值。</p>
<p>在生成训练数据时，HIN2Vec 采用了完全随机游走进行节点采样，而非 metapath2vec 中的按照给定的元路径的方式。通过随机替换 <code>$x, y, r$</code> 中的任何一个可以生成负样本，但当网络中的关系数量较少，节点数量远远大于关系数量时，这种方式很可能产生错误的负样本，因此 HIN2Vec 只随机替换 <code>$x, y$</code>，保持 <code>$r$</code> 不变。</p>
<h3 id="deep-learning">Deep Learning</h3>
<h4 id="sdne">SDNE</h4>
<p>SDNE <sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> 提出了一种利用自编码器同时优化一阶和二阶相似度的图嵌入算法，学习得到的向量能够保留局部和全局的结构信息。SDNE 使用的网络结构如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/sdne.png" class="lazyload"/>
  
</figure>
<p>对于二阶相似度，自编码器的目标是最小化输入和输出的重构误差。SDNE 采用邻接矩阵作为自编码器的输入，<code>$\mathbf{x}_i = \mathbf{s}_i$</code>，每个 <code>$\mathbf{s}_i$</code> 包含了节点 <code>$v_i$</code> 的邻居结构信息。模型的损失函数如下：</p>
<p><code>$$ \mathcal{L}=\sum_{i=1}^{n}\left\|\hat{\mathbf{x}}_{i}-\mathbf{x}_{i}\right\|_{2}^{2} $$</code></p>
<p>由于网络的稀疏性，邻接矩阵中的非零元素远远少于零元素，因此模型采用了一个带权的损失函数：</p>
<p><code>$$ \begin{aligned} \mathcal{L}_{2nd} &amp;=\sum_{i=1}^{n}\left\|\left(\hat{\mathbf{x}}_{i}-\mathbf{x}_{i}\right) \odot \mathbf{b}_{i}\right\|_{2}^{2} \\ &amp;=\|(\hat{X}-X) \odot B\|_{F}^{2} \end{aligned} $$</code></p>
<p>其中，<code>$\odot$</code> 表示按位乘，<code>$\mathbf{b}_i = \left\{b_{i, j}\right\}_{j=1}^{n}$</code>，如果 <code>$s_{i, j} = 0$</code> 则 <code>$b_{i, j} = 1$</code> 否则 <code>$b_{i, j} = \beta &gt; 1$</code>。</p>
<p>对于一阶相似度，模型利用了一个监督学习模块最小化节点在隐含空间中距离。损失函数如下：</p>
<p><code>$$ \begin{aligned} \mathcal{L}_{1st} &amp;=\sum_{i, j=1}^{n} s_{i, j}\left\|\mathbf{y}_{i}^{(K)}-\mathbf{y}_{j}^{(K)}\right\|_{2}^{2} \\ &amp;=\sum_{i, j=1}^{n} s_{i, j}\left\|\mathbf{y}_{i}-\mathbf{y}_{j}\right\|_{2}^{2} \end{aligned} $$</code></p>
<p>最终，模型联合损失函数如下：</p>
<p><code>$$ \begin{aligned} \mathcal{L}_{mix} &amp;=\mathcal{L}_{2nd}+\alpha \mathcal{L}_{1st}+\nu \mathcal{L}_{reg} \\ &amp;=\|(\hat{X}-X) \odot B\|_{F}^{2}+\alpha \sum_{i, j=1}^{n} s_{i, j}\left\|\mathbf{y}_{i}-\mathbf{y}_{j}\right\|_{2}^{2}+\nu \mathcal{L}_{reg} \end{aligned} $$</code></p>
<p>其中，<code>$\mathcal{L}_{reg}$</code> 为 L2 正则项。</p>
<h4 id="dngr">DNGR</h4>
<p>DNGR <sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup> 提出了一种利用基于 Stacked Denoising Autoencoder（SDAE）提取特征的网络表示学习算法。算法的流程如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/dngr.png" class="lazyload"/>
  
</figure>
<p>模型首先利用 Random Surfing 得到一个概率共现（PCO）矩阵，之后利用其计算得到 PPMI 矩阵，最后利用 SDAE 进行特征提取得到节点的向量表示。</p>
<p>对于传统的将图结构转换为一个线性序列方法存在几点缺陷：</p>
<ol>
<li>采样序列边缘的节点的上下文信息很难被捕捉。</li>
<li>很难直接确定游走的长度和步数等超参数，尤其是对于大型网络来说。</li>
</ol>
<p>受 PageRank 思想影响，作者采用了 Random Surfing 模型。定义转移矩阵 <code>$A$</code>，引入行向量 <code>$p_k$</code>，第 <code>$j$</code> 个元素表示通过 <code>$k$</code> 步转移之后到达节点 <code>$j$</code> 的概率。<code>$p_0$</code> 为一个初始向量，其仅第 <code>$i$</code> 个元素为 1，其它均为 0。在考虑以 <code>$1 - \alpha$</code> 的概率返回初始节点的情况下有：</p>
<p><code>$$ p_{k}=\alpha \cdot p_{k-1} A+(1-\alpha) p_{0} $$</code></p>
<p>在不考虑返回初始节点的情况下有：</p>
<p><code>$$ p_{k}^{*}=p_{k-1}^{*} A=p_{0} A^{k} $$</code></p>
<p>直观而言，两个节点越近，两者的关系越亲密，因此通过同当前节点的相对距离来衡量上下文节点的重要性是合理的。基于此，第 <code>$i$</code> 个节点的表示可以用如下方式构造：</p>
<p><code>$$ r=\sum_{k=1}^{K} w(k) \cdot p_{k}^{*} $$</code></p>
<p>其中，<code>$w \left(\cdot\right)$</code> 是一个衰减函数。</p>
<p>利用 PCO 计算得到 PPMI 后，再利用一个 SDAE 进行特征提取。Stacking 策略可以通过不同的网络层学习得到不同层级的表示，Denoising 策略则通过去除数据中的噪声，增加结果的鲁棒性。同时，SNGR 相比基于 SVD 的方法效率更高。</p>
<h3 id="others">Others</h3>
<h4 id="line">LINE</h4>
<p>LINE <sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup> 提出了一个用于大规模网络嵌入的方法，其满足如下 3 个要求：</p>
<ol>
<li>同时保留节点之间的一阶相似性（first-order proximity）和二阶相似性（second-order proximity）。</li>
<li>可以处理大规模网络，例如：百万级别的顶点和十亿级别的边。</li>
<li>可以处理有向，无向和带权的多种类型的图结构。</li>
</ol>
<p>给定一个无向边 <code>$\left(i, j\right)$</code>，点 <code>$v_i$</code> 和 <code>$v_j$</code> 的联合概率如下：</p>
<p><code>$$ p_{1}\left(v_{i}, v_{j}\right)=\frac{1}{1+\exp \left(-\vec{u}_{i}^{T} \cdot \vec{u}_{j}\right)} $$</code></p>
<p>其中，<code>$\vec{u}_{i} \in R^{d}$</code> 为节点 <code>$v_i$</code> 的低维向量表示。在空间 <code>$V \times V$</code> 上，分布 <code>$p \left(\cdot, \cdot\right)$</code> 的经验概率为 <code>$\hat{p}_1 \left(i, j\right) = \dfrac{w_{ij}}{V}$</code>，其中 <code>$W = \sum_{\left(i, j\right) \in E} w_{ij}$</code>。通过最小化两个分布的 KL 散度来优化模型，则目标函数定义如下：</p>
<p><code>$$ O_{1}=-\sum_{(i, j) \in E} w_{i j} \log p_{1}\left(v_{i}, v_{j}\right) $$</code></p>
<p>需要注意的是一阶相似度仅可用于无向图，通过最小化上述目标函数，我们可以将任意顶点映射到一个 <code>$d$</code> 维空间向量。</p>
<p>二阶相似度既可以用于无向图，也可以用于有向图。二阶相似度假设共享大量同其他节点连接的节点之间是相似的，每个节点被视为一个特定的上下文，则在上下文上具有类似分布的节点是相似的。在此，引入两个向量 <code>$\vec{u}_{i}$</code> 和 <code>$\vec{u}_{\prime i}$</code>，其中 <code>$\vec{u}_{i}$</code> 是 <code>$v_i$</code> 做为节点的表示，<code>$\vec{u}_{\prime i}$</code> 是 <code>$v_i$</code> 做为上下文的表示。对于一个有向边 <code>$\left(i, j\right)$</code>，由 <code>$v_i$</code> 生成上下文 <code>$v_j$</code> 的概率为：</p>
<p><code>$$ p_{2}\left(v_{j} | v_{i}\right)=\frac{\exp \left(\vec{u}_{j}^{\prime T} \cdot \vec{u}_{i}\right)}{\sum_{k=1}^{|V|} \exp \left(\vec{u}_{k}^{\prime T} \cdot \vec{u}_{i}\right)} $$</code></p>
<p>其中，<code>$|V|$</code> 为节点或上下文的数量。在此我们引入一个参数 <code>$\lambda_i$</code> 用于表示节点 <code>$v_i$</code> 的重要性程度，重要性程度可以利用度或者 PageRank 算法进行估计。经验分布 <code>$\hat{p}_{2}\left(\cdot \mid v_{i}\right)$</code> 定义为 <code>$\hat{p}_{2}\left(v_{j} \mid v_{i}\right)=\dfrac{w_{i j}}{d_{i}}$</code>，其中 <code>$w_{ij}$</code> 为边 <code>$\left(i, j\right)$</code> 的权重，<code>$d_i$</code> 为节点 <code>$v_i$</code> 的出度。LINE 中采用 <code>$d_i$</code> 作为节点的重要性 <code>$\lambda_i$</code>，利用 KL 散度同时忽略一些常量，目标函数定义如下：</p>
<p><code>$$ O_{2}=-\sum_{(i, j) \in E} w_{i j} \log p_{2}\left(v_{j} \mid v_{i}\right) $$</code></p>
<p>LINE 采用负采样的方式对模型进行优化，同时利用 Alias 方法 <sup id="fnref:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> <sup id="fnref:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 加速采样过程。</p>
<h2 id="图神经网络">图神经网络</h2>
<blockquote>
<p>本节内容主要参考自：<br>
Deep Learning on Graphs: A Survey <sup id="fnref:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup><br>
A Comprehensive Survey on Graph Neural Networks <sup id="fnref:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup><br>
Graph Neural Networks: A Review of Methods and Applications <sup id="fnref:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup><br>
Introduction to Graph Neural Networks <sup id="fnref:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup></p>
</blockquote>
<p>图神经网络（Graph Neural Network，GNN）最早由 Scarselli 等人 <sup id="fnref:21"><a href="#fn:21" class="footnote-ref" role="doc-noteref">21</a></sup> 提出。图中的一个节点可以通过其特征和相关节点进行定义，GNN 的目标是学习一个状态嵌入 <code>$\mathbf{h}_v \in \mathbb{R}^s$</code> 用于表示每个节点的邻居信息。状态嵌入 <code>$\mathbf{h}_v$</code> 可以生成输出向量 <code>$\mathbf{o}_v$</code> 用于作为预测节点标签的分布等。</p>
<p>下面三张图分别从图的类型，训练方法和传播过程角度列举了不同 GNN 的变种 <sup id="fnref1:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup>。</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gnn-graph-types.png" class="lazyload"/>
  
</figure>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gnn-training-methods.png" class="lazyload"/>
  
</figure>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gnn-propagation-steps.png" class="lazyload"/>
  
</figure>
<p>下面我们主要从模型的角度分别介绍不同种类的 GNN。</p>
<h3 id="graph-neural-networks">Graph Neural Networks</h3>
<p>为了根据邻居更新节点的状态，定义一个用于所有节点的函数 <code>$f$</code>，称之为 <em>local transition function</em>。定义一个函数 <code>$g$</code>，用于生成节点的输出，称之为 <em>local output function</em>。有：</p>
<p><code>$$ \begin{array}{c} \mathbf{h}_{v}=f\left(\mathbf{x}_{v}, \mathbf{x}_{co[v]}, \mathbf{h}_{ne[v]}, \mathbf{x}_{ne[v])}\right. \\ \mathbf{o}_{v}=g\left(\mathbf{h}_{v}, \mathbf{x}_{v}\right) \end{array} $$</code></p>
<p>其中，<code>$\mathbf{x}$</code> 表示输入特征，<code>$\mathbf{h}$</code> 表示隐含状态。<code>$co[v]$</code> 为连接到节点 <code>$v$</code> 的边集，<code>$ne[v]$</code> 为节点 <code>$v$</code> 的邻居。</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/graph-example.png" class="lazyload"/>
  
</figure>
<p>上图中，<code>$\mathbf{x}_1$</code> 表示 <code>$l_1$</code> 的输入特征，<code>$co[l_1]$</code> 包含了边 <code>$l_{(1, 4)}, l_{(6, 1)}, l_{(1, 2)}$</code> 和 <code>$l_{(3, 1)}$</code>，<code>$ne[l_1]$</code> 包含了节点 <code>$l_2, k_3, l_4$</code> 和 <code>$l_6$</code>。</p>
<p>令 <code>$\mathbf{H}, \mathbf{O}, \mathbf{X}$</code> 和 <code>$\mathbf{X}_N$</code> 分别表示状态、输出、特征和所有节点特征的向量，有：</p>
<p><code>$$ \begin{aligned} &amp;\mathbf{H}=F(\mathbf{H}, \mathbf{X})\\ &amp;\mathbf{O}=G\left(\mathbf{H}, \mathbf{X}_{N}\right) \end{aligned} $$</code></p>
<p>其中，<code>$F$</code> 为 <em>global transition function</em>，<code>$G$</code> 为 <em>global output function</em>，分别为图中所有节点的 local transition function <code>$f$</code> 和 local output function <code>$g$</code> 的堆叠版本。依据 Banach 的 Fixed Point Theorem <sup id="fnref:22"><a href="#fn:22" class="footnote-ref" role="doc-noteref">22</a></sup>，GNN 利用传统的迭代方式计算状态：</p>
<p><code>$$ \mathbf{H}^{t+1}=F\left(\mathbf{H}^{t}, \mathbf{X}\right) $$</code></p>
<p>其中，<code>$\mathbf{H}^t$</code> 表示第  <code>$t$</code> 论循环 <code>$\mathbf{H}$</code> 的值。</p>
<p>介绍完 GNN 的框架后，下一个问题就是如果学习得到 local transition function <code>$f$</code> 和 local output function <code>$g$</code>。在包含目标信息（<code>$\mathbf{t}_v$</code> 对于特定节点）的监督学习情况下，损失为：</p>
<p><code>$$ loss = \sum_{i=1}^{p} \left(\mathbf{t}_i - \mathbf{o}_i\right) $$</code></p>
<p>其中，<code>$p$</code> 为用于监督学习的节点数量。利用基于梯度下降的学习方法优化模型后，我们可以得到针对特定任务的训练模型和图中节点的隐含状态。</p>
<p>尽管实验结果表明 GNN 是一个用于建模结构数据的强大模型，但对于一般的 GNN 模型仍存在如下缺陷：</p>
<ol>
<li>对于固定点，隐含状态的更新是低效地。</li>
<li>GNN 在每一轮计算中共享参数，而常见的神经网络结构在不同层使用不同的参数。同时，隐含节点状态的更新可以进一步应用 RNN 的思想。</li>
<li>边上的一些信息特征并没有被有效的建模，同时如何学习边的隐含状态也是一个重要问题。</li>
<li>如果我们更关注节点的表示而非图的表示，当迭代轮数 <code>$T$</code> 很大时使用固定点是不合适的。这是因为固定点表示的分布在数值上会更加平滑，从而缺少用于区分不同节点的信息。</li>
</ol>
<h3 id="graph-convolutional-networks">Graph Convolutional Networks</h3>
<p>图卷积神经网络是将用于传统数据（例如：图像）的卷积操作应用到图结构的数据中。核心思想在于学习一个函数 <code>$f$</code>，通过聚合节点 <code>$v_i$</code> 自身的特征 <code>$\mathbf{X}_i$</code> 和邻居的特征 <code>$\mathbf{X}_j$</code> 获得节点的表示，其中 <code>$j \in N\left(v_i\right)$</code> 为节点的邻居。</p>
<p>下图展示了一个用于节点表示学习的 GCN 过程：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gcn.png" class="lazyload"/>
  
</figure>
<p>GCN 在构建更复杂的图神经网路中扮演了一个核心角色：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gcn-classification.png" class="lazyload"/>
  <figcaption><p class="figcaption">包含 Pooling 模块用于图分类的 GCN</p></figcaption>
</figure>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gcn-auto-encoder.png" class="lazyload"/>
  <figcaption><p class="figcaption">包含 GCN 的图自编码器</p></figcaption>
</figure>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gcn-graph-spatial-temporal-network.png" class="lazyload"/>
  <figcaption><p class="figcaption">包含 GCN 的图时空网络</p></figcaption>
</figure>
<p>GCN 方法可以分为两大类：基于频谱（Spectral Methods）和基于空间（Spatial Methods）的方法。</p>
<h4 id="基于频谱的方法-spectral-methods">基于频谱的方法（Spectral Methods）</h4>
<p>基于频谱的方法将图视为无向图进行处理，图的一种鲁棒的数学表示为标准化的图拉普拉斯矩阵：</p>
<p><code>$$ \mathbf{L}=\mathbf{I}_{\mathbf{n}}-\mathbf{D}^{-\frac{1}{2}} \mathbf{A} \mathbf{D}^{-\frac{1}{2}} $$</code></p>
<p>其中，<code>$\mathbf{A}$</code> 为图的邻接矩阵，<code>$\mathbf{D}$</code> 为节点度的对角矩阵，<code>$\mathbf{D}_{ii} = \sum_{j} \left(\mathbf{A}_{i, j}\right)$</code>。标准化的拉普拉斯矩阵具有实对称半正定的性质，因此可以分解为：</p>
<p><code>$$ \mathbf{L}=\mathbf{U} \mathbf{\Lambda} \mathbf{U}^{T} $$</code></p>
<p>其中，<code>$\mathbf{U}=\left[\mathbf{u}_{\mathbf{0}}, \mathbf{u}_{\mathbf{1}}, \cdots, \mathbf{u}_{\mathbf{n}-\mathbf{1}}\right] \in \mathbf{R}^{N \times N}$</code> 是由 <code>$\mathbf{L}$</code> 的特征向量构成的矩阵，<code>$\mathbf{\Lambda}$</code> 为特征值的对角矩阵，<code>$\mathbf{\Lambda}_{ii} = \lambda_i$</code>。在图信号处理过程中，一个图信号 <code>$\mathbf{x} \in \mathbb{R}^N$</code> 是一个由图的节点构成的特征向量，其中 <code>$\mathbf{x}_i$</code> 表示第 <code>$i$</code> 个节点的值。对于信号 <code>$\mathbf{x}$</code>，图上的傅里叶变换可以定义为：</p>
<p><code>$$ \mathscr{F}(\mathbf{x})=\mathbf{U}^{T} \mathbf{x} $$</code></p>
<p>傅里叶反变换定义为：</p>
<p><code>$$ \mathscr{F}^{-1}(\hat{\mathbf{x}})=\mathbf{U} \hat{\mathbf{x}} $$</code></p>
<p>其中，<code>$\hat{\mathbf{x}}$</code> 为傅里叶变换后的结果。</p>
<p>转变后信号 <code>$\hat{\mathbf{x}}$</code> 的元素为新空间图信号的坐标，因此输入信号可以表示为：</p>
<p><code>$$ \mathbf{x}=\sum_{i} \hat{\mathbf{x}}_{i} \mathbf{u}_{i} $$</code></p>
<p>这正是傅里叶反变换的结果。那么对于输入信号 <code>$\mathbf{x}$</code> 的图卷积可以定义为：</p>
<p><code>$$ \begin{aligned} \mathbf{x} *_{G} \mathbf{g} &amp;=\mathscr{F}^{-1}(\mathscr{F}(\mathbf{x}) \odot \mathscr{F}(\mathbf{g})) \\ &amp;=\mathbf{U}\left(\mathbf{U}^{T} \mathbf{x} \odot \mathbf{U}^{T} \mathbf{g}\right) \end{aligned} $$</code></p>
<p>其中，<code>$\mathbf{g} \in \mathbb{R}^N$</code> 为滤波器，<code>$\odot$</code> 表示逐元素乘。假设定义一个滤波器 <code>$\mathbf{g}_{\theta}=\operatorname{diag}\left(\mathbf{U}^{T} \mathbf{g}\right)$</code>，则图卷积可以简写为：</p>
<p><code>$$ \mathbf{x} *_{G} \mathbf{g}_{\theta}=\mathbf{U} \mathbf{g}_{\theta} \mathbf{U}^{T} \mathbf{x} $$</code></p>
<p>基于频谱的图卷积网络都遵循这样的定义，不同之处在于不同滤波器的选择。</p>
<p>一些代表模型及其聚合和更新方式如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>聚合方式</th>
          <th>更新方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ChebNet <sup id="fnref:23"><a href="#fn:23" class="footnote-ref" role="doc-noteref">23</a></sup></td>
          <td><code>$\mathbf{N}_{k}=\mathbf{T}_{k}(\tilde{\mathbf{L}}) \mathbf{X}$</code></td>
          <td><code>$\mathbf{H}=\sum_{k=0}^{K} \mathbf{N}_{k} \mathbf{\Theta}_{k}$</code></td>
      </tr>
      <tr>
          <td>1st-order model</td>
          <td><code>$\begin{array}{l} \mathbf{N}_{0}=\mathbf{X} \\ \mathbf{N}_{1}=\mathbf{D}^{-\frac{1}{2}} \mathbf{A} \mathbf{D}^{-\frac{1}{2}} \mathbf{X} \end{array}$</code></td>
          <td><code>$\mathbf{H}=\mathbf{N}_{0} \mathbf{\Theta}_{0}+\mathbf{N}_{1} \mathbf{\Theta}_{1}$</code></td>
      </tr>
      <tr>
          <td>Single parameter</td>
          <td><code>$\mathbf{N}=\left(\mathbf{I}_{N}+\mathbf{D}^{-\frac{1}{2}} \mathbf{A} \mathbf{D}^{-\frac{1}{2}}\right) \mathbf{X}$</code></td>
          <td><code>$\mathbf{H}=\mathbf{N} \mathbf{\Theta}$</code></td>
      </tr>
      <tr>
          <td>GCN <sup id="fnref:24"><a href="#fn:24" class="footnote-ref" role="doc-noteref">24</a></sup></td>
          <td><code>$\mathbf{N}=\tilde{\mathbf{D}}^{-\frac{1}{2}} \tilde{\mathbf{A}} \tilde{\mathbf{D}}^{-\frac{1}{2}} \mathbf{X}$</code></td>
          <td><code>$\mathbf{H}=\mathbf{N} \mathbf{\Theta}$</code></td>
      </tr>
  </tbody>
</table>
<h4 id="基于空间的方法-spatial-methods">基于空间的方法（Spatial Methods）</h4>
<p>基于空间的方法通过节点的空间关系来定义图卷积操作。为了将图像和图关联起来，可以将图像视为一个特殊形式的图，每个像素点表示一个节点，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/spatial-based-gcn.png" class="lazyload"/>
  
</figure>
<p>每个像素同周围的像素相连，以 <code>$3 \times 3$</code> 为窗口，每个节点被 8 个邻居节点所包围。通过对中心节点和周围邻居节点的像素值进行加权平均来应用一个 <code>$3 \times 3$</code> 大小的滤波器。由于邻居节点的特定顺序，可以在不同位置共享权重。同样对于一般的图，基于空间的图卷积通过对中心和邻居节点的聚合得到节点新的表示。</p>
<p>为了使节点可以感知更深和更广的范围，通常的做法是将多个图卷积层堆叠在一起。根据堆叠方式的不同，基于空间的图卷积可以进一步分为两类：基于循环（Recurrent-based）和基于组合（Composition-based）的。基于循环的方法使用相同的图卷积层来更新隐含表示，基于组合的方式使用不同的图卷积层更新隐含表示，两者差异如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/recurrent-based-vs-composition-based.png" class="lazyload"/>
  
</figure>
<p>一些代表模型及其聚合和更新方式如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>聚合方式</th>
          <th>更新方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Neural FPs <sup id="fnref:25"><a href="#fn:25" class="footnote-ref" role="doc-noteref">25</a></sup></td>
          <td><code>$\mathbf{h}_{\mathcal{N}_{v}}^{t}=\mathbf{h}_{v}^{t-1}+\sum_{k=1}^{\mathcal{N}_{v}} \mathbf{h}_{k}^{t-1}$</code></td>
          <td><code>$\mathbf{h}_{v}^{t}=\sigma\left(\mathbf{h}_{\mathcal{N}_{v}}^{t} \mathbf{W}_{L}^{\mathcal{N}_{v}}\right)$</code></td>
      </tr>
      <tr>
          <td>DCNN <sup id="fnref:26"><a href="#fn:26" class="footnote-ref" role="doc-noteref">26</a></sup></td>
          <td>Node classification:<br/><code>$\mathbf{N}=\mathbf{P}^{*} \mathbf{X}$</code><br/> Graph classification:<br/><code>$\mathbf{N}=1_{N}^{T} \mathbf{P}^{*} \mathbf{X} / N$</code></td>
          <td><code>$\mathbf{H}=f\left(\mathbf{W}^{c} \odot \mathbf{N}\right)$</code></td>
      </tr>
      <tr>
          <td>GraphSAGE <sup id="fnref:27"><a href="#fn:27" class="footnote-ref" role="doc-noteref">27</a></sup></td>
          <td><code>$\mathbf{h}_{\mathcal{N}_{v}}^{t}=\text{AGGREGATE}_{t}\left(\left\{\mathbf{h}_{u}^{t-1}, \forall u \in \mathcal{N}_{v}\right\}\right)$</code></td>
          <td><code>$\mathbf{h}_{v}^{t}=\sigma\left(\mathbf{W}^{t} \cdot\left[\mathbf{h}_{v}^{t-1} \Vert \mathbf{h}_{\mathcal{N}_{v}}^{t}\right]\right)$</code></td>
      </tr>
  </tbody>
</table>
<h3 id="graph-recurrent-networks">Graph Recurrent Networks</h3>
<p>一些研究尝试利用门控机制（例如：GRU 或 LSTM）用于减少之前 GNN 模型在传播过程中的限制，同时改善在图结构中信息的长距离传播。GGNN <sup id="fnref:28"><a href="#fn:28" class="footnote-ref" role="doc-noteref">28</a></sup> 提出了一种使用 GRU 进行传播的方法。它将 RNN 展开至一个固定 <code>$T$</code> 步，然后通过基于时间的传导计算梯度。传播模型的基础循环方式如下：</p>
<p><code>$$ \begin{aligned} &amp;\mathbf{a}_{v}^{t}=\mathbf{A}_{v}^{T}\left[\mathbf{h}_{1}^{t-1} \ldots \mathbf{h}_{N}^{t-1}\right]^{T}+\mathbf{b}\\ &amp;\mathbf{z}_{v}^{t}=\sigma\left(\mathbf{W}^{z} \mathbf{a}_{v}^{t}+\mathbf{U}^{z} \mathbf{h}_{v}^{t-1}\right)\\ &amp;\mathbf{r}_{v}^{t}=\sigma\left(\mathbf{W}^{r} \mathbf{a}_{v}^{t}+\mathbf{U}^{r} \mathbf{h}_{v}^{t-1}\right)\\ &amp;\begin{array}{l} \widetilde{\mathbf{h}}_{v}^{t}=\tanh \left(\mathbf{W} \mathbf{a}_{v}^{t}+\mathbf{U}\left(\mathbf{r}_{v}^{t} \odot \mathbf{h}_{v}^{t-1}\right)\right) \\ \mathbf{h}_{v}^{t}=\left(1-\mathbf{z}_{v}^{t}\right) \odot \mathbf{h}_{v}^{t-1}+\mathbf{z}_{v}^{t} \odot \widetilde{\mathbf{h}}_{v}^{t} \end{array} \end{aligned} $$</code></p>
<p>节点 <code>$v$</code> 首先从邻居汇总信息，其中 <code>$\mathbf{A}_v$</code> 为图邻接矩阵 <code>$\mathbf{A}$</code> 的子矩阵表示节点 <code>$v$</code> 及其邻居的连接。类似 GRU 的更新函数，通过结合其他节点和上一时间的信息更新节点的隐状态。<code>$\mathbf{a}$</code> 用于获取节点 <code>$v$</code> 邻居的信息，<code>$\mathbf{z}$</code> 和 <code>$\mathbf{r}$</code> 分别为更新和重置门。</p>
<p>GGNN 模型设计用于解决序列生成问题，而之前的模型主要关注单个输出，例如：节点级别或图级别的分类问题。研究进一步提出了 Gated Graph Sequence Neural Networks（GGS-NNs），使用多个 GGNN 产生一个输出序列 <code>$\mathbf{o}^{(1)}, \cdots, \mathbf{o}^{(K)}$</code>，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/ggs-nn.png" class="lazyload"/>
  
</figure>
<p>上图中使用了两个 GGNN，<code>$\mathcal{F}_o^{(k)}$</code> 用于从 <code>$\mathcal{\boldsymbol{X}}^{(k)}$</code> 预测 <code>$\mathbf{o}^{(k)}$</code>，<code>$\mathcal{F}_x^{(k)}$</code> 用于从 <code>$\mathcal{\boldsymbol{X}}^{(k)}$</code> 预测 <code>$\mathcal{\boldsymbol{X}}^{(k+1)}$</code>。令 <code>$\mathcal{\boldsymbol{H}}^{(k, t)}$</code> 表示第 <code>$k$</code> 步输出的第 <code>$t$</code> 步传播，<code>$\mathcal{\boldsymbol{H}}^{(k, 1)}$</code> 在任意 <code>$k$</code> 步初始化为 <code>$\mathcal{\boldsymbol{X}}^{(k)}$</code>，<code>$\mathcal{\boldsymbol{H}}^{(t, 1)}$</code> 在任意 <code>$t$</code> 步初始化为 <code>$\mathcal{\boldsymbol{X}}^{(t)}$</code>，<code>$\mathcal{F}_o^{(k)}$</code> 和 <code>$\mathcal{F}_x^{(k)}$</code> 可以为不同模型也可以共享权重。</p>
<p>一些代表模型及其聚合和更新方式如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>聚合方式</th>
          <th>更新方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GGNN <sup id="fnref1:28"><a href="#fn:28" class="footnote-ref" role="doc-noteref">28</a></sup></td>
          <td><code>$\mathbf{h}_{\mathcal{N}_{v}}^{t}=\sum_{k \in \mathcal{N}_{v}} \mathbf{h}_{k}^{t-1}+\mathbf{b}$</code></td>
          <td><code>$\begin{aligned} &amp;\mathbf{z}_{v}^{t}=\sigma\left(\mathbf{W}^{z} \mathbf{h}_{\mathcal{N}_{v}}^{t}+\mathbf{U}^{z} \mathbf{h}_{v}^{t-1}\right)\\ &amp;\mathbf{r}_{v}^{t}=\sigma\left(\mathbf{W}^{r} \mathbf{h}_{\mathcal{N}_{v}}^{z}+\mathbf{U}^{r} \mathbf{h}_{v}^{t-1}\right)\\ &amp;\begin{array}{l} \widetilde{\mathbf{h}}_{v}^{t}=\tanh \left(\mathbf{W h}_{\mathcal{N}_{v}}^{t}+\mathbf{U}\left(\mathbf{r}_{v}^{t} \odot \mathbf{h}_{v}^{t-1}\right)\right) \\ \mathbf{h}_{v}^{t}=\left(1-\mathbf{z}_{v}^{t}\right) \odot \mathbf{h}_{v}^{t-1}+\mathbf{z}_{v}^{t} \odot \widetilde{\mathbf{h}}_{v}^{t} \end{array} \end{aligned}$</code></td>
      </tr>
      <tr>
          <td>Tree LSTM (Child sum) <sup id="fnref:29"><a href="#fn:29" class="footnote-ref" role="doc-noteref">29</a></sup></td>
          <td><code>$\mathbf{h}_{\mathcal{N}_{v}}^{t}=\sum_{k \in \mathcal{N}_{v}} \mathbf{h}_{k}^{t-1}$</code></td>
          <td><code>$\begin{aligned} &amp;\mathbf{i}_{v}^{t}=\sigma\left(\mathbf{W}^{i} \mathbf{x}_{v}^{t}+\mathbf{U}^{i} \mathbf{h}_{\mathcal{N}_{v}}^{t}+\mathbf{b}^{i}\right)\\ &amp;\mathbf{f}_{v k}^{t}=\sigma\left(\mathbf{W}^{f} \mathbf{x}_{v}^{t}+\mathbf{U}^{f} \mathbf{h}_{k}^{t-1}+\mathbf{b}^{f}\right)\\ &amp;\mathbf{o}_{v}^{t}=\sigma\left(\mathbf{W}^{o} \mathbf{x}_{v}^{t}+\mathbf{U}^{o} \mathbf{h}_{\mathcal{N}_{v}}^{t}+\mathbf{b}^{o}\right)\\ &amp;\mathbf{u}_{v}^{t}=\tanh \left(\mathbf{W}^{u} \mathbf{x}_{v}^{t}+\mathbf{U}^{u} \mathbf{h}_{\mathcal{N}_{v}}^{t}+\mathbf{b}^{u}\right)\\ &amp;\begin{array}{l} \mathbf{c}_{v}^{t}=\mathbf{i}_{v}^{t} \odot \mathbf{u}_{v}^{t}+\sum_{k \in \mathcal{N}_{v}} \mathbf{f}_{v k}^{t} \odot \mathbf{c}_{k}^{t-1} \\ \mathbf{h}_{v}^{t}=\mathbf{o}_{v}^{t} \odot \tanh \left(\mathbf{c}_{v}^{t}\right) \end{array} \end{aligned}$</code></td>
      </tr>
      <tr>
          <td>Tree LSTM (N-ary) <sup id="fnref1:29"><a href="#fn:29" class="footnote-ref" role="doc-noteref">29</a></sup></td>
          <td><code>$\begin{aligned} &amp;\mathbf{h}_{\mathcal{N}_{v}}^{t i}=\sum_{l=1}^{K} \mathbf{U}_{l}^{i} \mathbf{h}_{v l}^{t-1}\\ &amp;\mathbf{h}_{\mathcal{N}_{v} k}^{t f}=\sum_{l=1}^{K} \mathbf{U}_{k l}^{f} \mathbf{h}_{v l}^{t-1}\\ &amp;\mathbf{h}_{\mathcal{N}_{v}}^{t o}=\sum_{l=1}^{K} \mathbf{U}_{l}^{o} \mathbf{h}_{v l}^{t-1}\\ &amp;\mathbf{h}_{\mathcal{N}_{v}}^{t u}=\sum_{l=1}^{K} \mathbf{U}_{l}^{u} \mathbf{h}_{v l}^{t-1} \end{aligned}$</code></td>
          <td><code>$\begin{aligned} &amp;\mathbf{i}_{v}^{t}=\sigma\left(\mathbf{W}^{i} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v},}^{t i}+\mathbf{b}^{i}\right)\\ &amp;\mathbf{f}_{v k}^{t}=\sigma\left(\mathbf{W}^{f} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v} k}^{f f}+\mathbf{b}^{f}\right)\\ &amp;\mathbf{o}_{v}^{t}=\sigma\left(\mathbf{W}^{o} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v}}^{t o}+\mathbf{b}^{o}\right)\\ &amp;\mathbf{u}_{v}^{t}=\tanh \left(\mathbf{W}^{u} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v}}^{t u}+\mathbf{b}^{u}\right)\\ &amp;\mathbf{c}_{v}^{t}=\mathbf{i}_{v}^{t} \odot \mathbf{u}_{v}^{t}+\sum_{l=1}^{K} \mathbf{f}_{v l}^{t} \odot \mathbf{c}_{v l}^{t-1}\\ &amp;\mathbf{h}_{v}^{t}=\mathbf{o}_{v}^{t} \odot \tanh \left(\mathbf{c}_{v}^{t}\right) \end{aligned}$</code></td>
      </tr>
      <tr>
          <td>Graph LSTM <sup id="fnref:30"><a href="#fn:30" class="footnote-ref" role="doc-noteref">30</a></sup></td>
          <td><code>$\begin{aligned} \mathbf{h}_{\mathcal{N}_{v}}^{t i}=\sum_{k \in \mathcal{N}_{v}} \mathbf{U}_{m(v, k)}^{i} \mathbf{h}_{k}^{t-1} \\ \mathbf{h}_{\mathcal{N}_{v}}^{t o}=\sum_{k \in \mathcal{N}_{v}} \mathbf{U}_{m(v, k)}^{o} \mathbf{h}_{k}^{t-1} \\ \mathbf{h}_{\mathcal{N}_{v}}^{t u}=\sum_{k \in \mathcal{N}_{v}} \mathbf{U}_{m(v, k)}^{u} \mathbf{h}_{k}^{t-1} \end{aligned}$</code></td>
          <td><code>$\begin{aligned} &amp;\mathbf{i}_{v}^{t}=\sigma\left(\mathbf{W}^{i} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v}}^{t i}+\mathbf{b}^{i}\right)\\ &amp;\mathbf{f}_{v k}^{t}=\sigma\left(\mathbf{W}^{f} \mathbf{x}_{v}^{t}+\mathbf{U}_{m(v, k)}^{f} \mathbf{h}_{k}^{t-1}+\mathbf{b}^{f}\right)\\ &amp;\mathbf{o}_{v}^{t}=\sigma\left(\mathbf{W}^{o} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v}}^{t o}+\mathbf{b}^{o}\right)\\ &amp;\mathbf{u}_{v}^{t}=\tanh \left(\mathbf{W}^{u} \mathbf{x}_{v}^{t}+\mathbf{h}_{\mathcal{N}_{v}}^{t u}+\mathbf{b}^{u}\right)\\ &amp;\begin{array}{l} \mathbf{c}_{v}^{t}=\mathbf{i}_{v}^{t} \odot \mathbf{u}_{v}^{t}+\sum_{k \in \mathcal{N}_{v}} \mathbf{f}_{v k}^{t} \odot \mathbf{c}_{k}^{t-1} \\ \mathbf{h}_{v}^{t}=\mathbf{o}_{v}^{t} \odot \tanh \left(\mathbf{c}_{v}^{t}\right) \end{array} \end{aligned}$</code></td>
      </tr>
  </tbody>
</table>
<h3 id="graph-attention-networks">Graph Attention Networks</h3>
<p>与 GCN 对于节点所有的邻居平等对待相比，注意力机制可以为每个邻居分配不同的注意力评分，从而识别更重要的邻居。</p>
<p>GAT <sup id="fnref:31"><a href="#fn:31" class="footnote-ref" role="doc-noteref">31</a></sup> 将注意力机制引入传播过程，其遵循自注意力机制，通过对每个节点邻居的不同关注更新隐含状态。GAT 定义了一个图注意力层（<em>graph attentional layer</em>），通过堆叠构建图注意力网络。对于节点对 <code>$\left(i, j\right)$</code>，基于注意力机制的系数计算方式如下：</p>
<p><code>$$ \alpha_{i j}=\frac{\exp \left(\text { LeakyReLU }\left(\overrightarrow{\mathbf{a}}^{T}\left[\mathbf{W} \vec{h}_{i} \| \mathbf{W} \vec{h}_{j}\right]\right)\right)}{\sum_{k \in N_{i}} \exp \left(\text { LeakyReLU }\left(\overrightarrow{\mathbf{a}}^{T}\left[\mathbf{W} \vec{h}_{i} \| \mathbf{W} \vec{h}_{k}\right]\right)\right)} $$</code></p>
<p>其中，<code>$\alpha_{i j}$</code> 表示节点 <code>$j$</code> 对 <code>$i$</code> 的注意力系数，<code>$N_i$</code> 表示节点 <code>$i$</code> 的邻居。令 <code>$\mathbf{h}=\left\{\vec{h}_{1}, \vec{h}_{2}, \ldots, \vec{h}_{N}\right\}, \vec{h}_{i} \in \mathbb{R}^{F}$</code> 表示输入节点特征，其中 <code>$N$</code> 为节点的数量，<code>$F$</code> 为特征维度，则节点的输出特征（可能为不同维度 <code>$F^{\prime}$</code>）为 <code>$\mathbf{h}^{\prime}=\left\{\vec{h}_{1}^{\prime}, \vec{h}_{2}^{\prime}, \ldots, \vec{h}_{N}^{\prime}\right\}, \vec{h}_{i}^{\prime} \in \mathbb{R}^{F^{\prime}}$</code>。<code>$\mathbf{W} \in \mathbb{R}^{F^{\prime} \times F}$</code> 为所有节点共享的线性变换的权重矩阵，<code>$a: \mathbb{R}^{F^{\prime}} \times \mathbb{R}^{F^{\prime}} \rightarrow \mathbb{R}$</code> 用于计算注意力系数。最后的输出特征计算方式如下：</p>
<p><code>$$ \vec{h}_{i}^{\prime}=\sigma\left(\sum_{j \in \mathcal{N}_{i}} \alpha_{i j} \mathbf{W} \vec{h}_{j}\right) $$</code></p>
<p>注意力层采用多头注意力机制来稳定学习过程，之后应用 <code>$K$</code> 个独立的注意力机制计算隐含状态，最后通过拼接或平均得到输出表示：</p>
<p><code>$$ \vec{h}_{i}^{\prime}=\Vert_{k=1}^{K} \sigma\left(\sum_{j \in \mathcal{N}_{i}} \alpha_{i j}^{k} \mathbf{W}^{k} \vec{h}_{j}\right) $$</code></p>
<p><code>$$ \vec{h}_{i}^{\prime}=\sigma\left(\frac{1}{K} \sum_{k=1}^{K} \sum_{j \in \mathcal{N}_{i}} \alpha_{i j}^{k} \mathbf{W}^{k} \vec{h}_{j}\right) $$</code></p>
<p>其中，<code>$\Vert$</code> 表示连接操作，<code>$\alpha_{ij}^k$</code> 表示第 <code>$k$</code> 个注意力机制计算得到的标准化的注意力系数。整个模型如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/gat.png" class="lazyload"/>
  
</figure>
<p>GAT 中的注意力架构有如下几个特点：</p>
<ol>
<li>针对节点对的计算是并行的，因此计算过程是高效的。</li>
<li>可以处理不同度的节点并对邻居分配对应的权重。</li>
<li>可以容易地应用到归纳学习问题中去。</li>
</ol>
<h3 id="应用">应用</h3>
<p>图神经网络已经被应用在监督、半监督、无监督和强化学习等多个领域。下图列举了 GNN 在不同领域内相关问题中的应用，具体模型论文请参考 Graph Neural Networks: A Review of Methods and Applications 原文 <sup id="fnref2:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup>。</p>
<figure>
  <img data-src="/images/cn/2020-04-11-graph-embedding-and-gnn/applications.png" class="lazyload"/>
  
</figure>
<h2 id="开放资源">开放资源</h2>
<h3 id="开源实现">开源实现</h3>
<table>
  <thead>
      <tr>
          <th>项目</th>
          <th>框架</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/rusty1s/pytorch_geometric">rusty1s/pytorch_geometric</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/dmlc/dgl">dmlc/dgl</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i>, <i class="icon icon-tensorflow">TF</i> &amp; <i class="icon icon-mxnet">MXNet</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/alibaba/euler">alibaba/euler</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/alibaba/graph-learn">alibaba/graph-learn</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/deepmind/graph_nets">deepmind/graph_nets</a></td>
          <td><i class="icon icon-tensorflow">TF</i> &amp; <i class="icon icon-sonnet">Sonnet</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/facebookresearch/PyTorch-BigGraph">facebookresearch/PyTorch-BigGraph</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/tencent/plato">tencent/plato</a></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/PaddlePaddle/PGL">PaddlePaddle/PGL</a></td>
          <td><i class="icon icon-paddlepaddle"></i> PaddlePaddle</td>
      </tr>
      <tr>
          <td><a href="https://github.com/Accenture/AmpliGraph">Accenture/AmpliGraph</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/danielegrattarola/spektral">danielegrattarola/spektral</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/THUDM/cogdl/">THUDM/cogdl</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
      </tr>
      <tr>
          <td><a href="https://github.com/DeepGraphLearning/graphvite">DeepGraphLearning/graphvite</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
      </tr>
  </tbody>
</table>
<h3 id="论文列表和评测">论文列表和评测</h3>
<ul>
<li><a href="https://github.com/thunlp/NRLPapers">Must-read papers on network representation learning (NRL) / network embedding (NE)</a></li>
<li><a href="https://github.com/thunlp/GNNPapers">Must-read papers on graph neural networks (GNN)</a></li>
<li><a href="https://github.com/DeepGraphLearning/LiteratureDL4Graph">DeepGraphLearning/LiteratureDL4Graph</a></li>
<li><a href="https://github.com/nnzhan/Awesome-Graph-Neural-Networks">nnzhan/Awesome-Graph-Neural-Networks</a></li>
<li><a href="https://github.com/graphdeeplearning/benchmarking-gnns">graphdeeplearning/benchmarking-gnns</a></li>
<li><a href="https://ogb.stanford.edu/">Open Graph Benchmark</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Cai, H., Zheng, V. W., &amp; Chang, K. C. C. (2018). A comprehensive survey of graph embedding: Problems, techniques, and applications. <em>IEEE Transactions on Knowledge and Data Engineering</em>, 30(9), 1616-1637.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Goyal, P., &amp; Ferrara, E. (2018). Graph embedding techniques, applications, and performance: A survey. <em>Knowledge-Based Systems</em>, 151, 78-94.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Hamilton, W. L., Ying, R., &amp; Leskovec, J. (2017). Representation learning on graphs: Methods and applications. <em>arXiv preprint arXiv:1709.05584</em>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Perozzi, B., Al-Rfou, R., &amp; Skiena, S. (2014). Deepwalk: Online learning of social representations. In <em>Proceedings of the 20th ACM SIGKDD international conference on Knowledge discovery and data mining</em> (pp. 701-710).&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Grover, A., &amp; Leskovec, J. (2016). node2vec: Scalable feature learning for networks. In <em>Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining</em> (pp. 855-864).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Fogaras, D., Rácz, B., Csalogány, K., &amp; Sarlós, T. (2005). Towards scaling fully personalized pagerank: Algorithms, lower bounds, and experiments. <em>Internet Mathematics</em>, 2(3), 333-358.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Haveliwala, T. H. (2002). Topic-sensitive PageRank. In <em>Proceedings of the 11th international conference on World Wide Web</em> (pp. 517-526).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Cao, S., Lu, W., &amp; Xu, Q. (2015). Grarep: Learning graph representations with global structural information. In <em>Proceedings of the 24th ACM international on conference on information and knowledge management</em> (pp. 891-900).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Ou, M., Cui, P., Pei, J., Zhang, Z., &amp; Zhu, W. (2016). Asymmetric transitivity preserving graph embedding. In <em>Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining</em> (pp. 1105-1114).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Dong, Y., Chawla, N. V., &amp; Swami, A. (2017). metapath2vec: Scalable representation learning for heterogeneous networks. In <em>Proceedings of the 23rd ACM SIGKDD international conference on knowledge discovery and data mining</em> (pp. 135-144).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Fu, T. Y., Lee, W. C., &amp; Lei, Z. (2017). Hin2vec: Explore meta-paths in heterogeneous information networks for representation learning. In <em>Proceedings of the 2017 ACM on Conference on Information and Knowledge Management</em> (pp. 1797-1806).&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>Wang, D., Cui, P., &amp; Zhu, W. (2016). Structural deep network embedding. In <em>Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining</em> (pp. 1225-1234).&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p>Cao, S., Lu, W., &amp; Xu, Q. (2016). Deep neural networks for learning graph representations. In <em>Thirtieth AAAI conference on artificial intelligence</em>.&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p>Tang, J., Qu, M., Wang, M., Zhang, M., Yan, J., &amp; Mei, Q. (2015). Line: Large-scale information network embedding. In <em>Proceedings of the 24th international conference on world wide web</em> (pp. 1067-1077).&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:15">
<p>Walker, A. J. (1974). New fast method for generating discrete random numbers with arbitrary frequency distributions. <em>Electronics Letters</em>, 10(8), 127-128.&#160;<a href="#fnref:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:16">
<p>Walker, A. J. (1977). An efficient method for generating discrete random variables with general distributions. <em>ACM Transactions on Mathematical Software (TOMS)</em>, 3(3), 253-256.&#160;<a href="#fnref:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:17">
<p>Zhang, Z., Cui, P., &amp; Zhu, W. (2020). Deep learning on graphs: A survey. <em>IEEE Transactions on Knowledge and Data Engineering</em>.&#160;<a href="#fnref:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:18">
<p>Wu, Z., Pan, S., Chen, F., Long, G., Zhang, C., &amp; Philip, S. Y. (2020). A comprehensive survey on graph neural networks. <em>IEEE Transactions on Neural Networks and Learning Systems</em>.&#160;<a href="#fnref:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:19">
<p>Zhou, J., Cui, G., Zhang, Z., Yang, C., Liu, Z., Wang, L., &hellip; &amp; Sun, M. (2018). Graph neural networks: A review of methods and applications. <em>arXiv preprint arXiv:1812.08434</em>.&#160;<a href="#fnref:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:20">
<p>Liu, Z., &amp; Zhou, J. (2020). Introduction to Graph Neural Networks. <em>Synthesis Lectures on Artificial Intelligence and Machine Learning</em>, 14(2), 1–127.&#160;<a href="#fnref:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:21">
<p>Scarselli, F., Gori, M., Tsoi, A. C., Hagenbuchner, M., &amp; Monfardini, G. (2008). The graph neural network model. <em>IEEE Transactions on Neural Networks</em>, 20(1), 61-80.&#160;<a href="#fnref:21" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:22">
<p>Khamsi, M. A., &amp; Kirk, W. A. (2011). <em>An introduction to metric spaces and fixed point theory</em> (Vol. 53). John Wiley &amp; Sons.&#160;<a href="#fnref:22" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:23">
<p>Defferrard, M., Bresson, X., &amp; Vandergheynst, P. (2016). Convolutional neural networks on graphs with fast localized spectral filtering. In <em>Advances in neural information processing systems</em> (pp. 3844-3852).&#160;<a href="#fnref:23" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:24">
<p>Kipf, T. N., &amp; Welling, M. (2016). Semi-supervised classification with graph convolutional networks. <em>arXiv preprint arXiv:1609.02907</em>.&#160;<a href="#fnref:24" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:25">
<p>Duvenaud, D. K., Maclaurin, D., Iparraguirre, J., Bombarell, R., Hirzel, T., Aspuru-Guzik, A., &amp; Adams, R. P. (2015). Convolutional networks on graphs for learning molecular fingerprints. In <em>Advances in neural information processing systems</em> (pp. 2224-2232).&#160;<a href="#fnref:25" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:26">
<p>Atwood, J., &amp; Towsley, D. (2016). Diffusion-convolutional neural networks. In <em>Advances in neural information processing systems</em> (pp. 1993-2001).&#160;<a href="#fnref:26" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:27">
<p>Hamilton, W., Ying, Z., &amp; Leskovec, J. (2017). Inductive representation learning on large graphs. In <em>Advances in neural information processing systems</em> (pp. 1024-1034).&#160;<a href="#fnref:27" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:28">
<p>Li, Y., Tarlow, D., Brockschmidt, M., &amp; Zemel, R. (2015). Gated graph sequence neural networks. <em>arXiv preprint arXiv:1511.05493.</em>&#160;<a href="#fnref:28" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:28" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:29">
<p>Tai, K. S., Socher, R., &amp; Manning, C. D. (2015). Improved Semantic Representations From Tree-Structured Long Short-Term Memory Networks. In <em>Proceedings of the 53rd Annual Meeting of the Association for Computational Linguistics and the 7th International Joint Conference on Natural Language Processing</em> (Volume 1: Long Papers) (pp. 1556-1566).&#160;<a href="#fnref:29" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:29" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:30">
<p>Peng, N., Poon, H., Quirk, C., Toutanova, K., &amp; Yih, W. T. (2017). Cross-sentence n-ary relation extraction with graph lstms. <em>Transactions of the Association for Computational Linguistics</em>, 5, 101-115.&#160;<a href="#fnref:30" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:31">
<p>Veličković, P., Cucurull, G., Casanova, A., Romero, A., Lio, P., &amp; Bengio, Y. (2017). Graph attention networks. <em>arXiv preprint arXiv:1710.10903.</em>&#160;<a href="#fnref:31" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>预训练自然语言模型 (Pre-trained Models for NLP)</title><link>https://zeqiang.fun/cn/2020/03/pre-trained-model-for-nlp/</link><pubDate>Sat, 28 Mar 2020 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2020/03/pre-trained-model-for-nlp/</guid><description><![CDATA[
        <blockquote>
<p>本文为 Pre-trained Models for Natural Language Processing: A Survey 和相关模型的读书笔记 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
</blockquote>
<p>在当下的 NLP 研究领域，随着计算机算力的不断增强，越来越多的通用语言表征的预训练模型（Pre-trained Models，PTMs）逐渐涌现出来。这对下游的 NLP 任务非常有帮助，可以避免大量从零开始训练新的模型。PTM 大致可以分为两代：</p>
<ul>
<li>第一代 PTM 旨在学习词嵌入。由于下游任务不在需要这些模型，因此为了计算效率，这些模型往往采用浅层模型，例如 Skip-Gram <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，GloVe <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 等。尽管这些模型可以捕获词的语义，但由于未基于上下文环境，因此不能够捕捉到更深层次的概念，例如：句法结构，语义角色，指代等等。</li>
<li>第二代 PTM 专注于学习基于上下文的词嵌入，例如 CoVe <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>，ELMo <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>，OpenAI GPT <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 和 BERT <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 等。这些学习到的编码器在下游任务中仍会用于词在上下文中的语义表示。</li>
</ul>
<h2 id="预训练原理">预训练原理</h2>
<h3 id="语言表示学习">语言表示学习</h3>
<p>分布式表示的核心思想为用一个低维的实值向量表示一段文本，向量单独每个维度不具有任何实质含义，但整个向量表示了一个具体的概念。下图展示了一个 NLP 任务的一般神经网络架构：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/generic-neural-architecture-for-nlp.png" class="lazyload"/>
  <figcaption><p class="figcaption">NLP 任务的一般神经网络架构</p></figcaption>
</figure>
<p>词嵌入包含两种类型：<strong>上下文无关的词嵌入</strong>和<strong>基于上下文的词嵌入</strong>。两者的不同点在于一个词的嵌入是够会随着上下文的不同而随之改变。</p>
<ul>
<li>上下文无关的词嵌入</li>
</ul>
<p>为了表征语义，我们需要将离散的语言符号映射到一个分布式嵌入空间中。对于词典 <code>$\mathcal{V}$</code> 中的一个词 <code>$x$</code>，我们将其映射为查询表 <code>$\mathbf{E} \in \mathbb{R}^{D_e \times \|\mathcal{V}\|}$</code> 中的一个向量 <code>$\mathbf{e}_x \in \mathbb{R}^{D_e}$</code>，其中 <code>$D_e$</code> 为嵌入的维度。</p>
<p>这种类型的嵌入主要有两个缺陷：一是嵌入是静态的，词在不同的上下文中的嵌入表示是相同的，因此无法处理一词多义；二是未登录词（out-of-vocabulary，OOV）问题，通常可以采用字符级嵌入表示解决该问题。更多上下文无关的词嵌入模型，请参见之前的博客 <a href="/cn/2018/10/word-embeddings/">词向量</a>。</p>
<ul>
<li>基于上下文的词嵌入</li>
</ul>
<p>为了解决上述问题，我们需要区分在不同上下文下词的含义。给定一段文本 <code>$x_1, x_2, \dotsc, x_T$</code> 其中每段标记 <code>$x_t \in \mathcal{V}$</code> 为一个词或子词，<code>$x_t$</code> 基于上下文的表示依赖于整段文本。</p>
<p><code>$$ \left[\mathbf{h}_1, \mathbf{h}_2, \dotsc, \mathbf{h}_T\right] = f_{\text{enc}} \left(x_1, x_2, \dotsc, x_T\right) $$</code></p>
<p>其中，<code>$f_{\text{enc}} \left(\cdot\right)$</code> 为神经编码器，<code>$\mathbf{h}_t$</code> 为标记 <code>$x_t$</code> 的<strong>基于上下文的嵌入</strong>或<strong>动态嵌入</strong>。</p>
<h3 id="神经上下文编码器">神经上下文编码器</h3>
<p>神经上下文编码器大致可以分为 3 类：</p>
<ol>
<li><strong>基于卷积的模型</strong>：基于卷积的模型通过卷积操作从一个词的邻居中聚合局部信息来捕获这个词的含义 <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>。
<figure>
    <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/convolutional-model.png" class="lazyload"/>
    <figcaption><p class="figcaption">Convolutional model</p></figcaption>
  </figure></li>
<li><strong>基于序列的模型</strong>：基于序列的模型采用 RNNs（LSTM <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 和 GRU <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>） 来捕获词的上下文信息。实际中，我们采用双向的 RNNs 从词的两端收集信息，不过整体效果容易收到长期依赖问题的影响。
<figure>
    <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/sequential-model.png" class="lazyload"/>
    <figcaption><p class="figcaption">Sequential model</p></figcaption>
  </figure></li>
<li><strong>基于图的模型</strong>：基于图的模型将字作为图中的一个节点来学习上下文表示，这个图通常是一个词之间预定义的语言结构，例如：语法结构 <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup> <sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> 或语义关系 <sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup>。尽管基于语言学的图结构能提供有用的信息，但如何构建一个好的图结构则成为了难题。除此之外，基于语言学的图结构需要依赖专家知识和外部工具，例如：依存句法分析等。事实上，我们会采用一个更直接的方式去学习任意两个词之间的关系，通常连接的权重可以通过自注意力机制自动计算得出。Transformer <sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup> 是一个采用了全链接自注意力架构的实现，同时也采用了位置嵌入（positional embedding），层标准化（layer normalization）和残差连接（residual connections）等网络设计理念。
<figure>
    <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/fully-connected-graph-based-model.png" class="lazyload"/>
    <figcaption><p class="figcaption">Fully-connected graph-based model</p></figcaption>
  </figure></li>
</ol>
<h3 id="为什么预训练">为什么预训练</h3>
<p>对于大多数的 NLP 任务，构建一个大规模的有标签的数据集是一项很大的挑战。相反，大规模的无标签语料是相对容易构建的，为了充分利用这些无标签数据，我们可以先利用它们获取一个好的语言表示，再将这些表示用于其他任务。预训练的好处如下：</p>
<ol>
<li>预训练可以从大规模语料中学习得到通用的语言表示，并用于下游任务。</li>
<li>预训练提供了更优的模型初始化方法，有助于提高模型的泛化能力和加速模型收敛。</li>
<li>预训练可以当作是在小数据集上一种避免过拟合的正则化方法。</li>
</ol>
<h3 id="预训练任务">预训练任务</h3>
<p>预训练任务对于学习语言的通用表示来说至关重要。通常情况下，预训练任务具有挑战性，同时需要大量训练数据。我们将预训练任务划分为 3 类：</p>
<ol>
<li><strong>监督学习</strong>，即从包含输入输出对的训练数据中学习一个由输入到输出的映射函数。</li>
<li><strong>非监督学习</strong>，即从无标签数据获取一些固有的知识，例如：聚类，密度，潜在表征等。</li>
<li><strong>自监督学习</strong>，是监督学习和非监督学习的混合体，核心思想是对于输入的一部分利用其他部分进行预测。</li>
</ol>
<h4 id="语言模型-language-modeling-lm">语言模型（Language Modeling，LM）</h4>
<p>NLP 中最常见的非监督任务为概率语言建模，这是一个经典的概率密度估计问题。给定一个文本序列 <code>$x_{1:T} = \left[x_1, x_2, \dotsc, x_T\right]$</code>，他的联合概率 <code>$p \left(x_{1:T}\right)$</code> 可以分解为：</p>
<p><code>$$ p \left(x_{1:T}\right) = \prod_{t=1}^{y}{p \left(x_t \mid x_{0:t-1}\right)} $$</code></p>
<p>其中 <code>$x_0$</code> 为序列开始的特殊标记。条件概率 <code>$p \left(x_t \mid x_{0:t-1}\right)$</code> 可以通过给定的语言上下文 <code>$x_{0:t-1}$</code> 词的概率分布进行建模估计。上下文 <code>$x_{0:t-1}$</code> 可以通过神经编码器 <code>$f_{\text{enc}} \left(\cdot\right)$</code> 进行建模，则条件概率可以表示为：</p>
<p><code>$$ p \left(x_t | x_{0:t-1}\right) = g_{\text{LM}} \left(f_{\text{enc}} \left(x_{0:t-1}\right)\right) $$</code></p>
<p>其中，<code>$g_{\text{LM}}$</code> 为预测层。</p>
<h4 id="遮罩语言模型-masked-language-modeling-mlm">遮罩语言模型（Masked Language Modeling，MLM）</h4>
<p>大致上来说，MLM 首先将输入句子的一些词条进行遮挡处理，其次再训练模型利用剩余的部分预测遮挡的部分。这种预训练方法会导致在预训练（pre-training）阶段和微调（fine-tuning）阶段的不一致，因为在微调阶段遮挡标记并未出现，BERT <sup id="fnref:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 通过一个特殊的符号 <code>[MASK]</code> 对其进行处理。</p>
<h5 id="sequence-to-sequence-mlm-seq2seq-mlm">Sequence-to-Sequence MLM (Seq2Seq MLM)</h5>
<p>MLM 通常以一个分类问题进行求解，我们将遮挡后的序列输入到一个神经编码器，再将输出向量传给一个 Softmax 分类器来预测遮挡的字符。我们可以采用 Encoder-Decoder（Seq2Seq）网络结构，将遮挡的序列输入到 Encoder，Decoder 则会循序的产生被遮挡的字符。MASS <sup id="fnref:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 和 T5 <sup id="fnref:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup> 均采用了这种序列到序列的 MLM 结构，这种结构对 Seq2Seq 风格的下游任务很有帮助，例如：问答，摘要和机器翻译。</p>
<h5 id="enhanced-masked-language-modeling-e-mlm">Enhanced Masked Language Modeling (E-MLM)</h5>
<p>同时，大量研究对于 BERT 所使用的遮罩处理进行了改进。RoBERTa <sup id="fnref:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup> 采用了一种动态的遮罩处理。UniLM 将遮罩任务拓展到 3 种不同的类型：单向的，双向的和 Seq2Seq 类型的。</p>
<h4 id="排列语言模型-permuted-language-modeling-plm">排列语言模型（Permuted Language Modeling，PLM）</h4>
<p>在 MLM 中一些特殊字符（例如：<code>[MASK]</code>）在下游任务中是无用的，为了解决这个问题，XLNet <sup id="fnref:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup> 提出了一种排列语言模型（Permuted Language Modeling，PLM）用于替代 MLM。简言之，PLM 是对输入序列的排列进行语言建模。给定一个序列，从所有可能的排列中随机抽样得到一个排列，将排列后的序列中的一些字符作为模型的预测目标，利用其他部分和目标的自然位置进行训练。需要注意的是这种排列并不会影响序列的自然位置，其仅用于定义字符预测的顺序。</p>
<h4 id="去噪自编码-denoising-autoencoder-dae">去噪自编码（Denoising Autoencoder，DAE）</h4>
<p>DAE 旨在利用部分有损的输入恢复原始无损的输入。对于语言模型，例如 Seq2Seq 模型，可以采用标准的 Transformer 来重构原始文本。有多种方式可以对文本进行破坏 <sup id="fnref:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup>：</p>
<ol>
<li>字符遮罩：随机采样字符并将其替换为 <code>[MASK]</code>。</li>
<li>字符删除：随机的从输入中删除字符，不同于字符遮罩，模型需要确定丢失字符的位置。</li>
<li>文本填充：采样一段文本并将其替换为一个 <code>[MASK]</code>，每段文本的长度服从泊松分布（$\lambda = 3$），模型需要确定这段文本中缺失的字符个数。</li>
<li>句子重排：将文档以终止标点进行分割，再进行随机排序。</li>
<li>文档旋转：随机均匀地选择一个字符，对文档进行旋转使得这个字符作为文档的起始字符，模型需要确定文档真实的起始位置。</li>
</ol>
<h4 id="对比学习-contrastive-learning-ctl">对比学习（Contrastive Learning，CTL）</h4>
<p>对比学习 <sup id="fnref:21"><a href="#fn:21" class="footnote-ref" role="doc-noteref">21</a></sup> 假设一些观测到的文本对比随机采样的文本具有更相似的语义。对于文本对 <code>$\left(x, y\right)$</code> 通过最小化如下目标函数来学习评分函数 <code>$s \left(x, y\right)$</code>：</p>
<p><code>$$ \mathbb{E}_{x, y^+, y^-} \left[- \log \dfrac{\exp \left(s \left(x, y^+\right)\right)}{\exp \left(s \left(x, y^+\right)\right) + \exp \left(s \left(x, y^-\right)\right)}\right] $$</code></p>
<p>其中，<code>$\left(x, y^+\right)$</code> 为一个相似对，<code>$y^-$</code> 对于 <code>$x$</code> 而言假定为不相似，<code>$y^+$</code> 和 <code>$y^-$</code> 通常称之为正样本和负样本。评分函数 <code>$s \left(x, y\right)$</code> 通过一个神经编码器计算可得，<code>$s \left(x, y\right) = f^{\top}_{\text{enc}} \left(x\right) f_{\text{enc}} \left(y\right)$</code> 或 <code>$s \left(x, y\right) = f_{\text{enc}} \left(x \oplus y\right)$</code>。CTL 的核心思想是“通过对比进行学习”。</p>
<p>下图展示了预训练模型的分类和部分代表模型：</p>

<div class="box fancy-figure caption-position-bottom caption-effect-fade" >
  <figure class="photoswipe-figure" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
    <div class="img">
      <img itemprop="thumbnail" src="/images/cn/2020-03-28-pre-trained-model-for-nlp/ptms.png" alt="预训练模型分类及代表性模型"/>
    </div>
    <a href="/images/cn/2020-03-28-pre-trained-model-for-nlp/ptms.png" itemprop="contentUrl"></a>
      <figcaption>
          <p>预训练模型分类及代表性模型</p>
      </figcaption>
  </figure>
</div>

<h3 id="应用于下游任务">应用于下游任务</h3>
<h4 id="如何迁移">如何迁移</h4>
<h5 id="选择合适的预训练任务-模型架构和语料">选择合适的预训练任务，模型架构和语料</h5>
<p>不同的 PTMs 在相同的下游任务上有着不同的效果，这是因为 PTMs 有着不同的预训练任务，模型架构和语料。</p>
<ol>
<li>目前，语言模型是最流行的预训练任务，同时也可以有效地解决很多 NLP 问题。但是不同的预训练任务有着自己的侧重，在不同的任务上会有不同的效果。例如：NSP 任务使得 PTM 可以理解两句话之间的关系，因此 PTM 可以在例如问答（Question Answering，QA）和自然语言推理（Natural Language Inference，NLI）等下游任务上表现更好。</li>
<li>PTM 的网络架构对下游任务也至关重要。例如：尽管 BERT 可以处理大多数自然语言理解任务，对其很难生成语言。</li>
<li>下游任务的数据分布应该和 PTM 训练所用语料相似。目前，大量现成的 PTM 仅可以快速地用于特定领域或特定语言的下游任务上。</li>
</ol>
<h5 id="选择合适的网络层">选择合适的网络层</h5>
<p>给定一个预训练的模型，不同的网络层捕获了不同的信息，例如：词性标记（POS tagging），语法（parsing），长期依赖（long-term dependencies），语义角色（semantic roles），指代（coreference）等。Tenney <sup id="fnref:22"><a href="#fn:22" class="footnote-ref" role="doc-noteref">22</a></sup> 等人发现 BERT 表示方式类似传统的 NLP 流程：基础的句法信息出现在浅层的网络中，高级的语义信息出现在更高的层级中。</p>
<p>令 <code>$\mathbf{H}^{\left(l\right)} \left(1 \leq l \leq L\right)$</code> 表示共 <code>$L$</code> 层的预训练模型的第 <code>$l$</code> 层表示，<code>$g \left(\cdot\right)$</code> 表示用于特定任务的的模型。一般有 3 中情况选择表示：</p>
<ol>
<li>Embedding Only：一种情况是仅选用预训练模型的静态嵌入，模型的其他部分仍需作为一个任务从头训练。这种情况不能够获取到一些有用的深层信息，词嵌入仅能够捕获词的语义信息。</li>
<li>Top Layer：最简单有效的方式是将网络的顶层表示输入到模型中 <code>$g \left(\mathbf{H}^{\left(L\right)}\right)$</code>。</li>
<li>All Layers：另一种更灵活的方式是自动选择最合适的层，例如 ELMo：
<code>$$ \mathbf{r}_t = \gamma \sum_{l=1}^{L}{\alpha_l \mathbf{h}^{\left(l\right)}_t} $$</code>
其中 <code>$\alpha_l$</code> 是层 <code>$l$</code> 的 softmax 归一的权重，<code>$\gamma$</code> 是用于缩放预训练模型输出向量的一个标量值，再将不同层的混合输出输入到后续模型中 <code>$g \left(\mathbf{r}_t\right)$</code>。</li>
</ol>
<h5 id="是否微调">是否微调</h5>
<p>目前，主要有两种方式进行模型迁移：特征提取（预训练模型的参数是固定的）和模型微调（预训练模型的参数是经过微调的）。当采用特征提取时，预训练模型可以被看作是一个特征提取器。除此之外，我们应该采用内部层作为特征，因为他们通常是最适合迁移的特征。尽管两种不同方式都能对大多数 NLP 任务效果有显著提升，但以特征提取的方式需要更复杂的特定任务的架构。因此，微调是一种更加通用和方便的处理下游任务的方式。</p>
<h4 id="微调策略">微调策略</h4>
<p>随着 PTMs 网络层数的加深，其捕获的表示使得下游任务变得越来越简单，因此整个模型中用于特定任务的网络层一般比较简单，微调已经成为了采用 PTMs 的主要方式。但是微调的过程通常是比较不好预估的，即使采用相同的超参数，不同的随机数种子也可能导致差异较大的结果。除了标准的微调外，如下为一些有用的微调策略：</p>
<h5 id="两步骤微调">两步骤微调</h5>
<p>一种方式是两阶段的迁移，在预训练和微调之间引入了一个中间阶段。在第一个阶段，PTM 通过一个中间任务或语料转换为一个微调后的模型，在第二个阶段，再利用目标任务进行微调。</p>
<h5 id="多任务微调">多任务微调</h5>
<p>在多任务学习框架下对其进行微调。</p>
<h5 id="利用额外模块进行微调">利用额外模块进行微调</h5>
<p>微调的主要缺点就是其参数的低效性。每个下游模型都有其自己微调好的参数，因此一个更好的解决方案是将一些微调好的适配模块注入到 PTMs 中，同时固定原始参数。</p>
<h3 id="开放资源">开放资源</h3>
<h4 id="ptms-开源实现">PTMs 开源实现：</h4>
<table>
  <thead>
      <tr>
          <th>项目</th>
          <th>框架</th>
          <th>PTMs</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/tmikolov/word2vec">word2vec</a></td>
          <td>-</td>
          <td>CBOW, Skip-Gram</td>
      </tr>
      <tr>
          <td><a href="https://nlp.stanford.edu/projects/glove">GloVe</a></td>
          <td>-</td>
          <td>Pre-trained word vectors</td>
      </tr>
      <tr>
          <td><a href="https://github.com/facebookresearch/fastText">FastText</a></td>
          <td>-</td>
          <td>Pre-trained word vectors</td>
      </tr>
      <tr>
          <td><a href="https://github.com/huggingface/transformers">Transformers</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i> &amp; <i class="icon icon-tensorflow">TF</i></td>
          <td>BERT, GPT-2, RoBERTa, XLNet, etc.</td>
      </tr>
      <tr>
          <td><a href="https://github.com/pytorch/fairseq">Fairseq</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td>English LM, German LM, RoBERTa, etc.</td>
      </tr>
      <tr>
          <td><a href="https://github.com/%EF%AC%82airNLP/%EF%AC%82air">Flair</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td>BERT, ELMo, GPT, RoBERTa, XLNet, etc.</td>
      </tr>
      <tr>
          <td><a href="https://github.com/allenai/allennlp">AllenNLP</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td>ELMo, BERT, GPT-2, etc.</td>
      </tr>
      <tr>
          <td><a href="https://github.com/fastnlp/fastNLP">FastNLP</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td>BERT, RoBERTa, GPT, etc.</td>
      </tr>
      <tr>
          <td><a href="https://github.com/ymcui/Chinese-BERT-wwm">Chinese-BERT</a></td>
          <td>-</td>
          <td>BERT, RoBERTa, etc. (for Chinese)</td>
      </tr>
      <tr>
          <td><a href="https://github.com/google-research/bert">BERT</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
          <td>BERT, BERT-wwm</td>
      </tr>
      <tr>
          <td><a href="https://github.com/pytorch/fairseq/tree/master/examples/roberta">RoBERTa</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/zihangdai/xlnet">XLNet</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/google-research/ALBERT">ALBERT</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/google-research/text-to-text-transfer-transformer">T5</a></td>
          <td><i class="icon icon-tensorflow">TF</i></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/thunlp/ERNIE">ERNIE(THU)</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i></td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/PaddlePaddle/ERNIE">ERNIE(Baidu)</a></td>
          <td><i class="icon icon-paddlepaddle"></i> PaddlePaddle</td>
          <td></td>
      </tr>
      <tr>
          <td><a href="https://github.com/huggingface/transformers">Hugging Face</a></td>
          <td><i class="icon icon-pytorch">PyTorch</i> &amp; <i class="icon icon-tensorflow">TF</i></td>
          <td>很多&hellip;</td>
      </tr>
  </tbody>
</table>
<h4 id="论文列表和-ptms-相关资源">论文列表和 PTMs 相关资源：</h4>
<table>
  <thead>
      <tr>
          <th>资源</th>
          <th>URL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>论文列表</td>
          <td><a href="https://github.com/thunlp/PLMpapers">https://github.com/thunlp/PLMpapers</a></td>
      </tr>
      <tr>
          <td>论文列表</td>
          <td><a href="https://github.com/tomohideshibata/BERT-related-papers">https://github.com/tomohideshibata/BERT-related-papers</a></td>
      </tr>
      <tr>
          <td>论文列表</td>
          <td><a href="https://github.com/cedrickchee/awesome-bert-nlp">https://github.com/cedrickchee/awesome-bert-nlp</a></td>
      </tr>
      <tr>
          <td>Bert Lang Street</td>
          <td><a href="https://bertlang.unibocconi.it">https://bertlang.unibocconi.it</a></td>
      </tr>
      <tr>
          <td>BertViz</td>
          <td><a href="https://github.com/jessevig/bertviz">https://github.com/jessevig/bertviz</a></td>
      </tr>
  </tbody>
</table>
<h2 id="预训练模型">预训练模型</h2>
<h3 id="cove-2017">CoVe (2017) <sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></h3>
<p>首先，给定一个源语言序列 <code>$w^x = \left[w^x_1, \dotsc, w^x_n\right]$</code> 和一个翻译目标语言序列 <code>$w^z = \left[w^z_1, \dotsc, w^z_n\right]$</code>。令 <code>$\text{GloVe} \left(w^x\right)$</code> 为词 <code>$w^x$</code> 对应的 GloVe 向量，<code>$z$</code> 为 <code>$w^z$</code> 中的词随机初始化的词向量。将 <code>$\text{GloVe} \left(w^x\right)$</code> 输入到一个标准的两层 biLSTM 网络中，称之为 MT-LSTM，MT-LSTM 用于计算序列的隐含状态如下：</p>
<p><code>$$ h = \text{MT-LSTM} \left(\text{GloVe} \left(w^x\right)\right) $$</code></p>
<p>对于机器翻译，MT-LSTM 的注意力机制的解码器可以对于输出的词在每一步产生一个分布 <code>$p \left(\hat{w}^z_t \mid H, w^z_1, \dotsc, w^z_{t-1}\right)$</code>。在 <code>$t$</code> 步，解码器利用一个两层的单向 LSTM 基于之前目标词嵌入 <code>$z_{t-1}$</code> 和一个基于上下文调整的隐含状态 <code>$\tilde{h}_{t-1}$</code> 生成一个隐含状态 <code>$h^{\text{dec}}_t$</code>：</p>
<p><code>$$ h^{\text{dec}}_t = \text{LSTM} \left(\left[z_{t-1}; \tilde{h}_{t-1}\right], h^{\text{dec}}_{t-1}\right) $$</code></p>
<p>之后解码器计算每一步编码到当前解码状态的注意力权重 <code>$\alpha$</code>：</p>
<p><code>$$ \alpha_t = \text{softmax} \left(H \left(W_1 h^{\text{dec}}_t + b_1\right)\right) $$</code></p>
<p>其中 <code>$H$</code> 表示 <code>$h$</code> 按照时间维度的堆叠。之后解码器将这些权重作为相关性用于计算基于上下文调整的隐含状态 <code>$\tilde{h}$</code>：</p>
<p><code>$$ \tilde{h}_t = \text{tanh} \left(W_2 \left[H^{\top} \alpha_t; h^{\text{dec}}_t\right] + b_2\right) $$</code></p>
<p>最后，输出词的分布通过基于上下文调整的隐含状态计算可得：</p>
<p><code>$$ p \left(\hat{w}^z_t \mid H, w^z_1, \dotsc, w^z_{t-1}\right) = \text{softmax} \left(W_{\text{out}} \tilde{h}_t + b_{\text{out}}\right) $$</code></p>
<p>CoVe 将 MT-LSTM 学习到的表示迁移到下游任务中，令 <code>$w$</code> 表示文字序列，<code>$\text{GloVe} \left(w\right)$</code> 表示对应的 GloVe 向量，则：</p>
<p><code>$$ \text{CoVe} \left(w\right) = \text{MT-LSTM} \left(\text{GloVe} \left(w\right)\right) $$</code></p>
<p>表示由 MT-LSTM 产生的上下文向量，对于分类和问答任务，有一个输入序列 <code>$w$</code>，我们可以将 GloVe 和 CoVe 向量进行拼接作为其嵌入表示：</p>
<p><code>$$ \tilde{w} = \left[\text{GloVe} \left(w\right); \text{CoVe} \left(w\right)\right] $$</code></p>
<p>CoVe 网络架构示意图如下：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/cove.png" class="lazyload"/>
  
</figure>
<h3 id="elmo-2018">ELMo (2018) <sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></h3>
<p>在 ELMo 模型中，对于每个词条 <code>$t_k$</code>，一个 <code>$L$</code> 层的 biLM 可以计算得到 <code>$2L + 1$</code> 个表示：</p>
<p><code>$$ \begin{aligned} R_k &amp;= \left\{\mathbf{x}^{LM}_k, \overrightarrow{\mathbf{h}}^{LM}_{k, j}, \overleftarrow{\mathbf{h}}^{LM}_{k, j} \mid j = 1, \dotsc, L \right\} \\ &amp;= \left\{\mathbf{h}^{LM}_{k, j} \mid j = 0, \dotsc, L\right\} \end{aligned} $$</code></p>
<p>其中 <code>$\mathbf{h}^{LM}_{k, 0}$</code> 为词条的嵌入层，<code>$\mathbf{h}^{LM}_{k, j} = \left[\overrightarrow{\mathbf{h}}^{LM}_{k, j}; \overleftarrow{\mathbf{h}}^{LM}_{k, j}\right]$</code> 为每个 biLSTM 层。</p>
<p>对于下游任务，ELMo 将 <code>$R$</code> 中的所有层汇总成一个向量 <code>$\mathbf{ELMo}_k = E \left(R_k; \mathbf{\Theta}_e\right)$</code>。在一些简单的案例中，ELMo 仅选择顶层，即：<code>$E \left(R_k\right) = \mathbf{h}^{LM}_{k, L}$</code>。更通用的，对于一个特定的任务，我们可以计算一个所有 biLM 层的加权：</p>
<p><code>$$ \mathbf{ELMo}^{task}_k = E \left(R_k; \Theta^{task}\right) = \gamma^{task} \sum_{j=0}^{L}{s^{task}_j \mathbf{h}^{LM}_{k, j}} $$</code></p>
<p>其中，<code>$s^{task}$</code> 表示 softmax 归一化后的权重，<code>$\gamma^{task}$</code> 允许模型对整个 ELMo 向量进行缩放。<code>$\gamma$</code> 对整个优化过程具有重要意义，考虑每个 biLM 层的激活具有不同的分布，在一些情况下这相当于在进行加权之前对每一个 biLM 层增加了层标准化。</p>
<p>ELMo 网络架构示意图如下 <sup id="fnref:23"><a href="#fn:23" class="footnote-ref" role="doc-noteref">23</a></sup>：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/elmo.png" class="lazyload"/>
  
</figure>
<h3 id="gpt-2018">GPT (2018) <sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></h3>
<p>给定一个语料 <code>$\mathcal{U} = \left\{u_1, \dotsc, u_n\right\}$</code>，使用标准的语言建模目标来最大化如下似然：</p>
<p><code>$$ L_1 \left(\mathcal{U}\right) = \sum_{i} \log P \left(u_i \mid u_{i-k}, \dotsc, u_{i-1}; \Theta\right) $$</code></p>
<p>其中，<code>$k$</code> 为上下文窗口的大小，条件概率 <code>$P$</code> 通过参数为 <code>$\Theta$</code> 的神经网络进行建模。GPT 中使用了一个多层的 Transformer Decoder 作为语言模型。模型首先对输入上下文词条应用多头自注意力机制，再通过按位置的前馈层产生目标词条的输出分布：</p>
<p><code>$$ \begin{aligned} h_0 &amp;= UW_e + W_p \\ h_l &amp;= \text{transformer_black} \left(h_{l-1}\right), \forall i \in \left[1, n\right] \\ P \left(u\right) &amp;= \text{softmax} \left(h_n W^{\top}_e\right) \end{aligned} $$</code></p>
<p>其中，<code>$U = \left(u_{-k}, \dotsc, u_{-1}\right)$</code> 为词条的上下文向量，<code>$n$</code> 为网络层数，<code>$W_e$</code> 为词条的嵌入矩阵，<code>$W_p$</code> 为位置嵌入矩阵。</p>
<p>给定一个有标签的数据集 <code>$\mathcal{C}$</code>，其中包含了输入词条序列 <code>$x^1, \dotsc, x^m$</code> 和对应的标签 <code>$y$</code>。利用上述预训练的模型获得输入对应的最后一个 Transformer 的激活输出 <code>$h^m_l$</code>，之后再将其输入到一个参数为 <code>$W_y$</code> 的线性输入层中预测 <code>$y$</code>：</p>
<p><code>$$ P \left(y \mid x^1, \dotsc, x^m\right) = \text{softmax} \left(h^m_l W_y\right) $$</code></p>
<p>模型通过最小化如下损失进行优化：</p>
<p><code>$$ L_2 \left(\mathcal{C}\right) = \sum_{\left(x, y\right)} \log P \left(y \mid x^1, \dotsc, x^m\right) $$</code></p>
<p>研究还发现将语言建模作为微调的附加目标可以帮助提高模型的泛化能力，同时可以加速模型收敛。GPT 中采用如下的优化目标：</p>
<p><code>$$ L_3 \left(\mathcal{C}\right) = L_2 \left(\mathcal{C}\right) + \lambda L_1 \left(\mathcal{C}\right) $$</code></p>
<p>GPT 网络架构示意图如下：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/gpt.png" class="lazyload"/>
  
</figure>
<h3 id="bert-2018">BERT (2018) <sup id="fnref1:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup></h3>
<p>BERT 采用了一中基于 Vaswani <sup id="fnref1:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup> 所提出模型的多层双向 Transformer 编码器。在 BERT 中，令 <code>$L$</code> 为 Transformer Block 的层数，<code>$H$</code> 为隐层大小，<code>$A$</code> 为自注意力头的数量。在所有情况中，设置前馈层的大小为 <code>$4H$</code>，BERT 提供了两种不同大小的预训练模型：</p>
<ul>
<li><code>$\text{BERT}_{\text{BASE}}$</code>：<code>$L=12, H=768, A=12$</code>，参数总量为 100 M。</li>
<li><code>$\text{BERT}_{\text{LARGE}}$</code>：<code>$L=24, H=1024, A=16$</code>，参数总量为 340 M。</li>
</ul>
<p><code>$\text{BERT}_{\text{BASE}}$</code> 采用了同 GPT 相同的模型大小用于比较，不同与 GPT，BERT 使用了双向的注意力机制。在文献中，双向 Transformer 通常称之为 Transformer 编码器，仅利用左边上下文信息的 Transformer 由于可以用于文本生成被称之为 Transformer 解码器。BERT，GPT 和 ELMo 之间的不同如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/bert-gpt-elmo-model-architectures.png" class="lazyload"/>
  
</figure>
<p>BERT 的输入表示既可以表示一个单独的文本序列，也可以表示一对文本序列（例如：问题和答案）。对于一个给定的词条，其输入表示由对应的词条嵌入，分割嵌入和位置嵌入三部分加和构成，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/bert-input-representation.png" class="lazyload"/>
  
</figure>
<p>具体的有：</p>
<ul>
<li>采用一个包含 30,000 个词条的 WordPiece 嵌入 <sup id="fnref:24"><a href="#fn:24" class="footnote-ref" role="doc-noteref">24</a></sup>。</li>
<li>位置嵌入最大支持 512 个词条。</li>
<li>序列的第一字符采用特殊的分类嵌入 <code>[CLS]</code>，其最终的隐含状态在分类任务中用于汇总整个序列的表示，对于非分类任务则忽视该向量。</li>
<li>句子对被整合成一个序列，首先利用一个特殊词条 <code>[SEP]</code> 对句子进行分割，其次对于第一个句子中的每个词条叠加一个学习到的 A 句子嵌入，对于第二个句子中的每个词条叠加一个学习到的 B 句子嵌入。</li>
<li>对于一个单独的句子，仅使用 A 句子嵌入。</li>
</ul>
<p>在预训练阶段，BERT 采用了两个无监督预测任务：</p>
<ol>
<li>遮罩的语言模型（Masked LM，MLM）<br>
不同于一般的仅利用 <code>[MASK]</code> 进行遮挡，BERT 选择采用 80% 的 <code>[MASK]</code>，10% 的随机词和 10% 保留原始词的方式对随机选择的 15% 的词条进行遮挡处理。由于编码器不知会预测哪个词或哪个词被随机替换了，这迫使其必须保留每个输入词条的分布式上下文表示。同时 1.5% 的随机替换也不会过多的损害模型的理解能力。</li>
<li>预测是否为下一个句子（Next Sentence Prediction）<br>
一些重要的下游任务，例如问答（Question Answering，QA）和自然语言推断（Natural Language Inference，NLI）是基于两个句子之间关系的理解，这是语言建模无法直接捕获的。BERT 通过训练一个预测是否为下一个句子的二分类任务来实现，对于一个句子对 A 和 B，50% 的 B 是句子 A 真实的下一句，剩余 50% 为随机抽取的。</li>
</ol>
<p>基于 BERT 的不同下游任务的实现形式如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/bert-task-specific-models.png" class="lazyload"/>
  
</figure>
<h3 id="unilm-2019">UniLM (2019) <sup id="fnref:25"><a href="#fn:25" class="footnote-ref" role="doc-noteref">25</a></sup></h3>
<p>给定一个输入序列 <code>$x = x_1 \cdots x_{|x|}$</code>，UniLM 通过下图的方式获取每个词条的基于上下文的向量表示。整个预训练过程利用单向的语言建模（unidirectional LM），双向的语言建模（bidirectional LM）和 Seq2Seq 语言建模（sequence-to-sequence LM）优化共享的 Transformer 网络。</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/unilm.png" class="lazyload"/>
  
</figure>
<p>输入序列 <code>$x$</code> 对于单向语言模型而言是一个分割的文本，对于双向语言模型和 Seq2Seq 语言模型而言是一对打包的分割文本。UniLM 在输入的起始位置添加特殊的 <code>[SOS]</code> （start-of-sequence），在结尾处添加 <code>[EOS]</code>（end-of-sequence）。<code>[EOS]</code> 对于自然语言理解（NLU）任务可以标记句子之间的界线，对于自然语言生成（NLG）任务可以确定解码过程停止的时间。输入的表示同 BERT 一样，文本利用 WordPiece 进行分割，对于每个输入词条，其向量表示为对应的词条嵌入，位置嵌入和分割嵌入的汇总。</p>
<p>对于输入向量 <code>$\left\{\mathbf{x}_i\right\}^{|x|}_{i=1}$</code> 首先将其输入到隐层 <code>$\mathbf{H}^0 = \left[\mathbf{x}_1, \dotsc, \mathbf{x}_{|x|}\right]$</code>，之后使用一个 <code>$L$</code> 层的 Transformer <code>$\mathbf{H}^l = \text{Transformer}_l \left(\mathbf{H}^{l-1}\right), l \in \left[1, L\right]$</code> 对每一层 <code>$\mathbf{H}^l = \left[\mathbf{h}^l_1, \dotsc, \mathbf{h}^l_{|x|}\right]$</code> 进行上下文表示编码。在每个 Tansformer 块中，使用多头自注意力机制对输出向量和上一层进行汇总，第 <code>$l$</code> 层 Transformer 自注意力头 <code>$\mathbf{A}_l$</code> 的输入通过如下方式计算：</p>
<p>`$$
\begin{aligned}
\mathbf{Q} &amp;= \mathbf{H}^{l-1} \mathbf{W}^Q_l, \mathbf{K} = \mathbf{H}^{l-1} \mathbf{W}^K_l, \mathbf{V} = \mathbf{H}^{l-1} \mathbf{W}^W_l \
\mathbf{M}_{ij} &amp;=
\begin{cases}
0, &amp; \text{allow to attend} \</p>
<ul>
<li>\infty, &amp; \text{prevent from attending}
\end{cases} \
\mathbf{A}_l &amp;= \text{softmax} \left(\dfrac{\mathbf{Q} \mathbf{K}^{\top}}{\sqrt{d_k}} + \mathbf{M}\right) \mathbf{V}_l
\end{aligned}
$$`</li>
</ul>
<p>其中，上一层的输出 <code>$\mathbf{H}^{l-1} \in \mathbb{R}^{|x| \times d_h}$</code> 通过参数矩阵 <code>$\mathbf{W}^Q_l, \mathbf{W}^K_l, \mathbf{W}^V_l \in \mathbb{R}^{d_h \times d_k}$</code> 线性地映射为相应的 Query，Key 和 Value，遮罩矩阵 <code>$\mathbf{M} \in \mathbb{R}^{|x| \times |x|}$</code> 用于确定一对词条是否可以被相互连接。</p>
<h3 id="transformer-xl-2019">Transformer-XL (2019) <sup id="fnref:26"><a href="#fn:26" class="footnote-ref" role="doc-noteref">26</a></sup></h3>
<p>将 Transformer 或注意力机制应用到语言建模中的核心问题是如何训练 Transformer 使其有效地将一个任意长文本编码为一个固定长度的表示。Transformer-XL 将整个语料拆分为较短的段落，仅利用每段进行训练并忽略之前段落的上下文信息。这种方式称之为 Vanilla Model <sup id="fnref:27"><a href="#fn:27" class="footnote-ref" role="doc-noteref">27</a></sup>，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/transformer-xl-vanilla-model.png" class="lazyload"/>
  
</figure>
<p>在这种训练模式下，无论是前向还是后向信息都不会跨越分割的段落进行传导。利用固定长度的上下文主要有两个弊端：</p>
<ol>
<li>这限制了最大依赖的长度，虽然自注意力机制不会像 RNN 一样受到梯度弥散的影响，但 Vanilla Model 也不能完全利用到这个优势。</li>
<li>虽然可以利用补全操作来实现句子或其他语义的分割，但实际上通常会简单的将一个长文本截断成一个固定长度的分割，这样会产生上下文分裂破碎的问题。</li>
</ol>
<p>为了解决这个问题，Transformer-XL 采用了一种循环机制的 Transformer。在训练阶段，在处理新的分割段落时，之前分割分部分的隐含状态序列将被**固定（fixed）<strong>和</strong>缓存（cached）**下来作为一个扩展的上下文被复用参与计算，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/transformer-xl-model.png" class="lazyload"/>
  
</figure>
<p>虽然梯度仍仅限于这个分割段落内部，但网络可以从历史中获取信息，从而实现对长期依赖的建模。令两个长度为 <code>$L$</code> 的连续分割段落为 <code>$\mathbf{s}_{\tau} = \left[x_{\tau, 1}, \dotsc, x_{\tau, L}\right]$</code> 和 <code>$\mathbf{s}_{\tau + 1} = \left[x_{\tau + 1, 1}, \dotsc, x_{\tau + 1, L}\right]$</code>，第 <code>$\tau$</code> 段分割 <code>$\mathbf{s}_{\tau}$</code> 的第 <code>$n$</code> 层隐含状态为 <code>$\mathbf{h}^n_{\tau} \in \mathbb{R}^{L \times d}$</code>，其中 <code>$d$</code> 为隐含维度。则对于分割段落 <code>$\mathbf{s}_{\tau + 1}$</code> 的第 <code>$n$</code> 层隐含状态通过如下方式进行计算：</p>
<p><code>$$ \begin{aligned} \tilde{\mathbf{h}}^{n-1}_{\tau + 1} &amp;= \left[\text{SG} \left(\mathbf{h}^{n-1}_{\tau}\right) \circ \mathbf{h}^{n-1}_{\tau + 1} \right] \\ \mathbf{q}^{n}_{\tau + 1}, \mathbf{k}^{n}_{\tau + 1}, \mathbf{v}^{n}_{\tau + 1} &amp;= \mathbf{h}^{n-1}_{\tau + 1} \mathbf{W}^{\top}_{q}, \tilde{\mathbf{h}}^{n-1}_{\tau + 1} \mathbf{W}^{\top}_{k}, \tilde{\mathbf{h}}^{n-1}_{\tau + 1} \mathbf{W}^{\top}_{v} \\ \mathbf{h}^{n}_{\tau + 1} &amp;= \text{Transformer-Layer} \left(\mathbf{q}^{n}_{\tau + 1}, \mathbf{k}^{n}_{\tau + 1}, \mathbf{v}^{n}_{\tau + 1}\right) \end{aligned} $$</code></p>
<p>其中，<code>$\text{SG} \left(\cdot\right)$</code> 表示停止梯度，<code>$\left[\mathbf{h}_u \circ \mathbf{h}_v\right]$</code> 表示将两个隐含序列按照长度维度进行拼接，<code>$\mathbf{W}$</code> 为模型的参数。与一般的 Transformer 相比，最大的不同在于 <code>$\mathbf{k}^n_{\tau + 1}$</code> 和 <code>$\mathbf{v}^n_{\tau + 1}$</code> 不仅依赖于 <code>$\tilde{\mathbf{h}}^{n-1}_{\tau - 1}$</code> 还依赖于之前分割段落的 <code>$\mathbf{h}^{n-1}_{\tau}$</code> 缓存。</p>
<p>在标准的 Transformer 中，序列的顺序信息通过位置嵌入 <code>$\mathbf{U} \in \mathbb{R}^{L_{\max} \times d}$</code> 提供，其中第 <code>$i$</code> 行 <code>$\mathbf{U}_i$</code> 对应一个分割文本内部的第 <code>$i$</code> 个<strong>绝对</strong>位置，<code>$L_{\max}$</code> 为最大可能长度。在 Transformer-XL 中则是通过一种<strong>相对</strong>位置信息对其进行编码，构建一个相对位置嵌入 <code>$\mathbf{R} \in \mathbb{R} ^{L_{\max} \times d}$</code>，其中第 <code>$i$</code> 行 <code>$\mathbf{R}_i$</code> 表示两个位置之间相对距离为 <code>$i$</code> 的嵌入表示。</p>
<p>对于一般的 Transformer，一个分割段落内部的 <code>$q_i$</code> 和 <code>$k_j$</code> 之间的注意力分数可以分解为：</p>
<p><code>$$ \begin{aligned} \mathbf{A}_{i, j}^{\mathrm{abs}} &amp;=\underbrace{\mathbf{E}_{x_{i}}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k} \mathbf{E}_{x_{j}}}_{(a)}+\underbrace{\mathbf{E}_{x_{i}}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k} \mathbf{U}_{j}}_{(b)} \\ &amp;+\underbrace{\mathbf{U}_{i}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k} \mathbf{E}_{x_{j}}}_{(c)}+\underbrace{\mathbf{U}_{i}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k} \mathbf{U}_{j}}_{(d)} \end{aligned} $$</code></p>
<p>利用相对位置思想，变化如下：</p>
<p><code>$$ \begin{aligned} \mathbf{A}_{i, j}^{\mathrm{rel}} &amp;=\underbrace{\mathbf{E}_{x_{i}}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k, E} \mathbf{E}_{x_{j}}}_{(a)}+\underbrace{\mathbf{E}_{x_{i}}^{\top} \mathbf{W}_{q}^{\top} \mathbf{W}_{k, R} \textcolor{blue}{\mathbf{R}_{i-j}}}_{(b)} \\ &amp;+\underbrace{\textcolor{red}{u^{\top}} \mathbf{W}_{k, E} \mathbf{E}_{x_{j}}}_{(c)}+\underbrace{\textcolor{red}{v^{\top}} \mathbf{W}_{k, R} \textcolor{blue}{\mathbf{R}_{i-j}}}_{(d)} \end{aligned} $$</code></p>
<ol>
<li>首先，利用相对位置 <code>$\textcolor{blue}{\mathbf{R}_{i-j}}$</code> 替代绝对位置嵌入 <code>$\mathbf{U}_j$</code>，这里 <code>$\mathbf{R}$</code> 采用的是无需学习的 sinusoid 编码矩阵 <sup id="fnref2:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup>。</li>
<li>其次，引入了一个可训练的参数 <code>$\textcolor{red}{u} \in \mathbb{R}^d$</code> 用于替换 <code>$\mathbf{U}^{\top}_i \mathbf{W}^{\top}_q$</code>。类似的，对于 <code>$\mathbf{U}^{\top} \mathbf{W}^{\top}_q$</code> 使用一个可训练的 <code>$\textcolor{red}{v} \in \mathbb{R}^d$</code> 替换。</li>
<li>最后，有意地划分了两个权重矩阵 <code>$\mathbf{W}_{k, E}$</code> 和 <code>$\mathbf{W}_{k, R}$</code> 用于生成基于内容的 Key 向量和基于位置的 Key 向量。</li>
</ol>
<p>这样，<code>$\left(a\right)$</code> 代表了基于内容的位置信息，<code>$\left(b\right)$</code> 捕获了内容无关的位置偏置，<code>$\left(c\right)$</code> 表示了一个全局的内容偏置，<code>$\left(d\right)$</code> 捕获了一个全局的位置偏置。</p>
<p>利用一个自注意力头计算 <code>$N$</code> 层的 Transformer-XL 的过程如下，对于 <code>$n = 1, \dotsc, N$</code> 有：</p>
<p><code>$$ \begin{aligned} \widetilde{\mathbf{h}}_{\tau}^{n-1}=&amp;\left[\mathrm{SG}\left(\mathbf{m}_{\tau}^{n-1}\right) \circ \mathbf{h}_{\tau}^{n-1}\right] \\ \mathbf{q}_{\tau}^{n}, \mathbf{k}_{\tau}^{n}, \mathbf{v}_{\tau}^{n}=&amp; \mathbf{h}_{\tau}^{n-1} {\mathbf{W}_{q}^{n}}^{\top}, \widetilde{\mathbf{h}}_{\tau}^{n-1} {\mathbf{W}_{k, E}^{n}}^{\top}, \widetilde{\mathbf{h}}_{\tau}^{n-1} {\mathbf{W}_{v}^{n}}^{\top} \\ \mathbf{A}_{\tau, i, j}^{n}=&amp; {\mathbf{q}_{\tau, i}^{n}}^{\top} \mathbf{k}_{\tau, j}^{n} + {\mathbf{q}_{\tau, i}^{n}}^{\top} \mathbf{W}_{k, R}^{n} \mathbf{R}_{i-j} \\ &amp;+u^{\top} \mathbf{k}_{\tau, j}+v^{\top} \mathbf{W}_{k, R}^{n} \mathbf{R}_{i-j} \\ \mathbf{a}_{\tau}^{n}=&amp; \text { Masked-Softmax }\left(\mathbf{A}_{\tau}^{n}\right) \mathbf{v}_{\tau}^{n} \\ \mathbf{o}_{\tau}^{n}=&amp; \text { LayerNorm } \left(\text{Linear}\left(\mathbf{a}_{\tau}^{n}\right)+\mathbf{h}_{\tau}^{n-1}\right) \\ \mathbf{h}_{\tau}^{n}=&amp; \text { Positionwise-Feed-Forward }\left(\mathbf{o}_{\tau}^{n}\right) \end{aligned} $$</code></p>
<h3 id="xlnet-2019">XLNet (2019) <sup id="fnref1:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup></h3>
<p>给定一个序列 <code>$\mathbf{X} = \left[x_1, \dotsc, x_T\right]$</code>，AR 语言模型通过最大化如下似然进行预训练：</p>
<p><code>$$ \max_{\theta} \quad \log p_{\theta}(\mathbf{x})=\sum_{t=1}^{T} \log p_{\theta}\left(x_{t} | \mathbf{x}_{&lt;t}\right)=\sum_{t=1}^{T} \log \frac{\exp \left(h_{\theta}\left(\mathbf{x}_{1: t-1}\right)^{\top} e\left(x_{t}\right)\right)}{\sum_{x^{\prime}} \exp \left(h_{\theta}\left(\mathbf{x}_{1: t-1}\right)^{\top} e\left(x^{\prime}\right)\right)} $$</code></p>
<p>其中，<code>$h_{\theta}\left(\mathbf{x}_{1: t-1}\right)$</code> 是由 RNNs 或 Transformer 等神经网络网络模型生成的上下文表示，<code>$e \left(x\right)$</code> 为 <code>$x$</code> 的嵌入。对于一个文本序列 <code>$\mathbf{x}$</code>，BERT 首先构建了一个遮罩的数据集 <code>$\hat{\mathbf{x}}$</code>，令被遮挡的词条为 <code>$\overline{\mathbf{x}}$</code>，通过训练如下目标来利用 <code>$\hat{\mathbf{x}}$</code> 重构 <code>$\overline{\mathbf{x}}$</code>：</p>
<p><code>$$ \max_{\theta} \quad \log p_{\theta}(\overline{\mathbf{x}} | \hat{\mathbf{x}}) \approx \sum_{t=1}^{T} m_{t} \log p_{\theta}\left(x_{t} | \hat{\mathbf{x}}\right)=\sum_{t=1}^{T} m_{t} \log \frac{\exp \left(H_{\theta}(\hat{\mathbf{x}})_{t}^{\top} e\left(x_{t}\right)\right)}{\sum_{x^{\prime}} \exp \left(H_{\theta}(\hat{\mathbf{x}})_{t}^{\top} e\left(x^{\prime}\right)\right)} $$</code></p>
<p>其中 <code>$m_t = 1$</code> 表示 <code>$x_t$</code> 是被遮挡的，<code>$H_{\theta}$</code> 是一个 Transformer 将一个长度为 <code>$T$</code> 的文本序列映射到一个隐含向量序列 <code>$H_{\theta}(\mathbf{x})=\left[H_{\theta}(\mathbf{x})_{1}, H_{\theta}(\mathbf{x})_{2}, \cdots, H_{\theta}(\mathbf{x})_{T}\right]$</code>。两种不同的预训练目标的优劣势如下</p>
<ol>
<li><strong>独立假设</strong>：BERT 中联合条件概率 <code>$p(\overline{\mathbf{x}} | \hat{\mathbf{x}})$</code> 假设在给定的 <code>$\hat{\mathbf{x}}$</code> 下，遮挡的词条 <code>$\overline{\mathbf{x}}$</code> 是相关独立的，而 AR 语言模型则没有这样的假设。</li>
<li><strong>输入噪声</strong>：BERT 在预训练是使用了特殊标记 <code>[MASK]</code>，在下游任务微调时不会出现，而 AR 语言模型则不会存在这个问题。</li>
<li><strong>上下文依赖</strong>：AR 语言模型仅考虑了词条左侧的上下文，而 BERT 则可以捕获两个方向的上下文。</li>
</ol>
<p>为了利用 AR 语言模型和 BERT 的优点，XLNet 提出了排序语言模型。对于一个长度为 <code>$T$</code> 序列 <code>$\mathbf{x}$</code>，共有 <code>$T!$</code> 种不同的方式进行 AR 分解，如果模型共享不同分解顺序的参数，那么模型就能学习到两侧所有位置的信息。令 <code>$\mathcal{Z}_T$</code> 为长度为 <code>$T$</code> 的索引序列 <code>$\left[1, 2, \dotsc, T\right]$</code> 的所有可能排列，<code>$z_t$</code> 和 <code>$\mathbf{z}_{&lt;t}$</code> 分别表示一个排列 <code>$\mathbf{z} \in \mathcal{Z}_T$</code> 第 <code>$t$</code> 个和前 <code>$t-1$</code> 个元素。则排列语言模型的优化目标为：</p>
<p><code>$$ \max_{\theta} \quad \mathbb{E}_{\mathbf{z} \sim \mathcal{Z}_{T}}\left[\sum_{t=1}^{T} \log p_{\theta}\left(x_{z_{t}} | \mathbf{x}_{\mathbf{z}_{&lt;t}}\right)\right] $$</code></p>
<p>根据标准的 Transformer，下一个词条的分布 <code>$p_{\theta}\left(X_{z_{t}} | \mathbf{x}_{\mathbf{z}&lt;t}\right)$</code> 为：</p>
<p><code>$$ p_{\theta}\left(X_{z_{t}} = x | \mathbf{x}_{\mathbf{z}&lt;t}\right)=\frac{\exp \left(e(x)^{\top} h_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}\right)\right)}{\sum_{x^{\prime}} \exp \left(e\left(x^{\prime}\right)^{\top} h_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}\right)\right)} $$</code></p>
<p>其中，<code>$h_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}\right)$</code> 表示通过共享的 Transformer 产生的 <code>$\mathbf{X}_{\mathbf{Z}&lt;t}$</code> 的隐含表示。该表示并不依赖于所预测的位置，为了避免这个问题，我们将位置 <code>$z_t$</code> 加入到模型中：</p>
<p><code>$$ p_{\theta}\left(X_{z_{t}}=x | \mathbf{x}_{z_{&lt;t}}\right)=\frac{\exp \left(e(x)^{\top} g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)\right)}{\sum_{x^{\prime}} \exp \left(e\left(x^{\prime}\right)^{\top} g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)\right)} $$</code></p>
<p>对于 <code>$g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)$</code> 进行建模需要满足如下两个要求：</p>
<ol>
<li>预测 <code>$x_{z_t}$</code> 时，<code>$g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)$</code> 只能使用位置信息 <code>$z_t$</code> 而不能使用内容信息 <code>$x_{z_t}$</code>。</li>
<li>在预测 <code>$x_{z_t}$</code> 之后的词条时，<code>$g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)$</code> 又必须包含 <code>$x_{z_t}$</code> 的语义信息。</li>
</ol>
<p>为了解决这个问题，XLNet 提供了两种隐含表示：</p>
<ol>
<li>内容隐含表示 <code>$h_{\theta}\left(\mathbf{x}_{\mathbf{z} \leq t}\right)$</code>，简写为 <code>$h_{z_t}$</code>，它和标准的 Transformer 一样，既编码上下文也编码 <code>$x_{z_t}$</code> 的内容。</li>
<li>查询隐含表示 <code>$g_{\theta}\left(\mathbf{x}_{\mathbf{z}&lt;t}, z_{t}\right)$</code>，简写为 <code>$g_{z_t}$</code>，它仅编码上下文信息 <code>$\mathbf{X}_{\mathbf{Z}&lt;t}$</code> 和位置信息 <code>$z_t$</code>，不编码内容 <code>$x_{z_t}$</code>。</li>
</ol>
<p>模型的整个计算过程如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/xlnet.png" class="lazyload"/>
  
</figure>
<p>虽然排列语言模型有很多优点，但是由于计算量很大，模型很难进行优化，因此我们通过仅预测一个句子后面的一些词条解决这个问题。将 <code>$\mathbf{z}$</code> 分为两部分：非目标子序列 <code>$\mathbf{z}_{\leq c}$</code> 和目标子序列 <code>$\mathbf{z}_{&gt;c}$</code>，其中 <code>$c$</code> 为切分点。同时会设置一个超参数 <code>$K$</code>，表示仅 <code>$1 / K$</code> 的词条会被预测，有 <code>$|\mathbf{z}| /(|\mathbf{z}|-c) \approx K$</code>。对于未被选择的词条，其查询隐状态无需被计算，从而节省计算时间和资源。</p>
<h3 id="mass-2019">MASS (2019) <sup id="fnref1:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup></h3>
<p>MASS 是一个专门针对序列到序列的自然语言任务设计的预训练方法，对于一个给定的原始句子 <code>$x \in \mathcal{X}$</code>，令 <code>$x^{\setminus u:v}$</code> 表示将 <code>$x$</code> 从 <code>$u$</code> 到 <code>$v$</code> 位置进行遮挡处理，<code>$k = v - u + 1$</code> 为被遮挡词条的个数，<code>$x^{u:v}$</code> 为从 <code>$u$</code> 到 <code>$v$</code> 位置被遮挡的部分。MASS 利用被遮挡的序列 <code>$x^{\setminus u:v}$</code> 预测被遮挡的部分 <code>$x^{u:v}$</code>，目标函数的对数似然如下：</p>
<p><code>$$ \begin{aligned} L(\theta ; \mathcal{X}) &amp;=\frac{1}{|\mathcal{X}|} \Sigma_{x \in \mathcal{X}} \log P\left(x^{u: v} | x^{\setminus u: v} ; \theta\right) \\ &amp;=\frac{1}{|\mathcal{X}|} \Sigma_{x \in \mathcal{X}} \log \prod_{t=u}^{v} P\left(x_{t}^{u: v} | x_{&lt;t}^{u: v}, x^{\setminus u: v} ; \theta\right) \end{aligned} $$</code></p>
<p>对于一个具有 8 个词条的序列，<code>$x_3 x_4 x_5 x_6$</code> 被遮挡的示例如下：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/mass.png" class="lazyload"/>
  
</figure>
<p>模型仅预测遮挡的部分 <code>$x_3 x_4 x_5 x_6$</code>，对于解码器中位置 <code>$4-6$</code> 利用 <code>$x_3 x_4 x_5$</code> 作为输入，利用特殊遮挡符号 <code>$\left[\mathbb{M}\right]$</code> 作为其他位置的输入。对于不同长度 <code>$k$</code>，MASS 包含了上文中提到的两种预训练模型：</p>
<table>
  <thead>
      <tr>
          <th>长度</th>
          <th>概率</th>
          <th>模型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>$k=1$</code></td>
          <td><code>$P\left(x^{u} \mid x^{\setminus u} ; \theta\right)$</code></td>
          <td>masked LM in BERT</td>
      </tr>
      <tr>
          <td><code>$k=m$</code></td>
          <td><code>$P\left(x^{1:m} \mid x^{\setminus 1:m} ; \theta\right)$</code></td>
          <td>masked LM in GPT</td>
      </tr>
      <tr>
          <td><code>$k \in \left(1, m\right)$</code></td>
          <td><code>$P\left(x^{u:v} \mid x^{\setminus u:v} ; \theta\right)$</code></td>
          <td>两种之间</td>
      </tr>
  </tbody>
</table>
<p>对于不同 <code>$k$</code> 值，实验发现当 <code>$k$</code> 处于 <code>$m$</code> 的 <code>$50\%$</code> 至 <code>$70\%$</code> 之间时下游任务性能最优。</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/mass-k.png" class="lazyload"/>
  
</figure>
<p>当 <code>$k = 0.5 m$</code> 时，MASS 可以很好地平衡编码器和解码器的预训练。过度地偏向编码器（<code>$k=1$</code>，masked LM in BERT）和过度地偏向解码器（<code>$k=m$</code>，masked LM in GPT）均不能在下游的自然语言生成任务中取得很好的效果。</p>
<h3 id="roberta-2019">RoBERTa (2019) <sup id="fnref1:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup></h3>
<p>RoBERTa 主要围绕 BERT 进行了如下改进：</p>
<ol>
<li>模型采用了动态遮罩，不同于原始 BERT 中对语料预先进行遮罩处理，RoBERTa 在 40 轮训练过程中采用了 10 种不同的遮罩。</li>
<li>模型去掉了 NSP 任务，发现可以略微提升下游任务的性能。</li>
<li>模型采用了更大的训练数据和更大的 Batch 大小。</li>
<li>原始 BERT 采用一个 30K 的 BPE 词表，RoBERTa 采用了一个更大的 50K 的词表 <sup id="fnref:28"><a href="#fn:28" class="footnote-ref" role="doc-noteref">28</a></sup>。</li>
</ol>
<h3 id="bart-2019">BART (2019) <sup id="fnref1:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup></h3>
<p>BART 采用了一个标准的 Seq2Seq Transformer 结构，类似 GPT 将 ReLU 激活函数替换为 GeLUs。对于基线模型，采用了一个 6 层的编码和解码器，对于更大模型采用了 12 层的结构。相比于 BERT 的架构主要有以下两点不同：</p>
<ol>
<li>解码器的每一层叠加了对编码器最后一个隐含层的注意力。</li>
<li>BERT 在预测之前采用了一个前馈的网络，而 BART 没有。</li>
</ol>
<p>BART 采用了最小化破坏后的文档和原始文档之间的重构误差的方式进行预训练。不同于其他的一些去噪自编码器，BART 可以使用任意类型的文档破坏方式。极端情况下，当源文档的所有信息均丢失时，BART 就等价与一个语言模型。BART 中采用的文本破坏方式有：字符遮罩，字符删除，文本填充，句子重排，文档旋转，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/bart-transformations.png" class="lazyload"/>
  
</figure>
<h3 id="t5-2019">T5 (2019) <sup id="fnref1:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup></h3>
<p>T5（Text-to-Text Transfer Transformer） 提出了一种 text-to-text 的框架，旨在利用相同的模型，损失函数和超参数等对机器翻译，文档摘要，问答和分类（例如：情感分析）等任务进行统一建模。我们甚至可以利用 T5 通过预测一个数字的文本表示而不是数字本身来建模一个回归任务。模型及其输入输出如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/t5-text-to-text-framework.gif" class="lazyload"/>
  
</figure>
<p>Google 的这项研究并不是提出一种新的方法，而是从全面的视角来概述当前 NLP 领域迁移学习的发展现状。T5 还公开了一个名为 C4（Colossal Clean Crawled Corpus）的数据集，该数据集是一个比 Wikipedia 大两个数量级的 Common Crawl 的清洗后版本的数据。更多模型的细节请参见源论文和 Google 的 <a href="https://ai.googleblog.com/2020/02/exploring-transfer-learning-with-t5.html">官方博客</a>。</p>
<h3 id="ernie-baidu-2019">ERNIE (Baidu, 2019) <sup id="fnref:29"><a href="#fn:29" class="footnote-ref" role="doc-noteref">29</a></sup> <sup id="fnref:30"><a href="#fn:30" class="footnote-ref" role="doc-noteref">30</a></sup></h3>
<p>ERNIE 1.0 <sup id="fnref1:29"><a href="#fn:29" class="footnote-ref" role="doc-noteref">29</a></sup> 通过建模海量数据中的词、实体及实体关系，学习真实世界的语义知识。相较于 BERT 学习原始语言信号，ERNIE 直接对先验语义知识单元进行建模，增强了模型语义表示能力。例如：</p>
<p><code>BERT ：哈 [mask] 滨是 [mask] 龙江的省会，[mask] 际冰 [mask] 文化名城。</code><br>
<code>ERNIE：[mask] [mask] [mask] 是黑龙江的省会，国际 [mask] [mask] 文化名城。</code></p>
<p>在 BERT 模型中，我们通过『哈』与『滨』的局部共现，即可判断出『尔』字，模型没有学习与『哈尔滨』相关的任何知识。而 ERNIE 通过学习词与实体的表达，使模型能够建模出『哈尔滨』与『黑龙江』的关系，学到『哈尔滨』是 『黑龙江』的省会以及『哈尔滨』是个冰雪城市。</p>
<p>训练数据方面，除百科类、资讯类中文语料外，ERNIE 还引入了论坛对话类数据，利用 DLM（Dialogue Language Model）建模 Query-Response 对话结构，将对话 Pair 对作为输入，引入 Dialogue Embedding 标识对话的角色，利用 Dialogue Response Loss 学习对话的隐式关系，进一步提升模型的语义表示能力。</p>
<p>ERNIE 2.0 <sup id="fnref1:30"><a href="#fn:30" class="footnote-ref" role="doc-noteref">30</a></sup> 是基于持续学习的语义理解预训练框架，使用多任务学习增量式构建预训练任务。ERNIE 2.0 中，新构建的预训练任务类型可以无缝的加入训练框架，持续的进行语义理解学习。 通过新增的实体预测、句子因果关系判断、文章句子结构重建等语义任务，ERNIE 2.0 语义理解预训练模型从训练数据中获取了词法、句法、语义等多个维度的自然语言信息，极大地增强了通用语义表示能力。</p>
<figure>
  <img data-src="/images/cn/2020-03-28-pre-trained-model-for-nlp/ernie-2-framework.png" class="lazyload"/>
  
</figure>
<h3 id="state-of-art">State-of-Art</h3>
<p>NLP 任务的 State-of-Art 模型详见：</p>
<ul>
<li><a href="https://gluebenchmark.com/leaderboard">GLUE Leaderboard</a></li>
<li><a href="https://super.gluebenchmark.com/leaderboard">SuperGLUE Leaderboard</a></li>
<li><a href="https://rajpurkar.github.io/SQuAD-explorer/">SQuAD</a></li>
<li><a href="https://nlpprogress.com/">NLP-progress</a></li>
<li><a href="https://www.cluebenchmarks.com/">中文任务基准测评</a></li>
</ul>


<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Qiu, X., Sun, T., Xu, Y., Shao, Y., Dai, N., &amp; Huang, X. (2020). Pre-trained Models for Natural Language Processing: A Survey. <em>ArXiv:2003.08271 [Cs]</em>. <a href="http://arxiv.org/abs/2003.08271">http://arxiv.org/abs/2003.08271</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Mikolov, T., Sutskever, I., Chen, K., Corrado, G. S., &amp; Dean, J. (2013). Distributed representations of words and phrases and their compositionality. In <em>Advances in neural information processing systems</em> (pp. 3111-3119).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Pennington, J., Socher, R., &amp; Manning, C. D. (2014, October). Glove: Global vectors for word representation. In <em>Proceedings of the 2014 conference on empirical methods in natural language processing (EMNLP)</em> (pp. 1532-1543).&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>McCann, B., Bradbury, J., Xiong, C., &amp; Socher, R. (2017). Learned in translation: Contextualized word vectors. In <em>Advances in Neural Information Processing Systems</em> (pp. 6294-6305).&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Peters, M. E., Neumann, M., Iyyer, M., Gardner, M., Clark, C., Lee, K., &amp; Zettlemoyer, L. (2018). Deep contextualized word representations. <em>arXiv preprint arXiv:1802.05365.</em>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Radford, A., Narasimhan, K., Salimans, T., &amp; Sutskever, I. (2018). Improving language understanding by generative pre-training. <em>URL <a href="https://openai.com/blog/language-unsupervised/">https://openai.com/blog/language-unsupervised/</a></em>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Devlin, J., Chang, M. W., Lee, K., &amp; Toutanova, K. (2018). Bert: Pre-training of deep bidirectional transformers for language understanding. <em>arXiv preprint arXiv:1810.04805.</em>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Kim, Y. (2014). Convolutional Neural Networks for Sentence Classification. In <em>Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP)</em> (pp. 1746-1751).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Hochreiter, S., &amp; Schmidhuber, J. (1997). Long short-term memory. <em>Neural computation</em>, 9(8), 1735-1780.&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Chung, J., Gulcehre, C., Cho, K., &amp; Bengio, Y. (2014). Empirical evaluation of gated recurrent neural networks on sequence modeling. <em>arXiv preprint arXiv:1412.3555.</em>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Socher, R., Perelygin, A., Wu, J., Chuang, J., Manning, C. D., Ng, A. Y., &amp; Potts, C. (2013). Recursive deep models for semantic compositionality over a sentiment treebank. In <em>Proceedings of the 2013 conference on empirical methods in natural language processing</em> (pp. 1631-1642).&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>Tai, K. S., Socher, R., &amp; Manning, C. D. (2015). Improved Semantic Representations From Tree-Structured Long Short-Term Memory Networks. In <em>Proceedings of the 53rd Annual Meeting of the Association for Computational Linguistics and the 7th International Joint Conference on Natural Language Processing (Volume 1: Long Papers)</em> (pp. 1556-1566).&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p>Marcheggiani, D., Bastings, J., &amp; Titov, I. (2018). Exploiting Semantics in Neural Machine Translation with Graph Convolutional Networks. In <em>Proceedings of the 2018 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 2 (Short Papers)</em> (pp. 486-492).&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p>Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., &hellip; &amp; Polosukhin, I. (2017). Attention is all you need. In <em>Advances in neural information processing systems</em> (pp. 5998-6008).&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:15">
<p>Devlin, J., Chang, M. W., Lee, K., &amp; Toutanova, K. (2019). BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. In <em>Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long and Short Papers)</em> (pp. 4171-4186).&#160;<a href="#fnref:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:16">
<p>Song, K., Tan, X., Qin, T., Lu, J., &amp; Liu, T. Y. (2019). MASS: Masked Sequence to Sequence Pre-training for Language Generation. In <em>International Conference on Machine Learning</em> (pp. 5926-5936).&#160;<a href="#fnref:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:17">
<p>Raffel, C., Shazeer, N., Roberts, A., Lee, K., Narang, S., Matena, M., &hellip; &amp; Liu, P. J. (2019). Exploring the limits of transfer learning with a unified text-to-text transformer. <em>arXiv preprint arXiv:1910.1068</em>&#160;<a href="#fnref:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:18">
<p>Liu, Y., Ott, M., Goyal, N., Du, J., Joshi, M., Chen, D., &hellip; &amp; Stoyanov, V. (2019). Roberta: A robustly optimized bert pretraining approach. <em>arXiv preprint arXiv:1907.11692.</em>&#160;<a href="#fnref:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:19">
<p>Yang, Z., Dai, Z., Yang, Y., Carbonell, J., Salakhutdinov, R. R., &amp; Le, Q. V. (2019). Xlnet: Generalized autoregressive pretraining for language understanding. In <em>Advances in neural information processing systems</em> (pp. 5754-5764).&#160;<a href="#fnref:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:20">
<p>Lewis, M., Liu, Y., Goyal, N., Ghazvininejad, M., Mohamed, A., Levy, O., &hellip; &amp; Zettlemoyer, L. (2019). Bart: Denoising sequence-to-sequence pre-training for natural language generation, translation, and comprehension. <em>arXiv preprint arXiv:1910.13461.</em>&#160;<a href="#fnref:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:21">
<p>Saunshi, N., Plevrakis, O., Arora, S., Khodak, M., &amp; Khandeparkar, H. (2019). A Theoretical Analysis of Contrastive Unsupervised Representation Learning. In <em>International Conference on Machine Learning</em> (pp. 5628-5637).&#160;<a href="#fnref:21" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:22">
<p>Tenney, I., Das, D., &amp; Pavlick, E. (2019). BERT Rediscovers the Classical NLP Pipeline. In <em>Proceedings of the 57th Annual Meeting of the Association for Computational Linguistics</em> (pp. 4593-4601).&#160;<a href="#fnref:22" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:23">
<p>图片来源：http://www.realworldnlpbook.com/blog/improving-sentiment-analyzer-using-elmo.html&#160;<a href="#fnref:23" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:24">
<p>Wu, Y., Schuster, M., Chen, Z., Le, Q. V., Norouzi, M., Macherey, W., &hellip; &amp; Klingner, J. (2016). Google&rsquo;s neural machine translation system: Bridging the gap between human and machine translation. <em>arXiv preprint arXiv:1609.08144.</em>&#160;<a href="#fnref:24" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:25">
<p>Dong, L., Yang, N., Wang, W., Wei, F., Liu, X., Wang, Y., &hellip; &amp; Hon, H. W. (2019). Unified language model pre-training for natural language understanding and generation. In <em>Advances in Neural Information Processing Systems</em> (pp. 13042-13054).&#160;<a href="#fnref:25" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:26">
<p>Dai, Z., Yang, Z., Yang, Y., Carbonell, J. G., Le, Q., &amp; Salakhutdinov, R. (2019, July). Transformer-XL: Attentive Language Models beyond a Fixed-Length Context. In <em>Proceedings of the 57th Annual Meeting of the Association for Computational Linguistics</em> (pp. 2978-2988).&#160;<a href="#fnref:26" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:27">
<p>Al-Rfou, R., Choe, D., Constant, N., Guo, M., &amp; Jones, L. (2019). Character-level language modeling with deeper self-attention. In <em>Proceedings of the AAAI Conference on Artificial Intelligence</em> (Vol. 33, pp. 3159-3166).&#160;<a href="#fnref:27" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:28">
<p>Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., &amp; Sutskever, I. (2019). Language models are unsupervised multitask learners. <em>URL <a href="https://openai.com/blog/better-language-models/">https://openai.com/blog/better-language-models/</a></em>.&#160;<a href="#fnref:28" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:29">
<p>Sun, Y., Wang, S., Li, Y., Feng, S., Chen, X., Zhang, H., &hellip; &amp; Wu, H. (2019). Ernie: Enhanced representation through knowledge integration. <em>arXiv preprint arXiv:1904.09223.</em>&#160;<a href="#fnref:29" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:29" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:30">
<p>Sun, Y., Wang, S., Li, Y., Feng, S., Tian, H., Wu, H., &amp; Wang, H. (2019). Ernie 2.0: A continual pre-training framework for language understanding. <em>arXiv preprint arXiv:1907.12412.</em>&#160;<a href="#fnref:30" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:30" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>利用 Flask 和 Google App Engine 部署模型服务</title><link>https://zeqiang.fun/cn/2018/10/serving-models-with-flask-and-gae/</link><pubDate>Fri, 19 Oct 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/10/serving-models-with-flask-and-gae/</guid><description><![CDATA[
        <div class="blockquote" style='border-left: 4px solid #369BE5;'>本文的配套代码请参见 <a href="https://github.com/leovan/model-serving-demo">这里</a>，建议配合代码阅读本文。</div>
<h2 id="模型部署和服务调用">模型部署和服务调用</h2>
<p>对于做算法的同学，大家或多或少的更关心模型的性能指标多些，对于一些工程性问题考虑的较少。模型的部署是这些工程性问题中重要的一个，它直接关系到模型在生产系统的使用。一些成熟的机器学习框架会提供自己的解决方案，例如 <a href="https://www.tensorflow.org">Tensorflow</a> 提供的 <a href="https://www.tensorflow.org/serving/">Serving</a> 服务等。但很多情况下我们构建的工程可能不只使用了一种框架，因此一个框架自身的部署工具可能就很难满足我们的需求了。</p>
<p>针对此类情况，本文介绍一个 <strong>简单</strong> 的 <strong>准生产</strong> 模型部署方案。简单是指除了模型相关代码之外的工程性代码量不大，这得益于将要使用的 <a href="http://flask.pocoo.org/">Flask</a> 框架。准生产是指这种部署方案应对一般的生产环境问题不大，对于高并发的场景可以通过横向扩容并进行负载均衡解决，但对于单次调用时效性要求较高的场景则需要另寻其他解决方案。</p>
<p>本文方案的模型部署和服务调用框架如下图所示：</p>
<p><img src="/images/cn/2018-10-19-serving-models-with-flask-and-gae/model-serving.png" alt="Model-Serving"></p>
<p>其主要特性如下：</p>
<ol>
<li>服务端采用 Python 的 Flask 框架构建，无需使用其他外部服务。Flask 框架的 <a href="https://zh.wikipedia.org/zh/%E5%BE%AE%E6%9C%8D%E5%8A%A1">微服务</a> (Microframework) 特性使得服务端代码简洁高效。</li>
<li>利用 <a href="https://gunicorn.org/">Gunicorn</a> 提供的高性能 Python WSGI HTTP UNIX Server，方便在服务端运行 Flask 应用。</li>
<li>客户端和服务端之间采用 <a href="https://zh.wikipedia.org/zh/%E8%A1%A8%E7%8E%B0%E5%B1%82%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2">RESTful API</a> 调用方式，尽管在性能上可能不及其他一些方案 (例如：基于 RPC 的解决方案等)，但其较好地解决了跨语言交互的问题，不同语言之间交互仅需使用 HTTP 协议和 JSON 数据格式即可。</li>
</ol>
<h2 id="flask-服务和-ajax-调用">Flask 服务和 AJAX 调用</h2>
<h3 id="flask-服务封装">Flask 服务封装</h3>
<p>为了将模型代码和 Flask 服务进行整合，首先假设你已经对模型部分代码做了完美的封装 &#x1f60e;，整个工程先叫做 <code>model-serving-demo</code> 吧。整理一下代码的目录结构，给一个我中意的 Python 目录结构风格：</p>
<pre><code>model-serving-demo/                # 工程根目录
├── bin/                           # 可执行命令目录
|   ├─ start.sh                    # 启动脚本
|   ├─ stop.sh                     # 停止脚本
|   └─ ...
├── conf/                          # 配置文件目录
|   ├─ logging.conf                # 日志配置文件
|   ├─ xxx_model.conf              # XXX Model 配置文件
|   └─ ...
├── data/                          # 数据文件目录
├── docs/                          # 文档目录
├── model_serving/                 # 模块根目录
|   ├─ models/                     # 模型代码目录
|   |   ├─ __init__.py
|   |   ├─ xxx_model.py            # XXX Model 代码
|   |   └─ ...
|   ├─ resources/                  # Flask RESTful Resources 代码目录
|   |   ├─ __init__.py
|   |   ├─ xxx_model_resource.py   # XXX Model Flask RESTful Resources 代码
|   |   └─ ...
|   ├─ tests/                      # 测试代码根目录
|   |   ├─ models                  # 模型测试代码目录
|   |   |   ├─ __init__.py
|   |   |   ├─ test_xxx_model.py   # XXX Model 测试代码
|   |   |   └─ ...
|   |   ├─ __init__.py
|   |   └─ ...
|   ├─ tmp/                        # 临时目录
|   └─ ...
├── .gitignore                     # Git Ignore 文件
├── app.yaml                       # Google App Engine 配置文件
├── LICENSE                        # 授权协议
├── main.py                        # 主程序代码
├── README.md                      # 说明文件
└── requirements.txt               # 依赖包列表
</code></pre>
<p>我们利用一个极简的示例介绍整个模型部署，相关的库依赖 <code>requirements.txt</code> 如下：</p>
<pre><code>Flask==1.0.2
Flask-RESTful==0.3.6
Flask-Cors==3.0.6
jsonschema==2.6.0
docopt==0.6.2

# 本地部署时需保留，GAE 部署时请删除
# gunicorn==19.9.0
</code></pre>
<p>其中：</p>
<ol>
<li><a href="http://flask.pocoo.org/">Flask</a> 用于构建 Flask 服务。</li>
<li><a href="https://flask-restful.readthedocs.io/">Flask-RESTful</a> 用于构建 Flask RESTful API。</li>
<li><a href="https://flask-cors.readthedocs.io/">Flask-Cors</a> 用于解决 AJAX 调用时的 <a href="https://zh.wikipedia.org/zh/%E8%B7%A8%E4%BE%86%E6%BA%90%E8%B3%87%E6%BA%90%E5%85%B1%E4%BA%AB">跨域问题</a>。</li>
<li><a href="https://python-jsonschema.readthedocs.io/">jsonschema</a> 用于对请求数据的 JSON 格式进行校验。</li>
<li><a href="http://docopt.org/">docopt</a> 用于从代码文档自动生成命令行参数解析器。</li>
<li><a href="https://gunicorn.org/">gunicorn</a> 用于提供的高性能 Python WSGI HTTP UNIX Server。</li>
</ol>
<p>XXX Model 的代码 <code>xxx_model.py</code> 如下：</p>
<pre><code class="language-python">from ..utils.log_utils import XXXModel_LOGGER


LOGGER = XXXModel_LOGGER


class XXXModel():
    def __init__(self):
        LOGGER.info('Initializing XXX Model ...')

        LOGGER.info('XXX Model Initialized.')

    def hello(self, name:str) -&gt; str:
        return 'Hello, {name}!'.format(name=name)
</code></pre>
<p>其中 <code>hello()</code> 为服务使用的方法，其接受一个类型为 <code>str</code> 的参数 <code>name</code>，并返回一个类型为 <code>str</code> 的结果。</p>
<p>XXX Model 的 Flask RESTful Resource 代码 <code>xxx_model_resource.py</code> 如下：</p>
<pre><code class="language-python">from flask_restful import Resource, request

from ..models.xxx_model import XXXModel
from ..utils.validation_utils import validate_json


xxx_model_instance = XXXModel()
xxx_model_schema = {
    'type': 'object',
    'properties': {
        'name': {'type': 'string'}
    },
    'required': ['name']
}


class XXXModelResource(Resource):
    @validate_json(xxx_model_schema)
    def post(self):
        json = request.json

        return {'result': xxx_model_instance.hello(json['name'])}
</code></pre>
<p>我们需要从 Flask RESTful 的 <code>Resource</code> 类继承一个新的类 <code>XXXModelResource</code> 用于处理 XXX Model 的服务请求。如上文介绍，我们在整个模型服务调用中使用 POST 请求方式和 JSON 数据格式，因此我们需要在类 <code>XXXModelResource</code> 中实现 <code>post()</code> 方法，同时对于传入数据的 JSON 格式进行校验。</p>
<p><code>post()</code> 方法用于处理整个模型的服务请求，<code>xxx_model_instance</code> 模型实例在类 <code>XXXModelResource</code> 外部进行实例化，避免每次处理请求时都进行初始化。<code>post()</code> 的返回结果无需处理成 JSON 格式的字符串，仅需返回词典数据即可，Flask RESTful 会自动对其进行转换。</p>
<p>为了方便对请求数据的 JSON 格式进行校验，我们将对 JSON 格式的校验封装成一个修饰器。使用时如上文代码中所示，在 <code>post()</code> 方法上方添加 <code>@validate_json(xxx_model_schema)</code> 即可，其中 <code>xxx_model_schema</code> 为一个符合 <a href="https://python-jsonschema.readthedocs.io/">jsonschema</a> 要求的 JSON Schema。示例代码中要求传入的 JSON 数据 <strong>必须</strong> 包含一个名为 <code>name</code> 类型为 <code>string</code> 的字段。</p>
<p><code>validate_json</code> 修饰器的代码 <code>validation_utils.py</code> 如下：</p>
<pre><code class="language-python">from functools import wraps
from jsonschema import validate, ValidationError
from flask_restful import request


def validate_json(schema, force=False):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            json_body = request.get_json(force=force)

            if json_body is None:
                return {'message': 'No JSON object'}, 400

            try:
                validate(json_body, schema)
            except ValidationError as e:
                return {'message': e.message}, 400

            return f(*args, **kwargs)
        return wrapper
    return decorator
</code></pre>
<p>首先我们需要验证请求包含一个 JSON 请求体，同时 JSON 请求体的内容需满足 <code>schema</code> 的要求。如果不满足这些条件，我们需要返回对应的错误信息 <code>message</code>，同时返回合理的 <a href="https://zh.wikipedia.org/zh/HTTP%E7%8A%B6%E6%80%81%E7%A0%81">HTTP 状态码</a> (例如：<code>400</code>) 用于表示无法处理错误的请求。对于正常的请求响应 (即 HTTP 状态码为 200 的情况)，状态码可以省略不写。</p>
<p>构建完 XXX Model 的 Flask RESTful Resource 后，我们就可以构建 Flask 的主服务了，主程序代码 <code>main.py</code> 如下：</p>
<pre><code class="language-python">&quot;&quot;&quot;
Model Serving Demo

Usage:
    main.py [--host &lt;host&gt;] [--port &lt;port&gt;] [--debug]
    main.py (-h | --help)
    main.py --version

Options:
    --host &lt;host&gt;                     绑定的 Host [default: 0.0.0.0]
    --port &lt;port&gt;                     绑定的 Port [default: 9999]
    --debug                           是否开启 Debug [default: False]
    -h --help                         显示帮助
    -v --version                      显示版本

&quot;&quot;&quot;

from docopt import docopt

from flask import Flask
from flask_cors import CORS
from flask_restful import Api

from model_serving.resources.xxx_model_resource import XXXModelResource


app = Flask(__name__)
CORS(app)

api = Api(app)
api.add_resource(XXXModelResource, '/v1/XXXModel')


if __name__ == '__main__':
    args = docopt(__doc__, version='Model Serving Demo v1.0.0')
    app.run(host=args['--host'], port=args['--port'], debug=args['--debug'])
</code></pre>
<p><code>docopt</code> 库用于从代码文档自动生成命令行参数解析器，具体使用方法请参见 <a href="http://docopt.org/">官方文档</a>。整个 Flask 主服务的构建比较简单，流程如下：</p>
<ol>
<li>构建 Flask 主程序，<code>app = Flask(__name__)</code>。</li>
<li>解决 AJAX 调用的跨域问题， <code>CORS(app)</code>。为了方便起见，我们不加任何参数，允许任意来源的请求，详细的使用方式请参见 <a href="https://flask-cors.readthedocs.io/">官方文档</a>。</li>
<li>构建 Flask RESTful API，<code>api = Api(app)</code>。</li>
<li>将构建好的 XXX Model 的 Flask RESTful Resource 添加到 API 中，<code>api.add_resource(XXXModelResource, '/v1/XXXModel')</code>。
其中第二个参数为请求的 URL，对于这个 URL 的建议将在后续小节中详细说明。</li>
</ol>
<p>Flask 主程序配置完毕后，我们通过 <code>app.run()</code> 在本地启动 Flask 服务，同时可以指定绑定的主机名，端口，以及是否开启调试模式等。通过 <code>python main.py</code> 启动 Flask 服务后，可以在命令行看到如下类似的日志：</p>
<pre><code>[2018/10/21 00:00:00] - [INFO] - [XXXModel] - Initializing XXX Model ...
[2018/10/21 00:00:00] - [INFO] - [XXXModel] - XXX Model Initialized.
 * Serving Flask app &quot;main&quot; (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
[2018/10/21 00:00:00] - [INFO] - [werkzeug] -  * Running on http://0.0.0.0:9999/ (Press CTRL+C to quit)
</code></pre>
<p>现在就可以测试调用服务了，我们用 <code>curl</code> 命令进行简单的测试，相关代码 <code>request-demo.sh</code> 如下：</p>
<pre><code class="language-bash">host=0.0.0.0
port=9999
url=https://zeqiang.fun/v1/XXXModel
curl_url=http://${host}:${port}${url}

invalid_json='{}'
valid_json='{&quot;name&quot;: &quot;Leo&quot;}'

# No JSON object
curl --request POST --url ${curl_url} --verbose

# Invalid JSON object
curl --header 'Content-Type: application/json; charset=UTF-8' \
    --request POST --data ${invalid_json} --url ${curl_url} --verbose

# Valid JSON object
curl --header 'Content-Type: application/json; charset=UTF-8' \
    --request POST --data ${valid_json} --url ${curl_url} --verbose
</code></pre>
<p>三种不同的请求返回的 HTTP 状态码和结果如下：</p>
<pre><code>HTTP/1.0 400 BAD REQUEST
{&quot;message&quot;: &quot;No JSON object&quot;}

HTTP/1.0 400 BAD REQUEST
{&quot;message&quot;: &quot;'name' is a required property&quot;}

HTTP/1.0 200 OK
{&quot;result&quot;: &quot;Hello, Leo!&quot;}
</code></pre>
<p>上文中，我们通过 <code>python main.py</code> 利用内置的 Server 启动了 Flask 服务，启动后日志中打印出来一条警告信息，告诉使用者不要在生产环境中使用内置的 Server。在生产环境中我们可以利用高性能 Python WSGI HTTP UNIX Server <a href="https://gunicorn.org/">gunicorn</a> 来启动 Flask 服务。</p>
<p>服务启动 (<code>start.sh</code>) 脚本代码如下：</p>
<pre><code class="language-bash">cd `dirname $0`
cd ..

base_dir=`pwd`
tmp_dir=${base_dir}/tmp
pid_file_path=${tmp_dir}/model-serving-demo.pid
log_file_path=${tmp_dir}/model-serving-demo.log

bind_host=0.0.0.0
bind_port=9999
workers=2

nohup gunicorn -b ${bind_host}:${bind_port} \
  -w ${workers} -p ${pid_file_path} \
  main:app &gt; ${log_file_path} 2&gt;&amp;1 &amp;
</code></pre>
<p>服务停止 (<code>stop.sh</code>) 脚本代码如下：</p>
<pre><code class="language-bash">cd `dirname $0`
cd ..

base_dir=`pwd`
tmp_dir=${base_dir}/tmp
pid_file_path=${tmp_dir}/model-serving-demo.pid

kill -TERM `echo ${pid_file_path}`
</code></pre>
<p>gunicorn 的详细参数配置和使用教程请参见 <a href="https://docs.gunicorn.org/en/stable/">官方文档</a>。</p>
<h3 id="restful-api-设计">RESTful API 设计</h3>
<p>RESTful API 是一种符合 REST(Representational State Transfer，表现层状态转换) 原则的框架，该框架是由 Fielding 在其博士论文 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 中提出。相关的核心概念如下：</p>
<ol>
<li><strong>资源 (Resources)</strong>，即网络中的一个实体 (文本，图片，服务等)，使用一个 URL 进行表示。</li>
<li><strong>表现层 (Representation)</strong>，资源具体的呈现形式即为表现层，例如图片可以表示为 PNG 文件，音乐可以表示为 MP3 文件，还有本文使用的数据格式 JSON 等。HTTP 请求的头信息中用 Accept 和 Content-Type 字段对表现层进行描述。</li>
<li><strong>状态转换 (State Transfer)</strong>，互联网通信协议 HTTP 协议是一个无状态协议，所有的状态都保存在服务端。因此如果客户端想要操作服务器，必须通过某种手段让服务器端发生 <strong>状态转换</strong>。客户端利用 HTTP 协议中的动作对服务器进行操作，例如：GET，POST，PUT，DELETE 等。</li>
</ol>
<p>利用 RESTful API 构建模型服务时，需要注意如下几点：</p>
<ol>
<li>为模型服务设置专用域名，例如：<code>https://api.example.com</code>，并配以负载均衡。</li>
<li>将 API 的版本号写入 URL 中，例如：<code>https://api.example.com/v1</code>。</li>
<li>RESTful 框架中每个 URL 表示一种资源，因此可以将模型的名称作为 URL 的终点 (Endpoint)，例如：<code>https://api.example.com/v1/XXXModel</code>。</li>
<li>对于操作资源的 HTTP 方式有多种，综合考虑建议选用 POST 方式，同时建议使用 JSON 数据格式。</li>
<li>为请求响应设置合理的状态码，例如：200 OK 表示正常返回，400 INVALID REQUEST 表示无法处理客户端的错误请求等。</li>
<li>对于错误码为 4xx 的情况，建议在返回中添加键名为 <code>message</code> 的错误信息。</li>
</ol>
<h3 id="ajax-调用">AJAX 调用</h3>
<p>对于动态网页，我们可以很容易的在后端服务中发起 POST 请求调用模型服务，然后将结果在前端进行渲染。对于静态网页，我们可以利用 AJAX 进行相关操作。首先我们需要一个交互界面，如下为利用 <a href="https://material.io/design/">Google Material Design</a> 风格的 <a href="https://github.com/material-components/material-components-web">Material Design Components Web</a> 组件设计一个交互界面，实现细节请参见 <a href="https://github.com/leovan/model-serving-demo/tree/master/client/xxx-model-ajax-client.html">示例代码</a>。</p>
<p>
  
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/material-components-web@latest/dist/material-components-web.min.css">





















<style type="text/css">
.center {
  justify-content: center;
}

.text-field--fullwidth {
  width: 100%;
}

.loading {
  width: 32px;
  height: 32px;
  position: relative;
  margin: auto;
}

.loading-bounce-1, .loading-bounce-2 {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: #333;
  opacity: 0.6;
  position: absolute;
  top: 0;
  left: 0;

  -webkit-animation: bounce 2.0s infinite ease-in-out;
  animation: bounce 2.0s infinite ease-in-out;
}

.loading-bounce-2 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

@-webkit-keyframes bounce {
  0%, 100% { -webkit-transform: scale(0.0) }
  50% { -webkit-transform: scale(1.0) }
}

@keyframes bounce {
  0%, 100% {
    transform: scale(0.0);
    -webkit-transform: scale(0.0);
  } 50% {
    transform: scale(1.0);
    -webkit-transform: scale(1.0);
  }
}
</style>

<div class="mdc-card">
  <div class="mdc-card__actions center">
    <div class="mdc-typography--headline6"><span>XXX Model AJAX Client</span></div>
  </div>
  <div class="mdc-card__actions">
    <div class="mdc-text-field text-field--fullwidth" data-mdc-auto-init="MDCTextField">
      <input id="name" class="mdc-text-field__input" value="Leo">
      <div class="mdc-line-ripple"></div>
    </div>
  </div>
  <div class="mdc-card__actions center">
    <button id="submit" class="mdc-button mdc-button--outlined" data-mdc-auto-init="MDCRipple">Submit</button>
    <div class="loading" id="loading" style="display: none;">
      <div class="loading-bounce-1"></div>
      <div class="loading-bounce-2"></div>
    </div>
  </div>
  <div class="mdc-card__actions center">
    <div class="mdc-typography--body1">
      <p id="result">Result</p>
    </div>
  </div>
</div>

<script>
  $(document).ready(function() {
    $("#submit").click(function() {
      $("#submit").toggle();
      $("#loading").toggle();

      /*
      $.ajax({
        url: "http://0.0.0.0:9999/v1/XXXModel",
        method: "POST",
        contentType: "application/json; charset=UTF-8",
        data: JSON.stringify({"name": $("#name").val()}),
        timeout: 3000,

        success: function (data, textStatus, jqXHR) {
          $("#result").html(data.result);

          $("#loading").toggle();
          $("#submit").toggle();
        },
        error: function (jqXHR, textStatus, errorThrown) {
          $("#result").html(errorThrown);

          $("#loading").toggle();
          $("#submit").toggle();
        }
      });
      */

      setTimeout(function() {
        $("#result").html("Hello, " + $("#name").val() + "!");
        $("#submit").toggle();
        $("#loading").toggle();
      }, 1000);
    });
  });
</script>





</p>
<p>AJAX 服务请求代码的核心部分如下：</p>
<pre><code class="language-javascript">$(document).ready(function() {
    $(&quot;#submit&quot;).click(function() {
        $.ajax({
            url: &quot;http://0.0.0.0:9999/v1/XXXModel&quot;,
            method: &quot;POST&quot;,
            contentType: &quot;application/json; charset=UTF-8&quot;,
            data: JSON.stringify({&quot;name&quot;: $(&quot;#name&quot;).val()}),
            timeout: 3000,

            success: function (data, textStatus, jqXHR) {
                $(&quot;#result&quot;).html(data.result);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                $(&quot;#result&quot;).html(errorThrown);
            }
        });
    });
});
</code></pre>
<p>代码使用了 <a href="https://jquery.com/">jQuery</a> 的相关函数。<code>JSON.stringify({&quot;name&quot;: $(&quot;#name&quot;).val()})</code> 获取 ID 为 <code>name</code> 的元素的值，并将其转换成符合服务端要求的 JSON 格式。通过 AJAX 向远程发出请求后，如果请求成功则将返回数据 <code>data</code> 中对应的结果 <code>result</code> 填充到 ID 为 <code>result</code> 的元素中，否则填入返回的错误信息。</p>
<h2 id="google-app-engine-部署">Google App Engine 部署</h2>
<p>上文中已经介绍了如何在本地利用 Flask 部署模型服务和相关调用方法，但如果希望在自己的网站中调用时，则利用 SaaS 来部署符合会是一个不二之选。国内外多个厂商均提供了相应的 SaaS 产品，例如 <a href="https://cloud.google.com/appengine/">Google</a>，<a href="https://aws.amazon.com/partners/saas-on-aws/">Amazon</a>，<a href="https://azure.microsoft.com/en-us/solutions/saas/">Microsoft</a> 等。Google App Engine (GAE) 提供了一个 <a href="https://cloud.google.com/free/docs/always-free-usage-limits">始终免费</a> 方案，虽然部署阶段会受到 GFW 的影响，但调用阶段测试影响并不是很大 (不同地区和服务提供商会有差异)。综合考虑，本文选择 GAE 作为 SaaS 平台部署服务，各位看官请自备梯子。</p>
<h3 id="环境准备">环境准备</h3>
<p>首先，在 <a href="https://console.cloud.google.com/projectcreate">Google Cloud Platform Console</a> 中建立一个新的 Project，假设项目名为 <code>YOUR_PROJECT_ID</code>。</p>
<p>其次，根据 <a href="https://cloud.google.com/sdk/docs/">Google Cloud SDK 文档</a> 在本地安装相应版本的 Google Cloud SDK。MacOS 下建议通过 <code>brew cask install google-cloud-sdk</code> 方式安装，安装完毕后确认在命令行中可以运行 <code>gcloud</code> 命令。</p>
<pre><code class="language-bash">$ gcloud version
Google Cloud SDK 221.0.0
bq 2.0.35
core 2018.10.12
gsutil 4.34
</code></pre>
<h3 id="构建-gae-工程">构建 GAE 工程</h3>
<p>模型服务仅作为后端应用，因此本节不介绍前端页面开发的相关部分，有兴趣的同学请参见 <a href="https://cloud.google.com/appengine/docs/standard/python3/quickstart">官方文档</a>。GAE 部署 Python Web 应用采用了 <a href="https://wsgi.readthedocs.io/en/latest/">WSGI 标准</a>，我们构建的本地部署版本完全满足这个要求，因此仅需为项目在根目录添加一个 GAE 配置文件 <code>app.yaml</code> 即可，内容如下：</p>
<pre><code class="language-yaml">runtime: python37

handlers:
  - url: /.*
    script: main.app

skip_files:
  - .idea/
  - .vscode/
  - __pycache__/
  - .hypothesis/
  - .pytest_cache/
  - bin/
  - ^(.*/)?.*\.py[cod]$
  - ^(.*/)?.*\$py\.class$
  - ^(.*/)?.*\.log$
</code></pre>
<p>其中，<code>runtime</code> 指定了服务运行的环境，<code>handlers</code> 指定了不同的 URL 对应的处理程序，在此所有的 URL 均由 <code>main.py</code> 中的 <code>app</code> 进行处理，<code>skip_files</code> 用于过滤不需要上传的文件。更多关于 <code>app.yaml</code> 的设置信息，请参见 <a href="https://cloud.google.com/appengine/docs/standard/python3/config/appref">官方文档</a>。</p>
<h3 id="部署-gae-工程">部署 GAE 工程</h3>
<p>在部署 GAE 工程之前我们可以利用本地的开发环境对其进行测试，测试无误后，即可运行如下命令将其部署到 GAE 上：</p>
<pre><code class="language-bash">gcloud app deploy --project [YOUR_PROJECT_ID]
</code></pre>
<p>然后根据命令行提示完成整个部署流程，部署完成的远程服务 URL 为 <code>https://YOUR_PROJECT_ID.appspot.com</code>，更多的测试和部署细节请参见 <a href="https://cloud.google.com/appengine/docs/standard/python3/testing-and-deploying-your-app">官方文档</a>。</p>
<p>部署后的 GAE 服务使用了其自带的域名 <code>appspot.com</code>。如果你拥有自己的域名，可以根据官方文档 <a href="https://cloud.google.com/appengine/docs/standard/python3/mapping-custom-domains">设置自己的域名</a> 并 <a href="https://cloud.google.com/appengine/docs/standard/python3/secURLng-custom-domains-with-ssl">开启 SSL</a>。</p>
<div class="blockquote" style='border-left: 4px solid #369BE5;'>本文部分内容参考了 Genthial 的博客 <a href="https://guillaumegenthial.github.io/serving.html">Serving a model with Flask</a> 和阮一峰的博客 <a href="https://www.ruanyifeng.com/blog/2011/09/restful.html">理解RESTful架构</a> 和 <a href="https://www.ruanyifeng.com/blog/2014/05/restful_api.html">RESTful API 设计指南</a>。</div>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Fielding, Roy T., and Richard N. Taylor. <em>Architectural styles and the design of network-based software architectures.</em> Vol. 7. Doctoral dissertation: University of California, Irvine, 2000.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>序列到序列 (Seq2Seq) 和注意力机制 (Attention Machanism)</title><link>https://zeqiang.fun/cn/2018/10/seq2seq-and-attention-machanism/</link><pubDate>Fri, 12 Oct 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/10/seq2seq-and-attention-machanism/</guid><description><![CDATA[
        <h2 id="encoder-decoder-seq2seq">Encoder-Decoder &amp; Seq2Seq</h2>
<p>Encoder-Decoder 是一种包含两个神经网络的模型，两个网络分别扮演编码器和解码器的角色。Cho 等人 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 提出了一个基于 RNN 的 Encoder-Decoder 神经网络用于机器翻译。网络结构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/rnn-encoder-decoder.png" alt="RNN-Encoder-Decoder"></p>
<p>整个模型包含编码器 (Encoder) 和解码器 (Decoder) 两部分：Encoder 将一个可变长度的序列转换成为一个固定长度的向量表示，Decoder 再将这个固定长度的向量表示转换为一个可变长度的序列。这使得模型可以处理从一个可变长度序列到另一个可变长度序例的转换，即学习到对应的条件概率 <code>$p \left(y_1, \dotsc, y_{T'} | x_1, \dotsc, x_T\right)$</code>，其中 <code>$T$</code> 和 <code>$T'$</code> 可以为不同的值，也就是说输入和输出的序列的长度不一定相同。</p>
<p>在模型中，Encoder 为一个 RNN，逐次读入输入序列 <code>$\mathbf{x}$</code> 中的每个元素，其中 RNN 隐状态的更新方式如下：</p>
<p><code>$$ \mathbf{h}_{\langle t \rangle} = f \left(\mathbf{h}_{\langle t-1 \rangle}, x_t\right) $$</code></p>
<p>在读入序列的最后一个元素后 (通常为一个结束标记)，RNN 的隐状态则为整个输入序列的概括信息 <code>$\mathbf{c}$</code>。Decoder 为另一个 RNN，用于根据隐状态 <code>$\mathbf{h}'_{\langle t \rangle}$</code> 预测下一个元素 <code>$y_t$</code>，从而生成整个输出序列。不同于 Encoder 中的 RNN，Decoder 中 RNN 的隐状态 <code>$\mathbf{h}'_{\langle t \rangle}$</code> 除了依赖上一个隐含层的状态和之前的输出外，还依赖整个输入序列的概括信息 <code>$\mathbf{c}$</code>，即：</p>
<p><code>$$ \mathbf{h}'_{\langle t \rangle} = f \left(\mathbf{h}'_{\langle t-1 \rangle}, y_{t-1}, \mathbf{c}\right) $$</code></p>
<p>类似的，下一个输出元素的条件分布为：</p>
<p><code>$$ P \left(y_t | y_{t-1}, y_{t-2}, \dotsc, y_1, \mathbf{c}\right) = g \left(\mathbf{h}_{\langle t \rangle}, y_{t-1}, \mathbf{c}\right) $$</code></p>
<p>RNN Encoder-Decoder 的两部分通过最大化如下的对数似然函数的联合训练进行优化：</p>
<p><code>$$ \max_{\theta} \dfrac{1}{N} \sum_{n=1}^{N}{\log p_{\theta} \left(\mathbf{y}_n | \mathbf{x}_n\right)} $$</code></p>
<p>其中，<code>$\theta$</code> 为模型的参数，<code>$\mathbf{x}_n$</code> 和 <code>$\mathbf{y}_n$</code> 分别为输入和输出序列的成对样本。当模型训练完毕后，我们可以利用模型根据给定的输入序列生成相应的输出序列，或是根据给定的输入和输出序列对计算概率得分 <code>$p_{\theta} \left(\mathbf{y} | \mathbf{x}\right)$</code>。同时，作者还提出了一种新的 RNN 单元 GRU (Gated Recurrent Unit)，有关 GRU 的更多介绍请参见 <a href="/cn/2018/09/rnn">之前的博客</a>。</p>
<p>序列到序列 (Sequence to Sequence, Seq2Seq) 模型从名称中不难看出来是一种用于处理序列数据到序列数据转换问题 (例如：机器翻译等) 的方法。Sutskever 等人 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> 提出了一种基于 Encoder-Decoder 网络结构的 Seq2Seq 模型用于机器翻译，网络结构细节同 RNN Encoder-Decoder 略有不同，如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/seq2seq.png" alt="Seq2Seq"></p>
<p>模型的相关细节如下：</p>
<ol>
<li>对数据进行预处理，在每个句子的结尾添加特殊字符 <code>&lt;EOS&gt;</code>，如上图所示。首先计算 <code>A, B, C, &lt;EOS&gt;</code> 的表示，再利用该表示计算 <code>W, X, Y, Z, &lt;EOS&gt;</code> 的条件概率。</li>
<li>利用两个不同的 LSTM，一个用于输入序列，另一个用于输出序列。</li>
<li>选用一个较深的 LSTM 模型 (4 层) 提升模型效果。</li>
<li>对输入序列进行倒置处理，例如对于输入序列 <code>$a, b, c$</code> 和对应的输出序列 <code>$\alpha, \beta, \gamma$</code>，LSTM 需要学习的映射关系为 <code>$c, b, a \to \alpha, \beta, \gamma$</code>。</li>
</ol>
<p>在模型的解码阶段，模型采用简单的从左到右的 Beam Search，该方法维护一个大小为 <code>$B$</code> 的集合保存最好的结果。下图展示了 <code>$B = 2$</code> 情况下 Beam Search 的具体工作方式：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/beam-search.png" alt="Beam-Search"></p>
<p>其中，红色的虚线箭头表示每一步可能的搜索方向，绿色的实线箭头表示每一步概率为 Top <code>$B$</code> 的方向。例如，从 S 开始搜索：</p>
<ol>
<li>第一步搜索的可能结果为 SA 和 SB，保留 Top 2，结果为 SA 和 SB。</li>
<li>第二步搜索的可能结果为 SAC，SAD，SBE 和 SBF，保留 Top 2，结果为 SAC 和 SBE。</li>
<li>第三步搜索的可能结果为 SACG，SACH，SBEK 和 SBEL，保留 Top 2，结果为 SACH 和 SBEK。至此，整个搜索结束。</li>
</ol>
<p>Bahdanau 等人 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 提出了一种基于双向 RNN (Bidirectional RNN, BiRNN) 结合注意力机制 (Attention Mechanism) 的网络结构用于机器翻译。网络结构如下：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/seq2seq-birnn-attention.png" alt="Seq2Seq-BiRNN-Attention"></p>
<p>模型的编码器使用了一个双向的 RNN，前向的 RNN <code>$\overrightarrow{f}$</code> 以从 <code>$x_1$</code> 到 <code>$x_T$</code> 的顺序读取输入序列并计算前向隐状态 <code>$\left(\overrightarrow{h}_1, \dotsc, \overrightarrow{h}_T\right)$</code>，后向的 RNN <code>$\overleftarrow{f}$</code> 以从 <code>$x_T$</code> 到 <code>$x_1$</code> 的顺序读取输入序列并计算后向隐状态 <code>$\left(\overleftarrow{h}_1, \dotsc, \overleftarrow{h}_T\right)$</code>。对于一个词 <code>$x_j$</code>，通过将对应的前向隐状态 <code>$\overrightarrow{h}_j$</code> 和后向隐状态 <code>$\overleftarrow{h}_j$</code> 进行拼接得到最终的隐状态 <code>$h_j = \left[\overrightarrow{h}_j^{\top}; \overleftarrow{h}_j^{\top}\right]^{\top}$</code>。这样的操作使得隐状态 <code>$h_j$</code> 既包含了前面词的信息也包含了后面词的信息。</p>
<p>在模型的解码器中，对于一个给定的序例 <code>$\mathbf{x}$</code>，每一个输出的条件概率为：</p>
<p><code>$$ p \left(y_i | y_1, \dotsc, y_{i-1}, \mathbf{x}\right) = g \left(y_{i-1}, s_i, c_i\right) $$</code></p>
<p>其中，<code>$s_i$</code> 为 <code>$i$</code> 时刻 RNN 隐含层的状态，即：</p>
<p><code>$$ s_i = f \left(s_{i-1}, y_{i-1}, c_i\right) $$</code></p>
<p>这里需要注意的是不同于之前的 Encoder-Decoder 模型，此处每一个输出词 <code>$y_i$</code> 的条件概率均依赖于一个单独的上下文向量 <code>$c_i$</code>。该部分的改进即结合了注意力机制，有关注意力机制的详细内容将在下个小节中展开说明。</p>
<h2 id="注意力机制-attention-mechanism">注意力机制 (Attention Mechanism)</h2>
<p>Bahdanau 等人在文中 <sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 提出传统的 Encoder-Decoder 模型将输入序列压缩成一个固定长度的向量 <code>$c$</code>，但当输入的序例很长时，尤其是当比训练集中的语料还长时，模型的的效果会显著下降。针对这个问题，如上文所述，上下文向量 <code>$c_i$</code> 依赖于 <code>$\left(h_1, \dotsc, h_T\right)$</code>。其中，每个 <code>$h_i$</code> 都包含了整个序列的信息，同时又会更多地关注第 <code>$i$</code> 个词附近的信息。对于 <code>$c_i$</code>，计算方式如下：</p>
<p><code>$$ c_i = \sum_{j=1}^{T}{\alpha_{ij} h_j} $$</code></p>
<p>对于每个 <code>$h_j$</code> 的权重 <code>$\alpha_{ij}$</code>，计算方式如下：</p>
<p><code>$$ \alpha_{ij} = \dfrac{\exp \left(e_{ij}\right)}{\sum_{k=1}^{T}{\exp \left(e_{ik}\right)}} $$</code></p>
<p>其中，<code>$e_{ij} = a \left(s_{i-1}, h_j\right)$</code> 为一个 Alignment 模型，用于评价对于输入的位置 <code>$j$</code> 附近的信息与输出的位置 <code>$i$</code> 附近的信息的匹配程度。Alignment 模型 <code>$a$</code> 为一个用于评分的前馈神经网络，与整个模型进行联合训练，计算方式如下：</p>
<p><code>$$ a \left(s_{i-1}, h_j\right) = v_a^{\top} \tanh \left(W_a s_{i-1} + U_a h_j\right) $$</code></p>
<p>其中，<code>$W_a \in \mathbb{R}^{n \times n}, U_a \in \mathbb{R}^{n \times 2n}，v_a \in \mathbb{R}^n$</code> 为网络的参数。</p>
<h3 id="hard-soft-attention">Hard &amp; Soft Attention</h3>
<p>Xu 等人 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> 在图像标题生成 (Image Caption Generation) 任务中引入了注意力机制。在文中作者提出了 Hard Attenttion 和 Soft Attention 两种不同的注意力机制。</p>
<p>对于 Hard Attention 而言，令 <code>$s_t$</code> 表示在生成第 <code>$t$</code> 个词时所关注的位置变量，<code>$s_{t, i} = 1$</code> 表示当第 <code>$i$</code> 个位置用于提取视觉特征。将注意力位置视为一个中间潜变量，可以以一个参数为 <code>$\left\{\alpha_i\right\}$</code> 的多项式分布表示，同时将上下文向量 <code>$\hat{\mathbf{z}}_t$</code> 视为一个随机变量：</p>
<p><code>$$ \begin{equation} \begin{split} &amp; p \left(s_{t, i} = 1 | s_{j &lt; t}, \mathbf{a}\right) = \alpha_{t, i} \\ &amp; \hat{\mathbf{z}}_t = \sum_{i}{s_{t, i} \mathbf{a}_i} \end{split} \end{equation} $$</code></p>
<p>因此 Hard Attention 可以依据概率值从隐状态中进行采样计算得到上下文向量，同时为了实现梯度的反向传播，需要利用蒙特卡罗采样的方法来估计梯度。</p>
<p>对于 Soft Attention 而言，则直接计算上下文向量 <code>$\hat{\mathbf{z}}_t$</code> 的期望，计算方式如下：</p>
<p><code>$$ \mathbb{E}_{p \left(s_t | a\right)} \left[\hat{\mathbf{z}}_t\right] = \sum_{i=1}^{L}{\alpha_{t, i} \mathbf{a}_i} $$</code></p>
<p>其余部分的计算方式同 Bahdanau 等人 <sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 的论文类似。Soft Attention 模型可以利用标准的反向传播算法进行求解，直接嵌入到整个模型中一同训练，相对更加简单。</p>
<p>下图展示了一些图片标题生成结果的可视化示例，其中图片内 <span style="background-color:#000; color:#FFF; font-style:bold;">白色</span> 为关注的区域，<span style="border-bottom:2px solid;">画线的文本</span> 即为生成的标题中对应的词。</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/image-caption-generation-visual-attention.png" alt="Image-Caption-Generation-Visual-Attention"></p>
<h3 id="global-local-attention">Global &amp; Local Attention</h3>
<p>Luong 等人 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> 提出了 Global Attention 和 Local Attention 两种不同的注意力机制用于机器翻译。Global Attention 的思想是在计算上下文向量 <code>$c_t$</code> 时将编码器的所有隐状态均考虑在内。对于对齐向量 <code>$\boldsymbol{a}_t$</code>，通过比较当前目标的隐状态 <code>$\boldsymbol{h}_t$</code> 与每一个输入的隐状态 <code>$\bar{\boldsymbol{h}}_s$</code> 得到，即：</p>
<p><code>$$ \begin{equation} \begin{split} \boldsymbol{a}_t &amp;= \text{align} \left(\boldsymbol{h}_t, \bar{\boldsymbol{h}}_s\right) \\ &amp;= \dfrac{\exp \left(\text{score} \left(\boldsymbol{h}_t, \bar{\boldsymbol{h}}_s\right)\right)}{\sum_{s'}{\exp \left(\text{score} \left(\boldsymbol{h}_t, \bar{\boldsymbol{h}}_{s'}\right)\right)}} \end{split} \end{equation} $$</code></p>
<p>其中 <code>$\text{score}$</code> 为一个基于内容 (content-based) 的函数，可选的考虑如下三种形式：</p>
<p><code>$$ \text{score} \left(\boldsymbol{h}_t, \bar{\boldsymbol{h}}_s\right) = \begin{cases} \boldsymbol{h}_t^{\top} \bar{\boldsymbol{h}}_s &amp; dot \\ \boldsymbol{h}_t^{\top} \boldsymbol{W}_a \bar{\boldsymbol{h}}_s &amp; general \\ \boldsymbol{W}_a \left[\boldsymbol{h}_t; \bar{\boldsymbol{h}}_s\right] &amp; concat \end{cases} $$</code></p>
<p>我们利用一个基于位置 (location-based) 的函数构建注意力模型，其中对齐分数通过目标的隐状态计算得到：</p>
<p><code>$$ \boldsymbol{a}_t = \text{softmax} \left(\boldsymbol{W}_a \boldsymbol{h}_t\right) $$</code></p>
<p>Global Attention 模型的网络结构如下所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/global-attention.png" alt="Global-Attention"></p>
<p>Global Attention 的一个问题在于任意一个输出都需要考虑输入端的所有隐状态，这对于很长的文本 (例如：一个段落或一篇文章) 计算量太大。Local Attention 为了解决这个问题，首先在 <code>$t$</code> 时刻对于每个目标词生成一个对齐位置 <code>$p_t$</code>，其次上下文向量 <code>$\boldsymbol{c}_t$</code> 则由以 <code>$p_t$</code> 为中心前后各 <code>$D$</code> 大小的窗口 <code>$\left[p_t - D, p_t + D\right]$</code> 内的输入的隐状态计算得到。不同于 Global Attention，Local Attention 的对齐向量 <code>$\boldsymbol{a}_t \in \mathbb{R}^{2D + 1}$</code> 为固定维度。</p>
<p>一个比较简单的做法是令 <code>$p_t = t$</code>，也就是假设输入和输出序列是差不多是单调对齐的，我们称这种做法为 <em>Monotonic</em> Alignment (<strong>local-m</strong>)。另一种做法是预测 <code>$p_t$</code>，即：</p>
<p><code>$$ p_t = S \cdot \text{sigmoid} \left(\boldsymbol{v}_p^{\top} \tanh \left(\boldsymbol{W}_p \boldsymbol{h}_t\right)\right) $$</code></p>
<p>其中，<code>$\boldsymbol{W}_p$</code> 和 <code>$\boldsymbol{h}_t$</code> 为预测位置模型的参数，<code>$S$</code> 为输入句子的长度。我们称这种做法为 <em>Predictive</em> Alignment (<strong>local-p</strong>)。作为 <code>$\text{sigmoid}$</code> 函数的结果，<code>$p_t \in \left[0, S\right]$</code>，则通过一个以 <code>$p_t$</code> 为中心的高斯分布定义对齐权重：</p>
<p><code>$$ \boldsymbol{a}_t \left(s\right) = \text{align} \left(\boldsymbol{h}_t, \bar{\boldsymbol{h}}_s\right) \exp \left(- \dfrac{\left(s - p_t\right)^2}{2 \sigma^2}\right) $$</code></p>
<p>其中，根据经验设置 <code>$\sigma = \dfrac{D}{2}$</code>，<code>$s$</code> 为在窗口大小内的一个整数。</p>
<p>Local Attention 模型的网络结构如下所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/local-attention.png" alt="Local-Attention"></p>
<h3 id="self-attention">Self Attention</h3>
<p>Vaswani 等人 <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 提出了一种新的网络结构，称之为 Transformer，其中采用了自注意力 (Self-attention) 机制。自注意力是一种将同一个序列的不同位置进行自我关联从而计算一个句子表示的机制。Transformer 利用堆叠的 Self Attention 和全链接网络构建编码器 (下图左) 和解码器 (下图右)，整个网络架构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/self-attention.png" alt="Self-Attention"></p>
<h4 id="编码器和解码器">编码器和解码器</h4>
<p><strong>编码器</strong> 是由 <code>$N = 6$</code> 个相同的网络层构成，每层中包含两个子层。第一层为一个 Multi-Head Self-Attention 层，第二层为一个 Position-Wise 全链接的前馈神经网络。每一层再应用一个残差连接 (Residual Connection) <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 和一个层标准化 (Layer Normalization) <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>。则每一层的输出为 <code>$\text{LayerNorm} \left(x + \text{Sublayer} \left(x\right)\right)$</code>，其中 <code>$\text{Sublayer} \left(x\right)$</code> 为子层本身的函数实现。为了实现残差连接，模型中所有的子层包括 Embedding 层的输出维度均为 <code>$d_{\text{model}} = 512$</code>。</p>
<p><strong>解码器</strong> 也是由 <code>$N = 6$</code> 个相同的网络层构成，但每层中包含三个子层，增加的第三层用于处理编码器的输出。同编码器一样，每一层应用一个残差连接和一个层标准化。除此之外，解码器对 Self-Attention 层进行了修改，确保对于位置 <code>$i$</code> 的预测仅依赖于位置在 <code>$i$</code> 之前的输出。</p>
<h4 id="scaled-dot-product-multi-head-attention">Scaled Dot-Product &amp; Multi-Head Attention</h4>
<p>一个 Attention 函数可以理解为从一个序列 (Query) 和一个键值对集合 (Key-Value Pairs Set) 到一个输出的映射。文中提出了一种名为 <strong>Scaled Dot-Product Attention</strong> (如下图所示)，其中输入包括 queries，维度为 <code>$d_k$</code> 的 keys 和维度为 <code>$d_v$</code> 的 values。通过计算 queries 和所有 keys 的点积，除以 <code>$\sqrt{d_k}$</code>，再应用一个 softmax 函数获取 values 的权重。</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/scaled-dot-product-attention.png" alt="Scaled-Dot-Product-Attention"></p>
<p>实际中，我们会同时计算一个 Queries 集合中的 Attention，并将其整合成一个矩阵 <code>$Q$</code>。Keys 和 Values 也相应的整合成矩阵 <code>$K$</code> 和 <code>$V$</code>，则有：</p>
<p><code>$$ \text{Attention} \left(Q, K, V\right) = \text{softmax} \left(\dfrac{Q K^{\top}}{\sqrt{d_k}}\right) V $$</code></p>
<p>其中，<code>$Q \in \mathbb{R}^{n \times d_k}$</code>，<code>$Q$</code> 中的每一行为一个 query，<code>$K \in \mathbb{R}^{n \times d_k}, V \in \mathbb{R}^{n \times d_v}$</code>。<code>$\dfrac{1}{\sqrt{d_k}}$</code> 为一个归一化因子，避免点积的值过大导致 softmax 之后的梯度过小。</p>
<p><strong>Multi-Head Attention</strong> 的做法并不直接对原始的 keys，values 和 queries 应用注意力函数，而是学习一个三者各自的映射再应用 Atteneion，同时将这个过程重复 <code>$h$</code> 次。Multi-Head Attention 的网路结构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/multi-head-attention.png" alt="Multi-Head-Attention"></p>
<p>Multi-Head Attention 的计算过程如下所示：</p>
<p><code>$$ \begin{equation} \begin{split} \text{MultiHead} \left(Q, K, V\right) &amp;= \text{Concat} \left(\text{head}_1, \dotsc, \text{head}_h\right) W^O \\ \textbf{where } \text{head}_i &amp;= \text{Attention} \left(QW_i^Q, KW_i^K, VW_i^V\right) \end{split} \end{equation} $$</code></p>
<p>其中，<code>$W_i^Q \in \mathbb{R}^{d_{\text{model}} \times d_k}, W_i^K \in \mathbb{R}^{d_{\text{model}} \times d_k}, W_i^V \in \mathbb{R}^{d_{\text{model}} \times d_v}, W_i^O \in \mathbb{R}^{h d_v \times d_{\text{model}}}, $</code> 为映射的参数，<code>$h = 8$</code> 为重复的次数，则有 <code>$d_k = d_v = d_{\text{model}} / h = 64$</code>。</p>
<p>整个 Transformer 模型在三处使用了 Multi-Head Attention，分别是：</p>
<ol>
<li>Encoder-Decoder Attention Layers，其中 queries 来自于之前的 Decoder 层，keys 和 values 来自于 Encoder 的输出，该部分同其他 Seq2Seq 模型的 Attention 机制类似。</li>
<li>Encoder Self-Attention Layers，其中 queries，keys 和 values 均来自之前的 Encoder 层的输出，同时 Encoder 层中的每个位置都能够从之前层的所有位置获取到信息。</li>
<li>Decoder Self-Attention Layers，其中 queries，keys 和 values 均来自之前的 Decoder 层的输出，但 Decoder 层中的每个位置仅可以从之前网络层的包含当前位置之前的位置获取信息。</li>
</ol>
<h4 id="position-wise-feed-forward-networks">Position-wise Feed-Forward Networks</h4>
<p>在 Encoder 和 Decoder 中的每一层均包含一个全链接的前馈神经网络，其使用两层线性变换和一个 ReLU 激活函数实现：</p>
<p><code>$$ \text{FFN} \left(x\right) = \max \left(0, x W_1 + b_1\right) W_2 + b_2 $$</code></p>
<p>全链接层的输入和输出的维度 <code>$d_{\text{model}} = 512$</code>，内层的维度 <code>$d_{ff} = 2048$</code>。</p>
<h4 id="positional-encoding">Positional Encoding</h4>
<p>Transformer 模型由于未使用任何循环和卷积组件，因此为了利用序列的位置信息则在模型的 Embedding 输入中添加了 <strong>Position Encoding</strong>。Position Encoding 的维度同 Embedding 的维度相同，从而可以与 Embedding 进行加和，文中使用了如下两种形式：</p>
<p><code>$$ \begin{equation} \begin{split} PE_{\left(pos, 2i\right)} &amp;= \sin \left(pos / 10000^{2i / d_{\text{model}}}\right) \\ PE_{\left(pos, 2i+1\right)} &amp;= \cos \left(pos / 10000^{2i / d_{\text{model}}}\right) \end{split} \end{equation} $$</code></p>
<p>其中，<code>$pos$</code> 为位置，<code>$i$</code> 为对应的维度，选用这种表示形式的原因是对于一个固定的偏移 <code>$k$</code>，<code>$PE_{pos + k}$</code> 都可以利用 <code>$PE_{pos}$</code> 线性表示。这是因为对于正弦和余弦函数有：</p>
<p><code>$$ \begin{equation} \begin{split} \sin \left(\alpha + \beta\right) &amp;= \sin \alpha \cos \beta + \cos \alpha \sin \beta \\ \cos \left(\alpha + \beta\right) &amp;= \cos \alpha \sin \beta - \sin \alpha \sin \beta \end{split} \end{equation} $$</code></p>
<h4 id="why-self-attention">Why Self-Attention</h4>
<p>相比于循环和卷积层，Transformer 模型利用 Self-Attention 层用于一个序列 <code>$\left(x_1, \dotsc, x_n\right)$</code> 到另一个等长序例 <code>$\left(z_1, \dotsc, z_n\right)$</code> 的映射，其中 <code>$x_i, z_i \in \mathbb{R}^d$</code>。Self-Attention 与循环和卷积的对比如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>层类型</th>
          <th>每层的复杂度</th>
          <th>序列操作数</th>
          <th>长距离依赖路径长度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Self-Attention</td>
          <td><code>$O \left(n^2 \cdot d\right)$</code></td>
          <td><code>$O \left(1\right)$</code></td>
          <td><code>$O \left(1\right)$</code></td>
      </tr>
      <tr>
          <td>Recurrent</td>
          <td><code>$O \left(n \cdot d^2\right)$</code></td>
          <td><code>$O \left(n\right)$</code></td>
          <td><code>$O \left(n\right)$</code></td>
      </tr>
      <tr>
          <td>Convolutional</td>
          <td><code>$O \left(k \cdot n \cdot d^2\right)$</code></td>
          <td><code>$O \left(1\right)$</code></td>
          <td><code>$O \left(\log_k \left(n\right)\right)$</code></td>
      </tr>
      <tr>
          <td>Self-Attention (restricted)</td>
          <td><code>$O \left(r \cdot n \cdot d\right)$</code></td>
          <td><code>$O \left(1\right)$</code></td>
          <td><code>$O \left(n/r\right)$</code></td>
      </tr>
  </tbody>
</table>
<ol>
<li>对于每层的复杂度，当序例的长度 <code>$n$</code> 比表示的维度 <code>$d$</code> 小时，Self-Attention 要比循环结构计算复杂度小。为了改进在长序列上 Self-Attention 的计算性能，Self-Attention 可以被限制成仅考虑与输出位置对应的输入序列位置附近 <code>$r$</code> 窗口大小内的信息。</li>
<li>Recurrent 层的最小序列操作数为 <code>$O \left(n\right)$</code>，其他情况为 <code>$O \left(1\right)$</code>，这使得 Recurrent 的并行能力较差，即上表中的 Self-Attention (restricted)。</li>
<li>学习到长距离依赖是很多序列任务的关键，影响该能力的一个重要因素就是前向和后向信号穿越整个网络的路径长度，这个路径长度越短，越容易学习到长距离依赖。</li>
</ol>
<h4 id="attention-visualizations">Attention Visualizations</h4>
<p>第一张图展示了 Self-Attention 学到的句子内部的一个长距离依赖 <strong>“making &hellip; more diffcult”</strong>，图中不同的颜色表示不同 Head 的 Attention，颜色越深表示 Attention 的值越大。</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/self-attention-long-distance-dependencies.png" alt="Self-Attention-Long-Distance-Dependencies"></p>
<p>第二张图展示了 Self-Attention 学到的一个指代消解关系 (Anaphora Resolution)，its 指代的为上文中的 law。下图 (上) 为 Head 5 的所有 Attention，下图 (下) 为 Head 5 和 6 关于词 its 的 Attention，不难看出模型学习到了 its 和 law 之间的依赖关系 (指代消解关系)。</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/self-attention-anaphora-resolution.png" alt="Self-Attention-Long-Anaphora-Resolution"></p>
<h3 id="hierarchical-attention">Hierarchical Attention</h3>
<p>Yang 等人 <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 提出了一种层级的注意力 (Hierarchical Attention) 网络用于文档分类。Hierarchical Attention 共包含 4 层：一个词编码器 (Word Encoder)，一个词级别的注意力层 (Word Attention)，一个句子编码器 (Sentence Encoder) 和一个句子级别的注意力层 (Sentence Attention)。网络架构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/hierarchical-attention.png" alt="Hierarchical-Attention"></p>
<h4 id="word-encoder">Word Encoder</h4>
<p>对于一个给定的句子 <code>$w_{it}, t \in \left[0, T\right]$</code>，通过一个 Embedding 矩阵 <code>$W_e$</code> 得到每个词的向量表示，再应用一个双向的 GRU，即：</p>
<p><code>$$ \begin{equation} \begin{split} x_{it} &amp;= W_e w_{it}, t \in \left[1, T\right] \\ \overrightarrow{h}_{it} &amp;= \overrightarrow{\text{GRU}} \left(x_{it}\right), t \in \left[1, T\right] \\ \overleftarrow{h}_{it} &amp;= \overleftarrow{\text{GRU}} \left(x_{it}\right), t \in \left[T, 1\right] \end{split} \end{equation} $$</code></p>
<p>最后将前向的隐状态 <code>$\overrightarrow{h}_{it}$</code> 和后向的隐状态 <code>$\overleftarrow{h}_{it}$</code> 进行拼接，得到 <code>$h_{ij} = \left[\overrightarrow{h}_{it}, \overleftarrow{h}_{it}\right]$</code> 为整个句子在词 <code>$w_{ij}$</code> 附近的汇总信息。</p>
<h4 id="word-attention">Word Attention</h4>
<p>Word Attention 同一般的 Attention 机制类似，计算方式如下：</p>
<p><code>$$ \begin{equation} \begin{split} u_{it} &amp;= \tanh \left(W_w h_{it} + b_w\right) \\ a_{it} &amp;= \dfrac{\exp \left(u_{it}^{\top} u_w\right)}{\sum_{t}{\exp \left(u_{it}^{\top} u_w\right)}} \\ s_i &amp;= \sum_{t}{a_{it} h_{it}} \end{split} \end{equation} $$</code></p>
<h4 id="sentence-encoder">Sentence Encoder</h4>
<p>在 Word Attention 之后，我们得到了一个句子的表示 <code>$s_i$</code>，类似的我们利用一个双向的 GRU 编码文档中的 <code>$L$</code> 个句子：</p>
<p><code>$$ \begin{equation} \begin{split} \overrightarrow{h}_i &amp;= \overrightarrow{\text{GRU}} \left(s_i\right), i \in \left[1, L\right] \\ \overleftarrow{h}_i &amp;= \overleftarrow{\text{GRU}} \left(s_i\right), i \in \left[L, 1\right] \end{split} \end{equation} $$</code></p>
<p>最后将前向的隐状态 <code>$\overrightarrow{h}_i$</code> 和后向的隐状态 <code>$\overleftarrow{h}_i$</code> 进行拼接，得到 <code>$h_i = \left[\overrightarrow{h}_i, \overleftarrow{h}_i\right]$</code> 为整个文档关于句子 <code>$s_i$</code> 的注意力汇总信息。</p>
<h4 id="sentence-attention">Sentence Attention</h4>
<p>同理可得 Sentence Attention 的计算方式如下：</p>
<p><code>$$ \begin{equation} \begin{split} u_i &amp;= \tanh \left(W_s h_i + b_s\right) \\ a_i &amp;= \dfrac{\exp \left(u_i^{\top} u_s\right)}{\sum_{i}{\exp \left(u_i^{\top} u_s\right)}} \\ v &amp;= \sum_{i}{a_i h_i} \end{split} \end{equation} $$</code></p>
<p>最终得到整个文档的向量表示 <code>$v$</code>。</p>
<h3 id="attention-over-attention">Attention-over-Attention</h3>
<p>Cui 等人 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> 提出了 Attention-over-Attention 的模型用于阅读理解 (Reading Comprehension)。网络结构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/attention-over-attention.png" alt="Attention-over-Attention"></p>
<p>对于一个给定的训练集 <code>$\langle \mathcal{D}, \mathcal{Q}, \mathcal{A} \rangle$</code>，模型包含两个输入，一个文档 (Document) 和一个问题序列 (Query)。网络的工作流程如下：</p>
<ol>
<li>先获取 Document 和 Query 的 Embedding 结果，再应用一个双向的 GRU 得到对应的隐状态 <code>$h_{doc}$</code> 和 <code>$h_{query}$</code>。</li>
<li>计算一个 Document 和 Query 的匹配程度矩阵 <code>$M \in \mathbb{R}^{\lvert \mathcal{D} \rvert \times \lvert \mathcal{Q} \rvert}$</code>，其中第 <code>$i$</code> 行第 <code>$j$</code> 列的值计算方式如下：
<code>$$ M \left(i, j\right) = h_{doc} \left(i\right)^{\top} \cdot h_{query} \left(j\right) $$</code></li>
<li>按照 <strong>列</strong> 的方向对矩阵 <code>$M$</code> 应用 softmax 函数，矩阵中的每一列为考虑一个 Query 中的词的 Document 级别的 Attention，因此定义 <code>$\alpha \left(t\right) \in \mathbb{R}^{\lvert \mathcal{D} \rvert}$</code> 为 <code>$t$</code> 时刻的 Document 级别 Attention (<strong><em>query-to-document</em> attention</strong>)。计算方式如下：
<code>$$ \begin{equation} \begin{split} \alpha \left(t\right) &amp;= \text{softmax} \left(M \left(1, t\right), \dotsc, M \left(\lvert \mathcal{D} \rvert, t\right)\right) \\ \alpha &amp;= \left[\alpha \left(1\right), \alpha \left(2\right), \dotsc, \alpha \left(\lvert \mathcal{Q} \rvert\right)\right] \end{split} \end{equation} $$</code></li>
<li>同理按照 <strong>行</strong> 的方向对矩阵 <code>$M$</code> 应用 softmax 函数，可以得到 <code>$\beta \left(t\right) \in \mathbb{R}^{\lvert \mathcal{Q} \rvert}$</code> 为 <code>$t$</code> 时刻的 Query 级别的 Attention (<strong><em>document-to-query</em> attention</strong>)。计算方式如下：
<code>$$ \beta \left(t\right) = \text{softmax} \left(M \left(t, 1\right), \dotsc, M \left(t, \lvert \mathcal{Q} \rvert\right)\right) $$</code></li>
<li>对于 document-to-query attention，我们对结果进行平均得到：
<code>$$ \beta = \dfrac{1}{n} \sum_{t=1}^{\lvert \mathcal{D} \rvert}{\beta \left(t\right)} $$</code></li>
<li>最终利用 <code>$\alpha$</code> 和 <code>$\beta$</code> 的点积 <code>$s = \alpha^{\top} \beta \in \mathbb{R}^{\lvert \mathcal{D} \rvert}$</code> 得到 attended document-level attention (即 <strong><em>attention-over-attention</em></strong>)。</li>
</ol>
<h3 id="multi-step-attention">Multi-step Attention</h3>
<p>Gehring 等人 <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup> 提出了基于 CNN 和 Multi-step Attention 的模型用于机器翻译。网络结构如下图所示：</p>
<p><img src="/images/cn/2018-10-12-seq2seq-and-attention-machanism/multi-step-attention.png" alt="Multi-step-Attention"></p>
<h4 id="position-embeddings">Position Embeddings</h4>
<p>模型首先得到序列 <code>$\mathbf{x} = \left(x_1, \dotsc, x_m\right)$</code> 的 Embedding <code>$\mathbf{w} = \left(w_1, \dotsc , w_m\right), w_j \in \mathbb{R}^f$</code>。除此之外还将输入序列的位置信息映射为 <code>$\mathbf{p} = \left(p_1, \dotsc, p_m\right), p_j \in \mathbb{R}^f$</code>，最终将两者进行合并得到最终的输入 <code>$\mathbf{e} = \left(w_1 + p_1, \dotsc, w_m + p_m\right)$</code>。同时在解码器部分也采用类似的操作，将其与解码器网络的输出表示合并之后再喂入解码器网络 <code>$\mathbf{g} = \left(g_1, \dotsc, g_n\right)$</code> 中。</p>
<h4 id="convolutional-block-structure">Convolutional Block Structure</h4>
<p>编码器和解码器均由多个 Convolutional Block 构成，每个 Block 包含一个卷积计算和一个非线性计算。令 <code>$\mathbf{h}^l = \left(h_1^l, \dotsc, h_n^l\right)$</code> 表示解码器第 <code>$l$</code> 个 Block 的输出，<code>$\mathbf{z}^l = \left(z_1^l, \dotsc, z_m^l\right)$</code> 表示编码器第 <code>$l$</code> 个 Block 的输出。对于一个大小 <code>$k = 5$</code> 的卷积核，其结果的隐状态包含了这 5 个输入，则对于一个 6 层的堆叠结构，结果的隐状态则包含了输入中的 25 个元素。</p>
<p>在每一个 Convolutional Block 中，卷积核的参数为 <code>$W \in \mathbb{R}^{2d \times kd}, b_w \in \mathbb{R}^{2d}$</code>，其中 <code>$k$</code> 为卷积核的大小，经过卷积后的输出为 <code>$Y \in \mathbb{R}^{2d}$</code>。之后的非线性层采用了 Dauphin 等人 <sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> 提出的 Gated Linear Units (GLU)，对于卷积后的输出 <code>$Y = \left[A, B\right]$</code> 有：</p>
<p><code>$$ v \left(\left[A, B\right]\right) = A \otimes \sigma \left(B\right) $$</code></p>
<p>其中，<code>$A, B \in \mathbb{R}^d$</code> 为非线性单元的输入，<code>$\otimes$</code> 为逐元素相乘，<code>$\sigma \left(B\right)$</code> 为用于控制输入 <code>$A$</code> 与当前上下文相关度的门结构。</p>
<p>模型中还加入了残差连接，即：</p>
<p><code>$$ h_i^l = v \left(W^l \left[h_{i-k/2}^{l-1}, \dotsc, h_{i+k/2}^{l-1}\right] + b_w^l\right) + h_i^{l-1} $$</code></p>
<p>为了确保网络卷积层的输出同输入的长度相匹配，模型对输入数据的前后填补 <code>$k - 1$</code> 个零值，同时为了避免解码器使用当前预测位置之后的信息，模型删除了卷积输出尾部的 <code>$k$</code> 个元素。在将 Embedding 喂给编码器网络之前，在解码器输出应用 softmax 之前以及所有解码器层计算 Attention 分数之前，建立了一个从 Embedding 维度 <code>$f$</code> 到卷积输出大小 <code>$2d$</code> 的线性映射。最终，预测下一个词的概率计算方式如下：</p>
<p><code>$$ p \left(y_{i+1} | y_1, \dotsc, y_i, \mathbf{x}\right) = \text{softmax} \left(W_o h_i^L + b_o\right) \in \mathbb{R}^T $$</code></p>
<h4 id="multi-step-attention-1">Multi-step Attention</h4>
<p>模型的解码器网络中引入了一个分离的注意力机制，在计算 Attention 时，将解码器当前的隐状态 <code>$h_i^l$</code> 同之前输出元素的 Embedding 进行合并：</p>
<p><code>$$ d_i^l = W_d^l h_i^l + b_d^l + g_i $$</code></p>
<p>对于解码器网络层 <code>$l$</code> 中状态 <code>$i$</code> 和输入元素 <code>$j$</code> 之间的的 Attention <code>$a_{ij}^l$</code> 通过解码器汇总状态 <code>$d_i^l$</code> 和最后一个解码器 Block <code>$u$</code> 的输出 <code>$z_j^u$</code> 进行点积运算得到：</p>
<p><code>$$ a_{ij}^l = \dfrac{\exp \left(d_i^l \cdot z_j^u\right)}{\sum_{t=1}^{m}{\exp \left(d_i^l \cdot z_t^u\right)}} $$</code></p>
<p>条件输入 <code>$c_i^l$</code> 的计算方式如下：</p>
<p><code>$$ c_i^l = \sum_{j=1}^{m}{a_{ij}^l \left(z_j^u + e_j\right)} $$</code></p>
<p>其中，<code>$e_j$</code> 为输入元素的 Embedding。与传统的 Attention 不同，<code>$e_j$</code> 的加入提供了一个有助于预测的具体输入元素信息。</p>
<p>最终将 <code>$c_i^l$</code> 加到对应的解码器层的输出 <code>$h_i^l$</code>。这个过程与传统的单步 Attention 不同，被称之为 Multiple Hops <sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup>。这种方式使得模型在计算 Attention 时会考虑之前已经注意过的输入信息。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Cho, K., van Merrienboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., &amp; Bengio, Y. (2014). Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation. In <em>Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP)</em> (pp. 1724–1734).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Sutskever, I., Vinyals, O., &amp; Le, Q. V. (2014). Sequence to Sequence Learning with Neural Networks. In Z. Ghahramani, M. Welling, C. Cortes, N. D. Lawrence, &amp; K. Q. Weinberger (Eds.), <em>Advances in Neural Information Processing Systems 27</em> (pp. 3104–3112).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Bahdanau, D., Cho, K., &amp; Bengio, Y. (2014). Neural Machine Translation by Jointly Learning to Align and Translate. <em>arXiv preprint arXiv:1409.0473</em>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Xu, K., Ba, J., Kiros, R., Cho, K., Courville, A., Salakhudinov, R., … Bengio, Y. (2015). Show, Attend and Tell: Neural Image Caption Generation with Visual Attention. In <em>International Conference on Machine Learning</em> (pp. 2048–2057).&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Luong, T., Pham, H., &amp; Manning, C. D. (2015). Effective Approaches to Attention-based Neural Machine Translation. In <em>Proceedings of the 2015 Conference on Empirical Methods in Natural Language Processing</em> (pp. 1412–1421).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., … Polosukhin, I. (2017). Attention is All you Need. In I. Guyon, U. V. Luxburg, S. Bengio, H. Wallach, R. Fergus, S. Vishwanathan, &amp; R. Garnett (Eds.), <em>Advances in Neural Information Processing Systems 30</em> (pp. 5998–6008).&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>He, K., Zhang, X., Ren, S., &amp; Sun, J. (2016). Deep Residual Learning for Image Recognition. In <em>2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR)</em> (pp. 770–778).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Ba, J. L., Kiros, J. R., &amp; Hinton, G. E. (2016). Layer Normalization. <em>arXiv preprint arXiv:1607.06450</em>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Yang, Z., Yang, D., Dyer, C., He, X., Smola, A., &amp; Hovy, E. (2016). Hierarchical Attention Networks for Document Classification. In <em>Proceedings of the 2016 Conference of the North American Chapter of the Association for Computational Linguistics</em>: Human Language Technologies (pp. 1480–1489).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Cui, Y., Chen, Z., Wei, S., Wang, S., Liu, T., &amp; Hu, G. (2017). Attention-over-Attention Neural Networks for Reading Comprehension. In <em>Proceedings of the 55th Annual Meeting of the Association for Computational Linguistics</em> (Volume 1: Long Papers) (pp. 593–602).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Gehring, J., Auli, M., Grangier, D., Yarats, D., &amp; Dauphin, Y. N. (2017). Convolutional Sequence to Sequence Learning. In <em>International Conference on Machine Learning</em> (pp. 1243–1252).&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>Dauphin, Y. N., Fan, A., Auli, M., &amp; Grangier, D. (2016). Language Modeling with Gated Convolutional Networks. <em>arXiv preprint arXiv:1612.08083</em>&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p>Sukhbaatar, S., szlam,  arthur, Weston, J., &amp; Fergus, R. (2015). End-To-End Memory Networks. In C. Cortes, N. D. Lawrence, D. D. Lee, M. Sugiyama, &amp; R. Garnett (Eds.), <em>Advances in Neural Information Processing Systems 28</em> (pp. 2440–2448).&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>词向量 (Word Embeddings)</title><link>https://zeqiang.fun/cn/2018/10/word-embeddings/</link><pubDate>Mon, 01 Oct 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/10/word-embeddings/</guid><description><![CDATA[
        <h2 id="文本表示">文本表示</h2>
<p>文本表示是计算机处理自然语言的核心，我们希望计算机能够同人类一样对自然语言能够实现语义层面的理解，但这并非易事。在中文和拉丁语系中，文本的直观表示就存在一定的差异，拉丁语系中词与词之间存在天然的分隔符，而中文则没有。</p>
<blockquote>
<p>I can eat glass, it doesn&rsquo;t hurt me.<br>
我能吞下玻璃而不伤身体。</p>
</blockquote>
<p>所以，在处理中文之前我们往往需要对原始文本进行分词，在此我们不谈这部分工作，假设我们已经得到了分词完的文本，即我们后续需要处理的“<strong>词</strong>”。早期的词表示方法多采用独热编码 (One-Hot Encoding)，对于每一个不同的词都使用一个单独的向量进行表示。对于一个包含 <code>$n$</code> 个词的语料而言，一个词的向量表示 <code>$\text{word}_i \in \left\{0, 1\right\}^n$</code> 仅在第 <code>$i$</code> 的位置值为 1，其他位置的值均为 0。例如，我们可以将“父亲”表示为：</p>
<p><code>$$ \left[1, 0, 0, 0, 0, 0, ...\right] \nonumber $$</code></p>
<p>One-Hot Encoding 的表示方法十分简洁，但也存在着一些问题。</p>
<h3 id="维数灾难-the-curse-of-dimensionality">维数灾难 (The Curse of Dimensionality)</h3>
<p>在很多现实问题中，我们仅用少数的特征是很难利用一个线性模型将数据区分开来的，也就是线性不可分问题。一个有效的方法是利用核函数实现一个非线性变换，将非线性问题转化成线性问题，通过求解变换后的线性问题进而求解原来的非线性问题。</p>
<p>假设 <code>$\mathcal{X}$</code> 是输入空间（欧式空间 <code>$\mathbb{R}^n$</code> 的子集或离散结合），<code>$\mathcal{H}$</code> 为特征空间（希尔伯特空间），若存在一个从 <code>$\mathcal{X}$</code> 到 <code>$ \mathcal{H}$</code> 的映射：</p>
<p><code>$$\phi \left(x\right): \mathcal{X} \rightarrow \mathcal{H}$$</code></p>
<p>使得对所有 <code>$x, z \in \mathcal{X}$</code> ，函数 <code>$K\left(x, z\right)$</code> 满足条件：</p>
<p><code>$$K\left(x, z\right) = \phi \left(x\right) \cdot \phi \left(z\right)$$</code></p>
<p>则 <code>$K\left(x, z\right)$</code> 为核函数， <code>$\phi \left(x\right)$</code> 为映射函数，其中 <code>$\phi \left(x\right) \cdot \phi \left(z\right)$</code> 为 <code>$\phi \left(x\right)$</code> 和 <code>$\phi \left(z\right)$</code> 的内积。</p>
<p>例如，对于一个下图所示的二维数据，显然是线性不可分的。</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/2d-points.png" alt="2d-Points"></p>
<p>构建一个映射 <code>$\phi: \mathbb{R}^2 \rightarrow \mathbb{R}^3$</code> 经 <code>$X$</code> 映射为： <code>$x = x^2, y = y^2, z = y$</code> ，则通过变换后的数据通过可视化可以明显地看出，数据是可以通过一个超平面来分开的。</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/3d-points.png" alt="3d-Points"></p>
<p>可以说随着维度的增加，我们更有可能找到一个超平面（线性模型）将数据划分开来。尽管看起来，随着维度的增加似乎有助于我们构建模型，但是同时数据在高维空间的分布变得越来越<strong>稀疏</strong>。因此，在构建机器学习模型时，当我们需要更好的覆盖数据的分布时，我们需要的数据量就更大，这也就会导致需要更多的时间去训练模型。例如，假设所有特征均为0到1之间连续分布的数据，针对1维的情况，当覆盖50%的数据时，仅需全体50%的样本即可；针对2维的情况，当覆盖50%的数据时，则需全体71% ( <code>$0.71^2 \approx 0.5$</code> ) 的样本；针对3维的情况，当覆盖50%的数据时，则需全体79% ( <code>$0.79^3 \approx 0.5$</code> )，这就是我们所说的维数灾难。</p>
<h3 id="分散式表示-distributed-representations">分散式表示 (Distributed Representations)</h3>
<p>分散式表示（Distributed Representations）<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 最早由 Hiton 提出，对比于传统的 One-Hot Representation ，Distributed Representations 可以将数据表示为低维，稠密，连续的向量，也就是说将原始空间中的潜在信息分散的表示在低维空间的不同维度上。</p>
<p>传统的 One-Hot Representation 会将数据表示成一个很长的向量，例如，在 NLP 中，利用 One-Hot Representation 表示一个单词：</p>
<pre><code>父亲: [1, 0, 0, 0, 0, 0, ...]
爸爸: [0, 1, 0, 0, 0, 0, ...]
母亲: [0, 0, 1, 0, 0, 0, ...]
妈妈: [0, 0, 0, 1, 0, 0, ...]
</code></pre>
<p>这种表示形式很简介，但也很稀疏，相当于语料库中有多少个词，则表示空间的维度就需要多少。那么，对于传统的聚类算法，高斯混合模型，最邻近算法，决策树或高斯 SVM 需要 <code>$O\left(N\right)$</code> 个参数 (或 <code>$O\left(N\right)$</code> 个样本) 将能够将 <code>$O\left(N\right)$</code> 的输入区分开来。而像 RBMs ，稀疏编码，Auto-Encoder 或多层神经网络则可以利用 <code>$O\left(N\right)$</code> 个参数表示 <code>$O\left(2^k\right)$</code> 的输入，其中 <code>$k \leq N$</code> 为稀疏表示中非零元素的个数 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p>
<p>采用 Distributed Representation，则可以将单词表示为：</p>
<pre><code>父亲: [0.12, 0.34, 0.65, ...]
爸爸: [0.11, 0.33, 0.58, ...]
母亲: [0.34, 0.98, 0.67, ...]
妈妈: [0.29, 0.92, 0.66, ...]
</code></pre>
<p>利用这种表示，我们不仅可以将稀疏的高维空间转换为稠密的低维空间，同时我们还能学习出文本间的语义相似性来，例如实例中的 <code>父亲</code> 和 <code>爸爸</code>，从语义上看其均表示 <code>父亲</code> 的含义，但是如果利用 One-Hot Representation 编码则 <code>父亲</code> 与 <code>爸爸</code> 的距离同其与 <code>母亲</code> 或 <code>妈妈</code> 的距离时相同的，而利用 Distributed Representation 编码，则 <code>父亲</code> 同 <code>爸爸</code> 之间的距离要远小于其同 <code>母亲</code> 或 <code>妈妈</code> 之间的距离。</p>
<h2 id="word-embedding-之路">Word Embedding 之路</h2>
<h3 id="n-gram-模型">N-gram 模型</h3>
<p>N-gram (N 元语法) 是一种文本表示方法，指文中连续出现的 <code>$n$</code> 个词语。N-gram 模型是基于 <code>$n-1$</code> 阶马尔科夫链的一种概率语言模型，可以通过前 <code>$n-1$</code> 个词对第 <code>$n$</code> 个词进行预测。Bengio 等人 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 提出了一个三层的神经网络的概率语言模型，其网络结构如下图所示：</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/nplm-network.png" alt="NPLM-Network"></p>
<p>模型的最下面为前 <code>$n-1$</code> 个词 <code>$w_{t-n+1}, ..., w_{t-2}, w_{t-1}$</code>，每个词 <code>$w_i$</code> 通过查表的方式同输入层对应的词向量 <code>$C \left(w_i\right)$</code> 相连。词表 <code>$C$</code> 为一个 <code>$\lvert V\rvert \times m$</code> 大小的矩阵，其中 <code>$\lvert V\rvert$</code> 表示语料中词的数量，<code>$m$</code> 表示词向量的维度。输入层则为前 <code>$n-1$</code> 个词向量拼接成的向量 <code>$x$</code>，其维度为 <code>$m \left(n-1\right) \times 1$</code>。隐含层直接利用 <code>$d + Hx$</code> 计算得到，其中 <code>$H$</code> 为隐含层的权重，<code>$d$</code> 为隐含层的偏置。输出层共包含 <code>$\lvert V\rvert$</code> 个神经元，每个神经元 <code>$y_i$</code> 表示下一个词为第 <code>$i$</code> 个词的未归一化的 log 概率，即：</p>
<p><code>$$ y = b + Wx + U \tanh \left(d + Hx\right) $$</code></p>
<p>对于该问题，我们的优化目标为最大化如下的 log 似然函数：</p>
<p><code>$$ L = \dfrac{1}{T} \sum_{t}{f \left(w_t, w_{t-1}, ..., w_{t-n+1}\right) + R \left(\theta\right)} $$</code></p>
<p>其中，<code>$f \left(w_t, w_{t-1}, ..., w_{t-n+1}\right)$</code> 为利用前 <code>$n-1$</code> 个词预测当前词 <code>$w_t$</code> 的条件概率，<code>$R \left(\theta\right)$</code> 为参数的正则项，<code>$\theta = \left(b, d, W, U, H, C\right)$</code>。<code>$C$</code> 作为模型的参数之一，随着模型的训练不断优化，在模型训练完毕后，<code>$C$</code> 中保存的即为词向量。</p>
<h3 id="continuous-bag-of-words-cbow-和-skip-gram-模型">Continuous Bag-of-Words (CBOW) 和 Skip-gram 模型</h3>
<p>CBOW 和 Skip-gram 均考虑一个词的上下文信息，两种模型的结构如下图所示：</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/cbow-skipgram.png" alt="CBOW-Skipgram"></p>
<p>两者在给定的上下文信息中 (即前后各 <code>$m$</code> 个词) 忽略了上下文环境的序列信息，CBOW (上图左) 是利用上下文环境中的词预测当前的词，而 Skip-gram (上图右) 则是用当前词预测上下文中的词。</p>
<p>对于 CBOW，<code>$x_{1k}, x_{2k}, ..., x_{Ck}$</code> 为上下文词的 One-Hot 表示，<code>$\mathbf{W}_{V \times N}$</code> 为所有词向量构成的矩阵 (词汇表)，<code>$y_j$</code> 为利用上下文信息预测得到的当前词的 One-Hot 表示输出，其中 <code>$C$</code> 为上下文词汇的数量，<code>$V$</code> 为词汇表中词的总数量，<code>$N$</code> 为词向量的维度。从输入层到隐含层，我们对输入层词对应的词向量进行简单的加和，即：</p>
<p><code>$$ h_i = \sum_{c=1}^{C}{x_{ck} \mathbf{W}_{V \times N}} $$</code></p>
<p>对于 Skip-gram，<code>$x_k$</code> 为当前词的 One-Hot 表示，<code>$\mathbf{W}_{V \times N}$</code> 为所有词向量构成的矩阵 (词汇表)，<code>$y_{1j}, y_{2j}, ..., y_{Cj}$</code> 为预测的上次文词汇的 One-Hot 表示输出。从输入层到隐含层，直接将 One-Hot 的输入向量转换为词向量表示即可。</p>
<p>除此之外两者还有一些其他的区别：</p>
<ol>
<li>CBOW 要比 Skip-gram 模型训练快。从模型中我们不难发现：从隐含层到输出层，CBOW 仅需要计算一个损失，而 Skip-gram 则需要计算 <code>$C$</code> 个损失再进行平均进行参数优化。</li>
<li>Skip-gram 在小数量的数据集上效果更好，同时对于生僻词的表示效果更好。CBOW 在从输入层到隐含层时，对输入的词向量进行了平均 (可以理解为进行了平滑处理)，因此对于生僻词，平滑后则容易被模型所忽视。</li>
</ol>
<h3 id="word2vec">Word2Vec</h3>
<p>Mikolov 等人 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> 利用上面介绍的 CBOW 和 Skip-gram 两种模型提出了经典的 Word2Vec 算法。Word2Vec 中针对 CBOW 和 Skip-gram 又提出了两种具体的实现方案 Hierarchical Softmax (层次 Softmax) 和 Negative Sampling (负采样)，因此共有 4 种不同的模型。</p>
<ul>
<li><strong>基于 Hierarchical Softmax 的模型</strong></li>
</ul>
<p><strong>基于 Hierarchical Softmax 的 CBOW 模型如下</strong>：</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/hierarchical-softmax-cbow.png" alt="Hierarchical-Softmax-CBOW"></p>
<p>其中：</p>
<ol>
<li><strong>输入层</strong>：包含了 <code>$C$</code> 个词的词向量，<code>$\mathbf{v} \left(w_1\right), \mathbf{v} \left(w_2\right), ..., \mathbf{v} \left(w_C\right) \in \mathbb{R}^N$</code>，<code>$N$</code> 为词向量的维度。</li>
<li><strong>投影层</strong>：将输入层的向量进行加和，即：<code>$\mathbf{x}_w = \sum_{i=1}^{C}{\mathbf{v} \left(w_i\right)} \in \mathbb{R}^N$</code>。</li>
<li><strong>输出层</strong>：输出为一颗二叉树，是根据语料构建出来的 Huffman 树 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>，其中每个叶子节点为词汇表中的一个词。</li>
</ol>
<p>Hierarchical Softmax 是解决概率语言模型中计算效率的关键，CBOW 模型去掉了隐含层，同时将输出层改为了 Huffman 树。对于该模型的优化求解，我们首先引入一些符号，对于 Huffman 树的一个叶子节点 (即词汇表中的词 <code>$w$</code>)，记：</p>
<ul>
<li><code>$p^w$</code>：从根节点出发到达 <code>$w$</code> 对应的叶子节点的路径。</li>
<li><code>$l^w$</code>：路径 <code>$p^w$</code> 包含的节点的个数。</li>
<li><code>$p_1^w, p_1^w, ..., p_{l^w}^w$</code>：路径 <code>$p^w$</code> 中的 <code>$l^w$</code> 个节点，其中 <code>$p_1^w$</code> 表示根节点，<code>$p_{l^w}^w$</code> 表示词 <code>$w$</code> 对应的叶子节点。</li>
<li><code>$d_2^w, d_3^w, ..., d_{l^w}^w \in \{0, 1\}$</code>：词 <code>$w$</code> 的 Huffman 编码，由 <code>$l^w - 1$</code> 位编码构成，<code>$d_j^w$</code> 表示路径 <code>$p^w$</code> 中第 <code>$j$</code> 个结点对应的编码。</li>
<li><code>$\theta_1^w, \theta_1^w, ..., \theta_{l^w - 1}^w \in \mathbb{R}^N$</code>：路径 <code>$p^w$</code> 中非叶子节点对应的向量，<code>$\theta_j^w$</code> 表示路径 <code>$p^w$</code> 中第 <code>$j$</code> 个非叶子节点对应的向量。</li>
</ul>
<p>首先我们需要根据向量 <code>$\mathbf{x}_w$</code> 和 Huffman 树定义条件概率 <code>$p \left(w | Context\left(w\right)\right)$</code>。我们可以将其视为一系列的二分类问题，在到达对应的叶子节点的过程中，经过的每一个非叶子节点均为对应一个取值为 0 或 1 的 Huffman 编码。因此，我们可以将编码为 1 的节点定义为负类，将编码为 0 的节点定义为正类 (即分到左边为负类，分到右边为正类)，则这条路径上对应的标签为：</p>
<p><code>$$ Label \left(p_i^w\right) = 1 - d_i^w, i = 2, 3, ..., l^w $$</code></p>
<p>则对于一个节点被分为正类的概率为 <code>$\sigma \left(\mathbf{x}_w^{\top} \theta\right)$</code>，被分为负类的概率为 <code>$1 - \sigma \left(\mathbf{x}_w^{\top} \theta\right)$</code>。则条件概率可以表示为：</p>
<p><code>$$ p \left(w | Context\left(w\right)\right) = \prod_{j=2}^{l^w}{p \left(d_j^w | \mathbf{x}_w, \theta_{j-1}^w\right)} $$</code></p>
<p>其中</p>
<p><code>$$ p \left(d_j^w | \mathbf{x}_w, \theta_{j-1}^w\right) = \begin{cases} \sigma \left(\mathbf{x}_w^{\top} \theta\right) &amp; d_j^w = 0 \\ 1 - \sigma \left(\mathbf{x}_w^{\top} \theta\right) &amp; d_j^w = 1 \end{cases} $$</code></p>
<p>或表示为：</p>
<p><code>$$ p \left(d_j^w | \mathbf{x}_w, \theta_{j-1}^w\right) = \left[\sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}\right)\right]^{1 - d_j^w} \cdot \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}\right)\right]^{d_j^w} $$</code></p>
<p>则对数似然函数为：</p>
<p><code>$$ \begin{equation} \begin{split} \mathcal{L} &amp;= \sum_{w \in \mathcal{C}}{\log \prod_{j=2}^{l^w}{\left\{\left[\sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}\right)\right]^{1 - d_j^w} \cdot \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}\right)\right]^{d_j^w}\right\}}} \\ &amp;= \sum_{w \in \mathcal{C}}{\sum_{j=2}^{l^w}{\left\{\left(1 - d_j^w\right) \cdot \log \left[\sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] + d_j^w \cdot \log \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right]\right\}}} \end{split} \end{equation} $$</code></p>
<p>记上式花括号中的内容为 <code>$\mathcal{L} \left(w, j\right)$</code>，则 <code>$\mathcal{L} \left(w, j\right)$</code> 关于 <code>$\theta_{j-1}^w$</code> 的梯度为：</p>
<p><code>$$ \begin{equation} \begin{split} \dfrac{\partial \mathcal{L} \left(w, j\right)}{\partial \theta_{j-1}^w} &amp;= \dfrac{\partial}{\partial \theta_{j-1}^w} \left\{\left(1 - d_j^w\right) \cdot \log \left[\sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] + d_j^w \cdot \log \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right]\right\} \\ &amp;= \left(1 - d_j^w\right) \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] \mathbf{x}_w - d_j^w \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right) \mathbf{x}_w \\ &amp;= \left\{\left(1 - d_j^w\right) \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] - d_j^w \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right\} \mathbf{x}_w \\ &amp;= \left[1 - d_j^w - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] \mathbf{x}_w \end{split} \end{equation} $$</code></p>
<p>则 <code>$\theta_{j-1}^w$</code> 的更新方式为：</p>
<p><code>$$ \theta_{j-1}^w \gets \theta_{j-1}^w + \eta \left[1 - d_j^w - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] \mathbf{x}_w $$</code></p>
<p>同理可得，<code>$\mathcal{L} \left(w, j\right)$</code> 关于 <code>$\mathbf{x}_w$</code> 的梯度为：</p>
<p><code>$$ \dfrac{\partial \mathcal{L} \left(w, j\right)}{\partial \mathbf{x}_w} = \left[1 - d_j^w - \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)\right] \theta_{j-1}^w $$</code></p>
<p>但 <code>$\mathbf{x}_w$</code> 为上下文词汇向量的加和，Word2Vec 的做法是将梯度贡献到上下文中的每个词向量上，即：</p>
<p><code>$$ \mathbf{v} \left(u\right) \gets \mathbf{v} \left(u\right) + \eta \sum_{j=2}^{l^w}{\dfrac{\partial \mathcal{L} \left(w, j\right)}{\partial \mathbf{x}_w}}, u \in Context \left(w\right) $$</code></p>
<p>基于 Hierarchical Softmax 的 CBOW 模型的随机梯度上升算法伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{基于 Hierarchical Softmax 的 CBOW 随机梯度上升算法}
\begin{algorithmic}
\STATE $\mathbf{e} = 0$
\STATE $\mathbf{x}_w = \sum_{u \in Context \left(w\right)}{\mathbf{v} \left(u\right)}$
\FOR{$j = 2, 3, ..., l^w$}
    \STATE $q = \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^w\right)$
    \STATE $g = \eta \left(1 - d_j^w - q\right)$
    \STATE $\mathbf{e} \gets \mathbf{e} + g \theta_{j-1}^w$
    \STATE $\theta_{j-1}^w \gets \theta_{j-1}^w + g \mathbf{x}_w$
\ENDFOR
\FOR{$u \in Context \left(w\right)$}
    \STATE $\mathbf{v} \left(u\right) \gets \mathbf{v} \left(u\right) + \mathbf{e}$
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<p><strong>基于 Hierarchical Softmax 的 Skip-gram 模型如下</strong>：</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/hierarchical-softmax-skipgram.png" alt="Hierarchical-Softmax-Skipgram"></p>
<p>对于 Skip-gram 模型，是利用当前词 <code>$w$</code> 对上下文 <code>$Context \left(w\right)$</code> 中的词进行预测，则条件概率为：</p>
<p><code>$$ p \left(Context \left(w\right) | w\right) = \prod_{u \in Context \left(w\right)}{p \left(u | w\right)} $$</code></p>
<p>类似于 CBOW 模型的思想，有：</p>
<p><code>$$ p \left(u | w\right) = \prod_{j=2}^{l^u}{p \left(d_j^u | \mathbf{v} \left(w\right), \theta_{j-1}^u\right)} $$</code></p>
<p>其中</p>
<p><code>$$ p \left(d_j^u | \mathbf{v} \left(w\right), \theta_{j-1}^u\right) = \left[\sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^u\right)\right]^{1 - d_j^u} \cdot \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^u\right)\right]^{d_j^u} $$</code></p>
<p>可得对数似然函数为：</p>
<p><code>$$ \begin{equation} \begin{split} \mathcal{L} &amp;= \sum_{w \in \mathcal{C}}{\log \prod_{u \in Context \left(w\right)}{\prod_{j=2}^{l^u}{\left\{\left[\sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right]^{1 - d_j^u} \cdot \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^u\right)\right]^{d_j^u}\right\}}}} \\ &amp;= \sum_{w \in \mathcal{C}}{\sum_{u \in Context \left(w\right)}{\sum_{j=2}^{l^u}{\left\{\left(1 - d_j^u\right) \cdot \log \left[\sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] + d_j^u \cdot \log \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right]\right\}}}} \end{split} \end{equation} $$</code></p>
<p>记上式花括号中的内容为 <code>$\mathcal{L} \left(w, u, j\right)$</code>，在 <code>$\mathcal{L} \left(w, u, j\right)$</code> 关于 <code>$\theta_{j-1}^u$</code> 的梯度为：</p>
<p><code>$$ \begin{equation} \begin{split} \dfrac{\partial \mathcal{L} \left(w, u, j\right)}{\partial \theta_{j-1}^{u}} &amp;= \dfrac{\partial}{\partial \theta_{j-1}^{u}} \left\{\left(1 - d_j^u\right) \cdot \log \left[\sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] + d_j^u \cdot \log \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right]\right\} \\ &amp;= \left(1 - d_j^u\right) \cdot \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] \mathbf{v} \left(w\right) - d_j^u \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right) \mathbf{v} \left(w\right) \\ &amp;= \left\{\left(1 - d_j^u\right) \cdot \left[1 - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] - d_j^u \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right\} \mathbf{v} \left(w\right) \\ &amp;= \left[1 - d_j^u - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] \mathbf{v} \left(w\right) \end{split} \end{equation} $$</code></p>
<p>则 <code>$\theta_{j-1}^u$</code> 的更新方式为：</p>
<p><code>$$ \theta_{j-1}^u \gets \theta_{j-1}^u + \eta \left[1 - d_j^u - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] \mathbf{v} \left(w\right) $$</code></p>
<p>同理可得，<code>$\mathcal{L} \left(w, u, j\right)$</code> 关于 <code>$\mathbf{v} \left(w\right)$</code> 的梯度为：</p>
<p><code>$$ \dfrac{\partial \mathcal{L} \left(w, u, j\right)}{\partial \mathbf{v} \left(w\right)} = \left[1 - d_j^u - \sigma \left(\mathbf{v} \left(w\right)^{\top} \theta_{j-1}^{u}\right)\right] \theta_{j-1}^u $$</code></p>
<p>则 <code>$\mathbf{v} \left(w\right)$</code> 的更新方式为：</p>
<p><code>$$ \mathbf{v} \left(w\right) \gets \mathbf{v} \left(w\right) + \eta \sum_{u \in Context \left(w\right)}{\sum_{j=2}^{l^u}{\dfrac{\partial \mathcal{L} \left(w, u, j\right)}{\partial \mathbf{v} \left(w\right)}}} $$</code></p>
<p>基于 Hierarchical Softmax 的 Skip-gram 模型的随机梯度上升算法伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{基于 Hierarchical Softmax 的 Skig-gram 随机梯度上升算法}
\begin{algorithmic}
\STATE $\mathbf{e} = 0$
\FOR{$u \in Context \left(w\right)$}
    \FOR{$j = 2, 3, ..., l^u$}
        \STATE $q = \sigma \left(\mathbf{x}_w^{\top} \theta_{j-1}^u\right)$
        \STATE $g = \eta \left(1 - d_j^u - q\right)$
        \STATE $\mathbf{e} \gets \mathbf{e} + g \theta_{j-1}^u$
        \STATE $\theta_{j-1}^u \gets \theta_{j-1}^u + g \mathbf{v} \left(w\right)$
    \ENDFOR
\ENDFOR
\STATE $\mathbf{v} \left(w\right) \gets \mathbf{v} \left(w\right) + \mathbf{e}$
\end{algorithmic}
\end{algorithm}
</pre></div>

<ul>
<li><strong>基于 Negative Sampling 的模型</strong></li>
</ul>
<p>基于 Negative Sampling (NEG) 的模型相比于基于 Hierarchical Softmax 的模型不再使用复杂的 Huffman 树，而是使用简单的<strong>随机负采样</strong>，从而大幅的提高了模型的性能。</p>
<p><strong>基于 Negative Sampling 的 CBOW 模型如下</strong>：</p>
<p>对于基于 Negative Sampling  CBOW 模型，已知词 <code>$w$</code> 的上下文 <code>$Context \left(w\right)$</code>，预测词 <code>$w$</code>，则词 <code>$w$</code> 即为一个<strong>正样本</strong>，其他词则为<strong>负样本</strong>。对于一个给定 <code>$Context \left(w\right)$</code> 的负样本集合 <code>$NEG \left(w\right) \neq \varnothing$</code>，词典中的任意词 <code>$\forall \tilde{w} \in \mathcal{D}$</code>，其样本的标签定义为：</p>
<p><code>$$ L^w \left(\tilde{w}\right) =  \begin{cases} 1, &amp; \tilde{w} = w \\ 0, &amp; \tilde{w} \neq w \end{cases} $$</code></p>
<p>则对于一个正样本 <code>$\left(Context, \left(w\right)\right)$</code>，我们希望最大化：</p>
<p><code>$$ g \left(w\right) = \prod_{u \in \left\{w\right\} \cup NEG \left(w\right)}{p \left(u | Context \left(w\right)\right)} $$</code></p>
<p>或表示为：</p>
<p><code>$$ p \left(u | Context \left(w\right)\right) = \left[\sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]^{L^w \left(w\right)} \cdot \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]^{1 - L^w \left(w\right)} $$</code></p>
<p>即增大正样本概率的同时减少负样本的概率。对于一个给定的语料库 <code>$\mathcal{C}$</code>，对数似然函数为：</p>
<p><code>$$ \begin{equation} \begin{split} \mathcal{L} &amp;= \sum_{w \in \mathcal{C}}{\log g \left(w\right)} \\ &amp;= \sum_{w \in \mathcal{C}}{\log \prod_{u \in \left\{w\right\} \cup NEG \left(w\right)}{\left\{\left[\sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]^{L^w \left(u\right)} \cdot \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]^{1 - L^w \left(u\right)}\right\}}} \\ &amp;= \sum_{w \in \mathcal{C}}{\sum_{u \in \left\{w\right\} \cup NEG \left(w\right)}{\left\{L^w \left(u\right) \cdot \log \left[\sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right] + \left[1 - L^w \left(u\right)\right] \cdot \log \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]\right\}}} \end{split} \end{equation} $$</code></p>
<p>记上式花括号中的内容为 <code>$\mathcal{L} \left(w, u\right)$</code>，则 <code>$\mathcal{L} \left(w, u\right)$</code> 关于 <code>$\theta^u$</code> 的梯度为：</p>
<p><code>$$ \begin{equation} \begin{split} \dfrac{\partial \mathcal{L} \left(w, u\right)}{\partial \theta^u} &amp;= \dfrac{\partial}{\partial \theta^u} \left\{L^w \left(u\right) \cdot \log \left[\sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right] + \left[1 - L^w \left(u\right)\right] \cdot \log \left[1 - \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right]\right\} \\ &amp;= L^w \left(u\right) \left[1 - \sigma \left(\mathbf{w}_w^{\top} \theta^u\right)\right] \mathbf{x}_w - \left[1 - L^w \left(u\right)\right] \sigma \left(\mathbf{x}_w^{\top} \theta^u\right) \mathbf{x}_w \\ &amp;= \left\{L^w \left(u\right) \left[1 - \sigma \left(\mathbf{w}_w^{\top} \theta^u\right)\right] - \left[1 - L^w \left(u\right)\right] \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)\right\} \mathbf{x}_w \\ &amp;= \left[L^w \left(u\right) - \sigma \left(\mathbf{w}_w^{\top} \theta^u\right)\right] \mathbf{x}_w \end{split} \end{equation} $$</code></p>
<p>则 <code>$\theta^u$</code> 的更新方式为：</p>
<p><code>$$ \theta^u \gets \theta^u + \eta \left[L^w \left(u\right) - \sigma \left(\mathbf{w}_w^{\top} \theta^u\right)\right] \mathbf{x}_w $$</code></p>
<p>同理可得，<code>$\mathcal{L} \left(w, u\right)$</code> 关于 <code>$\mathbf{x}_w$</code> 的梯度为：</p>
<p><code>$$ \dfrac{\partial \mathcal{L} \left(w, u\right)}{\partial \mathbf{x}_w} = \left[L^w \left(u\right) - \sigma \left(\mathbf{w}_w^{\top} \theta^u\right)\right] \theta^u $$</code></p>
<p>则 <code>$\mathbf{v} \left(\tilde{w}\right), \tilde{w} \in Context \left(w\right)$</code> 的更新方式为：</p>
<p><code>$$ \mathbf{v} \left(\tilde{w}\right) \gets \mathbf{v} \left(\tilde{w}\right) + \eta \sum_{u \in \left\{w\right\} \cup NEG \left(w\right)}{\dfrac{\partial \mathcal{L} \left(w, u\right)}{\partial \mathbf{x}_w}}, \tilde{w} \in Context \left(w\right) $$</code></p>
<p>基于 Negative Sampling 的 CBOW 模型的随机梯度上升算法伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{基于 Negative Sampling 的 CBOW 随机梯度上升算法}
\begin{algorithmic}
\STATE $\mathbf{e} = 0$
\STATE $\mathbf{x}_w = \sum_{u \in Context \left(w\right)}{\mathbf{v} \left(u\right)}$
\FOR{$u \in Context \left\{w\right\} \cup NEG \left(w\right)$}
    \STATE $q = \sigma \left(\mathbf{x}_w^{\top} \theta^u\right)$
    \STATE $g = \eta \left(L^w \left(u\right) - q\right)$
    \STATE $\mathbf{e} \gets \mathbf{e} + g \theta^u$
    \STATE $\theta^u \gets \theta^u + g \mathbf{x}_w$
\ENDFOR
\FOR{$u \in Context \left(w\right)$}
    \STATE $\mathbf{v} \left(u\right) \gets \mathbf{v} \left(u\right) + \mathbf{e}$
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<p><strong>基于 Negative Sampling 的 Skip-gram 模型如下</strong>：</p>
<p>对于 Skip-gram 模型，利用当前词 <code>$w$</code> 对上下文 <code>$Context \left(w\right)$</code> 中的词进行预测，则对于一个正样本 <code>$\left(Context, \left(w\right)\right)$</code>，我们希望最大化：</p>
<p><code>$$ g \left(w\right) = \prod_{\tilde{w} \in Context \left(w\right)}{\prod_{u \in \left\{w\right\} \cup NEG^{\tilde{w}} \left(w\right)}{p \left(u | \tilde{w}\right)}} $$</code></p>
<p>其中，<code>$NEG^{\tilde{w}} \left(w\right)$</code> 为处理词 <code>$\tilde{w}$</code> 时生成的负样本集合，且：</p>
<p><code>$$ p \left(u | \tilde{w}\right) =  \begin{cases} \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right) &amp; L^w \left(u\right) = 1 \\ 1 - \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right) &amp; L^w \left(u\right) = 0 \end{cases} $$</code></p>
<p>或表示为：</p>
<p><code>$$ p \left(u | \tilde{w}\right) = \left[\sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]^{L^w \left(u\right)} \cdot \left[1 - \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]^{1 - L^w \left(u\right)} $$</code></p>
<p>对于一个给定的语料库 <code>$\mathcal{C}$</code>，对数似然函数为：</p>
<p><code>$$ \begin{equation} \begin{split} \mathcal{L} &amp;= \sum_{w \in \mathcal{C}}{\log g \left(w\right)} \\ &amp;= \sum_{w \in \mathcal{C}}{\log \prod_{\tilde{w} \in Context \left(w\right)}{\prod_{u \in \left\{w\right\} \cup NEG^{\tilde{w}} \left(w\right)}{\left\{\left[\sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]^{L^w \left(u\right)} \cdot \left[1 - \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]^{1 - L^w \left(u\right)}\right\}}}} \\ &amp;= \sum_{w \in \mathcal{C}}{\sum_{\tilde{w} \in Context \left(w\right)}{\sum_{u \in \left\{w\right\} \cup NEG^{\tilde{w}} \left(w\right)}{\left\{L^w \left(u\right) \cdot \log \left[\sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right] + \left[1 - L^w \left(u\right)\right] \cdot \log \left[1 - \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]\right\}}}} \end{split} \end{equation} $$</code></p>
<p>记上式花括号中的内容为 <code>$\mathcal{L} \left(w, \tilde{w}, u\right)$</code>，则 <code>$\mathcal{L} \left(w, \tilde{w}, u\right)$</code> 关于 <code>$\theta^u$</code> 的梯度为：</p>
<p><code>$$ \begin{equation} \begin{split} \dfrac{\partial \mathcal{L} \left(w, \tilde{w}, u\right)}{\partial \theta^u} &amp;= \dfrac{\partial}{\partial \theta^u} \left\{L^w \left(u\right) \cdot \log \left[\sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right] + \left[1 - L^w \left(u\right)\right] \cdot \log \left[1 - \sigma \left(\mathbf{v}\left(\tilde{w}\right)^{\top} \theta^u\right)\right]\right\} \\ &amp;= L^w \left(u\right) \left[1 - \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right] \mathbf{v} \left(\tilde{w}\right) - \left[1 - L^w \left(u\right)\right] \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right) \mathbf{v} \left(\tilde{w}\right) \\ &amp;= \left\{L^w \left(u\right) \left[1 - \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right] - \left[1 - L^w \left(u\right)\right] \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right\} \mathbf{v} \left(\tilde{w}\right) \\ &amp;= \left[L^w \left(u\right) - \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right] \mathbf{v} \left(\tilde{w}\right) \end{split} \end{equation} $$</code></p>
<p>则 <code>$\theta^u$</code> 的更新方式为：</p>
<p><code>$$ \theta^u \gets \theta^u + \eta \left[L^w \left(u\right) - \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right] \mathbf{v} \left(\tilde{w}\right) $$</code></p>
<p>同理可得，<code>$\mathcal{L} \left(w, \tilde{w}, u\right)$</code> 关于 <code>$\mathbf{v} \left(\tilde{w}\right)$</code> 的梯度为：</p>
<p><code>$$ \dfrac{\partial \mathcal{L} \left(w, \tilde{w}, u\right)}{\partial \mathbf{v} \left(\tilde{w}\right)} = \left[L^w \left(u\right) - \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)\right] \theta^u $$</code></p>
<p>则 <code>$\mathbf{v} \left(\tilde{w}\right)$</code> 的更新方式为：</p>
<p><code>$$ \mathbf{v} \left(\tilde{w}\right) \gets \mathbf{v} \left(\tilde{w}\right) + \eta \sum_{u \in \left\{w\right\} \cup NEG^{\tilde{w}} \left(w\right)}{\dfrac{\partial \mathcal{L} \left(w, \tilde{w}, u\right)}{\partial \mathbf{v} \left(\tilde{w}\right)}} $$</code></p>
<p>基于 Negative Sampling 的 Skig-gram 模型的随机梯度上升算法伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{基于 Negative Sampling 的 Skig-gram 随机梯度上升算法}
\begin{algorithmic}
\STATE $\mathbf{e} = 0$
\FOR{$\tilde{w} \in Context \left(w\right)$}
    \FOR{$u \in \left\{w\right\} \cup NEG^{\tilde{w}} \left(w\right)$}
        \STATE $q = \sigma \left(\mathbf{v} \left(\tilde{w}\right)^{\top} \theta^u\right)$
        \STATE $g = \eta \left(L^w \left(u\right) - q\right)$
        \STATE $\mathbf{e} \gets \mathbf{e} + g \theta^u$
        \STATE $\theta^u \gets \theta^u + g \mathbf{v} \left(\tilde{w}\right)$
    \ENDFOR
\ENDFOR
\STATE $\mathbf{v} \left(\tilde{w}\right) \gets \mathbf{v} \left(\tilde{w}\right) + \mathbf{e}$
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>无论是基于 Negative Sampling 的 CBOW 模型还是 Skip-gram 模型，我们都需要对于给定的词 <code>$w$</code> 生成 <code>$NEG \left(w\right)$</code>，对于一个词典 <code>$\mathcal{D}$</code> 和给定的语料 <code>$\mathcal{C}$</code>，一个词被选择中的概率为：</p>
<p><code>$$ p_{NEG} \left(w\right) = \dfrac{\#w}{\sum_{u \in \mathcal{D}}{\#u}} $$</code></p>
<p>其中 <code>$\#w$</code> 和 <code>$\#u$</code> 表示词 <code>$w$</code> 和 <code>$u$</code> 在语料 <code>$\mathcal{C}$</code> 中出现的频次。在 Word2Vec 的 C 代码中 <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>，并没有使用词的原始频次，而是对其做了 0.75 次幂，即：</p>
<p><code>$$ p_{NEG} \left(w\right) = \dfrac{\left(\#w\right)^{0.75}}{\sum_{u \in \mathcal{D}}{\left(\#u\right)^{0.75}}} $$</code></p>
<div class="blockquote" style='border-left: 4px solid #369BE5;'>本节内容参考了 licstar 的 <a href="http://licstar.net/archives/328">博客</a> 和 peghoty 的 <a href="https://www.cnblogs.com/peghoty/p/3857839.html">博客</a>。</div>
<h2 id="其他-embedding-方法">其他 Embedding 方法</h2>
<h3 id="glove">GloVe</h3>
<p>GloVe (Global Vector 的简写) 是由 Pennington 等人 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 提出了一种词向量生成方法，该方法利用了语料的全局统计信息。</p>
<p>令 <code>$X$</code> 表示词与词之间的共现矩阵，<code>$X_{ij}$</code> 表示词 <code>$j$</code> 在词 <code>$i$</code> 为上下文的情况下出现的频次。则 <code>$X_i = \sum_{k}{X_{ik}}$</code> 表示在词<code>$i$</code> 为上下文的情况任意词出现的总次数。令 <code>$P_{ij} = P \left(j | i\right) = X_{ij} / X_i$</code> 表示词 <code>$j$</code> 在词 <code>$i$</code> 出现前提下出现的条件概率。</p>
<p>例如，我们令 <code>$i = ice, j = steam$</code>，则这两个词之间的关系可以利用同其他词 <code>$k$</code> 共现概率的比率学习得出。则有：</p>
<ol>
<li>与词 <code>ice</code> 相关，但与词 <code>steam</code> 不太相关，例如 <code>$k = solid$</code>，则比率 <code>$P_{ik} / P_{jk}$</code> 应该较大；类似的当词 <code>$k$</code> 与 <code>steam</code> 相关，但与词 <code>ice</code> 不太相关，则比率 <code>$P_{ik} / P_{jk}$</code> 应该较小。</li>
<li>当与词 <code>ice</code> 和词 <code>steam</code> 均相关或者均不太相关时，例如 <code>$k = water$</code> 或 <code>$k = fashion$</code>，则比率 <code>$P_{ik} / P_{jk}$</code> 应该和 1 接近。</li>
</ol>
<p>下表展示了在一个大量语料上的概率及其比率：</p>
<table>
  <thead>
      <tr>
          <th>概率和比例</th>
          <th><code>$k = solid$</code></th>
          <th><code>$k = gas$</code></th>
          <th><code>$k = water$</code></th>
          <th><code>$k = fashion$</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>$P \left(k \vert ice\right)$</code></td>
          <td><code>$1.9 \times 10^{-4}$</code></td>
          <td><code>$6.6 \times 10^{-5}$</code></td>
          <td><code>$3.0 \times 10^{-3}$</code></td>
          <td><code>$1.7 \times 10^{-5}$</code></td>
      </tr>
      <tr>
          <td><code>$P \left(k \vert steam\right)$</code></td>
          <td><code>$2.2 \times 10^{-5}$</code></td>
          <td><code>$7.8 \times 10^{-4}$</code></td>
          <td><code>$2.2 \times 10^{-3}$</code></td>
          <td><code>$1.8 \times 10^{-5}$</code></td>
      </tr>
      <tr>
          <td><code>$P \left(k \vert ice\right) / P \left(k \vert steam\right)$</code></td>
          <td><code>$8.9$</code></td>
          <td><code>$8.5 \times 10^{-2}$</code></td>
          <td><code>$1.36$</code></td>
          <td><code>$0.96$</code></td>
      </tr>
  </tbody>
</table>
<p>根据如上的假设，我们可以得到一个最基础的模型：</p>
<p><code>$$ F \left(w_i, w_j, \tilde{w}_k\right) = \dfrac{P_{ik}}{P_{jk}} $$</code></p>
<p>其中 <code>$w \in \mathbb{R}^d$</code> 为词向量，<code>$\tilde{w}_k \in \mathbb{R}^d$</code> 为单独的上下文词的词向量。假设向量空间是一个线性结构，因此 <code>$F$</code> 仅依赖于两个向量之间的差异，则模型可以改写为：</p>
<p><code>$$ F \left(w_i - w_j, \tilde{w}_k\right) = \dfrac{P_{ik}}{P_{jk}} $$</code></p>
<p>上式中右面是一个标量，如果左面的参数利用一个复杂的模型进行计算，例如神经网络，则会破坏我们希望保留的线性结构。因此，我们对参数采用点积运算，即：</p>
<p><code>$$ F \left(\left(w_i - w_j\right)^{\top} \tilde{w}_k\right) = \dfrac{P_{ik}}{P_{jk}} $$</code></p>
<p>在词之间的共现矩阵中，一个词和其上下文中的一个词之间应该是可以互换角色的。首先我们要保证 <code>$F$</code> 在 <code>$\left(\mathbb{R}, +\right)$</code> 和 <code>$\left(\mathbb{R}_{&gt;0}, \times\right)$</code> 上是同态的 (homomorphism)，例如：</p>
<p><code>$$ F \left(\left(w_i - w_j\right)^{\top} \tilde{w}_k\right) = \dfrac{F \left(w_i^{\top} \tilde{w}_k\right)}{F \left(w_j^{\top} \tilde{w}_k\right)} $$</code></p>
<p>其中 <code>$F \left(w_i^{\top} \tilde{w}_k\right) = P_{ik} = \dfrac{X_{ik}}{X_i}$</code>，则上式的一个解为 <code>$F = \exp$</code>，或：</p>
<p><code>$$ w_i^{\top} \tilde{w}_k = \log \left(P_{ik}\right) = \log \left(X_{ik}\right) - \log \left(X_i\right) $$</code></p>
<p>其中 <code>$\log \left(X_i\right)$</code> 与 <code>$k$</code> 无关记为 <code>$b_i$</code>，同时为了对称性添加 <code>$\tilde{b}_k$</code>，则上式改写为：</p>
<p><code>$$ w_i^{\top} \tilde{w}_k + b_i + \tilde{b}_k = \log \left(X_{ik}\right) $$</code></p>
<p>上式中，左侧为词向量的相关运算，右侧为共现矩阵的常量信息，则给出模型的损失函数如下：</p>
<p><code>$$ J = \sum_{i,j=1}^{V}{f \left(X_{ij}\right) \left(w_i^{\top} \tilde{w}_k + b_i + \tilde{b}_k - \log X_{ij}\right)^2} $$</code></p>
<p>其中，<code>$V$</code> 为词典中词的个数，<code>$f$</code> 为一个权重函数，其应具有如下特点：</p>
<ol>
<li><code>$f \left(0\right) = 0$</code>。如果 <code>$f$</code> 为一个连续函数，则当 <code>$x \to 0$</code> 时 <code>$\lim_{x \to 0}{f \left(x\right) \log^2 x}$</code> 应足够快地趋近于无穷。</li>
<li><code>$f \left(x\right)$</code> 应为非减函数，以确保稀少的共现不会权重过大。</li>
<li><code>$f \left(x\right)$</code> 对于较大的 <code>$x$</code> 应该相对较小，以确保过大的共现不会权重过大。</li>
</ol>
<p>文中给出了一个符合要求的函数如下：</p>
<p><code>$$ f \left(x\right) =  \begin{cases} \left(x / x_{\max}\right)^{\alpha} &amp; \text{if} \  x &lt; x_{\max} \\ 1 &amp; \text{otherwise} \end{cases} $$</code></p>
<p>其中两个超参数的值建议为 <code>$x_{\max} = 100, \alpha = 0.75$</code>。</p>
<h3 id="fasttext">fastText</h3>
<p>fastText 是由 Bojanowski 和 Grave 等人 <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> 提出的一种词向量表示方法。原始的 Skip-gram 模型忽略了词语内部的结构信息，fastText 利用 N-gram 方法将其考虑在内。</p>
<p>对于一个词 <code>$w$</code>，利用一系列的 N-gram 进行表示，同时在词的前后添加 <code>&lt;</code> 和 <code>&gt;</code> 边界符号以同其他文本序列进行区分。同时还将词语本身也包含在这个 N-gram 集合中，从而学习到词语的向量表示。例如，对于词 <code>$where$</code> 和 <code>$n = 3$</code>，则 N-gram 集合为：<code>&lt;wh, whe, her, ere, re&gt;</code>，同时包含词本身 <code>&lt;where&gt;</code>。需要注意的是，序列 <code>&lt;her&gt;</code> 与词 <code>$where$</code> 中的 tri-gram <code>her</code> 是两个不同的概念。模型提取所有 <code>$3 \leq n \leq 6$</code> 的 N-gram 序列。</p>
<p>假设 N-gram 词典的大小为 <code>$G$</code>，对于一个词 <code>$w$</code>，<code>$\mathcal{G}_w \subset \left\{1, ..., G\right\}$</code> 表示词中出现的 N-gram 的集合。针对任意一个 N-gram <code>$g$</code>，用向量 <code>$\mathbf{z}_g$</code> 表示，则我们利用一个词的所有 N-gram 的向量的加和表示该词。可以得到该模型的评分函数为：</p>
<p><code>$$ s \left(w, c\right) = \sum_{g \in \mathcal{G}_w}{\mathbf{z}_g^{\top} \mathbf{v}_c} $$</code></p>
<p>模型在学习不同词向量时可以共享权重 (不同词的可能包含相同的 N-gram)，使得在学习低频词时也可得到可靠的向量表示。</p>
<h3 id="wordrank">WordRank</h3>
<p>WordRank 是由 Ji 等人 <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 提出的一种词向量表示方法，其将词向量学习问题转换成一个排序问题。</p>
<p>我们令 <code>$\mathbf{u}_w$</code> 表示当前词 <code>$w$</code> 的 <code>$k$</code> 维词向量，<code>$\mathbf{v}_c$</code> 表示当前词上下文 <code>$c$</code> 的词向量。通过两者的内积 <code>$\langle \mathbf{u}_w, \mathbf{v}_c \rangle$</code> 来捕获词 <code>$w$</code> 和上下文 <code>$c$</code> 之间的关系，两者越相关则该内积越大。对于一个给定的词 <code>$w$</code>，利用上下文集合 <code>$\mathcal{C}$</code> 同词的内积分数进行排序，对于一个给定的上下文 <code>$c$</code>，排序为：</p>
<p><code>$$ \begin{equation} \begin{split} \text{rank} \left(w, c\right) &amp;= \sum_{c' \in \mathcal{C} \setminus \left\{c\right\}}{I \left(\langle \mathbf{u}_w, \mathbf{v}_c \rangle - \langle \mathbf{u}_w, \mathbf{v}_{c'} \rangle \leq 0\right)} \\ &amp;= \sum_{c' \in \mathcal{C} \setminus \left\{c\right\}}{I \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'}  \rangle \leq 0\right)} \end{split} \end{equation} $$</code></p>
<p>其中，<code>$I \left(x \leq 0\right)$</code> 为一个 0-1 损失函数，当 <code>$x \leq 0$</code> 时为 1 其他情况为 0。由于 <code>$I \left(x \leq 0\right)$</code> 为一个非连续函数，因此我们可以将其替换为一个凸上限函数 <code>$\ell \left(\cdot\right)$</code>，其可以为任意的二分类损失函数，构建排序的凸上限如下：</p>
<p><code>$$ \text{rank} \left(w, c\right) \leq \overline{\text{rank}} \left(w, c\right) = \sum_{c' \in \mathcal{C} \setminus \left\{c\right\}}{\ell \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right)} $$</code></p>
<p>我们期望排序模型将更相关的上下文排在列表的顶部，基于此构建损失函数如下：</p>
<p><code>$$ J \left(\mathbf{U}, \mathbf{V}\right) := \sum_{w \in \mathcal{W}}{\sum_{c \in \Omega_w}{r_{w, c} \cdot \rho \left(\dfrac{\overline{\text{rank}} \left(w, c\right) + \beta}{\alpha}\right)}} $$</code></p>
<p>其中，<code>$\mathcal{W}$</code> 表示词典，<code>$\mathbf{U} := \left\{\mathbf{u}_w\right\}_{w \in \mathcal{W}}$</code> 和 <code>$\mathbf{V} := \left\{\mathbf{c}_w\right\}_{c \in \mathcal{C}}$</code> 分别表示词及其上下文词向量的参数，<code>$\Omega_w$</code> 表示与词 <code>$w$</code> 共现的上下文的集合，<code>$r_{w, c}$</code> 为衡量 <code>$w$</code> 和 <code>$c$</code> 之间关系的权重，<code>$\rho \left(\cdot\right)$</code> 为用于衡量排序好坏的单调递增的损失函数，<code>$\alpha \geq 0, \beta \geq 0$</code> 为超参数。可选的有：</p>
<p><code>$$ r_{w, c} = \begin{cases} \left(X_{w, c} / x_{\max}\right)^{\epsilon} &amp; \text{if} \ X_{w, c} &lt; x_{\max} \\ 1 &amp; \text{otherwise} \end{cases} $$</code></p>
<p>其中 <code>$x_{\max} = 100, \epsilon = 0.75$</code>。根据 <code>$\rho \left(\cdot\right)$</code> 的要求，损失函数在排序的顶部 (rank 值小) 的地方更加敏感，同时对于 rank 值较大的地方不敏感。这可以使得模型变得更加稳健 (避免语法错误和语言的非常规使用造成干扰)，因此可选的有：</p>
<p><code>$$ \begin{equation} \begin{split} \rho \left(x\right) &amp;:= \log_2 \left(1 + x\right) \\ \rho \left(x\right) &amp;:= 1 - \dfrac{1}{\log_2 \left(2 + x\right)} \\ \rho \left(x\right) &amp;:= \dfrac{x^{1 - t} - 1}{1 - t}, t \neq 1 \end{split} \end{equation} $$</code></p>
<p>损失函数可以等价的定义为：</p>
<p><code>$$ J \left(\mathbf{U}, \mathbf{V}\right) := \sum_{\left(w, c\right) \in \Omega}{r_{w, c} \cdot \rho \left(\dfrac{\overline{\text{rank}} \left(w, c\right) + \beta}{\alpha}\right)} $$</code></p>
<p>在训练过程中，外层的求和符号容易利用 SDG 算法解决，但对于内层的求和符号除非 <code>$\rho \left(\cdot\right)$</code> 是一个线性函数，否则难以求解。然而，<code>$\rho \left(\cdot\right)$</code> 函数的性质要求其不能是一个线性函数，但我们可以利用其凹函数的特性对其进行一阶泰勒分解，有：</p>
<p><code>$$ \rho \left(x\right) \leq \rho \left(\xi^{-1}\right) + \rho' \left(\xi^{-1}\right) \cdot \left(x - \xi^{-1}\right) $$</code></p>
<p>对于任意 <code>$x$</code> 和 <code>$\xi \neq 0$</code> 均成立，同时当且仅当 <code>$\xi = x^{-1}$</code> 时等号成立。因此，令 <code>$\Xi := \left\{\xi_{w, c}\right\}_{\left(w, c\right) \in \Sigma}$</code>，则可以得到 <code>$J \left(\mathbf{U}, \mathbf{V}\right)$</code> 的一个上界：</p>
<p><code>$$ \begin{equation} \begin{split} \overline{J} \left(\mathbf{U}, \mathbf{V}, \Xi\right) &amp;:= \sum_{\left(w, c\right)  \in \Omega}{r_{w, c} \cdot \left\{\rho \left(\xi_{wc}^{-1}\right) + \rho' \left(\xi_{wc}^{-1}\right) \cdot \left(\alpha^{-1} \beta + \alpha^{-1} \sum_{c' \in \mathcal{C} \setminus \left\{c\right\}}{\ell \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right) - \xi_{w, c}^{-1}}\right)\right\}} \\ &amp;= \sum_{\left(w, c, c'\right)}{r_{w, c} \cdot \left(\dfrac{\rho \left(\xi_{w, c}^{-1}\right) + \rho' \left(\xi_{w, c}^{-1}\right) \cdot \left(\alpha^{-1} \beta - \xi_{w, c}^{-1}\right)}{\lvert \mathcal{C} \rvert - 1} + \dfrac{1}{\alpha} \rho' \left(\xi_{w, c}^{-1}\right) \cdot \ell \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right)\right)} \end{split} \end{equation} $$</code></p>
<p>其中，<code>$\left(w, c, c'\right) \in \Omega \times \left(\mathcal{C} \setminus \left\{c\right\}\right)$</code>，至此我们可以通过均匀采样 <code>$\left(w, c\right) \in \Sigma$</code> 和 <code>$c' \in \mathcal{C} \setminus \left\{c\right\}$</code> 解决训练问题。</p>
<p>整个 WordRank 算法的伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{WordRank 算法}
\begin{algorithmic}
\STATE $\eta$ 为学习率
\WHILE{$\mathbf{U}$，$\mathbf{V}$ 和 $\Xi$ 未收敛}
    \STATE \COMMENT{阶段1：更新 $\mathbf{U}$ 和 $\mathbf{V}$}
    \WHILE{$\mathbf{U}$ 和 $\mathbf{V}$ 未收敛}
        \STATE 从 $\Omega$ 中均匀采样 $\left(w, c\right)$
        \STATE 从 $\mathcal{C} \setminus \left\{c\right\}$ 中均匀采样 $c'$
        \STATE \COMMENT{同时更新如下 3 个参数}
        \STATE $\mathbf{u}_w \gets \mathbf{u}_w - \eta \cdot r_{w, c} \cdot \rho' \left(\xi_{w, c}^{-1}\right) \cdot \ell' \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right) \cdot \left(\mathbf{v}_c - \mathbf{v}_{c'}\right)$
        \STATE $\mathbf{v}_c \gets \mathbf{v}_c - \eta \cdot r_{w, c} \cdot \rho' \left(\xi_{w, c}^{-1}\right) \cdot \ell' \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right) \cdot \mathbf{u}_w$
        \STATE $\mathbf{v}_{c'} \gets \mathbf{v}_{c'} - \eta \cdot r_{w, c} \cdot \rho' \left(\xi_{w, c}^{-1}\right) \cdot \ell' \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right) \cdot \mathbf{u}_w$
    \ENDWHILE
    \STATE \COMMENT{阶段2：更新 $\Xi$}
    \FOR{$w \in \mathcal{W}$}
        \FOR{$c \in \mathcal{C}$}
            \STATE $\xi_{w, c} = \alpha / \left(\sum_{c' \in \mathcal{C} \setminus \left\{c\right\}}{\ell \left(\langle \mathbf{u}_w, \mathbf{v}_c - \mathbf{v}_{c'} \rangle\right) + \beta}\right)$
        \ENDFOR
    \ENDFOR
\ENDWHILE
\end{algorithmic}
\end{algorithm}
</pre></div>

<h3 id="cw2vec">cw2vec</h3>
<p>cw2vec 是由 Cao 等人 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> 提出的一种基于汉字笔画 N-gram 的中文词向量表示方法。该方法根据汉字作为象形文字具有笔画信息的特点，提出了笔画 N-gram 的概念。针对一个词的笔画 N-gram，其生成过程如下图所示：</p>
<p><img src="/images/cn/2018-10-01-word-embeddings/cw2vec-stroke-n-gram-generation.png" alt="cw2vec-Stroke-N-gram-Generation"></p>
<p>共包含 4 个步骤：</p>
<ol>
<li>将一个词拆解成单个的汉字，例如：“大人” 拆解为 “大” 和 “人”。</li>
<li>将每个汉字拆解成笔画，例如：“大” 和 “人” 拆解为 “一，丿，乀，丿，乀”。</li>
<li>将每个笔画映射到对应的编码序列，例如： “一，丿，乀，丿，乀” 映射为 13434。</li>
<li>利用编码序列生成笔画 N-gram，例如：134，343，434；1343，3434；13434。</li>
</ol>
<p>模型中定义一个词 <code>$w$</code> 及其上下文 <code>$c$</code> 的相似度如下：</p>
<p><code>$$ sim \left(w, c\right) = \sum_{q \in S\left(w\right)}{\vec{q} \cdot \vec{c}} $$</code></p>
<p>其中，<code>$S$</code> 为由笔画 N-gram 构成的词典，<code>$S \left(w\right)$</code> 为词 <code>$w$</code> 对应的笔画 N-gram 集合，<code>$q$</code> 为该集合中的一个笔画 N-gram，<code>$\vec{q}$</code> 为 <code>$q$</code> 对应的向量。</p>
<p>该模型的损失函数为：</p>
<p><code>$$ \mathcal{L} = \sum_{w \in D}{\sum_{c \in T \left(w\right)}{\log \sigma \left(sim \left(w, c\right)\right) + \lambda \mathbb{E}_{c' \sim P} \left[\log \sigma \left(- sim \left(w, c'\right)\right)\right]}} $$</code></p>
<p>其中，<code>$D$</code> 为语料中的全部词语，<code>$T \left(w\right)$</code> 为给定的词 <code>$w$</code> 和窗口内的所有上次文词，<code>$\sigma \left(x\right) = \left(1 + \exp \left(-x\right)\right)^{-1}$</code>，<code>$\lambda$</code> 为负采样的个数，<code>$\mathbb{E}_{c' \sim P} \left[\cdot\right]$</code> 表示负样本 <code>$c'$</code> 按照 <code>$D$</code> 中词的分布 <code>$P$</code> 进行采样，该分布可以为词的一元模型的分布 <code>$U$</code>，同时为了避免数据的稀疏性问题，类似 Word2Vec 中的做法采用 <code>$U^{0.75}$</code>。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Hinton, G. E. (1986, August). Learning distributed representations of concepts. In <em>Proceedings of the eighth annual conference of the cognitive science society</em> (Vol. 1, p. 12).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Bengio, Y., Courville, A., &amp; Vincent, P. (2013). Representation learning: A review and new perspectives. <em>IEEE transactions on pattern analysis and machine intelligence</em>, 35(8), 1798-1828.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Bengio, Y., Ducharme, R., Vincent, P., &amp; Jauvin, C. (2003). A Neural Probabilistic Language Model. <em>Journal of Machine Learning Research</em>, 3(Feb), 1137–1155.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Mikolov, T., Chen, K., Corrado, G., &amp; Dean, J. (2013). Efficient Estimation of Word Representations in Vector Space. <em>arXiv preprint arXiv:1301.3781</em>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://zh.wikipedia.org/zh/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81">https://zh.wikipedia.org/zh/霍夫曼编码</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://code.google.com/archive/p/word2vec/">https://code.google.com/archive/p/word2vec/</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Pennington, J., Socher, R., &amp; Manning, C. (2014). Glove: Global Vectors for Word Representation. In <em>Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP)</em> (pp. 1532–1543).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Bojanowski, P., Grave, E., Joulin, A., &amp; Mikolov, T. (2017). Enriching Word Vectors with Subword Information. <em>Transactions of the Association for Computational Linguistics</em>, 5, 135–146.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Ji, S., Yun, H., Yanardag, P., Matsushima, S., &amp; Vishwanathan, S. V. N. (2016). WordRank: Learning Word Embeddings via Robust Ranking. In <em>Proceedings of the 2016 Conference on Empirical Methods in Natural Language Processing</em> (pp. 658–668).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Cao, S., Lu, W., Zhou, J., &amp; Li, X. (2018). cw2vec: Learning Chinese Word Embeddings with Stroke n-gram Information. In <em>Thirty-Second AAAI Conference on Artificial Intelligence</em>.&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>循环神经网络 (Recurrent Neural Network, RNN)</title><link>https://zeqiang.fun/cn/2018/09/rnn/</link><pubDate>Fri, 21 Sep 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/09/rnn/</guid><description><![CDATA[
        <blockquote>
<p>文章部分内容参考了 Christopher 的博客 <a href="http://colah.github.io/posts/2015-08-Understanding-LSTMs/">Understanding LSTM Networks</a>，内容翻译和图片重绘已得到原作者同意，重绘后的图片源文件请参见 <a href="https://github.com/leovan/cdn.leovan.me/tree/master/images/blog/cn/2018-09-21-rnn/">这里</a>。</p>
</blockquote>
<h2 id="发展史">发展史</h2>
<p>循环神经网络 (Recurrent Neural Network, RNN) 一般是指时间递归神经网络而非结构递归神经网络 (Recursive Neural Network)，其主要用于对序列数据进行建模。Salehinejad 等人 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 的一篇综述文章列举了 RNN 发展过程中的一些重大改进，如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>Year</th>
          <th>1st Author</th>
          <th>Contribution</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1990</td>
          <td>Elman</td>
          <td>Popularized simple RNNs (Elman network)</td>
      </tr>
      <tr>
          <td>1993</td>
          <td>Doya</td>
          <td>Teacher forcing for gradient descent (GD)</td>
      </tr>
      <tr>
          <td>1994</td>
          <td>Bengio</td>
          <td>Difficulty in learning long term dependencies with gradient descend</td>
      </tr>
      <tr>
          <td>1997</td>
          <td>Hochreiter</td>
          <td>LSTM: long-short term memory for vanishing gradients problem</td>
      </tr>
      <tr>
          <td>1997</td>
          <td>Schuster</td>
          <td>BRNN: Bidirectional recurrent neural networks</td>
      </tr>
      <tr>
          <td>1998</td>
          <td>LeCun</td>
          <td>Hessian matrix approach for vanishing gradients problem</td>
      </tr>
      <tr>
          <td>2000</td>
          <td>Gers</td>
          <td>Extended LSTM with forget gates</td>
      </tr>
      <tr>
          <td>2001</td>
          <td>Goodman</td>
          <td>Classes for fast Maximum entropy training</td>
      </tr>
      <tr>
          <td>2005</td>
          <td>Morin</td>
          <td>A hierarchical softmax function for language modeling using RNNs</td>
      </tr>
      <tr>
          <td>2005</td>
          <td>Graves</td>
          <td>BLSTM: Bidirectional LSTM</td>
      </tr>
      <tr>
          <td>2007</td>
          <td>Jaeger</td>
          <td>Leaky integration neurons</td>
      </tr>
      <tr>
          <td>2007</td>
          <td>Graves</td>
          <td>MDRNN: Multi-dimensional RNNs</td>
      </tr>
      <tr>
          <td>2009</td>
          <td>Graves</td>
          <td>LSTM for hand-writing recognition</td>
      </tr>
      <tr>
          <td>2010</td>
          <td>Mikolov</td>
          <td>RNN based language model</td>
      </tr>
      <tr>
          <td>2010</td>
          <td>Neir</td>
          <td>Rectified linear unit (ReLU) for vanishing gradient problem</td>
      </tr>
      <tr>
          <td>2011</td>
          <td>Martens</td>
          <td>Learning RNN with Hessian-free optimization</td>
      </tr>
      <tr>
          <td>2011</td>
          <td>Mikolov</td>
          <td>RNN by back-propagation through time (BPTT) for statistical language modeling</td>
      </tr>
      <tr>
          <td>2011</td>
          <td>Sutskever</td>
          <td>Hessian-free optimization with structural damping</td>
      </tr>
      <tr>
          <td>2011</td>
          <td>Duchi</td>
          <td>Adaptive learning rates for each weight</td>
      </tr>
      <tr>
          <td>2012</td>
          <td>Gutmann</td>
          <td>Noise-contrastive estimation (NCE)</td>
      </tr>
      <tr>
          <td>2012</td>
          <td>Mnih</td>
          <td>NCE for training neural probabilistic language models (NPLMs)</td>
      </tr>
      <tr>
          <td>2012</td>
          <td>Pascanu</td>
          <td>Avoiding exploding gradient problem by gradient clipping</td>
      </tr>
      <tr>
          <td>2013</td>
          <td>Mikolov</td>
          <td>Negative sampling instead of hierarchical softmax</td>
      </tr>
      <tr>
          <td>2013</td>
          <td>Sutskever</td>
          <td>Stochastic gradient descent (SGD) with momentum</td>
      </tr>
      <tr>
          <td>2013</td>
          <td>Graves</td>
          <td>Deep LSTM RNNs (Stacked LSTM)</td>
      </tr>
      <tr>
          <td>2014</td>
          <td>Cho</td>
          <td>Gated recurrent units</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Zaremba</td>
          <td>Dropout for reducing Overfitting</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Mikolov</td>
          <td>Structurally constrained recurrent network (SCRN) to enhance learning longer memory for vanishing gradient problem</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Visin</td>
          <td>ReNet: A RNN-based alternative to convolutional neural networks</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Gregor</td>
          <td>DRAW: Deep recurrent attentive writer</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Kalchbrenner</td>
          <td>Grid long-short term memory</td>
      </tr>
      <tr>
          <td>2015</td>
          <td>Srivastava</td>
          <td>Highway network</td>
      </tr>
      <tr>
          <td>2017</td>
          <td>Jing</td>
          <td>Gated orthogonal recurrent units</td>
      </tr>
  </tbody>
</table>
<h2 id="rnn">RNN</h2>
<h3 id="网络结构">网络结构</h3>
<p>不同于传统的前馈神经网络接受特定的输入得到输出，RNN 由人工神经元和一个或多个反馈循环构成，如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/rnn-loops.png" alt="RNN-Loops"></p>
<p>其中，<code>$\boldsymbol{x}_t$</code> 为输入层，<code>$\boldsymbol{h}_t$</code> 为带有循环的隐含层，<code>$\boldsymbol{y}_t$</code> 为输出层。其中隐含层包含一个循环，为了便于理解我们将循环进行展开，展开后的网络结构如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/rnn-loops-unrolled.png" alt="RNN-Loops-Unrolled"></p>
<p>对于展开后的网络结构，其输入为一个时间序列 <code>$\left\{\dotsc, \boldsymbol{x}_{t-1}, \boldsymbol{x}_t, \boldsymbol{x}_{t+1}, \dotsc\right\}$</code>，其中 <code>$\boldsymbol{x}_t \in \mathbb{R}^n$</code>，<code>$n$</code> 为输入层神经元个数。相应的隐含层为 <code>$\left\{\dotsc, \boldsymbol{h}_{t-1}, \boldsymbol{h}_t, \boldsymbol{h}_{t+1}, \dotsc\right\}$</code>，其中 <code>$\boldsymbol{h}_t \in \mathbb{R}^m$</code>，<code>$m$</code> 为隐含层神经元个数。隐含层节点使用较小的非零数据进行初始化可以提升整体的性能和网络的稳定性 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。隐含层定义了整个系统的状态空间 (state space)，或称之为 memory <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>：</p>
<p><code>$$ \boldsymbol{h}_t = f_H \left(\boldsymbol{o}_t\right) $$</code></p>
<p>其中</p>
<p><code>$$ \boldsymbol{o}_t = \boldsymbol{W}_{IH} \boldsymbol{x}_t + \boldsymbol{W}_{HH} \boldsymbol{h}_{t-1} + \boldsymbol{b}_h $$</code></p>
<p><code>$f_H \left(\cdot\right)$</code> 为隐含层的激活函数，<code>$\boldsymbol{b}_h$</code> 为隐含层的偏置向量。对应的输出层为 <code>$\left\{\dotsc, \boldsymbol{y}_{t-1}, \boldsymbol{y}_t, \boldsymbol{y}_{t+1}, \dotsc\right\}$</code>，其中 <code>$\boldsymbol{y}_t \in \mathbb{R}^p$</code>，<code>$p$</code> 为输出层神经元个数。则：</p>
<p><code>$$ \boldsymbol{y}_t = f_O \left(\boldsymbol{W}_{HO} \boldsymbol{h}_t + \boldsymbol{b}_o\right) $$</code></p>
<p>其中 <code>$f_O \left(\cdot\right)$</code> 为隐含层的激活函数，<code>$\boldsymbol{b}_o$</code> 为隐含层的偏置向量。</p>
<p>在 RNN 中常用的激活函数为双曲正切函数：</p>
<p><code>$$ \tanh \left(x\right) = \dfrac{e^{2x} - 1}{e^{2x} + 1} $$</code></p>
<p>Tanh 函数实际上是 Sigmoid 函数的缩放：</p>
<p><code>$$ \sigma \left(x\right) = \dfrac{1}{1 + e^{-x}} = \dfrac{\tanh \left(x / 2\right) + 1}{2} $$</code></p>
<h3 id="梯度弥散和梯度爆炸">梯度弥散和梯度爆炸</h3>
<p>原始 RNN 存在的严重的问题就是<strong>梯度弥散 (Vanishing Gradients)</strong> 和<strong>梯度爆炸 (Exploding Gradients)</strong>。我们以时间序列中的 3 个时间点 <code>$t = 1, 2, 3$</code> 为例进行说明，首先假设神经元在前向传导过程中没有激活函数，则有：</p>
<p><code>$$ \begin{equation} \begin{split} &amp;\boldsymbol{h}_1 = \boldsymbol{W}_{IH} \boldsymbol{x}_1 + \boldsymbol{W}_{HH} \boldsymbol{h}_0 + \boldsymbol{b}_h, &amp;\boldsymbol{y}_1 = \boldsymbol{W}_{HO} \boldsymbol{h}_1 + \boldsymbol{b}_o \\ &amp;\boldsymbol{h}_2 = \boldsymbol{W}_{IH} \boldsymbol{x}_2 + \boldsymbol{W}_{HH} \boldsymbol{h}_1 + \boldsymbol{b}_h, &amp;\boldsymbol{y}_2 = \boldsymbol{W}_{HO} \boldsymbol{h}_2 + \boldsymbol{b}_o \\ &amp;\boldsymbol{h}_3 = \boldsymbol{W}_{IH} \boldsymbol{x}_3 + \boldsymbol{W}_{HH} \boldsymbol{h}_2 + \boldsymbol{b}_h, &amp;\boldsymbol{y}_3 = \boldsymbol{W}_{HO} \boldsymbol{h}_3 + \boldsymbol{b}_o \end{split} \end{equation} $$</code></p>
<p>在对于一个序列训练的损失函数为：</p>
<p><code>$$ \mathcal{L} \left(\boldsymbol{y}, \boldsymbol{\hat{y}}\right) = \sum_{t=0}^{T}{\mathcal{L}_t \left(\boldsymbol{y_t}, \boldsymbol{\hat{y}_t}\right)} $$</code></p>
<p>其中 <code>$\mathcal{L}_t \left(\boldsymbol{y_t}, \boldsymbol{\hat{y}_t}\right)$</code> 为 <code>$t$</code> 时刻的损失。我们利用 <code>$t = 3$</code> 时刻的损失对 <code>$\boldsymbol{W}_{IH}, \boldsymbol{W}_{HH}, \boldsymbol{W}_{HO}$</code> 求偏导，有：</p>
<p><code>$$ \begin{equation} \begin{split} \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{W}_{HO}} &amp;= \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{W}_{HO}} \\ \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{W}_{IH}} &amp;= \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{W}_{IH}} + \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{h}_2} \dfrac{\partial \boldsymbol{h}_2}{\partial \boldsymbol{W}_{IH}} + \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{h}_2} \dfrac{\partial \boldsymbol{h}_2}{\partial \boldsymbol{h}_1} \dfrac{\partial \boldsymbol{h}_1}{\partial \boldsymbol{W}_{IH}} \\ \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{W}_{HH}} &amp;= \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{W}_{HH}} + \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{h}_2} \dfrac{\partial \boldsymbol{h}_2}{\partial \boldsymbol{W}_{HH}} + \dfrac{\partial \mathcal{L}_3}{\partial \boldsymbol{y}_3} \dfrac{\partial \boldsymbol{y}_3}{\partial \boldsymbol{h}_3} \dfrac{\partial \boldsymbol{h}_3}{\partial \boldsymbol{h}_2} \dfrac{\partial \boldsymbol{h}_2}{\partial \boldsymbol{h}_1} \dfrac{\partial \boldsymbol{h}_1}{\partial \boldsymbol{W}_{HH}} \end{split} \end{equation} $$</code></p>
<p>因此，不难得出对于任意时刻 <code>$t$</code>，<code>$\boldsymbol{W}_{IH}, \boldsymbol{W}_{HH}$</code> 的偏导为：</p>
<p><code>$$ \dfrac{\partial \mathcal{L}_t}{\partial \boldsymbol{W}_{IH}} = \sum_{k=0}^{t}{\dfrac{\partial \mathcal{L}_t}{\partial \boldsymbol{y}_t} \dfrac{\partial \boldsymbol{y}_t}{\partial \boldsymbol{h}_t} \left(\prod_{j=k+1}^{t}{\dfrac{\partial \boldsymbol{h}_j}{\partial \boldsymbol{h}_{j-1}}}\right) \dfrac{\partial \boldsymbol{h}_k}{\partial \boldsymbol{W}_{IH}}} $$</code></p>
<p><code>$\dfrac{\partial \mathcal{L}_t}{\partial \boldsymbol{W}_{HH}}$</code> 同理可得。对于 <code>$\dfrac{\partial \mathcal{L}_t}{\partial \boldsymbol{W}_{HH}}$</code>，在存在激活函数的情况下，有：</p>
<p><code>$$ \prod_{j=k+1}^{t}{\dfrac{\partial \boldsymbol{h}_j}{\partial \boldsymbol{h}_{j-1}}} = \prod_{j=k+1}^{t}{f'_H \left(h_{j-1}\right) \boldsymbol{W}_{HH}} $$</code></p>
<p>假设激活函数为 <code>$\tanh$</code>，下图刻画了 <code>$\tanh$</code> 函数及其导数的函数取值范围：</p>
<p><img src="/images/cn/2018-09-21-rnn/tanh-function.png" alt="Tanh-Function"></p>
<p>可得，<code>$0 \leq \tanh' \leq 1$</code>，同时当且仅当 <code>$x = 0$</code> 时，<code>$\tanh' \left(x\right) = 1$</code>。因此：</p>
<ol>
<li>当 <code>$t$</code> 较大时，<code>$\prod_{j=k+1}^{t}{f'_H \left(h_{j-1}\right) \boldsymbol{W}_{HH}}$</code> 趋近于 0，则会产生<strong>梯度弥散</strong>问题。</li>
<li>当 <code>$\boldsymbol{W}_{HH}$</code> 较大时，<code>$\prod_{j=k+1}^{t}{f'_H \left(h_{j-1}\right) \boldsymbol{W}_{HH}}$</code> 趋近于无穷，则会产生<strong>梯度爆炸</strong>问题。</li>
</ol>
<h3 id="长期依赖问题">长期依赖问题</h3>
<p>RNN 隐藏节点以循环结构形成记忆，每一时刻的隐藏层的状态取决于它的过去状态，这种结构使得 RNN 可以保存、记住和处理长时期的过去复杂信号。但有的时候，我们仅需利用最近的信息来处理当前的任务。例如：考虑一个用于利用之前的文字预测后续文字的语言模型，如果我们想预测 “the clouds are in the <strong>sky</strong>” 中的最后一个词，我们不需要太远的上下信息，很显然这个词就应该是 <strong>sky</strong>。在这个情况下，待预测位置与相关的信息之间的间隔较小，RNN 可以有效的利用过去的信息。</p>
<p><img src="/images/cn/2018-09-21-rnn/rnn-long-term-dependencies-short.png" alt="RNN-Long-Term-Dependencies-Short"></p>
<p>但也有很多的情况需要更多的上下文信息，考虑需要预测的文本为 “I grew up in France &hellip; I speak fluent <strong>French</strong>”。较近的信息表明待预测的位置应该是一种语言，但想确定具体是哪种语言需要更远位置的“在法国长大”的背景信息。理论上 RNN 有能力处理这种<strong>长期依赖</strong>，但在实践中 RNN 却很难解决这个问题 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>。</p>
<p><img src="/images/cn/2018-09-21-rnn/rnn-long-term-dependencies-long.png" alt="RNN-Long-Term-Dependencies-Long"></p>
<h2 id="lstm">LSTM</h2>
<h3 id="lstm-网络结构">LSTM 网络结构</h3>
<p>长短时记忆网络 (Long Short Term Memroy, LSTM) 是由 Hochreiter 和 Schmidhuber <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> 提出一种特殊的 RNN。LSTM 的目的就是为了解决长期依赖问题，记住长时间的信息是 LSTM 的基本功能。</p>
<p>所有的循环神经网络都是由重复的模块构成的一个链条。在标准的 RNN 中，这个重复的模块的结构比较简单，仅包含一个激活函数为 <code>$\tanh$</code> 的隐含层，如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/rnn.png" alt="RNN"></p>
<p>LSTM 也是类似的链条状结构，但其重复的模块的内部结构不同。模块内部并不是一个隐含层，而是四个，并且以一种特殊的方式进行交互，如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm.png" alt="LSTM"></p>
<p>下面我们将一步一步的介绍 LSTM 单元 (cell) 的具体工作原理，在之前我们先对使用到的符号进行简单的说明，如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-operations-symbols.png" alt="LSTM-Operations-Symbols"></p>
<p>其中，每条线都包含一个从输出节点到其他节点的整个向量，粉红色的圆圈表示逐元素的操作，黄色的矩形为学习到的神经网络层，线条的合并表示连接，线条的分叉表示内容的复制并转移到不同位置。</p>
<h3 id="lstm-单元状态和门控机制">LSTM 单元状态和门控机制</h3>
<p>LSTM 的关键为单元的状态 (cell state)，即下图中顶部水平穿过单元的直线。单元的状态像是一条传送带，其直接运行在整个链条上，同时仅包含少量的线性操作。因此，信息可以很容易得传递下去并保持不变。</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-cell-state.png" alt="LSTM-Cell-State"></p>
<p>LSTM 具有向单元状态添加或删除信息的能力，这种能力被由一种称之为“门” (gates) 的结构所控制。门是一种可选择性的让信息通过的组件，其由一层以 Sigmoid 为激活函数的网络层和一个逐元素相乘操作构成的，如下图所示：</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-pointwise-operation.png" alt="LSTM-Pointwise-Operation"></p>
<p>Sigmoid 层的输出值介于 0 和 1 之间，代表了所允许通过的数据量。0 表示不允许任何数据通过，1 表示允许所有数据通过。一个 LSTM 单元包含 3 个门用于控制单元的状态。</p>
<h3 id="lstm-工作步骤">LSTM 工作步骤</h3>
<p>LSTM 的第一步是要决定从单元状态中所<strong>忘记</strong>的信息，这一步是通过一个称之为“<strong>遗忘门 (forget gate)</strong>”的 Sigmoid 网络层控制。该层以上一时刻隐含层的输出 <code>$h_{t-1}$</code> 和当前这个时刻的输入 <code>$x_t$</code> 作为输入，输出为一个介于 0 和 1 之间的值，1 代表全部保留，0 代表全部丢弃。回到之前的语言模型，单元状态需要包含主语的性别信息以便选择正确的代词。但当遇见一个新的主语后，则需要忘记之前主语的性别信息。</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-cell-forget-gate.png" alt="LSTM-Cell-Forget-Gate"></p>
<p><code>$$ f_t = \sigma \left(W_f \cdot \left[h_{t-1}, x_t\right] + b_f\right) $$</code></p>
<p>第二步我们需要决定要在单元状态中存储什么样的新信息，这包含两个部分。第一部分为一个称之为“<strong>输入门 (input gate)</strong>” 的 Sigmoid 网络层，其决定更新那些数据。第二部分为一个 Tanh 网络层，其将产生一个新的候选值向量 <code>$\tilde{C}_t$</code> 并用于添加到单元状态中。之后会将两者进行整合，并对单元状态进行更新。在我们的语言模型中，我们希望将新主语的性别信息添加到单元状态中并替代需要忘记的旧主语的性别信息。</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-cell-input-gate.png" alt="LSTM-Cell-Input-Gate"></p>
<p><code>$$ \begin{equation} \begin{split} i_t &amp;= \sigma \left(W_i \cdot \left[h_{t-1}, x_t\right] + b_i\right) \\ \tilde{C}_t &amp;= \tanh \left(W_C \cdot \left[h_{t-1}, x_t\right] + b_C\right) \end{split} \end{equation} $$</code></p>
<p>接下来需要将旧的单元状态 <code>$C_{t-1}$</code> 更新为 <code>$C_t$</code>。我们将旧的单元状态乘以 <code>$f_t$</code> 以控制需要忘记多少之前旧的信息，再加上 <code>$i_t \odot \tilde{C}_t$</code> 用于控制单元状态的更新。在我们的语言模型中，该操作真正实现了我们对与之前主语性别信息的遗忘和对新信息的增加。</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-cell-state-update.png" alt="LSTM-Cell-State-Update"></p>
<p><code>$$ C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t $$</code></p>
<p>最后我们需要确定单元的输出，该输出将基于单元的状态，但为一个过滤版本。首先我们利用一个 Sigmoid 网络层来确定单元状态的输出，其次我们对单元状态进行 <code>$\tanh$</code> 操作 (将其值缩放到 -1 和 1 之间) 并与之前 Sigmoid 层的输出相乘，最终得到需要输出的信息。</p>
<p><img src="/images/cn/2018-09-21-rnn/lstm-cell-output-gate.png" alt="LSTM-Cell-Output-Gate"></p>
<p><code>$$ \begin{equation} \begin{split} o_t &amp;= \sigma \left(W_o \cdot \left[h_{t-1}, x_t\right] + b_o\right) \\ h_t &amp;= o_t \odot \tanh \left(C_t\right) \end{split} \end{equation} $$</code></p>
<h3 id="lstm-变种">LSTM 变种</h3>
<p>上文中介绍的基础的 LSTM 模型，事实上不同学者对 LSTM 的结构进行了或多或少的改变，其中一个比较有名的变种是由 Gers 和 Schmidhuber 提出的 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>。其添加了一种“窥视孔连接 (peephole connections)”，这使得每一个门结构都能够窥视到单元的状态。</p>
<p><img src="/images/cn/2018-09-21-rnn/peephole-cell.png" alt="Peephole-Cell"></p>
<p><code>$$ \begin{equation} \begin{split} f_t &amp;= \sigma \left(W_f \cdot \left[\boldsymbol{C_{t-1}}, h_{t-1}, x_t\right] + b_f\right) \\ i_t &amp;= \sigma \left(W_i \cdot \left[\boldsymbol{C_{t-1}}, h_{t-1}, x_t\right] + b_i\right) \\ o_t &amp;= \sigma \left(W_o \cdot \left[\boldsymbol{C_t}, h_{t-1}, x_t\right] + b_o\right) \end{split} \end{equation} $$</code></p>
<p>另一个变种是使用了成对的遗忘门和输入门。不同于一般的 LSTM 中分别确定需要遗忘和新添加的信息，成对的遗忘门和输入门仅在需要添加新输入是才会忘记部分信息，同理仅在需要忘记信息时才会添加新的输入。</p>
<p><img src="/images/cn/2018-09-21-rnn/cfig-cell.png" alt="CFIG-Cell"></p>
<p><code>$$ C_t = f_t \odot C_{t-1} + \boldsymbol{\left(1 - f_t\right)} \odot \tilde{C}_t $$</code></p>
<p>另外一个比较有名的变种为 Cho 等人提出的 Gated Recurrent Unit (GRU) <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>，单元结构如下：</p>
<p><img src="/images/cn/2018-09-21-rnn/gru-cell.png" alt="GRU-Cell"></p>
<p>GRU 将遗忘门和输入门整个成一层，称之为“<strong>更新门 (update gate)</strong>”，同时配以一个“<strong>重置门 (reset gate)</strong>”。具体的计算过程如下：</p>
<p>首先计算更新门 <code>$z_t$</code> 和重置门 <code>$r_t$</code>：</p>
<p><code>$$ \begin{equation} \begin{split} z_t &amp;= \sigma \left(W_z \cdot \left[h_{t-1}, x_t\right]\right) \\ r_t &amp;= \sigma \left(W_r \cdot \left[h_{t-1}, x_t\right]\right) \end{split} \end{equation} $$</code></p>
<p>其次计算候选隐含层 (candidate hidden layer) <code>$\tilde{h}_t$</code>，与 LSTM 中计算 <code>$\tilde{C}_t$</code> 类似，其中 <code>$r_t$</code> 用于控制保留多少之前的信息：</p>
<p><code>$$ \tilde{h}_t = \tanh \left(W \cdot \left[r_t \odot h_{t-1}, x_t\right]\right) $$</code></p>
<p>最后计算需要从之前的隐含层 <code>$h_{t-1}$</code> 遗忘多少信息，同时加入多少新的信息 <code>$\tilde{h}_t$</code>，<code>$z_t$</code> 用于控制这个比例：</p>
<p><code>$$ h_t = \left(1 - z_t\right) \odot h_{t-1} + z_t \odot \tilde{h}_t $$</code></p>
<p>因此，对于短距离依赖的单元重置门的值较大，对于长距离依赖的单元更新门的值较大。如果 <code>$r_t = 1$</code> 并且 <code>$z_t = 0$</code>，则 GRU 退化为一个标准的 RNN。</p>
<p>除此之外还有大量的 LSTM 变种，Greff 等人 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 对一些常见的变种进行了比较，Jozefowicz 等人 <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> 测试了大量的 RNN 结构在不同任务上的表现。</p>
<h2 id="扩展与应用">扩展与应用</h2>
<p>循环神经网络在序列建模上有着天然的优势，其在自然语言处理，包括：语言建模，语音识别，机器翻译，对话与QA，文本生成等；计算视觉，包括：目标识别，视觉追踪，图像生成等；以及一些综合场景，包括：图像标题生成，视频字幕生成等，多个领域均有不错的表现，有代表性的论文请参见 <a href="https://github.com/kjw0612/awesome-rnn">awesome-rnn</a>。</p>
<p>Google 的 <a href="https://magenta.tensorflow.org/">Magenta</a> 是一项利用机器学习创作艺术和音乐的研究，其中也包含了大量利用 RNN 相关模型构建的有趣项目。<a href="https://magenta.tensorflow.org/sketch-rnn-demo">SketchRNN</a> 是由 Ha 等人 <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 提出了一种能够根据用户描绘的一些简单图形自动完成后续绘画的 RNN 网络。</p>
<p><img src="/images/cn/2018-09-21-rnn/sketch-rnn-demo.gif" alt="SketchRNN-Demo"></p>
<p><a href="https://magenta.tensorflow.org/performance-rnn-browser">Performance RNN</a> 是由 Ian
等人 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> 提出了一种基于时间和动态因素生成复合音乐的 LSTM 网络。</p>
<p><img src="/images/cn/2018-09-21-rnn/performance-rnn-demo.gif" alt="Performance-RNN-Demo"></p>
<p>更多有趣的作品请参见 Megenta 的 <a href="https://magenta.tensorflow.org/demos">Demos</a> 页面。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Salehinejad, H., Sankar, S., Barfett, J., Colak, E., &amp; Valaee, S. (2017). Recent Advances in Recurrent Neural Networks. <em>arXiv preprint arXiv:1801.01078.</em>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Sutskever, I., Martens, J., Dahl, G., &amp; Hinton, G. (2013). On the importance of initialization and momentum in deep learning. In <em>International Conference on Machine Learning</em> (pp. 1139–1147).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Bengio, Y., Simard, P., &amp; Frasconi, P. (1994). Learning long-term dependencies with gradient descent is difficult. <em>IEEE Transactions on Neural Networks, 5(2)</em>, 157–166.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Hochreiter, S., &amp; Schmidhuber, J. (1997). Long short-term memory. Neural Computation, 9(8), 1735–1780.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Gers, F. A., &amp; Schmidhuber, J. (2000). Recurrent nets that time and count. In <em>Proceedings of the IEEE-INNS-ENNS International Joint Conference on Neural Networks. IJCNN 2000. Neural Computing: New Challenges and Perspectives for the New Millennium</em> (Vol. 3, pp. 189–194 vol.3).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Cho, K., van Merrienboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., &amp; Bengio, Y. (2014). Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation. In <em>Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP)</em> (pp. 1724–1734).&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Greff, K., Srivastava, R. K., Koutník, J., Steunebrink, B. R., &amp; Schmidhuber, J. (2017). LSTM: A Search Space Odyssey. <em>IEEE Transactions on Neural Networks and Learning Systems, 28(10)</em>, 2222–2232.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Jozefowicz, R., Zaremba, W., &amp; Sutskever, I. (2015). An Empirical Exploration of Recurrent Network Architectures. In <em>Proceedings of the 32Nd International Conference on International Conference on Machine Learning</em> - Volume 37 (pp. 2342–2350).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Ha, D., &amp; Eck, D. (2017). A Neural Representation of Sketch Drawings. <em>arXiv preprint arXiv:1704.03477</em>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Ian S., &amp; Sageev O. Performance RNN: Generating Music with Expressive Timing and Dynamics. Magenta Blog, 2017. <a href="https://magenta.tensorflow.org/performance-rnn">https://magenta.tensorflow.org/performance-rnn</a>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>卷积神经网络 (Convolutional Neural Network, CNN)</title><link>https://zeqiang.fun/cn/2018/08/cnn/</link><pubDate>Sat, 25 Aug 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/08/cnn/</guid><description><![CDATA[
        <h2 id="发展史">发展史</h2>
<p>卷积神经网络 (Convolutional Neural Network, CNN) 是一种目前广泛用于图像，自然语言处理等领域的深度神经网络模型。1998 年，Lecun 等人 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 提出了一种基于梯度的反向传播算法用于文档的识别。在这个神经网络中，卷积层 (Convolutional Layer) 扮演着至关重要的角色。</p>
<p>随着运算能力的不断增强，一些大型的 CNN 网络开始在图像领域中展现出巨大的优势，2012 年，Krizhevsky 等人 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> 提出了 AlexNet 网络结构，并在 ImageNet 图像分类竞赛 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 中以超过之前 11% 的优势取得了冠军。随后不同的学者提出了一系列的网络结构并不断刷新 ImageNet 的成绩，其中比较经典的网络包括：VGG (Visual Geometry  Group) <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>，GoogLeNet <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> 和 ResNet <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>。</p>
<p>CNN 在图像分类问题上取得了不凡的成绩，同时一些学者也尝试将其应用在图像的其他领域，例如：物体检测 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup><sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>，语义分割 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>，图像摘要 <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>，行为识别 <sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> 等。除此之外，在非图像领域 CNN 也取得了一定的成绩 <sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup>。</p>
<h2 id="模型原理">模型原理</h2>
<p>下图为 Lecun 等人提出的 LeNet-5 的网络架构：</p>
<p><img src="/images/cn/2018-08-25-cnn/lenet-5.png" alt="LeNet-5"></p>
<p>下面我们针对 CNN 网络中的不同类型的网络层逐一进行介绍。</p>
<h3 id="输入层">输入层</h3>
<p>LeNet-5 解决的手写数字分类问题的输入为一张 32x32 像素的灰度图像 (Gray Scale)。日常生活中计算机常用的图像的表示方式为 RGB，即将一张图片分为红色通道 (Red Channel)，绿色通道 (Green Channel) 和蓝色通道 (Blue Channel)，其中每个通道的每个像素点的数值范围为 <code>$\left[0, 255\right]$</code>。灰度图像表示该图片仅包含一个通道，也就是不具备彩色信息，每个像素点的数值范围同 RGB 图像的取值范围相同。</p>
<p>因此，一张图片在计算机的眼里就是一个如下图所示的数字矩阵 (示例图片来自于 MNIST 数据集 <sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup>)：</p>
<p><img src="/images/cn/2018-08-25-cnn/digit-pixels.png" alt="Digit-Pixels"></p>
<p>在将图像输入到 CNN 网络之前，通常我们会对其进行预处理，因为每个像素点的最大取值为 <code>$255$</code>，因此将每个像素点的值除以 <code>$255$</code> 则可以将其归一化到 <code>$\left[0, 1\right]$</code> 的范围。</p>
<h3 id="卷积层">卷积层</h3>
<p>在了解卷积层之前，让我们先来了解一下什么是卷积？设 <code>$f\left(x\right), g\left(x\right)$</code> 是 <code>$\mathbb{R}$</code> 上的两个可积函数，则卷积定义为：</p>
<p><code>$$ \left(f * g\right) \left(x\right) = \int_{- \infty}^{\infty}{f \left(\tau\right) g \left(x - \tau\right) d \tau} $$</code></p>
<p>离散形式定义为：</p>
<p><code>$$ \left(f * g\right) \left(x\right) = \sum_{\tau = - \infty}^{\infty}{f \left(\tau\right) g \left(x - \tau\right)} $$</code></p>
<p>我们用一个示例来形象的理解一下卷积的含义，以离散的形式为例，假设我们有两个骰子，<code>$f\left(x\right), g\left(x\right)$</code> 分别表示投两个骰子，<code>$x$</code> 面朝上的概率。</p>
<p><code>$$ f \left(x\right) = g \left(x\right) = \begin{cases} 1/6 &amp; x = 1, 2, 3, 4, 5, 6 \\ 0 &amp; \text{otherwise} \end{cases} $$</code></p>
<p>卷积 <code>$\left(f * g\right) \left(x\right)$</code> 表示投两个骰子，朝上数字之和为 <code>$x$</code> 的概率。则和为 <code>$4$</code> 的概率为：</p>
<p><code>$$ \begin{equation} \begin{split} \left(f * g\right) \left(4\right) &amp;= \sum_{\tau = 1}^{6}{f \left(\tau\right) g \left(4 - \tau\right)} \\ &amp;= f \left(1\right) g \left(4 - 1\right) + f \left(2\right) g \left(4 - 2\right) + f \left(3\right) g \left(4 - 3\right) \\ &amp;= 1/6 \times 1/6 + 1/6 \times 1/6 + 1/6 \times 1/6 \\ &amp;= 1/12 \end{split} \end{equation} $$</code></p>
<p>这是一维的情况，我们处理的图像为一个二维的矩阵，因此类似的有：</p>
<p><code>$$ \left(f * g\right) \left(x, y\right) = \sum_{v = - \infty}^{\infty}{\sum_{h = - \infty}^{\infty}{f \left(h, v\right) g \left(x - h, y - v\right)}} $$</code></p>
<p>这次我们用一个抽象的例子解释二维情况下卷积的计算，设 <code>$f, g$</code> 对应的概率矩阵如下：</p>
<p><code>$$ f = \left[ \begin{array}{ccc} \color{red}{a_{0, 0}} &amp; \color{orange}{a_{0, 1}} &amp; \color{yellow}{a_{0, 2}} \\ \color{green}{a_{1, 0}} &amp; \color{cyan}{a_{1, 1}} &amp; \color{blue}{a_{1, 2}} \\ \color{purple}{a_{2, 0}} &amp; \color{black}{a_{2, 1}} &amp; \color{gray}{a_{2, 2}} \end{array} \right] , g = \left[ \begin{array}{ccc} \color{gray}{b_{-1, -1}} &amp; \color{black}{b_{-1, 0}} &amp; \color{purple}{b_{-1, 1}} \\ \color{blue}{b_{0, -1}} &amp; \color{cyan}{b_{0, 0}} &amp; \color{green}{b_{0, 1}} \\ \color{yellow}{b_{1, -1}} &amp; \color{orange}{b_{1, 0}} &amp; \color{red}{b_{1, 1}} \end{array} \right] $$</code></p>
<p>则 <code>$\left(f * g\right) \left(1, 1\right)$</code> 计算方式如下：</p>
<p><code>$$ \left(f * g\right) \left(1, 1\right) = \sum_{v = 0}^{2}{\sum_{h = 0}^{2}{f \left(h, v\right) g \left(1 - h, 1 - v\right)}} $$</code></p>
<p>从这个计算公式中我们就不难看出为什么上面的 <code>$f, g$</code> 两个概率矩阵的角标会写成上述形式，即两个矩阵相同位置的角标之和均为 <code>$1$</code>。<code>$\left(f * g\right) \left(1, 1\right)$</code> 即为 <code>$f, g$</code> 两个矩阵中对应颜色的元素乘积之和。</p>
<p>在上例中，<code>$f, g$</code> 两个概率矩阵的大小相同，而在 CNN 中，<code>$f$</code> 为输入的图像，<code>$g$</code> 一般是一个相对较小的矩阵，我们称之为卷积核。这种情况下，卷积的计算方式是类似的，只是会将 <code>$g$</code> 矩阵旋转 <code>$180^{\circ}$</code> 使得相乘的元素的位置也相同，同时需要 <code>$g$</code> 在 <code>$f$</code> 上进行滑动并计算对应位置的卷积值。下图 <sup id="fnref:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 展示了一步计算的具体过程：</p>
<p><img src="/images/cn/2018-08-25-cnn/conv-example.png" alt="Conv-Example"></p>
<p>下图 <sup id="fnref1:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 形象的刻画了利用一个 3x3 大小的卷积核的整个卷积计算过程：</p>
<p><img src="/images/cn/2018-08-25-cnn/conv-sobel.gif" alt="Conv-Sobel"></p>
<p>一些预设的卷积核对于图片可以起到不同的滤波器效果，例如下面 4 个卷积核分别会对图像产生不同的效果：不改变，边缘检测，锐化和高斯模糊。</p>
<p><code>$$ \left[ \begin{array}{ccc} 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 \\ 0 &amp; 0 &amp; 0 \end{array} \right] , \left[ \begin{array}{ccc} -1 &amp; -1 &amp; -1 \\ -1 &amp;  8 &amp; -1 \\ -1 &amp; -1 &amp; -1 \end{array} \right] , \left[ \begin{array}{ccc} 0  &amp; -1 &amp; 0 \\ -1 &amp;  5 &amp; -1 \\ 0  &amp; -1 &amp; 0 \end{array} \right] , \dfrac{1}{16} \left[ \begin{array}{ccc} 1 &amp; 2 &amp; 1 \\ 2 &amp; 4 &amp; 2 \\ 1 &amp; 2 &amp; 1 \end{array} \right] $$</code></p>
<p>对 lena 图片应用这 4 个卷积核，变换后的效果如下 (从左到右，从上到下)：</p>
<p><img src="/images/cn/2018-08-25-cnn/lena-filters.png" alt="Lena-Filters"></p>
<p>在上面整个计算卷积的动图中，我们不难发现，利用 3x3 大小 (我们一般将这个参数称之为 <code>kernel_size</code>，即<strong>卷积核的大小</strong>，其可以为一个整数表示长宽大小相同，也可以为两个不同的整数) 的卷积核对 5x5 大小的原始矩阵进行卷积操作后，结果矩阵并没有保持原来的大小，而是变为了 (5-(3-1))x(5-(3-1)) (即 3x3) 大小的矩阵。这就需要引入 CNN 网络中卷积层的两个常用参数 <code>padding</code> 和 <code>strides</code>。</p>
<p><code>padding</code> 是指是否对图像的外侧进行<strong>补零操作</strong>，其取值一般为 <code>VALID</code> 和 <code>SAME</code> 两种。<code>VALID</code> 表示<strong>不进行补零</strong>操作，对于输入形状为 <code>$\left(x, y\right)$</code> 的矩阵，利用形状为 <code>$\left(m, n\right)$</code> 的卷积核进行卷积，得到的结果矩阵的形状则为 <code>$\left(x-m+1, y-n+1\right)$</code>。<code>SAME</code> 表示<strong>进行补零</strong>操作，在进行卷积操作前，会对图像的四个边缘分别向左右补充 <code>$\left(m \mid 2 \right) + 1$</code> 个零，向上下补充 <code>$\left(n \mid 2 \right) + 1$</code> 个零 (<code>$\mid$</code> 表示整除)，从而保证进行卷积操作后，结果的形状与原图像的形状保持相同，如下图 <sup id="fnref2:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/conv-zero-padding.png" alt="Conv2d-Zero-Padding"></p>
<p><code>strides</code> 是指进行卷积操作时，每次卷积核移动的步长。示例中，卷积核在横轴和纵轴方向上的移动步长均为 <code>$1$</code>，除此之外用于也可以指定不同的步长。移动的步长同样会对卷积后的结果的形状产生影响。</p>
<p>除此之外，还有另一个重要的参数 <code>filters</code>，其表示在一个卷积层中使用的<strong>卷积核的个数</strong>。在一个卷积层中，一个卷积核可以学习并提取图像的一种特征，但往往图片中包含多种不同的特征信息，因此我们需要多个不同的卷积核提取不同的特征。下图 <sup id="fnref3:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 是一个利用 4 个不同的卷积核对一张图像进行卷积操作的示意图：</p>
<p><img src="/images/cn/2018-08-25-cnn/conv2d-kernels.png" alt="Conv2d-Kernels"></p>
<p>上面我们都是以一个灰度图像 (仅包含 1 个通道) 为示例进行的讨论，那么对于一个 RGB 图像 (包含 3 个通道)，相应的，卷积核也是一个 3 维的形状，如下图 <sup id="fnref4:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup> 所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/conv3d-kernels.png" alt="Conv3d-Kernels"></p>
<p>卷积层对于我们的神经网络的模型带来的改进主要包括如下三个方面：<strong>稀疏交互 (sparse interactions)</strong>，<strong>参数共享 (parameter sharing)</strong> 和<strong>等变表示 (equivariant representations)</strong>。</p>
<p>在全连接的神经网络中，隐含层中的每一个节点都和上一层的所有节点相连，同时有被连接到下一层的全部节点。而卷积层不同，节点之间的连接性受到卷积核大小的制约。下图 <sup id="fnref:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 分别以自下而上 (左) 和自上而下 (右) 两个角度对比了卷积层和全连接层节点之间连接性的差异。</p>
<p><img src="/images/cn/2018-08-25-cnn/sparse-interactions.png" alt="Sparse-Interactions"></p>
<p>在上图 (右) 中，我们可以看出节点 <code>$s_3$</code> 受到节点 <code>$x_2$</code>，<code>$x_3$</code> 和 <code>$x_4$</code> 的影响，这些节点被称之为 <code>$s_3$</code> 的<strong>接受域 (receptive field)</strong>。稀疏交互使得在 <code>$m$</code> 个输入和 <code>$n$</code> 个输出的情况下，参数的个数由 <code>$m \times n$</code> 个减少至 <code>$k \times n$</code> 个，其中 <code>$k$</code> 为卷积核的大小。尽管一个节点在一个层级之间仅与其接受域内的节点相关联，但是对于深层中的节点，其与绝大部分输入之间却存在这<strong>间接交互</strong>，如下图 <sup id="fnref1:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/indirect-interactions.png" alt="Indirect-Interactions"></p>
<p>节点 <code>$g_3$</code> 尽管<strong>直接</strong>的连接是稀疏的，但处于更深的层中可以<strong>间接</strong>的连接到全部或者大部分的输入节点。这就使得网络可以仅通过这种稀疏交互来高效的描述多个输入变量之间的复杂关系。</p>
<p>除了稀疏交互带来的参数个数减少外，<strong>参数共享</strong>也起到了类似的作用。所谓参数共享就是指在进行不同操作时使用相同的参数，具体而言也就是在我们利用卷积核在图像上滑动计算卷积时，每一步使用的卷积核都是相同的。同全连接网络的情况对比如下图 <sup id="fnref2:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/parameter-sharing.png" alt="Parameter-Sharing"></p>
<p>在全连接网络 (上图 - 下) 中，任意两个节点之间的连接 (权重) 仅用于这两个节点之间，而在卷积层中，如上图所示，其对卷积核中间节点 (黑色箭头) 的使用方式 (权重) 是相同的。参数共享虽然对于计算的时间复杂度没有带来改进，仍然是 <code>$O \left(k \times n\right)$</code>，但其却将参数个数降低至 <code>$k$</code> 个。</p>
<p>正是由于参数共享机制，使得卷积层具有平移 <strong>等变 (equivariance)</strong> 的性质。对于函数 <code>$f\left(x\right)$</code> 和 <code>$g\left(x\right)$</code>，如果满足 <code>$f\left(g\left(x\right)\right) = g\left(f\left(x\right)\right)$</code>，我们就称 <code>$f\left(x\right)$</code> 对于变换 <code>$g$</code> 具有等变性。简言之，对于图像如果我们将所有的像素点进行移动，则卷积后的输出表示也会移动同样的量。</p>
<h3 id="非线性层">非线性层</h3>
<p>非线性层并不是 CNN 特有的网络层，在此我们不再详细介绍，一般情况下我们会使用 ReLU 作为我们的激活函数。</p>
<h3 id="池化层">池化层</h3>
<p><strong>池化层</strong> 是一个利用 <strong>池化函数 (pooling function)</strong> 对网络输出进行进一步调整的网络层。池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出。常用的池化函数包括最大池化 (max pooling) 函数 (即给出邻域内的最大值) 和平均池化 (average pooling) 函数 (即给出邻域内的平均值) 等。但无论选择何种池化函数，当对输入做出少量平移时，池化对输入的表示都近似 <strong>不变 (invariant)</strong>。<strong>局部平移不变性</strong> 是一个很重要的性质，尤其是当我们关心某个特征是否出现而不关心它出现的位置时。</p>
<p>池化层同卷积层类似，具有三个比较重要的参数：<code>pool_size</code>，<code>strides</code> 和 <code>padding</code>，分别表示池化窗口的大小，步长以及是否对图像的外侧进行补零操作。下图 <sup id="fnref3:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> 是一个 <code>pool_size=3</code>，<code>strides=3</code>，<code>padding='valid'</code> 的最大池化过程示例：</p>
<p><img src="/images/cn/2018-08-25-cnn/max-pooling.gif" alt="Max-Pooling"></p>
<p>池化层同时也能够提高网络的计算效率，例如上图中在横轴和纵轴的步长均为 <code>$3$</code>，经过池化后，下一层网络节点的个数降低至前一层的 <code>$\frac{1}{3 \times 3} = \frac{1}{9}$</code>。</p>
<h3 id="全连接层">全连接层</h3>
<p>全链接层 (Fully-connected or Dense Layer) 的目的就是将我们最后一个池化层的输出连接到最终的输出节点上。例如，最后一个池化层的输出大小为 <code>$\left[5 \times 5 \times 16\right]$</code>，也就是有 <code>$5 \times 5 \times 16 = 400$</code> 个节点，对于手写数字识别的问题，我们的输出为 0 至 9 共 10 个数字，采用 one-hot 编码的话，输出层共 10 个节点。例如在 LeNet 中有 2 个全连接层，每层的节点数分别为 120 和 84，在实际应用中，通常全连接层的节点数会逐层递减。需要注意的是，在进行编码的时候，第一个全连接层并不是直接与最后一个池化层相连，而是先对池化层进行 flatten 操作，使其变成一个一维向量后再与全连接层相连。</p>
<h3 id="输出层">输出层</h3>
<p>输出层根据具体问题的不同会略有不同，例如对于手写数字识别问题，采用 one-hot 编码的话，输出层则包含 10 个节点。对于回归或二分类问题，输出层则仅包含 1 个节点。当然对于二分类问题，我们也可以像多分类问题一样将其利用 one-hot 进行编码，例如 <code>$\left[1, 0\right]$</code> 表示类型 0，<code>$\left[0, 1\right]$</code> 表示类型 1。</p>
<h2 id="扩展与应用">扩展与应用</h2>
<p>本节我们将介绍一些经典的 CNN 网络架构及其相关的改进。</p>
<h3 id="alexnet">AlexNet <sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></h3>
<p><img src="/images/cn/2018-08-25-cnn/alexnet.png" alt="AlexNet"></p>
<p>AlexNet 在整体结构上同 LeNet-5 类似，其改进大致如下：</p>
<ul>
<li>网络包含了 5 个卷积层和 3 个全连接层，网络规模变大。</li>
<li>使用了 ReLU 非线性激活函数。</li>
<li>应用了 Data Augmentation，Dropout，Momentum，Weight Decay 等策略改进训练。</li>
<li>在算力有限的情况下，对模型进行划分为两部分并行计算。</li>
<li>增加局部响应归一化 (LRN, Local Response Normalization)。</li>
</ul>
<p>LRN 的思想来自与生物学中侧抑制 (Lateral Inhibition) 的概念，简单来说就是相近的神经元之间会发生抑制作用。在 AlexNet 中，给出的 LRN 计算公式如下：</p>
<p><code>$$ b_{x,y}^{i} = a_{x,y}^{i} / \left(k + \alpha \sum_{j = \max \left(0, i - n/2\right)}^{\min \left(N - 1, i + n/2\right)}{\left(a_{x,y}^{j}\right)^2}\right)^{\beta} $$</code></p>
<p>其中，<code>$a_{x,y}^{i}$</code> 表示第 <code>$i$</code> 个卷积核在位置 <code>$\left(x,y\right)$</code> 的输出，<code>$N$</code> 为卷积核的个数，<code>$k, n, \alpha, \beta$</code> 均为超参数，在原文中分别初值为：<code>$k=2, n=5, \alpha=10^{-4}, \beta=0.75$</code>。在上式中，分母为所有卷积核 (Feature Maps) 的加和，因此 LRN 可以简单理解为一个跨 Feature Maps 的像素级归一化。</p>
<p><strong>开源实现</strong>：</p>
<ul>
<li><i class="icon icon-tensorflow"></i> <a href="https://github.com/tensorflow/models/tree/master/research">tensorflow/models</a>,  <a href="https://github.com/tflearn/tflearn/blob/master/examples/images">tflearn/examples</a></li>
<li><i class="icon icon-pytorch"></i> <a href="https://pytorch.org/docs/stable/torchvision/models.html">pytorch/torchvision/models</a></li>
<li><i class="icon icon-caffe2"></i> <a href="https://github.com/caffe2/models">caffe2/models</a></li>
<li><i class="icon icon-mxnet"></i> <a href="https://github.com/apache/incubator-mxnet/blob/master/example/image-classification/symbols">incubator-mxnet/example</a></li>
</ul>
<h3 id="vgg-net">VGG Net <sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></h3>
<img src="/images/cn/2018-08-25-cnn/vgg-16.png" width="180" style="float:left; margin-right:3em;"/>
<p>左图是 VGG-16 Net 的网络结构，原文中还有一个 VGG-19 Net，其差别在于后面三组卷积层中均多叠加了一个卷积层，使得网络层数由 16 增加至 19。</p>
<p>VGG Net 的主要改变如下：</p>
<ul>
<li>网络层级更深，从 AlexNet 的 8 层增加至 16 层和 19 层，更深的网络层级意味着更强的学习能力，但也需要更多的算力对模型进行优化。</li>
<li>仅使用 3x3 大小的卷积。在 AlexNet 中，浅层部分使用了较大的卷积核，而 VGG 使用了 3x3 的小卷积核进行串联叠加，减少了参数个数。</li>
<li>卷积采样的步长为 1x1，Max Pooling 的步长为 2x2。</li>
<li>去掉了效果提升不明显的但计算耗时的 LRN。</li>
<li>增加了 Feature Maps 的个数。</li>
</ul>
<p><strong>开源实现</strong>：</p>
<ul>
<li><i class="icon icon-tensorflow"></i> <a href="https://github.com/tensorflow/models/tree/master/research">tensorflow/models</a>,  <a href="https://github.com/tflearn/tflearn/tree/master/examples">tflearn/examples</a>, <a href="https://github.com/tensorlayer/awesome-tensorlayer">tensorlayer/awesome-tensorlayer</a></li>
<li><i class="icon icon-keras"></i> <a href="https://www.tensorflow.org/api_docs/python/tf/keras/applications">tf/keras/applications</a>, <a href="https://keras.io/applications">keras/applications</a></li>
<li><i class="icon icon-pytorch"></i> <a href="https://pytorch.org/docs/stable/torchvision/models.html">pytorch/torchvision/models</a></li>
<li><i class="icon icon-caffe2"></i> <a href="https://github.com/caffe2/models">caffe2/models</a></li>
<li><i class="icon icon-mxnet"></i> <a href="https://github.com/apache/incubator-mxnet/tree/master/example/image-classification/symbols">incubator-mxnet/example</a></li>
</ul>
<p style="clear:both;"></p>
<h3 id="network-in-network-nin">Network in Network (NIN) <sup id="fnref:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup></h3>
<p><img src="/images/cn/2018-08-25-cnn/network-in-network.png" alt="NIN"></p>
<p>NIN 网络的主要改变如下：</p>
<ul>
<li>利用多层的全连接网络替换线性的卷积，即 mlpconv (Conv + MLP) 层。其中卷积层为线性的操作，而 MLP 为非线性的操作，因此具有更高的抽象能力。</li>
<li>去掉了全连接层，使用 Global Average Pooling，也就是将每个 Feature Maps 上所有的值求平均，直接作为输出节点，如下图所示：</li>
</ul>
<p><img src="/images/cn/2018-08-25-cnn/global-average-pooling.png" alt="Global-Average-Pooling"></p>
<ul>
<li>相比 AlexNet 简化了网络结构，仅包含 4 个 NIN 单元和一个 Global Average Pooling，整个参数空间比 AlexNet 小了一个数量级。</li>
</ul>
<p>在 NIN 中，在跨通道的情况下，mlpconv 层又等价于传统的 Conv 层后接一个 1x1 大小的卷积层，因此 mlpconv 层有时也称为 cccp (cascaded cross channel parametric pooling) 层。1x1 大小的卷积核可以说实现了不同通道信息的交互和整合，同时对于输入通道为 <code>$m$</code> 和输出通道为 <code>$n$</code>，1x1 大小的卷积核在不改变分辨率的同时实现了降维 (<code>$m &gt; n$</code> 情况下) 或升维 (<code>$m &lt; n$</code> 情况下) 操作。</p>
<p><strong>开源实现</strong>：</p>
<ul>
<li><i class="icon icon-tensorflow"></i> <a href="https://github.com/tflearn/tflearn/tree/master/examples">tflearn/examples</a></li>
</ul>
<h3 id="googlenet-inception-v1-inception-v3-inception-v4">GoogLeNet (Inception V1) <sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>, Inception V3 <sup id="fnref:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup>, Inception V4 <sup id="fnref:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup></h3>
<p><img src="/images/cn/2018-08-25-cnn/googlenet.png" alt="GoogLeNet"></p>
<p>除了 VGG 这种从网络深度方向进行优化的策略以外，Google 还提出了在同一层利用不同大小的卷积核同时提取不同特征的思路，对于这样的结构我们称之为 Inception。</p>
<p><img src="/images/cn/2018-08-25-cnn/inception-v1-naive-dim-reduction.png" alt="Inception-V1"></p>
<p>上图 (左) 为原始的 Inception 结构，在这样一层中分别包括了 1x1 卷积，3x3 卷积，5x5 卷积和 3x3 Max Polling，使得网络在每一层都能学到不同尺度的特征。最后通过 Filter Concat 将其拼接为多个 Feature Maps。</p>
<p>这种方式虽然能够带来性能的提升，但同时也增加了计算量，因此为了进一步改善，其选择利用 1x1 大小的卷积进行降维操作，改进后的 Inception 模块如上图 (右) 所示。我们以 GoogLeNet 中的 inception (3a) 模块为例 (输入大小为 28x28x192)，解释 1x1 卷积的降维效果。</p>
<p>对于原始 Inception 模块，1x1 卷积的通道为 64，3x3 卷积的通道为 128，5x5 卷积的通道为 32，卷积层的参数个数为：</p>
<p><code>$$ \begin{equation} \begin{split} \# w_{\text{3a_conv_without_1x1}} =&amp; 1 \times 1 \times 192 \times 64 \\ &amp; + 3 \times 3 \times 192 \times 128 \\ &amp; + 5 \times 5 \times 192 \times 32 \\ =&amp; 387072 \end{split} \end{equation} $$</code></p>
<p>对于加上 1x1 卷积后的 Inception 模块 (通道数分别为 96 和 16) 后，卷积层的参数个数为：</p>
<p><code>$$ \begin{equation} \begin{split} \# w_{\text{3a_conv_with_1x1}} =&amp; 1 \times 1 \times 192 \times 64 \\ &amp; + 1 \times 1 \times 192 \times 96 + 3 \times 3 \times 96 \times 128 \\ &amp; + 1 \times 1 \times 192 \times 16 + 5 \times 5 \times 16 \times 32 \\ =&amp; 157184 \end{split} \end{equation} $$</code></p>
<p>可以看出，在添加 1x1 大小的卷积后，参数的个数减少了 2 倍多。通过 1x1 卷积对特征进行降维的层称之为 Bottleneck Layer 或 Bottleneck Block。</p>
<p>在 GoogLeNet 中，作者还提出了 Auxiliary Classifiers (AC)，用于辅助训练。AC 通过增加浅层的梯度来减轻深度梯度弥散的问题，从而加速整个网络的收敛。</p>
<p>随后 Google 在此对 Inception 进行了改进，同时提出了卷积神经网络的 4 项设计原则，概括如下：</p>
<ol>
<li>避免表示瓶颈，尤其是在网络的浅层部分。一般来说，在到达任务的最终表示之前，表示的大小应该从输入到输出缓慢减小。</li>
<li>高维特征在网络的局部更容易处理。在网络中增加更多的非线性有助于获得更多的解耦特征，同时网络训练也会加快。</li>
<li>空间聚合可以在低维嵌入中进行，同时也不会对表征能力带来太多影响。例如，再进行尺寸较大的卷积操作之前可以先对输入进行降维处理。</li>
<li>在网络的宽度和深度之间进行权衡。通过增加网络的深度和宽度均能够带来性能的提升，在同时增加其深度和宽度时，需要综合考虑算力的分配。</li>
</ol>
<p>Inception V3 的主要改进包括：</p>
<ul>
<li>增加了 Batch Normalized 层。</li>
<li>将一个 5x5 的卷积替换为两个串联的 3x3 的卷积 (基于原则 3)，减少了网络参数，如下图所示：</li>
</ul>
<p><img src="/images/cn/2018-08-25-cnn/inception-v3-v1-3x3.png" alt="Inception-V3-3x3"></p>
<ul>
<li>利用串联的 1xn 和 nx1 的非对称卷积 (Asymmetric Convolutions) 替代 nxn 的卷积 (基于原则 3)，减少了网络参数，如下图 (左) 所示：</li>
<li>增加带有滤波器组 (filter bank) 的 Inception 模块 (基于原则 2)，用于提升高维空间的表示能力，如下图 (右) 所示：</li>
</ul>
<p><img src="/images/cn/2018-08-25-cnn/inception-v3-1xn-nx1.png" alt="Inception-V3-1xn-nx1"></p>
<ul>
<li>重新探讨了 Auxiliary Classifiers 的作用，发现其在训练初期并没有有效的提高收敛速度，尽在训练快结束时会略微提高网络的精度。</li>
<li>新的下采样方案。在传统的做法中，如果先进行 Pooling，在利用 Inception 模块进行操作，如下图 (左) 所示，会造成表示瓶颈 (原则 1)；而先利用 Inception 模块进行操作，再进行 Pooling，则会增加参数数量。
<img src="/images/cn/2018-08-25-cnn/inception-v3-reducing-gird-size-old.png" alt="Inception-V3-reducing-grid-size-old"><br>
因此，借助 Inception 结构的思想，作者提出了一种新的下采样方案。下图 (左) 是利用 Inception 的思想进行下采样的内部逻辑，下图 (右) 为同时利用 Inception 思想和 Pooling 进行下采样的整体框架。</li>
</ul>
<p><img src="/images/cn/2018-08-25-cnn/inception-v3-reducing-gird-size-new.png" alt="Inception-V3-reducing-grid-size-new"></p>
<ul>
<li>Label Smoothing 机制。假设标签的真实分布为 <code>$q\left(k\right)$</code>，则对于一个真实标签 <code>$y$</code> 而言，有 <code>$q\left(y\right) = 1$</code>，对于 <code>$k \neq y$</code>，有 <code>$q\left(k\right) = 0$</code>。这种情况会导致两个问题：一个是当模型对于每个训练样本的真实标签赋予全部的概率时，模型将会发生过拟合；另一个是其鼓励拉大最大概率标签同其他标签之间的概率差距，从而降低网络的适应性。也就是说这种情况的发生是由于网络对于其预测结果过于自信。因此，对于一个真实标签 <code>$y$</code>，我们将其标签的分布 <code>$q\left(k | x\right) = \delta_{k, y}$</code> 替换为：
<code>$$ q' \left(k | x\right) = \left(1 - \epsilon\right) \delta_{k, y} + \epsilon u \left(k\right) $$</code>
其中，<code>$u \left(k\right)$</code> 是一个固定的分布，文中采用了均匀分布，即 <code>$u \left(k\right) = 1 / K$</code>；<code>$\epsilon$</code> 为权重项，试验中取为 <code>$0.1$</code>。</li>
</ul>
<p>Inception V4 对于 Inception 网络做了进一步细致的调整，其主要是将 Inception V3 中的前几层网络替换为了 stem 模块，具体的 stem 模块结构就不在此详细介绍了。</p>
<p><strong>开源实现</strong>：</p>
<ul>
<li><i class="icon icon-tensorflow"></i> <a href="https://github.com/tensorflow/models/tree/master/research">tensorflow/models</a>,  <a href="https://github.com/tflearn/tflearn/tree/master/examples">tflearn/examples</a>, <a href="https://github.com/tensorlayer/awesome-tensorlayer">tensorlayer/awesome-tensorlayer</a></li>
<li><i class="icon icon-keras"></i> <a href="https://www.tensorflow.org/api_docs/python/tf/keras/applications">tf/keras/applications</a>, <a href="https://keras.io/applications">keras/applications</a></li>
<li><i class="icon icon-pytorch"></i> <a href="https://pytorch.org/docs/stable/torchvision/models.html">pytorch/torchvision/models</a></li>
<li><i class="icon icon-caffe2"></i> <a href="https://github.com/caffe2/models">caffe2/models</a></li>
<li><i class="icon icon-mxnet"></i> <a href="https://github.com/apache/incubator-mxnet/tree/master/example/image-classification/symbols">incubator-mxnet/example</a></li>
</ul>
<h3 id="deep-residual-net-identity-mapping-residual-net-densenet">Deep Residual Net <sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>, Identity Mapping Residual Net <sup id="fnref:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup>, DenseNet <sup id="fnref:21"><a href="#fn:21" class="footnote-ref" role="doc-noteref">21</a></sup></h3>
<img src="/images/cn/2018-08-25-cnn/residual-network.png" width="300" style="float:left; margin-right:3em;"/>
<p>随着网络深度的不断增加啊，其效果并未如想象一般提升，甚至发生了退化，He 等人 <sup id="fnref2:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 发现在 CIFAR-10 数据集上，一个 56 层的神经网络的性能要比一个 20 层的神经网络要差。网络层级的不断增加，不仅导致参数的增加，同时也可能导致梯度弥散问题 (vanishing gradients)。</p>
<p>这对这些问题，He 等人提出了一种 Deep Residual Net，在这个网络结构中，残差 (residual) 的思想可以理解为：假设原始潜在的映射关系为 <code>$\mathcal{H} \left(\mathbf{x}\right)$</code>，对于新的网络层我们不再拟合原始的映射关系，而是拟合 <code>$\mathcal{F} \left(\mathbf{x}\right) = \mathcal{H} \left(\mathbf{x}\right) - \mathbf{x}$</code>，也就是说原始潜在的映射关系变为 <code>$\mathcal{F} \left(\mathbf{x}\right) + \mathbf{x}$</code>。新的映射关系可以理解为在网络前向传播中添加了一条捷径 (shortcut connections)，如下图所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/residual-block.png" alt="Residual-Block"></p>
<p style="clear:both;"></p>
<p>增加 Short Connections 并没有增加参数个数，也没有增加计算量，与此同时模型依旧可以利用 SGD 等算法进行优化。</p>
<p><img src="/images/cn/2018-08-25-cnn/residual-results.png" alt="Residual-Results"></p>
<p>从 Deep Residual Net 的实验结果 (如上图) 可以看出，在没有加入残差模块的网络中 (上图 - 左) 出现了上文中描述的问题：更多层级的网络的效果反而较差；在加入了残差模块的网络中 (上图 - 右)，其整体性能均比未加入残差模块的网络要好，同时具有更多层级的网络的效果也更好。</p>
<p>随后 He 等人 <sup id="fnref1:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup> 又提出了 Identity Mapping Residual Net，在原始的 ResNet 中，一个残差单元可以表示为：</p>
<p><code>$$ \begin{equation} \begin{split} \mathbb{y}_{\ell} = &amp; h \left(\mathbb{x}_{\ell}\right) + \mathcal{F} \left(\mathbb{x}_{\ell}, \mathcal{W}_l\right) \\ \mathbb{x}_{\ell+1} = &amp; f \left(\mathbb{y}_{\ell}\right) \end{split} \end{equation} $$</code></p>
<p>其中 <code>$\mathbb{x}_{\ell}$</code> 和 <code>$\mathbb{x}_{\ell+1}$</code> 为第 <code>$\ell$</code> 个单元的输入和输出，<code>$\mathcal{F}$</code> 为残差函数，<code>$h \left(\mathbb{x}_{\ell}\right) = \mathbb{x}_{\ell}$</code> 为一个恒等映射，<code>$f$</code> 为 ReLU 函数。在 Identity Mapping Residual Net，作者将 <code>$f$</code> 由原来的 ReLU 函数也替换成一个恒定映射，即 <code>$\mathbb{x}_{\ell+1} \equiv \mathbb{y}_{\ell}$</code>，则上式可以改写为：</p>
<p><code>$$ \mathbb{x}_{\ell+1} = \mathbb{x}_{\ell} + \mathcal{F} \left(\mathbb{x}_{\ell}, \mathcal{W}_{\ell}\right) $$</code></p>
<p>则对于任意深度的单元 <code>$L$</code>，有如下表示：</p>
<p><code>$$ \mathbb{x}_L = \mathbb{x}_{\ell} + \sum_{i=\ell}^{L-1}{\mathcal{F} \left(\mathbb{x}_i, \mathcal{W}_i\right)} $$</code></p>
<p>上式形式的表示使得其在反向传播中具有一个很好的性质，假设损失函数为 <code>$\mathcal{E}$</code>，根据链式法则，对于单元 <code>$\ell$</code>，梯度为：</p>
<p><code>$$ \dfrac{\partial \mathcal{E}}{\partial \mathbb{x}_{\ell}} = \dfrac{\partial \mathcal{E}}{\partial \mathbb{x}_L} \dfrac{\partial \mathbb{x}_L}{\partial \mathbb{x}_{\ell}} = \dfrac{\partial \mathcal{E}}{\partial \mathbb{x}_{\ell}} \left(1 + \dfrac{\partial}{\partial \mathbb{x}_{\ell}} \sum_{i=\ell}^{L-1}{\mathcal{F} \left(\mathbb{x}_i, \mathcal{W}_i\right)}\right) $$</code></p>
<p>对于上式形式的梯度，我们可以将其拆解为两部分：<code>$\frac{\partial \mathcal{E}}{\partial \mathbb{x}_{\ell}}$</code> 为不通过任何权重层的直接梯度传递，<code>$\frac{\partial \mathcal{E}}{\partial \mathbb{x}_{\ell}} \left(\frac{\partial}{\partial \mathbb{x}_{\ell}} \sum_{i=\ell}^{L-1}{\mathcal{F} \left(\mathbb{x}_i, \mathcal{W}_i\right)}\right)$</code> 为通过权重层的梯度传递。前一项保证了梯度能够直接传回任意浅层 <code>$\ell$</code>，同时对于任意一个 mini-batch 的所有样本，<code>$\frac{\partial}{\partial \mathbb{x}_{\ell}} \sum_{i=\ell}^{L-1}\mathcal{F}$</code> 不可能永远为 <code>$-1$</code>，所以保证了即使权重很小的情况下也不会出现梯度弥散。下图展示了原始的 ResNet 和 Identity Mapping Residual Net 之间残差单元的区别和网络的性能差异：</p>
<p><img src="/images/cn/2018-08-25-cnn/identity-mapping-residual-network-unit.png" alt="Identity-Mapping-Residual-Net-Unit"></p>
<p>Huang 等人 <sup id="fnref1:21"><a href="#fn:21" class="footnote-ref" role="doc-noteref">21</a></sup> 在 ResNet 的基础上又提出了 DenseNet 网络，其网络结构如下所示：</p>
<p><img src="/images/cn/2018-08-25-cnn/densenet.png" alt="DenseNet"></p>
<p>DenseNet 的主要改进如下：</p>
<ul>
<li>Dense Connectivity：将网络中每一层都与其后续层进行直接连接。</li>
<li>Growth Rate：<code>$H_{\ell}$</code> 将产生 <code>$k$</code> 个 Feature Maps，因此第 <code>$\ell$</code> 层将包含 <code>$k_0 + k \times \left(\ell - 1\right)$</code> 个 Feature Maps，其中 <code>$k_0$</code> 为输入层的通道数。DenseNet 与现有框架的不同之处就是将网络限定的比较窄，例如：<code>$k = 12$</code>，并将该超参数称之为网络的增长率 (Growth Rate)。</li>
<li>Bottleneck Layers：在 3x3 的卷积之前增加 1x1 的卷积进行降维操作。</li>
<li>Compression：在两个 Dense Block 之间增加过渡层 (Transition Layer)，进一步减少 Feature Maps 个数。</li>
</ul>
<p><strong>开源实现</strong>：</p>
<ul>
<li><i class="icon icon-tensorflow"></i> <a href="https://github.com/tensorflow/models/tree/master/research">tensorflow/models</a>,  <a href="https://github.com/tflearn/tflearn/tree/master/examples">tflearn/examples</a>, <a href="https://github.com/tensorlayer/awesome-tensorlayer">tensorlayer/awesome-tensorlayer</a></li>
<li><i class="icon icon-keras"></i> <a href="https://www.tensorflow.org/api_docs/python/tf/keras/applications">tf/keras/applications</a>, <a href="https://keras.io/applications">keras/applications</a></li>
<li><i class="icon icon-pytorch"></i> <a href="https://pytorch.org/docs/stable/torchvision/models.html">pytorch/torchvision/models</a></li>
<li><i class="icon icon-caffe2"></i> <a href="https://github.com/caffe2/models">caffe2/models</a></li>
<li><i class="icon icon-mxnet"></i> <a href="https://github.com/apache/incubator-mxnet/tree/master/example/image-classification/symbols">incubator-mxnet/example</a></li>
</ul>
<h3 id="综合比较">综合比较</h3>
<p>Canziani 等人 <sup id="fnref:22"><a href="#fn:22" class="footnote-ref" role="doc-noteref">22</a></sup> 综合了模型的准确率，参数大小，内存占用，推理时间等多个角度对现有的 CNN 模型进行了对比分析。</p>
<p><img src="/images/cn/2018-08-25-cnn/cnn-accuracy-and-parameters.png" alt="CNN-Accuracy-and-Parameters"></p>
<p>上图 (左) 展示了在 ImageNet 挑战赛中不同 CNN 网络模型的 Top-1 的准确率。可以看出近期的 ResNet 和 Inception 架构以至少 7% 的显著优势超过了其他架构。上图 (右) 以另一种形式展现了除了准确率以外的更多信息，包括计算成本和网络的参数个数，其中横轴为计算成本，纵轴为 Top-1 的准确率，气泡的大小为网络的参数个数。可以看出 ResNet 和 Inception 架构相比 AlexNet 和 VGG 不仅有更高的准确率，其在计算成本和网络的参数个数 (模型大小) 方面也具有一定优势。</p>
<p>文章部分内容参考了 <strong>刘昕</strong> 的 <a href="http://valser.org/2016/dl/%E5%88%98%E6%98%95.pdf"><strong>CNN近期进展与实用技巧</strong></a>。CNN 除了在图像分类问题上取得很大的进展外，在例如：物体检测：R-CNN <sup id="fnref:23"><a href="#fn:23" class="footnote-ref" role="doc-noteref">23</a></sup>, SPP-Net <sup id="fnref:24"><a href="#fn:24" class="footnote-ref" role="doc-noteref">24</a></sup>, Fast R-CNN <sup id="fnref:25"><a href="#fn:25" class="footnote-ref" role="doc-noteref">25</a></sup>, Faster R-CNN <sup id="fnref:26"><a href="#fn:26" class="footnote-ref" role="doc-noteref">26</a></sup>，语义分割：FCN <sup id="fnref:27"><a href="#fn:27" class="footnote-ref" role="doc-noteref">27</a></sup> 等多个领域也取得了不俗的成绩。针对不同的应用场景，网络模型和处理方法均有一定的差异，本文就不再对其他场景一一展开说明，不同场景将在后续进行单独整理。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>LeCun, Y., Bottou, L., Bengio, Y., &amp; Haffner, P. (1998). Gradient-based learning applied to document recognition. <em>Proceedings of the IEEE, 86</em>(11), 2278-2324.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Krizhevsky, A., Sutskever, I., &amp; Hinton, G. E. (2012). Imagenet classification with deep convolutional neural networks. In <em>Advances in neural information processing systems</em> (pp. 1097-1105).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="http://www.image-net.org/">http://www.image-net.org/</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Simonyan, K., &amp; Zisserman, A. (2014). Very deep convolutional networks for large-scale image recognition. <em>arXiv preprint arXiv:1409.1556.</em>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., &hellip; &amp; Rabinovich, A. (2015). Going deeper with convolutions. In <em>Proceedings of the IEEE conference on computer vision and pattern recognition</em> (pp. 1-9).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>He, K., Zhang, X., Ren, S., &amp; Sun, J. (2016). Deep residual learning for image recognition. In <em>Proceedings of the IEEE conference on computer vision and pattern recognition</em> (pp. 770-778).&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Girshick, R., Donahue, J., Darrell, T., &amp; Malik, J. (2014). Rich feature hierarchies for accurate object detection and semantic segmentation. In <em>Proceedings of the IEEE conference on computer vision and pattern recognition</em> (pp. 580-587).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Girshick, R. (2015). Fast r-cnn. In <em>Proceedings of the IEEE international conference on computer vision</em> (pp. 1440-1448).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Ren, S., He, K., Girshick, R., &amp; Sun, J. (2015). Faster r-cnn: Towards real-time object detection with region proposal networks. In <em>Advances in neural information processing systems</em> (pp. 91-99).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Long, J., Shelhamer, E., &amp; Darrell, T. (2015). Fully convolutional networks for semantic segmentation. In <em>Proceedings of the IEEE conference on computer vision and pattern recognition</em> (pp. 3431-3440).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Vinyals, O., Toshev, A., Bengio, S., &amp; Erhan, D. (2015). Show and tell: A neural image caption generator. In <em>Proceedings of the IEEE conference on computer vision and pattern recognition</em> (pp. 3156-3164).&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>Ji, S., Xu, W., Yang, M., &amp; Yu, K. (2013). 3D convolutional neural networks for human action recognition. <em>IEEE transactions on pattern analysis and machine intelligence, 35</em>(1), 221-231.&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p>Kim, Y. (2014). Convolutional neural networks for sentence classification. <em>arXiv preprint arXiv:1408.5882.</em>&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p><a href="http://yann.lecun.com/exdb/mnist">http://yann.lecun.com/exdb/mnist</a>&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:15">
<p><a href="https://mlnotebook.github.io/post/CNN1/">https://mlnotebook.github.io/post/CNN1/</a>&#160;<a href="#fnref:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:16">
<p>Goodfellow, I., Bengio, Y., Courville, A., &amp; Bengio, Y. (2016). <em>Deep learning</em> (Vol. 1). Cambridge: MIT press.&#160;<a href="#fnref:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:17">
<p>Lin, M., Chen, Q., &amp; Yan, S. (2013). Network In Network. <em>arXiv preprint arXiv:1312.4400.</em>&#160;<a href="#fnref:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:18">
<p>Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J., &amp; Wojna, Z. (2016). Rethinking the Inception Architecture for Computer Vision. In <em>Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition</em> (pp. 2818–2826).&#160;<a href="#fnref:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:19">
<p>Szegedy, C., Ioffe, S., Vanhoucke, V., &amp; Alemi, A. (2016). Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning. <em>arXiv preprint arXiv:1602.07261.</em>&#160;<a href="#fnref:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:20">
<p>He, K., Zhang, X., Ren, S., &amp; Sun, J. (2016). Identity Mappings in Deep Residual Networks. <em>arXiv preprint arXiv:1603.05027.</em>&#160;<a href="#fnref:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:21">
<p>Huang, G., Liu, Z., van der Maaten, L., &amp; Weinberger, K. Q. (2016). Densely Connected Convolutional Networks. <em>arXiv preprint arXiv:1608.06993</em>&#160;<a href="#fnref:21" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:21" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:22">
<p>Canziani, A., Paszke, A., &amp; Culurciello, E. (2016). An Analysis of Deep Neural Network Models for Practical Applications. <em>arXiv preprint arXiv:1605.07678</em>&#160;<a href="#fnref:22" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:23">
<p>Girshick, R., Donahue, J., Darrell, T., &amp; Malik, J. (2014). Rich Feature Hierarchies for Accurate Object Detection and Semantic Segmentation. In <em>Proceedings of the 2014 IEEE Conference on Computer Vision and Pattern Recognition</em> (pp. 580–587).&#160;<a href="#fnref:23" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:24">
<p>He, K., Zhang, X., Ren, S., &amp; Sun, J. (2015). Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition. <em>IEEE Transactions on Pattern Analysis and Machine Intelligence, 37(9)</em>, 1904–1916.&#160;<a href="#fnref:24" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:25">
<p>Girshick, R. (2015). Fast R-CNN. <em>arXiv preprint arXiv:1504.08083.</em>&#160;<a href="#fnref:25" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:26">
<p>Ren, S., He, K., Girshick, R., &amp; Sun, J. (2017). Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks. <em>IEEE Transactions on Pattern Analysis and Machine Intelligence, 39(6),</em> 1137–1149.&#160;<a href="#fnref:26" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:27">
<p>Shelhamer, E., Long, J., &amp; Darrell, T. (2017). Fully Convolutional Networks for Semantic Segmentation. <em>IEEE Transactions on Pattern Analysis and Machine Intelligence, 39(4),</em> 640–651.&#160;<a href="#fnref:27" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>深度学习优化算法 (Optimization Methods for Deeplearning)</title><link>https://zeqiang.fun/cn/2018/02/optimization-methods-for-deeplearning/</link><pubDate>Sat, 24 Feb 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/02/optimization-methods-for-deeplearning/</guid><description><![CDATA[
        <p>在构建神经网络模型的时候，除了网络结构设计以外，选取合适的优化算法也对网络起着至关重要的作用，本文将对神经网络中常用的优化算法进行简单的介绍和对比，本文部分参考了 Ruder 的关于梯度下降优化算法一文 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。首先，我们对下文中使用的符号进行同意说明：网络中的参数同一表示为 <code>$\theta$</code>，网络的假设函数为 <code>$h_{\boldsymbol{\theta}}\left(\boldsymbol{x}\right)$</code>，网络的损失函数为 <code>$J\left(\boldsymbol{\theta}\right)$</code>，学习率为 <code>$\alpha$</code>，假设训练数据中共包含 <code>$m$</code> 个样本，网络参数个数为 <code>$n$</code>。</p>
<h1 id="梯度下降">梯度下降</h1>
<p>在梯度下降算法中，常用的主要包含 3 种不同的形式，分别是批量梯度下降 (Batch Gradient Descent, BGD)，随机梯度下降 (Stochastic Gradient Descent, SGD) 和小批量梯度下降 (Mini-Batch Gradient Descent, MBGD)。一般情况下，我们在谈论梯度下降时，更多的是指小批量梯度下降。</p>
<h2 id="bgd">BGD</h2>
<p>BGD 为梯度下降算法中最基础的一个算法，其损失函数定义如下：</p>
<p><code>$$ J \left(\boldsymbol{\theta}\right) = \dfrac{1}{2m} \sum_{i=1}^{m}{\left(h_{\boldsymbol{\theta}}\left(x^{\left(i\right)}\right) - y^{\left(i\right)}\right)} $$</code></p>
<p>针对任意参数 <code>$\theta_j$</code> 我们可以求得其梯度为：</p>
<p><code>$$ \nabla_{\theta_j} = \dfrac{\partial J\left(\boldsymbol{\theta}\right)}{\partial \theta_j} = - \dfrac{1}{m} \sum_{i=1}^{m}{\left(y^{\left(i\right)} - h_{\boldsymbol{\theta}} \left(x^{\left(i\right)}\right)\right) x_j^{\left(i\right)}} $$</code></p>
<p>之后，对于任意参数 <code>$\theta_j$</code> 我们按照其<strong>负梯度</strong>方向进行更新：</p>
<p><code>$$ \theta_j = \theta_j + \alpha \left[\dfrac{1}{m} \sum_{i=1}^{m}{\left(y^{\left(i\right)} - h_{\boldsymbol{\theta}} \left(x^{\left(i\right)}\right)\right) x_j^{\left(i\right)}}\right] $$</code></p>
<p>整个算法流程可以表示如下：</p>


<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css">


<div><pre class="pseudocode">
\begin{algorithm}
\caption{BGD 算法}
\begin{algorithmic}
\FOR{$epoch = 1, 2, ...$}
    \FOR{$j = 1, 2, ..., n$}
        \STATE $J \left(\boldsymbol{\theta}\right) = \dfrac{1}{2m} \sum_{i=1}^{m}{\left(h_{\boldsymbol{\theta}}\left(x^{\left(i\right)}\right) - y^{\left(i\right)}\right)}$
        \STATE $\theta_j = \theta_j - \alpha \dfrac{\partial J\left(\boldsymbol{\theta}\right)}{\partial \theta_j}$
    \ENDFOR
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>从上述算法流程中我们可以看到，BGD 算法每次计算梯度都使用了整个训练集，也就是说对于给定的一个初始点，其每一步的更新都是沿着全局梯度最大的负方向。但这同样是其问题，当 <code>$m$</code> 太大时，整个算法的计算开销就很高了。</p>
<h2 id="sgd">SGD</h2>
<p>SGD 相比于 BGD，其最主要的区别就在于计算梯度时不再利用整个数据集，而是针对单个样本计算梯度并更新权重，因此，其损失函数定义如下：</p>
<p><code>$$ J \left(\boldsymbol{\theta}\right) = \dfrac{1}{2} \left(h_{\boldsymbol{\theta}}\left(x^{\left(i\right)}\right) - y^{\left(i\right)}\right) $$</code></p>
<p>整个算法流程可以表示如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{SGD 算法}
\begin{algorithmic}
\FOR{$epoch = 1, 2, ...$}
    \STATE Randomly shuffle dataset
    \FOR{$i = 1, 2, ..., m$}
        \FOR{$j = 1, 2, ..., n$}
            \STATE $J \left(\boldsymbol{\theta}\right) = \dfrac{1}{2} \left(h_{\boldsymbol{\theta}}\left(x^{\left(i\right)}\right) - y^{\left(i\right)}\right)$
            \STATE $\theta_j = \theta_j - \alpha \dfrac{\partial J\left(\boldsymbol{\theta}\right)}{\partial \theta_j}$
        \ENDFOR
    \ENDFOR
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>SGD 相比于 BGD 具有训练速度快的优势，但同时由于权重改变的方向并不是全局梯度最大的负方向，甚至相反，因此不能够保证每次损失函数都会减小。</p>
<h2 id="mbgd">MBGD</h2>
<p>针对 BGD 和 SGD 的问题，MBGD 则是一个折中的方案，在每次更新参数时，MBGD 会选取 <code>$b$</code> 个样本计算的梯度，设第 <code>$k$</code> 批中数据的下标的集合为 <code>$B_k$</code>，则其损失函数定义如下：</p>
<p><code>$$ \nabla_{\theta_j} = \dfrac{\partial J\left(\boldsymbol{\theta}\right)}{\partial \theta_j} = - \dfrac{1}{|B_k|} \sum_{i \in B_k}{\left(y^{\left(i\right)} - h_{\boldsymbol{\theta}} \left(x^{\left(i\right)}\right)\right) x_j^{\left(i\right)}} $$</code></p>
<p>整个算法流程可以表示如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{MBGD 算法}
\begin{algorithmic}
\FOR{$epoch = 1, 2, ...$}
    \FOR{$k = 1, 2, ..., m / b$}
        \FOR{$j = 1, 2, ..., n$}
            \STATE $J \left(\boldsymbol{\theta}\right) = \dfrac{1}{|B_k|} \sum_{i \in B_k}{\left(y^{\left(i\right)} - h_{\boldsymbol{\theta}} \left(x^{\left(i\right)}\right)\right) x_j^{\left(i\right)}}$
            \STATE $\theta_j = \theta_j - \alpha \dfrac{\partial J\left(\boldsymbol{\theta}\right)}{\partial \theta_j}$
        \ENDFOR
    \ENDFOR
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<h1 id="momentum">Momentum</h1>
<p>当梯度沿着一个方向要明显比其他方向陡峭，我们可以形象的称之为峡谷形梯度，这种情况多位于局部最优点附近。在这种情况下，SGD 通常会摇摆着通过峡谷的斜坡，这就导致了其到达局部最优值的速度过慢。因此，针对这种情况，Momentum <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> 方法提供了一种解决方案。针对原始的 SGD 算法，参数每 <code>$t$</code> 步的变化量可以表示为</p>
<p><code>$$ \boldsymbol{v}_t = - \alpha \nabla_{\boldsymbol{\theta}} J \left(\boldsymbol{\theta}_t\right) $$</code></p>
<p>Momentum 算法则在其变化量中添加了一个动量分量，即</p>
<p><code>$$ \begin{equation} \begin{split} \boldsymbol{v}_t &amp;= - \alpha \nabla_{\boldsymbol{\theta}} J \left(\boldsymbol{\theta}_t\right) + \gamma \boldsymbol{v}_{t-1} \\ \boldsymbol{\theta}_t &amp;= \boldsymbol{\theta}_{t-1} + \boldsymbol{v}_t \end{split} \end{equation} $$</code></p>
<p>对于添加的动量项，当第 <code>$t$</code> 步和第 <code>$t-1$</code> 步的梯度方向<strong>相同</strong>时，<code>$\boldsymbol{\theta}$</code> 则以更快的速度更新；当第 <code>$t$</code> 步和第 <code>$t-1$</code> 步的梯度方向<strong>相反</strong>时，<code>$\boldsymbol{\theta}$</code> 则以较慢的速度更新。利用 SGD 和 Momentum 两种方法，在峡谷行的二维梯度上更新参数的示意图如下所示</p>
<p><img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/sgd-and-momentum.png" alt=""></p>
<h1 id="nag">NAG</h1>
<p>NAG (Nesterov Accelerated Gradient) <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 是一种 Momentum 算法的变种，其核心思想会利用“下一步的梯度”确定“这一步的梯度”，当然这里“下一步的梯度”并非真正的下一步的梯度，而是指仅根据动量项更新后位置的梯度。Sutskever <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> 给出了一种更新参数的方法：</p>
<p><code>$$ \begin{equation} \begin{split} \boldsymbol{v}_t &amp;= - \alpha \nabla_{\boldsymbol{\theta}} J \left(\boldsymbol{\theta}_t + \gamma \boldsymbol{v}_{t-1}\right) + \gamma \boldsymbol{v}_{t-1} \\ \boldsymbol{\theta}_t &amp;= \boldsymbol{\theta}_{t-1} + \boldsymbol{v}_t \end{split} \end{equation} $$</code></p>
<p>针对 Momentum 和 NAG 两种不同的方法，其更新权重的差异如下图所示：</p>
<p><img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/momentum-and-nag.png" alt=""></p>
<h1 id="adagrad">AdaGrad</h1>
<p>AdaGrad <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> 是一种具有自适应学习率的的方法，其对于低频特征的参数选择更大的更新量，对于高频特征的参数选择更小的更新量。因此，AdaGrad算法更加适用于处理稀疏数据。Pennington 等则利用该方法训练 GloVe <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 词向量，因为对于出现次数较少的词应当获得更大的参数更新。</p>
<p>因为每个参数的学习速率不再一样，则在 <code>$t$</code> 时刻第 <code>$i$</code> 个参数的变化为</p>
<p><code>$$ \theta_{t, i} = \theta_{t-1, i} - \alpha \nabla_{\theta} J \left(\theta_{t-1, i}\right) $$</code></p>
<p>根据 AdaGrad 方法的更新方式，我们对学习率做出如下变化</p>
<p><code>$$ \theta_{t, i} = \theta_{t-1, i} - \dfrac{\alpha}{\sqrt{G_{t, i}} + \epsilon} \nabla_{\theta} J \left(\theta_{t-1, i}\right) $$</code></p>
<p>其中，<code>$G_t$</code> 表示截止到 <code>$t$</code> 时刻梯度的平方和；<code>$\epsilon$</code> 为平滑项，防止除数为零，一般设置为 <code>$10^{-8}$</code>。AdaGrad 最大的优势就在于其能够自动调节每个参数的学习率。</p>
<h1 id="adadelta">Adadelta</h1>
<p>上文中 AdaGrad 算法存在一个缺点，即其用于调节学习率的分母中包含的是一个梯度的平方累加项，随着训练的不断进行，这个值将会越来越大，也就是说学习率将会越来越小，最终导致模型不会再学习到任何知识。Adadelta <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> 方法针对 AdaGrad 的这个问题，做出了进一步改进，其不再计算历史所以梯度的平方和，而是使用一个固定长度 <code>$w$</code> 的滑动窗口内的梯度。</p>
<p>因为存储 <code>$w$</code> 的梯度平方并不高效，Adadelta 采用了一种递归的方式进行计算，定义 <code>$t$</code> 时刻梯度平方的均值为</p>
<p><code>$$ E \left[g^2\right]_t = \rho E \left[g^2\right]_{t-1} + \left(1 - \rho\right) g^2_{t} $$</code></p>
<p>其中，<code>$g_t$</code> 表示 <code>$t$</code> 时刻的梯度；<code>$\rho$</code> 为一个衰减项，类似于 Momentum 中的衰减项。在更新参数过程中我们需要其平方根，即</p>
<p><code>$$ \text{RMS} \left[g\right]_t = \sqrt{E \left[g^2\right]_t + \epsilon} $$</code></p>
<p>则参数的更新量为</p>
<p><code>$$ \Delta \theta_t = - \dfrac{\alpha}{\text{RMS} \left[g\right]_t} g_t $$</code></p>
<p>除此之外，作者还考虑到上述更新中更新量和参数的假设单位不一致的情况，在上述更新公式中添加了一个关于参数的衰减项</p>
<p><code>$$ \text{RMS} \left[\Delta \theta\right]_t = \sqrt{E \left[\Delta \theta^2\right]_t + \epsilon} $$</code></p>
<p>其中</p>
<p><code>$$ E \left[\Delta \theta^2\right]_t = \rho E \left[\Delta \theta^2\right]_{t-1} + \left(1 - \rho\right) \Delta \theta_t^2 $$</code></p>
<p>在原始的论文中，作者直接用 <code>$\text{RMS} \left[\Delta \theta^2\right]_t$</code> 替换了学习率，即</p>
<p><code>$$ \Delta \theta_t = - \dfrac{\text{RMS} \left[\Delta \theta\right]_{t-1}}{\text{RMS} \left[g\right]_t} g_t $$</code></p>
<p>而在 <code>Keras</code> 源码中，则保留了固定的学习率，即</p>
<p><code>$$ \Delta \theta_t = - \alpha \dfrac{\text{RMS} \left[\Delta \theta\right]_{t-1}}{\text{RMS} \left[g\right]_t} g_t $$</code></p>
<h1 id="rmsprop">RMSprop</h1>
<p>RMSprop <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> 是由 Hinton 提出的一种针对 AdaGrad 的改进算法。参数的更新量为</p>
<p><code>$$ \Delta \theta_t = - \dfrac{\alpha}{\text{RMS} \left[g\right]_t} g_t $$</code></p>
<h1 id="adam">Adam</h1>
<p>Adam (Adaptive Moment Estimation) <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> 是另一种类型的自适应学习率方法，类似 Adadelta，Adam 对于每个参数都计算各自的学习率。Adam 方法中包含一个一阶梯度衰减项 <code>$m_t$</code> 和一个二阶梯度衰减项 <code>$v_t$</code></p>
<p><code>$$ \begin{equation} \begin{split} m_t &amp;= \beta_1 m_{t-1} + \left(1 - \beta_1\right) g_t \\ v_t &amp;= \beta_2 v_{t-1} + \left(1 - \beta_2\right) g_t^2 \end{split} \end{equation} $$</code></p>
<p>算法中，<code>$m_t$</code> 和 <code>$v_t$</code> 初始化为零向量，作者发现两者会更加偏向 <code>$0$</code>，尤其是在训练的初始阶段和衰减率很小的时候 (即 <code>$\beta_1$</code> 和 <code>$\beta_2$</code> 趋近于1的时候)。因此，对其偏差做如下校正</p>
<p><code>$$ \begin{equation} \begin{split} \hat{m}_t &amp;= \dfrac{m_t}{1 - \beta_1^t} \\ \hat{v}_t &amp;= \dfrac{v_t}{1 - \beta_2^t} \end{split} \end{equation} $$</code></p>
<p>最终得到 Adam 算法的参数更新量如下</p>
<p><code>$$ \Delta \theta = - \dfrac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t $$</code></p>
<h1 id="adamax">Adamax</h1>
<p>在 Adam 中参数的更新方法利用了 <code>$L_2$</code> 正则形式的历史梯度 (<code>$v_{t-1}$</code>) 和当前梯度 (<code>$|g_t|^2$</code>)，因此，更一般的，我们可以使用 <code>$L_p$</code> 正则形式，即</p>
<p><code>$$ \begin{equation} \begin{split} v_t &amp;= \beta_2^p v_{t-1} + \left(1 - \beta_2^p\right) |g_t|^p \\ &amp;= \left(1 - \beta_2^p\right) \sum_{i=1}^{t} \beta_2^{p\left(t-i\right)} \cdot |g_t|^p \end{split} \end{equation} $$</code></p>
<p>这样的变换对于值较大的 <code>$p$</code> 而言是很不稳定的，但对于极端的情况，当 <code>$p$</code> 趋近于无穷的时候，则变为了一个简单并且稳定的算法。则在 <code>$t$</code> 时刻对应的我们需要计算 <code>$v_t^{1/p}$</code>，令 <code>$u_t = \lim_{p \to \infty} \left(v_t\right)^{1/p}$</code>，则有</p>
<p><code>$$ \begin{equation} \begin{split} u_t &amp;= \lim_{p \to \infty} \left(\left(1 - \beta_2^p\right) \sum_{i=1}^{t} \beta_2^{p\left(t-i\right)} \cdot |g_t|^p\right)^{1/p} \\ &amp;= \lim_{p \to \infty} \left(1 - \beta_2^p\right)^{1/p} \left(\sum_{i=1}^{t} \beta_2^{p\left(t-i\right)} \cdot |g_t|^p\right)^{1/p} \\ &amp;= \lim_{p \to \infty} \left(\sum_{i=1}^{t} \beta_2^{p\left(t-i\right)} \cdot |g_t|^p\right)^{1/p} \\ &amp;= \max \left(\beta_2^{t-1} |g_1|, \beta_2^{t-2} |g_2|, ..., \beta_{t-1} |g_t|\right) \end{split} \end{equation} $$</code></p>
<p>写成递归的形式，则有</p>
<p><code>$$ u_t = \max \left(\beta_2 \cdot u_{t-1}, |g_t|\right) $$</code></p>
<p>则 Adamax 算法的参数更新量为</p>
<p><code>$$ \Delta \theta = - \dfrac{\alpha}{u_t} \hat{m}_t $$</code></p>
<h1 id="nadam">Nadam</h1>
<p>Adam 算法可以看做是对 RMSprop 和 Momentum 的结合：历史平方梯度的衰减项 <code>$v_t$</code> (RMSprop) 和 历史梯度的衰减项 <code>$m_t$</code> (Momentum)。Nadam (Nesterov-accelerated Adaptive Moment Estimation) <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> 则是将 Adam 同 NAG 进行了进一步结合。我们利用 Adam 中的符号重新回顾一下 NAG 算法</p>
<p><code>$$ \begin{equation} \begin{split} g_t &amp;= \nabla_{\theta} J \left(\theta_t - \gamma m_{t-1}\right) \\ m_t &amp;= \gamma m_{t-1} + \alpha g_t \\ \theta_t &amp;= \theta_{t-1} - m_t \end{split} \end{equation} $$</code></p>
<p>NAG 算法的核心思想会利用“下一步的梯度”确定“这一步的梯度”，在 Nadam 算法中，作者在考虑“下一步的梯度”时对 NAG 进行了改动，修改为</p>
<p><code>$$ \begin{equation} \begin{split} g_t &amp;= \nabla_{\theta} J \left(\theta_t\right) \\ m_t &amp;= \gamma m_{t-1} + \alpha g_t \\ \theta_t &amp;= \theta_{t-1} - \left(\gamma m_t + \alpha g_t\right) \end{split} \end{equation} $$</code></p>
<p>对于 Adam，根据</p>
<p><code>$$ \hat{m}_t = \dfrac{\beta_1 m_{t-1}}{1 - \beta_1^t} + \dfrac{\left(1 - \beta_1\right) g_t}{1 - \beta_1^t} $$</code></p>
<p>则有</p>
<p><code>$$ \begin{equation} \begin{split} \Delta \theta &amp;= - \dfrac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t \\ &amp;= - \dfrac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \left(\dfrac{\beta_1 m_{t-1}}{1 - \beta_1^t} + \dfrac{\left(1 - \beta_1\right) g_t}{1 - \beta_1^t}\right) \end{split} \end{equation} $$</code></p>
<p>上式中，仅 <code>$\dfrac{\beta_1 m_{t-1}}{1 - \beta_1^t}$</code> 和动量项相关，因此我们类似上文中对 NAG 的改动，通过简单的替换加入 Nesterov 动量项，最终得到 Nadam 方法的参数的更新量</p>
<p><code>$$ \Delta \theta = - \dfrac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \left(\dfrac{\beta_1 m_{t-1}}{1 - \beta_1^{t+1}} + \dfrac{\left(1 - \beta_1\right) g_t}{1 - \beta_1^t}\right) $$</code></p>
<h1 id="amsgrad">AMSGrad</h1>
<p>对于前面提到的 Adadelta，RMSprop，Adam 和 Nadam 方法，他们均采用了平方梯度的指数平滑平均值迭代产生新的梯度，但根据观察，在一些情况下这些算法并不能收敛到最优解。Reddi 等提出了一种新的 Adam 变体算法 AMSGrad <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>，在文中作者解释了为什么 RMSprop 和 Adam 算法无法收敛到一个最优解的问题。通过分析表明，为了保证得到一个收敛的最优解需要保留过去梯度的“长期记忆”，因此在 AMSGrad 算法中使用了历史平方梯度的最大值而非滑动平均进行更新参数，即</p>
<p><code>$$ \begin{equation} \begin{split} m_t &amp;= \beta_1 m_{t-1} + \left(1 - \beta_1\right) g_t \\ v_t &amp;= \beta_2 v_{t-1} + \left(1 - \beta_2\right) g_t^2 \\ \hat{v}_t &amp;= \max \left(\hat{v}_{t-1}, v_t\right) \\ \Delta \theta &amp;= - \dfrac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} m_t \end{split} \end{equation} $$</code></p>
<p>作者在一些小数据集和 CIFAR-10 数据集上得到了相比于 Adam 更好的效果，但与此同时一些其他的 <a href="https://fdlm.github.io/post/amsgrad/">实验</a> 却得到了相比与 Adam 类似或更差的结果，因此对于 AMSGrad 算法的效果还有待进一步确定。</p>
<h1 id="算法可视化">算法可视化</h1>
<p>正所谓一图胜千言，<a href="https://imgur.com/a/Hqolp">Alec Radford</a> 提供了 2 张图形象了描述了不同优化算法之间的区别</p>
<img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/contours-evaluation-optimizers.gif" style="float: left; width: 50%;" />
<img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/saddle-point-evaluation-optimizers.gif" style="clear: right; width: 50%;" />
<p>左图为 <a href="https://en.wikipedia.org/wiki/Test_functions_for_optimization">Beale Function</a> 在二维平面上的等高线，从图中可以看出 AdaGrad，Adadelta 和 RMSprop 算法很快的找到正确的方向并迅速的收敛到最优解；Momentum 和 NAG 则在初期出现了偏离，但偏离之后调整了方向并收敛到最优解；而 SGD 尽管方向正确，但收敛速度过慢。</p>
<p>右图为包含鞍点的一个三维图像，图像函数为 <code>$z = x^2 - y^2$</code>，从图中可以看出 AdaGrad，Adadelta 和 RMSprop 算法能够相对很快的逃离鞍点，而 Momentum，NAG 和 SGD 则相对比较困难逃离鞍点。</p>
<p>很不幸没能找到 Alec Radford 绘图的原始代码，不过 Louis Tiao 在 <a href="http://louistiao.me/notes/visualizing-and-animating-optimization-algorithms-with-matplotlib/">博客</a> 中给出了绘制类似动图的方法。因此，本文参考该博客和 <code>Keras</code> 源码中对不同优化算法的实现重新绘制了 2 张类似图像，详细过程参见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2018-02-24-optimization-methods-for-deeplearning">源代码</a>，动图如下所示：</p>
<img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/beales-2d-anim.gif" style="float: left; clear: both; width: 50%;" />
<img src="/images/cn/2018-02-24-optimization-methods-for-deeplearning/saddle-3d-anim.gif" style="clear: both; width: 50%;" />
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Ruder, Sebastian. &ldquo;An overview of gradient descent optimization algorithms.&rdquo; <em>arXiv preprint arXiv:1609.04747</em> (2016).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Qian, Ning. &ldquo;On the momentum term in gradient descent learning algorithms.&rdquo; <em>Neural networks</em> 12.1 (1999): 145-151.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Nesterov, Yurii. &ldquo;A method for unconstrained convex minimization problem with the rate of convergence O (1/k^2).&rdquo; <em>Doklady AN USSR.</em> Vol. 269. 1983.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Sutskever, Ilya. &ldquo;Training recurrent neural networks.&rdquo; University of Toronto, Toronto, Ont., Canada (2013).&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Duchi, John, Elad Hazan, and Yoram Singer. &ldquo;Adaptive subgradient methods for online learning and stochastic optimization.&rdquo; <em>Journal of Machine Learning Research</em> 12.Jul (2011): 2121-2159.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Pennington, Jeffrey, Richard Socher, and Christopher Manning. &ldquo;Glove: Global vectors for word representation.&rdquo; <em>Proceedings of the 2014 conference on empirical methods in natural language processing (EMNLP).</em> 2014.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Zeiler, Matthew D. &ldquo;ADADELTA: an adaptive learning rate method.&rdquo; <em>arXiv preprint arXiv:1212.5701</em> (2012).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Hinton, G., Nitish Srivastava, and Kevin Swersky. &ldquo;Rmsprop: Divide the gradient by a running average of its recent magnitude.&rdquo; <em>Neural networks for machine learning, Coursera lecture 6e</em> (2012).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Kingma, Diederik P., and Jimmy Ba. &ldquo;Adam: A method for stochastic optimization.&rdquo; <em>arXiv preprint arXiv:1412.6980</em> (2014).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Dozat, Timothy. &ldquo;Incorporating nesterov momentum into adam.&rdquo; (2016).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Reddi, Sashank J., Satyen Kale, and Sanjiv Kumar. &ldquo;On the convergence of adam and beyond.&rdquo; International Conference on Learning Representations. 2018.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>生成对抗网络简介 (GAN Introduction)</title><link>https://zeqiang.fun/cn/2018/02/gan-introduction/</link><pubDate>Sat, 03 Feb 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/02/gan-introduction/</guid><description><![CDATA[
        <h1 id="generative-adversarial-networks-gan">Generative Adversarial Networks (GAN)</h1>
<p><strong>生成对抗网络</strong> (<strong>Generative Adversarial Network, GAN</strong>) 是由 Goodfellow <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 于 2014 年提出的一种对抗网络。这个网络框架包含两个部分，一个生成模型 (generative model) 和一个判别模型 (discriminative model)。其中，生成模型可以理解为一个伪造者，试图通过构造假的数据骗过判别模型的甄别；判别模型可以理解为一个警察，尽可能甄别数据是来自于真实样本还是伪造者构造的假数据。两个模型都通过不断的学习提高自己的能力，即生成模型希望生成更真的假数据骗过判别模型，而判别模型希望能学习如何更准确的识别生成模型的假数据。</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/zhoubotong.png" alt=""></p>
<h2 id="网络框架">网络框架</h2>
<p>GAN 由两部分构成，一个<strong>生成器</strong> (<strong>Generator</strong>) 和一个<strong>判别器</strong> (<strong>Discriminator</strong>)。对于生成器，我们需要学习关于数据 <code>$\boldsymbol{x}$</code> 的一个分布 <code>$p_g$</code>，首先定义一个输入数据的先验分布 <code>$p_{\boldsymbol{z}} \left(\boldsymbol{z}\right)$</code>，其次定义一个映射 <code>$G \left(\boldsymbol{z}; \theta_g\right): \boldsymbol{z} \to \boldsymbol{x}$</code>。对于判别器，我们则需要定义一个映射 <code>$D \left(\boldsymbol{x}; \theta_d\right)$</code> 用于表示数据 <code>$\boldsymbol{x}$</code> 是来自于真实数据，还是来自于 <code>$p_g$</code>。GAN 的网络框架如下图所示 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>：</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/gan-framework.svg" alt=""></p>
<h2 id="模型训练">模型训练</h2>
<p>Goodfellow 在文献中给出了一个重要的公式用于求解最优的生成器</p>
<p><code>$$ \min_{G} \max_{D} V\left(D, G\right) = \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log D\left(\boldsymbol{x}\right)\right]} + \mathbb{E}_{\boldsymbol{z} \sim p_{\boldsymbol{z}}\left(\boldsymbol{z}\right)}{\left[\log \left(1 - D\left(G\left(\boldsymbol{z}\right)\right)\right)\right]} $$</code></p>
<p>上式中，在给定的 <code>$G$</code> 的情况下，<code>$\max_{D} V\left(G, D\right)$</code>衡量的是 <code>$p_{data}$</code> 和 <code>$p_g$</code> 之间的“区别”，因此我们最终的优化目标就是找到最优的 <code>$G^*$</code> 使得 <code>$p_{data}$</code> 和 <code>$p_g$</code> 之间的“区别”最小。</p>
<p>首先，在给定 <code>$G$</code> 的时候，我们可以通过最大化 <code>$V \left(G, D\right)$</code> 得到最优 <code>$D^*$</code></p>
<p><code>$$ \begin{equation} \begin{split} V \left(G, D\right) &amp;= \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log D\left(\boldsymbol{x}\right)\right]} + \mathbb{E}_{\boldsymbol{z} \sim p_{\boldsymbol{z}}\left(\boldsymbol{z}\right)}{\left[\log \left(1 - D\left(G\left(\boldsymbol{z}\right)\right)\right)\right]} \\ &amp;= \int_{\boldsymbol{x}}{p_{data}\left(\boldsymbol{x}\right) \log D\left(\boldsymbol{x}\right) dx} + \int_{\boldsymbol{z}}{p_{\boldsymbol{z}} \left(\boldsymbol{z}\right) \log \left(1 - D\left(g\left(\boldsymbol{z}\right)\right)\right) dz} \\ &amp;= \int_{\boldsymbol{x}}{p_{data}\left(\boldsymbol{x}\right) \log D\left(\boldsymbol{x}\right) + p_g\left(\boldsymbol{x}\right) \log \left(1 - D\left(\boldsymbol{x}\right)\right) dx} \end{split} \end{equation} $$</code></p>
<p>对于给定的任意 <code>$a, b \in \mathbb{R}^2 \setminus \{0, 0\}$</code>，<code>$a \log\left(x\right) + b \log\left(1 - x\right)$</code>在 <code>$x = \dfrac{a}{a+b}$</code> 处取得最大值，<code>$D$</code> 的最优值为</p>
<p><code>$$ D_{G}^{*} = \dfrac{p_{data} \left(\boldsymbol{x}\right)}{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)} $$</code></p>
<p>因此，<code>$\max_{D} V \left(G, D\right)$</code> 可重写为</p>
<p><code>$$ \begin{equation} \begin{split} &amp;C\left(G\right) \\ =&amp; \max_{D} V \left(G, D\right) = V \left(G, D^*\right) \\ =&amp; \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log D_{G}^{*}\left(\boldsymbol{x}\right)\right]} + \mathbb{E}_{\boldsymbol{z} \sim p_{\boldsymbol{z}}\left(\boldsymbol{z}\right)}{\left[\log \left(1 - D_{G}^{*}\left(G\left(\boldsymbol{z}\right)\right)\right)\right]} \\ =&amp; \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log D_{G}^{*}\left(\boldsymbol{x}\right)\right]} + \mathbb{E}_{\boldsymbol{x} \sim p_g\left(\boldsymbol{x}\right)}{\left[\log \left(1 - D_{G}^{*}\left(\boldsymbol{x}\right)\right)\right]} \\ =&amp; \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log \dfrac{p_{data} \left(\boldsymbol{x}\right)}{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)} \right]} + \mathbb{E}_{\boldsymbol{x} \sim p_g\left(\boldsymbol{x}\right)}{\left[\log  \dfrac{p_g \left(\boldsymbol{x}\right)}{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}\right]} \\ =&amp; \int_{x}{p_{data} \left(\boldsymbol{x}\right) \log \dfrac{\dfrac{1}{2} p_{data} \left(\boldsymbol{x}\right)}{\dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}} dx} + \int_{x}{p_g \left(\boldsymbol{x}\right) \log  \dfrac{\dfrac{1}{2} p_g \left(\boldsymbol{x}\right)}{\dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}} dx} \\ =&amp; \int_{x}{p_{data} \left(\boldsymbol{x}\right) \log \dfrac{p_{data} \left(\boldsymbol{x}\right)}{\dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}} dx} + \int_{x}{p_g \left(\boldsymbol{x}\right) \log  \dfrac{p_g \left(\boldsymbol{x}\right)}{\dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}} dx} + 2 \log \dfrac{1}{2} \\ =&amp; KL \left(p_{data} \left(\boldsymbol{x}\right) \Vert \dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}\right) + KL \left(p_g \left(\boldsymbol{x}\right) \Vert \dfrac{p_{data} \left(\boldsymbol{x}\right) + p_g \left(\boldsymbol{x}\right)}{2}\right) - 2 \log 2 \\ =&amp; 2 JS \left(p_{data} \left(\boldsymbol{x}\right) \Vert p_g \left(\boldsymbol{x}\right) \right) - 2 \log 2 \end{split} \end{equation} $$</code></p>
<p>其中 <code>$KL$</code> 表示 KL 散度 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>，<code>$JS$</code> 表示 JS 散度 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>，因此在全局最优情况下 <code>$p_g = p_{data}$</code>。</p>
<p>整个 GAN 的训练过程如下所示：</p>


<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css">


<div><pre class="pseudocode">
\begin{algorithm}
\caption{Minibatch SGD for GAN 算法}
\begin{algorithmic}
\REQUIRE $iter, k, m$
\ENSURE $\theta_d, \theta_g$
\FOR{$i = 1, 2, ..., iter$}
    \FOR{$j = 1, 2, ..., k$}
        \STATE Sample minibatch of $m$ noise samples $\{z^{\left(1\right)}, ..., z^{\left(m\right)}\}$ from $p_g \left(\boldsymbol{z}\right)$
        \STATE Sample minibatch of $m$ examples $\{x^{\left(1\right)}, ..., x^{\left(m\right)}\}$ from $p_{data} \left(\boldsymbol{z}\right)$
        \STATE $\theta_d \gets \theta_d \textcolor{red}{+} \nabla_{\theta_d} \dfrac{1}{m} \sum_{i=1}^{m}{\left[\log D \left(x^{\left(i\right)}\right) + \log \left(1 - D \left(G \left(z^{\left(i\right)}\right)\right)\right)\right]}$
    \ENDFOR
    \STATE Sample minibatch of $m$ noise samples $\{z^{\left(1\right)}, ..., z^{\left(m\right)}\}$ from $p_g \left(\boldsymbol{z}\right)$
    \STATE $\theta_g \gets \theta_g \textcolor{red}{-} \nabla_{\theta_g} \dfrac{1}{m} \sum_{i=1}^{m}{\log \left(1 - D \left(G \left(z^{\left(i\right)}\right)\right)\right)}$
\ENDFOR
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>在实际的训练过程中，我们通常不会直接训练 <code>$G$</code> <strong>最小化</strong> <code>$\log \left(1 - D \left(G \left(\boldsymbol{z}\right)\right)\right)$</code>，因为其在学习过程中的早起处于饱和状态，因此我们通常会通过<strong>最大化</strong> <code>$\log \left(D \left(G \left(z\right)\right)\right)$</code>。</p>
<h2 id="存在的问题">存在的问题</h2>
<p>针对 GAN，包括 Goodfellow 自己在内也提出了其中包含的很多问题 <sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，因此后人也提出了大量的改进，衍生出了大量的 GAN 变种。本章节仅对原始的 GAN 中存在的问题进行简略介绍，相关的改进请参见后续的具体改进算法。</p>
<h3 id="js-散度问题">JS 散度问题</h3>
<p>我们在训练判别器的时候，其目标是最大化 JS 散度，但 JS 散度真的能够很好的帮助我们训练判别器吗？ Wasserstein GAN 一文 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>给出了不同生成器情况下 JS 散度的变化情况。</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/different-generator-jsd.png" alt=""></p>
<p>上图中，左边为一个基于 MLP 的生成器，右边为一个 DCGAN <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 生成器，两者均有一个 DCGAN 的判别器。根据上文我们可以知道判别器的目标是最大化</p>
<p><code>$$ \begin{equation} \begin{split} L \left(D, \theta_g\right) &amp;= \mathbb{E}_{\boldsymbol{x} \sim p_{data}{\left(\boldsymbol{x}\right)}}{\left[\log D_{G}^{*}\left(\boldsymbol{x}\right)\right]} + \mathbb{E}_{\boldsymbol{x} \sim p_g\left(\boldsymbol{x}\right)}{\left[\log \left(1 - D_{G}^{*}\left(\boldsymbol{x}\right)\right)\right]} \\ &amp;= 2 JS \left(p_{data} \left(\boldsymbol{x}\right) \Vert p_g \left(\boldsymbol{x}\right) \right) - 2 \log 2 \end{split} \end{equation} $$</code></p>
<p>上图中 Y 轴绘制的为 <code>$\dfrac{1}{2} L \left(D, \theta_g\right) + \log 2$</code>，因为 <code>$-2 \log 2 \leq L \left(D, \theta_g\right) \leq 0$</code>，因此我们可得 <code>$0 \leq \dfrac{1}{2} L \left(D, \theta_g\right) + \log 2 \leq \log 2$</code>。从图中我们可以看出，针对两种不同的情况，其值均很快的逼近最大值 <code>$\log 2 \approx 0.69$</code>，当接近最大值的时候，判别器将具有接近于零的损失，此时我们可以发现，尽管 JS 散度很快趋于饱和，但 DCGAN 生成器的效果却仍在不断的变好，因此，使用 JS 散度作为判别其的目标就显得不是很合适。</p>
<h3 id="多样性问题-mode-collapse">多样性问题 Mode Collapse</h3>
<p>Mode Collapse 问题是指生成器更多的是生成了大量相同模式的数据，导致的结果就是生成的数据缺乏多样性，如下图所示 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>:</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/mode-collapse-demo.png" alt=""></p>
<p>不难看出，其中红色方框圈出来的图像十分相似，这样的问题我们就称之为 Mode Collapse。Goolfellow 曾经从不同的 KL 散度的角度解释引起 Mode Collapse 的问题，但最后发现其并非由散度的不同所导致。对于 KL 散度，其并非是对称的，即 <code>$D_{KL} \left(p_{data} \Vert p_{model}\right)$</code> 与 <code>$D_{KL} \left(p_{model} \Vert p_{data}\right)$</code> 是不同的。在最大化似然估计的时候使用的是前者，而在最小化 JS 散度的时候使用的更类似于后者。如下图所示</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/difference-of-kl-distributation.svg" alt=""></p>
<p>假设我们的模型 <code>$q$</code> 并没有足够能能力去拟合真实数据分布 <code>$p$</code>，假设真实数据由两个二维的高斯分布构成，而模型需要使用一个一维的高斯分布去拟合。在左图中，模型更倾向于覆盖两个高斯分布，也就是说其更倾向与在有真实数据的地方得到更大的概率。在右图中，模型更倾向于覆盖其中一个高斯分布，也就是说其更倾向于在没有真实数据的地方取得更小的概率。这样，如果我们用 JS 散度训练模型的时候就容易出现模式缺失的问题，但尽管我们利用前者去优化模型，但结果中仍然出现了 Mode Collapse 的问题，这也就说明并非 JS 散度问题导致的 Mode Collapse。</p>
<p>针对 Mode Collapse 的问题，出现了大量不同角度的优化</p>
<ul>
<li>基于正则化的优化 <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup></li>
<li>基于 Minibatch 的优化 <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup></li>
<li>基于 Unrolled Optimization 的优化 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup></li>
<li>基于集成算法的优化 <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup></li>
</ul>
<h2 id="mnist-示例">MNIST 示例</h2>
<p>我们利用 MNIST 数据集测试原始的 GAN 模型的效果，代码主要参考了 <a href="https://github.com/eriklindernoren/Keras-GAN"><code>Keras-GAN</code></a>，最终实现代码详见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2018-02-03-gan-introduction/image_gan_keras.py"><code>image_gan_keras.py</code></a>，我们简单对其核心部分进行说明。</p>
<ul>
<li>
<p>生成器</p>
<pre><code class="language-python">def build_generator(self):
    model = Sequential()

    model.add(Dense(int(self._hidden_dim / 4),
                        input_shape=self._noise_shape))
    model.add(LeakyReLU(alpha=0.2))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Dense(int(self._hidden_dim / 2)))
    model.add(LeakyReLU(alpha=0.2))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Dense(self._hidden_dim))
    model.add(LeakyReLU(alpha=0.2))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Dense(np.prod(self._input_shape), activation='tanh'))
    model.add(Reshape(self._input_shape))

    print('Generator Summary: ')
    model.summary()

    noise = Input(shape=self._noise_shape)
    image = model(noise)

    return Model(noise, image)
</code></pre>
</li>
</ul>
<p>在生成器中，我们使用了一个包含3个隐含层的全链接网络，其中 <code>self._hidden_dim</code> 是我们定义的隐含节点最多一层的节点数；<code>self._noise_shape</code> 为用于生成器的噪音数据的形状；<code>self._input_shape</code> 为输入数据形状，即图片数据的形状，中间层次采用的激活函数为 <code>LeakyReLU</code>，最后一层采用的激活函数为 <code>tanh</code>。</p>
<ul>
<li>
<p>判别器</p>
<pre><code class="language-python">def build_discriminator(self):
    model = Sequential()

    model.add(Flatten(input_shape=self._input_shape))
    model.add(Dense(int(self._hidden_dim / 2)))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dense(int(self._hidden_dim / 4)))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dense(1, activation='sigmoid'))

    print('Discriminator Summary: ')
    model.summary()

    image = Input(shape=self._input_shape)
    label = model(image)

    return Model(image, label)
</code></pre>
</li>
</ul>
<p>在判别器中，我们使用了一个包含2个隐含层的全链接网络，中间层次采用的激活函数为 <code>LeakyReLU</code>，最后一层采用的激活函数为 <code>sigmoid</code>。</p>
<ul>
<li>
<p>对抗网络</p>
<pre><code class="language-python">class ImageBasicGAN():
    def __init__(self, width, height, channels,
                 a_optimizer=Adam(1e-4, beta_1=0.5),
                 g_optimizer=Adam(1e-4, beta_1=0.5),
                 d_optimizer=Adam(1e-4, beta_1=0.5),
                 noise_dim=100, hidden_dim=1024):
        '''

        Args:
            width: 图像宽度
            height: 图像高度
            channels: 图像颜色通道数
            a_optimizer: 对抗网络优化器
            g_optimizer: 生成器优化器
            d_optimizer: 判别器优化器
            noise_dim: 噪音数据维度
            hidden_dim: 隐含层最大维度
        '''

        # 省略一大坨代码

        # 构建和编译判别器
        self._discriminator = self.build_discriminator()
        self._discriminator.compile(loss='binary_crossentropy',
                                    optimizer=d_optimizer,
                                    metrics=['accuracy'])

        # 构建和编译生成器
        self._generator = self.build_generator()
        self._generator.compile(loss='binary_crossentropy',
                                optimizer=g_optimizer)

        # 生成器利用噪声数据作为输入
        noise = Input(shape=self._noise_shape)
        generated_image = self._generator(noise)

        # 当训练整个对抗网络时，仅训练生成器
        self._discriminator.trainable = False

        # 判别器将生成的图像作为输入
        label = self._discriminator(generated_image)

        # 构建和编译整个对抗网络
        self._adversarial = Model(noise, label)
        self._adversarial.compile(loss='binary_crossentropy',
                                  optimizer=a_optimizer)
</code></pre>
</li>
</ul>
<p>在构造整个对抗网络的时候，需要注意我们训练完判别器后，通过训练整个对抗网络进而训练生成器的时候是固定住训练好的判别器的，因此在训练整个对抗网络的时候我们应该将判别器置为无需训练的状态。</p>
<ul>
<li>
<p>训练过程</p>
<pre><code class="language-python">def train(self, x_train, output_dir, iters,
          batch_size=32, k=1, save_interval=200):
    ''' 训练模型

    Args:
        x_train: 训练数据
        output_dir: 相关输出路径
        iters: 迭代次数
        batch_size: 批大小
        k: K
        save_interval: 结果保存间隔
    '''

    # 省略一大坨代码

    for iter in range(iters):
        # 训练判别器
        for _ in range(k):
            train_indices = np.random.randint(0, x_train.shape[0],
                                              batch_size)
            train_images = x_train[train_indices]

            noises = np.random.normal(0, 1, (batch_size, self._noise_dim))
            generated_images = self._generator.predict(noises)

            self._discriminator.train_on_batch(train_images,
                                               np.ones((batch_size, 1)))
            self._discriminator.train_on_batch(generated_images,
                                               np.zeros((batch_size, 1)))

        # 训练生成器
        noises = np.random.normal(0, 1, (batch_size, self._noise_dim))
        labels = np.ones(batch_size)

        self._adversarial.train_on_batch(noises, labels)

    # 再省略一大坨代码
</code></pre>
</li>
</ul>
<p>在训练整个对抗网络的时候，我们对于一个给定的生成器，我们将生成器生成的数据作为负样本，将从真实数据中采样的数据作为正样本训练判别器。Goodfellow 在描述 GAN 训练的过程中，对于给定的生成器，训练判别器 <code>$k$</code> 次，不过通常取 <code>$k = 1$</code>。训练好判别器后，再随机生成噪音数据用于训练生成器，周而复始直至达到最大迭代次数。</p>
<p>在整个训练过程中，我们分别记录了判别器和生成器的损失的变化，以及判别器的准确率的变化，如下图所示：</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/mnist-gan-keras-train-history.png" alt=""></p>
<p>从上图中我们可以看出，在训练开始阶段，判别器能够相对容易的识别出哪些数据是来自于真实数据的采样，哪些数据是来自于生成器的伪造数据。随着训练的不断进行，判别器的准确率逐渐下降，并稳定在 60% 左右，也就是说生成器伪造的数据越来越像真实的数据，判别器越来越难进行甄别。</p>
<p>下图中我们展示了利用 MNIST 数据集，进行 30000 次的迭代，每 1000 次截取 100 张生成器利用相同噪音数据伪造的图像，最后合成的一张生成图片的变化动图。</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/mnist-gan-generated-images.gif" alt=""></p>
<h1 id="deep-convolutional-gan">Deep Convolutional GAN</h1>
<p>DCGAN (Deep Convolutional GAN) 是由 Radford <sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> 等人提出的一种对原始 GAN 的变种，其基本的思想就是将原始 GAN 中的全链接层用卷积神经网络代替。在文中，Radford 等人给出构建一个稳定的 DCGAN 的建议，如下：</p>
<ul>
<li>在网络中不使用 pooling 层，而是使用多步长的卷积层 (判别器) 和多步长的反卷积层 (生成器)。</li>
<li>在生成器和判别器中均使用批标准化。</li>
<li>对于深层的框架，去掉全链接层。</li>
<li>在生成器中使用 ReLU 激活函数，最后一层使用 Tanh 激活函数。</li>
<li>在判别器中使用 LeakyReLU 激活函数。</li>
</ul>
<p>我们利用 MNIST 数据集测试 DCGAN 模型的效果，最终实现代码详见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2018-02-03-gan-introduction/image_dcgan_keras.py"><code>image_dcgan_keras.py</code></a>。训练过程中判别器和生成器的损失的变化，以及判别器的准确率的变化，如下图所示：</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/mnist-dcgan-keras-train-history.png" alt=""></p>
<p>下图中我们展示了利用 MNIST 数据集，进行 30000 次的迭代，每 1000 次截取 100 张生成器利用相同噪音数据伪造的图像，最后合成的一张生成图片的变化动图。</p>
<p><img src="/images/cn/2018-02-03-gan-introduction/mnist-dcgan-generated-images.gif" alt=""></p>
<p>从生成的结果中可以看出，DCGAN 生成的图片的质量还是优于原始的 GAN 的，在原始的 GAN 中我们能够明显的看出其中仍旧包含大量的噪音点，而在 DCGAN 中这种情况几乎不存在了。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Goodfellow, Ian, et al. &ldquo;Generative adversarial nets.&rdquo; <em>Advances in neural information processing systems</em>. 2014.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Goodfellow, Ian. &ldquo;NIPS 2016 tutorial: Generative adversarial networks.&rdquo; <em>arXiv preprint arXiv:1701.00160</em> (2016).&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://en.wikipedia.org/wiki/Kullback">https://en.wikipedia.org/wiki/Kullback</a>–Leibler_divergence&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://en.wikipedia.org/wiki/Jensen">https://en.wikipedia.org/wiki/Jensen</a>–Shannon_divergence&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Arjovsky, Martin, Soumith Chintala, and Léon Bottou. &ldquo;Wasserstein gan.&rdquo; <em>arXiv preprint arXiv:1701.07875</em> (2017).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Radford, Alec, Luke Metz, and Soumith Chintala. &ldquo;Unsupervised representation learning with deep convolutional generative adversarial networks.&rdquo; <em>arXiv preprint arXiv:1511.06434</em> (2015).&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="http://speech.ee.ntu.edu.tw/~tlkagk/courses/MLDS_2017/Lecture/GAN%20(v11).pdf">http://speech.ee.ntu.edu.tw/~tlkagk/courses/MLDS_2017/Lecture/GAN%20(v11).pdf</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Che, Tong, et al. &ldquo;Mode regularized generative adversarial networks.&rdquo; <em>arXiv preprint arXiv:1612.02136</em> (2016).&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Salimans, Tim, et al. &ldquo;Improved techniques for training gans.&rdquo; <em>Advances in Neural Information Processing Systems.</em> 2016.&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Metz, Luke, et al. &ldquo;Unrolled generative adversarial networks.&rdquo; <em>arXiv preprint arXiv:1611.02163</em> (2016).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Tolstikhin, Ilya O., et al. &ldquo;Adagan: Boosting generative models.&rdquo; <em>Advances in Neural Information Processing Systems.</em> 2017.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item></channel></rss>