当前位置:   article > 正文

基于tensorflow+CNN的新闻文本分类

基于tensorflow的cnn新闻文本分类

2018年10月4日笔记

tensorflow是谷歌google的深度学习框架,tensor中文叫做张量,flow叫做流。
CNN是convolutional neural network的简称,中文叫做卷积神经网络
文本分类是NLP(自然语言处理)的经典任务。

0.编程环境

操作系统:Win10
tensorflow版本:1.6
tensorboard版本:1.6
python版本:3.6

1.致谢声明

本文是作者学习《使用卷积神经网络以及循环神经网络进行中文文本分类》的成果,感激前辈;
github链接:https://github.com/gaussic/text-classification-cnn-rnn

2.配置环境

使用卷积神经网络模型要求有较高的机器配置,如果使用CPU版tensorflow会花费大量时间。
读者在有nvidia显卡的情况下,安装GPU版tensorflow会提高计算速度50倍。
安装教程链接:https://blog.csdn.net/qq_36556893/article/details/79433298
如果没有nvidia显卡,但有visa信用卡,请阅读我的另一篇文章《在谷歌云服务器上搭建深度学习平台》,链接:https://www.jianshu.com/p/893d622d1b5a

3.下载并解压数据集

数据集下载链接: https://pan.baidu.com/s/1oLZZF4AHT5X_bzNl2aF2aQ 提取码: 5sea
下载压缩文件cnews.zip完成后,选择解压到cnews,如下图所示:

img_e6952a3ce715e43552896d3ae26a681d.png
image.png

文件夹结构如下图所示:
img_b601870eb5855cfebb68904ee2d356a2.png
image.png

cnew文件夹中有4个文件:
1.训练集文件cnews.train.txt
2.测试集文件cnew.test.txt
3.验证集文件cnews.val.txt
4.词汇表文件cnews.vocab.txt
共有10个类别,65000个样本数据,其中训练集50000条,测试集10000条,验证集5000条。

4.完整代码

