|
公開日:2018/7/24 , 最終更新日:2020/6/20
|
前提知識
・ニューラルネットワークとは
・誤差逆伝播法
・pythonとは
ニューラルネットワークの考え方をこちらで説明しましたが、ここでは実際にニューラルネットワークをpythonで実装し画像認識させます。
認識させたい画像は私の手書き文字をデータとして取り込んだものです。画像が少し荒いですが、これは後述する学習データと同じサイズに合わせてます。

■実装イメージ
この画像を2と認識させるためには、ニューラルネットワークの入力には画像の1ピクセル毎の色情報を数値化したもの、出力には各ノードを0~9と割り当て、
正解のノードに最も高い数値を出力させれば良いです。今回は画像が2なので、正解が2のノード(出力層の上から3番目)が最も高い数値になれば、正しく画像を認識できた事になります。
下記の様に、中間層と出力層の全ノードに対してパーセプトロンの計算を行います。シグモイド関数は中間層で100個、出力層で10個。
重みの数は入力層と中間層間で784x100個、中間層と出力層間で100x10個となり、相当な数の計算を行います。

入力層は画像のピクセル数と同じ784層、出力層は正解のパターン数の10層(0~9)となり、中間層は100層を設定しています。
中間層をいくつにするかが設計上のポイントの一つで、試行錯誤で決めていきます。この様な人間の手で試行錯誤しながら決めるパラメータをハイパーパラメータといいます。
学習スピードを決める係数などもハイパーパラメータに含まれます。
今回私が100層とした理由はまさに試行錯誤の結果ですが、入力層のデータを重要なデータに厳選しつつ、ただし出力層よりは多くないと0~9の答えを表現できなくなると考えたからです。
200や50ではなくて100であるという理由は、計算数を多くし過ぎず、なおかつ良い正解率を得られたのが100だったためです。
■重みの設定(誤差逆伝播法)
重みをどんな値にするかも非常に重要で、それで画像認識の精度が全く異なってきます。
重みの設定は誤差逆伝播法を用い、出力結果と真の値との差分を取り、この差分が無くなる様に重みを調整していきます。
先ずは、自分の手書きの画像とは別の画像(訓練データ)で重みを充分更新させた後、自分の手書きの画像(テストデータ)を入力として与えます。

テストデータと訓練データを分ける理由は、オーバーフィッティング(過学習)を防ぐためです。
精度100%の完璧な予測精度を目指すためには、その気になればテストデータのパターンを完全に覚えてしまえば良い(これが過学習の最たるもの)ですが、
それでは他のデータに対しては役に立たないからです。学習結果、どれくらい汎化性能を持っているか評価したり、ハイパーパラメータを調整する方法に交差検証やホールドアウト法などがあります。
■訓練データの入手
重みの最適化には訓練データを大量に用意する必要がありますが、自分で準備するのは大変なのでMNISTのデータを用います。
MNISTとはニューラルネットワークの研究者Yann LeCun氏の有名なサイトのことで、ここに機械学習用のデータ用として1から9までの手書きの数字をデータ化したものがあります。
トレーニングデータとして60,000個のデータ、テストデータとして10,000個のデータがあります。
■pythonによる実装
これまで説明した内容をpythonで実装してみます。pythonに実装するために必要な考え方はおおよそ説明しました。
まとめてみると画像認識は以下の流れとなります。

pythonで実行するためのプログラムのテキストデータはこちら。(対象を名前を付けて保存)
この全体コードを見比べながら読んでください。
①画像取り込み、データ化
訓練データ
先ほど訓練データの形について説明しましたが、実際にpython上で読み取って画像データとして表示させてみます。
なおこの画像として開くコードの部分はニューラルネットワークとしては使いません。
import numpy as np
import matplotlib.pyplot as plt
training_file = open("mnist_train_data.csv", 'r') # 訓練データを開く
training_list = training_file.readlines() # データを読み込む
training_file.close() # ファイルを閉じる
↓ニューラルネットワークの本プログラムには不要なコード
data= training_list[0].split(',') # 0行目を取り出し、splitでデータをカンマ(,)区切る
img= np.asfarray(data[1:]).reshape((28,28)) # 0行目の2つ目のデータから取り出し、28x28に配置
plt.imshow(img, cmap='Greys') # 画像をグレースケールで表示出来るようにする
plt.show() # 画像を表示
結果は以下となります。これは確かに5であることが人間の目では分かります。また数値と対比させると、'Greys'の設定においては、0が白、最大255が黒であることが分かります。
その間の数は白と黒の中間の色となります。

