Go中文分词

sego

Go中文分词

词典用双数组trie(Double-Array Trie)实现, 分词器算法为基于词频的最短路径加动态规划。

支持普通和搜索引擎两种分词模式,支持用户词典、词性标注,可运行JSON RPC服务

分词速度单线程9MB/s,goroutines并发42MB/s(8核Macbook Pro)。

安装/更新

go get -u github.com/huichen/sego

使用

package main

import (
	"fmt"
	"github.com/huichen/sego"
)

func main() {
	// 载入词典
	var segmenter sego.Segmenter
	segmenter.LoadDictionary("github.com/huichen/sego/data/dictionary.txt")

	// 分词
	text := []byte("中华人民共和国中央人民政府")
	segments := segmenter.Segment(text)
  
	// 处理分词结果
	// 支持普通模式和搜索模式两种分词,见代码中SegmentsToString函数的注释。
	fmt.Println(sego.SegmentsToString(segments, false)) 
}
Comments
  • 分词速度提升至原来的 3.5 倍

    分词速度提升至原来的 3.5 倍

    Hi, 我发现几个问题

    • 并行分词的部分有bug,程序可能会在所有分词线程 (goroutine) 完成之前退出。 我加入了一个 done channel,保证程序在所有分词完成后才退出。
    • 传递 linechannel 开得太小,导致分词线程完成一个循环后有可能需要等待 channel 的输入。具体表现在 cpu 不能达到 100%. 适当增加管道的大小可以显著提高分词速度。我这里测试是直接分配原来的10倍大小比较合适。

    现在 goroutines.go 在我这里测试快了 29% 左右。 之前,

    2015/09/11 23:32:08 载入sego词典 ../data/dictionary.txt
    2015/09/11 23:32:12 sego词典载入完毕
    2015/09/11 23:32:12 开始分词
    2015/09/11 23:32:14 分词花费时间 1.800412677s
    2015/09/11 23:32:14 分词速度 7.044359 MB/s
    

    现在,

    2015/09/11 23:32:39 载入sego词典 ../data/dictionary.txt
    2015/09/11 23:32:42 sego词典载入完毕
    2015/09/11 23:32:43 开始分词
    2015/09/11 23:32:44 分词花费时间 1.39470814s
    2015/09/11 23:32:44 分词速度 9.093481 MB/s
    
  • 关于splitTextToWords的多语言支持

    关于splitTextToWords的多语言支持

    目前segmenter.go中的splitTextToWords函数,将会把所有non-english语言,分解为最小单位。

    除了CJK中日韩等东亚语言,其它国家的语言都还是类似英语,属于字母型语言,利用unicode包中的IsLetter、IsNumber函数,可以很方便的处理。因此,建议将 _, size := utf8.DecodeRune(text[current:]) if size == 1 && (text[current] >= 'a' && text[current] <= 'z') || (text[current] >= 'A' && text[current] <= 'Z') || (text[current] >= '0' && text[current] <= '9') {

    改为 r, size := utf8.DecodeRune(text[current:]) if unicode.IsLetter(r) || unicode.IsNumber(r) {

    这样sego基本上可以用于所有的语言。

  • 减少30%的内存占用

    减少30%的内存占用

    Hi,感谢开源这个分词库。 在使用过程中发现内存占用比较大,看了一下是 splitTextToWords 里面的 slice 开得太大了。 改了之后内存占用少了30%多。

    对比: 之前

    (pprof) top
    280.15MB of 281.15MB total (99.64%)
    Dropped 13 nodes (cum <= 1.41MB)
    Showing top 10 nodes out of 15 (cum >= 4MB)
          flat  flat%   sum%        cum   cum%
      131.53MB 46.78% 46.78%   131.53MB 46.78%  github.com/adamzy/sego.splitTextToWords
          65MB 23.12% 69.90%   276.15MB 98.22%  github.com/adamzy/sego.(*Segmenter).LoadDictionary
       54.57MB 19.41% 89.31%    54.57MB 19.41%  github.com/adamzy/sego.upsert
       15.50MB  5.51% 94.83%    15.50MB  5.51%  github.com/adamzy/sego.(*Segmenter).segmentWords
           8MB  2.85% 97.67%        8MB  2.85%  fmt.(*ss).convertString
        5.55MB  1.97% 99.64%    60.11MB 21.38%  github.com/adamzy/sego.(*Dictionary).addToken
             0     0% 99.64%        8MB  2.85%  fmt.(*ss).doScan
             0     0% 99.64%        8MB  2.85%  fmt.(*ss).scanOne
             0     0% 99.64%        8MB  2.85%  fmt.Fscanln
             0     0% 99.64%        4MB  1.42%  github.com/adamzy/sego.(*Segmenter).Segment
    

    现在

    (pprof) top
    183.56MB of 184.06MB total (99.73%)
    Dropped 5 nodes (cum <= 0.92MB)
    Showing top 10 nodes out of 12 (cum >= 183.56MB)
          flat  flat%   sum%        cum   cum%
          61MB 33.14% 33.14%   183.56MB 99.73%  github.com/adamzy/sego.(*Segmenter).LoadDictionary
       52.51MB 28.53% 61.67%    52.51MB 28.53%  github.com/adamzy/sego.upsert
       42.50MB 23.09% 84.76%    42.50MB 23.09%  github.com/adamzy/sego.splitTextToWords
          14MB  7.61% 92.37%       14MB  7.61%  github.com/adamzy/sego.(*Segmenter).segmentWords
           8MB  4.35% 96.71%        8MB  4.35%  fmt.(*ss).convertString
        5.55MB  3.01% 99.73%    58.05MB 31.54%  github.com/adamzy/sego.(*Dictionary).addToken
             0     0% 99.73%        8MB  4.35%  fmt.(*ss).doScan
             0     0% 99.73%        8MB  4.35%  fmt.(*ss).scanOne
             0     0% 99.73%        8MB  4.35%  fmt.Fscanln
             0     0% 99.73%   183.56MB 99.73%  main.main
    
  • segmenter.go第185行

    segmenter.go第185行

        // 当前字元没有对应分词时补加一个伪分词
        if numTokens == 0 || len(tokens[0].text) > 1 {
            updateJumper(&jumpers[current], baseDistance,
                &Token{text: []Text{text[current]}, frequency: 1, distance: 32, pos: "x"})
        }
    

    感觉注释和代码不一致啊?

  • Support output result to a string slice.

    Support output result to a string slice.

    Python 的中文分词模块 (fxsjy/jieba, pymmesg) 可以用函数方便的把分词结果存在一个 list 中返回,我想在sego中也添加这样的函数,所以我在 SegmentsToString 的基础上增加了 SegmentsToSlice 来返回一个保存分词结果的 string slice 。

  • added function to load dictionary from []byte instead of files

    added function to load dictionary from []byte instead of files

    Hello,

    I wanted to be able to use your library in my project and have sego load from a []byte in stead of a file, so i added a function to do that. I am sending you this pull request so my code can hopefully be useful to others as well

    regards, Bart

  • 怎样可以不输出日志?

    怎样可以不输出日志?

    怎样可以不输出类似下面这样的日志?

    2017/07/08 19:45:34 载入sego词典 dictionary.txt
    2017/07/08 19:45:37 sego词典载入完毕
    

    segmenter.go里,下面的代码是写死的,能否提供一个选项不输出日志?

    log.Printf("载入sego词典 %s", file)
    log.Fatalf("无法载入字典文件 \"%s\" \n", file)
    log.Println("sego词典载入完毕")
    

    『无法载入字典文件』,这个能让LoadDictionary()返回一个error吗?

  • 分词时应该把原词加入结果

    分词时应该把原词加入结果

    比如demo中的词:中国互联网历史上最大的一笔并购案 分词后的结果:中国/ns 互联/v 互联网/n 历史/n 上/f 最大/a 的/uj 一笔/m 并购/v 并购案/n 应该把原词加入到结果:中国/ns 互联/v 互联网/n 历史/n 上/f 最大/a 的/uj 一笔/m 并购/v 并购案/n 中国互联网历史上最大的一笔并购案

  • 请问如何自定义词典,有什么规律吗?

    请问如何自定义词典,有什么规律吗?

    我想自定义词典,看词典的内容,第二列,三列,是什么意思呢?

    AA制 3 n
    AB型 3 n
    AT&T 3 nz
    ...
    二里頭 277 nrt
    肾上腺 278 l
    ...
    純天然 28 b
    纯天然 28 b
    挨个儿 28 d
    ...
    
    

    我该如何自定义词典呢? 比如:多少本书,我想让 “多少本”,是其中的一个词。

  • 错误:当词典只有一个关键词并且该关键词在句首时,无法得到该分词

    错误:当词典只有一个关键词并且该关键词在句首时,无法得到该分词

    字典文件内容:

    张三 3 n
    

    程序:

    	var sgr sego.Segmenter
    	sgr.LoadDictionary("main.dic")
    	var words []string
    	for _, sg := range sgr.Segment([]byte("张三,你好啊")) {
    		token := sg.Token()
    		words = append(words, fmt.Sprintf("%s/%s", token.Text(), token.Pos()))
    	}
    	fmt.Println(strings.Join(words, " "))
    //      张/x 三/x ,/x 你/x 好/x 啊/x
    	
    	words = words[:0]
    	for _, sg := range sgr.Segment([]byte("你好啊,张三")) {
    		token := sg.Token()
    		words = append(words, fmt.Sprintf("%s/%s", token.Text(), token.Pos()))
    	}
    	fmt.Println(strings.Join(words, " "))
    //      你/x 好/x 啊/x ,/x 张三/n