pytorch实现textCNN的具体操作

1. 原理

2014年的一篇文章,开创cnn用到文本分类的先河。

Convolutional Neural Networks for Sentence Classification

原理说简单也简单,其实就是单层CNN加个全连接层:

不过与图像中的cnn相比,改动为将卷积核的宽固定为一个词向量的维度,而长度一般取2,3,4,5这样。

上图中第一幅图的每个词对应的一行为一个词向量,可以使用word2vec或者glove预训练得到。本例中使用随机初始化的向量。

2. 数据预处理

手中有三个文件,分别为train.txt,valid.txt,test.txt。其中每一行是一个字符串化的字典,格式为{‘type': ‘xx', ‘text':‘xxxxx'}。

2.1 转换为csv格式

首先将每个文件转换为csv文件,分为text和label两列。一共有4种label,可以转换为数字表示。代码如下:

# 获取文件内容
def getData(file):
    f = open(file,'r')
    raw_data = f.readlines()
    return raw_data
# 转换文件格式
def d2csv(raw_data,label_map,name):
    texts = []
    labels = []
    i = 0
    for line in raw_data:
        d = eval(line) #将每行字符串转换为字典
        if len(d['type']) <= 1 or len(d['text']) <= 1: #筛掉无效数据
            continue
        y = label_map[d['type']] #根据label_map将label转换为数字表示
        x = d['text']
        texts.append(x)
        labels.append(y)
        i+=1
        if i%1000 == 0:
            print(i)
    df = pd.DataFrame({'text':texts,'label':labels})
    df.to_csv('data/'+name+'.csv',index=False,sep='\t') # 保存文件
label_map = {'执行':0,'刑事':1,'民事':2,'行政':3}
train_data = getData('data/train.txt') #22000+行
d2csv(train_data,label_map,'train')
valid_data = getData('data/valid.txt') # 2000+行
d2csv(valid_data,label_map,'valid')
test_data = getData('data/test.txt') # 2000+行
d2csv(test_data,label_map,'test')

2.2 观察数据分布

对于本任务来说,需要观察每个文本分词之后的长度。因为每个句子是不一样长的,所以需要设定一个固定的长度给模型,数据中不够长的部分填充,超出部分舍去。

训练的时候只有训练数据,因此观察训练数据的文本长度分布即可。分词可以使用jieba分词等工具。

train_text = []
for line in train_data:
    d = eval(line)
    t = jieba.cut(d['text'])
    train_text.append(t)
sentence_length = [len(x) for x in train_text] #train_text是train.csv中每一行分词之后的数据
%matplotlib notebook
import matplotlib.pyplot as plt
plt.hist(sentence_length,1000,normed=1,cumulative=True)
plt.xlim(0,1000)
plt.show()

得到长度的分布图:

可以看到长度小于1000的文本占据所有训练数据的80%左右,因此训练时每个文本固定长度为1000个词。

2.3 由文本得到训练用的mini-batch数据

目前我们手里的数据为csv形式的两列数据,一列字符串text,一列数字label。label部分不需要再处理了,不过text部分跟可训练的数据还差得远。

假设每个词对应的词向量维度为 D i m Dim Dim,每一个样本的分词后的长度已知设为 W = 1000 W=1000 W=1000,每个mini-batch的大小为 N N N。那么我们希望得到的是一个个维度为 N ∗ W ∗ D i m N*W*Dim N∗W∗Dim的浮点数数据作为mini-batch输入到模型。

于是还需要以下几个步骤:

分词去除停用词建立词汇表(词汇表是词语到index的映射,index从0到M,M为已知词汇的个数,形如{'可爱‘:0, ‘美好':1,…})将分词且去除停用词之后的数据转换为下标数据,维度应该为 N a l l ∗ W N_{all}*W Nall​∗W, N a l l N_{all} Nall​是所有样本的数量。其中长度不足W的样本在后面补特定字符,长度超过W的样本截断。将数据分割为一个个 N ∗ W N*W N∗W大小的mini-batch作为模型的输入。根据mini-batch数据向词向量中映射得到 N ∗ W ∗ D i m N*W*Dim N∗W∗Dim大小的最终输入。(这步在模型中)

