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

MNIST用の畳み込みニューラルネットワーク(CNN)をフルスクラッチで作成 ~Delphiソースコード集

検索:

MNIST用の畳み込みニューラルネットワーク(CNN)をフルスクラッチで作成 ~Delphiソースコード集

畳み込みニューラルネットワーク(Convolutional Neural Network)を使用する為のファイルの準備

プロジェクト用フォルダを作成し、
本ページ最下部にある「UMamCnn.pas」ソースコードを、プロジェクトフォルダ内に「UMamCnn.pas」ファイルとして保存します。
CNNの構造は以下となっています。

Delphiで畳み込みニューラルネットワーク(Convolutional Neural Network)を使用する

MNISTファイルのダウンロード

http://yann.lecun.com/exdb/mnist/
から
「train-images-idx3-ubyte.gz」(トレーニングデータ)、
「train-labels-idx1-ubyte.gz」(トレーニングラベル)、
「t10k-images-idx3-ubyte.gz」(テストデータ)、
「t10k-labels-idx1-ubyte.gz」(テストラベル)
ファイルをダウンロードして解凍し、プロジェクトフォルダ内に保存します。

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

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

Delphiで畳み込みニューラルネットワーク(Convolutional Neural Network)を使用する

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

ソースコードを記述する

Button1のOnClickイベント、Button2のOnClickイベント、Button3の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
  ,UMamCnn;

type

  TFannData=packed record
    id:Cardinal;
    num:Cardinal;
    row:Cardinal;
    col:Cardinal;
    data:array of array of array of Byte;
  end;
  TFannLabel=record
    id:Cardinal;
    num:Cardinal;
    data:array of Byte;
  end;


  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Button3: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
    cnn:TMamCnn;
    FannTrainData:TFannData;
    FannTrainLabel:TFannLabel;
    FannTestData:TFannData;
    FannTestLabel:TFannLabel;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
    i,j{,k,l,m,n}:integer;
    strm:TFileStream;
    rbyte:array[0..3] of Byte;
begin
  Button1.Enabled:=False;
  Button2.Enabled:=False;
  Button3.Enabled:=False;
  Sleep(100);
  Application.ProcessMessages;

  {
    訓練用画像ファイル「train-images.idx3-ubyte」の構造
    id:4byte   0x00000803(2051)
    num:4byte  60000
    row:4byte  28
    col:4byte  28
    data:num×row×col byte

    ビッグエンディアンなので、リトルエンディアンが基本のWindowsから読む場合は、
    1バイトずつ読んで反転して取得する必要がある
  }
  strm:=TFileStream.Create('..\..\train-images.idx3-ubyte',fmOpenRead);
  strm.Position:=0;
  strm.Read(rbyte[0],4);
  FannTrainData.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTrainData.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTrainData.row:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTrainData.col:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  setlength(FannTrainData.data,FannTrainData.num);
  for i := 0 to FannTrainData.num-1 do
  begin
    SetLength(FannTrainData.data[i],FannTrainData.row);
    for j:=0 to FannTrainData.row-1 do
    begin
      SetLength(FannTrainData.data[i][j],FannTrainData.col);
      strm.Read(FannTrainData.data[i][j][0],FannTrainData.col);
    end;
  end;
  strm.Free;

  {
    訓練用ラベルファイル「train-labels.idx1-ubyte」の構造
    id:4byte
    num:4byte
    data[id]=num;

    ビッグエンディアンなので、リトルエンディアンが基本のWindowsから読む場合は、
    1バイトずつ読んで反転して取得する必要がある
  }
  strm:=TFileStream.Create('..\..\train-labels.idx1-ubyte',fmOpenRead);
  strm.Position:=0;
  strm.Read(rbyte[0],4);
  FannTrainLabel.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTrainLabel.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  setlength(FannTrainLabel.data,FannTrainLabel.num);
  strm.Read(FannTrainLabel.data[0],FannTrainLabel.num);
  strm.Free;


  //テストデータの読み込み
  strm:=TFileStream.Create('..\..\t10k-images.idx3-ubyte',fmOpenRead);
  strm.Position:=0;
  strm.Read(rbyte[0],4);
  FannTestData.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTestData.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTestData.row:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTestData.col:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  SetLength(FannTestData.data,FannTestData.num);
  for i := 0 to FannTestData.num-1 do
  begin
    setlength(FannTestData.data[i],FannTestData.row);
    for j:=0 to FannTestData.row-1 do
    begin
      SetLength(FannTestdata.data[i][j],FannTestData.col);
      strm.Read(FannTestData.data[i][j][0],FannTestData.col);
    end;
  end;
  strm.Free;

  //テストラベルの読み込み
  strm:=TFileStream.Create('..\..\\t10k-labels.idx1-ubyte',fmOpenRead);
  strm.Position:=0;
  strm.Read(rbyte[0],4);
  FannTestLabel.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  strm.Read(rbyte[0],4);
  FannTestLabel.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
  setlength(FannTestLabel.data,FannTestLabel.num);
  strm.Read(FannTestLabel.data[0],FannTestLabel.num);
  strm.Free;

  Memo1.Lines.Add('MNISTデータの読み込みが完了しました');
  Button1.Enabled:=True;
  Button2.Enabled:=True;
  Button3.Enabled:=True;
