segmenter.go
//Go中文分词
package sego
import (
"bufio"
"fmt"
"log"
"math"
"os"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
const (
minTokenFrequency = 2 // 仅从字典文件中读取大于等于此频率的分词
)
// 分词器结构体
type Segmenter struct {
dict *Dictionary
}
// 该结构体用于记录Viterbi算法中某字元处的向前分词跳转信息
type jumper struct {
minDistance float32
token *Token
}
// 返回分词器使用的词典
func (seg *Segmenter) Dictionary() *Dictionary {
return seg.dict
}
// 从文件中载入词典
//
// 可以载入多个词典文件,文件名用","分隔,排在前面的词典优先载入分词,比如
// "用户词典.txt,通用词典.txt"
// 当一个分词既出现在用户词典也出现在通用词典中,则优先使用用户词典。
//
// 词典的格式为(每个分词一行):
// 分词文本 频率 词性
func (seg *Segmenter) LoadDictionary(files string) {
seg.dict = NewDictionary()
for _, file := range strings.Split(files, ",") {
log.Printf("载入sego词典 %s", file)
dictFile, err := os.Open(file)
defer dictFile.Close()
if err != nil {
log.Fatalf("无法载入字典文件 \"%s\" \n", file)
}
reader := bufio.NewReader(dictFile)
var text string
var freqText string
var frequency int
var pos string
// 逐行读入分词
for {
size, _ := fmt.Fscanln(reader, &text, &freqText, &pos)
if size == 0 {
// 文件结束
break
} else if size < 2 {
// 无效行
continue
} else if size == 2 {
// 没有词性标注时设为空字符串
pos = ""
}
// 解析词频
var err error
frequency, err = strconv.Atoi(freqText)
if err != nil {
continue
}
// 过滤频率太小的词
if frequency < minTokenFrequency {
continue
}
// 将分词添加到字典中
words := splitTextToWords([]byte(text))
token := Token{text: words, frequency: frequency, pos: pos}
seg.dict.addToken(token)
}
}
// 计算每个分词的路径值,路径值含义见Token结构体的注释
logTotalFrequency := float32(math.Log2(float64(seg.dict.totalFrequency)))
for i := range seg.dict.tokens {
token := &seg.dict.tokens[i]
token.distance = logTotalFrequency - float32(math.Log2(float64(token.frequency)))
}
// 对每个分词进行细致划分,用于搜索引擎模式,该模式用法见Token结构体的注释。
for i := range seg.dict.tokens {
token := &seg.dict.tokens[i]
segments := seg.segmentWords(token.text, true)
// 计算需要添加的子分词数目
numTokensToAdd := 0
for iToken := 0; iToken < len(segments); iToken++ {
if len(segments[iToken].token.text) > 1 {
// 略去字元长度为一的分词
// TODO: 这值得进一步推敲,特别是当字典中有英文复合词的时候
numTokensToAdd++
}
}
token.segments = make([]*Segment, numTokensToAdd)
// 添加子分词
iSegmentsToAdd := 0
for iToken := 0; iToken < len(segments); iToken++ {
if len(segments[iToken].token.text) > 1 {
token.segments[iSegmentsToAdd] = &segments[iToken]
iSegmentsToAdd++
}
}
}
log.Println("sego词典载入完毕")
}
// 对文本分词
//
// 输入参数:
// bytes UTF8文本的字节数组
//
// 输出:
// []Segment 划分的分词
func (seg *Segmenter) Segment(bytes []byte) []Segment {
return seg.internalSegment(bytes, false)
}
func (seg *Segmenter) internalSegment(bytes []byte, searchMode bool) []Segment {
// 处理特殊情况
if len(bytes) == 0 {
return []Segment{}
}
// 划分字元
text := splitTextToWords(bytes)
return seg.segmentWords(text, searchMode)
}
func (seg *Segmenter) segmentWords(text []Text, searchMode bool) []Segment {
// 搜索模式下该分词已无继续划分可能的情况
if searchMode && len(text) == 1 {
return []Segment{}
}
// jumpers定义了每个字元处的向前跳转信息,包括这个跳转对应的分词,
// 以及从文本段开始到该字元的最短路径值
jumpers := make([]jumper, len(text))
tokens := make([]*Token, seg.dict.maxTokenLength)
for current := 0; current < len(text); current++ {
// 找到前一个字元处的最短路径,以便计算后续路径值
var baseDistance float32
if current == 0 {
// 当本字元在文本首部时,基础距离应该是零
baseDistance = 0
} else {
baseDistance = jumpers[current-1].minDistance
}
// 寻找所有以当前字元开头的分词
numTokens := seg.dict.lookupTokens(
text[current:minInt(current+seg.dict.maxTokenLength, len(text))], tokens)
// 对所有可能的分词,更新分词结束字元处的跳转信息
for iToken := 0; iToken < numTokens; iToken++ {
location := current + len(tokens[iToken].text) - 1
if !searchMode || current != 0 || location != len(text)-1 {
updateJumper(&jumpers[location], baseDistance, tokens[iToken])
}
}
// 当前字元没有对应分词时补加一个伪分词
if numTokens == 0 || len(tokens[0].text) > 1 {
updateJumper(&jumpers[current], baseDistance,
&Token{text: []Text{text[current]}, frequency: 1, distance: 32, pos: "x"})
}
}
// 从后向前扫描第一遍得到需要添加的分词数目
numSeg := 0
for index := len(text) - 1; index >= 0; {
location := index - len(jumpers[index].token.text) + 1
numSeg++
index = location - 1
}
// 从后向前扫描第二遍添加分词到最终结果
outputSegments := make([]Segment, numSeg)
for index := len(text) - 1; index >= 0; {
location := index - len(jumpers[index].token.text) + 1
numSeg--
outputSegments[numSeg].token = jumpers[index].token
index = location - 1
}
// 计算各个分词的字节位置
bytePosition := 0
for iSeg := 0; iSeg < len(outputSegments); iSeg++ {
outputSegments[iSeg].start = bytePosition
bytePosition += textSliceByteLength(outputSegments[iSeg].token.text)
outputSegments[iSeg].end = bytePosition
}
return outputSegments
}
// 更新跳转信息:
// 1. 当该位置从未被访问过时(jumper.minDistance为零的情况),或者
// 2. 当该位置的当前最短路径大于新的最短路径时
// 将当前位置的最短路径值更新为baseDistance加上新分词的概率
func updateJumper(jumper *jumper, baseDistance float32, token *Token) {
newDistance := baseDistance + token.distance
if jumper.minDistance == 0 || jumper.minDistance > newDistance {
jumper.minDistance = newDistance
jumper.token = token
}
}
// 取两整数较小值
func minInt(a, b int) int {
if a > b {
return b
}
return a
}
// 取两整数较大值
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
// 将文本划分成字元
func splitTextToWords(text Text) []Text {
output := make([]Text, 0, len(text)/3)
current := 0
inAlphanumeric := true
alphanumericStart := 0
for current < len(text) {
r, size := utf8.DecodeRune(text[current:])
if size <= 2 && (unicode.IsLetter(r) || unicode.IsNumber(r)) {
// 当前是拉丁字母或数字(非中日韩文字)
if !inAlphanumeric {
alphanumericStart = current
inAlphanumeric = true
}
} else {
if inAlphanumeric {
inAlphanumeric = false
if current != 0 {
output = append(output, toLower(text[alphanumericStart:current]))
}
}
output = append(output, text[current:current+size])
}
current += size
}
// 处理最后一个字元是英文的情况
if inAlphanumeric {
if current != 0 {
output = append(output, toLower(text[alphanumericStart:current]))
}
}
return output
}
// 将英文词转化为小写
func toLower(text []byte) []byte {
output := make([]byte, len(text))
for i, t := range text {
if t >= 'A' && t <= 'Z' {
output[i] = t - 'A' + 'a'
} else {
output[i] = t
}
}
return output
}
segmenter.go的更多相关文章
- Windows平台下使用ffmpeg和segmenter实现m3u8直播点播
1.安装windows media service 实现 流媒体服务器功能 2.windows media编码器 实现 直播推流 3.使用 vlc 将 mms://127.0.0.1/live ...
- [转]Iphone m3u8 segmenter from ffmpeg for video streaming
源地址:http://lukasz.cepowski.com/devlog/30,iphone-m3u8-segmenter-from-ffmpeg-for-video-streaming Recen ...
- Stanford Word Segmenter使用
1,下载 Stanford Word Segmenter软件包: Download Stanford Word Segmenter version 2014-06-16 2,在eclipse上建立一个 ...
- Configure the Stanford segmenter for NLTK
>>> from nltk.tokenize.stanford_segmenter import StanfordSegmenter >>> segmenter = ...
- Stanford Word Segmenter的特定领域训练
有没有人自己训练过Stanford Word Segmenter分词器,因为我想做特定领域的分词,但在使用Stanford Word Segmenter分词的时候发现对于我想做的领域的一些词分词效果并 ...
- 【NLP】干货!Python NLTK结合stanford NLP工具包进行文本处理
干货!详述Python NLTK下如何使用stanford NLP工具包 作者:白宁超 2016年11月6日19:28:43 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的 ...
- Python自然语言处理工具小结
Python自然语言处理工具小结 作者:白宁超 2016年11月21日21:45:26 目录 [Python NLP]干货!详述Python NLTK下如何使用stanford NLP工具包(1) [ ...
- 【NLP】Python NLTK处理原始文本
Python NLTK 处理原始文本 作者:白宁超 2016年11月8日22:45:44 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集的大量公开 ...
- 【NLP】Python NLTK获取文本语料和词汇资源
Python NLTK 获取文本语料和词汇资源 作者:白宁超 2016年11月7日13:15:24 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集 ...
随机推荐
- 八、Join 连接查询
文档目录 开发中...
- 如何用Python网络爬虫爬取网易云音乐歌曲
今天小编带大家一起来利用Python爬取网易云音乐,分分钟将网站上的音乐down到本地. 跟着小编运行过代码的筒子们将网易云歌词抓取下来已经不再话下了,在抓取歌词的时候在函数中传入了歌手ID和歌曲名两 ...
- C#逻辑面试题汇总【不断更新中】
(1)产生本月的月历,参考样式: 1 2 3 4 5 6 SU MO TU WE TH FR SA 01 02 03 04 05 06 07 08 09 10 11 12 13 14 ...
- Linux的pwd命令详解
在Linux层次结构中,用户可以在被授权的任意目录下利用mkdir命令创建新目录,也可以利用cd命令从一个目录转换到另一个目录.然而,没有提示符来告知用户目前处于哪一个目录中.想要知道当前所处的目录, ...
- 解读2017之Service Mesh:群雄逐鹿烽烟起
https://mp.weixin.qq.com/s/ur3PmLZ6VjP5L5FatIYYmg 在过去的2016年和2017年,微服务技术得以迅猛普及,和容器技术一起成为这两年中最吸引眼球的技术热 ...
- EF CodeFirst 数据库初始化策略
最近用EF做了几个小东西,了解简单使用后有了深入研究的兴趣,所以想系统的研究一下EF CodeFist的几个要点.下面简单列一下目录 1.1 目录 数据库初始化策略和数据迁移Migration的简单介 ...
- bootstrap-table+x-editable入门
Bootstrap-table 快速入门bootstrap-table----我的表单不可能这么帅. Table of contents Quick start Why use it What's i ...
- hive 分组排序,topN
hive 分组排序,topN 语法格式:row_number() OVER (partition by COL1 order by COL2 desc ) rankpartition by:类似hiv ...
- Fiddler证书安装(查看HTTPS)
现在很多带有比较重要信息的接口都使用了安全性更高的HTTPS,而Fiddler默认是抓取HTTP类型的接口,要想查看HTTPS类型接口就需要安装fiddler证书. fiddler安装教程可参考: ...
- 易用性测试、本地化测试、部署测试、无障碍测试、回归测试、冒烟测试、A/B测试
1.易用性定义: 易用性测试是指测试用户使用软件时是否感觉方便,是否能保证用户使用的测试类型2.本地化测试: ·定义:针对软件的本地化版本实施的针对性测试 ·测试内容: (1)语言,书写习惯 (2)时 ...