PyTorch C ++ VS Python (Ausgabe 2019)

Es gibt viele Arten von Deep Learning-Frameworks wie PyTorch, Tensorflow und Keras. Dieses Mal werde ich auf ** PyTorch ** achten, das ich oft benutze!

Wussten Sie, dass die C ++ - Version sowie die PyTorch- und Python-Version veröffentlicht wurden? Dies erleichtert die Integration, wenn Sie Deep Learning als Teil der Verarbeitung Ihres C ++ - Programms verwenden möchten!

Bei einer solchen C ++ - Version von PyTorch habe ich mich gefragt ** "C ++ ist eine kompilierte Sprache, also ist sie vielleicht schneller als die Python-Version?" **.

Diesmal habe ich also untersucht ** "Wie viel Geschwindigkeit unterscheidet sich zwischen C ++ und Python?" </ Font> **! Außerdem machte ich mir Sorgen um die Genauigkeit und überprüfte sie.

Was für Vergleichsexperimente zu verwenden

1. Rahmen

Dieses Mal werden wir, wie der Titel schon sagt, die C ++ - Version von "PyTorch" verwenden. Sie können es von der folgenden Website herunterladen, versuchen Sie es also bitte!

PyTorch Official: https://pytorch.org/ libtorch.png

Ich habe es mit den obigen Einstellungen heruntergeladen. Die "Preview (Nightly) version" enthält immer die neuesten Dateien. Es befindet sich jedoch noch in der Entwicklung. Wenn Sie also die stabile Version verwenden möchten, wählen Sie "Stabil (1.4)".

Außerdem ist "Diesen Befehl ausführen" unten sehr wichtig. Wenn Sie eine Build-Version von CXX 11 oder höher haben, empfehlen wir Ihnen, die untere Version auszuwählen. Derzeit ist es fast CXX17, also denke ich, dass es unten in Ordnung ist. Wenn Sie das oben Gesagte auswählen, treten Verknüpfungsfehler anderer Bibliotheken auf und es wird viel Ärger geben.

2. Modell

Dieses Mal verwenden wir ** Faltungs-Autoencoder </ font> ** (Faltungs-Autoencoder). Verfügbar von meinem GitHub → https://github.com/koba-jon/pytorch_cpp

