Hallo!
Leider habe ich den Code nicht mehr, den ich verwendet habe, um die untenstehenden Ausgaben zu erzeugen.
Aber trotzdem will ich mal einen Anfang machen.
Hier noch mal die Idee zum Linienfolger, wobei der ganz genaue Antrieb (mit Problemen etc.) jezt mal nicht so wichtig ist.
Es geht hier nur um eine gedankliche Skizze, um die KNN-Funktion anhand eines Beispiels zu veranschaulichen.
Ich bin mit dem Thema noch nicht durch, aber ich habe zumindest einmal den Linienfolgermechanismus mit einer Wahrheitstabelle getestet.
Als Quellcode habe ich den genommen, wo MXT den Link auf die Beispielseite gesetzt hat.
Es sind etwa 2400 bis 2800 Trainingszyklen notwendig, bis die vorgegebene Genauigkeit erreicht wurde. Die Werte, welche diese bestimmen, wurden aus dem Originalquelltext beibehalten.
Das Training dauert, auf einem nodeMCU ESP-12E, ca. 5 Sekunden.
Da es sich um analoge Berechnungen handelt, sind die Werte der Ausgabeneurone Fließkomma-Näherungswerte. Eine Berechnung im KNN stellt immer eine Annäherung dar.
Würden die Ausgaben, ohne Nachkommastellen, gerundet, erhielte man das gewünschte Ergebnis, als "0" oder "1".
Die Trainingszyklen variieren bei jedem Neustart, weil in den Berechnungen Zufallswerte mitspielen. So werden u.a. die Neuronengewichte Anfangs mit Zufallswerten und nicht mit immer gleichen festen Werten initialisiert.
Die Fließkommaergebnisse variieren genau so, weil es Näherungswerte sind und eben in den Berechnungen Zufallswerte mitspielen.
Hier ein Schema, für das Netz aus 2 Input, 2 Hidden und 2 Output Neurons (ich hatte auf die Schnelle keine runden Symbole):
Im Input haben wir 2 Eingänge. Der Erste, für den linken Sensor. Der Zweite für den rechten Sensor. In der Mitte der Sensoren soll die schwarze Linie verlaufen.
Im Output haben wir 2 Ausgänge.
Der Erste, für den linken Motor, der langsam vorwärts fährt oder steht (0); oder schneller vorwärts fährt (1).
Der Zweite, für den rechten Motor, der langsam vorwärts fährt oder steht (0); oder schneller vorwärts fährt (1).
1. Wenn sich der linke Sensor neben der schwarzen Linie befindet (0), kann der linke Motor laufen (1).
2. Wenn sich der rechte Sensor neben der schwarzen Linie befindet (0), kann der rechte Motor laufen (1).
3. Wenn sich der linke Sensor auf der schwarzen Linie befindet (1), kann der linke Motor stehen oder langsam drehen (0).
4. Wenn sich der rechte Sensor auf der schwarzen Linie befindet (1), kann der rechte Motor stehen oder langsam drehen (0).
Das Ergebnis des neuronalen Netzes sieht dazu so aus; zum Vergleich wird Target (das Muster, auf das zuvor trainiert wurde) mit ausgegeben; Target ist eine Vorgabe und wird nicht durch das neuronale Netz erzeugt:
Input 0 0 Target 1 1 Output 0.98662 0.98579
Input 1 0 Target 0 1 Output 0.01587 0.99852
Input 0 1 Target 1 0 Output 0.99999 0.00660
Input 1 1 Target 1 0 Output 0.99995 0.01100
Input sind die Werte des linken und rechten Lichtsensors, auf die das KNN eine Ausgabe (Output) für den linken und rechten Motor erzeugen würde, um das Fahrzeug zu steuern.
Das stellt für mich jetzt den einfachsten Fall einer solchen Steuerung dar, den man mit einem KNN umsetzen kann. Sozusagen das Notwendigste. Das Trainingsset ist als Wahrheitstabelle fest vorgegeben und wird durch die CPU so lange abgearbeitet, bis die richtigen Reaktionen der Ausgänge, auf die Eingangssignale, stattfinden.
Verwendet habe ich 2 Eingangsneuronen, 2 versteckte Neuronen und 2 Ausgangsneuronen.
Mit einem versteckten Neuron funktioniert es auch. Die Ergebnisse sind dann etwas genauer, die Trainingsphase dauert aber ca. 9 Sekunden, bei ca. 9000 Zyklen:
Input 0 0 Target 1 1 Output 0.98612 0.98504
Input 1 0 Target 0 1 Output 0.01456 0.99999
Input 0 1 Target 1 0 Output 1.00000 0.00847
Input 1 1 Target 1 0 Output 1.00000 0.00998
Mit 10 versteckten Neuronen, ca. 900 Trainingszyklen, bei 4 Sekunden:
Input 0 0 Target 1 1 Output 0.98621 0.98668
Input 1 0 Target 0 1 Output 0.01535 0.99720
Input 0 1 Target 1 0 Output 0.99992 0.00877
Input 1 1 Target 1 0 Output 0.99469 0.00908
Mit 20 versteckten Neuronen, ca. 800 Trainingszyklen, bei 4 Sekunden:
Input 0 0 Target 1 1 Output 0.98555 0.98742
Input 1 0 Target 0 1 Output 0.01570 0.99923
Input 0 1 Target 1 0 Output 0.99999 0.00424
Input 1 1 Target 1 0 Output 0.99645 0.01238
Mit 50 versteckten Neuronen dauern die Berechnungen ca. 12 Sekunden, bei ca. 650 Trainingszyklen:
Input 0 0 Target 1 1 Output 0.98657 0.98793
Input 1 0 Target 0 1 Output 0.01528 0.99561
Input 0 1 Target 1 0 Output 0.99999 0.00667
Input 1 1 Target 1 0 Output 0.99218 0.01062
Der Code müsste dafür so modifiziert werden, wenn der auf einem nodeMCU ausgeführt wird.
Nur die Ausgabe entspricht nicht der, die ich hatte, weil ich dafür den Code
noch weiter geändert habe, bloß diesen geänderten Code habe ich nicht mehr.
Code:
/******************************************************************
ArduinoANN - An artificial neural network for the Arduino
All basic settings can be controlled via the Network Configuration
section.
See robotics.hobbizine.com/arduinoann.html for details.
******************************************************************/
#include <math.h>
/******************************************************************
Network Configuration - customized per network
******************************************************************/
const float LearningRate = 0.3;
const float Momentum = 0.9;
const float InitialWeightMax = 0.5;
const float Success = 0.0004;
const int InputNodes = 2;
const int HiddenNodes = 2;
const int OutputNodes = 2;
const int PatternCount = 4; //zu trainierende Muster
const byte Input[PatternCount][InputNodes] =
{
{ 0, 0 },
{ 1, 0 },
{ 0, 1 },
{ 1, 1 }
};
const byte Target[PatternCount][OutputNodes] =
{
{ 1, 1 },
{ 0, 1 },
{ 1, 0 },
{ 1, 0 }
};
/******************************************************************
End Network Configuration
******************************************************************/
int i, j, p, q, r;
int ReportEvery1000;
int RandomizedIndex[PatternCount];
long TrainingCycle;
float Rando;
float Error;
float Accum;
float Hidden[HiddenNodes];
float Output[OutputNodes];
float HiddenWeights[InputNodes + 1][HiddenNodes];
float OutputWeights[HiddenNodes + 1][OutputNodes];
float HiddenDelta[HiddenNodes];
float OutputDelta[OutputNodes];
float ChangeHiddenWeights[InputNodes + 1][HiddenNodes];
float ChangeOutputWeights[HiddenNodes + 1][OutputNodes];
void setup() {
Serial.begin(9600);
randomSeed(analogRead(3));
ReportEvery1000 = 1;
for ( p = 0 ; p < PatternCount ; p++ )
{
RandomizedIndex[p] = p ;
}
}
void loop ()
{
/******************************************************************
Initialize HiddenWeights and ChangeHiddenWeights
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ )
{
for ( j = 0 ; j <= InputNodes ; j++ )
{
ChangeHiddenWeights[j][i] = 0.0 ;
Rando = float(random(100)) / 100;
HiddenWeights[j][i] = 2.0 * ( Rando - 0.5 ) * InitialWeightMax ;
}
}
/******************************************************************
Initialize OutputWeights and ChangeOutputWeights
******************************************************************/
for ( i = 0 ; i < OutputNodes ; i ++ )
{
for ( j = 0 ; j <= HiddenNodes ; j++ )
{
ChangeOutputWeights[j][i] = 0.0 ;
Rando = float(random(100)) / 100;
OutputWeights[j][i] = 2.0 * ( Rando - 0.5 ) * InitialWeightMax ;
}
}
Serial.println("Initial/Untrained Outputs: ");
toTerminal();
/******************************************************************
Begin training
******************************************************************/
for ( TrainingCycle = 1 ; TrainingCycle < 2147483647 ; TrainingCycle++)
{
yield();
/******************************************************************
Randomize order of training patterns
******************************************************************/
for ( p = 0 ; p < PatternCount ; p++)
{
q = random(PatternCount);
r = RandomizedIndex[p] ;
RandomizedIndex[p] = RandomizedIndex[q] ;
RandomizedIndex[q] = r ;
}
Error = 0.0 ;
/******************************************************************
Cycle through each training pattern in the randomized order
******************************************************************/
for ( q = 0 ; q < PatternCount ; q++ )
{
p = RandomizedIndex[q];
/******************************************************************
Compute hidden layer activations
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ )
{
Accum = HiddenWeights[InputNodes][i] ;
for ( j = 0 ; j < InputNodes ; j++ )
{
Accum += Input[p][j] * HiddenWeights[j][i] ;
}
Hidden[i] = 1.0 / (1.0 + exp(-Accum)) ;
}
/******************************************************************
Compute output layer activations and calculate errors
******************************************************************/
for ( i = 0 ; i < OutputNodes ; i++ )
{
Accum = OutputWeights[HiddenNodes][i] ;
for ( j = 0 ; j < HiddenNodes ; j++ )
{
Accum += Hidden[j] * OutputWeights[j][i] ;
}
Output[i] = 1.0 / (1.0 + exp(-Accum)) ;
OutputDelta[i] = (Target[p][i] - Output[i]) * Output[i] * (1.0 - Output[i]) ;
Error += 0.5 * (Target[p][i] - Output[i]) * (Target[p][i] - Output[i]) ;
}
/******************************************************************
Backpropagate errors to hidden layer
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ )
{
Accum = 0.0 ;
for ( j = 0 ; j < OutputNodes ; j++ ) {
Accum += OutputWeights[i][j] * OutputDelta[j] ;
}
HiddenDelta[i] = Accum * Hidden[i] * (1.0 - Hidden[i]) ;
}
/******************************************************************
Update Inner-->Hidden Weights
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ )
{
ChangeHiddenWeights[InputNodes][i] = LearningRate * HiddenDelta[i] + Momentum * ChangeHiddenWeights[InputNodes][i] ;
HiddenWeights[InputNodes][i] += ChangeHiddenWeights[InputNodes][i] ;
for ( j = 0 ; j < InputNodes ; j++ )
{
ChangeHiddenWeights[j][i] = LearningRate * Input[p][j] * HiddenDelta[i] + Momentum * ChangeHiddenWeights[j][i];
HiddenWeights[j][i] += ChangeHiddenWeights[j][i] ;
}
}
/******************************************************************
Update Hidden-->Output Weights
******************************************************************/
for ( i = 0 ; i < OutputNodes ; i ++ )
{
ChangeOutputWeights[HiddenNodes][i] = LearningRate * OutputDelta[i] + Momentum * ChangeOutputWeights[HiddenNodes][i] ;
OutputWeights[HiddenNodes][i] += ChangeOutputWeights[HiddenNodes][i] ;
for ( j = 0 ; j < HiddenNodes ; j++ )
{
ChangeOutputWeights[j][i] = LearningRate * Hidden[j] * OutputDelta[i] + Momentum * ChangeOutputWeights[j][i] ;
OutputWeights[j][i] += ChangeOutputWeights[j][i] ;
}
}
}
/******************************************************************
Every 1000 cycles send data to terminal for display
******************************************************************/
ReportEvery1000 = ReportEvery1000 - 1;
if (ReportEvery1000 == 0)
{
Serial.println();
Serial.println();
Serial.print ("TrainingCycle: ");
Serial.print (TrainingCycle);
Serial.print (" Error = ");
Serial.println (Error, 5);
toTerminal();
if (TrainingCycle == 1)
{
ReportEvery1000 = 999;
}
else
{
ReportEvery1000 = 1000;
}
}
/******************************************************************
If error rate is less than pre-determined threshold then end
******************************************************************/
if ( Error < Success ) break ;
}
Serial.println ();
Serial.println();
Serial.print ("TrainingCycle: ");
Serial.print (TrainingCycle);
Serial.print (" Error = ");
Serial.println (Error, 5);
toTerminal();
Serial.println ();
Serial.println ();
Serial.println ("Training Set Solved! ");
Serial.println ("--------");
Serial.println ();
Serial.println ();
ReportEvery1000 = 1;
while(1){yield();}
}
void toTerminal()
{
for ( p = 0 ; p < PatternCount ; p++ )
{
Serial.println();
Serial.print (" Training Pattern: ");
Serial.println (p);
Serial.print (" Input ");
for ( i = 0 ; i < InputNodes ; i++ )
{
Serial.print (Input[p][i], DEC);
Serial.print (" ");
}
Serial.print (" Target ");
for ( i = 0 ; i < OutputNodes ; i++ )
{
Serial.print (Target[p][i], DEC);
Serial.print (" ");
}
/******************************************************************
Compute hidden layer activations
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ )
{
Accum = HiddenWeights[InputNodes][i] ;
for ( j = 0 ; j < InputNodes ; j++ )
{
Accum += Input[p][j] * HiddenWeights[j][i] ;
}
Hidden[i] = 1.0 / (1.0 + exp(-Accum)) ;
}
/******************************************************************
Compute output layer activations and calculate errors
******************************************************************/
for ( i = 0 ; i < OutputNodes ; i++ )
{
Accum = OutputWeights[HiddenNodes][i] ;
for ( j = 0 ; j < HiddenNodes ; j++ )
{
Accum += Hidden[j] * OutputWeights[j][i] ;
}
Output[i] = 1.0 / (1.0 + exp(-Accum)) ;
}
Serial.print (" Output ");
for ( i = 0 ; i < OutputNodes ; i++ )
{
Serial.print (Output[i], 5);
Serial.print (" ");
}
}
}
MfG
- - - Aktualisiert - - -
Zur Frage, was muss wo gestellt werden oder eingetragen werden, ist ein Grundverständnis notwendig.
in Input[PatternCount][InputNodes] werden die möglichen Eingangszustände vorgegeben, bei 2 Eingängen sind es eben 4 Stück (PatternCount = 4).
in Target[PatternCount][OutputNodes] werden zu den Eingangszuständen, die Ausgangszustände vorgegeben.
Das Netz wird trainiert, indem die Eingangszustände "eingeübt" werden. Dafür werden bei jedem Durchlauf immer die Ergebnisse an den Ausgabeneuronen mit den Werten in Target[x][y] verglichen und dann Korrekturen durchgeführt. Wenn eine vorgegebene Genauigkeit erreicht ist, ist das Training beendet. Ab dem Zeitpunkt kann dann jeder der möglichen Eingangszustände auf den trainierten Ausgangszustand "übersetzt" werden. Und zwar ohne, dass die Trainingsdaten für die Ausgänge hinzugezogen werden.
So wie es vorliegt, mit einer verdeckten Schicht, ähnelt das Ganze mehr einem Logikgatter mit beliebig vielen Eingängen und beliebig vielen Ausgängen. Die Logik ist aber nicht fest verdrahtet, sondern frei wählbar. Das Programm rechnet dann so lange, bis es alle Eingangszustände auf die gewünschten Ausgangszustände abbilden kann.
Lesezeichen