Entwickler Magazin   Spezial Vol. 17 - Machine Learning

Preis: 9,80 €

Erhältlich ab:  September 2018

Autoren / Autorinnen: 
Rainer Stropek ,  
Ann-Cathrin Klose ,  
Oliver Zeigermann ,  
Christoph Henkelmann ,  
,  
Björn SchullerMaximilian SchmittShahin Amiriparian ,  
Tam Hanna ,  
Harald Bosch ,  
Constantin Gonzalez ,  
Markus Ehrenmüller-Jensen ,  
Paul Dubs ,  
Andreas Bühlmeier

Liebe Leserinnen, liebe Leser,

die Welt des maschinellen Lernens entwickelt sich rasant. Wann hatten Sie zuletzt ein Handy in der Hand, das keinen smarten digitalen Assistenten enthielt, keine App mit intelligenten Funktionen? Auch die Anforderungen an immer mehr Softwareprojekte umfassen heute bereits derartige Aspekte: Ein Chatbot soll her, oder eine Anwendung, die selbstständig Reports erstellt oder Bilder klassifiziert. Das alles ist heute möglich, teilweise sogar schon mit Lösungen von der Stange. Werden die Anforderungen komplexer, muss aber eine eigene Implementierung her. Maschinelles Lernen entwickelt sich zur nächsten großen Welle der Disruption der Geschäftswelt.

