Machine Learning

Entwicklung, Training und Deployment von neuronalen Netzwerken

Neuronale Netzwerke mit PyTorch entwickeln, trainieren und deployen
Keine Kommentare

PyTorch ist zur Zeit eines der populärsten Frameworks zur Entwicklung und zum Trainieren von neuronalen Netzwerken. Es zeichnet sich vor allem durch seine hohe Flexibilität und die Möglichkeit aus, Standard-Python-Debugger einzusetzen. Dabei müssen keinerlei Abstriche bezüglich der Trainingsperformance gemacht werden.

Aufgrund der genannten Eigenschaften ist PyTorch vor allem bei Deep-Learning-Forschern und bei Entwicklern im Bereich Natural Language Processing (NLP) sehr beliebt. Auch im Bereich Integration und Deployment wurden in der letzten Version, dem ersten offiziellen Release 1.0, wesentliche Neuerungen eingeführt.

Tensoren

Die elementare Datenstruktur zur Repräsentation und Verarbeitung von Daten in PyTorch ist torch.Tensor. Der mathematische Begriff Tensor steht für eine Generalisierung von Vektoren und Matrizen. In PyTorch werden Tensoren in Form von multidimensionalen Arrays implementiert. Ein Vektor ist dabei nichts anderes als ein eindimensionaler Tensor (oder ein Tensor mit Rang 1), deren Elemente Zahlen eines bestimmten Datentyps (z. B. torch.float64 oder torch.int32) sein können. Eine Matrix ist somit ein zweidimensionaler Tensor (Rang 2) und ein Skalar ein nulldimensionaler Tensor (Rang 0). Tensoren noch höherer Dimensionen besitzen keine speziellen Namen mehr (Abb. 1).

Abbildung 1: Tensoren

Abbildung 1: Tensoren

Das Interface für PyTorch-Tensoren lehnt sich stark an das Design von multidimensionalen Arrays in NumPy an. Genauso wie NumPy stellt PyTorch vordefinierte Methoden bereit, mit denen man Tensoren manipulieren und Operationen der linearen Algebra durchführen kann. Einige Beispiele sind in Listing 1 dargestellt.

# Generierung eines eindimensionalen Tensors mit 
# 8 (uninitialisierten) Elementen (float32)
x = torch.Tensor(8)
x.double()  # Konvertierung nach float64 Tensor
x.int()     # Konvertierung nach int32 Datentyp

# 2D-Long-Tensor vorinitialisiert mit Nullen
x = torch.zeros([2, 2])

# 2D-Long-Tensor vorinitialisiert mit Einsen 
# und anschließende Konvertierung nach int64
y = torch.ones([2, 3]).long()

# Zusammenfügen zweier Tensoren entlang Dimension 1
x = torch.cat([x, y], 1)  

x.sum()  # Summe über alle Elemente
x.mean() # Durchschnitt über alle Elemente

# Matrixmultiplikation
x.mm(y)

# Transponieren
x.t()

# Inneres Produkt zweier Tensoren
torch.dot(x, y) 

# Berechnet Eigenwerte und -vektoren
torch.eig(x)

# Gibt Tensor mit dem Sinus der Elemente zurück
torch.sin(x)

Die Anwendung optimierter Bibliotheken wie BLAS, LAPACK und MKL erlaubt eine höchst performante Ausführung von Tensoroperationen auf der CPU (vor allem mit Intel-Prozessoren). Zusätzlich unterstützt PyTorch (im Gegensatz zu NumPy) auch die Ausführung der Operationen auf NVIDIA-Grafikkarten mit Hilfe des CUDA-Toolkits und der CuDNN-Bibliothek. Listing 2 zeigt an einem Beispiel, wie man Tensorobjekte auf den Speicher der Grafikkarte verschiebt, um dort optimierte Tensoroperationen durchzuführen.

# 1D Tensoren
x = torch.ones(1)
y = torch.zeros(1)

# Tensoren auf den GPU-Speicher verschieben
x = x.cuda()
y = y.cuda()

# oder:
device = torch.device("cuda")
x = x.to(device)
y = y.to(device)

# Die Additionsoperation wird nun auf der GPU durchgeführt
x + y  # wie torch.add(x, y)
# Zurückkopieren auf die CPU
x = x.cpu()
y = y.cpu()

