Korrekter Gradient descent algo für NNs?

Einloggen oder registrieren, um einen Kommentar zu schreiben
Bayne
745
Bayne  
Nachdem ich nun die Grundprinzipien zum 1000sten Mal verstanden habe und und mir auch etwa den halben Kurs von Andrew ng reingezogen habe, 
Finde ich immernoch keinen Ansatz durch die loops!

Matrizen in MQL zu implementieren verkompliziert das bisher gelernte derartig, dass es mich nur zurückwirft in meinem Schneckentempo.

Daher die frage nach den loops, was wann berechnet werden muss.
Ich fand vor Tagen einen extrem vereinfachten example Code, ist diese Reihenfolge & Implementation korrekt? Das würde mir sehr helfen, weil ich so wenigstens eine Referenzlösung für mein Problem habe (konkreter Code ist sehr schwer zu finden)

https://github.com/jakezhaojb/Backpropagation-C/blob/master/bp.cpp
#define InputN 64               // number of neurons in the input layer
#define HN 25                   // number of neurons in the hidden layer
#define OutN 64                 // number of neurons in the output layer
#define datanum 500             // number of training samples

void main(){
        double sigmoid(double);
        CString result = "";
        char buffer[200];
        double x_out[InputN];           // input layer
        double hn_out[HN];                      // hidden layer
        double y_out[OutN];         // output layer
        double y[OutN];                         // expected output layer
        double w[InputN][HN];           // weights from input layer to hidden layer
        double v[HN][OutN];                     // weights from hidden layer to output layer
        
        double deltaw[InputN][HN];  
        double deltav[HN][OutN];        
        
        double hn_delta[HN];            // delta of hidden layer
        double y_delta[OutN];           // delta of output layer
        double error;
        double errlimit = 0.001;
        double alpha = 0.1, beta = 0.1;
        int loop = 0;
        int times = 50000;
        int i, j, m;
        double max, min;
        double sumtemp;
        double errtemp;
        
        // training set
        struct{
                double input[InputN];
                double teach[OutN];
        }data[datanum];
        
        // Generate data samples
        // You can use your own data!!!
        for(m=0; m<datanum; m++){
                for(i=0; i<InputN; i++)
                        data[m].input[i] = (double)rand()/32767.0;
                for(i=0;i<OutN;i++)
                        data[m].teach[i] = (double)rand()/32767.0;
        }

        // Initializition
        for(i=0; i<InputN; i++){
                for(j=0; j<HN; j++){
                        w[i][j] = ((double)rand()/32767.0)*2-1;
                        deltaw[i][j] = 0;
                }
        }
        for(i=0; i<HN; i++){
                for(j=0; j<OutN; j++){
                        v[i][j] = ((double)rand()/32767.0)*2-1;
                        deltav[i][j] = 0;
                }
        }

        // Training
        while(loop < times){
                loop++;
                error = 0.0;

                for(m=0; m<datanum ; m++){
                        // Feedforward
                        max = 0.0;
                        min = 0.0;
                        for(i=0; i<InputN; i++){
                                x_out[i] = data[m].input[i];
                                if(max < x_out[i])
                                        max = x_out[i];
                                if(min > x_out[i])
                                        min = x_out[i];
                        }
                        for(i=0; i<InputN; i++){
                                x_out[i] = (x_out[i] - min) / (max - min);
                        }

                        for(i=0; i<OutN ; i++){
                                y[i] = data[m].teach[i];
                        }

                        for(i=0; i<HN; i++){
                                sumtemp = 0.0;
                                for(j=0; j<InputN; j++)
                                        sumtemp += w[j][i] * x_out[j];
                                hn_out[i] = sigmoid(sumtemp);           // sigmoid serves as the activation function
                        }

                        for(i=0; i<OutN; i++){
                                sumtemp = 0.0;
                                for(j=0; j<HN; j++)
                                        sumtemp += v[j][i] * hn_out[j];
                                y_out[i] = sigmoid(sumtemp);
                        }

                        // Backpropagation
                        for(i=0; i<OutN; i++){
                                errtemp = y[i] - y_out[i];
                                y_delta[i] = -errtemp * sigmoid(y_out[i]) * (1.0 - sigmoid(y_out[i]));
                                error += errtemp * errtemp;
                        }

                        for(i=0; i<HN; i++){
                                errtemp = 0.0;
                                for(j=0; j<OutN; j++)
                                        errtemp += y_delta[j] * v[i][j];
                                hn_delta[i] = errtemp * (1.0 + hn_out[i]) * (1.0 - hn_out[i]);
                        }

                        // Stochastic gradient descent
                        for(i=0; i<OutN; i++){
                                for(j=0; j<HN; j++){
                                        deltav[j][i] = alpha * deltav[j][i] + beta * y_delta[i] * hn_out[j];
                                        v[j][i] -= deltav[j][i];
                                }
                        }

                        for(i=0; i<HN; i++){
                                for(j=0; j<InputN; j++){
                                        deltaw[j][i] = alpha * deltaw[j][i] + beta * hn_delta[i] * x_out[j];
                                        w[j][i] -= deltaw[j][i];
                                }
                        }
                }

                // Global error 
                error = error / 2;
                if(loop%1000==0){
                        result = "Global Error = ";
                        sprintf(buffer, "%f", error);
                        result += buffer;
                        result += "\r\n";
                }
                if(error < errlimit)
                        break;

                printf("The %d th training, error: %f\n", loop, error);
        }

}