Dieses Modell ordnet ** Eingabebild (hohe Dimension) ** dem ** latenten Raum (niedrige Dimension) ** zu und diesmal basierend auf dieser ** latenten Variablen (niedrige Dimension) ** ** Bild ( Der Zweck besteht darin, hochdimensional zu erzeugen ** und den Fehler zwischen diesem und dem Eingabebild zu minimieren. Nach dem Training kann dieses Modell wieder ein hochdimensionales Bild aus einem hochdimensionalen Bild durch einen niedrigdimensionalen Raum erzeugen, so dass ** ein latenter Raum erhalten werden kann, der das Trainingsbild ** charakterisiert **. Mit anderen Worten, es hat die Rolle der Dimensionskomprimierung und kann als sogenannte nichtlineare Hauptkomponentenanalyse bezeichnet werden. Dies ist sehr praktisch, da es verschiedene Verwendungszwecke hat, wie z. B. ** Beseitigung des Dimensionsfluchs **, ** Transferlernen ** und ** Erkennung von Anomalien **.

Jetzt werde ich die Struktur des zu verwendenden Modells erläutern.

  • Die Bildgröße wird mit einer Faltung halbiert und mit umgekehrter Faltung verdoppelt
  • Stabilisierung des Lernens und Beschleunigung der Konvergenz
  • Der mögliche Bereich latenter Variablen ist (-∞, + ∞)
  • Der mögliche Bereich von Pixelwerten ist [-1, +1]

In Erwartung dieser Effekte haben wir das folgende Netzwerk aufgebaut.

Operation Kernel Size Stride Padding Bias Feature Map BN Activation
Input Output
1 Convolution 4 2 1 False 3 64 ReLU
2 64 128 True ReLU
3 128 256 True ReLU
4 256 512 True ReLU
5 512 512 True ReLU
6 512 512
7 Transposed Convolution 512 512 True ReLU
8 512 512 True ReLU
9 512 256 True ReLU
10 256 128 True ReLU
11 128 64 True ReLU
12 64 3 tanh

3. Datensatz

  • CelebA-Datensatz (Large-Scale CelebFaces Attributes)
    http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html

Dieses Mal verwenden wir den CelebA-Datensatz, eine Sammlung von 202.599 Promi-Gesichtsbildern (Farbe). Die Bildgröße beträgt 178 x 218 [Pixel], was beim umgekehrten Falten einige Unannehmlichkeiten verursacht. Deshalb habe ich diesmal die Größe auf ** 64 x 64 [Pixel] ** geändert. Von diesen wurden ** 90% (182.340) zum Lernen von Bildern ** und ** 10% (20.259) für Testbilder ** verwendet.

Wenn dies in das vorherige Modell eingegeben wird, wird der latente Raum (C, H, W) = (512,1,1). Wenn Sie ein Bild mit 128 x 128 [Pixel] oder mehr eingeben, wird die Zwischenebene zu einem räumlichen latenten Raum.

Vergleich

Dieses Mal werde ich hauptsächlich untersuchen, ** "wie unterschiedlich die Geschwindigkeit zwischen C ++ und Python ist" **, aber ich möchte die Geschwindigkeit und Leistung unter den folgenden 5 Arten von Umgebungen vergleichen. ..

  • CPU-Hauptbetrieb
    • Python
    • C++
  • GPU Hauptbetrieb
    • Python
  • Nicht deterministisch --Entscheidend
    • C++

1. Unterschied in der Haupteinheit (CPU oder GPU)

  • CPU
    Gut im Umgang mit "seriellen" und "komplexen" Anweisungen
  • GPU
    Gut in der Verarbeitung von "parallelen" und "einfachen" Anweisungen

Wie aus den obigen Funktionen hervorgeht, ist die GPU in Deep Learning, das Bilder verarbeitet, hinsichtlich der Berechnungsgeschwindigkeit überwältigend vorteilhaft.

(1) Implementierung durch Python

  • Bei Verwendung der CPU

CPU.py


device = torch.device('cpu')  #CPU verwenden

model.to(device)  #Modell in CPU verschieben
image = image.to(device)  #Daten in die CPU verschieben
  • Bei Verwendung der GPU

GPU.py


device = torch.device('cuda')    #Verwenden Sie die Standard-GPU
device = torch.device('cuda:0')  #Verwenden Sie die erste GPU
device = torch.device('cuda:1')  #Verwenden Sie die zweite GPU

model.to(device)  #Modell auf GPU verschieben
image = image.to(device)  #Verschieben Sie Daten auf die GPU

(2) Implementierung durch C ++

  • Bei Verwendung der CPU

CPU.cpp


torch::Device device(torch::kCPU);  //CPU verwenden

model->to(device);  //Modell in CPU verschieben
image = image.to(device);  //Daten in die CPU verschieben
  • Bei Verwendung der GPU

GPU.cpp


torch::Device device(torch::kCUDA);     //Verwenden Sie die Standard-GPU
torch::Device device(torch::kCUDA, 0);  //Verwenden Sie die erste GPU
torch::Device device(torch::kCUDA, 1);  //Verwenden Sie die zweite GPU

model->to(device);  //Modell auf GPU verschieben
image = image.to(device);  //Verschieben Sie Daten auf die GPU

2. Unterschied zwischen deterministisch und nicht deterministisch (GPU-Hauptoperation & nur Python)

In der Python-Version von PyTorch wird beim Lernen mit GPU cuDNN verwendet, um die Lerngeschwindigkeit zu verbessern.

Im Gegensatz zu C ++ bedeutet die Verbesserung der Lerngeschwindigkeit jedoch nicht, dass genau dieselbe Situation reproduziert werden kann, indem das Lernen erneut gedreht wird.

Daher besagt die PyTorch-Formel, dass zur Gewährleistung der Reproduzierbarkeit das Verhalten von cuDNN wie folgt deterministisch gemacht werden muss und gleichzeitig die Geschwindigkeit abnimmt.

https://pytorch.org/docs/stable/notes/randomness.html

Deterministic mode can have a performance impact, depending on your model. This means that due to the deterministic nature of the model, the processing speed (i.e. processed batch items per second) can be lower than when the model is non-deterministic.

Aus Sicht des Ingenieurs habe ich es in diesen Geschwindigkeitsvergleich aufgenommen, da ich möglicherweise besorgt bin über das Vorhandensein oder Nichtvorhandensein von Reproduzierbarkeit und die Geschwindigkeitsänderungen in Abhängigkeit vom Vorhandensein oder Fehlen von Reproduzierbarkeit.

Wenn Sie im Gegensatz zur "Rand" -Funktion von C ++ keinen Anfangswert der Zufallszahl festlegen, ist diese zufällig. Um die Reproduzierbarkeit in Python sicherzustellen, ** explizit ** den Anfangswert der Zufallszahl Sie müssen es einstellen. (Das Einstellen des Anfangswertes der Zufallszahl hat keinen Einfluss auf die Geschwindigkeit.)

Die Implementierung ist wie folgt.

  • Definitiver Fall

deterministic.py


seed = 0
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True  #Definitiv statt langsamer zu werden
torch.backends.cudnn.benchmark = False     #Definitiv statt langsamer zu werden
  • Nicht deterministischer Fall

non_deterministic.py


torch.backends.cudnn.deterministic = False  #Schneller statt nicht deterministisch
torch.backends.cudnn.benchmark = True       #Beschleunigen Sie, wenn sich die Bildgröße nicht ändert

3. Unterschiede in der Implementierung zwischen den Programmiersprachen

Selbst wenn der Inhalt, den Sie implementieren möchten, derselbe ist, können sich bei Änderung der Programmiersprache ** Notation und Regeln ** und ** erforderliche Bibliotheken ** ändern. Da sowohl Python als auch C ++ objektorientierte Sprachen sind, sind die Konzepte selbst ähnlich. Vor allem aber, da Python ein Interpretertyp und C ++ ein Kompilierungstyp ist, muss es implementiert werden, da die dynamische Typisierung für C ++ nicht funktioniert. Es sollte auch berücksichtigt werden, dass einige Funktionen nicht verfügbar sind, da die C ++ - API von PyTorch derzeit entwickelt wird.

Basierend auf diesen Punkten werde ich den Unterschied in der Implementierung zwischen Python und C ++ und dem von mir implementierten Programm vorstellen.

(1) Bibliotheksnutzung

Neben dem derzeit allgemein geschriebenen Nutzungsstatus der Python-Bibliothek werden auch die für die Implementierung in C ++ empfohlene Bibliothek und der Nutzungsstatus der Bibliothek des Programms beschrieben, das ich tatsächlich geschrieben habe.

Python (empfohlen) C++(Empfehlung) C++(selbstgemacht)
Umgang mit Befehlszeilenargumenten argparse boost::program_options boost::program_options
Modelldesign torch.nn torch::nn torch::nn
Vorverarbeitung (Transformation) torchvision.transforms torch::data::Transformationen (für verschiedene Vorverarbeitungen vor der Ausführung)
or
Selbst gemacht (bei verschiedenen Vorverarbeitungen nach der Ausführung)
Selbst gemacht (mit OpenCV)
Datensätze abrufen (Datensätze) torchvision.Datensätze (mit Kissen) Selbst gemacht (mit OpenCV) Selbst gemacht (mit OpenCV)
Dataloader torch.utils.data.DataLoader torch::data::make_data_Lader (zur Klassifizierung)
or
Selbst gemacht (außer Klassifizierung)
Selbst gemacht (mit OpenMP)
Verlustfunktion (Verlust) torch.nn torch::nn torch::nn
Optimierungsmethode (Optimierer) torch.optim torch::optim torch::optim
Fehler zurück Propagierungsmethode (rückwärts) torch.Tensor.backward() torch::Tensor::backward() torch::Tensor::backward()
Fortschrittsanzeige tqdm boost selbstgemacht

** Im Moment (24.03.2020) </ font> ** sieht es wie oben aus.

Bei Verwendung der PyTorch-Bibliothek in C ++ sind die Klassennamen und Funktionsnamen fast identisch mit Python. Dies scheint auf die Rücksichtnahme des Benutzers auf der Herstellerseite zurückzuführen zu sein. Ich bin sehr dankbar!

Als nächstes beschreibe ich die Punkte, auf die Sie beim Schreiben von PyTorch-Programmen in C ++ besonders achten sollten.

(2) Modellgestaltung

Das Folgende ist ein Auszug aus einem Teil des Programms, das ich geschrieben habe.

networks.hpp (Teilauszug)


using namespace torch;
namespace po = boost::program_options;

struct ConvolutionalAutoEncoderImpl : nn::Module{
private:
    nn::Sequential encoder, decoder;
public:
    ConvolutionalAutoEncoderImpl(po::Variables_map &vm);
    torch::Tensor forward(torch::Tensor x);
}

TORCH_MODULE(ConvolutionalAutoEncoder);

Verwenden Sie beim Entwerfen eines Modells die Klasse "torch :: nn" wie in Python. Verwenden Sie beim Erstellen eines Modells auch eine Struktur. (Es gibt auch eine Klassenversion, aber es scheint ein wenig kompliziert) Zu diesem Zeitpunkt sollte beachtet werden, dass ** nn :: Module wie in Python </ font> ** geerbt wird. Dies entspricht dem Schreiben von Python.

Als nächstes ist es wichtig, die Struktur ** "[Modellname] Impl" </ font> ** und unter der Struktur ** Fügen Sie "TORCH_MODULE ([Modellname])" </ font> ** hinzu. Wenn Sie dies nicht tun, können Sie das Modell nicht speichern oder laden. Durch Setzen von "TORCH_MODULE ([Modellname])" kann die normale Struktur "ConvolutionalAutoEncoderImpl" als Struktur für das Modell "ConvolutionalAutoEncoder" deklariert werden, wahrscheinlich jedoch durch internes Erben der Klasse. Bist du da? (Erwartet) Daher wird beim Zugriff auf Elementvariablen wie "model-> to (device)" ** "->" (Pfeiloperator) </ font> ** Bitte beachten Sie, dass Sie verwenden müssen.

Als nächstes werden wir in Bezug auf die oben genannten Punkte die Punkte erläutern, die bei der Verwendung des nn-Klassenmoduls zu beachten sind. Sie können "nn :: Sequential" wie in Python verwenden. Verwenden Sie zum Hinzufügen eines Moduls zu "nn :: Sequential" in C ++ ** "push_back" </ font> ** wie den Vektortyp. Beachten Sie, dass Sie ** "->" (Pfeiloperator) </ font> ** verwenden sollten, um die Funktion "push_back" aufzurufen. Das Implementierungsbeispiel sieht wie folgt aus.

networks.cpp (Teilauszug / Modifikation)


nn::Sequential sq;
sq->push_back(nn::Conv2d(nn::Conv2dOptions(3, 64, /*kernel_size=*/4).stride(2).padding(1).bias(false)));
sq->push_back(nn::BatchNorm2d(64));
sq->push_back(nn::ReLU(nn::ReLUOptions().inplace(true)));

(3) Selbst erstellte Transformation / Datensätze / Datenlader

Wenn Sie selbst Transformationen, Datasets und Datenlader erstellen, wird ** ".clone ()" verwendet, um ** zu übergeben, wenn Tensortypdaten an andere Variablen übergeben werden. Ich war hier süchtig danach. Bezieht sich der Tensortyp auf die Handhabung von Berechnungsgraphen? (Erwartet) Wenn Sie es nicht so einstellen, kann sich der Wert im Tensor ändern.

transforms.cpp (Teilauszug)


void transforms::Normalize::forward(torch::Tensor &data_in, torch::Tensor &data_out){
    torch::Tensor data_out_src = (data_in - this->mean) / this->std;
    data_out = data_out_src.clone();
    return;
}

(4) Andere Programme

Andere Programme sind fast die gleichen wie die Python-Version, und es macht nichts besonders süchtig. Außerdem habe ich meine eigene Klasse erstellt, die ich für etwas schwierig hielt, da sie sich von der Python-Version unterschied. Bitte beziehen Sie sich auf den folgenden GitHub für das spezifische Programm. https://github.com/koba-jon/pytorch_cpp/tree/master/ConvAE

Vielleicht schreibe ich einen Kommentar zum Quellcode. Wenn Sie der Meinung sind, dass "das seltsam ist", heißen wir Sie herzlich willkommen.

Elemente, die unter den Programmiersprachen vereinheitlicht sind

Grundsätzlich können Sie denken, dass es fast dasselbe ist, mit Ausnahme des Teils, dem nicht geholfen werden kann, z. B. dass die in Python vorhandene Bibliothek in C ++ nicht vorhanden ist. Sie können auch denken, dass Sie sich nicht vom GitHub-Programm geändert haben.

Insbesondere wurden die folgenden Inhalte beim Vergleich der Python-Version und der C ++ - Version vereinheitlicht.

  • Bildgröße (64 x 64 x 3)
  • Bildtyp (Bildgruppe der Lernmethode A = Bildgruppe der Lernmethode B)
  • Chargengröße (16)
  • Latente Raumgröße (1 x 1 x 512)
  • Optimierungsmethode (Adam, Lernrate = 0,0001, β1 = 0,5, β2 = 0,999)
  • Modellstruktur
  • So initialisieren Sie das Modell
  • Faltschicht, umgekehrte Faltschicht: Durchschnitt 0,0, Standardabweichung 0,02
  • Chargennormalisierung: Durchschnitt 1,0, Standardabweichung 0,02
  • Wie lade ich Daten?
  • Wenn die Klasse "Datasets" initialisiert wird, wird nur der Pfad erfasst, und wenn er tatsächlich betrieben wird, wird das Bild zum ersten Mal basierend auf dem Pfad gelesen.
  • Wenn die Klasse "Datasets" ausgeführt wird, wird nur ein Datensatz (ein Bild und ein Pfad) gelesen.
  • Führen Sie "transform" aus, wenn die Klasse "datasets" ausgeführt wird.
  • Wenn die Klasse "DataLoader" ausgeführt wird, werden die Mini-Batch-Daten parallel aus der Klasse "Datasets" gelesen.
  • So mischen Sie den Datensatz
  • Mischt während des Lernens, mischt aber nicht während der Folgerung.
  • Mischen Sie für jede Epoche ganz am Anfang der Dateneingabe.

Versuchsergebnis

Für jedes zu vergleichende Objekt wurden 182.340 64 × 64-Bilder von celebA verwendet, und ein Mini-Batch-Training eines durch 1 [Epoche] verschlungenen Auto-Encoder-Modells wurde durchgeführt, um den L1-Fehler zu minimieren. ** "Zeit pro [Epoche]" </ font> ** und ** "GPU-Speichernutzung" </ font> Ich überprüfte **.

Hier umfasst "Zeit pro [Epoche]" die Verarbeitungszeit von tqdm und selbst erstellten Funktionen. Ich habe dies aufgenommen, weil es fast keinen Einfluss auf die gesamte Verarbeitungszeit hatte und weil es bequemer ist, eine Visualisierung zu haben, wenn PyTorch tatsächlich verwendet wird, verwenden viele Leute es.

Zusätzlich wurden unter Verwendung des trainierten Modells 20.259 Testbilder einzeln in das Modell eingegeben und getestet. ** "Durchschnittliche Geschwindigkeit der Vorwärtsausbreitung" </ font> ** und ** "L1-Fehler zwischen Eingabebild und Ausgabebild" </ font> Ich habe auch> ** überprüft.

Dann habe ich gelernt und getestet, ohne etwas anderes als die "Ausführungsdatei" und "nvidia-smi" (die von Anfang an ausgeführt wurde, als Ubuntu gestartet wurde) zu starten.

CPU(Core i7-8700) GPU(GeForce GTX 1070)
Python C++ Python C++
Nicht deterministisch Definitiv
Lernen Zeit [Zeit / Epoche] 1 Stunde 04 Minuten 49 Sekunden 1 Stunde 03:00 5 Minuten 53 Sekunden 7 Minuten 42 Sekunden 17 Minuten 36 Sekunden
GPU-Speicher [MiB] 2 9 933 913 2941
Test Geschwindigkeit [Sekunden / Daten] 0.01189 0.01477 0.00102 0.00101 0.00101
L1-Fehler (MAE) 0.12621 0.12958 0.12325 0.12104 0.13158

C ++ ist eine kompilierte Sprache. Daher dachte ich, dass ich die Interpretersprache Python schlagen würde ... aber ** beide waren gute Übereinstimmungen **.

In Bezug auf die Lernzeit haben wir festgestellt, dass die CPU fast gleich ist und die GPU mehr als doppelt so langsam wie C ++ als Python ist. (Warum?) Was dieses Ergebnis betrifft, ist die CPU ungefähr gleich, unterscheidet sich jedoch nur dann stark, wenn es sich um eine GPU handelt.

  • Es ist möglich, dass die Verarbeitung in der PyTorch C ++ - Version der GPU nicht perfekt ist und die Vorwärts- und Rückwärtsausbreitung durch die GPU nicht optimiert ist.
  • Es kann einige Zeit dauern, bis die von der CPU erhaltenen Mini-Batch-Daten auf die GPU übertragen wurden.

Wird wahrscheinlich erwähnt. Während die folgenden Leute experimentieren, scheint es keinen Zweifel zu geben, dass ** Python schneller ist </ font> **, wenn die GPU ausgeführt wird. https://www.noconote.work/entry/2019/01/11/151624

Außerdem sind die Geschwindigkeit und Leistung der Inferenz (Tests) fast identisch mit Python, sodass ** Python derzeit möglicherweise besser ist </ font> **.

Die Speichernutzung der GPU ist aus irgendeinem Grund ebenfalls groß. (Obwohl ReLUs Platz auf True gesetzt ist ...)

Es ist ein Ergebnis von Python (GPU) deterministisch und nicht deterministisch, aber wie die Formel klar sagt, ist deterministisch langsamer. Immerhin wird sich die Zeit hier ändern.

Fazit

  • Lerngeschwindigkeit

    1. Platz: Python-Version (nicht deterministisch, GPU-Hauptoperation)
    1. Platz: Python-Version (deterministisch, GPU-Hauptoperation)
    1. Platz: C ++ - Version (GPU-Hauptoperation)
    1. Platz: CPU-Hauptoperation (Python-Version, C ++ - Version ähnlich)

  • Argumentationsgeschwindigkeit

    1. Platz: GPU-Hauptoperation (Python-Version, C ++ - Version ähnlich)
    1. Platz: CPU-Hauptoperation (Python-Version, C ++ - Version ähnlich)

  • Leistung

  • Alle sind ungefähr gleich

abschließend

Dieses Mal habe ich die Geschwindigkeit und Leistung von PyTorch für Python und C ++ verglichen.

Infolgedessen sind Python und C ++ in Bezug auf die Leistung fast gleich, so dass ich dachte, dass es kein Problem geben würde, PyTorch von ** C ++ ** zu verwenden. ** In diesem Stadium wird möglicherweise nicht empfohlen, C ++ PyTorch aus Gründen der Geschwindigkeit </ font> ** auszuführen.

Vielleicht befindet sich die C ++ - API noch in der Entwicklung und könnte in Zukunft erheblich verbessert werden! Von nun an ist dies meine Erwartung!

Referenz-URL

  • https://pytorch.org/cppdocs/
  • https://orizuru.io/blog/deep-learning/pytorch-cpp_01/
  • https://www.noconote.work/entry/2019/01/08/200120
  • https://www.noconote.work/entry/2019/01/11/151624
  • https://github.com/pytorch/examples/tree/master/cpp

Recommended Posts