TOP PAGE

このリンク先で示している判定器Neural2Netを継承したNeural2NetLNの学習クラスを定義してこれを検証しています。
ここで紹介したほとんどのコードは通常のPythonでも動作しますが、
最終的な検証は、このリンク先で紹介したESP32-S3の環境内のMicroPythonで、MNISTを使った手書きイメージの学習を行います。
この環境の「ESP32-S3-DEV-KIT-N16R8-M」で実験した内容を紹介しています。

MicroPythonで行うシンプルなディープニューラルネットワーク学習(Neural2Netを継承したNeural2NetLNを定義して行う)

学習に使うMNISTのデータをダウンロードして前処理を行う

次のコード(prepare_learning_data.py)で、MNISTの「'train-images-idx3-ubyte.gz':学習用の手書きイメージ」 と、 「'train-labels-idx1-ubyte.gz':学習用の正解ラベルイメージ」 のダウンロードを行う。
そして、それぞれをファイルを解凍して、NNに記憶される数だけ「画像と正解ラベル」をそれぞれを学習のための前処理を行って、 それそれを"x_train.bin"と"t_train_a.bin"に保存しています。
それぞれは、次の内容のNumArrayをシリアライズしています。
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しかありません。
学習素材の1枚の画像は、28×28×4=3136で、NN=1500の場合は3136*1500=4704000byteで他に4×3=12byte次元情報がるのため、
"x_train.bin"は4704012byteの学習素材ファイルになり、それを「ESP32-S3-DEV-KIT-N16R8-M」に転送することができました。
しかし、その読み取り処理(NumArrayのread)でアロケーションエラーが起きました。

どの程度がサイズなら読み取り可能か?ということになりますが、下記で後述する学習用クラス(Neural2NetLn)のオブジェクトと、 この学習素材のオブジェクトが最終的に両立して存続できなければないません。
そして、入力層が28*28=784で中間層ニューロンが20個、出力が10の2層ネットワークの場合のおおよそ可能な素材数の目安は、 NN=300個という実験結果が得られました。
NN=300時の"x_train.bin"は28*28*4*300+4*3=940812byteで、"x_train.bin"は10*4*300+4*3=12012byteです。
この学習素材で学習を開始した所、100イテレーションに約6時間掛かりました。
なお、1回のイテレーションの処理は、「入力データを予測、正解を比較して損失を計算、損失をもとに勾配を計算、勾配を使ってパラメータを更新」です。
PC上で同一プログラムを実行すると、学習に必要な回数は2000イテレーション程度という結果が得られていますが、そのためには5日程掛かることになります。

NN=200時の"x_train.bin"は28*28*4*200+4*3=627212byteで、"x_train.bin"は10*4*200+4*3=8012byteです。
この学習素材で学習を開始した所、100イテレーションに約1.5時間掛かりました。
(2000イテレーションに程度という結果が得られていますが、そのためには約30時間程掛かることになります。)

上記の実験結果の学習時間は、Neural2NetLn.csの学習クラスと上記にテスト用の学習処理を含めて実行した時間です。
この後、後述のように Neural2NetLn.py に学習用クラスだけを定義して、学習処理を分離しました。
その場合に、学習が終わる場合の時間を改めて計測すると、学習時間が大幅に掛かってしまいました。
MicroPythonにおいて、ソースを分離して一方をimportで呼び出すような構造にすると、 モジュールの読み込みによってRAM使用量が急増し、GCが頻繁に発生すると処理が遅くなるようです。
特にESP32などRAMが限られている環境では顕著で、実際に活用する場合は、モジュール分割を避けた構造にした方が良いと思われます。


なお上記で生成した"x_train.bin"と"t_train_a.bin"が正しいか手動で検証するコード(check.py)を以下に示します。
実行すると、"0〜200>"のプロンプトが出ます。ここで入力した添え字の正解ラベルとその画像が表示されるので、それを目視で確かめるためのコードです。
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)) # 添え字の正解ラベルとその手書き画像を表示

学習用クラスNeural2NetLnのコード(neural2netln.py)とその実行例(n2nln_test.py)

このリンク先ページでAI判定器のNeural2Netを作成していますが、それに使う学習を行って学習済みデータを作成するためクラスが 次のNeural2NetLnクラスです。
このリンク先で示している「Unityで行うシンプルなディープニューラルネットワーク」で紹介した内容と同じ処理で、 同じシリアライズファイルを操作します)

以下で定義しているNeural2NetLnクラスは、AI判定器のNeural2Netクラスをを継承して作成しています。
#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)
    #

