Pay money To my Life

Spend time for myself, and you... "Knowledge was the only way to remember that your past is true"

Python高速化・小技メモ

本記事について

 Pythonでの記述に関して、小技をメモしていく。自分用が主たる目的なので、記述に関して詳説はしない。目的と、その手法の2段ツリー構造でまとめていく。

高速化

配列の、指定したindexの要素で構成された新しい配列の作成

何がしたいって、こういうこと。配列dataがあって、それに対応する各行ごとのindexをまとめた配列idxsがある。これらを組み合わせて、新たにidxsに対応するdataの要素で構成された配列expected_outを作成したい。ってこと。

>>> from platform import python_version
>>> print(python_version())
3.7.4

>>> import numpy as np
>>> np.__version__
'1,17,2'
data = np.array(
    [[1, 3, 2, 4, 8],
     [8, 9, 3, 4, 2],
     [7, 8, 1, 2, 3]])
indexes = np.array(
    [[0, 1, 3, 4, 2],
     [1, 1, 2, 3, 4],
     [3, 2, 3, 4, 3]])
expected_out = np.array(
    [[1, 3, 4, 8, 2],
     [9, 9, 3, 4, 2],
     [2, 1, 2, 3, 2]])

np.take_along_axisを使用

[doc] こんな便利な関数があったのね、やるじゃん、numpy。

>>> np.take_along_axis(arr=data, indices=indexes, axis=1) == expected_out
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]])

idx + W*y を使用

なんのこっちゃねーんってね。
 これは、index用の配列indexesの各行にそれぞれそれまでの行に含まれる要素数を足す(今回の例だと、5x5の配列なので、0行目には0を、1行目には5を、2行目には10をそれぞれ足す)。それを、対象となるdataを1次元に変換した時の配列data.ravel()のindexとして使用し、元の配列の形状にその後戻す、というやり方。強引だけどパッとこんなやり方思いついたらかっこいいよね。。

>>> idx_ = indexes + (np.arange(0, indexes.shape[0]) * indexes.shape[1])[:, None]
>>> out = data.ravel()[idx_].reshape(data.shape)
>>> out ==  expected_out
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]])

実行時間の比較

 timeitモジュールを使用して実行速度の比較。numpyのメソッドであるnp.take_along_axisに軍配。一行で記述できることからも、こっちが実用上は正解の選択肢になりそう。それでも、アイデアとしては後者の方がかっこいいけどなぁ(内部で同じようなことしていたりして)。

>>> from timeit import timeit
>>> LOOP = 100000

>>> result = timeit("np.take_along_axis(arr=data, indices=indexes, axis=1)", globals=globals(), number=LOOP)
>>> print(result / LOOP)
7.96317385000293e-06

>>> def func(org, idx, ans):
>>>     idx_ = idx + (np.arange(0, idx.shape[0]) * idx.shape[1])[:, None]
>>>     ret = org.ravel()[idx_].reshape(data.shape)
>>>     return None
>>> 
>>> result = timeit("func(data, indexes, expected_out)", globals=globals(), number=LOOP)
>>> print(result / LOOP)
9.105247100014822e-06

小技

010101...な配列を生成したい

>>> from platform import python_version
>>> print(python_version())
3.7.4

>>> import numpy as np
>>> np.__version__
'1,17,2'

正攻法だと、

>>> N = 10
>>> a = np.ones(N)
>>> a[::2] = 0
>>> a
array([0., 1., 0., 1., 0., 1., 0., 1., 0., 1.])

>>> b = np.ones(N)
>>> b[1::2] = 0
>>> b
array([1., 0., 1., 0., 1., 0., 1., 0., 1., 0.])

>>> c = np.ones((N, N))
>>> c[::2, ::2] = 0
>>> c[1::2, 1::2] = 0
>>> c
array([[0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0., 1., 0.]])

ちょっと洒落たことやる。np.indicesを使用する。

>>> d = np.indices((N,N))
>>> d.shape
(2, 10, 10)
>>> d
array([[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
        [4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
        [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
        [6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
        [7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
        [8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
        [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]],

       [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]])

>>> np.sum(d, axis=0) % 2
array([[0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
       [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
       [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
       [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
       [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]])

# 1 dimensional
>>> (np.sum(np.indices((N,1)), axis=0) % 2).ravel()
array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1])

>>> (np.sum(np.indices((N,1)), axis=0) % 2).ravel() ^ 1
array([1, 0, 1, 0, 1, 0, 1, 0, 1, 0])