トップへ(mam-mam.net/)

人工ニューラルネットワークをフルスクラッチで作成 ~Delphiソースコード集

検索:

人工ニューラルネットワークをフルスクラッチで作成 ~Delphiソースコード集

人工ニューラルネットワーク(Artificial Neural Network)を使用する為のファイルの準備

本ページ最下部にある「UMamAnn.pas」ソースコードを、プロジェクトフォルダ内に「UMamAnn.pas」ファイルとして保存します。 ほかに必要なファイルやライブラリはありません。
TMamAnnクラスのインスタンスを作り、Trainプロシージャで学習し、Runプロシージャで学習結果をもとに結果を返します。
活性化関数はシグモイド関数だけです。

参考URL
https://atmarkit.itmedia.co.jp/ait/articles/2202/09/news027.html

Delphiを起動して新規作成を行い、必要なコンポーネントをドラッグ&ドロップする

Delphi起動⇒ファイル⇒新規作成⇒WindowsVCLアプリケーション を選択します。
TButton 2個、TMemo 1個、をフォームへドラッグ&ドロップします。

Delphiで人工ニューラルネットワーク(Artificial Neural Network)を使用する

「ファイル」⇒「全て保存」でフォルダを作成して、プロジェクトとユニットを保存します。
プロジェクトフォルダ内にUMamAnn.pasファイルを配置します。

ソースコードを記述する

Button1のOnClickイベント、Button2のOnClickイベントに以下ソースコードを記述します。
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses UMamAnn;

procedure TForm1.Button1Click(Sender: TObject);
var Neurons:TArray<Integer>;
    Ann:TMamAnn;
    Inputs,OutPuts:TArray<Single>;
    epoch:Integer;//繰り返し学習
begin
  //排他論理和(XOR)を「回帰問題」として学習させる

  Memo1.Clear;

  SetLength(Neurons,3);//3層
  Neurons[0]:=2;//入力層のニューロン数
  Neurons[1]:=2;//中間層のニューロン数
  Neurons[2]:=1;//出力層のニューロン数
  SetLength(Inputs,2); //入力層データ
  SetLength(Outputs,1);//出力層データ
  Ann:=TMamAnn.Create(Neurons);
  try
    //10000回繰り返し学習させる
    for epoch := 1 to 10000 do
    begin
      //0 XOR 0 = 0を学習させる
      Inputs[0]:=0;
      Inputs[1]:=0;
      OutPuts[0]:= Trunc(Inputs[0]) XOR Trunc(Inputs[1]);
      Ann.Train(Inputs,Outputs);//学習
      //0 XOR 1 = 1を学習させる
      Inputs[0]:=0;
      Inputs[1]:=1;
      OutPuts[0]:= Trunc(Inputs[0]) XOR Trunc(Inputs[1]);
      Ann.Train(Inputs,Outputs);//学習
      //1 XOR 0 = 1を学習させる
      Inputs[0]:=1;
      Inputs[1]:=0;
      OutPuts[0]:= Trunc(Inputs[0]) XOR Trunc(Inputs[1]);
      Ann.Train(Inputs,Outputs);//学習
      //1 XOR 1 = 0を学習させる
      Inputs[0]:=1;
      Inputs[1]:=1;
      OutPuts[0]:= Trunc(Inputs[0]) XOR Trunc(Inputs[1]);
      Ann.Train(Inputs,Outputs);//学習
    end;

    //学習結果をファイルに保存する
    //Ann.SaveToFile('xor.net');

    Memo1.Lines.Add('排他論理和(XOR)を「回帰問題」として学習');
    //0 XOR 0 がいくつかAIに聞く
    Inputs[0]:=0;
    Inputs[1]:=0;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0f = %1.0f',[Inputs[0],Inputs[1],Outputs[0]])
    );
    //0 XOR 1 がいくつかAIに聞く
    Inputs[0]:=0;
    Inputs[1]:=1;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0f = %1.0f',[Inputs[0],Inputs[1],Outputs[0]])
    );
    //1 XOR 0 がいくつかAIに聞く
    Inputs[0]:=1;
    Inputs[1]:=0;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0f = %1.0f',[Inputs[0],Inputs[1],Outputs[0]])
    );
    //1 XOR 1 がいくつかAIに聞く
    Inputs[0]:=1;
    Inputs[1]:=1;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0f = %1.0f',[Inputs[0],Inputs[1],Outputs[0]])
    );
  finally
    Ann.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var Neurons:TArray<Integer>;
    Ann:TMamAnn;
    Inputs,OutPuts:TArray<Single>;
    epoch:Integer;//繰り返し学習
begin
  //排他論理和(XOR)を「分類問題」として学習させる

  Memo1.Clear;

  SetLength(Neurons,3);//3層
  Neurons[0]:=2;//入力層のニューロン数
  Neurons[1]:=2;//中間層のニューロン数
  Neurons[2]:=2;//出力層のニューロン数
  SetLength(Inputs,2); //入力層データ
  SetLength(Outputs,2);//出力層データ
  Ann:=TMamAnn.Create(Neurons);
  try
    //10000回繰り返し学習させる
    for epoch := 1 to 10000 do
    begin
      //0 XOR 0 = 0を学習させる
      Inputs[0]:=0;
      Inputs[1]:=0;
      OutPuts[0]:=1;//答えは「0」か
      OutPuts[1]:=0;//答えは「1」か
      Ann.Train(Inputs,Outputs);//学習
      //0 XOR 1 = 1を学習させる
      Inputs[0]:=0;
      Inputs[1]:=1;
      OutPuts[0]:=0;//答えは「0」か
      OutPuts[1]:=1;//答えは「1」か
      Ann.Train(Inputs,Outputs);//学習
      //1 XOR 0 = 1を学習させる
      Inputs[0]:=1;
      Inputs[1]:=0;
      OutPuts[0]:=0;//答えは「0」か
      OutPuts[1]:=1;//答えは「1」か
      Ann.Train(Inputs,Outputs);//学習
      //1 XOR 1 = 0を学習させる
      Inputs[0]:=1;
      Inputs[1]:=1;
      OutPuts[0]:=1;//答えは「0」か
      OutPuts[1]:=0;//答えは「1」か
      Ann.Train(Inputs,Outputs);//学習
    end;

    //学習結果をファイルに保存する
    //Ann.SaveToFile('xor.net');

    Memo1.Lines.Add('排他論理和(XOR)を「分類問題」として0か1かを学習');

    //0 XOR 0 が「0」か「1」のどちらかをAIに聞く
    Inputs[0]:=0;
    Inputs[1]:=0;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0fが 0の確率%3.0f%%、1の確率%3.0f%%',
        [Inputs[0],Inputs[1],Outputs[0]*100,Outputs[1]*100])
    );
    //0 XOR 1 がいくつかAIに聞く
    Inputs[0]:=0;
    Inputs[1]:=1;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0fが 0の確率%3.0f%%、1の確率%3.0f%%',
        [Inputs[0],Inputs[1],Outputs[0]*100,Outputs[1]*100])
    );
    //1 XOR 0 がいくつかAIに聞く
    Inputs[0]:=1;
    Inputs[1]:=0;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0fが 0の確率%3.0f%%、1の確率%3.0f%%',
        [Inputs[0],Inputs[1],Outputs[0]*100,Outputs[1]*100])
    );
    //1 XOR 1 がいくつかAIに聞く
    Inputs[0]:=1;
    Inputs[1]:=1;
    Ann.Run(Inputs,Outputs);
    Memo1.Lines.Add(
      Format('%1.0f XOR %1.0fが 0の確率%3.0f%%、1の確率%3.0f%%',
        [Inputs[0],Inputs[1],Outputs[0]*100,Outputs[1]*100])
    );
  finally
    Ann.Free;
  end;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOK)
