行列のランクとは?NumPy (matrix_rank) での計算方法


行列のランクとは? NumPy (matrix_rank) での計算方法の詳細解説

はじめに:線形代数の核となる概念「ランク」

線形代数は、現代科学、工学、コンピュータサイエンス、データ分析、機械学習といった多岐にわたる分野で不可欠な数学的ツールです。その中でも、「行列のランク」という概念は、行列が持つ情報の本質的な量や、それが表現する線形変換の性質、線形方程式系の解の構造などを理解する上で極めて重要な役割を果たします。

ランクは、一見すると抽象的な数値に思えるかもしれませんが、その背後には「独立した情報の数」や「変換が情報をどれだけ圧縮するか」といった直感的な意味合いが隠されています。例えば、データ分析において、各行がデータポイント、各列が特徴量を表す行列を考えたとき、その行列のランクは「データに含まれる独立した特徴量の数」や「データの真の次元」を示唆することがあります。

本記事では、行列のランクについて、その厳密な定義から始め、それが持つ様々な意味や性質、そしてどのように計算されるのかを解説します。特に、プログラミング言語Pythonの強力な数値計算ライブラリであるNumPyを使った行列のランクの計算方法に焦点を当てます。NumPyのnumpy.linalg.matrix_rank関数は、ランク計算の標準的な方法を提供しますが、その使い方、特に浮動小数点数の精度問題を扱うための重要な引数tolについて、詳細な説明と具体的なコード例を通じて深く掘り下げていきます。

この記事を読むことで、行列のランクという概念への理解を深め、NumPyを使って実際にランクを計算し、その結果を解釈できるようになることを目指します。線形代数を学習中の方、データ分析や機械学習で線形代数を利用する方、あるいは単に行列のランクについて知りたいすべての方にとって、本記事が有用な情報源となることを願っています。

行列のランクの定義:多様な側面から理解する

行列のランクは、いくつかの同値な定義を持つ概念です。これらの異なる定義を理解することで、ランクの持つ様々な側面やその意味するところをより深く把握することができます。ここでは、主要な3つの定義を紹介します。

1. 線形独立な行(または列)ベクトルの最大個数

最も直感的で、線形代数の基礎概念に根ざした定義はこれでしょう。$m \times n$行列 $A$ は、$m$個の行ベクトルと$n$個の列ベクトルを持つと見なせます。

  • 行ランク (row rank): 行列 $A$ の行ベクトルの中から選ぶことができる、互いに線形独立なベクトルの最大個数。
  • 列ランク (column rank): 行列 $A$ の列ベクトルの中から選ぶことができる、互いに線形独立なベクトルの最大個数。

線形代数の重要な定理として、「任意の行列において、行ランクと列ランクは常に等しい」というものがあります。この等しい値を、その行列の「ランク (rank)」と呼び、$\text{rank}(A)$ と表記します。

線形独立とは?
$k$個のベクトル $v_1, v_2, \dots, v_k$ が線形独立であるとは、それらの線形結合がゼロベクトルになる唯一の方法が、すべての係数をゼロにすることである場合を指します。すなわち、
$c_1 v_1 + c_2 v_2 + \dots + c_k v_k = \mathbf{0}$
を満たすスカラー $c_1, c_2, \dots, c_k$ が $c_1 = c_2 = \dots = c_k = 0$ の場合に限られるとき、これらのベクトルは線形独立です。
もし、少なくとも一つゼロでない係数を用いてゼロベクトルを生成できる場合、これらのベクトルは線形従属(線形依存)であると言います。このとき、少なくとも一つのベクトルを、他のベクトルの線形結合として表すことができます。

直感的な意味:
線形独立なベクトルの個数は、それらのベクトルが張る空間の次元を表します。したがって、行列のランクは、その行ベクトル(または列ベクトル)が張るベクトル空間(行空間または列空間と呼ばれます)の次元に等しいと言えます。これは、行列が表現する情報の「真の次元」を示唆するものです。

2. 階段行列(または簡約化された階段行列)の非ゼロ行の個数

行列のランクを計算する実際的な方法の一つに、行基本変形(または列基本変形)を用いて行列を階段行列(row-echelon form)または簡約化された階段行列(reduced row-echelon form, RREF)に変形する方法があります。

行基本変形: 以下の3種類の操作は、行列の行空間を変えず、したがってランクも変えません。
1. ある行をゼロでない定数倍する。
2. ある行と別の行を入れ替える。
3. ある行に別の行の定数倍を加える。

