波特词干算法
in python自然语言处理 with 0 comment

波特词干算法

in python自然语言处理 with 0 comment

在英语中,一个单词常常通常是由另外一个词经过某种变化所得到的,如:happy -> happiness,这里 happy 被称为 happiness 的词干(stem)。

应用最为广泛的、中等复杂程度的、基于后缀剥离的词干提取算法是波特词干算法,也叫波特词干器(Porter Stemmer)。

戳我直达

这里先声明一些基本定义:
对于一个单词(比如 happy ),可以将其辅音字母用c代替,元音字母用v代替,所以针对 happy 这个单词可以获得一个 cv 序列:cvccc。

对这个存在 cv 序列,可以使用 C 来表示连续出现的几个辅音字母,用 V 来表示连续出现的几个元音字母。所以针对以上获得的 cv 序列可以将其改写为:CVC。

如果我们对其推广,可以获得所有的 CV 结构:CVCVC,CVCV,VCVC,VCVCV,这种形式可以归纳为 [C](VC){m}[V],m表示前一个结构重复的次数。

前文提及到波特词干算法是一种去除后缀的算法,这种去除后缀是有某些准则的。

去除后缀的规则如下形式:
(condition) S1 -> S2
也就是说,如果这个单词的后缀是 S1 ,如果词干满足条件(condition),那么就用 S2 替换 S1 。这个条件通常是由m决定的。

假设两个单词 replace 和 replacement ,针对词根 replace 的CV序列为:C(r)V(e)C(pl)V(a)C(c)V(e)。m的值为2。替换准则为:(m > 1) ement -> ''。所以需要把 replacement 中 ement 置为空。

本文以 Python 语言为基础对其基础函数进行分析(当然是我写的版本了…,因为官方的版本是完全的C语言翻译),当然波特词干算法的官网也有其他各类语言的翻译。

class Stemmer(object):
    def __init__(self):
        # 设定元音字母序列
        self.vowels = 'aeiou'
        # 在以cvc形式的单词中需要忽略的字母
        self.cvc_ignore = 'wxy'

    def is_consonant(self, letter: str) -> bool:
        # 判断字母是否为辅音
        if letter not in self.vowels:
            return True
        return False

    def is_vowel(self, letter: str) -> bool:
        # 判断字母是否为元音
        return not self.is_consonant(letter=letter)

    def consonant_in_word_by_index(self, word: str, index: int) -> bool:
        # 判断单词第index位是否为辅音
        letter = word[index]
        if self.is_consonant(letter=letter):
            if letter == 'y' and self.is_consonant(word[index - 1]):
                return False
            return True
        return False

    def vowel_in_word_by_index(self, word: str, index: int) -> bool:
        # 判断单词第index位是否为元音
        return not self.consonant_in_word_by_index(word=word, index=index)

    def is_end_with(self, stem: str, letter='s') -> bool:
        # *s stem词干是否以s结尾
        if stem.endswith(letter):
            return True
        return False

    def is_end_with_double_same_consonants(self, stem: str) -> bool:
        # *d 判断词干以两个相同辅音结尾
        if len(stem) < 2:
            return False
        if stem[-1] != stem[-2]:
            return False
        if not (self.consonant_in_word_by_index(word=stem, index=-1)
                and self.consonant_in_word_by_index(word=stem, index=-2)):
            return False
        return True

    def vowel_in_word(self, stem: str) -> bool:
        # *v* 判断词干包含一个元音
        for letter in stem:
            if self.is_vowel(letter=letter):
                return True
        return False

    def get_form(self, word: str) -> str:
        # 根据word的元音和辅音组成返回VC序列
        form = str()
        for index in range(len(word)):
            if self.consonant_in_word_by_index(word=word, index=index):
                if index:
                    if not self.is_end_with(stem=form, letter='C'):
                        form += 'C'
                else:
                    form += 'C'
            else:
                if index:
                    if not self.is_end_with(stem=form, letter='V'):
                        form += 'V'
                else:
                    form += 'V'
        return form

    def get_m_count(self, word: str) -> int:
        # 获得单词中VC出现的次数
        return self.get_form(word=word).count('VC')

    def cvc(self, word: str) -> bool:
        # *o  - 判断词干是否以以cvc的形式结束, 但是第二个C(辅音)不是 W, X 或者Y
        if len(word) < 3:
            return False
        if self.consonant_in_word_by_index(word=word, index=-1) and \
                self.vowel_in_word_by_index(word=word, index=-2) and \
                self.consonant_in_word_by_index(word, index=-3):
            if word[-1] not in self.cvc_ignore:
                return True
            return False
        return False

    def replace(self, origin: str, rem: str, rep: str, m=None) -> str:
        # 将输入的origin单词后缀替换
        if m is None:
            return origin[:origin.rfind(rem)] + rep
        else:
            base = origin[:origin.rfind(rem)]
            if self.get_m_count(word=base) > m:
                return base + rep
            else:
                return origin

戳我直达GitHub

波特词干算法 - 残阳似血的博客
jedijulia

Comments are closed.