TPU での Keras と最新の convnets

1. 概要

このラボでは、Keras と Tensorflow 2 を使用して、独自の畳み込みニューラル ネットワークをゼロから構築して、トレーニングし、チューニングする方法を学びます。TPU のパワーにより、この処理を数分で実行できるようになりました。また、非常にシンプルな転移学習から、Squeezenet などの最新の畳み込みアーキテクチャまで、複数のアプローチについても学習します。このラボには、ニューラル ネットワークに関する理論的な説明が含まれており、ディープ ラーニングについて学習するデベロッパーにとって最適な出発点となります。

ディープ ラーニングの論文を読むのは難しく、混乱を招く可能性があります。最新の畳み込みニューラル ネットワーク アーキテクチャを実際に見てみましょう。

ca8cc21f6838eccc.png

学習内容

  • Keras と Tensor Processing Unit(TPU)を使用して、カスタムモデルをより迅速に構築する。
  • tf.data.Dataset API と TFRecord 形式を使用して、トレーニング データを効率的に読み込む。
  • 独自のモデルを構築する代わりに、転移学習を使用する。😈
  • Keras のシーケンシャル モデルと関数型モデルのスタイルを使用する。
  • ソフトマックス レイヤと交差エントロピー損失を使用して独自の Keras 分類子を構築する。
  • 畳み込みレイヤを適切に選択してモデルをファインチューニングする。
  • モジュールやグローバル平均プーリングなどの最新の convnet アーキテクチャのアイデアを検討する。
  • Squeezenet アーキテクチャを使用してシンプルな最新の convnet を構築する。

フィードバック

この Codelab で何か問題が見つかった場合は、お知らせください。フィードバックは GitHub の問題 [フィードバック リンク] から送信できます。

2. Google Colaboratory クイック スタート

このラボでは Google Collaboratory を使用するため、ユーザー側での設定は不要です。Chromebook から実行できます。以下のファイルを開き、セルを実行して Colab ノートブックについて理解を深めてください。

c3df49e90e5a654f.png Welcome to Colab.ipynb

TPU バックエンドを選択する

8832c6208c99687d.png

Colab のメニューで、[ランタイム] > [ランタイムのタイプを変更] を選択し、[TPU] を選択します。このコードラボでは、ハードウェア アクセラレータによるトレーニングをサポートする強力な TPU(Tensor Processing Unit)を使用します。ランタイムへの接続は、初回実行時に自動的に行われます。右上にある [接続] ボタンを使用することもできます。

ノートブックの実行

76d05caa8b4db6da.png

セルを 1 つずつ実行するには、セルをクリックして Shift+Enter キーを押します。[ランタイム] > [すべて実行] を選択して、ノートブック全体を実行することもできます。

目次

429f106990037ec4.png

すべてのノートブックに目次があります。左側の黒い矢印を使用して開くことができます。

非表示のセル

edc3dba45d26f12a.png

一部のセルにはタイトルのみが表示されます。これは Colab 固有のノートブック機能です。ダブルクリックすると内部のコードを確認できますが、通常はあまり興味深いものではありません。通常はサポート関数またはビジュアリゼーション関数。内部の関数を定義するには、これらのセルを実行する必要があります。

認証

cdd4b41413100543.png

承認済みアカウントで認証すれば、Colab からプライベート Google Cloud Storage バケットにアクセスできます。上記のコード スニペットは、認証プロセスをトリガーします。

3. [INFO] Tensor Processing Unit(TPU)とは

概要

f88cf6facfc70166.png

Keras で TPU 上でモデルをトレーニングするコード(TPU が使用できない場合は GPU または CPU にフォールバックします)。

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

今日は、TPU を使用して、インタラクティブな速度(トレーニング実行あたり数分)で花の分類子を構築して最適化します。

688858c21e3beff2.png

TPU を使用する理由

最新の GPU は、プログラマブルな「コア」を中心に構成されています。これは、3D レンダリング、ディープ ラーニング、物理シミュレーションなど、さまざまなタスクを処理できる非常に柔軟なアーキテクチャです。一方、TPU は、従来のベクトル プロセッサと専用の行列乗算ユニットを組み合わせたもので、ニューラル ネットワークなど、大規模な行列乗算が支配的なタスクに優れています。

8eb3e718b8e2ed08.png

図: 密ニューラル ネットワーク レイヤを行列乗算として表した図。8 枚の画像のバッチがニューラル ネットワークで一度に処理されます。1 行 x 列の乗算を実行して、画像内のすべてのピクセル値の加重和が実際に計算されていることを確認してください。畳み込みレイヤも行列乗算として表現できますが、少し複雑になります( 説明はこちらのセクション 1 をご覧ください)。

ハードウェア

MXU と VPU

TPU v2 コアは、行列乗算を実行する Matrix Multiply Unit(MXU)と、活性化やソフトマックスなどの他のすべてのタスク用の Vector Processing Unit(VPU)で構成されています。VPU は float32 と int32 の計算を処理します。一方、MXU は混合精度 16 ~ 32 ビットの浮動小数点形式で動作します。

7d68944718f76b18.png

混合精度浮動小数点と bfloat16

MXU は、bfloat16 入力と float32 出力を使用して行列乗算を計算します。中間累積は float32 精度で実行されます。

19c5fc432840c714.png

通常、ニューラル ネットワークのトレーニングは、浮動小数点精度の低下によって生じるノイズの影響を受けません。ノイズがオプティマイザーの収束に役立つ場合もあります。16 ビット浮動小数点精度は、従来、計算の高速化に使用されてきましたが、float16 形式と float32 形式では範囲が大きく異なります。精度を float32 から float16 に下げると、通常はオーバーフローとアンダーフローが発生します。ソリューションは存在しますが、通常は float16 を機能させるために追加の作業が必要です。

そのため、Google は TPU に bfloat16 形式を導入しました。bfloat16 は、float32 とまったく同じ指数ビットと範囲を持つ切り捨てられた float32 です。また、TPU は bfloat16 入力と float32 出力を使用して混合精度で行列乗算を計算するため、通常は、精度を下げてパフォーマンスを向上させるためにコードを変更する必要はありません。

シストリック アレイ

