Auf Thema antworten

[php]   class BackpropagationNet

   {

      use T;


      private $weights;

      private $layers;

      private $onNeuron;

     

      /**

       * Gewichte der Verbindungen von On-Neuronen werden mit 0.5 definiert.

       * @param boolean $on ob mit On-Neuron

       */

      function __construct(array &$weights, IPropagationFunc $pF, IActivationFunc $aF, IOutputFunc $oF, $on)

      {

         // Neuronen erzeugen und in den Schichten ablegen:

         $this->layers = Layers::get($weights, $pF, $aF, $oF);

        

         // Gewichtsmatrix expandieren, falls mit On-Neuron gearbeitet werden soll:

         if($on)

         {

            $this->addOnNeuron($weights);

         }

        

         $this->weights = &$weights;

      }

      /**

       * Gewichtsmatrix um ein On-Neuron erweitern.

       */

      private function addOnNeuron(array &$weights)

      {

         $count = count($weights);

        

         // Spalte an existierende Zeilen anghängen:

         for($y = 0; $y < $count; ++$y)

         {

            $weights[$y][] = 0;

         }

        

         // Zeile anhängen:

         $weights[$count] = array_fill(0, $count + 1, 0);

        

         // Neues Neuron-Objekt:

         $this->onNeuron = new OnNeuron($count);

        

         // Verbindungen des On-Neurons zu den verdeckten Neuronen und Ausgabe-Neuronen:

         $countL = count($this->layers);

         for($l = 1; $l < $countL; ++$l)

         {

            $countN = count($this->layers[$l]);

            for($n = 0; $n < $countN; ++$n)

            {

               $neuron = $this->layers[$l][$n];

              

               // Verbindungsgewicht in Matrix setzen:

               $weights[$count][$neuron->getId()] = 0.5;

              

               // Dem On-Neuron einen Nachfolger hinzufügen:

               $this->onNeuron->successors[] = $neuron;

              

               // Dem aktuellen Neuron einen Vorgänger hinzufügen:

               $neuron->predecessors[] = $this->onNeuron;

            }

         }

      }

      /**

       * Werte der Neuronen der Eingabeschicht setzen.

       * @param array $inputs

       * @throws Exception falls die Anzahl der inputs-Werte nicht der Anzahl der Eingabe-Neuronen entspricht

       */

      function setInputs(array $inputs)

      {

         // Anzahl der Eingabe-Neuronen:

         $count = count($this->layers[0]);

        

         // Kontrolle, ob Anzahl der Eingabe-Werten mit der Anzahl der Eingabe-Neuronen übereinstimmt:

         if(count($inputs) != $count)

         {

            throw new Exception('invalid count inputs');

         }

        

         // Eingänge setzen:

         for($n = 0; $n < $count; ++$n)

         {

            $this->layers[0][$n]->setO($inputs[$n]);

           

            if(DEBUG)

            {

               echo "Neuron ", $this->layers[0][$n]->getId(), " in: ", $this->layers[0][$n]->getO(), "\n";

            }

         }

      }

      /**

       * Setzt die Ausgabe der Neuronen der verdeckten Schicht und Ausgabeschicht.

       */

      function setOutputs()

      {

         $countLayers = count($this->layers);

         for($l = 1; $l < $countLayers; ++$l)

         {

            // Anzahl der Neuronen der aktuellen Schicht:

            $countNeurons = count($this->layers[$l]);

           

            // Ausgänge der Neuronen der aktuellen Schicht berechen:

            for($n = 0; $n < $countNeurons; ++$n)

            {

               $this->layers[$l][$n]->setO();

            }

         }

         // In der Eingabeschicht müssen keine Ausgaben berechnet werden.

         // Sie entsprechen unverändert den Eingaben.

      }

      /**

       * Passt die Gewichte ausgehender Verbindungen an.

       * @param Neuron neuron

       * @param number LerningRate

       */

      function updateWeights(Neuron $neuron, $learningRate)

      {

         if(DEBUG)

         {

            echo "Gewichte ausgehender Verbindungen von Neuron ", $neuron->getId(), ":\n";

         }

        

         $count = count($neuron->successors);

         for($s = 0; $s < $count; ++$s)

         {

            $successor = $neuron->successors[$s];

            $deltaW = $learningRate * $neuron->getO() * $successor->getErrSig();

            $this->weights[$neuron->getId()][$successor->getId()] += $deltaW;

           

            if(DEBUG)

            {

               echo "   zu Nachfolger ", $successor->getId(), " deltaW: ", $deltaW, "\n";

               echo "   zu Nachfolger ", $successor->getId(), " w: ", $this->weights[$neuron->getId()][$successor->getId()], "\n";

            }

         }

      }

      /**

       * Gibt den Netzfehler zurück.

       * @param array $targets Werte des Musters

       * @return number

       */

      function getError(array &$targets)

      {

         $err = 0;

        

         // Ausgabeebene:

         $l = count($this->layers) - 1;

        

         // Anzahl der Ausgabe-Neuronen:

         $count = count($this->layers[$l]);

        

         // Fehler aufsummieren:

         for($n = 0; $n < $count; ++$n)

         {

            $err += pow($targets[$n] - $this->layers[$l][$n]->getO(), 2);

         }

        

         return $err;

      }

      /**

       * 1 Muster lernen.

       * @param number $learningRate

       * @param array $targets Werte des Musters

       * @return number

       *    Trainingsfehler (Fehler beim Lernen, bezogen auf das gesamten Netz)

       *    Summe aus den quadartischen Fehlern aller Ausgabe-Neuronen

       * @throws Exception falls die Anzahl der target-Werte nicht der Anzahl der Ausgabe-Neuronen entspricht

       */

      function learn($learningRate, array &$targets)

      {

         $l = count($this->layers) - 1;     // Ausgabe-Schicht

         $count = count($this->layers[$l]); // Anzahl der Ausgabe-Neuronen

         if(count($targets) != $count)

         {

            throw new Exception("invalid count target");

         }

        

         // Ausgabeschicht:

         for($n = 0; $n < $count; ++$n)

         {

            $this->layers[$l][$n]->setErrSig($targets[$n]); // fSig = c * o * (1 - o) * (t - o)

         }

        

         // Verdeckte Schichten:

         for($l = count($this->layers) - 2; $l > 0; --$l)

         {        

            $count = count($this->layers[$l]);

            for($n = 0; $n < $count; ++$n)

            {

               $neuron = $this->layers[$l][$n];

              

               // Zuerst muss das Fehlersignal des aktuellen Neurons berechnet werden (benötigt die alten Gewichte zu seinen Nachfolgern):

               $neuron->setErrSig($this->weights); // fSig = c * o * (1 - o) * Sum(w[successor] * fSig[successor])


               // Danach können die Gewichte der Verbindungen zu seinen Nachfolgern aktualisiert werden:

               $this->updateWeights($neuron, $learningRate); // je successor: w[successor] += lernrate * o * fSig[successor]

            }

         }

        

         // Eingabeschicht:

         $count = count($this->layers[0]);

         for($n = 0; $n < $count; ++$n)

         {

            $this->updateWeights($this->layers[0][$n], $learningRate); // je successor: w[successor] += lernrate * o * fSig[successor]

         }

        

         // On-Neuron:

         if($this->onNeuron != null)

         {

            $this->updateWeights($this->onNeuron, $learningRate);

         }

        

         // Nach den Aktualisierungen der Gewichte die Ausgaben neu berechnen:

         $this->setOutputs();

        

         // Noch bestehenden Fehler zurückgeben:

         return $this->getError($targets);

      }

   }

  

   function dump(array &$weights)

   {

      for($y = 0; $y < count($weights); ++$y)

      {

         for($x = 0; $x < count($weights[$y]); ++$x)

         {

            echo $weights[$y][$x], " ";

         }

         echo "\n";

      }

   }

    

   // Konstante c für logarithmische Funktion:

   $c = 1;

  

   // Lernrate:

   $learningRate = 0.5;

  

   // Architektur:

   //    0             1  Eingabeschicht

   //     \\         //

   //      \ \     / /

   //       \  \ /  /

   //        \  2  /      verdeckte Schicht

   //         \ | /

   //          \|/

   //           3         Ausgabeschicht

   //    Die Neuronen 2 und 3 hängen an einem On-Neuron.

  

   // Gewichte:

   $weights = // connection [from][to]

   [ 

   //   0    1            2                    3

      [0.0, 0.0, -0.693072371801492,  0.757039433217412], // 0

      [0.0, 0.0,  0.373419505445732, -0.22438301746938],  // 1

      [0.0, 0.0,  0.0,               -0.732313332093937], // 2

      [0.0, 0.0,  0.0,                0.0]                // 3

   ];

   // Wenn die Gewichte in die EXCEL-Tabelle kopiert werden, die Punkte durch Kommas ersetzen!

   // -0,693072371801492  0,757039433217412

   //  0,373419505445732 -0,22438301746938

   //                    -0,732313332093937

   //

   //  0,5                0,5

     

   // Eingaben (Muster):

   $patterns =

   [

      [0.0, 0.0],

      [0.0, 0.1],

      [1.0, 0.0],

      [1.0, 1.0]

   ];

     

   // Sollwerte für Ausgaben:

   $targets =

   [

      [0.0],

      [1.0],

      [1.0],

      [0.0]

   ];

  

   // Funktionen:

   $pF = new LinearFunc($weights);

   $aF = new SigmoidFunc($c);

   $oF = new IdentityFunc();

  

   // neuronales Netz mit backpropagation-Lernalgorithmus:

   $net = new BackpropagationNet($weights, $pF, $aF, $oF, true);

  

   // Netz-Fehler:

   $err = 0;

  

   // TODO: hier fehlt noch die Lernschleife


            // Eingabe-Neuronen setzen:

            $net->setInputs($patterns[3]);


            // Schichtweise von oben nach unten die Ausgaben der Neuronen definieren:

            $net->setOutputs();

           

            // Muster lernen:

            $err += $net->learn($learningRate, $targets[3]);


   if(DEBUG)

   {  

      dump($weights);

   }

