NLP大作业—用CNN, RNN等模型实现文本分类

布置了一个NLP的大作业,用神经网络模型对中文文本进行分类,使用THUCNews(百度网盘地址 提取码:tkvz)作为数据集。是个入门级别的作业,但是由于对pytorch以及python的语法不熟悉,导致写得很慢,下面大概总结一下学习过程。

数据集

文章分为十个类别,分别是体育,娱乐,家居,房产,教育,时尚,时政,游戏,科技,财经。训练集有五万条数据,测试集有一万条数据,验证集有五千条数据,数据长下面这个样子
在这里插入图片描述

图一 数据集样本示例

知识预备

  • 分词:我们认为词是一篇文章的最基本单元,词构成句子,句子构成篇,所以要对文章进行分类处理,首先要将文章分割成若干个词语,由词语再对文章类别进行判断。中文可以用jieba库对文本进行分词
  • 词向量:NLP的很重要的概念,单词是无法直接送入模型中进行训练的,必须要先要将它量化,用向量去表示一个词语,比如有3个单词,我们可以用[1, 0 0], [0, 1, 0], [0, 0 ,1]去表示每个词语,这种编码方式叫做独热(one-hot)编码。当然这种编码方式过于冗余了,如果词有几十万,就需要几十万长度的向量,耗费空间太大了。google 提出了word2vec模型,能够将词语用比较小的向量表示出来(几百维)。不清楚的同学可以自行百度word2vec
  • 词向量矩阵:由所有词向量所构成的矩阵,比如由1000个词,每个词向量为300维,那么矩阵的大小为[1000, 300]
  • 嵌入层(embedding layer):嵌入层实质上就是一个词向量矩阵,它包含着所有有可能出现的词语的词向量,这个词向量矩阵可以随机生成,也可以由自己使用word2vec训练得到,也可以使用别人训练好的词向量。需要注意的一点是,嵌入层的输入是词语的序号,也就是说每个词语对应着一个独一无二的序号,通过这个序号就可以在词向量矩阵当中选出对应的那一个词向量。

pipeline

网上很多教程都是上来就开始讲原理,讲原理的博客有很多,我在这里不详细的说明,而我在这里主要着重说明比较宏观的流程。知乎上有一篇写得很好的专栏中文文本分类的pytorch实现, 他的github地址, 大佬的代码写得很漂亮,自己的代码基本上是模仿他写的。OK,现在进入我们的正题。

预处理

网上的教程很多都是用的自带的数据集,可以直接从库中直接加载,不需要预处理。而自己进行预处理就麻烦很多,以图一的数据样本为例,这些都是文本信息,我们要将他们全部数字化

分割类别与正文
类别数字化
正文分词
正文词语数字化
截断与填充
图二 预处理流程图
  • 分割类别与正文:很简单用split函数就可以很好地分割出来, 记住读取的格式必须要为utf-8。
		with open(path, 'r', enconding = 'utf-8' ) as f:
			for line in f.readlines()
				label, content = line.strip().split('\t')
  • 类别数字化:定义一个类别的字典,就可以把类别映射为数字了
  • 正文分词:可以先过滤掉非中文字符,然后使用jieba进行分词
        pattern = re.compile(r'[^\u4e00-\u9fa5]')     #取出中文字符
        content = re.sub(pattern, '', content)
        words = jieba.lcut(content)
  • 正文词语数字化:我们首先需要定义一个包含所有词的字典,给每个词一个序号,这样就可以将词语映射为数字了。在这个任务当中,我使用的是预训练的词向量,所以词典要包含预训练中的词向量中的所有单词。
        for word in words:
            res.append(word_to_id.get(word, 0))
  • 截断与填充:经过之前处理之后,我们得到了长度不一的文章词语数字序列,由于输入模型的数据需要具有相同长度所构成的tensor,首先我们自定义一个pad_size,在该任务中,我取的是300,对于大于pad_size的数字序列进行截断,对于小于pad_size的数字序列进行填充,填充的值可以自己指定,这里我用0进行填充
        if(len(data[i]) > pad_size):          #单词量比所给长度大,截断
            data[i] = data[i][:pad_size]
        else:
            data[i].extend([0] *(pad_size - len(data[i])))  #单词量比所给的小,填充0

输入模型

经过预处理后,我们得到一个矩阵,矩阵的列数是pad_size,即文章的词数,矩阵的行数是文章的数目,这就是我们输入模型的结果。除embedding层外,后面的接的模型可以随意改动。下面约定一下变量的含义:

  • batch_size: 一个batch里面含有的文章数
  • pad_size:文章的词数
  • embedding_size:词向量的维度
  • conv_width:卷积核宽度
  • conv_num:卷积核数目
  • conv_type:卷积核种类
  • num_class:类别数

模型

此部分基本复述上述提及的知乎专栏,自己写一遍加深理解

CNN模型
输入层
embedding层
卷积层
池化层
全连接层
输出层
图二 CNN流程图
  • 输入层:每次随机抽取batch_size数量的文章。tensor的size为 [batch_size, pad_size]
  • embedding层:经过embedding之后,每个词序号对应一个词向量。tensor的size变为[batch_size, pad_size, embedding_size]
  • 卷积层:卷积核的size为[conv_num, conv_width, embedding_size], 卷积完后的输出为[batch_size, conv_num, pad_size - conv_width + , 1], 需要对第3维度(从0开始计数)进行squeeze,变成[batch_size, conv_num, pad_size - conv_width + 1]。在这个任务中,使用了conv_type种卷积核,会生成conv_type个上述的tensor
  • 池化层:使用max pooling,经过池化后,[batch_size, conv_num, pad_size - conv_width + 1]变为[batch_size, conv_num], 然后将多个卷积核得到的tensor在第1维上拼接起来,得到[batch_size, conv_num * conv_type]的tensor
  • 全连接层:输入为[batch_size, conv_num * conv_type],输出为[batch_size, hidden_size]
  • 输出层:输入为[batch_size, hidden_size], 输出为[batch_size, num_class]
RNN模型
输入层
embedding层
LSTM层
全连接层
输出层
图三 RNN流程图

LSTM

图四 LSTM的结构
  • 输入层:每次随机抽取batch_size数量的文章。tensor的size为 [batch_size, pad_size]
  • embedding层:经过embedding之后,每个词序号对应一个词向量。tensor的size变为[batch_size, pad_size, embedding_size]
  • 双向LSTM层:隐层大小为hidden_size,得到所有时刻的隐层状态(前向隐层和后向隐层拼接),尺寸为[batch_size, pad_size, hidden_size * 2], 取出最后一个状态的tensor,size变为[batch_size, hidden_size * 2]
  • 全连接层:输出为[batch_size, num_class]

猜你喜欢

转载自blog.csdn.net/SJTUKK/article/details/108972298