动手学深度学习--文本情感分析之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"
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‘)
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]
可以根据分好词的训练数据集来创建词典了。我们在这?过滤掉了出现次数少于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)
因为每条评论?度不?致所以不能直接组合成?批量,我们定义 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
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)
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
创建?个含两个隐藏层的双向循环神经?络。
1 embed_size, num_hiddens, num_layers = 100, 100, 2 2 net = BiRNN(vocab, embed_size, num_hiddens, num_layers)
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 # 直接加载预训练好的, 所以不需要更新它
对于预训练词向量我们使?基于维基百科?集预训练的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)
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‘
实现预测
predict_sentiment(net, vocab, [‘this‘, ‘movie‘, ‘is‘, ‘so‘, ‘great‘]) # positive
以上是关于动手学深度学习--文本情感分析之RNN的主要内容,如果未能解决你的问题,请参考以下文章
长短期记忆网络 LSTM 深层循环神经网络 Deep RNN 双向循环神经网络 Bidirectional RNN 动手学深度学习v2
长短期记忆网络 LSTM 深层循环神经网络 Deep RNN 双向循环神经网络 Bidirectional RNN 动手学深度学习v2