2020/08/03

中文斷詞

在中文自然語言處理NLP中,要對一堆文字詞語組成的文章進行分析, 分析前要先拆解文章,也就是斷詞,我們要分析的對象是詞語,而不是一個一個中文字,這跟英文完全不同,因為英文的斷詞就直接用標點符號、空白去區隔即可。

目前繁體中文斷詞系統有 中研院 CKIP 以及 jieba,在一些舊的文章中都提到 jieba 無法適當地處理繁體中文,而有替換繁體中文字典的改進作法,不過目前 jieba 已經到了 0.42 版,以下先了解官方的套件的功能,再看看需不需要修改繁體中文字典。

jieba 演算法

  • 基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)
  • 採用了動態規劃查找最大概率路徑,找出基於詞頻的最大切分組合
  • 對於未登錄詞,採用了基於漢字成詞能力的 HMM 模型,使用了 Viterbi 算法

安裝

可直接用 pip 安裝,或是將 jieba source code 的 jieba 目錄放在目前的工作目錄,或是 site-packages 目錄中

如果要使用 paddle 的分詞語詞性標注功能,必須安裝 paddlepaddle-tiny

pip3 install paddlepaddle-tiny==1.6.1

先直接下載 source code 試試看

wget https://github.com/fxsjy/jieba/archive/v0.42.1.tar.gz -O jieba-0.41.1.tgz

tar zxvf jieba-0.41.1.tgz

virtual environemnt

virtualenv --system-site-packages /root/venv-jieba
source /root/venv-jieba/bin/activate

## 如果不使用 paddlepaddle,這兩個套件也可以不安裝
pip3 install numpy==1.16.4
pip3 install paddlepaddle-tiny==1.6.1

# 把 soruce code 中的 jieba 目錄移動到工作目錄中
mv ~/temp/download/jieba-0.41.1/jieba ~/temp

斷詞

有四種斷詞模式

  • 精確模式,試圖將句子最精確地切開,適合文本分析
  • 完整模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
  • paddle模式,利用PaddlePaddle深度學習框架,訓練序列標注(雙向GRU)網絡模型實現分詞。同時支持詞性標注。paddle模式使用需安裝 paddlepaddle-tiny

函式

  • jieba.cut 方法接受四個輸入參數: 需要分詞的字符串;cutall 參數用來控制是否採用全模式;HMM 參數用來控制是否使用 HMM 模型;usepaddle 參數用來控制是否使用paddle模式下的分詞模式,paddle模式採用延遲加載方式,通過enable_paddle接口安裝paddlepaddle-tiny,並且import相關代碼
  • jieba.cutforsearch 方法接受兩個參數:需要分詞的字符串;是否使用 HMM 模型。該方法適合用於搜索引擎構建倒排索引的分詞,粒度比較細
  • 待分詞的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建議直接輸入 GBK 字符串,可能無法預料地錯誤解碼成 UTF-8
  • jieba.cut 以及 jieba.cutforsearch 返回的結構都是一個可迭代的 generator,可以使用 for 循環來獲得分詞後得到的每一個詞語(unicode),或者用 jieba.lcut 以及 jieba.lcutforsearch 直接返回 list
  • jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定義分詞器,可用於同時使用不同詞典。jieba.dt 為默認分詞器,所有全局分詞相關函數都是該分詞器的映射。

# encoding=utf-8
import jieba

print()
print("完整模式:")
seg_list = jieba.cut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list))  # 全模式

print()
print("精確模式:")
seg_list = jieba.cut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精確模式

print()
print("預設是精確模式:")
seg_list = jieba.cut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。")  # 預設是精確模式
print(", ".join(seg_list))

print()
print("搜索引擎模式:")
seg_list = jieba.cut_for_search("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。")  #
print(", ".join(seg_list))

print()
print("Paddle Mode:")
jieba.enable_paddle()# 啓動paddle模式。 0.40版之後開始支持,早期版本不支持
strs=["肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。","乒乓球拍賣完了","新竹清華大學"]
for str in strs:
    seg_list = jieba.cut(str,use_paddle=True) # 使用paddle模式
    print("Paddle Mode: " + '/'.join(list(seg_list)))

執行結果

完整模式:
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.433 seconds.
Prefix dict has been built successfully.
Full Mode: 肺炎/ 疫情/ 的/ 挑/ 戰/ 日益/ 嚴/ 峻/ ,/ 新竹/ 清/ 華/ 大/ 學/ 自/ 農/ 曆/ 年/ 起/ 陸/ 續/ 已/ 採/ 取/ 了/ 量/ 測/ 體/ 溫/ 等/ 全面/ 的/ 防疫/ 措施/ 。

精確模式:
Default Mode: 肺炎/ 疫情/ 的/ 挑戰/ 日益/ 嚴峻/ ,/ 新竹/ 清華大學/ 自農/ 曆/ 年/ 起/ 陸續/ 已/ 採取/ 了/ 量/ 測體/ 溫/ 等/ 全面/ 的/ 防疫/ 措施/ 。