Auch die Schlagzeilen aus der KI-Forschung tragen natürlich dazu bei, das öffentliche Bild selbstlernender Systeme zu formen: Go galt als Königsdisziplin für künstliche Intelligenzen und als ein Spiel, das der Mensch definitiv besser beherrscht als ein Computer. Google hat bereits 2016 das Gegenteil bewiesen und Lee Sedol, einen der besten Go-Spieler der Welt, mit der KI AlphaGo besiegt (https://bit.ly/2LxptMQ). Anfang 2018 erreichte eine KI von Microsoft schließlich sogar ein menschliches Leistungsniveau in einem Test zum Textverständnis (https://bit.ly/2mzQk0C). Das ist doch eine spannende Ausgangslage für eigene Gehversuche auf diesem Gebiet, oder?

Bedenken sollte man beim Einstieg in die Welt der Künstlichen Intelligenz aber auch die Fehlschläge der Branche, aus denen es zu lernen gilt. So muss an dieser Stelle auch an Tay erinnert werden, Microsofts Chatbot mit der Persönlichkeit einer jungen Frau, der auf Twitter lernte, rassistische und sexistische Antworten zu geben (https://bit.ly/2Pe1FzR). Dieser Fall ist eine Mahnung an alle, die die Zukunft selbstlernender Systeme mitgestalten wollen: Künstliche Intelligenzen sind nur so gut wie die Daten, die wir ihnen zur Verfügung stellen, und so gut wie die Algorithmen, die den Systemen zugrunde liegen. Umfassen diese Stereotypen und Vorurteile, transportiert das lernende System sie weiter. Das gilt es zu verhindern, je weiter wir uns den wirklich intelligenten Systemen annähern.

Die gibt es aber natürlich noch nicht. Geht es um das Vorstellungsvermögen, um Kreativität und viele weitere Bereiche, ist der Mensch der Maschine immer noch überlegen. Eine Artificial General Intelligence, die dem Menschen auf allen Gebieten ebenbürtig ist, ist noch lange nicht entwickelt. Und wann wir den Status einer künstlichen Superintelligenz oder einer technologischen Singularität erreichen, ist durchaus umstritten. Manche Schätzungen gehen jedoch davon aus, dass das noch in diesem Jahrhundert der Fall sein könnte. Wir werden sehen. Heute jedoch geht es erst einmal darum, wie man selbst spannende Implementierungen einfacher KIs schaffen kann. Ein eigener Chatbot, der Emotionen erkennt? Spiele mit klugen Funktionen? Das ist gar kein Problem – legen Sie einfach los!

Für Einsteiger in das Machine Learning und alle, die ihr Wissen vertiefen wollen, haben wir dieses Entwickler Magazin Spezial zusammengestellt. Angefangen mit der Frage nach den mathematischen Grundlagen des maschinellen Lernens und einer Einführung in Python über ein umfassendes Tutorial zu Deeplearning4j bis hin zu einer Vorstellung von TensorFlow.js und somit Machine Learning fürs Web: All das und vieles mehr finden Sie in diesem Magazin. Ich wünsche Ihnen viel Spaß bei der Lektüre!

klose_ann-cathrin_sw.tif_fmt1.jpgAnn-Cathrin Klose, Redakteurin Entwickler Magazin Spezial

Entwickler Magazin Website Entwickler Magazin

Wenn man über Machine Learning und Googles TensorFlow spricht, denken die meisten Leute eher an Python und spezialisierte Hardware als an JavaScript und einen beliebigen Browser. Was TensorFlow.js kann und warum es Sinn ergibt, Machine Learning im Browser zu betreiben, erklärt dieser Artikel.

TensorFlow.js [1] ist eine JavaScript-Bibliothek, die sowohl im Browser als auch mit Node.js auf dem Server läuft. Wir interessieren uns in diesem Artikel allerdings nur für die Anwendung im Browser. Die Schnittstelle von TensorFlow.js ist stark an TensorFlows High-Level API Keras [2] angelehnt. Keras-Code ist oft nur auf den zweiten Blick von TensorFlow.js-Code zu unterscheiden. Die meisten Unterschiede gehen auf die unterschiedlichen Sprachkonstrukte von Python und JavaScript für Konfigurationsparameter zurück.

Machine Learning mit jeder GPU

Mit TensorFlow.js lassen sich Machine-Learning-Projekte von null auf erstellen. Stehen die notwendigen Daten zur Verfügung, können Modelle direkt im Browser trainiert und ausgeführt werden. Dabei nutzt TensorFlow.js die Grafikkarte (GPU) des Rechners über das Browser-API WebGL. Man verliert dadurch zwar etwas Performance, weil WebGL nur durch ein paar Tricks dazu gebracht werden kann, die von TensorFlow.js gewünschten Matrixmultiplikationen auszuführen. Doch die sind notwendig, da TensorFlow.js als Strategie für Machine Learning hauptsächlich neuronale Netzwerke unterstützt. Diese sind sowohl beim Training, als auch bei der Vorhersage sehr gut durch Matrixmultiplikationen abbildbar. Hier sehen wir schon den ersten Vorteil von TensorFlow.js gegenüber TensorFlow: Während TensorFlow zurzeit nur NVIDIA-GPUs über CUDA unterstützt, funktioniert TensorFlow.js mit jeder Grafikkarte. Listing 1 enthält den Code, um mit dem High-Level API ein sequenzielles neuronales Netzwerk im Browser zu erstellen. Wer TensorFlows Keras API kennt, kommt hier sehr schnell klar. Tutorials finden sich unter [3].

Listing 1

// create a sequential model
const model = tf.sequential();
 
// add a fully connected layer with 10 units (neurons)
model.add(tf.layers.dense({units: 10}));
 
// add a convolutional layer to work on a monochrome 28x28 pixel image
// with 8 filter units
model.add(tf.layers.conv2d({
  inputShape: [28, 28, 1],
  filters: 8
}));
 
// compile the model like you would do in Keras
// the API speaks for itself
model.compile({
  optimizer: 'adam',
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

Interaktion mit allen Browser-APIs

Das Ansprechen von Schnittstellen auf unterschiedlichen Betriebssystemen und Geräten kann immer noch eine leidvolle Erfahrung sein. Nicht so, wenn man eine browserbasierte Anwendung entwickelt. Selbst der Zugriff auf so komplexe Hardware wie eine Kamera oder ein Mikrofon sind im HTML-Standard verankert und werden von allen aktuellen Browsern unterstützt. Auch die ohnehin von Natur aus auf Interaktion ausgelegte Natur des Browsers kommt einem hier zupass. Interaktive Anwendungen mit einem Anteil an Machine Learning sind damit so leicht wie noch nie.

Als Beispiel haben wir ein einfaches Spiel, Scavenger Hunt [4], das natürlich auch im Browser eines Mobiltelefons läuft und damit auch am meisten Spaß bringt. Wie in Abbildung 1 gezeigt, musst man in der echten Welt innerhalb kurzer Zeit ein Objekt finden, das zum angezeigten Emoticon passt. Dazu wird die eingebaute Kamera und ein entsprechend trainiertes neuronales Netzwerk verwendet, das die passenden Objekte erkennen kann. Ein solches Modell kann von jedem JavaScript-Entwickler auch ohne Machine-Learning-Kenntnisse genutzt werden.

zeigermann_tensorflow_js_1.tif_fmt1.jpgAbb. 1: Mach ein Foto des Emoticons im echten Leben

Machine Learning ohne Installation auf jedem Rechner

TensorFlow.js ermöglicht das Deployment eines vorab mit TensorFlow erzeugten Modells. Dieses Modell kann bereits komplett oder auch teilweise auf starker Hardware trainiert worden sein. Im Browser kommt es dann nur noch zur Anwendung oder wird weiter trainiert. Abbildung 2 zeigt eine Pac-Man-Variante, die sich durch unterschiedliche Posen steuern lässt. Sie basiert auf einem vortrainierten Netzwerk [5], das im Browser auf die eigenen Posen nachtrainiert wird. Man spricht hier von Transfer-Learning.

zeigermann_tensorflow_js_2.tif_fmt1.jpgAbb. 2: Ein vortrainiertes MobilNet wird durch eigene Posen nachtrainiert

Das Modell wird dazu von einem mitgelieferten Programm konvertiert und kann nach dem Laden durch eine Zeile, ähnlich wie die folgende, asynchron geladen werden:

const model = await tf.loadModel('model.json');

Danach ist das Modell nicht mehr von einem direkt im Browser entstandenen Modell unterscheidbar. Es kann z. B. sehr einfach zur Vorhersage genutzt werden, die wiederum asynchron auf der GPU ausgeführt wird:

const example = tf.tensor([[150, 45, 10]]);
const prediction = model.predict(example);
const value = await prediction.data();

Neben der Unterhaltung durch Spiele sind hier noch nützlichere Anwendungen denkbar. Navigation oder Interaktion durch Gesten können sowohl für Menschen mit Behinderungen als auch für Menschen in besonderen Situationen hilfreich sein. Und wie gesagt: ohne jegliche Installation, durch einfaches Laden einer Webseite.

Ein weiteres Beispiel für eine solche Positionserkennung ist das PoseNet [6] in Abbildung 3. Es ist bereits so vortrainiert, dass es auch bei mehreren Personen im Bild die Position von Gesicht, Armen und Beinen erkennen kann. Auch hier reicht das Potenzial von Spielen bis zu Steuerungen seriöser Anwendungen auch aus einer gewissen Entfernung. Die Nutzung des PoseNet ist wieder trivial und erfordert nicht einmal Grundkenntnisse im Bereich Machine Learning. In Listing 2 ist kurz skizziert, wie einfach das geht.

zeigermann_tensorflow_js_3.tif_fmt1.jpgAbb. 3: Ein vortrainiertes Modell erkennt auch bei mehreren Personen die Position

Listing 2

import * as posenet from '@tensorflow-models/posenet';
import * as tf from '@tensorflow/tfjs';
 
// load the posenet model
const model = await posenet.load();
 
// get the poses from a video element linked to the camera
const poses = await model.estimateMultiplePoses(video);
 
// poses contain
// - confidence score
// - x, y positions

Userdaten müssen den Browser nicht verlassen

Gerade jetzt, wo Datenschutz durch die DSGVO [7] immer größere Bedeutung bekommt, überlegt sich so mancher zweimal, ob er ein bestimmtes Cookie auf seinem Rechner haben möchte oder ob man eine Statistik seiner Nutzerdaten für die Verbesserung der User Experience an den Hersteller einer Software senden möchte. Wie wäre nun aber die Umkehrung? Der Hersteller liefert ein generelles Modell über die Nutzung einer Software, und ähnlich wie beim oben beschriebenen Pac-Man-Spiel wird dieses mit Transfer-Learning-Modell an den einzelnen User angepasst. Hier ist bisher noch nicht viel passiert, das Potenzial ist aber groß. Warten wir die Entwicklungen ab.

Fazit

Machine Learning im Browser erscheint im ersten Moment für viele Entwickler nicht sonderlich sinnvoll. Wenn man jedoch etwas genauer hinsieht, ergeben sich Anwendungsmöglichkeiten, die sonst keine andere Plattform bieten kann:

  1. Ausbildung: Man kann direkt mit Machine-Learning-Konzepten interagieren und durch Experimentieren lernen

  2. Entwicklung: Wenn man ohnehin eine JS-Anwendung hat oder bauen will bzw. muss, kann man darin direkt Machine-Learning-Modelle nutzen oder mittrainieren

  3. Spiele: Real-Time Position Estimation nur über die Kamera (also wie die Personen vor der Kamera sich gerade bewegen) oder Image Recognition kann direkt mit Spielen gekoppelt werden. Es gibt sehr coole Beispiele dafür, was man alles damit machen kann. Die Möglichkeiten gehen aber deutlich über Spiele hinaus

  4. Deployment: Man hat bereits ein Machine-Learning-Modell und fragt sich, wie man es in Produktion bringt. Der Browser ist eine Lösung. Auch fertige Modelle können ohne tiefe Kenntnis von Machine Learning in die eigene Anwendung eingebunden werden

  5. Interaktive Visualisierungen: Für interaktives Clustering [8] oder auch künstlerische Projekte [9]

Wie in Abbildung 4 zu sehen, müssen wir, was die Performanz gegenüber TensorFlow angeht, bei gleicher Hardware allerdings noch einige Abstriche machen. Der Vergleich läuft auf einer 1080 GTX GPU und misst die Zeit für eine Vorhersage mit dem MobileNet, wie es auch für die Beispiele hier verwendet wird. TensorFlow läuft in diesem Fall drei- bis viermal schneller als TensorFlow. js; allerdings sind beide Werte sehr niedrig. Der WebGPU-Standard [11], der einen direkteren Zugang zur GPU erlauben wird, lässt auf bessere Performanz hoffen.

zeigermann_tensorflow_js_4.tif_fmt1.jpgAbb. 4: Performanzvergleich: klassisches TensorFlow und TensorFlow.js (Quelle: [10])

zeigermann_oliver_sw.tif_fmt1.jpgOliver Zeigermann hat in den 90er Jahren Informatik mit den Schwerpunkten KI und Compilerbau studiert. Als Berater in Hamburg knüpft er an diese Erfahrungen an und entwickelt zudem meist browserbasierte Software. Mehr Informationen unter zeigermann.eu.

Die automatische Spracherkennung und -analyse ist ein komplexes Themenfeld. Welche Prozesse liegen der maschinellen Emotionsanalyse zugrunde und womit kann diese Funktion selbst implementiert werden? Dieser Artikel gibt einen Überblick.

Technologien für die Erkennung und das Verstehen von menschlicher Sprache sind heute Bestandteil des Lebens und der Arbeit von Millionen von Menschen weltweit. Sie sind nicht nur die zentrale Komponente von Smart Devices wie Amazon Echo, sondern auch auf jedem Smartphone verfügbar. Aus Sicht des Anwenders scheint die Funktionalität dieser Systeme oft sehr simpel, sie beruhen jedoch auf technisch sehr komplexen Algorithmen und auf Forschungsergebnissen der letzten sechzig Jahre. Die automatische Spracherkennung und -analyse ist auch deshalb ein solch anspruchsvolles Problem, da für jede vorkommende Sprache und deren Dialekte eigene Modelle entwickelt bzw. trainiert werden müssen. Für ein vollwertiges System zur Mensch-Maschine-Interaktion ist allerdings auch die Erkennung weiterer Parameter notwendig, beispielsweise die Erkennung des emotionalen Zustands des Sprechers oder dessen Alter. Daneben kann auch die Diagnostik von Krankheiten aus der gesprochenen Sprache eine wichtige Anwendung der Sprachtechnologien sein, da sie nicht invasiv ist, den Anwender also nicht belastet und nicht der physischen Präsenz eines Arztes bedarf. Die meisten aktuellen Algorithmen beruhen dabei auf dem maschinellen Lernen, also der Modellbildung aus einer großen Anzahl von Beispieldaten.

Maschinelles Lernen steht prinzipiell für das Lernen aus Beispielen. Aktuelle Algorithmen zur automatischen Spracherkennung und zur Erkennung von paralinguistischen Parametern (wie Alter, Geschlecht, Gesundheits- oder emotionalem Zustand) beruhen zu einem großen Teil auf diesem Prinzip. Das erfordert die Verfügbarkeit einer großen Menge an Trainingsdaten, also Sprachaufnahmen mit den entsprechenden Annotationen. Im Fall der Spracherkennung bedeutet das, dass eine Transkription in geschriebenem Text vorliegen muss, im Fall der Emotionserkennung, dass die Angabe einer Emotion für jedes Zeitfenster einer Aufnahme verfügbar ist. Dadurch, dass sich die Eigenschaften, also u. a. der Klang der Stimme, die Sprechweise und der Ausdruck von Emotionen, stark von Sprecher zu Sprecher unterscheiden, sind Aufnahmen von einer Vielzahl von Sprechern notwendig, um sprecherunabhängige Systeme trainieren zu können. Sprecherunabhängige Systeme sind Spracherkenner, die auch von Personen verwendet werden können, von denen keine Aufnahmen in den Trainingsdaten vorhanden sind. Bei den meisten Tools zur Spracherkennung, die im Bürobetrieb, insbesondere für das Diktieren von Briefen eingesetzt werden, findet jedoch auch eine Feinjustierung statt, die das Modell an die Charakteristika des jeweiligen Nutzers adaptiert. Um moderne, sprecherunabhängige Systeme zu trainieren, sind in der Regel sehr große Datenmengen erforderlich: Der unter einer Creative-Commons-Lizenz veröffentlichte LibriSpeech-Corpus enthält insgesamt etwa 1 000 Stunden an englischen Sprachaufnahmen von mehr als 2 000 Sprechern. Datensätze, die zum Training kommerzieller Systeme verwendet werden, sind mitunter noch deutlich größer, allerdings halten die entsprechenden Unternehmen genauere Informationen darüber meist geheim. Im Jahr 2017 hat Mozilla das Projekt Common Voice gestartet, bei dem sowohl Sprachdaten, die Internetnutzer freiwillig zur Verfügung stellen, als auch damit trainierte Spracherkennungmodelle verfügbar gemacht werden.

Automatische Spracherkennung

Bis vor wenigen Jahren wurde automatische Spracherkennung (Sprache zu Text) meist durch sogenannte Hidden-Markov-Modelle (HMMs) realisiert. Das war der erste Ansatz, der Spracherkennung für verschiedene Anwendungszwecke verfügbar gemacht hat – vorherige Ansätze waren sowohl aufgrund der fehlenden Methodik als auch der fehlenden Rechenleistung meist nur für die Erkennung isolierter Schlagworte geeignet. HMMs sind stochastische Modelle, mit denen sprachliche Einheiten (Phoneme oder Wörter) als Folge von Zuständen modelliert werden, die nicht direkt bestimmt, sondern nur anhand der akustischen Emissionen geschätzt werden können. Phoneme sind die kleinsten lautlichen Einheiten einer Sprache (bspw. der Vokal /a/), die jedoch von verschiedenen Sprechern unterschiedlich ausgesprochen werden können. Die Realisierungen eines Phonems werden als Phon bezeichnet. Ein Vokal kann, auch im gleichen Wort, unterschiedlich lang gehalten werden. Ein HMM hat die Fähigkeit des dynamischen Time Warping, d. h. es kann Sequenzen wiedererkennen, auch wenn die Verweildauer in einem Zustand (beispielsweise dem Phonem /a/) verschieden ist. Außerdem unterscheidet sich jeder Sprecher hinsichtlich der Grundfrequenz seiner Stimme (quasi der Tonhöhe, in der gesprochen wird) und der genauen Lage der Formanten im Spektrum seiner Sprache. Formanten sind bei Betrachtung der spektralen Darstellung eines bestimmten Phons diejenigen Frequenzbereiche, die durch die Filterung im Vokaltrakt des Menschen (Hals und Mund) verstärkt werden und den Klang des jeweiligen Lauts ausmachen, sich jedoch auch zwischen Sprechern unterscheiden. Aus diesem Grund werden als Vorverarbeitungsschritt zeitabhängige Merkmalsvektoren, sogenannte Mel Frequency Cepstral Coefficients (MFCCs) aus dem Sprachsignal extrahiert. Diese enthalten eine komprimierte Darstellung des Spektrums, in denen der Anteil der Grundfrequenz und der Anteil des Vokaltraktfilters getrennt wurden. Die Folge der MFCCs sind dann die beobachteten akustischen Emissionen eines HMMs, wobei dessen Parameter aus den vorhandenen transkribierten Sprachdaten trainiert werden müssen.

Neben dem beschriebenen akustischen Modell besteht ein Spracherkennungssystem immer auch aus einem Sprachmodell (Language Model). Mithilfe des akustischen Modells werden für die zu erkennenden sprachlichen Einheiten die wahrscheinlichsten Wortfolgen ermittelt. Denn verschiedene Wörter klingen bei Aussprache im Kontext eines Satzes oft sehr ähnlich oder gar identisch. Als Beispiel kann der Satz „Er malte ein Bild von einem Dorf.“ dienen. Wenn die Wörter einzeln erkannt würden, könnte das akustische Modell die Wortsequenz „Eher mahlte ein Bild von einem Torf.“ ausgeben. Deshalb werden die Wahrscheinlichkeiten des akustischen Modells schließlich noch mit denen des Sprachmodells kombiniert, das verschiedenen Folgen von Wörtern Wahrscheinlichkeiten zuordnet. Das Sprachmodell muss ebenfalls unter Zuhilfenahme von großen Datenbanken trainiert werden, allerdings müssen hierfür keine zugehörigen Audiodaten vorhanden sein.

Heutige Systeme zur automatischen Spracherkennung verwenden normalerweise ausschließlich tiefe neuronale Netze (Deep Neural Networks). Für die Problemstellung der automatischen Spracherkennung, bei der eine Audiosequenz (Sprachsignal) in eine Sequenz von Wörtern übersetzt wird, eignen sich besonders rekurrente neuronale Netze (RNNs), genauer Long Short-Term Memory-RNNs (LSTM-RNNs). Diese sind dazu in der Lage, Informationen einer Sequenz über eine bestimmte Zeitdauer zu speichern und als Kontext für die Erkennung von Wörtern bzw. Sätzen zu verwenden. Als Eingabe können auch hier die oben beschriebenen MFCC-Sequenzen genutzt werden. Allerdings wird auch der Vorverarbeitungsschritt der Merkmalsextraktion zunehmend von neuronalen Netzen übernommen. Hierfür werden normalerweise sogenannte Convolutional Neural Networks (CNNs) benutzt, die als eine Menge von Filtern betrachtet werden können, mit denen die Eingangssequenz (das Audiosignal) verarbeitet wird, wobei jeder Filter für verschiedene Frequenzbereiche zuständig ist. Die Gewichte der einzelnen Filter werden jedoch – anders als in der klassischen Signalverarbeitung – nicht berechnet, sondern ebenfalls mit den gegebenen Daten trainiert. Das gesamte System der Spracherkennung inklusive Sprachmodellen wird also durch ein tiefes neuronales Netz dargestellt. Alle Teilmodelle werden damit zusammen trainiert, wobei die Grenzen zwischen ihnen zunehmend verschwimmen. In diesem Zusammenhang wird auch von End-to-End Learning gesprochen.

Es ist zu erkennen, dass maschinelles Lernen eine zentrale Komponente der heutigen Sprachtechnologien darstellt. Da gerade die automatische Spracherkennung allerdings eine sehr komplexe Problemstellung mit vielen Teilaspekten und hohem Datenbedarf ist, die eine Modellierung auf verschiedenen Ebenen erfordert, werden im Folgenden die Möglichkeiten des maschinellen Lernens an einem leichter zu demonstrierenden Beispiel erläutert: der Emotionserkennung aus der menschlichen Sprache.

Emotionserkennung

Automatische Emotionserkennung ist ein wesentlicher Forschungsgegenstand im Bereich des Affective Computing und unter anderem relevant für die Entwicklung von virtuellen Agenten, für die Robotik und die Marktforschung. Ein virtueller Agent sollte bspw. neben dem gesprochenen Wort auch den emotionalen Zustand seines menschlichen Gegenübers verstehen, um in bestimmten Situationen (Freude oder Langeweile) angemessen reagieren zu können. Automatische Emotionserkennung aus dem Sprachsignal ist längst keine Neuigkeit mehr. Während sich inzwischen auch hier End-to-End-Modelle durchsetzen, besteht die klassische Verarbeitungskette wie auch bei den Sprache-zu-Text-Systemen aus einem Schritt der Merkmalsextraktion und dem Decoding mittels eines auf maschinellem Lernen basierenden Modells.

Akustische Merkmale

Während für die meisten Sprache-zu-Text-Modelle ausschließlich die zuvor beschriebenen MFCC-Merkmale verwendet werden, kommen für die Emotionserkennung weitere akustische Merkmale in Betracht. Wie oben erwähnt, ist die Grundfrequenz der Stimme, oft als Fundamentalfrequenz (F0) bezeichnet, in den MFCCs nicht in einer präzisen Darstellung enthalten, da der Frequenzbereich stark komprimiert wird. Die Grundfrequenz, bzw. deren zeitlicher Verlauf, ist jedoch ein wichtiges Merkmal für die emotionale Färbung der Stimme und wird deshalb für die Emotionserkennung zumeist berücksichtigt. Die Schätzung von F0 ist eine komplizierte Problemstellung, da die Schwingung der Stimmbänder kein stationärer Prozess ist und eine Vielzahl an Algorithmen dafür existiert. Neben der Grundfrequenz zählen die zeitabhängige Energie des Signals und der Rhythmus der Sprache zu den prosodischen Merkmalen, die nicht von einem bestimmten Phonem abhängen. Während die Energie eines Signals relativ einfach zu berechnen ist, gibt es keine einheitliche Vorgehensweise für die Bestimmung rhythmischer Merkmale. Eine Möglichkeit besteht darin, die harmonischen Abschnitte (Zeitintervalle, in denen eine eindeutige Grundfrequenz detektiert wird) im Signal zu ermitteln und deren Länge bzw. die Pausen zwischen den Abschnitten als Merkmal zu verwenden. Relevant für die Emotionserkennung sind außerdem die sogenannten mikroprosodischen Merkmale Jitter und Shimmer, die auch für die Diagnostik von Kehlkopferkrankungen von Bedeutung sind. Sie beschreiben kurzzeitige Schwankungen der Grundfrequenz bezüglich der Periodendauer oder der Amplitude des Signals bei einer bestimmten Phase der Schwingung. Schließlich werden auch oft die Frequenzen der ersten drei Formanten und deren Bandbreiten bzw. Amplituden berücksichtigt. Alle beschriebenen Merkmale werden in kleinen überlappenden Zeitfenstern (normalerweise 20–60 ms Länge) aus dem Audiosignal extrahiert. In diesem Intervall wird das Sprachsignal als quasistationär angenommen, die Eigenschaften ändern sich also nicht signifikant.

Merkmalsvektoren

Für die Klassifikation einer kurzen Sprachaufnahme hinsichtlich der Emotion oder einer anderen paralinguistischen Information muss die Information aus den zeitabhängigen Merkmalen in einen Vektor fester Länge transformiert werden. Das geschieht häufig mittels stochastischer Maße, die auf jedes Merkmal (jeden MFCC-Koeffizienten, die Grundfrequenz usw.) angewendet werden. In vielen Fällen werden durch Verwendung von Mittelwert und Varianz gute Ergebnisse erreicht, gebräuchlich sind jedoch auch Momente höherer Ordnung, Perzentile, die Steigung der Regressionsgeraden und weitere stochastische Maße. Somit wird schließlich unabhängig von der Länge des betrachteten Audiosegments ein Merkmalsvektor fester Länge erhalten. Die enthaltene Information ist auch invariant gegenüber zeitlichen Verschiebungen des Signals.

Alternativ dazu ist der sogenannte Bag-of-Audio-Words-Ansatz (BoAW) gebräuchlich, um die zeitabhängigen Merkmale in Vektoren fester Länge zu überführen. Hierbei werden Histogramme der Audiowörter gebildet, die im Audiosegment vorkommen. Da die zeitabhängigen Merkmalsvektoren keine diskreten, sondern kontinuierliche Werte annehmen, muss zunächst ein Wörterbuch definiert werden. Üblicherweise wird das mittels eines Clustering-Ansatzes (bspw. K-Means) auf einer Menge von Trainingsdaten bewerkstelligt, für die die entsprechenden Merkmale extrahiert wurden. Nun wird für alle zeitabhängigen Audiomerkmalsvektoren im zu klassifizierenden Audiosegment das (basierend auf der euklidischen Distanz) nächste Audiowort im Wörterbuch gesucht und der Zähler des entsprechenden Index im Histogramm inkrementiert. Schließlich werden die Werte des Histogramms normalerweise logarithmiert, um den Wertebereich zu komprimieren. Der resultierende Merkmalsvektor wird durch den Quantisierungsschritt als relativ robust gegenüber Störungen des Audiosignals betrachtet. Die Größe des Wörterbuchs ist dabei jedoch ein entscheidender Parameter. Je kleiner das Wörterbuch, desto robuster ist der Ansatz tendenziell gegenüber Störungen, allerdings ist die Genauigkeit dann meist geringer als bei einem umfangreichen Wörterbuch.

Der BoAW-Ansatz ist analog im Bereich der Bilderkennung gebräuchlich (Bag-of-Visual-Words). Die Idee stammt ursprünglich aus dem Bereich des Natural Language Processing, insbesondere für die Anwendung in der Textklassifikation. Hierbei wird ein Text (einer oder mehrere Sätze) anhand eines vordefinierten Wörterbuchs in ein Worthistogramm überführt. Die Reihenfolge der Wörter spielt erstmal keine Rolle, allerdings existiert hierfür mit N-Grammen eine Erweiterung des Ansatzes, bei denen das Wörterbuch nicht (nur) aus isolierten Wörtern, sondern (auch) aus Sequenzen von N-Tupeln, also häufig vorkommenden Kombinationen mehrerer Wörter besteht.

Der BoAW-Ansatz und der beschriebene Bag-of-Words-Ansatz zur Textklassifikation können für die Emotionsanalyse oder auch diagnostische Anwendungen sehr gut miteinander kombiniert werden, wie in Abbildung 1 gezeigt wird. Zunächst ist hierfür die Transkription der gesprochenen Sprache mittels eines zuvor beschriebenen Systems zur automatischen Spracherkennung notwendig. Die Histogramme aus der Audio- bzw. Textdomäne können dann einfach verkettet werden. Die Erkennung der Emotion geschieht dann mittels eines Modells, das mit einem maschinellen Lernalgorithmus, wie im folgenden Abschnitt beschrieben, trainiert wurde. Optional kann das Audiosignal vor der Merkmalsextraktion aufbereitet werden, um bspw. Rauschen oder Nachhall zu entfernen oder das Signal zu entzerren.

schmitt_amiriparian_schuller_spracherkennung_1.tif_fmt1.jpgAbb. 1: Beispiel einer Verarbeitungskette mit dem Bag-of-Words-Ansatz zur Emotionserkennung aus einer Sprachaufnahme

Maschinelles Lernen

Mithilfe eines maschinellen Lernverfahrens, genauer: eines überwachten Lernverfahrens, wird ein Modell trainiert, das eine möglichst genaue Klassifizierung einer Dateninstanz mit unbekannter Klasse vornimmt. Beim überwachten Lernen müssen, im Gegensatz zum halb- oder unüberwachten Lernen, neben den Merkmalsvektoren für alle Dateninstanzen auch die jeweiligen Klassen bekannt sein. Zu den gebräuchlichsten Algorithmen zählen das k-Nearest-Neighbor-Verfahren (kNN), Random Forests, Support Vector Machines (SVMs) und (tiefe) neuronale Netze. Beim kNN-Verfahren wird eine gegebene Instanz derjenigen Klasse zugeordnet, der die k am nächsten Instanzen in der Menge der Trainingsdaten angehören. Als Abstandsmaß wird hier normalerweise die euklidische Distanz zwischen den Merkmalsvektoren betrachtet. Allerdings können bestimmte Merkmale ein spezifisches Gewicht erhalten. kNN ist ein sehr einfaches, aber rechenaufwendiges Verfahren; die Modelle haben oft eine große Genauigkeit. Der Parameter k für die Anzahl der Nachbarn ist hier ein Hyperparameter, der bei der Modellentwicklung optimiert werden muss. SVMs hingegen sind in den 90er-Jahren sehr populär geworden und auch heute noch in manchen Bereichen des maschinellen Lernens Stand der Technik. Es werden Hyperebenen zwischen den Instanzen verschiedener Klassen gefunden, mit denen die Klassen möglichst gut voneinander getrennt werden.

Ein allgemeines Problem des maschinellen Lernens ist die Überanpassung (Overfitting) des Modells an die verwendeten Trainingsdaten. Die Lernalgorithmen neigen dazu, die Parameter zu stark an die Eigenschaften der gegebenen Daten anzupassen, sodass die Modelle nicht optimal generalisieren. Deshalb muss vor der Entwicklung stets eine Partitionierung der Daten in Trainings- und Testdaten vorgenommen werden. Das heißt, dass ein kleinerer Prozentsatz der vorliegenden Daten nicht zum Training verwendet wird, sondern nur zur Evaluierung des fertigen Modells. Bei den meisten Lernalgorithmen gibt es außerdem Hyperparameter (bspw. die Lernrate bei neuronalen Netzen), die optimiert werden müssen. Da auch bei den Hyperparametern die Gefahr der Überanpassung besteht, werden normalerweise drei Partitionen verwendet: Training, Validierung und Test. Das Modell, das auf den Validierungsdaten die besten Ergebnisse erzielt, wird dann schließlich auf den Testdaten evaluiert; nur dieses Ergebnis kann als zuverlässige Schätzung für die Genauigkeit des Modells auf unbekannten Daten dienen. Bei personenbezogenen Daten – wie Sprache – ist darüber hinaus darauf zu achten, dass die Partitionierung so vorgenommen wird, dass jeder Sprecher in nur einer Partition vorkommt. Andernfalls adaptiert sich der Algorithmus an die Eigenschaften eines bestimmten Sprechers und die Ergebnisse scheinen viel besser zu sein, als sie bei unbekannten Sprechern zu erwarten sind. Prinzipiell ist Überanpassung bei allen maschinellen Lernverfahren zu finden, insbesondere jedoch, wenn die Anzahl der Modellparameter groß und die Anzahl der Trainingsdaten gering ist.

Bei der SVM kann Overfitting mit einem Komplexitätsparameter gesteuert werden. Ist dieser groß, werden bei der Optimierung der Hyperebene nur sehr wenige Fehler (Ausreißer in den Daten) zugelassen. Wird der Komplexitätsparameter kleiner gewählt, werden hingegen zunehmend Ausreißer zugelassen, sodass die Hyperebene oft besser generalisiert. Bei einem zu kleinen Wert adaptiert die Hyperebene die Daten aber zu ungenau und es kommt zur Unteranpassung. Es muss also immer – mithilfe der Validierungsdaten – ein guter Kompromiss für die Komplexität gefunden werden. Insbesondere für die hochdimensionalen Merkmalsvektoren, die in der Audioverarbeitung üblich sind (>1 000 Merkmale pro Instanz), sind SVMs gut geeignet. Bei Merkmalsvektoren niedrigerer Dimension sind normalerweise sogenannte Kernels hilfreich, um eine implizite Transformation in einen höherdimensionalen Raum durchzuführen, in dem eine trennende Hyperebene einfacher gefunden werden kann.

Neuronale Netze und Deep Learning

In den letzten zehn Jahren ist das Gebiet des maschinellen Lernens von den neuronalen Netzen revolutioniert worden. Während die Grundlagen künstlicher neuronaler Netze bereits in den 40er Jahren des 20. Jahrhunderts mit der McCulloch-Pitts-Zelle gelegt worden sind und es im weiteren Verlauf des Jahrhunderts zu mehreren Wiederbelebungen des Forschungsgebiets kam, ist die Anzahl an Veröffentlichungen, Tools und Anwendungen basierend darauf erst vor weniger als zehn Jahren sprunghaft angestiegen. Das hat mehrere Gründe. Zum einen hat es vor allem in den 90er-Jahren, aber teilweise auch schon früher, mehrere wegweisende Forschungsarbeiten gegeben, die neuartige Architekturen von neuronalen Netzen ermöglichen. Dazu gehören insbesondere die oben beschriebenen CNNs und LSTM-RNNs, aber auch neuere Entwicklungen wie Generative Adversarial Networks (GANs), die u. a. interessante Möglichkeiten zur Datensynthese bieten. Zum anderen haben sich durch die einfache Verfügbarkeit von parallelverarbeitenden Rechenressourcen (Grafikprozessoren) und die große Menge an schnell zugänglichen Datensätzen auch die Rahmenbedingungen deutlich verbessert. Aufgrund der dadurch möglichen Kaskadierung vieler Schichten in neuronalen Netzen, die Aufgaben übernehmen, die bis vor wenigen Jahren noch handgestrickten Algorithmen vorbehalten waren (z. B. Merkmalsextraktion auf Ebene des Audiosignals), wird in diesem Zusammenhang von Deep Learning gesprochen.

In der Grundstruktur bestehen künstliche neuronale Netze aus einer Vielzahl an Neuronen, die meist in einer Struktur von mehreren Schichten angeordnet sind. Ein Neuron bildet dabei jeweils eine gewichtete Summe seiner Eingangsgrößen (plus einem Bias-Gewicht) und wendet eine nichtlineare Aktivierungsfunktion auf das Ergebnis an, bspw. eine Tangens-hyperbolicus-Funktion oder im Fall des tiefen Lernens oft eine sogenannte Rectified Linearfunktion. In der Ausgabeschicht des Netzes gibt es normalerweise so viele Neuronen wie Klassen. Bei der Eingabe der Merkmale bzw. des Signals einer Instanz einer Klasse soll die Ausgabe des entsprechenden Neurons möglichst groß und die der anderen möglichst klein sein. Während der Trainingsphase werden die Daten dann iterativ durch das Netz propagiert, der Fehler am Ausgang wird ermittelt und zurückpropagiert, d. h. Schicht für Schicht zurückberechnet. Schließlich werden die Gewichte jedes Neurons so adaptiert, dass der Fehler in der Ausgabeschicht geringer wird. Entscheidende Parameter sind hierbei die Lernrate, d. h. eine Konstante, mit der der Fehler in jeder Schicht multipliziert wird um die Gewichte anzupassen, und wie viele Daten in jedem Schritt berücksichtigt werden.

Neuronale Netze haben den Vorteil, dass sie sehr flexibel sind. Mehrere Klassifizierungsaufgaben können auf einmal trainiert und verschiedene Eingangsmerkmale sehr einfach fusioniert werden. Verschiedene Schichten (wie CNNs oder LSTM-RNNs) können einfach miteinander kombiniert werden. Allerdings gibt es dadurch für jede Aufgabenstellung eine große Anzahl an Hyperparametern und möglichen Netzarchitekturen (Anzahl der Neuronen oder LSTM-Einheiten in jeder Schicht, Aktivierungsfunktionen etc.), sodass der Entwickler über einige Erfahrung und Ressourcen verfügen muss, um ein optimales Modell trainieren zu können. Ein allgemeiner Vorteil im Vergleich mit statischen Verfahren wie den SVMs ist, dass mit den rekurrenten Architekturen (wie LSTM-RNNs) Sequenzen modelliert werden können wie bei den HMMs. Eine Berechnung von stochastischen Maßen oder bspw. BoAW, um einen Merkmalsvektor fester Länge zu erzeugen, ist also nicht erforderlich.

Beispiel einer Implementierung in Python

Schließlich möchten wir an einem einfachen Beispiel eine mögliche Implementierung eines Verfahrens zur automatischen Emotionserkennung aus Sprachdaten vermitteln. Der hier verwendete Ansatz verwendet einen vordefinierten Merkmalssatz und eine SVM als Klassifikator. Als Datensatz wird im Beispiel die Datenbank Emo-DB vorausgesetzt [1]. Diese Datenbank besteht aus Sprachaufnahmen zehn verschiedener Sprecher (fünf männlich, fünf weiblich) in sechs verschiedenen emotionalen Zuständen (Angst, Ekel, Freude, Langeweile, Trauer, Wut) und einem neutralen Zustand. Alle Sprecher in der Datenbank sind Schauspieler und haben die Emotionen gezielt gespielt; die zu hörenden Emotionen sind also nicht unbedingt natürlich und oft übertrieben. Im Bereich des Affective Computing wird deshalb meist mit Datenbanken gearbeitet, bei denen in den Sprechern in einer natürlichen Weise Emotionen elizitiert wurden. Allerdings sind die Klassen in Emo-DB sehr leicht zu erkennen, daher ist die Datenbank für Demonstrationszwecke sehr gut geeignet. In derselben Weise wie im dargestellten Codebeispiel lassen sich natürlich auch Systeme für die Erkennung von Gesundheitsparametern oder allgemein Systeme zur Audioklassifikation implementieren.

Im Beispiel wird ein Modell betrachtet, das auf akustischen Merkmalsvektoren beruht, die mit dem Toolkit openSMILE extrahiert wurden. openSMILE ist ein in C++ geschriebenes Programm zur sehr schnellen Extraktion von Audiomerkmalen. Es kann auf verschiedenen Plattformen wie Windows, Linux und Android eingesetzt werden. Version 2.3 kann als Quellcode und als ausführbare Datei unter [2] heruntergeladen werden. Zudem wird für unser Codebeispiel eine Installation von Python einschließlich der Bibliothek scikit-learn und deren Abhängigkeiten benötigt. Die Bibliothek enthält Funktionen zum Training und zur Evaluierung vieler Modelle des maschinellen Lernens (u. a. kNN, SVM, Random Forest, einfache neuronale Netze) und zur Datenvorverarbeitung. Empfohlen wird die Verwendung der Distribution Anaconda (Python 2 oder 3), die alle notwendigen Pakete enthält.

Für das Modell werden in openSMILE zunächst 130 zeitabhängige Merkmale (u. a. MFCC, spektrale Deskriptoren, F0, Jitter, Shimmer) extrahiert (Listing 1) und aus diesen dann Statistiken auf Ebene der jeweiligen Audioinstanz berechnet, sodass wir für jede Audiodatei einen Merkmalsvektor der Länge 6 373 erhalten. Es kommt der Merkmalssatz ComParE zum Einsatz, der sich für viele Anwendungen der paralinguistischen Sprachanalyse (u. a. Emotion, Erkennung von Parkinson und Infektionen des Atemwegs) als geeignet erwiesen hat.

Listing 1: Audiomerkmalsextraktion mit openSMILE

#!/usr/bin/python
# Extraktion akustischer Merkmale für die Datenbank EmoDB mit openSMILE
 
import os
 
wavfolder = 'download/wav'  # Ordner mit allen EmoDB-wav-Dateien.
# Emotionslabels sind im Dateinamen kodiert.
 
# openSMILE Pfad und Konfigurationsdatei
SMILEpath = '/tools/opensmile-2.3.0/bin/linux_x64_standalone_static/SMILExtract'  
# Linux, bei Windows entsprechend: '/tools/opensmile-2.3.0/bin/Win32/SMILExtract_Release.exe'
SMILEconf = '/tools/opensmile-2.3.0/config/ComParE_2016.conf'
 
# Training
outfile_train = 'EmoDB_train.csv'
indexes_train = ['11','12','13','14','15','16']
 
# Test
outfile_test = 'EmoDB_test.csv'
indexes_test = ['03','08','09','10']
 
# Merkmalsdateien entfernen, falls vorhanden
if os.path.isfile(outfile_train):
  os.remove(outfile_train)
if os.path.isfile(outfile_test):
  os.remove(outfile_test)
 
# Extraktion der Merkmale
for fn in sorted(os.listdir(wavfolder)):
  fn_full = wavfolder + '/' + fn
  
  if fn[:2] in indexes_train:
    outfile = outfile_train
  elif fn[:2] in indexes_test:
    outfile = outfile_test
  
  os.system(SMILEpath + ' -C ' + SMILEconf + ' -I ' + fn_full + ' -instname ' + fn + \ 
' -csvoutput ' + outfile + ' -timestampcsv 0')

Als Klassifikator kommt eine SVM zum Einsatz, bei der der Komplexitätsparameter optimiert werden muss (Listing 2). Da wir insgesamt nur Daten von zehn verschiedenen Sprechern haben, wäre eine Aufteilung in drei Partitionen nur schwierig möglich. Von daher werden die Daten zunächst nur in eine Trainings- und eine Testpartition aufgeteilt. Alle Sprecher mit einer ID größer als 10 (sechs Sprecher) werden zum Trainieren verwendet, die übrigen (vier Sprecher) zum Testen. Zur Optimierung der Komplexität wird dann eine Kreuzvalidierung verwendet. Hierbei werden für jede Komplexität im untersuchten Bereich sechs verschiedene Modelle trainiert, wobei jeweils die Daten eines Sprechers im Trainingssatz zum Validieren, die der übrigen zum Trainieren verwendet werden. Als Maß für die Güte der Klassifizierung wird der Mittelwert des Recalls (auch: Trefferquote) für jede Klasse betrachtet, d. h. des Prozentsatzes der Instanzen jeder der sieben Klassen, die korrekt klassifiziert worden sind. Dieser Wert wird manchmal auch als Unweighted Average Recall (UAR) oder Macro-Average Recall bezeichnet. Es wird diejenige Komplexität ausgewählt, für die sich gemittelt über alle sechs Permutationen der größte UAR ergibt. Schließlich wird das Modell auf dem gesamten Trainingssatz nochmals mit der ausgewählten Komplexität trainiert und auf den Testdaten evaluiert.

Listing 2: Support Vector Machine

#!/usr/bin/python
import numpy as np
import pandas as pd
from sklearn import svm
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import recall_score
from sklearn.metrics import confusion_matrix
 
# Emotionen
emodb_dict = {'A':'Angst', 'E':'Ekel', 'F':'Freude', 'L':'Langeweile', 'N':'Neutral', 'T':'Trauer', 'W':'Wut'}
 
# Sprecher-IDs in Trainingsset
speaker_folds = [11, 12, 13, 14, 15, 16]
 
# Lade beide Merkmalsdateien
data_train = pd.read_csv('EmoDB_train.csv', sep=';', header=0)
data_test  = pd.read_csv('EmoDB_test.csv',  sep=';', header=0)
 
# Merkmale
X_train = np.float64(data_train.values[:,1:])
X_test  = np.float64(data_test.values[:,1:])
 
# Dateinamen
fn_train = data_train.values[:,0]
fn_test  = data_test.values[:,0]
 
# Extrahiere Klassenlabel und Sprecher-IDs aus den Dateinamen
y_train   = np.array([])
y_test    = np.array([])
ids_train = np.array([], dtype=np.int)
 
for k in range(len(fn_train)):
  y_train   = np.append( y_train, emodb_dict[ fn_train[k].replace("'","")[5] ])
  ids_train = np.append( ids_train, int(fn_train[k].replace("'","")[:2]))
for k in range(len(fn_test)):
  y_test = np.append( y_test, emodb_dict[ fn_test[k].replace("'","")[5] ])
 
 
# Skalierung (Normalisierung) der Merkmale
scaler = MinMaxScaler(feature_range=(0,1))
 
# Optimierung des Complexity-Parameters der SVM mittels # Leave-One-Speaker-Out Cross Validation (LOSO-CV)
print('\nOptimierung der Complexity')
mean_score_UAR = np.array([])
r_exp = range(-12,-2)
 
for m in r_exp:
  comp = 2**m
  clf = svm.LinearSVC(C=comp, random_state=0)  # SVM mit linearem Kernel
  scores_UAR = np.array([])
  
  for speaker in speaker_folds:  # Jeder Sprecher in den Trainingsdaten ist   # einmal Test-Fold
    X_test_fold = X_train[speaker==ids_train,:]
    y_test_fold = y_train[speaker==ids_train]
    X_train_fold = X_train[speaker!=ids_train,:]
    y_train_fold = y_train[speaker!=ids_train]
    
    X_train_fold = scaler.fit_transform(X_train_fold)  # Skalierung der     # Merkmale
    X_test_fold  = scaler.transform(X_test_fold)
    
    clf.fit(X_train_fold, y_train_fold)  # Training der SVM
    y_pred = clf.predict(X_test_fold)
    UAR = recall_score(y_test_fold, y_pred, average='macro')  # Unweighted     # Average Recall
    
    scores_UAR = np.append(scores_UAR, UAR)
  
  mean_score_UAR = np.append(mean_score_UAR, scores_UAR.mean())
  print('C=2^' + str(m) + ': UAR = ' + str(round(scores_UAR.mean()*100,2)) + ' %')
 
# Auswahl der Complexity mit optimalem UAR
comp_opt_UAR = 2**r_exp[np.argmax(mean_score_UAR)]
 
 
# Skalierung der gesamten Trainings- und Testdaten
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)
 
# Training einer SVM auf den gesamten Testdaten mit der optimalen # Complexity
clf = svm.LinearSVC(C=comp_opt_UAR, random_state=0)
clf.fit(X_train, y_train)
 
# Anwendung des Modells auf die Testdaten
y_pred   = clf.predict(X_test)
UAR_test = recall_score(y_test, y_pred, average='macro')  # Unweighted # Average Recall
CM = confusion_matrix(y_test, y_pred, labels=emodb_dict.values())  # Konfusionsmatrix
 
# Ausgabe des UAR und der Konfusionsmatrix auf den Testdaten
print('\nUAR auf den Testdaten mit optimierter Complexity: ' + str(round(UAR_test*100,2)) + ' %')
print('\nKonfusionsmatrix (Praediktionen einer Klasse in jeweiliger Spalte):')
print(emodb_dict.values())
print(CM)

Als Ergebnis erhält man ein UAR von fast 80 Prozent auf den Sprechern der Testdaten. Bei einem Zufallsklassifikator oder einem Klassifikator, der stets dieselbe Klasse prädiziert, würde man nur ein UAR von 1 geteilt durch die Anzahl der Klassen, hier also etwa 14 Prozent erhalten. Diesbezüglich ist das Ergebnis ansehnlich, allerdings ist zu bedenken, dass die Sprecher die Emotionen gespielt haben und die akustischen Aufnahmebedingungen sehr gut sind. Die Konfusionsmatrix, die zum Ende des Skripts berechnet wird, gibt schließlich an, zwischen welchen Klassen die Verwechslung am häufigsten besteht. Die meisten Verwechslungen gibt es im Beispiel zwischen Freude und Angst bzw. Wut, allesamt Emotionen mit einem hohen Erregungsgrad (Arousal) und einer typischerweise hohen Dynamik der Lautstärke. Die verwendete SVM kann einfach durch ein anderes maschinelles Lernverfahren aus der Bibliothek scikit-learn ersetzt werden. Die Onlinedokumentation ist hierfür sehr hilfreich.

Weitere Ansätze

Schließlich können auch andere akustische Merkmale bzw. Merkmalsdarstellungen ausprobiert werden. Die beschriebenen BoAW-Merkmale können einfach mit dem Toolkit openXBOW [3] generiert werden. Diese beruhen auf den zeitabhängigen Merkmalen, die wiederum mit openSMILE extrahiert werden können. Die Dokumentation von openXBOW im GitHub Repository bietet ein Tutorial hierfür an. Des Weiteren gibt es auch fortgeschrittene Methoden zur Erzeugung von Merkmalen mittels unüberwachter Methoden des Deep Learning. Hierfür sei das Toolkit auDeep als Beispiel genannt, dessen Merkmale bereits sehr gute Ergebnisse für verschiedenste Bereiche der Audioklassifikation, u. a. Emotionserkennung, akustische Szenenerkennung und Klassifikation von Musikgenres, erzielt haben [4].

Für die Klassifikation bietet scikit-learn mit dem MLPClassifier (MLP: Multi-Layer Perceptron) auch einfache neuronale Netze an. Allerdings sind für weiterführende Architekturen wie die mit LSTM spezialisierte Frameworks notwendig. Eins der populärsten Frameworks hierfür ist TensorFlow, das von Google Brain entwickelt wurde und derzeit regelmäßig erweitert wird. Die parallelisierbaren Berechnungen werden bei Verfügbarkeit einer NVIDIA-CUDA-kompatiblen Grafikkarte automatisch auf diese ausgelagert. Das tiefere Verständnis der Funktionsweise von TensorFlow nimmt normalerweise etwas Zeit in Anspruch. Um den Einstieg zu erleichtern, kann das High-Level API Keras verwendet werden, das sowohl TensorFlow als auch alternativ dazu Theano verwenden kann. Alle beschriebenen APIs sind hauptsächlich für Python geschrieben. Auch das Exportieren von trainierten Modellen auf andere Plattformen ist mit TensorFlow relativ einfach. Insbesondere für die Integration in Android eignet sich TensorFlow Lite, eine schnelle Version zum Ausführen trainierter Modelle.

Fazit

Maschinelles Lernen und Deep Learning sind derzeit in aller Munde und haben die Bereiche der Datenanalyse, der Signal- und Sprachverarbeitung, der Computer Vision und der Robotik revolutioniert. Onlinekurse zu Deep Learning werden teilweise von mehr als 100 000 registrierten Nutzern verfolgt. Auf der Onlineplattform Kaggle mit mehr als einer halben Million Nutzern finden täglich Wettbewerbe statt, bei denen Unternehmen oder Behörden Daten hochladen und teilweise hochdotierte Modellierungsaufgaben definieren. Die Funktionsweise maschineller Lernverfahren, neuronaler Netzmodelle und Kenntnisse zur Implementierung dieser sind daher eine wichtige Grundlage für Anwendungen in den verschiedensten Bereichen.

schmitt_maximilian_sw.tif_fmt1.jpgMaximilian Schmitt ist Wissenschaftler an der Universität Augsburg. Er ist Experte im Bereich des maschinellen Lernens und der Audioanalyse. Von 2015 bis 2017 hat er einen Kurs über maschinelles Lernen an der Universität Passau geleitet.

Twitter

amiriparian_shahin_sw.tif_fmt1.jpgShahin Amiriparian ist Wissenschaftler an der Universität Augsburg. Er ist Experte im Bereich des maschinellen Lernens und der Audioanalyse. Von 2014 bis 2017 hat er den Kurs „Programmierung in Java“ an der Universität Passau geleitet.

Twitter

schuller_bjoern_sw.tif_fmt1.jpgBjörn Schuller ist Professor an der Universität Augsburg, Professor für Künstliche Intelligenz am Imperial College London und CSO der audEERING GmbH. Er ist ein international führender Experte im Bereich des Affective Computing und der maschinellen Audioanalyse.

Twitter

Als Guido van Rossum Python entwickelte, wollte er eine „einfache“ Programmiersprache erzeugen, die Schwachstellen anderer Systeme umging. Aufgrund der einfachen Syntax und durchdachter syntaktischer Kniffe entwickelte sich die Sprache als Standard für diverse wissenschaftliche Anwendungen wie Machine Learning.

Wer mit C, Java oder Perl aufgewachsen ist, betrachtet Python (auch ob der sehr eigenwilligen Community) vielleicht als Programmiersprache für eher weniger begabte Entwickler. Das ist schon deshalb unfair, weil Python im Bereich der Bibliotheken einen immensen Umfang aufweist und einige sehr interessante syntaktische Kniffe mitbringt.

Der Artikel geht davon aus, dass der Leser eine andere Programmiersprache – sei es C++ oder Java – kennt und mehr über die Besonderheiten Pythons erfahren will. Als Host soll eine unter Ubuntu 14.04 laufende AMD-8-Kern-Workstation dienen. Das bedeutet nicht, dass Python nicht auch unter macOS oder Windows existiert: Es gibt kaum ein Betriebssystem, das ohne Python auskommen muss.

Zähmung des Chaos

Der vor einigen Wochen erfolgte Rücktritt Guido van Rossums bestätigte eine schwerwiegende Schwachstelle der Python-Welt: Man hatte noch nie eine wirklich klare Roadmap – Python ist die Referenzimplementierung für das System des intelligenten Chaos. Aufgrund der vergleichsweise hohen Popularität ergibt sich eine problematische Situation. Es gibt eine Vielzahl verschiedener Python-Versionen, die miteinander im Großen und Ganzen inkompatibel sind. Zudem spannen viele Linux-Distributionen Python zur Realisierung von Systemdiensten ein – installiert man versehentlich einen inkompatiblen Interpreter, hat man es danach mit einer nicht mehr startfähigen Station zu tun.

Unter Ubuntu 14.04 ist die Lage insofern haarig, als Python und Python3 verschiedene Versionen des Interpreters aufrufen können:

tamhan@TAMHAN14:~$ python -V
Python 2.7.6
tamhan@TAMHAN14:~$ python3 -V
Python 3.4.3

Kommen Bibliotheken und andere lustige Dinge dazu, fühlt man sich an den berühmt-berüchtigten Würfel aus ballistischem Gel erinnert. Es ist nur eine Frage der Zeit, wann eine Änderung die Enchilada zum Kollaps bringt. Zur Umgehung des Problems bietet sich das System des virtuellen Environments an, dessen Funktion in Abbildung 1 schematisch dargestellt ist.

hanna_pythonml_1.tif_fmt1.jpgAbb. 1: Virtuelle Environments isolieren Python-Ausführungsumgebungen analog zu einem Container

Die Installation der virtuellen Environment Engine ist von Betriebssystem und Python-Version abhängig. Auf der Workstation des Autors erfolgt sie durch Eingabe des folgenden Kommandos, Nutzer alternativer Betriebssysteme finden online weitere Informationen:

tamhan@TAMHAN14:~$ sudo apt-get install python3.4-venv

Das Anlegen eines neuen virtuellen Environments erfolgt durch eine Befehlssequenz, die ebenfalls von Plattform zu Plattform unterschiedlich ist. Unter Ubuntu 14.04 sehen die Befehle folgendermaßen aus:

tamhan@TAMHAN14:~/suspython$ python3 -m venv workspace
tamhan@TAMHAN14:~/suspython$ cd workspace/
tamhan@TAMHAN14:~/suspython/workspace$ source bin/activate
(workspace) tamhan@TAMHAN14:~/suspython/workspace$

Der Lohn der Mühen ist die in Abbildung 2 gezeigte Arbeitsumgebung, die neben einer Konfigurationsdatei auch eine Gruppe von Verweisen enthält, über die Shell und Betriebssystem den Zugriff auf den Python-Interpreter „verbiegen“ können. Ein weiterer netter Aspekt des virtuellen Environments ist, dass sie das lokale Installieren von Bibliotheken erlauben – sitzt die Bibliothek im virtuellen Environment, ist sie vom Rest des Systems unabhängig.

hanna_pythonml_2.tif_fmt1.jpgAbb. 2: Virtuelle Environments bestehen aus Verknüpfungen und Konfigurationsskripten

Nach der Ausführung des Kommandos source bin/activate ist das Terminal parametriert. Das erkennt man unter anderem daran, dass der Name der Arbeitsumgebung in einer Klammer vor dem eigentlichen Prompt erscheint. Möchte man ein schon erstelltes virtuelles Environment nachträglich laden, muss die Source abermals eingegeben werden:

tamhan@TAMHAN14:~/suspython/workspace$ source bin/activate
(workspace) tamhan@TAMHAN14:~/suspython/workspace$

Interaktiv und eingabegesteuert

Programmiersprachen wie C folgen dem EVA-Prinzip. Der Programmierer liefert eine oder mehrere Codedateien an, die kompiliert und ausgeführt werden. In Python sieht die Lage anders aus – dazu gebe man probeweise das Kommando python ein und drücke einige Male CTRL + C, um sich am in Abbildung 3 gezeigten Verhalten zu erfreuen.

hanna_pythonml_3.tif_fmt1.jpgAbb. 3: Der Interpreter zeigt sich vom Keyboard-Interrupt nur wenig beeindruckt

Die Ursache dieses Verhaltens ist, dass der Python-Interpreter auch interaktiv betrieben werden kann. Dabei gibt man Zeile um Zeile ein und bekommt sofort eine Ausgabe – das ist als behelfsmäßiger Taschenrechner ideal geeignet. Wer diesen Betriebsmodus verlassen möchte, muss die Tastenkombination CTRL + D verwenden.

In der Praxis nutzt man Python zur Ausführung von angelieferten Dateien. Code liegt in Form von Dateien mit der Endung .py vor, die sich folgendermaßen zur Ausführung anweisen lassen:

(workspace) tamhan@TAMHAN14:~/suspython/workspace$ python test.py

Retter der Männerfreundschaft

Ist man streitlustig und jung, kommt der Moment, an dem man mit Kollegen über die beste Art der Formatierung von Code zusammendonnert. Was für einen melierten Coder harmlos klingt, kann tiefe Freundschaften auf eine harte Probe stellen.

In Python wird dieses Problem umgangen, weil die Sprache radikale Einschränkungen an den Aufbau der Codedateien anlegt. Der erste Unterschied ist, dass der Python-Interpreter Code in logische und physikalische Zeilen unterteilt. Eine logische Zeile kann dabei aus mehreren physikalischen bestehen, alternativ kann eine physikalische Zeile unter Nutzung des ;-Operators mehrere logische Zeilen aufnehmen.

Python 3 erlaubt die Nutzung von UTF-8-Zeichen im Quellcode, während Python 2 auf ASCII beschränkt ist. Beide Programmierschemata können sowohl Tabs als auch Whitespaces aufnehmen, was für Diskussionen sorgt. Im als Quasistandard geltenden Dokument PEP8 [1] schreibt Guido van Rossum, dass die Verwendung von Leerzeichen der Verwendung von Tabulatoren vorzuziehen ist. Idealerweise verwendet man pro Einrückungsebene vier Leerzeichen, ein intelligent eingestellter Editor kann diese bei Drücken der Tab-Taste automatisch einschreiben.

Python 2 brachte Logik mit, die während der Programmausführung Tabs durch Spaces zu ersetzen sucht – bei der Arbeit mit Python 3 müssen Sie in der Datei entweder nur Tabs oder nur Spaces verwenden. Die Ursache dieser pedantisch klingenden Richtlinie erschließt sich durch folgendes Beispielprogramm:

weight = float(input("Tam Air - Gewichtsrechner"))
print("Habe eine Kiste!")
if weight > 70:
  print("Strafe zahlen: 25€.")
elif weight > 50:
  print("Strafe zahlen: 5€.")
print("Die Tam Air bedankt sich")

Dieser primitive Gewichtsrechner einer wenig kulanten Airline ist interessant, weil er eine Gruppe von Python-spezifischen Elementen demonstriert. Erstens wird die Funktion print mit geschwungenen Klammern aufgerufen, stellt also eine gewöhnliche Funktion dar – in Python 2 war der Befehl ein in die Sprache eingebautes Intrinsic, das logischerweise ohne Klammern aktiviert werden musste.

C- und Visual-Basic-Programmierer reiben sich hier verwundert die Augen, da sowohl Klammern als auch EndIf-Statements fehlen. Die Codeblöcke von Selektionen, Interaktionen und ähnlichen Elementen werden in Python ausschließlich durch die Anzahl der Whitespaces bestimmt, die vor ihnen stehen.

Der Interpreter zeigt sich bei der Durchsetzung dieser Richtlinie wenig kooperativ – als Beispiel wollen wir den if-Teil ein wenig anpassen, und beim Einfügen des zweiten Print-Befehls statt vier Leerzeichen derer fünf platzieren:

if weight > 70:
  print("Strafe zahlen: 25€.")
    print("Sie packen zu viel!")

Wer das vorliegende Programm auszuführen versucht, wird mit der in Abbildung 4 gezeigten Fehlermeldung bestraft. Im Interesse der Vollständigkeit sei angemerkt, dass es sich hierbei um ein fundamentales Designkonzept von Python handelt – wer damit nicht zurechtkommt, muss sich eine andere Programmiersprache aussuchen.

hanna_pythonml_4.tif_fmt1.jpgAbb. 4: Fehler in Sachen Indentation werden vom Interpreter scharf geahndet

Mehr Datentypen als üblich

Ein alter Kalauer besagt, dass ein Programmierer, der eine objektorientierte Sprache kennt, alle kennt. Dies mag auf Python bis zu einem gewissen Grad zutreffen, da man Klassen und Co. unterstützt. Interessant ist, dass sich Python im Bereich der grundlegenden Datentypen wesentlich breiter aufstellt als P.-T.-Konkurrenz. Wo C und Co. nur primitive Variablen direkt unterstützen, bietet Python reiche Vielfalt.

Erfahrene Entwickler mögen sich nicht daran stoßen, wenn sie Listen und ähnliches von Hand realisieren. Wer Python als Werkzeug zur Implementierung eines Algorithmus sieht, freut sich darüber, wenn er zur Realisierung der eigentlichen Geschäftsaufgabe so wenige Jobs wie möglich durchführen muss.

Eine angenehme Erweiterung ist die direkte Unterstützung komplexer Zahlen: ein Darstellungsformat, das insbesondere in der Elektrotechnik weit verbreitet ist und sich aus Absolutbetrag und Winkel zusammensetzt. Komplexe Zahlen dienen beispielsweise zur Beschreibung von Spannungen und Strömen in Schaltkreisen, die bei sinusoidaler Anregung zeitverschoben auftreten.

Wo man in anderen Sprachen von Hand drauflos programmieren muss, unterstützt Python den Entwickler direkt mit einem komplexen Datentyp:

import cmath
z = complex(4,4);
print ("Realteil : ",end="")
print (z.real)
print ("Imaginärteil : ",end="")
print (z.imag)

Elektrotechniker und Rüstungselektroniker sind an dieser Stelle traurig: In Python werden komplexe Zahlen prinzipiell in Komponenten dargestellt, die Polarform ist nur als errechnete Darstellung verfügbar. Das vorliegende Programmstück illustriert den Import des cmath-Moduls, das die für die Verarbeitung von komplexen Zahlen vorgesehenen Funktionen bereitstellt. Die komplexe Aufrufweise von print nutzt einen Tupel – eine Konstruktion, die wir unds später anschauen wollen. Als alter Elektronikknochen kann sich der Autor hier die Vorstellung von rect nicht verkneifen. Sie zeigt, wie man einen in Polarkoordinaten vorliegenden Wert in eine Python-Variable überträgt:

cmath.rect(r, phi)

Elemente in Reih und Glied

Eine weitere Besonderheit der reichen Basistypvielfalt der Sprache sind Sequenztypen. Die Frage, was ein Sequenztyp genau ist, beschäftigt einen Raum voller Python-Entwickler lange. Der Klassiker „Python in a Nutshell“ bezeichnet Sequenztypen als Felder, die eine Art KV-Speicher realisieren, der aus einer Ganzzahl und einer anderen Werteart zusammengesetzt ist. Es sei angemerkt, dass diese Definition nicht zu 100 Prozent stimmt – der Named Tuple bricht mit dem Gebot des Vorhandenseins eines ints als Primärschlüssel.

Der offene Aufbau von Python erlaubt Bibliotheken und Drittentwicklern das Erzeugen eigener Sequenztypen. In der Praxis trifft man die folgenden sieben Elemente besonders häufig:

  • Buffer

  • Byte Arrays

  • Lists

  • Strings

  • Tuples

  • Unicode Strings

  • Xranges

Dass Arrays in dieser Auflistung nicht erscheinen, ist kein Zufall. Die aus C bekannten Datenstrukturen stehen in Python zwar in Form verschiedener Module zur Verfügung, die zur Basisdistribution gehören. Man setzt sie außerhalb mathematischer Prozesse allerdings selten ein und nutzt stattdessen Python-spezifischere Elemente.

Da eine komplette Besprechung all dieser Datentypen in einem einzigen Artikel illusorisch ist, wollen wir uns auf Tuples und ihre Konsorten beschränken. Im Prinzip hatten wir die Elemente weiter oben kennen gelernt – der Aufruf von print erfolgte mit einem seltsamen Parameter:

print ("Imaginärteil : ",end="")

Ein primitives Tupel ist eine Liste, die von Python allerdings als immutable deklariert wird. Hinter diesem komplex klingenden Begriff verbirgt sich der Gedanke, dass sich das Objekt zur Laufzeit nicht mehr ändert – mutable-Elemente verändern sich mitunter auch noch nach der Deklaration. Aus syntaktischer Sicht schenken sich die beiden Speicherfelder wenig – Tabelle 1 demonstriert die Unterschiede.

Beispielcode Tupel

Beispielcode Liste

thistuple = ("apple", "banana", "cherry")

print(thistuple[1])

thislist = ["apple", "banana", "cherry"]

thislist[1] = "blackcurrant"

print(thislist)

Tabelle 1: Unterschiede zwischen Liste und Tupel

Tupel und Listen unterscheiden sich vor allem bei der Erstellung. Möchte ein Entwickler ein Tupel erstellen, schließt er die anzulegenden Elemente in runde Klammern ein. Eine Liste entsteht, wenn man die aus anderen Programmiersprachen als Arraysymbol verwendeten eckigen Klammern einspannt.

Zur Laufzeit findet sich derweil kein großer Unterschied – auch beim Tupel erfolgt der Zugriff auf die individuellen Elemente mit eckigen Klammern. Wichtig ist, dass Änderungen der Tupel- bzw. Listenstruktur nur im Fall der Liste erlaubt sind. Das bedeutet allerdings nicht, dass alle in einem Tupel befindlichen Elemente nicht veränderbar sind. Speichert man beispielsweise eine Liste als Member eines Tupels, lassen sich die individuellen Werte der Liste sehr wohl verändern – konstant ist nur ihre Position im Tupel.

Die Frage, wann man Listen und wann Tupel verwenden sollte, lässt sich hervorragend diskutieren [2]. Logisch ist, dass die zur Laufzeit nicht mehr veränderliche Form Tupel für alle Situationen ideal geeignet macht, wo die gehaltenen Informationen keinen Veränderungen unterliegen. Ein weiterer exzellenter Einsatzbereich sind geschwindigkeitsrelevante Situationen – da der Speicheraufbau nicht mehr verändert werden muss, erweist sich diese Variante als performanter.

Damit ist der weiter oben verwendete Aufruf von print allerdings noch nicht erklärt. Ein Tupel, aber auch eine Liste, ordnet den enthaltenen Elementen Zahlenwerte zu. Es gibt Situationen, in denen man andere Schlüsseltypen bevorzugen würde. In diesem Fall schlägt die Stunde des Named Tuple, das mit Python 2.6 bzw. 3.0 Eingang in den Sprachstandard fand. Die Erzeugung eines Named Tuple könnte einfacher nicht sein:

from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)
print(pt1.x)
print(pt1[0])

Die Verwendung des richtigen Ladebefehls ist wichtig. Wir nutzen from – import, um die im Paket „Collections“ liegende Funktion namedtuple direkt ansprechbar zu machen. Diese dient sodann als Generator, mit dem wir zwei Points ins Leben rufen.

Interessant ist, dass die in einem Named Tuple befindlichen Elemente nicht nur über ihre Namen angesprochen werden können. Im Interesse der Abwärtskompatibilität sorgt die collections-Klasse auch dafür, dass ein klassischer numerischer Zugriff ebenfalls problemlos funktioniert.

Theoretisch lässt sich ein derartiger Named Tuple direkt an eine Funktion übergeben. Der weiter oben gesehene seltsame Aufruf von print hat allerdings eine andere Ursache: Es handelt sich dabei um namensbehaftete Funktionsparameter. Auch sie wollen wir mit einem kleinen Beispiel näher ansehen. Die Funktion hurz realisiert dabei das gewichtete Summieren von vier Parametern, die der Bequemlichkeit halber auf die Namen a, b, c und d hören:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(1,2,3,4))
print (hurz(4,3,2,1))

Dank der Multiplikation mit verschiedenen Gewichten ist die Reihenfolge der Parameter von Bedeutung. Der erste Aufruf der Funktion liefert den Wert 30 zurück, während der zweite Aufruf, ob der anderen Anordnung der Zahlen, nur eine Summe von 20 ergibt. Die Nutzung „namensbehafteter“ Parameter erlaubt uns das Umschiffen des Problems:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(1,2,3,4))
print (hurz(d=4,c=3,b=2,a=1))

