Pythonで特異値分解する方法(SciPy利用)

最近使う機会があったので、特異値分解について紹介します。
まず、$A$をランク$r$の$m$行$n$列の実行列とします。
(複素行列も考慮したい場合は、この後出てくる転置行列を随伴行列に読み替え、直行行列をユニタリ行列で読み替えてください。)

このとき、$U$は$m\times m$の直行行列、$V$は$n\times n$の直行行列、Sは非対角成分が0の$m \times n$行列を用いて、
$$A = USV^{\top}$$
と分解することを特異値分解と言います。
$S$の対角成分のうち$0$でないもの(特異値)の個数が$A$のランク$r$になります。

さて、この特異値分解をPythonで実行する方法は複数あり、numpy, SciPy, scikit-learnにそれぞれ実装があります。
参考:
numpy.linalg.svd
scipy.linalg.svd
sklearn.decomposition.TruncatedSVD

これらの使い分けですが、機械学習のパイプラインに組み込んだり、可視化が目的の時など次元削減のために利用するのであればscikit-learnがおすすめです。
それ以外の場合は、SciPyが良いでしょう。numpyより設定できるオプションが多く、また、いくつか補助的な便利関数が用意されています。

とりあえず乱数で生成した行列を、SciPyで特異値分解してみます。


import numpy as np
import scipy

m = 5
n = 3

np.random.seed(110)  # 乱数固定
A = np.random.randint(-10, 10, size=(m, n))
print(A)
"""
[[-10  -7   5]
 [  5 -10   6]
 [  6  -4  -8]
 [ -2  -1  -5]
 [-10   7  -9]]
"""

U, S_diags, V_t = scipy.linalg.svd(A)

# U
print(U)
"""
U:
[[ 0.10801327  0.81765612 -0.427367   -0.18878079 -0.31857632]
 [ 0.62638958  0.06527027 -0.28451679  0.07142207  0.71925307]
 [ 0.04632544 -0.55033827 -0.69658511 -0.39810108 -0.226421  ]
 [-0.16944935 -0.04718317 -0.43857367  0.87461141 -0.1084836 ]
 [-0.75173806  0.14859275 -0.24254891 -0.18929111  0.56404698]]
"""

# Sの対角成分:
print(S_diags)
# [19.70238709 15.08068891  9.76671719]

# Vの転置行列:"
print(V_t)
"""
[[ 0.51699558 -0.6241887   0.58575083]
 [-0.83177902 -0.20473932  0.51597042]
 [ 0.20213668  0.75396968  0.62503639]]
"""

変数名で表現してみましたが、 $U$以外の二つは少し癖のある形で返ってきます。
$S$は対角成分しか戻ってきませんし、$V$は転置された状態で戻されます。
再度掛け算して結果を角にする時などは注意が必要です。

さて、$S$は対角成分だけ返ってきましたが、これを$m\times n$行列に変換するには、専用の関数が用意されています。
scipy.linalg.diagsvd


S = scipy.linalg.diagsvd(S_diags, m, n)
print(S)
"""
[[19.70238709  0.          0.        ]
 [ 0.         15.08068891  0.        ]
 [ 0.          0.          9.76671719]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]]
"""

あとは、 $USV^{\top}$計算して、元の$A$になることをみておきましょう。


print(U@S@V_t)
"""
[[-10.  -7.   5.]
 [  5. -10.   6.]
 [  6.  -4.  -8.]
 [ -2.  -1.  -5.]
 [-10.   7.  -9.]]
"""

元の値が復元できましたね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です