|
Programmering

For at illustrere læring af netværk har vi to programmer der begge er programmeret i delphi. Den ene udnytter
error-correction for at opnå det bedste resultat, denne kode er forholdsvist simpel. Svære bliver det når vi laver et uovervåget
competitive netværk, hvor træningsalgoritmen er noget mere indviklet. Derfor har vi også splittet selve netværket og det
visuelle op i to individuelle filer. Men lad os komme i gang.
Binær lommeregner med error-correction læring
Igen bringer vi linket til koden, hvis du ikke har hentet programmet i forvejen.
|
Navn
|
Beskrivelse
|
Størrelse
|
|
traincalc.zip
|
Et simpelt lommeregnerprogram der bruger én neuron for at omdanne binære tal til decimaltal, i starten er
den ubrugelig, men ved hjælp af træningseksempler trænes den til at kunne regne.
|
138 KB
|
Samt et billede

Billede 1: Screenshot af Binær til decimal lommeregner
Da meget af koden allerede er beskrevet i lommeregneren med predefinerede synapsestyrker, vil kun den nye kode blive
forklaret. Hvis du ikke i forvejen har gennemgået den gamle kode vil det være en udemærket ide at gøre det nu og her.
De før predefinerede, konstante synapsestyrker er erstattet med variable. Træningen af netværket, og
derved synapsestyrkerne gøres ved hjælp af 8 træningseksempler der bliver defineret således
type
IOEksempel = record
Input : Array[1..8] of Integer;
Output : Integer;
end;
const
IOEks: Array[1..8] of IOEksempel = (
(Input: (1, 0, 0, 0, 0, 0, 0, 0); Output: 1 ),
(Input: (0, 1, 0, 0, 0, 0, 0, 0); Output: 2 ),
(Input: (0, 0, 1, 0, 0, 0, 0, 0); Output: 4 ),
(Input: (0, 0, 0, 1, 0, 0, 0, 0); Output: 8 ),
(Input: (0, 0, 0, 0, 1, 0, 0, 0); Output: 16 ),
(Input: (0, 0, 0, 0, 0, 1, 0, 0); Output: 32 ),
(Input: (0, 0, 0, 0, 0, 0, 1, 0); Output: 64 ),
(Input: (0, 0, 0, 0, 0, 0, 0, 1); Output: 128),
);
|
Et input/output eksempel ( IOEksempel ) består jo af et input og et ønsket output, i vores
tilfælde har vi 8 input ( Array[1..8] of Integer ), og ét output ( Integer ). Bagefter definerer vi vores
8 eksempler, Input og Output .
Næste ændring er at der er tilført nogle nye komponenter på vores form. Det drejer sig om de
følgende:
TrainHistory: TMemo;
EksempelNr: TSpinEdit;
TrainButton: TButton;
LearnRateProc: TTrackBar;
|
TrainHistory er vores logvindue hvor alt hvad der sker i træningsprocessen bliver skrevet op.
EksempelNr angiver hvilket I/O eksempel der skal trænes efter.
TrainButton er simpelthen den knap man skal trykke på for at træne netværket.
LearnRateProc angiver læringsraten i %.
Der er ligeledes tilføjer to procedurer som hedder
procedure UpdateWeights;
procedure TrainButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
|
UpdateWeights har ligesom UpdateBin kun en rent grafisk funktion som vi ikke vil uddybe
nærmere da den giver sig selv, den opdaterer bare formens synapsestyrke labels.
TrainButtonClick bliver kaldt når der trykkes på TrainButton, inde i
proceduren gemmer der sig hele træningsalgoritmen som skal optimere synapsestyrkerne.
FormCreate bliver kaldt lige når programmet startes og har til formål at give
synapsestyrkerne tilfældige værdier samt at opdatere formen.
Wk er desuden rykket ned til var sektionen da de skal kunne ændres. Som man kan se
er de blevet ændret til single som er en floating-point variabel, dette muliggør at vi ikke skal arbejde med kedelige
heltal.
Wk: Array[1..8] of Single;
|
Der er ingen grund til at kommentere anden kode end træningsalgoritmen TrainButtonClick og
initialiseringsproceduren FormCreate. Vi starter med det sidste, med FormCreate .
procedure TCalcForm.FormCreate(Sender: TObject);
var
j: Integer;
begin
randomize;
for j:= 1 to 8 do
Wk[j]:= Random(256);
UpdateWeights;
end;
|
Randomize, som er en funktion der genererer tilfældige tal, bliver kaldt da vi skal bruge tallene
til at sætte snapsestyrkerne. Derefter løber vi alle synapser igennem og giver dem en tilfældig værdi på mellem
0 og 256 (det er i grunden ligemeget hvilket interval de ligger imellem, det kunne være imellem ti tusind og en
milliard for den sags skyld). Til sidst opdaterer vi formens synapsestyrkelabels ved hjælp af UpdateWeights.
Nu til sidst kommer den vi alle har ventet på, selve træningsalgoritmen. Vi deler den op i stykker for bedre at
overskue den
procedure TCalcForm.TrainButtonClick(Sender: TObject);
var
SetNr: Integer;
j: Integer;
yk: Single;
dk: Single;
ek: Single;
LearnRate: Single;
deltaw: Single;
|
Vi starter med variablerne.
SetNr er en heltalsværdi der angiver det træningssæt vi arbejder med.
j er en helt normal tællevariabel
yk er neuronens output som floating-point
dk er det ønskede signal, også som floating-point
ek er fejlen i neuronens output
LearnRate er læringsraten der er floating-point fra 0..1
deltaw er den ændring der skal foretages i synapsestyrken for at neuronen bliver lært.
Og går videre med initialiseringen.
1begin
2 if TrainHistory.Lines.Count > 500 then
3 TrainHistory.Lines.Clear;
4 LearnRate:= LearnRateProc.Position/100;
5 SetNr:= EksempelNr.Value;
6 TrainHistory.Lines.Add('Begynder træning med sæt #' + IntToStr(SetNr) + ' med ' +
FloatToStr(LearnRate) + ' læringsrate:');
7 TrainHistory.Lines.Add('');
8 TrainHistory.Lines.Add('Opdaterer neuronen ...');
9 TrainHistory.Lines.Add('');
10 if IOEks[SetNr].Input[1] = 1 then
11 x1.Checked:= True
12 else x1.Checked:= False;
Her springer vi lige lidt
13 UpdateNeuron;
14 yk:= StrToFloat(Sum.Caption);
15 dk:= IOEks[SetNr].Output;
|
linie 2 og 3 sørger for at logboksen ikke bliver for overfyldt, da den har en maxkapacitet, nu bliver den slettet
efter hver 500 linier.
linie 4-9 giver sig selv så de bliver ikke forklaret yderligere.
på linie 10-12, den kode vi har sprunget over har til formål at sætte alle formens checkbokse
x1 - x8 til I/O eksemplets input. For når vi så kalder UpdateNeuron på linie 13 så
bliver neuronens output opdateret i henhold til det korrekte inputeksempel, derefter kan vi beregne fejlen ek.
I linie 14 sættes yk til værdien der står på den store label Sum.
Linie 15 sætter dk til det pågældende I/O eksempels ønskede output.
Nu har vi initialiseret alle vores værdier yk, dk og LearnRate, nu
går vi videre til selve træningen.
1 TrainHistory.Lines.Add('Beregner ek');
2 ek:= dk-yk;
3 TrainHistory.Lines.Add('ek=' + FloatToStr(ek));
4 TrainHistory.Lines.Add('');
5 TrainHistory.Lines.Add('Opdaterer synapser');
6 TrainHistory.Lines.Add('');
7 for j:= 1 to 8 do
8 begin
9 deltaw:=LearnRate*ek*x[j];
10 TrainHistory.Lines.Add('dwk' + IntToStr(j) + '=' + FloatToStr(deltaw));
11 Wk[j]:= Wk[j]+ deltaw;
12 end;
13 TrainHistory.Lines.Add('');
14 TrainHistory.Lines.Add('Opdaterer synapser og neuron');
15 TrainHistory.Lines.Add('');
16 UpdateWeights;
17 UpdateNeuron;
18 TrainHistory.Lines.Add('Træning færdig');
19 TrainHistory.Lines.Add('');
20 TrainHistory.Lines.Add('');
21 end;
|
Linie 2 er jo helt klart den følgende ligning vi har fra error-correction sektionen.
Ligning 1: Fejlsignalet
Linie 3-6 er opdatering af logboksen
Linie 7-12 er faktisk disse to ligninger flettet sammen

