はじめに
『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。
この記事は5.1節「線形従属」と5.2節「基底」の内容です。
線形独立なベクトル集合のグラフを作成します。
【前の内容】
【他の内容】
【今回の内容】
ベクトルの線形独立の可視化
ベクトルの線形独立(inear independent・linearly independent vectors)をグラフで確認します。
線形結合については「【Python】1.3:ベクトルの線形結合の可視化【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」、線形従属については「【Python】ベクトルの線形従属の可視化【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」を参照してください。
利用するライブラリを読み込みます。
# 利用ライブラリ import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation
2次元の場合
まずは、2次元空間(平面)上でベクトルの線形独立を可視化します。
2次元ベクトルを指定します。
# ベクトルを指定 a1 = np.array([3.0, 2.0]) a2 = np.array([-2.0, 2.0])
2つのベクトルをa1, a2
として値を指定します。Pythonでは0からインデックスが割り当てられるので、の値はa1[0]
に対応します。
ベクトルと平行な平面(原点と点を通る平面)の座標を計算します。
# 平面用の係数を作成 beta_vals = np.arange(start=-1.5, stop=1.6, step=0.5) print(beta_vals) print(beta_vals.shape) # 格子点を作成 beta1_grid, beta2_grid = np.meshgrid(beta_vals, beta_vals) print(beta1_grid.shape) # ベクトルa1,a2と平行な平面の座標を計算 x1_grid = beta1_grid * a1[0] + beta2_grid * a2[0] x2_grid = beta1_grid * a1[1] + beta2_grid * a2[1] print(x1_grid[:5, :5]) print(x2_grid[:5, :5])
[-1.5 -1. -0.5 0. 0.5 1. 1.5]
(7,)
(7, 7)
[[-1.5 0. 1.5 3. 4.5]
[-2.5 -1. 0.5 2. 3.5]
[-3.5 -2. -0.5 1. 2.5]
[-4.5 -3. -1.5 0. 1.5]
[-5.5 -4. -2.5 -1. 0.5]]
[[-6. -5. -4. -3. -2.]
[-5. -4. -3. -2. -1.]
[-4. -3. -2. -1. 0.]
[-3. -2. -1. 0. 1.]
[-2. -1. 0. 1. 2.]]
を線形結合したベクトルがとり得る値(座標)を計算します。
の値をbeta_vals
とし、np.meshgrid()
で格子状の点(全ての組み合わせ)を作成してbeta1_grid, beta2_grid
として、x軸の値とy軸の値を計算してx1_grid, x2_grid
とします。
2次元空間上に、ベクトルと平面のグラフを作成します。
# グラフサイズ用の値を設定 x_min = np.floor(x1_grid.min()) x_max = np.ceil(x1_grid.max()) y_min = np.floor(x2_grid.min()) y_max = np.ceil(x2_grid.max()) # 線形独立の2Dベクトルを作図 fig, ax = plt.subplots(figsize=(8, 6), facecolor='white') ax.quiver(0, 0, *a1, color='red', width=0.01, headwidth=5, headlength=5, angles='xy', scale_units='xy', scale=1, label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1 ax.quiver(0, 0, *a2, color='blue', width=0.01, headwidth=5, headlength=5, angles='xy', scale_units='xy', scale=1, label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2 ax.plot(x1_grid, x2_grid, color='C0', zorder=0) # ベクトルa1,a2と平行な平面:(a2と平行な直線) ax.plot(x1_grid.T, x2_grid.T, color='C0', label=['$\\beta_1 a_1 + \\beta_2 a_2$']+['' for _ in range(len(beta_vals)-1)], zorder=0) # ベクトルa1,a2と平行な平面:(a1と平行な直線) ax.set_xticks(ticks=np.arange(x_min, x_max+1)) ax.set_yticks(ticks=np.arange(y_min, y_max+1)) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.grid() fig.suptitle('linear independent', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
axes.quiver()
で2次元ベクトルを描画します。第1・2引数に始点の座標、第3・4引数にベクトルのサイズ(移動量)を指定します。その他に、指定した値の通りに(調整せずに)矢印を描画するための設定をしています。
配列a1, a2
の前に*
を付けてアンパック(展開)して座標を指定します。始点は原点です。
平面のグラフについては「傾いた2次元格子の作図【Matplotlib】 - からっぽのしょこ」を参照してください。
ベクトルそれぞれと平行な直線によって平面が作られます。この平面上の点は、ベクトルの線形結合によって表現できます。
ベクトルと線形結合したベクトルのグラフについて、係数(ベクトル)の値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 60 # 係数の作成用の値(ラジアン)として利用する値を指定 t_n = np.linspace(start=0.0, stop=2.0*np.pi, num=frame_num+1)[:frame_num] # ベクトルのサイズ調整用の値を指定 r = 1.0 # グラフオブジェクトを初期化 fig, ax = plt.subplots(figsize=(8, 6), facecolor='white') fig.suptitle('linear dependent', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目の係数を作成 t = t_n[i] beta1 = r * np.cos(t) beta2 = r * np.sin(t) # 線形従属になるベクトルを作成 b = beta1 * a1 + beta2 * a2 # ラベル用の符号を設定 sgn2 = '+' if beta2 >= 0.0 else '' # 線形従属の2Dベクトルを作図 ax.quiver(0, 0, *a1, color='red', width=0.01, headwidth=5, headlength=5, angles='xy', scale_units='xy', scale=1, label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1 ax.quiver(0, 0, *a2, color='blue', width=0.01, headwidth=5, headlength=5, angles='xy', scale_units='xy', scale=1, label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2 ax.quiver(0, 0, *b, color='purple', width=0.01, headwidth=5, headlength=5, angles='xy', scale_units='xy', scale=1, label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb ax.plot(x1_grid, x2_grid, color='C0', zorder=0) # ベクトルa1,a2と平行な平面:(a2と平行な直線) ax.plot(x1_grid.T, x2_grid.T, color='C0', label=['$\\beta_1 a_1 + \\beta_2 a_2$']+['' for _ in range(len(beta_vals)-1)], zorder=0) # ベクトルa1,a2と平行な平面:(a1と平行な直線) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_title('$b=' + str(beta1.round(2))+'a_1' + sgn2+str(beta2.round(2))+'a_2$', loc='left') ax.grid() ax.legend() ax.set_aspect('equal') # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100) # gif画像を保存 ani.save('independence_2d.gif')
作図処理をupdate()
として定義して、FuncAnimation()
でgif画像を作成します。
サイズr
を変更すると、ベクトルが平面(とその延長)上の全ての点になり得るのを確認できます。
同一平面上に3つ以上のベクトルがある場合、それらは線形結合で等しくなる(この例だととして)ので、として(を移項して)が成り立ち、線形従属の条件を満たすのでした(5.1節)。ここで、は2次元の0ベクトルです。
逆に、同一平面上に平行でない2つのベクトルのみの場合、のときに限りが成り立ち、線形独立の条件を満たします。
またこの例は、線形独立な2つの2次元ベクトルなので、基底ベクトルと言えます。
以上が、2次元におけるベクトルの線形独立の可視化です。
3次元の場合
次は、3次元空間上でベクトルの線形独立を可視化します。
3次元ベクトルを指定します。
# ベクトルを指定 a1 = np.array([2.0, 2.0, 2.0]) a2 = np.array([-2.0, 1.5, 1.0]) a3 = np.array([1.0, -1.0, -3.0])
3つのベクトルをa*
として値を指定します。
ベクトルそれぞれと平行な直線によって作られる空間の座標を計算します。
# 空間用の係数を作成 beta_vals = np.arange(start=-1.0, stop=1.1, step=1.0) print(beta_vals) print(beta_vals.shape) # 格子点を作成 beta1_grid, beta2_grid, beta3_grid = np.meshgrid(beta_vals, beta_vals, beta_vals) print(beta1_grid.shape) # ベクトルa1,a2,a3と平行な空間の座標を計算 x1_grid = beta1_grid * a1[0] + beta2_grid * a2[0] + beta3_grid * a3[0] x2_grid = beta1_grid * a1[1] + beta2_grid * a2[1] + beta3_grid * a3[1] x3_grid = beta1_grid * a1[2] + beta2_grid * a2[2] + beta3_grid * a3[2] print(x1_grid[0]) print(x2_grid[0]) print(x3_grid[0])
[-1. 0. 1.]
(3,)
(3, 3, 3)
[[-1. 0. 1.]
[ 1. 2. 3.]
[ 3. 4. 5.]]
[[-2.5 -3.5 -4.5]
[-0.5 -1.5 -2.5]
[ 1.5 0.5 -0.5]]
[[ 0. -3. -6.]
[ 2. -1. -4.]
[ 4. 1. -2.]]
を線形結合したベクトルがとり得る値(座標)を計算します。
の値をbeta_vals
とし、np.meshgrid()
で格子状の点(全ての組み合わせ)を作成してbeta*_grid
として、x軸・y軸・z軸の値を計算してx*_grid
とします。
3次元空間上に、ベクトルと空間(3方向の平面)のグラフを作成します。
# グラフサイズ用の値を設定 x_min = np.floor(x1_grid.min()) x_max = np.ceil(x1_grid.max()) y_min = np.floor(x2_grid.min()) y_max = np.ceil(x2_grid.max()) z_min = np.floor(x3_grid.min()) z_max = np.ceil(x3_grid.max()) # 矢のサイズを指定 l = 0.6 # 線形独立の3Dベクトルを作図 fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', subplot_kw={'projection': '3d'}) ax.quiver(0, 0, 0, *a1, color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1 ax.quiver(0, 0, 0, *a2, color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2 ax.quiver(0, 0, 0, *a3, color='green', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a3), label='$a_3=('+', '.join(map(str, a3))+')$') # ベクトルa3 ax.quiver([0, a1[0], a2[0], a3[0]], [0, a1[1], a2[1], a3[1]], [0, a1[2], a2[2], a3[2]], [0, 0, 0, 0], [0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-a3[2]], color=['gray', 'red', 'blue', 'green'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線 for i in range(len(beta_vals)): formula_label = '$\\beta_1 a_1 + \\beta_2 a_2 + \\beta_3 a_3$' if i == 0 else '' # 1つだけ凡例を設定 ax.plot_wireframe(x1_grid[i], x2_grid[i], x3_grid[i], alpha=0.5, label=formula_label) # ベクトルa1,a2,a3による空間:(a1,a3と平行な平面) ax.plot_wireframe(x1_grid[:, i], x2_grid[:, i], x3_grid[:, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a2,a3と平行な平面) ax.plot_wireframe(x1_grid[:, :, i], x2_grid[:, :, i], x3_grid[:, :, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a1,a2と平行な平面) #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$') fig.suptitle('linear independence', fontsize=20) ax.legend() ax.set_aspect('equal') #ax.view_init(elev=90, azim=270) # xy面 #ax.view_init(elev=0, azim=270) # xz面 #ax.view_init(elev=0, azim=0) # yz面 plt.show()
axes.quiver()
で3次元ベクトルを描画します。第1・2・3引数に始点の座標、第4・5・6引数にベクトルのサイズ(移動量)を指定します。
配列a1, a2, a3
の前に*
を付けてアンパック(展開)して座標を指定します。始点は原点です。
arrow_length_ratio
引数で矢のサイズ(全体に対する矢の割合)を指定できます。この例では、各ベクトルのノルムの逆数を指定することで、ベクトルのサイズに関わらず、全ての矢のサイズを統一します。
さらに、全てのベクトルで同じ値l
を掛けることで、サイズを調整できます。
空間のグラフについては「傾いた3次元格子の作図【Matplotlib】 - からっぽのしょこを参照してください。
ベクトルのそれぞれと平行な直線によって空間が作られます。この空間上の点は、ベクトルの線形結合によって表現できます。
グラフを回転させて確認します。
・作図コード(クリックで展開)
# フレーム数を指定 frame_num = 60 # 水平方向の角度として利用する値を作成 h_n = np.linspace(start=0.0, stop=360.0, num=frame_num+1)[:frame_num] # グラフオブジェクトを初期化 fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', subplot_kw={'projection': '3d'}) fig.suptitle('linear independence', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目の角度を取得 h = h_n[i] # 線形独立の3Dベクトルを作図 ax.quiver(0, 0, 0, *a1, color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1 ax.quiver(0, 0, 0, *a2, color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2 ax.quiver(0, 0, 0, *a3, color='green', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a3), label='$a_3=('+', '.join(map(str, a3))+')$') # ベクトルa3 ax.quiver([0, a1[0], a2[0], a3[0]], [0, a1[1], a2[1], a3[1]], [0, a1[2], a2[2], a3[2]], [0, 0, 0, 0], [0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-a3[2]], color=['gray', 'red', 'blue', 'green'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線 for i in range(len(beta_vals)): formula_label = '$\\beta_1 a_1 + \\beta_2 a_2 + \\beta_3 a_3$' if i == 0 else '' # 1つだけ凡例を設定 ax.plot_wireframe(x1_grid[i], x2_grid[i], x3_grid[i], alpha=0.5, label=formula_label) # ベクトルa1,a2,a3による空間:(a1,a3と平行な平面) ax.plot_wireframe(x1_grid[:, i], x2_grid[:, i], x3_grid[:, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a2,a3と平行な平面) ax.plot_wireframe(x1_grid[:, :, i], x2_grid[:, :, i], x3_grid[:, :, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a1,a2と平行な平面) #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.legend() ax.set_aspect('equal') ax.view_init(elev=30, azim=h) # 表示角度 # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100) # gif画像を保存 ani.save('independence360_3d.gif')
ベクトルと線形結合したベクトルのグラフについて、係数(ベクトル)の値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 60 # 係数の作成用の値(ラジアン)として利用する値を指定 t_n = np.linspace(start=0.0, stop=2.0*np.pi, num=frame_num+1)[:frame_num] u_n = np.linspace(start=2.0/3.0*np.pi, stop=2.0/3.0*np.pi, num=frame_num+1)[:frame_num] # ベクトルのサイズ調整用の値を指定 r = 1.0 # 矢印のサイズを指定 l = 0.6 # グラフオブジェクトを初期化 fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', subplot_kw={'projection': '3d'}) fig.suptitle('linear dependence', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目の係数を作成 t = t_n[i] u = u_n[i] beta1 = r * np.sin(t) * np.cos(u) beta2 = r * np.sin(t) * np.sin(u) beta3 = r * np.cos(t) # 線形従属になるベクトルを作成 b = beta1 * a1 + beta2 * a2 + beta3 * a3 # ラベル用の符号を設定 sgn2 = '+' if beta2 >= 0.0 else '' sgn3 = '+' if beta3 >= 0.0 else '' # 線形従属の3Dベクトルを作図 ax.quiver(0, 0, 0, *a1, color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1 ax.quiver(0, 0, 0, *a2, color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2 ax.quiver(0, 0, 0, *a3, color='green', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a3), label='$a_3=('+', '.join(map(str, a3))+')$') # ベクトルa3 ax.quiver(0, 0, 0, *b, color='chocolate', linewidth=3, arrow_length_ratio=l/np.linalg.norm(b), label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb ax.quiver([0, a1[0], a2[0], a3[0], b[0]], [0, a1[1], a2[1], a3[1], b[1]], [0, a1[2], a2[2], a3[2], b[2]], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-a3[2], z_min-b[2]], color=['gray', 'red', 'blue', 'green', 'chocolate'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線 for i in range(len(beta_vals)): formula_label = '$\\beta_1 a_1 + \\beta_2 a_2 + \\beta_3 a_3$' if i == 0 else '' # 1つだけ凡例を設定 ax.plot_wireframe(x1_grid[i], x2_grid[i], x3_grid[i], alpha=0.5, label=formula_label) # ベクトルa1,a2,a3による空間:(a1,a3と平行な平面) ax.plot_wireframe(x1_grid[:, i], x2_grid[:, i], x3_grid[:, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a2,a3と平行な平面) ax.plot_wireframe(x1_grid[:, :, i], x2_grid[:, :, i], x3_grid[:, :, i], alpha=0.5) # ベクトルa1,a2,a3による空間:(a1,a2と平行な平面) 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('$b=' + str(beta1.round(2))+'a_1' + sgn2+str(beta2.round(2))+'a_2' + sgn3+str(beta3.round(2))+'a_3$', loc='left') ax.legend(loc='upper left', prop={'size': 7}) ax.set_aspect('equal') #ax.view_init(elev=90, azim=270) # xy面 #ax.view_init(elev=0, azim=270) # xz面 #ax.view_init(elev=0, azim=0) # yz面 # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100) # gif画像を保存 ani.save('dependence_3d.gif')
サイズr
と角度t, u
を変更すると、ベクトルが空間(とその延長)上の全ての点になり得るのを確認できます。
同一空間上に4つ以上のベクトルがある場合、それらは線形結合で等しくなる(この例だととして)なるので、として(を移項して)が成り立ち、線形従属の条件を満たすのでした(5.1節)。ここで、は3次元の0ベクトルです。
逆に、同一平面上に平行でない3つのベクトルのみの場合、のときに限りが成り立ち、線形独立の条件を満たします。
またこの例は、線形独立な3つの3次元ベクトルなので、基底ベクトルと言えます。
以上が、3次元におけるベクトルの線形独立の可視化です。
この記事では、線形独立なベクトルを可視化しました。次の記事では、正射影ベクトルを可視化します。
参考書籍
- Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.
おわりに
前回の記事と対の内容になってるのでセットで読んでください。
(どうでもいいのですが)自分の感覚的に「従属↔独立」の関係は「従属↔非従属」ではなく「非独立↔独立」なので「dependence↔independence」の文字列と意味を対応させにくい。
【次の内容】