tf.image.resizeを含むFull Integer Quantization (.tflite)モデルのEdgeTPUモデルへの変換後の推論時に発生する "main.ERROR - Only float32 and uint8 are supported currently, got -xxx.Node number n (op name) failed to invoke" エラーの回避方法

# 1. はじめに
2020年11月時点の **`TFLite Converter`** あるいは **`edgetpu compiler`** のどちらかにバグがあり、 **`TensorFlow v2.x`** の **`tf.image.resize`** オペレーションを含むモデルを量子化すると構造が壊れます。 下記に問題提起されています。

1. **[issue : Resize Nearest Neighbor does not convert #187 - google-coral/edgetpu](https://github.com/google-coral/edgetpu/issues/187)**
2. **[issue : Yolov4-Tiny EdgeTPU Missing Leaky Relu #51 - PINTO0309/PINTO_model_zoo](https://github.com/PINTO0309/PINTO_model_zoo/issues/51)**
3. **[issue : RESIZE_NEAREST_NEIGHBOR Operation version not supported #42024 - tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/issues/42024)**

具体的には、下図の一例のようにEdgeTPUモデルへ変換した際の仕様不備により、 **`ResizeNearestNeighbor`** あるいは **`ResizeBilinear`** 以降の変換が正しく行われないばかりか、推論実行時に edgetpu_runtime が動作不安定になりエラーを出力します。 これは TensorFlow v1.x の頃には発生していなかった事象です。 **`half_pixel_centers`** が **`true`** の状態のモデルを量子化して生成すると発生します。 また、 EdgeTPUモデルへ変換せず **`INT8量子化`** を行っただけのモデルの挙動も不安定になるなど、問題が多いです。
![Screenshot 2020-11-29 20:27:28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194769/f8a8c98c-51e6-f450-4aee-ba7f111d7d8c.png)

エラーの発生例は下記のとおりですが、 **`ResizeNearestNeighbor`** や **`ResizeBilinear`** の部分でエラーになるわけではなく、その後ろのどこかのレイヤーで仕様アンマッチによるエラーを引き起こしますので、問題の所在がとてもわかりくいバグです。

```console:エラー例１
main.ERROR - Only float32 and uint8 is supported currently, got INT8.Node number 404 (LEAKY_RELU) failed to invoke.
```

```console:エラー例２
main.ERROR - Only float32 and uint8 are supported currently, got -1695564548.Node number 5 (PRELU) failed to invoke.
```

# 2. 回避方法
下記は Cloud TPU を使用した場合に発生する類似の問題を回避する方法をご説明いただいている記事です。 今回は下記２種類の対処を同時に実施します。

1. **[TPUでアップサンプリングする際にエラーを出さない方法 - Shikoan's ML Blog - @koshian2さん](https://www.google.com/amp/s/blog.shikoan.com/tpu-upsampling/amp/)**
2. **[Colab TPUでもtensorflow.kerasでBilinear法のアップサンプリングを行う方法 - Qiita - @fwattyさん](https://qiita.com/fwatty/items/691fc4a5b697dde86dc5)**

**`TensorFlow v1.x`** 世代の **`resize`** オペレーションへ置き換えることにより問題を回避します。 **`Lamda`** を経由してコールしないと Numpy 変換は対応していない、という旨のエラーが発生してしまいます。 **`Lamda`** でラップして定義した v1.x ベースの resize オペレーションは、正常に v2.x ベースの **`ResizeNearestNeighbor`** や **`ResizeBilinear`** へ変換されることを確認済みです。 

```python:Lamdaを使用したupsamplingの実装例
### 呼び出し先の関数定義 ############################################################
def upsampling2d_bilinear(x, upsampling_factor_height, upsampling_factor_width):
    w = x.shape[2] * upsampling_factor_width
    h = x.shape[1] * upsampling_factor_height
    return tf.compat.v1.image.resize_bilinear(x, (h, w))

def upsampling2d_nearest(x, upsampling_factor_height, upsampling_factor_width):
    w = x.shape[2] * upsampling_factor_width
    h = x.shape[1] * upsampling_factor_height
    return tf.compat.v1.image.resize_nearest_neighbor(x, (h, w))

### 呼び出し元のメインロジック #######################################################
# height方向に２倍、width方向に2倍のupsamplingを行います。整数のみ指定可能です。
x = Lambda(upsampling2d_bilinear,
           arguments={'upsampling_factor_height': 2,
                      'upsampling_factor_width':  2})(x)

# height方向に4倍、width方向に4倍のupsamplingを行います。整数のみ指定可能です。
x = Lambda(upsampling2d_nearest,
           arguments={'upsampling_factor_height': 4,
                      'upsampling_factor_width':  4})(x)
```

下図のように v1.x ベースの resize オペレーションを使用していても v2.x ベースの resize オペレーションへ正常に変換されていることが分かります。 更に **`half_pixel_centers`** が **`false`** に設定されていることが分かります。
![Screenshot 2020-11-29 21:12:55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194769/8b0a1b46-8017-007b-7c9d-a42187eff27e.png)


**`edgetpu_compiler`** を通して.tfliteをコンバートすると、下図のように全てのオペレーションが EdgeTPU 対応のカスタムオペレータに変換されたことが分かります。 これで EdgeTPU 上で推論を実行してもエラーになることはありません。
![Screenshot 2020-11-29 20:46:53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194769/835324f5-bb63-4031-fece-e3494bcbe246.png)
今回の問題を発生させる標準オペレーションは下記です。 そもそも **`half_pixel_centers`** を設定するパラメータがありませんので外から制御することができません。

- tf.image.resize **https://www.tensorflow.org/api_docs/python/tf/image/resize**

```python:TF2.x_Resize_images_to_size_using_the_specified_method
tf.image.resize(
    images, size, method=ResizeMethod.BILINEAR, preserve_aspect_ratio=False,
    antialias=False, name=None
)
```

|Args|Overview|
|:--|:--|
|images|4-D Tensor of shape [batch, height, width, channels] or 3-D Tensor of shape [height, width, channels].|
|size|A 1-D int32 Tensor of 2 elements: new_height, new_width. The new size for the images.|
|method|An image.ResizeMethod, or string equivalent. Defaults to bilinear.|
|preserve_aspect_ratio|Whether to preserve the aspect ratio. If this is set, then images will be resized to a size that fits in size while preserving the aspect ratio of the original image. Scales up the image if size is bigger than the current size of the image. Defaults to False.|
|antialias|Whether to use an anti-aliasing filter when downsampling an image.|
|name|A name for this operation (optional).|

# 3. おわりに
以上です。 それでは皆さん、 良いTensorFlowライフを満喫してください！ ちなにみに私お手製の **`PyTorch(NCHW) to TensoFlow(NHWC) 変換ツール`** **[openvino2tensorflow](https://github.com/PINTO0309/openvino2tensorflow)** はツールレベルで対処済みです。
<a href="https://github.com/PINTO0309/openvino2tensorflow"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/PINTO0309/openvino2tensorflow.png" width="460px"></a>