IMDB文本分类
案例为最基本的分本分类——情感分析,其中采用IMDB(电影影评)作为数据集。
IMDB,收录了400多万部电影,数据集共有50000项,训练测试五五开,标签为正面评价或者负面评价。
可以从Kaggle官网搜索并下载IMDB电影数据集(imdbmovies)
多的不说,开始代码部分
先将需要的包导入。
import torch
import torch.nn as nn
import torch.optim as optim
from torchtext import data
from torchtext import datasets
import random
首先我们需要torchtext这个库,因为不是pytorch自带,所以需要自己下载。
pip install torchtext
torchtext.data定义了Field的类,他可以用来定义数据读取。
这里定义了两个Field,分别用于文本和标签。
TEXT = data.Field(tokenize='spacy', tokenizer_language='en_core_web_sm')
LABEL = data.LabelField(dtype=torch.float)
我们采用torch.dataset下载IMDB数据集,并将数据集划分。
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
train_data, valid_data = train_data.split(random_state=random.seed(SEED))
print(f'number of training examples:{len(train_data)}')
print(f'number of training examples:{len(test_data)}')
print(f'number of training examples:{len(valid_data)}')
这里的显示划分结果为
下面开始构建词表,把单词mapping到index。这里使用最大为25000个词,且下载glove.6B.100d为词向量。
TEXT.build_vocab(train_data, max_size=25000, vectors="glove.6B.100d", unk_init=torch.Tensor.normal_ )
LABEL.build_vocab(train_data)
print(f'Unique tokens in TEXT vocabulary: {len(TEXT.vocab)}')
print(f'Unique tokens in LABEL vocabulary: {len(LABEL.vocab)}')
打印结果如下,TEXT的词表为25000个词加上unk和pad,分别表示为未知词和填充词。LABEL词表为两个,POS和NEG,即为积极影评和消极影评。
开始训练预处理,我们将使用GPU来跑这个模型,再定义一个64的batch,再用BucketIterator将所有文本的词替换成词的索引。
BATCH_SIZE = 64
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_iterator,valid_iterator,test_iterator = data.BucketIterator.splits(
(train_data,valid_data,test_data),
batch_size=BATCH_SIZE,
device=device)
我们创建一个batch并显示batch的结果。
bacth = next(iter(valid_iterator))
print(bacth.text)
print(bacth.label)
结果如下,文本数据已转换为向量,标签也是0/1形式
定义网络模型。模型采用word averageing模型,将每个单词通过embedding投射成词向量,然后将每一句话的词向量做个平均,将平均向量传入Linear做分类。
import torch.nn.functional as F
import torch.nn as nn
class WordAVGModel(nn.Module):
def __init__(self,vocab_size,embedding_size,output_size,pad_idx):
super(WordAVGModel,self).__init__()
self.embedding=nn.Embedding(vocab_size,embedding_size,padding_idx=pad_idx)
self.linear = nn.Linear(embedding_size,output_size)
def forward(self,text):
embedded = self.embedding(text) #[seq_len, batch_size,embedding_size]
embedded = embedded.permute(1,0,2) #[bacth,seq_len,embedding_size]
pooled = F.avg_pool2d(embedded,(embedded.shape[1],1)).squeeze(1)#batch,1,embedding
return self.linear(pooled)
设置一些模型需要的参数
VOCAB_SIZE=len(TEXT.vocab)
EMBEDDING_SIZE = 100
OUTPUT_SIZE = 1
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]
model = WordAVGModel(VOCAB_SIZE,EMBEDDING_SIZE,OUTPUT_SIZE,PAD_IDX)
模型结构如下
定义损失函数优化器等。
optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss() #binaryCrossEntropy 二分类
model = model.to(device)
criterion = criterion.to(device)
定义一个计算精度的函数。通过预测值是否等于真实值进行计算(注意精度)
def binary_accuracy(preds,y):
rounded_preds = torch.round(torch.sigmoid(preds))
correct = (rounded_preds == y).float()
acc = correct.sum()/len(correct)
return acc
定义训练及测试过程(两者其实差不多)
def train(model,iterator,optimizer,criterion):
epoch_loss = 0.
epoch_acc = 0.
model.train()
for batch in iterator:
predictions = model(batch.text).squeeze(1)
#print(predictions.shape,batch.label.shape)
loss = criterion(predictions,batch.label)
acc = binary_accuracy(predictions,batch.label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss+=loss.item()
epoch_acc +=acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
def evaluate(model,iterator,criterion):
epoch_loss = 0.
epoch_acc = 0.
model.eval()
with torch.no_grad():
for batch in iterator:
optimizer.zero_grad()
predictions = model(batch.text).squeeze(1)
loss = criterion(predictions,batch.label)
acc = binary_accuracy(predictions,batch.label)
epoch_loss+=loss.item()
epoch_acc +=acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
开始训练,这里将训练精度高的模型保存下来。
N_EPOCHS = 10
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
train_loss,train_acc = train(model,train_iterator,optimizer,criterion)
valid_loss,valid_acc = evaluate(model,valid_iterator,criterion)
if valid_loss < best_valid_loss:
best_valid_loss = valid_loss
torch.save(model.state_dict(),'wordavg_model.pt')
print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
print(f'\t Val. Loss: {valid_loss:.3f} | Val. Acc: {valid_acc*100:.2f}%')
训练结果如下
然后将我们保存的模型导出
model.load_state_dict(torch.load('wordavg_model.pt'))
可以开始测试我们自己的句子。这里将句子进行一些处理,让它转为向量,在进行维度上加1,这个1相当于我们的batch,不然送不到网络当中去。
import spacy
nlp = spacy.load('en_core_web_sm')
def predict_sentiment(sentence):
tokenized =[tok.text for tok in nlp.tokenizer(sentence)]
indexed = [TEXT.vocab.stoi[t] for t in tokenized]
tensor = torch.LongTensor(indexed).to(device)
tensor = tensor.unsqueeze(1)
pred = torch.sigmoid(model(tensor))
return pred.item()
调用函数
print(predict_sentiment("This film is super-inspirational and sweet--mostly because it's true。"))
print(predict_sentiment('This film is great'))
print(predict_sentiment('This film is terrible'))
这里得到的结果为
前两个句子都是正面的评价,所有值会接近1也就是POS,而负面评价的值很小,接近0,则为NEG.
本文采用了很简单的网络模型,没有用RNN、LSTM这些网络结构(因为我还不是很会),可能有很多问题,望指正。
参考链接
https://blog.csdn.net/bryant_meng/article/details/81214649?ops_request_misc=&request_id=&biz_id=102&utm_source=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0
https://github.com/bentrevett/pytorch-sentiment-analysis/blob/master/2%20-%20Upgraded%20Sentiment%20Analysis.ipynb