【问题标题】:How do I pull key words (not most frequent words) out of a corpus using Python and NLTK?如何使用 Python 和 NLTK 从语料库中提取关键词(不是最常见的词)?
【发布时间】:2017-04-30 21:08:05
【问题描述】:

我正在尝试从文本或语料库中提取关键词。这些不是最常见的词,而是最“关于”文本的词。我有一个比较示例,我生成的列表与示例列表非常不同。您能否给我一个指导,以生成一个很好的关键词列表,其中不包括像“thou”和“tis”这样的低含义词?

我使用“罗密欧与朱丽叶”作为我的文本。我的方法(参见下面的 Scott & Tribble)是将 R&J 与莎士比亚的完整戏剧进行比较,并找出 R&J 中出现频率明显高于完整戏剧的单词。这应该清除像“the”这样的常用词,但在我的代码中它没有。

我得到了很多像“thou”、“she”和“tis”这样的词,它们没有出现在他们的列表中,而且我没有得到像“banished”和“churchyard”这样的词。我得到了“romeo”、“juliet”、“capulet”和“nurse”,所以如果不是真的在正确的轨道上,我至少接近正确的轨道。

这是从文本中提取单词和百分比的函数:

def keywords(corpus, threshold=0):
    """ generates a list of possible keywords and the percentage of 
           occurrences.
          corpus (list): text or collection of texts
          threshold (int): min # of occurrences of word in corpus                    
              target text has threshold 3, ref corp has 0
          return percentKW: list of tuples (word, percent)                         
    """

    # get freqDist of corpus as dict. key is word, value = # occurences
    fdist = FreqDist(corpus)
    n = len(corpus)

    # create list of tuple of w meeting threshold & sort w/most common first
    t = [(k, v) for k, v in fdist.items() if v >= threshold]
    t = sorted(t, key=lambda tup: tup[1], reverse=True)

    # calculate number of total tokens
    n = len(corpus)

    # return list of tuples (word, percent word is of total tokens)
    percentKW =[(k, '%.2f'%(100*(v/n))) for k, v in t]
    return percentKW

这是调用代码的关键部分。 targetKW 是 R&J,refcorpKWDict 是完整的莎士比亚戏剧。

# iterate through text list of tuples
for w, p in targetKW:
    # for each word, store the percent in KWList
    targetPerc = float(p)
    refcorpPerc = float(refcorpKWDict.get(w, 0))
    # if % in text > % in reference corpus
    if (refcorpPerc or refcorpPerc == 0) and (targetPerc > refcorpPerc):
        diff = float('%.2f'%(targetPerc - refcorpPerc))
        # save result to KWList
        KWList.append((w, targetPerc, refcorpPerc, diff))        

这是我迄今为止尝试过的:

  • 将所有可能的关键字标准化为小写(帮助)
  • 创建了自定义的关键词短列表(文本和比较文本)。似乎有效,但没有告诉我任何信息
  • 将 R&J 与剧本删减列表、剧本 + 十四行诗和布朗语料库进行比较(没有帮助)
  • 检查了潜在关键词的百分比,例如“放逐”。百分比远低于预期。我不知道如何解释。
  • 设置潜在关键词的最小长度以消除诸如“ll”和“is”之类的词(帮助)
  • 用谷歌搜索了这个问题。 (找不到任何东西)

我正在使用 IDLE 版本 3.5.2 在 Windows 10 上使用 Python 3.5.2。

