numpyのndarrayには0次元配列(以下、0-dim array)という、不思議な挙動をする配列が存在する。
これは、numpy.arrayにスカラー値を渡すと生成できる。
numpy.array(1, numpy.float32)
0-dim arrayはほとんどスカラー値(float, numpy.float32など)と同じ挙動である。
- 型はndarrayである
-
shapeは()である - 通常のスカラー値とほとんど同じ振る舞いをする(ブロードキャストなど)
- 計算結果がスカラー(
numpy.float32など)に変換される
以下に詳細を書く。
numpyの挙動
スカラー変換問題
0-dim arrayはある日突然スカラー値に変換される。
>>> type(numpy.array(1, numpy.float32) + 1)
<class 'numpy.float64'> # ndarrayではない
numpyの仕様によれば、返却値が0-dimのときはスカラーに変換されるということである。
すなわち、ndarrayの演算の結果がndarrayでないときは、0-dim arrayに変換する処理が必要である。
float64昇格問題
numpy.float32は、pythonの通常のintやfloatと演算すると、結果はnumpy.float64が返される。
これらは64ビットである可能性があるからだとおもわれる。
>>> (numpy.float32(1) + 1).dtype
dtype('float64')
もちろん、ユーザーは気をつけて全ての値をfloat32にすれば、この問題は発生しないが、大変面倒である。
この問題は、0次元配列にも伝搬する。
>>> (numpy.array(1, numpy.float32) + 1).dtype
dtype('float64') # float32ではない
ちなみに、1次元以上の配列ではこの問題は実は発生しない。先にアロケーションしてから__iadd__的な処理をしているからと推測している。
>>> (numpy.array([1], numpy.float32) + 1).dtype
dtype('float32')
また、既存の静的型付け言語でこの問題は発生しにくい。何故か。
float x = 1.0; // floatになる
float y = x + 1.0; // float + double だが、floatに変換される
最終的に代入先の変数に変換されるからである。
ただし、途中過程でdoubleを経由することはあり得る。
あくまで、最後に型を強制されるから問題にならない。
chainerにおける注意点
chainerではVariableの中身はndarrayに限定している。これは、複数の型を許すと、型ごとに処理を切り替える必要が現れる可能性があるためである。
また同時に、原則的に現状ではfloat32に限定している。これはCUDAのコードではメモリ配置がどうなっているか事前に把握した状態で処理する必要があるため、float64とfloat32でことなるカーネルを用意する必要が有るためである(将来的に、ここをテンプレート展開する用に処理することはあり得る)。
forward計算でfloat32を返す
特に損失関数のFunctionでは、正確にfloat32を返す必要がある。
これは、xxxLossなどのクラスで注意する。
以下の様なコードは危険である。
return numpy.array(loss), # たぶんfloat64になる
必ずfloat32にする。
return numpy.array(loss, numpy.float32),
numpyに処理を委譲する場合
numpyの振る舞いを考慮して実装する必要がある。
例えばAddの実装には注意が必要である。
def forward_cpu(self, inputs):
return inputs[0] + inputs[1],
この処理は、仮にinputs[0]が0-dim arrayでinputs[1]がintのときに、float64昇格問題を引き起こす。