?>[/php]


Das ist aktuell aber nur ein einziger Lerndurchlauf. Ich weiß leider nämlich noch nicht, wie man den Netzfehler berechnet. Damit habe ich noch Probleme.


Debug-Ausgabe:

[code]Neuron 0 ist ein input-Neuron.

Neuron 1 ist ein input-Neuron.

Neuron 2 ist ein Neuron einer verdeckten Schicht.

Neuron 3 ist ein output-Neuron.

Nachfolger von Neuron 0: 2 3

Nachfolger von Neuron 1: 2 3

Nachfolger von Neuron 2: 3

Vorgänger von Neuron 2: 0 1

Vorgänger von Neuron 3: 0 1 2

Ebene 0: 0/0 0/1

Ebene 1: 1/2

Ebene 2: 2/3

Neuron 0 in: 1

Neuron 1 in: 1

Neuron 2 net: 0.18034713364424

Neuron 2 o: 0.54496497527562

Neuron 3 net: 0.63357129882945

Neuron 3 o: 0.65329880174484

Neuron 3 fSig: -0.14797183717054

Neuron 2 fSig: 0.026871346189312

Gewichte ausgehender Verbindungen von Neuron 2:

   zu Nachfolger 3 deltaW: -0.040319734292565

   zu Nachfolger 3 w: -0.7726330663865

