Dragon Arrow written by Tatsuya Nakaji, all rights reserved animated-dragon-image-0164

ゼロから作る Deep Learning 第3章

イメージ
May 09, 2019

手書き数字認識

MNIST データセット

ここで使用するデータセットは MNIST という手書き数字の画像セット(機械学習の分野で最も有名なデータセットの一つ)

0 から 9 までの数字画像から構成されます(図3-24)。 訓練画像が 60,000 枚、テスト画像が 10,000 枚用意されており、それらの画像を使用 して、学習と推論を行う。


準備: データセットのダウンロード・画像のNumPy 配列 への変換


本で紹介されている通り、MNIST データセットのダウンロードから画像データの NumPy 配列 への変換を記述した Python スクリプト(mnist.py)を作成して実行する

mnist.pyのload_mnist引数 

1 つ目の引数の normalize は、入力画像を 0.0~1.0 の値に正規化する(255で割る)かどうか False にすれば、入力画像のピクセル(色のついた微小な点)は元の 0~255 のまま

2 つ目の引数の flatten は、入力画像を平らにする(1 次元配列にする)かどうか 

False に設定すると、入力画像は 1 × 28 × 28 の 3 次元配列として、True にすると 784 個の要素からなる 1 次元配列として格納

3 つ目の引数の one_hot_label は、ラベルを one-hot 表現([0,0,1,0,0,0,0,0,0,0] のように、正解となるラベルだけが 1 で、それ以外 は 0 で表現)として格納するかどうか 

False のときは、7、2 といったように単純に 正解となるラベルを格納

Python には、pickle という便利な機能があります。これは、プログラム の実行中のオブジェクトをファイルとして保存する機能です。一度保存したpickle ファイルをロードすると、プログラムの実行中だったときのオブジェク トを即座に復元することができます。なお、MNIST データセットを読み込むload_mnist() 関数の内部でも、(2 回目以降の読み込み時に)pickle を利 用しています。pickle の機能を利用することによって、MNIST のデータの準 備を高速に行うことができます。


$ cd work_dir
$ mkdir dataset && cd dataset # フォルダ名datasetにしておく(任意)

dataset配下にmnist.pyを貼り付け
$ python mnist.py
ダウンロードされれば終了

(mnist.pyと同じディレクトリに
mnist.pkl, t10k-images-idx3-ubyte.gz, t10k-labels-idx1-ubyte.gz, train-images-idx3-ubyte.gz, train-labels-idx1-ubyte.gz 
が作成されます)


$ cd work_dir 
$ mkdir ch03 # ch03は3章で作成するファイルをまとめるフォルダ(名前は任意)
$ ls
dataset ch03 # dataset(miniset.pyなどが入ったフォルダ) ch03(3章で作成するファイルをまとめるフォルダ)
$ cd ch03 # 以下、このフォルダにファイルを作成する


実践

訓練画像データ, 訓練教師データ, テスト画像データ, テスト教師データ を見てみる

(test.py)
import sys, os
sys.path.append(os.pardir) # 親ディレクトリからファイルをインポートするためのパス追加
from dataset.mnist import load_mnist # 親ディレクトリ直下の datasetフォルダのmnist.pyファイルからload_mnistメソッドをimport

# コードのxは 画像データ 、tは 教師ラベルデータ で、trainは学習用データ、testはモデルの性能を評価する際に使用するデータ
# trainデータとtestデータには、データとしての本質的な違いはない。
# X_train、y_train、X_test、y_test はすべてnumpy.ndarray型
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

# それぞれのデータの形状を出力
# ndarray.shapeは、各次元ごとの要素数を示します。
print(x_train.shape) # (60000, 784) 訓練画像が6万枚用意されており、それぞれに28ピクセル*28ピクセル=784個の要素からなる平坦化された一次元配列が入った二次元配列になっている
print(t_train.shape) # (60000,) ラベル(0~9のどれか)が6万枚分答えとして一次元配列になっている
print(x_test.shape) # (10000, 784) テスト画像が1万枚用意されており、それぞれに28ピクセル*28ピクセル=784個の要素からなる平坦化された一次元配列が入った二次元配列になっている
print(t_test.shape) # (10000,) ラベル(0~9のどれか)が1万枚分答えとして一次元配列になっている

* 親ディレクトリに置かれているスクリプトファイルをimportしたい場合

  • os.pardirは、親ディレクトリを表す文字列定数。
  • sys.pathは、importするファイルを検索するパスを示す文字列のリストです。PYTHONPATH 環境変数と、インストール先でのデフォルトパスで初期化されます。ですので、sys.pathにimportしたいファイルのディレクトリを含めれば、適当なディレクトリに置かれているスクリプトファイルをimport出来る。
>>> import sys,os
>>> sys.path.append(os.pardir)
>>> import tools


MNISTの手書き文字の画像を表示してみる

(mnist_show.py)
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image # MNIST 画像表示には PIL(Python Image Library)モジュールを使用


def img_show(img):
pil_img = Image.fromarray(np.uint8(img)) # NumPy として格納された画像データを、PIL用のデータオブジェクトに変換する
pil_img.show()

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]
print(label)  # 5

print(img.shape)  # (784,) flatten=True として読み込んだ画像imgは NumPy配列として1列(1次元)で格納されている
img = img.reshape(28, 28)  # imgは一次元配列の入りのついた点の集まりなので、縦横28pxで元の画像サイズに変形(ちなみにimg.reshape(2, 392)とかやるとビヨーンと横線みたいになる)
print(img.shape)  # (28, 28)