// sigmoid serves as avtivation function
double sigmoid(double x){
        return(1.0 / (1.0 + exp(-x)));
}

jakezhaojb/Backpropagation-C
jakezhaojb/Backpropagation-C
  • jakezhaojb
  • github.com
Available when training a Neural Network, or an Autoencoder. - jakezhaojb/Backpropagation-C
Bayne
745
Bayne  
Hier die Version auf meinen code umgeschrieben (semi-pseudo code)
int layers = 6; // Hiddenlayers inkl Outputlayer
int times = 1000; // 1000 durchläufe des Trainings
int datanum = Arraysize(x_data);

// was bisher existent ist: (Y(=Label), aNet(=aktivierter Neuronenwert), W[j][i][l] (=Gewichte), Neuronenarray[l] (=enthällt die anzahl an Neuronen für den jedweiligen Layer l)
// Nomenklatur (bei mir in der regel -> j = Sendeneuron, i = empfängerneuron, l = Layer)

//... alle neuen Arrays deklarieren 
Z_delta[][...][...]; // wird größer als nötig deklariert und später resized
deltaW[][...][...];

double error=0, sumtemp=0, errtemp=0;
double alpha, beta; // Keine ahnung warum der mustercode 2 learningrates hatte

// Training
        while(loop < times)
                {
                loop++;
                error = 0.0;
                
                for(m=0; m<datanum ; m++){ // amount of samples
                        // Feedforward                  
                        {
                        
                        
                        for (int l=0; l<layers;l++)
                                {
                                for(i=0; i<Neuronenarray[l]; i++)
                                        {
                                        if (l+1==layers):
                                                        {
                                                        sumtemp = 0.0;
                                                        for(j=0; j<Neuronenarray[l-1]; j++)
                                                                {
                                                                sumtemp += W[j][i][l] * aNet[j][l]; // uncertain?
                                                                }
                                                        aNet[i][l] = sigmoid(sumtemp);      
                                                        }                               
                                        else:
                                                        {
                                                        sumtemp = 0.0;
                                                        for(j=0; j<Neuronenarray[l-1]; j++)
                                                                {
                                                                sumtemp += W[j][i][l] * aNet[j][l]; // uncertain?
                                                                }
                                                        aNet[i][l] = sigmoid(sumtemp);              // sigmoid serves as the activation function
                                                        }
                                        }
                                }

                        // Backpropagation
                        
                        for (int l = layers; layers>0;l--)
                                {
                                for(i=0; i<Neuronenarray[l]; i++)
                                        {
                                        if (l+1==layers):
                                                {
                                                errtemp = y[i] - aNet[i][l];
                                                Z_delta[i][l] = -errtemp * (1.0 + aNet[i][l]) * (1.0 - aNet[i][l]);                                                
                                                error += errtemp * errtemp;             //errorfunktion
                                                }
                                        else    
                                                {
                                                errtemp = 0.0;
                                                for(j=0; j<NeuronenArray[l+1]; j++)                     //für jedes Outputneuron den outputerror mit gewicht  multiplpizieren und dazu summieren
                                                        {
                                                        errtemp += Z_delta[j][l+1] * W[i][j][l];
                                                        Z_delta[i][l] = errtemp * (1.0 + aNet[i][l]) * (1.0 - aNet[i][l]); // NeuronError * Deactivation
                                                        }
                                                }
                                        }
                                }

                        // Stochastic gradient descent

                        for (int l = layers; layers>0;l--)
                                {
                                for(i=0; i<Neuronenarray[l]; i++)
                                        {
                                        if(l== Outputlayer)
                                                for(j=0; j<Neuronenarray[l-1]; j++){                    // für jedes seiner Inputneuronen
                                                deltaW[j][i][l] = alpha * deltaW[j][i][l] + beta * Z_delta[i][l] * aNet[j];     // das deltaWeight berechnen
                                                W[j][i][l] -= deltaW[j][i][l];
                                        else
                                                deltaW[j][i][l] = alpha * deltaW[j][i][l] + beta * Z_delta[i][l] * aNet[j];
                                                W[j][i][l] -= deltaW[j][i][l];
                                                }
                                        }       
                                }
                        }

                                        
                }

                // Global error 
                error = error / 2;
                if(loop%1000==0){
                        result = "Global Error = ";
                        sprintf(buffer, "%f", error);
                        result += buffer;
                        result += "\r\n";
                }
                if(error < errlimit)
                        break;

                printf("The %d th training, error: %f\n", loop, error);
        }

        