Die Ausführung des vorliegenden Programms führt dazu, dass zweimal die gleiche Zahl am Bildschirm erscheint. Der Interpreter weist die angelieferten Werte nämlich anhand der Namen in die gewünschten Parameterslots ein.

Interessanter ist in diesem Zusammenhang die Frage, was passiert, wenn man nicht alle Parameter anliefert. Als Beispiel dafür wollen wir versuchen, hurz nur mit Werten für a und d auszustatten:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(d=4,a=1))

Die Ausführung des vorliegenden Programms wird mit dem in Abbildung 5 gezeigten Fehler quittiert. Für das Anliefern optionaler Parameter ist also etwas zusätzliche Arbeit erforderlich, die im Methodenkopf erfolgen muss.

hanna_pythonml_5.tif_fmt1.jpgAbb. 5: Das Fehlen der Parameter „b“ und „c“ lässt die Programmausführung scheitern

Ein naiver Versuch zur Umgehung des Problems bestünde darin, nur den Werten b und c Standardwerte einzuschreiben:

def hurz(a, b=0, c=0, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(d=4,a=1))

Leider ist diese Vorgehensweise nicht erfolgversprechend. Der Python-Sprachstandard mag zwar sehr flexibel sein, schreibt an dieser Stelle aber vor, dass Parameter ohne Defaultwerte immer nur vor dem ersten Parameter stehen dürfen, der einen Defaultwert mitbringt. Zum Erhalten eines kompilierungsfähigen Programms müssen wir also auch dem Wert d einen Standard Value einschreiben, um uns an einer lauffähigen Variante zu erfreuen. Diese auf den ersten Blick akademisch klingende Spielerei ist in der Praxis von großer Bedeutung. Viele Python-Funktionen und -Bibliotheken – nicht nur das weiter oben verwendete print – haben einen oder zwei fixe Parameter und werden sonst über eine Art Named Tuple aus diversen Parametern mit Informationen versorgt. Das werden wir im Artikel weiter unten abermals sehen, wenn wir uns der automatisierten Realisierung von Diagrammen zuwenden.

