写点什么

详解 CNN 实现中文文本分类过程

  • 2022 年 2 月 15 日
  • 本文字数:17412 字

    阅读完需:约 57 分钟

本文分享自华为云社区《[Python 人工智能] 二十一.Word2Vec+CNN 中文文本分类详解及与机器学习算法对比》,作者:eastmount。

一.文本分类

文本分类旨在对文本集按照一定的分类体系或标准进行自动分类标记,属于一种基于分类体系的自动分类。文本分类最早可以追溯到上世纪 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 两种方式进行实验。


在知乎史老师的“https://zhuanlan.zhihu.com/p/34212945”里总结归类来说,基于深度学习的文本分类主要有 5 个大类别:


  • 词嵌入向量化:word2vec, FastText 等

  • 卷积神经网络特征提取:TextCNN(卷积神经网络)、Char-CNN 等

  • 上下文机制:TextRNN(循环神经网络)、BiRNN、BiLSTM、RCNN、TextRCNN(TextRNN+CNN)等

  • 记忆存储机制:EntNet, DMN 等

  • 注意力机制:HAN、TextRNN+Attention 等


二.基于随机森林的文本分类

该部分主要围绕常见的文本分类案例进行讲解,由于随机森林效果较好,故主要分享该方法。具体步骤包括:


  • 读取 CSV 中文文本

  • 调用 Jieba 库实现中文分词及数据清洗

  • 特征提取采用 TF-IDF 或 Word2Vec 词向量表示

  • 基于机器学习的分类

  • 准确率、召回率、F 值计算及评估


1.文本分类

(1).数据集

本文的数据为近期贵州黄果树瀑布的旅游评论文本,来自大众点评网,共有 240 条数据,其中差评数据 114 条,好评数据 126 条,如下图所示:


(2) 随机森林文本分类


本文不再详细叙述代码实现过程,前面很多文章都介绍过,并且源代码有详细的注释供大家参考。

# -*- coding:utf-8 -*-import csvimport numpy as npimport jiebaimport jieba.analysefrom sklearn import feature_extraction  from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom sklearn.ensemble import RandomForestClassifier
#----------------------------------第一步 读取文件--------------------------------file = "data.csv"
with open(file, "r", encoding="UTF-8") as f: # 使用csv.DictReader读取文件中的信息 reader = csv.DictReader(f) labels = [] contents = [] for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.append(res) content = row['content'] seglist = jieba.cut(content,cut_all=False) #精确模式 output = ' '.join(list(seglist)) #空格拼接 #print(output) contents.append(output)
print(labels[:5])print(contents[:5])
#----------------------------------第二步 数据预处理--------------------------------# 将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频vectorizer = CountVectorizer()
# 该类会统计每个词语的tf-idf权值transformer = TfidfTransformer()
#第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵tfidf = transformer.fit_transform(vectorizer.fit_transform(contents))for n in tfidf[:5]: print(n)#tfidf = tfidf.astype(np.float32)print(type(tfidf))
# 获取词袋模型中的所有词语 word = vectorizer.get_feature_names()for n in word[:5]: print(n)print("单词数量:", len(word))
# 将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重X = tfidf.toarray()print(X.shape)
# 使用 train_test_split 分割 X y 列表# X_train矩阵的数目对应 y_train列表的数目(一一对应) -->> 用来训练模型# X_test矩阵的数目对应 (一一对应) -->> 用来测试模型的准确性X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.3, random_state=1)
#----------------------------------第三步 机器学习分类--------------------------------# 随机森林分类方法模型# n_estimators:森林中树的数量clf = RandomForestClassifier(n_estimators=20)
# 训练模型clf.fit(X_train, y_train)
# 使用测试值 对 模型的准确度进行计算print('模型的准确度:{}'.format(clf.score(X_test, y_test)))print("\n")
# 预测结果pre = clf.predict(X_test)print('预测结果:', pre[:10])print(len(pre), len(y_test))print(classification_report(y_test, pre))
复制代码


输出结果如下图所示,随机森林的平均准确率为 0.86,召回率为 0.86,F 值也为 0.86。


2.算法评价

接着作者尝试自定义准确率(Precision)、召回率(Recall)和 F 特征值(F-measure),其计算公式如下:


由于本文主要针对 2 分类问题,其实验评估主要分为 0 和 1 两类,完整代码如下:

