はじめに
『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。
この記事は3.1節「ノルム」の内容です。
ベクトルのユークリッドノルムを可視化します。
【前の内容】
【他の内容】
【今回の内容】
ノルムの可視化
ユークリッドノルム(euclidean norm)をグラフで確認します。
利用するライブラリを読み込みます。
# 利用ライブラリ import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation
2次元の場合
まずは、2次元空間(平面)上でベクトルのノルムを可視化します。
2次元ベクトルを指定します。
# ベクトルを指定 x = np.array([3.0, 2.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 # ノルムを計算 norm_x = np.sqrt(np.sum(x**2)) print(norm_x) norm_x = np.sqrt(np.dot(x, x)) print(norm_x) norm_x = np.linalg.norm(x) print(norm_x) # 2Dベクトルを作図 fig, ax = plt.subplots(figsize=(6, 5), 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))+'), ' + '\|x\|='+str(norm_x.round(2))+'$', loc='left') fig.suptitle('norm x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
3.605551275463989
3.605551275463989
3.605551275463989
ベクトルをaxes.quiver()
で描画します。第1・2引数に始点の座標、第3・4引数にベクトルのサイズ(変化量)を指定します。その他に、指定した値の通りに(調整せずに)矢印を描画するための設定をしています。
配列x
の前に*
を付けてアンパック(展開)して指定しています。
この例では、原点を始点とします。原点以外を始点とする場合については「距離の可視化」を参照してください。
ユークリッドノルムを計算して、タイトルに表示します。
平方根はnp.sqrt()
、内積はnp.dot()
で計算できます。また、ノルムはnp.linalg.norm()
でも計算できます。
ベクトルを直角三角形の斜辺としたときの、3辺の関係を確認します。
# 2Dベクトルを作図 fig, ax = plt.subplots(figsize=(6, 5), 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.quiver(0, 0, x[0], 0, ec='black', linewidth=1.5, scale_units='xy', scale=1, units='dots', width=0.1, headwidth=0.1) # 底辺 ax.quiver(x[0], 0, 0, x[1], ec='black', linewidth=1.5, scale_units='xy', scale=1, units='dots', width=0.1, headwidth=0.1) # 高さ ax.annotate(xy=0.5*x, text='$\sqrt{x_1^2+x_2^2}$', size=15, ha='right', va='bottom') # 斜辺ラベル ax.annotate(xy=[0.5*x[0], 0], text='$x_1$', size=15, ha='center', va='top') # 底辺ラベル ax.annotate(xy=[x[0], 0.5*x[1]], text='$x_2$', size=15, ha='left', va='center') # 高さラベル 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))+'), ' + '\|x\|='+str(norm_x.round(2))+'$', loc='left') fig.suptitle('norm x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
底辺は、高さはなので、三平方の定理より斜辺がになるのが分かります。
ベクトルの値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 51 # ベクトルとして利用する値を指定 x_vals = np.array( [np.linspace(start=-6.0, stop=6.0, num=frame_num), np.linspace(start=-4.0, stop=10.0, num=frame_num)] ).T # 作図用の値を設定 x_min = np.floor(np.min([0.0, *x_vals[:, 0]])) - 1 x_max = np.ceil(np.max([0.0, *x_vals[:, 0]])) + 1 y_min = np.floor(np.min([0.0, *x_vals[:, 1]])) - 1 y_max = np.ceil(np.max([0.0, *x_vals[:, 1]])) + 1 # 作図用のオブジェクトを初期化 fig, ax = plt.subplots(figsize=(7, 7), facecolor='white') fig.suptitle('norm x', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目のベクトルを取得 x = x_vals[i] # ノルムを計算 norm_x = np.linalg.norm(x) # 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_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.round(2)))+'), ' + '\|x\|='+str(norm_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('norm_2d.gif')
作図処理をupdate()
として定義して、FuncAnimation()
でgif画像を作成します。
3次元の場合
続いて、3次元空間上でベクトルのノルムを可視化します。
3次元ベクトルを指定します。
# ベクトルを指定 x = np.array([4.0, 3.0, 2.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 # ノルムを計算 norm_x = np.sqrt(np.sum(x**2)) print(norm_x) norm_x = np.sqrt(np.dot(x, x)) print(norm_x) norm_x = np.linalg.norm(x) print(norm_x) # 3Dベクトルを作図 fig, ax = plt.subplots(figsize=(7, 7), facecolor='white', subplot_kw={'projection': '3d'}) ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, 0, *x, color='black', arrow_length_ratio=0.05) # ベクトル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))+'), ' + '\|x\|='+str(norm_x.round(2))+'$', loc='left') fig.suptitle('norm x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
5.385164807134504
5.385164807134504
5.385164807134504
axes.quiver()
の第1・2・3引数に始点の座標、第4・5・6引数にベクトルのサイズを指定します。
「2次元の場合」と同じコードでノルムを計算できます。
ベクトルを直角三角形の斜辺としたときの、3辺の関係を確認します。
# 3Dベクトルを作図 fig, ax = plt.subplots(figsize=(7, 7), facecolor='white', subplot_kw={'projection': '3d'}) ax.scatter(*x, s=100, label='x') # 点x ax.scatter(0, 0, 0, s=100, label='O') # 原点 ax.quiver(0, 0, 0, *x, color='black', arrow_length_ratio=0.05) # ベクトルx ax.quiver(0, 0, 0, x[0], x[1], 0, color='black', arrow_length_ratio=0.00) # 底辺の斜辺 ax.quiver([0, 0, 0, 0], [0, x[1], 0, x[1]], [0, 0, x[2], x[2]], [x[0], x[0], x[0], x[0]], [0, 0, 0, 0], [0, 0, 0, 0], color='gray', arrow_length_ratio=0.0) # 横幅 ax.quiver([0, x[0], 0, x[0]], [0, 0, 0, 0], [0, 0, x[2], x[2]], [0, 0, 0, 0], [x[1], x[1], x[1], x[1]], [0, 0, 0, 0], color='gray', arrow_length_ratio=0.0) # 奥行き ax.quiver([0, x[0], 0, x[0]], [0, 0, x[1], x[1]], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [x[2], x[2], x[2], x[2]], color='gray', arrow_length_ratio=0.0) # 高さ ax.text(*0.5*x, s='$\sqrt{x_1^2 + x_2^2 + x_3^2}$', size=10, ha='right', va='bottom') # 斜辺ラベル ax.text(0.5*x[0], 0.5*x[1], 0, s='$\sqrt{x_1^2 + x_2^2}$', size=10, ha='center', va='top') # 底辺の斜辺ラベル ax.text(0.5*x[0], 0, 0, s='$x_1$', size=15, ha='center', va='top') # 横幅ラベル ax.text(x[0], 0.5*x[1], 0, s='$x_2$', size=15, ha='left', va='center') # 奥行きラベル ax.text(x[0], x[1], 0.5*x[2], s='$x_3$', size=15, ha='left', va='center') # 高さラベル 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))+'), ' + '\|x\|='+str(norm_x.round(2))+'$', loc='left') fig.suptitle('norm x', fontsize=20) ax.legend() ax.set_aspect('equal') plt.show()
三平方の定理より、横幅がで奥行きがなので底辺上の斜辺がであり、また高さがなので直方体の対角線がになるのが分かります。
ベクトルの値を変化させたアニメーションを作成します。
・作図コード(クリックで展開)
# フレーム数を設定 frame_num = 51 # ベクトルとして利用する値を指定 x_vals = np.array( [np.linspace(start=-6.0, stop=6.0, num=frame_num), np.linspace(start=0.0, stop=10.0, num=frame_num), np.linspace(start=-4.0, stop=3.0, num=frame_num)] ).T # 作図用の値を設定 x_min = np.floor(np.min([0.0, *x_vals[:, 0]])) - 1 x_max = np.ceil(np.max([0.0, *x_vals[:, 0]])) + 1 y_min = np.floor(np.min([0.0, *x_vals[:, 1]])) - 1 y_max = np.ceil(np.max([0.0, *x_vals[:, 1]])) + 1 z_min = np.floor(np.min([0.0, *x_vals[:, 2]])) - 1 z_max = np.ceil(np.max([0.0, *x_vals[:, 2]])) + 1 # 作図用のオブジェクトを初期化 fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', subplot_kw={'projection': '3d'}) fig.suptitle('norm x', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目のベクトルを取得 x = x_vals[i] # ノルムを計算 norm_x = np.linalg.norm(x) # 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.1, 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)))+'), ' + '\|x\|='+str(norm_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('norm_3d.gif')
この記事では、ユークリッドノルムを作図しました。次の記事では、ユークリッド距離を作図します。
参考書籍
- Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.
おわりに
1章の始めとほぼ同じ感じになってしまいました。まぁよく言うと、螺旋階段を一周したような感じですね?
2023年3月12日は、モーニング娘。'23の小田さくらさんの24歳のお誕生日です♪
生で聴いた「もののけ姫」が言葉にならないほど凄かった。歌声の魅力を最大限引き出すようなソロ曲はまだでしょうか。
【次の内容】