Die Liste als Werkzeug

Das Speichern von Informationen in Listen ist nur in den seltensten Fällen Selbstzweck. Insbesondere im wissenschaftlichen Bereich handelt es sich dabei meist um Mess- oder Rechenwerte, die gewichtet oder sonstwie durch Programmlogik verändert, bewertet oder bearbeitet werden sollen. Python ist sich dieser Aufgabe bzw. Situation seit langer Zeit bewusst und trägt ihr durch umfangreiche Methoden zur Listenverarbeitung Rechnung. Ob des immensen Umfangs der Features wollen wir uns hier nur Exemplare herausgreifen, die für Umsteiger interessant und im Machine-Learning-Bereich von Bedeutung sind.

Um mit Listen und Co. zu arbeiten, brauchen wir im ersten Schritt Informationen. Da die Interaktion mit echten Dateien an dieser Stelle zu weit führen würde, nutzen wir stattdessen den range-Operator:

mylist = range(2,20)
print(mylist)
for n in mylist:
  print(n)

In Python 3 ist range eine vergleichsweise flexible Methode, die mehr oder weniger beliebige Listen aus Zahlen erstellt. Im Prinzip ist nur ein Endwert erforderlich; wer auch einen Startwert und/oder eine Schrittweite angibt, kann die Ausgabe noch genauer beeinflussen. Interessant ist in diesem Zusammenhang noch das Verhalten des Aufrufs von print, wenn er nur mit dem mylist-Objekt versorgt wird. Abbildung 6 zeigt, was Sie erwarten dürfen.

