在 Python 和 Golang 中,都有一个中文分词库 Jieba , 是由一个作者发布的,本文尝试对比两种语言中这个库的准确度或者准确性。
准备
使用分词库 Jieba 对长文本进行词频分析,大致流程如下:
从文件读取文本-->进行中文分词-->过滤掉utf-8编码时长度为1的词语,过滤掉不需要的词语,合并同义词(比如在《红楼梦》中,贾母和老太太是严格的同义词,宝玉和二爷是严格的同义词,凤姐和王熙凤是严格的同义词)-->生成词频字典-->把字典按照元素的值(也就是词语出现的频度)进行DESC排序-->输出指定数量(也就是若干个)出现次数比较多的词语。
具体的样本获取的话,可以在网络上搜索 "红楼梦 txt" , 找个合适的地方,下载来,然后用 NotePad++ 把它格式调整为 UTF-8 无DOM 头。
Python
使用 pip3 安装 jieba
1 | pip3 install jieba |
然后写一个分析红楼梦中人物出现次数的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | # -*- coding: utf-8 -*- # @Author: suifengtec # @Date: 2018-09-13 12:53:18 # @Last Modified by: suifengtec # @Last Modified time: 2018-09-13 17:33:20 # # # 使用 jieba 对文本进行简单的词语统计 # # 使用本脚本 # # python jb4.py # # import sys import jieba def fc(filePath, excludes, showCount, mergeData): file = open(filePath, "r", encoding='utf-8') txt = file.read() words = jieba.lcut(txt) counts = {} for word in words: if len(word) == 1: continue elif word in mergeData: rword = mergeData[word] else: rword = word counts[rword] = counts.get(rword, 0)+1 for word in excludes: if word in counts: del(counts[word]) items = list(counts.items()) items.sort(key=lambda x: x[1], reverse=True) for i in range(showCount): word, count = items[i] print("{0:<10}{1:>5}".format(word, count)) file.close() def main(argv): # 文件的相对路径 filePath = "data/红楼梦1.txt" # 排除的词语 excludes = {"什么", "一个", "我们", "那里", "你们", "如今", "说道", "知道", "起来", "姑娘", "这里", "出来", "他们", "众人", "自己", "一面", "只见", "怎么", "两个", "没有", "不是", "不知", "这个", "听见", "这样", "进来", "咱们", "告诉", "就是", "东西", "回来", "只是", "大家", "只得", "这些", "不敢", "出去", "所以", "不过", "的话", "不好", "丫头", "姐姐", "一时", "不能", "过来", "心里", "如此", "今日", "银子", "几个", "答应", "二人", "还有", "只管", "这么", "说话", "一回", "那边", "这话", "外头", "打发", "自然", "今儿", "罢了", "屋里", "那些", "听说", "小丫头", "如何", "问道", "看见", "妹妹", "人家", "不用", "媳妇", "原来", "一声", "一句", "家里", "不得", "到底", "这会子", "进去", "姊妹", "别人", "回去", "明儿", "丫鬟", "过去", "连忙", "心中", "方才", "还是", "婆子", "里头", "小厮", "哥哥", "不成", "身上", "只有", "有人", "起身", "于是", "一件", "这是", "果然", "明白", "那个", "一日", "已经", "怎么样", "跟前", "谁知", "有些", "瞧瞧", "越发", "难道", "不肯", "不必", "只怕", "主意", "不如", "喜欢", "好些", "吩咐", "况且", "便是", "夫人", "笑道", "老爷"} # 显示多少个词语 showCount = 23 # 字典:合并含义相同用的词语 mergeData = {} mergeData['老太太'] = "贾母" mergeData['太太'] = "王夫人" mergeData['奶奶'] = "王夫人" mergeData['凤姐儿'] = "凤姐" mergeData['老爷'] = "老爷" mergeData['二爷'] = "宝玉" mergeData['黛玉'] = "林黛玉" fc(filePath, excludes, showCount, mergeData) if __name__ == "__main__": main(sys.argv) |
注意文件的相对位置,由于是测试,所以没有添加异常处理。
Python 得出的相应结果如下(人物以及对应的出现次数,不是人物的词语,请肉眼忽略)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 宝玉 4147 王夫人 2608 贾母 2194 凤姐 1570 林黛玉 840 贾琏 670 平儿 588 袭人 585 宝钗 567 薛姨妈 453 探春 432 鸳鸯 425 贾政 350 晴雯 336 湘云 324 刘姥姥 293 邢夫人 284 贾珍 281 紫鹃 273 香菱 258 尤氏 225 薛蟠 193 贾赦 183 |
Golang
确定电脑上安装了 Golang,获取 Golang 版本的 Jieba 中文分词包
1 | go get github.com/yanyiwu/gojieba |
然后准备 Golang 相应的分析脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | /* * @Author: suifengtec * @Date: 2018-09-13 14:49:20 * @Last Modified by: suifengtec * @Last Modified time: 2018-09-13 17:25:36 **/ /* go build -o b.exe main.go */ package main import ( "fmt" "github.com/yanyiwu/gojieba" "io/ioutil" "sort" "unicode/utf8" //"strings" ) type kv struct { Key string Value int } func isStrSliceContainStr(s []string, e string) bool { for _, a := range s { if a == e { return true } } return false } func isMapKeyMatchStr(mapStr map[string]string, key string) bool { for k, _ := range mapStr { if k == key { return true } } return false } func main() { showCount := 30 use_hmm := true x := gojieba.NewJieba() defer x.Free() var words []string var counts map[string]int var ss []kv counts = make(map[string]int) //在处理过程中过滤掉无关的高频词 excludes := []string{"什么", "一个", "我们", "那里", "你们", "如今", "说道", "知道", "起来", "姑娘", "这里", "出来", "他们", "众人", "自己", "一面", "只见", "怎么", "两个", "没有", "不是", "不知", "这个", "听见", "这样", "进来", "咱们", "告诉", "就是", "东西", "回来", "只是", "大家", "只得", "这些", "不敢", "出去", "所以", "不过", "的话", "不好", "丫头", "姐姐", "一时", "不能", "过来", "心里", "如此", "今日", "银子", "几个", "答应", "二人", "还有", "只管", "这么", "说话", "一回", "那边", "这话", "外头", "打发", "自然", "今儿", "罢了", "屋里", "那些", "听说", "小丫头", "如何", "问道", "看见", "妹妹", "人家", "不用", "媳妇", "原来", "一声", "一句", "家里", "不得", "到底", "这会子", "进去", "姊妹", "别人", "回去", "明儿", "丫鬟", "过去", "连忙", "心中", "方才", "还是", "婆子", "里头", "小厮", "哥哥", "不成", "身上", "只有", "有人", "起身", "于是", "一件", "这是", "果然", "明白", "那个", "一日", "已经", "怎么样", "跟前", "谁知", "有些", "瞧瞧", "越发", "难道", "不肯", "不必", "只怕", "主意", "不如", "喜欢", "好些", "吩咐", "况且", "便是", "夫人", "笑道", "老爷"} var mergeData map[string]string mergeData = make(map[string]string) mergeData["老太太"] = "贾母" mergeData["老太"] = "贾母" mergeData["太太"] = "王夫人" mergeData["奶奶"] = "王夫人" mergeData["凤姐儿"] = "凤姐" mergeData["老爷"] = "老爷" mergeData["二爷"] = "宝玉" mergeData["黛玉"] = "林黛玉" data, err := ioutil.ReadFile("data/红楼梦1.txt") if err != nil { fmt.Println("File reading error", err) return } s := string(data) words = x.CutForSearch(s, use_hmm) var rword string for _, word := range words { if utf8.RuneCountInString(word) == 1 { continue } else if isStrSliceContainStr(excludes, word) { continue } else if isMapKeyMatchStr(mergeData, word) { rword = mergeData[word] } else { rword = word } if i, ok := counts[rword]; ok == false { counts[rword] = 1 } else { counts[rword] = i + 1 } } // 把字典转换为以词语出现次数DESC排序的列表 for k, v := range counts { ss = append(ss, kv{k, v}) } sort.Slice(ss, func(i, j int) bool { return ss[i].Value > ss[j].Value }) for i := 0; i < showCount; i++ { fmt.Printf("%s\t %d\n", string(ss[i].Key), ss[i].Value) } } |
注意文件的相对位置,由于是测试,所以没有添加异常处理。
近似的结果是(人物以及对应的出现次数,不是人物的词语,请肉眼忽略)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 宝玉 4458 王夫人 3765 贾母 2568 凤姐 962 平儿 683 林黛玉 620 姐儿 592 姨妈 535 薛姨妈 453 探春 438 鸳鸯 426 湘云 417 去了 404 宝钗 356 晴雯 341 姥姥 319 贾琏 313 袭人 297 刘姥姥 293 邢夫人 284 香菱 265 |
准确性对比
上面截图左侧是 Golang 使用 Jieba 对红楼梦中词语频度的分析,右侧是 Python 中的相应结果。
从截图中可以发现:
- Golang 中的 Jieba 的分词能力更强;
- Golang 中的 Jieba 和 Python 中的 Jieba 相比,发现的几乎每一个词都要更多;
为了验证 Golang 中 Jieba 的准确性,我使用没有“合并”可能的没有歧义的“平儿”这个词,在文本文件中查找和替换,结果为:
实际结果是我下载的这个红楼梦文本里,平儿这个词出现了683次, 和 Golang 中 Jieba 的分词结果一致。由此看来, Python 中的 Jieba 分词的结果是不准确的。
结论
在性能上, Python的执行效率从理论和实际上都是远低于 Golang 的, Jieba 库的作者在其博客中有相应的分析( Golang 使用 Jieba 进行中文分词的要比 Python 进行相应操作快 9 倍左右)。
1 2 3 | "结巴"(Jieba)中文分词系列性能评测 https://yanyiwu.com/work/2015/06/14/jieba-series-performance-test.html |
在准确度上, Golang 中的 Jieba 库在进行中文分词时,更加准确。
同一个作者基于 Cpp 做的一个中文分词库,为什么在不同语言上准确度会有如此大的差异呢? 这个就不去分析了。
还有什么理由不用 Golang 呢?