end;


procedure TForm1.Button2Click(Sender: TObject);
var i,j,k:integer;
    e:Integer;//エポック(繰り返し学習)
    AInput: TArray<TArray<Single>>;
    AOutput:TArray<TArray<TArray<Single>>>;
begin
  Button1.Enabled:=False;
  Button2.Enabled:=False;
  Button3.Enabled:=False;
  Sleep(100);
  Application.ProcessMessages;

  for e := 1 to 1 do //繰り返し学習回数1
  begin
    SetLength(AInput,FannTrainData.row);
    for i:=0 to High(FannTrainData.data) do
    begin
      for j := Low(FannTrainData.data[i]) to High(FannTrainData.data[i]) do
      begin
        SetLength(AInput[j],FannTrainData.col);
        for k := Low(FannTrainData.data[i][j]) to High(FannTrainData.data[i][j]) do
        begin
          AInput[j][k]:=FannTrainData.data[i][j][k]/255;
        end;
      end;
      SetLength(AOutput,10);
      for j := Low(AOutput) to High(AOutput) do
      begin
        SetLength(AOutput[j],1);
        for k := Low(AOutput[j]) to High(AOutput[j]) do
        begin
          SetLength(AOutput[j][k],1);
          if j=FannTrainLabel.data[i] then
            AOutput[j][k][0]:=1
          else
            AOutput[j][k][0]:=0;
        end;
      end;
      cnn.Train(AInput,Aoutput);
      if(i mod 1000)=0 then
      begin
        Edit1.Text:=floattostr(cnn.GetMSE());
        Application.ProcessMessages;
      end;
    end;
  end;
  Edit1.Text:=floattostr(cnn.GetMSE());
  Application.ProcessMessages;

  Memo1.Lines.Add('MNISTデータの学習が完了しました');
  Button1.Enabled:=True;
  Button2.Enabled:=True;
  Button3.Enabled:=True;
end;


procedure TForm1.Button3Click(Sender: TObject);
var i,j,k,l:Integer;
    AInput: TArray<TArray<Single>>;
    AOutput:TArray<TArray<TArray<Single>>>;
    mx:Single;
    ans:Integer;
    CorrectRate:Single;
begin
  Button1.Enabled:=False;
  Button2.Enabled:=False;
  Button3.Enabled:=False;
  Sleep(100);
  Application.ProcessMessages;

  CorrectRate:=0;
  SetLength(AInput,FannTestData.row);
  for i := 0 to FannTestData.num-1 do
  begin
    for j := Low(FannTestData.data[i]) to High(FannTestData.data[i]) do
    begin
      SetLength(AInput[j],FannTestData.col);
      for k := Low(FannTestData.data[i][j]) to High(FannTestData.data[i][j]) do
      begin
        AInput[j][k]:=FannTestData.data[i][j][k]/255;
      end;
    end;
    AOutput:=cnn.Run(AInput);
    ans:=0;
    mx:=-1e37;
    for j := Low(AOutput) to High(AOutput) do
    begin
      if mx<AOutput[j][0][0] then
      begin
        ans:=j;
        mx:=AOutput[j][0][0];
      end;
    end;
    if ans=FannTestLabel.data[i] then
      CorrectRate:=CorrectRate+1;
  end;
  Memo1.Lines.Add(
    Format('正答率 %f',[CorrectRate/FannTestData.num])
  );
  Button1.Enabled:=True;
  Button2.Enabled:=True;
  Button3.Enabled:=True;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //メモリリークの検知
  ReportMemoryLeaksOnShutdown := True;
  cnn:=TMamCnn.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  cnn.Free;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOK)
