ich bin mir nicht wirklich sicher ob mein Beitrag in dieses Forum passt. Eigentlich will ich mich hier nur mit Euch austauschen. Ich bin nämlich an einem Punkt angelangt, an dem ich Hilfe bei Design einer Library brauche. Aber ich will ja nicht nehmen ohne zu geben und werde über meine Erfahrungen mit neuronalen Netzen, in diesem Fall dynamische Netze, berichten. Vielleicht ist das ja dem ein oder anderen eine Hilfe, wenn er seine eigenen Netzte implementieren möchte.
Kurze Einführung
Ich werde nur eine kurze Einleitung zu neuronalen Netzen geben. Danach komme ich zu meinen Implementierung eines solchen Netzes. Auf dem Weg dahin werde ich immer wieder Ideen in den Raum werfen. Ganz am Schluss will ich eine, wie ich finde recht interessante, Idee vorstellen, an deren Umsetzung ich mich nun heranwagen will.
neuronale Netze : Gehirn, Neuronen und Impulse
Inspiert wurde das neuronale Netz vom menschlichen Gehrin. Im diesem finden wir Neuronen, die miteinander verknüpft sind. Die Übertragung von Signalen findet dabei als elektrischer Impuls statt. Ein Neuron empfängt eine Vielzahl solcher Impulse, verarbeitet sie und sendet letzten Endes einen weiteren Impuls aus, wichtig ist, dass die Stärke dieser Impulse variert. Normalisieren wir das Output stellen wir fest, dass ein Neuron Impulse aussendet, deren Stärke zwischen 0 und 1 liegt, natürlich nur unter der Voraussetzung, dass die Stärke des Impulses immer positiv ist.
Wir halten also folgendes fest:
- - neuronale Netze bestehen aus Neuronen
- die Neuronen sind untereinander verbunden
- ein Neuron sammelt alle Eingangsimpulse und gibt darauf einen Ausgangsimpuls aus
- der Wertebereich der normalisierten Impulse liegt ist 0 bis 1
Kommen wir nun zum anwendungsorientierten Teil. Wie müssen meine Datentypen aussehen, damit ich ein neuronales Netz implementieren kann? Die ganze Sache ist extrem einfach, schauen wir uns dazu einfach mal an, welche Forderungen unser Datentyp erfüllen muss.
- 1) muss Input von anderen Neuronen verarbeiten können
2) der Wertebereich von Input und Output muss von 0 bis 1 reichen
3) muss die Inputs verrechnen und ein Output ausgeben
zu 1)
Damit wir in der Lage sind Input von anderen Neuronen anzunehmen brauchen wir nichts weiter als eine Referenz oder einen Zeiger über den wir auf diese "Inputneuronen" zugreifen können. Wählt selbst was ihr nehmen wollt, es ist Situationsabhängig für welche Methode man sich entscheidet. Ich halte mich immer daran, dass man mit einer Referenz weniger falsch machen kann als mit einem Zeiger. Unser Datentyp sähe also wie folgt aus:
Code: Alles auswählen
struct Neuron{
array Input;
};
zu 2)
Da der Wertebereich von In- und Output zwischen 0 und 1 liegen soll, bietet sich hier natürlich die Verwendung der elementaren Datentypen float oder double an. Zu beachten, dass double doppelt so viel Speicher wie float besetzt. float bietet sich hier einfach an, da die Genauigkeit der ersten Nachkommastellen vollkommen ausreicht. Beim Durchlaufen eines Netzes wird oft auf den "Output"-Wert eines Neurons zugegriffen, daher empfiehlt es sich diesen in dem Datentypen zu speichern.
Code: Alles auswählen
struct Neuron{
array Input;
float Output;
};
Nun müssen wir also nur noch eine Funktion schreiben, die alle "Output"-Werte aller "Input"-Neuronen abruft, verrechnet und in Output speichert.
Code: Alles auswählen
struct Neuron{
array Input;
float Output;
void Do_Calc(void){
for(uint i = 0; i < array.lentgh(); i++){
//hier werden die Werte verrechnet und das Ergebnis in Output gespeichert
}
}
};
Unsere Do_Calc() Funktion ist die Implementierung einer Akivierungsfunktion, welche uns "Output"-Werte liefert. Es gibt verschiedene Ansätze für diese Funktionen. Die Graphen dieser Funktionen haben jedoch eines gemeinsam, sie nähern sich 0 und 1 an. 0 für negative und 1 für positive Werte. Ich werde hier nur auf die Sidmoidfunktion eingehen. Wer sich diese Funktion genauer ansehen will sollte einfach mal googlen oder gleich auf wikipedia nachsehen. Nun da wir eine Formel für unsere Aktivierungsfunktion haben sollten wir noch klären, wie wir aus unseren "Input"-Werten einen Wert berechnen, den wir unserer Funktion übergeben können.
Machen wir das ganze Schritt für Schritt. Als erstes könnte man alle "Input"-Werte aufsummieren und damit unsere Funnktion speisen. Das für sich reicht noch nicht aus, den alle "Input"-Wert werden gleich gewichtet, etwas was wir eigentlich nicht haben wollen. Hierzu ein kurzes Beispiel: "Wir müssen eine Entscheidung auf Grundlage von Informationen treffen, im allgemeinen können wir zwischen wichtigen, nicht ganz so wichtigen und unwichtigen Informationen unterscheiden. Soll heißen das wir abhängig von der Gewichtung der Informationen unsere Entscheidung treffen." Um zu erreichen, dass "Input"-Werte unterschiedlich gewichtet werden multiplizieren wir zu jeden "Input"-Wert mit einen dazugehörigen "Weight"-Wert, das machen wir für alle "Input"-Werte und summieren diese auf. Schauen wir uns einmal an, wie nun unser Datentyp aussieht.
Code: Alles auswählen
struct Neuron{
array Input;
array Weight;
float Output.
void Do_Calc();
};
Training
Um das Netz nun zu trainieren gibt es verschiedene Möglichkeiten. Man unterscheidet hierbei zwischen dem überwachten und dem unüberwachten Lernen. Ich will hier bei der Natur als Beispiel bleiben und werde hier genetische Algorithmen als eine Trainingsmethode für neuronale Netze aufzeigen. Im Prinzip ist es ganz einfach. Nachdem wir unser Netz erstellt haben legen wir Kopien an und initialisieren alle Gewichte unserer Neuronen mit einem Zufallswert. Nun lassen wir unsere Netze gegeneinander antreten, wählen am Schluss die Netze, die das beste Ergebnis liefern, kopieren diese und machen nun folgendes. Wir gehen über alle Gewichte der Netze drüber und entscheiden per Zufall ob sich diesen Wert ändern soll oder nicht. Hier nochmal das Vorgehen:
- 1) über alle Gewichte iterieren
2) per Zufall bestimmen ob ein Geicht mutieren soll oder nicht
3) Wert des Gewichts ändern