Nodejs 中文分詞常用模塊用法分析
???
???
???????????????(生活只有在平淡無味的人看來才是空虛而平淡無味的。 —— 車爾尼雪夫斯基)
???
???
????????????????
中文分詞器
引用百度的說明 ~~
中文分詞就是將連續(xù)的字序列按照一定的規(guī)范重新組合成詞序列的過程。我們知道,在英文的行文中,單詞之間是以空格作為自然分界符的,而中文只是字、句和段能通過明顯的分界符來簡單劃界,唯獨詞沒有一個形式上的分界符,雖然英文也同樣存在短語的劃分問題,不過在詞這一層上,中文比之英文要復雜得多、困難得多
- 與英文為代表的拉丁語系語言相比,英文以空格作為天然的分隔符,而中文由于繼承自古代漢語的傳統(tǒng),詞語之間沒有分隔。 古代漢語中除了連綿詞和人名地名等,詞通常就是單個漢字,所以當時沒有分詞書寫的必要。而現(xiàn)代漢語中雙字或多字詞居多,一個字不再等同于一個詞。
- 在中文里,“詞”和“詞組”邊界模糊
現(xiàn)代漢語的基本表達單元雖然為“詞”,且以雙字或者多字詞居多,但由于人們認識水平的不同,對詞和短語的邊界很難去區(qū)分。
例如:“對隨地吐痰者給予處罰”,“隨地吐痰者”本身是一個詞還是一個短語,不同的人會有不同的標準,同樣的“海上”“酒廠”等等,即使是同一個人也可能做出不同判斷,如果漢語真的要分詞書寫,必然會出現(xiàn)混亂,難度很大。
中文分詞的方法其實不局限于中文應用,也被應用到英文處理,如手寫識別,單詞之間的空格就不很清楚,中文分詞方法可以幫助判別英文單詞的邊界
NodeJS中的中文分詞器
mmseg-node
https://github.com/zzdhidden/mmseg-node
一個基于 libmmseg 的 NodeJS 驅(qū)動
安裝流程
sudo apt-get install make gcc g++ automake libtool wget http://www.coreseek.cn/uploads/csft/3.2/mmseg-3.2.14.tar.gz sudo tar zxvf mmseg-3.2.14.tar.gz cd mmseg-3.2.14 ./bootstrap ./configure make && make install npm install mmseg
示例代碼
const mmseg = require("mmseg"); const q = mmseg.open('/usr/local/etc/'); console.log(q.segmentSync("我是中文分詞"));
從該包的下載量和issue的解決程度來看,不推薦使用,很容易碰到問題,而沒有得到有效的解決
nseg
https://github.com/mountain/nseg
Chih-Hao Tsai發(fā)明的MMSG,一種非常流行的中文分詞算法。許多實現(xiàn)可在不同的平臺上使用,包括Python,Java等
安裝流程
npm install nseg
示例代碼
const dict = require('../data/dict'), freq = require('../data/freq'), date = require('../lex/datetime'), sina = require('../lex/sina'); const opts = { dict: dict, freq: freq, lexers: [date, sina], }; const nseg = require('nseg').normal(opts); nseg('研究生源計劃', (result) => { console.log(result); });
同mmseg一樣,雖然在其他環(huán)境(比如Java,Python)有著較為成熟的應用和場景,但包的維護者重心可能不在nodejs上,所以還是不太建議使用…
node-segment
https://github.com/leizongmin/node-segment
以盤古分詞組件中的詞庫為基礎, 算法設計也部分參考了盤古分詞組件中的算法
具有以下特點
- 純JavaScript編寫,可以在任何支持ECMAScript5的引擎上執(zhí)行(需要稍微修改部分代碼)
- 基于詞性進行聯(lián)想識別
- 可使用JavaScript編寫自定義的分詞模塊
安裝流程
npm install segment --save
示例代碼
const Segment = require('segment'); const segment = new Segment(); segment.useDefault(); // 載入默認詞典 segment.loadDict('test.text'); // 載入字典,詳見dicts目錄,或者是自定義字典文件的絕對路徑 const text = "歡迎來到CSDN開發(fā)者博客論壇~~"; console.log(segment.doSegment(text, { stripPunctuation: true //去除標點符號 }));
定制化示例文件 .text
0x00100000代表16進制的詞性,但在返回時轉(zhuǎn)換為了十進制來標識
- 名詞 十進制:1048576 十六進制:0x00100000
- 動詞 十進制:4096 十六進制:0x00001000
- 機構團體 十進制:20 十六進制:0x00000020
- 時間 十進制:16384 十六進制:0x00004000
- 人名 十進制:128 十六進制:0x00000080
- 標點符號 十進制:2048 十六進制:0x00000800
- …
101代表了這個詞語的權重,在后續(xù)匹配詞語時,若匹配到近似詞,則以權重優(yōu)先匹配
架構師|0x00100000|101 程序員|0x00100000|101 運維工程師|0x00100000|101
該包使用純JavaScript編寫,github上的star量有1k,但通過查看issue,該包的維護著似乎已經(jīng)不太關心這個包了。但如果對性能要求沒那么高,且多關注內(nèi)存,使用得當?shù)脑挘€是可以考慮的
nodejieba
https://github.com/yanyiwu/nodejieba
NodeJieba是"結巴(jieba)"中文分詞的 Node.js 版本實現(xiàn), 由CppJieba提供底層分詞算法實現(xiàn), 是兼具高性能和易用性兩者的 Node.js 中文分詞組件
具有以下特點
- 詞典載入方式靈活,無需配置詞典路徑也可使用,需要定制自己的詞典路徑時也可靈活定制。
- 底層算法實現(xiàn)是C++,性能高效。
- 支持多種分詞算法,各種分詞算法見CppJieba的README.md介紹。
- 支持動態(tài)補充詞庫。
安裝流程
npm install nodejieba
但在安裝該包的時候可能會遇到npm下載權限的問題,因為底層使用了CppJieba,當使用sudo npm安裝時會提示權限不足
github issue問題
npm 官方解釋
解決辦法
- 單獨下載該包文件
sudo npm install nodejieba --unsafe-perm- 將該包緩存至全局
sudo npm install nodejieba -g --unsafe-perm- 配置npm
sudo su -
npm config set unsafe-perm- 切換至root身份運行
sudo su -
npm i
示例代碼
jieba詞性
- 獲取文本分詞標注結果
- n 名詞
- v 動詞
- nt 機構名稱
- t 時間
- nr 人名
…
示例自定義文件 .utf-8
客戶模板 n 客戶狀態(tài) n 客戶手機號 n 自定義 n 自定義字段 n 云通訊 n
const nodejieba = require("nodejieba"); jieba.load({ userDict: `self.utf-8`}); // 自定義詞典 const text = '紅掌撥清波'; const tags = ['n', 'v', 'nt', 'nr', 't']; const docuemnt = jieba.tag(text).filter((v) => tags.includes(v.tag)).map((v) => v.word); console.log(docuemnt);
目前NodeJS中性能最強的分詞器,且github上star量達到2k+,但開發(fā)者可能會遇到安裝的問題,因為底層依賴了python g++,如果版本過低或者過高,在安裝時會比較棘手。但如果能夠?qū)C器環(huán)境配置好的話,還是推薦使用該工具的,畢竟性能很強~~
性能對比
起初在使用nodejieba時,遇到了很多安裝的問題,比如npm權限,g++版本不匹配等等,期間也嘗試了node-segment,但因為服務對性能的要求較高,所以還是選擇克服機器配置的問題,使用了nodejieba
node-sgement和nodejieba的性能對比以及代碼分析
示例代碼
'use strict'; const express = require('express'); const app = express(); const bodyParser = require('body-parser'); const Segment = require('segment'); const segment = new Segment(); const nodejieba = require('nodejieba'); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); segment.useDefault(); segment.loadDict('test.text'); app.post('/', function (req, res, next) { const text = req.body.text; const type = req.body.type; const num = req.body.num; let str = ''; let array = []; for (let index = 0; index < num; index++) { str += text; } const tags = ['n', 'v', 'nt', 'nr', 't']; if (type === 'nodejieba') { array = nodejieba.tag(str).filter((v) => tags.includes(v.tag)).map((v) => v.word) } else { array = segment.doSegment(str).map((v) => v.w); } console.log(array); res.send(array); }); app.listen(3000);
示例請求文件 .text
{ "text": "生活只有在平淡無味的人看來才是空虛而平淡無味的。", "type": "segment", "num": 1 }
- 使用apach bench進行測試
測試方法:ab -n 1000 -p post.txt -c 200 -T application/json localhost:3000
- -n 請求總數(shù)
- -p post請求文件
- -c 并發(fā)量
- -T 請求格式
…
- 請求返回參數(shù)說明
- Document Path: /phpinfo.php #測試的頁面
- Document Length: 50797 bytes #頁面大小
- Concurrency Level: 200 #測試的并發(fā)數(shù)
- Time taken for tests: 11.846 seconds #整個測試持續(xù)的時間
- Complete requests: 1000 #完成的請求數(shù)量
- Failed requests: 0 #失敗的請求數(shù)量
- Write errors: 0
- Total transferred: 204586997 bytes #整個過程中的網(wǎng)絡傳輸量
- HTML transferred: 203479961 bytes #整個過程中的HTML內(nèi)容傳輸量
- Requests per second: 337.67 [#/sec] (mean) #最重要的指標之一,每秒處理請求數(shù)
- Time per request: 2961.449 [ms] (mean) #最重要的指標之二,平均等待時長
- Time per request: 2.961 [ms] (mean, across all concurrent requests) #每個連接請求實際運行時間的平均值
- Transfer rate: 16866.07 [Kbytes/sec] received #平均每秒網(wǎng)絡上的流量,可以幫助排除是否存在網(wǎng)絡流量過大導致響應時間延長的問題
- …
- 測試版本
- node v12
- segment v0.1.3
- nodejieba v2.4.1
- 測試結果
分詞類型 | 字符長度 | 請求總數(shù) | 并發(fā)條數(shù) | 平均每秒處理 | 平均等待時長(ms) | 是否包含分隔符(標點符號等) |
---|---|---|---|---|---|---|
segment | 60 | 1000 | 20 | 129.47 | 154.474 | 是 |
nodejieba | 60 | 1000 | 20 | 2662.83 | 7.511 | 是 |
segment | 60 | 1000 | 50 | 137.51 | 363.598 | 是 |
nodejieba | 60 | 1000 | 50 | 2660.41 | 17.222 | 是 |
segment | 60 | 1000 | 100 | 134.56 | 753.688 | 是 |
nodejieba | 60 | 1000 | 100 | 2774.89 | 36.038 | 是 |
segment | 60 | 1000 | 200 | 134.05 | 1491.937 | 是 |
nodejieba | 60 | 1000 | 200 | 2846.30 | 70.267 | 是 |
---- | ---- | — | – | – | – | – |
segment | 180 | 1000 | 200 | 67.40 | 2967.466 | 是 |
nodejieba | 180 | 1000 | 200 | 2390.53 | 83.664 | 是 |
segment | 360 | 1000 | 200 | 42.55 | 4700.340 | 是 |
nodejieba | 360 | 1000 | 200 | 1801.97 | 110.989 | 是 |
---- | ---- | — | – | – | – | – |
segment | 60 | 1000 | 200 | 88.26 | 2265.952 | 否 |
nodejieba | 60 | 1000 | 200 | 2860.95 | 69.907 | 否 |
segment | 60 | 200 | 50 | 101.18 | 494.188 | 否 |
nodejieba | 60 | 200 | 50 | 2688.39 | 18.598 | 否 |
segment | 120 | 200 | 50 | 1.73 | 28933.129 | 否 |
nodejieba | 120 | 200 | 50 | 1551.11 | 32.235 | 否 |
segment | 360 | 200 | 50 | error(棧溢出) | error(棧溢出) | 否 |
nodejieba | 360 | 200 | 50 | 1117.46 | 44.744 | 否 |
- 測試結果點評
- 從測試結果來看,nodejieba是node-sgement的20-30倍,這得益于nodejieba依賴的CppJieba,而node-sgement使用的純JS編寫,所以性能要遜色很多
- nodejieba和node-sgement在處理分詞的請求都比較穩(wěn)定,但面對長文本,即便性能很強的nodejieba依然會有性能瓶頸問題
- 面對不包含標點符號的文本,nodejieba和node-sgement性能都會有所影響
- node-sgement在對長文本(360個無標點符號的字符)進行分詞時會導致棧溢出
- 測試過程說明
查看node-sgement源碼發(fā)現(xiàn) 作者建議不要對較長且無任何標點符號的文本進行分詞的是發(fā)生在字典分詞階段
因為分詞是通過空格換行,通配符,特殊字符進行分離處理的(正是因為有了分離,所以每次分詞的文本不會很長)
而segment處理一串字符是通過循環(huán)每一個字符進行遞增匹配的,從而導致匹配的的次數(shù)是成倍的增長
…一開始想問原作者沒有特殊符號的長文導致時間倍增的具體原因,但是他可能很久沒有維護這個包了,就沒有提供有用的信息,然后只能看源碼去了解了
- node-segment 分詞邏輯
- 例子 你好我是七陌,循環(huán)次數(shù)是6+5+4+3+2+1 = (1+n)*n/2
- 第一輪對比
你
你好
你好我
…- 第二輪對比
好
好我
好我是
好我是七
…
一共分為12個分詞模塊
- 6個分詞模塊
url識別
通配符識別
標點符號
字母/數(shù)字識別
字典識別- 6個優(yōu)化模塊
中文姓
人名
中文人名
日期時間
詞典
郵箱地址
- 整體執(zhí)行邏輯
每次分詞會把紅框中的所有模塊文件執(zhí)行一遍
- 將(空格/換行)進行分組,用于后續(xù)循環(huán)匹配字符和詞性,然后進行六個階段的分詞計算
- url識別 地址匹配,https/http… 如果命中則進行轉(zhuǎn)換,并返回數(shù)據(jù)和詞性,每次從當前下標開始截取協(xié)議名稱字符數(shù),如果是http則是7個。將截取的字符進行對比,直到循環(huán)非url字符時結束。此時將這段字符的前后分成三段返回
- 通配符識別 通過循環(huán)遍歷將命中的通配符返回,并在上層匹配通配符詞性
- 標點符號 和通配符識別邏輯相同
- 字母/數(shù)字識別 將匹配的英文和數(shù)字字符使用ASCII轉(zhuǎn)換,如果命中ASCII區(qū)間,則將區(qū)間對應的16進制轉(zhuǎn)換為10進制作為詞性并分離返回,分離后的結構和請求協(xié)議的結構相同
- 字典識別 將字符串的字符循環(huán)和每一個字典的內(nèi)的字符進行包含性對比
第一次先從0下標開始查找,如果未命中則字符串截取數(shù)++,如果都沒有命中就將對比下標向前進一位,其中重復命中的會經(jīng)過一層優(yōu)化(去重打分),留下詞頻率最高,未識別詞最少的等等- 中文人名識別
- 人名優(yōu)化
…
總結
當然是推薦使用nodejieba了,畢竟是目前nodejs性能最強的分詞,但對機器環(huán)境依賴較為苛刻,所以建議使用docker將分詞服務單獨部署,進行環(huán)境依賴的隔離。
但如果更改機器配置對其他服務影響較大,且服務對性能要求沒那么高的話,可以考慮使用node-segment,然后加一些避免棧溢出的代碼,效果還是不錯的~~
PS:筆者比較喜歡實用nodejieba分詞工具,但是針對某些領域(如:區(qū)塊鏈相關)的專業(yè)術語會出現(xiàn)分詞錯誤的情況,這個時候就需要去尋找相關的詞庫并手動加載進來使用。
相關文章
用nodejs實現(xiàn)json和jsonp服務的方法
本篇文章主要介紹了用nodejs實現(xiàn)json和jsonp服務的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08express.js如何做mysql注入與node-mysql中防止SQL注入方法解析
這篇文章主要介紹了express.js如何做mysql注入與node-mysql中防止SQL注入方法,結合實例形式分析了express框架使用mysql數(shù)據(jù)庫過程中SQL注入的原理與防范技巧,需要的朋友可以參考下2023-05-05nodejs連接mongodb數(shù)據(jù)庫實現(xiàn)增刪改查
本篇文章主要結合了nodejs操作mongodb數(shù)據(jù)庫實現(xiàn)增刪改查,包括對數(shù)據(jù)庫的增加,刪除,查找和更新,有興趣的可以了解一下。2016-12-12