11 回答
感谢大家的帮助!
经过一番研究,我找到了一些工作工具(考虑到您的所有建议),这就是我回答自己问题的原因。
一个 PHP 类(http://www.phpclasses.org/browse/package/2431.html)
一个 Drupal 模块,基本上是另一个 PHP 解决方案,具有 4 种不同的分割算法(很容易理解它是如何工作的)(http://drupal.org/project/csplitter)
中文分词的PHP扩展(http://code.google.com/p/phpcws/)
如果您尝试在 baidu.com 中搜索“中文分词”,还有其他一些解决方案可用
真挚地,
平等
You might want to consider using a trie data structure. You first construct the trie from the dictionary then searching for valid words will be much faster. The advantage is determining if you are at the end of a word or need to continue looking for longer words is very fast.
You have the input text, sentence, paragraph whatever. So yes, your processing of it will need to query against your DB for each check.
With decent indexing on the word column though, you shouldn't have too many problems.
Having said that, how big is this dictionary? After all, you would only need the words, not their definitions to check whether it's a valid word. So if at all possible (depending on the size), having a huge memory map/hashtable/dictionary with just keys (the actual words) may be an option and would be quick as lightning.
At 15 million words, say average 7 characters @ 2 bytes each works out around the 200 Megabytes mark. Not too crazy.
Edit: At 'only' 1 million words, you're looking at around just over 13 Megabytes, say 15 with some overhead. That's a no-brainer I would say.
我确实意识到中文分词问题是一个非常复杂的问题,但在某些情况下,这种简单的算法可能就足够了:从第 i 个字符开始搜索最长的单词 w,然后再从第 i+length(w) 个字符开始.
这是一个 Python 实现:
#!/usr/bin/env python
# encoding: utf-8
import re
import unicodedata
import codecs
class ChineseDict:
def __init__(self,lines,rex):
self.words = set(rex.match(line).group(1) for line in lines if not line.startswith("#"))
self.maxWordLength = max(map(len,self.words))
def segmentation(self,text):
result = []
previousIsSticky = False
i = 0
while i < len(text):
for j in range(i+self.maxWordLength,i,-1):
s = text[i:j]
if s in self.words:
break
sticky = len(s)==1 and unicodedata.category(s)!="Lo"
if previousIsSticky or (result and sticky):
result[-1] += s
else:
result.append(s)
previousIsSticky = sticky
i = j
return u" | ".join(result)
def genWords(self,text):
i = 0
while i < len(text):
for j in range(i+self.maxWordLength,i,-1):
s = text[i:j]
if s in self.words:
yield s
break
i = j
if __name__=="__main__":
cedict = ChineseDict(codecs.open("cedict_ts.u8",'r','utf-8'),re.compile(r"(?u)^.+? (.+?) .+"))
text = u"""33. 你可以叫我夏尔
戴高乐将军和夫人在科隆贝双教堂村过周末。星期日早晨,伊冯娜无意中走进浴室,正巧将军在洗盆浴。她感到非常意外,不禁大叫一声:“我的上帝!”
戴高乐于是转过身,看见妻子因惊魂未定而站立在门口。他继续用香皂擦身,不紧不慢地说:“伊冯娜,你知道,如果是我们之间的隐私,你可以叫我夏尔,用不着叫我上帝……”
"""
print cedict.segmentation(text)
print u" | ".join(cedict.genWords(text))
最后一部分使用CCEDICT 词典的副本将(简体)中文文本分割成两种风格(分别,有和没有非单词字符):
33. 你 | 可以 | 叫 | 我 | 夏 | 尔
戴高乐 | 将军 | 和 | 夫人 | 在 | 科隆 | 贝 | 双 | 教堂 | 村 | 过 | 周末。星期日 | 早晨,伊 | 冯 | 娜 | 无意中 | 走进 | 浴室,正巧 | 将军 | 在 | 洗 | 盆浴。她 | 感到 | 非常 | 意外,不禁 | 大 | 叫 | 一声:“我的 | 上帝!”
戴高乐 | 于是 | 转 | 过 | 身,看见 | 妻子 | 因 | 惊魂 | 未定 | 而 | 站立 | 在 | 门口。他 | 继续 | 用 | 香皂 | 擦 | 身,不 | 紧 | 不 | 慢 | 地 | 说:“伊 | 冯 | 娜,你 | 知道,如果 | 是 | 我们 | 之间 | 的 | 隐私,你 | 可以 | 叫 | 我 | 夏 | 尔,用不着 | 叫 | 我 | 上帝……”
你 | 可以 | 叫 | 我 | 夏 | 尔 | 戴高乐 | 将军 | 和 | 夫人 | 在 | 科隆 | 贝 | 双 | 教堂 | 村 | 过 | 周末 | 星期日 | 早晨 | 伊 | 冯 | 娜 | 无意中 | 走进 | 浴室 | 正巧 | 将军 | 在 | 洗 | 盆浴 | 她 | 感到 | 非常 | 意外 | 不禁 | 大 | 叫 | 一声 | 我的 | 上帝 | 戴高乐 | 于是 | 转 | 过 | 身 | 看见 | 妻子 | 因 | 惊魂 | 未定 | 而 | 站立 | 在 | 门口 | 他 | 继续 | 用 | 香皂 | 擦 | 身 | 不 | 紧 | 不 | 慢 | 地 | 说 | 伊 | 冯 | 娜 | 你 | 知道 | 如果 | 是 | 我们 | 之间 | 的 | 隐私 | 你 | 可以 | 叫 | 我 | 夏 | 尔 | 用不着 | 叫 | 我 | 上帝
另一个运行良好的是http://www.itgrass.com/phpanalysis/index.html
它是我发现的唯一一个与 utf-8 一起正常工作的。其余的只在 gb18030 中对我有用,这导致了后来的大量问题。我以为我将不得不重新开始,但这一次为我节省了很多时间。
Well, if you have a database with all words and there is no other way to get those word involved I think you are forced to re-query the database.
为了提高这个性能,你不能在将句子插入数据库之前进行所有这些检查,并自己添加空格吗?
(为简单起见,用ABCDE表示汉字)
假设您有 'sentence' ABCDE输入,并且您的字典包含以下以A开头的单词:AB、ABC、AC、AE和ABB。并假设单词CDE存在,但DE和E不存在。
解析输入句子时,从左到右,脚本提取第一个字符A。无需查询数据库以查看A是否为单词,而是查询数据库以提取所有以A开头的单词。
遍历这些结果,从输入字符串中获取接下来的几个字符以获得正确的比较:
AB ?= AB : True
ABC ?= ABC: True
AC ?= AB : False
AE ?= AB : False
ABB ?= ABC: False
此时,程序将它找到的两个“真实”分支分叉下来。首先,它假定AB是第一个词,并试图找到以C开头的词。找到了CDE,因此该分支是可能的。在另一个分支下,ABC是第一个词,但DE是不可能的,所以这个分支是无效的,意味着第一个必须是真正的解释。
我认为这种方法最大限度地减少了对数据库的调用次数(尽管它可能会从数据库返回更大的集合,因为您正在获取所有以相同字符开头的单词集合)。如果您的数据库被索引用于这种搜索,我认为这会比逐个字母更有效。现在看看这整个过程以及其他答案,我认为这实际上是一个特里结构(假设搜索的字符是树的根),正如另一位海报所建议的那样。好吧,这是该想法的实现!
一个又好又快的中文文本切分方法是基于最大匹配切分法,它基本上会测试不同长度的单词,看看哪种切分组合最有可能。它包含所有可能的单词列表。
在此处阅读更多信息:http: //technology.chtsai.org/mmseg/
这就是我在阅读器(DuZhe)文本分析器(http://duzhe.aaginskiy.com)中使用的方法。我不使用数据库,实际上我将单词列表预加载到一个数组中,该数组确实占用了大约 2MB 的 RAM,但执行速度非常快。
如果您正在考虑使用词汇分割而不是统计(尽管根据一些研究,统计方法可以准确到 ~97%),一个非常好的分割工具是 ADSOtrans,可以在这里找到:http: //www.adsotrans.com
它使用数据库,但有很多冗余表来加速分段。您还可以提供语法定义来辅助分割。
这是计算语言学中相当标准的任务。它的名称为“标记化”或“分词”。尝试搜索“中文分词”或“中文标记化”,您会发现已经为完成这项任务而制作的几种工具,以及有关研究系统的论文。
要做到这一点,您通常需要使用通过在相当大的训练语料库上运行机器学习系统而构建的统计模型。您可以在网上找到的几个系统都带有预训练模型。
You can build very very long Regular Expression.
Edit: I meant to build it automatically with script from the DB. Not to write it by hand.