# -*- coding:utf-8 -*-import csvimport numpy as npimport jiebaimport jieba.analysefrom sklearn import feature_extraction  from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom sklearn.ensemble import RandomForestClassifier
#----------------------------------第一步 读取文件--------------------------------file = "data.csv"
with open(file, "r", encoding="UTF-8") as f: # 使用csv.DictReader读取文件中的信息 reader = csv.DictReader(f) labels = [] contents = [] for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.append(res) content = row['content'] seglist = jieba.cut(content,cut_all=False) #精确模式 output = ' '.join(list(seglist)) #空格拼接 #print(output) contents.append(output)
print(labels[:5])print(contents[:5])
#----------------------------------第二步 数据预处理--------------------------------# 将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频vectorizer = CountVectorizer()
# 该类会统计每个词语的tf-idf权值transformer = TfidfTransformer()
#第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵tfidf = transformer.fit_transform(vectorizer.fit_transform(contents))for n in tfidf[:5]: print(n)#tfidf = tfidf.astype(np.float32)print(type(tfidf))
# 获取词袋模型中的所有词语 word = vectorizer.get_feature_names()for n in word[:5]: print(n)print("单词数量:", len(word))
# 将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重X = tfidf.toarray()print(X.shape)
# 使用 train_test_split 分割 X y 列表# X_train矩阵的数目对应 y_train列表的数目(一一对应) -->> 用来训练模型# X_test矩阵的数目对应 (一一对应) -->> 用来测试模型的准确性X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.3, random_state=1)
#----------------------------------第三步 机器学习分类--------------------------------# 随机森林分类方法模型# n_estimators:森林中树的数量clf = RandomForestClassifier(n_estimators=20)
# 训练模型clf.fit(X_train, y_train)
# 使用测试值 对 模型的准确度进行计算print('模型的准确度:{}'.format(clf.score(X_test, y_test)))print("\n")
# 预测结果pre = clf.predict(X_test)print('预测结果:', pre[:10])print(len(pre), len(y_test))print(classification_report(y_test, pre))
#----------------------------------第四步 评价结果--------------------------------def classification_pj(name, y_test, pre): print("算法评价:", name) # 正确率 Precision = 正确识别的个体总数 / 识别出的个体总数 # 召回率 Recall = 正确识别的个体总数 / 测试集中存在的个体总数 # F值 F-measure = 正确率 * 召回率 * 2 / (正确率 + 召回率)
YC_B, YC_G = 0,0 #预测 bad good ZQ_B, ZQ_G = 0,0 #正确 CZ_B, CZ_G = 0,0 #存在
#0-good 1-bad 同时计算防止类标变化 i = 0 while i<len(pre): z = int(y_test[i]) #真实 y = int(pre[i]) #预测
if z==0: CZ_G += 1 else: CZ_B += 1 if y==0: YC_G += 1 else: YC_B += 1
if z==y and z==0 and y==0: ZQ_G += 1 elif z==y and z==1 and y==1: ZQ_B += 1 i = i + 1
print(ZQ_B, ZQ_G, YC_B, YC_G, CZ_B, CZ_G) print("")
# 结果输出 P_G = ZQ_G * 1.0 / YC_G P_B = ZQ_B * 1.0 / YC_B print("Precision Good 0:", P_G) print("Precision Bad 1:", P_B)
R_G = ZQ_G * 1.0 / CZ_G R_B = ZQ_B * 1.0 / CZ_B print("Recall Good 0:", R_G) print("Recall Bad 1:", R_B)
F_G = 2 * P_G * R_G / (P_G + R_G) F_B = 2 * P_B * R_B / (P_B + R_B) print("F-measure Good 0:", F_G) print("F-measure Bad 1:", F_B)
# 函数调用classification_pj("RandomForest", y_test, pre)
复制代码

输出结果如下图所示,其中好评的准确率、召回率、F 值分别为 0.9268、0.9268、0.9268,差评的准确率、召回率、F 值分别为 0.9032、0.9032、0.9032。



3.算法对比


最后作者给出机器学习 RF、DTC、SVM、KNN、NB、LR 的文本分类结果,这也是写论文中很常见的操作。

