ホーム » 「TF-Slim」タグがついた投稿

タグアーカイブ: TF-Slim

TF-Slim : TensorFlow 軽量ライブラリ Slim (2)

TF-Slim : TensorFlow 軽量ライブラリ Slim (2)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/27/2017

* 本ページは、github 上の TensorFlow-Slim の README.md の後半を動作確認・翻訳した上で適宜、補足説明したものです :
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/README.md
* 前半部翻訳は こちら
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

モデルの訓練

TensorFlow モデルの訓練は、モデル、損失関数、勾配計算そして損失に関するモデルの重みの勾配を反復的に計算してそれに従って重みを更新する訓練ルーチンが必要です。TF-Slim は、訓練と評価ルーチンを実行する一般的な損失関数とヘルパー関数の両者のセットを提供します。

損失

損失関数は最小化したい量を定義します。分類問題のためには、これは典型的には真の分布と予測される確率分布の間のクラスに渡る交差エントロピーです。回帰問題のためには、これはしばしば予測値と真の値の平方和誤差 (SSD, sum-of-squares differences) です。

マルチタスク学習モデルのような、あるモデルでは同時に複数の損失関数の使用を必要とします。換言すれば、究極的に最小化される損失関数は様々な他の損失関数の総和です。例えば、各ピクセルのカメラからの深度 (= 原文: the depth from the camera of each pixel) に加えて画像の場面のタイプの両者を予測するというモデルを考えてください。このモデルの損失関数は分類損失と深度予測損失の総和になるでしょう。

TF-Slim は、損失モジュールを通して損失関数を定義して追跡するための使いやすい機構を提供します。VGG ネットワークを訓練することを望む単純なケースについて考えます :

import tensorflow as tf
vgg = tf.contrib.slim.nets.vgg

# 画像とラベルをロードする。
images, labels = ...

# モデルを作成する。
predictions, _ = vgg.vgg_16(images)

# 損失関数を定義して損失総和を得る。
loss = slim.losses.softmax_cross_entropy(predictions, labels)

この例では、(TF-Slim の VGG 実装を使用して) モデルを作成することから始めて、標準的な分類損失を追加しています。さて、複数の出力を生成するマルチタスク・モデルを持つケースに取り掛かりましょう :

# 画像とラベルをロードする。
images, scene_labels, depth_labels = ...

# モデルを作成する。
scene_predictions, depth_predictions = CreateMultiTaskModel(images)

# 損失関数を定義して損失総和を得ます。
classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)
sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)

# 次の2行は同じ効果を持ちます :
total_loss = classification_loss + sum_of_squares_loss
total_loss = slim.losses.get_total_loss(add_regularization_losses=False)

この例では、slim.losses.softmax_cross_entropy と slim.losses.sum_of_squares を呼び出すことにより追加した2つの損失を持ちます。それらを一緒に (total_loss) 加算するか slim.losses.get_total_loss() を呼び出すことで損失総和を得ることができます。これはどのように動作するのでしょう? TF-Slim を通して損失関数を作成する時、TF-Slim はその損失を損失関数の特別な TensorFlow コレクションに追加します。これは手動で損失総和を管理することを有効にするか、TF-Slim に貴方のためにそれらを管理させることを可能にします。

TF-Slim に貴方のために損失を管理させることを望み、しかしカスタム損失関数を持つことを望む場合はどうでしょう? loss_ops.py はまたこの損失を TF-Slim コレクションに追加する関数を持ちます。例えば :

# 画像とラベルをロードする。
images, scene_labels, depth_labels, pose_labels = ...

# モデルを作成する。
scene_predictions, depth_predictions, pose_predictions = CreateMultiTaskModel(images)

# 損失関数を定義して損失総和を得る。
classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)
sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)
pose_loss = MyCustomLossFunction(pose_predictions, pose_labels)
slim.losses.add_loss(pose_loss) # TF-Slim に追加の損失について知らせる。

# 損失総和を計算する次の2つの方法は同値です :
regularization_loss = tf.add_n(slim.losses.get_regularization_losses())
total_loss1 = classification_loss + sum_of_squares_loss + pose_loss + regularization_loss

