TextCNN结构

以下为原论文中的模型结构图:

  1. embedding
  2. 卷积层
  3. MaxPooling
  4. Flatten
  5. 全连接层

卷积操作

  • 在卷积神经网络中仅涉及离散卷积的情形。
  • 卷积运算的作用就类似与滤波,因此也称卷积核为filter滤波器。
  • 卷积神经网络的核心思想是捕捉局部特征(n-gram)。CNN的优势在于能够自动地对g-gram特征进行组合和筛选,获得不同抽象层次的语义信息。
  • 下图为用于文本分类任务的TextCNN结构描述(这里详细解释了TextCNN架构以及词向量矩阵是如何做卷积的)
  1. 输入层:n∗kn*knk的矩阵,n为句子中的单词数,k为embedding_size。(为了使向量长度一致,对原句进行了padding操作)
  2. 卷积层:在NLP中输入层是一个由词向量拼成的词矩阵,且卷积核的宽和该词矩阵的宽相同,该宽度即为词向量大小,且卷积核只会在高度方向移动。输入层的矩阵与我们的filter进行convolution,然后经过激活函数得到feature map。filter这里有三种大小(3,4,5)。
  3. 池化层:max-pooling
  4. softmax输出结果。

简单代码实现

import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import torch.nn.functional as Fdtype = torch.FloatTensor
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 3 words sentences (=sequence_length is 3)
sentences = ["i love you", "he loves me", "she likes baseball", "i hate you", "sorry for that", "this is awful"]
labels = [1, 1, 1, 0, 0, 0]  # 1 is good, 0 is not good.embedding_size = 2
sequence_length = len(sentences[0])
num_classes = len(set(labels))
batch_size = 3word_list = " ".join(sentences).split()
vocab = list(set(word_list))
word2idx = {w:i for i,w in enumerate(vocab)}
vocab_size = len(vocab)
def make_data(sentences, labels):inputs = []for sen in sentences:inputs.append([word2idx[n] for n in sen.split()])targets = []for out in labels:targets.append(out)return inputs, targets
input_batch, target_batch = make_data(sentences, labels)
input_batch, target_batch = torch.LongTensor(input_batch), torch.LongTensor(target_batch)dataset = Data.TensorDataset(input_batch,target_batch)
loader = Data.DataLoader(dataset, batch_size, True)
class TextCNN(nn.Module):def __init__(self):super(TextCNN, self).__init__()self.W = nn.Embedding(vocab_size, embedding_size)output_channel = 3self.conv = nn.Sequential(nn.Conv2d(1, output_channel, (2,embedding_size)), # inpu_channel, output_channel, 卷积核高和宽 n-gram 和 embedding_sizenn.ReLU(),nn.MaxPool2d((2,1)))self.fc = nn.Linear(output_channel,num_classes)def forward(self, X):'''X: [batch_size, sequence_length]'''batch_size = X.shape[0]embedding_X = self.W(X) # [batch_size, sequence_length, embedding_size]embedding_X = embedding_X.unsqueeze(1) # add channel(=1) [batch, channel(=1), sequence_length, embedding_size]conved = self.conv(embedding_X) # [batch_size, output_channel,1,1]flatten = conved.view(batch_size, -1)# [batch_size, output_channel*1*1]output = self.fc(flatten)return output

维度变换

  1. 输入X:[batch_size, sequence_length]
  2. embedding:相当于把单词增加了一个维度。[batch_size, sequence_length, embedding_size];然后我们对它做了一个unsqueeze(1)操作,原因是卷积操作的需要。[batch_size, channel(=1), sequence_length, embedding_size]
  3. conved:我们这了进行了一个二维卷积,input_channel为1,output_channel为3,filter_size为(2,embedding_size),相当于bi-gram。
    [batch_size, output_channel,sequence_len-1, 1]

卷积输出的高和宽的计算公式:
heightout=heightin−heightkernel+2×paddingstride+1height_{out} = \frac{height_{in}-height_{kernel}+2\times padding}{stride}+1heightout=strideheightinheightkernel+2×padding+1widthout=widthin−widthkernel+2×paddingstride+1width{out} = \frac{width_{in}-width_{kernel}+2\times padding}{stride}+1widthout=stridewidthinwidthkernel+2×padding+1

  1. Max-pooling:[batch_size, output_channel,1,1]
  2. Flatten: [batch_size,ouput_channel]
model = TextCNN().to(device)
criterion = nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)# Training
for epoch in range(5000):for batch_x, batch_y in loader:batch_x, batch_y = batch_x.to(device), batch_y.to(device)pred = model(batch_x)loss = criterion(pred, batch_y)if (epoch + 1) % 1000 == 0:print('Epoch:', '%04d' % (epoch + 1), 'loss =', '{:.6f}'.format(loss))optimizer.zero_grad()loss.backward()optimizer.step()
# Test
test_text = 'i hate me'
tests = [[word2idx[n] for n in test_text.split()]]
test_batch = torch.LongTensor(tests).to(device)
# Predict
model = model.eval()
predict = model(test_batch).data.max(1, keepdim=True)[1]
if predict[0][0] == 0:print(test_text,"is Bad Mean...")
else:print(test_text,"is Good Mean!!")