Bayne
745
Bayne  
Ich bitte auf Antworten die kurz thematisieren wo im netzwerk etwas falsch läuft 
(nicht an meiner Herangehensweise an das gesamte Themenfeld der NNs bitte).
Oder ergänzend mögliche Infoquellen bezügl loop-reihenfolge für Gradienten Abstieg empfehlen.
Christian
2826
Christian  
Bayne:
Ich bitte auf Antworten die kurz thematisieren wo im netzwerk etwas falsch läuft 
(nicht an meiner Herangehensweise an das gesamte Themenfeld der NNs bitte).
Oder ergänzend mögliche Infoquellen bezügl loop-reihenfolge für Gradienten Abstieg empfehlen.

https://github.com/davidalbertonogueira/MLP

Lesen ...verstehen...konvertieren nach MQL5.

davidalbertonogueira/MLP
davidalbertonogueira/MLP
  • davidalbertonogueira
  • github.com
MLP stands for multilayer perceptron. This project is a simple & fast C++ implementation of a MLP, oriented towards hacking and rapid prototyping. It is well-tested and includes multiple tests for each component as well as use cases. Featuring C++ implementation. Modular-oriented, with classes built on top of each other: Node, Layer and...
Chris70
122
Chris70  

einfach mal stichpunktartig, was mir so auffällt:

  • zu "layers=6": kann man im finetuning machen wenn das Netzwerk funktioniert, doch für erste Tests würde ich die Zahl der Layers runtersetzen mit nur 1-2 hidden layers; die Ergebnisse werden ohnehin selten merklich besser durch viele Layers, doch Du hast eine größere Wahrscheinlichkeit von NaN-Errrors
  • Initialisierung der Gewichte nicht vergessen (i.d.R. Zufallszahl zwischen 0 und 1, siehe der Beispielcode);
  • der Beispielcode mit fix(!) nur 1 hidden layer ist für den Verallgemeinerungsfall nicht sonderlich gut geeignet (meine Meinung), da sich dadurch dass das hidden layer zu sowohl input als auch output eine direkte Verbindung hat, ein paar Besonderheiten ergeben
  • was der Kerl da mit alpha und beta macht, verstehe ich auch nicht; verschiedene learning rates sind allerdings durchaus üblich; das Problem zu großer learning rates (sofern sie noch unter der exploding-gradient-Schwelle liegen) ist, dass zwar schnell ein Minimum erreicht wird, jedoch bei späteren Iterationen die Genauigkeit sinkt; der globale Error wird also zu einem leicht höheren Wert konvergieren; was man also gut machen kann ist eine Funktion für eine dynamische learning rate: am Anfang etwas höher; das ist zwar nicht das, was der da mit alpha und beta macht, doch Du kannst z.B. ein alpha definieren und ein dynamisch kleiner werdendes "addon" abhängig von der Zahl der bereits erfolgten Iterationen draufschlagen
  • was ist bei Dir Z_delta?
  • warum die while-Schleife statt for (int loop=0;loop<times;loop++)? macht letztlich ja das Gleiche und kannst Du so lassen, ist halt nur eine ungewöhnliche Angewohnheit; while-Schleifen sollte man nur einsetzen wo unbedingt nötig wg. infinite-loop-Gefahr; ist in diesem Beispiel zwar nicht vorhanden, doch wenn Du es Dir so angewöhnst, werden des Öfteren Fehler kommen, wo Du nicht verstehst, weshalb sich das Programm aufhängt
  • zur Nomenklatur: wenn Du irgendwo im Internet über neuronale Netze liest, wirst Du feststellen, dass zu 99% die Buchstaben, die näher am Anfang des Alphabets stehen, für Layer weiter "links", also näher im Input-Layer, verwendet werden. In allgemeinen Artikeln zum Thema backpropagation, chain rule und delta rule (siehe z.B. Wikipedia) ist meist j ein Neuron im aktuellen Bezugslayer und i steht dann weiter links bzw. k weiter rechts. Die Buchstaben können auch anders gewählt sein, doch die Richtung ist zu 99% so. Wenn Du also i und j verdrehst, bringt Dich das unnötig durcheinander, wenn Du nach allgemeinen Informationen suchst (kann man so machen, der Code wird funktionieren, trotzdem eine unnötige Irritation)
  • mit l+1==layers meinst Du wahrscheinlich l==layers-1? das soll das letzte hidden layer vor dem output layer sein, oder? warum unterscheidest Du da? was ist anders als bei den anderen hidden layers? [edit: in der backpropagation macht die Unterscheidung Sinn, s.u., im Forward-feed ist das unnötig]
  • forward-feed: für ein Neuron j ERST [Summe aller (Neuronen i * weight_ij)] = Net_in[j] bilden und DANACH erst die Aktivierungsfunktion auf das Ergebnis anwenden; während der Summation hat die Aktivierungsfunktion dort noch nichts zu suchen (= Dein "uncertain")
  • backpropagation: der Beispielcode hat da ein paar Probleme, die für den Verallgemeinerungsfall so nicht funktionieren werden; daher hier einfache Zusammenfassung, wie es definitiv funktioniert (vielleicht haben ja auch andere was davon, die ebenfalls mit dem Thema experimentieren, daher mache ich mir hier die Arbeit, denn ich wäre damals froh über praktische Infos gewesen); ganz wichtig ist hier die Reihenfolge! ERST alle errors/hidden errors berechnenen und erst DANACH der Update der Gewichte, denn die Gewichte müssen unbedingt noch im Ursprungszustand bleiben, welcher zu dem jeweiligen globalen Fehler geführt hat, solange bis alle hidden errrors ermittelt sind; in der Praxis:
    • globaler Error: global_error=0; for (int j=0;j<outputs;j++){global_error+=0.5*pow(label[i]-OUT[j],2);} // das ist nichts anderes als die berühmte "squared error" Funktion, welche wir minimieren wollen; der Faktor 0.5 vorweg dient nur der einfacheren Ableitung, denn f'(x^2)=2x und f'(0.5x^2) ist dann schlicht und einfach x
    • OUT_err[j]=OUT[j]-label[j]
    • hidden error Hidden_err[l][j]: for (int j=1;i<neurons(j);j++) { Hidden_err[l][j]=0; for (int k=0;k<neurons[k];k++){Hidden_err[l][j]+=OUT_err[k]*Weight[l][j][k]; }}   // l ist hier das Layer, für das gerade der hidden error berechnet wird
    • Wiederholung des letzten Schritts für alle hidden layers (also "l")
  • DANACH der Update der Gewichte mittels delta rule: delta_w= -(learnrate) * g(Net_in[i]) * g'(Net_in[j]) * Hidden_err[l][j];
    • "g" steht hier für die Aktivierungsfunktion
    • g(Net_in[i]) kennen wir bereits aus dem forward-feed: es ist ja nichts anderes als die Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron i; dies wiederum ist ja exakt das Output des Neurons i bzw. das was Du bei Dir aNet nennst
    • g'(Net_in[j]) ist die ABLEITUNG der Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron j; letzteren Wert kennen wir ebenfalls noch aus dem forward-feed
    • für das Update der Gewichte zwischen letztem Hidden Layer und dem Output layer haben wir natürlich keinen "hidden error", sondern nur den Error des Output layers selbst; hier kann man in der delta rule also den OUT_err[j] anstelle von Hn_err[j] einsetzen
    • Zeit für das Update der Gewichte: Weight(n)[i][j]+=delta_w
