Data Science, Machine Learning und KI
Kontakt

«Vertrauen schaffen durch menschenzentrierte KI»: Unter diesem Slogan hat die Europäische Kommission in der vergangenen Woche ihren Vorschlag zur Regulierung von künstlicher Intelligenz (KI-Regulierung) vorgestellt. Dieser historische Schritt positioniert Europa als ersten Kontinent, der KI und den Umgang mit personenbezogenen Daten einheitlich reguliert. Mithilfe dieser wegweisenden Regulierung soll Europa Standards zur Nutzung mit Daten und KI setzen – auch über die europäischen Grenzen hinaus. Dieser Schritt ist richtig. KI ist ein Katalysator der digitalen Transformation mit nachhaltigen Implikationen für Wirtschaft, Gesellschaft und Umwelt. Klare Spielregeln für den Einsatz dieser Technologie sind deshalb ein Muss. Damit kann sich Europa als progressiver Standort positionieren, der bereit ist für das digitale Zeitalter. In der aktuellen Form wirft der Vorschlag aber noch einige Fragen bei der praktischen Umsetzung auf. Abstriche bei der digitalen Wettbewerbsfähigkeit kann sich Europa im immer weiter klaffenden Wettbewerb mit Amerika und China nicht leisten.

Transparenz bei Risiken von KI

Zwei zentrale Vorschläge der KI-Regulierung zur Schaffung von Vertrauen

Um Vertrauen in KI-Produkte zu schaffen, setzt der Vorschlag zur KI-Regulierung auf zwei zentrale Ansätze: Risiken künstlicher Intelligenz überwachen und gleichzeitig ein «Ökosystem an KI-Exzellenz» kultivieren. Konkret beinhaltet der Vorschlag ein Verbot für die Anwendung von KI zu manipulativen und diskriminierenden Zwecken oder zur Beurteilung von Verhalten durch ein «Social Scoring System». Anwendungsfälle, die nicht in diese Kategorie fallen, sollen trotzdem auf Gefahren untersucht und auf einer vagen Risikoskala platziert werden. An Hochrisikoanwendungen werden besondere Anforderungen gestellt, deren Einhaltung sowohl vor als auch nach der Inbetriebnahme geprüft werden soll.

Dass anstelle einer Pauschalregulierung KI-Anwendungen auf Fallbasis beurteilt werden sollen, ist entscheidend. Noch letztes Jahr forderte die Europäische Kommission in einem Whitepaper die breite Einstufung aller Anwendungen in Geschäftsbereichen wie dem Gesundheitssektor oder der Transportindustrie. Diese flächendeckende Einstufung anhand definierter Branchen, unabhängig der eigentlichen Use Cases, wäre hinderlich und hätte für ganze Industrien auf dem Kontinent strukturelle Benachteiligungen bedeutet. Diese Fall-zu-Fall-Beurteilung erlaubt die agile und innovative Entwicklung von KI in allen Sektoren und unterstellt zudem alle Branchen den gleichen Standards zur Zulassung von risikoreichen Anwendungen.

Klare Definition von Risiken einer KI-Anwendung fehlt

Allerdings lässt der Vorschlag zur KI-Regulierung eine klare Definition von «hohen Risiken» vermissen. Da Entwickler selbst für die Beurteilung ihrer Anwendungen zuständig sind, ist eine klar definierte Skala zur Beurteilung von Risiken unabdingbar. Artikel 6 und 7 umschreiben zwar Risiken und geben Beispiele von «Hochrisikoanwendungen», ein Prozess zur Beurteilung von Risiken einer KI-Anwendung wird aber nicht definiert. Besonders Start-ups und kleinere Unternehmen, die unter KI-Entwicklern stark vertreten sind, sind auf klare Prozesse und Standards angewiesen, um gegenüber Großunternehmen mit entsprechenden Ressourcen nicht ins Hintertreffen zu geraten. Dazu sind praxisnahe Leitlinien zur Beurteilung von Risiken nötig.

Wird ein Use Case als «Hochrisikoanwendung» eingestuft, dann müssen verschiedene Anforderungen hinsichtlich Data Governance und Risk Management erfüllt sein, bevor das Produkt auf den Markt gebracht werden kann. So müssen verwendete Trainingsdatensätze nachweislich auf Verzerrungen und einseitige Tendenzen geprüft werden. Auch sollen die Modellarchitektur und Trainingsparameter dokumentiert werden. Nach dem Deployment muss ein Maß an menschlicher Aufsicht über getroffene Entscheidungen des Modells sichergestellt werden.

Verantwortlichkeit zu KI-Produkten ist ein hohes und wichtiges Ziel. Allerdings bleibt erneut die praktische Umsetzung dieser Anforderungen fraglich. Viele moderne KI-Systeme nutzen nicht länger den herkömmlichen Ansatz von Trainings- und Testdaten, sondern setzen bspw. durch Reinforcement Learning auf exploratives Training durch Feedback anstelle eines statischen, prüfbaren Datensatzes. Fortschritte in Explainable AI brechen zwar undurchschaubare Black-Box Modelle stetig weiter auf und ermöglichen immer mehr Rückschlüsse auf die Wichtigkeit von Variablen im Entscheidungsprozess eines Modelles, aber komplexe Modellarchitekturen und Trainingsprozesse vieler moderner neuronaler Netzwerke machen einzelne Entscheide eines solchen Modells für Menschen kaum sinnhaft rekonstruierbar.

Auch werden Anforderungen an die Genauigkeit der Prognosen oder Klassifizierungen gestellt. Dies stellt Entwickler vor besondere Herausforderungen, denn kein KI-System hat eine perfekte Genauigkeit. Dieser Anspruch besteht auch nicht, oftmals werden Fehlklassifikationen so eingeplant, dass sie für den jeweiligen Use Case möglichst wenig ins Gewicht fallen. Deshalb ist es unabdinglich, dass die Anforderungen an die Genauigkeit von Prognosen und Klassifikationen von Fall zu Fall in Anbetracht der Anwendung festgelegt werden und auf Pauschalwerte verzichtet wird.

KI-Exzellenz ermöglichen

Europa gerät ins Hintertreffen

Mit diesen Anforderungen will der Vorschlag zur KI-Regulierung durch Transparenz und Verantwortlichkeit Vertrauen in die Technologie wecken. Dies ist ein erster, richtiger Schritt in Richtung «KI-Exzellenz». Nebst Regulierung muss der KI-Standort Europa dazu aber auch für Entwickler und Investoren mehr Strahlkraft erhalten.

Laut einer jüngst veröffentlichten Studie des Center for Data Innovation gerät Europa sowohl gegenüber den Vereinigten Staaten als auch China im Anspruch um die weltweite Führungsposition in Sachen KI bereits ins Hintertreffen. So hat China mittlerweile in der Anzahl veröffentlichter Studien und Publikationen zu künstlicher Intelligenz Europa den Rang abgelaufen und die weltweite Führung übernommen. Auch ziehen europäische KI-Unternehmen erheblich weniger Investitionen an als ihre amerikanischen Pendants. Europäische KI-Unternehmen investieren weniger Geld in Forschung und Entwicklung und werden auch seltener aufgekauft als ihre amerikanischen Kollegen.

Ein Schritt in die richtige Richtung: Unterstützung von Forschung und Innovation

Der Vorschlag der EU-Kommission erkennt an, dass für Exzellenz auf dem europäischen Markt mehr Unterstützung für KI-Entwicklung benötigt wird und verspricht Regulatory Sandboxes, also rechtliche Spielräume zur Entwicklung und Testung innovativer KI-Produkte, und die Kofinanzierung von Forschungs- und Teststätten für KI. Dadurch sollen insbesondere Start-ups und kleinere Unternehmen wettbewerbsfähiger werden und für mehr europäische Innovationen sorgen.

Dies sind notwendige Schritte, um Europa auf den Weg zur KI-Exzellenz zu hieven, allerdings ist damit nicht genug getan. KI-Entwickler brauchen einfacheren Zugang zu Märkten außerhalb der EU, was auch das Vereinfachen von Datenströmen über Landesgrenzen bedeutet. Die Möglichkeiten zur Expansion in die USA und Zusammenarbeit mit Silicon Valley ist für die digitale Branche besonders wichtig, um der Vernetzung von digitalen Produkten und Services gerecht zu werden.

