pytorch实现textCNN的具体操作

pytorch实现textCNN的具体操作本文主要介绍pytorch实现textCNN的具体操作过程,具有很好的参考价值,希望对大家有所帮助如有错误或不足之处,请不吝赐教1. 原理2014年的一篇文章开创了cnn使用文本分类的先河用于句子分类的卷积神经网络原理简单明了其实就是单层CNN加全连接层:但与图像中的cnn相比,卷积核的宽度固定为一个字向量的维数,而长度一般为2、3、4、5上面第一张图中每个单词对应的线是单词向量,可以用word2vec或者glove进行预训练在这个例子中,使用了随机初始化的向量2. 数据预处理手头有三个文件,分别是train.txt、valid.txt和test.txt每行都是一个字符串字典,格式为{type: xx , text :xxxxx

}。

2.1 转换为csv格式

首先,将每个文件转换为csv文件,该文件分为两列:文本和标签。有四种标签可以转换成数字表示。代码如下:

#获取文件内容

def getData(文件):

f=打开(文件,“r”)

raw_data=f.readlines()

返回原始数据

#转换文件格式

def d2csv(raw_data,label_map,name):

文本=[]

标签=[]

i=0

对于raw_data中的行:

D=eval(line) #将字符串的每一行转换成字典

如果len (d [type])=1或len (d [text])=1: #过滤掉无效数据

继续

Y=label_map[d[type]] #根据label_map将标注转换为数字表示。

x=d[text]

文本.追加(x)

标签.追加(y)

i=1

如果i00==0:

打印(一)

df=pd。DataFrame({text:texts, label:labels})

Df.to _ csv (data/name )。csv ,index=false,sep= t) #保存文件

Label_map={Execution: 0, Criminal: 1, Civil: 2, Administration: 3}

train _ data=get data( data/train . txt )# 22000行

d2csv(列车数据,标签地图,“列车”)

valid _ data=get data( data/valid . txt )# 2000行

d2csv(有效数据,标签映射,有效)

test _ data=get data( data/test . txt )# 2000行

d2csv(测试数据,标签映射,“测试”)

2.2 观察数据分布

对于这个任务,需要观察分词后每段文字的长度。因为每句话的长度不一样,所以需要给模型设定一个固定的长度。数据中不够长的部分被填充,多余的部分被丢弃。

训练时只有训练数据,所以观察训练数据的文本长度分布。可以用解霸分词之类的工具。

train_text=[]

对于train_data中的行:

d=评估(线)

t=jieba.cut(d[text])

火车_文本.附加(t)

sentence _ length=[len(x)for x in train _ text]# train _ text是train.csv中每一行分词后的数据。

%matplotlib笔记本

将matplotlib.pyplot作为plt导入

plt.hist(句子长度,1000,正常=1,累积=真)

plt.xlim(0,1000)

plt.show()

得到长度分布图:

可以看出,长度小于1000的文本占所有训练数据的80%左右,所以训练时每篇文本的长度都是固定的1000字。

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

目前我们手里的数据是csv形式的两列数据,一列字符串文本,一列数字标签。标签部分不再需要处理,但是文本部分离可训练数据很远。

假设每个词对应的词向量维数为D i m Dim Dim,每个样本的分段长度已知为W=1000 W=1000 W=1000,每个小批量的大小为N N N N那么我们要得到维数为n w d I m n * w * dimnwdim的浮点数据作为模型的小批量输入。

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

去掉分词的停用词,建立一个词表(词表是词到index的映射,index从0到m,m是已知词的个数,比如{ 可爱:0,漂亮:1,…})。将分词和去除停用词后的数据转换为下标数据,维数应为n a l w n _ { all } * w nall w,n a l n _ { all }。其中长度小于w的样本用特定字符补充,长度超过w的样本被截断。将数据分成N W N * W NW大小的小批量作为模型的输入。根据mini-batch数据,通过映射到词向量,得到最终输入的n w d I m n * w * dimnwdim的大小。(这一步在模型中)

看起来很复杂,我都哭了。手动处理真的很麻烦。不过后来发现有一个和pytorch相关的包,torchtext,可以很方便的做这些步骤,所以我就介绍一下直接用这个包的做法。

在贴代码之前先贴两个torchtext的教程。如果你仍然不理解torchtext入门教程,请阅读torchtext文档。还是不明白,请直接看源代码。对照教程看下面的代码。

首先,它是一个分词函数,写成一个只有一个参数的函数:

定义标记符(x):

res=[w for w in jieba.cut(x)]

返回资源

然后停止使用这个词汇,在网上找一个停用词资源(你也可以跳过这一步):

stop_words=[]

打印(“构建停用词集”)

用open(data/stopwords.dat )作为f:

对于f.readlines()中的l:

stop_words.append(l.strip())

然后设置两个字段,文本和标签。并定义参数的含义。参见上述文档或教程。

文本=数据。字段(sequential=True,tokenize=tokenizer,fix_length=1000,stop_words=stop_words)

标签=数据。Field(sequential=False,use_vocab=False)

读文件,分词,去掉停用词等等。直接挥手带走:

训练,有效,测试=数据。tabular dataset . splits(path= data ,train=train.csv ,

验证=valid.csv ,测试=test.csv ,

格式=csv ,

skip_header=True,CSV _ reader _ params={ delimiter : t },

fields=[(text ,text),( label,LABEL)])

建立词汇:

TEXT.build_vocab(火车)

以迭代器形式生成小批量数据:

