配色: 字号:
深入浅出PageRank算法 - SegmentFault
2014-10-11 | 阅:  转:  |  分享 
  
2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111281/18

深入浅出PageRank算法

Hujiawei233天前发布

推荐0推荐

收藏6收藏,1.3k浏览

PageRank算法

PageRank算法是谷歌曾经独步天下的“倚天剑”,该算法由LarryPage和Sergey

Brin在斯坦福大学读研时发明的,论文点击下载:ThePageRankCitation

Ranking:BringingOrdertotheWeb。

本文首先通过一些参考文献引出问题,然后给出了PageRank的几种实现算法,

最后将其推广至在MapReduce框架下如何实现PageRank算法。

PageRank的核心思想有2点:

1.如果一个网页被很多其他网页链接到的话说明这个网页比较重要,也就是

pagerank值会相对较高;

2.如果一个pagerank值很高的网页链接到一个其他的网页,那么被链接到的网页

的pagerank值会相应地因此而提高。

下面是一张来自WikiPedia的图,每个球代表一个网页,球的大小反应了网页的

pagerank值的大小。指向网页B和网页E的链接很多,所以B和E的pagerank值较

高,另外,虽然很少有网页指向C,但是最重要的网页B指向了C,所以C的

pagerank值比E还要大。

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111282/18

参考内容:

1.WikiaboutPageRank

2.Google的秘密-PageRank彻底解说中文版

3.数值分析与算法Page161应用实例:Google的PageRank算法

4.NumericMethodswithMatlab或者中文翻译版本Matlab数值计算

5.使用MapReduce思想计算PageRankPage62PageRank和马尔可夫链

1.问题背景

来自参考内容3

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111283/18

2.数学建模

来自参考内容3,理解网页连接矩阵$G$,马尔科夫过程("网上冲浪"),转移矩阵

$A$,概率$p$为用户点击当前网页中的某个链接地址的概率(一般都为0.85)。

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111284/18

最后得到一个等式$Ax=x$,这实际上就是求矩阵$A$的特征值为1的特征向量!

下面的内容使用圆盘定理解释了1是矩阵$A$的主特征值,所以我们可以使用幂

法来求解。

关于幂法的详细介绍参考另一篇文章NumericalMethodsUsingMatlab:第三章

矩阵特征值和奇异值求解

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111285/18

3.求解PageRank

假设有如上图右侧所示的网页链接模型。

(1)幂法

wiki上有一个PageRank的简便算法,它不考虑转移概率,而是采用的是迭代的

方式,每次都更新所有网页的pagerank值,更新的方式就是将每个网页的

pagerank值平摊分给它指向的所有网页,每个网页累计所有指向它的网页平摊

给它的值作为它该回合的pagerank值,直到全部网页的pagerank值收敛了或者

满足一定的阈值条件就停止。

后面的MapReduce框架下PageRank算法的实现就采用了这个思想。考虑转移概

率的情况和这个算法类似,乘上一个转移概率再加上一个随机跳转的概率。

根据上面的思想,下面Matlab代码实现可以得到各个网页的PageRank值。

n=6;

i=[234456161];

j=[122333456];

G=sparse(i,j,1,n,n);

%Powermethod

forj=1:n

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111286/18

得到的向量$x$保存了各个网页的pagerank值,虽然链接数目一样,但是网页①

比网页④和网页⑤都高,而网页②的pagerank值第二高,因为网页①链接到了

它上面,相当于沾了网页①的光。

这篇文章给出该算法的一个Python版本实现,该博主使用第三方模块python-

graph,python-graph模块实现了很多图算法,该模块的使用示例,使用前需要

先安装,代码如下:

Python版本的算法实现:

L{j}=find(G(:,j));

c(j)=length(L{j});

end

p=.85;

delta=(1-p)/n;

x=ones(n,1)/n;

z=zeros(n,1);

cnt=0;

whilemax(abs(x-z))>.0001

z=x;

x=zeros(n,1);

forj=1:n

ifc(j)==0

x=x+z(j)/n;%转移到任意一个网页

else

x(L{j})=x(L{j})+z(j)/c(j);%将上次的pagerank值平摊给所

