赞
踩
目前,大多数的深度学习都是从CNN神经网络模型开始的,但CNN更多地被应用于图像领域之中,因为一张图片可以很容易地被抽象成一个矩阵的形式,矩阵的每一个元素可以看做是图片的一个像素点,定义一个或多个CNN的卷积核,可以很容易的进行卷积操作,进而通过池化操作,就可以抽取图片中的特征。而文本数据相较于图片就难处理得多,首先,我们无法去规范一个词的大小,因为它可能是一个字,也可能是很多字的组合,在上一层,我们也无法确定一个句子中有多少个词,由于这种不确定,我们需要进行一些额外的设计。一般来说,当我们面对的是文本数据的时候,我们对于这种数据的处理方式一般是以下两种:
1.填充/缩减 处理:这种处理的方式最为的简单,其基本的思想是硬性的将数据规范成统一的形式,然后再进行接下来的任务。
2.序列化的处理方式:文本数据中,每个词是有前后的衔接性的,当前词的出现是会受到前面的词的影响的。基于这种序列化的方式,我们可以对文本进行处理,然后利用LSTM,RNN,GRU的序列模型来完成后面的任务。
整个textCNN模型的基本组成包括初始的Embedding层,卷积层,池化层和输出层。
textCNN模型结构如下图所示:
采用预训练的方式,为每一个词生成一个稠密的向量表示,生成的算法有我word2vector,GLOVE,Bert等算法,一般思想都是基于语言模型来为每一个词生成一个词向量。预训练的模型考虑了模型的语义信息,可以为后面的层提供更多的特征信息。
再得到每一个词的向量之后,我们就可以来组成一个表示一个句子的或者文本的矩阵,矩阵中的一个行就是一个词的词向量。由于句子和文本中的词的数量是不相同的,所以矩阵的行数也是不相同的,一般可以采用截断或者填充的方式进行进一步的处理成规范的数据。最终的用公式表示拼接的过程为:
X = {X1,X2,X3…Xn}
文本或者句子中含有n个词汇,每一个词汇的向量表示为Xi,最终形成的矩阵为X。
获得了句子或者文本对应的矩阵之后,需要对该矩阵进行卷积操作,一般来说,图片可以抽象成一个二维的矩阵,所以我们将卷积核定义为一个二维的,但是,对于文本数据来说,为了保证涉及到一个词完整的信息,所以我们选用一维的卷积方式。
在原论文中,定义了三个区域,区域长度分别为4,3,2,为每个区域定义了两个卷积核,经过卷积滑动计算后,生成了文本特征矩阵。三个区域,每个区域生成两个文本特征矩阵。
获得了卷积结果之后,下一步就是进行池化操作,池化操作的目的就是为了放大视野,提取泛化特征,提高模型的泛化能力。原论文采用的是一维的最大池化操作,对卷积结果中的行进行最大池化操作。每一个提取出一个池化特征。然后将各个池化结果进行拼接,形成池化结果。
通过池化得到句子或者文本的特征向量之后,进行softmax的操作,我们就可以对文本进行分类操作。
总的来说,textCNN和经典的CNN很多相同之处,机制简单,便于理解。同时通过不同区域的划分,再进行卷积操作,一定程度上考虑了语义信息。
这个模型思路很简单,就是通过RNN+池化操作完成文本的分类工作。
RCNN模型的结构如图:
在这个模型中,采用的是Bi-LSTM来模拟语言模型,因为单纯的语言模型N-Gram是通过前面的词来预测当前词,其局限性就是忽略了后面的词的信息,为了结合当前词的上下文信息,选择了双向的LSTM来获得上下文的信息。对于LSTM每一个时刻的输出就是当前词汇+上下文信息。然后将所有词汇输出。下一步就是一个池化的过程。
在获得了Bi-LSTM的每一个词汇的输出之后,我们下一步就是对输出向量进行一个max-pooling的操作,对于每一个单词,抽取出其最大特征,然后将每一个词汇的最大特征进行拼接,从而形成一个表示文本的最大特征的特征向量。最后进入softmax的输出层,进行分类。
ACL2017中,腾讯AI-LAB提出了Deep Pyramid Convolutional Neural Networks for Text Categorization(DPCNN)。DPCNN通过不断加深网络,可以抽取长距离的文本依赖关系。实验证明在不增加太多计算成本的情况下,增加网络深度就可以获得最佳的准确率。
DPCNN结构如下图所示:
由结构图可以看出,DPCNN和TextCNN在底层有很多的相似性,这里将包含多尺寸卷积滤波器的卷积层的卷积结果称为Region Embedding,意思就是对一个文本区域/片段进行一组卷积操作后生成的Embedding。
对于一个3gram进行卷积操作时可以有两种选择,一种是保留词序,也就是设置一组size=3*D的二维卷积核对3gram进行卷积,其中D为word Embedding的维度;还有一种是不保留词序(即使用词袋模型),首先对3gram中的3个词的embedding取均值得到一个size=D的向量,然后设置一组size=D的一维卷积对该3gram进行卷积。显然TextCNN中采用的是保留词序的做法,而DPCNN采用的词袋模型的做法,DPCNN作者认为前者的做法更容易造成过拟合问题。
等长卷积:假设输入的序列长度为n,卷积核大小为m,步长(stride)为s,输入序列两端各补充p个零(Zero padding),那么卷积层的输出序列为(n=m+2p)/s+1。步长s=1,两端补零p=(m-1)/2,卷积后输出长度为n,与原来一致。
作者选择了适当的两层等长卷积来提高词位Embedding表示的丰富性,在每一个卷积块(两层的等长卷积)后,使用一个 size=3 和 stride=2 进行 maxpooling 进行池化。序列的长度就被压缩成了原来的一半。其能够感知到的文本片段就比之前长了一倍。
固定了 feature map 的数量,每当使用一个 size=3 和 stride=2 进行 maxpooling 进行池化时,每个卷积层的计算时间减半(数据大小减半),从而形成一个金字塔。
HAN模型包括Word Encoder,Word Attention,Sentence Encoder,Sentence Attention四个过程。
采用双向的结构,可以同时获取到上文的特征信息和下文的特征信息。基于这个思想,所以在Word Encoder层中,采用的是双向的GRU结构。
在HAN模型中,采用的是Soft-Attention的计算机制,也就是说,我们初始给出一个查询向量Uw(作为一个参数,在反向传播中进行优化。),然后利用这个查询向量分别和Word Encoder层的输出计算相关的权重系数(内积+softmax),在利用权重系数对于输入的词汇的表示向量进行加权求和来形成最后的Attention值。
过程如图所示:
过程与Word Encoder相似,按照句子出现的顺序,将句子的表征向量Si作为输入,获取每一个句子在综合上下文的输出结果
通过随机的初始变量Us和句子级的GRU输出来计算Attention值,加权求和之后可以获得关于文本的Attention向量,在利用softmax来对文本进行分类操作。
采用了清华NLP组提供的THUCNews新闻文本分类数据集的子集
数据下载链接:
THUCNews数据子集
stopwords_path = 'D:\\nlp\\textClassifier-master\\textClassifier-master\\data\\词表.txt' stopwords = open(stopwords_path).read().split('\n') import jieba def cut(sentence): return [token for token in jieba.lcut(sentence) if token not in stopwords] import torchtext import torch TEXT = torchtext.data.Field(sequential=True,lower=True,tokenize=cut) LABEL = torchtext.data.LabelField(sequential=False, dtype=torch.int64) train_dataset,dev_dataset,test_dataset = torchtext.data.TabularDataset.splits( path='D:\\nlp\\textClassifier-master\\textClassifier-master\\data\\cnews', format='tsv', skip_header=False, train='train.tsv', validation='dev.tsv', test='test.tsv', fields=[('label',LABEL),('content',TEXT)] pretrained_name = 'sgns.sogou.word' pretrained_path = './drive/My Drive/TextCNN/word_embedding' vectors = torchtext.vocab.Vectors(name=pretrained_name, cache=pretrained_path) TEXT.build_vocab(train_dataset, dev_dataset,test_dataset, vectors=vectors) LABEL.build_vocab(train_dataset, dev_dataset,test_dataset) train_iter, dev_iter,test_iter = torchtext.data.BucketIterator.splits( (train_dataset, dev_dataset,test_dataset), #需要生成迭代器的数据集 batch_sizes=(128, 128,128), # 每个迭代器分别以多少样本为一个batch sort_key=lambda x: len(x.content) ) )
import torch import torch.nn as nn import torch.nn.functional as F class TextCNN(nn.Module): def __init__(self, class_num, # 最后输出的种类数 filter_sizes, # 卷积核的长也就是滑动窗口的长 filter_num, # 卷积核的数量 vocabulary_size, # 词表的大小 embedding_dimension, # 词向量的维度 vectors, # 词向量 dropout): # dropout率 super(TextCNN, self).__init__() # 继承nn.Module chanel_num = 1 self.embedding = nn.Embedding(vocabulary_size, embedding_dimension) self.embedding = self.embedding.from_pretrained(vectors) self.convs = nn.ModuleList([nn.Conv2d(chanel_num, filter_num, (fsz, embedding_dimension)) for fsz in filter_sizes]) self.dropout = nn.Dropout(dropout) # dropout self.fc = nn.Linear(len(filter_sizes) * filter_num, class_num) def forward(self, x): x = self.embedding(x) x = x.permute(1,0,2) x = x.unsqueeze(1) x = [conv(x) for conv in self.convs] x = [sub_x.squeeze(3) for sub_x in x] x = [F.relu(sub_x) for sub_x in x] x = [F.max_pool1d(sub_x,sub_x.size(2)) for sub_x in x] x = [sub_x.squeeze(2) x = torch.cat(x, 1) x = self.dropout(x) logits = self.fc(x) return logits
class_num = len(LABEL.vocab) # 类别数目 filter_size = [3,4,5] # 卷积核种类数 filter_num=16 # 卷积核数量 vocab_size = len(TEXT.vocab) # 词表大小 embedding_dim = TEXT.vocab.vectors.size()[-1] # 词向量维度 vectors = TEXT.vocab.vectors # 词向量 dropout=0.5 learning_rate = 0.001 # 学习率 epochs = 5 # 迭代次数 save_dir = './drive/My Drive/TextCNN/model' # 模型保存路径 steps_show = 10 # 每10步查看一次训练集loss和mini batch里的准确率 steps_eval = 100 # 每100步测试一下验证集的准确率 early_stopping = 1000 # 若发现当前验证集的准确率在1000步训练之后不再提高 一直小于best_acc,则提前停止训练 textcnn_model = TextCNN(class_num=class_num, filter_sizes=filter_size, filter_num=filter_num, vocabulary_size=vocab_size, embedding_dimension=embedding_dim, vectors=vectors, dropout=dropout) def train(train_iter, dev_iter, model): if torch.cuda.is_available(): # 判断是否有GPU,如果有把模型放在GPU上训练,速度质的飞跃 model.cuda() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 梯度下降优化器,采用Adam steps = 0 best_acc = 0 last_step = 0 model.train() for epoch in range(1, epochs + 1): for batch in train_iter: feature, target = batch.content, batch.label if torch.cuda.is_available(): # 如果有GPU将特征更新放在GPU上 feature,target = feature.cuda(),target.cuda() optimizer.zero_grad() # 将梯度初始化为0,每个batch都是独立训练地,因为每训练一个batch都需要将梯度归零 logits = model(feature) loss = F.cross_entropy(logits, target) # 计算损失函数 采用交叉熵损失函数 loss.backward() # 反向传播 optimizer.step() # 放在loss.backward()后进行参数的更新 steps += 1 if steps % steps_show == 0: # 每训练多少步计算一次准确率,我这边是1,可以自己修改 corrects = (torch.max(logits, 1)[1].view(target.size()).data == target.data).sum() # logits是[128,10],torch.max(logits, 1)也就是选出第一维中概率最大的值,输出为[128,1],torch.max(logits, 1)[1]相当于把每一个样本的预测输出取出来,然后通过view(target.size())平铺成和target一样的size (128,),然后把与target中相同的求和,统计预测正确的数量 train_acc = 100.0 * corrects / batch.batch_size # 计算每个mini batch中的准确率 print('steps:{} - loss: {:.6f} acc:{:.4f}'.format( steps, loss.item(), train_acc)) if steps % steps_eval == 0: # 每训练100步进行一次验证 dev_acc = dev_eval(dev_iter,model) if dev_acc > best_acc: best_acc = dev_acc last_step = steps print('Saving best model, acc: {:.4f}%\n'.format(best_acc)) save(model,save_dir, steps) else: if steps - last_step >= early_stopping: print('\n提前停止于 {} steps, acc: {:.4f}%'.format(last_step, best_acc)) raise KeyboardInterrupt def dev_eval(dev_iter,model): model.eval() corrects, avg_loss = 0, 0 for batch in dev_iter: feature, target = batch.content, batch.label if torch.cuda.is_available(): feature, target = feature.cuda(), target.cuda() logits = model(feature) loss = F.cross_entropy(logits, target) avg_loss += loss.item() corrects += (torch.max(logits, 1) [1].view(target.size()).data == target.data).sum() size = len(dev_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
Evaluation - loss:0.001031 acc:96.5500%(9655/10000)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。