看起来复杂哭了,手动处理起来确实有些麻烦。不过后来发现跟pytorch很相关的有个包torchtext能够很方便的做到这几步,所以直接来介绍用这个包的做法。

在贴代码之前先贴两个torchtext的教程。torchtext入门教程 还是不懂的话看torchtext文档。 还还是不懂请直接看源码。对照教程看以下代码。

首先是分词函数,写为有一个参数的函数:

def tokenizer(x):
    res = [w for w in jieba.cut(x)]
    return res

接着是停用词表,在网上找的一个停用词资源(也可以跳过这步):

stop_words = []
print('build stop words set')
with open('data/stopwords.dat') as f:
    for l in f.readlines():
        stop_words.append(l.strip())

然后设定TEXT和LABEL两个field。定义以及参数含义看上面的文档或教程。

TEXT = data.Field(sequential=True, tokenize=tokenizer,fix_length=1000,stop_words=stop_words)
LABEL = data.Field(sequential=False,use_vocab=False)

读取文件,分词,去掉停用词等等。直接一波带走:

train,valid,test = data.TabularDataset.splits(path='data',train='train.csv',
                                              validation='valid.csv',test='test.csv',
                                              format='csv',
                                              skip_header=True,csv_reader_params={'delimiter':'\t'},
                                              fields=[('text',TEXT),('label',LABEL)])

建立词汇表:

TEXT.build_vocab(train)

生成iterator形式的mini-batch数据:

train_iter, val_iter, test_iter = data.Iterator.splits((train,valid,test),
                                                             batch_sizes=(args.batch_size,args.batch_size,args.batch_size),
                                                             device=args.device,
                                                             sort_key=lambda x:len(x.text),
                                                             sort_within_batch=False,
                                                             repeat=False)

That's all! 简单得令人发指!虽然为了搞懂这几个函数整了大半天。最终的这几个xxx_iter就会生成我们需要的维度为N ∗ W N*WN∗W的数据。

3. 模型

模型其实相对很简单,只有一个embedding映射,加一层cnn加一个激活函数以及一个全连接。

不过需要注意使用不同大小的卷积核的写法。

可以选择使用多个nn.Conv2d然后手动拼起来,这里使用nn.ModuleList模块。其实本质上还是使用多个Conv2d然后拼起来。

import torch
import torch.nn as nn
import torch.nn.functional as F
class textCNN(nn.Module):
    def __init__(self, args):
        super(textCNN, self).__init__()
        self.args = args

        Vocab = args.embed_num ## 已知词的数量
        Dim = args.embed_dim ##每个词向量长度
        Cla = args.class_num ##类别数
        Ci = 1 ##输入的channel数
        Knum = args.kernel_num ## 每种卷积核的数量
        Ks = args.kernel_sizes ## 卷积核list,形如[2,3,4]

        self.embed = nn.Embedding(Vocab,Dim) ## 词向量,这里直接随机

        self.convs = nn.ModuleList([nn.Conv2d(Ci,Knum,(K,Dim)) for K in Ks]) ## 卷积层
        self.dropout = nn.Dropout(args.dropout)
        self.fc = nn.Linear(len(Ks)*Knum,Cla) ##全连接层

    def forward(self,x):
        x = self.embed(x) #(N,W,D)

        x = x.unsqueeze(1) #(N,Ci,W,D)
        x = [F.relu(conv(x)).squeeze(3) for conv in self.convs] # len(Ks)*(N,Knum,W)
        x = [F.max_pool1d(line,line.size(2)).squeeze(2) for line in x]  # len(Ks)*(N,Knum)

        x = torch.cat(x,1) #(N,Knum*len(Ks))

        x = self.dropout(x)
        logit = self.fc(x)
        return logit

4. 训练脚本

