jieba关键词提取的源码解析

一、简介

1.1 什么是关键词

关键词是指能反映文本主题或者意思的词语,如论文中的Keyword字段。

关键词提取是文本挖掘领域一个很重要的部分,通过对文本提取的关键词可以窥探整个文本的主题思想,进一步应用于文本的推荐或文本的搜索。

常用的关键词提取算法:TF-IDF算法、TextRank算法

1.2 jieba关键词提取简介

利用jieba进行关键字提取时,有两种接口。一个基于TF-IDF算法,一个基于TextRank算法。

TF-IDF算法,完全基于词频统计来计算词的权重,然后排序,再返回TopK个词作为关键字。

TextRank相对于TF-IDF,基本思路一致,也是基于统计的思想,只不过其计算词的权重时,还考虑了词的上下文(通过窗口滑动来实现),而且计算词的权重时,也考虑了相关联系词的影响。可以说,TextRank实际上是依据位置与词频来计算词的权重的。

下面,结合基于jieba源码,来分别解释两种算法的实现。

二、基于 TF-IDF 算法的关键词抽取

2.1 算法原理

TF-IDF是关键词提取最基本、最简单易懂的方法。判断一个词再一篇文章中是否重要,一个最容易想到的衡量指标就是词频,重要的词往往在文章中出现的频率也非常高;但另一方面,不是出现次数越多的词就一定重要,因为有些词在各种文章中都频繁出现(例如:我们),那它的重要性肯定不如哪些只在某篇文章中频繁出现的词重要性强。从统计学的角度,就是给予那些不常见的词以较大的权重,而减少常见词的权重,最终得分较高的词语即为关键词。

TF和IDF计算公式如下:
词 频 ( T F ) = 某 个 词 在 文 章 中 出 现 的 次 数 文 章 的 总 词 数 词频(TF)=\frac{某个词在文章中出现的次数}{文章的总词数} TF=
逆 文 档 频 率 ( I D F ) = l o g ( 语 料 库 的 文 档 总 数 包 含 该 词 的 文 档 数 + 1 ) 逆文档频率(IDF)=log(\frac{语料库的文档总数}{包含该词的文档数+1}) IDF=log(+1)

一个词IDF值的计算是根据语料库得出的,如果一个词在语料库中越常见,那么分母就越大,IDF就越小越接近0。分母之所以要加1,是为了避免分母为0(即所有文档都不包含该词)。该词在语料库中越常见,IDF值越小。

最终得到TF-IDF值:
T F − I D F = 词 频 ( T F ) × 逆 文 档 频 率 ( I D F ) TF-IDF = 词频(TF)×逆文档频率(IDF) TFIDF=TF×IDF
可以看出TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语料库中出现次数成反比。一个词的TF-IDF值非常高,说明这个词比较少见,但是它在这篇文章中多次出现,那么这个词就非常可能是我们需要的关键词。

以文章《中国的蜜蜂养殖》为例,“蜜蜂”和“养殖”两个词的TF-IDF值都非常高,作为这篇文章的关键词实际上看也是非常合适的。另外“中国”这个词虽然在文章中的词频并不低“蜜蜂”和“养殖”低,但因为它在整个语料库中经常出现,导致IDF值非常低,所以不会作为文章的关键词。

2.2 使用及效果

import jieba.analyse

  • jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
    • sentence 为待提取的文本
    • topK 为返回几个 TF/IDF 权重最大的关键词,默认值为 20
    • withWeight 为是否一并返回关键词权重值,默认值为 False
    • allowPOS 仅包括指定词性的词,默认值为空,即不筛选
import jieba.analyse as analyse
import pandas as pd
df = pd.read_csv("./origin_data/technology_news.csv", encoding='utf-8')
df = df.dropna()
lines=df.content.values.tolist()
content = "".join(lines)
keywords = analyse.extract_tags(content, topK=30, withWeight=False, allowPOS=())
print(keywords)

[‘用户’, ‘2016’, ‘互联网’, ‘手机’, ‘平台’, ‘人工智能’, ‘百度’, ‘2017’, ‘智能’, ‘技术’, ‘数据’, ‘360’, ‘服务’, ‘直播’, ‘产品’, ‘企业’, ‘安全’, ‘视频’, ‘移动’, ‘应用’, ‘网络’, ‘行业’, ‘游戏’, ‘机器人’, ‘电商’, ‘内容’, ‘中国’, ‘领域’, ‘通过’, ‘发展’]

2.3 源码实现

idf.txt

jieba有统计好的idf值,在 jieba/analyse/idf.txt中。

劳动防护 13.900677652
生化学 13.900677652
奥萨贝尔 13.900677652
考察队员 13.900677652
岗上 11.5027823792
倒车档 12.2912397395

