Ubuntu 16.04.6
python:3.7
pytorch:1.7.1
torchtext:0.8.0
数据集:train_data_sentiment
提取码:gw77
#导入常用库
import math
import torch
import pandas as pd
import matplotlib.pyplot as plt
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import torch.nn.functional as F
import torchtext
from torchtext.vocab import Vectors
#比较新版本的需要使用torchtext.legacy.data,旧版本的torchtext使用torchtex.data
from torchtext.data import TabularDataset
import warnings
warnings.filterwarnings("ignore")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#导入数据集
train_data = pd.read_csv('train_data_sentiment.csv')
train_data
torchtext处理数据的步骤主要是:
torchtext可以很方便的对文本数据进行分词、截断补长、构建词表等。对torchtext不熟悉的可以学习官方文档或者讲解博客。
#定义Field
TEXT = torchtext.data.Field(sequential=True,lower=True,fix_length=30) #这里使用默认分词器split(),按照空格进行分词
LABEL = torchtext.data.Field(sequential=False,use_vocab=False)
train_x = TabularDataset(path = 'train_data_sentiment.csv',
format = 'csv',skip_header=True,
fields = [('utterance',TEXT),('label',LABEL)])
#构建词表
TEXT.build_vocab(train_x) #构建了10440个词,从0-10439
for w,i in TEXT.vocab.stoi.items():
print(w,i)
#加载glove词向量,第一次使用会自动下载,也可以自己下载好该词向量,我这里用的是400000词,每个词由100维向量表示
TEXT.vocab.load_vectors('glove.6B.100d',unk_init=torch.Tensor.normal_) #将数据中有但glove词向量中不存在的词进行随机初始化分配100维向量
我们可以查看一下构建的词嵌入矩阵维度大小,也就是我们构建的词表中的每个词被100维向量表示,所以词嵌入矩阵维度为[10440,100]
print(TEXT.vocab.vectors.shape) #torch.Size([10440, 100])
迭代器有Iterator和BucketIterator
一般在进行训练网络时,每一次我们都会输入一个batch的数据,我设置了batch_size=64,那么就有9989//64+1=157个batch,因为我们总共有9989条数据,每个batch有64条数据,而9989/64=156余5,那么余下的5条数据就会组成一个batch。
#创建迭代器
batch_size = 64
train_iter = torchtext.data.Iterator(dataset = train_x,batch_size=64,shuffle=True,sort_within_batch=False,repeat=False,device=device)
len(train_iter) #157
查看构建的迭代器以及内部数据表示:
#查看构建的迭代器
list(train_iter)
#查看批数据的大小
for batch in train_iter:
print(batch.utterance.shape)
可以看到每批数据为64条(除了最后一批数据),即batch_size=64,每条数据由30个词组成,我们也能看到最后剩下的5条数据组成了一个batch。
#查看第一条数据
batch.utterance[:,0]#我们取的是第1列,因为第1列表示第一条数据,即第64列表示第64条数据。每条数据由30个词组成,下面非1部分表示第一条数据中的词在词表中的索引,剩下的1表示补长的部分。
#查看第一条数据中的词所对应的索引值
list_a=[]
for i in batch.utterance[:,0]:
if i.item()!=1:
list_a.append(i.item())
print(list_a)
for i in list_a:
print(TEXT.vocab.itos[i],end=' ')
#查看迭代器中的数据及其对应的文本
l =[]
for batch in list(train_iter)[:1]:
for i in batch.utterance:
l.append(i[0].item())
print(l)
print(' '.join([TEXT.vocab.itos[i] for i in l]))
至此,我们就把数据处理完了,接下来就是搭建网络了。
本文的模型结构是比较简单的,采用了transformer中计算注意力的方法,我就只简单的解释一下Self-Attention这一部分。
首先,使用了LSTM输出层中的输出(记为X1,X2,X3)作为Self-Attention的输入,将这些输入通过Linear层(也就是下图中的W_Q,W_K,W_V)得到了每个输出的q,k,v,然后将得到的每个q,k,v组合到一起用于下面计算attention。这里补充一下,代码中没有将得到的每个q,k,v进行组合,是因为下面x1,x2,x3在得到自己的q时,用的线性层W_Q是一样的,所以我们可以将x1,x2和x3组合起来直接通过线性层W_Q就能的到组合之后的Q。(K和V在代码中也是这样操作的)
其次,根据公式
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q . ∗ K T / d k ) V Attention(Q,K,V) = softmax(Q.*K^T/\sqrt[]d_k)V Attention(Q,K,V)=softmax(Q.∗KT/dk)V
就能得到通过注意力机制后的向量表示了。
网络模型中的参数说明:
vocab_size = 10440
embedding_size = 100
hidden_dim = 128
n_layers = 1
num_class = 3
class LSTM_Attention(nn.Module):
def __init__(self,vocab_size,embedding_dim,hidden_dim,n_layers,num_class):
super(LSTM_Attention,self).__init__()
#从LSTM得到output之后,将output通过下面的linear层,然后就得到了Q,K,V
#这里我是用的attention_size是等于hidden_dim的,这里可以自己换成别的attention_size
self.W_Q = nn.Linear(hidden_dim,hidden_dim,bias =False)
self.W_K = nn.Linear(hidden_dim,hidden_dim,bias =False)
self.W_V = nn.Linear(hidden_dim,hidden_dim,bias =False)
#embedding层
self.embedding = nn.Embedding(vocab_size,embedding_dim)
#LSTM
self.rnn = nn.LSTM(input_size = embedding_dim,hidden_size = hidden_dim,num_layers = n_layers)
#Linear层,因为是三分类,所以后面的维度为3
self.fc = nn.Linear(hidden_dim,num_class)
#dropout
self.dropout = nn.Dropout(0.5)
#用来计算attention
def attention(self,Q,K,V):
d_k = K.size(-1)
scores = torch.matmul(Q,K.transpose(1,2)) / math.sqrt(d_k)
alpha_n = F.softmax(scores,dim=-1)
context = torch.matmul(alpha_n,V)
#这里都是组合之后的矩阵之间的计算,所以.sum之后,得到的output维度就是[batch_size,hidden_dim],并且每一行向量就表示一句话,所以总共会有batch_size行
output = context.sum(1)
return output,alpha_n
def forward(self,x):
#x.shape = [seq_len,batch_size] = [30,64]
embedding = self.dropout(self.embedding(x)) #embedding.shape = [seq_len,batch_size,embedding_dim = 100]
embedding = embedding.transpose(0,1) #embedding.shape = [batch_size,seq_len,embedding_dim]
#进行LSTM
output,(h_n,c) = self.rnn(embedding) #out.shape = [batch_size,seq_len,hidden_dim=128]
Q = self.W_Q(output) #[batch_size,seq_len,hidden_dim]
K = self.W_K(output)
V = self.W_V(output)
#将得到的Q,K,V送入attention函数进行运算
attn_output,alpha_n = self.attention(Q,K,V)
#attn_output.shape = [batch_size,hidden_dim=128]
#alpha_n.shape = [batch_size,seq_len,seq_len]
out = self.fc(attn_output) #out.shape = [batch_size,num_class]
return out
#看一下我们搭建的网络模型
net = LSTM_Attention(vocab_size=vocab_size, embedding_dim=embedding_size,hidden_dim=hidden_dim,n_layers=n_layers,num_class=num_class).to(device)
net
一般我是定义好训练函数,再调用进行模型训练,大家可以随意操作。
net.embedding.weight.data.copy_(TEXT.vocab.vectors) #给模型的Embedding层传入我们的词嵌入矩阵
optimizer = optim.Adam(net.parameters(),lr=1e-3) #定义优化器,lr是学习率可以自己调
criterion = nn.CrossEntropyLoss().to(device) #定义损失函数
train_x_len = len(train_x) #这一步是我为了计算后面的Acc而获取的数据数量,也就是9989
#定义训练函数
def train(net,iterator,optimizer,criterion,train_x_len):
epoch_loss = 0 #初始化loss值
epoch_acc = 0 #初始化acc值
for batch in iterator:
optimizer.zero_grad() #梯度清零
preds = net(batch.utterance) #前向传播,求出预测值
loss = criterion(preds,batch.label) #计算loss
epoch_loss +=loss.item() #累加loss,作为下面求平均loss的分子
loss.backward() #反向传播
optimizer.step() #更新网络中的权重参数
epoch_acc+=((preds.argmax(axis=1))==batch.label).sum().item() #累加acc,作为下面求平均acc的分子
return epoch_loss/(len(iterator)),epoch_acc/train_x_len #返回的是loss值和acc值
n_epoch = 100
acc_plot=[] #用于后面画图
loss_plot=[] #用于后面画图
for epoch in range(n_epoch):
train_loss,train_acc = train(net,train_iter,optimizer,criterion,train_x_len)
acc_plot.append(train_acc)
loss_plot.append(train_loss)
if (epoch+1)%10==0:
print('epoch: %d \t loss: %.4f \t train_acc: %.4f'%(epoch+1,train_loss,train_acc))
#使用画图函数matplotlib
plt.figure(figsize =(10,5),dpi=80)
plt.plot(acc_plot,label='train_acc')
plt.plot(loss_plot,color='coral',label='train_loss')
plt.legend(loc = 0)
plt.grid(True,linestyle = '--',alpha=1)
plt.xlabel('epoch',fontsize = 15)
plt.show()
本文主要是利用一个三分类的数据集做分类任务,模型是LSTM后面拼接了Self-Attention机制,本文没有使用验证集对模型进行评估,大家可以尝试从训练集中分出来一部分数据做验证集,并对模型进行评估。