本模塊基於node-segment 魔改,增加了electron、瀏覽器支持,並準備針對electron 多線程運行環境進行優化。
之所以要花時間魔改,是因為segment和nodejieba雖然在node 環境下很好用,但根本無法在瀏覽器和electron 環境下運行。我把代碼重構為ES2015,並用babel 插件內聯了字典文件,全部載入的話大小是3.8M,但如果有些字典你並不需要,字典和模塊是支持tree shaking 的(請使用ESM 模塊)。
< script src =" https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/segmentit.min.js " /> npm i segmentit import { Segment , useDefault } from 'segmentit' ;
const segmentit = useDefault ( new Segment ( ) ) ;
const result = segmentit . doSegment ( '工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作。' ) ;
console . log ( result ) ;對於runkit 環境:
const { Segment , useDefault } = require ( 'segmentit' ) ;
const segmentit = useDefault ( new Segment ( ) ) ;
const result = segmentit . doSegment ( '工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作。' ) ;
console . log ( result ) ;在Runkit 上免費試用
瀏覽器直接使用例:
首先請引用“https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/segmentit.js”
const segmentit = Segmentit . useDefault ( new Segmentit . Segment ( ) ) ;
const result = segmentit . doSegment ( '工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作。' ) ;
console . log ( result ) ; (其實就是把所有調用,初始化什麼的全都加上Segmentit.就可以了)
結巴分詞風格的詞類標註:
// import Segment, { useDefault, cnPOSTag, enPOSTag } from 'segmentit';
const { Segment , useDefault , cnPOSTag , enPOSTag } = require ( 'segmentit' ) ;
const segmentit = useDefault ( new Segment ( ) ) ;
console . log ( segmentit . doSegment ( '一人得道,鸡犬升天' ) . map ( i => ` ${ i . w } < ${ cnPOSTag ( i . p ) } > < ${ enPOSTag ( i . p ) } >` ) )
// ↑ ["一人得道 <习语,数词 数语素> <l,m>", ", <标点符号> <w>", "鸡犬升天 <成语> <i>"] useDefault 的具體實現是這樣的:
// useDefault
import { Segment , modules , dicts , synonyms , stopwords } from 'segmentit' ;
const segmentit = new Segment ( ) ;
segmentit . use ( modules ) ;
segmentit . loadDict ( dicts ) ;
segmentit . loadSynonymDict ( synonyms ) ;
segmentit . loadStopwordDict ( stopwords ) ;因此你實際上可以import 所需的那部分字典和模塊,然後一個個如下載入。沒有import 的那些字典和模塊應該會被webpack 的tree shaking 去掉。你也可以這樣載入自己定義的字典文件,只需要主要loadDict 的函數簽名是(dicts: string | string[]): Segment 。
// load custom module and dicts
import {
Segment ,
ChsNameTokenizer ,
DictOptimizer ,
EmailOptimizer ,
PunctuationTokenizer ,
URLTokenizer ,
ChsNameOptimizer ,
DatetimeOptimizer ,
DictTokenizer ,
ForeignTokenizer ,
SingleTokenizer ,
WildcardTokenizer ,
pangu ,
panguExtend1 ,
panguExtend2 ,
names ,
wildcard ,
synonym ,
stopword ,
} from 'segmentit' ;
const segmentit = new Segment ( ) ;
// load them one by one, or by array
segmentit . use ( ChsNameTokenizer ) ;
segmentit . loadDict ( pangu ) ;
segmentit . loadDict ( [ panguExtend1 , panguExtend2 ] ) ;
segmentit . loadSynonymDict ( synonym ) ;
segmentit . loadStopwordDict ( stopword ) ;盤古的詞典比較復古了,像「軟萌蘿莉」這種詞都是沒有的,請有能力的朋友PR 一下自己的詞庫。
Tokenizer 是分詞時要經過的一個個中間件,類似於Redux 的MiddleWare,它的split 函數接受分詞分到一半的token 數組,返回一個同樣格式的token 數組(這也就是不要對太長的文本分詞的原因,不然這個數組會巨爆大)。
例子如下:
// @flow
import { Tokenizer } from 'segmentit' ;
import type { SegmentToken , TokenStartPosition } from 'segmentit' ;
export default class ChsNameTokenizer extends Tokenizer {
split ( words : Array < SegmentToken > ) : Array < SegmentToken > {
// 可以获取到 this.segment 里的各种信息
const POSTAG = this . segment . POSTAG ;
const TABLE = this . segment . getDict ( 'TABLE' ) ;
// ...
}Optimizer 是在分詞結束後,發現有些難以利用字典處理的情況,卻可以用啟發式規則處理時,可以放這些啟發式規則的地方,它的doOptimize 函數同樣接收一個token 數組,返回一個同樣格式的token 數組。
除了token 數組以外,你還可以自定義餘下的參數,比如在下面的例子裡,我們會遞歸調用自己一次,通過第二個參數判斷遞歸深度:
// @flow
import { Optimizer } from './BaseModule' ;
import type { SegmentToken } from './type' ;
export default class DictOptimizer extends Optimizer {
doOptimize ( words : Array < SegmentToken > , isNotFirst : boolean ) : Array < SegmentToken > {
// 可以获取到 this.segment 里的各种信息
const POSTAG = this . segment . POSTAG ;
const TABLE = this . segment . getDict ( 'TABLE' ) ;
// ...
// 针对组合数字后无法识别新组合的数字问题,需要重新扫描一次
return isNotFirst === true ? words : this . doOptimize ( words , true ) ;
}例如目前各種分詞工具都沒法把「一條紅色內褲」中的紅色標對詞性,但在segmentit 裡我加了個簡單的AdjectiveOptimizer 來處理它:
// @flow
// https://github.com/linonetwo/segmentit/blob/master/src/module/AdjectiveOptimizer.js
import { Optimizer } from './BaseModule' ;
import type { SegmentToken } from './type' ;
import { colors } from './COLORS' ;
// 把一些错认为名词的词标注为形容词,或者对名词作定语的情况
export default class AdjectiveOptimizer extends Optimizer {
doOptimize ( words : Array < SegmentToken > ) : Array < SegmentToken > {
const { POSTAG } = this . segment ;
let index = 0 ;
while ( index < words . length ) {
const word = words [ index ] ;
const nextword = words [ index + 1 ] ;
if ( nextword ) {
// 对于<颜色>+<的>,直接判断颜色是形容词(字典里颜色都是名词)
if ( nextword . p === POSTAG . D_U && colors . includes ( word . w ) ) {
word . p = POSTAG . D_A ;
}
// 如果是连续的两个名词,前一个是颜色,那这个颜色也是形容词
if ( word . p === POSTAG . D_N && nextword . p === POSTAG . D_N && colors . includes ( word . w ) ) {
word . p = POSTAG . D_A ;
}
}
// 移到下一个单词
index += 1 ;
}
return words ;
}
} MIT LICENSED