代码文件需要放到和cnews文件夹同级目录
给读者提供完整代码,旨在读者能够直接运行代码,有直观的感性认识。
如果要理解其中代码的细节,请阅读后面的章节

  1. with open('./cnews/cnews.train.txt', encoding='utf8') as file:
  2. line_list = [k.strip() for k in file.readlines()]
  3. train_label_list = [k.split()[0] for k in line_list]
  4. train_content_list = [k.split(maxsplit=1)[1] for k in line_list]
  5. with open('./cnews/cnews.vocab.txt', encoding='utf8') as file:
  6. vocabulary_list = [k.strip() for k in file.readlines()]
  7. word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
  8. content2idList = lambda content : [word2id_dict[word] for word in content if word in word2id_dict]
  9. train_idlist_list = [content2idList(content) for content in train_content_list]
  10. vocab_size = 5000 # 词汇表大小
  11. seq_length = 600 # 序列长度
  12. embedding_dim = 64 # 词向量维度
  13. num_classes = 10 # 类别数
  14. num_filters = 256 # 卷积核数目
  15. kernel_size = 5 # 卷积核尺寸
  16. hidden_dim = 128 # 全连接层神经元
  17. dropout_keep_prob = 0.5 # dropout保留比例
  18. learning_rate = 1e-3 # 学习率
  19. batch_size = 64 # 每批训练大小
  20. import tensorflow.contrib.keras as kr
  21. train_X = kr.preprocessing.sequence.pad_sequences(train_idlist_list, seq_length)
  22. from sklearn.preprocessing import LabelEncoder
  23. labelEncoder = LabelEncoder()
  24. train_y = labelEncoder.fit_transform(train_label_list)
  25. train_Y = kr.utils.to_categorical(train_y, num_classes)
  26. import tensorflow as tf
  27. tf.reset_default_graph()
  28. X_holder = tf.placeholder(tf.int32, [None, seq_length])
  29. Y_holder = tf.placeholder(tf.float32, [None, num_classes])
  30. embedding = tf.get_variable('embedding', [vocab_size, embedding_dim])
  31. embedding_inputs = tf.nn.embedding_lookup(embedding, X_holder)
  32. conv = tf.layers.conv1d(embedding_inputs, num_filters, kernel_size)
  33. max_pooling = tf.reduce_max(conv, reduction_indices=[1])
  34. full_connect = tf.layers.dense(max_pooling, hidden_dim)
  35. full_connect_dropout = tf.contrib.layers.dropout(full_connect, keep_prob=0.75)
  36. full_connect_activate = tf.nn.relu(full_connect_dropout)
  37. softmax_before = tf.layers.dense(full_connect_activate, num_classes)
  38. predict_Y = tf.nn.softmax(softmax_before)
  39. cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(labels=Y_holder, logits=softmax_before)
  40. loss = tf.reduce_mean(cross_entropy)
  41. optimizer = tf.train.AdamOptimizer(learning_rate)
  42. train = optimizer.minimize(loss)
  43. isCorrect = tf.equal(tf.argmax(Y_holder, 1), tf.argmax(predict_Y, 1))
  44. accuracy = tf.reduce_mean(tf.cast(isCorrect, tf.float32))
  45. init = tf.global_variables_initializer()
  46. session = tf.Session()
  47. session.run(init)
  48. with open('./cnews/cnews.test.txt', encoding='utf8') as file:
  49. line_list = [k.strip() for k in file.readlines()]
  50. test_label_list = [k.split()[0] for k in line_list]
  51. test_content_list = [k.split(maxsplit=1)[1] for k in line_list]
  52. test_idlist_list = [content2idList(content) for content in test_content_list]
  53. test_X = kr.preprocessing.sequence.pad_sequences(test_idlist_list, seq_length)
  54. test_y = labelEncoder.transform(test_label_list)
  55. test_Y = kr.utils.to_categorical(test_y, num_classes)
  56. import random
  57. for i in range(3000):
  58. selected_index = random.sample(list(range(len(train_y))), k=batch_size)
  59. batch_X = train_X[selected_index]
  60. batch_Y = train_Y[selected_index]
  61. session.run(train, {X_holder:batch_X, Y_holder:batch_Y})
  62. step = i + 1
  63. if step % 100 == 0:
  64. selected_index = random.sample(list(range(len(test_y))), k=200)
  65. batch_X = test_X[selected_index]
  66. batch_Y = test_Y[selected_index]
  67. loss_value, accuracy_value = session.run([loss, accuracy], {X_holder:batch_X, Y_holder:batch_Y})
  68. print('step:%d loss:%.4f accuracy:%.4f' %(step, loss_value, accuracy_value))

上面一段代码的运行结果如下(只截取前十行):

step:100 loss:0.8617 accuracy:0.7500
step:200 loss:0.4324 accuracy:0.8700
step:300 loss:0.2810 accuracy:0.9150
step:400 loss:0.1910 accuracy:0.9500
step:500 loss:0.2348 accuracy:0.9350
step:600 loss:0.2127 accuracy:0.9300
step:700 loss:0.2282 accuracy:0.9400
step:800 loss:0.1539 accuracy:0.9550
step:900 loss:0.1268 accuracy:0.9750
step:1000 loss:0.1339 accuracy:0.9600

5.数据准备

第1-4行代码是加载训练集的数据;
第1行代码调用open方法打开文本文件
第2行代码使用列表推导式得到文本文件中的行内容列表赋值给变量label_list;
第3行代码得到训练集的标签列表赋值给变量train_label_list;
第4行代码得到训练集的内容列表赋值给变量train_content_list。
第5-6行代码得到词汇表文件cnews.vocab.txt中的词汇列表赋值给变量vocabulary_list;
第7行代码使用列表推导式得到词汇及其id对应的列表,并调用dict方法将列表强制转换为字典。
打印变量word2id_dict的前5项,如下图所示:

img_12cadc72b3fe47254f5a46ee73a44928.png
image.png