預設是精確模式:
肺炎, 疫情, 的, 挑戰, 日益, 嚴峻, ,, 新竹, 清華大學, 自農, 曆, 年, 起, 陸續, 已, 採取, 了, 量, 測體, 溫, 等, 全面, 的, 防疫, 措施, 。

搜索引擎模式:
肺炎, 疫情, 的, 挑戰, 日益, 嚴峻, ,, 新竹, 清華大學, 自農, 曆, 年, 起, 陸續, 已, 採取, 了, 量, 測體, 溫, 等, 全面, 的, 防疫, 措施, 。

Paddle Mode:
W0327 11:45:31.179752 21561 init.cc:157] AVX is available, Please re-compile on local machine
Paddle enabled successfully......
Paddle Mode: 肺炎/疫情/的/挑戰/日益/嚴/峻,新竹清華大學自農曆年起陸續/已/採取/了/量測體溫/等/全面/的/防疫/措施/。
Paddle Mode: 乒乓/球拍/賣/完/了
Paddle Mode: 新竹/清華大學
  • jieba.lcut() lcut(),意思跟cut()是一樣的,只是返回的型態變成list

# encoding=utf-8
import jieba

print()
print("完整模式:")
seg_list = jieba.lcut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=True)
print("Full Mode: ", seg_list)  # 全模式


print()
print("精確模式:")
seg_list = jieba.lcut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=False)
print("Default Mode: ", seg_list)  # 精確模式

print()
print("預設是精確模式:")
seg_list = jieba.lcut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。")  # 預設是精確模式
print(seg_list)

print()
print("搜索引擎模式:")
seg_list = jieba.lcut_for_search("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。")  #
print(seg_list)

print()
print("Paddle Mode:")
jieba.enable_paddle()# 啓動paddle模式。 0.40版之後開始支持,早期版本不支持
strs=["肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。","乒乓球拍賣完了","新竹清華大學"]
for str in strs:
    seg_list = jieba.lcut(str,use_paddle=True) # 使用paddle模式
    print("Paddle Mode: ", seg_list)

自訂詞典

  • 雖然 jieba 有新詞識別能力,但是自行添加新詞可以保證更高的正確率

  • 用法: jieba.loaduserdict(filename)

    file_name 為文件類對象或自定義詞典的路徑

  • 詞典格式和 dict.txt 一樣,一個詞佔一行;每一行分三部分:詞語、詞頻(可省略)、詞性(可省略),用空格隔開,順序不可顛倒。file_name 若為路徑或二進制方式打開的文件,則文件必須為 UTF-8 編碼。

  • 詞頻省略時使用自動計算的方式處理,能保證分出該詞的詞頻。

  • 使用 addword(word, freq=None, tag=None) 和 delword(word) 可在程序中動態修改詞典。

  • 使用 suggest_freq(segment, tune=True) 可調節單個詞語的詞頻,使其能(或不能)被分出來。

  • 注意:自動計算的詞頻在使用 HMM 新詞發現功能時可能無效。

在專案路徑下新增一個檔案叫做:userdict.txt

內容如下:

農曆年
量測
體溫
日益嚴峻

可在程式一開始,就載入自訂詞典

jieba.load_userdict('userdict.txt')

執行結果

精確模式:
Default Mode:  ['肺炎', '疫情', '的', '挑戰', '日益嚴峻', ',', '新竹', '清華大學', '自', '農曆年', '起陸續', '已', '採取', '了', '量測', '體溫', '等', '全面', '的', '防疫', '措施', '。']

可動態調整詞語的頻率

jieba.suggest_freq(('陸續'), True)
print()
print("精確模式2:")
seg_list = jieba.lcut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=False)
print("Default Mode: ", seg_list)  # 精確模式

執行結果

精確模式2:
Default Mode:  ['肺炎', '疫情', '的', '挑戰', '日益嚴峻', ',', '新竹', '清華大學', '自', '農曆年', '起', '陸續', '已', '採取', '了', '量測', '體溫', '等', '全面', '的', '防疫', '措施', '。']

也可以直接替換詞典

ithomeironman/day16NLP_Chinese/ 可下載一個繁體中文的字典 dict.txt.big

# encoding=utf-8
import jieba

jieba.set_dictionary('dict.txt.big')
# with open('stops.txt', 'r', encoding='utf8') as f:
#     stops = f.read().split('\n')

print()
print("精確模式:")
seg_list = jieba.lcut("肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。", cut_all=False)
print("Default Mode: ", seg_list)

關鍵詞抽取

TF-IDF 方法

# jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
# sentence 為待提取的文本
# topK 為返回幾個 TF/IDF 權重最大的關鍵詞,默認值為 20
# withWeight 為是否一並返回關鍵詞權重值,默認值為 False
# allowPOS 僅包括指定詞性的詞,默認值為空,即不篩選
# jieba.analyse.TFIDF(idf_path=None) 新建 TFIDF 實例,idf_path 為 IDF 頻率文件

TextRank 方法

jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v')) 直接使用,接口相同,注意默認過濾詞性。
jieba.analyse.TextRank() 新建自定義 TextRank 實例
# -*- coding: utf-8 -*-
import jieba
import jieba.analyse

jieba.set_dictionary('dict.txt.big')

print()
text = '肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。'
tags = jieba.analyse.extract_tags(text, topK=10)
print(tags)
# ['挑戰', '嚴峻', '清華大學', '農曆年', '陸續', '採取', '測體溫', '防疫', '新竹', '肺炎']

print()
print('textrank:')
for x, w in jieba.analyse.textrank(text, withWeight=True):
    print('%s %s' % (x, w))

# textrank:
# 日益 1.0
# 全面 1.0
# 肺炎 0.6631715416020616
# 防疫 0.6631715416020616
# 疫情 0.6605033585768562
# 措施 0.6605033585768562
# 新竹 0.3607120276929184
# 了量 0.3607120276929184

詞性標注

ref: 彙整中文與英文的詞性標註代號:結巴斷詞器與FastTag / Identify the Part of Speech in Chinese and English

結巴預設會將標點符號標示為「x」,而不是「w」。而且英文會被標示為「eng」

# -*- coding: utf-8 -*-
import jieba
import jieba.posseg as pseg

text = '肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。'
seg_list = pseg.lcut(text)
# print("Default Mode: ", seg_list)

for word, flag in seg_list:
    print("", word, " : ", flag)

執行結果

 肺炎  :  n
 疫情  :  n
 的  :  uj
 挑戰  :  vn
 日益  :  n
 嚴峻  :  a
 ,  :  x
 新竹  :  ns
 清華大學  :  nt
 自  :  p
 農  :  ng
 曆  :  zg
 年  :  q
 起  :  v
 陸  :  nr
 續  :  v
 已  :  d
 採  :  v
 取  :  v
 了  :  ul
 量  :  n
 測  :  v
 體  :  ng
 溫  :  v
 等  :  u
 全面  :  n
 的  :  uj
 防疫  :  vn
 措施  :  n
 。  :  x

word cloud

參考這篇文章中文自然語言處理基礎 以及資源

下載檔案:

cloud_mask7.png
sumsun.ttf
stops.txt

安裝其它套件

pip3 install collections
pip3 install wordcloud
pip3 install matplotlib
yum -y install python-imaging
import jieba
jieba.set_dictionary('dict.txt.big')  # 如果是使用繁體文字,請記得去下載繁體字典來使用
with open('stops.txt', 'r', encoding='utf8') as f:
    stops = f.read().split('\n')

text = "肺炎疫情的挑戰日益嚴峻,新竹清華大學自農曆年起陸續已採取了量測體溫等全面的防疫措施。"

from collections import Counter
from wordcloud import WordCloud
from matplotlib import pyplot as plt

stops.append('\n')  ## 換行符號,加入停用字中,可以把它拿掉
stops.append('\n\n')
terms = [t for t in jieba.cut(text, cut_all=True) if t not in stops]

sorted(Counter(terms).items(), key=lambda x:x[1], reverse=True)  ## 這個寫法很常出現在Counter中,他可以排序,list每個item出現的次數。


plt.clf()
wordcloud = WordCloud(font_path="simsun.ttf")  ##做中文時務必加上字形檔
wordcloud.generate_from_frequencies(frequencies=Counter(terms))
plt.figure(figsize=(15,15))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis("off")
plt.savefig('cloud1.png')


from PIL import Image
import numpy as np

alice_mask = np.array(Image.open("cloud_mask7.png"))  ## 請更改cloud_mask7.png路徑
wc = WordCloud(background_color="white", max_words=2000, mask=alice_mask, font_path="simsun.ttf")
wc.generate_from_frequencies(Counter(terms))  ## 請更改Counter(terms)

wc.to_file("cloud2.png")  ##如果要存檔,可以使用

# plt.clf()
# plt.imshow(wc, interpolation='bilinear')
# plt.axis("off")
# plt.figure()
# plt.imshow(alice_mask, cmap=plt.cm.gray, interpolation='bilinear')
# plt.axis("off")
# plt.savefig('cloud2.png')

中文斷詞.png

References

pypi jieba

jieba 原始 github

python-11-利用jieba實現中文斷詞

NLP 中文斷詞最方便的開源工具之一 —— Jieba

Python-知名Jieba中文斷詞工具教學

2017

結巴中文斷詞台灣繁體版本 APCLab

結巴中文斷詞台灣繁體版本 ldkrsi

沒有留言:

張貼留言