一.公式的分析和编写代码的思路:
根据公式,计算TF-IDF首先需要计算TF和IDF,然后在做乘积
1.TF运算
(1)TF运算的时候,首先需要统计某个单词在该篇文章出现的次数、和统计单词所在那篇文章的单词的总数
(2)实现思路:在每个文章中对单词进行计数,形成一个(word,count)形式的元组,因为在不同文章中分别计算TF需要用到该单词所在文章单词的数量,所以最后的对于每篇文章我将计数的结果生成如下形式:即结果列表有两个元素,一个元素存储每个单词在文章的计数情况,另一个存储单词的个数
然后使用map函数对每一篇文章进行计算tf,生成新的结果,并且以元组的形式保存(word,tf),如下图所示。
2.IDF的计算
(1)IDF的运算需要用到数据中包含的所有文本的数量,以及包含某个单词的文本的数量,因此idf的值实际上并不受某篇文章的影响,他描述的是所有数据集整体的属性,所以并不需要在每一篇文章对分别对某个单词计算,整体计算IDF然后以字典的形式存储,再以共享变量的方式在整个executor上共享即可以减少空间的开销也可以节省计算时间,这一点起初我并没有考虑到。
(2)文本的总数,可以较为容易获取到,包含word的文本个数整体思路,就是在每篇文章中对单词进行去重形成新的RDD,然后运用word count的思路先将RDD展平,然后map生成键值对,最后reducebykey得到结果
3.TF_IDF计算
运用得到的TF,IDF字典进行乘积运算
二.代码实现:
1.定义全局共享的数据集
2.一些用于辅助的函数:
3.主函数部分:
三.代码的执行结果:
完整代码:
import math
from pyspark import SparkConf,SparkContext
#定义数据集
doc_list = [["hello","world","china","good","spark","good"],
["hello","china","china","great","love","china"],
["love","spark","spark","good","hello","spark"]
]
#计算文档的数目
doc_num = len(doc_list)
#这个函数作用是分别对每篇文章中进行计数,生成类似于[((word1,1),(word2,2)),6]的列表,
#这个列表除了存储单词计数的结果还存储,单词所在文章包含所有单词的个数
def map_with_its_doc_lens(x):
dic = {}
l = []
ll = []
for i in x:
dic[i] = dic.get(i,0)+1
for k,v in dic.items():
l.append((k,v))
ll.append(l)
ll.append(len(x))
return ll
##这个函数用于正则化,为了使结果对比更加明显
def normalize(x):
sum = 0
li = []
for i in x:
sum+=i[1]**2
sum = sum**0.5
for i in x:
li.append((i[0],i[1]/sum))
return li
if __name__ == '__main__':
# spark的基本配置
conf = SparkConf().setAppName("tf-idf").setMaster("local")
sc = SparkContext(conf=conf)
#创建rdd并进行缓存,缓存的目的是为了防止重复计算
docs_rdd = sc.parallelize(doc_list).cache()
#对每篇文章进行计数
word_tf = docs_rdd.map(lambda x:map_with_its_doc_lens(x)).map(lambda x:[(i[0],i[1]/x[1]) for i in x])
#计算idf
word_idf = docs_rdd.map(lambda x:list(set(x)))\
.flatMap(lambda x:[(i,1) for i in x])\
.reduceByKey(lambda x,y:x+y).map(lambda x:(x[0],math.log((doc_num+1)/(x[1]+1),2))).collect()
#idf值转化成字典
word_idf_dict = dict(word_idf)
#设置广播变量,一个executor上的所有task共享这个idf字典,减少空间开销
broadcast_idf_dict = sc.broadcast(word_idf_dict)
#计算tf*idf
word_tf_idf = word_tf.map(lambda x:[(i[0],i[1]*broadcast_idf_dict.value[i[0]])for i in x])
#正则化结果
word_normalize_result = word_tf_idf.map(lambda x:normalize(x)).collect()
#排序输出结果
for i in word_normalize_result:
print(sorted(i,key=lambda x:x[1]))
sc.stop()