CoreML変換時の謎のInvalid dst shapeを倒す


謎のWarning

coremltoolsでKerasでは完璧に動くモデルを変換するとこのようなWarningが出ることがあります。

RuntimeWarning: You will not be able to run predict() on this Core ML model.Underlying exception message was: Error compiling model: "compiler error:  Invalid dst shape1 x 62 x 18 x 1->1->1 x 1 x 12 x 0 x 62 x ".

※ 最後のshapeは人によって違うと思います。

書いてあるとおりに読むと、「なんやかんや問題があって predict() が実行できません」と書いてありますが、これWarningなので実際mlmodelは生成されます。

ただ、CoreMLは学習用ではなく推論用と今の所認識しているので predict() が使えないmlmodelは何のために存在するのか理解できません。WarningではなくErrorにするべきでは。。。

何のエラーなのか

以前私の記事にも書いたことがありますが、coremltoolsで変換したmlmodelには同一のレイヤーを使い回すshared layerが存在すると結構不安定な挙動をします。(というか多分coremltoolsが色々対応できていない。)

それの一環(?)で、以下のようなモデルを変換するとKerasやTensorflowでは完璧に動作するのにmlmodelへ変換するとshapeがバグります。

input1 = Input((3,3,3))
input2 = Input((3,3,3))
input3 = Input((3,3,3))
input4 = Input((3,3,3))

conv1 = Conv2D(3, kernel_size=(2, 2), dilation_rate=(1, 1), strides=1, padding='same')
conv2 = Conv2D(3, kernel_size=(2, 2), dilation_rate=(1, 1), strides=1, padding='same')

concat = [Concatenate()([conv1(input1), conv2(input2)]), Concatenate()([conv1(input3), conv2(input4)])]

model = Model(inputs = [input1, input2, input3, input4], outputs = concat)

例示のためあまり意味はないモデルですが、conv1,conv2のレイヤーを2箇所に共有で持たせた上で2ずつ入力に適用しConcatenateでつなぎ合わせたものを出力しています。

Concatenateはaxis=-1、すなわちch次元(第3次元)で接続されるので出力は 3,3,6 のサイズが2つの出力になるはずです。Kerasではもちろんその通りです。

ではcoremltoolsでconvertしてみます。
その結果がこちら。(coreMLではch次元が1次元目に来るのでshapeの順序が変わっているのは問題ではありません。)

0 : input_1, <keras.engine.topology.InputLayer object at 0x122c67a90>
1 : input_2, <keras.engine.topology.InputLayer object at 0x122c67b00>
2 : input_3, <keras.engine.topology.InputLayer object at 0x122c67dd8>
3 : input_4, <keras.engine.topology.InputLayer object at 0x122c67f28>
4 : conv2d_1_0, <keras.layers.convolutional.Conv2D object at 0x122c67b38>
5 : conv2d_1_1, <keras.layers.convolutional.Conv2D object at 0x122c67b38>
6 : conv2d_2_0, <keras.layers.convolutional.Conv2D object at 0x122c67b70>
7 : conv2d_2_1, <keras.layers.convolutional.Conv2D object at 0x122c67b70>
8 : concatenate_1, <keras.layers.merge.Concatenate object at 0x122c732b0>
9 : concatenate_2, <keras.layers.merge.Concatenate object at 0x122c732e8>
Input name(s) and shape(s): 
4 : (C,H,W) = (3, 3, 3) 
test1 : (C,H,W) = (3, 3, 3) 
test2 : (C,H,W) = (3, 3, 3) 
test3 : (C,H,W) = (3, 3, 3) 
Neural Network compiler 0: 100 , name = conv2d_1_0, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 1: 100 , name = conv2d_1_1, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 2: 100 , name = conv2d_2_0, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 3: 100 , name = conv2d_2_1, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 4: 320 , name = concatenate_1, output shape : (C,H,W) = (12, 3, 3) <- !!!!?!?!?!??!???!?!?!??!??!??????
Neural Network compiler 5: 320 , name = concatenate_2, output shape : (C,H,W) = (12, 3, 3) <- !!!!?!?!?!??!???!?!?!??!??!??????

最後の2行を見てください。
突然C(ch)のサイズが6ではなく12になっています。今回のモデルはここで出力にして終わっているのでshapeに関するエラーも起きていませんが、もちろんこの後ろにReshapeなどのshapeの総数の変更を許さない系のレイヤーが来れば冒頭のWarningが起こるわけです。

正直この問題はKerasやTensorflowの書き方が悪いのではなく、ほぼ間違いなくcoremltoolsのバグだと思います。