Ligning 2: Deltareglen
og

Ligning 3: Opdateringen af synapsestyrker
Ligning 2 bliver implementeret på linie 9 mens ligning 3 opdaterer synapsestyrken på linie 11, ganz
einfach.
Resten af træningsalgoritmen går ud på at opdatere synapsestyrke labels og neuronens output.
pREC competitive netværk
Igen kommer der både et billede og et link

Billede 2: Screenshot af pREC
pREC.zip - 161 KB
Dette program ville være for stort at kommentere hvis jeg skulle gennemgå
det hele. Jeg vil derfor nøjedes med at gennemgå én enkelt funktion i
nn.pas filen, det er også klart den vigtigste funktion, nemlig den der genkender
mønsteret lærer netværket.
Først vil jeg give en forklaring af hvordan vores netværk egentligt er bygget
op, og hvordan det fungerer. Selve netværksarkitekturen er typisk for de programmer
der skal genkende et mønster, Recog har lavet et billede af sit netværk,
som på mange måder ligner vores.

Billede 3: Recogs netværksarkitektur
Forskellen på vores og Recogs netværk er at Recog's netværk er mindre,
mens det regner med et 6x8 input bruger vi et 10x12 input (se også på billede to,
hvor vores billede har 10x12 pixler). Vi har på samme måde som Recog ti outputneuroner,
som er dem der gør de egentlige beregninger og også er de eneste der er interresante.
Hver outputneuron er forbundet med alle inputneuronerne, så det giver i alt 10 gange 12 synapser,
altså 120. De individuelle synapsestyrker kan også ses på konturplottet, som viser
dem fra sort (mindst) til rød (størst). Formålet med netværket er at få
synapsestyrkerne til at afspejle det mønster man gerne vil genkende.
En god måde at forklare det på er at se på netværket som en kage. Det lyder
måske fjollet, men det er der jo ikke noget at gøre ved.
|