テストデータ
imreadでテスト画像データを読み込むと0から255の数値が返ってくるかと思います。ですが訓練データと違って白が255、黒が0となっているので、数値を反転させる必要があります。
また、0~255の値を0.01~1に正規化します。0.01が必要なのは、重みの値が全く反映されなくなるのを防ぐためです。上限が1なのは特段の理由は有りません。それを踏まえたコードは以下となります。
img = spm.imread("neu.JPG" , flatten=True) # 自分で準備した画像のファイル名。使用したのはこちら。
img_a = ((255-np.asfarray(img).reshape(784))/255*0.99)+0.01
②パラメータ(ノード数、学習値)、重み初期値の設定
inod = 784 # 入力層の数
hnod = 100 # 中間層の数
onod = 10 # 出力層の数
rate = 0.1 # 学習率
wih = np.random.normal(0,pow(hnod,-0.5),(hnod, inod)) # 入力層ー中間層間の重み
who = np.random.normal(0,pow(onod,-0.5),(onod, hnod)) # 中間層ー出力層間の重み
入力層は784、隠れ層は100、出力層は10、誤差逆伝播法の重みの学習率(上記(2)のα)を0.1とします。
重みは以下の様に行列で表現し、入力層数と中間層数の行列と、中間層数と出力層数の行列で表現します。

重みの初期値は、平均値0、分散が重み数の-0.5乗の標準偏差の分布に従ったランダム値を設定するのが良いとされております。
例えば重みの数が10個の時、取りうる重みの範囲は以下が望ましいです。

③訓練用の画像を用いて、重みを設定(誤差逆伝播法)
重みを設定するには上記(2)式を計算する必要があるのですが、(2)式を計算するには以下図に記載しているパラメータをまずは求めます。

計算の流れは以下のとおり。右から左への流れとなります。

pythonのプログラムは以下のとおり。
# 訓練データによる重みの学習
for n in training_list: # training_listを一行ずつ読みだし、nに格納する。
all_values = n.split(',') # nに格納した情報を','で区切る。
in_list = (np.asfarray(all_values[1:]) / 255 * 0.99) + 0.01 # 最初の一文字を除き処理をする
target_list = np.zeros(onod)+0.01 # 目標値を先ずは全てのラベルを0.01にする。
target_list[int(all_values[0])] = 0.99 # 次に目標値を正解のラベルだけ0.99にする。
neu.perce(in_list,wih,who) # パーセプトロンの計算をする。
neu.learn(target_list,wih,who) # 重みの学習をする。
def perce(self, in_list, wih, who): # パーセプトロンの計算
self.inputs = np.array(in_list,ndmin=2).T # 入力(784x1)
hid_in = np.dot(wih, self.inputs) # 中間層への入力(100x1)
self.hid_out = sp.expit(hid_in) # 中間層からの出力(シグモイド関数)(100x1)
fin_in = np.dot(who, self.hid_out) # 出力層への入力(10x1)
self.fin_out = sp.expit(fin_in) # 出力層からの出力(シグモイド関数)(10x1)
return self.fin_out
def learn(self, target_list, wih, who): # 訓練データによる重みの学習(誤差逆伝播法)
target = np.array(target_list,ndmin=2).T #真値(10x1)
out_err = target - self.fin_out # 出力層の誤差(10x1)
hid_err = np.dot(who.T, out_err) # 中間層の誤差(100x1)
who += rate * np.dot((out_err * self.fin_out *(1 - self.fin_out)), self.hid_out.T)# (10x100)
wih += rate * np.dot((hid_err * self.hid_out *(1 - self.hid_out)), self.inputs.T) # (100x784)
④テスト用の画像に対しニューラルネットワークで画像を識別
テストデータを入力としてパーセプトロンの計算を行います。(計算方法は重みを学習した時と同じ)
print(neu.perce(in_list,wih,who)) # パーセプトロンの計算
■プログラムの動作環境、必要ファイル
・pythonバージョン:Ver3.6で確認
・必要ライブラリ:numpy , scipy (インストール方法はこちら)
・必要ファイル:neural.zip(プログラムファイル) , neu.jpg(テスト画像データ) , 訓練データ
■プログラムの実行結果
プログラムの実行結果は以下のとおり、画像が2であるという事を認識することが出来ました。エラーが出る場合は、画像データを同一フォルダに置いていない、
numpyなどのライブラリがインストールされていない、などの原因が考えられるので確認しましょう。

■ニューラルネットワークの精度を検証する
今回は一つの画像に対してきちんと認識できたことを確認しましたが、たまたま正解しただけかもしれません。
この設計結果が本当に妥当なものか評価する必要があり、そのやり方をこちらで説明します。
サブチャンネルあります。⇒ 何かのお役に立てればと
|
|