关键词提取:TF-IDF和n-gram

Posted Terry_dong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关键词提取:TF-IDF和n-gram相关的知识,希望对你有一定的参考价值。

一:引导

目录

一:引导

二:内容预告

三:关键词提取的方法

01 TF-IDF

02.TextRank

03.文本聚类法

04.有监督的关键词提取

四:TF-IDF+n-gram提取关键词

01.英文新闻测试语料

02.需要的库

03.对新闻做单词拆分

04.n-gram的生成

05.n-gram的过滤

06.计算TF-IDF

07.完成关键词提取


搭档镇楼。

今天的头版给我漂亮的搭档,啥年芳二六、待字闺中之类的矫情话就不说了,希望看到文章的小伙子,如果对眼,请放下你手中的游戏,我可以牵线搭桥。

好好相爱,就是为民除害。

搭档是重庆妹纸,重庆妹纸长得是很水灵。

搭档给我的感觉是情商比较高,比较会捧哏,说话不会闷。

搭档身高160体重100,学历本科水瓶座,目前在重庆的银行工作。

以下为搭档的自我介绍:

性格慢热,绝不随便。

心里住着公主,也住着女王。

能吃苦下田种地,也能温书习字梳妆。

喜欢跳伞游泳潜水冲浪,就差一个男生一起去体验。

家里两房一车,父母事业单位就职,性格开明。

希望男生干净、上进,五官端正,性格开朗健谈,收入稳定能养家,家庭和谐,会做饭更佳。

二:内容预告

 

最近公司要做英语新闻推荐系统,需要对新闻内容做分析,主要包括网页去重、实体抽取和关键词提取。

提到关键词提取,大家很容易就能想到TF-IDF和TextRank。这两种方法可以分别通过调sklearn和jiabe的包来实现。

可如果要提取的是几个单词组成的短语呢?

这时候按标准的调包教程来做,是难以满足业务需要的。

所以本文探索,如何把TF-IDF和n-gram结合,用来提取短语。

本文关注以下三个问题:

  • 关键词提取有哪些方法?

  • 如何把TF-IDF和n-gram结合?

  • 如何根据词性和停用词过滤?

三:关键词提取的方法

 

01 TF-IDF

 

TF-IDF在nlp领域无人不知无人不晓,思想简单却有效,荣获nlp界的诺贝尔奖:奥卡姆剃刀奖。

TF(term frequency),即词频,用来衡量词在一篇文档中的重要性,词频越高,越重要。计算公式为:

某文档中某词出现的次数该文档的总词数