# (正則化損失はデフォルトで損失総和に含まれます。)
total_loss2 = slim.losses.get_total_loss()

この例では、再び手動で損失総和関数を生成することもできるし、あるいは TF-Slim に追加の損失について知らせて TF-Slim のこの損失を処理させることもできます。

 

訓練ループ

TF-Slim は、learning.py で見つかる、モデル訓練のための単純でしかし強力なツールのセットを提供します。これらは、繰り返し損失を計測して、勾配を計算してモデルをディスクに保存する訓練関数を含み、もちろん勾配を操作するための幾つかの便利な関数も含みます。例えば、モデル、損失関数そして最適化スキームをひとたび指定すれば、最適化を実行するために slim.learning.create_train_op と slim.learning.train を呼び出すことができます :

g = tf.Graph()

# モデルを作成して損失を指定する…
...

total_loss = slim.losses.get_total_loss()
optimizer = tf.train.GradientDescentOptimizer(learning_rate)

# create_train_op は、損失を求めるたびに、
# update_ops が実行されて計算される勾配がまた適用されることを保証します。
train_op = slim.learning.create_train_op(total_loss, optimizer)
logdir = ... # ここで checkpoint が保存されます。

slim.learning.train(
    train_op,
    logdir,
    number_of_steps=1000,
    save_summaries_secs=300,
    save_interval_secs=600):

この例では、slim.learning.train に train_op が提供され、これは (a) 損失を計算して (b) 勾配ステップを適用するために使用されます。logdir は checkpoint とイベントファイルが保存されるディレクトリを指定します。取られる勾配ステップの数は任意の数に制限できます。この場合、1000 ステップを取ることを求めています。最後に、save_summaries_secs=300 は 5 分毎に summaries を計算することを示し、save_interval_secs=600 は 10 分毎にモデル checkpoint を保存することを示します。

実施例: VGG16 モデルを訓練する

これを示すために、VGG ネットワークを訓練する次のサンプルを検証しましょう :

import tensorflow as tf

slim = tf.contrib.slim
vgg = tf.contrib.slim.nets.vgg

...

train_log_dir = ...
if not tf.gfile.Exists(train_log_dir):
  tf.gfile.MakeDirs(train_log_dir)

with tf.Graph().as_default():
  # データ・ロードをセットアップ :
  images, labels = ...

  # モデルを定義する :
  predictions = vgg.vgg16(images, is_training=True)

  # 損失関数を指定する :
  slim.losses.softmax_cross_entropy(predictions, labels)

  total_loss = slim.losses.get_total_loss()
  tf.summary.scalar('losses/total_loss', total_loss)

  # 最適化スキームを指定する :
  optimizer = tf.train.GradientDescentOptimizer(learning_rate=.001)

  # create_train_op は損失を得るためにそれを評価する時、
  # update_ops が行なわれて勾配更新が計算されることを保証します。
  train_tensor = slim.learning.create_train_op(total_loss, optimizer)

  # 実際に訓練を実行します。
  slim.learning.train(train_tensor, train_log_dir)

 

既存モデルを再調整する

Checkpoint から変数を復元する簡単な復習

モデルが訓練された後は、それは与えられた checkpoint から変数を復元する tf.train.Saver() を使用して復元されます。多くの場合、tf.train.Saver() は全てあるいは 2, 3 だけの変数を復元する単純な機構を提供します。

# 幾つかの変数を作成する。
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# 全ての変数を復元するための op を追加する。
restorer = tf.train.Saver()

# 幾つかの変数を復元するための op を追加する。
restorer = tf.train.Saver([v1, v2])

# 後で、モデルを launch し、ディスクから変数を復元するために saver を使用し、
# そしてモデルである作業を行ないます。
with tf.Session() as sess:
  # ディスクから変数を復元する。
  restorer.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # 
モデルである作業を行なう
  ...

より詳細は、変数 のページの「変数を復元する」と「保存し復元する変数を選択する」の項を見てください。

