社畜エンジニア発掘戦線

駆けだしAIエンジニア

MNIST画像認識のNN正解率の検討

おつかれさまです。

入力と出力のみから構成されるシンプルなニューラルネットワーク、そしてそれに隠れ層を追加したニューラルネットワークで正解率の比較を行いました。しかしながら、結果は同じくらいの正解率にとどまり、あまり違いが見れませんでした。

シンプルニューラルネットワーク:86.64 %
隠れ層追加ニューラルネットワーク:87.08 %

ただ、学習経過のグラフを確認すると、もっとイテレーションを回すことでまだまだ正解率が上昇しそうな傾向が見られました。この検証はイテレーション回数を3000回でトライしたので、学習がまだまだ足りなかった可能性が考えられます。

f:id:sutokun:20190401024045p:plain:w500
シンプルニューラルネットワークの学習グラフ
f:id:sutokun:20190417102000p:plain:w500
隠れ層追加ニューラルネットワークの学習グラフ

とりあえず、単純にイテレーション回数を増加させてみるとどうなるのか確認してみたいと思います。

イテレーション回数の検証

これまでに作った2つのプログラムをくっつけてみます。これで同じ初期パラメータ、バッチ画像となるようにして、2つのニューラルネットワークを比較させてみました。

イテレーション回数は2000回〜500000回で適当に振り分けてみました。ちなみに、このときの学習率は0.001です。

こちらがその結果、左側が2000回のグラフで、右側が500000のグラフです。赤色が「シンプルニューラルネットワーク」、青色が「隠れ層追加ニューラルネットワーク」になります。

f:id:sutokun:20190504105957p:plain:w400f:id:sutokun:20190504110017p:plain:w400

2000回のグラフは、まあ見たことあるわって感じですが、500000のグラフはなんじゃこりゃってなりますね。赤色の正解率は完全に飽和してしまっていますが、青色のグラフは、文字通り「青天井」でどんどん上がっています。何なら100%超えとるやんけ。

2000〜500000の間をアニメ化してみます、まぁ特に深い意味はないですが、隠れ層の正解率がぶりぶり上昇しているのが分かります。

f:id:sutokun:20190504110635g:plain:w400


ではそれぞれの学習で得たパラメータ(WとB)を使ってテスト画像で正解率を求めてみます。…これは気が遠くなる作業でした、まぁプログラムでだいたい自動化してたので待つだけでしたが。さすがに500000の計算は時間がかかった、一晩中CPUをあっちっちで走らせて計算させました(GPU使えたら楽になるんかなぁ、と初めて実感)。

こちらがその結果、

f:id:sutokun:20190504111853p:plain:w400

これは分かりやすい結果が得られました、この結果から得られる情報は以下の通り。

・ 訓練では青天井で正解率が上昇していますが、テスト結果では飽和します。
・ 赤グラフは92%ほどで飽和しているのに対して、青グラフは96%まで上昇して飽和しています。
・ 学習結果は100000〜150000回で飽和します。

やっぱり学習回数を増やせば結果は上昇するんですね、86%程度でわーいって言ってた頃が私にもありますた。

学習率の検証

正解率が飽和することが分かってくると、「じゃあさっさと飽和してくれよ、時間かかりすぎるんだよ」と思うのが世の常です。短い時間で学習を終えようとするなら、とりあえず学習率を上げてみることにします。最初のトライは学習率が0.001だったので、0.002で同じように計算してみます。

こちらがその結果、

f:id:sutokun:20190504114307p:plain:w400

おお、正解率は変わりませんが、なんだか飽和が早くなった気がします。このまま学習率を上げていってみましょう。めっちゃ時間かかるので、イテレーション回数は200000回にしています。

f:id:sutokun:20190504115109p:plain:w400
学習率:0.003
f:id:sutokun:20190504115149p:plain:w400
学習率:0.005
f:id:sutokun:20190504115222p:plain:w400
学習率:0.01

学習率を0.01まで上げると形が崩れてき始めているので、このあたりが限度なんですかね。
(もっと突っ込んで調べてみてもいいんですが、計算に時間がかかりすぎるんでこのあたりにしておきます)

まとめ

今回の検証で、最初に行ったテストは全然学習が足りていないことが分かりました。最終的な正解率は、シンプルニューラルネットワークだと約92%、隠れ層を追加すると96%まで上昇するようです、これってもうほとんど人間やんけ、ディープラーニングすげーな。

あとは、学習率を最適化することで、飽和する正解率に到達するまでの時間も短縮できることが分かりました。学習時間を短縮する他のアイディアとしては、パラメータの更新を勾配法からモーメンタム法などに変更してみるのも効果的かもしれません。またいつかトライしてみたいと思います。

しかしながら、96%を叩き出したこのニューラルネットワークも専門家からするとまだまだのようです。CNNという手法でこの正解率は99%を超えるところまで上昇できるようです。この後、CNNも頑張ってトライしてみよう。


元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

MNIST手書き数字の画像識別(隠れ層NNの評価)

前回はMNISTの画像識別を行うためのニューラルネットワークに隠れ層を追加して学習を行いました。訓練の結果は80%そこそこであんまり向上した感じがなかったですが、とりあえずテストデータで精度を評価してみます。コードの流れは前回評価したときと同じです。

CONTENTS

コーディング

前回、学習済みのパラメータを保存するところまではやってしまったので、そのロードからです、まずはMNISTのデータセット

#load dataset
import pickle

save_file = '/Users/FUTOSHI/Desktop/MNIST_test/mnist.pkl'

with open(save_file, 'rb') as f:
    dataset = pickle.load(f)

train_img,train_label,test_img,test_label = dataset

完全にコピペなので、コメントはしょります。

続いて、学習済みのパラメータをロードします。

save_file = '/Users/FUTOSHI/Desktop/MNIST_test/Hidden.pkl'

with open(save_file, 'rb') as f:
    parameters = pickle.load(f)

Wh,Bh,Wo,Bo = parameters

パラメータの数が4つになっているので、順番と添字だけ間違えないようにご注意。

あとはほとんど同じなのでテンポよくいきます、詳しくは前回の記事をご参考。


まずはライブラリのインポート、

#library import
import numpy as np
import matplotlib.pyplot as plt
import time

次にMNISTデータの前処理(正規化とOne-Hot化)です。

#pixel normalization
X_train, X_test = train_img/255, test_img/255

#transform_OneHot
T_train = np.eye(10)[list(map(int,train_label))] 
T_test = np.eye(10)[list(map(int,test_label))]

それから関数の定義、

#function
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def softmax(z):
    return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)

def loss(y, t):
    delta = 1e-7
    return -np.sum(np.multiply(t, np.log(y+delta)) + np.multiply((1 - t), np.log(1 - y+delta)))

正解と不正解の画像を振り分ける空行列を作っておきます。

correct_img = np.empty((0,784))
error_img = np.empty((0,784))


では、10000枚のテスト画像で正解率を計算していきます。

start_time = time.time()
num_of_test = X_test.shape[0] #10000

#test start
for i in range(num_of_test):

今回は隠れ層があるので、Hを計算してから出力Yを求めます。

    #forward prop
    H = sigmoid(np.dot(X_test[i],Wh)+Bh)
    Y = softmax(np.dot(H, Wo)+Bo)

では正解かどうかを判断して、それぞれの行列に振り分けます。

    #set accuracy
    Y_accuracy = np.argmax(Y)
    T_accuracy = np.argmax(T_test[i])

    if Y_accuracy == T_accuracy:
        correct_img = np.vstack((correct_img,X_test[i]))

    else:
        error_img = np.vstack((error_img,X_test[i]))

いちおう、計算時間を測定するコードも添えておきます。