Button1をクリックすると、MNISTの訓練データを読み込みます。

Delphiで畳み込みニューラルネットワーク(Convolutional Neural Network)でMNISTデータを読み込み

Button2をクリックするとMNISTの訓練データで学習を行います。

Delphiで畳み込みニューラルネットワーク(Convolutional Neural Network)でMNISTデータを学習する

Button3をクリックするとMNISTのテストデータでテストを行い正答率を表示します。
以下画像では、正答率が87%です。

Delphiで畳み込みニューラルネットワーク(Convolutional Neural Network)でMNISTデータを学習する

UMamCnn.pasソースコード

unit UMamCnn;

interface
  {
    MNIST 28x28(60000画像学習) モノクロ(1チャンネル) パディングは無しとする
    1層目 10カーネル17x17 stride=1(固定)
    ↓
    12x12
    1層目 10プーリング 2x2 max stride=2
    ↓
    ↓
    6x6
    2層目 10カーネル5x5 stride=1
    ↓
    2x2
    2層目 10プーリング 2x2 max stride=2
    ↓
    3層目(出力層)
    10x1x1

    CInput(fH:28,fW:28)MNIST
               padding=0 padding=0
               stride=1 fCStride=2
       fCSum   fCK      fCKOut          fCKHW(カーネルの幅と高さ)
               CKDer    fCKMax(fCKoutのMAX1以外0)
               AKDer    PoolingDer(プーリング層の逆伝播)
    i0 j0~j9  j0~j9   j0~j9
       k0~k27 k0~k16  k0~k11         17
       l0~l27 l0~l16  l0~l11

    i1 j0~j9  j0~j9   j0~j9
       k0~k5  k0~k4   k0~k1          5
       l0~l5  l0~l4   l0~l1

    i2 j0~j9
       k0~k0
       l0~l0
  }
  uses System.Math,Winapi.windows{,dialogs};

  type
    TMamCnn=class(TObject)
      private
        fCH,fCW:Integer;//入力層の縦横
        fCLNum:integer;//層数
        fCKNum:integer;//各層の重み(カーネル)の数 =10
        fCStride:Integer;//プーリング時のストライド(2)
        fCKHW:TArray<Integer>;//各層毎の各重み(カーネル)の縦と横のピクセル数

        //重み(カーネル)
        //    層×Kernel数×Height×Width の4次配列
        fCK:TArray<TArray<TArray<TArray<Single>>>>;
        //重み(カーネル)で畳み込み後の値
        //    層×Kernel数×Height×Width の4次配列
        fCKOut:TArray<TArray<TArray<TArray<Single>>>>;
        //順伝播のプーリング時に最大値の場所を保持(最大値=1,それ以外は0を入れる)
        //    層×Kernel数×Height×Width の4次配列
        fCKMax:TArray<TArray<TArray<TArray<Single>>>>;
        //順伝播のプーリング後の値を入れる
        //    層×Kernel数×Height×Width の4次配列
        fCSum:TArray<TArray<TArray<TArray<Single>>>>;
        fCB:TArray<TArray<Single>>;//バイアス

        fCLearningRate:Single;//学習率

        fTrainCount:Integer;
        fLoss:Single;
        procedure ForwardProp(CInput:TArray<TArray<Single>>);
        procedure BackProp(COutput:TArray<TArray<TArray<Single>>>);
      public
        constructor Create();
        destructor Destroy();override;
        function  Run(CInput:TArray<TArray<Single>>):TArray<TArray<TArray<Single>>>;
        procedure Train(CInput:TArray<TArray<Single>>;
                        COutput:TArray<TArray<TArray<Single>>>);
        function GetMSE:Single;
    end;

