高效的柬式分词解决方案?

15 投票
3 回答
2648 浏览
提问于 2025-04-16 11:00

我正在寻找一个方法,把柬埔寨语(高棉语)中的长句子拆分成单独的词(使用UTF-8编码)。高棉语的特点是单词之间没有空格。虽然网上有一些解决方案,但效果都不太理想(可以参考这里这里),而且这些项目也没有继续更新。

下面是一个需要拆分的高棉语示例句子(实际上可能会更长):

ចូរសរសើរដល់ទ្រង់ដែលទ្រង់បានប្រទានការទាំងអស់នោះមកដល់រូបអ្នកដោយព្រោះអង្គព្រះយេស៊ូវ ហើយដែលអ្នកមិនអាចរកការទាំងអស់នោះដោយសារការប្រព្រឹត្តរបស់អ្នកឡើយ។

我想要找到一个有效的解决方案来拆分高棉语单词,主要有两个目的:首先,它可以鼓励那些使用高棉旧字体(非Unicode字体)的人转向Unicode字体(Unicode有很多好处);其次,它可以让旧的高棉字体快速导入到Unicode中,以便能用拼写检查器进行检查,而不需要手动逐个拆分单词,因为对于大文档来说,这样做会非常耗时。

我不需要100%的准确性,但速度很重要(尤其是需要拆分的高棉语句子可能会很长)。我对建议持开放态度,目前我有一个正确拆分的高棉语单词的大词库(使用不换行的空格),并且我创建了一个单词概率字典文件(frequency.csv),打算用它作为拆分器的字典。

我发现了这段Python代码这里,它使用了维特比算法,据说运行速度很快。

import re
from itertools import groupby

def viterbi_segment(text):
    probs, lasts = [1.0], [0]
    for i in range(1, len(text) + 1):
        prob_k, k = max((probs[j] * word_prob(text[j:i]), j)
                        for j in range(max(0, i - max_word_length), i))
        probs.append(prob_k)
        lasts.append(k)
    words = []
    i = len(text)
    while 0 < i:
        words.append(text[lasts[i]:i])
        i = lasts[i]
    words.reverse()
    return words, probs[-1]

def word_prob(word): return dictionary.get(word, 0) / total
def words(text): return re.findall('[a-z]+', text.lower()) 
dictionary = dict((w, len(list(ws)))
                  for w, ws in groupby(sorted(words(open('big.txt').read()))))
max_word_length = max(map(len, dictionary))
total = float(sum(dictionary.values()))

我还尝试使用了这篇页面作者的Java源代码:基于字典的文本分割,但运行速度太慢,无法使用(因为我的单词概率字典有超过10万个词条……)。

这里还有一个Python的选项,来自从没有空格的文本中检测最可能的单词/组合词

WORD_FREQUENCIES = {
    'file': 0.00123,
    'files': 0.00124,
    'save': 0.002,
    'ave': 0.00001,
    'as': 0.00555
}

def split_text(text, word_frequencies, cache):
    if text in cache:
        return cache[text]
    if not text:
        return 1, []
    best_freq, best_split = 0, []
    for i in xrange(1, len(text) + 1):
        word, remainder = text[:i], text[i:]
        freq = word_frequencies.get(word, None)
        if freq:
            remainder_freq, remainder = split_text(
                    remainder, word_frequencies, cache)
            freq *= remainder_freq
            if freq > best_freq:
                best_freq = freq
                best_split = [word] + remainder
    cache[text] = (best_freq, best_split)
    return cache[text]

print split_text('filesaveas', WORD_FREQUENCIES, {})

--> (1.3653e-08, ['file', 'save', 'as'])

我对Python还是个新手,实际上对编程(除了网站开发)也很陌生,所以请多多包涵。有没有人有觉得比较有效的方案呢?

3 个回答

1

我觉得这个主意挺不错的。

我建议你在有了一些经验之后,可以添加一些规则,这些规则可以非常具体。比如,可以根据前面的词、后面的词、周围的词,或者是当前词前面的一系列词来制定规则。这里列举了一些最常见的情况。你可以在 gposttl.sf.net 项目中找到一套规则,这个项目是关于词性标注的,相关的规则在 data/contextualrulefile 文件里。

这些规则应该在统计评估完成后使用,它们可以进行一些微调,显著提高准确性。

1

这个 Python 示例中的 filesaveas 看起来是在整个输入字符串中进行递归处理(for i in xrange(1, len(text) + 1)),一路上把最好的结果放进 cache 中;在每个可能的单词处,它会 接着 查看 下一个 单词(这个单词又会去看下一个,以此类推),如果第二个单词看起来不太好,就不会保存这个特定的单词。感觉这个过程的运行时间是 O(N!),其中 N 是输入字符串的长度。

这个方法非常聪明,但可能只适合简单的任务。你知道最长的高棉语单词有多长吗?我希望它少于 20 个字符。

也许如果你每次输入 20 个字符,可以把运行时间控制在一个合理的范围内。先输入前 20 个字符,提取出第一个单词,然后再输入剩下的内容。如果你重复使用缓存,可能会出现一些奇怪的情况,比如在过程中存储部分单词。

换个话题,有多少高棉语单词是由两个或多个合法的高棉语单词组合而成的?(类似于“penknife”或“basketball”)如果不太多,可能可以创建一组字典,按单词长度分类,从单词到使用概率进行映射。

比如,最长的高棉语单词是 14 个字符;那么就把 14 个字符的输入放入 len14 字典中,存储它的概率。再把 13 个字符放入 len13,存储概率。依此类推,直到 1 个字符放入 len1。然后选择概率最高的解释,保存这个单词,去掉相应的字符,再试一次。

这样对于像“I”和“Image”这样的输入就不会出错,或许更长的输入应该自动增加概率?

谢谢你提出这个有趣的问题 ;) 我之前不知道有这样的语言,真不错。

3

ICU库(它有适用于Python和Java的接口)里面有一个叫做DictionaryBasedBreakIterator的类,可以用来处理这个问题。

撰写回答