So erstellen Sie ein neuronales Netz zum Übersetzen der Gebärdensprache ins Englische

Der Autor hat Code.org ausgewählt, um im Rahmen des Programms Write for DOnations eine Spende zu erhalten.

Einführung

Computer Vision (deutsch: computerbasiertes Sehen) ist ein Teilbereich der Informatik, mit dem ein höherrangiges Verstehen von Bildern und Videos ermöglicht werden soll. Damit werden Technologien wie lustige Video-Chat-Filter, die Gesichtserkennung Ihres Mobilgeräts und selbstfahrende Autos unterstützt.

In diesem Tutorial nutzen Sie Computer Vision, um einen Übersetzer für die amerikanische Gebärdensprache zu entwickeln, der mithilfe Ihrer Webcam arbeitet. Während des Tutorial werden Sie OpenCV, eine Computer-Vision-Bibliothek, PyTorch zum Einrichten eines tiefen neuronalen Netzes und onnx zum Exportieren Ihres neuronalen Netzes verwenden. Zudem werden Sie eine Computer-Vision-Anwendung erstellen und dabei folgende Konzepte anwenden:

  • Sie verwenden dieselbe dreistufige Methode, die auch im Tutorial How To Apply Computer Vision to Build an Emotion-Based Dog Filter (So wenden Sie Computer Vision beim Erstellen eines emotionsbasierten Hundefilters an) genutzt wird: Vorverarbeitung eines Datensatzes, Trainieren eines Modells und Bewertung des Modells.
  • Außerdem werden Sie jeden dieser einzelnen Schritte erweitern: Sie nutzen Data Augmentation (Datenanreicherung) für den Umgang mit gedrehten oder nicht zentrierten Händen, Sie ändern die Learning Rate Schedules (Zeitpläne für die Lernrate), um die Modellgenauigkeit zu verbessern, und Sie exportieren Modelle für eine höhere Inferenzgeschwindigkeit.
  • Überdies werden Sie auch verwandte Konzepte im maschinellen Lernen erkunden.

Am Ende dieses Tutorials verfügen Sie über einen Übersetzer für die amerikanische Gebärdensprache sowie über ein umfassendes Know-how über das Deep Learning. Sie können auch auf den kompletten Quellcode für dieses Projekt zugreifen.

Voraussetzungen

Um dieses Tutorial zu absolvieren, benötigen Sie Folgendes:

  • Eine lokale Entwicklungsumgebung für Python 3 mit mindestens 1 GB RAM. Unter How to Install and Set Up a Local Programming Environment for Python 3 (Installieren und Einrichten einer lokalen Programmierumgebung für Python 3) finden Sie Informationen darüber, wie Sie die benötigten Konfigurationen vornehmen.
  • Eine funktionierende Webcam zur Nutzung der Bilderkennung in Echtzeit.
  • (Empfohlen) Build an Emotion-Based Dog Filter (Erstellen eines emotionsbasierten Hundes); dieses Tutorial wird zwar nicht explizit verwendet, aber es wird dasselbe Wissen vermittelt und darauf aufgebaut.

Schritt 1 – Erstellen des Projekts und Installieren von Abhängigkeiten

Wir wollen einen Arbeitsbereich für dieses Projekt erstellen und die Abhängigkeiten installieren, die wir benötigen.

Beginnen Sie mit den Linux-Distributionen, indem Sie Ihre Systempaketverwaltung vorbereiten und das Python3 virtualenv-Paket installieren. Verwenden Sie Folgendes:

  • apt-get update
  • apt-get upgrade
  • apt-get install python3-venv

Wir nennen unseren Arbeitsbereich SignLanguage (Gebärdensprache):

  • mkdir ~/SignLanguage

Navigieren Sie zum Verzeichnis SignLanguage:

  • cd ~/SignLanguage

Erstellen Sie dann eine neue virtuelle Umgebung für das Projekt:

  • python3 -m venv signlanguage

Aktivieren Sie Ihre Umgebung:

  • source signlanguage/bin/activate

Installieren Sie anschließend PyTorch, ein Deep-Learning-Framework für Python, das wir in diesem Tutorial verwenden werden.

Auf macOS installieren Sie Pytorch mit dem folgenden Befehl:

  • python -m pip install torch==1.2.0 torchvision==0.4.0

Auf Linux und Windows verwenden Sie die folgenden Befehle für einen reinen CPU-Build:

  • pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
  • pip install torchvision

Installieren Sie nun vorgefertigte Binärdateien für OpenCV, numpy und onnx, die als Bibliotheken für Computer Vision, lineare Algebra, den Export des KI-Modells und die Ausführung des KI-Modells dienen. OpenCV bietet Hilfsfunktionen wie Bilddrehung und numpy bietet Hilfsfunktionen für lineare Algebra an, z. B. eine Matrixinversion:

  • python -m pip install opencv-python==3.4.3.18 numpy==1.14.5 onnx==1.6.0 onnxruntime==1.0.0

Auf Linux-Distributionen müssen Sie libSM.so installieren:

  • apt-get install libsm6 libxext6 libxrender-dev

Wenn die Abhängigkeiten installiert wurden, erstellen wir die erste Version unseres Gebärdensprachenübersetzers: einen Gebärdensprachen-Classifier.

Schritt 2 — Vorbereiten des Datensatzes für die Klassifikation der Gebärdensprache

In diesen nächsten drei Abschnitten erstellen Sie einen Gebärdensprachen-Classifier mithilfe eines neuronalen Netzes. Ihr Ziel besteht darin, ein Modell zu erstellen, das ein Bild einer Hand als Eingabe annimmt und einen Buchstaben ausgibt.

