TextCNN: Convolutional Neural Networks for Sentence Classification
本文是CNN应用在NLP领域的开山之作。TextCNN的成功并不是网络结构的成功,而是通过引入已经训练好的词向量在多个数据集上达到了超越benchmark的表现,证明了构造更好的embedding,是提升NLP各项任务的关键能力。
作者做了一系列实验,这些实验使用卷积神经网络(CNN)在预训练的词向量之上进行训练,用于句子级分类任务。作者发现,一个简单的CNN只需要很少的超参数调整和静态向量即可在多个基准测试上都能获得出色的结果。通过微调来学习特定于任务的向量可以进一步提高性能。此外,作者还建议对架构进行间的修改,以允许使用特定于任务的向量和静态向量。
论文所使用的模型结构图如下图,我们来看看作者说的一个简单的CNN,到底有多简单。

上图的最左边便是词嵌入矩阵,将输入的句子中的每个单词映射成一个向量表示,然后拼接起来成为一个7x5的矩阵,7就是输入句子的长度,5就是词向量的维度。然后使用尺寸为2x2,3x3,4x4的卷积核(每种尺寸的卷积核个数为2个)对词嵌入矩阵进行卷积操作,得到2x3(3是尺寸个数,2是每种尺寸的卷积核个数)个特征向量。再对得到的6个特征向量分别进行最大池化操作再拼接池化化的向量得到最终的特征向量。再将得到的特征向量经过一个全连接层将特征向量大小映射为类别个数大小并输出各个类别的概率大小。
没了,就是这么简单,再看下面的实验结果。CNN-rand是词嵌入矩阵随机初始化,然后通过反向传播更新输入层的各个单词对应的词向量。CNN-static是词嵌入矩阵使用与训练好的词向量矩阵,然后所有单词的词向量都保持静态(包括随机初始化的未知单词),仅学习模型的其他参数。CNN-non-static是使用预训练的词向量矩阵+通过反向传播的方式动态调整词向量嵌入矩阵的值。CNN-multichannel是具有两组词向量的模型,每组词向量都被视为一个“通道”,每个卷积核都应用于两个通道,但梯度仅通过其中一个通道反向传播,因此,该模型能够微调一组词向量,同时保持另一组词向量静态。

CNN-static的实验结果的数据说明了作者的发现:一个简单的CNN只需要很少的超参数调整和静态向量可以在多个基准测试上都能获得出色的结果。CNN-non-static 和CNN-static的对比说明了“通过微调来学习特定于任务的向量可以进一步提高性能”。
代码实现
接下来才是我真正想记录的部分,就是从TextCNN的实现去窥探NLP的深度学习的整体流程是怎么样的。我就拿TextCNN结构去做中文新闻文本主题分类为例。
1.数据集
毋庸置疑,数据集怎么搞我就不记录在这了,我这里使用的是THUCNews。

你先只需要关注class.txt、dev.txt、test.txt、train.txt文件。class.txt是新闻类别,里面的内容如下:

剩下的三个分别是验证集、测试集和训练集。拿训练集为例,我们看一看里面的内容:

一句新闻标题,然后跟了一个数字,数字代表类别与上面的class.txt里面的内容进行一一对应,比如0代表的就是finance,1代表的就是realty,依此类推。
2.embedding
图片输入到神经网络,输入的数据是它的像素,也就是一个二维矩阵。我们不可能直接将文本输入到神经网络中,那样网络是无法进行数学运算的,我们肯定要先将文本转换为数字,也就是上面我们所说到的词向量嵌入矩阵embedding。在说embedding怎么得到之前,我们先需要一个词表,就是类似于字典的东西。我们根据训练集中的文本数据制作一个词表,如果你不懂什么意思,直接看代码就行。
MAX_VOCAB_SIZE = 30000 # 词表长度限制
UNK, PAD = '<UNK>', '<PAD>' # 未知字
# 词表
def build_vocab(file_path, max_size = MAX_VOCAB_SIZE, min_frequency=1):
'''
:param file_path: 制作词表的文本数据路径,这儿我用的是训练集了,也就是训练集路径
:param max_size: 词表最大长度
:param min_frequency: 过滤掉训练集中出现次数少于min_frequency次数的字
:return:
'''
vocab_dic = {}
tokenizer = lambda x: [y for y in x] # 匿名函数,用来遍历获取一句话中的各个字
with open(file_path, 'r', encoding='UTF-8') as file:
for line in file:
line = line.strip()
if not line: # 该行为空
continue
content = line.split('\t')[0] # 获取新闻标题
for word in tokenizer(content):
vocab_dic[word] = vocab_dic.get(word, 0) + 1 # vocab_dict字典中如果有word变量代表的键,那么对应的值加1,否则值设为0
vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] >= min_frequency], key=lambda x: x[1], reverse=True)[:max_size]
vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}
vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic) + 1})
return vocab_dic
唉,整理一篇博客是真累,不想写了,等有空再梳理代码吧,如果有需要完整代码的可以私信me