Da NumPy-Arrays quasi als Standarddatenstrukturen in der Python-Data-Science-Community gelten, ist in der Praxis ein häufiges Konvertieren von PyTorch nach NumPy und zurück nötig. Diese Konvertierungen können unkompliziert und effizient durchgeführt werden (Listing 3), da dabei der gleiche Speicherbereich geteilt wird, sodass kein Kopieren von Speicherinhalten durchgeführt werden muss.

# Konvertierung nach NumPy
x = x.numpy()

# Konvertierung zurück als PyTorch-Tensor
y = torch.from_numpy(x)
# y zeigt jetzt auf den gleichen Speicherbereich wie x
# eine Änderung von y ändert gleichzeitig auch x

Netzwerkmodule

Die Bibliothek torch.nn enthält viele Tools und vordefinierte Module zur Generierung neuronaler Netzwerkarchitekturen. In der Praxis definiert man seine eigenen Netzwerke durch die Ableitung der abstrakten Klasse torch.nn.Module. In Listing 4 ist die Implementierung eines einfachen Feed-Forward-Netzwerks mit einem Hidden Layer und einer Tanh-Aktivierung aufgeführt.

import torch
import torch.nn as nn

class Net(nn.Module):

  def __init__(self, input_dim, hidden_dim, output_dim):
    super(Net, self).__init__()
    # Hier generierst du Instanzen aller Submodule des Netzwerks

    self.fc1 = nn.Linear(input_dim, hidden_dim) 
    self.act1 = nn.Tanh()
    self.fc2 = nn.Linear(hidden_dim, output_dim)

  def forward(self, x):
    # Hier definierst du die Vorwärtssequenz
    # torch.autograd generiert dynamisch einen Graphen bei jedem Durchlauf

    x = self.fc1(x)
    x = self.act1(x)
    x = self.fc2(x)
    return x

Dabei wird eine Netzwerkklasse von der abstrakten Klasse nn.Module abgeleitet. Die Methoden __init__() und forward() müssen dabei definiert werden. In __init__() sollten alle benötigten Elemente, aus denen das gesamte Netzwerk zusammengebaut ist, instanziiert und initiiert werden. In unserem Falle generieren wir drei Elemente:

  1. fc1 – mit nn.Linear(input_dim, hidden_dim) wird ein Fully Connected Layer mit einer Eingabedimension von input_dim und einer Ausgabedimension von hidden_dim erzeugt
  2. act1 – eine Tanh-Aktivierungsfunktion
  3. fc2 – ein weiterer Fully Connected Layer mit einer Eingabedimension von hidden_dim und einer Ausgabedimension von output_dim.

Die Reihenfolge in __init()__ ist im Grunde egal, aber aus stilistischen Gründen sollte man sie möglichst in der Reihenfolge generieren, in der sie in der Methode forward() aufgerufen werden. Entscheidend für die Prozessierung ist die Reihenfolge in der forward()-Methode, in der man die Sequenzen des Vorwärtsdurchlaufs festlegt. An dieser Stelle kann man sogar beliebige bedingte Abfragen und Verzweigungen einbauen, da bei jedem Lauf ein Berechnungsgraph dynamisch generiert wird (Listing 5). Das ist nützlich, wenn man z. B. mit variierenden Batchgrößen arbeiten oder mit komplexen Verzweigungen experimentieren möchte. Insbesondere die Behandlung von Sequenzen unterschiedlicher Länge als Eingabe, wie es häufig bei vielen NLP-Problemen vorkommt, ist mit dynamischen Graphen wesentlich unkomplizierter zu realisieren als mit statischen.

class Net(nn.Module): 
  ...
  def forward(self, x, a, b):

    x = self.fc1(x)

    # Bedingte Anwendung der Aktivierungsfunktion
    if a > b:
      x = self.act1(x)

    x = self.fc2(x)
    return x

„autograd“ und dynamische Graphen

PyTorch benutzt das Paket torch.autograd, um bei jedem Vorwärtslauf dynamisch einen gerichteten azyklischen Graphen (DAG) zu generieren. Im Gegensatz dazu wird bei einer statischen Generierung der Graph am Anfang einmal komplett konstruiert und danach nicht mehr geändert. Der statische Graph wird bei jeder Iteration mit den neuen Daten gefüllt und ausgeführt. Dynamische Graphen haben einige Vorteile bzgl. der Flexibilität, wie schon im vorigen Abschnitt beschrieben wurde. Die Nachteile liegen im Bereich der Möglichkeiten der Optimierung, des verteilten (parallelen) Trainierens und des Deployments der Modelle.

