-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
127 lines (61 loc) · 126 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>文本分类的深度学习方法【二】</title>
<link href="/2021/08/31/text_classification2/"/>
<url>/2021/08/31/text_classification2/</url>
<content type="html"><![CDATA[<h1 id="DPCNN-😊"><a href="#DPCNN-😊" class="headerlink" title="DPCNN 😊"></a>DPCNN 😊</h1><blockquote><p>论文:<a href="https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf">https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf</a><br>代码:<a href="https://github.com/649453932/Chinese-Text-Classification-Pytorch">https://github.com/649453932/Chinese-Text-Classification-Pytorch</a></p></blockquote><p><code>ACL</code> 2017 年,腾讯 <code>AI-lab</code> 提出了 <code>Deep Pyramid Convolutional Neural Networks for Text Categorization(DPCNN)</code>,由于 <code>TextCNN</code> 不能通过卷积获得文本的长距离依赖关系,而论文中 <code>DPCNN</code> 通过不断加深网络,可以抽取长距离的文本依赖关系。实验证明在不增加太多计算成本的情况下,增加网络深度就可以获得最佳的准确率。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5cdd94fd35d64417a0cd2cd0b0b6116f~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="Region-embedding"><a href="#Region-embedding" class="headerlink" title="Region embedding"></a>Region embedding</h2><p><code>TextCNN</code> 的包含多尺寸卷积滤波器的卷积层的卷积结果称之为 <code>Region embedding</code>,意思就是对一个文本区域/片段(比如<code>3-gram</code>)进行一组卷积操作后生成的 <code>embedding</code>。</p><h2 id="卷积和全连接的权衡"><a href="#卷积和全连接的权衡" class="headerlink" title="卷积和全连接的权衡"></a>卷积和全连接的权衡</h2><p>产生 <code>region embedding</code> 后,按照经典的 <code>TextCNN</code> 的做法的话,就是从每个特征图中挑选出最有代表性的特征,也就是直接应用全局最大池化层,这样就生成了这段文本的特征向量,假如卷积滤波器的 <code>size</code> 有 <code>(3,4,5)</code> 这三种,每种 <code>size</code> 包含 $100$ 个卷积核,那么当然就会产生 $3*100$ 幅特征图,然后将<code>max-over-time-pooling</code> 操作应用到每个特征图上,于是文本的特征向量即 $3 \times 100=300$ 维。</p><p><code>TextCNN</code> 这样做的意义本质上与词袋模型的经典文本分类模型没本质区别,只不过 <code>one-hot</code> 到 <code>word embedding</code> 表示的转变避免了词袋模型遭遇的数据稀疏问题。<code>TextCNN</code> 本质上收益于词向量的引入带来的近义词有相近向量表示的 <code>bonus</code>,同时 <code>TextCNN</code> 可以较好的利用词向量中近义关系。</p><blockquote><p>但是文本中的远距离信息在 <code>TextCNN</code> 中依然难以学习。</p></blockquote><h2 id="等长卷积"><a href="#等长卷积" class="headerlink" title="等长卷积"></a>等长卷积</h2><p>假设输入的序列长度为 $n$,卷积核大小为 $m$,步长为 $s$,输入序列两端各填补 $p$ 个零,那么该卷积层的输出序列为 $\frac{(n-m+2p)}{s}+1$。</p><ul><li><p>窄卷积🍊:步长 $s=1$ ,两端不补零,即 $p=0$,卷积后输出长度为 $n-m+1$。</p></li><li><p>宽卷积🍊:步长 $s=1$,两端补零 $p=m-1$,卷积后输出长度 $n+m-1$。</p></li><li><p>等长卷积🍊: 步长 $s=1$,两端补零 $p=(m-1)/2$,卷积后输出长度为 $n$。</p></li></ul><p>我们将输入输出序列的第 $n$ 个 <code>embedding</code> 称为第 $n$ 个词位,那么这时 <code>size=n</code> 的卷积核产生的等长卷积的意义就是将输入序列的每个词位及其左右 $\frac{n-1}{2}$ 个词的上下文信息压缩为该词位的 <code>embedding</code>,产生了每个词位的被上下文信息修饰过的更高 <code>level</code>、更加准确的语义。想要克服 <code>TextCNN</code> 的缺点,捕获长距离模式,显然就要用到深层 <code>CNN</code>。</p><p>直接等长卷积堆等长卷积会让每个词位包含进去越来越多,越来越长的上下文信息,这种方式会让网络层数变得非常非常非常深,但是这种方式太笨重。不过,既然等长卷积堆等长卷积会让每个词位的<code>embedding</code> 描述语义描述的更加丰富准确,可以适当的堆两层来提高词位 <code>embedding</code>的表示的丰富性。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/304e0f83debe45fab2883e8a29f92712~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="固定-feature-map-的数量"><a href="#固定-feature-map-的数量" class="headerlink" title="固定 feature map 的数量"></a>固定 feature map 的数量</h2><p>在表示好每个词位的语义后,很多邻接词或者邻接 <code>ngram</code> 的词义是可以合并,例如 “小明 人 不要 太好” 中的 “不要” 和 “太好” 虽然语义本来离得很远,但是作为邻接词“不要太好”出现时其语义基本等价为“很好”,完全可以把“不要”和“太好”的语义进行合并。同时,合并的过程完全可以在原始的 <code>embedding space</code> 中进行的,原文中直接把“不要太好”合并为“很好”是很可以的,完全没有必要动整个语义空间。</p><p>实际上,相比图像中这种从“点、线、弧”这种 <code>low-level</code> 特征到“眼睛、鼻子、嘴”这种 <code>high-level</code> 特征的明显层次性的特征区分,文本中的特征进阶明显要扁平的多,即从单词(<code>1gram</code>)到短语再到 <code>3gram</code>、<code>4gram</code> 的升级,其实很大程度上均满足“语义取代”的特性。而图像中就很难发生这种“语义取代”现象。因此,<code>DPCNN</code> 与 <code>ResNet</code> 很大一个不同就是,<strong>在 <code>DPCNN</code> 中固定死了 <code>feature map</code> 的数量</strong>,也就是固定住了 <code>embedding space</code> 的维度(为了方便理解,以下简称语义空间),使得网络有可能让整个邻接词(邻接 <code>ngram</code>)的合并操作在原始空间或者与原始空间相似的空间中进行(当然,网络在实际中会不会这样做是不一定的,只是提供了这么一种条件)。也就是说,整个网络虽然形状上来看是深层的,但是从语义空间上来看完全可以是扁平的。</p><h2 id="池化"><a href="#池化" class="headerlink" title="池化"></a>池化</h2><p>每经过一个 $size=3,stride=2$ 的池化层(简称 $1/2$ 池化层),序列的长度就被压缩成了原来的一半。这样同样是 $size=3$ 的卷积核,每经过一个 $1/2$ 池化层后,其能感知到的文本片段就比之前长了一倍。例如之前是只能感知 $3$ 个词位长度的信息,经过 $1/2$ 池化层后就能感知 $6$ 个词位长度的信息,这时把 $1/2$ 池化层和 <code>size=3</code> 的卷积层组合起来如图:</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1b697f3e79d34f8382862416a0495de2~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="残差连接"><a href="#残差连接" class="headerlink" title="残差连接"></a>残差连接</h2><p>在初始化深度 <code>CNN</code> 时,往往各层权重都是初始化为一个很小的值,这就导致最开始的网络中,后续几乎每层的输入都是接近 $0$,这时网络的输出自然是没意义的,而这些小权重同时也阻碍了梯度的传播,使得网络的初始训练阶段往往要迭代很久才能启动。同时,就算网络启动完成,由于深度网络中仿射矩阵近似连乘,训练过程中网络也非常容易发生梯度爆炸或弥散问题(虽然由于非共享权重,深度<code>CNN</code> 网络比 <code>RNN</code> 网络要好点)。针对深度 <code>CNN</code> 网络的梯度弥散问题 <code>ResNet</code> 中提出的<code>shortcut-connection\skip-connection\residual-connection</code>(残差连接)就是一种非常简单、合理、有效的解决方案。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6b32d4001dd4a9db3cf71cb2256ae2a~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="模型结构和代码"><a href="#模型结构和代码" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DPCNN</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> config.vocab_size:词表大小</span></span><br><span class="line"><span class="string"> config.embedding_size:词向量维度</span></span><br><span class="line"><span class="string"> config.num_labels: 类别数</span></span><br><span class="line"><span class="string"> config.n_filters: 卷积核个数(对应2维卷积的通道数)</span></span><br><span class="line"><span class="string"> config.filter_sizes: 卷积核的尺寸(3,4,5)</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">super</span>(DPCNN, self).__init__()</span><br><span class="line"> self.embedding_size = config.embedding_size</span><br><span class="line"> self.embedding = nn.Embedding(config.vocab_size, config.embedding_size)</span><br><span class="line"></span><br><span class="line"> self.conv_region = nn.Conv2d(<span class="number">1</span>, config.n_filters, (<span class="number">3</span>, self.embedding_size), stride=<span class="number">1</span>)</span><br><span class="line"> self.conv = nn.Conv2d(config.n_filters, config.n_filters, (<span class="number">3</span>, <span class="number">1</span>), stride=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"> self.max_pool = nn.MaxPool2d(kernel_size=(<span class="number">3</span>, <span class="number">1</span>), stride=<span class="number">2</span>)</span><br><span class="line"> self.padding1 = nn.ZeroPad2d((<span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span>)) <span class="comment"># top bottom</span></span><br><span class="line"> self.padding2 = nn.ZeroPad2d((<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>)) <span class="comment"># bottom</span></span><br><span class="line"></span><br><span class="line"> self.relu = nn.ReLU()</span><br><span class="line"> self.fc = nn.Linear(config.n_filters, config.num_labels)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, input_ids</span>):</span></span><br><span class="line"> <span class="comment"># [batch size, seq len, emb dim]</span></span><br><span class="line"> x = self.embedding(input_ids) </span><br><span class="line"> <span class="comment"># [batch size, 1, seq len, emb dim]</span></span><br><span class="line"> x = x.unsqueeze(<span class="number">1</span>).to(torch.float32) </span><br><span class="line"> <span class="comment"># [batch size, n_filters, seq len-3+1, 1]</span></span><br><span class="line"> x = self.conv_region(x) </span><br><span class="line"> <span class="comment"># [batch size, n_filters, seq len, 1]</span></span><br><span class="line"> x = self.padding1(x) </span><br><span class="line"> x = self.relu(x)</span><br><span class="line"> <span class="comment"># [batch size, n_filters, seq len-3+1, 1]</span></span><br><span class="line"> x = self.conv(x) </span><br><span class="line"> <span class="comment"># [batch size, n_filters, seq len, 1]</span></span><br><span class="line"> x = self.padding1(x) </span><br><span class="line"> x = self.relu(x)</span><br><span class="line"> <span class="comment"># [batch size, n_filters, seq len-3+1, 1]</span></span><br><span class="line"> x = self.conv(x)</span><br><span class="line"> <span class="comment"># [batch size, n_filters, 1, 1]</span></span><br><span class="line"> <span class="keyword">while</span> x.size()[<span class="number">2</span>] >= <span class="number">2</span>:</span><br><span class="line"> x = self._block(x) </span><br><span class="line"> <span class="comment"># [batch size, n_filters]</span></span><br><span class="line"> x_embedding = x.squeeze() </span><br><span class="line"> <span class="comment"># [batch_size, 1]</span></span><br><span class="line"> x = self.fc(x_embedding) </span><br><span class="line"> <span class="keyword">return</span> x</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_block</span>(<span class="params">self, x</span>):</span></span><br><span class="line"> x = self.padding2(x)</span><br><span class="line"> px = self.max_pool(x)</span><br><span class="line"></span><br><span class="line"> x = self.padding1(px)</span><br><span class="line"> x = F.relu(x)</span><br><span class="line"> x = self.conv(x)</span><br><span class="line"></span><br><span class="line"> x = self.padding1(x)</span><br><span class="line"> x = F.relu(x)</span><br><span class="line"> x = self.conv(x)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Short Cut</span></span><br><span class="line"> x = x + px</span><br><span class="line"> <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure><h1 id="TextRCNN-😊"><a href="#TextRCNN-😊" class="headerlink" title="TextRCNN 😊"></a>TextRCNN 😊</h1><blockquote><p>论文:<a href="https://dl.acm.org/doi/10.5555/2886521.2886636">https://dl.acm.org/doi/10.5555/2886521.2886636</a><br>代码:<a href="https://github.com/649453932/Chinese-Text-Classification-Pytorch">https://github.com/649453932/Chinese-Text-Classification-Pytorch</a></p></blockquote><p><code>RNN</code> 和 <code>CNN</code> 作为文本分类问题的主要模型架构,都存在各自的优点及局限性。</p><ul><li><p><code>RNN</code> 擅长处理序列结构,能够考虑到句子的上下文信息,但 <code>RNN</code> 属于 <code>biased model</code>,一个句子中越往后的词重要性越高,这有可能影响最后的分类结果,因为对句子分类影响最大的词可能处在句子任何位置。</p></li><li><p><code>CNN</code> 属于无偏模型,能够通过最大池化获得最重要的特征,但是 <code>CNN</code> 的滑动窗口大小不容易确定,选的过小容易造成重要信息丢失,选的过大会造成巨大参数空间。</p></li></ul><p>为了解决二者的局限性,这篇文章提出了一种新的网络架构,用双向循环结构获取上下文信息,这比传统的基于窗口的神经网络更能减少噪声,而且在学习文本表达时可以大范围的保留词序。其次使用最大池化层获取文本的重要部分,自动判断哪个特征在文本分类过程中起更重要的作用。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/396b3ac302114add92c8edecf7770060~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="单词表示学习"><a href="#单词表示学习" class="headerlink" title="单词表示学习"></a>单词表示学习</h2><p>作者提出将单词的左上下文、右上下文、单词本身结合起来作为单词表示。作者使用了双向 <code>RNN</code> 来分别提取句子的上下文信息。公式如下:</p><script type="math/tex; mode=display">\begin{array}{l}c_{l}\left(w_{i}\right)=f\left(W^{(l)} c_{l}\left(w_{i-1}\right)+W^{(s l)} e\left(w_{i-1}\right)\right) \\c_{r}\left(w_{i}\right)=f\left(W^{(r)} c_{r}\left(w_{i+1}\right)+W^{(s r)} e\left(w_{i+1}\right)\right)\end{array}</script><p>其中,$c_l(w_i)$ 代表单词 $w_i$ 的左上下文,$c_l(w_i)$ 由上一个单词的左上下文 $c_l(w_{i-1})$ 和 上一个单词的词嵌入向量 $e(w_{i-1})$ 计算得到,所有句子第一个单词的左侧上下文使用相同的共享参数 $c_l(w_1)$。</p><p>$W^{(l)},W^{(sl)}$ 用于将上一个单词的左上下文语义和上一个单词的语义结合到单词 $w_i$ 的左上下文表示中。右上下文的处理与左上下文完全相同,同样所有句子最后一个单词的右侧上下文使用相同的共享参数 $c_r(w_n)$。 得到句子中每个单词的左上下文表示和右上下文表示后,就可以定义单词 $w_i$ 的表示如下</p><script type="math/tex; mode=display">\boldsymbol{x}_{i}=\left[\boldsymbol{c}_{l}\left(w_{i}\right) ; \boldsymbol{e}\left(w_{i}\right) ; \boldsymbol{c}_{r}\left(w_{i}\right)\right]</script><p>实际就是单词$w_i$,单词的词嵌入表示向量 $e(w_i)$ 以及单词的右上下文向量 $c_e(w_i)$ 的拼接后的结果。得到 $w_i$ 的表示$x_i$后,就可以输入激活函数得到$w_i$的潜在语义向量 $y_i^{(2)}$ 。</p><script type="math/tex; mode=display">\boldsymbol{y}_{i}^{(2)}=\tanh \left(W^{(2)} \boldsymbol{x}_{i}+\boldsymbol{b}^{(2)}\right)</script><h2 id="文本表示学习"><a href="#文本表示学习" class="headerlink" title="文本表示学习"></a>文本表示学习</h2><p>经过卷积层后,获得了所有词的表示,首先对其进行最大池化操作,最大池化可以帮助找到句子中最重要的潜在语义信息。</p><script type="math/tex; mode=display">\boldsymbol{y}^{(3)}=\max _{i=1}^{n} \boldsymbol{y}_{i}^{(2)}</script><p>然后经过全连接层得到文本的表示,最后通过 <code>softmax</code> 层进行分类。</p><script type="math/tex; mode=display">\begin{aligned}&\boldsymbol{y}^{(4)}=W^{(4)} \boldsymbol{y}^{(3)}+\boldsymbol{b}^{(4)}\\&p_{i}=\frac{\exp \left(\boldsymbol{y}_{i}^{(4)}\right)}{\sum_{k=1}^{n} \exp \left(\boldsymbol{y}_{k}^{(4)}\right)}\end{aligned}</script><h2 id="模型结构和代码-1"><a href="#模型结构和代码-1" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RCNN</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> config.vocab_size:词表大小</span></span><br><span class="line"><span class="string"> config.embedding_size:词向量维度</span></span><br><span class="line"><span class="string"> config.num_labels: 类别数</span></span><br><span class="line"><span class="string"> config.hidden_dim: rnn隐藏层的维度</span></span><br><span class="line"><span class="string"> config.n_layers: rnn层数</span></span><br><span class="line"><span class="string"> config.rnn_type: rnn类型,包括['lstm', 'gru', 'rnn']</span></span><br><span class="line"><span class="string"> config.bidirectional: 是否双向</span></span><br><span class="line"><span class="string"> config.dropout: dropout率</span></span><br><span class="line"><span class="string"> config.batch_first: 第一个维度是否是批量大小</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">super</span>(RCNN, self).__init__()</span><br><span class="line"> word_embedding = word_embedding</span><br><span class="line"> self.embedding_size = config.embedding_size</span><br><span class="line"> self.embedding = nn.Embedding(config.vocab_size, config.embedding_size)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> config.rnn_type == <span class="string">'lstm'</span>:</span><br><span class="line"> self.rnn = nn.LSTM(self.embedding_size,</span><br><span class="line"> config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> <span class="keyword">elif</span> config.rnn_type == <span class="string">'gru'</span>:</span><br><span class="line"> self.rnn = nn.GRU(self.embedding_size,</span><br><span class="line"> hidden_size=config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.rnn = nn.RNN(self.embedding_size,</span><br><span class="line"> hidden_size=config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 1 x 1 卷积等价于全连接层,故此处使用全连接层代替</span></span><br><span class="line"> self.fc_cat = nn.Linear(config.hidden_dim * <span class="number">2</span> + self.embedding_size, self.embedding_size)</span><br><span class="line"> self.fc = nn.Linear(self.embedding_size, config.num_labels)</span><br><span class="line"></span><br><span class="line"> self.dropout = nn.Dropout(config.dropout)</span><br><span class="line"> self.batch_first = config.batch_first</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, text, text_lengths</span>):</span></span><br><span class="line"> <span class="comment"># 按照句子长度从大到小排序</span></span><br><span class="line"> text, sorted_seq_lengths, desorted_indices = self.prepare_pack_padded_sequence(text, text_lengths)</span><br><span class="line"> <span class="comment"># text = [batch size, sent len]</span></span><br><span class="line"> embedded = self.dropout(self.embedding(text)).<span class="built_in">float</span>()</span><br><span class="line"> <span class="comment"># embedded = [batch size, sent len, emb dim]</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># pack sequence</span></span><br><span class="line"> packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, sorted_seq_lengths, batch_first=self.batch_first)</span><br><span class="line"> self.rnn.flatten_parameters()</span><br><span class="line"> <span class="keyword">if</span> config.rnn_type <span class="keyword">in</span> [<span class="string">'rnn'</span>, <span class="string">'gru'</span>]:</span><br><span class="line"> packed_output, hidden = self.rnn(packed_embedded)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># output (batch, seq_len, num_directions * hidden_dim)</span></span><br><span class="line"> <span class="comment"># hidden (batch, num_layers * num_directions, hidden_dim)</span></span><br><span class="line"> packed_output, (hidden, cell) = self.rnn(packed_embedded)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># unpack sequence</span></span><br><span class="line"> output, output_lengths = nn.utils.rnn.pad_packed_sequence(packed_output, batch_first=self.batch_first)</span><br><span class="line"> <span class="comment"># 把句子序列再调整成输入时的顺序</span></span><br><span class="line"> output = output[desorted_indices]</span><br><span class="line"> <span class="comment"># output = [batch_size, seq_len, hidden_dim * num_directionns ]</span></span><br><span class="line"></span><br><span class="line"> batch_size, max_seq_len, hidden_dim = output.shape</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 拼接左右上下文信息</span></span><br><span class="line"> output = torch.tanh(self.fc_cat(torch.cat((output, embedded), dim=<span class="number">2</span>)))</span><br><span class="line"> <span class="comment"># output = [batch_size, seq_len, embedding_size]</span></span><br><span class="line"></span><br><span class="line"> output = torch.transpose(output, <span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line"> output = F.max_pool1d(output, <span class="built_in">int</span>(max_seq_len)).squeeze().contiguous()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> self.fc(output)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">prepare_pack_padded_sequence</span>(<span class="params">self, inputs_words, seq_lengths, descending=<span class="literal">True</span></span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> for rnn model :按照句子长度从大到小排序</span></span><br><span class="line"><span class="string"> </span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> sorted_seq_lengths, indices = torch.sort(seq_lengths, descending=descending)</span><br><span class="line"> _, desorted_indices = torch.sort(indices, descending=<span class="literal">False</span>)</span><br><span class="line"> sorted_inputs_words = inputs_words[indices]</span><br><span class="line"> <span class="keyword">return</span> sorted_inputs_words, sorted_seq_lengths, desorted_indices</span><br></pre></td></tr></table></figure><h1 id="HAN-😊"><a href="#HAN-😊" class="headerlink" title="HAN 😊"></a>HAN 😊</h1><blockquote><p>论文:<a href="https://www.aclweb.org/anthology/N16-1174.pdf">https://www.aclweb.org/anthology/N16-1174.pdf</a><br>代码:<a href="https://github.com/richliao/textClassifier">https://github.com/richliao/textClassifier</a></p></blockquote><p>上文都是句子级别的分类,虽然用到长文本、篇章级也是可以的,但速度精度都会下降,于是有研究者提出了层次注意力分类框架,即<code>Hierarchical Attention</code>。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01f2bd3e66f440e59b9445002b141090~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>整个网络结构包括五个部分:</p><ol><li><p>词序列编码器</p></li><li><p>基于词级的注意力层</p></li><li><p>句子编码器</p></li><li><p>基于句子级的注意力层</p></li><li><p>分类层</p></li></ol><p>整个网络结构由双向 <code>GRU</code> 网络和注意力机制组合而成。</p><h2 id="词序编码器"><a href="#词序编码器" class="headerlink" title="词序编码器"></a>词序编码器</h2><p>给定一个句子中的单词 $w_{it}$,其中 $i$ 表示第 $i$ 个句子,$t$ 表示第 $t$ 个词。通过一个词嵌入矩阵 $W_e$ 将单词转换成向量表示,具体如下所示: </p><script type="math/tex; mode=display">x_{it}=W_e w_{it}</script><p>将获取的词向量输入词编码器,即一个双向 <code>GRU</code>,将两个方向的 <code>GRU</code> 输出拼接在一起得到词级别的隐向量 $h$ </p><h2 id="词级别的注意力"><a href="#词级别的注意力" class="headerlink" title="词级别的注意力"></a>词级别的注意力</h2><p>但是对于一句话中的单词,并不是每一个单词对分类任务都是有用的,比如在做文本的情绪分类时,可能我们就会比较关注 “很好”、“伤感” 这些词。为了能使循环神经网络也能自动将“注意力”放在这些词汇上,作者设计了基于单词的注意力层的具体流程如下: </p><script type="math/tex; mode=display">u_{i t} =\tanh \left(W_{w} h_{i t}+b_{w}\right)</script><script type="math/tex; mode=display">\alpha_{i t} =\frac{\exp \left(u_{i t}^{\top} u_{w}\right)}{\sum_{t} \exp \left(u_{i t}^{\top} u_{w}\right)}</script><script type="math/tex; mode=display">s_{i} =\sum_{t} \alpha_{i t} h_{i t}</script><p>上面式子中,$u_{it}$ 是 $h_{it}$ 的隐层表示,$a_{it}$ 是经 <code>softmax</code> 函数处理后的归一化权重系数,$u_w$是一个随机初始化的向量,之后会作为模型的参数一起被训练,$s_i$ 就是我们得到的第 $i$ 个句子的向量表示。</p><h2 id="句子编码器和句子级注意力"><a href="#句子编码器和句子级注意力" class="headerlink" title="句子编码器和句子级注意力"></a>句子编码器和句子级注意力</h2><p>对于句子级别的向量,我们用相类似的方法,将其通过双向 <code>GRU</code> 和注意力层,最后将文档中所有句子的隐向量表示加权求和,得到整个文档的文档向量 $v$,将该向量通过一个全连接分类器进行分类。</p><h2 id="模型结构和代码-2"><a href="#模型结构和代码-2" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HierAttNet</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, rnn_type, word_hidden_size, sent_hidden_size, num_classes,word_embedding, n_layers, bidirectional, batch_first, freeze, dropout</span>):</span></span><br><span class="line"> <span class="built_in">super</span>(HierAttNet, self).__init__()</span><br><span class="line"> self.word_embedding = word_embedding</span><br><span class="line"> self.word_hidden_size = word_hidden_size</span><br><span class="line"> self.sent_hidden_size = sent_hidden_size</span><br><span class="line"> self.word_att_net = WordAttNet(rnn_type,word_embedding, word_hidden_size,n_layers, bidirectional,batch_first,dropout,freeze)</span><br><span class="line"> self.sent_att_net = SentAttNet(rnn_type,sent_hidden_size, word_hidden_size,n_layers,bidirectional,batch_first,dropout, num_classes)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, batch_doc, text_lengths</span>):</span></span><br><span class="line"> output_list = []</span><br><span class="line"> <span class="comment"># ############################ 词级 #########################################</span></span><br><span class="line"> <span class="keyword">for</span> idx,doc <span class="keyword">in</span> <span class="built_in">enumerate</span>(batch_doc):</span><br><span class="line"> <span class="comment"># 把一篇文档拆成多个句子</span></span><br><span class="line"> doc = doc[:text_lengths[idx]]</span><br><span class="line"> doc_list = doc.cpu().numpy().tolist()</span><br><span class="line"> sep_index = [i <span class="keyword">for</span> i, num <span class="keyword">in</span> <span class="built_in">enumerate</span>(doc_list) <span class="keyword">if</span> num == self.word_embedding.stoi[<span class="string">'[SEP]'</span>]]</span><br><span class="line"> sentence_list = []</span><br><span class="line"> <span class="keyword">if</span> sep_index:</span><br><span class="line"> pre = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> cur <span class="keyword">in</span> sep_index:</span><br><span class="line"> sentence_list.append(doc_list[pre:cur])</span><br><span class="line"> pre = cur</span><br><span class="line"></span><br><span class="line"> sentence_list.append(doc_list[cur:])</span><br><span class="line"></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> sentence_list.append(doc_list)</span><br><span class="line"> max_sentence_len = <span class="built_in">len</span>(<span class="built_in">max</span>(sentence_list,key=<span class="keyword">lambda</span> x:<span class="built_in">len</span>(x)))</span><br><span class="line"> seq_lens = []</span><br><span class="line"> input_token_ids = []</span><br><span class="line"> <span class="keyword">for</span> sent <span class="keyword">in</span> sentence_list:</span><br><span class="line"> cur_sent_len = <span class="built_in">len</span>(sent)</span><br><span class="line"> seq_lens.append(cur_sent_len)</span><br><span class="line"> input_token_ids.append(sent+[self.word_embedding.stoi[<span class="string">'PAD'</span>]]*(max_sentence_len-cur_sent_len))</span><br><span class="line"> input_token_ids = torch.LongTensor(np.array(input_token_ids)).to(batch_doc.device)</span><br><span class="line"> seq_lens = torch.LongTensor(np.array(seq_lens)).to(batch_doc.device)</span><br><span class="line"> word_output, hidden = self.word_att_net(input_token_ids,seq_lens)</span><br><span class="line"> <span class="comment"># word_output = [bs,hidden_size]</span></span><br><span class="line"> output_list.append(word_output)</span><br><span class="line"></span><br><span class="line"> max_doc_sent_num = <span class="built_in">len</span>(<span class="built_in">max</span>(output_list,key=<span class="keyword">lambda</span> x: <span class="built_in">len</span>(x)))</span><br><span class="line"> batch_sent_lens = []</span><br><span class="line"> batch_sent_inputs = []</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># ############################ 句子级 #########################################</span></span><br><span class="line"> <span class="keyword">for</span> doc <span class="keyword">in</span> output_list:</span><br><span class="line"> cur_doc_sent_len = <span class="built_in">len</span>(doc)</span><br><span class="line"> batch_sent_lens.append(cur_doc_sent_len)</span><br><span class="line"> expand_doc = torch.cat([doc,torch.zeros(size=((max_doc_sent_num-cur_doc_sent_len),<span class="built_in">len</span>(doc[<span class="number">0</span>]))).to(doc.device)],dim=<span class="number">0</span>)</span><br><span class="line"> batch_sent_inputs.append(expand_doc.unsqueeze(dim=<span class="number">0</span>))</span><br><span class="line"></span><br><span class="line"> batch_sent_inputs = torch.cat(batch_sent_inputs, <span class="number">0</span>)</span><br><span class="line"> batch_sent_lens = torch.LongTensor(np.array(batch_sent_lens)).to(doc.device)</span><br><span class="line"> output = self.sent_att_net(batch_sent_inputs,batch_sent_lens)</span><br><span class="line"> <span class="keyword">return</span> output</span><br></pre></td></tr></table></figure><h1 id="BERT-😊"><a href="#BERT-😊" class="headerlink" title="BERT 😊"></a>BERT 😊</h1><div align=center><img width="600" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4d4a04d28ef44ec84efa17066a30ebe~tplv-k3u1fbpfcp-zoom-1.image"/></div><p><code>BERT(Bidirectional Encoder Representations from Transformers)</code> 的发布是 <code>NLP</code> 领域发展的最新的里程碑之一,这个事件 <code>NLP</code> 新时代的开始。<code>BERT</code> 模型打破了基于语言处理的任务的几个记录。</p><p>在 <code>BERT</code> 的论文发布后不久,这个团队还公开了模型的代码,并提供了模型的下载版本,这些模型已经在大规模数据集上进行了预训练。这是一个重大的发展,因为它使得任何一个构建构建机器学习模型来处理语言的人,都可以将这个强大的功能作为一个现成的组件来使用,从而节省了从零开始训练语言处理模型所需要的时间、精力、知识和资源。</p><p>更多详细内容见 <a href="https://zhuanlan.zhihu.com/p/266364526">【图解BERT】</a>、<a href="https://cloud.tencent.com/developer/article/1389555">【图解BERT模型】</a></p><h2 id="Task-1-Masked-Language-Model"><a href="#Task-1-Masked-Language-Model" class="headerlink" title="Task 1: Masked Language Model"></a>Task 1: Masked Language Model</h2><p>由于 <code>BERT</code> 需要通过上下文信息,来预测中心词的信息,同时又不希望模型提前看见中心词的信息,因此提出了一种 <code>Masked Language Model</code> 的预训练方式,即随机从输入预料上 <code>mask</code> 掉一些单词,然后通过的上下文预测该单词,类似于一个完形填空任务。</p><p>在预训练任务中,15% 的 <code>Word Piece</code> 会被 <code>mask</code>,这 15% 的 <code>Word Piece</code> 中,80%的时候会直接替换为 <code>[Mask]</code> ,10% 的时候将其替换为其它任意单词,10% 的时候会保留原始 <code>Token</code></p><ul><li>没有100% <code>mask</code> 的原因<ul><li>如果句子中的某个<code>Token</code> 100% 都会被 <code>mask</code> 掉,那么在<code>fine-tuning</code> 的时候模型就会有一些没有见过的单词</li></ul></li><li>加入 10% 随机 <code>token</code> 的原因<ul><li><code>Transformer</code> 要保持对每个输入 <code>token</code> 的分布式表征,否则模型就会记住这个 <code>[mask]</code> 是 某个特定的 <code>token</code></li><li>另外编码器不知道哪些词需要预测的,哪些词是错误的,因此被迫需要学习每一个 <code>token</code> 的表示向量</li></ul></li><li>另外,每个 <code>batchsize</code> 只有 15% 的单词被 <code>mask</code> 的原因,是因为性能开销的问题,双向编码器比单项编码器训练要更慢</li></ul><h2 id="Task-2-Next-Sequence-Prediction"><a href="#Task-2-Next-Sequence-Prediction" class="headerlink" title="Task 2: Next Sequence Prediction"></a>Task 2: Next Sequence Prediction</h2><div align=center><img width="600" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f9094e61c9049d09b118dafde9d31f1~tplv-k3u1fbpfcp-zoom-1.image"/></div><p>仅仅一个 <code>MLM</code> 任务是不足以让 <code>BERT</code> 解决阅读理解等句子关系判断任务的,因此添加了额外的一个预训练任务,即 <code>Next Sequence Prediction</code>。</p><p>具体任务即为一个句子关系判断任务,即判断句子B是否是句子A的下文,如果是的话输出 <code>IsNext</code>,否则输出 <code>NotNext</code>。</p><p>训练数据的生成方式是从平行语料中随机抽取的连续两句话,其中 50% 保留抽取的两句话,它们符合 <code>IsNext</code> 关系,另外 50% 的第二句话是随机从预料中提取的,它们的关系是 <code>NotNext</code> 的。这个关系保存在 <code>[CLS]</code> 符号中。</p><h2 id="输入"><a href="#输入" class="headerlink" title="输入"></a>输入</h2><ul><li><p><code>Token Embeddings</code>:即传统的词向量层,每个输入样本的首字符需要设置为 <code>[CLS]</code>,可以用于之后的分类任务,若有两个不同的句子,需要用 <code>[SEP]</code> 分隔,且最后一个字符需要用 <code>[SEP]</code> 表示终止。</p></li><li><p><code>Segment Embeddings</code>:为 <code>[0,1]</code> 序列,用来在 <code>NSP</code> 任务中区别两个句子,便于做句子关系判断任务。</p></li><li><p><code>Position Embeddings</code> :与 <code>Transformer</code> 中的位置向量不同,<code>BERT</code> 中的位置向量是直接训练出来的。</p></li></ul><div align=center><img width="600" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5931f6c34cc64d979ff80dc3d482d74d~tplv-k3u1fbpfcp-zoom-1.image"/></div><h2 id="Fine-tuning"><a href="#Fine-tuning" class="headerlink" title="Fine-tuning"></a>Fine-tuning</h2><p>对于不同的下游任务,我们仅需要对 <code>BERT</code> 不同位置的输出进行处理即可,或者直接将BERT不同位置的输出直接输入到下游模型当中。具体的如下所示:</p><div align=center><img width="600" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec6c09b656bd4b3a8698b5ebe98a9c57~tplv-k3u1fbpfcp-zoom-1.image"/></div><ul><li><p>对于情感分析等单句分类任务,可以直接输入单个句子(不需要 <code>[SEP]</code> 分隔双句),将 <code>[CLS]</code> 的输出直接输入到分类器进行分类。</p></li><li><p>对于句子对任务(句子关系判断任务),需要用 <code>[SEP]</code> 分隔两个句子输入到模型中,然后同样仅须将 <code>[CLS]</code> 的输出送到分类器进行分类。</p></li><li><p>对于问答任务,将问题与答案拼接输入到 <code>BERT</code> 模型中,然后将答案位置的输出向量进行二分类并在句子方向上进行 <code>softmax</code>(只需预测开始和结束位置即可)。</p></li><li><p>对于命名实体识别任务,对每个位置的输出进行分类即可,如果将每个位置的输出作为特征输入到 <code>CRF</code> 将取得更好的效果。</p></li></ul><h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><ul><li><p><code>BERT</code> 的预训练任务 <code>MLM</code> 使得能够借助上下文对序列进行编码,但同时也使得其预训练过程与中的数据与微调的数据不匹配,难以适应生成式任务。</p></li><li><p>另外,<code>BERT</code> 没有考虑预测 <code>[MASK]</code> 之间的相关性,是对语言模型联合概率的有偏估计。</p></li><li><p>由于最大输入长度的限制,适合句子和段落级别的任务,不适用于文档级别的任务(如长文本分类)。</p></li><li><p>适合处理自然语义理解类任务(<code>NLU</code>),而不适合自然语言生成类任务(<code>NLG</code>)。</p></li></ul><p><code>BERT</code> 分类的优化可以尝试 👼:</p><ul><li><p>尝试不同的预训练模型,比如 <code>RoBERT</code>、<code>WWM</code>、<code>ALBERT</code>。</p></li><li><p>除了 <code>[CLS]</code> 外还可以用 <code>avg</code>、<code>max</code> 池化做句表示,甚至可以把不同层组合起来。</p></li><li><p>在领域数据上增量预训练。</p></li><li><p>集成蒸馏,训多个大模型集成起来后蒸馏到一个上。</p></li><li><p>先用多任务训,再迁移到自己的任务。</p></li></ul><h2 id="模型结构和代码-3"><a href="#模型结构和代码-3" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BertForSeqCLS</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="built_in">super</span>(BertForSeqCLS, self).__init__()</span><br><span class="line"> self.bert = BertModel.from_pretrained(config)</span><br><span class="line"></span><br><span class="line"> self.dropout = nn.Dropout(config.dropout)</span><br><span class="line"> self.fc = nn.Linear(<span class="number">768</span> * <span class="number">3</span>, config.class_num)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, input_ids, attention_mask, labels=<span class="literal">None</span></span>):</span></span><br><span class="line"> <span class="comment"># input_ids 输入的句子序列</span></span><br><span class="line"> <span class="comment"># seq_len 句子长度</span></span><br><span class="line"> <span class="comment"># attention_masks 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]</span></span><br><span class="line"> <span class="comment"># pooled_out [batch_size, 768]</span></span><br><span class="line"> <span class="comment"># sentence [batch size, sen len, 768]</span></span><br><span class="line"> </span><br><span class="line"> outputs = self.bert(input_ids, attention_mask=attention_mask, </span><br><span class="line"> output_hidden_states=<span class="literal">True</span>)</span><br><span class="line"> cat_out = torch.cat((outputs.pooler_output, outputs.hidden_states[-<span class="number">1</span>][:,<span class="number">0</span>], </span><br><span class="line"> outputs.hidden_states[-<span class="number">2</span>][:, <span class="number">0</span>]), <span class="number">1</span>)</span><br><span class="line"> logits = self.fc(self.dropout(cat_out))</span><br><span class="line"></span><br><span class="line"> loss = <span class="literal">None</span></span><br><span class="line"> <span class="keyword">if</span> labels <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> loss = F.cross_entropy(logits.view(-<span class="number">1</span>, config.class_num), labels.view(-<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {<span class="string">"loss"</span>: loss, <span class="string">"logits"</span>: logits}</span><br></pre></td></tr></table></figure><h1 id="文本分类技巧"><a href="#文本分类技巧" class="headerlink" title="文本分类技巧"></a>文本分类技巧</h1><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/62d4e2fd39be4a5db9471baf753f49cd~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="数据集构建"><a href="#数据集构建" class="headerlink" title="数据集构建"></a>数据集构建</h2><p>首先是标签体系的构建,拿到任务时自己先试标一两百条,看有多少是难确定(思考1s以上)的,如果占比太多,那这个任务的定义就有问题。可能是标签体系不清晰,或者是要分的类目太难了,这时候就要找项目owner去反馈而不是继续往下做。</p><p>其次是训练评估集的构建,可以构建两个评估集,一个是贴合真实数据分布的线上评估集,反映线上效果,另一个是用规则去重后均匀采样的随机评估集,反映模型的真实能力。训练集则尽可能和评估集分布一致,有时候我们会去相近的领域拿现成的有标注训练数据,这时就要注意调整分布,比如句子长度、标点、干净程度等,尽可能做到自己分不出这个句子是本任务的还是从别人那里借来的。</p><p>最后是数据清洗:</p><ul><li><p>去掉文本强 <code>pattern</code>:比如做新闻主题分类,一些爬下来的数据中带有的XX报道、XX编辑高频字段就没有用,可以对语料的片段或词进行统计,把很高频的无用元素去掉。还有一些会明显影响模型的判断,比如之前判断句子是否为无意义的闲聊时,发现加个句号就会让样本由正转负,因为训练预料中的闲聊很少带句号(跟大家的打字习惯有关),于是去掉这个<code>pattern</code> 就好了不少</p></li><li><p>纠正标注错误:简单的说就是把训练集和评估集拼起来,用该数据集训练模型两三个 <code>epoch</code> (防止过拟合),再去预测这个数据集,把模型判错的拿出来按 <code>abs(label-prob)</code> 排序,少的话就自己看,多的话就反馈给标注人员,把数据质量搞上去了提升好几个点都是可能的。</p></li></ul><h2 id="长文本"><a href="#长文本" class="headerlink" title="长文本"></a>长文本</h2><ul><li><p>任务简单的话(比如新闻分类),直接用 <code>fasttext</code> 就可以达到不错的效果。</p></li><li><p>想要用 <code>BERT</code> 的话,最简单的方法是粗暴截断,比如只取句首+句尾、句首 + <code>tfidf</code> 筛几个词出来;或者每句都预测,最后对结果综合。</p></li><li><p>另外还有一些魔改的模型可以尝试,比如<code>BERT+HAN</code>、<code>XLNet</code>、<code>Reformer</code>、<code>Longformer</code>。</p></li></ul><h2 id="稳健性"><a href="#稳健性" class="headerlink" title="稳健性"></a>稳健性</h2><p>在实际的应用中,鲁棒性是个很重要的问题,否则在面对 <code>badcase</code> 时会很尴尬,怎么明明那样就分对了,加一个字就错了呢?这里可以直接使用一些粗暴的数据增强,加停用词加标点、删词、同义词替换等,如果效果下降就把增强后的训练数据洗一下。当然也可以用对抗学习、对比学习这样的高阶技巧来提升,一般可以提1个点左右,但不一定能避免上面那种尴尬的情况。</p><h1 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h1><ol><li><a href="https://arxiv.org/abs/1607.01759">https://arxiv.org/abs/1607.01759</a></li><li><a href="https://arxiv.org/abs/1408.5882">https://arxiv.org/abs/1408.5882</a></li><li><a href="https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf">https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf</a></li><li><a href="https://dl.acm.org/doi/10.5555/2886521.2886636">https://dl.acm.org/doi/10.5555/2886521.2886636</a></li><li><a href="https://www.aclweb.org/anthology/N16-1174.pdf">https://www.aclweb.org/anthology/N16-1174.pdf</a></li><li><a href="https://zhuanlan.zhihu.com/p/266364526">https://zhuanlan.zhihu.com/p/266364526</a></li><li><a href="https://cloud.tencent.com/developer/article/1389555">https://cloud.tencent.com/developer/article/1389555</a></li><li><a href="https://www.cnblogs.com/sandwichnlp/p/11698996.html">https://www.cnblogs.com/sandwichnlp/p/11698996.html</a></li><li><a href="https://github.com/jeffery0628/text_classification">https://github.com/jeffery0628/text_classification</a></li><li><a href="https://zhuanlan.zhihu.com/p/349086747">https://zhuanlan.zhihu.com/p/349086747</a></li><li><a href="https://zhuanlan.zhihu.com/p/35457093">https://zhuanlan.zhihu.com/p/35457093</a></li><li><a href="https://www.pianshen.com/article/4319299677/">https://www.pianshen.com/article/4319299677/</a></li><li><a href="https://www.zhihu.com/question/326770917/answer/698646465">https://www.zhihu.com/question/326770917/answer/698646465</a></li></ol>]]></content>
<categories>
<category> 自然语言处理 </category>
</categories>
<tags>
<tag> 文本分类 </tag>
<tag> 深度学习 </tag>
<tag> 深层模型 </tag>
<tag> BERT </tag>
</tags>
</entry>
<entry>
<title>文本分类的深度学习方法【一】</title>
<link href="/2021/08/31/text_classification1/"/>
<url>/2021/08/31/text_classification1/</url>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>文本分类📖在文本处理中是很重要的一个模块,它的应用也非常广泛,比如:新闻分类、简历分类、邮件分类、办公文档分类、区域分类等诸多方面,还能够实现文本过滤,从大量文本中快速识别和过滤出符合特殊要求的信息。核心方法为首先提取分类数据的特征,然后选择最优的匹配,从而分类。</p><p>通常来讲,文本分类任务是指在给定的分类体系中,将文本指定分到某个或某几个类别中,被分类的对象有短文本,例如句子、标题、商品评论等等,长文本如文章等。分类体系一般人工划分,例如:1)政治、体育、军事 2)正能量、负能量 3)好评、中性、差评。此外,还有文本多标签分类,比如一篇博客的标签可以同时是:自然语言处理,文本分类等。因此,对应的分类模式可以分为:<strong>二分类</strong>、<strong>多分类</strong>以及<strong>多标签分类</strong>。</p><p><img src="https://few-shot-text-classification.fastforwardlabs.com/figures/ff18-01.png" width="500" height="300" align="middle" /></p><h1 id="文本分类的主要任务"><a href="#文本分类的主要任务" class="headerlink" title="文本分类的主要任务"></a>文本分类的主要任务</h1><ul><li><p>情感分析⭐️:分析人们在文本数据(如产品评论、电影评论和推特)中的观点,并提取他们的极性和观点。可以是二分类问题也可以是多分类问题,二元情感分析是将文本分为正类和负类,而多类情感分析则侧重于将数据分为细粒度的标签或多层次的强度。</p></li><li><p>新闻分类⭐️:新闻分类系统可以帮助用户实时获取感兴趣的信息。基于用户兴趣的新闻主题识别和相关新闻推荐是新闻分类的两个主要应用。</p></li><li><p>主题分析⭐️:主题分析试图通过识别文本的主题从文本中自动获得意义。主题分类的目标是为每个文档分配一个或多个主题,以便于分析。</p></li><li><p>问答系统⭐️:有两种类型的问答系统:提取式和生成式。抽取式 <code>QA</code> 可以看作是文本分类的一个特例。给定一个问题和一组候选答案,根据问题需要将每个候选答案正确分类。</p></li><li><p>自然语言推理⭐️:<code>NLI</code>,也称为文本蕴含识别(<code>RTE</code>),预测一个文本的含义是否可以从另一个文本中推断出来。特别是,一个系统需要给每对文本单元分配一个标签,比如蕴涵、矛盾和中立。</p></li></ul><h1 id="文本分类的主要方法"><a href="#文本分类的主要方法" class="headerlink" title="文本分类的主要方法"></a>文本分类的主要方法</h1><ul><li><p>基于规则特征匹配的方法(如根据“喜欢”,“讨厌”等特殊词来评判情感,但准确率低,通常作为一种辅助判断的方法)。</p></li><li><p>基于传统机器学习的方法(特征工程 + 分类算法)。</p></li><li><p>基于深度学习的方法(词向量 + 神经网络)。</p></li></ul><h1 id="GLUE-任务与数据集"><a href="#GLUE-任务与数据集" class="headerlink" title="GLUE 任务与数据集"></a>GLUE 任务与数据集</h1><p><code>GLUE</code> 数据集:通用语言理解评估 <code>General language Understanding Evaluation: GLUE</code> 基准是一组各种 <code>NLP</code> 文本任务。大多数 <code>GLUE</code> 数据集已经存在多年,而 <code>GLUE</code> 将它们收集在一起的目的是:</p><ul><li><p>为这些数据集建立统一的训练集、验证集、测试集拆分标准。</p></li><li><p><code>GLUE</code> 不给测试集打标签,用户必须将测试结果上传到 <code>GLUE</code> 服务器进行评估(但是提交次数有限)。</p></li></ul><p><code>GLUE</code> 基准包含以下数据集:</p><ul><li><p><code>Multi-Genre Natural Language Inference: MNLI</code>⭐️:大规模的、众包的蕴含分类任务。给定一对句子,其目标是预测第二句相对于第一句是蕴含句 <code>entailment</code>、矛盾句、还是中性句。</p></li><li><p><code>Quora Question Pairs: QQP</code>⭐️:一个二元分类任务。给定一对 <code>Quora</code> 上的两个问题,其目标是预测它们在语义上是否等价。</p></li><li><p><code>Question Natural Language Inference: QNLI</code>⭐️:斯坦福 <code>Question Answering</code> 数据集的一个转换为分类任务的版本。正类样本由问题和正确答案组成,负类样本由问题和非正确答案组成,其中非正确答案来自于同一段文本。</p></li><li><p><code>Stanford Sentiment Treebank: SST-2</code>⭐️:一个二元单句分类任务。其数据集从电影评论中提取的句子组成,由人类对其进行二元情感标注。</p></li><li><p><code>Corpus of Linguistic Acceptability: CoLA</code>⭐️:一个二元单句分类任务。其目标是预测一个英语句子是否在语法上可接受的。</p></li><li><p><code>Semantic Textual Similarity Benchmark: STS-B</code>⭐️:一个句子相似度多元分类任务。其数据集从新闻标题和其它数据源提取的句子对,通过人工来标记一对句子在语义上的相似得分(1分到5分)。</p></li><li><p><code>Microsoft Research Paraphrase Corpus: MRPC</code>⭐️:一个句子相似度二元分类任务。从在线新闻数据源抽取的句子对,由人工标记一对句子是否语义上相等。</p></li><li><p><code>Recognizing Textual Entailment: RTE</code>⭐️:类似 <code>MNLI</code> 的二元蕴含关系任务,但是 <code>RTE</code> 数据集规模更小。</p></li></ul><h1 id="FastText-😊"><a href="#FastText-😊" class="headerlink" title="FastText 😊"></a>FastText 😊</h1><blockquote><p>论文:<a href="https://arxiv.org/abs/1607.01759">https://arxiv.org/abs/1607.01759</a><br>代码:<a href="https://github.com/facebookresearch/fastText">https://github.com/facebookresearch/fastText</a></p></blockquote><p><code>FastText</code>是 <code>Facebook</code> 于2016年开源的一个词向量计算和文本分类工具,在文本分类任务中,<code>FastText</code>(浅层网络)往往能取得和深度网络相媲美的精度,却在训练时间上比深度网络快许多数量级。</p><p><code>FastText</code>是一个快速文本分类算法,其具有两大优点:</p><ul><li><p><code>FastText</code>在保持高精度的情况下加快了训练速度和测试速度</p></li><li><p><code>FastText</code>不需要预训练好的词向量,<code>FastText</code>会自己训练词向量</p></li><li><p><code>FastText</code>两个重要的优化:<code>Hierarchical Softmax</code>、<code>N-gram</code></p></li></ul><h2 id="Word2Vec"><a href="#Word2Vec" class="headerlink" title="Word2Vec"></a>Word2Vec</h2><p>🎈 在我们深入研究 <code>FastText</code> 之前,让我们快速回顾一下 <code>Word2Vec</code>是什么,通过 <code>Word2Vec</code>,我们训练一个具有单一隐藏层的神经网络,根据其上下文(邻近的词)预测目标词。我们的假设是,一个词的含义可以通过它周围出现的词来推断。实际上,在训练时,你可以使用两种不同的网络架构来实现: <code>CBOW</code> 和 <code>Skip-Gram</code>。</p><ul><li><code>Continuous Bag of Words</code>: 根据背景词预测中心词。</li></ul><p><img src="https://amitness.com/images/nlp-ssl-center-word-prediction.gif" alt="image.png"></p><ul><li><code>Skip-Gram</code>: 根据中心词预测背景词。</li></ul><p><img src="https://amitness.com/images/nlp-ssl-neighbor-word-prediction.gif" alt="image.png"></p><p>通过这种方法所学到的词向量表征具有一些有趣的特性,例如这个流行的例子:</p><p><img src="https://amitness.com/images/word2vec-analogy.gif" alt="image.png"></p><p>🎈 <u><code>word2vec</code> 中提供了两种针对大规模多分类问题的优化手段, <code>Negative sampling</code> 和 <code>Hierarchical softmax</code>。</u></p><ul><li><p><code>Negative sampling</code> 通过只更新少量负类词的词向量,从而减轻了计算量。</p></li><li><p><code>Hierarchical softmax</code> 将词库表示成前缀树,从树根到叶子的路径可以表示为一系列二分类器,一次多分类计算的复杂度从 $|V|$ 降低到了树的高度 $log_2 |V|$ 。</p></li></ul><p>🎈 <code>FastText</code> 的核心思想就是:将整篇文档的词及 <code>n-gram</code> 向量叠加平均得到文档向量,然后使用文档向量做 <code>softmax</code> 多分类,其本质上就是传统的词袋法,即将文档看成一个由词构成的集合。</p><p><img src="https://files.mdnice.com/user/11071/9d3b06dd-0cfa-4557-af5f-177589324304.png" alt=""></p><p>🎈 <u>基于词袋的方法一个明显的缺点是:</u></p><blockquote><p>我不喜欢这类电影,但是喜欢这一个。</p><p>我喜欢这类电影,但是不喜欢这一个。</p></blockquote><p>这样的两个句子经过词向量平均以后已经送入单层神经网络的时候已经完全一模一样了,分类器不可能分辨出这两句话的区别,只有添加 <code>n-gram</code> 特征以后才可能有区别。因此,在实际应用的时候需要对数据有足够的了解,然后再选择模型。</p><p>🎈 对于文本长且对速度要求高的场景,<code>Fasttext</code> 是 <code>Baseline</code> 首选。同时用它在无监督语料上训练词向量,进行文本表示也不错。不过想继续提升效果还需要更复杂的模型。</p><h2 id="模型结构和代码"><a href="#模型结构和代码" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a1715764f473450e8234f27072bba684~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><ol><li><p>模型输入: <code>[batch_size, seq_len]</code></p></li><li><p><code>embedding</code>层:随机初始化, 词向量维度为<code>embed_size</code>: <code>[batch_size, seq_len, embed_size]</code></p></li><li><p>求所有 <code>seq_len</code> 个词的均值: <code>[batch_size, embed_size]</code></p></li><li><p>全连接 + <code>softmax</code>归一化: <code>[batch_size,num_class]</code></p></li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FastText</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> config.vocab_size:词表大小</span></span><br><span class="line"><span class="string"> config.embedding_size:词向量维度</span></span><br><span class="line"><span class="string"> config.num_labels: 类别数</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">super</span>(FastText, self).__init__()</span><br><span class="line"> self.embedding_size = config.embedding_size</span><br><span class="line"> self.embedding = nn.Embedding(config.vocab_size, config.embedding_size)</span><br><span class="line"> self.fc = nn.Linear(self.embedding_size, config.num_labels)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, input_ids</span>):</span></span><br><span class="line"> <span class="comment"># text = [batch size, sent len]</span></span><br><span class="line"> embedded = self.embedding(input_ids).<span class="built_in">float</span>()</span><br><span class="line"> <span class="comment"># embedded = [batch size, sent len, emb dim]</span></span><br><span class="line"> pooled = F.avg_pool2d(embedded, (embedded.shape[<span class="number">1</span>], <span class="number">1</span>)).squeeze(<span class="number">1</span>)</span><br><span class="line"> <span class="comment"># pooled = [batch size, embedding_dim]</span></span><br><span class="line"> <span class="keyword">return</span> self.fc(pooled), pooled</span><br></pre></td></tr></table></figure><h1 id="TextCNN-😊"><a href="#TextCNN-😊" class="headerlink" title="TextCNN 😊"></a>TextCNN 😊</h1><blockquote><p>论文:<a href="https://arxiv.org/abs/1408.5882">https://arxiv.org/abs/1408.5882</a><br>代码:<a href="https://github.com/yoonkim/CNN_sentence">https://github.com/yoonkim/CNN_sentence</a></p></blockquote><p><code>TextCNN</code> 是 <code>Yoon Kim</code> 在 2014 年提出的模型,开创了用 <code>CNN</code> 编码 <code>n-gram</code> 特征的先河。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bccf33f717d742f5a3a28e378d1f3f97~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>详细过程:</p><ul><li><p><code>Embedding</code>:第一层是图中最左边的 $5 \times 3$ 的句子矩阵,每行是词向量,维度为 $3$,这个可以类比为图像中的原始像素点。</p></li><li><p><code>Convolution</code>:然后经过 <code>filter_sizes=(2,3,4)</code>的一维卷积层,每个<code>filter_size</code> 有 $2$ 个输出 <code>channel</code>。</p></li><li><p><code>MaxPolling</code>:第三层是一个 <code>1-max pooling</code> 层(因为是时间维度的,也称 <code>max-over-time pooling</code>),这样不同长度句子经过 <code>pooling</code> 层之后都能变成定长的表示。</p></li><li><p><code>Full Connection and Softmax</code>:最后接一层全连接的 <code>softmax</code> 层,输出每个类别的概率。</p></li></ul><p><u>在 <code>TextCNN</code> 的实践中,有很多地方可以优化:</u></p><ul><li><p><code>filter_sizes</code>🤗:这个参数决定了抽取 <code>n-gram</code> 特征的长度,这个参数主要跟数据有关,平均长度在 $50$ 以内的话,用 $10$ 以下就可以了,否则可以长一些。在调参时可以先用一个尺寸 <code>grid search</code>,找到一个最优尺寸,然后尝试最优尺寸和附近尺寸的组合。</p></li><li><p><code>n_filters</code>🤗:这个参数会影响最终特征的维度,维度太大的话训练速度就会变慢。这里在 $100-600$ 之间调参即可。</p></li><li><p><code>CNN</code>的激活函数🤗:可以尝试 <code>ReLU</code>、<code>tanh</code>。</p></li><li><p><code>Regularization</code>🤗:指对 <code>CNN</code> 参数的正则化,可以使用 <code>dropout</code> 或 <code>L2</code>,但能起的作用很小,可以试下小概率的 <code>dropout</code>。</p></li><li><p><code>Pooling</code>方法🤗:根据情况选择 <code>mean</code>、<code>max</code>、<code>k-max pooling</code>,大部分时候 <code>max</code> 表现就很好,因为分类任务对细粒度语义的要求不高,只抓住最大特征就好了。</p></li><li><p><code>Embedding</code>🤗:中文可以选择 <code>char</code> 或 <code>word</code> 级别的输入,也可以两种都用,会提升些效果。如果训练数据充足($10w+$),也可以从头训练。</p></li></ul><blockquote><p><code>TextCNN</code> 是很适合 <strong>中短文本场景</strong> 的强 <code>Baseline</code>,但 <strong>不太适合长文本</strong>,因为卷积核尺寸通常不会设很大,<strong>无法捕获长距离特征</strong>。同时 <code>max-pooling</code> 也存在局限,丢失了文本的结构信息,因此很难去发现文本中的转折关系等复杂模式。<code>TextCNN</code> 和传统的 <code>n-gram</code> 词袋模型本质是一样的,它的好效果很大部分来自于词向量的引入,解决了词袋模型的稀疏性问题。</p></blockquote><h3 id="模型结构和代码-1"><a href="#模型结构和代码-1" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h3><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1f132345a36444b796b32fb04c7595f0~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TextCNN</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> config.vocab_size:词表大小</span></span><br><span class="line"><span class="string"> config.embedding_size:词向量维度</span></span><br><span class="line"><span class="string"> config.num_labels: 类别数</span></span><br><span class="line"><span class="string"> config.n_filters: 卷积核个数(对应2维卷积的通道数)</span></span><br><span class="line"><span class="string"> config.filter_sizes: 卷积核的尺寸(3,4,5)</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">super</span>(TextCNN, self).__init__()</span><br><span class="line"> self.embedding_size = config.embedding_size</span><br><span class="line"> self.embedding = nn.Embedding(config.vocab_size, config.embedding_size)</span><br><span class="line"> self.convs = nn.ModuleList(</span><br><span class="line"> [nn.Conv2d(in_channels=<span class="number">1</span>, out_channels=config.n_filters, </span><br><span class="line"> kernel_size=(fs, self.embedding_size)) <span class="keyword">for</span> fs <span class="keyword">in</span> config.filter_sizes])</span><br><span class="line"> self.fc = nn.Linear(<span class="built_in">len</span>(config.filter_sizes) * config.n_filters, config.num_labels)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, input_ids</span>):</span></span><br><span class="line"> <span class="comment"># text = [batch size, sent len]</span></span><br><span class="line"> embedded = self.embedding(input_ids)</span><br><span class="line"> <span class="comment"># embedded = [batch size, sent len, emb dim]</span></span><br><span class="line"> embedded = embedded.unsqueeze(<span class="number">1</span>).<span class="built_in">float</span>()</span><br><span class="line"> <span class="comment"># embedded = [batch size, 1, sent len, emb dim]</span></span><br><span class="line"> conved = [F.relu(conv(embedded)).squeeze(<span class="number">3</span>) <span class="keyword">for</span> conv <span class="keyword">in</span> self.convs]</span><br><span class="line"> <span class="comment"># conved_n = [batch size, n_filters, sent len - filter_sizes[n] + 1]</span></span><br><span class="line"> pooled = [F.max_pool1d(conv, <span class="built_in">int</span>(conv.shape[<span class="number">2</span>])).squeeze(<span class="number">2</span>) <span class="keyword">for</span> conv <span class="keyword">in</span> conved]</span><br><span class="line"> <span class="comment"># pooled_n = [batch size, n_filters]</span></span><br><span class="line"> cat = torch.cat(pooled, dim=<span class="number">1</span>)</span><br><span class="line"> <span class="comment"># cat = [batch size, n_filters * len(filter_sizes)]</span></span><br><span class="line"> <span class="keyword">return</span> self.fc(cat)</span><br></pre></td></tr></table></figure><h1 id="TextRNN-😊"><a href="#TextRNN-😊" class="headerlink" title="TextRNN 😊"></a>TextRNN 😊</h1><h2 id="RNN"><a href="#RNN" class="headerlink" title="RNN"></a>RNN</h2><p>循环神经网络(<code>Recurrent Neural Network,RNN</code>)是一类具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其它神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1da27ed23744731a09834126f1f983a~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><script type="math/tex; mode=display">H_t = Tanh([H_{t-1}, X_t]W+b)</script><h2 id="长程依赖问题"><a href="#长程依赖问题" class="headerlink" title="长程依赖问题"></a>长程依赖问题</h2><p>虽然简单循环网络理论上可以建立长时间间隔的状态之间的依赖关系,但<br>是由于梯度爆炸或消失问题,实际上只能学习到短期的依赖关系。这样,如果时刻 $t$ 的输出 $y_t$ 依赖于时刻 $k$ 的输入 $x_k$,当间隔 $t−k$ 比较大时,简单神经网络很难建模这种长距离的依赖关系,称为长程依赖问题(<code>Long-Term Dependencies Problem</code>)。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e36102444c34a23a873c939ad0bfd91~tplv-k3u1fbpfcp-watermark.image" alt="RNN"></p><h2 id="LSTM"><a href="#LSTM" class="headerlink" title="LSTM"></a>LSTM</h2><p>为了改善循环神经网络的长程依赖问题,一种非常好的解决方案是引入门控机制来控制信息的累积速度,包括有选择地加入新的信息,并有选择地遗忘之前累积的信息。</p><p>长短期记忆(<code>Long Short-Term Memory,LSTM</code>)网络[Gers 等人; Hochreiter等人,2000; 1997] 是循环神经网络的一个变体,可以有效地解决简单循环神经网络的梯度爆炸或消失问题。</p><blockquote><p><code>LSTM</code> 的关键在于细胞的状态 $C_t$ 和穿过细胞的线,细胞状态类似于传送带,直接在整个链上运行,只有一些少量的线性交互,信息在上面流动保持不变会变得容易。</p></blockquote><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa43fb1414de4a7e9f36021b7cf44137~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><code>LSTM</code> 网络引入门控机制(<code>Gating Mechanism</code>)来控制信息传递的路径。<code>LSTM</code>网络中的“门”是一种“软”门,取值在 $(0, 1)$ 之间,表示以一定的比例运行信息通过。</p><h3 id="遗忘门"><a href="#遗忘门" class="headerlink" title="遗忘门"></a>遗忘门</h3><p>遗忘门 $f_{t}$ 控制上一个时刻的内部状态 $c_{t−1}$ 需要遗忘多少信息。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9666f2ff6f5c42ffb44e2ac5ed05591e~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="输入门"><a href="#输入门" class="headerlink" title="输入门"></a>输入门</h3><p>输入门决定让多少新的信息加入到当前的 $C_t$ 中来。实现这个需要两个步骤:</p><ol><li>首先计算出输入门 $i-t$ 和候选记忆细胞状态。</li></ol><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/409cd2537d404fe59c2786500c956d30~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><ol><li>结合遗忘门 $f_t$ 和输入门 $i_t$ 来更新记忆单元 $C_t$</li></ol><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3ae335e67eb24ffda32bedcd1ca43485~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="输出门"><a href="#输出门" class="headerlink" title="输出门"></a>输出门</h3><p>最终,我们需要确定输出什么值,这个输出将会基于我们的细胞的状态,但是也是一个过滤后的版本。首先,我们通过一个 <code>sigmoid</code> 层来确定细胞状态的哪些部分将输出出去。接着,我们把细胞状态通过 <code>tanh</code> 进行处理(得到一个 $[-1,1]$ 之间的值)并将它和<code>sigmoid</code> 门的输出相乘。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/49e914d3e12a4a6ba6e4ef4aafc9e6a5~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="GRU"><a href="#GRU" class="headerlink" title="GRU"></a>GRU</h2><p>在 <code>LSTM</code> 中引入了三个门函数:输入门、遗忘门和输出门来控制信息的传递,由于输入门和遗忘门是互补关系,具有一定的冗余性,<code>GRU</code> 网络<br>直接使用一个门来控制输入和遗忘之间的平衡,在 <code>GRU</code> 模型中只有两个门:分别是更新门和重置门。</p><p>图中的 $z_{t}$ 和 $r_{t}$ 分别表示更新门和重置门。更新门用于控制前一时刻的状态信息被带入到当前状态中的程度,更新门的值越大说明前一时刻的状态信息带入越多。重置门控制前一状态有多少信息被写入到当前的候选状态 $\tilde{h}_{t}$ 上,重置门越小,前一状态的信息被写入的越少。</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c35dd733ddbc4a51ab9693f35a476588~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><code>LSTM</code> 和 <code>GRU</code> 都是通过各种门函数来将重要特征保留下来,这样就保证了在 <code>long-term</code> 传播的时候也不会丢失。此外 <code>GRU</code> 相对于 <code>LSTM</code> 少了一个门函数,因此在参数的数量上也是要少于<code>LSTM</code> 的,所以整体上 <code>GRU</code> 的训练速度要快于 <code>LSTM</code> 的。</p><h2 id="Attention"><a href="#Attention" class="headerlink" title="Attention"></a>Attention</h2><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0c0b980f90654c2baf45c882a1331543~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><code>Attention</code> 计算:</p><script type="math/tex; mode=display">M=tanh(H)</script><script type="math/tex; mode=display">\alpha = softmax(u^TM)</script><script type="math/tex; mode=display">rep = H \alpha^{T}</script><p>其中 $H$ 为每个时刻得到的隐藏状态, $u$为 <code>context vector</code>,随机初始化并随着训练更新,最后得到句子表示 $rep$,再进行分类。</p><h2 id="模型结构和代码-2"><a href="#模型结构和代码-2" class="headerlink" title="模型结构和代码"></a>模型结构和代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RNNAttention</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, config</span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> config.vocab_size:词表大小</span></span><br><span class="line"><span class="string"> config.embedding_size:词向量维度</span></span><br><span class="line"><span class="string"> config.num_labels: 类别数</span></span><br><span class="line"><span class="string"> config.hidden_dim: rnn隐藏层的维度</span></span><br><span class="line"><span class="string"> config.n_layers: rnn层数</span></span><br><span class="line"><span class="string"> config.rnn_type: rnn类型,包括['lstm', 'gru', 'rnn']</span></span><br><span class="line"><span class="string"> config.bidirectional: 是否双向</span></span><br><span class="line"><span class="string"> config.dropout: dropout率</span></span><br><span class="line"><span class="string"> config.batch_first: 第一个维度是否是批量大小</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">super</span>(RNNAttention, self).__init__()</span><br><span class="line"> self.embedding_size = config.embedding_size</span><br><span class="line"> self.embedding = nn.Embedding(config.vocab_size, config.embedding_size)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> config.rnn_type == <span class="string">'lstm'</span>:</span><br><span class="line"> self.rnn = nn.LSTM(self.embedding_size,</span><br><span class="line"> config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> <span class="keyword">elif</span> config.rnn_type == <span class="string">'gru'</span>:</span><br><span class="line"> self.rnn = nn.GRU(self.embedding_size,</span><br><span class="line"> hidden_size=config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.rnn = nn.RNN(self.embedding_size,</span><br><span class="line"> hidden_size=config.hidden_dim,</span><br><span class="line"> num_layers=config.n_layers,</span><br><span class="line"> bidirectional=config.bidirectional,</span><br><span class="line"> batch_first=config.batch_first,</span><br><span class="line"> dropout=config.dropout)</span><br><span class="line"> <span class="comment"># query向量 </span></span><br><span class="line"> self.u = nn.Parameter(torch.randn(config.hidden_dim * <span class="number">2</span>), requires_grad=<span class="literal">True</span>)</span><br><span class="line"> self.tanh = nn.Tanh()</span><br><span class="line"> self.fc = nn.Linear(config.hidden_dim * <span class="number">2</span>, config.num_labels)</span><br><span class="line"></span><br><span class="line"> self.dropout = nn.Dropout(config.dropout)</span><br><span class="line"> self.batch_first = config.batch_first</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, text, text_lengths</span>):</span></span><br><span class="line"> <span class="comment"># 按照句子长度从大到小排序</span></span><br><span class="line"> text, sorted_seq_lengths, desorted_indices = self.prepare_pack_padded_sequence(text, text_lengths)</span><br><span class="line"> <span class="comment"># text = [batch size, sent len]</span></span><br><span class="line"> embedded = self.dropout(self.embedding(text)).<span class="built_in">float</span>()</span><br><span class="line"> <span class="comment"># embedded = [batch size, sent len, emb dim]</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># pack sequence</span></span><br><span class="line"> packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, sorted_seq_lengths, batch_first=self.batch_first)</span><br><span class="line"> self.rnn.flatten_parameters()</span><br><span class="line"> <span class="keyword">if</span> config.rnn_type <span class="keyword">in</span> [<span class="string">'rnn'</span>, <span class="string">'gru'</span>]:</span><br><span class="line"> packed_output, hidden = self.rnn(packed_embedded)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># output (batch, seq_len, num_directions * hidden_dim)</span></span><br><span class="line"> <span class="comment"># hidden (batch, num_layers * num_directions, hidden_dim)</span></span><br><span class="line"> packed_output, (hidden, cell) = self.rnn(packed_embedded)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># unpack sequence</span></span><br><span class="line"> output, output_lengths = nn.utils.rnn.pad_packed_sequence(packed_output, batch_first=self.batch_first)</span><br><span class="line"> <span class="comment"># 把句子序列再调整成输入时的顺序</span></span><br><span class="line"> output = output[desorted_indices]</span><br><span class="line"> <span class="comment"># output = [batch_size, seq_len, hidden_dim * num_directionns ]</span></span><br><span class="line"></span><br><span class="line"> alpha = F.softmax(torch.matmul(self.tanh(output), self.u), dim=<span class="number">1</span>).unsqueeze(-<span class="number">1</span>) </span><br><span class="line"> <span class="comment"># alpha = [batch_size, seq_len, 1]</span></span><br><span class="line"> output_attention = output * alpha <span class="comment"># [batch_size, seq_len, hidden_dim * num_directionns ]</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> output_attention = torch.<span class="built_in">sum</span>(output_attention, dim=<span class="number">1</span>) <span class="comment"># [batch_size, hidden_dim]</span></span><br><span class="line"></span><br><span class="line"> fc_input = self.dropout(output_attention)</span><br><span class="line"> out = self.fc(fc_input)</span><br><span class="line"> <span class="keyword">return</span> out, fc_input</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">prepare_pack_padded_sequence</span>(<span class="params">self, inputs_words, seq_lengths, descending=<span class="literal">True</span></span>):</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> for rnn model :按照句子长度从大到小排序</span></span><br><span class="line"><span class="string"> </span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> sorted_seq_lengths, indices = torch.sort(seq_lengths, descending=descending)</span><br><span class="line"> _, desorted_indices = torch.sort(indices, descending=<span class="literal">False</span>)</span><br><span class="line"> sorted_inputs_words = inputs_words[indices]</span><br><span class="line"> <span class="keyword">return</span> sorted_inputs_words, sorted_seq_lengths, desorted_indices</span><br></pre></td></tr></table></figure><h1 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h1><ol><li><a href="https://arxiv.org/abs/1607.01759">https://arxiv.org/abs/1607.01759</a></li><li><a href="https://arxiv.org/abs/1408.5882">https://arxiv.org/abs/1408.5882</a></li><li><a href="https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf">https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf</a></li><li><a href="https://dl.acm.org/doi/10.5555/2886521.2886636">https://dl.acm.org/doi/10.5555/2886521.2886636</a></li><li><a href="https://www.aclweb.org/anthology/N16-1174.pdf">https://www.aclweb.org/anthology/N16-1174.pdf</a></li><li><a href="https://zhuanlan.zhihu.com/p/266364526">https://zhuanlan.zhihu.com/p/266364526</a></li><li><a href="https://cloud.tencent.com/developer/article/1389555">https://cloud.tencent.com/developer/article/1389555</a></li><li><a href="https://www.cnblogs.com/sandwichnlp/p/11698996.html">https://www.cnblogs.com/sandwichnlp/p/11698996.html</a></li><li><a href="https://github.com/jeffery0628/text_classification">https://github.com/jeffery0628/text_classification</a></li><li><a href="https://zhuanlan.zhihu.com/p/349086747">https://zhuanlan.zhihu.com/p/349086747</a></li><li><a href="https://zhuanlan.zhihu.com/p/35457093">https://zhuanlan.zhihu.com/p/35457093</a></li><li><a href="https://www.pianshen.com/article/4319299677/">https://www.pianshen.com/article/4319299677/</a></li><li><a href="https://www.zhihu.com/question/326770917/answer/698646465">https://www.zhihu.com/question/326770917/answer/698646465</a></li></ol>]]></content>
<categories>
<category> 自然语言处理 </category>
</categories>
<tags>
<tag> 文本分类 </tag>
<tag> 深度学习 </tag>
<tag> 浅层模型 </tag>
</tags>
</entry>
<entry>
<title>集成学习【二】</title>
<link href="/2021/07/22/ensemble2/"/>
<url>/2021/07/22/ensemble2/</url>
<content type="html"><![CDATA[<h1 id="随机森林"><a href="#随机森林" class="headerlink" title="随机森林"></a>随机森林</h1><p><img src="https://miro.medium.com/max/1204/1*iWHiPjPv0yj3RKaw0pJ7hA.png" alt=""></p><ol><li><p>随机森林算法背后的思想是群体智慧的体现,它通过随机的 <code>行采样</code> 和 <code>列采样</code> 构造不同的训练集,建立一个决策树森林,利用加权平均方式或多数表决的方式得到最后的预测结果,能够并行学习,对噪声和异常数据具有很好的过滤作用,因此有很广泛的应用。</p></li><li><p>随机森林的 <code>行采样</code> 和 <code>列采样</code> 都是为了减小模型之间的相关性使基学习器变得不同从而减小集成模型的方差。这种随机性会导致随机森林的偏差有所增加(相比于单棵决策树),因此随机森林的单棵树都会采用很深的决策树,并不进行剪枝操作,以减小每棵树的偏差,这使得每一棵决策树就是一个精通于某一个窄领域的专家(因为我们从全部特征中选择部分来让每一棵决策树学习),这样在随机森林中就有了很多个精通不同领域的专家,对一个新的问题(新的输入数据),可以用不同的角度去看待它,最终再通过投票或平均得到结果。这也正是群体智慧的体现。</p></li><li><p>随机森林是 <code>Bagging</code> 的一种扩展变体,与 <code>Bagging</code> 相比:</p><ul><li><p><code>Bagging</code> 中基学习器的 <code>多样性</code> 来自于样本扰动。> 样本扰动来自于对初始训练集的随机采样。</p></li><li><p>随机森林中的基学习器的多样性不仅来自样本扰动,还来自属性扰动。</p></li><li><p>传统决策树在选择划分属性时,是在当前结点的属性集合(假定有 $n$ 个属性)中选择一个最优属性。</p></li><li><p>随机森林中,对基决策树的每个结点,先从该结点的属性集合中随机选择一个包含 $k$ 个属性的子集,然后再从这个子集中选择一个最优属性用于划分。(通常建议 $k=log_2n$)</p></li></ul></li></ol><p><strong>这使得最终集成的泛化性能可以通过个体学习器之间差异度的增加而进一步提升。</strong></p><ol><li><code>Extra Tree</code></li></ol><p><code>Extra Tree</code>与<code>Random Forest</code>的十分相似,其主要区别如下:</p><ul><li><p><code>RF</code>会随机采样来作为子决策树的训练集,而<code>Extra Tree</code>每个子决策树采用原始数据集训练。</p></li><li><p><code>RF</code>在选择划分特征的阈值时会和传统决策树一样,基于信息增益、信息增益率、基尼系数、均方差等原则来选择最优> 的<code>threshold</code>。<code>Extra Tree</code>则是随机选择<code>threshold</code>来划分决策树。</p></li></ul><blockquote><p>由于<code>Extra Tree</code>是随机选择特征点进行划分,所以最后得到的决策树规模会大于<code>RF</code>生成的决策树,<code>Extra Tree</code>决策树的偏差更大,但方差会减少,泛化能力比<code>RF</code>更强。</p></blockquote><h1 id="AdaBoost"><a href="#AdaBoost" class="headerlink" title="AdaBoost"></a>AdaBoost</h1><ol><li><p><code>AdaBoost</code> 是基于 <code>Boosting</code> 的思想,通过多个弱分类器的线性组合来得到强分类器,训练时重点关注被错分的样本,准确率高的弱分类器权重大。</p></li><li><p>在训练过程中,它不改变所给的训练数据,而是不断改变训练数据权值的分布,使得被误分类的数据再后一轮的分类中受到更大的关注。</p></li><li><p>采用加权多数表决的方法,加大分类误差率小的弱分类器的权值,使其在最后的表决中起更大的作用,减小分类误差率大的弱分类器的权值,使其在最后的表决中起较小的作用。所有弱分类器的权值之和并不为 $1$,是通过最后结果的符号来决定实例的类别,该结果的绝对值表示分类的确信度。</p></li></ol><center><img src="./../img/adaboost1.png"> </center><center><img src="./../img/adaboost1.png"> </center><blockquote><p><code>Adaboost</code> 还有另外一种理解,即可以认为其模型是加法模型、损失函数为指数函数、学习算法为前向分步算法的二类分类学习方法。</p><p>加法模型:多个基函数线性组合得到的模型</p><p>前向分步算法:对于加法模型,从前往后,每一步只学习一个基函数及其系数,而不是一次性学习所有的基函数,从而简化优化的复杂度。</p></blockquote><h2 id="算法步骤"><a href="#算法步骤" class="headerlink" title="算法步骤"></a>算法步骤</h2><p>考虑如下形式的二分类训练集:</p><script type="math/tex; mode=display">(x^{(1)}, y^{(1)}), (x^{(2)}, y^{(2)}), \dots, (x^{(N)}, y^{(N)})</script><p>其中 $x^{(i)}\in R^d$ 为第 $i$ 个样本的特征向量,$y^{(i)}\in -1, +1$</p><p><code>AdaBoost</code>算法的步骤如下:</p><ol><li>初始化样本权重:</li></ol><script type="math/tex; mode=display">D_1=(w_{1,1}, w_{1,2}, \dots, w_{1,N}), w_{1,k}=\frac{1}{N},k=1,2,\dots,N</script><ol><li><p>对 $m=1,2,\dots,M$,重复以下操作,得到 $M$ 个学习器:</p><p> (1) 按照样本权重分布 $D_m$ 在训练集上得到第 $m$ 个基学习器 $G_m(x)$</p><script type="math/tex; mode=display">G_m(x):map(R^d) ~ to ~ {-1, +1}</script><p> (2) 计算 $G_m(x)$ 在训练集上的加权分类误差率:</p><script type="math/tex; mode=display">e_m=\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)}) \not= y^{(i)}) \tag{1}</script><p> (3) 计算 $G_m(x)$ 的系数:</p><script type="math/tex; mode=display">\alpha_m=\frac{1}{2}log(\frac{1-e_m}{e_m}) \tag{2}</script><p> (4)更新样本权重 $D_{m+1}$:</p><script type="math/tex; mode=display">D_{m+1}=(w_{m+1,1}, w_{m+1,2}, \dots, w_{m+1,N})</script><script type="math/tex; mode=display">w_{m+1,i}=\frac{w_{m,i}exp(-\alpha_my^{(i)}G_m(x^{(i)})}{Z_m} \tag{3}</script><p> 其中 $Z_m$ 为归一化因子:$Z_m=\sum_{i=1}^{N}w_{m,i}exp(-\alpha_my^{(i)}G_m(x^{(i)})$</p></li><li><p>构建最终的分类器的线性组合:</p></li></ol><script type="math/tex; mode=display">f(x)=\sum_{m=1}^{M}\alpha_mG_m(x)\tag{4}</script><ol><li>最终用于决策的分类器为:</li></ol><script type="math/tex; mode=display">G(x)=sign(f(x))=sign(\sum_{m=1}^{M}\alpha_mG_m(x)) \tag{5}</script><h2 id="AdaBoost算法的解释"><a href="#AdaBoost算法的解释" class="headerlink" title="AdaBoost算法的解释"></a><code>AdaBoost</code>算法的解释</h2><p>从理论上解释 <code>AdaBoost</code> 算法设置样本权重 $w_{m,i}$ 和分类器权重 $\alpha_m$ 的原因。</p><h3 id="前向分步算法"><a href="#前向分步算法" class="headerlink" title="前向分步算法"></a>前向分步算法</h3><p><code>AdaBoost</code> 算法的最终模型的表达式为:</p><script type="math/tex; mode=display">f(x)=\sum_{m=1}^{M}\alpha_mG_m(x)</script><p>可以看到,这是一个“加性模型”,我们希望这个模型在训练集上的经验误差最小,即:</p><script type="math/tex; mode=display">min\sum_{i=1}^{N}\ell(y^{(i)},f(x^{(i)})) \tag{6}</script><p>通常这是一个复杂的优化问题。前向分步算法求解这一优化问题的基本思路为:如果从前往后,每一步只学习一个基学习器<br>$G_m(x)$ 及其权重$\alpha_m$,不断迭代得到最终的模型,那么我们可以简化问题的复杂度。</p><p>具体的,当我们经过 $m-1$ 轮迭代得到最优模型 $f_{m-1}(x)$ 时,因为:</p><script type="math/tex; mode=display">f_m(x)=f_{m-1}(x)+\alpha_mG_m(x)\tag{7}</script><p>此时,优化目标为:</p><script type="math/tex; mode=display">min\sum_{i=1}^{N}\ell(y^{(i)},f_{m-1}(x^{(i)})+\alpha_mG_m(x^{(i)}))\tag{8}</script><h3 id="算法证明"><a href="#算法证明" class="headerlink" title="算法证明"></a>算法证明</h3><p>下面证明当损失函数为指数损失函数时,上述加性模型的学习步骤如上所示。</p><p>指数损失函数:</p><script type="math/tex; mode=display">\ell(y,f(x))=exp(-yf(x)) \tag{9}</script><blockquote><p>可以证明,指数损失时分类任务0/1损失函数的一致替代损失函数。由于指数损失函数有更好的数学性质,例如处处可微,所以我们用它替代0/1损失作为优化目标。</p></blockquote><p>将损失函数代入 $(8)$ 式,则优化目标为:</p><script type="math/tex; mode=display">\underset{\alpha_m,~G_m(x)}{argmin}\sum_{i=1}^{N}exp[-y^{(i)}(f_{m-1}(x^{(i)})+\alpha_mG_m(x^{(i)}))] \tag{10}</script><p>由于 $y^{(i)}f_{m-1}(x^{(i)})$ 与优化的变量无关,如果令:</p><script type="math/tex; mode=display">w_{m,i}=exp(-y^{(i)}f_{m-1}(x^{(i)})) \tag{11}</script><p>则 $(10)$ 等价于:</p><script type="math/tex; mode=display">\underset{\alpha_m,~G_m(x)}{argmin}\sum_{i=1}^{N}w_{m,i}exp(-y^{(i)}\alpha_mG_m(x^{(i)})) \tag{12}</script><blockquote><p>这里的 $w_{m,i}$ 其实与算法是一致的。</p></blockquote><p>我们分两步来求解 $(12)$ 所示的优化问题的最优解 $\alpha_m$ 和 $G_m(x)$:</p><ol><li>$\forall \alpha_m \gt 0$ 求 $\hat{G_m(x)}$:</li></ol><script type="math/tex; mode=display">\hat{G_m(x)}=\underset{G_m(x)}{argmin}\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)})\not =y^{(i)}) \tag{13}</script><ol><li>求解 $\hat{\alpha_m}$:</li></ol><script type="math/tex; mode=display">\begin{aligned}\sum_{i=1}^{N}w_{m,i}exp(-y^{(i)}\alpha_mG_m(x^{(i)}))&=exp(-\alpha_m)\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)})=y^{(i)}) \\&+ exp(\alpha_m)\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)})\not =y^{(i)}) \\&=[exp(\alpha_m)-exp(-\alpha_m)]\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)})\not =y^{(i)}) \\&+ exp(-\alpha_m)\sum_{i=1}^{N}w_{m,i}\end{aligned}</script><p>对上式关于 $\alpha_m$ 求导并令其为 $0$:</p><script type="math/tex; mode=display">[exp(\alpha_m)+exp(-\alpha_m)]\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)})\not =y^{(i)})-exp(-\alpha_m)\sum_{i=1}^{N}w_{m,i}=0 \tag{15}</script><p>解得:</p><script type="math/tex; mode=display">\hat{\alpha_m}=\frac{1}{2}log(\frac{1-e_m}{e_m}) \tag{16}</script><p>其中 $e_m$ 为加权误差率:</p><script type="math/tex; mode=display">e_m=\frac{\sum_{i=1}^{N}w_{m,i}I(G_m(x^{(i)}) \not= y^{(i)})}{\sum_{i=1}^{N}w_{m,i}} \tag{17}</script><p>可见,只需将权重进行归一化,便得到 <code>AdaBoost</code> 算法中的加权误差率</p><ol><li>最后来看看每一轮样本权值的更新:</li></ol><script type="math/tex; mode=display">\begin{aligned}w_{m+1,i}=exp(-y^{(i)}f_m(x^{(i)}))&=exp(-y^{(i)}[f_{m-1}(x^{(i)})+\alpha_mG_m(x^{(i)})]) \\&=w_{m,i}exp(-y^{(i)}\alpha_mG_m(x^{(i)})) \end{aligned}</script><blockquote><p>将权重进行归一化,便得到 <code>AdaBoost</code> 算法中的权重更新公式</p></blockquote><ol><li>为防止过拟合,<code>AdaBoost</code> 通常会加入正则化项。该正则化项称作步长或者学习率,定义为 $\eta$,此时模型的更新为:</li></ol><script type="math/tex; mode=display">f_m(x)=f_{m-1}(x)+\eta\alpha_mG_m(x) \tag{19}</script><h1 id="GBDT"><a href="#GBDT" class="headerlink" title="GBDT"></a>GBDT</h1><p><code>GBDT</code> 是基于 <code>Boosting</code> 的思想,串行地构造多棵决策树来进行数据的预测,它是<strong>在损失函数所在的函数空间中做梯度下降,即把待求的决策树模型当作参数,每轮迭代都去拟合损失函数在当前模型下的负梯度</strong>,从而使得参数朝着最小化损失函数的方向更新。</p><blockquote><p><code>GBDT</code> 可以看作是 <code>AdaBoost</code> 的一个推广,<code>AdaBoost</code> 是通过错分数据点来识别问题,通过调整错分数据点的权重来改进模型,<code>GBDT</code> 是通过负梯度来识别问题,通过计算负梯度来改进模型,实际上,负梯度绝对值大的样例同样会在之后的训练中受到更大的关注,因为它造成的损失更容易在最后的损失函数中占很大的比重,因此,需要更多地偏向于它去减小损失。这也是 <code>GBDT</code> 和 <code>AdaBoost</code> 相似的一个点,而相比 <code>AdaBoost</code>, <code>Gradient Boosting</code> 可以使用更多类型的损失函数,因此可以解决更多的问题。</p></blockquote><h1 id="XGBoost"><a href="#XGBoost" class="headerlink" title="XGBoost"></a>XGBoost</h1><p><code>XGBoost</code>是梯度提升树的一种高效系统实现,是对 <code>GBDT</code> 进一步的改进,包括对代价函数进行了二阶泰勒展开,在代价函数里加入了正则项,借鉴了随机森林的列采样方法,支持并行计算等。</p><ul><li><p>传统 <code>GBDT</code> 在优化时只用到一阶导数信息,<code>XGBoost</code> 则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。此外,<code>XGBoost</code> 工具支持自定义代价函数,只要函数可一阶和二阶求导。</p></li><li><p><code>XGBoost</code>在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点权重的 $L2$ 范数。从 <code>Bias-variance tradeoff</code> 角度来讲,正则项降低了模型的 <code>variance</code>,使学习出来的模型更加简单,防止过拟合,这也是 <code>XGBoost</code> 优于传统 <code>GBDT</code> 的一个特性。</p></li><li><p><code>Shrinkage</code>(缩减),相当于学习速率(<code>XGBoost</code> 中的<code>eta</code>)。<code>XGBoost</code> 在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把 <code>eta</code> 设置得小一点,然后迭代次数设置得大一点。</p></li><li><p>列抽样,<code>XGBoost</code> 借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算。</p></li><li><p>对缺失值的处理。对于特征的值有缺失的样本,<code>XGBoost</code> 可以自动学习出它的分裂方向。</p></li><li><p>支持并行。<code>XGBoost</code> 的并行不是树粒度的并行,<code>XGBoost</code> 也是一次迭代完才能进行下一次迭代的。<code>XGBoost</code> 的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),<code>XGBoost</code> 在训练之前,预先对数据进行了排序,然后保存为 <code>Block</code> 结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个 <code>Block</code> 结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。</p></li><li><p>可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以 <code>XGBoost</code> 还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。</p></li></ul><h1 id="LightGBM"><a href="#LightGBM" class="headerlink" title="LightGBM"></a>LightGBM</h1><p><code>LightGBM</code> 是一个实现 <code>GBDT</code> 算法的分布式高效框架。它通过 <code>leaf-wise</code> 分裂方法进行决策树的生成,通过基于直方图的算法寻找特征分割点,并支持并行学习,能够更高效的处理大数据。</p><p>要减少训练的复杂度,可以通过减少特征量和数据量来实现,即从行和列两个角度来减少数据,同时要尽可能少的影响最后的精度。在 <code>LightGBM</code> 中,就是这样做的,对应着 <code>GOSS</code> 和 <code>EFB</code>。</p><ul><li><p><code>Gradient-based One-Side Sampling (GOSS)</code>:<code>GBDT</code> 虽然没有数据权重,但每个数据实例有不同的梯度,根据计算信息增益的定义,梯度大的实例对信息增益有更大的影响,因此在下采样时,我们应该尽量保留梯度大的样本(预先设定阈值,或者最高百分位间),随机去掉梯度小的样本。此措施在相同的采样率下比随机采样获得更准确的结果,尤其是在信息增益范围较大时。</p></li><li><p><code>Exclusive Feature Bundling (EFB)</code>:通常在真实应用中,虽然特征量比较多,但是由于特征空间十分稀疏,许多特征几乎是互斥的,<code>EFB</code> 通过捆绑互斥的特征,并将捆绑问题归约到图着色问题,通过贪心算法求得近似解,以减少特征数量。</p></li><li><p>对于树的分裂方法,它通过 <code>leaf-wise</code> 分裂产生比 <code>level-wise</code> 分裂更复杂的树,能够实现更高的准确率。虽然这样有时候会导致过拟合,但可以通过设置 <code>max-depth</code> 参数来防止过拟合的发生。(每一次的生长都是选取分裂增益最高的节点,而不是对一层中的所有节点都进行分裂)。</p></li><li><p>其次,它使用基于直方图的算法,将连续的特征值分桶装进离散的箱子(<code>Bins</code>),并通过直方图做差加速计算兄弟节点的直方图,能够加速训练过程,并实现更少的内存占用。</p></li></ul>]]></content>
<categories>
<category> 机器学习 </category>
</categories>
<tags>
<tag> 随机森林 </tag>
<tag> GBDT </tag>
<tag> XGBoost </tag>
<tag> LightGBM </tag>
</tags>
</entry>
<entry>
<title>集成学习【一】</title>
<link href="/2021/07/20/ensemble1/"/>
<url>/2021/07/20/ensemble1/</url>
<content type="html"><![CDATA[<h1 id="集成学习"><a href="#集成学习" class="headerlink" title="集成学习"></a>集成学习</h1><ul><li><p>集成学习 <code>(ensemble learning)</code> 是通过构建及结合多个学习器来完成学习任务的,其一般结构为:</p><ul><li><p>先产生一组个体学习器 <code>(individual learner)</code>。个体学习器通常由一种或多种现有的学习算法从训练数据中产生</p></li><li><p>然后使用某种 <code>strategy</code> 将个体学习器结合起来。通常可以获得比单一学习器显著优越的泛化性能</p></li></ul></li><li><p>选取基学习器的准则:好而不同</p><ul><li><p>个体学习器要具备一定的准确性,预测能力不能太差</p></li><li><p>个体学习器之间要有差异</p></li></ul></li><li><p>一个简单的理论分析:</p><p>考虑一个二分类问题 $y\in -1, +1$,真实函数 $f(x)$ 及奇数 $M$ 个相互独立的犯错概率为 $\epsilon$ 的个体学习器 $h_i(x)$,我们使用简单的投票 <code>(voting)</code> 规则进行决策:</p><script type="math/tex; mode=display">H(x)=sign(\sum_{i=1}^{M}h_i(x))</script><p>由 $Hoeffding$ 不等式可知,集成学习的犯错概率满足:</p><script type="math/tex; mode=display">P(H(x) \not = f(x))= \sum_{k=1}^{(M+1)/2}C_{M}^{k}(1-\epsilon)^k\epsilon^{M-k} \le exp{(-\frac{1}{2}M(1-2\epsilon)^2)}</script><p>当基学习器的个数 $M$ 很大的时候,集成学习的犯错概率将接近于 $0$。这也很符合人们的直觉:大多数人同时犯错的概率将很低。</p></li><li><p>根据基学习器的生成方式,目前的集成学习方法大概分成两类:</p><ul><li>个体学习器之间存在强依赖关系,必须串行生成的序列化方法,每一轮迭代生成一个个体学习器,其中以 <code>Boosting</code> 为代表</li><li>个体学习器之间不存在强依赖关系, 可以同时生成的并行化方法,其中以 <code>Bagging</code> 和 <code>Random Forest</code> 为代表</li></ul></li></ul><p><img src="https://easy-ai.oss-cn-shanghai.aliyuncs.com/2020-02-13-weizhi.png" alt=""></p><blockquote><p>集成学习归属于机器学习,他是一种 <strong>训练思路</strong>,并不是某种具体的方法或者算法。</p></blockquote><h1 id="Boosting"><a href="#Boosting" class="headerlink" title="Boosting"></a>Boosting</h1><p><code>Boosting</code> 的主要思想是将弱的基学习器提升为强学习器,在分类问题中,它通过改变训练样本的权重学习多个分类器,并将这些分类器们进行线性组合来提高分类的能力。其主要步骤为:</p><ul><li><p>先用样本权重相等的训练集学习一个初始的基学习器</p></li><li><p>根据上一轮得到的学习器在训练集上的表现情况调整样本权重(<strong>提高被上一轮学习器分类错误的样本的权重,使之再下一轮训练中得到更多关注</strong>),然后据此学习一个新的基学习器</p></li><li><p>重复第二步直到得到$M$个学习器,最后的集成学习器为$M$个学习器的组合</p></li></ul><p><img src="https://easy-ai.oss-cn-shanghai.aliyuncs.com/2019-10-17-boosting.png" alt=""></p><h1 id="Bagging"><a href="#Bagging" class="headerlink" title="Bagging"></a>Bagging</h1><p><code>Bagging</code> 直接基于自主采样 <code>Boostrap Sampling</code>。自主采样的一般步骤为:</p><ul><li><p>先随机取出一个样本放入采样集中,再把该样本放回原始数据集。</p></li><li><p>这样经过$N$次随机采样操作,得到包含$N$个样本的采样集。</p></li></ul><blockquote><p>初始训练集中有的样本在采样集中多次出现,有的则从未出现。初始训练集中约有 $63.2\%$ 的样本出现在了采样集中。因此剩下的约 $36.8\%$ 的样本可用作验证集来对泛化性能进行包外估计。</p></blockquote><p><code>Bagging</code>方法的主要步骤为:</p><ul><li><p>经过 $M$ 轮自助采样,可以得到 $M$ 个包含 $N$ 个训练样本的采样集。</p></li><li><p>然后基于每个采样集训练出一个基学习器。</p></li><li><p>最后将这$M$个基学习器进行组合,得到集成模型。</p></li></ul><p><img src="https://easy-ai.oss-cn-shanghai.aliyuncs.com/2019-10-17-bagging.png" alt=""></p><h1 id="偏差-方差分解"><a href="#偏差-方差分解" class="headerlink" title="偏差-方差分解"></a>偏差-方差分解</h1><blockquote><script type="math/tex; mode=display">Error = bias + var_f + var_{\epsilon}</script><p>即泛化误差可以分解为偏差、方差和噪声之和:</p><p><strong>偏差</strong>:度量了学习算法的期望预测与真实结果之间的偏离程度,刻画了<strong>学习算法本身的拟合能力</strong>。</p><p><strong>方差</strong>:度量了训练集的变动所导致的学习性能的变化,刻画了<strong>数据扰动造成的影响</strong>。</p><p><strong>噪声</strong>:度量了在当前任务上任何学习算法所能达到的期望泛化误差的下界,刻画了<strong>学习问题本身的难度</strong>。</p><p><strong>偏差-方差分解表明:泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度共同决定的。</strong></p></blockquote><p>在 <code>Bagging</code> 和 <code>Boosting</code> 框架中,通过计算基模型的期望和方差我们可以得到模型整体的期望和方差。为了简化模型,我们假设基模型的期望为 $\mu$ ,方差 $\sigma^2$ ,模型的权重为 $r$,两两模型间的相关系数 $\rho$ 相等。由于 <code>Bagging</code> 和 <code>Boosting</code> 的基模型都是线性组成的,那么有:</p><script type="math/tex; mode=display">F=\sum_{i}^m r_if_i</script><script type="math/tex; mode=display">E(F) = \sum_{i}^m r_iE(f_i)</script><script type="math/tex; mode=display">var(F) = var(\sum_{i}^m r_if_i)</script><blockquote><p>对于 <code>Bagging</code> 来说,每个基模型的权重等于 $1/m$ 且期望近似相等。</p><ul><li><p>整体模型的期望等于基模型的期望,这也就意味着整体模型的偏差和基模型的偏差近似。</p></li><li><p>整体模型的方差小于等于基模型的方差,随着基模型数量增多,整体模型的方差减少,从而防止过拟合的能力增强,模型的准确度得到提高。</p></li><li><p>因此 <code>Bagging</code> 中的基模型一定要为强模型。</p></li></ul><p>对于 <code>Boosting</code> 来说,由于基模型共用同一套训练集,所以基模型间具有强相关性,因此整体模型的方差近似等于基模型的方差。</p></blockquote><ul><li><p><code>Bagging</code> 主要关注降低方差,它能平滑强学习器的方差。因此它在非剪枝决策树、神经网络等容易受到样本扰动的学习器上效果更为明显。</p></li><li><p><code>Boosting</code> 主要关注降低偏差,它能将一些弱学习器提升为强学习器。因此它在 <code>SVM</code>、<code>KNN</code> 等不容易受到样本扰动的学习器上效果更为明显。</p></li></ul>]]></content>
<categories>
<category> 机器学习 </category>
</categories>
<tags>
<tag> 集成学习 </tag>
<tag> bagging </tag>
<tag> boosting </tag>
</tags>
</entry>
</search>