Help us understand the problem. What is going on with this article?

特異値分解を詳しく解説

はじめに

特異値分解は機械学習ではよく使われるテクニックだが、大学の教養過程で使うような線形代数の教科書には載っていなかったりする。

書店で色々探した結果、こちらの本に分かりやすい解説があり、理解が深まったのでまとめておく。

線形代数セミナー: 射影,特異値分解,一般逆行列 金谷 健一 (著)

今回は特異値分解の解説のみで、具体的な活用法はまた別の記事に書く(予定)。

定義

${\rm rank}(A)=r$ の $m \times n$ 行列 $A$ を考える。 ただし、$r < m < n$とする。
$\rm rank$って何?という方はあまり気にしなくても大丈夫ですが、気になる方はこちらの記事を参照。
このとき、$A$の特異値分解は下記のように表される。

$$A = U \Gamma V^T$$

ここで、$U, V$は直交行列であり、$\Gamma$は対角行列である。$\Gamma$の対角成分$\sigma_1,..., \sigma_r$が行列$A$の特異値と呼ばれる。特異値は0より大きい値である(0も特異値とする場合もある)。

$U, V$の列ベクトルをそれぞれ左特異ベクトル、右特異ベクトルと呼ぶ。$U, V$がそれぞれ$\Gamma$の左、右に来るためだと覚えておけばよい。

特異ベクトルの定義は下記のようにも書ける。固有値・固有ベクトルの定義($A \boldsymbol v = \lambda \boldsymbol v$)に似ているが、左辺と右辺に含まれるベクトルがそれぞれ $\boldsymbol u, \boldsymbol v$ と異なるベクトルになっていることに注意。

A \boldsymbol v = \sigma \boldsymbol u \\
A^T \boldsymbol u = \sigma \boldsymbol v

ここで、これらの式にそれぞれ、$A^T, A$を左からかけると、

A^T A \boldsymbol v = \sigma  A^T \boldsymbol u = \sigma^2 \boldsymbol v \\
AA^T \boldsymbol u = \sigma A \boldsymbol v = \sigma^2 \boldsymbol u

上式の最左辺と最右辺を比べると、前述した固有ベクトルの定義と同じ形になっている。
よって、$\boldsymbol u, \boldsymbol v$ はそれぞれ、$AA^T, A^T A$ の固有ベクトルであり、固有値はいずれも$\sigma^2$となる。

つまり、

(行列$A$の特異値) $^2=$ (行列 $AA^T, A^TA$ の固有値)

ということが言える。(なぜか中央寄せにならない??)[error]()

特異値分解のイメージとしては、対称行列(行と列の数が等しい行列)で定義される固有値を、一般の行列に拡張したものだと考えると良さそう。実際、対称行列の場合は固有値と特異値は一致する。

表記法

特異値分解の表記方法はいくつかあって、自分はちょっと混乱した。

まず、0を特異値に含めて、$\Gamma$を $m \times m$ の行列とする場合もある(下図(2))。ただし、 $\boldsymbol u, \boldsymbol v$ は特異値0に対応する左特異ベクトル、右特異ベクトルを並べた行列である。表記(1)から拡張されたグレーの部分は、結局かけ合わせて0になるので、実質的な影響はない。

また、$\Gamma$を対角行列ではなく、$m \times n$の行列とした表記もある(下図(3))。これは、$\Gamma$を 零行列でさらに拡張して$m \times n$に、$V$も$n \times n$に拡張したものだと考えれば良い。やはり、グレーの部分は掛け合わせて0になるので、実質的な影響はない。


Pythonコード

pythonでは、numpyのlinalgモジュールにあるsvdという関数で特異値分解を実行することができる。
この関数が返す $U, V$ はデフォルトでは表記(3)に対応する対称行列である。ただし、full_matrices=Falseとすると、表記(2)に対応する $U, V$ が得られる。
また、$\Gamma$については、行列ではなく(0を含む)特異値の配列が返される。行列Aのランクは2なので、0でない特異値の数は2つとなっている。

特異値分解の$\Gamma$にするためには、np.diagで対角行列にする必要がある。
確認のため、$U, \Gamma, V$の積をとってみると、確かに元の行列$A$と一致することが分かる。

import numpy as np
from numpy.linalg import svd, matrix_rank

# rank=2の 3x4行列を作成
A = np.array([[2, 4, 1, 3], [1, 5, 3, 2], [5, 7, 0, 7]])

print('matrix A\n', A)
print('rank: ', matrix_rank(A))

# singular value decomposition
u, s, vh = svd(A)
print('\nSVD result')
print('shape of u, s, vh:', u.shape, s.shape, vh.shape)
print('singular values:', s.round(2))

# full_matrices=Falseの場合
u, s, vh = svd(A, full_matrices=False)
print('\nSVD result (full_matrices: False)')
print('shape of u, s, vh:', u.shape, s.shape, vh.shape)

# 復元
# A_re = (u @ np.diag(s, -1)[1:] @ vh).round(2)
A_re = (u @ np.diag(s) @ vh).round(2)
print('\nreconstructed A:\n', A_re)
実行結果
matrix A
 [[2 4 1 3]
 [1 5 3 2]
 [5 7 0 7]]
rank:  2

SVD result
shape of u, s, vh: (3, 3) (3,) (4, 4)
singular values: [13.39  3.58  0.  ]

SVD result (full_matrices: False)
shape of u, s, vh: (3, 3) (3,) (3, 4)

reconstructed A:
 [[ 2.  4.  1.  3.]
 [ 1.  5.  3.  2.]
 [ 5.  7. -0.  7.]]
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away