我该如何词干或词法化?


111

我已经尝试过PorterStemmer和Snowball,但都无法在所有单词上使用,缺少一些非常常见的单词。

我的测试词是:“ 猫跑了仙人掌仙人掌社区仙人掌 ”,并且两人都获得了不到一半的权利。

也可以看看:


28
那不是仙人掌吗?
MSalters

3
只是循环引用Reddit上发布的原始问题: 我如何以编程方式进行词干处理?(例如,从“吃”到“吃”,从“仙人掌”到“仙人掌”) 在此处发布,因为评论中包含有用的信息。
Renaud Bompuis 09年

Answers:


143

如果您了解Python,自然语言工具包(NLTK)具有使用WordNet的非常强大的lemmatizer 。

请注意,如果您是初次使用该词法分析器,则必须先下载该语料库,然后再使用它。这可以通过以下方式完成:

>>> 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模块中还有其他lemmatizers,但我自己还没有尝试过。


11
真可惜...在知道要搜索之前,我实现了自己的搜索引擎!
克里斯·普弗

12
第一次使用nltk之前,请不要忘记安装语料库!velvetcache.org/2010/03/01/…–
马蒂·

1
嗯,这使用了一些不确定性算法,例如Porter Stemmer,因为如果您尝试使用dies,它将dy代替die。没有某种硬编码词干词典吗?
SexyBeast

3
知道WordNetLemmatizer错误地用词来形容的词是什么吗?
alvas 2013年

21
nltk WordNetLemmatizer需要pos标记作为参数。默认情况下,它是“ n”(代表名词)。因此,它对于动词将无法正常工作。如果POS标签不可用,则一种简单(但即席)的方法是进行两次词素化,一次用于'n',另一次用于'v'(表示动词),然后选择与原始字词(通常长度较短,但是'ran'和'run'具有相同的长度)。似乎我们不必担心'adj','adv','prep'等,因为它们在某种意义上已经处于原始形式。
Fashandge 2014年

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 2012年

1
“ pipeline = new ...”行对我来说不编译。如果我将其更改为“ StanfordCoreNLP pipelne = new ...”,它将进行编译。这是正确的吗?
Adam_G 2013年

是的,您必须先声明管道var。Stanford NLP也可以从命令行使用,因此您无需执行任何编程,只需制作属性文件并向其提供可执行文件即可。阅读文档:nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl 2014年

24

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

  • 猫->猫
  • 运行->运行
  • 跑->跑
  • 仙人掌->仙人掌
  • 仙人掌->仙人掌
  • 社区->社区
  • 社区->社区

词干被认为可以将词的变形形式转化为某些共同的词根。使该词根成为“适当的”字典词并不是真正的工作。为此,您需要查看形态/正交分析仪

我认为这个问题或多或少是同一件事,而Kaarel对这个问题的回答是我从第二个链接中获得的。