import os
import sys
import torch
import torch.autograd as autograd
import torch.nn.functional as F
def train(train_iter, dev_iter, model, args):
    if args.cuda:
        model.cuda(args.device)
    optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)

    steps = 0
    best_acc = 0
    last_step = 0
    model.train()
    print('training...')
    for epoch in range(1, args.epochs+1):
        for batch in train_iter:
            feature, target = batch.text, batch.label #(W,N) (N)
            feature.data.t_()

            if args.cuda:
                feature, target = feature.cuda(), target.cuda()

            optimizer.zero_grad()
            logit = model(feature)
            loss = F.cross_entropy(logit, target)
            loss.backward()
            optimizer.step()

            steps += 1
            if steps % args.log_interval == 0:
                result = torch.max(logit,1)[1].view(target.size())
                corrects = (result.data == target.data).sum()
                accuracy = corrects*100.0/batch.batch_size
                sys.stdout.write('\rBatch[{}] - loss: {:.6f} acc: {:.4f}$({}/{})'.format(steps,
                                                                                        loss.data.item(),
                                                                                        accuracy,
                                                                                        corrects,
                                                                                        batch.batch_size))
            if steps % args.dev_interval == 0:
                dev_acc = eval(dev_iter, model, args)
                if dev_acc > best_acc:
                    best_acc = dev_acc
                    last_step = steps
                    if args.save_best:
                        save(model,args.save_dir,'best',steps)
                else:
                    if steps - last_step >= args.early_stop:
                        print('early stop by {} steps.'.format(args.early_stop))
            elif steps % args.save_interval == 0:
                save(model,args.save_dir,'snapshot',steps)

训练脚本中还有设置optimizer以及loss的部分。其余部分比较trivial。

模型的保存:

def save(model, save_dir, save_prefix, steps):
    if not os.path.isdir(save_dir):
        os.makedirs(save_dir)
    save_prefix = os.path.join(save_dir,save_prefix)
    save_path = '{}_steps_{}.pt'.format(save_prefix,steps)
    torch.save(model.state_dict(),save_path)

eval函数,用来评估验证集与测试集合上的准确率acc。

def eval(data_iter, model, args):
    model.eval()
    corrects, avg_loss = 0,0
    for batch in data_iter:
        feature, target = batch.text, batch.label
        feature.data.t_()

        if args.cuda:
            feature, target = feature.cuda(), target.cuda()

        logit = model(feature)
        loss = F.cross_entropy(logit,target)

        avg_loss += loss.data[0]
        result = torch.max(logit,1)[1]
        corrects += (result.view(target.size()).data == target.data).sum()

    size = len(data_iter.dataset)
    avg_loss /= size
    accuracy = 100.0 * corrects/size
    print('\nEvaluation - loss: {:.6f} acc: {:.4f}%({}/{}) \n'.format(avg_loss,accuracy,corrects,size))
    return accuracy

5. main函数

这暂时就不贴了。可以参考下一部分给出的github。

最终在测试集合上accuracy为97%(毕竟只是四分类)。

但是遇到个问题就是随着accuracy上升,loss也在迅速增大。

在一番探究之后大致得出结论就是,这样是没问题的。比如在本例中是个四分类,加入全连接层输出的结果是[-10000,0,0,10000],而正确分类是0。

那么这就是个错误的结果。计算一下这个单个样例的loss。先算softmax,约等于[ e − 20000 , e − 10000 , e − 10000 , 1 e^{-20000},e^{-10000},e^{-10000},1 e−20000,e−10000,e−10000,1]。真实的label为[1,0,0,0],因此交叉熵为20000。

所以我们发现这一个错误样例的loss就会这么大。最终的loss大一些也是正常的。

不过为什么随着accuracy接近100%而导致loss迅速增加这个问题还需要进一步研究。大概是因为随着accuracy升高导致结果接近训练集的分布,这样与验证集或测试集的分布产生比较极端差别的个例会增加。

6.引用

