このリンク先で示している「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.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_train.bin", mode='rb') as fr:# 学習や検証素材を、前処理してシリアラスした画像ファイル(28*28の6万個) x_train.read(fr) # デシリアライズ print(f"load x_train.bin:{x_train.shapeR}") # 画像データの構造表示 t_train = NumArray() # One-Hot ベクトル表現 上記の解答情報 with open("t_train_a.bin", mode='rb') as fr: # 上記の正解ファイルで、One-Hot ベクトル表現に前処理してファイル t_train.read(fr) # デシリアライズ print(f"load t_train_a.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回の学習を行ったデータです。