train_iter,val_iter,test_iter=data。Iterator.splits((train,valid,test),

batch _ size=(args.batch_size,args.batch_size,args . batch _ size),

device=args.device

sort_key=lambda x:len(x.text),

sort_within_batch=False,

重复=假)

仅此而已!简单得可怕!虽然花了很长时间才理解这些功能。最后,这些xxx_iter将生成我们需要的维数为n w n * wnw的数据。

3. 模型

其实模型比较简单,只有一个嵌入图,一层cnn,一个激活函数,一个全连接。

但是你要注意不同大小的卷积核的写法。

您可以选择使用多个神经网络。Conv2d然后手动拼写。这里,nn。使用了ModuleList模块。实际上,它仍然使用多个Conv2d,然后将它们放在一起。

进口火炬

将torch.nn作为nn导入

导入torch.nn.functional as F

类textCNN(nn。模块):

def __init__(self,args):

超级(textCNN,self)。__init__()

self.args=args

Vocab=args.embed_num ##已知字数

Dim=args.embed_dim ##每个单词的向量长度

Cla=args.class_num ##类别数

Ci=1 ##输入的频道数

Knum=args.kernel_num ##每个卷积核的数量

Ks=args.kernel _ sizes # #卷积核列表,格式为[2,3,4]

自我。embed=nn。嵌入(词汇,dim) # #词向量,这里直接随机。

self . convs=nn . modulelist([nn . conv 2d(ci,knum,(k,dim)) for k in ks]) # #卷积层

self.dropout=nn。辍学(args.dropout)

self.fc=nn .线性(len(Ks)*Knum,Cla) ##全连接层

向前定义(自身,x):

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

x=x.unsqueeze(1) #(N,Ci,W,D)

x=[F.relu(conv(x)).为self.convs] # len(Ks)*(N,Knum,W)中的卷积神经挤压(3)

x=[F.max_pool1d(line,line.size(2)).为x] # len(Ks)*(N,Knum)中的线挤压(2)

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

x=自我放弃(十)

logit=self.fc(x)

返回分对数

4. 训练脚本

导入操作系统

导入系统

进口火炬

进口火炬。亲笔签名

导入火炬. nn .功能为F

定义训练(训练项,开发项,模型,参数):

if args.cuda:

模型。cuda(参数。设备)

优化器=火炬。optim。亚当(模特。parameters(),lr=args.lr)

步骤=0

best_acc=0

最后一步=0

model.train()

打印(培训.)

对于范围内的纪元(1,args.epochs 1):

对于火车_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=模型(特征)

loss=F.cross _熵(逻辑,目标)

loss.backward()

optimizer.step()

步骤=1

如果步骤% args.log_interval==0:

result=torch.max(logit,1)[1].视图(target.size())

更正=(结果。数据==目标。数据).总和()

准确度=校正值* 100.0/批次。批次大小

sys。stdout。写( r batch[{ }]-loss:{:6f } ACC:{:4f } $({ }/{ }) .格式(步骤,

loss.data.item(),

准确性,

纠正,

batch.batch_size))

如果步骤% args.dev_interval==0:

dev_acc=eval(dev_iter,model,args)

如果开发_acc最佳_acc:

最佳_acc=开发_acc

最后一步=步数

if args.save_best:

保存(model,args.save_dir,最佳,步骤)

否则:

如果步骤-最后一步=参数。早_停:

打印(提前{}步停止。format(args.early_stop))

否则如果步骤% args.save_interval==0:

保存(model,args.save_dir,"快照",步骤)

训练脚本中还有设置【计算机】优化程序以及失败的部分。其余部分比较琐碎的。

模型的保存:

定义保存(型号、保存目录、保存前缀、步骤):

如果不是os.path.isdir(save_dir):

os.makedirs(保存目录)

save_prefix=os.path.join(保存目录,保存前缀)

save_path={}_steps_{} .角.格式(保存前缀,步骤)

torch.save(model.state_dict(),save_path)

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

def eval(data_iter,model,args):

model.eval()

校正,avg_loss=0,0

对于数据_iter中的批处理:

feature,target=batch.text批处理。标签

feature.data.t_()

if args.cuda:

feature,target=feature.cuda(),target.cuda()

logit=模型(特征)

loss=F.cross _熵(逻辑,目标)

avg_loss=loss.data[0]

result=torch.max(logit,1)[1]

修正=(结果。查看(目标。size().data==target.data).总和()

size=len(data_iter.dataset)

avg_loss /=size

准确度=100.0 *校正/尺寸

打印( n评估-损失:{:6f} acc: {:4f}%({}/{}) n .格式(avg_loss,accuracy,corrects,size))

返回精度

5. main函数

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

在最终的测试集中,准确率达到百分之九十七(毕竟只是四类)。

但是有一个问题,就是随着精度的提高,损耗也在快速增加。

经过一番研究,我大致得出结论,这是没有问题的。比如本例中是四分类,加上全连接层输出的结果是[-10000,0,0,10000],而正确的分类是0。

那么这就是一个错误的结果。计算这单个样本的损失。先计算softmax,大约等于[E20000,E10000,E10000,1E {-20000},E {-10000},E {-10000},1E20000,E10000,E10000,E10000,1]。的真实标签是[1,0,0,0],所以交叉熵是20000。

于是我们发现,这个错题的损失会这么大。最后损失较大也是正常的。

然而,为什么当准确率接近100%时,损耗会迅速增加,这需要进一步研究。很可能是因为随着准确率的提高,结果越来越接近训练集的分布,这样与验证集或测试集分布差异极大的情况就会越来越多。

6.引用

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

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

pytorch实现textCNN的具体操作