有指向的网页

end

end

x=px+delta;

cnt=cnt+1;

end

x=

0.2675

0.2524

0.1323

0.1698

0.0625

0.1156

easy_installpython-graph-core

easy_installpython-graph-dot

#coding=utf-8

#python-graphhttps://code.google.com/p/python-graph/

#Importgraphviz

importgraphvizasgv

#Importpygraph

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111287/18

frompygraph.classes.digraphimportdigraph

frompygraph.readwrite.dotimportwrite

#Definepagerankfunction

defpagerank(graph,damping_factor=0.85,max_iterations=100,\

min_delta=0.00001):

"""

ComputeandreturnthePageRankinandirectedgraph.

@typegraph:digraph

@paramgraph:Digraph.

@typedamping_factor:number

@paramdamping_factor:PageRankdumpingfactor.

@typemax_iterations:number

@parammax_iterations:Maximumnumberofiterations.

@typemin_delta:number

@parammin_delta:Smallestvariationrequiredforanewiterati

on.

@rtype:Dict

@return:DictcontainingallthenodesPageRank.

"""

nodes=graph.nodes()

graph_size=len(nodes)

ifgraph_size==0:

return{}

#valuefornodeswithoutinboundlinks

min_value=(1.0-damping_factor)/graph_size

#itializethepagerankdictwith1/Nforallnodes

#pagerank=dict.fromkeys(nodes,1.0/graph_size)

pagerank=dict.fromkeys(nodes,1.0)

foriinrange(max_iterations):

diff=0#totaldifferencecomparedtolastiteraction

#computeseachnodePageRankbasedoninboundlinks

fornodeinnodes:

rank=min_value

forreferring_pageingraph.incidents(node):

rank+=damping_factorpagerank[referring_page]/

\

len(graph.neighbors(referring_page))

diff+=abs(pagerank[node]-rank)

pagerank[node]=rank

print''ThisisNO.%siteration''%(i+1)

printpagerank