Button1をクリックすると、排他論理和(XOR)を「回帰問題」として学習し、AIが排他論理和の答えを返します。

Delphiで人工ニューラルネットワーク(Artificial Neural Network)を使用し排他論理和(XOR)を「回帰問題」として学習して答えを返す

Button2をクリックすると、排他論理和(XOR)を「分類問題」として学習し、AIが排他論理和の答えを返します。

Delphiで人工ニューラルネットワーク(Artificial Neural Network)を使用し排他論理和(XOR)を「分類問題」として学習して答えを返す

UMamAnn.pasソースコード

unit UMamAnn;
interface
uses System.Math,System.Classes,System.SysUtils;
{
  人工ニューラルネットワーク(Artificial Neural Network)クラスのライブラリ
  活性化関数
      TMamActivationFunc.Sigmoid指定時
        中間層はシグモイド関数、出力層は恒等関数を使用
      TMamActivationFunc.ReLU指定時
        中間層はReLU関数、出力層はシグモイド関数を使用

  ◎=入力層のニューロン
  ●=中間層のニューロン
  ○=出力層のニューロン
  ■=重み
  ▼=バイアス
      値      重み           バイアス
      fSum    fWeight        fBias
      fOut    fGradsW        fGradsB
  i0 ◎◎
     j0j1     j0   j1   j2     j0j1j2
            i0■■ ■■ ■■ i0▼▼▼
              k0k1 k0k1 k0k1
  i1 ●●●
     j0j1j2   j0     j1        j0j1
            i1■■■ ■■■  i1▼▼
              k0k1k2 k0k1k2
  i2 ●●
     j0j1     j0   j1          j0j1
            i2■■ ■■      i2▼▼
              k0k1 k0k1
  i3 ○○
     j0j1
}

  type
    TMamActivationFunc=(Sigmoid,ReLU);

    TMamAnn=class(TObject)
    private
      fSum:TArray<TArray<single>>;//各層の値(重み付線形和)
      fOut:TArray<TArray<single>>;//各層の値(重み付線形和の活性化関数適用後)
      fLayerCount:Integer;//層の数
      fNeuronCountInLayers:TArray<Integer>;//各層のニューロン数
      fLearningRate:single;//学習率
      fLoss:Single;//損失値(累計2乗誤差)
      fTrainCount:Integer;//学習回数
      fBias:TArray<TArray<single>>;//バイアス
      fWeight:TArray<TArray<TArray<single>>>;//重みづけ(-1~1)
      fActivationFunc:TMamActivationFunc;
      procedure ForwardProp(Inputs:TArray<Single>);//順伝播
      procedure BackProp(Outputs:TArray<Single>);  //逆伝播とパラメータ更新
      function Sigmoid(x:Single):Single;           //シグモイド
      function SigmoidDerivative(x:single):single; //シグモイドの導関数
      function ReLU(x: single): single;
      function ReLUDerivative(x: single): single;
      function GetLearningRate():Single;
      procedure SetLearningRate(LearningRage:Single);
    public
      constructor Create(
        NeuronCountInLayers:TArray<Integer>;LerningRate:Single=0.2;
        ActivationFunc:TMamActivationFunc=TMamActivationFunc.Sigmoid
      );overload;
      constructor Create(Filename:String);overload;
      //学習
      procedure Train(Inputs,Outputs: TArray<Single>);
      //予測の実行
      procedure Run(Inputs:TArray<Single>;out Outputs:TArray<Single>);
      //MSE(平均二乗誤差)出力
      function GetMSE():Single;
      destructor Destroy; override;
      procedure SaveToFile(FileName:String);
      procedure LoadFromFile(FileName:String);
      property LearningRate:Single read GetLearningRate write SetLearningRate;
  end;

implementation

{ TMamAnn }

function TMamAnn.ReLU(x: single): single;
begin
  if x>0 then result:=x else result:=0;
end;

function TMamAnn.ReLUDerivative(x: single): single;
begin
 if x>0 then result:=1 else result:=0;
end;

procedure TMamAnn.BackProp(Outputs:TArray<Single>);
var i,j,k:Integer;
    loss_der:single;
    back_error:TArray<Single>;
    active_der:single;
    delta:single;
    sum_der_w,sum_der_x:TArray<Single>;
    sum_der_b:Single;
    layer_grads_x:TArray<Single>;
