TensorFlow – 使用CNN进行中文文本分类

使用卷积神经网络(CNN)处理自然语言处理(NLP)中的文本分类问题。本文将结合TensorFlow代码介绍:

  • 词嵌入

    • 填充
    • Embedding
  • 卷积层

    • 卷积(tf.nn.conv1d)
    • 池化(pooling)
  • 全连接层

  • dropout

  • 输出层

  • softmax

池化、dropout和softmax函数的介绍见博客卷积神经网络(CNN)与TensorFlow实现
  在文章的最后给出了项目的完整代码。

结果预览


文件测试

键盘输入测试

网络结构与解释

网络的主体结构如下如所示:

代码的详细流程图为:

1. word embedding 词向量转换

这也是NLP文本分类任务中最重要的一步,因为当我们知道如何用计算机能理解的词向量(word vector)表示自然语言的句子(sequence)时,文本分类问题就退化成了一个简单的数据分类问题,和MNIST分类本质上没有区别。
  在解决这个问题之前,我们先回顾一下语言是如何表示的。

**  如何表示一个词语的意思**

先来看看如何定义“意思”的意思,英文中meaning代表人或文字想要表达的idea。这是个递归的定义,估计查询idea词典会用meaning去解释它。
  1. 用单词、短语等表示的想法
  2. 人们想要通过单词、符号等表达的想法
  3. 用写作,绘画的作品表达出来的想法
  但是,目前在计算机系统处理语言上无法应用这种表示方法。

**  计算机如何处理词语的意思**

最初的词向量是one-hot形式的向量,即只有单词所在的那一维是1,其他维都是0,向量长度和词汇表(vocab)大小一样。如下表所示:

文本词向量
[0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0]
[0,0,0,0,0,0,1,0,0,……,0,0,0,0,0,0,0]
[0,0,0,0,0,1,0,0,0,……,0,0,0,0,0,0,0]
[0,0,1,0,0,0,0,0,0,……,0,0,0,0,0,0,0]
[0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0]

这种表示方法的缺点显而易见:
  1.容易造成维度灾难。假设我们要表示5000个常用字,需要用5000维的词向量。如果表示词语或者成语则需要更大的词向量。
  2.对词语之间的语义关系起不到任何表达作用。任何两个词语之间的距离都是相同的,无法使意思相近的词语距离也相近。
  
  能不能把词向量的维度变小呢?
  
  Dristributed representation可以解决One hot representation的问题,它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度是多大呢?这个一般需要我们在训练时自己来指定。
  也就是用神经网络来训练表示本身
  理想状态下,我们可以将词语训练成如下图的表示方法。但是,其实实际操作中我们只需要指定词向量维度的大小,在训练时不知道每一维具体表示什么含义

我们将king这个词从一个可能非常稀疏的向量坐在的空间,映射到现在这个四维向量所在的空间,必须满足以下性质:
  (1) 这个映射是单设;
  (2) 映射之后的向量不会丢失之前的那种向量所含的信息。
  这个过程称为word embedding(词嵌入),即将高维词向量嵌入到一个低维空间。

经过我们一系列的降维神操作,有了用Dristributed representation表示的较短的词向量,我们就可以较容易的分析词之间的关系了,比如我们将词的维度降维到2维,有一个有趣的研究表明,用下图的词向量表示我们的词时,我们可以发现:

K i n g → − M a n → + W o m a n → = Q u e e n → \overrightarrow{K i n g}-\overrightarrow{M a n}+\overrightarrow{W o m a n}=\overrightarrow{Q u e e n} King Man +Woman =Queen

是不是机器学习的学习能力也不错!

**  本文如何处理词语的意思**

中文文本的表示方式与英文有所不同,因为英文单词可以很容易地由空格区别,而中文通常需要先进行分词操作,然后对分词后的词语编码。不事先进行分词,直接对汉字编码的称为字符级编码。
  本文先使用one-hot对文本进行字符级编码,然后通过神经网络训练出高维到低维的映射方式。详细的步骤为:
  1.建立一个词汇表(vocab),该词汇表是包含输入所有可能出现的字母、数字、符号及汉字的集合(本文使用的vocab大小为5000)。vocab的形式如下表:

id词汇
15
16
17
18
19
20
21
22
23(空格)

2.用词汇表(vocab)将输入的文本转换成id列表的形式,代码为:

with open_file(vocab_dir) as fp:  # 打开vocab文件
    words = [_.strip() for _ in fp.readlines()]  # 按行读取词汇,并转成列表的形式
word_to_id = dict(zip(words, range(len(words))))  # 将词汇与id组合,并转成字典(dict)的形式
# word_to_id = {'兰':15, '布':16, '柏':17 ...}

假设有一段文本输入为:

'兰柏蒂克 布艺床 1.8米 双人床 软床 婚床'

使用上面的词汇表(vocab)转成id形式后为:

