NumPyで単位行列を生成:eye関数の基本と使い方


NumPyで単位行列を生成:eye関数の基本と使い方

はじめに:行列演算の「1」である単位行列

科学技術計算、データサイエンス、機械学習など、現代の多くの分野で「行列」は不可欠な数学的ツールです。連立一次方程式の解法、データの変換、グラフ構造の表現、量子力学の計算など、様々な場面で行列演算が登場します。NumPy(Numerical Python)は、Pythonにおいて高速かつ効率的に多次元配列や行列演算を行うためのデファクトスタンダードライブラリであり、これらの計算を強力にサポートします。

行列演算の世界には、「掛け算しても値を変えない特別な行列」が存在します。それが「単位行列」です。ちょうど、数の掛け算における「1」のような役割を果たします。例えば、どんな数に1を掛けてもその数は変わりません(例: 5 × 1 = 5)。同様に、ある行列に適切なサイズの単位行列を掛けても、元の行列は変わりません。

単位行列は、線形代数の理論において非常に重要な役割を果たします。例えば、行列の「逆行列」を定義する際に単位行列が登場しますし、固有値問題や行列の対角化においても単位行列は中心的な概念となります。

NumPyでは、この重要な単位行列を簡単に生成するための専用の関数が用意されています。それが今回詳しく解説する numpy.eye() 関数です。本記事では、単位行列の数学的な定義から、NumPyのeye関数の基本的な使い方、様々なオプション引数による柔軟な生成方法、さらには具体的な応用例までを詳細に解説します。約5000語にわたる徹底的な解説を通じて、eye関数だけでなく、単位行列そのものに対する理解も深めていただけることを目指します。

単位行列の基礎知識

NumPyのeye関数について掘り下げる前に、まずは単位行列そのものについて理解しておきましょう。

単位行列の定義

単位行列(Identity Matrix)とは、以下の条件を満たす正方行列のことです。

  1. 正方行列であること: 行列の行数と列数が等しい。
  2. 対角要素が全て1であること: 行列の左上から右下にかけての対角線上の要素が全て1である。
  3. 非対角要素が全て0であること: 対角線上にない要素は全て0である。

n行n列の単位行列は通常 I_n または単に I と表記されます。具体例をいくつか見てみましょう。

2次(2×2)の単位行列 I_2:
[ 1 0 ]
[ 0 1 ]

3次(3×3)の単位行列 I_3:
[ 1 0 0 ]
[ 0 1 0 ]
[ 0 0 1 ]

4次(4×4)の単位行列 I_4:
[ 1 0 0 0 ]
[ 0 1 0 0 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]

一般に、n次単位行列 I_n の(i, j)成分(i行目、j列目の要素)は、以下のクロネッカーのデルタ記号 δ_ij を使って表されます。

δ_ij = 1 (if i = j)
δ_ij = 0 (if i ≠ j)

したがって、I_n の(i, j)成分は δ_ij です。

単位行列の重要な性質

単位行列がなぜ重要なのでしょうか?それは、行列演算におけるその特別な性質にあります。

  1. 乗法における単位元:
    任意の m行n列の行列 A に対して、以下の関係が成り立ちます。

    • I_m A = A
    • A I_n = A
      つまり、行列 A に左から m 次単位行列 I_m を掛けても、または右から n 次単位行列 I_n を掛けても、結果は元の行列 A になります。これが「単位元」としての性質です。数の計算における a × 1 = a1 × a = a に対応します。
  2. 逆行列との関係:
    正方行列 A に対して、もし逆行列 A⁻¹ が存在するならば、以下の関係が成り立ちます。

    • A A⁻¹ = I_n
    • A⁻¹ A = I_n
      ここで I_nA と同じ次数の単位行列です。逆行列は、「掛けると単位行列になる」ような行列として定義されるため、単位行列は逆行列の概念に不可欠です。
  3. 基底ベクトル:
    単位行列の各列ベクトルは、標準的な基底ベクトル(座標軸方向の単位ベクトル)です。例えば、3次単位行列 I_3 の列ベクトルは [1, 0, 0]ᵀ[0, 1, 0]ᵀ[0, 0, 1]ᵀ です(ᵀは転置を表す)。これらのベクトルは、3次元空間の座標軸に沿った単位ベクトルであり、任意のベクトルをこれらの線形結合で一意に表すことができます。

  4. 線形変換:
    単位行列に対応する線形変換は、恒等変換(何もしない変換)です。つまり、ベクトルに行列 I を掛けても、そのベクトルの向きや大きさは変わりません。