# -*- coding:utf-8 -*-import csvimport numpy as npimport jiebaimport jieba.analysefrom sklearn import feature_extraction  from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.tree import DecisionTreeClassifierfrom sklearn import svmfrom sklearn import neighborsfrom sklearn.naive_bayes import MultinomialNBfrom sklearn.linear_model import LogisticRegression
#----------------------------------第一步 读取文件--------------------------------file = "data.csv"
with open(file, "r", encoding="UTF-8") as f: # 使用csv.DictReader读取文件中的信息 reader = csv.DictReader(f) labels = [] contents = [] for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.append(res) content = row['content'] seglist = jieba.cut(content,cut_all=False) #精确模式 output = ' '.join(list(seglist)) #空格拼接 #print(output) contents.append(output)
print(labels[:5])print(contents[:5])
#----------------------------------第二步 数据预处理--------------------------------# 将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频vectorizer = CountVectorizer()
# 该类会统计每个词语的tf-idf权值transformer = TfidfTransformer()
#第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵tfidf = transformer.fit_transform(vectorizer.fit_transform(contents))for n in tfidf[:5]: print(n)#tfidf = tfidf.astype(np.float32)print(type(tfidf))
# 获取词袋模型中的所有词语 word = vectorizer.get_feature_names()for n in word[:5]: print(n)print("单词数量:", len(word))
# 将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重X = tfidf.toarray()print(X.shape)
# 使用 train_test_split 分割 X y 列表# X_train矩阵的数目对应 y_train列表的数目(一一对应) -->> 用来训练模型# X_test矩阵的数目对应 (一一对应) -->> 用来测试模型的准确性X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.3, random_state=1)
#----------------------------------第四步 评价结果--------------------------------def classification_pj(name, y_test, pre): print("算法评价:", name) # 正确率 Precision = 正确识别的个体总数 / 识别出的个体总数 # 召回率 Recall = 正确识别的个体总数 / 测试集中存在的个体总数 # F值 F-measure = 正确率 * 召回率 * 2 / (正确率 + 召回率)
YC_B, YC_G = 0,0 #预测 bad good ZQ_B, ZQ_G = 0,0 #正确 CZ_B, CZ_G = 0,0 #存在
#0-good 1-bad 同时计算防止类标变化 i = 0 while i<len(pre): z = int(y_test[i]) #真实 y = int(pre[i]) #预测
if z==0: CZ_G += 1 else: CZ_B += 1 if y==0: YC_G += 1 else: YC_B += 1
if z==y and z==0 and y==0: ZQ_G += 1 elif z==y and z==1 and y==1: ZQ_B += 1 i = i + 1 print(ZQ_B, ZQ_G, YC_B, YC_G, CZ_B, CZ_G)
# 结果输出 P_G = ZQ_G * 1.0 / YC_G P_B = ZQ_B * 1.0 / YC_B print("Precision Good 0:{:.4f}".format(P_G)) print("Precision Bad 1:{:.4f}".format(P_B)) print("Avg_precision:{:.4f}".format((P_G+P_B)/2))
R_G = ZQ_G * 1.0 / CZ_G R_B = ZQ_B * 1.0 / CZ_B print("Recall Good 0:{:.4f}".format(R_G)) print("Recall Bad 1:{:.4f}".format(R_B)) print("Avg_recall:{:.4f}".format((R_G+R_B)/2))
F_G = 2 * P_G * R_G / (P_G + R_G) F_B = 2 * P_B * R_B / (P_B + R_B) print("F-measure Good 0:{:.4f}".format(F_G)) print("F-measure Bad 1:{:.4f}".format(F_B)) print("Avg_fmeasure:{:.4f}".format((F_G+F_B)/2)) #----------------------------------第三步 机器学习分类--------------------------------# 随机森林分类方法模型rf = RandomForestClassifier(n_estimators=20)rf.fit(X_train, y_train)pre = rf.predict(X_test)print("随机森林分类")print(classification_report(y_test, pre))classification_pj("RandomForest", y_test, pre)print("\n")
# 决策树分类方法模型dtc = DecisionTreeClassifier()dtc.fit(X_train, y_train)pre = dtc.predict(X_test)print("决策树分类")print(classification_report(y_test, pre))classification_pj("DecisionTree", y_test, pre)print("\n")
# SVM分类方法模型SVM = svm.LinearSVC() #支持向量机分类器LinearSVCSVM.fit(X_train, y_train)pre = SVM.predict(X_test)print("支持向量机分类")print(classification_report(y_test, pre))classification_pj("LinearSVC", y_test, pre)print("\n")
# KNN分类方法模型knn = neighbors.KNeighborsClassifier() #n_neighbors=11knn.fit(X_train, y_train)pre = knn.predict(X_test)print("最近邻分类")print(classification_report(y_test, pre))classification_pj("KNeighbors", y_test, pre)print("\n")
# 朴素贝叶斯分类方法模型nb = MultinomialNB()nb.fit(X_train, y_train)pre = nb.predict(X_test)print("朴素贝叶斯分类")print(classification_report(y_test, pre))classification_pj("MultinomialNB", y_test, pre)print("\n")
# 逻辑回归分类方法模型LR = LogisticRegression(solver='liblinear')LR.fit(X_train, y_train)pre = LR.predict(X_test)print("逻辑回归分类")print(classification_report(y_test, pre))classification_pj("LogisticRegression", y_test, pre)print("\n")
复制代码

