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进行能量规一化,这样是可以保证结果的收敛性的。但是这种做法与原始算法的结果会有一点细微的出入。因此建议按照原始的公式进行迭代求解
  • 相关阅读:
    Arthas线上问题排查
    如何快速增加pdf书签,解除pdf限制
    数组指针——指向数组的指针(通过指针控制数组)
    指针和数组直接对应关系之如何记忆
    C/C++语言中指针数组和数组指针比较区别
    int最大值+1为什么是-2147483648最小值-1为什么是2147483647
    电脑进行二进制加减运算方法
    C/C++语言中的函数参数传参三种对比
    Python基于VS2013 开发环境搭建 Hello World 10分钟搞定
    算法的复杂度包括时间复杂度和空间复杂度分别如何计算?
  • 原文地址:https://www.cnblogs.com/jcchen1987/p/4271234.html
Copyright © 2011-2022 走看看