hanna_pythonml_6.tif_fmt1.jpgAbb. 6: Die Ausgabe der einzelnen Elemente erfolgt in Python nur auf explizite Anweisung

Nun, da wir im Besitz einer Folge bzw. Liste sind, können wir damit beginnen, mit ihr zu rechnen. Die mit Abstand einfachste Rechenmethode ist dabei map. Sie weisen den Interpreter damit dazu an, eine angelieferte Funktion auf alle Elemente der Liste anzuwenden und danach eine neue Liste bzw. ein Iterable zu retournieren, in dem die Ergebnisse der jeweiligen Elemente bereitliegen. Ein Beispiel wäre die Verdoppelung der Werte, die sich mit folgendem Code bewerkstelligen lässt:

def arbeiter(x):
  return x*2
mylist = range(2,20)
values = map(arbeiter, mylist)
for n in values:
  print(n)

Die map-Funktion nimmt als ersten Parameter ein Lambda entgegen. Wer bisher mit Microsoft’schen Programmiersprachen programmiert hat, weiß instinktiv, was sich dahinter verbirgt: Eine Art Funktions-Pointer, der die Struktur der anzulegenden Funktion vorgibt, Entwicklern beim Anlegen sonst aber freie Hand lässt.

Wer das vorliegende Programm ausführt, sieht in der Ausgabe eine Verdoppelung der von Range generierten Werte. Daraus folgt, dass der map-Befehl die Funktion arbeiter auch wirklich auf alle Elemente von mylist angewendet und das resultierende Feld im Wert value zurückgeliefert hat. Die Ausgabe unter Nutzung einer for-Iteration erfolgt dann nach dem von weiter oben bekannten Schema.

