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