Software-Engineering

Download this notebook

In unserer Einführung werden wir auf all diese Frameworks eingehen. Wichtig ist, zu wissen, dass all diese Frameworks stetig weiterentwickelt werden. Dies führt dazu, dass Programme sehr schnell veralten und gegebenenfalls mit neueren Versionen der Frameworks nicht mehr laufen. Deshalb kann es sinnvoll sein, sich für einzelne Projekte sogenannte virtual environments einzurichten und die genutzten Versionen der Frameworks und weiteren Pakete in einer Datei requirements.txt abzuspeichern.

Weiterhin wird es im wissenschaftlichen oder auch geschäftlichen Nutzung unweigerlich dazu kommen, dass auch andere Personen an einem Projekt mitarbeiten sollen oder dieses weiterentwickeln sollen. Ein kleines Projekt kann dadurch schnell sehr komplex werden und man sollte frühzeitig Werkzeuge und Prinzipien der Software-Entwicklung nutzen und sich mit diesen Tools auseinandersetzen. Das wichtigste und zentralste Tool hier ist git, welches eine Versionskontrolle und gemeinsames Arbeiten am Code ermöglicht. Dieser Code kann dann zum Beispiel über GitHub oder GitLab mit anderen geteilt werden. An der Universität Münster gibt es dazu ein eigenes GitLab.

Ratsam ist es zudem auch, sich mit einer Linux-Umgebung vertraut zu machen. Falls man nicht selber einen Linux-Rechner hat, steht zum Beispiel über den JupyterHub eine Unix-Umgebung für alle Mitglieder der Universität Münster zur Verfügung, die speziell zur Nutzung von wissenschaftlicher Software, aber auch verschiedenster Programmiersprachen gedacht ist. Für MacOS bietet es sich an, brew zu nutzen um die vollen Fähigkeiten des Unix Systems bereitzustellen, unter Windows gibt es inzwischen das Windows-Subsystem für Linux (WSL). Es gibt für verschiedene Betriebssysteme eine diverse Palette an Editoren, um zu programmieren. Als Programmierumgebung für den incubAItor bietet sich insbesondere Visal Studio Code an, da es sich einfach erweitern lässt. Für das Deployment wird es dann auch wichtig, Anwendungen als abgeschlossenes System bereitzustellen.

Zusammenfassend werden wir folgende Tools nutzen:

  • Unix System und die Unix Kommandozeile
  • SSH
  • Python (ab Version 3.6), die Paketverwaltung pip und Virtuelle Environments
  • Jupyter-Notebooks auf dem JupyterHub oder in Visual Studio Code
  • KI-Frameworks Torch, Tensorflow, Keras, PhotonAI und Transformers
  • Git zur Versionsverwaltung
  • Docker

In den folgenden Abschnitten wird auf diese Tools eingegangen, wobei z.B. Docker erst später gebraucht wird und nicht direkt installiert werden muss.

Unix Kommandozeile

Für Anfänger*innen kann insbesondere das Arbeiten mit der Kommandozeile herausfordernd sein. Die Nutzung einer Programmierumgebung wie Visual Studio Code kann hier zwar Hilfestellung geben, jedoch sind banale Kenntnisse für diese Einführung unvermeidbar. Im JupyterHub kann man über New Launcher => Terminal die Kommandozeile aufrufen und sich z.B. mit dem Kommando ls den Inhalt des Ordners anzeigen lassen, in dem man sich grad befindet. Mit cd <folder> kann man in von da aus in einen Ordner namens <folder> gelangen (die Autovervollständigung mit der TAB Taste ist oft hilfreich). Mit cd .. kommt man wieder eine Ebene höher. Wichtig sind für uns noch Python Kommandos. Falls Python installiert ist, kann man das Programm python aufrufen. Dann sollte sich ein Python Interpreter öffnen und man kann z.B. print("Hello World") ausgeben lassen. Mit quit() kann man das Programm beenden. Jedes laufende Programm kann auch mit CTRL+C beendet werden.

SSH

SSH ist ein sicheres Netzwerkprotokoll, das verschlüsselte Verbindungen über unsichere Netzwerke ermöglicht. Es dient dazu, vertrauliche Daten vor unbefugtem Zugriff zu schützen und wird häufig für sichere Serverzugriffe, Dateiübertragungen und Remote-Befehle verwendet.

In unserem Tutorial werden wir den SSH-Zugang für den Login auf PALMA und im Kapitel über OpenStack benötigen. Wir erklären dir hier, wie du einen existierenden SSH-Key finden kannst oder einen neuen SSH-Key generieren kannst.