用TextCNN进行IMDB电影评论情感分析

1. 数据预处理

  • 设置种子SEED,保证结果可复现
  • 利用torchtext构建数据集
import torch
from torchtext.legacy import data
from torchtext.legacy import datasets
import random
import numpy as npSEED = 1234random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = TrueTEXT = data.Field(tokenize = 'spacy', tokenizer_language = 'en_core_web_sm',batch_first = True)
LABEL = data.LabelField(dtype = torch.float)train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)train_data, valid_data = train_data.split(random_state = random.seed(SEED))
  • 构建vocab,加载预训练词嵌入:
MAX_VOCAB_SIZE = 25_000TEXT.build_vocab(train_data, max_size = MAX_VOCAB_SIZE, vectors = "glove.6B.100d", unk_init = torch.Tensor.normal_)LABEL.build_vocab(train_data)
  • 创建迭代器:
BATCH_SIZE = 64device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits((train_data, valid_data, test_data), batch_size = BATCH_SIZE, device = device)

2. 构建模型

  • nn.Embedding中padding_idx:当Embedding是随机初始化的矩阵时,会对padding_idx所在的行进行填0。保证了padding行为的正确性。

维度变换

  1. 输入:text [batct_size, senquence_len]
  2. embedding和unsqueeze(1): [batch_size,1,sequence_len,embedding_dim]
  3. conved:[batch_size, output_channel,senquence_len-filter_size[n]+1,1]
  4. pooled:[batch_size,output,channel,1,1]
  5. Flatten: 这里的flatten在第三步和第四步进行了squeeze操作[batch_size,output_channel]
  6. concat: [batch_size, output_channel*len(filter_sizes]
class CNN(nn.Module):def __init__(self, vocab_size, embedding_dim, n_filters, filter_sizes, output_dim, dropout, pad_idx):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx = pad_idx)self.convs = nn.ModuleList([nn.Conv2d(in_channels = 1, out_channels = n_filters, kernel_size = (fs, embedding_dim)) for fs in filter_sizes])self.fc = nn.Linear(len(filter_sizes) * n_filters, output_dim)self.dropout = nn.Dropout(dropout)def forward(self, text):#text = [batch size, sent len]embedded = self.embedding(text)#embedded = [batch size, sent len, emb dim]embedded = embedded.unsqueeze(1)#embedded = [batch size, 1, sent len, emb dim]conved = [F.relu(conv(embedded)).squeeze(3) for conv in self.convs]#conved_n = [batch size, n_filters, sent len - filter_sizes[n] + 1]pooled = [F.max_pool1d(conv, conv.shape[2]).squeeze(2) for conv in conved]#pooled_n = [batch size, n_filters]cat = self.dropout(torch.cat(pooled, dim = 1))#cat = [batch size, n_filters * len(filter_sizes)]return self.fc(cat)
  • 设置超参数,实例化模型
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
N_FILTERS = 100
FILTER_SIZES = [3,4,5]
OUTPUT_DIM = 1
DROPOUT = 0.5
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]model = CNN(INPUT_DIM, EMBEDDING_DIM, N_FILTERS, FILTER_SIZES, OUTPUT_DIM, DROPOUT, PAD_IDX)
  • 查看参数