end_time = time.time()


最後に評価結果を出力するprintを書いておきます。

print(correct_img.shape)
print(error_img.shape)
print(100*correct_img.shape[0]/num_of_test)
print(end_time - start_time)

今回も、
・正解画像枚数
・不正解画像枚数
・正解率
・計算時間
を出力させます。

これでコードが完成です。

プログラム実行

ではプログラムを走らせてみます。

(8708, 784) #正解枚数
(1292, 784) #不正解枚数
87.08 #正解率
196.74863386154175 #計算時間

相変わらずそこそこ計算時間がかかります。エンター押したら死んだ魚の目で正座待機です。

10000枚中、正解枚数は8708枚ということで、前回より若干良くなった気がしますが、ほとんど同じです。プログラム(アルゴリズム)自体は基礎問題で何度もトライした内容ですし、間違ってはいなさそう。前回もちらっと書きましたが、学習回数が足りてないのでは説があります。

ということで、学習(イテレーション)回数を変化させて正解率がどう変化するかこちらで検証してみました。

まとめ

今回は隠れ層を追加したシンプルなニューラルネットワークがどれほどの正解率を出すのか検証してみました。結果については考察の余地が残っているのでなんとも言えないところですが、ひとまずアルゴリズムとコードは書き上げることができました。

次回からはCNN(コンボリューション・ニューラルネットワーク)というものにチャレンジしていこうと思います。しかしながらこの手法、画像を画像のままニューラルネットワークに突っ込むという、けっこう複雑なアルゴリズムでできているので、コードを書くのもひと苦労ですし、愚直に書いているとミスを連発してしまいます。そこで、具体的にトライしていく前にいくつかのアルゴリズムを関数化して、外部ライブラリで保管して使えるようにしたいと思います。

このあたりのネタをコラム的にしばらくまとめていくことになりそうです。


アメリカの生活(仕事)がけっこう忙しくなってきて、ディープラーニングに割く時間が削られてきている現状なんですが、まぁマイペースで進めていきたい今日このごろです。

全体コード
github.com

元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

MNIST手書き数字の画像識別(隠れ層の追加)

手書き文字を識別するためのニューラルネットワークを構築するベースができてきました。前回は「入力」と「出力」だけのシンプルなネットワークでしたが、今回はこれに隠れ層を追加してみたいと思います。基礎問題では、隠れ層と言えば「非線形」がキーワードで、直線で分類できないような問題に適応しました。画像認識が直線で分類可能かどうか?、はよく分からないんですが、より複雑な構造の分類(特徴量の抽出)も可能になり、正解率アップを期待します。

CONTENTS

ニューラルネットワークの設計

前回のニューラルネットワークに隠れ層を追加します。とりあえず、隠れ層のニューロン数は100で固定してみます。100という数字に意味はありません、なにか決めないと進まないので決めました。ここで使用する活性化関数はおなじみのシグモイド関数を使いましょう。パラメータも「隠れ層」と「出力層」にそれぞれWとBがあるので注意です。

f:id:sutokun:20190417101656j:plain:w500

いつもの層がひとつ増えるだけなので、そんなに複雑ではありません。前回のコードをベースに実装してみます。

ニューラルネットワークの実装(MNISTデータのロード、関数定義)

もろもろ実装していくための準備からです。基本的にほとんどコピペなのでサラッといきましょう。

まずはライブラリもインポートします。使用するライブラリも前回と同じです。

#library import
import numpy as np
import matplotlib.pyplot as plt
import time

ニューラルネットの構築の第一歩はデータセットの作成からでした。ここは前回と同じです、コピペしてしまいましょう。

#load dataset
import pickle

save_file = '/Users/FUTOSHI/Desktop/MNIST_test/mnist.pkl'

with open(save_file, 'rb') as f:
    dataset = pickle.load(f)

train_img,train_label,test_img,test_label = dataset

ロードしたデータセットの前処理(ピクセルの規格化、One-Hot変換)を行います。

#pixel normalization
X_train, X_test = train_img/255, test_img/255

#transform_OneHot
T_train = np.eye(10)[list(map(int,train_label))] 
T_test = np.eye(10)[list(map(int,test_label))]

「ctr + C & V」が捗ります。まぁMacなんでcmdなんですけどね。

次に使用する関数を定義しておきます。今回は隠れ層の活性化関数にシグモイド関数を使うので、こちらも合わせて記述しておきます。

#function
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def softmax(z):
    return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)

def loss(y, t):
    delta = 1e-7
    return -np.sum(np.multiply(t, np.log(y+delta)) + np.multiply((1 - t), np.log(1 - y+delta)))

MNISTの問題からクロスエントロピーに(0で割り算系の)対策を施しています。なんやこれってなったら最初の問題を参照下さい。

さて、こんな感じで前準備が完了です。

ニューラルネットワークの実装(初期パラメータの設定)

次はニューラルネットワークのパラメータ:W、Bを設定します。隠れ層もあるのでWh、Bh、Wo、Boで区別します。

#initial setting
num_of_hidden=100
Wh = np.random.randn(784, num_of_hidden)
Bh = np.random.randn(1,num_of_hidden)
Wo = np.random.randn(num_of_hidden, 10)
Bo = np.random.randn(1,10)
learning_rate = 0.001

「num_of_hidden」で隠れ層のニューロン数を定義しています。行列の型にだけ注意して、それぞれランダムな値で設定します。ここもいつも通りですね。

あと、損失量と正解率(精度)を保存するリストも定義しておきます。学習にかかる時間を測定するtime関数もついでに入れています。

E_save = []
accuracy_save = []
start_time = time.time()

そんな感じで初期値の設定が完了です。

ニューラルネットワークの実装(イテレーション:順伝搬)

ではイテレーションの中身を書いていきましょう。

#iteration
num_of_itr=3000
for i in range(num_of_itr):

まずはミニバッチの設定からです。ミニバッチ学習はランダムに画像を100個抽出して、そのカタマリをニューラルネットワークに突っ込んで1回学習させる、というものでした。ちなみに100個という数字は状況に応じて変更するパラメータです、たまたま100にしているだけです。詳しくは最初の問題を参照下さい。

ではバッチを抽出するコードを書きます。

    #set batch
    train_size = X_train.shape[0]
    batch_size = 100
    batch_mask = np.random.choice(train_size,batch_size)

    X_batch = X_train[batch_mask]
    T_batch = T_train[batch_mask]

ここも前回と同じなので、コピペです。

では順伝搬計算を書いていきましょう。

    #forward prop
    H = sigmoid(np.dot(X_batch,Wh)+Bh)
    Y = softmax(np.dot(H, Wo)+Bo)
    E = loss(Y,T_batch)/len(Y)
    E_save = np.append(E_save, E)

隠れ層のHが増えていますが、ここも今まで通りの記述です。ここもコピペするんですが、出力Yの中身をX_batchからHに変えないといけません。たまにパラメータを変え忘れてよくエラー出すんですよね、ほんま凡ミス。

出力Yまで出せたので、正解率(精度)を計算するコードを書きます。

    #set accuracy
    Y_accuracy = np.argmax(Y, axis=1)
    T_accuracy = np.argmax(T_batch, axis=1)
    accuracy = 100*np.sum(Y_accuracy == T_accuracy)/batch_size

    accuracy_save = np.append(accuracy_save, accuracy)

ここもコピペですね、いや〜コピペって素晴らしい。

隠れ層Hの計算が増えただけで、大きな変更点はありません。これで順伝搬計算が完了です。

ニューラルネットワークの実装(イテレーション:逆伝搬)

でわでわ、逆伝搬を進めていきます。ここもこれまでの計算と同じです。まずは出力層から微分式を求めていきます。