部分的にモデルを復元する

全体的に新しいデータセット上か新しいタスク上でさえも事前に訓練されたモデルを再調整することはしばしば望ましいです。これらの状況では、復元する変数のサブセットを選択するために TF-Slim のヘルパー関数を使用できます :

# 幾つかの変数を作成する。
v1 = slim.variable(name="v1", ...)
v2 = slim.variable(name="nested/v2", ...)
...

# 復元する変数のリストを得る (それは 'v2' のみを含みます)。
# これらは全て同値な方法です :
variables_to_restore = slim.get_variables_by_name("v2")
# あるいは
variables_to_restore = slim.get_variables_by_suffix("2")
# あるいは
variables_to_restore = slim.get_variables(scope="nested")
# あるいは
variables_to_restore = slim.get_variables_to_restore(include=["nested"])
# あるいは
variables_to_restore = slim.get_variables_to_restore(exclude=["v1"])

# 変数を復元するために使用される saver を作成します。
restorer = tf.train.Saver(variables_to_restore)

with tf.Session() as sess:
  # 変数をディスクから復元する。
  restorer.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # Do some work with the model
  ...

異なる変数名でモデルを復元する

checkpoint から変数を復元する時、Saver は checkpoint ファイルの変数名を見つけてそれらを現在のグラフ内の変数にマップします。上では、変数のリストを渡すことで saver を作成しました。この場合、checkpoint ファイルで見つける変数の名前は各提供される変数の var.op.name から暗黙的に得られます。

これは、checkpoint ファイルの変数名がグラフのそれらとマッチする時は上手く動作します。けれども、時々、変数が現在のグラフのものとは異なる名前の checkpoint からモデルを復元することを望みます。この場合、各 checkpoint 変数名から各グラフ変数にマップする辞書を Saver に提供しなければなりません。

次のサンプルを考えます、ここでは checkpoint 変数名が単純な関数経由で得られます :

#  'conv1/weights' は 'vgg16/conv1/weights' から復元されることを仮定します。
def name_in_checkpoint(var):
  return 'vgg16/' + var.op.name

# 'conv1/weights' と 'conv1/bias' は 'conv1/params1' と 'conv1/params2' から復元されることを仮定します。
def name_in_checkpoint(var):
  if "weights" in var.op.name:
    return var.op.name.replace("weights", "params1")
  if "bias" in var.op.name:
    return var.op.name.replace("bias", "params2")

variables_to_restore = slim.get_model_variables()
variables_to_restore = {name_in_checkpoint(var):var for var in variables_to_restore}
restorer = tf.train.Saver(variables_to_restore)

with tf.Session() as sess:
  # ディスクから変数を復元する。
  restorer.restore(sess, "/tmp/model.ckpt")

異なるタスク上でモデルを再調整する

事前訓練された VGG16 モデルを持つ場合を考えましょう。モデルは、1000 クラスを持つ ImageNet データセット上で訓練されました。けれども、20 クラスだけを持つ Pascal VOC データセットにそれを適用したいとします。そのためには、最終層を除く事前訓練されたモデルの値を使用して新しいモデルを初期化できます :

# Pascal VOC データをロードする
image, label = MyPascalVocDataLoader(...)
images, labels = tf.train.batch([image, label], batch_size=32)

# モデルを作成する
predictions = vgg.vgg_16(images)

train_op = slim.learning.create_train_op(...)

# ImageNet で訓練されたモデルがどこに保存されているかを指定します。
model_path = '/path/to/pre_trained_on_imagenet.checkpoint'

# 新しいモデルがどこに存在するかを指定します :
log_dir = '/path/to/my_pascal_model_dir/'

# 畳込み層のみを復元します :
variables_to_restore = slim.get_variables_to_restore(exclude=['fc6', 'fc7', 'fc8'])
init_fn = assign_from_checkpoint_fn(model_path, variables_to_restore)

# 訓練を開始します。
slim.learning.train(train_op, log_dir, init_fn=init_fn)

 

モデルを評価する

