其他分享
首页 > 其他分享> > torchtext+textcnn情感分类

torchtext+textcnn情感分类

作者:互联网

目录

本例子所用的数据为三分类的英文数据,利用torchtext处理数据,构建迭代器并搭建textcnn,将数据用textcnn进行训练,得到训练结果。本例中没有使用验证集对模型进行评估。

一、开发环境和数据集

1、开发环境

Ubuntu 16.04.6

python:3.7

pytorch:1.8.1

torchtext: 0.9.1

2、数据集

数据集:train_data_sentiment
提取码:gw77

二、使用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对数据的处理主要包括:定义Field、Dataset和迭代器这三部分,可以很方便的对文本数据进行处理,如:分词、截断补长、构建词表等。对torchtext不熟悉的可以学习一下官房文档或者讲解博客。

3.1、定义Field

TEXT = torchtext.legacy.data.Field(sequential=True,lower=True,fix_length=30)#默认分词器是split(),空格分割
LABEL = torchtext.legacy.data.Field(sequential=False,use_vocab=False)

3.2、定义Dataset

TabularDataset可以很方便地读取CSV、JSON或者TSV格式的数据文件。

train_x = TabularDataset(path = 'train_data_sentiment.csv',
                        format = 'csv',skip_header=True,
                        fields = [('utterance',TEXT),('label',LABEL)])

可以看到文本数据已经被分词
在这里插入图片描述

3.3、构建词表、加载预训练词向量

​ 因为计算机不认识文本,所以我们需要将文本数据转换成数值或者向量,这样我们才能输入到textcnn或者深度神经网络中去训练。首先将文本数据构建成词-索引的形式,接着,加载了预训练的词向量之后,每个词会对应一个词向量,即词-向量的形式,最后,在后面的模型训练中,我们就可以使用词嵌入矩阵了,即Embedding层。这样我们就将每个词都转换为向量了,可以输入到模型中进行训练了。

​ 这里解释一下词嵌入矩阵,因为我在学习的时候理解词嵌入矩阵就花费了很长时间。我们构建了词表之后,就可以用索引去表示一句话了,根据下面构建的词表举个例子,例如:it is you,这句话就可以用10 9 3表示,当然我们可以直接使用索引输入到网络中进行训练,但是索引所表示的特征太少了,我们为了得到更好的特征去训练网络一般会使用word2vec向量或者glove向量。本文使用了glove向量,这样每个词的特征就得到了更好的表示,更有利于我们训练网络。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维向量表示的,这里只截图了部分展示。
在这里插入图片描述

​ 我们再看一下glove向量中you这个词所对应的向量,这里也只截图了部分展示,这就说明了我们可以通过索引获取对应词的向量,也就是词嵌入矩阵的意义。
在这里插入图片描述

#查看词向量维度
print(TEXT.vocab.vectors.shape) #torch.Size([10440, 300])

可以看出来,我们的数据总共被分成10440个词,每个词由300维向量表示,接下来就可以构建迭代器了。

3.4、构建迭代器

迭代器有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.legacy.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]))

在这里插入图片描述

至此,数据就处理好了,我们接下来了解textcnn。

三、textcnn知识与pytorch版框架搭建

1、textcnn知识

​ textcnn和我们所熟悉处理图像的cnn是异曲同工的,只不过cnn中卷积核大小一般都是k * k,而NLP中的卷积核大小一般为k * embedding_size,其中embedding_size为每个词用embdding_size维的词向量表示。例如下图中的一句话由三个词组成,每个词由3维词向量表示,我们选取两个卷积核大小为2*3,可以得到以下结果,池化后的结果其实是会拼接起来,这里为了展示,就没有进行拼接。
在这里插入图片描述
​ 从上图可以看出,一个卷积核进行卷积之后得到的结果为[len(sentence)-k+1,1],其中len(sentence)表示一句话中由多少个词组成,池化后就会得到[2,1]的结果,这个结果是进行拼接了的。

基本的理论知识了解之后,我们就可以搭建textcnn的网络框架了,想要详细了解textcnn可以自己找资料继续学习。

2、利用pytorch搭建textcnn

我搭建了一个两层的textcnn网络,textcnn的框架主要是:卷积、激活、池化

网络框架中的参数说明:

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

在这里插入图片描述
从图中可以看出,搭建的网络共有两层,这两层的不同之处就是卷积核大小和池化层不相同,其他都相同。

四、模型训练及结果

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

在这里插入图片描述

五、总结

​ 本文主要是利用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()

标签:acc,loss,情感,batch,textcnn,torchtext,train,epoch,size
来源: https://blog.csdn.net/weixin_44376341/article/details/117911499