begin
//逆伝播とパラメータ更新
  SetLength(layer_grads_x,0);
  for i := (fLayerCount-1) downto 1 do
  begin
    if i=(fLayerCount-1) then
    begin
      //出力層の場合
      SetLength(back_error,Length(Outputs));
      for j := Low(Outputs) to High(Outputs) do
      begin
        loss_der:=fOut[i][j]-Outputs[j];
        back_error[j]:=loss_der;
      end;
    end
    else
    begin
      //隠れ層(次の層への入力の偏微分係数)
      //最後に追加された入力の勾配(layer_grads_x)をback_errorに入れる
      SetLength(back_error,Length(layer_grads_x));
      Move(
        layer_grads_x[0],
        back_error[0],
        SizeOf(layer_grads_x[0])*Length(layer_grads_x)
      );
    end;
    for j := Low(fSum[i]) to High(fSum[i]) do
    begin
      if i=(fLayerCount-1) then
      begin
        //出力層の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //恒等関数の導関数
          active_der:=1.0
        else
          //シグモイド関数の導関数
          active_der:=SigmoidDerivative(fSum[i][j]);
      end
      else
      begin
        //出力層以外の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //シグモイド関数の導関数
          active_der:=SigmoidDerivative(fSum[i][j])
        else
          //ReLU関数の導関数
          active_der:=ReLUDerivative(fSum[i][j]);
      end;

      //fOut[i-1]配列をsum_der_wにコピー
      SetLength(sum_der_w,Length(fOut[i-1]));
      Move(
        fOut[i-1][0],
        sum_der_w[0],
        Sizeof(fOut[i-1][0])*Length(fOut[i-1])
      );
      sum_der_b:=1.0;
      //fWeight[i-1][j]配列をsum_der_xにコピー
      SetLength(sum_der_x,Length(fWeight[i-1][j]));
      Move(
        fWeight[i-1][j][0], sum_der_x[0],
        Sizeof(fWeight[i-1][j][0])*Length(fWeight[i-1][j])
      );

      //勾配を計算
      delta:=back_error[j]*active_der;
      //勾配からバイアス計算してバイアスを更新する
      fBias[i-1][j]:=fBias[i-1][j]-delta*sum_der_b*fLearningRate;

      if j=0 then SetLength(layer_grads_x,Length(sum_der_w));

      //重みと入力
      for k := Low(sum_der_w) to High(sum_der_w) do
      begin
        //勾配から重みの計算を行い重みを更新する
        fWeight[i-1][j][k]:=
          fWeight[i-1][j][k]-delta*sum_der_w[k]*fLearningRate;
        //入力は各ノードから前のノードに接続する全ての入力を合計する
        if j=0 then
        begin
          layer_grads_x[k]:=delta *sum_der_x[k];
        end
        else
        begin
          layer_grads_x[k]:=layer_grads_x[k]+delta *sum_der_x[k];
        end;
      end;
    end;
  end;
end;

constructor TMamAnn.Create(
  NeuronCountInLayers:TArray<Integer>;LerningRate: Single=0.2;
  ActivationFunc:TMamActivationFunc=TMamActivationFunc.Sigmoid);
var i,j,k,num:Integer;
    scale:Single;
begin
  fLearningRate:=LerningRate;
  if fLearningRate>0.999 then fLearningRate:=0.999;
  if fLearningRate<0.001 then fLearningRate:=0.001;

  //Sigmoid または ReLU
  fActivationFunc:=ActivationFunc;

  fLoss:=0;
  fTrainCount:=0;

  fLayerCount:=Length(NeuronCountInLayers);
  SetLength(fNeuronCountInLayers,fLayerCount);
  Move(
    NeuronCountInLayers[0],
    fNeuronCountInLayers[0],
    SizeOf(NeuronCountInLayers[0])*Length(NeuronCountInLayers)
  );

  Randomize;

  //値初期化
  SetLength(fOut,fLayerCount);
  SetLength(fSum,fLayerCount);
  for i := Low(fOut) to High(fOut) do
  begin
    num:=fNeuronCountInLayers[i];
    SetLength(fOut[i],num);
    SetLength(fSum[i],num);
  end;

  //バイアス初期化(0にする)
  SetLength(fBias,fLayerCount-1);
  for i := Low(fBias) to High(fBias) do
  begin
    SetLength(fBias[i],fNeuronCountInLayers[i+1]);
    for j := Low(fBias[i]) to High(fBias[i]) do
      fBias[i][j]:=0;
  end;

  //重みづけ初期化
  SetLength(fWeight,fLayerCount-1);
  for i := Low(fWeight) to High(fWeight) do
  begin
    SetLength(fWeight[i],fNeuronCountInLayers[i+1]);
    for j := Low(fWeight[i]) to High(fWeight[i]) do
    begin
      SetLength(fWeight[i][j],Length(fOut[i]));
      if i=High(fWeight) then
      begin
          scale:=1/sqrt(Length(fWeight[i][j]));
      end
      else
      begin
        //乱数だけだと永遠にMSEが収束しない場合がある(1/20回くらい)
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //平均0、1/√nのガウス分布乱数
          scale:=1/sqrt(Length(fWeight[i][j]))
        else
          //平均0、√(2/(n1+n2))のガウス分布乱数
          scale:=sqrt(2/Length(fWeight[i][j]))
      end;
      for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
      begin
        if (fActivationFunc=TMamActivationFunc.Sigmoid) then
        begin
          if k=Low(fWeight[i][j]) then
            fWeight[i][j][k]:=scale
          else if k=High(fWeight[i][j]) then
            fWeight[i][j][k]:=-scale
          else
            fWeight[i][j][k]:=RandG(0,scale);
        end
        else
        begin
          if (i=High(fWeight[i])) then
          begin
            if k=Low(fWeight[i][j]) then
              fWeight[i][j][k]:=scale
            else if k=High(fWeight[i][j]) then
              fWeight[i][j][k]:=-scale
            else
              fWeight[i][j][k]:=RandG(0,scale);
          end
          else
            if k=Low(fWeight[i][j]) then
              fWeight[i][j][k]:=scale*3.5
            else if k=High(fWeight[i][j]) then
              fWeight[i][j][k]:=-scale*0.5
            else
              fWeight[i][j][k]:=RandG(0,scale);
        end;
      end;
    end;
  end;
end;


constructor TMamAnn.Create(Filename: String);
begin
  Randomize;
  Self.LoadFromFile(Filename);
end;

destructor TMamAnn.Destroy;
begin
  inherited;
end;

procedure TMamAnn.ForwardProp(Inputs: TArray<Single>);
var i,j,k:Integer;
    ww:Single;
