SciPyを用いて潜在的意味解析(LSA)
自然言語処理の技法の1つに、潜在的意味解析(LSA)というものがある。
単語文書行列Aがあった場合、特異値分解(SVD)により
A=UΣV
に分解し、特異値を大きいほうからk個使って
Ak=UkΣkVk
のように階数の低減を行うことで、階数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はええもんですな〜。