写点什么

文本分类:Keras+RNN vs 传统机器学习

  • 2021 年 11 月 30 日
  • 本文字数:26811 字

    阅读完需:约 88 分钟

摘要:本文通过 Keras 实现了一个 RNN 文本分类学习的案例,并详细介绍了循环神经网络原理知识及与机器学习对比。

 

本文分享自华为云社区《基于Keras+RNN的文本分类vs基于传统机器学习的文本分类》,作者: eastmount 。

一.RNN 文本分类

1.RNN


循环神经网络英文是 Recurrent Neural Networks,简称 RNN。RNN 的本质概念是利用时序信息,在传统神经网络中,假设所有的输入(以及输出)都各自独立。但是,对于很多任务而言,这非常局限。举个例子,假如你想根据一句没说完的话,预测下一个单词,最好的办法就是联系上下文的信息。而 RNN(循环神经网络)之所以是“循环”,是因为它们对序列的每个元素执行相同的任务,而每次的结果都独立于之前的计算。


假设有一组数据 data0、data1、data2、data3,使用同一个神经网络预测它们,得到对应的结果。如果数据之间是有关系的,比如做菜下料的前后步骤,英文单词的顺序,如何让数据之间的关联也被神经网络学习呢?这就要用到——RNN。


比如存在 ABCD 数字,需要预测下一个数字 E,会根据前面 ABCD 顺序进行预测,这就称为记忆。预测之前,需要回顾以前的记忆有哪些,再加上这一步新的记忆点,最终输出 output,循环神经网络(RNN)就利用了这样的原理。



​首先,让我们想想人类是怎么分析事物之间的关联或顺序的。人类通常记住之前发生的事情,从而帮助我们后续的行为判断,那么是否能让计算机也记住之前发生的事情呢?


在分析 data0 时,我们把分析结果存入记忆 Memory 中,然后当分析 data1 时,神经网络(NN)会产生新的记忆,但此时新的记忆和老的记忆没有关联,如上图所示。在 RNN 中,我们会简单的把老记忆调用过来分析新记忆,如果继续分析更多的数据时,NN 就会把之前的记忆全部累积起来



下面是一个典型的 RNN 结果模型,按照时间点 t-1、t、t+1,每个时刻有不同的 x,每次计算会考虑上一步的 state 和这一步的 x(t),再输出 y 值。在该数学形式中,每次 RNN 运行完之后都会产生 s(t),当 RNN 要分析 x(t+1)时,此刻的 y(t+1)是由 s(t)和 s(t+1)共同创造的,s(t)可看作上一步的记忆。多个神经网络 NN 的累积就转换成了循环神经网络,其简化图如下图的左边所示。例如,如果序列中的句子有 5 个单词,那么,横向展开网络后将有五层神经网络,一层对应一个单词。



总之,只要你的数据是有顺序的,就可以使用 RNN,比如人类说话的顺序,电话号码的顺序,图像像素排列的顺序,ABC 字母的顺序等。RNN 常用于自然语言处理、机器翻译、语音识别、图像识别等领域。

2.文本分类


文本分类旨在对文本集按照一定的分类体系或标准进行自动分类标记,属于一种基于分类体系的自动分类。文本分类最早可以追溯到上世纪 50 年代,那时主要通过专家定义规则来进行文本分类;80 年代出现了利用知识工程建立的专家系统;90 年代开始借助于机器学习方法,通过人工特征工程和浅层分类模型来进行文本分类。现在多采用词向量以及深度神经网络来进行文本分类。



牛亚峰老师将传统的文本分类流程归纳如下图所示。在传统的文本分类中,基本上大部分机器学习方法都在文本分类领域有所应用。主要包括:

  • Naive Bayes

  • KNN

  • SVM

  • 集合类方法

  • 最大熵

  • 神经网络



利用 Keras 框架进行文本分类的基本流程如下:

  • 步骤 1:文本的预处理,分词->去除停用词->统计选择 top n 的词做为特征词

  • 步骤 2:为每个特征词生成 ID

  • 步骤 3:将文本转化成 ID 序列,并将左侧补齐

  • 步骤 4:训练集 shuffle

  • 步骤 5:Embedding Layer 将词转化为词向量

  • 步骤 6:添加模型,构建神经网络结构

  • 步骤 7:训练模型

  • 步骤 8:得到准确率、召回率、F1 值


注意,如果使用 TFIDF 而非词向量进行文档表示,则直接分词去停后生成 TFIDF 矩阵后输入模型。本文将采用词向量、TFIDF 两种方式进行实验。


深度学习文本分类方法包括:

  • 卷积神经网络(TextCNN)

  • 循环神经网络(TextRNN)

  • TextRNN+Attention

  • TextRCNN(TextRNN+CNN)


推荐牛亚峰老师的文章:基于 word2vec 和 CNN 的文本分类 :综述 & 实践

二.基于传统机器学习贝叶斯算法的文本分类

1.MultinomialNB+TFIDF 文本分类


数据集采用基基伟老师的自定义文本,共 21 行数据,包括 2 类(小米手机、小米粥)。其基本流程是:

  • 获取数据集 data 和 target

  • 调用 Jieba 库实现中文分词

  • 计算 TF-IDF 值,将词频矩阵转换为 TF-IDF 向量矩阵

  • 调用机器学习算法进行训练和预测

  • 实验评估及可视化分析