begin //順伝播
  //入力層に入力値を入れる
  Move(Inputs[0],fSum[0][0],SizeOf(Inputs[0])*Length(Inputs));
  Move(Inputs[0],fOut[0][0],SizeOf(Inputs[0])*Length(Inputs));

  //入力層から中間層から出力層まで計算する
  for i := Low(fWeight) to High(fWeight) do
  begin
    for j := Low(fWeight[i]) to High(fWeight[i]) do
    begin
      //fSum[i+1][j]:=
      //        fOut[i][  0]*fWeight[i][j][  0]+
      //        fOut[i][  1]*fWeight[i][j][  1]+
      //        fOut[i][...]*fWeight[i][j][...]+fBias[i][j];
      //fOut[i*1][j]:=活性化関数(fSum[i+1][j]);
      ww:=fBias[i][j];
      for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
      begin
        ww:=ww+fOut[i][k]*fWeight[i][j][k];
      end;
      fSum[i+1][j]:=ww;
      if i=(fLayerCount-2) then
      begin
        //出力層の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
        begin
          //恒等関数
          ww:=ww;
        end
        else
        begin
          //シグモイド関数
          ww:=Sigmoid(ww);
        end;
      end
      else
      begin
        //出力層以外の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //シグモイド関数
          ww:=Sigmoid(ww)
        else
          //ReLU関数
          ww:=ReLU(ww);
      end;
      fOut[i+1][j]:=ww;
    end;
  end;
end;

function TMamAnn.GetLearningRate: Single;
begin
  result:=fLearningRate;
end;

function TMamAnn.GetMSE: Single;
begin //MSE(平均二乗誤差)出力
  if fTrainCount>0 then
    result:=fLoss/fTrainCount
  else
    result:=0;
end;

procedure TMamAnn.LoadFromFile(FileName: String);
var strm:TMemoryStream;
    i,j,k:Integer;
    act:Integer;
begin //ファイルからの読み込み
  strm:=TMemoryStream.Create;
  try
    strm.LoadFromFile(FileName);
    strm.Position:=0;
    //活性化関数読み込み
    strm.ReadData(act);
    if act=0 then
      fActivationFunc:=TMamActivationFunc.Sigmoid
    else
      fActivationFunc:=TMamActivationFunc.ReLU;
    //層数読み込み
    strm.ReadData(fLayerCount);
    //層ごとのニューロン数読み込み
    setLength(fNeuronCountInLayers,fLayerCount);
    for i := 0 to fLayerCount-1 do
      strm.ReadData(fNeuronCountInLayers[i]);
    //学習率の読み込み
    strm.ReadData(fLearningRate);
    //損失値(累計2乗誤差)読み込み
    strm.ReadData(fLoss);
    //学習回数読み込み
    strm.ReadData(fTrainCount);
    //fSum,fOutの準備
    SetLength(fSum,fLayerCount);
    SetLength(fOut,fLayerCount);
    for i := 0 to fLayerCount-1 do
    begin
      SetLength(fSum[i],fNeuronCountInLayers[i]);
      SetLength(fOut[i],fNeuronCountInLayers[i]);
    end;
    //バイアス読み込み
    SetLength(fBias,fLayerCount-1);
    for i := Low(fBias) to High(fBias) do
    begin
      SetLength(fBias[i],fNeuronCountInLayers[i+1]);
      for j := Low(fBias[i]) to High(fBias[i]) do
      begin
        strm.ReadData(fBias[i][j]);
      end;
    end;
    //重み読み込み
    SetLength(fWeight,fLayerCount-1);
    for i := Low(fWeight) to High(fWeight) do
    begin
      SetLength(fWeight[i],fNeuronCountInLayers[i+1]);
      for j := Low(fWeight[i]) to High(fWeight[i]) do
      begin
        SetLength(fWeight[i][j],fNeuronCountInLayers[i]);
        for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
        begin
          strm.ReadData(fWeight[i][j][k]);
        end;
      end;
    end;
  finally
    strm.Free;
  end;
end;

procedure TMamAnn.Run(Inputs: TArray<Single>; out Outputs: TArray<Single>);
begin //予測の実行
  ForwardProp(Inputs);
  //出力層の値のコピー
  SetLength(Outputs,Length(fOut[High(fOut)]));
  Move(
    fOut[High(fOut)][0],
    Outputs[0],
    SizeOf(fOut[High(fOut)][0])*Length(fOut[High(fOut)])
  );
end;

procedure TMamAnn.SaveToFile(FileName: String);
var strm:TMemoryStream;
    i,j,k:Integer;
    act:Integer;
begin //バイナリで保存する
  strm:=TMemoryStream.Create();
  try
    //活性化関数保存
    if fActivationFunc=TMamActivationFunc.Sigmoid then
      act:=0
    else
      act:=1;
    strm.WriteData(act);
    //層数保存
    strm.WriteData(fLayerCount);
    //層ごとのニューロン数保存
    for i := Low(fNeuronCountInLayers) to High(fNeuronCountInLayers) do
      strm.WriteData(fNeuronCountInLayers[i]);
    //学習率保存
    strm.WriteData(fLearningRate);
    //損失値(累計2乗誤差)保存
    strm.WriteData(fLoss);
    //学習回数保存
    strm.WriteData(fTrainCount);
    //バイアス保存
    for i := Low(fBias) to High(fBias) do
      for j := Low(fBias[i]) to High(fBias[i]) do
        strm.WriteData(fBias[i][j]);
    //重み保存
    for i := Low(fWeight) to High(fWeight) do
      for j := Low(fWeight[i]) to High(fWeight[i]) do
        for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
          strm.WriteData(fWeight[i][j][k]);
    strm.SaveToFile(FileName);
  finally
    strm.Free;
  end;
end;

procedure TMamAnn.SetLearningRate(LearningRage:Single);
begin
  if LearningRate>0.999 then LearningRate:=0.999;
  if LearningRate<0.001 then LearningRate:=0.001;
  fLearningRate:=LearningRate;
end;

function TMamAnn.Sigmoid(x: Single): Single;
begin //シグモイド関数
  result:=1.0/(1.0+system.exp(-x))
end;

function TMamAnn.SigmoidDerivative(x:single): single;
begin //シグモイド関数の微分関数
  result:=Sigmoid(x);
  result:=result*(1.0-result);
end;

