Szöveg generálása Genetikus
algoritmussal
Írjunk programot, mely egy beviteli mezőben megadható
szöveget állít elő Genetikus algoritmus segítségével.
A program alkalmazza a Genetikus algoritmus
hagyományos lépéseit. Kezdetként állítson elő egy 40 egyedből álló populációt.
A szöveg hosszának felével hajtson végre keresztezéseket a jó egyedek között és
a leggyengébb egyedeket ezekkel helyettesítse. Ha a populáció túlságosan
homogénné válna, akkor alkalmazzon mutációt.
A jóság megfogalmazása büntetőpontokkal történjen.
Minél távolabb van két szöveg egymástól, annál nagyobb legyen a büntetőpont
értéke. Ennek megvalósításához írjunk egy függvényt két azonos hosszúságú
szöveg távolságára. A függvény visszaadott értéke a két szövegben ugyanazon
helyen lévő karaktereknek, kódjaiban mért távolságának összege legyen. Ha a távolság nulla, akkor a két szöveg értelemszerűen
azonos. Generáláskor tehát most minimumot, illetve nulla távolságot fogunk
keresni.
A populáció elemeit egy listadobozban, és finess
értékeit egy mellette illesztett módon elhelyezett másik listadobozban
helyezzük el. A program futása közben a listák akkor frissüljenek, amikor jobb
tulajdonságú elemet találunk az előzőeknél. A generálás maximum kétszázezer
generációig tartson.
A
program futási képe közvetlenül a populáció generálása után:

A
program futási képe generálás közben:

A
program futási képe a keresés befejezés után:

