行列計算の Python 実現ガイド

【概要】行列計算の基礎と実装を,NumPyライブラリを中心に解説する。行列の積,LU分解,逆行列,行列式,固有値・固有ベクトル,特異値分解,連立一次方程式の求解,条件数,畳み込みについて,Pythonによる演習を通じて学ぶ。

【前提環境】本ガイドは Windows のパソコン上で動作することを前提とする(GPU の有無は問わない。GPU 非搭載のCPUのみの環境でも動作する)。必要なライブラリ NumPy・SciPy は Windows 向けのビルド済みパッケージが配布されており,後述の pip コマンドのみで導入できる。
掲載の実行結果は NumPy 2.x/SciPy 1.x 系での出力例である。LU分解の L・U の数値や行の入れ替え方,固有ベクトルや特異ベクトルの符号は,実装・バージョンにより異なる場合がある。ただし A = P・L・U が成り立つ関係や,連立方程式の解が一意に定まる点は変わらない。各演習のコードはご自身の環境で実行し,結果を確認しながら進めてほしい。

目次

【サイト内のPython関連主要ページ】

【外部リソース】

用語リスト

本ガイドで使用する用語を以下に定義する。

基礎概念

評価指標

本ガイドの演習を実行するには,次のコマンドで必要なライブラリをインストールする。

pip install numpy scipy
【演習の進め方】各演習は,テーマ名,手順,ヒント,考察ポイントで構成される。手順に従ってコードを記述・実行し,考察ポイントに沿って実行結果を読み取る。各演習のコードは単独で実行できるよう,先頭に必要な import 文を記述している。

演習1.行列の積

行列の積 C = A × B において,結果の各要素 C[i][j] は,A の i 行目と B の j 列目の対応する要素の積の総和として計算される。m×n 行列 A と n×p 行列 B の積は m×p 行列となる。NumPyでは @演算子または np.dot()関数を使用する(内部では BLAS(Basic Linear Algebra Subprograms:行列・ベクトル演算の標準ライブラリ)が使用される)。

手順

  1. 2×2 の行列 A と B を作成する。
  2. @演算子を用いて積 C = A @ B を計算する。
  3. A,B,C を表示する。
import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
C = A @ B  # または C = np.dot(A, B)
print("行列A:\n", A)
print("行列B:\n", B)
print("A×B:\n", C)

ヒント

考察ポイント

演習2.LU分解と計算精度の評価

LU分解は,行列 A を下三角行列(L:対角成分より上側がすべて0の行列)と上三角行列(U:対角成分より下側がすべて0の行列)の積に分解する手法である。実装では,数値安定性を高めるため,置換行列(P:行の入れ替えを表す行列)も含めて A = PLU の形で分解する。分解の各段階で対角成分(ピボット)を割り算に用いるが,対角成分に0または絶対値の小さな値が現れると数値的に不安定になるため,行を入れ替えて,対象列の対角成分以下の要素のうち絶対値が最大の要素をピボットとして選択する。これを部分ピボット選択(partial pivoting)と呼ぶ。

scipy.linalg モジュールの lu()関数により,LU分解を計算できる。lu() は分解結果の確認に用い,連立方程式の求解には,分解と求解を分けて扱う lu_factor() と lu_solve() を用いる。

手順

  1. 3×3 の行列 A を作成する。
  2. linalg.lu() で LU分解し,P,L,U を表示する。
  3. P @ L @ U を計算し,元の行列 A との誤差を測定する。
  4. lu_factor() と lu_solve() で連立方程式 Ax = b を解き,残差を測定する。
  5. matrix_rank() で行列の階数を求める。
import numpy as np
from scipy import linalg

A = np.array([[2, -1, 1],
              [4, 1, -1],
              [1, 1, 1]])

# LU分解
P, L, U = linalg.lu(A)
print("元の行列A:\n", A)
print("置換行列P:\n", P)
print("下三角行列L:\n", L)
print("上三角行列U:\n", U)

# LU分解の検証(誤差の測定)
print("\nP×L×U:\n", P @ L @ U)
error_lu = np.max(np.abs(A - P @ L @ U))
print("元の行列との誤差:", error_lu)

