【问题标题】:Extract most important keywords from a set of documents从一组文档中提取最重要的关键字
【发布时间】:2018-02-02 07:30:30
【问题描述】:

我有一组 3000 个文本文档,我想提取前 300 个关键字(可以是单个词或多个词)。

我尝试了以下方法 -

RAKE:这是一个基于 Python 的关键字提取库,失败得很惨。

Tf-Idf:它为每个文档提供了很好的关键字,但它无法聚合它们并找到代表整个文档组的关键字。 另外,仅根据 Tf-Idf 分数从每个文档中选择前 k 个单词也无济于事,对吧?

Word2vec:我可以做一些很酷的事情,比如找到相似的词,但不知道如何使用它找到重要的关键词。

您能否提出一些好的方法(或详细说明如何改进上述 3 项)来解决这个问题?谢谢:)

【问题讨论】:

    标签: nlp rake feature-extraction word2vec tf-idf


    【解决方案1】:

    你最好手动选择这 300 个单词(不是那么多,是一次) - 用 Python 3 编写的代码

    import os
    files = os.listdir()
    topWords = ["word1", "word2.... etc"]
    wordsCount = 0
    for file in files: 
            file_opened = open(file, "r")
            lines = file_opened.read().split("\n")
            for word in topWords: 
                    if word in lines and wordsCount < 301:
                                    print("I found %s" %word)
                                    wordsCount += 1
            #Check Again wordsCount to close first repetitive instruction
            if wordsCount == 300:
                    break
    

    【讨论】:

    • 这个答案没有回答“自动提取”的问题。阅读 3000 篇文档,单独提取关键词,相当耗时。
    • 确实如此,但正如我已经提到的,如果它是一次性操作,我认为如果脚本需要 1 秒或 1 分钟,那这并不重要......如果我的回答没有t真的有帮助......我可以删除这个。你可以这样@LucaFoppiano 吗?谢谢
    • 我认为你的答案有几个问题,因为知道这 300 个单词是事先不知道的艰巨任务。目前尚不清楚您的脚本实际上试图做什么;-) 因为 topWords 已经知道..
    【解决方案2】:
    import os
    import operator
    from collections import defaultdict
    files = os.listdir()
    topWords = ["word1", "word2.... etc"]
    wordsCount = 0
    words = defaultdict(lambda: 0)
    for file in files:
        open_file = open(file, "r")
        for line in open_file.readlines():
            raw_words = line.split()
            for word in raw_words:
                words[word] += 1
    sorted_words = sorted(words.items(), key=operator.itemgetter(1))
    

    现在从排序的词中取出前 300 个词,它们就是你想要的词。

    【讨论】:

    • 谢谢@Awaish,但我也试过这个。这种方法的结果很差,因为重要的术语只出现一两次。如果我尝试根据频率对 Tf-idf 术语进行排序和选择,则会出现很多常见且不相关的术语。
    • 此解决方案意味着您已经知道要查找的单词。
    【解决方案3】:

    对最重要的单词应用 tf-idf 实现的最简单有效的方法。如果您有停用词,您可以在应用此代码之前过滤停用词。希望这对你有用。

    import java.util.List;
    
    /**
     * Class to calculate TfIdf of term.
     * @author Mubin Shrestha
     */
    public class TfIdf {
    
        /**
         * Calculates the tf of term termToCheck
         * @param totalterms : Array of all the words under processing document
         * @param termToCheck : term of which tf is to be calculated.
         * @return tf(term frequency) of term termToCheck
         */
        public double tfCalculator(String[] totalterms, String termToCheck) {
            double count = 0;  //to count the overall occurrence of the term termToCheck
            for (String s : totalterms) {
                if (s.equalsIgnoreCase(termToCheck)) {
                    count++;
                }
            }
            return count / totalterms.length;
        }
    
        /**
         * Calculates idf of term termToCheck
         * @param allTerms : all the terms of all the documents
         * @param termToCheck
         * @return idf(inverse document frequency) score
         */
        public double idfCalculator(List allTerms, String termToCheck) {
            double count = 0;
            for (String[] ss : allTerms) {
                for (String s : ss) {
                    if (s.equalsIgnoreCase(termToCheck)) {
                        count++;
                        break;
                    }
                }
            }
            return 1 + Math.log(allTerms.size() / count);
        }
    }
    

    【讨论】:

    • 谢谢@shiv。但是我已经实现了 Tf-Idf,并且我使用 Lucene 实现了它(为了更快的处理)。问题是 Tf-Idf 为每个文档而不是整个文档集提供“重要术语”。
    【解决方案4】:

    虽然Latent Dirichlet allocationHierarchical Dirichlet Process 通常用于在文本语料库中派生主题,然后使用这些主题对单个条目进行分类,但也可以开发一种为整个语料库派生关键字的方法。这种方法受益于不依赖另一个文本语料库。一个基本的工作流程是将这些 Dirichlet 关键字与最常用的词进行比较,以查看 LDA 或 HDP 是否能够识别出包含在最常用词中的重要词。

    在使用以下代码之前,一般建议先对文本进行如下预处理:

    1. 从文本中删除标点符号(参见string.punctuation
    2. 将字符串文本转换为“标记”(str.split(‘ ’).lower() 为单个单词)
    3. 删除数字和停用词(请参阅 stopwordsisostop_words
    4. 创建二元组 - 文本中经常一起出现的单词组合(参见 gensim.Phrases
    5. 词形化标记 - 将单词转换为其基本形式(参见 spacyNLTK
    6. 删除不够频繁(或过于频繁,但在这种情况下跳过删除过于频繁的令牌,因为这些是很好的关键字)的标记

    这些步骤将在下面创建变量corpus。可以在 here 找到对所有这一切的一个很好的概述以及对 LDA 的解释。

    现在使用 gensim 用于 LDA 和 HDP:

    from gensim.models import LdaModel, HdpModel
    from gensim import corpora
    

    首先创建一个将corpus 中的单词映射到索引的dirichlet 字典,然后使用它创建一个单词包,其中corpus 中的标记被它们的索引替换。这是通过:

    dirichlet_dict = corpora.Dictionary(corpus)
    bow_corpus = [dirichlet_dict.doc2bow(text) for text in corpus]
    

    对于LDA,需要推导最优的主题数量,可以通过this answer中的方法启发式地完成。假设我们的最佳主题数是 10,根据问题我们想要 300 个关键字:

    num_topics = 10
    num_keywords = 300
    

    创建一个 LDA 模型:

    dirichlet_model = LdaModel(corpus=bow_corpus,
                               id2word=dirichlet_dict,
                               num_topics=num_topics,
                               update_every=1,
                               chunksize=len(bow_corpus),
                               passes=20,
                               alpha='auto')
    

    接下来是一个函数,可以根据整个语料库的平均连贯性得出最佳主题。首先将生成每个主题最重要单词的有序列表;然后找到每个主题对整个语料库的平均连贯性;最后,主题根据这个平均连贯性进行排序,并与稍后使用的平均值列表一起返回。所有这些的代码如下(包括从下面使用 HDP 的选项):

    def order_subset_by_coherence(dirichlet_model, bow_corpus, num_topics=10, num_keywords=10):
        """
        Orders topics based on their average coherence across the corpus
    
        Parameters
        ----------
            dirichlet_model : gensim.models.type_of_model
            bow_corpus : list of lists (contains (id, freq) tuples)
            num_topics : int (default=10)
            num_keywords : int (default=10)
    
        Returns
        -------
            ordered_topics, ordered_topic_averages: list of lists and list
        """
        if type(dirichlet_model) == gensim.models.ldamodel.LdaModel:
            shown_topics = dirichlet_model.show_topics(num_topics=num_topics, 
                                                       num_words=num_keywords,
                                                       formatted=False)
        elif type(dirichlet_model)  == gensim.models.hdpmodel.HdpModel:
            shown_topics = dirichlet_model.show_topics(num_topics=150, # return all topics
                                                       num_words=num_keywords,
                                                       formatted=False)
        model_topics = [[word[0] for word in topic[1]] for topic in shown_topics]
        topic_corpus = dirichlet_model.__getitem__(bow=bow_corpus, eps=0) # cutoff probability to 0 
    
        topics_per_response = [response for response in topic_corpus]
        flat_topic_coherences = [item for sublist in topics_per_response for item in sublist]
    
        significant_topics = list(set([t_c[0] for t_c in flat_topic_coherences])) # those that appear
        topic_averages = [sum([t_c[1] for t_c in flat_topic_coherences if t_c[0] == topic_num]) / len(bow_corpus) \
                          for topic_num in significant_topics]
    
        topic_indexes_by_avg_coherence = [tup[0] for tup in sorted(enumerate(topic_averages), key=lambda i:i[1])[::-1]]
    
        significant_topics_by_avg_coherence = [significant_topics[i] for i in topic_indexes_by_avg_coherence]
        ordered_topics = [model_topics[i] for i in significant_topics_by_avg_coherence][:num_topics] # limit for HDP
    
        ordered_topic_averages = [topic_averages[i] for i in topic_indexes_by_avg_coherence][:num_topics] # limit for HDP
        ordered_topic_averages = [a/sum(ordered_topic_averages) for a in ordered_topic_averages] # normalize HDP values
    
        return ordered_topics, ordered_topic_averages
    

    现在获取关键字列表 - 主题中最重要的单词。这是通过基于它们与整体的平均连贯性从每个有序主题中对单词(默认情况下再次按重要性排序)进行子集化来完成的。为了明确解释,假设只有两个主题,文本与第一个主题的连贯性为 70%,与第二个主题的连贯性为 30%。然后关键字可以是第一个主题中前 70% 的词,以及第二个主题中尚未选择的前 30% 的词。这是通过以下方式实现的:

    ordered_topics, ordered_topic_averages = \
        order_subset_by_coherence(dirichlet_model=dirichlet_model,
                                  bow_corpus=bow_corpus, 
                                  num_topics=num_topics,
                                  num_keywords=num_keywords)
    
    keywords = []
    for i in range(num_topics):
        # Find the number of indexes to select, which can later be extended if the word has already been selected
        selection_indexes = list(range(int(round(num_keywords * ordered_topic_averages[i]))))
        if selection_indexes == [] and len(keywords) < num_keywords: 
            # Fix potential rounding error by giving this topic one selection
            selection_indexes = [0]
                  
        for s_i in selection_indexes:
            if ordered_topics[i][s_i] not in keywords and ordered_topics[i][s_i] not in ignore_words:
                keywords.append(ordered_topics[i][s_i])
            else:
                selection_indexes.append(selection_indexes[-1] + 1)
    
    # Fix for if too many were selected
    keywords = keywords[:num_keywords]
    

    上面还包括变量ignore_words,它是一个不应该包含在结果中的单词列表。

    对于 HDP,模型遵循与上述类似的过程,除了在模型创建中不需要传递 num_topics 和其他参数。 HDP 自己推导出最佳主题,但随后需要使用order_subset_by_coherence 对这些主题进行排序和子集化,以确保将最佳主题用于有限选择。通过以下方式创建模型:

    dirichlet_model = HdpModel(corpus=bow_corpus, 
                               id2word=dirichlet_dict,
                               chunksize=len(bow_corpus))
    

    最好同时测试 LDA 和 HDP,因为如果能够找到合适数量的主题,LDA 可以根据问题的需要表现出色(这仍然是 HDP 的标准)。仅将 Dirichlet 关键字与词频进行比较,希望生成的是与文本的整体主题更相关的关键字列表,而不仅仅是最常见的词。

    显然,根据文本连贯性百分比从主题中选择有序词并不能按重要性对关键字进行整体排序,因为稍后会选择一些在整体连贯性较低的主题中非常重要的词。

    使用LDA为语料库中的单个文本生成关键字的过程可以在this answer找到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-18
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多