Für das Erstellen eines Klassifizierungsmodells für das maschinelle Lernen sind Sie die folgenden drei Schritte erforderlich:

  1. Vorbearbeiten der Daten: Wenden Sie one-hot encoding (One-Hot-Kodierung) auf Ihre Labels an und umschließen Sie Ihre Daten mit PyTorch-Tensoren. Trainieren Sie Ihr Modell auf angereicherten Daten, um es auf „unübliche“ Eingabedaten vorzubereiten, z. B. eine außermittige oder eine gedrehte Hand.
  2. Legen Sie das Modell fest und trainieren Sie es: Richten Sie ein neuronales Netz mit PyTorch ein. Legen Sie die Hyperparameter für das Training fest (z. B. wie lange das Training dauern soll) und führen Sie ein stochastisches Gradientenverfahren durch. Variieren Sie zudem einen bestimmten Hyperparameter für das Training: Learning Rate Schedule. Dadurch wird die Modellgenauigkeit erhöht.
  3. Führen Sie eine Vorhersage mit dem Modell aus: Bewerten Sie das neuronale Netz anhand Ihrer Validierungsdaten, um dessen Genauigkeit zu erfassen. Exportieren Sie dann das Modell in ein Format namens ONNX, um höhere Inferenzgeschwindigkeiten zu erreichen.

In diesem Abschnitt des Tutorials führen Sie Schritt 1 von 3 durch. Sie werden die Daten herunterladen, ein Dataset-Objekt erstellen, das wiederholt auf Ihre Daten angewendet wird, und abschließend noch die Data Augmentation anwenden. Am Ende dieses Schritts verfügen Sie über ein Programm, mit dem Sie auf Bilder und Labels in Ihrem Datensatz zugreifen können, um Ihr Modell zu füttern.

Laden Sie zuerst den Datensatz in Ihr aktuelles Arbeitsverzeichnis herunter:

Anmerkung: Auf makOS ist wget standardmäßig nicht verfügbar. Installieren Sie dazu HomeBrew, indem Sie diesem DigitalOcean Tutorial folgen. Führen Sie dann brew install wget aus.

  • wget https://assets.digitalocean.com/articles/signlanguage_data/sign-language-mnist.tar.gz

Entzippen Sie die Zip-Datei, die das Verzeichnis data/ enthält:

  • tar -xzf sign-language-mnist.tar.gz

Erstellen Sie eine neue Datei namens step_2_dataset.py:

  • nano step_2_dataset.py

Importieren Sie wie zuvor die erforderlichen Hilfsfunktionen und erstellen Sie die Klasse, die Ihre Daten enthalten soll. Erstellen Sie die Trainings- und Testdaten zum Zwecke der Datenverarbeitung. Sie implementieren die Dataset-Schnittstelle von PyTorch, damit Sie die integrierte Daten-Pipeline von PyTorch laden und für den Datensatz Ihrer Gebärdensprachenklassifikation verwenden können:

step_2_dataset.py

from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import numpy as np import torch  import csv   class SignLanguageMNIST(Dataset):     """Sign Language classification dataset.      Utility for loading Sign Language dataset into PyTorch. Dataset posted on     Kaggle in 2017, by an unnamed author with username `tecperson`:     https://www.kaggle.com/datamunge/sign-language-mnist      Each sample is 1 x 1 x 28 x 28, and each label is a scalar.     """     pass 

Löschen Sie den Platzhalter pass in der Klasse SignLanguageMNIST. Fügen Sie an seiner Stelle eine Methode hinzu, um ein Label Mapping zu generieren:

step_2_dataset.py

    @staticmethod     def get_label_mapping():         """         We map all labels to [0, 23]. This mapping from dataset labels [0, 23]         to letter indices [0, 25] is returned below.         """         mapping = list(range(25))         mapping.pop(9)         return mapping 

Die Labels reichen von 0 bis 25. Die Buchstaben J (9) und Z (25) sind jedoch ausgeschlossen. Das bedeutet, dass es nur 24 gültige Label-Werte gibt. Damit der Satz aller von 0 ausgehenden Label-Werte zusammenhängend ist, werden alle Labels [0, 23] zugeordnet. Dieses Mapping von den Datensätzen [0, 23] bis zu den Buchstabenindizes [0, 25] wird mithilfe der Methode get_label_mapping herbeigeführt.

Als Nächstes fügen Sie eine Methode hinzu, um Labels und Beispielproben aus einer CSV-Datei zu extrahieren. Im Folgenden wird davon ausgegangen, dass jede Zeile mit dem label startet, auf das 784-Pixelwerte folgen. Diese 784 Pixelwerte repräsentieren ein 28x28 Bild:

step_2_dataset.py

    @staticmethod     def read_label_samples_from_csv(path: str):         """         Assumes first column in CSV is the label and subsequent 28^2 values         are image pixel values 0-255.         """         mapping = SignLanguageMNIST.get_label_mapping()         labels, samples = [], []         with open(path) as f:             _ = next(f)  # skip header             for line in csv.reader(f):                 label = int(line[0])                 labels.append(mapping.index(label))                 samples.append(list(map(int, line[1:])))         return labels, samples 

Eine Erklärung darüber, wie diese 784 Werte ein Bild repräsentieren, finden Sie unter Build an Emotion-Based Dog Filter, Step 4 (Erstellen eines emotionsbasierten Hundes, Schritt 4).

Beachten Sie, dass jede Zeile im csv.reader-Iterable eine Liste von Zeichenfolgen ist; die Aufrufe int und map(int, ...) wandeln alle Zeichenfolgen in Ganzzahlen um. Fügen Sie direkt unter unserer statischen Methode eine Funktion hinzu, die unseren Datenbehälter initialisieren wird:

step_2_dataset.py

    def __init__(self,             path: str="data/sign_mnist_train.csv",             mean: List[float]=[0.485],             std: List[float]=[0.229]):         """         Args:             path: Path to `.csv` file containing `label`, `pixel0`, `pixel1`...         """         labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)         self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))         self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))          self._mean = mean         self._std = std 