Billede 4: Højdekurve af to-tal
|

Billede 5: Binært to-tal
|
Hvis vi forestiller os denne højdekurve af tegnet "2" som en kage, og vi skærer i den som det
binære mønster i Billede 5, og lade 1 være det afskårne, og 0 være det tilbageblivende, vil vi
umiddelbart få mest kage, hvis vi skærer noget der minder om et 2-tal. Man kunne så også blot tage hele
kagen. Men her er denne kage så smart indrettet, at hvis man vælger et sted der ikke er kage, vil det
blive trukket fra.
Hvis vi nu skærer et 2-tal oven på en 5-talskage af samme slags, vil vi ikke få så meget kage, som hvis vi
skar et 5-tal. Har vi så et binært mønster der minder om et 2-tal, og vi prøver dem af på hele talrækken,
formet som kager, vil vi få mest kage ved 2-tallet. Vi kan herved vise, at det givne mønster er et 2-tal.
Hvis vi ser på Kagen som synapsestyrkerne og inputtet som billedet man tagner kan man i grove træk
sige at det neurale netværk i pREC, nLAB og Recog fungerer efter samme princip.
Nok om arkitekturen, lad os se på selve koden. åbn nn.pas og find proceduren TNNetwork.Recognize,
som gerne skulle starte med følgende:
procedure TNNetwork.Recognize;
type
TSumArray = Array[1..10*12] of TSynapse;
var
k, j: Integer;
WeightArray: TSumArray;
SampleArray: TSumArray;
NeuronOutput: Array[1..10] of Double;
dwkj: Double;
|
TSumArray er en af de indviklede her i dette program, den består af en array af 120 floating-point numre.
Grunden til denne type er at vi skal bruge den til gennemløbet. Inputtet of synapsestyrkerne er jo givet
i en todimensionel array (Array[1..10,1..12]), som laves om til en endimensionel array (Array[1..120]).
Præcis hvad der sker når vi laver dem om forklares senere.
k og j bliver brugt til tællevariabler.
WeightArray og SampleArray er som sagt en array med 120 elementer, istedet for den todimensionelle array.
NeuronOutput indeholder alle outputneuronernes output, det bruges til at finde ud af hvilken neuron der giver
det største output, og derfor vinder konkurrencen.
Det mærkelige navn dwkj, eller delta wkj er ændringen på den pågældende synapse, som bruges under
træningsprocessen.
begin
1 for k:=1 to 10 do
2 begin
3 NeuronOutput[k]:= 0;
4 WeightArray:= TSumArray(FNeurons[k]);
5 SampleArray:= TSumArray(FSample);
6 for j:= 1 to 10*12 do
7 NeuronOutput[k]:= NeuronOutput[k] + WeightArray[j] * SampleArray[j];
8 end;
9 if (NeuronOutput[1]> NeuronOutput[2]) and (NeuronOutput[1]> NeuronOutput[3]) and
(NeuronOutput[1]> NeuronOutput[4]) and (NeuronOutput[1]> NeuronOutput[5]) and
(NeuronOutput[1]> NeuronOutput[6]) and (NeuronOutput[1]> NeuronOutput[7]) and
(NeuronOutput[1]> NeuronOutput[8]) and (NeuronOutput[1]> NeuronOutput[9]) and
(NeuronOutput[1]> NeuronOutput[10]) then
FResult:= 1
Stort spring i koden
|
Linie 1 gør at vi gennemløber alle neuroner i outputlaget
Linie 3 sætter neuronoutputtet for den pågeldende neuron til 0, da vi ved hjælp af
en senere løkke beregner outputtet.
Linie 4 og 5 udfører den lidt mærkelige funktion der gør at vi kan løbe igennem
løkken bagefter. Linie 4 laver synapsestyrkekortet (den todimensionelle array) om til en endimensionel
array, som kan ses på følgende billede.
![array[1..10, 1..12] til array[1..120]](../images/train/programmering/10x12-120.gif)
Billede 6: array[1..10, 1..12] til array[1..120]
Som du kan se på billede 6 bliver arrayen overført søjle for søjle, nu har vi en array
der er til at arbejde med.
Linie 6 og 7 beregner neuronens output som vi også gør i formlen nedenfor

