TensorFlow Hub：Kaggle Modelで配布されているmultiple inputsを備えたTensorFlow1形式の生成AI「BigGAN」をTensorFlow Lite形式とONNX形式へと変換して保存・推論する

# Kaggle Modelで配布されているモデルをONNX形式へと変換して組み込み開発に活用しよう

**TensorFlow Hub：Kaggle Model** では数多くのAIモデルが配布されています。これらの多くは学習済みのAIモデルとなっていますので、学習用のデータセットを準備することなく、すぐにAIアプリケーションの実装に活用することが可能です。今回は、こうしたAIモデルを、組み込み開発でよく利用されるONNX形式へと変換し、onnxruntimeにより推論を実行する方法についてご説明いたします。

https://learn.microsoft.com/ja-jp/azure/machine-learning/concept-onnx?view=azureml-api-

<font color="blue">**2025/04/13追記：入力テンソルを固定長にする方法は以下の通りです**</font>

https://qiita.com/xtrizeShino/items/0b390ffdebadc45b8e62

#### 本記事の範囲外 <font color="red">（調査中）</font>

本記事では、ソースコードを利用してTensorFlow.Keras形式のAIモデルをONNX形式へと変換します。tf2onnxコマンドを利用した変換方法については現在調査中です（2025年4月11日現在：outputsのリストを正しく参照できていないようです）。

https://wiki.st.com/stm32mpu/index.php?title=How_to_convert_a_Tensorflow%E2%84%A2_Lite_model_to_ONNX_using_tf2onnx&oldid=93105

https://github.com/onnx/tensorflow-onnx/issues/2388

## BigGANをONNX形式へと変換して推論する

AIモデルには様々なバリエーションがありますが、中でも生成AIである **BigGAN** はKaggle Modelで配布されているAIモデルをONNX形式として活用する方法を理解するためのベストプラクティスです。 **BigGAN** は、Imagenetで定義されるクラス(ジャンル)のうち、指定されたクラスに対応した画像を生成できるAIモデルであり、3個の入力テンソルを入力として、1個の画像を含んだ出力テンソルを生成します。

https://www.tensorflow.org/hub/tutorials/biggan_generation_with_tf_hub?hl=ja

https://www.kaggle.com/models/deepmind/biggan

**BigGANがTensorFlow Hub：Kaggle Modelを理解するためのベストプラクティスと感じる理由は、BigGANを利用するために、以下の疑問を解決しなければならないためです。** これらのポイントを抑えておけば、Kaggle Modelで配布されている大抵のAIモデルをONNX形式へと変換することができるでしょう。

- 複数の入力を必要とするモデルを扱う方法を理解しているか
- scalar値の入力を扱うモデルについて理解しているか
- 出力が妥当であることを簡単に評価できるか


### BigGANの出力を確認する

BigGANが生成する出力は、以下のような **RGB画像を含むテンソル** です。float32型で定義されているため、matplotlibを利用した以下のメソッドを定義することで簡単に可視化することができます。可視化した結果、自身が指定したクラスらしい画像を生成できていれば、BigGANの制御に成功していると言えます。 **結果を評価関数に通すことなく、目視で結果を評価できるため、わかりやすいです。**

![export_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/90db8161-8043-4b66-91f1-44e610c3bf43.png)

```python
### (128, 128, 3)のテンソルを画像として保存するメソッド
def save_tensor_as_image(image_tensor, title):
  # 一度に1個の画像のみを扱う
  n = 1
  # BigGANの生成する画像サイズを基に計算する
  image_size = 128
  # ピクセル数から幅を決定する
  w = (image_size * 6) // 320
  # グラフの縦と横の長さを設定する
  plt.figure(figsize=(w * n, w * 1.1))
  # グラフに0.0～1.0の値を取る画像をプロットする
  plt.imshow(image_tensor, aspect='equal')
  plt.axis('off')
  plt.title(title)
  # PNG形式で保存する
  plt.savefig("export.png")
  
### -1.0～+1.0の出力テンソルの値を0.0～1.0に丸める
output = (output * 0.5) + 0.5

### 出力テンソルを画像として保存する
save_tensor_as_image(output[0], 'Output Image')
```

