這一次我們需要利用HanLP進行文本分類與情感分析。同時這也是pyhanlp用戶指南的倒數第二篇關於接口和Python實現的文章了,再之后就是導論,使用技巧匯總和幾個實例落。真是可喜可賀啊。
文本分類
在HanLP中,文本分類與情感分析都是使用一個分類器,朴素貝葉斯分類器。或許這個分類器還算是比較一般,不過從最終結果來看效果還是很可以的。
因為底層采用詞袋模式,所以當文本較大時可能會是內存開效果大,不過沒關系,作者預先寫了一個特征檢測的方法。使用卡方檢測,利用閾值來過濾特征,減少內存的開銷。
原作者只給了文本分類的例子,這里我們對原來的例子稍加改造,使其更適用分類任務。
語料庫
本文語料庫特指文本分類語料庫,對應IDataSet接口。而文本分類語料庫包含兩個概念:文檔和類目。一個文檔只屬於一個類目,一個類目可能含有多個文檔。比如搜狗文本分類語料庫迷你版.zip,下載前請先閱讀搜狗實驗室數據使用許可協議。
數據格式
分類語料的根目錄.目錄必須滿足如下結構:
根目錄
├── 分類A
│ └── 1.txt
│ └── 2.txt
│ └── 3.txt
├── 分類B
│ └── 1.txt
│ └── ...
└── ...
文件不一定需要用數字命名,也不需要以txt作為后綴名,但一定需要是文本文件.
分詞
目前,本系統中的分詞器接口一共有兩種實現: BigramTokenizer and HanLPTokenizer。
但文本分類是否一定需要分詞?答案是否定的。 我們可以順序選取文中相鄰的兩個字,作為一個“詞”(術語叫bigram)。這兩個字在數量很多的時候可以反映文章的主題(參考清華大學2016年的一篇論文《Zhipeng Guo, Yu Zhao, Yabin Zheng, Xiance Si, Zhiyuan Liu, Maosong Sun. THUCTC: An Efficient Chinese Text Classifier. 2016》)。這在代碼中對應BigramTokenizer. 當然,也可以采用傳統的分詞器,如HanLPTokenizer。 另外,用戶也可以通過實現ITokenizer來實現自己的分詞器,並通過IDataSet#setTokenizer來使其生效
特征提取
特征提取指的是從所有詞中,選取最有助於分類決策的詞語。理想狀態下所有詞語都有助於分類決策,但現實情況是,如果將所有詞語都納入計算,則訓練速度將非常慢,內存開銷非常大且最終模型的體積非常大。
本系統采取的是卡方檢測,通過卡方檢測去掉卡方值低於一個閾值的特征,並且限定最終特征數不超過100萬。
預測
classify方法直接返回最可能的類別的String形式,而predict方法返回所有類別的得分(是一個Map形式,鍵是類目,值是分數或概率),categorize方法返回所有類目的得分(是一個double數組,分類得分按照分類名稱的字典序排列),label方法返回最可能類目的字典序。
線程安全性
類似於HanLP的設計,以效率至上,本系統內部實現沒有使用任何線程鎖,但任何預測接口都是線程安全的(被設計為不儲存中間結果,將所有中間結果放入參數棧中)。
from pyhanlp import SafeJClass import zipfile import os from pyhanlp.static import download, remove_file, HANLP_DATA_PATH # 設置路徑,否則會從配置文件中尋找 HANLP_DATA_PATH = "/home/fonttian/Data/CNLP" """ 獲取測試數據路徑,位於$root/data/textClassification/sogou-mini, 根目錄由配置文件指定,或者等於我們前面手動設置的HANLP_DATA_PATH。 """ DATA_FILES_PATH = "textClassification/sogou-mini" def test_data_path(): data_path = os.path.join(HANLP_DATA_PATH, DATA_FILES_PATH) if not os.path.isdir(data_path): os.mkdir(data_path) return data_path def ensure_data(data_name, data_url): root_path = test_data_path() dest_path = os.path.join(root_path, data_name) if os.path.exists(dest_path): return dest_path if data_url.endswith('.zip'): dest_path += '.zip' download(data_url, dest_path) if data_url.endswith('.zip'): with zipfile.ZipFile(dest_path, "r") as archive: archive.extractall(root_path) remove_file(dest_path) dest_path = dest_path[:-len('.zip')] return dest_path NaiveBayesClassifier = SafeJClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier') IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil') sogou_corpus_path = ensure_data('搜狗文本分類語料庫迷你版', 'http://hanlp.linrunsoft.com/release/corpus/sogou-text-classification-corpus-mini.zip') def train_or_load_classifier(path): model_path = path + '.ser' if os.path.isfile(model_path): return NaiveBayesClassifier(IOUtil.readObjectFrom(model_path)) classifier = NaiveBayesClassifier() classifier.train(sogou_corpus_path) model = classifier.getModel() IOUtil.saveObjectTo(model, model_path) return NaiveBayesClassifier(model) def predict(classifier, text): print("《%16s》\t屬於分類\t【%s】" % (text, classifier.classify(text))) # 如需獲取離散型隨機變量的分布,請使用predict接口 # print("《%16s》\t屬於分類\t【%s】" % (text, classifier.predict(text))) if __name__ == '__main__': classifier = train_or_load_classifier(sogou_corpus_path) predict(classifier, "C羅壓梅西內馬爾蟬聯金球獎 2017=C羅年") predict(classifier, "英國造航母耗時8年仍未服役 被中國速度遠遠甩在身后") predict(classifier, "研究生考錄模式亟待進一步專業化") predict(classifier, "如果真想用食物解壓,建議可以食用燕麥") predict(classifier, "通用及其部分競爭對手目前正在考慮解決庫存問題") print("\n 我們這里再用訓練好的模型連測試一下新的隨便從網上找來的幾個新聞標題 \n") predict(classifier, "今年考研壓力進一步增大,或許考研正在變成第二次高考") predict(classifier, "張繼科被劉國梁連珠炮喊醒:醒醒!奧運會開始了。") predict(classifier, "福特終於開竅了!新車1.5T懟出184馬力,不足11萬,思域自愧不如")
《C羅壓梅西內馬爾蟬聯金球獎 2017=C羅年》 屬於分類 【體育】
《英國造航母耗時8年仍未服役 被中國速度遠遠甩在身后》 屬於分類 【軍事】
《 研究生考錄模式亟待進一步專業化》 屬於分類 【教育】
《如果真想用食物解壓,建議可以食用燕麥》 屬於分類 【健康】
《通用及其部分競爭對手目前正在考慮解決庫存問題》 屬於分類 【汽車】
我們這里再用訓練好的模型連測試一下新的隨便從網上找來的幾個新聞標題
《今年考研壓力進一步增大,或許考研正在變成第二次高考》 屬於分類 【教育】
《張繼科被劉國梁連珠炮喊醒:醒醒!奧運會開始了。》 屬於分類 【體育】
《福特終於開竅了!新車1.5T懟出184馬力,不足11萬,思域自愧不如》 屬於分類 【汽車】
從我們最后自己增加的幾個新聞標題來看,分類器的效果相當的好。這多虧了word2vec。
情感分析
我們對於情感分析的實現與之前的文本分類具有高度的相似性,同時剛剛也提到了,實際上他們就是用的一個分類器。而在python的實現中,他們則幾乎一模一樣。
也正是因為如此,所以只要我們擁有同樣格式的語料,那么我們可以使用這個分類器做任何我們需要的文本分類
語料來源
可以利用文本分類在情感極性語料上訓練的模型做淺層情感分析。目前公開的情感分析語料庫有:中文情感挖掘語料-ChnSentiCorp,語料發布者為譚松波。
""" 獲取測試數據路徑,位於$root/data/textClassification/sogou-mini, 根目錄由配置文件指定,或者等於我們前面手動設置的HANLP_DATA_PATH。 ChnSentiCorp評論酒店情感分析 """ DATA_FILES_PATH = "sentimentAnalysis/ChnSentiCorp" if __name__ == '__main__': ChnSentiCorp_path = ensure_data('酒店評論情感分析', '這里是找不到數據時/默認去下載的地址/不過這里我們不需要/所以隨便寫點什么就好了/') classifier = train_or_load_classifier(ChnSentiCorp_path) predict(classifier, '距離川沙公路較近,但是公交指示不對,如果是"蔡陸線"的話,會非常麻煩.建議用別的路線.房間較為簡單.') predict(classifier, "商務大床房,房間很大,床有2M寬,整體感覺經濟實惠不錯!") predict(classifier, "標准間太差 房間還不如3星的 而且設施非常陳舊.建議酒店把老的標准間從新改善.") predict(classifier, "服務態度極其差,前台接待好象沒有受過培訓,連基本的禮貌都不懂,竟然同時接待幾個客人") print("\n 我們這里再用訓練好的模型連測試一下我自己編的‘新的’的文本 \n") predict(classifier, "服務態度很好,認真的接待了我們,態度可以的!") predict(classifier, "有點不太衛生,感覺不怎么樣。")
《距離川沙公路較近,但是公交指示不對,如果是"蔡陸線"的話,會非常麻煩.建議用別的路線.房間較為簡單.》 屬於分類 【正面】
《商務大床房,房間很大,床有2M寬,整體感覺經濟實惠不錯!》 屬於分類 【正面】
《標准間太差 房間還不如3星的 而且設施非常陳舊.建議酒店把老的標准間從新改善.》 屬於分類 【負面】
《服務態度極其差,前台接待好象沒有受過培訓,連基本的禮貌都不懂,竟然同時接待幾個客人》 屬於分類 【負面】
我們這里再用訓練好的模型連測試一下我自己編的‘新的’的文本
《服務態度很好,認真的接待了我們,態度可以的!》 屬於分類 【正面】
《 有點不太衛生,感覺不怎么樣。》 屬於分類 【負面】