モデルをひとたび訓練したら (あるいはモデルが訓練中である間でさえも) モデルが実際にどの程度上手くやるのかを見たくなります。これは、モデル性能を等級分けする評価メトリック (= 測定基準) のセットとデータを実際にロードして、推論を実行して、結果を ground truth と比較して評価スコアを記録する、評価コードを選択することでなされます。このステップは一度実行されるか定期的に繰り返されます。

メトリック

メトリックをパフォーマンス尺度として定義します、これは損失関数ではありませんが (損失は訓練中に直接的に最適化されますので)、それはモデルを評価する目的で依然として興味があります。例えば、ログ損失を最小化することを望むかもしれませんが、興味あるメトリックは F1 スコア (テスト精度)、あるいは Intersection Over Union スコア (微分可能でないので、損失としては使用できません) かもしれません。TF-Slim は、モデルの評価を容易にするメトリック演算のセットを提供します。抽象的には、メトリックの値の計算は3つのパートに分割されます :

  1. 初期化: メトリックを計算するのに使用される変数を初期化する。
  2. 集計 (Aggregation): メトリックの計算に使用される演算 (sums, etc) を実行する。
  3. ファイナリゼーション (Finalization): (オプション) メトリック値を計算する最後の演算を実行する。例えば、means, mins, maxes, etc. を計算する。

例えば、mean_absolute_error を計算するためには、2つの変数、count と total 変数がゼロに初期化されます。集計の間には、予測とラベルの幾つかのセットを観測して、それらの絶対値差を計算して総和を total に追加します。他の値を観測するたびに、count はインクリメントされます。最後に、ファイナリゼーションの間には、平均を得るために total が count で除算されます。

次のサンプルはメトリックを宣言するための API を示します。メトリックはしばしば訓練セット (その上で損失は計算されました) とは異なるテストセット上で評価されますので、テストデータを使用しているものと仮定します :

images, labels = LoadTestData(...)
predictions = MyModel(images)

mae_value_op, mae_update_op = slim.metrics.streaming_mean_absolute_error(predictions, labels)
mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels, labels)
pl_value_op, pl_update_op = slim.metrics.percentage_less(mean_relative_errors, 0.3)

サンプルが示すように、メトリックの作成は2つの値を返します : value_op と update_op です。value_op は冪等 (べきとう = idempotent) な演算でメトリックの現在の値を返します。update_op は、メトリックの値を返すだけでなく上で述べた集計ステップを実行する演算です。

各 value_op と update_op の追跡は面倒でしょう。これを処理するために、TF-Slim は2つの便利な関数を提供します :

# value と update ops を2つのリストに集計する :
value_ops, update_ops = slim.metrics.aggregate_metrics(
    slim.metrics.streaming_mean_absolute_error(predictions, labels),
    slim.metrics.streaming_mean_squared_error(predictions, labels))

# value と udpate ops を2つの辞書に集計する :
names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
    "eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels),
    "eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels),
})

実施例: 複数のメトリックを追跡する

全てをまとめます :

import tensorflow as tf

slim = tf.contrib.slim
vgg = tf.contrib.slim.nets.vgg


# データをロードする
images, labels = load_data(...)

# Define the network
predictions = vgg.vgg_16(images)

# 計算するためのメトリックを選択します :
names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
    "eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels),
    "eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels),
})

# データの 1000 バッチを使用してモデルを評価する ;
num_batches = 1000

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  sess.run(tf.local_variables_initializer())

  for batch_id in range(num_batches):
    sess.run(names_to_updates.values())

  metric_values = sess.run(name_to_values.values())
  for metric, value in zip(names_to_values.keys(), metric_values):
    print('Metric %s has value: %f' % (metric, value))

metric_ops.pylayers.pyloss_ops.py を使用することなしに分離して利用可能であることに注意してください。

評価ループ

TF-Slim は評価モジュール ( evaluation.py ) を提供します、これは metric_ops.py モジュールからのメトリックを使用してモデル評価スクリプトを書くためのヘルパー関数を含みます。これらは、定期的に評価を実行し、データのバッチに渡りメトリックを評価し、そしてメトリック結果を出力して要約するための関数を含みます。例えば :