## 学習済みのBigGANを含むONNX形式のモデルで推論する

それでは実装をはじめましょう。

### 必要なパッケージをimportする

まず、以下のパッケージをimportします。各パッケージのバージョンは本記事末尾に掲載する `requirements.txt` の内容をご参照ください。

```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow_hub as hub
import tf2onnx
import onnx
import onnxruntime
from onnxruntime.datasets import get_example
```

https://github.com/OCR-D/ocrd_calamari/issues/87#issuecomment-1466324758

https://stackoverflow.com/a/78838762/30064284

### 複数入力をとるKerasLayerの扱い方

次に、TensorFlow Hub：Kaggle Modelから学習済みのモデルを取得し、これをレイヤーとしたモデルを生成します。TensorFlow Hub：Kaggle Modelのモデルを基にレイヤーを作成するためには `hub.KerasLayer` を利用します。レイヤーを定義した後に `keras.Input` で定義した入力テンソルを接続します。複数の入力を結合するためには `dict` を使用します。これらの要素に基づいて、Kerasの提供するFuncational API機能を利用し、モデルを定義します。 **特に`input_truncation` を定義する方法について注意してください。このInputはscalar値であるため `batch_shape()` 引数を利用して、batch_sizeを含まないInputとなるように定義する必要があります。**

https://www.tensorflow.org/guide/keras/functional?hl=ja

モデルを定義した後に `build` メソッドを呼び出してモデルを確定させ、 `summary` メソッドを実行して期待するモデルを定義でいていることを確認します。問題なくAIモデルが構成できていれば `save` メソッドで保存しましょう。

```python
######
### 指定したURLからモデルを取得してKeras形式のAIモデルを定義する
######
model_url = "https://www.kaggle.com/models/deepmind/biggan/TensorFlow1/128/2"

### AIモデルの入力を定義する
# truncation (scalar)
input_truncation = keras.Input(batch_shape=(), name='truncation')
# class (1, 1000)
input_y = keras.Input(shape=(1000, ), name='y')
# noize (1, 120)
input_z = keras.Input(shape=(120, ), name='z')

# Kaggleから取得したAIモデルをKerasLayerに変換する
hub_layer = hub.KerasLayer(
            model_url,
            trainable=False,
            signature="default", 
            signature_outputs_as_dict=True,
        )
        
# 複数の入力(multiple inputs)をKerasLayerに接続する(dictを利用する)
inputs = dict(y=input_y, z=input_z, truncation=input_truncation)
output = hub_layer(inputs)

### KerasのFunctional API機能を利用し
### 入力テンソルと出力テンソルを指定してモデルを定義する
model = tf.keras.models.Model(inputs=[input_truncation, input_y, input_z], outputs=[output])

### モデルをビルドする
model.build(([1], [1, 1000], [1, 120]))

### モデルのサマリーを表示する
model.summary()

### Kerasモデルを保存する
model.save("biggan-128")
```

```text
Model: "model"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 truncation (InputLayer)        [None]               0           []                               
                                                                                                  
 y (InputLayer)                 [(None, 1000)]       0           []                               
                                                                                                  
 z (InputLayer)                 [(None, 120)]        0           []                               
                                                                                                  
 keras_layer (KerasLayer)       {'default': (None,   141028845   ['truncation[0][0]',             
                                128, 128, 3)}                     'y[0][0]',                      
                                                                  'z[0][0]']                      
                                                                                                  
==================================================================================================
Total params: 141,028,845
Trainable params: 0
Non-trainable params: 141,028,845
```

### Kaggleから読み込んだTensorFlow1形式のAIモデルをTensorFlow Lite形式として保存する