Möchte man die angelieferten Werte bei Nichtgefallen verwerfen, bietet sich stattdessen die Methode filter an. Zu guter Letzt gibt es auch noch reduce. Die in Python 3.0 aufgrund extremer Unbeliebtheit bei Guido van Rossum nicht mehr nativ implementierte Funktion erlaubt das Zusammenfassen von Werten. Weil es sich dabei um ein interessantes Beispiel handelt, hier noch eine kurze Implementierung:

import functools
def arbeiter(x, x2):
  return x+x2
mylist = range(2,20)
value = functools.reduce(arbeiter, mylist)
print(value)

Die an reduce übergebene Lambdafunktion nimmt anstatt eines alleinstehenden Parameters derer zwei auf. Der erste ist beim ersten Durchlauf der Funktion der des ersten Felds, während der zweite Parameter immer mit dem Wert des darauffolgenden Felds befüllt wird. Interessant wird es ab dem zweiten Durchlauf: Der erste Parameter, der an die zweite Invokation des Arbeiters übergeben wird, ist der Rückgabewert des ersten. Der zweite Parameter kommt aus dem dritten Feld et cetera. Im vorliegenden Beispiel entsteht auf diese Art und Weise eine bequem berechnete Summe aller im Feld befindlichen Zahlen.

Angemerkt sei, dass der hier verwendete dedizierte Import des Moduls nur in Python 3 erforderlich ist, in 2.X war die Funktion Teil des Hauptmoduls. Als Alternative hierzu gibt es das Konzept der Listenabstraktion, die auch unter dem englischen Namen List Comprehension bekannt ist. Es handelt sich dabei um eine aus der Mengenlehre abgeleitete Methode, um schnell und effizient mit Listen zu arbeiten. Als Beispiel hierfür dient der folgende Code, der im interaktiven Betrieb eine Sequenz von Quadratzahlen erzeugt:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Unter Nutzung einer Comprehension lässt sich diese Aufgabe wesentlich verkürzen:

squares = [x**2 for x in range(10)]

Über die Frage, ob man Comprehensions dem Aufrufen von reduce() vorziehen sollte, lässt sich hervorragend diskutieren.

Bibliotheken sparen Zeit

Auch wenn es über die Syntax von Python noch vieles zu berichten gäbe, müssen wir auf einen weiteren wichtigen Aspekt des Ökosystems eingehen: die Vielzahl von Bibliotheken.

Eine besondere Rolle spielt dabei das SciPy-Projekt, dessen Bibliotheksschatz in Abbildung 7 kurz zusammengefasst wird. Sowohl NumPy als auch SciPy zählen als Quasistandardbibliotheken, die auf so gut wie jeder Python-Installation leicht herunterladbar sind. Sie geben Entwicklern potente mathematische Verfahren an die Hand, deren Implementierung und/oder Lizenzierung bis vor einigen Jahren Tausende von Euros kostete.

hanna_pythonml_7.tif_fmt1.jpgAbb. 7: Das SciPy-Projekt bietet neben der namensgebenden Library auch andere Bibliotheken an

Zur praktischen Nutzung ist es empfehlenswert, eine ganze Gruppe von Utilities herunterzuladen. Bei aktiviertem Virtual Environment reicht hierzu folgende Kommandosequenz:

sudo apt-get install libatlas-base-dev gfortran
sudo apt-get build-dep python-numpy python-scipy python3-numpy python3-scipy
python -m pip install numpy scipy matplotlib

Fehler vermeiden

Bei komplexen Bibliotheken ist es manchmal sinnvoller, eine globale Installation zu befehligen. Die vom Anbieter der Distribution bereitgestellten Pakete sind zwar so gut wie immer veraltet, sind dafür aber präkompiliert.

Alternativ kann man auch eine globale Installation durchführen. Diese erweist sich allerdings als haarig, weil sowohl NumPy als auch SciPy permanenter Entwicklung unterliegen. Installiert ein anderer Nutzer oder ein anderes Programm eine aktuellere oder ältere Version, kann es vorkommen, dass das eigene Programm die Arbeit einstellt. PIP ist jedenfalls eine Art Paketmanager für Python-Bibliotheken [3].

Die Installation von Fortran und libatlas ist keine Marotte des Autors: NumPy und SciPy bringen jede Menge nativen Code mit, der die Performance des Systems optimiert und vor der Einsatzfähigkeit des Environments kompiliert werden muss. Zur Demonstration der Möglichkeiten des Pakets wollen wir ein Programm realisieren, das ein Tortendiagramm auf den Bildschirm bringt. Eine naive Implementierung sieht man in Listing 1.

Listing 1

import matplotlib.pyplot as plt
labels = 'A', 'B', 'C', 'D'
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0)
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
ax1.axis('equal')
plt.show()

Im Interesse der Vollständigkeit sei angemerkt, dass wir uns an dieser Stelle auf haariges Terrain begeben. Das genaue Verhalten von matplotlib ist von Workstation zu Workstation unterschiedlich. Es kann sein, dass einige der hier beschriebenen Probleme auf Ihrem PC bzw. in Ihrer Python-Distribution nicht auftreten.

Aus programmiertechnischer Sicht findet sich hier nicht viel Neues: Wir legen ein Tupel und eine Liste mit Informationen an, die das Verhalten der Diagramm-Engine beeinflussen. Im nächsten Schritt schreiben wir einige Werte ein. pie ist hierbei insofern interessant, als es nur vergleichsweise wenige fixe Parameter verlangt, die genauere Parametrierung des Verhaltens erfolgt über Dutzende optionale Parameter, die man nur bei Bedarf angeben muss. Der (laut Dokumentation) blockierende Aufruf von show sollte in der Theorie dann dafür sorgen, dass das Diagramm am Bildschirm erscheint.

Auf der Workstation des Autors ist das nicht der Fall. Das Programm läuft zwar problemlos durch, beendet sich aber, ohne dass das Diagramm am Bildschirm erscheint (Abb. 8). Die Ursache für das seltsame Verhalten ist, das matplotlib die Aufgabe, wie in Abbildung 9 gezeigt, in zwei Teile aufteilt. Diese Vorgehensweise ist übrigens kein Spezifikum, sondern findet sich auch in vielen anderen Python-Bibliotheken.

hanna_pythonml_9.tif_fmt1.jpgAbb. 8: Das Tortendiagramm erscheint am Bildschirm
hanna_pythonml_8.tif_fmt1.jpgAbb. 9: Das Einführen eines plattformspezifischen Backend-Objekts gilt in der Welt von Python als „sauber“

Wer seine Bibliothek aus den Paketquellen der jeweiligen Distribution bezieht, hat – wie im Kasten erwähnt – kein Problem. Da wir per pip installiert haben, müssen wir im ersten Schritt für die Bereitstellung eines Backends sorgen. Als Erstes wechseln wir in den interaktiven Modus, wo wir die Bibliothek nach dem gerade aktivierten Backend befragen:

(workspace) tamhan@TAMHAN14:~/suspython/workspace$ python
. . .
>>> import matplotlib
>>> matplotlib.get_backend()
'agg'

Zur Lösung des Problems müssen wir eine Gruppe zusätzlicher Bibliotheken herunterladen. Wir wollen hier auf den TK-GUI-Stack setzen, um Zeit zu sparen:

sudo apt-get install tcl-dev tk-dev python-tk python3-tk

Die eigentliche Einbindung ist einfach – wichtig ist nur, dass der Aufruf von use() vor dem Laden des pyplot-Moduls passieren muss:

import matplotlib
matplotlib.use('tkagg')
import matplotlib.pyplot as plt
 
labels = 'A', 'B', 'C', 'D'
. . .
plt.show()

Fazit

Wer von C zu Java, von Java zu C oder von C zu C# umsteigt, findet im Großen und Ganzen Bekanntes vor. Python tanzt insofern aus der Reihe, als sowohl in Sachen Syntax als auch in Sachen Sprachumfang Ungewohntes auftaucht: Vieles, was in anderen Programmiersprachen in Form von Bibliotheken vorliegt, ist hier Teil der Sprache.

Die vergleichsweise chaotische Weiterentwicklung sorgt dafür, dass auch exotischere Programmierkonzepte schnell von der Community absorbiert werden. Daraus folgt, dass das Produkt die Rolle eines Schmelzkessels einnimmt, in dem sich alte und neue Ideen vermischen.

Die Hysterie wegen des Weggangs von Guido van Rossum ist in meinen Augen unnötig: Python war vorher schon chaotisch und wird in der Zukunft nicht plötzlich am Chaos scheitern. Über die Frage, ob man sich den Wahnsinn antun soll, kann man heute nur noch leidlich diskutieren. Es gibt Situationen, in denen die Bibliotheken tausende von Mannstunden einsparen. Man sollte beim Kauf des Lehrbuchs offen sein und der Umgebung eine Chance geben – glauben Sie meiner Wenigkeit, dass Sie den Aufwand nicht bereuen werden.

hanna_tam_sw.tif_fmt1.jpgTam Hanna befasst sich seit der Zeit des Palm IIIc mit der Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.

Mail

Amazon SageMaker ist eine Werkbank für maschinelles Lernen, die für die Bedürfnisse von Data Scientists und Entwicklern optimiert ist. Der Service ist Teil des Amazon-Web-Services-(AWS-)Cloud-Angebots. Er deckt den gesamten Machine-Learning-Zyklus von der Datenexploration und Aufbereitung mit Jupyter Notebook über das Training und die Evaluierung von Modellen bis zum Betrieb eines trainierten Modells in der Produktion ab.

Bei Machine-Learning-Anwendungen werden Daten gesammelt und aufbereitet, um sie dann mithilfe von ML-Algorithmen wie z. B. statistischen Verfahren, Clustering-Algorithmen oder Deep Neural Networks (DNNs) auf einem oder mehreren Rechnern zu trainieren. Dabei leitet der Trainingsalgorithmus aus den Daten ein Modell ab, das in der Lage ist, bestimmte Eigenschaften in den Daten zu erkennen – auch wenn es sich hierbei um Daten handelt, die im Trainingssatz nicht enthalten waren (Abb. 1).

gonzales_sagemaker_1.tif_fmt1.jpgAbb. 1: Ein typischer Machine-Learning-Arbeitszyklus

Machine-Learning-Modelle können also aus Trainingsdaten Gelerntes generalisieren und das Gelernte im Rahmen einer Anwendung für die Verarbeitung von neuen, unbekannten Daten wiederverwenden. Für die Aufbereitung von Trainingsdaten, das Training der Algorithmen und für den Betrieb von ML-Modellen in der Produktion (Inferenz) muss man sich um passende Speicher- und Rechenressourcen kümmern. Nicht selten sind Hunderte oder Tausende von Servern mit dem Training von Terabytes von Trainingsdaten beschäftigt. Doch auch verhältnismäßig kleine Set-ups mit nur einem Rechner für Training oder Betrieb können Mühe machen, da Rechner in Betrieb genommen, Software installiert und die Trainings- und Modelldaten transferiert und verwaltet werden wollen.

Hier hilft AWS mit dem Amazon SageMaker Service [1], der Entwicklern und Data Scientists die Verwaltung von virtuellen Maschinen in der Cloud für die Vorbereitung, das Training und den Betrieb von ML-Modellen abnimmt und diesen Zyklus automatisiert. Dabei können Anwender aus verschiedenen optimierten Algorithmen auswählen, die gängige ML-Aufgaben wie Lineare Regression, K-Means-Clustering oder Bildklassifikation abdecken oder ihre eigenen ML-Algorithmen mitbringen: als neuronales Netz auf TensorFlow- oder MXNet-Basis oder als Docker-Container mit beliebigem ML-Innenleben.