def count_parameters(model):return sum(p.numel() for p in model.parameters() if p.requires_grad)print(f'The model has{count_parameters(model):,}trainable parameters')
  • 加载预训练词嵌入
pretrained_embeddings = TEXT.vocab.vectorsmodel.embedding.weight.data.copy_(pretrained_embeddings)
  • 将unk 和 pad 初始权重归零。
UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM)

3. 训练模型

  • 初始化优化器、损失函数
import torch.optim as optimoptimizer = optim.Adam(model.parameters())criterion = nn.BCEWithLogitsLoss()model = model.to(device)
criterion = criterion.to(device)
  • 计算精度的函数
def binary_accuracy(preds, y):"""Returns accuracy per batch, i.e. if you get 8/10 right, this returns 0.8, NOT 8"""#round predictions to the closest integerrounded_preds = torch.round(torch.sigmoid(preds))correct = (rounded_preds == y).float() #convert into float for divisionacc = correct.sum() / len(correct)return acc
  • train
def train(model, iterator, optimizer, criterion):epoch_loss = 0epoch_acc = 0model.train()for batch in iterator:optimizer.zero_grad()predictions = model(batch.text).squeeze(1)loss = criterion(predictions, batch.label)acc = binary_accuracy(predictions, batch.label)loss.backward()optimizer.step()epoch_loss += loss.item()epoch_acc += acc.item()return epoch_loss / len(iterator), epoch_acc / len(iterator)
  • eval(注意:同样,由于使用的是dropout,我们必须记住使用model.eval()来确保在评估时能够关闭 dropout。)
def evaluate(model, iterator, criterion):epoch_loss = 0epoch_acc = 0model.eval()with torch.no_grad():for batch in iterator:predictions = model(batch.text).squeeze(1)loss = criterion(predictions, batch.label)acc = binary_accuracy(predictions, batch.label)epoch_loss += loss.item()epoch_acc += acc.item()return epoch_loss / len(iterator), epoch_acc / len(iterator)
import timedef epoch_time(start_time, end_time):elapsed_time = end_time - start_timeelapsed_mins = int(elapsed_time / 60)elapsed_secs = int(elapsed_time - (elapsed_mins * 60))return elapsed_mins, elapsed_secs
  • training
N_EPOCHS = 5best_valid_loss = float('inf')for epoch in range(N_EPOCHS):start_time = time.time()train_loss, train_acc = train(model, train_iterator, optimizer, criterion)valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)end_time = time.time()epoch_mins, epoch_secs = epoch_time(start_time, end_time)if valid_loss < best_valid_loss:best_valid_loss = valid_losstorch.save(model.state_dict(), 'tut4-model.pt')print(f'Epoch:{epoch+1:02}| Epoch Time:{epoch_mins}m{epoch_secs}s')print(f'\tTrain Loss:{train_loss:.3f}| Train Acc:{train_acc*100:.2f}%')print(f'\t Val. Loss:{valid_loss:.3f}|  Val. Acc:{valid_acc*100:.2f}%')
  • test
model.load_state_dict(torch.load('tut4-model.pt'))test_loss, test_acc = evaluate(model, test_iterator, criterion)print(f'Test Loss:{test_loss:.3f}| Test Acc:{test_acc*100:.2f}%')

4. 模型验证

