[PYTHON] Essayez l'API C ++ de NNabla

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).

Apprendre avec Python

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.

train.png

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.

graph.png

Inférence en C ++

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

Impressions

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.

Installer la bibliothèque python (OSX)

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.

Installer la bibliothèque C ++ (OSX)

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

Essayez l'API C ++ de NNabla
API C en Python 3
Essayez Google Mock avec C
Essayez d'utiliser l'API PeeringDB 2.0
Essayez d'utiliser l'API Admin de la passerelle Janus
Créer Awaitable avec l'API Python / C
Essayez d'utiliser l'API de Pleasant (python / FastAPI)
Essayez d'utiliser l'API d'action de Python argparse
Essayez d'utiliser l'API Wunderlist en Python
Essayez d'utiliser l'API Kraken avec Python
Essayez d'implémenter XOR avec l'API fonctionnelle Keras
Essayez l'API de visage de Microsoft Cognitive Services
Essayez rapidement l'API Face de Microsoft en Python
Essayez la comparaison de vitesse de l'API BigQuery Storage
Essayez d'accéder à l'API Spotify dans Django.
Essayez de créer quelque chose comme C # LINQ