print''''

#stopifPageRankhasconverged

ifdiff
break

returnpagerank

#Graphcreation

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111288/18

经过32次迭代之后得到的结果如下,和前面的结果一致:

(2)利用马尔可夫矩阵的特殊结构

来自参考内容4,其中$\delta=\frac{1-p}{n}$

也就是将矩阵$A$进行分解,并不需要显示求出矩阵$A$,然后便是求解一个线

性方程组即可。

gr=digraph()

#Addnodesandedges

gr.add_nodes(["1","2","3","4"])

gr.add_edge(("1","2"))

gr.add_edge(("1","3"))

gr.add_edge(("1","4"))

gr.add_edge(("2","3"))

gr.add_edge(("2","4"))

gr.add_edge(("3","4"))

gr.add_edge(("4","2"))

#DrawasPNG

#dot=write(gr)

#gvv=gv.readstring(dot)

#gv.layout(gvv,''dot'')

#gv.render(gvv,''png'',''Model.png'')

pagerank(gr)

ThisisNO.32iteration

{''1'':0.2675338708706491,''3'':0.13227261904986046,''2'':0.25240379

02400518,''5'':0.062477242064127136,''4'':0.1697488529161491,''6'':

0.1155828978186352}

functionx=pagerank1(G)

%PAGERANK1Google''sPageRankmodifiedversion1-hujiawei

%ifnargin<3,p=.85;end

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/11900000007111289/18

(3)巧妙解法:逆迭代算法

巧妙利用Matlab中的精度误差导致原本是一个奇异矩阵的$I-A$变成一个非奇异

矩阵,运行时只是会有些警告提示,但是运行结果和其他算法一样。

p=0.85;

%Eliminateanyself-referentiallinks

G=G-diag(diag(G));

%c=out-degree,r=in-degree

[n,n]=size(G);

c=sum(G,1);%eachrow''ssum

r=sum(G,2);%eachcol''ssum

%Scalecolumnsumstobe1(or0wheretherearenooutlinks).

k=find(c~=0);

D=sparse(k,k,1./c(k),n,n);

%Solve(I-pGD)x=e

e=ones(n,1);

I=speye(n,n);

x=(I-pGD)\e;

%Normalizesothatsum(x)==1.

x=x/sum(x);

functionx=pagerank2(G)

%PAGERANK1Google''sPageRankmodifiedversion2-hujiawei

%usinginverseiterationmethod

%ifnargin<3,p=.85;end

p=0.85;

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112810/18

最后,附上参考内容4中给出的一份好代码,用于模拟随机冲浪生成矩阵$G$的

代码

%Eliminateanyself-referentiallinks

G=G-diag(diag(G));

%c=out-degree,r=in-degree

[n,n]=size(G);

c=sum(G,1);%eachrow''ssum

r=sum(G,2);%eachcol''ssum

%Scalecolumnsumstobe1(or0wheretherearenooutlinks).

k=find(c~=0);

D=sparse(k,k,1./c(k),n,n);

%Solve(I-pGD)x=e

e=ones(n,1);

I=speye(n,n);

%x=(I-pGD)\e;

delta=(1-p)/n;

A=pGD+delta;

x=(I-A)\e;

%Normalizesothatsum(x)==1.

x=x/sum(x);

function[U,G]=surfer(root,n)

%SURFERCreatetheadjacencygraphofaportionoftheWeb.

%[U,G]=surfer(root,n)startsattheURLrootandfollows

%Weblinksuntilitformsanadjacencygraphwithnnodes.

%U=acellarrayofnstrings,theURLsofthenodes.

%G=ann-by-nsparsematrixwithG(i,j)=1ifnodejislinked

tonodei.

%

%Example:[U,G]=surfer(''http://www.harvard.edu'',500);

%SeealsoPAGERANK.

%

%Thisfunctioncurrentlyhastwodefects.(1)Thealgorithmfo

r

%findinglinksisnaive.Wejustlookforthestring''http:''.

%(2)AnattempttoreadfromaURLthatisaccessible,butvery

slow,

%mighttakeanunacceptablylongtimetocomplete.Insomecas

es,

%itmaybenecessarytohavetheoperatingsystemterminateMAT

LAB.

%KeywordsfromsuchURLscanbeaddedtotheskiplistinsurf

er.m.

%Initialize

clf

shg

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112811/18

set(gcf,''doublebuffer'',''on'')

axis([0n0n])

axissquare

axisij

boxon

set(gca,''position'',[.12.20.78.78])

uicontrol(''style'',''frame'',''units'',''normal'',''position'',[.01.09.98

.07]);

uicontrol(''style'',''frame'',''units'',''normal'',''position'',[.01.01.98

.07]);

t1=uicontrol(''style'',''text'',''units'',''normal'',''position'',[.02.10

.94.04],...

''horiz'',''left'');

t2=uicontrol(''style'',''text'',''units'',''normal'',''position'',[.02.02

.94.04],...

''horiz'',''left'');

slow=uicontrol(''style'',''toggle'',''units'',''normal'',...

''position'',[.01.24.07.05],''string'',''slow'',''value'',0);

quit=uicontrol(''style'',''toggle'',''units'',''normal'',...

''position'',[.01.17.07.05],''string'',''quit'',''value'',0);

U=cell(n,1);

hash=zeros(n,1);

G=logical(sparse(n,n));

m=1;

U{m}=root;

hash(m)=hashfun(root);

j=1;

whilej
%Trytoopenapage.

try

set(t1,''string'',sprintf(''%5d%s'',j,U{j}))

set(t2,''string'','''');

drawnow

page=urlread(U{j});

catch

set(t1,''string'',sprintf(''fail:%5d%s'',j,U{j}))

drawnow

continue

end

ifget(slow,''value'')

pause(.25)

end

%Followthelinksfromtheopenpage.

forf=findstr(''http:'',page);

%Alinkstartswith''http:''andendswiththenextquote.