SageMaker dient hierbei als automatisierte Ablaufumgebung für Docker-Container, in die alle Algorithmen eingebettet sind. Als Datenspeicher kommt Amazon S3 zum Einsatz, sowohl für die Trainingsdaten als auch für die resultierenden Modelle. Während der Trainings- und der Betriebsphase startet und verwaltet Amazon SageMaker ein Cluster von Docker-Instanzen in der Amazon Elastic Compute Cloud (EC2) automatisch und nach den Skalierungs- und Typvorgaben des Benutzers.

Die Kosten richten sich dabei nach der Nutzung: Anwender zahlen grundsätzlich nur die aufgewendeten Instanzstunden für Notebook, Trainings- und Hostinginstanzen. Es werden verschiedene Instanztypen mit unterschiedlichen Eigenschaften wie CPU- und Memory-Ausstattung oder GPU-Fähigkeit zur Verfügung gestellt, die pro Stunde unterschiedliche Kosten verursachen. Abgerechnet wird im Sekundentakt.

1. Schritt: Jupyter-Notebook-Instanz anlegen

Viele Data Scientists bevorzugen Jupyter Notebook [2] als Entwicklerwerkzeug, Notizbuch und Benutzerinterface in einem, um mithilfe verschiedener Programmiersprachen wie Python, R oder Shellskripten ihre Daten zu verwalten, zu visualisieren und sie für die Nutzung im ML-Prozess aufzubereiten. Für solche Benutzer bietet Amazon SageMaker einen Set-up-Wizard, der hilft, eine automatisch verwaltete Jupyter-Umgebung im eigenen AWS-Account einzurichten.

Nach Anklicken des Buttons Create notebook instance in der Amazon-SageMaker-Webkonsole führt ein Wizard den Benutzer durch die nötigen Konfigurationseinstellungen wie Notebook-Name oder Amazon-EC2-Instanztyp. Letzterer definiert die Ausstattung der virtuellen Maschine wie CPU, Speicherkapazität oder GPU-Unterstützung: Damit können Entwickler bestimmen, ob sie lieber ein einfaches System mit variabler, aber kostengünstiger Performance aus der ml.t2-Familie als reines „Notizbuch“ nutzen möchten oder ein leistungsfähiges System mit hoher CPU- und/oder RAM-Kapazität für leistungsstarke Vorverarbeitungen bevorzugen. Auch der Zugriff auf GPUs der neuesten Generation direkt auf der Notebook-Instanz ist möglich, um bereits in Entwicklung/Vorverarbeitung mit dieser Technologie experimentieren oder arbeiten zu können (Abb. 2).

gonzales_sagemaker_2.tif_fmt1.jpgAbb. 2: Ein Wizard erleichtert die Einrichtung der automatisch verwalteten Jupyter-Notebook-Umgebung

Weitere Aspekte wie Verschlüsselung, Netzwerkumgebung („Virtual Private Cloud“) sowie Start-up-Skripte für die Anpassung der Jupyter-Umgebung können ebenfalls via Wizard konfiguriert werden. Kurz darauf läuft der Jupyter-Server in der eigenen AWS-Umgebung und man kann von der AWS-Konsole aus sicher und komfortabel über eine verschlüsselte Verbindung auf die Jupyter-Umgebung zugreifen, ohne sich um weitere Authentifizierungs- oder Proxy-Einstellungen kümmern zu müssen.

Die vorinstallierte Jupyter-Umgebung liefert zahlreiche vorbereitete Beispiel-Notebooks zu typischen Anwendungsfällen sowie den in SageMaker integrierten Algorithmen samt Beispieldatensätzen mit. Wer sich vorab einen Eindruck davon verschaffen möchte, findet diese auch auf GitHub unter [3].

Das Hosting von Jupyter Notebook als Benutzerinterface für SageMaker ist nicht obligatorisch, sondern lediglich eine Erleichterung für Anwender. Wer andere Werkzeuge bevorzugt, kann diesen Schritt überspringen und Amazon SageMaker aus anderen Umgebungen heraus einsetzen: Der Dienst ist vollständig über ein RESTful Web Service API nutzbar, sodass man aus beliebigen Umgebungen heraus darauf zugreifen kann. Dabei helfen zahlreiche Software Development Kits (SDKs), die in gängigen Programmiersprachen verfügbar sind.

2. Schritt: Algorithmus auswählen

Der für das Training verwendete Algorithmus entscheidet darüber, was das ML-Modell nach dem Training können soll: Er analysiert die Trainingsdaten und lernt aus ihnen Muster, die er zu einem Modell generalisiert. Dieses Modell ist dann in der Lage, mit beliebigen Daten zu arbeiten und daraus Entscheidungen abzuleiten (Inferenz).

Nehmen wir als Beispiel die Kategorisierung von Bildern basierend auf bestimmten vorgegebenen Kategorien. In der Praxis könnte es sich hierbei um verschiedene Produktkategorien (Oberbekleidung, Sportbekleidung, Schuhe, etc.) für eine E-Commerce-Seite handeln oder um Modellreihen eines Automobilherstellers. Während Bilderkennungsdienste in der Regel schon sehr viele Kategorien unterstützen, gibt es oft die Anforderung, spezielle kundenspezifische Kategorien erkennen zu können, die von generischen Diensten nicht unterschieden werden. Hier kann man einen fertig vorbereiteten Algorithmus mit eigenen Trainingsdaten füttern (bei denen man die Kategorisierung bereits vorgenommen und markiert hat, Stichwort „Labeling“), um ein Modell zu erhalten, das die vorgegebenen Bildkategorien erkennen kann.

Für gängige ML-Anwendungsfälle stellt Amazon SageMaker zurzeit vierzehn skalierbare Algorithmen zur Verfügung [4]. Von einfacher, linearer Regression über Clustering, Forecasting, Bildklassifikation und Objekterkennung bis hin zu Textanalysemethoden wird ein weiter Bereich von Anwendungen abgedeckt.

Hat man einen Algorithmus gefunden, der zur eigenen Anwendung passt, sollte man das passende Jupyter-Beispiel-Notebook öffnen und sich den Python-Code anschauen, der den gewählten Algorithmus anhand eines Beispieldatensatzes erklärt und eine Beispielanwendung vom Laden und Vorverarbeiten der Trainingsdaten über Training bis zum Betrieb des fertigen Modells veranschaulicht. Ein gutes Beispiel ist das „Image classification transfer learning demo“-Notebook [5], das aufzeigt, wie man ein Bildklassifikationsmodell mithilfe von 15 420 Beispielbildern aus 257 Kategorien trainieren und in Betrieb nehmen kann.

Tipp: Am einfachsten arbeitet man sich nacheinander durch die Zellen des Jupyter Notebook, wobei Codezellen per SHIFT + RETURN oder mit dem Run-Button gestartet werden können. Ein Sternchen oben links von der Zelle deutet an, dass der Code innerhalb der Zelle gerade läuft. Unter dem Codeteil der Zelle werden alle Ausgaben sowie etwaige Fehlermeldungen des Codes angezeigt.

Auf diese Weise helfen Jupyter Notebooks Entwicklern und Data Scientists, Codeschnipsel mit Kommentaren, Dokumentation oder Notizen zu mischen und dabei interaktiv Daten aufzubereiten, zu sichten und mit ihnen zu experimentieren. Bei den in SageMaker mitgelieferten Beispiel-Notebooks sollte man beachten, dass in der Regel in den Anfangszellen benutzerspezifische Parameter wie z. B. die Namen der zu verwendenden Amazon-S3-Buckets als Speicher für Eingangs- und Hilfsdaten einzutragen sind. Vergisst man, hier den eigenen S3-Bucket einzutragen, kann es im weiteren Verlauf zu Fehlermeldungen kommen (Abb. 3).

gonzales_sagemaker_3.tif_fmt1.jpgAbb. 3: Ein Jupyter Notebook mit Beispielcode für einen Bildklassifikationsalgorithmus

3. Schritt: Training

Je nach verwendetem Algorithmus unterscheidet man zwischen überwachtem (Supervised) und nicht überwachtem Lernen (Unsupervised Learning). Im ersten Fall übergibt man dem Algorithmus neben den Trainingsdaten auch sogenannte Labels, also Markierungen, die das richtige Ergebnis des zu lösenden Problems enthalten und dem Algorithmus helfen sollen, zu lernen, selbst auf die richtige Lösung zu kommen.

Beim Unsupervised Learning übergibt man dem Algorithmus lediglich die Trainingsdaten und der versucht dann, eigenständig Ergebnisse aus den Daten abzuleiten, wie z. B. Cluster zu bilden oder eine reduzierte Form der Daten zu erzeugen.

In beiden Fällen gilt: Ohne Daten kein Machine Learning! Daher sollte man in der Praxis eine gute Strategie für das Sammeln, Speichern und Verwalten großer Datenmengen haben. Hier fällt oft das Stichwort „Data Lake“, das eine Strategie beschreibt, mit der man alle Daten im Unternehmen zentral sammelt und für Big Data, Analyse- oder ML-Projekte einfach, performant und gemäß bestimmter Unternehmensrichtlinien zur Verfügung stellt. Auch hierfür stellen Cloud-Anbieter wie Amazon Web Services Werkzeuge bereit.

Für Entwicklungs- und Testzwecke gibt es verschiedene offen zugängliche Quellen mit Datensätzen, die für Machine Learning genutzt werden können. Beispiele hierfür sind https://www.kaggle.com [6], eine Webseite, die auch regelmäßig ML-Wettbewerbe ausrichtet, sowie die Open Data Registry des AWS Public Dataset Program. Je nach Algorithmus verwenden die Amazon SageMaker Beispiel-Notebooks öffentlich zugängliche Trainingsdaten bzw. die nötigen Labels, oder sie enthalten Code, der passende Beispieldaten generiert, sodass man die jeweiligen Algorithmen auch ohne eigene Daten ausprobieren kann.

Das Trainieren eines Algorithmus in Amazon SageMaker folgt aus Entwicklersicht einem einfachen Muster: Die Trainings- bzw. Labeldaten werden geladen, aufbereitet und in ein Amazon S3 Bucket hochgeladen.

In einer Datenstruktur werden alle für das Training benötigten Parameter wie z. B. Bucket-Name und Pfadpräfix für die Trainings- und Labeldaten in S3, der gewählte Docker-Container mit dem Trainingsalgorithmus, Instanztyp und -anzahl der Trainingsinstanzen sowie algorithmusspezifische Parameter gesammelt.

Über einen API Call werden die Trainingsparameter an Amazon SageMaker übergeben und der Trainingsjob gestartet.

Mithilfe weiterer API Calls oder über die AWS-Konsole kann der Fortschritt des Trainingsjobs beobachtet werden. Status- und Fehlerausgaben des Trainingsalgorithmus werden über den hauseigenen Logging-Dienst Amazon CloudWatch Logs zur Verfügung gestellt und können mitverfolgt werden.

Nach Abschluss des Trainingsjobs wird das fertige ML-Modell im Amazon S3 Bucket des Benutzers hochgeladen.

Hinter den Kulissen kümmert sich Amazon SageMaker um alle Infrastrukturmaßnahmen, die für das Training benötigt werden. Dazu gehören das Starten der EC2-Instanzen für die Trainingsflotte, die Installation und das Patching der benötigten Software, das Starten von Docker-Containern für Training und Hilfsarbeiten, das Laden der Trainingsdaten in die Docker-Umgebung, die Koordination von Zuständen und Zwischenergebnissen zwischen Trainingsinstanzen sowie das Abspeichern des resultierenden Modellartefakts.

Nicht immer verläuft das Trainieren eines ML-Algorithmus nach Plan. Je nach Algorithmus, gewählter Parameter oder Qualität der Trainingsdaten kann es passieren, dass ein Modell lediglich „auswendig lernt“, wie es die Trainingsdaten erkennen kann, aber in der Praxis mit neuen Daten nicht zurechtkommt.

Um solches „Overfitting“ zu erkennen, aber auch um einen neutralen Eindruck von der Qualität des resultierenden Modells zu bekommen, ist es üblich, im Vorfeld die Eingangsdaten samt Labels nach dem Zufallsprinzip in Trainings- und Validierungsdaten aufzuteilen. Das wird von SageMaker unterstützt, indem es während des Trainingsvorgangs periodisch das Modell gegen die Validierungsdaten testet und daraus ein Präzisionsmaß berechnet (Abb. 4).

gonzales_sagemaker_4.tif_fmt1.jpgAbb. 4: Amazon-SageMaker-Trainingsdashboard

Hyper, hyper! Automatisches Tuning von Trainingsparametern

In der Regel bieten ML-Algorithmen über sogenannte Hyperparameter die Möglichkeit, den Trainingsverlauf sowie den genaueren Aufbau des Modells zu beeinflussen. Das kann sich erheblich auf die Qualität des Modells und die Dauer (und damit die Kosten) des Trainingsprozesses auswirken. Typische Hyperparameter sind etwa die Anzahl von Schichten in einem neuronalen Netz, die Lernrate (wie schnell passt sich der Algorithmus an Trainingsdaten an) oder die Batch-Größe, also die Anzahl der Trainingsdatensätze, die pro Lernschritt gemeinsam verarbeitet werden.

Daher verbringen Data Scientists oft viel Zeit damit, verschiedene Hyperparameter auszuprobieren und ihren Einfluss auf die resultierende Modellpräzision zu verstehen. Beispielsweise kann eine zu große Lernrate verhindern, dass sich der Algorithmus einer optimalen Lösung annähert, während eine zu kleine Lernrate dazu führt, dass der Algorithmus mehr Durchläufe benötigt, um ein optimales Ergebnis zu liefern, was längere Laufzeiten und höhere Kosten verursacht. Je mehr Hyperparameter zur Auswahl stehen, desto aufwendiger und schwieriger wird dieser Prozess, der häufig mehr Kunst als Wissenschaft ist.

Hierfür gibt es seit Kurzem eine automatische Hyperparametertuningoption in Amazon SageMaker: Anwender können hier für jeden Hyperparameter Ober- und Untergrenzen vorgeben, innerhalb derer die automatische Optimierungslogik versucht, möglichst effizient die richtigen Werte zu finden. Auch hierbei kommen Verfahren des Machine Learning zum Einsatz: SageMaker probiert dabei verschiedene Kombinationen von Hyperparametern aus und bewertet ihre Ergebnisse anhand einer benutzerdefinierten Metrik. Über mehrere Zyklen dieser Art und durch Einstreuen von Parametern, die weit weg von bekannten Konfigurationen liegen, lotet SageMaker den Hyperparametersuchraum aus, um darin eine möglichst gute Kombination zu finden. So können Maschinen lernen, ihre eigenen Maschinenlernprozesse zu optimieren.

