One place for hosting & domains

      Erstellen

      Erstellen eines redundanten Speicherpools mit GlusterFS unter Ubuntu 20.04


      Eine frühere Version dieses Tutorials wurde von Justin Ellingwood verfasst.

      Einführung

      Single Points of Failure stellen bei der Speicherung kritischer Daten ein beträchtliches Risiko dar. Während sich mit vielen Datenbanken und anderer Software Daten im Kontext einer einzelnen Anwendung verteilen lassen, arbeiten andere Systeme auf der Ebene des Dateisystems, um sicherzustellen, dass Daten beim Schreiben auf Festplatte jedes Mal auch an einen anderen Ort kopiert werden.

      GlusterFS ist ein Network-Attached-Storage-Dateisystem (NAS), mit dem Sie Speicherressourcen verschiedener Geräte bündeln können. So lassen sich mehrere Speichergeräte, die auf unterschiedliche Computer verteilt sind, als eine leistungsfähigere Einheit nutzen. Außerdem bietet Ihnen GlusterFS die Möglichkeit, verschiedenartige Speicherkonfigurationen einzurichten, von denen viele funktionell RAID-Leveln ähneln. Zum Beispiel können Sie Daten auf verschiedenen Knoten im Cluster stripen oder für eine höhere Datenverfügbarkeit Redundanz implementieren.

      Ziele

      In diesem Leitfaden erstellen Sie ein redundantes geclustertes Speicherarray, auch als verteiltes Dateisystem oder (wie in der GlusterFS-Dokumentation) als Trusted Storage Pool bezeichnet. Damit erhalten Sie Funktionen, die einer über das Netzwerk gespiegelten RAID-Konfiguration ähneln: Jeder unabhängige Server enthält eine eigene Kopie der Daten, sodass Ihre Anwendungen auf eine beliebige Kopie zugreifen können. Dadurch lässt sich die Leselast besser verteilen.

      Dieser redundante GlusterFS-Cluster wird aus zwei Ubuntu 20.04-Servern bestehen. Er wird sich ähnlich wie ein NAS-Server mit gespiegeltem RAID verhalten. Dann werden Sie auf den Cluster über einen dritten Ubuntu 20.04-Server zugreifen, der als GlusterFS-Client konfiguriert ist.

      Anmerkung zur sicheren Ausführung von GlusterFS

      Wenn Sie einem GlusterFS-Volume Daten hinzufügen, werden diese Daten mit jedem Gerät im Speicherpool, in dem das Volumen gehostet wird, synchronisiert. Dieser Datenverkehr zwischen Knoten wird standardmäßig nicht verschlüsselt, d. h. es besteht das Risiko, dass er von bösartigen Akteuren abgefangen wird.

      Wenn Sie GlusterFS in der Produktion verwenden möchten, wird daher empfohlen, das Dateisystem in einem isolierten Netzwerk auszuführen. Sie könnten GlusterFS beispielsweise so einrichten, dass es in einer Virtual Private Cloud (VPC) oder mit einem VPN zwischen den einzelnen Knoten ausgeführt wird.

      Wenn Sie GlusterFS in DigitalOcean bereitstellen möchten, können Sie es in einem isolierten Netzwerk einrichten, indem Sie Ihre Serverinfrastruktur einer DigitalOcean Virtual Private Cloud hinzufügen. Details zur entsprechenden Einrichtung finden Sie in unserer VPC-Produktdokumentation.

      Voraussetzungen

      Um diesem Tutorial zu folgen, benötigen Sie drei Server, auf denen Ubuntu 20.04 ausgeführt wird. Jeder dieser Server sollte über einen Nicht-root-Benutzer mit Administratorberechtigungen und eine mit UFW konfigurierte Firewall verfügen. Folgen Sie dazu unserem Leitfaden für die Ersteinrichtung des Servers für Ubuntu 20.04.

      Anmerkung: Wie im Abschnitt „Ziele“ erwähnt, wird dieses Tutorial Sie durch die Konfiguration von zwei Ubuntu-Servern als Server in einem Speicherpool und dem dritten Server als Client begleiten; diesen werden Sie für Zugriff auf die beiden Speicherknoten verwenden.

      Aus Gründen der Einfachheit wird sich das Tutorial auf diese Computer mit folgenden Hostnamen beziehen:

      Hostname Rolle im Speicherpool
      gluster0 Server
      gluster1 Server
      gluster2 Client

      Befehle, die entweder auf gluster0 oder gluster1 ausgeführt werden müssen, weisen einen blauen bzw. roten Hintergrund auf:

      Befehle, die nur auf dem Client (gluster2) ausgeführt werden müssen, haben einen grünen Hintergrund:

      Befehle, die auf mehr als einem Computer ausgeführt werden können oder müssen, weisen einen grauen Hintergrund auf:

      Schritt 1 — Konfigurieren der DNS-Auflösung auf jedem Computer

      Das Erstellen einer Auflösung von Hostnamen zwischen den einzelnen Computern kann Ihnen bei der Verwaltung Ihres Gluster-Speicherpools helfen. Wenn Sie in diesem Tutorial später in einem gluster-Befehl auf einen Ihrer Computer verweisen, können Sie dies dann mit einem leicht zu merkenden Domänennamen oder sogar einem Spitznamen anstelle der jeweiligen IP-Adresse tun.

      Wenn Sie keinen freien Domänennamen haben oder einfach nur eine schnelle Einrichtung vornehmen möchten, können Sie stattdessen die Datei /etc/hosts auf den einzelnen Computern bearbeiten. Dies ist eine spezielle Datei auf Linux-Computern, in der Sie das System statisch konfigurieren können, um alle in der Datei enthaltenen Hostnamen in Form statischer IP-Adressen aufzulösen.

      Anmerkung: Wenn Sie Ihre Server zur Authentifizierung mit einer Domäne konfigurieren möchten, die Ihnen gehört, müssen Sie sich zunächst einen Domänennamen von einer Domänenregistrierungstelle wie Namecheap oder Enom verschaffen und dann die entsprechenden DNS-Einträge konfigurieren.

      Sobald Sie für jeden Server einen A-Eintrag konfiguriert haben, können Sie mit Schritt 2 fortfahren. Stellen Sie sicher, dass Sie glusterN.example.com und glusterN durch den Domänennamen ersetzen, der auf den jeweiligen im Beispielbefehl verwiesenen Server auflöst.

      Wenn Sie Ihre Infrastruktur von DigitalOcean erhalten haben, könnten Sie Ihren Domänennamen DigitalOcean hinzufügen und für jeden Ihrer Server einen eindeutigen A-Eintrag erstellen.

      Öffnen Sie die Datei mit root-Berechtigungen mit einem Texteditor Ihrer Wahl auf jedem Ihrer Computer. Wir verwenden hier nano:

      Standardmäßig wird die Datei in etwa so aussehen (mit entfernten Kommentaren):

      /etc/hosts

      127.0.1.1 hostname hostname
      127.0.0.1 localhost
      
      ::1 ip6-localhost ip6-loopback
      fe00::0 ip6-localnet
      ff00::0 ip6-mcastprefix
      ff02::1 ip6-allnodes
      ff02::2 ip6-allrouters
      ff02::3 ip6-allhosts
      

      Fügen Sie auf einem Ihrer Ubuntu-Server unter der lokalen Hostdefinition die IP-Adresse der einzelnen Server hinzu, gefolgt von allen Namen, die Sie verwenden möchten, um auf sie in Befehlen verweisen zu können.

      Im folgenden Beispiel erhält jeder Server einen langen Hostnamen, der auf glusterN.example.com abgestimmt ist, und einen kurzen Hostnamen, der auf glusterN abgestimmt ist. Sie können die Abschnitte glusterN.example.com und glusterN jeder Zeile in einen beliebigen Namen – oder durch einzelne Leerzeichen getrennte Namen – ändern, die Sie für den Zugriff auf einzelne Server verwenden möchten. Beachten Sie jedoch, dass in diesem Tutorial durchgehend die folgenden Beispiele verwenden werden:

      Anmerkung: Wenn Ihre Server Teil eines Infrastrukturpools vom Typ Virtual Private Cloud sind, sollten Sie in der Datei /etc/hosts die privaten IP-Adressen der einzelnen Server anstelle ihrer öffentlichen IP-Adressen verwenden.

      /etc/hosts

      . . .
      127.0.0.1       localhost
      first_ip_address gluster0.example.com gluster0
      second_ip_address gluster1.example.com gluster1
      third_ip_address gluster2.example.com gluster2
      
      . . .
      

      Wenn Sie fertig damit sind, der Datei /etc/hosts eines Computers diese neuen Zeilen hinzuzufügen, kopieren Sie die Zeilen und fügen Sie sie den /etc/hosts-Dateien auf Ihren anderen Computern hinzu. Jede /etc/hosts-Datei sollte dieselben Zeilen enthalten und die IP-Adressen Ihrer Server mit den ausgewählten Namen verknüpfen.

      Speichern und schließen Sie dann die Datei. Wenn Sie nano verwendet haben, drücken Sie STRG+X, Y und dann ENTER​​​.

      Nachdem Sie die Auflösung der Hostnamen zwischen den einzelnen Servern konfiguriert haben, können Sie Befehle leichter ausführen, wenn Sie später einen Speicherpool und ein Volume einrichten. Als Nächstes führen Sie einen weiteren Schritt aus, der auf jedem Ihrer Server abgeschlossen werden muss. Und zwar fügen Sie jedem Ihrer drei Ubuntu-Server das offizielle Personal Package Archive (PPA) des Gluster-Projekts hinzu, um dafür zu sorgen, dass Sie die neueste Version von GlusterFS installieren können.

      Schritt 2 — Einrichten von Softwarequellen auf jedem Computer

      Zwar enthalten die standardmäßigen Ubuntu 20.04-APT-Repositorys GlusterFS-Pakete, doch handelt es sich dabei zum Zeitpunkt der Verfassung dieses Dokuments nicht um die aktuellsten Versionen. Eine Möglichkeit, die neueste stabile Version von GlusterFS (zum Zeitpunkt der Verfassung dieses Dokuments Version 7.6) zu installieren, besteht darin, jedem Ihrer drei Ubuntu-Server das offizielle PPA des Gluster-Projekts hinzuzufügen.

      Fügen Sie das PPA für die GlusterFS-Pakete hinzu, indem Sie auf jedem Server folgenden Befehl ausführen:

      • sudo add-apt-repository ppa:gluster/glusterfs-7

      Drücken Sie ENTER, wenn Sie dazu aufgefordert werden, um zu bestätigen, dass Sie das PPA tatsächlich hinzufügen möchten.

      Aktualisieren Sie nach dem Hinzufügen des PPA den lokalen Paketindex der einzelnen Server. Dadurch wird sich jeder Server der neu verfügbaren Pakete bewusst:

      Nachdem Sie das offizielle PPA des Gluster-Projekts den einzelnen Servern hinzugefügt und den lokalen Paketindex aktualisiert haben, können Sie die erforderlichen GlusterFS-Pakete installieren. Da zwei Ihrer drei Computer als Gluster-Server und der dritte Computer als Client fungieren werden, müssen Sie jedoch zwei separate Installations- und Konfigurationsverfahren befolgen. Zuerst installieren und richten Sie die Serverkomponenten ein.

      Schritt 3 — Installieren von Serverkomponenten und Erstellen eines Trusted Storage Pool

      Ein Speicherpool ist eine beliebige Menge an Speicherkapazität, die aus mehr als einer Speicherquelle aggregiert wird. In diesem Schritt konfigurieren Sie zwei Ihrer Server — gluster0 und gluster1 — als Clusterkomponenten.

      Installieren Sie sowohl auf gluster0 als auch gluster1 das Paket für GlusterFS-Server, indem Sie Folgendes eingeben:

      • sudo apt install glusterfs-server

      Drücken Sie auf Aufforderung Y und dann ENTER, um die Installation zu bestätigen.

      Der Installationsprozess konfiguriert GlusterFS automatisch so, dass eine Ausführung als systemd-Dienst erfolgt. Er sorgt jedoch nicht für einen automatischen Start des Diensts oder das Aktivieren zum Ausführen zur Startzeit.

      Um glusterd (den GlusterFS-Dienst) zu starten, führen Sie den Befehl systemctl start sowohl auf gluster0 als auch gluster1 aus:

      • sudo systemctl start glusterd.service

      Führen Sie dann folgenden Befehl auf beiden Servern aus. Dadurch wird der Dienst jedes Mal gestartet, wenn der Server gestartet wird:

      • sudo systemctl enable glusterd.service

      Anschließend können Sie den Status des Diensts auf einem oder beiden Servern überprüfen:

      • sudo systemctl status glusterd.service

      Wenn der Dienst erfolgreich ausgeführt wird, erhalten Sie eine Ausgabe, die wie folgt aussieht:

      Output

      ● glusterd.service - GlusterFS, a clustered file-system server Loaded: loaded (/lib/systemd/system/glusterd.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-02 21:32:21 UTC; 32s ago Docs: man:glusterd(8) Main PID: 14742 (glusterd) Tasks: 9 (limit: 2362) CGroup: /system.slice/glusterd.service └─14742 /usr/sbin/glusterd -p /var/run/glusterd.pid --log-level INFO

      Wenn Sie dem Leitfaden zur Ersteinrichtung des Servers gefolgt sind, haben Sie auf jedem Ihrer Computer eine Firewall mit UFW eingerichtet. Aus diesem Grund müssen Sie die Firewall für jeden Knoten öffnen, bevor Sie eine Verbindung zwischen ihnen herstellen und einen Speicherpool einrichten können.

      Der Gluster-Daemon nutzt Port 24007, sodass Sie jedem Knoten über die Firewall der einzelnen Knoten in Ihrem Speicherpool Zugriff auf den Port gewähren müssen. Führen Sie dazu folgenden Befehl auf gluster0 aus. Denken Sie daran, gluster1_ip_address in die IP-Adresse von gluster1 zu ändern:

      • sudo ufw allow from gluster1_ip_address to any port 24007

      Führen Sie dann folgenden Befehl auf gluster1 aus. Vergessen Sie auch hier nicht, gluster0_ip_address in die IP-Adresse von gluster0 zu ändern:

      • sudo ufw allow from gluster0_ip_address to any port 24007

      Außerdem müssen Sie Ihrem Clientcomputer (gluster2) Zugriff auf diesen Port gewähren. Andernfalls werden Sie später Probleme haben, wenn Sie versuchen, das Volumen bereitzustellen. Führen Sie sowohl auf gluster0 als auch gluster1 folgenden Befehl aus, um diesen Port für Ihren Clientcomputer zu öffnen:

      • sudo ufw allow from gluster2_ip_address to any port 24007

      Um sicherzustellen, dass keine anderen Computer auf einem der Server auf den Port von Gluster zugreifen können, fügen Sie dann die folgende Rahmenregel deny sowohl gluster0 als auch gluster1 hinzu:

      Sie können nun eine Verbindung zwischen gluster0 und gluster1 herstellen. Dazu müssen Sie auf einem Ihrer Knoten den Befehl gluster peer probe ausführen. Es spielt dabei keine Rolle, welchen Knoten Sie verwenden. Das folgende Beispiel veranschaulicht die Ausführung des Befehls auf gluster0:

      • sudo gluster peer probe gluster1

      Dieser Befehl weist gluster0 im Wesentlichen an, gluster1 zu vertrauen und als Teil seines Speicherpools zu registrieren. Wenn der Test erfolgreich war, wird folgende Ausgabe zurückgegeben:

      Output

      peer probe: success

      Sie können jederzeit überprüfen, ob die Knoten miteinander kommunizieren, indem Sie auf einem der Knoten den Befehl gluster peer status ausführen. In diesem Beispiel wird er auf gluster1 ausgeführt:

      Wenn Sie diesen Befehl auf gluster1 ausführen, wird eine Ausgabe angezeigt, die wie folgt aussieht:

      Output

      Number of Peers: 1 Hostname: gluster0.example.com Uuid: a3fae496-c4eb-4b20-9ed2-7840230407be State: Peer in Cluster (Connected)

      An diesem Punkt kommunizieren Ihre beiden Server miteinander und sind bereit, gemeinsam Speichervolumes zu erstellen.

      Schritt 4 — Einrichten eines Speichervolumes

      Denken Sie daran, dass das primäre Ziel dieses Tutorials in der Einrichtung eines redundanten Speicherpools besteht. Dazu richten Sie ein Volume mit Replikatfunktion ein, damit Sie mehrere Kopien Ihrer Daten speichern und verhindern können, dass Ihr Cluster einen Single Point of Failure aufweist.

      Um ein Volume zu erstellen, verwenden Sie den Befehl gluster volume create mit dieser allgemeinen Syntax:

      sudo gluster volume create volume_name replica number_of_servers domain1.com:/path/to/data/directory domain2.com:/path/to/data/directory force
      

      Das bedeuten die Argumente und Optionen des Befehls gluster volume create:

      • volume_name: Das ist der Name, mit dem Sie nach der Erstellung auf das Volume verweisen. Der folgende Beispielbefehl sorgt für die Erstellung eines Volumes namens volume1.
      • replica number_of_servers: Nach dem Namen des Volumes können Sie festlegen, welche Art von Volume Sie erstellen möchten. Denken Sie daran, dass das Ziel dieses Tutorials darin besteht, einen redundanten Speicherpool einzurichten, sodass wir den Volume-Typ replica wählen. Dies erfordert ein Argument, mit dem angegeben wird, auf wie viele Server die Daten des Volumes repliziert werden sollen (in diesem Tutorial 2).
      • domain1.com:/… und domain2.com:/…: Diese definieren die Computer und den Speicherort des Verzeichnisses der Bricks (eine GlusterFS-Bezeichnung für die grundlegende Speichereinheit des Systems), was alle Verzeichnisse auf allen Computern umfasst, die als Teil oder Kopie eines größeren Volumes dienen. So entsteht volume1. Im folgenden Beispiel wird im root-Verzeichnis beider Server ein Verzeichnis namens gluster-storage erstellt.
      • force: Diese Option sorgt für das Überschreiben aller Warnungen oder Optionen, die sonst auftreten und die Erstellung des Volumes unterbrechen würden.

      Anhand der in diesem Tutorial zuvor aufgeführten Konventionen können Sie diesen Befehl zur Erstellung eines Volumes ausführen. Beachten Sie, dass Sie den Befehl entweder auf gluster0 oder gluster1 ausführen können:

      • sudo gluster volume create volume1 replica 2 gluster0.example.com:/gluster-storage gluster1.example.com:/gluster-storage force

      Wenn das Volume erfolgreich erstellt wurde, erhalten Sie folgende Ausgabe:

      Output

      volume create: volume1: success: please start the volume to access data

      An diesem Punkt wurde Ihr Volume bereits erstellt, ist aber noch nicht aktiv. Sie können das Volume starten und zur Verwendung bereitstellen, indem Sie folgenden Befehl ausführen (erneut auf einem Ihrer beiden Gluster-Server):

      • sudo gluster volume start volume1

      Wenn das Volume korrekt gestartet wurde, erhalten Sie folgende Ausgabe:

      Output

      volume start: volume1: success

      Überprüfen Sie als Nächstes, ob das Volume online ist. Führen Sie auf einem Ihrer Knoten folgenden Befehl aus:

      • sudo gluster volume status

      Dadurch wird eine Ausgabe zurückgegeben, die der folgenden ähnelt:

      Output

      Status of volume: volume1 Gluster process TCP Port RDMA Port Online Pid ------------------------------------------------------------------------------ Brick gluster0.example.com:/gluster-storage 49152 0 Y 18801 Brick gluster1.example.com:/gluster-storage 49152 0 Y 19028 Self-heal Daemon on localhost N/A N/A Y 19049 Self-heal Daemon on gluster0.example.com N/A N/A Y 18822 Task Status of Volume volume1 ------------------------------------------------------------------------------ There are no active volume tasks

      Laut dieser Ausgabe sind die Bricks auf beiden Servern online.

      Als letzter Schritt zur Konfiguration Ihres Volumes müssen Sie die Firewall auf beiden Servern öffnen, damit Ihr Clientcomputer in der Lage ist, sich mit dem Volume zu verbinden und das Volume bereitzustellen. Gemäß der Beispielausgabe des vorherigen Befehls wird volume1 auf beiden Computern an Port 49152 ausgeführt. Dies ist der Standardport von GlusterFS für das erste Volumen. Weitere Volumes werden also Port 49153, dann 49154 usw. verwenden.

      Führen Sie sowohl auf gluster0 als auch gluster1 folgenden Befehl aus, um gluster2 über die jeweilige Firewall Zugriff auf diesen Port zu gewähren:

      • sudo ufw allow from gluster2_ip_address to any port 49152

      Fügen Sie dann für zusätzliche Sicherheit eine weitere deny-Rahmenregel für den Port des Volumes hinzu – sowohl auf gluster0 als auch gluster1. Dadurch wird sichergestellt, dass auf beiden Servern keine anderen Computer außer Ihrem Client auf das Volume zugreifen können:

      Nachdem Ihr Volume nun ausgeführt wird, können Sie Ihren Clientcomputer einrichten und remote nutzen.

      Schritt 5 — Installieren und Konfigurieren von Clientkomponenten

      Ihr Volume ist nun konfiguriert und zur Verwendung durch Ihren Clientcomputer verfügbar. Bevor Sie beginnen, müssen Sie jedoch das Paket glusterfs-client aus dem PPA installieren, das Sie in Schritt 1 auf Ihrem Clientcomputer eingerichtet haben. Die Abhängigkeiten dieses Pakets umfassen einige gemeinsame Bibliotheken und Übersetzermodule von GlusterFS sowie die für die Arbeit erforderlichen FUSE-Tools.

      Führen Sie folgenden Befehl auf gluster2 aus:

      • sudo apt install glusterfs-client

      Sie werden Ihr Remote-Speichervolume in Kürze auf Ihrem Clientcomputer bereitstellen. Bevor Sie dies tun können, müssen Sie einen Bereitstellungspunkt erstellen. Traditionell befindet sich dieser im Verzeichnis /mnt, doch kann jeder beliebige Ort verwendet werden.

      Erstellen Sie aus Gründen der Einfachheit auf Ihrem Clientcomputer ein Verzeichnis namens /storage-pool als Bereitstellungspunkt. Dieser Verzeichnisname beginnt mit einem Schrägstrich (/), der es im root-Verzeichnis platziert. Daher müssen Sie das Verzeichnis mit sudo-Berechtigungen erstellen:

      Jetzt können Sie das Remotevolume bereitstellen. Werfen Sie zuvor einen Blick auf die Syntax des Befehls mount, den Sie dazu verwenden werden:

      sudo mount -t glusterfs domain1.com:volume_name /path/to/mount/point
      

      mount ist ein Dienstprogramm in vielen Unix-ähnlichen Betriebssystemen. Es dient dazu, Dateisysteme (ob externe Speichergeräte wie SD-Karten bzw. USB-Sticks oder NAS-Systeme wie im Fall dieses Tutorials) im vorhandenen Dateisystem des Computers in Verzeichnissen bereitzustellen. Die von Ihnen verwendete mount-Befehlssyntax umfasst die Option -t, die drei Argumente erfordert: den Typ des Dateisystems, der bereitgestellt werden soll, das Gerät, auf dem sich das bereitzustellende Dateisystem befindet, und das Verzeichnis auf dem Client, in dem Sie das Volume bereitstellen möchten.

      Beachten Sie, dass das Geräteargument in dieser Beispielsyntax auf einen Hostnamen verweist, gefolgt von einem Doppelpunkt und dann dem Namen des Volumes. GlusterFS abstrahiert die tatsächlichen Speicherverzeichnisse auf jedem Host, was bedeutet, dass dieser Befehl nicht das Verzeichnis /gluster-storage, sondern vielmehr das Volume volume1 bereitstellt.

      Beachten Sie außerdem, dass Sie nur ein Mitglied des Speicherclusters angeben müssen. Dies kann einer der beiden Knoten sein, da der GlusterFS-Dienst sie als einen Computer behandelt.

      Führen Sie auf Ihrem Clientcomputer (gluster2) folgenden Befehl aus, um das Volume im von Ihnen erstellten Verzeichnis /storage-pool bereitzustellen:

      • sudo mount -t glusterfs gluster0.example.com:/volume1 /storage-pool

      Führen Sie danach den Befehl df aus. Dadurch wird für Dateisysteme, auf die der aufrufende Benutzer Zugriff hat, der verfügbare Speicherplatz angezeigt:

      Dieser Befehl zeigt an, dass das GlusterFS-Volume am richtigen Ort bereitgestellt wurde:

      Output

      Filesystem 1K-blocks Used Available Use% Mounted on . . . gluster0.example.com:/volume1 50633164 1938032 48695132 4% /storage-pool

      Jetzt können Sie mit der Prüfung fortfahren, ob alle Daten, die Sie in das Volume auf Ihrem Client schreiben, wie erwartet auf Ihren Serverknoten repliziert werden.

      Schritt 6 — Testen von Redundanzfunktionen

      Nachdem Sie Ihren Client zur Verwendung des Speicherpools und Volumes eingerichtet haben, können Sie seine Funktionalität testen.

      Navigieren Sie auf Ihrem Clientcomputer (gluster2) zum im vorherigen Schritt definierten Bereitstellungspunkt:

      Erstellen Sie dann einige Testdateien. Der folgende Befehl erstellt in Ihrem Speicherpool zehn separate leere Dateien:

      • sudo touch file_{0..9}.test

      Wenn Sie sich die zuvor auf den einzelnen Speicherhosts definierten Speicherverzeichnisse ansehen, werden Sie feststellen, dass alle diese Dateien in jedem System vorhanden sind.

      Auf gluster0:

      Output

      file_0.test file_2.test file_4.test file_6.test file_8.test file_1.test file_3.test file_5.test file_7.test file_9.test

      Und auch auf gluster1:

      Output

      file_0.test file_2.test file_4.test file_6.test file_8.test file_1.test file_3.test file_5.test file_7.test file_9.test

      Wie diese Ausgaben zeigen, wurden auch die Testdateien, die Sie dem Client hinzugefügt haben, in beide Knoten geschrieben.

      Sollte jemals einer der Knoten in Ihrem Speichercluster ausfallen, kann es vorkommen, dass er nicht mehr mit dem Speicherpool synchron ist, wenn Änderungen am Dateisystem vorgenommen werden. Wenn der Knoten wieder online ist, können Sie durch Ausführung eines Lesevorgangs am Bereitstellungspunkt des Clients den Knoten auf fehlende Dateien aufmerksam machen:

      Nachdem Sie verifiziert haben, dass Ihr Speichervolumen korrekt bereitgestellt wurde und Sie Daten an beiden Computer im Cluster replizieren können, können Sie den Zugriff auf den Speicherpool sperren.

      Schritt 7 — Beschränken der Redundanzfunktionen

      Gegenwärtig kann sich jeder Computer ganz ohne Einschränkungen mit Ihrem Speichervolume verbinden. Sie können das ändern, indem Sie die Option auth.allow festlegen, um die IP-Adressen der einzelnen Clients zu definieren, die Zugriff auf das Volume haben sollen.

      Wenn Sie die Konfiguration /etc/hosts verwenden, werden die Namen, die Sie für die Server festgelegt haben, nicht korrekt geroutet. Sie müssen stattdessen eine statische IP-Adresse verwenden. Wenn Sie jedoch DNS-Einträge verwenden, wird hier der Domänenname, den Sie konfiguriert haben, funktionieren.

      Führen Sie auf einem Ihrer beiden Speicherknoten (gluster0 oder gluster1) folgenden Befehl aus:

      • sudo gluster volume set volume1 auth.allow gluster2_ip_address

      Wenn der Befehl erfolgreich abgeschlossen wird, gibt er folgende Ausgabe zurück:

      Output

      volume set: success

      Wenn Sie die Einschränkung irgendwann entfernen möchten, können Sie Folgendes eingeben:

      • sudo gluster volume set volume1 auth.allow *

      Dadurch werden wieder Verbindungen von beliebigen Computern aus möglich. Dies ist nicht sicher, kann aber für die Fehlerbehebung nützlich sein.

      Wenn Sie über mehrere Clients verfügen, können Sie ihre IP-Adressen oder Domänennamen gleichzeitig angeben (je nachdem, ob Sie /etc/hosts oder die Auflösung von DNS-Hostnamen verwenden), getrennt durch Kommas:

      • sudo gluster volume set volume1 auth.allow gluster_client1_ip,gluster_client2_ip

      Ihr Speicherpool ist nun konfiguriert, gesichert und einsatzbereit. Als Nächstes werden Sie einige Befehle kennen lernen, die Ihnen helfen, Informationen über den Status Ihres Speicherpools zu erhalten.

      Schritt 8 — Abrufen von Informationen über den Speicherpool mit GlusterFS-Befehlen

      Wenn Sie bestimmte Einstellungen für Ihren GlusterFS-Speicher ändern, können Sie den Überblick darüber verlieren, welche Optionen Sie zur Verfügung haben, welche Volumes live sind und welche Knoten mit einzelnen Volumes verknüpft sind.

      Es gibt verschiedene Befehle, die auf Ihren Knoten verfügbar sind, mit denen Sie diese Daten abrufen und mit Ihrem Speicherpool interagieren können.

      Wenn Sie Informationen über die einzelnen Volumes wünschen, führen Sie den Befehl gluster volume info aus:

      Output

      Volume Name: volume1 Type: Replicate Volume ID: a1e03075-a223-43ab-a0f6-612585940b0c Status: Started Snapshot Count: 0 Number of Bricks: 1 x 2 = 2 Transport-type: tcp Bricks: Brick1: gluster0.example.com:/gluster-storage Brick2: gluster1.example.com:/gluster-storage Options Reconfigured: auth.allow: gluster2_ip_address transport.address-family: inet storage.fips-mode-rchecksum: on nfs.disable: on performance.client-io-threads: off

      Um Informationen über Peers zu erhalten, mit denen dieser Knoten verbunden ist, können Sie Folgendes eingeben:

      Number of Peers: 1
      
      Hostname: gluster0.example.com
      Uuid: cb00a2fc-2384-41ac-b2a8-e7a1793bb5a9
      State: Peer in Cluster (Connected)
      

      Wenn Sie genaue Informationen zur Ausführung einzelner Knoten wünschen, können Sie ein Profil für ein Volume erstellen, indem Sie Folgendes eingeben:

      • sudo gluster volume profile volume_name start

      Nach erfolgreicher Ausführung dieses Befehls können Sie die gesammelten Informationen abrufen, indem Sie Folgendes eingeben:

      • sudo gluster volume profile volume_name info

      Output

      Brick: gluster0.example.com:/gluster-storage -------------------------------------------- Cumulative Stats: %-latency Avg-latency Min-Latency Max-Latency No. of calls Fop --------- ----------- ----------- ----------- ------------ ---- 0.00 0.00 us 0.00 us 0.00 us 30 FORGET 0.00 0.00 us 0.00 us 0.00 us 36 RELEASE 0.00 0.00 us 0.00 us 0.00 us 38 RELEASEDIR Duration: 5445 seconds Data Read: 0 bytes Data Written: 0 bytes Interval 0 Stats: %-latency Avg-latency Min-Latency Max-Latency No. of calls Fop --------- ----------- ----------- ----------- ------------ ---- 0.00 0.00 us 0.00 us 0.00 us 30 FORGET 0.00 0.00 us 0.00 us 0.00 us 36 RELEASE 0.00 0.00 us 0.00 us 0.00 us 38 RELEASEDIR Duration: 5445 seconds Data Read: 0 bytes Data Written: 0 bytes . . .

      Führen Sie wie zuvor gezeigt den Befehl gluster volume status aus, um eine Liste aller zu GlusterFS zugehörigen Komponenten zu erhalten, die auf den einzelnen Knoten ausgeführt werden:

      • sudo gluster volume status

      Output

      Status of volume: volume1 Gluster process TCP Port RDMA Port Online Pid ------------------------------------------------------------------------------ Brick gluster0.example.com:/gluster-storage 49152 0 Y 19003 Brick gluster1.example.com:/gluster-storage 49152 0 Y 19040 Self-heal Daemon on localhost N/A N/A Y 19061 Self-heal Daemon on gluster0.example.com N/A N/A Y 19836 Task Status of Volume volume1 ------------------------------------------------------------------------------ There are no active volume tasks

      Wenn Sie Ihre GlusterFS-Speichervolumes verwalten möchten, kann es eine gute Idee sein, die GlusterFS-Konsole zu nutzen. Dadurch können Sie mit Ihrer GlusterFS-Umgebung interagieren, ohne zunächst sudo gluster eingeben zu müssen:

      Daraufhin wird eine Eingabeaufforderung angezeigt, in der Sie Ihre Befehle eingeben können. help (Hilfe) ist eine gute Methode, um sich einen Überblick zu verschaffen:

      Output

      peer help - display help for peer commands volume help - display help for volume commands volume bitrot help - display help for volume bitrot commands volume quota help - display help for volume quota commands snapshot help - display help for snapshot commands global help - list global commands

      Führen Sie anschließend exit aus, um die Gluster-Konsole zu verlassen:

      Nun können Sie damit beginnen, GlusterFS mit der nächsten Anwendung zu integrieren.

      Zusammenfassung

      Durch Absolvieren dieses Tutorials haben Sie ein redundantes Speichersystem eingerichtet, mit dem Sie gleichzeitig auf zwei separate Server schreiben können. Das kann für verschiedene Anwendungen nützlich sein und dafür sorgen, dass Ihre Daten verfügbar bleiben, auch wenn ein Server ausfällt.



      Source link

      Erstellen einer Webanwendung mit Flask in Python 3


      Der Autor hat den Free and Open Source Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Flask ist ein kleines und schlankes Python-Web-Framework mit nützlichen Tools und Funktionen, die das Erstellen von Webanwendungen in Python erleichtern. Es bietet Entwicklern mehr Flexibilität und ist ein besser zugängliches Framework für neue Entwickler, da Sie Webanwendungen schnell unter Verwendung einer einzigen Python-Datei erstellen können. Außerdem ist Flask erweiterbar und setzt keine bestimmte Verzeichnisstruktur oder komplizierte Codebausteine voraus, bevor Sie loslegen können.

      Als Teil dieses Tutorials nutzen Sie das Bootstrap-Toolkit, um Ihre Anwendung so zu gestalten, dass sie optisch ansprechender aussieht. Bootstrap wird Ihnen helfen, responsive Webseiten in Ihre Webanwendung zu integrieren, sodass sie problemlos auch mit mobilen Browsern funktioniert, ohne dass Sie dafür eigenen HTML-, CSS- und JavaScript-Code schreiben müssen. Mit dem Toolkit können Sie sich darauf konzentrieren, die Funktionsweise von Flask zu erlernen.

      Flask verwendet die Jinja-Vorlagen-Engine für das dynamische Einrichten von HTML-Seiten mit bekannten Python-Konzepten wie Variablen, Schleifen, Listen usw. Sie werden diese Vorlagen im Rahmen dieses Projekts nutzen.

      In diesem Tutorial entwickeln Sie einen kleinen Weblog mit Flask und SQLite in Python 3. Benutzer der Anwendung können alle Beiträge in Ihrer Datenbank anzeigen und auf den Titel eines Beitrags klicken, um dessen Inhalt anzuzeigen und bei Bedarf der Datenbank einen neuen Beitrag hinzuzufügen bzw. einen bestehenden Beitrag zu bearbeiten oder zu löschen.

      Voraussetzungen

      Bevor Sie mit diesem Leitfaden fortfahren, benötigen Sie Folgendes:

      Schritt 1 – Installieren von Flask

      In diesem Schritt aktivieren Sie Ihre Python-Umgebung und installieren Flask mit dem Package Installer pip.

      Wenn Sie Ihre Programmierumgebung noch nicht aktiviert haben, stellen Sie sicher, dass Sie sich in Ihrem Projektverzeichnis befinden (flask_blog) und den folgenden Befehl nutzen, um die Umgebung zu aktivieren:

      Sobald Ihre Programmierumgebung aktiviert ist, weist Ihre Eingabeaufforderung das Präfix env auf, was wie folgt aussieht:

      Dieses Präfix ist ein Hinweis darauf, dass die Umgebung env derzeit aktiv ist; sie trägt möglicherweise einen anderen Namen, je nach dem Namen, den Sie der Umgebung bei der Erstellung gegeben haben.

      Anmerkung: Sie können Git, ein System für die Versionskontrolle, verwenden, um den Entwicklungsprozess für Ihr Projekt zu verwalten und zu verfolgen. Um mehr über die Verwendung von Git zu erfahren, lesen Sie unseren Artikel Einleitung zu Installation, Nutzung und Verzweigungen von Git.

      Wenn Sie Git verwenden, ist es eine gute Idee, das neu erstellte Verzeichnis env in Ihrer Datei .gitignore zu ignorieren, um eine Verfolgung von Dateien zu vermeiden, die nicht mit dem Projekt in Verbindung stehen.

      Nun installieren Sie Python-Pakete und isolieren Ihren Projektcode abseits von der Hauptinstallation des Python-Systems. Sie werden das mithilfe von pip und python tun.

      Um Flask zu installieren, führen Sie den folgenden Befehl aus:

      Sobald die Installation abgeschlossen ist, führen Sie den folgenden Befehl aus, um die Installation zu überprüfen:

      • python -c "import flask; print(flask.__version__)"

      Sie nutzen die python-Befehlszeilenschnittstelle mit der Option -c, um Python-Code auszuführen. Als Nächstes importieren Sie das flask-Paket mit import flask und drucken dann die Flask-Version, die über die Variable flask.__version__ verfügbar ist.

      Die Ausgabe wird eine Versionsnummer sein, die der folgenden ähnelt:

      Output

      1.1.2

      Sie haben den Projektordner und eine virtuelle Umgebung eingerichtet sowie Flask installiert. Sie können nun mit der Einrichtung Ihrer Basisanwendung fortfahren.

      Schritt 2 — Erstellen einer Basisanwendung

      Nachdem Sie Ihre Programmierumgebung eingerichtet haben, beginnen Sie nun mit der Verwendung von Flask. In diesem Schritt erstellen Sie eine kleine Webanwendung in einer Python-Datei und führen sie aus, um den Server zu starten. Dadurch werden im Browser verschiedene Informationen angezeigt.

      Öffnen Sie in Ihrem Verzeichnis flask_blog eine Datei namens hello.py zum Bearbeiten; verwenden Sie dazu nano oder Ihren bevorzugten Texteditor:

      Die Datei hello.py wird als Minimalbeispiel für die Handhabung von HTTP-Anfragen dienen. Darin werden Sie das Flask-Objekt importieren und eine Funktion erstellen, die eine HTTP-Antwort zurückgibt. Schreiben Sie den folgenden Code in hello.py:

      flask_blog/hello.py

      from flask import Flask
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return 'Hello, World!'
      

      Im vorherigen Codeblock importieren Sie zuerst das Flask-Objekt aus dem flask-Paket. Anschließend nutzen Sie es, um Ihre Flask-Anwendungsinstanz mit dem Namen app zu erstellen. Sie übergeben die spezielle Variable __name__, die den Namen des aktuellen Python-Moduls enthält. Damit wird der Instanz mitgeteilt, wo sie sich befindet. Das ist nötig, da Flask einige Pfade im Hintergrund einrichtet.

      Nachdem Sie die Instanz app erstellt haben, verwenden Sie sie zum Handhaben eingehender Webanfragen und zum Senden von Antworten an den Benutzer. @app.route ist ein Decorator, der eine reguläre Python-Funktion in eine Flask-Anzeigefunktion verwandelt. Diese verwandelt den Rückgabewert der Funktion in eine HTTP-Antwort, die von einem HTTP-Client (wie einem Webbrowser) angezeigt wird. Sie übergeben den Wert '/' an @app.route(), um anzugeben, dass diese Funktion auf Webanfragen bezüglich der URL / reagiert. Dabei handelt es sich um die Haupt-URL.

      Die Anzeigefunktion hello() gibt die Zeichenfolge 'Hello, World!' als Antwort zurück.

      Speichern und schließen Sie die Datei.

      Um Ihre Webanwendung auszuführen, teilen Sie Flask zuerst mit, wo sich die Anwendung (in Ihrem Fall die Datei hello.py) befindet. Nutzen Sie dazu die Umgebungsvariable FLASK_APP:

      Führen Sie sie dann im Entwicklungsmodus mit der Umgebungsvariable FLASK_ENV aus:

      • export FLASK_ENV=development

      Führen Sie die Anwendung abschließend mit dem Befehl flask run aus:

      Sobald die Anwendung ausgeführt wird, sieht die Ausgabe in etwa wie folgt aus:

      Output

      * Serving Flask app "hello" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 813-894-335

      Die vorangehende Ausgabe weist mehrere Informationen auf, zum Beispiel:

      • Der Name der Anwendung, die Sie ausführen.
      • Die Umgebung, in der die Anwendung ausgeführt wird.
      • Debug mode: on bedeutet, dass der Flask-Debugger ausgeführt wird. Das ist bei der Entwicklung nützlich, da wir detaillierte Fehlermeldungen erhalten, wenn etwas schiefgeht. Das erleichtert die Fehlerbehebung.
      • Die Anwendung wird lokal an der URL http://127.0.0.1:5000/ ausgeführt; 127.0.0.1​​ ist die IP-Adresse, die den localhost Ihres Computers darstellt, während :5000 die Portnummer ist.

      Öffnen Sie einen Browser und geben Sie die URL http://127.0.0.1:5000/ ein; Sie erhalten die Zeichenfolge Hello, World! als Antwort. Das bestätigt, dass Ihre Anwendung erfolgreich ausgeführt wird.

      Warnung Flask verwendet einen einfachen Webserver, um unsere Anwendung in einer Entwicklungsumgebung bereitzustellen. Das bedeutet auch, dass der Flask-Debugger ausgeführt wird, um die Erkennung von Fehlern zu erleichtern. Dieser Entwicklungsserver sollte nicht in Produktionsbereitstellungen verwendet werden. Weitere Informationen finden Sie auf der Seite Deploymment Options in der Flask-Dokumentation. Sie können sich auch dieses Flask-Bereitstellungs-Tutorial ansehen.

      Sie können den Entwicklungsserver nun im Terminal laufen lassen und ein anderes Terminalfenster öffnen. Gehen Sie zum Projektordner, in dem sich hello.py befindet, aktivieren Sie die virtuelle Umgebung, setzen Sie die Umgebungsvariablen FLASK_ENV und FLASK_APP und fahren Sie mit den nächsten Schritten fort. (Diese Befehle sind in diesem Schritt zuvor aufgelistet.)

      Anmerkung: Beim Öffnen eines neuen Terminals ist es wichtig, die virtuelle Umgebung zu aktivieren und die Umgebungsvariablen FLASK_ENV und FLASK_APP festzulegen.

      Wenn bereits ein Entwicklungsserver einer Flask-Anwendung ausgeführt wird, ist es nicht möglich, eine weitere Flask-Anwendung mit dem gleichen Befehl flask run auszuführen. Das liegt daran, dass flask run die Portnummer 5000 standardmäßig verwendet. Sobald sie vergeben ist, ist sie nicht mehr zur Ausführung einer weiteren Anwendung verfügbar. Sie erhalten einen Fehler, der dem folgenden ähnelt:

      Output

      OSError: [Errno 98] Address already in use

      Um dieses Problem zu lösen, stoppen Sie entweder den gerade ausgeführten Server mit STRG+C und führen flask run erneut aus. Wenn Sie beide Server gleichzeitig ausführen möchten, können Sie eine andere Portnummer an das Argument -p übergeben. Um zum Beispiel eine weitere Anwendung an Port 5001 auszuführen, verwenden Sie den folgenden Befehl:

      Sie verfügen nun über eine kleine Flask-Webabwendung. Sie haben Ihre Anwendung ausgeführt und Informationen im Webbrowser angezeigt. Als Nächstes nutzen Sie in Ihrer Anwendung HTML-Dateien.

      Schritt 3 — Verwenden von HTML-Vorlagen

      Derzeit zeigt Ihre Anwendung nur eine einfache Meldung ohne HTML an. Webanwendungen nutzen HTML hauptsächlich zum Anzeigen von Informationen für den Besucher. Daher arbeiten Sie nun an der Einbindung von HTML-Dateien in Ihre App, die im Webbrowser angezeigt werden können.

      Flask bietet eine render_template()-Hilfsfunktion, die eine Verwendung der Jinja-Vorlagen-Engine ermöglicht. Dadurch wird das Verwalten von HTML wesentlich erleichtert, da Ihr HTML-Code in .html-Dateien geschrieben und in Ihrem HTML-Code Logik verwendet wird. Sie werden diese HTML-Dateien (templates) zum Erstellen aller Ihrer Anwendungsseiten nutzen. Dazu gehören zum Beispiel die Hauptseite, auf der Sie die aktuellen Blogbeiträge anzeigen, die Seite des Blogbeitrags, die Seite, auf der Benutzer einen neuen Beitrag hinzufügen können, und so weiter.

      In diesem Schritt erstellen Sie Ihre Flask-Hauptanwendung in einer neuen Datei.

      Verwenden Sie zuerst in Ihrem Verzeichnis flask_blog nano oder Ihren bevorzugten Editor, um Ihre Datei app.py zu erstellen und zu bearbeiten. Diese wird den gesamten Code enthalten, den Sie zum Erstellen der Bloganwendung verwenden.

      In dieser neuen Datei importieren Sie das Flask-Objekt, um eine Flask-Anwendungsinstanz zu erstellen, wie Sie dies zuvor getan haben. Außerdem importieren Sie die render_template()-Hilfsfunktion, mit der Sie im Ordner templates, den Sie gleich erstellen werden, vorhandene HTML-Vorlagendateien rendern können. Die Datei wird eine Einzelansichtfunktion aufweisen, die für die Bearbeitung von Anfragen an die Hauptroute / zuständig ist​​. Fügen Sie den folgenden Inhalt hinzu:

      flask_blog/app.py

      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      @app.route('/')
      def index():
          return render_template('index.html')
      

      Die Ansichtsfunktion index() gibt das Ergebnis des Aufrufs von render_template() mit index.html als Argument zurück. Das teilt render_template() mit, nach einer Datei namens index.html im Ordner templates zu suchen. Sowohl der Ordner als auch die Datei existieren noch nicht. Sie erhalten einen Fehler, wenn Sie die Anwendung an dieser Stelle ausführen würden. Sie werden sie trotzdem ausführen, damit Sie sich mit dieser häufig auftretenden Ausnahme vertraut machen können. Dann beheben Sie die Ausnahme durch Erstellung des erforderlichen Ordners und der Datei.

      Speichern und schließen Sie die Datei.

      Stoppen Sie den Entwicklungsserver im anderen Terminal, in dem die Anwendung hello ausgeführt wird, mit STRG+C.

      Bevor Sie die Anwendung ausführen, stellen Sie sicher, dass Sie den Wert für die Umgebungsvariable FLASK_APP richtig angeben, da Sie nicht mehr die Anwendung hello verwenden:

      • export FLASK_APP=app
      • flask run

      Durch Öffnen der URL http://127.0.0.1:5000/ in Ihrem Browser wird die Debugger-Seite angezeigt, die Ihnen mitteilt, dass die Vorlage index.html nicht gefunden wurde. Die Hauptzeile im Code, die für diesen Fehler verantwortlich war, wird hervorgehoben. In diesem Fall ist es die Zeile return render_template('index.html')​.

      Wenn Sie auf diese Zeile klicken, zeigt der Debugger weiteren Code an, damit Sie über mehr Kontext zum Lösen des Problems verfügen.

      Der Flask-Debugger

      Um diesen Fehler zu beheben, erstellen Sie ein Verzeichnis namens templates in Ihrem Verzeichnis flask_blog. Öffnen Sie dann darin eine Datei namens index.html zum Bearbeiten:

      • mkdir templates
      • nano templates/index.html

      Fügen Sie anschließend in index.html den folgenden HTML-Code hinzu:

      flask_blog/templates/index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>FlaskBlog</title>
      </head>
      <body>
         <h1>Welcome to FlaskBlog</h1>
      </body>
      </html>
      

      Speichern Sie die Datei und navigieren Sie in Ihrem Browser erneut zu http://127.0.0.1:5000/ oder aktualisieren Sie die Seite. Dieses Mal sollte der Browser den Text Welcome to FlaskBlog in einem <h1>-Tag anzeigen.

      Zusätzlich zum Ordner templates weisen Flask-Webanwendungen typischerweise einen Ordner static zum Hosting von statischen Dateien auf, wie z. B. CSS-Dateien, JavaScript-Dateien und Bilder, die die Anwendung verwendet.

      Sie können eine Stylesheetdatei style.css erstellen, um CSS Ihrer Anwendung hinzuzufügen. Erstellen Sie zuerst ein Verzeichnis namens static im Hauptverzeichnis flask_blog:

      Erstellen Sie dann ein anderes Verzeichnis namens css im Verzeichnis static, um .css-Dateien zu hosten. Das dient normalerweise der Organisation von statischen Dateien in dedizierten Ordnern. So befinden sich JavaScript-Dateien meist in einem Verzeichnis namens js, Bilder in einem Verzeichnis namens images (oder img) und so weiter. Der folgende Befehl erstellt das Verzeichnis css im Verzeichnis static:

      Öffnen Sie dann eine style.css-Datei im css-Verzeichnis zur Bearbeitung:

      • nano static/css/style.css

      Fügen Sie Ihrer Datei style.css die folgende CSS-Regel hinzu:

      flask_blog/static/css/style.css

      h1 {
          border: 2px #eee solid;
          color: brown;
          text-align: center;
          padding: 10px;
      }
      

      Der CSS-Code fügt einen Rahmen hinzu, ändert die Farbe in braun, zentriert den Text und fügt <h1>-Tags ein wenig Abstand hinzu.

      Speichern und schließen Sie die Datei.

      Öffnen Sie als Nächstes die Vorlagendatei index.html zur Bearbeitung:

      • nano templates/index.html

      Sie fügen der Datei style.css im Abschnitt <head> der Vorlagendatei index.html einen Link hinzu:

      flask_blog/templates/index.html

      . . .
      <head>
          <meta charset="UTF-8">
          <link rel="stylesheet" href="https://www.digitalocean.com/{{ url_for("static', filename="css/style.css") }}">
          <title>FlaskBlog</title>
      </head>
      . . .
      

      Hier verwenden Sie die Hilfsfunktion url_for(), um den entsprechenden Ort der Datei zu generieren. Das erste Argument gibt an, dass Sie mit einer statischen Datei verknüpfen, und das zweite Argument ist der Pfad der Datei im static-Verzeichnis.

      Speichern und schließen Sie die Datei.

      Wenn Sie die Indexseite Ihrer Anwendung aktualisieren, werden Sie bemerken, dass der Text Welcome to FlaskBlog jetzt braun, zentriert und in einen Rahmen eingeschlossen ist.

      Sie können die CSS-Sprache verwenden, um den Stil der Anwendung zu verändern und nach Ihrem eigenen Geschmack attraktiver zu gestalten. Wenn Sie jedoch kein Webdesigner sind oder nicht mit CSS vertraut sind, können Sie das Bootstrap-Toolkit verwenden, das anwenderfreundliche Komponenten für die Anpassung des Stils Ihrer Anwendung bereitstellt. In diesem Projekt verwenden wir Bootstrap.

      Vielleicht haben Sie sich schon gedacht, dass die Einrichtung einer weiteren HTML-Vorlage eine Wiederholung des größten Teils des HTML-Codes bedeutet, den Sie in der Vorlage index.html bereits geschrieben haben. Sie können mithilfe einer Basisvorlagen-Datei, aus der alle Ihre HTML-Dateien erben werden, unnötige Codewiederholungen vermeiden. Weitere Informationen finden Sie in Vorlagenvererbung in Jinja.

      Um eine Basisvorlage einzurichten, erstellen Sie zuerst eine Datei namens base.html in Ihrem Verzeichnis templates:

      Geben Sie in Ihrer Vorlage base.html den folgenden Code ein:

      flask_blog/templates/base.html

      <!doctype html>
      <html lang="en">
        <head>
          <!-- Required meta tags -->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      
          <!-- Bootstrap CSS -->
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      
          <title>{% block title %} {% endblock %}</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-md navbar-light bg-light">
              <a class="navbar-brand" href="https://www.digitalocean.com/{{ url_for('index')}}">FlaskBlog</a>
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                  <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarNav">
                  <ul class="navbar-nav">
                  <li class="nav-item active">
                      <a class="nav-link" href="https://www.digitalocean.com/#">About</a>
                  </li>
                  </ul>
              </div>
          </nav>
          <div class="container">
              {% block content %} {% endblock %}
          </div>
      
          <!-- Optional JavaScript -->
          <!-- jQuery first, then Popper.js, then Bootstrap JS -->
          <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
          <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        </body>
      </html>
      

      Speichern und schließen Sie die Datei, sobald Sie die Bearbeitung abgeschlossen haben.

      Der größte Teil des Codes im vorherigen Block ist Standard-HTML und Code, der für Bootstrap benötigt wird. Die <meta>-Tags bieten Informationen für den Webbrowser, das <link>-Tag verknüpft die Bootstrap CSS-Dateien und die <script>-Tags sind Links zu JavaScript-Code, der einige zusätzliche Bootstrap-Funktionen bietet. Konsultieren Sie die Bootstrap-Dokumentation, um mehr zu erfahren.

      Die folgenden hervorgehobenen Teile sind jedoch für die Jinja-Vorlagen-Engine spezifisch:

      • {% block title %} {% endblock %}: Ein Block, der als Platzhalter für einen Titel dient; Sie werden ihn später in anderen Vorlagen nutzen, um einen benutzerdefinierten Titel für jede Seite in Ihrer Anwendung zu vergeben, ohne den gesamten Abschnitt <head> neu schreiben zu müssen.
      • {{ url_for('index')}}: Ein Funktionsaufruf, der die URL für die Ansichtsfunktion index() zurückgibt. Das unterscheidet sich von dem vorherigen Aufruf url_for(), den Sie verwendet haben, um eine statische CSS-Datei zu verknüpfen. Er weist nur ein Argument auf, bei dem es sich um den Namen der Ansichtsfunktion handelt, und verknüpft mit der Route, die mit der Funktion verbunden ist (anstelle einer statischen Datei).
      • {% block content %} {% endblock %}: Ein weiterer Block, der durch Inhalt ersetzt wird, je nach der untergeordneten Vorlage (Vorlagen, die von base.html erben), die ihn überschreiben wird.

      Nachdem Sie nun über eine Basisvorlage verfügen, können Sie sie mithilfe von Vererbung nutzen. Öffnen Sie die Datei index.html:

      • nano templates/index.html

      Ersetzen Sie die Inhalte mit Folgendem:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
      {% endblock %}
      

      In dieser neuen Version der Vorlage index.html verwenden Sie das {% extends %}-Tag, um von der Vorlage base.html zu erben. Dann erweitern Sie sie, indem Sie den content-Block in der Basisvorlage durch das ersetzen, was innerhalb des content-Blocks im vorherigen Codeblock enthalten ist.

      Dieser content-Block enthält ein <h1>-Tag mit dem Text Welcome to FlaskBlog in einem title-Block, der wiederum den ursprünglichen title-Block in der Vorlage base.html durch den Text Welcome to FlaskBlog ersetzt. So müssen Sie den gleichen Text nicht zweimal wiederholen, da er sowohl als Titel für die Seite als auch Überschrift dienen kann, die unterhalb der Navigationsleiste erscheint, geerbt von der Basisvorlage.

      Außerdem bietet Ihnen Vererbung mit Vorlagen die Möglichkeit, den HTML-Code, über den Sie in anderen Vorlagen verfügen (in diesem Fall base.html), wiederzuverwenden, ohne ihn jedes Mal wiederholen zu müssen, wenn er benötigt wird.

      Speichern und schließen Sie die Datei und aktualisieren Sie die Indexseite in Ihrem Browser. Sie werden Ihre Seite mit einer Navigationsleiste und einem formatierten Titel sehen.

      Indexseite mit Bootstrap

      Sie haben in Flask HTML-Vorlagen und statische Dateien verwendet. Außerdem haben Sie Bootstrap genutzt, um die Optik Ihrer Seite und eine Basisvorlage anzupassen, um Codewiederholungen zu vermeiden. Im nächsten Schritt richten Sie eine Datenbank ein, die Ihre Anwendungsdaten speichern wird.

      Schritt 4 — Einrichten der Datenbank

      In diesem Schritt richten Sie eine Datenbank zur Speicherung von Daten ein, d. h. die Blogbeiträge für Ihre Anwendung. Außerdem befüllen Sie die Datenbank mit einigen Beispieleinträgen.

      Sie werden eine SQLite-Datenbankdatei verwenden, um Ihre Daten zu speichern, da das Modul sqlite3, das wir zur Interaktion mit der Datenbank verwenden, in der Python-Standardbibliothek einsatzbereit ist. Weitere Informationen zu SQLite finden Sie in diesem Tutorial.

      Da Daten in SQLite in Tabellen und Spalten gespeichert werden und Ihre Daten hauptsächlich aus Blogbeiträgen bestehen, müssen Sie zunächst eine Tabelle namens posts mit den erforderlichen Spalten erstellen. Sie werden eine .sql-Datei erstellen, die SQL-Befehle enthält, um die Tabelle posts mit einigen Spalten zu erstellen. Dann verwenden Sie diese Datei zur Erstellung der Datenbank.

      Öffnen Sie eine Datei namens schema.sql in Ihrem Verzeichnis flask_blog:

      Geben Sie in dieser Datei die folgenden SQL-Befehle ein:

      flask_blog/schema.sql

      DROP TABLE IF EXISTS posts;
      
      CREATE TABLE posts (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          title TEXT NOT NULL,
          content TEXT NOT NULL
      );
      

      Speichern und schließen Sie die Datei.

      Der erste SQL-Befehl ist DROP TABLE IF EXISTS posts; – damit werden alle bereits vorhandenen Tabellen namens posts gelöscht, damit Sie nicht durcheinander kommen. Beachten Sie, dass bei Nutzung dieser SQL-Befehle stets alle in der Datenbank vorhandenen Inhalte gelöscht werden. Schreiben Sie also keine wichtigen Inhalte in die Webanwendung, bis Sie dieses Tutorial abgeschlossen und mit dem Endergebnis experimentiert haben. Als Nächstes verwenden Sie CREATE TABLE posts zur Erstellung der Tabelle posts mit den folgenden Spalten:

      • id: Eine ganze Zahl, die einen Primärschlüssel darstellt; diesem wird von der Datenbank für jeden Eintrag (also jeden Blogbeitrag) ein eindeutiger Wert zugewiesen.
      • created: Die Zeit, zu der der Blogbeitrag erstellt wurde. NOT NULL bedeutet, dass diese Spalte nicht leer sein darf und der DEFAULT-Wert der CURRENT_TIMESTAMP-Wert ist. Das ist der Zeitpunkt, zu dem der Blogbeitrag der Datenbank hinzugefügt wurde. Genau wie bei id müssen Sie keinen Wert für diese Spalte angeben, da er automatisch ausgefüllt wird.
      • title: Der Titel des Beitrags.
      • content: Der Inhalt des Beitrags.

      Nachdem Sie in der Datei schema.sql über ein SQL-Schema verfügen, verwenden Sie es nun zur Erstellung der Datenbank mit einer Python-Datei, die eine SQLite .db-Datenbank generieren wird. Öffnen Sie mit Ihrem bevorzugten Editor eine Datei namens init_db.py im Verzeichnis flask_blog:

      Und fügen Sie dann den folgenden Code hinzu.

      flask_blog/init_db.py

      import sqlite3
      
      connection = sqlite3.connect('database.db')
      
      
      with open('schema.sql') as f:
          connection.executescript(f.read())
      
      cur = connection.cursor()
      
      cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)",
                  ('First Post', 'Content for the first post')
                  )
      
      cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)",
                  ('Second Post', 'Content for the second post')
                  )
      
      connection.commit()
      connection.close()
      

      Zuerst importieren Sie das Modul sqlite3 und öffnen dann eine Verbindung zu einer Datenbankdatei namens database.db, die erstellt wird, sobald Sie die Python-Datei ausführen. Dann verwenden Sie die Funktion open(), um die Datei schema.sql zu öffnen. Als Nächstes führen Sie ihre Inhalte mit der Methode executescript() aus, die mehrere SQL-Anweisungen auf einmal ausführt. Dadurch wird die Tabelle posts erstellt. Sie erstellen ein Cursor-Objekt, mit dem Sie die Methode execute() verwenden können, um zwei INSERT SQL-Anweisungen auszuführen und Ihrer Tabelle posts zwei Blogbeiträge hinzuzufügen. Schließlich committen Sie die Änderungen und schließen die Verbindung.

      Speichern und schließen Sie die Datei und führen Sie sie dann im Terminal mit dem python-Befehl aus:

      Sobald die Ausführung der Datei beendet ist, wird in Ihrem Verzeichnis flask_blog eine neue Datei namens database.db angezeigt. Das bedeutet, dass Sie Ihre Datenbank erfolgreich eingerichtet haben.

      Im nächsten Schritt rufen Sie die Beiträge ab, die Sie in Ihre Datenbank eingefügt haben, und zeigen sie auf der Homepage Ihrer Anwendung an.

      Schritt 5 — Anzeigen aller Beiträge

      Nachdem Sie Ihre Datenbank eingerichtet haben, können Sie die Ansichtsfunktion index() nun so ändern, dass sie alle Beiträge anzeigt, die Sie in Ihrer Datenbank haben.

      Öffnen Sie die Datei app.py, um die folgenden Änderungen vorzunehmen:

      Als erste Änderung importieren Sie das Modul sqlite3 an den Anfang der Datei:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template
      
      . . .
      

      Als Nächstes erstellen Sie eine Funktion, die eine Datenbankverbindung erstellt und zurückgibt. Fügen Sie sie direkt nach den Importen hinzu:

      flask_blog/app.py

      . . .
      from flask import Flask, render_template
      
      def get_db_connection():
          conn = sqlite3.connect('database.db')
          conn.row_factory = sqlite3.Row
          return conn
      
      . . .
      

      Diese get_db_connection()-Funktion öffnet eine Verbindung zur Datenbankdatei database.db und legt dann das Attribut row_factory auf sqlite3. Row fest, damit Sie namenbasierten Zugriff auf Spalten erhalten. Das bedeutet, dass die Datenbankverbindung Zeilen zurückgibt, die sich wie reguelmäßige Python-Wörterbücher verhalten. Schließlich gibt die Funktion das Verbindungsobjekt conn zurück, das Sie zum Zugriff auf die Datenbank verwenden werden.

      Nach der Definition der get_db_connection()-Funktion ändern Sie die Funktion index(), damit sie wie folgt aussieht:

      flask_blog/app.py

      . . .
      
      @app.route('/')
      def index():
          conn = get_db_connection()
          posts = conn.execute('SELECT * FROM posts').fetchall()
          conn.close()
          return render_template('index.html"https://www.digitalocean.com/, posts=posts)
      

      In dieser neuen Version der Funktion index() öffnen Sie zuerst mit der Funktion get_db_connection(), die Sie zuvor definiert haben, eine Datenbankverbindung. Dann führen Sie eine SQL-Abfrage aus, um alle Einträge aus der Tabelle posts auszuwählen. Sie implementieren die Methode fetchall(), um alle Zeilen des Abfrageergebnisses abzurufen. Dadurch wird eine Liste der Beiträge, die Sie der Datenbank im vorherigen Schritt hinzugefügt haben, zurückgegeben.

      Sie schließen die Datenbankverbindung mit dem Befehl close() und geben das Ergebnis vom Rendern der Vorlage index.html zurück. Außerdem übergeben Sie das Objekt posts als Argument, das die Ergebnisse enthält, die Sie aus der Datenbank erhalten haben. Das ermöglicht Ihnen, auf die Blogbeiträge in der Vorlage index.html zuzugreifen.

      Speichern und schließen Sie die Datei app.py nach Vornahme der Änderungen.

      Nachdem Sie die Beiträge, die Sie von der Datenbank abgerufen haben, an die Vorlage index.html übergeben haben, können Sie eine for-Schleife verwenden, um einzelne Beiträge auf Ihrer Indexseite anzuzeigen.

      Öffnen Sie die Datei index.html:

      • nano templates/index.html

      Ändern Sie sie dann so, dass sie wie folgt aussieht:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
          {% for post in posts %}
              <a href="https://www.digitalocean.com/#">
                  <h2>{{ post['title'] }}</h2>
              </a>
              <span class="badge badge-primary">{{ post['created'] }}</span>
              <hr>
          {% endfor %}
      {% endblock %}
      

      Hier ist die Syntax {% for post in posts %} eine Jinja-for-Schleife, die einer Python-for-Schleife ähnelt; sie muss allerdings später mit der {% endfor %}-Syntax geschlossen werden. Sie verwenden diese Syntax, um für jedes Element in der Liste posts, die von der Funktion index() in der Zeile return render_template('index.html', posts=posts) übergeben wurde, eine Schleife zu durchlaufen. Innerhalb dieser for-Schleife zeigen Sie den Titel des Beitrags in einer <h2>-Überschrift in einem <a>-Tag an (Sie verwenden dieses Tag später, um Verknüpfungen für die einzelnen Beiträge zu erstellen).

      Sie zeigen den Titel an mit einem Literal-Variablen-Trennzeichen ({{ ... }}). Denken Sie daran, dass post ein wörterbuchähnliches Objekt sein wird, damit Sie mit post['title'] auf den Beitragstitel zugreifen können. Außerdem zeigen Sie mit der gleichen Methode das Erstellungsdatum des Beitrags an.

      Sobald Sie die Bearbeitung der Datei abgeschlossen haben, speichern und schließen Sie sie. Navigieren Sie dann in Ihrem Browser zur Indexseite. Sie werden die beiden Beiträge, die Sie der Datenbank hinzugefügt haben, auf Ihrer Seite sehen.

      Indexseite mit den angezeigten Beiträgen

      Nachdem Sie die Ansichtsfunktion index() modifiziert haben, um alle Beiträge, die Sie in der Datenbank haben, auf der Homepage Ihrer Anwendung anzeigen, zeigen Sie nun jeden Beitrag auf einer einzelnen Seite an und ermöglichen es Benutzern, Links zu den einzelnen Beiträgen zu nutzen.

      Schritt 6 — Anzeigen eines einzelnen Beitrags

      In diesem Schritt erstellen Sie eine neue Flask-Route mit einer Ansichtsfunktion und eine neue HTML-Vorlage zur Anzeige eines einzelnen Blogeintrags anhand seiner ID.

      Am Ende dieses Schritts wird die URL http://127.0.0.1:5000/1 eine Seite sein, die den ersten Beitrag anzeigt (weil dieser die ID 1 hat). Die URL http://127.0.0.1:5000/ID zeigt den Beitrag mit der zugehörigen ID-Nummer an, so sie vorhanden ist.

      Öffnen Sie app.py zum Bearbeiten:

      Da Sie in diesem Projekt noch an unterschiedlichen Stellen einen Blogeintrag aus der Datenbank abrufen müssen, erstellen Sie eine Standalone-Funktion namens get_post(). Sie können sie aufrufen, indem Sie ihr eine ID übergeben, und erhalten den Blogbeitrag zurück, der mit der bereitgestellten ID verknüpft ist; oder Sie sorgen dafür, dass Flask mit einer 404 Nicht gefunden-Nachricht antwortet, wenn der Blogbeitrag nicht existiert.

      Um mit einer 404-Seite zu antworten, müssen Sie die Funktion abort() aus der Werkzeug-Bibliothek, die zusammen mit Flask installiert wurde, am Anfang der Datei importieren:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template
      from werkzeug.exceptions import abort
      
      . . .
      

      Fügen Sie die Funktion get_post() direkt nach der Funktion get_db_connection() hinzu, die Sie im vorherigen Schritt erstellt haben:

      flask_blog/app.py

      . . .
      
      def get_db_connection():
          conn = sqlite3.connect('database.db')
          conn.row_factory = sqlite3.Row
          return conn
      
      
      def get_post(post_id):
          conn = get_db_connection()
          post = conn.execute('SELECT * FROM posts WHERE id = ?',
                              (post_id,)).fetchone()
          conn.close()
          if post is None:
              abort(404)
          return post
      
      . . .
      

      Diese neue Funktion verfügt über ein post_id-Argument, das bestimmt, welcher Blogbeitrag zurückgegeben wird.

      Innerhalb der Funktion verwenden Sie die Funktion get_db_connection() zum Öffnen einer Datenbankverbindung und Ausführen einer SQL-Abfrage, um den Blogbeitrag zu erhalten, der mit dem angegebenen post_id-Wert verknüpft ist. Sie fügen die Methode fetchone() hinzu, um das Ergebnis zu erhalten und in der Variable post zu speichern. Dann schließen Sie die Verbindung. Wenn die Variable post den Wert None (Keine) hat, was bedeutet, dass in der Datenbank kein Ergebnis gefunden wird, verwenden Sie die Funktion abort(), die Sie zuvor importiert haben, um mit einem 404-Fehlercode zu reagieren; die Ausführung der Funktion wird beendet. Wenn jedoch ein Beitrag gefunden wurde, geben Sie den Wert der Variable post zurück.

      Fügen Sie als Nächstes die folgende Ansichtsfunktion am Ende der Datei app.py hinzu:

      flask_blog/app.py

      . . .
      
      @app.route('/<int:post_id>')
      def post(post_id):
          post = get_post(post_id)
          return render_template('post.html', post=post)
      

      In dieser neuen Ansichtsfunktion fügen Sie eine Variablenregel <int:post_id> hinzu, um anzugeben, dass der Teil nach dem Schrägstrich (/) eine positive ganze Zahl ist (markiert mit dem int-Konverter), die Sie in Ihrer Ansichtsfunktion aufrufen müssen. Flask erkennt das und übergibt ihren Wert an das Schlüsselwortargument post_id Ihrer post()-Ansichtsfunktion. Dann verwenden Sie die Funktion get_post(), um den Blogbeitrag abzurufen, der mit der angegebenen ID verknüpft ist, und speichern das Ergebnis in der Variable post, die Sie an eine post.html-Vorlage, die Sie bald erstellen werden, übergeben.

      Speichern Sie die Datei app.py und öffnen Sie eine neue post.html-Vorlagendatei zur Bearbeitung:

      Geben Sie in dieser neuen post.html-Datei den folgenden Code ein. Das wird ähnlich aussehen wie bei der Datei index.html; es wird jedoch nur ein einziger Blogbeitrag angezeigt, und zudem mit dem Inhalt des Beitrags:

      flask_blog/templates/post.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h2>{% block title %} {{ post['title'] }} {% endblock %}</h2>
          <span class="badge badge-primary">{{ post['created'] }}</span>
          <p>{{ post['content'] }}</p>
      {% endblock %}
      

      Sie fügen den title-Block hinzu, den Sie in der Vorlage base.html definiert haben, um den Titel der Seite an den Titel des Beitrags anzupassen, der gleichzeitig in einer <h2>-Überschrift angezeigt wird.

      Speichern und schließen Sie die Datei.

      Sie können nun zu den folgenden URLs navigieren, um die beiden Beiträge, die Sie in Ihrer Datenbank haben, sowie eine Seite anzuzeigen, die dem Benutzer mitteilt, dass der angeforderte Blogbeitrag nicht gefunden wurde (da bisher kein Beitrag mit der ID-Nummer 3 vorhanden ist):

      http://127.0.0.1:5000/1
      http://127.0.0.1:5000/2
      http://127.0.0.1:5000/3
      

      Zurück auf der Indexseite verknüpfen Sie jeden Titel eines Beitrags mit seiner jeweiligen Seite. Dazu verwenden Sie die Funktion url_for(). Öffnen Sie zuerst die Vorlage index.html zur Bearbeitung:

      • nano templates/index.html

      Ändern Sie dann den Wert des Attributs href von # in {{ url_for('post', post_id=post['id']) }}, damit die for-Schleife genau wie folgt aussieht:

      flask_blog/templates/index.html

      {% for post in posts %}
          <a href="https://www.digitalocean.com/{{ url_for('post', post_id=post['id']) }}">
              <h2>{{ post['title'] }}</h2>
          </a>
          <span class="badge badge-primary">{{ post['created'] }}</span>
          <hr>
      {% endfor %}
      

      Hier übergeben Sie 'post' an die Funktion url_for() als erstes Argument. Das ist der Name der post()-Ansichtsfunktion; da sie ein post_id-Argument akzeptiert, geben Sie den Wert post['id']. Die Funktion url_for() gibt für jeden Beitrag auf Grundlage seiner ID die richtige URL zurück.

      Speichern und schließen Sie die Datei.

      Die Links auf der Indexseite funktionieren nun wie erwartet. Damit haben Sie die Erstellung des Teils der Anwendung abgeschlossen, der für die Anzeige der Blogbeiträge in Ihrer Datenbank verantwortlich ist. Als Nächstes fügen Sie die Möglichkeit zur Erstellung, Bearbeitung und Löschung von Blogbeiträgen zu Ihrer Anwendung hinzu.

      Schritt 7 — Bearbeiten von Beiträgen

      Nachdem Sie die Anzeige der Blogbeiträge, die in der Datenbank vorhanden sind, in der Webanwendung abgeschlossen haben, müssen Sie es den Benutzern Ihrer Anwendung nun ermöglichen, neue Blogbeiträge zu schreiben und der Datenbank hinzuzufügen, vorhandene Beiträge zu bearbeiten und unnötige Blogbeiträge zu löschen.

      Erstellen eines neuen Beitrags

      Bislang verfügen Sie über eine Anwendung, die die Beiträge in Ihrer Datenbank anzeigt, aber keine Möglichkeit zum Hinzufügen eines neuen Beitrags, es sei denn, Sie verbinden sich direkt mit der SQLite-Datenbank und fügen manuell einen Beitrag hinzu. In diesem Abschnitt richten Sie eine Seite ein, auf der Sie einen Beitrag erstellen können, indem Sie den Titel und den Inhalt bereitstellen.

      Öffnen Sie die Datei app.py zur Bearbeitung:

      Zuerst importieren Sie Folgendes aus dem Flask-Framework:

      • Das globale request-Objekt für den Zugriff auf eingehende Anfragedaten, die über ein HTML-Formular übermittelt werden.
      • Die Funktion url_for() zur Erstellung von URLs.
      • Die flash()-Funktion zur Anzeige einer Meldung, wenn eine Anfrage verarbeitet wird.
      • Die Funktion redirect() zum Umleiten des Clients an einen anderen Ort.

      Fügen Sie die Importe wie folgt in Ihre Datei ein:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template, request, url_for, flash, redirect
      from werkzeug.exceptions import abort
      
      . . .
      

      Die flash()-Funktion speichert geflashte Nachrichten in der Browsersitzung des Clients, was die Einrichtung eines geheimen Schlüssels erfordert. Dieser geheime Schlüssel dient zur Sicherung von Sitzungen, sodass sich Flask Informationen von einer Anfrage zu einer anderen merken kann, zum Beispiel beim Navigieren von der Seite für neue Beiträge zur Indexseite. Der Benutzer kann auf die in der Sitzung gespeicherten Daten zugreifen, sie aber nicht ändern, es sei denn, er verfügt über den geheimen Schlüssel; das bedeutet, dass Sie niemandem Zugriff auf Ihren geheimen Schlüssel gewähren dürfen. Weitere Informationen finden Sie in der Flask-Dokumentation für Sitzungen.

      Um einen geheimen Schlüssel einzurichten, fügen Sie Ihrer Anwendung eine SECRET_KEY-Konfiguration über das Objekt app.config hinzu. Fügen Sie sie unmittelbar nach der app-Definition hinzu, bevor Sie die Ansichtsfunktion index() definieren:

      flask_blog/app.py

      . . .
      app = Flask(__name__)
      app.config['SECRET_KEY'] = 'your secret key'
      
      
      @app.route('/')
      def index():
          conn = get_db_connection()
          posts = conn.execute('SELECT * FROM posts').fetchall()
          conn.close()
          return render_template('index.html', posts=posts)
      
      . . .
      

      Denken Sie daran, dass der geheime Schlüssel eine lange Zufallszeichenfolge sein sollte.

      Nach der Einrichtung eines geheimen Schlüssels erstellen Sie eine Ansichtsfunktion, die eine Vorlage rendern wird, um ein Formular anzuzeigen, das Sie zum Erstellen eines neuen Blogbeitrags ausfüllen können. Fügen Sie diese neue Funktion am Ende der Datei hinzu:

      flask_blog/app.py

      . . .
      
      @app.route('/create', methods=('GET', 'POST'))
      def create():
          return render_template('create.html')
      

      Dadurch wird eine /create-Route erstellt, die sowohl GET- als auch POST-Anfragen akzeptiert. GET-Anfragen werden standardmäßig akzeptiert. Um auch POST-Anfragen zu akzeptieren, die vom Browser beim Übermitteln von Formularen gesendet werden, übergeben Sie ein Tupel mit den akzeptierten Arten von Anfragen an das methods-Argument des @app.route()-Decorators.

      Speichern und schließen Sie die Datei.

      Um die Vorlage zu erstellen, öffnen Sie eine Datei namens create.html in Ihrem Ordner templates:

      • nano templates/create.html

      Fügen Sie in dieser neuen Datei den folgenden Code hinzu:

      flask_blog/templates/create.html

      {% extends 'base.html' %}
      
      {% block content %}
      <h1>{% block title %} Create a New Post {% endblock %}</h1>
      
      <form method="post">
          <div class="form-group">
              <label for="title">Title</label>
              <input type="text" name="title"
                     placeholder="Post title" class="form-control"
                     value="{{ request.form['title'] }}"></input>
          </div>
      
          <div class="form-group">
              <label for="content">Content</label>
              <textarea name="content" placeholder="Post content"
                        class="form-control">{{ request.form['content'] }}</textarea>
          </div>
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      </form>
      {% endblock %}
      

      Der größte Teil dieses Codes ist Standard-HTML. Er zeigt ein Eingabefeld für den Titel des Beitrags, einen Textbereich für den Inhalt des Beitrags und eine Schaltfläche zum Übermitteln des Formulars an.

      Der Wert der Beitragstiteleingabe ist {{ request.form['title'] }} und der Textbereich hat den Wert {{ request.form['content'] }}; das sorgt dafür, dass die eingegebenen Daten nicht verloren gehen, wenn etwas schiefläuft. Wenn Sie zum Beispiel einen langen Beitrag verfassen und vergessen, ihm einen Titel zu geben, wird Ihnen eine Meldung angezeigt, die Sie daran erinnert, dass der Titel erforderlich ist. Das geschieht, ohne dass Sie den geschriebenen Beitrag verlieren, da er im globalen request-Objekt gespeichert wird, auf das Sie in Ihren Vorlagen Zugriff haben.

      Nachdem der Entwicklungsserver ausgeführt wird, verwenden Sie nun Ihren Browser, um zur Route /create zu navigieren:

      http://127.0.0.1:5000/create
      

      Sie werden eine Seite für Create a New Post mit einem Feld für Titel und Inhalt sehen.

      Seite zum Erstellen eines neuen Beitrags

      Dieses Formular übermittelt eine POST-Anfrage an Ihre create()-Funktion. In der Funktion gibt es jedoch noch keinen Code, um eine POST-Anfrage zu bearbeiten; also wird nach dem Ausfüllen und Übermitteln des Formulars nichts geschehen.

      Sie werden die eingehende POST-Anfrage bearbeiten, wenn ein Formular übermittelt wird. Das tun Sie in der create()-Ansichtsfunktion. Sie können die POST-Anfrage separat bearbeiten, indem Sie den Wert von request.method überprüfen. Wenn der Wert auf 'POST' festgelegt ist, bedeutet das, dass die Anfrage eine POST-Anfrage ist. Dann fahren Sie mit dem Extrahieren der übermittelten Daten sowie dem Validieren und Einfügen der Daten in Ihre Datenbank fort.

      Öffnen Sie die Datei app.py zur Bearbeitung:

      Ändern Sie die create()-Ansichtsfunktion, damit sie genau wie folgt aussieht:

      flask_blog/app.py

      . . .
      
      @app.route('/create', methods=('GET', 'POST'))
      def create():
          if request.method == 'POST':
              title = request.form['title']
              content = request.form['content']
      
              if not title:
                  flash('Title is required!')
              else:
                  conn = get_db_connection()
                  conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)',
                               (title, content))
                  conn.commit()
                  conn.close()
                  return redirect(url_for('index'))
      
          return render_template('create.html')
      

      Stellen Sie in der if-Anweisung sicher, dass der darauf folgende Code nur ausgeführt wird, wenn die Anfrage eine POST-Anfrage ist. Nutzen Sie dazu den Vergleich request.method == 'POST'.

      Dann extrahieren Sie den übermittelten Titel und Inhalt aus dem request.from-Objekt, das Ihnen Zugriff auf die Formulardaten in der Anfrage bietet. Wenn der Titel nicht angegeben ist, wäre die Bedingung if not title erfüllt. In dem Fall wird dem Benutzer eine Meldung angezeigt, die ihm mitteilt, dass ein Titel erforderlich ist. Wenn der Titel hingegen angegeben ist, öffnen Sie eine Verbindung mit der Funktion get_db_connection() und fügen den Titel und Inhalt, die Sie erhalten haben, in die Tabelle posts ein.

      Dann übergeben Sie die Änderungen mit „commit“ an die Datenbank und schließen die Verbindung. Nachdem Sie den Blogbeitrag in die Datenbank eingefügt haben, leiten Sie den Client mit der redirect()-Funktion auf die Indexseite um. Dazu übergeben Sie ihr die von der Funktion url_for() mit dem Wert 'index' als Argument generierte URL.

      Speichern und schließen Sie die Datei.

      Navigieren Sie nun mit Ihrem Webbrowser zur Route /create:

      http://127.0.0.1:5000/create
      

      Füllen Sie das Formular mit einem Titel Ihrer Wahl und einem Inhalt aus. Sobald Sie das Formular übermitteln, sehen Sie, dass der neue Beitrag auf der Indexseite aufgelistet wird.

      Schließlich zeigen Sie geflashte Nachrichten an und fügen zur Navigationsleiste in der Vorlage base.html eine Verknüpfung hinzu, um einfachen Zugriff auf diese neue Seite zu ermöglichen. Öffnen Sie die Vorlagendatei:

      Bearbeiten Sie die Datei, indem Sie ein neues <li>-Tag nach dem Link About im Tag <nav> hinzufügen. Fügen Sie dann direkt über dem content-Block eine neue for-Schleife hinzu, um die geflashten Nachrichten unterhalb der Navigationsleiste anzuzeigen. Diese Nachrichten sind in der speziellen Funktion get_flashed_messages() von Flask verfügbar:

      flask_blog/templates/base.html

      <nav class="navbar navbar-expand-md navbar-light bg-light">
          <a class="navbar-brand" href="https://www.digitalocean.com/{{ url_for("index')}}">FlaskBlog</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNav">
              <ul class="navbar-nav">
              <li class="nav-item">
                  <a class="nav-link" href="https://www.digitalocean.com/#">About</a>
              </li>
              <li class="nav-item">
                  <a class="nav-link" href="https://www.digitalocean.com/{{url_for("create')}}">New Post</a>
              </li>
              </ul>
          </div>
      </nav>
      <div class="container">
          {% for message in get_flashed_messages() %}
              <div class="alert alert-danger">{{ message }}</div>
          {% endfor %}
          {% block content %} {% endblock %}
      </div>
      

      Speichern und schließen Sie die Datei. Die Navigationsleiste verfügt nun über ein Element New Post, das mit der Route /create verknüpft ist.

      Bearbeiten eines Beitrags

      Damit Ihr Blog aktuell bleibt, müssen Sie Ihre vorhandenen Beiträge bearbeiten können. Dieser Abschnitt führt Sie durch die Erstellung einer neuen Seite in Ihrer Anwendung, um das Bearbeiten eines Beitrags zu vereinfachen.

      Zuerst fügen Sie der Datei app.py eine neue Route hinzu. Ihre Ansichtsfunktion empfängt die ID des Beitrags, der bearbeitet werden soll; die URL wird im Format /post_id/edit vorliegen, wobei die Variable post_id die ID des Beitrags ist. Öffnen Sie die Datei app.py zur Bearbeitung:

      Fügen Sie als Nächstes die folgende Ansichtsfunktion edit() am Ende der Datei hinzu: Die Bearbeitung eines bestehenden Beitrags ähnelt der Erstellung eines neuen Beitrags. Daher wird diese Ansichtsfunktion ähnlich aussehen wie die create()-Ansichtsfunktion:

      flask_blog/app.py

      . . .
      
      @app.route('/<int:id>/edit', methods=('GET', 'POST'))
      def edit(id):
          post = get_post(id)
      
          if request.method == 'POST':
              title = request.form['title']
              content = request.form['content']
      
              if not title:
                  flash('Title is required!')
              else:
                  conn = get_db_connection()
                  conn.execute('UPDATE posts SET title = ?, content = ?'
                               ' WHERE id = ?',
                               (title, content, id))
                  conn.commit()
                  conn.close()
                  return redirect(url_for('index'))
      
          return render_template('edit.html', post=post)
      

      Der Beitrag, den Sie bearbeiten, wird von der URL bestimmt; Flask übergibt die ID-Nummer an die Funktion edit() über das Argument id. Sie fügen diesen Wert der Funktion get_post() hinzu, um den Beitrag, der mit der bereitgestellten ID verknüpft ist, aus der Datenbank abzurufen. Die neuen Daten kommen in einer POST-Anfrage, die in der Bedingung if request.method == 'POST' verwaltet wird.

      Genau wie bei der Erstellung eines neuen Beitrags extrahieren Sie die Daten zuerst aus dem request.form-Objekt und flashen dann eine Nachricht, wenn der Titel einen leeren Wert hat; andernfalls öffnen Sie eine Datenbankverbindung. Dann aktualisieren Sie die Tabelle posts durch Festlegen eines neuen Titels und eines neuen Inhalts, wobei die ID des Beitrags in der Datenbank gleich der ID ist, die in der URL enthalten war.

      Im Fall einer GET-Anfrage rendern Sie eine Vorlage edit.html, indem Sie die Variable post übergeben, die den zurückgegebenen Wert der Funktion get_post() enthält. Sie tun das, um den bestehenden Titel und Inhalt auf der Bearbeitungsseite anzuzeigen.

      Speichern und schließen Sie die Datei und erstellen Sie dann eine neue edit.html-Vorlage:

      Schreiben Sie in dieser neuen Datei den folgenden Code:

      flask_blog/templates/edit.html

      {% extends 'base.html' %}
      
      {% block content %}
      <h1>{% block title %} Edit "{{ post['title'] }}" {% endblock %}</h1>
      
      <form method="post">
          <div class="form-group">
              <label for="title">Title</label>
              <input type="text" name="title" placeholder="Post title"
                     class="form-control"
                     value="{{ request.form['title'] or post['title'] }}">
              </input>
          </div>
      
          <div class="form-group">
              <label for="content">Content</label>
              <textarea name="content" placeholder="Post content"
                        class="form-control">{{ request.form['content'] or post['content'] }}</textarea>
          </div>
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      </form>
      <hr>
      {% endblock %}
      

      Speichern und schließen Sie die Datei.

      Dieser Code folgt dem gleichen Muster, außer der Syntax {{ request.form['title'] or post['title'] }} und {{ request.form['content'] or post['content'] }}. Das zeigt die in der Anfrage gespeicherten Daten an, so vorhanden. Andernfalls werden die Daten aus der Variablen post angezeigt, die mit den aktuellen Datenbankdaten an die Vorlage übergeben wurde.

      Navigieren Sie nun zur folgenden URL, um den ersten Beitrag zu bearbeiten:

      http://127.0.0.1:5000/1/edit
      

      Sie werden eine Seite Edit “First Post” sehen.

      Seite zum Bearbeiten eines Beitrags

      Bearbeiten Sie den Beitrag und übermitteln Sie das Formular; überprüfen Sie dann, ob der Beitrag aktualisiert wurde.

      Nun müssen Sie für jeden Beitrag auf der Indexseite einen Link hinzufügen, der auf die Bearbeitungsseite verweist. Öffnen Sie die Vorlagendatei index.html:

      • nano templates/index.html

      Bearbeiten Sie die Datei, damit sie genau wie folgt aussieht:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
          {% for post in posts %}
              <a href="https://www.digitalocean.com/{{ url_for("post', post_id=post['id']) }}">
                  <h2>{{ post['title'] }}</h2>
              </a>
              <span class="badge badge-primary">{{ post['created'] }}</span>
              <a href="https://www.digitalocean.com/{{ url_for("edit', id=post['id']) }}">
                  <span class="badge badge-warning">Edit</span>
              </a>
              <hr>
          {% endfor %}
      {% endblock %}
      

      Speichern und schließen Sie die Datei.

      Hier fügen Sie ein <a>-Tag hinzu, um eine Verknüpfung zur Ansichtsfunktion edit() herzustellen. Dazu übergeben Sie den Wert post['id'], um mit dem Link Edit eine Verknüpfung zur Bearbeitungsseite der einzelnen Beiträge herzustellen.

      Löschen eines Beitrags

      Manchmal müssen Beiträge nicht mehr öffentlich verfügbar sein. Dafür gibt es die Funktion zum Löschen von Beiträgen. In diesem Schritt fügen Sie Ihrer Anwendung die Löschfunktion hinzu.

      Zuerst fügen Sie eine neue Route /ID/delete hinzu, die POST-Anfragen akzeptiert, ähnlich wie bei der Ansichtsfunktion edit(). Ihre neue delete()-Ansichtsfunktion empfängt die ID des Beitrags, der gelöscht werden soll, aus der URL. Öffnen Sie die Datei app.py:

      Fügen Sie am Ende der Datei die folgende Ansichtsfunktion hinzu:

      flask_blog/app.py

      # ....
      
      @app.route('/<int:id>/delete', methods=('POST',))
      def delete(id):
          post = get_post(id)
          conn = get_db_connection()
          conn.execute('DELETE FROM posts WHERE id = ?', (id,))
          conn.commit()
          conn.close()
          flash('"{}" was successfully deleted!'.format(post['title']))
          return redirect(url_for('index'))
      

      Diese Ansichtsfunktion akzeptiert nur POST-Anfragen. Das bedeutet, dass, wenn Sie in Ihrem Browser zur Route /ID/delete navigieren, ein Fehler zurückgegeben wird, da Webbrowser standardmäßig GET-Anfragen nutzen.

      Sie können diese Route jedoch über ein Formular aufrufen, das eine POST-Anfrage sendet, mit der die ID des Beitrags übergeben wird, den Sie löschen möchten. Die Funktion empfängt den ID-Wert und verwendet ihn, um den Beitrag mit der Funktion get_post() aus der Datenbank abzurufen.

      Dann öffnen Sie eine Datenbankverbindung und führen einen DELETE FROM-SQL-Befehl aus, um den Beitrag zu löschen. Sie übergeben die Änderung an die Datenbank und schließen die Verbindung, während Sie dem Benutzer eine Nachricht flashen, dass der Beitrag erfolgreich gelöscht wurde, und leiten ihn zur Indexseite weiter.

      Beachten Sie, dass Sie eine Vorlagendatei nicht rendern, da Sie der Bearbeitungsseite einfach eine Delete-Schaltfläche hinzufügen.

      Öffnen Sie die Vorlagendatei edit.html:

      Fügen Sie dann das folgende <form>-Tag nach dem Tag <hr> und direkt vor der Zeile {% endblock %} hinzu:

      flask_blog/templates/edit.html

      <hr>
      
      <form action="https://www.digitalocean.com/{{ url_for("delete', id=post['id']) }}" method="POST">
          <input type="submit" value="Delete Post"
                  class="btn btn-danger btn-sm"
                  onclick="return confirm('Are you sure you want to delete this post?')">
      </form>
      
      {% endblock %}
      

      Sie verwenden die Methode confirm(), um eine Bestätigungsmeldung anzuzeigen, bevor die Anfrage übermittelt wird.

      Navigieren Sie nun erneut zur Bearbeitungsseite eines Blogbeitrags und versuchen Sie, ihn zu löschen:

      http://127.0.0.1:5000/1/edit
      

      Am Ende dieses Schritts sieht der Quellcode Ihres Projekts wie der Code auf dieser Seite aus.

      Damit können die Benutzer Ihrer Anwendung nun neue Blogbeiträge schreiben und der Datenbank hinzufügen sowie bestehende Beiträge bearbeiten und löschen.

      Zusammenfassung

      Dieses Tutorial hat eine Einleitung in grundlegende Konzepte des Flask-Python-Frameworks geboten. Sie haben gelernt, wie Sie eine kleine Webanwendung erstellen und auf einem Entwicklungsserver ausführen sowie dem Benutzer erlauben, über URL-Parameter und Webformulare benutzerdefinierte Daten anzugeben. Außerdem haben Sie die Jinja-Vorlagen-Engine verwendet, um HTML-Dateien wiederzuverwenden und Logik darin zu nutzen. Am Ende dieses Tutorials verfügen Sie nun über einen voll funktierende Weblog, der mit einer SQLite-Datenbank interagiert, um mithilfe der Python-Sprache und SQL-Abfragen das Erstellen, Anzeigen, Bearbeiten und Löschen von Blogbeiträgen zu ermöglichen.

      Sie können diese Anwendung weiter entwickeln, indem Sie Benutzerauthentifizierung hinzufügen, damit nur registrierte Benutzer Blogbeiträge erstellen und ändern können. Außerdem können Sie zu jedem Blogbeitrag Kommentare und Tags sowie Datei-Upload-Funktionen hinzufügen, damit Benutzer die Möglichkeit haben, Bilder im Beitrag zu verwenden. Weitere Informationen finden Sie in der Flask-Dokumentation.

      Flask verfügt über eine Vielzahl von Flask-Erweiterungen, die von der Community entwickelt wurden. Im Folgenden finden Sie eine Liste von Erweiterungen, die Sie verwenden können, um Ihren Entwicklungsprozess zu erleichtern:

      • Flask-Login: verwaltet die Benutzersitzung und übernimmt die An- und Abmeldung sowie das Erinnern angemeldeter Benutzer.
      • Flask-SQLAlchemy: vereinfacht die Verwendung von Flask mit SQLAlchemy, einem Python-SQL-Toolkit, und Object Relational Mapper zur Interaktion mit SQL-Datenbanken.
      • Flask-Mail: hilft beim Versand von E-Mail-Nachrichten in Ihrer Flask-Anwendung.



      Source link

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

      Navigieren Sie zum Verzeichnis 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:

      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:

      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:

      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:

      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.

      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:

      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.

      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?



      Source link