如何使用Python和NLTK从语料库中提取关键词(不是最常用的词)?

时间:2017-04-27 15:48:51

标签: python nltk corpus

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

我正在使用“罗密欧与朱丽叶”作为我的文字。我的方法(参见下面的Scott& Tribble)是将R& J与莎士比亚的完整剧本进行比较,并将R& J中出现的单词与完整剧本相比更频繁地抽出。这应该排除像“the”这样频繁的单词,但在我的代码中却没有。

我收到的很多单词,如“你”,“她”和“tis”都没有出现在他们的名单上,而且我没有得到像“流放”和“教堂墓地”这样的词语。我正在获得“romeo”,“juliet”,“capulet”和“护士”,所以如果不是真正的话,我至少会接近正确的轨道。

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

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”(帮助)之类的词语
  • 用Google搜索问题。 (找不到任何东西)

我正在使用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页)

感谢您的时间。

3 个答案:

答案 0 :(得分:0)

可能有用的两种可能的技术(并且可能偏离书籍的过程)是术语频率反向文档频率(通常为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

如果您感兴趣的话,请尽快为代码提供帮助。

答案 1 :(得分:0)

我已经在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)

如果您有点好奇,这就是数组的样子。每行代表语料库中的文档,而每列都是按字母顺序排列的唯一术语。在这种情况下,行跨越两行,术语是['和','另一个','是',' juliet' ,'更多',' 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]

答案 2 :(得分:0)

您的代码存在的问题是您对“关键字”所接受的内容过于宽容:任何在文本中频率甚至稍微的单词都比参考语料库中的单词更大被视为关键字。从逻辑上讲,这应该可以解决大约一半在文本中没有特殊状态的单词。

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

为了使测试更具选择性,请选择更大的阈值或使用更智能的衡量标准,例如“排名测量”(google it),和/或对候选关键字进行排名,并将其保持在列表的顶部,即相对频率增幅最大的那些。