-
简介
TF-IDF算法(Term Frequency-Inverse Document Frequency,词频-逆文档频率算法)是一种基于统计的计算方法,常用于评估在一个文档集中一个词对某份文档的重要程度,由两部分组成:TF算法以及IDF算法。
- TF算法是统计一个词在一篇文档中出现的频次,其基本思想是,一个词在文档中出现的次数越多,则其对文档的表达能力也就越强。
- IDF算法则是统计一个词在文档集的多少个文档中出现,其基本思想是,如果一个词在越少的文档中出现,则其对文档的区分能力也就越强。
TF算法和IDF算法也能单独使用,在最早的时候就是如此。但是在使用过程中,学者们发现这两种算法都有其不足之处。TF仅衡量词的出现频次,但是没有考虑到词的对文档的区分能力。比如针对下面这篇文档:
世界献血日,学校团体、献血服务志愿者等可到血液中心参观检验加工过程,我们会对检验结果进行公示,同时血液的价格也将进行公示。
上文中“献血”“血液”“进行”“公示”等词出现的频次均为2,如果从TF算法的角度,他们对于这篇文档的重要性是一样的。但是实际上明显”血液“”献血“对这篇文档来说更关键。而IDF则是相反,强调的是词的区分能力,但是一个词既然能在一篇文档中频繁出现,那说明这个词能够很好地表现该篇文档的特征,忽略这一点显然也是不合理的。于是,学者们将这两种算法综合进行使用,构成TF-IDF算法,从词频、逆文档频率两个角度对词的重要性进行衡量。
-
计算式
1.TF的计算常用式:
其中,表示词i在文档j中的出现频次,分母部分统计文档中每个词出现次数的总和,也就是文档的总词数。还是以上文文档为例,”献血“一词出现次数为2,文档的总词数为50,则tf(献血)=n(献血)/n(总)=2/30=0.067。更直白的表示方式是,tf(word)=(word在文档中出现的次数)/(文档总词数)。
2.IDF的计算常用式:
为文档集中总文档数,为文档集中出现词i的文档数量。如果一个词越常见,那么分母就越大越接近,逆文档频率就越小越接近0。分母加1是采用了拉普拉斯平滑,避免有部分新的词没有在语料库中出现过而导致分母为零的情况出现,增强算法的健壮性。
3.TF-IDF算法的具体计算方法:
以上文文档为例,经过计算得到“献血”“血液”“进行”“公示”四个词出现的频次均为2,因此他们的tf值都是0.067。现假设我们具有的文档集有1000篇文档,其中出现“献血”“血液”“进行”“公示”的文档数分别为10、15、100、50,则idf(献血)=log(1000/10)=10,idf(血液)=4.20,idf(进行)=2.30,idf(公示)=3.00。得到这些信息后,计算每个词的tf-idf值,可以知道”献血“的tf-idf值最高,为最适合这篇文档的关键词。当然,关键词数量可以不止一个,可以根据tf-idf值由大到小排序取前n个作为关键词。
-
提取文本关键词
1.加载相关模块,其中functools模块主要是使用了其cmp_to_key函数,因为在python3中sorted函数废弃了cmp参数,我们可使用该函数来实现cmp的功能。
import math
import jieba
import jieba.posseg as psg
import functools
2.读入一个数据集,其由多个文本组成,并对所有的输入文本分词。
# 数据加载,pos为是否词性标注的参数,corpus_path为数据集路径
def load_data(pos=False, corpus_path='./corpus.txt'):
# 调用上面方式对数据集进行处理,处理后的每条数据仅保留非干扰词
doc_list = []
for line in open(corpus_path, 'r'):
content = line.strip()
seg_list = seg_to_list(content, pos)
filter_list = word_filter(seg_list, pos)
doc_list.append(filter_list)
return doc_list
# 分词方法,调用结巴接口
def seg_to_list(sentence, pos=False):
if not pos:
# 不进行词性标注的分词方法
seg_list = jieba.cut(sentence)
else:
# 进行词性标注的分词方法
seg_list = psg.cut(sentence)
return seg_list
3.分完词之后,每个文档都可以表示为一系列词的集合。一般在算法开始前,还需要去除停用词,一般是使用一个受控停用表来对词进行筛选。现在中文自然语言处理中较常用的一个停用词表就是哈工大的停用词表,包含了大部分中文文本中常见的干扰词。除了停用词表外,也可以使用词性对词进行进一步筛选,例如在关键词提取中,可以尝试只要名词性的词语,其他词语视为干扰次过滤掉。
# 停用词表加载方法
def get_stopword_list():
# 停用词表存储路径,每一行为一个词,按行读取进行加载
# 进行编码转换确保匹配准确率
stop_word_path = './stopword.txt'
stopword_list = [sw.replace('\n', '') for sw in open(stop_word_path).readlines()]
return stopword_list
# 去除干扰词
def word_filter(seg_list, pos=False):
stopword_list = get_stopword_list()
filter_list = []
# 根据POS参数选择是否词性过滤
## 不进行词性过滤,则将词性都标记为n,表示全部保留
for seg in seg_list:
if not pos:
word = seg
flag = 'n'
else:
word = seg.word
flag = seg.flag
if not flag.startswith('n'):
continue
# 过滤停用词表中的词,以及长度为<2的词
if not word in stopword_list and len(word) > 1:
filter_list.append(word)
return filter_list
4.使用预处理完成的数据来训练算法,TF-IDF的训练主要是根据数据集生成对应的IDF值字典,后续计算每个词的TF-IDF时,直接从字典中读取。
# idf值统计方法
def train_idf(doc_list):
idf_dic = {}
# 总文档数
tt_count = len(doc_list)
# 每个词出现的文档数
for doc in doc_list:
for word in set(doc):
idf_dic[word] = idf_dic.get(word, 0.0) + 1.0
# 按公式转换为idf值,分母加1进行平滑处理
for k, v in idf_dic.items():
idf_dic[k] = math.log(tt_count / (1.0 + v))
# 对于没有在字典中的词,默认其仅在一个文档出现,得到默认idf值
default_idf = math.log(tt_count / (1.0))
return idf_dic, default_idf
5.cmp函数是为了输出top关键词时,先按照关键词的计算分值排序,在得分相同时,根据关键词进行排序。
# 排序函数,用于topK关键词的按值排序
def cmp(e1, e2):
import numpy as np
res = np.sign(e1[1] - e2[1])
if res != 0:
return res
else:
a = e1[0] + e2[0]
b = e2[0] + e1[0]
if a > b:
return 1
elif a == b:
return 0
else:
return -1
6.完整的TF-IDF实现方法。根据具体要处理的文本,计算每个词的TF值,并获取前面训练后的IDF数据,直接获取每个词的IDF值,综合计算每个词的TF-IDF。TF-IDF类传入参数主要有三个:idf-dic为前面训练好的idf数据;word_list为经过分词、去除干扰词后的待提取关键词文本,是一个非干扰词组成的列表;keyword_num决定要提取多少个关键词。
# TF-IDF类
class TfIdf(object):
# 四个参数分别是:训练好的idf字典,默认idf值,处理后的待提取文本,关键词数量
def __init__(self, idf_dic, default_idf, word_list, keyword_num):
self.word_list = word_list
self.idf_dic, self.default_idf = idf_dic, default_idf
self.tf_dic = self.get_tf_dic()
self.keyword_num = keyword_num
# 统计tf值
def get_tf_dic(self):
tf_dic = {}
for word in self.word_list:
tf_dic[word] = tf_dic.get(word, 0.0) + 1.0
tt_count = len(self.word_list)
for k, v in tf_dic.items():
tf_dic[k] = float(v) / tt_count
return tf_dic
# 按公式计算tf-idf
def get_tfidf(self):
tfidf_dic = {}
for word in self.word_list:
idf = self.idf_dic.get(word, self.default_idf)
tf = self.tf_dic.get(word, 0)
tfidf = tf * idf
tfidf_dic[word] = tfidf
tfidf_dic.items()
# 根据tf-idf排序,去排名前keyword_num的词作为关键词
for k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:
print(k + "/ ", end='')
print()
7.进行封装
def tfidf_extract(word_list, pos=False, keyword_num=10):
doc_list = load_data(pos)
idf_dic, default_idf = train_idf(doc_list)
tfidf_model = TfIdf(idf_dic, default_idf, word_list, keyword_num)
tfidf_model.get_tfidf()
8.进行关键词提取
if __name__ == '__main__':
text = '费尔南多·托雷斯(Fernando Jose Torres Sanz),1984年3月20日出生于西班牙马德里,' + \
'西班牙足球运动员,司职前锋,效力于日本职业足球甲级联赛鸟栖砂岩足球俱乐部。' + \
'托雷斯2001出道于马德里竞技,2007年加盟英超利物浦,2011年转会切尔西,' + \
'期间帮助球队夺得了2012年欧洲冠军联赛冠军,其后以租借的形式加盟AC米兰,' + \
'2014年12月,托雷斯宣布回归马德里竞技。2018年7月,托雷斯宣布加盟日本鸟栖砂岩足球俱乐部。' + \
'2004年欧洲杯,托雷斯首次代表国家队参加国际大赛,2008年和2012年跟随西班牙队两度夺得欧洲杯冠军,' + \
'2010年随队夺得世界杯冠军,其个人在2008年荣膺欧洲杯决赛MVP,2012获得欧洲杯金靴奖、2013年获得联合会杯金靴奖。'
pos = True
seg_list = seg_to_list(text, pos)
filter_list = word_filter(seg_list, pos)
print('TF-IDF模型结果:')
tfidf_extract(filter_list)
执行结果如下,不选择词性过滤,各种词性的词都可能被选为关键词:
TF-IDF模型结果:
托雷斯/ 足球/ 欧洲杯/ 马德里/ 夺得/ 冠军/ 加盟/ 鸟栖/ 金靴奖/ 西班牙/
这是使用词性过滤再次去除干扰词,仅选择名词作为关键词的结果:
TF-IDF模型结果:
托雷斯/ 足球/ 欧洲杯/ 马德里/ 冠军/ 加盟/ 金靴奖/ 西班牙/ 竞技/ 砂岩/
一般情况下,使用词性过滤,仅保留名词作为关键词的结果更符合我们的要求,但是有些场景对其他词性的词有特殊的要求,可以根据场景的不同,选择需要过滤的不同词性。