NumPy♪nditerを使うと様々な次元数に対応できる

NumPyでは、引数の次元数を限定しないコーディングができるように様々な関数が用意されています。nditerもそのひとつであり、配列の要素を順番に指定することができます。なお、op_flags=['readwrite']についても簡単に紹介します。

0.「ゼロから作るDeep Learning」のポイント整理シリーズ

この記事は「ゼロから作るDeep Learning」のポイント整理シリーズの記事です。シリーズの名前のとおり私の自習用のメモです。書籍では説明されない基礎文法や補足・関連事項などを説明しており、書籍がなくてもわかる内容になっています。

大きな問題ではない。

1.nditerはインデックスをタプル形式で順番に返す

NumPyは配列のインデックスをタプル形式で順番に返すイテレータです。イテレータという言葉に慣れていない場合は具体例を見た方が早いと思います。

コード01のnditerにおいて、flags=['multi_index']を指定することによって、xのインデックスをタプル形式で順番に返すイテレータitを生成することができます。

9行目のidxがタプル形式のインデックスであり、NumPy配列にファンシーインデックスとしてNumPy配列xに渡すことによって、要素を指定することができます。

11行目のit.iternext()により、it.multi_indexを次のインデックスのタプルに更新することができます。

#コード01
import numpy as np

x = np.array([[1,2],[3,4]])
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
print(x)
while not it.finished:  #イテレータitの最後までループ
    #it.multi_indexは、xのインデックスをタプル形式で順番に返す
    idx = it.multi_index
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
#出力01
[[1 2]
 [3 4]]
idx= (0, 0) , x[idx]= 1 , it[0]= 1
idx= (0, 1) , x[idx]= 2 , it[0]= 2
idx= (1, 0) , x[idx]= 3 , it[0]= 3
idx= (1, 1) , x[idx]= 4 , it[0]= 4

なお、あまり使うことはないかもしれませんが、x[idx]はイテレータitの0番目の要素it[0]として指定することも可能です。it[0]の0はxのインデックス番号とは関係ありません。it[1]やit[2]は存在しないので注意してください。

コード02は、1次元配列の場合の使用例です。4行目以外はコード01と全く同じである事に注目してください。次元数の違いに影響を受けないコードを記述できることがnditerのよいところです。

#コード02
import numpy as np

x = np.array([1, 2, 3, 4])
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
print(x)
while not it.finished:  #イテレータitの最後までループ
    #it.multi_indexは、xのインデックスをタプル形式で順番に返す
    idx = it.multi_index
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
#出力02
[1 2 3 4]
idx= (0,) , x[idx]= 1 , it[0]= 1
idx= (1,) , x[idx]= 2 , it[0]= 2
idx= (2,) , x[idx]= 3 , it[0]= 3
idx= (3,) , x[idx]= 4 , it[0]= 4

2.op_flags=['readwrite']とは

実際にop_flags=['readwrite']まで使う方は少ないかもしれませんが、「ゼロから作るDeepLearnig」のサンプルコードで指定されていたので簡単に説明したいと思います。

なお、「ゼロから作るDeepLearnig」のサンプルコードにおいても、このオプションは特に指定しなくても動作に影響はありません。

x[idx]だけではなく、it[0]によっても配列xの要素を指定することができることは説明しましたが、コード03の6行目のようにit[0]に値を代入することにより配列xの内容を変更することができます。

6行目の時点では、it.multi_indexは(0, 0)であるため、x[0, 0]の要素を99に変更することができます。it[0]の0はインデックス番号ではないため、it[0, 1]やit[0][1], it[1]といった指定はできません。

#コード03
import numpy as np

x = np.array([[1,2],[3,4]])
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
it[0] = 99
print(x)
while not it.finished:  #イテレータitの最後までループ
    idx = it.multi_index
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
#出力03
[[99  2]
 [ 3  4]]
idx= (0, 0) , x[idx]= 99 , it[0]= 99
idx= (0, 1) , x[idx]= 2 , it[0]= 2
idx= (1, 0) , x[idx]= 3 , it[0]= 3
idx= (1, 1) , x[idx]= 4 , it[0]= 4

it[0]を使って、配列xの他の要素を変更する場合にはコード04のように、it.multi_indexが代入したいインデックスであるときに、it[0]に値を代入します。すると、配列xの任意の要素を変更することができます。

#コード04
import numpy as np
x = np.array([[1,2],[3,4]])
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
print(x)
while not it.finished:  #イテレータitの最後までループ
    it[0] = 99
    idx = it.multi_index
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
print(x)
#出力04
[[1 2]
 [3 4]]
idx= (0, 0) , x[idx]= 99 , it[0]= 99
idx= (0, 1) , x[idx]= 99 , it[0]= 99
idx= (1, 0) , x[idx]= 99 , it[0]= 99
idx= (1, 1) , x[idx]= 99 , it[0]= 99
[[99 99]
 [99 99]]

op_flags=['readwrite']は、op_flags=['readonly']とすることができますが、その場合にはit[0]に代入しようとするとエラーになります。

#コード05
x = np.array([[1,2],[3,4]])
it = np.nditer(x, flags=['multi_index'], op_flags=['readonly'])
print(x)
while not it.finished:  #イテレータitの最後までループ
    it[0] = 99
    idx = it.multi_index
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
#出力05
RuntimeError: Iterator operand 0 is not writeable

ただし、op_flags=['readonly']であっても、配列xに直接代入することは可能です。

#コード06
x = np.array([[1,2],[3,4]])
it = np.nditer(x, flags=['multi_index'], op_flags=['readonly'])
print(x)
while not it.finished:  #イテレータitの最後までループ
    idx = it.multi_index
    x[idx] = 99
    print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
    it.iternext()   #itを次のインデックスに更新
print(x)
#出力06
[[1 2]
 [3 4]]
idx= (0, 0) , x[idx]= 99 , it[0]= 99
idx= (0, 1) , x[idx]= 99 , it[0]= 99
idx= (1, 0) , x[idx]= 99 , it[0]= 99
idx= (1, 1) , x[idx]= 99 , it[0]= 99
[[99 99]
 [99 99]]

nditerは、その他にも色々な細かい機能があるようですので、興味があるかたはNumPyのリファレンスを参照してください。

3.同じコードで様々な次元数に対応可能

nditerは、前述のとおり様々な次元数の配列を同じコードで処理できるのが最も大きな利点ではないでしょうか。特に関数で用いると便利だと思います。

例えばコード07の関数view_np(x)は関数の引数として様々な次元数の配列を渡すことができるので対応能力の高い柔軟な関数をコーディングすることが可能です。

#コード07
import numpy as np

def view_np(x):
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    print(x)
    while not it.finished:
        idx = it.multi_index
        print('idx=', idx ,', x[idx]=', x[idx], ', it[0]=', it[0])
        it.iternext()   #itを次のインデックスに更新

x = np.array([[1, 2], [3, 4]])
view_np(x)
x = np.array([1, 2, 3, 4])
view_np(x)

4.まとめ

nditerは、配列のインデックスをタプル形式で順番に返すイテレータです。

nditerを用いることで、様々な次元数に対応できる関数を記述することが可能であり、柔軟なコーディングが可能です。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー
UdemyのPythonの動画講座を書籍を買う感覚で購入してみた

その他

Twitterへのリンクです。SNSもはじめました♪

液晶ペンタブレットを買いました
 (1) モバイルディスプレイを買うつもりだったのに激安ペンタブレット購入

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?