Segment 是基於結巴分詞詞庫實現的更加靈活,高性能的 java 分詞實現。java
變動日誌git
分詞是作 NLP 相關工做,很是基礎的一項功能。github
jieba-analysis 做爲一款很是受歡迎的分詞實現,我的實現的 opencc4j 以前一直使用其做爲分詞。算法
可是隨着對分詞的瞭解,發現結巴分詞對於一些配置上不夠靈活。api
(1)有不少功能沒法指定關閉,好比 HMM 對於繁簡體轉換是無用的,由於繁體詞是固定的,不須要預測。數組
(2)最新版本的詞性等功能好像也被移除了,可是這些都是我的很是須要的。性能優化
(3)對於中文繁體分詞支持不友好。多線程
因此從新實現了一遍,但願實現一套更加靈活,更多特性的分詞框架。框架
並且 jieba-analysis 的更新彷佛停滯了,我的的實現方式差別較大,因此創建了全新的項目。maven
面向用戶的極簡靜態 api 設計
面向開發者 fluent-api 設計,讓配置更加優雅靈活
詳細的中文代碼註釋,便於源碼閱讀
基於 DFA 實現的高性能分詞
基於 HMM 的新詞預測
支持不一樣的分詞模式
支持全角半角/英文大小寫/中文繁簡體格式處理
jdk1.7+
maven 3.x+
<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
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());
只依賴詞庫實現分詞,沒有 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());
這裏主要的區別就是會返回 伸手
、伸手不見
等其餘詞組。
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());
這裏使用貪心算法實現,準確率通常,性能較好。
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());
性能對比基於 jieba 1.0.2 版本,測試條件保持一致,保證兩者都作好預熱,而後統一處理。
驗證下來,默認模式性能略優於 jieba 分詞,貪心模式是其性能 3 倍左右。
備註:
(1)默認模式和結巴 Search 模式一致。
後期考慮 HMM 也能夠配置是否開啓,暫定爲默認開啓
(2)後期將引入多線程提高性能。
代碼參見 BenchmarkTest.java
相同長文本,循環 1W 次耗時。(Less is Better)
HMM 詞性標註
HMM 實體標註
CRF 算法實現
多線程的支持,性能優化
感謝 jieba 分詞提供的詞庫,以及 jieba-analysis 的相關實現。