『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

ゼロから作るDeep Learning ? ?フレームワーク編

中古価格
¥3,500から
(2020/8/20 11:38時点)

基本行列と基本変形

【Pythonで学ぶ線形代数学講座(26)】基本行列と基本変形

基本行列

次回記事でガウス・ジョルダンの消去法による連立方程式の解き方を学びますが、その準備として線形代数における 基本行列(elementary matrices) について説明しておきます。

置換行列

任意の行列 $A$ に単位行列 $I$ を掛けると、$A$ が元の形を保つことはすでに学んでいます:
 \[IA=AI=A\]
すなわち単位行列 $I$ は恒等変換行列です。
いま、$3\times 3$ 単位行列と、$3\times 3$ 正方行列
 \[I=\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix},\quad A=\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix}\]
を考えて、$I$ の 2 行目と 3 行目を入れ替えた行列
 \[P_{2,3}=\begin{bmatrix}1&0&0\\0&0&1\\0&1&0\end{bmatrix}\]
を定義して行列 $A$ の左側に掛けてみると、
 \[P_{2,3\ }A=\begin{bmatrix}1&0&0\\0&0&1\\0&1&0\end{bmatrix}
\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix}=\begin{bmatrix}a&b&c\\g&h&i\\d&e&f\end{bmatrix}\]
となって、$A$ の 2 行目と 3 行目を入れ替えることがわかります。

$n\times n$ の単位行列の $i$ 行目と $j$ 行目を入れ替えた行列 $P_{i,j}$ を任意の行列 $A$ に左側から掛けると、$A$ の $i$ 行目と $j$ 行目を入れ替えます。$A$ の右側から掛けると列を交換します。

一般に行列に作用して行または列の位置を変える行列を 置換行列 とよびます。複数回の交換をほどこす行列、すなわち一回交換の置換行列同士の積も置換行列に含まれます。

$3\times 3$ の正方行列の置換行列は $3!=6$ 種類です。
そのうち行を $0$ 回交換する (すなわち交換しない) 置換行列は単位行列です:
 \[I=\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix}\]
行を $1$ 回交換する置換行列は $3$ 種類あります:
 \[P_{12}=\begin{bmatrix}0&1&0\\1&0&0\\0&0&1\end{bmatrix},\quad P_{13}=\begin{bmatrix}0&0&1\\0&1&0\\1&0&0\end{bmatrix},\quad P_{23}=\begin{bmatrix}1&0&0\\0&0&1\\0&1&0\end{bmatrix}\]
行を $2$ 回交換する置換行列は $2$ 種類です:
 \[P_{23}P_{12}=\begin{bmatrix}0&1&0\\0&0&1\\1&0&0\end{bmatrix},\quad P_{12}P_{23}=\begin{bmatrix}0&0&1\\1&0&0\\0&1&0\end{bmatrix}\]

行を定数倍する

正方単位行列の $(i,i)$ 成分を $k$ で置き換えた行列 $C_{i,k}$ は $A$ の $i$ 行目を $k$ 倍します:
 \[C_{1,k\ }A=\begin{bmatrix}k&0&0\\0&1&0\\0&0&1\end{bmatrix}\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix}=\begin{bmatrix}ka&kb&kc\\d&e&f\\g&h&i\end{bmatrix}\]
$k$ 倍した行を $1/k$ 倍すれば、もとの行列に戻るので、$C_{i,k}$ の逆行列は $C_{i,1/k}$ です。

別の行の定数倍を加える

正方単位行列の $(i,j)$ 成分を $k$ で置き換えた行列 $E_{i,j,k}$ は $A$ の $i$ 行目に $j$ 行目の $k$ 倍を加えます:
 \[E_{2,1,k\ }A=\begin{bmatrix}1&0&0\\k&1&0\\0&0&1\end{bmatrix}\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix}=\begin{bmatrix}a&b&c\\d+ka&e+kb&f+kc\\g&h&i\end{bmatrix}\]
$i$ 行目に $j$ 行目の $k$ 倍を加えたあとで $i$ 行目から $j$ 行目の $k$ 倍を引けば、もとの行列に戻ります。したがって、$E_{i,j,k}$ の逆行列は $E_{i,j,-k}$ となります。
 \[\begin{bmatrix}1&0&0\\-k&1&0\\0&0&1\end{bmatrix}\begin{bmatrix}a&b&c\\d+ka&e+kb&f+kc\\g&h&i\end{bmatrix}=\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix}\]

基本行列の実装

今後の記事で基本行列の積を計算することがあるので、3 種の基本行列を Python で実装しておきます。

# python_elementary_matrices

# In[1]

import numpy as np

# i行とj行の交換
def matrix_p(n, i=1, j=1):
    arr = np.eye(n)
    arr[[i-1, j-1]] = arr[[j-1, i-1]]
    return arr

# i行の定数倍
def matrix_c(n, i=1, k=1):
    arr = np.eye(n)
    arr[i-1, i-1] = k
    return arr

# i行にj行の定数倍を加える
def matrix_e(n, i=1, j=1, k=1):
    arr = np.eye(n)
    arr[i-1, j-1] = k
    return arr

numpy.eye() で指定した大きさの単位行列を生成して、必要な成分を書き換えます。matrix_p() の内部に記述されている

arr[[i-1, j-1]] = arr[[j-1, i-1]]

の意味については記事の後半を参照してください。
 
定義した関数を使って 4 × 4 の基本行列を生成してみましょう。
 
2行目と3行目を入れ替える行列:

# In[2]

p = matrix_p(4, 2, 3)

print(p)
# [[1. 0. 0. 0.]
#  [0. 0. 1. 0.]
#  [0. 1. 0. 0.]
#  [0. 0. 0. 1.]]

3行目を5倍する行列:

# In[3]

c = matrix_c(4, 3, 5)

print(c)
# [[1. 0. 0. 0.]
#  [0. 1. 0. 0.]
#  [0. 0. 5. 0.]
#  [0. 0. 0. 1.]]

3行目に1行目の2倍を加える行列:

# In[4]

e = matrix_e(4, 3, 1, 2)

print(e)
# [[1. 0. 0. 0.]
#  [0. 1. 0. 0.]
#  [2. 0. 1. 0.]
#  [0. 0. 0. 1.]]

一般に、NumPy で行を操作する場合、配列の行にアクセスして書き換えます。たとえば「 3 行目に 1 行目の 5 倍を加える」という操作は次のように記述します。

# In[5]

# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]
x = np.arange(1, 10).reshape(3,3)

# xの3行目に1行目の5倍を加える
x[2, :] = x[2, :] + 5 * x[0, :]

print(x)
# [[ 1  2  3]
#  [ 4  5  6]
#  [12 18 24]]

行の交換については以下の節を参照してください。

配列の行を入れ替える

NumPy で 配列の行を入れ替える方法を解説します。
最初に 3×3 の 2 次元配列を作成しておきます。

# python_swap_rows

# In[1]

import numpy as np

x = np.arange(1, 10).reshape(3, 3)

print(x)
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

NumPy ではファンシーインデックス (インデックスリストを渡して複数要素に同時アクセスする手法) を使って特定の行にアクセスできます。たとえば、x[[1, 2]] は 2 行目と 3 行目を縦積みした配列を取得します。

# In[2]

print(x[[1, 2]])
# [[4 5 6]
#  [7 8 9]]

x[[2, 1]] は 3 行目と 2 行目を取得します。

# In[3]

print(x[[2, 1]])
# [[7 8 9]
#  [4 5 6]]

ファンシーインデックスはメモリへの直接アクセスなので、x[[1, 2]] に x[[2, 1]] を代入すると、結果として 2 行目と 3 行目が交換されることになります (もとの配列が変更されます)。

# In[4]

x[[1, 2]] = x[[2, 1]]

print(x)
# [[1 2 3]
#  [7 8 9]
#  [4 5 6]]

一般に、x[[i-1, j-1]] = x[[j-1, i-1]] という記述で i 行と j 行を入れ替えられます。

def swap_rows(x, i, j):
    x[[i-1, j-1]] = x[[j-1, i-1]]

とはいえ、場合によっては元の配列を変更したくないと思うかもしれません。以下に定義する swap_rows()関数 は、受け取った配列のコピーを作成して行を入れ替えます。

# In[5]

def swap_rows(x, i, j):
    a = x.copy()
    a[[i-1, j-1]] = a[[j-1, i-1]]
    return a

swap_rows() の動作を確認するために、新しい 2 次元配列 y を作成しておきます。

# In[6]

# 乱数生成器のシードを0に固定
np.random.seed(0)

# 要素をランダムに決定して配列を生成
y = np.random.randint(0, 10, (5, 5))

print(y)
# [[5 0 3 3 7]
#  [9 3 5 2 4]
#  [7 6 8 8 1]
#  [6 7 7 8 1]
#  [5 9 8 9 4]]

y を swap_rows() に渡して 2 行目と 4 行目を入れ替えます。

# In[7]

# yの2行目と4行目を交換
sy = swap_rows(y, 2, 4)

print(sy)
# [[5 0 3 3 7]
#  [6 7 7 8 1]
#  [7 6 8 8 1]
#  [9 3 5 2 4]
#  [5 9 8 9 4]]

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
     
    IA=AI=I → IA=AI=A
    「別の行の定数倍を加える」で、i 行目に j 行目の k 倍を引けば → i 行目から j 行目の k 倍を引けば
    In[2]の上の文章で、x[[1, 2]] は 1 行目と 2 行目を → x[[1, 2]] は 2 行目と 3 行目を
    In[3]の上の文章で、x[[2, 1]] は 2 行目と 1 行目を → x[[2, 1]] は 3 行目と 2 行目を
    In[4]の上の文章で、1 行目と 2 行目が交換 → 2 行目と 3 行目が交換
    In[4]の下の文章で、x[[i, j]] = x[[j, i]] → x[[i-1, j-1]] = x[[j-1, i-1]]
     
    swap_rows( ) 関数も、~行目を渡して、0から始まるインデックスへの変換は関数の中で調整するやり方に統一したほうがいいのではないかと思いました。

    • あとりえこばと より:

      今回も丁寧に見てくださってありがとうございます。
      ご指摘いただいた箇所はすべて訂正しておきました。
      swap_rows() 関数も書き直しておきました。
      確かに、入れ替える行を直接引数に渡せるほうがわかりやすいですよね。