これらの性質から、単位行列は線形代数の理論構築において、そして実際に様々な問題を解く上で行列の「基準」や「出発点」として重要な役割を果たします。

NumPyの eye() 関数:単位行列を生成する

いよいよ、NumPyを使って単位行列を生成する方法を見ていきましょう。NumPyには、numpy.eye() という関数が用意されています。これは、指定した形状と特性を持つ行列を生成し、その対角要素(または指定したオフセットの対角線上の要素)を1に、その他の要素を0に設定します。

numpy.eye() 関数の基本的な使い方

numpy.eye() 関数の最も基本的な使い方は、生成したい正方単位行列のサイズ(行数、列数)を指定することです。これは必須引数 N で行います。

“`python
import numpy as np

3×3の単位行列を生成

identity_matrix_3x3 = np.eye(3)
print(identity_matrix_3x3)
“`

出力:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]

この例では、np.eye(3) と指定することで、3行3列の単位行列が生成されました。デフォルトでは要素のデータ型は浮動小数点数(float64)になります。

同様に、5×5の単位行列を生成するには np.eye(5) とします。

“`python

5×5の単位行列を生成

identity_matrix_5x5 = np.eye(5)
print(identity_matrix_5x5)
“`

出力:
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]

このように、eye() 関数の必須引数 N は、生成される行列の行数を指定します。正方行列を生成する場合、列数も N と同じになります。

eye() 関数のシグネチャと引数

numpy.eye() 関数の完全なシグネチャは以下のようになります。

python
numpy.eye(N, M=None, k=0, dtype=<class 'float'>, order='C', *, like=None)

各引数について詳しく見ていきましょう。

  • N: 必須。生成する配列の行数を表す整数。
  • M: オプション。生成する配列の列数を表す整数。指定しない場合 (None がデフォルト)、MN と同じになり、正方行列が生成されます。
  • k: オプション。対角線を指定するオフセットを表す整数。デフォルトは 0 で、これは主対角線(左上から右下への対角線)を意味します。
    • k > 0 の場合: 主対角線より上の対角線(右上方向)。k=1 は主対角線の1つ上の対角線、k=2 は2つ上の対角線…
    • k < 0 の場合: 主対角線より下の対角線(左下方向)。k=-1 は主対角線の1つ下の対角線、k=-2 は2つ下の対角線…
  • dtype: オプション。生成される配列の要素のデータ型を指定します。デフォルトは float です。int, complex など、NumPyがサポートする任意のデータ型を指定できます。
  • order: オプション。配列のメモリー内での配置順序を指定します。'C' (C-contiguous order) または 'F' (Fortran-contiguous order) を指定できます。デフォルトは 'C' です。通常の使用ではあまり気にする必要はありませんが、特定のアルゴリズムでパフォーマンスに影響する場合があります。
  • like: オプション (NumPy 1.20以降)。他の配列 a を指定すると、その配列の属性 (dtype, shapeなど) を引き継いで配列を生成します。主に内部的に使われるか、特定の高度な場面で使用されます。ここでは詳細な説明は割愛します。

これらのオプション引数を使うことで、単位行列だけでなく、より一般的な形状や、1が対角線以外の位置にある行列を柔軟に生成できます。

オプション引数の詳細な使い方と例

eye() 関数の柔軟性は、Mkdtype といったオプション引数によって実現されます。それぞれの使い方を詳しく見ていきましょう。

M 引数:非正方行列の生成

デフォルトでは M=None となり M=N と見なされて正方行列が生成されますが、MN とは異なる整数を指定することで、行数と列数が異なる非正方行列を生成できます。この場合も、指定された対角線(デフォルトは主対角線 k=0)上の要素が1になり、それ以外の要素は0になります。

例1:行数 > 列数 (背の高い行列)

“`python

4行3列の非正方行列を生成

主対角線は左上から右下へ延びるが、列数が少ないため途中で終わる

matrix_4x3 = np.eye(4, M=3)
print(matrix_4x3)
“`

出力:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 0. 0.]]

4×3の行列が生成されました。主対角線((0,0), (1,1), (2,2))に1が配置されています。最後の行には1を配置する位置(列3)がないため、全て0になっています。

例2:行数 < 列数 (幅の広い行列)

