1. 程式人生 > >pyhanlp 文字分類與情感分析

pyhanlp 文字分類與情感分析

這一次我們需要利用HanLP進行文字分類與情感分析。同時這也是pyhanlp使用者指南的倒數第二篇關於介面和Python實現的文章了,再之後就是導論,使用技巧彙總和幾個例項落。真是可喜可賀啊。

文字分類

在HanLP中,文字分類與情感分析都是使用一個分類器,樸素貝葉斯分類器。或許這個分類器還算是比較一般。但是因為計算文字內容時,底層依舊使用word2vec,所以其表現出來的效能還是相當不錯的。

原作者之給了文字分類的例子,這裡我們對原來的例子稍加改造,使其更適用分類任務。

語料庫

本文語料庫特指文字分類語料庫,對應IDataSet介面。而文字分類語料庫包含兩個概念:文件和類目。一個文件只屬於一個類目,一個類目可能含有多個文件。比如

搜狗文字分類語料庫迷你版.zip,下載前請先閱讀搜狗實驗室資料使用許可協議

資料格式

分類語料的根目錄.目錄必須滿足如下結構:

  •                根目錄<br>
    
  •                ├── 分類A<br>
    
  •                │   └── 1.txt<br>
    
  •                │   └── 2.txt<br>
    
  •                │   └── 3.txt<br>
    
  •                ├── 分類B<br>
    
  •                │   └── 1.txt<br>
    
  •                │   └── ...<br>
    
  •                └── ...<br>
    
  •                檔案不一定需要用數字命名,也不需要以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星的 而且設施非常陳舊.建議酒店把老的標準間從新改善.》	屬於分類	【負面】
《服務態度極其差,前臺接待好象沒有受過培訓,連基本的禮貌都不懂,竟然同時接待幾個客人》	屬於分類	【負面】

 我們這裡再用訓練好的模型連測試一下我自己編的‘新的’的文字 

《服務態度很好,認真的接待了我們,態度可以的!》	屬於分類	【正面】
《  有點不太衛生,感覺不怎麼樣。》	屬於分類	【負面】

參考