はじめに
『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。
この記事は6.1節「行列」から6.3節「転置, 和, ノルム」の内容です。
行列の定義を確認して、性質を導出します。また、NumPyライブラリでの扱い方を確認します。
【前の内容】
【他の内容】
【今回の内容】
6.1.0 行列の定義
行列(matrix)の基本的な定義を数式とプログラム(NumPyライブラリ)で確認します。
利用するライブラリを読み込みます。
# 利用ライブラリ import numpy as np
行列
行列は、要素(成分)を縦横に並べて次のように表されます。
行
列の行列を
行列と呼び、
行
列目の要素を
で表します。
2次元配列を作成します。
# 行列(2次元配列)を作成 A = np.array( [[0.0, 1.0, -2.3, 0.1], [1.2, 4.0, -0.1, 0.0], [4.1, -1.0, 0.0, 1.7]] ) print(A)
[[ 0. 1. -2.3 0.1]
[ 1.2 4. -0.1 0. ]
[ 4.1 -1. 0. 1.7]]
多次元配列は、入れ子にしたリストを np.array()
に渡して作成できます。
次の 行列
を2次元配列
A
とします。
ただし、Pythonのインデックスが0から割り当てられるのに合わせた添字(インデックス)で表記しています。
配列の形状を確認します。
# 次元数を確認 print(A.ndim) # 形状を確認 print(A.shape) # 要素数を確認 print(A.size)
2
(3, 4)
12
NumPy配列の ndim
メソッドで次元数、shape
メソッドで次元ごとの要素数、size
メソッドで全ての要素数を返します。
A
は の行列(2次元配列)だと分かります。
インデックスを指定して、配列から要素を抽出します。
# 行・列インデックスを指定 i = 2 j = 1 # 要素を抽出 a_ij = A[i, j] print(a_ij)
-1.0
添字を使って 2次元配列[行番号, 列番号]
で要素を取り出せます。ただし、数式 とプログラム
A[i, j]
でインデックスの割り当て方が異なる場合に注意が必要です。
行列 の
列目の全ての要素を列ベクトル(column vector)と呼びます。
次元縦ベクトルを
行列として扱います。
または、 行目の全ての要素を行ベクトル(row vector)と呼びます。
次元横ベクトルを
行列として扱います。
ベクトル を縦ベクトル・横ベクトルのどちらで扱うのかはその記事等の定義によります。
を縦ベクトルとする場合は
で横ベクトル、
を横ベクトルとする場合は
で縦ベクトルを表わせます。
また、 行列はスカラとして扱います。
行インデックスを指定して、配列から行を取り出します。
# 行インデックスを指定 i = 2 # 行ベクトルを抽出 a_i = A[i] print(a_i)
[ 4.1 -1. 0. 1.7]
2次元配列[行番号]
で行を取り出せます。
列インデックスを指定して、配列から列を取り出します。
# 列インデックスを指定 j = 1 # 列ベクトルを抽出 a_j = A[:, j] print(a_j)
[ 1. 4. -1.]
2次元配列[:, 列番号]
で列を取り出せます。:
は全ての要素を表します。
NumPyの1次元配列には縦横の区別がなく、転置できません。
# 転置を確認 print(a_j.T)
[ 1. 4. -1.]
転置については「転置行列」を参照してください。
行数と列数の大小関係により呼び方が変わります。
# 行・列数を取得 m = A.shape[0] n = A.shape[1] print(m) print(n) # 行列の形を確認 if m == n: print('square matrix') elif m > n: print('tail matrix') elif m < n: print('wide matrix')
3
4
wide matrix
行数と列数が同じ( の)行列を正方行列(square matrix)と呼びます。
6.2.0 行列の例
ここまでは、行列の基本的な定義を確認しました。ここからは、個別に名前の付いた(それぞれ特徴を持つ)行列を確認していきます。
ブロック行列と部分行列
次の行列を部分行列(submatrix)とします。
横方向に結合する行列の行数、縦方向に結合する行列の列数がそれぞれ一致する必要があります。
複数の行列によって構成される行列をブロック行列(block matrix)と呼びます。また、ブロック行列を構成する行列を部分行列と呼びます。
複数の2次元配列を作成します。
# (部分)行列を作成 B = np.array( [[0.0, 1.0], [2.0, 3.0]] ) C = np.array( [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]] ) D = np.array( [[0.0, 1.0], [2.0, 3.0], [4.0, 5.0]] ) E = np.array( [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]] ) print(B) print(C) print(D) print(E)
[[0. 1.]
[2. 3.]]
[[0. 1. 2.]
[3. 4. 5.]]
[[0. 1.]
[2. 3.]
[4. 5.]]
[[0. 1. 2.]
[3. 4. 5.]
[6. 7. 8.]]
B, C
、D, E
の行数、B, D
、C, E
の列数がそれぞれ同じになるように行列を作成して、部分行列とします。
複数の配列を結合します。
# (ブロック)行列を作成 A = np.block( [[B, C], [D, E]] ) print(A) print(A.shape)
[[0. 1. 0. 1. 2.]
[2. 3. 3. 4. 5.]
[0. 1. 0. 1. 2.]
[2. 3. 3. 4. 5.]
[4. 5. 6. 7. 8.]]
(5, 5)
np.block()
で複数の行列を結合して、ブロック行列を作成できます。
スカラや1次元配列を含む場合も同様に処理できます。
# 1次元配列を作成 B = np.array([0.0, 2.0, 3.0]) print(B) # スカラを作成 C = -1.0 print(C) # 2次元配列を作成 D = np.array( [[2.0, 2.0, 1.0], [1.0, 3.0, 5.0]] ) print(D) # 2次元配列を作成 E = np.array( [[4.0], [4.0]] ) print(E) # (ブロック)行列を作成 A = np.block( [[B, C], [D, E]] ) print(A) print(A.shape)
[0. 2. 3.]
-1.0
[[2. 2. 1.]
[1. 3. 5.]]
[[4.]
[4.]]
[[ 0. 2. 3. -1.]
[ 2. 2. 1. 4.]
[ 1. 3. 5. 4.]]
(3, 4)
スカラを の行列、ベクトル(1次元配列)を
または
の行列として扱い、対応する行数・列数が一致する必要があります。
行インデックスについて 、列インデックスについて
として、
行列を
とします。
の「行
から行
まで」と「列
から列
まで」の全ての要素を
で表します。
部分行列 は、
行列になります。
は次のブロック行列で表わせます。
インデックスの範囲を指定して、配列から配列を抽出します。
# 行・列インデックスの範囲を指定 p, q = 1, 2 r, s = 2, 3 # 部分行列を抽出 A_sub = A[p:(q+1), r:(s+1)] print(A_sub) print(A_sub.shape)
[[1. 4.]
[5. 4.]]
(2, 2)
2次元配列[複数の行番号, 複数の列番号]
で配列を取り出せます。
を列ベクトルとすると、
を次のブロック行列で表わせます。
次元の列ベクトルを横に
個並べて
行列になります。
また、 を行ベクトルとすると、次のように表わせます。
次元の行ベクトルを縦に
個並べて
行列になります。
ちなみに、行方向の結合を連結(concatenatiton)、列方向の結合をスタック(stack)と呼ぶとありますが、np.np.concatenate()
と np.stack()
とは挙動がことなります。
ゼロ行列
全ての要素が0の行列をゼロ行列(zero matrix)と呼びます。
全てのインデックスで です。
のゼロ行列を
で表すこともあります。
行数と列数を指定して、全ての要素が 0
の配列を作成します。
# 行・列数を指定 m = 5 n = 6 # ゼロ行列を作成 O = np.zeros((m, n)) print(O) print(O.shape)
[[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]]
(5, 6)
np.zeros()
でゼロ行列を作成できます。
単位行列
インデックス が同じ要素を対角要素(diagonal element)、
が異なる要素を非対角要素(off-diagonal element)と呼びます。
対角要素が1でそれ以外の要素が0の正方行列を単位行列(identity matrix)と呼びます。
対角要素(値が1の要素)と非対角要素(値が0の要素)をまとめて次の式で表わせます。
の単位行列を
や
で表すこともあります。
行数を指定して、対角要素が 1
で非対角要素が 0
の配列を作成します。
# 行数を指定 n = 4 # 単位行列を作成 I = np.identity(n) print(I) print(I.shape)
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
(4, 4)
np.identity()
で単位行列を作成できます。
インデックスを指定して、対角要素や非対角要素を抽出します。
# 行・列インデックスを指定 i = 3 j = 2 # 対角要素を抽出 I_ii = I[i, i] print(I_ii) # 非対角要素を抽出 I_ij = I[i, j] print(I_ij)
1.0
0.0
標準単位ベクトルを縦ベクトル
とすると、単位行列は次のブロック行列で表わせます。
また、標準単位ベクトルを横ベクトル
とすると、次のブロック行列で表わせます。
次元の縦または行ベクトルを
個並べて
行列になります。
標準単位ベクトルについては「【Python】1.3:標準単位ベクトルの線形結合の可視化【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」を参照してください。
対角行列
非対角要素が0の正方行列を対角行列(diagonal matrix)と呼びます。
です。対角要素が0の場合もあります。
ゼロ行列と単位行列も対角行列です。
個の対角要素を
として、
で対角行列を表すこともあります。
さらに、 の正方行列を
として、
で対角要素のベクトルを表すこともあります。
1次元配列を指定して、指定した値を対角要素とする配列を作成します。
# ベクトルを作成 a = np.array([0.2, -3.0, 1.2]) print(a) print(a.shape) # 対角行列を作成 A = np.diag(a) print(A) print(A.shape)
[ 0.2 -3. 1.2]
(3,)
[[ 0.2 0. 0. ]
[ 0. -3. 0. ]
[ 0. 0. 1.2]]
(3, 3)
1次元配列を np.diag()
に渡すと、対角要素が入力した値、非対角要素が 0
の2次元配列を返します。
対角要素を抽出します。
# 対角要素を抽出 b = np.diag(A) print(b) print(b.shape)
[ 0.2 -3. 1.2]
(3,)
2次元配列を np.diag()
に渡すと、対角要素を取り出して1次元配列として返します。
ゼロ行列や単位行列も対角行列なので、np.diag()
を使って作成できます。
対角要素として、全ての要素が 0
の1次元配列を指定します。
# 行数を指定 n = 4 # ゼロ行列を作成 O = np.diag(np.zeros(n)) print(O) print(O.shape)
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
(4, 4)
対角要素が 0
で非対角要素も 0
になるので、ゼロ行列を作成できます。
対角要素として、全ての要素が 1
の1次元配列を指定します。
# 行数を指定 n = 4 # 単位行列を作成 I = np.diag(np.ones(n)) print(I) print(I.shape)
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
(4, 4)
対角要素が 1
で非対角要素も 0
になるので、単位行列を作成できます。
三角行列
の要素が
である正方行列を上三角行列(upper triangular matrix)と呼びます。
左下の要素が全て0です。
また、 の要素が
である正方行列を下三角行列(lower triangular matrix)と呼びます。
右上の要素が全て0です。
上三角行列と下三角行列をまとめて三角行列(triangular matrix)と呼びます。対角行列は上三角行列であり下三角行列でもあります。
2次元配列を指定して、左下の要素を 0
にします。
# 行列を作成 A = np.array( [[1.0, -1.0, 0.7], [-0.6, 1.2, -1.1], [-0.3, 3.5, 3.2]] ) print(A) print(A.shape) # 上三角行列を作成 B = np.triu(A) print(B) print(B.shape)
[[ 1. -1. 0.7]
[-0.6 1.2 -1.1]
[-0.3 3.5 3.2]]
(3, 3)
[[ 1. -1. 0.7]
[ 0. 1.2 -1.1]
[ 0. 0. 3.2]]
(3, 3)
2次元配列を np.triu()
に渡すと、 の要素を
0
に置き換えた配列を返します。言い換えると、 の要素を取り出して上三角行列を作成します。
2次元配列を指定して、左下の要素を 0
にします。
# 行列を作成 A = np.array( [[-0.6, 1.2], [-0.3, 3.5]] ) print(A) print(A.shape) # 下三角行列を作成 B = np.tril(A) print(B) print(B.shape)
[[-0.6 1.2]
[-0.3 3.5]]
(2, 2)
[[-0.6 0. ]
[-0.3 3.5]]
(2, 2)
2次元配列を np.tril()
に渡すと、 の要素を
0
に置き換えた配列を返します。言い換えると、 の要素を取り出して下三角行列を作成します。
疎行列
多くの要素が0である(少数の要素が0ではない値である)行列を疎行列(sparse matrix)と呼びます。
ゼロ行列や単位行列も疎行列です。
0でない値とインデックスを指定して、他の要素が全て 0
の配列を作成します。
# 行・列数を指定 m = 3 n = 4 # 非ゼロの値を指定 a = np.array([1.0, 2.0, 3.0, 4.0]) # 非ゼロの行・列インデックスを指定 row_idx = np.array([0, 0, 1, 1]) col_idx = np.array([1, 2, 2, 3]) # 疎行列を作成 A = np.zeros((m, n)) A[row_idx, col_idx] = a print(A) print(A.shape)
[[0. 1. 2. 0.]
[0. 0. 3. 4.]
[0. 0. 0. 0.]]
(3, 4)
非ゼロの要素の値とインデックスを指定します。
np.zero()
で全ての要素が 0
の配列を作成して、指定したインデックスに値を代入して、疎行列を作成します。
行列
を作成します。ただし、Pythonのインデックスが0から割り当てられるのに合わせた添字(インデックス)で表記しています。
2次元配列を指定して、多くの要素が 0
の配列を作成します。
# 行列を作成 A = np.array( [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]] ) print(A) print(A.shape) # 行列の形状を取得 m = A.shape[0] n = A.shape[1] # ゼロ行列を作成 O_nm = np.zeros((n, m)) # 単位行列を作成 I_m = np.identity(m) I_n = np.identity(n) # 行列を作成 B = np.block( [[I_m, A], [O_nm, I_n]] ) print(B) print(B.shape)
[[1. 2. 3.]
[4. 5. 6.]]
(2, 3)
[[1. 0. 1. 2. 3.]
[0. 1. 4. 5. 6.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
(5, 5)
行列
を用いて、疎行列な上三角行列
を作成します。
転置行列
行列の行と列を入れ替えることを転置(transpose)と呼び、行列 の転置行列を
で表します。
行列の転置行列は
行列になります。また、
の
行
列目の要素と
の
行
列目の要素が一致します。
転置行列 の転置行列
は元の行列
になります。
2次元配列を指定して、転置した配列を作成します。
# 行列を作成 A = np.array( [[0.0, 4.0], [7.0, 0.0], [3.0, 1.0]] ) print(A) print(A.shape) # 転置行列を作成 At = A.T print(At) print(At.shape)
[[0. 4.]
[7. 0.]
[3. 1.]]
(3, 2)
[[0. 7. 3.]
[4. 0. 1.]]
(2, 3)
NumPy配列の T
メソッドで転置できます。
元の配列と転置した配列の要素を確認します。
# 行・列インデックスを指定 i, j = 1, 0 # 行・列を入れ替えた要素を確認 print(A[i, j] == At[j, i])
True
行インデックスと列インデックスを入れ替えた要素が同じ値なのを確認できます。
更に転置すると元の配列になります。
# 転置行列の転置を確認 print(A == At.T)
[[ True True]
[ True True]
[ True True]]
それぞれの要素が同じ値なのを確認できます。
次の行列を部分行列とします。
転置してもブロック行列が成り立つには、全ての部分行列の形状が一致している必要があります。
この4つの行列によるブロック行列を とします。
それぞれ転置した行列によるブロック行列は になります。
同じ形状の複数の2次元配列を作成します。
# (部分)行列を作成 B = np.array( [[0.0, 0.1, 0.2], [0.3, 0.4, 0.5]] ) C = np.array( [[1.0, 1.1, 1.2], [1.3, 1.4, 1.5]] ) D = np.array( [[2.0, 2.1, 2.2], [2.3, 2.4, 2.5]] ) E = np.array( [[3.0, 3.1, 3.2], [3.3, 3.4, 3.5]] ) print(B) print(C) print(D) print(E)
[[0. 0.1 0.2]
[0.3 0.4 0.5]]
[[1. 1.1 1.2]
[1.3 1.4 1.5]]
[[2. 2.1 2.2]
[2.3 2.4 2.5]]
[[3. 3.1 3.2]
[3.3 3.4 3.5]]
複数の配列を結合します。
# (ブロック)行列を結合 A = np.block( [[B, C], [D, E]] ) print(A) print(A.shape)
[[0. 0.1 0.2 1. 1.1 1.2]
[0.3 0.4 0.5 1.3 1.4 1.5]
[2. 2.1 2.2 3. 3.1 3.2]
[2.3 2.4 2.5 3.3 3.4 3.5]]
(4, 6)
同様に、転置した配列を結合します。
# 転置した(ブロック)行列を結合 At = np.block( [[B.T, D.T], [C.T, E.T]] ) print(At) print(At.shape)
[[0. 0.3 2. 2.3]
[0.1 0.4 2.1 2.4]
[0.2 0.5 2.2 2.5]
[1. 1.3 3. 3.3]
[1.1 1.4 3.1 3.4]
[1.2 1.5 3.2 3.5]]
(6, 4)
元の配列と転置した配列の要素を確認します。
# 行・列インデックスを指定 i, j = 1, 3 # 行・列を入れ替えた要素を確認 print(A[i, j] == At[j, i]) # 転置行列の転置を確認 print(A == At.T)
True
[[ True True True True True True]
[ True True True True True True]
[ True True True True True True]
[ True True True True True True]]
それぞれの要素が同じ値なのを確認できます。
この記事では、基本的な行列の定義と性質を確認しました。次の記事では、グラフで確認します。
参考書籍
- Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.
おわりに
ベクトル編が終わって3か月ほど三角関数を進めていました。
この章はさすがに分かるので飛ばそうかとも思ったのですが、行列計算とかPythonやNumPyの使い方とかLaTeXコマンドで行列を書く手間とか諸々のリハビリとして書きました。
この記事を読むのは初学者・初心者の方だと思います。
このブログでは、コードと数式が対応するように表現することを心がけています。プログラムで分かるのであれば同じように数式でも分かるよってことが伝われば幸いです。
また、あえて数式を多めに書くことにしています。数式が大量に登場するとドン引きます(私もそうでしたあるいは今も)が、初心者であればこそ数式を丁寧に見た方が分かりやすいと思います。
どちらもあくまで私の経験上の話ですが。とはいえ全部をじっくり追っていたら進まないので、必要に応じて読んでください。
2023年7月12日は、エビ中の元メンバーである柏木ひなたさんのソロでの1st EPのリリース日です!
わーい、いっぱい聴くぞー。間違ってないYo~♪
【次の内容】