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);
}
?>