結巴分詞 java 高性能實現,優雅易用的 api 設計,性能優於 huaban jieba 分詞

Segment

Segment 是基於結巴分詞詞庫實現的更加靈活,高性能的 java 分詞實現。

變更日誌

創作目的

分詞是做 NLP 相關工作,非常基礎的一項功能。

jieba-analysis 作爲一款非常受歡迎的分詞實現,個人實現的 opencc4j 之前一直使用其作爲分詞。

但是隨着對分詞的瞭解,發現結巴分詞對於一些配置上不夠靈活。

(1)有很多功能無法指定關閉,比如 HMM 對於繁簡體轉換是無用的,因爲繁體詞是固定的,不需要預測。

(2)最新版本的詞性等功能好像也被移除了,但是這些都是個人非常需要的。

(3)對於中文繁體分詞支持不友好。

所以重新實現了一遍,希望實現一套更加靈活,更多特性的分詞框架。

而且 jieba-analysis 的更新似乎停滯了,個人的實現方式差異較大,所以建立了全新的項目。

Features 特點

  • 面向用戶的極簡靜態 api 設計

  • 面向開發者 fluent-api 設計,讓配置更加優雅靈活

  • 詳細的中文代碼註釋,便於源碼閱讀

  • 基於 DFA 實現的高性能分詞

  • 基於 HMM 的新詞預測

  • 支持不同的分詞模式

  • 支持全角半角/英文大小寫/中文繁簡體格式處理

  • 允許指定自定義詞庫

最新變更

  • 支持中文繁體分詞

快速入門

準備

jdk1.7+

maven 3.x+

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>segment</artifactId>
    <version>0.1.2</version>
</dependency>

相關代碼參見 SegmentHelperTest.java

默認分詞示例

返回分詞,下標等信息。

final String string = "這是一個伸手不見五指的黑夜。我叫孫悟空,我愛北京,我愛學習。";

List<ISegmentResult> resultList = SegmentHelper.segment(string);
Assert.assertEquals("[這是[0,2), 一個[2,4), 伸手不見五指[4,10), 的[10,11), 黑夜[11,13), 。[13,14), 我[14,15), 叫[15,16), 孫悟空[16,19), ,[19,20), 我愛[20,22), 北京[22,24), ,[24,25), 我愛[25,27), 學習[27,29), 。[29,30)]", resultList.toString());

指定返回形式

有時候我們根據自己的應用場景,需要選擇不同的返回形式。

SegmentResultHandlers 用來指定對於分詞結果的處理實現,便於保證 api 的統一性。

方法 實現 說明
common() SegmentResultHandler 默認實現,返回 ISegmentResult 列表
word() SegmentResultWordHandler 只返回分詞字符串列表

默認模式

默認分詞形式,等價於下面的寫法

List<ISegmentResult> resultList = SegmentHelper.segment(string, SegmentResultHandlers.common());

只獲取分詞信息

final String string = "這是一個伸手不見五指的黑夜。我叫孫悟空,我愛北京,我愛學習。";

List<String> resultList = SegmentHelper.segment(string, SegmentResultHandlers.word());
Assert.assertEquals("[這是, 一個, 伸手不見五指, 的, 黑夜, 。, 我, 叫, 孫悟空, ,, 我愛, 北京, ,, 我愛, 學習, 。]", resultList.toString());

分詞模式

分詞模式簡介

分詞模式可以通過類 SegmentModes 工具類獲取。

序號 方法 準確度 性能 備註
1 search() 一般 結巴分詞的默認模式
2 dict() 較高 一般 和 search 模式類似,但是缺少 HMM 新詞預測
3 index() 一般 儘可能多的返回詞組信息,提高召回率
4 greedyLength() 一般 貪心最大長度匹配,對準確度要求不高時可採用。

使用方式

針對靈活的配置,引入了 SegmentBs 作爲引導類,解決工具類方法配置參數過多的問題。

測試代碼參見 SegmentModeTest.java

search 模式

segmentMode() 指定分詞模式,不指定時默認就是 SegmentModes.search()

final String string = "這是一個伸手不見五指的黑夜。";

List<ISegmentResult> resultList = SegmentBs.newInstance()
       .segmentMode(SegmentModes.search())
       .segment(string);

Assert.assertEquals("[這是[0,2), 一個[2,4), 伸手不見五指[4,10), 的[10,11), 黑夜[11,13), 。[13,14)]", resultList.toString());

dict 模式

只依賴詞庫實現分詞,沒有 HMM 新詞預測功能。

final String string = "這是一個伸手不見五指的黑夜。";

List<ISegmentResult> resultList = SegmentBs.newInstance()
        .segmentMode(SegmentModes.dict())
        .segment(string);