# LU分解を使用した連立方程式の解法(残差の測定)
b = np.array([1, 2, 3])
x = linalg.lu_solve(linalg.lu_factor(A), b)
print("\n連立方程式Ax = bの解:", x)
residual = np.linalg.norm(A @ x - b)
print("残差 ||Ax - b||:", residual)

# 行列の階数
rank_A = np.linalg.matrix_rank(A)
print("行列Aの階数:", rank_A)

ヒント

考察ポイント

演習3.逆行列と行列式

逆行列 A⁻¹ は,元の行列との積が単位行列(対角成分が1で他がすべて0の正方行列)となる行列であり,線形変換の逆変換を表す。逆行列は行列式が0でない場合(正則行列の場合)にのみ存在する。連立方程式 Ax = b を解く場合は,逆行列を計算して A⁻¹b を求めるよりも,LU分解を用いる方法が計算コストおよび数値精度の点で優れている。本演習では,逆行列および行列式の計算手順を確認する目的で np.linalg.inv() を使用する。

手順

  1. 2×2 の行列 A を作成する。
  2. np.linalg.det() で行列式を計算する。
  3. np.linalg.inv() で逆行列を計算する。
  4. A @ A_inv を計算し,単位行列との誤差を測定する。
import numpy as np

A = np.array([[4, 7], [2, 6]])

# 行列式
det_A = np.linalg.det(A)
print("行列A:\n", A)
print("行列式:", det_A)

# 逆行列
A_inv = np.linalg.inv(A)
print("\n逆行列:\n", A_inv)

# 検証: A×A^(-1) = I(誤差の測定)
print("A×A^(-1):\n", A @ A_inv)
error_inv = np.max(np.abs(A @ A_inv - np.eye(2)))
print("逆行列の精度(誤差):", error_inv)

ヒント

考察ポイント

演習4.連立一次方程式の求解

連立一次方程式 Ax = b を解く場合,逆行列を計算して A⁻¹b を求めるのではなく,np.linalg.solve()関数を用いる。solve() は内部で LU分解に基づく直接法を用いており,逆行列を経由するよりも計算コストおよび数値精度の点で優れている。演習2の lu_factor()/lu_solve() は,同じ係数行列 A に対して異なる b を繰り返し解く場合に用いる。

手順

  1. 2×2 の行列 A とベクトル b を作成する。
  2. np.linalg.solve() で連立方程式 Ax = b を解く。
  3. 残差を測定する。
import numpy as np

A = np.array([[3, 2], [1, 2]])
b = np.array([2, 0])

# 連立方程式 Ax = b を解く
x = np.linalg.solve(A, b)
print("行列A:\n", A)
print("ベクトルb:", b)
print("解 x:", x)

# 検証(残差の測定)
residual = np.linalg.norm(A @ x - b)
print("残差 ||Ax - b||:", residual)

ヒント

考察ポイント

演習5.固有値と固有ベクトル

正方行列 A に対して,Av = λv を満たすスカラー λ を固有値,0でないベクトル v を固有ベクトルと呼ぶ。固有値・固有ベクトルは,主成分分析,振動解析,グラフ解析などに応用される。一般の正方行列には np.linalg.eig()関数を用いる。行列が対称行列(A = Aᵀ)またはエルミート行列の場合は,np.linalg.eigh()関数を用いると,実数の固有値が昇順で得られ,数値的にも安定する。固有ベクトルの符号や全体の定数倍には自由度があり,環境により符号が異なる場合がある。

手順

  1. 2×2 の対称行列 A を作成する。
  2. np.linalg.eigh() で固有値と固有ベクトルを求める。
  3. A @ eigenvectors と eigenvectors * eigenvalues の誤差を測定する。
import numpy as np

# 対称行列
A = np.array([[2, 1], [1, 2]])

# 固有値・固有ベクトル(対称行列なので eigh を使用)
eigenvalues, eigenvectors = np.linalg.eigh(A)
print("行列A:\n", A)
print("固有値:", eigenvalues)
print("固有ベクトル(各列が1つの固有ベクトル):\n", eigenvectors)

# 検証: A v = λ v(誤差の測定)
error_eig = np.max(np.abs(A @ eigenvectors - eigenvectors * eigenvalues))
print("固有値分解の誤差:", error_eig)

ヒント

考察ポイント