Amazon-SageMaker-Algorithmen im Überblick

  • Linear Learner: Lineare Regression errechnet einen Zielwert auf Basis beliebig vieler Attribute voraus. Typischer Anwendungsfall: Vorausschauende Wartung (Predictive Maintenance), bei der aufgrund von gemessenen Attributen (z. B. Sensordaten) vorausgesagt werden soll, ob ein Bauteil demnächst ausgetauscht werden sollte, bevor es ausfällt. Weiteres Beispiel: Abschätzung der erwarteten monatlichen Miete einer Immobilie basierend auf Attributen wie qm-Größe, Anzahl der Zimmer, Postleitzahlenbereich, Stockwerk etc.

  • Factorization Machines: Während man bei der linearen Regression annimmt, dass alle Eingangswerte unabhängig voneinander sind, berücksichtigen Faktorisierungsmaschinen paarweise Abhängigkeiten zwischen zwei Attributen. Beispiel: Für eine Webseite soll vorausgesagt werden, ob ein Besucher auf eine Anzeige klicken wird, die zu einer bestimmten Anzeigenkategorie gehört (erster Faktor) und auf einer bestimmten Seitenkategorie auftaucht (zweiter Faktor). Je nach Paar aus Anzeigen- und Seitenkategorie können sich andere Voraussagen für die Klickwahrscheinlichkeit ergeben, die durch dieses Modell erfasst werden.

  • XGBoost: Der Algorithmus „Gradient Boosted Trees“ kombiniert die Ergebnisse vieler einfacher, schwächerer Modelle zu einem Gesamtergebnis, das deutlich besser ist als die Ergebnisse der einzelnen Modelle. XGBoost ist eine populäre Open-Source-Implementierung dieses Ansatzes.

  • Image Classification Algorithm: Bildklassifikation basierend auf einem Convolutional Neural Network (CNN) basierend auf der ResNet-Architektur. Er unterstützt sowohl ein komplettes Training von Grund auf als auch ein Training nach dem Transfer-Learning-Prinzip, bei dem das Netzwerk bereits auf dem ImageNet-Datensatz mit 11 Millionen Bildern in 11 000 Kategorien vortrainiert wurde und nur noch auf anwendungsspezifische Bilder angepasst werden muss. Dieser Ansatz kommt mit deutlich weniger Trainingsbildern aus, weil allgemeine Eigenschaften von Bildern bereits erlernt wurden.

  • Object Detection Algorithm: Während der vorangegangene Algorithmus jedes Bild einer einzelnen Kategorie zuordnet, identifiziert dieser Algorithmus ein oder mehrere Objekte aus mehreren Kategorien im Bild und gibt deren Position, Kategorie und Größe zurück.

  • Sequence to Sequence (seq2seq): Ein Übersetzungsalgorithmus, der aus einer Sequenz von Symbolen eine andere Sequenz von Symbolen erzeugt. Neben Übersetzung von Sprache kann dieser Algorithmus auch für die Zusammenfassung von Texten oder die Sprachausgabe trainiert werden.

  • K-Means Clustering: Identifiziert k-Gruppen (Cluster) von Datenpunkten, die möglichst nah beieinander und möglichst weit entfernt von anderen Clustern sind. k ist hier eine vorgegebene Anzahl von zu findenden Clustern.

  • Principal Component Analysis: Häufig bestehen Daten für ML-Anwendungen aus sehr vielen (Hunderten oder Tausenden) verschiedenen Attributen pro Datenpunkt, die die Rechen- und Speicheranforderungen für ML-Anwendungen stark erhöhen können. Dieser Algorithmus versucht, aus den gegebenen Daten eine kleinere Anzahl von möglichst voneinander unabhängigen Attributen zu extrahieren (sogenannte Komponenten), die so viele Informationen wie möglich enthalten und nach absteigendem Informationsgehalt sortiert sind. Als Vorstufe für andere Algorithmen geeignet, um Rechenaufwand und damit -kosten zu sparen.

  • Latent Dirichlet Allocation (LDA): Dieser Algorithmus extrahiert Themen aus Textdokumenten basierend auf der statistischen Häufung der darin enthaltenen Wörter.

  • Neural Topic Modeling (NTM): Ähnlich wie LDA extrahiert auch dieses Verfahren Themen aus Textdokumenten, verwendet dabei aber einen Ansatz basierend auf neuronalen Netzen. Die Ergebnisse von LDA und NTM können je nach Anwendungsgebiet unterschiedlich ausfallen, sodass man in der Praxis ausprobieren sollte, welcher Algorithmus für die jeweilige Anwendung besser funktioniert.

  • DeepAR Forecasting: Dieser Algorithmus verwendet ein neuronales Netz mit Gedächtniszellen (Long Short-term Memory Network, LSTM), um Datenreihen (z. B. Zeitreihen oder Sätze in Texten) vorauszusagen. Typische Anwendungsfälle sind Vorhersagen für Rohstoffe und Waren in Lieferketten (Supply Chain Optimization).

  • BlazingText: Bei der Verarbeitung von Texten in ML-Systemen muss man sie vorher in Zahlenreihen umwandeln (vektorisieren). Einfache, sogenannte Bag-of-Words-Verfahren hierfür sind sehr speicheraufwendig, weil sie sehr lange Vektoren ergeben, bei denen nur ein Wert für das jeweilige Wort aus einem Wörterbuch gleich 1 ist und alle anderen Werte auf 0 gesetzt werden. Sogenannte Word2Vec-Verfahren wie BlazingText ordnen Wörter einem Vektor mit einer vorgegebenen Länge zu, sodass Wörter, die in ähnlichen Nachbarschaften auftreten (z. B. „Berlin“ und „Paris“) ähnliche Vektoren ergeben und sich semantische Beziehungen zwischen Wörtern einstellen können (z. B.: „Berlin“ – „Deutschland“ + „Frankreich“ ≈ „Paris“.

  • Random Cut Forest: Oft sind Datensätze nicht immer „sauber“, sondern es treten Außenseiter oder Anomalien auf. Dieser Algorithmus weist jedem Datensatz einen Anomaliewert zu, der beschreibt, wie „normal“ bzw. „ungewöhnlich“ er ist. So können Daten für eine spätere Verwendung entweder gesäubert werden, um unnötige Komplexität in späteren Schritten zu sparen, oder es können Ausreißer in Datensätzen (z. B. Fehler oder unerwartete Ereignisse) gefunden werden.

  • K-Nearest Neighbors: Dieser Algorithmus sucht die k am nächsten liegenden Punkte im Datenraum und liefert die dabei am häufigsten gefundene Kategorie (Klassifikationsmodus) bzw. den Mittelwert daraus (Regressionsmodus).

4. Schritt: Überführung des ML-Modells in Produktion

Zum Schluss soll das Machine-Learning-Modell zeigen, was es kann, und in einer Produktivumgebung seinen Dienst verrichten. Dabei schickt man die zu verarbeitenden Daten über ein API an das Modell, das daraus ein Ergebnis gemäß der gelernten Parameter ableitet (Inferenz). Die Einrichtung einer Produktionsumgebung („Deployment“) für das gelernte Modell verläuft ähnlich wie das Training:

In einer Datenstruktur werden alle für das Deployment benötigten Parameter wie z. B. Bucket-Name und Pfadpräfix für das trainierte Modellartefakt in Amazon S3, der gewählte Docker-Container mit dem Inferenzcode, der gewählte Instanztyp für die Hostinginstanzen etc. gesammelt.

Über einen API Call werden die Hostingparameter an Amazon SageMaker übergeben und daraus ein SageMaker-Modellobjekt sowie ein RESTful-Endpunkt für den Zugang erzeugt.

Sobald der Endpunkt für das Modell verfügbar ist, kann er mithilfe einer REST-Schnittstelle angesprochen werden. Für die Entwicklung von Clientapplikationen sind Software Development Kits in verschiedenen Programmiersprachen erhältlich.

Auch hier nimmt SageMaker dem Anwender Arbeit ab – dazu gehört der Betrieb der EC2-Instanzen für das Hosting, die Verwaltung der Container mit den Inferenzalgorithmen und den Hilfsfunktionen, der Betrieb des API-Endpunkts sowie die Bereitstellung von Monitoring- und Logdaten für die Überwachung der Infrastruktur und die Fehlersuche. Fortgeschrittene Anwender können unterschiedliche Modelle gleichzeitig betreiben und ihre jeweiligen Anteile an der Verarbeitung von Anfragen prozentual gewichten. Dadurch ergibt sich die Möglichkeit, Modellupdates anfänglich nur an einem kleinen Teil der Arbeitslast zu testen (sogenannte Canary-Tests) oder A/B-Tests zwischen unterschiedlichen Modellen durchzuführen.

Wer bisher die Schritte im Jupyter-Beispiel-Notebook vollzogen hat, kann auch für das Deployment die vorbereiteten Zellen mit dem passenden Code ausführen, um das Modell auszurollen und anhand von Beispieldaten zu testen. Im Fall des hier betrachteten Bildklassifikations-Notebook kann man beispielsweise Bilder aus dem Internet mit dem gelernten Modell ausprobieren, um zu sehen, welche Kategorie dem Bild durch das Modell zugeordnet wird (Abb. 5).

gonzales_sagemaker_5.tif_fmt1.jpgAbb. 5: Inferenz: Es ist ein Hund! (Quelle: https://unsplash.com)

Sicherheit mit Authentifizierung, Autorisierung und Auditing

Das AWS Identity and Access Management System (IAM) bietet über User, Rollen und Policies die Möglichkeit, feingranulare Berechtigungen an menschliche Benutzer sowie technische Benutzer (z. B. EC2-Instanzen, Identity-Federation-Nutzer, andere AWS Services) zu vergeben. Diese werden durch Amazon SageMaker unterstützt, sodass der gesamte Datenfluss und der Zugriff auf API-Endpunkte sowohl für SageMaker selbst als auch für die daraus resultierenden Modellendpunkte kontrolliert, überwacht und über AWS CloudTrail auditiert werden kann.

Auch Verschlüsselung von Trainings- und Modelldaten ist umsetzbar: Amazon SageMaker unterstützt die Verschlüsselung von Daten sowohl beim Transport als auch bei der Speicherung. Dabei kann wahlweise der AWS Key Management Service (KMS) mit automatisch erzeugten oder eigenen importierten Schlüsseln verwendet werden.

Nutzer von Amazon Virtual Private Cloud (VPC) für die Abschottung eigener Cloud-Ressourcen in einem eigenen, privaten Netzwerk können Amazon SageMaker ebenfalls aus ihrer VPC-Umgebung heraus nutzen und haben daher die Möglichkeit, Trainings- und Modelldaten so zu speichern, dass sie nur aus dem VPC heraus zugänglich sind.

Skalierbare Datenaufbereitung mit Apache Spark und Amazon SageMaker

Häufig reicht ein einzelner Rechner mit Notebook-Umgebung oder der eigene Laptop nicht aus, um größere Mengen an Trainingsdaten aufzubereiten, bzw. um in der Produktion eine automatisierte Datenaufbereitung zur Verfügung zu stellen. Hier haben sich in der Praxis Lösungen auf Basis von Apache Spark bewährt. SageMaker bietet hierfür eine Brücke in Form einer Apache Spark Library, die es ermöglicht, aus einer Spark-Anwendung heraus Daten an Amazon SageMaker für das Training weiterzuleiten, sowie die Modelle daraus in Amazon SageMaker zu verwalten [7]. Wer innerhalb von AWS Spark nutzen möchte, kann das mithilfe des Amazon EMR Service tun, der das Management des Spark-Clusters auf Basis von Hadoop automatisiert.

Automatische Skalierung

Je nach Anwendung kann es sein, dass ein ML-Modell schwankenden Lasten ausgesetzt wird oder eine besonders hohe Anzahl von Clients bedienen muss, die über die Leistung einer einzelnen virtuellen Maschine hinausgehen. Hierfür bietet SageMaker die Möglichkeit, die Anzahl der virtuellen Maschinen im Hintergrund automatisch nach Auslastung zu skalieren. Dabei können Anwender die minimale und die maximale Anzahl von Instanzen sowie die Parameter und die zugrundeliegenden Metriken für das Hoch- und Runterskalieren einstellen.

Amazon SageMaker für die Erweiterung eigener Algorithmen oder Deep-Learning-Netze

Neben traditionellen Machine-Learning-Algorithmen wie linearer Regression, K-Means-Clustering oder Random Cut Forest erfreuen sich neuronale Netze steigender Beliebtheit. Grund dafür ist die Kombination aus hoher Rechenkapazität für Matrixoperationen durch die Verwendung von GPUs sowie die Verfügbarkeit leistungsfähiger und hochoptimierter Programmierframeworks with TensorFlow, MXNet oder PyTorch.

Wer eigene neuronale Netze für Deep-Learning-Anwendungen trainieren und betreiben möchte, kann in Amazon SageMaker speziell dafür vorbereitete Container verwenden [8]. Diese bieten Python-basierte Schnittstellen an, in die man nur noch seine eigenen Skripte für die gängigen Deep-Learning-Frameworks einbauen muss. Unterstützt werden derzeit TensorFlow, Apache MXNet, Chainer und PyTorch. Auch verwandte Frameworks wie Keras oder Gluon, die auf diesen Libraries aufbauen, werden unterstützt.

Wer jenseits von Deep Learning Eigenentwicklungen oder bisher nicht direkt unterstützte Frameworks in beliebigen Programmiersprachen mit SageMaker nutzen möchte, kann den eigenen Code mithilfe eines vorgegebenen Dockerfile in einen Docker-Container einbetten und damit die volle Kontrolle über den ML-Algorithmus und die Implementierung für die Inferenz von Modellen übernehmen. Sowohl für Deep-Learning-Anwendungen als auch für das Einbetten eigener Algorithmen bieten die Dokumentation [9] bzw. die mitgelieferten Jupyter-Notebook-Beispiele nützliche Hinweise und Codeschnipsel.

Fazit

Der Machine-Learning-Zyklus startet mit der Sammlung, Säuberung und Aufbereitung von Trainingsdaten und ggfs. Labelinformationen. Diese werden einem Machine-Learning-Algorithmus zugeführt, der aus diesen Daten ein Modell erzeugt, das Regeln und Muster aus den Daten extrahiert und generalisiert. Solche Modelle können dann in der Praxis ausgerollt werden, um aus neuen Daten Erkenntnisse wie z. B. Klassifizierungen oder Vorhersagen abzuleiten.

Amazon SageMaker ist eine Werkbank, die mithilfe von Containertechnologie diesen Zyklus abbildet und dem Entwickler oder Data Scientist die Verwaltung der darunterliegenden IT-Ressourcen abnimmt. Automatisiert gehostete Jupyter Notebooks mit vielen Beispielen tragen weiter zur Arbeitserleichterung bei, während fortgeschrittene Funktionen wie Hyperparametertuning oder automatische Skalierung weitere Vorteile bieten.

gonzalez_constantin_sw.tif_fmt1.jpgConstantin Gonzalez beschäftigt sich seit über zwanzig Jahren mit IT-Technologien: von CPU-, Speicher- und Systemarchitekturen über Betriebssysteme und Webtechologien bis zu Cloud-Computing, Microservices, Internet der Dinge und künstlicher Intelligenz. Bei Amazon Web Services unterstützt er als Principal Solutions Architect Kunden, von Start-ups bis zu globalen Unternehmen, dabei, mit Cloud-Computing effizienter zu sein und digitale Innovationen voranzutreiben.

Desktop Tablet Mobile
Desktop Tablet Mobile