idf.txt 加载

代码在 jieba/analyse/tfidf.py

class IDFLoader(object):

    def __init__(self, idf_path=None):
        self.path = ""
        self.idf_freq = {}
        # 初始化idf的中位数值
        self.median_idf = 0.0
        if idf_path:
            # 解析idf.txt
            self.set_new_path(idf_path)

    def set_new_path(self, new_idf_path):
        if self.path != new_idf_path:
            self.path = new_idf_path
            content = open(new_idf_path, 'rb').read().decode('utf-8')
            self.idf_freq = {}
            # 解析 idf.txt,拿到词与idf的对应值,key = word,value = idf
            for line in content.splitlines():
                word, freq = line.strip().split(' ')
                self.idf_freq[word] = float(freq)
            # 取idf的中位数
            self.median_idf = sorted(
                self.idf_freq.values())[len(self.idf_freq) // 2]

利用tfidf算法提取关键字的接口:extract_tags

def extract_tags(self, sentence, topK=20, withWeight=False, allowPOS=(), withFlag=False):
        """
        Extract keywords from sentence using TF-IDF algorithm.
        Parameter:
            - topK: return how many top keywords. `None` for all possible words.
            - withWeight: if True, return a list of (word, weight);
                          if False, return a list of words.
            - allowPOS: the allowed POS list eg. ['ns', 'n', 'vn', 'v','nr'].
                        if the POS of w is not in this list,it will be filtered.
            - withFlag: only work with allowPOS is not empty.
                        if True, return a list of pair(word, weight) like posseg.cut
                        if False, return a list of words
        """
        # (1)中文分词
        # 判断提取出哪些词性的关键字
        if allowPOS:
            allowPOS = frozenset(allowPOS)
            # 如果需要提取指定词性的关键字,则先进行词性分割
            words = self.postokenizer.cut(sentence)
        else:
            # 如果提取所有词性的关键字,则使用精确分词
            words = self.tokenizer.cut(sentence)
        
        # (2)计算词频TF 
        freq = {}
        # 按照分词结果,统计词频
        for w in words:
            if allowPOS:
                if w.flag not in allowPOS:
                    continue
                elif not withFlag:
                    w = w.word
            wc = w.word if allowPOS and withFlag else w
            # 该词不能是停用词
            if len(wc.strip()) < 2 or wc.lower() in self.stop_words:
                continue
            #统计该词出现的次数
            freq[w] = freq.get(w, 0.0) + 1.0
        # 计算总的词数目
        total = sum(freq.values())
        # (3)计算IDF
        for k in freq:
            kw = k.word if allowPOS and withFlag else k
            # 依据tf-idf公式进行tf-idf值,作为词的权重。其中,idf是jieba通过语料库统计得到的
            freq[k] *= self.idf_freq.get(kw, self.median_idf) / total

        # (4)排序得到关键词集合
        # 对词频做个排序,获取TopK的词
        if withWeight:
            tags = sorted(freq.items(), key=itemgetter(1), reverse=True)
        else:
            tags = sorted(freq, key=freq.__getitem__, reverse=True)
        if topK:
            return tags[:topK]
        else:
            return tags

jieba实现tf-idf总结

  1. extract_tags()函数将原始文本作为输入,输出文本的关键词集合,代码大致分为四个部分:(1)中文分词 (2)计算词频TF (3)计算IDF (4)将所有词排序得到关键词集合。
  2. idf的值时通过语料库统计得到的,所以,实际使用时,可能需要依据使用环境,替换为使用对应的语料库统计得到的idf值。
  3. 需要从分词结果中去除停用词。
  4. 如果指定了仅提取指定词性的关键词,则词性分割非常重要,词性分割中准确程度,影响关键字的提取。
  5. 详见fxsjy/jieba文档。

三、基于 TextRank 算法的关键词抽取

3.1 算法原理

TextRank采用图的思想,将文档中的词表示成一张无向有权图,词为图的节点,词之间的联系紧密程度体现为图的边的权值。计算词的权重等价于计算图中节点的权重。提取关键字,等价于找出图中权重排名TopK的节点。
jieba关键词提取的源码解析_第1张图片
如上图所示:有A B C D E五个词,词之间的关系使用边连接起来,词之间连接的次数作为边的权值。比如:A和C一起出现了2次,则其边的权重为2,A与B/C/E都有联系,而D仅与B有联系。

所以TextRank背后体现的思想为:与其他词关联性强的词,越重要。通俗一点就是:围着谁转,谁就重要。就像大家基本都会围着领导转一样。

图的构建分为两部分:

1)确认图的节点之间的联系
2)确认边的权值

基本思想:

  • 将待抽取关键词的文本进行分词
  • 以固定窗口大小(默认为5,通过span属性调整),词之间的共现关系,构建图
  • 计算图中节点的PageRank,注意是无向带权图

