文本分类模型(二)——DPCNN

文本分类模型(二)——DPCNN

一、概述

  DPCNN(Deep Pyramid Convolutional Neural Networksfor Text Categorization),是RieJohnson和腾讯AI-Lab等提出的一种深度卷积神经网络,可以称之为"深度金字塔卷积神经网络"。相关论文链接Deep Pyramid Convolutional Neural Networks for Text Categorization

  文献中提出了一种基于word-level级别的网络——DPCNN。由于TextCNN不能通过卷积获得文本的长距离依赖关系,而论文中DPCNN通过不断加深网络,可以抽取长距离的文本依赖关系。实验证明在不增加太多计算成本的情况下,增加网络深度就可以获得最佳的准确率。‍

  DPCNN的深度与padding后的最大句子长度有关系,例如 m a x _ l e n = 64 = 2 5 max\_len=64=2^5 max_len=64=25,即长度为64的句子只够5次pooling/2,因此DPCNN更加适合长文本。

二、背景

  Deep Pyramid Convolutional设计的目的,一是提高准确率;另一个则是在提高准确率的前提下,尽量的提升计算效率。

  文本分类是一个十分重要的任务,其应用场景包括垃圾邮件检测,情感和主题分类等。研究显示:深度为32层字符级CNN显示出优于深度为9层字符级CNN;非常浅的1层单词级CNN被证明比非常深的字符级CNN更加准确且快得多。结合这两点,对深度的单词级CNN进行探究,文献中提出了一种深度但低复杂度的网络架构,名为DPCNN(深度金字塔卷积神经网络)。

  DPCNN主要的特色是引入了残差结构,增加了多尺度信息,并且增加了用于文本分类CNN的网络深度,以提取文本中远程关系特征,并且并没有带来较高的复杂度。

三、DPCNN特点

  • 1.减采样的设计。每层设计步长为2的卷积层,保持filter数目不变(默认250),像一个金字塔的形状(pyramid)。研究发现下采样时增加feature map的数量除了白白增加了工作量以外,对提升准确率没有很大的帮助。DPCNN中将每层的计算量减半,保持filter(feature mapping)的数目不变,省去维度匹配的计算时间,而且准确率不会受影响,
  • 2.残差连接的设计。该结构仿照了resnet的shortcut的设计思路,并且将原有的卷积计算 σ ( W x + b ) \sigma(Wx+b) σ(Wx+b),修改为先将x进行激活 W σ ( x ) + b W\sigma(x)+b Wσ(x)+b,这样可以带来更好的效果;此外filter数目不变,可以省略维度匹配的时间。这种设计使得网络可以扩展为深层网络进行参数的训练。
  • 3.Region Embeeding层。该结构接在embedding层后面。DPCNN的embedding层是one-hot的形式。在region=k的范围内,对word进行线性映射( W x + b Wx+b Wx+b),实质上相当于CNN+one-hot的实现。
  • 4.Block结构:在每个卷积块之后都用大小为3的feature map进行stride为2的max-pooling。这样可以得到相邻3个文本区域的之间特征表示,而且stride为2的pooling操作将每个文本的中间表示直接减少了一半,从而使得每个卷积层的计算复杂度直接减少一半。此外pooling操作使得卷积操作的有效覆盖面积加倍。

四、DPCNN原理

4.1 模型结构

在这里插入图片描述

  DPCNN主要的组成结构包括:

  • Redion embedding层(文本区域嵌入层)
  • 两个convolution block,每层block由两个固定卷积核为3的conv卷积函数构成,两个block构建的层可以pre-activation直接连接
  • Repeat结构,与上一层相似,只不过在conv之前、pre-activate之后增加了Max-polling层

4.2 结构细节

  1.模型输入:输入词维度为[batch_size, seq_len]。

  2.Region Embeeding层

  将TextCNN的包含多尺寸卷积滤波器的卷积层的卷积结果称之为Region embedding,意思就是对一个文本区域/片段(比如3-gram)进行一组卷积操作后生成的embedding。

  此外为了进一步提高性能,还使用了tv-embedding (two-views embedding)进一步提高DPCNN的accuracy,也就是引入预训练的词向量.

  经过embedding层,词向量维度为:[batch_size, seq_len, embedded_size]

  3.等长卷积层

  等长卷积(equal-width convolution):步长 s = 1 s=1 s=1,两端补零 p = ( m − 1 ) / 2 p=(m-1)/2 p=(m1)/2,卷积后输出长度为 n n n。DPCNN模型中采用两层(+relu)每层均为250个尺寸为3的卷积核。