演習6.特異値分解(SVD)

特異値分解は,行列 A を A = UΣVᵀ の形に分解する手法である。Σ の対角成分を特異値と呼び,非負の値が降順で並ぶ。特異値分解は,行列の階数の判定,低ランク近似,擬似逆行列の計算などに用いられる。演習2で用いた np.linalg.matrix_rank() も,内部では特異値を用いて階数を求めている。np.linalg.svd()関数の返り値は U,特異値の配列 s,Vᵀ の3つである。U と Vᵀ の符号には自由度があり,環境により符号が異なる場合があるが,特異値 s は一意に定まる。

手順

  1. 2×2 の行列 A を作成する。
  2. np.linalg.svd() で特異値分解する。
  3. 特異値の配列 s を対角行列 Σ に変換し,U @ Σ @ Vᵀ で再構成して誤差を測定する。
import numpy as np

A = np.array([[2, 0], [0, 3]])

# 特異値分解
U, s, Vt = np.linalg.svd(A)
print("行列A:\n", A)
print("特異値:", s)

# 検証: A = U Σ Vᵀ(誤差の測定)
Sigma = np.diag(s)
A_reconstructed = U @ Sigma @ Vt
print("再構成した行列 U Σ Vᵀ:\n", A_reconstructed)
error_svd = np.max(np.abs(A - A_reconstructed))
print("特異値分解の誤差:", error_svd)

ヒント

考察ポイント

演習7.条件数

条件数は,連立方程式 Ax = b において,入力(A や b)の小さな誤差が解 x にどれだけ増幅されて伝わるかを表す指標である。条件数が1に近いほど数値的に安定(良条件)であり,値が大きいほど誤差が増幅されやすく不安定(悪条件)である。np.linalg.cond()関数の既定では2ノルムが用いられ,このとき条件数は「最大特異値 ÷ 最小特異値」に等しい。

手順

  1. 演習6と同じ 2×2 の行列 A を作成する。
  2. np.linalg.cond() で条件数を計算する。
import numpy as np

A = np.array([[2, 0], [0, 3]])

# 条件数(既定は2ノルム)
cond_A = np.linalg.cond(A)
print("行列A:\n", A)
print("条件数:", cond_A)

ヒント

考察ポイント

演習8.畳み込み

畳み込みは,信号処理や画像処理で用いられる演算であり,特徴抽出やフィルタリングに適用される。2次元畳み込みは,入力画像 f(x, y) とカーネル(畳み込み演算で用いる小さな行列)g(x, y) に対して次の式で定義される。

$$(f * g)(x, y) = \sum_{i} \sum_{j} f(i, j) \cdot g(x - i, y - j)$$

この演算では,カーネルを入力画像上でスライドさせながら,対応する要素の積の総和を計算する。scipy.signal.convolve2d() は上記の定義に従いカーネルを180度反転して計算する(要素を反転せずに計算する「相関」とは異なる)。本演習のカーネルは点対称のため,反転の有無で結果は変わらない。

modeパラメータには以下の選択肢がある。

入力サイズを (H, W),カーネルサイズを (Kh, Kw) とすると,各 mode の出力サイズは次の通りである。

手順

  1. 4×4 の入力画像と 3×3 のカーネルを作成する。
  2. 'full','same','valid' の各モードで convolve2d() を実行する。
  3. 各モードの畳み込み結果と出力サイズを表示する。
import numpy as np
from scipy import signal

# 2D配列(画像を想定)
image = np.array([[1, 2, 3, 4],
                  [5, 6, 7, 8],
                  [9, 10, 11, 12],
                  [13, 14, 15, 16]])

# カーネル(エッジ検出フィルタ)
kernel = np.array([[-1, -1, -1],
                   [-1,  8, -1],
                   [-1, -1, -1]])

print("入力画像:\n", image)
print("\nカーネル(エッジ検出フィルタ):\n", kernel)

# 異なるモードでの畳み込み演算
modes = ['full', 'same', 'valid']
for mode in modes:
    conv_result = signal.convolve2d(image, kernel, mode=mode)
    print(f"\n畳み込み結果(mode='{mode}'):\n", conv_result)
    print(f"出力サイズ: {conv_result.shape}")

ヒント

考察ポイント