以我的理解,最简单的分词程序,应该是先将中文文本切成最小的单位--汉字--再从词典里找词,将这些字按照最左最长原则(与正则精神暗合),合并为以词为单位的集合。这样的应该是最快的,只按照给定的数据划分合并即可,不必考虑语法元素的权重(词性:名动形数量代等等,语法:主谓宾定状补),以及上下文的出现次数。
关于源文本的切分,就参照《统计汉字/英文单词数》一文的思路,使用正则表达式r"(?x) (?: [w-]+ | [x80-xff]{3} )")来匹配即可。
关于词典,我使用的是CC-CEDICT的词典,原因有三:没有版权问题;速度较快;Chrome也在用它(发现了吧:在Chrome上双击中文句子,会自动选择中文词汇而不是单字或整行进行反选高亮)。
接下来是如何分词。经过思考,我发现搜索树的原理可以拿来就用。原理请见此文:Trie in Python。具体方法是,将词库逐字读入内存,建立搜索树;然后对目标文本进行逐字分析,如果该字之后还可搜索,则继续搜索;否则停止,作为一个词汇单位处理。
这样的算法理论上比较快(未进行benchmark),原因有三:使用Trie结构,本质上是哈希表,空间换时间,是O(0)级的搜索;词库只有800K,可以轻易载入,内存空间没占多少;算法最慢的部分是载入Trie的阶段,之后速度就不再受影响。
不过,谈到它的扩充性,目前只能在words.txt中手动添加新词,而不能实现机器学习。
源码
完整的程序(包括我处理过的词库列表)放在github上了。有兴趣的可以把玩一下。这里列出主程序:
代码如下 | 复制代码 |
#!/usr/bin/python # -*- coding: utf-8 -*- # #author: rex #blog: http://iregex.org #filename nlp.py #created: 2010-09-26 19:15 import re regex=re.compile(r"(?x) (?: [w-]+ | [x80-xff]{3} )") def init_wordslist(fn="./words.txt"): def words_2_trie(wordslist): def search_in_trie(chars, trie): if __name__=='__main__': |
本机测试
测试的文本如下:
只听得一个女子低低应了一声。绿竹翁道:“姑姑请看,这部琴谱可有些古怪。”那
女子又嗯了一声,琴音响起,调了调弦,停了一会,似是在将断了的琴弦换去,又调了调
弦,便奏了起来。初时所奏和绿竹翁相同,到后来越转越高,那琴韵竟然履险如夷,举重
若轻,毫不费力的便转了上去。令狐冲又惊又喜,依稀记得便是那天晚上所听到曲洋所奏
的琴韵。这一曲时而慷慨激昂,时而温柔雅致,令狐冲虽不明乐理,但觉这位婆婆所奏,
和曲洋所奏的曲调虽同,意趣却大有差别。这婆婆所奏的曲调平和中正,令人听着只觉音
乐之美,却无曲洋所奏热血如沸的激奋。奏了良久,琴韵渐缓,似乎乐音在不住远去,倒
像奏琴之人走出了数十丈之遥,又走到数里之外,细微几不可再闻。
理性爱国
性爱体验
我爱正则表达式
请留意末尾三行。
再看一下程序处理的结果:(*表示词汇间的分隔)
1
只 * 听 得 * 一 个 * 女 子 * 低 低 * 应 * 了 * 一 声 * 。 * 绿 * 竹 * 翁 * 道 * : * “ * 姑 姑 * 请 看 * , * 这 * 部 * 琴 * 谱 * 可 有 * 些 * 古 怪 * 。 * ” * 那 * 女 子 * 又 * 嗯 * 了 * 一 声 * , * 琴 * 音 响 * 起 * , * 调 * 了 * 调 * 弦 * , * 停 * 了 * 一 会 * , * 似 是 * 在 * 将 * 断 * 了 * 的 * 琴 弦 * 换 * 去 * , * 又 * 调 * 了 * 调 * 弦 * , * 便 * 奏 * 了 * 起 来 * 。 * 初 * 时 * 所 * 奏 * 和 * 绿 * 竹 * 翁 * 相 同 * , * 到 * 后 来 * 越 * 转 * 越 * 高 * , * 那 * 琴 * 韵 * 竟 然 * 履 险 如 夷 * , * 举 重 * 若 * 轻 * , * 毫 不 费 力 * 的 * 便 * 转 * 了 * 上 去 * 。 * 令 狐 * 冲 * 又 * 惊 * 又 * 喜 * , * 依 稀 * 记 得 * 便 是 * 那 天 * 晚 上 * 所 * 听 到 * 曲 * 洋 * 所 * 奏 * 的 * 琴 * 韵 * 。 * 这 一 * 曲 * 时 而 * 慷 慨 * 激 昂 * , * 时 而 * 温 柔 * 雅 致 * , * 令 狐 * 冲 * 虽 * 不 明 * 乐 理 * , * 但 * 觉 * 这 位 * 婆 婆 * 所 * 奏 * , * 和 * 曲 * 洋 * 所 * 奏 * 的 * 曲 调 * 虽 * 同 * , * 意 趣 * 却 * 大 有 * 差 别 * 。 * 这 * 婆 婆 * 所 * 奏 * 的 * 曲 调 * 平 和 * 中 正 * , * 令 人 * 听 * 着 * 只 * 觉 * 音 乐 之 * 美 * , * 却 * 无 * 曲 * 洋 * 所 * 奏 * 热 血 * 如 * 沸 * 的 * 激 * 奋 * 。 * 奏 * 了 * 良 久 * , * 琴 * 韵 * 渐 * 缓 * , * 似 乎 * 乐 音 * 在 * 不 住 * 远 * 去 * , * 倒 像 * 奏 * 琴 * 之 * 人 * 走 出 * 了 * 数 十 * 丈 * 之 * 遥 * , * 又 * 走 * 到 * 数 * 里 * 之 外 * , * 细 微 * 几 * 不 可 再 * 闻 * 。 * 理 性 * 爱 国 * 性 爱 * 体 验 * 我 * 爱 * 正 则 * 表 达 式
1,实用,能满足绝大部分网络文章的分词需要。
2,快速,分词过程中不会抛出DeadlineExceededError错误。
3,低内存占用,不会因为内存占用超过限制而每个实例运行一次之后就被强制kill掉。
最初的思路是:将分词词库排序好保存在一个list对象里,然后用bisect库对词库进行快速查找。因为bisect默认是c实现的,所以匹配速度非常快,但是list对象保存的词库过于耗费内存,加载速度非常慢。完全不适合在google app engine上使用。
解决的办法是:把词库中不同长度的词分开存储在不用的str对象中,使用跟bisect库同样的二分法对词库进行匹配。
.新版论坛系列介绍之二——功能介绍篇 公告:CSDN博客频道博客搬家功能上线! JavaEE快速开发平台G4Studio作者熊春专访
中国最大规模移动开发者高水平盛会 没有重量只有质量:iPad版《程序员杂志》应用上线 “第一次亲密接触”——有奖征文活动
python 中文分词——FMM 算法 .
分类: 各种脚本包括(python) 数据结构&算法 2009-06-23 12:04 2842人阅读 评论(2) 收藏 举报
FMM算法的最简单思想是使用贪心算法向前找n个,如果这n个组成的词在词典中出现,就ok,如果没有出现,那么找n-1个...然后继续下去。假如n个词在词典中出现,那么从n+1位置继续找下去,知道句子结束。
代码如下 | 复制代码 |
.import re def PreProcess(sentence,edcode="utf-8"): sentence = sentence.decode(edcode) sentence=re.sub(u"[。,,!……!《》<>/"'::?/?、/|“”‘’;]"," ",sentence) return sentence .def FMM(sentence,diction,result = [],maxwordLength = 4,edcode="utf-8"): def FMM(sentence,diction,result = [],maxwordLength = 4,edcode="utf-8"): |
测试代码:
代码如下 | 复制代码 |
[c-sharp] view plaincopyprint? .dictions = {} .dictions["ab"] = 1 .dictions["cd"] = 2 .dictions["abc"] = 1 .dictions["ss"] = 1 .dictions[ConvertGBKtoUTF("好的")] = 1 .dictions[ConvertGBKtoUTF("真的")] = 1 .sentence = "asdfa好的是这样吗vasdiw呀真的daf dasfiw asid是吗?" .s = FMM(ConvertGBKtoUTF(sentence),dictions) .for i in s: . print i.decode("utf-8") dictions = {} dictions["ab"] = 1 dictions["cd"] = 2 dictions["abc"] = 1 dictions["ss"] = 1 dictions[ConvertGBKtoUTF("好的")] = 1 dictions[ConvertGBKtoUTF("真的")] = 1 sentence = "asdfa好的是这样吗vasdiw呀真的daf dasfiw asid是吗?" s = FMM(ConvertGBKtoUTF(sentence),dictions) for i in s: print i.decode("utf-8") 文本测试代码: [c-sharp] view plaincopyprint?
|