下記のコード(n2nln_test.py)は、前述のprepare_learning_data.pyで作成した学習素材(手書き画像の"x_train.bin"と、One-Hot正解ラベルの "t_train_a.bin")を 使って学習を」しています。
下記学習は「損失計算 → 勾配計算 → W2_B2_W1_B1パラメータ更新」ののイテレーションを900回行っています。
そのパラメータをシリアライズして、"weight_bias_params_0.bin"のファイルを作るコードのn2nln_test.pyを下記に示します。
以前の学習結果のファイルが"weight_bias_params_0.bin"で、それの継続学習を行い、その学習結果を"weight_bias_params_1.bin"に記憶します。
("weight_bias_params_0.bin"が存在しない場合は、乱数から学習を始めます。)
この学習用コード(n2nln_test.py)を次に示します。
#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}")
上記を、このリンク先で紹介したESP32-S3の環境で、PCから「python client_esp32.py」コマンドによる遠隔操作で 実行させます。
この操作で、上記で用意した手書き画像の"x_train.bin"と、 One-Hot正解ラベルの "t_train_a.bin"、 Neural2NetLnクラスのneural2netln.py、 学習実行用ソースnをn2nln_test.pyの4つを「ESP32-S3-DEV-KIT-N16R8-M」にアップロードします。
(以前に、numarray.pyとneural2net.pyがアップロード済みです。)
なお"t_train_a.bin"のサイズが大きいので、 転送コマンドは「F」でなく「f」の文字のアップロードコマンドを使うと良いでしょう。
「f」の文字のアップロードコマンドは1byte送信の繰り返しを使う方法で、バッファを使わないので遅くなりますが大きなファイル転送が可能です。

以下は、上記のn2nln_test.pyで、「#import server」、「#print=server.send」、 「#print(f"-------------start:{start}")」、
#print(f"Used Memory:{gc.mem_alloc()}bytes") 」、「#print(f"Free memory:{gc.mem_free()}bytes")」、 「#print(msg)
のコメントを外して、表示文字列を送信してPC側に表示させた実行の例です。
(表示は部分的に省略しています)
入力: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日間必要です。
これは、PCとWifiを介した通信状態を保つ必要があります。
そこで、途中から「ESP32-S3-DEV-KIT-N16R8-M」単体で動作させるのが現実的です。
それにはPCに出力している上記でコメント(「#import server」、・・)をコメントに戻して実行ささせます。
そして、メニュー操作で、入力:M/F/'quit'?>でquitを入力して、PC側のクライアント側で終了して通信を切断させます。
このような操作で、通信は終わりますが、「ESP32-S3-DEV-KIT-N16R8-M」ではn2nln_testモジュールの実行が継続されます。
そして、n2nln_testモジュールの実行終了時に、ProcessingTime.txtのファイルに実行時間を記録します。
つまりファイルの有無で、終わっているかの判定が可能です。
以下は、このファイルの内容を確認した時の実行例です。
入力: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日の学習を行ったことが分かります。
これで、現在のDNN構造で学習にどれだけ時間が分かるので、下記のように"weight_bias_params_1.bin"の学習済みデータから 継続して学習して、"weight_bias_params_0.bin"の結果得るように変更し、希望の回数の学習を、終了時間を予測して継続させます。
load_params_file="weight_bias_params_1.bin" # このパラメタファイルが無い場合は、最初からの学習
save_params_file="weight_bias_params_0.bin" # 学習結果を記憶ファイル。上記に設定すると続きから学習可能


このような継続で、合計約2000回の学習で得られた"weight_bias_params_0.bin" を、使った検証を行った例を下記で示しています。
なお、2000回直後の学習過程を表示させた実行例を以下に示します。(交差エントロピー誤差から学習が進んでいることが確認できます。)
入力: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'?>

上記Neural2NetLnの学習結果の"weight_bias_params_0.bin"を使って判定できるか検証する

検証に使う手書き画像10個のシリアライズファイルのx_train10.binと、その正解となるOne-Hotのファイルのt_train_a10.binを用意するコード例(extract10.py)を以下に示します。
これはPC側のPythonプログラムで、MNISTのダダウンロードファイル('train-images-idx3-ubyte.gz'、'train-labels-idx1-ubyte.gz')から位置とサイズを指定して、 そのファイルを目視で確かめて、シリアライズのファイル化を行っています。
このextract10.pyの内容を以下に示します。
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)を下記に示します。
このリンク先で紹介したコード(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") 


前述で生成した"x_train10.bin"と"t_train_a10.bin"と、上記のn2n_test.pyを「ESP32-S3-DEV-KIT-N16R8-M」にアップロードして、 n2n_test.pyを実行し、weight_bias_params_0.binの学習データが正しく学習できているかを、次の様に確かめた実行例です。
入力: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の判定だけ正解ラベルと異なる結果が得られていますが、 おおよそ、正解の判定結果が得られています。