Классификация эмоций LSTM + Self-Attention

NLP

Данные, используемые в этой статье, представляют собой набор данных с тремя категориями на английском языке. Torchtext используется для обработки данных, а также создаются набор данных и итератор. Построенная сетевая модель - LSTM + Self-Attention. Данные обучаются с помощью построенной модели. и тренированный результат. В этой статье не используется набор проверки для оценки модели.

1. Среда разработки и наборы данных

1. Среда разработки

Ubuntu 16.04.6

питон: 3.7

Питорч: 1.7.1

торчтекст: 0.8.0

2. Набор данных

набор данных:train_data_sentiment

Код извлечения: gw77

2. Используйте torchtext для обработки наборов данных

1. Импортируйте необходимые библиотеки

 #导入常用库
 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") 

2. Импорт и просмотр наборов данных

 #导入数据集
 train_data = pd.read_csv('train_data_sentiment.csv')
 train_data

image.png

3. Используйте torchtext для обработки наборов данных

Основные этапы обработки данных torchtext:

  • Определить поле
  • Создать набор данных
  • Создать итератор

torchtext может легко сегментировать текстовые данные, усекать и дополнять, формировать словарный запас и т. д. Те, кто не знаком с torchtext, могут изучить официальные документы или объяснить блоги.

3.1. Определить поле

 #定义Field
 TEXT = torchtext.data.Field(sequential=True,lower=True,fix_length=30)  #这里使用默认分词器split(),按照空格进行分词
 LABEL = torchtext.data.Field(sequential=False,use_vocab=False)
  • последовательный: следует ли представлять данные в виде последовательности, если это False, сегментация слов не может использоваться. По умолчанию: Истина.
  • нижний: следует ли преобразовывать данные в нижний регистр. По умолчанию: Ложь.
  • fix_length: при построении итератора измените длину всех текстовых данных на это значение, усеките длину и используйте pad_token для ее завершения. По умолчанию: Нет.
  • use_vocab: использовать ли объект словаря.Если False, тип данных уже должен быть числовым типом. По умолчанию: Истина.

3.2. Создать набор данных

 train_x = TabularDataset(path = 'train_data_sentiment.csv',
                         format = 'csv',skip_header=True,
                         fields = [('utterance',TEXT),('label',LABEL)])
  • skip_header = True, не обрабатывать имена столбцов как данные.
  • Порядок полей должен быть таким же, как порядок исходных столбцов данных.

Глядя на данные, которые мы обработали на этом шаге, вы можете видеть, что исходные данные были сегментированы.

image.png

3.3, создайте таблицу слов, загрузите вектор слов перед обучением

 #构建词表
 TEXT.build_vocab(train_x)  #构建了10440个词,从0-10439
 for w,i in TEXT.vocab.stoi.items():
     print(w,i)

image.png

 #加载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])

image.png

3.4 Создание итератора

Итераторы имеют Iterator и BucketIterator

  • Итератор: создавайте пакеты данных в том же порядке, что и исходные данные.
  • BucketIterator: конструирует данные одинаковой длины в пакет, что снижаетобрезатьПрокладка во время работы.

Как правило, при обучении сети мы каждый раз будем вводить пакет данных.Я установил batch_size=64, тогда получается 9989//64+1=157 пакетов, потому что у нас всего 9989 блоков данных, каждый пакет имеет 64 фрагмента данных и 9989/64=156 с более чем 5, тогда оставшиеся 5 фрагментов данных образуют пакет.

 #创建迭代器
 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
  • shuffle: перемешивать ли данные
  • sort_within_batch: сортировать ли каждый пакет данных
  • Repeat: повторять ли пакетные данные повторно в разные эпохи

Посмотрите построенный итератор и его внутреннее представление данных:

 #查看构建的迭代器
 list(train_iter)

image.png

 #查看批数据的大小
 for batch in train_iter:
     print(batch.utterance.shape)

image.png

Видно, что каждый пакет данных составляет 64 штуки (кроме последнего пакета данных), то есть batch_size=64, каждый кусок данных состоит из 30 слов, а также мы можем видеть, что последние оставшиеся 5 кусков данные образуют пакет.

 #查看第一条数据
 batch.utterance[:,0]#我们取的是第1列,因为第1列表示第一条数据,即第64列表示第64条数据。每条数据由30个词组成,下面非1部分表示第一条数据中的词在词表中的索引,剩下的1表示补长的部分。

image.png

 #查看第一条数据中的词所对应的索引值
 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=' ')

image.png

 #查看迭代器中的数据及其对应的文本
 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]))

image.png

На этом мы закончили обработку данных, и следующим шагом будет построение сети.

3. Создайте сетевую модель LSTM + Self-Attention

1. Структура сетевой модели

image.png

2. Внимание к себе

Структура модели этой статьи относительно проста.Принят метод расчета внимания в преобразователе.Я лишь кратко объясню эту часть Само-Внимания.

Во-первых, выходные данные выходного слоя LSTM (обозначаемые как X1, X2, X3) используются в качестве входных данных для самоконтроля, и каждый выходной сигнал получается путем передачи этих входных данных через линейный слой (то есть W_Q, W_K, W_V). на рисунке ниже) q, k, v, а затем объединить каждое из полученных q, k, v для последующего расчета внимания.Здесь добавлено, что каждое полученное q, k, v не объединяется в коде, потому что следующие x1, x2, x3 используют один и тот же линейный слой W_Q при получении собственного q, поэтому мы можем использовать x1, x2, x3 Комбинация x2 и x3 могут напрямую пройти через линейный слой W_Q, чтобы получить комбинированный Q. (K и V также работают так в коде)

image.png

Во-вторых, по формуле

Attention(Q,K,V) = softmax(Q.*K^T/\sqrt[]d_k)V

Можно получить векторное представление после прохождения механизма внимания.

3. Используйте pytorch для создания модели

Описание параметра в сетевой модели:

  • vocab_size: количество слов в построенном словаре
  • embedding_size: размер вектора слова каждого слова
  • hidden_dim: количество единиц в скрытом слое в LSTM.
  • n_layers: количество скрытых слоев в LSTM
  • num_class: количество классов
 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

image.png

4. Обучение модели и результаты

1. Определите такие параметры, как функция обучения, оптимизатор и функция потерь.

Как правило, я определяю функцию обучения, а затем вызываю ее для обучения модели, и вы можете управлять ею по своему усмотрению.

 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值

2. тренироваться

 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))

Результат выглядит следующим образом:

image.png

3. Визуализируйте результаты

 #使用画图函数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()

image.png

V. Резюме

В этом документе в основном используется набор данных с тремя категориями для задач классификации. Модель представляет собой LSTM, за которым следует механизм самоконтроля. В этом документе не используется набор проверки для оценки модели. Вы можете попытаться отделить часть данных от обучающий набор в качестве проверочного набора Оцените модель.