Pytorch-情感分类实战(基于LSTM,调用torchtext)
提前安装torchtext和scapy,运行下面语句(压缩包地址链接:https://pan.baidu.com/s/1_syic9B-SXKQvkvHlEf78w 提取码:ahh3):
pip install torchtext
pip install scapy
pip install 你的地址\en_core_web_md-2.2.5.tar.gz
- en_core_web_md安装成功了,但spyder还是没法调用,把F:\Anaconda\Lib\site-packages这个目录下的en_core_web_md复制一份放到当前.py文件相同目录下。
- 在torchtext中使用spacy时,由于field的默认属性是tokenizer_language='en',当使用en_core_web_md时要改field.py文件中创建的field属性为tokenizer_language='en_core_web_md',且data.Field()中的参数也要改为tokenizer_language='en_core_web_md'。
1.加载数据
1 import numpy as np 2 import torch 3 from torch import nn, optim 4 from torchtext import data, datasets 5 6 #为CPU设置随机种子 7 torch.manual_seed(123) 8 9 #两个Field对象定义字段的处理方法(文本字段、标签字段) 10 TEXT = data.Field(tokenize='spacy', tokenizer_language='en_core_web_md') 11 LABEL = data.LabelField(dtype=torch.float) 12 13 #IMDB共50000影评,包含正面和负面两个类别。数据被前面的Field处理 14 train_data, test_data = datasets.IMDB.splits(TEXT, LABEL) 15 16 print('len of train data:', len(train_data)) #25000 17 print('len of test data:', len(test_data)) #25000 18 19 print(train_data.examples[15].text) 20 print(train_data.examples[15].label)
['Like', 'one', 'of', 'the', 'previous', 'commenters', 'said', ',', 'this', 'had', 'the', 'foundations', 'of', 'a', 'great', 'movie', 'but', 'something', 'happened', 'on', 'the', 'way', 'to', 'delivery', '.', 'Such', 'a', 'waste', 'because', 'Collette', "'s", 'performance', 'was', 'eerie', 'and', 'Williams', 'was', 'believable', '.', 'I', 'just', 'kept', 'waiting', 'for', 'it', 'to', 'get', 'better', '.', 'I', 'do', "n't", 'think', 'it', 'was', 'bad', 'editing', 'or', 'needed', 'another', 'director', ',', 'it', 'could', 'have', 'just', 'been', 'the', 'film', '.', 'It', 'came', 'across', 'as', 'a', 'Canadian', 'movie', ',', 'something', 'like', 'the', 'first', 'few', 'seasons', 'of', 'X', '-', 'Files', '.', 'Not', 'cheap', ',', 'just', 'hokey', '.', 'Also', ',', 'it', 'needed', 'a', 'little', 'more', 'suspense', '.', 'Something', 'that', 'makes', 'you', 'jump', 'off', 'your', 'seat', '.', 'The', 'movie', 'reached', 'that', 'moment', 'then', 'faded', 'away', ';', 'kind', 'of', 'like', 'a', 'false', 'climax', '.', 'I', 'can', 'see', 'how', 'being', 'too', 'suspenseful', 'would', 'have', 'taken', 'away', 'from', 'the', '"', 'reality', '"', 'of', 'the', 'story', 'but', 'I', 'thought', 'that', 'part', 'was', 'reached', 'when', 'Gabriel', 'was', 'in', 'the', 'hospital', 'looking', 'for', 'the', 'boy', '.', 'This', 'movie', 'needs', 'to', 'have', 'a', 'Director', "'s", 'cut', 'that', 'tries', 'to', 'fix', 'these', 'problems', '.']
pos
当我们把句子传进模型的时候,是按照一个个batch传进去的,而且每个batch中的句子必须是相同的长度。为了确保句子的长度相同,TorchText会把短的句子pad到和最长的句子等长。
创建vocabulary
vocabulary把每个单词一一映射到一个数字。使用10k个单词来构建单词表(用max_size这个参数可以设定),所有其他的单词都用<unk>来表示。
词典中应当有10002个单词,且有两个label,可以通过TEXT.vocab和TEXT.label查询,可以直接用stoi(stringtoint) 或者itos(inttostring) 来查看单词表。
1 TEXT.build_vocab(train_data, max_size=10000, vectors='glove.6B.100d') 2 LABEL.build_vocab(train_data) 3 print(len(TEXT.vocab)) #10002 4 print(TEXT.vocab.itos[:12]) #['<unk>', '<pad>', 'the', ',', '.', 'and', 'a', 'of', 'to', 'is', 'in', 'I'] 5 print(TEXT.vocab.stoi['and']) #5 6 print(LABEL.vocab.stoi) #defaultdict(None, {'neg': 0, 'pos': 1})
创建iteratiors
每个iterator中各有两部分:词(.text)和标签(.label),其中text全部转换成数字了。BucketIterator会把长度差不多的句子放到同一个batch中,确保每个batch中不出现太多的padding。这里因为pad比较少,所以把<pad>也当做了模型的输入进行训练。如果有GPU,还可以指定每个iteration返回的tensor都在GPU上。
1 batchsz = 30 2 train_iterator, test_iterator = data.BucketIterator.splits( 3 (train_data, test_data), 4 batch_size = batchsz, 5 )
2.定义模型
1 class RNN(nn.Module): 2 3 def __init__(self, vocab_size, embedding_dim, hidden_dim): 4 5 super(RNN, self).__init__() 6 7 # [0-10001] => [100] 8 self.embedding = nn.Embedding(vocab_size, embedding_dim) #单词数,嵌入向量维度 9 # [100] => [256] 10 self.rnn = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, #双向RNN,所以下面使用hidden_dim*2 11 bidirectional=True, dropout=0.5) 12 # [256*2] => [1] 13 self.fc = nn.Linear(hidden_dim*2, 1) 14 self.dropout = nn.Dropout(0.5) 15 16 17 def forward(self, x): 18 """ 19 x: [seq_len, b] vs [b, 3, 28, 28] 20 """ 21 # [seq_len, b, 1] => [seq_len, b, 100] 22 embedding = self.dropout(self.embedding(x)) 23 24 # output: [seq, b, hidden_len*2] 25 # hidden/h: [num_layers*2, b, hidden_len] 26 # cell/c: [num_layers*2, b, hidden_len] 27 output, (hidden, cell) = self.rnn(embedding) #[h0,c0]随机初始化 28 29 # [num_layers*2, b, hidden_len] => 2 of [b, hidden_len] => [b, hidden_len*2] 30 hidden = torch.cat([hidden[-2], hidden[-1]], dim=1) #双向,所以要把最后两个输出连接 31 32 # [b, hidden_len*2] => [b, 1] 33 hidden = self.dropout(hidden) 34 out = self.fc(hidden) 35 36 return out
使用预训练过的embedding来替换随机初始化(Tip:.copy_()这种带着下划线的函数均代表替换inplace)
1 rnn = RNN(len(TEXT.vocab), 100, 256) #词个数,词嵌入维度,输出维度 2 3 pretrained_embedding = TEXT.vocab.vectors 4 print('pretrained_embedding:', pretrained_embedding.shape) #torch.Size([10002, 100]) 5 rnn.embedding.weight.data.copy_(pretrained_embedding) 6 print('embedding layer inited.')
或者在定义embedding的时候直接self.embedding = nn.Embedding.from_pretrained(pretrained_embedding) #torch.tensor(embedding_matrix, dtype=torch.float)
3.训练模型
首先定义模型和损失函数。
1 optimizer = optim.Adam(rnn.parameters(), lr=1e-3) 2 criteon = nn.BCEWithLogitsLoss() #BCEWithLogitsLoss是针对二分类的CrossEntropy
定义一个函数用于计算准确率
1 def binary_acc(preds, y): 2 3 preds = torch.round(torch.sigmoid(preds)) 4 correct = torch.eq(preds, y).float() 5 acc = correct.sum() / len(correct) 6 return acc
定义一个训练函数
1 def train(rnn, iterator, optimizer, criteon): 2 3 avg_acc = [] 4 rnn.train() #表示进入训练模式 5 6 for i, batch in enumerate(iterator): 7 8 # [seq, b] => [b, 1] => [b] 9 pred = rnn(batch.text).squeeze(1) #batch.text 就是上面forward函数的参数text,压缩维度是为了和batch.label维度一致 10 11 loss = criteon(pred, batch.label) 12 acc = binary_acc(pred, batch.label).item() #计算每个batch的准确率 13 avg_acc.append(acc) 14 15 optimizer.zero_grad() 16 loss.backward() 17 optimizer.step() #不断训练,pred的值会越来越接近真实的label值 18 19 if i%10 == 0: 20 print(i, acc) 21 22 avg_acc = np.array(avg_acc).mean() 23 print('avg acc:', avg_acc)
4.评估模型
定义一个评估函数,和训练函数高度重合,区别是要把rnn.train()改为rnn.val(),不需要反向传播过程。
1 def evaluate(rnn, iterator, criteon): 2 avg_acc = [] 3 rnn.eval() #表示进入测试模式 4 5 with torch.no_grad(): 6 for batch in iterator: 7 8 pred = rnn(batch.text).squeeze(1) #[b, 1] => [b] 9 loss = criteon(pred, batch.label) 10 acc = binary_acc(pred, batch.label).item() 11 avg_acc.append(acc) 12 13 avg_acc = np.array(avg_acc).mean() 14 15 print('test acc:', avg_acc)
运行
1 for epoch in range(10): 2 3 train(rnn, train_iterator, optimizer, criteon) 4 evaluate(rnn, test_iterator, criteon)
渣渣本实在是跑不动,结果就先不放了。