将一个句子分成单独的单词

时间:2010-01-26 15:03:06

标签: php cjk multibyte text-segmentation

我需要将一个中文句子分成单独的单词。中文的问题是没有空格。例如,句子可能如下所示:主楼怎么走(带空格:主楼 怎么 走)。

目前我可以想到一个解决方案。我有一个带有中文单词的字典(在数据库中)。该脚本将:

  1. 尝试在数据库中找到句子的前两个字符(主楼),

  2. 如果主楼实际上是一个单词并且它在数据库中,则脚本将尝试查找前三个字符(主楼怎)。 主楼怎不是单词,因此它不在数据库中=>我的应用程序现在知道主楼是一个单独的词。

  3. 尝试与其他角色一起使用。

  4. 我真的不喜欢这种方法,因为要分析一个小文本,它会查询数据库太多次。

    还有其他解决方法吗?

11 个答案:

答案 0 :(得分:6)

感谢大家的帮助!

经过一番研究后,我找到了一些工具(考虑到你的所有建议),这就是我回答自己问题的原因。

  1. PHP类(http://www.phpclasses.org/browse/package/2431.html

  2. 一个Drupal模块,基本上是另一个PHP解决方案,有4种不同的分割算法(很容易理解它是如何工作的)(http://drupal.org/project/csplitter

  3. 中文分词的PHP扩展(http://code.google.com/p/phpcws/

  4. 如果您尝试在baidu.com上搜索“中文分词”,可以使用其他一些解决方案

  5. 此致

    等式

答案 1 :(得分:2)

您可能需要考虑使用trie数据结构。首先从字典构造trie,然后搜索有效的单词会快得多。优点是确定你是否在一个单词的最后或需要继续寻找更长的单词是非常快。

答案 2 :(得分:1)

你有输入文字,句子,段落等等。所以,是的,您对它的处理将需要在每次检查时查询您的数据库。

虽然在单词列上有合适的索引,但你不应该有太多问题。

话虽如此,这本字典有多大?毕竟,你只需要单词而不是它们的定义来检查它是否是一个有效的单词。因此,如果可能的话(取决于大小),拥有一个巨大的内存映射/哈希表/字典只有键(实际的单词)可能是一个选项,并且会像闪电一样迅速。

1500万字,比如平均7个字符@ strong> 2字节,每个字符大约200兆字节。不要太疯狂。

编辑:在'仅'100万字的情况下,你只需要超过13兆字节就可以看到,比如说只需要一些开销。我会说,这是一个明智的选择。

答案 3 :(得分:1)

另一个运作良好的是http://www.itgrass.com/phpanalysis/index.html

我发现它是唯一一个与utf-8配合使用的产品。其余的只在gb18030中为我工作,后来引起了大量的问题。我以为我不得不重新开始,但这个节省了我很多时间。

答案 4 :(得分:0)

好吧,如果你有一个包含所有单词的数据库,并且没有其他方法可以获得这些单词,我认为你被迫重新查询数据库。

答案 5 :(得分:0)

为了提高性能,在将句子插入数据库之前不能进行所有这些检查,并自己添加空格吗?

答案 6 :(得分:0)

(使用 ABCDE 代表简体中文字符)

假设你有'句子' 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 是不可能的,因此分支无效,这意味着第一个必须是真正的解释。

我认为这种方法可以最大限度地减少对数据库的调用次数(尽管它可能会从数据库中返回更大的集合,因为您提取的所有单词都以相同的字符开头)。如果你的数据库被编入索引进行这种搜索,我认为这比逐字母更好。现在看看整个过程,以及其他答案,我认为这实际上是一个特里结构(假设搜索到的字符是树的根),正如另一张海报所暗示的那样。好吧,这是这个想法的实现!

答案 7 :(得分:0)

我确实认识到中文分词问题是一个非常复杂的问题,但在某些情况下,这个简单的算法可能就足够了:搜索以第i个字符开头的最长字w,然后再次开始i +长度(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 dictionary的副本来分割两种风格的(简化的)中文文本(分别包含和不包含非单词字符):

33. 你 | 可以 | 叫 | 我 | 夏 | 尔
    戴高乐 | 将军 | 和 | 夫人 | 在 | 科隆 | 贝 | 双 | 教堂 | 村 | 过 | 周末。星期日 | 早晨,伊 | 冯 | 娜 | 无意中 | 走进 | 浴室,正巧 | 将军 | 在 | 洗 | 盆浴。她 | 感到 | 非常 | 意外,不禁 | 大 | 叫 | 一声:“我的 | 上帝!”
    戴高乐 | 于是 | 转 | 过 | 身,看见 | 妻子 | 因 | 惊魂 | 未定 | 而 | 站立 | 在 | 门口。他 | 继续 | 用 | 香皂 | 擦 | 身,不 | 紧 | 不 | 慢 | 地 | 说:“伊 | 冯 | 娜,你 | 知道,如果 | 是 | 我们 | 之间 | 的 | 隐私,你 | 可以 | 叫 | 我 | 夏 | 尔,用不着 | 叫 | 我 | 上帝……”

你 | 可以 | 叫 | 我 | 夏 | 尔 | 戴高乐 | 将军 | 和 | 夫人 | 在 | 科隆 | 贝 | 双 | 教堂 | 村 | 过 | 周末 | 星期日 | 早晨 | 伊 | 冯 | 娜 | 无意中 | 走进 | 浴室 | 正巧 | 将军 | 在 | 洗 | 盆浴 | 她 | 感到 | 非常 | 意外 | 不禁 | 大 | 叫 | 一声 | 我的 | 上帝 | 戴高乐 | 于是 | 转 | 过 | 身 | 看见 | 妻子 | 因 | 惊魂 | 未定 | 而 | 站立 | 在 | 门口 | 他 | 继续 | 用 | 香皂 | 擦 | 身 | 不 | 紧 | 不 | 慢 | 地 | 说 | 伊 | 冯 | 娜 | 你 | 知道 | 如果 | 是 | 我们 | 之间 | 的 | 隐私 | 你 | 可以 | 叫 | 我 | 夏 | 尔 | 用不着 | 叫 | 我 | 上帝 

答案 8 :(得分:0)

分割中文文本的一种快捷方式是基于最大匹配分割,它基本上会测试不同长度的单词,以查看哪种分割组合最有可能。它会列出所有可能的单词。

在此处详细了解:http://technology.chtsai.org/mmseg/

这是我在我的读者(DuZhe)文本分析器(http://duzhe.aaginskiy.com)中使用的方法。我不使用数据库,实际上我将一个单词列表预先加载到一个数组中,该数组占用大约2MB的RAM,但执行速度非常快。

如果你正在研究使用词法分词而不是统计学(虽然根据一些研究统计方法可以达到~97%),一个非常好的分词工具是ADSOtrans,可以在这里找到:http://www.adsotrans.com < / p>

它使用数据库,但有很多冗余表来加速分段。您还可以提供语法定义以帮助进行细分。

答案 9 :(得分:-1)

这是计算语言学中相当标准的任务。它的名称是“标记化”或“分词”。尝试搜索“中文分词”或“中文标记化”,你会发现为完成这项任务所做的几个工具,以及有关研究系统的论文。

为了做到这一点,您通常需要使用通过在相当大的训练语料库上运行机器学习系统而构建的统计模型。您可以在网上找到的几个系统都配有预先训练过的模型。

答案 10 :(得分:-3)

您可以构建非常长的正则表达式。

修改 我的意思是使用DB中的脚本自动构建它。不写它 手。