procedure TMamAnn.Train(Inputs, Outputs: Tarray<Single>);
var j:Integer;
    sub:Single;
begin //学習
  //順伝播
  ForwardProp(Inputs);
  //逆伝播
  BackProp(Outputs);
  //MSE(平均二乗誤差)の為に累計2乗誤差計算
  for j := Low(Outputs) to High(Outputs) do
  begin
    inc(fTrainCount);
    sub:=fOut[High(fOut)][j]-Outputs[j];
    fLoss:=fLoss+sub*sub;
  end;
end;

end.

(64Bitコンパイル用)UMamAnn.pasソースコード

unit UMamAnn;
interface
uses System.Math,System.Classes,System.SysUtils,winapi.windows;
{
  人工ニューラルネットワーク(Artificial Neural Network)クラスのライブラリ
  活性化関数
      TMamActivationFunc.Sigmoid指定時
        中間層はシグモイド関数、出力層は恒等関数を使用します
      TMamActivationFunc.ReLU指定時
        中間層はReLU関数、出力層はシグモイド関数を使用します

  ◎=入力層のニューロン
  ●=中間層のニューロン
  ○=出力層のニューロン
  ■=重み
  ▼=バイアス
      値      重み           バイアス
      fSum    fWeight        fBias
      fOut    fGradsW        fGradsB
  i0 ◎◎
     j0j1     j0   j1   j2     j0j1j2
            i0■■ ■■ ■■ i0▼▼▼
              k0k1 k0k1 k0k1
  i1 ●●●
     j0j1j2   j0     j1        j0j1
            i1■■■ ■■■  i1▼▼
              k0k1k2 k0k1k2
  i2 ●●
     j0j1     j0   j1          j0j1
            i2■■ ■■      i2▼▼
              k0k1 k0k1
  i3 ○○
     j0j1
}

  type
    TMamActivationFunc=(Sigmoid,ReLU);

    TMamAnn=class(TObject)
    private
      fSum:TArray<TArray<single>>;//各層の値(重み付線形和)
      fOut:TArray<TArray<single>>;//各層の値(重み付線形和の活性化関数適用後)
      fLayerCount:Integer;//層の数
      fNeuronCountInLayers:TArray<Integer>;//各層のニューロン数
      fLearningRate:single;//学習率
      fLoss:Single;//損失値(累計2乗誤差)
      fTrainCount:Integer;//学習回数
      fBias:TArray<TArray<single>>;//バイアス
      fWeight:TArray<TArray<TArray<single>>>;//重みづけ(-1~1)
      fActivationFunc:TMamActivationFunc;
      procedure ForwardProp(Inputs:TArray<Single>);//順伝播
      procedure BackProp(Outputs:TArray<Single>);  //逆伝播とパラメータ更新
      function Sigmoid(x:Single):Single;           //シグモイド
      function SigmoidDerivative(x:single):single; //シグモイドの導関数
      function ReLU(x: single): single;
      function ReLUDerivative(x: single): single;
      function GetLearningRate():Single;
      procedure SetLearningRate(LearningRage:Single);
    public
      constructor Create(
        NeuronCountInLayers:TArray<Integer>;LerningRate:Single=0.2;
        ActivationFunc:TMamActivationFunc=TMamActivationFunc.Sigmoid
      );overload;
      constructor Create(Filename:String);overload;
      //学習
      procedure Train(Inputs,Outputs: TArray<Single>);
      //予測の実行
      procedure Run(Inputs:TArray<Single>;out Outputs:TArray<Single>);
      //MSE(平均二乗誤差)出力
      function GetMSE():Single;
      destructor Destroy; override;
      procedure SaveToFile(FileName:String);
      procedure LoadFromFile(FileName:String);
      property LearningRate:Single read GetLearningRate write SetLearningRate;
  end;

implementation

//SSEで同時4つの浮動小数点演算を行う pD = pD + pS*coff4
procedure coffSum4(var pD, pS, coff4: single);
asm  //               RCX  RDX  R8
      MOVUPS  XMM0, [R8]     //XMM0=coff4[0..3]
      MOVUPS  XMM1, [RDX]    //XMM1=pS[0..3]
      MOVUPS  XMM2, [RCX]    //XMM2=pD[0..3]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      ADDPS   XMM2, XMM1     //XMM2=XMM2+XMM1
      MOVUPS  [RCX],XMM2     //pD=XMM2
end;
procedure coffSum16(var pD, pS, coff4: single);
asm  //                RCX  RDX  R8
      MOVUPS  XMM0, [R8]     //XMM0=coff4[0..3]
      MOVUPS  XMM1, [RDX]    //XMM1=pS[0..3]
      MOVUPS  XMM2, [RCX]    //XMM2=pD[0..3]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      ADDPS   XMM2, XMM1     //XMM2=XMM2+XMM1
      MOVUPS  [RCX],XMM2     //pD[0..3]=XMM2
      MOVUPS  XMM1, [RDX+16] //XMM1=pS[4..7]
      MOVUPS  XMM2, [RCX+16] //XMM2=pD[4..7]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      ADDPS   XMM2, XMM1     //XMM2=XMM2+XMM1
      MOVUPS  [RCX+16],XMM2  //pD[4..7]=XMM2
      MOVUPS  XMM1, [RDX+32] //XMM1=pS[8..11]
      MOVUPS  XMM2, [RCX+32] //XMM2=pD[8..11]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      ADDPS   XMM2, XMM1     //XMM2=XMM2+XMM
      MOVUPS  [RCX+32],XMM2  //pD[8..11]=XMM2
      MOVUPS  XMM1, [RDX+48] //XMM1=pS[12..15]
      MOVUPS  XMM2, [RCX+48] //XMM2=pD[12..15]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      ADDPS   XMM2, XMM1     //XMM2=XMM2+XMM
      MOVUPS  [RCX+48],XMM2  //pD[12..15]=XMM2
end;


procedure AVXcoffSum(var arD, arS: single; const coff: single; Count: integer);
var  Coff4: array[0..3] of single; //SSE命令の為の4word(16byte)の入れ物
  pD, pS: ^single;
  i: integer;