ML Conference 2019

Workshop: Machine Learning 101++ using Python

mit Dr. Pieter Buteneers (Chatlayer.ai)

Honey Bee Conservation using Deep Learning

mit Thiago da Silva Alves, Jean Metz (JArchitects)

Python Summit 2019

Daten analysieren und transformieren mit Python

mit Doniyor Jurabayev (Freelancer)

Advanced Flow Control

mit Oz Tiram (noris network AG)

Abbildung 2: Beispiel eines DAG generiert mit „torch.autograd“

Abbildung 2: Beispiel eines DAG generiert mit „torch.autograd“

Durch die Definition des Vorwärtspfades generiert torch.autograd einen Graphen, dessen Knoten die Tensoren und dessen Kanten die elementaren Tensoroperationen repräsentieren. Mit Hilfe dieser Informationen können die Gradienten aller Tensoren automatisch zur Laufzeit ermittelt und somit Backpropagation effizient durchgeführt werden. Ein Beispielgraph ist in Abbildung 2 dargestellt.

Debugger

Der größte Vorteil der Implementierung dynamischer Graphen gegenüber statischen Graphen liegt in der Möglichkeit des Debuggings. Innerhalb der forward()-Methode kann man beliebige Printouts durchführen oder Breakpoints setzen, die z. B. mit Hilfe des Standarddebuggers pdb analysiert werden können. Bei statischen Graphen ist das nicht ohne Weiteres möglich, da man zur Laufzeit keinen direkten Zugriff auf die Objekte des Netzwerks besitzt.

Training

Das Paket torchvision enthält viele nützliche Werkzeuge, vortrainierte Modelle und Datensätze für den Bereich Bildverarbeitung. In Listing 6 wird der Datensatz FashionMNIST geladen. Er besteht aus einem Trainings- und Validierungsdatensatz von 60 000 bzw. 10 000 Icons aus dem Modebereich.

transform = transforms.Compose([transforms.ToTensor()])
# weitere Beispiele für Transformationen:
# transforms.RandomSizedCrop()
# transforms.RandomHorizontalFlip()
# transforms.Normalize()

# Download und Laden des Trainingsdatensatzes (60k)
trainset = datasets.FashionMNIST('./FashionMNIST/', download=True, train=True, transform=transform)  # Objekt der Klasse torch.utils.data.Dataset
trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=4)

# Download und Laden des Validationsdatensatzes (10k)
validset = datasets.FashionMNIST('./FashionMNIST/', download=True, train=False, transform=transform)  # Objekt der Klasse torch.utils.data.Dataset
validloader = DataLoader(validset, batch_size=batch_size, shuffle=True, num_workers=4)
Abbildung 3: Beispiele aus dem „FashionMNIST“-Datensatz

Abbildung 3: Beispiele aus dem „FashionMNIST“-Datensatz

Die Icons sind Graustufenbilder aus 28×28 Pixeln, die in zehn Klassen eingeteilt sind (0-9): 0. T-Shirt, 1. Trouser, 2. Pullover, 3. Dress, 4. Coat, 5. Sandal, 6. Shirt, 7. Sneaker, 8. Bag, 9. Ankle Boot (Abb. 3). Die Klasse Dataset repräsentiert einen Datensatz, den man beliebig partitionieren und auf den man verschiedene Transformationen anwenden kann. In diesem Beispiel werden die NumPy-Arrays in Torch-Tensoren konvertiert. Daneben werden aber noch etliche andere Transformationen zur Augmentierung und Normalisierung der Daten angeboten (z. B. Ausschnitte, Rotationen, Spiegelungen etc.). DataLoader ist eine Iterator-Klasse, die einzelne Batches des Datensatzes generiert und in den Speicher lädt, sodass man große Datensätze nicht vollständig laden muss. Optional kann man auswählen, ob man mehrere Threads starten möchte (num_workers) oder ob der Datensatz vor jeder Epoche neu gemischt werden soll (shuffle).

In Listing 7 generieren wir zunächst eine Instanz unseres Modells und verschieben den kompletten Graphen auf die GPU. PyTorch bietet verschiedene Loss-Funktionen und Optimierungsalgorithmen an. Für ein Multi-Label-Klassifizierungsproblem können beispielsweise als Loss-Funktion CrossEntropyLoss() und Stochastic Gradient Descent (SGD) als Optimierungsalgorithmus gewählt werden. An die Methode SGD() werden die Parameter des Netzwerks übergeben, die optimiert werden sollen. Ein optionaler Parameter ist die Learning Rate (lr).