import spacy
nlp = spacy.load('en_core_web_sm')def predict_sentiment(model, sentence, min_len = 5):model.eval()tokenized = [tok.text for tok in nlp.tokenizer(sentence)]if len(tokenized) < min_len:tokenized += ['<pad>'] * (min_len - len(tokenized))indexed = [TEXT.vocab.stoi[t] for t in tokenized]tensor = torch.LongTensor(indexed).to(device)tensor = tensor.unsqueeze(0)prediction = torch.sigmoid(model(tensor))return prediction.item()
  • 输入需要评测的句子
predict_sentiment(model, "This film is terrible")
predict_sentiment(model, "This film is great")

优化

  • 在TextCNN的实践中,有很多地方可以优化:

    Filter尺寸:这个参数决定了抽取n-gram特征的长度,这个参数主要跟数据有关,平均长度在50以内的话,用10以下就可以了,否则可以长一些。在调参时可以先用一个尺寸grid search,找到一个最优尺寸,然后尝试最优尺寸和附近尺寸的组合

    Filter个数:这个参数会影响最终特征的维度,维度太大的话训练速度就会变慢。这里在100-600之间调参即可
    CNN的激活函数:可以尝试Identity、ReLU、tanh

    正则化:指对CNN参数的正则化,可以使用dropout或L2,但能起的作用很小,可以试下小的dropout率(<0.5),L2限制大一点

    Pooling方法:根据情况选择mean、max、k-max pooling,大部分时候max表现就很好,因为分类任务对细粒度语义的要求不高,只抓住最大特征就好了

    Embedding表:中文可以选择char或word级别的输入,也可以两种都用,会提升些效果。如果训练数据充足(10w+),也可以从头训练
    蒸馏BERT的logits,利用领域内无监督数据

    加深全连接:原论文只使用了一层全连接,而加到3、4层左右效果会更好

TextCNN是很适合中短文本场景的强baseline,但不太适合长文本,因为卷积核尺寸通常不会设很大,无法捕获长距离特征。同时max-pooling也存在局限,会丢掉一些有用特征。另外再仔细想的话,TextCNN和传统的n-gram词袋模型本质是一样的,它的好效果很大部分来自于词向量的引入,解决了词袋模型的稀疏性问题。

参考文献
TextCNN 的 PyTorch 实现
深入TextCNN(一)详述CNN及TextCNN原理
深度学习文本分类模型综述+代码+技巧