ここは前回と同じ、連鎖率を2段階構造にするパターン。

{\displaystyle 
\frac{\partial E}{\partial P} = \frac{\partial Y}{\partial P} \frac{\partial E}{\partial Y} 
}

式も同じです、添字だけ調整すればオッケー。

Woの微分式:{\displaystyle 
\frac{\partial E}{\partial W_o} = \frac{\partial }{\partial W_o} (X_{batch} \cdot W_o + B_o) \cdot \Delta= X_{batch} \cdot (Y-T_{batch} )
}

Boの微分式:{\displaystyle 
\frac{\partial E}{\partial B_o} = \frac{\partial }{\partial B_o} (X_{batch} \cdot W_o + B_o) \cdot \Delta= Y-T_{batch} 
}


続いて、隠れ層の微分式ですが、こちらは連鎖率を3段階構造にします。

{\displaystyle 
\frac{\partial E}{\partial P} = \frac{\partial H}{\partial P} \frac{\partial Y}{\partial H} \frac{\partial E}{\partial Y} 
}

基礎問題でもやりましたが、ひとつ微分が増えるだけでかなり長くなりますね、、まぁ根気よく。

Whの微分式:{\displaystyle 
\frac{\partial E}{\partial W_h} = \frac{\partial }{\partial W_h} sigmoid(X_{batch} \cdot W_h+B_h) \cdot \frac{\partial }{\partial H}(H \cdot W_o + B_o) \cdot \Delta
}

{\displaystyle 
\frac{\partial E}{\partial W_h} = X_{batch} \cdot H(1-H) \cdot W_o \cdot (Y-T_{batch})
}


Bhの微分式:{\displaystyle 
\frac{\partial E}{\partial B_h} = \frac{\partial }{\partial B_h} sigmoid(X_{batch} \cdot W_h+B_h) \cdot \frac{\partial }{\partial H}(H \cdot W_o + B_o) \cdot \Delta
}

{\displaystyle 
\frac{\partial E}{\partial B_h} = H(1-H) \cdot W_o \cdot (Y-T_{batch})
}

シグモイドの微分も忘れがちなのでご注意。

ではコードに落とします。

    #back prop
    dWo = np.dot(H.T,(Y-T_batch))
    dBo = np.reshape(np.sum(Y-T_batch, axis=0),(1,10))

    dWh = np.dot(X_batch.T,H*(1-H)*np.dot(Y-T_batch,Wo.T))
    dBh = np.sum(H*(1-H)*np.dot(Y-T_batch,Wo.T), axis=0, keepdims=True)

一気にパラメータ更新まで書いてしまいます。ちなみに勾配降下法です。

    Wo = Wo - learning_rate*dWo
    Bo = Bo - learning_rate*dBo
    Wh = Wh - learning_rate*dWh
    Bh = Bh - learning_rate*dBh

最後に学習時間を測定するコードを書いておきます。

end_time = time.time()
time = end_time - start_time
print(time)

これでイテレーションの中身が完成です、わーい。

グラフ化

グラフ化するコードを書きます。

plt.figure()
plt.plot(accuracy_save, color='blue')

plt.figure()
plt.plot(E_save, color='blue')
plt.show()

ここは前回と同じ、正解率と損失量をグラフ化します。

パラメータの保存

今回は隠れ層も追加したので全部で4つのパラメータが出てきました。それぞれをリストにまとめて、またpickle形式で保存します。

#Save parameters

parameters = [Wh,Bh,Wo,Bo]

dataset_dir = '/Users/FUTOSHI/Desktop/MNIST_test'

save_file = dataset_dir + '/hidden.pkl'

with open(save_file, 'wb') as f:
    pickle.dump(parameters, f) 

parametersの中身と保存する名前「hidden.pkl」だけ変更しています、後は前回と同じです。

プログラムの実行

やっと完成しました。ではイテレーション3000回でプログラムを走らせてみます。

出力は3つ、正解率と損失量のグラフと計算時間です。

f:id:sutokun:20190417102000p:plain:w300f:id:sutokun:20190417102027p:plain:w300

3000回のイテレーション(3000エポック)で正解率は80%なかばなので、前回のニューラルネットワークとそんなに変わらない感触ですが、このグラフをよく見ると、まだ伸びそう。。

これは、3000回じゃ少ないんじゃないか説が提唱されそうです。とりあえず、この条件でテストまでしてみましょう。

ちなみに学習にかかった時間ですが、

> 5.614243984222412

ということで、前回の2倍ぐらいですね。まぁ隠れ層を追加してるんでそんなもんか、って感じです。

まとめ

今回はシンプルなニューラルネットワークから隠れ層を追加して、少し工夫してみました。結果としては80%なかばで前回とそんなに変わらなそうですが、イテレーションをもっと回すことでまだまだ向上していきそうな雰囲気があります。とりあえず、次回はこの条件で保存したパラメータを使ってテストまでしてみます。ここではアルゴリズムとコーディングがメインなので、イテレーションの回数によって正解率がどう向上するのかは別途調べてみたいと思います。


全体コード
github.com

元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

MNIST手書き数字の画像識別(学習の評価)

前回はシンプルなニューラルネットワークを構築し、MNISTの訓練データを使って学習を行いました。その正解率(精度)は80%を超える結果となり、おおすげぇ、と満足しています。しかしながら、ここまでは訓練データを使った正解率です。7万枚の画像のうち、6万枚を使って訓練を行ったので、次は残りの1万枚でどれだけの正解率を出せるのか評価してみようと思います。

CONTENTS

パラメータの保存(pickle形式)

前回の問題でイテレーションにより更新されたパラメータ:WとBがこのニューラルネットワークの「賢さ」になります。つまり、学習(イテレーション)が終了した時点でこのWとBを保存しておかなければいけません。幸いにも、MNISTの画像をダウンロードするときにpickle形式で保存する方法を勉強しました。このpickle形式で、学習が完了したWとBを保存したいと思います。

前回のプログラムのコードの最後に、パラメータを保存するコードを追加します。

#Save parameters
parameters = [W,B]

dataset_dir = '/Users/FUTOSHI/Desktop/MNIST_test'
save_file = dataset_dir + '/perceptron.pkl'

with open(save_file, 'wb') as f:
    pickle.dump(parameters, f) 

MNISTのデータダウンロードで一度やっているのでサラッといきましょう。まずは保存したいパラメータ:WとBをリストで保存します。それから、保存先ディレクトリと保存ファイル名でパスを指定して、保存を実行する、という流れです。

詳しくは以前の記事をご参考。
MNIST手書き数字の画像識別(データ・セット) - 社畜エンジニア発掘戦線

プログラムを実行し、これで指定したディレクトリに「perceptron.pkl」という、学習済みのWとBが詰まったファイルが新しく追加されました。perceptronという名前に深い意味はないです、なんかperceptronっぽいってだけでこの名前にしただけです笑

全体コード
github.com


データセット、及びパラメータのロード

データが保存できたので、評価用のプログラムファイルを新しく作成します。ここから、評価用のプログラムコードを書いていきます。まずはテスト用のMNISTデータセットと上で保存したパラメータWとBをロードするコードを書きます。

#load dataset
import pickle

#MNIST_dataset
save_file = '/Users/FUTOSHI/Desktop/MNIST_test/mnist.pkl'

with open(save_file, 'rb') as f:
    dataset = pickle.load(f)

train_img,train_label,test_img,test_label = dataset

#Parameters
save_file = '/Users/FUTOSHI/Desktop/MNIST_test/perceptron.pkl'

with open(save_file, 'rb') as f:
    parameters = pickle.load(f)

W, B = parameters

