Swoole加速結巴分詞

中文分詞

對於英文句子來說,可以通過空格來切分單詞,如

// 今天天氣不錯
the weather is nice today

可以很簡單的把該句子中的單詞區分出來

the/weather/is/nice/today

在中文裏面,就沒有那麼方便的區分方法了。當然,如果你習慣這樣說話:

今天 天氣 不錯

大家也不會打你,只會覺得你像個“結巴”(點題了!)

爲什麼需要分詞?

在中文裏面的英文單詞是兩個不同的東西。在讀書的時候,最痛苦的一件事就是學習文言文,我想了一下,有大於等於三個原因:

  1. 不知道在哪裏斷句
  2. 字或詞的含義很多
  3. 這個字是通假字(它不是這個它,它是那個它),或者說純粹就是寫錯了,但是細想一下也能讀的通。

我們常說中文博大精深,歷史原因就不細究了,簡單來說就是,我們的祖先在中文上的造詣非常高,好幾層樓那麼高,研究非常透徹,一句話能說出幾個意思。我們自小在中文環境下成長,經過千錘百煉,讀寫是沒問題的,但是計算機要怎麼理解一句話呢?先從分詞開始。

計算機學習分詞的過程,和人類是很像的(或許這是侷限性),目前有幾種:

  1. 基於字符串匹配。按一定的策略在一個已分好詞的大詞典中搜尋,若找到則視爲一個詞。
  2. 統計。大量已經分詞的文本,利用統計機器學習模型學習詞語切分的規律(訓練),從而實現對未知文本的切分。
  3. 組合。結合1、2點,如結巴分詞

我們學習中文的時候,也有這樣的過程,

  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算法識別出來了)

【搜索引擎模式】: 小明, 碩士, 畢業, 於, 中國, 科學, 學院, 科學院, 中國科學院, 計算, 計算所, 後, 在, 日本, 京都, 大學, 日本京都大學, 深造

算法實現

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

針對結巴分詞的原理,網上的文章寫的非常詳細了,這裏就不再贅述了。有興趣的讀者可以看看

  1. 中文分詞的基本原理以及jieba分詞的用法
  2. 一個隱馬爾科夫模型的應用實例:中文分詞

PHP結巴分詞庫(擴展)

有國人實現了PHP版本:

  1. PHP擴展:jonnywang/phpjieba
  2. 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)給其他應用提供服務是個不錯的方案。

在考察該方案前,我們需要解決幾個問題:

  1. 進程拉起才初始化詞典
  2. 爲其他應用提供分詞服務,需要應對高併發
  3. 更新用戶自定義詞庫

我們第一時間想到了Swoole,有下面的優勢:

  1. 假設提供HTTP服務,可以在Worker進程啓動時(onWorkerStart)初始化詞典,當服務啓動後,字典樹就完全載入到內存中了,由於常駐內存,後面我們只需要處理請求(onRequest)即可。
  2. 使用HTTP服務,可以爲其他應用提供服務,而不需要每一個需要分詞服務的應用都寫一個類似的分詞庫。
  3. 用戶自定義詞庫需要在初始化詞典階段載入,因此,如果需要添加/刪除自定義詞庫,需要做下面幾步:
    • Process模式
      1. 服務啓動時,記錄Master進程ID到本地文件
      2. 提供給外部應用增加/刪除詞的接口,寫入用戶自定義詞庫(user_dict.txt)文件
      3. Worker進程給Master進程發SIGUSR1信號,進行柔性重啓(重啓Worker進程)
    • Base模式
      • 只有一個Worker進程,默認不開啓Manager進程,所以需要自己終止掉,由外部來重啓,如Supervisor
      • 大於等於兩個Worker進程
        1. 服務啓動時,記錄Manager進程ID到本地文件
        2. 同Process模式第2點
        3. 同Process模式第3點

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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章