输出结果如下所示,发现贝叶斯算法在文本分类中的效果还是很棒;同时随机森林、逻辑回归、SVM 效果都还不错。


完整结果如下:

随机森林分类              precision    recall  f1-score   support
0 0.92 0.88 0.90 41 1 0.85 0.90 0.88 31
accuracy 0.89 72 macro avg 0.89 0.89 0.89 72weighted avg 0.89 0.89 0.89 72
算法评价: RandomForest28 36 33 39 31 41Precision Good 0:0.9231Precision Bad 1:0.8485Avg_precision:0.8858Recall Good 0:0.8780Recall Bad 1:0.9032Avg_recall:0.8906F-measure Good 0:0.9000F-measure Bad 1:0.8750Avg_fmeasure:0.8875决策树分类 precision recall f1-score support
0 0.81 0.73 0.77 41 1 0.69 0.77 0.73 31
accuracy 0.75 72 macro avg 0.75 0.75 0.75 72weighted avg 0.76 0.75 0.75 72
算法评价: DecisionTree24 30 35 37 31 41Precision Good 0:0.8108Precision Bad 1:0.6857Avg_precision:0.7483Recall Good 0:0.7317Recall Bad 1:0.7742Avg_recall:0.7530F-measure Good 0:0.7692F-measure Bad 1:0.7273Avg_fmeasure:0.7483支持向量机分类最近邻分类朴素贝叶斯分类逻辑回归分类......
复制代码

三.基于 CNN 的文本分类


接着我们开始通过 CNN 实现文本分类,该方法可以应用于很多领域,只要有数据集即可分析。这里仅给出最基础且可用的方法及源码,希望对您有所帮助。

1.数据预处理

上一部分我在写机器学习文本分类时,已经介绍了中文分词等预处理操作,为什么这部分还要介绍呢?因为这里我要增加两个新的操作:


  • 去停用词

  • 词性标注


这两个操作在文本挖掘过程中非常重要,它一方面能提升我们的分类效果,另一方面能过滤掉无关的特征词,词性标注也能辅助我们进行其他的分析,如情感分析、舆情挖掘等。


该部分代码如下:

# -*- coding:utf-8 -*-import csvimport numpy as npimport jiebaimport jieba.analyseimport jieba.posseg as psegfrom sklearn import feature_extraction  from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_report
#----------------------------------第一步 数据预处理--------------------------------file = "data.csv"
# 获取停用词def stopwordslist(): #加载停用词表 stopwords = [line.strip() for line in open('stop_words.txt', encoding="UTF-8").readlines()] return stopwords
# 去除停用词def deleteStop(sentence): stopwords = stopwordslist() outstr = "" for i in sentence: # print(i) if i not in stopwords and i!="\n": outstr += i return outstr
# 中文分词Mat = []with open(file, "r", encoding="UTF-8") as f: # 使用csv.DictReader读取文件中的信息 reader = csv.DictReader(f) labels = [] contents = [] for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.append(res)
# 中文分词 content = row['content'] #print(content) seglist = jieba.cut(content,cut_all=False) #精确模式 #print(seglist) # 去停用词 stc = deleteStop(seglist) #注意此时句子无空格 # 空格拼接 seg_list = jieba.cut(stc,cut_all=False) output = ' '.join(list(seg_list)) #print(output) contents.append(output) # 词性标注 res = pseg.cut(stc) seten = [] for word,flag in res: if flag not in ['nr','ns','nt','mz','m','f','ul','l','r','t']: seten.append(word) Mat.append(seten)
print(labels[:5])print(contents[:5])print(Mat[:5])
# 文件写入fileDic = open('wordCut.txt', 'w', encoding="UTF-8")for i in Mat: fileDic.write(" ".join(i)) fileDic.write('\n')fileDic.close()words = [line.strip().split(" ") for line in open('WordCut.txt',encoding='UTF-8').readlines()]print(words[:5])
复制代码


