Sentiment Analysis
🥰

Sentiment Analysis

Tags
Deep Learning
ANN
Pytorch
Description
Project 2 of THU course Introduction to AI

1. 网络结构

实现CNN,RNN,MLP三类模型,每一类模型尝试不同的网络结构。

1.1 MLP

BasicMLP
将词向量拉成一维,通过隐藏层、激活函数、最后进入分类器。
# 前向传播过程 def forward(self, x, _): x = x.view(x.shape[0], -1) # 256 * 3200 x = self.fc1(x) # 256 * 1024 x = self.dropout(x) x = self.sigmoid(x) x = self.fc2(x) # 256 * 256 x = self.sigmoid(x) x = self.fc3(x) return x
 
MMPMLP (min-max pooling MLP)
对词向量的每一维,分别取输入词向量的最大值,最小值。将合成向量通过全连接层、激活函数、分类器。
# 前向传播过程 def forward(self, x, _): # (bs, padding, embedding) # 256 * 100 x = torch.cat([torch.max(x, dim=1)[0], torch.min(x, dim=1)[0]], dim=1) x = self.fc1(x) # 256 * 1024 x = self.dropout(x) x = self.sigmoid(x) x = self.fc2(x) # 256 * 256 x = self.sigmoid(x) x = self.fc3(x) return x
notion image

1.2 CNNs

SimpleCNN
采用 Yoon Kim模型。
图3:SimpleCNN 网络结构
图3:SimpleCNN 网络结构
# 前向传播过程  def forward(self, x, _):      x = x.unsqueeze(1)      x = [conv(x) for conv in self.conv]      x = [self.activate(i.squeeze(3)) for i in x]      x = [F.max_pool1d(i, i.size(2)).squeeze(2) for i in x]      x = [self.dropout(y) for y in x]      x = torch.cat(x, 1)      x = self.fc(x)      return x
DeepCNN
与传统的CNN类似,在网络中多次堆叠卷积层、激活函数、缩小一倍的池化层。最后用一个全连接层进行分类。
图4:DeepCNN 网络结构
图4:DeepCNN 网络结构
# 前向传播过程 def forward(self, x, _): x = self.dropout(x) x = x.unsqueeze(1) x = self.conv0(x) x = x.squeeze(3) x = self.pool0(x) x = self.relu0(x) x = self.conv1(x) if (self.args['dep'] > 1): x = self.conv2(x) if (self.args['dep'] > 2): x = self.conv3(x) if (self.args['dep'] > 3): x = self.conv4(x) if (self.args['dep'] > 4): x = self.conv5(x) x = x.view([x.shape[0], -1]) x = self.fc(x) return x

1.3 RNNs

BasicRNN
将词向量一次输入循环神经网络,把循环神经网络最后一个时刻的输出输入分类器。
# 前向传播过程 def forward(self, x, x_len): # (bs, padding, embedding) (bs) ... x_padded = nn.utils.rnn.pack_padded_sequence(x, lengths=list(x_len[idx_sort]), batch_first=True) _, (ht, _) = self.lstm(x_padded) ht = ht.permute([1, 0, 2]) ht = ht.index_select(0, Variable(idx_unsort)) ht = ht.view([ht.shape[0], -1]) output = self.fc(ht) return output
MPRNN
将词向量依次输入循环神经网络,取每个时刻输出的最大值进入分类器。
# 前向传播过程 def forward(self, x, x_len): # (bs, padding, embedding) (bs) ... x_padded = nn.utils.rnn.pack_padded_sequence(x, lengths=list(x_len[idx_sort]), batch_first=True) states, (ht, _) = self.lstm(x_padded) states = nn.utils.rnn.pad_packed_sequence(states, batch_first=True)[0] states = states.index_select(0, Variable(idx_unsort)) states = torch.max(states, dim = 1)[0] output = self.fc(states) return output
图5:BasicRNN 及 MPRNN 网络结构
图5:BasicRNN 及 MPRNN 网络结构

2. 实现细节

2.1 输入数据

pytorch 的 dataloader 要求数据的位数严格一致,因此需要对较长的句子要进行截取,对较短的句子则要用0补全。如图为训练集,验证集和测试集中句子长度(分词后词语的数量)的分布。为了尽可能保留文章的全部信息,同时又不至于太大的计算开销,选取句子长度为64,由图可知这样可以覆盖超过97%的数据。
对于句中一些没有出现在 word2vec 中的词语,将这些位置填上0。
notion image

