協作閣

開源協作部落格

中文文本分析工具小評比

W3 Microblog

Jessica / 2019-03-21 /


W3 Microblog

中文文本分析工具小評比

上週我提到“我對小說中的對話這件事還是有很大的興趣,所以下週希望還會繼續做下去(希望啦,如果沒有意外的話)”~所以本來應該要繼續做下去的。 但反正~噹噹~意外就!是!發生惹(其實也不是什麼意外啦),我“意外”地發現要做文本分析前,應該先弄清楚最基本的中文斷詞、詞性標記到底是怎麼運作的。這也算是解決了我上禮拜說的“我會努力搞清楚那個分詞系統的套件到底要怎麼安裝!! ” 所以,接下來會試試看使用能處理中文不同的斷詞及詞性標記工具與套件~

1. NLTK真是太強大啦

NLTK可以做的事情真多!斷詞部分似乎還是使用jieba,但只要一個簡單的步驟,就可以找到指定詞彙的相似詞。不過所謂“相似”的意思是指什麼,我還沒有弄懂。從結果上來看,似乎是指常常一起出現的字?

#jieba.cut 是做中文斷詞, nltk.text.Text 讓文本成為 NLTK 可以吃的格式
import nltk
import jieba.analyse
raw=open('ghost_mansion.txt', encoding='utf-8').read()
text=nltk.text.Text(jieba.lcut(raw))
text.similar("說")                       
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/s3/x6bgjnss22ggm9fct3z9mgz00000gn/T/jieba.cache
Loading model cost 0.848 seconds.
Prefix dict has been built succesfully.


對 聽 看 父親 知道 告訴 想 是 也 都 跟 搞 的 只是 幫 了 把 和 房子 爸爸

NLTK還可以像語料庫一樣呈現特定詞的concordance

print(text.concordance(u'說'))
Displaying 25 of 339 matches:
 的 角色 , 雖然 他 也 只是 那個 掠奪 的 時代 ( 文雅 一點 的 說 法 是 相 對 剝奪 ) 的 幫 兇 , 雖然 他 自己 至少 在 財產上 
 個 月 , 七十多 歲 的 他 在 病榻 前花 了 好 幾個 禮拜 對 我 說 出 整件 事件 , 然 後 交代 我務 必想 辦法 把 事件 見諸 文字 ,
有 的 小康 國家 慢慢 崩壞 成 宛如 第三世界 , 不 ! 就 實情 來 說 , 已經 被 列為 第三世界 , 許多人 歸咎 於 人口 減少 , 從 23
 這些 外籍 移工 繳交稅 金付給 軍 公教 新水 與 退休金 。 也 有人 說 是 房 地產 炒 過頭 , 所有 的 資源 都 投入 沒什麼 生產力 的 房
 的 人士 , 誰 又 不是 如此 ? 我 不如 僅憑 他 在 病榻 前所訴 說 的 故事 , 就 武斷 地書 寫 出 在 當年 鬧 的 沸沸 騰騰 的 「 
騰 的 「 星友 事件 」 , 除了 信任感 的 疑慮 外 , 我 老爸 的 說 法中 彷 彿 存在 許多神 祕 的 謎團 , 或許 是 忘 了 ! 或許 是
準確 無誤 地 再現 事件 的 原貌 是 不 可能 的 , 因為 經由 口中 說 出來 的 事永遠 不 可能 與 事件 原樣 絲 毫不 差 。 總難免 有 許
裝 保全 的 面前 吐露 積藏 在 內心 幾 十年 的 祕 密 , 尤其 是 說 不定 還會 引來 不必要 的 曝光 與 糾紛 的 秘密 。   從捷運 的 
, 沒人願 意載 客到 星 友社 區 , 好不容易 找到 一部 , 好 說歹 說 外 加上 支付 兩倍 的 現金 , 司機 才 願 意載 我 。 「 妳 真的
 呼嘯而過 的 一瞥 , 但 殘敗 的 程度 完全 映 在 眼簾 , 與 其 說 是 破舊 , 到 不如 說 是 已經 進入 自然 演化 的 廢墟 。   「
 殘敗 的 程度 完全 映 在 眼簾 , 與 其 說 是 破舊 , 到 不如 說 是 已經 進入 自然 演化 的 廢墟 。   「 那裡頭 聽 說 有 鹿 啊
 到 不如 說 是 已經 進入 自然 演化 的 廢墟 。   「 那裡頭 聽 說 有 鹿 啊 、 山羌 啊 ! 我 有 幾個 朋友 , 偶爾會 來 這一帶 打