[15, 17, 18, 22, 23, 16, ...]  

one-hot编码矩阵为:

[ 0,  0,  0,  0,  0,  0, ...]  
              ...  
[ 1,  0,  0,  0,  0,  0, ...]  # 下标15
[ 0,  0,  0,  0,  0,  1, ...]  
[ 0,  1,  0,  0,  0,  0, ...]  
[ 0,  0,  1,  0,  0,  0, ...]  
[ 0,  0,  0,  0,  0,  0, ...]  
[ 0,  0,  0,  0,  0,  0, ...]  # 下标20
[ 0,  0,  0,  0,  0,  0, ...]  
[ 0,  0,  0,  1,  0,  0, ...]  
[ 0,  0,  0,  0,  1,  0, ...]  
              ...  

3.将文本pad为固定长度

x_pad = kr.preprocessing.sequence.pad_sequences(data_id, max_length)   

这里max_length设为100,代表文本的最大长度不能超过max_length,转成id形式的列表经过填充后变成固定长度的列表。填充(pad)的方式为在前面填充若干个0

[0, 0, 0, 0, 0, ...... ,15, 17, 18, 22, 23, 16, ...]  # 填充0后长度为 max_length  

4.词嵌入(embedding)

embedding = tf.get_variable('embedding', [vocab_size, embedding_dim])  # 5000×64
embedding_inputs = tf.nn.embedding_lookup(embedding, input_x)

上面的代码将5000维one-hot编码的输入文本转为较低维度(embedding_dim维)的用实数表示的词向量。在项目代码中embedding_dim设为64,为了简化问题,学习tf.nn.embedding_lookup的用法,下面假设:

embedding_dim = 2  # 假设词向量仅用2维实数编码
input_x = [[0, 0, 0, 0, 0, ...... ,15, 17, 18, 22, 23, 16, ...]]  # 这里有两层列表,外层列表表示输入的语句,因为只有一个语句所以长度为1
embedding = [[0,0], .....(下标为15)[0.1,1.5], [1.0,0.1], [0.2,0.1], [1.0,0.3], [0.5,0.1], (下标为20)[0.3,1.5], [0.1,0.6], [0.4,0.8], [0.5,0.5]....]  # embedding 为5000×2维

embedding用表格表示为:

下标内容
0[0.0,0.0]
15[0.1,1.5]
16[1.0,0.1]
17[0.2,0.1]
18[1.0,0.3]
19[0.5,0.1]
20[0.3,1.5]
21[0.1,0.6]
22[0.4,0.8]
23[0.5,0.5]

注意,这些参数都是在训练中不断更新的
  使用上面的embedding,tf.nn.embedding_lookup(embedding, input_x)的结果为:

[[[ 0.0  0.0]
  ....
  [ 0.1  1.5]  # 15-兰
  [ 0.2  0.1]  # 17-柏
  [ 1.0  0.3]  # 18-蒂
  [ 0.4  0.8]  # 22-克
  [ 0.5  0.5]  # 23-(空格)
  [ 1.0  0.1]  # 16-布
  ...........]]

也就是将input_x从5000×100维的one-hot编码映射为2×100的词向量(每个字映射为2维词向量,长度为100)。代码中没有明确出现one-hot的编码过程,但是tf.nn.embedding_lookup函数从embedding中取input_x指定下标的序列,因为下标i的范围是[0,5000),而embedding[i]是一个2维的向量,相当于完成了5000维(one-hot形式)到2维的映射,这与先进行one-hot编码再映射结果是相同的。当embedding的维度为n时,原理与2维相同,仅仅是表示的数组要换成n维。

2. conv1d 卷积

conv = tf.layers.conv1d(embedding_inputs, filters=5, kernel_size=256)

卷积的计算方法如下图所示:
  

和处理图像时用的二维卷积不同,处理文本时使用的是一维卷积。如上图所示,使用了256个卷积核,每个卷积核大小为1×5,卷积核在每个特征上同时向右滑动,计算方式为每个维度的特征与卷积核的卷积之和加上偏移(如图中的红色区域)。可以看出一句话中两个字之间距离超过5时,不会在一个卷积核中计算到,也就是不会考虑它们之间的关联性,这也是CNN处理文本的局限之处,使用LSTM可以改进这一不足。

3. max_pool 最大池化

 max_pool = tf.reduce_max(conv, reduction_indices=[1])

在卷积的过程中,长度为5的卷积核在长度为100的文本上滑动,最终得到96个输出值,由于有256个卷积核,卷积后的最终输出大小为96×256。
  代码中使用的是一个简化的最大值池化,即对96个输出直接取最大值(而没有使用池化窗口滑动),池化后的输出大小为256。

4. dense 全连接层和 output 输出层

fc = tf.layers.dense(max_pool , units=512)
fc = tf.contrib.layers.dropout(fc, self.keep_prob)
fc = tf.nn.relu(fc)