A. Existierenden SSH-Key nutzen

Eventuell besitzt du bereits einen SSH-Key. Diesen kannst du dann einfach verwenden. Er liegt üblicherweise in deinem Home-Verzeichnis in einem Ordner .ssh/. Falls dieser Ordner leer ist oder nicht existiert, hast du vermutlich noch keinen SSH-Key und musst einen neuen generieren (siehe nächster Abschnitt).

Die SSH-Keys bestehen immer aus einem Pärchen bestehend aus einem privaten und öffentlichen Schlüssel. Es gibt verschiedene Algorithmen mit denen die Schlüssel generiert werden können. Üblicherweise sind die Dateien entsprechend benannt:

ALGORITHMUSPUBLIC KEYPRIVATE KEY
ED25519 (bevorzugt)id_ed25519.pubid_ed25519
RSA (mindestens 2048-Bit großer Key)id_rsa.pubid_rsa
DSA (veraltet)id_dsa.pubid_dsa
ECDSAid_ecdsa.pubid_ecdsa
B. Einen neuen SSH-Key generieren

Wenn du noch keinen SSH-Key besitzt, musst du ein neues Schlüssel-Paar generieren. Du kannst dazu das Programm ssh-keygen nutzen. Wahrscheinlich hattest du dieses (abhängig von deinem Betriebssystem) bereits installiert oder hast es zusammen mit Git installiert. Führe in einer Konsole den folgenden Befehl aus:

ssh-keygen -t ed25519 -C "<comment>"

Du wirst nun gefragt, wo du das neue Schlüssel-Paar speichern möchtest. Falls du unsicher bist oder keinen besonderen Grund hast, solltest du es bei der Standardeinstellung belassen und diese mit Enter bestätigen.

Als nächstes wirst du nach einem Passwort gefragt. Mit diesem kannst du den Zugriff auf den Schlüssel beschränken. Es ist möglich kein Passwort zu vergeben, aber beachte, dass dann jeder mit Zugang zu deinem Gerät auch auf den Schlüssel zugreifen kann. Mit dem privaten Schlüssel kann man sich gegenüber dem GitLab-Server (und anderen Servern auf denen du deinen öffentlichen Schlüssel hinterlegst) als du ausgeben. Das hier eingegebene Passwort stellt eine zusätzliche Sicherheitshürde dar und muss bei jedem Zugriff auf den Server (bzw. dafür benötigten privaten Schlüssel) eingegeben werden.

Hast du deine SSH-Schlüssel gefunden oder generiert, kannst du diesen im IT-Portal der Uni Münster hochladen. Hierzu gehst du links im Menü auf den Punkt Passwörter und PINs => Öffentliche SSH-Schlüssel verwalten.

Hier kannst du nun den Inhalt der Datei, die den Öffentlichen Schlüssel (endet mit *.pub) enthält entweder in das Eingabefeld kopieren oder die Datei selbst hochladen (du solltest sie im Ordner ~/.ssh finden können).

Achtung! Achte darauf, dass du auf jeden Fall den öffentlichen Schlüssel hochlädst, gib niemals deinen privaten Schlüssel (ohne Dateiendung) heraus! Mit Hilfe dieses Schlüssels können im schlimmsten Fall andere deine Identität annehmen! Du solltest die privaten Schlüssel also gut hüten. Im Optimalfall hast du ebenfalls für jeden SSH-Zugang zu deinen Systemen ein eigenes Schlüsselpaar.

Python

Wichtig ist es, zu schauen, welche Python Version installiert ist. Diese wird entweder angezeigt, wenn in der Kommandozeile der Python Interpreter gestartet wird, oder das Kommando python --version eingegeben wird. Ggf. kann auch das Kommando python3 weiterhelfen, falls noch eine veraltete Version installiert ist. Gemeinsam mit Python sollte auch pip installiert sein, welches die Paketverwaltung von Python ist, über welche neue Pakete installiert werden können. Das Kommando pip freeze zeigt die derzeit installierten Pakete an. Neue Pakete können über pip install <paket> installiert werden, wobei dabei oft auch die Abhängigkeiten installiert werden.

ACHTUNG! Da verschiedene Versionen von Paketen und Frameworks verschiedene Abhängkeiten haben (insbesondere bei KI-Frameworks), kann es sinnvoll sein, sich einzelne Virtuelle Environments für Projekte zu erstellen. Über Virtuelle Environments venv können einzelne voneinander unabhängige Python Interpreter installiert werden. Falls eine andere Python Version benötigt wird, oder komplexere Pakete installiert werden sollen, kann auch die Paketverwaltung conda weiterhelfen. Gleichzeitig sollte man ggf. darauf achten, alte Environments wieder zu entfernen, da manche Pakete wie z.B. Tensorflow viel Festplattenplatz benötigen.