“`python

3行5列の非正方行列を生成

主対角線は左上から右下へ延びるが、行数が少ないため途中で終わる

matrix_3x5 = np.eye(3, M=5)
print(matrix_3x5)
“`

出力:
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]]

3×5の行列が生成されました。主対角線((0,0), (1,1), (2,2))に1が配置されています。列数が多いため、対角線以降の要素は全て0になっています。

このように、M を使うことで、厳密な意味での単位行列(正方で対角要素が1)ではありませんが、「対角成分に1が並び、その他が0」という構造を持つ非正方行列を生成できます。

k 引数:対角オフセットの指定

k 引数は、1を配置する対角線の位置を制御します。デフォルトの k=0 は主対角線ですが、正または負の整数を指定することで、他の対角線に1を配置できます。

例3:k > 0 (主対角線より上の対角線)

k=1 を指定すると、主対角線の1つ上の対角線((0,1), (1,2), (2,3), …)に1が配置されます。

“`python

4×4行列でk=1の対角線に1を配置

matrix_k1 = np.eye(4, k=1)
print(matrix_k1)
“`

出力:
[[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]]

確かに、主対角線のすぐ上の要素が1になっています。

k=2 を指定すると、主対角線の2つ上の対角線((0,2), (1,3), (2,4), …)に1が配置されます。

“`python

4×4行列でk=2の対角線に1を配置

matrix_k2 = np.eye(4, k=2)
print(matrix_k2)
“`

出力:
[[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]

例4:k < 0 (主対角線より下の対角線)

k=-1 を指定すると、主対角線の1つ下の対角線((1,0), (2,1), (3,2), …)に1が配置されます。

“`python

4×4行列でk=-1の対角線に1を配置

matrix_k_neg1 = np.eye(4, k=-1)
print(matrix_k_neg1)
“`

出力:
[[0. 0. 0. 0.]
[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]]

主対角線のすぐ下の要素が1になっています。

k=-2 を指定すると、主対角線の2つ下の対角線((2,0), (3,1), …)に1が配置されます。

“`python

4×4行列でk=-2の対角線に1を配置

matrix_k_neg2 = np.eye(4, k=-2)
print(matrix_k_neg2)
“`

出力:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[1. 0. 0. 0.]
[0. 1. 0. 0.]]

例5:Mk の組み合わせ

Mk は組み合わせて使うこともできます。これにより、非正方行列の主対角線以外の対角線に1を配置できます。

“`python

3行5列の行列でk=1の対角線に1を配置

matrix_3x5_k1 = np.eye(3, M=5, k=1)
print(matrix_3x5_k1)
“`

出力:
[[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]]

3行5列の行列で、主対角線から1つ上の対角線((0,1), (1,2), (2,3))に1が配置されています。

“`python

5行3列の行列でk=-1の対角線に1を配置

matrix_5x3_k_neg1 = np.eye(5, M=3, k=-1)
print(matrix_5x3_k_neg1)
“`

出力:
[[0. 0. 0.]
[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 0. 0.]]

5行3列の行列で、主対角線から1つ下の対角線((1,0), (2,1), (3,2))に1が配置されています。

k の値によっては、指定された対角線が生成される行列の範囲外になる場合もあります。その場合、全ての要素が0の行列が生成されます。

“`python

3×3行列でk=3を指定 -> 対角線が範囲外

matrix_k3_out_of_bounds = np.eye(3, k=3)
print(matrix_k3_out_of_bounds)
“`

出力:
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]

dtype 引数:要素のデータ型

デフォルトでは np.eye()float64 型の配列を生成します。これは、多くの場合で数値計算には浮動小数点数が適しているためです。しかし、必要に応じて整数型 (int) や複素数型 (complex) など、他のデータ型を指定することも可能です。

例6:整数型の単位行列

“`python

3×3の整数型単位行列を生成

identity_matrix_int = np.eye(3, dtype=int)
print(identity_matrix_int)
print(identity_matrix_int.dtype)
“`

出力:
[[1 0 0]
[0 1 0]
[0 0 1]]
int64 # またはシステムに応じたデフォルトの整数型

要素が整数 1 および 0 で構成される単位行列が生成されました。整数演算を行いたい場合などに便利です。

例7:複素数型の単位行列

“`python

3×3の複素数型単位行列を生成

identity_matrix_complex = np.eye(3, dtype=complex)
print(identity_matrix_complex)
print(identity_matrix_complex.dtype)
“`

