TOP PAGE

DNNを目指したライブラリで、Numpyの一部の機能などを実現するNumArrayクラスです。
またUnityと連係するコードは、こちらで紹介しています。
メソッドの使い方は後述しています。

NumArrayクラス

using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using System.Buffers.Binary;//Unity 2021.2以降で使える BinaryPrimitives.WriteInt32LittleEndian用

/*
int[,] array2D = {
    {1, 2, 3},
    {4, 5, 6}
};
上記で6の要素は、 2次元の添え字が 1 で、1次元目の添え字が2であるが、
int[] array1D = {1, 2, 3,4, 5, 6};の管理で、
array1D[ 1 * 1次元サイス + 2 ] により2次元操作ができ、
これ応用しして、1次元で、3次元もで配列を管理する考えが、このNumArrayクラス
 */

// Numpyの機能の限定した代替え用クラスで、 1〜3次元の構造変更ができ、行列演算を行うクラス
//(利用したいNumpyの機能の絞って、似た能力のメソッドを実装している)
public class NumArray 
{
    public static System.Random random;
    static NumArray()  //C#の静的コンストラクター(Javaの静的イニシャライザ(staticブロック)相当)
    {
        random = new System.Random(123); //乱数シード
    }

    // Box-Muller法:2つの独立な標準正規分布に従う乱数を生成するアルゴリズム
    public static double NormalDistribution(double mean=0, double stdDev=1)// 平均値と標準偏差を指定
    {
        double u1 = 1.0 - random.NextDouble();
        double u2 = random.NextDouble();
        double randStdNormal = System.Math.Sqrt(-2.0 * System.Math.Log(u1)) * System.Math.Sin(2.0 * System.Math.PI * u2);
        return mean + stdDev * randStdNormal;
    }

    public int []shapeR= { 1, 1, 1 };  // 下記arrayの1次元、2次元、3次元の要素数 (それぞれの次元のサイズ)
    // numpyのshapeは、次元の大きい方から1次元まで各次元のサイズがタプルで得られる情報で、その逆の並びになる
    // shapeR[2]が0の場合、次元の大きさが2で、shapeR[1]も0であれば一次元の配列として扱う。
    public float [] array;  // 多次元配列の要素は、全てこの一次配列に記憶

    // array[i3, i2, i1]のような操作対象要素の参照返す。
    //  [i3, i2, i1]の位置の浮動小数点(Floating Point at [i3, i2, i1])を、FPAt(i3, i2, i1)と表現できるようにするメソッド
    public ref float FPAt(int i1, int i2 = 0, int i3 = 0)
    {
        int dim1 = shapeR[0];// 1次元サイズ
        int idx = i3 * (dim1 * shapeR[1]) + i2 * dim1 + i1;// 1次元の配列に相当する添え字を求める
        //Debug.Log($"array[{idx}]");
        return ref array[idx];
    }

    // 1次元、2次元、3次元のサイズだけを指定した初期化生成
    public static NumArray create(int dim1, int dim2 = -1, int dim3 = -1)
    {
        dim2 = dim2 < 0 ? 0 : dim2;
        dim3 = dim3 < 0 ? 0 : dim3;
        NumArray na = new NumArray();
        na.array = new float[dim1 * (dim2 == 0 ? 1 : dim2) * (dim3 == 0 ? 1 : dim3)];
        na.shapeR[0] = dim1; na.shapeR[1] = dim2; na.shapeR[2] = dim3;
        return na;
    }

    // 自身のNumArrayを複製
    public NumArray deepcopy()
    {
        NumArray naC = NumArray.create(this.shapeR[0], this.shapeR[1], this.shapeR[2]);
        for(int i=0; i < naC.array.Length; i++)
        {
            naC.array[i] = this.array[i];
        }
        return naC;
    }

