行列は、ベクトルを変形する
次のレッスンの 主成分分析 では、「データが一番大きく広がっている向き」を求めます。その道具が、このレッスンで学ぶ 固有値・固有ベクトル と 特異値分解 です。まず、行列の見方を1つ増やします。
第6章の @ の復習
第6章では、@(行列の積)を「たくさんの内積をまとめて計算する記号」として使いました。例えば、2行2列の行列 A と、2つの数の組(ベクトル)v の積 Av は、「A の各行と v の内積」を並べたものです。
A=(2112),v=(10)⇒Av=(2×1+1×01×1+2×0)=(21)
新しい見方:行列は、矢印を別の矢印に変える
ベクトル (1,0) を、原点から点 (1,0) への矢印だと考えます。A を掛けると、矢印 (2,1) に変わりました。長さが変わり、向きも変わっています。いろいろなベクトルに A を掛けてみます。
- (1, 0)
- A を掛けた (2, 1)
- (1, 1)
- A を掛けた (3, 3)
原点から引いた4本の矢印(線分)。横向きの (1, 0) は、A を掛けると (2, 1) になり、向きが斜め上に変わる。斜め45度の (1, 1) は、A を掛けると (3, 3) になり、同じ向きのまま3倍の長さに伸びる。(1, 1) の線は、同じ向きの (3, 3) の線の上に重なっている。4本の矢印の先の点を○で示す
ほとんどのベクトルは、A を掛けると向きが変わります。ところが、(1,1) と (1,−1) の向きのベクトルだけは、向きが変わらず、長さが何倍かになるだけ でした。この特別な向きが、次のスライドの固有ベクトルです。
行列を掛けることは、平面全体を「引き伸ばしたり、回したり、押しつぶしたり」する変形だと見ることができます。ニューラルネットワークの各層の @ W も、入力をこのように変形しています。
固有値と固有ベクトル:向きを変えずに、伸ばすだけの向き
定義
行列 A を掛けても 向きが変わらず、λ 倍になるだけ のベクトル v(0ではない)を、A の 固有ベクトル、その倍率 λ(ラムダ)を 固有値 と呼びます。式で書くと、次のとおりです。
Av=λv
前のスライドの A=(2112) では、
- (1,1) の向き: A を掛けると3倍になる → 固有値 3
- (1,−1) の向き: A を掛けても1倍のまま → 固有値 1
です。固有ベクトルは「向き」が大事なので、(1,1) でも (2,2) でも同じ固有ベクトルとみなします。ふつうは、長さを1にそろえた (0.707,0.707) のような形で表します(0.707≈21)。
どう見つけるか
固有値・固有ベクトルを手で求める方法(2行2列なら2次方程式を解く)は大学の数学で学ぶので、ここでは NumPy に任せます。
import numpy as np
A = np.array([[2.0, 1.0],
[1.0, 2.0]])
vals, vecs = np.linalg.eigh(A) # 固有値と固有ベクトル(対称な行列用)
print(vals) # → [1. 3.](小さい順)
print(vecs) # → [[-0.707 0.707]
# [ 0.707 0.707]](各「列」が固有ベクトル)
np.linalg.eigh は、対称な行列(左上から右下への対角線について、折り返すと同じになる行列)用の関数です。固有値は小さい順に並びます
- 固有ベクトルは、
vecs の 列 です(vecs[:, 1] が固有値3の固有ベクトル (0.707,0.707))。行ではないことに注意します
- 1列目は (−0.707,0.707) で、(1,−1) と逆向きですが、同じ向きの線上にあるので、同じ固有ベクトルです
対称な行列の、うれしい性質
対称な行列では、固有ベクトルどうしが必ず 直角に交わります((1,1) と (1,−1) は直角)。第10章の 共分散の表(np.cov が返す2行2列の表)は、いつも対称な行列です。
次のレッスンでは、共分散の表の固有ベクトルが「データが一番大きく広がっている向き」になり、固有値が「その向きの広がり(分散)の大きさ」になることを使います。
特異値分解・ランク・テンソル、振り返り
特異値分解:どんな形の行列にも使える「伸ばす向き」
固有値・固有ベクトルは、正方形の行列(行と列の数が同じ)にしか使えません。データの表のように、行と列の数が違う行列にも使えるように広げたのが、特異値分解(SVD)です。どんな行列 M も、次の3つの行列の積に分けられます。
M=UΣV⊤
「V⊤ で回す → Σ で、向きごとに伸ばす → U で回す」という3段階の変形に分けた、という意味です。Σ(シグマ)の対角線に並ぶ「伸ばす倍率」を 特異値 と呼び、大きい順に並べます。
M = np.array([[3.0, 1.0],
[1.0, 3.0],
[1.0, 1.0]]) # 3行2列の行列
U, s, Vt = np.linalg.svd(M)
print(s) # → [4.243 2. ](特異値、大きい順)
大きい特異値の部分だけを残し、小さい特異値を0にすると、情報をあまり失わずに行列を小さくまとめられます。画像の圧縮や、次のレッスンの主成分分析(scikit-learn の PCA は、内部で特異値分解を使っています)に使われます。
ランク:本当に独立な向きの数
(1224) は、2行目が1行目のちょうど2倍です。この行列を掛けると、平面のどの点も、1本の直線の上に押しつぶされます。このように、行列が変形した後に残る「独立な向きの数」を ランク と呼びます。この行列のランクは1、前のスライドの A はランク2です。ランクは、0でない特異値の数と一致します(np.linalg.matrix_rank で求められます)。
テンソルと、アダマール積
- テンソル: 数を並べる方向(軸)の数を問わない、配列の呼び方です。1つの数(0階)、ベクトル(1階)、行列(2階)、さらに「縦 × 横 × 色 × 枚数」の画像のまとまり(4階)など。PyTorch では、配列のことを
Tensor と呼びます
- アダマール積: 同じ形の2つの配列の、同じ位置どうしの掛け算 です。NumPy の
* がこれにあたり、第10章の期待値の計算(値 × 確率)でも使いました。行列の積 @ とは違う計算なので、区別します
よくある誤解
- 「行列を掛けると、ベクトルは必ず向きが変わる」: 固有ベクトルの向きだけは、向きが変わらず伸び縮みするだけです
- 「
np.linalg.eigh の結果の各行が、固有ベクトル」: 固有ベクトルは 列 です(vecs[:, i])
- 「
* と @ は、どちらも行列の掛け算」: * は同じ位置どうしの掛け算(アダマール積)、@ は内積をまとめた行列の積です
振り返り
- 行列を掛けることは、ベクトル(矢印)を変形することと見られる
- 固有ベクトルは、行列を掛けても向きが変わらない向きで、固有値はその倍率。対称な行列の固有ベクトルは互いに直角
- 特異値分解は、どんな形の行列も「回す・伸ばす・回す」に分ける。特異値の大きい部分だけで、行列を近似できる
- ランクは独立な向きの数。テンソルは軸の数を問わない配列、アダマール積は同じ位置どうしの掛け算