import torch.optim as optim
# Benutze die GPU falls vorhanden
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Definiere Modell
input_dim = 784
hidden_dim = 100
output_dim = 10
model = Net(input_dim, hidden_dim, output_dim)
model.to(device)  # verschiebe alle Elemente des Graphen auf das aktuelle Device

# Definiere Optimizer-Algorithmus und verknüpfe mit Modellparametern
optimizer = optim.SGD(model.parameters(), lr=0.01)

# Definiere Loss-Funktion: CrossEntropy für Klassifikation
loss_function = nn.CrossEntropyLoss()

Die Funktion train() in Listing 8 führt eine Trainingsiteration durch. Am Anfang werden alle Gradienten des Netzwerkgraphen zurückgesetzt (zero_grad()). Danach wird ein Vorwärtslauf durch den Graphen durchgeführt. Der Loss-Wert wird durch einen Vergleich der Netzwerkausgabe und des Label-Tensors ermittelt. Die Gradienten werden mit backward() berechnet und mit optimizer.step() schließlich die Gewichte des Netzwerks mittels Backpropagation aktualisiert. Eine Variante der Trainingsiteration ist die Validationsiteration valid(), dabei werden alle Backpropagation-Arbeitsschritte ausgelassen.

# Training eines Batch
def train(model, images, label, train=True):
  if train:
    model.zero_grad() # Zurücksetzen der Gradienten

  x_out = model(images)
  loss = loss_function(x_out, label)  # Ermittle Loss-Wert

  if train:
    loss.backward()  # Berechne alle Gradienten
    optimizer.step() # Aktualisiere die Gewichte
  return loss

# Validierung: Nur Vorwärtslauf ohne Backpropagation
def valid(model, images, label):
  return train(model, images, label, train=False)

Die vollständige Iteration über mehrere Epochen ist in Listing 9 aufgeführt. Für die Anwendung des Feed-Forward-Modells Net() müssen die Icon-Tensoren mit den Dimensionen (batch_size, 1, 28, 28) nach (batch_size, 784) transformiert werden. Der Aufruf von train_loop() sollte also mit dem Argument ‚flatten‚ erfolgen:

train_loop(model, trainloader, validloader, 10, 200, 'flatten')
import numpy as np

def train_loop(model, trainloader, validloader=None, num_epochs = 20, print_every = 200, input_mode='flatten', save_checkpoints=False):
  for epoch in range(num_epochs):

    # Trainingsschleife
    train_losses = []
    for i, (images, labels) in enumerate(trainloader):
      images = images.to(device)
      if input_mode == 'flatten':
        images = images.view(images.size(0), -1)  # Flattening des Images
      elif input_mode == 'sequence':
        images = images.view(images.size(0), 28, 28)  # Sequence aus 28 Elementen mit 28 Features

      labels = labels.to(device)
      loss = train(model, images, labels)
      train_losses.append(loss.item())
      if (i+1) % print_every == 0:
        print('Training', epoch+1, i+1, loss.item())

    if validloader is None:
      continue

    # Validationsschleife
    val_losses = []
    for i, (images, labels) in enumerate(validloader):
      images = images.to(device)
      if input_mode == 'flatten':
        images = images.view(images.size(0), -1)  # Flattening des Images
      elif input_mode == 'sequence':
        images = images.view(images.size(0), 28, 28)  # Sequence aus 28 Elementen mit 28 Features
      labels = labels.to(device)
      loss = valid(model, images, labels)
      val_losses.append(loss.item())
      if (i+1) % print_every == 0:
        print('Validation', epoch+1, i+1, loss.item())

    print('--- Epoch, Train-Loss, Valid-Loss:', epoch, np.mean(train_losses), np.mean(val_losses))
        
    if save_checkpoints:
      model_filename = 'checkpoint_ep'+str(epoch+1)+'.pth'
      torch.save({
        'model_state_dict': model.state_dict(),
        'optimizer_state_dict': optimizer.state_dict(),
      },  model_filename)

Speichern und Laden trainierter Gewichte

