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

14

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

目前我能想到的解决方案是:我有一个包含中文词汇的字典(存在数据库中)。脚本将会:

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

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

  3. 尝试对剩余的字符进行同样的操作。

我并不太喜欢这种方法,因为即使分析一个小文本,它也会查询数据库太多次。

还有其他解决方案吗?


只是好奇,如果中文没有空格,为什么需要-可能-用空格来显示?这里的用例是什么?嗯-抱歉,再读一遍问题,显然是某些单词字典查找,不要紧。 - Wim
补充一下Wim所说的,如果只是为了换行的目的,那就不必费心了:据我所知,你可以在任何字符处换行。 - C. K. Young
该应用程序需要找到单独的词语并提供它们的拼音(中文转录)。 - Peterim
你是每输入一个字母就查询数据库吗?还是只在单词的第一个字母查询? - Kevin
1
如果前两个字符是单词,第一个三个字符也都是单词,而第三个字符则是一个单独的单词,你怎么知道该使用哪一个? - sprugman
好的,它不必完美,实际上它不能完美,这是机器翻译,不是人类 =) - Peterim
11个回答

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. 如果你尝试在百度搜索“中文分词”,也可以找到其他解决方案

真诚地,

Equ


2
你可能想考虑使用trie数据结构。你可以先从字典中构建trie,然后搜索有效单词的速度将会更快。它的优点在于确定是否已经到达一个单词的结尾或者需要继续寻找更长的单词非常快速。

它也可以作为正则表达式实现(你知道:自动机、有限状态机、正则语言等)。编译后,大多数正则表达式的实现将构建一种类似于trie的结构。 - Yaakov Shoham
3
有人说正则表达式会让你早上烤面包。 - Wim

1

我知道中文分词问题非常复杂,但在某些情况下,这个简单的算法可能已经足够了:搜索以第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. 你 | 可以 | 叫 | 我 | 夏 | 尔
    戴高乐 | 将军 | 和 | 夫人 | 在 | 科隆 | 贝 | 双 | 教堂 | 村 | 过 | 周末。星期日 | 早晨,伊 | 冯 | 娜 | 无意中 | 走进 | 浴室,正巧 | 将军 | 在 | 洗 | 盆浴。她 | 感到 | 非常 | 意外,不禁 | 大 | 叫 | 一声:“我的 | 上帝!”
    戴高乐 | 于是 | 转 | 过 | 身,看见 | 妻子 | 因 | 惊魂 | 未定 | 而 | 站立 | 在 | 门口。他 | 继续 | 用 | 香皂 | 擦 | 身,不 | 紧 | 不 | 慢 | 地 | 说:“伊 | 冯 | 娜,你 | 知道,如果 | 是 | 我们 | 之间 | 的 | 隐私,你 | 可以 | 叫 | 我 | 夏 | 尔,用不着 | 叫 | 我 | 上帝……”

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

1

你有输入的文本、句子、段落或其他内容。因此,你需要对每个检查进行数据库查询。

不过,如果单词列索引得当,就不应该遇到太多问题。

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

1500万个单词中,平均每个单词7个字符,每个字符2个字节,大约占用200兆字节。这不算太疯狂。

编辑:只有100万个单词,大约13兆字节,加上一些开销也只有15兆字节。我想那是一个没得选的选择。


目前只有30万个单词,最多会有100万个。所以我想这是一个选择 =) - Peterim
在这种情况下,我肯定会将所有单词放入一个字典对象中存储在内存中。 - Wim

1

另一个表现良好的工具是http://www.itgrass.com/phpanalysis/index.html

这是我发现唯一能够正确处理utf-8编码的工具。其他工具只能在gb18030编码下正常工作,但后来会引起很多问题。我本以为不得不重新开始,但这个工具节省了我很多时间。


0

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

假设您有一个“句子”输入ABCDE,并且您的字典包含以A开头的这些单词:ABABCACAEABB。并且假设单词CDE存在,但DEE不存在。

在解析输入句子时,从左到右进行,脚本提取第一个字符A。不要查询数据库以查看A是否为单词,而是查询数据库以获取以A开头的所有单词。

循环遍历这些结果,从输入字符串中获取接下来的几个字符以进行适当的比较:

AB  ?= AB : True
ABC ?= ABC: True
AC  ?= AB : False
AE  ?= AB : False
ABB ?= ABC: False

在这一点上,程序分叉了它找到的两个“真实”分支。在第一个分支上,它假设AB是第一个单词,并尝试查找以C开头的单词。找到了CDE,因此该分支是可能的。在另一个分支中,ABC是第一个单词,但DE不可能,因此该分支无效,这意味着第一个必须是真正的解释。

我认为这种方法最小化了对数据库的调用次数(尽管它可能会从数据库返回更大的集合,因为您正在获取所有以相同字符开头的单词集)。如果您的数据库针对这种搜索进行了索引,我认为这比逐字母进行更好。现在看整个过程和其他答案,我认为这实际上是一种trie结构(假设要搜索的字符是树的根),就像另一个帖子所建议的那样。好吧,这里是这个想法的实现!


0
一个好而快速的中文分词方法是基于最大匹配分词,它会测试不同长度的单词,以确定哪种分割组合最有可能。它需要一个包含所有可能单词的列表来实现。
在我的读者(DuZhe)文本分析器(http://duzhe.aaginskiy.com)中,我使用的就是这种方法。实际上,我没有使用数据库,而是将一个单词列表预先加载到数组中,这会占用大约2MB的RAM,但执行非常快。
如果您正在考虑使用词法分割而不是统计分析(尽管某些研究表明,统计方法的准确率可以达到约97%),那么一个非常好的分割工具是ADSOtrans,可以在这里找到:http://www.adsotrans.com
它使用数据库,但有很多冗余表来加速分割。您还可以提供语法定义来帮助分割。

0

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


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


问题似乎在于对于这些检查,您需要查询字典...这样您就知道在哪里分割句子。 - Wim
不幸的是,这应该由应用程序自动完成。用户输入一个句子,然后得到带有拼音的单独单词。 - Peterim

-1

这是计算语言学中一个相当标准的任务。它被称为“分词”或“词语分割”。尝试搜索“中文分词”或“中文词汇划分”,您会找到已开发出用于执行此任务的多个工具以及相关研究系统的论文。

要做得好,通常需要使用通过在相当大的训练语料库上运行机器学习系统建立的统计模型。您可以在网络上找到的一些系统带有预训练模型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接