あっぱれ日和、 - What a "Appare" day -

あっぱれ日和、 - What a "Appare" day -

通りすがりのしがない映画好きが綴る、モノづくりの記録や備忘録。そして、影響を受けた映画の記録。

機械学習初心者がDeep Learningを試してみる ~mnistを使ってニューラルネットを実装する~

はじめに、

機械学習初心者の人間がDeep Learningの実装にチャレンジする記録を残していこうと思い立ち本記事を書きました。

そして、本記事では機械学習初心者の私にふさわしいmnistを用いて実際にニューラルネットワークを実装し、実際に評価するところまでの実装の記録を記します。

コードはpythonで記述してますが、そんなに行数もないですし、人工知能の分野に馴染みのある数式も今回の記事では深くは記載いたしません。ざっくりこんな感じで実装するだという事が分かればと思い書きました。 
★今回の実装では、結果として、モデルを評価した際の損失関数がとても大きい値になってしまいました。何かしらの間違いがあるかもしれません。。。しかし、認識率はとても良い値を出すことができました。もし何か間違っていることがありましたらご指摘いただけますとありがたいです。

【目次】

本記事のゴール

で、結局、今回何をするかというとmnistを用いてニューラルネットワークを構築し、実際に画像認識をやってみるということが目的となります。

mnistとは

実際に実装する前に幾つか今回用いるものを抑えておきます。
まず、mnistですmnistとは画像認識をするためのディープラーニング機械学習をするために使われるデータセットです。

  • train-images-idx3-ubyte.gz (訓練用の60000枚の画像データ)
  • train-labels-idx1-ubyte.gz (訓練用の60000枚のそれぞれの画像に対するラベルデータ)
  • t10k-images-idx3-ubyte.gz (テスト用の10000枚の画像データ)
  • t10k-labels-idx1-ubyte.gz (テスト用の10000枚のそれぞれの画像に対するラベルデータ)

で構成されています。
以下の記事が分かりやすいと思います。
MNIST データの仕様を理解しよう
mnistは以下のページからダウンロードできます。今回学習に用いるデータはここから持ってきます。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

ニューラルネットワークとは

ニューラルネットワークとはニューロンという脳の神経細胞が情報を伝達する仕組みを模して作られたアルゴリズムになります。これだけではなんのこっちゃだと思いますが、詳しくわかりやすく書こうとすると長くなってしまうので今回は上記の説明でご勘弁ください。(機会があれば自分なりに記事にまとめたいです。)
ニューラルネットワークについては以下のyoutubeの動画はわかりやすいですね。英語ですが。。。
www.youtube.com
日本語でもこういう動画があればいいんですけどね。。。

実装環境

今回実装する環境に関してですが、以下のようになります。

1. 実装 ~実装前の事前準備~

では、早速、実装をしていきましょう。と言ってもまず事前準備をする必要があります。(ここで結構時間をとられました。。。)

ここでは大きく分けて2つの事をします。環境構築(1-1.)とデータの前処理(1-2.)になります。

1-1. 環境構築
まず、ニューラルネットワークを構築する環境を整えます。今回は以下のQiitaの記事を参考にしました。
AnacondaによるKeras-TensorFlowインストール(Win10, CPU版) - Qiita


まず、Anaconda Promptを立ち上げます。そして以下のコマンドを順に実行していきます。

■1-1-1. 仮想環境構築

conda create -n keras_work
■1-1-2. 構築した仮想環境の有効化
conda activate keras_work
今後、KerasやTensorFlowを使いたいときはまず、仮想環境の有効化を行ってからjupyter notebookを起動しましょう。

仮想環境を有効かしたことにより以下の処理から仮想環境内での作業になります。

■1-1-3. TensorFlowをインストール

conda install tensorflow
■1-1-4. Kerasをインストール
conda install keras
■1-1-5. Jupyterカーネルの登録
Jupyterからkernelを変更できるようにする
ipython kernel install --user --name keras222_tf110 --display-name keras222_tf110
必要に応じて適宜ipythonをインストールする必要があります。

