[PYTHON] Probieren Sie die C ++ - API von NNabla aus

Mit dem Deep Learning Framework von SONY NNabla werde ich die Schritte vom Lernen mit Python bis zu Schlussfolgerungen aus C ++ ausprobieren.

Ich denke, eines der Merkmale von NNabla ist, dass der Kern reines C ++ ist. Das Lernen erfolgt auf einem Computer mit einer GPU, und ich denke, es ist ein Vorteil, beim Exportieren des gelernten Netzes auf ein eingebettetes Gerät schnell auf C ++ migrieren zu können.

Es gibt noch kein Dokument zur C ++ - API, aber ab Version 0.9.4 wurden Verwendungsbeispiele zu examples / cpp hinzugefügt. Wenn es nur eine Folgerung ist, scheint es, dass es verwendet werden kann, indem dies nachgeahmt wird.

Wie üblich lautet das Thema Ketten- und Tiefenlernen, das durch Funktionsnäherung gelernt wurde. Eine Funktion namens exp (x) wird von einem dreischichtigen MLP abgeleitet.

Der gesamte Code wird hier veröffentlicht [https://github.com/ashitani/NNabla_exp].

Lernen mit Python

Importieren Sie zunächst die Basisbibliothek.

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import nnabla.solvers as S

Die Graphgenerierung ist fast die gleiche wie bei Chainer. Funktionen ohne Parameter sind in F definiert, und Funktionen mit Parametern sind in PF definiert.

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

Für PF können Sie den Bereich definieren oder das Namensargument angeben. Beachten Sie jedoch, dass der Namespace abgedeckt wird und ein Fehler auftritt, wenn Sie ihn nicht angeben. Da es kein leaky_relu gab, habe ich es diesmal durch elu ersetzt.

Definieren Sie als Nächstes die Verlustfunktion. Es gibt kein besonderes Zögern.

t = nn.Variable((batch_size,1))
loss = F.mean(F.squared_error(y, t))

Die Definition des Lösers. In Adam ohne nachzudenken.

solver = S.Adam()
solver.set_parameters(nn.get_parameters())

Drehen Sie die Lernschleife. Mit dem Fluss von forward (), zero_grad (), backward (), update () ist es ein Workflow, der sich überhaupt nicht seltsam anfühlt, wenn Sie an Chainer gewöhnt sind. Es scheint eine Funktion für die Datenversorgung zu geben, aber ich habe sie diesmal nicht verwendet.

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)

Ich habe den Verlust geplant.

train.png

Verwenden Sie .d oder .data, um auf die Daten des Knotens zuzugreifen. Sie können () an einen beliebigen Knoten im Diagramm weiterleiten. Verwenden Sie dies, um Rückschlüsse zu ziehen.

x.d= 0.2
y.forward()
print(y.d[0][0])

Auf den ersten Blick ist es seltsam, dass selbst wenn Sie einen Skalar in ein Netz mit einer Stapelgröße von 100 einfügen, dieser erfolgreich ist. Wenn Sie jedoch einen Skalar einfügen, wird dieser durch Daten ersetzt, die denselben Wert für 100 Teile enthalten. Daher wird für 100 Ausgänge der gleiche Wert ausgegeben.

Gelernte Parameter können mit save_parameters () gespeichert werden.

nn.save_parameters("exp_net.h5")

Der Code, der mithilfe der gespeicherten Parameter abgeleitet werden kann, lautet hier. Es kann mit load_parameters () aufgerufen werden. Zum Zeitpunkt der Inferenz ist es besser, die Chargengröße auf 1 zu ändern.

Das Inferenzergebnis. Die blaue Linie ist die Ausgabe der Mathematikbibliothek, und die rote Linie ist die Ausgabe dieses Netzes, das sich fast überlappt.

graph.png

Inferenz in C ++

Es scheint, dass Sie es aus C ++ verwenden können, wenn Sie es im Format von NNP speichern. Ich habe versucht, die Beschreibung dieses Bereichs nachzuahmen, aber ich denke, dass die API-Dokumentation bald erstellt wird.

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)

Verwenden Sie dies im folgenden Code. Es ist fast ein Beispiel.

#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;
}

Das für den Build verwendete Makefile ist unten aufgeführt. Es bleibt als Beispiel.

all: exp_net.cpp
    $(CXX) -std=c++11 -O -o exp_net exp_net.cpp -lnnabla -lnnabla_utils

clean:
    rm -f exp_net

Dies ist das Ausführungsergebnis.

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

Der Zeitvergleich der Inferenz selbst ist eine Schleife von 10000 Abtastwerten

Python C++
566msec 360msec