运行结果如下图所示,可以看到原文本被分词,并且过滤掉了“还”、“,”、“常常”等停用词,并且以两种形式呈现,读者可以结合自己的需要进行后续分析。同时,将分词后的文本也写入到 wordCut.txt 文件中。


  • contents:显示已分词且以列表形式存在的句子

  • Mat:显示已分词且以列表形式存在的词序列



2.特征提取及 Word2Vec 词向量转换

(1) 特征词编号


首先,我们先调用 Tokenizer 和 fit_on_texts 函数将文本中的每个词编号,词频出现越高其编号越小。如下图所示,“瀑布”、“景区”、“排队”、“水帘洞”等特征词出现较多,注意空格、“评论”、“收起”可以继续过滤掉,在停用词表中添加即可。

#fit_on_texts函数可以将输入的文本每个词编号 编号根据词频(词频越大编号越小)tokenizer = Tokenizer()tokenizer.fit_on_texts(Mat)vocab = tokenizer.word_index  #停用词已过滤,获取每个词的编号print(vocab)
复制代码

输出结果如下图所示:


(2) Word2Vec 词向量训练


获取了特征词编号即将特征矩阵的表头定义好了,接下来我们需要将每一行文本转换成一维词向量,最终构建特征矩阵,用于训练和分类。注意,利用 pad_sequences 方法将 CNN 训练的长度统一,更好地进行训练。比如设置为 100,如果句子超过 100 后面的单词会被切掉;如果句子未超过 100,则会在句子前面补 0,下图展示了补 0 过程。同时,分类结果[0,1]表示类标是好评 0,[1,0]表示类标是差评 1。


此时的完整代码如下:

# 使用 train_test_split 分割 X y 列表X_train, X_test, y_train, y_test = train_test_split(Mat, labels, test_size=0.3, random_state=1)print(X_train[:5])print(y_train[:5])
#----------------------------------第三步 词向量构建--------------------------------# Word2Vec训练maxLen = 100 #词序列最大长度num_features = 100 #设置词语向量维度min_word_count = 3 #保证被考虑词语的最低频度num_workers = 4 #设置并行化训练使用CPU计算核心数量context = 4 #设置词语上下文窗口大小
# 设置模型model = word2vec.Word2Vec(Mat, workers=num_workers, size=num_features, min_count=min_word_count,window=context)# 强制单位归一化model.init_sims(replace=True)# 输入一个路径保存训练模型 其中./data/model目录事先存在model.save("CNNw2vModel")model.wv.save_word2vec_format("CNNVector",binary=False)print(model)# 加载模型 如果word2vec已训练好直接用下面语句w2v_model = word2vec.Word2Vec.load("CNNw2vModel")
# 特征编号(不足的前面补0)trainID = tokenizer.texts_to_sequences(X_train)print(trainID)testID = tokenizer.texts_to_sequences(X_test)print(testID)# 该方法会让CNN训练的长度统一trainSeq = pad_sequences(trainID, maxlen=maxLen)print(trainSeq)
# 标签独热编码 转换为one-hot编码trainCate = to_categorical(y_train, num_classes=2) #二分类问题print(trainCate)testCate = to_categorical(y_test, num_classes=2) #二分类问题print(testCate)
复制代码


输出结果如下:

