动⼿学深度学习--⽂本情感分析之RNN ⽂本分类是⾃然语⾔处理的⼀个常⻅任务,它把⼀段不定⻓的⽂本序列变换为⽂本的类别。它的⼀个⼦问题:使⽤⽂本情感分类来分析
⽂本作者的情绪。这个问题也叫情感分析,并有着⼴泛的应⽤。例如,我们可以分析⽤户对产品的评论并统计⽤户的满意度,或者分析⽤户对市场⾏情的情绪并⽤以预测接下来的⾏情。
这⾥将应⽤预训练的词向量和含多个隐藏层的双向循环神经⽹络,来判断⼀段不定⻓的⽂本序列中包含的是正⾯还是负⾯的情绪。
1、导⼊包和模块
1import collections
2import os
3import random
4import tarfile
5import torch
酒后头疼
6from torch import nn
7import torchtext.vocab as Vocab
8import torch.utils.data as Data
9
10import sys
11 sys.path.append("..")
12import d2lzh_pytorch as d2l
13
14 device = torch.device('cuda'if torch.cuda.is_available() el'cpu')
15
16 DATA_ROOT = "./Datats"
View Code
2、读⼊数据
使⽤斯坦福的IMDb数据集(Stanford's Large Movie Review Datat)作为⽂本情感分类的数据集。这个数据集分为训练和测试⽤的两个数据集,分别包含25,000条从IMDb下载的关于电影的评论。在每个数据集中,标签为“正⾯”和“负⾯”的评论数量相等。数据解压⾄Datats 中。读取训练数据集和测试数据集。每个样本是⼀条评论及其对应的标签: 1表⽰“正⾯”, 0表⽰“负⾯”。
1from tqdm import tqdm
2def read_imdb(folder='train', data_root="./Datats/aclImdb"):
3 data = []
4for label in ['pos', 'neg']:
5 folder_name = os.path.join(data_root, folder, label)
6for 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('\n', '').lower()
9 data.append([review, 1 if label == 'pos'el 0])
10 random.shuffle(data)
11return data
12
军团圣盾
13 train_data, test_data = read_imdb('train'), read_imdb('test')
View Code
3、预处理数据
定义的 get_tokenized_imdb 函数使⽤最简单的⽅法:基于空格进⾏分词。
1def get_tokenized_imdb(data):
2"""
3 data: list of [string, label]
4"""
5def tokenizer(text):
6return [tok.lower() for tok in text.split('')]
7return [tokenizer(review) for review, _ in data]杨紫的剧
View Code
可以根据分好词的训练数据集来创建词典了。我们在这⾥过滤掉了出现次数少于5的词。
1def 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])
4return 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。
1def preprocess_imdb(data, vocab):
2 max_l = 500 # 将每条评论通过截断或者补0,使得长度变成500
3
4def pad(x):
乒乓球拉球5return x[:max_l] if len(x) > max_l el x + [0] * (max_l - len(x))
6
7 tokenized_data = get_tokenized_imdb(data)
8 features = sor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])
9 labels = sor([score for _, score in data])
10return features, labels
View Code
4、创建数据迭代器
宝宝舌苔我们创建数据迭代器。每次迭代将返回⼀个⼩批量的数据。
1 batch_size = 64
2 train_t = Data.TensorDatat(*preprocess_imdb(train_data, vocab))
3 test_t = Data.TensorDatat(*preprocess_imdb(test_data, vocab))
4 train_iter = Data.DataLoader(train_t, batch_size, shuffle=True)
5 test_iter = Data.DataLoader(test_t, batch_size)
View Code
5、RNN model
在这个模型中,每个词先通过嵌⼊层得到特征向量。然后,我们使⽤双向循环神经⽹络对特征序列进⼀步编码得到序列信息。最后,我们将编码的序列信息通过全连接层变换为输出。具体来说,我们可以将双向⻓短期记忆在最初时间步和最终时间步的隐藏状态连结,作为特征序列的表征传递给输出层
分类。在下⾯实现的 BiRNN 类中, Embedding 实例即嵌⼊层, LSTM 实例即为序列编码的隐藏层, Linear实例即⽣成分类结果的输出层。
1class BiRNN(nn.Module):
2def__init__(lf, vocab, embed_size, num_hiddens, num_layers):
3 super(BiRNN, lf).__init__()
4 lf.embedding = nn.Embedding(len(vocab), embed_size) # 嵌⼊层
5
6# bidirectional设为True即得到双向循环神经⽹络
7 lf.encoder = nn.LSTM(input_size=embed_size,
8 hidden_size=num_hiddens,
9 num_layers=num_layers,
10 bidirectional=True) # 隐藏层
11 lf.decoder = nn.Linear(4*num_hiddens, 2) # 初始时间步和最终时间步的隐藏状态作为全连接层输⼊
12
13def forward(lf, inputs):
14# inputs的形状是(批量⼤⼩, 词数),因为LSTM需要将序列长度(q_len)作为第⼀维,所以将输⼊转置后
15# 再提取词特征,输出形状为(词数, 批量⼤⼩, 词向量维度)
16 embeddings = lf.embedding(inputs.permute(1, 0))
17# rnn.LSTM只传⼊输⼊embeddings,因此只返回最后⼀层的隐藏层在各时间步的隐藏状态。
18# outputs形状是(词数, 批量⼤⼩, 2 * 隐藏单元个数)
19 outputs, _ = lf.encoder(embeddings) # output, (h, c)
20# 连结初始时间步和最终时间步的隐藏状态作为全连接层输⼊。它的形状为
21# (批量⼤⼩, 4 * 隐藏单元个数)。
22 encoding = torch.cat((outputs[0], outputs[-1]), -1)
23 outs = lf.decoder(encoding)
24return 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"))
2def load_pretrained_embedding(words, pretrained_vocab):
3"""从预训练好的vocab中提取出words对应的词向量"""
4 embed = s(len(words), pretrained_vocab.vectors[0].shape[0]) # 初始化为0
5 oov_count = 0 # out of vocabulary
6for i, word in enumerate(words):
7try:
8 idx = pretrained_vocab.stoi[word]
9 embed[i, :] = pretrained_vocab.vectors[idx]腋下副乳怎么消除
10except KeyError:
11 oov_count += 0
12if oov_count > 0:
13print("There are %d oov words.")
14return embed
15
bedding.py_(load_pretrained_embedding(vocab.itos, glove_vocab))
quires_grad = Fal # 直接加载预训练好的, 所以不需要更新它
View Code
对于预训练词向量我们使⽤基于维基百科⼦集预训练的50维GloVe词向量。第⼀次创建预训练词向量实例时会⾃动下载相应的词向量到 cache 指定⽂件夹(默认为 .vector_cache ),因此需要联⽹。
cache_dir = "./Datats/glove"
glove = vocab.GloVe(name='6B', dim=50, cache=cache_dir)
# ./Datats/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()
ain(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
View Code
8、定义预测函数
1def predict_ntiment(net, vocab, ntence):
2"""ntence是词语的列表"""
3 device = list(net.parameters())[0].device
4 ntence = sor([vocab.stoi[word] for word in ntence], device=device)
5 label = torch.argmax(net(ntence.view((1, -1))), dim=1)
6return'positive'if label.item() == 1 el'negative'
View Code
实现预测
predict_ntiment(net, vocab, ['this', 'movie', 'is', 'so', 'great']) # positive