Designfrage: Neuronale Netze



  • Hallo zusammen,

    ich arbeite gerade an neuronalen Netzen (die Details der funktionsweise sind für die Frage nicht so wichtig) und habe gerade angefangen, ein paar Abstraktionen einzuführen, was aber leider dazu geführt hat, dass ich ein paar casts brauche, was ja meistens auf schlechtes Design hindeutet. Ich habe bisher aber keine gute Alternative gefunden, deswegen frage ich jetzt hier.

    Hier erstmal ein grober Überblick: die neuronalen Netze haben verschiedene Layer, die übereinander angeordnet sind. Der output von layer l ist der input von layer l+1 usw. Außerdem gibt es für jedes Layer Gewichte (Matrix/Vektor), mit deren Hilfe der output (auch Aktivierung genannt) berechnet wird. Die Gewichte müssen trainiert werden, dafür habe ich eine Klasse Trainer.
    Es gibt verschiedene Arten von Layers, deswegen habe ich eine Basisklasse Layer eingeführt.
    Außerdem habe ich eine Klasse Network eingeführt, die ein ganzes neuronales Netz mit mehreren layers repräsentiert. Die Layer-Abstraktion benutze ich jetzt z.B. so:

    myVec fwd = input;
    for(size_t l = 0; l < L; ++l)
    {
      fwd = layers[l]->activate(fwd, weights[l]);
    }
    

    Die weights waren früher Teil der Layer selbst, inzwischen habe ich das aber ausgelagert, weil das für das Trainieren an ein paar Stellen dadurch einfacher wird.
    Da es verschiedene Layertypen gibt, gibt es auch verschiedene Weight-Typen (verschiedene Layers haben z.B. verschieden viele Matrizen als Gewichte).
    Das Problem ist nun, dass das Netzwerk die konkreten Klassen der Layers und der weights nicht kennt. Deswegen kann die Signatur von activate nur eine Basisklasse von weights annehmen, sieht also ca. so aus:

    virtual myVec activate(const myVec& input, const Weights& w) = 0;
    

    wobei Weights eine (möglicherweise abstrakte) Basisklasse ist.
    Die einzelnen Layers müssen für das activate aber weights ihres richtigen typs haben, wozu dann leider ein cast notwendig ist, also z.B.

    myVec SpecialLayer::activate(const myVec& input, const Weights& w) override
    {
       const SpecialWeights& sw = dynamic_cast<SpecialWeights&>(w);
       //todo berechne Aktivierung mit den speziellen Gewichten...
    }
    

    Habt ihr eine Idee, wie man das Design so ändern kann, dass dieser cast nicht nötig ist?



  • Handelt es sich um ein feed-forward-backpropagation NN oder um etwas rekursives?



  • Etwas rekursives



  • Schau Dir mal die CVRLib an: http://cvrlib.sourceforge.net/doc/homepage/index.shtml Da sind einige Klassifikatoren, auch kNN mit drin.

    Q schrieb:

    Etwas rekursives

    rekursiv oder rekurrent (also Rueckkopplung von Layer n nach n-m, m<=n)?



  • Auf englisch: recurrent neural network. Rückkopplung nur von layer l nach layer l. Ich möchte etwas eigenes implementieren, nichts fertiges nehmen. Oder meinst du ich soll mir das Design da anschauen?



  • Wie wäre es mit virtuellen Funktionen als Schnittstellen und spezifischen Berechnungen in der jeweiligen sub klasse?

    das ist so das Standard C++ Design was auf deine Formulierung passen würde.

    Interface Klasse ( Basis )
    virtual [ berechne aktivierung ]

    Spezifische Klasse ( Sub ) ( final )
    [ berechne aktivierung spezifisch ]



  • Schau Dir das Design mal an. Vielleicht willst Du ja deren Basisdatenstrukturen fuer Vector und Matrix uebernehmen und Dich mehr auf die Implementierung der kNN konzentrieren.

    Fuer den Rekurrenten Teil kannst Du entweder die existierende Matrix erweitern (dann kommen aber auch viele Leere stellen dazu) oder Du nimmst eine separte Matrix, die nur die rekurrenten Gewichte enthaelt. In letzterem Fall musst Du dann vor der Berechnung der Aktivierung die Summe der beiden Werte bilden.



  • bei knns nicht zu viel abstrahieren. Die meisten Knns sind kurze, einfache Formeln und das sollte man auch so implementieren.

    Insbesondere bei rekurrenten Netzwerken nichts fancy implementeiren, du kannst sie eh nicht ordentlich trainieren - je eingeschränkter die Struktur, desto höher die Wahrscheinlichkit, dass da was sinnvolles bei rum kommt.



  • Some Dude schrieb:

    Wie wäre es mit virtuellen Funktionen als Schnittstellen und spezifischen Berechnungen in der jeweiligen sub klasse? [...]

    Virtuelle methoden verwende ich ja bereits (siehe oben). Das Problem ist nur, dass die Argumente der Methode bereits bei der Basisklasse festgelegt werden müssen, ich aber für die überschriebenen methoden Objekte abgeleitete Klassen als Argumente nehmen möchte, wozu dann leider ein cast notwendig ist.

    SOFM schrieb:

    Schau Dir das Design mal an. Vielleicht willst Du ja deren Basisdatenstrukturen fuer Vector und Matrix uebernehmen und Dich mehr auf die Implementierung der kNN konzentrieren.

    Für Matrizen und Vektoren habe ich bereits die Bibliothek Eigen.
    Übrigens funktioniert meine Implementierung bereits. Nur bevor ich das weiter ausbaue, möchte ich ein paar Abstraktionen einbauen, damit ich nicht bei jedem aus verschiedenen Typen von Layern zusammengesetzen Netzwerk einen eigenen Trainer usw. schreiben muss. Bei VCR sehe ich auf den ersten blick keine RNNs und da dort nur relativ einfache Formen von neuronalen netzen implementiert sind, ist da vermutlich auch keine große Notwendigkeit für Abstraktionen.

    otze schrieb:

    bei knns nicht zu viel abstrahieren. Die meisten Knns sind kurze, einfache Formeln und das sollte man auch so implementieren.

    Insbesondere bei rekurrenten Netzwerken nichts fancy implementeiren, du kannst sie eh nicht ordentlich trainieren - je eingeschränkter die Struktur, desto höher die Wahrscheinlichkit, dass da was sinnvolles bei rum kommt.

    Das mit den relativ kurzen Formeln stimmt einigermaßen, aber ich habe verschiedene Arten von Layern mit verschiedenen Arten von Formeln (z.B. LSTM Layer, verschiedene output-Layer, z.B. SoftmaxLayer, evtl. später noch Subsampling-Layer), die ich flexibel zusammen bauen möchte. Und für eine neue Layer-Kombination möchte ich nicht alles anderes wie z.B. Trainer neuschreiben müssen.

    Ihr geht alle sehr auf neuronale Netze ein, was ja auch okay ist. Meine Ausgangsfrage war aber eigentlich relativ spezifisch und ging nur sekundär um die neuronalen Netze selbst.
    Also nochmal zurück zur Ausgangsfrage: Gibt es C++-technisch bzw. designtechnisch Alternativen zu dem ganz oben beschriebenen Ansatz mit casts?



  • Ich verstehe bei Deiner ursprüngöichen F rage nicht, was an einem Gewicht so speziell ist, dass es sch nicht als virtuelle Methode abhandeln lässt?

    Kannst Du das Beispiel dafür etwas genauer darstellen?



  • Entschuldigt die späte Antwort, hatte in letzter Zeit viel zu tun...

    Ok, ich versuchs nochmal neu zu erklären.

    Ich habe verschiedene Layer-Klassen, sagen wir mal abstrakte Basisklasse Layer und davon erbende Klassen LayerA, LayerB und LayerC.
    Außerdem gibt es Gewichte, wofür es eine abstrakte Basisklasse WeightContainer gibt. Für die abgeleiteten Layers gibt es auch entsprechende Gewichte, also WeightContainerA, WeightContainerB, WeightContainerC.

    Dann gibt es eine Klasse Network, die mehrere Layer besitzt.
    Verkürzt/vereinfacht sieht die ca. so aus:

    class Network
    {
    public:
      myVec feedFwd(const myVec& inp)
      {
        myVec fwd = input;
        for(size_t l = 0; l < layers_.size(); ++l)
        {
          fwd = layers[l]->feedFwd(fwd, *weights_[l]);
        }
        return fwd;
      }
    private:
      vector<unique_ptr<Layer>> layers_;
      vector<unique_ptr<WeightContainer>> weights_;
    };
    
    class Layer
    {
    public:
      virtual ~Layer(){}
      virtual myVec feedFwd(const myVec& inp) = 0; 
    };
    
    class LayerA : public Layer
    {
    public:
      virtual myVec feedFwd(const vector<myVec>& inp, WeightContainer& baseWeights) override
      {
        WeightContainerA& weights = dynamic_cast<WeightContainerA&>(baseWeights);
        myVec res;
        //berechne res unter benutzung von weights,
        //Berechnung und Gewichte bei LayerB/LayerC sehen anders aus
        return res;
      }
    }
    
    //LayerB, LayerC analog
    

    Das Problem ist jetzt, dass in den ganzen abgeleiteten Layer-Klassen downcasts benötigt werden, weil die Layers Gewichte vom richtigen konkreten Typ benötigen um das Ergebnis zu berechnen.
    Die Gewichte werden außerhalb der Layer gespeichert, weil eine Trainer-Klasse die Gewichte manipulieren muss, was dadurch einfacher wird.
    Das Netzwerk kennt die konkreten Layer/Weight typen nicht, da der Netzwerk-Code für verschiedene Kombinationen von Layern wiederverwendbar sein soll.
    Eine Alternative hier wäre evtl. ein variadisches Template, aber dafür gibt es noch keine ausreichende Compilerunterstützung und außerdem muss die Zusammensetzung der Layer dann bereits zur Compilezeit feststehen.

    Seht ihr hier designtechnisch oder c++-technisch Alternativen?



  • Unter der Bedingung, dass du nur Gradient-Descent brauchst, kannst das Problem ohne Casts ganz einfach über eine Verallgemeinerung des backpropagation algorithmus lösen.

    Wir habend dazu mal ein kleines Tutorial geschrieben:

    http://image.diku.dk/shark/sphinx_pages/build/html/rest_sources/tutorials/concepts/optimization/conventions_derivatives.html

    und hier das daraus entstehende Interface auch als Konzept tutorial:

    http://image.diku.dk/shark/sphinx_pages/build/html/rest_sources/tutorials/concepts/library_design/models.html

    natürlich musst du es nicht so machen, aber als kleiner Denkanstoß...


Log in to reply