Data Science, Machine Learning und KI
Kontakt

Am vergangenen Freitag, den 31. Dezember, ging ein erstaunliches Jahr zu Ende, sowohl für mich als auch für STATWORX. Das Jahr 2021 war geprägt von (zu) viel Arbeit, großen Erfolgen und Siegen, aber auch bitteren Niederlagen und Verlusten. Im Laufe eines Jahres passieren so viele Dinge, die es wert sind, erlebt zu werden, seien sie gut oder schlecht, und die mich immer wieder daran erinnern, warum ich das, was ich tue, liebe. 2021 war mit Sicherheit eines der aufregendsten, herausforderndsten aber auch lohnendsten Jahre meiner bisherigen, beruflichen Laufbahn. Wie auch im letzten Jahr habe ich beschlossen, dieses Jahr mit einem kurzen Rückblick auf 2021 zu beginnen und einen Ausblick darauf zu geben, was im Jahr 2022 alles auf dem Programm steht. Spoiler-Alarm: Dieses Jahr wirft bereits große Schatten voraus – durch den Aufstieg von statworx next.

2021 – Das Jahr im Rückblick

Trotz der anhaltenden Pandemie hatte STATWORX in 2021 ein sehr erfolgreiches Geschäftsjahr. Nach einem eher unspektakulären Jahr 2020 ist es meinem Team und mir gelungen, Umsatz, Größe und Impact unseres Unternehmens im Jahr 2021 deutlich zu steigern. Vor allem das wichtige 4. Quartal hat in diesem Jahr anders zugeschlagen und steigerte unsere Umsatz- und Gewinnzahlen nochmals erheblich. Neben den reinen Zahlen haben unsere Teams viele großartige Dinge erreicht: Wir haben neue Abteilungen und Funktionen eingeführt, sowohl im Front- als auch im Backoffice. Wir verwenden jetzt OKR als unser offizielles Zielsetzungsframework. Wir hatten einen Exit unserer erste Start-up-Investition an Databricks. Wir haben in drei weitere KI-Start-ups investiert. Wir sind mehreren Verbänden beigetreten, wie dem KI Bundesverband und AI Frankfurt. Wir haben Jugendliche in KI weitergebildet. Wir haben an großartigen Events wie dem Fifteen Seconds Festival in Graz, der Startup Safari in Frankfurt, deploy(impact) oder SDS201 in der Schweiz teilgenommen und dazu beigetragen. Und so weiter, und so weiter. Die Liste ließe sich endlos fortsetzen. Hier noch einige persönliche Eindrücke aus diesem großartigen Jahr bei STATWORX.

Impressionen aus dem Geschäftsjahr 2021

Nicht zuletzt war 2021 auch unser 10-jähriges Firmenjubiläum! Im Januar 2011 ging die erste STATWORX-Website online. Dazu habe ich einen eigenen Blogbeitrag geschrieben. Das Jahr 2021 bescherte uns einige wirklich tolle Erlebnisse, brachte gleichzeitig aber auch viele neue Herausforderungen für unser wachsendes Unternehmen mit sich.

Erfolge und Herausforderungen gehen Hand in Hand, wenn es um Wachstum geht

Ein Unternehmen wachsen zu lassen ist schwer. Wirklich schwer. Während wir uns Schritt für Schritt der 100-Mitarbeiter-Marke nähern, ändern sich viele Dinge im Unternehmen – und müssen sich ändern. Aus „Neuigkeiten beim Mittagessen verbreiten“ wird „offizielle Kommunikation“. Aus „Das war schon immer so“ wird ein dokumentierter Prozess. Beförderungskriterien werden standardisiert. Karrierewege werden entwickelt. Persönliche Situationen ändern sich. Mitarbeitende verlassen das Unternehmen, um neue Chancen zu ergreifen. Interne Politik kommt auf. You know the deal. Nicht falsch verstehen, Wachstum ist eine gute Sache und etwas, auf das wir stolz sind, aber es hat seinen Preis. Wie bei jedem anderen Objekt bedeutet mehr Masse auch mehr Schwerkraft – mehr Dinge werden angezogen. Meistens positive Dinge, aber auch Herausforderungen und Probleme. Je größer das Unternehmen wird, desto schwieriger wird es, das Gesamtkonstrukt zusammenzuhalten. Das erinnerte mich an eine Vorlesung zu Greiners Wachstumsmodell aus meiner Zeit and der Universität:

Greiners Wachstumsmodell am Fallbeispiel von STATWORX 🙂

Wenn ein Unternehmen wächst, wird schließlich die Bedeutung und das Verlangen nach einem gemeinsamen Ziel, einer gemeinsamen Vision unausweichlich, um das Unternehmen und alle seine Mitarbeiter gemeinsam und zielgerichtet in die Zukunft zu führen. In der Vergangenheit waren viele Teammitglieder bei STATWORX stark motiviert, weil sie sahen, dass das Unternehmen erfolgreich und auf dem Markt nachgefragt war. Das berühmte „Why“ war für viele jedoch lange Zeit unklar. Man wuchs mit dem Unternehmen. Monetäre Erfolge und Wachstum haben uns die ersten Kilometer des Weges getragen. Aber motivieren sie auf einer linearen Skala? Eindeutig nein. Nicht jeder neue Kunde erzeugt die gleiche Motivation und Begeisterung wie der erste große Deal.

Eine Folie, die ich in 2021 meinem Team gezeigt habe

STATWORX hat mir also die Frage gestellt: Was treibt und an? Auch in schwierigen Zeiten, wie im COVID-Jahr 2020? Was ist unser Ziel? Warum tun wir, was wir tun? Ich musste Antworten auf diese Fragen finden. Damit begann die Reise zu statworx next.

Die Reise zu statworx next

Angetrieben von diesen Fragen begab ich mich zwischen den Jahren 2020 und Januar 2021 auf eine Reise, um eine Vision und ein Ziel für die Zukunft von STATWORX zu entwickeln. Rückblickend war dies – ohne Frage – eines der wichtigsten Dinge, die ich je für das Unternehmen getan habe. Aber auch eine der schwierigsten. Ich habe viele Tage und Nächte mit Recherchen und Brainstorming verbracht, mich durch endlose Artikel, Videos und Bücher gearbeitet, bis ich schließlich an meinem Ziel ankam. In der ersten Januarwoche 2021 wurde statworx next geboren. Die Antwort auf unser Warum. Eine Vision. Ein Ziel.

Noch eine Folie, die ich meinem Team gezeigt habe 🙂

In einer motivierenden Rede in der letzten Januarwoche 2021 habe ich vor allen Mitarbeitern des Unternehmens statworx next vorgestellt und dargelegt, was ich mir für STATWORX in der Zukunft vorstelle. In Anlehnung an Cameron Herolds „Vivid Vision“-Ansatz habe ich die gesamte Vision in einem Dokument niedergeschrieben, damit jeder klar und deutlich versteht und sich vorstellen kann, was ich vor meinem inneren Auge sehe. Die „Vivid Vision“ ist ein schriftlicher Ausdruck dessen, wie das Unternehmen in Zukunft aussehen und sich anfühlen soll. In dem Dokument beschreibe ich STATWORX in der Zukunft, wobei ich alle wichtigen Bereiche des Unternehmens hervorhebe: Geschäftsbereiche, Dienstleistungen, Produkte, Mitarbeiter, Kultur usw.

Aber statworx next ist mehr. Es hat einen tiefen, inspirierenden Kern. Er hat eine Bedeutung. Es gibt unserem Unternehmen einen Grund, das zu tun, was wir tun, so wie wir es tun. Es ist ein Leuchtturm für jeden Mitarbeitenden, der unseren Weg des Wachstums sowie unsere Rolle als Unternehmen in unserer heutigen und zukünftigen Wirtschaft, Gesellschaft und Umwelt definiert.

Nach der Präsentation von statworx next hat sich das gesamte Unternehmen sofort angefangen zu bewegen. Es wurden spezielle Visions-OKRs entwickelt, die sich herunterkaskadieren auf unsere Unternehmensziele, Geschäftsabteilungen und Backoffice-Teams. Es wurden Mitarbeiter für unsere neuen Geschäftsbereiche eingestellt, die aus statworx next hervorgegangen sind. Interne Initiativen wurden an der Schnittstelle zwischen statworx next und unserem Kerngeschäft gebildet. Die Mitarbeiter begannen, an Innovationsprojekten zu arbeiten. Die Energie war wahnsinnig. Das zu sehen, hat mir die elementare Bedeutung des „Why“ in einem Unternehmen klar vor Augen geführt. Lektion gelernt.

Das ganze Jahr 2021 hindurch hat das gesamte Unternehmen hinter den Kulissen hart an statworx next gearbeitet. Jetzt, im Jahr 2022, Ende Januar, werden wir statworx next offiziell unseren Kunden und der Öffentlichkeit vorstellen. Aus diesem Grund glaube ich fest daran, dass 2022 das beste Jahr für unser Unternehmen in der Geschichte sein wird!

Im Jahr 2021 sind wir gewachsen – und haben an Impact gewonnen. Wir sind bereit weiterzugehen. In 2022 ist es Zeit für das nächste Kapitel. Time to challenge the now. Create the next. Shape the future. For the better. Es ist Zeit für statworx next.

Sebastian Heinz Sebastian Heinz

Dieser Artikel ist am 28. August 2021 in der Zeitung DIE WELT erschienen.

WELT_Menschzentrierte KI
 

Künstliche Intelligenz (KI) ist einer der zentralen Treiber der digitalen Transformation. Über die vergangene Dekade hinweg haben sich bahnbrechende Anwendungen der Technologie regelmäßig aneinandergereiht und sich dabei regelmäßig selbst überholt. Angetrieben durch den immer weiter voranschreitenden Erfolg von KI-Technologie diffundiert diese stetig in alle wesentlichen Bereiche des menschlichen Lebens hinein. Neben der Arbeitswelt schließt dies insbesondere auch das breitere gesellschaftliche Leben ein. Dabei sind sich KI-Experten alle einig, dass der aktuelle Entwicklungsstand von KI nur die Spitze eines riesigen Eisbergs ist, dessen Freilegung gerade erst begonnen hat. Die dadurch entstehenden Veränderungen werden eine neue Ära unserer Gesellschaft entstehen lassen, in der Menschen mit Maschinen Seite an Seite arbeiten und leben werden.

Mit der wachsenden Bedeutung von KI für die Gesellschaft nimmt auch die Sorge der Menschen zu, dass die Technologie, die ohnehin schon so viel verändert, in einigen Jahren dazu führen wird, dass Menschen „wie Roboter“ handeln. Während Künstliche Intelligenz heute noch als Werkzeug dient – so wie Maschinen oder Computer früher – wird sie sich in Zukunft zunehmend in die Gedanken und Handlungen der Menschen einbringen. Dazu wird sie das Verhalten von Personen und das Zusammenspiel mit anderen Menschen analysieren, um daraus abzulesen, welche Handlungsoptionen für ihre Nutzer in einem bestimmten Moment am besten sind.

Menschenzentrierte KI ist ein Teilbereich der KI-Forschung, der sich auf die Entwicklung von KI-Systemen konzentriert, die sich menschenähnlich verhalten. Im Gegensatz zur traditionellen KI-Forschung, die sich auf die Entwicklung von KI-Systemen konzentriert, die sich rational und ohne menschenähnliche Eigenschaften verhalten sollen, zielt die menschenzentrierte KI darauf ab, den Menschen und die menschliche Intelligenz zu studieren, um KI-Systeme mit ähnlichen Eigenschaften zu entwickeln.

Die menschenzentrierte KI ist durch den Wunsch motiviert, KI-Systeme zu schaffen, die mit Menschen auf natürliche Weise interagieren können. Rationale KI-Systeme können zwar intelligent handeln, verhalten sich aber nicht wie Menschen, was ein Hindernis für ihre Akzeptanz durch Menschen sein kann. Während sich die herkömmliche KI-Forschung auf die Überwindung dieser Barriere konzentriert hat, zielt die menschenzentrierte KI-Forschung darauf ab, KI-Systeme zu schaffen, die sich auf natürliche Weise verhalten, so dass sie für den Menschen besser nachvollziehbar und akzeptabel sind. Es ist wichtig, dass wir uns im Vorfeld vergewissern, dass die Technologie im Einklang mit unserer Menschlichkeit steht und den Menschen in der Arbeitswelt nicht nur unterstützt, sondern ihn auch nicht „überholt“. Eine solche menschenzentrierte KI kann dabei helfen, menschliche und nicht-menschliche Intelligenz in einem gesunden Verhältnis miteinander zu integrieren. Der Fokus liegt dabei auf den Bedürfnissen der Nutzer, die als die wichtigsten Zielgruppen der Technologie betrachtet werden.

Die zentrale Herausforderung der künstlichen Intelligenz ist daher nicht die Technologie selbst, sondern vielmehr der Umgang des Menschen mit dieser Technologie. Die technische Entwicklung wird in Zukunft weitergehen und damit immer intelligentere, autonome Systeme hervorbringen. Die Systeme und ihre Entscheidungsprozesse werden den Menschen zunehmend hinter sich lassen, „maschinelles Lernen“ wird das Gebot der Stunde sein. Dabei ist es wichtig, dass die Menschen die KI-Technologie als einen Schritt in eine bessere Zukunft begreifen und nicht als Bedrohung verkörpert. Die Entwicklung der KI ist ein Prozess, der nur durch den Dialog zwischen Menschen und Technologien bewältigt werden kann. Die unzureichende Aufklärung der Gesellschaft über die Technologie und die unzureichende Kommunikation zwischen den Nutzern und der Industrie spielen hierbei eine große Rolle. Dabei sollte der Mensch immer im Vordergrund stehen und nicht nur als ein Werkzeug der KI.

Genese

Der vorstehende Text wurde mithilfe der KI „GPT-3“ verfasst. Das Akronym GPT steht für „Generative Pretrained Transformer“ (in der Version 3), einer KI, die in 2020 durch das Amerikanische Forschungsunternehmen OpenAI entwickelt wurde. Bei GPT-3 handelt es sich um ein sogenanntes generatives Sprachmodell, das auf einer extrem großen Menge an diversen Texten aus dem Internet trainiert wurde.

Das Training von GPT-3 erfolgt mittels Deep Learning, einer Gruppe von Methoden aus dem maschinellen Lernen, die grobe Ähnlichkeiten zur Informationsverarbeitung und -weitergabe im menschlichen Gehirn aufweisen (Nervenzellen bzw. Neuronen geben die zu verarbeitenden Informationen an andere Neuronen und Hirnareale weiter). Im Rahmen des Modelltrainings passen sich die rund 175 Milliarden Parameter von GPT-3 an die zugrundeliegenden Trainingsdaten an und erlernen den Zusammenhang zwischen dem zuvor beobachteten Text eines Satzes/Abschnittes und dem nächsten, wahrscheinlich auftauchenden Wort. Hierdurch generiert die KI während des Lernvorgangs ein generelles und umfassendes Verständnis über die Verwendung von Worten im jeweiligen Kontext.

So geschah es auch in diesem Artikel. Als Ausgangsbasis der Textgenerierung dienten lediglich die ersten fünf Sätze des ersten Absatzes. Hierin wurde der Kontext definiert, in dem die KI operieren soll. Alle darauffolgenden Abschnitte wurden eigenständig durch die KI generiert und inhaltlich nicht modifiziert (Anmerkung: es wurden einzelne Absätze wiederholt erzeugt und anschließend zu einem Artikel zusammengefügt).

Der Schlüssel zum Erfolg liegt also in der geschickten Definition des Kontextes, der die KI im Prozess der Generierung anleitet. Bahnbrechend ist hierbei, dass die Kontextdefinition über natürliche Sprache funktioniert und nicht manuell in die KI „einprogrammiert“ werden muss. Die KI „versteht“ somit eigenständig den Kontext, in dem sie sich bewegen soll.

