Caffe: Tutorial: Blob, 層, そしてネット : Caffe モデルの解剖

Caffe: Tutorial: Blob, 層, そしてネット : Caffe モデルの解剖 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
日時 : 03/30/2017

* 本ページは、Caffe の本家サイトの Tutorial – Blobs, Layers, and Nets: anatomy of a Caffe model を翻訳した上で
適宜、補足説明したものです:
    http://caffe.berkeleyvision.org/tutorial/net_layer_blob.html
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

深層ネットワークは、データのチャンク上で動作する相互接続された層のコレクションとして自然に表現される合成モデルです。Caffe は自身のモデル・スキーマでネットを層ごとに定義します。ネットワークは入力データから損失へと bottom-to-top でモデル全体を定義します。データと導関数(勾配)が forward と backword パス でネットワークを通して流れる時、Caffe はそれらの情報を blob として保持、通信、そして操作します : blob は標準的な配列とフレームワークに対する統一されたメモリ・インターフェイスです。層はモデルと計算の両者の基礎として次に来ます。ネットは層のコレクションと接続として続きます。blob の詳細は、層とネットの中そしてそれらに渡る情報がどのように保持されて通信されるかを記述します。

Solving はモデルと最適化を切り離すために別々に構成されます。

これらのコンポーネントについてより詳しく調べていきます。

 

Blob ストレージと通信

Blob は Caffe により処理され沿って渡される実際のデータに渡るラッパーで、そしてまた内部で CPU と GPU 間の同期機能を提供します。数学的には、blob は C-連続 (contiguous) 流儀で保持される N-次元配列です。

Caffe は blob を使用してデータをストアして通信します。blob はデータ; e.g. 画像のバッチ、モデル・パラメータ、そして最適化のための導関数 etc. を保持する統一されたメモリ・インターフェイスを提供します。

blob は、ミックスされた CPU/GPU 演算の計算と心理的な (mental) オーバーヘッドを CPU ホストから GPU デバイスへ必要に応じて同期することで隠します。ホストとデバイス上のメモリは効率的なメモリ使用のためにオン・デマンド (lazily) で割り当てられます。

画像データのバッチのための慣習的な blob 次元は 数 N x チャネル K x 高さ H x 幅 W です。blob メモリのレイアウトは行優先 (row-major) なので、最後の/最右端の次元は最も速く変更されます。例えば、4D blob において、インデックス (n, k, h, w) の値は物理的にはインデックス ((n * K + k) * H + h) * W + w に位置します。

  • 数 / N はデータのバッチサイズです。バッチ処理は通信とデバイス処理のためにより良いスループットを達成します。ImageNet 訓練のための 256 画像のバッチは N = 256 です。
  • チャネル / K は特徴次元で e.g. RGB 画像のためには K = 3 です。

Caffe サンプルの多くの blob は画像アプリケーションのための 4D 軸ですが、画像でないアプリケーションのためでも blob を使用することは総合的に妥当である点に注意してください。例えば、畳込み多層パーセプトロンのような完全結合ネットワークが単に必要であれば、2D blob (shape (N, D)) を使用して InnerProductLayer を呼び出します(これについてはすぐにカバーします)。

パラメータ blob の次元は層の型と構成に従って多様です。11 x 11 空間次元と 3 入力の 96 フィルタを持つ畳込み層のためには blob は 96 x 3 x 11 x 11 です。1000 出力チャネルと 1024 入力チャネルを持つ内積 (inner product) / 完全結合層のためにはパラメータ blob は 1000 x 1024 です。

カスタムデータのためには入力準備ツールやデータ層をハックすることが必要かもしれません。けれども貴方のデータがひとたびジョブに入れば完了です。層のモジュール性は残りのワークを貴方のために達成します。

 

実装の詳細

blog の勾配はもちろん値にもしばしば興味があるので、blob は2つのメモリ・チャンク、data と diff を保持します。前者は沿って渡す通常のデータで、後者はネットワークにより計算される勾配です。

更に、実際の値は CPU 上または GPU 上に保持されますので、それらにアクセスするために2つの異なる方法があります : const 法、これは値を変更しません、そして mutable 法、これは値を変更します :

const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();

(gpu と diff についても同様).

そのようなデザインである理由は、同期の詳細を隠してデータ転送を最小化する目的で CPU と GPU の間の値を同期するために Blob は SyncedMem クラスを使用するからです。経験則として、値を変更することを望まないのであれば、いつも const コールを使用して貴方自身のオブジェクトにポインタを決して保持しないことです。blob 上で作業する時はいつでも、ポインタを得るために関数を呼び出します、何故ならば SyncedMem はデータをいつコピーするかを見つけ出すためにこれが必要だからです。

実際には GPU が存在する時、低いレベルの詳細は無視して高いレベルのパフォーマンスを維持しながら、CPU コードでディスクから blob へデータをロードし GPU 計算を行なうためにデバイス・カーネルを呼び出しそして blob を次の層へと運びます。全ての層が GPU 実装を持つ限りは、全ての中間データと勾配は GPU に残ります。

Blob がデータをコピーする時の確認を望むのであれば、説明例があります :

// Assuming that data are on the CPU initially, and we have a blob.
const Dtype* foo;
Dtype* bar;
foo = blob.gpu_data(); // data copied cpu->gpu.
foo = blob.cpu_data(); // no data copied since both have up-to-date contents.
bar = blob.mutable_gpu_data(); // no data copied.
// ... some operations ...
bar = blob.mutable_gpu_data(); // no data copied when we are still on GPU.
foo = blob.cpu_data(); // data copied gpu->cpu, since the gpu side has modified the data
foo = blob.gpu_data(); // no data copied since both have up-to-date contents
bar = blob.mutable_cpu_data(); // still no data copied.
bar = blob.mutable_gpu_data(); // data copied cpu->gpu.
bar = blob.mutable_cpu_data(); // data copied gpu->cpu.

 

層計算と接続

層はモデルの本質であり計算の基礎ユニットです。層はフィルタを畳込み、プールし、内積を取り、rectified-linear と sigmoid のような非線形性と他の elementwise な変換を適用し、正規化し、データをロードし、そして softmax と hinge のような損失を計算します。全ての操作のためには 層カタログ を参照してください。最先端の深層学習タスクのために必要な大抵の型はそこにあります。

層は bottom 接続を通して入力を取りtop 接続を通して出力を作成します。

各層の型は3つの重要な計算を定義します : setup, forward そして backward です。

  • Setup: 層とその接続をモデル初期化時に一度だけ初期化します。
  • Forward: bottom から入力が与えられたら出力を計算して top に送ります。
  • Backward: top 出力に関する勾配が与えられたら入力に関する勾配を計算して bottom に送ります。パラメータを持つ層はそのパラメータに関する勾配を計算してそれを内部的に保持します。

特に、2つの Foward と Backward 関数が実装されるでしょう、一つは CPU のためで一つは GPU のためです。GPU 版を実装しないのであれば、層はバックアップ・オプションとして CPU 関数へと退却 (fall back) します。迅速な実験をしたいのであればこれは便利かもしれません、けれどもそれは追加のデータ転送コストが付随します(その入力は GPU から CPU へとコピーされ、その出力は CPU から GPU へコピーバックされます)。

層は全体的にネットワークの演算のために2つのキーとなる責任を持ちます : forward パスは入力を取って出力を生成します、そして backward パスは出力に関する勾配を取り、そしてパラメータと入力に関する勾配を計算します、これらはより早い層へと順番に back-propagate されます。これらのパスは単に各層の forward と backword の合成です。

カスタム層の開発は、ネットワークの構成性とコードのモジュール性により最小限の努力だけが必要です。層のために setup, forward そして backward を定義すればネットに包含する準備ができます。

 

ネット定義と演算

ネットは協調的に関数とその勾配を合成と自動微分で定義します。全ての層の出力の合成は与えられたタスクを行なうために関数を計算し、そして全ての層の backwoard の合成はタスクを学習するために損失からの勾配を計算します。Caffe モデルは end-to-end な機械学習エンジンです。

ネットは計算グラフ – 正確には、有向非巡回グラフ (DAG, directed acyclic graph) に接続された層のセットです。Caffe は forward と backward パスの正しさを保証するために層の任意の DAG のために全ての bookkeeping (帳簿付け) を行います。典型的なネットはディスクからロードするデータ層から始まり、そして分類や復元のようなタスクのための目的を計算する損失層で終わります。

ネットは層とそれらの接続のセットとして平文テキストのモデリング言語で定義されます。単純なロジスティック回帰分類器 :

は以下で定義されます :