これらの操作を有限回繰り返すことで、任意の行列を階段行列に変形できます。

階段行列の定義:
階段行列は、以下の条件を満たす行列です。
1. すべてのゼロ行は、非ゼロ行の下に位置する。
2. 非ゼロ行の先頭の非ゼロ要素(これを「主成分 (pivot)」または「先行要素 (leading entry)」と呼ぶ)は、その上の行の主成分よりも右に位置する。
3. 主成分を含む列において、主成分より下の要素はすべてゼロである。

さらに、以下の条件を満たす場合、簡約化された階段行列(RREF)と呼ばれます。
4. すべての非ゼロ行の主成分は1である。
5. 主成分を含む列において、主成分以外の要素はすべてゼロである。

ランクとの関係:
行列を階段行列(または簡約化された階段行列)に変形したとき、非ゼロ行の個数がその行列のランクに等しくなります。これは、行基本変形が線形独立な行ベクトルの個数を変えないため、そして階段行列の非ゼロ行は必ず線形独立になるためです。

この定義は、手計算やアルゴリズムとしてランクを計算する際に非常に有用です。ガウス消去法(Gaussian elimination)は、この階段行列への変形プロセスそのものです。

3. 特異値分解(SVD)における非ゼロ特異値の個数

特異値分解(Singular Value Decomposition, SVD)は、任意の $m \times n$ 行列 $A$ を3つの行列の積に分解する強力な手法です。
$A = U \Sigma V^T$
ここで、$U$ は $m \times m$ の直交行列、$V$ は $n \times n$ の直交行列、$\Sigma$ は $m \times n$ の対角行列です。
$\Sigma$ の対角成分 $\sigma_1, \sigma_2, \dots, \sigma_{\min(m, n)}$ は非負の実数であり、これらを $A$ の特異値と呼びます。慣習として、特異値は通常、大きい順に並べられます ($\sigma_1 \ge \sigma_2 \ge \dots \ge 0$)。

ランクとの関係:
行列 $A$ のランクは、その非ゼロ特異値の個数に等しくなります。

この定義は、特に数値計算において非常に重要です。なぜなら、SVDは数値的に安定した方法で計算できるため、浮動小数点数の精度問題を伴うランク計算に適しているからです。多くの数値計算ライブラリ(NumPyを含む)では、内部的にSVDを利用してランクを計算しています。

まとめ:異なる定義の関連性

これらの定義はすべて同値であり、行列のランクという単一の概念を異なる視点から捉えたものです。

  • 線形独立な行/列の最大個数は、ランクの「次元」に関する意味を強調します。
  • 階段行列の非ゼロ行の個数は、ガウス消去法という「計算アルゴリズム」との関連を明確にします。
  • SVDの非ゼロ特異値の個数は、数値計算における「安定した計算方法」を提供します。

これらの定義を総合的に理解することで、行列のランクが線形代数における様々な概念(線形独立、ベクトル空間、線形変換、固有値/特異値など)といかに深く結びついているかが分かります。

行列のランクが持つ意味と性質

行列のランクは単なる数値ではなく、行列が表現する線形変換やデータ構造に関する深い情報を含んでいます。

1. 線形方程式系の解の構造

$m$個の連立一次方程式と$n$個の変数からなる線形方程式系 $A\mathbf{x} = \mathbf{b}$ を考えます。ここで、$A$ は $m \times n$ 係数行列、$\mathbf{x}$ は $n$次元の未知数ベクトル、$\mathbf{b}$ は $m$次元の定数ベクトルです。

この方程式系に解が存在するか、そして解が一意であるかどうかは、係数行列 $A$ のランクと、拡大係数行列 $[A | \mathbf{b}]$ のランクの関係によって決まります。

  • 解が存在する条件: 解が存在するための必要十分条件は、$\text{rank}(A) = \text{rank}([A | \mathbf{b}])$ であることです。これは、$\mathbf{b}$ が $A$ の列空間に含まれることと同値です。
  • 解が一意である条件: 解が存在する場合において、解が一意であるための条件は、$\text{rank}(A) = n$(変数の数)であることです。もし $\text{rank}(A) < n$ ならば、解は無限に存在します。この場合、$\text{rank}(A)$ 個の線形独立な特殊解と、$n – \text{rank}(A)$ 個の線形独立な斉次方程式($A\mathbf{x} = \mathbf{0}$)の解(核空間の基底ベクトル)の線形結合として一般解が表されます。$n – \text{rank}(A)$ は核空間の次元(nullity)と呼ばれ、次元定理 $\text{rank}(A) + \text{nullity}(A) = n$ が成り立ちます。

