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に表示します。