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

行列の核 (零空間)

 

核 (零空間)

平面全体 $\mathbb{R}^2$ は線形変換
 \[A=\begin{bmatrix}1&2\\3&4\end{bmatrix}\tag{1}\]
によって平面全体に写ります。なぜなら、$A$ を構成する列ベクトル
 \[\begin{bmatrix}1\\3\end{bmatrix},\quad \begin{bmatrix}2\\4\end{bmatrix}\]
は互いに独立であり、$A$ による変換は、これらのベクトルの線形結合によって表される平面全体となるからです:
 \[\begin{bmatrix}1&2\\3&4\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}=x\begin{bmatrix}1\\3\end{bmatrix}+y\begin{bmatrix}2\\4\end{bmatrix}\tag{2}\]
今回、着目するのは零ベクトル $\boldsymbol{0}$ の写り先です。もちろん、明らかに $\boldsymbol{0}$ はどのような線形変換によっても $\boldsymbol{0}$ に写ります:
 \[\begin{bmatrix}1&2\\3&4\end{bmatrix}\begin{bmatrix}0\\0\end{bmatrix}=\begin{bmatrix}0\\0\end{bmatrix}\tag{3}\]
正則行列 (逆行列をもつ行列) による写像は全単射 (点と点が1対1に対応する変換) なので重複はありません。したがって、$\boldsymbol{0}$ 以外に $\boldsymbol{0}$ に写されるベクトルは存在しません。連立方程式 $A\boldsymbol{x}=\boldsymbol{0}$ を満たす解は $\boldsymbol{x}=\boldsymbol{0}$ だけです。

しかし行列が正則でない場合、すなわち行列を構成する列ベクトルが互いに独立でないときは事情が異なります。たとえば、
 \[B=\begin{bmatrix}1&2\\2&4\end{bmatrix}\tag{4}\]
という行列を考えます。行列 $B$ は $\mathbb{R}^2$ 空間、すなわち平面全体を直線に変換します:
 \[\begin{bmatrix}1&2\\2&4\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}=x\begin{bmatrix}1\\2\end{bmatrix}+y\begin{bmatrix}2\\4\end{bmatrix}=(x+2y)\begin{bmatrix}1\\2\end{bmatrix}\tag{5}\]
こうした性質は 『行列の階数』 でも学びましたが、今知りたいことは、平面全体のうち、どの部分が $\boldsymbol{0}$ に写されたかということです。すなわち、$B\boldsymbol{x}=\boldsymbol{0}$ を満たすような部分空間 を $B$ から切り出すことを考えます。(5) が $\boldsymbol{0}$ になるためには、
 \[x+2y=0\tag{6}\]
という条件を満たす必要があります。すなわち、平面上でこの方程式で表される直線はすべて $B$ によって $\boldsymbol{0}$ に押し込められます。(6) は媒介変数 $s$ を使って
 \[\boldsymbol{r}=s\begin{bmatrix}-2\\1\end{bmatrix}\tag{7}\]
のようにベクトル形式で表すこともできます。

核 (零空間)

一般にベクトル空間 $V$ に属する $\boldsymbol{x}$ の中で、$T\boldsymbol{x}=\boldsymbol{0}$ ($T$ は線形変換) を満たすベクトル $\boldsymbol{x}$ の集合を $T$ の 核 (kernel) または 零空間 とよび、$\mathrm{Ker}T$ または $\mathbb{N}(T)$ で表します。
 
$\boldsymbol{x},\ \boldsymbol{y}$ がともに $T$ の核であるとき、すなわち $T\boldsymbol{x}=\boldsymbol{0},\ T\boldsymbol{y}=\boldsymbol{0}$ を満たすとき、
 \[\begin{align*}&T(\boldsymbol{x}+\boldsymbol{y})=\boldsymbol{0}\tag{8}\\[6pt]&T(c\boldsymbol{x})=\boldsymbol{0}\tag{9}\end{align*}\]
が成り立つので、$\mathrm{Ker}T$ は $V$ の部分空間です。核 (kernel) あるいは零空間最初の例で見たように、$T$ が正則であれば、核は $\boldsymbol{x}=\boldsymbol{0}$ だけです ($\mathrm{Ker}T=\{\boldsymbol{0}\}$)。ベクトル空間は必ず $\boldsymbol{0}$ を含むことを考えると、これは最小サイズの部分空間といえます。証明は省略しますが、逆も成立することが知られています。すなわち $\mathrm{Ker}T=\{\boldsymbol{0}\}$ であれば $T$ は正則です。つまり「 $T$ が正則である」と $\mathrm{Ker}T=\{\boldsymbol{0}\}$ は同値です。

特解と一般解

矩形行列
 \[A=\begin{bmatrix}1&2&2&4\\3&10&6&20\end{bmatrix}\tag{10}\]
の核 $\mathrm{Ker}A$ を見つけてみましょう。すなわち、$4$ 元連立方程式
 \[\begin{bmatrix}1&2&2&4\\3&10&6&20\end{bmatrix}\begin{bmatrix}x\\y\\z\\w\end{bmatrix}=\begin{bmatrix}0\\0\\0\\0\end{bmatrix}\tag{11}\]
を満たす解集合を求めます。このままでも解けますが、係数行列を操作して、なるべく簡単な形に直しておきます。最初に $2$ 行目から $1$ 行目の $3$ 倍を引きます:
 \[\begin{bmatrix}1&2&2&4\\0&4&0&8\end{bmatrix}\tag{12}\]
次に $2$ 行目を $4$ で割ると、
 \[\begin{bmatrix}1&2&2&4\\0&1&0&2\end{bmatrix}\tag{13}\]
