numpy.emptyで未初期化状態の配列を生成する(numpy.empty_like)

numpy.emptyで値が未初期化状態の配列を生成する(empty_like)

NumPyでは配列(ndarray)を生成するために、さまざまな関数が用意されています。その中で、値が初期化されていない状態の配列を生成するための関数が、numpy.empty です。値の初期化を伴う配列の生成(numpy.zerosなど)と比べ、高速に動作します。

numpy.empty では、配列の領域のみをメモリ上に確保し、値は初期化しません。値の初期化が行われないため、各要素の値は不明(めちゃくちゃな値)です。何が入っているかはわかりません。したがって生成後、手動で値の初期化を行ってから使用しなければなりません。

要素数を指定して未初期化状態の配列を生成する numpy.empty と、ある配列とまったく同様の性質をもった未初期化状態の配列を生成する `numpyt.empty_like について紹介します。

また numpy.zerosnumpy.ones と比較してどの程度高速に動作するのか、パフォーマンスを比較してみます。

numpy.empty関数の使い方

基本的には引数でshape(形状)を指定するだけです。多次元配列を生成する場合、シーケンス(リストやタプル)で指定する点に注意しましょう。

以下の例では、”要素数5の配列” と “2×3の2次元配列” を生成しています。各要素の値は未初期化状態なので、めちゃくちゃな値になっています。もちろんこれは実行時の環境によってどのような値になるかはわかりません。

import numpy as np

a = np.empty(5) # 要素数5の配列
print(a)
# [  2.67276450e+185   1.69506143e+190   1.75184147e+190   9.48819320e+077   1.63730399e-306]

b = np.empty((2, 3)) # 2x3の2次元配列をシーケンス(リストやタプル)で指定
print(b)
# [[  7.79018398e-075   2.23185784e-074   1.41890730e-075]
#  [  9.39249846e-076   3.97945114e-315   0.00000000e+000]]

numpy.empty関数のパラメータ

以下のパラメータ指定方法は、numpy.zerosnumpy.ones と同様です。

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

パラメータ 説明
shape int, intのシーケンス 生成される配列の形状を指定します。多次元配列の場合はシーケンスで指定します。例: 2×3の配列なら [2, 3] もしくは (2, 3)
dtype dtype 省略可能。出力配列の型を指定します。※省略した場合は float64 になるみたいです。
order string 省略可能。データをメモリ上にどのように保持するかを、’C’ もしくは ‘F’ で指定します。C言語とFortanのいずれかです。

return値として、指定した shape, dtype の ndarray(値は未初期化状態) を返します。

パラメータの指定で注意すべきは、shapeの指定をシーケンスで行うという点です。以下にいくつかの未初期化状態の配列(ndarray)を作ってみます。

import numpy as np

# 10x10、int64、Fortan
a = np.ones((10, 10), dtype="int64", order='F')

# 3x4x5の3次元配列(shapeはリストやタプルで指定する)
b = np.ones((3, 4, 5), dtype="float")
c = np.ones([3, 4, 5], dtype="float")

生成される配列の中身はめちゃくちゃな値になっているので割愛します。

numpy.empty_like関数の使い方

ある配列とまったく同様の性質(shape, dtype等)をもった配列を新しく未初期化状態で生成するには、numpy.empty_like を使います。numpy.empty で同じことを実現するよりもすっきり記述できます。

import numpy as np

# 配列aがある
a = np.arange(1, 10, dtype="float")

# 配列aと同じ形状や型の配列をゼロで初期化したものを生成したい
# b = np.empty(a.shape, dtype=a.dtype)
# ↑ のように書くよりもわかりやすい
b = np.empty_like(a)

print(a) # [ 1. 2. 3. 4. 5. 6. 7. 8. 9.]
print(b) # むちゃくちゃな値

出力結果を見ると、形状と型が同じの配列が生成されていることが確認できます。値自体は未初期化状態の為、むちゃくちゃな値になっています。

numpy.empty_like関数のパラメータ

numpy.empty_like(a, dtype=None, order='K', subok=True)

パラメータ 説明
a array_like 生成する配列と同じ形状や型を持つ配列を指定します。Pythonのシーケンスでも指定可能です。
dtype dtype 省略可能。出力配列の型を指定します。※省略した場合は aの型を継承します。
order string 省略可能。データをメモリ上にどのように保持するかを、’C’,’F’,’A’,’K’ でから指定します。デフォルトは ‘K’ で元の配列と同じメモリレイアウトを継承します。
subok bool 省略可能。新しく作成された配列は ‘a’のサブクラス型を使用します。デフォルトはTrue。

return値として、与えられた配列と同じ形と型を持つ未初期化状態の配列(ndarray)を返します。

パフォーマンスの比較(empty, zeros, ones)

以上が numpy.emptynumpy.empty_like の使い方です。

値を明示的に初期化しなくてもよいという状況においてのみ使うべき関数です。値が未初期化状態のまま参照や計算等何かしらの処理が行われると、予期せぬ動作を引き起こすかもしれないので、使う前には必ず初期化しましょう。

では、ここでは値を明示的に初期化しない numpy.empty が初期化する関数と比べ、どの程度高速に配列を生成するのか確認してみます。

Jupyter Notebook の %%timeit で処理速度の計測を行います。以下のコードではそれぞれ 100*100 の行列をそれぞれ生成しています。

%%timeit
array_zeros = np.zeros((100, 100))
4.14 µs ± 10.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
array_ones = np.ones((100, 100))
4.39 µs ± 20.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
array_empty = np.empty((100, 100))
533 ns ± 1.74 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
配列の生成速度比較結果

計測結果の単位が異なることに注意しましょう。numpy.empty による生成が 533ns(≒0.5μs)、そのほかが 約4μs でした。

numpy.empty による生成速度が明らかに高速であることがわかります。この例では初期化を伴う生成に比べ、8倍近く高速になっています。

環境や生成されるサイズ等によっていくらか結果に影響があるかもしれませんが、高速に動作することが確認できました。

まとめ

  • numpy.empty は配列(ndarray)の生成を行うが値の初期化は行わない。
  • 値の明示的な初期化が必要ない場面では、numpy.empty を使うことができる。
  • numpy.empty によって生成された配列は、むちゃくちゃな値を持っている。
  • numpy.empty は、値の初期化を伴う関数(numpy.zeros等)に比べ高速に動作する。

以上。

参考URL