自然语言处理之TF-IDF算法
TF-IDF算法(词频-逆文档频次算法)是一种基于统计的计算方法,常用于评估在一个文档集中一个词对某份文档的重要程度,可以作为文档关键词提取的一种算法。简单地说,就是一个词对文档越重要,越可能是文档的关键词。
1. 定义
从名字上,可以看出,TF-IDF算法由两部分构成:TF算法和IDF算法。TF算法是统计一个词在一篇文档中出现的频次,但因为文档的长度对词出现的频次影响很大,因此需要对词频进行归一化,计算公式如下:
IDF算法是统计一个词在文档集的多少个文档中出现,基本思想是:如果一个词在越少的文档中出现,则其对文档的区分能力越强。计算公式如下:
分母加1是采用了拉普拉斯平滑,避免有部分新的词没有在语料库中出现过而导致分母为0的情况出现,增强算法的健壮性。
TF-IDF算法就是把TF算法和IDF算法进行综合使用,关于两种算法的组合,最常用得是下面这种相乘的方式:
2. 例子
我们使用TF-IDF算法提取一篇文章的关键词时,需要有一个语料库,同时,涉及到分词。也就是说,如果是中文文章,那么我们对语料库和待计算的文章都要进行分词,这里采用的是jieba分词。同时,我们也考虑到每个词词性的问题,我们尽量选择名词作为关键词。代码如下:
1import jieba
2import jieba.posseg as psg
3import functools
4import math
5import numpy as np
6
7# 停用词表加载方法
8def get_stopword_list():
9 # 停用词表存储路径,每一行为一个词,按行读取进行加载
10 # 进行编码转换确保匹配准确率
11 stop_word_path = 'chinese_stopword.txt'
12 stopword_list = [sw.replace('\n', '') for sw in open(stop_word_path, encoding='UTF-8').readlines()]
13 return stopword_list
14
15# 分词方法,调用结巴接口
16def seg_to_list(sentence, pos=False):
17 if not pos:
18 # 不进行词性标注的分词方法
19 seg_list = jieba.cut(sentence)
20 else:
21 # 进行词性标注的分词方法
22 seg_list = psg.cut(sentence)
23 return seg_list
24
25# 去除干扰词
26def word_filter(seg_list, pos=False):
27 stopword_list = get_stopword_list()
28 filter_list = []
29 # 根据POS参数选择是否词性过滤
30 ## 不进行词性过滤,则将词性都标记为n,表示全部保留
31 for seg in seg_list:
32 if not pos:
33 word = seg
34 flag = 'n'
35 else:
36 word = seg.word
37 flag = seg.flag
38 if not flag.startswith('n'):
39 continue
40 # 过滤停用词表中的词,以及长度为<2的词
41 if not word in stopword_list and len(word) > 1:
42 filter_list.append(word)
43
44 return filter_list
45
46# 数据加载,pos为是否词性标注的参数,corpus_path为数据集路径
47def load_data(pos=False, corpus_path='corpus.txt'):
48 # 调用上面方式对数据集进行处理,处理后的每条数据仅保留非干扰词
49 doc_list = []
50 for line in open(corpus_path, 'r', encoding='UTF-8'):
51 content = line.strip()
52 seg_list = seg_to_list(content, pos)
53 filter_list = word_filter(seg_list, pos)
54 doc_list.append(filter_list)
55 return doc_list
56
57# idf值统计方法
58def train_idf(doc_list):
59 idf_dic = {}
60 # 总文档数
61 tt_count = len(doc_list)
62
63 # 每个词出现的文档数
64 for doc in doc_list:
65 for word in set(doc):
66 idf_dic[word] = idf_dic.get(word, 0.0) + 1.0
67
68 # 按公式转换为idf值,分母加1进行平滑处理
69 for k, v in idf_dic.items():
70 idf_dic[k] = math.log(tt_count / (1.0 + v))
71
72 # 对于没有在字典中的词,默认其仅在一个文档出现,得到默认idf值
73 default_idf = math.log(tt_count / (1.0))
74 return idf_dic, default_idf
75
76# 排序函数,用于topK关键词的按值排序
77def cmp(e1, e2):
78 res = np.sign(e1[1] - e2[1]) #若为正,res=1;若相等,res=0;若为负,res=-1
79 if res != 0:
80 return res
81 else:
82 a = e1[0] + e2[0]#若得分相同,则比较关键字
83 b = e2[0] + e1[0]
84 if a > b:
85 return 1
86 elif a == b:
87 return 0
88 else:
89 return -1
90
91# TF-IDF类
92class TfIdf(object):
93 # 四个参数分别是:训练好的idf字典,默认idf值,处理后的待提取文本,关键词数量
94 def __init__(self, idf_dic, default_idf, word_list, keyword_num):
95 self.word_list = word_list
96 self.idf_dic, self.default_idf = idf_dic, default_idf
97 self.tf_dic = self.get_tf_dic()
98 self.keyword_num = keyword_num
99
100 # 统计tf值
101 def get_tf_dic(self):
102 tf_dic = {}
103 for word in self.word_list:
104 tf_dic[word] = tf_dic.get(word, 0.0) + 1.0
105
106 tt_count = len(self.word_list)
107 for k, v in tf_dic.items():
108 tf_dic[k] = float(v) / tt_count
109
110 return tf_dic
111
112 # 按公式计算tf-idf
113 def get_tfidf(self):
114 tfidf_dic = {}
115 for word in self.word_list:
116 idf = self.idf_dic.get(word, self.default_idf)
117 tf = self.tf_dic.get(word, 0)
118
119 tfidf = tf * idf
120 tfidf_dic[word] = tfidf
121
122 tfidf_dic.items()
123 # 根据tf-idf排序,去排名前keyword_num的词作为关键词
124 for k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:
125 print(k + "/ ", end='')
126 print()
127
128def tfidf_extract(word_list, pos=False, keyword_num=10):
129 doc_list = load_data(pos)
130 idf_dic, default_idf = train_idf(doc_list)
131 tfidf_model = TfIdf(idf_dic, default_idf, word_list, keyword_num)
132 tfidf_model.get_tfidf()
133
134if __name__ == '__main__':
135 text = '6月19日,《2012年度“中国爱心城市”公益活动新闻发布会》在京举行。' + \
136 '中华社会救助基金会理事长许嘉璐到会讲话。基金会高级顾问朱发忠,全国老龄' + \
137 '办副主任朱勇,民政部社会救助司助理巡视员周萍,中华社会救助基金会副理事长耿志远,' + \
138 '重庆市民政局巡视员谭明政。晋江市人大常委会主任陈健倩,以及10余个省、市、自治区民政局' + \
139 '领导及四十多家媒体参加了发布会。中华社会救助基金会秘书长时正新介绍本年度“中国爱心城' + \
140 '市”公益活动将以“爱心城市宣传、孤老关爱救助项目及第二届中国爱心城市大会”为主要内容,重庆市' + \
141 '、呼和浩特市、长沙市、太原市、蚌埠市、南昌市、汕头市、沧州市、晋江市及遵化市将会积极参加' + \
142 '这一公益活动。中国雅虎副总编张银生和凤凰网城市频道总监赵耀分别以各自媒体优势介绍了活动' + \
143 '的宣传方案。会上,中华社会救助基金会与“第二届中国爱心城市大会”承办方晋江市签约,许嘉璐理' + \
144 '事长接受晋江市参与“百万孤老关爱行动”向国家重点扶贫地区捐赠的价值400万元的款物。晋江市人大' + \
145 '常委会主任陈健倩介绍了大会的筹备情况。'
146
147 pos = True
148 seg_list = seg_to_list(text, pos)
149 filter_list = word_filter(seg_list, pos)
150
151 print('TF-IDF模型结果:')
152 tfidf_extract(filter_list)然后计算出上述文档的关键词:
如果你觉得自己写代码计算TF-IDF值很麻烦,那么你可以直接调用jieba.analyse.extract_tags来计算。
停用词和语料库文档、以及代码都在:https://github.com/summerheday/TF-IDF。
注:本文参考于《python自然语言处理实战》一书。