TextCNN pytorch实现相关推荐

  1. Pytorch+Text-CNN+Word2vec+电影评论情感分析实战

    文章目录 0.前言 1.电影评论数据集 2.数据读取 3.数据预处理 4.准备训练和测试集 5.加载词向量模型Word2vec 6.定义网络 7.训练网络 8.测试网络和可视化 9.总结 0.前言 很 ...

  2. 【Pytorch神经网络实战案例】40 TextCNN模型分析IMDB数据集评论的积极与消极

    卷积神经网络不仅在图像视觉领域有很好的效果,而且在基于文本的NLP领域也有很好的效果.TextCN如模型是卷积神经网络用于文本处理方面的一个模型. 在TextCNN模型中,通过多分支卷积技术实现对文本 ...

  3. Pytorch TextCNN实现中文文本分类(附完整训练代码)

    Pytorch TextCNN实现中文文本分类(附完整训练代码) 目录 Pytorch TextCNN实现中文文本分类(附完整训练代码) 一.项目介绍 二.中文文本数据集 (1)THUCNews文本数 ...

  4. 『NLP学习笔记』TextCNN文本分类原理及Pytorch实现

    TextCNN文本分类原理及Pytorch实现 文章目录 一. TextCNN网络结构 1.1. CNN在文本分类上得应用 1.2. 回顾CNN以及Pytorch解析 1.2.1. CNN特点 1.2 ...

  5. nlp系列(3)文本分类(Bert+TextCNN)pytorch

    在前面两章讲解了 bert 和 TextCNN 模型,用这两个模型来进行文本分类.那我们就可以试一下将这两个模型进行融合来进行文本分类. 模型介绍 我们知道在进行模型融合时,要注意的时在第一个模型的输 ...

  6. 从零开始构建基于textcnn的文本分类模型(上),word2vec向量训练,预训练词向量模型加载,pytorch Dataset、collete_fn、Dataloader转换数据集并行加载

    伴随着bert.transformer模型的提出,文本预训练模型应用于各项NLP任务.文本分类任务是最基础的NLP任务,本文回顾最先采用CNN用于文本分类之一的textcnn模型,意在巩固分词.词向量 ...

  7. pytorch搭建TextCNN与使用案例

    TextCNN算法流程 整体流程是将词拼接在一起,一句话构成一个特征图 根据卷积核得到多个特征向量 每个特征向量全局池化,选最大的特征作为这个特征向量的值 拼接特征值,得到句子的特征向量 全连接后得到 ...

  8. 深度学习笔记(3)——pytorch+TextCNN实现情感分类(外卖数据集)

    文章目录 0 前言 1 数据准备 1.1 常量 1.2 加载数据集 2 数据预处理 3 文本表示 4 TextCNN模型 5 模型训练 6 模型评估 7 总览 8 完整代码 0 前言 使用数据集:某外 ...

  9. 基于PyTorch+TextCNN实现英文长文本诗歌文本分类

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  10. 【代码实战】基于pytorch实现中文文本分类任务

    点击上方,选择星标或置顶,不定期资源大放送! 阅读大概需要15分钟 Follow小博主,每天更新前沿干货 来自 | 知乎 地址 | https://zhuanlan.zhihu.com/p/73176 ...

最新文章

  1. 智源青年科学家黄高:面向高效推理的深度网络结构设计
  2. matlab求图形的聚类系数,求助,为什么画不出来聚类系数的图?一直为0啊
  3. python 使用__slots__来限制类的实例属性的数量
  4. 03_数据的特征抽取,sklearn特征抽取API,字典特征抽取DictVectorizer,文本特征抽取CountVectorizer,TF-IDF(TfidfVectorizer),详细案例
  5. 【好程序员笔记分享】C语言之break和continue
  6. oracle 虚拟补丁,趋势科技虚拟补丁(Virtual Patch)
  7. Flutter for Web 详细预研
  8. [蓝桥杯][2019年第十届真题c/c++B组]完全二叉树的权值
  9. C++ 运算符重载的原理
  10. Quartz时间配置(周期任务类)
  11. php web长时间不操作退出,Ecshop管理员登陆后台后短时间不操作自动退出的解决方法...
  12. 视频教程-基于VUE和Hplus通用后台管理系统(前端篇)-Vue
  13. matlab复杂网络上的博弈演化,复杂网络上的演化博弈.pdf
  14. SRCNN中的PSNR计算问题
  15. Lync 2010升级到Lync 2013 之Lync 2010 planning tool 的使用!
  16. xpath获取同级元素 子元素,子元素取父元素等
  17. 网络应用程序体系结构
  18. ARP欺骗——断网攻击
  19. 【个人记录 | 研二预答辩】
  20. JS判断页面是否被iframe嵌套

热门文章

  1. 关于foobar2000中Convolver,大家觉得哪个Impulse效果最好?
  2. 用AS3+Flash+FlashBuilder开发AIR
  3. 怎么获取股票大数据接口的方法介绍
  4. 鸿蒙系统操作界面跟苹果很像,华为鸿蒙系统的操作界面可能长这样 和安卓全完不同而且图标也太酷了...
  5. 她每天吃一个煮熟的苹果,从此打开了通往新世界的大门~
  6. ios签名php在线监控,IOS无需签名无需越狱H5网页在线封装APP教程
  7. C#调用WPS2016方法和常见问题处理
  8. laravel框架解决sql注入问题
  9. 最好用的jQuery日期插件合集,前端设计必备素材
  10. QPushButton设置背景图片变换(素材四连图)