算法论文: TextRank: Bringing Order into Texts

3.2 使用及效果

  • jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)) 直接使用,接口相同,注意默认过滤词性。
  • jieba.analyse.TextRank() 新建自定义 TextRank 实例
import jieba.analyse as analyse
import pandas as pd
df = pd.read_csv("./origin_data/military_news.csv", encoding='utf-8')
df = df.dropna()
lines=df.content.values.tolist()
content = "".join(lines)

print("  ".join(analyse.textrank(content, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'))))
print("---------------------我是分割线----------------")
print("  ".join(analyse.textrank(content, topK=20, withWeight=False, allowPOS=('ns', 'n'))))

中国 海军 训练 美国 部队 进行 官兵 航母 作战 任务 能力 军事 发展 工作 国家 问题 建设 导弹 编队 记者
---------------------我是分割线----------------
中国 海军 美国 部队 官兵 航母 军事 国家 任务 能力 导弹 技术 问题 日本 军队 编队 装备 系统 记者 战略

3.3 源码实现

def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):
        """
        Extract keywords from sentence using TextRank algorithm.
        Parameter:
            - topK: return how many top keywords. `None` for all possible words.
            - withWeight: if True, return a list of (word, weight);
                          if False, return a list of words.
            - allowPOS: the allowed POS list eg. ['ns', 'n', 'vn', 'v'].
                        if the POS of w is not in this list, it will be filtered.
            - withFlag: if True, return a list of pair(word, weight) like posseg.cut
                        if False, return a list of words
        """
        # 初始化关键字词性过滤条件
        self.pos_filt = frozenset(allowPOS)
        # 初始化一个无向权值图
        g = UndirectWeightedGraph()
        cm = defaultdict(int)
        # 使用精确模式进行分词
        words = tuple(self.tokenizer.cut(sentence))
        # 遍历分词结果
        for i, wp in enumerate(words):
            # 词wp如果满足关键词备选条件,则加入图中
            if self.pairfilter(wp):
                # span为滑动窗口,即词的上下文,借此来实现此的共现,完成词之间的连接。
                for j in xrange(i + 1, i + self.span):
                    if j >= len(words):
                        break
                    # 后向词也要满足备选词条件
                    if not self.pairfilter(words[j]):
                        continue
                    if allowPOS and withFlag:
                        # 共现词作为图一条边的两个节点,共现词出现的次数,作为边的权值
                        cm[(wp, words[j])] += 1
                    else:
                        cm[(wp.word, words[j].word)] += 1
        # 将 备选词和与该词连接的词加入到graph中,即完成graph的构造
        for terms, w in cm.items():
            g.addEdge(terms[0], terms[1], w)
        # 调用graph的rank接口,完成TextRank算法的计算,即计算出各节点的权重
        nodes_rank = g.rank()
        if withWeight:
            # 对graph中的阶段的权重进行排序
            tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)
        else:
            tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)

        if topK:
            return tags[:topK]
        else:
            return tags
def rank(self):
        ws = defaultdict(float)
        outSum = defaultdict(float)

        # 计算初始化节点的weight值
        wsdef = 1.0 / (len(self.graph) or 1.0)
        # 初始化各个节点的weight值,并计算各个节点的出度数目
        for n, out in self.graph.items():
            ws[n] = wsdef
            outSum[n] = sum((e[2] for e in out), 0.0)

        # this line for build stable iteration
        sorted_keys = sorted(self.graph.keys())
        # 循环迭代10,迭代计算出各个节点的weight值
        for x in xrange(10):  # 10 iters
            for n in sorted_keys:
                s = 0
                # 依据TextRank公式计算weight
                for e in self.graph[n]:
                    s += e[2] / outSum[e[1]] * ws[e[1]]
                ws[n] = (1 - self.d) + self.d * s

        (min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])

        for w in itervalues(ws):
            if w < min_rank:
                min_rank = w
            if w > max_rank:
                max_rank = w

        for n, w in ws.items():
            # to unify the weights, don't *100.
            ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)

        return ws

四、TF-IDF与TextRank算法的比较

  1. 从算法原理上来看,基础都是词频统计,只是TD-IDF通过IDF来调整词频的权值,而TextRank通过上下文的连接数来调整词频的权值。TextRank通过滑动窗口的方式,来实现词的位置对词的权值的影响。

  2. TD-IDF计算简单,运行性能更好。

参考网址:
使用python的jieba库中的TF-IDF算法进行关键词提取
【NLP】【三】jieba源码分析之关键字提取(TF-IDF/TextRank)

你可能感兴趣的:(自然语言处理,python,NLP,关键词提取,jieba,textrank)