文本分类TextRCNN模型
- RCNN简介
- TextRCNN模型介绍
- TextRCNN代码(文本10分类)
RCNN简介
从之前的文章中介绍过RNN
的优点是能够捕捉到序列的时序信息,这可能有利于捕获长文本的语义。但是RNN
对于文本序列后面的单词获取到的语义会更多,故RNN
是一个有偏倚的模型。而CNN
能够捕获到全局的信息,它可以很好地确定文本中带有最大池化层的识别性短语。因此,与递归或循环神经网络相比,CNN
可以更好地捕捉文本的语义。于是为了综合两者的优点,提出了TextRCNN
模型。主要用于解决文本分类问题。
首先,我们应用一个双向的循环结构(LSTM
,GRU
),与传统的基于窗口的神经网络相比,它可以大大减少噪声,从而最大程度地捕捉上下文信息。此外,该模型在学习文本表示时可以保留更大范围的词序。其次,我们使用了一个可以自动判断哪些特性在文本分类中扮演关键角色的池化层(max-pooling
),以捕获文本中的关键组件。我们的模型结合了RNN的结构和最大池化层,利用了循环神经模型和卷积神经模型的优点。此外,我们的模型显示了O(n)的时间复杂度,它与文本长度的长度是线性相关的。
TextRCNN模型介绍
如下图是作者提出的模型框架,输入是一个文本sentence
,它可以看成是由一系列单词组成的(w1
,w2
…)。输出是一个概率分布,最大的那个位置对应文章属于的类别K 。
RCNN整体的模型构建流程如下:
- 利用
Bi-LSTM
获得上下文的信息,类似于语言模型。 - 将
Bi-LSTM
获得的隐层输出和词向量拼接[fwOutput
,wordEmbedding
,bwOutput
]。 - 将拼接后的向量非线性映射到低维。
- 向量中的每一个位置的值都取所有时序上的最大值,得到最终的特征向量,该过程类似于max-pool。
softmax
分类。
RCNN整体的运算过程为:
- 首先将词进行词向量编码,即第一栏中间的
word-embedding
层 - 接着将词向量输入到双向的
RNN
(这里的RNN cell
可以使用lstm
或者gru
或者最简单的)双向的RNN
能够更好的捕捉上下文关系 - 将1和2得到的结果拼接在一起也就是模型构建流程中的2,然后输入到激活函数中(
tanh
或者sigmod
)得到图中所示的 y ( 2 ) y^{(2)} y(2) - 然后对于相同位置的值做最大池化,得到图中的 y ( 3 ) y^{(3)} y(3)
softmax
分类或者fc
映射,得到图中最终结果。
TextRCNN代码(文本10分类)
# coding: UTF-8
import torch
import torch.nn as nn
import torch.nn.functional as F
class Config(object):
"""配置参数"""
def __init__(self):
self.model_name = 'TextRCNN'
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 设备
self.dropout = 1.0 # 随机失活
self.num_classes = 10 # 类别数
self.n_vocab = 10000 # 词表大小,在运行时赋值
self.num_epochs = 10 # epoch数
self.batch_size = 128 # mini-batch大小
self.pad_size = 32 # 每句话处理成的长度(短填长切)
self.learning_rate = 1e-3 # 学习率
self.embed = 300 # 字向量维度, 若使用了预训练词向量,则维度统一
self.hidden_size = 256 # lstm隐藏层
self.num_layers = 1 # lstm层数
'''Recurrent Convolutional Neural Networks for Text Classification'''
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__()
self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
self.lstm = nn.LSTM(config.embed, config.hidden_size, config.num_layers,
bidirectional=True, batch_first=True, dropout=config.dropout)
self.maxpool = nn.MaxPool1d(config.pad_size)
self.fc = nn.Linear(config.hidden_size * 2 + config.embed, config.num_classes)
def forward(self, x):
print(x)
x, _ = x # torch.Size([128, 32])
embed = self.embedding(x) # [batch_size, seq_len, embeding]=[128,32,300]
out, _ = self.lstm(embed) # [batch_size, seq_len, hiden_size*2]=[128,32,512]
out = torch.cat((embed, out), 2) # [batch_size, seq_len, hiden_size*2]=[128,32,512+300]
out = F.relu(out) # ReLU 激活函数将所有负数变换为零,而保持正数不变 [batch_size, seq_len, hiden_size*2]=[128,32,512+300]
out = out.permute(0, 2, 1) # 将张量 out 的第二个和第三个维度进行交换。[128,812,32]
out = self.maxpool(out).squeeze() # config.pad_size=32,则maxpool之后,维度为[128,812,1],经过压缩之后变为[128,812]
out = self.fc(out) # [128,812]====>[128,10]
return out
config=Config()
model=Model(config)
print(model)
输出:
Model(
(embedding): Embedding(10000, 300, padding_idx=9999)
(lstm): LSTM(300, 256, batch_first=True, dropout=1.0, bidirectional=True)
(maxpool): MaxPool1d(kernel_size=32, stride=32, padding=0, dilation=1, ceil_mode=False)
(fc): Linear(in_features=812, out_features=10, bias=True)
)