2.2 RNN

在RNN中,使用 pack_padded_sequence 填补0。该接口需要batch中各个数据的句长,为了实现整个pipline的通用性,在各个模型的前向过程中均加入参数 input_len

2.3 学习率递减

随着训练过程的进行,减小学习率有利于模型的收敛。
通过torch.optim.lr_scheduler.LambdaLR 实现学习率递减,其中 --dr 为递减速率,实验中RNNs选择0.5,其他模型选择0.95。
lambda1 = lambda epoch: options.dr ** epoch scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda = lambda1)

2.4 早停机制

通过参数 --early_stop--max_epoch 来决定是否进行训练早停(尚未达到设定的epoch数就停止训练)以及规定所容忍的模型性能连续没有提升的epoch数(即模型在validation set上准确率不提升)。
通过早停机制及--max_epoch 的设定,可缩短训练时间,节约训练资源,同时使模型精度的变化在容忍范围内。

2.5 模型保存与测试

对于每个epoch,当模型在validation set上的准确率达到新高时就保存该模型,同时删除之前的模型。当训练早停或到达指定 epoch 后,通过report(model, test_data_loader)函数加载训练过程中在验证集中表现最佳的模型,测试其在测试集上的准确率并计算 F-score。

3. 对比实验

3.1 Optimizer 的选择

常见的 optimizer 有 Adam 和 SGD 两种。但 SGD 需提供动量参数,设置不当则会导致模型不收敛。即便设置了合适的动量参数 SGD 的收敛速度仍慢于 Adam 如图,因此在一般情况下,optimizer 应选用 Adam.
notion image

3.2 随机种子的影响

使用不同的随机种子,运行10遍同一个 simpleCNN,结果如图,这10组模型在测试集上的准确率的标准差为0.5%, 极差为0.89%,小于模型之间的差距。

3.3 激活函数的选择

深度学习中常用的 activation function 有Sigmoid, RELU, TANH, PRELU等,使用不同的激活函数进行训练在收敛速度、准确率上有所差异。

4. 实验结果

选择3.1节中各类模型表现最好的结构计算F-score,结果如下。
notion image

5. 问题思考

  1. 实验中训练什么时候停止是最合适的?简要陈述你的实现方式,并试分析固定迭代次数与通过验证集调整等方法的优缺点。
    1. 实验最开始时,我通过设置不同大小的epoch数训练模型,发现实验中的6个模型局可以在50个epoch内实现收敛,这与训练集数据量较大有关,因此将 --epochs 的默认值设为50。
      固定迭代次数的实现较为简单,且如果最后检查一下是否有继续训练的必要,基本不会导致丢失最优解。但固定迭代次数也易造成过拟合和计算资源的浪费。
      因此,一方面是为了避免过拟合,另一方面为了缩短训练时间我采用了早停机制(见2.4节)。
  1. 实验参数的初始化是怎么做的?不同的方法适合哪些地方?(现有的初始化方法为零均值初始化,高斯分布初始化,正交初始化等。
    1. 在执行构造函数时,pytorch 会自动做初始化,因此不需要写额外的初始化代码。通过查阅pytorch源码可以发现,大多数 layer 都使用零均值初始化。
      在CNN中,不能把所有权重初始化为0。因为如果所有 channel 的初值相同,则在反向传播过程中变化也相同,失去了设置多个 channel 的意义。
      对于层数较多的神经网络,一般采用高斯分布初始化 + relu 激活函数。
  1. 过拟合是深度学习常见的问题,有什么方法可以防止训练过程陷入过拟合
    1. 早停机制,dropout,正则化,实验中采用了前两种
  1. 试分析CNN,RNN,MLP三者的优缺点
    1. MLP 参数数量大,但由于深度小,参数复用率低,运行速度和收敛速度是三类模型中最快的。但它的过拟合问题最为严重。MMPMLP 是增加了池化层的 MLP,参数数量大幅减小,但由于 pooling 而丢失过多信息,无法达到很高的精度。
      CNN 类模型的准确率最高,且训练速度快于 RNN,与 MLP 相近。
      RNN 对算力要求较高,训练速度慢。因为想要记住文章之前出现过的内容,中间层必须足够大。MPRNN 使用了中间过程中的输出,在一定程度上减小了对记忆深度的要求,因而较 BasicRNN有较好的效果。
      三类模型特点如下表
      模型
      运行速度
      模型大小
      收敛速度
      准确率
      MLP
      CNN
      RNN
 

Loading Comments...