はじめに
『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。
この記事は6.4節「行列ベクトル積」の内容です。
行列とベクトルの積の定義を確認して、行列とベクトルの積を用いた計算を導出します。また、NumPyライブラリでの扱い方を確認します。
【前の内容】
【他の内容】
【今回の内容】
6.4.0 行列のベクトル積の計算例の導出
行列とベクトルの積の計算を数式とプログラム(NumPyライブラリ)で確認します。
「6.4.0:行列のベクトル積の性質の導出【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」では、定義式を確認して、性質を導出しました。今回は、計算例を導出します。
利用するライブラリを読み込みます。
# 利用ライブラリ import numpy as np
行列のベクトル積の計算例
行列とベクトルの積を用いた計算例を導出します。
行列とゼロベクトルの積
行列 と全ての要素が の 次元ベクトル(ゼロベクトル) の積を考えます。
全ての要素が0によって消えるので、 次元のゼロベクトル になります。
0ベクトルを作成して、行列とベクトルの積を計算します。
# 行列を確認 print(A) print(A.shape) # 行数を取得 n = A.shape[1] print(n) # 0ベクトルを作成 o_n = np.zeros(n) print(o_n) print(o_n.shape) # 行列と0ベクトルの積を計算 o_m = np.dot(A, o_n) print(o_m) print(o_m.shape)
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
(3, 4)
4
[0. 0. 0. 0.]
(4,)
[0. 0. 0.]
(3,)
0ベクトルとの積が0ベクトルになるのを確認できます。
ゼロ行列とベクトルの積
全ての要素が の 行列(ゼロ行列) と 次元ベクトル の積を考えます。
全ての要素が0によって消えるので、 次元のゼロベクトル になります。
0行列を作成して、行列とベクトルの積を計算します。
# ベクトルを確認 print(x) print(x.shape) # 行数を指定 m = 5 # 列数を取得 n = len(x) print(n) # 0行列を作成 O = np.zeros((m, n)) print(O) print(O.shape) # 0行列とベクトルの積を計算 o = np.dot(O, x) print(o) print(o.shape)
[-1. 2. -3. 4.]
(4,)
4
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
(5, 4)
[0. 0. 0. 0. 0.]
(5,)
0行列との積が0ベクトルになるのを確認できます。
行列と標準単位ベクトルの積
行列 と 番目(1つ)の要素が で他の要素が の 次元ベクトル(標準単位ベクトル) の積を考えます。
の 列目の要素が取り出される(他の要素が0によって消える)ので、列ベクトル になります。
同様に、 の転置行列と 次元の標準単位ベクトル の積を考えます。
の 行目( の 列目)の要素が取り出される(他の要素が0によって消える)ので、行ベクトル になります。行ベクトルを横ベクトルとする場合は、 です。
列インデックスを指定し、列数に対応した標準単位ベクトルを作成して、行列とベクトルの積を計算します。
# 行列を確認 print(A) print(A.shape) # 列インデックスを指定 j = 2 # 列数を取得 n = A.shape[1] print(n) # 標準単位ベクトルを作成 e_j = np.zeros(n) e_j[j] = 1.0 print(e_j) print(e_j.shape) # 行列と標準単位ベクトルの積を計算 a_j = np.dot(A, e_j) print(a_j) print(a_j.shape)
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
(3, 4)
4
[0. 0. 1. 0.]
(4,)
[ 2. 6. 10.]
(3,)
行列に標準単位ベクトルを掛けると、対応する(指定した)列ベクトルを取り出せるのを確認できます。
列ベクトルの抽出処理を添字で行います。
# 列を抽出 a_j = A[:, j] print(a_j) print(a_j.shape)
[ 2. 6. 10.]
(3,)
同様に、行インデックスを指定し、行数に対応した標準単位ベクトルを作成して、行列とベクトルの積を計算します。
# 行インデックスを指定 i = 1 # 行数を取得 m = A.shape[0] print(m) # 標準単位ベクトルを作成 e_i = np.zeros(m) e_i[i] = 1.0 print(e_i) print(e_i.shape) # 転置行列と標準単位ベクトルの積を計算 a_i = np.dot(A.T, e_i) print(a_i) print(a_i.shape)
3
[0. 1. 0.]
(3,)
[4. 5. 6. 7.]
(4,)
転置行列に標準単位ベクトルを掛けると、対応する(指定した)行ベクトルを取り出せるのを確認できます。
行ベクトルの抽出処理を添字で行います。
# 行を抽出 a_i = A[i] print(a_i) print(a_i.shape)
[4. 5. 6. 7.]
(4,)
単位行列とベクトルの積
対角要素が で非対角要素が の 行列(単位行列) と 次元ベクトル の積を考えます。
各行と同じインデックスの要素が から取り出される(他の要素が0によって消える)ので、ベクトル が変化しません。
単位行列を作成して、行列とベクトルの積を計算します。
# ベクトルを確認 print(x) print(x.shape) # 行(列)数を取得 n = len(x) print(n) # 単位行列を作成 I = np.identity(n) print(I) print(I.shape) # 単位行列とベクトルの積を計算 x = np.dot(I, x) print(x) print(x.shape)
[-1. 2. -3. 4.]
(4,)
4
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
(4, 4)
[-1. 2. -3. 4.]
(4,)
単位行列との積は要素が変化しないのを確認できます。
行列とイチベクトルの積
行列 と全ての要素が の 次元ベクトル(イチベクトル) の積を考えます。
を行ごとに総和をとった 次元ベクトルになります。
さらに、 の積を行列の列数(ベクトルの次元数) で割ることを考えます。
の行ごとの総和をそれぞれ要素数(列数)で割ったベクトルになります。つまり、各要素は、 の各行の平均です。
行ベクトル の要素の平均を
とすると、次のように表わせます。
を行ごとに平均をとった 次元ベクトルになります。
内積と平均の関係については「1.4:内積の性質と計算例【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」を参照してください。
同様に、 の転置行列と 次元のイチベクトル の積を考えます。
を列ごと( を行ごと)に総和をとった 次元ベクトルになります。
さらに、 の積を転置行列の列数(元の行列の行数・ベクトルの次元数) で割ることを考えます。
の列ごと( の行ごと)の総和をそれぞれ要素数(行数)で割ったベクトルになります。つまり、各要素は、 の各列の平均です。
列ベクトル の要素の平均を
とすると、次のように表わせます。
を列ごと( を行ごと)に平均をとった 次元ベクトルになります。
列数に対応した1ベクトルを作成して、行列とベクトルの積を計算します。
# 行列を確認 print(A) print(A.shape) # 列数を取得 n = A.shape[1] print(n) # 1ベクトルを作成 c = np.ones(n) print(c) print(c.shape) # 行列と1ベクトルの積を計算 sum_a = np.dot(A, c) print(sum_a) print(sum_a.shape)
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
(3, 4)
4
[1. 1. 1. 1.]
(4,)
[ 6. 22. 38.]
(3,)
行列に1ベクトルを掛けると、行ごとの総和になるのを確認できます。
さらに、列数で割ります。
# 行列と1ベクトルの積を計算 avg_a = np.dot(A, c) / n print(avg_a) print(avg_a.shape)
[1.5 5.5 9.5]
(3,)
行列に1ベクトルを掛けて列数(要素数)で割ると、行ごとの平均になるのを確認できます。
総和と平均の計算をNumPy関数で行います。
# 行ごとの和を計算 sum_a = np.sum(A, axis=1) print(sum_a) print(sum_a.shape) # 行ごとの平均を計算 avg_a = np.mean(A, axis=1) print(avg_a) print(avg_a.shape)
[ 6. 22. 38.]
(3,)
[1.5 5.5 9.5]
(3,)
行ごとの和は np.sum(axis=1)
、行ごとの平均は np.mean(axis=1)
で計算できます。
同様に、行数に対応した1ベクトルを作成して、行列とベクトルの積を計算します。
# 行列を確認 print(A) print(A.shape) # 行数を取得 m = A.shape[0] print(m) # 1ベクトルを作成 c = np.ones(m) print(c) print(c.shape) # 転置行列と1ベクトルの積を計算 sum_a = np.dot(A.T, c) print(sum_a) print(sum_a.shape)
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
(3, 4)
3
[1. 1. 1.]
(3,)
[12. 15. 18. 21.]
(4,)
転置行列に1ベクトルを掛けると、列ごとの総和になるのを確認できます。
さらに行数で割ります。
# 転置行列と1ベクトルの積を計算 avg_a = np.dot(A.T, c) / m print(avg_a) print(avg_a.shape)
[4. 5. 6. 7.]
(4,)
転置行列に1ベクトルを掛けて行数(要素数)で割ると、列ごとの平均になるのを確認できます。
総和と平均の計算をNumPy関数で行います。
# 列ごとの和を計算 sum_a = np.sum(A, axis=0) print(sum_a) print(sum_a.shape) # 行ごとの平均を計算 avg_a = np.mean(A, axis=0) print(avg_a) print(avg_a.shape)
[12. 15. 18. 21.]
(4,)
[4. 5. 6. 7.]
(4,)
列ごとの和は np.mean(axis=0)
、列ごとの平均は np.mean(axis=0)
で計算できます。
イチ行列とベクトルの積
全ての要素が の 行列(イチ行列) と 次元ベクトル の積を考えます。
の総和を 個に複製した 次元ベクトルになります。
さらに、 の積をベクトルの次元数(行列の列数) で割ることを考えます。
の総和を要素数(次元数)で割ったベクトルになります。つまり、各要素は、 の平均です。
ベクトル の要素の平均を
とすると、次のように表わせます。
の総和を 個に複製した 次元ベクトルになります。
1行列を作成して、行列とベクトルの積を計算します。
# ベクトルを確認 print(x) print(x.shape) # 行数を指定 m = 5 # 列数を取得 n = len(x) print(n) # 1行列を作成 C = np.ones((m, n)) print(C) print(C.shape) # 1行列とベクトルの積を計算 sum_x = np.dot(C, x) print(sum_x) print(sum_x.shape)
[-1. 2. -3. 4.]
(4,)
4
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
(5, 4)
[2. 2. 2. 2. 2.]
(5,)
1行列にベクトルを掛けると、総和が複製されるのを確認できます。
さらに、次元数で割ります。
# 1行列とベクトルの積を計算 avg_x = np.dot(C, x) / n print(avg_x) print(avg_x.shape)
[0.5 0.5 0.5 0.5 0.5]
(5,)
1行列にベクトルを掛けて次元数(要素数)で割ると、平均が複製されるのを確認できます。
複製処理をNumPy関数で行います。
# 総和を複製 sum_x = np.repeat(np.sum(x), repeats=m) print(sum_x) print(sum_x.shape) # 平均を複製 avg_x = np.repeat(np.mean(x), repeats=m) print(avg_x) print(avg_x.shape)
[2. 2. 2. 2. 2.]
(5,)
[0.5 0.5 0.5 0.5 0.5]
(5,)
値の複製は np.repeat()
で行えます。複製する要素数を repeats
引数に指定します。
差分行列とベクトルの積
対角要素が でその右隣の要素が でそれら以外の要素が の 行列を差分行列(difference matrix)と呼びます。
、、 です。
差分行列 と 次元ベクトル の積を考えます。
各行と同じインデックスの要素の符号を反転し、またその次の要素が から取り出される(他の要素が0によって消える)ので、 の隣り合う要素の差の 次元ベクトルになります。
差分行列を作成して、行列とベクトルの積を計算します。
# ベクトルを確認 print(x) print(x.shape) # 列数を設定 n = len(x) print(n) # 差分行列を作成 D = np.array( [[-1.0 if i == j else 1.0 if i+1 == j else 0.0 for j in range(n)] for i in range(n-1)] ) print(D) print(D.shape) # 差分行列とベクトルの積を計算 diff_x = np.dot(D, x) print(diff_x) print(diff_x.shape)
[-1. 2. -3. 4.]
(4,)
4
[[-1. 1. 0. 0.]
[ 0. -1. 1. 0.]
[ 0. 0. -1. 1.]]
(3, 4)
[ 3. -5. 7.]
(3,)
行と列についての2重のリスト内包表記の for
文の中で、複数条件の if
文を使って差分行列を作成します。
リスト内包表記の中で、ループ処理と2つの条件分岐を行う場合は、次のように記述します。
[条件1の値 if 条件1 else 条件2の値 if 条件2 else 条件1,2を満たさない場合の値 for文]
行インデックス i
と列インデックス j
を比較して、条件に応じて -1.0, 1.0, 0.0
の値を配列に格納します。
差分の計算をNumPy関数で行います。
# 隣り合う要素の差を計算 diff_x = np.diff(x, n=1) print(diff_x) print(diff_x.shape)
[ 3. -5. 7.]
(3,)
隣り合う要素の差分は np.diff()
で計算できます。差分をとる要素数を n
引数に指定します。
累積和行列とベクトルの積
1番目の要素から対角要素までが でそれら以外の要素が の 行列を累積和行列(running sum matrix)と呼びます。
、 です。
累積和行列 と 次元ベクトル の積を考えます。
各行と同じインデックス までの要素 が から取り出される(他の要素が0によって消える)ので、 の 番目までの累積和( 番目から 番目までの 個の要素の和)の 次元ベクトルになります。
累積和行列を作成して、行列とベクトルの積を計算します。
# ベクトルを確認 print(x) print(x.shape) # 行(列)数を設定 n = len(x) print(n) # 累積和行列を作成 S = np.array( [[np.float32(i >= j) for j in range(n)] for i in range(n)] ) print(S) print(S.shape) # 累積和行列とベクトルの積を計算 cumsum_x = np.dot(S, x) print(cumsum_x) print(cumsum_x.shape)
[-1. 2. -3. 4.]
(4,)
4
[[1. 0. 0. 0.]
[1. 1. 0. 0.]
[1. 1. 1. 0.]
[1. 1. 1. 1.]]
(4, 4)
[-1. 1. -2. 2.]
(4,)
行と列についての2重のリスト内包表記の for
文を使って累積和行列を作成します。
行インデックス i
と列インデックスの j
の大小関係を比較して、因子型の返り値を np.float()
で数値型に変換して、配列に格納します。True
は 1
、False
は 0
になります。
累積和の計算をNumPy関数で行います。
# 累積和を計算 cumsum_x = np.cumsum(x) print(cumsum_x) print(cumsum_x.shape)
[-1. 1. -2. 2.]
(4,)
累積和は np.cumsum()
で計算できます。
この記事では、行列とベクトルの積の計算例を確認しました。次の記事では、性質を確認します。
参考書籍
- Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.
おわりに
くどくどくどと書き下しました。
分かってるつもりのことも丁寧に掘り下げていくと分かってなかったことに気付けるので、いつも自分の理解にツッコミを入れながら言語化してます。が、6章の内容については、分かってることをやっぱり分かってたと再確認してる感じです。分からないよりはいいのですが、要は少し物足りないです。ま、ゼロつくシリーズで散々ぱらやったからね。
そんなに言うなら応用例もやれよって話ですが、これまで同様飛ばします。
私の代わりに、読んでなるほどと思ってくれる人がいれば嬉しいです。
今回で言うと、差分行列と累積和行列は知らなかったのでそれでよしとします。何に使えるのか分からないけど。
しかし丁寧に読んでくれた人がいたとして、結局どれも関数一発で求められるよというオチにどう感じるのでしょうかね。私はNumPyって便利ねと思いました。
【次の内容】