我该如何进行词干提取或词形归并?

114

28
那应该是仙人掌吧? - MSalters
3
只是为了循环引用Reddit上发布的原始问题: [如何以编程方式进行词干提取? (例如,“eating”变为“eat”,“cactuses”变为“cactus”)](http://www.reddit.com/r/programming/comments/8e5d3/how_do_i_programatically_do_stemming_eg_eating_to/)在此发布,因为评论包含有用的信息。 - Renaud Bompuis
1
请参见 https://dev59.com/4WQm5IYBdhLWcg3w0Bq5 - alvas
22个回答

145
如果你了解Python,自然语言工具包(NLTK)有一个非常强大的词形还原器,它使用了WordNet
请注意,如果您是第一次使用这个词形还原器,您必须在使用之前下载语料库。可以通过以下方式完成:
>>> import nltk
>>> nltk.download('wordnet')

您只需要执行一次此操作。假设您现在已经下载了语料库,它的工作方式如下:

>>> from nltk.stem.wordnet import WordNetLemmatizer
>>> lmtzr = WordNetLemmatizer()
>>> lmtzr.lemmatize('cars')
'car'
>>> lmtzr.lemmatize('feet')
'foot'
>>> lmtzr.lemmatize('people')
'people'
>>> lmtzr.lemmatize('fantasized','v')
'fantasize'

nltk.stem模块中还有其他的词形还原器,但我自己没有尝试过。


11
哦,很遗憾...在我知道要搜索 S.O. 之前,我已经实现了自己的方法! - Chris Pfohl
12
使用 nltk 前不要忘记安装语料库!http://www.velvetcache.org/2010/03/01/looking-up-words-in-a-dictionary-using-python - Mathieu Rodic
3
WordNetLemmatizer错误还原词形的单词有哪些? - alvas
1
就性能(执行速度)而言,词形还原比提取词干慢得多吗? - mchangun
21
nltk的WordNetLemmatizer需要一个词性标记作为参数, 默认情况下是'n'(表示名词),因此对于动词它不能正确工作。如果没有可用的词性标记,一个简单但特定的方法是进行两次词形还原,一次为'n',另一次为'v'(代表动词),并选择与原始单词不同的结果(通常长度较短,但'ran'和'run'长度相同)。似乎我们不需要担心'adj'、'adv'、'prep'等,因为它们在某种意义上已经处于原始形式。 - Fashandge
显示剩余2条评论

29

我使用斯坦福NLP进行词形还原。在过去的几天里,我一直卡在了一个类似的问题上。感谢stackoverflow帮助我解决了这个问题。

import java.util.*; 
import edu.stanford.nlp.pipeline.*;
import edu.stanford.nlp.ling.*; 
import edu.stanford.nlp.ling.CoreAnnotations.*;  

public class example
{
    public static void main(String[] args)
    {
        Properties props = new Properties(); 
        props.put("annotators", "tokenize, ssplit, pos, lemma"); 
        pipeline = new StanfordCoreNLP(props, false);
        String text = /* the string you want */; 
        Annotation document = pipeline.process(text);  

        for(CoreMap sentence: document.get(SentencesAnnotation.class))
        {    
            for(CoreLabel token: sentence.get(TokensAnnotation.class))
            {       
                String word = token.get(TextAnnotation.class);      
                String lemma = token.get(LemmaAnnotation.class); 
                System.out.println("lemmatized version :" + lemma);
            }
        }
    }
}

如果后面的分类器要使用词形还原的结果,建议使用停用词来减少输出结果。请查看 John Conwell 编写的coreNLP扩展。


抱歉回复晚了,我现在才解决了这个问题! :) - CTsiddharth
1
“pipeline = new…” 这一行代码无法编译通过。如果我将其改为 “StanfordCoreNLP pipeline = new…”,它就可以编译通过了。这样正确吗? - Adam_G
是的,你必须先声明管道变量。Stanford NLP 也可以从命令行使用,因此您不必进行任何编程,只需创建属性文件并将可执行文件与其一起使用即可。阅读文档:http://nlp.stanford.edu/software/corenlp.shtml - Jindra Helcl

24

我在这个Snowball演示网站上尝试了您的术语列表,结果看起来还不错...

  • cats -> 猫(cat)
  • running -> 运行(run)
  • ran -> 跑(ran)
  • cactus -> 仙人掌(cactus)
  • cactuses -> 仙人掌(cactus)
  • community -> 社区(communiti)
  • communities -> 社区(communiti)

一个词干提取器应该将单词的屈折形式转化为一些常见的词根形式。词干提取器的工作并不是使这个词根成为一个“正确”的词典词汇。要做到这一点,您需要查看形态/正字分析器

我认为这个问题大致也是关于同样的事情,Kaarel对那个问题的回答是我从中获取第二个链接的地方。