Wir hoffen, Ihnen mit diesem Beispiel einen Eindruck von der Mächtigkeit solcher KI-Systeme geben zu können. Es ist zu erwarten, dass bereits in naher Zukunft neue Versionen dieser und ähnlicher KI-Systeme entstehen, die in der Lage sein werden, noch bessere Ergebnisse erzeugen zu können.

Sebastian Heinz Sebastian Heinz

Für Außenstehende umgeben neuronale Netze eine mystische Aura. Obwohl die Funktionsweise der elementaren Bausteine neuronaler Netze, Neuronen genannt, bereits seit vielen Jahrzehnten bekannt sind, stellt das Training von neuronalen Netzen Anwender auch heute noch vor Herausforderungen. Insbesondere im Bereich Deep Learning, in dem sehr tiefe oder anderweitig komplexe Netzarchitekturen geschätzt werden, spielt die Art und Weise wie das Netz aus den vorhandenen Daten lernt, eine zentrale Rolle für den Erfolg des Trainings.

In diesem Beitrag sollen die beiden grundlegenden Bausteine des Lernens von neuronalen Netzen beleuchtet werden: (1) Gradient Descent, eine iterative Methode zur Minimierung von Funktionen sowie (2) Backpropagation, ein Verfahren, mit dem in neuronalen Netzen die Stärke und Richtung der Anpassungen der Modellparameter berechnet werden können. Im Zusammenspiel beider Methoden sind wir heute in der Lage, verschiedenste Modelltypen und Architekturen zu entwickeln und auf vorhandenen Daten zu trainieren.

Formaler Aufbau eines Neurons

Ein Neuron j berechnet seinen Output o_j als gewichtete Summe der Eingangssignale x, die anschließend durch die sog. Aktivierungsfunktion des Neurons, g(x) transformiert wird. Je nachdem, welche Aktivierungsfunktion für g() gewählt wird, verändert sich der funktionale Output des Neurons. Die folgende Abbildung soll den Aufbau eines einzelnen Neurons schematisch darstellen.

Neuron

Neben den Inputs x_1,...,x_n beinhaltet jedes Neuron einen sog. Bias. Der Bias steuert das durchschnittliche Aktivierungsniveau und ist elementarer Bestandteil des Neurons. Jeder Input sowie der Bias fließen als gewichtete Summe in das Neuron ein. Anschließend wird die gewichtete Summe durch die Aktivierungsfunktion des Neurons nichtlinear transformiert. Heute wird insbesondere die sog. Rectified Linear Unit (ReLU) als Aktivierungsfunktion verwendet. Diese ist definiert als g(x)=max(0,x). Andere Aktivierungsfunktionen sind bspw. die Sigmoidfunktion, definiert als g(x)=frac{1}{1+e^{-x}} oder der Tangens Hyperbolicus, g(x)=1-frac{2}{e^{2x}+1}.

Der folgende Python Code soll exemplarisch den Ablauf zur Berechnung des Outputs eines Neurons aufzeigen.

# Imports
import numpy as np

# ReLU Aktivierungsfunktion
def relu(x):
    """ ReLU Aktivierungsfunktion """
    return(np.maximum(0, x))

# Funktion für ein einzelnes Neuron
def neuron(w, x):
    """ Neuron """
    # Gewichtete Summe von w und x
    ws = np.sum(np.multiply(w, x))
    # Aktivierungsfunktion (ReLU)
    out = relu(ws)
    # Wert zurückgeben
    return(out)

# Gewichtungen und Inputs
w = [0.1, 1.2, 0.5]
x = [1, 10, 10]

# Berechnung Output (ergibt 18.0)
p = neuron(w, x)
array([18.0])

Durch die Anwendung der ReLU Aktivierungsfunktion beträgt der Output des Neurons im obigen Beispiel 18. Selbstverständlich besteht ein neuronales Netz nicht nur aus einem, sondern sehr vielen Neuronen, die über ihre Gewichtungsfaktoren in Schichten miteinander verbunden sind. Durch die Kombination vieler Neuronen ist das Netz in der Lage, auch hochgradig komplexe Funktionen zu lernen.

Gradient Descent

Einfach gesprochen lernen Neuronale Netze, indem sie iterativ die Modellprognosen mit den tatsächlich beobachteten Daten vergleichen und die Gewichtungsfaktoren im Netz so anpassen, dass in jeder Iteration der Fehler zwischen Modellprognose und Istdaten reduziert wird. Zur Quantifizierung des Modellfehlers wird eine sog. Kostenfunktion (cost function) berechnet. Die Kostenfunktion hat i.d.R. zwei Funktionsargumente: (1) den Output des Modells, o sowie (2) die tatsächlich beobachteten Daten, y. Eine typische Kostenfunktion E, die in neuronalen Netzen häufig Anwendung findet, ist der mittlere quadratische Fehler (Mean Squared Error, MSE):

    \[E=frac{1}{n}sum (y_i-o_i)^2\]

Der MSE berechnet zunächst für jeden Datenpunkt die quadratische Differenz zwischen y und o und bildet anschließend den Mittelwert. Ein hoher MSE reflektiert somit eine schlechte Anpassung des Modells an die vorliegenden Daten. Ziel des Modelltrainings soll es sein, die Kostenfunktion durch Anpassung der Modellparameter (Gewichtungen) zu minimieren. Woher weiß das neuronale Netz, welche Gewichtungen im Netzwerk angepasst werden müssen, und vor allem, in welche Richtung?

Die Kostenfunktion hängt von allen Parametern im neuronalen Netz ab. Wird auch nur eine Gewichtung minimal verändert, hat dies eine unmittelbare Auswirkung auf alle folgenden Neuronen, den Output des Netzes und somit auch auf die Kostenfunktion. In der Mathematik können die Stärke und Richtung der Veränderung der Kostenfunktion durch Veränderung eines einzelnen Parameters im Netzwerk durch die Berechnung der partiellen Ableitung der Kostenfunktion nach dem entsprechenden Gewichtungsparameter bestimmt werden: frac{partial E}{partial w_{ij}}. Da die Kostenfunktion E im neuronalen Netz hierarchisch von den verschiedenen Gewichtungsfaktoren im Netz abhängt, kann nach Anwendung der Kettenregel für Ableitungen folgende Gleichung zur Aktualisierung der Gewichtungen im Netzwerk abgeleitet werden:

    \[w_{ijt}=w_{ijt-1}-eta frac{partial E}{partial w_{ij}}\]

Die Anpassung der Gewichtungsfaktoren von Iteration zu Iteration in Richtung der Minimierung der Kostenfunktion hängt also lediglich von dem Wert des jeweiligen Gewichts aus der vorhergehenden Iteration, der partiellen Ableitung von E nach w_{ij} sowie einer Lernrate (learning rate), eta ab. Die Lernrate steuert dabei, wie groß die Schritte in Richtung der Fehlerminimierung ausfallen. Das oben skizzierte Vorgehen, die Kostenfunktion iterativ auf Basis von Gradienten zu minimieren, wird als Gradient Descent bezeichnet. Die folgende Abbildung soll den Einfluss der Lernrate auf die Minimierung der Kostenfunktion verdeutlichen (in Anlehnung an Yun Le Cun):

Learning Rate

Idealerweise würde die Lernrate so gesetzt werden, dass mit nur einer Iteration das Minimum der Kostenfunktion erreicht wird (oben links). Da in der Anwendung der theoretisch korrekte Wert nicht bekannt ist, muss die Lernrate vom Anwender definiert werden. Im Falle einer kleinen Lernrate kann man relativ sicher sein, dass ein (zumindest lokales) Minimum der Kostenfunktion erreicht wird. Allerdings steigt in diesem Szenario die Anzahl der benötigten Iterationen bis zum Minimum deutlich an (oben rechts). Für den Fall, dass die Lernrate größer als das theoretische Optimum gewählt wird, destabilisiert sich der Pfad hin zum Minimum zusehends. Zwar sinkt die benötigte Anzahl der Iterationen, es kann aber nicht sichergestellt werden, dass tatsächlich ein lokales oder globales Optimum erreicht wird. Insbesondere in Regionen, in denen die Steigung bzw. Oberfläche der Fehlerkurve sehr flach wird, kann es vorkommen, dass durch eine zu große Schrittweite das Minimum verfehlt wird (unten links). Im Falle einer deutlich zu hohen Lernrate führt dies zu einer Destabilisierung der Minimierung und es wird keine sinnvolle Lösung für die Anpassung der Gewichtungen gefunden (unten rechts). In aktuellen Optimierungsschemata finden auch adaptive Lernraten Anwendung, die die Lernrate im Laufe des Trainings anpassen. So kann bspw. zu Beginn eine höhere Lernrate gewählt werden, die dann im Laufe der Zeit weiter reduziert wird, um stabiler am (lokalen) Optimum zu landen.

# Imports
import numpy as np

# ReLU Aktivierungsfunktion
def relu(x):
    """ ReLU Aktivierungsfunktion """
    return(np.maximum(0, x))


# Ableitung der Aktivierungsfunktion
def relu_deriv(x):
    """ Ableitung ReLU """
    return(1)


# Kostenfunktion
def cost(y, p):
    """ MSE Kostenfunktion """
    mse = np.mean(pow(y-p, 2))
    return(mse)


# Ableitung der Kostenfunktion
def cost_deriv(y, p):
    """ Ableitung MSE """
    return(y-p)


# Funktion für ein einzelnes Neuron
def neuron(w, x):
    """ Neuron """
    # Gewichtete Summe von w und x
    ws = np.sum(np.multiply(w, x))
    # Aktivierungsfunktion (ReLU)
    out = relu(ws)
    # Wert zurückgeben
    return(out)
 

# Initiales Gewicht
w = [5.5]

# Input
x = [10]

# Target
y = [100]

# Lernrate
eta = 0.01

# Anzahl Iterationen
n_iter = 100

# Neuron trainieren
for i in range(n_iter):
    # Ausgabe des Neurons berechnen
    p = neuron(w, x)
    # Ableitungen berechnen
    delta_cost = cost_deriv(p, y)
    delta_act = relu_deriv(x)
    # Gewichtung aktualisieren
    w = w - eta * delta_act * delta_cost
    
# Ergebnis des Trainings
print(w)
array([9.99988047])

Durch das iterative Training des Modells ist es also gelungen, die Gewichtung des Inputs so anzupassen, dass der Abstand zwischen der Modellprognose und dem tatsächlich beobachteten Wert nahe 0 ist. Dies ist einfach nachzurechnen:

    \[o_j=g(x)=g(w*x)=max(0, 9.9998*10) = max(0, 99.998) = 99.998\]

Das Ergebnis liegt also fast bei 100. Der mittlere quadratische Fehler beträgt 3.9999e-06 und ist somit ebenfalls nahe 0. Die folgende Abbildung visualisiert abschließend den MSE im Trainingsverlauf.

Verlauf MSE Training

Man sieht deutlich den monoton abnehmenden, mittleren quadratischen Fehler. Nach 30 Iterationen liegt der Fehler praktisch bei null.

Backpropagation

Das oben dargestellte Verfahren des steilsten Gradientenabstiegs wird auch heute zur Minimierung der Kostenfunktion von neuronalen Netzen eingesetzt. Allerdings gestaltet sich die Berechnung der benötigten Gradienten in komplexeren Netzarchitekturen deutlich schwieriger als im oben gezeigten Beispiel eines einzelnen Neurons. Der Grund hierfür ist, dass die Gradienten der einzelnen Neuronen voneinander abhängen. Es besteht also eine Verkettung der Wirkungen einzelner Neuronen im Netz. Zur Lösung dieses Problems wird der sog. Backpropagation Algorithmus eingesetzt, der es ermöglicht, die Gradienten in jeder Iteration rekursiv zu berechnen.

Ursprünglich wurde der Backpropagation Algorithmus in den 1970er Jahren entwickelt, fand aber erst deutlich später, im Jahre 1986, Anerkennung durch das bahnbrechende Paper von Rumelhart, Hinton und Williams (1986), in dem Backpropagation erstmalig zum Training von neuronalen Netzen verwendet wurde. Die gesamte formale Herleitung des Backpropagation Algorithmus ist zu komplex, um hier im Detail dargestellt zu werden. Im Folgenden soll der grundsätzliche Ablauf formal skizziert werden.

In Matrizenschreibweise ist der Vektor der Outputs o aller Neuronen eines Layers l definiert als

    \[o_l=g(w_lo_{l-1}+b_l)\]

wobei w_l die Gewichtungsmatrix zwischen den Neuronen der Layer l und l-1 ist und o_{l-1} den Output des vorhergehenden Layers bezeichnet. Der Term b_l repräsentiert die Biaswerte des Layers l. Die Funktion g ist die Aktivierungsfunktion des Layers. Der Term innerhalb der Klammer wir auch als gewichteter Input z bezeichnet. Es gilt also o_l=g(z) mit z=w_lo_{l-1}+b_l. Zudem nehmen wir an, dass das Netzwerk insgesamt aus L Layern besteht. Der Output des Netzes ist also o_L. Die Kostenfunktion E des neuronalen Netzes muss so definiert sein, dass sie als Funktion des Outputs o_L sowie der beobachteten Daten y geschreiben werden kann.