ただ、これに関しては以前から私も問題視していてPRやIssueで色々訴えかけていますが、あんまりApple自身にやる気が無いのか、coremltoolsのGithubの盛り上がりが著しく低く数ヶ月単位でPRやIssueが動かないような事もザラですので、あんまり改善を期待して待っているのもよろしくなさそうです。

じゃあどうすれば良いのか

一応coremltoolsのソースを読むと、怪しいのがshared layerに関する箇所だという事は把握できました。その上でどうにかsharedじゃない状況を作りshapeについての操作を迂回する事で解決に至った感じですが、この経緯を知らない人からすると謎すぎる解法になります。

input1 = Input((3,3,3))
input2 = Input((3,3,3))
input3 = Input((3,3,3))
input4 = Input((3,3,3))

conv1 = Conv2D(3, kernel_size=(2, 2), dilation_rate=(1, 1), strides=1, padding='same')
conv2 = Conv2D(3, kernel_size=(2, 2), dilation_rate=(1, 1), strides=1, padding='same')

concat = [Concatenate()([Reshape((3,3,3))(Flatten()(conv1(input1))), Reshape((3,3,3))(Flatten()(conv2(input2)))]), Concatenate()([Reshape((3,3,3))(Flatten()(conv1(input3))), Reshape((3,3,3))(Flatten()(conv2(input4)))])]

model = Model(inputs = [input1, input2, input3, input4], outputs = concat)

なにが変わったか分かりますか?

変わったのは1行だけ、concat =の行です。
Flattenで平らにした後Reshapeで元の形状に戻すという本来何の意味もないことをしています。しかしながらこのモデルをcoremltoolsで変換すると…

0 : input_1, <keras.engine.topology.InputLayer object at 0x11d31fba8>
1 : input_2, <keras.engine.topology.InputLayer object at 0x11d31fc18>
2 : input_3, <keras.engine.topology.InputLayer object at 0x11d31fef0>
3 : input_4, <keras.engine.topology.InputLayer object at 0x11d31fc50>
4 : conv2d_1_0, <keras.layers.convolutional.Conv2D object at 0x11d31fd68>
5 : conv2d_1_1, <keras.layers.convolutional.Conv2D object at 0x11d31fd68>
6 : conv2d_2_0, <keras.layers.convolutional.Conv2D object at 0x11d31feb8>
7 : conv2d_2_1, <keras.layers.convolutional.Conv2D object at 0x11d31feb8>
8 : flatten_1, <keras.layers.core.Flatten object at 0x11d3293c8>
9 : flatten_2, <keras.layers.core.Flatten object at 0x11d329438>
10 : flatten_3, <keras.layers.core.Flatten object at 0x11d3294a8>
11 : flatten_4, <keras.layers.core.Flatten object at 0x11d329518>
12 : reshape_1, <keras.layers.core.Reshape object at 0x11d329588>
13 : reshape_2, <keras.layers.core.Reshape object at 0x11d3295c0>
14 : reshape_3, <keras.layers.core.Reshape object at 0x11d3295f8>
15 : reshape_4, <keras.layers.core.Reshape object at 0x11d329630>
16 : concatenate_1, <keras.layers.merge.Concatenate object at 0x11d329668>
17 : concatenate_2, <keras.layers.merge.Concatenate object at 0x11d3296a0>
Input name(s) and shape(s): 
4 : (C,H,W) = (3, 3, 3) 
test1 : (C,H,W) = (3, 3, 3) 
test2 : (C,H,W) = (3, 3, 3) 
test3 : (C,H,W) = (3, 3, 3) 
Neural Network compiler 0: 100 , name = conv2d_1_0, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 1: 100 , name = conv2d_1_1, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 2: 100 , name = conv2d_2_0, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 3: 100 , name = conv2d_2_1, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 4: 301 , name = flatten_1, output shape : (C,H,W) = (27, 1, 1) 
Neural Network compiler 5: 301 , name = flatten_2, output shape : (C,H,W) = (27, 1, 1) 
Neural Network compiler 6: 301 , name = flatten_3, output shape : (C,H,W) = (27, 1, 1) 
Neural Network compiler 7: 301 , name = flatten_4, output shape : (C,H,W) = (27, 1, 1) 
Neural Network compiler 8: 300 , name = reshape_1, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 9: 300 , name = reshape_2, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 10: 300 , name = reshape_3, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 11: 300 , name = reshape_4, output shape : (C,H,W) = (3, 3, 3) 
Neural Network compiler 12: 320 , name = concatenate_1, output shape : (C,H,W) = (6, 3, 3) <- YES!!!!!!!!!
Neural Network compiler 13: 320 , name = concatenate_2, output shape : (C,H,W) = (6, 3, 3) <- YES!!!!!!!!!

めでたし、めでたし。

な訳あるか

coreml界隈もう少し盛り上がってくれませんかね。。。