python中文文本分类
一,中文文本分類流程:
二,具體實現
1.1 打標簽:
對評論數據打好標簽,這里將汽車評論數據分為正面和負面兩類。假設負面標簽為0,正面標簽為1.
1.2 整理數據集:
一般可分為訓練集,驗證集和測試集。為什么要這么分,這篇博文給了淺顯的解釋:訓練集、驗證集和測試集的意義本試驗將數據分為訓練集和測試集兩類。
1.3 得到訓練集預料庫:
例如,訓練集文本的路徑:train_data/train_positive.xlsx , train_data/train_negtive.xlsx…
1.4 得到測試集語料庫:
例如,測試集文本的路徑:test_data/test_negtive.xlsx , test_data/test_positive.xlsx…
2 中文分詞
2.1 概述
第1節預處理中的語料庫都是沒有分詞的原始語料(即連續的句子,而后面的工作需要把文本分為一個個單詞),現在需要對這些文本進行分詞,只有這樣,才能在基于單詞的基礎上,對文檔進行結構化表示。
中文分詞有其特有的難點(相對于英文而言),最終完全解決中文分詞的算法是基于概率圖模型的條件隨機場(CRF),CRF的原理我們不細說了,直接調用中文分詞的工具,這里用的是python第三方分詞庫jieba(所采用的算法就是條件隨機場)
關于分詞庫的更多討論可以參考這篇文章:python分詞工具推薦
2.2 jieba分詞簡述
首先講解jieba分詞使用方法(詳細的和更進一步的,可以參考jieba分詞原理
jieba.cut 方法接受三個輸入參數: 需要分詞的字符串;cut_all 參數用來控制是否采用全模式;HMM 參數用來控制是否使用 HMM 模型 jieba.cut_for_search 方法接受兩個參數:需要分詞的字符串;是否使用 HMM 模型。該方法適合用于搜索引擎構建倒排索引的分詞,粒度比較細 待分詞的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建議直接輸入 GBK 字符串,可能無法預料地錯誤解碼成 UTF-8 jieba.cut 以及 jieba.cut_for_search 返回的結構都是一個可迭代的 generator,可以使用 for 循環來獲得分詞后得到的每一個詞語(unicode),或者用 jieba.lcut 以及 jieba.lcut_for_search 直接返回 list jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定義分詞器,可用于同時使用不同詞典。jieba.dt 為默認分詞器,所有全局分詞相關函數都是該分詞器的映射。實例代碼:
import jiebaseg_list = jieba.cut("我來到北京清華大學", cut_all=True) print("Full Mode: " + "/ ".join(seg_list)) # 全模式seg_list = jieba.cut("我來到北京清華大學", cut_all=False) print("Default Mode: " + "/ ".join(seg_list)) # 精確模式seg_list = jieba.cut("他來到了網易杭研大廈") # 默認是精確模式 print(", ".join(seg_list))seg_list = jieba.cut_for_search("小明碩士畢業于中國科學院計算所,后在日本京都大學深造") # 搜索引擎模式 print(", ".join(seg_list)) 輸出: 【全模式】: 我/ 來到/ 北京/ 清華/ 清華大學/ 華大/ 大學【精確模式】: 我/ 來到/ 北京/ 清華大學【新詞識別】:他, 來到, 了, 網易, 杭研, 大廈 (此處,“杭研”并沒有在詞典中,但是也被Viterbi算法識別出來了)【搜索引擎模式】: 小明, 碩士, 畢業, 于, 中國, 科學, 學院, 科學院, 中國科學院, 計算, 計算所, 后, 在, 日本, 京都, 大學, 日本京都大接下來,我們要通過python編程,來將1.3節中的 ./train_data/原始訓練語料庫和1.4節中的./test_data/原始測試語料庫進行分詞,分詞后保存的路徑可以設置為:./train_data_seg/和./test_data_seg/
代碼如下,思路很簡單,就是將excel里面的評論數據一條一條取出來保存到DataFrame中,然后遍歷DataFrame中的文本數據,將每個文本依次進行分詞之后保存到對應路徑。
# encoding = utf-8import sys import os import jieba import jieba.analyse import pandas as pd import xlrd import importlib from pandas import DataFrame from sklearn.datasets.base import Bunch ''' #全模式 seg_list = jieba.cut("我是一位小仙女", cut_all = True) print("Full Mode:" + "/".join(seg_list))#精確模式,cut_all 屬性不寫的話也是默認精確模式 seg_list = jieba.cut("DX7 Prime是國產汽車SUV由內到外最上乘的!", cut_all= False) print("Default Mode:" + "/".join(seg_list)) '''#保存至文件 def savefile(savepath, content):with open(savepath, "wb") as fp:fp.write(content.encode("utf-8"))# 讀取文件 def readfile(path):with open(path , 'rb') as fp:content = fp.read()return content#讀取Excel中的文件 def read_excel(path):df = pd.read_excel(path)return df#搜索引擎模式,對數據集做分詞切分 def data_segment(df):list=[]for item in df :seg = jieba.cut_for_search(item)seg_str = " ".join(seg)list.append(seg_str)dff = pd.DataFrame(list, columns=["context"])return dffdef text_segment(df, seg_path ):"""corpus_path是未分詞預料庫的路徑seg_path是分詞后語料庫的路徑"""list =[]i = 1if not os.path.exists(seg_path):os.makedirs(seg_path)for item in df:seg =jieba.cut(item)seg_str =",".join(seg)class_path = str(i)+".txt"savefile(seg_path + class_path, "".join(seg_str))i+=1if __name__ == "__main__":#訓練集df_positive = pd.read_excel('train_data/train_positive.xlsx')seg_path1 = "./train_data_seg/1/" # 分詞后分類語料庫路徑df_positive_segment = text_segment(df_positive['context'], seg_path1)df_negtive = pd.read_excel('train_data/train_negtive.xlsx')seg_path0 = "./train_data_seg/0/" #分詞后分類預料庫的路徑df_negtive_segment = text_segment(df_negtive['context'], seg_path0)#df_negtive_segment = data_segment(df_negtive['context'])#測試集test_positive = pd.read_excel('test_data/test_positive.xlsx')seg_test_path1 = "./test_data_seg/1/" #分詞后分類語料庫路徑test_positive_segment = text_segment(test_positive['context'] ,seg_test_path1)test_negtive = pd.read_excel('test_data/test_negtive.xlsx')seg_test_path0 = "./test_data_seg/0/" #分詞后分類語料庫路徑test_negtive_segment = text_segment(test_negtive['context'] , seg_test_path0)現在,我們已經得到了分詞后的訓練集語料庫和測試集語料庫,下面我們要把這兩個數據集表示為變量,從而為下面程序調用提供服務。我們采用的是Scikit-Learn庫中的Bunch數據結構來表示這兩個數據集。
首先來看看Bunch:,通俗的說:
Bunch這玩意兒,其實就相當于python中的字典。你往里面傳什么,它就存什么。
接下來,讓我們看看的我們的數據集(訓練集)有哪些信息:
1,類別,也就是所有分類類別的集合,即我們./train_data_seg/和./test_data_seg/下的所有子目錄的名字。我們在這里不妨把它叫做target_name(這是一個列表)2,文本文件名。例如./train_data_seg/0/1.txt,我們可以把所有文件名集合在一起做一個列表,叫做filenames3,文本標簽(就是文本的類別),不妨叫做label(與2中的filenames相對應)例如2中的文本“1.txt”在./train_data_seg/0/目錄下,則它的標簽就是0。文本標簽與1中的類別區別在于:文本標簽集合里面的元素就是1中類別,而文本標簽集合的元素是可以重復的,因為./train_data_seg/0/目錄下有好多文本,不是嗎?相應的,1中的類別集合元素顯然都是獨一無二的類別。4,文本內容(contens)。上一步代碼我們已經成功的把文本內容進行了分詞,并且去除掉了所有的換行,得到的其實就是一行詞袋。那么,用Bunch表示,就是:
from sklearn.datasets.base import Bunch
bunch = Bunch(target_name=[],label=[],filenames=[],contents=[])
我們在Bunch對象里面創建了有4個成員:
target_name:是一個list,存放的是整個數據集的類別集合。
label:是一個list,存放的是所有文本的標簽。
filenames:是一個list,存放的是所有文本文件的名字。
contents:是一個list,分詞后文本文件(一個文本文件只有一行)
代碼實現:
3,結構化表示–向量空間模型
在第2節中,我們對原始數據集進行了分詞處理,并且通過綁定為Bunch數據類型,實現了數據集的變量表示。詞向量并沒有清晰的概念,簡單來講,詞向量就是詞向量空間里面的一個向量。這里有一篇非常棒的文章《Deep Learning in NLP (一)詞向量和語言模型》
你可以類比為三維空間里面的一個向量,例如:
如果我們規定詞向量空間為:(我,喜歡,相國大人),這相當于三維空間里面的(x,y,z)只不過這里的x,y,z的名字變成了“我”,“喜歡”,“相國大人”
現在有一個詞向量是:我 喜歡 喜歡相國大人
表示在詞向量空間中就變為:(1,2,1),歸一化后可以表示為:(0.166666666667 0.333333333333 0.166666666667)表示在剛才的詞向量空間中就是這樣:
接下來我們要做的,就是把所有這些詞統一到同一個詞向量空間中。
為了節省空間,我們首先將訓練集中每個文本中一些垃圾詞匯去掉。所謂的垃圾詞匯,就是指意義模糊的詞,或者一些語氣助詞,標點符號等等,通常他們對文本起不了分類特征的意義。這些垃圾詞匯我們稱之為停用詞。把所有停用詞集合起來構成一張停用詞表格,這樣,以后我們處理文本時,就可以從這個根據表格,過濾掉文本中的一些垃圾詞匯了。
下面的程序,目的就是要將訓練集所有文本文件統一到同一個詞向量空間中。
下面的一節主要目標是希望得到兩個東西:
1.詞典(單詞和單詞對應的序號)
2.權重矩陣tdm,其中,權重矩陣是一個二維矩陣,tdm[i][j]表示,第j個詞(即詞典中的序號)在第i個類別中的IF-IDF值(下文有講解)。
事實上,tdm的每一列都是一個單詞在各個類別中的全職。我們把這每一列當作詞向量。
4,權重策略–TF-IDF
什么是TF-IDF?今后有精力我會在這里更新補充,現在,先給你推薦一篇非常棒的文章《使用scikit-learn工具計算文本TF-IDF值》
下面,我們假定你已經對TF-IDF有了最基本的了解。請你動動你的小腦袋瓜想一想,我們把訓練集文本轉換成了一個TF-IDF詞向量空間,姑且叫它為A空間吧。那么我們還有測試集數據,我們以后實際運用時,還會有新的數據,這些數據顯然也要轉到詞向量空間,那么應該和A空間為同一個空間嗎?
是的。
即使測試集出現了新的詞匯(不是停用詞),即使新的文本數據有新的詞匯,只要它不是訓練集生成的TF-IDF詞向量空間中的詞,我們就都不予考慮。這就實現了所有文本詞向量空間“大一統”,也只有這樣,大家才在同一個世界里。才能進行下一步的研究。
下面的程序就是要將訓練集所有文本文件(詞向量)統一到同一個TF-IDF詞向量空間中(或者叫做用TF-IDF算法計算權重的有權詞向量空間)。這個詞向量空間最終存放在train_word_bag/tfdifspace.dat中。
這段代碼你可能有點看不懂,因為我估計你可能比較懶,還沒看過TF-IDF(盡管我剛才已經給你推薦那篇文章了)。你只需要明白,它把一大坨訓練集數據成功的構建了一個TF-IDF詞向量空間,空間的各個詞都是出自這個訓練集(去掉了停用詞)中,各個詞的權值也都一并保存了下來,叫做權重矩陣。
需要注意的是,你要明白,權重矩陣是一個二維矩陣,a[i][j]表示,第j個詞在第i個類別中的IF-IDF值(看到這里,我估計你壓根就沒去看那篇文章,所以你可能到現在也不知道 這是個啥玩意兒。。。)
請記住權重矩陣這個詞,代碼解釋中我會用到。
# -*- coding: UTF-8 -*-import os import sys from scikit_Bunch import Bunch from sklearn.feature_extraction.text import TfidfVectorizerimport pickle#讀取文件 def _readfile(path):with open(path, "r" ,encoding="utf-8") as fp:content = fp.read()return content#讀取bunch對象 def _readbunchobj(path):with open(path, "rb" ) as file_obj:bunch = pickle.load(file_obj)return bunch#寫入bunch對象 def _writebunchobj(path, bunch_obj):with open(path, "wb") as file_obj:pickle.dump(bunch_obj, file_obj, 0)#這個函數用于創建TF-IDF詞向量空間 def Vector_Space(stopWords_path, bunch_path, space_path, train_tfidf_path = None):stopWords = _readfile(stopWords_path).splitlines() #讀取停用詞stopWords[0] = "???"bunch = _readbunchobj(bunch_path) #導入分詞后的詞向量bunch對象# 構建tf-idf詞向量空間對象tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={})'''與下面這2行代碼等價的代碼是:vectorizer=CountVectorizer()#構建一個計算詞頻(TF)的玩意兒,當然這里面不只是可以做這些transformer=TfidfTransformer()#構建一個計算TF-IDF的玩意兒tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus))#vectorizer.fit_transform(corpus)將文本corpus輸入,得到詞頻矩陣#將這個矩陣作為輸入,用transformer.fit_transform(詞頻矩陣)得到TF-IDF權重矩陣看名字你也應該知道:TfidfTransformer + CountVectorizer = TfidfVectorizer下面的代碼一步到位,把上面的兩個步驟一次性全部完成值得注意的是,CountVectorizer()和TfidfVectorizer()里面都有一個成員叫做vocabulary_(后面帶一個下劃線)這個成員的意義,與我們之前在構建Bunch對象時提到的自己定義的那個vocabulary的意思是一樣的,只不過一個是私有成員,一個是外部輸入,原則上應該保持一致。創建tfidfspace中定義的vocabulary就應該被賦值為這個vocabulary_'''#構建一個快樂地一步到位的玩意兒,專業一點兒叫做:使用TfidfVectorizer初始化向量空間模型#這里面有TF-IDF權重矩陣還有我們要的詞向量空間坐標軸信息vocabulary_if train_tfidf_path is not None:trainbunch = _readbunchobj(train_tfidf_path)tfidfspace.vocabulary = trainbunch.vocabularyvectorizer = TfidfVectorizer(stop_words=stopWords, sublinear_tf=True,max_df=0.5, vocabulary=trainbunch.vocabulary,analyzer='word',token_pattern=u"(?u)\\b\\w+\\b")# 此時tdm里面存儲的就是if-idf權值矩陣print(bunch.contents)#print("gggggggggg")tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)'''stop_words:參數是用來傳入停用詞,以后我們獲得vocabulary_的時候,就會根據文本信息去掉停用詞得到sublinear_tf:計算tf值采用亞線性策略。比如,我們以前算tf是詞頻,現在用1+log(tf)來充當詞頻。smooth_idf:計算idf的時候log(分子/分母)分母有可能是0,smooth_idf會采用log(分子/(1+分母))的方式解決。默認已經開啟,無需關心。norm:歸一化,我們計算TF-IDF的時候,是用TF*IDF,TF可以是歸一化的,也可以是沒有歸一化的,一般都是采用歸一化的方法,默認開啟.max_df:有些詞,他們的文檔頻率太高了(一個詞如果每篇文檔都出現,那還有必要用它來區分文本類別嗎?當然不用了呀),所以,我們可以設定一個閾值,比如float類型0.5(取值范圍[0.0,1.0]),表示這個詞如果在整個數據集中超過50%的文本都出現了,那么我們也把它列為臨時停用詞。當然你也可以設定為int型,例如max_df=10,表示這個詞如果在整個數據集中超過10的文本都出現了,那么我們也把它列為臨時停用詞。min_df:與max_df相反,雖然文檔頻率越低,似乎越能區分文本,可是如果太低,例如10000篇文本中只有1篇文本出現過這個詞,僅僅因為這1篇文本,就增加了詞向量空間的維度,太不劃算。當然,max_df和min_df在給定vocabulary參數時,就失效了。'''else:vectorizer = TfidfVectorizer(stop_words=stopWords, sublinear_tf=True, max_df=0.5, analyzer='word',token_pattern=u"(?u)\\b\\w+\\b")tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)tfidfspace.vocabulary = vectorizer.vocabulary_print(tfidfspace)print("666666666666")_writebunchobj(space_path, tfidfspace)if __name__ == '__main__':#訓練集stopword_path = "train_word_bag/stop_words.txt"#停用詞表的路徑bunch_path = "train_word_bag/train_set1.dat" #導入訓練集Bunch的路徑space_path = "train_word_bag/tfdifspace.dat" # 詞向量空間保存路徑Vector_Space(stopword_path, bunch_path, space_path)#測試集bunch_path = "test_word_bag/test_set.dat" #導入測試集Bunch的路徑space_path = "test_word_bag/testspace.dat" #測試集詞向量保存路徑train_tfidf_path = "train_word_bag/tfdifspace.dat"Vector_Space(stopword_path, bunch_path, space_path , train_tfidf_path)上面的代碼運行之后,會將訓練集數據轉換為TF-IDF詞向量空間中的實例,保存在train_word_bag/tfdifspace.dat中,具體來說,這個文件里面有兩個我們感興趣的東西,一個是vocabulary,即詞向量空間坐標,一個是tdm,即訓練集的TF-IDF權重矩陣。
接下來,我們要開始第5步的操作,設計分類器,用訓練集訓練,用測試集測試。在做這些工作之前,你一定要記住,首先要把測試數據也映射到上面這個TF-IDF詞向量空間中,也就是說,測試集和訓練集處在同一個詞向量空間(vocabulary相同),只不過測試集有自己的tdm,與訓練集(train_word_bag/tfdifspace.dat)中的tdm不同而已。
同一個世界,同一個夢想。
至于說怎么弄,請看下節。
5,分類器
這里我們采用的是樸素貝葉斯分類器,今后我們會詳細講解它。
現在,你即便不知道這是個啥玩意兒,也一點不會影響你,這個分類器我們有封裝好了的函數,MultinomialNB,這玩意兒獲取訓練集的權重矩陣和標簽,進行訓練,然后獲取測試集的權重矩陣,進行預測(給出預測標簽)。
from sklearn.naive_bayes import MultinomialNB #導入多項式貝葉斯算法 from sklearn import metrics import pickle from sklearn.feature_extraction.text import TfidfVectorizer#讀取bunch對象 def _readbunchobj(path):with open(path, "rb") as file_obj:bunch = pickle.load(file_obj)return bunch#導入訓練集 trainpath = "train_word_bag/tfdifspace.dat" train_set =_readbunchobj(trainpath) print(train_set.tdm.shape)#導入測試集 testpath = "test_word_bag/testspace.dat" test_set = _readbunchobj(testpath) print(test_set.tdm.shape)# 訓練分類器:輸入詞袋向量和分類標簽,alpha:0.001 alpha越小,迭代次數越多,精度越高 clf = MultinomialNB(alpha=0.001).fit(train_set.tdm, train_set.label)#之前報訓練集和測試集維度不匹配,predict方法出錯,百度搜到的解決辦法,然而并沒有什么鬼用的三行 # vectorizer = TfidfVectorizer() # fea_train = vectorizer.fit_transform(train_set) # fea_test = vectorizer.transform(test_set)#預測分類結果,輸出是測試訓練集預測出來的標簽列表 predicted = clf.predict(test_set.tdm)for flabel, file_name, expct_cate in zip(test_set.label, test_set.filenames, predicted):if flabel != expct_cate:print(file_name, ": 實際類別:", flabel, " -->預測類別:", expct_cate)# 計算分類精度: def metrics_result(actual, predict):print('精度:{0:.3f}'.format(metrics.precision_score(actual, predict, average='weighted')))print('召回:{0:0.3f}'.format(metrics.recall_score(actual, predict, average='weighted')))print('f1-score:{0:.3f}'.format(metrics.f1_score(actual, predict, average='weighted')))metrics_result(test_set.label, predicted)當然,你也可以采用其他分類器,比如KNN
總結
以上是生活随笔為你收集整理的python中文文本分类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 都说dlib是人脸识别的神器,那到底能不
- 下一篇: C++知识总结(1)--变量和基本类型