このリンク先で示している判定器Neural2Netを継承したNeural2NetLNの学習クラスを定義してこれを検証しています。
ここで紹介したほとんどのコードは通常のPythonでも動作しますが、
最終的な検証は、このリンク先で紹介したESP32-S3の環境内のMicroPythonで、MNISTを使った手書きイメージの学習を行います。
この環境の「ESP32-S3-DEV-KIT-N16R8-M」で実験した内容を紹介しています。
import urllib.request
def download(file):
url_base = 'https://storage.googleapis.com/cvdf-datasets/mnist/' # 手書き数字のMNIST取得元
print(f"{url_base+file}をダウンロードして、{'./'+file}に記憶")
urllib.request.urlretrieve(url_base+file , './'+file)
download('train-images-idx3-ubyte.gz') # 学習用の手書きイメージ のダウンロード
download('train-labels-idx1-ubyte.gz') # 学習用の正解ラベルイメージ のダウンロード
import gzip
import sys
sys.path.append( '..')# 親ディレクトリ相対パスをモジュール検索パスに追加
from numarray import NumArray
def mk_x_train_bin( nn ): # gz_fileから手書き画像nn件の前処理した "x_train.bin"を生成
fimg = gzip.open('train-images-idx3-ubyte.gz','r')#画像データ
fimg.read(16) #メタ
buf = fimg.read(28 * 28 * nn); # (28X28)=784画素
fimg.close()
x_train = NumArray.createByArray(buf, 28 * 28)
x_train.mul_scalar(1.0 / 255); # 画像データ0〜255を、0〜1.0に正規化
print(x_train)
with open("x_train.bin", mode='wb') as fw: x_train.write(fw) # シリアライズしてファイル化
def mk_t_train_a_bin(nn): # 手書き画像6万件の答えをOne-Hot ベクトル表現へ前処理
fimg = gzip.open('train-labels-idx1-ubyte.gz') # 学習用の正解ラベル
fimg.read(8) #メタ
buf = fimg.read(nn); # 画像の正解データを読み込む
fimg.close()
t_train = NumArray(10, nn) # One-Hot ベクトル表現
for i in range( nn ):
t_train[buf[i], i, 0] = 1.0;
with open("t_train_a.bin", mode='wb') as fw: t_train.write(fw) # シリアライズしてファイル化
NN=2001500 # 「画像と正解ラベル」をそれぞれこの数だけ抽出して用意
mk_x_train_bin( NN ) # 手書き画像を抽出し、前処理してファイル保存
mk_t_train_a_bin( NN ) # 上記画像の正解情報を、One-Hot ベクトル表現の前処理をしてファイル保存
最終的な検証は、このリンク先で紹介した「ESP32-S3-DEV-KIT-N16R8-M」で行う予定で、その環境ではTotal Storage: 6291456 bytesしかありません。
import sys
sys.path.append('..')# 親ディレクトリ相対パスをモジュール検索パスに追加
from numarray import NumArray
import matplotlib.pyplot as plt
x = NumArray()
with open("x_train.bin", mode='rb') as fr:
x.read(fr)
y = NumArray()
with open("t_train_a.bin", mode='rb') as fr:
y.read(fr)
print(x,y)
def image_check(idx):
xa=x.createLineAt(idx).array
print(y.createLineAt(idx))
img=[]
for n in range(0,28*28, 28): img.append(xa[n:n+28])
plt.imshow(img, "gray")
plt.show()
while True:
idx=input(f"0〜{x.shapeR[1]}>")
if idx == '': break
image_check(int(idx)) # 添え字の正解ラベルとその手書き画像を表示
#import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う
#print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する
from numarray import NumArray # 1〜3次元配列とシリアライス関連のクラス
from neural2net import Neural2Net # 上記を利用して作成した判定器のクラス
class Neural2NetLn (Neural2Net): # 学習用(ニューラル2層ネット)
#
def __init__(self,input_size:int=784, hidden_size:int=50, output_size:int=10, weight_init_std:float=0.01): # create 相当
super().__init__()
self.naW1 = NumArray.createGaussian(hidden_size, input_size, 1, 0, weight_init_std);
self.naB1 = NumArray.createParam(0, hidden_size);
self.naW2 = NumArray.createGaussian(output_size, hidden_size, 1, 0, weight_init_std);
self.naB2 = NumArray.createParam(0, output_size);
self.naA1:NumArray=None # predictの計算過程で一時記憶し、gradientで利用
self.naZ1:NumArray=None # predictの計算過程で一時記憶し、gradientで利用
self.gradsW2:NumArray=None # gradientにる勾配情報の一次記憶用
self.gradsB2:NumArray=None;
self.gradsW1:NumArray=None;
self.gradsB1:NumArray=None;
self.loss: float=0 # 学習で進行状態(交差エントロピー誤差)
self.learning_rate:float=0.1 # 学習率
#
# 勾配降下法でシグモイド関数の勾配を求める時に利用するメソッド
def sigmoid_grad(self, x:NumArray)->NumArray:
# (1.0 - sigmoid(x)) * sigmoid(x)
sigmoid_x:NumArray = x.sigmoid()
grad:NumArray = sigmoid_x.deepcopy();
grad.mul_scalar(-1);
grad.add_scalar(1);
grad.mul_matrix(sigmoid_x);
return grad;
#
# モデルの予測 → 損失計算 → 勾配計算 → W2_B2_W1_B1パラメータ更新
def gradient_and_set_W2_B2_W1_B1(self, x:NumArray, t:NumArray):
naY = self.predict(x) # 予測値を取得
#print(f"naY:{naY}, naY.shapeR:{naY.shapeR}")
self.loss = NumArray.cross_entropy_error(naY, t) # 交差エントロピー誤差取得
#print(f"-----cross_entropy_error:{self.loss}")
# -------W1,B1, W2,B2 の予測値から、正解に近づける勾配(gradsW2,B2,W1,B1)を算出---------
naDy:NumArray = t.deepcopy()
naDy.mul_scalar(-1)
naDy.add_matrix(naY)
#print(f"naDy:{naDy}, naDy.shapeR:{naDy.shapeR}")
self.gradsW2 = NumArray.dot(NumArray.T(self.naZ1), naDy)
#print(f"gradsW2:{self.gradsW2}, gradsW2.shapeR:{self.gradsW2.shapeR}")
self.gradsB2 = NumArray.sum(naDy, 0)
#print(f"gradsB2:{self.gradsB2}, gradsB2.shapeR:{self.gradsB2.shapeR}")
da1 = NumArray.dot(naDy, NumArray.T(self.naW2))
#print(f"da1:{da1}, da1.shapeR:{da1.shapeR}")
dz1 = self.sigmoid_grad(self.naA1)
dz1.mul_matrix(da1)
#print(f"dz1:{dz1}, dz1.shapeR:{dz1.shapeR}")
self.gradsW1 = NumArray.dot(NumArray.T(x), dz1)
#print(f"gradsW1:{self.gradsW1}, gradsW1.shapeR:{self.gradsW1.shapeR}")
self.gradsB1 = NumArray.sum(dz1, 0)
#print(f"gradsB1:{self.gradsB1}, gradsB1.shapeR:{self.gradsB1.shapeR}")
# -------W1,B1, W2,B2 の重みバイアスを正解に近づける更新----------------
self.gradsW1.mul_scalar( -self.learning_rate );
self.naW1.add_matrix(self.gradsW1);
#print(f"naW1:{self.naW1}, naW1.shapeR:{self.naW1.shapeR}")
self.gradsB1.mul_scalar(-self.learning_rate);
self.naB1.add_matrix(self.gradsB1);
#print(f"naB1:{self.naB1}, naB1.shapeR:{self.naB1.shapeR}")
self.gradsW2.mul_scalar(-self.learning_rate);
self.naW2.add_matrix(self.gradsW2);
#print(f"naW2:{self.naW2}, naW1.shapeR:{self.naW2.shapeR}")
self.gradsB2.mul_scalar(-self.learning_rate)
self.naB2.add_matrix(self.gradsB2)
#print(f"naB2:{self.naB2}, naB2.shapeR:{self.naB2.shapeR}")
#raise Exception("強制停止") # デバック用
#
# W1,B1, W2,B2 の重みバイアスのパラメタをファイルにシリアライスする。
def save_params(self, path:str="weight_bias_params_0.bin"):
with open(path, mode='wb') as fw:
self.naW1.write(fw) # 直列化(Serialize) して保存
self.naB1.write(fw)
self.naW2.write(fw)
self.naB2.write(fw)
#
#import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う
#print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する
from numarray import NumArray
from neural2netln import Neural2NetLn # 学習クラス
import random
import gc
import time
start = time.time()
import os
load_params_file="weight_bias_params_0.bin" # このパラメタファイルが無い場合は、最初からの学習
save_params_file="weight_bias_params_1.bin" # 学習結果を記憶ファイル。上記に設定すると続きから学習可能
if load_params_file in os.listdir():
twoLayerNet=Neural2NetLn()
twoLayerNet.load_params( load_params_file )
else:
twoLayerNet = Neural2NetLn(28 * 28, 20, 10, 0.01) # 学習用ニューラルネット2層クラス生成
#print(f"-------------start:{start}")
x_train = NumArray()
with open("x_train.bin", mode='rb') as fr: # 手書き画像読み取り
x_train.read(fr)
print(f"x_train.bin load x_train.shapeR:{x_train.shapeR}")
t_train = NumArray()
with open("t_train_a.bin", mode='rb') as fr: # 正解のOne-Hot情報読み取り
t_train.read(fr)
print(f"t_train_a.bin load x_train.shapeR:{t_train.shapeR}")
train_size=x_train.shapeR[1] # 学習素材(x_train、t_train)を0〜train_sizeの範囲にする。
for n in range(900): # 学習のイテレーション(#モデルの予測 → 損失計算 → 勾配計算 → パラメータ更新) 繰り返し
gc.collect() # ガベージコレクションを即座に実行
#print(f"Used Memory:{gc.mem_alloc()}bytes") # MicroPython専用
#print(f"Free memory:{gc.mem_free()}bytes") # MicroPython専用
#print(gc.get_stats()) # PC のPythonで使う
idx = int(random.random()*train_size) # 学習対象の画像取得
x_batch = x_train.createLineAt(idx)
x_batch.shapeR[1] = 1
t_batch = t_train.createLineAt(idx)
t_batch.shapeR[1] = 1
#print(f"idx:{idx} extract")
twoLayerNet.gradient_and_set_W2_B2_W1_B1(x_batch, t_batch) # twoLayerNet内の gradsW2,B2,W1,B1の傾き設定
msg = f"count:{n:7}, cross entropy loss:{twoLayerNet.loss:10.6f}"
#print(msg)
#print(f"Elapsed time:{time.time()-start}")
twoLayerNet.save_params(path=save_params_file) # 学習したデータを保存 2000回目 "22日24時に終わる予定
end = time.time()
with open('ProcessingTime.txt', 'w', encoding="utf-8") as fw:
fw.write(f'Processing time:{end-start} sec') # 500の繰り返しで、mProcessing time:178383 sec 約2.065日
#print(f"-------------End:{end} , Processing time:{end-start}")
入力:M/F/'quit'?>M
ls -l/cat /get /del /import >ls -l
Send:ls -l
ls_filelist:
40 boot.py
760 df.py
1002 filerec.py
2087 n2n_test.py
2284 n2nln_test.py
2213 neural2net.py
4682 neural2netln.py
17762 numarray.py
8901 server.py
449 setap.py
8012 t_train_a.bin
627212 x_train.bin
入力:M/F/'quit'?>M
ls -l/cat /get /del /import >import n2nln_test
Send:import n2nln_test
import_name:n2nln_test
入力:M/F/'quit'?>-------------start:430
x_train.bin load x_train.shapeR:[784, 200, 0]
t_train_a.bin load x_train.shapeR:[10, 200, 0]
Used Memory:3513392bytes
Free memory:4680928bytes
count: 0, cross entropy loss: 2.332474
Used Memory:3837280bytes
Free memory:4357040bytes
count: 1, cross entropy loss: 2.374296
Used Memory:3837616bytes
Free memory:4356704bytes
count: 2, cross entropy loss: 2.442477
・・・・・表示省略・・・・・
Used Memory:3837616bytes
Free memory:4356704bytes
count: 98, cross entropy loss: 2.498507
Used Memory:3837520bytes
Free memory:4356800bytes
count: 99, cross entropy loss: 2.403183
・・・・・表示省略・・・・・
count: 199, cross entropy loss: 2.493203
・・・・・表示省略・・・・・
上記の実行には約5日間必要です。入力:M/F/'quit'?>M ls -l/cat /get /del /import >cat ProcessingTime.txt Send:cat ProcessingTime.txt cat_filepath:ProcessingTime.txt Processing time:321508 sec 入力:M/F/'quit'?>これから、321508/60/60/24=3.721157407407407日の学習を行ったことが分かります。
入力:M/F/'quit'?>M ls -l/cat /get /del /import >import n2nln_test Send:import n2nln_test import_name:n2nln_test 入力:M/F/'quit'?>x_train.bin load x_train.shapeR:[784, 200, 0] t_train_a.bin load x_train.shapeR:[10, 200, 0] Used Memory:3513696bytes Free memory:4534064bytes count: 0, cross entropy loss: 0.015715 Used Memory:3836960bytes Free memory:4210800bytes count: 1, cross entropy loss: 0.036865 Used Memory:3836960bytes Free memory:4210800bytes count: 2, cross entropy loss: 0.036055 Used Memory:3836960bytes Free memory:4210800bytes count: 3, cross entropy loss: 0.022176 Used Memory:3836848bytes Free memory:4210912bytes count: 4, cross entropy loss: 0.116104 入力:M/F/'quit'?>
import sys
sys.path.append('..')# 親ディレクトリ相対パスをモジュール検索パスに追加
from numarray import NumArray
import matplotlib.pyplot as plt
# ファイル('train-images-idx3-ubyte.gz','train-labels-idx1-ubyte.gz'))のMNISTの素材から前処理してから
# startIdx の変数が指定する位置の画像から10個抽出して# ("x_train10.bin","t_train_a10.bin")のファイルを作る。
startIdx = 10000 # この位置からNN個の素材を抽出
NN=10 # 抽出個数
import gzip
fimg = gzip.open('train-images-idx3-ubyte.gz','r')#画像データ
fimg.read(16) #メタ
fimg.read(28 * 28 * startIdx); # (28X28)=784画素
buf = fimg.read(28 * 28 * NN); # (28X28)=784画素
fimg.close()
x = NumArray.createByArray(buf, 28 * 28)
x.mul_scalar(1.0 / 255); # 画像データ0〜255を、0〜1.0に正規化
fimg = gzip.open('train-labels-idx1-ubyte.gz') # 学習用の正解ラベル
fimg.read(8) #メタ
fimg.read(startIdx); # 画像の正解データを読み込む
buf = fimg.read(NN); # 画像の正解データを読み込む
fimg.close()
y = NumArray(10, NN) # One-Hot ベクトル表現
for i in range( NN ):
y[buf[i], i, 0] = 1.0;
print(x,y)
def image_check(idx):
# x の idx が指す要素を表示
xa=x.createLineAt(idx).array
print(y.createLineAt(idx))
img=[]
for n in range(0,28*28, 28): img.append(xa[n:n+28])
plt.imshow(img, "gray")
plt.show()
# チェック
while True:
idx=input(f"0〜{x.shapeR[1]}>")
if idx == '': break
image_check(int(idx)) # 添え字の正解ラベルとその手書き画像を表示
with open("x_train10.bin", mode='wb') as fw:
x.write(fw) # 直列化(Serialize) して保存
with open("t_train_a10.bin", mode='wb') as fw:
y.write(fw) # 直列化(Serialize) して保存
上記で生成した"x_train10.bin"と"t_train_a10.bin"を「ESP32-S3-DEV-KIT-N16R8-M」側で判定するプログラム(n2n_test.py)を下記に示します。
import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う
print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する
# n2n_test.pyの判定器用ファイル
from numarray import NumArray
from neural2net import Neural2Net
n2n=Neural2Net()
n2n.load_params( "weight_bias_params_0.bin" )
print(f"n2n.naW1.shapeR:{n2n.naW1.shapeR}") # ロードした学習済みの1層目の重みパラメタ
print(f"n2n.naB1.shapeR:{n2n.naB1.shapeR}") # ロードした学習済みの1層目のバイアスパラメタ
print(f"n2n.naW2.shapeR:{n2n.naW2.shapeR}") # ロードした学習済みの2層目の重みパラメタ
print(f"n2n.naB2.shapeR:{n2n.naB2.shapeR}") # ロードした学習済みの2層目のバイアスパラメタ
x_train = NumArray() # 手書き画像
with open("x_train10.bin", mode='rb') as fr:# 学習や検証素材を、前処理してシリアラスした画像ファイル(28*28の10個)
x_train.read(fr) # デシリアライズ
print(f"load x_train10.bin:{x_train.shapeR}") # 画像データの構造表示
t_train = NumArray() # One-Hot ベクトル表現 上記の解答情報
with open("t_train_a10.bin", mode='rb') as fr: # 上記の正解ファイルで、One-Hot ベクトル表現に前処理してファイル
t_train.read(fr) # デシリアライズ
print(f"load t_train_a10.bin:{t_train.shapeR}") # 正解データの構造表示
# load x_train10.binに存在する画像(28×28)の判定を行う。
for i in range(x_train.shapeR[1]):
testImage = NumArray.createLineAt(x_train, i) # 入力変数 (Input Variable)で、画像(28×28)
print(f"x_train[{i}].shapeR:{testImage.shapeR}")
one_hot = NumArray.createLineAt(t_train, i) # 正解ラベル (Ground Truth Label)
print(f"t_train[{i}].shapeR:{one_hot}")
prediction=n2n.predict(testImage) # # 画像(28×28)の予測対象変数 (Target Variable)
print(f" prediction:{prediction}\n")
入力:M/F/'quit'?>M
ls -l/cat /get /del /import >ls -l
Send:ls -l
ls_filelist:
24 ProcessingTime.txt
40 boot.py
760 df.py
1002 filerec.py
2086 n2n_test.py
2731 n2nln_test.py
2213 neural2net.py
4682 neural2netln.py
17763 numarray.py
8901 server.py
449 setap.py
8012 t_train_a.bin
412 t_train_a10.bin
63688 weight_bias_params_0.bin
63688 weight_bias_params_1.bin
1528 wifi.py
627212 x_train.bin
31372 x_train10.bin
入力:M/F/'quit'?>M
ls -l/cat /get /del /import >import n2n_test
Send:import n2n_test
import_name:n2n_test
n2n.naW1.shapeR:[20, 784, 1]
n2n.naB1.shapeR:[20, 1, 0]
n2n.naW2.shapeR:[10, 20, 1]
n2n.naB2.shapeR:[10, 1, 0]
load x_train10.bin:[784, 10, 0]
load t_train_a10.bin:[10, 10, 0]
入力:M/F/'quit'?>x_train[0].shapeR:[784, 0, 0]
t_train[0].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0011 0.0015 0.0076 0.9613 0.0003 0.0175 0.0011 0.0005 0.0071 0.0018 ]
x_train[1].shapeR:[784, 0, 0]
t_train[1].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0049 0.0082 0.0082 0.0217 0.0003 0.0314 0.0001 0.0173 0.8961 0.0117 ]
x_train[2].shapeR:[784, 0, 0]
t_train[2].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0007 0.0005 0.0068 0.0027 0.0015 0.0012 0.0000 0.9399 0.0050 0.0417 ]
x_train[3].shapeR:[784, 0, 0]
t_train[3].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 ]
prediction:10, 0, 0
[ 0.0002 0.0001 0.0000 0.0017 0.0608 0.0008 0.0002 0.0076 0.0013 0.9274 ]
x_train[4].shapeR:[784, 0, 0]
t_train[4].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 ]
prediction:10, 0, 0
[ 0.0024 0.0030 0.0117 0.0714 0.6155 0.0305 0.0080 0.0685 0.0044 0.1845 ]
x_train[5].shapeR:[784, 0, 0]
t_train[5].shapeR:10, 0, 0
[ 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.6433 0.0016 0.0488 0.0503 0.0216 0.1246 0.0752 0.0026 0.0259 0.0062 ]
x_train[6].shapeR:[784, 0, 0]
t_train[6].shapeR:10, 0, 0
[ 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0000 0.9699 0.0042 0.0001 0.0000 0.0216 0.0003 0.0004 0.0034 0.0000 ]
x_train[7].shapeR:[784, 0, 0]
t_train[7].shapeR:10, 0, 0
[ 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0000 0.9746 0.0081 0.0000 0.0003 0.0138 0.0004 0.0016 0.0011 0.0000 ]
x_train[8].shapeR:[784, 0, 0]
t_train[8].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.3231 0.0020 0.0541 0.2980 0.0256 0.1381 0.1351 0.0013 0.0126 0.0101 ]
x_train[9].shapeR:[784, 0, 0]
t_train[9].shapeR:10, 0, 0
[ 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0074 0.0087 0.9070 0.0022 0.0003 0.0286 0.0041 0.0022 0.0391 0.0003 ]
上記の判定実行で、t_train[4].shapeR:10, 0, 0の判定だけ正解ラベルと異なる結果が得られていますが、
おおよそ、正解の判定結果が得られています。