6
关键是,stem(“ updates”)== stem(“ update”)可以做到这一点(更新->
updat

1
该软件可以执行stem(x)== stem(y),但这并不能完全回答问题
用户

11
小心行话,词干不是单词的基本形式。如果需要基本形式,则需要定形器。词干是单词的最大部分,不包含前缀或后缀。单词更新的词干确实是“ updat”。单词是通过添加词尾和后缀(例如updat-e或updating)从词干中创建的。(en.wikipedia.org/wiki/Word_stem
Jindra Helcl

20

词干对词条还原器的争论还在继续。这是优先考虑精度而不是效率的问题。您应该进行词形化处理以实现语言上有意义的单元,并阻止使用最少的计算汁,并且仍然在同一键下索引单词及其变体。

参见词干机与引爆机

这是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
正如前面提到的,WordNetLemmatizerlemmatize()可以采取POS标签。所以从您的示例:" ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])给出'cat run run cactus cactuses cacti community communities'
尼克·鲁伊斯

@NickRuiz,我想你的意思是pos=NOUN?顺便说一句:很久
没见了

实际上,不是(希望会议是“是”)。因为如果设置,pos=VERB您只会对动词进行词形化。名词保持不变。我只需要编写一些自己的代码即可围绕实际的Penn Treebank POS标签旋转,以对每个令牌应用正确的词形化。同样,WordNetLemmatizer臭名昭著地使nltk的默认标记生成器变得模糊不清。因此,像这样的例子does n't不会成为制约do not
尼克·鲁伊斯

但是,即使为每个提供正确的pos,它也会port.stem("this")产生thiport.stem("was") wa
Lerner Zhang

词干分析器不会返回语言上的声音输出。只是为了使文本更“密集”(即,包含更少的词汇)。参见stackoverflow.com/questions/17317418/stemmers-vs-lemmatizersstackoverflow.com/questions/51943811/…–
alvas

8

Martin Porter的官方页面包含PHP以及其他语言Porter Stemmer

如果您真的很想做好词干,尽管您需要从Porter Algorithm之类的东西入手,则可以通过添加规则来修复数据集中常见的不正确案例来完善它,最后为规则添加很多例外。这可以通过键/值对(dbm / hash / dictionaries)轻松实现,其中键是要查找的单词,而值是要替换原始单词的词干。我曾经研究过的一个商业搜索引擎最终以800个修改后的Porter算法的例外而告终。


理想的解决方案将自动了解这些期望。您对这种系统有经验吗?
马尔科姆

否。在我们的案例中,被索引的文件是特定法律领域的法规和法规,并且有数十位(人类)编辑对索引进行了分析。
Van Gale


5

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


2

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


3
欢迎使用SO,感谢您的信息+1。如果您可以对此词干提取器的用法,性能等发表一些评论,那就太好了。通常,仅将链接视为一个很好的答案。
jogojapan 2012年


2

顶部Python包(在没有特定的顺序)的词形还原是:spacynltkgensimpatternCoreNLPTextBlob。我更喜欢spaCy和gensim的实现(基于模式),因为它们可以识别单词的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']

上面的例子是从这个词化页面借来的。


1

在Lucene上进行搜索,我不确定是否有PHP端口,但是我确实知道Lucene可用于许多平台。Lucene是OSS(来自Apache)的索引和搜索库。自然地,它和社区附加功能可能会让人感兴趣。至少您可以学习一种语言的完成方式,以便将“想法”翻译成PHP


1

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

这里的核心问题是,词干算法在语音的基础上运行,而对它们所使用的语言没有真正的了解。

由于他们不了解该语言,并且不使用术语词典,因此他们无法识别和应对不规则的情况,例如“ run” /“ ran”。

如果需要处理不正常的案件,则需要选择其他方法,或者使用您自己的自定义更正词典来增强词干,以便在词干完成任务后运行。



1

您可以使用Morpha提取器。如果您打算在Java应用程序中使用UW,则UW已将morpha stemmer上传到Maven Central。有一个包装器,使它更容易使用。您只需要将其添加为依赖项并使用edu.washington.cs.knowitall.morpha.MorphaStemmer该类。实例是线程安全的(原始JFlex不必要地具有用于局部变量的类字段)。实例化一个类并运行morpha,然后您想阻止该单词。

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

0

.Net lucene具有内置的porter stemmer。你可以试试看。但请注意,推导词干在推导出引理时不会考虑单词上下文。(仔细研究算法及其实现,您将看到其工作原理)


0

马丁·波特(Martin Porter)撰写了Snowball(一种用于词干算法的语言),并在Snowball中重写了“ English Stemmer”。有一个针对C和Java的英语词干。

他明确指出,出于历史原因重新实施了Porter Stemmer ,因此对Porter Stemmer进行词干正确性测试将获得您(应该)已经知道的结果。

来自http://tartarus.org/~martin/PorterStemmer/index.html(重点是我的)

Porter词干提取器应被视为“已冻结 ”,即严格定义且不宜进行进一步修改。作为词干,它稍逊于衍生自它的Snowball English或Porter2词干,并且偶尔会进行改进。因此,在实际工作中,建议使用新的Snowball提取器。Porter提取器适用于IR研究工作,涉及需要严格重复实验的提取。

波特博士建议使用英语或Porter2词干分析器代替Porter词干分析器。英文茎干是演示站点中实际使用的,因为@StompChicken早先已回答。


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
}

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"
  )
);

0

我强烈建议使用Spacy(基本文本解析和标记)和Textacy(基于Spacy的高级文本处理)。

在Spacy中,默认情况下.lemma_可以使用词法化的单词作为标记的属性,并且可以在对文本进行很多其他文本预处理的同时对文本进行词素化。例如,在创建术语 或单词包时,或者通常在执行需要它的某些处理之前。

我建议您在编写任何代码之前先将两者都检查一下,因为这样可以节省大量时间!


-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')))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.