ディープラーニングとコンピュータビジョンの実践学習(3) 最も単純な画像分類 - 手書き数字認識(CaffeとLeNet-5に基づく)

最も単純な画像分類 - 手書き数字認識

LeNet-5 は、手書きの数字を認識するために MNIST データセットでトレーニングされています - 画像分類における Hello World

1. データの準備 - MNIST

ほとんどのフレームワークの例では、MNIST を使用した LeNet-5 のトレーニングの例は、スクリプトによって高度にカプセル化されています。スクリプトを実行するだけで、データのダウンロードからトレーニングまでのプロセスが完了します。たとえば、MXNet では、mxnet/example に移動して train_mnist.py を実行するだけです。Caffe にも同様のシェル スクリプトがあります。

それでは、何が起こっているのかを初心者が理解するのには役立ちません。この記事では、データ準備部分を分離し、各トレーニングを図で詳しく説明し、プロセスを完全にゼロから実行します。このプロセスを理解すると、画像データから開始して分類用のモデルをトレーニングする方法が基本的に理解できます。

Linux では wget を使用して直接ダウンロードできます。

>> wget http://deeplearning.net/data/mnist/mnist.pkl.gz

ダウンロードされた mnist.pkl.gz 圧縮パッケージには、実際にはデータのトレーニング セット、検証セット、テスト セットが含まれています。pickle によってエクスポートされたファイルは gzip 形式に圧縮されているため、Python の gzip モジュールを使用してファイルとして読み取ることができます。各データ セットはタプルです。最初の要素には手書きのデジタル イメージが格納されます。つまり、各イメージは長さ 28*28=784 の 1 次元浮動小数点 numpy 配列です。この配列はシングル チャネルのグレースケール イメージです。行ごとに展開すると、最大値は 1 (白い部分)、最小値は 0 (黒い部分) になります。タプルの 2 番目の要素は、ピクチャに対応するラベルです。これは、添字の位置に応じてピクチャ内の数値に対応する、整数の 1 次元の numpy 配列です。上記に基づいて、データセットを画像に変換するコードは次のようになります。

import os
import pickle, gzip
from matplotlib import pyplot
print('Loading data from mnist.pkl.gz ...')
with gzip.open('mnist.pkl.gz', 'rb') as f:
  train_set, valid_set, test_set = pickle.load(f)
imgs_dir = 'mnist'
os.system('mkdir -p {}'.format(imgs_dir))
datasets = {'train': train_set, 'val': valid_set, 'test': test_set}
for dataname, dataset in datasets.items():
  print('Converting {} dataset ...'.format(data_dir))
  for i, (img, label) in enumerate(zip(*dataset)):
    filename = '{:0>6d}_{}.jpg'.format(i, label)
    filepath = os.sep.join([data_dir, filename])
    img = img.reshape((28, 28))
    pyplot.imsave(filepath, img, cmap='gray')
    if(i+1) % 10000 == 0:
       print('{} images converted!'.format(i+1))

このスクリプトは、まず mnist というフォルダーを作成し、次に mnist の下に 3 つのサブフォルダー train、val、test を作成します。これらのサブフォルダーには、それぞれトレーニング イメージ、検証イメージ、テスト イメージが含まれており、変換後に対応する 3 つのデータ セットを保存するために使用されます。結果の写真。各ファイルの命名規則は、最初のフィールドがシリアル番号、2 番目のフィールドが数値であり、JPG 形式で保存されます。

2. Caffe および LeNet-5 に基づいて手書き数字認識モデルをトレーニングし、モデルを評価およびテストします。

(1) LMDBの作成

Caffe ベースの場合は、最初に LMDB データを作成する必要があります。LMDB は、Caffe で最も一般的に使用されるデータベース形式です。正式名は、Lightning Memory-Mapped Database (超高速メモリ マップ データベース) です。LMDB は高速であることに加えて、同時にデータを読み取るための複数のプログラムもサポートしています。これは、Caffe によって以前にサポートされていた LevelDB の利点です。現在、LMDB は、Caffe が画像をトレーニングするためにほぼ最も一般的に使用されているデータ形式です。