begin
  pD := @arD;
  pS := @arS;
  for i := 0 to High(Coff4) do coff4[i] := coff;
    while Count >= 16 do  //SSE命令はWin64なら必須だから4個一度に計算可能
    begin
      coffSum16(pD^, pS^, coff4[0]);
      Inc(pD, 16);
      Inc(pS, 16);
      Dec(Count, 16);
    end;
  while Count >= 4 do     //SSE命令はWin64なら必須だから4個一度に計算可能
  begin
    coffSum4(pD^, pS^,coff4[0]);
    Inc(pD, 4);
    Inc(pS, 4);
    Dec(Count, 4);
  end;

  while Count > 0 do //残りは最大3個で普通に計算
  begin
    pD^ := pD^ + coff * pS^;
    Inc(pD);
    Inc(pS);
    Dec(Count, 1);
  end;
end;

//SSEで同時4つの浮動小数点演算を行う coff4[0]=SUM(pD*pS)
//ww:=ww+fOut[i][k]*fWeight[i][j][k];
procedure MulSum4(var pD, pS, coff4: single);
asm  //               RCX  RDX  R8
      MOVUPS  XMM1, [RDX]    //XMM1=pS[0..3]
      MOVUPS  XMM2, [RCX]    //XMM2=pD[0..3]
      DPPS    XMM2, XMM1,$f1 //XMM2=SUM(XMM2*XMM1)
      MOVUPS  [R8],XMM2      //conn4=XMM2
end;
function MulAdd(var arD, arS: single; Count: integer):single;
var  Coff4: array[0..3] of single; //SSE命令の為の4word(16byte)の入れ物
  pD, pS: ^single;
begin
  pD := @arD;
  pS := @arS;
  //ZeroMemory(@Coff4[0],Length(Coff4)*Sizeof(Coff4[0]));
  result:=0;
  while Count >= 4 do     //SSE命令はWin64なら必須だから4個一度に計算可能
  begin
    MulSum4(pD^, pS^,coff4[0]);
    result:=result+coff4[0];
    Inc(pD, 4);
    Inc(pS, 4);
    Dec(Count, 4);
  end;

  while Count > 0 do //残りは最大3個で普通に計算
  begin
    result := result+ pD^ * pS^;
    Inc(pD);
    Inc(pS);
    Dec(Count, 1);
  end;
end;

//SSEで同時4つの浮動小数点演算を行う layer_grads_x[k]:=sum_der_x[k]*delta
procedure Mul4(var pD, pS, coff4: single);
asm  //               RCX  RDX  R8
      MOVUPS  XMM0, [R8]     //XMM0=coff4[0..3]
      MOVUPS  XMM1, [RDX]    //XMM1=pS[0..3]
      MULPS   XMM1, XMM0     //XMM1=XMM1*XMM0(pS*coff4)
      MOVUPS  [RCX],XMM1     //pD = XMM1
end;
procedure Mul(var arD, arS: single; const coff:Single; Count: integer);
var  Coff4: array[0..3] of single; //SSE命令の為の4word(16byte)の入れ物
  pD, pS: ^single;
  i:Integer;
begin
  pD := @arD;
  pS := @arS;
  for i := 0 to High(Coff4) do coff4[i] := coff;
  while Count >= 4 do     //SSE命令はWin64なら必須だから4個一度に計算可能
  begin
    Mul4(pD^, pS^,coff4[0]);
    Inc(pD, 4);
    Inc(pS, 4);
    Dec(Count, 4);
  end;

  while Count > 0 do //残りは最大3個で普通に計算
  begin
    pD^ := coff * pS^;
    Inc(pD);
    Inc(pS);
    Dec(Count, 1);
  end;
end;

//SSEで同時4つの浮動小数点演算を行う back_error[j]:=fOut[i][j]-Outputs[j]
procedure SUBPS4(var pD, pS, pC: single);
asm  //               RCX  RDX  R8
      MOVUPS  XMM0, [R8]     //XMM0=pC[0..3]
      MOVUPS  XMM1, [RDX]    //XMM1=pS[0..3]
      SUBPS   XMM1, XMM0     //XMM1=XMM1-XMM0
      MOVUPS  [RCX],XMM1     //pD=XMM1
end;
procedure SUBPS(var arD, arS, arC: single; Count: integer);
var
  pD, pS, pC: ^single;
  i:Integer;
begin
  pD := @arD;
  pS := @arS;
  pC := @arC;
  while Count >= 4 do     //SSE命令はWin64なら必須だから4個一度に計算可能
  begin
    SUBPS4(pD^, pS^, pC^);
    Inc(pD, 4);
    Inc(pS, 4);
    Inc(pC, 4);
    Dec(Count, 4);
  end;

  while Count > 0 do //残りは最大3個で普通に計算
  begin
    pD^ := pS^ - pC^;
    Inc(pD);
    Inc(pS);
    Inc(pC);
    Dec(Count, 1);
  end;
end;




{ TMamAnn }

function TMamAnn.ReLU(x: single): single;
begin
  if x>0 then result:=x else result:=0;
end;

function TMamAnn.ReLUDerivative(x: single): single;
begin
 if x>0 then result:=1 else result:=0;
end;

procedure TMamAnn.BackProp(Outputs:TArray<Single>);
var i,j{,k}:Integer;
    //loss_der:single;
    back_error:TArray<Single>;
    active_der:single;
    delta:single;
    sum_der_w,sum_der_x:TArray<Single>;
    sum_der_b:Single;
    layer_grads_x:TArray<Single>;
