MNIST用の畳み込みニューラルネットワーク(CNN)をフルスクラッチで作成 ~Delphiソースコード集
畳み込みニューラルネットワーク(Convolutional Neural Network)を使用する為のファイルの準備
プロジェクト用フォルダを作成し、
本ページ最下部にある「UMamCnn.pas」ソースコードを、プロジェクトフォルダ内に「UMamCnn.pas」ファイルとして保存します。
CNNの構造は以下となっています。
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個、をフォームへドラッグ&ドロップします。
「ファイル」⇒「全て保存」でフォルダを作成して、プロジェクトとユニットを保存します。
プロジェクトフォルダ内に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の訓練データを読み込みます。
Button2をクリックするとMNISTの訓練データで学習を行います。
Button3をクリックするとMNISTのテストデータでテストを行い正答率を表示します。
以下画像では、正答率が87%です。
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.