该层采用了pre-activation的做法,卷积运算是 W σ ( x ) + b W\sigma(x)+b Wσ(x)+b,而不是通常用的 σ ( W x + b ) \sigma(Wx+b) σ(Wx+b),这种“线性”简化了深度网络的训练。
经过两层的卷积之后,输出序列维度为:[batch_size, 250, seq_len - 3 + 1]

  4.Repeat结构

  等长卷积后,固定feature maps的数量(减少计算量等),再进行池化。模型中的池化是在每个卷积块后结束之后,对特征合集做一个池化,池化用的是最大池化( p o o l _ s i z e = 3 , s t r i d e = 2 pool\_size=3, stride=2 pool_size=3,stride=2),使每个卷积核的维度减半,形成一个金字塔结构。这种压缩式的Downsampling,可以拼接等实现文本远距离信息匹配于对应。
在这里插入图片描述

  残差连接:为了使深度网络的训练成为可能,使用加法进行shortcut connections,接近等值连接,不需要做任何的维度匹配工作。 z + f ( z ) z+f(z) z+f(z),其中 f f f用的是两层的等长卷积,缓解了梯度消失问题。

  该层的具体操作如下:

  • 进行 p o o l _ s i z e = 3 , s t r i d e = 2 pool\_size=3, stride=2 pool_size=3,stride=2的Max-Pooling,将序列长度压缩为原来的二分之一。
  • 两层等长卷积(+relu),每层都是250个尺寸为3的卷积核。
  • 残差连接:将上述二者的结果相加
    重复以上操作,直至序列长度等于1。

  最终该结构的输出维度为:[batch_size, 250, 1]

  5.全连接+softmax归一化

将张量的维度从[batch_size, num_block]转化到[batch_size, 1]。

五、DPCNN模型的使用

5.1 文本二分类

  首先从文件中读取相应训练集和测试集的语料,利用jieba对文本进行分词,然后利用nltk去停用词。

def tokenizer(text):
    sentence = jieba.lcut(text, cut_all=False)
    stopwords = stopwords.words('chinese')
    sentence = [_ for _ in sentence if _ not in stopwords]
    return sentence

  利用torchtext包处理预处理好的语料,将所提供的语料集转化为相应的词向量模型。由于每一个词均为一个向量,作为模型的输入。

train_set, validation_set = data.TabularDataset.splits(
            path='corpus_data/',
            skip_header=True,
            train='corpus_train.csv',
            validation='corpus_validation.csv',
            format='csv',
            fields=[('label', label), ('text', text)],
        )
text.build_vocab(train_set, validation_set)

  将处理好的词向量输入到DPCNN模型中进行处理。

self.conv_region = nn.Conv2d(1, args.filter_num, (3, embedding_dim), stride=1)
self.conv = nn.Conv2d(args.filter_num, args.filter_num, (3, 1), stride=1)
self.max_pool = nn.MaxPool2d(kernel_size=(3, 1), stride=2)
self.padding1 = nn.ZeroPad2d((0, 0, 1, 1))  # top bottom
self.padding2 = nn.ZeroPad2d((0, 0, 0, 1))  # bottom
self.relu = nn.ReLU()
self.fc = nn.Linear(args.filter_num, label_num)
def forward(self, x):
    # region embedding 层
    # 输入x的维度为(batch_size, max_len)
    x = self.embedding(x)  # [batch_size, seq_length, embedding_dim]
    x = x.view(x.size(0), 1, x.size(1), self.args.embedding_dim)  # [(]batch_size, 1, seq_length, embedding_dim]
    x = self.conv_region(x)  # x = [batch_size, num_filters, seq_len-3+1, 1]

    # 等长卷积层
    x = self.padding1(x)  # [batch_size, num_filters, seq_len, 1]
    x = self.relu(x)
    x = self.conv(x)  # [batch_size, num_filters, seq_len-3+1, 1]
    x = self.padding1(x)  # [batch_size, num_filters, seq_len, 1]
    x = self.relu(x)
    x = self.conv(x)  # [batch_size, num_filters, seq_len-3+1, 1]

    # block结构
    while x.size()[2] >= 2:
        x = self._block(x)  # [batch_size, num_filters,1,1]
    x = x.squeeze()  # [batch_size, num_filters]
    # 全连接+输出
    loggits = self.fc(x)  # [batch_size, 1]
    return loggits
def _block(self, x):
    x = self.padding2(x)
    px = self.max_pool(x)

    x = self.padding1(px)
    x = F.relu(x)
    x = self.conv(x)

    x = self.padding1(x)
    x = F.relu(x)
    x = self.conv(x)

    x = x + px # Short Cut
    return x

  利用训练集对模型进行训练,同时评估训练效果,并利用测试集对模型的准确性进行评估。为了防止偶然性产生的不确定,每一轮迭代会产生100个模型,分别评估其效率,进行调优后再用测试集测试其效率。

  在训练开始时,测试集的准确率在75%左右,然后逐步提高到90%。
在这里插入图片描述

  多轮迭代之后,模型的准确率大概稳定在91%~92%之间。
在这里插入图片描述