import tensorflow as tf

slim = tf.contrib.slim

# データをロードする
images, labels = load_data(...)

# ネットワークを定義する
predictions = MyModel(images)

# 計算するためのメトリックを選択する :
names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
    'accuracy': slim.metrics.accuracy(predictions, labels),
    'precision': slim.metrics.precision(predictions, labels),
    'recall': slim.metrics.recall(mean_relative_errors, 0.3),
})

# Create the summary ops such that they also print out to std output:
summary_ops = []
for metric_name, metric_value in names_to_values.iteritems():
  op = tf.summary.scalar(metric_name, metric_value)
  op = tf.Print(op, [metric_value], metric_name)
  summary_ops.append(op)

num_examples = 10000
batch_size = 32
num_batches = math.ceil(num_examples / float(batch_size))

# global ステップをセットアップする。
slim.get_or_create_global_step()

output_dir = ... # Where the summaries are stored.
eval_interval_secs = ... # How often to run the evaluation.
slim.evaluation.evaluation_loop(
    'local',
    checkpoint_dir,
    log_dir,
    num_evals=num_batches,
    eval_op=names_to_updates.values(),
    summary_op=tf.summary.merge(summary_ops),
    eval_interval_secs=eval_interval_secs)

 

Authors

Sergio Guadarrama and Nathan Silberman

 

以上

TF-Slim : TensorFlow 軽量ライブラリ Slim

TF-Slim : TensorFlow 軽量ライブラリ Slim
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/16/2017

* 本ページは、github 上の TensorFlow-Slim の README.md の前半を動作確認・翻訳した上で適宜、補足説明したものです:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/README.md
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

TF-Slim は TensorFlow で複雑なモデルを定義し、訓練しそして評価するための軽量ライブラリです。tf-slim のコンポーネントは、tf.contrib.learn のような他のフレームワークに加えて native tensorflow と自由にミックスできます。

 

使い方

import tensorflow.contrib.slim as slim

 

何故 TF-Slim なのか?

TF-Slim はニューラルネットワークの構築、訓練そして評価することを単純にします :

  • ユーザに、ボイラープレート・コードを省くことでモデルを遥によりコンパクトに定義することを可能にします。これは 引数スコーピング と数多くの高位な 変数 の使用を通して成されます。これらのツールは可読性と保守性を高めて、ハイパーパラメータ値のコピー & ペーストからのエラーの可能性を減らし、そしてハイパーパラメータ調整を単純化します。
  • 一般に使用される正則化項 (= regularizer) の提供によりモデルの開発を単純化します。
  • 幾つかの広く利用されているコンピュータ・ビジョン・モデル (e.g., VGG, AlexNet) が slim で開発されてユーザに 利用可能 となっています。これらはブラックボックスとして利用可能ですし、様々な方法で拡張可能です、e.g. 異なる内部層に “multiple heads” を追加することによって。
  • Slim は複雑なモデルを拡張することを簡単にし、既存のモデル・チェックポイントの断片を使用することで訓練アルゴリズムをウォームスタートすることも簡単にしおます。

 

TF-Slim の各種コンポーネントはどのようなものでしょうか?

TF-Slim は独立的に存在するように設計された幾つかのパーツから構成されます。これらは以下の主要な断片 (詳細は後述) を含みます。

  • arg_scope : arg_scope と命名された新しいスコープを提供します、これは (そのスコープ内で) 特定の演算のためのデフォルト引数を定義することをユーザに可能にします。
  • data : TF-Slim の データセット 定義、データ・プロバイダーparallel_readerデコーディング・ユティリティを含みます。
  • evaluation : モデルを評価するためのルーチンを含みます。
  • layers : tensorflow を使用してモデルを構築するための高位な層を含みます。
  • learning : モデルを訓練するためのルーチンを含みます。
  • losses : 一般的に使用される損失関数を含みます。
  • metrics : 人気のある評価メトリックを含みます。
  • nets : VGGAlexNet モデルのような人気のあるネットワーク定義をふくみます。
  • queues : QueueRunners を簡単にそして安全に開始してクローズするためのコンテキスト・マネージャを提供します。
  • regularizers : 重み正則化項を含みます。
  • variables : 変数作成と操作のための便利なラッパーを提供します。

 

