FANN(Fast Artificial Neural Network Library)を使って手書き数字(0~9)の画像データセットMNISTを利用してみる ~Delphiでお手軽プログラミング
FANNを使用する為のファイルの準備
https://mam-mam.net/delphi/fann.html からfannfloat.dll、fann.pas、MamFann.pasをダウンロードする。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 2個、Edit 2個をフォームへドラッグ&ドロップします。
ソースコードを記述する
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
fann, MamFann ;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
Edit2: TEdit;
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
MamFann: TMamFann;
public
{ Public 宣言 }
end;
TFannData=packed record
id:Cardinal;
num:Cardinal;
row:Cardinal;
col:Cardinal;
data:array of array of Byte;
end;
TFannLabel=record
id:Cardinal;
num:Cardinal;
data:array of Byte;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var //stl,stll:TStringList;
i,j:integer;
NeuronNumInLayer:array of Cardinal;
inputs: array of TFann_type;
outputs: array of TFann_type;
//epoch:Integer; //一連の学習の実施回数
Mse: single; //誤差分散
fanndata:TFannData;
fannlabel:TFannLabel;
strm:TFileStream;
rbyte:array[0..3] of Byte;
epoch:Integer;//繰り返し学習の回数用
begin
//学習データの読み込み(60000個)
strm:=TFileStream.Create('.\train-images.idx3-ubyte',fmOpenRead);
strm.Position:=0;
strm.Read(rbyte[0],4);
fanndata.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.row:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.col:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
setlength(fanndata.data,fanndata.num);
for i := 0 to fanndata.num-1 do
begin
setlength(fanndata.data[i],fanndata.row*fanndata.col);
strm.Read(fanndata.data[i][0],fanndata.row*fanndata.col);
end;
strm.Free;
//学習ラベルの読み込み(60000個)
strm:=TFileStream.Create('.\train-labels.idx1-ubyte',fmOpenRead);
strm.Position:=0;
strm.Read(rbyte[0],4);
fannlabel.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fannlabel.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
setlength(fannlabel.data,fannlabel.num);
strm.Read(fannlabel.data[0],fannlabel.num);
strm.Free;
//TMamFannクラスのインスタンス化
if Assigned(MamFann) then
FreeAndNil(MamFann);
setlength(NeuronNumInLayer,3);//レイヤー(層)の数
//入力層のニューロン数
NeuronNumInLayer[0]:=fanndata.row*fanndata.col;
NeuronNumInLayer[1]:=350; //中間層のニューロン数
NeuronNumInLayer[2]:=10; //出力層のニューロン数
MamFann:=TMamFann.Create(NeuronNumInLayer);
//必要なら以下を入れる
//MamFann.TrainingAlgorithm:=TFann_train_enum.FANN_TRAIN_INCREMENTAL;
//MamFann.TrainingAlgorithm:=TFann_train_enum.FANN_TRAIN_RPROP;
setlength(outputs,10);
setlength(inputs,fanndata.row*fanndata.col);
//繰り返し学習を1回のみ行う
for epoch := 1 to 1 do
begin
for i := 0 to fanndata.num-1 do
begin
for j := 0 to fanndata.row*fanndata.col-1 do
begin
inputs[j]:=fanndata.data[i][j]/255;
end;
for j := 0 to 9 do
outputs[j]:=0;
outputs[fannlabel.data[i]]:=1;
MamFann.Train(inputs,outputs);
end;
end;
//学習結果をファイルに保存
//ロード[MamFann.LoadFromFile('a.net');]すれば学習結果を使用できる
MamFann.SaveToFile('a.net');
Mse:=MamFann.GetMSE;
Edit1.Text:=FloatToStr(Mse);
end;
procedure TForm1.Button2Click(Sender: TObject);
var i,j: integer;
inputs: array of TFann_type;
outputs: array of TFann_type;
fanndata:TFannData;
fannlabel:TFannLabel;
strm:TFileStream;
rbyte:array[0..3] of Byte;
num:integer;
rate:single;
hit:integer;
begin
if not Assigned(MamFann) then exit;
//テストデータの読み込み
strm:=TFileStream.Create('.\t10k-images.idx3-ubyte',fmOpenRead);
strm.Position:=0;
strm.Read(rbyte[0],4);
fanndata.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.row:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fanndata.col:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
setlength(fanndata.data,fanndata.num);
for i := 0 to fanndata.num-1 do
begin
setlength(fanndata.data[i],fanndata.row*fanndata.col);
strm.Read(fanndata.data[i][0],fanndata.row*fanndata.col);
end;
strm.Free;
//テストラベルの読み込み
strm:=TFileStream.Create('.\t10k-labels.idx1-ubyte',fmOpenRead);
strm.Position:=0;
strm.Read(rbyte[0],4);
fannlabel.id :=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
strm.Read(rbyte[0],4);
fannlabel.num:=(rbyte[0] shl 24)+(rbyte[1] shl 16)+(rbyte[2] shl 8)+rbyte[3];
setlength(fannlabel.data,fannlabel.num);
strm.Read(fannlabel.data[0],fannlabel.num);
strm.Free;
setlength(outputs,10);
setlength(inputs,fanndata.row*fanndata.col);
hit:=0;
for i := 0 to fanndata.num-1 do
begin
for j := 0 to fanndata.row*fanndata.col-1 do
begin
inputs[j]:=fanndata.data[i][j]/255;
end;
//テストデータを学習したFannに入れて、AIの結果を得る
MamFann.Run(inputs,outputs);
num:=0;
rate:=outputs[0];
for j := 1 to 9 do
begin
if outputs[j]>rate then
begin
rate:=outputs[j];
num:=j;
end;
end;
//Fann(AI)の結果が正答の場合
if fannlabel.data[i]=num then
begin
inc(hit);//正答数をインクリメントする
end;
end;
//正答率を表示する
Edit2.Text:=floattostr(hit/fannlabel.num);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(MamFann) then
FreeAndNil(MamFann);
end;
end.
実行する
実行ファイルが生成させるフォルダ内に「fannfloat.dll」を配置するのを忘れずに。(又はc:\windosフォルダ等に配置する)また、実行ファイルが生成させるフォルダ内に、上記で解凍した
「train-images.idx3-ubyte」(トレーニングデータ)、
「train-labels-idx1-ubyte」(トレーニングラベル)、
「t10k-images-idx3-ubyte」(テストデータ)、
「t10k-labels-idx1-ubyte」(テストラベル)
を入れておく。
実行ボタンを押して実行します。(デバッグ実行でもOK)
Button1をクリックすると、学習を開始し、学習が完了するとEdit1に「平均二乗誤差(MSE)」を表示します。
その後、Button2をクリックするとテストデータをAIに入力し、出力結果が正しいかどうかを判断し、AIの正答率をEdit2に表示します。