A
program listája:
unit UGenSzov;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
Const EgyedSzM=100;
GAl=5;
type
TfmGenSzov = class(TForm)
btKilepes: TButton;
lbSzoveg: TLabel;
edSzoveg: TEdit;
ldGenSzov: TListBox;
lbEgyedSz: TLabel;
edEgyedSz: TEdit;
lbPopSz: TLabel;
edPopSz: TEdit;
lbPopMaxSz: TLabel;
edPopMaxSz: TEdit;
btStart: TButton;
lbKereszt: TLabel;
edKereszt: TEdit;
lbMutSz: TLabel;
edMutSz: TEdit;
btUjPop: TButton;
ldPop: TListBox;
lbMinimum: TLabel;
edMin: TEdit;
Procedure PopKepre;
Procedure PopInit;
Function StrDist(S1, S2: String): Word;
Procedure Keresztez;
Procedure Mutacio;
Procedure Vizsgal;
procedure btKilepesClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btUjPopClick(Sender: TObject);
procedure edSzovegChange(Sender: TObject);
procedure edEgyedSzChange(Sender: TObject);
procedure edKeresztChange(Sender: TObject);
procedure edMutSzChange(Sender: TObject);
procedure edPopMaxSzChange(Sender: TObject);
procedure btStartClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TEgyed=Record
ESz: String;
EOK: Word;
EJo: Boolean;
End;
var
fmGenSzov: TfmGenSzov;
Szoveg: String;
EgyedT: Array[1..EgyedSzM] Of TEgyed;
Uj1, Uj2: TEgyed;
EgyedSz, MutSz, Kereszt, ELen: Word;
PopSz, PopMaxSz: LongInt;
IR1, IR2, IJ1, IJ2, OKMin, IMin, OldMin, JokSz: LongInt;
Josag: Real;
implementation
{$R *.dfm}
procedure TfmGenSzov.btKilepesClick(Sender: TObject);
begin
Close;
end;
procedure TfmGenSzov.FormCreate(Sender: TObject);
begin
Randomize; Szoveg:= 'Genetikus algoritmussal';
EgyedSz:= 40; Kereszt:= 11; MutSz:= 1; PopMaxSz:= 200000;
end;
procedure TfmGenSzov.edSzovegChange(Sender: TObject);
begin
Szoveg:= edSzoveg.Text; ELen:= Length(Szoveg);
Kereszt:= ELen Div 2; edKereszt.Text:= IntToStr(Kereszt);
end;
procedure TfmGenSzov.edEgyedSzChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edEgyedSz.Text,EgyedSz,Kod);
end;
procedure TfmGenSzov.edKeresztChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edKereszt.Text,Kereszt,Kod);
end;
procedure TfmGenSzov.edMutSzChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edMutSz.Text,MutSz,Kod);
end;
procedure TfmGenSzov.edPopMaxSzChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edPopMaxSz.Text,PopMaxSz,Kod);
end;
Procedure TfmGenSzov.PopInit;
Var I, J: Word;
Begin
ELen:= Length(Szoveg);
For I:= 1 To EgyedSz Do With EgyedT[I] Do
Begin
SetLength(ESz,ELen); For J:= 1 To ELen Do ESz[J]:= Chr(Random(256));
EOK:= 0; EJo:= False;
End;
End;
Function TfmGenSzov.StrDist(S1, S2: String): Word;
Var I, D: Word;
Begin
D:= 0; StrDist:= D;
If Length(S1)<>Length(S2) Then Exit; ELen:= Length(S1); If ELen=0 Then Exit;
For I:= 1 To ELen Do Inc(D,Abs(Ord(S1[I])-Ord(S2[I]))); StrDist:= D;
End;
Procedure TfmGenSzov.Keresztez;
Var I, V, R: Word;
Begin
Inc(PopSz); edPopSz.Text:= IntToStr(PopSz); edPopSz.Repaint;
SetLength(Uj1.ESz,ELen); SetLength(Uj2.ESz,ELen);
V:= Random(EgyedSz)+1; R:= 0; IR1:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do
If Not EJo And (EOK>R) Then Begin R:= EOK; IR1:= I End;
For I:= 1 To V Do With EgyedT[I] Do
If Not EJo And (EOK>R) Then Begin R:= EOK; IR1:= I End;
V:= Random(EgyedSz)+1; R:= 0; IR2:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IR1 Then
If Not EJo And (EOK>R)Then Begin R:= EOK; IR2:= I End;
For I:= 1 To V Do With EgyedT[I] Do If I<>IR1 Then
If Not EJo And (EOK>R) Then Begin R:= EOK; IR2:= I End;
V:= Random(EgyedSz)+1; R:= 65000; IJ1:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do
If EOK<R Then Begin R:= EOK; IJ1:= I End;
For I:= 1 To V Do With EgyedT[I] Do
If EOK<R Then Begin R:= EOK; IJ1:= I End;
V:= Random(EgyedSz)+1; IJ2:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IJ1 Then
If EJo Then IJ2:= I;
For I:= 1 To V Do With EgyedT[I] Do If I<>IJ1 Then
If EJo Then IJ2:= I;
If IJ1*IJ2<>0 Then
Begin
For I:= 1 To Kereszt Do Uj1.ESz[I]:= EgyedT[IJ1].ESz[I];
For I:= Kereszt+1 To ELen Do Uj1.ESz[I]:= EgyedT[IJ2].ESz[I];
For I:= 1 To Kereszt Do Uj2.ESz[I]:= EgyedT[IJ2].ESz[I];
For I:= Kereszt+1 To ELen Do Uj2.ESz[I]:= EgyedT[IJ1].ESz[I];
End;
End;
Procedure TfmGenSzov.Mutacio;
Var I, K: Word;
Begin
Inc(PopSz); edPopSz.Text:= IntToStr(PopSz); edPopSz.Repaint;
For K:= 1 To EgyedSz Do With EgyedT[K] Do
For I:= 1 To ELen Do If Random(1000)<MutSz Then ESz[I]:= Chr(Random(256));
End;
Procedure TfmGenSzov.PopKepre;
Var I: Word;
Begin
With ldGenSzov Do
Begin
Clear;
For I:= 1 To EgyedSz Do Items.Add(EgyedT[I].ESz); RePaint;
End;
With ldPop Do
Begin
Clear;
For I:= 1 To EgyedSz Do Items.Add(IntToStr(StrDist(Szoveg,EgyedT[I].ESz)));
RePaint;
End;
edMin.Text:= IntToStr(OKMin); edMin.RePaint;
End;
procedure TfmGenSzov.btUjPopClick(Sender: TObject);
begin
PopInit; PopKepre; edPopSz.Text:= ''; edMin.Text:= '';
end;
Procedure TfmGenSzov.Vizsgal;
Var K: Word;
Begin
JokSz:= 0; OKMin:= 65000;
For K:= 1 To EgyedSz Do With EgyedT[K] Do
Begin
EOK:= StrDist(Szoveg,ESz);
If EOK<OKMin Then Begin OKMin:= EOK; IMin:= K End;
Inc(JokSz,EOK);
End;
Josag:= JokSz/EgyedSz; JokSz:= 0;
For K:= 1 To EgyedSz Do With EgyedT[K] Do If EOK<Josag Then
Begin Inc(JokSz); EJo:= True End Else EJo:= False;
End;
procedure TfmGenSzov.btStartClick(Sender: TObject);
begin
OKMin:= 65000; IMin:= 0; PopSz:= 1;
Repeat
OldMin:= OKMin; Keresztez;
If (JokSz<GAl) Or (EgyedSz-JokSz<GAl) Then Mutacio Else
If (IR1*IR2<>0) And (IJ1*IJ2<>0) Then
Begin EgyedT[IR1]:= Uj1; EgyedT[IR2]:= Uj2 End;
Vizsgal; If OKMin<OldMin Then PopKepre;
Until (OKMin=0) Or (PopSz>PopMaxSz);
ldGenSzov.ItemIndex:= IMin-1; ldPop.ItemIndex:= IMin-1;
end;
end.
Let’s write a program demonstrating permutational
operators. The well-known operators are the next ones:
- Partial Matched Crossover (PMX)
- Order Crossover (OX)
- Cycle Crossover (CX).
In genetic algorithms
crossover is a genetic operator used to vary the programming of a chromosome
(example: inversion operator) or two chromosomes from one generation to the
next. It is similar to reproduction and biological crossover on which genetic
algorithms are based.
The aim of the crossover
operator is to interchange the information and genes between chromosomes.
Therefore crossover operator combines two parents to reproduce new children,
then one of these children may hopefully collect all good features that exist
in parents.
This screen-shots made in run time, Partial Matched Crossover (PMX):

Order Crossover (OX):

Cycle Crossover (CX):

