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
昇格問題を引き起こす。