Diese Funktion startet mit dem Laden von Samples und Labels. Dann umschließt sie die Daten mit NumPy-Arrays. Die mittlere und Standardabweichung wird kurz im folgenden Abschnitt __getitem__ erklärt.

Fügen Sie direkt nach der Funktion __init__ eine Funktion __len__ hinzu. Diese Methode wird für das Dataset benötigt, um zu ermitteln, wann das Iterieren über die Daten beendet werden muss.

step_2_dataset.py

...     def __len__(self):         return len(self._labels) 

Fügen Sie abschließend die Methode __getitem__ hinzu, die ein Wörterbuch zurückgibt, das das Sample und das Label enthält:

step_2_dataset.py

    def __getitem__(self, idx):         transform = transforms.Compose([             transforms.ToPILImage(),             transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),             transforms.ToTensor(),             transforms.Normalize(mean=self._mean, std=self._std)])          return {             'image': transform(self._samples[idx]).float(),             'label': torch.from_numpy(self._labels[idx]).float()         } 

Sie verwenden eine Technik namens Data Augmentation, bei der Samples während des Trainings gestört werden, um die Robustheit des Modells gegenüber diesen Störungen zu erhöhen. Hierfür wird insbesondere das Bild über RandomResizedCrop in variierenden Werten und an verschiedenen Stellen eingezoomt. Beachten Sie, dass sich das Einzoomen nicht auf die finale Gebärdensprachenklasse auswirken sollte. So wird das Label nicht transformiert. Sie normalisieren die Eingaben zusätzlich, damit die Bildwerte wie erwartet auf den Bereich [0, 1] neu skaliert werden anstatt auf [0, 255]; verwenden Sie bei der Normalisierung den Datensatz _mean und _std, um dies zu erreichen.

Ihre abgeschlossene Klasse SignLanguageMNIST sieht wie folgt aus:

step_2_dataset.py

from torch.utils.data import Dataset from torch.autograd import Variable import torchvision.transforms as transforms import torch.nn as nn import numpy as np import torch  from typing import List  import csv   class SignLanguageMNIST(Dataset):     """Sign Language classification dataset.      Utility for loading Sign Language dataset into PyTorch. Dataset posted on     Kaggle in 2017, by an unnamed author with username `tecperson`:     https://www.kaggle.com/datamunge/sign-language-mnist      Each sample is 1 x 1 x 28 x 28, and each label is a scalar.     """      @staticmethod     def get_label_mapping():         """         We map all labels to [0, 23]. This mapping from dataset labels [0, 23]         to letter indices [0, 25] is returned below.         """         mapping = list(range(25))         mapping.pop(9)         return mapping      @staticmethod     def read_label_samples_from_csv(path: str):         """         Assumes first column in CSV is the label and subsequent 28^2 values         are image pixel values 0-255.         """         mapping = SignLanguageMNIST.get_label_mapping()         labels, samples = [], []         with open(path) as f:             _ = next(f)  # skip header             for line in csv.reader(f):                 label = int(line[0])                 labels.append(mapping.index(label))                 samples.append(list(map(int, line[1:])))         return labels, samples      def __init__(self,             path: str="data/sign_mnist_train.csv",             mean: List[float]=[0.485],             std: List[float]=[0.229]):         """         Args:             path: Path to `.csv` file containing `label`, `pixel0`, `pixel1`...         """         labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)         self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))         self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))          self._mean = mean         self._std = std      def __len__(self):         return len(self._labels)      def __getitem__(self, idx):         transform = transforms.Compose([             transforms.ToPILImage(),             transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),             transforms.ToTensor(),             transforms.Normalize(mean=self._mean, std=self._std)])          return {             'image': transform(self._samples[idx]).float(),             'label': torch.from_numpy(self._labels[idx]).float()         } 

Wie zuvor überprüfen Sie unsere Datensatz-Hilfsfunktionen, indem Sie den Datensatz SignLanguageMNIST laden. Fügen Sie am Ende Ihrer Datei hinter der Klasse SignLanguageMNIST den folgenden Code hinzu:

step_2_dataset.py

def get_train_test_loaders(batch_size=32):     trainset = SignLanguageMNIST('data/sign_mnist_train.csv')     trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)      testset = SignLanguageMNIST('data/sign_mnist_test.csv')     testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)     return trainloader, testloader 

Dieser Code initialisiert den Datensatz mithilfe der Klasse SignLanguageMNIST. Für die Trainings- und Validierungssätze wird dann der Datensatz mit einem DataLoader umschlossen. Dadurch wird der Datensatz für den späteren Gebrauch in ein Iterable umgewandelt.

Nun überprüfen Sie, ob die Datensatz-Hilfsfunktionen funktionieren. Erstellen Sie einen Sample-Datensatz-Loader mithilfe von DataLoader und drucken Sie das erste Element dieses Loaders aus. Fügen Sie Folgendes am Ende der Datei hinzu:

step_2_dataset.py

if __name__ == '__main__':     loader, _ = get_train_test_loaders(2)     print(next(iter(loader))) 

Sie können überprüfen, ob Ihre Datei mit der Datei step_2_dataset in diesem (repository) übereinstimmt. Beenden Sie Ihren Editor und führen Sie das Skript mit Folgendem aus:

  • python step_2_dataset.py

Dadurch werden folgende Tensoren paarweise ausgegeben. Unsere Datenpipeline gibt zwei Samples und zwei Labels aus. Dies zeigt an, dass unsere Datenpipeline eingerichtet ist und bereit ist, fortzufahren:

