import numpy as np #
x = np.array([1.0, 0.0])
print(x)
[1. 0.]
numpy
を使うか?¶numpy
では array
の間の演算として定義されている。$x=\begin{bmatrix}1 \\ 0 \end{bmatrix}$, $y=\begin{bmatrix}0 \\ 1\end{bmatrix}$ とする
x = np.array([1.0, 0])
y = np.array([0, 1.0])
print(x, y)
[1. 0.] [0. 1.]
print(3 * x)
[3. 0.]
print(x + y)
print(x - y)
print(3 * x - 10 * y)
[1. 1.] [ 1. -1.] [ 3. -10.]
print(x @ y)
print((3 * x - y) @ (x + 2 * y))
print(np.dot(x, y)) # 関数の形で書くこともできる
0.0 1.0 0.0
※内積は、2つベクトルを受け取って、1つのスカラーを返す関数としても書ける
print(((2 * x - y) @ (2 * x - y)) ** (0.5)) # 内積を使って計算した場合
print(np.linalg.norm(2 * x - y)) # numpyの関数を使って計算した場合
2.23606797749979 2.23606797749979
各次元で積を取る演算
print(x * y)
[0. 0.]
array
というオブジェクトで定義するnp.dot
np.linalg.norm
Q. np.dot
とか np.linalg.norm
とかなんやねん
A. ライブラリは階層構造になっている。
np.dot
は、numpy
(np
と書いてる)直下に定義された dot
という関数、np.linalg.norm
は、numpy
の下の linalg
(linear algebra; 線形代数)という線形代数の関数をまとめた集まりのなかの norm
という関数
と解釈するQ. じゃあ np.array
は?
A. オブジェクトを作る関数と言ってもいいかも。
リストを受け取って array
を返す
$A = \begin{bmatrix} 1 & 1 \\ 0 & 2 \end{bmatrix}$
# np.array にリストのリストを渡すと行列
A = np.array([[1.0, 1.0], [0.0, 2.0]])
print(A)
[[1. 1.] [0. 2.]]
print(A @ x)
print(x @ A)
[1. 0.] [1. 1.]
print(A @ np.array([1,2,3,4])) # 2x2の行列に4次元のベクトルは掛けられない
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-22-a386da54a8ef> in <module>() ----> 1 print(A @ np.array([1,2,3,4])) # 2x2の行列に4次元のベクトルは掛けられない ValueError: shapes (2,2) and (4,) not aligned: 2 (dim 1) != 4 (dim 0)
$A = \begin{bmatrix} 1 & 1 \\ 0 & 2 \end{bmatrix}$, $B = \begin{bmatrix} 0 & 1 \\ 1 & 2 \end{bmatrix}$
A = np.array([[1.0, 1.0],
[0.0, 2.0]])
B = np.array([[0.0, 1.0],
[1.0, 2.0]])
print(A @ B)
print(B @ A)
[[1. 3.] [2. 4.]] [[0. 2.] [1. 5.]]
numpy
で実装されているのをつかうnp.linalg.eigvals(np.array([[2,1],[1,2]]))
array([3., 1.])
numpy
で実装されているのを使う¶A = np.array([[2, 1], [1, 2]])
np.linalg.eig(A)
(array([3., 1.]), array([[ 0.70710678, -0.70710678], [ 0.70710678, 0.70710678]]))
絶対値最大の固有値は $3$、対応する固有ベクトルは $\begin{bmatrix}0.70710678\\ 0.70710678 \end{bmatrix}$
(返り値の解釈は、APIを見ましょう)
任意のベクトル $x\in\mathbb{R}^N$ は、固有ベクトルで展開できる(固有ベクトルは基底を成す):
$\begin{eqnarray} x = \sum_{n=1}^{N}c_n v_n \end{eqnarray}$
$A$ を掛け続けると絶対値最大の固有値に対応する固有ベクトルが(相対的に)強調される:
$\begin{eqnarray} A^k x &= \sum_{n=1}^{N}c_n A^k v_n = \sum_{n=1}^N c_n \lambda_n^k v_n\\ &= \lambda_1^k \sum_{n=1}^N c_n \left(\frac{\lambda_n}{\lambda_1}\right)^k v_n\\ &\approx \lambda_1^k c_1 v_1 \end{eqnarray}$
A = np.array([[2, 1], [1, 2]])
x = np.array([1, 2])
for _ in range(100):
x = A @ x
x = x / np.linalg.norm(x) # ベクトルを正規化しないと数値誤差が乗る
print(x@A@x / (x@x), x)
3.0 [0.70710678 0.70710678]
$A\in\mathbb{R}^{N\times N}$, $b\in\mathbb{R}^N$ としたとき、 $Ax=b$ を満たす $x\in\mathbb{R}^N$ を求める。
$A$ が正則行列(=逆行列を持つ)のとき、 $x = A^{-1}b$ が解。
二通りの実装方法がある
numpy
では逆行列を求めるのに$AX=I$を解いている(=線形方程式を解くのと同じ計算時間がここで必要)(参考)伊理正夫, 藤野和建: 数値計算の常識
numpy
を使うデータ $x_1,\dots,x_N\in \mathbb{R}^D$ があったとき、その"特性"を保ったまま低次元表現を得たい。
PCA は、次のような線形変換のペア $W, U$ を求めるという気持ち:
$$ \min_{W, U\in\mathbb{R}^{K\times D}} \sum_{n=1}^{N}\|x_n - U^{\top}Wx_n\|_{2}^2 \tag{1} $$
式(1)の最適値は
$$ \begin{align} \min_{V\in\mathbb{R}^{K\times D},\ VV^\top=I} \sum_{n=1}^{N}\|x_n - V^{\top}Vx_n\|_{2}^2\tag{2} \end{align} $$
の最適値と等しい
最適化問題(2)は $$ \max_{V\in\mathbb{R}^{K\times D}, VV^\top=I}\mathrm{tr}\left(V \left(\sum_{n=1}^N x_n x_n^\top\right)V^\top\right) \tag{3} $$ と同値。
展開すれば良い。
$$ \sum_{n=1}^N \|x_n - V^\top V x_n\|^2 = \sum_{n=1}^N \|x_n\|^2 - 2\sum_{n=1}^N x_n^\top V^\top V X_n + \sum_{n=1}^N x_n^\top V^\top V V^\top V x_n $$
$$ =\sum_{n=1}^N \|x_n\|^2 - \sum_{n=1}^N x_n^\top V^\top V x_n $$
第二項は、
$$ \begin{align} \sum_{n=1}^N x_n^\top V^\top V x_n = \sum_{n=1}^N \mathrm{tr}(x_n^\top V^\top V x_n) = \sum_{n=1}^N \mathrm{tr}(x_n x_n^\top V^\top V) \\ = \mathrm{tr}\left(\left(\sum_{n=1}^N x_n x_n^\top\right) V^\top V\right) = \mathrm{tr}\left(V\left(\sum_{n=1}^N x_n x_n^\top\right) V^\top\right) \end{align} $$
$A=\sum_{n=1}^N x_n x_n^\top$ として、その固有値、固有ベクトルを $\lambda_1,\dots, \lambda_D$, $u_1,\dots,u_D$ とする($\lambda_1\geq\lambda_2\geq\cdots\geq\lambda_D$)。 この時任意の $V\in\mathbb{R}^{K\times D}, VV^\top=I$ について
$$ \mathrm{tr}\left(V A V^\top\right) \leq \max_{w\in[0,1]^D, \sum_{d=1}^D w_d\leq K}\sum_{d=1}^D \lambda_d w_d = \sum_{d=1}^K \lambda_d $$
が成立
$A=U^\top \Lambda U$ と固有値分解できる($U\in\mathbb{R}^{D\times D}$)。 $V\in\mathbb{R}^{K\times D}, VV^\top = I$ を一つ持ってくる。
$W=VU^\top\in\mathbb{R}^{K\times D}$と置くと、
$$ \begin{align} \mathrm{tr}(W\Lambda W^\top) = \sum_{d=1}^D \lambda_d \sum_{k=1}^Kw_{k,d}^2 \end{align} $$
$w_d:=\sum_{k=1}^K w_{k,d}^2$ と置くと、
$$ \begin{align} \mathrm{tr}(W\Lambda W^\top) &= \sum_{d=1}^D \lambda_d \sum_{k=1}^Kw_{k,d}^2\\ &= \sum_{d=1}^D \lambda_d w_d \end{align} $$
ここで $0\leq\sum_{k=1}^K w_{k,d}^2 \leq 1~(d=1,\dots,D)$, $\sum_{d=1}^D \sum_{k=1}^K w_{k,d}^2=K$ なので、
$$ \begin{align} \mathrm{tr}(W\Lambda W^\top) &= \sum_{d=1}^D \lambda_d w_d\\ &\leq \max_{w_d\in[0,1], \sum_{d=1}^D w_d\leq K}\sum_{d=1}^D \lambda_d w_d \end{align} $$ が成立.
$A=\sum_{n=1}^N x_n x_n^\top$ として、その固有値、固有ベクトルを $\lambda_1,\dots, \lambda_D$, $u_1,\dots,u_D$ とする($\lambda_1\geq\lambda_2\geq\cdots\geq\lambda_D$)。
この時 $V = \begin{bmatrix}u_1 \dots u_K\end{bmatrix}^\top$ が最適化問題(2), (3)の解
$$ \mathrm{tr}(VAV^\top) = \mathrm{tr}(VU^\top \Lambda UV^\top) = \mathrm{tr}(\mathbb{1}_K \Lambda \mathbb{1}_K) = \sum_{d=1}^K \lambda_d $$ が成り立つ。ここで $$ [\mathbb{1}_K]_{i,j} = \begin{cases} \delta_{i,j} &\text{ if } i=j\leq K\\ 0 & \text{otherwise}. \end{cases} $$
補題3より、これは最適値。
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_olivetti_faces
# データを取得
dataset = fetch_olivetti_faces()
num_examples, row_size, col_size = dataset['images'].shape
X = dataset['data']
# 平均0にしておく(しなくてもまあ大丈夫だけど)
X_mean = X.mean(axis=0)
X_centered = X - X_mean
# 顔データを表示してみる
plt.imshow(dataset['images'][0], cmap=plt.cm.gray)
plt.show()
X.shape
(400, 4096)
PCA を実行する関数を書け
ヒント: 対称行列の固有値分解を行う関数 https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.eigh.html#scipy.linalg.eigh
from scipy.linalg import eigh
def pca(X, K):
A = X.T @ X
eig_val, eig_vec = eigh(A)
V = eig_vec[:, -K:]
z = X @ V
return z, V
# pca を実行
K=20
z, V = pca(X_centered, K)
# V の行ベクトルが正規直交基底であることを確認
print('distance from the identity:', np.abs(V.T @ V - np.identity(K)).max())
print('mean reconstruction loss: ',((X_centered - z @ V.T) * (X_centered - z @ V.T)).mean())
distance from the identity: 1.5404075384140015e-06 mean reconstruction loss: 0.0045594834
# データの貼る空間の固有ベクトルを見てみる
plt.imshow(-V[:, -1].reshape(row_size, col_size), cmap=plt.cm.gray)
plt.show()
plt.scatter(z[:, 0], z[:, 1])
<matplotlib.collections.PathCollection at 0x1a202855f8>
# この場合は2次元に落としてもよくわからない...
import matplotlib.cm as cm
import numpy as np
colors = cm.rainbow(np.linspace(0, 1, 10))
for each_idx in range(100):
plt.scatter(z[each_idx, 0], z[each_idx, 1], color=colors[dataset['target'][each_idx]])
plt.show()
# 再構成
X_rec = z @ V.T + X_mean
idx = 190
f, (ax1, ax2) = plt.subplots(1, 2)
ax1.imshow(dataset['images'][idx], cmap=plt.cm.gray) # 左が元の画像
ax2.imshow(X_rec[idx].reshape(row_size, col_size), cmap=plt.cm.gray) # 右が再構成画像
plt.show()
numpy
を使って実装するnumpy
の API を探せば実装されていることが多い