第8行代码使用列表推导式定义函数content2vec函数,函数作用是将文章中的每个字转换为id;
第9行代码使用列表推导式得到的结果是 列表的列表,总列表train_idlist_list中的元素是每篇文章中的字对应的id列表;
第10-19这10行代码设置卷积神经网络的超参数;
第20-25这6行代码获得能够用于模型训练的 特征矩阵预测目标值
第20行代码导入tensorflow.contrib.keras库,取别名kr;
第21行代码将每个样本统一长度为seq_length,即600;
第22行代码导入sklearn.preprocessing库的labelEncoder方法;
第23行代码实例化LabelEncoder对象;
第24行代码调用LabelEncoder对象的fit_transform方法做标签编码;
第25行代码调用keras.untils库的to_categorical方法将标签编码的结果再做Ont-Hot编码。
第26行代码导入tensorflow库,取别名tf;
第27行代码重置tensorflow图,加强代码的健壮性;
第28、29行代码中placeholder中文叫做占位符,将每次训练的特征矩阵X和预测目标值Y赋值给变量X_holder和Y_holder。

  1. with open('./cnews/cnews.train.txt', encoding='utf8') as file:
  2. line_list = [k.strip() for k in file.readlines()]
  3. train_label_list = [k.split()[0] for k in line_list]
  4. train_content_list = [k.split(maxsplit=1)[1] for k in line_list]
  5. with open('./cnews/cnews.vocab.txt', encoding='utf8') as file:
  6. vocabulary_list = [k.strip() for k in file.readlines()]
  7. word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
  8. content2idList = lambda content : [word2id_dict[word] for word in content if word in word2id_dict]
  9. train_idlist_list = [content2idList(content) for content in train_content_list]
  10. vocab_size = 5000 # 词汇表大小
  11. seq_length = 600 # 序列长度
  12. embedding_dim = 64 # 词向量维度
  13. num_classes = 10 # 类别数
  14. num_filters = 256 # 卷积核数目
  15. kernel_size = 5 # 卷积核尺寸
  16. hidden_dim = 128 # 全连接层神经元
  17. dropout_keep_prob = 0.5 # dropout保留比例
  18. learning_rate = 1e-3 # 学习率
  19. batch_size = 64 # 每批训练大小
  20. import tensorflow.contrib.keras as kr
  21. train_X = kr.preprocessing.sequence.pad_sequences(train_idlist_list, seq_length)
  22. from sklearn.preprocessing import LabelEncoder
  23. labelEncoder = LabelEncoder()
  24. train_y = labelEncoder.fit_transform(train_label_list)
  25. train_Y = kr.utils.to_categorical(train_y, num_classes)
  26. import tensorflow as tf
  27. tf.reset_default_graph()
  28. X_holder = tf.placeholder(tf.int32, [None, seq_length])
  29. Y_holder = tf.placeholder(tf.float32, [None, num_classes])

6.搭建神经网络

