我如何做词干或词形还原?

时间:2009-04-21 10:07:04

标签: nlp stemming lemmatization

我尝试过PorterStemmer和Snowball,但两个都不能用于所有单词,缺少一些非常常见的单词。

我的测试词是:“猫跑仙人掌仙人掌仙人掌社区社区”,两者都不到一半。

另见:

21 个答案:

答案 0 :(得分:140)

如果您了解Python,Natural Language Toolkit (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 module中还有其他词形变换器,但我自己没有尝试过。

答案 1 :(得分:29)

我使用stanford 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扩展程序。

答案 2 :(得分:24)

我在this snowball demo site上尝试了您的条款列表,结果看起来还不错....

  • 猫 - >猫
  • 正在运行 - >跑
  • ran - >然
  • 仙人掌 - >仙人掌
  • cactuses - >仙人掌
  • 社区 - > communiti
  • 社区 - > communiti

一个词干分子应该将变形的词汇转换成一些共同的词根。使根成为一个“正确的”字典词并不是一个干部的工作。为此,您需要查看morphological/orthographic analysers

我认为this question或多或少是相同的,Kaarel对这个问题的回答是我从第二个链接开始的。

答案 3 :(得分:20)

词干与vs lemmatizer辩论还在继续。这是一个优于精确而不是效率的问题。你应该使用lemotize来获得语言上有意义的单位,并且使用最少的计算成果,并且仍然在同一个键下索引一个单词及其变体。

请参阅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'

答案 4 :(得分:8)

Martin Porter的官方网页包含Porter Stemmer in PHP以及other languages

如果你真的认真考虑好干预虽然你需要从像Porter算法这样的东西开始,通过添加规则来修改它来修复数据集常见的不正确的情况,然后最后添加很多例外遵守规则。这可以通过键/值对(dbm / hash / dictionaries)轻松实现,其中键是要查找的单词,值是用于替换原始单词的词干单词。我曾经使用的商业搜索引擎最终得到了一些改进的Porter算法的800个例外。

答案 5 :(得分:5)

http://wordnet.princeton.edu/man/morph.3WN

对于我的很多项目,我更喜欢基于词典的WordNet变形器而不是更具攻击性的搬运工。

http://wordnet.princeton.edu/links#PHP有一个指向WN API的PHP接口的链接。

答案 6 :(得分:4)

根据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', '.']

答案 7 :(得分:3)

查看WordNet,一个用于英语的大型词汇数据库:

http://wordnet.princeton.edu/

有些API可以用多种语言访问它。

答案 8 :(得分:2)

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

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

  • 猫 - >猫
  • 运行
  • ran - >运行
  • 仙人掌
  • cactuses - >仙人掌
  • cacti - >仙人掌
  • 社区
  • 社区 - >社区

答案 9 :(得分:2)

这看起来很有趣: MIT Java WordnetStemmer: http://projects.csail.mit.edu/jwi/api/edu/mit/jwi/morph/WordnetStemmer.html

答案 10 :(得分:1)

用于lemmatization的顶级python软件包(无特定顺序)是:spacynltkgensimpatternCoreNLP和{{1 }}。我更喜欢spaCy和gensim的实现(基于模式),因为它们可以识别单词的POS标签并自动分配适当的引理。给出更多相关的引理,使含义完整。

如果您打算使用nltk或TextBlob,则需要注意手动查找正确的POS标签和查找正确的引理。

spaCy的合法化示例:

TextBlob

Gensim的合法化示例:

# 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'

以上示例是从此lemmatization页中借来的。

答案 11 :(得分:1)

搜索Lucene,我不确定是否有PHP端口,但我知道Lucene可用于许多平台。 Lucene是一个OSS(来自Apache)索引和搜索库。当然,它和社区演员可能会有一些有趣的东西。至少你可以用一种语言学习它是如何完成的,这样你就可以将“想法”翻译成PHP

答案 12 :(得分:1)

你可以使用Morpha stemmer。如果您打算从Java应用程序中使用它,那么UW有uploaded morpha stemmer to Maven central。有一个包装,使它更容易使用。您只需将其添加为依赖项并使用edu.washington.cs.knowitall.morpha.MorphaStemmer类。实例是线程安全的(原始JFlex不必要地具有局部变量的类字段)。实例化一个类并运行morpha和要阻止的单词。

new MorphaStemmer().morpha("climbed") // goes to "climb"

答案 13 :(得分:1)

NLTK中最新版的词干分析器是Snowball。

您可以在此处找到有关如何使用它的示例:

http://nltk.googlecode.com/svn/trunk/doc/api/nltk.stem.snowball2-pysrc.html#demo

答案 14 :(得分:1)

如果我可以引用我对StompChicken提到的问题的回答:

这里的核心问题是词干算法在语音的基础上运作而没有真正理解他们正在使用的语言。

由于他们对语言一无所知并且没有从术语词典中运行,因此他们无法识别并适当地回应不正常的案例,例如“run”/“ran”。

如果你需要处理不规则的情况,你需要选择一种不同的方法,或者用你自己的自定义修正词典来增加你的词干,以便在词干分析完成之后运行。

答案 15 :(得分:0)

Martin Porter写了Snowball(一种用于阻止算法的语言)并在Snowball中重写了“英语词干”。有一个英语Stemmer for C和Java。

他明确指出,由于历史原因,Porter Stemmer已重新实现 ,因此针对Porter Stemmer进行测试可以获得您应该已经知道的结果。

  

来自http://tartarus.org/~martin/PorterStemmer/index.html(强调我的)

     

Porter词干分析器应被视为“冻结”,即严格定义,并且不适合进一步修改。作为一个干扰器,它略逊于Snowball English或Porter2干扰器,它源于它,并且偶尔会有改进。因此,对于实际工作,建议使用新的Snowball割除器。 Porter干扰器适用于IR研究工作,涉及干细胞,实验需要完全可重复。

博士。 Porter建议使用英语或Porter2词干分析器而不是Porter词干分析器。英语词干分析器是demo site中实际使用的,正如@StompChicken早先回答的那样。

答案 16 :(得分:0)

在Java中,我使用tartargus-snowball来阻止词语

<强>的Maven:

<dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-snowball</artifactId>
        <version>3.0.3</version>
        <scope>test</scope>
</dependency>

示例代码:

SnowballProgram stemmer = new EnglishStemmer();
String[] words = new String[]{
    "testing",
    "skincare",
    "eyecare",
    "eye",
    "worked",
    "read"
};
for (String word : words) {
    stemmer.setCurrent(word);
    stemmer.stem();
    //debug
    logger.info("Origin: " + word + " > " + stemmer.getCurrent());// result: test, skincar, eyecar, eye, work, read
}

答案 17 :(得分:0)

在此处试试这个:http://www.twinword.com/lemmatizer.php

我在演示"cats running ran cactus cactuses cacti community communities"中输入了您的查询,并使用可选标记["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]获得了ALL_TOKENS

示例代码

这是一个API,因此您可以从任何环境连接到它。这是PHP REST调用的样子。

// These code snippets use an open-source library. http://unirest.io/php
$response = Unirest\Request::post([ENDPOINT],
  array(
    "X-Mashape-Key" => [API KEY],
    "Content-Type" => "application/x-www-form-urlencoded",
    "Accept" => "application/json"
  ),
  array(
    "text" => "cats running ran cactus cactuses cacti community communities"
  )
);

答案 18 :(得分:0)

.Net lucene有一个内置的搬运工。你可以试试。但请注意,在推导引理时,搬运工阻止不考虑单词上下文。 (完成算法及其实现,你会看到它是如何工作的)

答案 19 :(得分:0)

我强烈建议您使用Spacy(基本文本解析和标记)和Textacy(在Spacy之上构建更高级别的文本处理)。

作为令牌的.lemma_属性和文本的词汇化词are available by default in Spacy可以在使用文本进行大量其他文本预处理时被词形化。例如while creating a bag of terms or words或通常在执行某些需要它的处理之前。

我建议您在编写任何代码之前检查两者,因为这可能会为您节省大量时间!

答案 20 :(得分:-1)

df_plots = pd.read_excel("Plot Summary.xlsx", index_col = 0)
df_plots
# Printing first sentence of first row and last sentence of last row
nltk.sent_tokenize(df_plots.loc[1].Plot)[0] + nltk.sent_tokenize(df_plots.loc[len(df)].Plot)[-1]

# Calculating length of all plots by words
df_plots["Length"] = df_plots.Plot.apply(lambda x : 
len(nltk.word_tokenize(x)))

print("Longest plot is for season"),
print(df_plots.Length.idxmax())

print("Shortest plot is for season"),
print(df_plots.Length.idxmin())



#What is this show about? (What are the top 3 words used , excluding the #stop words, in all the #seasons combined)

word_sample = list(["struggled", "died"])
word_list = nltk.pos_tag(word_sample)
[wnl.lemmatize(str(word_list[index][0]), pos = word_list[index][1][0].lower()) for index in range(len(word_list))]

# Figure out the stop words
stop = (stopwords.words('english'))

# Tokenize all the plots
df_plots["Tokenized"] = df_plots.Plot.apply(lambda x : nltk.word_tokenize(x.lower()))

# Remove the stop words
df_plots["Filtered"] = df_plots.Tokenized.apply(lambda x : (word for word in x if word not in stop))

# Lemmatize each word
wnl = WordNetLemmatizer()
df_plots["POS"] = df_plots.Filtered.apply(lambda x : nltk.pos_tag(list(x)))
# df_plots["POS"] = df_plots.POS.apply(lambda x : ((word[1] = word[1][0] for word in word_list) for word_list in x))
df_plots["Lemmatized"] = df_plots.POS.apply(lambda x : (wnl.lemmatize(x[index][0], pos = str(x[index][1][0]).lower()) for index in range(len(list(x)))))



#Which Season had the highest screenplay of "Jesse" compared to "Walt" 
#Screenplay of Jesse =(Occurences of "Jesse")/(Occurences of "Jesse"+ #Occurences of "Walt")

df_plots.groupby("Season").Tokenized.sum()

df_plots["Share"] = df_plots.groupby("Season").Tokenized.sum().apply(lambda x : float(x.count("jesse") * 100)/float(x.count("jesse") + x.count("walter") + x.count("walt")))

print("The highest times Jesse was mentioned compared to Walter/Walt was in season"),
print(df_plots["Share"].idxmax())
#float(df_plots.Tokenized.sum().count('jesse')) * 100 / #float((df_plots.Tokenized.sum().count('jesse') + #df_plots.Tokenized.sum().count('walt') + #df_plots.Tokenized.sum().count('walter')))