implementation

//1次配列の場合のsoftmax関数
procedure Softmax1(var source, dest: TArray<Single>);
var SumExp:Single;
    m:Single;
    i:Integer;
begin
  SetLength(dest,Length(Source));
  SumExp:=0;
  m:=-3.40e+38;
  for i := Low(source) to High(source) do
    m:=max(m,source[i]);
  for i := Low(source) to High(source) do
  begin
    dest[i]:=System.Exp(source[i]-m);
    SumExp:=SumExp+dest[i];
  end;
  for i := Low(dest) to High(dest) do
    dest[i]:=dest[i]/SumExp;
end;

//1次配列の場合のsoftmax導関数
procedure SoftmaxDerivative1(var source, dest: TArray<Single>);
var i:Integer;
begin
  SetLength(dest,Length(Source));
  Softmax1(source,dest);
  for i := Low(dest) to High(dest) do
  begin
    dest[i]:=dest[i]*(1-dest[i]);
  end;
end;

//3次配列の場合のsoftmax関数
procedure Softmax3(var source, dest: TArray<TArray<TArray<Single>>>);
var SumExp:Single;
    m:Single;
    i,j,k:Integer;
begin
  SetLength(dest,Length(Source));
  SumExp:=0;
  m:=-3.40e+38;
  for i := Low(source) to High(source) do
  begin
    SetLength(dest[i],Length(Source[i]));
    for j := Low(source[i]) to High(source[i]) do
    begin
      SetLength(dest[i][j],Length(Source[i][j]));
      for k := Low(source[i][j]) to High(source[i][j]) do
        m:=max(m,source[i][j][k]);
    end;
  end;
  //m:=0;
  for i := Low(source) to High(source) do
  begin
    for j := Low(source[i]) to High(source[i]) do
      for k := Low(source[i][j]) to High(source[i][j]) do
      begin
        dest[i][j][k]:=System.Exp(source[i][j][k]-m);
        SumExp:=SumExp+dest[i][j][k];
      end;
  end;
  for i := Low(dest) to High(dest) do
    for j := Low(dest[i]) to High(dest[i]) do
      for k := Low(dest[i][j]) to High(dest[i][j]) do
        dest[i][j][k]:=dest[i][j][k]/SumExp;
end;

//3次配列の場合のsoftmax導関数
procedure SoftmaxDerivative3(var source, dest: TArray<TArray<TArray<Single>>>);
var i,j,k:Integer;
begin
  SetLength(dest,Length(Source));
  for i := Low(Source) to High(Source) do
  begin
    SetLength(dest[i],Length(Source[i]));
    for j := Low(Source[i]) to High(Source[i]) do
      SetLength(dest[i][j],Length(Source[i][j]));
  end;

  Softmax3(source,dest);
  for i := Low(dest) to High(dest) do
  begin
    for j := Low(dest[i]) to High(dest[i]) do
    begin
      for k := Low(dest[i][j]) to High(dest[i][j]) do
      begin
        dest[i][j][k]:=dest[i][j][k]*(1-dest[i][j][k]);
      end;
    end;
  end;
end;

//ReLU関数
function ReLU(x: single): single;
begin
  //if x>0 then result:=x else result:=0;
  result:=System.Math.Max(x,0);
end;

//ReLUの導関数
function ReLUDerivative(x: single): single;
begin
 if x>0 then result:=1 else result:=0;
end;

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

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



{ TMamCnn }

procedure TMamCnn.BackProp(COutput: TArray<TArray<TArray<Single>>>);
var i,j,k,l:Integer;
    m,n:Integer;
    row,col:Integer;
    //プーリング層で逆伝播する誤差情報(∂E/∂y)
    PoolingDer:TArray<TArray<TArray<Single>>>;
    //重み(カーネル)の勾配
    AKGrad:TArray<TArray<TArray<Single>>>;
    //「重み(カーネル)」(fCK)にゼロパディングした値を入れる
    ACKPad:TArray<TArray<TArray<Single>>>;
    der:Single;
    //ASumDer:TArray<TArray<TArray<Single>>>;
    padh,padw:Integer;
    xDer:TArray<TArray<TArray<Single>>>;//入力層の逆伝播
    ActiveDer:TArray<TArray<TArray<Single>>>;//導関数値を入れる
