如何用 Python 和循环神经网络做中文文本分类?

如何用 Python 和循环神经网络做中文文本分类?_第1张图片
image

本文为你展示,如何使用 fasttext 词嵌入预训练模型和循环神经网络(RNN), 在 Keras 深度学习框架上对中文评论信息进行情感分类。

疑问

回顾一下,之前咱们讲了很多关于中文文本分类的内容。

你现在应该已经知道如何对中文文本进行分词了。

你也已经学习过,如何利用经典的机器学习方法,对分词后的中文文本,做分类。

你还学习过,如何用词嵌入预训练模型,以向量,而不是一个简单的索引数值,来代表词语,从而让中文词语的表征包含语义级别的信息。

但是,好像还差了点儿什么。

对,基于深度学习的中文文本分类方法,老师是不是忘了讲?

其实没有。

我一直惦记着,把这个重要的知识点,给你详细讲解一下。但是之前这里面一直有一条鸿沟,那就是循环神经网络(Recurrent Neural Network, RNN)。

如果你不知道 RNN 是怎么回事儿,你就很难理解文本作为序列,是如何被深度学习模型来处理的。

好在,我已经为你做了视频教程,用手绘的方式,给你讲了这一部分。

如何用 Python 和循环神经网络做中文文本分类?_第2张图片
image

既然现在这道鸿沟,已被跨越了。本文咱们就来尝试,把之前学过的知识点整合在一起,用 Python 和 Keras 深度学习框架,对中文文本尝试分类。

环境

为了对比的便捷,咱们这次用的,还是《如何用Python和机器学习训练中文文本情感分类模型?》一文中采用过的某商户的点评数据。

我把它放在了一个 github repo 中,供你使用。

请点击这个链接,访问咱们的代码和数据。

如何用 Python 和循环神经网络做中文文本分类?_第3张图片
image

我们的数据就是其中的 dianping.csv 。你可以点击它,看看内容。

如何用 Python 和循环神经网络做中文文本分类?_第4张图片
image

每一行是一条评论。评论内容和情感间,用逗号分隔。

1 代表正向情感,0 代表负面情感。

注意,请使用 Google Chrome 浏览器来完成以下操作。因为你需要安装一个浏览器插件插件,叫做 Colaboratory ,它是 Google 自家的插件,只能在 Chrome 浏览器中,才能运行。

点击这个链接,安装插件。

如何用 Python 和循环神经网络做中文文本分类?_第5张图片
image

把它添加到 Google Chrome 之后,你会在浏览器的扩展工具栏里面,看见下图中间的图标:

image

回到本范例的github repo 主页面,打开其中的 demo.ipynb 文件。

如何用 Python 和循环神经网络做中文文本分类?_第6张图片
image

然后,点击刚刚安装的 Colaboratory 扩展图标。Google Chrome 会自动帮你开启 Google Colab,并且装载这个 ipynb 文件。

如何用 Python 和循环神经网络做中文文本分类?_第7张图片
image

点击菜单栏里面的“代码执行程序”,选择“更改运行时类型”。

如何用 Python 和循环神经网络做中文文本分类?_第8张图片
image

在出现的对话框中,确认选项如下图所示。

如何用 Python 和循环神经网络做中文文本分类?_第9张图片
image

点击“保存”即可。

下面,你就可以依次执行每一个代码段落了。

注意第一次执行的时候,可能会有警告提示。

如何用 Python 和循环神经网络做中文文本分类?_第10张图片
image

出现上面这个警告的时候,点击“仍然运行”就可以继续了。

环境准备好了,下面我们来一步步运行代码。

预处理

首先,我们准备好 Pandas ,用来读取数据。

import pandas as pd

我们从前文介绍的github repo里面,下载代码和数据。

!git clone https://github.com/wshuyi/demo-chinese-text-classification-lstm-keras.git
如何用 Python 和循环神经网络做中文文本分类?_第11张图片
image

下面,我们调用 pathlib 模块,以便使用路径信息。

from pathlib import Path