こちらもこれまでと同じ内容でデータをロードしています。MNISTのデータについては、「save_file」でロードしたいディレクトリとファイルをパス指定して、ロードを実行、「train_img,train_label,test_img,test_label = dataset」でデータをパラメータに展開しています。Parameters(WとB)のデータについても同様です。

これでMNISTのテストデータと学習済のパラメータをロードできました。

評価プログラムの実装

では具体的に評価を実行するコードを書いていきたいと思います。何はともあれ、まずはライブラリのインポートからスタート。特に新しいライブラリは使わないので、前回分をコピペです。

#library import
import numpy as np
import matplotlib.pyplot as plt
import time

次に、MNISTのデータセットを規格化したり、One-Hotにしたりの前処理をします。今回はテストデータだけでいいんですが、まぁコピペしたってことで訓練用も入ってます。

#pixel normalization
X_train, X_test = train_img/255, test_img/255

#transform_OneHot
T_train = np.eye(10)[list(map(int,train_label))] 
T_test = np.eye(10)[list(map(int,test_label))]

次にニューラルネットの計算に使う活性化関数をコードに落としておきます。今回、学習はすでに終えているので逆伝搬計算は不要です。そのため、順伝搬計算に使うソフトマックス関数だけで大丈夫です。

#function
def softmax(z):
    return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)

あと、計算した画像が「正解グループ」と「間違いグループ」にそれぞれ振り分けられるように空の配列を準備しておきます。

correct_img = np.empty((0,784))
error_img = np.empty((0,784))

計算結果が合っていれば「correct_img」に、間違っていれば「error_img」にその画像が保存されるようにします。

では、画像をニューラルネットに突っ込む処理を書いていきます。

start_time = time.time()
num_of_test = X_test.shape[0]
for i in range(num_of_test):

1万枚の画像を繰り返し処理するのでfor文を使います。繰り返し回数には直接10000って入れてもいいんですが、なんとなく汎用性を持たせたくて「X_test.shape[0] #10000」にしています。

計算する内容は順伝搬計算だけです

    #forward prop
    Y = softmax(np.dot(X_test[i], W)+B)

さて、ニューラルネットは頑張って計算してくれるので、その結果を判定する内容を書いていきます。

    #set accuracy
    Y_accuracy = np.argmax(Y)
    T_accuracy = np.argmax(T_test[i])

    if Y_accuracy == T_accuracy:
        correct_img = np.vstack((correct_img,X_test[i]))

    else:
        error_img = np.vstack((error_img,X_test[i]))

計算結果(Y)と正解ラベル(T)の最大値となる番号を比較して、一致していれば「correct_img」、不一致なら「error_img 」に追加する、という処理内容です。

あと、計算時間を測定するコードも貼り付けておきます。

end_time = time.time()

これで計算すべき内容は完了です。後は欲しい情報を出力する処理を書いておきます。このテストで欲しい情報はこんな感じ。
・正解数(correct_imgの型)
・間違い数(error_imgの型)
・正解率(正解数/10000 [%])
・計算時間

特にグラフで出力したい内容はないので全部printでターミナル上に出力させます。

print(correct_img.shape)
print(error_img.shape)
print(100*correct_img.shape[0]/num_of_test)
print(end_time - start_time)

これでコードの記述はすべて完了です。

プログラムの実行

コードも書けたことなので、このプログラムを実行してみます。

> (8664, 784)
> (1336, 784)
> 86.64
> 211.79083514213562

おお、うまくいきました、まぁそんなに複雑な計算でもないし。10000枚中、正解は8664枚ということで、正解率は86%となりました。だいたい訓練データと一致していますね。せっかくピックアップしてるので、間違い画像がどんな数字なのか確認してみたいんですが、まだ1336枚もあるのでもうちょっと正解率が上がってきたら具体的にチェックしてみたいと思います。

ただ、計算時間が200秒を超えてきました、だいたい3分半。けっこう時間かかったな、学習は2秒ぐらいで終わるのに、そのテストは100倍かかるのか、ふーん、そんなもんなのか。

まとめ

今回は学習が完了した時点のパラメータを保存して、そのパラメータを使って実際にこのニューラルネットワークがどの程度の実力を持っているのかを評価しました。その結果は概ね訓練データと一致しており、MNISTを識別するニューラルネットワークの評価ベースができあがった、というところでしょうか。ここから、ニューラルネットワークをいろいろと工夫して、どのように正解率が向上していくのか見ていきたいと思います。

次回はこのニューラルネットワークに隠れ層を追加してみます。

全体コード
github.com

元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

MNIST手書き数字の画像識別(シンプルニューラルネット)

データセットの準備が整ったところで、実際にMNISTの画像をニューラルネットワークを使って分類してみたいと思います。今回はあまり複雑なことをせずに、「画像の入力」と「数字の出力」の2層でネットワークを作成してみます。考え方、コード作成の流れはこれまでの分類問題とほとんど同じです。

CONTENTS

データセットの作成

基礎問題でもずっとそうでしたが、ニューラルネットワークによる分類問題の第一歩はデータセットの作成から始まります。前回の問題でMNISTの画像データと正解ラベルをオリジナルのホームページからダウンロードし、使いやすいpickle形で保存しました。まずはこのデータセットを確認してみます。


・ライブラリのインポート

import numpy as np
import matplotlib.pyplot as plt
import time

何ごともまずはライブラリのインポートからですね。今回は、計算にかかる時間も測定したいのでtimeというライブラリもインポートしておきます。


・データセットのロード
前回と同じコードを書いてデータセットをロードします。

#load dataset
import pickle

save_file = '/Users/FUTOSHI/Desktop/MNIST_test/mnist.pkl'

with open(save_file, 'rb') as f:
    dataset = pickle.load(f)

train_img,train_label,test_img,test_label = dataset

これで「train_img、train_label、test_img、test_label」にMNISTのデータが入りました。この時点でこれらのデータセットの型を確認しておきます。

print(train_img.shape) #(60000, 784)
print(train_label.shape) #(60000,)
print(test_img.shape) #(10000, 784)
print(test_label.shape) #(10000,)

「train_img」は28x28 = 784ピクセルの画像データが60000枚、「train_label」は画像に対応した60000個の正解ラベル。testデータは同じ形で10000枚、おっけーですね。

f:id:sutokun:20190331141102j:plain:w500


ピクセル値の規格化
次に、画像のピクセル値の加工です。一般的に画像データは「輝度値」といって、ピクセルごとの色(RGB)の濃さを0〜255段階で表現しています。今回も扱うデータは画像なので、ピクセルの値は0〜255となっています。いちおう最大値を確認してみます。

print(np.amax(train_img)) #255

確かに0〜255の値で構成されているようです。まあこのまま分類を進めてしまっても良いんですが、扱いやすいように1で正規化しておきます。

#pixel normalization
X_train, X_test = train_img/255, test_img/255

これでもう一度、最大値を確認してみます。変数をtrain_imgからX_trainに変更しているのでご注意。

print(np.amax(X_train)) #1.0

おっけーですね、色の濃さがmax:1のパーセンテージに変換されたので分かりやすくなりました。


・正解ラベルのOne-Hot表現
次に、正解ラベルの加工です。今回は0〜9の数字を識別するので、10クラス分類になります。多クラス分類といえば「One-Hot表現」が必要でした。しかし、正解ラベルは0〜9の値で構成されているので、これらを0と1の「One-Hot表現」に変換しなければいけません。

一度に変換するとややこしいので、ひとつずつトライ。

まず、train_labelのデータの型を確認してみます。0番目の値を抜き出してtype関数でprint。

print(type(train_label[0])) #<class 'numpy.uint8'>