出力:
[[1.+0.j 0.+0.j 0.+0.j]
[0.+0.j 1.+0.j 0.+0.j]
[0.+0.j 0.+0.j 1.+0.j]]
complex128 # またはシステムに応じたデフォルトの複素数型

複素数 1+0j および 0+0j で構成される単位行列が生成されました。量子力学や電気工学など、複素数を用いる計算で役立ちます。

データ型を指定することで、生成される配列のメモリ使用量や、その後の計算の精度、演算速度に影響を与えることがあります。目的に合わせて適切なデータ型を選択することが重要です。

order 引数:メモリー配置順序

order 引数は、生成される配列のメモリー内での要素の並び方を指定します。NumPy配列は多次元ですが、内部的には1次元の連続したメモリーブロックとして格納されています。このブロック内での要素の並び方には、主に2種類あります。

  • 'C' (C-contiguous): 行優先。C言語の多次元配列のように、行の要素が連続してメモリーに配置されます。デフォルトはこちらです。
  • 'F' (Fortran-contiguous): 列優先。Fortranの多次元配列のように、列の要素が連続してメモリーに配置されます。

どちらの順序で格納されているかは、NumPy配列の .flags 属性で確認できます。

“`python

デフォルト (‘C’ order)

matrix_c_order = np.eye(3, dtype=int)
print(matrix_c_order)
print(matrix_c_order.flags[‘C_CONTIGUOUS’])
print(matrix_c_order.flags[‘F_CONTIGUOUS’])

print(“-” * 20)

‘F’ order

matrix_f_order = np.eye(3, dtype=int, order=’F’)
print(matrix_f_order)
print(matrix_f_order.flags[‘C_CONTIGUOUS’])
print(matrix_f_order.flags[‘F_CONTIGUOUS’])
“`

出力:
“`
[[1 0 0]
[0 1 0]
[0 0 1]]
True
False


[[1 0 0]
[0 1 0]
[0 0 1]]
False # 小さい配列では両方Trueになることもあります (NumPyの実装詳細)
True
“`
表示される配列の内容は同じですが、内部的なメモリーの並びが異なります。

メモリー配置順序は、特定の操作(例:転置、特定のアルゴリズム)のパフォーマンスに影響を与えることがあります。一般的には、要素にアクセスする順序とメモリー配置順序が一致している方が、キャッシュ効率が良くなり高速になります。PythonやC/C++で標準的な行優先の処理を行う場合は 'C'、Fortranで行優先(論理的には列優先)の処理や、一部の科学技術計算ライブラリがFortran順序を好む場合には 'F' を指定することがあります。しかし、多くの場合はデフォルトの 'C' で問題ありません。

eye() 関数と関連するNumPy関数

NumPyには、配列や行列を生成するための様々な関数があります。eye() 関数と似ている、あるいは関連性の深い関数をいくつか見てみましょう。

numpy.identity()

numpy.identity(n, dtype=None, *, like=None) 関数は、厳密に「n次正方単位行列」を生成するための関数です。

identity(n)eye(N=n, M=n, k=0) と完全に等価です。

“`python

identity()で3×3単位行列を生成

identity_matrix_ident = np.identity(3)
print(identity_matrix_ident)

print(“-” * 20)

eye()で3×3単位行列を生成

identity_matrix_eye = np.eye(3)
print(identity_matrix_eye)

両者は等価

print(np.array_equal(identity_matrix_ident, identity_matrix_eye))
“`

出力:
“`
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]


[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
True
``identity()eye()の機能の一部(正方単位行列、主対角線のみ)に特化した関数と言えます。もし正方単位行列だけが必要なのであればidentity()は意図が明確で良いかもしれません。しかし、eye()Mkといった引数でより柔軟な行列生成が可能であり、機能的にはidentity()を包含しています。どちらを使うかは好みや文脈によりますが、eye()` を覚えておけばより幅広い状況に対応できます。

numpy.diag()

numpy.diag(v, k=0) 関数は、主に以下の2つの用途で使われます。

  1. 1次元配列 v を受け取り、その要素を対角成分とする正方対角行列を生成する。
  2. 行列 a を受け取り、その対角成分(または指定したオフセット k の対角線上の要素)を抽出して1次元配列として返す。

eye() 関数が「対角線に1が並ぶ行列を生成する」機能と関連が深いです。

例:diag() による対角行列の生成