我们定义自己要使用的代码和数据文件夹。

mypath = Path("demo-chinese-text-classification-lstm-keras")

下面,从这个文件夹里,把数据文件打开。

df = pd.read_csv(mypath/'dianping.csv')

看看头几行数据:

df.head()
如何用 Python 和循环神经网络做中文文本分类?_第12张图片
image

读取正确,下面我们来进行分词。

我们先把结巴分词安装上。

!pip install jieba
如何用 Python 和循环神经网络做中文文本分类?_第13张图片
image

安装好之后,导入分词模块。

import jieba

对每一条评论,都进行切分:

df['text'] = df.comment.apply(lambda x: " ".join(jieba.cut(x)))

因为一共只有2000条数据,所以应该很快完成。

Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 1.089 seconds.
Prefix dict has been built succesfully.

再看看此时的前几行数据。

df.head()
如何用 Python 和循环神经网络做中文文本分类?_第14张图片
image

如图所示,text 一栏下面,就是对应的分词之后的评论。

我们舍弃掉原始评论文本,只保留目前的分词结果,以及对应的情感标记。

df = df[['text', 'sentiment']]

看看前几行:

df.head()
如何用 Python 和循环神经网络做中文文本分类?_第15张图片
image

好了,下面我们读入一些 Keras 和 Numpy 模块,为后面的预处理做准备:

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np

系统提示我们,使用的后端框架,是 Tensorflow 。

Using TensorFlow backend.

下面我们要设置一下,每一条评论,保留多少个单词。当然,这里实际上是指包括标点符号在内的“记号”(token)数量。我们决定保留 100 个。

然后我们指定,全局字典里面,一共保留多少个单词。我们设置为 10000 个。

maxlen = 100
max_words = 10000

下面的几条语句,会自动帮助我们,把分词之后的评论信息,转换成为一系列的数字组成的序列。

tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(df.text)
sequences = tokenizer.texts_to_sequences(df.text)

看看转换后的数据类型。

type(sequences)
list

可见, sequences 是列表类型。

我们看看第一条数据是什么。

sequences[:1]
如何用 Python 和循环神经网络做中文文本分类?_第16张图片
image

评论语句中的每一个记号,都被转换成为了对应的序号。

但是这里有个问题——评论句子有长有短,其中包含的记号个数不同啊。

我们验证一下,只看前面5句。

for sequence in sequences[:5]:
  print(len(sequence))
150
12
16
57
253

果然,不仅长短不一,而且有的还比我们想要的记号数量多。

没关系,用 pad_sequences 方法裁长补短,我们让它统一化:

data = pad_sequences(sequences, maxlen=maxlen)

再看看这次的数据:

data
array([[   2,    1,   74, ..., 4471,  864,    4],
       [   0,    0,    0, ...,    9,   52,    6],
       [   0,    0,    0, ...,    1, 3154,    6],
       ...,
       [   0,    0,    0, ..., 2840,    1, 2240],
       [   0,    0,    0, ...,   19,   44,  196],
       [   0,    0,    0, ...,  533,   42,    6]], dtype=int32)

那些长句子,被剪裁了;短句子,被从头补充了若干个 0 。

同时,我们还希望知道,这些序号分别代表什么单词,所以我们把这个索引保存下来。

word_index = tokenizer.word_index

看看索引的类型。

type(word_index)
dict

没错,它是个字典(dict)。打印看看。

print(word_index)
如何用 Python 和循环神经网络做中文文本分类?_第17张图片
image

好了,中文评论数据,已经被我们处理成一系列长度为 100 ,其中都是序号的序列了。下面我们要把对应的情感标记,存储到 labels 中。

labels = np.array(df.sentiment)

看一下其内容:

labels
array([0, 1, 0, ..., 0, 1, 1])

好了,总体数据都已经备妥了。下面我们来划分一下训练集和验证集。

我们采用的,是把序号随机化,但保持数据和标记之间的一致性。

indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

看看此时的标记:

labels
array([0, 1, 1, ..., 0, 1, 1])