begin
  //出力層の値に活性化関数softmax導関数を適用した値を取得する
  SoftmaxDerivative3(fCSum[high(fCSum)],ActiveDer);

  {
  //活性化関数シグモイドの場合
  SetLength(ActiveDer,Length(fCSum[High(fCSum)]));
  for j := Low(fCSum[High(fCSum)]) to High(fCSum[High(fCSum)]) do
  begin
    SetLength(ActiveDer[j],Length(fCSum[High(fCSum)][j]));
    for k := Low(fCSum[High(fCSum)][j]) to High(fCSum[High(fCSum)][j]) do
    begin
      SetLength(ActiveDer[j][k],Length(fCSum[High(fCSum)][j][k]));
      for l := Low(fCSum[High(fCSum)][j][k]) to High(fCSum[High(fCSum)][j][k]) do
      begin
        ActiveDer[j][k][l]:=Sigmoid(fCSum[High(fCSum)][j][k][l]);
      end;
    end;
  end;
  }


  //出力層から入力層まで逆伝播
  for i := High(fCKMax) downto Low(fCKMax) do
  begin
    SetLength(AKGrad,Length(fCK[i]));
    SetLength(ACKPad,Length(fCK[i]));
    SetLength(xDer,Length(fCSum[i]));
    SetLength(PoolingDer,Length(fCKMax[i]));
    //各層の重み(カーネル)の数だけループ
    for j := Low(fCKMax[i]) to High(fCKMax[i]) do
    begin
      {
        ■プーリング層の逆伝播  ∂E/∂y
        順伝播での入力層fCKOutで最大値だった位置(fCKMAX)に
        入力層の逆伝播の値を入れる

        順伝播
          fCKout[i]→2x2の最大値stride=2→fCSum[i+1]
          l0 l1 l2 l3    最大値の位置     l0 l1
       k0  0  1  2  3    k1l1 k1l3  → k0  3  5
       k1  2  3  4  5                  k1  2  2
       k2  1  2  1  1    k2l1 k3l2  →───┘
       k3  0  0  2  1       │
                            │
        逆伝播              ↓
          AGrad ←最大の位置に誤差を配置←x or COutput
          l0 l1 l2 l3                     l0 l1
       k0  0  0  0  0                  k0  1  3
       k1  0  1  0  3                  k1  4  5
       k2  0  4  0  0
       k3  0  0  5  0
           ↑実際には誤差情報(出力値-正解値)を入れる
      }
      SetLength(PoolingDer[j],Length(fCKMax[i][j]));
      for k := Low(fCKMax[i][j]) to High(fCKMax[i][j]) do
      begin
        SetLength(PoolingDer[j][k],Length(fCKMax[i][j][k]));
      end;

      if i=High(fCKMax) then
      begin
        //出力層の場合
        for k := Low(fCKMax[i][j]) to High(fCKMax[i][j]) do
        begin
          for l := Low(fCKMax[i][j][k]) to High(fCKMax[i][j][k]) do
          begin
            //活性化関数値 × 誤差情報(出力層の値-正解値)
            //を順伝播で採用した重み(カーネル)の最大値の位置に配置したもの
            PoolingDer[j][k][l]:=
              ActiveDer[j][k div fCStride][l div fCStride]*
              fCKMax[i][j][k][l]*
              ( fCSum[i+1][j][k div fCStride][l div fCStride]-
                COutput[j][(k div fCStride)][(l div fCStride)] );
          end;
        end;
      end
      else
      begin
        //出力層以外の場合
        for k := Low(fCKMax[i][j]) to High(fCKMax[i][j]) do
        begin
          for l := Low(fCKMax[i][j][k]) to High(fCKMax[i][j][k]) do
          begin
            //逆伝播してきたxDerを
            //順伝播で採用した重み(カーネル)の最大値の位置に配置したもの
            PoolingDer[j][k][l]:=
              fCKMax[i][j][k][l]*
              xDer[j][(k div fCStride)][(l div fCStride)];
          end;
        end;
      end;

      //■重み(カーネル)の勾配 ∂E/∂w
      //⇒プーリング層から伝播した誤差(PoolingDer)に入力(fCSum)を掛ける
      SetLength(AKGrad[j],Length(fCK[i][j]));
      for k := Low(fCK[i][j]) to High(fCK[i][j]) do
        SetLength(AKGrad[j][k],Length(fCK[i][j][k]));
      for k := Low(AKGrad[j]) to High(AKGrad[j]) do
      begin
        for l := Low(AKGrad[j][k]) to High(AKGrad[j][k]) do
        begin
          der:=0;
          for m := Low(PoolingDer[j])to High(PoolingDer[j]) do
          begin
            for n := Low(PoolingDer[j][m]) to High(PoolingDer[j][m]) do
            begin
              der:=der+fCSum[i][j][k+m][l+n]*PoolingDer[j][m][n];
            end;
          end;
          AKGrad[j][k][l]:=der;
        end;
      end;

      //■「入力の逆伝播」(Xder)
      //「重み(カーネル)」(fCK)から
      //「上下左右にゼロパディングを追加した重み(カーネル)」(ACKPad)を作成し、
      //PoolingDerの範囲内にあるACKPadの上下左右反転にPoolingDerを掛けたもの
      padh:=High(PoolingDer[j]);
      padw:=High(PoolingDer[j][0]);
      //縦方向上下にパディング追加
      SetLength(ACKPad[j],Length(AKGrad[j])+padh*2);
      for k := Low(ACKPad[j]) to High(ACKPad[j]) do
      begin
        //横方向左右にパディング追加
        SetLength(ACKPad[j][k],Length(AKGrad[j][0])+padw*2);
        for l := Low(ACKPad[j][k]) to High(ACKPad[j][k]) do
        begin
          //ゼロに初期化
          ACKPad[j][k][l]:=0;
        end;
      end;
      for k := Low(fCK[i][j]) to High(fCK[i][j]) do
      begin
        for l := Low(fCK[i][j][k]) to High(fCK[i][j][k]) do
        begin
          ACKPad[j][k+padh][l+padw]:=fCK[i][j][k][l];
        end;
      end;
      SetLength(xDer[j],Length(fCSum[i][j]));
      for k := Low(xDer[j]) to High(xDer[j]) do
      begin
        SetLength(xDer[j][k],Length(fCSum[i][j][k]));
        for l := Low(xDer[j][k]) to High(xDer[j][k]) do
        begin
          der:=0;
          row:=High(PoolingDer[j]);
          col:=High(PoolingDer[j][0]);
          for m := Low(PoolingDer[j]) to High(PoolingDer[j]) do
            for n := Low(PoolingDer[j][m]) to High(PoolingDer[j][m]) do
              der:=der+ACKPad[j][k+(row-m)][l+(col-n)]*PoolingDer[j][m][n];
          xDer[j][k][l]:=der;
        end;
      end;

      //■重み(カーネル)の更新
      //W=Wー学習率×∂E/∂w
      //                  ↑勾配
      for k := Low(fCK[i][j]) to High(fCK[i][j]) do
      begin
        for l := Low(fCK[i][j][k]) to High(fCK[i][j][k]) do
        begin
          fCK[i][j][k][l]:=fCK[i][j][k][l]-fCLearningRate*(AKGrad[j][k][l]);
        end;
      end;

      //■バイアスの更新
      //B=B-η×Σ∂E/∂y
      der:=0;
      for k := Low(PoolingDer[j])to High(PoolingDer[j]) do
      begin
        for l := Low(PoolingDer[j][k])to High(PoolingDer[j][k]) do
        begin
          der:=der+PoolingDer[j][k][l];
        end;
      end;
      fCB[i][j]:=fCB[i][j]-fCLearningRate*der;
    end;
  end;

