中文分詞
對於英文句子來說,可以通過空格
來切分單詞,如
// 今天天氣不錯
the weather is nice today
可以很簡單的把該句子中的單詞區分出來
the/weather/is/nice/today
在中文裏面,就沒有那麼方便的區分方法了。當然,如果你習慣這樣說話:
今天 天氣 不錯
大家也不會打你,只會覺得你像個“結巴”
(點題了!)
爲什麼需要分詞?
在中文裏面的字
和英文單詞
是兩個不同的東西。在讀書的時候,最痛苦的一件事就是學習文言文,我想了一下,有大於等於三個原因:
- 不知道在哪裏斷句
- 字或詞的含義很多
- 這個字是通假字(它不是這個它,它是那個它),或者說純粹就是寫錯了,但是細想一下也能讀的通。
我們常說中文博大精深,歷史原因就不細究了,簡單來說就是,我們的祖先在中文上的造詣非常高,好幾層樓那麼高,研究非常透徹,一句話能說出幾個意思。我們自小在中文環境下成長,經過千錘百煉,讀寫是沒問題的,但是計算機要怎麼理解一句話呢?先從分詞開始。
計算機學習分詞的過程,和人類是很像的(或許這是侷限性),目前有幾種:
- 基於字符串匹配。按一定的策略在一個已分好詞的大詞典中搜尋,若找到則視爲一個詞。
- 統計。大量已經分詞的文本,利用統計機器學習模型學習詞語切分的規律(訓練),從而實現對未知文本的切分。
- 組合。結合1、2點,如
結巴分詞
。
我們學習中文的時候,也有這樣的過程,
- 積累詞語(建立詞典)
- 訓練不同詞語在不同句子中的含義的概率(權重),選擇具有最大概率(權重)的含義的詞語(動態規劃,尋找切分組合)
結巴分詞是什麼?
結巴分詞
是國內程序員用python
開發的一個中文
分詞模塊, 源碼被託管在Github
爲了方便說明,下面截取了部分文檔和例子。
特點
- 精確模式,試圖將句子最精確地切開,適合文本分析
- 全模式,把句子中所有可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義
- 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞
- 支持繁體分詞
- 支持自定義詞典
- MIT 授權協議
例子
# 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算法識別出來了)
【搜索引擎模式】: 小明, 碩士, 畢業, 於, 中國, 科學, 學院, 科學院, 中國科學院, 計算, 計算所, 後, 在, 日本, 京都, 大學, 日本京都大學, 深造
算法實現
- 基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)
- 採用了動態規劃查找最大概率路徑, 找出基於詞頻的最大切分組合
- 對於未登錄詞,採用了基於漢字成詞能力的
HMM
模型,使用了Viterbi
算法
針對結巴分詞的原理,網上的文章寫的非常詳細了,這裏就不再贅述了。有興趣的讀者可以看看
PHP結巴分詞庫(擴展)
有國人實現了PHP版本:
- PHP擴展:jonnywang/phpjieba
- PHP類庫:fukuball/jieba-php
尤其是這個擴展jonnywang/phpjieba
實現,支持PHP7,果斷安裝了。
使用方法
PHP-FPM模式
PHP的LNMP架構在Web開發領域常年佔據一定的市場,那麼是否可以使用結巴分詞呢?當然可以,不過,我們知道在FPM模式下,PHP的生命週期非常短,前面我們瞭解到,結巴分詞使用前綴字典樹
建立詞庫,該操作需要一定的時間和耗費內存(默認詞典dict.txt佔用將近1G)。那麼,在常規FPM模式下,假設開啓8個worker,那就需要大約8G內存分配。而且,在應對大量請求時,頻繁的申請/銷燬操作並不合理。所以,在FPM模式下,使用結巴分詞不合適。
CLI模式
我們想到,和應用強耦合在一起不是個好辦法,把結巴分詞獨立出來作爲一個公共服務,通過不同的接口(HTTP,unixsocket)給其他應用提供服務是個不錯的方案。
在考察該方案前,我們需要解決幾個問題:
- 進程拉起才初始化詞典
- 爲其他應用提供分詞服務,需要應對高併發
- 更新用戶自定義詞庫
我們第一時間想到了Swoole,有下面的優勢:
- 假設提供HTTP服務,可以在Worker進程啓動時(onWorkerStart)初始化詞典,當服務啓動後,字典樹就完全載入到內存中了,由於常駐內存,後面我們只需要處理請求(onRequest)即可。
- 使用HTTP服務,可以爲其他應用提供服務,而不需要每一個需要分詞服務的應用都寫一個類似的分詞庫。
- 用戶自定義詞庫需要在初始化詞典階段載入,因此,如果需要添加/刪除自定義詞庫,需要做下面幾步:
- Process模式
- 服務啓動時,記錄Master進程ID到本地文件
- 提供給外部應用增加/刪除詞的接口,寫入用戶自定義詞庫(user_dict.txt)文件
- Worker進程給Master進程發
SIGUSR1
信號,進行柔性重啓(重啓Worker進程)
- Base模式
- 只有一個Worker進程,默認不開啓Manager進程,所以需要自己終止掉,由外部來重啓,如Supervisor
- 大於等於兩個Worker進程
- 服務啓動時,記錄Manager進程ID到本地文件
- 同Process模式第2點
- 同Process模式第3點
- Process模式
Base模式比Process模式少了兩次ipc的過程,性能會更好些。
性能測試
- 4c
- 2g
Base 模式、1 Worker
- 請求:10000
- 併發:1000
- api:a=2&s=我愛中華民族、廣東、美食
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software: swoole-http-server
Server Hostname: 127.0.0.1
Server Port: 9501
Document Path: /
Document Length: 204 bytes
Concurrency Level: 1000
Time taken for tests: 8.499 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 10000
Total transferred: 3580000 bytes
Total body sent: 2180000
HTML transferred: 2040000 bytes
Requests per second: 1176.63 [#/sec] (mean)
Time per request: 849.883 [ms] (mean)
Time per request: 0.850 [ms] (mean, across all concurrent requests)
Transfer rate: 411.36 [Kbytes/sec] received
250.49 kb/s sent
661.86 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 5 14.1 0 69
Processing: 29 800 181.2 840 1260
Waiting: 4 800 181.2 840 1260
Total: 30 805 174.0 840 1275
Percentage of the requests served within a certain time (ms)
50% 840
66% 855
75% 866
80% 870
90% 894
95% 912
98% 1139
99% 1214
100% 1275 (longest request)
Base 模式、2 Worker
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software: swoole-http-server
Server Hostname: 127.0.0.1
Server Port: 9501
Document Path: /
Document Length: 204 bytes
Concurrency Level: 1000
Time taken for tests: 4.746 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 10000
Total transferred: 3580000 bytes
Total body sent: 2180000
HTML transferred: 2040000 bytes
Requests per second: 2106.85 [#/sec] (mean)
Time per request: 474.643 [ms] (mean)
Time per request: 0.475 [ms] (mean, across all concurrent requests)
Transfer rate: 736.57 [Kbytes/sec] received
448.53 kb/s sent
1185.10 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 9 28.0 0 148
Processing: 0 415 407.8 421 1270
Waiting: 0 415 407.8 421 1270
Total: 0 423 409.2 443 1282
Percentage of the requests served within a certain time (ms)
50% 443
66% 822
75% 827
80% 830
90% 838
95% 850
98% 1157
99% 1225
100% 1282 (longest request)