CNTK 2.0のチュートリアルを試行する。
今回はある程度翻訳という形で実施してみた。だいぶ誤訳はあるだろうが、ザックリ概要をつかむという形でやっていきたい。
CNTK 103のチュートリアルは4編で構成されている。MNISTの数値認識のチュートリアルについて、
- 103 Part-A : MNIST data preparation
- 103 Part-B : Multi-class logistic regression classifier
- 103 Part-C : Multi-layer perception classifier
- 103 Part-D : Convolutional neural network classifier
となっている。ここでは、すべての章を詳細にみていくと大変なので、ザックリと1つの項目にまとめた。
最初の準備段階のところとかはかなり被っているので省略している。
それぞれ、MNISTの問題を
と3種類の方法をCNTKで実装し比較検討を行っている。
github.com
以降の文章については、CNTK 2.0の翻訳を含んでいるが、誤訳を含んでいる可能性がある。勉強しながら書き進めていった翻訳兼メモのため、文章の構成にはあまり力を注いでいない。
問題がある場合は指摘していただけるとありがたいです。
from IPython.display import display, Image
CNTK 103 Part A: MNIST Data Loader
CNTKでのMNISTを実施する。CNTK 101とCNTK 102のようなデータではなく、実際のデータを使用して実施していく。
- Part A: MNISTデータベースについてまずは慣れる。
- 後続のPart: MNISTを使用して異なるネットワークを構成する。
from __future__ import print_function
import gzip
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import numpy as np
import os
import shutil
import struct
import sys
import time
try:
from urllib.request import urlretrieve
except ImportError:
from urllib import urlretrieve
%matplotlib inline
データダウンロード
MNISTデータベースはマシンラーニングアルゴリズムにおいて非常に一般的に使用されている手書きデータである。
トレーニングには60000個のデータを使用しており、10000個のデータをテストに利用している。各データには28x28ピクセルのデータで構成されている。
このデータセットはトレーニングのデータを可視化しやすいものである。
def loadData(src, cimg):
print ('Downloading ' + src)
gzfname, h = urlretrieve(src, './delete.me')
print ('Done.')
try:
with gzip.open(gzfname) as gz:
n = struct.unpack('I', gz.read(4))
if n[0] != 0x3080000:
raise Exception('Invalid file: unexpected magic number.')
n = struct.unpack('>I', gz.read(4))[0]
if n != cimg:
raise Exception('Invalid file: expected {0} entries.'.format(cimg))
crow = struct.unpack('>I', gz.read(4))[0]
ccol = struct.unpack('>I', gz.read(4))[0]
if crow != 28 or ccol != 28:
raise Exception('Invalid file: expected 28 rows/cols per image.')
res = np.fromstring(gz.read(cimg * crow * ccol), dtype = np.uint8)
finally:
os.remove(gzfname)
return res.reshape((cimg, crow * ccol))
def loadLabels(src, cimg):
print ('Downloading ' + src)
gzfname, h = urlretrieve(src, './delete.me')
print ('Done.')
try:
with gzip.open(gzfname) as gz:
n = struct.unpack('I', gz.read(4))
if n[0] != 0x1080000:
raise Exception('Invalid file: unexpected magic number.')
n = struct.unpack('>I', gz.read(4))
if n[0] != cimg:
raise Exception('Invalid file: expected {0} rows.'.format(cimg))
res = np.fromstring(gz.read(cimg), dtype = np.uint8)
finally:
os.remove(gzfname)
return res.reshape((cimg, 1))
def try_download(dataSrc, labelsSrc, cimg):
data = loadData(dataSrc, cimg)
labels = loadLabels(labelsSrc, cimg)
return np.hstack((data, labels))
データのダウンロード
MNISTデータはトレーニングデータとテストデータから構成されている。
60000個のイメージデータと10000個のテストデータから構成されている。
まずはこのデータをダウンロードする。
url_train_image = 'http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz'
url_train_labels = 'http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz'
num_train_samples = 60000
print("Downloading train data")
train = try_download(url_train_image, url_train_labels, num_train_samples)
url_test_image = 'http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz'
url_test_labels = 'http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz'
num_test_samples = 10000
print("Downloading test data")
test = try_download(url_test_image, url_test_labels, num_test_samples)
Downloading train data
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Done.
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Done.
Downloading test data
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Done.
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Done.
データの可視化
sample_number = 5001
plt.imshow(train[sample_number,:-1].reshape(28,28), cmap="gray_r")
plt.axis('off')
print("Image Label: ", train[sample_number,-1])
イメージの保存
MNISTのデータは、ベクトルにフラットに保存されている(28x28のイメージデータは、784個のデータポイントに変換されて保存されている)。
ラベルはワンホットデータに変換されている(10個の分類データで、3は0001000000として表現されている)
def savetxt(filename, ndarray):
dir = os.path.dirname(filename)
if not os.path.exists(dir):
os.makedirs(dir)
if not os.path.isfile(filename):
print("Saving", filename )
with open(filename, 'w') as f:
labels = list(map(' '.join, np.eye(10, dtype=np.uint).astype(str)))
for row in ndarray:
row_str = row.astype(str)
label_str = labels[row[-1]]
feature_str = ' '.join(row_str[:-1])
f.write('|labels {} |features {}\n'.format(label_str, feature_str))
else:
print("File already exists", filename)
data_dir = os.path.join("..", "Examples", "Image", "DataSets", "MNIST")
if not os.path.exists(data_dir):
data_dir = os.path.join("data", "MNIST")
print ('Writing train text file...')
savetxt(os.path.join(data_dir, "Train-28x28_cntk_text.txt"), train)
print ('Writing test text file...')
savetxt(os.path.join(data_dir, "Test-28x28_cntk_text.txt"), test)
print('Done')
Writing train text file...
File already exists data\MNIST\Train-28x28_cntk_text.txt
Writing test text file...
File already exists data\MNIST\Test-28x28_cntk_text.txt
Done
from IPython.display import Image
CNTK 103: Part-B - 回帰分析
CNTK 103: Part-C - マルチレイヤパーセプションによるMNIST
CNTK 103: Part-D - 畳み込みニューラルネットワークによるMNIST
まずはMNISTに必要なデータを用意ししている。手書きの文字を認識する。
Image(url= "http://3.bp.blogspot.com/_UpN7DfJA0j4/TJtUBWPk0SI/AAAAAAAAABY/oWPMtmqJn3k/s1600/mnist_originals.png", width=200, height=200)
目的 MNISTのデータセットを使って、数字の認識分類器のトレーニングを実施する。
アプローチ チュートリアルで説明した同じような手順を踏む。データ読み込み、データ処理、モデルの生成、モデルパラメータの学習、モデルの評価である。
- データ読み込み: CNTKのテキストリーダを使用する。
- データのプリプロセッシング: Part-Aで紹介済み。
ロジスティック回帰
ロジスティック回帰(LR)は基本的な機械学習の手法であり、線形の重み分析を行い、各クラスについて確率に基づく予測を行う。
ロジスティック回帰には2種類が存在し、Binary LRは2つのクラスを予測する場合でも1つの出力を生成し、Multinomial LRは複数のクラスを予測する場合では複数の出力を生成する。
Image(url="https://camo.githubusercontent.com/2eb7e133489dbf4eb253c271b2688056be84dbe0/687474703a2f2f7777772e636e746b2e61692f6a75702f636e746b313033625f54776f466f726d734f664c522d76332e706e67")
from __future__ import print_function
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
import cntk as C
%matplotlib inline
if 'TEST_DEVICE' in os.environ:
if os.environ['TEST_DEVICE'] == 'cpu':
C.device.try_set_default_device(C.device.cpu())
else:
C.device.try_set_default_device(C.device.gpu(0))
初期化
np.random.seed(0)
input_dim = 784
num_output_classes = 10
データ読み込み
MNISTのデータは60000個のトレーニングセットを持っており、10000個のテストセットを持っている。
def create_reader(path, is_training, input_dim, num_label_classes):
labelStream = C.io.StreamDef(field='labels', shape=num_label_classes, is_sparse=False)
featureStream = C.io.StreamDef(field='features', shape=input_dim, is_sparse=False)
deserailizer = C.io.CTFDeserializer(path, C.io.StreamDefs(labels = labelStream, features = featureStream))
return C.io.MinibatchSource(deserailizer,
randomize = is_training, max_sweeps = C.io.INFINITELY_REPEAT if is_training else 1)
data_found = False
for data_dir in [os.path.join("..", "Examples", "Image", "DataSets", "MNIST"),
os.path.join("data", "MNIST")]:
train_file = os.path.join(data_dir, "Train-28x28_cntk_text.txt")
test_file = os.path.join(data_dir, "Test-28x28_cntk_text.txt")
if os.path.isfile(train_file) and os.path.isfile(test_file):
data_found = True
break
if not data_found:
raise ValueError("Please generate the data by completing CNTK 103 Part A")
print("Data directory is {0}".format(data_dir))
Data directory is data\MNIST
ロジスティック回帰のモデル生成
ロジスティック回帰(logistic regression: LR)ネットワークは機械学習の世界において過去10年で使われてきた、シンプルなビルディングブロックを使用した効率的なネットワークである。
Image(url="https://camo.githubusercontent.com/0fc29e9645a061453403af37d46750beb2d91356/68747470733a2f2f7777772e636e746b2e61692f6a75702f636e746b313033625f4d4e4953545f4c522e706e67")
input = C.input_variable(input_dim)
label = C.input_variable(num_output_classes)
def create_model(features):
with C.layers.default_options(init = C.glorot_uniform()):
r = C.layers.Dense(num_output_classes, activation = None)(features)
return r
z = create_model(input/255.0)
def moving_average(a, w=5):
if len(a) < w:
return a[:]
return [val if idx < w else sum(a[(idx-w):idx])/w for idx, val in enumerate(a)]
def print_training_progress(trainer, mb, frequency, verbose=1):
training_loss = "NA"
eval_error = "NA"
if mb%frequency == 0:
training_loss = trainer.previous_minibatch_loss_average
eval_error = trainer.previous_minibatch_evaluation_average
if verbose:
print ("Minibatch: {0}, Loss: {1:.4f}, Error: {2:.2f}%".format(mb, training_loss, eval_error*100))
return mb, training_loss, eval_error
minibatch_size = 64
num_samples_per_sweep = 60000
num_sweeps_to_train_with = 10
num_minibatches_to_train = (num_samples_per_sweep * num_sweeps_to_train_with) / minibatch_size
loss = C.cross_entropy_with_softmax(z, label)
label_error = C.classification_error(z, label)
learning_rate = 0.2
lr_schedule = C.learning_rate_schedule(learning_rate, C.UnitType.minibatch)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])
reader_train = create_reader(train_file, True, input_dim, num_output_classes)
input_map = {
label : reader_train.streams.labels,
input : reader_train.streams.features
}
training_progress_output_freq = 500
plotdata = {"batchsize":[], "loss":[], "error":[]}
for i in range(0, int(num_minibatches_to_train)):
data = reader_train.next_minibatch(minibatch_size, input_map = input_map)
trainer.train_minibatch(data)
batchsize, loss, error = print_training_progress(trainer, i, training_progress_output_freq, verbose=1)
if not (loss == "NA" or error =="NA"):
plotdata["batchsize"].append(batchsize)
plotdata["loss"].append(loss)
plotdata["error"].append(error)
Minibatch: 0, Loss: 2.2132, Error: 79.69%
Minibatch: 500, Loss: 0.5081, Error: 12.50%
Minibatch: 1000, Loss: 0.2309, Error: 4.69%
Minibatch: 1500, Loss: 0.4300, Error: 15.62%
Minibatch: 2000, Loss: 0.1918, Error: 6.25%
Minibatch: 2500, Loss: 0.1843, Error: 7.81%
Minibatch: 3000, Loss: 0.1117, Error: 1.56%
Minibatch: 3500, Loss: 0.3094, Error: 12.50%
Minibatch: 4000, Loss: 0.3554, Error: 10.94%
Minibatch: 4500, Loss: 0.2465, Error: 4.69%
Minibatch: 5000, Loss: 0.1988, Error: 3.12%
Minibatch: 5500, Loss: 0.1277, Error: 4.69%
Minibatch: 6000, Loss: 0.1448, Error: 3.12%
Minibatch: 6500, Loss: 0.2789, Error: 9.38%
Minibatch: 7000, Loss: 0.1692, Error: 6.25%
Minibatch: 7500, Loss: 0.3069, Error: 9.38%
Minibatch: 8000, Loss: 0.1194, Error: 3.12%
Minibatch: 8500, Loss: 0.1464, Error: 4.69%
Minibatch: 9000, Loss: 0.1096, Error: 3.12%
plotdata["avgloss"] = moving_average(plotdata["loss"])
plotdata["avgerror"] = moving_average(plotdata["error"])
import matplotlib.pyplot as plt
plt.figure(1)
plt.subplot(211)
plt.plot(plotdata["batchsize"], plotdata["avgloss"], 'b--')
plt.xlabel('Minibatch number')
plt.ylabel('Loss')
plt.title('Minibatch run vs. Training loss')
plt.show()
plt.subplot(212)
plt.plot(plotdata["batchsize"], plotdata["avgerror"], 'r--')
plt.xlabel('Minibatch number')
plt.ylabel('Label Prediction Error')
plt.title('Minibatch run vs. Label Prediction Error')
plt.show()
ロジスティック回帰の評価 / テスト
トレーニングの完了したネットワークで、テストを実施する。
reader_test = create_reader(test_file, False, input_dim, num_output_classes)
test_input_map = {
label : reader_test.streams.labels,
input : reader_test.streams.features,
}
test_minibatch_size = 512
num_samples = 10000
num_minibatches_to_test = num_samples // test_minibatch_size
test_result = 0.0
for i in range(num_minibatches_to_test):
data = reader_test.next_minibatch(test_minibatch_size,
input_map = test_input_map)
eval_error = trainer.test_minibatch(data)
test_result = test_result + eval_error
print("Average test error: {0:.2f}%".format(test_result*100 / num_minibatches_to_test))
Average test error: 7.41%
マルチレイヤパーセプションのモデル生成
マルチレイヤパーセプションは2レイヤの隠しレイヤ(num_hidden_layers
)の、比較的シンプルなネットワークである。
Image(url="https://camo.githubusercontent.com/173a509c6aab581dc9f2e872f56a349883b1776d/687474703a2f2f636e746b2e61692f6a75702f636e746b313033635f4d4e4953545f4d4c502e706e67")
num_hidden_layers = 2
hidden_layers_dim = 400
input = C.input_variable(input_dim)
label = C.input_variable(num_output_classes)
def create_model(features):
with C.layers.default_options(init = C.layers.glorot_uniform(), activation = C.ops.relu):
h = features
for _ in range(num_hidden_layers):
h = C.layers.Dense(hidden_layers_dim)(h)
r = C.layers.Dense(num_output_classes, activation = None)(h)
return r
z = create_model(input)
z = create_model(input/255.0)
loss = C.cross_entropy_with_softmax(z, label)
label_error = C.classification_error(z, label)
learning_rate = 0.2
lr_schedule = C.learning_rate_schedule(learning_rate, C.UnitType.minibatch)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])
minibatch_size = 64
num_samples_per_sweep = 60000
num_sweeps_to_train_with = 10
num_minibatches_to_train = (num_samples_per_sweep * num_sweeps_to_train_with) / minibatch_size
reader_train = create_reader(train_file, True, input_dim, num_output_classes)
input_map = {
label : reader_train.streams.labels,
input : reader_train.streams.features
}
training_progress_output_freq = 500
plotdata = {"batchsize":[], "loss":[], "error":[]}
for i in range(0, int(num_minibatches_to_train)):
data = reader_train.next_minibatch(minibatch_size, input_map = input_map)
trainer.train_minibatch(data)
batchsize, loss, error = print_training_progress(trainer, i, training_progress_output_freq, verbose=1)
if not (loss == "NA" or error =="NA"):
plotdata["batchsize"].append(batchsize)
plotdata["loss"].append(loss)
plotdata["error"].append(error)
Minibatch: 0, Loss: 2.3329, Error: 95.31%
Minibatch: 500, Loss: 0.2762, Error: 7.81%
Minibatch: 1000, Loss: 0.0661, Error: 1.56%
Minibatch: 1500, Loss: 0.1294, Error: 6.25%
Minibatch: 2000, Loss: nan, Error: 92.19%
Minibatch: 2500, Loss: nan, Error: 93.75%
Minibatch: 3000, Loss: nan, Error: 90.62%
Minibatch: 3500, Loss: nan, Error: 85.94%
Minibatch: 4000, Loss: nan, Error: 92.19%
Minibatch: 4500, Loss: nan, Error: 90.62%
Minibatch: 5000, Loss: nan, Error: 95.31%
Minibatch: 5500, Loss: nan, Error: 84.38%
Minibatch: 6000, Loss: nan, Error: 87.50%
Minibatch: 6500, Loss: nan, Error: 87.50%
Minibatch: 7000, Loss: nan, Error: 82.81%
Minibatch: 7500, Loss: nan, Error: 95.31%
Minibatch: 8000, Loss: nan, Error: 89.06%
Minibatch: 8500, Loss: nan, Error: 96.88%
Minibatch: 9000, Loss: nan, Error: 92.19%
plotdata["avgloss"] = moving_average(plotdata["loss"])
plotdata["avgerror"] = moving_average(plotdata["error"])
import matplotlib.pyplot as plt
plt.figure(1)
plt.subplot(211)
plt.plot(plotdata["batchsize"], plotdata["avgloss"], 'b--')
plt.xlabel('Minibatch number')
plt.ylabel('Loss')
plt.title('Minibatch run vs. Training loss')
plt.show()
plt.subplot(212)
plt.plot(plotdata["batchsize"], plotdata["avgerror"], 'r--')
plt.xlabel('Minibatch number')
plt.ylabel('Label Prediction Error')
plt.title('Minibatch run vs. Label Prediction Error')
plt.show()
マルチレイヤパーセプションの評価/テスト
reader_test = create_reader(test_file, False, input_dim, num_output_classes)
test_input_map = {
label : reader_test.streams.labels,
input : reader_test.streams.features,
}
test_minibatch_size = 512
num_samples = 10000
num_minibatches_to_test = num_samples // test_minibatch_size
test_result = 0.0
for i in range(num_minibatches_to_test):
data = reader_test.next_minibatch(test_minibatch_size,
input_map = test_input_map)
eval_error = trainer.test_minibatch(data)
test_result = test_result + eval_error
print("Average test error: {0:.2f}%".format(test_result*100 / num_minibatches_to_test))
Average test error: 90.20%
入力次元
畳み込みニューラルネットワークの入力データは3Dマトリックス(チャネル,画像の幅,画像の高さ)にて構成される
np.random.seed(0)
input_dim_model = (1, 28, 28)
input_dim = 28*28
num_output_classes = 10
CNNは複数のレイヤから構成されるネットワークであり、あるレイヤの出力が次のレイヤの入力になるようなネットワークである。
これはマルチレイヤパーセプション(Multi Layer Perception: MLP)と似ている。
MLPはすべてのピクセルの入力ペアを出力をすべて接続されている。したがって、組み合わせ爆発が発生するのと、オーバフィッティングが発生しやすい。
畳み込みレイヤは、ピクセルの空間は位置関係を活用して、ネットワーク中のパラメータの数を減らすことができる。
フィルタのサイズは畳み込みレイヤのパラメータである。
畳み込みレイヤ
畳み込みレイヤはフィルタの集合である。各フィルタは重み と バイアスから構成される。
これらのフィルタは画像内をスキャンし、画像とのドットプロダクトを計算し、バイアスを加算する。ドットプロダクトの値を活性化関数に渡す。
Image(url="https://www.cntk.ai/jup/cntk103d_conv2d_final.gif", width= 300)
畳み込みレイヤには以下の特徴がある。
入力と出力が完全接続されたレイヤと異なり、畳み込みノードは入力ノードの一部とその隣接するローカルノードでのみ接続される。これはreceptive field(RF)と呼ばれる。上図では3x3の領域がRF領域である。RGBの場合には、画像は3つの3×3の領域があり、3つの色チャネルのそれぞれが1つ存在する。
Denseレイヤのような単一の重みレイヤの代わりに、畳み込みレイヤは複数の重みの集合を持っている(上図ではそれぞれ別の色で表現されている)。これをフィルタと呼ぶ。各フィルタは入力画像の受信フィールドの特徴を検出する。畳み込みの出力はサブレイヤの一部である(下記のアニメーションに示す)。ここでn
はフィルタの数である。
サブレイヤの中では、各ノードが自分自身の重み情報を持つ代わりに、サブレイヤ中では同じ重み情報が利用される。これによりパラメータの数を減らし、学習のオーバフィッティングを防いでいる。この軽量化により、ディープラーニングのさまざな実際の問題で問題を解決可能にしている:
- より大きな画像の処理 (512 x 512など)
- より大きなフィルタサイズを使用する (11 x 11など)
- より多くのフィルタを学習させる(128など)
- よりレイヤ数の深いアーキテクチャを実現する(100+のレイヤなど)
- 変換の不変性を達成する(画像内のどの領域にあっても認識することができるような能力)
Strides and Pad parameters
フィルタをどのように配置するのか? 一般的に、フィルタはおーばラップしたタイルのように配置され、左から右方向へ、上から下へと進んでいく。
各畳み込みレイヤはfilter_shape
を指定するパラメータを持っており、ほとんど自然な画像の場合にフィルタの幅と高さを指定するパラメータを持っている。また、フィルタが画像内を移動する場合にどの程度のステップで移動するかを指定するためのパラメータ(stride
)が存在する。
booleanのパラメータであるpad
は入力値が境界線の近くでのタイルどりを可能にするために、入力がパディングされるかどうかを制御する。
上記のアニメーションではfilter_shape = (3, 3), strides = (2, 2), pad = False
の場合の処理を表現している。
次のアニメーションでは、pad
をTrue
に設定している。
最初のアニメーションではstride
は2だが、次のアニメーションではstride
は1である。
ここで、出力値の形(最も下のレイヤ)はそれぞれのストライドの設定により異なっている。
pad
およびstride
の値の決定は、出力レイヤの必要な形により決定される。
images = [("https://www.cntk.ai/jup/cntk103d_padding_strides.gif" , 'With stride = 2'),
("https://www.cntk.ai/jup/cntk103d_same_padding_no_strides.gif", 'With stride = 1')]
for im in images:
print(im[1])
display(Image(url=im[0], width=200, height=200))
With stride = 2
With stride = 1
CNNモデルの構築
本CNNのチュートリアルでは、最初に2つのコンテナを定義する。
1つ目のコンテナはMNIST画像を入力するためのもので、2つ目のものは10個の数字へのラベリングを行うためのものである。
データを読み込んだ時、readerは自動的に1画像あたり784ピクセルをinput_dim_model
のタプルへと変換する (この例では (1, 28, 28)
x = C.input_variable(input_dim_model)
y = C.input_variable(num_output_classes)
最初のモデルは、単純な畳み込みのみのモデルである。
ここでは2つの畳み込みモデルを持っており、MNISTの10個の数値を検出することが目的であるから、幅10のベクトルがネットワークの出力であり、各ベクトルの要素が各数字に相当する。
これは、num_output_class
のdenseレイヤの出力を使って、最後の畳み込み層の出力を反映することで可能となる。
このチュートリアルの前に、ロジスティック回帰およびMLPでは最後のレイヤでクラスの数分だけマッピングされることを見た。
また、トレーニング中にcross entropy損失関数を使用したsoftmax関数を利用することにより、最後のdenseレイヤは活性化関数を持っていない。
以下の図は我々が構築しようとしているネットワークのモデルである。
このモデル内のパラメータは実験に使用されることに注意すること。
これらはネットワークのハイパーパラメータと呼ばれる。
フィルタの形が増大すると、モデルパラメータの数も増え、計算時間も増大し、モデルがデータにフィットすることを助ける。
しかし、オーバフィッティングのリスクもある。
典型的に、より深い層のレイヤのフィルタの数はそれより前のレイヤのフィルタよりも大きい。
私たちは最初のレイヤで8、次のレイヤで16のフィルタを選択した。
これらのハイパーパラメータはモデル構築中に実験されるべきである。
Image(url="https://camo.githubusercontent.com/a157ed88853727d46ecbdc11fa0dcc868804c247/68747470733a2f2f7777772e636e746b2e61692f6a75702f636e746b313033645f636f6e766f6e6c79322e706e67")
x = C.input_variable(input_dim_model)
y = C.input_variable(num_output_classes)
def create_model(features):
with C.layers.default_options(init=C.glorot_uniform(), activation=C.relu):
h = features
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=8,
strides=(2,2),
pad=True, name='first_conv')(h)
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=16,
strides=(2,2),
pad=True, name='second_conv')(h)
r = C.layers.Dense(num_output_classes, activation=None, name='classify')(h)
return r
z = create_model(x)
print("Output Shape of the first convolution layer:", z.first_conv.shape)
print("Bias value of the last dense layer:", z.classify.b.value)
C.logging.log_number_of_parameters(z)
Output Shape of the first convolution layer: (8, 14, 14)
Bias value of the last dense layer: [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Training 11274 parameters in 6 parameter tensors.
モデルパラメータの学習
前回のチュートリアルと同様に、softmax
関数を使用してevidenceもしくは活性化された値を確率に変換する。
トレーニング
CNTK 102と同様に、cross-entropyを最小化することを目的とする。
最終的な値が想定と異なるならば、CNTK 102を再度見てほしい。
複数のモデルを効率的にビルドするために、いくつかのサポート関数を用意している。
def create_criterion_function(model, labels):
loss = C.cross_entropy_with_softmax(model, labels)
errs = C.classification_error(model, labels)
return loss, errs
def train_test(train_reader, test_reader, model_func, num_sweeps_to_train_with=10):
model = model_func(x/255)
loss, label_error = create_criterion_function(model, y)
learning_rate = 0.2
lr_schedule = C.learning_rate_schedule(learning_rate, C.UnitType.minibatch)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])
minibatch_size = 64
num_samples_per_sweep = 60000
num_minibatches_to_train = (num_samples_per_sweep * num_sweeps_to_train_with) / minibatch_size
input_map={
y : train_reader.streams.labels,
x : train_reader.streams.features
}
training_progress_output_freq = 500
start = time.time()
for i in range(0, int(num_minibatches_to_train)):
data=train_reader.next_minibatch(minibatch_size, input_map=input_map)
trainer.train_minibatch(data)
print_training_progress(trainer, i, training_progress_output_freq, verbose=1)
print("Training took {:.1f} sec".format(time.time() - start))
test_input_map = {
y : test_reader.streams.labels,
x : test_reader.streams.features
}
test_minibatch_size = 512
num_samples = 10000
num_minibatches_to_test = num_samples // test_minibatch_size
test_result = 0.0
for i in range(num_minibatches_to_test):
data = test_reader.next_minibatch(test_minibatch_size, input_map=test_input_map)
eval_error = trainer.test_minibatch(data)
test_result = test_result + eval_error
print("Average test error: {0:.2f}%".format(test_result*100 / num_minibatches_to_test))
トレーニングの実行とテストの実施
これで、畳み込みニューラルネットワークの構築が完了した。
def do_train_test():
global z
z = create_model(x)
reader_train = create_reader(train_file, True, input_dim, num_output_classes)
reader_test = create_reader(test_file, False, input_dim, num_output_classes)
train_test(reader_train, reader_test, z)
do_train_test()
Minibatch: 0, Loss: 2.3157, Error: 96.88%
Minibatch: 500, Loss: 0.1691, Error: 9.38%
Minibatch: 1000, Loss: 0.0890, Error: 1.56%
Minibatch: 1500, Loss: 0.1083, Error: 3.12%
Minibatch: 2000, Loss: 0.0115, Error: 0.00%
Minibatch: 2500, Loss: 0.0058, Error: 0.00%
Minibatch: 3000, Loss: 0.0049, Error: 0.00%
Minibatch: 3500, Loss: 0.0750, Error: 3.12%
Minibatch: 4000, Loss: 0.0220, Error: 0.00%
Minibatch: 4500, Loss: 0.0192, Error: 1.56%
Minibatch: 5000, Loss: 0.0101, Error: 0.00%
Minibatch: 5500, Loss: 0.0014, Error: 0.00%
Minibatch: 6000, Loss: 0.0048, Error: 0.00%
Minibatch: 6500, Loss: 0.0098, Error: 0.00%
Minibatch: 7000, Loss: 0.0079, Error: 0.00%
Minibatch: 7500, Loss: 0.0038, Error: 0.00%
Minibatch: 8000, Loss: 0.0139, Error: 0.00%
Minibatch: 8500, Loss: 0.0058, Error: 0.00%
Minibatch: 9000, Loss: 0.0148, Error: 0.00%
Training took 17.5 sec
Average test error: 1.50%
print("Bias value of the last dense layer:", z.classify.b.value)
Bias value of the last dense layer: [-0.02818329 -0.08973262 0.02727109 -0.14188322 0.0115815 -0.09806534
0.00059815 -0.09239398 0.39416206 0.01649885]
out = C.softmax(z)
reader_eval=create_reader(test_file, False, input_dim, num_output_classes)
eval_minibatch_size = 25
eval_input_map = {x: reader_eval.streams.features, y:reader_eval.streams.labels}
data = reader_eval.next_minibatch(eval_minibatch_size, input_map=eval_input_map)
img_label = data[y].asarray()
img_data = data[x].asarray()
img_data = np.reshape(img_data, (eval_minibatch_size, 1, 28, 28))
predicted_label_prob = [out.eval(img_data[i]) for i in range(len(img_data))]
pred = [np.argmax(predicted_label_prob[i]) for i in range(len(predicted_label_prob))]
gtlabel = [np.argmax(img_label[i]) for i in range(len(img_label))]
print("Label :", gtlabel[:25])
print("Predicted:", pred)
Label : [7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 3, 4, 9, 6, 6, 5, 4]
Predicted: [7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 3, 4, 9, 6, 6, 5, 4]
sample_number = 5
plt.imshow(img_data[sample_number].reshape(28,28), cmap="gray_r")
plt.axis('off')
img_gt, img_pred = gtlabel[sample_number], pred[sample_number]
print("Image Label: ", img_pred)
プーリングレイヤ
多くの場合に、特にディープネットワークを制御する場合にはパラメータの数を制御する必要がある。
全ての畳み込みレイヤの出力(各レイヤ、フィルタの出力に相当する)では、プーリングレイヤを配置することができる。
プーリングレイヤの典型的な特徴として、
- 前のレイヤの次元数を削減する(ネットワークを高速化する)
- 画像内のオブジェクトの位置ずれに対しても対応できるネットワークを構築する。例えば、もし画像の位置が中央ではなく横にずれていても、分類器が分類処理を問題なく実行できるようにする。
プーリングノードの計算は、通常のフィードフォワード処理よりも簡単である。
プーリングノードには重みも、バイアスも活性化関数も存在しない。
プーリングノードは簡単な集合関数(max, 平均など)を使用して出力を計算する。
最も共通して使用されるのはmaxである。入力のフィルタ位置に対応する入力値の最大値を出力する。
以下の図は4x4の領域を持つ入力値である。
maxプーリングウィンドウサイズは2x2であり、左上のこーなから処理を開始する。
ウィンドウ内の最大値がその領域の出力値となる。
モデルは指定されたストライドパラメータでシフトしていき、maxのプーリング操作が繰り返される。
もう一つのプーリング関数は"average"である。
averageはmaxの代わりに使用される。2つのプーリング処理をそれぞれアニメーションでまとめた。
images = [("https://www.cntk.ai/jup/c103d_max_pooling.gif" , 'Max pooling'),
("https://www.cntk.ai/jup/c103d_average_pooling.gif", 'Average pooling')]
for im in images:
print(im[1])
display(Image(url=im[0], width=200, height=200))
Max pooling
Average pooling
典型的な畳み込みネットワーク
典型的なCNNは複数の畳み込みレイヤとプーリングレイヤで構成されており、最後にdense出力レイヤが接続されている。
多くの分類器のディープネットワーク(VGG, AlexNetなど)のバリエーションを見つけることができるだろう。
CNTK_103Cで使用したMLPネットワークは対照的に、2つのdenseレイヤで構成されており、最後にdense出力レイヤが接続されている。
説明の図では2次元の画像処理のネットワークとして表現しているが、CNTKのコンポーネントはどのような次元のデータでも取り扱うことができる。
上位の根とワークでは2つの畳み込みレイヤと2つのmax-プーリングレイヤで構成されている。
典型的な戦略としては、より深いレイヤになるにつれてフィルタの数を増やし、中間レイヤの空間的なサイズを減らすことである。
タスク: MaxPoolingを利用したネットワークの構築
CNTKのMaxPooling関数を使用してこのタスクを実現する。create_model
関数を以下のように変更して、MaxPooling
操作を追加する。
def create_model(features):
with C.layers.default_options(init = C.layers.glorot_uniform(), activation = C.relu):
h = features
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=8,
strides=(1,1),
pad=True, name="first_conv")(h)
h = C.layers.MaxPooling(filter_shape=(2,2),
strides=(2,2), name="first_max")(h)
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=16,
strides=(1,1),
pad=True, name="second_conv")(h)
h = C.layers.MaxPooling(filter_shape=(3,3),
strides=(3,3), name="second_max")(h)
r = C.layers.Dense(num_output_classes, activation = None, name="classify")(h)
return r
do_train_test()
Minibatch: 0, Loss: 2.3284, Error: 95.31%
Minibatch: 500, Loss: 0.1598, Error: 7.81%
Minibatch: 1000, Loss: 0.0865, Error: 3.12%
Minibatch: 1500, Loss: 0.1355, Error: 3.12%
Minibatch: 2000, Loss: 0.0236, Error: 1.56%
Minibatch: 2500, Loss: 0.0036, Error: 0.00%
Minibatch: 3000, Loss: 0.0031, Error: 0.00%
Minibatch: 3500, Loss: 0.0744, Error: 3.12%
Minibatch: 4000, Loss: 0.0186, Error: 1.56%
Minibatch: 4500, Loss: 0.0503, Error: 1.56%
Minibatch: 5000, Loss: 0.0300, Error: 1.56%
Minibatch: 5500, Loss: 0.0176, Error: 0.00%
Minibatch: 6000, Loss: 0.0137, Error: 0.00%
Minibatch: 6500, Loss: 0.0308, Error: 1.56%
Minibatch: 7000, Loss: 0.0204, Error: 1.56%
Minibatch: 7500, Loss: 0.0040, Error: 0.00%
Minibatch: 8000, Loss: 0.0046, Error: 0.00%
Minibatch: 8500, Loss: 0.0363, Error: 3.12%
Minibatch: 9000, Loss: 0.0049, Error: 0.00%
Training took 18.4 sec
Average test error: 1.15%