6
重点在于stem("updates")与stem("update")相等,而它确实是相等的(update -> updat)。 - Stompchicken
1
该软件可以执行stem(x) == stem(y),但这并不能完全回答问题。 - user
11
注意术语的使用,词干并不是单词的基本形式。如果您想要基本形式,您需要一个词形还原器。词干是一个单词中不包含前缀或后缀的最大部分。单词“update”的词干确实是“updat”。通过添加词尾和后缀,例如updat-e 或 updat-ing,可以从词干创建单词。(http://en.wikipedia.org/wiki/Word_stem) - Jindra Helcl

21

词干提取器(stemmer)与词形还原器(lemmatizer)的辩论仍在继续。这是一个关于精度和效率之间偏好的问题。你应该进行词形还原以实现语言学上有意义的单位,而进行词干提取则能够使用最少的计算资源来将单词及其变体索引到相同的键下。

参见Stemmers vs Lemmatizers

以下是使用Python NLTK的示例:

>>> sent = "cats running ran cactus cactuses cacti community communities"
>>> from nltk.stem import PorterStemmer, WordNetLemmatizer
>>>
>>> port = PorterStemmer()
>>> " ".join([port.stem(i) for i in sent.split()])
'cat run ran cactu cactus cacti commun commun'
>>>
>>> wnl = WordNetLemmatizer()
>>> " ".join([wnl.lemmatize(i) for i in sent.split()])
'cat running ran cactus cactus cactus community community'

3
如前所述,“WordNetLemmatizer”的“lemmatize()”方法可以接受一个词性标记。因此,对于您的示例代码:“" ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])”将返回“'cat run run cactus cactuses cacti community communities'”。 - Nick Ruiz
@NickRuiz,我想你是指pos=NOUN吧?顺便说一句:好久不见了,希望我们很快能在会议上见面 =) - alvas
实际上,不行(但希望能参加会议)。因为如果你设置pos=VERB,你只对动词进行词形还原。名词保持不变。我不得不编写一些自己的代码来绕过实际的Penn Treebank POS标签,以对每个令牌应用正确的词形还原。此外,WordNetLemmatizer在对nltk的默认分词器进行词形还原时效果不佳。所以像does n't这样的例子无法还原为do not - Nick Ruiz
但是,port.stem("this") 产生 thi,而 port.stem("was") 则产生 wa,即使为每个单词提供了正确的 pos。 - Lerner Zhang
一个词干提取器不会返回语言学上正确的输出。它只是为了使文本更加“密集”(即包含较少的词汇)。请参见 https://dev59.com/4WQm5IYBdhLWcg3w0Bq5 和 https://dev59.com/rK3la4cB1Zd3GeqPSczI#51978364。 - alvas

9
马丁·波特的官方页面包含一个用PHP编写的波特词干算法(Porter Stemmer in PHP)以及其他语言版本。
如果你真的想要好的词干提取,那么你需要从波特算法开始,通过添加规则来修复数据集中常见的不正确情况,并最终添加很多规则的例外情况。这可以很容易地使用键值对(dbm/hash/dictionaries)实现,其中键是要查找的单词,而值是要替换原始单词的词干单词。我曾经参与开发的一款商业搜索引擎最终有800多个修改后的波特算法的例外情况。

一个理想的解决方案应该能够自动学习这些期望。你有使用过这样的系统吗? - Malcolm
不,在我们的情况下,被索引的文件是特定法律领域的代码和法规,有数十个(人类)编辑分析索引中的任何错误词干。 - Van Gale

6
根据我在Stack Overflow和博客上看到的各种答案,我正在使用以下方法,它似乎可以很好地返回实际单词。 思路是将输入的文本拆分为一个单词数组(使用任何您喜欢的方法),然后找到这些单词的词性(POS)并使用它来帮助提取词干和词形还原。
由于无法确定POS,您上面的示例效果不太好。 但是,如果我们使用一个真实的句子,情况会好得多。
import nltk
from nltk.corpus import wordnet

lmtzr = nltk.WordNetLemmatizer().lemmatize


def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN


def normalize_text(text):
    word_pos = nltk.pos_tag(nltk.word_tokenize(text))
    lemm_words = [lmtzr(sw[0], get_wordnet_pos(sw[1])) for sw in word_pos]

    return [x.lower() for x in lemm_words]

print(normalize_text('cats running ran cactus cactuses cacti community communities'))
# ['cat', 'run', 'ran', 'cactus', 'cactuses', 'cacti', 'community', 'community']

print(normalize_text('The cactus ran to the community to see the cats running around cacti between communities.'))
# ['the', 'cactus', 'run', 'to', 'the', 'community', 'to', 'see', 'the', 'cat', 'run', 'around', 'cactus', 'between', 'community', '.']

5

3

2

针对词形还原,最常用的 Python 包(无特定顺序)包括:spacynltkgensimpatternCoreNLPTextBlob。我更喜欢 spaCy 和 gensim 的实现方式(基于 pattern),因为它们可以自动识别单词的 POS 标签并分配适当的词元。这样可以得到更相关的词元,保持意义不变。

如果您计划使用 nltk 或 TextBlob,则需要手动找到正确的 POS 标签并找到正确的词元。

spaCy 词形还原示例:

# Run below statements in terminal once. 
pip install spacy
spacy download en

import spacy

# Initialize spacy 'en' model
nlp = spacy.load('en', disable=['parser', 'ner'])

sentence = "The striped bats are hanging on their feet for best"

# Parse
doc = nlp(sentence)

# Extract the lemma
" ".join([token.lemma_ for token in doc])
#> 'the strip bat be hang on -PRON- foot for good'

使用 Gensim 进行词形还原的例子:

from gensim.utils import lemmatize
sentence = "The striped bats were hanging on their feet and ate best fishes"
lemmatized_out = [wd.decode('utf-8').split('/')[0] for wd in lemmatize(sentence)]
#> ['striped', 'bat', 'be', 'hang', 'foot', 'eat', 'best', 'fish']

上述例子是从这个词形还原页面借来的。

2

请看LemmaGen -一款用C#3.0编写的开源库。

测试词汇结果(http://lemmatise.ijs.si/Services

  • cats -> cat(猫-猫)
  • running(奔跑)
  • ran -> run(跑-跑)
  • cactus(仙人掌)
  • cactuses -> cactus(仙人掌-仙人掌)
  • cacti -> cactus(仙人掌-仙人掌)
  • community(社区)
  • communities -> community(社区-社区)

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