注意顺序已经发生了改变。

我们希望,训练集占 80% ,验证集占 20%。根据总数,计算一下两者的实际个数:

training_samples = int(len(indices) * .8)
validation_samples = len(indices) - training_samples

其中训练集包含多少数据?

training_samples
1600

验证集呢?

validation_samples
400

下面,我们正式划分数据。

X_train = data[:training_samples]
y_train = labels[:training_samples]
X_valid = data[training_samples: training_samples + validation_samples]
y_valid = labels[training_samples: training_samples + validation_samples]

看看训练集的输入数据:

X_train
array([[   0,    0,    0, ...,  963,    4,  322],
       [   0,    0,    0, ..., 1485,   79,   22],
       [   1,   26,  305, ...,  289,    3,   71],
       ...,
       [   0,    0,    0, ...,  365,  810,    3],
       [   0,    0,    0, ...,    1,  162, 1727],
       [ 141,    5,  237, ...,  450,  254,    4]], dtype=int32)

好了,至此预处理部分,就算完成了。

词嵌入

下面,我们安装 gensim 软件包,以便使用 Facebook 提供的 fasttext 词嵌入预训练模型。

!pip install gensim
如何用 Python 和循环神经网络做中文文本分类?_第18张图片
image

读入加载工具:

from gensim.models import KeyedVectors

然后我们需要把 github repo 中下载来的词嵌入预训练模型压缩数据解压。

myzip = mypath / 'zh.zip'
!unzip $myzip
Archive:  demo-chinese-text-classification-lstm-keras/zh.zip
  inflating: zh.vec

好了,读入词嵌入预训练模型数据。

zh_model = KeyedVectors.load_word2vec_format('zh.vec')

看看其中的第一个向量是什么:

zh_model.vectors[0]
如何用 Python 和循环神经网络做中文文本分类?_第19张图片
image

这么长的向量,对应的记号是什么呢?

看看前五个词汇:

list(iter(zh_model.vocab))[:5]
['的', '', '在', '是', '年']

原来,刚才这个向量,对应的是标记“的”。

向量里,到底有多少个数字?

len(zh_model[next(iter(zh_model.vocab))])
300

我们把这个向量长度,进行保存。

embedding_dim = len(zh_model[next(iter(zh_model.vocab))])

然后,以我们最大化标记个数,以及每个标记对应向量长度,建立一个随机矩阵。

embedding_matrix = np.random.rand(max_words, embedding_dim)

看看它的内容:

embedding_matrix
如何用 Python 和循环神经网络做中文文本分类?_第20张图片
image

因为这种随机矩阵,默认都是从0到1的实数。

然而,我们刚才已经看过了“的”的向量表示,

如何用 Python 和循环神经网络做中文文本分类?_第21张图片
image

请注意,其中的数字在 -1 到 1 的范围中间。为了让我们随机产生的向量,跟它类似,我们把矩阵进行一下数学转换:

embedding_matrix = (embedding_matrix - 0.5) * 2
embedding_matrix
如何用 Python 和循环神经网络做中文文本分类?_第22张图片
image

这样看起来就好多了。

我们尝试,对某个特定标记,读取预训练的向量结果:

zh_model.get_vector('的')
如何用 Python 和循环神经网络做中文文本分类?_第23张图片
image

但是注意,如果标记在预训练过程中没有出现,会如何呢?

试试输入我的名字:

zh_model.get_vector("王树义")
如何用 Python 和循环神经网络做中文文本分类?_第24张图片
image

不好意思,因为我的名字,在 fasttext 做预训练的时候没有出现,所以会报错。

因此,在我们构建适合自己任务的词嵌入层的时候,也需要注意那些没有被训练过的词汇。

这里我们判断一下,如果无法获得对应的词向量,我们就干脆跳过,使用默认的随机向量。

for word, i in word_index.items():
    if i < max_words:
        try:
          embedding_vector = zh_model.get_vector(word)
          embedding_matrix[i] = embedding_vector
        except:
          pass

