En utilisant le framework d'apprentissage profond de SONY NNabla, je vais essayer la procédure de l'apprentissage avec python à la réalisation d'inférences à partir de C ++.
Je pense que l'une des caractéristiques de NNabla est que le noyau est du C ++ pur. L'apprentissage se fait sur une machine avec un GPU, et je pense que c'est un avantage de pouvoir migrer rapidement vers C ++ lors de l'exportation du réseau appris vers un appareil embarqué.
Il n'y a pas encore de document sur l'API C ++, mais à partir de la version 0.9.4, des exemples d'utilisation ont été ajoutés à examples / cpp. Si ce n'est que de l'inférence, vous pouvez l'utiliser en imitant cela.
Comme d'habitude, le thème est chainer et apprentissage profond appris par approximation de fonction. Une fonction appelée exp (x) est déduite par un MLP à trois couches.
Tout le code est affiché ici [https://github.com/ashitani/NNabla_exp).
Tout d'abord, importez la bibliothèque de base.
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import nnabla.solvers as S
La génération de graphes est presque la même que le chainer. Les fonctions sans paramètres sont définies dans F et les fonctions avec paramètres sont définies dans PF.
x = nn.Variable((batch_size,1))
h1 = F.elu(PF.affine(x, 16,name="affine1"))
h2 = F.elu(PF.affine(h1, 32,name="affine2"))
y = F.elu(PF.affine(h2, 1,name="affine3"))
Pour PF, vous pouvez définir la portée ou spécifier l'argument de nom, mais sachez que si vous ne le spécifiez pas, l'espace de noms sera couvert et une erreur se produira. Puisqu'il n'y avait pas de leaky_relu, je l'ai remplacé par elu cette fois.
Ensuite, définissez la fonction de perte. Il n'y a pas d'hésitation particulière.
t = nn.Variable((batch_size,1))
loss = F.mean(F.squared_error(y, t))
La définition du solveur. En Adam sans réfléchir.
solver = S.Adam()
solver.set_parameters(nn.get_parameters())
Tournez la boucle d'apprentissage. Avec le flux de forward (), zero_grad (), backward (), update (), si vous êtes habitué au chainer, c'est un flux de travail qui ne vous semble pas du tout étrange. Il semble y avoir une fonction pour la fourniture de données, mais je ne l'ai pas utilisée cette fois.
losses=[]
for i in range(10000):
dat=get_batch(batch_size)
x.d=dat[0].reshape((batch_size,1))
t.d=dat[1].reshape((batch_size,1))
loss.forward()
solver.zero_grad()
loss.backward()
solver.update()
losses.append(loss.d.copy())
if i % 1000 == 0:
print(i, loss.d)
J'ai tracé la perte.
Utilisez .d ou .data pour accéder aux données du nœud. Vous pouvez transférer () vers n'importe quel nœud du graphique. Utilisez ceci pour faire des inférences.
x.d= 0.2
y.forward()
print(y.d[0][0])
À première vue, il est étrange que même si vous mettez un scalaire dans un filet composé d'une taille de lot de 100, cela passera, mais il semble que si vous mettez un scalaire, il sera remplacé par des données contenant la même valeur pour 100 pièces. Par conséquent, la même valeur sera sortie pour 100 sorties.
Les paramètres appris peuvent être enregistrés avec save_parameters ().
nn.save_parameters("exp_net.h5")
Le code à déduire à l'aide des paramètres enregistrés est ici. Il peut être appelé avec load_parameters (). Au moment de l'inférence, il est préférable de modifier la taille du lot à 1.
Le résultat de l'inférence. La ligne bleue est la sortie de la bibliothèque mathématique et la ligne rouge est la sortie de ce réseau, qui se chevauche presque.
Il semble que vous puissiez l'utiliser à partir de C ++ si vous l'enregistrez au format NNP. J'ai essayé d'imiter la description de cette zone, mais je pense que la documentation API sera bientôt créée.
import nnabla.utils.save
runtime_contents = {
'networks': [
{'name': 'runtime',
'batch_size': 1,
'outputs': {'y': y},
'names': {'x': x}}],
'executors': [
{'name': 'runtime',
'network': 'runtime',
'data': ['x'],
'output': ['y']}]}
nnabla.utils.save.save('exp_net.nnp', runtime_contents)
Utilisez ceci dans le code ci-dessous. C'est presque un exemple.
#include <nbla_utils/nnp.hpp>
#include <iostream>
#include <string>
#include <cmath>
int main(int argc, char *argv[]) {
nbla::CgVariablePtr y;
float in_x;
const float *y_data;
// Load NNP files and prepare net
nbla::Context ctx{"cpu", "CpuCachedArray", "0", "default"};
nbla::utils::nnp::Nnp nnp(ctx);
nnp.add("exp_net.nnp");
auto executor = nnp.get_executor("runtime");
executor->set_batch_size(1);
nbla::CgVariablePtr x = executor->get_data_variables().at(0).variable;
float *data = x->variable()->cast_data_and_get_pointer<float>(ctx);
for(int i=1;i<10;i++){
// set input data
in_x = 0.1*i;
*data = in_x;
// execute
executor->execute();
y = executor->get_output_variables().at(0).variable;
y_data= y->variable()->get_data_pointer<float>(ctx);
// print output
std::cout << "exp(" << in_x <<"):" << "predict: " << y_data[0] << ", actual: " << std::exp(in_x) <<std::endl;
}
return 0;
}
Le Makefile utilisé pour la construction est ci-dessous. Cela reste à titre d'exemple.
all: exp_net.cpp
$(CXX) -std=c++11 -O -o exp_net exp_net.cpp -lnnabla -lnnabla_utils
clean:
rm -f exp_net
C'est le résultat de l'exécution.
exp(0.1):predict: 1.10528, actual: 1.10517
exp(0.2):predict: 1.22363, actual: 1.2214
exp(0.3):predict: 1.34919, actual: 1.34986
exp(0.4):predict: 1.4878, actual: 1.49182
exp(0.5):predict: 1.64416, actual: 1.64872
exp(0.6):predict: 1.81886, actual: 1.82212
exp(0.7):predict: 2.01415, actual: 2.01375
exp(0.8):predict: 2.2279, actual: 2.22554
exp(0.9):predict: 2.45814, actual: 2.4596
La comparaison temporelle de l'inférence elle-même est une boucle de 10000 échantillons
Python | C++ |
---|---|
566msec | 360msec |
était. Je pense que le net comme cette fois est trop petit et pas très crédible pour la comparaison. Cependant, par-dessus tout, la surcharge immédiatement après le démarrage est différente et la durée totale d'exécution du programme qui ne calcule que le même échantillon 10000 est la suivante. Les tailles de h5 et NNP sont assez différentes, et le côté python doit recommencer à partir de la construction du modèle, donc je pense que c'est une comparaison injuste, donc c'est juste pour référence, mais c'est assez différent.
Python | C++ | |
---|---|---|
real | 7.186s | 0.397s |
user | 1.355s | 0.385s |
sys | 0.286s | 0.007s |
En gros, je viens d'imiter l'exemple, mais je pense qu'il est assez pratique que le modèle appris en python puisse être utilisé rapidement à partir de C ++. La comparaison de vitesse n'est pas aussi bonne que celle-ci, mais il n'y a aucune raison d'être lent. Je voudrais comparer avec un grand filet.
Il existe divers pièges car la version de NNabla sur OS X n'est pas encore prise en charge, mais elle sera bientôt résolue. La procédure d'installation à ce stade sera décrite dans les chapitres suivants.
De plus, il y a une merveilleuse instruction dans ici pour l'exécuter avec Docker. Je pense que l'idée de mettre le stockage de fichiers du notebook jupyter du côté hôte est excellente.
La procédure de construction est conforme aux instructions.
git clone https://github.com/sony/nnabla
cd nnabla
sudo pip install -U -r python/setup_requirements.txt
sudo pip install -U -r python/requirements.txt
mkdir build
cd build
cmake ../
make
cd dist
sudo pip install -U nnabla-0.9.4.post8+g1aa7502-cp27-cp27mu-macosx_10_11_x86_64.whl
Cependant, l'erreur suivante se produit avec import nnabla.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/***/.pyenv/versions/2.7.11/Python.framework/Versions/2.7/lib/python2.7/site-packages/nnabla/__init__.py", line 16, in <module>
import _init # Must be imported first
ImportError: dlopen(/Users/***/.pyenv/versions/2.7.11/Python.framework/Versions/2.7/lib/python2.7/site-packages/nnabla/_init.so, 2): Library not loaded: @rpath/libnnabla.dylib
Referenced from: /Users/***/.pyenv/versions/2.7.11/Python.framework/Versions/2.7/lib/python2.7/site-packages/nnabla/_init.so
Reason: image not found
Library not loaded: @rpath/libnnabla.dylib
Alors, ajoutez le chemin où libnnabla.dylib existe à DYLD_LIBRARY_PATH.
export DYLD_LIBRARY_PATH='~/.pyenv/versions/2.7.11/Python.framework/Versions/2.7/lib/python2.7/site-packages/nnabla/':$DYLD_LIBRARY_PATH
L'import nnabla est maintenant passé.
Cependant, quand j'essaye "examples / vision / mnist / classification.py"
Symbol not found: __gfortran_stop_numeric_f08
Je ne pouvais pas le faire. Il semble que nous préparons des données MNIST, donc nous ne les suivons pas en profondeur. Pour le moment, l'exemple de cet article a pu fonctionner même dans cet état.
libarchive est ajouté à l'homebrew.
brew install libarchive
brew link --force libarchive
cmake .. -DBUILD_CPP_UTILS=ON -DBUILD_PYTHON_API=OFF
make
J'ai eu l'erreur suivante.
Undefined symbols for architecture x86_64:
"_archive_read_free", referenced from:
nbla::utils::nnp::Nnp::add(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) in nnp.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [lib/libnnabla_utils.dylib] Error 1
make[1]: *** [src/nbla_utils/CMakeFiles/nnabla_utils.dir/all] Error 2
make: *** [all] Error 2
Apparemment, il y a une incohérence de version dans libarchive. Lorsque je le suis, les éléments suivants sont arrêtés en raison d'une erreur
nnabla/build_cpp/src/nbla_utils/CMakeFiles/nnabla_utils.dir/link.txt
À la fin de, où /usr/lib/libarchive.dylib est spécifié. Je l'ai réécrit dans /usr/local/lib/libarchive.dylib installé par brew et la construction est passée. Je ne sais pas si / usr / lib provient d'OSX ou si je l'ai mis dans le passé. ..
À l'origine, il doit se référer à celui installé par infusion au moment de cmake. Eh bien, cela a fonctionné, donc je suis content.
Recommended Posts