论文复现TextCNN(基于PyTorch)

前言

复现经典论文,可以回顾深度学习和自然语言处理基础,强化对常用模型的理解,体会经典模型中蕴含的设计哲学,为后续实现自己的模型打下良好基础。如果不深入了解深度学习原理,对基本的模型都不能实现,只会拿开源代码抄抄改改,就会陷入盲目调参的误区,最终浪费时间又一无所获。因此,我们要避免成为调包侠、调参侠,强化对模型的主人翁意识,做到调参有的放矢,方能事半功倍。自己动手实现一个模型并达到或接近原论文效果,正是锻炼动手能力,提升深度学习基本功的好方法。

TextCNN

TextCNN(论文Arxiv地址)是CNN用于文本分类的开山之作。据Google Scholar统计,目前该论文引用量已达9000多次,足见其影响之深远。学习深度学习自然语言处理的人一定都听说过它的大名,也在初入NLP大门的时候运行过相关代码。但是一般都是简单看一下论文,跑一下示例代码,得出个差不多的结果就可以了,没有进一步深究细节。毕竟,在如此内卷的今天,大家时间都很宝贵,没有那么多心思花费在基本模型上。
殊不知,由于篇幅所限,论文中不能把模型的每一个细节都解释清楚,很多地方只有在代码里才能完美呈现。因此,使用作者的源代码才能真正实现复现论文模型的全貌。而原始论文的代码是基于Theano,这个框架几乎已经没人使用,因此我们使用新的框架PyTtorch来复现论文,争取通过这样的方式理解论文的每一个细节,对论文源代码进行原汁原味的还原。

数据预处理

在正式写模型之前,我们需要进行数据的预处理。
之前有句流传很广的话“数据决定了模型的上限, 而调参只不过是在逼近这个上限而已”,可见数据在深度学习与自然语言处理领域中的重要性。本文用到了MR,SST,SST2,SUBJ,TREC,TR,MPQA共7个数据集来进行文本分类,大多都是情感二分类。下面说几个需要注意的点:

  • 字符串预处理
    拿到原始数据集的文本后,在tokenize化之前,需要对字符串做出初步的处理,这里我们参考了论文原文作者的实现方式,即SST数据集、MR以及其他数据集分别使用不同的处理方式,具体代码见https://github.com/yoonkim/CNN_sentence/blob/master/process_data.py的97117行。
  • 数据集划分
    已经分好的train,testvalid的数据集,直接使用原始的切分方式即可,最终的评价方式就是模型在测试集上的得分。对于未做切分的数据集,则进行10折交叉验证,10份测试结果的均值作为最终得分
  • 构建Embedding
    原论文使用Google原版的word2vec,权重数据下载地址https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit。对于没有在词典中的词语,采用随机初始化的方式,这里随机初始化不是随便初始化,而使用均匀分布效果更好。论文作者使用了[-0.25,0.25]的均匀分布,我使用的是[-0.1,0.1]的均匀分布,相比之前使用的正态分布随机初始化可以至少提高1个百分点。
    np.random.uniform_(-0.1,0.1,300)
  • 使用预训练词向量时,训练、测试、验证集的词都加入词表,加载其对应的词向量,效果会更好。

代码实现

在仔细阅读之后,我动手实现了该论文的完整代码,代码位于这里。深度学习框架实用PyTorch,因为PyTorch1.5以上不能完全保证两次结果一致,影响控制变量,因此我使用1.4版本的PyTorch。在实现过程中我参考了以下代码和教程:

  1. TextCNN论文作者原版,使用Theano框架,地址:https://github.com/yoonkim/CNN_sentence
  2. Tensorflow版,地址:https://github.com/dennybritz/cnn-text-classification-tf
  3. Torch版,来自Harvard NLP,地址:https://github.com/harvardnlp/sent-conv-torch,lua语言
  4. 数据预处理参考Diaobo的范式,地址:https://github.com/DianboWork/Graph4CNER
    言归正传,本篇论文提出了基于卷积神经网络(Convolutional neural network)进行文本分类的模型。
  • 编码
    该模型首先使用词向量对句子进行编码,词向量矩阵为 n × e n\times e n×e n n n e e e分别是词表大小和词向量的维度;句子长度为 l l l,经过词向量编码后,句子的表示为 l × e l\times e l×e,表示 l l l个单词,每个单词是长度为 e e e的向量
    词向量定义:
self.embeddings = nn.Embedding(alphabet.size(), config.emb_dim) # l x e

词向量编码:

input = self.embeddings(input).unsqueeze(1) 

这里需要注意,input的输入维度是(batch_size, seq_length),而输出维度是(batch_size, seq_length, emb_size)。标准的CNN模型针对的是图片,图片的维度是batch_size x channel_size x width x height,其中channel_size代表通道数,可以理解为RGB三原色,每个像素点都有在RGB三个图层上的数值。
因此,为了使用原始的CNN模块的接口,我们也需要将输入改为四个维度,unsqueeze(1)代表在第一维上作扩展,这样,输出的维度就变成了(batch_size, 1, seq_length, emb_size),和普通图片的维度一致,就可以使用CNN接口进行卷积了。

  • 卷积
    使用一个宽度等于词向量维度,长度 h h h可变的卷积核对矩阵进行扫描,每一次扫描得到一个$l - h + 1 的 向 量 , 使 用 的向量,使用 使k 个 卷 积 核 扫 描 , 得 到 结 果 向 量 的 维 度 为 个卷积核扫描,得到结果向量的维度为 k\times (l-h+1) , 在 行 方 向 使 用 M a x − P o o l i n g 得 到 每 一 行 的 最 大 值 , 输 出 ,在行方向使用Max-Pooling得到每一行的最大值,输出 使MaxPoolingk$维向量。经过线性变换再接Softmax进行二分类即可
    论文复现TextCNN(基于PyTorch)_第1张图片

复现结果

目前在SST-2和MR数据集上测试了复现效果,结果如下表所示。源代码中实际上记录了两个最优值,分别是测试集全局的最优和验证集最优时测试集的结果,一般来说应该取后者,我们这里记录的是前者,当然,在我们的测试结果中这两个差别不大,一般不超过1个百分点,甚至完全一致。可以发现,尽管我们已经取了全局的最优,仍然无法完全复现或超越原论文的结果。路漫漫其修远兮,吾将上下而求索。
以SST2为例,CNN-static和原论文效果最接近,相差约1个百分点,而CNN-rand和CNN-fine-tuned则和原论文相差2个百分点左右;复现效果基本满意,还有提升空间。
复现结果(括号内为复现结果)

模型选项 SST-2 MR
CNN-rand 82.7 (80.78) 76.1 (77.10)
CNN-static 86.8 (85.83) 81.0 (80.49)
CNN-fine-tuned 87.2 (84.68) 81.5 (79.88)
Bert-base-cased 93.5 (90.57)

你可能感兴趣的:(深度学习,人工智能,神经网络,nlp)