begin
//逆伝播とパラメータ更新
  SetLength(layer_grads_x,0);
  for i := (fLayerCount-1) downto 1 do
  begin
    if i=(fLayerCount-1) then
    begin
      //出力層の場合
      SetLength(back_error,Length(Outputs));
      SUBPS(back_error[0],fOut[i][0],Outputs[0],Length(Outputs));
    end
    else
    begin
      //隠れ層(次の層への入力の偏微分係数)
      //最後に追加された入力の勾配(layer_grads_x)をback_errorに入れる
      SetLength(back_error,Length(layer_grads_x));
      Move(
        layer_grads_x[0],
        back_error[0],
        SizeOf(layer_grads_x[0])*Length(layer_grads_x)
      );
    end;
    for j := Low(fSum[i]) to High(fSum[i]) do
    begin
      if i=(fLayerCount-1) then
      begin
        //出力層の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //恒等関数の導関数
          active_der:=1.0
        else
          //シグモイド関数の導関数
          active_der:=SigmoidDerivative(fSum[i][j]);
      end
      else
      begin
        //出力層以外の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //シグモイド関数の導関数
          active_der:=SigmoidDerivative(fSum[i][j])
        else
          //ReLU関数の導関数
          active_der:=ReLUDerivative(fSum[i][j]);
      end;

      //fOut[i-1]配列をsum_der_wにコピー
      SetLength(sum_der_w,Length(fOut[i-1]));
      Move(
        fOut[i-1][0],
        sum_der_w[0],
        Sizeof(fOut[i-1][0])*Length(fOut[i-1])
      );
      sum_der_b:=1.0;
      //fWeight[i-1][j]配列をsum_der_xにコピー
      SetLength(sum_der_x,Length(fWeight[i-1][j]));
      Move(
        fWeight[i-1][j][0], sum_der_x[0],
        Sizeof(fWeight[i-1][j][0])*Length(fWeight[i-1][j])
      );

      //勾配を計算
      delta:=back_error[j]*active_der;
      //勾配からバイアス計算してバイアスを更新する
      fBias[i-1][j]:=fBias[i-1][j]-delta*sum_der_b*fLearningRate;

      if j=0 then SetLength(layer_grads_x,Length(sum_der_w));

      //勾配から重みの計算を行い重みを更新する
      AVXcoffSum(
        fWeight[i-1][j][0],sum_der_w[0],-delta*fLearningRate,Length(sum_der_w));
      //入力は各ノードから前のノードに接続する全ての入力を合計する
      if j=0 then
      begin
        Mul(layer_grads_x[0],sum_der_x[0],delta,Length(layer_grads_x));
      end
      else
      begin
        AVXcoffSum(
          layer_grads_x[0],sum_der_x[0],delta,Length(sum_der_w));
      end;

    end;
  end;
end;

constructor TMamAnn.Create(
  NeuronCountInLayers:TArray<Integer>;LerningRate: Single=0.2;
  ActivationFunc:TMamActivationFunc=TMamActivationFunc.Sigmoid);
var i,j,k,num:Integer;
    scale:Single;
begin
  fLearningRate:=LerningRate;
  if fLearningRate>0.999 then fLearningRate:=0.999;
  if fLearningRate<0.001 then fLearningRate:=0.001;

  //Sigmoid または ReLU
  fActivationFunc:=ActivationFunc;

  fLoss:=0;
  fTrainCount:=0;

  fLayerCount:=Length(NeuronCountInLayers);
  SetLength(fNeuronCountInLayers,fLayerCount);
  Move(
    NeuronCountInLayers[0],
    fNeuronCountInLayers[0],
    SizeOf(NeuronCountInLayers[0])*Length(NeuronCountInLayers)
  );

  Randomize;

  //値初期化
  SetLength(fOut,fLayerCount);
  SetLength(fSum,fLayerCount);
  for i := Low(fOut) to High(fOut) do
  begin
    num:=fNeuronCountInLayers[i];
    SetLength(fOut[i],num);
    SetLength(fSum[i],num);
  end;

  //バイアス初期化(0にする)
  SetLength(fBias,fLayerCount-1);
  for i := Low(fBias) to High(fBias) do
  begin
    SetLength(fBias[i],fNeuronCountInLayers[i+1]);
    for j := Low(fBias[i]) to High(fBias[i]) do
      fBias[i][j]:=0;
  end;

  //重みづけ初期化
  SetLength(fWeight,fLayerCount-1);
  for i := Low(fWeight) to High(fWeight) do
  begin
    SetLength(fWeight[i],fNeuronCountInLayers[i+1]);
    for j := Low(fWeight[i]) to High(fWeight[i]) do
    begin
      SetLength(fWeight[i][j],Length(fOut[i]));
      if i=High(fWeight) then
      begin
          scale:=1/sqrt(Length(fWeight[i][j]));
      end
      else
      begin
        //乱数だけだと永遠にMSEが収束しない場合がある(1/20回くらい)
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //平均0、1/√nのガウス分布乱数
          scale:=1/sqrt(Length(fWeight[i][j]))
        else
          //平均0、√(2/(n1+n2))のガウス分布乱数
          scale:=sqrt(2/Length(fWeight[i][j]))
      end;
      for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
      begin
        if (fActivationFunc=TMamActivationFunc.Sigmoid) then
        begin
          if k=Low(fWeight[i][j]) then
            fWeight[i][j][k]:=scale
          else if k=High(fWeight[i][j]) then
            fWeight[i][j][k]:=-scale
          else
            fWeight[i][j][k]:=RandG(0,scale);
        end
        else
        begin
          if (i=High(fWeight[i])) then
          begin
            if k=Low(fWeight[i][j]) then
              fWeight[i][j][k]:=scale
            else if k=High(fWeight[i][j]) then
              fWeight[i][j][k]:=-scale
            else
              fWeight[i][j][k]:=RandG(0,scale);
          end
          else
            if k=Low(fWeight[i][j]) then
              fWeight[i][j][k]:=scale*3.5
            else if k=High(fWeight[i][j]) then
              fWeight[i][j][k]:=-scale*0.5
            else
              fWeight[i][j][k]:=RandG(0,scale);
        end;
      end;
    end;
  end;
end;


constructor TMamAnn.Create(Filename: String);
begin
  Randomize;
  Self.LoadFromFile(Filename);
end;

destructor TMamAnn.Destroy;
begin
  inherited;
end;

procedure TMamAnn.ForwardProp(Inputs: TArray<Single>);
var i,j{,k}:Integer;
    ww:Single;
