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.