“`python

対角成分を指定して対角行列を生成

diagonal_elements = np.array([1, 2, 3])
diagonal_matrix = np.diag(diagonal_elements)
print(diagonal_matrix)
“`

出力:
[[1 0 0]
[0 2 0]
[0 0 3]]

diag() を使って単位行列を生成することも可能ですが、そのためには要素が全て1の1次元配列を用意する必要があります。

“`python

diag()で単位行列を生成する別の方法

ones_vector = np.ones(3)
identity_matrix_diag = np.diag(ones_vector)
print(identity_matrix_diag)
“`

出力:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]

これは np.eye(3) と同じ結果ですが、np.eye(3) の方が直接的で意図が明確です。diag() は対角成分が1以外の場合や、既存の行列から対角成分を抽出する場合に主に使われます。

diag() 関数も k 引数を持ち、主対角線以外の要素を抽出したり、指定したオフセットの対角線上に要素を配置する行列を生成したりできます。

“`python

diag()でk=1の対角線に要素を配置

matrix_diag_k1 = np.diag([10, 20, 30], k=1)
print(matrix_diag_k1)
“`

出力:
[[ 0 10 0 0]
[ 0 0 20 0]
[ 0 0 0 30]
[ 0 0 0 0]]

eye(4, k=1) が1を配置するのに対し、diag([10, 20, 30], k=1) は指定された配列の要素を配置します。このように、eye() は特定の対角線に「1」を配置することに特化しているのに対し、diag() は任意の値を対角線に配置したり、対角線を抽出したりする、より一般的な対角成分操作に使われます。

numpy.zeros()numpy.ones()

これらの関数は、それぞれ全ての要素が0、または全ての要素が1の配列を生成します。

  • numpy.zeros(shape, dtype=float, order='C')
  • numpy.ones(shape, dtype=float, order='C')

eye() 関数は、zeros() で0で埋めた配列を作成し、特定の対角線に1を配置する操作と見ることができます。

“`python

zeros()とones()を使った単位行列の「手動」生成 (eye()の方が良いですが概念理解のために)

n = 3
manual_identity = np.zeros((n, n), dtype=int)

主対角線に1を設定

for i in range(n):
manual_identity[i, i] = 1
print(manual_identity)
“`

出力:
[[1 0 0]
[0 1 0]
[0 0 1]]

このように、eye() はこの「0で埋めて、対角に1をセットする」というよくある操作を効率的に行うための便利な関数です。

eye() 関数の応用例

単位行列や eye() 関数を使って生成される特殊な行列は、様々な計算やアルゴリズムで利用されます。いくつかの応用例を見てみましょう。

1. 線形方程式の解法

連立一次方程式 Ax = b を解く際、理論的には係数行列 A の逆行列 A⁻¹ を求めて x = A⁻¹ b と計算できます。逆行列の定義には単位行列 I が不可欠です (A A⁻¹ = I)。

NumPyには np.linalg.solve(A, b) という、より数値的に安定した方法で線形方程式を解く関数がありますが、逆行列の概念を理解したり、逆行列が存在するか(det(A) ≠ 0 か)を考えたりする上で、単位行列は常に背後にある存在です。

また、行列 A の逆行列が正しく計算できているかを確認する際にも単位行列が使われます。

“`python

例:行列とその逆行列

A = np.array([[2, 1], [1, 1]])
A_inv = np.linalg.inv(A)
print(“Matrix A:\n”, A)
print(“Inverse of A:\n”, A_inv)

A * A_inv を計算し、単位行列になるか確認

product = np.dot(A, A_inv)
print(“A * A_inv:\n”, product)

期待される単位行列と比較 (浮動小数点誤差を考慮)

identity_expected = np.eye(2)
print(“Expected Identity:\n”, identity_expected)

nearly equal check

print(“Are they nearly equal?”, np.allclose(product, identity_expected))
“`

出力:
Matrix A:
[[2 1]
[1 1]]
Inverse of A:
[[ 1. -1.]
[-1. 2.]]
A * A_inv:
[[1.00000000e+00 0.00000000e+00]
[2.22044605e-16 1.00000000e+00]] # 微小な浮動小数点誤差が含まれることがある
Expected Identity:
[[1. 0.]
[0. 1.]]
Are they nearly equal? True

計算結果の product が、浮動小数点誤差を除いて np.eye(2) で生成した単位行列とほぼ一致していることから、逆行列が正しく計算できていることが確認できます。

2. 固有値問題