'numpy.uint8'ってなんだ、よく分からんけどこのままだとエラーが出るので、int(整数)型に変換します。配列の全要素を変換するのでmap関数を使います。そしてlist関数で変換された値を配列として構築し直します。

list(map(int,train_label))

また0番目を抜き出して、printで型を確認してみます。

print(type(list(map(int,train_label))[0])) #<class 'int'>

無事にintへ変換されました、めでたし。できたとは言えブッサイクなコードやな…、たぶん他にいい書き方があると思います。

次に、10x10の単位行列を作成します。0〜9までの10クラスなので「10」という値になっています。単位行列は正方行列(縦横同じ要素数)の斜め要素だけが1でそれ以外は0という特殊な行列です。

T_train = np.eye(10)

そして、上で作成したint型の正解ラベルの行を抜き出すことで、その値に対応したOne-Hot表現を得ることができます。test_labelも合わせて変換しておきます。

#one-hot transform
T_train = np.eye(10)[list(map(int,train_label))] 
T_test = np.eye(10)[list(map(int,test_label))] 

f:id:sutokun:20190331142500j:plain:w500

これでようやく準備が整いました。正直なところ、この作業も「前処理」なので、前回の内容でやってしまっても良かった気がします。

ではうまく加工できているか確認してみます。前回の最後に確認した方法と同じです。train_labelとT_trainを2つ並べて、きちんと数字がOne-Hotに対応しているか確認します。nは自分で好きな好きな値を選択してください。あと、見やすいようにグレーにしました。

n = 1234
img = np.reshape(X_train[n],(28,28))

plt.figure()
plt.imshow(img, cmap='gray_r')
plt.show()

print(train_label[n])
print(T_train[n])

f:id:sutokun:20190331143327p:plain:w300

おお、これは3っぽい。そして正解ラベルは…

> 3
> [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]

素晴らしい、成功です、念のためnの値を変えて他の順番でもできているか確認してみてください。

ニューラルネットワークの設計

今回は一番シンプルなネットワーク、「入力」と「出力」の2層で構築したいと思います。「入力」はX_train、784の値を1次元でつっこみます。「出力」は0〜9の10個です。

f:id:sutokun:20190331144821j:plain:w500

画像を扱いますが、分類問題であることに変わりはないので活性化関数にはソフトマックス関数を使います。

とてもシンプルですね。しかし、X_trainだと画像は全部で60000枚あります。まぁ1個ずつ全部つっこんでも良いんですが、ここでは「ミニバッチ学習」を導入します。ミニバッチ学習とは、60000枚の画像の中からランダムに100枚の画像を選び、まずはこの100枚に対して損失量を計算します。そして、100通りの損失量を平均し、1度学習を更新します。その後、再び100枚の画像を選び、学習を行う、という流れです。ミニバッチは必ずしも100枚じゃなくていいんですが、100枚ごとに計算するのがよく見るかな〜ってことで100という値をチョイスしています。

ニューラルネットワークの実装(計算準備)

では具体的にコードを書いていきたいと思います。ミニバッチのところ以外、今まで通りです。

まずは関数を定義しておきます。ソフトマックスとクロスエントロピーです。

#function
def softmax(z):
    return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)

def loss(y, t):
    delta = 1e-7
    return -np.sum(np.multiply(t, np.log(y+delta)) + np.multiply((1 - t), np.log(1 - y+delta)))

少し問題が発生したので、クロスエントロピーの関数を修正しました。データ数が多くなったせい?かクロスエントロピーで「0で割り算しちゃう系」のエラーが発生します。logの中なんで0になると無限小に発散しちゃうんですかね。そこで、0になりきらないように「delta = 1e-7」という小さい数字を足して対策しておきます。

なんか最近新しいことたくさんやってるんで、アレンジはしていますが、こういう見慣れたコード見ると安心します。


次に初期値の設定です。

#initial setting
W = np.random.randn(784, 10)
B = np.random.randn(1,10)
learning_rate = 0.001

E_save = []
accuracy_save = []

重みとバイアスは型にだけ注意、他は同じです。学習率は、とりあえず経験的に0.001を入れておきます。基礎問題もだいたいこれぐらいでうまくいってたし…。あと、パラメータの更新するときに正解率(精度)も計算します。その精度を保存するためにからの配列「accuracy_save」もここで定義しておきます。

ニューラルネットワークの実装(順伝搬計算)

ではイテレーションの中身を書いていきます。計算を始める前に、計算時間を計算するためのコードも足しておきます。「start_time」は計算が始まる時間を保存しておく変数です。

イテレーションはとりあえず3000回でトライしてみます。ちなみに、今回はミニバッチで学習するのでイテレーション1回の学習で100枚の計算が実行されます。このミニバッチ:100というカタマリを「EPOCH」という単位で扱うそうです。またグラフ化のときに使います。

#iteration
start_time = time.time()
num_of_itr=3000
for i in range(num_of_itr):

まずはミニバッチの設定から、

    #set batch
    train_size = X_train.shape[0] #60000
    batch_size = 100
    batch_mask = np.random.choice(train_size,batch_size)

    X_batch = X_train[batch_mask]
    T_batch = T_train[batch_mask]

60000枚(X_train.shape[0])の中から100枚(batch_size)をランダムに選択(np.random.choice)して「batch_mask」にまとめます。このbatch_maskを画像と正解ラベルに適応させて、100枚のミニバッチが完成です。ちなみにここからはイテレーション(for)の中なのでインテンドされているので、ご注意。

いちおうミニバッチの型も見ておきましょう。イテレーション値を3000のまま回すとめっちゃ出力されるんで、確認するときは1とかに変更して下さい。

   print(X_batch.shape) #(100, 784)
   print(T_batch.shape) #(100, 10)

ちゃんとミニバッチ100枚分が反映されていますね。100枚からまた1枚ずつ取り出して計算するんじゃなくて、ひとつの行列として一気に計算します。

続いて順伝搬計算、損失量Eまで計算します。

    #forward prop
    Y = softmax(np.dot(X_batch, W)+B)
    E = loss(Y,T_batch)/len(Y)
    E_save = np.append(E_save, E)

100枚あるので、Eも100個出てきます。なので、要素数:len(Y)で割って、平均化しています。E_saveにはバッチごとに平均された損失量が保存されていきます。

ちょっとイレギュラーですが、基本的には今までと同じ計算処理です。

ニューラルネットワークの実装(精度の計算)

これで順伝搬計算が終わりました、この時点で一度このニューラルネットワークの正解率(精度)を計算しておきます。

    #set accuracy
    Y_accuracy = np.argmax(Y, axis=1)
    T_accuracy = np.argmax(T_batch, axis=1)
    accuracy = 100*np.sum(Y_accuracy == T_accuracy)/batch_size

    accuracy_save = np.append(accuracy_save, accuracy)

「Y_accuracy」は10個の出力のうち一番確率の高いもの、「T_accuracy」は正解ラベルの最大値、つまり「1」になっているもの、batch_size:100枚のうち「Y_accuracy」と「T_accuracy」が一致しているものがいくつありますか、という計算です。

f:id:sutokun:20190401004628j:plain:w500

ちなみに、バッチ計算でYは100行になっているのでargmaxに「axis=1」が必要です。

ニューラルネットワークの実装(逆伝搬計算)

さてさて、順伝搬計算が終わったところで逆伝搬の計算に移ります。逆伝搬を計算するにはまず微分式を求めて、パラメータを更新する、という流れでした。入力が画像になりましたが、基本的に計算は同じです。

まずは微分式を求めてみます。今回、更新すべきパラメータは重みWとバイアスBです。2層しかないので連鎖率は2段階構造になっています。

