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) | 格式化責任鏈 | 你可以針對上述的格式化自由組合,同時允許自定義格式化。 |
默認格式化
全角半角+英文大小寫格式化處理,默認開啟。
這里的 Q
為全角大寫,默認會被轉換處理。
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 的相關實現。