    // bufの各要素を、floatに変換してarrayに設定し、引数で、次元の配列構造に設定する生成する特殊関数。
    // [0,1,2,3,4,5,6,7,8,9,10,11]のbuf 、dim1=3で、
    // dim2=0の実行すると、[[0,1,2],[3,4,5],[6,7,8],[9,10,11]]の4×3の配列イメージになり、
    // dim2=2の実行すると、[ [[0,1,2],[3,4,5]],  [[6,7,8],[9,10,11]] ]の2×2×3の配列イメージになる
    // dim1を指定しなければ1次元(dim1=0)、dim1だけ指定すると2次元、dim1とdim2を0以外で指定すると3次元になる
    public static NumArray createByBytes(byte[] buf, int dim1=0, int dim2 = 0)
    {
        dim2 = dim2 < 0 ? 0 : dim2;
        NumArray na = new NumArray();
        int n = buf.Length;
        na.array = new float[n];
        for (int i = 0; i < n; i++) na.array[i] = buf[i];
        if (dim1 == 0)
        {
            na.shapeR[0] = n; // 1次元
            na.shapeR[1] = na.shapeR[2] = 0;
        }
        else // 2次元または3次元
        {
            na.shapeR[0] = dim1;
            if( dim2 == 0)
            {
                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;
    }

    // bufの各要素を、floatに変換してarrayに設定し、引数で、次元の配列構造に設定する生成する特殊関数。
    public static NumArray createByArray<T>(T[] buf, int dim1=0, int dim2 = 0)
    {
        dim2 = dim2 < 0 ? 0 : dim2;
        NumArray na = new NumArray();
        int n = buf.Length;
        na.array = new float[n];
        for (int i = 0; i < n; i++)
        {
            T t = buf[i];
            if (t is int)
                na.array[i] = (int)(object)t;
            else if (t is float)
                na.array[i] = (float)(object)t;
            else if (t is double)
                na.array[i] = (float)(double)(object)t;
            else throw new System.Exception("NumArray.createByArray:Unsupported type");
        }
        if (dim1 == 0)
        {
            na.shapeR[0] = n; // 1次元
            na.shapeR[1] = na.shapeR[2] = 0;
        }
        else // 2次元または3次元
        {
            na.shapeR[0] = dim1;
            if (dim2 == 0)
            {
                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;
    }

    // paramの値で、1次元をdim1、2次元をdim2、3次元をdim3にして、NumArrayを生成
    public static NumArray createParam(float param, int dim1, int dim2=1, int dim3=0)
    {
        dim2 = dim2 < 0 ? 0 : dim2;
        dim3 = dim3 < 0 ? 0 : dim3;
        NumArray na = new NumArray();
        na.array = new float[dim1 * (dim2==0?1: dim2) * (dim3==0?1: dim3)];
        for (int i = 0; i < na.array.Length; i++) na.array[i] = param;
        na.shapeR[0] = dim1;
        na.shapeR[1] = dim2;
        na.shapeR[2] = dim3;
        return na;
    }

    // 正規分布乱数(ガウス分布乱数)で初期化したNumArrayの生成
    public static NumArray createGaussian(int dim1, int dim2 = 1, int dim3 = 1, double mean = 0, double stdDev = 1)
    {
        dim2 = dim2 < 0 ? 0 : dim2;
        dim3 = dim3 < 0 ? 0 : dim3;
        NumArray na = new NumArray();
        na.array = new float[dim1 * (dim2 == 0 ?1: dim2) * (dim3 == 0 ? 1 : dim3)];
        na.shapeR[0] = dim1; na.shapeR[1] = dim2; na.shapeR[2] = dim3;
        for(int i=0; i < na.array.Length; i++)
        {
            na.array[i] = (float)NormalDistribution(mean, stdDev);
        }
        return na;
    }

    // 自身が2次配列、または3次配列の場合に、idx行を抽出した1次または2次配列を返す。
    public NumArray createLineAt(int idx)
    {
        if (this.shapeR[1] == 0)
            throw new System.Exception("createLineAtは、1次配列で、NumArrayAtメソッドは使えません。");
        if (this.shapeR[1] <= idx)
            throw new System.Exception($"createLineAt({idx})が、shapeR[1]:{this.shapeR[0]}の添え字範囲を超えました");
        NumArray naA = new NumArray();
        { }
        naA.shapeR[0] = this.shapeR[0];
        naA.shapeR[1] = this.shapeR[2];
        naA.shapeR[2] = 0;
        int size = naA.shapeR[0] * (naA.shapeR[1] == 0 ? 1 : naA.shapeR[1]) * (naA.shapeR[2] == 0 ? 1 : naA.shapeR[2]);
        naA.array = new float[size];
        int iA = 0;
        int iS = idx * this.shapeR[0];
        while (iA < size)
        {
            int iE = iS + this.shapeR[0];
            for (int i = iS; i < iE; i++)
            {
                naA.array[iA] = this.array[i];
                iA += 1;
            }
            iS += this.shapeR[0] * this.shapeR[1];
        }
        return naA;
    }

    public void add_scalar(float v)//行列に対するスカラー加算した行列に変更
    {
        for (int i = 0; i < this.array.Length; i++) this.array[i] += v;
    }

    public void add_matrix(NumArray na)//行列の各要素を加算した要素に変更
    {
        if(this.shapeR[0] != na.shapeR[0] || this.shapeR[0] != na.shapeR[0])
        {
            string s = $"add_matrix:{this.shapeR[0]}!={na.shapeR[0]} || {this.shapeR[0]}! {na.shapeR[0]}";
            throw new System.Exception(s);
        }
        for (int i = 0; i < this.array.Length; i++) this.array[i] += na.array[i];
    }

    public void mul_scalar(float v)//行列に対するスカラー乗算した行列に変更  
    {
        for (int i = 0; i < this.array.Length; i++) this.array[i] *= v;
    }

    public void mul_matrix(NumArray na)//行列の各要素を乗算した要素に変更
    {
        for (int i = 0; i < this.array.Length; i++) this.array[i] *= na.array[i];
    }

    // a行列のk行とb行列のn列において、各要素を積の総和を求める
    public static float sum_of_products(NumArray a, int k, NumArray b, int n)
    {
        float rtnv = 0;
        for(int i=0; i < a.shapeR[0]; i++)
        {
            //Debug.Log($"a.FPAt({i}, {k}, 0) * b.FPAt({n}, {i}, 0)");
            rtnv += a.FPAt(i, k, 0) * b.FPAt(n, i, 0);
        }
        return rtnv;
    }

    // 行列の積 戻り値: a × b
    public static NumArray dot(NumArray a, NumArray b)
    {
        if( a.shapeR[0] != b.shapeR[1])
        {
            string msg = $"aの1次元サイズ]{a.shapeR[0]}と、bの2次元サイズ]{b.shapeR[1]}が一致しません。";
            throw new System.Exception(msg);
        }
        NumArray c = NumArray.createParam(0, b.shapeR[0], a.shapeR[1]);
        int row_size = c.shapeR[1] == 0 ? 1 : c.shapeR[1];
        for (int row = 0; row < row_size; row++)// 行の繰り返し
        {
            for (int column = 0; column < c.shapeR[0]; column++)// 列の繰り返し
            {
                c.FPAt(column, row, 0) = sum_of_products(a, row, b, column);
            }
        }
        return c;
    }

    // このクラスのオブジェクトをファイルに書き込むシリアライズメソッド
    // this.shapeの3要素(int型)と、この要素数のthis.array内容のデータ(float)の並びをストリームに書き込みます。
    public void write(FileStream fileStream)
    {
        // リトルエンディアンで byte 配列に変換
        byte[] littleEndianBytes = new byte[sizeof(int)];
        BinaryPrimitives.WriteInt32LittleEndian(littleEndianBytes, (int)this.shapeR[0]);// 1次元サイズ
        fileStream.Write(littleEndianBytes);
        BinaryPrimitives.WriteInt32LittleEndian(littleEndianBytes, (int)this.shapeR[1]);// 2次元サイズ
        fileStream.Write(littleEndianBytes);
        BinaryPrimitives.WriteInt32LittleEndian(littleEndianBytes, (int)this.shapeR[2]);// 3次元サイズ
        fileStream.Write(littleEndianBytes);
        for (int i = 0; i < this.array.Length; i++)
        {
            byte[] buf = System.BitConverter.GetBytes((float)this.array[i]);
            if (System.BitConverter.IsLittleEndian == false)
            {   // BinaryPrimitives.WriteSingleLittleEndianが使えなかったので代替えコード
                System.Array.Reverse(buf);// リトルエンディアンへ反転
            }
            fileStream.Write(buf);
        }
    }

    // writeメソッドで書き込んだシリアライズデータから このクラスのオブジェクトを復元する。
    public void read(FileStream fileStream)
    {
        byte[] buf = new byte[sizeof(int)];
        for(int dim=0; dim<this.shapeR.Length; dim++)
        {
            for (int i = 0; i < buf.Length;) {
                int size = fileStream.Read(buf, i, buf.Length - i);//読み込み
                if (size <= 0) break;
                i += size;
            }
            try
            {
                this.shapeR[dim] = BinaryPrimitives.ReadInt32LittleEndian(buf);
            }
            catch (System.Exception e)
            {
                throw new System.Exception($"NumArray Read fail:{e}");
            }
        }
        this.array = new float[this.shapeR[0]* (this.shapeR[1]==0?1: this.shapeR[1]) * (this.shapeR[2]==0?1: this.shapeR[2])];
        buf = new byte[sizeof(float)];
        for (int idx = 0; idx < this.array.Length; idx++)
        {
            for (int i = 0; i < buf.Length;)
            {
                int size = fileStream.Read(buf, i, buf.Length - i);//読み込み
                if (size <= 0) break;
                i += size;
            }
            if (System.BitConverter.IsLittleEndian == false)
            {   // BinaryPrimitives.WriteSingleLittleEndianが使えなかったので代替えコード
                System.Array.Reverse(buf);// リトルエンディアンへ反転
            }
            this.array[idx]=System.BitConverter.ToSingle(buf, 0);// floatを復元
        }
    }


    // 指定位置のグレー手書き画像をRawImageに描画する時に使うColor32[]取得用
    public Color32[] getPixels(int width, int height, int idx)
    {
        // 画素配列を初期化
        idx *= (width * height);
        Color32 []pixels = new Color32[width * height];
        for (int y = height-1; y >= 0; y--)
        {
            for (int x = 0; x < width; x++)
            {
                byte v = (byte)(this.array[idx++] * 255);
                pixels[y * width + x] = new Color32(v, v, v, 255);// グレー表示
                pixels[y * width + x] = new Color32(v, v, v, 255);// グレー表示
            }
        }
        return pixels;
    }


    // シグモイド関数 (arrayの全要素を0〜1.0に変換したNumArrayの生成)
    public NumArray sigmoid()
    {
        NumArray na = NumArray.create(this.shapeR[0], this.shapeR[1], this.shapeR[2]);
        for (int i = 0; i < this.array.Length; i++) {
            na.array[i] = 1/(1+Mathf.Exp(-this.array[i]));
        }
        return na;
    }

    // 転置行列 行列の行と列を入れ替えた行列を生成して返す。(2次元に対応する)
    // (numpyの場合、各要素は参照を維持するが、このメソッドは要素を複製する。)
    public NumArray T()
    {
        NumArray y = new NumArray();
        y.shapeR[0] = this.shapeR[1];
        y.shapeR[1] = this.shapeR[0];
        y.shapeR[2] = 0;
        int nRow = this.shapeR[1] == 0 ? 1 : this.shapeR[1];
        int nColumn = this.shapeR[0] == 0 ? 1 : this.shapeR[0];
        int idx = 0;
        y.array = new float[nRow * nColumn];
        for (int column = 0; column < nColumn; column++) 
        {
            for (int row = 0; row < nRow; row++)
            {
                y.array[idx++] = this.FPAt(column, row);
            }
        }
        return y;
    }

    // 要素全てで、ネイピア数eの累乗を求める 
    public static NumArray exp(NumArray x)
    {
        NumArray y = NumArray.create(x.shapeR[0], x.shapeR[1], x.shapeR[2]);
        for (int i = 0; i < y.array.Length; i++) y.array[i] = Mathf.Exp(x.array[i]);
        return y;
    }

    // 集計する。axis=-1の場合、y.array[0] に合計が設定される。
    public static NumArray sum(NumArray x, int axis= -1)
    {
        int nRow = x.shapeR[1] == 0 ? 1 : x.shapeR[1];
        int nColumn = x.shapeR[0] == 0 ? 1 : x.shapeR[0];
        NumArray y = new NumArray();
        float sumV = 0;
        if(axis == -1)
        {
            for (int i = 0; i < x.array.Length; i++) sumV += x.array[i];
            y.shapeR[0] = y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[1] { sumV };
        }
        else if (axis == 0)//同列の各要素を集計 
        {
            y.shapeR[0] = nColumn;
            y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[nColumn];
            for (int column = 0; column < nColumn; column++)
            {
                for (int row = 0; row < nRow; row++)
                {
                    y.array[column] += x.FPAt(column, row);
                }
            }
        }
        else if (axis == 1)//同行の各要素の集計
        {
            y.shapeR[0] = y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[1] { sumV };
            y.shapeR[0] = x.shapeR[1];
            y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[nRow];
            for (int row = 0; row < nRow; row++) 
            {
                for (int column = 0; column < nColumn; column++)
                {
                    y.array[row] += x.FPAt(column, row);
                }
            }
        }
        return y;
    }

    // 集計する。axis=-1の場合、y.array[0] に最大値が設定される。
    public static NumArray max(NumArray x, int axis = -1)
    {
        int nRow = x.shapeR[1] == 0 ? 1 : x.shapeR[1];
        int nColumn = x.shapeR[0] == 0 ? 1 : x.shapeR[0];
        NumArray y = new NumArray();
        float maxV = -3.402823e+38f;
        if (axis == -1)
        {
            for (int i = 0; i < x.array.Length; i++) if(x.array[i]> maxV) maxV= x.array[i];
            y.shapeR[0] = y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[1] { maxV };
        }
        else if (axis == 0)//同列の各要素で求める
        {
            y.shapeR[0] = x.shapeR[1];
            y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[nColumn];
            for (int column = 0; column < nColumn; column++)
            {
                for (int row = 1; row < nRow; row++)
                {
                    if(x.FPAt(column, row) > y.array[column]) y.array[column] = x.FPAt(column, row);
                }
            }
        }
        else if (axis == 1)//同行の各要素で求める
        {
            y.shapeR[0] = y.shapeR[1] = y.shapeR[2] = 0;
            y.shapeR[0] = x.shapeR[1];
            y.shapeR[1] = y.shapeR[2] = 0;
            y.array = new float[nRow];
            for (int row = 0; row < nRow; row++)
            {
                for (int column = 1; column < nColumn; column++)
                {
                    if(x.FPAt(column, row) > y.array[row]) y.array[row]  = x.FPAt(column, row);
                }
            }
        }
        return y;
    }


    // ソフトマックス関数:xを確率的な値に変換する。
    // x要素群のごちゃごちゃした数字を、最終総和が100%(1.0)になる比率に変えてくれる関数
    public static NumArray softmax(NumArray x)
    {
        NumArray maxX = NumArray.max(x); // maxX.array[0] に最大値
        x =x.deepcopy();
        x.add_scalar(- maxX.array[0]);//# オーバーフロー対策
        //Debug.Log($"- maxX.array[0]:{-maxX.array[0]}, x:{x}");
        NumArray naY = NumArray.exp(x);
        float na_sum = NumArray.sum(naY).array[0];
        naY.mul_scalar(1 / na_sum);
        return naY;
    }

    // 2次元をワンホットの表現の文字列として表現
    public string getOneOfKstring(int i2)
    {
        string s = "";
        for (int i1 = 0; i1 < this.shapeR[0]; i1++)
        {
            s += $"  {this.FPAt(i1, i2),5:F3}";
        }
        return s;
    }

    //   交差エントロピー誤差取得
    public static float cross_entropy_error(NumArray y, NumArray t)
    {
        //Debug.Log($"y:{y}\nt:{t}");
        float rtnV = 0;
        for(int i=0; i < y.array.Length; i++)
        {
            rtnV += -Mathf.Log(y.array[i]) * t.array[i];
        }
        return rtnV;
    }

    // 確認用の文字列化
    public override string ToString()
    {
        if (this.shapeR[0] == 0)
        {
            return $" {this.array[0],8:F4}";
        }
        bool brackets0 = false;// 始まりのカッコ出力済みでtrue
        bool brackets1 = false;
        bool brackets2 = false;
        bool flagLF = false;
        int dim1 = this.shapeR[1] == 0 ? 1 : this.shapeR[1];
        int dim2 = this.shapeR[2] == 0 ? 1 : this.shapeR[2];
        string bS1 = this.shapeR[1] == 0 ? "" : "[";
        string bS2 = this.shapeR[2] == 0 ? "" : "[";
        string bE1 = this.shapeR[1] == 0 ? "" : "]";
        string bE2 = this.shapeR[2] == 0 ? "" : "]";

        string s = "";
        s += $"{this.shapeR[0]}, {this.shapeR[1]}, {this.shapeR[2]}\n";
        for (int i = 0; i < this.array.Length + 1; i++)
        {
            bool s0 = i % this.shapeR[0] == 0;
            bool s1 = i % (this.shapeR[0]* dim1) == 0;
            bool s2 = i % (this.shapeR[0] * dim1 * dim2) == 0;
            if (s0 && brackets0) { s += "]"; brackets0 = false; flagLF = true; }
            if (s1 && brackets1) { s += bE1; brackets1 = false; }
            if (s2 && brackets2) { s += bE2; brackets2 = false; }
            if (i == this.array.Length) break;
            if (flagLF) { s += $"\n"; flagLF = false; };
            if (s2 && !brackets2) { s += bS2; brackets2 = true; }
            if (s1 && !brackets1) { s += bS1; brackets1 = true; }
            if (s0 && !brackets0) { s += "["; brackets0 = true; }

            s += $" {this.array[i],8:F4}"; // 要素の表示
        }
        int countLF = System.Text.RegularExpressions.Regex.Matches(s,"\n").Count;
        if(countLF > 30 ) //&& false) // 条件行数を超えた場合の一部省略
        {
            int i = 0;
            for(int count = 0; count++ < 10; ) i = s.IndexOf("\n", i)+1;
            string s1 = s.Substring(0, i);
            i = s.Length - 1;
            for (int count = 0; count++ < 10;) i = s.LastIndexOf("\n", i)-1;
            string s2 = s.Substring(i);
            s = s1 + "...................." + s2; // 一部を省略した文字列
        }
        return s;
    }
}

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

変数概要
int shapeR[3]下記arrayの1次元、2次元、3次元の要素数 (それぞれの次元のサイズ)
float[]array上記の次元を、この1次配列で管理

メソッドヘッダー概要
NumArray()デフォルトコンストラクタ
ref float FPAt(int i1, int i2 = 0, int i3 = 0)array[i3, i2, i1]のような操作対象要素の参照返す。
static NumArray create(int dim1, int dim2 = -1, int dim3 = -1)サイズ指定の生成メソッド
NumArray deepcopy()自身のNumArrayを複製
static NumArray createByBytes(byte[] buf, int dim1=0, int dim2 = 0)バイト配列からの生成メソッド
static NumArray createByArray<T>(T[] buf, int dim1, int dim2 = 0)bufの各要素(intやdouble)からの生成メソッド
static NumArray createParam(float param,int dim1,int dim2=1,int dim3=0)全ての要素をparamの値で生成
static NumArray createGaussian(int dim1,int dim2=1,int dim3=1, double mean=0,double stdDev=1)正規分布乱数(ガウス分布乱数)で初期化したNumArrayの生成
NumArray createLineAt(int idx)自身が2次配列、または3次配列の場合に、idx行を抽出した1次または2次配列を返す。
void add_scalar(float v)行列に対するスカラー加算した行列に変更
static float sum_of_products(NumArray a, int k, NumArray b, int n)a行列のk行とb行列のn列において、各要素を積の総和を求める
static NumArray dot(NumArray a, NumArray b)行列の積 戻り値: a×bを生成して返す。
void write(FileStream fileStream)シリアライスしてストリームに書き込む
void read(FileStream fileStream)デシリアライスしてストリームから読み込んで初期化
Color32[] getPixels(int width, int height, int idx)idx位置のグレー手書き画像をRawImageに描画する時に使うColor32[]取得用
NumArray sigmoid()シグモイド関数 (arrayの全要素を0〜1.0に変換したNumArrayの生成)
NumArray T()転置行列:行列の行と列を入れ替えた行列を生成して返す。
static NumArray exp(NumArray x)要素全てで、ネイピア数eの累乗を求める
static NumArray sum(NumArray x, int axis= -1)集計する。axis=-1の場合、y.array[0] に合計が設定される。
static NumArray max(NumArray x, int axis = -1)集計する。axis=-1の場合、y.array[0] に最大値が設定される。
static NumArray softmax(NumArray x)ソフトマックス関数:xを確率的な値に変換する。
string getOneOfKstring(int i2)2次元をワンホットの表現の文字列として表現
static float cross_entropy_error(NumArray y, NumArray t)交差エントロピー誤差取得
override string ToString()確認用の文字列化

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

createByBytes ,FPAt メソッドの検証

byte配列の各要素を使って、createByBytesにより次元サイズ指定でNumArrayを生成して、その検証用ToStringで確認後、各要素を1/10倍して表示させている。
学習や判定素材にbyte列を指定するケースで、そのbyte列からNumArrayオブジェクトを容易に取得するメソッドとして、このメソッドを用意している。
なお、FPAtメソッドは配列要素を参照を返すメソッドで、戻り値で要素を変更をできるようにいます。 [ii, i2, i3]の位置の浮動小数点(Floating Point at [i1, i2, i3])を、12.3fにする場合、na.FPAt(i1, i2, i3)=12.3f;と表現できます。

    public void test00()// createByBytes ,FPAt メソッドの検証
    {     
        byte[] buf = new byte[3 * 2 * 6];//byte配列生成して初期化
        for (int i = 0; i < buf.Length; i++) buf[i] = (byte)i;

        NumArray a = NumArray.createByBytes(buf, 6, 2);//byte配列から次元サイズ指定で、NumArrayを生成
        Debug.Log($"NumArrayのa:{a.ToString()}");

        for (int i3 = 0; i3 < a.shapeR[2]; i3++)
        {
            for (int i2 = 0; i2 < a.shapeR[1]; i2++)
            {
                for (int i1 = 0; i1 < a.shapeR[0]; i1++)
                {
                    a.FPAt(i1, i2, i3)/=10; // 変更
                    Debug.Log($"[{i1}][{i2}][{i3}]={a.FPAt(i1, i2, i3)}");// 要素の表示
                }
            }
        }
    }
実行結果
NumArrayのa: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]]]
[0][0][0]=0
[0][0][1]=0.1
[0][0][2]=0.2
・・・省略


read ,write メソッドでシリアライズ検証

3要素が2行の2次元構成のNumArrayを生成後、そのオブジェクトをシリアライズしてファイル化した後、 それを復元して表示し、確認後にシグモイド関数を検証している例です。
    public void test01()  // NumArrayのreadとwriteの確認
    {
        // dim1を指定しなければ1次元配列、dim1だけ指定すると2次元、dim1とdim2を0以外で指定すると3次元になる。
        NumArray na = NumArray.createByArray(new double[] { -2, -1, -0.5, 0, 3, 6 },3);//配列よりNumArrayを生成
        // numpyの.reshape(2,3)に似た仕組みを取り入れてますが、次元の並びが逆です。(上記以は3個が2列の2次元指定)

        Debug.Log($"NumArrayのa:{na.ToString()}");

        using (FileStream fileStream = new FileStream("test.bin", FileMode.Create))
            na.write(fileStream);// シリアライズ

        NumArray naA = new NumArray();
        using (FileStream fileStream = new FileStream("test.bin", FileMode.Open))
            naA.read(fileStream);// デシリアライズ

        Debug.Log($"NumArrayのaA:{naA.ToString()}と復元");

        NumArray naB = naA.sigmoid();// データの範囲を0から1の値に変更するシグモイド
        Debug.Log($"NumArrayのa:{naB.ToString()}");
    }
実行結果
NumArrayのa:3, 2, 0
[[[  -2.0000  -1.0000  -0.5000]
[   0.0000   3.0000   6.0000]]]
NumArrayのaA:3, 2, 0
[[[  -2.0000  -1.0000  -0.5000]
[   0.0000   3.0000   6.0000]]]と復元
NumArrayのa:3, 2, 0
[[[   0.1192   0.2689   0.3775]
[   0.5000   0.9526   0.9975]]]
なお、検証に使ったシリアライズを除いた同等のpythonのコードを示す。
import numpy as np
naA=np.array([-2, -1, -0.5, 0, 3, 6])
naA=naA.reshape(2,-1) # .reshape(2,3)と同じで、 NumArrayでは1次元が3になる
naB=1 / (1 + np.exp(-naA)) # sigmoid();// データの範囲を0から1の値に変更するシグモイド処理
print(f"naA:{naA}\nnaB:{naB}");


dot 行列の積メソッド検証