Die Veränderung der Kostenfunktion in Abhängigkeit des gewichteten Inputs eines Neurons j im Layer l ist definiert als delta_j=partial E / partial z_j. Umso größer dieser Wert, desto stärker ist die Kostenfunktion abhängig vom gewichteten Inputs dieses Neurons. Die Veränderung der Kostenfunktion in Abhängigkeit der Outputs des Layers L ist definiert als

    \[delta_L=frac{partial E}{partial o_L}g'(z_L)\]

Die genaue Form von delta_L ist abhängig von der Wahl der Kostenfunktion E. Die hier verwendete mittlere quadratische Abweichung lässt sich einfach ableiten nach o_L:

    \[frac{partial E}{partial o_L}=(y-o_l)\]

Somit kann der Vektor der Gradienten für die Outputschicht folgendermaßen geschrieben werden:

    \[delta_L=(y-o_L)g'(z_L)\]

Für die vorhergehenden Layer l=1,...,L-1 lässt sich der Gradientenvektor schreiben als

    \[delta_l=(w_{l+1})^T delta_{l+1} g'(z_l)\]

Wie man sieht, sind die Gradienten im l-ten Layer eine Funktion der Gradienten und Gewichtungen des folgenden Layers l+1. Somit ist es unabdingbar, die Berechnung der Gradienten im letzten Layer des Netzes zu beginnen und diese dann iterativ in die vorhergehenden Layer zu propagieren (=Backpropagation). Durch die Kombination von delta_L und delta_l lassen sich somit die Gradienten für alle Layer im neuronalen Netz berechnen.

Nachdem die Gradienten aller Layer im Netz bekannt sind, kann anschließend ein Update der Gewichtungen im Netz mittels Gradient Descent stattfinden. Dieses Update wird so gewählt, dass die Gewichtungen in entgegengesetzter Richtung zu den berechneten Gradienten stattfinden – und somit die Kostenfunktion reduzieren.

Für das folgende Programmierbeispiel, das exemplarisch den Backpropagation Algorithmus darstellt, erweitern wir unser neuronales Netz aus dem vorherigen Beispiel um ein weiteres Neuron. Somit besteht das Netz nun aus zwei Schichten – dem Hidden Layer sowie dem Output Layer. Der Hidden Layer wird weiterhin mit der ReLU aktiviert, der Output Layer verfügt über eine lineare Aktivierung, er gibt also nur die gewichtete Summe des Neurons weiter.

# Neuron
def neuron(w, x, activation):
    """ Neuron """
    # Gewichtete Summe von w und x
    ws = np.sum(np.multiply(w, x))
    # Aktivierungsfunktion (ReLU)
    if activation == 'relu':
        out = relu(ws)
    elif activation == 'linear':
        out = ws
    # Wert zurückgeben
    return(out)


# Initiale Gewichte
w = [1, 1]

# Input
x = [10]

# Target
y = [100]

# Lernrate
eta = 0.001

# Anzahl Iterationen
n_iter = 100

# Container für Gradienten
delta = [0, 0]

# Container für MSE
mse = []

# Anzahl Layer
layers = 2

# Neuron trainieren
for i in range(100):
    # Hidden layer
    hidden = neuron(w[0], x, activation='relu')
    # Output layer
    p = neuron(w[1], x=hidden, activation='linear')
    # Ableitungen berechnen
    d_cost = cost_deriv(p, y)
    d_act = relu_deriv(x)
    # Backpropagation
    for l in reversed(range(layers)):
        # Output Layer
        if l == 1:
            # Gradienten und Update
            delta[l] = d_act * d_cost
            w[l] = w[l] - eta * delta[l]
        # Hidden Layer
        else:
            # Gradienten und Update
            delta[l] = w[l+1] * delta[l+1] * d_act
            w[l] = w[l] - eta * delta[l]
    # Append MSE
    mse.append(cost(y, p))
    
# Ergebnis des Trainings
print(w)
[array([3.87498172]), array([2.58052067])]

Im Gegensatz zum letzten Beispiel ist die Schleife nun durch Hinzufügen einer weiteren Schleife nun etwas komplexer geworden. Die weitere Schleife bildet die Backpropagation im Netz ab. Über reversed(range(layers)) iteriert die Schleife rückwärts über die Anzahl der Layer und startet somit beim Output Layer. Danach werden die Gradienten gem. oben dargestellter Formel berechnet und anschließend die Gewichtungen mittels Gradient Descent aktualisiert. Multipliziert man die berechneten Gewichtungen erhält man

    \[3.87498172*10*2.58052067=99.99470\]

was wiederum fast genau dem Zielwert von y=100 entspricht. Durch die Anwendung von Backpropagation und Gradient Descent hat das (wenn auch sehr minimalistische) neuronale Netz den Zielwert gelernt.

Zusammenfassung und Ausblick

Neuronale Netze lernen heute mittels Gradient Descent (bzw. moderneren Abwandlungen davon) und Backpropagation. Die Entwicklung effizienterer Lernverfahren, die schneller, genauer und stabiler als bestehende Formen von Backpropagation und Gradient Descent sind, gehört zu den zentralen Forschungsbereichen in diesem Fachgebiet. Insbesondere für Deep Learning Modelle, in denen sehr tiefe Modellarchitekturen geschätzt werden müssen, spielt die Auswahl des Lernverfahrens eine zentrale Rolle für die Geschwindigkeit des Trainings sowie für die Genauigkeit des Modells. Die fundamentalen Mechanismen der Lernverfahren für neuronale Netze sind bisher weitestgehend unverändert geblieben. Vielleicht wird aber künftig, angetrieben durch die massive Forschung in diesem Bereich, ein neues Lernverfahren entwickelt, das genauso revolutionär und erfolgreich sein wird wie der Backpropagation Algorithmus. Sebastian Heinz Sebastian Heinz

Die meisten Machine Learning Algorithmen, die heute in der Praxis Anwendung finden, gehören zur Klasse des überwachten Lernens (Supervised Learning). Im Supervised Learning wird dem Machine Learning Modell ex post eine bereits bekannte Zielgröße y präsentiert, die auf Basis verschiedener Einflussfaktoren X in den Daten durch eine Funktion f möglichst genau vorhergesagt werden soll. Die Funktion f repräsentiert dabei abstrakt das jeweilige Machine Learning Modell, das ein Mapping zwischen den Inputs und den Outputs des Modells bereitstellt.

funktion

Die gelernten Zielgrößen einfacher ML Modelle sind somit i.d.R. statisch und verändern sich nur im historischen Zeitverlauf, der ex post bereits bekannt ist. Im Zeitverlauf können neue Datenpunkte gesammelt werden, diese fließen dann in Form eines „Retrainings“ in das gelernte Mapping f zwischen X und y ein.

Basierend auf den Prognosen eines Modells werden im Anschluss weitere, meist noch menschlich gesteuerte, Handlungen ausgelöst. Diese stellen oft die eigentlichen Entscheidungen dar, die geschäftsrelevante Auswirkungen bzw. Implikationen nach sich ziehen. Die eigentlichen Geschäftsentscheidungen, die auf Basis von ML Modellen getroffen werden, sind somit vielerorts noch nicht bzw. nur teilweise maschinengesteuert. Nur an sehr wenigen Stellen werden heute bereits vollständig autonom agierende Modelle angewendet, die in der Lage sind, autark Entscheidungen zu treffen und ihr Handeln zu überwachen bzw. modellbasiert anzupassen.

Auf dem Weg hin zu selbstlernenden, autonomen Algorithmen, wie sie im Bereich der künstilichen Intelligenz verwendet werden müssen, liefern Modelle aus dem supervised learning somit nur einen begrenzten Beitrag.

Auf dem Weg hin zu selbstlernenden, autonomen Algorithmen, wie sie im Bereich der künstlichen Intelligenz verwendet werden müssen, liefern Modelle aus dem Supervised Learning somit nur einen begrenzten Beitrag. Dies ist meist darin begründet, dass die von Machine Learning Modellen gelernte 1:1 Beziehung zwischen Inputs und Outputs in komplexeren Szenarien oder Handlungsumgebungen nicht mehr ausreichend ist. Beispielweise sind die meisten Machine Learning Modelle damit überfordert, mehrere Zielgrößen gleichzeitig oder eine Sequenz von Zielgrößen bzw. Handlungen zu erlernen. Weiterhin kann der Zusammenhang zwischen Einflussfaktoren und Zielgrößen in Abhängigkeit der Umgebung bzw. auf Basis bereits getroffenener Prognosen und Entscheidungen unmittelbar variieren, was ein fortlaufendes „Retraining“ der Modelle unter geänderten Rahmenbedingungen implizieren würde.

Damit Machine Learning Modelle auch in Umgebungen Anwendung finden können, in denen sie eigenständige Aktions-Reaktions Ereignisse erlernen können, werden Lernverfahren benötigt, die einer sich verändernden Dynamik der Umgebung Rechnung tragen. Ein populäres Beispiel für die erfolgreiche Anwendung von Algorithmen dieser Art ist der Sieg von Goolge’s KI AlphaGo über den weltbesten menschlichen Go-Spieler. AlphaGo wäre mit klassischen Methoden des Supervised Learning nicht darstellbar gewesen, da aufgrund der unendlichen Anzahl an Spielzügen und Szenarien kein Modell in der Lage gewesen wäre, die Komplexität der Aktions-Reaktions-Beziehungen als reines Input-Output-Mapping abzubilden. Stattdessen werden Methoden benötigt, die in der Lage sind, selbständig auf neue Gegebenheiten der Umgebung zu reagieren, mögliche zukünftige Handlungen zu antizipieren und diese in aktuelle Entscheidung mit einfließen zu lassen. Die Klasse der Lernverfahren, auf denen Systeme wie AlphaGo basieren werden als Reinforcement Learning bezeichnet.

Was ist Reinforcement Learing?

Reinforcement Learning (RL) bildet neben Supervised und Unsupervised Learning die dritte große Gruppe von Machine Learning Verfahren. RL ist eine am natürlichen Lernverhalten des Menschen orientierte Methode. Menschliches Lernen erfolgt, insbesondere in frühen Stadien des Lernens, häufig über eine einfache Exploration der Umwelt. Dabei sind unsere Handlungen im Rahmen des Lernproblems durch einen gewissen Aktionsraum definiert. Über „Trial and Error“ werden die Auswirkungen verschiedener Handlungen auf unsere Umwelt beobachtet und bewertet. Als Reaktion auf unsere Handlungen erhalten wir von unserer Umgebung ein Feedback, abstrakt dargestellt in Form einer Belohnung oder Bestrafung. Dabei ist das Konzept der Belohnung bzw. Bestrafung nur in den allerwenigsten Fällen monetär zu verstehen. In vielen Fällen wird die Belohnung in Form von sozialer Akzeptanz, Lob anderer Menschen aber auch durch persönliches Wohlbefinden oder Erfolgserlebnisse ausgezahlt. Vielfach zeigt sich auch eine zeitliche Latenz zwischen Handlung und Belohnung. Hierbei versucht der Mensch häufig, durch sein Handeln die erwartete „Gesamtbelohnung“ im Zeitverlauf zu maximieren und nicht nur unmittelbare Belohnungen zu generieren.

Ein Beispiel: Wenn wir lernen Gitarre zu spielen, umfasst unser Aktionsraum das Zupfen der Seiten sowie das Greifen am Bund. Über eine zunächst zufällige Exploration des Handlungsraums erhalten wir in Form von Tönen der Gitarre ein Feedback der Umwelt. Dabei werden wir belohnt, wenn die Töne „gerade“ sind bzw. bestraft, wenn die Nachbarn mit dem Besen von unten gegen die Decke klopfen. Wir versuchen, die erwartete Gesamtbelohnung in Form von richtig gespielten Noten und Akkorden im für uns relevanten Zeithorizont zu maximieren. Dies geschieht nicht dadurch, dass wir aufhören zu lernen sobald wir einen Akkord sauber spielen können, sondern manifestiert sich durch ein stetiges Training und immer wieder neue Belohnungen und Erfolge, die uns im Zeitverlauf erwarten. Natürlich kann die Dauer der Exploration der möglichen Handlungen und Belohnungen der Umwelt durch die Hinzunahme eines externen Trainers verbessert werden. Dieses Beispiel ist natürlich sehr vereinfacht, stellt aber das Grundprinzip im Kern gut dar.

Reinforcement Learning besteht formal betrachtet aus fünf wichtigen Komponenten, nämlich (1) dem Agenten (agent), (2) der Umgebung (environment), (3) dem Status (state), (4) der Aktion (action) sowie (5) der Belohnung (reward). Grundsätzlich lässt sich der Ablauf wie folgt beschreiben: Der Agent führt in einer Umgebung zu einem bestimmten Status (s_t) eine Aktion (a_t) aus dem zur Verfügung stehenden Aktionsraum A durch, die zu einer Reaktion der Umgebung in Form einer Belohnungen (r_{t}) führt.

reinforcement-learning

Die Reaktion der Umgebung auf die Aktion des Agenten beeinflusst nun wiederum die Wahl der Aktion des Agenten im nächsten Status (s_{t+1}). Über mehrere tausend, hunderttausend oder sogar millionen von Iterationen ist der Agent in der Lage, einen Zusammenhang zwischen seinen Aktionen und dem künftig zu erwartenden Nutzen in jedem Status zu approximieren und sich somit entsprechend optimal zu verhalten. Dabei befindet sich der Agent immer in einem Dilemma zwischen der Nutzung seiner bisher erworbenen Erfahrung auf der einen und der Exploration neuer Strategien zur Erhöhung der Belohnung auf der anderen Seite. Dies wird als „Exploration-Exploitation Dilemma“ bezeichnet.

Die Approximation des Nutzens kann dabei modellfrei, also über reine Exploration der Umgebung erfolgen oder durch die Anwendung von Machine Learning Modellen, die den Nutzen einer Aktion versuchen zu approximieren. Letztere Variante wird insbesondere dann angewendet, wenn der Status- und/oder Aktionsraum von hoher Dimensionalität ist.

Q-Learning

Um Reinforcement Learning Systeme zu trainieren, wird häufig eine Methode verwendet, die als Q-Learning bekannt ist. Den Namen erhält Q-Learning von der sog. Q-Funktion Q(s,a), die den erwarteten Nutzen Q einer Aktion a im Status s beschreibt. Die Nutzenwerte werden in der sog. Q-Matrix Q gespeichert, deren Dimensionalität sich über die Anzahl der möglichen Stati sowie Aktionen definiert. Während des Trainings versucht der Agent, die Q-Werte der Q-Matrix durch Exploration zu approximieren, um diese später als Entscheidungsregel zu nutzen. Die Belohnungsmatrix R enthält, korrespondierend zu Q, die entsprechenden Belohnungen, die der Agent in jedem Status-Aktions-Paar erhält.

Die Approximation der Q-Werte funktioniert im einfachsten Falle wie folgt: Der Agent startet in einem zufällig initialisierten Status s_t. Anschließend selektiert der Agent zufällig eine Aktion a_t aus A, beobachtet die entsprechende Belohnung r_t und den darauf folgenden Status s_{t+1}. Die Update-Regel der Q-Matrix ist dabei wie folgt definiert:

    \[Q(s_t,a_t)=(1-\alpha)Q(s_t, a_t)+\alpha(r_t+\gamma \max Q(s_{t+1},a))\]

Der Q-Wert im Status s_t bei Ausführung der Aktion a_t ist eine Funktion des bereits gelernten Q-Wertes (erster Teil der Gleichung) sowie der Belohnung im aktuellen Status zzgl. des diskontierten maximalen Q-Wertes aller möglichen Aktionen a im folgenden Status s_{t+1}.

Der Parameter \alpha im ersten Teil der Gleichung wird als Lernrate (learning rate) bezeichnet und steuert, zu welchem Anteil eine neu beobachtete Information den Agenten in seiner Entscheidung eine bestimmte Aktion zu treffen beeinflusst.

Der Parameter \gamma ist der sog. Diskontierungsfaktor (discount factor) und steuert den Trade-off zwischen der Präferenz von kurzfristigen oder zukünftigen Belohnungen in der Entscheidungsfindung des Agenten. Kleine Werte für \gamma lassen den Agenten eher Entscheidungen treffen, die näher liegende Belohnungen in der Entscheidungsfindung priorisieren, während höhere Werte für \gamma den Agenten langfristige Belohnungen in der Entscheidungsfindung priorisieren lassen.

In modellbasierten Q-Learning Umgebungen findet die Exploration der Umgebung nicht rein zufällig statt. Die Q-Werte der Q-Matrix werden basierend auf dem aktuellen Status durch Machine Learning Modelle, in der Regel neuronale Netze und Deep Learning Modelle, approximiert. Häufig wird während des Trainings von modellbasierten RL Systemen noch eine zufällige Handlungskomponente implementiert, die der Agent mit einer gewissen Wahrscheinlichkeit p < \epsilon durchführt. Dieses Vorgehen wird als \epsilon-greedy bezeichnet und soll verhindern, dass der Agent immer nur die gleichen Aktionen bei der Exploration der Umgebung durchführt.

Nach Abschluss der Lernphase wählt der Agent in jedem Status diejenige Aktion mit dem höchsten Q-Wert aus, \max Q(s_t, a). Somit kann sich der Agent von Status zu Status bewegen und immer diejenige Aktion wählen, die den approximierten Nutzen maximiert.

Q-Learning eignet sich insbesondere dann als Lernverfahren, wenn die Anzahl der möglichen Stati und Aktionen überschaubar ist. Andernfalls wird das Problem aufgrund der kombinatorischen Komplexität mit reinen Explorationsmechanismen nur schwer lösbar. Aus diesem Grund findet in extrem hochdimensionalen Status- und Aktionsräumen die Approximation der Q-Werte häufig über modellbasierte Ansätze statt.

Eine weitere Schwierigkeit bei der Anwendung von Q-Learning zeigt sich, wenn die Belohnungen zeitlich sehr weit vom aktuellen Status- und Handlungsraum des Agenten entfernt liegen. Wenn in naheliegenden Stati keine Belohnungen vorhanden sind, kann der Agent erst nach einer lagen Explorationsphase weit in der Zukunft liegende Belohnungen in die naheliegenden Stati propagieren.

Minimalbeispiel für Q Learning

Der neue, autonome Stabsaugerroboter „Dusty3000“ der Firma STAUBWORX soll sich vollautomatisch in unbekannten Wohnungen zurecht finden. Dabei nutzt das Gerät einen Reinforcement Learning Ansatz, um herauszufinden, in welchen Räumen einer Wohnung sich Staubballen und Flusen anhäufen. Eine virtuelle Testwohnung, in der der Roboter kalibriert werden soll, hat den folgenden Grundriss:

grundriss-wohnung-1

Insgesamt verfügt die virtuelle Testwohnung über 5 Zimmer, wobei im Testszenario lediglich im Wohnzimmer Staub anzufinden ist. Findet der Roboter den Weg zum Staub, erhält er eine Belohnung von r = 1 andernfalls wird keine Belohnung angesetzt r=0. Räume, die der Roboter von seiner aktuellen Position aus nicht erreichen kann, werden in der Belohnungsmatrix mit r=-1 definiert. In Matrizenschreibweise stellen sich die Belohnungen sowie die möglichen Aktionen pro Raum wie folgt dar:

reward-matrix-1

Stellen wir uns vor, der Saugroboter startet seine Erkundung zufällig im Flur (Raum 0). Ausgehend vom aktuellen Raum (Status) s=0 bieten sich dem Roboter drei mögliche Aktionen: 1, 2 oder 4. Aktion 0 und 3 sind nicht möglich, da diese Räume vom Flur aus nicht erreichbar sind. Der Agent erhält keine Belohnung, wenn er Aktion a=1 wählt und sich vom Flur in Raum 1 (Bad) begibt. Das gleiche gilt für Aktion a=2 (Bewegung ins Schlafzimmer). Wählt der Roboter jedoch Aktion a=4 und fährt ins Wohnzimmer, so findet er dort den Staub und er erhält eine Belohnung von r=1. Für den externen Betrachter erscheint die Wahl der Aktion trivial, unser Roboter jedoch kennt seine Umgebung nicht und wählt zufällig aus den zur Verfügung stehenden Aktionen aus.

Der Roboter startet die zufällige Erkundung der Wohnung. Die Lernrate wird auf \alpha=1 und der Diskontierungsfaktor auf \gamma=0.95 gesetzt. Basierend auf dem Startpunkt im Flur wählt er per Zufallsgenerator aus den zur Verfügung stehenden Aktionen a=2 aus. Somit initiiert der Roboter zunächst eine Bewegung ins Schlafzimmer. Im vereinfachten Fall von \alpha=0.8 is der Q Value für die Bewegung vom Flur ins Schlafzimmer ist definiert durch:

    \[Q(0,2)=(1-\alpha)Q(0,2)+\alpha(r_t+\gamma \max Q(s_{t+1},a))\]

    \[=(1-1)*0+1*(0+0.95\max[Q(2,0),Q(2,4)])\]

    \[=0+1*(0+0.95 \max[0, 1])=0.95\]

Hier zeigt sich auch die Bedeutung des Diskontierungsfaktors: Bei einem Wert von 0 würde die mögliche Bewegung vom Schlafzimmer aus ins Wohnzimmer bei der momentanen Bewegung vom Flur ins Schlafzimmer nicht berücksichtigt werden. Es ergäbe sich Q Value von Q(0,2)=0​. Da der Diskontierungsfaktor in unserem Beispiel aber größer 0 ist, wird auch die mögliche, zukünftige Belohnung in Q(0,2)​ mit eingepreist.

Im Schlafzimmer angekommen ergeben sich wiederum zwei mögliche Aktionen. Entweder der Roboter bewegt sich zurück in den Flur, a=0 oder er fährt weiter ins Wohnzimmer, a=4. Zufällig fällt die Wahl diesmal auf das Wohnzimmer, womit sich folgender Q Value ergibt

    \[Q(2,4)=(1-\alpha)+\alpha(r_t+\gamma \max Q(s_{t+1},a))\]

    \[=(1-1)*0+1*(1+0.95\max[Q(4,0),Q(4,3)])\]

    \[=0+1*(1+0.95\max[0,0])=1\]

Der gesamte Vorgang der Exploration bis hin zur Belohnung wird als eine Episode bezeichnet. Da der Roboter nun am Ziel angekommen ist, wird die Episode beendet. Während dieses Durchlaufs konnte der Agent zwei Q-Werte berechnen. Diese werden in die Q-Matrix eingetragen, die sozusagen das Gedächtnis des Roboters abbildet.

q-matrix-1

Im weiteren Trainingsverlauf werden nun Schritt für Schritt die Werte der Q-Matrix durch den Algorithmus aktualisiert. Der Roboter entscheidet sicht in jedem Raum für diejenige Aktion, die den höchsten Q-Wert aufweist. Die Exploration der Umgebung endet dann, wenn der Agent eine Belohnung erhalten hat.

Implementierung in Python

Zur Verdeutlichung des obigen Beispiels findet sich im Folgenden der Programmiercode zur Umsetzung in Python. Zunächst wird eine Funktion erstellt, die in Abhängigkeit der Belohnungsmatrix R der Lernrate alpha, dem Diskontierungsfaktor gamma sowie der Episodenzahl episodes den oben skizzierten Algorithmus durchführt.

# Imports
import numpy as np

# Funktion
def q_learning(R, gamma, alpha, episodes):

    """ Funktion für Q Learning """
    
    # Anzahl der Zeilen und Spalten der R-Matrix
    n, p = R.shape

    # Erstellung der Q Matrix (0-Werte)
    Q = np.zeros(shape=[n, p])
    
    # Loop Episoden
    for i in range(episodes):

        # Zufälliger Startpunkt des Roboters
        state = np.random.randint(0, n, 1)

        # Iteration
        for j in range(100):

            # Mögliche Rewards im aktuellen Status
            rewards = R[state]

            # Mögliche Bewegungen des Roboters im aktuellen Status
            possible_moves = np.where(rewards[0] > -1)[0]
            
            # Zufällige Bewegung des Roboters
            next_state = np.random.choice(possible_moves, 1)

            # Update der Q values berechnen
            Q[state, next_state] = (1 - alpha) * Q[state, next_state] + alpha * (
                R[state, next_state] + gamma * np.max(Q[next_state, :]))

            # Abbrechen der Episode wenn Ziel erreicht
            if R[state, next_state] == 1:
                break

    # Q-Matrix zurückgeben
    return Q

Zunächst wird die Anzahl der Stati n sowie die Anzahl der Aktionen p bestimmt. Diese werden aus der Belohnungsmatrix R abgeleitet. Anschließend wird die zunächst noch leere Matrix Q erstellt, die die Q-Werte während der Lernphase speichert. In der Schleife über die Anzahl der festgelegten Episoden episodes wird zunächst ein zufälliger Startpunkt für den Agenten gewählt. Anschließend wird in j=100 Iterationen der oben beschriebene Algorithmus durchgeführt: Aus der Menge möglicher Aktionen possible_moves für den aktuellen Status state wird eine zufällige Auswahl getroffen und in der Variable next_state gespeichert. Im Anschluss daran findet das Update der Q-Werte, gem. oben beschriebener Formel statt. Hierbei werden sowohl der aktuelle Q-Wert an der Stelle Q[state, next_state] als auch die Belohnung des aktuellen Status R[state, next_state] verarbeitet. Nach der Aktualisierung der Q-Werte wird noch geprüft, ob der Agent die Belohnung erhalten hat. Falls ja, wird der innere Loop beendet und die Simulation geht in die nächste Episode. Nach Beendigung aller Episoden wird die finale Q-Matrix zurückgegeben.

Eine praktische Anwendung der oben gezeigten Funktion findet sich in der unten stehenden Codebox. Zunächst wird die Belohnungsmatrix analog für das oben skizzierte Beispiel definiert, die dann mit den weiteren Funktionsargumenten an die Funktion q_learning übergeben wird. Die Lernrate wird auf \alpha=0.8 und der Diskontierungsfaktor auf \gamma=0.95 gesetzt. Insgesamt sollen n=1000 Episoden simuliert werden.

# Anzahl der Räume
rooms = 5

# Belohnungs-Matrix
R = np.zeros(shape=[rooms, rooms])
R[0, :] = [-1,  0,  0, -1,  1]
R[1, :] = [ 0, -1, -1, -1, -1]
R[2, :] = [ 0, -1, -1, -1,  1]
R[3, :] = [-1, -1, -1, -1,  1]
R[4, :] = [ 0, -1,  0,  0,  1]

# Q-Learning
Q = q_learning(R=R, gamma=0.95, alpha=0.8, episodes=1000)

# Finale Q Matrix normalisieren und anzeigen
np.round(Q / np.max(Q), 4)
array([[ 0.    ,  0.9025,  0.95  ,  0.    ,  1.    ],
       [ 0.95  ,  0.    ,  0.    ,  0.    ,  0.    ],
       [ 0.95  ,  0.    ,  0.    ,  0.    ,  1.    ],
       [ 0.    ,  0.    ,  0.    ,  0.    ,  1.    ],
       [ 0.95  ,  0.    ,  0.95  ,  0.95  ,  1.    ]])

An der finalen Q-Matrix kann man nun die gelernten Handlungen des Roboters ablesen. Dies geschieht durch Ablesen des maximalen Q-Wertes pro Status (Zeile). Beispiel: Befindet sich der Roboter im Flur (Zeile 1 der Matrix) so ist der maximale Q-Wert in der letzten Spalte zu finden. Dies korrespondiert zu einer Bewegung ins Wohnzimmer. Befindet sich der Agent im Bad (Zeile 2) bewegt er sich zunächst in den Flur (Zeile 1). Von dort bestimmt der maximale Q-Wert eine Bewegung ins Wohnzimmer.

Ein komplexeres Beispiel

Unser Dusty3000 hat sich in der einfachen Simulationsumgebung bereits bewährt. Nun soll geklärt werden, wie sich der Roboter in komplexeren Wohnungen zurecht finden kann. Zu diesem Zweck wurde die Wohnung um zwei weitere Zimmer ergänzt und der Staub vom Wohnzimmer ins Kinderzimmer verlegt:

grundriss-wohnung-2

Das Kinderzimmer ist im Vergleich zum vorherigen Beispiel deutlich schwieriger und nur über verschiedene Pfade zu erreichen. Somit wird der Roboter es schwerer haben, die Q-Werte richtig zu schätzen. Die Belohnungsmatrix verändert sich entsprechend wie folgt:

reward-matrix-2

Analgog zum vorhergehenden Beispiel wird die Lernrate auf \alpha=0.8 und der Diskontierungsfaktor auf \gamma=0.95 gesetzt. Insgesamt werden n=1000 Episoden simuliert.

# Anzahl der Räume
rooms = 7

# Belohnungs-Matrix
R = np.zeros(shape=[rooms, rooms])
R[0, :] = [-1,  0,  0, -1,  0, -1, -1]
R[1, :] = [ 0, -1, -1, -1, -1, -1, -1]
R[2, :] = [ 0, -1, -1, -1,  0,  1, -1]
R[3, :] = [-1, -1, -1, -1,  0, -1, -1]
R[4, :] = [ 0, -1,  0,  0, -1, -1,  0]
R[5, :] = [-1, -1,  0, -1, -1, -1,  0]
R[6, :] = [-1, -1, -1, -1,  0,  0, -1]

# Q-Learning!
Q = q_learning(R=R, gamma=0.95, alpha=0.8, episodes=1000)

# Normalize
np.round(Q / np.max(Q), 4)
array([[ 0.    ,  0.8571,  0.9499,  0.    ,  0.9023,  0.    ,  0.    ],
       [ 0.9023,  0.    ,  0.    ,  0.    ,  0.    ,  0.    ,  0.    ],
       [ 0.9023,  0.    ,  0.    ,  0.    ,  0.9023,  1.    ,  0.    ],
       [ 0.    ,  0.    ,  0.    ,  0.    ,  0.9023,  0.    ,  0.    ],
       [ 0.9023,  0.    ,  0.9497,  0.857 ,  0.    ,  0.    ,  0.857 ],
       [ 0.    ,  0.    ,  0.9499,  0.    ,  0.    ,  0.    ,  0.8571],
       [ 0.    ,  0.    ,  0.    ,  0.    ,  0.9023,  0.9023,  0.    ]])

Die resultierende Q-Matrix ist komplexer als im einfacheren Vorgängerbeispiel. Beispiel: Ausgehend vom Flur (Zeile 1) bewegt sich der Agent in Zimmer 2 (Schlafzimmer) und von dort aus dann ins Kinderzimmer, wo die Belohnung auf ihn wartet. Ausgehend von der Küche (Zeile 4) fährt der Roboter ins Wohnzimmer (Zeile 5) von dort ins Schlafzimmer (Zeile 3) und dann schlussendlich ins Kinderzimmer.

Fazit und Ausblick

Mit Reinforcement Learning und Q-Learning ist es möglich, Algorithmen und Systeme zu entwickeln, die autark in deterministischen als auch stochastischen Umgebungen Handlungen erlernen und ausführen können; ohne diese exakt zu kennen. Dabei versucht der Agent stets basierend auf seinen Handlungen, die für ihn von der Umgebung erzeugte Belohnung zu maximieren. Dabei kann über den Diskontierungsfaktor gesteuert werden, ob der Agent einen Fokus auf kurzfristige oder langfristige Belohnungen legen soll. Die Anwendungsgebiete für solche Agenten sind vielfältig und spannend: Vor einiger Zeit veröffentlichte Google ein Paper, in dem mittels modellbasiertem Reinforcement Learning ein Agent trainiert darauf wurde, verschiedenste Atari Computerspiele zu spielen. In der nächsten Entwicklungsstufe wurde das zuvor entwickelte RL System DQN (Deep Q-Network) auf den deutlich komplexeren Strategiespiel-Klassiker StarCraft angewendet. Im Gegensatz zum hier gezeigten Beispiel wurden dabei die Ableitung der jeweils optimalen Handlung nicht in Form einer matrizenbasierten Übersicht, sondern über Deep Learning Modelle gelöst, die die Q-Werte in s_{t+1} modellbasiert auf Basis der Pixel auf dem Bildschirm approximieren. Auf diesen Anwendungsfall werden wir im zweiten Teil der Reihe zum Thema Reinforcement Learning eingehen und zeigen, wie neuronale Netze und Deep Learning als Q-Approximatoren genutzt werden können. Dies eröffnet nochmals deutliche komplexere und realitätsnähere Anwendungsfälle, da die Anzahl der Stati beliebig hoch sein können.

Referenzen

  1. Sutton, Richard S.; Barto, Andrew G. (1998). Reinforcement Learning: An Introduction. MIT Press.
  2. Goodfellow, Ian; Bengio Yoshua; Courville, Cohan (2016). Deep Learning. MIT Press.

[author class=“mtl“ title=“Über den Autor“] Sebastian Heinz Sebastian Heinz

TensorFlow ist aktuell eines der wichtigsten Frameworks zur Programmierung von neuronalen Netzen, Deep Learning Modellen und anderen Machine Learning Algorithmen. Es basiert auf einem C++ Low Level Backend, das jedoch über eine Python Library gesteuert wird. TensorFlow lässt sich sowohl auf CPU als auch GPU (Clustern) ausführen. Seit kurzem existiert auch ein R Package, mit dem TensorFlow genutzt werden kann. TensorFlow ist keine Software für Deep Learning oder Data Science Einsteiger sondern richtet sich klar an erfahrene Anwender, die über solide Programmierkenntnisse verfügen. Seit einiger Zeit gibt es jedoch Keras, eine High Level API, die mit vereinfachten Funktionen auf TensorFlow aufbaut und die Implementierung von Standardmodellen schnell und einfach gestaltet. Dies ist nicht nur für Deep Learning Einsteiger interessant sondern auch für Experten, die durch den Einsatz von Keras ihre Modelle schneller und effizienter prototypen können.

Der folgende Beitrag soll die zentralen Elemente und Konzepte von TensorFlow näher erläutern und anhand eines Praxisbeispiels verdeutlichen. Der Fokus liegt dabei nicht auf der formalen mathematischen Darstellung der Funktionsweise neuronaler Netze, sondern auf den grundlegenden Konzepten und Terminologien von TensorFlow sowie deren Umsetzung in Python.

Tensoren

In der ursprünglichen Bedeutung beschreibt ein Tensor den Absolutbetrag sog. Quaterionen, komplexer Zahlen, die den Wertebereich reeler Zahlen erweitern. Heutzutage ist diese Bedeutung jedoch nicht mehr gebräuchlich. Unter einem Tensor versteht man heute eine Verallgemeinerung von Skalaren, Vektoren und Matrizen. Ein zweidimensionaler Tensor ist also eine Matrix mit Zeilen und Spalten (also zwei Dimensionen). Insbesondere höherdimensionale Matrizen werden häufig als Tensoren bezeichnet. Die Bedeutung Tensor ist grundsätzlich jedoch unabhängig von der Anzahl der vorliegenden Dimensionen. Somit kann ein Vektor also als 1-dimensionaler Tensor beschrieben werden. In TensorFlow fließen also Tensoren – durch den sog. Graphen.

Der Graph

Die grundlegende Funktionsweise von TensorFlow basiert auf einem sog. Graphen. Dieser bezeichnet eine abstrakte Darstellung des zugrunde liegenden mathematischen Problems in Form eines gerichteten Diagramms. Das Diagramm besteht aus Knoten und Kanten die miteinander verbunden sind. Die Knoten des Graphen repräsentieren in TensorFlow Daten und mathematische Operationen. Durch die richtige Verbindung der Knoten kann ein Graph erstellt werden, der die notwendigen Daten und mathematischen Operationen zur Erstellung eines neuronalen Netzes beinhaltet. Das folgende Beispiel soll die grundlegende Funktionsweise verdeutlichen:

Beispiel graph

In der obenstehenden Abbildung sollen zwei Zahlen addiert werden. Die beiden Zahlen werden in den Variablen a und b gespeichert. Die Variablen fließen durch den Graphen bis zur quadratischen Box, an der eine Addition durchgeführt wird. Das Ergebnis der Addition wird in der Variablen c gespeichert. Die Variablen a, b und c können als Platzhalter, in TensorFlow „placeholder“ genannt, verstanden werden. Alle Zahlen, die für a und b eingesetzt werden, werden nach dem gleichen Ablauf verarbeitet. Diese abstrakte Darstellung der durchzuführenden mathematischen Operationen in der Kern von TensorFlow. Der folgende Code zeigt die Umsetzung dieses einfachen Beispiels in Python:

# TensorFlow laden 
import tensorflow as tf 

# a und b als Platzhalter definieren 
a = tf.placeholder(dtype=tf.int8) 
b = tf.placeholder(dtype=tf.int8) 

# Die Addition definieren 
c = tf.add(a, b) 

# Den Graphen initialisieren 
graph = tf.Session() 

# Den Graphen an der Stelle c ausführen 
graph.run(c)

Zunächst wird die TensorFlow Library importiert. Danach werden die beiden Platzhalter a und b mittels tf.placeholder() definiert. Da TensorFlow auf einem C++ Backend basiert, müssen die Datentypen der Platzhalter im Voraus fix definiert und können nicht zur Laufzeit angepasst werden. Dies geschieht innerhalb der Funktion tf.placeholder() mit dem Argument dtype=tf.int8, was einem 8-bit Integer (Ganzzahl) entspricht. Über die Funktion tf.add()werden nun die beiden Platzhalter miteinander addiert und in der Variable c gespeichert. Mittels tf.Session() wird der Graph initialisiert und anschließend durch graph.run(c) an der Stelle c ausgeführt. Natürlich handelt es sich bei diesem Beispiel um eine triviale Operation. Die benötigten Schritte und Berechnungen in neuronalen Netzen sind deutlich komplexer. Die prinzipielle Funktionsweise der graphenbasierten Ausführung bleibt jedoch bestehen.

Platzhalter

Wie bereits zuvor beschrieben, spielen Platzhalter in TensorFlow eine zentrale Rolle. Platzhalter beinhalten in der Regel alle Daten, die zum Training des neuronalen Netzes benötigt werden. Hierbei handelt es sich normalerweise um Inputs (die Eingangssignale des Modells) und Outputs (die zu vorhersagenden Variablen).

# Platzhalter definieren 
X = tf.placeholder(dtype=tf.float32, shape=[None, p]) 
Y = tf.placeholder(dtype=tf.float32, shape=[None])

Im obigen Codebeispiel werden zwei Platzhalter definiert. X soll als Platzhalter für die Inputs des Modells dienen, Y als Platzhalter für die tatsächlich beobachteten Outputs in den Daten. Neben dem Datentyp der Platzhalter muss noch die Dimension der Tensoren definiert werden, die in den Platzhaltern gespeichert werden. Dies wird über das Funktionsargument shape gesteuert. Im Beispiel handelt es sich bei den Inputs um einen Tensor der Dimension [None, p] und bei dem Output um einen eindimensionalen Tensor. Der Parameter None weist TensorFlow an, diese Dimension flexibel zu halten, da im aktuellen Stadium noch unklar ist, welche Ausdehnung die Daten zum Trainieren des Modells haben werden.

Variablen

Neben Platzhaltern sind Variablen ein weiteres Kernkonzept der Funktionsweise von TensorFlow. Während Platzhalter zum Speichern der Input- und Outputdaten verwendet werden, sind Variablen flexibel und können Ihre Werte während der Laufzeit der Berechnung verändern. Der wichtigste Anwendungsbereich für Variablen in neuronalen Netzen sind die Gewichtungsmatrizen der Neuronen (Weights) und Biasvektoren (Biases), die während des Trainings stetig an die Daten angepasst werden. Im folgenden Codeblock werden die Variablen für ein einschichtiges, Feedforward Netz definiert.

# Anzahl der zu Inputs und Outputs 
n_inputs = 10 
n_outputs = 1 

# Anzahl der Neuronen 
n_neurons = 64 

# Hidden Layer: Variablen für Weights und Biases 
w_hidden = tf.Variable(weight_initializer([n_inputs, n_neurons])) 
bias_hidden = tf.Variable(bias_initializer([n_neurons])) 

# Output layer: Variablen für Weights und Biases 
w_out = tf.Variable(weight_initializer([n_neurons, n_outputs])) 
bias_out = tf.Variable(bias_initializer([n_outputs]))

Im Beispielcode werden n_inputs = 10 Inputs und n_outputs = 1 Outputs definiert. Die Anzahl der Neuronen im Hidden Layer beträgt n_neurons = 64. Im nächsten Schritt werden die benötigten Variablen instanziert. Für ein einfaches Feedforward Netz werden zunächst die Gewichtungsmatrizen und Biaswerte zwischen Input- und Hidden Layer benötigt. Diese werden in den Objekten w_hidden und bias_hidden mittels der Funktion tf.Variable() angelegt. Innerhalb von tf.Variable() wird weiterhin die Funktion weight_initializer() verwendet, auf die wir im nächsten Abschnitt genauer eingehen. Nach der Definition der benötigten Variablen zwischen Input- und Hidden Layer werden noch die Weights und Biases zwischen Hidden- und Output Layer instanziert.

Es ist wichtig zu verstehen, welche Dimensionen die benötigten Matrizen der Weights und Biases annehmen müssen, damit sie korrekt verarbeitet werden. Als Daumenregel für Gewichtungsmatrizen in einfachen Feedforward Netzen gilt, dass die zweite Dimension des vorhergehenden Layers die erste Dimension des aktuellen Layers darstellt. Was sich zunächst sehr komplex anhört ist schlussendlich nichts anderes als das Weiterreichen von Outputs von Layer zur Layer im Netz. Die Dimension der Biaswerte entspricht normalerweise der Anzahl der Neuronen im aktuellen Layer. Im obigen Beispiel wird somit aus der Anzahl Inputs und der Anzahl Neuronen eine Gewichtungsmatrix der Form [n_inputs, n_neurons] = [10, 64] sowie ein Biasvektor im Hidden Layer der Ausdehnung [bias_hidden] = [64]. Zwischen dem Hidden- und Output Layer hat die Gewichtungsmatrix die Form [n_neurons, n_outputs] = [64, 1] sowie der Biasvektor die Form [1].

Initialisierung

Bei der Definition der Variablen im Codeblock des vorhergehenden Abschnitts wurde die Funktionen weight_initializer() und bias_initializer() verwendet. Die Art und Weise, wie die initialen Gewichtungsmatrizen und Biasvektoren gefüllt werden, hat einen großen Einfluss darauf, wie schnell und wie gut sich das Modell an die vorliegenden Daten anpassen kann. Dies hängt damit zusammen, dass neuronale Netze und Deep Learning Modelle mittels numerischer Optimierungsverfahren trainiert werden, die immer von einer bestimmten Startposition aus beginnen die Parameter des Modells anzupassen. Wenn nun eine vorteilhafte Startposition für das Training des neuronalen Netzes gewählt wird, wirkt sich dies in der Regel positiv auf die Rechenzeit und Anpassungsgüte des Modells aus.

In TensorFlow sind verschiedenste Initialisierungsstrategien implementiert. Angefangen von einfachen Matrizen mit immer dem gleichen Wert, z.B. tf.zeros_initializer() über Zufallswerte, z.B. tf.random_normal_initializer() oder tf.truncated_normal_initializer() bis hin zu komplexeren Funktionen wie tf.glorot_normal_initializer() oder tf.variance_scaling_initializer(). Je nachdem, welche Initialisierung der Gewichte und Biaswerte vorgenommen wird, kann das Ergebnis des Modelltrainings mehr oder weniger stark variieren.

# Initializers 
weight_initializer = tf.variance_scaling_initializer(mode="fan_avg", distribution="uniform", scale=1) 
bias_initializer = tf.zeros_initializer()

In unserem Beispiel verwenden wir zwei verschiedene Initialisierungsstrategien für die Gewichte und Biaswerte. Während zur Initialisierung der Gewichtungen tf.variance_scaling_initializer() verwendet wird, nutzen wir tf.zeros_initializer() für die Biaswerte.

Design der Netzwerkarchitektur

Nach der Implementierung der benötigten Gewichtungs- und Biasvariablen wird im nächsten Schritt die Netzwerkarchitektur, auch Topologie genannt, erstellt. Hierbei werden sowohl Platzhalter als auch Variablen in Form von aufeinanderfolgenden Matrizenmultiplikationen miteinander kombiniert.

Weiterhin werden bei der Spezifikation der Topologie auch die Aktivierungsfunktionen der Neuronen festgelegt. Aktivierungsfunktionen führen eine nichtlineare Transformation der Outputs der Hidden Layer durch bevor diese an den nächsten Layer weitergegeben werden. Dadurch wird das gesamte System nichtlinear und kann sich dadurch sowohl an lineare als auch nichtlineare Funktionen anpassen. Es existieren zahllose Aktivierungsfunktionen für neuronale Netze, die sich im Laufe der Zeit entwickelt haben. Heutiger Standard bei der Entwicklung von Deep Learning Modellen ist die sog. Rectified Linear Unit (ReLU), die sich in vielen Anwendungen als vorteilhaft herausgestellt hat.

# Hidden layer 
hidden = tf.nn.relu(tf.add(tf.matmul(X, w_hidden), bias_hidden)) 

# Output layer 
out = tf.transpose(tf.add(tf.matmul(hidden, w_out), bias_out))

ReLU Aktivierungsfunktionen sind in TensorFlow mittels tf.nn.relu() implementiert. Die Aktivierungsfunktion nimmt den Output der Matrizenmultiplikation zwischen Platzhalter und Gewichtungsmatrix sowie der Addition der Biaswerte entgegen und transformiert diese, tf.nn.relu(tf.add(tf.matmul(X, w_hidden), bias_hidden)). Das Ergebnis der nichtlinearen Transformation wird als Output an den nächsten Layer weitergegeben, der diesen als Input einer erneuten Matrizenmultiplikation verwendet. Da es sich beim zweiten Layer bereits um den Output Layer handelt, wird in diesem Beispiel keine erneute ReLU Transformation durchgeführt. Damit die Dimensionalität des Output Layers mit derer der Daten übereinstimmt muss nochmals mittels tf.transpose() eine Transponierung der Matrix vorgenommen werden. Andernfalls kann es zu Problemen bei der Schätzung des Modells kommen.

Architektur von TensorFlow

Die oben stehende Abbildung soll die Architektur des Netzwerkes schematisch illustrieren. Das Modell besteht aus drei Teilen: (1) dem Input Layer, (2) dem Hidden Layer sowie (3) dem Output Layer. Diese Architektur nennt man Feedforward Netzwerk. Feedforward beschreibt die Tatsache, dass die Daten nur in eine Richtung durch das Netzwerk fließen. Andere Arten von neuronalen Netzen und Deep Learning Modellen beinhalten Architekturen, die es den Daten erlauben sich auch „rückwärts“ oder in Schleifen im Netzwerk zu bewegen.

Kostenfunktion

Die Kostenfunktion (Cost Function) des neuronalen Netzes wird verwendet, um eine Maßzahl zur Bestimmung der Abweichung zwischen der Prognose des Modells und den tatsächlich beobachteten Daten zu berechnen. Hierzu stehen, je nachdem, ob es sich um eine Klassifikation oder Regression handelt, verschiedene Kostenfunktionen zur Verfügung. Für Klassifikationen wird heute zumeist die sog. Kreuzentropie (Cross Entropy) verwendet, für Regressionen die mittlere quadratische Abweichung (Mean Squared Error, MSE). Grundsätzlich kann jede mathematisch differenzierbare Funktion als Kostenfunktion verwendet werden.

# Cost function 
mse = tf.reduce_mean(tf.squared_difference(out, Y))

Im obigen Beispiel wird die mittlere quadratische Abweichung als Kostenfunktion implementiert. Hierzu stehen in TensorFlow die beiden Funktionen tf.reduce_mean() sowie tf.squared_difference() zur Verfügung, die einfach miteinander kombiniert werden können. Man sieht, dass die Funktionsargumente von tf.squared_difference() zum einen der Platzhalter Y ist, der die tatsächlich beobachteten Outputs enthält sowie das Objekt out, das die vom Modell erzeugten Prognosen beinhaltet. An der Kostenfunktion laufen also die tatsächlich beobachteten Daten mit den Modellprognosen zusammen und werden miteinander verglichen.

Optimierer

Der Optimierer (Optimizer) hat die Aufgabe, auf Basis der berechneten Modellabweichungen der Kostenfunktion, die Gewichte und Biaswerte des Netzes während des Trainings anzupassen. Um dies zu tun, werden von TensorFlow sog. Gradienten der Kostenfunktion berechnet, die die Richtung anzeigen, in der die Gewichte und Biaswerte angepasst werden müssen, um die Kostenfunktion des Modells zu minimieren. Die Entwicklung von schnellen und stabilen Optimierern ist ein großer Forschungszweig im Bereich neuronaler Netze und Deep Learning.

# Optimizer 
opt = tf.train.AdamOptimizer().minimize(mse)

Hier wird der sog. tf.AdamOptimizer()verwendet, der im Moment einer der am häufigsten angewendeten Optimierer ist. Adam steht für Adaptive moment estimation und ist eine methodische Kombination von zwei anderen Optimierungstechniken (AdaGrad und RMSProp). An dieser Stelle gehen wir nicht auf die mathematischen Details der Optimierer ein, da dies den Scope dieser Einführung deutlich sprengen würde. Wichtig zu verstehen ist, dass es verschiedene Optimierer gibt, die auf unterschiedlichen Strategien zur Berechnung der benötigten Anpassungen der Gewichtungs- und Biaswerte basieren.

Session

Die TensorFlow Session ist das Grundgerüst zur Ausführung des Graphen. Die Session wird mit dem Kommando tf.Session() gestartet. Bevor die Session nicht gestartet wurde, kann keine Berechnung innerhalb des Graphen erfolgen.

# Session starten 
graph = tf.Session()

In dem Codebeispiel wird eine TensorFlow Session in dem Objekt graph instanziert und kann im Folgenden an einer beliebigen Stelle im Graphen ausgeführt werden. In den aktuellen Entwicklungsversionen (Dev-Builds) von TensorFlow entstehen Ansätze den Code auch ohne die Definition einer Session auszuführen. Aktuell ist dies jedoch nicht im Stable Build enthalten.

Training

Nachdem die notwendigen Bestandteile des neuronalen Netzes definiert wurden, können diese nun im Rahmen des Modelltrainings miteinander verbunden werden. Das Training von neuronalen Netzen läuft heute in der Regel über ein sog. „Minibatch Training“ ab. Minibatch bedeutet, dass wiederholt zufällige Stichproben der Inputs und Outputs verwendet werden, um die Gewichtungen und Biaswerte des Netzes anzupassen. Hierzu wird ein Parameter definiert, der die Größe der Zufallsstichprobe (Batches) der Daten steuert. Hierbei ist es häufig so, dass die Daten ohne Zurücklegen gezogen werden, sodass jede Beobachtung im Datensatz für eine Trainingsrunde (auch Epoche genannt) nur einmal dem Netz präsentiert wird. Die Anzahl der Epochen wird ebenfalls als Parameter durch den Anwender definiert.

Die einzelnen Batches werden über die zuvor erstellten Platzhalter X und Y mittels Feed Dictionary in den TensorFlow Graphen übergeben und entsprechend im Modells verwendet. Dies geschieht in Kombination mit der zuvor definierten Session.

# Aktuellen Batch in Netzwerk übergeben 
graph.run(opt, feed_dict={X: data_x, Y: data_y})

Im obigen Beispiel wird der Optimierungsschritt opt im Graphen durchgeführt. Damit TensorFlow die notwendigen Berechnungen ausführen kann, müssen über das Argument feed_dict Daten in den Graphen übergeben werden, die für die Berechnungen an die Stelle der Platzhalter X und Y treten sollen.

Nach der Übergabe mittels feed_dict wird X über die Multiplikation mit der Gewichtungsmatrix zwischen Input und Hidden Layer in das Netz eingespeist und durch die Aktivierungsfunktion nichtlinear transformiert. Anschließend wird das Ergebnis des Hidden Layers wiederum mit der Gewichtungsmatrix zwischen Hidden und Output Layer multipliziert und an den Output Layer weitergereicht. Hier wird durch die Kostenfunktion die Differenz zwischen der Prognose des Netzes und den tatsächlich beobachteten Werten Y berechnet. Auf Basis des Optimierers werden nun für jeden einzelnen Gewichtungsparameter im Netz die Gradienten berechnet. Die Gradienten wiederum sind die Basis auf denen eine Anpassung der Gewichtungen in Richtung der Minimierung der Kostenfunktion durchgeführt wird. Diesen Vorgang nennt man auch Gradient Descent. Anschließend wird der soeben beschriebene Prozess mit dem nächsten Batch erneut durchgeführt. Das neuronale Netz bewegt sich also in jeder Iteration näher in Richtung der Kostenminimierung, sprich in Richtung einer kleineren Abweichung zwischen Prognose und beobachteten Werten.

Anwendungsbeispiel

Im folgenden Beispiel sollen die zuvor vermittelten Konzepte nun anhand eines praktischen Beispiels dargestellt werden. Zur Durchführung benötigen wir zunächst einige Daten auf Basis derer das Modell trainiert werden kann. Diese können mittels der in sklearn enthaltenen Funktion sklearn.datasets.make_regression() einfach und schnell simuliert werden.

# Anzahl der zu Inputs, Neuronen und Outputs 
n_inputs = 10 
n_neurons = 64 
n_outputs = 1 

# Daten für das Modell simulieren 
from sklearn.datasets import make_regression 
data_x, data_y = make_regression(n_samples=1000, n_features=n_inputs, n_targets=n_outputs)

Wie im obigen Beispiel verwenden wir 10 Inputs und 1 Output zur Erstellung eines neuronalen Netzes zur Prognose des beobachteten Outputs. Anschließend definieren wir Platzhalter, Initialisierung, Variablen, Netzarchitektur, Kostenfunktion, Optimierer und Session in TensorFlow.

# Platzhalter definieren 
X = tf.placeholder(dtype=tf.float32, shape=[None, n_inputs]) 
Y = tf.placeholder(dtype=tf.float32, shape=[None]) 

# Initializers 
weight_initializer = tf.variance_scaling_initializer(mode="fan_avg", distribution="uniform", scale=1) 
bias_initializer = tf.zeros_initializer() 

# Hidden Layer: Variablen für Weights und Biases 
w_hidden = tf.Variable(weight_initializer([n_inputs, n_neurons])) 
bias_hidden = tf.Variable(bias_initializer([n_neurons])) 

# Output layer: Variablen für Weights und Biases 
w_out = tf.Variable(weight_initializer([n_neurons, n_outputs])) 
bias_out = tf.Variable(bias_initializer([n_outputs])) 

# Hidden Layer 
hidden = tf.nn.relu(tf.add(tf.matmul(X, w_hidden), bias_hidden)) 

# Output Layer 
out = tf.transpose(tf.add(tf.matmul(hidden, w_out), bias_out)) 

# Kostenfunktion 
mse = tf.reduce_mean(tf.squared_difference(out, Y)) 

# Optimizer 
opt = tf.train.AdamOptimizer().minimize(mse) 

# Session starten 
graph = tf.Session()

Nun beginnt das Training des Modells. Hierfür benötigen wir zunächst einen äußeren Loop, der über die Anzahl der definierten Epochen ausgeführt wird. Innerhalb jeder Iteration des äußeren Loops werden die Daten zufällig in Batches eingeteilt und nacheinander in einem inneren Loop dem Netzwerk präsentiert. Nach Beendigung einer Epoche wird der MSE, also die mittlere quadratische Abweichung des Modells zu den tatsächlich Beobachteten Daten berechnet und ausgegeben.

# Batch Größe 
batch_size = 256 

# Anzahl möglicher Batches 
max_batch = len(data_y) // batch_size 

# Anzahl Epochen 
n_epochs = 100 

# Training des Modells 
for e in range(n_epochs): 
    # Zufällige Anordnung von X und Y für Minibatch Training 
    shuffle_indices = np.random.randint(low=0, high=n, size=n) 
    data_x = data_x[shuffle_indices] 
    data_y = data_y[shuffle_indices] 
 
    # Minibatch training 
    for i in range(0, max_batch): 
       # Batches erzeugen 
        start = i * batch_size 
        end = start + batch_size 
        batch_x = data_x[start:end] 
        batch_y = data_y[start:end] 

        # Aktuellen Batch in Netzwerk übergeben 
        net.run(opt, feed_dict={X: batch_x, Y: batch_y}) 

    # Pro Epoche den MSE anzeigen 
    mse_train = graph.run(mse, feed_dict={X: data_x, Y: data_y}) 
    print('MSE: ' + str(mse))

Insgesamt werden dem Netzwerk 15 Batches pro Epoche präsentiert. Innerhalb von 100 Epochen ist das Modell in der Lage den MSE von anfangs 20.988,60 auf 55,13 zu reduzieren (Anmerkung: diese Werte unterscheiden sich von Ausführung zu Ausführung aufgrund der zufälligen Initialisierung der Gewichte und Biaswerte sowie der zufälligen Ziehung der Batches). Die untenstehende Abbildung zeigt den Verlauf der mittleren quadratischen Abweichung während des Trainings.

Verlauf MSE-Training

Es ist zu sehen, dass das Modell mit einer ausreichend hohen Anzahl an Epochen in der Lage ist, den Trainingsfehler auf nahe 0 zu reduzieren. Was sich zunächst vorteilhaft anhört ist für echte Machine Learning Projekte ein Problem. Die Kapazität neuronaler Netze ist häufig so hoch, dass sie die Trainingsdaten einfach „auswendig lernen“ und auf neuen, ungesehenen Daten schlecht generalisieren. Man spricht hierbei von einem sog. Overfitting der Trainingsdaten. Aus diesem Grund wird häufig bereits während des Trainings der Prognosefehler auf ungesehenen Testdaten überwacht. In der Regel wird das Training des Modells an der Stelle abgebrochen, an der der Prognosefehler der Testdaten wieder anzusteigen beginnt. Dies nennt man Early Stopping.

Zusammenfassung und Ausblick

Mit TensorFlow ist es Google gelungen, einen Meilenstein im Bereich Deep Learning Forschung zu setzen. Durch die geballte intellektuelle Kapazität von Google ist es gelungen eine Software zu erschaffen, die sich bereits nach sehr kurzer Zeit als Quasi-Standard bei der Entwicklung von neuronalen Netzen und Deep Learning Modellen etablieren konnte. Auch bei STATWORX arbeiten wir in der Data Science Beratung erfolgreich mit TensorFlow, um für unsere Kunden Deep Learning Modelle und neuronale Netze zu entwickeln. Sebastian Heinz Sebastian Heinz

Deep Learning ist aktuell einer der spannendsten Forschungsbereiche im Machine Learning. Für eine Vielzahl von Fragestellungen liefern Deep Learning Modelle State-of-the-Art Ergebnisse, vor allem im Bereich der Bild-, Sequenz- und Spracherkennung. Weiterhin findet Deep Learning erfolgreich Anwendung in der Fahrzeugkonstruktion (selbstfahrende Autos), in der Finanzwelt (Aktienkursvorhersage, Risikoprognose, automatische Handelssysteme), in der Medizin (maschinelle Bilderkennung von Karzinomen) und Biologie (Genomik), im e-Commerce (Recommendation Systeme) und im Web Umfeld (Anomalieerkennung).

Wie unterscheidet sich Deep Learning von klassischen Machine Learning Algorithmen und warum funktioniert es bei vielen Fragestellungen so gut? Dieser Artikel soll Ihnen eine Einführung in die Grundkonzepte von Deep Learning geben und Unterschiede zu klassischen Verfahren aus dem Machine Learning herausarbeiten.

Einführung in neuronale Netze

Deep Learning erscheint dem Anwender auf den ersten Blick als eine relative neue Methodik. Dies ist hauptsächlich darin begründet, dass die generelle Aufmerksamkeit rund um das Thema durch die vielen methodischen Durchbrüche in den letzten Jahren nicht abzureißen scheint. Fast täglich erscheinen neue wissenschaftliche Publikationen zum Thema Deep Learning bzw. zu angrenzenden Forschungsbereichen.

Fakt ist jedoch, dass die theoretischen und methodischen Grundlagen für Deep Learning durch die wissenschaftliche Entwicklung von neuronalen Netzen bereits vor vielen Jahrzehnten in der 1950er Jahren gelegt wurden. Aufgrund verschiedener technischer und methodischer Limitationen waren damals wirkliche „Netze“, die aus hunderten oder tausenden von Elementen bestehen noch in weiter Ferne. Zunächst beschränkte sich die Forschung auf einzelne Einheiten neuronaler Netze, das Perzeptron bzw. Neuron. Als Vorreiter auf diesem Gebiet ist Frank Rosenblatt zu nennen, der während seiner Zeit am Cornell Aeronautical Laboratory 1957 bis 1959 ein einzelnes Perzeptron bestehend aus 400 photosensitiven Einheiten (Inputs), die über eine „Zuordnungsschicht“ (Association Layer) mit einer Ausgabeschicht von 8 Einheiten verbunden waren. Heute gilt das Rosenblatt Perzeptron als die Geburtsstunde von neuronalen Netzen und Deep Learning Modellen.

Abbildung Frank Rosenblatt

Selbst Jahrzehnte später in den 1980er Jahren, als neuronale Netze ein erstes gesteigertes Interesse erfuhren, blieben tatsächliche praktische Erfolge eher überschaubar. Andere Algorithmen, bspw. Kernel Methoden (Support Vector Machines) oder Decision Trees verdrängten neuronale Netze in den 80er und 90er Jahren auf die hinteren Plätze der Machine Learning Rangliste. Zum damaligen Zeitpunkt konnten neuronale Netze ihr volles Potenzial noch nicht entfalten. Zum einen fehlte es noch immer an Rechenpower, zum anderen litten komplexere Netzarchitekturen, so wie sie heute verwendet werden, am sog. „Vanishing Gradient Problem“, welches das ohnehin schon schwierige Training der Netze weiter erschwerte oder gänzlich unmöglich machte. Bis in die Anfänge des neuen Jahrtausends fristeten die heute umjubelten neuronale Netze ein weitestgehend tristes Dasein im stillen Kämmerlein.

Erst als Geoffrey Hinton, einer der renommiertesten Forscher im Bereich neuronaler Netze, im Jahr 2006 ein revolutionäres Paper veröffentlichte in dem erstmalig das erfolgreiche Training eines mehrschichtigen neuronalen Netzes gelang, erfuhr das Thema neue Beachtung in Forschung und Praxis. Neue methodische Fortschritte, bspw. die Spezifikation von alternativen Aktivierungsfunktionen, die das Vanishing Gradient Problem verhindern sollten oder die Entwicklung von verteilten Rechensystemen beflügelten die Forschung und Anwendung im Bereich neuronaler Netze. Seit nun mehr als 10 Jahren etablieren sich neuronale Netze und Deep Learning zusehends als Synonym für maschinelles Lernen und künstliche Intelligenz. Sie sind ein boomendes wissenschaftliches Forschungsfeld und finden in vielen Praxisszenarien Anwendung. Regelmäßig unterbieten sich Deep Learning Modelle bei der Lösung komplexer Machine Learning Aufgaben im Bereich Sprach- und Bilderkennung. Getrieben durch die rasanten technischen Fortschritte im Bereich verteiltes Rechnen und GPU Computing scheint es nun so, als wäre Deep Learning schlussendlich zur richtigen Zeit am richtigen Ort, um sein volles Potenzial zu entfalten.

Neuronale Netze – ein Abbild des  Gehirns?

Entgegen der weit verbreiteten Meinung, dass neuronale Netze auf der Funktionsweise des Gehirns basieren, kann lediglich bestätigt werden, dass sich moderne Deep Learning Modelle nur zu einem gewissen Teil aus Erkenntnissen der Neurowissenschaft entwickelten. Zudem weiß man heute, dass die tatsächlichen Abläufe und Funktionen im Gehirn, die zur Verarbeitung von Informationen berechnet werden, wesentlich komplexer sind als in neuronalen Netzen abgebildet. Grundsätzlich kann jedoch die Idee, dass viele einzelne „Recheneinheiten“ (Neuronen) durch eine Vernetzung untereinander Informationen intelligent verarbeiten als Grundprinzip anerkannt werden.

Im Gehirn sind Neuronen über Synapsen miteinander verbunden, die sich zwischen Neuronen neu bilden bzw. allgemein verändern können. Die unten stehende Abbildung zeigt eine Pyramidenzelle, eine der am häufigsten anzutreffenden Neuronenstrukturen im menschlichen Gehirn.

Darstellung Pyramidenzelle

Wie auch andere Neuronenarten empfängt die Pyramidenzelle die meisten Ihre Eingangssignale über die Dendriten, die mittels Axon und dessen Dendriten zehntausend oder mehr Verbindungen zu anderen Neuronen eingehen können. Gemäß dem sog. Konvergenz-Divergenzprinzip erfolgt die Erregung einzelner Neuronen auf Basis vieler anderer Zellen wobei das Neuron ebenfalls gleichzeitig Signale an viele andere Zellen aussendet. Somit entsteht ein hochkomplexes Netzwerk von Verbindungen im Gehirn, die zur Informationsverbeitung genutzt werden.

Einfach gesprochen, verarbeiten im Gehirn somit einzelne Neuronen die Signale anderer Neuronen (Inputs) und geben ein darauf basierendes, neues Signal an die nächste Gruppe von Neuronen weiter. Ein einzelnes Neuron ist somit durch eine bestimmte biologische (bzw. mathematische) Funktion repräsentiert, die seine Eingangssignale bewertet, ein entsprechendes Reaktionssignal erzeugt und dieses im Netzwerk an weitere Neuronen weitergibt. Der Begriff Netzwerk entsteht dadurch, dass viele dieser Neuronen in Schichten zusammengefasst werden und Ihre Signale die jeweils folgenden Knoten bzw. Schichten weitergeben und sich somit ein Netz zwischen den Neuronen spannt.

Was versteht man unter Deep Learning?

Unter Deep Learning versteht man heute Architekturen von neuronalen Netzen, die über mehr als einen Hidden Layer verfügen. Der Nutzen mehrerer Neuronen-Schichten macht sich dadurch bemerkbar, dass zwischen den Schichten „neue“ Informationen gebildet werden können, die eine Repräsentation der ursprünglichen Informationen darstellen. Wichtig hierbei ist zu verstehen, dass diese Repräsentationen eine Abwandlung bzw. Abstaktion der eigentlichen Eingangssignale sind.

„Unter Deep Learning versteht man heute neuronalen Netze, die über mehr als einen Hidden Layer verfügen.“

Dieser Mechanismus, der unter dem Begriff „Representation Learning“ zusammengefasst werden kann sorgt dafür, dass Deep Learning Modelle in der Regel sehr gut auf neue Datenpunkte abstrahieren können. Der Grund dafür ist, dass die geschaffenen Abstraktionen der Daten wesentlich generellerer Natur als es die ursprünglichen Eingangsdaten sind. Lernen durch Repräsentation ist bei Deep Learning eines der Hauptunterscheidungsmerkmale gegenüber klassischen Machine Learning Modellen, die i.d.R. ohne Repräsentation der Daten lernen. Die folgende Abbildung illustriert den Unterschied zwischen statistischen Computerprogrammen (Rule-based Systems), klassischen Machine Learning Modellen und Deep Learning.

Abbildung zum Vergleich des Aufbaues von Representation Learning

Da Deep Learning Modelle theoretisch über sehr viele Schichten verfügen, ist die Abstraktionskapazität besonders hoch. Somit kann ein Deep Learning Modell auf verschiedenen Ebenen Abstraktionen der Eingangsdaten bilden und zur Lösung des Machine Learning Problems verwenden. Ein Beispiel für eine solche komplexe Architektur ist das GoogleNet zur Bildklassifikation (siehe Abbildung).

Abbildung des GoogleNet zur Bildklassifikation

Das GoogleNet verfügt über mehrere Architekturblöcke, die speziell auf die Anforderungen im Bereich Objekt- bzw. Bilderkennung ausgelegt sind. Somit ist das Netz in der Lage durch Representation Learning mehrere tausend Objekte auf Bildern mit einer hohen Genauigkeit zu erkennen.

Aufbau und Bestandteile von neuronalen Netzen

Zurück zu den Basics. Der Grundbaustein jedes neuronalen Netzes ist das Neuron. Ein Neuron ist ein Knotenpunkt im neuronalen Netzwerk an dem ein oder mehrere Eingangssignale (numerische Daten) zusammentreffen und von der sog. Aktivierungsfunktion des Neurons weiterverarbeitet werden. Der Output des Neurons ist somit eine Funktion des Inputs. Es existieren verschiedene Aktivierungsfunktionen, auf die wir in späteren Teilen unserer Deep Learning Reihe genauer eingehen werden.

Der elementare Grundbaustein jedes neuronalen Netzes ist das Neuron. Ein Neuron ist ein Knotenpunkt im neuronalen Netzwerk an dem ein oder mehrere Eingangssignale (Inputs) zusammentreffen und verarbeitet werden. Dabei kann es sich, je nachdem an welcher Stelle sich das Neuron im Netzwerk befindet, sowohl um Signale der Eimngangsschicht als auch Signale vorhergehender Neuronen handeln. Nach der Verarbeitung der Eingangssignale werden diese als Output an die nachfolgenden Neuronen weitergegeben. Formal gesprochen, ist der Output eines Neurons eine Funktion der Inputs. Die folgende Abbildung soll den grundsätzlichen Aufbau darstellen:

Einfache Darstellung eines Neurons

Die obenstehende Abbildung stellt schematisch den Aufbau eines einzelnen Neurons dar. Auf der linken Seite der Abbildung treffen die Inputs x_1,...,x_n am Neuron ein. Input steht dabei für einen beliebigen numerischen Wert (z.B. vorhandene Daten oder Signale vorhergehender Neuronen). Das Neuron bewertet (gewichtet) den Input, berechnet den Output o_j und gibt diesen an die darauffolgenden, verbundenen Neuronen weiter. Der Input eines Neurons ist somit die gewichtete Summe aller mit ihm verknüpften vorher liegenden Neuronen.

In der Regel werden in neuronalen Netzen keine einzelnen Neuronen, sondern ganze Schichten mit beliebig vielen dieser Knoten, modelliert. Diese Schichten werden „Hidden Layer“ genannt und sind der Kern der Architektur von Deep Learning Modellen. Analog zum Hidden Layer existieren der Input- und Output Layer. Ersterer ist der Eintrittspunkt der Daten in das Modell, während letzterer das Ergebnis des Modells repräsentiert. In einer einfachen Architektur sind alle Neuronen aller benachbarten Layer miteinander verbunden. Die folgende Abbildung stellt den Aufbau eines einfachen neuronalen Netzes mit einem Input Layer, einem Hidden Layer und einem Output Layer dar.

Abbildung eines Feedforward Netzes

Die Verbindungen (Gewichte / Weights) zwischen den Neuronen sind derjenige Teil des neuronalen Netzes, der auf die vorliegenden Daten angepasst wird. Die Architektur des Netzes bleibt (zumindest in einfachen Architekturen) konstant während des Trainings. Die Gewichte werden von Iteration zu Iteration so angepasst, dass der Fehler, den das neuronale Netz während dem Trainings macht immer weiter reduziert wird. Während des Trainings werden Abweichungen der Schätzung des Netzwerks von den tatsächlich beobachteten Datenpunkten berechnet. Nach der Berechnung des Gesamtfehlers werden die Gewichte des Netzes rekursiv aktualisiert. Die Richtung dieser Aktualisierung wird so gewählt, dass der Fehler im nächsten Durchlauf kleiner wird. Diese Methodik nennt man „Gradient Descent“ und beschreibt die iterative Anpassung der Modellparameter in entgegengesetzter Richtung zum Modellfehler. Auch heutzutage werden Deep Learning Modelle und neuronale Netze mittels Gradient Descent bzw. aktuelleren Abwandlungen davon trainiert.

Architekturen von neuronalen Netzen

Die Architektur von neuronalen Netzen kann durch den Anwender nahezu frei spezifiziert werden. Tools wie  TensorFlow  oder Theano ermöglichen es dem Anwender, Netzarchitekturen beliebiger Komplexität zu modellieren und auf den vorliegenden Daten zu schätzen. Zu den Parametern der Netzarchitektur zählen im einfachsten Falle die Anzahl der Hidden Layer, die Anzahl der Neuronen pro Layer sowie deren Aktivierungsfunktion. Im Rahmen der Forschung zu neuronalen Netzen und Deep Learning haben sich unterschiedlichste Architekturen von Netzwerken für spezifische Anwendungen entwickelt. Jede dieser Architekturen weist spezifische Vorteile und Eigenschaften auf, die die Verarbeitung von speziellen Informationen erleichtern sollen. So wird beispielsweise bei Convolutional Neural Networks (CNNs), die primär zur Verarbeitung von Bildinformationen eingesetzt werden, die räumliche Anordnung von Informationen berücksichtigt, bei Recurrent Neural Nets (RNNs) die zeitliche Anordnung von Datenpunkten. Im Folgenden sollen die wichtigsten Typen von Architekturen kurz skizziert werden.

Feedforward  Netze

Unter einem Feedforward  Netz versteht man ein neuronales Netz mit einer Inputschicht, einem oder mehreren Hidden Layers sowie einer Outputschicht. In der Regel handelt es sich bei den Hidden Layers um sogenannte „Dense Layers“, d.h. voll vernetzte Neuronen mit Gewichtungen zu allen Neuronen der vorherigen und folgenden Schicht. Die folgende Abbildung zeigt ein Deep Learning Modell, das mit vier Hidden Layers konstruiert wurde.

Abbildung eines mehrschichtigen Deep Learning Modells

Während Multi Layer Perceptrons (MLP) als universale Architekturen für eine Vielzahl von Fragestellungen geeignet sind, weisen Sie einige Schwächen in bestimmte Einsatzgebieten auf. So steigt die für eine Bildklassifikation notwendige Anzahl der Neuronen für eine Bildklassifikation mit jedem Pixel des Bildes immer weiter an.

Convolutional Neural Networks (CNNs)

Convolutional Neural Networks (CNN) sind ein spezieller Typ von neuronalen Netzwerken zur Verarbeitung von räumlich angeordneten Daten. Hierzu zählen bspw. Bildinformationen (2 Dimensionen), Videos (3 Dimensionen) oder Audiospuren (1-2 Dimensionen). Die Architektur von CNNs unterscheidet sich deutlich von der eines klassischen Feedforward Netzes. CNNs werden mit einer speziellen Architektur gestaltet, den sogenannten Convolutional und Pooling Layers. Der Zweck dieser Schichten ist die Untersuchung des Inputs aus verschiedenen Perspektiven. Jedes Neuron im Convolutional Layer überprüft einen bestimmten Bereich des Input Feldes mithilfe eines Filters, dem sog. Kernel. Ein Filter untersucht das Bild auf eine bestimmte Eigenschaft, wie z.B. Farbzusammensetzung oder Helligkeit. Das Ergebnis eines Filters ist der gewichtete Input eines Bereichs und wird im Convolutional Layer gespeichert.

Schematische Darstellung eines Convolutional Layers

Die Größe (oder vielmehr Tiefe) des Convolutional Layers definiert sich über die Anzahl der Filter, da jeweils das gesamte Input Bild von jedem Filter geprüft wird. Diese Information wird zwischen den einzelnen Convolutional Layern mit sogenannten Pooling Layern komprimiert. Die Pooling Layer laufen die, durch die Filter erstellten, Feature Maps ab und komprimieren diese, d.h. sie reduzieren die Anzahl der Pixel nach einer gegebenen Logik weiter. Beispielsweise können hierbei Maximalwerte oder Mittelwerte der Filter verwendet werden. Anschließend können noch weitere Convolutional Layer und/oder Pooling Schichten folgen, bis die abstrahierten Features in ein voll vernetztes MLP übergeben werden, das wiederum im Output Layer mündet und die Schätzungen des Modells berechnet. Convolutional und Pooling Layer komprimieren somit die räumlich angeordneten Informationen und reduzieren die Anzahl der geschätzten Gewichtungen im Netzwerk. Somit können auch hochdimensionale Bilder (hohe Auflösung) als Inputs verwendet werden.

Recurrent Neural Networks (RNNs)

Recurrent Neural Networks (RNNs) sind ein Oberbegriff für eine Gruppe von Netzwerkarchitekturen bei denen die Neuronen ihre Signale in einem geschlossenen Kreis weitergeben. Dies bedeutet, dass der Output einer Schicht auch an die gleiche Schicht als Input zurückgegeben wird. Es ist dem Netzwerk dadurch möglich Informationen bzw. Daten aus zeitlich weit auseinanderliegende Observationen im Training mit zu berücksichtigen. Dadurch eignet sich diese Architekturform primär für die Analyse sequentieller Daten, wie etwa Sprache, Text oder Zeitreihendaten.

Abbildung eines Recurrent Neural Networks

Eine der bekanntesten RNN Architekturen ist das Long Short Term Memory (LSTM) Netzwerk. Hierbei werden zusätzliche Parameter darauf trainiert, den Input und Output des Netzes für die nächste Iteration zu speichern oder zu verwerfen, um auf diese Weise zusätzliche Informationen zur Vorhersage für den nächsten Sequenzabschnitt zur Verfügung zu stellen. So können zuvor aufgetretene Signale über die zeitliche Dimension der Daten gespeichert und später verwendet werden. LSTMs werden aktuell sehr erfolgreich im NLP (Natural Language Processing) angewendet, um Übersetzungen von Texten anzufertigen oder Chat-Bots zu trainieren. Weiterhin eignen sich RNNs für die Modellierung von Sequenzen im Allgemeinen, bspw. bei der Zeitreihenprognose oder aber auch für Next Best Action Empfehlungen.

Weitere Netzarchitekturen

Neben den drei oben dargestellten Architekturen für Deep Learning Modelle existieren zahlreiche Varianten und Abwandlungen von CNNs und RNNs. Weiterhin erfahren aktuell sog. GANs (Generative Adversarial Networks) große Aufmerksamkeit in der Deep Learning Forschung. GANs werden verwendet, um Inputs des Modells zu synthetisieren, um somit neue Datenpunkte aus der gleichen Wahrscheinlichkeitsverteilung der Inputs zu generieren. So lassen sich zum Beispiel Datensätze aber auch Bild- und Toninformationen erzeugen, die dem gleichen „Stil“ der Inputs entsprechen. Somit können z.B. neue Texte, die in ihrer Komposition denen bekannter Schriftsteller entsprechen oder neue Kunstwerke, die dem Malstil großer Künstler nachempfunden sind erzeugt werden.

Schematische Darstellung eines GAN

Die Anwendungsbereiche von GANs sind extrem spannend und zukunftsträchtig, jedoch ist aktuell das Training solcher Netze noch experimentell und noch nicht ausreichend gut erforscht. Erste Modelle und Ergebnisse aus der aktuellen Forschung sind jedoch äußerst vielversprechend. Spannende Ergebnisse werden insbesondere im Bereich der künstlichen Bilderzeugung generiert. So werden heute bereits GANs trainiert, die es ermöglichen anhand eines Fotos das Aussehen im hohen Alter zu simulieren oder anhand einer Frontalaufnahme eine 360-Grad-Ansicht des Motivs zu generieren. Weitere Anwendung finden GANs im Bereich Text-t0-Image Synthese, d.h. anhand einer textualen Bildbeschreibung erzeugt das GAN eine fotorealistische Abbildung.

Zusammenfassung und Ausblick

Deep Learning und neuronale Netze sind spannende Machine Learning Methoden, die auf eine Vielzahl von Fragestellungen angewendet werden können. Durch Representation Learning, also der Fähigkeit abstrakte Konzepte aus Daten zu extrahieren und diese zur Lösung eines Problems zu verwenden, zeigen Deep Learning Modelle für viele komplexe Fragestellungen eine hohe Genauigkeit und Generalisierbarkeit auf neue Daten. Die Entwicklung von Deep Learning Modellen, Algorithmen und Architekturen schreitet extrem schnell voran, sodass davon ausgegangen werden kann, dass sich Deep Learning weiter als Benchmark in vielen Machine Learning Disziplinen festigen wird. Durch die immer weitere voranschreitende technische Entwicklung kann damit gerechnet werden, dass immer komplexere Architekturen modelliert werden können. Bereits heute gibt es spezielle Deep Learning Hardware, wie Google TPUs (Tensor Processing Units), die speziell auf die numerischen Anforderungen von Deep Learning Modellen abgestimmt sind.

Referenzen

  1. Rosenblatt, Frank (1958). The perceptron. A probabilistic model for information storage and organization in the brain. Psychological Reviews, 65: S. 386–408.
  2. Goodfellow, Ian; Bengio Yoshua; Courville, Cohan (2016). Deep Learning. MIT Press.
  3. Haykin, Simon (2008). Neural Networks and Machine Learning. Pearson.
  4. Bishop, Christopher M (1996). Neural Networks for Pattern Recognition. Oxford Press.

[author class=“mtl“ title=“Über den Autor“] Sebastian Heinz Sebastian Heinz

Die Annahme multivariat normalverteilter Items bei der Durchführung einer exploratorischen Faktorenanalyse (EFA) verhindert, streng genommen, die Verwendung von binär skalierten Items (0/1-Codierung). Zwar entspricht der Pearson-Korrelationskoeffizient zwischen zwei binären Items dem Phi-Koeffizienten, der Stärke und Richtung des Zusammenhangs zwischen zwei binären Items misst, jedoch ist aufgrund des limitierten Wertebereichs eines binären Items die geforderte Verteilungsannahme einer multivariaten Normalverteilung klar verletzt.

Verwendung von binären Items bei Faktorenanalysen

Um binäre Items in einer EFA verwenden zu können muss eine sog. polychorische (bzw. im Falle zwei binärer Items die daraus resultierende tetrachorische) Korrelation zwischen jedem Itempaar berechnet werden und die resultierende Korrelationsmatrix in die EFA-Prozedur überführt werden. Polychorische Korrelationen sind spezielle Korrelationskoeffizienten für ordinale Daten, die davon ausgehen, dass hinter einem Item eine nicht beobachtete (latente) Variable liegt, deren Wertebereich in die Intervalle der ordinalen Variable „aufgeteilt“ wurde. Das Verfahren versucht nun, die Korrelation zwischen den latenten Variablen zu schätzen und nicht anhand deren ordinalen Ausprägungen.

Durchführung der Faktorenanalyse mit binären Items

Da bis dato in SPSS keine polychorische/tetrachorische Korrelation implementiert ist, muss die Schätzung der entsprechenden Korrelationsmatrix in einer anderen Software erfolgen. In einem vor Kurzem durchgeführten Projekt haben wir die Schätzung der tetrachorischen Korrelation in der Open Source Software R implementiert und die resultierende Korrelationsmatrix in SPSS zur Durchführung der EFA weiterverwendet. Im Folgenden gehen wir kurz, anhand eines exemplarischen Beispiels, auf die wichtigsten Schritte im Rahmen dieses Vorgehens ein.

Schritt 1: Vorbereitungen zur Analyse

Das R-Paket „foreign“ ermöglicht es, neben anderen Dateiformaten, SPSS-Daten (.sav) zu importieren. Zur Installation wird der Befehlt install.packages(“foreign”) verwendet. Des Weiteren wird zu Schätzung der polychorischen Korrelation das R-Paket „polycor“ benötigt.

Schritt 2: Datenimport von SPSS nach R

Nach dem Laden beider Pakete kann mittels der R Funktion read.spss() der SPSS-Datensatz nach R importiert werden. Möglicherweise erscheint ein Warnhinweis bzgl. falscher Zeichenkodierungen o.ä. Dies kann in der Regel ignoriert werden.

Schritt 3: Berechnung der tetrachorischen Korrelation

Zum Abspeichern der berechneten Korrelationen wird in R ein leeres Matrix-Objekt angelegt, in dem anschließend, mittels einer doppelten Schleife, für jedes Itempaar die polychorische Korrelation berechnet wird. Hierfür wird der Befehl polychor() verwendet. Darauf hin muss noch die Diagonale der Korrelationsmatrix mit dem Wert 1 befüllt werden.

Schritt 4: Export der Korrelationsmatrix

Zur weiteren Verwendung wird die Korrelationsmatrix aus R als CSV-Datei exportiert. Dies geschieht mittels write.table(). Hierbei ist darauf zu achten, dass der richtige Dezimaltrenner exportiert wird (SPSS verwendet i.d.R. ein Komma, R verwendet einen Punkt). Im Folgenden finden Sie einen exemplarischen R-Code.


# Benötigte Pakete installieren
install.packages("foreign")
install.packages("polycor")
 
# Pakete laden
library(foreign)
library(polycor)
 
# SPSS Daten importieren (Dateipfad anpassen)
data <- read.spss(file="~/Desktop/Binary-Items.sav", 
                  use.value.labels=FALSE, to.data.frame=TRUE)
 
# Anzahl Zeilen/Spalten Korrelationsmatrix
n <- ncol(data); m <- n
 
# Leere Korrelationsmatrix anlegen
cormat <- matrix(nrow=n, ncol=m, data=NA)
 
# Polychorische Korrelationen für jedes Itempaar berechnen
for (i in 1:n){
  for(j in 1:m){
    cormat[i,j] <- polychor(data[,i], data[,j])
  }
}
 
# Diagonalelemente auf 1 setzen
diag(cormat) <- 1
 
# Exportieren
write.table(cormat, "~/STATWORX/PolyCorMat.csv", row.names=F, sep=";", dec=",")

Schritt 5: Anfertigung MATRIX-Daten in SPSS

Zur Verwendung der Korrelationsmatrix im Rahmen der FACTOR-Prozedur in SPSS muss manuell eine bestimmte Aufbereitung der Korrelationsmatrix stattfinden damit die Berechnung durchgeführt werden kann (siehe Screenshot unten). Die ersten beiden Spalten enthalten die Attribute ROWTYPE_ und VARNAME_, die vom Benutzer manuell angelegt werden müssen. Die ersten drei Zeilen müssen Mittelwert (MEAN), Standardabweichung (STDDEV) und Anzahl der Beobachtungen (N) der jeweiligen Variablen enthalten. Diese Werte können z.B. anhand der Ausgangsdaten in SPSS erzeugt werden. Die berechneten Korrelationen können z.B. nach Import in Excel mittels Copy und Paste übernommen werden oder direkt in SPSS geladen werden.

Schritt 6: Durchführung der Faktorenanaylse in SPSS

Nachdem die Korrelationsmatrix entsprechend aufbereitet wurde kann nun die eigentliche Faktorenanalyse durchgeführt werden. Diese MUSS mit SPSS-Syntax erfolgen, da nur hier eine benutzerdefinierte Korrelationsmatrix spezifiziert werden kann. Hierbei ist darauf zu achten, dass die Korrelationsmatrix der aktive Datensatz ist. Die Syntax lautet:


FACTOR MATRIX IN (COR=*)
/MISSING LISTWISE
/PRINT UNIVARIATE INITIAL KMO EXTRACTION ROTATION
/FORMAT BLANK(.30)
/PLOT EIGEN
/CRITERIA MINEIGEN(1) ITERATE(25)
/EXTRACTION PAF
/CRITERIA ITERATE(25)
/ROTATION PROMAX(4)
/METHOD=CORRELATION.

Mit MATRIX IN (COR=*) weist man SPSS an, den zuvor angefertigten Korrelationsdatensatz als Basis der EFA zu verwenden. Im obigen Beispiel wurde eine Hauptachsen-Analyse mit Promax-Rotierung durchgeführt. Selbstverständlich können auch andere Extraktions- bzw. Rotationsverfahren angewendet werden. Nach dem Ausführen der Syntax werden die gewohnten Resultate im Ausgabefenster angezeigt.

Zusammenfassung

Bis dato gibt es in SPSS noch nicht die Möglichkeit binäre Items adäquat in Faktorenanalysen zu verwenden. Dies wird sich wahrscheinlich auch in Zukunft nicht ändern (obwohl SPSS im Rahmen der Restrukturierung des Lizenzmodells nun wieder verstärkt Funktionen implementiert). Durch die Open-Source Software R können aber dennoch in SPSS entsprechende Analysen durchgeführt werden. Natürlich können auch nach der Berechnung der polychorischen Korrelationen in R, die Analyse weiter in R durchgeführt werden. Falls du noch Fragen zur Durchführung einer exploratorischen Faktorenanalyse mit binären Items haben solltest oder ein konkretes Projekt besprechen willst, stehen dir unsere Statistik Experten unter info@statworx.com gerne zur Verfügung. Sebastian Heinz Sebastian Heinz

Das Erzeugen von korrelierten Zufallsvariablen ist einer der Kernpunkte bei der Programmierung von Simulationen. Aktienrenditen, Zeitreihen, MCMC-Algorithmen und viele weitere Verfahren verwenden können durch das Erstellen korrelierter Zufallszahlen verwendet werden.

Zerlegen der Korrelationsmatrix

Korrelierte Zufallszahlen können durch Multiplikation der n x p Matrix der Zufallswerte mit der gewünschten Cholesky-zerlegten Korrelationsmatrix C der Zufallswerte erzeugt werden. Eine Cholesky-Zerlegung lässt sich in R einfach mit dem Befehl chol() durchführen. Dieses Vorgehen findet z. B. Anwendung bei der Simulation von korrelierten Zeitreihenverläufen im Finance-Bereich (z.B. Aktienkursentwicklungen). In unserem Beispiel simulieren wir so die Renditen zweier Aktien und visualisieren diese in einem X-Y Streudiagramm nebst entsprechendem, linearen Fit. Hierzu bedienen wir uns eines Features in RStudio, mit dessen Hilfe man einfach, interaktive Plots erstellen kann. Hierzu wird das R Package manipulate benötigt, das zusammen mit RStudio installiert wird.

Interaktive Plots in RStudio mit Manipulate

manipulate() erzeugt Slider oder Checkboxen, die es dem Benutzer ermöglichen Parameter des Plots variieren zu lassen. In unserem Beispiel können folgende Parameter vom Benutzer bestimmt werden:

  • Korrelation der Zufallswerte r
  • Anzahl der Zufallsrenditen n
  • Mittelwert der Renditen m1 und m2
  • Standardabweichung der Renditen s1 und s2

Zu Beginn des Codes wird der „Seed“ von R, der Zufallszahlengenerator, fixiert, sodass bei wiederholter Ausführung des Codes die gleichen Zufallszahlen erzeugt werden. Dies geschieht mit der Funktion set.seed(). Nach Ausführung des Codes erscheint oben links im Plot-Fensten von RStudio ein kleines Zahnrad, das durch Klicken die interaktiven Parameter anzeigt, die Sie nun verändern können. Der Plot passt sich nach Einstellung des jeweiligen Parameters automatisch an. Der Code kann per Copy und Paste direkt nach RStudio übernommen werden. Viel Spaß beim Exerimentieren!


# Libraries laden
library(manipulate)
library(ggplot2)
 
# Seed
set.seed(123)
 
# Interaktive Grafik zur Darstellung von korrelierten Aktienrenditen
manipulate(
  # Plot
  qplot(x=X1, y=X2, xlab="Rendite Aktie X", ylab="Rendite Aktie Y",
        data=data.frame(matrix(nrow=n, ncol=2,
                        data=cbind(rnorm(n, m1, s1), rnorm(n, m1, s1))) %*%
                        chol(matrix(nrow=2, ncol=2, data=c(1, r, r, 1))))) +
        theme_bw() + 
        stat_smooth(method="lm"),
  # Slider
  r = slider(-0.99, 0.99),
  n = slider(100, 1000),
  m1 = slider(0, 0.1),
  s1 = slider(0.01, 0.05),
  m2 = slider(0, 0.1),
  s2 = slider(0.01, 0.05)
) # Ende manipulate

Zusammenfassung

Mit manipulate() lassen sich schnell interaktive Visualisierungen erstellen. Für größere Projekte im Bereich interaktive Visualisierung eignet sich Shiny, das HTML5/CSS/JavaScript Framework von RStudio. Hiermit können Sie komplexe Applikationen entwickeln, die im Intra- oder Internet veröffentlicht werden können. Falls Sie Fragen zum Thema interaktive Visualisierung haben sollten, stehen Ihnen unsere Experten unter info@statworx.com gerne zur Verfügung. Sebastian Heinz Sebastian Heinz

Das Erzeugen von korrelierten Zufallsvariablen ist einer der Kernpunkte bei der Programmierung von Simulationen. Aktienrenditen, Zeitreihen, MCMC-Algorithmen und viele weitere Verfahren verwenden können durch das Erstellen korrelierter Zufallszahlen verwendet werden.

Zerlegen der Korrelationsmatrix

Korrelierte Zufallszahlen können durch Multiplikation der n x p Matrix der Zufallswerte mit der gewünschten Cholesky-zerlegten Korrelationsmatrix C der Zufallswerte erzeugt werden. Eine Cholesky-Zerlegung lässt sich in R einfach mit dem Befehl chol() durchführen. Dieses Vorgehen findet z. B. Anwendung bei der Simulation von korrelierten Zeitreihenverläufen im Finance-Bereich (z.B. Aktienkursentwicklungen). In unserem Beispiel simulieren wir so die Renditen zweier Aktien und visualisieren diese in einem X-Y Streudiagramm nebst entsprechendem, linearen Fit. Hierzu bedienen wir uns eines Features in RStudio, mit dessen Hilfe man einfach, interaktive Plots erstellen kann. Hierzu wird das R Package manipulate benötigt, das zusammen mit RStudio installiert wird.

Interaktive Plots in RStudio mit Manipulate

manipulate() erzeugt Slider oder Checkboxen, die es dem Benutzer ermöglichen Parameter des Plots variieren zu lassen. In unserem Beispiel können folgende Parameter vom Benutzer bestimmt werden:

Zu Beginn des Codes wird der „Seed“ von R, der Zufallszahlengenerator, fixiert, sodass bei wiederholter Ausführung des Codes die gleichen Zufallszahlen erzeugt werden. Dies geschieht mit der Funktion set.seed(). Nach Ausführung des Codes erscheint oben links im Plot-Fensten von RStudio ein kleines Zahnrad, das durch Klicken die interaktiven Parameter anzeigt, die Sie nun verändern können. Der Plot passt sich nach Einstellung des jeweiligen Parameters automatisch an. Der Code kann per Copy und Paste direkt nach RStudio übernommen werden. Viel Spaß beim Exerimentieren!


# Libraries laden
library(manipulate)
library(ggplot2)
 
# Seed
set.seed(123)
 
# Interaktive Grafik zur Darstellung von korrelierten Aktienrenditen
manipulate(
  # Plot
  qplot(x=X1, y=X2, xlab="Rendite Aktie X", ylab="Rendite Aktie Y",
        data=data.frame(matrix(nrow=n, ncol=2,
                        data=cbind(rnorm(n, m1, s1), rnorm(n, m1, s1))) %*%
                        chol(matrix(nrow=2, ncol=2, data=c(1, r, r, 1))))) +
        theme_bw() + 
        stat_smooth(method="lm"),
  # Slider
  r = slider(-0.99, 0.99),
  n = slider(100, 1000),
  m1 = slider(0, 0.1),
  s1 = slider(0.01, 0.05),
  m2 = slider(0, 0.1),
  s2 = slider(0.01, 0.05)
) # Ende manipulate

Zusammenfassung

Mit manipulate() lassen sich schnell interaktive Visualisierungen erstellen. Für größere Projekte im Bereich interaktive Visualisierung eignet sich Shiny, das HTML5/CSS/JavaScript Framework von RStudio. Hiermit können Sie komplexe Applikationen entwickeln, die im Intra- oder Internet veröffentlicht werden können. Falls Sie Fragen zum Thema interaktive Visualisierung haben sollten, stehen Ihnen unsere Experten unter info@statworx.com gerne zur Verfügung. Sebastian Heinz Sebastian Heinz