はじめに
『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
本の内容に関して「Pythonを使って再現」や「数式の行間埋め」によって理解を目指します。本と一緒に読んでください。
この記事は1.1節「ベクトル」の内容です。
ベクトルにより表現される情報を確認します。
【他の内容】
【今回の内容】
1.1.1 ベクトルの例
ベクトルとして表現される例をグラフで確認します。
ベクトルの作図については「矢印プロットの作図【ゼロつく1のノート(Python)】 - からっぽのしょこ」も参照してください。
利用するライブラリを読み込みます。
# 利用ライブラリ import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation
位置と移動
n次元空間上の位置や移動をn次元ベクトルで表します。
2次元の場合
まずは、2次元空間(平面)上でベクトルを可視化します。
2次元ベクトルを指定します。
# ベクトルを指定 x = np.array([2.0, 3.0])
を
x
として値を指定します。ただし、Pythonでは0からインデックスが割り当てられるので、の値は
x[0]
に対応します。
2次元ベクトルを空間上の点として描画します。
# (次の図との比較用に)作図用の値を設定 x_min = np.floor(np.min([0.0, x[0]])) - 1 x_max = np.ceil(np.max([0.0, x[0]])) + 1 y_min = np.floor(np.min([0.0, x[1]])) - 1 y_max = np.ceil(np.max([0.0, x[1]])) + 1 # 点を作図 fig, ax = plt.subplots(figsize=(5, 6), facecolor='white') ax.scatter(*x, s=100) # 点x ax.annotate(xy=x, text='x', size=15, ha='left', va='bottom') # 点xラベル ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.grid() ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_title('x=('+', '.join(map(str, x))+')', loc='left') fig.suptitle('point x', fontsize=20) ax.set_aspect('equal') plt.show()
点を
axes.scatter()
で描画します。第1引数にx軸の値、第2引数にy軸の値を指定します。
配列x
の前に*
を付けてアンパック(展開)して指定しています。
を座標として
の点を表現できます。
2次元ベクトルを空間上のベクトルとして描画します。
# 2Dベクトルを作図 fig, ax = plt.subplots(figsize=(5, 6), facecolor='white') ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, *x, angles='xy', scale_units='xy', scale=1) # ベクトルx ax.annotate(xy=0.5*x, text='x', size=15, ha='right', va='bottom') # ベクトルxラベル ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.grid() ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_title('x=('+', '.join(map(str, x))+')', loc='left') fig.suptitle('vector x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
ベクトルを
axes.quiver()
で描画します。第1・2引数に始点の座標、第3・4引数にベクトルのサイズ(変化量)を指定します。その他に、指定した値の通りに(調整せずに)矢印を描画するための設定をしています。
を変化量としてある点からの移動を表現できます。
この例では、原点からの移動としています。原点からx軸方向に、y軸方向に
移動した座標は点
になります。原点以外からの移動については「【Python】ベクトルの和の例【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」を参照してください。
ベクトルの値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 51 # 各次元の要素として利用する値を指定 x1_vals = np.linspace(start=-6.0, stop=6.0, num=frame_num) x2_vals = np.linspace(start=-4.0, stop=10.0, num=frame_num) # 作図用の値を設定 x_min = np.floor(np.min([0.0, x1_vals.min()])) - 1 x_max = np.ceil(np.max([0.0, x1_vals.max()])) + 1 y_min = np.floor(np.min([0.0, x2_vals.min()])) - 1 y_max = np.ceil(np.max([0.0, x2_vals.max()])) + 1 # 作図用のオブジェクトを初期化 fig, ax = plt.subplots(figsize=(7, 7), facecolor='white') fig.suptitle('vector x', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目のベクトルを作成 x = np.array([x1_vals[i], x2_vals[i]]) # 2Dベクトルを作図 ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, *x, angles='xy', scale_units='xy', scale=1) # ベクトルx ax.annotate(xy=0.5*x, text='x', size=15, ha='right', va='bottom') # ベクトルxラベル ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_title('x=('+', '.join(map(str, x.round(2)))+')', loc='left') ax.grid() ax.legend() ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) ax.set_aspect('equal') # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100) # gif画像を保存 ani.save('vector_2d.gif')
作図処理をupdate()
として定義して、FuncAnimation()
でgif画像を作成します。
3次元の場合
続いて、3次元空間上でベクトルを可視化します。
3次元ベクトルを指定します。
# ベクトルを指定 x = np.array([2.0, 3.0, 4.0])
を
x
として値を指定します。
3次元ベクトルを空間上の点として描画します。
# (次の図との比較用に)作図用の値を設定 x_min = np.floor(np.min([0.0, x[0]])) - 1 x_max = np.ceil(np.max([0.0, x[0]])) + 1 y_min = np.floor(np.min([0.0, x[1]])) - 1 y_max = np.ceil(np.max([0.0, x[1]])) + 1 z_min = np.floor(np.min([0.0, x[2]])) - 1 z_max = np.ceil(np.max([0.0, x[2]])) + 1 # 点を作図 fig, ax = plt.subplots(figsize=(7, 8), subplot_kw={'projection': '3d'}, facecolor='white') ax.scatter(*x, s=100) # 点x ax.text(*x, s='x', size=15, ha='left', va='bottom') # 点xラベル ax.quiver(x[0], x[1], z_min, 0, 0, x[2]-z_min, color='gray', arrow_length_ratio=0, linestyle=':') # 補助線 ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.set_zticks(ticks=np.arange(z_min, z_max+1)) ax.set_zlim(z_min, z_max) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_zlabel('$x_3$') ax.set_title('x=('+', '.join(map(str, x))+')', loc='left') fig.suptitle('point x', fontsize=20) ax.set_aspect('equal') plt.show()
の点を表現できます。
3次元ベクトルを空間上のベクトルとして描画します。
# 3Dベクトルを作図 fig, ax = plt.subplots(figsize=(7, 8), subplot_kw={'projection': '3d'}, facecolor='white') ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, 0, *x, arrow_length_ratio=0.05, color='black') # ベクトルx ax.text(*0.5*x, s='x', size=15, ha='right', va='bottom') # ベクトルxラベル ax.quiver([0, x[0]], [0, x[1]], [z_min, z_min], [0, 0], [0, 0], [-z_min, x[2]-z_min], color='gray', arrow_length_ratio=0, linestyle=':') # 補助線 ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.set_zticks(ticks=np.arange(z_min, z_max+1)) ax.set_zlim(z_min, z_max) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_zlabel('$x_3$') ax.set_title('x=('+', '.join(map(str, x))+')', loc='left') fig.suptitle('vector x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
axes.quiver()
の第1・2・3引数に始点の座標、第4・5・6引数にベクトルのサイズを指定します。
原点からx軸方向に、y軸方向に
、z軸方向に
移動した座標は点
になります。
ベクトルの値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 51 # 各次元の要素として利用する値を指定 x1_vals = np.linspace(start=-6.0, stop=6.0, num=frame_num) x2_vals = np.linspace(start=0.0, stop=10.0, num=frame_num) x3_vals = np.linspace(start=-4.0, stop=3.0, num=frame_num) # 作図用の値を設定 x_min = np.floor(np.min([0.0, x1_vals.min()])) - 1 x_max = np.ceil(np.max([0.0, x1_vals.max()])) + 1 y_min = np.floor(np.min([0.0, x2_vals.min()])) - 1 y_max = np.ceil(np.max([0.0, x2_vals.max()])) + 1 z_min = np.floor(np.min([0.0, x3_vals.min()])) - 1 z_max = np.ceil(np.max([0.0, x3_vals.max()])) + 1 # 作図用のオブジェクトを初期化 fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={'projection': '3d'}, facecolor='white') fig.suptitle('vector x', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目のベクトルを作成 x = np.array([x1_vals[i], x2_vals[i], x3_vals[i]]) # 3Dベクトルを作図 ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, 0, *x, arrow_length_ratio=0.05, color='black') # ベクトルx ax.text(*0.5*x, s='x', size=15, ha='right', va='bottom') # ベクトルxラベル ax.quiver([x_min, x_min, 0, x[0], 0, x[0]], [0, x[1], y_max, y_max, 0, x[1]], [0, x[2], 0, x[2], z_min, z_min], [-x_min, x[0]-x_min, 0, 0, 0, 0], [0, 0, -y_max, x[1]-y_max, 0, 0], [0, 0, 0, 0, -z_min, x[2]-z_min], color='gray', arrow_length_ratio=0, linestyle=':') # 補助線 ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.set_zticks(ticks=np.arange(z_min, z_max+1)) ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) ax.set_zlim(z_min, z_max) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_zlabel('$x_3$') ax.set_title('x=('+', '.join(map(str, x.round(2)))+')', loc='left') ax.legend() ax.set_aspect('equal') # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100) # gif画像を保存 ani.save('vector_3d.gif')
色
赤(red)・緑(green)・青(blue)の色情報を3次元ベクトルで表します。RGBの他に、透明度(alpha)を入れて4次元ベクトルで表すこともあります。
RGBベクトル(として使う配列)を作成します。
# 各次元の値として利用する値を指定 v = np.linspace(start=0, stop=1, num=11) print(v) # 3次元の格子点を作成 R, G, B = np.meshgrid(v, v, v) print(R.shape) print(G.shape) print(B.shape)
[0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
(11, 11, 11)
(11, 11, 11)
(11, 11, 11)
各次元の値として、0
から1
の値を作成してv
とします。
3次元の格子点(全ての組み合わせ)をnp.meshgrid()
で作成します。R, G, B
の同じインデックスの要素が1つのRGBベクトルに対応します。v
の要素数の3乗個の要素が格納された配列が出力されます。
色付け用に、R, G, B
を1つの2次元配列にまとめます。
# RGBベクトルを結合 RGB = np.stack([R.flatten(), G.flatten(), B.flatten()], axis=1) print(RGB.shape) print(RGB[:5])
(1331, 3)
[[0. 0. 0. ]
[0. 0. 0.1]
[0. 0. 0.2]
[0. 0. 0.3]
[0. 0. 0.4]]
各配列をflatten()
で1次元配列に変換して、np.stack()
にaxis=1
を指定して列方向に結合します。RGB
の各行が1つのRGBベクトルに対応します。
RGBベクトルを位置情報として配置し、また情報として配色して、散布図を作成します。
# RGBベクトルを散布図で可視化 fig, ax = plt.subplots(figsize=(9, 9), subplot_kw={'projection': '3d'}, facecolor='white') ax.scatter(R, G, B, color=RGB) ax.set_xlabel('R') ax.set_ylabel('G') ax.set_zlabel('B') ax.set_title('$a_i=(r, g, b)$', loc='left') fig.suptitle('RGB vector', fontsize=20) ax.set_aspect('equal') plt.show()
axes.ax.scatter()
のcolor
引数にRGBベクトルの配列を指定して点ごとに色付けします。
他の軸が0に近くR軸が1に近いほど赤色、G軸が1に近いほど緑色、B軸が1に近いほど青色になるのが分かります。また、全ての軸が0に近いと黒色、1に近いと白色になります。
図を回転させて確認します。
・作図コード(クリックで展開)
# 水平方向の角度として利用する値を指定 h_vals = np.arange(0.0, 360.0, step=5.0) # フレーム数を設定 frame_num = len(h_vals) # 作図用のオブジェクトを初期化 fig, ax = plt.subplots(figsize=(9, 9), subplot_kw={'projection': '3d'}, facecolor='white') fig.suptitle(t='RGB vector', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目の角度を取得 h = h_vals[i] # RGBベクトルの散布図を作成 ax.scatter(R, G, B, color=RGB) ax.set_xlabel('R') ax.set_ylabel('G') ax.set_zlabel('B') ax.set_title('$a_i=(r, g, b)$', loc='left') ax.set_aspect('equal') ax.view_init(elev=40, azim=h) # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=150) # gif画像を保存 ani.save('RGB.gif')
画像
画素の画像データを
次元ベクトルとして表します。画像データがRGB情報を持つ場合は、画素(ピクセル)ごとに3つの値を持つので
の画像データになります。
グレースケール
まずは、グレースケール(色情報が1つの場合)の例として、MNIST(手書き文字)データを確認します。
追加で利用するDeZeroを読み込みます。
# 読み込み用の設定 import sys sys.path.insert(0, '「・・・」/deep-learning-from-scratch-3-master')
# 追加ライブラリ from dezero.datasets import MNIST
DeZeroについてはゼロつく3巻「『ゼロから作るDeep Learning 3』の学習ノート:記事一覧 - からっぽのしょこ」を参照してください。
NumPyのバージョンが1.20以降だと(?)np.int
が使えなくて読み込みエラーになるようです。この例では、ゼロつく3巻のサポートページ「
GitHub - oreilly-japan/deep-learning-from-scratch-3: 『ゼロから作る Deep Learning ❸』(O'Reilly Japan, 2020)」からライブラリのソースコードを保存して、エラー箇所をnp.int32
に置き換えたものを読み込んでいます。
MNISTデータセットを取得します。
# 訓練用データセットを出力 train_set = MNIST(train=True, transform=None) print(len(train_set)) # データ数
60000
6万枚の画像データ(入力データ)とラベルデータ(教師データ)が格納されています。
1つの画像データを取り出します。
# データ番号を指定 i = 0 # i番目の画像データとラベルデータを抽出 x, t = train_set[i] #x = x.astype(np.float32) / 255.0 # 正規化 print(x[:, 10:15, 10:15]) # 画像データ print(x.shape) print(t) # ラベルデータ print(t.shape)
[[[ 1 154 253 90 0]
[ 0 139 253 190 2]
[ 0 11 190 253 70]
[ 0 0 35 241 225]
[ 0 0 0 81 240]]]
(1, 28, 28)
5
()
画素のグレースケールデータが得られます。各要素は、
0
から255
の256段階の整数です。各要素を、最大値の255
で割ると0
から1
の値に正規化できます。どちらでも同じ結果が得られます。
手書き文字を描画します。
# 2次元配列に変換 Gray = x[0] # ヒートマップを作成 fig, ax = plt.subplots(figsize=(5, 5), facecolor='white') ax.imshow(Gray, cmap='gray') # 手書き文字 ax.set_xlabel('n') ax.set_ylabel('m') ax.set_title('$A=(a_{1,1},\cdots,a_{N,M}),\ a_{m,n}=a_i\ (i=1,\dots,MN)$', loc='left') fig.suptitle('MNIST : '+str(t), fontsize=20) plt.show()
の2次元配列の形状のままヒートマップを作成しました。
値が小さいほど黒、大きいほど白のグラデーションで表現されます。
次元ベクトル(1次元配列)の形状でヒートマップを作成します。
# 1次元配列に変換して、(分かりやすいように)行方向に複製 tmp_Gray = np.tile(Gray.reshape(1, -1), reps=(10, 1)) # ヒートマップを作成 fig, ax = plt.subplots(figsize=(9, 1), facecolor='white') ax.imshow(tmp_Gray, cmap='gray') # 手書き文字 ax.set_yticks(ticks=[4.5], labels=[0]) ax.set_xlabel('i') ax.set_title('$a=(a_1, \cdots, a_{MN})$', loc='left') fig.suptitle('MNIST : '+str(t), fontsize=20) plt.show()
m行目の要素の次にm+1行目の要素となるように並べたヒートマップ(次元ベクトル)になります。
RGB
続いて、RGBデータ(色情報が3つの場合)の例としてカートポールを確認します。
追加で利用するGymを読み込みます。
# 追加ライブラリ import gym
OpenAI Gymについては「8.1:OpenAI Gym:Classic Control【ゼロつく4のノート】 - からっぽのしょこ」を参照してください。
カートポールのRGBデータを出力します。
# 環境のインスタンスを作成 env = gym.make('CartPole-v1', render_mode='rgb_array') # 状態を初期化 state, info = env.reset() # RGBデータを出力 RGB = env.render() #RGB = RGB.astype(np.float32) / 255.0 # 正規化 print(RGB.shape) print(RGB[250:300, 300:350, 0])
(400, 600, 3)
[[202 202 202 ... 255 255 255]
[202 202 202 ... 255 255 255]
[202 202 202 ... 255 255 255]
...
[129 136 158 ... 255 255 255]
[ 0 0 0 ... 255 255 255]
[ 0 0 0 ... 0 0 0]]
画素のRGBデータが得られます。各要素は、
0
から255
の256段階の整数です。各要素を、最大値の255
で割ると0
から1
の値に正規化できます。どちらでも同じ結果が得られます。
色ごとに並べてカートポールを描画します。
# 作図用のオブジェクトを初期化 fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 6), facecolor='white') fig.suptitle('Cart Pole', fontsize=20) # タイトル用のリストを設定 title_lt = ['$a_i=(r, 0, 0)$', '$a_i=(0, g, 0)$', '$a_i=(0, 0, b)$', '$a_i=(r, g, b)\ i=1,\dots,MN$'] # カウントを初期化 idx = 0 # 次元ごとに処理 for r in range(2): for c in range(2): if idx < 3: # 1次元のみ抽出 tmp_rgb = np.zeros_like(RGB) tmp_rgb[:, :, idx] = RGB[:, :, idx] # RGBの1次元のみ描画 axes[r, c].imshow(tmp_rgb) axes[r, c].set_title(title_lt[idx], loc='left') axes[r, c].set_xlabel('n') axes[r, c].set_ylabel('m') else: # RGBの3次元を描画 axes[r, c].imshow(RGB) axes[r, c].set_title(title_lt[idx], loc='left') axes[r, c].set_xlabel('n') axes[r, c].set_ylabel('m') # カウントアップ idx += 1 plt.show()
RGBそれぞれのみを使ったヒートマップと、全てを使ったヒートマップを並べて描画します。
各色のヒートマップについて、それぞれ値が小さいほど黒、大きいほど赤・緑・青のグラデーションで表現されます。全色のヒートマップについて、同じピクセル(インデックス)が赤・緑・青だと白になり、どれも黒のピクセルは黒のままです。
この節では、ベクトルを使って表現できる情報を確認しました。次の節では、ベクトルの和を考えます。
この記事で扱わなかった例については、後々の章でトイデータなどとして利用することになれば書き足すつもりです。
参考書籍
- Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.
おわりに
とりあえず1つめです。これくらいのレベルなら飛ばして3章のノルムから書こうと思ったのですが、作図周りの説明やらベクトルと座標の関係やらで情報過多になりそうだったので、色々分割していくと結局ここから書くのと同じだなとなりました。
というわけでこの記事の主な目的は、Axes.quiver()
の使い方です。
個人的にはこのシリーズで、MATLAB-style(plt.関数名()
の記法)を卒業してOPP-style(ax.関数名()
の記法)を覚えるというのも目標です。
3月3日は、Berryz工房のデビュー日ということで、この曲を聴きながら進めましょう🍑
【次の内容】