{\displaystyle 
\frac{\partial E}{\partial P} = \frac{\partial Y}{\partial P} \frac{\partial E}{\partial Y} 
}

今回もソフトマックスとクロスエントロピーの組み合わせなのでΔを使って計算します。

Wの微分式:{\displaystyle 
\frac{\partial E}{\partial W} = \frac{\partial }{\partial W} (X_{batch} \cdot W + B) \cdot \Delta= X_{batch} \cdot (Y-T_{batch} )
}

Bの微分式:{\displaystyle 
\frac{\partial E}{\partial B} = \frac{\partial }{\partial B} (X_{batch} \cdot W + B) \cdot \Delta= Y-T_{batch} 
}


基礎問題で散々やったのでここはサラッと。


ではパラメータを更新します。更新方法は勾配降下法を用います、一番シンプルなんで。画像識別もいろいろできるようになってきたらmomentumとか試してみます。

Wのパラメータ更新:{\displaystyle 
W_{new} = W_{old} - \mu \frac{\partial E}{\partial W} 
}

Bのパラメータ更新:{\displaystyle 
W_{new} = W_{old} - \mu \frac{\partial E}{\partial W} 
}

ではコードに落としていきます。

    #back prop
    dW = np.dot(X_batch.T,(Y-T_batch))
    dB = np.reshape(np.sum(Y-T_batch, axis=0),(1,10))

    W = W - learning_rate*dW
    B = B - learning_rate*dB

なんか普通に計算するとdBの結果が転置?しちゃってBと型が合わなかったのでreshapeしています。特に新しいこともなく、今まで通りの流れ。


これでニューラルネットワークの計算は完了です。最後にこの計算にかかる時間を計測したいので下記のコードを足しておきます。

end_time = time.time()
time = end_time - start_time
print(time)

イテレーションの計算前にstart_timeを定義しておきました。イテレーションが終わった時間をend_timeで指定して、「end - start」でかかった時間を計測します。ちなみに、ここはもうfor文を抜けているのでインテンドはありません。

グラフを描画

ひと通りの計算が終わったので、グラフ化するコードを書いていきます。グラフ化したいのはイテレーションごとに変化する精度「accuracy」、あと損失量「E」です。

plt.figure()
plt.plot(accuracy_save, color='blue')

plt.figure()
plt.plot(E_save, color='blue')
plt.show()
プログラムを実行

これでコードは全て書き終えました。ドキドキしながらターミナルで実行してみます。出力される結果は「精度(グラフ)」「損失量(グラフ)」「計算時間(値)」です。

f:id:sutokun:20190401024045p:plain:w300f:id:sutokun:20190401024059p:plain:w300

おお、うまく計算できました。accuracyのグラフを見ると、3000エポック、つまり3000回のイテレーションを回したところで精度は80%を超えています。まじか、こんなシンプルなニューラルネットワークで80%超えるんだ。よく人間も汚い字で書いて読み間違えることもあるし、8割正解するなら上等じゃないの、と思うんですが、ちゃんとした専門家からすると99%とか超えないとダメなようです。

ただ、精度に幅があります。3000EPOCHあたりに、下は78%?、上は92%?ぐらいで学習ごとに差が見られます。間をとって、だいたい86%の正解率ですかね。次回、テストよう画像を使ってこの正解率を測定してみたいと思います。

損失量についても、こちらも幅がありますがよく収束してくれています。精度のグラフはもっとイテレーションを回すとまだ伸びそうな雰囲気がありますが、損失量は収束しきった感がありますね。

あと、この計算にかかった時間ですが、

> 2.679206132888794 (sec)

まあ悪くないんじゃないの、という感覚です。プログラムを走らせて一瞬フリーズした?と思ったら結果が出てくる、みたいな感じ。ただ、基本問題をトライしていたときはほぼノータイムで結果が出てきていたので、それと比べると確かにデータ量が増えただけ計算時間が増えたのかな、と思います。

まだまだこのPCのスペックでもやれそうです。

まとめ

今回はシンプルなニューラルネットワークを使って画像認識の問題にトライしてみました。入力と出力の2層だけですが、精度は80%を超えるぐらいの結果が得られました。個人的には、こんなシンプルな設計だと50%ぐらいなんちゃうかな、って思っていたんでけっこう驚きました。

次回は1万枚のテスト用画像を使って、このニューラルネットワークがどれぐらいの正解率を出すのか測定してみたいと思います。まぁ、だいたい86%ぐらいなんだろうけど。

全体コード
github.com

元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

MNIST手書き数字の画像識別(データ・セット)

おつかれさまです。

今回より、ディープラーニングの発展問題として手書き数字の認識問題にトライしたいと思います。問題と言っても、"how to"が多くて問題にしてると効率が悪いので、ここからは高校数学スタイルはやめて、流れにそってコードを書いていきたいと思います。

CONTENTS

MNISTの手書き数字

このMNISTという言葉ですが、"Mixed National Institute of Standards and Technology database"の略字で、0〜9までの手書き数字画像が7万枚パックになったデータセットです。画像は28x28のピクセルデータです。しかし、7万枚って気が遠くなりますね。

f:id:sutokun:20190321100859p:plain:w500
この7万枚の手書き画像をニューラルネットワークにつっこんで学習させて、例えば自分で書いた数字が0〜9のどれにあたるのかを判別させたりします。まぁ、ここの問題では数字を手書きできるようなソフトウェアを作るのはそれはそれで大変なので、学習が進むにつれて識別正解率がどう上昇していくかを確認できるようなプログラムを作ってみたいと思います。

データのダウンロード

ディープラーニングフレームワーク」という魔法の杖を使うと、コード一発でダウンロードもできるんですが、今回はもとのデータセットをPCにダウンロードして、がちゃがちゃ加工して、どんなふうになっているのか確認しながら扱っていきたいと思います。

データセット自体はYann LeCunという先生のホームページからダウンロードできます。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges


ホームページにとぶと英語なのでうへぇとなりますが、欲しいのは真ん中のデータです。

f:id:sutokun:20190321102518p:plain:w500

とりあえずリンクポチーして4つのデータをダウンロードします。これら4つのデータがMNISTのデータセットになります。

まず、データセットは7万枚の画像ですがここではトレーニング用とテスト用、それぞれ6万枚と1万枚に分かれています。なぜ、わざわざトレーニング用とテスト用の2つに分けるのかと言うと、学習が完了したニューラルネットワークがどれぐらい賢いかテストするときに、もし1度でも学習に用いた画像を使うと、このニューラルネットワークは「あ、これ進研ゼミでやったやつだ!」とウキウキして、正解率が上がってしまいます。そのためテストに用いる画像をあらかじめ分けておく必要があります。

また、画像データには正解ラベルがセットになっています。例えば「5」という画像には「5」という正解ラベルが対応します、あたりまえですね。この正解ラベルもトレーニング用とテスト用に分かれているので、全部で4つのデータとなります。

コードでダウンロード

ふつうにリンクポチーでダウンロードできるんですが、勉強も兼ねてPythonのコードでダウンロードしてみます。とりあえず「MNIST_DL.py」とか名前つけて、コードを書いていきます。


まずはホームページのURLにアクセスするライブラリ&モジュール(urllib.request)をインポートします。

import urllib.request

次にアクセスしたいURLをurl_baseという変数で定義しておきます。上のYann LeCun先生のURLです。

url_base = 'http://yann.lecun.com/exdb/mnist/'

次に、ダウンロードしたいデータの名前をリストでまとめておきます。後でまとめてダウンロードするときに使います。

dl_list = [
    'train-images-idx3-ubyte.gz',
    'train-labels-idx1-ubyte.gz',
    't10k-images-idx3-ubyte.gz',
    't10k-labels-idx1-ubyte.gz'
]