end;

constructor TMamCnn.Create;
var i,j,k,l:Integer;
begin
  Randomize;
  fTrainCount:=0;
  fLoss:=0;
  fCLearningRate:=0.05; //学習率 5%
  fCLNum:=3;  //層の数
  fCH:=28;    //入力層の高さ
  fCW:=28;    //入力層の幅
  fCKNum:=10; //各層の重み(カーネル)の数
  SetLength(fCKHW,fCLnum-1);////層の数-1
  fCKHW[0]:=17;//1層目の各重み(カーネル)の高さと幅(高さ=幅)
  fCKHW[1]:=5; //2層目の各重み(カーネル)の高さと幅(高さ=幅)
  //畳み込みはストライド=1、パディングは無しとする

  //プーリング処理は、2x2領域固定でパディング無しとする
  fCStride:=2;//プーリング時のストライド

  SetLength(fCSum,fCLNum);
  for i := Low(fCSum) to High(fCSum) do
  begin
    SetLength(fCSum[i],fCKNum);
    for j := Low(fCSum[i]) to High(fCSum[i]) do
    begin
      if i=0 then //入力層の場合
        SetLength(fCSum[i][j],fCH)
      else
      begin //入力層以外
        //切り上げ
        k:=System.Math.Ceil((Length(FCSum[i-1][0])-fCKHW[i-1]+1)/fCStride);
        SetLength(fCSum[i][j],k);
      end;
      for k := Low(fCSum[i][j]) to High(fCSum[i][j]) do
      begin
        if i=0 then //入力層の場合
          SetLength(fCSum[i][j][k],fCW)
        else
        begin //入力層以外
          //切り上げ
          l:=System.Math.Ceil((Length(fCSum[i-1][j][k])-fCKHW[i-1]+1)/fCStride);
          SetLength(fCSum[i][j][k],l);
        end;
      end;
    end;
  end;

  //重み(カーネル)の初期化
  SetLength(fCK,fCLNum-1);//2
  for i := Low(fCK) to High(fCK) do
  begin
    SetLength(fCK[i],fCKNum);
    for j := Low(fCK[i]) to High(fCK[i]) do
    begin
      SetLength(fCK[i][j],fCKHW[i]);
      for k := Low(fCK[i][j]) to High(fCK[i][j]) do
      begin
        SetLength(fCK[i][j][k],fCKHW[i]);
        for l := Low(fCK[i][j][k]) to High(fCK[i][j][k]) do
        begin
          fCK[i][j][k][l]:=(Random()*1)/Length(fCK[i][j])/Length(fCK[i][j][k]);
        end;
      end;
    end;
  end;

  SetLength(fCKOut,fCLNum-1);//層の数-1
  SetLength(fCKMax,fCLNum-1);//層の数-1
  for i := Low(fCKOut) to High(fCKOut) do
  begin
    SetLength(fCKOut[i],fCKNum);
    SetLength(fCKMax[i],fCKNum);
    for j := Low(fCKOut[i]) to High(fCKOut[i]) do
    begin
      SetLength(fCKOut[i][j],
        Length(fCSum[i][j])-Length(fCK[i][j])+1);
      SetLength(fCKMax[i][j],Length(fCKOut[i][j]));
      for k := Low(fCKOut[i][j]) to High(fCKOut[i][j]) do
      begin
        SetLength(fCKOut[i][j][k],
          Length(fCSum[i][j][0])-Length(fCK[i][j][0])+1);
        SetLength(fCKMax[i][j][k],Length(fCKOut[i][j][k]));
      end;
    end;
  end;

  //バイアスの初期化
  SetLength(fCB,fCLNum-1);//2
  for i := Low(fCB) to High(fCB) do
  begin
    SetLength(fCB[i],fCKNum);
    ZeroMemory(@fCB[i][0],fCKNum*SizeOf(fCB[i][0]));
  end;

