対角成分が1で非対角成分が0の行列を単位行列と言います。単位行列はNumPyにも単位行列を生成する関数が備わっています。

NumPyでは主に2つの関数であるnp.eye()np.identity()が使われており、使われる頻度としては2つの関数間にそこまで差はありません。 この記事では

  • 単位行列 と正方行列
  • eye関数とidentity関数との違い
  • それぞれの関数の使い方
  • 処理速度の比較

について解説します。

単位行列と正方行列

まずは簡単な数学的な知識について解説します。
単位行列というのは行列の対角成分が全て1になっている正方行列のことを指します。

正方行列というのは行数と列数が一致している、N \times N の行列のことを指します。
$2\times 3$の行列は正方行列とは言いませんが、2\times 2 3\times 3 の行列は正方行列と呼びます。以下の行列A B はどちらも正方行列です。

A = \left( \begin{array}{cc} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{array} \right) \\ \\ B = \left( \begin{array}{ccc} b_{11} & b_{12} & b_{13} \\ b_{21} & b_{22} & b_{23} \\ b_{31} & b_{32} & b_{33} \\ \end{array} \right)

これを踏まえると単位行列は対角成分(行列の右肩下がりの対角線上に並ぶ成分)が全て1となるN\times N の行列ということになります。
通常単位行列はI E で表されることが多いです。

I = \left( \begin{array}{cccc} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \\ \end{array} \right)

2つの関数の違い

identity関数は行数、列数が同じ正方行列しか生成できないのに対し、eye関数は行数と列数各々を指定して単位行列を生成できるところに違いがあります。 他にもeye関数では要素が1となっている対角線の位置を指定することもできます。

特別な指定の必要のない単位行列を生成する場合はnp.identity関数で十分ですが特殊なものを用いたい場合はnp.eye関数を使う必要があるでしょう。

それでは各々の関数について見ていきます。

np.eye

ここではeye関数について解説していきます。 まずはAPIドキュメントを見てみましょう。

numpy.eye(N, M=None, k=0, dtype=float, like=None)

params:

パラメータ名 概要
N int 生成される単位行列の行数を指定します。
M int (省略可能)初期値None
生成される単位行列の列数を指定します。NoneのときはNと同じ値になります。
k int (省略可能)初期値0
1を要素とする対角線が行列のどの位置にするかを指定します。kの値が正であれば上方の位置となり、負の値であれば下方の位置となります。
dtype データ型 (省略可能)初期値float
生成される配列の要素が持つデータ型を指定します。
like コレクション NumPy配列以外の参照オブジェクトで生成します。
__array_function__プロトコルを継承しているオブジェクトを指定することができます

returns:

N×Mの2次元行列で指定した対角線上の要素が全て1となっており、その他は全て0となっている行列を返します。

引数を見ると、NMで行列のサイズを指定することができ、kで要素が1となっている対角線の位置を指定することができます。また、dtypeで要素のデータ型を指定できます。like引数ではNumPy配列以外のオブジェクトを生成するときに指定します。

サンプルコード

実際に使っていきましょう。簡単な単位行列から作っていきます。

In [1]: import numpy as np

In [2]: np.eye(3) # 3×3の単位行列
Out[2]:
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])

In [3]: np.eye(10)
Out[3]:
array([[ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.]])

行数、列数をNMを使って指定します。

In [4]: np.eye(2, 3) # 2×3
Out[4]:
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.]])

In [5]: np.eye(5, 4) # 5×4
Out[5]:
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  0.]])

次に、要素1が並ぶ対角線の位置を指定します。

In [6]: np.eye(5, k=0) # k=0とすると対角成分は移動しない。
Out[6]:
array([[ 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.]])

In [7]: np.eye(5, k=1) # 上方に1つ移動
Out[7]:
array([[ 0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  0.,  0.]])

In [8]: np.eye(5, k=-1) # 下方に1つ移動
Out[8]:
array([[ 0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.]])

In [9]: np.eye(5, k=3) # 上方に3つ移動
Out[9]:
array([[ 0.,  0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

最後に、データ型を指定します。

In [10]: np.eye(5, dtype=int)
Out[10]:
array([[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]])

In [11]: np.eye(5, dtype=complex) # 複素数型で
Out[11]:
array([[ 1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
       [ 0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
       [ 0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j],
       [ 0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j],
       [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j]])

np.identity()

ここではidentity関数について解説します。
APIドキュメントは以下の通りです。

numpy.identity(n, dtype=float, …, like=None)

params:

パラメータ名 概要
n int 生成する行列のサイズを指定します。N×Nの正方行列が生成されます。
dtype データ型 (省略可能)初期値float
要素のデータ型を指定します。
like コレクション NumPy配列以外の参照オブジェクトで生成します。
__array_function__プロトコルを継承しているオブジェクトを指定することができます

returns:

N×Nの単位行列を生成します。

こちらの引数はeye関数に比べるとシンプルになっています。nで配列のサイズを指定し、dtypeで要素のデータ型を指定します。第3引数ではndarray以外の配列を生成したい場合に__array_function__プロトコルを実装しているオブジェクトを指定します。

サンプルコード

実際に使っていきます。

In [12]: np.identity(5) # まずは単純なものから
Out[12]:
array([[ 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.]])

In [13]: np.identity(2)
Out[13]:
array([[ 1.,  0.],
       [ 0.,  1.]])

dtypeでデータ型を指定します。

In [14]: np.identity(3, dtype=int)
Out[14]:
array([[1, 0, 0],
       [0, 1, 0],
       [0, 0, 1]])

In [15]: np.identity(4, dtype="float32")
Out[15]:
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.]], dtype=float32)

速さの比較

10000×10000の単位行列を生成する速度を比較してみます。

In [17]: %timeit np.eye(10000)
27.3 ms ± 603 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [18]: %timeit np.identity(10000)
27.1 ms ± 822 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

2つの関数は共にほぼ同じ処理速度で実行していますね。

速さの観点からは、どちらを使っても差異はなさそうです。