固有値問題 Ax = λx は、行列 A がベクトル x に作用したときに、ベクトルの方向を変えずにスケールだけを変更するような特別なベクトル(固有ベクトル x)とそのスケーリング係数(固有値 λ)を求める問題です。この方程式は (A - λI)x = 0 と書き換えられます。ここで IA と同じサイズの単位行列です。

A - λI という行列が登場することから、単位行列は固有値問題を定式化する上で不可欠です。

“`python

例:固有値・固有ベクトルの計算

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

固有値と固有ベクトルを計算

eigenvalues, eigenvectors = np.linalg.eig(A)

print(“Eigenvalues:”, eigenvalues)
print(“Eigenvectors:\n”, eigenvectors) # 各列が対応する固有ベクトル

検証: A * v = lambda * v ?

最初の固有値と固有ベクトルについて検証

lambda1 = eigenvalues[0]
v1 = eigenvectors[:, 0]

A * v1

Av1 = np.dot(A, v1)

lambda1 * v1

lambdav1 = lambda1 * v1

print(“\nA * v1:”, Av1)
print(“lambda1 * v1:”, lambdav1)

単位行列を使って (A – lambda*I) * v = 0 を検証

I = np.eye(A.shape[0])

matrix_minus_lambda_I = A – lambda1 * I

print(“\n(A – lambda1*I):\n”, matrix_minus_lambda_I)

print(“(A – lambda1*I) * v1:”, np.dot(matrix_minus_lambda_I, v1)) # ほぼゼロベクトルになるはず

浮動小数点誤差のため np.allclose を使用

print(“Is (A – lambda1*I) * v1 nearly zero?”, np.allclose(np.dot(A – lambda1 * np.eye(A.shape[0]), v1), np.zeros_like(v1)))

出力 (固有値・固有ベクトルは計算環境で若干異なることがあります):
Eigenvalues: [3. 2.]
Eigenvectors:
[[0.89442719 0.70710678]
[0.4472136 0.70710678]]

A * v1: [2.68328157 1.34164079]
lambda1 * v1: [2.68328157 1.34164079]
Is (A – lambda1*I) * v1 nearly zero? True
``
固有値・固有ベクトルの計算においても、単位行列は理論的な背景として、また計算の実装 (
A – λI` の形成など) において重要な役割を果たします。

3. 特定の構造を持つ行列の生成

eye() 関数の k 引数を使うと、主対角線以外に1を持つ行列を簡単に生成できます。これらは、特定の種類の行列(例:シフト行列、差分行列の構成要素など)を生成する際に役立ちます。

例:シフト行列

要素をずらす効果を持つ「シフト行列」は、eye() 関数と k 引数で簡単に生成できます。

  • 上シフト行列: 行列 A に左から上シフト行列 S_U を掛けると、A の各行が1つ上にシフトし、最後の行が0になります。S_U は主対角線より1つ上の対角線に1が並ぶ行列です(k=1)。

“`python

4×4の上シフト行列

shift_up_matrix = np.eye(4, k=1, dtype=int)
print(“Shift Up Matrix:\n”, shift_up_matrix)

例:行列に作用させてみる

A = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]])
shifted_A = np.dot(shift_up_matrix, A)
print(“\nA after shifting up:\n”, shifted_A)
“`

出力:
“`
Shift Up Matrix:
[[0 1 0 0]
[0 0 1 0]
[0 0 0 1]
[0 0 0 0]]

A after shifting up:
[[ 5 6 7 8] # 元の2行目
[ 9 10 11 12] # 元の3行目
[13 14 15 16] # 元の4行目
[ 0 0 0 0]] # 0行目になった
“`
確かに、元の行列の各行が1つ上に移動し、最後の行がゼロになっています。

  • 下シフト行列: 行列 A に左から下シフト行列 S_D を掛けると、A の各行が1つ下にシフトし、最初の行が0になります。S_D は主対角線より1つ下の対角線に1が並ぶ行列です(k=-1)。

“`python

4×4の下シフト行列

shift_down_matrix = np.eye(4, k=-1, dtype=int)
print(“Shift Down Matrix:\n”, shift_down_matrix)

例:行列に作用させてみる

shifted_A_down = np.dot(shift_down_matrix, A)
print(“\nA after shifting down:\n”, shifted_A_down)
“`

