からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【Python】1.1.1:ベクトルの例【『スタンフォード線形代数入門』のノート】

はじめに

 『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
 本の内容に関して「Pythonを使って再現」や「数式の行間埋め」によって理解を目指します。本と一緒に読んでください。

 この記事は1.1節「ベクトル」の内容です。
 ベクトルにより表現される情報を確認します。

【他の内容】

www.anarchive-beta.com

【今回の内容】

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])

  \mathbf{x} = (x_1, x_2)^{\top}xとして値を指定します。ただし、Pythonでは0からインデックスが割り当てられるので、 x_1の値は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()

2次元空間上の点

 点 \mathbf{x}axes.scatter()で描画します。第1引数にx軸の値、第2引数にy軸の値を指定します。
 配列xの前に*を付けてアンパック(展開)して指定しています。

  \mathbf{x}を座標として (x, y) = (x_1, x_2)の点を表現できます。

 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()

2次元空間上のベクトル

 ベクトル \mathbf{x}axes.quiver()で描画します。第1・2引数に始点の座標、第3・4引数にベクトルのサイズ(変化量)を指定します。その他に、指定した値の通りに(調整せずに)矢印を描画するための設定をしています。

  \mathbf{x}を変化量としてある点からの移動を表現できます。
 この例では、原点からの移動としています。原点からx軸方向に x_1、y軸方向に x_2移動した座標は点 \mathbf{x}になります。原点以外からの移動については「【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画像を作成します。

2次元空間上のベクトル


3次元の場合

 続いて、3次元空間上でベクトルを可視化します。

 3次元ベクトルを指定します。

# ベクトルを指定
x = np.array([2.0, 3.0, 4.0])

  \mathbf{x} = (x_1, x_2, x_3)^{\top}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次元空間上の点

  (x, y, z) = (x_1, x_2, x_3)の点を表現できます。

 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()

3次元空間上のベクトル

 axes.quiver()の第1・2・3引数に始点の座標、第4・5・6引数にベクトルのサイズを指定します。

 原点からx軸方向に x_1、y軸方向に x_2、z軸方向に x_3移動した座標は点 \mathbf{x}になります。

 ベクトルの値を変化させたアニメーションを作成します。

・作図コード(クリックで展開)

# フレーム数を設定
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')

3次元空間上のベクトル


 赤(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')

ベクトルによる色の表現


画像

  M \times N画素の画像データを MN次元ベクトルとして表します。画像データがRGB情報を持つ場合は、画素(ピクセル)ごとに3つの値を持つので M \times N \times 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
()

  1 \times 28 \times 28画素のグレースケールデータが得られます。各要素は、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()

グレースケールの画像データの例

  M \times Nの2次元配列の形状のままヒートマップを作成しました。
 値が小さいほど黒、大きいほど白のグラデーションで表現されます。

  MN次元ベクトル(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行目の要素となるように並べたヒートマップ( 28^2次元ベクトル)になります。

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]]

  400 \times 600画素の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画像データの例

 RGBそれぞれのみを使ったヒートマップと、全てを使ったヒートマップを並べて描画します。
 各色のヒートマップについて、それぞれ値が小さいほど黒、大きいほど赤・緑・青のグラデーションで表現されます。全色のヒートマップについて、同じピクセル(インデックス)が赤・緑・青だと白になり、どれも黒のピクセルは黒のままです。

 この節では、ベクトルを使って表現できる情報を確認しました。次の節では、ベクトルの和を考えます。
 この記事で扱わなかった例については、後々の章でトイデータなどとして利用することになれば書き足すつもりです。

参考書籍

  • Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.

おわりに

 とりあえず1つめです。これくらいのレベルなら飛ばして3章のノルムから書こうと思ったのですが、作図周りの説明やらベクトルと座標の関係やらで情報過多になりそうだったので、色々分割していくと結局ここから書くのと同じだなとなりました。
 というわけでこの記事の主な目的は、Axes.quiver()の使い方です。

 個人的にはこのシリーズで、MATLAB-style(plt.関数名()の記法)を卒業してOPP-style(ax.関数名()の記法)を覚えるというのも目標です。

 3月3日は、Berryz工房のデビュー日ということで、この曲を聴きながら進めましょう🍑


【次の内容】

www.anarchive-beta.com