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

FANN(Fast Artificial Neural Network Library)を使って手書き数字(0~9)の画像データセットMNISTを利用してみる ~Delphiでお手軽プログラミング

検索:

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