それから、データセットを保存するディレクトリ(フォルダ)のパスをdataset_dirで定義しておきます。デスクトップに「MNIST_test」とかフォルダを作って、そこへ保存することにします。まぁ、自分で好きなディレクトリを指定してください。

dataset_dir = '/Users/FUTOSHI/desktop/MNIST_test/'

ちなみに、ディレクトリ上で「command+opt+C」を押すと、そのディレクトリのパスがコピーできます。おお、便利!(まぁ、Windowsだったら常に上に出てますけどね)


これで準備は整ったので、ダウンロードを行うコードを書きます。ダウンロードにはurllibライブラリの「urllib.request.urlretrieve(目的のURL, 保存先のパス)」というメソッドを使います。目的のURLはurl_baseで定義したホームページにあるデータ:train-images-idx3-ubyte.gz(ひとつ目)です。なので、こんな感じ。

url_download = url_base + 'train-images-idx3-ubyte.gz'


次に保存先のパスですが、上で保存したいディレクトリのパスをdataset_dirで定義しておきました。これにファイル名をくっつければ完了です。これ、真ん中に区切りの'/'が必要なので注意。

file_path = dataset_dir + '/' + 'train-images-idx3-ubyte.gz'


最後にダウンロードを実行するコードを書いて完了です。

urllib.request.urlretrieve(url_download, file_path)

いったんプログラムを保存して、ターミナルで実行してみます。保存先に指定したフォルダに'train-images-idx3-ubyte.gz'がダウンロードされています、成功です、わーい。

f:id:sutokun:20190329101814p:plain

無事にデータをダウンロードできたところで、この処理をあと3回繰り返します。

…いやいや、それはめんどくさい。(てか、そもそも、ポチーでダウンロードできるのにわざわざコードを書いてる時点でもっとめんどくさいことをしている、、とか言わないで)

「繰り返し」といえばfor文です。この処理をfor文で書いてみたいと思います。「dl_list」が最初に名前をまとめておいたリストです。

for i in dl_list:
    file_path = dataset_dir + '/' + i 
    urllib.request.urlretrieve(url_base + i, file_path) 


こっちのコードに書き換えて、もう一度プログラムを実行して保存先のフォルダを確認してみます。

f:id:sutokun:20190329103223p:plain
おお、ちゃんと保存されてる、わーい。

ひとまず、これでデータのダウンロードは完了です。ポチーで終わるのに長くなりました、まぁ勉強にはなったかな。次はデータの展開に進みます。

MNIST_DL.py : 全体コード
github.com


データの展開

さて、MNISTのデータはダウンロードできたものの、使えるように展開して加工しなくてはいけません。Pythonのファイルを一度新しくして、適当に「MNIST_SAVE.py」とか名前をつけて、デートを展開するためのコードを書いていきます。

まず、ダウンロードしたデータを見てみると拡張子が.gzとあります。これはgzipという圧縮ファイルです、プログラミング独学マンのわたしは初めて見たのでうろたえました、、うろたえました!この圧縮ファイルを解凍するためには、gzipというライブラリをインポートしなければいけません。numpyも使うのでこちらもインポート。

import gzip
import numpy as np

Pythonのファイルが新しくなったので、保存したデータの名前をもう一度リスト化しておきます。

dl_list = [
    'train-images-idx3-ubyte.gz',
    'train-labels-idx1-ubyte.gz',
    't10k-images-idx3-ubyte.gz',
    't10k-labels-idx1-ubyte.gz'
]

あと、データを保存しているディレクトリももう一度書いておきます。

dataset_dir = '/Users/FUTOSHI/Desktop/MNIST_test'


では進めていきましょう。まずはひとつ目のデータを展開してみます。ここも、ファイルのパスを定義しておきます。

file_path = dataset_dir + '/' + dl_list[0]

このfile_pathを使ってデータを展開します。

data = gzip.open(file_path, 'rb')

open関数は「open(ファイルパス、処理モード)」という使い方です。読み込みモードはとりあえず「rb」にしとけって、田舎のばあちゃんが言ってました。とりあえず「バイナリー読み込みモード」というものです。ここはまた時間があるときに詳しく見てみることにします。とりあえずこのモードは下のテーブルみたいな分けられ方(他にもあるけど)をしています。

変数 処理モード
w テキスト書き込み
wb バイナリ書き込み
r テキスト読み込み
rb バイナリ読み込み


データは展開できたんですが、さらにread関数を使って読み込み、データとして返してもらわなければいけません。

data = data.read()

一度、printを使って中身を見てみましょう。

print(data)