end;

destructor TMamCnn.Destroy;
begin

  inherited;
end;

procedure TMamCnn.ForwardProp(CInput:TArray<TArray<Single>>);
var i,j,k,l,h,w,hmax,wmax{,hs,ws}:Integer;
    v:Single;
    s,sm,av:single;
begin
  i:=0;
  //入力層の値を入れる
  for j := Low(fCSum[i]) to High(fCSum[i]) do
  begin
    for k := Low(fCSum[i][j]) to High(fCSum[i][j]) do
    begin
      Move(CInput[k][0],fCSum[i][j][k][0],
           Length(fCSum[i][j][k])*SizeOf(fCSum[i][j][k][0]));
    end;
  end;
  for i := Low(fCKOut) to High(fCKOut) do
  begin
    sm:=0;
    for j := Low(fCKOut[i]) to High(fCKOut[i]) do
    begin
      //畳み込み処理
      for k := Low(fCKOut[i][j]) to High(fCKOut[i][j]) do
      begin
        for l := Low(fCKOut[i][j][k]) to High(fCKOut[i][j][k]) do
        begin
          v:=0;
          for h := Low(fCK[i][j]) to High(fCK[i][j]) do
          begin
            for w := Low(fCK[i][j][h]) to High(fCK[i][j][h]) do
            begin
              v:=v+fCSum[i][j][k+h][l+w]*fCK[i][j][h][w];
            end;
          end;
          fCKOut[i][j][k][l]:=v+fCB[i][j];
        end;
      end;
      //最大プーリング処理 2x2 ストライド=2 ⇒縦横半分のサイズになる
      //最大値の位置を保持しておく
      for k:=Low(fCKMax[i][j]) to High(fCKMax[i][j]) do
        ZeroMemory(@fCKMax[i][j][k][0],
          Length(fCKMax[i][j][k])*SizeOf(fCKMax[i][j][k][0]));
      for k := Low(fCSum[i+1][j]) to High(fCSum[i+1][j]) do
      begin
        for l := Low(fCSum[i+1][j][k]) to High(fCSum[i+1][j][k]) do
        begin
          v:=-1e38;
          hmax:=0;
          wmax:=0;
          for h := 0 to fCStride-1 do
          begin
            if High(fCKOut[i][j])>=(k*fCStride+h) then
            begin
              for w := 0 to fCStride-1 do
              begin
                if High(fCKOut[i][j][k*fCStride+h])>=(l*fCStride+w) then
                begin
                  if v<fCKOut[i][j][k*fCStride+h][l*fCStride+w] then
                  begin
                    hmax:=h;
                    wmax:=w;
                    v:=fCKOut[i][j][k*fCStride+h][l*fCStride+w];
                  end;
                end;
              end;
            end;
          end;
          fCKMax[i][j][k*fCStride+hmax][l*fCStride+wmax]:=1;
          fCSum[i+1][j][k][l]:=v;
        end;
      end;
    end;
  end;