完整代码如下:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcut
#--------------------------------载入数据及预处理-------------------------------data = [ [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'], [0, '煮粥时一定要先烧开水然后放入洗净后的小米'], [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'], [0, '小米是传统健康食品,可单独焖饭和熬粥'], [0, '苹果,是水果中的一种'], [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'], [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'], [0, '这家超市的苹果都非常新鲜'], [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'], [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'], [0, '蛋白质及氨基酸、脂肪、维生素、盐分'], [1, '小米、三星、华为,作为安卓三大手机旗舰'], [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'], [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'], [1, '三星想要继续压制华为,仅凭A70还不够'], [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'], [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'], [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上线,适配深度系统'], [1, '三星刚刚更新了自家的可穿戴设备APP'], [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分析X, Y = [' '.join(lcut(i[1])) for i in data], [i[0] for i in data]print(X)print(Y)#['煮粥 时 一定 要 先烧 开水 然后 放入 洗净 后 的 小米', ...]
#--------------------------------------计算词频------------------------------------from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformer
#将文本中的词语转换为词频矩阵vectorizer = CountVectorizer()
#计算个词语出现的次数X_data = vectorizer.fit_transform(X)print(X_data)
#获取词袋中所有文本关键词word = vectorizer.get_feature_names()print('【查看单词】')for w in word: print(w, end = " ")else: print("\n")
#词频矩阵print(X_data.toarray())
#将词频矩阵X统计成TF-IDF值transformer = TfidfTransformer()tfidf = transformer.fit_transform(X_data)
#查看数据结构 tfidf[i][j]表示i类文本中的tf-idf权重weight = tfidf.toarray()print(weight)
#--------------------------------------数据分析------------------------------------from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import classification_reportfrom sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(weight, Y)print(len(X_train), len(X_test))print(len(y_train), len(y_test))print(X_train)
#调用MultinomialNB分类器 clf = MultinomialNB().fit(X_train, y_train)pre = clf.predict(X_test)print("预测结果:", pre)print("真实结果:", y_test)print(classification_report(y_test, pre))
#--------------------------------------可视化分析------------------------------------#降维绘制图形from sklearn.decomposition import PCAimport matplotlib.pyplot as plt
pca = PCA(n_components=2)newData = pca.fit_transform(weight)print(newData) L1 = [n[0] for n in newData]L2 = [n[1] for n in newData]plt.scatter(L1, L2, c=Y, s=200)plt.show()
复制代码


输出结果如下所示:

  • 6 个预测数据的 accuracy ===> 0.67


['小米粥 是 以 小米 作为 主要 食材 熬 制而成 的 粥 , 口味 清淡 , 清香味 , 具有 简单 易制 , 健胃 消食 的 特点',  '煮粥 时 一定 要 先烧 开水 然后 放入 洗净 后 的 小米',  '蛋白质 及 氨基酸 、 脂肪 、 维生素 、 矿物质',  ... '三星 刚刚 更新 了 自家 的 可 穿戴 设备 APP',  '华为 、 小米 跨界 并 不 可怕 , 可怕 的 打 不破 内心 的 “ 天花板 ”'][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 【查看单词】2016 app linux p30 一定 一张 一种 三星 健康 ... 雷军 食品 食材 食物 魅族 鸡蛋 
[[0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] ... [0 0 1 ... 0 0 0] [0 0 1 ... 0 0 0] [0 0 0 ... 0 0 0]] 15 615 6[[0. 0. 0. ... 0. 0. 0. ] [0. 0. 0. ... 0. 0. 0.32161043] [0. 0. 0. ... 0. 0. 0. ] ... [0. 0.31077094 0. ... 0. 0. 0. ] [0. 0. 0. ... 0. 0. 0. ] [0.35882035 0. 0. ... 0. 0. 0. ]]预测结果: [0 1 0 1 1 1]真实结果: [0, 0, 0, 0, 1, 1]
precision recall f1-score support
0 1.00 0.50 0.67 4 1 0.50 1.00 0.67 2
accuracy 0.67 6 macro avg 0.75 0.75 0.67 6weighted avg 0.83 0.67 0.67 6
复制代码


绘制图形如下图所示:



2.GaussianNB+Word2Vec 文本分类


该方法与前面不同之处是采用 Word2Vec 进行词向量计算,将每行数据集分词,并计算每个特征词的词向量,接着转换为词向量矩阵,比如 15 行数据,每行数据 40 个特征词,每个特征词用 20 维度的词向量表示,即(15, 40, 20)。同时,由于词向量存在负数,所以需要使用 GaussianNB 算法替代 MultinomialNB 算法。


Word2Vec 详见作者前文:[Python人工智能] 九.gensim词向量Word2Vec安装及《庆余年》中文短文本相似度计算

  • sentences:传入的数据集序列(list of lists of tokens),默认值为 None

  • size:词向量维数,默认值为 100

  • window:同句中当前词和预测词的最大距离,默认值为 5

  • min_count:最低词频过滤,默认值为 5

  • workers:线程数,默认值为 3

  • sg:模型参数,其值为 0 表示 CBOW,值为 1 表示 skip-gram,默认值为 0

  • hs:模型参数,其值为 0 表示负例采样,值为 1 表示层次 softmax,默认值为 0

  • negative:负例样本数,默认值为 5

  • ns_exponent:用于形成负例样本的指数,默认值为 0.75

  • cbow_mean:上下文词向量参数,其值为 0 表示上下文词向量求和值,值为 1 表示上下文词向量平均值,默认值为 1

  • alpha:初始学习率,默认值为 0.025

  • min_alpha:最小学习率,默认值为 0.0001


完整代码如下:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequences
max_features = 20 #词向量维度maxlen = 40 #序列最大长度
#--------------------------------载入数据及预处理-------------------------------data = [ [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'], [0, '煮粥时一定要先烧开水然后放入洗净后的小米'], [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'], [0, '小米是传统健康食品,可单独焖饭和熬粥'], [0, '苹果,是水果中的一种'], [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'], [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'], [0, '这家超市的苹果都非常新鲜'], [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'], [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'], [0, '蛋白质及氨基酸、脂肪、维生素、盐分'], [1, '小米、三星、华为,作为安卓三大手机旗舰'], [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'], [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'], [1, '三星想要继续压制华为,仅凭A70还不够'], [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'], [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'], [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上线,适配深度系统'], [1, '三星刚刚更新了自家的可穿戴设备APP'], [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#划分训练集和预测集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '刚刚', '更新', '了', '自家', '的', '可', '穿戴', '设备', 'APP']"""
#--------------------------------Word2Vec词向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低过滤频次1print(word2vec)
#映射特征词w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【显示词语】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '维生素', '蛋白质', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '华为': 4, ....}"""
#词向量计算vectors = word2vec.wv.vectorsprint("【词向量矩阵】")print(vectors.shape)print(vectors)
#自定义函数-获取词向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定义函数-序列预处理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化处理 转换为词向量X_train, X_test = pad(X_train), pad(X_test)print(X_train.shape)print(X_test.shape)"""(15, 40, 20) 15个样本 40个特征 每个特征用20词向量表示"""
#拉直形状 (15, 40, 20)=>(15, 40*20) (6, 40, 20)=>(6, 40*20)X_train = X_train.reshape(len(y_train), maxlen*max_features)X_test = X_test.reshape(len(y_test), maxlen*max_features)print(X_train.shape)print(X_test.shape)
#--------------------------------建模与训练-------------------------------from sklearn.naive_bayes import GaussianNBfrom sklearn.metrics import classification_reportfrom sklearn.model_selection import train_test_split
#调用GaussianNB分类器 clf = GaussianNB().fit(X_train, y_train)pre = clf.predict(X_test)print("预测结果:", pre)print("真实结果:", y_test)print(classification_report(y_test, pre))
复制代码


输出结果如下所示:

  • 6 个预测数据的 accuracy ===> 0.83


15 615 6Word2Vec(vocab=126, size=20, alpha=0.025)
【显示词语】[',', '、', '小米', '的', '华为', '手机', '苹果', '维生素', 'APP', '官方', 'Linux', ... '安卓三大', '旗舰']{',': 0, '、': 1, '小米': 2, '的': 3, '华为': 4, '手机': 5, '苹果': 6, ..., '安卓三大': 124, '旗舰': 125}
【词向量矩阵】(126, 20)[[ 0.02041552 -0.00929706 -0.00743623 ... -0.00246041 -0.00825108 0.02341811] [-0.00256093 -0.01301112 -0.00697959 ... -0.00449076 -0.00551124 -0.00240511] [ 0.01535473 0.01690796 -0.00262145 ... -0.01624218 0.00871249 -0.01159615] ... [ 0.00631155 0.00369085 -0.00382834 ... 0.02468265 0.00945442 -0.0155745 ] [-0.01198495 0.01711261 0.01097644 ... 0.01003117 0.01074963 0.01960118] [ 0.00450704 -0.01114052 0.0186879 ... 0.00804681 0.01060277 0.01836049]] (15, 40, 20)(6, 40, 20)(15, 800)(6, 800)
预测结果: [1 1 1 0 1 0]真实结果: [0, 1, 1, 0, 1, 0] precision recall f1-score support
0 1.00 0.67 0.80 3 1 0.75 1.00 0.86 3
accuracy 0.83 6 macro avg 0.88 0.83 0.83 6weighted avg 0.88 0.83 0.83 6
复制代码


三.Keras 实现 RNN 文本分类

1.IMDB 数据集和序列预处理


(1) IMDB 数据集 Keras 框架为我们提供了一些常用的内置数据集。比如,图像识别领域的手写识别 MNIST 数据集、文本分类领域的电影影评 imdb 数据集等等。这些数据库可以用一条代码就可以调用:

  • (trainX, trainY), (testX, testY) = imdb.load_data(path=“imdb.npz”, num_words=max_features)

这些数据集是通过https://s3.amazonaws.com进行下载的,但有时该网站不能使用,需要下载数据至本地,再进行调用分析。Keras 数据集百度云链接:


作者将下载后的数据放在 C:\Users\Administrator.keras\datasets 文件夹下,如下图所示。



该数据集是互联网电影资料库(Internet Movie Database,简称 IMDb),它是一个关于电影演员、电影、电视节目、电视明星和电影制作的在线数据库。


imdb.npz 文件中数据和格式如下:


[list([1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, ...]) list([1, 194, 1153, 194, 8255, 78, 228, 5, 6, 1463, 4369, 5012, 134, 26, 4, 715, 8, 118, 1634, 14, 394, 20, 13, 119, 954, 189, 102, 5, 207, 110, 3103, 21, 14, 69, ...]) list([1, 14, 47, 8, 30, 31, 7, 4, 249, 108, 7, 4, 5974, 54, 61, 369, 13, 71, 149, 14, 22, 112, 4, 2401, 311, 12, 16, 3711, 33, 75, 43, 1829, 296, 4, 86, 320, 35, ...]) ... list([1, 11, 6, 230, 245, 6401, 9, 6, 1225, 446, 2, 45, 2174, 84, 8322, 4007, 21, 4, 912, 84, 14532, 325, 725, 134, 15271, 1715, 84, 5, 36, 28, 57, 1099, 21, 8, 140, ...]) list([1, 1446, 7079, 69, 72, 3305, 13, 610, 930, 8, 12, 582, 23, 5, 16, 484, 685, 54, 349, 11, 4120, 2959, 45, 58, 1466, 13, 197, 12, 16, 43, 23, 2, 5, 62, 30, 145, ...]) list([1, 17, 6, 194, 337, 7, 4, 204, 22, 45, 254, 8, 106, 14, 123, 4, 12815, 270, 14437, 5, 16923, 12255, 732, 2098, 101, 405, 39, 14, 1034, 4, 1310, 9, 115, 50, 305, ...])] train sequences
复制代码


​每个 list 是一个句子,句子中每个数字表示单词的编号。那么,怎么获取编号对应的单词?此时需要使用 imdb_word_index.json 文件,其文件格式如下:


{"fawn": 34701, "tsukino": 52006,..., "paget": 18509, "expands": 20597}
复制代码


​共有 88584 个单词,采用 key-value 格式存放,key 代表单词,value 代表(单词)编号。词频(单词在语料中出现次数)越高编号越小,例如, “the:1”出现次数最高,编号为 1。


(2) 序列预处理在进行深度学习向量转换过程中,通常需要使用 pad_sequences()序列填充。其基本用法如下:


keras.preprocessing.sequence.pad_sequences(    sequences, 	maxlen=None,	dtype='int32',	padding='pre',	truncating='pre', 	value=0.	)
复制代码


参数含义如下:

  • sequences:浮点数或整数构成的两层嵌套列表

  • maxlen:None 或整数,为序列的最大长度。大于此长度的序列将被截短,小于此长度的序列将在后部填 0

  • dtype:返回的 numpy array 的数据类型

  • padding:pre 或 post,确定当需要补 0 时,在序列的起始还是结尾补 0

  • truncating:pre 或 post,确定当需要截断序列时,从起始还是结尾截断

  • value:浮点数,此值将在填充时代替默认的填充值 0

  • 返回值是个 2 维张量,长度为 maxlen


基本用法如下所示:


from keras.preprocessing.sequence import pad_sequences
print(pad_sequences([[1, 2, 3], [1]], maxlen=2))"""[[2 3] [0 1]]"""print(pad_sequences([[1, 2, 3], [1]], maxlen=3, value=9))"""[[1 2 3] [9 9 1]]"""
print(pad_sequences([[2,3,4]], maxlen=10))"""[[0 0 0 0 0 0 0 2 3 4]]"""print(pad_sequences([[1,2,3,4,5],[6,7]], maxlen=10))"""[[0 0 0 0 0 1 2 3 4 5] [0 0 0 0 0 0 0 0 6 7]]"""
print(pad_sequences([[1, 2, 3], [1]], maxlen=2, padding='post'))"""结束位置补: [[2 3] [1 0]]"""print(pad_sequences([[1, 2, 3], [1]], maxlen=4, truncating='post'))"""起始位置补: [[0 1 2 3] [0 0 0 1]]"""
复制代码


​在自然语言中一般和分词器一起使用。


>>> tokenizer.texts_to_sequences(["下 雨 我 加班"])[[4, 5, 6, 7]]
>>> keras.preprocessing.sequence.pad_sequences(tokenizer.texts_to_sequences(["下 雨 我 加班"]), maxlen=20)array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7]],dtype=int32)
复制代码


