challenge PageRankの計算

ページランク - Wikipediaを求めるプログラムを作ってください。(PageRankはGoogleの商標です)

詳しい導出方法はGoogle の秘密 - PageRank 徹底解説の3章に載っていますが、 簡単に説明すると

  1. ページがn枚ある場合、n×nの2次元配列を用意する。
  2. ページiからページjにリンクがある場合、mat[j][i] = 1 / num_link[i]とする。ただしnum_link[i]はi番目のページから出ているリンクの総数。
  3. 行列計算ライブラリを使ってできあがった2次元配列の固有値、固有ベクトルを求める。
  4. 出力された固有ベクトルから合計が1になるようなPageRankを算出する

という流れになります。

Pythonで表現すると下のようになります。 ?????の部分は空行を入れて10行でしたので 何百行ものコードになってしまった場合は きっとお題の趣旨から外れていると思います。 このお題の趣旨は「行列計算ライブラリを使って」PageRankを計算することなので、 自力で固有値の計算を実装することは求められていません。

data = {
    1: [2, 3, 4, 5, 7],
    2: [1],
    3: [1, 2],
    4: [2, 3, 5],
    5: [1, 3, 4, 6],
    6: [1, 5],
    7: [5],
}

?????

print pagerank
# [0.303514376997, 0.166134185304, 0.140575079872,
#  0.105431309904, 0.178913738019, 0.0447284345048,
#  0.0607028753994]
このお題はところてんさんの「行列演算系のお題が欲しい」という要望を元に考えたものです。ありがとうございました。

Posted feedbacks - Python

正直、行列というか数学自体の素養がないので、
お題からリンクされているサイトのコードを、
SciPyを使って内容を理解せずに移植しました。

特に7行めあたりが怪しい。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from scipy import absolute
from scipy.linalg import eig, norm

def pagerank(data):
  n = len(data)
  l, v = eig([[1.0 / len(data[i]) if j in data[i] else 0 for i in range(1, n+1)] for j in range(1, n+1)])
  ev = v[:, (l == absolute(l).max()).nonzero()[0][0]]
  return ev / norm(ev, 1)

data = {
  1: [2, 3, 4, 5, 7],
  2: [1],
  3: [1, 2],
  4: [2, 3, 5],
  5: [1, 3, 4, 6],
  6: [1, 5],
  7: [5],
}

for x in pagerank(data): print float(x)

#2349の7行目は何をやっているのかよくわかりません…
__getslice__がオーバーライドされてるんでしょうか…。

とりあえず僕のコードを貼ってみます。
数学的にはさほど難しいことはしていなくて、
単に固有値を求めて絶対値を取ってから
合計でそれぞれを割って合計が1になるように正規化しています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
data = {
    1: [2, 3, 4, 5, 7],
    2: [1],
    3: [1, 2],
    4: [2, 3, 5],
    5: [1, 3, 4, 6],
    6: [1, 5],
    7: [5],
}

m = [[1.0 / len(data[j]) if i in data[j] else 0
      for j in range(1, 8)]
      for i in range(1, 8)]

from numpy import array
from numpy.linalg import eig

(v, d) = eig(array(m))

pagerank = [abs(row[0]) for row in d]
k = sum(pagerank)
pagerank = [x / k for x in pagerank]

print pagerank
# [0.303514376997, 0.166134185304, 0.140575079872,
#  0.105431309904, 0.178913738019, 0.0447284345048,
#  0.0607028753994] 

v[:, i] でi列目のスライスを得られるようですけど、このあたりは
にしおさんのコードでは20行目でrow[0]と、0で決め打ちになってますが、
オリジナルのOctaveのコードではfind(abs(diag(D)) == max(abs(diag(D))))と、
計算でだしてますよね。
この部分を推測をまじえ、固有値の絶対値の最大値が存在する位置をインデックス値として
一列分取り出す、と解釈したものがあの7行目です。

原理が理解できてないんで、ここが0でいいのか、きちんと計算すべきなのか、
わかりません。

ついでに、Numeric付属のMLabを使ったバージョンも投稿します。

MLabのeig関数とSciPyのeig関数では戻り値の行と列が逆になっているような気が。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import MLab

def pagerank(data):
  a = [[1.0 / len(data[i]) if j in data[i] else 0 for i in range(1, len(data)+1)] for j in range(1, len(data)+1)]
  d, v = MLab.eig(MLab.array(a))
  ev = v[MLab.nonzero(d == MLab.max(MLab.absolute(d)))[0], :]
  return (ev / MLab.sum(ev)).real

data = {
  1: [2, 3, 4, 5, 7],
  2: [1],
  3: [1, 2],
  4: [2, 3, 5],
  5: [1, 3, 4, 6],
  6: [1, 5],
  7: [5],
}

for x in pagerank(data): print x

>固有値の絶対値の最大値が存在する位置をインデックス値として
>一列分取り出す、と解釈したものがあの7行目です。

なるほどなるほど。
「最大固有値は(少なくともNumPyを使って固有値を求めた場合は)0番目にある」
と解釈したのが僕のコードです。

最大固有値が何番目にあるのかをチェックするバージョンも作ってみました。

# あと余談ですがnumpyはLAPACKを使っているということがわかったのでタグを付けときます
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
data = {
    1: [2, 3, 4, 5, 7],
    2: [1],
    3: [1, 2],
    4: [2, 3, 5],
    5: [1, 3, 4, 6],
    6: [1, 5],
    7: [5],
}

m = [[1.0 / len(data[j]) if i in data[j] else 0
      for j in range(1, 8)]
      for i in range(1, 8)]

from numpy import array
from numpy.linalg import eig

(v, d) = eig(array(m))

max_v = list(v).index(max(v))
pagerank = [abs(row[max_v]) for row in d]
k = sum(pagerank)
pagerank = [x / k for x in pagerank]

print pagerank

Index

Feed

Other

Link

Pathtraq

loading...