赞
踩
关键词是指能反映文本主题或者意思的词语,如论文中的Keyword字段。
关键词提取是文本挖掘领域一个很重要的部分,通过对文本提取的关键词可以窥探整个文本的主题思想,进一步应用于文本的推荐或文本的搜索。
常用的关键词提取算法:TF-IDF算法、TextRank算法
利用jieba进行关键字提取时,有两种接口。一个基于TF-IDF算法,一个基于TextRank算法。
TF-IDF算法,完全基于词频统计来计算词的权重,然后排序,再返回TopK个词作为关键字。
TextRank相对于TF-IDF,基本思路一致,也是基于统计的思想,只不过其计算词的权重时,还考虑了词的上下文(通过窗口滑动来实现),而且计算词的权重时,也考虑了相关联系词的影响。可以说,TextRank实际上是依据位置与词频来计算词的权重的。
下面,结合基于jieba源码,来分别解释两种算法的实现。
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)
TF−IDF=词频(TF)×逆文档频率(IDF)
可以看出TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语料库中出现次数成反比。一个词的TF-IDF值非常高,说明这个词比较少见,但是它在这篇文章中多次出现,那么这个词就非常可能是我们需要的关键词。
以文章《中国的蜜蜂养殖》为例,“蜜蜂”和“养殖”两个词的TF-IDF值都非常高,作为这篇文章的关键词实际上看也是非常合适的。另外“中国”这个词虽然在文章中的词频并不低“蜜蜂”和“养殖”低,但因为它在整个语料库中经常出现,导致IDF值非常低,所以不会作为文章的关键词。
import jieba.analyse
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’, ‘服务’, ‘直播’, ‘产品’, ‘企业’, ‘安全’, ‘视频’, ‘移动’, ‘应用’, ‘网络’, ‘行业’, ‘游戏’, ‘机器人’, ‘电商’, ‘内容’, ‘中国’, ‘领域’, ‘通过’, ‘发展’]
jieba有统计好的idf值,在 jieba/analyse/idf.txt中。
劳动防护 13.900677652
生化学 13.900677652
奥萨贝尔 13.900677652
考察队员 13.900677652
岗上 11.5027823792
倒车档 12.2912397395
代码在 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]
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
TextRank采用图的思想,将文档中的词表示成一张无向有权图,词为图的节点,词之间的联系紧密程度体现为图的边的权值。计算词的权重等价于计算图中节点的权重。提取关键字,等价于找出图中权重排名TopK的节点。
如上图所示:有A B C D E五个词,词之间的关系使用边连接起来,词之间连接的次数作为边的权值。比如:A和C一起出现了2次,则其边的权重为2,A与B/C/E都有联系,而D仅与B有联系。
所以TextRank背后体现的思想为:与其他词关联性强的词,越重要。通俗一点就是:围着谁转,谁就重要。就像大家基本都会围着领导转一样。
图的构建分为两部分:
1)确认图的节点之间的联系
2)确认边的权值
基本思想:
算法论文: TextRank: Bringing Order into Texts
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'))))
中国 海军 训练 美国 部队 进行 官兵 航母 作战 任务 能力 军事 发展 工作 国家 问题 建设 导弹 编队 记者
---------------------我是分割线----------------
中国 海军 美国 部队 官兵 航母 军事 国家 任务 能力 导弹 技术 问题 日本 军队 编队 装备 系统 记者 战略
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
从算法原理上来看,基础都是词频统计,只是TD-IDF通过IDF来调整词频的权值,而TextRank通过上下文的连接数来调整词频的权值。TextRank通过滑动窗口的方式,来实现词的位置对词的权值的影响。
TD-IDF计算简单,运行性能更好。
参考网址:
使用python的jieba库中的TF-IDF算法进行关键词提取
【NLP】【三】jieba源码分析之关键字提取(TF-IDF/TextRank)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。