目录

  • 一、系列文章
  • 二、TextCNN介绍
  • 2.1 一些细节
  • 2.2 搭建TextCNN
  • 三、训练&测试


一、系列文章

  1. 情感分析系列(一)——IMDb数据集及其预处理
  2. 情感分析系列(二)——使用BiLSTM进行情感分析

二、TextCNN介绍

TextCNN架构:



RNN情感分析 textcnn情感分析_textcnn


设一个序列的长度为 RNN情感分析 textcnn情感分析_自然语言处理_02,嵌入维度为 RNN情感分析 textcnn情感分析_textcnn_03,则该序列的嵌入矩阵的形状为 RNN情感分析 textcnn情感分析_textcnn_04。TextCNN对该嵌入矩阵做一维卷积,视嵌入维度为通道维度,则输入通道数为 RNN情感分析 textcnn情感分析_textcnn_03。不妨设输出通道数为 RNN情感分析 textcnn情感分析_深度学习_06,卷积核的大小为 RNN情感分析 textcnn情感分析_python_07,因此卷积之后会得到形状为 RNN情感分析 textcnn情感分析_textcnn_08 的矩阵。对该矩阵应用最大时间汇聚(取每一列的最大元素)会得到一个长度为 RNN情感分析 textcnn情感分析_深度学习_06

一般地,我们会做多个一维卷积,不妨设输出通道数为 RNN情感分析 textcnn情感分析_RNN情感分析_10,与之对应的卷积核的大小为 RNN情感分析 textcnn情感分析_textcnn_11,则卷积之后会得到 RNN情感分析 textcnn情感分析_RNN情感分析_12 个形状为 RNN情感分析 textcnn情感分析_textcnn_13 的矩阵,分别对这 RNN情感分析 textcnn情感分析_RNN情感分析_12 个矩阵应用最大时间汇聚可得到 RNN情感分析 textcnn情感分析_RNN情感分析_12 个长度为 RNN情感分析 textcnn情感分析_自然语言处理_16 的向量,将这些向量拼在一起可得到一个长度为 RNN情感分析 textcnn情感分析_textcnn_17 的向量,最后将该向量丢进全连接层 nn.Linear(sum(c_i), 2) 即可得到分类结果。

需要注意的是,TextCNN实际上使用了两个嵌入矩阵,这两个矩阵均采用相同的初始化方式(使用预训练的词向量,word2vec或GloVe),不同之处在于,其中一个矩阵被“冻结”,不参与训练,而另一个矩阵参与训练。

TextCNN 设置了三大小的卷积核,分别为 RNN情感分析 textcnn情感分析_RNN情感分析_18,每种大小下都有两个卷积核,其中一个卷积核作用于被冻结的嵌入矩阵,另一个卷积核作用于没有被冻结的嵌入矩阵。六个卷积核的输出通道数均为 RNN情感分析 textcnn情感分析_textcnn_19,因此卷积之后会得到六个矩阵,形状分别为:

RNN情感分析 textcnn情感分析_textcnn_20

对它们应用最大时间汇聚然后进行拼接可得到一个长度为 RNN情感分析 textcnn情感分析_深度学习_21 的向量,所以最后的全连接层为 nn.Linear(600, 2)

2.1 一些细节

细节一: 既然对于同一个序列有两个嵌入矩阵,那么能否把它们当作一个通道数为 RNN情感分析 textcnn情感分析_深度学习_22 ,高度为 RNN情感分析 textcnn情感分析_自然语言处理_02,宽度为 RNN情感分析 textcnn情感分析_textcnn_03

我们先来回顾一下原始过程。设卷积核大小为 RNN情感分析 textcnn情感分析_python_07,输出通道数为 RNN情感分析 textcnn情感分析_深度学习_06,则我们需要两个这种配置的卷积核以便让它们各自作用于不同的嵌入矩阵。卷积之后会得到两个不同的但形状均为 RNN情感分析 textcnn情感分析_textcnn_08 的矩阵,并且在这个过程中,两个嵌入矩阵的元素不会发生“交互”。

再来看下二维卷积的情形。此时卷积核的大小为 RNN情感分析 textcnn情感分析_深度学习_28,输入通道数为 RNN情感分析 textcnn情感分析_深度学习_22,输出通道数为 RNN情感分析 textcnn情感分析_深度学习_06,卷积之后只会得到一个形状为 RNN情感分析 textcnn情感分析_textcnn_08 的矩阵,并且在这个过程中,两个嵌入矩阵的元素发生了“交互”(卷积时,两个嵌入矩阵的元素会和卷积核的权重按元素相乘再相加),这显然是不对的。

细节二: 能否将两个嵌入矩阵沿列方向拼接起来形成一个大的嵌入矩阵,然后只用一个卷积核去做卷积呢?

此情形下,输入通道数为 RNN情感分析 textcnn情感分析_RNN情感分析_32,输出通道数为 RNN情感分析 textcnn情感分析_深度学习_06,进行卷积时,两个嵌入矩阵的元素仍然发生了“交互”,这仍然是不对的。

2.2 搭建TextCNN

class TextCNN(nn.Module):
    def __init__(self, vocab, embed_size=100, kernel_sizes=[3, 4, 5], num_channels=[100] * 3):
        super().__init__()
        self.glove = GloVe(name="6B", dim=100)
        self.unfrozen_embedding = nn.Embedding.from_pretrained(self.glove.get_vecs_by_tokens(vocab.get_itos()), padding_idx=vocab['<pad>'])
        self.frozen_embedding = nn.Embedding.from_pretrained(self.glove.get_vecs_by_tokens(vocab.get_itos()),
                                                             padding_idx=vocab['<pad>'],
                                                             freeze=True)

        self.convs_for_unfrozen = nn.ModuleList()
        self.convs_for_frozen = nn.ModuleList()
        for out_channels, kernel_size in zip(num_channels, kernel_sizes):
            self.convs_for_unfrozen.append(nn.Conv1d(in_channels=embed_size, out_channels=out_channels, kernel_size=kernel_size))
            self.convs_for_frozen.append(nn.Conv1d(in_channels=embed_size, out_channels=out_channels, kernel_size=kernel_size))

        self.pool = nn.AdaptiveMaxPool1d(1)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        self.fc = nn.Linear(sum(num_channels) * 2, 2)

        self.apply(self._init_weights)

    def forward(self, x):
        x_unfrozen = self.unfrozen_embedding(x).transpose(1, 2)  # (batch_size, embed_size, seq_len)
        x_frozen = self.frozen_embedding(x).transpose(1, 2)  # (batch_size, embed_size, seq_len)
        # 池化后得到的向量
        pooled_vector_for_unfrozen = [self.pool(self.relu(conv(x_unfrozen))).squeeze()
                                      for conv in self.convs_for_unfrozen]  # shape of each element: (batch_size, 100)
        pooled_vector_for_frozen = [self.pool(self.relu(conv(x_frozen))).squeeze()
                                    for conv in self.convs_for_frozen]  # shape of each element: (batch_size, 100)
        # 将向量拼接起来后得到一个更长的向量                            
        feature_vector = torch.cat(pooled_vector_for_unfrozen + pooled_vector_for_frozen, dim=-1)  # (batch_size, 600)
        output = self.fc(self.dropout(feature_vector))  # (batch_size, 2)
        return output

    def _init_weights(self, m):
    	# 仅对线性层和卷积层进行xavier初始化
        if type(m) in (nn.Linear, nn.Conv1d):
            nn.init.xavier_uniform_(m.weight)

三、训练&测试

训练&测试的代码和之前的几乎相同,但对于TextCNN,这里主要做了以下几点改动:

NUM_EPOCHS = 60
optimizer = torch.optim.Adam(model.parameters(), lr=0.0009, weight_decay=5e-4)

最终结果:

Accuracy: 0.8698

可以看出TextCNN和使用了预训练词向量的BiLSTM相差无几,但前者的训练速度要快很多。