代码部分参考了很多这位老哥的github,在此感谢。跟他不一样的地方主要是数据处理部分。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • pytorch实现ResNet结构的实例代码

    1.ResNet的创新 现在重新稍微系统的介绍一下ResNet网络结构. ResNet结构首先通过一个卷积层然后有一个池化层,然后通过一系列的残差结构,最后再通过一个平均池化下采样操作,以及一个全连接层的得到了一个输出.ResNet网络可以达到很深的层数的原因就是不断的堆叠残差结构而来的. 1)亮点 网络中的亮点 : 超深的网络结构( 突破1000 层) 提出residual 模块 使用Batch Normalization 加速训练( 丢弃dropout) 但是,一般来说,并不是一直的加深神经

  • Pytorch 如何实现LSTM时间序列预测

    开发环境说明: Python 35 Pytorch 0.2 CPU/GPU均可 1.LSTM简介 人类在进行学习时,往往不总是零开始,学习物理你会有数学基础.学习英语你会有中文基础等等. 于是对于机器而言,神经网络的学习亦可不再从零开始,于是出现了Transfer Learning,就是把一个领域已训练好的网络用于初始化另一个领域的任务,例如会下棋的神经网络可以用于打德州扑克. 我们这讲的是另一种不从零开始学习的神经网络--循环神经网络(Recurrent Neural Network, RNN

  • Pytorch 实现变量类型转换

    Pytorch的数据类型为各式各样的Tensor,Tensor可以理解为高维矩阵. 与Numpy中的Array类似.Pytorch中的tensor又包括CPU上的数据类型和GPU上的数据类型,一般GPU上的Tensor是CPU上的Tensor加cuda()函数得到.通过使用Type函数可以查看变量类型. 一般系统默认是torch.FloatTensor类型. 例如data = torch.Tensor(2,3)是一个2*3的张量,类型为FloatTensor; data.cuda()就转换为GP

  • 安装pytorch时报sslerror错误的解决方案

    首先说一下 ,我是用的anaconda3装的pytorch 为了方便建议你也安装一个. 其实这个挺简单的,你找找"c:/user/你的用户名/"目录下有没有一个叫 .condarc 的文件,如图: 如果没有,创建一个就好,不过一般会自动创建一个 然后复制下面的文件进入这个文件覆盖 channels: - https://mirrors.ustc.edu.cn/anaconda/cloud/pytorch/win-64 - https://mirrors.ustc.edu.cn/anac

  • pytorch常用数据类型所占字节数对照表一览

    PyTorch上的常用数据类型如下 Data type dtype CPU tensor GPU tensor Size/bytes 32-bit floating torch.float32 or torch.float torch.FloatTensor torch.cuda.FloatTensor 4 64-bit floating torch.float64 or torch.double torch.DoubleTensor torch.cuda.DoubleTensor 8 16-b

  • Linux环境下GPU版本的pytorch安装

    服务器环境: Ubuntu 16.04.7 显卡:2080 cuda:10.1 注:若服务器有管理员账户和个人账户,最好在个人账户下重新安装anaconda,否则安装pytorch过程中可能有些库安装失败,由于权限问题,不能删除这些失败的库重新安装.在个人账户下就不存在权限问题. 一 添加镜像源 目的:使用默认的源地址下载速度很慢,会出现超时,导致某些第三方库只下载了部分,不完整,最终失败. 首先查看当前镜像源 cat ~/.condarc 或者 conda config --show chan

  • pytorch实现textCNN的具体操作

    1. 原理 2014年的一篇文章,开创cnn用到文本分类的先河. Convolutional Neural Networks for Sentence Classification 原理说简单也简单,其实就是单层CNN加个全连接层: 不过与图像中的cnn相比,改动为将卷积核的宽固定为一个词向量的维度,而长度一般取2,3,4,5这样. 上图中第一幅图的每个词对应的一行为一个词向量,可以使用word2vec或者glove预训练得到.本例中使用随机初始化的向量. 2. 数据预处理 手中有三个文件,分别

  • pytorch快速搭建神经网络_Sequential操作

    之前用Class类来搭建神经网络 class Neuro_net(torch.nn.Module): """神经网络""" def __init__(self, n_feature, n_hidden_layer, n_output): super(Neuro_net, self).__init__() self.hidden_layer = torch.nn.Linear(n_feature, n_hidden_layer) self.outp

  • Pytorch 高效使用GPU的操作

    前言 深度学习涉及很多向量或多矩阵运算,如矩阵相乘.矩阵相加.矩阵-向量乘法等.深层模型的算法,如BP,Auto-Encoder,CNN等,都可以写成矩阵运算的形式,无须写成循环运算.然而,在单核CPU上执行时,矩阵运算会被展开成循环的形式,本质上还是串行执行.GPU(Graphic Process Units,图形处理器)的众核体系结构包含几千个流处理器,可将矩阵运算并行化执行,大幅缩短计算时间.随着NVIDIA.AMD等公司不断推进其GPU的大规模并行架构,面向通用计算的GPU已成为加速可并

  • Pytorch 统计模型参数量的操作 param.numel()

    param.numel() 返回param中元素的数量 统计模型参数量 num_params = sum(param.numel() for param in net.parameters()) print(num_params) 补充:Pytorch 查看模型参数 Pytorch 查看模型参数 查看利用Pytorch搭建模型的参数,直接看程序 import torch # 引入torch.nn并指定别名 import torch.nn as nn import torch.nn.functio

  • Pytorch之扩充tensor的操作

    我就废话不多说了,大家还是直接看代码吧~ b = torch.zeros((3, 2, 6, 6)) a = torch.zeros((3, 2, 1, 1)) a.expand_as(b).size() Out[32]: torch.Size([3, 2, 6, 6]) a = torch.zeros((3, 2, 2, 1)) a.expand_as(b).size() Traceback (most recent call last): File "/home/lart/.conda/en

  • pytorch 多个反向传播操作

    之前我的一篇文章pytorch 计算图以及backward,讲了一些pytorch中基本的反向传播,理清了梯度是如何计算以及下降的,建议先看懂那个,然后再看这个. 从一个错误说起: RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed 在深度学习中,有些场景需要进行两次反向,比如Gan网络,需要对D进行一次,还要对G进行一次,很多人都会遇到

  • pytorch 实现变分自动编码器的操作

    本来以为自动编码器是很简单的东西,但是也是看了好多资料仍然不太懂它的原理.先把代码记录下来,有时间好好研究. 这个例子是用MNIST数据集生成为例子 # -*- coding: utf-8 -*- """ Created on Fri Oct 12 11:42:19 2018 @author: www """ import os import torch from torch.autograd import Variable import tor

  • pytorch Dropout过拟合的操作

    如下所示: import torch from torch.autograd import Variable import matplotlib.pyplot as plt torch.manual_seed(1) N_SAMPLES = 20 N_HIDDEN = 300 # training data x = torch.unsqueeze(torch.linspace(-1, 1, N_SAMPLES), 1) y = x + 0.3 * torch.normal(torch.zeros(

  • pytorch 6 batch_train 批训练操作

    看代码吧~ import torch import torch.utils.data as Data torch.manual_seed(1) # reproducible # BATCH_SIZE = 5 BATCH_SIZE = 8 # 每次使用8个数据同时传入网路 x = torch.linspace(1, 10, 10) # this is x data (torch tensor) y = torch.linspace(10, 1, 10) # this is y data (torc

  • 使用Pytorch训练two-head网络的操作

    之前有写过一篇如何使用Pytorch实现two-head(多输出)模型 在那篇文章里,基本把two-head网络以及构建讲清楚了(如果不清楚请先移步至那一篇博文). 但是我后来发现之前的训练方法貌似有些问题. 以前的训练方法: 之前是把两个head分开进行训练的,因此每一轮训练先要对一个batch的数据进行划分,然后再分别训练两个头.代码如下: f_out_y0, _ = net(x0) _, f_out_y1 = net(x1) #实例化损失函数 criterion0 = Loss() cri

随机推荐