モデルを定義する

モデルは簡潔に言えば TF-Slim を使用してその変数、層とスコープを結合することで定義できます。これらの要素の各々は以下のように定義されます。

変数

native TensorFlow の変数作成は事前定義値か初期化機構 (e.g. ガウシアンからランダムにサンプリング) を必要とします。その上、変数が GPU のような特定のデバイス上に作成される必要があるのであれば、その指定は明示的にされなければなりません 。変数作成のために必要なコードを軽減するために、TF-Slim は variables.py で薄いラッパー関数のセットを提供します、これは呼び出し側が変数を簡単に定義することを可能にします。

例えば、重み変数を作成しそれを切断正規分布を使用して初期化し、l2_loss で正則化してそれを CPU 上に置くためには、次を宣言する必要があるだけです :

weights = slim.variable('weights',
                             shape=[10, 10, 3 , 3],
                             initializer=tf.truncated_normal_initializer(stddev=0.1),
                             regularizer=slim.l2_regularizer(0.05),
                             device='/CPU:0')

native TensorFlow では、2つのタイプの変数があることに注意してください : レギュラー (regular) 変数とローカル (transient = 一時) 変数です。変数の大半はレギュラー変数です : 一度作成されれば、それらは saver を使用してディスクに保存されます。ローカル変数は session の間だけ存在してディスクに保存はされません。

TF-Slim ではモデル変数を定義することにより更に変数を区別していて、これはモデルのパラメータを表す変数です。モデル変数は学習の間に訓練か再調整されて評価または推論時にはチェックポイントからロードされます。サンプルは slim.fully_connected または slim.conv2d layer で作成された変数を含みます。非モデル変数 (= Non-model variables) はその他全ての変数で学習か評価時に使用されますが実際に推論を実行する際には必要とされません。例えば、global_step は学習と評価時に使用する変数ですが、実際にはモデルの一部ではありません。同様に、移動平均変数はモデル変数を反映しますが、移動平均はそれ自身はモデル変数ではありません。

モデル変数とレギュラー変数の両者は TF-Slim 経由で簡単に作成されて取得されます :

# モデル変数
weights = slim.model_variable('weights',
                              shape=[10, 10, 3 , 3],
                              initializer=tf.truncated_normal_initializer(stddev=0.1),
                              regularizer=slim.l2_regularizer(0.05),
                              device='/CPU:0')
model_variables = slim.get_model_variables()

# レギュラー変数
my_var = slim.variable('my_var',
                       shape=[20, 1],
                       initializer=tf.zeros_initializer())
regular_variables_and_model_variables = slim.get_variables()

これはどのように動作するのでしょうか?TF-Slim の層経由かあるいは直接に slim.model_variable 関数経由でモデル変数を作成する時、TF-Slim は tf.GraphKeys.MODEL_VARIABLES コレクションに変数を追加します。

貴方自身のカスタム層や変数作成ルーチンを持ち、依然として TF-Slim がモデル変数を管理するあるいは知っていることを望む場合はどうでしょう?TF-Slim はモデル変数をそのコレクションに追加するための便利な関数を提供します。

my_model_variable = CreateViaCustomCode()

# TF-Slim に追加の変数について知らせます。
slim.add_model_variable(my_model_variable)

TensorFlow 演算のセットが非常に広範囲に渡る一方で、ニューラルネットワークの開発者は典型的には、モデルを “層”, “損失”, “メトリック”, そして “ネットワーク” のようなより高いレベルの概念の見地から考えます。畳込み層、完全結合層あるいは BatchNorm 層のような層は単一の TensorFlow 演算よりもより抽象的で典型的には幾つかの演算を含みます。更に、よりプリミティブな演算とは異なり、層は通常は (但し常にではなく) それに関係する変数 (調整可能なパラメータ) を持ちます。例えば、畳込み層は幾つかの低いレベルの演算から構成されます :

  1. 重みとバイアス変数を作成する。
  2. 前の層からの入力で重みを畳み込む。
  3. 畳み込みの結果にバイアスを加算する。
  4. 活性化関数を適用する。