Caffe は、画像分類タスクのために画像を LMDB に変換するための公式ツールを提供します。パスは caffe/build/tools/convert_imageset です。このツールを使用するには、最初のステップは画像ファイル パスのリストを生成することです。各行はファイル パスと対応するラベル (添え字) であり、スペース キーまたはタブ (Tab) で区切られています。

前の MNIST フォルダーの下に生成された 3 つのフォルダー、train、val、test の画像パスと対応するラベルを上記の形式に変換すると、コードは次のようになります。

import os
import sys
input_path = sys.argv[1].rstrip(os.sep)
output_path = sys.argv[2]
filenames = os.listdir(input_path)
with open(output_path, 'w') as f:
    for filename in filenames:
        filepath = os.sep.join([input_path, filename])
        label = filename[:filename.rfind('.')].split('_')[1]
        line = '{} {}\n'.format(filepath, label)
        f.write(line)

このファイルを gen_caffe_imglist.py として保存し、次のコマンドを順番に実行します。

>> python gen_caffe_imglist.py mnist/train train.txt

>> python gen_caffe_imglist.py mnist/val val.txt

>> python gen_caffe_imglist.py mnist/test test.txt

このようにして、3 つのデータセットのファイルリストと対応するラベルが生成されます。次に、convert_imageset を直接呼び出して lmdb を作成します。

>> /path/to/caffe/build/tools/convert_imageset ./ train.txt train_lmdb --gray --shuffle

>> /path/to/caffe/build/tools/convert_imageset ./ val.txt train_lmdb --gray --shuffle

>> /path/to/caffe/build/tools/convert_imageset ./test.txt train_lmdb --gray --shuffle

このうち、--gray はグレースケール画像をシングルチャンネルで読み込むためのオプションで、--shuffle はファイルリストの順序を乱すためによく使われるオプションですが、この例では元の順序が乱れるため不要です。 。このツールを実行すると、イメージをopencvのMatとして読み込み、lmdbに保存します。Convert_imageset の詳細な使用法については、次のコマンドを実行するか、ソース コードを参照してください。

>> /パス/to/caffe/build/tools/convert_imageset -h

(2) LeNet-5の訓練

入力データ層が自作のLMDBになっている以外はCaffeの公式サンプル版と違いはなく、データソースとネットワーク構造を記述するlenet_train_val.prototxtは以下の通りです。

name: "LeNet"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  include {
    phase: TRAIN
  }

  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  data_param {
    source: "../data/train_lmdb"
    batch_size: 50
    backend: LMDB
  }
}

layer {
  name: "mnist"
  type: "Data"
  top: "data"
  include {
    phase: TEST
  }

  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  data_param {
    source: "../data/val_lmdb"
    batch_size: 100
    backend: LMDB
  }
}

layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}

layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}

layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "relu1"
}

layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

