もじとばコム

numpy行列の要素へのアクセスはどれが速い?

pythonで行列を表現するとき、単純には二重リストを用います。

#3*3の行列
>>> a = [[1,2,3],[4,5,6],[7,8,9]]

 

しかし一般にpythonで行列を扱う場合は、以下のようにnumpyを用いて処理することが多いでしょう。

>>> import numpy as np
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a
>>> array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

ここで、行列aの各要素にアクセスする方法を比較します。

関連:あなたに最適なpython独習の参考書は?各参考書の長所と短所をご紹介!

 

計測に用いる配列

>>> import numpy as np
>>> import timeit
>>> a = np.random.rand(1000,1000)

numpyのrandomを用いて、1000*1000の行列aを用意します。

上では計測実験に用いるnumpyライブラリと、計測に使用するtimeitライブラリをインポートしています。

以下の速度計測では全て同じaを対象としているものとします。

 

行列の要素抜き出し比較

a[i][j]による抜き出し

>>> a[0][0]
0.9640700280557352

まず、最もよく目にする方法で要素を抜き出してみます。

行列aの各要素i,jについて抜き出したリストを生成する操作を100回行い、かかった時間を調べます。

単位は秒です。

>>> timeit.timeit(lambda:[[a[i][j] for j in range(1000)] for i in range(1000)], number=100)
33.471876372932456

 

a[i,j]による抜き出し

>>> a[0,0]
0.9640700280557352

行列aのi,j番目要素はこのように抜き出すことも出来ます。

上と同様に速度計測をすると、こちらの方がだいぶ速いことが分かります。

>>> timeit.timeit(lambda:[[a[i,j] for j in range(1000)] for i in range(1000)], number=100)
21.25709904509131

 

a.item(i,j)による抜き出し

>>> a.item(0,0)
0.9640700280557352

滅多に見ないというか、見たことが無い書き方ですが、このように要素を抜き出すことも出来るそうです。

同様の実験を行うとa[i,j]を用いた時より若干遅いことが分かります。

>>> timeit.timeit(lambda:[[a.item(i,j) for j in range(1000)] for i in range(1000)], number=100)
21.97407022002153

 

リストからの抜き出し

ここまではnumpyの行列について計測しましたが、pythonのリストそのままだとどうなるか確認します。

>>> b = a.tolist()
>>> type(b)
<class 'list'>
>>> b[0][0]
0.9640700280557352

まず行列aをリストで表現した行列bに変換します。

次に、上と同様の処理をさせてみます。
ちなみにリストにはb[i][j]という書き方しかありません。

>>> timeit.timeit(lambda:[[b[i][j] for j in range(1000)] for i in range(1000)], number=100)
9.682289591059089

 

爆速ですね。

 

まとめ

以上の結果をまとめると、以下のような表になります。

a[i][j] 33.471876372932456
a[i,j] 21.25709904509131
a.item(i,j) 21.97407022002153
b[i][j] 9.682289591059089

ここで、aはnumpyの行列、bはリストです。

このことからも分かりますが、numpyの要素へのアクセスはリストに比べて非常に遅いことが知られています。

行列の要素に大量にアクセスしたいとき、特に理由がないのであればリストとして保持することが速度改善の鍵です。

 

しかし、既にnumpyでの計算結果を使いたいときなど、numpy行列を使わざるを得ないこともあります。
この時、これをtolist()でリストにするのにもそれなりに時間がかかります

例えば上で用いた1000*1000の行列を100回tolist()すると、以下のように3.4秒ほどかかります。

 

行列そのものの大きさや、その行列に何回アクセスするかによって、tolist()するかnumpy行列のままアクセスするかを決めましょう。

numpy行列のままアクセスする場合はa[i,j]のように書くことで多少の速度改善が得られます。

 

関連:あなたに最適なpython独習の参考書は?各参考書の長所と短所をご紹介!

参考:Numpy individual element access slower than for lists