そのままの TensorFlow コードだけを使用すると、これはなかなか面倒です :

input = ...
with tf.name_scope('conv1_1') as scope:
  kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32,
                                           stddev=1e-1), name='weights')
  conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
  biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32),
                       trainable=True, name='biases')
  bias = tf.nn.bias_add(conv, biases)
  conv1 = tf.nn.relu(bias, name=scope)

このコードを繰り返し複写する必要性を軽減するために、TF-Slim はニューラルネットワーク層のより抽象化されたレベルで定義された多くの便利な演算を提供します。例えば、上のコードと相当する TF-Slim コードの呪文を比較してください :

input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')

TF-Slim はニューラルネットワークを構築する多数のコンポーネントのための標準実装を提供します。これらは以下を含みます (訳注 : リンク先はすべて同じ layers.py です) :

TF-Slim

BiasAdd

slim.bias_add

BatchNorm

slim.batch_norm

Conv2d

slim.conv2d

Conv2dInPlane

slim.conv2d_in_plane

Conv2dTranspose (Deconv)

slim.conv2d_transpose

FullyConnected

slim.fully_connected

AvgPool2D

slim.avg_pool2d

Dropout

slim.dropout

Flatten

slim.flatten

MaxPool2D

slim.max_pool2d

OneHotEncoding

slim.one_hot_encoding

SeparableConv2

slim.separable_conv2d

UnitNorm

slim.unit_norm

TF-Slim はまた repeat と stack と呼ばれる2つのメタ演算も提供します、これはユーザに同じ演算を繰り返し実行することを可能にします。例えば、VGG ネットワークからの次のスニペットを考えます、この層(群)はプーリング層間に幾つかの畳込みを連続的に実行します :

net = ...
net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')

このコードの複写を減らす一つの方法は for ループを通すことでしょう :

net = ...
for i in range(3):
  net = slim.conv2d(net, 256, [3, 3], scope='conv3_' % (i+1))
net = slim.max_pool2d(net, [2, 2], scope='pool2')

これは TF-Slim の repeat 演算の使用によって見やすくなります :

net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')

slim.repeat は in-line の同じ引数に適用されるだけでなく、slim.conv2d の続く各呼び出しに割り当てられたスコープにアンダースコアと反復数が追加されたような、スコープを展開する (= unroll) ためにも十分にスマートです。より具体的には、上の例のスコープは ‘conv3/conv3_1’, ‘conv3/conv3_2’ と ‘conv3/conv3_3’ のように命名されるでしょう。

更に、TF-Slim の slim.stack 演算は、層のスタックあるいはタワーを作成するために異なる引数で同じ演算を繰り返し適用することを呼び出し側に可能にします。slim.stack はまた作成された各演算のために新しい tf.variable_scope を作成します。例えば、多層パーセプトロン (MLP) を作成する単純な方法は :

# Verbose way:
x = slim.fully_connected(x, 32, scope='fc/fc_1')
x = slim.fully_connected(x, 64, scope='fc/fc_2')
x = slim.fully_connected(x, 128, scope='fc/fc_3')

# Equivalent, TF-Slim way using slim.stack:
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')

この例では、slim.stack は関数の一つの呼び出しの出力を次へ渡して slim.fully_connected を3回呼び出しています。けれども、各呼び出しにおける隠れユニットの数は 32 から 64 へ 128 へ変わります。複数の畳込みのタワーを単純化するために stack を使用できます :

# Verbose way:
x = slim.conv2d(x, 32, [3, 3], scope='core/core_1')
x = slim.conv2d(x, 32, [1, 1], scope='core/core_2')
x = slim.conv2d(x, 64, [3, 3], scope='core/core_3')
x = slim.conv2d(x, 64, [1, 1], scope='core/core_4')