begin //順伝播
  //入力層に入力値を入れる
  Move(Inputs[0],fSum[0][0],SizeOf(Inputs[0])*Length(Inputs));
  Move(Inputs[0],fOut[0][0],SizeOf(Inputs[0])*Length(Inputs));

  //入力層から中間層から出力層まで計算する
  for i := Low(fWeight) to High(fWeight) do
  begin
    for j := Low(fWeight[i]) to High(fWeight[i]) do
    begin
      //fSum[i+1][j]:=
      //        fOut[i][  0]*fWeight[i][j][  0]+
      //        fOut[i][  1]*fWeight[i][j][  1]+
      //        fOut[i][...]*fWeight[i][j][...]+fBias[i][j];
      //fOut[i*1][j]:=活性化関数(fSum[i+1][j]);

      ww:=fBias[i][j]+MulAdd(fOut[i][0],fWeight[i][j][0],Length(fOut[i]));

      fSum[i+1][j]:=ww;
      if i=(fLayerCount-2) then
      begin
        //出力層の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
        begin
          //恒等関数
          //ww:=ww;
        end
        else
        begin
          //シグモイド関数
          ww:=Sigmoid(ww);
        end;
      end
      else
      begin
        //出力層以外の場合
        if fActivationFunc=TMamActivationFunc.Sigmoid then
          //シグモイド関数
          ww:=Sigmoid(ww)
        else
          //ReLU関数
          ww:=ReLU(ww);
      end;
      fOut[i+1][j]:=ww;
    end;
  end;
end;

function TMamAnn.GetLearningRate: Single;
begin
  result:=fLearningRate;
end;

function TMamAnn.GetMSE: Single;
begin //MSE(平均二乗誤差)出力
  if fTrainCount>0 then
    result:=fLoss/fTrainCount
  else
    result:=0;
end;

procedure TMamAnn.LoadFromFile(FileName: String);
var strm:TMemoryStream;
    i,j,k:Integer;
    act:Integer;
begin //ファイルからの読み込み
  strm:=TMemoryStream.Create;
  try
    strm.LoadFromFile(FileName);
    strm.Position:=0;
    //活性化関数読み込み
    strm.ReadData(act);
    if act=0 then
      fActivationFunc:=TMamActivationFunc.Sigmoid
    else
      fActivationFunc:=TMamActivationFunc.ReLU;
    //層数読み込み
    strm.ReadData(fLayerCount);
    //層ごとのニューロン数読み込み
    setLength(fNeuronCountInLayers,fLayerCount);
    for i := 0 to fLayerCount-1 do
      strm.ReadData(fNeuronCountInLayers[i]);
    //学習率の読み込み
    strm.ReadData(fLearningRate);
    //損失値(累計2乗誤差)読み込み
    strm.ReadData(fLoss);
    //学習回数読み込み
    strm.ReadData(fTrainCount);
    //fSum,fOutの準備
    SetLength(fSum,fLayerCount);
    SetLength(fOut,fLayerCount);
    for i := 0 to fLayerCount-1 do
    begin
      SetLength(fSum[i],fNeuronCountInLayers[i]);
      SetLength(fOut[i],fNeuronCountInLayers[i]);
    end;
    //バイアス読み込み
    SetLength(fBias,fLayerCount-1);
    for i := Low(fBias) to High(fBias) do
    begin
      SetLength(fBias[i],fNeuronCountInLayers[i+1]);
      for j := Low(fBias[i]) to High(fBias[i]) do
      begin
        strm.ReadData(fBias[i][j]);
      end;
    end;
    //重み読み込み
    SetLength(fWeight,fLayerCount-1);
    for i := Low(fWeight) to High(fWeight) do
    begin
      SetLength(fWeight[i],fNeuronCountInLayers[i+1]);
      for j := Low(fWeight[i]) to High(fWeight[i]) do
      begin
        SetLength(fWeight[i][j],fNeuronCountInLayers[i]);
        for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
        begin
          strm.ReadData(fWeight[i][j][k]);
        end;
      end;
    end;
  finally
    strm.Free;
  end;
end;

procedure TMamAnn.Run(Inputs: TArray<Single>; out Outputs: TArray<Single>);
begin //予測の実行
  ForwardProp(Inputs);
  //出力層の値のコピー
  SetLength(Outputs,Length(fOut[High(fOut)]));
  Move(
    fOut[High(fOut)][0],
    Outputs[0],
    SizeOf(fOut[High(fOut)][0])*Length(fOut[High(fOut)])
  );
end;

procedure TMamAnn.SaveToFile(FileName: String);
var strm:TMemoryStream;
    i,j,k:Integer;
    act:Integer;
begin //バイナリで保存する
  strm:=TMemoryStream.Create();
  try
    //活性化関数保存
    if fActivationFunc=TMamActivationFunc.Sigmoid then
      act:=0
    else
      act:=1;
    strm.WriteData(act);
    //層数保存
    strm.WriteData(fLayerCount);
    //層ごとのニューロン数保存
    for i := Low(fNeuronCountInLayers) to High(fNeuronCountInLayers) do
      strm.WriteData(fNeuronCountInLayers[i]);
    //学習率保存
    strm.WriteData(fLearningRate);
    //損失値(累計2乗誤差)保存
    strm.WriteData(fLoss);
    //学習回数保存
    strm.WriteData(fTrainCount);
    //バイアス保存
    for i := Low(fBias) to High(fBias) do
      for j := Low(fBias[i]) to High(fBias[i]) do
        strm.WriteData(fBias[i][j]);
    //重み保存
    for i := Low(fWeight) to High(fWeight) do
      for j := Low(fWeight[i]) to High(fWeight[i]) do
        for k := Low(fWeight[i][j]) to High(fWeight[i][j]) do
          strm.WriteData(fWeight[i][j][k]);
    strm.SaveToFile(FileName);
  finally
    strm.Free;
  end;
end;

procedure TMamAnn.SetLearningRate(LearningRage:Single);
begin
  if LearningRate>0.999 then LearningRate:=0.999;
  if LearningRate<0.001 then LearningRate:=0.001;
  fLearningRate:=LearningRate;
end;

function TMamAnn.Sigmoid(x: Single): Single;
begin //シグモイド関数
  result:=1.0/(1.0+system.exp(-x))
end;

function TMamAnn.SigmoidDerivative(x:single): single;
begin //シグモイド関数の微分関数
  result:=Sigmoid(x);
  result:=result*(1.0-result);
end;

procedure TMamAnn.Train(Inputs, Outputs: Tarray<Single>);
var j:Integer;
    sub:Single;
begin //学習
  //順伝播
  ForwardProp(Inputs);
  //逆伝播
  BackProp(Outputs);
  //MSE(平均二乗誤差)の為に累計2乗誤差計算
  for j := Low(Outputs) to High(Outputs) do
  begin
    inc(fTrainCount);
    sub:=fOut[High(fOut)][j]-Outputs[j];
    fLoss:=fLoss+sub*sub;
  end;
end;

end.