end;

function TMamCnn.GetMSE: Single;
begin
  if fTrainCount>0 then
    result:=fLoss/fTrainCount
  else
    result:=0;
end;

function TMamCnn.Run(CInput: TArray<TArray<Single>>):TArray<TArray<TArray<Single>>>;
var o:TArray<TArray<TArray<Single>>>;
begin
  ForwardProp(CInput);
  Softmax3(fCSum[High(fCSum)],o);
  result:=o;
end;

procedure TMamCnn.Train(CInput: TArray<TArray<Single>>;
  COutput: TArray<TArray<TArray<Single>>>);
var j,k,l:Integer;
    sub:Single;
    o:TArray<TArray<TArray<Single>>>;
begin
  ForwardProp(CInput);
  BackProp(COutput);
  //活性化関数Softmaxの場合
  Softmax3(fCSum[High(fCSum)],o);

  {
  //活性化関数シグモイドの場合
  SetLength(o,Length(fCSum[High(fCSum)]));
  for j := Low(fCSum[High(fCSum)]) to High(fCSum[High(fCSum)]) do
  begin
    SetLength(o[j],Length(fCSum[High(fCSum)][j]));
    for k := Low(fCSum[High(fCSum)][j]) to High(fCSum[High(fCSum)][j]) do
    begin
      SetLength(o[j][k],Length(fCSum[High(fCSum)][j][k]));
      for l := Low(fCSum[High(fCSum)][j][k]) to High(fCSum[High(fCSum)][j][k]) do
      begin
        o[j][k][l]:=Sigmoid(fCSum[High(fCSum)][j][k][l]);
      end;
    end;
  end;
  }

  for j := Low(COutput) to High(COutput) do
  begin
    for k := Low(COutput[j]) to High(COutput[j]) do
    begin
      for l := Low(COutput[j][k]) to High(COutput[j][k]) do
      begin
        inc(fTrainCount);
        sub:=o[j][k][l]-COutput[j][k][l];
        fLoss:=fLoss+sub*sub;
      end;
    end;
  end;
end;

end.