动手学深度学习--文本情感分析之RNN

Posted harbin-ho

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动手学深度学习--文本情感分析之RNN相关的知识,希望对你有一定的参考价值。

        ?本分类是?然语?处理的?个常?任务,它把?段不定?的?本序列变换为?本的类别。它的?个?问题:使??本情感分类来分析?本作者的情绪。这个问题也叫情感分析,并有着?泛的应?。例如,我们可以分析?户对产品的评论并统计?户的满意度,或者分析?户对市场?情的情绪并?以预测接下来的?情。

       这里将应?预训练的词向量和含多个隐藏层的双向循环神经?络,来判断?段不定?的?本序列中包含的是正?还是负?的情绪。

1、导入包和模块

技术图片
 1 import collections
 2 import os
 3 import random
 4 import tarfile
 5 import torch
 6 from torch import nn
 7 import torchtext.vocab as Vocab
 8 import torch.utils.data as Data
 9 
10 import sys
11 sys.path.append("..") 
12 import d2lzh_pytorch as d2l
13 
14 device = torch.device(cuda if torch.cuda.is_available() else cpu)
15 
16 DATA_ROOT = "./Datasets"
View Code

2、读入数据 

使?斯坦福的IMDb数据集(Stanford‘s Large Movie Review Dataset)作为?本情感分类的数据集。这个数据集分为训练和测试?的两个数据集,分别包含25,000条从IMDb下载的关于电影的评论。在每个数据集中,标签为“正?”和“负?”的评论数量相等。下载数据解压至Datasets中。读取训练数据集和测试数据集。每个样本是?条评论及其对应的标签: 1表示“正?”, 0表示“负?”。

技术图片
 1 from tqdm import tqdm
 2 def read_imdb(folder=train, data_root="./Datasets/aclImdb"):  
 3     data = []
 4     for label in [pos, neg]:
 5         folder_name = os.path.join(data_root, folder, label)
 6         for file in tqdm(os.listdir(folder_name)):
 7             with open(os.path.join(folder_name, file), rb) as f:
 8                 review = f.read().decode(utf-8).replace(
, ‘‘).lower()
 9                 data.append([review, 1 if label == pos else 0])
10     random.shuffle(data)
11     return data
12 
13 train_data, test_data = read_imdb(train), read_imdb(test)
View Code

3、预处理数据

定义的 get_tokenized_imdb 函数使?最简单的?法:基于空格进?分词。

技术图片
1 def get_tokenized_imdb(data): 
2     """
3     data: list of [string, label]
4     """
5     def tokenizer(text):
6         return [tok.lower() for tok in text.split( )]
7     return [tokenizer(review) for review, _ in data]
View Code

可以根据分好词的训练数据集来创建词典了。我们在这?过滤掉了出现次数少于5的词。

技术图片
1 def get_vocab_imdb(data):  
2     tokenized_data = get_tokenized_imdb(data)
3     counter = collections.Counter([tk for st in tokenized_data for tk in st])
4     return Vocab.Vocab(counter, min_freq=5)
5 
6 vocab = get_vocab_imdb(train_data)
7 # words in vocab:, len(vocab)  # (‘# words in vocab:‘, 46151)
View Code

因为每条评论?度不?致所以不能直接组合成?批量,我们定义 preprocess_imdb 函数对每条评论进?分词,并通过词典转换成词索引,然后通过截断或者补0来将每条评论?度固定成500。

技术图片
 1 def preprocess_imdb(data, vocab):  
 2     max_l = 500  # 将每条评论通过截断或者补0,使得长度变成500
 3 
 4     def pad(x):
 5         return x[:max_l] if len(x) > max_l else x + [0] * (max_l - len(x))
 6 
 7     tokenized_data = get_tokenized_imdb(data)
 8     features = torch.tensor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])
 9     labels = torch.tensor([score for _, score in data])
10     return features, labels
View Code

4、创建数据迭代器

我们创建数据迭代器。每次迭代将返回?个?批量的数据。

技术图片
1 batch_size = 64
2 train_set = Data.TensorDataset(*preprocess_imdb(train_data, vocab))
3 test_set = Data.TensorDataset(*preprocess_imdb(test_data, vocab))
4 train_iter = Data.DataLoader(train_set, batch_size, shuffle=True)
5 test_iter = Data.DataLoader(test_set, batch_size)
View Code

5、RNN model

在这个模型中,每个词先通过嵌?层得到特征向量。然后,我们使?双向循环神经?络对特征序列进?步编码得到序列信息。最后,我们将编码的序列信息通过全连接层变换为输出。具体来说,我们可以将双向?短期记忆在最初时间步和最终时间步的隐藏状态连结,作为特征序列的表征传递给输出层分类。在下?实现的 BiRNN 类中, Embedding 实例即嵌?层, LSTM 实例即为序列编码的隐藏层, Linear实例即?成分类结果的输出层。