We have to select in
crossovers to choose number of genes, and in case of PMX and OX two cut spaces.
The genes of parents setup the program (random values), or we fix by +/- signs.
The list of program:
unit UCODemo;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
Const M=32;
FontS=18;
XOrig=320;
YOrigP1=132;
YOrigP2=212;
YOrigC1=332;
YOrigC2=412;
GenM=10;
type
TfmCODemo = class(TForm)
lbCODemo: TLabel;
btExit: TButton;
btP1RND: TButton;
rgCO: TRadioGroup;
btP2RND: TButton;
btCutClear: TButton;
lbCuts: TLabel;
btCrossover: TButton;
edGensNum: TEdit;
lbGensNum: TLabel;
lbChild1: TLabel;
lbChild2: TLabel;
lbCutSet: TLabel;
Procedure GensShow;
Procedure ChildShow;
Procedure GensHide;
Procedure ChildDel;
Procedure ShowCuts;
procedure btExitClick(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FormClick(Sender: TObject);
procedure btP1RNDClick(Sender: TObject);
procedure btP2RNDClick(Sender: TObject);
procedure FormDblClick(Sender: TObject);
procedure btCutClearClick(Sender: TObject);
procedure edGensNumChange(Sender: TObject);
procedure btCrossoverClick(Sender: TObject);
procedure rgCOClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Type
TGen=Class
FNum: Integer;
FX, FY: Integer;
FWidth, FHeight: Word;
FBGColor, FFGColor: TColor;
FSign: Boolean;
Procedure Init(INum, IX, IY, IWidth, IHeight: Integer; ISign: Boolean);
Procedure SetColors(IBGColor, IFGColor: TColor);
Function GetBGColor: TColor;
Procedure Show;
Procedure Hide;
Function GetLeft: Integer;
Function GetTop: Integer;
Function GetNum: Byte;
Procedure SetNum(SNum: Byte);
End;
var
fmCODemo: TfmCODemo;
GenP1, GenP2, GenC1, GenC2: Array[1..GenM] Of TGen;
P1, P2, PX1, PX2, C1, C2: Array[1..GenM] Of Byte;
Parents: Byte;
GenN: Byte;
P1OK, P2OK: Boolean;
MouseX, MouseY: Integer;
ActGen, SelGen, Cut, Cut1, Cut2: Byte;
implementation
{$R *.dfm}
Procedure TGen.Init(INum, IX, IY, IWidth, IHeight: Integer; ISign: Boolean);
Begin
FNum:= INum; FX:= IX; FY:= IY; FWidth:= IWidth; FHeight:= IHeight;
FSign:= ISign;
End;
Procedure TGen.SetColors(IBGColor, IFGColor: TColor);
Begin
FBGColor:= IBGColor; FFGColor:= IFGColor;
End;
Function TGen.GetBGColor: TColor;
Begin
GetBGColor:= FBGColor;
End;
Procedure TGen.Show;
Begin
With fmCODemo.Canvas Do
Begin
Brush.Color:= FBGColor;
Pen.Color:= FFGColor;
RecTangle(FX,FY,FX+FWidth,FY+FHeight);
With Font Do
Begin
Color:= FFGColor;
Size:= FontS;
End;
TextOut(FX+FontS Div 2, FY+2, IntToStr(FNum));
If FSign Then
Begin
Brush.Color:= clBtnFace;
Font.Size:= 14;
TextOut((2*FX+FWidth) Div 2-6,Fy-24,'+');
TextOut((2*FX+FWidth) Div 2-3,Fy+FHeight,'-');
End;
End;
End;
Procedure TGen.Hide;
Begin
With fmCODemo.Canvas Do
Begin
Brush.Color:= clBtnFace;
Pen.Color:= clBtnFace;
RecTangle(FX,FY-24,FX+FWidth,FY+FHeight+24);
End;
End;
Function TGen.GetLeft: Integer;
Begin
GetLeft:= FX;
End;
Function TGen.GetTop: Integer;
Begin
GetTop:= FY;
End;
Function TGen.GetNum: Byte;
Begin
GetNum:= FNum;
End;
Procedure TGen.SetNum(SNum: Byte);
Begin
FNum:= SNum;
End;
procedure TfmCODemo.btExitClick(Sender: TObject);
begin
Close;
end;
procedure TfmCODemo.FormPaint(Sender: TObject);
begin
GensShow;
end;
Procedure TfmCODemo.GensShow;
Var I: Word;
Begin
For I:= 1 To GenN Do
Begin
GenP1[I].Show;
GenP2[I].Show;
GenC1[I].Show;
GenC2[I].Show;
End;
End;
Procedure TfmCODemo.ChildShow;
Var I: Word;
Begin
For I:= 1 To GenN Do
Begin
GenC1[I].Show;
GenC2[I].Show;
End;
End;
Procedure TfmCODemo.GensHide;
Var I: Word;
Begin
For I:= 1 To GenN Do
Begin
GenP1[I].Hide;
GenP2[I].Hide;
GenC1[I].Hide;
GenC2[I].Hide;
End;
End;
Procedure TfmCODemo.ChildDel;
Var I: Word;
Begin
For I:= 1 To GenN Do
Begin
GenC1[I].SetNum(0);
GenC2[I].SetNum(0);
End;
End;
Procedure TfmCODemo.ShowCuts;
Begin
lbCuts.Caption:= IntToStr(Cut1)+' - '+IntToStr(Cut2);
End;
procedure TfmCODemo.FormCreate(Sender: TObject);
Var I: Word;
begin
Randomize;
GenN:= 9; Cut1:= 1; Cut2:= GenN;
For I:= 1 To GenN Do
Begin
GenP1[I]:= TGen.Create;
With GenP1[I] Do
Begin
Init(0,XOrig+(M-1)*(I-1),YOrigP1,M,M, True);
SetColors(clWhite,clBlack);
End;
GenP2[I]:= TGen.Create;
With GenP2[I] Do
Begin
Init(0,XOrig+(M-1)*(I-1),YOrigP2,M,M, True);
SetColors(clWhite,clBlack);
End;
GenC1[I]:= TGen.Create;
With GenC1[I] Do
Begin
Init(0,XOrig+(M-1)*(I-1),YOrigC1,M,M, False);
SetColors(clWhite,clBlack);
End;
GenC2[I]:= TGen.Create;
With GenC2[I] Do
Begin
Init(0,XOrig+(M-1)*(I-1),YOrigC2,M,M, False);
SetColors(clWhite,clBlack);
End;
End;
rgCO.ItemIndex:= 0; P1OK:= False; P2OK:= False;
ShowCuts;
end;
procedure TfmCODemo.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
MouseX:= X; MouseY:= Y;
end;
procedure TfmCODemo.FormClick(Sender: TObject);
begin
ActGen:= 0; Parents:= 0; SelGen:= 0;
If (MouseX<GenP1[1].GetLeft) Or (MouseX>GenP1[GenN].GetLeft+M) Then Exit;
If (MouseY>GenP1[1].GetTop-M) And (MouseY<GenP1[1].GetTop+2*M) Then
Parents:= 1;
If (MouseY>GenP2[1].GetTop-M) And (MouseY<GenP2[1].GetTop+2*M) Then
Parents:= 2;
SelGen:= (MouseX-GenP1[1].GetLeft+M Div 7) Div M +1;
Case Parents Of
1: With GenP1[SelGen] Do
Begin
If (MouseY<GetTop) And (GetNum<GenN) Then SetNum(GetNum+1);
If (MouseY>GetTop+M) And (GetNum>1) Then SetNum(GetNum-1);
Hide; Show;
End;
2: With GenP2[SelGen] Do
Begin
If (MouseY<GetTop) And (GetNum<GenN) Then SetNum(GetNum+1);
If (MouseY>GetTop+M) And (GetNum>1) Then SetNum(GetNum-1);
Hide; Show;
End;
End;
end;
procedure TfmCODemo.btP1RNDClick(Sender: TObject);
Var I, J, K, P: Word;
T: Array[1..GenM] Of Integer;
begin
For I:= 1 To GenN Do T[I]:= I;
For K:= 1 To 10*GenN Do
Begin
I:= Random(GenN)+1; J:= Random(GenN)+1;
P:= T[I]; T[I]:= T[J]; T[J]:= P;
End;
For I:= 1 To GenN Do With GenP1[I] Do
Begin Hide; SetNum(T[I]); Show End;
ChildDel; ChildShow;
P1OK:= True; btCrossover.Enabled:= P1OK And P2OK;
end;
procedure TfmCODemo.btP2RNDClick(Sender: TObject);
Var I, J, K, P: Word;
T: Array[1..GenM] Of Integer;
begin
For I:= 1 To GenN Do T[I]:= I;
For K:= 1 To 10*GenN Do
Begin
I:= Random(GenN)+1; J:= Random(GenN)+1;
P:= T[I]; T[I]:= T[J]; T[J]:= P;
End;
For I:= 1 To GenN Do With GenP2[I] Do
Begin Hide; SetNum(T[I]); Show End;
ChildDel; ChildShow;
P2OK:= True; btCrossover.Enabled:= P1OK And P2OK;
end;
procedure TfmCODemo.FormDblClick(Sender: TObject);
begin
If rgCO.ItemIndex=2 Then Exit;
Cut:= (MouseX-GenP1[1].GetLeft+M Div 7) Div M +1;
With GenP1[Cut] Do Begin SetColors(clYellow,clBlack); Show End;
With GenP2[Cut] Do Begin SetColors(clYellow,clBlack); Show End;
With GenC1[Cut] Do Begin SetColors(clYellow,clBlack); Show End;
With GenC2[Cut] Do Begin SetColors(clYellow,clBlack); Show End;
Cut1:= 1; While GenP1[Cut1].GetBGColor=clWhite Do Inc(Cut1);
Cut2:= GenN; While GenP1[Cut2].GetBGColor=clWhite Do Dec(Cut2);
ShowCuts;
end;
procedure TfmCODemo.btCutClearClick(Sender: TObject);
Var I: Word;
begin
For I:= 1 To GenN Do
Begin
With GenP1[I] Do Begin SetColors(clWhite,clBlack); Show End;
With GenP2[I] Do Begin SetColors(clWhite,clBlack); Show End;
With GenC1[I] Do Begin SetColors(clWhite,clBlack); Show End;
With GenC2[I] Do Begin SetColors(clWhite,clBlack); Show End;
End;
Cut1:= 1; Cut2:= GenN; ShowCuts; ChildDel; ChildShow;
end;
procedure TfmCODemo.edGensNumChange(Sender: TObject);
Var I: Word;
Kod: Integer;
begin
btCutClearClick(Sender); GensHide;
Val(edGensNum.Text,GenN,Kod); If GenN>9 Then GenN:= 9;
edGensNum.Text:= IntToStr(GenN);
For I:= 1 To GenN Do
Begin
GenP1[I].SetNum(0); GenP2[I].SetNum(0);
GenC1[I].SetNum(0); GenC2[I].SetNum(0);
End;
GensShow; Cut1:= 1; Cut2:= GenN; ShowCuts;
P1OK:= False; P2OK:= False; btCrossover.Enabled:= False;
end;
procedure TfmCODemo.btCrossoverClick(Sender: TObject);
Var I, J, A, B, C, P: Word;
begin
For I:= 1 To GenN Do
Begin
P1[I]:= GenP1[I].GetNum; P2[I]:= GenP2[I].GetNum; C1[I]:= 0; C2[I]:= 0;
End;
Case rgCO.ItemIndex Of
0: Begin //PMX (Partially Matched Crossover)
A:= 0; B:= 0;
For I:= Cut1 To Cut2 Do
Begin
For J:= 1 To GenN Do If P1[J]=P2[I] Then A:= J;
For J:= 1 To GenN Do If P2[J]=P1[I] Then B:= J;
P:= P1[I]; P1[I]:= P2[I]; P2[I]:= P;
P:= P1[A]; P1[A]:= P2[B]; P2[B]:= P;
End;
C1:= P1;
C2:= P2;
End;
1: Begin //OX (Order Crossover)
For I:= 1 To GenN Do Begin PX1[I]:= P1[I]; PX2[I]:= P2[I] End;
For I:= Cut1 To Cut2 Do For J:= 1 To GenN Do
If P2[J]=PX1[I] Then P2[J]:= 0;
For I:= Cut1 To Cut2 Do For J:= 1 To GenN Do
If P1[J]=PX2[I] Then P1[J]:= 0;
A:= 0; B:= 0;
For I:= Cut1 To GenN Do
Begin
If P1[I]<>0 Then
Begin
Inc(A); If A=Cut1 Then A:= Cut2+1; PX1[A]:= P1[I];
End;
If P2[I]<>0 Then
Begin
Inc(B); If B=Cut1 Then B:= Cut2+1; PX2[B]:= P2[I];
End;
End;
For I:= 1 To Cut1-1 Do
Begin
If P1[I]<>0 Then
Begin
Inc(A); If A=Cut1 Then A:= Cut2+1; PX1[A]:= P1[I];
End;
If P2[I]<>0 Then
Begin
Inc(B); If B=Cut1 Then B:= Cut2+1; PX2[B]:= P2[I];
End;
End;
For I:= 1 To Cut1 Do P1[I]:= PX1[I];
For I:= Cut1 To Cut2 Do P1[I]:= PX2[I];
For I:= Cut2+1 To GenN Do P1[I]:= PX1[I];
For I:= 1 To Cut1 Do P2[I]:= PX2[I];
For I:= Cut1 To Cut2 Do P2[I]:= PX1[I];
For I:= Cut2+1 To GenN Do P2[I]:= PX2[I];
C1:= P1;
C2:= P2;
End;
2: Begin //CX (Cycle Crossover)
For I:= 1 To GenN Do Begin PX1[I]:= 0; PX2[I]:= 0 End;
For I:= 1 To GenN Do If P1[I]=P2[I] Then
Begin PX1[I]:= P1[I]; PX2[I]:= P2[I] End;
A:= 1;
While PX1[A]<>0 Do Inc(A); B:= A;
Repeat
PX1[A]:= P1[A];
PX2[A]:= P2[A];
C:= 0; For J:= 1 To GenN Do
If P1[J]=PX2[A] Then C:= J; A:= C;
Until A=B;
For I:= 1 To GenN Do
Begin
If PX1[I]=0 Then PX1[I]:= P2[I];
If PX2[I]=0 Then PX2[I]:= P1[I];
End;
C1:= PX1;
C2:= PX2;
End;
End;
For I:= 1 To GenN Do
Begin GenC1[I].SetNum(C1[I]); GenC2[I].SetNum(C2[I]) End;
GensShow;
end;
procedure TfmCODemo.rgCOClick(Sender: TObject);
begin
If rgCO.ItemIndex=2 Then
Begin
btCutClearClick(Sender);
btCutClear.Enabled:= False;
End Else btCutClear.Enabled:= True;
end;
end.
Bűvös négyzet készítése Genetikus
algoritmussal
Bűvös négyzet alatt olyan négyzetes számtáblázatot
értünk, amelynek soraiban, oszlopaiban és átlóiban elhelyezkedő számok összege
mindig ugyanaz. Az átlók mellett néha az átlókkal párhuzamosan elhelyezkedő
(mellékátlókban lévő) számok összegét is be szokták venni ebbe a kritériumba,
néha csak bizonyos számokat, például csak prímeket, néha tetszőleges számokat
írhatunk a négyzetekbe.
Az alap bűvös négyzetekben mindig 1-től n2-ig
szerepelnek a számok. Ebben a fejezetben ilyen bűvös négyzetek generálásáról
lesz szó. A generálást genetikus algoritmus segítségével fogjuk megoldani.
Egyedeknek az 1-től n2-ig terjedő számok
egy permutációját fogjuk tekinteni. Egy populációban maximum 40 egyedet fogunk
szerepeltetni, míg a generációk maximális számát 10000-ben határozzuk meg. Ezek
értékeket természetesen az algoritmus beindítása előtt módosíthatók.
Az egyedek minősítése (fitness értéke, ami most inkább
bad points-nak lenne mondható) a következő: minden n-re a program az egyedek
generálásakor megállapítja a bűvös szám értékét, azaz kiszámolja 1-től n2-ig
a számok összegét és osztja n-el. Ennek kellene lenni minden összegnek a sorokban,
oszlopokban és a két átlóban. A fitness érték úgy adódik, hogy minden sorban és
oszlopban, valamint a két átlóban meghatározzuk a tényleges összegeket, vesszük
mindegyiknek az eltérését a szükséges értéktől és összeadjuk az így kapott
2*n+2 darab számot. Ez egy véletlen feltöltés esetén nagyobb, mint 0. Minél
kisebb ez az összeg annál jobbnak tartunk egy egyedet. Ha az összeg 0, akkor a
táblázat egy bűvös négyzet.
A genetikus algoritmus lépései a következők:
A 0. generáció
előállítása, mely 40 db permutációja az 1-n2 számoknak.
Szelekciónál két jó minősítésű (a fitness értékek átlagánál
kisebb) egyedet választunk a keresztezéshez szülőként. Az egyik szülő mindig
olyan, hogy nála jobb tulajdonságú nincs a populációban.
Keresztezésnél a következőre kell figyelnünk: az egyedek
önmagukban hordozzák azt a megkövetelt tulajdonságot (fenotípusát), hogy csupa
különböző számból állnak. Az egyszerű egy, vagy többpontos keresztezéseknél
könnyen adódna olyan egyed, mely ezt a tulajdonságát elveszítené, életképtelenné
lenne. Az életképtelen egyedek kiszűrése külön problémát és az algoritmus
szempontjából fölösleges lépések sokaságát jelentené. Csak olyan keresztezési
módszert engedhetünk meg, amelyek a permutációkon újra permutációkat (csupa
különböző géneket) állít elő az egyes egyedeken belül. Három ilyen keresztezési
módszer közül választhatunk a generálás előtt:
- Partially Matched Crossover (PMX), mely egy két vágási pontos keresztezés, melynél a
vágási pontok közötti gének helyet cserélnek, majd a kialakított génpárok
segítségével a gének kicserélésre kerülnek.
- Order Crossover (OX), mely szintén egy kétpontos keresztezés, ahol a
szülőkből a gének a ciklikus sorrendjük megtartásával kerülnek át az utódokba.
- Cycle Crossover (CX), mely vágási pont nélküli keresztezés, melyben a
gének egymásba kapcsolásával egy ciklust hozunk létre. A ciklusban szereplő
egyedek az utódokban ugyanott szerepelnek, míg a ciklusból kimaradt gének
kicserélődnek. (Itt az is előfordulhat, hogy a gének teljes láncot alkotnak,
vagy csak egyelemű láncot, és ekkor az utódok génkészlete a szülőkkel
megegyezik.)
Mutációnál az alapértelmezett értéket 30-nak állítottam be,
amely 0,3 valószínűségű változtatást jelent a populáció minden rossz egyedének
minden génjére. A jó egyedek génjeninél egy 5 értékű rátát alkalmaz az
algoritmus, valamint a legjobb egyed génjeit nem változtatja.
Mind keresztezésnél, mind a mutációnál csak olyan
egyedek kerülhetnek az új populációba, melyek lényegesen nem rosszabb
tulajdonságúak, mint elődeik. Nevezetesen a megengedett romlás 5%-nyi. A
sikertelen egyedgenerálást ellenére a program új generációként kezeli az ebben
az esetben változatlanul hagyott populációt is (ha nem így lenne akkor nem
vennénk észre a beragadást egy állapotba, külön figyelni kellene erre is egy
változóval, melyet ki kellene íratni).
A
továbbiakban n=3-tól n=11-ig egy-egy screenshot-on megtekinthetjük a generált
bűvös nézeteket. A 40 egyed fitness értékeit és a génjeit is megjeleníti a
program, de génekből csak maximum az első 17-et.









A
program listája:
unit UGenBuv;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls, Grids, ExtCtrls;
Const Max=20;
EgyedSzM=100;
GAL=3;
type
TfmGenBuv = class(TForm)
lbGenBuv: TLabel;
btKilepes: TButton;
lbNegyzOld: TLabel;
edNOld: TEdit;
btStart: TButton;
sgGenBuv: TStringGrid;
sgEgyed: TStringGrid;
btUjPop: TButton;
edPopSz: TEdit;
edKesz: TEdit;
lbPopSz: TLabel;
lbFitness: TLabel;
lbKereszt: TLabel;
rgKereszt: TRadioGroup;
lbBuvosSz: TLabel;
edBuvosSz: TEdit;
lbIndex: TLabel;
edIndex: TEdit;
lbPopSzM: TLabel;
edPopSzM: TEdit;
lbEgyedSz: TLabel;
edEgyedSz: TEdit;
edMutSz: TEdit;
lbMutSz: TLabel;
Procedure PopInit;
Procedure Josaga(E: Word);
Procedure Vizsgal;
Procedure PopKepre;
Procedure Tablara(E: Word);
Procedure Keresztez;
Procedure Mutacio;
procedure btKilepesClick(Sender: TObject);
procedure btStartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure edNOldChange(Sender: TObject);
procedure btUjPopClick(Sender: TObject);
procedure edPopSzMChange(Sender: TObject);
procedure edEgyedSzChange(Sender: TObject);
procedure edMutSzChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TSzam=Record
NN: Array[1..Max*Max] Of Word;
End;
TEgyed=Record
ENN: TSzam;
EOK: Word;
EJo: Boolean;
End;
var
fmGenBuv: TfmGenBuv;
EgyedT: Array[0..EgyedSzM] Of TEgyed;
Josag: Real;
Uj1, Uj2: TEgyed;
EgyedSz, NOld, Tablan, JokSz, Ker1, Ker2, Kozep, OsszJo: Word;
IR1, IR2, IJ1, IJ2, OKMin, IMin, OldMin, MutSz: Word;
PopSz, PopSzM: LongInt;
implementation
{$R *.dfm}
procedure TfmGenBuv.btKilepesClick(Sender: TObject);
begin
Close;
end;
procedure TfmGenBuv.FormCreate(Sender: TObject);
Var I: Integer;
begin
Randomize;
EgyedSz:= 40; Mutsz:= 30; PopSz:= 1; PopSzM:= 10000;
rgKereszt.ItemIndex:= 0;
btStart.Enabled:= False;
With sgEgyed Do
Begin
RowCount:= EgyedSz+1;
For I:= 1 To ColCount-2 Do Cells[I,0]:= IntToStr(I);
For I:= 1 To RowCount-1 Do Cells[0,I]:= IntToStr(I);
Cells[ColCount-1,0]:= 'Fit';
End;
end;
procedure TfmGenBuv.edPopSzMChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edPopSzM.Text,PopSzM,Kod);
end;
procedure TfmGenBuv.edEgyedSzChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edEgyedSz.Text,EgyedSz,Kod);
end;
procedure TfmGenBuv.edMutSzChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edMutSz.Text,MutSz,Kod);
end;
procedure TfmGenBuv.edNOldChange(Sender: TObject);
Var Kod: Integer;
begin
Val(edNOld.Text,NOld,Kod); If NOld<3 Then NOld:= 3;
edNOld.Text:= IntToStr(NOld);
btStart.Enabled:= False;
end;
Procedure TfmGenBuv.PopInit;
Var I, J, K, L, P: Word;
T: TSzam;
Begin
For I:= 1 To Max*Max Do T.NN[I]:= I;
For K:= 1 To EgyedSz Do With EgyedT[K] Do
Begin
For L:= 1 To Max*Max*Max Do
Begin
Repeat
I:= Random(NOld*NOld)+1;
J:= Random(NOld*NOld)+1;
Until I<>J;
P:= T.NN[I]; T.NN[I]:= T.NN[J]; T.NN[J]:= P;
End;
ENN:= T;
EOK:= 0;
EJo:= False;
End;
End;
Procedure TfmGenBuv.Josaga(E: Word);
Var I, J, N, S: Word;
V: Array[1..Max,1..Max] Of Word;
Begin
With EgyedT[E] Do
Begin
For I:= 1 To NOld*NOld Do V[(I-1) Mod NOld+1,(I-1) Div NOld+1]:= ENN.NN[I];
S:= 0;
For I:= 1 To NOld Do
Begin
N:= 0; For J:= 1 To NOld Do Inc(N,V[I,J]); Inc(S,Abs(N-Kozep));
N:= 0; For J:= 1 To NOld Do Inc(N,V[J,I]); Inc(S,Abs(N-Kozep));
End;
N:= 0; For J:= 1 To NOld Do Inc(N,V[J,J]); Inc(S,Abs(N-Kozep));
N:= 0; For J:= 1 To NOld Do Inc(N,V[J,NOld-J+1]); Inc(S,Abs(N-Kozep));
EOK:= S;
End;
End;
Procedure TfmGenBuv.Vizsgal;
Var I: Word;
Begin
OsszJo:= 0; OKMin:= 65000; IMin:= 1;
For I:= 1 To EgyedSz Do With EgyedT[I] Do
Begin Inc(OsszJo,EOK); If EOK<OKMin Then Begin OKMin:= EOK; IMin:= I End End;
Josag:= OsszJo/EgyedSz;
For I:= 1 To EgyedSz Do With EgyedT[I] Do If EOK<Josag Then
EJo:= True Else EJo:= False;
End;
Procedure TfmGenBuv.PopKepre;
Var I, J: Word;
Begin
With sgEgyed Do
Begin
For I:= 1 To ColCount-1 Do For J:= 1 To RowCount-1 Do Cells[I,J]:= '';
For J:= 1 To EgyedSz Do With EgyedT[J] Do For I:= 1 To NOld*NOld Do
Begin
Cells[I,J]:= IntToStr(ENN.NN[I]);
Cells[ColCount-1,J]:= IntToStr(EOK);
End;
End;
End;
Procedure TfmGenBuv.Tablara(E: Word);
Var I: Word;
Begin
With sgGenBuv Do For I:= 1 To NOld*NOld Do
Cells[(I-1) Mod NOld+1,(I-1) Div NOld+1]:= IntToStr(EgyedT[E].ENN.NN[I]);
End;
Procedure TfmGenBuv.Keresztez;
Var I, J, V, R, A, B, C, P, T, U: Word;
P1, P2: TSzam;
Van: Boolean;
Begin
Inc(PopSz);
V:= Random(EgyedSz)+1; R:= 0; IR1:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do
If Not EJo And (EOK>R) Then Begin R:= EOK; IR1:= I End;
For I:= 1 To V Do With EgyedT[I] Do
If Not EJo And (EOK>R) Then Begin R:= EOK; IR1:= I End;
V:= Random(EgyedSz)+1; R:= 0; IR2:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IR1 Then
If Not EJo And (EOK>R)Then Begin R:= EOK; IR2:= I End;
For I:= 1 To V Do With EgyedT[I] Do If I<>IR1 Then
If Not EJo And (EOK>R) Then Begin R:= EOK; IR2:= I End;
V:= Random(EgyedSz)+1; R:= 65000; IJ1:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do
If EJo And (EOK<R) Then Begin R:= EOK; IJ1:= I End;
For I:= 1 To V Do With EgyedT[I] Do
If EJo And (EOK<R) Then Begin R:= EOK; IJ1:= I End;
V:= Random(EgyedSz)+1; IJ2:= 0;
For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IJ1 Then
If EJo Then IJ2:= I;
For I:= 1 To V Do With EgyedT[I] Do If I<>IJ1 Then
If EJo Then IJ2:= I;
//keresztezés -> Uj1, Uj2: TEgyed
If IJ1*IJ2*IR1*IR2<>0 Then
Begin
Uj1:= EgyedT[IJ1];
Uj2:= EgyedT[IJ2];
Case rgKereszt.ItemIndex Of
0: Begin //PMX (Partially Matched Crossover)
A:= 0; B:= 0;
For I:= Ker1+1 To Ker2 Do
Begin
For J:= 1 To NOld*NOld Do
If Uj1.ENN.NN[J]=Uj2.ENN.NN[I] Then A:= J;
For J:= 1 To NOld*NOld Do
If Uj2.ENN.NN[J]=Uj1.ENN.NN[I] Then B:= J;
P:= Uj1.ENN.NN[I];
Uj1.ENN.NN[I]:= Uj2.ENN.NN[I];
Uj2.ENN.NN[I]:= P;
P:= Uj1.ENN.NN[A];
Uj1.ENN.NN[A]:= Uj2.ENN.NN[B];
Uj2.ENN.NN[B]:= P;
End;
End;
1: Begin //OX (Order Crossover)
P1:= Uj1.ENN;
P2:= Uj2.ENN;
For I:= Ker1+1 To Ker2 Do For J:= 1 To NOld*NOld Do
If Uj2.ENN.NN[J]=P1.NN[I] Then Uj2.ENN.NN[J]:= 0;
For I:= Ker1+1 To Ker2 Do For J:= 1 To NOld*NOld Do
If Uj1.ENN.NN[J]=P2.NN[I] Then Uj1.ENN.NN[J]:= 0;
A:= 0; B:= 0;
For I:= Ker1+1 To NOld*NOld Do
Begin
If Uj1.ENN.NN[I]<>0 Then
Begin
Inc(A); If A=Ker1+1 Then A:= Ker2+1; P1.NN[A]:= Uj1.ENN.NN[I];
End;
If Uj2.ENN.NN[I]<>0 Then
Begin
Inc(B); If B=Ker1+1 Then B:= Ker2+1; P2.NN[B]:= Uj2.ENN.NN[I];
End;
End;
For I:= 1 To Ker1 Do
Begin
If Uj1.ENN.NN[I]<>0 Then
Begin
Inc(A); If A=Ker1+1 Then A:= Ker2+1; P1.NN[A]:= Uj1.ENN.NN[I];
End;
If Uj2.ENN.NN[I]<>0 Then
Begin
Inc(B); If B=Ker1+1 Then B:= Ker2+1; P2.NN[B]:= Uj2.ENN.NN[I];
End;
End;
With Uj1.ENN Do
Begin
For I:= 1 To Ker1 Do NN[I]:= P1.NN[I];
For I:= Ker1+1 To Ker2 Do NN[I]:= P2.NN[I];
For I:= Ker2+1 To NOld*NOld Do NN[I]:= P1.NN[I];
End;
With Uj2.ENN Do
Begin
For I:= 1 To Ker1 Do NN[I]:= P2.NN[I];
For I:= Ker1+1 To Ker2 Do NN[I]:= P1.NN[I];
For I:= Ker2+1 To NOld*NOld Do NN[I]:= P2.NN[I];
End;
End;
2: Begin //CX (Cycle Crossover)
For I:= 1 To NOld*NOld Do Begin P1.NN[I]:= 0; P2.NN[I]:= 0 End;
For I:= 1 To NOld*NOld Do If Uj1.ENN.NN[I]=Uj2.ENN.NN[I] Then
Begin P1.NN[I]:= Uj1.ENN.NN[I]; P2.NN[I]:= Uj2.ENN.NN[I] End;
A:= 1;
While P1.NN[A]<>0 Do Inc(A); B:= A;
Repeat
P1.NN[A]:= Uj1.ENN.NN[A];
P2.NN[A]:= Uj2.ENN.NN[A];
C:= 0; For J:= 1 To NOld*NOld Do
If Uj1.ENN.NN[J]=P2.NN[A] Then C:= J; A:= C;
Until A=B;
For I:= 1 To NOld*NOld Do
Begin
If P1.NN[I]=0 Then P1.NN[I]:= Uj2.ENN.NN[I];
If P2.NN[I]=0 Then P2.NN[I]:= Uj1.ENN.NN[I];
End;
Uj1.ENN:= P1;
Uj2.ENN:= P2;
End;
End;
EgyedT[0]:= Uj1; Josaga(0); T:= EgyedT[0].EOK;
EgyedT[0]:= Uj2; Josaga(0); U:= EgyedT[0].EOK;
If T+U<=1.05*(EgyedT[IR1].EOK+EgyedT[IR2].EOK) Then
Begin
EgyedT[IR1]:= Uj1; Josaga(IR1);
EgyedT[IR2]:= Uj2; Josaga(IR2);
End;
End;
End;
Procedure TfmGenBuv.Mutacio;
Var I, J, K, L, P, U: Word;
T: TSzam;
Begin
Inc(PopSz);
For L:= 1 To EgyedSz Do With EgyedT[L] Do
If L<>IMin Then For K:= 1 To NOld Do
If Not EJo And (Random(100)<MutSz) Or EJo And (Random(100)<MutSz/5) Then
Begin
U:= EOK;
T:= ENN;
Repeat
I:= Random(NOld*NOld)+1;
J:= Random(NOld*NOld)+1;
Until I<>J;
P:= T.NN[I]; T.NN[I]:= T.NN[J]; T.NN[J]:= P;
EgyedT[0].ENN:= T;
Josaga(0);
If EgyedT[0].EOK<=1.05*U Then ENN:= T;
End;
End;
procedure TfmGenBuv.btStartClick(Sender: TObject);
Var I: Word;
begin
rgKereszt.Enabled:= False;
btUjPop.Enabled:= False;
OKMin:= 65000; IMin:= 0; PopSz:= 1;
Repeat
OldMin:= OKMin; edPopSz.Text:= IntToStr(PopSz); edPopSz.Repaint;
If IJ1*IJ2*IR1*IR2<>0 Then Keresztez Else
Begin Mutacio; For I:= 1 To EgyedSz Do Josaga(I) End;
Vizsgal; PopKepre; sgEgyed.Repaint;
If OKMin<OldMin Then
Begin
edKesz.Text:= IntToStr(OKMin); edKesz.Repaint;
edIndex.Text:= IntToStr(IMin); edIndex.Repaint;
Tablara(IMin); sgGenBuv.Repaint;
End;
Until (OKMin=0) Or (PopSz>PopSzM);
Tablara(IMin);
rgKereszt.Enabled:= True;
btUjPop.Enabled:= True;
end;
procedure TfmGenBuv.btUjPopClick(Sender: TObject);
Var I, J: Word;
Kod: Integer;
begin
Val(edNOld.Text,NOld,Kod);
Kozep:= (NOld*NOld*(NOld*NOld+1) Div 2) Div NOld;
edBuvosSz.Text:= IntToStr(Kozep);
Ker1:= NOld*NOld Div 3; Ker2:= 2*NOld*NOld Div 3;
With sgGenBuv Do
Begin
DefaultColWidth:= 42;
DefaultRowHeight:= 36;
Width:= 43*NOld+7;
Height:= 37*NOld+5;
ColCount:= NOld+2;
RowCount:= NOld+2;
ColWidths[0]:= 0;
RowHeights[0]:= 0;
ColWidths[ColCount-1]:= 0;
RowHeights[RowCount-1]:= 0;
Col:= ColCount-1;
Row:= RowCount-1;
For I:= 0 To ColCount-1 Do For J:= 1 To RowCount-1 Do Cells[I,J]:= '';
Visible:= True;
End;
PopInit;
For I:= 1 To EgyedSz Do Josaga(I);
Vizsgal;
PopKepre;
btStart.Enabled:= True;
end;
end.