Salut tout le monde !
Problème
Le titre n'est pas très clair alors je vais essayer de détailler.
Je suis actuellement en train de coder des réseaux de neurones artificiels, en tant qu'exercice pour apprendre à m'en servir. Je vais donc tout coder avec la bibliothèque standard du C++, pas question pour moi d'utiliser une bibliothèque spécialisée.
Ce n'est pas juste un perceptron multicouche, j'essaie de faire du deep learning. J'ai donc besoin de pouvoir créer facilement des réseaux avec plusieurs couches de profondeur.
Il existe une bibliothèque en Python appelée Theano permettant de faire ça, et on créait un réseau de neurones de la façon suivante :
1 2 3 4 5 6 7 8 9 10 11 | >>> net = Network([ ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), filter_shape=(20, 1, 5, 5), poolsize=(2, 2)), ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), filter_shape=(40, 20, 5, 5), poolsize=(2, 2)), FullyConnectedLayer(n_in=40*4*4, n_out=100), SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size) >>> net.SGD(training_data, 60, mini_batch_size, 0.1, validation_data, test_data) |
Source : http://neuralnetworksanddeeplearning.com/chap6.html
J'essaie donc de faire quelque chose de similaire en C++ (et je ne compte pas changer de langage, c'est vraiment un exercice et je garde mes outils). Je me demandais donc comment implémenter ça.
C'est donc plus un problème de conception qu'un vrai problème de réseaux de neurones.
Code de départ
J'ai cette classe pour l'instant :
1 2 3 4 5 6 7 8 9 10 11 12 13 | class NeuralNetwork { public: NeuralNetwork(const std::vector<Layer>&); void feedForward(); void backPropagation(); private: std::vector<std::unique_ptr<Layer>> layers; }; |
Et pour l'instant un Layer c'est ça :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef std::function<double (double)> Neuron; class Layer { public: Layer(unsigned int, unsigned int, const Neuron&); std::vector<double> feedForward(const std::vector<double>&); std::vector<double> backPropagation(const std::vector<double>&); private: std::vector<double> a_in; std::vector<double> z; }; |
Les méthodes feedForward() et backPropagation() de NeuralNetwork appellent simplement les méthodes de même nom sur chaque layer, dans l'ordre. Elles sont spécifiques pour chaque type de layer.
Là plusieurs solutions possibles.
Solution 1 : héritage
Là je rend les méthodes feedForward() et backPropagation() virtuelles pures, je fais dériver les classes ConvPoolLayer, FullyConnectedLayer et SoftmaxLayer de Layer et je ré-implémente les méthodes feedForward() et backPropagation().
J'aime pas trop cette solution. Déjà elle m'oblige à utiliser un vector de pointeurs plutôt qu'un tableau d'objets dans NeuralNetwork (pour profiter du polymorphisme) ce qui n'est pas génial. Ensuite créer une nouvelle classe à chaque nouveau type de Layer me paraît lourd.
Qu'en pensez-vous ?
Solution 2 : std::function
Je l'a trouve plus séduisante car elle correspond plus naturellement à ma façon de penser mais plus difficile à mettre en œuvre. Là le constructeur de Layer prend en paramètre deux std::function<> qui encapsulent le comportement de feedForward() et backPropagation(). Et donc les méthodes feedForward() et backPropagation() se content d'appeler l'objet std::function<> stocké en interne.
L'objectif serait de pouvoir créer mes layers spécifiques comme ça :
1 2 3 | // Notez que le constructeur utilise ici n'est pas celui du code donne plus haut Layer ConvPoolLayer = Layer(ConvPoolLayerFeedForward, ConvPoolLayerBackPropagation); Layer FullyConnectedLayer = Layer(FullyConnectedLayerFeedForward, FullyConnectedLayerBackPropagation); |
Est-ce une bonne idée ?
Néanmoins on voit ici un gros problème. Chaque type de Layer possède un constructeur différent (vous pouvez le voir sur le code Python) parce qu'il fonctionne de manière différente des autres, et donc les natures des données qui servent à l'initialiser ne sont pas les mêmes.
Donc je me demandais si en C++ il était possible de faire quelque chose qui ressemblerait à la curryfication dans les langages fonctionnels (application partiel d'arguments).
Je m'explique : j'initialiserais partiellement mon objet avec les fonction correspondant à backPropagation et feedForward (ce qui correspondrait à des politiques différentes).
À ce stade j'aurais des objets génériques partiellement initialisés du type ConvPoolLayer ou FullyConnectedLayer, et je pourrais compléter leur instanciation en les appelant avec des paramètres spécifiques (nombre de neurones d'entré, de sortie… ).
Naïvement comme ça, je me doute que ce n'est pas possible. Néanmoins serait-il possible de simuler ce principe ?
Il s'agit juste de trouver une alternative à l'héritage pour redéfinir les méthodes pour les prendre en paramètre. Pensez-vous que ça puisse être une bonne idée ? Je trouve ça conceptuellement plus logique (mais bon c'est peut-être débile) mais dur à mettre en œuvre dans un langage non fonctionnel.
Notez que je bosse sous VS2012 qui supporte seulement partiellement C++11.