\x00\x00\x00\x00\x00\x00{\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x10\xe6\xfd\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xfd\xfd
\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\xf1\xfd\xfd\x8f\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0…

嗚呼ぁぁあああああ(ctr+c:強制終了)

なんか大量のバイナリがなだれ込んできました、そりゃ(バイナリで読み込んだんだから)そうか。ということは、分かる値に変換しなければいけません。今回はnumpyのfrombufferを使います。さらに画像のデータなので「np.uint8」も添えなければいけないらしい。

このfrombufferの詳細はリンク貼っときます。。。
【NumPy】np.frombuffer() – 既存のデータを高速に読み込みndarrayを生成する – 青の弾丸


とりあえず、コードを書いてみましょう。

data = np.frombuffer(data, np.uint8)
print(data) #[0 0 8 ... 0 0 0]

おお、なんかそれっぽい配列が見えた。この配列が画像ピクセルの値っぽい。MNISTの画像は28x28のピクセルで構成されているので、この要素数は「28x28x60000 = 47040000」のはず。lenで要素数を確認してみる。

print(len(data)) #47040016

ファッ!?なんか16個多ない??どうも、変換すると16個はみ出るらしいので、これをカットしなければいけません。カット自体は簡単で、frombufferに「offset=16」を添えると尻の16個を取り除くことができる。

data = np.frombuffer(data,np.uint8,offset=16)
print(len(data)) #47040000


ここまでdataという変数を上書き&上書きでブサイクな加工してきましたが、withブロックを使うとまとめて書くことができます。

with gzip.open(file_path, 'rb') as f:
    data = np.frombuffer(f.read(), np.uint8)


最後に、今のままでは1次元の数字配列が47040000個並んでいるだけなので、60000枚の画像に分けておきます。ひとつあたりの画像は「28x28 = 784」ピクセル、この配列にリシェイプしておきます。ここでデータの名前を「train_img」にしておきます。

train_img = data.reshape(-1, 784)

「reshape(-1, 784)」という型ですが、マイナスってなんぞや、なんか784ずつブツブツブツと切って配列にしていくらしい。今回はこれでうまくいくっぽいので、また時間があるときによく調べてみます。

print(train_img.shape) #(60000, 784)

おっしゃー、ひとつ目が完成。では残り3つ、ここは繰り返しのforを…と思ったんですが、画像と正解ラベルでデータ数も違うし、frombufferのはみ出し個数も違います(正解ラベルは8個だった)。頑張ってまとめてもあんまり恩恵ないかな〜と思い、そこまで行数もないので3つとも力技で書いちゃいます。


…あとは書き下すだけなので全体コードを参照ください(すいません、疲れた…)。


4つのデータは「train_img、train_label、test_img、test_label」という変数名で保存しました。最後に、この変数をひとつのリストにまとめておきます。この後、データを保存するときに使います。

dataset = [train_img,train_label,test_img,test_label]

ひとまずこれでデータの展開と加工が完了です。

pickle形式で保存

とりあえずそれらしいデータの形に加工することができました。MNISTの問題を扱うとき、毎回このコードを書かなければいけないのはなんともブサイクです。というわけで、加工したデータを保存して、簡単に取り出せるようにしておきましょう。


今回、加工したデータを保存する形式はpickle形式です。緑色の戦闘力高いヤツじゃなくて、pickleです。

pickleとはハンバーガーとかに入ってるアレ、ピクルスの単数形です。

f:id:sutokun:20190328113845j:plain:w200
ピクルスは塩漬けにして野菜をそのまま保存した食べ物ですが、この保存方法がこの名前を由来となっていて、データをそのままの形で保存する、ということのようです(よく分からんけど)。データの保存には、他にCSVやバイナリといった方法がありますが、このpickleはデータの読み込みや出力が手軽なのでpython界隈ではよく使われているようです、知らんかった。

とりあえず使ってみます、こちらもpickle形式を使うためのライブラリをインポート。

import pickle


次に、保存するフォルダのパスを指定しておきます。ここらへんの流れは上で書いた内容と同じですね。今回は「mnist.pkl」という名前で保存します。

save_file = dataset_dir + '/mnist.pkl'


データの書き込みにもopen関数を使います。今回は処理モードは書き込みなので「wb」となります。バイナリで保存します。

with open(save_file, 'wb') as f:
    pickle.dump(dataset, f) 

pickle.dumpで保存を実行します。


ここでプログラムを実行してみて、ちゃんと保存できているか、ディレクトリを確認してみます。

f:id:sutokun:20190330132749p:plain
おお、できてる(感動)、「.pkl」で保存することができました。

MNIST_SAVE.py : 全体コード
github.com


pickleデータのロード

無事にMNISTのデータをpickle形式で保存できました。では、このデータを再度ロードしてきちんとデータとして扱えるのか確認してみます。「MNIST_OPEN.py」とでも名前をつけて、また新しいPythonファイルにコードを書いていきます。

pickle形式のデータロードにはもちろんpickleのライブラリが必要なので、先にこちらをインポート。

import pickle


そして、pickleで保存したデータのファイルパスを指定しておきます。

save_file = '/Users/FUTOSHI/Desktop/MNIST_test/mnist.pkl'

データのロードにはもちろんopen関数を使います、これで3度目の登場。

with open(save_file, 'rb') as f:
    dataset = pickle.load(f)

datasetという変数に、pickle.load(f)でデータをロードします。上の方で、このpickleデータは配列で保存しました。pickleというと塩漬けの名の通り、このデータがそのままの形で保存されているはずです。

dataset = [train_img,train_label,test_img,test_label]


ということで、ロードしたデータを再度これらの変数に割り当てなければいけません。

train_img,train_label,test_img,test_label = dataset


これでデータのロードが完了です。きちんと画像、そして正解ラベルが格納できているか確認してみます。まずは画像をグラフで表示するので、そのライブラリのインポートから。いつものmatplotlibです。

import matplotlib.pyplot as plt


次に、何番目の画像をチョイスするか「n」で指定しておきます。例えばtrain_imgだったら、6万画像ファイルが1次元の数字配列784でまとめられています。なので、6万枚の中からn番目のデータをチョイスして、imgという変数に28x28でreshapeします。とりあえず10000番目の画像を展開してみましょう。

n = 10000
img = train_img[n].reshape((28, 28))

さて、準備は整いました。画像を表示してみます。printでその正解ラベルもターミナルに表示させてみます。

plt.imshow(img)
plt.show()

print(train_label[n])


ファイルを保存してプログラムを実行します。おお、「ゼロ」っぽい画像が出てきました。

f:id:sutokun:20190330224740p:plain:w300

そしてターミナルには

> 0


素晴らしい、成功です。たまたま画像とラベルがそろっただけかもしれないので、nの値を変えて何度か試してみましたが、ちゃんとマッチしている、大丈夫そうです。

MNIST_OPEN.py : 全体コード
github.com


まとめ

今回のデータ処理は機械学習の「前処理」と呼ばれる工程のようです。前処理とは苦痛を伴うのが一般的らしいですが、苦痛でした。いつも機械学習アルゴリズムばっかり考えていたので、この手のデータ処理はとても苦手です。けっこうゆるふわなところが多いので、また理解が深まればそれぞれ記事にしたいと思います。機械学習をしっかりするならばデータ処理とは嫌でも向き合わなければいけないので、いずれガッツリやるときが来るでしょう…。

次回から、このデータセットを使って画像識別の問題にトライしていきたいと思います。


元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter

開発環境のアップデート

おつかれさまです。


ディープラーニングの基礎問題もひと通り終え、いよいよ発展的な問題に取り組んでいこう、というところです。まだまだnumpyだけでチャレンジしていこうとは思っているんですが、いずれはフレームワークを使う日が来るかと思います。昨今、いろいろなフレームワークが開発されていますが、とりあえず圧倒的知名度のもと、Google先生のtensorflowをインストールしてみようと思います。あわせて、開発環境とか諸々アップデート、その辺の奮闘をまとめました。


f:id:sutokun:20190318140725p:plain:w500

とりあえずダウンロードページでtensorflowインストールポチーするもののいくつかトラブルが発生、うまくインストールできない。


いろいろ原因を調べていると、pythonのインストールした環境が問題っぽい。なんかよく分からんのですが、このMacにはHomebrew~pyenvなるソフトウェアツールを使ってPython3系をインストールしたんですが、その後にAnacondaというライブラリをまとめたパッケージをインストールしてnumpyとかmatplotlibとか使えるように設定していました。最初のpythonをインストールするとき、pipなるコマンドで進めたんですが、このpipとAnacondaの相性が良くないのだとか?
onoz000.hatenablog.com

こういう経緯もあり、一度ひと通りのソフトウェアをアンインストール。
ai-coordinator.jp

Pythonからもろもろのライブラリも再インストールをするわけですが、ほんとよく分からんので、次はpythonもこのAnacondaを経由してインストールしてみました。
qiita.com


Anacondaについていろいろ調べてみるとけっこう便利そう、まずAnacondaナビゲータというGUI(画面操作で設定できるやつ)がある。
slideship.com

今回はこのGUIを使ってtensorflowもインストールしました。
qiita.com

CUI(コマンド使ってがちゃがちゃやるやつ)だと設定状況がよく分からんので、GUIでできるのはたしかに便利。


で、Anacondaを再インストールするときに、「合わせてVScode(Visual Studio code)というエディタもいかがでしょうか?(スマイル」と推奨され、そういえばずっとSublimetextを使っていたので、そろそろ新しいのにもトライしてみたいな、という気持ちが沸き立ち、このVScodeなるものをインストールしてみました。Sublimetextは慣れていたので、使い心地に不満はなかったんですが、「プロ版買え!」みたいなポップアップがポンポン出てくるんで、ちょっとウザいと思ってしまいVScodeに手を出してしまいました。お世話になったのにウザいとか思ってごめんやで。

このエディタはマイクロソフトが提供しているソフトウェアということで、日本語などの対応がしっかりしています。Sublimetextは英語しかなかったかな、そこまで大きな問題はなかったけど。そしてVScodeなんかAnacondaとの相性も良いらしい、詳しく知らんけど。


f:id:sutokun:20190318143133p:plain:w500


とりあえず日本語化、
qiita.com


それからSublimetextで行っていた設定にそろえたりします。

  • 自動保存設定

notti-blog.com

  • 空白表示

qiita.com

検索窓で「renderWhitespace」って打ったら出てきました。
f:id:sutokun:20190319132727p:plain


いろいろと手間はかかりましたが、次のディープラーニングの発展問題に取り組むための下準備が整いました。まだ使い始めたばかりなのでこれから設定とかいろいろアレンジしていくんですが、なんかあればまた記事にしてメモしたいと思います。

元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter