次の方法で共有


ハンズオン ラボの画像認識

このチュートリアルでは、最新のマスター バージョン、または近日中にリリースされる予定の CNTK 1.7 が必要であることに注意してください。 中間バイナリダウンロードは、このチュートリアルが最初に設計されたKDD CNTK Hands-Onチュートリアルの手順に記載されています。

Hands-Onラボ: 畳み込みネットワーク、バッチ正規化、および残差ネットを使用した画像認識

このハンズオン ラボでは、CNTKを使用して畳み込みベースの画像認識を実装する方法について説明します。 まず、一般的な畳み込み画像認識アーキテクチャから始め、バッチ正規化を追加し、それを残余ネットワーク (ResNet-20) に拡張します。

練習する手法は次のとおりです。

  • 定義済みの操作 (ドロップアウト) を追加するためにCNTKネットワーク定義を変更する
  • ネットワーク内の繰り返し部分を再利用可能なモジュールに抽出するユーザー定義関数の作成
  • カスタム ネットワーク構造の実装 (ResNet の接続のスキップ)
  • 再帰ループを使用して一度に多数のレイヤーを作成する
  • 並列トレーニング
  • 畳み込みネットワーク
  • バッチ正規化

前提条件

CNTKが既にインストールされており、CNTK コマンドを実行できることを前提としています。 このチュートリアルは KDD 2016 で開催され、最新のビルドが必要です。セットアップ手順 については、こちらを参照 してください。 そのページからバイナリ インストール パッケージをダウンロードする手順に従ってください。 イメージ関連のタスクの場合は、CUDA と互換性のある GPU 対応のマシンでこれを行う必要があります。

次に、ZIP アーカイブ (約 12 MB) をダウンロードしてください。 このリンクをクリックし、[ダウンロード] ボタンをクリックします。 アーカイブには、このチュートリアルのファイルが含まれています。 アーカイブを行い、作業ディレクトリを ..ImageHandsOn 次のファイルを操作します。

最後に、CIFAR-10 データ・セットをダウンロードして変換する必要があります。 変換手順には約 10 分かかります。 作業ディレクトリにも含まれる次の 2 つの Python スクリプトを実行してください。

wget -rc http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
tar xvf www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
python CifarConverter.py cifar-10-batches-py

これにより、画像が PNG ファイル、トレーニング用に 50000、テスト用に 10000 に変換され、それぞれ cifar-10-batches-py/data/traincifar-10-batches-py/data/test次の 2 つのディレクトリに配置されます。

モデル構造

このチュートリアルでは、単純な畳み込みモデルを使用して開始します。 これは、5x5畳み込み + 非線形性 + 3x3 max-poolingによる2x次元減少の3層で構成され、その後、密な隠れ層と高密度変換が続き、10方向ソフトマックス分類器への入力が形成されます。

または、CNTKネットワークの説明として使用します。 簡単に見て、上記の説明と一致してください。

featNorm = features - Constant (128)
l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 0.0043} (featNorm)
p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
l2 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p1)
p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l2)
l3 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p2)
p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)

これらの演算子の詳細については、次のページを参照してください。 ConvolutionalLayer{}MaxPoolingLayer{}DenseLayer{}LinearLayer{}

CNTK構成

構成ファイル

CNTKでモデルをトレーニングしてテストするには、実行する操作 (command変数) と各コマンドのパラメーター セクションCNTK示す構成ファイルを提供する必要があります。

トレーニング コマンドの場合は、CNTKに次の情報を伝える必要があります。

  • データの読み取り方法 (reader セクション)
  • 計算グラフ (BrainScriptNetworkBuilder セクション) 内のモデル関数とその入力と出力
  • 学習器のハイパーパラメーター (SGD セクション)

評価コマンドの場合は、次CNTK認識する必要があります。

  • テスト データの読み取り方法 (reader セクション)
  • 評価するメトリック (evalNodeNames パラメーター)

最初に使用する構成ファイルを次に示します。 ご覧のように、CNTK構成ファイルは、レコードの階層に編成されたパラメーターの定義で構成されるテキスト ファイルです。 構文を使用して$parameterName$、CNTKが基本的なパラメーター置換をサポートする方法も確認できます。 実際のファイルには、上記の数個のパラメーターだけが含まれていますが、それをスキャンして、先ほど説明した構成項目を探してください。