まとめ: ランクは、線形方程式系が「解を持つかどうか(可解性)」および「解が一意かどうか」を判定する決定的な指標となります。

2. 線形変換の性質

$m \times n$ 行列 $A$ は、ベクトル空間 $\mathbb{R}^n$ からベクトル空間 $\mathbb{R}^m$ への線形変換 $T: \mathbb{R}^n \to \mathbb{R}^m$ を表現します。
$T(\mathbf{x}) = A\mathbf{x}$

この線形変換において、変換によって到達可能な $\mathbb{R}^m$ の部分空間を像空間 (image space) と呼び、$\text{Im}(T)$ または $\text{col}(A)$(列空間)と表記します。像空間は、行列 $A$ の列ベクトルが張る空間です。

  • ランクと像空間の次元: 行列 $A$ のランクは、線形変換 $T$ の像空間の次元に等しくなります。$\text{rank}(A) = \dim(\text{Im}(T))$.

これは、ランクが変換によって「保存される情報量」や「変換後の空間の次元」を示していることを意味します。ランクが低い行列は、高次元の情報を低次元の空間に押し込める(情報を圧縮する)変換を表します。特に、ランクが $\min(m, n)$ よりも小さい場合、その変換は「情報落ち」を伴います。

3. 行列の正則性(正方行列の場合)

$n \times n$ の正方行列 $A$ について考えます。正方行列が「正則 (invertible)」(逆行列を持つ)であることと、そのランクが最大であることは同値です。

  • $n \times n$ 行列 $A$ が正則である ⇔ $\text{rank}(A) = n$

ランクが最大である状態を「フルランク (full rank)」と呼びます。したがって、正方行列がフルランクであることと、それが正則であることは同じ意味です。ランクが $n$ より小さい場合、その行列は「特異 (singular)」または「ランク落ち (rank deficient)」であると言われ、逆行列を持ちません。

4. データ分析における意味

データを行列として表現する場合、各行がサンプル、各列が特徴量(またはその逆)を表すことが多いです。このデータ行列のランクは、データセットに含まれる「独立した情報の個数」や「データが実際に存在する真の次元」を示唆します。

例えば、3次元空間にある点が、すべて一つの平面上にある場合、これらの点の座標を並べた行列のランクは2になります。これは、見かけ上3次元のデータであっても、本質的には2次元の情報しか含まれていないことを意味します。

主成分分析(PCA)のような次元削減手法は、データ行列のランクに近い次元の空間にデータを射影することで、情報の損失を最小限に抑えつつ次元を削減します。特異値分解とランクの関係は、PCAの理論的基盤とも深く関連しています。データ行列の非ゼロ特異値の個数がランクであり、大きい特異値に対応する特異ベクトルがデータの主要な方向を示します。

5. ランクの計算と数値安定性

理論的には、ランクは線形独立性の判定や階段行列への変換によって計算できます。しかし、コンピュータ上での浮動小数点数を用いた計算では、微小な誤差が問題となります。

例えば、真のランクが2の行列に非常に小さなノイズが乗ると、理論的には線形従属だったベクトルが、計算上は「ほぼ線形独立」と判定されてしまい、ランクが3と計算されてしまうことがあります。

$\begin{pmatrix} 1 & 2 \ 2 & 4 \end{pmatrix}$ のランクは1です。(2行目 = 2 × 1行目)
しかし、 $\begin{pmatrix} 1 & 2 \ 2 & 4 + 10^{-10} \end{pmatrix}$ という行列を考えます。この2つの行ベクトルは厳密には線形独立です。したがって、定義上この行列のランクは2です。しかし、実質的にはランク1の行列と「ほとんど同じ」であり、多くの応用においてはランク1と見なしたい場合があります。

このような数値的な問題を回避するために、ランクの計算では「ほぼゼロ」を判定するための閾値(tolerance, tol)が重要になります。特にSVDに基づく計算では、特異値のうち「閾値より大きいもの」の個数をランクとします。この閾値を適切に設定することが、数値計算において正確なランクを得るために不可欠です。NumPyのmatrix_rank関数も、この閾値を扱うための引数を提供しています。

