ndarrayのfortran flagと実際のメモリ配列について
実験¶
import numpy as np
2x2のndarrayを生成する。
a = np.array([[1,2], [3,4]])
a
.Tで転置を取ると行と列が入れ替わる。
a.T
元のndarrayの.flagsを表示すると、C_CONTIGUOUSがTrueとなっている一方、F_CONTIGUOUSがFalseとなっていることが分かる。C_CONTIGUOUSはC形式配列であるかを表すフラグであり、F_CONTIGUOUSはFortran形式配列であるかを表すフラグである。
a.flags
一方.Tを呼んだ後のndarrayの.flagsを表示すると、C_CONTIGUOUSがFalseに変化し、一方F_CONTIGUOUSがTrueとなっている。
a.T.flags
np.saveでの保存時の形式¶
.Tで転置を取ると.flagsのC形式配列であるかFortran形式配列であるかのフラグが入れ替わることが分かったが、実際の格納形式がどうなっているかを知るために、np.saveを用いて保存した.npyファイルに対して!hexdump -Cvを実行することでバイナリの中身を調べる。ここでhexdumpコマンドの-CはASCII表示を行うためのオプションであり、-vは全データを表示するためのオプションである(おそらくBSD系のhexdumpのみ)。
まず、np.saveで転置前後のndarrayを保存する。
np.save("a.npy", a)
np.save("a.T.npy", a.T)
これらをnp.loadで再度読み込み直すと、.flagsは読み込んだ後も保持されていることが分かる。
np.load("a.npy").flags
np.load("a.T.npy").flags
ディスクへダンプした際の実際の格納形式がどうなっているかをhexdumpコマンドを用いて調べる。
!hexdump -Cv a.npy
!hexdump -Cv a.T.npy
ヘッダーのfortran_orderが変化していることが分かる。更にbashのプロセス置換で両者のdiffを見てみる。
%%bash
diff <(hexdump -Cv a.npy) <(hexdump -Cv a.T.npy)
ヘッダーのみ差分が存在し、実際のデータが格納されている後半部は変化していないことが分かる。
転置した状態のまま保存する方法¶
ndarrayに対して.copy()を呼んだ上で保存すれば良い。下記に実例を示す。
まず、.copy()を呼んだ後の.flagsを表示する。
a.T.copy().flags
転置したにもかかわらずC_CONTIGUOUSがTrueになっていることが分かる。
更にこのndarrayをnp.saveで保存し、hexdumpで中身を見てみる。
np.save("a.T.copy.npy", a.T.copy())
!hexdump -C a.T.copy.npy
C_CONTIGUOUSがTrue、F_CONTIGUOUSがFalseになっていたことからも分かるように、ヘッダーのfortran_orderがFalseになっている。
当然、再度np.loadで読み込んだ場合でもこの.flagsは保たれている。
np.load("a.T.copy.npy").flags
元のndarrayと、転置した上で.copy()を呼んだ後のndarrayをnp.saveしたバイナリを比較してみる。
%%bash
diff <(hexdump -Cv a.npy) <(hexdump -Cv a.T.copy.npy)
両者ともにC形式配列となっているため、純粋にデータ部の並びのみが異なる。
同様に転置を取った場合と、その上で.copy()を呼んだ場合のバイナリを比較してみる。
%%bash
diff <(hexdump -Cv a.T.npy) <(hexdump -Cv a.T.copy.npy)
この場合、ヘッダーもデータ部も異なっていることが分かる。
なぜこの記事を書いたか¶
とある.npyファイルのデータ部分を直接読み込むCプログラムを触っていた際に、.Tで転置した.npyを読み込んでも読み込んだデータ部分が全く変化していないということが発生したためである。
当該プログラムではヘッダーのfortran_orderを完全に無視していたため、.Tしただけのndarrayを保存した.npyではデータ部分が転置されておらず、意図しない動作となってしまっていた。これを解決するためには前述のように.copy()を呼んだ上で保存した.npyファイルを読み込む必要があった。
Comments
Comments powered by Disqus