出力:
“`
Shift Down Matrix:
[[0 0 0 0]
[1 0 0 0]
[0 1 0 0]
[0 0 1 0]]

A after shifting down:
[[ 0 0 0 0] # 0行目になった
[ 1 2 3 4] # 元の1行目
[ 5 6 7 8] # 元の2行目
[ 9 10 11 12]] # 元の3行目
“`
同様に、元の行列の各行が1つ下に移動し、最初の行がゼロになっています。

これらのシフト行列は、信号処理や時系列データの操作などで行列形式で表現されるアルゴリズムにおいて利用されることがあります。

4. マスク行列としての利用

単位行列や eye() で生成した行列は、特定の要素を選択したり抽出したりするための「マスク」として使用できます。

“`python

5×5行列

matrix_A = np.arange(25).reshape(5, 5)
print(“Original Matrix A:\n”, matrix_A)

主対角成分だけを取り出す (要素ごとの乗算)

identity_mask = np.eye(5, dtype=int)
diagonal_elements_matrix = matrix_A * identity_mask # 要素ごとの乗算 ( Hadamard product )
print(“\nDiagonal elements (as a matrix):\n”, diagonal_elements_matrix)

k=1の対角成分だけを取り出す

k1_mask = np.eye(5, k=1, dtype=int)
k1_elements_matrix = matrix_A * k1_mask
print(“\nk=1 diagonal elements (as a matrix):\n”, k1_elements_matrix)
“`

出力:
“`
Original Matrix A:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]

Diagonal elements (as a matrix):
[[ 0 0 0 0 0]
[ 0 6 0 0 0]
[ 0 0 12 0 0]
[ 0 0 0 18 0]
[ 0 0 0 0 24]]

k=1 diagonal elements (as a matrix):
[[ 0 1 0 0 0]
[ 0 0 7 0 0]
[ 0 0 0 13 0]
[ 0 0 0 0 19]
[ 0 0 0 0 0]]
``
要素ごとの乗算 (
*` 演算子) を使うことで、マスク行列の1に対応する位置の要素だけを残し、0に対応する位置の要素をゼロにすることができます。これは、特定のパターンを持つ要素だけを抽出したい場合に便利な手法です。

5. 差分行列の構成要素

数値解析や偏微分方程式の離散化において、差分を計算するための行列(差分作用素行列)が使われることがあります。これらの行列は、単位行列やシフト行列の組み合わせとして表現されることが多いです。

例えば、1階差分(前向き差分)を計算する行列は、単位行列と下シフト行列の差として構成できます(厳密にはスケールファクターや境界条件に依存しますが、基本的な構造として)。

“`python

1階差分行列 (簡略版, 境界条件なし)

n = 5
I = np.eye(n, dtype=int)
S_D = np.eye(n, k=-1, dtype=int)
D1 = I – S_D # D1[i, i] = 1, D1[i, i-1] = -1
print(“Simple First Difference Matrix:\n”, D1)

例:ベクトルに作用させてみる

v = np.array([10, 12, 15, 11, 18]) # 時系列データなどに見立てる

Dv = [v[0]-0, v[1]-v[0], v[2]-v[1], v[3]-v[2], v[4]-v[3]] となることを期待

difference_vector = np.dot(D1, v)
print(“\nDifference vector:\n”, difference_vector)

手計算で確認

[10-0, 12-10, 15-12, 11-15, 18-11] = [10, 2, 3, -4, 7]

“`

出力:
“`
Simple First Difference Matrix:
[[ 1 0 0 0 0]
[-1 1 0 0 0]
[ 0 -1 1 0 0]
[ 0 0 -1 1 0]
[ 0 0 0 -1 1]]

Difference vector:
[10 2 3 -4 7]
``
期待通りの差分が計算されました。このように、
eye()` 関数はより複雑な構造を持つ行列を構成する際の基本的な「ビルディングブロック」として機能します。

パフォーマンスに関する考慮事項