この作成した環境でjupyter notebookを起動すれば(conda activate keras_workコマンドを使ってkeras_work仮想環境に入った状態でjupyter notebookコマンドを実行すれば)、tensorflowまた、kerasを使える状態のjupyter notebookが起動できます。 

1-2. データの前処理
さて、環境が整ったところで次は今回の学習で用いるデータの前処理を行います。参考にしたのは以下のサイトです。
PythonでMNISTをダウンロードして前処理する - Qiita
そして、今回の学習と評価に使用するためのデータをダウンロードします。上記でも示した以下のmnistのサイトより
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

  • train-images-idx3-ubyte.gz (訓練用の60000枚の画像データ)
  • train-labels-idx1-ubyte.gz (訓練用の60000枚のそれぞれの画像に対するラベルデータ)
  • t10k-images-idx3-ubyte.gz (テスト用の10000枚の画像データ)
  • t10k-labels-idx1-ubyte.gz (テスト用の10000枚のそれぞれの画像に対するラベルデータ)

をダウンロードしてきてpythonコードと同じフォルダ階層に置いておきます。まず、以下に前処理のコードを記載いたします。

import urllib.request
import gzip
import numpy as np

key_file = {
  'train_img':'train-images-idx3-ubyte.gz',
  'train_label':'train-labels-idx1-ubyte.gz',
  'test_img':'t10k-images-idx3-ubyte.gz',
  'test_label':'t10k-labels-idx1-ubyte.gz'
}

# 圧縮されたファイルの読込み(画像)
def load_img(file_name):
  file_path = './' + file_name
  with gzip.open(file_path, 'rb') as f:
  data = np.frombuffer(f.read(), np.uint8, offset=16)
  data = data.reshape(-1, 784)
  return data

# 圧縮されたファイルの読込み(ラベル)
def load_label(file_name):
  file_path = './' + file_name
  with gzip.open(file_path, 'rb') as f:
  labels = np.frombuffer(f.read(), np.uint8, offset=8)
  return labels

# 読み込んだ画像データおよびラベルのデータをそれぞれ格納する
dataset = {}
dataset['train_img'] = load_img(key_file['train_img'])
dataset['train_label'] = load_label(key_file['train_label'])
dataset['test_img'] = load_img(key_file['test_img'])
dataset['test_label'] = load_label(key_file['test_label'])

では試しに、画像を表示させてみましょう。訓練用の画像の60000枚のうちの1枚めを見てみます。

import matplotlib.pyplot as plt
%matplotlib inline

example = dataset['train_img'][0].reshape((28, 28))

plt.figure()
plt.imshow(example)
plt.colorbar()
plt.grid(False)
plt.show()

すると次のような「5」の画像が見れると思います。
f:id:appare99:20200505161315p:plain:w360
そして、訓練用のラベルの一つ目を見てみるとちゃんと「5」というラベルがついていることが分かります。

dataset['train_label'][0]
# →5と出力される

次に訓練用のラベルデータをニューラルネットワークで学習させるためにone-hot形式に変換させます。
one-hot形式とは正解ラベルの場所だけが1で他は0である形式です。上記でdataset['train_label'][0]でラベルの出力をした際、5という出力が出ましたが、これを[0,0,0,0,1,0,0,0,0,0,0]という記述に変換します。
コードは以下のようにになります。

def to_one_hot(label):
    T = np.zeros((label.size, 10))
    for i in range(label.size):
        T[i][label[i]] = 1
    return T

dataset['train_label'] = to_one_hot(dataset['train_label'])

次に正規化を行います。これはピクセル値が0~255の256階調で表されているのですが、これを255で割ることで0~1の間に値が収まるようにします。コードは以下のようになります。

def normalize(key):
    dataset[key] = dataset[key].astype(np.float32)
    dataset[key] /= 255

    return dataset[key]

dataset['train_img'] = normalize('train_img')

これでデータの準備は完了です。

モデルの作成

やっと。やっとですが、ニューラルネットワークを構築していきます。
参考にしたのは以下動画になります。動画は英語です。。。
www.youtube.com

