PythonでTensorFlow使って簡単な方(つまり畳み込みじゃない方ね)のMNISTをやって、計算結果の係数(重み、バイアス)を出力して、iOSのMetal Performance Shadersで使い、手書き数字認識させるのにえらくハマったのでそのあたりのメモ。ちなみにiOSで使うのはObjective-Cです、はい。Swiftだったらサンプルコード(MPSCNNHelloWorld)もあるのでだいぶラクだったとは思いますけどね。
iOSでもTensorFlowが使えるんだけど、ここはやっぱりMPSの方がアプリの容量も小さくなるらしいのでMPSで。
iOSのMPSCNNに渡すモデルパラメータのフォーマット / TensorFlowからの書き出し - Qiita
↑基本こちらを参考にさせていただきました。
まず、Pythonでの学習後に係数を出力するところ。
{
with open(filePath_iOS+'weights.dat', 'wb') as f:
W_trans = tf.transpose(W, perm=[1,0])
f.write(sess.run(W_trans).tobytes())
with open(filePath_iOS+'bias.dat', 'wb') as f:
f.write(sess.run(b).tobytes())
}
参考のページと違って open( , ‘w’) → open( , ‘wb’) にしないとダメだった。
あと、重みは順番を変更。
次にiOS側。
わかりにくいのがGPUでの結果が16bitの浮動小数点数で出てくるけどCPUで使う場合にはfloatは32bitだというところ。
Swift で 半精度浮動小数点数 (16 bit Float) を扱う - Qiita
↑こちらなどを参考に16bit→32bitとして読む、というのをできるようにならないとデバッグが進みませんでした。
-(float)getFloatFromUInt16:(UInt16)value
{
UInt16 *input = calloc(1, sizeof(UInt16));
input[0] = value;
float *output = calloc(1, sizeof(float));
output[0] = 0.0;
vImage_Buffer sourceBuffer;
sourceBuffer.data = input;
sourceBuffer.height = 1;
sourceBuffer.width = 1;
sourceBuffer.rowBytes = sizeof(UInt16);
vImage_Buffer destinationBuffer;
destinationBuffer.data = output;
destinationBuffer.height = 1;
destinationBuffer.width = 1;
destinationBuffer.rowBytes = sizeof(float);
vImageConvert_Planar16FtoPlanarF(&sourceBuffer, &destinationBuffer, 0);
float floatValue = output[0];
free(input);
free(output);
return floatValue;
}
↑こういうのを作っといて入出力画像のMTLTextureの中身なんぞを確認してました。
無駄にハマったのが入力画像をセットする
replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:
のwithBytes:のところに context と書いてて正しく動かず(実行可能だけど正解が出ない)悩んでた。
正解はCGBitmapContextGetData (context)
サンプルコード(Swift)で
context!.data!
となってるのに、contextとするだけで必要なデータが取れると思ってたのよ。
ともかくこれで、Pythonで学習させ係数を出力、iOSのMPSで使う、って流れの第一段階はクリアできたかな。
次は畳み込みの場合でできるようにならねば。