Um die Modelle später zur Inferenz in einer Anwendung nutzen zu können, ist es möglich, die trainierten Gewichte in Form serialisierter Python-Dictionary-Objekte zu speichern. Dafür wird das Python-Paket pickle benutzt. Möchte man das Modell später weitertrainieren, sollte man auch den letzten Status des Optimizers speichern. In Listing 9 werden Modellgewichte und aktueller Status des Optimizers nach jeder Epoche gespeichert. Listing  0 zeigt, wie eine dieser pickle-Dateien geladen werden kann.

model = Net(input_dim, hidden_dim, output_dim)

checkpoint = torch.load('checkpoint_ep2.pth')
model.load_state_dict(checkpoint['model_state_dict'])

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])

Netzwerkmodule

PyTorch bietet noch viele weitere vordefinierte Module zum Konstruieren von Convolutional Neural Networks (CNN), Recurrent Neural Networks (RNN) oder noch komplexeren Architekturen wie Encoder-Decoder-Systemen. Das Net()-Modell könnte z. B. mit einem Dropout Layer erweitert werden (Listing 11).

class Net(nn.Module):

  def __init__(self, input_dim, hidden_dim, output_dim):
    super(Net, self).__init__()

    self.fc1 = nn.Linear(input_dim, hidden_dim)
    self.dropout = nn.Dropout(0.5) # Dropout Layer mit Wahrscheinlichkeit 50 Prozent
    self.act1 = nn.Tanh()
    self.fc2 = nn.Linear(hidden_dim, output_dim)

  def forward(self, x):        
    x = self.fc1(x)
    x = self.dropout(x) # Dropout nach dem ersten FC Layer
    x = self.act1(x)
    x = self.fc2(x)

    return x

Listing 12 zeigt ein Beispiel für ein CNN aus zwei Convolutional Layers mit Batch Normalization, jeweils einer ReLU-Aktivierung und einem Max Pooling Layer. Der Trainingsaufruf könnte dann so aussehen:

model = CNN(10).to(device)
optimizer = optim.SGD(model.parameters(), lr=0.01)
train_loop(model, trainloader, validloader, 10, 200, None)
class CNN(nn.Module):

  def __init__(self, num_classes=10):
    super(CNN, self).__init__()

    self.layer1 = nn.Sequential(
      nn.Conv2d(1, 16, kernel_size=5, padding=2),
      nn.ReLU(),
      nn.MaxPool2d(2)
    )
    self.layer2 = nn.Sequential(
      nn.Conv2d(16, 32, kernel_size=5, padding=2),
      nn.ReLU(), 
      nn.MaxPool2d(2))
    self.fc = nn.Linear(7*7*32, 10)

  def forward(self, x):
    out = self.layer1(x)
    out = self.layer2(out)
    out = out.view(out.size(0), -1)  # Flattening für FC-Input
    out = self.fc(out)
    return out

Ein Beispiel für ein LSTM-Netzwerk, das mit dem Adam Optimizer optimiert wird, wird in Listing 13 gezeigt. Dabei werden die Pixel der Bilder des FashionMNIST-Datensatzes als Sequenzen aus 28 Elementen mit jeweils 28 Features interpretiert und entsprechend vorprozessiert.

# Recurrent Neural Network
class RNN(nn.Module):
  def __init__(self, input_size, hidden_size, num_layers, num_classes):
    super(RNN, self).__init__()
    self.hidden_size = hidden_size
    self.num_layers = num_layers
    self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
    self.fc = nn.Linear(hidden_size, num_classes)

  def forward(self, x):
    # Initialisiere Hidden und Cell States
    h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
    c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)

    out, _ = self.lstm(x, (h0, c0))

    out = self.fc(out[:, -1, :]) # letzter Hidden State
    return out

sequence_length = 28
input_size = 28
hidden_size = 128
num_layers = 1

model = LSTM(input_size, hidden_size, num_layers, output_dim).to(device)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
train_loop(model, trainloader, validloader, 10, 200, 'sequence')

Das Paket torchvision ermöglicht auch das Laden bekannter Architekturen oder sogar ganzer vortrainierter Modelle, die man als Grundlage für die eigenen Anwendungen oder zum Transfer Learning benutzen kann. Ein vortrainiertes VGG-Modell mit 19 Layers kann beispielsweise folgendermaßen geladen werden:

from torchvision import models
vgg = models.vgg19(pretrained=True)

Deployment