    public void test02()// 行列の積検証
    {
        NumArray naA = NumArray.createByArray<int>(
            new int[] { 1,2,3,4,5,6,7,8,9,10,11,12 }, 3); // 4行3列
        Debug.Log(naA);
        NumArray naB = NumArray.createByArray<int>(
            new int[] { 1,2,3,4,5,6}, 2);// 3行2列
        Debug.Log(naB);

        NumArray naC = NumArray.dot(naA, naB);// 行列の積
        Debug.Log(naC + "          上記2つの積");

        NumArray naX = NumArray.createByArray<int>(
            new int[] { 1, 2, 3, 4}); // 1行4列
        Debug.Log(naX);

        NumArray naY = NumArray.dot(naX, naC);// 行列の積
        Debug.Log(naY + "          上記に積を施す");
    }
実行結果
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]]
2, 3, 0
[[   1.0000   2.0000]
   3.0000   4.0000]
   5.0000   6.0000]]
2, 4, 0
[[  22.0000  28.0000]
  49.0000  64.0000]
  76.0000 100.0000]
 103.0000 136.0000]]          上記2つの積
4, 0, 0
[   1.0000   2.0000   3.0000   4.0000]
2, 0, 0
[ 760.0000 1000.0000]          上記に積を施す
なお、検証に使った同等のpythonのコードを示す。
import numpy as np
A=np.array([ 1,2,3,4,5,6,7,8,9,10,11,12])
A=A.reshape(4,-1)
print(f"A={A}, A.shape:{A.shape}")