# Using stack:
slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')

スコープ

TensorFlow ( name_scope, variable_scope ) における幾つかのスコープ機構のタイプに加えて、TF-Slim は arg_scope と呼ばれる新しいスコーピング機構を追加します。この新しいスコープは、一つまたはそれ以上の演算と (arg_scope で定義される演算の各々に渡される) 引数のセットを指定することをユーザに可能にします。この機能はサンプルにより上手く説明されます。次のコード・スニペットを考えましょう :

net = slim.conv2d(inputs, 64, [11, 11], 4, padding='SAME',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv1')
net = slim.conv2d(net, 128, [11, 11], padding='VALID',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv2')
net = slim.conv2d(net, 256, [11, 11], padding='SAME',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv3')

これらの3つの畳込み層が同じハイパーパラメータの多くを共有することは明らかです。2つは同じ padding を持ち、3つ全てが同じ weights_initializer と weight_regularizer を持ちます。このコードは読みづらく括り出されるべき多くの繰り返される値を含みます。一つの解法は変数を使用してデフォルト値を指定することです :

padding = 'SAME'
initializer = tf.truncated_normal_initializer(stddev=0.01)
regularizer = slim.l2_regularizer(0.0005)
net = slim.conv2d(inputs, 64, [11, 11], 4,
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv1')
net = slim.conv2d(net, 128, [11, 11],
                  padding='VALID',
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv2')
net = slim.conv2d(net, 256, [11, 11],
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv3')

この解法は3つ全ての畳込みが正確に同じパラメータ値を共有することは保証しますが、コードの乱雑さを完全には減じていません。arg_scope を使用することで、各層が同じ値を使用してコードを単純化することの両者を確実にできます :

  with slim.arg_scope([slim.conv2d], padding='SAME',
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
                      weights_regularizer=slim.l2_regularizer(0.0005)):
    net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
    net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
    net = slim.conv2d(net, 256, [11, 11], scope='conv3')

サンプルが示すように、arg_scope の使用はコードをよりきれいに単純にしてそして保守を容易にします。引数値が arg_scope で指定される一方でローカルで上書きできることにも気がつくでしょう。特に、padding 引数が ‘SAME’ に指定される一方で、2つ目の畳込みは ‘VALID’ の値でオーバーライドしています。

arg_scopes はまたネストすることも可能で同じスコープで複数の演算も使用できます。例えば :

with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                      weights_regularizer=slim.l2_regularizer(0.0005)):
  with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'):
    net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1')
    net = slim.conv2d(net, 256, [5, 5],
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.03),
                      scope='conv2')
    net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')

この例では、最初の arg_scope は同じ weights_initializer と weights_regularizer 引数をそのスコープの conv2d と fully_connected 層に適用しています。2つ目の arg_scope では、追加のデフォルト引数が conv2d に対してのみ指定されています。 

実施例: VGG16 層を指定する

TF-Slim 変数、演算とスコープを結合することで、通常は非常に複雑なネットワークを非常に少ないコード行で書くことができます。例えば、VGG アーキテクチャ全体は単に次のようなスニペットで定義されます :

def vgg16(inputs):
  with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,
                      weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
                      weights_regularizer=slim.l2_regularizer(0.0005)):
    net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
    net = slim.max_pool2d(net, [2, 2], scope='pool1')
    net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
    net = slim.max_pool2d(net, [2, 2], scope='pool2')
    net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
    net = slim.max_pool2d(net, [2, 2], scope='pool3')
    net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
    net = slim.max_pool2d(net, [2, 2], scope='pool4')
    net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
    net = slim.max_pool2d(net, [2, 2], scope='pool5')
    net = slim.fully_connected(net, 4096, scope='fc6')
    net = slim.dropout(net, 0.5, scope='dropout6')
    net = slim.fully_connected(net, 4096, scope='fc7')
    net = slim.dropout(net, 0.5, scope='dropout7')
    net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8')
  return net

 

前半の翻訳はここまです。Training Models 以後の後半の翻訳は こちら

 

以上

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