Die Integration von PyTorch-Modellen in Anwendungen war bisher immer eine Herausforderung, da die Möglichkeiten, die trainierten Modelle in Produktivsystemen einzusetzen, relativ eingeschränkt waren. Eine häufig benutzte Methode ist die Entwicklung eines REST Services, z. B. mit flask. Dieser REST Service kann lokal oder innerhalb eines Docker Images in der Cloud laufen. Die drei großen Anbieter von Cloudservices (AWS, GCE, Azure) bieten inzwischen aber auch vordefinierte Konfigurationen mit PyTorch an.

Eine Alternative ist die Konvertierung in das ONNX-Format. ONNX (Open Neural Network Exchange Format) ist ein offenes Format zum Austausch von Neural-Network-Modellen, das z. B. auch von MxNet und Caffe unterstützt wird. Das sind Machine Learning Frameworks, die von Amazon und Facebook produktiv eingesetzt werden. Listing 14 zeigt ein Beispiel, wie man ein trainiertes Modell im ONNX-Format exportiert.

model = CNN(output_dim)

# Beliebiger Eingabetensor für das Tracing
dummy_input = torch.randn(1, 1, 28, 28)

# Das Konvertieren nach ONNX erfolgt über Tracing einer Dummyeingabe
torch.onnx.export(model, dummy_input, "onnx_model_name.onnx")

TorchScript und C++

Seit der Version 1.0 bietet PyTorch auch die Möglichkeit, Modelle im LLVM-IR-Format zu speichern. Solche können völlig unabhängig von Python ausgeführt werden. Das Werkzeug dafür ist TorchScript, das einen eigenen JIT-Compiler und spezielle Optimierungen (statische Datentypen, optimierte Implementierung der Tensoroperationen) implementiert.

# Beliebiger Eingabetensor für das Tracing
dummy_input = torch.randn(1, 1, 28, 28)

traced_model = torch.jit.trace(model, dummy_input)
traced_model.save('jit_traced_model.pth')

Das TorchScript-Format kann man auf zwei Arten erzeugen. Zum einen über das Tracing eines existierenden PyTorch-Modells (Listing 15) oder durch die direkte Implementierung als Script Module (Listing 16). Im Script-Modus wird ein optimierter statischer Graph generiert. Dieser bietet nicht nur die angesprochenen Vorteile für das Deployment, sondern könnte z. B. auch für das verteilte Trainieren eingesetzt werden.

from torch.jit import trace

class Net_script(torch.jit.ScriptModule):

  def __init__(self, input_dim, hidden_dim, output_dim):
    super(Net_script, self).__init__()

    self.fc1 = trace(nn.Linear(input_dim, hidden_dim), torch.randn(1, 784)) 
    self.fc2 = trace(nn.Linear(hidden_dim, output_dim), torch.randn(1, 100))

  @torch.jit.script_method
  def forward(self, x):        
    x = self.fc1(x)
    x = torch.tanh(x)
    x = self.fc2(x)
    
    return x

model = Net_script(input_dim, hidden_dim, output_dim)

model.save('jit_model.pth')

Das TorchScript-Modell kann nun mit Hilfe der C++-Frontend-Library (LibTorch) in jede C++-Anwendung eingebunden werden. Das ermöglicht die performante Ausführung der Inferenz, völlig unabhängig von Python in vielen verschiedenen Produktionsumgebungen wie z. B. auf mobilen Geräten.

Fazit

Mit PyTorch kann man sehr effizient und elegant sowohl einfache als auch sehr komplexe Neuronale Netzwerke entwickeln und trainieren. Durch die Implementierung dynamischer Graphen ist das Experimentieren mit sehr flexiblen Architekturen und der Einsatz von Standarddebuggingtools kein Problem. Die nahtlose Anbindung an Python ermöglicht eine sehr schnelle Entwicklung von Prototypen. Diese Merkmale machen PyTorch zurzeit zum beliebtesten Framework für Forscher und experimentierfreudige Entwickler. Die neueste Version bietet auch die Möglichkeit, PyTorch-Modelle in C++-Anwendungen einzubinden und somit eine bessere Integration in Produktivsysteme zu erreichen. Das ist ein erheblicher Fortschritt zu den früheren Versionen. In dieser Kategorie haben aber andere Frameworks, darunter vor allem TensorFlow, noch immer einen deutlichen Vorsprung. Mit TF Extended (TFX), TF Serving und TF Lite bietet das Framework von Google wesentlich anwendungsfreundlichere und robustere Werkzeuge zur Erzeugung von produktionsreifen Modellen. Es wird spannend, welche neuen Entwicklung auf diesem Gebiet wir von PyTorch noch zu sehen bekommen werden.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -