Данные, используемые в этом примере, представляют собой данные трех категорий на английском языке Torchtext используется для обработки данных, построения итератора и построения textcnn, а также для обучения данных с помощью textcnn для получения результата обучения. Набор проверки не использовался для оценки модели в этом примере.
1. Среда разработки и наборы данных
1. Среда разработки
Ubuntu 16.04.6
питон: 3.7
Питорч: 1.8.1
torchtext: 0.9.1
2. Набор данных
набор данных:train_data_sentimentКод извлечения: gw77
2. Используйте torchtext для обработки наборов данных
1. Импортируйте необходимые библиотеки
#导入常用库
import torch
import pandas as pd
import matplotlib.pyplot as plt
from gensim.models import KeyedVectors
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import torch.nn.functional as F
import torchtext
#比较新版本的需要使用torchtext.legacy.data,旧版本的torchtext使用torchtex.data
from torchtext.legacy.data import TabularDataset
import warnings
warnings.filterwarnings("ignore")
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") #我在写博客的时候我们实验室服务器3卡没人用,所以我用的3卡
2. Импорт и просмотр наборов данных
#导入数据集,这一步只是给大家看一下数据集,后面在构建Dataset的时候也可以直接处理数据集
train_data = pd.read_csv('train_data_sentiment.csv')
train_data
3. Используйте torchtext для обработки наборов данных
Обработка данных torchtext в основном включает в себя: определение поля, набора данных и итератора, которые могут легко обрабатывать текстовые данные, такие как сегментация слов, усечение и дополнение, а также создание словаря. Те, кто не знаком с torchtext, могут изучить официальную документацию или объяснить в блоге.
3.1. Определить поле
TEXT = torchtext.legacy.data.Field(sequential=True,lower=True,fix_length=30)#默认分词器是split(),空格分割
LABEL = torchtext.legacy.data.Field(sequential=False,use_vocab=False)
- последовательный: следует ли представлять данные в виде последовательности, если это False, сегментация слов не может использоваться. По умолчанию: Истина.
- нижний: следует ли преобразовывать данные в нижний регистр. По умолчанию: Ложь.
- fix_length: при построении итератора измените длину всех текстовых данных на это значение, усеките длину и используйте pad_token для ее завершения. По умолчанию: Нет.
- use_vocab: использовать ли объект словаря.Если False, тип данных уже должен быть числовым типом. По умолчанию: Истина.
3.2. Определить набор данных
TabularDataset может легко читать файлы данных в формате CSV, JSON или TSV.
train_x = TabularDataset(path = 'train_data_sentiment.csv',
format = 'csv',skip_header=True,
fields = [('utterance',TEXT),('label',LABEL)])
- skip_header = True, не обрабатывать имена столбцов как данные.
- Порядок полей должен быть таким же, как порядок исходных столбцов данных.
Вы можете видеть, что текстовые данные были сегментированы
3.3 Создайте словарный запас и загрузите предварительно обученные векторы слов
Поскольку компьютеры не понимают текст, нам нужно преобразовать текстовые данные в числовые значения или векторы, чтобы мы могли вводить их в textcnn или глубокие нейронные сети для обучения. Сначала создайте текстовые данные какуказатель словЗатем, после загрузки предварительно обученного вектора слов, каждое слово будет соответствовать вектору слов, то естьслово-векторНаконец, в последующем обучении модели мы можем использовать матрицу встраивания слов, то есть слой встраивания. Таким образом, мы преобразуем каждое слово в вектор, который можно передать в модель для обучения.
Объясните матрицу встраивания слова здесь, потому что мне потребовалось много времени, чтобы понять матрицу встраивания слова, когда я учился. После того, как мы построим словарь, мы можем использовать индекс для представления предложения, например, в соответствии со словарем, построенным ниже, например:it is you
, это предложение можно использовать 10 9 3
Представление, конечно, мы можем напрямую использовать индекс для ввода в сеть для обучения, но функций, представляемых индексом, слишком мало.Чтобы получить лучшие функции для обучения сети, мы обычно используем вектор word2vec или перчатку вектор. В этой статье используется вектор перчаток, так что характеристики каждого слова лучше представлены, что более полезно для обучения сети. it is you
Каждое слово будет представлено 300-мерным вектором, так что больше его признаков будет передано в сеть, и наша сетевая модель будет лучше обучена. Подводя итог матрице встраивания слов, можно сказать следующее: ① Создайте словарный запас, т.е.указатель слов; ② Загрузите предварительно обученный вектор слов, то естьслово-вектор; ③ получить матрицу встраивания слов, то естьиндекс-вектор.
#构建词表
TEXT.build_vocab(train_x) #构建了10440个词,从0-10439
for w,i in TEXT.vocab.stoi.items():
print(w,i)
#加载glove词向量,第一次使用会自动下载,也可以自己下载好该词向量,我这里用的是400000词,每个词由300维向量表示
TEXT.vocab.load_vectors('glove.6B.300d',unk_init=torch.Tensor.normal_) #将数据中有但glove词向量中不存在的词进行随机初始化分配300维向量
Мы можем посмотреть на векторы в построенной матрице встраивания слов, Вот вектор слова с индексом 3 в словаре, который мы построили, которыйyouЭто слово, конечно же, это слово представлено 300-мерным вектором, и здесь показана только часть скриншота.
Давайте посмотрим на вектор перчаткиyouВектор, соответствующий этому слову, показан здесь лишь частично, что показывает, что мы можем получить вектор соответствующего слова через индекс, то есть смысл матрицы вложения слова.
#查看词向量维度
print(TEXT.vocab.vectors.shape) #torch.Size([10440, 300])
Видно, что наши данные разбиты на 10440 слов, каждое слово представлено 300-мерным вектором, а затем мы можем построить итератор.
3.4 Создание итератора
Итераторы имеют Iterator и BucketIterator
- Итератор: создавайте пакеты данных в том же порядке, что и исходные данные.
- BucketIterator: конструирует данные аналогичной длины в пакет, что снижаетобрезатьПрокладка во время работы.
Как правило, при обучении сети мы каждый раз будем вводить пакет данных. Имеется 64 фрагмента данных, и 9989/64=156, если их больше 5, тогда оставшиеся 5 фрагментов данных образуют пакет.
batch_size = 64
train_iter = torchtext.legacy.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)
#查看批数据的大小
for batch in train_iter:
print(batch.utterance.shape)
Видно, что каждый пакет данных составляет 64 штуки (кроме последнего пакета данных), то есть batch_size=64, каждый кусок данных состоит из 30 слов, а также мы можем видеть, что последние оставшиеся 5 кусков данные образуют пакет.
#查看第一条数据
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]))
На этом этапе данные обрабатываются, и далее мы узнаем о textcnn.
В-третьих, знание textcnn и построение фреймворка версии pytorch
1. Знание textcnn
textcnn похож на cnn, с которым мы знакомы для обработки изображений, за исключением того, что размер ядра свертки в cnn обычноk * k
, а размер ядра свертки в НЛП обычноk * embedding_size
, где embedding_size представлен вектором слов размерности embedding_size для каждого слова. Например, предложение на рисунке ниже состоит из трех слов, и каждое слово представлено трехмерным вектором слов.Выбираем два ядра свертки размером 2*3, и можно получить следующие результаты.Результат после пулинга на самом деле происходит сращивание, здесь для наглядности сплайсинга нет.Как видно из приведенного выше рисунка, результат, полученный после свертки ядра свертки, равен[len(sentence)-k+1,1]
Где Лен (предложение) представлена количеством слов-слова, вы получите после объединения[2,1]
Получается, что результат сращен.
После понимания основных теоретических знаний мы можем построить сетевую структуру textcnn.Если вы хотите узнать больше о textcnn, вы можете найти информацию для продолжения обучения.
2. Используйте pytorch для сборки textcnn
Я построил двухслойную сеть textcnn.Структура textcnn в основном:卷积、激活、池化
.
Описание параметра в сетевом фрейме:
- vocab_size: количество слов в построенном словаре
- embedding_size: размер вектора слова каждого слова
- num_channels: количество выходных каналов, то есть количество ядер свертки.
- kernel_sizes : размер ядра свертки
kernel_sizes, nums_channels = [3, 4], [150, 150]
embedding_size = 300
num_class = 3
vocab_size = 10440
Здесь я построил двухслойную сеть textcnn, а размер первого слоя ядра свертки таков:3 * 300,共有150形状相同的卷积核
, размер ядра свертки второго слоя:4*300,同样的也有150个这样的卷积核
, код фреймворка textcnn выглядит следующим образом:
class TextCNN(nn.Module):
def __init__(self,kernel_sizes,num_channels):
super(TextCNN,self).__init__()
self.embedding = nn.Embedding(vocab_size,embedding_size) #embedding层
self.dropout = nn.Dropout(0.5)
self.convs = nn.ModuleList()
for c,k in zip(num_channels,kernel_sizes):
self.convs.append(nn.Sequential(nn.Conv1d(in_channels=embedding_size,
out_channels = c, #这里输出通道数为[150,150],即有150个卷积核大小为3*embedding_size和4*embedding_size的卷积核
kernel_size = k), # 卷积核的大小,这里指3和4
nn.ReLU(), #激活函数
nn.MaxPool1d(30-k+1))) #池化,在卷积后的30-k+1个结果中选取一个最大值,30表示的事每条数据由30个词组成,
self.decoder = nn.Linear(sum(num_channels),3) #全连接层,输入为300维向量,输出为3维,即分类数
def forward(self,inputs):
embed = self.embedding(inputs) #[30,64,300]
embed = embed.permute(1,2,0) #[64,300,30],这一步是交换维度,为了符合后面的卷积操作的输入
#在下一步的encoding中经过两层textcnn之后,每一层都会得到一个[64,150,1]的结果,squeeze之后为[64,150],然后将两个结果拼接得到[64,300]
encoding = torch.cat([conv(embed).squeeze(-1) for conv in self.convs],dim=1) #[64,300]
outputs =self.decoder(self.dropout(encoding)) #将[64,300]输入到全连接层,最终得到[64,3]的结果
return outputs
Давайте посмотрим на построенную сетевую структуру
net = TextCNN(kernel_sizes, nums_channels).to(device)
net
Как видно из рисунка, построенная сеть имеет два слоя, разница между двумя слоями в том, что размер ядра свертки и слоя пула разные, а остальные одинаковые.
- Количество входных каналовin_channels=300;
- Количество выходных каналовout_channels=150;
- Размер ядра свертки: первый слой3 * 300, второй слой4 * 300
- Шаг сверткиstride = 1
- Стимstride = 30-kernel_size+1, Поскольку каждый набор данных состоит из 30 слов, цель объединения состоит в том, чтобы выбрать наибольшее значение из результатов после свертки в качестве результата, что является целью максимального объединения, а после свертки результат
[30-kernel_size+1,1]
, Этот шаг вы можете подтолкнуть себя, очень просто.
4. Обучение модели и результаты
1. Определите такие параметры, как функция обучения, оптимизатор, функция потерь и т. д.
Как правило, я определяю функцию обучения, а затем вызываю ее для обучения модели, и вы можете управлять ею по своему усмотрению.
net.embedding.weight.data.copy_(TEXT.vocab.vectors) #给模型的Embedding层传入我们的词嵌入矩阵
optimizer = optim.Adam(net.parameters(),lr=1e-4) #定义优化器,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/(train_x_len),epoch_acc/train_x_len #返回的是loss值和acc值
2. тренироваться
Всего обучается 100 эпох, а распечатки производятся каждые 10 эпох.
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))
Результат выглядит следующим образом:
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()
V. Резюме
В этой статье в основном используется torchtext для обработки обучающего набора данных, он строится как итератор для ввода модели и не использует проверочный набор для оценки модели.Вы можете попытаться отделить проверочный набор от обучающего набора для оценки модели. . Полный код выглядит следующим образом:
#代码我测试过了,没有任何问题,一些用于打印输出的代码我注释掉了,
#导入常用库
import torch
import pandas as pd
import matplotlib.pyplot as plt
from gensim.models import KeyedVectors
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import torch.nn.functional as F
import torchtext
#比较新版本的需要使用torchtext.legacy.data,旧版本的torchtext使用torchtex.data
from torchtext.legacy.data import TabularDataset
import warnings
warnings.filterwarnings("ignore")
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") #我在写博客的时候我们实验室服务器3卡没人用,所以我用的3卡
train_data = pd.read_csv('train_data_sentiment.csv')
# train_data
#使用torchtext处理数据
#定义filed
TEXT = torchtext.legacy.data.Field(sequential=True,lower=True,fix_length=30)
LABEL = torchtext.legacy.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)])
# print(train_x[0].utterance)
# print(train_x[0].label)
TEXT.build_vocab(train_x)
# for w,i in TEXT.vocab.stoi.items():
# print(w,i)
TEXT.vocab.load_vectors('glove.6B.300d',unk_init=torch.Tensor.normal_)
glove_model = KeyedVectors.load_word2vec_format('glove.6B.300d.word2vec.txt', binary=False)
# glove_model['you']
# print(TEXT.vocab.vectors.shape) #torch.Size([10440, 300])
batch_size = 64
train_iter = torchtext.legacy.data.Iterator(dataset = train_x,batch_size=64,shuffle=True,sort_within_batch=False,repeat=False,device=device)
# len(train_iter)
# list(train_iter)
# for batch in train_iter:
# print(batch.utterance.shape)
# batch.utterance[:,0]
# 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]))
kernel_sizes, nums_channels = [3, 4], [150, 150]
embedding_size = 300
num_class = 3
vocab_size = 10440
#搭建textcnn
class TextCNN(nn.Module):
def __init__(self,kernel_sizes,num_channels):
super(TextCNN,self).__init__()
self.embedding = nn.Embedding(vocab_size,embedding_size)
self.dropout = nn.Dropout(0.5)
self.convs = nn.ModuleList()
for c,k in zip(num_channels,kernel_sizes):
self.convs.append(nn.Sequential(nn.Conv1d(in_channels=embedding_size,
out_channels = c,
kernel_size = k),
nn.ReLU(),
nn.MaxPool1d(30-k+1)))
self.decoder = nn.Linear(sum(num_channels),3)
def forward(self,inputs):
embed = self.embedding(inputs)
embed = embed.permute(1,2,0)
encoding = torch.cat([conv(embed).squeeze(-1) for conv in self.convs],dim=1)
outputs =self.decoder(self.dropout(encoding))
return outputs
net = TextCNN(kernel_sizes, nums_channels).to(device)
# net
net.embedding.weight.data.copy_(TEXT.vocab.vectors)
optimizer = optim.Adam(net.parameters(),lr=1e-4)
criterion = nn.CrossEntropyLoss().to(device)
train_x_len = len(train_x)
#定义训练函数
def train(net,iterator,optimizer,criterion,train_x_len):
epoch_loss = 0
epoch_acc = 0
for batch in iterator:
optimizer.zero_grad()
preds = net(batch.utterance)
loss = criterion(preds,batch.label)
epoch_loss +=loss.item()
loss.backward()
optimizer.step()
epoch_acc+=((preds.argmax(axis=1))==batch.label).sum().item()
return epoch_loss/(train_x_len),epoch_acc/train_x_len
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))
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()