img_show(img)



ニューラルネットワークの推論処理

(neuralnet_mnist.py)
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import pickle
from dataset.mnist import load_mnist # 親ディレクトリ直下の datasetフォルダのmnist.pyファイルからload_mnistメソッドをimport
from common.functions import sigmoid, softmax # 親ディレクトリ直下のcommonフォルダのfunctions.pyから必要なメソッドをimport


def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test


# pickleファイルの sample_weight.pklに保存された学習済みの重みパラメータを読み込みます
# このファイルには、重みとバイアスのパラメータがディクショナリ型の変数として保存されています
# 隠れ層が2つあり、ひとつ目の隠れ層か50 個、2つ目の層が100個のニューロンを持つものとします。この50と100という数字は、任意の値に設定できます
def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network

# ニューラルネットワークアルゴリズムで入力層 -> 隠れ層1 -> 隠れ層2 -> 隠れ層3 -> 出力層 まで算出
# ニューロンは764個のピクセル(正規化されているので、それぞれの値が0~1) 出力は [0の確率, 1の確率, 2の確率, ... ,9の確率]として分類される
def predict(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3) # 他クラス分類は、出力層の活性化関数はソフトマックス関数

return y


x, t = get_data() # x:テストする画像データ t: テストする教師ラベルデータ
network = init_network()
accuracy_cnt = 0

# テスト画像をループで渡し、全て予測していく
for i in range(len(x)):
  y = predict(network, x[i])
  p = np.argmax(y) # 最も確率の高い要素の インデックス を取得(深層学習で返された配列のインデックスが手書き文字の数字でvalueが確率)
# 確率が最も高いと予測される数字と教師データが一致したら精度をメモ
  if p == t[i]:
  accuracy_cnt += 1

# 最後に 精度 (的中した回数 / 予測した回数) を出力
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

# 出力結果 Accuracy:0.9352



ニューラルネットワークのバッチ化

上記のneuralnet_mnist.pyでは、ループでMNIST画像を1ループにつき1個渡しおり、画像の個数分ループを実行している。

しかし、ループでデータを一個一個渡してニューラルネットワークで計算して確率を出していくより、複数の画像をまとめて渡して巨大な配列を一度に計算するほうが、速く計算が完了する。

このようにデータを束でまとめて渡して計算処理を行うやり方をバッチ処理といい、1 枚あたりの処理時間を大幅に短縮できる。


(neuralnet_mnist_batch.py)
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax


def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test


def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network


def predict(network, x):
w1, w2, w3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, w1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, w2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, w3) + b3
y = softmax(a3)

return y

# バッチなし
# x, t = get_data()
# network = init_network()
# accuracy_cnt = 0
# for i in range(len(x)):
#     y = predict(network, x[i])
#     p = np.argmax(y)
#     if p == t[i]:
#         accuracy_cnt += 1
# print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

# バッチあり 1枚あたりの処理時間を大幅に短縮できるという利点 (大きな配列を一度に計算するほうが、分割した小さい配列を大量に計算するよりも速く計算が完了する)
x, t = get_data()
network = init_network()

batch_size = 100 # バッチの数
accuracy_cnt = 0

# 一回のループで画像一枚一枚ではなく, batch_size(100個)をまとめて処理する
for i in range(0, len(x), batch_size):
  x_batch = x[i:i+batch_size] # xのインデックスi番目 から (i+batch_size-1)番目までが取り出される i+batch_size-1が最大インデックスを超えても、最大までしか取りだされないので、範囲が大きくなってても綺麗に最後まで配列取得ができる
  y_batch = predict(network, x_batch) # batch_size(100)個分のバッチ画像配列それぞれに0~9の確率が入った, 二次元配列になっている
  p = np.argmax(y_batch, axis=1) # 行列の各行で最大のインデックスを返す すなわち、各画像で確率が最も高い数字を返す 大きさbatch_sizeの一次元配列になる
# batch_size個の予測データと正解データを比べる p == t[i:i+batch_size] => array([False,..., True])みたいになり
# np.sumにより、正解した数(False=0 と True=1の総和)がaccuracy_cntに足される
  accuracy_cnt += np.sum(p == t[i:i+batch_size])

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

axisはどの軸かを指定するための方法

二次元配列の時

三次元配列の時

例:

>>> import numpy as np

>>> a = np.arange(6).reshape((3, 2))

>>> a

array([[0, 1],

       [2, 3],

       [4, 5]])

>>> print(a.sum(axis=0)) # 列ごと

[6 9]

>>> print(a.sum(axis=1)) # 行ごと

[1 5 9]



>>> a = np.arange(24).reshape((3, 2, 4))


>>> a

array([[[ 0,  1,  2,  3],

        [ 4,  5,  6,  7]],


       [[ 8,  9, 10, 11],

        [12, 13, 14, 15]],


       [[16, 17, 18, 19],

        [20, 21, 22, 23]]])

>>> a.sum(axis=0).shape

(2, 4)

>>> a.sum(axis=1).shape

(3, 4)

>>> a.sum(axis=2).shape

(3, 2)

>>> a.sum(axis=0)

array([[24, 27, 30, 33],

       [36, 39, 42, 45]])

>>> a.sum(axis=1)

array([[ 4,  6,  8, 10],

       [20, 22, 24, 26],

       [36, 38, 40, 42]])

>>> a.sum(axis=2)

array([[ 6, 22],

       [38, 54],

       [70, 86]])



三章まとめ

ニューラルネットワークの順方向の伝播(forward propagation)につ いて解説しました