Python人工智能 | 二十一.CNN和Word2Vec中文文本分類詳解及與機器學習分類對比
一.文本分類
文本分類旨在對文本集按照一定的分類體系或標準進行自動分類標記,屬于一種基于分類體系的自動分類。文本分類最早可以追溯到上世紀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等
推薦牛亞峰老師的文章:基于 word2vec 和 CNN 的文本分類 :綜述 & 實踐
二.基于隨機森林的文本分類
該部分主要圍繞常見的文本分類案例進行講解,由于隨機森林效果較好,故主要分享該方法。具體步驟包括:
- 讀取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跳字模型。默認的優化方法負采樣,更多參數解釋請讀者百度。
參考作者前文:
- gensim詞向量Word2Vec安裝及《慶余年》中文短文本相似度計算
- 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文本分類學習的案例,并詳細介紹了文本分類原理知識及與機器學習對比。最后,作為人工智能的菜鳥,我希望自己能不斷進步并深入,后續將它應用于圖像識別、網絡安全、對抗樣本等領域,指導大家撰寫簡單的學術論文,一起加油!感謝這些年遇到很多以前進步的博友,共勉~