前言
本文用Python编写代码,并通过hadoop streaming框架运行。
算法思想
下图是一个网络:
考虑转移矩阵是一个很多的稀疏矩阵,我们可以用稀疏矩阵的形式表示,我们把web图中的每一个网页及其链出的网页作为一行,即用如下方式表示:
1 A B C D 2 B A D 3 C C 4 D B C
Map阶段
在Map阶段,Map操作的每一行,对所有出链发射当前网页概率值的1/k,k是当前网页的出链数,比如对第一行输出<B,1/3*1/4>,<C,1/3*1/4>,<D,1/3*1/4>。
Reduce阶段
Reduce操作收集同一网页的值,累加并按权重计算,即$P_{i} = alpha cdot (b_{1}+b_{2}+cdots +b_{m})+frac{(1-alpha )}{N}$,其中$m$是指向网页$j$的网页数,$N$为所有网页数。
实践的时候,怎样在Map阶段知道当前行网页的概率值,需要一个单独的文件专门保存上一轮的概率分布值,先进行一次排序,让出链行与概率值按网页id出现在同一Mapper里面,整个流程如下:
算法实现
sortMapper.py 和 sortReducer.py的功能就是将图和概率矩阵读入并进行排序。
sortMapper.py
1 #!/usr/bin/env python3 2 import sys 3 4 5 for line in sys.stdin: 6 print(line.strip())
sortReducer.py
1 #!/usr/bin/env python3 2 import sys 3 4 5 for line in sys.stdin: 6 print(line.strip())
pageRankMapper.py
1 #!/usr/bin/env python3 2 import sys 3 4 node1 = node2 = None 5 count1 = count2 = 0 6 page_rank = 0.0 7 8 9 for line in sys.stdin: 10 if line.count(' ') == len(line): 11 continue # 除去空行 12 data = line.strip().split(' ') 13 if data[1] == 'a': # 该行数据是PageRank 14 count1 += 1 15 if count1 > 1: # 避免某个结点的PageRank丢失,因为有可能连着两行数据是PageRank 16 print('%s %s' % (node1, 0.0)) 17 node1 = data[0] # 记录出发结点 18 page_rank = float(data[2]) # 记录出发结点的PageRank 19 else: # 该行是链 20 node2 = data[0] 21 reach_node_list = data[1:] 22 23 if node1 == node2 and node1: # 注意除去None的情况 24 node_number = len(reach_node_list) # 出链数 25 for reach_node in reach_node_list: 26 print('%s %s' % (reach_node, page_rank*1.0/node_number)) 27 print('%s %s' % (node1, 0.0)) 28 node1 = node2 = None 29 count1 = 0
pageRankReducer.py
1 #!/usr/bin/env python3 2 import sys 3 4 alpha = 0.8 5 node = None # 记录当前结点 6 page_rank_sum = 0.0 # 记录当前结点的PageRank总和 7 N = 4 8 9 for line in sys.stdin: 10 if line.count(' ') == len(line): continue 11 data = line.strip().split(' ') 12 if node is None or node != data[0]: 13 if(node): print('%s a %s' % (node, alpha*page_rank_sum + (1.0-alpha)/N)) 14 node = data[0] 15 page_rank_sum = 0.0 16 page_rank_sum += float(data[1]) 17 18 print('%s a %s' % (node, alpha*page_rank_sum + (1.0-alpha)/N))
算法运行
由于该算法需要迭代运行,所以编写shell脚本来运行。
我也不是很会写shell脚本,所以写了一个感觉比较傻的。
run.sh
1 #!/bin/bash 2 3 max=50 4 5 for i in `seq 1 $max` 6 do 7 /usr/local/hadoop/bin/hadoop jar /usr/local/hadoop/hadoop-streaming-2.9.2.jar 8 -mapper /usr/local/hadoop/sortMapper.py 9 -file /usr/local/hadoop/sortMapper.py 10 -reducer /usr/local/hadoop/sortReducer.py 11 -file /usr/local/hadoop/sortReducer.py 12 -input links.txt pageRank.txt 13 -output out1 14 15 16 /usr/local/hadoop/bin/hadoop jar /usr/local/hadoop/hadoop-streaming-2.9.2.jar 17 -mapper /usr/local/hadoop/pageRankMapper.py 18 -file /usr/local/hadoop/pageRankMapper.py 19 -reducer /usr/local/hadoop/pageRankReducer.py 20 -file /usr/local/hadoop/pageRankReducer.py 21 -input out1/part-00000 22 -output out2 23 24 /usr/local/hadoop/bin/hadoop fs -rm pageRank.txt 25 /usr/local/hadoop/bin/hadoop fs -cp out2/part-00000 pageRank.txt 26 /usr/local/hadoop/bin/hadoop fs -rm -r -f out1 27 /usr/local/hadoop/bin/hadoop fs -rm -r -f out2 28 29 30 rm -r ~/Desktop/tmp.txt 31 /usr/local/hadoop/bin/hadoop fs -get pageRank.txt ~/Desktop/tmp.txt 32 echo $i | ~/Desktop/slove.py 33 done
解释一下该脚本,首先每次需要执行sortMapper.py,sortReducer.py,pageRankMapper.py,pageRankReducer.py四段代码,执行完之后会产生新的结点pageRank值,即保存在part-00000中。因为下一次运行需要用到这新的数据,所以此时把旧的pageRank.txt文件删除,再把新产生的数据复制过去。
为了记录每次迭代的结果,我还额外编写了一段代码来将记录每次结果。代码如下:
slove.py
1 #!/usr/bin/env python3 2 import sys 3 4 5 number = sys.stdin.readline().strip() 6 7 f_src = open("tmp.txt","r") 8 f_dst = open("result.txt", "a") 9 10 mat = "{:^30} " 11 f_dst.write(' ' + number) 12 13 lines = f_src.readlines() 14 for line in lines: 15 if line.count(' ') == len(line): 16 continue 17 data = line.strip().split(' ') 18 f_dst.write(mat.format(data[2]))
最后的运行结果为:
后记
上面实现的稀疏图的算法,后来我又实现了矩阵的算法。有兴趣的可以转至:传送门
参考: