一,中文文本分類流程: 1,預(yù)處理 2,中文分詞 3,結(jié)構(gòu)化表示--構(gòu)建詞向量空間 4,權(quán)重策略--TF-IDF 5,分類器 6,評價(jià) 二,具體細(xì)節(jié)1,預(yù)處理。希望得到這樣的目標(biāo):
2,中文分詞。
實(shí)例代碼:jieba.cut 方法接受三個(gè)輸入?yún)?shù): 需要分詞的字符串;cut_all 參數(shù)用來控制是否采用全模式;HMM 參數(shù)用來控制是否使用 HMM 模型jieba.cut_for_search 方法接受兩個(gè)參數(shù):需要分詞的字符串;是否使用 HMM 模型。該方法適合用于搜索引擎構(gòu)建倒排索引的分詞,粒度比較細(xì)待分詞的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建議直接輸入 GBK 字符串,可能無法預(yù)料地錯(cuò)誤解碼成 UTF-8jieba.cut 以及 jieba.cut_for_search 返回的結(jié)構(gòu)都是一個(gè)可迭代的 generator,可以使用 for 循環(huán)來獲得分詞后得到的每一個(gè)詞語(unicode),或者用jieba.lcut 以及 jieba.lcut_for_search 直接返回 listjieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定義分詞器,可用于同時(shí)使用不同詞典。jieba.dt 為默認(rèn)分詞器,所有全局分詞相關(guān)函數(shù)都是該分詞器的映射。 import jieba seg_list = jieba.cut("我來到北京清華大學(xué)", cut_all=True) print("Full Mode: " + "/ ".join(seg_list)) # 全模式 seg_list = jieba.cut("我來到北京清華大學(xué)", cut_all=False) print("Default Mode: " + "/ ".join(seg_list)) # 精確模式 seg_list = jieba.cut("他來到了網(wǎng)易杭研大廈") # 默認(rèn)是精確模式 print(", ".join(seg_list)) seg_list = jieba.cut_for_search("小明碩士畢業(yè)于中國科學(xué)院計(jì)算所,后在日本京都大學(xué)深造") # 搜索引擎模式 print(", ".join(seg_list)) 輸出: 【全模式】: 我/ 來到/ 北京/ 清華/ 清華大學(xué)/ 華大/ 大學(xué) 【精確模式】: 我/ 來到/ 北京/ 清華大學(xué) 【新詞識(shí)別】:他, 來到, 了, 網(wǎng)易, 杭研, 大廈 (此處,“杭研”并沒有在詞典中,但是也被Viterbi算法識(shí)別出來了) 【搜索引擎模式】: 小明, 碩士, 畢業(yè), 于, 中國, 科學(xué), 學(xué)院, 科學(xué)院, 中國科學(xué)院, 計(jì)算, 計(jì)算所, 后, 在, 日本, 京都, 大學(xué), 日本京都大 2.2.2 #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: corpus_segment.py @time: 2017/2/5 15:28 @software: PyCharm """ import sys import os import jieba # 配置utf-8輸出環(huán)境 reload(sys) sys.setdefaultencoding('utf-8') # 保存至文件 def savefile(savepath, content): with open(savepath, "wb") as fp: fp.write(content) ''' 上面兩行是python2.6以上版本增加的語法,省略了繁瑣的文件close和try操作 2.5版本需要from __future__ import with_statement 新手可以參考這個(gè)鏈接來學(xué)習(xí)http:///archives/325 ''' # 讀取文件 def readfile(path): with open(path, "rb") as fp: content = fp.read() return content def corpus_segment(corpus_path, seg_path): ''' corpus_path是未分詞語料庫路徑 seg_path是分詞后語料庫存儲(chǔ)路徑 ''' catelist = os.listdir(corpus_path) # 獲取corpus_path下的所有子目錄 ''' 其中子目錄的名字就是類別名,例如: train_corpus/art/21.txt中,'train_corpus/'是corpus_path,'art'是catelist中的一個(gè)成員 ''' # 獲取每個(gè)目錄(類別)下所有的文件 for mydir in catelist: ''' 這里mydir就是train_corpus/art/21.txt中的art(即catelist中的一個(gè)類別) ''' class_path = corpus_path + mydir + "/" # 拼出分類子目錄的路徑如:train_corpus/art/ seg_dir = seg_path + mydir + "/" # 拼出分詞后存貯的對應(yīng)目錄路徑如:train_corpus_seg/art/ if not os.path.exists(seg_dir): # 是否存在分詞目錄,如果沒有則創(chuàng)建該目錄 os.makedirs(seg_dir) file_list = os.listdir(class_path) # 獲取未分詞語料庫中某一類別中的所有文本 ''' train_corpus/art/中的 21.txt, 22.txt, 23.txt ... file_list=['21.txt','22.txt',...] ''' for file_path in file_list: # 遍歷類別目錄下的所有文件 fullname = class_path + file_path # 拼出文件名全路徑如:train_corpus/art/21.txt content = readfile(fullname) # 讀取文件內(nèi)容 '''此時(shí),content里面存貯的是原文本的所有字符,例如多余的空格、空行、回車等等, 接下來,我們需要把這些無關(guān)痛癢的字符統(tǒng)統(tǒng)去掉,變成只有標(biāo)點(diǎn)符號(hào)做間隔的緊湊的文本內(nèi)容 ''' content = content.replace("\r\n", "") # 刪除換行 content = content.replace(" ", "")#刪除空行、多余的空格 content_seg = jieba.cut(content) # 為文件內(nèi)容分詞 savefile(seg_dir + file_path, " ".join(content_seg)) # 將處理后的文件保存到分詞后語料目錄 print "中文語料分詞結(jié)束?。?!" ''' 如果你對if __name__=="__main__":這句不懂,可以參考下面的文章 http://imoyao.lofter.com/post/3492bc_bd0c4ce 簡單來說如果其他python文件調(diào)用這個(gè)文件的函數(shù),或者把這個(gè)文件作為模塊 導(dǎo)入到你的工程中時(shí),那么下面的代碼將不會(huì)被執(zhí)行,而如果單獨(dú)在命令行中 運(yùn)行這個(gè)文件,或者在IDE(如pycharm)中運(yùn)行這個(gè)文件時(shí)候,下面的代碼才會(huì)運(yùn)行。 即,這部分代碼相當(dāng)于一個(gè)功能測試。 如果你還沒懂,建議你放棄IT這個(gè)行業(yè)。 ''' if __name__=="__main__": #對訓(xùn)練集進(jìn)行分詞 corpus_path = "./train_corpus/" # 未分詞分類語料庫路徑 seg_path = "./train_corpus_seg/" # 分詞后分類語料庫路徑 corpus_segment(corpus_path,seg_path) #對測試集進(jìn)行分詞 corpus_path = "./test_corpus/" # 未分詞分類語料庫路徑 seg_path = "./test_corpus_seg/" # 分詞后分類語料庫路徑 corpus_segment(corpus_path,seg_path) 截止目前,我們已經(jīng)得到了分詞后的訓(xùn)練集語料庫和測試集語料庫,下面我們要把這兩個(gè)數(shù)據(jù)集表示為變量,從而為下面程序調(diào)用提供服務(wù)。我們采用的是Scikit-Learn庫中的Bunch數(shù)據(jù)結(jié)構(gòu)來表示這兩個(gè)數(shù)據(jù)集。你或許對于Scikit-Learn和Bunch并不是特別了解,而官方的技術(shù)文檔有兩千多頁你可能也沒耐心去看,好在你們有相國大人。下面我們 以這兩個(gè)數(shù)據(jù)集為背景,對Bunch做一個(gè)非常通俗的講解,肯定會(huì)讓你一下子就明白。 首先來看看Bunch: Bunch這玩意兒,其實(shí)就相當(dāng)于python中的字典。你往里面?zhèn)魇裁?,它就存什么?/p> 好了,解釋完了。 是不是很簡單? 在本篇博文中,你對Bunch能夠有這種層次的理解,就足夠了。如果你想繼續(xù)詳細(xì)透徹的理解Bunch,請見博主的另一篇博文《暫時(shí)還沒寫,寫完在這里更新鏈接》 接下來,讓我們看看的我們的數(shù)據(jù)集(訓(xùn)練集)有哪些信息:
那么,用Bunch表示,就是: from sklearn.datasets.base import Bunch 我們在Bunch對象里面創(chuàng)建了有4個(gè)成員: 如果你還沒有明白,看一下下面這個(gè)圖,你總該明白了: Bunch: 下面,我們將文本文件轉(zhuǎn)為Bunch類形: #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: corpus2Bunch.py @time: 2017/2/7 7:41 @software: PyCharm """ import sys reload(sys) sys.setdefaultencoding('utf-8') import os#python內(nèi)置的包,用于進(jìn)行文件目錄操作,我們將會(huì)用到os.listdir函數(shù) import cPickle as pickle#導(dǎo)入cPickle包并且取一個(gè)別名pickle ''' 事實(shí)上python中還有一個(gè)也叫作pickle的包,與這里的名字相同了,無所謂 關(guān)于cPickle與pickle,請參考博主另一篇博文: python核心模塊之pickle和cPickle講解 http://blog.csdn.net/github_36326955/article/details/54882506 本文件代碼下面會(huì)用到cPickle中的函數(shù)cPickle.dump ''' from sklearn.datasets.base import Bunch #這個(gè)您無需做過多了解,您只需要記住以后導(dǎo)入Bunch數(shù)據(jù)結(jié)構(gòu)就像這樣就可以了。 #今后的博文會(huì)對sklearn做更有針對性的講解 def _readfile(path): '''讀取文件''' #函數(shù)名前面帶一個(gè)_,是標(biāo)識(shí)私有函數(shù) # 僅僅用于標(biāo)明而已,不起什么作用, # 外面想調(diào)用還是可以調(diào)用, # 只是增強(qiáng)了程序的可讀性 with open(path, "rb") as fp:#with as句法前面的代碼已經(jīng)多次介紹過,今后不再注釋 content = fp.read() return content def corpus2Bunch(wordbag_path,seg_path): catelist = os.listdir(seg_path)# 獲取seg_path下的所有子目錄,也就是分類信息 #創(chuàng)建一個(gè)Bunch實(shí)例 bunch = Bunch(target_name=[], label=[], filenames=[], contents=[]) bunch.target_name.extend(catelist) ''' extend(addlist)是python list中的函數(shù),意思是用新的list(addlist)去擴(kuò)充 原來的list ''' # 獲取每個(gè)目錄下所有的文件 for mydir in catelist: class_path = seg_path + mydir + "/" # 拼出分類子目錄的路徑 file_list = os.listdir(class_path) # 獲取class_path下的所有文件 for file_path in file_list: # 遍歷類別目錄下文件 fullname = class_path + file_path # 拼出文件名全路徑 bunch.label.append(mydir) bunch.filenames.append(fullname) bunch.contents.append(_readfile(fullname)) # 讀取文件內(nèi)容 '''append(element)是python list中的函數(shù),意思是向原來的list中添加element,注意與extend()函數(shù)的區(qū)別''' # 將bunch存儲(chǔ)到wordbag_path路徑中 with open(wordbag_path, "wb") as file_obj: pickle.dump(bunch, file_obj) print "構(gòu)建文本對象結(jié)束?。。? if __name__ == "__main__":#這個(gè)語句前面的代碼已經(jīng)介紹過,今后不再注釋 #對訓(xùn)練集進(jìn)行Bunch化操作: wordbag_path = "train_word_bag/train_set.dat" # Bunch存儲(chǔ)路徑 seg_path = "train_corpus_seg/" # 分詞后分類語料庫路徑 corpus2Bunch(wordbag_path, seg_path) # 對測試集進(jìn)行Bunch化操作: wordbag_path = "test_word_bag/test_set.dat" # Bunch存儲(chǔ)路徑 seg_path = "test_corpus_seg/" # 分詞后分類語料庫路徑 corpus2Bunch(wordbag_path, seg_path) 3,結(jié)構(gòu)化表示--向量空間模型在第2節(jié)中,我們對原始數(shù)據(jù)集進(jìn)行了分詞處理,并且通過綁定為Bunch數(shù)據(jù)類型,實(shí)現(xiàn)了數(shù)據(jù)集的變量表示。事實(shí)上在第2節(jié)中,我們通過分詞,已經(jīng)將每一個(gè)文本文件表示為了一個(gè)詞向量了。也許你對于什么是詞向量并沒有清晰的概念,這里有一篇非常棒的文章《Deep Learning in NLP (一)詞向量和語言模型》,簡單來講,詞向量就是詞向量空間里面的一個(gè)向量。 你可以類比為三維空間里面的一個(gè)向量,例如: 如果我們規(guī)定詞向量空間為:(我,喜歡,相國大人),這相當(dāng)于三維空間里面的(x,y,z)只不過這里的x,y,z的名字變成了“我”,“喜歡”,“相國大人” 現(xiàn)在有一個(gè)詞向量是:我喜歡 喜歡相國大人 表示在詞向量空間中就變?yōu)椋海?,2,1),歸一化后可以表示為:(0.166666666667 0.333333333333 0.166666666667)表示在剛才的詞向量空間中就是這樣: 但是在我們第2節(jié)處理的這些文件中,詞向量之間的單詞個(gè)數(shù)并不相同,詞向量的涵蓋的單詞也不盡相同。他們并不在一個(gè)空間里,換句話說,就是他們之間沒有可比性,例如: 詞向量1:我喜歡相國大人,對應(yīng)的詞向量空間是(我,喜歡,相國大人),可以表示為(1,1,1) 詞向量2:她不喜歡我,對應(yīng)的詞向量空間是(她,不,喜歡,我),可以表示為(1,1,1,1) 兩個(gè)空間不一樣 因此,接下來我們要做的,就是把所有這些詞向量統(tǒng)一到同一個(gè)詞向量空間中,例如,在上面的例子中,我們可以設(shè)置詞向量空間為(我,喜歡,相國大人,她,不) 這樣,詞向量1和詞向量2分別可以表示為(1,1,1,0,0)和(1,1,0,1,1),這樣兩個(gè)向量就都在同一個(gè)空間里面了??梢赃M(jìn)行比較和各種運(yùn)算了。 也許你已經(jīng)發(fā)現(xiàn)了,這樣做的一個(gè)很糟糕的結(jié)果是,我們要把訓(xùn)練集內(nèi)所有出現(xiàn)過的單詞,都作為一個(gè)維度,構(gòu)建統(tǒng)一的詞向量空間,即使是中等大小的文本集合,向量維度也很輕易就達(dá)到數(shù)十萬維。為了節(jié)省空間,我們首先將訓(xùn)練集中每個(gè)文本中一些垃圾詞匯去掉。所謂的垃圾詞匯,就是指意義模糊的詞,或者一些語氣助詞,標(biāo)點(diǎn)符號(hào)等等,通常他們對文本起不了分類特征的意義。這些垃圾詞匯我們稱之為停用詞。把所有停用詞集合起來構(gòu)成一張停用詞表格,這樣,以后我們處理文本時(shí),就可以從這個(gè)根據(jù)表格,過濾掉文本中的一些垃圾詞匯了。 你可以從這里下載停用詞表:hlt_stop_words.txt 存放在這里路徑中:train_word_bag/hlt_stop_words.txt 下面的程序,目的就是要將訓(xùn)練集所有文本文件(詞向量)統(tǒng)一到同一個(gè)詞向量空間中。值得一提的是,在詞向量空間中,事實(shí)上不同的詞,它的權(quán)重是不同的,它對文本分類的影響力也不同,為此我們希望得到的詞向量空間不是等權(quán)重的空間,而是不同權(quán)重的詞向量空間。我們把帶有不同權(quán)重的詞向量空間叫做“加權(quán)詞向量空間”,也有的技術(shù)文檔將其稱為“加權(quán)向量詞袋”,一個(gè)意思。 現(xiàn)在的問題是,如何計(jì)算不同詞的權(quán)重呢? 4,權(quán)重策略--TF-IDF什么是TF-IDF?今后有精力我會(huì)在這里更新補(bǔ)充,現(xiàn)在,先給你推薦一篇非常棒的文章《使用scikit-learn工具計(jì)算文本TF-IDF值》 下面,我們假定你已經(jīng)對TF-IDF有了最基本的了解。請你動(dòng)動(dòng)你的小腦袋瓜想一想,我們把訓(xùn)練集文本轉(zhuǎn)換成了一個(gè)TF-IDF詞向量空間,姑且叫它為A空間吧。那么我們還有測試集數(shù)據(jù),我們以后實(shí)際運(yùn)用時(shí),還會(huì)有新的數(shù)據(jù),這些數(shù)據(jù)顯然也要轉(zhuǎn)到詞向量空間,那么應(yīng)該和A空間為同一個(gè)空間嗎? 是的。 即使測試集出現(xiàn)了新的詞匯(不是停用詞),即使新的文本數(shù)據(jù)有新的詞匯,只要它不是訓(xùn)練集生成的TF-IDF詞向量空間中的詞,我們就都不予考慮。這就實(shí)現(xiàn)了所有文本詞向量空間“大一統(tǒng)”,也只有這樣,大家才在同一個(gè)世界里。才能進(jìn)行下一步的研究。 下面的程序就是要將訓(xùn)練集所有文本文件(詞向量)統(tǒng)一到同一個(gè)TF-IDF詞向量空間中(或者叫做用TF-IDF算法計(jì)算權(quán)重的有權(quán)詞向量空間)。這個(gè)詞向量空間最終存放在train_word_bag/tfdifspace.dat中。 這段代碼你可能有點(diǎn)看不懂,因?yàn)槲夜烙?jì)你可能比較懶,還沒看過TF-IDF(盡管我剛才已經(jīng)給你推薦那篇文章了)。你只需要明白,它把一大坨訓(xùn)練集數(shù)據(jù)成功的構(gòu)建了一個(gè)TF-IDF詞向量空間,空間的各個(gè)詞都是出自這個(gè)訓(xùn)練集(去掉了停用詞)中,各個(gè)詞的權(quán)值也都一并保存了下來,叫做權(quán)重矩陣。 需要注意的是,你要明白,權(quán)重矩陣是一個(gè)二維矩陣,a[i][j]表示,第i個(gè)詞在第j個(gè)類別中的IF-IDF值(看到這里,我估計(jì)你壓根就沒去看那篇文章,所以你可能到現(xiàn)在也不知道 這是個(gè)啥玩意兒。。。) 請記住權(quán)重矩陣這個(gè)詞,代碼解釋中我會(huì)用到。 #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: vector_space.py @time: 2017/2/7 17:29 @software: PyCharm """ import sys reload(sys) sys.setdefaultencoding('utf-8') # 引入Bunch類 from sklearn.datasets.base import Bunch import cPickle as pickle#之前已經(jīng)說過,不再贅述 from sklearn.feature_extraction.text import TfidfVectorizer#這個(gè)東西下面會(huì)講 # 讀取文件 def _readfile(path): with open(path, "rb") 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, bunchobj): with open(path, "wb") as file_obj: pickle.dump(bunchobj, file_obj) #這個(gè)函數(shù)用于創(chuàng)建TF-IDF詞向量空間 def vector_space(stopword_path,bunch_path,space_path): stpwrdlst = _readfile(stopword_path).splitlines()#讀取停用詞 bunch = _readbunchobj(bunch_path)#導(dǎo)入分詞后的詞向量bunch對象 #構(gòu)建tf-idf詞向量空間對象 tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={}) ''' 在前面幾節(jié)中,我們已經(jīng)介紹了Bunch。 target_name,label和filenames這幾個(gè)成員都是我們自己定義的玩意兒,前面已經(jīng)講過不再贅述。 下面我們講一下tdm和vocabulary(這倆玩意兒也都是我們自己創(chuàng)建的): tdm存放的是計(jì)算后得到的TF-IDF權(quán)重矩陣。請記住,我們后面分類器需要的東西,其實(shí)就是訓(xùn)練集的tdm和標(biāo)簽label,因此這個(gè)成員是 很重要的。 vocabulary是詞向量空間的索引,例如,如果我們定義的詞向量空間是(我,喜歡,相國大人),那么vocabulary就是這樣一個(gè)索引字典 vocabulary={"我":0,"喜歡":1,"相國大人":2},你可以簡單的理解為:vocabulary就是詞向量空間的坐標(biāo)軸,索引值相當(dāng)于表明了第幾 個(gè)維度。 我們現(xiàn)在就是要構(gòu)建一個(gè)詞向量空間,因此在初始時(shí)刻,這個(gè)tdm和vocabulary自然都是空的。如果你在這一步將vocabulary賦值了一個(gè) 自定義的內(nèi)容,那么,你是傻逼。 ''' ''' 與下面這2行代碼等價(jià)的代碼是: vectorizer=CountVectorizer()#構(gòu)建一個(gè)計(jì)算詞頻(TF)的玩意兒,當(dāng)然這里面不只是可以做這些 transformer=TfidfTransformer()#構(gòu)建一個(gè)計(jì)算TF-IDF的玩意兒 tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus)) #vectorizer.fit_transform(corpus)將文本corpus輸入,得到詞頻矩陣 #將這個(gè)矩陣作為輸入,用transformer.fit_transform(詞頻矩陣)得到TF-IDF權(quán)重矩陣 看名字你也應(yīng)該知道: Tfidf-Transformer + Count-Vectorizer = Tfidf-Vectorizer 下面的代碼一步到位,把上面的兩個(gè)步驟一次性全部完成 值得注意的是,CountVectorizer()和TfidfVectorizer()里面都有一個(gè)成員叫做vocabulary_(后面帶一個(gè)下劃線) 這個(gè)成員的意義,與我們之前在構(gòu)建Bunch對象時(shí)提到的自己定義的那個(gè)vocabulary的意思是一樣的,相當(dāng)于詞向量 空間的坐標(biāo)軸。顯然,我們在第45行中創(chuàng)建tfidfspace中定義的vocabulary就應(yīng)該被賦值為這個(gè)vocabulary_ 他倆還有一個(gè)叫做vocabulary(后面沒有下劃線)的參數(shù),這個(gè)參數(shù)和我們第45中講到的意思是一樣的。 那么vocabulary_和vocabulary的區(qū)別是什么呢? vocabulary_:是CountVectorizer()和TfidfVectorizer()的內(nèi)部成員,表示最終得到的詞向量空間坐標(biāo) vocabulary:是創(chuàng)建CountVectorizer和TfidfVectorizer類對象時(shí),傳入的參數(shù),它是我們外部輸入的空間坐標(biāo),不寫的話,函數(shù)就從 輸入文檔中自己構(gòu)造。 一般情況它倆是相同的,不一般的情況沒遇到過。 ''' #構(gòu)建一個(gè)快樂地一步到位的玩意兒,專業(yè)一點(diǎn)兒叫做:使用TfidfVectorizer初始化向量空間模型 #這里面有TF-IDF權(quán)重矩陣還有我們要的詞向量空間坐標(biāo)軸信息vocabulary_ vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5) ''' 關(guān)于參數(shù),你只需要了解這么幾個(gè)就可以了: stop_words: 傳入停用詞,以后我們獲得vocabulary_的時(shí)候,就會(huì)根據(jù)文本信息去掉停用詞得到 vocabulary: 之前說過,不再解釋。 sublinear_tf: 計(jì)算tf值采用亞線性策略。比如,我們以前算tf是詞頻,現(xiàn)在用1+log(tf)來充當(dāng)詞頻。 smooth_idf: 計(jì)算idf的時(shí)候log(分子/分母)分母有可能是0,smooth_idf會(huì)采用log(分子/(1+分母))的方式解決。默認(rèn)已經(jīng)開啟,無需關(guān)心。 norm: 歸一化,我們計(jì)算TF-IDF的時(shí)候,是用TF*IDF,TF可以是歸一化的,也可以是沒有歸一化的,一般都是采用歸一化的方法,默認(rèn)開啟. max_df: 有些詞,他們的文檔頻率太高了(一個(gè)詞如果每篇文檔都出現(xiàn),那還有必要用它來區(qū)分文本類別嗎?當(dāng)然不用了呀),所以,我們可以 設(shè)定一個(gè)閾值,比如float類型0.5(取值范圍[0.0,1.0]),表示這個(gè)詞如果在整個(gè)數(shù)據(jù)集中超過50%的文本都出現(xiàn)了,那么我們也把它列 為臨時(shí)停用詞。當(dāng)然你也可以設(shè)定為int型,例如max_df=10,表示這個(gè)詞如果在整個(gè)數(shù)據(jù)集中超過10的文本都出現(xiàn)了,那么我們也把它列 為臨時(shí)停用詞。 min_df: 與max_df相反,雖然文檔頻率越低,似乎越能區(qū)分文本,可是如果太低,例如10000篇文本中只有1篇文本出現(xiàn)過這個(gè)詞,僅僅因?yàn)檫@1篇 文本,就增加了詞向量空間的維度,太不劃算。 當(dāng)然,max_df和min_df在給定vocabulary參數(shù)時(shí),就失效了。 ''' #此時(shí)tdm里面存儲(chǔ)的就是if-idf權(quán)值矩陣 tfidfspace.tdm = vectorizer.fit_transform(bunch.contents) tfidfspace.vocabulary = vectorizer.vocabulary_ _writebunchobj(space_path, tfidfspace) print "if-idf詞向量空間實(shí)例創(chuàng)建成功!?。? if __name__ == '__main__': stopword_path = "train_word_bag/hlt_stop_words.txt"#停用詞表的路徑 bunch_path = "train_word_bag/train_set.dat" #導(dǎo)入訓(xùn)練集Bunch的路徑 space_path = "train_word_bag/tfdifspace.dat" # 詞向量空間保存路徑 vector_space(stopword_path,bunch_path,space_path) 上面的代碼運(yùn)行之后,會(huì)將訓(xùn)練集數(shù)據(jù)轉(zhuǎn)換為TF-IDF詞向量空間中的實(shí)例,保存在train_word_bag/tfdifspace.dat中,具體來說,這個(gè)文件里面有兩個(gè)我們感興趣的東西,一個(gè)是vocabulary,即詞向量空間坐標(biāo),一個(gè)是tdm,即訓(xùn)練集的TF-IDF權(quán)重矩陣。 接下來,我們要開始第5步的操作,設(shè)計(jì)分類器,用訓(xùn)練集訓(xùn)練,用測試集測試。在做這些工作之前,你一定要記住,首先要把測試數(shù)據(jù)也映射到上面這個(gè)TF-IDF詞向量空間中,也就是說,測試集和訓(xùn)練集處在同一個(gè)詞向量空間(vocabulary相同),只不過測試集有自己的tdm,與訓(xùn)練集(train_word_bag/tfdifspace.dat)中的tdm不同而已。 同一個(gè)世界,同一個(gè)夢想。 至于說怎么弄,請看下節(jié)。 5,分類器這里我們采用的是樸素貝葉斯分類器,今后我們會(huì)詳細(xì)講解它。 現(xiàn)在,你即便不知道這是個(gè)啥玩意兒,也一點(diǎn)不會(huì)影響你,這個(gè)分類器我們有封裝好了的函數(shù),MultinomialNB,這玩意兒獲取訓(xùn)練集的權(quán)重矩陣和標(biāo)簽,進(jìn)行訓(xùn)練,然后獲取測試集的權(quán)重矩陣,進(jìn)行預(yù)測(給出預(yù)測標(biāo)簽)。 下面我們開始動(dòng)手實(shí)踐吧! 首先,我們要把測試數(shù)據(jù)也映射到第4節(jié)中的那個(gè)TF-IDF詞向量空間上: #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: test.py @time: 2017/2/8 11:39 @software: PyCharm """ import sys reload(sys) sys.setdefaultencoding('utf-8') # 引入Bunch類 from sklearn.datasets.base import Bunch import cPickle as pickle from sklearn.feature_extraction.text import TfidfVectorizer def _readfile(path): with open(path, "rb") as fp: content = fp.read() return content def _readbunchobj(path): with open(path, "rb") as file_obj: bunch = pickle.load(file_obj) return bunch def _writebunchobj(path, bunchobj): with open(path, "wb") as file_obj: pickle.dump(bunchobj, file_obj) def vector_space(stopword_path,bunch_path,space_path,train_tfidf_path): stpwrdlst = _readfile(stopword_path).splitlines() bunch = _readbunchobj(bunch_path) tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={}) #導(dǎo)入訓(xùn)練集的TF-IDF詞向量空間 trainbunch = _readbunchobj(train_tfidf_path) tfidfspace.vocabulary = trainbunch.vocabulary vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5,vocabulary=trainbunch.vocabulary) tfidfspace.tdm = vectorizer.fit_transform(bunch.contents) _writebunchobj(space_path, tfidfspace) print "if-idf詞向量空間實(shí)例創(chuàng)建成功!??!" if __name__ == '__main__': stopword_path = "train_word_bag/hlt_stop_words.txt"#停用詞表的路徑 bunch_path = "test_word_bag/test_set.dat" # 詞向量空間保存路徑 space_path = "test_word_bag/testspace.dat" # TF-IDF詞向量空間保存路徑 train_tfidf_path="train_word_bag/tfdifspace.dat" vector_space(stopword_path,bunch_path,space_path,train_tfidf_path) 你已經(jīng)發(fā)現(xiàn)了,這段代碼與第4節(jié)幾乎一模一樣,唯一不同的就是在第39~41行中,我們導(dǎo)入了第4節(jié)中訓(xùn)練集的IF-IDF詞向量空間,并且第41行將訓(xùn)練集的vocabulary賦值給測試集的vocabulary,第43行增加了入口參數(shù)vocabulary,原因在上一節(jié)中都已經(jīng)說明,不再贅述。 考慮到第4節(jié)和剛才的代碼幾乎完全一樣,因此我們可以將這兩個(gè)代碼文件統(tǒng)一為一個(gè): #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: TFIDF_space.py @time: 2017/2/8 11:39 @software: PyCharm """ import sys reload(sys) sys.setdefaultencoding('utf-8') from sklearn.datasets.base import Bunch import cPickle as pickle from sklearn.feature_extraction.text import TfidfVectorizer def _readfile(path): with open(path, "rb") as fp: content = fp.read() return content def _readbunchobj(path): with open(path, "rb") as file_obj: bunch = pickle.load(file_obj) return bunch def _writebunchobj(path, bunchobj): with open(path, "wb") as file_obj: pickle.dump(bunchobj, file_obj) def vector_space(stopword_path,bunch_path,space_path,train_tfidf_path=None): stpwrdlst = _readfile(stopword_path).splitlines() bunch = _readbunchobj(bunch_path) tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={}) if train_tfidf_path is not None: trainbunch = _readbunchobj(train_tfidf_path) tfidfspace.vocabulary = trainbunch.vocabulary vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5,vocabulary=trainbunch.vocabulary) tfidfspace.tdm = vectorizer.fit_transform(bunch.contents) else: vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5) tfidfspace.tdm = vectorizer.fit_transform(bunch.contents) tfidfspace.vocabulary = vectorizer.vocabulary_ _writebunchobj(space_path, tfidfspace) print "if-idf詞向量空間實(shí)例創(chuàng)建成功!??!" if __name__ == '__main__': stopword_path = "train_word_bag/hlt_stop_words.txt" bunch_path = "train_word_bag/train_set.dat" space_path = "train_word_bag/tfdifspace.dat" vector_space(stopword_path,bunch_path,space_path) bunch_path = "test_word_bag/test_set.dat" 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) 哇哦,你好棒!現(xiàn)在連注釋都不用,就可以看懂代碼了。。。 對測試集進(jìn)行了上述處理后,接下來的步驟,變得如此輕盈和優(yōu)雅。 #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @version: python2.7.8 @author: XiangguoSun @contact: sunxiangguodut@qq.com @file: NBayes_Predict.py @time: 2017/2/8 12:21 @software: PyCharm """ import sys reload(sys) sys.setdefaultencoding('utf-8') import cPickle as pickle from sklearn.naive_bayes import MultinomialNB # 導(dǎo)入多項(xiàng)式貝葉斯算法 # 讀取bunch對象 def _readbunchobj(path): with open(path, "rb") as file_obj: bunch = pickle.load(file_obj) return bunch # 導(dǎo)入訓(xùn)練集 trainpath = "train_word_bag/tfdifspace.dat" train_set = _readbunchobj(trainpath) # 導(dǎo)入測試集 testpath = "test_word_bag/testspace.dat" test_set = _readbunchobj(testpath) # 訓(xùn)練分類器:輸入詞袋向量和分類標(biāo)簽,alpha:0.001 alpha越小,迭代次數(shù)越多,精度越高 clf = MultinomialNB(alpha=0.001).fit(train_set.tdm, train_set.label) # 預(yù)測分類結(jié)果 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,": 實(shí)際類別:",flabel," -->預(yù)測類別:",expct_cate print "預(yù)測完畢!!!" # 計(jì)算分類精度: from sklearn import metrics 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)出錯(cuò)的這個(gè),是我故意制造的,(因?yàn)閷?shí)際分類精度100%,不能很好的說明問題) 效果圖:
當(dāng)然,你也可以采用其他分類器,比如KNN 6,評價(jià)與小結(jié)評價(jià)部分的實(shí)際操作我們已經(jīng)在上一節(jié)的代碼中給出了。這里主要是要解釋一下代碼的含義,以及相關(guān)的一些概念。 截止目前,我們已經(jīng)完成了全部的實(shí)踐工作。接下來,你或許希望做的是: 1,分詞工具和分詞算法的研究 2,文本分類算法的研究 這些內(nèi)容,博主會(huì)在今后的時(shí)間里,專門研究并寫出博文。 整個(gè)工程的完整源代碼到這里下載: https://github.com/sunxiangguo/chinese_text_classification 需要說明的是,在工程代碼和本篇博文中,細(xì)心的你已經(jīng)發(fā)現(xiàn)了,我們所有的路徑前面都有一個(gè)點(diǎn)“. /”,這主要是因?yàn)槲覀儾恢滥鷷?huì)將工程建在哪個(gè)路徑內(nèi),因此這個(gè)表示的是你所在項(xiàng)目的目錄,本篇博文所有路徑都是相對路徑。因此你需要自己注意一下。工程里面語料庫是空的,因?yàn)樯蟼髻Y源受到容量的限制。你需要自己添加。 7,進(jìn)一步的討論:我們的這些工作究竟實(shí)不實(shí)用?這是很多人關(guān)心的問題。事實(shí)上,本博文的做法,是最經(jīng)典的文本分類思想。也是你進(jìn)一步深入研究文本分類的基礎(chǔ)。在實(shí)際工作中,用本文的方法,已經(jīng)足夠勝任各種情況了。 那么,我們也許想問,有沒有更好,更新的技術(shù)?答案是有的。未來,博主會(huì)集中介紹兩種技術(shù): 1.利用LDA模型進(jìn)行文本分類 2.利用深度學(xué)習(xí)進(jìn)行文本分類 利用深度學(xué)習(xí)進(jìn)行文本分類,要求你必須對深度學(xué)習(xí)的理論有足夠多的掌握。 為此,你可以參考博主的其他博文, 例如下面的這個(gè)系列博文《卷積神經(jīng)網(wǎng)絡(luò)CNN理論到實(shí)踐》。 這是一些列的博文。與網(wǎng)上其他介紹CNN的博文不同的是:
8,At last
|
|