続いて、ネットワークをNetronで可視化するために、AIモデルをTensorFlow Lite形式へと変換して保存します。 `tf.lite.TFLiteConverter.from_keras_model` を利用することにより、簡単にKeras形式のモデルをTensorFlow Lite形式へと変換、保存することが可能です。ここで保存されたtflite形式のファイルを可視化すると以下のようになります。可視化した際に **serving_default_truncation:0, serving_default_y:0, serving_default_z:0** の入力テンソルと **StatefulPartitionedCall:0** の出力テンソルが定義されていることを確認しておきましょう。

```python
### Keras形式のAIモデルをTensorFlow Lite形式へと変換して保存する
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("biggan-128.tflite", "wb").write(tflite_model)
```

https://netron.app/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/8906769b-cb57-489f-91e4-4dbf006d9151.png)


### Kaggleから読み込んだTensorFlow1形式のAIモデルをKeras形式のAIモデルを介してONNX形式へと変換する

TensorFlow Lite形式のモデルを利用し、AIモデルの内容を可視化できるかを確認した後に **`tf2onnx.convert.from_keras`** メソッドを利用して、Keras形式のAIモデルをONNX形式へと変換し、保存します。ここで保存したONNX形式のAIモデルも、Netronを利用することにより、内容を確認することができます。Netronでモデルを可視化した際に **truncation(float32), y(float32), z(float32)** の入力テンソルと **keras_layer(float32)** の出力テンソルが定義されていることを確認しておきましょう。 **またこのとき各入力テンソルと出力テンソルのshapeも確認しておいてください。**

```python
### Keras形式のAIモデルをONNX形式へと変換して保存する
onnx_model, _ = tf2onnx.convert.from_keras(model, outputs_as_nchw=[model.outputs[0].name])
onnx.save(onnx_model, './biggan-128.onnx')
```

https://www.inte.net/news/275843.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/908c7f1e-3a9d-40fd-81b5-87f37d2978c3.png)

### ONNX形式のAIモデルで推論を実行する

ONNX形式で保存したAIモデルを実行するために `onnxruntime` から `InferenceSession` クラスのコンストラクタを利用して開きます。

```python
### 推論用にモデルをロードする
sess = onnxruntime.InferenceSession('./biggan-128.onnx', providers=['CPUExecutionProvider'])
```

ONNX形式のAIモデルを開いた後に `sess.get_inputs()` と `sess.get_outputs()` の内容を確認することにより、AIモデルを実行する際に、どのような入力テンソルが必要であり、どのように出力テンソルをデコードすれば良いかについて知ることができます。ソースコードを実行すると、以下のように、AIモデルの実行には入力テンソルに **`truncation[](scalar), y[batch_size, 1000], z[batch_size, 120]`** が必要であり、AIモデルが出力テンソルとして **`keras_layer[batch_size, 128, 128, 3]`** を生成することがわかります。 **これらの構成は前の手順でNetronにより可視化したAIモデルの情報と一致します。**

```python
### 入力テンソルと出力テンソルの名前とshapeを出力する
inputs_onnx_model = sess.get_inputs()
for i in range(len(inputs_onnx_model)):
    input_name = inputs_onnx_model[i].name
    print("Input name  :", input_name)
    input_shape = inputs_onnx_model[i].shape
    print("Input shape :", input_shape)
    input_type = inputs_onnx_model[i].type
    print("Input type  :", input_type)
    print()

outputs_onnx_model = sess.get_outputs()
for i in range(len(outputs_onnx_model)):
    output_name = outputs_onnx_model[i].name
    print("Output name  :", output_name)  
    output_shape = outputs_onnx_model[i].shape
    print("Output shape :", output_shape)
    output_type = outputs_onnx_model[i].type
    print("Output type  :", output_type)
    print()
```

```text
Input name  : truncation
Input shape : []
Input type  : tensor(float)

Input name  : y
Input shape : ['unk__234', 1000]
Input type  : tensor(float)

Input name  : z
Input shape : ['unk__235', 120]
Input type  : tensor(float)

Output name  : keras_layer
Output shape : ['unk__236', 128, 128, 3]
Output type  : tensor(float)
```