2.词嵌入模型训练


此时我们将通过词嵌入模型进行训练,具体流程包括:

  • 导入 IMDB 数据集

  • 数据集转换为序列

  • 创建 Embedding 词嵌入模型

  • 神经网络训练


完整代码如下:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 17:08:28 2020@author: Eastmount CSDN"""from keras.datasets import imdb  #Movie Databasefrom keras.preprocessing import sequencefrom keras.models import Sequentialfrom keras.layers import Dense, Flatten, Embedding
#-----------------------------------定义参数-----------------------------------max_features = 20000 #按词频大小取样本前20000个词input_dim = max_features #词库大小 必须>=max_featuresmaxlen = 80 #句子最大长度batch_size = 128 #batch数量output_dim = 40 #词向量维度epochs = 2 #训练批次
#--------------------------------载入数据及预处理-------------------------------#数据获取(trainX, trainY), (testX, testY) = imdb.load_data(path="imdb.npz", num_words=max_features) print(trainX.shape, trainY.shape) #(25000,) (25000,)print(testX.shape, testY.shape) #(25000,) (25000,)
#序列截断或补齐为等长trainX = sequence.pad_sequences(trainX, maxlen=maxlen)testX = sequence.pad_sequences(testX, maxlen=maxlen)print('trainX shape:', trainX.shape)print('testX shape:', testX.shape)
#------------------------------------创建模型------------------------------------model = Sequential()
#词嵌入:词库大小、词向量维度、固定序列长度model.add(Embedding(input_dim, output_dim, input_length=maxlen))
#平坦化: maxlen*output_dimmodel.add(Flatten())
#输出层: 2分类model.add(Dense(units=1, activation='sigmoid'))
#RMSprop优化器 二元交叉熵损失model.compile('rmsprop', 'binary_crossentropy', ['acc'])
#训练model.fit(trainX, trainY, batch_size, epochs)
#模型可视化model.summary()
复制代码


输出结果如下所示:


(25000,) (25000,)(25000,) (25000,)trainX shape: (25000, 80)testX shape: (25000, 80)
Epoch 1/225000/25000 [==============================] - 2s 98us/step - loss: 0.6111 - acc: 0.6956Epoch 2/225000/25000 [==============================] - 2s 69us/step - loss: 0.3578 - acc: 0.8549Model: "sequential_2"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_2 (Embedding) (None, 80, 40) 800000 _________________________________________________________________flatten_2 (Flatten) (None, 3200) 0 _________________________________________________________________dense_2 (Dense) (None, 1) 3201 =================================================================Total params: 803,201Trainable params: 803,201Non-trainable params: 0_________________________________________________________________
复制代码


​显示矩阵如下图所示:



3.RNN 文本分类


RNN 对 IMDB 电影数据集进行文本分类的完整代码如下所示:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 17:08:28 2020@author: Eastmount CSDN"""from keras.datasets import imdb  #Movie Databasefrom keras.preprocessing import sequencefrom keras.models import Sequentialfrom keras.layers import Dense, Flatten, Embeddingfrom keras.layers import SimpleRNN
#-----------------------------------定义参数-----------------------------------max_features = 20000 #按词频大小取样本前20000个词input_dim = max_features #词库大小 必须>=max_featuresmaxlen = 40 #句子最大长度batch_size = 128 #batch数量output_dim = 40 #词向量维度epochs = 3 #训练批次units = 32 #RNN神经元数量
#--------------------------------载入数据及预处理-------------------------------#数据获取(trainX, trainY), (testX, testY) = imdb.load_data(path="imdb.npz", num_words=max_features) print(trainX.shape, trainY.shape) #(25000,) (25000,)print(testX.shape, testY.shape) #(25000,) (25000,)
#序列截断或补齐为等长trainX = sequence.pad_sequences(trainX, maxlen=maxlen)testX = sequence.pad_sequences(testX, maxlen=maxlen)print('trainX shape:', trainX.shape)print('testX shape:', testX.shape)
#-----------------------------------创建RNN模型-----------------------------------model = Sequential()
#词嵌入 词库大小、词向量维度、固定序列长度model.add(Embedding(input_dim, output_dim, input_length=maxlen))
#RNN Cellmodel.add(SimpleRNN(units, return_sequences=True)) #返回序列全部结果model.add(SimpleRNN(units, return_sequences=False)) #返回序列最尾结果
#输出层 2分类model.add(Dense(units=1, activation='sigmoid'))
#模型可视化model.summary()
#-----------------------------------建模与训练-----------------------------------#激活神经网络 model.compile(optimizer = 'rmsprop', #RMSprop优化器 loss = 'binary_crossentropy', #二元交叉熵损失 metrics = ['accuracy'] #计算误差或准确率 )
#训练history = model.fit(trainX, trainY, batch_size=batch_size, epochs=epochs, verbose=2, validation_split=.1 #取10%样本作验证 )
#-----------------------------------预测与可视化-----------------------------------import matplotlib.pyplot as pltaccuracy = history.history['accuracy']val_accuracy = history.history['val_accuracy']plt.plot(range(epochs), accuracy)plt.plot(range(epochs), val_accuracy)plt.show()
复制代码


​输出结果如下所示,三个 Epoch 训练。

  • 训练数据的 accuracy ===> 0.9075

  • 评估数据的 val_accuracy ===> 0.7844


Epoch 可以用下图进行形象的表示。