IDF((inverse document frequency),叫做逆文档频率,衡量某个词在所有文档集合中的常见程度。

当包含某个词的文档的数量越多时,这个词也就烂大街了,重要性越低。计算公式为:

全部文档的数量包含某词的文档的数量

于是TF-IDF = TF * IDF, 它表明字词的重要性与它在某篇文档中出现的次数成正比,与它在所有文档中出现的次数成反比。

使用TF-IDF的一个假设前提是:已经去掉了停用词。

TF-IDF的优点是计算速度快,结果稳健。

需要输入多篇文档,可以输出每篇文档的关键词。

02.TextRank

TextRank基于图计算来提取关键词,需要进行迭代,速度比TF-IDF慢,但不需要通过输入多篇文档来提取关键词。

TextRank是把一篇文档构建成无向图,图中的节点就是词语,图上的边就是共现词之间的连接。

共现词通过滑动窗口来确定,共现词之间用边相连,而边上的权重可以使用共现词的相似度。

jieba提供了用TextRank提取关键词的函数,但是边的权重是词共现的频率。

这样做其实比较粗糙,我们可以使用基于词向量计算的相似度得分作为权重,来进行迭代。
我这不知不觉又给自己安排了任务,文章名字都想好了:
《TextRank提取关键词:我也是改过jieba源码的人!》

TextRank也需要先去掉停用词。

03.文本聚类法

TF-IDF只从浅层的词频角度挖掘关键词,而通过Kmeans或Topic Model,可以从深层的隐含语义角度来提取关键词。

一种方法是通过Kmeans来提取,使用词向量作为特征。

比如对于一篇文档,我们要提取10个关键词。

那么可以把文档中的词聚成5类,然后取每个类中,与类中心最近的2个点,作为关键词。

也可以直接聚成10类,取和类中心最近的词。

另一种方法是通过Topic Model来提取,比如LSA和LDA,使用词频矩阵作为特征。

比如对于包含多篇文档的单领域语料,我们要挖掘关键词,整理词库。

那么可以用LDA进行聚类,得到每个主题的单词分布,再取出每个主题下排名靠前的topk个单词,或者权重高于某个阈值的单词,构成关键词库。

主题的单词分布为:

(0, '0.025*"基金" + 0.020*"分红" + 0.007*"中" + 0.006*"考试" + 0.006*"私募" + 0.005*"公司" + 0.004*"采用" + 0.004*"市场" + 0.004*"游戏" + 0.004*"元"')

(1, '0.007*"套装" + 0.007*"中" + 0.006*"设计" + 0.004*"元" + 0.004*"拍摄" + 0.003*"万" + 0.003*"市场" + 0.003*"编辑" + 0.003*"时尚" + 0.003*"穿"')

(2, '0.007*"英寸" + 0.005*"中" + 0.004*"中国" + 0.003*"拍摄" + 0.003*"比赛" + 0.002*"高清" + 0.002*"小巧" + 0.002*"产品" + 0.002*"亿股" + 0.002*"机型"')

(3, '0.082*"基金" + 0.015*"公司" + 0.014*"市场" + 0.014*"投资" + 0.009*"股票" + 0.007*"亿元" + 0.007*"中" + 0.006*"收益" + 0.006*"行业" + 0.006*"一季度"')
...

 

04.有监督的关键词提取

有监督的方法需要有标注的数据,我没有尝试过。

看一些文章说可以转化为统计机器翻译(SMT)的问题,转化为序列标注(NER)的问题,或者转化为词语排序(LTR)的问题。

我只理解了转化为序列标注问题的做法,这个和用深度学习做文本摘要类似。

文档中的词语,如果为关键词,则标注为1,否则标注为0,也就是对一个词进行二分类。

据说效果比上述无监督的方法好。

四:TF-IDF+n-gram提取关键词

提取单词作为关键词,比较容易实现,如果要提取短语呢?

我尝试了开源工具RAKE,它是根据停用词来划分句子,再提取短语的。

使用之后,我发现RAKE存在两个问题:

一是提取的短语有些长达4-5个单词,这显然不合适;

二是没有根据词性进行过滤。

当然,我们可以用RAKE得到一个粗糙的结果,然后再做细致的处理,比如根据包含单词的数量、根据词性模板等进行过滤。

不过RAKE的代码写得实在太乱了,我没有耐心看下去,对其原理也不太了解,也就没加工再利用。

我给出的方案是TF-IDF结合n-gram来提取关键短语,并根据单词长度、停用词和词性进行过滤。

01.英文新闻测试语料

我去某网站下载了4篇英语新闻,对于其中的一篇,经过增加一段和两段、删除一段和两段的操作,得到4篇内容高度重合的新闻,最终得到8篇英语新闻。

├── english_new_1.txt
├── english_new_2.txt
├── english_new_3.txt
├── english_new_add_1.txt       # 增加一段
├── english_new_add_2.txt       # 增加两段
├── english_new_base.txt        # 原始新闻
├── english_new_remove_1.txt    # 删除一段
└── english_new_remove_2.txt    # 删除两段

首先用SimHash去重,保留4篇英语新闻(为了测试SimHash)。

02.需要的库

TF-IDF的计算,用sklearn的包。

由于是英文文本的处理,所以需要用NLTK做英文单词拆分、词性标注和词形还原。

#coding:utf-8
import os,re
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
from itertools import chain

from nltk import pos_tag, word_tokenize,sent_tokenize
from nltk.stem import WordNetLemmatizer

""" 一:初始化词形还原的类 """
lemmatizer = WordNetLemmatizer()

为什么需要做词形还原呢?

词形还原是指把英文的单词,从复数形态、第三人称形态等复杂形态,转换为最基础的形态,比如 salaries 还原为salary,makes还原为make。

如果我们提取的关键词是单个词,那么在使用TF-IDF进行提取之前,先要对每个单词做词形还原,不然salaries和salary会被认为是不同的两个单词。

词形还原是基于词典的,准确率比较高,所以使用NLTK做词形还原时,需要下载数据 WordNet。

在公司很容易出现网络不通的问题,反正我是下载不了。

 

nltk.download('wordnet')

[nltk_data] Error loading wordnet: <urlopen error [Errno 104]
[nltk_data]     Connection reset by peer>
False

 

其实不止词形还原需要下载数据,做词性标注和实体识别,都需要下载,特别麻烦。

于是我干脆把NLTK的所有数据文件都下载了,文件大小为1.08G,解压后放到相应的路径,就一劳永逸了。

下载链接:

https://pan.baidu.com/s/1Ms4tfGlF3IA6F0Mg5Ljd8Q

提取码:

07qb

下载好后解压,在ubuntu环境下,给文件赋予相应的可执行权限(Goup和Others也需要可执行权限),然后把数据文件复制到以下路径:

 

  Searched in:
  
    - '/opt/anaconda3/nltk_data'
    - '/opt/anaconda3/share/nltk_data'
    - '/opt/anaconda3/lib/nltk_data'
    - '/usr/share/nltk_data'
    - '/usr/local/share/nltk_data'
    - '/usr/lib/nltk_data'
    - '/usr/local/lib/nltk_data'

 

03.对新闻做单词拆分

最好不要对整篇文档做 word_tokenize,而是先 sent_tokenize(划分句子),再word_tokenize。

因为实体识别、词性标注和句法分析,最好以句子为单位来做。

 

""" 一:对文档进行分词/拆字 """
def tokenize_doc(docs):
    """
    :params: docs——多篇文档
    """
    docs_tokenized = []
    for doc in docs:
        doc = re.sub('[\\n\\r\\t]+',' ',doc)

        """ 1:分句 """
        sents = sent_tokenize(doc)

        """ 2:单词拆分 """
        sents_tokenized = [word_tokenize(sent.strip()) for sent in sents]
        docs_tokenized.append(sents_tokenized)

    return docs_tokenized

 

04.n-gram的生成

sklearn的TfidfVectorizer的类里边,也提供了n-gram的功能,那为什么我还要自己生成呢?

原因是sklearn内置的功能里,生成n-gram后,就直接计算TF-IDF了,没根据停用词和词性,过滤n-gram。

而根据停用词和词性过滤n-gram,必须在计算TF-IDF之前。

那在tokenize的时候做过滤不就得了吗?

不行!

过滤n-gram,是指如果n-gram中,有一个单词是停用词,或者有一个单词的词性是过滤的词性,那么整个n-gram都需要去掉,而不能只去掉该单词。

在tokenize时过滤单词,那么生成的n-gram短语块可能是错误的。

 

""" 二:产生n-gram,用于提取短语块 """
def gene_ngram(sentence,n=3,m=2):
    """
    ----------
    sentence: 分词后的句子
    n: 取3,则为3-gram
    m: 取1,则保留1-gram
    ----------
    """
    if len(sentence) < n:
        n = len(sentence)

    ngrams = [sentence[i-k:i] for k in range(m, n+1) for i in range(k, len(sentence)+1)]
    return ngrams

 

05.n-gram的过滤

生成的trigram如下:

[['What', 'are', 'the'], ['are', 'the', 'legal'], ['the', 'legal', 'implications'], ['legal', 'implications', 'of'],

我们要对n-gram进行过滤,根据停用词、词性、单词的长度以及是否包含数字,来过滤。

词性可以去网上找英文词性对照表。

如果n-gram中,包含长度为1的单词,那么过滤掉。

 

""" n-gram中是否有单词长度为1 """
def clean_by_len(gram):

    for word in gram:
        if len(word) < 2:
            return False

    return True


""" 三:按停用词表和词性,过滤单词 """
def clean_ngrams(ngrams):
    """
    :params: ngrams
    """
    stopwords = open("./百度英文停用词表.txt",encoding='utf-8').readlines()
    stopwords = [word.strip() for word in stopwords]
    pat = re.compile("[0-9]+")

    """ 如果n-gram中有停用词,则去掉 """
    ngrams = [gram for gram in ngrams if len(set(stopwords).intersection(set(gram)))==0]

    """ 如果n-gram中有数字,则去掉 """
    ngrams = [gram for gram in ngrams if len(pat.findall(''.join(gram).strip()))==0]    

    """ n-gram中有单词长度为1,则去掉 """
    ngrams = [gram for gram in ngrams if clean_by_len(gram)]

    """ 只保留名词、动词和形容词 """
    allow_pos_one = ["NN","NNS","NNP","NNPS"]
    allow_pos_two = ["NN","NNS","NNP","NNPS","JJ","JJR","JJS"]
    allow_pos_three = ["NN","NNS","NNP","NNPS","VB","VBD","VBG","VBN","VBP","VBZ","JJ","JJR","JJS"]

    ngrams_filter = []
    for gram in ngrams:
        words,pos = zip(*pos_tag(gram))

        """ 如果提取单词作为关键词,则必须为名词 """
        if len(words) == 1:
            if not pos[0] in allow_pos_one:
                continue   
            ngrams_filter.append(gram)

        else:
            """ 如果提取短语,那么开头必须为名词、动词、形容词,结尾为名词 """
            if not (pos[0] in allow_pos_three and pos[-1] in allow_pos_one):
                continue  
            ngrams_filter.append(gram)

    return ngrams_filter

 

06.计算TF-IDF

用上面的函数,完成生成n-gram、过滤n-gram的步骤,然后把n-gram之间的单词用下划线连接:"_" ,n-gram之间用空格连接。

 

    """ 1:处理为n-gram,n_=2或3 """
    docs_ngrams = [gene_ngram(doc,n=n_,m=n_) for doc in docs_tokenized]

    """ 2: 按停用词表和词性,过滤 """
    docs_ngrams = [clean_ngrams(doc) for doc in docs_ngrams]

    docs_ = []
    for doc in docs_ngrams:
        docs_.append(' '.join(['_'.join(ngram) for ngram in doc]))

 

得到的n-gram如下:

['face_tough_times Hiring_activity_declines reveals_Naukri_JobSpeak health_system_preparedness micro-delivery_startup_DailyNinja ...]

 

为什么n-gram之间的单词用下划线来连接呢?

我试了其他的符号:#、=,但是发现送入sklearn的包里计算时,n-gram会被重新拆分为单词,vocab不是n-gram短语,而是单词。

我估计是因为python中,下划线比较特殊,如正则表达式 \\w,表示字母、数字和下划线,其他的符号则容易被视为文本噪音而去掉。

接着计算n-gram的TF-IDF,这里又有一个坑:如果不自己指定n-gram字典,那么sklearn自己构建的字典中,可能会有单词或单词加下划线这种奇怪的东西。

所以需要自己传入vocab。

""" 四:获取 tf-idf 特征 """
def calcu_tf_idf(documents):
    """
    :param: data为列表格式的文档集合, 计算 tf_idf 特征
    """

    """ 指定vocab,否则n-gram的计算会出错 """
    vocab = set(chain.from_iterable([doc.split() for doc in documents]))

    vec = TfidfVectorizer(vocabulary=vocab)
    D = vec.fit_transform(documents)
    voc = dict((i, w) for w, i in vec.vocabulary_.items())

    features = 
    for i in range(D.shape[0]):
        Di = D.getrow(i)
        features[i] = list(zip([voc[j] for j in Di.indices], Di.data))

    return features

 

07.完成关键词提取

接着,就可以完成关键词的提取了。

考虑到关键词提取的全面性,分别提取unigram、bigram和trigram关键词。

如果提取unigram关键词,那么需要做词形还原。

def get_ngram_keywords(docs_tokenized,topk=5,n_=2):

    """ 1:处理为n-gram """
    docs_ngrams = [gene_ngram(doc,n=n_,m=n_) for doc in docs_tokenized]

    """ 2: 按停用词表和词性,过滤 """
    docs_ngrams = [clean_ngrams(doc) for doc in docs_ngrams]

    docs_ = []
    for doc in docs_ngrams:
        docs_.append(' '.join(['_'.join(ngram) for ngram in doc]))

    """ 3: 计算tf-idf,提取关键词 """
    features = calcu_tf_idf(docs_)

    docs_keys = []
    for i,pair in features.items():
        topk_idx = np.argsort([v for w,v in pair])[::-1][:topk]
        docs_keys.append([pair[idx][0] for idx in topk_idx])

    return [[' '.join(words.split('_')) for words in doc ]for doc in docs_keys] 


""" 五:抽取n-gram关键词 """
def get_keywords(docs_tokenized,topk):

    """ 1: 英文单词拆分 """  
    docs_tokenized = [list(chain.from_iterable(doc)) for doc in docs_tokenized]

    """ 2: 提取关键词,包括unigram,bigram和trigram """
    docs_keys = []
    for n in [1,2,3]:
        if n == 1:
            """ 3: 如果是unigram,还需要做词形还原 """
            docs_tokenized = [[lemmatizer.lemmatize(word) for word in doc] for doc in docs_tokenized]

        keys_ngram = get_ngram_keywords(docs_tokenized, topk,n_=n)
        docs_keys.append(keys_ngram)

    return [uni+bi+tri for uni,bi,tri in zip(*docs_keys)]

 

ok,来看关键词提取的结果。

选取其中一篇新闻,关于新冠肺炎禁闭防范期间,降薪和裁员的影响。

""" 标题:在冠状病毒禁闭期间裁员或减薪有什么法律影响 """
What are the legal implications of layoffs or salary cuts during coronavirus lockdown

以下是提取的unigram、bigram和trigram关键词。

从关键词中,大致可以看出是关于降薪、削减成本、劳动者保护、公司决策。

遗憾的是,冠状病毒(Coronavirus)这个词没有提取出来,但是通过实体识别抽取了出来。

 

'keywords': ['employee',                 # 雇员
             'order',                    # 订货
             'employer',                 # 雇主
             'cut',                      # 削减
             'lockdown',                 # 一级防范禁闭(期)
             'scenario',                 # 方案
             'time',                     # 时期
             'salary',                   # 薪酬
             'organisation',             # 组织
             'startup',                  # 创业公司
             'cost reduction',           # 成本削减
             'legal implications',       # 法律影响
             'salary cuts',              # 降薪
             'employer beware',          # 雇主品牌
             'discretionary spending',   # 可自由支配的个人开支
             'activity declines',        # 活动减少
             'recommended philosophy',   # 被推荐的方式
             'practice group',           # 业务部门
             'population density',       # 人口密度
             'pay scales',               # 工资标准
             'advising startup founders',  # 建议创业者
             'broader startup ecosystem',  # 更广泛的创业生态系统
             'employers make payment',     # 雇主支付
             'ensure legal protection',    # 确保法律保护
             'face tough times',           # 面临艰难时期
             'garner sufficient caution',  # 获得足够的关注
             'health system preparedness', # 卫生系统的准备
             'lower pay scales',           # 更低的工资标准
             'seek legal advice',          # 寻求法律咨询
             'top management level']       # 公司高层

时间紧,关键词提取做得还不够深入,没有尝试更多方法。

写这篇文章,是为了整理思路,希望有小伙伴可以一起交流有效的提取短语的方法。

 

以上是关于关键词提取:TF-IDF和n-gram的主要内容,如果未能解决你的问题,请参考以下文章

Python中N-Gram、tf-idf和余弦相似度的简单实现

在python中使用sklearn为n-gram计算TF-IDF

关键字提取算法TF-IDF和TextRank(python3)————实现TF-IDF并jieba中的TF-IDF对比,使用jieba中的实现TextRank

自然语言处理--TF-IDF(关键词提取)

R语言文本挖掘tf-idf,主题建模,情感分析,n-gram建模研究

关键词提取TF-IDF