war. Ich denke, dass das Netz wie dieses zu klein und für den Vergleich nicht sehr glaubwürdig ist. Vor allem aber ist der Overhead unmittelbar nach dem Start unterschiedlich, und die Gesamtausführungszeit des Programms, das nur das gleiche 10000-Beispiel berechnet, ist wie folgt. Die Größen von h5 und NNP sind sehr unterschiedlich, und die Python-Seite muss von der Modellkonstruktion ausgehen. Ich denke, es ist ein unfairer Vergleich, also nur als Referenz, aber es ist ganz anders.

Python C++
real 7.186s 0.397s
user 1.355s 0.385s
sys 0.286s 0.007s

Impressionen

Grundsätzlich habe ich das Beispiel nur nachgeahmt, aber ich finde es sehr praktisch, dass das in Python erlernte Modell schnell aus C ++ verwendet werden kann. Der Geschwindigkeitsvergleich ist nicht so gut wie dieser, aber es gibt keinen Grund, langsam zu sein. Ich möchte mit einem großen Netz vergleichen.

Es gibt verschiedene Fallen, da der Build von NNabla auf OS X noch nicht unterstützt wird, aber bald behoben wird. Der Installationsvorgang an dieser Stelle wird in den folgenden Kapiteln beschrieben.

Darüber hinaus gibt es in hier eine wunderbare Anweisung, es mit Docker auszuführen. Ich finde die Idee, den Speicher des Jupyter-Notebooks auf der Host-Seite zu platzieren, großartig.

Installieren Sie die Python-Bibliothek (OSX)

Die Erstellungsprozedur ist wie angewiesen.

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

Der folgende Fehler tritt jedoch beim Import von nnabla auf.

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

Fügen Sie also den Pfad, in dem libnnabla.dylib vorhanden ist, zu DYLD_LIBRARY_PATH hinzu.

export DYLD_LIBRARY_PATH='~/.pyenv/versions/2.7.11/Python.framework/Versions/2.7/lib/python2.7/site-packages/nnabla/':$DYLD_LIBRARY_PATH

Der Import nnabla ist jetzt übergeben.

Wenn ich jedoch "examples / vision / mnist / klassifikation.py" versuche

Symbol not found: __gfortran_stop_numeric_f08

Ich konnte es nicht tun Es scheint, dass wir MNIST-Daten vorbereiten, daher verfolgen wir sie nicht eingehend. Das Beispiel in diesem Artikel konnte vorerst auch in diesem Zustand funktionieren.

Installieren Sie die C ++ - Bibliothek (OSX)

libarchive wird mit homebrew eingefügt.

brew install libarchive
brew link --force libarchive

cmake .. -DBUILD_CPP_UTILS=ON -DBUILD_PYTHON_API=OFF
make

Ich habe den folgenden Fehler erhalten.

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

Anscheinend gibt es eine Versionsinkonsistenz in libarchive. Wenn ich ihm folge, werden die folgenden aufgrund eines Fehlers gestoppt

nnabla/build_cpp/src/nbla_utils/CMakeFiles/nnabla_utils.dir/link.txt

Am Ende von, wo /usr/lib/libarchive.dylib angegeben ist. Ich habe es in /usr/local/lib/libarchive.dylib umgeschrieben, das von Brew installiert wurde, und der Build wurde bestanden. Ich bin nicht sicher, ob / usr / lib von OSX stammt oder ob ich es in die Vergangenheit gestellt habe. ..

Ursprünglich sollte es sich um diejenige handeln, die zum Zeitpunkt der Herstellung durch Brauen installiert wurde. Nun, es hat funktioniert, also bin ich froh.

Recommended Posts

Probieren Sie die C ++ - API von NNabla aus
C-API in Python 3
Versuchen Sie Google Mock mit C.
Versuchen Sie es mit der PeeringDB 2.0-API
Versuchen Sie es mit der Admin-API von Janus Gateway
Erstellen Sie Awaitable mit der Python / C-API
Versuchen Sie es mit der Pleasant-API (Python / FastAPI).
Versuchen Sie es mit der Aktions-API von Python argparse
Versuchen Sie es mit der Wunderlist-API in Python
Versuchen Sie, die Kraken-API mit Python zu verwenden
Versuchen Sie, XOR mit der Keras Functional API zu implementieren
Probieren Sie die Microsoft Cognitive Services Face-API aus
Probieren Sie schnell die Microsoft Face API in Python aus
Versuchen Sie den Geschwindigkeitsvergleich der BigQuery Storage API
Versuchen Sie, die Spotify-API in Django zu aktivieren.
Versuchen Sie etwas wie C # LINQ zu machen