NumPyを使った行列のランクの計算 (numpy.linalg.matrix_rank)

Pythonで数値計算を行う際の標準的なライブラリであるNumPyは、線形代数に関する豊富な機能を提供しており、その中には行列のランクを計算する関数も含まれています。それが numpy.linalg.matrix_rank です。

numpy.linalg.matrix_rank(M, tol=None, hermitian=False)

この関数は、行列 M のランクを計算して返します。以下に、その引数と詳細な挙動を説明します。

引数

  1. M:
    • 入力となる行列(またはテンソル)。array_like 型を受け付けます。
    • 形状は (..., M, N) である必要があります。つまり、最後の2つの次元が行列を表し、それより前の次元はバッチ処理を表します。本記事では主に2次元の行列 (M, N) を扱います。
  2. tol:
    • 浮動小数点数の比較における「ほぼゼロ」とみなすための閾値(Tolerance)を指定します。float または array_like を受け付けます。
    • 重要: tolNone の場合、関数は内部的に適切な閾値を自動的に計算します。通常、この自動計算に頼るのが最も安全で推奨される方法です。自動計算される閾値は、SVDによって得られた最大の特異値と、行列の形状に基づいて決定されます。具体的には、max(S) * max(M.shape) * np.finfo(M.dtype).eps という式で計算されることが多いです(ここで S は特異値の配列、np.finfo(M.dtype).eps は使用されている浮動小数点数型のマシンイプシロンです)。これは相対誤差を考慮した閾値であり、数値的な精度を考慮する上で非常に合理的です。
    • tol に明示的な値を指定する場合、その値より小さい非負の特異値はゼロとみなされます。したがって、非ゼロ特異値の個数は、「特異値 > tol」となる特異値の個数となります。閾値を自分で設定する際は、行列のスケールやデータの性質を十分に理解している必要があります。不適切な tol の設定は、ランクを誤って計算する原因となります。
  3. hermitian:
    • 入力行列 M がエルミート行列(または実対称行列)であるかどうかを指定するブール値です。デフォルトは False です。
    • True に設定すると、エルミート行列の性質を利用した、より効率的で数値的に安定したアルゴリズム(通常は固有値分解を使用)を用いてランクを計算することがあります。ただし、NumPyの内部実装によっては、エルミート行列の場合でもSVDを使用しつつ、その対称性を考慮した処理を行うこともあります。エルミート行列/対称行列であることが分かっている場合は、hermitian=True を設定することが推奨されます。

戻り値

  • 行列 M のランクを示す整数値を返します。
  • バッチ処理の場合(入力が3次元以上のテンソル)、最後の2次元の行列ごとに計算されたランクの配列を返します。

計算方法(内部)

numpy.linalg.matrix_rank は、内部的に特異値分解(SVD)を使用してランクを計算するのが一般的です。SVDにより得られた特異値を $\sigma_1, \sigma_2, \dots, \sigma_k$($k = \min(m, n)$)とすると、ランクは、これらの特異値のうち、指定された閾値 tol よりも厳密に大きいものの個数として計算されます。

$\text{rank}(A) = |{ \sigma_i \mid \sigma_i > \text{tol} }|$

このSVDに基づく方法は、ガウス消去法に比べて数値的に安定しているという利点があります。

NumPy matrix_rank の使い方:具体的なコード例

それでは、NumPyを使って実際に様々な行列のランクを計算してみましょう。

まず、NumPyライブラリをインポートします。

python
import numpy as np

例1:簡単なフルランク行列

簡単な正方行列で、ランクが最大(フルランク)となる場合を見てみましょう。

“`python

2×2のフルランク行列

A = np.array([[1, 2],
[3, 4]])
rank_A = np.linalg.matrix_rank(A)
print(f”行列 A:\n{A}”)
print(f”行列 A のランク: {rank_A}”)

3×3のフルランク行列

B = np.array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]) # 単位行列
rank_B = np.linalg.matrix_rank(B)
print(f”\n行列 B:\n{B}”)
print(f”行列 B のランク: {rank_B}”)

C = np.array([[1, 2, 3],
[0, 1, 2],
[0, 0, 1]]) # 階段行列
rank_C = np.linalg.matrix_rank(C)
print(f”\n行列 C:\n{C}”)
print(f”行列 C のランク: {rank_C}”)
“`