numpy.eye() 関数はNumPyのコアな関数として高度に最適化されており、効率的に単位行列やそのバリエーションを生成します。

  • 生成速度: 単位行列の生成は、全要素を0で初期化し、指定された対角線上の要素だけを1に設定するという比較的単純な操作です。NumPyはこの操作をC言語で実装しており、非常に高速に行われます。大きなサイズの単位行列でも、Pythonのループなどで手動で作成するよりもはるかに高速です。
  • メモリ使用量: 生成される行列のサイズ (N * M) に比例したメモリを使用します。これは他のNumPy配列生成関数(zeros, ones など)と同様です。非常に大きな行列(例: 10000×10000の単位行列は1億要素)を生成する場合、必要なメモリ量(float64型なら約800MB)に注意が必要です。システムメモリを超えるような巨大な行列は生成できません。ただし、単位行列のように非ゼロ要素が限られている疎行列の場合、NumPy自体は疎行列形式(例: scipy.sparse.eye)を直接は提供しません。もしメモリ効率が非常に重要で、かつ行列が非常に疎である場合は、SciPyライブラリの疎行列機能を使うことを検討してください。しかし、np.eye で生成される配列はNumPyの標準的な密行列形式であり、一般的な用途には十分高速かつ効率的です。

トラブルシューティングとよくある間違い

eye() 関数を使う上で遭遇しやすい問題や間違いをいくつか紹介します。

  1. 引数の解釈間違い: 特に k 引数の意味(オフセット)や、NM の違い(行数と列数)を混同しないように注意が必要です。k=0 が主対角線、正の値が上、負の値が下、というルールを覚えておきましょう。N は常に生成される配列の行数です。M を省略した場合のみ M=N となります。
  2. データ型の指定忘れ: デフォルトは float64 です。整数演算を行いたいのに dtype=int を指定し忘れると、予期せず浮動小数点数で計算が進んでしまうことがあります。逆に行列に整数値しか含まれないと分かっていても、意図的に浮動小数点型で生成したい場合(例えば、その後の計算で割り算や非整数の値が登場する場合)は、デフォルトのままで問題ありません。
  3. 大きすぎる k の指定: 指定した k の対角線が、生成される N x M の行列の範囲に全く含まれない場合、eye() はエラーではなく、全ての要素が0の行列を返します。これは仕様ですが、意図しない場合は注意が必要です。例えば np.eye(3, 3, k=3)np.eye(3, 3, k=-3) はゼロ行列になります。
  4. 非正方行列を「単位行列」と呼ぶこと: np.eye(N, M)N != M の場合に生成される行列は、厳密には数学的な意味での「単位行列」ではありません(単位行列は正方行列だからです)。これらは「特定の対角線上に1が並ぶ非正方行列」として区別するのが適切です。文脈によっては非正方でも「一般化された単位行列のようなもの」として扱うこともありますが、数学的な議論では正方であるかどうかが重要になります。

これらの点に注意することで、eye() 関数をより正確かつ効果的に使用できます。

まとめ

本記事では、NumPyの numpy.eye() 関数について、その背景にある単位行列の数学的な概念から、NumPyでの基本的な使い方、そして様々なオプション引数(M, k, dtype, order)による柔軟な生成方法、さらには具体的な応用例までを網羅的に解説しました。

  • 単位行列は、行列演算における「1」のような役割を果たす正方行列であり、線形代数の理論や計算において極めて重要です。
  • NumPyの **np.eye(N, M=None, k=0, dtype=float, ...)** 関数は、この単位行列や、対角線上に1が並ぶ非正方行列を簡単に生成するための主要なツールです。
  • 必須引数 N で行数を指定し、オプション引数 M で列数を、k で1を配置する対角線のオフセットを、dtype で要素のデータ型を制御できます。
  • eye() 関数は、線形方程式の解法、固有値問題、特定の構造を持つ行列の生成(シフト行列など)、行列の特定要素のマスクといった多様な応用があります。
  • np.identity()np.eye() の特殊なケース(正方・主対角線)であり、np.diag() は対角成分の抽出や任意の対角行列の生成に用いられる関連関数です。
  • eye() 関数はNumPy内部で効率的に実装されており、大きな行列でも高速に生成できますが、非常に巨大な疎行列の場合はSciPyなどの利用も検討できます。

eye() 関数はNumPyを使った行列演算や線形代数計算の基礎となる関数の一つです。その使い方をマスターすることで、より効率的かつ正確な数値計算が可能になります。

線形代数は、データ分析、機械学習、物理学、工学など、多くの分野で数理的なモデリングや問題解決の基盤となります。単位行列はその中心的な要素であり、np.eye() 関数はその概念をコンピュータ上で扱うための強力な手段を提供します。ぜひ、NumPyを使った計算の中でeye() 関数を積極的に活用してみてください。さらに深く学びたい場合は、NumPyの公式ドキュメントや、線形代数に関する書籍、オンラインリソースなどを参照することをお勧めします。


コメントする

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

上部へスクロール