(25000,) (25000,)(25000,) (25000,)trainX shape: (25000, 40)testX shape: (25000, 40)Model: "sequential_2"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_2 (Embedding)      (None, 40, 40)            800000    _________________________________________________________________simple_rnn_3 (SimpleRNN)     (None, 40, 32)            2336      _________________________________________________________________simple_rnn_4 (SimpleRNN)     (None, 32)                2080      _________________________________________________________________dense_2 (Dense)              (None, 1)                 33        =================================================================Total params: 804,449Trainable params: 804,449Non-trainable params: 0_________________________________________________________________
Train on 22500 samples, validate on 2500 samplesEpoch 1/3 - 11s - loss: 0.5741 - accuracy: 0.6735 - val_loss: 0.4462 - val_accuracy: 0.7876Epoch 2/3 - 14s - loss: 0.3572 - accuracy: 0.8430 - val_loss: 0.4928 - val_accuracy: 0.7616Epoch 3/3 - 12s - loss: 0.2329 - accuracy: 0.9075 - val_loss: 0.5050 - val_accuracy: 0.7844
复制代码


​​绘制的 accuracy 和 val_accuracy 曲线如下图所示:

  • loss: 0.2329 - accuracy: 0.9075 - val_loss: 0.5050 - val_accuracy: 0.7844



四.RNN 实现中文数据集的文本分类

1.RNN+Word2Vector 文本分类


第一步,导入文本数据集并转换为词向量。


