這一次我們需要利用HanLP進(jìn)行文本分類與情感分析。同時(shí)這也是pyhanlp用戶指南的倒數(shù)第二篇關(guān)于接口和Python實(shí)現(xiàn)的文章了,再之后就是導(dǎo)論沥潭,使用技巧匯總和幾個(gè)實(shí)例落编丘。真是可喜可賀啊谅畅。
文本分類
在HanLP中,文本分類與情感分析都是使用一個(gè)分類器亥曹,樸素貝葉斯分類器邓了。或許這個(gè)分類器還算是比較一般媳瞪,不過從最終結(jié)果來看效果還是很可以的骗炉。
因?yàn)榈讓硬捎迷~袋模式,所以當(dāng)文本較大時(shí)可能會(huì)是內(nèi)存開效果大蛇受,不過沒關(guān)系句葵,作者預(yù)先寫了一個(gè)特征檢測的方法。使用卡方檢測,利用閾值來過濾特征乍丈,減少內(nèi)存的開銷剂碴。
原作者只給了文本分類的例子,這里我們對原來的例子稍加改造轻专,使其更適用分類任務(wù)忆矛。
語料庫
本文語料庫特指文本分類語料庫,對應(yīng)IDataSet接口请垛。而文本分類語料庫包含兩個(gè)概念:文檔和類目催训。一個(gè)文檔只屬于一個(gè)類目,一個(gè)類目可能含有多個(gè)文檔宗收。比如搜狗文本分類語料庫迷你版.zip漫拭,下載前請先閱讀搜狗實(shí)驗(yàn)室數(shù)據(jù)使用許可協(xié)議。
數(shù)據(jù)格式
分類語料的根目錄.目錄必須滿足如下結(jié)構(gòu):
根目錄
├── 分類A
│? └── 1.txt
│? └── 2.txt
│? └── 3.txt
├── 分類B
│? └── 1.txt
│? └── ...
└── ...
文件不一定需要用數(shù)字命名,也不需要以txt作為后綴名,但一定需要是文本文件.
分詞
目前混稽,本系統(tǒng)中的分詞器接口一共有兩種實(shí)現(xiàn): BigramTokenizer and HanLPTokenizer采驻。
但文本分類是否一定需要分詞?答案是否定的荚坞。 我們可以順序選取文中相鄰的兩個(gè)字挑宠,作為一個(gè)“詞”(術(shù)語叫bigram)。這兩個(gè)字在數(shù)量很多的時(shí)候可以反映文章的主題(參考清華大學(xué)2016年的一篇論文《Zhipeng Guo, Yu Zhao, Yabin Zheng, Xiance Si, Zhiyuan Liu, Maosong Sun. THUCTC: An Efficient Chinese Text Classifier. 2016》)颓影。這在代碼中對應(yīng)BigramTokenizer. 當(dāng)然,也可以采用傳統(tǒng)的分詞器懒鉴,如HanLPTokenizer诡挂。 另外,用戶也可以通過實(shí)現(xiàn)ITokenizer來實(shí)現(xiàn)自己的分詞器临谱,并通過IDataSet#setTokenizer來使其生效
特征提取
特征提取指的是從所有詞中璃俗,選取最有助于分類決策的詞語。理想狀態(tài)下所有詞語都有助于分類決策悉默,但現(xiàn)實(shí)情況是城豁,如果將所有詞語都納入計(jì)算,則訓(xùn)練速度將非常慢抄课,內(nèi)存開銷非常大且最終模型的體積非常大唱星。
本系統(tǒng)采取的是卡方檢測,通過卡方檢測去掉卡方值低于一個(gè)閾值的特征跟磨,并且限定最終特征數(shù)不超過100萬间聊。
預(yù)測
classify方法直接返回最可能的類別的String形式,而predict方法返回所有類別的得分(是一個(gè)Map形式抵拘,鍵是類目哎榴,值是分?jǐn)?shù)或概率),categorize方法返回所有類目的得分(是一個(gè)double數(shù)組,分類得分按照分類名稱的字典序排列)尚蝌,label方法返回最可能類目的字典序迎变。
線程安全性
類似于HanLP的設(shè)計(jì),以效率至上飘言,本系統(tǒng)內(nèi)部實(shí)現(xiàn)沒有使用任何線程鎖,但任何預(yù)測接口都是線程安全的(被設(shè)計(jì)為不儲(chǔ)存中間結(jié)果衣形,將所有中間結(jié)果放入?yún)?shù)棧中)。
from pyhanlp import SafeJClass
import zipfile
import os
from pyhanlp.static import download, remove_file, HANLP_DATA_PATH
# 設(shè)置路徑热凹,否則會(huì)從配置文件中尋找
HANLP_DATA_PATH = "/home/fonttian/Data/CNLP"
"""
獲取測試數(shù)據(jù)路徑泵喘,位于$root/data/textClassification/sogou-mini,
根目錄由配置文件指定,或者等于我們前面手動(dòng)設(shè)置的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)))
? ? # 如需獲取離散型隨機(jī)變量的分布纪铺,請使用predict接口
? ? # print("《%16s》\t屬于分類\t【%s】" % (text, classifier.predict(text)))
if __name__ == '__main__':
? ? classifier = train_or_load_classifier(sogou_corpus_path)
? ? predict(classifier, "C羅壓梅西內(nèi)馬爾蟬聯(lián)金球獎(jiǎng) 2017=C羅年")
? ? predict(classifier, "英國造航母耗時(shí)8年仍未服役 被中國速度遠(yuǎn)遠(yuǎn)甩在身后")
? ? predict(classifier, "研究生考錄模式亟待進(jìn)一步專業(yè)化")
? ? predict(classifier, "如果真想用食物解壓,建議可以食用燕麥")
? ? predict(classifier, "通用及其部分競爭對手目前正在考慮解決庫存問題")
? ? print("\n 我們這里再用訓(xùn)練好的模型連測試一下新的隨便從網(wǎng)上找來的幾個(gè)新聞標(biāo)題 \n")
? ? predict(classifier, "今年考研壓力進(jìn)一步增大,或許考研正在變成第二次高考")
? ? predict(classifier, "張繼科被劉國梁連珠炮喊醒:醒醒碟渺!奧運(yùn)會(huì)開始了鲜锚。")
? ? predict(classifier, "福特終于開竅了!新車1.5T懟出184馬力苫拍,不足11萬芜繁,思域自愧不如")
《C羅壓梅西內(nèi)馬爾蟬聯(lián)金球獎(jiǎng) 2017=C羅年》 屬于分類 【體育】
《英國造航母耗時(shí)8年仍未服役 被中國速度遠(yuǎn)遠(yuǎn)甩在身后》 屬于分類 【軍事】
《 研究生考錄模式亟待進(jìn)一步專業(yè)化》 屬于分類 【教育】
《如果真想用食物解壓,建議可以食用燕麥》 屬于分類 【健康】
《通用及其部分競爭對手目前正在考慮解決庫存問題》 屬于分類 【汽車】
我們這里再用訓(xùn)練好的模型連測試一下新的隨便從網(wǎng)上找來的幾個(gè)新聞標(biāo)題
《今年考研壓力進(jìn)一步增大,或許考研正在變成第二次高考》 屬于分類 【教育】
《張繼科被劉國梁連珠炮喊醒:醒醒绒极!奧運(yùn)會(huì)開始了骏令。》 屬于分類 【體育】
《福特終于開竅了垄提!新車1.5T懟出184馬力榔袋,不足11萬,思域自愧不如》 屬于分類 【汽車】
從我們最后自己增加的幾個(gè)新聞標(biāo)題來看铡俐,分類器的效果相當(dāng)?shù)暮没硕摇_@多虧了word2vec。
情感分析
我們對于情感分析的實(shí)現(xiàn)與之前的文本分類具有高度的相似性审丘,同時(shí)剛剛也提到了吏够,實(shí)際上他們就是用的一個(gè)分類器。而在python的實(shí)現(xiàn)中滩报,他們則幾乎一模一樣锅知。
也正是因?yàn)槿绱耍灾灰覀儞碛型瑯痈袷降恼Z料露泊,那么我們可以使用這個(gè)分類器做任何我們需要的文本分類
語料來源
可以利用文本分類在情感極性語料上訓(xùn)練的模型做淺層情感分析喉镰。目前公開的情感分析語料庫有:中文情感挖掘語料-ChnSentiCorp,語料發(fā)布者為譚松波惭笑。
"""
獲取測試數(shù)據(jù)路徑侣姆,位于$root/data/textClassification/sogou-mini生真,
根目錄由配置文件指定,或者等于我們前面手動(dòng)設(shè)置的HANLP_DATA_PATH。
ChnSentiCorp評論酒店情感分析
"""
DATA_FILES_PATH = "sentimentAnalysis/ChnSentiCorp"
if __name__ == '__main__':
? ? ChnSentiCorp_path = ensure_data('酒店評論情感分析', \
? ? ? ? 'http://hanlp.linrunsoft.com/release/corpus/ChnSentiCorp.zip')
? ? # 此處感謝網(wǎng)友給出的下載鏈接
? ? # 本文示例中捺宗,如果需要使用本地資料柱蟀,請通過上面的DATA_FILES_PATH變量控制
? ? classifier = train_or_load_classifier(ChnSentiCorp_path)
? ? predict(classifier, '距離川沙公路較近,但是公交指示不對,如果是"蔡陸線"的話,會(huì)非常麻煩.建議用別的路線.房間較為簡單.')
? ? predict(classifier, "商務(wù)大床房,房間很大蚜厉,床有2M寬长已,整體感覺經(jīng)濟(jì)實(shí)惠不錯(cuò)!")
? ? predict(classifier, "標(biāo)準(zhǔn)間太差 房間還不如3星的 而且設(shè)施非常陳舊.建議酒店把老的標(biāo)準(zhǔn)間從新改善.")
? ? predict(classifier, "服務(wù)態(tài)度極其差,前臺接待好象沒有受過培訓(xùn)昼牛,連基本的禮貌都不懂术瓮,竟然同時(shí)接待幾個(gè)客人")
? ? print("\n 我們這里再用訓(xùn)練好的模型連測試一下我自己編的‘新的’的文本 \n")
? ? predict(classifier, "服務(wù)態(tài)度很好,認(rèn)真的接待了我們贰健,態(tài)度可以的胞四!")
? ? predict(classifier, "有點(diǎn)不太衛(wèi)生,感覺不怎么樣伶椿。")
《距離川沙公路較近,但是公交指示不對,如果是"蔡陸線"的話,會(huì)非常麻煩.建議用別的路線.房間較為簡單.》 屬于分類 【正面】
《商務(wù)大床房辜伟,房間很大,床有2M寬脊另,整體感覺經(jīng)濟(jì)實(shí)惠不錯(cuò)!》 屬于分類 【正面】
《標(biāo)準(zhǔn)間太差 房間還不如3星的 而且設(shè)施非常陳舊.建議酒店把老的標(biāo)準(zhǔn)間從新改善.》 屬于分類 【負(fù)面】
《服務(wù)態(tài)度極其差导狡,前臺接待好象沒有受過培訓(xùn),連基本的禮貌都不懂偎痛,竟然同時(shí)接待幾個(gè)客人》 屬于分類 【負(fù)面】
我們這里再用訓(xùn)練好的模型連測試一下我自己編的‘新的’的文本
《服務(wù)態(tài)度很好旱捧,認(rèn)真的接待了我們,態(tài)度可以的踩麦!》 屬于分類 【正面】
《? 有點(diǎn)不太衛(wèi)生廊佩,感覺不怎么樣【搁牛》 屬于分類 【負(fù)面】
文章來源于Font Tian的博客