# CNTK Configuration File for training a simple CIFAR-10 convnet.
# During the hands-on tutorial, this will be fleshed out into a ResNet-20 model.

command = TrainConvNet:Eval

makeMode = false ; traceLevel = 0 ; deviceId = "auto"

rootDir = "." ; dataDir  = "$rootDir$" ; modelDir = "$rootDir$/Models"

modelPath = "$modelDir$/cifar10.cmf"

# Training action for a convolutional network
TrainConvNet = {
    action = "train"

    BrainScriptNetworkBuilder = {
        imageShape = 32:32:3
        labelDim = 10

        model (features) = {
            featNorm = features - Constant (128)
            l1 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=0.0043} (featNorm)
            p1 = MaxPoolingLayer {(3:3), stride=(2:2)} (l1)
            l2 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p1)
            p2 = MaxPoolingLayer {(3:3), stride=(2:2)} (l2)
            l3 = ConvolutionalLayer {64, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p2)
            p3 = MaxPoolingLayer {(3:3), stride=(2:2)} (l3)
            d1 = DenseLayer {64, activation=ReLU, init="gaussian", initValueScale=12} (p3)
            z  = LinearLayer {10, init="gaussian", initValueScale=1.5} (d1)
        }.z

        # inputs
        features = Input {imageShape}
        labels   = Input {labelDim}

        # apply model to features
        z = model (features)

        # connect to system
        ce       = CrossEntropyWithSoftmax (labels, z)
        errs     = ErrorPrediction         (labels, z)

        featureNodes    = (features)
        labelNodes      = (labels)
        criterionNodes  = (ce)
        evaluationNodes = (errs)
        outputNodes     = (z)
    }

    SGD = {
        epochSize = 50000

        maxEpochs = 30 ; minibatchSize = 64
        learningRatesPerSample = 0.00015625*10:0.000046875*10:0.000015625
        momentumAsTimeConstant = 600*20:6400
        L2RegWeight = 0.03

        firstMBsToShowResult = 10 ; numMBsToShowResult = 100
    }

    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/train_map.txt"
            input = {
                features = { transforms = (
                    { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
                    { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                    { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

# Eval action
Eval = {
    action = "eval"
    minibatchSize = 16
    evalNodeNames = errs
    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/test_map.txt"
            input = {
                features = { transforms = (
                   { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                   { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

データとデータの読み取り

CIFAR-10 データをダウンロードし、このチュートリアルの冒頭で要求に応じてスクリプトを実行CifarConverter.pyすると、2 つのサブディレクトリを含み、 traintestPNG ファイルでいっぱいのディレクトリcifar-10-batches-py/dataが見つかります。 CNTKImageDeserializerでは、標準のイメージ形式が使用されます。

また、2つのファイル train_map.txt を見つけると test_map.txt. 後者を見ると、

% more cifar-10-batches-py/test_map.txt
cifar-10-batches-py/data/test/00000.png 3
cifar-10-batches-py/data/test/00001.png 8
cifar-10-batches-py/data/test/00002.png 8
...

両方のファイルは 2 つの列で構成されます。1 つ目にはイメージ ファイルへのパスと、2 つ目のクラス ラベルが数値インデックスとして含まれます。 これらの列は、リーダー入力 features に対応し、 labels 次のように定義されています。

 features = { transforms = (
     { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
     { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
     { type = "Transpose" }
 )}
 labels = { labelDim = 10 }

追加 transforms のセクションでは、画像の ImageDeserializer 読み取り中に一連の (共通の) 変換を適用するように指示します。 詳細については、 こちらを参照してください

それを実行する

上記の構成ファイルは、作業フォルダー内の名前 ImageHandsOn.cntk の下にあります。 これを実行するには、次のコマンドで上記の構成を実行してください。

cntk  configFile=ImageHandsOn.cntk

あなたの画面はログメッセージの流れで生き生きとします(CNTK時には話すことができます)が、すべてが正しければ、すぐにこれが表示されます:

Training 116906 parameters in 10 out of 10 parameter tensors and 28 nodes with gradient

次のような出力が続きます。

Finished Epoch[ 1 of 10]: [Training] ce = 1.66950797 * 50000; errs = 61.228% * 50000
Finished Epoch[ 2 of 10]: [Training] ce = 1.32699016 * 50000; errs = 47.394% * 50000
Finished Epoch[ 3 of 10]: [Training] ce = 1.17140398 * 50000; errs = 41.168% * 50000

これは、それが学習していることを示しています。 各エポックは、50000 のトレーニング画像を 1 つのパスで表します。 また、2 番目のエポックの後に、このエポックの 50000 サンプルで測定された 1.33 という名前 ceの構成のトレーニング基準が、同じ 50000 トレーニング サンプルでエラー率が 47% であることを示します。

CPU のみのマシンは約 20 倍低速であることに注意してください。 最初のログ出力が表示されるまで数分かかります。 システムが進行していることを確認するために、トレースを有効にして部分的な結果を確認できます。これは、合理的に迅速に表示されます。

cntk  configFile=ImageHandsOn.cntk  traceLevel=1

Epoch[ 1 of 10]-Minibatch[-498-   1, 0.13%]: ce = 2.30260658 * 64; errs = 90.625% * 64
...
Epoch[ 1 of 10]-Minibatch[   1- 100, 12.80%]: ce = 2.10434176 * 5760; errs = 78.472% * 5760
Epoch[ 1 of 10]-Minibatch[ 101- 200, 25.60%]: ce = 1.82372971 * 6400; errs = 68.172% * 6400
Epoch[ 1 of 10]-Minibatch[ 201- 300, 38.40%]: ce = 1.69708496 * 6400; errs = 62.469% * 6400

トレーニングが完了すると (Surface Bookと Titan-X GPU を搭載したデスクトップ コンピューターで約 3 分かかります)、最終的なメッセージは次のようになります。

Finished Epoch[10 of 10]: [Training] ce = 0.74679766 * 50000; errs = 25.486% * 50000

これは、ネットワークが損失を ce 正常に削減し、トレーニング セットで 25.5% の分類エラーに達したことを示しています。 変数は command 2 番目のコマンドEvalを指定するため、CNTKはそのアクションを続行します。 これは、テスト セットの 10000 個の画像の分類エラー率を測定します。

Final Results: Minibatch[1-625]: errs = 24.180% * 10000

テスト エラー率はトレーニングに近い値です。 CIFAR-10 はかなり小さなデータセットであるため、これはモデルがまだ完全に収束していないことを示しています (実際には、30 エポックで実行すると約 20% になります)。

これが完了するまで待ちたくない場合は、中間モデルを実行できます 。例:

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=Models/cifar10.cmf.5
Final Results: Minibatch[1-625]: errs = 31.710% * 10000

または、事前トレーニング済みのモデルも実行します。

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=cifar10.pretrained.cmf
Final Results: Minibatch[1-625]: errs = 24.180% * 10000

モデルの変更

次に、CNTK構成の変更を練習するタスクを示します。 解決策は、このドキュメントの最後に記載されています...しかし、せずに試してみてください!

タスク 1: ドロップアウトの追加

モデルの一般化性を向上させる一般的な手法は、ドロップアウトです。 CNTK モデルにドロップアウトを追加するには、

  • ドロップアウト操作を挿入するCNTK関数Dropout()の呼び出しを追加する
  • ドロップアウト確率をSGD定義するために呼び出されたセクションにパラメーターdropoutRateを追加する

この特定のタスクでは、最初の 1 つのエポックにドロップアウトを指定せず、その後にドロップアウト率 50% を指定してください。 その方法については、 Dropout() ドキュメントを参照してください。

すべてがうまくいった場合は、最初の1エポックの変更は観察されませんが、2番目のエポックでドロップアウトが開始された後の ce 改善ははるかに少なくなります。 これは予期されることです。 (この特定の構成では、認識の精度は実際には向上しません)。トレーニングが 10 エポックのみの場合の最終的な結果は約 32% です。 ドロップアウトには 10 エポックでは不十分です。

解決策については、 こちらを参照してください。

タスク 2: 関数に繰り返しパーツを抽出してモデル定義を簡略化する

この例では、シーケンス (畳み込み >> ReLU >> プール) が 3 回繰り返されます。 タスクは、これら 3 つの操作を再利用可能なモジュールにグループ化する BrainScript 関数を記述することです。 このシーケンスの 3 つの使用はすべて、異なるパラメーター (出力ディメンション、初期化重み) を使用します。 したがって、記述する関数は、入力データに加えて、これら 2 つをパラメーターとして受け取る必要があります。 たとえば、次のように定義できます。

MyLayer (x, depth, initValueScale)

これを実行すると、結果 ceerrs 値が同じであると予想されます。 ただし、GPU 上で実行すると、cuDNN のバック伝達実装で非決定性が発生し、小さなバリエーションが発生します。

解決策については、 こちらを参照してください。

タスク 3: BatchNormalization の追加

(CNTKのバッチ正規化の実装は cuDNN に基づいているため、このタスクには GPU が必要です)。

バッチ正規化は、収束を高速化して改善するための一般的な手法です。 CNTKでは、バッチ正規化は次のようにBatchNormalizationLayer{}実装されます。

空間形式 (すべてのピクセル位置が共有パラメーターで正規化される) は、省略可能なパラメーター BatchNormalizationLayer{spatialRank=2}によって呼び出されます。

3 つの畳み込みレイヤーすべてに、および 2 つの高密度レイヤーの間にバッチ正規化を追加してください。 バッチ正規化は、非線形性の直前に挿入する必要があることに注意してください。 そのため、パラメーターをactivation削除し、代わりに CNTK 関数ReLU()への明示的な呼び出しを挿入する必要があります。

さらに、バッチ正規化によって収束速度が変化します。 そのため、最初の 7 つのエポックの学習率を 3 倍に増やし、パラメーターを 0 に設定してモメンタムと L2 の正則化を無効にしてみましょう。

実行中に、学習可能な追加のパラメーターがトレーニング ログに一覧表示されます。 最終的な結果は約 28% になります。これは、同じ反復回数の後にバッチ正規化を行わない場合よりも 4 ポイント優れています。 収束は確かにスピードアップします。

解決策については、 こちらを参照してください。

タスク 4: 残存ネットに変換する

上記の構成は、CNTK構成の実行と変更で手を汚す "おもちゃ" の例であり、ターンアラウンド時間を低く保つために意図的に完全な収束を実行しませんでした。 次に、より実際の構成 (残差 Net) に進みましょう。 残余ネット (https://arxiv.org/pdf/1512.03385v1.pdf) は、レイヤーが入力から出力へのマッピングを学習する代わりに、修正用語を学習する変更されたディープ ネットワーク構造です。

(このタスクにはバッチ正規化操作に GPU も必要ですが、時間が 長い 場合は、呼び出しバッチ正規化を編集して CPU で実行し、精度が低下します)。

開始するには、前の構成を変更してください。 まず、model 関数 model(features) を次のものに置き換えてください。

        MySubSampleBN (x, depth, stride) =
        {
            s = Splice ((MaxPoolingLayer {(1:1), stride = (stride:stride)} (x) : ConstantTensor (0, (1:1:depth/stride))), axis = 3)  # sub-sample and pad: [W x H x depth/2] --> [W/2 x H/2 x depth]
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (s)
        }.b
        MyConvBN (x, depth, initValueScale, stride) =
        {
            c = ConvolutionalLayer {depth, (3:3), pad = true, stride = (stride:stride), bias = false,
                                    init = "gaussian", initValueScale = initValueScale} (x)
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (c)
        }.b
        ResNetNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 1)
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        ResNetIncNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        model (features) =
        {
            conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
            rn1   = ResNetNode (ResNetNode (ResNetNode (conv1, 16), 16), 16)

            rn2_1 = ResNetIncNode (rn1, 32)
            rn2   = ResNetNode (ResNetNode (rn2_1, 32), 32)

            rn3_1 = ResNetIncNode (rn2, 64)
            rn3   = ResNetNode (ResNetNode (rn3_1, 64), 64)

            pool = AveragePoolingLayer {(8:8)} (rn3)

            z = LinearLayer {labelDim, init = "gaussian", initValueScale = 0.4} (pool)
        }.z

SGD 構成を次に変更します。

SGD = {
    epochSize = 50000

    maxEpochs = 160 ; minibatchSize = 128
    learningRatesPerSample = 0.0078125*80:0.00078125*40:0.000078125
    momentumAsTimeConstant = 1200
    L2RegWeight = 0.0001

    firstMBsToShowResult = 10 ; numMBsToShowResult = 500
}

あなたの仕事は、次の賞に値するASCIIアートにレイアウトされた構造を実装するように変更ResNetNode()ResNetNodeInc()することです。

            ResNetNode                   ResNetNodeInc

                |                              |
         +------+------+             +---------+----------+
         |             |             |                    |
         V             |             V                    V
    +----------+       |      +--------------+   +----------------+
    | Conv, BN |       |      | Conv x 2, BN |   | SubSample, BN  |
    +----------+       |      +--------------+   +----------------+
         |             |             |                    |
         V             |             V                    |
     +-------+         |         +-------+                |
     | ReLU  |         |         | ReLU  |                |
     +-------+         |         +-------+                |
         |             |             |                    |
         V             |             V                    |
    +----------+       |        +----------+              |
    | Conv, BN |       |        | Conv, BN |              |
    +----------+       |        +----------+              |
         |             |             |                    |
         |    +---+    |             |       +---+        |
         +--->| + |<---+             +------>+ + +<-------+
              +---+                          +---+
                |                              |
                V                              V
            +-------+                      +-------+
            | ReLU  |                      | ReLU  |
            +-------+                      +-------+
                |                              |
                V                              V

正しく行ったことをログの検証出力で確認してください。

完了するまでに時間がかかります。 予想される出力は次のようになります。

Finished Epoch[ 1 of 160]: [Training] ce = 1.57037109 * 50000; errs = 58.940% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.06968234 * 50000; errs = 38.166% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 0.85858969 * 50000; errs = 30.316% * 50000

一方、スキップ接続のない正しくないモデルは次のようになります。

Finished Epoch[ 1 of 160]: [Training] ce = 1.72901219 * 50000; errs = 66.232% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.30180430 * 50000; errs = 47.424% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 1.04641961 * 50000; errs = 37.568% * 50000

解決策については、 こちらを参照してください。

タスク 5: 多くのレイヤーを自動的に生成する

最後に、最もパフォーマンスの高い ResNet には 152 個のレイヤーがあります。 152 個の個々の式を記述するのは非常に面倒でエラーが発生しやすいため、定義を変更してスタックを ResNetNode()自動的に生成します。

タスクは、次のシグネチャを使用して関数を記述することです。

ResNetNodeStack (x, depth, L)

ここでL、上記の式rn1をパラメーター化された呼び出しに置き換えるために、スタックする必要がある数ResNetNodesを示します。

rn1   = ResNetNodeStack (conv1, 16, 3)  # 3 means 3 such nodes

同様に、 rn2rn3. 必要なツールは、 条件 式です。

z = if cond then x else y

と再帰。

このトレーニングは、タイタンXの約半分のために実行されます。 正しく行った場合、ログの早い段階で次のメッセージが含まれます。

Training 200410 parameters in 51 out of 51 parameter tensors and 112 nodes with gradient:

参考までに、このモデルの事前トレーニング済みバージョンを含めます。 次のコマンドを使用して、エラー率を測定できます。

cntk  configFile=ImageHandsOn.ResNet.cntk  command=Eval

次のような結果が表示されます。

Final Results: Minibatch[1-625]: errs = 8.400% * 10000; top5Errs = 0.290% * 10000

このエラー率は、元の ResNet ペーパー (https://arxiv.org/pdf/1512.03385v1.pdf表 6) で報告されたものに非常に近いです。

解決策については、 こちらを参照してください。

タスク 6: 並列トレーニング

最後に、複数の GPU がある場合は、CNTK MPI (メッセージ パッシング インターフェイス) を使用してトレーニングを並列化できます。 このモデルは小さすぎて、ミニバッチ サイズなどのチューニングを行わずに大幅な高速化を期待できます (現在のミニバッチ サイズ設定が小さすぎて、使用可能な GPU コアを完全に使用できません)。 それでも、実際のワークロードに移行した後に実行する方法を知るために、モーションを確認しましょう。

ブロックに次の行を SGD 追加してください。

SGD = {
    ...
    parallelTrain = {
        parallelizationMethod = "DataParallelSGD"
        parallelizationStartEpoch = 2
        distributedMBReading = true
        dataParallelSGD = { gradientBits = 1 }
    }
}

次のコマンドを実行します。

mpiexec -np 4 cntk  configFile=ImageHandsOn.cntk  stderr=Models/log  parallelTrain=true

次は何をすればよいですか?

このチュートリアルでは、既存の構成を使用し、特定の方法で変更する方法を練習しました。

  • 定義済み操作の追加 (ドロップアウト)
  • 反復的なパーツを再利用可能なモジュール (関数) に抽出する
  • リファクタリング (バッチ正規化を挿入するため)
  • カスタム ネットワーク構造 (ResNet の接続をスキップ)
  • 再帰を使用した繰り返し構造のパラメーター化

並列化によってトレーニングを高速化する方法を見てきました。

それでは、ここからどこに行くのですか? これらの例で使用されているパターン ( グラフ構築 スタイルと呼びます) は、エラーが発生しやすい可能性があることに既に気が付いているかもしれません。 エラーを特定しますか?

model (features) =
{
    l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 0.0043} (featNorm)
    p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    l2 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 1.414} (p1)
    p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p2)
    z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)
}.z

このエラーを回避する方法は、 関数の構成を使用することです。 以下は、同じものを記述する別の、より簡潔な方法です。

model = Sequential (
    ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 0.0043} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 1.414} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} :
    LinearLayer {10, init = "gaussian", initValueScale = 1.5}
)

このスタイルは、次の実践的なチュートリアル「 Recurrent Networks を使用したテキスト理解」で導入され、使用されます。

ソリューション

解決策 1: ドロップアウトの追加

モデル定義を次のように変更します。

p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
d1_d = Dropout (d1)    ##### added
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)  ##### d1 -> d1_d

および SGD セクション:

SGD = {
    ...
    dropoutRate = 0*5:0.5   ##### added
    ...
}

解決策 2: 関数に繰り返しパーツを抽出してモデル定義を簡略化する

次のように関数定義を追加します。

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true, activation = ReLU,
                            init = "gaussian", initValueScale = initValueScale} (x)
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (c)
}.p

モデル定義を使用するように更新します。

featNorm = features - Constant (128)
p1 = MyLayer (featNorm, 32, 0.0043)  ##### replaced
p2 = MyLayer (p1,       32, 1.414)   ##### replaced
p3 = MyLayer (p2,       64, 1.414)   ##### replaced
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)

解決策 3: BatchNormalization の追加

MyLayer() を変更します。

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true,  ##### no activation=ReLU
                            init = "gaussian", initValueScale = initValueScale} (x)
    b = BatchNormalizationLayer {spatialRank = 2} (c)
    r = ReLU (b)   ##### now called explicitly
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (r)
}.p

を使用します。 また、次の前に zバッチ正規化を挿入します。

d1 = DenseLayer {64, init = "gaussian", initValueScale = 12} (p3)
d1_bnr = ReLU (BatchNormalizationLayer {} (d1))  ##### added BN and explicit ReLU
d1_d = Dropout (d1_bnr)                          ##### d1 -> d1_bnr
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)

SGD セクションで次のパラメーターを更新します。

SGD = {
    ....
    learningRatesPerSample = 0.00046875*7:0.00015625*10:0.000046875*10:0.000015625
    momentumAsTimeConstant = 0
    L2RegWeight = 0
    ...
}

解決策 4: 残差ネットに変換する

適切な実装はResNetNode()ResNetNodeInc()次のとおりです。

    ResNetNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 1)
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)
        r  = ReLU (x + c2)   ##### added skip connection
    }.r
    ResNetIncNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)

        xs = MySubSampleBN (x, depth, 2)

        r  = ReLU (xs + c2)   ##### added skip connection
    }.r

解決策 5: 多くのレイヤーを自動的に生成する

これは実装です。

    ResNetNodeStack (x, depth, L) =
    {
        r = if L == 0
            then x
            else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
    }.r

または、短い方:

    ResNetNodeStack (x, depth, L) =
        if L == 0
        then x
        else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)

また、モデル関数を変更する必要があります。

        conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
        rn1   = ResNetNodeStack (conv1, 16, 3)  ##### replaced

        rn2_1 = ResNetIncNode (rn1, 32)
        rn2   = ResNetNodeStack (rn2_1, 32, 2)  ##### replaced

        rn3_1 = ResNetIncNode (rn2, 64)
        rn3   = ResNetNodeStack (rn3_1, 64, 2)  ##### replaced