技术图片
 1 class BiRNN(nn.Module):
 2     def __init__(self, vocab, embed_size, num_hiddens, num_layers):
 3         super(BiRNN, self).__init__()
 4         self.embedding = nn.Embedding(len(vocab), embed_size)  # 嵌入层
 5         
 6         # bidirectional设为True即得到双向循环神经网络
 7         self.encoder = nn.LSTM(input_size=embed_size, 
 8                                 hidden_size=num_hiddens, 
 9                                 num_layers=num_layers,
10                                 bidirectional=True)  # 隐藏层
11         self.decoder = nn.Linear(4*num_hiddens, 2) # 初始时间步和最终时间步的隐藏状态作为全连接层输入
12 
13     def forward(self, inputs):
14         # inputs的形状是(批量大小, 词数),因为LSTM需要将序列长度(seq_len)作为第一维,所以将输入转置后
15         # 再提取词特征,输出形状为(词数, 批量大小, 词向量维度)
16         embeddings = self.embedding(inputs.permute(1, 0))
17         # rnn.LSTM只传入输入embeddings,因此只返回最后一层的隐藏层在各时间步的隐藏状态。
18         # outputs形状是(词数, 批量大小, 2 * 隐藏单元个数)
19         outputs, _ = self.encoder(embeddings) # output, (h, c)
20         # 连结初始时间步和最终时间步的隐藏状态作为全连接层输入。它的形状为
21         # (批量大小, 4 * 隐藏单元个数)。
22         encoding = torch.cat((outputs[0], outputs[-1]), -1)
23         outs = self.decoder(encoding)
24         return outs
View Code

创建?个含两个隐藏层的双向循环神经?络。

技术图片
1 embed_size, num_hiddens, num_layers = 100, 100, 2
2 net = BiRNN(vocab, embed_size, num_hiddens, num_layers)
View Code

6、加载预训练的词向量

由于情感分类的训练数据集并不是很?,为应对过拟合,我们将直接使?在更?规模语料上预训练的词向量作为每个词的特征向量。这?,我们为词典 vocab 中的每个词加载100维的GloVe词向量。然后,我们将?这些词向量作为评论中每个词的特征向量。注意,预训练词向量的维度需要与创建的模型中的嵌?层输出?? embed_size ?致。此外,在训练中我们不再更新这些词向量。

技术图片
 1 glove_vocab = Vocab.GloVe(name=6B, dim=100, cache=os.path.join(DATA_ROOT, "glove"))
 2 def load_pretrained_embedding(words, pretrained_vocab):
 3     """从预训练好的vocab中提取出words对应的词向量"""
 4     embed = torch.zeros(len(words), pretrained_vocab.vectors[0].shape[0]) # 初始化为0
 5     oov_count = 0 # out of vocabulary
 6     for i, word in enumerate(words):
 7         try:
 8             idx = pretrained_vocab.stoi[word]
 9             embed[i, :] = pretrained_vocab.vectors[idx]
10         except KeyError:
11             oov_count += 0
12     if oov_count > 0:
13         print("There are %d oov words.")
14     return embed
15 
16 net.embedding.weight.data.copy_(load_pretrained_embedding(vocab.itos, glove_vocab))
17 net.embedding.weight.requires_grad = False # 直接加载预训练好的, 所以不需要更新它
View Code

对于预训练词向量我们使?基于维基百科?集预训练的50维GloVe词向量。第?次 创 建 预 训 练 词 向 量 实 例 时 会 ? 动 下 载 相 应 的 词 向 量 到 cache 指 定 ? 件 夹 ( 默 认为 .vector_cache ),因此需要联?。

cache_dir = "./Datasets/glove"
glove = vocab.GloVe(name=6B, dim=50, cache=cache_dir)

# ./Datasets/glove/glove.6B.zip: 862MB [40:57, 351kB/s]                                
# 100%|█████████▉| 399022/400000 [00:30<00:00, 24860.36it/s]

7、训练并评价模型

技术图片
1 lr, num_epochs = 0.01, 5
2 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
3 loss = nn.CrossEntropyLoss()
4 d2l.train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
View Code

 8、定义预测函数

技术图片
1 def predict_sentiment(net, vocab, sentence):
2     """sentence是词语的列表"""
3     device = list(net.parameters())[0].device
4     sentence = torch.tensor([vocab.stoi[word] for word in sentence], device=device)
5     label = torch.argmax(net(sentence.view((1, -1))), dim=1)
6     return positive if label.item() == 1 else negative
View Code

实现预测

predict_sentiment(net, vocab, [this, movie, is, so, great])  # positive

以上是关于动手学深度学习--文本情感分析之RNN的主要内容,如果未能解决你的问题,请参考以下文章

长短期记忆网络 LSTM 深层循环神经网络 Deep RNN 双向循环神经网络 Bidirectional RNN 动手学深度学习v2

长短期记忆网络 LSTM 深层循环神经网络 Deep RNN 双向循环神经网络 Bidirectional RNN 动手学深度学习v2

门控循环单元 GRU 动手学深度学习v2

门控循环单元 GRU 动手学深度学习v2

Keras深度学习实战——使用循环神经网络构建情感分析模型

循环神经网络 RNN 动手学深度学习v2