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)

渣渣本实在是跑不动,结果就先不放了。

posted @ 2020-07-23 15:18  最咸的鱼  阅读(3427)  评论(0编辑  收藏  举报