logits = tf.layers.dense(fc, units=num_classes)
y = tf.nn.softmax(self.logits)  # 概率输出
y_pred_cls = tf.argmax(y, 1)  # 预测类别的索引

全连接层将256维的中间特征转成512维的,输出层进一步转成1258个类别的概率输出。取概率最大的下标即为预测的类别,最后在categories中找到对应下标的类别输出,就能得到预测的结果了。

项目代码

https://github.com/misads/text-classification-cnn
  给个star吧~

参考资料

通俗理解word2vec
https://www.jianshu.com/p/471d9bfbd72f

基于tensorflow+CNN的新浪新闻文本分类
https://www.jianshu.com/p/b1000d5345bb

深度学习中Embedding层有什么用?
https://www.cnblogs.com/fujian-code/p/8967340.html

CS224n NLP-Lecture 2: Word Vectors/第二讲-词向量表示: word2vec
https://blog.csdn.net/qq_34243930/article/details/88133716

word embedding系列(一)背景知识
https://cloud.tencent.com/developer/news/296053

Word embedding系列(二):word2vec详解
https://cloud.tencent.com/developer/news/296840

seq2seq
https://zhuanlan.zhihu.com/p/40920384

我的博客

如果这些内容对你有所帮助,可以关注我的个人博客哦~
http://www.xyu.ink/blog

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 文本分类 #### 数据预处理 要求训练集和测试集分开存储,对于中文的数据必须先分词,对分词后的词用空格符分开,并且将标签连接到每条数据的尾部,标签和句子用分隔符\分开。具体的如下: * 今天 的 天气 真好\积极 #### 文件结构介绍 * config文件:配置各种模型的配置参数 * data:存放训练集和测试集 * ckpt_model:存放checkpoint模型文件 * data_helpers:提供数据处理的方法 * pb_model:存放pb模型文件 * outputs:存放vocab,word_to_index, label_to_index, 处理后的数据 * models:存放模型代码 * trainers:存放训练代码 * predictors:存放预测代码 #### 训练模型 * python train.py --config_path="config/textcnn_config.json" #### 预测模型 * 预测代码都在predictors/predict.py中,初始化Predictor对象,调用predict方法即可。 #### 模型的配置参数详述 ##### textcnn:基于textcnn文本分类 * model_name:模型名称 * epochs:全样本迭代次数 * checkpoint_every:迭代多少步保存一次模型文件 * eval_every:迭代多少步验证一次模型 * learning_rate:学习速率 * optimization:优化算法 * embedding_size:embedding层大小 * num_filters:卷积核的数量 * filter_sizes:卷积核的尺寸 * batch_size:批样本大小 * sequence_length:序列长度 * vocab_size:词汇表大小 * num_classes:样本的类别数,二分类时置为1,多分类时置为实际类别数 * keep_prob:保留神经元的比例 * l2_reg_lambda:L2正则化的系数,主要对全连接层的参数正则化 * max_grad_norm:梯度阶段临界值 * train_data:训练数据的存储路径 * eval_data:验证数据的存储路径 * stop_word:停用词表的存储路径 * output_path:输出路径,用来存储vocab,处理后的训练数据,验证数据 * word_vectors_path:词向量的路径 * ckpt_model_path:checkpoint 模型的存储路径 * pb_model_path:pb 模型的存储路径 ##### bilstm:基于bilstm的文本分类 * model_name:模型名称 * epochs:全样本迭代次数 * checkpoint_every:迭代多少步保存一次模型文件 * eval_every:迭代多少步验证一次模型 * learning_rate:学习速率 * optimization:优化算法 * embedding_size:embedding层大小 * hidden_sizes:lstm的隐层大小,列表对象,支持多层lstm,只要在列表中添加相应的层对应的隐层大小 * batch_size:批样本大小 * sequence_length:序列长度 * vocab_size:词汇表大小 * num_classes:样本的类别数,二分类时置为1,多分类时置为实际类别数 * keep_prob:保留神经元的比例 * l2_reg_lambda:L2正则化的系数,主要对全连接层的参数正则化 * max_grad_norm:梯度阶段临界值 * train_data:训练数据的存储路径 * eval_data:验证数据的存储路径 * stop_word:停用词表的存储路径 * output_path:输出路径,用来存储vocab,处理后的训练数据,验证数据 * word_vectors_path:词向量的路径 * ckpt_model_path:checkpoint 模型的存储路径 * pb_model_path:pb 模型的存储路径 ##### bilstm atten:基于bilstm + attention 的文本分类 * model_name:模型名称 * epochs:全样本迭代次数 * checkpoint_every:迭代多少步保存一次模型文件 * eval_every:迭代多少步验证一次模型 * learning_rate:学习速率 * optimization:优化算法 * embedding_size:embedding层大小 * hidd

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值