以下はNetronにより可視化したInputsとOutputsです。

![スクリーンショット 2025-04-12 202947.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/caf173b8-be9f-44af-a3cc-a6d964cc35f9.png)

### 入力テンソルを作成する

入力テンソルの型に基づいて、以下のように入力テンソルを定義します。

```python
######
### 入力テンソルを作成する
######
# trunction
# -->> スカラー値
truncation = np.full((), 0.2, dtype=np.float32)
# クラスを選択するOne-Hot-Enodingテンソル(1000クラス)
# -->> [batch_size, 1000]
y = np.zeros((1, 1000), dtype=np.float32)
y[0][207] = 1.0 # golden retriever
# バリエーションを与えるテンソル(120個の乱数)
# -->> [batch_size, 120]
z = np.random.randn(1, 120).astype(np.float32)
```

**ここで `tuncation` の値は、Pythonの通常のスカラー値 `float` でなく、Numpyの定義する `np.float32` 型を持つスカラー値であることに注意してください。** なお、 `y` の値はOne-Hot-Encodingでクラスを示すフラグを含んだベクトルであり、`z` の値は生成する画像にバリエーションを与えるための乱数を格納したベクトルです。ベクトルの詳しい意味については、[BigGANの解説](https://www.tensorflow.org/hub/tutorials/biggan_generation_with_tf_hub?hl=ja) ページをご参照ください。

https://deepage.net/features/numpy-zeros.html

https://note.nkmk.me/python-numpy-zeros-ones-full/

https://note.nkmk.me/en/python-numpy-random/

### 推論を実行する

作成した入力テンソルを辞書形式に加工して、これを引数として `sess.run()` メソッドを実行することにより、推論が実行され、生成AIにより生成された画像をテンソル `outpuut` として取得することができます。なお、outputのshapeは **`[batch_size, 128, 128, 3]`** のため、他のプログラムが出力テンソルを参照する前に **`np.reshape(output, [-1, 128, 128, 3])`** メソッドを利用して、整形しておきましょう。

```python
### 推論を実行する
output = sess.run(["keras_layer"],
                  {"truncation":truncation, "y": y, "z": z,},)

### 出力テンソルのshapeを[batch_size, 128, 128, 3]にする
output = np.reshape(output, [-1, 128, 128, 3])

### 推論完了
print("sess.run finished.")
```

https://note.nkmk.me/python-numpy-reshape-usage/

### matplotlib.pyplotを利用して出力テンソルを画像として保存する

最後に、出力テンソルを画像として保存すれば、完了です。

```python
######
### (128, 128, 3)のテンソルを画像として保存するメソッド
######
def save_tensor_as_image(image_tensor, title):
  n = 1
  image_size = 128
  w = (image_size * 6) // 320
  plt.figure(figsize=(w * n, w * 1.1))
  plt.imshow(image_tensor, aspect='equal')
  plt.axis('off')
  plt.title(title)
  plt.savefig("export.png")

### -1.0～+1.0の出力テンソルの値を0.0～1.0に丸める
output = (output * 0.5) + 0.5

### 出力テンソルを画像として保存する
save_tensor_as_image(output[0], 'Output Image')
```

![export.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/20521bb7-8f48-4b12-9bb5-d5bbb7bda013.png)

**期待する画像を取得できましたね！**

----

### 補足：入力テンソルと出力テンソルのshapeを固定する

作成したONNX形式のモデルに対して、以下のコマンドを実行することにより、Input shapeとOutput shapeを固定することができます（ここでは1に固定しています）。指定している `unk__234` と `unk__235` はNetronで確認できる変数です。組み込み開発では、 **AIモデルをGPU以外のハードウェアアクセラレータへで実行する際に、固定長でAIモデルを扱うこともあるため、そうした場面で本コマンドは有用です。** 

https://onnxruntime.ai/docs/tutorials/mobile/helpers/make-dynamic-shape-fixed.html

```bash
### yを固定する(Outputsも固定される)
$ python -m onnxruntime.tools.make_dynamic_shape_fixed --dim_param unk__234 --dim_value 1 biggan-128.onnx biggan-128_fixed.onnx
### zを固定する
$ python -m onnxruntime.tools.make_dynamic_shape_fixed --dim_param unk__235 --dim_value 1 biggan-128_fixed.onnx biggan-128_fixed.onnx
```

#### 固定する前のshape

![スクリーンショット 2025-04-12 202947.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/caf173b8-be9f-44af-a3cc-a6d964cc35f9.png)

#### 固定した後のshape

![スクリーンショット 2025-04-12 204454.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/c96a57ba-74e5-4fcb-bc7b-7daeddb02f67.png)

----

<font color="blue">**本記事の内容により、複数入力とスカラー値の入力を持つ**
**Kaggle Model(TF1.x)をONNX形式へと変換できました！**</font>

なかなかKaggle ModelをONNXとして利用する方法や、
複数の入力を扱うKerasLayerの扱い方についての
情報が見当たらないため、まとめてみました。


**内容としては簡単ですので、是非お試しください!!**

----

#### 本記事が解決するQ＆A

https://github.com/onnx/tensorflow-onnx/issues/2388

https://discuss.ai.google.dev/t/how-can-i-convert-biggan-tensorflow-1-to-tensorflow-lite-format/79164/2

https://stackoverflow.com/questions/79536204/how-can-i-save-a-model-with-input-shape-1-none-none-3-with-none-fixed-to-25

https://stackoverflow.com/questions/79565183/how-can-i-convert-a-tensorflow-1-biggan-model-on-kaggle-to-tensorflow-lite-for/79569108#79569108

https://stackoverflow.com/questions/79535477/how-can-i-save-a-tflite-model-with-a-fixed-input-shape-after-using-interpreter-r

----

## ソースコード全文

以下に今回実装したソースコード全文を掲載します。

```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow_hub as hub
import tf2onnx
import onnx
import onnxruntime
from onnxruntime.datasets import get_example

######
### 指定したURLからモデルを取得してKeras形式のAIモデルを定義する
######
model_url = "https://www.kaggle.com/models/deepmind/biggan/TensorFlow1/128/2"

### AIモデルの入力を定義する
# truncation (scalar)
input_truncation = keras.Input(batch_shape=(), name='truncation')
# class (1, 1000)
input_y = keras.Input(shape=(1000, ), name='y')
# noize (1, 120)
input_z = keras.Input(shape=(120, ), name='z')

# Kaggleから取得したAIモデルをKerasLayerに変換する
hub_layer = hub.KerasLayer(
            model_url,
            trainable=False,
            signature="default", 
            signature_outputs_as_dict=True,
        )
        
# 複数の入力(multiple inputs)をKerasLayerに接続する(dictを利用する)
inputs = dict(y=input_y, z=input_z, truncation=input_truncation)
output = hub_layer(inputs)

### KerasのFunctional API機能を利用し
### 入力テンソルと出力テンソルを指定してモデルを定義する
model = tf.keras.models.Model(inputs=[input_truncation, input_y, input_z], outputs=[output])

### モデルをビルドする
model.build(([1], [1, 1000], [1, 120]))

### モデルのサマリーを表示する
model.summary()

### Kerasモデルを保存する
model.save("biggan-128")

### Keras形式のAIモデルをTensorFlow Lite形式へと変換して保存する
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("biggan-128.tflite", "wb").write(tflite_model)

### Keras形式のAIモデルをONNX形式へと変換して保存する
onnx_model, _ = tf2onnx.convert.from_keras(model, outputs_as_nchw=[model.outputs[0].name])
onnx.save(onnx_model, './biggan-128.onnx')

### 推論用にモデルをロードする
sess = onnxruntime.InferenceSession('./biggan-128.onnx', providers=['CPUExecutionProvider'])

### 入力テンソルと出力テンソルの名前とshapeを出力する
inputs_onnx_model = sess.get_inputs()
for i in range(len(inputs_onnx_model)):
    input_name = inputs_onnx_model[i].name
    print("Input name  :", input_name)
    input_shape = inputs_onnx_model[i].shape
    print("Input shape :", input_shape)
    input_type = inputs_onnx_model[i].type
    print("Input type  :", input_type)
    print()

outputs_onnx_model = sess.get_outputs()
for i in range(len(outputs_onnx_model)):
    output_name = outputs_onnx_model[i].name
    print("Output name  :", output_name)  
    output_shape = outputs_onnx_model[i].shape
    print("Output shape :", output_shape)
    output_type = outputs_onnx_model[i].type
    print("Output type  :", output_type)
    print()

######
### 入力テンソルを作成する
######
# trunction
# -->> スカラー値
truncation = np.full((), 0.2, dtype=np.float32)
# クラスを選択するOne-Hot-Enodingテンソル(1000クラス)
# -->> [batch_size, 1000]
y = np.zeros((1, 1000), dtype=np.float32)
y[0][207] = 1.0 # golden retriever
# バリエーションを与えるテンソル(120個の乱数)
# -->> [batch_size, 120]
z = np.random.randn(1, 120).astype(np.float32)

### 推論を実行する
output = sess.run(["keras_layer"],
                  {"truncation":truncation, "y": y, "z": z,},)

### 出力テンソルのshapeを[batch_size, 128, 128, 3]にする
output = np.reshape(output, [-1, 128, 128, 3])

### 推論完了
print("sess.run finished.")

######
### (128, 128, 3)のテンソルを画像として保存するメソッド
######
def save_tensor_as_image(image_tensor, title):
  n = 1
  image_size = 128
  w = (image_size * 6) // 320
  plt.figure(figsize=(w * n, w * 1.1))
  plt.imshow(image_tensor, aspect='equal')
  plt.axis('off')
  plt.title(title)
  plt.savefig("export.png")

### -1.0～+1.0の出力テンソルの値を0.0～1.0に丸める
output = (output * 0.5) + 0.5

### 出力テンソルを画像として保存する
save_tensor_as_image(output[0], 'Output Image')

```

### requirements.txt

```python
absl-py==2.2.1
astunparse==1.6.3
cachetools==5.5.2
certifi==2025.1.31
charset-normalizer==3.4.1
coloredlogs==15.0.1
contourpy==1.3.1
cycler==0.12.1
flatbuffers==1.12
fonttools==4.57.0
gast==0.4.0
google-auth==2.38.0
google-auth-oauthlib==0.4.6
google-pasta==0.2.0
grpcio==1.71.0
h5py==3.13.0
humanfriendly==10.0
idna==3.10
keras==2.9.0
Keras-Preprocessing==1.1.2
kiwisolver==1.4.8
libclang==18.1.1
Markdown==3.7
MarkupSafe==3.0.2
matplotlib==3.10.1
mpmath==1.3.0
numpy==1.23.5
oauthlib==3.2.2
onnx==1.14.1
onnx-graphsurgeon==0.5.7
onnx2tf==1.26.9
onnxruntime==1.21.0
opt_einsum==3.4.0
packaging==24.2
pillow==11.1.0
protobuf==3.20.3
psutil==7.0.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pyparsing==3.2.3
python-dateutil==2.9.0.post0
requests==2.32.3
requests-oauthlib==2.0.0
rsa==4.9
six==1.17.0
sng4onnx==1.0.4
sympy==1.13.3
tensorboard==2.9.0
tensorboard-data-server==0.6.1
tensorboard-plugin-wit==1.8.1
tensorflow==2.9.0
tensorflow-estimator==2.9.0
tensorflow-hub==0.16.1
tensorflow-io-gcs-filesystem==0.37.1
tensorflow-neuron==1.0
termcolor==3.0.0
tf-keras==2.14.1
tf2onnx==1.13.0
typing_extensions==4.13.0
urllib3==2.3.0
Werkzeug==3.1.3
wrapt==1.17.2
```



記事は以上となります。