这也是为什么,我们前面尽量把二者的分布调整成一致。

看看我们产生的词嵌入矩阵:

embedding_matrix
如何用 Python 和循环神经网络做中文文本分类?_第25张图片
image

模型

词嵌入准备好了,下面我们就要搭建模型了。

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(max_words, embedding_dim))
model.add(LSTM(units))
model.add(Dense(1, activation='sigmoid'))
model.summary()
如何用 Python 和循环神经网络做中文文本分类?_第26张图片
image

注意这里的模型,是最简单的顺序模型,对应的模型图如下:

如何用 Python 和循环神经网络做中文文本分类?_第27张图片
image

如图所示,我们输入数据通过词嵌入层,从序号转化成为向量,然后经过 LSTM (RNN 的一个变种)层,依次处理,最后产生一个32位的输出,代表这句评论的特征。

这个特征,通过一个普通神经网络层,然后采用 Sigmoid 函数,输出为一个0到1中间的数值。

如何用 Python 和循环神经网络做中文文本分类?_第28张图片
image

这样,我们就可以通过数值与 0 和 1 中哪个更加接近,进行分类判断。

但是这里注意,搭建的神经网络里,Embedding 只是一个随机初始化的层次。我们需要把刚刚构建的词嵌入矩阵导入。

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

这里,我们希望保留好不容易获得的单词预训练结果,所以在后面的训练中,我们不希望对这一层进行训练。

因为是二元分类,因此我们设定了损失函数为 binary_crossentropy

我们训练模型,保存输出为 history ,并且把最终的模型存储为 mymodel.h5

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(X_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(X_valid, y_valid))
model.save("mymodel.h5")

执行上面代码段,模型就在认认真真训练了。

如何用 Python 和循环神经网络做中文文本分类?_第29张图片
image

结果如上图所示。

讨论

对于这个模型的分类效果,你满意吗?

如果单看最终的结果,训练集准确率超过 90%, 验证集准确率也超过 80%,好像还不错嘛。

但是,我看到这样的数据时,会有些担心。

我们把那些数值,用可视化的方法,显示一下:

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()
如何用 Python 和循环神经网络做中文文本分类?_第30张图片
image

上图是准确率曲线。虚线是训练集,实线是验证集。我们看到,训练集一路走高,但是验证集在波动。虽然最后一步刚好是最高点。

看下面的图,会更加清晰。

如何用 Python 和循环神经网络做中文文本分类?_第31张图片
image

上图是损失数值对比。我们可以看到,训练集上,损失数值一路向下,但是,从第2个 epoch 开始,验证集的损失数值,就没有保持连贯的显著下降趋势。二者发生背离。

这意味着什么?

这就是深度学习中,最常见,也是最恼人的问题——过拟合(overfitting)。

《如何用机器学习处理二元分类任务?》一文中,我曾经就这个问题,为你做过详细的介绍。这里不赘述了。

但是,我希望你能够理解它出现的原因——相对于你目前使用的循环神经网络结构,你的数据量太小了。

深度学习,对于数据数量和质量的需求,都很高。

有没有办法,可以让你不需要这么多的数据,也能避免过拟合,取得更好的训练结果呢?

这个问题的答案,我在《如何用 Python 和深度迁移学习做文本分类?》一文中已经为你介绍过,如果你忘记了,请复习一下。

小结

本文,我们探讨了如何用循环神经网络处理中文文本分类问题。读过本文并且实践之后,你应该已经能够把下列内容融会贯通了:

  • 文本预处理
  • 词嵌入矩阵构建
  • 循环神经网络模型搭建
  • 训练效果评估

希望这份教程,可以在你的科研和工作中,帮上一些忙。

祝(深度)学习愉快!

喜欢请点赞和打赏。还可以微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。

如果你对 Python 与数据科学感兴趣,不妨阅读我的系列教程索引贴《如何高效入门数据科学?》,里面还有更多的有趣问题及解法。

你可能感兴趣的:(如何用 Python 和循环神经网络做中文文本分类?)