Eine Einführung in Python gibt es z.B. unter W3 Schools. Auch können Chatbots wie ChatGPT bei der Programmierung helfen, ein Prompt wie “Please help me to multiply a 3x3 matrix with a vector in python” sollte die gewünschten Programmierzeilen geben, ggf. kann noch spezifiziert werden, dass man das Paket numpy nutzt. Die Pakete numpy und pandas sind grundlegend bei der Nutzung von Daten und werden in dieser Einführung ebenfalls vielfach genutzt.

Jupyter-Notebooks

Python Programme sind einzelne Textdateien, die z.B. als program.py gespeichert werden und durch python program.py aufgerufen werden. Hier hat man jedoch wenige Möglichkeiten zur Fehlersuche, deshalb ist für Anfänger aber auch insbesondere für Data-Science Anwendung die Nutzung von Jupyter-Notebooks geeignet. Hier können einzelne Abschnitte eines Programms nacheinander eingegeben und ausgeführt werden. Im JupyterHub kann man sich über den Launcher => Notebook ein Notebook aufrufen. Oben rechts kann der genutzte Kernel geändert werden (der Kernel ist in etwa die Python Umgebung die man nutzen möchte). In Visual Studio Code kann man ein Notebook als eine Datei mit der Endung .ipynb erstellen. Auch hier kann der Kernel gewählt werden (man kann sich sogar mit einem entfernten Kernel wie dem JupyterHub verbinden und diesen nutzen). Für die Nutzung von Jupyter Notebooks kann es notwendig sein diese über die Kommandozeile mit pip install notebooks zu installieren.

In Jupyter-Notebooks kann man Code eingeben und mit dem Play Button oder Shift + Enter die einzelnen Abschnitte auszuführen. Falls zwischendurch Pakete installiert werden, muss man den Kernel ggf. neu starten. Auch können wie in diesem Text einzelne Textabschnitte mit der Markdown-Sprache erstellt werden, um den Code zu dokumentieren oder Anleitungen zu schreiben.

Conda Enviroment

Bei Conda handelt es sich um ein plattformübergreifendes Paket- und Umgebungsmanager für Python. Dieses Open-Source-Verwaltungssystem läuft auf den Betriebssystemen Windows, macOS und Linux. Dadurch lassen sich Umgebungen erstellen, welche auf die Bedürfnisse von genutzten Programmen zugeschnitten sind. Diese können bestimmte Python-Versionen, Bibliotheken sowie Anforderungen an die Hardware sein. Beispiel: Ein hypothetisch genutztes Programm A kann nur mit einer speziellen Version von Programm B laufen. Wenn jetzt noch das Programm C mit einer anderen Versionen von Programm B läuft – sind Probleme vorprogrammiert ;).

Um diesen Problemen aus dem Weg zu gehen, bietet Conda folgende Möglichkeiten:

  1. Conda ermöglicht eine einfache Installation, Aktualisierung, Deinstallation von Softwarepaketen. Es werden sowohl Python-Pakete als auch nicht-Python-Pakete unterstützt.

  2. Conda ermöglicht die Erstellung und Verwaltung von isolierten Umgebungen, mit verschiedenen Paketen und Python-Interpreter. Dadurch ist es möglich als Programmierer zwischen verschiedenen Projekte zu wechseln, ohne dass Probleme zwischen Abhängigkeiten auftreten.

  3. Conda ist als Open-Source-Verwaltungssystem plattformunabhängig und läuft auf Windows, macOS und Linux. Dadurch ist eine konsistente Verwaltung sowie die Zusammenarbeit von unterschiedlichen Personen möglich.

  4. Conda wird oft in der Verbindung mit der Anaconda-Distribution oder der leichtgewichtigeren Miniconda-Installation genutzt. Diese enthalten eine Zusammenfassung der wichtigsten Python-Pakete aus datenwissenschaftlichen und wissenschaftlichen Bereichen.

KI-Frameworks

KI-Frameworks sind Pakete in Python. Die gängigsten Frameworks sind Tensorflow und Torch, mit denen man die Daten laden kann, neuronale Netze konstruieren kann und die wichtigsten Operationen ausführen kann. Damit man ganze Netz-Architekturen und oft ausgeführte Operationen nicht komplett selbst schreiben muss, haben sich weitere Frameworks (z.B. Keras) entwickelt, die eines der grundlegenden Frameworks als sogenanntes Backend nutzen. Weiterhin gibt es mit scikit-learn eine Bibliothek mit den gängigsten Operationen. Das an der Universität Münster entwickelte PhotonAI kann ebenfalls dabei helfen, Standard-Anwendungen zu entwickeln und zu evaluieren.