e=min([findstr(''"'',page(f:end))findstr('''''''',page(f:end))])

;

ifisempty(e),continue,end

url=deblank(page(f:f+e-2));

url(url<'''')=''!'';%Nonprintablecharacters

ifurl(end)==''/'',url(end)=[];end

%Lookforlinksthatshouldbeskipped.

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112812/18

skips={''.gif'',''.jpg'',''.pdf'',''.css'',''lmscadsi'',''cybernet'',.

..

''search.cgi'',''.ram'',''www.w3.org'',...

''scripts'',''netscape'',''shockwave'',''webex'',''fansonly''}

;

skip=any(url==''!'')|any(url==''?'');

k=0;

while~skip&(k
k=k+1;

skip=~isempty(findstr(url,skips{k}));

end

ifskip

ifisempty(findstr(url,''.gif''))&isempty(findstr(url,''.jp

g''))

set(t2,''string'',sprintf(''skip:%s'',url))

drawnow

ifget(slow,''value'')

pause(.25)

end

end

continue

end

%Checkifpageisalreadyinurllist.

i=0;

fork=find(hash(1:m)==hashfun(url))'';

ifisequal(U{k},url)

i=k;

break

end

end

%Addanewurltothegraphthereifarefewerthann.

if(i==0)&(m
m=m+1;

U{m}=url;

hash(m)=hashfun(url);

i=m;

end

%Addanewlink.

ifi>0

G(i,j)=1;

set(t2,''string'',sprintf(''%5d%s'',i,url))

line(j,i,''marker'',''.'',''markersize'',6)

drawnow

ifget(slow,''value'')

pause(.25)

end

end

end

j=j+1;

end

delete(t1)

delete(t2)

delete(slow)

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112813/18

4.MapReduce框架下PageRank算法的实现

利用前面wiki上的迭代(或者幂法)的思想来实现MapReduce框架下PageRank算

法很简单,可以先阅读下参考内容5。

这篇文章using-mapreduce-to-compute-pagerank更加详细,可以参考

以下是我的大数据的一次作业,要求是参考wiki上的简便算法,实现MapReduce

框架下的PageRank算法。给的数据集是Twitter的用户之间的关系,可以看做是

网页之间的关系,但是助教没要求写代码以及运行这个数据集(有1G多),所以下

面只是一个Python版本的理想可行版本,并没有通过实际大数据集的验证,另

外,博主暂时还不太会Python的mapreduce框架中的一些函数,所以实现的是一

个简明的可以测试的PageRank算法。

1.输入输出格式

map函数的输入是<节点,从该节点引出的边列表>,其中节点是一个类,

包含了其当前的pagerank值,输出是<节点,反向节点pagerank值/反向节

点引出边的总数>;

reduce函数的输入是<节点,反向节点pagerank值/反向节点引出边的总数

>,输出是<节点,从该节点引出的边列表>,其中节点包含了其更新后的

pagerank值。

伪代码:[一时犯二写了个英文形式的]

set(quit,''string'',''close'',''callback'',''close(gcf)'',''value'',0)

%------------------------

functionh=hashfun(url)

%Almostuniquenumerichashcodeforpagesalreadyvisited.

h=length(url)+1024sum(url);

processthedatatotheformof{nodei:[itsadjacentnodelist],..

.}

whilethesumofdifferencebetweenthelasttwopagerankvalues<

threshold

map({nodei:[itsadjacentnodelist],...}):

map_output={}

foreverynodejinadjacentnodelist:

putorsumup{j:(i,PageRank(i)/length(adjacentnodel

ist))}intomap_output

returnmap_output

reduce(map_output):

reduce_output={}

foreveryentry{j:(i,PageRank(i)/length(adjacentnodelis

t))}inmap_output:

putorsumupallvaluespagerankvaluesfornodejwit

hitsadjacentnodelistintoreduce_output

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112814/18

2.示例演示

假设用户1,2,3,4是如下图所示的关系:

假设有2个mapper(A和B)和1个reducer(C),初始时4个节点的pagerank值都是

0.25

其中,关于用户1和2的数据被mapperA读取并处理,关于用户3和4的数据被

mapperB读取并处理[经验证,即使一个用户的数据是由不同的mapper来读取

的,最终收敛到的结果差不多]

map的输入输出结果如下:

returnreduce_output

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112815/18

reduce的输入输出结果如下,输入是2个mapper的输出,输出的结果中更新了节

点的pagerank值

reducer处理完了之后又将它的结果输入给mapper处理,直到迭代的次数超过了

设定值或者两次迭代之后得到的所有节点的pagerank值之差的总和(也可以是取

二范数)小于设定的阈值。

3.示例的实验结果

(1)首先是使用Matlab采用幂法的方式计算出在p=1.0的情况下示例得到的结果

[它的主要作用是验证后面python版本的正确性]

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112816/18

matlab源码如下:

结果为:

(2)matlab版本的pagerank没有采用mapreduce的思想进行迭代,所以我另外写

了一个python版本的利用mapreduce思想实现的pagerank算法(注:我并没有使

用python的map和reduce函数去实现,而是使用更加容易明白的实现),使用的

阈值为0.0001,最多迭代的次数为100次。

n=4;

i=[23434412];

j=[11122334];

G=sparse(i,j,1,n,n);

[n,n]=size(G);

forj=1:n

L{j}=find(G(:,j));

c(j)=length(L{j});

end

%Powermethod

p=1.0;

delta=(1-p)/n;

x=ones(n,1)/n;

z=zeros(n,1);

cnt=0;

whilemax(abs(x-z))>.0001

z=x;

x=zeros(n,1);

forj=1:n

ifc(j)==0

x=x+z(j)/n;

else

x(L{j})=x(L{j})+z(j)/c(j);

end

end

x=px+delta;

cnt=cnt+1;

end

sprintf(''pagerankresult:'')

x

0.1072

0.3571

0.2143

0.3214

#coding=utf-8

__author__=''hujiawei''

__doc__=''pagerankmapreduce''

classNode:

def__init__(self,id,pk):

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112817/18

self.id=id

self.pk=pk

defpk_map(map_input):

map_output={}

fornode,outlinksinmap_input.items():

forlinkinoutlinks:

size=len(outlinks)

iflinkinmap_output:

map_output[link]+=(float)(node.pk)/size

else:

map_output[link]=(float)(node.pk)/size

returnmap_output

defpk_reduce(reduce_input):

forresultinreduce_input:

fornode,valueinresult.items():

node.pk+=value

defpk_clear(nodes):

fornodeinnodes:

node.pk=0

defpk_last(nodes):

lastnodes=[]

fornodeinnodes:

lastnodes.append(Node(node.id,node.pk))

returnlastnodes

defpk_diff(nodes,lastnodes):

diff=0

foriinrange(len(nodes)):

print(''nodepk%f,lastnodepk%f''%(nodes[i].pk,lastno

des[i].pk))

diff+=abs(nodes[i].pk-lastnodes[i].pk)

returndiff

defpk_test1():

node1=Node(1,0.25)

node2=Node(2,0.25)

node3=Node(3,0.25)

node4=Node(4,0.25)

nodes=[node1,node2,node3,node4]

threshold=0.0001

max_iters=100

foriter_countinrange(max_iters):

iter_count+=1

lastnodes=pk_last(nodes)

print(''============mapcount%d=================''%(iter

_count))

in1={node1:[node2,node3,node4],node2:[node3,node4]}

in2={node3:[node1,node4],node4:[node2]}

mapout1=pk_map(in1)

mapout2=pk_map(in2)

fornode,valueinmapout1.items():

printstr(node.id)+''''+str(value)

fornode,valueinmapout2.items():

2014年10月11日深入浅出PageRank算法-SegmentFault

http://segmentfault.com/blog/hujiaweibujidao/119000000071112818/18

androidopencv

得到的结果为如下,总共迭代了15次

上面的结果和Matlab用幂法得到的pagerank值差别很小,可以认为是正确的,

所以说明了使用这种mapreduce输入输出格式的正确性。

OK,差不多了,希望对需要理解PageRank算法的人有帮助!:-)

printstr(node.id)+''''+str(value)

print(''============reducecount%d=================''%(i

ter_count))

reducein=[mapout1,mapout2]

pk_clear(nodes)

pk_reduce(reducein)

fornodeinnodes:

printstr(node.id)+''''+str(node.pk)

diff=pk_diff(nodes,lastnodes)

ifdiff
break

if__name__==''__main__'':

pk_test1()

10.107138774577

20.35712924859

30.214296601128

40.321435375705

献花(0)
+1
(本文系icecity0079...首藏)