朋友 , 偶爾會 來 這一帶 打獵 呢 ! 」 司機 喜孜孜 地 指著 窗外 說 著 。   過了 一 大片 廢棄 的 體育 園區 與 高球 場遺址 後 , 
 抵達 第四 棟 必須 先 穿越 過這三棟 大樓 中間 的 中庭 , 與 其 說 是 中庭 , 倒不如 說 是 小型 破敗 農村 , 中庭 已經 被 少數 還
 穿越 過這三棟 大樓 中間 的 中庭 , 與 其 說 是 中庭 , 倒不如 說 是 小型 破敗 農村 , 中庭 已經 被 少數 還住 在 這裡 的 居民 充
裡 找 人 ? 找什麼 人 ? 或許 一個 人 的 姓名 對 這裡 的 人來 說 , 只是 一個 虛幻 的 法律 意義 。   我 當然 知道 一點 物質 上
開來 , 電梯裡頭 一片 熏黑 燻 黑 , 好像 被 燒過 似的 , 與 其 說 是 電梯 , 倒不如 形容 是 個 墳 墓 , 電梯門 彷 若 陵墓 墓碑 
的 話 聽 進去 , 自顧 自地 走 到 最 角落 的 一戶 , 指著 大門 說 :   「 就 這一戶 ! 」   她 直接 推開 生 鏽 的 鐵 門 , 
 屋內 飛到 電梯間 走 道 。   「 是 野 鴿子 啦 ! 」 那婦 人 說 著 。   「 牠 們 八成 是 從 破損 的 窗戶 鑽 進來 的 , 後 
有人 在 嗎 ? 我要 找 姚 莉莉 女士 ! 」   那婦 人 對 著 我 說 : 「 妳 別 喊 了 ! 」   「 難道 姚 莉莉 不 在家 嗎 ? 」
, 我 不 自覺 地 把 外套 拉著 緊緊 的 , 滿腦 都 是 剛剛 老婦 說 的 毒蟲 遊民 的 畫面 。   「 我 就是 姚 莉莉 ! 」 那 老婦 
 毒蟲 遊民 的 畫面 。   「 我 就是 姚 莉莉 ! 」 那 老婦 人 說 著 。 
 「 妳 就是 yoyo ? 」 我 沒 有 在 第一 時間 就 
經 卸下 心防 , 把 我 當成 忘 年 的 閨密 。     衣物 間 可 說 是 別 有 洞天 , 至少 擺上 五十 款 各種 名牌 包包 、 上百 雙名
菜 鳥 警察 會嚇 得 亂 開槍 喔 ! 回收 的 資源 對 這裡 的 人來 說 , 可是 一筆 不小 的 財富 , 運氣 好 的 話 , 曾經 有人 搶到 
的 事情 啦 ! 我開 玩笑 的 ! 」   yoyo 收起 笑容 嚴肅 地 說 著 。   看樣子 她 這句 話 應該 沒 說 謊 , 但 我 還是 無 法
None

可以呈現詞彙分布圖:可以看到詞之間的先後順序,以及頻率分佈,下面以「說」為例:

import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5)) 
plt.rcParams['font.sans-serif'] = 'SimHei'
print(text.dispersion_plot(["說"]))
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/matplotlib/font_manager.py:1241: UserWarning: findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
  (prop.get_family(), self.defaultFamily[fontext]))

png

None

不過中文字體一直出不來,不知道為什麼,希望之後能解決。

2. SnowNLP

SnowNLP也是個可以處理中文的套件。它主要的功能有很多:可以分詞、詞性標註、情感分析、文本摘要、提取關鍵字、文本分類等等,看起來是個CP值非常高的工具。不過它斷詞的功能似乎不怎麼樣,看下面以“葉國強打斷她的話”為例,幾乎把每個字都斷開了,詞性標註也不知道是怎麼標的?

from snownlp import SnowNLP
s = SnowNLP(u'葉國強打斷她的話')
s.words
list(s.tags)
[('葉', 'o'),
 ('國', 'e'),
 ('強', 'e'),
 ('打', 'v'),
 ('斷', 'y'),
 ('她', 'r'),
 ('的', 'u'),
 ('話', 'Yg')]

我查了一下之後發現它是以簡體中文為訓練集的資料庫。而且加上它自帶了一些訓練好的字典,所以使用者雖然不用自己自建詞典於,但也因此受限於它的訓練資料庫。

3. Jieba:功能強悍的Python 中文斷詞(以及其他?)套件

jieba 斷詞有三種模式:全模式、精確模式(默認)、搜尋引擎模式,下面將分別用三種模式試試看哪種效果最好

# 全模式
import jieba
seg_list = jieba.cut("葉國強打斷她的話", cut_all=True)
print("Full Mode:", "/ ".join(seg_list)) 
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/s3/x6bgjnss22ggm9fct3z9mgz00000gn/T/jieba.cache
Loading model cost 1.014 seconds.
Prefix dict has been built succesfully.