5.2 文本多分类与可视化

  从.csv文件中导入相关的训练集、评估集和测试集数据,并进行一定的处理,转化为DataSet。词向量采用globe.6B dim=300的已训练好的词向量。

ID = Field(sequential=False, use_vocab=False)
LABEL = LabelField(sequential=False, use_vocab=True, is_target=True)
INFO = Field(sequential=True, tokenize=jieba.lcut, include_lengths=True)
fields = [
    ('id', ID),
    (None, None),
    ('label', LABEL),
    ('info', INFO),
]

# 加载数据
train_data = TabularDataset(
    os.path.join('data', 'train.csv'),
    format='csv',
    fields=fields,
    csv_reader_params={'delimiter': '\t'}
)
··· # 评估集和测试集数据加载省略
# 创建字典
INFO.build_vocab(train_data, vectors='glove.6B.300d')
LABEL.build_vocab(train_data)

return LABEL, INFO, train_data, valid_data, test_data

构建模型:通过nn所提供的函数,构造DPCNN的模型,以及其中的block结构。

class DPCNN(nn.Module):
    def __init__(self, vocab_size, label_size, embedding_dim, hidden_dim,
                 layer_num, filter_num, dropout=float(0.1)):
        super().__init__()

        # embedding层
        self.embedding = nn.Embedding(vocab_size, embedding_dim)

        self.conv_region = nn.Conv2d(1, filter_num, (3, embedding_dim), stride=1)
        self.conv = nn.Conv2d(filter_num, filter_num, (3, 1), stride=1)
        self.max_pool = nn.MaxPool2d(kernel_size=(3, 1), stride=2)
        self.padding1 = nn.ZeroPad2d((0, 0, 1, 1))  # top bottom
        self.padding2 = nn.ZeroPad2d((0, 0, 0, 1))  # bottom
        self.relu = nn.ReLU()
        self.fc = nn.Linear(filter_num, label_size)
        # 丢弃概率
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, text):
        embedded = self.embedding(text)
        embedded = embedded.unsqueeze(1)  # [batch_size, 1, seq_len, embedding_dim]
        conv_r = self.conv_region(embedded)  # [batch_size, num_filters, seq_len-3+1, 1]
        conv_r_padding = self.padding1(conv_r)  # [batch_size, num_filters, seq_len, 1]
        conv_r_padding = self.relu(conv_r_padding)
        conv = self.conv(conv_r_padding)  # [batch_size, num_filters, seq_len-3+1, 1]
        conv_padding = self.padding1(conv)  # [batch_size, num_filters, seq_len, 1]
        conv_padding = self.relu(conv_padding)
        conv_padding = self.conv(conv_padding)  # [batch_size, num_filters, seq_len-3+1, 1]
        while conv_padding.size()[2] >= 2:
            conv_padding = self._block(conv_padding)  # [batch_size, num_filters,1,1]
        conv_padding = conv_padding.squeeze()  # [batch_size, num_filters]
        loggits = self.fc(conv_padding)  # [batch_size, 1]
        return loggits

    def _block(self, x):
        x = self.padding2(x)
        px = self.max_pool(x)

        x = self.padding1(px)
        x = F.relu(x)
        x = self.conv(x)

        x = self.padding1(x)
        x = F.relu(x)
        x = self.conv(x)

        # Short Cut
        x = x + px
        return x

  数据分批:将dataset中的数据进行分批,依次传入模型中运行。

train_iterator, valid_iterator, test_iterator = BucketIterator.splits(
    (train_data, valid_data, test_data),
    batch_size=args.batch_size,
    sort_within_batch=True,
    sort_key = lambda x: len(x.info),
    device=torch.device('cpu')
)

  使用TensorBoard可视化

writer = SummaryWriter()
writer.add_scalar('训练集/损失率', loss.item(), step)
writer.add_scalar('训练集/学习率', scheduler.get_last_lr()[0], step)
···
writer.close()
tensorboard --logdir ./runs

TensorFlow installation not found - running with reduced feature set.
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.3.0 at http://localhost:6006/ (Press CTRL+C to quit)

  多轮迭代之后,模型的准确率在82%左右。
在这里插入图片描述

  查看模型在训练过程中的参数曲线,相关参数如下图所示:
在这里插入图片描述
在这里插入图片描述

六、总结

  • DPCNN模型的设计思想:设计一个词级别的深度CNN
    • 词序信息在文本分类中很重要,RNN和CNN都能获得词序信息,但由于CNN更简单并且容易并行,所以DPCNN中使用CNN
    • CNN研究中,浅层词级别的CNN效果好于深度字级别的CNN,DPCNN中使用词级别作为输入
    • CNN的词序信息有限(感受野),通过加深网络可以获得更大感受野,但是被证明加深网络词级别CNN性能反而下降。
  • DPCNN模型的创新点:
    • 预训练的Region Embedding
    • Pre-activation Shortcut Connections
    • 下采样不增加feature maps
  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值