关键字抽取算法
TF-IDF
TF-IDF称为词频逆文本,结果严重依赖文本分词之后的效果。其公式又可以分成词频(Term Frequency,TF)的计算和逆文档概率(IDF)的计算。
TF的计算较为简单,统计每个词出现的频次。计算公式如式1所示:
T
F
=
某个词在文档中出现的次数
该文档的总词数
TF=\frac{某个词在文档中出现的次数}{该文档的总词数}
TF=该文档的总词数某个词在文档中出现的次数
这里需要注意的是,该词的词频仅仅是计算在单一文档的词频,而不是所有文档出现的词频。也就是说每个词在不同的文档中出现的词频可能是不一样的。
IDF的计算稍显复杂,统计该词在所有文档出现的次数。也就是说如果该词仅在少数领域文档中出现,那么该词就在该领域更具有代表性,更可能是关键词。计算公式如式2所示:
I
D
F
=
l
o
g
(
语料库的文档总数
存在该词的文档数
+
1
)
IDF=log(\frac{语料库的文档总数}{存在该词的文档数+1})
IDF=log(存在该词的文档数+1语料库的文档总数)
TF-IDF的计算也就是两者的乘积,具体如下式3所示:
T
F
−
I
D
F
=
T
F
∗
I
D
F
TF-IDF=TF*IDF
TF−IDF=TF∗IDF
jieba中竟然实现了TF-IDF关键字抽取,使用如下:
import sys
import jieba
import jieba.analyse
import argparse
def parser_args():
parser = argparse.ArgumentParser(description='parse input information')
parser.add_argument('--filename', default='./data/files.txt', type=str)
parser.add_argument('--top_k', default=5, type=int)
args = parser.parse_args()
return args
args = parser_args()
content = open(args.filename, 'rb').read()
jieba.analyse.set_idf_path("./data/idf.txt.big.txt")
tags = jieba.analyse.extract_tags(content, topK=args.top_k)
print(",".join(tags))
另外自己实现了一份源码,
import jieba
import math
def read_file(filepath):
with open(filepath, 'r', encoding='utf-8') as file:
lines = file.readlines()
return lines
def load_stop_word(filepath):
with open(filepath, 'r', encoding='utf-8') as file:
data = file.readlines()
stop_words = set()
for word in data:
stop_words.add(word.strip())
return list(stop_words)
def segment_line(line, stop_words):
# start paddle segment
jieba.enable_paddle()
words_list = jieba.cut(line.strip(), use_paddle=True)
results = []
for word in words_list:
if word not in stop_words:
results.append(word)
return results
def cal_term_frequency(lines_results):
lines_tf = []
for line_result in lines_results:
temp_tf = {}
for word in line_result:
if word in temp_tf.keys():
temp_tf[word] += 1
else:
temp_tf[word] = 1
lines_tf.append(temp_tf)
return lines_tf
def cal_idf(lines_tf):
idf = {}
for line_tf in lines_tf:
for word in line_tf.keys():
if word not in idf.keys():
idf[word] = 1
else:
idf[word] += 1
return idf
def cal_tf_idf(lines_tf, idf):
lines_tf_idf = []
for line_tf in lines_tf:
temp_tf_idf = {}
for word, tf in line_tf.items():
temp_tf_idf[word] = tf * math.log(len(lines_tf) / (idf[word] + 1))
lines_tf_idf.append(temp_tf_idf)
return lines_tf_idf
TextRank
TextRank是一种基于图的排序算法,其思想来自于PageRank算法。TextRank算法有一篇较为经典的老论文( TextRank: Bringing Order into Texts),值得一读。
PageRank算法
PageRank最重要的也就是考虑了网络节点的出度(在有向图上),相对于其他的Rank算法。当然,PageRank也可以用于无向图,这就导致节点的出度等于入度,效果可能会稍微差点。其计算公式如下式4所示。
S
(
V
i
)
=
(
1
−
d
)
+
d
∗
∑
j
∈
I
n
(
V
i
)
1
∣
O
u
t
(
V
j
)
∣
S
(
V
j
)
S(V_i)=(1-d)+d*\sum_{j\in{In(V_i)}}\frac{1}{|Out(V_j)|}S(V_j)
S(Vi)=(1−d)+d∗j∈In(Vi)∑∣Out(Vj)∣1S(Vj)
其中各个参数含义如下:
S ( V i ) S(V_i) S(Vi):网页 V i V_i Vi的重要度(权重),初始值默认为1。
d d d:阻尼系数,一般为0.85,也就是看当前节点改变的自身的状态权重。
I n ( V i ) In(V_i) In(Vi):能跳转到网页 V i V_i Vi的页面,在图中对应入度对应的点。
O u t ( V j ) Out(V_j) Out(Vj):网页 V j V_j Vj能够跳转到的页面,在图中对应的出度的点。
TextRank关键字提取
提取关键词也可以采取和“网页中选哪个网页比较重要”类似的方法,只需要想办法把图构建出来。图的结点是“词”。把文章拆成句子,每个句子再拆成词,以词为结点。那么边如何定义呢?这里就可以利用n-gram的思路,简单来说,某个词,只与它附近的n个词有关,即与它附近的n个词对应的结点连一条无向边(两个有向边)。另外,还可以做一些操作,比如把某类词性的词删掉,一些自定义词删掉,只保留一部分单词,只有这些词之间能够连边。也就是TextRank的计算方法如下式5所示。
W
S
(
V
i
)
=
(
1
−
d
)
+
d
∗
∑
V
j
∈
I
n
(
V
i
)
w
j
i
∑
V
k
∈
O
u
t
(
V
j
)
W
S
(
V
j
)
WS(V_i)=(1-d)+d*\sum_{V_j \in{In(V_i)}}\frac{w_{ji}}{\sum_{V_k\in{Out(V_j)}}}WS(V_j)
WS(Vi)=(1−d)+d∗Vj∈In(Vi)∑∑Vk∈Out(Vj)wjiWS(Vj)
TextRank文章摘要提取
TextRank也可以用来提取文章摘要,以句子为节点,利用两个句子之间的相似度来计算边。两个句子的相似度如式5所示:
S
i
m
i
l
a
r
i
t
y
(
S
i
,
S
j
)
=
∣
{
w
k
∣
w
k
∈
S
i
&
w
k
∈
S
j
}
∣
l
o
g
(
∣
S
i
∣
)
+
l
o
g
(
∣
S
j
∣
)
Similarity(S_i,S_j)=\frac{|\{w_k|w_k\in{S_i} \& w_k\in{S_j}\}|}{log(|S_i|)+log(|S_j|)}
Similarity(Si,Sj)=log(∣Si∣)+log(∣Sj∣)∣{wk∣wk∈Si&wk∈Sj}∣
这个相似度也可以使用其他的计算方法,如word embedding来表示句子,来计算余弦相似度等。
python中已经实现TextRank实现的封装:
import codecs
from textrank4zh import TextRank4Keyword, TextRank4Sentence
text = codecs.open('./text/01.txt', 'r', 'utf-8').read()
tr4w = TextRank4Keyword(stop_words_file='./stopword.data') # 导入停止词
# 使用词性过滤,文本小写,窗口为2
tr4w.train(text=text, speech_tag_filter=True, lower=True, window=2)
print('关键词:')
# 20个关键词且每个的长度最小为1
print('/'.join(tr4w.get_keywords(20, word_min_len=1)) )
print('关键短语:')
# 20个关键词去构造短语,短语在原文本中出现次数最少为2
print('/'.join(tr4w.get_keyphrases(keywords_num=20, min_occur_num= 2)))
tr4s = TextRank4Sentence(stop_words_file='./stopword.data')
# 使用词性过滤,文本小写,使用words_all_filters生成句子之间的相似性
tr4s.train(text=text, speech_tag_filter=True, lower=True, source = 'all_filters')
print('摘要:')
print('\n'.join(tr4s.get_key_sentences(num=3))) # 重要性最高的三个句子
甚至在jieba中提供TextRank的接口,可以实现关键字抽取。
import jieba.analyse
import argparse
def parser_args():
parser = argparse.ArgumentParser(description='parse input information')
parser.add_argument('--filename', default='./data/files.txt', type=str)
parser.add_argument('--top_k', default=5, type=int)
args = parser.parse_args()
return args
args = parser_args()
content = open(args.filename, 'rb').read()
tags = jieba.analyse.textrank(content, topK=args.top_k, allowPOS=('n', 'nz', 'v', 'vd', 'vn', 'l', 'a', 'd'))
print(",".join(tags))
总结
这是常用的一些无监督的关键字抽取算法,主要可以用在推荐系统中,给用户按照关键字信息进行推荐,目前可能也有一些基于Transformer的关键字抽取,抽取的效果可能更佳准确,下周将对其进行扩展。
Reference
[1] TextRank系列之关键词提取算法_北木.的博客-CSDN博客_textrank英文关键词提取
[2] TextRank_coco_1998_2的博客-CSDN博客_textrank
[3] NLP中关键字提取方法总结和概述 - 知乎 (zhihu.com)
[4] Mihalcea R , Tarau P . TextRank: Bringing Order into Texts[J]. 2004.