layer{
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

データ層のパラメータ、平均値とスケーリング率を指定する機能は、データから平均値を減算し、それにスケールを掛けることで、具体的にはmnist画像の場合、値を0~255の間でスケーリングすることになります。収束を助けるために -0.5 ~ 0.5 に; 畳み込みカーネル 基本学習率の学習率は lr_mult で乗算され、偏った学習率は lr_mult の基本学習率で乗算されます;weight_filler はパラメーターの初期化に使用され、xavier はBengio グループのディープ フィードフォワード ニューラル ネットワークによる 2010 年の論文「トレーニングの難しさの理解」から派生した初期化方法。畳み込み層の後にプーリング層が続きます。ReLU ユニットは元のシグモイドより優れた収束効果を持っています。精度レイヤーは、分類の精度を計算するために検証/テスト段階でのみ使用されます。

ネットワーク構造とデータに加えて、lenet_solver.prototxt を構成する必要もあります。

net: "lenet_train_val.prototxt"
test_iter: 100
test_interval: 500
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
lr_policy: "inv"
gamma: 0.0001
power: 0.75
display: 100
max_iter: 36000
snapshot: 5000
snapshot_prefix: "mnist_lenet"
solver_mode: GPU

ソルバーの詳細については、Caffe の公式 Web サイト http://caffe.berkeleyvision.org/tutorial/solver.html を参照してください。

次に、トレーニングのために次のコマンドを呼び出すことができます。

>> /path/to/caffe/build/tools/caffe train -solver lenet_solver.prototxt -gpu 0 -log_dir ./

または、二重ダッシュで始まるパラメータ コマンド、

>> /path/to/caffe/build/tools/caffe train --solver=lenet_solver.prototxt --gpu=0 --log_dir=./

ただし、2 番目の方法はターミナルの自動補完を使用できないため、1 番目の方法ほど便利ではありません。

このうち、gpu パラメーターはトレーニングに使用する GPU を指定します (マルチカード GPU サーバーなど、複数のブロックがある場合)。必要に応じて、-gpu all パラメーターを使用してすべてのカードをトレーニングできます。log_dir パラメーターは、出力ログ ファイルへのパスを指定します (このパスが事前に存在している必要があります)。コマンドの実行後に出力が表示されます。

 

TEST データ層が指定されているため、val_lmdb 上の現在のモデルの精度と損失が、ソルバーで指定された間隔に従って出力に出力されることに注意してください。トレーニング後、caffemodel とsolverstate で終わるいくつかのファイルが生成されます。これは、指定された反復回数とトレーニングの終了時のモデル パラメーターとソルバーの状態のアーカイブです。名前のプレフィックスは、lenet_solver.prototxt で指定されたプレフィックスです。もちろん、ログ ファイルも同時に生成され、名前は次のとおりです。

caffe.[ホスト名].[ドメイン名].[ユーザー名].log.INFO.[年月日]-[時分秒].[マイクロ秒]

Caffe は、ログ ファイルを視覚化するためのツールも公式に提供しています。caffe\tools\extra に、plot_training_log.py.example があります。このファイルをコピーし、plot_training_log.py という名前を付けます。これは、画像の描画に使用できます。このスクリプトの入力パラメータは、グラフのタイプ、画像を生成するパス、ログへのパスです。

このうち、入力される画像タイプと対応する画像タイプは以下のとおりです。

0: テスト精度と反復回数

1: テスト精度とトレーニング時間 (秒)

2: テスト損失と反復

3: テスト精度と反復回数

4: テスト精度とトレーニング時間 (秒)

5: テスト損失と反復

6: テスト精度とトレーニング時間 (秒)

7: テスト損失と反復

さらに、このスクリプト ログ ファイルは .log で終わる必要があります。mv コマンドを使用してログ ファイル名を mnist_train.log に変更します。たとえば、テストの精度とテストの損失が反復回数によってどのように変化するかを確認するには、これらを順番に実行します。

>> Python Lot_training_log.py 0 test_acc_vs_iters.png mnist_train.log

>> Python Lot_training_log.py 2 test_loss_vs_iters.png mnist_train.log

 

(3) 試験と評価

テストモデルの精度

モデルをトレーニングした後、テストして評価する必要があります。実際、トレーニング プロセス中、モデルの精度は 500 回の反復ごとに val_lmdb で評価されています。ただし、MNIST には検証セットとは別にテスト セットがあり、データはテスト セットに基づいて評価されます。

モデルのパフォーマンスを評価する

一般的に、主な評価は速度とメモリ使用量です。

(4) 手書き数字の認識

トレーニングされたモデルを使用すると、手書きの数字を認識できます。私たちのテストでは、テスト データ セットと以前に生成されたリストからの画像を使用します。

 

(5) 並進外乱と回転外乱を追加する

 

サンプルに基づいて摂動によってデータを直接追加することは、データを増やす方法の 1 つにすぎません。追加されるデータの量には制限があり、元のサンプル用に追加のハードディスク容量も必要とするため、良い解決策とは言えません。最良の方法は、トレーニング中にリアルタイムでデータに摂動を与えることです。これは、無限のランダムな摂動と同等です。実際、Caffe のデータ層には、最も基本的なデータ摂動関数がすでに組み込まれていますが、ランダム クロッピングとランダム ミラーリングに限定されており、あまり役に立ちません。Github には、さまざまな一般的なデータ摂動メソッドを含むリアルタイム摂動 Caffe レイヤーのオープン ソース サードパーティ実装がいくつかあります。Github の検索ボックスで caffe augumentation を検索するだけで、多くのものが見つかります。

 

(足りない部分は後日追加します)

おすすめ

転載: blog.csdn.net/Fan0920/article/details/107562357