Output{'image': tensor([[[[ 0.4337,  0.5022,  0.5707,  ...,  0.9988,  0.9646,  0.9646],           [ 0.4851,  0.5536,  0.6049,  ...,  1.0502,  1.0159,  0.9988],           [ 0.5364,  0.6049,  0.6392,  ...,  1.0844,  1.0844,  1.0673],           ...,           [-0.5253, -0.4739, -0.4054,  ...,  0.9474,  1.2557,  1.2385],           [-0.3369, -0.3369, -0.3369,  ...,  0.0569,  1.3584,  1.3242],           [-0.3712, -0.3369, -0.3198,  ...,  0.5364,  0.5364,  1.4783]]],           [[[ 0.2111,  0.2796,  0.3481,  ...,  0.2453, -0.1314, -0.2342],           [ 0.2624,  0.3309,  0.3652,  ..., -0.3883, -0.0629, -0.4568],           [ 0.3309,  0.3823,  0.4337,  ..., -0.4054, -0.0458, -1.0048],           ...,           [ 1.3242,  1.3584,  1.3927,  ..., -0.4054, -0.4568,  0.0227],           [ 1.3242,  1.3927,  1.4612,  ..., -0.1657, -0.6281, -0.0287],           [ 1.3242,  1.3927,  1.4440,  ..., -0.4397, -0.6452, -0.2856]]]]), 'label': tensor([[24.],         [11.]])} 

Sie haben nun sichergestellt, dass Ihre Datenpipeline funktioniert. Damit ist der erste Schritt – die Verarbeitung Ihrer Daten – abgeschlossen. Nun beinhaltet sie Data Augmentation für eine erhöhte Modellstabilität. Als Nächstes definieren Sie das neuronale Netz und den Optimierer.

Schritt 3 – Erstellen und Trainieren des Gebärdensprachen-Classifiers mittels Deep Learning

Mit einer funktionierenden Datenpipeline definieren Sie nun ein Modell und trainieren es anhand der Daten. Insbesondere erstellen Sie ein neuronales Netz mit sechs Schichten, definieren einen Verlust, einen Optimierer und optimieren abschließend die Verlustfunktion für die Voraussagen mit Ihrem neuronalen Netz. Am Ende dieses Schritts verfügen Sie über einen funktionierenden Gebärdensprachen-Klassifizierer.

Erstellen Sie eine neue Datei namens step_3_train.py:

  • nano step_3_train.py

Importieren Sie die erforderlichen Dienstprogramme:

step_3_train.py

from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch  from step_2_dataset import get_train_test_loaders 

Definieren Sie ein neuronales PyTorch-Netz, das drei Convolutional Layers (faltende Schichten) enthält, gefolgt von drei vollständig zusammenhängenden Schichten. Fügen Sie dies am Ende Ihres bestehenden Skripts hinzu:

step_3_train.py

class Net(nn.Module):     def __init__(self):         super(Net, self).__init__()         self.conv1 = nn.Conv2d(1, 6, 3)         self.pool = nn.MaxPool2d(2, 2)         self.conv2 = nn.Conv2d(6, 6, 3)         self.conv3 = nn.Conv2d(6, 16, 3)         self.fc1 = nn.Linear(16 * 5 * 5, 120)         self.fc2 = nn.Linear(120, 48)         self.fc3 = nn.Linear(48, 24)      def forward(self, x):         x = F.relu(self.conv1(x))         x = self.pool(F.relu(self.conv2(x)))         x = self.pool(F.relu(self.conv3(x)))         x = x.view(-1, 16 * 5 * 5)         x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)         return x 

Initialisieren Sie nun das neuronale Netz, definieren Sie eine Verlustfunktion und legen Sie Hyperparameter zur Optimierung fest, indem Sie am Ende des Skripts den folgenden Code hinzufügen:

step_3_train.py

def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) 

Schließlich trainieren Sie es für zwei Epochen:

step_3_train.py

def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)      trainloader, _ = get_train_test_loaders()     for epoch in range(2):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)     torch.save(net.state_dict(), "checkpoint.pth") 

Sie definieren eine Epoche, die eine Iteration des Trainings ist, bei der jedes Trainings-Sample genau einmal verwendet wurde. Am Ende der Hauptfunktion werden die Modellparameter in einer Datei namens „checkpoint.pth“ gespeichert.

Fügen Sie am Ende Ihres Skripts den folgenden Code hinzu, um Bild und Label aus dem Datensatz-Loader zu extrahieren und dann jede mit einer PyTorch Variable zu umschließen:

step_3_train.py

def train(net, criterion, optimizer, trainloader, epoch):     running_loss = 0.0     for i, data in enumerate(trainloader, 0):         inputs = Variable(data['image'].float())         labels = Variable(data['label'].long())         optimizer.zero_grad()          # forward + backward + optimize         outputs = net(inputs)         loss = criterion(outputs, labels[:, 0])         loss.backward()         optimizer.step()          # print statistics         running_loss += loss.item()         if i % 100 == 0:             print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1))) 

Dieser Code führt auch die Vorwärtsrechnung und dann die Fehlerrückführung über den Verlust und das neuronale Netz aus.

Fügen Sie am Ende Ihrer Datei Folgendes hinzu, um die Funktion main aufzurufen:

step_3_train.py

if __name__ == '__main__':     main() 

Prüfen Sie nochmals, ob Ihre Datei dem Folgenden entspricht:

step_3_train.py

from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch  from step_2_dataset import get_train_test_loaders   class Net(nn.Module):     def __init__(self):         super(Net, self).__init__()         self.conv1 = nn.Conv2d(1, 6, 3)         self.pool = nn.MaxPool2d(2, 2)         self.conv2 = nn.Conv2d(6, 6, 3)         self.conv3 = nn.Conv2d(6, 16, 3)         self.fc1 = nn.Linear(16 * 5 * 5, 120)         self.fc2 = nn.Linear(120, 48)         self.fc3 = nn.Linear(48, 25)      def forward(self, x):         x = F.relu(self.conv1(x))         x = self.pool(F.relu(self.conv2(x)))         x = self.pool(F.relu(self.conv3(x)))         x = x.view(-1, 16 * 5 * 5)         x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)         return x   def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)      trainloader, _ = get_train_test_loaders()     for epoch in range(2):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)     torch.save(net.state_dict(), "checkpoint.pth")   def train(net, criterion, optimizer, trainloader, epoch):     running_loss = 0.0     for i, data in enumerate(trainloader, 0):         inputs = Variable(data['image'].float())         labels = Variable(data['label'].long())         optimizer.zero_grad()          # forward + backward + optimize         outputs = net(inputs)         loss = criterion(outputs, labels[:, 0])         loss.backward()         optimizer.step()          # print statistics         running_loss += loss.item()         if i % 100 == 0:             print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1)))   if __name__ == '__main__':     main() 

Speichern und schließen Sie sie. Starten Sie dann unser Proof-of-Concept-Training, indem Sie Folgendes ausführen:

  • python step_3_train.py

Während das neuronale Netz trainiert wird, sehen Sie ein Ergebnis, das dem Folgenden ähnelt:

Output[0,     0] loss: 3.208171 [0,   100] loss: 3.211070 [0,   200] loss: 3.192235 [0,   300] loss: 2.943867 [0,   400] loss: 2.569440 [0,   500] loss: 2.243283 [0,   600] loss: 1.986425 [0,   700] loss: 1.768090 [0,   800] loss: 1.587308 [1,     0] loss: 0.254097 [1,   100] loss: 0.208116 [1,   200] loss: 0.196270 [1,   300] loss: 0.183676 [1,   400] loss: 0.169824 [1,   500] loss: 0.157704 [1,   600] loss: 0.151408 [1,   700] loss: 0.136470 [1,   800] loss: 0.123326 

Um die Verluste niedrig zu halten, können Sie die Anzahl der Epochen auf 5, 10 oder sogar 20 erhöhen. Nach einer bestimmten Trainingszeit wird sich der Netzverlust trotz einer Steigerung der Trainingszeit nicht mehr verringern. Geben Sie einen Learning Rate Schedule vor, der die Lernrate im Laufe der Zeit verringert, um das Problem zu umgehen, das mit der zunehmenden Trainingszeit einhergeht. Wenn Sie verstehen wollen, warum das funktioniert, sehen Sie sich die Visualisierung von Distill an unter „Why Momentum Really Works“ („Warum das Momentum wirklich funktioniert“).

Ändern Sie Ihre Funktion main mit den folgenden zwei Zeilen ab, definieren Sie einen Scheduler und rufen Sie scheduler.step auf. Ändern Sie außerdem die Anzahl der Epochen in 12:

step_3_train.py

def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)     scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)      trainloader, _ = get_train_test_loaders()     for epoch in range(12):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)         scheduler.step()     torch.save(net.state_dict(), "checkpoint.pth") 

Überprüfen Sie, ob Ihre Datei mit der Datei aus Schritt 3 in diesem Repository übereinstimmt. Die Ausführung des Trainings dauert etwa 5 Minuten. Ihre Ausgabe wird in etwa wie folgt aussehen:

Output[0,     0] loss: 3.208171 [0,   100] loss: 3.211070 [0,   200] loss: 3.192235 [0,   300] loss: 2.943867 [0,   400] loss: 2.569440 [0,   500] loss: 2.243283 [0,   600] loss: 1.986425 [0,   700] loss: 1.768090 [0,   800] loss: 1.587308 ... [11,     0] loss: 0.000302 [11,   100] loss: 0.007548 [11,   200] loss: 0.009005 [11,   300] loss: 0.008193 [11,   400] loss: 0.007694 [11,   500] loss: 0.008509 [11,   600] loss: 0.008039 [11,   700] loss: 0.007524 [11,   800] loss: 0.007608 

Der erhaltene Verlust beträgt 0,007608, was 3 Größenordnungen kleiner als der anfängliche Verlust von 3,20 ist. Damit wird der zweite Schritt unseres Workflows abgeschlossen, in dem wir das neuronale Netz eingerichtet und trainiert haben. Doch auch ein kleiner Verlust hat eine Bedeutung, wenn auch nur eine sehr kleine. Um die Leistung des Modells in Perspektive zu setzen, werden wir seine Genauigkeit berechnen – der Prozentsatz der Bilder, die das Modell richtig klassifiziert hat.

Schritt 4 – Bewerten des Gebärdensprachen-Classifiers

Sie werden nun Ihren Gebärdensprachen-Klassifizierer bewerten, indem Sie seine Genauigkeit im Validierungssatz berechnen, ein Satz von Bildern, die das Modell während des Trainings nicht gesehen hat. Dadurch erhalten wir ein besseres Bild über die Leistung des Modells als durch den endgültigen Verlustwert. Zudem fügen Sie Dienstprogramme hinzu, um unser trainiertes Modell am Ende des Trainings zu speichern, und laden unser vorab trainiertes Modell, während die Inferenz durchgeführt wird.

Erstellen Sie eine neue Datei namens step_4_evaluate.py.

  • nano step_4_evaluate.py

Importieren Sie die erforderlichen Hilfsfunktionen:

step_4_evaluate.py

from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np  import onnx import onnxruntime as ort  from step_2_dataset import get_train_test_loaders from step_3_train import Net 

Definieren Sie als Nächstes eine Hilfsfunktion, um die Leistung des neuronalen Netzes zu bewerten. Die folgende Funktion vergleicht den vom neuronalen Netz vorhergesagten Buchstaben mit dem tatsächlichen Buchstaben:

step_4_evaluate.py

def evaluate(outputs: Variable, labels: Variable) -> float:     """Evaluate neural network outputs against non-one-hotted labels."""     Y = labels.numpy()     Yhat = np.argmax(outputs, axis=1)     return float(np.sum(Yhat == Y)) 

outputs ist eine Liste von Klassenwahrscheinlichkeiten für jedes Sample. Beispielsweise können Outputs für ein einziges Sample [0,1, 0,3, 0,4,] betragen. labels ist eine Liste von Label-Klassen. Die Label-Klasse kann beispielsweise 3 sein.

Y = ... wandelt die Labels in ein NumPy-Array um. Als Nächstes konvertiert Yhat = np.argmax(...) die Wahrscheinlichkeiten der Klasse outputs in vorausgesagte Klassen. Die Liste der Klassenwahrscheinlichkeiten [0,1, 0,3, 0,4, 0,2] würde die vorausgesagte Klasse 2 ergeben, da der Indexwert 2 von 0,4 der größte Wert ist.

Da Y und Yhat nun Klassen sind, können Sie sie vergleichen. Yhat == Y prüft, ob die vorhergesagte Klasse mit der Label-Klasse übereinstimmt, und np.sum(...) ist ein Trick, der die Anzahl der truth-y-Werte berechnet. Anders ausgedrückt: np.sum gibt die Anzahl der Samples aus, die richtig klassifiziert wurden.

Fügen Sie die zweite Funktion batch_evaluate hinzu, die die erste Funktion evaluate auf alle Bilder anwendet:

step_4_evaluate.py

def batch_evaluate(         net: Net,         dataloader: torch.utils.data.DataLoader) -> float:     """Evaluate neural network in batches, if dataset is too large."""     score = n = 0.0     for batch in dataloader:         n += len(batch['image'])         outputs = net(batch['image'])         if isinstance(outputs, torch.Tensor):             outputs = outputs.detach().numpy()         score += evaluate(outputs, batch['label'][:, 0])     return score / n 

Batch ist eine Gruppe von Bildern, die als ein einzelner Tensor gespeichert werden. Zuerst erhöhen Sie die Gesamtzahl der Bilder, die Sie evaluieren (n) um die Anzahl der Bilder in diesem Batch. Als Nächstes führen Sie in dem neuronalen Netz eine Inferenz mit diesem Batch von Bildern aus, outputs = net(...). Die Typenprüfung if isinstance(...) konvertiert die Ergebnisse in einen NumPy-Array bei Bedarf. Schließlich verwenden Sie evaluate, um die Anzahl der richtig klassifizierten Samples zu berechnen. Am Ende der Funktion berechnen Sie den prozentualen Anteil der Samples, die Sie richtig klassifiziert haben, score / n.

Fügen Sie abschließend das folgende Skript hinzu, um die vorherigen Hilfsfunktionen zu nutzen:

step_4_evaluate.py

def validate():     trainloader, testloader = get_train_test_loaders()     net = Net().float()      pretrained_model = torch.load("checkpoint.pth")     net.load_state_dict(pretrained_model)      print('=' * 10, 'PyTorch', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)   if __name__ == '__main__':     validate() 

Dadurch wird ein vorab trainiertes neuronales Netz geladen und seine Leistung auf dem bereitgestellten Gebärdensprachen-Datensatz bewertet. Das Skript gibt hier insbesondere Genauigkeit über die Bilder aus, die Sie für das Training verwendet haben, und einen separaten Satz von Bildern, die Sie für Testzwecke aufgehoben haben und die validation set heißen.

Als Nächstes exportieren Sie das PyTorch in eine ONNX-Binärdatei. Diese Binärdatei kann dann in der Produktion verwendet werden, um mit Ihrem Modell eine Inferenz auszuführen. Am wichtigsten ist, dass der Code, der diese Binärdatei ausführt, keine Kopie der ursprünglichen Netzwerkdefinition benötigt. Fügen Sie am Ende der Funktion validate Folgendes hinzu:

step_4_evaluate.py

    trainloader, testloader = get_train_test_loaders(1)      # export to onnx     fname = "signlanguage.onnx"     dummy = torch.randn(1, 1, 28, 28)     torch.onnx.export(net, dummy, fname, input_names=['input'])      # check exported model     model = onnx.load(fname)     onnx.checker.check_model(model)  # check model is well-formed      # create runnable session with exported model     ort_session = ort.InferenceSession(fname)     net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]      print('=' * 10, 'ONNX', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc) 

Dadurch wird das ONNX-Modell exportiert, das exportierte Modell überprüft und dann eine Inferenz mit dem exportierten Modell ausgeführt. Überprüfen Sie nochmals, ob Ihre Datei mit der Datei aus Schritt 4 in diesem Repository übereinstimmt.

step_4_evaluate.py

from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np  import onnx import onnxruntime as ort  from step_2_dataset import get_train_test_loaders from step_3_train import Net   def evaluate(outputs: Variable, labels: Variable) -> float:     """Evaluate neural network outputs against non-one-hotted labels."""     Y = labels.numpy()     Yhat = np.argmax(outputs, axis=1)     return float(np.sum(Yhat == Y))   def batch_evaluate(         net: Net,         dataloader: torch.utils.data.DataLoader) -> float:     """Evaluate neural network in batches, if dataset is too large."""     score = n = 0.0     for batch in dataloader:         n += len(batch['image'])         outputs = net(batch['image'])         if isinstance(outputs, torch.Tensor):             outputs = outputs.detach().numpy()         score += evaluate(outputs, batch['label'][:, 0])     return score / n   def validate():     trainloader, testloader = get_train_test_loaders()     net = Net().float().eval()      pretrained_model = torch.load("checkpoint.pth")     net.load_state_dict(pretrained_model)      print('=' * 10, 'PyTorch', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)      trainloader, testloader = get_train_test_loaders(1)      # export to onnx     fname = "signlanguage.onnx"     dummy = torch.randn(1, 1, 28, 28)     torch.onnx.export(net, dummy, fname, input_names=['input'])      # check exported model     model = onnx.load(fname)     onnx.checker.check_model(model)  # check model is well-formed      # create runnable session with exported model     ort_session = ort.InferenceSession(fname)     net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]      print('=' * 10, 'ONNX', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)   if __name__ == '__main__':     validate() 

Führen Sie Folgendes aus, um den Checkpoint vom letzten Schritt zu verwenden und zu evaluieren:

  • python step_4_evaluate.py

Dadurch erhalten Sie eine ähnliche Ausgabe wie die Folgende, die nicht nur bestätigt, dass Ihr exportiertes Modell funktioniert, sondern auch Ihr ursprüngliches PyTorch-Modell bestätigt:

Output========== PyTorch ========== Training accuracy: 99.9 Validation accuracy: 97.4 ========== ONNX ========== Training accuracy: 99.9 Validation accuracy: 97.4 

Ihr neuronales Netz erreicht eine Trainingsgenauigkeit von 99,9 % und eine Validierungsgenauigkeit von 97,4 %. Diese Lücke zwischen Trainings- und Validierungsgenauigkeit deutet auf eine Überanpassung Ihres Modells hin. Das heißt, dass Ihr Modell die Trainingsdaten gespeichert hat, anstatt generalisierbare Muster zu erlernen. Weiterführende Informationen zu den Implikationen und Ursachen der Überanpassung finden Sie in Understanding Bias-Variance Tradeoffs (Das Spannungsfeld von Verzerrungsvarianzen verstehen).

Nun haben wir einen Gebärdensprachen-Klassifizierer fertiggestellt. Im Grunde genommen kann unser Modell die Gebärden fast immer korrekt erkennen und voneinander unterscheiden. Da dies bereits ein recht gutes Modell ist, fahren wir nun mit der letzten Stufe unserer Anwendung fort. Wir werden diesen Gebärdensprachen-Classifier in einer Webcam-Anwendung in Echtzeit einsetzen.

Schritt 5 – Verknüpfen der Kameraaufzeichnungen

Ihr nächstes Ziel besteht darin, die Kamera des Computers mit Ihrem Gebärdensprachen-Klassifizierer zu verknüpfen. Sie erfassen die Eingangsdaten der Kamera, klassifizieren die angezeigte Gebärdensprache und melden dann die klassifizierte Gebärde an den Benutzer zurück.

Erstellen Sie nun ein Python-Skript für die Gesichtserkennung. Erstellen Sie die Datei step_6_camera.py mit nano oder Ihrem bevorzugten Texteditor:

  • nano step_5_camera.py

Fügen Sie in der Datei den folgenden Code hinzu:

step_5_camera.py

"""Test for sign language classification""" import cv2 import numpy as np import onnxruntime as ort  def main():     pass  if __name__ == '__main__':     main() 

Dieser Code importiert OpenCV, die Ihre Bildprogramme enthält, und die ONNX-Laufzeit. Das ist alles, was Sie mit Ihrem Modell in der Inferenz ausführen müssen. Der Rest des Codes ist eine typische Python-Programmvorgabe.

Ersetzen Sie nun pass in der Funktion main durch den folgenden Code, der einen Gebärdensprachen-Classifier mit den zuvor trainierten Parametern initialisiert. Fügen Sie zudem ein Mapping zwischen den Indizes und den Buchstaben sowie den Bildstatistiken hinzu:

step_5_camera.py

def main():     # constants     index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')     mean = 0.485 * 255.     std = 0.229 * 255.      # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx") 

Sie werden Elemente dieses Testskripts aus der offiziellen OpenCV verwenden. Aktualisieren Sie insbesondere den Körper der Funktion main. Zuerst initialisieren Sie ein VideoCapture-Objekt, das so eingestellt ist, dass es Live-Einspeisungen von der Kamera Ihres Computers erfassen kann. Platzieren Sie das ans Ende der Funktion main:

step_5_camera.py

def main():     ...     # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx")      cap = cv2.VideoCapture(0) 

Fügen Sie dann eine while-Schleife hinzu, die bei jedem Zeitschritt von der Kamera liest:

step_5_camera.py

def main():     ...     cap = cv2.VideoCapture(0)     while True:         # Capture frame-by-frame         ret, frame = cap.read() 

Schreiben Sie eine Hilfsfunktion, die das zentrale Cropping für das Kamerabild übernimmt. Platzieren Sie diese Funktion vor main:

step_5_camera.py

def center_crop(frame):     h, w, _ = frame.shape     start = abs(h - w) // 2     if h > w:         frame = frame[start: start + w]     else:         frame = frame[:, start: start + h]     return frame 

Führen Sie als Nächstes das zentrale Cropping für das Kamerabild durch, konvertieren Sie es in Graustufen, normalisieren Sie es and ändern Sie die Größe auf 28x28. Platzieren Sie dies in die while-Schleife innerhalb der Funktion main:

step_5_camera.py

def main():     ...     while True:         # Capture frame-by-frame         ret, frame = cap.read()          # preprocess data         frame = center_crop(frame)         frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)         x = cv2.resize(frame, (28, 28))         x = (frame - mean) / std 

Führen Sie noch innerhalb der while-Schleife eine Inferenz mit der ONNX-Laufzeit aus. Konvertieren Sie die Ausgaben in einen Klassenindex und dann in einen Buchstaben:

step_5_camera.py

        ...         x = (frame - mean) / std          x = x.reshape(1, 1, 28, 28).astype(np.float32)         y = ort_session.run(None, {'input': x})[0]          index = np.argmax(y, axis=1)         letter = index_to_letter[int(index)] 