となります。最後に $1$ 行目から $2$ 行目の $2$ 倍を引きます。
 \[\begin{bmatrix}1&0&2&0\\0&1&0&2\end{bmatrix}\tag{14}\]
このように、左端に部分的に単位行列の成分をもつ行列を行簡約階段行列といいます。

一般的な連立方程式の形で表すと
 \[\begin{align*}x+2z=0\tag{15}\\[6pt]
y+2w=0\tag{16}\end{align*}\]
となります。したがって、一般解 (解集合) は
 \[\begin{bmatrix}-2z\\-2w\\z\\w\end{bmatrix}=\begin{bmatrix}-2z\\0\\z\\0\end{bmatrix}+\begin{bmatrix}0\\-2w\\0\\w\end{bmatrix}=z\begin{bmatrix}-2\\0\\1\\0\end{bmatrix}+w\begin{bmatrix}0\\-2\\0\\1\end{bmatrix}\tag{17}\]
のように、$2$ つの特解の線形結合で表されます。$z$ と $w$ をあらためて $s,\ t$ とおきます。
 \[\mathrm{Ker}A=s\begin{bmatrix}-2\\0\\1\\0\end{bmatrix}+t\begin{bmatrix}0\\-2\\0\\1\end{bmatrix}\tag{18}\]
 これは $4$ 次元ベクトルが張る平面を表しています。

scipy.linalg.null_space()

scipy.linalg.null_space(A) は SVD 分解という手法を使って、配列 A の核 (零空間) の基底行列 (基底ベクトルを横に並べた行列) を返します。上の例で扱った矩形行列
 \[A=\begin{bmatrix}1&2&2&4\\3&10&6&20\end{bmatrix}\tag{10}\]
の核 (零空間) を null_space() を使って求めてみます。

# python_null_space

# In[1]

import numpy as np
from scipy import linalg

# 2×4の行列を定義
A = np.array([[1, 2, 2, 4],
              [3, 10, 6, 20]])

# Aの核(ゼロ空間)の基底行列
ns = linalg.null_space(A)

print(ns)
# [[-0.8 -0.4]
#  [ 0.4 -0.8]
#  [ 0.4  0.2]
#  [-0.2  0.4]]

アルゴリズムの性質上、核の基底は近似的な値となります。
(18) とは基底のとり方が異なっていますが、A と基底行列の積は、ほぼ零行列となります。

# In[2]

print(A @ ns)
# [[ 2.22044605e-16  2.22044605e-16]
#  [-4.44089210e-15 -1.77635684e-15]]

A と A の核の基底ベクトルの線形結合の積も確認しておきましょう。

# In[3]

K = 2*ns[:,0:1] + 3*ns[:,1:2]
print(A @ K)
# [[ 1.33226763e-15]
#  [-8.88178420e-15]]

kernel()

SymPy で行列の核を返す kernel() 関数を作成しておきました。

# python_kernel

# In[1]

from sympy import *
from sympy.matrices import Matrix

# aの核を求める関数
def kernel(a, x):
    ax = a * x
    lh = []
    var = []
    for i in range(ax.shape[0]):
        lh.append(ax[i])
        var.append(x[i])
    return solve(lh, var)

kernel(a, x) の a には行列、x には記号 (シンボル) を格納したベクトルを渡します。
先ほど扱った矩形行列
 \[A=\begin{bmatrix}1&2&2&4\\3&10&6&20\end{bmatrix}\tag{10}\]
の核を kernel() 関数を使って求めてみましょう。

# In[2]

# w,x,y,zを記号として定義
var("w:z")

# 行列を定義
a = Matrix([[1, 2, 2, 4],
            [3, 10, 6, 20]])

# ベクトルを定義
r = Matrix([[x],
            [y],
            [z],
            [w]])

# aの核(ar=0の解)
print(kernel(a, r))

# {x: -2*z, y: -2*w}

左零空間

転置行列 $A^T$ の零空間 (核)、すなわち
 \[A^T\boldsymbol{x}=\boldsymbol{0}\tag{19}\]
をみたす部分空間 $\boldsymbol{x}$ を $A$ の 左零空間 とよび、$\mathbb{N}(A^T)$ で表します。たとえば、$2\times 3$ の行列
 \[A=\begin{bmatrix}1&1&2\\3&3&6\end{bmatrix}\tag{20}\]
が与えられたとき、$A^T$ は $\mathbb{R}^2$ 全体を $\mathbb{R}^3$ の直線上に写します:
 \[\begin{bmatrix}1&3\\1&3\\2&6\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}=(x+3y)\begin{bmatrix}1\\1\\2\end{bmatrix}\tag{21}\]
このうち、$x+3y=0$ で表される直線上の点が $A^T$ によって $\boldsymbol{0}$ に写ります。すなわち $A$ の左零空間は
 \[\mathbb{N}(A^T)=s\begin{bmatrix}-3\\1\end{bmatrix}\tag{22}\]
となります。

 

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    ## 核 ( 零空間 )
    「x, y がともに」 の 「x, y」を太字に修正
    ##scipy.linalg.null_space( )
    #In[2]
    print(a @ ns) → print(A @ ns)
    #In[3]
    print(a @ K) → print(A @ K)
    ## 左零空間
    1 x 2 の行列 → 2 x 3 の行列
    (x+2y) → (x+3y)

    下記は誤植ではありませんが、括弧書きで説明を入れたほうがわかりやすいと思いました。
    ## 核 (零空間 )
    Tx=0を満たす → Tx=0 ( Tは線型変換 )
    ## kernel( )
    xには記号を → xには記号( シンボル )を

  2. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    (22)式の右辺で、s[1 1 2]^T → s[-3 1]^T