Main pageThe neural network bible

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

Screenshot af Binær til decimal lommeregner
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

Screenshot af pREC
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.

Recog's netværksarkitektur
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.

Højdekurve af to-tal
Billede 4: Højdekurve af to-tal

Binært 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]
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

Summering af output
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.

Neuronerne konkurrerer om at blive lært
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.

Ændring i synapsestyrker
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

Opdatering af synapsestyrken
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...

« forrige - top - næste »


Bottom frame
  Før du begynder at læse dette Error-correction læring Competitive læring Hebbian læring Andre metoder Programmering af træning Opgaver