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の問題を
- ロジスティック回帰分析を用いて実現する
- パーセプション分類器(MLP)を用いて実現する
- 畳み込みニューラルネットワークを使って実現する
と3種類の方法をCNTKで実装し比較検討を行っている。
以降の文章については、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をインラインで利用できるようにする %matplotlib inline
データダウンロード
MNISTデータベースはマシンラーニングアルゴリズムにおいて非常に一般的に使用されている手書きデータである。 トレーニングには60000個のデータを使用しており、10000個のデータをテストに利用している。各データには28x28ピクセルのデータで構成されている。 このデータセットはトレーニングのデータを可視化しやすいものである。
# Functions to load MNIST images and unpack into train and test set. # - loadData reads image data and formats into a 28x28 long array # - loadLabels reads the corresponding labels data, 1 for each image # - load packs the downloaded image and labels data into a combined format to be read later by # CNTK text reader 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)) # Read magic number. if n[0] != 0x3080000: raise Exception('Invalid file: unexpected magic number.') # Read number of entries. 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.') # Read data. 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)) # Read magic number. if n[0] != 0x1080000: raise Exception('Invalid file: unexpected magic number.') # Read number of entries. n = struct.unpack('>I', gz.read(4)) if n[0] != cimg: raise Exception('Invalid file: expected {0} rows.'.format(cimg)) # Read labels. 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個のテストデータから構成されている。 まずはこのデータをダウンロードする。
# URLs for the train image and labels data 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.
データの可視化
# Plot a random image 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として表現されている)
# Save the data files into a format compatible with CNTK text reader 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)
# Save the train and test files (prefer our default path for the data) 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に必要なデータを用意ししている。手書きの文字を認識する。
# Figure 1 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 # Use a function definition from future version (say 3.x from 2.7 interpreter) import matplotlib.image as mpimg import matplotlib.pyplot as plt import numpy as np import sys import os import cntk as C %matplotlib inline
# Select the right target device when this notebook is being tested: 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個のテストセットを持っている。
# テキストフォーマットのCTFを読み込む(ファイルからのCFTデシリアライザを読み込む) 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 # Scale the input to 0-1 range by dividing each pixel by 255. z = create_model(input/255.0)
# Define a utility function to compute the moving average sum. # A more efficient implementation is possible with np.cumsum() function def moving_average(a, w=5): if len(a) < w: return a[:] # Need to send a copy of the array return [val if idx < w else sum(a[(idx-w):idx])/w for idx, val in enumerate(a)] # Defines a utility that prints the training progress 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%
# Compute the moving average loss to smooth out the noise in SGD plotdata["avgloss"] = moving_average(plotdata["loss"]) plotdata["avgerror"] = moving_average(plotdata["error"]) # Plot the training loss and the training 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): # test_minibatch_size のバッチサイズでテストデータをロードする。 # 784ピクセルのMNISTデータをロードする 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) # Scale the input to 0-1 range by dividing each pixel by 255. z = create_model(input/255.0)
loss = C.cross_entropy_with_softmax(z, label) label_error = C.classification_error(z, label) # Instantiate the trainer object to drive the model training 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]) # Initialize the parameters for the trainer 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 # Create the reader to training data set reader_train = create_reader(train_file, True, input_dim, num_output_classes) # Map the data streams to the input and labels. input_map = { label : reader_train.streams.labels, input : reader_train.streams.features } # Run the trainer on and perform model training training_progress_output_freq = 500 plotdata = {"batchsize":[], "loss":[], "error":[]} for i in range(0, int(num_minibatches_to_train)): # Read a mini batch from the training data file 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%
# Compute the moving average loss to smooth out the noise in SGD plotdata["avgloss"] = moving_average(plotdata["loss"]) plotdata["avgerror"] = moving_average(plotdata["error"]) # Plot the training loss and the training 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): # We are loading test data in batches specified by test_minibatch_size # Each data point in the minibatch is a MNIST digit image of 784 dimensions # with one pixel per dimension that we will encode / decode with the # trained model. 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 # Average of evaluation errors of all test minibatches 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) # 28 x 28 with 1 チャネルの画像を定義する 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
ストライドとPadパラメータ
フィルタをどのように配置するのか? 一般的に、フィルタはおーばラップしたタイルのように配置され、左から右方向へ、上から下へと進んでいく。
各畳み込みレイヤはfilter_shape
を指定するパラメータを持っており、ほとんど自然な画像の場合にフィルタの幅と高さを指定するパラメータを持っている。また、フィルタが画像内を移動する場合にどの程度のステップで移動するかを指定するためのパラメータ(stride
)が存在する。
booleanのパラメータであるpad
は入力値が境界線の近くでのタイルどりを可能にするために、入力がパディングされるかどうかを制御する。
上記のアニメーションではfilter_shape = (3, 3), strides = (2, 2), pad = False
の場合の処理を表現している。
次のアニメーションでは、pad
をTrue
に設定している。
最初のアニメーションではstride
は2だが、次のアニメーションではstride
は1である。
ここで、出力値の形(最も下のレイヤ)はそれぞれのストライドの設定により異なっている。
pad
およびstride
の値の決定は、出力レイヤの必要な形により決定される。
# Plot images with strides of 2 and 1 with padding turned on 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) # function to モデルの構築 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 # (model, labels) -> (loss, error metric) def train_test(train_reader, test_reader, model_func, num_sweeps_to_train_with=10): # Instantiate the model function; x is the input (feature) variable # We will scale the input image pixels within 0-1 range by dividing all input value by 255. model = model_func(x/255) # Instantiate the loss and error function loss, label_error = create_criterion_function(model, y) # Instantiate the trainer object to drive the model training 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]) # Initialize the parameters for the trainer minibatch_size = 64 num_samples_per_sweep = 60000 num_minibatches_to_train = (num_samples_per_sweep * num_sweeps_to_train_with) / minibatch_size # Map the data streams to the input and labels. input_map={ y : train_reader.streams.labels, x : train_reader.streams.features } # Uncomment below for more detailed logging training_progress_output_freq = 500 # Start a timer start = time.time() for i in range(0, int(num_minibatches_to_train)): # Read a mini batch from the training data file 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 time print("Training took {:.1f} sec".format(time.time() - start)) # Test the model test_input_map = { y : test_reader.streams.labels, x : test_reader.streams.features } # Test data for trained model 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): # We are loading test data in batches specified by test_minibatch_size # Each data point in the minibatch is a MNIST digit image of 784 dimensions # with one pixel per dimension that we will encode / decode with the # trained model. 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 # Average of evaluation errors of all test minibatches 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)
# Read the data for evaluation 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() # reshape img_data to: M x 1 x 28 x 28 to be compatible with model 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))]
# Find the index with the maximum value for both predicted as well as the ground truth 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]
# Plot a random image 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つのプーリング処理をそれぞれアニメーションでまとめた。
# Plot images with strides of 2 and 1 with padding turned on 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
操作を追加する。
# function to build model 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%