Full Mode: 葉/ 國/ 強/ 打/ 斷/ 她/ 的/ 話
# 默認模式
seg_list = jieba.cut("葉國強打斷她的話", cut_all=False)
print("Default Mode:", "/ ".join(seg_list) ) 
seg_list = jieba.cut("小范立刻脫掉西裝強迫自己露出笑容", cut_all=False)
print("Default Mode:", "/ ".join(seg_list) ) 
Default Mode: 葉國強/ 打斷/ 她/ 的/ 話
Default Mode: 小范/ 立刻/ 脫掉/ 西裝/ 強迫/ 自己/ 露出/ 笑容
# 搜索引擎模式
seg_list = jieba.cut_for_search("葉國強打斷她的話")
print (", ".join(seg_list))
seg_list = jieba.cut_for_search("矗立著又大又明顯且看起來還很新的告示牌")
print (", ".join(seg_list))
葉國強, 打斷, 她, 的, 話
矗立, 著, 又, 大, 又, 明, 顯且, 看, 起來還, 很, 新, 的, 告示, 告示牌

Jieba 還能將斷開的詞標註詞性,如下面所示:

import jieba.posseg as jp
print(jp.lcut('葉國強打斷她的話'))

[pair('葉國強', 'nr'), pair('打斷', 'v'), pair('她', 'r'), pair('的', 'uj'), pair('話', 'n')]
print(jp.lcut('小范立刻脫掉西裝強迫自己露出笑容'))
[pair('小范', 'n'), pair('立刻', 'd'), pair('脫掉', 'v'), pair('西裝', 'n'), pair('強迫', 'a'), pair('自己', 'r'), pair('露出', 'v'), pair('笑容', 'n')]
print(jp.lcut('矗立著又大又明顯且看起來還很新的告示牌'))
[pair('矗立', 'v'), pair('著', 'v'), pair('又', 'd'), pair('大', 'a'), pair('又', 'd'), pair('明顯', 'a'), pair('且', 'c'), pair('看', 'v'), pair('起來', 'v'), pair('還', 'd'), pair('很', 'd'), pair('新', 'a'), pair('的', 'uj'), pair('告示牌', 'n')]
print(jp.lcut('說道'))
[pair('說', 'v'), pair('道', 'q')]

效果看起來很不錯,連“葉國華”這種人名都可以知道是專有名詞!(我甚至沒有沒有set user dictionary),真是太強大了~只不過較古典一點的用法“說道”似乎就沒有辦法識別。看來還是需要input自己的字典會更好。


Jieba 居然還可以提取關鍵字!分為TF-IDF與TextRank兩種:

# TF-IDF
#找出的關鍵詞會依照詞頻權重排列
s = "操著孟加拉腔調的司機長篇大論地繼續發表著"
print(jieba.analyse.extract_tags(s, topK=20, withWeight=False, allowPOS=())) #topK為返回幾個TF / IDF權重最大的關鍵詞,默認值為20
for x, w in jieba.analyse.extract_tags(s, withWeight=True):
    print('%s %s' % (x, w))
['操著', '腔調', '司機長', '大論', '地繼續', '發表著', '孟加拉']
操著 1.7078239289857142
腔調 1.7078239289857142
司機長 1.7078239289857142
大論 1.7078239289857142
地繼續 1.7078239289857142
發表著 1.7078239289857142
孟加拉 1.3092113147971427

這句的重點竟是“操著”!?“司機長”看起來是被斷開了…….

# TextRank
print(jieba.analyse.textrank(s,  withWeight=False))
for x, w in jieba.analyse.textrank(s, withWeight=True):
    print('%s %s' % (x, w))
['司機', '繼續', '腔調', '操著', '表著']
司機 1.0
繼續 0.7470070442103558
腔調 0.6683682393186766
操著 0.6656674769499933
表著 0.40679763001497043

如果比較兩種關鍵字算法,我會覺得兩個算法似乎都比較不符合人的思考邏輯跟解讀。因為這句“操著孟加拉腔調的司機長篇大論地繼續發表著”要表達的重點應該是句中的動詞組「長篇大論地發表」,而不是「操著」或「司機」。當然,這是我個人的想法啦,如果有不同的意見歡迎提出~

結論

其實也不是什麼結論啦,就是綜合評比下來結果覺得jieba在斷詞及標註詞性結果上最好,而中文的分析上還是nltk最好用。這篇blog其實只有提到部分的中文處理工具,所以或許還有更多厲害的套件或工具沒有被提到,希望之後若有機會能再多探索。

Reference