前言
我们在搜索引擎内输入一个查询,然后搜素引擎便会很快返回大量的网页结果。这些网页结果已经通过某种方式排好了序,每一页从上到下会显示若干条搜索结果,然后所有搜索结果会分为很多页。当然,我们平时使用搜索引擎的时候,经常只看排在最前面的几条结果,看完第一页全部网页的人都不是很多。
那么,排在前面的搜索结果更加容易被用户看到。一般来说,搜索引擎公司希望自己的产品能够给用户带来更好的搜索体验,因此,会把更加“重要的”网页向前排。至于某些搞竞价排行的公司,作者本人十分鄙视。我们接下来不妨默认网页排序的目标:让“重要的”网页排序靠前。
链接分析方法——PageRank
概念与计算方法
链接分析的第一种技术是对Web图中的每个节点赋一个0到1之间的分值,这个分值被称为 PageRank。
节点的PageRank值依赖于Web图的链接结构。对于给定的查询,Web搜索引擎会融合数以百计的特征来得到最后的综合得分,呈现给用户的网页排序结果由综合得分决定。这些特征包括余弦相似度、词项邻近度及PageRank等。本篇博客不讨论如何求得综合得分和最终排名,只讨论基于链接结构的网页排序方法。
浩如烟海的网页通过链接编织成网。一个网页内可能存在大量能够跳转到其他网页的链接,同时一个网页也可能由许多其他网页跳转而来。网页与网页之间的指向关系形成了网页间的链接结构。每一个网页都是Web图中的一个节点。
假设有这么一个十分无聊的上网冲浪人(像不像不想学习也不知道该玩什么所以漫无目的地刷抖音快手b站豆瓣微博的某些同学),他会从某个网页出发。然后在这个网页包含的所有超链接中,随机选择一个点击,然后跳转到下一个访问目标网页。因为这名用户会随机选择超链接,因此他访问的下一个网页也是随机的。
比如说,当前他访问的网页A,包含且仅包含三个超链接,分别指向网页B、C、D。那么这名假想的用户接下来就会有1/3的可能访问B、1/3的可能访问C、1/3的可能访问D。如果当前他访问的网页a,包含且仅包含五个超链接,分别指向网页b、c、d、e、f。那么这名假想的用户接下来就会有1/5的可能访问b、1/5的可能访问c、1/5的可能访问d、1/5的可能访问e、1/5的可能访问f。
有些网页比较特殊,他们不存在任何指向其他网页的链接。随机游走之路到了这里便撞了南墙,自由的旅人身陷囹圄。为了让他继续在互联网的海洋中漂游,我们只能外力介入把他拯救出来。
为了解决这个问题,我们引入一种“随机跳转”机制。如果这个假想的冲浪者来到了一个不指向其他任何网页的节点,我们就人为地给他随机安排一个网页,作为他下一个访问的节点。随机跳转的目标网页可以从满足均匀分布的所有网页中随机选一个。也就是说,N个网页形成Web图中的N个结点,随机跳转使得这个冲浪人以1/N的概率访问任何一个节点。当然,也有1/N的概率留在当前节点,这样我们只需要再随机跳转一次即可。在实际操作中,我们可以不需要判断冲浪者目前所在的网页到底有没有出链,直接把随机跳转操作加入到全部过程里。
当这个网上冲浪人在互联网上自由遨游一段时间之后,我们统计他访问过哪些网页,分别访问了多少次。通常我们会发现,这个随机访问人访问某些网页的次数会多于另一些网页。直观地说,就是有更多的链接从其他网页指向这些被频繁访问的网页。
PageRank计算思路就是,在随机访问过程中,越被频繁访问的网页,重要性就越高。
马尔科夫链是一个离散时间随机过程(discrete-time stochastic process),这个过程中的每一步都需要做一个随机选择。一个马尔科夫链包括N个状态(state)。在下面的计算和推导中,每一个网页,即Web图中的每一个节点,都对应我们所构造的马尔科夫链中的一个状态。马尔科夫链通过一个转移概率矩阵(transition probability matrix)P来刻画,其中,每个元素的值在[0,1]之间,并且P中每一行的元素之和为1。在任一步,马尔科夫链都可能处于N个状态之一。那么,元素 给出的就是从当前状态i到下一个状态j的条件转移概率。被称为转移概率。它仅仅依赖于当前的状态,并不理会之前经历的中间状态。这种性质被称为马尔科夫性。因此基于马尔科夫性,我们有
(1)转移概率矩阵的每一个元素都代表一个取值为[0,1]的概率值,即
(2)转移概率矩阵的第i行表示从第i个节点出发跳转到各个节点的概率,概率之和为1。即
代入我们所讨论的Web图的情景,的值即为当前访问第i个节点时,下一个访问的节点为第j个节点的概率。
让我们给出一个简单的例子来更好地理解马尔科夫链和转移概率矩阵。
有这么一个包含三个节点的Web图。
我们来计算这个Web图对应的转移概率矩阵。我们注意到,节点A有两个出链,分别指向B和C。由于我们假设冲浪人在节点网络中随机跳转,因此他以1/2的相等概率跳转到B或C节点。那么从节点A跳转到节点A的概率为0,从节点A跳转到节点B的概率为1/2,从节点A跳转到节点C的概率为1/2。
转移概率矩阵的第一行代表第一个节点(这里我们认为是节点A)的转移概率,因此第一行行向量已经计算完毕,为(0,0.5,0.5)。
节点B和节点C都只有一个出链,指向节点A。因此行向量均为(1,0,0)。表示从节点B或者节点C出发,跳转到节点A的概率为1,跳转到其他节点的概率均为0。
所以对应的转移概率矩阵为:
下面介绍一种具体化的计算转移概率矩阵的方法,并且加入随机跳转机制。
Web图的邻接矩阵A可以如下求出:如果存在网页到网页的一 条链接,那么=1,否则 =0。这样,我们很容易就可以从N×N的矩阵A推导出马尔科夫链的转移概率矩阵P。如果A的某一行没有1,则用1/N代替每个元素。对于其他行的处理如下:
(1) 每行的每一个1都除以该行1的个数,比如说某行有3个1,则用1/3代替每个1;
(2) 上一步处理后的结果矩阵乘以1−α;
(3) 上一步处理后的结果矩阵中的每个元素都加上α/N。
这样,我们就可以通过概率向量给出冲浪者在任一时间所处位置的概率分布。当时,冲浪者可能处于某个初始状态,这时中的相应元素为1,其他元素为0。比如说,一共有三个网页,我们令网页编号与概率向量中元素的下标一一对应。假设冲浪者从第1个网页出发。此时他的位置处在1号网页的概率是1,处在2号和3号网页的概率为0。根据对应关系,概率向量的第一个元素是1,其他元素是0。所以的初始值为(1,0,0)。
概率向量初始值的获取方法与转移概率矩阵行向量的计算方法在表观上有些近似,但实际含义是不同的。概率向量初始值根据假想冲浪者的初始位置计算,转移概率矩阵行向量根据网页之间的链接关系计算。前者描述冲浪者所处位置的概率分布,后者描述Web图的节点跳转关系。
根据上述定义,在时,冲浪者的状态分布可以用概率向量P来表示。同样,=2时概率向量为,以此类推。这样,我们知道了初始状态分布和转移概率矩阵P,就能计算冲浪者在任一时刻所处状态的概率分布。
介绍一个引理,这有利于我们最后计算PageRank。出于实用考虑,这里仅对结论做出描述。
定义:一个马尔科夫链,如果存在一个正整数使得对其中所有的状态对、都满足:若是初始状态,那么对所有的>,在时刻t处于状态的概率都大于 0。此时,称该马尔科夫链是遍历马尔科夫链。
引理:对任一遍历马尔科夫链,都存在一个唯一的稳态概率,它是矩阵P的主左特征向量,并且如果是在步之内状态的访问次数,那么有
其中,是状态的稳态概率。
简单来说,这个引理的意义可以描述为:概率向量反复左乘转移概率矩阵P的运算结果,会收敛于一个稳定概率。这个稳定概率就是相应网页的 PageRank。
假设有
那么我们认为即为1号网页的PageRank值。和分别为2号和3号网页的PageRank值。这些网页的编号可以随个人喜好规定,对于计算没有影响。
下面给出一个完整的计算网页PageRank的例子。这个例子来自参考教材。
考虑一个具有3个节点1、2、3 的Web图。其中的链接为:1→2,3→2,2→1,2→3。令,求网页PageRank。
根据之前给出的计算转移概率矩阵的方法,首先分析网页链接关系,得到矩阵
每行的每一个1都除以该行1的个数,得到矩阵
结果矩阵乘以,即0.5,得到矩阵
每个元素加上α/N,即1/6,得到矩阵
这就是Web图的转移概率矩阵。
假设冲浪者的初始位置在1号节点,则对应初始状态概率分布向量。初始位置并不需要固定,可以根据个人喜好设置。这里就设置为从1号节点出发。
第一次迭代过程:
第二次迭代过程
反复迭代得到概率分布向量序列
注意到,概率向量收敛于。因此三个节点的PageRank值分别为5/18、4/9、5/18。
示例
接下来我们给出另一个问题。假设每个文档(document)是一个节点,以下图所示的结构计算每个节点的PageRank值。其中,随机跳转概率。解决这个问题的代码(包含详细注释)会附在文末。
链接分析方法——HITS
概念与计算方法
这一节里面,我们对每个网页计算两个得分:分别是hub值和authority值。hub值指的是网页的导航能力,也被称作导航度。导航度越高,就代表着这个网页指向越多越重要的网页。authority值指的是网页的权威度,权威度越高,就代表着这个网页被越多重要的网页指向。为了避免翻译上的差错,以下就用hub值和authority值称呼这两个得分。
举一个例子方便大家理解。网页A指向新华社、联合国官网、《Nature》主页、哔哩哔哩百万播放的视频;网页B指向我的CSDN主页、常州二十四中学八(7)班博客、某民科的个人网站、bilibili某个100播放的视频。那么显然网页A导航度更高,因为网页A指向的网页更加权威。另外有网页C指向了新华社、联合国官网、《Nature》主页、哔哩哔哩百万播放的视频,还有国家统计局、NASA主页和泰勒斯威夫特的网站。那么网页C的导航度比网页A更高,因为同样指向权威网页,网页C指向的网页更多。
权威度也可以用类似的直观方法理解。新华社在年度政府工作报告的报道里附了链接指向网页A,你的普通同学在微信朋友圈里附了链接指向网页B,则网页A的权威度比网页B要高。
需要注意的是,这里的“权威度”一词和我们日常语境下的权威概念语义并不完全一样。这里的权威度与政府机关、科学家和意见领袖没有直接关系,而是描述网页在Web图里被指向的情况。我在上面的例子中就提到了政府、科学家,也提到了明星和一些大众喜闻乐见的视频。具体含义参考本节第一段。
根据定义我们可以容易发现,一个好的hub网页会同时指向多个好的authority网页,而一个好的authority网页同时会被多个好的hub网页所指向。因此,我们可以给出一个hub值和authority值的循环定义,通过迭代计算求解网页的这两个值。
在 Web 图中,某个网页v的hub值记为h(v),authority值记为a(v)。对于任一节点v,初始化赋值为h(v)=a(v)=1。如果从v到y存在一条超链接,则记为 。迭代算法的核心环节是hub值和authority值的双重更新过程。整个迭代过程体现了“ 好hub网页指向好authoriy网页、好authority网页被好hub网页所指向” 的直觉思想。
迭代公式如下:
上式第一行中,每个网页的hub值设为它指向的所有网页的authority值之和。也就是说,网页v指向的页面的authority值越高,v的hub值就越高。同样,如果页面v被hub值更高的网页所指向,它的authority值也就越高。
在循环迭代过程中,会利用不断更新的hub值authority值,和反复计算所有网页的hub值和authority值。我们可以通过矩阵-向量运算来推导双重更新迭代结果。
令和分别表示所有网页的hub值和authority值向量,A表示我们所处理的网页集的邻接矩阵。这个邻接矩阵反应网页之间的链接关系。很显然,A是一个方阵,其每一行和每一列都对应 Web图的一个网页。如果存在页面到页面的链接,则有,否则。于是,可以将迭代公式用矩阵-向量的形式改写为:
其中,表示的转置矩阵。迭代公式的每一个式子的右边就是另一个式子左边的一个向量。将上面两个式子互相代入,得到如下结果:
现在,我们得到了两个特征方程,它们在形式上有很高的相似性。实际上,如果将上式中的替换为号并引入未知特征值的话,那么迭代式上面那个式子就变成矩阵的特征方程,而下面那个式子则会变成矩阵的特征方程。这样就得到了如下的表达式:
其中,、分别是矩阵及的特征值。
假定的主特征向量是唯一的,那么和最后会收敛于某个唯一的稳态向量,而具体稳态向量的取值取决于矩阵A,也就是图的结构。
于是,最后的计算形式如下:
(1) 根据要计算的网页建立Web图,分析链接结构,计算及;
(2) 计算及的主特征向量,得到最后的hub值向量和authority值向量。
(3) 利用这两个值进行网页排序。
上述链接分析的方法被称为HITS(Hyperlink-Induced Topic Search,超链导向的主题搜 索)。
示例
接下来我们给出一个问题,以下图所示的结构为例计算每个document的authority值和hub值。解决这个问题的代码(包含详细注释)会附在文末。
实现代码
PageRank例题
程序实现步骤
首先根据图所示的结构初始化邻接矩阵,并且将概率向量初始化为(1,0,0)。然后取α=0.05,根据公式计算出马尔可夫转移概率矩阵。完成初始化。接着开始进行幂迭代,反复迭代直到向量收敛,停止计算并且返回PageRank值。
进行幂迭代,按照公式进行计算。计算过程中累加差的平方记为difference。当迭代后向量的各分量均变化不大时,我们认为已经收敛。具体操作是一次迭代操作各分量变化量的平方和小于0.00001(也可以是其他足够小的值),即认为概率分布向量已收敛。
注意计算矩阵乘法结果的每一个值时,不能将算出的值直接覆盖掉原值。因为计算新向量其他值仍然需要用到用过的原值,直接覆盖会导致之后计算的分量计算错误。需要先把计算结果暂存下来,然后再统一更新向量各分量的值。
#include <bits/stdc++.h>
#define SIZE 3
using namespace std;
int main() {
double difference;//保存每轮迭代差值的平方和
int A[SIZE][SIZE]= {{0,1,1},{0,0,1},{0,1,0}}; //邻接矩阵
double P[SIZE][SIZE]= {0}; //马尔可夫转移概率矩阵
double x[SIZE]= {1,0,0}; //概率向量
int i,j;
double a=0.05; //赋初值
cout<<"邻接矩阵初值为:"<<endl;
for(i=0; i<SIZE; i++) {
for(j=0; j<SIZE; j++)
cout<<A[i][j]<<" ";
cout<<endl;
}
//计算马尔可夫转移概率矩阵
int sum;
for(i=0; i<SIZE; i++) {
sum=0;//存储每行1的个数
for(j=0; j<SIZE; j++)
if(A[i][j]==1)
sum++;
if(sum==0) {
for(j=0; j<SIZE; j++)
P[i][j]=1.0/SIZE;
} else {
for(j=0; j<SIZE; j++)
P[i][j]=(A[i][j]*1.0/sum)*(1-a)+(a/SIZE);
}
}
cout<<"马尔可夫转移概率矩阵为:"<<endl;
for(i=0; i<SIZE; i++) {
for(j=0; j<SIZE; j++)
cout<<P[i][j]<<" ";
cout<<endl;
}
//计算PageRank
cout<<"x=(";
for(i=0; i<SIZE-1; i++)
cout<<x[i]<<" ";
cout<<x[i]<<")"<<endl;
sum=0;
while(1) {
difference=0;
double tempx[SIZE]= {0};
for(i=0; i<SIZE; i++) {
double temp=0;
for(j=0; j<SIZE; j++) {//计算矩阵乘法
temp+=x[j]*P[j][i];
}
if(x[i]!=temp)
difference+=(x[i]-temp)*(x[i]-temp);//计算迭代变化
tempx[i]=temp;
}
for(i=0; i<SIZE; i++) {//更新向量
x[i]=tempx[i];
}
sum++;
if(difference<0.00001)//如果向量收敛则终止
break;
/*cout<<"x=(";
for(i=0; i<SIZE-1; i++)
cout<<x[i]<<" ";
cout<<x[i]<<")"<<endl;*/
}
cout<<"经过"<<sum<<"次迭代,概率向量收敛于:(";
for(i=0; i<SIZE-1; i++)
cout<<x[i]<<" ";
cout<<x[i]<<")"<<endl;
}
程序运行结果
最终PageRank值:
文档1:0.01667
文档2:0.49167
文档3:0.49167
笔算结果:
HITS计算例题
程序实现步骤
用画图工具给图示结构中的各顶点标上编号。
用图的形式储存图示中的信息。建立有向图邻接矩阵。同时将authority值和hub值初始化,并进行归一化。计算A的转置,完成数据初始化。
轮流计算和的值,并进行迭代终止条件判断。若某一向量各分量的值和上一次迭代结果差距不大,则直接终止迭代。注意每次迭代计算完毕后,需要对算出的向量进行归一化,即使得迭代结果向量各分量和化为1。否则各分量将无限增大,永远不会收敛。
#include <bits/stdc++.h>
#define SIZE 9
using namespace std;
int main() {
int A[SIZE][SIZE]= {{0,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,0,0,0,0},
{1,0,1,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,1,0,0,0,0,0,0},
{0,0,1,0,0,0,1,0,0}
}; //有向图邻接矩阵
double h[SIZE];
double a[SIZE];
double tempx[SIZE];
int AT[SIZE][SIZE]= {0};
double difference;
int i,j;
for(i=0; i<SIZE; i++)
for(j=0; j<SIZE; j++) {
AT[j][i]=A[i][j];
h[j]=1.0/(sqrt(SIZE));//初始化为根号n分之1,使得向量的模为1
a[j]=1.0/(sqrt(SIZE));
}
//求A的转置
double sum;
while(1) {
difference=0;
sum=0;
for(i=0; i<SIZE; i++) {
double temp=0;
for(j=0; j<SIZE; j++)
temp+=a[j]*A[i][j];//矩阵乘法
tempx[i]=temp;//保存到临时向量
sum+=temp;//计算各分量和
}
for(i=0; i<SIZE; i++) { //向量归一化
tempx[i]=tempx[i]/sum;
if(tempx[i]!=h[i])//计算迭代条件
difference+=(h[i]-tempx[i])*(h[i]-tempx[i]);
h[i]=tempx[i];
}
if(difference<0.00001)
break;
cout<<" hub= ";
for(i=0; i<SIZE; i++)
cout<<fixed<<setprecision(5)<<h[i]<<" ";
cout<<endl;
//h=Aa
difference=0;
sum=0;
for(i=0; i<SIZE; i++) {
double temp=0;
for(j=0; j<SIZE; j++)
temp+=h[j]*AT[i][j];//矩阵乘法
tempx[i]=temp;//保存到临时向量
sum+=temp;//计算各分量和
}
for(i=0; i<SIZE; i++) { //向量归一化
tempx[i]=tempx[i]/sum;
if(tempx[i]!=a[i])//计算迭代条件
difference+=(a[i]-tempx[i])*(a[i]-tempx[i]);
a[i]=tempx[i];
}
cout<<"authority= ";
for(i=0; i<SIZE; i++)
cout<<fixed<<setprecision(5)<<a[i]<<" ";
cout<<endl;
if(difference<0.00001)
break;
//a=ATh
}
cout<<" hub值为: ";
for(i=0; i<SIZE; i++)
cout<<fixed<<setprecision(5)<<h[i]<<" ";
cout<<endl;
cout<<"authority值为:";
for(i=0; i<SIZE; i++)
cout<<fixed<<setprecision(5)<<a[i]<<" ";
cout<<endl;
return 0;
}
//直接累加法和特征向量计算法
程序运行结果
有最终计算结果为:
Document | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Hub | 0.00004 | 0.17526 | 0.00000 | 0.00004 | 0.27805 | 0.00000 | 0.00000 | 0.20808 | 0.33854 |
Authority | 0.15435 | 0.00009 | 0.45894 | 0.00000 | 0.00009 | 0.09879 | 0.28775 | 0.00000 | 0.00000 |
其他
基于连接结构信息的网页排序算法在实际应用上存在钻空子的空间。网页设计者可以在网页内放置大量无法与用户交互的不可见超链接,起到刷高评分的效果。尽管这些网页不具备很好的导航效果,但是网页排序算法有可能被其骗过,从而使它获得一个徒有其表的排名。另外有一些网页设计者会让一些网页毫无意义地互相指向来刷高评分,尽管对于用户而言循环指向的网页们和互相踢皮球的政府部门一样低效且无用。
参考书目
《信息检索导论》Christopher D. Manning(美)Prabhakar Raghavan(美)Hinrich Schütze(德)(第21章链接分析)