Tensorflow und Torch können auf der CPU oder auf der GPU arbeiten. Dazu werden die Netze und Daten in den entsprechenden Arbeitsspeicher geladen. Bei GPU-Anwendungen basiert die Kommunikation mit der Grafikkarte auf CUDA. Einfach kann man z.B. ein Modell model oder einen tensor durch den Aufruf model.to('cuda') in die Grafikkarte schieben. Dann werden die Rechnungen (zumeist Matrixmultiplikationen) deutlich schneller ausgeführt (man kann torch z.B. auch für viele Operationen anstelle von numpy nutzen, um diese auf der Grafikkarte auszuführen).

Git

Wer hat nicht schonmal eine Datei namens “Bachelorarbeit_v3_final_final.docx” gesehen? Jede Entwicklerin oder jeder Entwickler hat es schon erlebt, sich eigene, laufende Programme durch Weiterentwicklungen wieder zu zerstören. Die Fehlersuche kann dann sehr aufwändig sein. Auch würde man manchmal gerne wissen, wie man etwas zuvor gelöst hat, auch wenn man den Teil des Programms später nicht mehr gebraucht hat. Eine Versionierung der Programme anhand des Dateinamens ist jedoch unpraktisch. Dafür hat sich inzwischen Git durchgesetzt. Git sollte ebenfalls auf dem lokalen Rechner installiert sein, man kann dies durch das Kommando git --version testen. Auf dem JupyterHub ist dies bereits installiert und kann vereinfacht ohne Kommandozeile genutzt werden (und auch in Visual Studio Code kann man eine Erweiterung installieren).

Git ist ein System, mit dem man ein sogenanntes Repository auf einen lokalen Rechner klonen kann und dort weiterentwickeln kann. Wenn man die Dateien abspeichert, kann man sie durch sogenannte commits dann in dem (lokalen) Repository aktualisieren. Dies ermöglicht es auch, die Historie des Dokuments zu sehen. Weitere Vorteile ergeben sich, wenn man diese lokalen Repositories dann wieder hochlädt (push), so können diese Änderungen auch bei anderen Entwicklern durch einen pull synchronisiert werden. Git ermöglicht noch viel mehr im Bezug auf gemeinsames Arbeiten. Ein lokal initialisiertes Repository kann ebenfalls auf einen Server wie dem GitLab der Universität Münster oder GitHub geladen werden.

Zudem findet man bei GitHub vielfältige Lösungen, die öffentlich bereitgestellt werden.

Die Universität Münster bietet ihre eigene GitLab-Instanz an.

Eine Einführung in Git werden wir ebenfalls geben, da es zentral für die Organisation des Codes und die Deployment-Pipelines ist.

Erstellung eines Personal Access Tokens

Um im GitLab auf Ressourcen (z.B. Docker Images oder Container) zuzugreifen oder um diese hochzuladen, werden Personal Access Tokens (PAT) verwendet. Die Erstellung ist nicht kompliziert: Gehe dazu auf dein Profil (z.B. über deinen Avatar) und gehe zu Preferences -> Access Tokens. Von hier aus kannst du dir einen neuen PAT erstellen, indem du auf Add new token klickst. Nachdem du einen geeigneten Namen und ein Ablaufdatum ausgewählt hast, kannst du die scopes deines PAT einstellen. Da wir oft den lesenden und schreibenden Zugriff auf Repositories und Registries wollen, solltest du auf jeden Fall api, write_registry and read_registry auswählen. Nachdem du den Token erstellt hast, stelle sicher, dass du ihn sicher speicherst, da du ihn nach der Erstellung nur einmalig sehen/kopieren kannst.

Docker

Docker wird ebenfalls im Laufe des Tutorials eingeführt. Prinzipiell erlaubt es Docker, einzelne Anwendungen in sogenannten Containern bereitzustellen. Man kann sich diese Container wie schlanke virtuelle Computer vorstellen, die einzig für die Bereitstellung der Anwendung erstellt wurden. Darin werden alle Abhängigkeiten installiert. Einen gebauten Container kann man dann lokal ausführen oder auf einen Server laden. Da Docker alle Abhängigkeiten lädt und installiert, kann das Bauen eines Containers durchaus zeitaufwändig sein, jedoch bestehen die Container aus verschiedenen aufeinander aufbauenden Layern. Das Bauen des Containers wird dann nur ab dem Layer gestartet, wo eine Änderung stattgefunden hat, sodass eine gute Organisation des Codes auch hier Effizienz verspricht. Auch kann ein Projekt mehrere Container umfassen, die für verschiedene Teile (z.B. getrenntes Backend und Frontend) gemeinsam eine Anwendung ergeben.