MXU は、データ要素がハードウェア コンピューティング ユニットの配列を流れる「シストリック アレイ」アーキテクチャを使用して、ハードウェアで行列乗算を実装します。(医学では、収縮期とは心臓の収縮と血流を指しますが、ここではデータの流れを指します)。

行列乗算の基本要素は、一方の行列の行と他方の行列の列の内積です(このセクションの上部の図を参照)。行列乗算 Y=X*W の場合、結果の 1 つの要素は次のようになります。

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

GPU では、このドット積を GPU の「コア」にプログラムし、利用可能なすべての「コア」で並列実行して、結果の行列のすべての値を一度に計算しようとします。結果の行列が 128x128 の大きさの場合、128x128=16K 個の「コア」が必要になりますが、これは通常不可能です。最大の GPU には約 4,000 個のコアがあります。一方、TPU は MXU のコンピューティング ユニットに最小限のハードウェア(bfloat16 x bfloat16 => float32 乗算累算器のみ)を使用します。これらは非常に小さいため、TPU は 128x128 MXU に 16 K 個を実装し、この行列乗算を一度に処理できます。

f1b283fc45966717.gif

図: MXU シストリック アレイ。コンピューティング要素は乗算アキュムレータです。1 つの行列の値が配列に読み込まれます(赤い点)。もう一方の行列の値は配列を通過します(灰色の点)。縦線は値を上に伝播します。水平線は部分和を伝播します。配列をデータが流れるときに、右側から行列乗算の結果が得られることを確認するのは、ユーザーの演習として残されています。

また、MXU でドット積が計算されている間、中間合計は隣接するコンピューティング単位間を流れるだけです。メモリやレジスタ ファイルに保存して取得する必要はありません。その結果、TPU シストリック アレイ アーキテクチャは、行列乗算の計算において、GPU よりも密度と電力の面で大きな利点があり、速度の面でも無視できない利点があります。

Cloud TPU

Google Cloud Platform で「Cloud TPU v2」をリクエストすると、PCI 接続の TPU ボードを備えた仮想マシン(VM)が取得されます。TPU ボードには、4 つのデュアルコア TPU チップがあります。各 TPU コアには、VPU(ベクトル処理ユニット)と 128x128 MXU(マトリックス乗算ユニット)があります。この「Cloud TPU」は通常、ネットワークを介してリクエストした VM に接続されます。全体像は次のようになります。

dfce5522ed644ece.png

図: ネットワーク接続された「Cloud TPU」アクセラレータを備えた VM。「Cloud TPU」自体は、PCI 接続の TPU ボードと、そのボードに搭載された 4 つのデュアルコア TPU チップを備えた VM で構成されています。

TPU Pod

Google のデータセンターでは、TPU はハイ パフォーマンス コンピューティング(HPC)相互接続に接続されており、1 つの非常に大きなアクセラレータとして認識されることがあります。Google はこれらを Pod と呼び、最大 512 個の TPU v2 コアまたは 2,048 個の TPU v3 コアを包含できます。

2ec1e0d341e7fc34.jpeg

図: TPU v3 Pod。HPC 相互接続を介して接続された TPU ボードとラック。

トレーニング中、勾配は all-reduce アルゴリズム(all-reduce の詳細はこちら)を使用して TPU コア間で交換されます。トレーニング対象のモデルは、大きなバッチサイズでトレーニングすることでハードウェアを活用できます。

d97b9cc5d40fdb1d.gif

図: Google TPU の 2 次元トーラス メッシュ HPC ネットワークで all-reduce アルゴリズムを使用してトレーニング中に勾配を同期する。

ソフトウェア

大規模なバッチサイズのトレーニング

TPU の理想的なバッチサイズは TPU コアあたり 128 個のデータ項目ですが、ハードウェアは TPU コアあたり 8 個のデータ項目から高い使用率を示します。1 つの Cloud TPU には 8 個のコアがあります。

この Codelab では、Keras API を使用します。Keras で指定するバッチは、TPU 全体のグローバル バッチサイズです。バッチは自動的に 8 つに分割され、TPU の 8 個のコアで実行されます。

da534407825f01e3.png

パフォーマンスに関するその他のヒントについては、TPU パフォーマンス ガイドをご覧ください。バッチサイズが非常に大きい場合は、一部のモデルで特別な注意が必要になることがあります。詳しくは、LARSOptimizer をご覧ください。

仕組み: XLA

Tensorflow プログラムは計算グラフを定義します。TPU は Python コードを直接実行するのではなく、TensorFlow プログラムで定義された計算グラフを実行します。内部では、XLA(高速線形代数コンパイラ)と呼ばれるコンパイラが、計算ノードの Tensorflow グラフを TPU マシンコードに変換します。このコンパイラは、コードとメモリ レイアウトに対して多くの高度な最適化も実行します。コンパイルは、作業が TPU に送信されると自動的に行われます。ビルドチェーンに XLA を明示的に含める必要はありません。

edce61112cd57972.png

図: TPU で実行するには、TensorFlow プログラムで定義された計算グラフをまず XLA(アクセラレータ線形代数コンパイラ)表現に変換し、次に XLA によって TPU マシンコードにコンパイルします。

Keras で TPU を使用する

Tensorflow 2.1 以降、Keras API を介して TPU がサポートされています。Keras のサポートは、TPU と TPU Pod で機能します。TPU、GPU、CPU で動作する例を次に示します。

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

このコード スニペットでは:

  • TPUClusterResolver().connect() は、ネットワーク上の TPU を検出します。ほとんどの Google Cloud システム(AI Platform ジョブ、Colaboratory、Kubeflow、ctpu up ユーティリティで作成された Deep Learning VM)でパラメータなしで動作します。これらのシステムは、TPU_NAME 環境変数のおかげで TPU の場所を認識しています。TPU を手動で作成する場合は、使用する VM で TPU_NAME 環境変数を設定するか、明示的なパラメータを指定して TPUClusterResolver を呼び出します。TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy は、分布と「all-reduce」勾配同期アルゴリズムを実装する部分です。
  • この戦略はスコープを通じて適用されます。モデルは strategy scope() 内で定義する必要があります。
  • tpu_model.fit 関数は、TPU トレーニングの入力として tf.data.Dataset オブジェクトを想定しています。

