TFIDF
一 简介
1、TF-IDF(term frequency–inverse document frequency)是一种用于信息检索与数据挖掘的常用加权技术。
2、TF
TF意思是词频(Term Frequenc),某个词在文章出现的次数。
停用词:词频也不是绝对的,像出现次数最多的词是----"的"、"是"、"在"----这一类最常用的词。它们叫做"停用词"(stop words),表示对找到结果毫无帮助、必须过滤掉的词。
3、IDF
IDF意思是反文本频率(Inverse Document Frequency),在词频的基础上,赋予每一个词的权重,进一步体现该词的重要性。
比如:
最常见的词("的"、"是"、"在")给予最小的权重,
较常见的词("中国")给予较小的权重,
较少见的词("维基"、"养殖")给予较大的权重。
IDF是反文频率,主要是相反的,它的大小与一个词的常见程度成反比。
4、TFIDF
TF与IDF相乘,就得到一个TF-IDF值,某个词对文章的重要性越大,它的TFIDF值越大;
于是,排在前面的几个词就是这篇文章的关键词。
TF-IDF 目的就是提取关键词。
二 计算步骤
1、计算词频
词频(TF) = 某个词在文章中的出现次数
文章有长短之分,为了便于不同文章的比较,做"词频"标准化。
词频(TF) = 某个词在文章中的出现次数 / 文章总词数
或者 词频(TF) = 某个词在文章中的出现次数 / 拥有最高词频的词的次数
2、某个词在文章中的出现次数
这时,需要一个语料库(corpus),用来模拟语言的使用环境。
反文档频率(IDF) = log(语料库的文档总数/包含该词的文档总数+1)
3、计算TF-IDF
TF-IDF = 词频(TF) * 逆文档频率(IDF)
TF-IDF与一个词在文档中的出现次数成正比,与包含该词的文档数成反比
三 例子
假如一篇文件的总词语数是100个,而词语“母牛”出现了3次,那么“母牛”一词在该文件中的词频就是3/100=0.03。一个计算文件频率 (IDF) 的方法是文件集里包含的文件总数除以测定有多少份文件出现过“母牛”一词。所以,如果“母牛”一词在1,000份文件出现过,而文件总数是10,000,000份的话,其逆向文件频率就是 lg(10,000,000 / 1,000)=4。最后的TF-IDF的分数为0.03 * 4=0.12。
四 实际-求IDF值
有500多篇文章,开发MR,实现每个词语的IDF值。
[root@master tfidf_test]# ls input_tfidf_dir/ | wc -l
508
1、把508篇文章汇总到一篇文章里面,每篇文章一行,一共有508行
代码实现
[root@master tfidf_test]# cat convert.py
#coding=utf-8
import os
import sys
file_path_dir = sys.argv[1]
#代表要读入的文章
def read_file_handler(f):
fd = open(f, 'r')
return fd
#定义函数
file_name = 0
#给每一篇文章取个标签
for fd in os.listdir(file_path_dir):
#对输入的文章进行遍历
file_path = file_path_dir + '/' + fd
#定义变量,包括输出的文章格式
content_list = []
#定义一个列表
file_fd = read_file_handler(file_path)
#获取文章剧本
for line in file_fd:
content_list.append(line.strip())
#把读取的文章追加到列表中
print '\t'.join([str(file_name), ' '.join(content_list)])
#name跟文章内容是按制表符分隔,数值型转换为字符串,文章内容用空格分隔
file_name += 1
#一共读多少文章,对应上面的file_name = 0
[root@master tfidf_test]# python convert.py input_tfidf_dir > idf_input.data
[root@master tfidf_test]# wc -l idf_input.data
508 idf_input.data
这个脚本作用:把文章汇总,做元数据
2、完成map代码
分析:这个map要做什么,完成哪些内容
代码思路:有一堆文章,文章里面的某个词出现,那就打1,没出现就打0,其实0的也不用输出了,只要统计有输出的行了
[root@master tfidf_test]# vim map.py
#coding=utf-8
import sys
for line in sys.stdin:
ss = line.strip().split('\t')
#数组ss,\t是对应输入数据
if len(ss) != 2:
continue
file_name, file_content = ss
word_list = file_content.strip().split(' ')
#文章全部分词写入word_list
word_set = set(word_list)
#去重,因为一个词在当前文章只能出现一次
for word in word_set:
print '\t'.join([word, '1'])
不需要文章id,因为只是求idf,只需要知道语料库的文档总数,这里是508,还有包含该词的文档数;
map只是是先切分出每篇文章的单词个数。
一个词对于不同的文章,tf值不同,tfidf值也就不同,需要指定文章id,Idf是个全局概率,不需要文章id。
3、完成red代码
Map已经把每篇文章的单词已经切分了,没有统计每个单词的个数,red就相当于再写一个wordcount。
[root@master tfidf_test]# cat red.py
#coding=utf-8
import sys
import math
#结果是求log,需要数学模块
current_word = None
#定义一个变量,用来记录读取的单词
sum = 0
#定义一个变量,用来记录cur_word的数量
docs_cnt = 508
#分子,语料库文档总数
for line in sys.stdin:
#标准输入,循环
ss = line.strip().split('\t')
#定义数组,对应数据\t分隔
if len(ss) != 2:
continue
word, val = ss
#把ss数组里的两个值,单词和数量分别赋值给word和cnt
if current_word == None:
current_word = word
#把刚读取的word赋值给全局变量,记录一下,用来判断下次读取的是否和这个相同
if current_word != word:
#如果和全局变量不同,说明cur_word这个单词的统计已经完了
idf = math.log(float(docs_cnt) / (float(sum) + 1.0))
#计算idf值.+1是为了防止除0.
print '\t'.join([current_word, str(idf)])
current_word = word
sum = 0
sum += int(val)
idf = math.log(float(docs_cnt) / (float(sum) + 1.0))
print '\t'.join([current_word, str(idf)])
#避免数据遗漏
4、求IDF值
[root@master tfidf_test]# cat idf_input.data | python map.py |sort -k1 | python red.py > IDF
[root@master tfidf_test]# cat IDF | sort -k2 | head
分享 -0.00196656897204
搜狐 -0.00196656897204
的 0.0019704439873
在 0.0927543934922
了 0.123458559836
是 0.15513541649
一 0.195000015054
有 0.211888233082
和 0.234029358959
也 0.251595682677
[root@master tfidf_test]# cat idf | sort -k2 | tail
作弊 5.53733426702
作物 5.53733426702
做菜 5.53733426702
坐标 5.53733426702
坐等 5.53733426702
坐视 5.53733426702
坐收 5.53733426702
坐椅 5.53733426702
坐庄 5.53733426702
坐姿 5.53733426702
5、求TFIDF
[root@master tfidf_test]# cat tfidf.demo.py
#coding:utf-8
import sys
idf_dict_path = sys.argv[1]
#传输词表
docs_path = sys.argv[2]
input_docid = sys.argv[3]
#传具体的文章
input_token = sys.argv[4]
#传输token
idf_dict = {}
with open(idf_dict_path,'r') as fd:
for line in fd:
ss = line.strip().split('\t')
if len(ss) != 2:
continue
token, idf = ss
idf_dict[token] = float(idf)
#把词表入库
tf_dict = {}
#某个词在某一篇文章的个数
sum_content = 0
#统计文章总词数
with open(docs_path, 'r') as fd:
#读入文章
for line in fd:
ss = line.strip().split('\t')
if len(ss) != 2:
continue
docid, content = ss
if docid != input_docid:
continue
for token in content.strip().split(' '):
#遍历content,求token
if token not in tf_dict:
tf_dict[token] = 1
#如果token不在词表里面,说明是第一次出现,token=1
else:
tf_dict[token] += 1
#否则token个数+1
sum_content += 1
#统计文章总词数
#这里完成之后,就把指定的篇文章里面的token值,放到tf_dict里了
if (input_token not in tf_dict) or (input_token not in idf_dict):
print "no found token"
ssy.exit(-1)
#直接退出
tfidf = tf_dict[input_token] * idf_dict[input_token]/float(sum_content)
print '\t'.join([input_docid,input_token, str(tfidf)])
求某一个词的TFIDF值,part-00000是集群跑出的idf值数据。
[root@master tfidf_test]# python tfidf.demo.py part-00000 idf_input.data 9 手机
9 手机 0.0257687038165