TOP PAGE

このページで示している「Unityで行うシンプルなディープニューラルネットワーク」で行った学習済みデータを使って、
MicroPythonでAI判定などの実現に当たり、それに必要なNumpyの機能などを絞った自作のNumArrayクラスを紹介しているページです。
また、このNumArrayクラスを検証したコードを紹介します。
なお、以下で紹介したNumArrayクラスを利用し、学習データで初期化して判定するクラスをこのリンク先で紹介します。

MicroPythonでも動く NumArrayクラス(Numpyの機能などを絞った自作クラス)のコード

NumArrayクラスのコード(numarray.py)(参考にしたC#のコード

# import server # 「https://manabu.quu.cc/up/ume/ume_esp32_python.html」のサーバーで動作させる場合、この2行を使う
# print=server.send # サーバーで動作させる場合で、printの出力をTCPクライアントに送信する

# (numarray.pyの名前で作成)
import math
import struct
import io
import random
random.seed(123) # 乱数シードを123に設定 モジュールインポートで初期化

class NumArray:

    def __init__(self, dim1:int=1,  dim2:int = -1, dim3:int = -1): # create 相当
        # 1次元、2次元、3次元のサイズだけを指定した初期化生成
        dim2 = 0 if dim2 < 0 else dim2
        dim3 = 0 if dim3 < 0 else dim3
        self.shapeR = [dim1, dim2, dim3]
        dim2 = 1 if dim2 == 0 else dim2
        dim3 = 1 if dim3 == 0 else dim3
        self.array = [0.0] * (dim1 * dim2 * dim3) 
    #
    def __getitem__(self, index):
        if not isinstance(index, tuple) or len(index) != 3:
            raise TypeError("インデックスは (dim1, dim2, dim3) のタプルである必要があります")
        i1, i2, i3 = index
        dim1 = self.shapeR[0] # 1次元サイズ
        idx = i3 * (dim1 * self.shapeR[1]) + i2 * dim1 + i1 # 1次元の配列に相当する添え字を求める
        #print(f"array[{idx}]");
        return self.array[idx]
    #
    def __setitem__(self, index, value):
        i1, i2, i3 = index
        dim1 = self.shapeR[0] # 1次元サイズ
        idx = i3 * (dim1 * self.shapeR[1]) + i2 * dim1 + i1 # 1次元の配列に相当する添え字を求める
        self.array[idx] = value

    @classmethod
    def createByArray(cls, buf,dim1:int=0,  dim2:int = 0):
        # buf のリストで初期化したNumArrayを、次元指定で生成する
        dim2 = 0 if dim2 < 0 else dim2
        n = len(buf)
        na = NumArray(n)
        na.array = [0.0] * n
        for i in range(len(buf)): na.array[i] = buf[i] # コピー
        if dim1 == 0: # 第1引数省略時
            na.shapeR[0] = n; # 1次元
            na.shapeR[1] = na.shapeR[2] = 0
        else: # 2次元または3次元
            na.shapeR[0] = dim1
            if dim2 == 0:  # 第2引数省略時 
                na.shapeR[1] = int(n / (dim1) + 0.5)
                na.shapeR[2] = 0; # 次元の個数が2
            else:
                na.shapeR[1] = dim2
                na.shapeR[2] = int(n / (dim1 * dim2) + 0.5) # 次元の個数が3
            #
        #
        return na
    #
    def __str__(self):
        if self.shapeR[0] == 0:
            return f" {self.array[0],8:F4}"
        _MAX_LINE=5 # (この数×2)行より大きい場合、内部を表略表示
        brackets0 = False # 始まりのカッコ出力済みでTrue
        brackets1 = False
        brackets2 = False
        flagLF = False # ]の出力で直後に改行が必要な時にTrue
        dim1 = 1 if self.shapeR[1] == 0 else self.shapeR[1]
        dim2 = 1 if self.shapeR[2] == 0 else self.shapeR[2]
        bS1 = "" if self.shapeR[1] == 0 else "["
        bS2 = "" if self.shapeR[2] == 0 else "["
        bE1 = "" if self.shapeR[1] == 0 else "]"
        bE2 = "" if self.shapeR[2] == 0 else "]"
        start5line = ""
        s = ""
        countLF = 0
        s += f"{self.shapeR[0]}, {self.shapeR[1]}, {self.shapeR[2]}\n"
        for i in range(len(self.array)+1):
            s0 = i % self.shapeR[0] == 0
            s1 = i % (self.shapeR[0] * dim1) == 0
            s2 = i % (self.shapeR[0] * dim1 * dim2) == 0
            if s0 and brackets0:
                s += "]"
                brackets0 = False
                flagLF = True
            if s1 and brackets1:
                s += bE1
                brackets1 = False
            if s2 and brackets2:
                s += bE2
                brackets2 = False
            if i == len(self.array): break
            if flagLF:
                s += "\n"
                countLF += 1
                if countLF == _MAX_LINE:
                    start5line = s # 先頭の5行まで記憶
                    s = ""
                elif countLF >= _MAX_LINE and s.count('\n') >= _MAX_LINE: 
                    i_LF = s.find('\n')
                    s = s[i_LF+1:] # _MAX_LINE行を超えないようにsの内容制御
                flagLF = False
            if s2 and not brackets2:
                s += bS2
                brackets2 = True
            if s1 and not brackets1:
                s += bS1
                brackets1 = True
            if s0 and not brackets0:
                s += "["
                brackets0 = True
            s += f" {self.array[i]:8.4f} " # 要素の表示
        #
        if countLF >= _MAX_LINE * 2: s = start5line + "  .  .  .  .  .  .\n" + s
        else: s = start5line + s
        return s 
    #
    #
    def deepcopy(self) -> 'NumArray':
        # 自身のNumArrayを複製
        naC = NumArray(self.shapeR[0], self.shapeR[1], self.shapeR[2])
        for i in range(len(self.array)):
            naC.array[i] = self.array[i]
        return naC
    #
    #
    def add_scalar(self,  v: float) -> 'NumArray':
        #行列に対するスカラー加算した行列に変更
        for  i in range(len(self.array)): self.array[i] += v
        return self
    #
    #
    def mul_scalar(self, v:float) -> 'NumArray':
        #行列に対するスカラー乗算した行列に変更  
        for i in range(len(self.array)): self.array[i] *= v
        return self    
    #
    #
    def add_matrix(self, na:'NumArray') -> 'NumArray':
        #行列の各要素を加算した要素に変更
        if self.shapeR[0] != na.shapeR[0] or self.shapeR[0] != na.shapeR[0]:
            s = f"add_matrix:{self.shapeR[0]}!={na.shapeR[0]} or {self.shapeR[0]}!={na.shapeR[0]}"
            raise Exception(s)
        for i in  range(len(self.array)): self.array[i] += na.array[i]
        return self
    #
    #
    def mul_matrix(self, na:'NumArray') -> 'NumArray': 
        #行列の各要素を乗算した要素に変更
        if self.shapeR[0] != na.shapeR[0] or self.shapeR[0] != na.shapeR[0]:
            s = f"mul_matrix:{self.shapeR[0]}!={na.shapeR[0]} or {self.shapeR[0]}!={na.shapeR[0]}"
            raise Exception(s)
        for i in  range(len(self.array)): self.array[i] *= na.array[i]
        return self
    #
    #
    def exp(self)->'NumArray':
        # 要素全てで、ネイピア数eの累乗を求め、xの要素に設定
        for i in  range(len(self.array)): self.array[i] = math.exp(self.array[i])
        return self
    #
    #
    def  sigmoid(self)->'NumArray':
        # シグモイド関数 (arrayの全要素を0〜1.0に変換したNumArrayの生成)
        na = NumArray(self.shapeR[0], self.shapeR[1], self.shapeR[2])
        for i in range(len(self.array)):
            na.array[i] = 1/(1+math.exp(-self.array[i]))
        return na
    #
    #
    @classmethod
    def  sum_of_products(cls,a:'NumArray', k:int ,  b:'NumArray', n:int) -> float:
        # a行列のk行とb行列のn列において、各要素を積の総和を求める
        rtnv = 0
        for i in range(a.shapeR[0]):
            # print(f"a[{i}, {k}, 0] * b[{n}, {i}, 0]"); #Debug
            rtnv += a[i, k, 0] * b[n, i, 0]
        #
        return rtnv
    #
    #
    @classmethod
    def createParam(cls, param:float, dim1:int , dim2:int=1, dim3:int=0) -> 'NumArray':
        # paramの値の要素で、1次元をdim1、2次元をdim2、3次元をdim3にして、NumArrayを生成
        dim2 = 0 if dim2 < 0 else dim2
        dim3 = 0 if dim3 < 0 else dim3
        na = NumArray(dim1, dim2, dim3)
        for i in range(len(na.array)): na.array[i] = param
        na.shapeR[0] = dim1
        na.shapeR[1] = dim2
        na.shapeR[2] = dim3
        return na
    #
    #
    @classmethod
    def dot(cls , a:'NumArray',  b:'NumArray')-> 'NumArray': 
        # 行列の積 戻り値: a × b
        if a.shapeR[0] != b.shapeR[1]:
            msg = f"a dim1 size:{a.shapeR[0]} != b dim2 size:{b.shapeR[1]}"
            raise Exception(msg)
        #
        c = NumArray.createParam(0, b.shapeR[0], a.shapeR[1])
        row_size = 1 if c.shapeR[1] == 0 else c.shapeR[1]
        for row in range(row_size): # 行の繰り返し
            for column in range(c.shapeR[0]):  # 列の繰り返し
                c[column, row, 0] = NumArray.sum_of_products(a, row, b, column)
            #
        #
        return c
    #
    #
    def read(self, f:io.BufferedReader) -> 'NumArray':
        # fのファイルリーダーからNumArrayのバイナリーを読み取って、デシリアライズする
        self.shapeR=[1,1,1]
        self.shapeR[0]=struct.unpack("<i", f.read(4))[0] # リトルエンディアンの32bit整数を読み込む
        self.shapeR[1]=struct.unpack("<i", f.read(4))[0] # リトルエンディアンの32bit整数を読み込む
        self.shapeR[2]=struct.unpack("<i", f.read(4))[0] # リトルエンディアンの32bit整数を読み込む
        self.array=[0.0]*self.shapeR[0]*(1 if self.shapeR[1]==0 else self.shapeR[1])*(1 if self.shapeR[2]==0 else self.shapeR[2])
        #print(f"self.shapeR:{self.shapeR}, len(self.array): {len(self.array)}")
        #
        # i=0
        # while(i<len(self.array)):
        #     bin=f.read(4)
        #     if(not bin): break
        #     self.array[i] = struct.unpack("<f", bin)[0]# リトルエンディアンの32bit浮動小数点数デコード
        #     #print(f"self.array[{i}]:{self.array[i]:10.5f}")
        #     i+=1
        #
        # ########上記の速度は遅かったの一括読み取りの次へ変更 20250731
        # bin = f.read(len(self.array) * 4)
        # self.array[:] = struct.unpack("<" + "f" * len(self.array), bin)
        #
        # ########上記は余計な記憶域binのサイズが大きいので次に変更 0801、(速度は前述とあまり変わらない)
        buf = bytearray(4)  # 再利用可能なバッファ
        i = 0
        while i < len(self.array):
            n = f.readinto(buf)
            if n < 4: break
            self.array[i] = struct.unpack("<f", buf)[0]
            i += 1
    #
    #
    def write(self, f:io.BufferedWriter):
        # NumArrayをシリアライズして、fのファイルライターが書き出す
        f.write(struct.pack("<i", self.shapeR[0]))# リトルエンディアンの32bit整数を書き込む
        f.write(struct.pack("<i", self.shapeR[1]))# リトルエンディアンの32bit整数を書き込む
        f.write(struct.pack("<i", self.shapeR[2]))# リトルエンディアンの32bit整数を書き込む
        size=self.shapeR[0]*(1 if self.shapeR[1]==0 else self.shapeR[1])*(1 if self.shapeR[2]==0 else self.shapeR[2])

        # i=0
        # while(i<len(self.array[:size])):
        #     f.write(struct.pack("<f", self.array[i] ))# リトルエンディアンの32bit浮動小数点数を書き込む
        #     #print(f"{self.array[i]:10.5f}")
        #     i+=1
        #
        # (20250730) 上記のファイル書き込み繰り返しでは遅過ぎるので、次のように、
        # bufのバイト配列に、浮動小数点数を4バイトずつ格納する繰り返し後、一括で書き込みするように変更
        i=0
        buf = bytearray(size*4)
        while(i<size):
            struct.pack_into('<f', buf, i * 4, self.array[i] )  # リトルエンディアンの32bit浮動小数点数を格納
            # if(i % 1000 == 0): print(f"[{i}]{self.array[i]:10.5f}")
            i+=1
        #
        f.write(buf)

    #
    #
    @classmethod
    def T(cls, na: 'NumArray' )->'NumArray':
        # 転置行列 行列の行と列を入れ替えた行列としてNumArrayの生成して返す。
        y = na.deepcopy()
        y.shapeR[0] = na.shapeR[1]
        y.shapeR[1] = na.shapeR[0]
        y.shapeR[2] = 0
        nRow = 1 if na.shapeR[1] == 0 else  na.shapeR[1]
        nColumn = 1 if na.shapeR[0] == 0 else na.shapeR[0]
        idx = 0
        for col in range(nColumn):
            for row in range(nRow):
                y.array[idx] = na[col, row, 0]
                idx+=1
            #
        #
        return y
    #
    #
    @classmethod
    def sum(cls,  x:'NumArray', axis:int = -1)-> 'NumArray':
        # 集計する。axis=-1の場合、y.array[0] に合計が設定される。  
        nRow = 1 if x.shapeR[1] == 0 else x.shapeR[1]
        nColumn = 1 if x.shapeR[0] == 0 else x.shapeR[0]
        sumV = 0
        if axis == -1: # 全合計
            for i in range(len(x.array)): sumV += x.array[i]
            y = NumArray(1)
            y.array[0] = sumV 
        elif axis == 0: # 同列の各要素を集計 
            y = NumArray(nColumn)
            for column in range(nColumn):
                for row in range(nRow):
                    y.array[column] += x[column, row, 0]
                #
            #
        elif axis == 1: # 同行の各要素の集計
            y = NumArray(nRow)
            for row in range(nRow):
                for column in range(nColumn):
                    y.array[row] += x[column, row, 0]
                #
            #
        return y
    #
    #
    @classmethod
    def max(cls, x:'NumArray', axis:int = -1)->'NumArray':
        # 集計する。axis=-1の場合、y.array[0] に最大値が設定される。
        nRow = 1 if x.shapeR[1] == 0 else x.shapeR[1]
        nColumn = 1 if x.shapeR[0] == 0 else x.shapeR[0]
        maxV =  -3.402823e+38
        if axis == -1:
            for i in range(len(x.array)):
                if x.array[i]> maxV: maxV= x.array[i]
            y = NumArray(1)
            y.array[0] = maxV
        elif axis == 0: # 同列の各要素で求める
            y = NumArray(nColumn)
            for column in range(nColumn):
                y.array[column] =  -3.402823e+38
                for row in range(nRow):
                    if x[column, row, 0] > y.array[column]:
                        y.array[column] = x[column, row, 0]
                #
            #
        elif axis == 1: # 同行の各要素で求める
            y = NumArray(nRow)
            for row in range(nRow):
                y.array[row] =  -3.402823e+38
                for column in range(nColumn):
                    if x[column, row, 0] > y.array[row]:
                        y.array[row]  = x[column, row, 0]
                #
            #
        return y
    #
    #
    def createLineAt(self, idx: int)->'NumArray':
        # 自身が2次配列、または3次配列の場合に、idx行を抽出した1次または2次配列を返す。
        if self.shapeR[1] == 0:
            raise Exception(f"This method is not available for this structure self.shapeR:{self.shapeR}")
        if self.shapeR[1] <= idx:
            raise Exception(f"not available createLineAt({idx}) because self.shapeR[0]:{self.shapeR[0]}")
        if self.shapeR[2] == 0:
            naA = NumArray(self.shapeR[0], 0, 0) # 1次元配列を抽出
            for i in range(self.shapeR[0]): naA[i,0,0]=self[i, idx, 0]
        else:
            naA = NumArray(self.shapeR[0], self.shapeR[1], 0) # 2次元配列を抽出
            for i2 in range(self.shapeR[1]): 
                for i in range(self.shapeR[0]):
                    naA[i,i2,0]=self[i, i2, idx]
            #
        return naA
    #
    @classmethod
    def softmax(cls, x:'NumArray')->'NumArray':
        # ソフトマックス関数:xを確率的な値に変換する。
        # x要素群のごちゃごちゃした数字を、最終総和が100%(1.0)になる比率に変えてくれる関数
        maxX = NumArray.max(x) # maxX.array[0] に最大値
        x =x.deepcopy()
        x.add_scalar(- maxX.array[0]) # オーバーフロー対策
        # print(f"- maxX.array[0]:{-maxX.array[0]}, x:{x}")
        naY = x.exp()
        na_sum = NumArray.sum(naY).array[0]
        naY.mul_scalar(1 / na_sum)
        return naY
    #
    def setNumpy(self, npA):# これは、numpy とのコンバートメソッド-で、利用時は「import numpy」が必要
        ''' numpy のnpA で自身を初期化する '''
        self.array=list(npA.flatten())
        self.shapeR=[0,0,0]
        if npA.ndim == 1 : self.shapeR[0]=npA.shape[0]
        elif npA.ndim == 2 :
            self.shapeR[0]=npA.shape[1]
            self.shapeR[1]=npA.shape[0]
        elif npA.ndim == 3 :
            self.shapeR[0]=npA.shape[2]
            self.shapeR[1]=npA.shape[1] 
            self.shapeR[2]=npA.shape[0]
        #
    #
    def getNumpy(self):# これは、numpy とのコンバートメソッド-で、利用時は「import numpy」が必要
        ''' 自身のデータに対応するnumpy を生成して返す '''
        npA=np.array(self.array)
        if self.shapeR[1]!=0:
            if self.shapeR[2]!=0: npA=npA.reshape(self.shapeR[2], self.shapeR[1], self.shapeR[0]) #3次元
            else: npA=npA.reshape(self.shapeR[1], self.shapeR[0]) # 2次元
        #
        return npA
    #
    #
    # 20250731 追加
    @classmethod
    def createGaussian(cls, dim1:int, dim2:int=1, dim3:int=1, mean:float=0, stdDev:float=1)->'NumArray':
        # 正規分布乱数(ガウス分布乱数)で初期化したNumArrayの生成
        dim2 = 0 if dim2 < 0 else dim2
        dim3 = 0 if dim3 < 0 else dim3
        na = NumArray(dim1, dim2, dim3)
        na.shapeR[0] = dim1
        na.shapeR[1] = dim2
        na.shapeR[2] = dim3
        for i in range(len(na.array)): 
            na.array[i] = normal_random(mean, stdDev)
        #
        return na;

    # 20250801 追加
    # 交差エントロピー誤差(Cross-Entropy Loss)を得る。正解ラベルと一致しているかを評価します。
    @classmethod
    def cross_entropy_error(cls, y:'NumArray',  t:'NumArray')->float:
        # tが正解ラベル(one-hot表現)で、yがモデルの予測確率 yがtに近ければこの絶対値が小さい値になる。(最小値はゼロ)
        # print(f"y:{y}\nt:{t}");
        rtnV = 0
        for i in range( len(y.array) ):
            rtnV += -math.log(y.array[i]) * t.array[i]
        #
        return rtnV
    #

# 20250731 追加
# 正規分布乱数(ガウス分布乱数)
def normal_random(mu=0, sigma=1):
    # [0,1) の一様乱数を2つ生成
    u1 = random.random()
    u2 = random.random()
    # Box-Muller変換
    z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
    # 平均mu, 標準偏差sigmaにスケーリング
    return mu + sigma * z

# xのNumArrayより、第1次の要素をidxFromの位置からnumb個をコピーして、それを返す
def copyNumArray(x:NumArray, numb:int, idxFrom:int)->NumArray:
    x2=NumArray(x.shapeR[0], numb)
    idx = x.shapeR[0] * idxFrom # ここからコピー
    idxUntil = idx + x.shapeR[0] * numb # これ未満までコピー
    i = 0
    while(idx < idxUntil):
        x2.array[i] = x.array[idx]
        i+=1
        idx+=1
    return x2

NumArrayクラス メソッドの使い方の例

参考にしたC#のコードとの違い

C#では、na=NumArray.create(4,3,2) で1次元が4要素、2次元が3要素、2次元が3要素のNumArrayオブジェクトを生成できます。
このようにC#では、NumArrayクラスの static メソッドのcreateで、NumArrayを生成していましたが、 MicroPythonではコンストラクタで生成する方法にしています。
つまりMicroPythonでは、na=NumArray(4,3,2)と生成します。

そして、C#では配列要素の参照を返すFPAtメソッで、次のように配列要素を参照や変更をできるようにしています。
[i1, i2, i3]の位置の浮動小数点(Floating Point at [i1, i2, i3])を、12.3fにする場合、
na.FPAt(i1, i2, i3)=12.3f;と表現できる。
対してPythonでは、参照を返す能力はありません。
そこで、__getitem__(self, index)や__setitem__(self, index, value)の定義で対応しました。
これにより、na[i1, i2, i3]=12.3 の表現や、print(na[i1, i2, i3])と使えるようにしました。
以上の表現を、次のコードで検証しています。(3次元配列の各要素に、0〜23の値を設定してから、それらを表示している)
na=NumArray(4,3,2) // float要素が4個、それが3個あり、それが2個並ぶ3次元配列
v=0
for i3 in range(2):
    for i2 in range(3):
        for i1 in range(4):
            na[i1,i2,i3]=v // __setitem__が呼び出されている。
            v+=1
for i3 in range(2):
    for i2 in range(3):
        for i1 in range(4):
            print(f"{na[i1,i2,i3]}") # 0から23のデータが列挙できる。(__getitem__が呼び出されている)
なお、na[i1, i2, i3] の表現は、i1が1次元目の添え字、i2が2次元目、i3がの3次元目の添え字を意味します。 これは、一般の配列の順番の逆になっています。

createByArray, __setitem__(self, index, value), __str__(self) メソッドの検証 (対応するC#のコード

学習や判定素材にbyte列などの配列を使うケースで、
その配列からNumArrayオブジェクトを容易に生成して取得するのがcreateByArrayメソッドです。
C#では、createByBytesと、createByArrayに分けて定義していましたが、Pythonでは一つのcreateByArrayにまとめている。
(理由は、Pythonのリスト操作は、任意の型の要素で同じようにできるからです。)
createByArrayメソッドは、dim1とをdim2を指定すると3次元配列になります。(dim1が1次元目のサイズです)
この場合、bufのサイズ/(dim1 * dim2)が、3次元目のサイズとして自動的に設定されます。
2次元配列にする場合、dim2の指定を省略するか0にします。(bufのサイズ/dim1が2次元目のサイズとして自動的に設定されます。)
1次元配列の場合、dim1とdim2の指定を省略するか0にします。(bufのサイズが1次元目のサイズとして自動的に設定されます。)

下記は、 byte配列の各要素を使って、createByBytesにより次元サイズ指定でNumArrayを生成し、ToStringに相当する__str__で表示確認後、 各要素を1/10倍して表示させています。
(__str__の表示は、Numpyの表示をチョット真似て、一部を省略した表示ができるようにしています。)

buf = bytes([b for b in range(3 * 2 * 6)]) # byte列の生成
na=NumArray.createByArray(buf, 6,2) # 1次元目サイスが6,2次元目が2の3次配列にしている。(3次元目は自動的に残りのサイズで、3が設定)
print(na)  # __str__(self) の確認
for i3 in range(na.shapeR[2]):
    for i2 in range(na.shapeR[1]):
        for i1 in range(na.shapeR[0]):
            na[i1, i2, i3] /= 10; #変更
            print(f"na[{i3}][{i2}][{i1}]:{na[i1, i2, i3]}") # 要素の表示

実行結果
6, 2, 3
[[[   0.0000   1.0000   2.0000   3.0000   4.0000   5.0000]
[   6.0000   7.0000   8.0000   9.0000  10.0000  11.0000]]
[[  12.0000  13.0000  14.0000  15.0000  16.0000  17.0000]
[  18.0000  19.0000  20.0000  21.0000  22.0000  23.0000]]
[[  24.0000  25.0000  26.0000  27.0000  28.0000  29.0000]
[  30.0000  31.0000  32.0000  33.0000  34.0000  35.0000]]]
na[0][0][0]:0.0
na[0][0][1]:0.1
na[0][0][2]:0.2
・・・省略


deepcopy、add_scalar ,add_matrix 、mul_scalar、mul_matrixメソッドの検証

それぞれ、ディープコピー、引数の値を要素に加算、乗算、各要素ごとの加算と乗算のメソッドを利用した例

na=NumArray.createByArray([1,2,3, 4,5,6], 3) # 1次元目が3,2次元目省略で、2次元配列となり、2次元のサイスは残り情報から自動的に2になっている)
na2=na.deepcopy() # ディープコピー
print(na2) # 表示
na2.add_scalar(-1) # 全要素に -1 を加算
print(na2) # 表示
na2.mul_scalar(10) # 全要素を 10 倍へ
print(na2) # 表示
na2.add_matrix(na) # na2 とnaで行列として加算(各要素で加算)
print(na2) # 表示
na2.mul_matrix(na) # na2 とnaで、各要素で積算
print(na2) # 表示
print(na) # 最初の変数に変化が無いことを確認
実行結果
3, 2, 0				ディープコピーしたオブジェクトの表示
[[   1.0000   2.0000   3.0000]	
[   4.0000   5.0000   6.0000]]		
3, 2, 0				全要素に -1 を加算
[[   0.0000   1.0000   2.0000]
[   3.0000   4.0000   5.0000]]
3, 2, 0				全要素を 10 倍
[[   0.0000  10.0000  20.0000]
[  30.0000  40.0000  50.0000]]
3, 2, 0				na2 とnaで行列として加算
[[   1.0000  12.0000  23.0000]
[  34.0000  45.0000  56.0000]]
3, 2, 0				na2 とnaで、各要素で積算
[[   1.0000  24.0000  69.0000]
[ 136.0000 225.0000 336.0000]]
3, 2, 0
[[   1.0000   2.0000   3.0000]
[   4.0000   5.0000   6.0000]]


read ,write メソッドのシリアライズ検証と、sigmoidメソッドの検証 (対応するC#のコード

3要素が2行の2次元構成のNumArrayを生成後、そのオブジェクトをシリアライズしてファイル化した後、 それを復元して表示し、確認後にシグモイド関数を検証している例です。

# dim1を指定しなければ1次元配列、dim1だけ指定すると2次元、dim1とdim2を0以外で指定すると3次元になる
na = NumArray.createByArray([ -2, -1, -0.5, 0, 3, 6 ],3) #/配列よりNumArrayを生成
# 上記で、配列のサイズから、1次元のサイズだけ指定すれば、2次元が自動的に分かり、3個が2列の2次元配列になる。

print(f"na = {na}") # 確認表示
with open("test.bin", mode='wb') as fr: na.write(fr) # シリアライズしてファイル化

naA=NumArray()
with open("test.bin", mode='rb') as fr: naA.read(fr) # デシリアライズ

print(f"naA = {naA}") # 復元でータが同じかの確認表示

naSigmoid = naA.sigmoid(); # データの範囲を0から1の値に変更するシグモイド

print(f"naSigmoid = {naSigmoid}") # シグモイドの確認表示
実行結果
na = 3, 2, 0
[[  -2.0000  -1.0000  -0.5000]
[   0.0000   3.0000   6.0000]]
naA = 3, 2, 0
[[  -2.0000  -1.0000  -0.5000]
[   0.0000   3.0000   6.0000]]
naSigmoid = 3, 2, 0
[[   0.1192   0.2689   0.3775]
[   0.5000   0.9526   0.9975]]


dot 行列の積メソッド検証 (対応するC#のコード

dotメソッドの中で、createParamメソッドと、sum_of_productsメソッドを利用しており、それも検証していることになります。

naA =  NumArray.createByArray([ 1,2,3,4,5,6,7,8,9,10,11,12 ], 3) # 4行3列
print(f"naA={naA}")

naB =  NumArray.createByArray([ 1,2,3,4,5,6 ], 2) # 4行3列
print(f"naB={naB}")

naC = NumArray.dot(naA, naB) # 行列の積
print(f"\nnaA * naB = naC:{naC}")

naX = NumArray.createByArray([ 1, 2, 3, 4 ]) # 1行4列
print(f"naX:{naX}")

naY = NumArray.dot(naX, naC) # 行列の積
print(f"\nnaC * naX = naY:{naY}")
実行結果
naA=3, 4, 0
[[   1.0000    2.0000    3.0000 ]
[   4.0000    5.0000    6.0000 ]
[   7.0000    8.0000    9.0000 ]
[  10.0000   11.0000   12.0000 ]]
naB=2, 3, 0
[[   1.0000    2.0000 ]
[   3.0000    4.0000 ]
[   5.0000    6.0000 ]]

naA * naB = naC:2, 4, 0
[[  22.0000   28.0000 ]		上記2つの積
[  49.0000   64.0000 ]
[  76.0000  100.0000 ]
[ 103.0000  136.0000 ]]
naX:4, 0, 0
[   1.0000    2.0000    3.0000    4.0000 ]

naC * naX = naY:2, 0, 0
[ 760.0000  1000.0000 ]		上記2つの積



exp()メソッド 検証 (対応するC#のコード

C#のコードではstaticメソッドで引数のオブジェクトを変更せずに、算出で得られる要素を持つ新しいNumArrayを生成して返していた。
対してメモリが少ない環境が予想されるMicroPythonでは、不必要なオブジェクトの生成は避けるべきという観点から、 生成せずに自身のNumArrayの要素に算術結果を記憶させる方法に変更した。(softmaxメソッドで修正の必要あり)
(よって自身の内容が変わることを避けたい場合は、直前でdeepcopyで複製して、それを使う必要があります。)
naX = NumArray.createByArray( [ 2.5, 4.0, 3.5 ] )
naY = naX.deepcopy()
print(f"NumArrayのnaX:{naX}\n NumArray.exp(naX):{naY.exp()}")
実行結果
NumArrayのnaX:3, 0, 0
[   2.5000    4.0000    3.5000 ]
 NumArray.exp(naX):3, 0, 0
[  12.1825   54.5982   33.1155 ]



NumArray T(NumArray) のclassmethod検証 (対応するC#のコード

C#のコードではインスタンスメソッドでしたが、新しいNumArrayを生成して返すので、クラスメソッドに変更しました。
それに伴い、転置の対象となる行列を引数で指定します。
# 転置行列検証
naA = NumArray.createByArray( [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], 3)
print(f"NumArrayのnaX:{naA}\n")
t = NumArray.T(naA) # 転置行列を得る。
print(f"NumArrayのt:{t}\n")
実行結果
NumArrayのnaX:3, 4, 0
[[   1.0000    2.0000    3.0000 ]
[   4.0000    5.0000    6.0000 ]
[   7.0000    8.0000    9.0000 ]
[  10.0000   11.0000   12.0000 ]]

NumArrayのt:4, 3, 0
[[   1.0000    4.0000    7.0000   10.0000 ]
[   2.0000    5.0000    8.0000   11.0000 ]
[   3.0000    6.0000    9.0000   12.0000 ]]



sum(cls, x:'NumArray', axis:int = -1)-> 'NumArray'のclassmethod検証 (対応するC#のコード

集計するで、axis=-1の場合、戻り値のarray[0] に合計が設定されます。
axisが0で同列の各要素で集計し、axisが1で同列の各要素で集計したNumArrayを生成して返します。
naX = NumArray.createByArray( [ 0,1,2,3,4,5,6,7,8,9,10,11 ], 3)
print(f"NumArrayのnaX:{naX}")
print(f"NumArray.sum(naX):{NumArray.sum(naX)}") # naX.array[0] が結果となります。
print(f"NumArray.sum(naX, 0):{NumArray.sum(naX,  0)}")  # 同列の各要素を集計
print(f"NumArray.sum(naX, 1):{NumArray.sum(naX,  1)}")  # 同行の各要素の集計
実行結果
NumArrayのnaX:3, 4, 0
[[   0.0000    1.0000    2.0000 ]
[   3.0000    4.0000    5.0000 ]
[   6.0000    7.0000    8.0000 ]
[   9.0000   10.0000   11.0000 ]]
NumArray.sum(naX):1, 0, 0
[  66.0000 ]
NumArray.sum(naX, 0):3, 0, 0
[  18.0000   22.0000   26.0000 ]
NumArray.sum(naX, 1):4, 0, 0
[   3.0000   12.0000   21.0000   30.0000 ]




max(cls, x:'NumArray', axis:int = -1)->'NumArray'のクラスメソッド 検証 (対応するC#のコード

集計するで、axis=-1の場合、戻り値のarray[0] に最大値が設定されます。
axisが0で同列の各要素の最大値、axisが1で同列の各要素の最大値が要素となるNumArrayを返します。
naX = NumArray.createByArray( [ 0,1,2,3,4,5,6,7,8,9,10,11 ], 3)
print(f"NumArrayのnaX:{naX}")
print(f"NumArray.max(naX):{NumArray.max(naX)}") # naX.array[0] が結果となります。
print(f"NumArray.max(naX, 0):{NumArray.max(naX,  0)}")  # 同列の各要素の最大
print(f"NumArray.max(naX, 1):{NumArray.max(naX,  1)}")  # 同行の各要素の最大
実行結果
NumArrayのnaX:3, 4, 0
[[   0.0000    1.0000    2.0000 ]
[   3.0000    4.0000    5.0000 ]
[   6.0000    7.0000    8.0000 ]
[   9.0000   10.0000   11.0000 ]]
NumArray.max(naX):1, 0, 0
[  11.0000 ]
NumArray.max(naX, 0):3, 0, 0
[   9.0000   10.0000   11.0000 ]
NumArray.max(naX, 1):4, 0, 0
[   2.0000    5.0000    8.0000   11.0000 ]


createLineAt(self, idx: int)->'NumArray': のクラスメソッド 検証 (対応するC#のコード

自身が2次配列、または3次配列の場合に、idx行を抽出した1次または2次配列を返す。
C言語やC#のメソッド操作に合わせて、Pythonもメソッドで取得する方法にしています。
idxTest = 2 # createLineAt(idxTest)によるidxTest行目抽出の実験
naA = NumArray.createByArray( [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], 3)
print(f"NumArrayのnaA:{naA}")
naB = naA.createLineAt(idxTest)
print(f"NumArrayのnaB:{naB}")
naC = NumArray.createByArray( [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], 2, 3)
print(f"\n{naC}\n[1]=createLineAt(1)={naC.createLineAt(1)}")
実行結果
NumArrayのnaA:3, 4, 0
[[   1.0000    2.0000    3.0000 ]
[   4.0000    5.0000    6.0000 ]
[   7.0000    8.0000    9.0000 ]
[  10.0000   11.0000   12.0000 ]]
NumArrayのnaB:3, 0, 0
[   7.0000    8.0000    9.0000 ]

2, 3, 2
[[[   1.0000    2.0000 ]
[   3.0000    4.0000 ]
[   5.0000    6.0000 ]]
[[   7.0000    8.0000 ]
[   9.0000   10.0000 ]
[  11.0000   12.0000 ]]]
[1]=createLineAt(1)=2, 3, 0
[[   7.0000    8.0000 ]
[   9.0000   10.0000 ]
[  11.0000   12.0000 ]]
上記が目指したNumpyを利用するコードは次のようになります。
2次配列から添え字指定で1次配列を得る操作や、
3次配列から添え字指定で2次配列を得る操作の例。
import numpy as np
idxTest = 1
naA = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ).reshape(4, 3)
print(f"NumpyのnaA:{naA}")
naB = naA[idxTest]
print(f"\n  NumpyのnaB:{naB}") # 上記の2の要素の1次配列を得る
naC = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ).reshape(2, 3, 2) # 上記の1の要素の2次配列を得る
print(f"\n{naC}\n\n  [1]={naC[1]}")
この実行結果
NumpyのnaA:[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

  NumpyのnaB:[4 5 6]		得られた2の要素の1次配列

[[[ 1  2]
  [ 3  4]
  [ 5  6]]

 [[ 7  8]
  [ 9 10]
  [11 12]]]

  [1]=[[ 7  8]			得られた1の要素の2次配列
 [ 9 10]
 [11 12]]


softmax(cls, x:'NumArray')->'NumArray'のクラスメソッド 検証 (対応するC#のコード

xを確率的な値に変換するソフトマックス関数(1次元にのみ対応) x要素群のごちゃごちゃした数字を、最終総和が100%(1.0)になる比率に変えてくれる関数
naX = NumArray.createByArray( [ 2.5, 4.0, 3.5 ] ) #例えば、「人」、「ねこ」、「イヌ」の判定度合い
print(f"NumArrayのnaX:{naX}")
naY = NumArray.softmax(naX)  # 確率情報に変換
print(f"naY=NumArray.softmax(naX):{naY}")
実行結果
NumArrayのnaX:3, 0, 0
[   2.5000    4.0000    3.5000 ]
naY=NumArray.softmax(naX):3, 0, 0
[   0.1220    0.5465    0.3315 ]