一般的な TPU の移植タスク

  • Tensorflow モデルでデータを読み込む方法は多数ありますが、TPU では tf.data.Dataset API を使用する必要があります。
  • TPU は非常に高速であるため、TPU で実行すると、データの取り込みがボトルネックになることがよくあります。TPU パフォーマンス ガイドには、データ ボトルネックを検出するために使用できるツールや、その他のパフォーマンスに関するヒントが記載されています。
  • int8 または int16 の数値は int32 として扱われます。TPU には、32 ビット未満で動作する整数ハードウェアはありません。
  • 一部の TensorFlow オペレーションはサポートされていません。リストはこちらをご覧ください。この制限はトレーニング コード(モデルのフォワード パスとバックワード パス)にのみ適用されます。データ入力パイプラインでは、すべての TensorFlow オペレーションを CPU で実行できるため、引き続き使用できます。
  • TPU では tf.py_func はサポートされていません。

4. データを読み込んでいます

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

ここでは、花の画像データセットを使用します。目標は、それらを 5 種類の花に分類する方法を学習することです。データの読み込みは tf.data.Dataset API を使用して行われます。まず、API について理解しましょう。

実践的

次のノートブックを開き、セルを実行(Shift+Enter)して、「WORK REQUIRED」ラベルが表示されている手順に沿って操作してください。

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

その他の情報

「flowers」データセットについて

データセットは 5 つのフォルダに整理されています。各フォルダには 1 種類の花が含まれています。フォルダの名前は、sunflowers、daisy、dandelion、tulips、roses です。データは Google Cloud Storage の公開バケットでホストされています。抜粋:

gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg

tf.data.Dataset を使用する理由

Keras と TensorFlow は、すべてのトレーニング関数と評価関数でデータセットを受け入れます。データセットにデータを読み込むと、API はニューラル ネットワークのトレーニング データに役立つ一般的な機能をすべて提供します。

dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training

パフォーマンスのヒントとデータセットのベスト プラクティスについては、こちらの記事をご覧ください。リファレンス ドキュメントはこちらをご覧ください。

tf.data.Dataset の基本

通常、データは複数のファイル(ここでは画像)で提供されます。ファイル名のデータセットは、次の呼び出しで作成できます。

filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.

次に、各ファイル名に関数を「マッピング」します。通常、この関数はファイルを読み込んでデコードし、メモリ内の実際のデータに変換します。

def decode_jpeg(filename):
  bits = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  return image

image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)

データセットを反復処理するには:

for data in my_dataset:
  print(data)

タプルのデータセット

教師あり学習では、通常、トレーニング データセットはトレーニング データと正解のペアで構成されます。これを可能にするため、デコード関数はタプルを返すことができます。タプルのデータセットが作成され、そのデータセットを反復処理するとタプルが返されます。返される値は、モデルで使用できる Tensorflow テンソルです。.numpy() を呼び出すと、未加工の値を確認できます。

def decode_jpeg_and_label(filename):
  bits = tf.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  label = ... # extract flower name from folder name
  return image, label

image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs 

for image, label in dataset:
  print(image.numpy().shape, label.numpy())

結論:画像を 1 つずつ読み込むのは遅い

このデータセットを反復処理すると、1 秒あたり 1 ~ 2 枚の画像を読み込むことができることがわかります。遅すぎます。トレーニングに使用するハードウェア アクセラレータは、このレートの何倍ものレートを維持できます。次のセクションでは、この方法について説明します。

ソリューション

ソリューション ノートブックは次のとおりです。行き詰まった場合は、こちらをご利用ください。

c3df49e90e5a654f.png Fun with tf.data.Dataset (solution).ipynb

学習した内容

  • 🤔 tf.data.Dataset.list_files
  • 🤔 tf.data.Dataset.map
  • 🤔 タプルのデータセット
  • 😀 データセットの反復処理

このチェックリストを頭の中で確認してください。

5. データを高速で読み込む

このラボで使用する Tensor Processing Unit(TPU)ハードウェア アクセラレータは非常に高速です。課題は、TPU を十分に活用できる速度でデータをフィードすることです。Google Cloud Storage(GCS)は非常に高いスループットを維持できますが、すべてのクラウド ストレージ システムと同様に、接続の開始時にネットワークの往復コストが発生します。そのため、データを数千もの個別のファイルとして保存することは理想的ではありません。これらのファイルをより少ない数のファイルにバッチ処理し、tf.data.Dataset の機能を使用して複数のファイルから並行して読み取ります。

Read-through

画像ファイルを読み込み、共通のサイズに変更してから 16 個の TFRecord ファイルに保存するコードは、次のノートブックにあります。お急ぎのところ恐縮ですが、ご一読ください。この後の Codelab では、適切な TFRecord 形式のデータが提供されるため、実行する必要はありません。

c3df49e90e5a654f.png Flower pictures to TFRecords.ipynb

最適な GCS スループットを実現する理想的なデータ レイアウト

TFRecord ファイル形式

Tensorflow でデータを保存する際に推奨されるファイル形式は、protobuf ベースの TFRecord 形式です。他のシリアル化形式も使用できますが、次のように記述することで、TFRecord ファイルからデータセットを直接読み込むことができます。

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

最適なパフォーマンスを得るには、次のより複雑なコードを使用して、複数の TFRecord ファイルから一度に読み取ることをおすすめします。このコードは、N 個のファイルから並列で読み取り、読み取り速度を優先してデータの順序を無視します。

AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

TFRecord クイック リファレンス

TFRecord には、バイト文字列(バイトのリスト)、64 ビットの整数、32 ビットの浮動小数点数の 3 種類のデータを格納できます。常にリストとして保存されます。単一のデータ要素はサイズ 1 のリストになります。次のヘルパー関数を使用して、データを TFRecord に保存できます。

バイト文字列の書き込み

# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))

整数の書き込み

def _int_feature(list_of_ints): # int64
  return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))

浮動小数点数の書き込み

def _float_feature(list_of_floats): # float32
  return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))

上記のヘルパーを使用して TFRecord を書き込む

# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
  feature = {
    "image": _bytestring_feature([my_img_bytes]), # one image in the list
    "class": _int_feature([my_class]),            # one class in the list
    "size": _int_feature([my_height, my_width]),  # fixed length (2) list of ints
    "float_data": _float_feature(my_floats)       # variable length  list of floats
  }
  tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
  out_file.write(tf_record.SerializeToString())

TFRecord からデータを読み取るには、まず保存したレコードのレイアウトを宣言する必要があります。宣言では、名前付きフィールドに固定長リストまたは可変長リストとしてアクセスできます。

TFRecord からの読み取り

def read_tfrecord(data):
  features = {
    # tf.string = byte string (not text string)
    "image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
    "class": tf.io.FixedLenFeature([], tf.int64),  # shape [] means scalar, i.e. a single item
    "size": tf.io.FixedLenFeature([2], tf.int64),  # two integers
    "float_data": tf.io.VarLenFeature(tf.float32)  # a variable number of floats
  }

  # decode the TFRecord
  tf_record = tf.io.parse_single_example(data, features)

  # FixedLenFeature fields are now ready to use
  sz = tf_record['size']

  # Typical code for decoding compressed images
  image = tf.io.decode_jpeg(tf_record['image'], channels=3)

  # VarLenFeature fields require additional sparse.to_dense decoding
  float_data = tf.sparse.to_dense(tf_record['float_data'])

  return image, sz, float_data

# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)

便利なコード スニペット:

単一のデータ要素の読み取り

tf.io.FixedLenFeature([], tf.string)   # for one byte string
tf.io.FixedLenFeature([], tf.int64)    # for one int
tf.io.FixedLenFeature([], tf.float32)  # for one float

要素の固定サイズ リストの読み取り

tf.io.FixedLenFeature([N], tf.string)   # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64)    # list of N ints
tf.io.FixedLenFeature([N], tf.float32)  # list of N floats

可変数のデータアイテムを読み取る

tf.io.VarLenFeature(tf.string)   # list of byte strings
tf.io.VarLenFeature(tf.int64)    # list of ints
tf.io.VarLenFeature(tf.float32)  # list of floats

VarLenFeature はスパース ベクトルを返します。TFRecord のデコード後に、追加の手順が必要です。

dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])

TFRecords にオプションのフィールドを含めることもできます。フィールドの読み取り時にデフォルト値を指定すると、フィールドが存在しない場合にエラーではなくデフォルト値が返されます。

tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional

学習した内容

  • 🤔 GCS からの高速アクセス用にデータファイルをシャーディングする
  • 😓 TFRecords の書き込み方法。(もう構文を忘れてしまいましたか?(このページをチートシートとしてブックマークしておくと便利です)
  • 🤔 TFRecordDataset を使用して TFRecord から Dataset を読み込む

このチェックリストを頭の中で確認してください。

6. [INFO] ニューラル ネットワーク分類器 101

概要

次の段落の太字の用語をすべてご存知の場合は、次の演習に進んでください。ディープ ラーニングを始めたばかりの方は、ぜひこのまま読み進めてください。

レイヤのシーケンスとして構築されたモデルの場合、Keras は Sequential API を提供します。たとえば、3 つの密なレイヤを使用する画像分類器は、Keras で次のように記述できます。

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

密結合ニューラル ネットワーク

これは、画像を分類するための最も単純なニューラル ネットワークです。レイヤに配置された「ニューロン」で構成されています。最初のレイヤは入力データを処理し、その出力を他のレイヤに渡します。各ニューロンが前のレイヤのすべてのニューロンに接続されているため、「密」と呼ばれます。

c21bae6dade487bc.png

このようなネットワークに画像をフィードするには、すべてのピクセルの RGB 値を長いベクトルに平坦化して入力として使用します。画像認識に最適な手法ではありませんが、後で改善します。

ニューロン、アクティベーション、RELU

「ニューロン」は、すべての入力の加重和を計算し、「バイアス」と呼ばれる値を加算して、結果を「活性化関数」と呼ばれる関数に渡します。重みとバイアスは最初は不明です。これらはランダムに初期化され、既知のデータでニューラル ネットワークをトレーニングすることで「学習」されます。

644f4213a4ee70e5.png

最も一般的な活性化関数は、Rectified Linear Unit の略である RELU と呼ばれます。上のグラフでわかるように、非常にシンプルな関数です。

Softmax アクティベーション

上記のネットワークは、花を 5 つのカテゴリ(バラ、チューリップ、タンポポ、デイジー、ヒマワリ)に分類するため、5 つのニューロン レイヤで終わります。中間レイヤのニューロンは、従来の RELU アクティベーション関数を使用してアクティブ化されます。ただし、最後のレイヤでは、この花がバラである確率、チューリップである確率などを表す 0 ~ 1 の数値を計算します。これには、「ソフトマックス」と呼ばれる活性化関数を使用します。

ベクトルに softmax を適用するには、各要素の指数関数を取り、通常は L1 ノルム(絶対値の合計)を使用してベクトルを正規化します。これにより、値の合計が 1 になり、確率として解釈できます。

ef0d98c0952c262d.png d51252f75894479e.gif

交差エントロピー損失

ニューラル ネットワークが入力画像から予測を生成するようになったので、その予測の精度を測定する必要があります。つまり、ネットワークが示す内容と正解(ラベルと呼ばれることが多い)との間の距離を測定します。データセット内のすべての画像に正しいラベルが付いていることを思い出してください。

どの距離でも機能しますが、分類問題では「交差エントロピー距離」が最も効果的です。これをエラー関数または損失関数と呼びます。

7bdf8753d20617fb.png

勾配降下法

ニューラル ネットワークの「トレーニング」とは、実際には、トレーニング画像とラベルを使用して、交差エントロピー損失関数を最小限に抑えるように重みとバイアスを調整することを意味します。仕組みは次のとおりです。

クロスエントロピーは、重み、バイアス、トレーニング画像のピクセル、既知のクラスの関数です。

すべての重みとすべてのバイアスに対してクロスエントロピーの偏導関数を計算すると、特定の画像、ラベル、重みとバイアスの現在の値に対して計算された「勾配」が得られます。重みとバイアスは数百万個になる可能性があるため、勾配の計算は大変な作業になります。幸いなことに、Tensorflow がこの処理を代わりに行ってくれます。グラデーションの数学的特性は、「上」を指すことです。交差エントロピーが低い方向に進みたいので、反対方向に進みます。重みとバイアスは、勾配の分数で更新します。次に、トレーニング ループで、次のバッチのトレーニング画像とラベルを使用して同じ処理を繰り返し行います。この最小値が 1 つである保証はありませんが、この最小値に収束することを期待します。

gradient descent2.png

ミニバッチ処理とモメンタム

1 つのサンプル画像でグラデーションを計算し、重みとバイアスをすぐに更新することもできますが、たとえば 128 枚の画像のバッチでグラデーションを計算すると、さまざまなサンプル画像によって課せられる制約をより適切に表すグラデーションが得られるため、解に収束する可能性が高くなります。ミニバッチのサイズは調整可能なパラメータです。

この手法は「確率的勾配降下法」と呼ばれることもありますが、バッチ処理を行うことで、より大きな行列を扱うことになり、GPU や TPU での最適化が容易になるという実用的なメリットもあります。

ただし、収束はややカオスになる可能性があり、グラデーション ベクトルがすべてゼロの場合には停止することもあります。これは、最小値が見つかったことを意味しますか?必ずしも違反警告を受けるとは限りません。グラデーション コンポーネントは、最小値または最大値でゼロになることがあります。数百万個の要素を持つ勾配ベクトルで、すべての要素がゼロの場合、すべてのゼロが最小値に対応し、最大値に対応するゼロがない確率はかなり小さくなります。多次元空間では鞍点が非常に一般的であり、そこで停止したくありません。

52e824fe4716c4a0.png

イラスト: 鞍点。グラデーションは 0 ですが、すべての方向で最小値ではありません。(画像帰属 Wikimedia: By Nicoguaro - Own work, CC BY 3.0

解決策は、最適化アルゴリズムに運動量を追加して、鞍点を停止せずに通過できるようにすることです。

用語集

バッチまたはミニバッチ: トレーニングは常にトレーニング データとラベルのバッチで実行されます。これにより、アルゴリズムの収束が促進されます。「バッチ」ディメンションは通常、データテンソルの最初のディメンションです。たとえば、シェイプ [100, 192, 192, 3] のテンソルには、192x192 ピクセルの画像が 100 個含まれており、各ピクセルには 3 つの値(RGB)があります。

交差エントロピー損失: 分類子でよく使用される特殊な損失関数。

密結合層: 各ニューロンが前のレイヤのすべてのニューロンに接続されているニューロンのレイヤ。

特徴: ニューラル ネットワークの入力は「特徴」と呼ばれることもあります。優れた予測を得るために、データセットのどの部分(または部分の組み合わせ)をニューラル ネットワークにフィードするかを判断する技術は、「特徴量エンジニアリング」と呼ばれます。

ラベル: 教師あり分類問題における「クラス」または正解の別の名前

学習率: トレーニング ループの各イテレーションで重みとバイアスが更新される勾配の割合。

ロジット: 活性化関数が適用される前のニューロンのレイヤの出力は「ロジット」と呼ばれます。この用語は、かつて最も一般的な活性化関数であった「ロジスティック関数」(「シグモイド関数」とも呼ばれます)に由来します。「Neuron outputs before logistic function」が「logits」に短縮されました。

loss: ニューラル ネットワークの出力と正解を比較する誤差関数

ニューロン: 入力の加重和を計算し、バイアスを追加して、活性化関数を通じて結果をフィードします。

ワンホット エンコード: 5 つのクラスのうちのクラス 3 は、5 つの要素のベクトルとしてエンコードされます。3 番目の要素を除いてすべてゼロです。

relu: 正規化線形ユニット。ニューロンでよく使用される活性化関数。

sigmoid: 以前はよく使用されていた別の活性化関数で、特殊なケースでは今でも有用です。

softmax: ベクトルに作用し、最大成分と他のすべての成分の差を大きくする特別な活性化関数。また、確率のベクトルとして解釈できるように、ベクトルの合計が 1 になるように正規化します。分類子の最後のステップとして使用されます。

tensor: 「テンソル」は行列のようなものですが、任意の数の次元を持つことができます。1 次元テンソルはベクトルです。2 次元テンソルは行列です。3 次元、4 次元、5 次元以上のテンソルを使用することもできます。

7. 転移学習

画像分類の問題では、密レイヤだけでは不十分な可能性があります。畳み込みレイヤと、それらを配置するさまざまな方法について学習する必要があります。

しかし、ショートカットも可能です。完全にトレーニングされた畳み込みニューラル ネットワークをダウンロードできます。最後のレイヤであるソフトマックス分類ヘッドを切り取って、独自のレイヤに置き換えることができます。トレーニング済みの重みとバイアスはそのまま残り、追加した softmax レイヤのみを再トレーニングします。この手法は転移学習と呼ばれ、ニューラル ネットワークが事前トレーニングされたデータセットがユーザーのデータセットと「十分に類似」していれば、驚くほど効果を発揮します。

ハンズオン

次のノートブックを開き、セルを実行(Shift+Enter)して、「WORK REQUIRED」ラベルが表示されている手順に沿って操作してください。

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

その他の情報

転移学習では、トップレベルの研究者が開発した高度な畳み込みニューラル ネットワーク アーキテクチャと、画像の大規模なデータセットでの事前トレーニングの両方のメリットが得られます。今回のケースでは、ImageNet でトレーニングされたネットワークから転移学習を行います。ImageNet は、花に十分に近い多くの植物や屋外のシーンを含む画像のデータベースです。

b8fc1efd2001f072.png

図: すでにトレーニング済みの複雑な畳み込みニューラル ネットワークをブラックボックスとして使用し、分類ヘッドのみを再トレーニングする。これが転移学習です。畳み込みレイヤの複雑な配置がどのように機能するかについては、後で説明します。今のところ、それは他の誰かの問題です。

Keras での転移学習

Keras では、tf.keras.applications.* コレクションから事前トレーニング済みモデルをインスタンス化できます。たとえば、MobileNet V2 は、サイズが妥当な範囲に収まる優れた畳み込みアーキテクチャです。include_top=False を選択すると、最終的な softmax レイヤーのない事前トレーニング済みモデルを取得し、独自のレイヤーを追加できます。

pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False

model = tf.keras.Sequential([
    pretrained_model,
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(5, activation='softmax')
])

pretrained_model.trainable = False の設定にも注意してください。事前トレーニング済みモデルの重みとバイアスを固定して、softmax レイヤのみをトレーニングします。通常、この方法では比較的少ない重みが使用され、大規模なデータセットを必要とせずに迅速に実行できます。ただし、データが大量にある場合は、転移学習は pretrained_model.trainable = True でさらに効果を発揮します。事前トレーニング済みの重みは優れた初期値を提供し、トレーニングによって問題に合わせて調整できます。

最後に、密な softmax レイヤの前に Flatten() レイヤが挿入されていることに注目してください。密なレイヤはデータのフラットなベクトルで動作しますが、事前トレーニング済みモデルがそれを返すかどうかはわかりません。そのため、フラット化する必要があります。次の章では、畳み込みアーキテクチャについて詳しく説明する際に、畳み込みレイヤから返されるデータ形式について説明します。

このアプローチでは、75% に近い精度が得られるはずです。

ソリューション

ソリューション ノートブックは次のとおりです。行き詰まった場合は、こちらをご利用ください。

c3df49e90e5a654f.png Keras Flowers transfer learning (solution).ipynb

学習した内容

  • 🤔 Keras で分類子を作成する方法
  • 🤓: softmax の最終レイヤと交差エントロピー損失で構成
  • 転移学習
  • 🤔 最初のモデルをトレーニングする
  • 🧐 トレーニング中の損失と精度を追跡する

このチェックリストを頭の中で確認してください。

8. [INFO] 畳み込みニューラル ネットワーク

概要

次の段落の太字の用語をすべてご存知の場合は、次の演習に進んでください。畳み込みニューラル ネットワークを初めて使用する場合は、このままお読みください。

convolutional.gif

図: 4x4x3=48 個の学習可能な重みで構成される 2 つの連続したフィルタで画像をフィルタリングする。

Keras での単純な畳み込みニューラル ネットワークは次のようになります。

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

688858c21e3beff2.png

畳み込みニューラル ネットワーク 101

畳み込みネットワークのレイヤでは、1 つの「ニューロン」が、画像の小さな領域のみで、すぐ上のピクセルの加重和を計算します。通常の密結合レイヤのニューロンと同様に、バイアスを追加して、活性化関数を介して合計を渡します。このオペレーションは、同じ重みを使用して画像全体で繰り返されます。密結合レイヤでは、各ニューロンに独自の重みがありました。ここでは、重みの単一の「パッチ」が画像上を双方向にスライドします(「畳み込み」)。出力には、画像内のピクセル数と同じ数の値が含まれます(ただし、エッジでパディングが必要になります)。これは、4×4×3=48 個の重みを持つフィルタを使用するフィルタリング オペレーションです。

ただし、48 個の重みでは不十分です。自由度を高めるために、新しい重みのセットで同じ操作を繰り返します。これにより、新しい一連のフィルタ出力が生成されます。入力画像の R、G、B チャネルとの類似性から、出力の「チャネル」と呼びます。

Screen Shot 2016-07-29 at 16.02.37.png

2 つ以上の重みのセットは、新しいディメンションを追加することで 1 つのテンソルとして合計できます。これにより、畳み込みレイヤの重みテンソルの一般的な形状が得られます。入力チャネルと出力チャネルの数はパラメータであるため、畳み込みレイヤのスタックとチェーンを開始できます。

d1b557707bcd1cb9.png

図: 畳み込みニューラル ネットワークがデータの「キューブ」を別のデータの「キューブ」に変換する。

ストライド畳み込み、最大プーリング

ストライド 2 または 3 で畳み込みを行うことで、結果のデータキューブを水平方向に縮小することもできます。これを行うには、次の 2 つの一般的な方法があります。

  • ストライド畳み込み: 上記のスライディング フィルタ。ただし、ストライドが 1 より大きい
  • 最大プーリング: MAX オペレーションを適用するスライディング ウィンドウ(通常は 2x2 パッチで、2 ピクセルごとに繰り返されます)

2b2d4263bb8470b.gif

図: コンピューティング ウィンドウを 3 ピクセル スライドさせると、出力値が少なくなります。ストライド畳み込みまたは最大プーリング(ストライド 2 でスライドする 2x2 ウィンドウの最大値)は、水平方向にデータキューブを縮小する方法です。

Convolutional classifier

最後に、最後のデータキューブをフラット化し、密な softmax 活性化レイヤを介してフィードすることで、分類ヘッドを接続します。一般的な畳み込み分類器は次のようになります。

4a61aaffb6cba3d1.png

図: 畳み込みレイヤと softmax レイヤを使用する画像分類器。3x3 フィルタと 1x1 フィルタを使用します。maxpool レイヤは、2x2 のデータポイントのグループの最大値を取得します。分類ヘッドは、ソフトマックス活性化関数を持つ密結合レイヤで実装されます。

Keras で

上記の畳み込みスタックは、Keras で次のように記述できます。

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

9. カスタム convnet

ハンズオン

畳み込みニューラル ネットワークをゼロから構築してトレーニングしてみましょう。TPU を使用すると、非常に高速に反復処理を行うことができます。次のノートブックを開き、セルを実行(Shift+Enter)して、「WORK REQUIRED」ラベルが表示されている手順に沿って操作してください。

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

目標は、転移学習モデルの 75% の精度を上回ることです。このモデルは、数百万枚の画像のデータセットで事前トレーニングされているという利点がありましたが、ここでは 3, 670 枚の画像しかありません。少なくとも同額にしていただけませんか?

その他の情報

レイヤの数とサイズはどのくらいですか?

レイヤサイズの選択は、科学というよりアートです。パラメータ(重みとバイアス)の数が少なすぎたり多すぎたりしないように、適切なバランスを見つける必要があります。重みが少なすぎると、ニューラル ネットワークで花の形の複雑さを表現できません。多すぎると、「過学習」になりやすく、トレーニング画像に特化して一般化できなくなる可能性があります。パラメータが多いと、モデルのトレーニングも遅くなります。Keras では、model.summary() 関数はモデルの構造とパラメータ数を表示します。

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 192, 192, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 192, 192, 30)      4350      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 96, 96, 60)        16260     
_________________________________________________________________
 ... 