B=np.array([ 1,2,3,4,5,6 ])
B=B.reshape(3,-1)
print(f"B={B}, B.shape:{B.shape}")

C=np.dot(A, B)# 行列の積
print(f"C={C}, C.shape:{C.shape}")

X=np.array([ 1, 2, 3, 4 ])
print(f"X={X}, X.shape:{X.shape}")

Y=np.dot(X, C)# 行列の積
print(f"Y={Y}, Y.shape:{Y.shape}")


NumArray T() 検証

        // 転置行列検証
        NumArray naA = NumArray.createByArray<int>(
                    new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }, 3);
        Debug.Log($"naA:{naA}");
        NumArray t = naA.T(); // 転置行列を得る。
        Debug.Log($"t:{t}");
実行結果
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]]
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]]
なお、検証に使った同等のpythonのコードを示す。
a=np. array([x for x in range(12)])
a=a.reshape(4, -1) # (4,3)の構造に変更
print(f"{a}:a,\n a.shape:{a.shape}")
t=a.T	# 転置行列を得る。
print(f"{t}:t,\n t.shape:{t.shape}")


static NumArray exp(NumArray x) 検証

        NumArray naX = NumArray.createByArray<double>(new double[] { 2.5, 4.0, 3.5 });
        Debug.Log($"NumArrayのnaX:{naX}\n NumArray.exp(naX):{NumArray.exp(naX)}");