Was in dem Vorschlag zur KI-Regulierung gänzlich fehlt ist die Aufklärung über KI und deren Potenzial und Risiken außerhalb von Fachkreisen. Mit der zunehmenden Durchdringung aller Alltagsbereiche durch künstliche Intelligenz wird dies immer wichtiger, denn um Vertrauen in neue Technologien stärken zu können, müssen diese zuerst verstanden werden. Die Aufklärung sowohl über das Potenzial als auch die Grenzen von KI ist ein essenzieller Schritt, um künstliche Intelligenz zu entmystifizieren und dadurch Vertrauen in die Technologie zu schaffen.

Potenzial noch nicht ausgeschöpft

Mit diesem Vorschlag erkennt die Europäische Kommission an, dass Künstliche Intelligenz wegweisend ist für die Zukunft des europäischen Marktes. Leitlinien für eine Technologie dieser Tragweite sind wichtig – genauso wie die Förderung von Innovation. Damit diese Strategien auch Früchte tragen, muss ihre praktische Umsetzung auch für Start-ups und KMU zweifelsfrei umsetzbar sein. Das Potenzial zur KI-Exzellenz ist in Europa reichlich vorhanden. Mit klaren Spielregeln und Anreizen kann dies auch realisiert werden.

Oliver Guggenbühl Oliver Guggenbühl Oliver Guggenbühl

Computer sehen zu lassen, dies mag für viele nach Science-Fiction klingen. Denn mit «sehen» ist nicht das Filmen mit einer Webcam, sondern das Verständnis von Bildmaterial gemeint. Tatsächlich sind derartige Technologien hinter den Kulissen vieler alltäglicher Services schon lange im Einsatz. Soziale Netzwerke erkennen seit Jahren Freunde und Bekannte auf Fotos und moderne Smartphones lassen sich mit dem Gesicht anstatt einem PIN-Code entsperren. Neben diesen kleinen Alltagserleichterungen birgt das rasant wachsende Feld der «Computer Vision» weitaus größeres Potenzial für den industriellen Einsatz. Die spezialisierte Verarbeitung von Bildmaterial verspricht sowohl viele repetitive Prozesse zu erleichtern und automatisieren. Zudem sollen Experten und Fachpersonal entlastet und in ihren Entscheidungen unterstützt werden.

Die Grundlagen für Bilderkennung und Computer Vision wurden bereits in den 1970er Jahren geschaffen. Allerdings hat das Feld erst in den letzten Jahren vermehrt Anwendung außerhalb der Forschung gefunden. In unserer Tätigkeit als Data Science & AI Beratung hier bei STATWORX haben wir bereits einige interessante Anwendungsfälle von Computer Vision kennengelernt. Dieser Beitrag stellt fünf ausgewählte und besonders vielversprechende Use Cases verschiedener Industrien vor, die entweder bereits in Produktion anzutreffen sind, oder in den kommenden Jahren große Veränderungen in ihren jeweiligen Feldern versprechen.

Use Cases Computer Vision

1. Einzelhandel: Customer Behavior Tracking

Onlineshops wie Amazon können sich die Analysefähigkeit ihrer digitalen Plattform schon lange zunutze machen. Das Verhalten der Kundschaft kann detailliert analysiert und die User Experience dadurch optimiert werden. Auch die Retailbranche versucht die Erfahrung ihrer Kundschaft zu optimieren und ideal zu gestalten. Allerdings haben bisher die Tools gefehlt, um Interaktion von Personen mit ausgestellten Gegenständen automatisch zu erfassen. Computer Vision vermag diese Lücke für den Einzelhandel nun ein Stück weit zu schließen.

In Kombination mit bestehenden Sicherheitskameras können Algorithmen Videomaterial automatisch auswerten und somit das Kundschaftsverhalten innerhalb des Ladens studieren. Beispielsweise kann die aktuelle Anzahl an Personen im Laden jederzeit gezählt werden, was sich zu Zeiten der COVID-19 Pandemie mit den Auflagen zur maximal erlaubten Anzahl an Besuchern in Geschäften als Anwendungsgebiet anbietet. Interessanter dürften aber Analysen auf der Individualebene sein, wie die gewählte Route durch das Geschäft und einzelne Abteilungen. Damit lassen sich das Design, der Aufbau und die Platzierung von Produkten optimieren, Staus in gut besuchten Abteilungen vermeiden und insgesamt die User Experience der Kundschaft verbessern. Revolutionär ist die Möglichkeit zum Tracking der Aufmerksamkeit, welche einzelne Regale und Produkte von der Kundschaft erhalten. Spezialisierte Algorithmen sind dazu in der Lage, die Blickrichtung von Menschen zu erfassen und somit zu messen, wie lange ein beliebiges Objekt von Passanten betrachtet wird.

Mithilfe dieser Technologie hat der Einzelhandel nun die Möglichkeit zum Onlinehandel aufzuschließen und das Kundschaftsverhalten innerhalb ihrer Geschäfte detailliert auszuwerten. Dies ermöglicht nicht nur die Steigerung von Absätzen, sondern auch die Minimierung der Aufenthaltszeit und optimierte Verteilung von Kunden innerhalb der Ladenfläche.