Gewichte ausgehender Verbindungen von Neuron 0:

   zu Nachfolger 2 deltaW: 0.013435673094656

   zu Nachfolger 2 w: -0.67963669870684

   zu Nachfolger 3 deltaW: -0.073985918585268

   zu Nachfolger 3 w: 0.68305351463214

Gewichte ausgehender Verbindungen von Neuron 1:

   zu Nachfolger 2 deltaW: 0.013435673094656

   zu Nachfolger 2 w: 0.38685517854039

   zu Nachfolger 3 deltaW: -0.073985918585268

   zu Nachfolger 3 w: -0.29836893605465

Gewichte ausgehender Verbindungen von Neuron 4:

   zu Nachfolger 2 deltaW: 0.013435673094656

   zu Nachfolger 2 w: 0.51343567309466

   zu Nachfolger 3 deltaW: -0.073985918585268

   zu Nachfolger 3 w: 0.42601408141473

Neuron 2 net: 0.22065415292821

Neuron 2 o: 0.55494080458488

Neuron 3 net: 0.38193304448282

Neuron 3 o: 0.59433924502693

0 0 -0.67963669870684 0.68305351463214 0

0 0 0.38685517854039 -0.29836893605465 0

0 0 0 -0.7726330663865 0

0 0 0 0 0

0 0 0.51343567309466 0.42601408141473 0 [/code]



Oben