実行結果:

“`
行列 A:
[[1 2]
[3 4]]
行列 A のランク: 2

行列 B:
[[1 0 0]
[0 1 0]
[0 0 1]]
行列 B のランク: 3

行列 C:
[[1 2 3]
[0 1 2]
[0 0 1]]
行列 C のランク: 3
“`

これらの行列は、行ベクトル(または列ベクトル)が互いに線形独立であり、それぞれの次元における最大ランク(2×2行列なら2、3×3行列なら3)を持っています。単位行列や階段行列は明らかにフルランクです。

例2:ランク落ちする行列

行ベクトル(または列ベクトル)が線形従属であるような行列の場合、ランクは最大値よりも小さくなります。

“`python

2×2のランク落ち行列

D = np.array([[1, 2],
[2, 4]]) # 2行目 = 2 * 1行目
rank_D = np.linalg.matrix_rank(D)
print(f”行列 D:\n{D}”)
print(f”行列 D のランク: {rank_D}”)

3×3のランク落ち行列

E = np.array([[1, 2, 3],
[4, 5, 6],
[5, 7, 9]]) # 3行目 = 1行目 + 2行目
rank_E = np.linalg.matrix_rank(E)
print(f”\n行列 E:\n{E}”)
print(f”行列 E のランク: {rank_E}”)

3×2の行列 (m > n)

F = np.array([[1, 1],
[2, 2],
[3, 3]]) # 各列は同じベクトル
rank_F = np.linalg.matrix_rank(F)
print(f”\n行列 F:\n{F}”)
print(f”行列 F のランク: {rank_F}”)

2×3の行列 (m < n)

G = np.array([[1, 2, 3],
[2, 4, 6]]) # 2行目 = 2 * 1行目
rank_G = np.linalg.matrix_rank(G)
print(f”\n行列 G:\n{G}”)
print(f”行列 G のランク: {rank_G}”)
“`

実行結果:

“`
行列 D:
[[1 2]
[2 4]]
行列 D のランク: 1

行列 E:
[[1 2 3]
[4 5 6]
[5 7 9]]
行列 E のランク: 2

行列 F:
[[1 1]
[2 2]
[3 3]]
行列 F のランク: 1

行列 G:
[[1 2 3]
[2 4 6]]
行列 G のランク: 1
“`

これらの例では、行ベクトル間または列ベクトル間に線形従属関係が存在するため、ランクは行列の次元よりも小さくなっています。例えば、行列 D の行ベクトル $(1, 2)$ と $(2, 4)$ は線形従属です($(2, 4) = 2 \times (1, 2)$)。線形独立な行ベクトルは1つしか取れないため、ランクは1です。同様に行列 E では、3行目が1行目と2行目の和であるため、3つの行ベクトルは線形従属ですが、1行目と2行目は線形独立なので、ランクは2になります。

一般に、$m \times n$ 行列のランクは $\min(m, n)$ を超えることはありません。

例3:浮動小数点数と tol 引数の重要性

浮動小数点数を含む計算では、微小な誤差が結果に影響を与える可能性があります。tol 引数はこの問題を扱うために不可欠です。

