未来は僕以外の手の中

SI屋がIT技術やビジネスのことなどを気ままに書き綴ってみるなど

SciPyを用いて潜在的意味解析(LSA)

自然言語処理の技法の1つに、潜在的意味解析(LSA)というものがある。


単語文書行列Aがあった場合、特異値分解(SVD)により
 A=UΣV
に分解し、特異値を大きいほうからk個使って
 Ak=UkΣkk
のように階数の低減を行うことで、階数kのAへの近似を最小誤差で得ることができる。


つまり特異値分解の計算さえできてしまえばLSAもすぐできるわけだが、
pythonの数値解析モジュールScipyにかかれば特異値分解もあっという間である。


まずは特異値分解まで↓

from numpy import *
from scipy import linalg

A = matrix([ [5, 8, 9, -4, 2, 4],
             [2, -4, 9, 4, 3, 3],
             [-3, 4, 8, 0, 5, 6],
             [-2, 5, 4, 7, 0, 2] ])

u, sigma, v = linalg.svd(A) # 特異値分解
rank = shape(A)[0] # 階数
u = matrix(u)
s = matrix(linalg.diagsvd(sigma, rank, rank))
v = matrix(v[:rank, :])
print u*s*v

出力結果1

  5.00000000e+00   8.00000000e+00   9.00000000e+00  -4.00000000e+00   2.00000000e+00   4.00000000e+00
2.00000000e+00 -4.00000000e+00 9.00000000e+00 4.00000000e+00 3.00000000e+00 3.00000000e+00
-3.00000000e+00 4.00000000e+00 8.00000000e+00 4.44089210e-16 5.00000000e+00 6.00000000e+00
-2.00000000e+00 5.00000000e+00 4.00000000e+00 7.00000000e+00 -1.33226763e-15 2.00000000e+00

これより、ちゃんとA=UΣVになっていることが分かる。


なお、linalg.svdの出力であるsigma

[ 19.51915253  10.39603796   8.26622837   5.62090278]
のように特異値のみがリストで帰ってくるので、linalg.diagsvdによって行列の形に戻してやる必要がある。



さて、もとの行列の階数が4なので、LSAにより階数3に低減して近似を行ってみる。

z = 3
u3 = u[:, :z]
s3 = matrix(linalg.diagsvd(sigma[:z], z, z))
v3 = v[:z, :]

print u3*s3*v3

出力結果2

  3.58369761  7.65615451  8.72804857 -4.88803344  2.93407657  4.64581738
1.10005237 -4.21848649 8.82719648 3.43572531 3.59353144 3.41036563
0.06134317 4.74322337 8.58782408 1.91948777 2.98098982 4.60406321
-3.62255534 4.60608106 3.68844489 5.98264423 1.07010406 2.73986632

微妙にずれはありつつも、まぁなんとか元の行列に近似されてるといえる。


なお、

print sum(sigma[:z])/sum(sigma)
→0.871675688141

より累積寄与率は約0.872である。つまり、出力結果2は元の行列Aの情報を約87.2%含んでいるといえる。


いずれにせよ、Scipyを用いれば高度な計算も簡単に出来てしまう。
やはりScipyはええもんですな〜。