zoukankan      html  css  js  c++  java
  • PageRank算法原理及实现

    PageRank算法原理介绍

      PageRank算法是google的网页排序算法,在《The Top Ten Algorithms in Data Mining》一书中第6章有介绍。大致原理是用户搜索出的多个网页需要按照一定的重要程度(即后面讲的权重)排序,每个网页的权重由所有链接到它的其他网页的权重的加权和,加权系数为每个网页链出的网页数的倒数,也就是说每个网页的权重会平均分配到其链向的所有网页。
      例如A链接到B和C,B链接到C,C链接到A,P(X)表示X的权重,如下图所示
      则每个节点的权重关系为:
                                       P(A) = P(C)
                                       P(B) = P(A)/2
                                       P(C) = P(A)/2 + P(B)
      一般地,可以写成个线性方程组形式:
                                       P = AP
      如此通过迭代即可求出最终各个网页的权重值。
      但是,当存在某些特殊情况时,如某个网页的链入或链出数为0,则迭代不会收敛。因此在上述算法中增加了个阻尼系数d,d表示用户会继续点击下一网页的概率,同时用户在1-d的概率下会随机访问到任意网页,那么上面的公式会修正为:
                                       P = (1-d)/N * ones(N,1) + d*AP
      其中N为所有网页数,ones(N,1)表示N行1列的全1矩阵。通过上述公式可以迭代计算出最终各网页权重。
      详细介绍可以参考wiki百科
     
    算法实现

      首先声明这个CPageRank类:
     1 typedef unsigned char BYTE;
     2 class CPageRank
     3 {
     4 public:
     5     CPageRank(int nWebNum = 5, bool bLoadFromFile = false);
     6    ~CPageRank();
     7 
     8    int Process();
     9    float *GetWeight();
    10 
    11 private:
    12    void InitGraph(bool bLoadFromFile = false);
    13    void GenerateP();
    14    BYTE *m_pu8Relation; //节点关系 i行j列表示j是否指向i
    15    float *m_pf32P; //转移矩阵
    16    
    17    //缓存
    18    float *m_pf32Weight0;
    19    float *m_pf32Weight1;
    20    int *m_pl32Out;
    21    int m_nNum;
    22    float m_f32DampFactor; //阻尼系数
    23 
    24    int m_nMaxIterTime;
    25    float m_f32IterErr;
    26    float *m_pf32OutWeight;//输出的最终权重
    27 };

      下面就是具体各个函数的实现了。

      首先构造函数主要是初始化一些迭代相关变量,分配空间等,这里把生成节点指向图的工作也放在这里,支持直接随机生成和读取二进制文件两种方式。

     1 CPageRank::CPageRank(int nWebNum, bool bLoadFromFile)
     2 {
     3      m_f32DampFactor = 0.85;
     4      m_nMaxIterTime = 1000;
     5      m_f32IterErr = 1e-6;
     6  
     7      m_nNum = nWebNum;
     8      m_pu8Relation = new BYTE[m_nNum * m_nNum];
     9      m_pf32P = new float[m_nNum * m_nNum];
    10      m_pf32Weight0 = new float[m_nNum];
    11      m_pf32Weight1 = new float[m_nNum];
    12      m_pl32Out = new int[m_nNum];//每个节点指向的节点数
    13  
    14      InitGraph(bLoadFromFile);  
    15 }

      析构函数自然就是释放内存了:

    1 CPageRank::~CPageRank()
    2  {
    3      delete []m_pl32Out;
    4      delete []m_pf32P;
    5      delete []m_pf32Weight0;
    6      delete []m_pf32Weight1;
    7      delete []m_pu8Relation;
    8  }

      下面就是随机生成或读取文件产生节点指向关系,如果随机生成的话,会自动保存当前生成的图,便于遇到问题时可复现调试:

     1 void CPageRank::InitGraph(bool bLoadFromFile)
     2  {
     3      FILE *pf = NULL;
     4      if(bLoadFromFile)
     5      {
     6          pf = fopen("map.dat", "rb");
     7          if(pf)
     8          {
     9              fread(m_pu8Relation, sizeof(BYTE), m_nNum * m_nNum, pf);
    10              fclose(pf);
    11              return;
    12          }
    13      }
    14  
    15      //建立随机的节点指向图
    16      int i, j;
    17      srand((unsigned)time(NULL));
    18      for(i = 0; i < m_nNum; i++)
    19      {
    20          //指向第i个的节点
    21          for(j = 0; j < m_nNum; j++)
    22          {
    23              m_pu8Relation[i * m_nNum + j] = rand() & 1;
    24          }
    25          //自己不指向自己
    26          m_pu8Relation[i * m_nNum + i] = 0;
    27      }
    28  
    29      pf = fopen("map.dat", "wb");
    30      if(pf)
    31      {
    32          fwrite(m_pu8Relation, sizeof(BYTE), m_nNum * m_nNum, pf);
    33          fclose(pf);
    34      }         
    35  }

      既然已经产生了各个节点的关系了,那PageRank的核心思想就是根据关系,生成出上面的转移矩阵P:

     1 void CPageRank::GenerateP()
     2  {
     3      int i,j;
     4      float *pf32P = NULL;
     5      BYTE *pu8Relation = NULL;
     6  
     7      //统计流入流出每个节点数
     8      memset(m_pl32Out, 0, m_nNum * sizeof(int));
     9      pu8Relation = m_pu8Relation;
    10      for(i = 0; i < m_nNum; i++)
    11      {
    12          for(j = 0; j < m_nNum; j++)
    13          {
    14              m_pl32Out[j] += *pu8Relation;
    15              pu8Relation++;
    16          }
    17      }
    18  
    19      //生成转移矩阵,每个节点的权重平均流出
    20      pu8Relation = m_pu8Relation;
    21      pf32P = m_pf32P;
    22      for(i = 0; i < m_nNum; i++)
    23      {
    24          for(j = 0; j < m_nNum; j++)
    25          {
    26              if(m_pl32Out[j] > 0)
    27              {
    28                  *pf32P = *pu8Relation * 1.0f / m_pl32Out[j];
    29              }
    30              else
    31              {
    32                  *pf32P = 0.0f;
    33              }
    34              pu8Relation++;
    35              pf32P++;
    36          }
    37      }
    38  
    39      //考虑阻尼系数,修正转移矩阵
    40      pf32P = m_pf32P;
    41      for(i = 0; i < m_nNum; i++)
    42      {
    43          for(j = 0; j < m_nNum; j++)
    44          {
    45              *pf32P = *pf32P * m_f32DampFactor;
    46              pf32P++;
    47          }
    48      }
    49  }

      接下来就需要求解出各个节点的权重,process函数里先调用GenerateP生成出P矩阵,然后采用迭代法求解,当时为了测试收敛速度,直接返回了迭代次数:

     1 int CPageRank::Process()
     2  {
     3      int i,j,k,t;
     4      float f32MaxErr = 0.0f;
     5      float *pf32Org = m_pf32Weight0;
     6      float *pf32New = m_pf32Weight1;
     7      float f32MinWeight = (1 - m_f32DampFactor) / m_nNum;
     8  
     9      //设置初始值,全1
    10      for(i = 0; i < m_nNum; i++)
    11      {
    12          pf32Org[i] = 1.0f / m_nNum;//rand() * 2.0f / RAND_MAX;
    13      }
    14  
    15     //生成P矩阵
    16      GenerateP();
    17  
    18      //迭代
    19      for(t = 0; t < m_nMaxIterTime; t++)
    20      {
    21          //开始迭代
    22          f32MaxErr = 0.0f;
    23          for(i = 0; i < m_nNum; i++)
    24          {
    25              pf32New[i] = f32MinWeight;
    26              int l32Off = i * m_nNum;
    27              for(j = 0; j < m_nNum; j++)
    28              {
    29                  pf32New[i] += m_pf32P[l32Off + j] * pf32Org[j];
    30              }
    31  
    32              float f32Err = fabs(pf32New[i] - pf32Org[i]);
    33              if(f32Err > f32MaxErr)
    34              {
    35                  f32MaxErr = f32Err;
    36              }
    37          }
    38 
    39          //迭代误差足够小,停止
    40          if(f32MaxErr < m_f32IterErr)
    41          {
    42              break;
    43          }
    44  
    45          //交换2次迭代结果
    46          float *pf32Temp = pf32Org;
    47          pf32Org = pf32New;
    48          pf32New = pf32Temp;
    49      }
    50  
    51      //迭代结果存在pf32New中
    52      m_pf32OutWeight = pf32New;
    53      return t;
    54  }

      最后的结果已经存在了m_pf32OutWeight中了,下面函数直接传出结果:

    1  float * CPageRank::GetWeight()
    2  {
    3      return m_pf32OutWeight;
    4  }

      这样,整个算法就算完成了,考虑到篇幅,贴上来的代码把opencv显示相关的代码去掉了,完整代码见https://bitbucket.org/jcchen1987/mltest。

      下面是结果图,即便节点数较多时,算法收敛也比较快。
     
    分析总结

      对于上面这个公式,看到网上有人假定P的总能量是1,则可以改写为P=BP的形式来进行迭代,这种方法也实现了一下,问题仍然是当存在网页链入或者链出数为0时,每次迭代后不能保证能量守恒,那么下一次就会导致P=BP这个公式不成立,从而出现迭代不收敛;一种有效的做法是每次迭代后就将P进行能量规一化,这样是可以保证结果的收敛性的。但是这种做法与原始算法的结果会有一点细微的出入。因此建议按照原始的公式进行迭代求解
  • 相关阅读:
    ECharts之柱状图 饼状图 折线图
    Vue自定义指令(directive)
    HDU 1231 最大连续子序列
    POJ 2533 Longest Ordered Subsequence
    HDU 1163 Eddy's digital Roots
    HDU 2317 Nasty Hacks
    HDU 2571 命运
    HDU 4224 Enumeration?
    HDU 1257 最少拦截系统
    HDU 2740 Root of the Problem
  • 原文地址:https://www.cnblogs.com/jcchen1987/p/4271234.html
Copyright © 2011-2022 走看看