“`python

理論的にはランク1だが、浮動小数点誤差を含む行列

H_exact = np.array([[1.0, 2.0],
[2.0, 4.0]]) # ランク1
H_noisy = H_exact + np.random.randn(2, 2) * 1e-10 # 微小なノイズを加える

print(f”理論的にランク1の行列:\n{H_exact}”)
print(f”微小なノイズを加えた行列:\n{H_noisy}”)

デフォルトのtolで計算

rank_H_noisy_default = np.linalg.matrix_rank(H_noisy)
print(f”\n行列 H_noisy のランク (デフォルト tol): {rank_H_noisy_default}”)

小さすぎるtolで計算 (誤差をゼロと見なさない)

マシンイプシロンよりずっと小さい値を指定してみる

rank_H_noisy_small_tol = np.linalg.matrix_rank(H_noisy, tol=1e-20)
print(f”行列 H_noisy のランク (tol=1e-20): {rank_H_noisy_small_tol}”)

大きすぎるtolで計算 (微小な特異値だけでなく、本来非ゼロであるべき特異値もゼロと見なしてしまう可能性)

この例では特異値が比較的小さいので、適当な大きな値で試す

rank_H_noisy_large_tol = np.linalg.matrix_rank(H_noisy, tol=0.1)
print(f”行列 H_noisy のランク (tol=0.1): {rank_H_noisy_large_tol}”)

適切なtolで計算 (デフォルトtolは通常適切)

ここではデフォルトと同じ結果になる可能性が高い

デフォルトtolの値を自分で計算して指定してみる

SVDを実行して特異値を確認

U, S, V = np.linalg.svd(H_noisy)
print(f”\n行列 H_noisy の特異値: {S}”)
default_tol_value = S[0] * max(H_noisy.shape) * np.finfo(H_noisy.dtype).eps
print(f”計算されたデフォルト tol の値: {default_tol_value}”)
rank_H_noisy_calculated_tol = np.linalg.matrix_rank(H_noisy, tol=default_tol_value)
print(f”行列 H_noisy のランク (計算された tol): {rank_H_noisy_calculated_tol}”)

注: np.finfo(M.dtype).eps は dtype に応じたマシンイプシロン

float64の場合、約2.22e-16

float32の場合、約1.19e-7

上記のデフォルトtol計算式はNumPyの内部実装と完全に一致しない場合がありますが、考え方は同様です。

NumPyのドキュメントによると、tol=Noneの場合の閾値は max(S) * max(M.shape) * eps を少し調整した値が使われるようです。

“`

実行結果の例(ノイズはランダムなので毎回変わりますが、傾向は同じです):

“`
行列理論的にランク1の行列:
[[1. 2.]
[2. 4.]]
微小なノイズを加えた行列:
[[1.0000000001090694 2.0000000000725705]
[2.000000000063496 4.000000000033468 ]]

行列 H_noisy のランク (デフォルト tol): 1
行列 H_noisy のランク (tol=1e-20): 2
行列 H_noisy のランク (tol=0.1): 0

行列 H_noisy の特異値: [5.000000000101468e+00 1.048911532698532e-10]
計算されたデフォルト tol の値: 8.881784197001252e-16
行列 H_noisy のランク (計算された tol): 1
“`

この例から、tol の重要性が分かります。

  • 理論的にはランク1の行列に微小なノイズが加わると、厳密にはランク2になります。
  • tol=None (デフォルト) の場合、NumPyは適切に閾値を設定し、特異値のうち非常に小さいもの(ここでは約 $10^{-10}$)をゼロと見なして、ランクを1と計算します。これが通常望ましい振る舞いです。
  • tol を非常に小さく(例えば $10^{-20}$)設定すると、微小な特異値もゼロと見なされず、ランクは2と計算されてしまいます。これは数値計算における誤差を無視することになり、多くの場合、現実のデータ分析などでは不適切です。
  • tol を大きく(例えば 0.1)設定すると、本来ゼロと見なすべきでない特異値(ここでは約 5.0)もゼロと見なされてしまい、ランクが0と計算されてしまいます。これも明らかに間違いです。

結論として、NumPyでランクを計算する際は、特別な理由がない限り tol=None としてデフォルトの自動計算に任せるのが最も安全で推奨される方法です。自分で tol を設定するのは、行列の性質やスケール、そして必要な計算精度を十分に理解している場合に限るべきです。

例4:エルミート行列

実対称行列や複素エルミート行列の場合、hermitian=True 引数を使用することで、計算効率や安定性が向上する可能性があります。

“`python

実対称行列

J = np.array([[1, 2],
[2, 3]])
rank_J = np.linalg.matrix_rank(J, hermitian=True)
print(f”対称行列 J:\n{J}”)
print(f”対称行列 J のランク (hermitian=True): {rank_J}”)

複素エルミート行列

エルミート行列: 共役転置しても元の行列と同じになる行列 (A = A^H)

実対称行列はエルミート行列の特別な場合

K = np.array([[2, 1j],
[-1j, 3]]) # K[0,1] = conj(K[1,0])
rank_K = np.linalg.matrix_rank(K, hermitian=True)
print(f”\nエルミート行列 K:\n{K}”)
print(f”エルミート行列 K のランク (hermitian=True): {rank_K}”)
“`

実行結果:

“`
対称行列 J:
[[1 2]
[2 3]]
対称行列 J のランク (hermitian=True): 2

エルミート行列 K:
[[ 2.+0.j 0.+1.j]
[ 0.-1.j 3.+0.j]]
エルミート行列 K のランク (hermitian=True): 2
“`

