Path: blob/master/site/ja/guide/keras/functional.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Functional API のガイド
セットアップ
前書き
Keras Functional API は、tf.keras.Sequential
API よりも柔軟なモデルの作成が可能で、非線形トポロジー、共有レイヤー、さらには複数の入力または出力を持つモデル処理することができます。
これは、ディープラーニングのモデルは通常、レイヤーの有向非巡回グラフ(DAG)であるという考えに基づいてます。要するに、Functional API はレイヤーのグラフを構築する方法です。
次のモデルを考察してみましょう。
これは 3つ のレイヤーを持つ単純なグラフです。Functional API を使用してモデルを構築するために、まずは入力ノードを作成することから始めます。
データの形状は、784次元のベクトルとして設定されます。各サンプルの形状のみを指定するため、バッチサイズは常に省略されます。
例えば、(32, 32, 3)
という形状の画像入力がある場合には、次を使用します。
返されるinputs
には、モデルに供給する入力データの形状とdtype
についての情報を含みます。形状は次のとおりです。
dtype は次のとおりです。
このinputs
オブジェクトのレイヤーを呼び出して、レイヤーのグラフに新しいノードを作成します。
「レイヤー呼び出し」アクションは、「入力」から作成したこのレイヤーまで矢印を描くようなものです。dense
レイヤーに入力を「渡して」、x
を取得します。
レイヤーのグラフにあと少しレイヤーを追加してみましょう。
この時点で、レイヤーのグラフの入力と出力を指定することにより、Model
を作成できます。
モデルの概要がどのようなものか、確認しましょう。
また、モデルをグラフとしてプロットすることも可能です。
そしてオプションで、プロットされたグラフに各レイヤーの入力形状と出力形状を表示します 。
この図とコードはほぼ同じです。コードバージョンでは、接続矢印は呼び出し演算に置き換えられています。
「レイヤーのグラフ」はディープラーニングモデルの直感的なメンタルイメージであり、Functional API はこのメンタルイメージを忠実に映すモデルを作成する方法です。
トレーニング、評価、推論
Functional API を使用して構築されたモデルの学習、評価、推論は、Sequential
モデルの場合とまったく同じように動作します。
Model
クラスにはトレーニングループ(fit()
メソッド)と評価ループ(evaluate()
メソッド)が組み込まれています。これらのループをカスタマイズすることで、教師あり学習を超えるトレーニングのルーチン(GAN など)を簡単に実装することができます。
ここでは、MNIST 画像データを読み込み、ベクトルに再形成し、(検証分割のパフォーマンスを監視しながら)データ上でモデルを当てはめ、その後、テストデータ上でモデルを評価します。
さらに詳しくはトレーニングと評価ガイドをご覧ください。
保存とシリアライズ
Functional API を使用して構築されたモデルの保存とシリアル化は、Sequential
のモデルと同じように動作します。Functional モデルを保存する標準的な方法は、model.save()
を呼び出して、モデル全体を単一のファイルとして保存します。モデルを作成したコードが使用できなくなったとしても、後でこのファイルから同じモデルを再作成することが可能です。
保存されたファイルには次を含みます。
モデルのアーキテクチャ
モデルの重み値(トレーニングの間に学習された値)
ある場合は、モデルトレーニング構成(
compile
に渡される構成)あれば、オプティマイザとその状態(中断した所からトレーニングを再開するため)
詳細は、モデルシリアライゼーションと保存ガイドをご覧ください。
レイヤー群の同じグラフを使用してマルチモデルを定義する
Functional API では、モデルはレイヤー群のグラフでそれらの出入力を指定することにより生成されます。それはレイヤー群の単一のグラフ が複数のモデルを生成するために使用できることを意味しています。
次の例では、レイヤー群の同じスタックを使用して 2 つのモデルのインスタンス化を行います。これらは画像入力を 16 次元ベクトルに変換するencoder
モデルと、トレーニングのためのエンドツーエンドautoencoder
モデルです。
ここでは、デコーディングアーキテクチャはエンコーディングアーキテクチャに対して厳密に対称的であるため、出力形状は入力形状(28, 28, 1)
と同じです。
Conv2D
レイヤーの反対はConv2DTranspose
レイヤーで、MaxPooling2D
レイヤーの反対はUpSampling2D
レイヤーです。
レイヤー同様、全てのモデルは呼び出し可能
任意のモデルをInput
上、あるいはもう一つのレイヤーの出力上で呼び出すことによって、それがレイヤーであるかのように扱うことができます。モデルを呼び出すことにより、単にモデルのアーキテクチャを再利用しているのではなく、その重みも再利用していることになります。
これを実際に見るために、エンコーダモデルとデコーダモデルを作成し、それらを 2 回の呼び出しに連鎖してオートエンコーダ―モデルを取得する、オートエンコーダの異なる例を次に示します。
ご覧のように、モデルはネストすることができ、(モデルはちょうどレイヤーのようなものであるため)サブモデルを含むことができます。モデル・ネスティングのための一般的なユースケースは アンサンブル です。モデルのセットを (それらの予測を平均する) 単一のモデルにアンサンブルする方法の例を次に示します。
複雑なグラフトポロジーを操作する
マルチ入力と出力を持つモデル
Functional API はマルチ入力と出力の操作を容易にします。これは Sequential
API では処理できません。
たとえば、顧客が発行したチケットを優先度別にランク付けし、正しい部門にルーティングするシステムを構築する場合、モデルには次の 3 つの入力があります。
チケットの件名 (テキスト入力)
チケットの本文(テキスト入力)
ユーザーが追加した任意のタグ(カテゴリ入力)
このモデルには 2 つの出力があります。
0 と 1 の間のプライオリティスコア(スカラーシグモイド出力)
チケットを処理すべき部門(部門集合に渡るソフトマックス出力)
このモデルは Functional API を使用すると数行で構築が可能です。
では、モデルをプロットします。
このモデルをコンパイルする時に、各出力に異なる損失を割り当てることができます。また、各損失に異なる重みを割り当てて、トレーニング損失全体へのそれらの寄与をモジュール化することも可能です。
出力レイヤーの名前が異なるため、対応するレイヤー名を使用して、損失と損失の重みを指定することも可能です。
入力とターゲットの NumPy 配列のリストを渡し、モデルをトレーニングします。
Dataset
オブジェクトで fit を呼び出す時、それは([title_data, body_data, tags_data], [priority_targets, dept_targets])
などのリストのタプル、または({'title': title_data, 'body': body_data, 'tags': tags_data}、{'priority': priority_targets, 'department': dept_targets})
などのディクショナリのタプルを yield する必要があります。
さらに詳しい説明については、トレーニングと評価ガイドをご覧ください。
トイ ResNet モデル
複数の入力と出力を持つモデルに加えて、Functional API では非線形接続トポロジー、つまりシーケンシャルに接続されていないレイヤーを持つモデルの操作を容易にします。これはSequential
API では扱うことができません。
これの一般的なユースケースは、残差接続です。これを実証するために、CIFAR10 向けのトイ ResNet モデルを構築してみましょう。
モデルをプロットします。
モデルをトレーニングします。
レイヤーを共有する
Functional API のもう 1 つの良い使い方は、共有レイヤーを使用するモデルです。共有レイヤーは、同じモデルで複数回再利用されるレイヤーインスタンスのことで、レイヤーグラフ内の複数のパスに対応するフィーチャを学習します。
共有レイヤーは、似たような空間からの入力(例えば、似た語彙を特徴とする 2 つの異なるテキスト)をエンコードするためにしばしば使用されます。これにより、それら異なる入力間での情報の共有を可能になり、より少ないデータでそのようなモデルをトレーニングすることが可能になります。与えられた単語が入力のいずれかに見られる場合、それは共有レイヤーを通過する全ての入力の処理に有用です。
Functional API でレイヤーを共有するには、同じレイヤーインスタンスを複数回呼び出します。例えば、2 つの異なるテキスト入力間で共有される Embedding
レイヤを以下に示します。
レイヤーのグラフのノードを抽出して再利用する
操作しているレイヤーのグラフは静的なデータ構造であるため、アクセスして検査をすることができます。そして、これが関数型モデルを画像としてプロットする方法でもあります。
これはまた、中間レイヤー(グラフ内の「ノード」)のアクティブ化にアクセスが可能で、他の場所で再利用できることを意味します。これは特徴抽出などに非常に便利です。
例を見てみましょう。これは ImageNet 上で事前トレーニングされた、重みを持つ VGG19 モデルです。
そしてこれらはグラフデータ構造をクエリして得られる、モデルの中間的なアクティブ化です。
これらの機能を使用して、中間レイヤーのアクティブ化の値を返す新しい特徴抽出モデルを作成します。
これは特に、ニューラルスタイル転送などのタスクに有用です。
カスタム層を使用して API を拡張する
tf.keras
には、例えば、次のような幅広い組み込みレイヤーが含まれています。
畳み込みレイヤー :
Conv1D
、Conv2D
、Conv3D
、Conv2DTranspose
Pooling レイヤー :
MaxPooling1D
、MaxPooling2D
、MaxPooling3D
、AveragePooling1D
RNN レイヤー :
GRU
、LSTM
、ConvLSTM2D
BatchNormalization
、Dropout
、Embedding
、など
必要なものが見つからない場合は、独自のレイヤーを作成して容易に API を拡張することができます。すべてのレイヤーはLayer
クラスをサブクラス化して実装します。
call
メソッドは、レイヤーが行う計算を指定します。build
メソッドは、レイヤーの重みを作成します(__init__
でも重みを作成できるため、これは単なるスタイル慣習です)。
レイヤーの新規作成に関する詳細については、カスタムレイヤーとモデルガイドをご覧ください。
tf.keras.layers.Dense
の基本的な実装を以下に示します。
カスタムレイヤーでシリアル化をサポートするには、レイヤーインスタンスのコンストラクタ引数を返す get_config
メソッドを定義します。
オプションで、config ディクショナリが与えられたレイヤーインスタンスを再作成する際に使用されたクラスメソッド from_config(cls, config)
を実装します。デフォルトの from_config
の実装は以下の通りです。
いつ Functional API を使用するか
新しいモデルを作成する場合または Model
クラスを直接サブクラス化する場合に、Keras Functional API を使用する必要があるのでしょうか? 一般的に Functional API はより高レベル、より容易かつ安全で、サブクラス化されたモデルがサポートしない多くの特徴を持っています。
ただし、レイヤーの有向非巡回グラフ(DAG)として容易に表現できないモデルを構築する場合には、モデルのサブクラス化がより大きな柔軟性を与えます。例えば、Functional API では Tree-RNN を実装できず、Model
を直接サブクラス化する必要があります。
Functional API とモデルのサブクラス化の違いに関する詳細については、TensorFlow 2.0 における Symbolic API と Imperative API とは?をご覧ください。
Functional API の長所 :
以下のプロパティは、(データ構造体でもある)Sequential モデルには真であり、(Python のバイトコードであり、データ構造体ではない)サブクラス化されたモデルには真ではありません。
低い冗長性
super(MyClass, self).__init__(...)
、def call(self, ...):
などがありません。
比較しよう :
サブクラス化されたバージョンと比べます。
連結グラフを定義しながらモデルを検証する
Functional API では、入力仕様(形状とdtype)が(Input
を使用して)あらかじめ作成されています。レイヤーを呼び出すたびに、レイヤーは渡された仕様が想定と一致しているかどうかをチェックし、一致していない場合には有用なエラーメッセージを表示します。
これにより、Functional API を使用して構築できるモデルは全て確実に実行されます。収束関連のデバッグ以外の全てのデバッグは、実行時ではなく、モデル構築中に静的に行われます。これはコンパイラの型チェックに類似しています。
関数型モデルはプロット可能かつ検査可能です
モデルをグラフとしてプロットすることが可能で、このグラフの中間ノードに簡単にアクセスすることができます。例えば(前の例で示したように)中間レイヤーのアクティブ化を抽出して再利用します。
関数型モデルは、シリアル化やクローン化が可能です
関数型モデルはコードの一部ではなくデータ構造であるため、安全なシリアル化が可能です。また、単一のファイルとして保存できるため、元のコードにアクセスすることなく全く同じモデルを再作成することができます。詳細はシリアル化と保存に関するガイドをご覧ください。
サブクラス化されたモデルをシリアライズするには、実装者がモデルレベルでget_config()
およびfrom_config()
メソッドを指定する必要があります。
Functional API の弱点 :
動的アーキテクチャをサポートしません
Functional API は、モデルをレイヤーの DAG として扱います。これはほとんどのディープラーニングアーキテクチャでは真ですが、必ずしも全てのアーキテクチャに該当するわけではありません。例えば、再帰的ネットワークや Tree RNN はこの想定に従わないため、Functional API では実装できません。
異なる API スタイルをうまく組み合わせる
Functional API とモデルのサブクラス化のいずれかを選択することは、モデルの 1 つのカテゴリに制限する二者択一ではありません。tf.keras
API 内の全てのモデルは、それらがSequential
モデルでも、関数型モデルでも、新規に書かれたサブクラス化されたモデルであっても、お互いに相互作用することができます。
関数型モデルやSequential
モデルは、サブクラス化されたモデルやレイヤーの一部として常に使用することができます。
次のいずれかのパターンに従ったcall
メソッドを実装していれば、Functional API で任意のサブクラス化されたレイヤーやモデルを使用することができます。
call(self, inputs, **kwargs)
-- ここでいうinputs
は、テンソルまたはテンソルのネストされた構造(テンソルのリストなど)であり、**kwargs
は非テンソルの引数(非 inputs)です。call(self, inputs, training=None, **kwargs)
-- このtraining
は、レイヤーがトレーニングモードと推論モードで振る舞うべきかどうかを示すブールです。call(self, inputs, mask=None, **kwargs)
-- このmask
は、ブールマスクテンソルです。(例えば RNNに便利です。)call(self, inputs, training=None, mask=None, **kwargs)
-- もちろん、マスキングとトレーニング固有の動作の両方を同時に持つことができます。
さらに、カスタムレイヤーやモデルでget_config
メソッドを実装する場合、作成した関数型モデルは依然としてシリアル化やクローン化が可能です。
新規に書かれたカスタム RNN を関数型モデルで使用する簡単な例を以下に示します。