data = [    [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'],    [0, '煮粥时一定要先烧开水然后放入洗净后的小米'],     [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'],    [0, '小米是传统健康食品,可单独焖饭和熬粥'],     [0, '苹果,是水果中的一种'],    [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'],    [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'],    [0, '这家超市的苹果都非常新鲜'],     [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'],    [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'],     [0, '蛋白质及氨基酸、脂肪、维生素、盐分'],    [1, '小米、三星、华为,作为安卓三大手机旗舰'],     [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'],    [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'],     [1, '三星想要继续压制华为,仅凭A70还不够'],    [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'],     [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'],    [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'],     [1, '小米米聊APP官方Linux版上线,适配深度系统'],    [1, '三星刚刚更新了自家的可穿戴设备APP'],     [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#划分训练集和预测集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '刚刚', '更新', '了', '自家', '的', '可', '穿戴', '设备', 'APP']"""
#--------------------------------Word2Vec词向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低过滤频次1print(word2vec)
#映射特征词w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【显示词语】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '维生素', '蛋白质', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '华为': 4, ....}"""
#词向量计算vectors = word2vec.wv.vectorsprint("【词向量矩阵】")print(vectors.shape)print(vectors)
#自定义函数-获取词向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定义函数-序列预处理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化处理 转换为词向量X_train, X_test = pad(X_train), pad(X_test)
复制代码


此时输出结果如下所示:


15 615 6Word2Vec(vocab=120, size=20, alpha=0.025)【显示词语】[',', '的', '、', '小米', '三星', '是', '维生素', '蛋白质', '及',   '脂肪', '华为', '苹果', '可', 'APP', '氨基酸', '在', '手机', '旗舰',  '矿物质', '主要', '有', '小米粥', '作为', '刚刚', '更新', '设备', ...]{',': 0, '的': 1, '、': 2, '小米': 3, '三星': 4, '是': 5,  '维生素': 6, '蛋白质': 7, '及': 8, '脂肪': 9, '和': 10,  '华为': 11, '苹果': 12, '可': 13, 'APP': 14, '氨基酸': 15, ...}【词向量矩阵】(120, 20)[[ 0.00219526  0.00936278  0.00390177 ... -0.00422463  0.01543128   0.02481441] [ 0.02346811 -0.01520025 -0.00563479 ... -0.01656673 -0.02222313   0.00438196] [-0.02253242 -0.01633896 -0.02209039 ...  0.01301584 -0.01016752   0.01147605] ... [ 0.01793107  0.01912305 -0.01780855 ... -0.00109831  0.02460653  -0.00023512] [-0.00599797  0.02155897 -0.01874896 ...  0.00149929  0.00200266   0.00988515] [ 0.0050361  -0.00848463 -0.0235001  ...  0.01531716 -0.02348576   0.01051775]]
复制代码


第二步,建立 RNN 神经网络结构,使用 Bi-GRU 模型,并进行训练与预测。


#--------------------------------建模与训练-------------------------------model = Sequential()
#双向RNNmodel.add(Bidirectional(GRU(units), input_shape=(maxlen, max_features)))
#输出层 2分类model.add(Dense(units=1, activation='sigmoid'))
#模型可视化model.summary()
#激活神经网络 model.compile(optimizer = 'rmsprop', #RMSprop优化器 loss = 'binary_crossentropy', #二元交叉熵损失 metrics = ['acc'] #计算误差或准确率 )
#训练history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------预测与可视化------------------------------#预测score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可视化acc = history.history['acc']val_acc = history.history['val_acc']
# 设置类标plt.xlabel("Iterations")plt.ylabel("Accuracy")
#绘图plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("RNN-Word2vec")plt.show()
复制代码


输出结果如下图所示,发现 accuracy 和 val_accuracy 值非常不理想。怎么解决呢?



神经网络模型和 Epoch 训练结果如下图所示:

  • test loss: 0.7160684466362

  • test accuracy: 0.33333334



这里补充一个知识点——EarlyStopping。EarlyStopping 是 Callbacks 的一种,callbacks 用于指定在每个 epoch 开始和结束的时候进行哪种特定操作。Callbacks 中有一些设置好的接口,可以直接使用,如 acc、val_acc、loss 和 val_loss 等。EarlyStopping 则是用于提前停止训练的 callbacks,可以达到当训练集上的 loss 不在减小(即减小的程度小于某个阈值)的时候停止继续训练。上面程序中,当我们 loss 不在减小时就可以调用 Callbacks 停止训练。推荐文章:[深度学习] keras的EarlyStopping使用与技巧 - zwqjoy


最后给出该部分的完整代码:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, GRU, Bidirectionalfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定义参数----------------------------------max_features = 20 #词向量维度units = 30 #RNN神经元数量maxlen = 40 #序列最大长度epochs = 9 #训练最大轮数batch_size = 12 #每批数据量大小verbose = 1 #训练过程展示patience = 1 #没有进步的训练轮数
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------载入数据及预处理-------------------------------data = [ [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'], [0, '煮粥时一定要先烧开水然后放入洗净后的小米'], [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'], [0, '小米是传统健康食品,可单独焖饭和熬粥'], [0, '苹果,是水果中的一种'], [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'], [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'], [0, '这家超市的苹果都非常新鲜'], [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'], [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'], [0, '蛋白质及氨基酸、脂肪、维生素、盐分'], [1, '小米、三星、华为,作为安卓三大手机旗舰'], [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'], [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'], [1, '三星想要继续压制华为,仅凭A70还不够'], [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'], [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'], [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上线,适配深度系统'], [1, '三星刚刚更新了自家的可穿戴设备APP'], [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#划分训练集和预测集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '刚刚', '更新', '了', '自家', '的', '可', '穿戴', '设备', 'APP']"""
#--------------------------------Word2Vec词向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低过滤频次1print(word2vec)
#映射特征词w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【显示词语】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '维生素', '蛋白质', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '华为': 4, ....}"""
#词向量计算vectors = word2vec.wv.vectorsprint("【词向量矩阵】")print(vectors.shape)print(vectors)
#自定义函数-获取词向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定义函数-序列预处理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化处理 转换为词向量X_train, X_test = pad(X_train), pad(X_test)
#--------------------------------建模与训练-------------------------------model = Sequential()
#双向RNNmodel.add(Bidirectional(GRU(units), input_shape=(maxlen, max_features)))
#输出层 2分类model.add(Dense(units=1, activation='sigmoid'))
#模型可视化model.summary()
#激活神经网络 model.compile(optimizer = 'rmsprop', #RMSprop优化器 loss = 'binary_crossentropy', #二元交叉熵损失 metrics = ['acc'] #计算误差或准确率 )
#训练history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------预测与可视化------------------------------#预测score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可视化acc = history.history['acc']val_acc = history.history['val_acc']
# 设置类标plt.xlabel("Iterations")plt.ylabel("Accuracy")
#绘图plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("RNN-Word2vec")plt.show()
复制代码


2.LSTM+Word2Vec 文本分类


接着我们使用 LSTM 和 Word2Vec 进行文本分类。整个神经网络的结构很简单,第一层是嵌入层,将文本中的单词转化为向量;之后经过一层 LSTM 层,使用 LSTM 中最后一个时刻的隐藏状态;再接一个全连接层,即可完成整个网络的构造。


注意矩阵形状的变换。

  • X_train = X_train.reshape(len(y_train), maxlen*max_features)

  • X_test = X_test.reshape(len(y_test), maxlen*max_features)


完整代码如下所示:


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, LSTM, GRU, Embeddingfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定义参数----------------------------------max_features = 20 #词向量维度units = 30 #RNN神经元数量maxlen = 40 #序列最大长度epochs = 9 #训练最大轮数batch_size = 12 #每批数据量大小verbose = 1 #训练过程展示patience = 1 #没有进步的训练轮数
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------载入数据及预处理-------------------------------data = [ [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'], [0, '煮粥时一定要先烧开水然后放入洗净后的小米'], [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'], [0, '小米是传统健康食品,可单独焖饭和熬粥'], [0, '苹果,是水果中的一种'], [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'], [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'], [0, '这家超市的苹果都非常新鲜'], [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'], [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'], [0, '蛋白质及氨基酸、脂肪、维生素、盐分'], [1, '小米、三星、华为,作为安卓三大手机旗舰'], [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'], [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'], [1, '三星想要继续压制华为,仅凭A70还不够'], [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'], [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'], [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上线,适配深度系统'], [1, '三星刚刚更新了自家的可穿戴设备APP'], [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#划分训练集和预测集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '刚刚', '更新', '了', '自家', '的', '可', '穿戴', '设备', 'APP']"""
#--------------------------------Word2Vec词向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低过滤频次1print(word2vec)
#映射特征词w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【显示词语】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '维生素', '蛋白质', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '华为': 4, ....}"""
#词向量计算vectors = word2vec.wv.vectorsprint("【词向量矩阵】")print(vectors.shape)print(vectors)
#自定义函数-获取词向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定义函数-序列预处理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化处理 转换为词向量X_train, X_test = pad(X_train), pad(X_test)print(X_train.shape)print(X_test.shape)"""(15, 40, 20) 15个样本 40个特征 每个特征用20词向量表示"""
#拉直形状 (15, 40, 20)=>(15, 40*20) (6, 40, 20)=>(6, 40*20)X_train = X_train.reshape(len(y_train), maxlen*max_features)X_test = X_test.reshape(len(y_test), maxlen*max_features)
#--------------------------------建模与训练-------------------------------model = Sequential()
#构建Embedding层 128代表Embedding层的向量维度model.add(Embedding(max_features, 128))
#构建LSTM层model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
#构建全连接层#注意上面构建LSTM层时只会得到最后一个节点的输出,如果需要输出每个时间点的结果需将return_sequences=Truemodel.add(Dense(units=1, activation='sigmoid'))
#模型可视化model.summary()
#激活神经网络 model.compile(optimizer = 'rmsprop', #RMSprop优化器 loss = 'binary_crossentropy', #二元交叉熵损失 metrics = ['acc'] #计算误差或准确率 )
#训练history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------预测与可视化------------------------------#预测score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可视化acc = history.history['acc']val_acc = history.history['val_acc']
# 设置类标plt.xlabel("Iterations")plt.ylabel("Accuracy")
#绘图plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("LSTM-Word2vec")plt.show()
复制代码


输出结果如下所示,仍然不理想。

  • test loss: 0.712007462978363

  • test accuracy: 0.33333334


Model: "sequential_22"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_8 (Embedding)      (None, None, 128)         2560      _________________________________________________________________lstm_8 (LSTM)                (None, 128)               131584    _________________________________________________________________dense_21 (Dense)             (None, 1)                 129       =================================================================Total params: 134,273Trainable params: 134,273Non-trainable params: 0_________________________________________________________________Train on 15 samples, validate on 6 samplesEpoch 1/915/15 [==============================] - 8s 552ms/sample - loss: 0.6971 - acc: 0.5333 - val_loss: 0.6911 - val_acc: 0.6667Epoch 2/915/15 [==============================] - 5s 304ms/sample - loss: 0.6910 - acc: 0.7333 - val_loss: 0.7111 - val_acc: 0.3333Epoch 3/915/15 [==============================] - 3s 208ms/sample - loss: 0.7014 - acc: 0.4667 - val_loss: 0.7392 - val_acc: 0.3333Epoch 4/915/15 [==============================] - 4s 261ms/sample - loss: 0.6890 - acc: 0.5333 - val_loss: 0.7471 - val_acc: 0.3333Epoch 5/915/15 [==============================] - 4s 248ms/sample - loss: 0.6912 - acc: 0.5333 - val_loss: 0.7221 - val_acc: 0.3333Epoch 6/915/15 [==============================] - 3s 210ms/sample - loss: 0.6857 - acc: 0.5333 - val_loss: 0.7143 - val_acc: 0.3333Epoch 7/915/15 [==============================] - 3s 187ms/sample - loss: 0.6906 - acc: 0.5333 - val_loss: 0.7346 - val_acc: 0.3333Epoch 8/915/15 [==============================] - 3s 185ms/sample - loss: 0.7066 - acc: 0.5333 - val_loss: 0.7578 - val_acc: 0.3333Epoch 9/915/15 [==============================] - 4s 235ms/sample - loss: 0.7197 - acc: 0.5333 - val_loss: 0.7120 - val_acc: 0.33336/6 [==============================] - 0s 43ms/sample - loss: 0.7120 - acc: 0.3333test loss: 0.712007462978363test accuracy: 0.33333334
复制代码


对应的图形如下所示。



3.LSTM+TFIDF 文本分类


同时,补充 LSTM+TFIDF 文本分类代码。


# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, LSTM, GRU, Embeddingfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定义参数----------------------------------max_features = 20 #词向量维度units = 30 #RNN神经元数量maxlen = 40 #序列最大长度epochs = 9 #训练最大轮数batch_size = 12 #每批数据量大小verbose = 1 #训练过程展示patience = 1 #没有进步的训练轮数
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------载入数据及预处理-------------------------------data = [ [0, '小米粥是以小米作为主要食材熬制而成的粥,口味清淡,清香味,具有简单易制,健胃消食的特点'], [0, '煮粥时一定要先烧开水然后放入洗净后的小米'], [0, '蛋白质及氨基酸、脂肪、维生素、矿物质'], [0, '小米是传统健康食品,可单独焖饭和熬粥'], [0, '苹果,是水果中的一种'], [0, '粥的营养价值很高,富含矿物质和维生素,含钙量丰富,有助于代谢掉体内多余盐分'], [0, '鸡蛋有很高的营养价值,是优质蛋白质、B族维生素的良好来源,还能提供脂肪、维生素和矿物质'], [0, '这家超市的苹果都非常新鲜'], [0, '在北方小米是主要食物之一,很多地区有晚餐吃小米粥的习俗'], [0, '小米营养价值高,营养全面均衡 ,主要含有碳水化合物'], [0, '蛋白质及氨基酸、脂肪、维生素、盐分'], [1, '小米、三星、华为,作为安卓三大手机旗舰'], [1, '别再管小米华为了!魅族手机再曝光:这次真的完美了'], [1, '苹果手机或将重陷2016年困境,但这次它无法再大幅提价了'], [1, '三星想要继续压制华为,仅凭A70还不够'], [1, '三星手机屏占比将再创新高,超华为及苹果旗舰'], [1, '华为P30、三星A70爆卖,斩获苏宁最佳手机营销奖'], [1, '雷军,用一张图告诉你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上线,适配深度系统'], [1, '三星刚刚更新了自家的可穿戴设备APP'], [1, '华为、小米跨界并不可怕,可怕的打不破内心的“天花板”'],]
#中文分词X, Y = [' '.join(lcut(i[1])) for i in data], [i[0] for i in data]print(X)print(Y)#['煮粥 时 一定 要 先烧 开水 然后 放入 洗净 后 的 小米', ...]
#--------------------------------------计算词频------------------------------------from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformer
#将文本中的词语转换为词频矩阵vectorizer = CountVectorizer()
#计算个词语出现的次数X_data = vectorizer.fit_transform(X)print(X_data)
#获取词袋中所有文本关键词word = vectorizer.get_feature_names()print('【查看单词】')for w in word: print(w, end = " ")else: print("\n")
#词频矩阵print(X_data.toarray())
#将词频矩阵X统计成TF-IDF值transformer = TfidfTransformer()tfidf = transformer.fit_transform(X_data)
#查看数据结构 tfidf[i][j]表示i类文本中的tf-idf权重weight = tfidf.toarray()print(weight)
#数据集划分X_train, X_test, y_train, y_test = train_test_split(weight, Y)print(X_train.shape, X_test.shape)print(len(y_train), len(y_test))#(15, 117) (6, 117) 15 6
#--------------------------------建模与训练-------------------------------model = Sequential()
#构建Embedding层 128代表Embedding层的向量维度model.add(Embedding(max_features, 128))
#构建LSTM层model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
#构建全连接层#注意上面构建LSTM层时只会得到最后一个节点的输出,如果需要输出每个时间点的结果需将return_sequences=Truemodel.add(Dense(units=1, activation='sigmoid'))
#模型可视化model.summary()
#激活神经网络 model.compile(optimizer = 'rmsprop', #RMSprop优化器 loss = 'binary_crossentropy', #二元交叉熵损失 metrics = ['acc'] #计算误差或准确率 )
#训练history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------预测与可视化------------------------------#预测score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可视化acc = history.history['acc']val_acc = history.history['val_acc']
# 设置类标plt.xlabel("Iterations")plt.ylabel("Accuracy")
#绘图plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("LSTM-TFIDF")plt.show()
复制代码


​输出结果如下所示:

  • test loss: 0.7694947719573975

  • test accuracy: 0.33333334


Model: "sequential_1"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_1 (Embedding)      (None, None, 128)         2560      _________________________________________________________________lstm_1 (LSTM)                (None, 128)               131584    _________________________________________________________________dense_1 (Dense)              (None, 1)                 129       =================================================================Total params: 134,273Trainable params: 134,273Non-trainable params: 0_________________________________________________________________Train on 15 samples, validate on 6 samplesEpoch 1/915/15 [==============================] - 2s 148ms/sample - loss: 0.6898 - acc: 0.5333 - val_loss: 0.7640 - val_acc: 0.3333Epoch 2/915/15 [==============================] - 1s 48ms/sample - loss: 0.6779 - acc: 0.6000 - val_loss: 0.7773 - val_acc: 0.3333Epoch 3/915/15 [==============================] - 1s 36ms/sample - loss: 0.6769 - acc: 0.6000 - val_loss: 0.7986 - val_acc: 0.3333Epoch 4/915/15 [==============================] - 1s 47ms/sample - loss: 0.6722 - acc: 0.6000 - val_loss: 0.8097 - val_acc: 0.3333Epoch 5/915/15 [==============================] - 1s 42ms/sample - loss: 0.7021 - acc: 0.6000 - val_loss: 0.7680 - val_acc: 0.3333Epoch 6/915/15 [==============================] - 1s 36ms/sample - loss: 0.6890 - acc: 0.6000 - val_loss: 0.8147 - val_acc: 0.3333Epoch 7/915/15 [==============================] - 1s 37ms/sample - loss: 0.6906 - acc: 0.6000 - val_loss: 0.8599 - val_acc: 0.3333Epoch 8/915/15 [==============================] - 1s 43ms/sample - loss: 0.6819 - acc: 0.6000 - val_loss: 0.8303 - val_acc: 0.3333Epoch 9/915/15 [==============================] - 1s 40ms/sample - loss: 0.6884 - acc: 0.6000 - val_loss: 0.7695 - val_acc: 0.33336/6 [==============================] - 0s 7ms/sample - loss: 0.7695 - acc: 0.3333test loss: 0.7694947719573975test accuracy: 0.33333334
复制代码


​对应图形如下:



4.机器学习和深度学习对比分析


最终结果我们进行简单对比,发现机器学习比深度学习好,这是为什么呢?我们又能做哪些提升呢?

  • MultinomialNB+TFIDF:test accuracy = 0.67

  • GaussianNB+Word2Vec:test accuracy = 0.83

  • RNN+Word2Vector:test accuracy = 0.33333334

  • LSTM+Word2Vec:test accuracy = 0.33333334

  • LSTM+TFIDF:test accuracy = 0.33333334


作者结合大佬们的文章及自己的经验对其进行简单分析,原因如下:

  • 一是 数据集预处理的原因,上述代码没有进行停用词过滤,大量标点符号和停用词影响了文本分类效果。同时词向量的维度设置也需要进行调试。

  • 二是 数据集大小的原因。数据量少的情况下,推荐使用 CNN,RNN 的过拟合会让你欲哭无泪。如果数据量多,也许 RNN 效果会更好。如果为了创新,RLSTM 和 RCNN 等都是近几年不错的选择。但如果仅仅是为了应用,用普通的机器学习方法已经足够优秀了(特别是新闻数据集),如果考虑上时间成本,贝叶斯无疑才是真正的最好选择。

  • 三是 CNN 和 RNN 适用性不同。CNN 擅长空间特征的学习和捕获,RNN 擅长时序特征的捕获。从结构来讲,RNN 更胜一筹。主流的 NLP 问题,比如翻译、生成文本,seq2seq(俩独立 RNN)的引入突破了很多之前的 benchmark。Attention 的引入是为了解决长句问题,其本质就是外挂了额外的一个 softmax 去学词和词的映射关系,有点像外挂存储,其根源来自一篇名为“neural turing machine”的 paper。

  • 四是 不同的数据集适应不同的方法,各种方法各有所长。有的情感分析 GRU 好于 CNN,而新闻分类、文本分类竞赛 CNN 可能会有优势。CNN 具有速度优势,基本比较大的数据上 CNN 能加大参数,拟合更多种类的 local phrase frequency,获得更好的效果。如果你是想做系统,两个算法又各有所长,就是 ensemble 登场的时候了。

  • 五是 在文本情感分类领域,GRU 是要好于 CNN,并且随着句子长度的增长,GRU 的这一优势会进一步放大。当句子的情感分类是由整个句子决定的时候,GRU 会更容易分类正确, 当句子的情感分类是由几个局部的 key-phrases 决定的时候,CNN 会更容易分类正确。


总之,我们在真实的实验中,尽量选择适合我们数据集的算法,这也是实验中的一部分,我们需要对比各种算法、各种参数、各种学习模型,从而找到一个更好的算法。后续作者会进一步学习 TextCNN、Attention、BiLSTM、GAN 等算法,希望能与大家一起进步。


参考及推荐文章:请问对于中文长文本分类,是CNN效果好,还是RNN效果好?- 知乎

五.总结


写道这里,这篇文章就结束了。希望对您有所帮助,同时文章中不足或错误的地方,欢迎读者提出。这些实验都是我在做论文研究或项目评价常见的一些问题,希望读者带着这些问题,结合自己的需求进行深入的思考,更希望大家能学以致用。


总之,本文通过 Keras 实现了一个 RNN 文本分类学习的案例,并详细介绍了循环神经网络原理知识及与机器学习对比。最后,作为人工智能的菜鸟,我希望自己能不断进步并深入,后续将它应用于图像识别、网络安全、对抗样本等领域,指导大家撰写简单的学术论文,一起加油!


感恩能与大家在华为云遇见!希望能与大家一起在华为云社区共同成长。原文地址:https://blog.csdn.net/Eastmount/article/details/105165164(By:娜璋之家 Eastmount 2021-11-09 夜于武汉)


参考文献:

再次感谢参考文献前辈和老师们的贡献,同时也参考了作者的 Python 人工智能和数据分析系列文章,源码请在 github 下载。

[1] Keras的imdb和MNIST数据集无法下载问题解决 - 摸金青年v

[2] 官网实例详解4.42(imdb.py)-keras学习笔记四 - wyx100

[3] Keras文本分类 - 强推基基伟老师文章

[4] TextCNN文本分类(keras实现)- 强推Asia-Lee老师的文章

[5] Keras 之文本分类实现 - 强推知乎王奕磊老师的文章

[6 ]自然语言处理入门(二)–Keras实现BiLSTM+Attention新闻标题文本分类 - ilivecode

[7] 用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践 - 知乎清凇

[8] 基于 word2vec 和 CNN 的文本分类 :综述 & 实践 - 牛亚峰 serena

[9] https://github.com/keras-team/keras

[10] [深度学习] keras的EarlyStopping使用与技巧 - zwqjoy

[11] 深度学习为什么会出现validation accuracy大于train accuracy的现象?- ICOZ

[12] Keras实现CNN文本分类 - vivian_ll

[13] Keras文本分类实战(上)- weixin_34351321

[14] 请问对于中文长文本分类,是CNN效果好,还是RNN效果好?- 知乎


点击关注,第一时间了解华为云新鲜技术~

发布于: 1 小时前阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
文本分类:Keras+RNN vs 传统机器学习