Zeigen Sie den vorhergesagten Buchstaben innerhalb des Bildrahmens und das Bild wieder dem Benutzer an:

step_5_camera.py

        ...         letter = index_to_letter[int(index)]          cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)         cv2.imshow("Sign Language Translator", frame) 

Fügen Sie am Ende der while-Schleife diesen Code hinzu, um zu prüfen, ob der Benutzer das Zeichen q wählt und falls das der Fall ist, beenden Sie die Anwendung. Diese Zeile hält das Programm für 1 Millisekunde an. Fügen Sie Folgendes hinzu:

step_5_camera.py

        ...         cv2.imshow("Sign Language Translator", frame)          if cv2.waitKey(1) & 0xFF == ord('q'):             break 

Lösen Sie abschließend die Aufnahme aus und schließen Sie alle Fenster. Platzieren Sie sie außerhalb der while-Schleife, um die Funktion main zu beenden.

step_5_camera.py

...      while True:         ...         if cv2.waitKey(1) & 0xFF == ord('q'):             break       cap.release()     cv2.destroyAllWindows() 

Überprüfen Sie nochmals, ob Ihre Datei mit dem folgenden oder diesem Repository übereinstimmt:

step_5_camera.py

import cv2 import numpy as np import onnxruntime as ort   def center_crop(frame):     h, w, _ = frame.shape     start = abs(h - w) // 2     if h > w:         return frame[start: start + w]     return frame[:, start: start + h]   def main():     # constants     index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')     mean = 0.485 * 255.     std = 0.229 * 255.      # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx")      cap = cv2.VideoCapture(0)     while True:         # Capture frame-by-frame         ret, frame = cap.read()          # preprocess data         frame = center_crop(frame)         frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)         x = cv2.resize(frame, (28, 28))         x = (x - mean) / std          x = x.reshape(1, 1, 28, 28).astype(np.float32)         y = ort_session.run(None, {'input': x})[0]          index = np.argmax(y, axis=1)         letter = index_to_letter[int(index)]          cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)         cv2.imshow("Sign Language Translator", frame)          if cv2.waitKey(1) & 0xFF == ord('q'):             break      cap.release()     cv2.destroyAllWindows()  if __name__ == '__main__':     main() 

Schließen Sie Ihre Datei und führen Sie das Skript aus.

  • python step_5_camera.py

Sobald das Skript ausgeführt wird, wird ein Fenster mit der Live-Einspeisung der Webcam angezeigt. Der vorhergesagte Gebärdensprachenbuchstabe wird oben links angezeigt. Halten Sie die Hand hoch und machen Sie Ihre Lieblingsgebärde, um Ihren Classifier in Aktion zu sehen. Hier sind einige Beispiel-Ergebnisse, die den Buchstaben L und D zeigen.

Screenshot Ihres Sample-OpenCV-Programms für den Fingerbuchstaben ‚L‘.
 Screenshot Ihres Sample-OpenCV-Programms für den Fingerbuchstaben ,D‘.

Beachten Sie während des Tests, dass der Hintergrund für diesen Übersetzer ziemlich klar sein muss, damit er funktioniert. Dies bringt die Sauberkeit des Datensatzes leider mit sich. Würde der Datensatz Bilder von Handzeichen mit verschiedenen Hintergründen enthalten, hätte das Netz kein Problem mit rauschenden Hintergründen. Der Datensatz bietet jedoch leere Hintergründe und ziemlich zentrierte Hände. Die Webcam funktioniert daher am besten, wenn die Hand ebenfalls zentriert ist und sich vor einem leeren Hintergrund befindet.

Damit ist die Übersetzungsanwendung für die Gebärdensprache vollendet.

Zusammenfassung

In diesem Tutorial haben Sie einen Übersetzer für die amerikanische Gebärdensprache mittels Computer Vision und einem Modell für maschinelles Lernen entwickelt. Sie haben vor allem neue Aspekte des Trainings eines Modells für maschinelles Lernen gesehen – genauer gesagt Data Augmentation für die Modellstabilität, Learning Rate Schedules für geringere Verluste und Exportvorgänge von KI-Modellen mithilfe von ONNX zu Produktionszwecken. Dies führte schlussendlich zu der Schaffung einer Echtzeit-Computer-Vision-Anwendung, die Gebärdensprache mithilfe einer Pipeline, die Sie erstellt haben, in Buchstaben übersetzt. Es sollte allerdings bedacht werden, dass der finale Klassifizierer instabil ist. Glücklicherweise gibt es jedoch Methoden, die man einzeln oder auch zusammen einsetzen kann, um gegen diese Instabilität vorzugehen. Diese stellen wir nachfolgend vor. Die folgenden Themen beinhalten weiterführende Erläuterungen zu Verbesserung Ihrer Anwendung:

  • Generalisierung: Hierbei handelt es sich keineswegs um einen Unterbereich von Computer Vision, sondern um ein beständiges Problem, das im Grunde bei jedem Aspekt zum Thema maschinelles Lernen auftritt. Siehe Understanding Bias-Variance Tradeoffs​​​ (Das Spannungsfeld von Verzerrungsvarianzen verstehen)​​​
  • Domain Adaptation (Domänenanpassung): Angenommen, Ihr Modell ist für Domäne A trainiert (z. B. sonnige Umgebungen). Können Sie das Modell in diesem Fall auf Domäne B umstellen (z. B. wolkige Umgebungen)?
  • Adversarial Examples (gegnerische Beispiele): Angenommen ein Kontrahent erstellt absichtlich Bilder, um Ihr Modell zu täuschen. Wie können Sie solche Bilder gestalten? Wie können Sie gegen solche Bilder vorgehen?