このリンク先で示している「Unityで行うシンプルなディープニューラルネットワーク」で行った学習済みデータを使って、
MicroPythonでAI判定するNeural2Netクラスの実装例と、その検証内容を紹介しているページです。
Neural2Netでは、NumArrayクラス(Numpyの機能などを絞った自作クラス)を利用して作られています。
なお、このコードはNumpyが使えないMicroPython用に作ったコードですが、通常のPythonでも使えます。
# import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う
# print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する
# neural2net.pyのファイル内容
import numarray
from numarray import NumArray
# neural2net.pyのファイル内容
import numarray
from numarray import NumArray
class Neural2Net: # 判定器(ニューラル2層ネット)
def __init__(self): # create 相当
self.naW1 = NumArray() # 1層目の重みパラメタ
self.naB1 = NumArray() # 1層目のバイアスパラメタ
self.naW2 = NumArray() # 2層目の重みパラメタ
self.naB2 = NumArray() # 2層目のバイアスパラメタ
#
# 20250805追加
def load_params( self, path:str = "weight_bias_params_0.bin" ):
try:
with open(path, mode='rb') as fr:
self.naW1.read(fr) # 学習済みの重み、バイアス情報を読み取る。
self.naB1.read(fr)
self.naW2.read(fr)
self.naB2.read(fr)
except Exception as e:
raise RuntimeError(f"[Neural2Net create_load_params('{path})']:{e}")
#
@classmethod # 20250805変更
def create_load_params( cls, path:str = "weight_bias_params_0.bin" )->'Neural2Net':
my = Neural2Net()
my.load_params( str )
return my
#
#
def predict(self, x: 'NumArray' )->'NumArray': # 引数
# 予測メソッド x のグレー画像(28×28)の判定をソフトマックスを介して返す。
#print(f"x:{x}")
#print(f"naW1:{self.naW1}, naW1.shapeR{self.naW1.shapeR}")
#print(f"naB1:{self.naB1}")
self.naA1 = NumArray.dot(x, self.naW1)
#print(f"naA1:{self.naA1}")
self.naA1.add_matrix(self.naB1)
#print(f"naA1:{self.naA1}")
self.naZ1 = self.naA1.sigmoid()
#print(f"naZ1:{self.naZ1}")
naA2 = NumArray.dot(self.naZ1, self.naW2)
naA2.add_matrix(self.naB2)
#print(f"naA2:{naA2}")
naY = NumArray.softmax(naA2)
return naY # 確率分布を返す。
#
#
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")
n2n.naW1.shapeR:[50, 784, 1]
n2n.naB1.shapeR:[50, 1, 0]
n2n.naW2.shapeR:[10, 50, 1]
n2n.naB2.shapeR:[10, 1, 0]
load x_train.bin:[784, 60000, 0]
load t_train_a.bin:[10, 60000, 0]
x_train[0].shapeR:[784, 0, 0]
t_train[0].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.0234 0.0033 0.0052 0.0250 0.0000 0.9398 0.0011 0.0006 0.0013 0.0003 ]
x_train[1].shapeR:[784, 0, 0]
t_train[1].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.9971 0.0000 0.0000 0.0000 0.0000 0.0021 0.0001 0.0006 0.0000 0.0000 ]
x_train[2].shapeR:[784, 0, 0]
t_train[2].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0000 0.0003 0.0008 0.0053 0.9910 0.0002 0.0013 0.0002 0.0001 0.0009 ]
x_train[3].shapeR:[784, 0, 0]
t_train[3].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.9910 0.0020 0.0000 0.0000 0.0001 0.0000 0.0000 0.0069 0.0000 ]
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.0000 0.0003 0.0000 0.0000 0.0065 0.0000 0.0000 0.0018 0.0005 0.9909 ]
x_train[5].shapeR:[784, 0, 0]
t_train[5].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.0003 0.0003 0.9935 0.0003 0.0000 0.0004 0.0003 0.0001 0.0046 0.0001 ]
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.9975 0.0001 0.0001 0.0000 0.0002 0.0000 0.0001 0.0020 0.0000 ]
x_train[7].shapeR:[784, 0, 0]
t_train[7].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.0000 0.0001 0.0001 0.9991 0.0000 0.0003 0.0000 0.0000 0.0004 0.0001 ]
x_train[8].shapeR:[784, 0, 0]
t_train[8].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.9945 0.0001 0.0000 0.0000 0.0006 0.0000 0.0025 0.0022 0.0001 ]
x_train[9].shapeR:[784, 0, 0]
t_train[9].shapeR:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 ]
prediction:10, 0, 0
[ 0.0000 0.0000 0.0000 0.0000 0.9984 0.0001 0.0005 0.0000 0.0001 0.0008 ]
上記は、MicroPythonの環境ではなく、通常のPython(Python 3.6.8)の環境で実行した結果です。
from numarray import NumArray
x_train = NumArray() # [1,60000,28*28]の手書き画像
with open("x_train.bin", mode='rb') as fr:# 学習や検証素材を、前処理してシリアラスした画像ファイル(28*28の6万個)
x_train.read(fr) # デシリアライズ
print(f"load x_train.bin:{x_train.shapeR}") # 画像データの構造表示
x_train.shapeR[1]=10
with open("x_train10.bin", mode='wb') as fw:
x_train.write(fw) # 先頭10個を保存
t_train = NumArray() # One-Hot ベクトル表現 上記の解答情報
with open("t_train_a.bin", mode='rb') as fr: # 上記の正解ファイルで、One-Hot ベクトル表現に前処理してファイル
t_train.read(fr) # デシリアライズ
t_train.shapeR[1]=10
with open("t_train_a10.bin", mode='wb') as fw:
t_train.write(fw) # 先頭10個を保存
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.create_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")
D:\esp32ロボット1>python client_ume_esp32.py
IP (defualt:192.168.222.1)Address>
Connect!
入力:M/F/'quit'/w/a/s/d/b/?>F
['boot.py', 'client_ume_esp32.py', 'flow_off.umh', 'log.txt', 'neural2net.py', 'numarray.py', 'server.py', 'setap.py', 'uEsp32Init.umh',
't_train_a10.bin', 'weight_bias_params_0.bin', 'x_train10.bin', '_df.py']
送信したいファイル名入力>n2n_test.py
Server:'n2n_test.py' 2053bytes received.
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >import n2n_test
Send:import n2n_test
import_name:n2n_test
import_name Error:memory allocation failed, allocating 156800 bytes
入力:M/F/'quit'/w/a/s/d/b/?>
上記では、 'neural2net.py', 'numarray.py', 't_train_a10.bin', 'weight_bias_params_0.bin', 'x_train10.bin'のファイルを転送済みの環境で、
client_ume_esp32.pyを実行して、n2n_test.pyの送信と、その実行を行った場合の結果を示しています。)import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する import gc print(gc.mem_free()) # 今どのくらいヒープが空いているかのサイズ(byte)確認上記コードをUMEHOSHI ITA基板に取り付けたESP32-WROOM-32Dのサーバーで動作させると、「69520」byteの値が得られました。
D:\esp32ロボット1>python client_ume_esp32.py
IP (defualt:192.168.222.1)Address>
Connect!
入力:M/F/'quit'/w/a/s/d/b/?>F
['boot.py', 'client_ume_esp32.py', 'flow_off.umh', 'log.txt', 'neural2net.py', 'numarray.py', 'server.py', 'setap.py', 'uEsp32Init.umh',
't_train_a10.bin', 'weight_bias_params_0.bin', 'x_train10.bin', '_df.py']
送信したいファイル名入力>weight_bias_params_0.bin
Server:'weight_bias_params_0.bin' 3268bytes received.
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >import n2n_test
Send:import n2n_test.py
import_name:n2n_test.py
n2n.naW1.shapeR:[1, 784, 1]
n2n.naB1.shapeR:[1, 1, 0]
n2n.naW2.shapeR:[10, 1, 1]
n2n.naB2.shapeR:[10, 1, 0]
load x_train10.bin:[784, 1, 0]
load t_train_a10.bin:[10, 1, 0]
x_train[0].shapeR:[784, 0, 0]
t_train[0].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.0613 0.0480 0.1218 0.0448 0.0831 0.2893 0.0778 0.1020 0.0767 0.0952 ]
入力:M/F/'quit'/w/a/s/d/b/?>
上記で使っている学習済みのファイル(weight_bias_params_0.bin 3268bytes)は、
中間層のニューロン数が1で、41000回の学習を行ったデータです。