情感分析:使用卷积神经网络


文档摘要

情感分析:使用卷积神经网络 :label: 在 :numref: 中,我们探讨了使用二维卷积神经网络处理二维图像数据的机制,并将其应用于局部特征,如相邻像素。虽然卷积神经网络最初是为计算机视觉设计的,但它也被广泛用于自然语言处理。简单地说,只要将任何文本序列想象成一维图像即可。通过这种方式,一维卷积神经网络可以处理文本中的局部特征,例如$n$元语法。 本节将使用textCNN模型来演示如何设计一个表示单个文本 :cite: 的卷积神经网络架构。与 :numref: 中使用带有GloVe预训练的循环神经网络架构进行情感分析相比, :numref: 中唯一的区别在于架构的选择。

情感分析:使用卷积神经网络

🏷sec_sentiment_cnn

在 :numref:chap_cnn中,我们探讨了使用二维卷积神经网络处理二维图像数据的机制,并将其应用于局部特征,如相邻像素。虽然卷积神经网络最初是为计算机视觉设计的,但它也被广泛用于自然语言处理。简单地说,只要将任何文本序列想象成一维图像即可。通过这种方式,一维卷积神经网络可以处理文本中的局部特征,例如n元语法。

本节将使用textCNN模型来演示如何设计一个表示单个文本 :cite:Kim.2014的卷积神经网络架构。与 :numref:fig_nlp-map-sa-rnn中使用带有GloVe预训练的循环神经网络架构进行情感分析相比, :numref:fig_nlp-map-sa-cnn中唯一的区别在于架构的选择。

将GloVe放入卷积神经网络架构进行情感分析
🏷fig_nlp-map-sa-cnn

from d2l import mxnet as d2l from mxnet import gluon, init, np, npx from mxnet.gluon import nn npx.set_np() batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)
#@tab pytorch from d2l import torch as d2l import torch from torch import nn batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)
#@tab paddle from d2l import paddle as d2l import warnings warnings.filterwarnings("ignore") import paddle from paddle import nn batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)

一维卷积

在介绍该模型之前,让我们先看看一维卷积是如何工作的。请记住,这只是基于互相关运算的二维卷积的特例。

一维互相关运算。阴影部分是第一个输出元素以及用于输出计算的输入和核张量元素:0\times1+1\times2=2
🏷fig_conv1d

如 :numref:fig_conv1d中所示,在一维情况下,卷积窗口在输入张量上从左向右滑动。在滑动期间,卷积窗口中某个位置包含的输入子张量(例如, :numref:fig_conv1d中的01)和核张量(例如, :numref:fig_conv1d中的12)按元素相乘。这些乘法的总和在输出张量的相应位置给出单个标量值(例如, :numref:fig_conv1d中的0\times1+1\times2=2)。

我们在下面的corr1d函数中实现了一维互相关。给定输入张量X和核张量K,它返回输出张量Y

#@tab mxnet, pytorch def corr1d(X, K): w = K.shape[0] Y = d2l.zeros((X.shape[0] - w + 1)) for i in range(Y.shape[0]): Y[i] = (X[i: i + w] * K).sum() return Y
#@tab paddle def corr1d(X, K): w = K.shape[0] Y = d2l.zeros([X.shape[0] - w + 1], dtype=X.dtype) for i in range(Y.shape[0]): Y[i] = (X[i: i + w] * K).sum() return Y

我们可以从 :numref:fig_conv1d构造输入张量X和核张量K来验证上述一维互相关实现的输出。

#@tab all X, K = d2l.tensor([0, 1, 2, 3, 4, 5, 6]), d2l.tensor([1, 2]) corr1d(X, K)

对于任何具有多个通道的一维输入,卷积核需要具有相同数量的输入通道。然后,对于每个通道,对输入的一维张量和卷积核的一维张量执行互相关运算,将所有通道上的结果相加以产生一维输出张量。 :numref:fig_conv1d_channel演示了具有3个输入通道的一维互相关操作。

具有3个输入通道的一维互相关运算。阴影部分是第一个输出元素以及用于输出计算的输入和核张量元素:2\times(-1)+3\times(-3)+1\times3+2\times4+0\times1+1\times2=2
🏷fig_conv1d_channel

我们可以实现多个输入通道的一维互相关运算,并在 :numref:fig_conv1d_channel中验证结果。

#@tab all def corr1d_multi_in(X, K): # 首先,遍历'X'和'K'的第0维(通道维)。然后,把它们加在一起 return sum(corr1d(x, k) for x, k in zip(X, K)) X = d2l.tensor([[0, 1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8]]) K = d2l.tensor([[1, 2], [3, 4], [-1, -3]]) corr1d_multi_in(X, K)

注意,多输入通道的一维互相关等同于单输入通道的二维互相关。举例说明, :numref:fig_conv1d_channel中的多输入通道一维互相关的等价形式是 :numref:fig_conv1d_2d中的单输入通道二维互相关,其中卷积核的高度必须与输入张量的高度相同。

具有单个输入通道的二维互相关操作。阴影部分是第一个输出元素以及用于输出计算的输入和内核张量元素: 2\times(-1)+3\times(-3)+1\times3+2\times4+0\times1+1\times2=2
🏷fig_conv1d_2d

:numref:fig_conv1d和 :numref:fig_conv1d_channel中的输出都只有一个通道。与 :numref:subsec_multi-output-channels中描述的具有多个输出通道的二维卷积相同,我们也可以为一维卷积指定多个输出通道。

最大时间汇聚层

类似地,我们可以使用汇聚层从序列表示中提取最大值,作为跨时间步的最重要特征。textCNN中使用的最大时间汇聚层的工作原理类似于一维全局汇聚 :cite:Collobert.Weston.Bottou.ea.2011。对于每个通道在不同时间步存储值的多通道输入,每个通道的输出是该通道的最大值。请注意,最大时间汇聚允许在不同通道上使用不同数量的时间步。

textCNN模型

使用一维卷积和最大时间汇聚,textCNN模型将单个预训练的词元表示作为输入,然后获得并转换用于下游应用的序列表示。

对于具有由d维向量表示的n个词元的单个文本序列,输入张量的宽度、高度和通道数分别为n1d。textCNN模型将输入转换为输出,如下所示:

  1. 定义多个一维卷积核,并分别对输入执行卷积运算。具有不同宽度的卷积核可以捕获不同数目的相邻词元之间的局部特征。
  2. 在所有输出通道上执行最大时间汇聚层,然后将所有标量汇聚输出连结为向量。
  3. 使用全连接层将连结后的向量转换为输出类别。Dropout可以用来减少过拟合。

textCNN的模型架构
🏷fig_conv1d_textcnn

:numref:fig_conv1d_textcnn通过一个具体的例子说明了textCNN的模型架构。输入是具有11个词元的句子,其中每个词元由6维向量表示。因此,我们有一个宽度为11的6通道输入。定义两个宽度为2和4的一维卷积核,分别具有4个和5个输出通道。它们产生4个宽度为11-2+1=10的输出通道和5个宽度为11-4+1=8的输出通道。尽管这9个通道的宽度不同,但最大时间汇聚层给出了一个连结的9维向量,该向量最终被转换为用于二元情感预测的2维输出向量。

定义模型

我们在下面的类中实现textCNN模型。与 :numref:sec_sentiment_rnn的双向循环神经网络模型相比,除了用卷积层代替循环神经网络层外,我们还使用了两个嵌入层:一个是可训练权重,另一个是固定权重。

class TextCNN(nn.Block): def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels, **kwargs): super(TextCNN, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) # 这个嵌入层不需要训练 self.constant_embedding = nn.Embedding(vocab_size, embed_size) self.dropout = nn.Dropout(0.5) self.decoder = nn.Dense(2) # 最大时间汇聚层没有参数,因此可以共享此实例 self.pool = nn.GlobalMaxPool1D() # 创建多个一维卷积层 self.convs = nn.Sequential() for c, k in zip(num_channels, kernel_sizes): self.convs.add(nn.Conv1D(c, k, activation='relu')) def forward(self, inputs): # 沿着向量维度将两个嵌入层连结起来, # 每个嵌入层的输出形状都是(批量大小,词元数量,词元向量维度)连结起来 embeddings = np.concatenate(( self.embedding(inputs), self.constant_embedding(inputs)), axis=2) # 根据一维卷积层的输入格式,重新排列张量,以便通道作为第2维 embeddings = embeddings.transpose(0, 2, 1) # 每个一维卷积层在最大时间汇聚层合并后,获得的张量形状是(批量大小,通道数,1) # 删除最后一个维度并沿通道维度连结 encoding = np.concatenate([ np.squeeze(self.pool(conv(embeddings)), axis=-1) for conv in self.convs], axis=1) outputs = self.decoder(self.dropout(encoding)) return outputs
#@tab pytorch class TextCNN(nn.Module): def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels, **kwargs): super(TextCNN, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) # 这个嵌入层不需要训练 self.constant_embedding = nn.Embedding(vocab_size, embed_size) self.dropout = nn.Dropout(0.5) self.decoder = nn.Linear(sum(num_channels), 2) # 最大时间汇聚层没有参数,因此可以共享此实例 self.pool = nn.AdaptiveAvgPool1d(1) self.relu = nn.ReLU() # 创建多个一维卷积层 self.convs = nn.ModuleList() for c, k in zip(num_channels, kernel_sizes): self.convs.append(nn.Conv1d(2 * embed_size, c, k)) def forward(self, inputs): # 沿着向量维度将两个嵌入层连结起来, # 每个嵌入层的输出形状都是(批量大小,词元数量,词元向量维度)连结起来 embeddings = torch.cat(( self.embedding(inputs), self.constant_embedding(inputs)), dim=2) # 根据一维卷积层的输入格式,重新排列张量,以便通道作为第2维 embeddings = embeddings.permute(0, 2, 1) # 每个一维卷积层在最大时间汇聚层合并后,获得的张量形状是(批量大小,通道数,1) # 删除最后一个维度并沿通道维度连结 encoding = torch.cat([ torch.squeeze(self.relu(self.pool(conv(embeddings))), dim=-1) for conv in self.convs], dim=1) outputs = self.decoder(self.dropout(encoding)) return outputs
#@tab paddle class TextCNN(nn.Layer): def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels, **kwargs): super(TextCNN, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) # 这个嵌入层不需要训练 self.constant_embedding = nn.Embedding(vocab_size, embed_size) self.dropout = nn.Dropout(0.5) self.decoder = nn.Linear(sum(num_channels), 2) # 最大时间汇聚层没有参数,因此可以共享此实例 self.pool = nn.AdaptiveAvgPool1D(1) self.relu = nn.ReLU() # 创建多个一维卷积层 self.convs = nn.LayerList() for c, k in zip(num_channels, kernel_sizes): self.convs.append(nn.Conv1D(2 * embed_size, c, k)) def forward(self, inputs): # 沿着向量维度将两个嵌入层连结起来, # 每个嵌入层的输出形状都是(批量大小,词元数量,词元向量维度)连结起来 embeddings = paddle.concat(( self.embedding(inputs), self.constant_embedding(inputs)), axis=2) # 根据一维卷积层的输入格式,重新排列张量,以便通道作为第2维 embeddings = embeddings.transpose([0, 2, 1]) # 每个一维卷积层在最大时间汇聚层合并后,获得的张量形状是(批量大小,通道数,1) # 删除最后一个维度并沿通道维度连结 encoding = paddle.concat([ paddle.squeeze(self.relu(self.pool(conv(embeddings))), axis=-1) for conv in self.convs], axis=1) outputs = self.decoder(self.dropout(encoding)) return outputs

让我们创建一个textCNN实例。它有3个卷积层,卷积核宽度分别为3、4和5,均有100个输出通道。

embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100] devices = d2l.try_all_gpus() net = TextCNN(len(vocab), embed_size, kernel_sizes, nums_channels) net.initialize(init.Xavier(), ctx=devices)
#@tab pytorch embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100] devices = d2l.try_all_gpus() net = TextCNN(len(vocab), embed_size, kernel_sizes, nums_channels) def init_weights(m): if type(m) in (nn.Linear, nn.Conv1d): nn.init.xavier_uniform_(m.weight) net.apply(init_weights);
#@tab paddle embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100] devices = d2l.try_all_gpus() net = TextCNN(len(vocab), embed_size, kernel_sizes, nums_channels) def init_weights(net): init_normal = nn.initializer.XavierUniform() for i in net.sublayers(): if type(i) in [nn.Linear, nn.Conv1D]: init_normal(i.weight) init_weights(net)

加载预训练词向量

与 :numref:sec_sentiment_rnn相同,我们加载预训练的100维GloVe嵌入作为初始化的词元表示。这些词元表示(嵌入权重)在embedding中将被训练,在constant_embedding中将被固定。

glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.set_data(embeds) net.constant_embedding.weight.set_data(embeds) net.constant_embedding.collect_params().setattr('grad_req', 'null')
#@tab pytorch glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.data.copy_(embeds) net.constant_embedding.weight.data.copy_(embeds) net.constant_embedding.weight.requires_grad = False
#@tab paddle glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.set_value(embeds) net.constant_embedding.weight.set_value(embeds) net.constant_embedding.weight.stop_gradient = True

训练和评估模型

现在我们可以训练textCNN模型进行情感分析。

lr, num_epochs = 0.001, 5 trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr}) loss = gluon.loss.SoftmaxCrossEntropyLoss() d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
#@tab pytorch lr, num_epochs = 0.001, 5 trainer = torch.optim.Adam(net.parameters(), lr=lr) loss = nn.CrossEntropyLoss(reduction="none") d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
#@tab paddle lr, num_epochs = 0.001, 5 trainer = paddle.optimizer.Adam(learning_rate=lr, parameters=net.parameters()) loss = nn.CrossEntropyLoss(reduction="none") d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)

下面,我们使用训练好的模型来预测两个简单句子的情感。

#@tab all d2l.predict_sentiment(net, vocab, 'this movie is so great')
#@tab all d2l.predict_sentiment(net, vocab, 'this movie is so bad')

小结

  • 一维卷积神经网络可以处理文本中的局部特征,例如n元语法。
  • 多输入通道的一维互相关等价于单输入通道的二维互相关。
  • 最大时间汇聚层允许在不同通道上使用不同数量的时间步长。
  • textCNN模型使用一维卷积层和最大时间汇聚层将单个词元表示转换为下游应用输出。

练习

  1. 调整超参数,并比较 :numref:sec_sentiment_rnn中用于情感分析的架构和本节中用于情感分析的架构,例如在分类精度和计算效率方面。
  2. 请试着用 :numref:sec_sentiment_rnn练习中介绍的方法进一步提高模型的分类精度。
  3. 在输入表示中添加位置编码。它是否提高了分类的精度?

:begin_tab:mxnet
Discussions
:end_tab:

:begin_tab:pytorch
Discussions
:end_tab:

:begin_tab:paddle
Discussions
:end_tab:


作者与出处
原作者: d2l-ai
来源:d2l-ai
许可证:Apache-2.0
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: d2l-ai 转发
评论区 (0)
U