name: "LogReg"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  data_param {
    source: "input_leveldb"
    batch_size: 64
  }
}
layer {
  name: "ip"
  type: "InnerProduct"
  bottom: "data"
  top: "ip"
  inner_product_param {
    num_output: 2
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip"
  bottom: "label"
  top: "loss"
}

モデル初期化は Net::Init() で処理されます。初期化は主として2つのことを行ないます : blob と層を作成することにより DAG 全体の足場を設け(C++ ギークのために: ネットワークはそのライフタイムの間 blob と層の所有権を保持します)、そして層の SetUp() 関数を呼び出します。ネットワーク・アーキテクチャ全体の正しさを検証するような、他の帳簿付けのような事のセットもまた行ないます。また、初期化の間 Net はその初期化について進むに従い INFO にロギングすることで説明します :

I0902 22:52:17.931977 2079114000 net.cpp:39] Initializing net from parameters:
name: "LogReg"
[...model prototxt printout...]
# construct the network layer-by-layer
I0902 22:52:17.932152 2079114000 net.cpp:67] Creating Layer mnist
I0902 22:52:17.932165 2079114000 net.cpp:356] mnist -> data
I0902 22:52:17.932188 2079114000 net.cpp:356] mnist -> label
I0902 22:52:17.932200 2079114000 net.cpp:96] Setting up mnist
I0902 22:52:17.935807 2079114000 data_layer.cpp:135] Opening leveldb input_leveldb
I0902 22:52:17.937155 2079114000 data_layer.cpp:195] output data size: 64,1,28,28
I0902 22:52:17.938570 2079114000 net.cpp:103] Top shape: 64 1 28 28 (50176)
I0902 22:52:17.938593 2079114000 net.cpp:103] Top shape: 64 (64)
I0902 22:52:17.938611 2079114000 net.cpp:67] Creating Layer ip
I0902 22:52:17.938617 2079114000 net.cpp:394] ip <- data
I0902 22:52:17.939177 2079114000 net.cpp:356] ip -> ip
I0902 22:52:17.939196 2079114000 net.cpp:96] Setting up ip
I0902 22:52:17.940289 2079114000 net.cpp:103] Top shape: 64 2 (128)
I0902 22:52:17.941270 2079114000 net.cpp:67] Creating Layer loss
I0902 22:52:17.941305 2079114000 net.cpp:394] loss <- ip
I0902 22:52:17.941314 2079114000 net.cpp:394] loss <- label
I0902 22:52:17.941323 2079114000 net.cpp:356] loss -> loss
# set up the loss and configure the backward pass
I0902 22:52:17.941328 2079114000 net.cpp:96] Setting up loss
I0902 22:52:17.941328 2079114000 net.cpp:103] Top shape: (1)
I0902 22:52:17.941329 2079114000 net.cpp:109]     with loss weight 1
I0902 22:52:17.941779 2079114000 net.cpp:170] loss needs backward computation.
I0902 22:52:17.941787 2079114000 net.cpp:170] ip needs backward computation.
I0902 22:52:17.941794 2079114000 net.cpp:172] mnist does not need backward computation.
# determine outputs
I0902 22:52:17.941800 2079114000 net.cpp:208] This network produces output loss
# finish initialization and report memory usage
I0902 22:52:17.941810 2079114000 net.cpp:467] Collecting Learning Rate and Weight Decay.
I0902 22:52:17.941818 2079114000 net.cpp:219] Network initialization done.
I0902 22:52:17.941824 2079114000 net.cpp:220] Memory required for data: 201476

ネットワークの生成 (construction) はデバイス非依存 (agnostic) であることに注意してください – blob と層は実装の詳細をモデル定義から隠すという先の説明を思い出してください。生成の後、ネットワークは、Caffe::mode() で定義され Caffe::set_mode() で設定される単一のスイッチの設定によりCPU か GPU で実行されます。層は該当する CPU と GPU ルーチンが付属していて (数値的なエラー次第で、それを防ぐテストとともに) 同じ結果を生成します。CPU / GPU スイッチはシームレスでモデル定義からは独立です。研究者と開発者の両者のためにモデルと実装は分割するのがベストです。

 

モデル・フォーマット

モデルは平文の protocol buffer スキーマ (prototxt) で定義される一方で、学習されたモデルはバイナリの protocol buffer (binaryproto) .caffemodel ファイルとしてシリアライズされます。

モデル・フォーマットは caffe.proto の protobuf スキーマで定義されます。ソース・ファイルは多くの場合一目瞭然ですのでご覧になることが奨励されます。

Caffe は次の強みのために Google Protocol Buffer を話します : シリアライズされた時に最小サイズのバイナリ文字列、効率的なシリアリゼーション、バイナリ・バージョンと互換な可読なテキスト・フォーマット、そして複数の言語(特に C++ と Python)の効率的なインターフェイス実装。これら全てが Caffe におけるモデリングの柔軟性と拡張性に貢献します。

 

以上