Mach es einfach exakt so und ich garantiere Dir, dass es funktioniert. Die eigentliche Arbeit beginnt bei der Aufbereitung / Normalisierung der Eingangsdaten und dem finetuning der Netzarchitektur, Auswahl der Aktivierungsfunktionen, usw.
    Bayne
    745
    Bayne  
    Chris70:

    einfach mal stichpunktartig, was mir so auffällt:

    • zu "layers=6": kann man im finetuning machen wenn das Netzwerk funktioniert, doch für erste Tests würde ich die Zahl der Layers runtersetzen mit nur 1-2 hidden layers; die Ergebnisse werden ohnehin selten merklich besser durch viele Layers, doch Du hast eine größere Wahrscheinlichkeit von NaN-Errrors
    • Initialisierung der Gewichte nicht vergessen (i.d.R. Zufallszahl zwischen 0 und 1, siehe der Beispielcode);
    • der Beispielcode mit fix(!) nur 1 hidden layer ist für den Verallgemeinerungsfall nicht sonderlich gut geeignet (meine Meinung), da sich dadurch dass das hidden layer zu sowohl input als auch output eine direkte Verbindung hat, ein paar Besonderheiten ergeben
    • was der Kerl da mit alpha und beta macht, verstehe ich auch nicht; verschiedene learning rates sind allerdings durchaus üblich; das Problem zu großer learning rates (sofern sie noch unter der exploding-gradient-Schwelle liegen) ist, dass zwar schnell ein Minimum erreicht wird, jedoch bei späteren Iterationen die Genauigkeit sinkt; der globale Error wird also zu einem leicht höheren Wert konvergieren; was man also gut machen kann ist eine Funktion für eine dynamische learning rate: am Anfang etwas höher; das ist zwar nicht das, was der da mit alpha und beta macht, doch Du kannst z.B. ein alpha definieren und ein dynamisch kleiner werdendes "addon" abhängig von der Zahl der bereits erfolgten Iterationen draufschlagen
    • was ist bei Dir Z_delta?
    • warum die while-Schleife statt for (int loop=0;loop<times;loop++)? macht letztlich ja das Gleiche und kannst Du so lassen, ist halt nur eine ungewöhnliche Angewohnheit; while-Schleifen sollte man nur einsetzen wo unbedingt nötig wg. infinite-loop-Gefahr; ist in diesem Beispiel zwar nicht vorhanden, doch wenn Du es Dir so angewöhnst, werden des Öfteren Fehler kommen, wo Du nicht verstehst, weshalb sich das Programm aufhängt
    • zur Nomenklatur: wenn Du irgendwo im Internet über neuronale Netze liest, wirst Du feststellen, dass zu 99% die Buchstaben, die näher am Anfang des Alphabets stehen, für Layer weiter "links", also näher im Input-Layer, verwendet werden. In allgemeinen Artikeln zum Thema backpropagation, chain rule und delta rule (siehe z.B. Wikipedia) ist meist j ein Neuron im aktuellen Bezugslayer und i steht dann weiter links bzw. k weiter rechts. Die Buchstaben können auch anders gewählt sein, doch die Richtung ist zu 99% so. Wenn Du also i und j verdrehst, bringt Dich das unnötig durcheinander, wenn Du nach allgemeinen Informationen suchst (kann man so machen, der Code wird funktionieren, trotzdem eine unnötige Irritation)
    • mit l+1==layers meinst Du wahrscheinlich l==layers-1? das soll das letzte hidden layer vor dem output layer sein, oder? warum unterscheidest Du da? was ist anders als bei den anderen hidden layers? [edit: in der backpropagation macht die Unterscheidung Sinn, s.u., im Forward-feed ist das unnötig]
    • forward-feed: für ein Neuron j ERST [Summe aller (Neuronen i * weight_ij)] = Net_in[j] bilden und DANACH erst die Aktivierungsfunktion auf das Ergebnis anwenden; während der Summation hat die Aktivierungsfunktion dort noch nichts zu suchen (= Dein "uncertain")
    • backpropagation: der Beispielcode hat da ein paar Probleme, die für den Verallgemeinerungsfall so nicht funktionieren werden; daher hier einfache Zusammenfassung, wie es definitiv funktioniert (vielleicht haben ja auch andere was davon, die ebenfalls mit dem Thema experimentieren, daher mache ich mir hier die Arbeit, denn ich wäre damals froh über praktische Infos gewesen); ganz wichtig ist hier die Reihenfolge! ERST alle errors/hidden errors berechnenen und erst DANACH der Update der Gewichte, denn die Gewichte müssen unbedingt noch im Ursprungszustand bleiben, welcher zu dem jeweiligen globalen Fehler geführt hat, solange bis alle hidden errrors ermittelt sind; in der Praxis:
      • globaler Error: global_error=0; for (int j=0;j<outputs;j++){global_error+=0.5*pow(label[i]-OUT[j],2);} // das ist nichts anderes als die berühmte "squared error" Funktion, welche wir minimieren wollen; der Faktor 0.5 vorweg dient nur der einfacheren Ableitung, denn f'(x^2)=2x und f'(0.5x^2) ist dann schlicht und einfach x
      • OUT_err[j]=OUT[j]-label[j]
      • hidden error Hidden_err[l][j]: for (int j=1;i<neurons(j);j++) { Hidden_err[l][j]=0; for (int k=0;k<neurons[k];k++){Hidden_err[l][j]+=OUT_err[k]*Weight[l][j][k]; }}   // l ist hier das Layer, für das gerade der hidden error berechnet wird
      • Wiederholung des letzten Schritts für alle hidden layers (also "l")
    • DANACH der Update der Gewichte mittels delta rule: delta_w= -(learnrate) * g(Net_in[i]) * g'(Net_in[j]) * Hidden_err[l][j];
      • "g" steht hier für die Aktivierungsfunktion
      • g(Net_in[i]) kennen wir bereits aus dem forward-feed: es ist ja nichts anderes als die Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron i; dies wiederum ist ja exakt das Output des Neurons i bzw. das was Du bei Dir aNet nennst
      • g'(Net_in[j]) ist die ABLEITUNG der Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron j; letzteren Wert kennen wir ebenfalls noch aus dem forward-feed
      • für das Update der Gewichte zwischen letztem Hidden Layer und dem Output layer haben wir natürlich keinen "hidden error", sondern nur den Error des Output layers selbst; hier kann man in der delta rule also den OUT_err[j] anstelle von Hn_err[j] einsetzen
      • Zeit für das Update der Gewichte: Weight(n)[i][j]+=delta_w
    Mach es einfach exakt so und ich garantiere Dir, dass es funktioniert. Die eigentliche Arbeit beginnt bei der Aufbereitung / Normalisierung der Eingangsdaten und dem finetuning der Netzarchitektur, Auswahl der Aktivierungsfunktionen, usw.
      kann fast alles so unterschreiben
      • Initialisierung der Gewichte wirklich nur zwischen 0 & 1  oder auch evtl negative oder größere Werte? (z.B. bei nur tanh?)
      • Z_ delta habe ich einfach aus der Nomenklatur von Anrew Ng übernommen & dürfte mit dem Error eines einzelnen Neurons übereinstimmen, da es sich aus Zielwert- Output ergibt.
      • bei "uncertein war ich mir unsicher ob ich das richtige Neuron mit aNet[j][l] erwischt hatte (lag aber an der vertauschung von i & j -> bei rückvertauschung  wäre Sendeneuron i wäre richtig ) aktivierungsfunktion wird ja erst darunter angewendet.
      das beispiel oben sehe ich mir jetzt an und wende anschließend deine reihenfolge an.
      Chris70
      122
      Chris70  

      Ja, Du hast Recht mit den Gewichten, die können ruhig auch negativ initialisiert werden; Zufallswerte zwischen -1 und +1 wären auch ok. Einen großen Unterschied wird es allerdings nicht machen, denn die Gewichte können auch von alleine nach den ersten Iterationen (also dann, wenn der Fehler noch besonders groß ist) schnell negativ werden, falls größere negative Delta_w vorliegen. Es gibt bei "1" auch keine strenge Grenze, die Werte sollten nur in einem ähnlichen Bereich verteilt sein und in einer ähnlichen Größenordnung wie die Bias-Initialisierung. Die Details macht das Netzwerk dann von alleine.

      Wundere Dich übrigens nicht, wenn Du leicht unterschiedliche Formeln findest; das liegt meist daran, was als Cost Function gewählt wurde. Die Squared-Error-Methode ist mathematisch sicherlich am einfachsten weil sie sich so leicht ableiten lässt.

      Ich kann nur sagen, dass es so wie ich es mache jedenfalls funktioniert, wie ich z.B. anhand des genannten Binärzahlen-Decoders beweisen/prüfen konnte; da habe ich dann einfach 16 Inputs gewählt und mit 1 oder 0 gefüttert um eine zufällige Binärzahl zwischen 0 und 2^15 zu simulieren und als Label die zugehörige Dezimalzahl ("normalisiert" per Division durch 2^15 genommen). Schon nach wenigen Sekunden war das Netz trainiert genug um (nach Denormalisierung) die korrekte zugehörige Dezimalzahl selbst auszurechnen / auszuspucken. So ein simples System ist sehr nützlich um zu experimentieren, was versch. Aktivierungsfunktionen, learning rates und Netzwerkarchitekturen (Layerzahl, Neuronen pro Layer) für Auswirkungen haben. Mit Börsendaten kann man solche Tests nicht machen weil man nicht weiß, wie "korrekt" das Label ist bzw. welche Rolle Zufall spielt. 

      Bayne
      745
      Bayne  
      Chris70:

      einfach mal stichpunktartig, was mir so auffällt:

      • zu "layers=6": kann man im finetuning machen wenn das Netzwerk funktioniert, doch für erste Tests würde ich die Zahl der Layers runtersetzen mit nur 1-2 hidden layers; die Ergebnisse werden ohnehin selten merklich besser durch viele Layers, doch Du hast eine größere Wahrscheinlichkeit von NaN-Errrors
      • Initialisierung der Gewichte nicht vergessen (i.d.R. Zufallszahl zwischen 0 und 1, siehe der Beispielcode);
      • der Beispielcode mit fix(!) nur 1 hidden layer ist für den Verallgemeinerungsfall nicht sonderlich gut geeignet (meine Meinung), da sich dadurch dass das hidden layer zu sowohl input als auch output eine direkte Verbindung hat, ein paar Besonderheiten ergeben
      • was der Kerl da mit alpha und beta macht, verstehe ich auch nicht; verschiedene learning rates sind allerdings durchaus üblich; das Problem zu großer learning rates (sofern sie noch unter der exploding-gradient-Schwelle liegen) ist, dass zwar schnell ein Minimum erreicht wird, jedoch bei späteren Iterationen die Genauigkeit sinkt; der globale Error wird also zu einem leicht höheren Wert konvergieren; was man also gut machen kann ist eine Funktion für eine dynamische learning rate: am Anfang etwas höher; das ist zwar nicht das, was der da mit alpha und beta macht, doch Du kannst z.B. ein alpha definieren und ein dynamisch kleiner werdendes "addon" abhängig von der Zahl der bereits erfolgten Iterationen draufschlagen
      • was ist bei Dir Z_delta?
      • warum die while-Schleife statt for (int loop=0;loop<times;loop++)? macht letztlich ja das Gleiche und kannst Du so lassen, ist halt nur eine ungewöhnliche Angewohnheit; while-Schleifen sollte man nur einsetzen wo unbedingt nötig wg. infinite-loop-Gefahr; ist in diesem Beispiel zwar nicht vorhanden, doch wenn Du es Dir so angewöhnst, werden des Öfteren Fehler kommen, wo Du nicht verstehst, weshalb sich das Programm aufhängt
      • zur Nomenklatur: wenn Du irgendwo im Internet über neuronale Netze liest, wirst Du feststellen, dass zu 99% die Buchstaben, die näher am Anfang des Alphabets stehen, für Layer weiter "links", also näher im Input-Layer, verwendet werden. In allgemeinen Artikeln zum Thema backpropagation, chain rule und delta rule (siehe z.B. Wikipedia) ist meist j ein Neuron im aktuellen Bezugslayer und i steht dann weiter links bzw. k weiter rechts. Die Buchstaben können auch anders gewählt sein, doch die Richtung ist zu 99% so. Wenn Du also i und j verdrehst, bringt Dich das unnötig durcheinander, wenn Du nach allgemeinen Informationen suchst (kann man so machen, der Code wird funktionieren, trotzdem eine unnötige Irritation)
      • mit l+1==layers meinst Du wahrscheinlich l==layers-1? das soll das letzte hidden layer vor dem output layer sein, oder? warum unterscheidest Du da? was ist anders als bei den anderen hidden layers? [edit: in der backpropagation macht die Unterscheidung Sinn, s.u., im Forward-feed ist das unnötig]
      • forward-feed: für ein Neuron j ERST [Summe aller (Neuronen i * weight_ij)] = Net_in[j] bilden und DANACH erst die Aktivierungsfunktion auf das Ergebnis anwenden; während der Summation hat die Aktivierungsfunktion dort noch nichts zu suchen (= Dein "uncertain")
      • backpropagation: der Beispielcode hat da ein paar Probleme, die für den Verallgemeinerungsfall so nicht funktionieren werden; daher hier einfache Zusammenfassung, wie es definitiv funktioniert (vielleicht haben ja auch andere was davon, die ebenfalls mit dem Thema experimentieren, daher mache ich mir hier die Arbeit, denn ich wäre damals froh über praktische Infos gewesen); ganz wichtig ist hier die Reihenfolge! ERST alle errors/hidden errors berechnenen und erst DANACH der Update der Gewichte, denn die Gewichte müssen unbedingt noch im Ursprungszustand bleiben, welcher zu dem jeweiligen globalen Fehler geführt hat, solange bis alle hidden errrors ermittelt sind; in der Praxis:
        • globaler Error: global_error=0; for (int j=0;j<outputs;j++){global_error+=0.5*pow(label[i]-OUT[j],2);} // das ist nichts anderes als die berühmte "squared error" Funktion, welche wir minimieren wollen; der Faktor 0.5 vorweg dient nur der einfacheren Ableitung, denn f'(x^2)=2x und f'(0.5x^2) ist dann schlicht und einfach x
        • OUT_err[j]=OUT[j]-label[j]
        • hidden error Hidden_err[l][j]: for (int j=1;i<neurons(j);j++) { Hidden_err[l][j]=0; for (int k=0;k<neurons[k];k++){Hidden_err[l][j]+=OUT_err[k]*Weight[l][j][k]; }}   // l ist hier das Layer, für das gerade der hidden error berechnet wird
        • Wiederholung des letzten Schritts für alle hidden layers (also "l")
      • DANACH der Update der Gewichte mittels delta rule: delta_w= -(learnrate) * g(Net_in[i]) * g'(Net_in[j]) * Hidden_err[l][j];
        • "g" steht hier für die Aktivierungsfunktion
        • g(Net_in[i]) kennen wir bereits aus dem forward-feed: es ist ja nichts anderes als die Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron i; dies wiederum ist ja exakt das Output des Neurons i bzw. das was Du bei Dir aNet nennst
        • g'(Net_in[j]) ist die ABLEITUNG der Aktivierungsfunktion angewandt auf die Summe der gewichteten Eingänge am Neuron j; letzteren Wert kennen wir ebenfalls noch aus dem forward-feed
        • für das Update der Gewichte zwischen letztem Hidden Layer und dem Output layer haben wir natürlich keinen "hidden error", sondern nur den Error des Output layers selbst; hier kann man in der delta rule also den OUT_err[j] anstelle von Hn_err[j] einsetzen
        • Zeit für das Update der Gewichte: Weight(n)[i][j]+=delta_w
      Mach es einfach exakt so und ich garantiere Dir, dass es funktioniert. Die eigentliche Arbeit beginnt bei der Aufbereitung / Normalisierung der Eingangsdaten und dem finetuning der Netzarchitektur, Auswahl der Aktivierungsfunktionen, usw.

        Habe die Reihenfolge dann mal in code zusammengefasst:

        for (t==;t<times;t++)
        {
         for (d=datanum; d>0;d--)
         {
        globaler Error: global_error=0; 
                for (int j=0;j<Neuronenarray[layers];j++) 
                        {
                        global_error +=0.5 * pow(label[d]-aNet[j][outputlayer] , 2);
                        } 
                //wann (vor oder nach dem GewichtUpdate) berechnen wir den global error ? //XXX1
        
                // Im feedforward wird einfach eine Net Matrix deklariert (ohne aktivierungsfunktion also nicht aNet) (zur vereinfachung)
                double E[][...][...]; // deklarierung einer ErrorMatrix die genauso groß ist wie Net oder aNet, mit ausßahme des ersten Layers bei aNet(==die X_Inputs des Netzes) E[][...][0] bleibt dann einfach immer null (& existiert nur zur vereinfachung der iteration)
        
                for (l=layers; l>1;l--) //l>1 weil über alle hiddenlayers iteriert wird, nicht aber über l==0, wobei es sich (in Net[][][]) um den InputLayer handelt
                        {
                        OUT_err[j][l] = OUT[j][l]-label[j]
                        if (l==layers-1) // Befüllung der Erromatrix
                                {
                                E[j][l]=OUT_err[j][l];
                                }
                        //hidden error Hidden_err[l][j] & Befüllung der Errormatrix
                        for (int j=1;i<neurons(j);j++) // ich gehe davon aus das das i in "i<neurons(j)" ein simpler tippfehler war und normal auch j wäre, stimmts? //XXX2
                                {
                                Hidden_err[l][j]=0; // l ist hier das Layer, für das gerade der hidden error berechnet wird
                                for (int k=0;k<neurons[k];k++)
                                        {                                                     //XXX3
                                        Hidden_err[l][j] += OUT_err[k]*Weight[l][j][k]; // Ist Weight [l][j][k] das gewicht, das die Neuronen aus dem Layer davor mit dem Jetzigen verbindet (ja oder nein)?
                                                                                        // ich schätze nein, da k alphabetisch nach j kommt, was ist dann "neurons[k]"?
                                                                                        // habe die samplenummern (z.B Y[i]) zu d umgeschrieben, weil es mit "datanum" einfacher zu merken ist, 
                                                                                        //demnach wären dann doch i die Sendeneuronen (statt j) aus dem layer davor, j eigentlich die empfängerneuronen im jetzigen layer (statt k)
                                                                                        // und k bräuchten wir nicht oder?  und statt Out_err, dann einfach E[j][l+1]   
                                        }                                                       
        
                                }   
                        }
                //Wiederholung des letzten Schritts für alle hidden layers (also "l")
        
        //DANACH das Update der Gewichte mittels delta rule: 
        
                for (welcher layer zuerst, Fängt man von hinten an oder von vorne?) //XXX4 EDIT: Nets sind ja bereits berechnet worden, sofern ich //XXX3 richtig deute, könnte man die beiden layer loops
                        {                                                                       // zusammelegen, sofern pro layer iteration immernur das weight des darauffolgenden layers [l+1] geupdatet wird.
                                                                                                // da diese ja eigentlich nicht mehr angetastet wird. macht das sinn?
        
                        for (j=0; Neuronenarray[l];j++) //Empfängerneuron j   
                                {
                                for(i=0; Neuronenarray[l-1]) //Sendeneuron i
                                        {
                                        if (l==layers-1)
                                                {
                                                //delta_w[i][j][l-1] = -(learnrate) * g(Net_in[i][l-1]) * g'(Net_in[j][l]) * Hidden_err[j][l]; 
                                                delta_w[i][j][l-1]= -(learnrate) * aNet[i][l-1] * g'(Net[j][l]) * Output_err[j][l]; // aNet= Net ohne aktivierungsfunktion
                                                }
                                        else
                                                {
                                                //delta_w= -(learnrate) * g(Net_in[i]) * g'(Net_in[j]) * Hidden_err[l][j]; 
                                                delta_w[i][j][l-1]= -(learnrate) * aNet[i][l-1] * g'(Net[j][l]) * Hidden_err[j][l];
        
                                                // mit E Matrix wäre es für alle layer:
                                                // delta_w[i][j][l-1]= -(learnrate) * aNet[i][l-1] * g'(Net[j][l]) * E[j][l];
                                                }
        
                                        Weight(n)[i][j][l-1] += delta_w[i][j][l-1];
                                        }
                                }
                        }
         }
        }       

        habe die wenigen vier stellen die noch nicht ganz klar waren mit "//XXX" gekennzeichnet, weil ich sofern diese richtig gelöst sind, sehen könnte ob man die beiden for loops über die layer nicht noch zusammenfassen könnte (Nets sind ja bereits berechnet worden)



        desweiteren könnte ich anschließend schauen wie ich die softmax funktion darein intergrieren kann ( wäre ja dann das gleiche oder? (funktioniert backpropagation mit der ableitung einer softamx funktion oder muss man mit dem Net statt dem aNet wert der Outputneuronen arbeiten? würde dann einfach die jeweiligen Outputneuronenergebnisse an die stellen des labelvektors anpassen z.B. [0,0,1,0] sodass sich die ersten 2 Outputneuronen und das letze dem nullwert nähern müssen und der dritte in diesem Beispiel erhöhrt werden müsse))

        Einloggen oder registrieren, um einen Kommentar zu schreiben