実行結果
NumArrayのnaX:3, 0, 0
[   2.5000   4.0000   3.5000]
 NumArray.exp(naX):3, 0, 0
[  12.1825  54.5982  33.1155]
なお、検証に使った同等のpythonのコードを示す。
iport numpy as np
xs = np.array([2.5, 4.0, 3.5])
print(f"xs:{xs}\n np.exp(xs){np.exp(xs)}")


static NumArray sum(NumArray x,axis=0) 検証

        NumArray naX = NumArray.createByArray<double>(new double[] 
            { 0,1,2,3,4,5,6,7,8,9,10,11 }, 3);
        Debug.Log($"NumArrayのnaX:{naX}");
        Debug.Log($"NumArray.sum(naX):{NumArray.sum(naX)}");// naX.array[0] が結果となります。
        Debug.Log($"NumArray.sum(naX, 0):{NumArray.sum(naX,  0)}");//同列の各要素を集計
        Debug.Log($"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):  66.0000
NumArray.sum(naX, 0):4, 0, 0
[  18.0000  22.0000  26.0000
NumArray.sum(naX, 1):4, 0, 0
[   3.0000  12.0000  21.0000  30.0000]
なお、検証に使った同等のpythonのコードを示す。
import numpy as np
xs = np.arange(0,12).reshape(4,3)
print(f"xs:{xs}")
print(f"np.sum(xs):{np.sum(xs)}")
print(f"np.sum(xs,axis=0):{np.sum(xs,axis=0)}") # 同列の各要素を集計
print(f"np.sum(xs,axis=1):{np.sum(xs,axis=1)}") # 同行の各要素の集計


static NumArray max(NumArray x,axis=0) 検証

        NumArray naX = NumArray.createByArray<double>(new double[] 
            { 0,1,2,3,4,5,6,7,8,9,10,11 }, 3);
        Debug.Log($"NumArrayのnaX:{naX}");
        Debug.Log($"NumArray.max(naX):{NumArray.max(naX)}");// naX.array[0] が結果
        Debug.Log($"NumArray.max(naX, 0):{NumArray.max(naX,  0)}");//同列の各要素の最大
        Debug.Log($"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):  11.0000
NumArray.max(naX, 0):4, 0, 0
[   9.0000  10.0000  11.0000]
NumArray.max(naX, 1):4, 0, 0
[   2.0000   5.0000   8.0000  11.0000]
なお、検証に使った同等のpythonのコードを示す。
import numpy as np
xs = np.arange(0,12).reshape(4,3)
print(f"xs:{xs}")
print(f"np.max(xs):{np.max(xs)}")
print(f"np.max(xs):{np.max(xs,axis=0)}") # 同列の各要素の最大
print(f"np.max(xs):{np.max(xs,axis=1)}") # 同行の各要素の最大


NumArray createLineAt(int idx) 検証

自身が2次配列、または3次配列の場合に、idx行を抽出した1次または2次配列を返す。
        int idxTest = 2;// createLineAt(idxTest)によるidxTest行目抽出の実験
        NumArray naA = NumArray.createByArray(
            new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }, 3);
        Debug.Log(naA);
        NumArray naB = naA.createLineAt(idxTest);
        Debug.Log(naB);
実行結果
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]]
3, 0, 0
[   7.0000   8.0000   9.0000]
なお、検証に使った同等のpythonのコードを示す。
import numpy as np
idxTest = 2
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ).reshape(4,3)
print(f"a:{a}. a.shape:{a.shape}")
b=a[idxTest]
print(f"b:{b}. b.shape:{b.shape}")


static NumArray softmax(NumArray x) 検証

xを確率的な値に変換するソフトマックス関数(1次元にのみ対応) x要素群のごちゃごちゃした数字を、最終総和が100%(1.0)になる比率に変えてくれる関数
        NumArray naX = NumArray.createByArray<double>(new double[]
            { 2.5, 4.0, 3.5 });//例えば、「人」、「ねこ」、「イヌ」の判定度合い
        Debug.Log($"NumArrayのnaX:{naX}");
        NumArray naY = NumArray.softmax(naX); // 確率情報に変換
        Debug.Log($"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]
なお、検証に使った同等のpythonのコードを示す。
import numpy as np
def softmax(x):
   x = x - np.max(x) # オーバーフロー対策
   return np.exp(x) / np.sum(np.exp(x))

xs = np.array([2.5, 4.0, 3.5])#例えば、「人」、「ねこ」、「イヌ」の判定度合い
print(f"xs:{xs}, xs.shape:{xs.shape}") # 確率情報に変換
y = softmax(xs) # 確率情報に変換
print(f"y = softmax(xs):{y}, y.shape:{y.shape}")