【问题标题】:How do I do word Stemming or Lemmatization?如何进行词干提取或词形还原?
【发布时间】:2010-10-20 18:59:50
【问题描述】:

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

我的测试词是:“cats running ran cactus cactuses cacti community community”,两个都答对了不到一半。

另请参阅:

【问题讨论】:

标签: nlp stemming lemmatization


【解决方案1】:

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

【讨论】:

    【解决方案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']
    

    上面的例子是从这个lemmatization页面借用的。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:
        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')))
        

        【讨论】:

          【解决方案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', '.']
          

          【讨论】:

            【解决方案6】:

            如果你了解 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 中还有其他的词形还原器,但我自己没有尝试过。

            【讨论】:

            • 哦,可悲...在我知道搜索 S.O. 之前我实现了自己的!
            • 第一次使用nltk之前不要忘记安装语料库! velvetcache.org/2010/03/01/…
            • 嗯,这个使用了一些非确定性算法,比如 Porter Stemmer,如果你用dies 尝试它,它会给你dy 而不是die。不是有某种硬编码的词干词典吗?
            • 知道WordNetLemmatizer 错误地词形化的词是什么吗?
            • nltk WordNetLemmatizer 需要一个 pos 标签作为参数。默认情况下它是“n”(代表名词)。所以它不能正确地用于动词。如果 POS 标签不可用,一个简单(但特别)的方法是进行两次词形还原,一次用于“n”,另一次用于“v”(代表动词),并选择与原始单词(通常长度较短,但 'ran' 和 'run' 长度相同)。似乎我们不需要担心“adj”、“adv”、“prep”等,因为它们在某种意义上已经是原始形式了。
            【解决方案7】:

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

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

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

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

            【讨论】:

            • 重点是stem("updates") == stem("update"),它确实做到了(update -> updat)
            • 该软件可以做 stem(x) == stem(y) 但这并不能完全回答问题
            • 小心行话,词干不是单词的基本形式。如果你想要一个基本形式,你需要一个词形还原器。词干是单词中不包含前缀或后缀的最大部分。 update 的词干确实是“updat”。这些词是通过添加结尾和后缀从词干创建的,例如更新-e,或更新。 (en.wikipedia.org/wiki/Word_stem)
            【解决方案8】:

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

            【讨论】:

            • 抱歉回复晚了。我现在才解决这个问题! :)
            • 行 'pipeline = new...' 无法为我编译。如果我将其更改为“StanfordCoreNLP pipelne = new ...”,它会编译。这对吗?
            • 是的,你必须先声明管道变量。斯坦福 NLP 也可以从命令行使用,因此您无需进行任何编程,您只需制作属性文件并将其提供给可执行文件即可。阅读文档:nlp.stanford.edu/software/corenlp.shtml
            【解决方案9】:

            词干分析器与词形还原器的争论仍在继续。这是一个更喜欢精确而不是效率的问题。您应该进行词形还原以实现具有语言意义的单元,并使用最少的计算量来索引单词及其变体在同一键下。

            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'
            

            【讨论】:

            • 如前所述,WordNetLemmatizerlemmatize() 可以带 POS 标签。所以从你的例子中:" ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()]) 给出'cat run run cactus cactuses cacti community communities'
            • @NickRuiz,我想你的意思是pos=NOUN? BTW:好久不见,希望我们很快能在会议上见面=)
            • 实际上,没有(但希望对会议说“是”)。因为如果您设置pos=VERB,您只会对动词进行词形还原。名词保持不变。我只需要编写一些我自己的代码来围绕实际的 Penn Treebank POS 标签进行旋转,以对每个令牌应用正确的词形还原。此外,WordNetLemmatizer 在对 nltk 的默认标记器进行词形还原时很臭。因此,does n't 之类的示例不会引理为 do not
            • 但是,但是 port.stem("this") 产生 thiport.stem("was") wa,即使为每个都提供了正确的位置。
            • 词干分析器不会返回语言正确的输出。这只是为了使文本更“密集”(即包含更少的词汇)。见stackoverflow.com/questions/17317418/stemmers-vs-lemmatizersstackoverflow.com/questions/51943811/…
            【解决方案10】:

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

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

            示例代码

            这是一个 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"
              )
            );
            

            【讨论】:

              【解决方案11】:

              在 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
              }
              

              【讨论】:

                【解决方案12】:

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

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

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

                【讨论】:

                  【解决方案13】:

                  Martin Porter 编写了 Snowball(一种用于词干算法的语言)并在 Snowball 中重写了“English Stemmer”。有一个适用于 C 和 Java 的英语词干分析器。

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

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

                  Porter 词干分析器应被视为“frozen”,即严格定义,不能进一步修改。作为一个词干分析器,它略逊于从它派生的 Snowball English 或 Porter2 词干分析器,并且偶尔会进行改进。因此,对于实际工作,建议使用新的 Snowball 词干分析器。 Porter 词干分析器适用于涉及词干的 IR 研究工作,其中实验需要完全可重复。

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

                  【讨论】:

                    【解决方案14】:

                    .Net lucene 有一个内置的搬运工词干分析器。你可以试试。但请注意,波特词干在推导引理时不考虑词上下文。 (通过算法及其实现,你会看到它是如何工作的)

                    【讨论】:

                      【解决方案15】:

                      这看起来很有趣: 麻省理工学院 Java WordnetStemmer: http://projects.csail.mit.edu/jwi/api/edu/mit/jwi/morph/WordnetStemmer.html

                      【讨论】:

                      • 欢迎来到 SO,感谢您的帖子,+1。如果你能对这个词干分析器的用法、性能等做一些 cmet 那就太好了。通常只是一个链接不被认为是一个很好的答案。
                      【解决方案16】:

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

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

                      【讨论】:

                        【解决方案17】:

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

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

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

                        【讨论】:

                          【解决方案18】:

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

                          对于我的许多项目,我更喜欢基于词典的 WordNet 词形还原器,而不是更具侵略性的搬运工词干提取。

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

                          【讨论】:

                            【解决方案19】:

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

                            http://wordnet.princeton.edu/

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

                            【讨论】:

                              【解决方案20】:

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

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

                              由于他们不懂语言,也没有从字典中跑出来,他们无法识别和适当地应对不正常的情况,例如“run”/“ran”。

                              如果您需要处理不规则的情况,您需要选择不同的方法,或者使用您自己的自定义更正字典来增强您的词干提取,以便在词干分析器完成操作后运行。

                              【讨论】:

                                【解决方案21】:

                                Martin Porter 的官方页面包含Porter Stemmer in PHPother languages

                                如果您真的很重视良好的词干提取,尽管您需要从 Porter 算法之类的东西开始,通过添加规则来修复数据集中常见的不正确案例来完善它,然后最后添加大量异常到规则。这可以通过键/值对(dbm/hash/dictionaries)轻松实现,其中键是要查找的单词,值是替换原始单词的词干。我曾经工作过的一个商业搜索引擎以修改后的 Porter 算法有 800 个例外。

                                【讨论】:

                                • 一个理想的解决方案会自动学习这些期望。你有过这样的系统的经验吗?
                                • 没有。在我们的案例中,被索引的文档是特定法律领域的代码和法规,并且有数十名(人工)编辑人员分析索引是否存在任何不良词干。
                                猜你喜欢
                                • 2014-11-02
                                • 1970-01-01
                                • 2018-08-27
                                • 2017-06-21
                                • 2018-11-03
                                • 2021-09-11
                                • 2020-07-07
                                • 2021-10-19
                                • 1970-01-01
                                相关资源
                                最近更新 更多