これらの例では、行列が正則(ランクが最大)なので、ランクはそれぞれの次元と同じになります。エルミート行列のランク計算では、SVDではなく固有値分解(EVD)が使われることが一般的です。エルミート行列の固有値は常に実数であり、非ゼロ固有値の個数がランクに等しくなります(ただし、固有値の符号は正負両方あり得るので、絶対値が閾値より大きい固有値の個数を数えることになります)。hermitian=True は、NumPyにこの特性を活かした計算方法を選択させるためのヒントを与えます。

例5:大きな行列での計算

実際の応用では、数千、数万といった次元の大きな行列を扱うこともあります。matrix_rank は比較的大きな行列にも対応できますが、SVDは計算コストが高い(行列の次元の3乗に比例)ため、非常に大きな行列では時間がかかる可能性があります。

“`python

100×100 のランダムなフルランク行列(ほぼ確実に)

L = np.random.rand(100, 100)
rank_L = np.linalg.matrix_rank(L)
print(f”100×100 ランダム行列 L のランク: {rank_L}”)

100×100 のランク落ち行列を作成

ランクを50に設定してみる

M = np.random.rand(100, 50) @ np.random.rand(50, 100)

この行列 M は、最大でランク50になります。(ランク(AB) <= min(ランク(A), ランク(B)))

rank_M = np.linalg.matrix_rank(M)
print(f”100×100 ランク50で作成した行列 M のランク: {rank_M}”)

注: 上記 M の作成方法では、理論的にはランク50ですが、

浮動小数点誤差によりわずかに高いランクが計算される可能性もゼロではありません。

デフォルトのtolがこれをうまく処理してくれるはずです。

“`

実行結果:

100x100 ランダム行列 L のランク: 100
100x100 ランク50で作成した行列 M のランク: 50

ランダムに生成された行列は、よほど特殊な場合でない限りフルランクになります。行列の積 $AB$ のランクは、元の行列のランク以下になる性質を利用して、意図的にランク落ちした行列を作成することもできます。上記の例では、100×50の行列と50×100の行列の積をとることで、最大ランクが50の100×100行列を作成しています。matrix_rank はこれを正しく計算しています。

行列のランクの応用例

行列のランクは、理論的な概念であると同時に、様々な分野の実践的な問題解決に利用されています。

1. 線形方程式系の解析

これは最も基本的な応用です。経済学、工学、物理学など、多くの分野で現れる連立一次方程式系の解の存在や一意性を判定するためにランクが用いられます。例えば、回路解析、構造計算、流体シミュレーションなどで、大規模な線形方程式系を解く前に、その可解性をランクを使って確認することができます。

2. データの次元削減(主成分分析など)

先述のように、データ行列のランクはデータの真の次元と関連があります。高次元のデータから冗長な情報を取り除き、データの主要な構造を抽出する次元削減手法(例: PCA)において、ランクの概念、特にSVDにおける特異値が中心的な役割を果たします。特異値の大きさは、対応する方向(特異ベクトル)がデータにどれだけ寄与しているかを示し、小さい特異値に対応する方向は無視することで、データの次元を削減します。

3. 機械学習

  • 特徴量選択: データ行列のランクは、特徴量間にどれだけ線形従属関係があるかを示します。ランクが低い場合、一部の特徴量は他の特徴量の線形結合で表せるため、冗長な特徴量が存在することを示唆します。特徴量選択やエンジニアリングの際に、ランクを参考にすることもあります。
  • モデルの正則化: 線形回帰などで特徴量が多い場合に発生しやすい多重共線性(特徴量間に高い線形相関がある状態、データ行列がランク落ちに近い状態)は、モデルの安定性を損ないます。リッジ回帰やラッソ回帰といった正則化手法は、このようなランク落ちに近い状況でも安定した解を得るのに役立ちます。ランクの概念は、これらの手法の理解や診断に繋がります。
  • 低ランク近似: 行列をそのランクよりも低いランクの行列で近似する手法は、ノイズ除去、画像の圧縮、レコメンデーションシステムなどで利用されます。例えば、ユーザーとアイテムの評価行列を低ランク近似することで、評価されていない項目の値を予測するといった協調フィルタリングに応用されます。