第1行代码调用tf库的get_variable方法实例化可以更新的模型参数embedding,矩阵形状为5000*64
第2行代码调用tf.nn库的embedding_lookup方法将输入数据做词嵌入,得到新变量embedding_inputs的形状为batch_size*600*64
理解word2vec原理,推荐阅读文章链接:https://www.jianshu.com/p/471d9bfbd72f
第3行代码调用tf.layers.conv1d方法,方法需要3个参数,第1个参数是输入数据,第2个参数是卷积核数量num_filters,第3个参数是卷积核大小kernel_size。方法结果赋值给变量conv,形状为batch_size*596*num_filters,596是600-5+1的结果;
第4行代码调用tf.reduce_max方法对变量conv的第1个维度做求最大值操作。方法结果赋值给变量max_pooling,形状为batch_size*256
第5行代码添加全连接层,tf.layers.dense方法结果赋值给变量full_connect,形状为batch_size*128
第6行代码调用tf.contrib.layers.dropout方法,方法需要2个参数,第1个参数是输入数据,第2个参数是保留比例;
第7行代码调用tf.nn.relu方法,即激活函数;
第8行代码添加全连接层,tf.layers.dense方法结果赋值给变量softmax_before,形状为batch_size*num_classes
第9行代码调用tf.nn.softmax方法,方法结果是预测概率值;
第10、11行代码使用交叉熵作为损失函数;
第12行代码调用tf.train.Optimizer方法定义优化器optimizer;
第13行代码调用优化器对象的minimize方法,即最小化损失;
第14、15行代码计算预测准确率;

  1. embedding = tf.get_variable('embedding', [vocab_size, embedding_dim])
  2. embedding_inputs = tf.nn.embedding_lookup(embedding, X_holder)
  3. conv = tf.layers.conv1d(embedding_inputs, num_filters, kernel_size)
  4. max_pooling = tf.reduce_max(conv, reduction_indices=[1])
  5. full_connect = tf.layers.dense(max_pooling, hidden_dim)
  6. full_connect_dropout = tf.contrib.layers.dropout(full_connect, keep_prob=0.75)
  7. full_connect_activate = tf.nn.relu(full_connect_dropout)
  8. softmax_before = tf.layers.dense(full_connect_activate, num_classes)
  9. predict_Y = tf.nn.softmax(softmax_before)
  10. cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(labels=Y_holder, logits=softmax_before)
  11. loss = tf.reduce_mean(cross_entropy)
  12. optimizer = tf.train.AdamOptimizer(learning_rate)
  13. train = optimizer.minimize(loss)
  14. isCorrect = tf.equal(tf.argmax(Y_holder, 1), tf.argmax(predict_Y, 1))
  15. accuracy = tf.reduce_mean(tf.cast(isCorrect, tf.float32))

7.参数初始化

对于神经网络模型,重要是其中的W、b这两个参数。
开始神经网络模型训练之前,这两个变量需要初始化。
第1行代码调用tf.global_variables_initializer实例化tensorflow中的Operation对象。


img_eba6278c89eb8ef15ee4daee0eaab711.png
image.png

第2行代码调用tf.Session方法实例化会话对象;
第3行代码调用tf.Session对象的run方法做变量初始化。

  1. init = tf.global_variables_initializer()
  2. session = tf.Session()
  3. session.run(init)

8.模型训练

第1-8行代码获取文本文件cnews.test.txt,即测试集中的数据;
第9行代码导入random库;
第10行表示模型迭代训练3000次;
第11-13行代码从训练集中选取batch_size大小,即64个样本做批量梯度下降;
第14行代码每运行1次,表示模型训练1次;
第15行代码记录当前步数,赋值给变量step;
第16行代码表示每间隔100步打印;
第17-19行代码从测试集中随机选取200个样本;
第20行代码表示计算损失值loss_value、准确率accuracy_value;
第21行代码表示打印步数step、损失值loss_value、准确率accuracy_value。

  1. with open('./cnews/cnews.test.txt', encoding='utf8') as file:
  2. line_list = [k.strip() for k in file.readlines()]
  3. test_label_list = [k.split()[0] for k in line_list]
  4. test_content_list = [k.split(maxsplit=1)[1] for k in line_list]
  5. test_idlist_list = [content2idList(content) for content in test_content_list]
  6. test_X = kr.preprocessing.sequence.pad_sequences(test_idlist_list, seq_length)
  7. test_y = labelEncoder.transform(test_label_list)
  8. test_Y = kr.utils.to_categorical(test_y, num_classes)
  9. import random
  10. for i in range(3000):
  11. selected_index = random.sample(list(range(len(train_y))), k=batch_size)
  12. batch_X = train_X[selected_index]
  13. batch_Y = train_Y[selected_index]
  14. session.run(train, {X_holder:batch_X, Y_holder:batch_Y})
  15. step = i + 1
  16. if step % 100 == 0:
  17. selected_index = random.sample(list(range(len(test_y))), k=200)
  18. batch_X = test_X[selected_index]
  19. batch_Y = test_Y[selected_index]
  20. loss_value, accuracy_value = session.run([loss, accuracy], {X_holder:batch_X, Y_holder:batch_Y})
  21. print('step:%d loss:%.4f accuracy:%.4f' %(step, loss_value, accuracy_value))

9.词汇表

经过前文5-8章的讲解,已经完成卷积神经网络的训练。
本项目提供词汇表文件cnews.vocab.txt,但在实践中需要自己统计语料的词汇表。
下面代码可以由内容列表content_list产生词汇表:

  1. from collections import Counter
  2. def getVocabularyList(content_list, vocabulary_size):
  3. allContent_str = ''.join(content_list)
  4. counter = Counter(allContent_str)
  5. vocabulary_list = [k[0] for k in counter.most_common(vocabulary_size)]
  6. return vocabulary_list
  7. def makeVocabularyFile(content_list, vocabulary_size):
  8. vocabulary_list = getVocabularyList(content_list, vocabulary_size)
  9. with open('vocabulary.txt', 'w', encoding='utf8') as file:
  10. for vocabulary in vocabulary_list:
  11. file.write(vocabulary + '\n')
  12. makeVocabularyFile(train_content_list, 5000)

本段代码产生的文件,与提供的词汇表文件cnews.vocab.txt稍有不同。
造成原因有2点:
1.词汇表文件的第1个字<PAD>是源代码作者的特殊设计,本文作者没有体会到实际用处;
2.源代码作者使用了训练集、验证集、测试集作为总语料库,上面一段代码只使用了训练集作为语料库。

10.混淆矩阵

  1. import numpy as np
  2. import pandas as pd
  3. from sklearn.metrics import confusion_matrix
  4. def predictAll(test_X, batch_size=100):
  5. predict_value_list = []
  6. for i in range(0, len(test_X), batch_size):
  7. selected_X = test_X[i: i + batch_size]
  8. predict_value = session.run(predict_Y, {X_holder:selected_X})
  9. predict_value_list.extend(predict_value)
  10. return np.array(predict_value_list)
  11. Y = predictAll(test_X)
  12. y = np.argmax(Y, axis=1)
  13. predict_label_list = labelEncoder.inverse_transform(y)
  14. pd.DataFrame(confusion_matrix(test_label_list, predict_label_list),
  15. columns=labelEncoder.classes_,
  16. index=labelEncoder.classes_ )

上面一段代码的运行结果如下图所示:


img_d888102df788b1c7852a87ba470fb7a6.png
image.png

从上图的结果可以看出,家居类新闻分类效果较差。

11.报告表

下面一段代码能够成功运行的前提是已经运行第10章代码

  1. import numpy as np
  2. from sklearn.metrics import precision_recall_fscore_support
  3. def eval_model(y_true, y_pred, labels):
  4. # 计算每个分类的Precision, Recall, f1, support
  5. p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
  6. # 计算总体的平均Precision, Recall, f1, support
  7. tot_p = np.average(p, weights=s)
  8. tot_r = np.average(r, weights=s)
  9. tot_f1 = np.average(f1, weights=s)
  10. tot_s = np.sum(s)
  11. res1 = pd.DataFrame({
  12. u'Label': labels,
  13. u'Precision': p,
  14. u'Recall': r,
  15. u'F1': f1,
  16. u'Support': s
  17. })
  18. res2 = pd.DataFrame({
  19. u'Label': ['总体'],
  20. u'Precision': [tot_p],
  21. u'Recall': [tot_r],
  22. u'F1': [tot_f1],
  23. u'Support': [tot_s]
  24. })
  25. res2.index = [999]
  26. res = pd.concat([res1, res2])
  27. return res[['Label', 'Precision', 'Recall', 'F1', 'Support']]
  28. eval_model(test_label_list, predict_label_list, labelEncoder.classes_)

上面一段代码的运行结果如下图所示:


img_eae6726f528a4852df2434e213ca5810.png
image.png

12.总结

本文是作者第4个NLP小型项目,数据共有65000条。
分类模型的评估指标为0.96左右,总体来说这个分类模型较优秀,能够投入实际应用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/123649
推荐阅读
相关标签
  

闽ICP备14008679号