#转 https://blog.csdn.net/asialee_bird/article/details/88813385

前言:

深度学习模型在计算机视觉语音识别方面取得了卓越的成就,在 NLP 领域也是可以的。将卷积神经网络CNN应用到文本分类任务,利用多个不同size的kernel来提取句子中的关键信息(类似 n-gram 的关键信息,从而能够更好地捕捉局部相关性。
文本分类是自然语言处理领域最活跃的研究方向之一,目前文本分类在工业界的应用场景非常普遍,从新闻的分类、商品评论信息的情感分类到微博信息打标签辅助推荐系统,了解文本分类技术是NLP初学者比较好的切入点,较简单且应用场景高频。

一、论文笔记

1、Yoon Kim在2014年 “Convolutional Neural Networks for Sentence Classification” 论文中提出TextCNN(利用卷积神经网络对文本进行分类的算法)(该论文翻译)。
image.png
image.png
上图很好地诠释了模型的框架。假设我们有一些句子需要对其进行分类。句子中每个词是由n维词向量组成的,也就是说输入矩阵大小为m*n,其中m为句子长度。CNN需要对输入样本进行卷积操作,对于文本数据,filter不再横向滑动,仅仅是向下移动,有点类似于N-gram在提取词与词间的局部相关性。图中共有三种步长策略,分别是2,3,4,每个步长都有两个filter(实际训练时filter数量会很多)。在不同词窗上应用不同filter,最终得到6个卷积后的向量。然后对每一个向量进行最大化池化操作并拼接各个池化值,最终得到这个句子的特征表示,将这个句子向量丢给分类器进行分类,至此完成整个流程。
(1)嵌入层(Embedding Layer)
通过一个隐藏层, 将 one-hot 编码的词投影到一个低维空间中,本质上是特征提取器,在指定维度中编码语义特征。 这样, 语义相近的词, 它们的欧氏距离或余弦距离也比较近。(作者使用的单词向量是预训练的,方法为fasttext得到的单词向量,当然也可以使用word2vec和GloVe方法训练得到的单词向量)。
(2)卷积层(Convolution Laye)
在处理图像数据时,CNN使用的卷积核的宽度和高度的一样的,但是在text-CNN中,卷积核的宽度是与词向量的维度一致!这是因为我们输入的每一行向量代表一个词,在抽取特征的过程中,词做为文本的最小粒度。而高度和CNN一样,可以自行设置(通常取值2,3,4,5),高度就类似于n-gram了。由于我们的输入是一个句子,句子中相邻的词之间关联性很高,因此,当我们用卷积核进行卷积时,不仅考虑了词义而且考虑了词序及其上下文(类似于skip-gram和CBOW模型的思想)。
(3)池化层(Pooling Layer)
因为在卷积层过程中我们使用了不同高度的卷积核,使得我们通过卷积层后得到的向量维度会不一致,所以在池化层中,我们使用1-Max-pooling对每个特征向量池化成一个值,即抽取每个特征向量的最大值表示该特征,而且认为这个最大值表示的是最重要的特征。当我们对所有特征向量进行1-Max-Pooling之后,还需要将每个值给拼接起来。得到池化层最终的特征向量。在池化层到全连接层之前可以加上dropout防止过拟合。
(4)全连接层(Fully connected layer)
全连接层跟其他模型一样,假设有两层全连接层,第一层可以加上’relu’作为激活函数,第二层则使用softmax激活函数得到属于每个类的概率。
(5)TextCNN的小变种
在词向量构造方面可以有以下不同的方式: CNN-rand: 随机初始化每个单词的词向量通过后续的训练去调整。 CNN-static: 使用预先训练好的词向量,如word2vec训练出来的词向量,在训练过程中不再调整该词向量。 CNN-non-static: 使用预先训练好的词向量,并在训练过程进一步进行调整。 CNN-multichannel: 将static与non-static作为两通道的词向量。
(6)参数与超参数

  • sequence_length (Q: 对于CNN, 输入与输出都是固定的,可每个句子长短不一, 怎么处理? A: 需要做定长处理, 比如定为n, 超过的截断, 不足的补0. 注意补充的0对后面的结果没有影响,因为后面的max-pooling只会输出最大值,补零的项会被过滤掉)
  • num_classes (多分类, 分为几类)
  • vocabulary_size (语料库的词典大小, 记为|D|)
  • embedding_size (将词向量的维度, 由原始的 |D| 降维到 embedding_size)
  • filter_size_arr (多个不同size的filter)

2、2015年“A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification”论文详细地阐述了关于TextCNN模型的调参心得。
image.png
(1)TextCNN详细过程:

  • Embedding:第一层是图中最左边的7乘5的句子矩阵,每行是词向量,维度=5,这个可以类比为图像中的原始像素点。
  • Convolution:然后经过 kernel_sizes=(2,3,4) 的一维卷积层,每个kernel_size 有两个输出 channel。
  • MaxPolling:第三层是一个1-max pooling层,这样不同长度句子经过pooling层之后都能变成定长的表示。
  • FullConnection and Softmax:最后接一层全连接的 softmax 层,输出每个类别的概率。

(2)论文调参结论:

  • 使用预训练的word2vec 、 GloVe初始化效果会更好。一般不直接使用One-hot。
  • 卷积核的大小影响较大,一般取1~10,对于句子较长的文本,则应选择大一些。
  • 卷积核的数量也有较大的影响,一般取100~600 ,同时一般使用Dropout(0~0.5)。
  • 激活函数一般选用ReLU 和 tanh。
  • 池化使用1-max pooling。
  • 随着feature map数量增加,性能减少时,试着尝试大于0.5的Dropout。
  • 评估模型性能时,记得使用交叉验证。

    二、Keras文本预处理

    1、读取数据集

    2、将文字转换成数字特征

    使用Tokenizer将文字转换成数字特征
    使用Keras的Tokenizer模块实现转换。当我们创建了一个Tokenizer对象后,使用该对象的fit_on_texts()函数,可以将输入的文本中的每个词编号,编号是根据词频的,词频越大,编号越小。使用word_index属性可以看到每次词对应的编码。

    3、将每条文本转换为数字列表

    将数据集中的每条文本转换为数字列表,使用每个词的编号进行编号
    使用该对象的texts_to_sequences()函数,将每条文本转变成一个向量。

    4、将每条文本设置为相同长度

    使用pad_sequences()让每句数字影评长度相同
    由于每句话的长度不唯一,需要将每句话的长度设置一个固定值。将超过固定值的部分截掉,不足的在最前面用0填充。

    5、将每个词编码转换为词向量

    使用Embedding层将每个词编码转换为词向量
    Embedding层基于上文所得的词编码,对每个词进行one-hot编码,每个词都会是一个vocabulary_size维的向量;然后通过神经网络的训练迭代更新得到一个合适的权重矩阵(具体实现过程可以参考skip-gram模型),行大小为vocabulary_size,列大小为词向量的维度,将本来以one-hot编码的词向量映射到低维空间,得到低维词向量。需要声明一点的是Embedding层是作为模型的第一层,在训练模型的同时,得到该语料库的词向量。当然,也可以使用已经预训练好的词向量表示现有语料库中的词。
    文本预处理目的:将每个样本转换为一个数字矩阵,矩阵的每一行表示一个词向量。
    image.png

    6、Keras文本预处理代码实现

    ```python

from sklearn.model_selection import train_test_split import pandas as pd import jieba import numpy as np from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.models import Model from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.layers import Conv1D, MaxPooling1D, Embedding, concatenate, Flatten, Dropout, Input, Dense

if name==’main‘: dataset = pd.read_csv(‘sentiment_analysis/data_train.csv’, sep=’\t’,names=[‘ID’, ‘type’, ‘review’, ‘label’]).astype(str) cw = lambda x: list(jieba.cut(x)) dataset[‘words’] = dataset[‘review’].apply(cw) tokenizer=Tokenizer() #创建一个Tokenizer对象

  1. #fit_on_texts函数可以将输入的文本中的每个词编号,编号是根据词频的,词频越大,编号越小
  2. tokenizer.fit_on_texts(dataset['words'])
  3. vocab=tokenizer.word_index #得到每个词的编号
  4. x_train, x_test, y_train, y_test = train_test_split(dataset['words'], dataset['label'], test_size=0.1)
  5. # 将每个样本中的每个词转换为数字列表,使用每个词的编号进行编号
  6. x_train_word_ids=tokenizer.texts_to_sequences(x_train)
  7. x_test_word_ids = tokenizer.texts_to_sequences(x_test)
  8. #序列模式
  9. # 每条样本长度不唯一,将每条样本的长度设置一个固定值
  10. x_train_padded_seqs=pad_sequences(x_train_word_ids,maxlen=50) #将超过固定值的部分截掉,不足的在最前面用0填充
  11. x_test_padded_seqs=pad_sequences(x_test_word_ids, maxlen=50)
  1. <a name="0c276447"></a>
  2. ## **三、基于keras的TextCNN模型的构建、训练与测试**
  3. <a name="7a8dd713"></a>
  4. ### **1、基础版CNN(模仿LeNet-5)**
  5. LeNet-5是卷积神经网络的作者Yann LeCun用于MNIST识别任务提出的模型。模型很简单,就是卷积池化层的堆叠,最后加上几层全连接层。将其运用在文本分类任务中。
  6. ```python
  7. #构建CNN分类模型(LeNet-5)
  8. #模型结构:嵌入-卷积池化*2-dropout-BN-全连接-dropout-全连接
  9. def CNN_model(x_train_padded_seqs, y_train, x_test_padded_seqs, y_test):
  10. model = Sequential()
  11. model.add(Embedding(len(vocab) + 1, 300, input_length=50)) #使用Embeeding层将每个词编码转换为词向量
  12. model.add(Conv1D(256, 5, padding='same'))
  13. model.add(MaxPooling1D(3, 3, padding='same'))
  14. model.add(Conv1D(128, 5, padding='same'))
  15. model.add(MaxPooling1D(3, 3, padding='same'))
  16. model.add(Conv1D(64, 3, padding='same'))
  17. model.add(Flatten())
  18. model.add(Dropout(0.1))
  19. model.add(BatchNormalization()) # (批)规范化层
  20. model.add(Dense(256, activation='relu'))
  21. model.add(Dropout(0.1))
  22. model.add(Dense(3, activation='softmax'))
  23. model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
  24. one_hot_labels = keras.utils.to_categorical(y_train, num_classes=3) # 将标签转换为one-hot编码
  25. model.fit(x_train_padded_seqs, one_hot_labels,epochs=5, batch_size=800)
  26. y_predict = model.predict_classes(x_test_padded_seqs) # 预测的是类别,结果就是类别号
  27. y_predict = list(map(str, y_predict))
  28. print('准确率', metrics.accuracy_score(y_test, y_predict))
  29. print('平均f1-score:', metrics.f1_score(y_test, y_predict, average='weighted'))

2、简单版TextCNN

  1. #构建TextCNN模型
  2. #模型结构:词嵌入-卷积池化*3-拼接-全连接-dropout-全连接
  3. def TextCNN_model_1(x_train_padded_seqs,y_train,x_test_padded_seqs,y_test):
  4. main_input = Input(shape=(50,), dtype='float64')
  5. # 词嵌入(使用预训练的词向量)
  6. embedder = Embedding(len(vocab) + 1, 300, input_length=50, trainable=False)
  7. embed = embedder(main_input)
  8. # 词窗大小分别为3,4,5
  9. cnn1 = Conv1D(256, 3, padding='same', strides=1, activation='relu')(embed)
  10. cnn1 = MaxPooling1D(pool_size=48)(cnn1)
  11. cnn2 = Conv1D(256, 4, padding='same', strides=1, activation='relu')(embed)
  12. cnn2 = MaxPooling1D(pool_size=47)(cnn2)
  13. cnn3 = Conv1D(256, 5, padding='same', strides=1, activation='relu')(embed)
  14. cnn3 = MaxPooling1D(pool_size=46)(cnn3)
  15. # 合并三个模型的输出向量
  16. cnn = concatenate([cnn1, cnn2, cnn3], axis=-1)
  17. flat = Flatten()(cnn)
  18. drop = Dropout(0.2)(flat)
  19. main_output = Dense(3, activation='softmax')(drop)
  20. model = Model(inputs=main_input, outputs=main_output)
  21. model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  22. one_hot_labels = keras.utils.to_categorical(y_train, num_classes=3) # 将标签转换为one-hot编码
  23. model.fit(x_train_padded_seqs, one_hot_labels, batch_size=800, epochs=10)
  24. #y_test_onehot = keras.utils.to_categorical(y_test, num_classes=3) # 将标签转换为one-hot编码
  25. result = model.predict(x_test_padded_seqs) # 预测样本属于每个类别的概率
  26. result_labels = np.argmax(result, axis=1) # 获得最大概率对应的标签
  27. y_predict = list(map(str, result_labels))
  28. print('准确率', metrics.accuracy_score(y_test, y_predict))
  29. print('平均f1-score:', metrics.f1_score(y_test, y_predict, average='weighted'))

3、使用Word2Vec词向量的TextCNN

  1. w2v_model=Word2Vec.load('sentiment_analysis/w2v_model.pkl')
  2. # 预训练的词向量中没有出现的词用0向量表示
  3. embedding_matrix = np.zeros((len(vocab) + 1, 300))
  4. for word, i in vocab.items():
  5. try:
  6. embedding_vector = w2v_model[str(word)]
  7. embedding_matrix[i] = embedding_vector
  8. except KeyError:
  9. continue
  10. #构建TextCNN模型
  11. def TextCNN_model_2(x_train_padded_seqs,y_train,x_test_padded_seqs,y_test,embedding_matrix):
  12. # 模型结构:词嵌入-卷积池化*3-拼接-全连接-dropout-全连接
  13. main_input = Input(shape=(50,), dtype='float64')
  14. # 词嵌入(使用预训练的词向量)
  15. embedder = Embedding(len(vocab) + 1, 300, input_length=50, weights=[embedding_matrix], trainable=False)
  16. #embedder = Embedding(len(vocab) + 1, 300, input_length=50, trainable=False)
  17. embed = embedder(main_input)
  18. # 词窗大小分别为3,4,5
  19. cnn1 = Conv1D(256, 3, padding='same', strides=1, activation='relu')(embed)
  20. cnn1 = MaxPooling1D(pool_size=38)(cnn1)
  21. cnn2 = Conv1D(256, 4, padding='same', strides=1, activation='relu')(embed)
  22. cnn2 = MaxPooling1D(pool_size=37)(cnn2)
  23. cnn3 = Conv1D(256, 5, padding='same', strides=1, activation='relu')(embed)
  24. cnn3 = MaxPooling1D(pool_size=36)(cnn3)
  25. # 合并三个模型的输出向量
  26. cnn = concatenate([cnn1, cnn2, cnn3], axis=-1)
  27. flat = Flatten()(cnn)
  28. drop = Dropout(0.2)(flat)
  29. main_output = Dense(3, activation='softmax')(drop)
  30. model = Model(inputs=main_input, outputs=main_output)
  31. model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  32. one_hot_labels = keras.utils.to_categorical(y_train, num_classes=3) # 将标签转换为one-hot编码
  33. model.fit(x_train_padded_seqs, one_hot_labels, batch_size=800, epochs=20)
  34. #y_test_onehot = keras.utils.to_categorical(y_test, num_classes=3) # 将标签转换为one-hot编码
  35. result = model.predict(x_test_padded_seqs) # 预测样本属于每个类别的概率
  36. result_labels = np.argmax(result, axis=1) # 获得最大概率对应的标签
  37. y_predict = list(map(str, result_labels))
  38. print('准确率', metrics.accuracy_score(y_test, y_predict))
  39. print('平均f1-score:', metrics.f1_score(y_test, y_predict, average='weighted'))

四、绘制TextCNN模型结构图

使用keras的plot_model()画出的TextCNN模型结构图

1、环境配置

(1)安装graphviz模块
首先,命令行pip install graphviz;其次,安装graphviz软件,官网下载:graphviz-2.38.msi ;最后,将安装目录中的graphviz-2.38\release\bin添加进Path环境变量
(2)安装pydot模块
命令行pip install pydot
(3)在运行程序中加入下面两行代码

  1. import os
  2. os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'

2、绘制模型图

使用plot_model()画出模型图

  1. from keras.utils import plot_model
  2. #生成一个模型图,第一个参数为模型,第二个参数为要生成图片的路径及文件名,还可以指定两个参数:
  3. #show_shapes:指定是否显示输出数据的形状,默认为False
  4. #show_layer_names:指定是否显示层名称,默认为True
  5. plot_model(model,to_file='sentiment_analysis/model.png',show_shapes=True,show_layer_names=False)

image.png

五、keras模型的保存与加载

  1. from keras.models import load_model
  2. #模型的保存
  3. model.save('model.h5')
  4. #模型的加载
  5. model=load_model('model.h5')

参考学习资料:
(1)Keras之文本分类实现
(2)使用Keras进行深度学习
(3)NLP论文
(4)卷积神经网络(CNN)在句子建模上的应用
(5)用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践
(6)深度学习在文本分类中的应用
(7)深度学习与文本分类总结第一篇—常用模型总结
(8)基于 word2vec 和 CNN 的文本分类 :综述 & 实践