4. 信号処理と画像処理

  • ノイズ除去: 信号や画像は、理想的には低ランクな構造を持つことが多いですが、ノイズが加わるとランクが上がります。元の信号/画像を低ランク行列としてモデル化し、観測された高ランクなデータから低ランク成分を抽出することで、ノイズを除去する手法があります(例: Robust PCA)。
  • 画像圧縮: 画像データをブロックに分割し、それぞれのブロックを行列として扱い、低ランク近似することでデータを圧縮する手法があります。

5. 制御理論

システムの可制御性や可観測性といった重要な性質は、システムを表す行列(可制御性行列、可観測性行列など)のランクによって判定されます。システムの設計や解析において、ランクの計算は基本的なステップとなります。

6. ネットワーク分析

グラフの隣接行列やラプラシアン行列といった、ネットワーク構造を表す行列のランクも、ネットワークの特性(連結成分の数など)と関連があります。

これらの例からわかるように、行列のランクは単なる数学的な性質に留まらず、実世界の様々な問題を記述し、解析し、解決するための強力なツールとして活用されています。

ランク計算における注意点とベストプラクティス

NumPyのmatrix_rankを使う際に考慮すべき点や、ランク計算全般における注意点をまとめます。

  1. 浮動小数点数の精度: 最も重要な注意点です。コンピュータ上の浮動小数点演算には誤差が伴うため、理論的にゼロであるべき値が微小な非ゼロ値として計算されることがあります。これにより、ランクが過大評価される可能性があります。
  2. tol 引数の選択: 浮動小数点数の精度問題を適切に扱うために、tol 引数の役割を理解することが不可欠です。
    • 推奨: 通常は tol=None とし、NumPyによるデフォルトの自動計算に任せます。これは、行列のスケールやデータの型に応じて適切な閾値を設定してくれるため、最も頑健な方法です。
    • 注意: 明示的に tol を指定する場合は、行列の特異値のスケール、期待されるランク、およびデータの性質を十分に理解した上で慎重に行う必要があります。不適切な tol は誤ったランクを導きます。例えば、物理的な測定データのようにノイズレベルが分かっている場合は、ノイズレベルに基づいて tol を設定することを検討できます。
  3. 計算コスト: matrix_rank は内部でSVDを使用することが一般的であり、SVDの計算コストは行列の次元の3乗に比例します。したがって、非常に大きな行列のランク計算は計算リソースを大量に消費し、時間がかかる可能性があります。計算速度がボトルネックとなる場合は、行列のスパース性を利用した手法や、近似的なランク計算手法を検討する必要があるかもしれません。
  4. 整数行列の場合: 入力が行列要素が厳密な整数のNumPy配列(例: dtype=int64)の場合、matrix_rank は内部で浮動小数点数に変換してSVD計算を行います。この際も微小な誤差が発生する可能性はありますが、整数で表現できる行列が真にランク落ちしている場合、特異値の中に厳密なゼロ値や非常に小さい値が現れることが期待されます。デフォルトの tol はこの場合も有効に機能します。
  5. 複雑な行列: 複素数要素を持つ行列の場合も、matrix_rank は適切に処理します。特にエルミート行列の場合は、hermitian=True を設定することで効率的な計算が期待できます。

まとめ

行列のランクは、線形代数における最も基本的ながらも奥深い概念の一つです。線形独立な行/列の最大個数、階段行列の非ゼロ行の個数、特異値分解における非ゼロ特異値の個数といった複数の同値な定義を持ち、それぞれがランクの異なる側面を捉えています。

ランクは、線形方程式系の解の構造、線形変換が情報をどれだけ圧縮するか(像空間の次元)、正方行列の正則性、データの真の次元といった、行列やそれが表現するシステムに関する重要な情報を提供します。

NumPyのnumpy.linalg.matrix_rank関数は、Pythonにおいて行列のランクを計算するための標準的かつ便利なツールです。内部的には特異値分解(SVD)を利用して計算されており、特に浮動小数点数の計算精度に関わる問題に対して、tol 引数によって頑健な方法を提供します。通常は tol=None としてデフォルトの自動計算に任せるのが最も推奨される使い方です。

行列のランクとその計算方法は、理論数学だけでなく、データ分析、機械学習、信号処理、制御理論など、幅広い分野で実用的に応用されています。本記事を通じて、行列のランクの概念とNumPyでの計算方法についての理解が深まり、皆様の学習や研究、実務に役立つことを願っています。

線形代数は強力な道具であり、行列のランクはその中でも特に本質的な概念です。ぜひ、様々な行列でランクを計算し、その意味を考察してみてください。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール