## 目录
- [1. 什么是textRNN](#1-什么是textrnn)
  - [1.1 textRNN的原理](#11-textrnn的原理)
- [2. textRNN网络结构](#2-textrnn网络结构)
  - [2.1 structure 1](#21-structure-1)
  - [2.2 structure 2](#22-structure-2)
  - [2.3 总结](#23-总结)
- [3. 什么是textCNN](#3-什么是textcnn)
  - [3.1 ⼀维卷积层](#31-维卷积层)
  - [3.2 时序最⼤池化层](#3-2-时序最池化层)
  - [3.3 textCNN模型](#33-textcnn模型)
- [4. 代码实现](#4-代码实现)
- [5. 参考文献](#5-参考文献)

## 1. 什么是textRNN

**textRNN指的是利用RNN循环神经网络解决文本分类问题**，文本分类是自然语言处理的一个基本任务，试图推断出给定文本(句子、文档等)的标签或标签集合。

文本分类的应用非常广泛，如：

- 垃圾邮件分类：2分类问题，判断邮件是否为垃圾邮件
- 情感分析：2分类问题：判断文本情感是积极还是消极；多分类问题：判断文本情感属于{非常消极，消极，中立，积极，非常积极}中的哪一类。
- 新闻主题分类：判断一段新闻属于哪个类别，如财经、体育、娱乐等。根据类别标签的数量，可以是2分类也可以是多分类。
- 自动问答系统中的问句分类
- 社区问答系统中的问题分类：多标签多分类(对一段文本进行多分类，该文本可能有多个标签)，如知乎看山杯
- 让AI做法官：基于案件事实描述文本的罚金等级分类(多分类)和法条分类(多标签多分类)
- 判断新闻是否为机器人所写：2分类



### 1.1 textRNN的原理

在一些自然语言处理任务中，当对序列进行处理时，我们一般会采用循环神经网络RNN，尤其是它的一些变种，如LSTM(更常用)，GRU。当然我们也可以把RNN运用到文本分类任务中。

这里的文本可以一个句子，文档(短文本，若干句子)或篇章(长文本)，因此每段文本的长度都不尽相同。在对文本进行分类时，我们一般会指定一个固定的输入序列/文本长度：该长度可以是最长文本/序列的长度，此时其他所有文本/序列都要进行填充以达到该长度；该长度也可以是训练集中所有文本/序列长度的均值，此时对于过长的文本/序列需要进行截断，过短的文本则进行填充。总之，要使得训练集中所有的文本/序列长度相同，该长度除之前提到的设置外，也可以是其他任意合理的数值。在测试时，也需要对测试集中的文本/序列做同样的处理。

首先我们需要对文本进行分词，然后指定一个序列长度n（大于n的截断，小于n的填充），并使用词嵌入得到每个词固定维度的向量表示。对于每一个输入文本/序列，我们可以在RNN的每一个时间步长上输入文本中一个单词的向量表示，计算当前时间步长上的隐藏状态，然后用于当前时间步骤的输出以及传递给下一个时间步长并和下一个单词的词向量一起作为RNN单元输入，然后再计算下一个时间步长上RNN的隐藏状态，以此重复...直到处理完输入文本中的每一个单词，由于输入文本的长度为n，所以要经历n个时间步长。

基于RNN的文本分类模型非常灵活，有多种多样的结构。接下来，我们主要介绍两种典型的结构。



## 2. textRNN网络结构

### 2.1 structure 1

**流程**：embedding--->BiLSTM--->concat final output/average all output----->softmax layer

结构图如下图所示：

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_8-57-47.png)

一般取前向/反向LSTM在最后一个时间步长上隐藏状态，然后进行拼接，在经过一个softmax层(输出层使用softmax激活函数)进行一个多分类；或者取前向/反向LSTM在每一个时间步长上的隐藏状态，对每一个时间步长上的两个隐藏状态进行拼接，然后对所有时间步长上拼接后的隐藏状态取均值，再经过一个softmax层(输出层使用softmax激活函数)进行一个多分类(2分类的话使用sigmoid激活函数)。

**上述结构也可以添加dropout/L2正则化或BatchNormalization 来防止过拟合以及加速模型训练。**



### 2.2 structure 2

流程：embedding-->BiLSTM---->(dropout)-->concat ouput--->UniLSTM--->(droput)-->softmax layer

结构图如下图所示：

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_9-1-2.png)

与之前结构不同的是，在双向LSTM(上图不太准确，底层应该是一个双向LSTM)的基础上又堆叠了一个单向的LSTM。把双向LSTM在每一个时间步长上的两个隐藏状态进行拼接，作为上层单向LSTM每一个时间步长上的一个输入，最后取上层单向LSTM最后一个时间步长上的隐藏状态，再经过一个softmax层(输出层使用softamx激活函数，2分类的话则使用sigmoid)进行一个多分类。



### 2.3 总结

TextRNN的结构非常灵活，可以任意改变。比如把LSTM单元替换为GRU单元，把双向改为单向，添加dropout或BatchNormalization以及再多堆叠一层等等。TextRNN在文本分类任务上的效果非常好，与TextCNN不相上下，但RNN的训练速度相对偏慢，一般2层就已经足够多了。



## 3. 什么是textCNN

在“卷积神经⽹络”中我们探究了如何使⽤⼆维卷积神经⽹络来处理⼆维图像数据。在之前的语⾔模型和⽂本分类任务中，我们将⽂本数据看作是只有⼀个维度的时间序列，并很⾃然地使⽤循环神经⽹络来表征这样的数据。**其实，我们也可以将⽂本当作⼀维图像，从而可以⽤⼀维卷积神经⽹络来捕捉临近词之间的关联。本节将介绍将卷积神经⽹络应⽤到⽂本分析的开创性⼯作之⼀：textCNN**。



### 3.1 ⼀维卷积层

在介绍模型前我们先来解释⼀维卷积层的⼯作原理。与⼆维卷积层⼀样，⼀维卷积层使⽤⼀维的互相关运算。在⼀维互相关运算中，卷积窗口从输⼊数组的最左⽅开始，按从左往右的顺序，依次在输⼊数组上滑动。当卷积窗口滑动到某⼀位置时，窗口中的输⼊⼦数组与核数组按元素相乘并求和，得到输出数组中相应位置的元素。如下图所⽰，输⼊是⼀个宽为7的⼀维数组，核数组的宽为2。可以看到输出的宽度为 7 - 2 + 1 = 6，且第⼀个元素是由输⼊的最左边的宽为2的⼦数组与核数组按元素相乘后再相加得到的：0 *×* 1 + 1 × 2 = 2。 

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_9-35-48.png)

多输⼊通道的⼀维互相关运算也与多输⼊通道的⼆维互相关运算类似：在每个通道上，将核与相应的输⼊做⼀维互相关运算，并将通道之间的结果相加得到输出结果。下图展⽰了含3个输⼊ 通道的⼀维互相关运算，其中阴影部分为第⼀个输出元素及其计算所使⽤的输⼊和核数组元素： 0 *×* 1 + 1 *×* 2 + 1 *×* 3 + 2 *×* 4 + 2 *×* (-1) + 3 *×* (-3) = 2。 

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_9-38-34.png)

由⼆维互相关运算的定义可知，多输⼊通道的⼀维互相关运算可以看作单输⼊通道的⼆维互相关运算。如下图所⽰，我们也可以将上图中多输⼊通道的⼀维互相关运算以等价的单输⼊通道的⼆维互相关运算呈现。这⾥核的⾼等于输⼊的⾼。下图的阴影部分为第⼀个输出元素及其计算所使⽤的输⼊和核数组元素：2 *×* (-1) + 3 *×* (-3) + 1 *×* 3 + 2 *×* 4 + 0 *×* 1 + 1 *×* 2 = 2。 

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_9-52-37.png)

以上都是输出都只有⼀个通道。我们在“多输⼊通道和多输出通道”⼀节中介绍了如何在⼆维卷积层中指定多个输出通道。类似地，我们也可以在⼀维卷积层指定多个输出通道，从而拓展卷积层中的模型参数。



### 3. 2 时序最⼤池化层

类似地，我们有⼀维池化层。textCNN中使⽤的时序最⼤池化（max-over-time pooling）层实际上对应⼀维全局最⼤池化层：假设输⼊包含多个通道，各通道由不同时间步上的数值组成，各通道的输出即该通道所有时间步中最⼤的数值。因此，时序最⼤池化层的输⼊在各个通道上的时间步数可以不同。为提升计算性能，我们常常将不同⻓度的时序样本组成⼀个小批量，并通过在较短序列后附加特殊字符（如0）令批量中各时序样本⻓度相同。这些⼈为添加的特殊字符当然是⽆意义的。由于时序最⼤池化的主要⽬的是抓取时序中最重要的特征，它通常能使模型不受⼈为添加字符的影响。



### 3.3 textCNN模型

textCNN模型主要使⽤了⼀维卷积层和时序最⼤池化层。假设输⼊的⽂本序列由*n*个词组成，每个词⽤*d*维的词向量表⽰。那么输⼊样本的宽为*n*，⾼为1，输⼊通道数为*d*。textCNN的计算主要分为以下⼏步：

1. 定义多个⼀维卷积核，并使⽤这些卷积核对输⼊分别做卷积计算。宽度不同的卷积核可能会捕捉到不同个数的相邻词的相关性。
2. 对输出的所有通道分别做时序最⼤池化，再将这些通道的池化输出值连结为向量。
3. 通过全连接层将连结后的向量变换为有关各类别的输出。这⼀步可以使⽤丢弃层应对过拟合。 

下图⽤⼀个例⼦解释了textCNN的设计。这⾥的输⼊是⼀个有11个词的句⼦，每个词⽤6维词向量表⽰。因此输⼊序列的宽为11，输⼊通道数为6。给定2个⼀维卷积核，核宽分别为2和4，输出通道数分别设为4和5。因此，⼀维卷积计算后，4个输出通道的宽为 11 - 2 + 1 = 10，而其他5个通道的宽为 11 - 4 + 1 = 8。尽管每个通道的宽不同，我们依然可以对各个通道做时序最⼤池化，并将9个通道的池化输出连结成⼀个9维向量。最终，使⽤全连接将9维向量变换为2维输出，即正⾯情感和负⾯情感的预测。 

![](https://gitee.com/kkweishe/images/raw/master/ML/2019-8-26_11-4-51.png)



## 4. 代码实现

清华新闻分类数据集下载：[https://www.lanzous.com/i5t0lsd](https://www.lanzous.com/i5t0lsd)

- [textRNN实现新闻分类](https://github.com/NLP-LOVE/ML-NLP/blob/master/NLP/16.4%20textRNN%20%26%20textCNN/textRNN.ipynb)
- [textCNN实现新闻分类](https://github.com/NLP-LOVE/ML-NLP/blob/master/NLP/16.4%20textRNN%20%26%20textCNN/textCNN.ipynb)



## 5. 参考文献

- [基于TextRNN的文本分类原理](https://blog.csdn.net/sdu_hao/article/details/88080791)
- [动手学深度学习](https://www.lanzous.com/i5lqo4f)



------

> 作者：[@mantchs](https://github.com/NLP-LOVE/ML-NLP)
>
> GitHub：[https://github.com/NLP-LOVE/ML-NLP](https://github.com/NLP-LOVE/ML-NLP)
>
> 欢迎大家加入讨论！共同完善此项目！群号：【541954936】<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=863f915b9178560bd32ca07cd090a7d9e6f5f90fcff5667489697b1621cecdb3"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="NLP面试学习群" title="NLP面试学习群"></a>


