ホーム » CIFAR-10

CIFAR-10」カテゴリーアーカイブ

Sonnet 2.0 : Tutorials : snt.distribute で分散訓練 (CIFAR-10)

Sonnet 2.0 : Tutorials : snt.distribute で分散訓練 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/10/2020

* 本ページは、Sonnet の以下のドキュメントを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

snt.distribute で分散訓練

イントロダクション

このチュートリアルは Sonnet 2 “Hello, world!” サンプル (MLP on MNIST) を既に完了していることを仮定しています。

このチュートリアルでは、より大きなモデルとより大きなデータセットで物事をスケールアップしていきます、そして計算をマルチデバイスに渡り分散していきます。

 

import sys
assert sys.version_info >= (3, 6), "Sonnet 2 requires Python >=3.6"
!pip install dm-sonnet tqdm
import sonnet as snt
import tensorflow as tf
import tensorflow_datasets as tfds
print("TensorFlow version: {}".format(tf.__version__))
print("    Sonnet version: {}".format(snt.__version__))

最後に利用可能な GPU を素早く見ましょう :

!grep Model: /proc/driver/nvidia/gpus/*/information | awk '{$1="";print$0}'

 

分散ストラテジー

幾つかのデバイスに渡り計算を分散するためのストラテジーが必要です。Google Colab は単一 GPU を提供するだけですのでそれを 4 つの仮想 GPU に分割します :

physical_gpus = tf.config.experimental.list_physical_devices("GPU")
physical_gpus
[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]
tf.config.experimental.set_virtual_device_configuration(
    physical_gpus[0],
    [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2000)] * 4
)
gpus = tf.config.experimental.list_logical_devices("GPU")
gpus
[LogicalDevice(name='/job:localhost/replica:0/task:0/device:GPU:0', device_type='GPU'),
 LogicalDevice(name='/job:localhost/replica:0/task:0/device:GPU:1', device_type='GPU'),
 LogicalDevice(name='/job:localhost/replica:0/task:0/device:GPU:2', device_type='GPU'),
 LogicalDevice(name='/job:localhost/replica:0/task:0/device:GPU:3', device_type='GPU')]

Sonnet optimizer を使用するとき、snt.distribute からの Replicator か TpuReplicator を利用しなければあんりません、あるいは tf.distribute.OneDeviceStrategy を利用できます。Replicator は MirroredStrategy と等値でそして TpuReplicator は TPUStrategy と等値です。

strategy = snt.distribute.Replicator(
    ["/device:GPU:{}".format(i) for i in range(4)],
    tf.distribute.ReductionToOneDevice("GPU:0"))

 

データセット

基本的には MNIST サンプルと同じですが、今回は CIFAR-10 を使用しています。CIFAR-10 は 10 の異なるクラス (飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船そしてトラック) にある 32×32 ピクセルカラー画像を含みます。

# NOTE: This is the batch size across all GPUs.
batch_size = 100 * 4

def process_batch(images, labels):
  images = tf.cast(images, dtype=tf.float32)
  images = ((images / 255.) - .5) * 2.
  return images, labels

def cifar10(split):
  dataset = tfds.load("cifar10", split=split, as_supervised=True)
  dataset = dataset.map(process_batch)
  dataset = dataset.batch(batch_size)
  dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
  dataset = dataset.cache()
  return dataset

cifar10_train = cifar10("train").shuffle(10)
cifar10_test = cifar10("test")

 

モデル & Optimizer

都合良く、snt.nets にこのデータセットのために特に設計された事前ビルドされたモデルがあります。

作成された任意の変数が正しく分散されることを確実にするために、モデルと optimizer は strategy スコープ内で構築しなければなりません。代わりに、tf.distribute.experimental_set_strategy を使用してプログラム全体のためのスコープに入ることもでできるでしょう。

learning_rate = 0.1

with strategy.scope():
  model = snt.nets.Cifar10ConvNet()
  optimizer = snt.optimizers.Momentum(learning_rate, 0.9)

 

モデルを訓練する

Sonnet optimizer はできる限り綺麗でそして単純であるように設計されています。それらは分散実行を扱うためのどのようなコードも含みません。従ってそれはコードの 2, 3 の追加行を必要とします。

異なるデバイス上で計算された勾配を集めなければなりません。これは ReplicaContext.all_reduce を使用して成されます。

Replicator / TpuReplicator を使用するとき values が総てのレプリカで同一で在り続けることを確かなものにすることはユーザの責任であることに注意してください。

def step(images, labels):
  """Performs a single training step, returning the cross-entropy loss."""
  with tf.GradientTape() as tape:
    logits = model(images, is_training=True)["logits"]
    loss = tf.reduce_mean(
        tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels,
                                                       logits=logits))

  grads = tape.gradient(loss, model.trainable_variables)

  # Aggregate the gradients from the full batch.
  replica_ctx = tf.distribute.get_replica_context()
  grads = replica_ctx.all_reduce("mean", grads)

  optimizer.apply(grads, model.trainable_variables)
  return loss

@tf.function
def train_step(images, labels):
  per_replica_loss = strategy.run(step, args=(images, labels))
  return strategy.reduce("sum", per_replica_loss, axis=None)

def train_epoch(dataset):
  """Performs one epoch of training, returning the mean cross-entropy loss."""
  total_loss = 0.0
  num_batches = 0

  # Loop over the entire training set.
  for images, labels in dataset:
    total_loss += train_step(images, labels).numpy()
    num_batches += 1

  return total_loss / num_batches

cifar10_train_dist = strategy.experimental_distribute_dataset(cifar10_train)

for epoch in range(20):
  print("Training epoch", epoch, "...", end=" ")
  print("loss :=", train_epoch(cifar10_train_dist))

 

モデルを評価する

バッチ次元に渡り削減するために strategy.reduce による axis パラメータの使用方法に注意してください。

num_cifar10_test_examples = 10000

def is_predicted(images, labels):
  logits = model(images, is_training=False)["logits"]
  # The reduction over the batch happens in `strategy.reduce`, below.
  return tf.cast(tf.equal(labels, tf.argmax(logits, axis=1)), dtype=tf.int32)

cifar10_test_dist = strategy.experimental_distribute_dataset(cifar10_test)

@tf.function
def evaluate():
  """Returns the top-1 accuracy over the entire test set."""
  total_correct = 0

  for images, labels in cifar10_test_dist:
    per_replica_correct = strategy.run(is_predicted, args=(images, labels))
    total_correct += strategy.reduce("sum", per_replica_correct, axis=0)

  return tf.cast(total_correct, tf.float32) / num_cifar10_test_examples

print("Testing...", end=" ")
print("top-1 accuracy =", evaluate().numpy())
 

以上






ClassCat® TF/ONNX Hub : GAN モデル (Part II) – StarGAN, ACGAN

作成者 :(株)クラスキャット セールスインフォメーション
作成日 : 01/17/2019

 

ClassCat® TF/ONNX Hub とは

「ClassCat® TF/ONNX Hub」はクラスキャットが提供する実用性の高い機械学習モデルのレポジトリです。各モデルは TensorFlow 固有フォーマットと ONNX フォーマットの両者で提供されます。 [ClassCat® ONNX Hub 詳細]

  • ONNX (Open Neural Network Exchange) は深層学習モデルのためのオープンなフォーマットで、異なるオープンソースの深層学習フレームワーク間の相互作用を可能にします。

「ClassCat TF/ONNX Hub」で提供されるモデルについてはクラスキャットが検証の上で仕様を公開致しますので、ユーザ企業は希望するモデルを自由に選択することができます。更に (ユーザ企業の保持するデータセットによる) 再調整も含めて配備・実運用するために必要なトータルサポートを提供致します。

◆ 本ページでは提供モデル群として GAN モデル (Part II) – StarGAN, ACGAN を紹介致します。
StarGAN は顔の表情変換を可能にするモデルとして知られています。

 

 

GAN とは

GAN は敵対的生成ネットワーク (Generative Adversarial Network) と呼称される生成モデルの一種で、深層学習におけるホットな領域の一つとして様々なモデルやその応用が活発に研究されています。

GAN は 2014 年に Ian Goodfellow 氏により創案されペーパー Generative Adversarial Nets で最初に紹介されました。

基本的には 2 つのネットワーク generator と discriminator から構成され、訓練データセットの分布をネットワークに学習させるための仕組みです。その分布から新しい有用なデータを生成することができます :

  • generator のジョブは訓練画像のように見える ‘fake’ 画像を生むことです。
  • discriminator のジョブは画像を見てそれが real 訓練画像か (generator からの) fake 画像かを出力することです。

訓練の間、generator はより良い fake を生成することにより絶えず discriminator を出し抜こうとします。
一方、discriminator はより良い探偵として機能し、real 画像と fake 画像を正しく分類するために動作しています。

このゲームの均衡は、generator が訓練データに直接由来するかのように見える完全な fake 画像を生成しているときに、discriminator が generator 出力について real か fake か常に 50% の信頼度で推測する状態になることです。

 

GAN モデル (Part II) の紹介

Part II では以下の GAN モデルを紹介します :

Part I については こちら を参照してください。

 

StarGAN

StarGAN は顔の表情変換で有名になった、画像変換を主目的とする GAN の一種です。

Cycle GAN では 1 組のドメイン間の画像変換を扱いましたが、StarGAN ではマルチ・ドメイン間の変換を統合的に 1 つのモデルで処理することができます (ここでドメインは同じ属性を持つデータセットを指します)。

“Star” はアーキテクチャのトポロジーがスター型であることを表しています。ドメイン間を総て交差させる非効率性を回避しています。

マルチ・ドメインを処理するためにはクラス (属性) 情報や ACGAN (Auxiliary Classifier) のテクニックを利用しています。

オリジナルのペーパーでは RaFD (Radboud Faces Database) データセットで学習したモデルを CelebA データセットに適用してもいますが、ここでは CelebA だけをを利用したモデルを紹介します。

CelebA ではセレブ (有名人) の画像を多数含むとともに、アノテーションとして属性が示されます。このデータセットでは喜怒哀楽などの表情変換のサポートは限定的ですが、髪の色などの他の属性を多く含みます。

 

髪色の変換

髪色の変換は比較的容易です。
左端の画像が元の入力画像で順次、ブロンド・黒髪・茶髪に変換しています :

 

眼鏡の着脱

眼鏡の装着も容易です :

(眼鏡をかけている元画像は少ないですが) 眼鏡をはずした画像も生成できます :

 

老若変換

より若く画像を変換することもできます :

逆に年齢を高く変換することもできます :

 

笑顔

普通の表情を笑顔に変換することができます :

逆に笑顔を普通の表情に変換することもできます :

 

魅力的に

(どういう規準でアノテーションを作成したのか分かりませんが、) 魅力的に変換することもできます :

 

ACGAN

StarGAN の説明で ACGAN (Auxiliary Classifier) に言及しましたので簡単に説明しておきます。

ACGAN は DCGAN のような (画像変換を目的としていない) 生成モデルです。
但しノイズ (あるいは潜在変数) だけを入力とする DCGAN とは違い、クラス情報を利用します。Conditional GAN や InfoGAN と同系統の GAN になります。

Generator への入力にクラス情報を与え、Discriminator にはクラスを識別させます。

ACGAN モデルで CIFAR-10 画像を生成してみます。元画像が小さく粗いので小動物は分かりにくいですが、上から飛行機、自動車、鳥、ネコ、鹿、犬、カエル、馬、船、トラックの画像です :

 

 

以上






TensorFlow: Network-In-Network で CIFAR-10 精度 90%

TensorFlow: Network-In-Network で CIFAR-10 精度 90%
作成 : (株)クラスキャット セールスインフォメーション
日時 : 05/08/2017

 

Network In Network

マイクロ・ネットワークで有名な Network in Network (NiN) は、より小さいモデルでより高速に訓練することを可能とし、over-fitting も比較的起きにくいとされます。多大な影響を与えた epoch-making なモデルです。

その基本アイデアは MLP 畳込み層 (mlpconv) – マイクロ・ネットワーク – の導入とグローバル平均プーリングによる完全結合層の代用ですが、特に前者については広く利用され、類似のアイデアは Inception や ResNet にも見られます。後者は解釈を容易にして over-fitting を起きにくくします。

Network In Network 全体の構成としては mlpconv 層のスタックになり、そのトップにグローバル平均プーリング、更に目的コスト層が存在することになります。イメージと違い、意外にシンプルなモデルです。

参照されることが多いわりにはオリジナル・モデルがそのまま使われることは (知る限りでは) 少ないように思われますが、実装は簡単ですので定番のデータセットで威力を試しておきます。
MNIST、CIFAR-10 及び CIFAR-100 について原論文の結果に匹敵、あるいは凌駕する結果が得られました。CIFAR-10 では精度 90 % に到達します。

Network In Network の原論文は以下です :

  • Network In Network
    Min Lin, Qiang Chen, Shuicheng Yan
    (Submitted on 16 Dec 2013 (v1), last revised 4 Mar 2014 (this version, v3))

Abstract のみ翻訳しておきますが、基本的にはここに書かれていることが全てです :

受容野内のローカルパッチのモデル識別性を強化するために “Network in Network” (NIN) と呼ばれる、新しい深層ネットワーク構造を提案します。従来の畳込み層は入力をスキャンするために、非線形活性化関数が続く線形フィルタを使用します。代わりに、受容野内のデータを抽象化するためにより複雑な構造でマイクロ・ニューラルネットワークを構築します。そのマイクロ・ニューラルネットワークは潜在的な関数近似器である多層パーセプトロンでインスタンス化します。特徴マップは CNN と同様の流儀で入力に渡ってマイクロ・ネットワークをスライドさせることで得られます; そしてそれらは次の層に供給されます。深層 NIN は上で記述された構造を多層化することで実装されます。マイクロ・ネットワークを通したローカルモデリングの強化により、分類層の特徴マップに渡るグローバル平均プーリングを利用することが可能で、これは伝統的な完全結合層よりも解釈がより容易で過剰適合しにくいです。私たちは CIFAR-10 と CIFAR-100 上の NIN で state-of-the-art な分類性能を示し、SVHN と MNIST データセットで合理的な性能を示しました。

実装

実装は簡単ですが、以下の Caffe 実装を参考にしました (というか後者を殆どそのまま TensorFlow に流用しました) :

 

MNIST

手始めに MNIST です。取り敢えず 80 epochs 実行してみましたが、最後の 10 epochs の平均エラー率は 0.393 % で、state of the art に近い精度が出ました (ちなみに原論文のエラー率は 0.47 % ですので、僅かばかり良い結果が得られたことになります)。

 

CIFAR-10

原論文では 91.2 % のようですが、そこまでは出ませんでしたが、400 epochs で 90 % に到達します :

念のため更に 100 epochs 再調整します :

最後の 10 epochs の平均精度は 90.204 でした。時間をかければ 91 % に近い精度は出る感じですが、超えるのは難しそうです。

 

CIFAR-100

続いて CIAR-100 ですが、スクラッチからの訓練ではなく上記の CIFAR-10 の訓練済みのモデルを流用しました。この方法だと 66 % は出ます。原論文の結果は 64.32% です :

 
以上

TensorFlow で CNN AutoEncoder – CIFAR-10 –

TensorFlow で CNN AutoEncoder – CIFAR-10 –

先に MNIST を題材に Convolutional AutoEncoder を実装して視覚化してみました(TensorFlow で CNN AutoEncoder – MNIST –)が、CIFAR-10 でも試しておきます。

CIFAR-10 は機械学習定番のベンチマークとして使用され、RGB 32×32 ピクセル画像を 10 カテゴリーに渡って分類するものです : 飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船そしてトラック。

CIFAR-10 の詳細は TensorFlow : Tutorials : 畳込み ニューラルネットワーク を参照してください。

Convolutional AuoEncoder

AutoEncoder に使用するモデルは Encoder として畳込み層を3層使用する単純なものです。
そして decode された画像を元画像と比較し、encode された画像、特徴マップを可視化します。

最初にテスト・サンプル画像を頭から 40 枚まとめて、decode した画像群をトレーニング epochs 毎にリスト表示します :

【元サンプル画像】
ae_cifar10_list_samples.01

【1 epoch】
1 epoch だと RGB の 3 チャネルが復元できていません :
ae_cifar10_list_decoded.01

【10 epochs】
10 epochs で肉眼では殆ど差異が分からなくなります :
ae_cifar10_list_decoded.10

【30 epochs】
念のため、30 epochs, 50 epochs トレーニングしました :
ae_cifar10_list_decoded.30

【50 epochs】
ae_cifar10_list_decoded.50

 
次に個別のサンプル画像を適当に選択して epochs 別に decoded 画像を比較してみます。
画像リストでも見て取れたように、1 epoch では 3 チャネルが復元できていませんが、10 epochs 以後はほぼ完全に復元できています :

元サンプル画像

1 epoch

ae_cifar10_sample.01.01

ae_cifar10_decoded.01.01

10 epochs

50 epochs

ae_cifar10_decoded.10.01

ae_cifar10_decoded.50.01

以下は同じサンプル画像を encode した出力マップを epochs 別に表示したものです。
トレーニングが進むと特徴抽出が先鋭化します :

【Encode 画像 : 1 epoch】
ae_cifar10_encoded.01.01

【Encode 画像 : 10 epochs】
ae_cifar10_encoded.10.01

【Encode 画像: 50 epochs】
ae_cifar10_encoded.50.01

 
別例を幾つか掲示しておきます。
それぞれ、サンプル画像、50 epochs 後の decoded 画像、そして encoded 画像群です :

馬の全体画像 :

 

ae_cifar10_sample.50.13

ae_cifar10_decoded.50.13

 

ae_cifar10_encoded.50.13

犬の正面画像 :

 

ae_cifar10_sample.50.16

ae_cifar10_decoded.50.16

 

ae_cifar10_encoded.50.16

ステルス戦闘機の画像でしょうか、encoded 画像は分かりやすいです :

 

ae_cifar10_sample.50.21

ae_cifar10_decoded.50.21

 

ae_cifar10_encoded.50.21

馬の横顔 :

 

ae_cifar10_sample.50.17

ae_cifar10_decoded.50.17

 

ae_cifar10_encoded.50.17

 

以上

TensorFlow 畳込み層のフィルタ/特徴マップの可視化 – CIFAR-10 –

TensorFlow 畳込み層のフィルタ/特徴マップの可視化 – CIFAR-10 –

cifar10_model先に MNIST を題材として ConvNet (CNN) モデルの畳込み層のフィルタと出力としての特徴マップを可視化しました(特徴マップを視覚化 – MNIST – / フィルタを視覚化 – MNIST –)。

ついで CIFAR-10 についても同様な視覚化をしてみます。

MNIST 用の単純モデルのままだと精度が上がらないので、CIFAR-10 用に右図のように少しだけ多層化してみました。(畳込み層 + 畳込み層 + プーリング層) x 2 です :

Conv1 -> Conv2 -> MaxPooling2 -> Conv1 -> Conv1 -> MaxPooling2

トレーニング時には Dropout 層も使用しています。また精度よりも可視化を重視するためにフィルタサイズは大きめにして、テスト精度は 80 % 前後まであげてあります。

CIFAR-10

題材はお馴染みの CIFAR-10 です。以下は training samples の最初の 100 個を並べたものです :

CIFAR-10 の詳細は TensorFlow のチュートリアル TensorFlow : Tutorials : 畳込み ニューラルネットワーク を参照してください。

cifar10_samples

各畳込み層・プーリング層の出力については特に以下の2つのサンプル画像を元にしています。
これらの画像の選択に深い意味はなく、単に各層の出力画像が視認しやすかっただけです :

cifar10_sample.7 cifar10_sample.8

フィルタと特徴マップの視覚化

トレーニング後のモデルをベースにフィルタと各層の出力を可視化したものが以下に続く画像です。
* (明るさや輝度を修正してもあまり視認性に影響がなかったので、)画像は加工しないでそのまま表示しています。
* 回転もしていません。

【Conv1 層のフィルタ画像】
見やすくするためにフィルタサイズは 7 x 7 と大きめに取りました :
cifar10_filters_conv0.7x7

これだけだと良く分からないので、3チャネルを分解してグレースケール表示したものが以下です。
単純な線分、斜線、円などを処理するフィルタが見て取れますが、各チャネルで相違はあまりないようです。 :
cifar10_filters_conv0.7x7.0

cifar10_filters_conv0.7x7.1

cifar10_filters_conv0.7x7.2

【Conv1 層の出力マップ】
比較的単純なフィルタへの反応が分かりやすいです。
上が馬、下が船の画像に対応していますが、画像が未加工なので回転させていない点に注意してください。
cifar10_conv1out.7b

cifar10_conv1out.8b

【Conv2 層のフィルタ】
以後は数が多いので、最初の2つのみを表示しています。
cifar10_filters_conv2.5x5.num0

cifar10_filters_conv2.5x5.num1

【Conv2 層の出力マップ】
cifar10_conv2out.7b

cifar10_conv2out.8b

【MaxPooling2 層の出力マップ】
cifar10_maxpool2out.7b

cifar10_maxpool2out.8b

【Conv3 層のフィルタ】
cifar10_filters_conv5.3x3.num0

cifar10_filters_conv5.3x3.num1

【Conv3 層の出力マップ】
cifar10_conv3out.7
cifar10_conv3out.8

【Conv4 層のフィルタ】
cifar10_filters_conv7.3x3.num0

cifar10_filters_conv7.3x3.num1

【Conv4 層の出力マップ】
cifar10_conv4out.7

cifar10_conv4out.8

【MaxPooling4 層の出力マップ】
cifar10_maxpool4out.7

cifar10_maxpool4out.8

 

以上

TensorFlow ResNet (Deep Residual Learning) で CIFAR-100

TensorFlow ResNet (Deep Residual Learning) で CIFAR-100

CIFAR-10 については TensorFlow のチュートリアル : 畳み込み ニューラルネットワーク で解説されていますが、
CIFAR-100 についてはまだ試していなかったので TensorFlow 実装で試しておくことにします。

モデルとしては Deep Residual Learning(いわゆる ResNet)を利用しました。
これは Microsoft Research Asia (MSRA) が提唱したモデルで ILSVRC 2015 の分類タスクで優勝したモデルです。ゲートがない LSTM であるとの批評もあるようですが、興味深いモデルです。

CIFAR-100

CIFAR-100 のデータセットも CIFAR-10 と同じサイトから取得できます :

CIFAR-10 and CIFAR-100 dataset

CIFAR-10 との違いは、単に 100 種類に分かれているのではなく、20 種類のスーパークラスに分割された上で更に 100 種類のサブクラスに分割されていることです :

 

スーパークラス

クラス
海洋哺乳類

ビーバー, イルカ, カワウソ, アザラシ, 鯨
魚類

観賞魚, カレイ, エイ, サメ, マス

ラン, ひなげし, バラ, ヒマワリ, チューリップ
食品容器

ボトル, ボウル, カン, カップ, プレート
果物と野菜

リンゴ, キノコ, オレンジ, ナシ, ピーマン
家庭用電気機器

時計, コンピュータ・キーボード, ランプ, 電話, テレビ
家庭用家具

ベッド, 椅子, ソファー, テーブル, タンス
昆虫

蜂, カブトムシ, 蝶, 蝶等の幼虫, ゴキブリ
大きな肉食獣

クマ, ヒョウ, ライオン, 虎, 狼
大きな人造の屋外物

橋, 城, 家, 道, 超高層ビル
大きな自然野外シーン

雲, 森, 山, 平野, 海
大きな雑食動物と草食動物

ラクダ, 牛, チンパンジー, 象, カンガルー
中位の哺乳類

キツネ, ヤマアラシ, オポッサム, アライグマ, スカンク
昆虫でない無脊椎動物

カニ, ロブスター, カタツムリ, クモ, ワーム
人々

赤ちゃん, 少年, 少女, 男性, 女性
爬虫類

ワニ, 恐竜, トカゲ, ヘビ, 亀
小哺乳類

ハムスター, マウス, ウサギ, トガリネズミ, リス

カエデ, オーク, ヤシ, 松, 柳
乗り物 1

自転車, バス, オートバイ, ピックアップトラック, 電車
乗り物 2

芝刈り機, ロケット, 市街電車, タンク, トラクタ
 

参考まで、CIFAR-100 のトレーニング・セットの最初の 25 枚の画像を表示しておきます :
cifar100_top25b

画像が粗いので分かりにくいですが、最初の 10 枚の画像はそれぞれ以下のクラスに属します :

牛, 恐竜, リンゴ, 少年, 観賞魚, 電話, 電車, カップ, 雲, 象

Deep Residual Learning

Deep Residual Learning は Microsoft Research Asia (MSRA) が提唱したモデルで ILSVRC 2015 の分類タスクで優勝したモデルです :

ImageNet Large Scale Visual Recognition Challenge 2015 (ILSVRC2015)
and Results

ペーパーは以下 :

Deep Residual Learning for Image Recognition
K. He, X. Zhang, S. Ren and J. Sun

参考まで、abstract だけ翻訳しておきます :

ニューラルネットワークはより深くなるに従ってトレーニングがより困難になります。ネットワークのトレーニングを容易にすることが可能な residual learning フレームワークを公開します。これは前から使われていたものよりも本質的にはより深くなります。層を、learning unreferenced function の代わりに、learning residual function として明示的に再公式化 (reformulate) します。これらの residual ネットワークが最適化するのがより簡単であり、かなり増やした深さからでも精度を得られることを示す、包括的で実証的な証拠を提供します。ImageNet データセット上で 152 層まで増やした深さで residual ネットを評価します — これは VGG ネットよりも 8 倍深いですが依然として複雑の度合いは低いです。これらの residual ネットのアンサンブルは ImageNet テスト・セット上でエラー率 3.57 % を達成しています。結果は ILSVRC 2015 分類タスクにおいて1位を勝ち取りました。また 100 と 1000 層による CIFAR-10 上の解析も示します。表現の深さは多くのビジュアル認識タスクのために中心的な重要性があります。極めて深い表現のみによって、COCO 物体検知データセット上で 28% の相対的改善を得ました。deep residual ネットは ILSVRC & COCO 2015 コンペへの提示の拠り所で、そこではまた ImageNet 検知、ImageNet localization、COCO 検知、そして COCO セグメンテーションのタスクにおいて1 位を獲得しました。

 
それから関連記事も紹介しておきますと、ResNet については “ゲートのない LSTM と同等である” という興味深い議論もあります。以下で後者は評価しつつも、批判的なニュアンスもこめられています :

How does deep residual learning work?
Microsoft Wins ImageNet 2015 through Feedforward LSTM without Gates

以下は何故 Residual なのかという説明があります :

Why is Deep Residual Learning called that way?

この説明は短いので粗く翻訳しておきます :

【質問】
何故 Deep Residual Lerning はそのように呼称されるのでしょう?

F(X)=H(X)-X を学習することの本質は何でしょう?
フィッシャー・ベクトル (Fischer vectors) や他の residual テクニックとの関係は?
その関係がそこから名をとってこのテクニックに命名するに十分に本質的なのは何故でしょう?

【回答】
深層 Convolutional ネットワークを解析し始めた時、直感に反して、より深いネットワークはより少ないエラーを持つわけではないことを発見しました。事実、追加層は恒等写像であるにもかかわらず、エラーはより浅いネットワークよりも大きいです。これは奇妙なことです、何故なら全ての追加層が恒等層であるならば、ネットワークは少なくとも基となったネットワークと同程度の性能は保持すべきです。この問題は、バッチ正規化で効果的に取り組まれてきた勾配消失問題とは異なっていました。

この奇妙な挙動をガイド的な手がかりとして、特徴マッピングを学習するために、residual を学習して元の特徴ベクトルを追加することによりネットワークをトレーニングすることを決めました。こうして、residual が 0 だとしてもネットワークは恒等写像を学習します。residual は H(x) = F(x) – x によって与えられます、ここで x は画像で F(x) は通常ネットワークが学習するマッピング(写像)です。

この大きなアイデアは、もし AlexNet, VGG あるいは GoogLeNet のような成功したネットワークがあれば、そしてそれに更なる多くの層を追加したならば、後の層で基本的な恒等写像を学習することが今や可能になります。その結果、少なくとも元のネットワークと同程度には性能が出ます。この residual 公式化 (formulation) はそれを可能にします。

結果、1000 層以上を持つネットワークをトレーニングすることが今や可能となり、そして追加された深さは認識タスクにおいてより良い性能を提供する役割を果たします。

ResNet の TensorFlow 実装とトレーニング

cifar100_residual上記の abstract によればオリジナル・モデルは 152 層 – VGG の 8 倍の深さがあるわけですが、TensorFlow による実装は実は簡単です。

サンプルを流用することもできますし、各種ビルディング・ブロックも用意されています。但しビルディング・ブロックで構築した場合には右図の TensorBoard によるモデル・グラフ画像のようにブロックが積層されていることしか分かりませんが。

トレーニングは取り敢えず 200 epochs 回してみました。
その結果が下図です。左が損失グラフで、右がテストセットに対する精度です。
精度は約 50 % が獲得できています。この数字は必ずしも悪いわけではありません。
CIFAR-100 は決して易しい問題ではなく、公式記録では 76 % 程度が最高で 55 % もあればベスト 30 に入ります。

cifar100_loss2

cifar100_acc_test2

以上

TensorFlow Android デモ(CIFAR-10 モデル)

cifar10.cat2

【TensorFlow Android デモ(CIFAR-10 モデル) 】
TensorFlow の CIFAR-10 モデルを Android に組み込んでみました。CIFAR-10 は、「飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船、トラック」の画像を分類する課題です。
10 万ステップの訓練で精度は 86 % 程度まで高めてあります。

画像はクラスキャットのマスコットの “猫” を認識させてみたところです。選択肢が確率の高い順から3つ出ていますが、”猫” の確率が高く認識されています。ただし、蛙や鹿の確率も意外に高いですね。

実機は Xperia Z5 w/Android 5.1 (Lollipop) です。

Keras : データセット

Keras : Datasets(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : Datasets の簡単な要約です。

 

CIFAR10 小画像分類

keras.datasets.cifar10

10 カテゴリーにラベルづけられた、50,000 32×32 カラー訓練画像のデータセット、
そして 10,000 テスト画像。

使用方法:

(X_train, y_train), (X_test, y_test) = cifar10.load_data()

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 3, 32, 32) の RGB 画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) のカテゴリー・ラベル(range 0-9 の整数値)の uint8 配列。
 

CIFAR100 小画像分類

keras.datasets.cifar100

100 カテゴリーにラベルづけられた、50,000 32×32 カラー訓練画像のデータセット、
そして 10,000 テスト画像。

使用方法:

(X_train, y_train), (X_test, y_test) = cifar100.load_data(label_mode=’fine’)

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 3, 32, 32) の RGB 画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) のカテゴリー・ラベルの uint8 配列。

引数:

  • label_mode: “fine” または “coarse”.
 

MNIST 手書き数字のデータセット

keras.datasets.mnist

10 数字の 60,000 28×28 グレースケール画像のデータセット、10,000 画像のテストセット添付。

使用方法:

(X_train, y_train), (X_test, y_test) = mnist.load_data()

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 28, 28) のグレースケール画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) の数字ラベル(range 0-9 の整数値)の uint8 配列。

引数:

  • path: if you do have the index file locally (at ‘~/.keras/datasets/’ + path),
    if will be downloaded to this location (in cPickle format).

以上

TensorFlow : CNN – 畳み込み ニューラルネットワーク for CIFAR-10 (コード解説)

TensorFlow : コード解説 : CNN畳み込み ニューラルネットワーク for CIFAR-10

* TensorFlow : Tutorials : 畳み込み ニューラルネットワーク (翻訳/解説) に、数式は排除/コード重視の方針で詳細な解説を加筆したものです。

 
概要

CIFAR-10 分類は機械学習の共通のベンチマーク問題です。問題は RGB 32×32 ピクセル画像を 10 カテゴリーに渡って分類するものです : 飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船そしてトラック。

詳細については CIFAR-10 ページ と Alex Krizhevsky による Tech Report (pdf) を参照してください。

目標

目標は画像認識のために比較的小さい 畳み込みニューラルネットワーク (convolutional neural network (CNN)) を構築することです。その過程において :

  1. ネットワーク、訓練、そして評価のための標準的な構成を明らかにする、
  2. より大きく洗練されたモデルの構築のためのテンプレートを提供する。

CIFAR-10 を選択した理由は、大きなモデルにスケールするために、TesnorFlow の能力の多くを練習するために十分に複雑なことです。
同時に、迅速に訓練するために十分に小さいことです。これは新しいアイデアを試し新しい技法を使って実験するために理想的です。

ハイライト

TensorFlow でより大規模でより洗練されたモデルを設計するために必要な、
幾つかの重要な構成要素をデモします :

  • 核となる数学コンポーネントとして、畳み込みReLU最大プーリング、そして 局所反応正規化 (local response normalization) を含みます。
  • 入力画像、損失そして活性化と勾配の分布を含む、訓練中のネットワーク活動の 視覚化
  • 学習したパラメータの 移動平均 の計算をして、そして予測性能を高めるために評価中にこれらの平均を使うためのルーチン。(モーメンタム法?)
  • システマチックに時間をかけて減少する、学習率スケジュール の実装。
  • ディスク遅延と高コストな画像前処理からモデルを分離する、入力データのための キュー の先読み。

またモデルのマルチ-GPU 版も提供します、これは以下を実証します :

  • 複数の GPU カードに渡って並列に訓練するモデルを構成します。
  • 複数の GPU の中で変数を共有し更新します。

TensorFlow 上のビジョン・タスクのためのより大きな CNN を構築するための起点を提供します。

モデル・アーキテクチャ

この CIFAR-10 チュートリアルのモデルは交互の畳み込みと非線形から成る多層アーキテクチャです。これらの層には softmax 分類につながる全結合 (fully connected) 層が続きます。モデルは Alex Krizhevsky により記述されているアーキテクチャを、トップの2、3層で少しの違いはありますが、追随しています。

このモデルは GPU 上の 2, 3 時間の訓練時間内に約 86% の精度の最高性能を達成しています。詳細は後述の “モデルを評価する” とコードを見てください。モデルは 1,068,298 個の学習可能なパラメータから成り、そして一つの画像上の推論を計算するために 19.5 M の乗算-加算演算を必要とします。

 

コード構成

このチュートリアルのコードは tensorflow/models/image/cifar10/ にあります。

ファイル

目的
cifar10_input.py

native の CIFAR-10 バイナリ・ファイル・フォーマットを読む。
cifar10.py

CIFAR-10 モデルの構築。
cifar10_train.py

CPU または GPU で CIFAR-10 モデルを訓練する。
cifar10_multi_gpu_train.py

複数の GPU で CIFAR-10 モデルを訓練する。
cifar10_eval.py

CIFAR-10 モデルの予測性能を評価する。
 

CIFAR-10 モデル

CIFAR-10 ネットワークの大部分は cifar10.py に含まれています。完全な訓練グラフは約 765 演算を含みます。グラフを次のモジュールで構築することでコードを最大限再利用可能にしてあります。

  1. モデル入力 : inputs() と distorted_inputs() は評価と訓練のための CIFAR 画像を、それぞれ、読みそして前処理する演算を追加します。
  2. モデル予測 : inference() は提供された画像について 推論、すなわち分類 を行なう演算を追加します。
  3. モデル訓練 : loss() と train() は損失、勾配、変数更新と視覚化要約を計算する演算を追加します。

モデル入力

モデルの入力パートは CIFAR-10 バイナリ・データファイルから画像を読む、
inputs() と distorted_inputs() 関数で構築されます。
エントリは cifar10.py に含まれていますが、実装は cifar10_input.py にあります。

def distorted_inputs():
  """Construct distorted input for CIFAR training using the Reader ops.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  if not FLAGS.data_dir:
    raise ValueError('Please supply a data_dir')
  data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
  return cifar10_input.distorted_inputs(data_dir=data_dir,
                                        batch_size=FLAGS.batch_size)


def inputs(eval_data):
  """Construct input for CIFAR evaluation using the Reader ops.
  Args:
    eval_data: bool, indicating if one should use the train or eval data set.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  if not FLAGS.data_dir:
    raise ValueError('Please supply a data_dir')
  data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
  return cifar10_input.inputs(eval_data=eval_data, data_dir=data_dir,
                              batch_size=FLAGS.batch_size)
IMAGE_SIZE = 24

def distorted_inputs(data_dir, batch_size):
  """Construct distorted input for CIFAR training using the Reader ops.
  Args:
    data_dir: Path to the CIFAR-10 data directory.
    batch_size: Number of images per batch.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
               for i in xrange(1, 6)]
  for f in filenames:
    if not tf.gfile.Exists(f):
      raise ValueError('Failed to find file: ' + f)

  # 読むためのファイル名を生成するキューを作成する。
  filename_queue = tf.train.string_input_producer(filenames)

  # ファイル名キューにあるファイルからサンプルを読む。
  read_input = read_cifar10(filename_queue)
  reshaped_image = tf.cast(read_input.uint8image, tf.float32)

  height = IMAGE_SIZE
  width = IMAGE_SIZE

  # ネットワーク訓練のための画像処理。
  # 多くのランダムな歪みが画像に適用されます。

  # 画像の [height, width] セクションをランダムにクロップする。
  distorted_image = tf.random_crop(reshaped_image, [height, width, 3])

  # ランダムに画像を水平方向にフリップします。
  distorted_image = tf.image.random_flip_left_right(distorted_image)

  # これらの操作に可換性はないので、
  # 操作の順序をランダム化することを考えます。
  distorted_image = tf.image.random_brightness(distorted_image,
                                               max_delta=63)
  distorted_image = tf.image.random_contrast(distorted_image,
                                             lower=0.2, upper=1.8)

  # Subtract off the mean and divide by the variance of the pixels.
  float_image = tf.image.per_image_whitening(distorted_image)

  # Ensure that the random shuffling has good mixing properties.
  min_fraction_of_examples_in_queue = 0.4
  min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN *
                           min_fraction_of_examples_in_queue)
  print ('Filling queue with %d CIFAR images before starting to train. '
         'This will take a few minutes.' % min_queue_examples)

  # Generate a batch of images and labels by building up a queue of examples.
  return _generate_image_and_label_batch(float_image, read_input.label,
                                         min_queue_examples, batch_size)


def inputs(eval_data, data_dir, batch_size):
  """Construct input for CIFAR evaluation using the Reader ops.
  Args:
    eval_data: bool, indicating if one should use the train or eval data set.
    data_dir: Path to the CIFAR-10 data directory.
    batch_size: Number of images per batch.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  if not eval_data:
    filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
                 for i in xrange(1, 6)]
    num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN
  else:
    filenames = [os.path.join(data_dir, 'test_batch.bin')]
    num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_EVAL

  for f in filenames:
    if not tf.gfile.Exists(f):
      raise ValueError('Failed to find file: ' + f)

  # 読むためのファイル名を生成するキューを作成する。
  filename_queue = tf.train.string_input_producer(filenames)

  # ファイル名キューにあるファイルからサンプルを読む。
  read_input = read_cifar10(filename_queue)
  reshaped_image = tf.cast(read_input.uint8image, tf.float32)

  height = IMAGE_SIZE
  width = IMAGE_SIZE

  # 評価のための画像処理。
  # 画像の中央の [height, width] をクロップします。
  resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image,
                                                         width, height)

  # Subtract off the mean and divide by the variance of the pixels.
  float_image = tf.image.per_image_whitening(resized_image)

  # Ensure that the random shuffling has good mixing properties.
  min_fraction_of_examples_in_queue = 0.4
  min_queue_examples = int(num_examples_per_epoch *
                           min_fraction_of_examples_in_queue)

  # Generate a batch of images and labels by building up a queue of examples.
  return _generate_image_and_label_batch(float_image, read_input.label,
                                         min_queue_examples, batch_size)

実際には read_cifar10() がファイルからデータを読みます。
CIFAR-10 バイナリ・データファイルは固定バイト長レコードを含みますので、tf.FixedLengthRecordReader を使用しています。

def read_cifar10(filename_queue):
  """Reads and parses examples from CIFAR10 data files.
  Recommendation: if you want N-way read parallelism, call this function
  N times.  This will give you N independent Readers reading different
  files & positions within those files, which will give better mixing of
  examples.
  Args:
    filename_queue: A queue of strings with the filenames to read from.
  Returns:
    An object representing a single example, with the following fields:
      height: number of rows in the result (32)
      width: number of columns in the result (32)
      depth: number of color channels in the result (3)
      key: a scalar string Tensor describing the filename & record number
        for this example.
      label: an int32 Tensor with the label in the range 0..9.
      uint8image: a [height, width, depth] uint8 Tensor with the image data
  """

  class CIFAR10Record(object):
    pass
  result = CIFAR10Record()

  # Dimensions of the images in the CIFAR-10 dataset.
  # See http://www.cs.toronto.edu/~kriz/cifar.html for a description of the
  # input format.
  label_bytes = 1  # 2 for CIFAR-100
  result.height = 32
  result.width = 32
  result.depth = 3
  image_bytes = result.height * result.width * result.depth
  # Every record consists of a label followed by the image, with a
  # fixed number of bytes for each.
  record_bytes = label_bytes + image_bytes

  # filename_queue からファイル名を取得してレコードを読みます。
  # CIFAR-10 フォーマットにはヘッダやフッタはないので、
  # header_bytes と footer_bytes はデフォルトの 0 のままにしておきます。
  reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
  result.key, value = reader.read(filename_queue)

  # Convert from a string to a vector of uint8 that is record_bytes long.
  record_bytes = tf.decode_raw(value, tf.uint8)

  # The first bytes represent the label, which we convert from uint8->int32.
  result.label = tf.cast(
      tf.slice(record_bytes, [0], [label_bytes]), tf.int32)

  # The remaining bytes after the label represent the image, which we reshape
  # from [depth * height * width] to [depth, height, width].
  depth_major = tf.reshape(tf.slice(record_bytes, [label_bytes], [image_bytes]),
                           [result.depth, result.height, result.width])
  # Convert from [depth, height, width] to [height, width, depth].
  result.uint8image = tf.transpose(depth_major, [1, 2, 0])

  return result

Reader クラスの動作についての詳細は データを読む を参照してください。。

画像は次のように処理されます:

  • 24 x 24 ピクセルにクロップ(切り出)されます。
    評価目的では中央を、訓練のためには ランダム にクロップします。

    resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, width, height)
    
    distorted_image = tf.random_crop(reshaped_image, [height, width, 3])
    
  • モデルがダイナミックレンジに過敏にならないように おおよそ白色化 (approximately whitened) されます。
    float_image = tf.image.per_image_whitening(resized_image)
    

訓練のため、人工的にデータセットのサイズを増やすために一連のランダムな歪みを追加で適用します:

  distorted_image = tf.image.random_flip_left_right(distorted_image)

  distorted_image = tf.image.random_brightness(distorted_image,
                                               max_delta=63)
  distorted_image = tf.image.random_contrast(distorted_image,
                                             lower=0.2, upper=1.8)

利用可能な歪みのリストは Images ページを見てください。

それから image_summary を画像にアタッチします。

def _generate_image_and_label_batch(image, label, min_queue_examples,
                                    batch_size):
  """Construct a queued batch of images and labels.
  Args:
    image: 3-D Tensor of [height, width, 3] of type.float32.
    label: 1-D Tensor of type.int32
    min_queue_examples: int32, minimum number of samples to retain
      in the queue that provides of batches of examples.
    batch_size: Number of images per batch.
  Returns:
    images: Images. 4D tensor of [batch_size, height, width, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  # Create a queue that shuffles the examples, and then
  # read 'batch_size' images + labels from the example queue.
  num_preprocess_threads = 16
  images, label_batch = tf.train.shuffle_batch(
      [image, label],
      batch_size=batch_size,
      num_threads=num_preprocess_threads,
      capacity=min_queue_examples + 3 * batch_size,
      min_after_dequeue=min_queue_examples)

  # TensorBoard で訓練画像を表示する。
  tf.image_summary('images', images)

  return images, tf.reshape(label_batch, [batch_size])

TensorBoard でそれらを視覚化できます。入力が正しく組み立てられているか検証するためには良い実践です。

 

ディスクから画像を読んでそれらを歪ませることは、処理時間の自明ではない総量を使用する可能性があります。これらの操作が訓練を遅延させることを防ぐために、継続的に TensorFlow キュー を満たす、16 の分離したスレッドの内部でそれらを実行します。

モデル予測

モデルの予測パートは inference() で構築されます。これは予測のロジットを計算するための演算を追加します。モデルのそのパートは以下のように体系化されます :

層名

説明

conv1

畳み込みReLU 活性化。

pool1

最大プーリング

norm1

局所反応正規化 (local response normalization)

conv2

畳み込みReLU 活性化。

norm2

局所反応正規化 (local response normalization)

pool2

最大プーリング

local3

全結合 with ReLU 活性化

local4

全結合 with ReLU 活性化

softmax_linear

ロジット生成のための線形変換。
def inference(images):
  """Build the CIFAR-10 model.
  Args:
    images: Images returned from distorted_inputs() or inputs().
  Returns:
    Logits.
  """
  # We instantiate all variables using tf.get_variable() instead of
  # tf.Variable() in order to share variables across multiple GPU training runs.
  # If we only ran this model on a single GPU, we could simplify this function
  # by replacing all instances of tf.get_variable() with tf.Variable().
  #
  # conv1 : 畳み込みと ReLU 活性化
  with tf.variable_scope('conv1') as scope:
    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64],
                                         stddev=1e-4, wd=0.0)
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    bias = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(bias, name=scope.name)
    _activation_summary(conv1)

  # pool1 : 最大プーリング
  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                         padding='SAME', name='pool1')
  # norm1 : 局所反応正規化 (local response normalization)。
  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm1')

  # conv2 : 畳み込みと ReLU 活性化
  with tf.variable_scope('conv2') as scope:
    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64],
                                         stddev=1e-4, wd=0.0)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    bias = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(bias, name=scope.name)
    _activation_summary(conv2)

  # norm2 : 局所反応正規化 (local response normalization)
  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm2')
  # pool2 : 最大プーリング
  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')

  # local3 : 全結合 with ReLU 活性化
  with tf.variable_scope('local3') as scope:
    # Move everything into depth so we can perform a single matrix multiply.
    dim = 1
    for d in pool2.get_shape()[1:].as_list():
      dim *= d
    reshape = tf.reshape(pool2, [FLAGS.batch_size, dim])

    weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
    _activation_summary(local3)

  # local4 : 全結合 with ReLU 活性化
  with tf.variable_scope('local4') as scope:
    weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
    _activation_summary(local4)

  # softmax, i.e. softmax(WX + b)
  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=0.0)
    biases = _variable_on_cpu('biases', [NUM_CLASSES],
                              tf.constant_initializer(0.0))
    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
    _activation_summary(softmax_linear)

  return softmax_linear

次に推論演算を記述する TensorBoard から生成されたグラフを示します:

課題: 推論の出力は正規化されていないロジットです。tf.softmax() を使って正規化された予測を返すようにネットワークアーキテクチャを編集してみましょう。

課題: inference() のモデル・アーキテクチャは cuda-convnet で指定されている CIFAR-10 モデルと少し違います。特に、Alex のオリジナル・モデルのトップ層は局所的に接続され全結合されません。トップ層における局所的に接続されたアーキテクチャを正確に再現するためにアーキテクチャを編集してみましょう。

inputs() と inference() 関数はモデル上の評価を遂行するために必要な全てのコンポーネントを提供しています。
そこで焦点をモデルの訓練のための演算の構築にシフトします。

モデル訓練

N-項(多項)分類ネットワークを訓練する通常のメソッドは 多項ロジスティック回帰、別名 softmax 回帰です。
Softmax 回帰は出力に softmax 非線形性を適用して正規化された (normalized) 予測とラベルの 1-hot エンコーディング 間の 交差エントロピー を計算します。

また正則化 (regularization) のためには、全ての学習された変数に 荷重減衰 (weight decay) 損失を適用します。

loss() 関数によって返される、モデルに対する目的関数は交差エントロピー損失とこれらの荷重減衰の全ての項の合計です。

def loss(logits, labels):
  """Add L2Loss to all the trainable variables.
  Add summary for for "Loss" and "Loss/avg".
  Args:
    logits: Logits from inference().
    labels: Labels from distorted_inputs or inputs(). 1-D tensor
            of shape [batch_size]
  Returns:
    Loss tensor of type float.
  """
  # バッチに渡る平均交差エントロピー損失を計算します。
  labels = tf.cast(labels, tf.int64)
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
      logits, labels, name='cross_entropy_per_example')
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  tf.add_to_collection('losses', cross_entropy_mean)

  # 損失総計は、交差エントロピー損失に全ての荷重減衰項 (L2 loss) を加算したものとして定義されます。
  return tf.add_n(tf.get_collection('losses'), name='total_loss')

scalar_summary を使って TensorBoard で視覚化することができます。
cifar10.train() に埋め込まれています。

def train(total_loss, global_step):
  ...
  tf.scalar_summary('learning_rate', lr)

  loss_averages_op = _add_loss_summaries(total_loss)

def _add_loss_summaries(total_loss):
  loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
  losses = tf.get_collection('losses')
  loss_averages_op = loss_averages.apply(losses + [total_loss])

  for l in losses + [total_loss]:
    tf.scalar_summary(l.op.name +' (raw)', l)
    tf.scalar_summary(l.op.name, loss_averages.average(l))

  return loss_averages_op
 

CIFAR-10 Loss

時間とともに 指数的に減衰する 学習率で標準的な 勾配降下 アルゴリズム(他のメソッドについては Training を見てください)を用いてモデルを訓練します。

CIFAR-10 Learning Rate Decay

 

cifar10#train() 関数は、勾配を計算して学習された変数を更新することにより目的(関数)を最小化するに必要な演算を追加します(詳細は GradientDescentOptimizer を参照)。
画像の一つのバッチのためにモデルを訓練し更新するために必要な全ての計算を実行する演算を返します。

def train(total_loss, global_step):
  """Train CIFAR-10 model.
  Create an optimizer and apply to all trainable variables. Add moving
  average for all trainable variables.
  Args:
    total_loss: Total loss from loss().
    global_step: Integer Variable counting the number of training steps
      processed.
  Returns:
    train_op: op for training.
  """
  # Variables that affect learning rate.
  num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
  decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)

  # Decay the learning rate exponentially based on the number of steps.
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                  global_step,
                                  decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR,
                                  staircase=True)
  tf.scalar_summary('learning_rate', lr)

  # Generate moving averages of all losses and associated summaries.
  loss_averages_op = _add_loss_summaries(total_loss)

  # 勾配を計算します。
  with tf.control_dependencies([loss_averages_op]):
    opt = tf.train.GradientDescentOptimizer(lr)
    grads = opt.compute_gradients(total_loss)

  # 勾配を適用します。
  apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

  # Add histograms for trainable variables.
  for var in tf.trainable_variables():
    tf.histogram_summary(var.op.name, var)

  # Add histograms for gradients.
  for grad, var in grads:
    if grad:
      tf.histogram_summary(var.op.name + '/gradients', grad)

  # Track the moving averages of all trainable variables.
  variable_averages = tf.train.ExponentialMovingAverage(
      MOVING_AVERAGE_DECAY, global_step)
  variables_averages_op = variable_averages.apply(tf.trainable_variables())

  with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
    train_op = tf.no_op(name='train')

  return train_op
 

モデルの Launching と訓練

モデルを構築したので、スクリプト cifar10_train.py でそれを launch して訓練演算を実行しましょう。

python cifar10_train.py

注意: 最初に CIFAR-10 チュートリアルの任意のターゲットを実行する時、CIFAR-10 データセットは自動的にダウンロードされます。データセットは ~ 160MB あり、最初の実行ではコーヒーでも飲みたくなるかもしれません。

出力は以下のようなものです:

Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes.
2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch)
2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch)
2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch)
2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch)
2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch)
2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch)
...

スクリプトは、10 ステップ毎の損失の総計と最後のバッチが処理された速度を報告します。

    for step in xrange(FLAGS.max_steps):
      start_time = time.time()
      _, loss_value = sess.run([train_op, loss])
      duration = time.time() - start_time

      assert not np.isnan(loss_value), 'Model diverged with loss = NaN'

      if step % 10 == 0:
        num_examples_per_step = FLAGS.batch_size
        examples_per_sec = num_examples_per_step / duration
        sec_per_batch = float(duration)

        format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                      'sec/batch)')
        print (format_str % (datetime.now(), step, loss_value,
                             examples_per_sec, sec_per_batch))

      if step % 100 == 0:
        summary_str = sess.run(summary_op)
        summary_writer.add_summary(summary_str, step)

      # Save the model checkpoint periodically.
      if step % 1000 == 0 or (step + 1) == FLAGS.max_steps:
        checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
        saver.save(sess, checkpoint_path, global_step=step)

2、3コメントがあります:

  • データの最初のバッチは(例えば数分間)過度に遅いかもしれません、これは前処理するスレッドが 20,000 の前処理された CIFAR 画像でシャッフリング・キューを満たすためです。
  • 報告された損失はもっとも最近のバッチの平均損失です。この損失は交差エントロピーと全ての荷重減衰項の合計であることを覚えていてください。
  • バッチの処理速度に目を向けてください。上で表示された数値は Tesla K40c 上で得られたものです。もし貴方が CPU 上で実行しているのであれば、より遅いパフォーマンスが予想されます。

課題: 実験時、最初の訓練ステップが非常に長くかかり時に煩わしいかもしれません。初期化時にキューを満たす画像の数を減らしてみてください。cifar10.py の NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN を検索してください。

IMAGE_SIZE = 24
NUM_CLASSES = 10
NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000
IMAGE_SIZE = cifar10_input.IMAGE_SIZE
NUM_CLASSES = cifar10_input.NUM_CLASSES
NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL

cifar10_train.py は定期的(1000 ステップ毎)に チェックポイント・ファイル に全てのモデルパラメータを 保存 しますが、しかしモデルを評価はしません。

  # Save the model checkpoint periodically.
  if step % 1000 == 0 or (step + 1) == FLAGS.max_steps:
    checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
    saver.save(sess, checkpoint_path, global_step=step)

チェックポイント・ファイルは予測性能を計測するためには cifar10_eval.py で使用されます。(後述の「モデルを評価する」を見てください。)

もしこれまでのステップを追ったのであれば、今貴方は CIFAR-10 モデルの訓練が始められました。おめでとう!

cifar10_train.py から返される端末テキストはどのようにモデルが訓練されるか最少限の考察を提供しますが、訓練時のモデルへの更なる洞察を望むこともあるでしょう:

  • 損失は本当に減少しているのか、あるいは単なるノイズか?
  • モデルには妥当な画像が提供されているか?
  • 勾配は、活性化は、そして重みは妥当か?
  • 学習率は現在幾つか?

TensorBoard はこれらの機能を提供します、SummaryWriter を通して cifar10_train.py から定期的にエクスポートされたデータを表示します。

例えば、訓練時の local3 特徴における活性化の分布と疎性の程度がどのように進むかを見ることができます:


 

個々の損失関数は、総計の損失と同様に、時間を追って追跡するのに特に興味深いです。けれども、損失は、訓練で使用される小さいバッチサイズゆえにかなりの量のノイズを見せます。実際に移動平均を生の値に加えて視覚化することは非常に有用であることを見出すでしょう。スクリプトがこの目的のために ExponentialMovingAverage をどのように使うかを見てください。

def _add_loss_summaries(total_loss):
  """Add summaries for losses in CIFAR-10 model.
  Generates moving average for all losses and associated summaries for
  visualizing the performance of the network.
  Args:
    total_loss: Total loss from loss().
  Returns:
    loss_averages_op: op for generating moving averages of losses.
  """
  # Compute the moving average of all individual losses and the total loss.
  loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
  losses = tf.get_collection('losses')
  loss_averages_op = loss_averages.apply(losses + [total_loss])

  # Attach a scalar summary to all individual losses and the total loss; do the
  # same for the averaged version of the losses.
  for l in losses + [total_loss]:
    # Name each loss as '(raw)' and name the moving average version of the loss
    # as the original loss name.
    tf.scalar_summary(l.op.name +' (raw)', l)
    tf.scalar_summary(l.op.name, loss_averages.average(l))

  return loss_averages_op
 

モデルを評価する

訓練されたモデルが取っておいたデータセット上でどのように上手くやるか評価してみましょう。モデルはスクリプト cifar10_eval.py で評価されます。これは inference() 関数でモデルを構築し、CIFAR-10 の評価セットの 10,000 画像全てを使います。それは 1 における正確性 (precision at 1): どのくらいの頻度で(1番)トップの予測が画像の真のラベルにマッチするかを計算します。

def evaluate():
  """Eval CIFAR-10 for a number of steps."""
  with tf.Graph().as_default():
    # Get images and labels for CIFAR-10.
    eval_data = FLAGS.eval_data == 'test'
    images, labels = cifar10.inputs(eval_data=eval_data)

    # Build a Graph that computes the logits predictions from the
    # inference model.
    logits = cifar10.inference(images)

    # Calculate predictions.
    top_k_op = tf.nn.in_top_k(logits, labels, 1)

    # Restore the moving average version of the learned variables for eval.
    variable_averages = tf.train.ExponentialMovingAverage(
        cifar10.MOVING_AVERAGE_DECAY)
    variables_to_restore = variable_averages.variables_to_restore()
    saver = tf.train.Saver(variables_to_restore)

    # Build the summary operation based on the TF collection of Summaries.
    summary_op = tf.merge_all_summaries()

    graph_def = tf.get_default_graph().as_graph_def()
    summary_writer = tf.train.SummaryWriter(FLAGS.eval_dir,
                                            graph_def=graph_def)

    while True:
      eval_once(saver, summary_writer, top_k_op, summary_op)
      if FLAGS.run_once:
        break
      time.sleep(FLAGS.eval_interval_secs)

訓練時にモデルがどのように改善していくかをモニタするために、cifar10_train.py で作成された最新のチェックポイント・ファイル上で定期的に評価スクリプトが動作します。

python cifar10_eval.py

同じ GPU 上で評価と訓練バイナリを実行しないように注意してください。さもないとメモリ不足になるでしょう。もし利用可能であれば別の GPU 上で評価を実行するか、あるいは同じ GPU 上で評価を実行する間は訓練バイナリを中断することを考えてください。

次のような出力が見られるはずです:

2015-11-06 08:30:44.391206: precision @ 1 = 0.860
...

スクリプトは単に “precision(精度) @ 1” を定期的に返すだけです — この場合は 86 % の精度を返しています。

cifar10_eval.py はまた要約をエクスポートします、これは TensorBoard で視覚化されることができます。これらの要約は評価時のモデルへの追加の洞察を提供します。

訓練スクリプトは全ての学習された変数全ての 移動平均 バージョンを計算します。評価スクリプトは学習されたモデルパラメータ全てを移動平均バージョンで代用します。この代用は評価時のモデル性能を押し上げます。

課題: 平均化されたパラメータを使用すると precision @ 1 で計測した時予測性能を約 3% 押し上げることができます。モデルに対する平均化されたパラメータを使用しないように cifar10_eval.py を編集して予測性能が落ちることを確認しましょう。

 

複数の GPU カードを使ってモデルを訓練する

現代的なワークステーションは科学計算のために複数の GPU を装備できますが、TensorFlow は訓練演算を複数の GPU カードに渡って同時に実行することでこの環境が利用できます。

並列的・分散的なマナーによるモデル訓練は訓練プロセスのコーディネートが必要となります。便宜上、データのサブセット上で訓練したモデルの一つのコピーをモデル・レプリカと呼称します。

モデル・パラメータの非同期更新を単純なやりかたで行なうことは最適以下の訓練性能につながります、何故なら個々のモデル・レプリカはモデル・パラメータの最新ではないコピーの上で訓練されるかもしれないからです。
逆に、完全に同期した更新を行なう場合はもっとも遅いモデル・レプリカ並に遅いことになります。

複数の GPU カードを装備するワークステーションでは、各 GPU は同種の速度を持ち CIFAR-10 モデル全体を実行する十分なメモリを持つでしょう。そこで、私たちの訓練システムを次のような方法で設計することを選択します :

  • 各 GPU に個々のモデル・レプリカを配置する。
  • モデル・パラメータは同期的に、全ての GPU がデータのバッチ処理を終了するまで待ってから更新する。

ここにこのモデルのダイアグラムを示します :

 

各 GPU はデータの唯一のバッチに対して勾配だけでなく推論を計算することに注意してください。このセットアップは GPU に渡ってデータのより大きなバッチを効率的に分割することを許可します。

このセットアップは全ての GPU がモデル・パラメータを共有することを必要とします。良く知られた事実として GPU へのそして GPU からのデータ転送は非常に遅いです。このため、(GPU ではなくて)CPU 上で全てのモデル・パラメータを保持し更新することにしました。(図の緑色の枠線のボックスを見てください。)モデル・パラメータのフレッシュなセットは、データの新しいバッチが全ての GPU で処理された時に GPU に転送されます。

GPU は演算で同期されます。全ての勾配は GPU から蓄積されて平均化されます(緑色のボックスを見てください)。モデル・パラメータは、モデル・レプリカに渡って平均化された勾配で更新されます。

デバイス上に変数と演算を配置する

演算と変数のデバイス上への配置は幾つかの特別な抽象化が必要です。必要な最初の抽象化は、一つのモデル・レプリカのために推論と勾配を計算するための関数です。コードレベルではこの抽象化を “タワー” と呼んでいます。各タワーに2つの属性を設定しなければなりません:

  • タワー内の全ての演算に対する一意な名前。tf.name_scope() は、スコープを追加することでこの一意の名前を提供します。例えば、最初のタワーの全ての演算は tower_0、例えば tower_0/conv1/Conv2D で付加されます。
  • タワーで演算を実行するための望ましいハードウェアデバイス。tf.device() がこれを指定します。例えば、最初のタワーの全ての演算は device(‘/gpu:0’) スコープに存在しますが、これはそれらが最初の GPU で実行されるべきであることを示しています。
TOWER_NAME = 'tower'
"""A binary to train CIFAR-10 using multiple GPU's with synchronous updates.
Accuracy:
cifar10_multi_gpu_train.py achieves ~86% accuracy after 100K steps (256
epochs of data) as judged by cifar10_eval.py.
Speed: With batch_size 128.
System        | Step Time (sec/batch)  |     Accuracy
--------------------------------------------------------------------
1 Tesla K20m  | 0.35-0.60              | ~86% at 60K steps  (5 hours)
1 Tesla K40m  | 0.25-0.35              | ~86% at 100K steps (4 hours)
2 Tesla K20m  | 0.13-0.20              | ~84% at 30K steps  (2.5 hours)
3 Tesla K20m  | 0.13-0.18              | ~84% at 30K steps
4 Tesla K20m  | ~0.10                  | ~84% at 30K steps
Usage:
Please see the tutorial and website for how to download the CIFAR-10
data set, compile the program and train the model.
http://tensorflow.org/tutorials/deep_cnn/
"""

tf.app.flags.DEFINE_integer('num_gpus', 1,  """How many GPUs to use.""")

def train():
  with tf.Graph().as_default(), tf.device('/cpu:0'):
    ...
    # Calculate the gradients for each model tower.
    tower_grads = []
    for i in xrange(FLAGS.num_gpus):
      with tf.device('/gpu:%d' % i):
        with tf.name_scope('%s_%d' % (cifar10.TOWER_NAME, i)) as scope:
          # Calculate the loss for one tower of the CIFAR model. This function
          # constructs the entire CIFAR model but shares the variables across
          # all towers.
          loss = tower_loss(scope)

          # Reuse variables for the next tower.
          tf.get_variable_scope().reuse_variables()

          # Retain the summaries from the final tower.
          summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope)

          # Calculate the gradients for the batch of data on this CIFAR tower.
          grads = opt.compute_gradients(loss)

          # Keep track of the gradients across all towers.
          tower_grads.append(grads)

全ての変数は CPU にとどめられそしてマルチ-GPU 版ではそれらを共有するために tf.get_variable() でアクセスされます。how-to の 変数を共有する を見てください。

def train():
  with tf.Graph().as_default(), tf.device('/cpu:0'):
    # Create a variable to count the number of train() calls. This equals the
    # number of batches processed * FLAGS.num_gpus.
    global_step = tf.get_variable(
        'global_step', [],
        initializer=tf.constant_initializer(0), trainable=False)

複数の GPU カード上でモデルを Launching して訓練する

もし貴方のマシン上に幾つかの GPU カードをインストールしていれるのであれば、cifar10_multi_gpu_train.py スクリプトでモデルをより速く訓練するためにそれらを使用できます。訓練スクリプトのこのバージョンは複数の GPU カードに渡ってモデルを並列化します。

python cifar10_multi_gpu_train.py --num_gpus=2

訓練スクリプトは次のように出力するはずです :

Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes.
2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch)
2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch)
2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch)
2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch)
2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch)
2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch)
...

使用される GPU カード数は 1 にデフォルト設定されています。貴方のマシン上で 1 GPU のみが利用可能であるならば、仮にそれ以上を望んだとしても全ての計算はその上に配置されます。

課題:
cifar10_train.py のデフォルト設定では 128 のバッチサイズ上で動作します。64 のバッチサイズで cifar10_multi_gpu_train.py を 2 GPU 上で実行して訓練速度を比較してみてください。

 

次のステップ

おめでとうございます! 貴方は CIFAR-10 チュートリアルを終えました。

もし貴方自身の画像分類システムを開発して訓練することに興味があるのであれば、このチュートリアルを fork して、貴方の画像分類問題にアドレスするためにコンポーネントを置き換えることを勧めます。

課題:
Street View House Numbers (SVHN) データセットをダウンロードしてください。CIFAR-10 チュートリアルを fork して入力データとして SVHN と交換しましょう。予測性能を改善するためにネットワークアーキテクチャを適合させてください。

 

以上

AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com