CNTK-畳み込みニューラルネットワーク
この章では、CNTKで畳み込みニューラルネットワーク(CNN)を構築する方法を学習しましょう。
前書き
畳み込みニューラルネットワーク(CNN)もニューロンで構成されており、学習可能な重みとバイアスがあります。そのため、このように、通常のニューラルネットワーク(NN)のようになります。
通常のNNの動作を思い出すと、すべてのニューロンは1つ以上の入力を受け取り、加重和を取り、活性化関数を通過して最終出力を生成します。ここで、CNNと通常のNNに非常に多くの類似点がある場合、これら2つのネットワークが互いに異なる理由は何でしょうか。
それらの違いは、入力データとレイヤーのタイプの扱いです。通常のNNでは、入力データの構造は無視され、すべてのデータはネットワークに送られる前に1次元配列に変換されます。
ただし、畳み込みニューラルネットワークアーキテクチャは、画像の2D構造を考慮して処理し、画像に固有のプロパティを抽出できるようにします。さらに、CNNには、CNNの主要な構成要素である1つ以上の畳み込み層とプーリング層があるという利点があります。
これらの層の後には、標準の多層NNの場合と同様に、1つ以上の完全に接続された層が続きます。したがって、CNNは、完全に接続されたネットワークの特殊なケースと考えることができます。
畳み込みニューラルネットワーク(CNN)アーキテクチャ
CNNのアーキテクチャは、基本的に3次元、つまり画像ボリュームの幅、高さ、深さを3次元の出力ボリュームに変換するレイヤーのリストです。ここで注意すべき重要な点の1つは、現在の層のすべてのニューロンが前の層からの出力の小さなパッチに接続されていることです。これは、入力画像にN * Nフィルターをオーバーレイするようなものです。
基本的にエッジやコーナーなどの特徴を抽出する特徴抽出器であるMフィルターを使用します。以下はレイヤーです [INPUT-CONV-RELU-POOL-FC] 畳み込みニューラルネットワーク(CNN)の構築に使用される-
INPUT−名前が示すように、このレイヤーは生のピクセル値を保持します。生のピクセル値は、画像のデータをそのまま意味します。たとえば、INPUT [64×64×3]は、幅64、高さ64、奥行き3の3チャンネルのRGB画像です。
CONV−ほとんどの計算はこの層で行われるため、この層はCNNの構成要素の1つです。例-上記のINPUT [64×64×3]で6つのフィルターを使用すると、ボリューム[64×64×6]になる可能性があります。
RELU−前の層の出力に活性化関数を適用する、正規化線形ユニット層とも呼ばれます。他の方法では、非線形性はRELUによってネットワークに追加されます。
POOL−この層、つまりプーリング層は、CNNのもう1つの構成要素です。このレイヤーの主なタスクはダウンサンプリングです。つまり、入力のすべてのスライスで独立して動作し、空間的にサイズを変更します。
FC−完全接続層、より具体的には出力層と呼ばれます。これは、出力クラススコアの計算に使用され、結果の出力は、サイズ1 * 1 * Lのボリュームです。 ここで、Lはクラススコアに対応する数値です。
次の図は、CNNの典型的なアーキテクチャを表しています-
CNN構造の作成
CNNのアーキテクチャと基本を見てきたので、次にCNTKを使用して畳み込みネットワークを構築します。ここでは、最初にCNNの構造をまとめる方法を確認し、次にCNNのパラメーターをトレーニングする方法を確認します。
最後に、さまざまな異なるレイヤー設定でその構造を変更することにより、ニューラルネットワークをどのように改善できるかを見ていきます。MNIST画像データセットを使用します。
それでは、最初にCNN構造を作成しましょう。一般に、画像のパターンを認識するためのCNNを構築する場合、次のことを行います。
畳み込みレイヤーとプーリングレイヤーを組み合わせて使用します。
ネットワークの最後にある1つ以上の隠れ層。
最後に、分類のためにソフトマックス層でネットワークを完成させます。
次の手順の助けを借りて、ネットワーク構造を構築できます-
Step 1−まず、CNNに必要なレイヤーをインポートする必要があります。
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
Step 2−次に、CNNの活性化関数をインポートする必要があります。
from cntk.ops import log_softmax, relu
Step 3−その後、畳み込み層を後で初期化するために、インポートする必要があります glorot_uniform_initializer 次のように-
from cntk.initializer import glorot_uniform
Step 4−次に、入力変数を作成するには、 input_variable関数。そしてインポートdefault_option 関数、NNの構成を少し簡単にします。
from cntk import input_variable, default_options
Step 5−入力画像を保存するには、新しい画像を作成します input_variable。赤、緑、青の3つのチャンネルが含まれます。サイズは28x28ピクセルになります。
features = input_variable((3,28,28))
Step 6−次に、別のものを作成する必要があります input_variable 予測するラベルを保存します。
labels = input_variable(10)
Step 7−次に、を作成する必要があります default_optionNNのために。そして、私たちは使用する必要がありますglorot_uniform 初期化関数として。
with default_options(initialization=glorot_uniform, activation=relu):
Step 8−次に、NNの構造を設定するために、新しいものを作成する必要があります Sequential レイヤーセット。
Step 9−次に、を追加する必要があります Convolutional2D との層 filter_shape 5のと strides の設定 1、 以内 Sequentialレイヤーセット。また、パディングを有効にして、元のサイズを維持するために画像がパディングされるようにします。
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
Step 10−次に、を追加します。 MaxPooling との層 filter_shape 2の、そして strides 画像を半分に圧縮するには2に設定します。
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Step 11−ここで、ステップ9で行ったように、別のを追加する必要があります Convolutional2D との層 filter_shape 5のと strides1に設定すると、16個のフィルターを使用します。また、パディングを有効にして、前のプーリングレイヤーによって生成された画像のサイズを保持するようにします。
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
Step 12−ここで、手順10で行ったように、別の MaxPooling との層 filter_shape 3と strides 3に設定すると、画像が3分の1に縮小されます。
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Step 13−最後に、ネットワークが予測できる10個の可能なクラスに対して10個のニューロンを持つ高密度レイヤーを追加します。ネットワークを分類モデルに変えるには、log_siftmax 活性化関数。
Dense(10, activation=log_softmax)
])
CNN構造を作成するための完全な例
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
画像を使用したCNNのトレーニング
ネットワークの構造を作成したので、次はネットワークをトレーニングします。ただし、ネットワークのトレーニングを開始する前に、ミニバッチソースを設定する必要があります。これは、画像を処理するNNのトレーニングには、ほとんどのコンピューターよりも多くのメモリが必要になるためです。
前のセクションですでにミニバッチソースを作成しました。以下は、2つのミニバッチソースを設定するためのPythonコードです-
私たちが持っているように create_datasource 関数を使用すると、モデルをトレーニングするために2つの別々のデータソース(トレーニングとテスト)を作成できます。
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
これで、画像を準備したので、NNのトレーニングを開始できます。前のセクションで行ったように、損失関数でtrainメソッドを使用して、トレーニングを開始できます。以下はこのためのコードです-
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
前のコードの助けを借りて、NNの損失と学習者を設定しました。次のコードは、NN-をトレーニングおよび検証します。
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
完全な実装例
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
出力
-------------------------------------------------------------------
average since average since examples
loss last metric last
------------------------------------------------------
Learning rate per minibatch: 0.2
142 142 0.922 0.922 64
1.35e+06 1.51e+07 0.896 0.883 192
[………]
画像変換
これまで見てきたように、画像認識に使用されるNNをトレーニングすることは困難であり、トレーニングにも大量のデータが必要です。もう1つの問題は、トレーニング中に使用される画像に過剰適合する傾向があることです。例を挙げてみましょう。直立した顔の写真がある場合、モデルは別の方向に回転している顔を認識するのに苦労します。
このような問題を克服するために、画像のミニバッチソースを作成するときに、画像拡張を使用でき、CNTKは特定の変換をサポートします。次のようにいくつかの変換を使用できます-
トレーニングに使用する画像を数行のコードでランダムにトリミングできます。
スケールやカラーもご利用いただけます。
次のPythonコードを使用して、以前にミニバッチソースを作成するために使用した関数内にトリミング変換を含めることにより、変換のリストを変更する方法を見てみましょう。
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
上記のコードの助けを借りて、画像変換のセットを含めるように関数を拡張できます。これにより、トレーニング時に画像をランダムにトリミングして、画像のバリエーションを増やすことができます。