Assert.assertEquals("[這[0,1), 是[1,2), 一個[2,4), 伸手不見五指[4,10), 的[10,11), 黑夜[11,13), 。[13,14)]", resultList.toString());

index 模式

這裏主要的區別就是會返回 伸手伸手不見 等其他詞組。

final String string = "這是一個伸手不見五指的黑夜。";

List<ISegmentResult> resultList = SegmentBs.newInstance()
        .segmentMode(SegmentModes.index())
        .segment(string);
Assert.assertEquals("[這[0,1), 是[1,2), 一個[2,4), 伸手[4,6), 伸手不見[4,8), 伸手不見五指[4,10), 的[10,11), 黑夜[11,13), 。[13,14)]", resultList.toString());

GreedyLength 模式

這裏使用貪心算法實現,準確率一般,性能較好。

final String string = "這是一個伸手不見五指的黑夜。";

List<ISegmentResult> resultList = SegmentBs.newInstance()
        .segmentMode(SegmentModes.greedyLength())
        .segment(string);
Assert.assertEquals("[這[0,1), 是[1,2), 一個[2,4), 伸手不見五指[4,10), 的[10,11), 黑夜[11,13), 。[13,14)]", resultList.toString());

格式化處理

格式化接口

可以通過 SegmentFormats 工具類獲取對應的格式化實現,在分詞時指定即可。

序號 方法 名稱 說明
1 defaults() 默認格式化 等價於小寫+半角處理。
2 lowerCase() 字符小寫格式化 英文字符處理時統一轉換爲小寫
3 halfWidth() 字符半角格式化 英文字符處理時統一轉換爲半角
4 chineseSimple() 中文簡體格式化 用於支持繁體中文分詞
5 none() 無格式化 無任何格式化處理
6 chains(formats) 格式化責任鏈 你可以針對上述的格式化自由組合,同時允許自定義格式化。

默認格式化

全角半角+英文大小寫格式化處理,默認開啓。

這裏的 爲全角大寫,默認會被轉換處理。

String text = "阿Q精神";
List<ISegmentResult> segmentResults = SegmentHelper.segment(text);

Assert.assertEquals("[阿Q[0,2), 精神[2,4)]", segmentResults.toString());

中文繁體分詞

無論是結巴分詞還是當前框架,默認對繁體中文的分詞都不友好。

默認分詞示例

顯然和簡體中文的分詞形式不同。

String text = "這是一個伸手不見五指的黑夜";

List<String> defaultWords = SegmentBs.newInstance()
        .segment(text, SegmentResultHandlers.word());
Assert.assertEquals("[這是, 一, 個, 伸手, 不見, 五指, 的, 黑夜]", defaultWords.toString());

啓用中文繁體分詞

指定分詞中文格式化,可以得到符合我們預期的分詞。

String text = "這是一個伸手不見五指的黑夜";

List<String> defaultWords = SegmentBs.newInstance()
        .segmentFormat(SegmentFormats.chineseSimple())
        .segment(text, SegmentResultHandlers.word());
Assert.assertEquals("[這是, 一個, 伸手不見五指, 的, 黑夜]", defaultWords.toString());

格式化責任鏈

格式化的形式可以有很多,我們可以根據自己的需求自由組合。

比如我們想同時啓用默認格式化+中文簡體格式化。

final String text = "阿Q,這是一個伸手不見五指的黑夜";

List<String> defaultWords = SegmentBs.newInstance()
        .segmentFormat(SegmentFormats.chains(SegmentFormats.defaults(),
                SegmentFormats.chineseSimple()))
        .segment(text, SegmentResultHandlers.word());
Assert.assertEquals("[阿Q, ,, 這是, 一個, 伸手不見五指, 的, 黑夜]", defaultWords.toString());

Benchmark 性能對比

性能對比

性能對比基於 jieba 1.0.2 版本,測試條件保持一致,保證二者都做好預熱,然後統一處理。

驗證下來,默認模式性能略優於 jieba 分詞,貪心模式是其性能 3 倍左右。

備註:

(1)默認模式和結巴 Search 模式一致。

後期考慮 HMM 也可以配置是否開啓,暫定爲默認開啓

(2)後期將引入多線程提升性能。

代碼參見 BenchmarkTest.java

性能對比圖

相同長文本,循環 1W 次耗時。(Less is Better)

在這裏插入圖片描述

後期 Road-Map

核心特性

  • HMM 詞性標註

  • HMM 實體標註

  • CRF 算法實現

  • N 元組算法實現

優化

  • 多線程的支持,性能優化

  • 雙數組 DFA 實現,降低內存消耗

輔助特性

  • 拓展自定義詞庫的特性

創作感謝

感謝 jieba 分詞提供的詞庫,以及 jieba-analysis 的相關實現。

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