来源: 在“使用 Python 进行自然语言处理”(http://www.nltk.org/book/)中,练习 4.24 是“阅读‘关键字链接’((Scott & Tribble, 2006)的第 5 章)。从 NLTK 的莎士比亚语料库中提取关键字并使用 NetworkX 包,绘制关键字链接网络。”为了工作中的专业发展,我正在独自阅读这本书。 2006年参考的书是《文本模式:语言教育中的关键词和语料库分析》(尤其是第58-60页)

感谢您的宝贵时间。

【问题讨论】:

    标签: python nltk corpus


    【解决方案1】:

    我已经在为我正在进行的一个项目复习 TF-IDF,所以我们开始吧。代码本身基本上不需要 Pandas 或 Numpy 函数,尽管我强烈推荐 Pandas,因为我将它用作管理数据的首选。您需要 Scikit Learn 来进行 TFIDF 矢量化。如果您还没有得到它,您需要install it first。看起来只使用pip install scikit-learn[alldeps] 应该可以解决问题,但我个人使用的是Anaconda,它已经预先安装好了,所以我没有处理这方面的事情。我已经逐步分解了在罗密欧与朱丽叶中寻找重要术语的过程。解释每个对象的内容的步骤多于必要的步骤,但仅包含必要步骤的完整代码位于底部。

    一步一步

    from sklearn.feature_extraction.text import TfidfVectorizer
    
    # Two sets of documents
    # plays_corpus contains all documents in your corpus *including Romeo and Juliet*
    plays_corpus = ['This is Romeo and Juliet','this is another play','and another','and one more']
    
    #romeo is a list that contains *just* the text for Romeo and Juliet
    romeo = [plays_corpus[0]] # must be in a list even if only one object
    
    # Initialise your TFIDF Vectorizer object
    tfidf_vectorizer = TfidfVectorizer()
    
    # Now create a model by fitting the vectorizer to your main plays corpus. This is essentially an array of TFIDF scores.
    model =  tfidf_vectorizer.fit_transform(plays_corpus)
    

    如果你好奇,这就是数组的样子。每行代表您的语料库中的一个文档,而每列是按字母顺序排列的每个唯一术语。在这种情况下,行跨越两行,术语是 ['and', 'another', 'is', 'juliet', 'more', 'one', 'play', 'romeo', 'this' ].

    tfidf_vectorizer.fit_transform(plays_corpus).toarray()
    array([[ 0.33406745,  0.        ,  0.41263976,  0.52338122,  0.        ,
             0.        ,  0.        ,  0.52338122,  0.41263976],
           [ 0.        ,  0.46580855,  0.46580855,  0.        ,  0.        ,
             0.        ,  0.59081908,  0.        ,  0.46580855],
           [ 0.62922751,  0.77722116,  0.        ,  0.        ,  0.        ,
             0.        ,  0.        ,  0.        ,  0.        ],
           [ 0.41137791,  0.        ,  0.        ,  0.        ,  0.64450299,
             0.64450299,  0.        ,  0.        ,  0.        ]])
    

    接下来我们创建一个所有唯一terms 的列表(这就是我知道上述唯一术语的方式)。

    terms = tfidf_vectorizer.get_feature_names()
    

    所以现在我们有了 tfidf 分数的主要模型,它分别对每个文档中的每个词项进行评分,相对于其在其直接上下文(文档)和更大上下文(语料库)中的重要性。

    要了解具体在《罗密欧与朱丽叶》中的术语的得分是多少,我们现在使用我们的模型.transform 该文档。

    romeo_scored = tfidf_vectorizer.transform(romeo) # note .transform NOT .fit_transform
    

    这再次创建了一个数组,但只有一行(因为只有一个文档)。

    romeo_scored.toarray()
    array([[ 0.33406745,  0.        ,  0.41263976,  0.52338122,  0.        ,
             0.        ,  0.        ,  0.52338122,  0.41263976]])
    

    我们可以很容易地将这个数组转换成一个分数列表

    # we first view the object as an array, 
    # then flatten it as the array is currently like a list in a list.
    # Then we transform that array object into a simple list object.
    scores = romeo_scored.toarray().flatten().tolist()    
    

    现在我们有一个模型中的术语列表,以及每个术语的分数列表,专门针对罗密欧与朱丽叶。这些有用的顺序是相同的,这意味着我们可以将它们放在一起形成一个元组列表。

    data = list(zip(terms,scores)
    
    # Which looks like
    [('and', 0.3340674500232949),
     ('another', 0.0),
     ('is', 0.41263976171812644),
     ('juliet', 0.5233812152405496),
     ('more', 0.0),
     ('one', 0.0),
     ('play', 0.0),
     ('romeo', 0.5233812152405496),
     ('this', 0.41263976171812644)]
    

    现在我们只需要按分数排序就可以得到最上面的项目

    # Here we sort the data using 'sorted',
    # we choose to provide a sort key,
    # our key is lambda x: x[1]
    # x refers to the object we're processing (data)
    # and [1] specifies the second part of the tuple - the score.
    # x[0] would sort by the first part - the term.
    # reverse = True switches from Ascending to Descending order.
    
    sorted_data = sorted(data, key=lambda x: x[1],reverse=True)
    

    最后,毕竟这给了我们......

    [('juliet', 0.5233812152405496),
     ('romeo', 0.5233812152405496),
     ('is', 0.41263976171812644),
     ('this', 0.41263976171812644),
     ('and', 0.3340674500232949),
     ('another', 0.0),
     ('more', 0.0),
     ('one', 0.0),
     ('play', 0.0)]
    

    您可以通过切片列表将其限制为前 N 个术语。

    sorted_data[:3]
    [('juliet', 0.5233812152405496),
     ('romeo', 0.5233812152405496),
     ('is', 0.41263976171812644)]
    

    完整代码

    from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer
    
    # Two sets of documents
    # plays_corpus contains all documents in your corpus *including Romeo and Juliet*
    plays_corpus = ['This is Romeo and Juliet','this is another play','and another','and one more']
    
    #romeo is a list that contains *just* the text for Romeo and Juliet
    romeo = [plays_corpus[0]] # must be in a list even if only one object
    
    # Initialise your TFIDF Vectorizer object
    tfidf_vectorizer = TfidfVectorizer()
    
    # Now create a model by fitting the vectorizer to your main plays corpus, this creates an array of TFIDF scores
    model = tfidf_vectorizer.fit_transform(plays_corpus)
    
    romeo_scored = tfidf_vectorizer.transform(romeo) # note - .fit() not .fit_transform
    
    terms = tfidf_vectorizer.get_feature_names()
    
    scores = romeo_scored.toarray().flatten().tolist()
    
    data = list(zip(terms,scores))
    
    sorted_data = sorted(data,key=lambda x: x[1],reverse=True)
    
    sorted_data[:5]
    

    【讨论】:

    • 再次感谢詹姆斯。我看到你的代码中最重要的词是“juliet”、“romeo”、“is”、“this”、“and”、“another”、“more”、“one”和“play”。我实际上从我的代码中获得了更多“关于”的关键词。我的前 9 名是“romeo”、“juliet”、“thou”、“capulet”、“nurse”、“and”、“love”、“friar”、“lady”和“that”。然而,在我的前 20 名中还有“that”、“thy”、“she”、“with”、“thee”和“what”。 Scott & Tribble 书中的代码没有像这样的通用词,我不知道他们是如何筛选出来的。也许我只是对我的代码的期望不切实际。想法?
    • 也许可以调整矢量化器中的一些选项,请参阅scikit-learn.org/stable/modules/generated/…。也许您可以提出一个适合语料库的停用词列表,然后将其提供给矢量化器。如果不是,您可以创建一个标记器函数来过滤停止,然后将其用作自定义标记器而不是内置标记器,这是矢量化器中的另一个可选参数。您可能还会发现 ngram 选项很有用,因为它也会在术语列表中包含 ngram。最后尝试 sublinear_tf=True 看看是否有帮助。
    • @JenniferJames 另外max_df 可能有用。例如,将其设置为 max_df=0.3 意味着出现在语料库中超过 30% 的文档中的任何术语都将被忽略。
    • 将关键词与停用词列表进行比较有效。谢谢。
    【解决方案2】:

    两种可能有用的技术(并且可能会脱离书本)是词频逆文档频率(通常是 TFIDF)加权词...和搭配。

    与更大的相似文档语料库相比,TFIDF 用于确定文档中的重要词。它通常用作自动分类(情感分析等)的机器学习的初步准备

    TFIDF 本质上是查看整个戏剧语料库,并根据单词在每个戏剧中的重要性为每个单词实例分配一个值,并根据该词在整个语料库中的重要性进行加权。因此,您最好将您的 TFIDF 模型“拟合”到莎士比亚戏剧的整个语料库(包括罗密欧与朱丽叶),然后将罗密欧与朱丽叶“转换”为一系列单词分数。然后你会找到得分最高的词,这些词对罗密欧与朱丽叶来说是最重要的,在所有莎士比亚戏剧的背景下。

    我发现一些 TFIDF 指南很有帮助...

    https://buhrmann.github.io/tfidf-analysis.html

    http://www.ultravioletanalytics.com/2016/11/18/tf-idf-basics-with-pandas-scikit-learn/

    配置在 NLTK 中可用,并且相当容易实现。搭配寻找短语,通常一起出现的词。这些通常对于指示文本“关于”什么也很有用。 http://www.nltk.org/howto/collocations.html

    如果您对其中一种技术感兴趣,很乐意帮助编写代码。

    【讨论】:

    • 谢谢,詹姆斯。我查看了这两个 TFIDF 指南,看来要了解 TFIDF 我需要了解 pandas,要了解 pandas 我需要了解 NumPy。这是真的,还是我错过了什么?如果这是真的,我会花一些时间尝试学习它们,因为这看起来是一个非常好的解决方案。我知道搭配,并且实际上打算在练习的后面使用它们来创建关键字链接。
    • @JenniferJames Numpy 和 Pandas 是很棒的软件包,值得学习。事实上,你可以学习 Pandas,因为它在 Numpy 之上分层,所以它具有相同的功能,而且我发现它更易于访问。但是希望正如您将在我的其他答案中看到的那样,您不需要知道任何一个就可以进行一些基本的 TF-IDF 评分。
    【解决方案3】:

    你的代码的问题是你对你接受的“关键词”过于宽容:任何词在你的文本中的频率比参考语料库中的频率甚至一点都将是视为关键字。从逻辑上讲,这应该可以让您获得大约一半的文本中没有特殊地位的单词。

    if (refcorpPerc or refcorpPerc == 0) and (targetPerc > refcorpPerc):
        # accept it as a "key word"
    

    要使测试更具选择性,请选择更大的阈值或使用更智能的度量,例如“out of rank measure”(谷歌搜索),和/或对候选关键字进行排名并保持在列表的顶部,即相对频率增加最大的那些。

    【讨论】:

    • 我不确定我是否将阈值测试放在了正确的位置。我目前正在查看文本中出现超过 n 次的目标文本(“罗密欧与朱丽叶”)的所有单词,其中 n 是阈值。那是它应该去的地方吗?
    • 我指的阈值是上面sn-p中的>比较。 任何量的差异,无论多么微不足道,都足以使目标被接受为关键词。换句话说,this 阈值为 0。
    猜你喜欢
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-24
    • 2021-11-01
    • 2015-10-10
    • 2020-08-05
    • 1970-01-01
    相关资源
    最近更新 更多