まず、モデル作成の全体のプログラムを以下に示します。それからこのプログラムの細かな部分をプログラム中に記載の(1)~(4)に分けて説明していこうと思います。

rom keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.optimizers import RMSprop

# (1)
model = Sequential()

# (2)
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))

# (3)
model.add(Dense(10, activation='softmax'))

# (4)
model.summary()
model.compile(loss='categorical_crossentropy',
              optimizer=RMSprop(),
              metrics=['accuracy'])
(1) :空のモデルを作成
model = Sequential()

まず、Sequentialを使ってからのモデルを作成します。
この段階ではまだ、入力層や中間層や出力層などはありません。

(2) :入力層と中間層の追加
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))

ここでは入力層と中間層を追加していきます。
ここの部分は以下のサイトの「モデルの定義」という項目を見ていただければ各コードで何を定義しているかを視覚的に理解できると思います。Sequentialという箱を用意し、その中にaddで層を追加していくイメージです。
Kerasの使い方まとめ【入門者向け】

上記サイトにも記載してありますが、Denseは全結合ニューラルネットワークを定義しており、今回の入力層のユニット784個(28×28ピクセル)に対し、ユニット数512個の中間層を用意してあります。
そしてactivationは活性化関数を表し、今回の場合'relu'、つまりReLU関数を活性化関数として用いることを示しています。よって

model.add(Dense(512, activation='relu', input_shape=(784,)))

の処理で以下まで設定できてることになります。
f:id:appare99:20200505232557p:plain

model.add(Dropout(0.2))

は指定した割合で入力値を0にする層です。過学習を避けるために用意するものになります。ここでは0.2と設定しているので20%の入力が破棄されています。
そして、同様にまた再び

model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))

が続いているのでここまでで以下のような図になります。
f:id:appare99:20200505232537p:plain

(3):出力層の追加
model.add(Dense(10, activation='softmax'))

出力層は10種の数字の画像を識別するのでユニット数は10個用意します。
そして活性化関数にはsoftmax関数を用います。softmax関数は各成分の値が0~1の範囲で各成分の和が1になるような関数になります。ここまでで以下のようなモデルができました。
f:id:appare99:20200505232512p:plain
これで今回使用するモデルは完成しました。
さてこのモデルを次はコンパイルしなければなりません。

(4):モデルをコンパイルする

さて、次が学習させるまでの最後の工程となります。
モデルを学習する前にどのような学習処理を行うかを設定する必要があります。それが以下の部分の記述です。

model.summary()
model.compile(loss='categorical_crossentropy',
              optimizer=RMSprop(),
              metrics=['accuracy'])

「model.summary()」の部分はモデルの概要を表示させるためのものです。そして大事なのがcompileメソッドの部分です。
ここで設定しているのはloss、optimizer、metricsの3つとなります。

まず、lossですが、これは損失関数を記述する部分となってます。損失関数は正解からのズレの大きさを定量化するもので、少ないほど結果として間違いがないということを示しています。

次に、optimizerですが、上記のようにlossの部分で失敗している定量化ができているため、どれがどれだけ間違っているかの比較ができるようになっています。そのうえで正解に近づける修正方法の戦略の事を最適化といい、このoptimizerでは最適化するための手段を設定します。

最後のmetricsは評価関数になります。今回はaccuracyと設定していて、これに設定すると自動で"categorical_accuracy"などを判断してくれるらしいです。

学習させる

 やっと学習のステップになります。
ここまでで作成したモデルを用いて学習をさせていきましょう。
学習の部分のコードは以下のようになります。

batch_size = 128
epochs = 20

result = model.fit(dataset['train_img'], dataset['train_label'], 
                    batch_size=batch_size,
                    epochs=epochs, 
                    verbose=1, 
                    validation_data=(dataset['test_img'], dataset['test_label']))

batch_sizeはバッチサイズの事を示します。バッチサイズとは学習する際にデータセットを幾つかのサブセットに分けるのですが、この幾つかに分けたサブセットに含まれるデータ数をバッチサイズと言います。
例えば1000個のデータを200個のセット5つに分ける場合はバッチサイズは200ということになります。