[['景色', ' ', '景区', '太', '成熟', '从', '大', '瀑布', '景区', '出发', '景区', '观光车', '足足', '游客', '半小时', '世博会', '路上', '摩肩接踵', '欣赏', '美景', '心情', '观光车', '上车', '处', '标明', '目的地', '入口处', '引导', '走', '冤枉路', '稀里糊涂', '上车', '问', '司机', '到达', '司机', '含糊地', '说', '开出', '景区', '客运站', '七孔', '景区', '开发', '完美', '收起', '评论'], ['淡季', '瀑布', '人', '少', '景美', '机票', '便宜', '值得', '去'], ['瀑布', '体验', '差', '五星', '好评', '全', '是', '刷', '道路', '很窄', '导致', '大面积', '堵塞', '排队', '崩溃', '景区', '指引', '清晰', '排队', '大雨', '遮雨', '设计', '搞', '大人', '小孩', '老人', '淋雨', '景区', '接待', '能力差', '瀑布', '真的', '徒有虚名', '七孔', '收起', '评论'], ['老爸', '分', '瀑布', '瀑布', '瀑布', '游览', '瀑布', '门票', '反正', '超过', ' ', '来到', '熟悉', '告知', '只能', '出', '进入', '口', '回到', '高速', '出口', '直行', '回去', '倒', '指示', '清晰', '隔离', '栏杆', '自驾车', '导进', '停车场', '停车场', '收费', '且', '时间', ' ', '停车场', '经查', '景区', '门票', '单人', '含', '交通', '车费', '交通车', '需', '另付', '从外', '围绕', '路', '花', '不到', '分钟', '车费', '真心', '接受', ' ', '全家人', '不想', '┐', '(', '─', '__', '─', ')', '┌', '利益', '勾结', '剧烈', '涨费', '个金', '瀑布', '好看', '差', '评', ' ', '图片', '未', '开发', '瀑布', '天坑', '瀑布', '壮观', '壮观', '有', '灵秀', '景区', '膨胀', '成', '收起', '评论'], ['全家', '票', '居民', '专享', '优惠', '票']]
[1, 0, 1, 1, 1]Word2Vec(vocab=718, size=100, alpha=0.025)
[[ 0 0 0 ... 2481 5 4] [ 0 0 0 ... 570 52 90] [ 0 0 0 ... 187 5 4] ... [ 0 0 0 ... 93 5 4] [ 0 0 0 ... 30 5 4] [ 0 0 0 ... 81 18 78]] [[0. 1.] [1. 0.] [0. 1.] [0. 1.] [0. 1.] [0. 1.] [1. 0.]
复制代码

3.CNN 构建

接下来我们开始将构建好的特征矩阵拿去训练,计算不同文本或一维矩阵的相似度,这样会将好评和差评的不同句子按相似度分成两类。这里同样使用 Word2Vec 实现核心代码如下:

model = word2vec.Word2Vec(	Mat, 	workers=num_workers, 	size=num_features,	min_count=min_word_count,	window=context);
复制代码

训练模型的结果为“Word2Vec(vocab=718, size=100, alpha=0.025)”,这里设置的过滤频度为 3,相当于出现频率低于 3 的被过滤,最终得到 718 个特征词。num_features 值为 100,表示是 100 维的词向量。sg 默认为连续词袋模型,也可以设置为 1 跳字模型。默认的优化方法负采样,更多参数解释请读者百度。


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


如果我们存在一个训练集、一个测试集,如果测试集中不存在某个特征词,怎么解决呢?这里我们在获取某个特征词的词向量,并转换为训练矩阵时,使用了 try-except 异常捕获,如果未找到特征词则跳过即可,它会自动补 0。


该部分代码如下所示:

#----------------------------------第四步 CNN构建--------------------------------# 利用训练后的Word2vec自定义Embedding的训练矩阵 每行代表一个词(结合独热编码和矩阵乘法理解)embedding_matrix = np.zeros((len(vocab)+1, 100)) #从0开始计数 加1对应之前特征词for word, i in vocab.items():    try:        #提取词向量并放置训练矩阵        embedding_vector = w2v_model[str(word)]        embedding_matrix[i] = embedding_vector    except KeyError: #单词未找到跳过        continue
# 训练模型main_input = Input(shape=(maxLen,), dtype='float64')# 词嵌入 使用预训练Word2Vec的词向量 自定义权重矩阵 100是输出词向量维度embedder = Embedding(len(vocab)+1, 100, input_length=maxLen, weights=[embedding_matrix], trainable=False) #不再训练# 建立模型model = Sequential()model.add(embedder) #构建Embedding层model.add(Conv1D(256, 3, padding='same', activation='relu')) #卷积层步幅3model.add(MaxPool1D(maxLen-5, 3, padding='same')) #池化层model.add(Conv1D(32, 3, padding='same', activation='relu')) #卷积层model.add(Flatten()) #拉直化model.add(Dropout(0.3)) #防止过拟合 30%不训练model.add(Dense(256, activation='relu')) #全连接层model.add(Dropout(0.2)) #防止过拟合model.add(Dense(units=2, activation='softmax')) #输出层
# 模型可视化model.summary()
# 激活神经网络 model.compile(optimizer = 'adam', #优化器 loss = 'categorical_crossentropy', #损失 metrics = ['accuracy'] #计算误差或准确率 )
#训练(训练数据、训练类标、batch—size每次256条训练、epochs、随机选择、验证集20%)history = model.fit(trainSeq, trainCate, batch_size=256, epochs=6, validation_split=0.2)model.save("TextCNN")
#----------------------------------第五步 预测模型--------------------------------# 预测与评估mainModel = load_model("TextCNN")result = mainModel.predict(testSeq) #测试样本#print(result)print(np.argmax(result,axis=1))score = mainModel.evaluate(testSeq, testCate, batch_size=32)print(score)
复制代码

构建的模型如下:

Model: "sequential_1"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_2 (Embedding)      (None, 100, 100)          290400    _________________________________________________________________conv1d_1 (Conv1D)            (None, 100, 256)          77056     _________________________________________________________________max_pooling1d_1 (MaxPooling1 (None, 34, 256)           0         _________________________________________________________________conv1d_2 (Conv1D)            (None, 34, 32)            24608     _________________________________________________________________flatten_1 (Flatten)          (None, 1088)              0         _________________________________________________________________dropout_1 (Dropout)          (None, 1088)              0         _________________________________________________________________dense_1 (Dense)              (None, 256)               278784    _________________________________________________________________dropout_2 (Dropout)          (None, 256)               0         _________________________________________________________________dense_2 (Dense)              (None, 2)                 514       =================================================================Total params: 671,362Trainable params: 380,962Non-trainable params: 290,400
复制代码


输出结果如下图所示,该模型的预测结果不是很理想,accuracy 值仅为 0.625,为什么呢?作者也还在进一步研究深度模型的优化,本文更重要的是提供一种可用的方法,效果不好也请见谅~


4.测试可视化

最后增加可视化代码,绘制图形如下图所示。再次强调,该算法效果确实不理想,误差不是逐渐递减,正确率也不是不断升高。如果读者发现原因或优化方法也恳请您告知,谢谢。



最后附上完整代码:

# -*- coding:utf-8 -*-import csvimport numpy as npimport jiebaimport jieba.analyseimport jieba.posseg as psegfrom sklearn import feature_extraction  from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom keras import modelsfrom keras import layersfrom keras import Inputfrom gensim.models import word2vecfrom keras.preprocessing.text import Tokenizerfrom keras.utils.np_utils import to_categoricalfrom keras.preprocessing.sequence import pad_sequencesfrom keras.models import Modelfrom keras.models import Sequentialfrom keras.models import load_modelfrom keras.layers import Flatten, Dense, Dropout, Conv1D, MaxPool1D, Embedding
#----------------------------------第一步 数据预处理--------------------------------file = "data.csv"
# 获取停用词def stopwordslist(): #加载停用词表 stopwords = [line.strip() for line in open('stop_words.txt', encoding="UTF-8").readlines()] return stopwords
# 去除停用词def deleteStop(sentence): stopwords = stopwordslist() outstr = "" for i in sentence: # print(i) if i not in stopwords and i!="\n": outstr += i return outstr
# 中文分词Mat = []with open(file, "r", encoding="UTF-8") as f: # 使用csv.DictReader读取文件中的信息 reader = csv.DictReader(f) labels = [] contents = [] for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.append(res)
# 中文分词 content = row['content'] #print(content) seglist = jieba.cut(content,cut_all=False) #精确模式 #print(seglist) # 去停用词 stc = deleteStop(seglist) #注意此时句子无空格 # 空格拼接 seg_list = jieba.cut(stc,cut_all=False) output = ' '.join(list(seg_list)) #print(output) contents.append(output) # 词性标注 res = pseg.cut(stc) seten = [] for word,flag in res: if flag not in ['nr','ns','nt','mz','m','f','ul','l','r','t']: #print(word,flag) seten.append(word) Mat.append(seten)
print(labels[:5])print(contents[:5])print(Mat[:5])
#----------------------------------第二步 特征编号--------------------------------# fit_on_texts函数可以将输入的文本每个词编号 编号根据词频(词频越大编号越小)tokenizer = Tokenizer()tokenizer.fit_on_texts(Mat)vocab = tokenizer.word_index #停用词已过滤,获取每个词的编号print(vocab)
# 使用 train_test_split 分割 X y 列表X_train, X_test, y_train, y_test = train_test_split(Mat, labels, test_size=0.3, random_state=1)print(X_train[:5])print(y_train[:5])
#----------------------------------第三步 词向量构建--------------------------------# Word2Vec训练maxLen = 100 #词序列最大长度num_features = 100 #设置词语向量维度min_word_count = 3 #保证被考虑词语的最低频度num_workers = 4 #设置并行化训练使用CPU计算核心数量context = 4 #设置词语上下文窗口大小
# 设置模型model = word2vec.Word2Vec(Mat, workers=num_workers, size=num_features, min_count=min_word_count,window=context)# 强制单位归一化model.init_sims(replace=True)# 输入一个路径保存训练模型 其中./data/model目录事先存在model.save("CNNw2vModel")model.wv.save_word2vec_format("CNNVector",binary=False)print(model)# 加载模型 如果word2vec已训练好直接用下面语句w2v_model = word2vec.Word2Vec.load("CNNw2vModel")
# 特征编号(不足的前面补0)trainID = tokenizer.texts_to_sequences(X_train)print(trainID)testID = tokenizer.texts_to_sequences(X_test)print(testID)# 该方法会让CNN训练的长度统一trainSeq = pad_sequences(trainID, maxlen=maxLen)print(trainSeq)testSeq = pad_sequences(testID, maxlen=maxLen)print(testSeq)
# 标签独热编码 转换为one-hot编码trainCate = to_categorical(y_train, num_classes=2) #二分类问题print(trainCate)testCate = to_categorical(y_test, num_classes=2) #二分类问题print(testCate)
#----------------------------------第四步 CNN构建--------------------------------# 利用训练后的Word2vec自定义Embedding的训练矩阵 每行代表一个词(结合独热编码和矩阵乘法理解)embedding_matrix = np.zeros((len(vocab)+1, 100)) #从0开始计数 加1对应之前特征词for word, i in vocab.items(): try: #提取词向量并放置训练矩阵 embedding_vector = w2v_model[str(word)] embedding_matrix[i] = embedding_vector except KeyError: #单词未找到跳过 continue
# 训练模型main_input = Input(shape=(maxLen,), dtype='float64')# 词嵌入 使用预训练Word2Vec的词向量 自定义权重矩阵 100是输出词向量维度embedder = Embedding(len(vocab)+1, 100, input_length=maxLen, weights=[embedding_matrix], trainable=False) #不再训练# 建立模型model = Sequential()model.add(embedder) #构建Embedding层model.add(Conv1D(256, 3, padding='same', activation='relu')) #卷积层步幅3model.add(MaxPool1D(maxLen-5, 3, padding='same')) #池化层model.add(Conv1D(32, 3, padding='same', activation='relu')) #卷积层model.add(Flatten()) #拉直化model.add(Dropout(0.3)) #防止过拟合 30%不训练model.add(Dense(256, activation='relu')) #全连接层model.add(Dropout(0.2)) #防止过拟合model.add(Dense(units=2, activation='softmax')) #输出层
# 模型可视化model.summary()
# 激活神经网络 model.compile(optimizer = 'adam', #优化器 loss = 'categorical_crossentropy', #损失 metrics = ['accuracy'] #计算误差或准确率 )
#训练(训练数据、训练类标、batch—size每次256条训练、epochs、随机选择、验证集20%)history = model.fit(trainSeq, trainCate, batch_size=256, epochs=6, validation_split=0.2)model.save("TextCNN")
#----------------------------------第五步 预测模型--------------------------------# 预测与评估mainModel = load_model("TextCNN")result = mainModel.predict(testSeq) #测试样本print(result)print(np.argmax(result,axis=1))score = mainModel.evaluate(testSeq, testCate, batch_size=32)print(score)
#----------------------------------第五步 可视化--------------------------------import matplotlib.pyplot as pltplt.plot(history.history['accuracy'])plt.plot(history.history['val_accuracy'])plt.title('Model accuracy')plt.ylabel('Accuracy')plt.xlabel('Epoch')plt.legend(['Train','Valid'], loc='upper left')
plt.plot(history.history['loss'])plt.plot(history.history['val_loss'])plt.title('Model loss')plt.ylabel('Loss')plt.xlabel('Epoch')plt.legend(['Train','Valid'], loc='upper left')plt.show()
复制代码

四.总结

总之,本文通过 Keras 实现了一个 CNN 文本分类学习的案例,并详细介绍了文本分类原理知识及与机器学习对比。


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

发布于: 刚刚阅读数: 3
用户头像

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

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

评论

发布
暂无评论
详解CNN实现中文文本分类过程