Docker erlaubt es nicht nur, ein komplexes Projekt mit unterschiedlichen Anwendungen für andere einfach zugänglich zu machen, sondern auch diese Anwendung auf einen Server zu laden und dort auszuführen (Deployment). Die Container kann man entweder lokal bauen und hochladen, oder durch eine geeignete Pipeline direkt im GitLab der Universität aus dem Code bauen. Fertig gebaute Container können in einem Repository (z.B. Dockerhub oder Harbor der Universität Münster) gespeichert werden, aus dem sich die Server immer die aktuelle (oder gewünschte) Version des Containers ziehen.

Hardware

Neben der Software ist insbesondere bei KI-Anwendungen auch die Hardware zu beachten. So benötigt das Training von größeren Netzen oftmals erhebliche Ressourcen. Einerseits kann man hier durch Tricks bei der Programmierung (z.B. die Batch-Size) den Hardware-Hunger kanalisieren, andererseits wird man nicht umhin kommen, Grafikkarten zu nutzen. Das korrekte Installieren der Frameworks im Zusammenspiel mit den Grafikkarten kann sehr aufwändig werden, weshalb wir uns in diesem Tutorial auf die Ressourcen der Universität Münster konzentrieren werden, nämlich den JupyterHub und den Supercomputer PALMA II.

Weiterhin seien noch zwei Anmerkungen zur genutzten Hardware erlaubt:

  • Als Prozessor (CPU) werden sogenannte x86 CPUs genutzt, die in den meisten Rechnern verbaut sind und mit dem Arbeitsspeicher (RAM) verbunden sind
  • Als Grafikkarte (GPU) werden NVIDIA Grafikkarten mit dem Video-Arbeitsspeicher (VRAM) und der dazugehörigen Schnittstelle CUDA genutzt

Moderne Apple Rechner nutzen statt x86 Architektur ARM Prozessoren. Die meisten KI Anwendungen funktionieren darauf ebenfalls, ggf. müssen spezielle Versionen der Frameworks installiert werden. Die Architektur hat ebenfalls den Vorteil, dass sich die GPU und die CPU den Arbeitsspeicher teilen und somit die Begrenzung des zumeist kleinen VRAM hier kein großes Hindernis darstellt. Zur Bereitstellung von Docker-Containern, die auf anderen Architekturen laufen, muss man besonders bei dem Bauen der Container darauf achten, diese für die richtige Architektur zu bauen.

Cloud-Anwendungen

Ziel dieser Einführung ist es, eine KI-Anwendung zu bauen, die im Web funktioniert, zum Beispiel über eine API-Anfrage an einen Server. Wichtig ist hier zum Beispiel, dass zum Training eines neuronalen Netzes zwar erhebliche Ressourcen benötigt werden, zur Ausführung dieser Anwendung jedoch nur ein Bruchteil. Dazu muss die Anwendung mit dem vortrainierten Netz auf einem Web-Server laufen. Hierzu nutzt man Docker Container, die man auf unterschiedlichen Server-Systemen zum Laufen bringen kann. Ein Docker Container ist in etwa ein eigener Computer, auf dem nur das installiert wird, was von der Anwendung benötigt wird. Es können auch mehrere Container gemeinsam eine Anwendung ergeben, falls man zum Beispiel das Backend und das Frontend einer Anwendung trennen möchte oder auch komplett verschiedene Programmiersprachen nutzen möchte (JavaScript für die Webanwendung und Python für die KI).

Innerhalb dieses Tutorials sollen am Ende einfachste KI-Anwendungen als Web-Anwendung bereitgestellt werden. Dafür wird auch in die Details zum Deployment Pipelines (CI/CD) eingegangen, sodass eine Änderung des Codes im Git wünschenswerterweise am Ende direkt automatisiert die Webanwendung aktualisiert.

Der incubAItor bezieht sich hier auf die von der Universität bereitgestellten Cloud Infrastrukturen OpenStack und Kubernetes. Aber auch kommerzielle Anbieter arbeiten mit diesen oder ähnlichen Cloud Infrastrukturen, sodass sich der Transfer von Universität zu kommerziellen Clouds bei der richtigen Konfiguration einfach vollziehen lassen sollte.