Ligning 4: Summering af output
Den følgende kode (som vi springer det meste over af), er ikke ligefrem det kønneste der er blevet lavet, men
det gør hvad det skal, nemlig at finde den neuron der giver det største output, og gemme neuronens nummer i FResult.
1 if Learn then
2 begin
3 for k := 1 to 10 do
4 NeuronOutput[k]:= 0;
5 NeuronOutput[FResult]:= 1;
6 WeightArray:= TSumArray(FNeurons[FResult]);
7 FMeanSquareDelta:= 0;
8 for j:= 1 to 10*12 do
9 begin
10 dwkj:= LearnRate *(SampleArray[j]-WeightArray[j]);
11 FMeanSquareDelta:= FMeanSquareDelta + (dwkj*dwkj);
12 WeightArray[j]:= WeightArray[j] + dwkj
13 end;
14 FNeurons[FResult]:= TNeuron(WeightArray);
end;
end;
|
Mønsteret er nu genkendt, så nu går vi videre til læreprocessen.
Linie 1 gør at netværket kun læres hvis der er sat til det.
Linie 3, 4 og 5 er det samme som denne ligning.

Ligning 5: Neuronerne konkurrerer om at blive lært
Hvor det kun er den neuron der har det største output der bliver trænet
som vi kan se på denne ligning.

Ligning 6: Ændring i synapsestyrker
I linie 6 omdanner vi igen den vindende neurons synapsestyrkearray[1..10,1..12] til en brugbar array.
MeanSquareDelta har kun noget at gøre med grafen, så den indgår ikke direkte i
træningsprocessen, den viser bare hvor meget neuronens synapsestyrker er blevet ændret. Vi vil
ikke beskrive dennes funktion mere.
Linie 8-16 er de mest interresante, da det er dér læringen foregår. Linie 8 siger at vi
skal gennemgå alle synapsestyrkerne, altså 120 stk.
Linie 10 er faktisk det vi kan se i ligning 6, som definerer ændringen i synapsestyrken.
Derefter bliver synapsestyrken opdateret efter denne ligning

Ligning 7: Opdatering af synapsestyrken
Til sidst i linie 14 gør vi processen med omdanningen af arrays omvendt, så vores netværk
kan få de ændrede synapsestyrker.
Resten af programmet er rimeligt godt kommenteret, så det må du se som en opgave at forstå,
du kan maile Nikolaj på denne addresse hvis du har spørgsmål:
Herved slutter vores gennemgang af læringsprogrammering, og bortset fra nogle opgaver, også gennemgangen af neurale netværk.
Vi håber du har haft et oplysende ophold, og håber at høre mere fra dig hvis du går ind på forummet som du kan finde
under informationsdatabasen. Desuden vil det være en god ide at tilmelde dig nyhedsgruppen comp.ai.neural-nets der diskuterer neurale netværk.
Vil du have flere links så prøv denne addresse (link testet 13/03/2000) der giver et lille indtryk af hvor meget der egentligt findes om emnet derude.
Tak for denne gang...
|