Abbildung 1: Customer Behavior Tracking mit Computer Vision
(https://www.youtube.com/watch?v=jiaNA1hln5I)

2. Landwirtschaft: Erkennung von Weizenrost mittels Computer Vision

Moderne Technologien ermöglichen Landwirtschaftsbetrieben die effiziente Bestellung immer größerer Felder. Dies hat gleichzeitig zur Folge, dass diese Flächen auf Schädlinge und Pflanzenkrankheiten überprüfen müssen, denn falls übersehen, können Pflanzenkrankheiten zu schmerzhaften Ernteeinbrüchen und Verlusten führen.

Machine Learning verschafft hier Abhilfe, denn mittels des Einsatzes von Drohnen, Satellitenbildern und Remote-Sensoren können große Datenmengen generiert werden. Moderne Technologie erleichtert die Erhebung unterschiedlicher Messwerte, Parameter und Statistiken, welche automatisiert überwacht werden können. Landwirtschaftsbetriebe haben somit rund um die Uhr einen Überblick über die Bodenbedingungen, Bewässerungsgrad, Pflanzengesundheit und lokalen Temperaturen, trotz der großflächigen Bepflanzung von stetig größeren Feldern. Machine Learning Algorithmen werten diese Daten aus. So kann der Landwirtschaftbetrieb frühzeitig anhand dieser Informationen auf potenzielle Problemherde reagieren und vorhandene Ressourcen effizient verteilen kann.

Computer Vision ist für die Landwirtschaft besonders interessant, denn durch die Analyse von Bildmaterial lassen sich Pflanzenkrankheiten bereits im Anfangsstadium erkennen. Vor wenigen Jahren wurden Pflanzenkrankheiten häufig erst dann erkannt wurden, wenn sie sich bereits ausbreiten konnten. Basierend auf Computer Vision lässt sich die großflächige Ausbreitung mittels Frühwarnsysteme nun frühzeitig erkennen und stoppen. Landwirtschaftsbetriebe verlieren dadurch nicht nur weniger Ernte, sie sparen auch beim Einsatz von Gegenmaßnahmen wie Pestiziden, da vergleichsweise kleinere Flächen behandelt werden müssen.

Besonders die automatisierte Erkennung von Weizenrost hat innerhalb der Computer Vision Community viel Aufmerksamkeit erhalten. Verschiedene Vertreter dieses aggressiven Pilzes befallen Getreide in Ostafrika, rund ums Mittelmeer, wie auch in Zentraleuropa und führen zu großen Ernteausfällen von Weizen. Da der Schädling an Stängeln und Blättern von Getreide gut sichtbar ist, lässt er sich von trainierten Bilderkennungsalgorithmen schon früh erkennen und an der weiteren Ausbreitung hindern.


Abbildung 2: Erkennung von Weizenrost mit Computer Vision
(https://www.kdnuggets.com/2020/06/crop-disease-detection-computer-vision.html)

3. Gesundheitswesen: Bildsegmentierung von Scans

Das Potenzial von Computer Vision im Gesundheitswesen ist riesig, die möglichen Anwendungen zahllos. Die medizinische Diagnostik verlässt sich stark auf das Studium von Bildern, Scans und Fotografien. Die Analyse von Ultraschallbildern, MRI- und CT-Scans gehören zum Standardrepertoire der modernen Medizin. Computer Vision Technologien versprechen diesen Prozess nicht nur zu vereinfachen, sondern auch Fehldiagnosen vorzubeugen und entstehende Behandlungskosten zu senken. Computer Vision soll dabei medizinisches Fachpersonal nicht ersetzen, sondern deren Arbeit erleichtern und bei Entscheidungen unterstützen. Bildsegmentierung hilft bei der Diagnostik, indem relevante Bereiche auf 2D- oder 3D Scans erkannt und eingefärbt werden können, um das Studium von Schwarz-Weiß-Bildern zu erleichtern.

Der neuste Use Case für diese Technologie liefert die COVID-19 Pandemie. Bildsegmentierung kann Ärzt*innen und Wissenschaftler*innen bei der Identifikation von COVID-19 und der Analyse und Quantifizierung der Ansteckung und des Krankheitsverlaufs unterstützen. Der trainierte Bilderkennungsalgorithmus identifiziert verdächtige Stellen auf CT-Scans der Lunge. Anschließend ermittelt er deren Größe und Volumen, sodass der Krankheitsverlauf betroffener Patienten klar verfolgt werden kann.

Der Nutzen für das Monitoring einer neuen Krankheit ist riesig. Computer Vision erleichtert Ärzt*innen nicht nur die Diagnose der Krankheit und Überwachung während der Therapie. Die Technologie generiert auch wertvolle Daten zum Studium der Krankheit und ihrem Verlauf. Dabei profitiert auch die Forschung von den erhobenen Daten und dem erstellten Bildmaterial, sodass mehr Zeit für Experimente und Teste anstatt der Datenerhebung verwendet werden kann.

4. Automobil Industrie: Objekterkennung und -klassifizierung im Verkehr

Selbstfahrende Autos gehören definitiv zu den Use Cases aus dem Bereich der künstlichen Intelligenz, denen in letzten Jahren medial am meisten Aufmerksamkeit gewidmet wurde. Zu erklären ist dies wohl eher mit dem futuristischen Anstrich der Idee von autonomem Fahren als den tatsächlichen Konsequenzen der Technologie. Im Grunde genommen sind darin mehrere Machine Learning Probleme verpackt, Computer Vision bildet aber ein wichtiges Kernstück bei deren Lösung.

So muss der Algorithmus (der sogenannte «Agent»), von dem das Auto gesteuert wird, jederzeit über die Umgebung des Autos aufgeklärt sein. Der Agent muss wissen wie die Straße verläuft, wo sich andere Autos in der Nähe befinden, wie groß der Abstand zu potenziellen Hindernissen und Objekten ist und wie schnell sich diese Objekte auf der Straße bewegen, um sich konstant der sich stets ändernden Umwelt anpassen zu können. Dazu sind autonome Fahrzeuge mit umfangreichen Kameras ausgestattet, welche ihre Umgebung flächendeckend filmen. Das erstellte Filmmaterial wird anschließend in Echtzeit von einem Bilderkennungsalgorithmus überwacht. Ähnlich wie beim Customer Behavior Tracking setzt dies voraus, dass der Algorithmus nicht nur statische Bilder, sondern einen konstanten Fluss an Bildern nach relevanten Objekten absuchen und diese klassifizieren kann.


Abbildung 5: Objekterkennung und Klassifizierung im Straßenverkehr (https://miro.medium.com/max/1000/1*Ivhk4q4u8gCvsX7sFy3FsQ.png)

Diese Technologie existiert bereits und kommt auch industriell zum Einsatz. Die Problematik im Straßenverkehr stammt von dessen Komplexität, Volatilität und der Schwierigkeit, einen Algorithmus so zu trainieren, dass auch etwaiges Versagen des Agenten in komplexen Ausnahmesituationen ausgeschlossen werden kann. Dabei entblößt sich die Achillessehne von Computer Vision: Der Bedarf nach großen Mengen an Trainigsdaten, deren Generierung im Straßenverkehr mit hohen Kosten verbunden ist.

5. Fitness: Human Pose Estimation

Die Fitnessbranche befindet sich seit Jahren im Prozess der digitalen Transformation. Neue Trainingsprogramme und Trends werden via YouTube einem Millionenpublikum vorgestellt, Trainingsfortschritte werden mit Apps verfolgt und ausgewertet und spätestens seit dem Beginn der Coronakrise erfreuen sich virtuelle Trainings und Home Workouts massiver Beliebtheit. Gerade beim Kraftsport lassen sich Fitnesstrainer*innen aufgrund der hohen Verletzungsgefahr nicht aus dem Studio wegdenken – noch nicht. Denn während heute das Überprüfen der eigenen Haltung und Position beim Training via Video bereits gängig ist, ermöglicht es Computer Vision auch in diesem Feld Videomaterial genauer als das menschliche Auge auszuwerten und zu beurteilen.

Zum Einsatz kommt dabei eine Technologie, die dem bereits vorgestellten Attention Tracking der Einzelhandelsbranche ähnelt. Human Pose Estimation ermöglicht einem Algorithmus das Erkennen und Schätzen der Haltung und Pose von Menschen auf Video. Dazu wird die Position der Gelenke und deren Stellung im Bezug zueinander ermittelt. Da der Algorithmus gelernt hat, wie die ideale und sichere Ausführung einer Fitnessübung aussehen soll, lassen sich Abweichungen davon automatisiert erkennen und hervorheben. Implementiert in einer Smartphone App kann dies in Echtzeit und mit unmittelbarem Warnsignal geschehen. Somit kann rechtzeitig vor gefährlichen Fehlern gewarnt werden, anstatt Bewegungen erst im Nachhinein zu analysieren. Dies verspricht das Verletzungsrisiko beim Krafttraining maßgeblich zu reduzieren. Training ohne Fitnesstrainer*innen wird dadurch sicherer und die Kosten für sicheres Krafttraining werden gesenkt.

Human Pose Estimation ist ein weiterer Schritt in Richtung digitalem Fitnesstraining. Smartphones sind im Fitnesstraining bereits weitgehend etabliert. Apps, die das Training sicherer machen, dürften bei der breiten Nutzerbasis großen Anklang finden.


Abbildung 6: Analyse von Bewegungsabläufen in Echtzeit mit Computer Vision
(https://mobidev.biz/wp-content/uploads/2020/07/detect-mistakes-knee-cave.gif)

Zusammenfassung

Computer Vision ist ein vielseitiges und vielversprechendes Feld von Machine Learning. Es verspricht die Lösung einer breiten Palette von Problemen in verschiedensten Branchen und Industrien. Das Verarbeiten von Bild- und Videomaterial in Echtzeit ermöglicht die Lösung von Problemstellungen weit komplexer als mit herkömmlichen Datenformaten. Das bringt den Stand von Machine Learning den «intelligenten» Systemen immer näher. Bereits heute bieten sich immer häufiger alltägliche Schnittstellen zu Computer Vision an – ein Trend, der sich in den kommenden Jahren nur zu beschleunigen scheint.

Die hier vorgestellten Beispiele sind nur die Spitze des Eisbergs. Tatsächlich gibt es in jeder der genannten Branchen große Bestrebungen mithilfe von Computer Vision Technologie bestehende Prozesse effizienter zu gestalten. Aktuell gibt es viele Bestrebungen Computer Vision in die dritte Dimension zu heben und anstelle von Fotos und Scans auch 3D-Modelle verarbeiten zu lassen. Die Nachfrage nach industrieller Bildverarbeitung in 3D wächst, sowohl in der Vermessung, der Medizin, wie auch der Robotik. Die Verarbeitung von 3D-Bildmaterial wird in den kommenden Jahren noch Beachtung erhalten, denn viele Problemstellungen lassen sich erst in 3D effizient lösen.

Oliver Guggenbühl Oliver Guggenbühl

In meinem ersten Blogbeitrag dieser Reihe habe ich gezeigt, wie du deine R-Skripte in einem Docker-Container ausführen kannst. Für viele der Projekte, an denen wir hier bei STATWORX arbeiten, verwenden wir das RShiny-Framework, um unsere Produkte in interaktive Webapplikationen zu verwandeln.

Die Verwendung von Containern hat für die Bereitstellung von ShinyApps eine Vielzahl von Vorteilen. Zunächst sind es die üblichen Vorzüge wie einfache Cloud-Bereitstellung, Skalierbarkeit und praktisches Scheduling, aber es behebt auch einen der wesentlichen Nachteile von Shiny: Shiny erstellt nur eine einzige R-Sitzung pro App. Sollten also mehrere User auf dieselbe App zugreifen, dann arbeiten sie alle mit der selben R-Sitzung, was zu einer Vielzahl von Problemen führt. Mithilfe von Docker können wir dieses Problem umgehen und für jede:n User:in eine eigene Container-Instanz starten. Dadurch erhält jede:r User:in Zugriff auf eine eigene Instanz der App und somit eine eigene R-Sitzung. Ich gehe davon aus, dass du meinen vorigen Blogbeitrag über das Einbinden von R-Skripten in ein Docker-Image gelesen hast oder bereits über Grundkenntnisse der Docker-Terminologie verfügst.

Lassen wir also einfache R-Skripte hinter uns und führen wir jetzt ganze ShinyApps in Docker aus!

 

Das Setup

Einrichten eines Projekts

Für die Arbeit mit ShinyApps ist es ratsam, das Projekt-Setup von RStudio zu nutzen, insbesondere wenn man Docker verwendet. Projekte erleichtern nicht nur die Ordnung in RStudio, sondern ermöglichen es uns auch, mit dem Paket renv eine Paketbibliothek für unser spezifisches Projekt einzurichten. Dies ist besonders praktisch, um die benötigten Pakete für ein Programm in ein Docker-Image zu installieren.

Zu Demonstrationszwecken verwende ich eine Beispiel-App, die in einem früheren Blogbeitrag erstellt wurde und die du aus dem STATWORX GitHub Repository klonen kannst. Sie befindet sich im Unterordner „example-app“ und besteht aus den drei typischen Skripten, die von ShinyApps genutzt werden (global.R, ui.R und server.R) sowie aus Dateien, die zur Paketbibliothek renv gehören. Solltest du zur Übung ebenfalls die oben verlinkte Beispiel-App verwenden, musst du kein eigenes RStudio-Projekt einrichten. Stattdessen kannst du example-app.Rproj öffnen, das den von mir bereits eingerichteten Projektkontext startet. Falls du direkt mit einer eigenen App arbeiten möchtest und noch kein Projekt dafür erstellt hast, kannst du dein eigenes Projekt einrichten, indem du die von RStudio bereitgestellten Anweisungen befolgst.

Einrichten einer Paketbibliothek

Das RStudio-Projekt, das ich zur Verfügung gestellt habe, wird bereits mit einer Paketbibliothek geliefert, die in der Datei renv.lock gespeichert ist. Wenn du es vorziehst, mit deiner eigenen Anwendung zu arbeiten, kannst du deine eigene renv.lock Datei erstellen, indem du das renv Paket von deinem RStudio Projekt aus installierst und renv::init() ausführst. Dies initialisiert renv für dein Projekt und erstellt eine renv.lock Datei in deinem Projekt-Stammverzeichnis. Mehr Informationen über renv findest du unter RStudio’s Einführungsartikel.

Das Dockerfile

Das Dockerfile ist erneut das zentrale Element bei der Erstellung des Docker-Images. Während wir bisher nur ein einziges Skript in ein Image eingebaut haben, wollen wir diesen Prozess nun für eine ganze Anwendung wiederholen. Der Schritt von einem einzelnen Skript zu einem Ordner mit mehreren Skripten ist klein, aber es sind einige bedeutende Änderungen erforderlich, damit die App reibungslos läuft.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/shiny:latest

# system libraries of general use
## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install 
    libxml2-dev 
    libcairo2-dev 
    libsqlite3-dev 
    libmariadbd-dev 
    libpq-dev 
    libssh2-1-dev 
    unixodbc-dev 
    libcurl4-openssl-dev 
    libssl-dev

## update system libraries
RUN apt-get update && 
    apt-get upgrade -y && 
    apt-get clean

# copy necessary files
## app folder
COPY /example-app ./app
## renv.lock file
COPY /example-app/renv.lock ./renv.lock

# install renv & restore packages
RUN Rscript -e 'install.packages("renv")'
RUN Rscript -e 'renv::consent(provided = TRUE)'
RUN Rscript -e 'renv::restore()'

# expose port
EXPOSE 3838

# run app on container start
CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"]

Das Base-Image

Die erste Änderung betrifft das Base-Image. Da wir hier eine ShinyApp verdockern, können wir uns eine Menge Arbeit ersparen, indem wir das Base Image „rocker/shiny“ verwenden. Dieses Image kümmert sich um die notwendigen Dependencies für die Ausführung einer ShinyApp und enthält mehrere R-Pakete, die bereits vorinstalliert sind.

Erforderliche Dateien

Es ist natürlich notwendig, alle relevanten Skripte und Dateien für deine ShinyApp in dein Docker-Image einzubauen. Das Dockerfile erledigt genau das, indem es den gesamten Ordner, der die Anwendung enthält, in das Image kopiert.

Um die benötigten R-Pakete in ihrer korrekten Version in das Docker-Image zu installieren, verwenden wir am besten renv. Deshalb kopieren wir zuerst die Datei renv.lock separat in das Image. Das renv-Paket muss ebenfalls separat installiert werden, indem wir die Fähigkeit des Dockerfiles nutzen, mithilfe von RUN Rscript -e R-Code auszuführen. Diese Paketinstallation erlaubt es uns renv direkt aufzurufen und die kopierte Paketbibliothek in renv.lock innerhalb des Images mit renv::restore() wiederherzustellen. Dadurch wird die gesamte Paketbibliothek im Docker-Image installiert, mit genau der gleichen Version und dem gleichen Quellcode aller Pakete wie in der lokalen Projektumgebung. Und all dies mit nur ein paar Zeilen Code in unserem Dockerfile.

Starten der App zur Laufzeit

Ganz am Ende des Dockerfiles weisen wir den Container an, den folgenden R-Befehl auszuführen:

shiny::runApp('/app', host = '0.0.0.0', port = 3838)

Das erste Argument legt den Dateipfad zu den benötigten Skripten fest, welche in diesem Fall unter ./app abgelegt sind. Für den exponierten Port habe ich 3838 gewählt. Dies entspricht der Standardeinstellung für RStudio Server, kann aber nach Belieben geändert werden.

Wenn der letzte Befehl ausgeführt wird, startet jeder Container, der auf diesem Image basiert, die betreffende Anwendung automatisch zur Laufzeit (und schließt sie natürlich wieder, wenn sie beendet wurde).

Der letzte Schliff

Mit dem fertigen Dockerfile fehlen nur noch wenige Schritte bis zum laufenden Docker-Container. Dazu muss zunächst noch das Image erstellt werden, um anschließend einen Container basierend auf diesem Image zu starten.

Erstellen des Images

Also öffnen wir das Terminal, navigieren zu dem Ordner, der unser neues Dockerfile enthält und starten den Erstellungsprozess des Images:

docker build -t my-shinyapp-image . 

Starten eines Containers

Nachdem dieser Prozess abgeschlossen ist, können wir nun unser neu erstelltes Image testen, indem wir einen Container starten:

docker run -d --rm -p 3838:3838 my-shinyapp-image

Und da ist sie, die ShinyApp läuft auf localhost:3838!

docker-shiny-app-example

Ausblick

Jetzt, wo die ShinyApp in einem Docker-Container läuft, ist sie bereit für den Einsatz. Die Containerisierung unserer App macht diesen Prozess bereits sehr viel einfacher. Es gibt jedoch noch weitere Tools, die wir einsetzen, um Sicherheit, Skalierbarkeit und nahtloses Deployment auf dem neuesten Stand der Technik zu gewährleisten. Im nächsten Beitrag erwartet dich deshalb eine Einführung in ShinyProxy, womit du noch tiefer in das Spektrum an Möglichkeiten von RShiny und Docker eintauchen kannst.

Oliver Guggenbühl Oliver Guggenbühl

Seit der Veröffentlichung im Jahr 2014 hat sich Docker zu einem unverzichtbaren Werkzeug für die Bereitstellung von Anwendungen entwickelt. Bei STATWORX ist R eines unserer täglichen Werkzeuge, weshalb viele von uns begeistert sind von RStudio’s Rocker Projekt, das die Containerisierung von R-Code einfacher macht denn je.

Containerisierung ist in vielen verschiedenen Situationen nützlich. Die Technologie ist äußerst hilfreich beim Einsatz von R-Code in einer Cloud-Computing-Umgebung, in welcher der gecodete Arbeitsablauf in regelmäßigen Abständen ausgeführt werden soll. Docker ist für diese Aufgabe aus zwei Gründen perfekt geeignet: Container können automatisiert in gewünschten Intervallen gestartet werden und aufgrund ihrer statischen Natur ist immer klar, welches Verhalten und welchen Output du von einem Docker-Container zu erwarten hast. Wenn du also vor der Aufgabe stehst, ein Machine-Learning-Modell für regelmäßige Prognosen einzusetzen, dann lohnt es sich dies mit Docker tun. Dieser Blogbeitrag führt dich Schritt für Schritt durch den gesamten Prozess, wie du dein R-Skript in einem Docker-Container zum Laufen bringst. Der Einfachheit halber werden wir mit einem lokalen Datensatz arbeiten.

Zu Beginn möchte ich betonen, dass dieser Blogbeitrag kein allgemeines Docker-Tutorial ist. Wenn du dir nicht sicher bist was mit Images und Containern gemeint ist, dann empfehle ich dir, zunächst einen Blick auf das Docker Curriculum zu werfen. Solltest du daran interessiert sein, eine RStudio-Sitzung in einem Docker-Container laufen zu lassen, empfehle ich dir stattdessen dem OpenSciLabs Docker Tutorial einen Besuch abzustatten.

In diesem Blogbeitrag geht es konkret um die Containerisierung eines R-Skripts, damit es schließlich bei jedem Start des Containers automatisch ausgeführt wird, ohne dass der Benutzer eingreifen muss – damit entfällt die Notwendigkeit des RStudio-IDE. Ich werde nur kurz auf die im Dockerfile und in der Kommandozeile verwendete Syntax eingehen, so dass du dich am besten mit den Grundlagen von Docker bereits vor dem Weiterlesen vertraut machst.

Was wir brauchen:

Für diesen gesamten Workflow benötigen wir folgendes:

  • ein R-Skript, das wir in ein Docker-Image einbauen
  • ein Base-Image, auf dem wir unser eigenes Image aufbauen
  • ein Dockerfile, mit der wir unser neues Image definieren

Du kannst alle Dateien und die verwendete Ordnerstruktur aus dem STATWORX GitHub Repository klonen.

Das R-Skript

Wir arbeiten mit einem sehr einfachen R-Skript, das einen Datensatz importiert, als Dataframe manipuliert, ein Diagramm auf der Grundlage der manipulierten Daten erstellt und zum Schluss sowohl das Diagramm als auch die darauf basierenden Daten exportiert. Der für dieses Beispiel verwendete Datensatz ist US 500 Records, der von Brian Dunning zur Verfügung gestellt wird. Wenn du dem Beispiel folgen möchtest, empfehle ich dir, diesen Datensatz in den Ordner 01_data zu kopieren.

library(readr)
library(dplyr)
library(ggplot2)
library(forcats)

# import dataframe
df <- read_csv("01_data/us-500.csv")

# manipulate data
plot_data <- df %>%
  group_by(state) %>%
  count()

# save manipulated data to output folder
write_csv(plot_data, "03_output/plot_data.csv")

# create plot based on manipulated data
plot <- plot_data %>% 
  ggplot()+
  geom_col(aes(fct_reorder(state, n), 
               n, 
               fill = n))+
  coord_flip()+
  labs(
    title = "Number of people by state",
    subtitle = "From US-500 dataset",
    x = "State",
    y = "Number of people"
  )+ 
  theme_bw()

# save plot to output folder
ggsave("03_output/myplot.png", width = 10, height = 8, dpi = 100)

Damit wird ein einfaches Balkendiagramm auf der Grundlage des Datensatzes erstellt:

Wir verwenden dieses Skript, weil wir nicht nur R-Code innerhalb eines Docker-Containers ausführen, sondern diesen R-Code auf Daten von außerhalb unseres Containers anwenden und die Ergebnisse anschließend speichern wollen.

Das Base-Image

Die DockerHub-Seite des Rocker-Projekts listet alle verfügbaren Rocker-Repositories auf. Da wir in unserem Skript Tidyverse-Pakete verwenden, ist das rocker/tidyverse-Image eine naheliegende Wahl. Das Problem mit diesem Repository ist, dass es auch RStudio selbst enthält, was wir für dieses spezifische Projekt nicht benötigen. Das bedeutet, dass wir stattdessen mit dem r-base Repository arbeiten und unser eigenes Tidyverse-fähiges Image erstellen. Wir können das rocker/r-base-Image von DockerHub nutzen, indem wir den folgenden Befehl im Terminal ausführen:

docker pull rocker/r-base

Dadurch wird das Base-R-Image aus dem Rocker DockerHub-Repository lokal gespeichert („gepullt“). Wir können einen auf diesem Image basierenden Container starten, indem wir im Terminal folgenden Bashcode ausführen:

docker run -it --rm rocker/r-base

Herzlichen Glückwunsch, du führst jetzt R innerhalb eines Docker-Containers aus! Das Terminal wurde in eine R-Konsole verwandelt, mit der wir dank des Arguments -it interagieren können. Das Argument --rm sorgt dafür, dass der Container automatisch gelöscht wird, sobald wir ihn stoppen. Es steht dir frei, mit deiner containerisierten R-Sitzung zu experimentieren (die du mit der Funktion q() in der R-Konsole wieder beenden kannst). Du kannst zum Beispiel damit beginnen, die Pakete, die du für deinen Arbeitsablauf benötigst, mit install.packages() zu installieren, aber das ist eine mühsame und zeitraubende Herangehensweise. Besser ist es, die gewünschten Pakete bereits in das Image einzubauen, damit die benötigten Pakete nicht nach jedem Containerstart erneut manuell installiert werden müssen. Dazu benötigen wir ein Dockerfile.

Das Dockerfile

Mit einem Dockerfile teilen wir Docker mit, wie unser gewünschtes Image erstellt werden soll. Ein Dockerfile ist eine Textdatei, die „Dockerfile.txt“ heißen muss und standardmäßig im Stammverzeichnis des „Build-Kontextes“ liegt (in unserem Fall wäre das der Ordner „R-Script in Docker“).

Zunächst legen wir ein bestehendes Docker-Image fest, basierend auf dem wir unser neues Image erstellen möchten. Anschließend verfassen wir eine Liste von Anweisungen, die unser Image so definieren, dass die Ausführung von Containern reibungslos und effizient verläuft. In unserem Fall möchte ich unser neues Image auf dem zuvor besprochenen rocker/r-base-Image aufbauen. Ebenfalls möchte ich auch die lokale Ordnerstruktur replizieren, also erstelle ich die gewünschten Verzeichnisse direkt mit dem Dockerfile. Danach werden alle Dateien, auf die das Image Zugriff haben soll, in diese Verzeichnisse kopiert – so wird das R-Skript in das Docker-Image eingebaut. Auf diese Weise können wir auch die manuelle Installation von Paketen nach dem Starten eines Containers umgehen, da wir ein zweites R-Skript vorbereiten können, das sich um die Paketinstallation kümmert. Es reicht nicht aus, das R-Skript einfach zu kopieren, wir müssen Docker auch anweisen, es beim Erstellen des Images automatisch auszuführen. Und das ist unser erstes Dockerfile:

# Base image https://hub.docker.com/u/rocker/
FROM rocker/r-base:latest

## create directories
RUN mkdir -p /01_data
RUN mkdir -p /02_code
RUN mkdir -p /03_output

## copy files
COPY /02_code/install_packages.R /02_code/install_packages.R
COPY /02_code/myScript.R /02_code/myScript.R

## install R-packages
RUN Rscript /02_code/install_packages.R

Vergiss nicht, dein entsprechendes install_packages.R-Skript vorzubereiten und zu speichern. Dazu gibst du im Skript an, welche R-Pakete in deinem Image vorinstalliert werden sollen. In unserem Fall sieht die Datei folgendermaßen aus:

install.packages("readr")
install.packages("dplyr")
install.packages("ggplot2")
install.packages("forcats")

rstellen und Ausführen des Images

Nun haben wir alle benötigten Komponenten für unser neues Docker-Image vorbereitet. Verwende das Terminal, um zu dem Ordner zu navigieren, in dem sich dein Dockerfile befindet und erstelle das Image mit:

docker build -t myname/myimage .

Der Prozess wird aufgrund der Paketinstallation einen Moment dauern. Sobald er abgeschlossen ist, kannst du das neue Image testen, indem du einen Container startest mit:

docker run -it --rm -v ~/"R-Script in Docker"/01_data:/01_data -v ~/"R-Script in Docker"/03_output:/03_output myname/myimage

Die Verwendung der -v-Argumente teilt Docker mit, welche lokalen Ordner den erstellten Ordnern innerhalb des Containers zugeordnet werden sollen. Dies ist wichtig, da wir so sowohl Zugriff auf unseren Datensatz von innerhalb des Containers erhalten, als auch den erstellen Output des Workflows lokal abspeichern können. Dadurch muss der Datensatz nicht in das Image eingebaut werden und wenn das Image gestoppt wird, gehen keine Outputs verloren.

Dieser Container kann nun mit dem Datensatz im Ordner 01_data interagieren und hat eine Kopie unseres Workflow-Skripts in seinem eigenen Ordner 02_code. Wenn Sie R anweisen, source("02_code/myScript.R") auszuführen, wird das Skript ausgeführt und der Output im Ordner 03_output gespeichert, von wo aus er auch in den lokalen Ordner 03_output kopiert wird.

Verbessern was wir haben

Nachdem wir nun getestet und bestätigt haben, dass unser R-Skript im Container wie erwartet läuft, fehlen nur noch ein paar wenige Dinge.

  1. Wir wollen das Skript nicht manuell aus dem Container heraus aufrufen, sondern es automatisch ausführen lassen, sobald der Container gestartet wird.

Dies können wir ganz einfach erreichen, indem wir den folgenden Befehl an das Ende unserer Dockerdatei anhängen:

## run the script
CMD Rscript /02_code/myScript.R

Das verweist auf den Speicherort unseres Skripts in der Ordnerstruktur des Containers, markiert es als R-Code und weist Docker dann an, beim Starten des Containers das Skript gleich auszuführen. Änderungen am Dockerfile bedeuten natürlich, dass wir unser Image neu erstellen müssen, und das wiederum bedeutet, dass der langsame Prozess der Paketinstallationen wiederholt werden muss. Das ist mühsam, vor allem wenn die Wahrscheinlichkeit besteht, dass es im Laufe der Zeit weitere Überarbeitungen der einzelnen Komponenten unseres Images geben wird. Deshalb schlage ich folgendes vor:

  1. Erstelle ein Docker-Zwischen-Image, auf dem alle wichtigen Pakete und Abhängigkeiten installiert sind. Auf diesem Zwischen-Image als Basis bauen wir dann unser gewünschtes, finales Image auf.

Auf diese Weise wird die Paketinstallation vom eigentlichen Image entkoppelt, so dass unser finales Image innerhalb von Sekunden neu gebaut werden kann. Dies ermöglicht es uns, frei mit dem Code zu experimentieren, ohne dass wir uns immer wieder mit der Installation von Paketen durch Docker beschäftigen müssen.

Erstellen eines Zwischen-Images

Das Dockerfile für unser Zwischen-Image sieht unserem vorherigen Beispiel sehr ähnlich. Da ich mich entschieden habe, das install_packages()-Skript zu modifizieren, um das gesamte tidyverse für die zukünftige Verwendung einzuschließen, müssen auch einige Debian-Pakete installiert werden, die das tidyverse benötigt. Nicht alle davon sind absolut notwendig, aber alle sind auf die eine oder andere Weise nützlich.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/r-base:latest

## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install \
libxml2-dev \
libcairo2-dev \
libsqlite3-dev \
libmariadbd-dev \
libpq-dev \
libssh2-1-dev \
unixodbc-dev \
libcurl4-openssl-dev \
libssl-dev

## copy files
COPY 02_code/install_packages.R /install_packages.R

## install R-packages
RUN Rscript /install_packages.R

Ich baue das Image, indem ich im Terminal zu dem Ordner, in dem sich mein Dockerfile befindet, navigiere und den Befehl docker build erneut ausführe:

docker build -t oliverstatworx/base-r-tidyverse .

Ich habe dieses Image auch auf meinen DockerHub gepusht. So kannst du, wenn du jemals ein Base-R-Image mit vorinstalliertem tidyverse benötigst, es einfach auf meinem Image aufbauen, ohne dieses selbst erstellen zu müssen.

Nun, da das Zwischen-Image erstellt wurde, können wir unser ursprüngliches Dockerfile so ändern, dass es anstelle von rocker/r-base darauf aufbaut. Da sich unser Zwischen-Image bereits um die Paketinstallation kümmert kann dieser Abschnitt entfernt werden. Wir fügen auch die letzte Zeile hinzu, die unser Skript automatisch ausführt, sobald der Container gestartet wird. Unser endgültiges Dockerfile sollte in etwa so aussehen:

# Base image https://hub.docker.com/u/oliverstatworx/
FROM oliverstatworx/base-r-tidyverse:latest

## create directories
RUN mkdir -p /01_data
RUN mkdir -p /02_code
RUN mkdir -p /03_output

## copy files
COPY /02_code/myScript.R /02_code/myScript.R

## run the script
CMD Rscript /02_code/myScript.R

Der letzte Schliff

Da wir unser Image auf einem Zwischen-Image mit all unseren benötigten Paketen aufgebaut haben, können wir nun beliebig Teile des Dockerfiles ohne großen Zeitaufwand verändern. Beispielsweise kann es sinnvoll sein, das R-Skript so zu gestalten, dass Warnungen und Meldungen, die nicht mehr von Interesse sind (da das Image bereits getestet wurde und alles wie erwartet funktioniert) unterdrückt werden. Des weiteren können Meldungen hinzufügt werden, die dem Benutzenden mitteilen, welcher Teil des Skripts gerade von dem laufenden Container ausgeführt wird.

suppressPackageStartupMessages(library(readr))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(forcats))

options(scipen = 999,
        readr.num_columns = 0)

print("Starting Workflow")

# import dataframe
print("Importing Dataframe")
df <- read_csv("01_data/us-500.csv")

# manipulate data
print("Manipulating Data")
plot_data <- df %>%
  group_by(state) %>%
  count()

# save manipulated data to output folder
print("Writing manipulated Data to .csv")
write_csv(plot_data, "03_output/plot_data.csv")

# create plot based on manipulated data
print("Creating Plot")
plot <- plot_data %>% 
  ggplot()+
  geom_col(aes(fct_reorder(state, n), 
               n, 
               fill = n))+
  coord_flip()+
  labs(
    title = "Number of people by state",
    subtitle = "From US-500 dataset",
    x = "State",
    y = "Number of people"
  )+ 
  theme_bw()

# save plot to output folder
print("Saving Plot")
ggsave("03_output/myplot.png", width = 10, height = 8, dpi = 100)
print("Worflow Finished")

Nachdem wir mit dem Terminal zu dem Ordner navigieren, in dem sich unser Dockerfile befindet, bauen wir das Image noch einmal neu: docker build -t myname/myimage . Erneut starten wir einen Container auf Basis unseres Images und weisen die Ordner 01_data und 03_output den lokalen Verzeichnissen zu, so dass die Daten importiert und die erstellten Outputs lokal gespeichert werden:

docker run -it --rm -v ~/"R-Script in Docker"/01_data:/01_data -v ~/"R-Script in Docker"/03_output:/03_output myname/myimage

Herzlichen Glückwunsch, du hast jetzt ein sauberes Docker-Image erstellt! Dieses führt beim Containerstart nicht nur automatisch dein R-Skript aus, sondern teilt dirauch über Konsolenmeldungen genau mit, welchen Teil des Codes gerade ausgeführt wird. Viel Spaß beim Dockern! Oliver Guggenbühl Oliver Guggenbühl

Seit der Veröffentlichung im Jahr 2014 hat sich Docker zu einem unverzichtbaren Werkzeug für die Bereitstellung von Anwendungen entwickelt. Bei STATWORX ist R eines unserer täglichen Werkzeuge, weshalb viele von uns begeistert sind von RStudio’s Rocker Projekt, das die Containerisierung von R-Code einfacher macht denn je.

Containerisierung ist in vielen verschiedenen Situationen nützlich. Die Technologie ist äußerst hilfreich beim Einsatz von R-Code in einer Cloud-Computing-Umgebung, in welcher der gecodete Arbeitsablauf in regelmäßigen Abständen ausgeführt werden soll. Docker ist für diese Aufgabe aus zwei Gründen perfekt geeignet: Container können automatisiert in gewünschten Intervallen gestartet werden und aufgrund ihrer statischen Natur ist immer klar, welches Verhalten und welchen Output du von einem Docker-Container zu erwarten hast. Wenn du also vor der Aufgabe stehst, ein Machine-Learning-Modell für regelmäßige Prognosen einzusetzen, dann lohnt es sich dies mit Docker tun. Dieser Blogbeitrag führt dich Schritt für Schritt durch den gesamten Prozess, wie du dein R-Skript in einem Docker-Container zum Laufen bringst. Der Einfachheit halber werden wir mit einem lokalen Datensatz arbeiten.

Zu Beginn möchte ich betonen, dass dieser Blogbeitrag kein allgemeines Docker-Tutorial ist. Wenn du dir nicht sicher bist was mit Images und Containern gemeint ist, dann empfehle ich dir, zunächst einen Blick auf das Docker Curriculum zu werfen. Solltest du daran interessiert sein, eine RStudio-Sitzung in einem Docker-Container laufen zu lassen, empfehle ich dir stattdessen dem OpenSciLabs Docker Tutorial einen Besuch abzustatten.

In diesem Blogbeitrag geht es konkret um die Containerisierung eines R-Skripts, damit es schließlich bei jedem Start des Containers automatisch ausgeführt wird, ohne dass der Benutzer eingreifen muss – damit entfällt die Notwendigkeit des RStudio-IDE. Ich werde nur kurz auf die im Dockerfile und in der Kommandozeile verwendete Syntax eingehen, so dass du dich am besten mit den Grundlagen von Docker bereits vor dem Weiterlesen vertraut machst.

Was wir brauchen:

Für diesen gesamten Workflow benötigen wir folgendes:

Du kannst alle Dateien und die verwendete Ordnerstruktur aus dem STATWORX GitHub Repository klonen.

Das R-Skript

Wir arbeiten mit einem sehr einfachen R-Skript, das einen Datensatz importiert, als Dataframe manipuliert, ein Diagramm auf der Grundlage der manipulierten Daten erstellt und zum Schluss sowohl das Diagramm als auch die darauf basierenden Daten exportiert. Der für dieses Beispiel verwendete Datensatz ist US 500 Records, der von Brian Dunning zur Verfügung gestellt wird. Wenn du dem Beispiel folgen möchtest, empfehle ich dir, diesen Datensatz in den Ordner 01_data zu kopieren.

library(readr)
library(dplyr)
library(ggplot2)
library(forcats)

# import dataframe
df <- read_csv("01_data/us-500.csv")

# manipulate data
plot_data <- df %>%
  group_by(state) %>%
  count()

# save manipulated data to output folder
write_csv(plot_data, "03_output/plot_data.csv")

# create plot based on manipulated data
plot <- plot_data %>% 
  ggplot()+
  geom_col(aes(fct_reorder(state, n), 
               n, 
               fill = n))+
  coord_flip()+
  labs(
    title = "Number of people by state",
    subtitle = "From US-500 dataset",
    x = "State",
    y = "Number of people"
  )+ 
  theme_bw()

# save plot to output folder
ggsave("03_output/myplot.png", width = 10, height = 8, dpi = 100)

Damit wird ein einfaches Balkendiagramm auf der Grundlage des Datensatzes erstellt:

Wir verwenden dieses Skript, weil wir nicht nur R-Code innerhalb eines Docker-Containers ausführen, sondern diesen R-Code auf Daten von außerhalb unseres Containers anwenden und die Ergebnisse anschließend speichern wollen.

Das Base-Image

Die DockerHub-Seite des Rocker-Projekts listet alle verfügbaren Rocker-Repositories auf. Da wir in unserem Skript Tidyverse-Pakete verwenden, ist das rocker/tidyverse-Image eine naheliegende Wahl. Das Problem mit diesem Repository ist, dass es auch RStudio selbst enthält, was wir für dieses spezifische Projekt nicht benötigen. Das bedeutet, dass wir stattdessen mit dem r-base Repository arbeiten und unser eigenes Tidyverse-fähiges Image erstellen. Wir können das rocker/r-base-Image von DockerHub nutzen, indem wir den folgenden Befehl im Terminal ausführen:

docker pull rocker/r-base

Dadurch wird das Base-R-Image aus dem Rocker DockerHub-Repository lokal gespeichert („gepullt“). Wir können einen auf diesem Image basierenden Container starten, indem wir im Terminal folgenden Bashcode ausführen:

docker run -it --rm rocker/r-base

Herzlichen Glückwunsch, du führst jetzt R innerhalb eines Docker-Containers aus! Das Terminal wurde in eine R-Konsole verwandelt, mit der wir dank des Arguments -it interagieren können. Das Argument --rm sorgt dafür, dass der Container automatisch gelöscht wird, sobald wir ihn stoppen. Es steht dir frei, mit deiner containerisierten R-Sitzung zu experimentieren (die du mit der Funktion q() in der R-Konsole wieder beenden kannst). Du kannst zum Beispiel damit beginnen, die Pakete, die du für deinen Arbeitsablauf benötigst, mit install.packages() zu installieren, aber das ist eine mühsame und zeitraubende Herangehensweise. Besser ist es, die gewünschten Pakete bereits in das Image einzubauen, damit die benötigten Pakete nicht nach jedem Containerstart erneut manuell installiert werden müssen. Dazu benötigen wir ein Dockerfile.

Das Dockerfile

Mit einem Dockerfile teilen wir Docker mit, wie unser gewünschtes Image erstellt werden soll. Ein Dockerfile ist eine Textdatei, die „Dockerfile.txt“ heißen muss und standardmäßig im Stammverzeichnis des „Build-Kontextes“ liegt (in unserem Fall wäre das der Ordner „R-Script in Docker“).

Zunächst legen wir ein bestehendes Docker-Image fest, basierend auf dem wir unser neues Image erstellen möchten. Anschließend verfassen wir eine Liste von Anweisungen, die unser Image so definieren, dass die Ausführung von Containern reibungslos und effizient verläuft. In unserem Fall möchte ich unser neues Image auf dem zuvor besprochenen rocker/r-base-Image aufbauen. Ebenfalls möchte ich auch die lokale Ordnerstruktur replizieren, also erstelle ich die gewünschten Verzeichnisse direkt mit dem Dockerfile. Danach werden alle Dateien, auf die das Image Zugriff haben soll, in diese Verzeichnisse kopiert – so wird das R-Skript in das Docker-Image eingebaut. Auf diese Weise können wir auch die manuelle Installation von Paketen nach dem Starten eines Containers umgehen, da wir ein zweites R-Skript vorbereiten können, das sich um die Paketinstallation kümmert. Es reicht nicht aus, das R-Skript einfach zu kopieren, wir müssen Docker auch anweisen, es beim Erstellen des Images automatisch auszuführen. Und das ist unser erstes Dockerfile:

# Base image https://hub.docker.com/u/rocker/
FROM rocker/r-base:latest

## create directories
RUN mkdir -p /01_data
RUN mkdir -p /02_code
RUN mkdir -p /03_output

## copy files
COPY /02_code/install_packages.R /02_code/install_packages.R
COPY /02_code/myScript.R /02_code/myScript.R

## install R-packages
RUN Rscript /02_code/install_packages.R

Vergiss nicht, dein entsprechendes install_packages.R-Skript vorzubereiten und zu speichern. Dazu gibst du im Skript an, welche R-Pakete in deinem Image vorinstalliert werden sollen. In unserem Fall sieht die Datei folgendermaßen aus:

install.packages("readr")
install.packages("dplyr")
install.packages("ggplot2")
install.packages("forcats")

rstellen und Ausführen des Images

Nun haben wir alle benötigten Komponenten für unser neues Docker-Image vorbereitet. Verwende das Terminal, um zu dem Ordner zu navigieren, in dem sich dein Dockerfile befindet und erstelle das Image mit:

docker build -t myname/myimage .

Der Prozess wird aufgrund der Paketinstallation einen Moment dauern. Sobald er abgeschlossen ist, kannst du das neue Image testen, indem du einen Container startest mit:

docker run -it --rm -v ~/"R-Script in Docker"/01_data:/01_data -v ~/"R-Script in Docker"/03_output:/03_output myname/myimage

Die Verwendung der -v-Argumente teilt Docker mit, welche lokalen Ordner den erstellten Ordnern innerhalb des Containers zugeordnet werden sollen. Dies ist wichtig, da wir so sowohl Zugriff auf unseren Datensatz von innerhalb des Containers erhalten, als auch den erstellen Output des Workflows lokal abspeichern können. Dadurch muss der Datensatz nicht in das Image eingebaut werden und wenn das Image gestoppt wird, gehen keine Outputs verloren.

Dieser Container kann nun mit dem Datensatz im Ordner 01_data interagieren und hat eine Kopie unseres Workflow-Skripts in seinem eigenen Ordner 02_code. Wenn Sie R anweisen, source("02_code/myScript.R") auszuführen, wird das Skript ausgeführt und der Output im Ordner 03_output gespeichert, von wo aus er auch in den lokalen Ordner 03_output kopiert wird.

Verbessern was wir haben

Nachdem wir nun getestet und bestätigt haben, dass unser R-Skript im Container wie erwartet läuft, fehlen nur noch ein paar wenige Dinge.

  1. Wir wollen das Skript nicht manuell aus dem Container heraus aufrufen, sondern es automatisch ausführen lassen, sobald der Container gestartet wird.

Dies können wir ganz einfach erreichen, indem wir den folgenden Befehl an das Ende unserer Dockerdatei anhängen:

## run the script
CMD Rscript /02_code/myScript.R

Das verweist auf den Speicherort unseres Skripts in der Ordnerstruktur des Containers, markiert es als R-Code und weist Docker dann an, beim Starten des Containers das Skript gleich auszuführen. Änderungen am Dockerfile bedeuten natürlich, dass wir unser Image neu erstellen müssen, und das wiederum bedeutet, dass der langsame Prozess der Paketinstallationen wiederholt werden muss. Das ist mühsam, vor allem wenn die Wahrscheinlichkeit besteht, dass es im Laufe der Zeit weitere Überarbeitungen der einzelnen Komponenten unseres Images geben wird. Deshalb schlage ich folgendes vor:

  1. Erstelle ein Docker-Zwischen-Image, auf dem alle wichtigen Pakete und Abhängigkeiten installiert sind. Auf diesem Zwischen-Image als Basis bauen wir dann unser gewünschtes, finales Image auf.

Auf diese Weise wird die Paketinstallation vom eigentlichen Image entkoppelt, so dass unser finales Image innerhalb von Sekunden neu gebaut werden kann. Dies ermöglicht es uns, frei mit dem Code zu experimentieren, ohne dass wir uns immer wieder mit der Installation von Paketen durch Docker beschäftigen müssen.

Erstellen eines Zwischen-Images

Das Dockerfile für unser Zwischen-Image sieht unserem vorherigen Beispiel sehr ähnlich. Da ich mich entschieden habe, das install_packages()-Skript zu modifizieren, um das gesamte tidyverse für die zukünftige Verwendung einzuschließen, müssen auch einige Debian-Pakete installiert werden, die das tidyverse benötigt. Nicht alle davon sind absolut notwendig, aber alle sind auf die eine oder andere Weise nützlich.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/r-base:latest

## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install \
libxml2-dev \
libcairo2-dev \
libsqlite3-dev \
libmariadbd-dev \
libpq-dev \
libssh2-1-dev \
unixodbc-dev \
libcurl4-openssl-dev \
libssl-dev

## copy files
COPY 02_code/install_packages.R /install_packages.R

## install R-packages
RUN Rscript /install_packages.R

Ich baue das Image, indem ich im Terminal zu dem Ordner, in dem sich mein Dockerfile befindet, navigiere und den Befehl docker build erneut ausführe:

docker build -t oliverstatworx/base-r-tidyverse .

Ich habe dieses Image auch auf meinen DockerHub gepusht. So kannst du, wenn du jemals ein Base-R-Image mit vorinstalliertem tidyverse benötigst, es einfach auf meinem Image aufbauen, ohne dieses selbst erstellen zu müssen.

Nun, da das Zwischen-Image erstellt wurde, können wir unser ursprüngliches Dockerfile so ändern, dass es anstelle von rocker/r-base darauf aufbaut. Da sich unser Zwischen-Image bereits um die Paketinstallation kümmert kann dieser Abschnitt entfernt werden. Wir fügen auch die letzte Zeile hinzu, die unser Skript automatisch ausführt, sobald der Container gestartet wird. Unser endgültiges Dockerfile sollte in etwa so aussehen:

# Base image https://hub.docker.com/u/oliverstatworx/
FROM oliverstatworx/base-r-tidyverse:latest

## create directories
RUN mkdir -p /01_data
RUN mkdir -p /02_code
RUN mkdir -p /03_output

## copy files
COPY /02_code/myScript.R /02_code/myScript.R

## run the script
CMD Rscript /02_code/myScript.R

Der letzte Schliff

Da wir unser Image auf einem Zwischen-Image mit all unseren benötigten Paketen aufgebaut haben, können wir nun beliebig Teile des Dockerfiles ohne großen Zeitaufwand verändern. Beispielsweise kann es sinnvoll sein, das R-Skript so zu gestalten, dass Warnungen und Meldungen, die nicht mehr von Interesse sind (da das Image bereits getestet wurde und alles wie erwartet funktioniert) unterdrückt werden. Des weiteren können Meldungen hinzufügt werden, die dem Benutzenden mitteilen, welcher Teil des Skripts gerade von dem laufenden Container ausgeführt wird.

suppressPackageStartupMessages(library(readr))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(forcats))

options(scipen = 999,
        readr.num_columns = 0)

print("Starting Workflow")

# import dataframe
print("Importing Dataframe")
df <- read_csv("01_data/us-500.csv")

# manipulate data
print("Manipulating Data")
plot_data <- df %>%
  group_by(state) %>%
  count()

# save manipulated data to output folder
print("Writing manipulated Data to .csv")
write_csv(plot_data, "03_output/plot_data.csv")

# create plot based on manipulated data
print("Creating Plot")
plot <- plot_data %>% 
  ggplot()+
  geom_col(aes(fct_reorder(state, n), 
               n, 
               fill = n))+
  coord_flip()+
  labs(
    title = "Number of people by state",
    subtitle = "From US-500 dataset",
    x = "State",
    y = "Number of people"
  )+ 
  theme_bw()

# save plot to output folder
print("Saving Plot")
ggsave("03_output/myplot.png", width = 10, height = 8, dpi = 100)
print("Worflow Finished")

Nachdem wir mit dem Terminal zu dem Ordner navigieren, in dem sich unser Dockerfile befindet, bauen wir das Image noch einmal neu: docker build -t myname/myimage . Erneut starten wir einen Container auf Basis unseres Images und weisen die Ordner 01_data und 03_output den lokalen Verzeichnissen zu, so dass die Daten importiert und die erstellten Outputs lokal gespeichert werden:

docker run -it --rm -v ~/"R-Script in Docker"/01_data:/01_data -v ~/"R-Script in Docker"/03_output:/03_output myname/myimage

Herzlichen Glückwunsch, du hast jetzt ein sauberes Docker-Image erstellt! Dieses führt beim Containerstart nicht nur automatisch dein R-Skript aus, sondern teilt dirauch über Konsolenmeldungen genau mit, welchen Teil des Codes gerade ausgeführt wird. Viel Spaß beim Dockern! Oliver Guggenbühl Oliver Guggenbühl