Suche gut designten C++-Code für FeedForward Neuronale Netzwerke und Backpropagation



  • Ich suche gut designten C++-Code für FeedForward Neuronale Netzwerke und Backpropagation. Kann mir jemand was empfehlen?



  • Ich mache nicht viel in diesem Bereich aber von Kollegen weiss ich, dass TensorFlow von Google sehr beliebt ist. Gibts fuer Python und C++.



  • Ich suche eher ein sehr kleines Framework, so maximal zwei Header- und Sourcefiles.



  • Feedforward und Backpropagation ist jetzt kein Hexenwerk. Das kann man sich auch recht zügig selber implementieren 😉 . Wie ein ehemaliger Prof von mir gesagt hat: Das sind doch nur ein paar Matrixmultiplikationen 🕶

    Der Vorteil von selbst implementieren ist, dass das Design genau so ist, wie du es dir vorstellst. Außerdem ist der Lerneffekt nicht zu verachten.

    Ich habe mal die NN von OpenCV eingesetzt, ist aber schon ein bisschen was her und OpenCV beinhaltet auch deutlich mehr als 2 Header. Aber via Google sollte sich zu dem Thema sonst einiges finden lassen.





  • Ich habe mal was programmiert:

    #include <locale>
    #include <iostream>
    using namespace std;
    
    #include <vector>
    #include <cassert>
    
    class NeuralNetwork
    {
    	std::vector<std::vector<std::vector<double>>> weights;
    	std::vector<std::vector<std::vector<double>>> delta_weights;
    	std::vector<std::vector<double>> delta;
    	std::vector<std::vector<double>> output;
    
    	double f(double x) { return 1.0 / (1.0 + exp(-x)); }
    	double df(double x) { return f(x)*(1.0 - f(x)); }
    
    public:
    	NeuralNetwork(const std::vector<size_t>& nPerceptronsInLayer)
    	{
    		assert(!nPerceptronsInLayer.empty());
    
    		weights.resize(nPerceptronsInLayer.size());
    		delta_weights.resize(nPerceptronsInLayer.size());
    		delta.resize(nPerceptronsInLayer.size());
    		output.resize(nPerceptronsInLayer.size());
    		output[0].resize(nPerceptronsInLayer[0]);
    		for (size_t layer = 1; layer < nPerceptronsInLayer.size(); ++layer)
    		{
    			output[layer].resize(nPerceptronsInLayer[layer]);
    			weights[layer].resize(nPerceptronsInLayer[layer]);
    			delta_weights[layer].resize(nPerceptronsInLayer[layer]);
    			delta[layer].resize(nPerceptronsInLayer[layer]);
    			for (size_t j = 0; j < nPerceptronsInLayer[layer]; ++j)
    			{
    				weights[layer][j].resize(nPerceptronsInLayer[layer - 1]);
    				delta_weights[layer][j].resize(nPerceptronsInLayer[layer - 1]);
    			}
    		}
    	}
    
    	std::vector<double> feedForward(const std::vector<double>& input);
    	void backpropagate(const std::vector<double>& input, const std::vector<double>& desiredOutput, double learningRate);
    };
    
    using namespace std;
    
    vector<double> NeuralNetwork::feedForward(const vector<double>& input)
    {
    	assert(output[0].size() == input.size());
    
    	output[0] = input;
    	for (size_t layer = 1; layer < output.size(); ++layer)
    	{
    		for (size_t i = 0; i < output[layer].size(); ++i)
    		{
    			double o = 0;
    			for (size_t j = 0; j < output[layer - 1].size(); ++j)
    			{
    				o += weights[layer][i][j] * output[layer - 1][j];
    			}
    			output[layer][i] = f(o);
    		}
    	}
    	return output.back();
    }
    
    void NeuralNetwork::backpropagate(const std::vector<double>& input, const std::vector<double>& desiredOutput, double learningRate)
    {
    	feedForward(input);
    
    	for (size_t h = 0; h != output.back().size(); ++h)
    	{
    		delta.back()[h] = df(output.back()[h]) * (desiredOutput[h] - output.back()[h]);
    		for (size_t k = 0; k != weights[output.size() - 2][h].size(); ++k)
    			delta_weights.back()[h][k] = delta.back()[h] * output[output.size() - 2][h];
    	}
    
    	for (size_t layer = output.size() - 2; layer != 0; --layer)
    	{
    		for (size_t h = 0; h != output[layer].size(); ++h)
    		{
    			double sum = 0;
    			for (size_t l = 0; l != output[layer + 1].size(); ++l)
    				sum += delta[layer + 1][l] * weights[layer + 1][l][h];
    			delta[layer][h] = sum * df(output[layer][h]);
    			for (size_t k = 0; k != weights[layer][h].size(); ++k)
    				delta_weights[layer][h][k] = delta[layer][h] * output[layer - 1][h];
    		}
    	}
    
    	for (size_t layer = 1; layer != output.size(); ++layer)
    	{
    		for (size_t i = 0; i != weights[layer].size(); ++i)
    		{
    			for (size_t j = 0; j != weights[layer][i].size(); ++j)
    			{
    				weights[layer][i][j] += learningRate * delta_weights[layer][i][j];
    			}
    		}
    	}
    }
    
    int main()
    {
    	locale::global(std::locale(""));
    
    	NeuralNetwork nn({ 3, 3, 3, 1 });
    
    	vector<double> input = { 1,1,1 };
    	cout << "input = [";
    	for (auto i : input)
    		cout << i << ", ";
    	cout << "]\n";
    	vector<double> output = nn.feedForward(input);
    	cout << "output = [";
    	for (auto o : output)
    		cout << o << ", ";
    	cout << "]\n";
    
    	cout << "backpropagating\n";
    	double learningRate = 0.1;
    	for (size_t i = 0; i < 1000; ++i)
    	{
    		nn.backpropagate({ 0,0,0 }, { 0 }, learningRate);
    		nn.backpropagate({ 0,0,1 }, { 1 }, learningRate);
    		nn.backpropagate({ 0,1,0 }, { 1 }, learningRate);
    		nn.backpropagate({ 1,0,0 }, { 1 }, learningRate);
    		nn.backpropagate({ 1,1,0 }, { 0 }, learningRate);
    		nn.backpropagate({ 1,0,1 }, { 0 }, learningRate);
    		nn.backpropagate({ 0,1,1 }, { 0 }, learningRate);
    		nn.backpropagate({ 1,1,1 }, { 1 }, learningRate);
    	}
    	output = nn.feedForward(input);
    	cout << "output = [";
    	for (auto o : output)
    		cout << o << ", ";
    	cout << "]\n";
    
    	char c;
    	cin >> c;
        return 0;
    }
    

    Aber das gibt mir für alle Inputs ungefähr 0.5 zurück ... Was mache ich falsch?



  • Um deinen Code zu reviewn fehlt mir grade die Zeit. Aber das riecht ein bisschen nach schlechten initialen Gewichten (sind die sogar initial bei dir alle gleich Null???). Ich würde die Gewichte erstmal random wählen. Da NN mit Backprop in lokalen Minima stecken bleiben können, bietet es sich eh an, mit unterschiedlichen Startgewichten zu arbeiten.



  • Es geht jetzt.

    Ich suche nach Testdaten für Handschrifterkennung (nur Ziffern von 0 bis 9). Wie viele Layer mit wie vielen Neuronen jeweils sollte ich verwenden?



  • Für Trainings und Textdatei schau mal hier: http://yann.lecun.com/exdb/mnist/

    Wichtig, Trainings- und Textdatei trennen.

    Optimale Layer und Neuronen Anzahl ist nicht trivial. Allgemein kann man sagen, dass sich jedes NN durch ein äquivalentes NN mit einem Hiddenlayer darstellen lässt.
    Trotzdem werden in aktueller Forschung mehrere Hiddenlayer eingesetzt. Aber für dein Problem würde ich auf Keinen Fall mehr als 2 Hiddenlayer nehmen

    Wegen der Neuronen Anzahl wirst du ein bisschen ausprobieren müssen. Bei zuviele Neuronen lernt dein NN das Trainingsset auswendig, performt aber schlecht auf neuen Daten. Bei zu wenigen Neuronen wird das Ergebnis auch schlecht.
    Man kann sagen, die Anzahl Neuronen liegt zwischen 1 und der Anzahl der Input Neuronen + Anzahl der Output Neuronen. Aber eine allgemein gültige Antwort darauf, kenn ich zumindest nicht.

    Edit: Was mir grade noch aufgefallen ist, du hast zumindest in deinem Code oben, kein Bias Neuronen. Normalerweise gibt es zu jedemHiddenlayer ein Bias Neuronen, welches immer -1 ausgibt. Dadurch wird quasi der Schwellwert der Aktivierungsfunktion in jedem Neuronen angepasst und macht das noch ein bisschen flexibler.



  • Danke!

    Ja, das mit den Biasneuronen war ein Fehler, den ich behoben habe.



  • du kannst online ein bisschen herumprobieren, welche Netzarchitektur gute Ergebnisse beim MNIST Datensatz liefert:

    http://cs.stanford.edu/people/karpathy/convnetjs/demo/mnist.html

    Für Mustererkennung in Bildern empfehlen sich außerdem ein oder mehrere Convolutional Layer. Diese Schicht bildet nicht alle Eingaben auf alle Ausgaben ab, sondern bedient sich der Faltung. Dadurch ergeben sich viel weniger Parameter, die trainiert werden müssen (Größe Faltungskern ist oft in der Größenordnung von ein paar wenigen Pixel).


Anmelden zum Antworten