_________________________________________________________________
global_average_pooling2d (Gl (None, 130)               0         
_________________________________________________________________
dense (Dense)                (None, 90)                11790     
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 455       
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________

ヒント:

  • 複数のレイヤがあることが、ディープ ニューラル ネットワークを効果的にする理由です。この単純な花の認識問題では、5 ~ 10 個のレイヤが適切です。
  • 小さなフィルタを使用します。通常、3x3 フィルタはどこでも有効です。
  • 1x1 フィルタも使用でき、安価です。実際には何もフィルタリングせず、チャネルの線形結合を計算します。実際のフィルタと交互に適用します。(「1x1 畳み込み」については、次のセクションで詳しく説明します)。
  • このような分類問題では、最大プーリング レイヤ(またはストライド > 1 の畳み込み)を使用して頻繁にダウンサンプリングします。花の位置は重要ではなく、バラかタンポポかだけが重要なので、x と y の情報を失っても問題なく、小さな領域をフィルタリングする方がコストを抑えられます。
  • 通常、フィルタの数はネットワークの最後にあるクラスの数とほぼ同じになります(理由は、以下の「グローバル平均プーリング」のトリックをご覧ください)。数百のクラスに分類する場合は、連続するレイヤでフィルタ数を段階的に増やします。5 つのクラスを含む花データセットの場合、5 つのフィルタのみでフィルタリングすることはできません。ほとんどのレイヤで同じフィルタ数(32 など)を使用し、最後に向かって減らしていくことができます。
  • 最後の Dense レイヤはコストが高くなります。すべての畳み込みレイヤの合計よりも多くの重みを持つことができます。たとえば、最後のデータキューブから 24x24x10 のデータポイントという非常に妥当な出力が得られたとしても、100 個のニューロンを持つ密結合レイヤには 24x24x10x100=576,000 個の重みが必要になります。慎重に検討するか、グローバル平均プーリングを試してください(下記を参照)。

グローバル平均プーリング

畳み込みニューラル ネットワークの最後に高価な密結合レイヤを使用する代わりに、入力データ「キューブ」をクラスの数だけ分割し、それらの値を平均して、ソフトマックス アクティベーション関数に渡すことができます。この分類ヘッドの構築方法では、重みは 0 になります。Keras では、構文は tf.keras.layers.GlobalAveragePooling2D(). です。

93240029f59df7c2.png

ソリューション

ソリューション ノートブックは次のとおりです。行き詰まった場合は、こちらをご利用ください。

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

学習した内容

  • 🤔 畳み込みレイヤを試した
  • 🤓 最大プーリング、ストライド、グローバル平均プーリングなどを試した。
  • 😀 TPU で実際のモデルを迅速に反復処理した

このチェックリストを頭の中で確認してください。

10. [INFO] 最新の畳み込みアーキテクチャ

概要

7968830b57b708c0.png

図: 畳み込み「モジュール」。この時点で最適な方法はどれですか?最大プーリング レイヤの後に 1x1 畳み込みレイヤが続くのか、それとも別のレイヤの組み合わせなのか?すべてを試して結果を連結し、ネットワークに判断させます。右: このようなモジュールを使用する「 inception」畳み込みアーキテクチャ。

Keras でデータフローが分岐するモデルを作成するには、「関数型」モデル スタイルを使用する必要があります。以下に例を示します。

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

その他の安易な方法

3x3 の小さなフィルタ

40a7b15fb7dbe75c.png

この図では、連続する 2 つの 3x3 フィルタの結果を示しています。結果に寄与したデータポイントをトレースバックしてみましょう。これらの 2 つの連続した 3x3 フィルタは、5x5 領域の組み合わせを計算します。5x5 フィルタで計算される組み合わせとまったく同じではありませんが、2 つの連続する 3x3 フィルタは 1 つの 5x5 フィルタよりも安価であるため、試してみる価値はあります。

1x1 畳み込みとは?

fd7cac16f8ecb423.png

数学的には、「1x1」畳み込みは定数による乗算であり、あまり有用な概念ではありません。ただし、畳み込みニューラル ネットワークでは、フィルタは 2D 画像だけでなくデータキューブにも適用されることを覚えておいてください。したがって、「1x1」フィルタは 1x1 のデータ列の加重合計を計算します(図を参照)。このフィルタをデータ上でスライドさせると、入力のチャネルの線形結合が得られます。これは実際に役立ちます。チャネルを個々のフィルタリング オペレーションの結果と考えると、たとえば「尖った耳」のフィルタ、「ひげ」のフィルタ、「縦長の目」のフィルタがあるとします。この場合、「1x1」の畳み込みレイヤは、これらの特徴の複数の可能な線形結合を計算します。これは、「猫」を探す場合に役立ちます。また、1x1 レイヤは重みの使用量が少なくなります。

11. Squeezenet

これらのアイデアを組み合わせる簡単な方法が、「Squeezenet」論文で紹介されています。著者は、1x1 と 3x3 の畳み込みレイヤのみを使用する非常にシンプルな畳み込みモジュール設計を提案しています。

1730ac375379269b.png

図: 「ファイア モジュール」に基づく SqueezeNet アーキテクチャ。垂直方向に受信データを「圧縮」する 1x1 レイヤと、データの深さを再び「拡張」する 2 つの並列 1x1 および 3x3 畳み込みレイヤを交互に使用します。

ハンズオン

前のノートブックを続行し、SqueezeNet にインスパイアされた畳み込みニューラル ネットワークを構築します。モデルコードを Keras の「関数スタイル」に変更する必要があります。

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

追加情報

この演習では、squeezenet モジュールのヘルパー関数を定義すると便利です。

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

今回の目標は 80% の精度を達成することです。

お試しください

単一の畳み込みレイヤから始め、MaxPooling2D(pool_size=2) レイヤと交互に fire_modules を続けます。ネットワークで 2 ~ 4 個の最大プーリング レイヤを試すことができます。また、最大プーリング レイヤ間に 1、2、3 個の連続するファイア モジュールを試すこともできます。

火のモジュールでは、通常、「squeeze」パラメータは「expand」パラメータよりも小さくする必要があります。これらのパラメータは、実際にはフィルタの数です。通常、8 ~ 196 の範囲で指定できます。ネットワーク全体でフィルタの数が徐々に増加するアーキテクチャや、すべての fire モジュールでフィルタの数が同じであるシンプルなアーキテクチャを試すことができます。

以下に例を示します。

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

この時点で、テストがうまくいかず、80% の精度目標が遠く感じられるかもしれません。もういくつか簡単なトリックをご紹介します。

バッチ正規化

バッチ正規化は、発生している収束の問題の解決に役立ちます。この手法の詳細については、次のワークショップで説明します。ここでは、ネットワークの各畳み込みレイヤの後に(fire_module 関数内のレイヤを含む)次の行を追加して、ブラック ボックスの「マジック」のヘルパーとして使用してください。

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

データセットが小さいため、モーメンタム パラメータのデフォルト値 0.99 を 0.9 に減らす必要があります。この詳細は今は気にしないでください。

データ拡張

彩度の左右反転などの簡単な変換でデータを拡張すると、数パーセントの精度向上が見込めます。

4ed2958e09b487ca.png

ad795b70334e0d6b.png

TensorFlow では、tf.data.Dataset API を使用してこれを簡単に行うことができます。データの新しい変換関数を定義します。

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

次に、最終的なデータ変換(セル「トレーニング データセットと検証データセット」、関数「get_batched_dataset」)で使用します。

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

データ拡張をオプションにし、トレーニング用データセットのみが拡張されるように必要なコードを追加することを忘れないでください。検証データセットを拡張しても意味がありません。

35 エポックで 80% の精度を達成できるようになりました。

ソリューション

ソリューション ノートブックは次のとおりです。行き詰まった場合は、こちらをご利用ください。

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

学習した内容

  • 🤔 Keras の「関数スタイル」モデル
  • 🤓 Squeezenet アーキテクチャ
  • 🤓 tf.data.datset を使用したデータ拡張

このチェックリストを頭の中で確認してください。

12. Xception ファインチューニング済み

分離可能な畳み込み

最近、畳み込みレイヤの別の実装方法である深さ方向分離畳み込みが人気を集めています。少し複雑な用語ですが、コンセプトは非常にシンプルです。これらは、Tensorflow と Keras で tf.keras.layers.SeparableConv2D として実装されています。

分離可能な畳み込みも画像に対してフィルタを実行しますが、入力画像の各チャネルに別々の重みセットを使用します。その後に「1x1 畳み込み」が続きます。これは、フィルタされたチャネルの重み付き合計を生成する一連のドット積です。新しい重みを使用して、必要に応じてチャネルの重み付き再結合を計算します。

615720b803bf8dda.gif

イラスト: 分離可能な畳み込み。フェーズ 1: チャネルごとに個別のフィルタを使用した畳み込み。フェーズ 2: チャネルの線形再結合。目的の出力チャネル数に達するまで、新しい重みセットで繰り返されます。フェーズ 1 も繰り返すことができます。そのたびに新しい重みが使用されますが、実際にはほとんど行われません。

分離可能な畳み込みは、MobileNetV2、Xception、EfficientNet など、最新の畳み込みネットワーク アーキテクチャで使用されています。ちなみに、MobileNetV2 は以前に転移学習で使用したものです。

通常の畳み込みよりも安価で、実際には同等の効果があることがわかっています。上記の例の重みの数は次のとおりです。

畳み込みレイヤ: 4 × 4 × 3 × 5 = 240

分離可能な畳み込みレイヤ: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63

各スタイルの畳み込みレイヤ スケールを適用するために必要な乗算の数を計算するのは、読者の練習問題として残されています。分離可能な畳み込みは、サイズが小さく、計算効率が大幅に向上します。

ハンズオン

「転移学習」の Playground ノートブックから再開しますが、今回は事前トレーニング済みモデルとして Xception を選択します。Xception は分離可能な畳み込みのみを使用します。すべての重みをトレーニング可能にします。事前トレーニング済みのレイヤをそのまま使用するのではなく、データで事前トレーニング済みの重みをファインチューニングします。

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

目標: 精度 95% 超(真面目な話、可能です!)

これが最後の演習なので、コードとデータ サイエンスの作業が少し多くなります。

ファインチューニングに関する追加情報

Xception は、tf.keras.application.* の標準の事前トレーニング済みモデルで使用できます。今回はすべての重みをトレーニング可能にすることを忘れないでください。

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

モデルをファインチューニングして良好な結果を得るには、学習率に注意し、立ち上げ期間のある学習率スケジュールを使用する必要があります。この場合、次のように指定します。

9b1af213b2b36d47.png

標準の学習率で開始すると、モデルの事前トレーニング済みの重みが中断されます。プログレッシブ スタートでは、モデルがデータにラッチして、それらを適切な方法で変更できるようになるまで、それらが保持されます。ランプアップ後、学習率を一定に保つか、指数関数的に減衰させることができます。

Keras では、各エポックの適切な学習率を計算できるコールバックで学習率を指定します。Keras は、各エポックの正しい学習率をオプティマイザーに渡します。

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

ソリューション

ソリューション ノートブックは次のとおりです。行き詰まった場合は、こちらをご利用ください。

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

学習した内容

  • 🤔 深度分離畳み込み
  • 🤓 学習率のスケジュール
  • 😈 事前トレーニング済みモデルのファインチューニング。

このチェックリストを頭の中で確認してください。

13. 完了

最初の最新の畳み込みニューラル ネットワークを構築し、TPU を使用して数分で連続するトレーニング実行を繰り返し、90% 以上の精度でトレーニングしました。

TPU の実践

TPU と GPU は、Google Cloud の Vertex AI で使用できます。

最後に、皆様のフィードバックをお待ちしています。このラボで不具合が見つかった場合や、改善すべき点があると思われる場合は、お知らせください。フィードバックは GitHub の問題 [フィードバック リンク] から送信できます。

HR.png

Martin Görner ID small.jpg
著者: Martin Görner
Twitter: @martin_gorner