そして、epochsはエポック数を表します。エポック数とは訓練データを何回繰り返して学習させるかという回数になります。
そして、このプログラムを実行すると2分~3分ほどで学習が完了します。

評価

最後に評価を行います。テスト画像を用いて今回学習させたモデルでどれくらいの精度の認識になるかを見てみます。コードは以下のようになります。

score = model.evaluate(dataset['test_img'], dataset['test_label'], verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

'''
出力は以下のようになります。
Test loss: 72.43405909423828
Test accuracy: 0.9819999933242798
'''

はい、上記でもお分かりの通り、accuracyは高い値を示しているので高い精度を表しているのですが、lossがとても高い値になってしまっています。。。損失の定量化であるのでこの値は低い方がいいはずなんですよね。。。

試しに訓練時のlossとaccuracyと評価時のlossとaccuracyの値の推移を見てみましょう。以下のコードになります。

acc = result.history['accuracy']
val_acc = result.history['val_accuracy']
loss = result.history['loss']
val_loss = result.history['val_loss']

epochs = range(len(acc))

# 1) Accracy Plt
plt.plot(epochs, acc,label = 'training acc')
plt.plot(epochs, val_acc, label= 'validation acc')
plt.title('Training and Validation acc')
plt.legend()

plt.figure()

# 2) Loss Plt
plt.plot(epochs, loss,label = 'training loss')
plt.plot(epochs, val_loss, label= 'validation loss')
plt.title('Training and Validation loss')
plt.legend()

plt.show()

出力結果は以下のようになります。
まずは精度。
f:id:appare99:20200506182117p:plain:w360
ここは特に問題が無いように思えます。
そして、次に損失。
f:id:appare99:20200506182231p:plain:w360
訓練時の損失は小さい値を維持しているのに対し、評価時の損失はどんどんと大きい値をとってしまっています。。。本来これが下がっていくのが理想なんですけどね。。。

検証

では試しにテスト画像をちゃんと識別できるかをやってみます。
その前に一旦、今回学習させたモデルを保存しておきます。

model.save('./models/mnist_leaning.model')

そして、今一度保存したモデルをmodel_00という名で読み込みます。

import tensorflow as tf

model_00 = tf.keras.models.load_model('./models/mnist_leaning.model')

そして、テスト画像を読み込んだモデルを使って認識させてみます。(もちろん保存前のmodelを使っても同じモデルなので同様の結果が得られます。)

predictions = model_00.predict(dataset['test_img'])

認識結果の一枚目をみてみましょう

print(np.argmax(predictions[0]))
# 出力結果は7になります。

では実際にこの認識があっているのか実際のテスト画像の1枚目を見てみましょう

example = dataset['test_img'][0].reshape((28, 28))

plt.figure()
plt.imshow(example)
plt.colorbar()
plt.grid(False)
plt.show()

f:id:appare99:20200506183834p:plain:w360
ちゃんと「7」と書いてある画像でした。なので識別は成功しています。

まとめ(課題)

はい、今回はmnistを使って簡単なニューラルネットワークを構築しました。認識の精度は評価時のグラフから高いように思えますが、損失が学習ごとに高くなっているのがとにかく気になる結果になりました。
実際にコードを書いてみるとそこまで長いコードも書かなくてよく構えていたほどよりは簡単に実装ができました。
さらに実際に書いてみることで、どんな処理がどう行われているかをざっくりとですが把握することができたと思います。実際に本職で機械学習を使われている方に比べれば数式を理解していなかったり力の差は歴然ですが、今の機械学習ブームに少しはついていけるかもと思えるよい機会になりました。
 
 ★上記記事内容に問題や記述ミスがあればご指摘のほどよろしくお願いいたします。また、記述の意味が分からないところがあれば質問をしていただければ答えるようにいたします。

 

*1:Kerasとはオープンソースとして使用できるDeep Learningのライブラリです。