題圖:by Lucas Davies
一、前言
分詞,我想是大多數大前端開發人員,都不會接觸到的一個概念。這個不影響我們瞭解它,畢竟我們要多方向發展。今天就來簡單介紹一些分詞,我儘量用簡介的語言來描述這個概念,並且最後再提供一個解決方案,希望對你有幫助。
分詞簡單來講就是把一句話,按照詞義,切分成一個個單獨的詞。這麼說可能沒什麼感覺,先看看它適用的場景。分詞是文字挖掘的基礎,通常會用於自然語言處理、分詞搜尋、推薦等等領域。
二、分詞的原理和演算法
2.1 什麼是分詞
先理解一下分詞的概念。
分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。在英文中,單詞之間會以空格作為分割符,將詞與詞之間進行分割,但是對於中文,沒有一個顯式的分割符。
正是因為缺乏這種顯式的分割符,導致我們對中文中的詞,進行分割的時候會出現很多的偏差。
2.2 分詞的演算法
中文分詞有難度,不過也有成熟的解決方案。現有的分詞演算法,大概可分為三類:
- 基於字串匹配的分詞演算法
- 基於理解的分詞演算法
- 基於統計的分詞演算法
1. 基於字串匹配的分詞演算法
這種分詞方法,又叫機械分詞演算法,它會提前維護一個大的字典,然後將句子和字典中的詞進行匹配,若匹配成功,則可以進行分詞處理。
當然,它實際上會更復雜一些,因為當字典足夠大的時候,就又涉及到不同的匹配演算法,這裡就不展開講了。通常會基於 Trie 樹結構,來實現高效的詞圖掃描。
2. 基於理解的分詞演算法
這種分詞方法,通過讓計算機,模擬人對句子的理解,達到識別片語的效果。其基本思想是在分詞的同事進行句法、語義的分析,利用句法和語義資訊來處理歧義現象。
它通常會包含三部分:分詞子系統、句法語義子系統、總控部分。在總控部分的協調下,分詞子系統可以獲得有關詞、句子等的句法和語義資訊,來對分詞歧義進行判斷,即它模擬了人對句子的理解過程。由於漢語語言知識的籠統、複雜性,難以將各種語言資訊組織成機器可直接讀取的形式,因此目前基於理解的分詞系統還處在試驗階段。
3. 基於統計的分詞演算法
給出大量已經分詞的文字,利用統計機器學習模型學習詞語切分的規律(稱為訓練),從而實現對未知文字的切分。
隨著大規模語料庫的建立,統計機器學習方法的研究和發展,基於統計的中文分詞方法漸漸成為了主流方法。
2.3 分詞的訴求
雖然分詞的演算法,講解起來很簡單,但是從現有的經驗來看,幾乎是不存在通用且效果非常好的分詞系統。
每個領域,都有其獨特的詞彙,這很難通過有限的訓練資料,捕捉到所有的語言特徵。例如:通過人民日報訓練的分詞系統,在網路玄幻小說上,分詞的效果就不會好。
這是必然的,在分詞系統中,沒有銀彈。
不同的場景,對分詞的要求也差異很大,通常可以從兩個維度進行區分:分詞速度、分詞準確性。
例如分詞搜尋,對速度要求就高於準確性的要求。而一些問答系統中,則需要對文字實現較深的理解,要求準確性高於速度要求。
不同的領域,不同的使用場景,對分詞的要求是不同的,所以我們不能片面的去理解分詞的準確率。並且隨著新詞的增加,訓練資料的變化,分詞的準確率也是在波動的。這也是為什麼,現在吹噓分詞準確率的公司越來越少的原因。
2.4 分詞的解決方案
分詞是可以解決實際問題的功能,經過這麼長時間的反覆迭代更新,市面上一家產生了一批有特色的分詞系統。例如:IK、Jieba、Ansj、Hanlp、Stanford分詞 等等。
有興趣可以一個個瞭解,接下來就其中的一個開源庫 Jieba,進行講解。
三、jieba
3.1 jieba 的優點
jieba 是開源的,號稱是 Python 中,最好的中文分片語件。並且是基於 MIT 的協議,使用起來無後顧之憂。
jieba 使用起來也非常的簡單,幾行程式碼就可以實現分詞呼叫和詞性標註,而且速度還不錯。
它內部維護了一個詞典,是根據人民日報分析獲得,在超出詞典之外的新詞,會基於 HMM 模型進行識別。
它提供三種分詞模式:精準模式、全模式、搜尋模式。全模式是找到所有可能的詞語,搜尋模式是在精確模式的基礎上對長詞進行切分,提高分割率。
在分詞的速度上,精確模式能達到 400KB/s,全模式下能達到 1.5MB/s。同時除了 Python 版本之外,還有不同的人基於 Python 版的 jieba ,擴充套件出多種語言實現,包括:JavaScript、Java、Golang、R、PHP 等。
jieba 的使用
jieba 的程式碼對 Python 2/3 均相容,在使用之前,需要通過命令 pip install jieba
或者 pip3 install jieba
進行安裝。
具體 Api,就不展開講了,有興趣可以去檢視 Github 上的文件(文末有地址)。
這裡提供一個簡單的程式碼示例,來感受一下 jieba 的方便與強大。
# encoding=utf-8
import jieba
seg_list = jieba.cut("我來到北京清華大學", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut("我來到北京清華大學", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list)) # 精確模式
seg_list = jieba.cut("他來到了網易杭研大廈") # 預設是精確模式
print(", ".join(seg_list))
seg_list = jieba.cut_for_search("小明碩士畢業於中國科學院計算所,後在日本京都大學深造") # 搜尋引擎模式
print(", ".join(seg_list))
複製程式碼
輸出的結果:
【全模式】: 我/ 來到/ 北京/ 清華/ 清華大學/ 華大/ 大學
【精確模式】: 我/ 來到/ 北京/ 清華大學
【新詞識別】:他, 來到, 了, 網易, 杭研, 大廈 (此處,“杭研”並沒有在詞典中,但是也被Viterbi演算法識別出來了)
【搜尋引擎模式】: 小明, 碩士, 畢業, 於, 中國, 科學, 學院, 科學院, 中國科學院, 計算, 計算所, 後, 在, 日本, 京都, 大學, 日本京都大學, 深造
複製程式碼
前面也提到,jieba 自身維護了一個片語的字典,如果自身需求上有專有名詞需要拆分,還可以通過 jieba.Tokenizer(dictionary=DEFAULT_DICT)
自定義一個字典資訊。
3.2 jieba 的分詞演算法
匹配的演算法,說起來就複雜了,這裡就簡單介紹一下 jiaba 分詞匹配的原理。
首先,jieba 分詞已經自帶了一個 dict.txt 的詞典,裡面有 2w 多個詞條,包括出現的次數和詞性,這是作者自己基於人民日報為主的資料,訓練的出來的。
jieba 會先將這個詞典中的資料,放到一個 Trie 樹中,Trie 樹是有名的字首樹,當一個詞語的前面幾個字一樣的時候,就標識他們具有相同的字首,就可以使用 Trie 數來儲存,具有查詢速度快的優勢。
其次,在需要對句子進行分詞的時候,再根據前面生成的 Trie 數,生成有向無環圖(DAG),這一步的意義在於,消除分詞中的歧義,提高切分準確度,找出這句話中,所有可能的詞。
到這一步,基本上就完成了,所有字典中記錄的詞,進行分詞的過程。
但是如果你把 dict.txt 這個字典刪除,jieba 依然可以進行分詞,只是拆分出來的詞,大部分的長度為 2。這是因為,對於未在字典中收錄的詞,基於隱馬爾科夫模型(HMM)來預測分詞,使用的是 Viterbi 演算法。
HMM 模型中,將中文詞彙按照 BEMS 四個狀態來標記, B 是開始 begin 位置, E 是 end, 是結束位置, M 是 middle, 是中間位置, S 是 singgle, 單獨成詞的位置, 沒有前, 也沒有後. 也就是說, 他採用了狀態為(B,E,M,S)這四種狀態來標記中文詞語, 比如北京可以標註為 BE, 即 北/B 京/E, 表示北是開始位置, 京是結束位置, 中華民族可以標註為 BMME , 就是開始, 中間, 中間, 結束.
作者通過對大量語料的訓練,得到了 finalseg 目錄下的訓練結果,有興趣可以自行研究。
到這裡基本上就清晰了,jieba 分詞的過程主要有以下三步:
- 載入 dict.txt 字典,生成 Trie 樹。
- 對待分詞的句子,通過 Trie 樹,生成 DAG 圖,匹配出所有可能的詞。
- 再使用 HMM 模型,將字典中未收錄的詞,匹配出來。
這就是 jieba 分詞的執行過程。
四、jieba(Java or Android)
4.1 Java 版的 jieba
jieba 發展到現在,已經支援眾多的版本。Java 版並非原作者開發,而是 hanban 參考原作者的分詞原理,進行開發的。
不過 Java 版並沒有原版 Python 版本那麼強大,做了部分閹割,例如關鍵詞提取就沒有實現。
有興趣可以直接去看 Github : https://github.com/huaban/jieba-analysis/
1. 引入依賴(穩定版)
<dependency>
<groupId>com.huaban</groupId>
<artifactId>jieba-analysis</artifactId>
<version>1.0.2</version>
</dependency>
複製程式碼
2. 如何使用
@Test
public void testDemo() {
JiebaSegmenter segmenter = new JiebaSegmenter();
String[] sentences =
new String[] {"這是一個伸手不見五指的黑夜。我叫孫悟空,我愛北京,我愛Python和C++。", "我不喜歡日本和服。", "雷猴迴歸人間。",
"工信處女幹事每月經過下屬科室都要親口交代24口交換機等技術性器件的安裝工作", "結果婚的和尚未結過婚的"};
for (String sentence : sentences) {
System.out.println(segmenter.process(sentence, SegMode.INDEX).toString());
}
}
複製程式碼
3. 效能評估
作者在測試機上進行測試,配置為:
Processor 2 Intel(R) Pentium(R) CPU G620 @ 2.60GHz
Memory:8GB
複製程式碼
測試結果還算理想,單執行緒,對測試文字逐行分詞,並迴圈呼叫上萬次的效率分析。
迴圈呼叫一萬次
第一次測試結果:
time elapsed:12373, rate:2486.986533kb/s, words:917319.94/s
第二次測試結果:
time elapsed:12284, rate:2505.005241kb/s, words:923966.10/s
第三次測試結果:
time elapsed:12336, rate:2494.445880kb/s, words:920071.30/s
迴圈呼叫2萬次
第一次測試結果:
time elapsed:22237, rate:2767.593144kb/s, words:1020821.12/s
第二次測試結果:
time elapsed:22435, rate:2743.167762kb/s, words:1011811.87/s
第三次測試結果:
time elapsed:22102, rate:2784.497726kb/s, words:1027056.34/s
統計結果:詞典載入時間1.8s左右,分詞效率每秒2Mb多,近100萬詞。
2 Processor Intel(R) Core(TM) i3-2100 CPU @ 3.10GHz
12G 測試效果
time elapsed:19597, rate:3140.428063kb/s, words:1158340.52/s
time elapsed:20122, rate:3058.491639kb/s, words:1128118.44/s
複製程式碼
4.2 在 Android 下使用 jieba
jieba(Java)版本,本身也是自帶詞典的,所以在 Android 下引入,會增大 Apk 的體積,這沒有什麼很好的規避方法。而且因為裝置的配置,還會影響到分詞的效率。
不過如果非要使用在 Android 裝置上,例如對搜尋詞進行一個預處理,也是可以的。
jieba(java) 使用 maven 管理,所以需要 Gradle 簡單配置一下,讓其支援。
1. 配置 build.gradle
repositories {
google()
jcenter()
mavenCentral()
}
複製程式碼
2. 引入依賴
api 'com.huaban:jieba-analysis:1.0.2'
複製程式碼
引入之後,使用細節就沒什麼好說的了,和 Java 版本無差別。
參考:
https://github.com/fxsjy/jieba
https://github.com/huaban/jieba-analysis/
https://blog.csdn.net/John_xyz/article/details/54645527
http://www.infoq.com/cn/articles/nlp-word-segmentation
公眾號後臺回覆成長『成長』,將會得到我準備的學習資料,也能回覆『加群』,一起學習進步;你還能回覆『提問』,向我發起提問。
推薦閱讀:
寫作是核心競爭力 | Google 工程師解密“猜畫小歌” | 圖解:HTTP 範圍請求 | Android P 適配經驗 | 技術創業選擇清單 | HTTP傳輸編碼 | 什麼正在消耗你? | HTTP 內容編碼 | 圖解 HTTP 快取 | 聊聊 HTTP 的 Cookie | 輔助模式實戰 | Accessibility 輔助模式 | 小程式 Flex 佈局 | 好的 PR 讓你更靠譜 | 密碼管理之道