📝

TensorFlow の使い方練習2:Functional API

2023/03/25に公開

はじめに

本記事は以下の記事の続きとなる。

https://zenn.dev/wsuzume/articles/6971d9449214ca

前回は Keras で Sequential モデルを使って単純なフィードフォワード NN を作るとこまでやったので、今度はもっと複雑なコネクションを持つ NN を構築する。

ネットワークが複雑になるとデータセットを用意するのも学習するのも面倒くさくなっていくのでどう記事にしたものか悩んだのだが、とりあえず出力の shape が期待したものになっていたり、shape だけ一致している擬似データに対して学習が実行できればネットワークは構築できたことにしよう

今回作成した Google Colab の notebook はこちら

Functional API

Keras で複雑なネットワークを作るには Functional API を使う。私が説明するまでもなくとても丁寧なガイドが書かれているのでとりあえずはもうそちらを読んでもろて。

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

Functional API では有向非巡回グラフが書け、モデルの保存なども簡単である。一方で再帰的な結合を持つネットワーク(RNN)や状態を持つモデルなどは Imperative APIModelのサブクラス化)を使わないと定義できないし、保存も若干面倒臭いらしい。

準備

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

擬似データ

入力を入れて出力が出てくればいいので、適当に 64 次元のデータ 100 個分を用意する。

X = np.random.normal(0, 1, (100, 64))

入力レイヤの定義

入ってくるデータの shape を指定する。1つのデータが 64 次元であれば以下のようになる。

inputs = keras.Input(shape=64)

print(inputs.dtype)
print(inputs.shape)
output
<dtype: 'float32'>
(None, 64)

画像であれば以下のようになる。

img_inputs = keras.Input(shape=(32, 32, 3), dtype=tf.uint8)

print(img_inputs.dtype)
print(img_inputs.shape)
output
<dtype: 'uint8'>
(None, 32, 32, 3)

モデル

モデルには inputsoutputs を指定できる。基本的に inputs には入力レイヤを与えればよく、outputs を構成するときにいろいろ細工すると複雑なモデルができるということになる。モデルには名前も指定できる(モデルはネストできるので名前をつけておくとあとで参照するときに便利)。

model = keras.Model(inputs=inputs, outputs=..., name='my_model')

何もしないモデル

入力をそのまま出力すれば何もしないモデル(恒等変換)になる。

inputs = keras.Input(shape=64)

outputs = inputs

model = keras.Model(inputs=inputs, outputs=outputs)
y = model(X)

print(y.shape)
output
(100, 64)

線形回帰

Dense レイヤを噛ますだけ。

inputs = keras.Input(shape=64)

outputs = layers.Dense(2)(inputs)

model = keras.Model(inputs=inputs, outputs=outputs)
y = model(X)

print(y.shape)
output
(100, 2)
keras.utils.plot_model(model, to_file="linear.png", show_shapes=True)

層を積み重ねてモデルを作る

何段階も噛ますだけ。

inputs = keras.Input(shape=64)
x = layers.Dense(32, activation='relu')(inputs)
x = layers.Dense(16, activation='relu')(x)
x = layers.Dense(8, activation='relu')(x)
x = layers.Dense(4, activation='relu')(x)
outputs = layers.Dense(2)(x)

model = keras.Model(inputs=inputs, outputs=outputs)
y = model(X)

print(y.shape)
output
(100, 2)
# モデルに関する情報
model.summary()
output
Model: "model_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_5 (InputLayer)        [(None, 64)]              0         
                                                                 
 dense_1 (Dense)             (None, 32)                2080      
                                                                 
 dense_2 (Dense)             (None, 16)                528       
                                                                 
 dense_3 (Dense)             (None, 8)                 136       
                                                                 
 dense_4 (Dense)             (None, 4)                 36        
                                                                 
 dense_5 (Dense)             (None, 2)                 10        
                                                                 
=================================================================
Total params: 2,790
Trainable params: 2,790
Non-trainable params: 0
_________________________________________________________________
keras.utils.plot_model(model, to_file="ffn.png", show_shapes=True)

スキップコネクション

ResNet とか U-Net で使われてる、層をまたいで別の層に出力を伝搬させるやつ。
以前のレイヤの出力を layers.add で足しこむ。

inputs = keras.Input(shape=64)
x = layers.Dense(32, activation='relu')(inputs)
y = layers.Dense(32, activation='relu')(x)
x = layers.add([x, y])
x = layers.Dense(16, activation='relu')(x)
outputs = layers.Dense(2)(x)

model = keras.Model(inputs=inputs, outputs=outputs)
y = model(X)

print(y.shape)
output
(100, 2)
keras.utils.plot_model(model, to_file="skip.png", show_shapes=True)

複数入力/複数出力

マルチモーダル AI とかに使えるだろう。

# 擬似データ
X1 = np.random.normal(0, 1, (100, 64))
X2 = np.random.normal(0, 1, (100, 64))
X3 = np.random.normal(0, 1, (100, 64))

Y1 = np.random.normal(0, 1, (100, 2))
Y2 = np.random.normal(0, 1, (100, 2))
inputs_1st = keras.Input(shape=64, name='inputs_1st')
inputs_2nd = keras.Input(shape=64, name='inputs_2nd')
inputs_3rd = keras.Input(shape=64, name='inputs_3rd')

x = layers.Dense(32, activation='relu')(inputs_1st)
x = layers.Dense(32, activation='relu')(x)

y = layers.Dense(32, activation='relu')(inputs_2nd)
y = layers.Dense(32, activation='relu')(y)

z = layers.Dense(32, activation='relu')(inputs_3rd)
z = layers.Dense(32, activation='relu')(z)

x = layers.concatenate([x, y, z])

x = layers.Dense(32, activation='relu')(x)
x = layers.Dense(32, activation='relu')(x)

v = layers.Dense(32, activation='relu')(x)
outputs_1st = layers.Dense(2, activation='relu', name='outputs_1st')(v)

w = layers.Dense(32, activation='relu')(x)
outputs_2nd = layers.Dense(2, activation='relu', name='outputs_2nd')(w)

model = keras.Model(
    inputs=[inputs_1st, inputs_2nd, inputs_3rd],
    outputs=[outputs_1st, outputs_2nd]
)
# オプティマイザと損失関数の設定
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "outputs_1st": keras.losses.MeanAbsoluteError(),
        "outputs_2nd": keras.losses.MeanSquaredError(),
    },
    loss_weights={
        "outputs_1st": 1.0,
        "outputs_2nd": 0.2,
    },
)
# 学習
model.fit(
    {"inputs_1st": X1, "inputs_2nd": X2, "inputs_3rd": X3},
    {"outputs_1st": Y1, "outputs_2nd": Y2},
    epochs=2,
    batch_size=32,
)
Y1_pred, Y2_pred = model({
    'inputs_1st': X1,
    'inputs_2nd': X2,
    'inputs_3rd': X3,
})
keras.utils.plot_model(model, to_file="multi.png", show_shapes=True)

Discussion