Geschriebene Artikel über Big Data Analytics

Fehler-Rückführung mit der Backpropagation

Dies ist Artikel 4 von 6 der Artikelserie –Einstieg in Deep Learning.

Das Gradienten(abstiegs)verfahren ist der Schlüssel zum Training einzelner Neuronen bzw. deren Gewichtungen zu den Neuronen der vorherigen Schicht. Wer dieses Prinzip verstanden hat, hat bereits die halbe Miete zum Verständnis des Trainings von künstlichen neuronalen Netzen.

Der Gradientenabstieg wird häufig fälschlicherweise mit der Backpropagation gleichgesetzt, jedoch ist das nicht ganz richtig, denn die Backpropagation ist mehr als die Anwendung des Gradientenabstiegs.

Bevor wir die Backpropagation erläutern, nochmal kurz zurück zur Forward-Propagation, die die eigentliche Prädiktion über ein künstliches neuronales Netz darstellt:

Forward-Propagation

Abbildung 1: Ein simples kleines künstliches neuronales Netz mit zwei Schichten (+ Eingabeschicht) und zwei Neuronen pro Schicht.

In einem kleinen künstlichen neuronalen Netz, wie es in der Abbildung 1 dargestellt ist, und das alle Neuronen über die Sigmoid-Funktion aktiviert, wird jedes Neuron eine Nettoeingabe z berechnen…

z = w^{T} \cdot x

… und diese Nettoeingabe in die Sigmoid-Funktion einspeisen…

\phi(z) = sigmoid(z) = \frac{1}{1 + e^{-z}}

… die dann das einzelne Neuron aktiviert. Die Aktivierung erfolgt also in der mittleren Schicht (N-Schicht) wie folgt:

N_{j} = \frac{1}{1 + e^{- \sum (w_{ij} \cdot x_{i}) }}

Die beiden Aktivierungsausgaben N werden dann als Berechnungsgrundlage für die Ausgaben der Ausgabeschicht o verwendet. Auch die Ausgabe-Neuronen berechnen ihre jeweilige Nettoeingabe z und aktivieren über Sigmoid(z).

Ausgabe eines Ausgabeknotens als Funktion der Eingänge und der Verknüpfungsgewichte für ein dreischichtiges neuronales Netz, mit nur zwei Knoten je Schicht, kann also wie folgt zusammen gefasst werden:

O_{k} = \frac{1}{1 + e^{- \sum (w_{jk} \cdot \frac{1}{1 + e^{- \sum (w_{ij} \cdot x_{i}) }}) }}

Abbildung 2: Forward-Propagation. Aktivierung via Sigmoid-Funktion.

Sollte dies die erste Forward-Propagation gewesen sein, wird der Output noch nicht auf den Input abgestimmt sein. Diese Abstimmung erfolgt in Form der Gewichtsanpassung im Training des neuronalen Netzes, über die zuvor erwähnte Gradientenmethode. Die Gradientenmethode ist jedoch von einem Fehler abhängig. Diesen Fehler zu bestimmen und durch das Netz zurück zu führen, das ist die Backpropagation.

Back-Propagation

Um die Gewichte entgegen des Fehlers anpassen zu können, benötigen wir einen möglichst exakten Fehler als Eingabe. Der Fehler berechnet sich an der Ausgabeschicht über eine Fehlerfunktion (Loss Function), beispielsweise über den MSE (Mean Squared Error) oder über die sogenannte Kreuzentropie (Cross Entropy). Lassen wir es in diesem Beispiel einfach bei einem simplen Vergleich zwischen dem realen Wert (Sollwert o_{real}) und der Prädiktion (Ausgabe o) bleiben:

e_{o} = o_{real} - o

Der Fehler e ist also einfach der Unterschied zwischen dem Ziel-Wert und der Prädiktion. Jedes Training ist eine Wiederholung von Prädiktion (Forward) und Gewichtsanpassung (Back). Im ersten Schritt werden üblicherweise die Gewichtungen zufällig gesetzt, jede Gewichtung unterschiedlich nach Zufallszahl. So ist die Wahrscheinlichkeit, gleich zu Beginn die “richtigen” Gewichtungen gefunden zu haben auch bei kleinen neuronalen Netzen verschwindend gering. Der Fehler wird also groß sein und kann über den Gradientenabstieg durch Gewichtsanpassung verkleinert werden.

In diesem Beispiel berechnen wir die Fehler e_{1} und e_{2} und passen danach die Gewichte w_{j,k} (w_{1,1} & w_{2,1} und w_{1,2} & w_{2,2}) der Schicht zwischen dem Hidden-Layer N und dem Output-Layer o an.

Abbildung 3: Anpassung der Gewichtungen basierend auf dem Fehler in der Ausgabe-Schicht.

Die Frage ist nun, wie die Gewichte zwischen dem Input-Layer X und dem Hidden-Layer N anzupassen sind. Es stellt sich die Frage, welchen Einfluss diese auf die Fehler in der Ausgabe-Schicht haben?

Um diese Gewichtungen anpassen zu können, benötigen wir den Fehler-Anteil der beiden Neuronen N_{1} und N_{2}. Dieser Anteil am Fehler der jeweiligen Neuronen ergibt sich direkt aus den Gewichtungen w_{j,k} zum Output-Layer:

e_{N_{1}} = e_{o1} \cdot \frac{w_{1,1}}{w_{1,1} + w_{1,2}} + e_{o2} \cdot \frac{w_{1,2}}{w_{1,1} + w_{1,2}}

e_{N_{2}} = e_{o1} \cdot \frac{w_{2,1}}{w_{2,1} + w_{2,2}} + e_{o2} \cdot \frac{w_{2,2}}{w_{2,1} + w_{2,2}}

Wenn man das nun generalisiert:

    \[ e_{N} = \left(\begin{array}{rr} \frac{w_{1,1}}{w_{1,1} + w_{1,2}} & \frac{w_{1,2}}{w_{1,1} + w_{1,2}} \\ \frac{w_{2,1}}{w_{2,1} + w_{2,2}} & \frac{w_{2,2}}{w_{2,1} + w_{2,2}} \end{array}\right) \cdot \left(\begin{array}{c} e_{1} \\ e_{2} \end{array}\right) \qquad \]

Dabei ist es recht aufwändig, die Gewichtungen stets ins Verhältnis zu setzen. Diese Berechnung können wir verkürzen, indem ganz einfach direkt nur die Gewichtungen ohne Relativierung zur Kalkulation des Fehleranteils benutzt werden. Die Relationen bleiben dabei erhalten!

    \[ e_{N} = \left(\begin{array}{rr} w_{1,1} & w_{1,2} \\ w_{2,1} & w_{2,2} \end{array}\right) \cdot \left(\begin{array}{c} e_{1} \\ e_{2} \end{array}\right) \qquad \]

Oder folglich in Kurzform: e_{N} = w^{T} \cdot e_{o}

Abbildung 4: Vollständige Gewichtsanpassung auf Basis der Fehler in der Ausgabeschicht und der Fehleranteile in der verborgenden Schicht.

Und nun können, basierend auf den Fehleranteilen der verborgenden Schicht N, die Gewichtungen w_{i,j} zwischen der Eingabe-Schicht I und der verborgenden Schicht N angepasst werden, entgegen dieser Fehler e_{N}.

Die Backpropagation besteht demnach aus zwei Schritten:

  1. Fehler-Berechnung durch Abgleich der Soll-Werte mit den Prädiktionen in der Ausgabeschicht und durch Fehler-Rückführung zu den Neuronen der verborgenden Schichten (Hidden-Layer)
  2. Anpassung der Gewichte entgegen des Gradientenanstiegs der Fehlerfunktion (Loss Function)

Buchempfehlungen

Die folgenden zwei Bücher haben mir sehr beim Verständnis und beim Verständlichmachen der Backpropagation in künstlichen neuronalen Netzen geholfen.

Neuronale Netze selbst programmieren: Ein verständlicher Einstieg mit Python Deep Learning. Das umfassende Handbuch: Grundlagen, aktuelle Verfahren und Algorithmen, neue Forschungsansätze (mitp Professional)

Cross-industry standard process for data mining

Introduced in 1996, the cross-industry standard process for data mining (CRISP-DM) became the most
common procedure for all data mining projects. This method consists of six phases: Business
understanding, Data understanding, Data preparation, Modeling, Evaluation and Deployment (see
Figure 1). It is being used not just as a reference manual but as a user guide as it explains every phase
in detail (Hipp, 2000). The six phases of this model are explained below:

Figure 1: Different phases of CRISP-DM

Business Understanding

It includes understanding the business problem and determining the
objective of the business as well as of the project. It is also important to understand the previous work
done on the project (if any) to achieve the business goals and to examine if the scope of the project has changed.

The job of a Data Scientist is not limited to coding or just make a machine learning model and I guess that’s why this whole lifecycle was developed.  The key points a project owner should take care in this process are:

– Identify stakeholders  and involve them to define the scope your project
– Describe your product (your machine learning model)
– Identify how your product ties into the client’s business processes
– Identify metrics / KPIs for measuring success

Evaluating a model is a different thing as it can only tell you how good are your predictions but identifying the success metric is really important for any data science project because when your model is deployed in production this measure will tell you if your model actually works or not. Now, let’s discuss what is this success metric
Consider that you are working in an e-commerce company where Head of finance ask you to create a machine learning model to predict if a specific product will return or not. The problem is not hard to understand, its a binary classification problem and you know you can do the job. But before you start working with the data you should define a metric to measure the success. What do you think your success metric could be? I would go with the return rate, in other words, calculate the rate for how many orders are actually coming back and if this measure is getting decrease you would know your model works and if not then FIX IT !!

Data understanding

The initial step in this phase is to gather all the data from different sources. It is
then important to describe the data, generate graphs for distribution in order to get familiar with the
data. This phase is important as without enough data or without understanding about the data analysis
cannot be performed. In data mining terms this can be compared to Exploratory data analysis (EDA)
where techniques from descriptive statistics are used to have an insight into the data. For instance, if it is
a time series data it makes sense to know from when until when the data is available before diving deep into
the data.

Data preparation

This phase takes most of the time in data mining project as a lot of methods from
data cleaning, feature subset, feature engineering, the transformation of data etc. are used before the final
dataset is trained for modeling purpose. The single dataset can also be prepared in different forms as some
algorithms can learn more with a certain type of data, some algorithms can deal with imbalance dataset
and for some algorithms, the target variable must be balanced. This phase also requires sometimes to
calculate new KPI’s according to the business need or sometimes to reduce the dimension of the dataset.

Modeling and Evaluation

Various models are selected and build in this process and appropriate hyperparameters are
selected after an intensive grid search.  Once all the models are built it is now time to evaluate and compare performances of all the models.

Deployment

A model is of no use if it is not deployed into production. Until now you have been doing the job of a data scientist but for deployment, you need some software engineering

skills. There are several ways to deploy a machine learning model or python code. Few of them are:

  • Re-implement your python code in C++, Java etc. (LOL)
  • Save the coefficients and use them to get predictions
  • Serialized objects (REST API with flask, Django)

To understand the concept of deploying an ML model using REST API this post is highly recommended.

Training eines Neurons mit dem Gradientenverfahren

Dies ist Artikel 3 von 6 der Artikelserie –Einstieg in Deep Learning.

Das Training von neuronalen Netzen erfolgt nach der Forward-Propagation über zwei Schritte:

  1. Fehler-Rückführung über aller aktiver Neuronen aller Netz-Schichten, so dass jedes Neuron “seinen” Einfluss auf den Ausgabefehler kennt.
  2. Anpassung der Gewichte entgegen den Gradienten der Fehlerfunktion

Beide Schritte werden in der Regel zusammen als Backpropagation bezeichnet. Machen wir erstmal einen Schritt vor und betrachten wir, wie ein Neuron seine Gewichtsverbindungen zu seinen Vorgängern anpasst.

Gradientenabstiegsverfahren

Der Gradientenabstieg ist ein generalisierbarer Algorithmus zur Optimierung, der in vielen Verfahren des maschinellen Lernens zur Anwendung kommt, jedoch ganz besonders als sogenannte Backpropagation im Deep Learning den Erfolg der künstlichen neuronalen Netze erst möglich machen konnte.

Der Gradientenabstieg lässt sich vom Prinzip her leicht erklären: Angenommen, man stünde im Gebirge im dichten Nebel. Das Tal, und somit der Weg nach Hause, ist vom Nebel verdeckt. Wohin laufen wir? Wir können das Ziel zwar nicht sehen, tasten uns jedoch so heran, dass unser Gehirn den Gradienten (den Unterschied der Höhen beider Füße) berechnet, somit die Steigung des Bodens kennt und sich entgegen dieser Steigung unser Weg fortsetzt.

Konkret funktioniert der Gradientenabstieg so: Wir starten bei einem zufälligen Theta \theta (Random Initialization). Wir berechnen die Ausgabe (Forwardpropogation) und vergleichen sie über eine Verlustfunktion (z. B. über die Funktion Mean Squared Error) mit dem tatsächlich korrekten Wert. Auf Grund der zufälligen Initialisierung haben wir eine nahe zu garantierte Falschheit der Ergebnisse und somit einen Verlust. Für die Verlustfunktion berechnen wir den Gradienten für gegebene Eingabewerte. Voraussetzung dafür ist, dass die Funktion ableitbar ist. Wir bewegen uns entgegen des Gradienten in Richtung Minimum der Verlustfunktion. Ist dieses Minimum (fast) gefunden, spricht man auch davon, dass der Lernalgorithmus konvergiert.

Das Gradientenabstiegsverfahren ist eine Möglichkeit der Gradientenverfahren, denn wollten wir maximieren, würden wir uns entlang des Gradienten bewegen, was in anderen Anwendungen sinnvoll ist.

Ob als “Cost Function” oder als “Loss Function” bezeichnet, in jedem Fall ist es eine “Error Function”, aber auf die Benennung kommen wir später zu sprechen. Jedenfalls versuchen wir die Fehlerrate zu senken! Leider sind diese Funktionen in der Praxis selten so einfach konvex (zwei Berge mit einem Tal dazwischen).

 

Aber Achtung: Denn befinden wir uns nur zwischen zwei Bergen, finden wir das Tal mit Sicherheit über den Gradienten. Befinden wir uns jedoch in einem richtigen Gebirge mit vielen Bergen und Tälern, gilt es, das richtige Tal zu finden. Bei der Optimierung der Gewichtungen von künstlichen neuronalen Netzen wollen wir die besten Gewichtungen finden, die uns zu den geringsten Ausgaben der Verlustfunktion führen. Wir suchen also das globale Minimum unter den vielen (lokalen) Minima.

Programmier-Beispiel in Python

Nachfolgend ein Beispiel des Gradientenverfahrens zur Berechnung einer Regression. Wir importieren numpy und matplotlib.pyplot und erzeugen uns künstliche Datenpunkte:

Nun wollen wir einen Lernalgorithmus über das Gradientenverfahren erstellen. Im Grunde haben wir hier es bereits mit einem linear aktivierten Neuron zutun:

Bei der linearen Regression, die wir durchführen wollen, nehmen wir zwei-dimensionale Daten (wobei wir die Regression prinzipiell auch mit x-Dimensionen durchführen können, dann hätte unser Neuron weitere Eingänge). Wir empfangen einen Bias (w_0) der stets mit einer Eingangskonstante multipliziert und somit als Wert erhalten bleibt. Der Bias ist das Alpha \alpha in einer Schulmathe-tauglichen Formel wie y = \beta \cdot x + \alpha.

Beta \beta ist die Steigung, der Gradient, der Funktion.

Sowohl \alpha als auch \beta sind uns unbekannt, versuchen wir jedoch über die Betrachtung unserer Prädiktion durch Berechnung der Formel \^y = \beta \cdot x + \alpha und den darauffolgenden Abgleich mit dem tatsächlichen y herauszufinden. Anfangs behaupten wir beispielsweise einfach, sowohl \beta als auch \alpha seien 0.00. Folglich wird \^y = \beta \cdot x + \alpha ebenfalls gleich 0.00 sein und die Fehlerfunktion (Loss Function) wird maximal sein. Dies war der erste Durchlauf des Trainings, die sogenannte erste Epoche!

Die Epochen (Durchläufe) und dazugehörige Fehlergrößen. Wenn die Fehler sinken und mit weiteren Epochen nicht mehr wesentlich besser werden, heißt es, das der Lernalogorithmus konvergiert.

Als Fehlerfunktion verwenden wir bei der Regression die MSE-Funktion (Mean Squared Error):

MSE = \sum(\^y_i - y_i)^2

Um diese Funktion wird sich nun alles drehen, denn diese beschreibt den Fehler und gibt uns auch die Auskunft darüber, ob wie stark und in welche Richtung sie ansteigt, so dass wir uns entgegen der Steigung bewegen können. Wer die Regeln der Ableitung im Kopf hat, weiß, dass die Ableitung der Formel leichter wird, wenn wir sie vorher auf halbe Werte runterskalieren. Da die Proportionen dabei erhalten bleiben und uns quadrierte Fehlerwerte unserem menschlichen Verstand sowieso nicht so viel sagen (unser Gehirn denkt nunmal nicht exponential), stört das nicht:

MSE = \frac{\frac{1}{2} \cdot \sum(\^y_i - y_i)^2}{n}

MSE = \frac{\frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2}{n}

Wenn die Mathematik der partiellen Ableitung (Ableitung einer Funktion nach jedem Gradienten) abhanden gekommen ist, bitte nochmal folgende Regeln nachschlagen, um die nachfolgende Ableitung verstehen zu können:

  • Allgemeine partielle Ableitung
  • Kettenregel

Ableitung der MSD-Funktion nach dem einen Gewicht w bzw. partiell nach jedem vorhandenen w_j:

\frac{\partial}{\partial w_j}MSE = \frac{\partial}{\partial w} \frac{1}{2} \cdot \sum(\^y - y_i)^2

\frac{\partial}{\partial w_j}MSE = \frac{\partial}{\partial w} \frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2

\frac{\partial}{\partial w_j}MSE = \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i) \cdot x_{ij}

Woher wir das x_{ij} am Ende her haben? Das ergibt sie aus der Kettenregel: Die äußere Funktion wurde abgeleitet, so wurde aus \frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2 dann \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i). Jedoch muss im Sinne eben dieser Kettenregel auch die innere Funktion abgeleitet werden. Da wir nach w_j ableiten, bleibt nur x_ij erhalten.

Damit können wir arbeiten! So kompliziert ist die Formel nun auch wieder nicht: \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i) \cdot x_{ij}

Mit dieser Formel können wir unsere Gewichte an den Fehler anpassen: (f\nabla ist der Gradient der Funktion!)

w_j = w_j - \nabla MSE(w_j)

Initialisieren der Gewichtungen

Die Gewichtungen \alpha und \beta müssen anfänglich mit Werten initialisiert werden. In der Regression bietet es sich an, die Gewichte anfänglich mit 0.00 zu initialisieren.

Bei vielen neuronalen Netzen, mit nicht-linearen Aktivierungsfunktionen, ist das jedoch eher ungünstig und zufällige Werte sind initial besser. Gut erprobt sind normal-verteilte Zufallswerte.

Lernrate

Nur eine Kleinigkeit haben wir bisher vergessen: Wir brauchen einen Faktor, mit dem wir anpassen. Hier wäre der Faktor 1. Das ist in der Regel viel zu groß. Dieser Faktor wird geläufig als Lernrate (Learning Rate) \eta (eta) bezeichnet:

w_j = w_j - \eta \cdot \nabla MSE(w_j)

Die Lernrate \eta ist ein Knackpunkt und der erste Parameter des Lernalgorithmus, den es anzupassen gilt, wenn das Training nicht konvergiert.

Die Lernrate \eta darf nicht zu groß klein gewählt werden, da das Training sonst zu viele Epochen benötigt. Ungeduldige erhöhen die Lernrate möglicherweise aber so sehr, dass der Lernalgorithmus im Minimum der Fehlerfunktion vorbeiläuft und diesen stets überspringt. Hier würde der Algorithmus also sozusagen konvergieren, weil nicht mehr besser werden, aber das resultierende Modell wäre weit vom Optimum entfernt.

Beginnen wir mit der Implementierung als Python-Klasse:

Die Klasse sollte so funktionieren, bevor wir sie verwenden, sollten wir die Input-Werte standardisieren:

Bei diesem Beispiel mit künstlich erzeugten Werten ist das Standardisieren bzw. das Fehlen des Standardisierens zwar nicht kritisch, aber man sollte es sich zur Gewohnheit machen. Testweise es einfach mal weglassen 🙂

Kommen wir nun zum Einsatz der Klasse, die die Regression via Gradientenabstieg absolvieren soll:

Was tut diese Instanz der Klasse LinearRegressionGD nun eigentlich?

Bildlich gesprochen, legt sie eine Gerade auf den Boden des Koordinatensystems, denn die Gewichtungen werden mit 0.00 initialisiert, y ist also gleich 0.00, egal welche Werte in x enthalten sind. Der Fehler ist dann aber sehr groß (sollte maximal sein, im Vergleich zu zukünftigen Epochen). Die Gewichte werden also angepasst, die Gerade somit besser in die Punktwolke platziert. Mit jeder Epoche wird die Gerade erneut in die Punktwolke gelegt, der Gesamtfehler (über alle x, da wir es hier mit dem Batch-Verfahren zutun haben) berechnet, die Werte angepasst… bis die vorgegebene Zahl an Epochen abgelaufen ist.

Schauen wir uns das Ergebnis des Trainings an:

Die Linie sieht passend aus, oder? Da wir hier nicht zu sehr in die Theorie der Regressionsanalyse abdriften möchten, lassen wir das testen und prüfen der Akkuratesse mal aus, hier möchte ich auf meinen Artikel Regressionsanalyse in Python mit Scikit-Learn verweisen.

Prüfen sollten wir hingegen mal, wie schnell der Lernalgorithmus mit der vorgegebenen Lernrate eta konvergiert:

Hier die Verlaufskurve der Cost Function:

Die Kurve zeigt uns, dass spätestens nach 40 Epochen kaum noch Verbesserung (im Sinne der Gesamtfehler-Minimierung) erreicht wird.

Wichtige Hinweise

Natürlich war das nun nur ein erster kleiner Einstieg und wer es verstanden hat, hat viel gewonnen. Denn erst dann kann man sich vorstellen, wie ein einzelnen Neuron eines künstlichen neuronalen Netzes grundsätzlich trainiert werden kann.

Folgendes sollte noch beachtet werden:

  • Lernrate \eta:
    Die Lernrate ist ein wichtiger Parameter. Wer das Programmier-Beispiel bei sich zum Laufen gebracht hat, einfach mal die Lernrate auf Werte zwischen 10.00 und 0.00000001 setzen, schauen was passiert 🙂
  • Globale Minima vs lokale Minima:
    Diese lineare zwei-dimensionale Regression ist ziemlich einfach. Neuronale Netze sind hingegen komplexer und haben nicht einfach nur eine simple konvexe Fehlerfunktion. Hier gibt es mehrere Hügel und Täler in der Fehlerfunktion und die Gefahr ist groß, in einem lokalen, nicht aber in einem globalen Minimum zu landen.
  • Stochastisches Gradientenverfahren:
    Wir haben hier das sogenannte Batch-Verfahren verwendet. Dieses ist grundsätzlich besser als die stochastische Methode. Denn beim Batch verwenden wir den gesamten Stapel an x-Werten für die Fehlerbestimmung. Allerdings ist dies bei großen Daten zu rechen- und speicherintensiv. Dann werden kleinere Unter-Stapel (Sub-Batches) zufällig aus den x-Werten ausgewählt, der Fehler daraus bestimmt (was nicht ganz so akkurat ist, wie als würden wir den Fehler über alle x berechnen) und der Gradient bestimmt. Dies ist schon Rechen- und Speicherkapazität, erfordert aber meistens mehr Epochen.

Buchempfehlung

Die folgenden zwei Bücher haben mir bei der Erstellung dieses Beispiels geholfen und kann ich als hilfreiche und deutlich weiterführende Lektüre empfehlen:

 

Machine Learning mit Python und Scikit-Learn und TensorFlow: Das umfassende Praxis-Handbuch für Data Science, Predictive Analytics und Deep Learning (mitp Professional) Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques for Building Intelligent Systems

 

Über die Integration symbolischer Inferenz in tiefe neuronale Netze

Tiefe neuronale Netze waren in den letzten Jahren eine enorme Erfolgsgeschichte. Viele Fortschritte im Bereich der KI, wie das Erkennen von Objekten, die fließende Übersetzung natürlicher Sprache oder das Spielen von GO auf Weltklasseniveau, basieren auf tiefen neuronalen Netzen. Über die Grenzen dieses Ansatzes gab es jedoch nur wenige Berichte. Eine dieser Einschränkungen ist die Unfähigkeit, aus einer kleinen Anzahl von Beispielen zu lernen. Tiefe neuronale Netze erfordern in der Regel eine Vielzahl von Trainingsbeispielen, während der Mensch aus nur einem einzigen Beispiel lernen kann. Wenn Sie eine Katze einem Kind zeigen, das noch nie zuvor eine gesehen hat, kann es eine weitere Katze anhand dieser einzigen Instanz erkennen. Tiefe neuronale Netze hingegen benötigen Hunderttausende von Bildern, um zu erlernen, wie eine Katze aussieht. Eine weitere Einschränkung ist die Unfähigkeit, Rückschlüsse aus bereits erlerntem Allgemeinwissen zu ziehen. Beim Lesen eines Textes neigen Menschen dazu, weitreichende Rückschlüsse auf mögliche Interpretationen des Textes zu ziehen. Der Mensch ist dazu in der Lage, weil er Wissen aus sehr unterschiedlichen Bereichen abrufen und auf den Text anwenden kann.

Diese Einschränkungen deuten darauf hin, dass in tiefen neuronalen Netzen noch etwas Grundsätzliches fehlt. Dieses Etwas ist die Fähigkeit, symbolische Bezüge zu Entitäten in der realen Welt herzustellen und sie in Beziehung zueinander zu setzen. Symbolische Inferenz in Form von formaler Logik ist seit Jahrzehnten der Kern der klassischen KI, hat sich jedoch als spröde und komplex in der Anwendung erwiesen. Gibt es dennoch keine Möglichkeit, tiefe neuronale Netze so zu verbessern, dass sie in der Lage sind, symbolische Informationen zu verarbeiten? Tiefe neuronale Netzwerke wurden von biologischen neuronalen Netzwerken wie dem menschlichen Gehirn inspiriert. Im Wesentlichen sind sie ein vereinfachtes Modell der Neuronen und Synapsen, die die Grundbausteine des Gehirns ausmachen. Eine solche Vereinfachung ist, dass statt mit zeitlich begrenzten Aktionspotenzialen nur mit einem Aktivierungswert gearbeitet wird. Aber was ist, wenn es nicht nur wichtig ist, ob ein Neuron aktiviert wird, sondern auch, wann genau. Was wäre, wenn der Zeitpunkt, zu dem ein Neuron feuert, einen relationalen Kontext herstellt, auf den sich diese Aktivierung bezieht? Nehmen wir zum Beispiel ein Neuron, das für ein bestimmtes Wort steht. Wäre es nicht sinnvoll, wenn dieses Neuron jedes Mal ausgelöst würde, wenn das Wort in einem Text erscheint? In diesem Fall würde das Timing der Aktionspotenziale eine wichtige Rolle spielen. Und nicht nur das Timing einer einzelnen Aktivierung, sondern auch das Timing aller eingehenden Aktionspotenziale eines Neurons relativ zueinander wäre wichtig. Dieses zeitliche Muster kann verwendet werden, um eine Beziehung zwischen diesen Eingangsaktivierungen herzustellen. Wenn beispielsweise ein Neuron, das ein bestimmtes Wort repräsentiert, eine Eingabesynapse für jeden Buchstaben in diesem Wort hat, ist es wichtig, dass das Wort Neuron nur dann ausgelöst wird, wenn die Buchstabenneuronen in der richtigen Reihenfolge zueinander abgefeuert wurden. Konzeptionell könnten diese zeitlichen Unterschiede als Relationen zwischen den Eingangssynapsen eines Neurons modelliert werden. Diese Relationen definieren auch den Zeitpunkt, zu dem das Neuron selbst im Verhältnis zu seinen Eingangsaktivierungen feuert. Aus praktischen Gründen kann es sinnvoll sein, der Aktivierung eines Neurons mehrere Slots zuzuordnen, wie z.B. den Anfang und das Ende eines Wortes. Andernfalls müssten Anfang und Ende eines Wortes als zwei getrennte Neuronen modelliert werden. Diese Relationen sind ein sehr mächtiges Konzept. Sie ermöglichen es, die hierarchische Struktur von Texten einfach zu erfassen oder verschiedene Bereiche innerhalb eines Textes miteinander in Beziehung zu setzen. In diesem Fall kann sich ein Neuron auf eine sehr lokale Information beziehen, wie z.B. einen Buchstaben, oder auf eine sehr weitreichende Information, wie z.B. das Thema eines Textes.

Eine weitere Vereinfachung im Hinblick auf biologische neuronale Netze besteht darin, dass mit Hilfe einer Aktivierungsfunktion die Feuerrate eines einzelnen Neurons angenähert wird. Zu diesem Zweck nutzen klassische neuronale Netze die Sigmoidfunktion. Die Sigmoidfunktion ist jedoch symmetrisch bezüglich großer positiver oder negativer Eingangswerte, was es sehr schwierig macht, ausssagenlogische Operationen mit Neuronen mit der Sigmoidfunktion zu modellieren. Spiking-Netzwerke hingegen haben einen klaren Schwellenwert und ignorieren alle Eingangssignale, die unterhalb dieses Schwellenwerts bleiben. Daher ist die ReLU-Funktion oder eine andere asymmetrische Funktion eine deutlich bessere Annäherung für die Feuerrate. Diese Asymmetrie ist auch für Neuronen unerlässlich, die relationale Informationen verarbeiten. Das Neuron, das ein bestimmtes Wort repräsentiert, muss nämlich für alle Zeitpunkte, an denen das Wort nicht vorkommt, völlig inaktiv bleiben.

Ebenfalls vernachlässigt wird in tiefen neuronalen Netzwerken die Tatsache, dass verschiedene Arten von Neuronen in der Großhirnrinde vorkommen. Zwei wichtige Typen sind die bedornte Pyramidenzelle, die in erster Linie eine exzitatorische Charakteristik aufweist, und die nicht bedornte Sternzelle, die eine hemmende aufweist. Die inhibitorischen Neuronen sind besonders, weil sie es ermöglichen, negative Rückkopplungsschleifen aufzubauen. Solche Rückkopplungsschleifen finden sich normalerweise nicht in einem tiefen neuronalen Netzwerk, da sie einen inneren Zustand in das Netzwerk einbringen. Betrachten wir das folgende Netzwerk mit einem hemmenden Neuron und zwei exzitatorischen Neuronen, die zwei verschiedene Bedeutungen des Wortes “August” darstellen.

Beide Bedeutungen schließen sich gegenseitig aus, so dass das Netzwerk nun zwei stabile Zustände aufweist. Diese Zustände können von weiteren Eingangssynapsen der beiden exzitatorischen Neuronen abhängen. Wenn beispielsweise das nächste Wort nach dem Wort ‘August’ ein potenzieller Nachname ist, könnte eine entsprechende Eingabesynapse für das Entitätsneuron August-(Vorname) das Gewicht dieses Zustands erhöhen. Es ist nun wahrscheinlicher, dass das Wort “August” als Vorname und nicht als Monat eingestuft wird. Aber bedenken Sie, dass beide Zustände evaluiert werden müssen. In größeren Netzwerken können viele Neuronen durch negative oder positive Rückkopplungsschleifen verbunden sein, was zu einer großen Anzahl von stabilen Zuständen im Netzwerk führen kann.

Aus diesem Grund ist ein effizienter Optimierungsprozess erforderlich, der den besten Zustand in Bezug auf eine Zielfunktion ermittelt. Diese Zielfunktion könnte darin bestehen, die Notwendigkeit der Unterdrückung stark aktivierter Neuronen zu minimieren. Diese Zustände haben jedoch den enormen Vorteil, dass sie es erlauben, unterschiedliche Interpretationen eines bestimmten Textes zu berücksichtigen. Es ist eine Art Denkprozess, in dem verschiedene Interpretationen bewertet werden und die jeweils stärkste als Ergebnis geliefert wird. Glücklicherweise lässt sich die Suche nach einem optimalen Lösungszustand recht gut optimieren.

Der Grund, warum wir in diesen Rückkopplungsschleifen hemmende Neuronen benötigen, ist, dass sonst alle gegenseitig unterdrückenden Neuronen vollständig miteinander verbunden sein müssten. Das würde zu einer quadratisch zunehmenden Anzahl von Synapsen führen.

Durch die negativen Rückkopplungsschleifen, d.h. durch einfaches Verbinden einer negativen Synapse mit einem ihrer Vorläuferneuronen, haben wir plötzlich den Bereich der nichtmonotonen Logik betreten. Die nichtmonotone Logik ist ein Teilgebiet der formalen Logik, in dem Implikationen nicht nur zu einem Modell hinzugefügt, sondern auch entfernt werden. Es wird davon ausgegangen, dass eine nichtmonotone Logik erforderlich ist, um Schlussfolgerungen für viele Common Sense Aufgaben ziehen zu können. Eines der Hauptprobleme der nichtmonotonen Logik ist, dass sie oft nicht entscheiden kann, welche Schlussfolgerungen sie ziehen soll und welche eben nicht. Einige skeptische oder leichtgläubige Schlussfolgerungen sollten nur gezogen werden, wenn keine anderen Schlussfolgerungen wahrscheinlicher sind. Hier kommt die gewichtete Natur neuronaler Netze zum Tragen. In neuronalen Netzen können nämlich eher wahrscheinliche Zustände weniger wahrscheinliche Zustände unterdrücken.

Beispielimplementierung innerhalb des Aika-Frameworks

An dieser Stelle möchte ich noch einmal das Beispielneuron für das Wort ‘der’ vom Anfang aufgreifen. Das Wort-Neuron besteht aus drei Eingabesynapsen, die sich jeweils auf die einzelnen Buchstaben des Wortes beziehen. Über die Relationen werden die Eingabesynapsen nun zueinander in eine bestimmte Beziehung gesetzt, so dass das Wort ‘der’ nur erkannt wird, wenn alle Buchstaben in der korrekten Reihenfolge auftreten.
Als Aktivierungsfunktion des Neurons wird hier der im negativen Bereich abgeschnittene (rectified) hyperbolische Tangens verwendet. Dieser hat gerade bei einem UND-verknüpfenden Neuron den Vorteil, dass er selbst bei sehr großen Werten der gewichteten Summe auf den Wert 1 begrenzt ist. Alternativ kann auch die ReLU-Funktion (Rectified Linear Unit) verwendet werden. Diese eignet sich insbesondere für ODER-verknüpfende Neuronen, da sie die Eingabewerte unverzerrt weiterleitet.
Im Gegensatz zu herkömmlichen neuronalen Netzen gibt es hier mehrere Bias Werte, einen für das gesamte Neuron (in diesem Fall auf 5.0 gesetzt) und einen für jede Synapse. Intern werden diese Werte zu einem gemeinsamen Bias aufsummiert. Es ist schon klar, dass dieses Aufteilen des Bias nicht wirklich gut zu Lernregeln wie der Delta-Rule und dem Backpropagation passt, allerdings eignen sich diese Lernverfahren eh nur sehr begrenzt für diese Art von neuronalem Netzwerk. Als Lernverfahren kommen eher von den natürlichen Mechanismen Langzeit-Potenzierung und Langzeit-Depression inspirierte Ansätze in Betracht.

Fazit

Obwohl tiefe neuronale Netze bereits einen langen Weg zurückgelegt haben und mittlerweile beeindruckende Ergebnisse liefern, kann es sich doch lohnen, einen weiteren Blick auf das Original, das menschliche Gehirn und seine Schaltkreise zu werfen. Wenn eine so inhärent komplexe Struktur wie das menschliche Gehirn als Blaupause für ein neuronales Modell verwendet werden soll, müssen vereinfachende Annahmen getroffen werden. Allerdings ist bei diesem Prozess Vorsicht geboten, da sonst wichtige Aspekte des Originals verloren gehen können.

Referenzen

  1. Der Aika-Algorithm
    Lukas Molzberger
  2. Neuroscience: Exploring the Brain
    Mark F. Bear, Barry W. Connors, Michael A. Paradiso
  3. Neural-Symbolic Learning and Reasoning: A Survey and Interpretation
    Tarek R. Besold, Artur d’Avila Garcez, Sebastian Bader; Howard Bowman, Pedro Domingos, Pascal Hitzler, Kai-Uwe Kuehnberger, Luis C. Lamb, ; Daniel Lowd, Priscila Machado Vieira Lima, Leo de Penning, Gadi Pinkas, Hoifung Poon, Gerson Zaverucha
  4. Deep Learning: A Critical Appraisal
    Gary Marcus
  5. Nonmonotonic Reasoning
    Gerhard Brewka, Ilkka Niemela, Mirosław Truszczynski

Cloudera und Hortonworks vollenden geplante Fusion

Kombiniertes „ Open-Source-Powerhouse” wird die branchenweit erste Enterprise Data Cloud vom Netzwerk-Rand (Edge) bis hin zu künstlicher Intelligenz bauen.

München, Palo Alto (Kalifornien), 03. Januar 2019 – Cloudera, Inc. (NYSE: CLDR) hat den Abschluss seiner Fusion mit Hortonworks, Inc. bekanntgegeben. Cloudera wird die erste Enterprise Data Cloud bereitstellen, die die ganze Macht der Daten freisetzt, welche sich in einer beliebigen Cloud vom Netzwerk-Rand (Edge) bis zur KI bewegen –  all dies basierend auf einer hundertprozentigen Open-Source-Datenplattform. Die Enterprise Data Cloud unterstützt sowohl hybride als auch Multi-Cloud-Deployments. Unternehmen erhalten dadurch die nötige Flexibilität, um Machine Learning und Analysen mit ihren Daten, auf ihre Art und Weise und ohne Lock-in durchzuführen.

„Heute startet ein aufregendes neues Kapitel für Cloudera als führender Anbieter von Enterprise Data Clouds”, so Tom Reilly, Chief Executive Officer von Cloudera. „Das kombinierte Team und Technologieportfolio etabliert das neue Cloudera als klaren Marktführer mit der Größe und den Ressourcen für weitere Innovationen und Wachstum. Wir bieten unseren Kunden eine umfassende Lösung, um die richtige Datenanalyse für Daten überall dort bereitzustellen, wo das Unternehmen arbeiten muss, vom Edge bis zur KI, mit der branchenweit ersten Enterprise Data Cloud”.  

Ergänzend dazu stellte das Forschungsunternehmen Forrester fest1, dass „diese Fusion … die Messlatte für Innovationen im Big-Data-Bereich höher legen wird, insbesondere bei der Unterstützung einer durchgehenden Big-Data-Strategie in einer Hybrid- und Multi-Cloud-Umgebung. Wir glauben, dass dies eine Win-Win-Situation für Kunden, Partner und Lieferanten ist.”

Cloudera wird weiterhin unter dem Symbol „CLDR” an der New Yorker Börse gehandelt. Die Aktionäre von Hortonworks erhielten 1,305 Stammaktien von Cloudera für jede Aktie von Hortonworks.

Das Cloudera-Management wird am 10. Januar 2019 um 19:00 Uhr ein Online-Meeting veranstalten, um zu diskutieren, wie das neue Cloudera Innovationen beschleunigen und die erste Enterprise Data Cloud der Branche liefern wird. Registrieren Sie sich jetzt. Die Veranstaltung wird am 14. Januar 2019 um 14:00 Uhr auch für die EMEA-Region stattfinden. Registrieren Sie sich hier für dieses Webinar.

1 „Cloudera And Hortonworks Merger: A Win-Win For All”, Beitrag von Noel Yuhanna im Forrester-Blog (4. Oktober 2018)

Über Cloudera

Bei Cloudera glauben wir, dass Daten morgen Dinge ermöglichen werden, die heute noch unmöglich sind. Wir versetzen Menschen in die Lage, komplexe Daten in klare, umsetzbare Erkenntnisse zu transformieren. Cloudera stellt dafür eine Enterprise Data Cloud bereit – für alle Daten, jederzeit, vom Netzwerkrand (Edge) bis hin zu künstlicher Intelligenz. Mit der Innovationskraft der Open-Source-Community treibt Cloudera die digitale Transformation für die größten Unternehmen der Welt voran. Erfahren Sie mehr unter  de.cloudera.com/.

Cloudera und damit verbundene Zeichen und Warenzeichen sind registrierte Warenzeichen der Cloudera Inc. Alle anderen Unternehmen und Produktnamen können Warenzeichen der jeweiligen Besitzer sein.

IIIb. Einführung in TensorFlow: Realisierung eines Perzeptrons mit TensorFlow

In [1]:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Reset des TensorFlows
tf.reset_default_graph() 

Daten laden und eigene Definitionen

In [2]:
data = pd.read_csv('data_train.csv')
input_X = data[['x0', 'x1']]
input_y = data.y

data_test = pd.read_csv('data_test.csv')
test_X = data_test[['x0', 'x1']]
test_y = data_test.y

Damit unser Modell schneller lernt, teilen wir unseren Datensatz in Stapel ein. Dafür erstellen wir eine Funktion, welche unseren Datensatz in Stapel teilt!

Je nach Datensatz und Modell empfehlt sich eine andere Stapelgröße.

In [3]:
def stapel_erstellen(X, Y, stapel_grosse, p_index):
    return X[stapel_grosse * p_index: stapel_grosse * (p_index + 1)], Y[stapel_grosse * p_index: stapel_grosse * (p_index + 1)]

Erstellen des Graphen

Formen der Tensoren

In [4]:
# Anzahl der Ergebnissspalten
anz_unit = 1
# Anzahl der Eingänge bzw. Merkmale 
anz_ein = 2
# Anzahl der Ausgänge
anz_aus = 1

Parameter zur Steuerung des Graphen

Die richtige Wahl der Parameter zur Steuerung des Graphen sind entscheidend, wenn es darum geht, wie schnell ein Modell lernt. Wenn wir zum Beispiel anz_stapel=10 statt anz_stapel=5 nutzen, dann brauch unser Modell länger um eine Genauigkeit von 100 % zu erreichen, wenn überhaupt.

In [5]:
# Lernrate
eta = 0.1
# Anzahl der der Pakete mit den zu analysierenden Datenwerte
anz_stapel = 5
# Anzahl der zu analysierenden Datenwerte
stapel_grosse = int(len(input_X)/anz_stapel)
# Anzahl der Wiederholungen
epochen = 50

Relevante Größen

In [6]:
# Eingangssignal
x = tf.placeholder(tf.float32, shape=[None, anz_ein],name='Input')  # Stapelgröße(k) x 2
# Ausgangssignal
y_true = tf.placeholder(tf.float32, shape=None, name='Labels')  # Stapelgröße(k) x 1
# Gewichte
w = tf.Variable(tf.random_normal([anz_ein, anz_unit]), name='Weights')  # 2x1

Berechnungsgleichungen

In der Theorie sind wir immer nur einen Datenpunkt in Betracht gezogen. In TensorFlow wollen wir jedoch einen Stapel betrachten. Dadurch ändert sich die Berechnung ein wenig. Wir berechnen für alle Punkte eine Fehlerfunktion. Der Mittelwert aller Fehlerfunktionen, die Kostenfunktion, soll dann optimiert werden.

In [7]:
# z = xw
z = tf.matmul(x, w, name='Z')
# H = y * -log(sigmoid(z)) + (1 - y) * -log(1 - sigmoid(z)) -> Kreuzentropie
err = tf.reduce_mean(
    tf.nn.sigmoid_cross_entropy_with_logits(labels=y_true, logits=z),name='Costfunction')
# Minimieren der Fehlerfunktion
opt = tf.train.GradientDescentOptimizer(learning_rate=eta).minimize(err)

# Berechnung der Genauigkeit
eins = tf.reshape(tf.round(tf.sigmoid(z)),[len(test_X), 1])
zwei = tf.reshape(y_true,[len(test_X), 1])
acc = tf.equal(eins, zwei)
acc = tf.reduce_mean(tf.cast(acc, tf.float32), name='Accuracy')

Ausführung des Graphen

Bei der Ausführung ist es wichtig, dass wir die Variablen initialisieren. Auch ist es vorteilhaft, wenn wir die Session mit with starten.

In [8]:
# Größen zur späteren Datenvisualisierung
W_set = []
Err_set = []
Acc_set = []
# Initialisierung der Variablen
init = tf.global_variables_initializer()
# Ausführung des Graphen
with tf.Session() as sess:
    # Wichtig für TensorBoard
    writer = tf.summary.FileWriter('./graphs/perceptron', sess.graph)
    sess.run(init)
    # Schleife für Epoche
    for e in range(epochen):
        # Schleife für Stapel
        for i in range(anz_stapel):
            # Einteilen unserer Daten in Stapel
            stapel_x, stapel_y = stapel_erstellen(X=input_X,
                                                  Y=input_y,
                                                  stapel_grosse=stapel_grosse,
                                                  p_index=i)
            # Ausführung der Berechnung
            Z, W, _, Err = sess.run([z, w, opt, err],
                                    feed_dict={x: stapel_x, y_true: stapel_y})

        # Datenspeicherung für Visualisierung über die Epochen
        W_set.append(W)
        Err_set.append(np.mean(Err))
        Acc = sess.run([acc],
                       feed_dict={x: test_X, y_true: test_y})
        Acc_set.append(Acc)
        print('{:}. Epoche Genauigkeit: {:.2f} %'.format(e, Acc[0]*100))
    sess.close()
0. Epoche Genauigkeit: 50.75 %
1. Epoche Genauigkeit: 65.00 %
2. Epoche Genauigkeit: 80.75 %
3. Epoche Genauigkeit: 93.00 %
4. Epoche Genauigkeit: 97.75 %
5. Epoche Genauigkeit: 98.75 %
6. Epoche Genauigkeit: 99.75 %
7. Epoche Genauigkeit: 100.00 %
8. Epoche Genauigkeit: 100.00 %
9. Epoche Genauigkeit: 100.00 %
10. Epoche Genauigkeit: 100.00 %
11. Epoche Genauigkeit: 100.00 %
12. Epoche Genauigkeit: 100.00 %
13. Epoche Genauigkeit: 100.00 %
14. Epoche Genauigkeit: 100.00 %
15. Epoche Genauigkeit: 100.00 %
16. Epoche Genauigkeit: 100.00 %
17. Epoche Genauigkeit: 100.00 %
18. Epoche Genauigkeit: 100.00 %
19. Epoche Genauigkeit: 100.00 %
20. Epoche Genauigkeit: 100.00 %
21. Epoche Genauigkeit: 100.00 %
22. Epoche Genauigkeit: 100.00 %
23. Epoche Genauigkeit: 100.00 %
24. Epoche Genauigkeit: 100.00 %
25. Epoche Genauigkeit: 100.00 %
26. Epoche Genauigkeit: 100.00 %
27. Epoche Genauigkeit: 100.00 %
28. Epoche Genauigkeit: 100.00 %
29. Epoche Genauigkeit: 100.00 %
30. Epoche Genauigkeit: 100.00 %
31. Epoche Genauigkeit: 100.00 %
32. Epoche Genauigkeit: 100.00 %
33. Epoche Genauigkeit: 100.00 %
34. Epoche Genauigkeit: 100.00 %
35. Epoche Genauigkeit: 100.00 %
36. Epoche Genauigkeit: 100.00 %
37. Epoche Genauigkeit: 100.00 %
38. Epoche Genauigkeit: 100.00 %
39. Epoche Genauigkeit: 100.00 %
40. Epoche Genauigkeit: 100.00 %
41. Epoche Genauigkeit: 100.00 %
42. Epoche Genauigkeit: 100.00 %
43. Epoche Genauigkeit: 100.00 %
44. Epoche Genauigkeit: 100.00 %
45. Epoche Genauigkeit: 100.00 %
46. Epoche Genauigkeit: 100.00 %
47. Epoche Genauigkeit: 100.00 %
48. Epoche Genauigkeit: 100.00 %
49. Epoche Genauigkeit: 100.00 %
In [9]:
w_0, w_1 = zip(*W_set)
fig, ax = plt.subplots(3,1, figsize=(15,30), sharex='all')
ax[0].plot(range(len(W_set)), w_0, label='w0')
ax[0].plot(range(len(W_set)), w_1, label='w1')
ax[0].legend()
ax[0].grid()
ax[0].set_title('Gewichte')

ax[1].plot(range(len(W_set)), Err_set, c='r', label='err')
ax[1].legend()
ax[1].set_title('Fehlerfunktion')
ax[1].grid()

ax[2].plot(range(len(W_set)), Acc_set, c='g', label='acc')
ax[2].legend()
ax[2].set_title('Genauigkeit')
ax[2].set_xlabel('Epoche')
ax[2].grid()

Zusammenfassung

Nun haben wir unser Perzeptron erfolgreich mit TensorFlow realisiert. Um ein Gefühl zu bekommen, könnt ihr gerne mit den "Parameter zur Steuerung des Graphen" herumexperimentieren. Je nach Auswahl der Parameter ändert sich die Optimierung und sogar die Genauigkeit unseres Modells. Bei so einfachen Daten, sollte unser Modell definitiv 100% Genauigkeit erreichen. Dies ist jedoch nur möglich, wenn wir die richtigen Parameter wählen. Probiert es also einfach mal aus.

PS: Wenn ihr die Trainings- und Testdaten sucht, dann werdet ihr auf Github fündig.

Künstliche Intelligenz und Vorurteil

Kaum ein anderes technologisches Thema heutzutage wird hinsichtlich gesellschaftlicher Auswirkungen so kontrovers diskutiert wie das der Künstlichen Intelligenz (KI). Während das Wörtchen „KI“ bei den einen Zukunftsvisionen hervorruft, in welchen technologischer Fortschritt menschliche Probleme wie Hunger, Krankheit und Klimawandel reduziert hat, zeichnen andere düstere Bilder von Orwell‘schen Überwachungsstaaten und technologischen Apokalypsen.

Starke, schwache KI

Es ist die Unschärfe des Begriffes „KI“, welcher eine derart große Bandbreite an Zukunftsszenarien ermöglicht. Für diejenigen, welche sich an solch spekulativen Debatten beteiligen, beutet KI „starke KI“ – eine künstliche Intelligenz, deren intellektuellen Fähigkeiten die eines Menschen erreichen oder gar übertreffen. Und so spannend die Diskussion über starke KI auch ist – sie ist reine Spekulation. Heute existierende KI ist weit, sehr weit von starker KI entfernt. Worüber wir heutzutage verfügen ist die sogenannte „schwache KI“ – Algorithmen, die spezifische Anwendungsprobleme (z.B. Bilderkennung, Spracherkennung, Übersetzung, Go spielen) lösen können. Und das mitunter sehr viel besser als Menschen.

Wo heutzutage „KI“ draufsteht, sind innen überwiegend Algorithmen aus dem Bereich des maschinellen Lernens (allen voran Deep Learning) am Werk. Diese Algorithmen können selbständig die Vorgehensweise erlernen, die zum Beispiel nötig ist, um einen gegebenen Input (z.B. ein Bild) auf einen gegebenen Output (z.B. eine Kategorie, welche den Bildinhalt beschreibt) abzubilden. Aber selbst diese „schwache KI“ birgt beträchtliches Potential – denken wir an mögliche Verbesserungen z.B. im Bereich der Medizin, Logistik, Verkehrssicherheit oder Energie- und Ressourcennutzung! Angesichts der Chancen, heutige Prozesse und Anwendungen zu verbessern, haben wir allen Grund, dem Einsatz von KI aufgeschlossen gegenüber zu stehen. Vorausgesetzt natürlich, dass KI verantwortungsvoll, „ethisch“ und sicher eingesetzt wird.

KI auf Abwegen

Ethische Herausforderungen von KI ergeben sich dabei zum einen durch die Zielsetzung. Wie ein Hammer für den Nagel an der Wand oder für den Hinterkopf eines Gegners verwendet werden kann, kann auch KI für böse Ziele missbraucht werden. Nur, dass KI im Zweifel deutlich größeren Schaden anrichten kann als ein einfacher Hammer. Und so sollten wir angesichts der Risiken dringend international diskutieren, wie wir uns hinsichtlich militärischer Anwendungen von KI verhalten wollen.

Zum anderen dringen besonders aus den USA, in denen KI Algorithmen schon heute in deutlich größerem Ausmaß eingesetzt werden als in Deutschland, immer wieder beunruhigende Nachrichten über voreingenommene KI Algorithmen. Zum ersten fand eine Studie kürzlich heraus, dass kommerziell erhältliche Gesichtserkennungsalgorithmen für Frauen bzw. dunkelhäutige Menschen schlechter funktionieren als für Männer bzw. hellhäutige Menschen. Mit der unschönen Konsequenz, dass es z.B. bei einem Abgleich mit Verbrecherfotos bei Menschen mit dunkler Hautfarbe deutlich häufiger zu falschen Übereinstimmungen kommen kann als bei Menschen heller Hautfarbe. Zum zweiten wurde vor kurzem bekannt, dass eine experimentell von einem großen Technologiekonzern zur Bewertung von Bewerbungen verwendete KI von Frauen stammende Bewerbungen systematisch schlechter bewertete als von Männern stammende Bewerbungen.

Wie KI zu Vorurteilen kommt

Um die Ursachen für vorurteilsbehaftete KI besser zu verstehen, lohnt es sich, einen Blick hinter die Kulissen zu werfen. Denn wie jede Technologie existiert auch KI nicht im luftleeren Raum. Dies lässt sich leicht anhand der Faktoren verdeutlichen, welche zum Erfolg heutiger KI beigetragen haben: bessere Hardware, cleverere Algorithmen und größere Datenmengen. Und gerade diese Daten sind es, durch welche Vorurteile in KI Einzug halten können.

Die Vorstellung von „neutralen Daten“ ist nämlich eine Wunschvorstellung. Im besten Fall spiegeln Daten die Welt wider, in der wir leben.       Eine Welt zum Beispiel, in der in Technologiekonzernen typischerweise deutlich mehr Männer beschäftigt sind als Frauen – was eine auf dem Personalbestand eines Technologiekonzerns trainierte KI dazu veranlassen kann, zu „schlussfolgern“, dass männliche Bewerber im Auswahlverfahren zu bevorzugen sind. Oder eine Welt, in der Länder bzw. gesellschaftliche Schichten innerhalb eines Landes unterschiedlichen Zugang zu modernen Technologien oder auch Bildung haben. Eine Ungleichheit, die sich als Dominanz westlicher Industrienationen in der geografischen Zusammensetzung von zum Training von KI-Algorithmen verwendeter Datensätze auswirken kann. Eine Dominanz, die wiederum zur Folge haben kann, dass derart trainierte KI-Algorithmen besonders gut für Menschen aus westlichen Industrienationen funktionieren. Ganz zu schweigen von der Voreingenommenheit der menschlichen Wahrnehmung, welche die Zusammensetzung von Daten beeinflusst – denken wir an das begrenzte Spektrum der Bilder, welche uns zuerst zu dem Begriff „Genie“ in den Sinn kommen.

Aber nicht nur die verwendeten Trainingsdaten, sondern auch bei der Entwicklung von KI getroffenen Design-Entscheidungen können negative Auswirkungen haben. Wenn bei einem nicht perfekt funktionierenden Bilderkennungsalgorithmus potentiell abwertende Kategorien zur Klassifikation zur Verfügung stehen, kann dies dazu führen, dass – wie in der Vergangenheit geschehen – dunkelhäutige Menschen als Gorillas klassifiziert werden. Wenn bei der Evaluation eines z.B. für die Gesichtserkennung eingesetzten KI-Algorithmus nur die Genauigkeit über alle Bevölkerungsgruppen hinweg berücksichtigt wird, können Ungleichheiten in der Genauigkeit nicht entdeckt werden, was zu Problemen bei der Anwendung führen kann. Denn Nutzer von KI-Algorithmen vermuten zumeist, dass die Algorithmen für alle denkbaren Anwendungszwecke geeignet sind.

Werte statt Wegsehen

Entgegen der verbreiteten Auffassung sind KI Algorithmen also nicht notwendigerweise vorurteilsfrei – sie können menschliche Voreingenommenheit bzw. gesellschaftliche Ungleichheit widerspiegeln. Da Algorithmen anders und in anderem Maß als Menschen eingesetzt werden, kann das bei blauäugiger Verwendung dazu führen, dass bestehende Ungleichheiten nicht nur bestärkt, sondern sogar vergrößert werden. Richtig angewendet können Algorithmen jedoch helfen, implizite und explizite Vorurteile menschlicher Entscheider zu mindern. Denn wie wir durch viele Studien wissen, ist die Liste der kognitiven Verzerrungen, die wir Menschen aufweisen, lang.

Es ist für den verantwortlichen Einsatz von KI in einem sensiblen Kontext somit essenziell, zu wissen, welche „ethischen“ Kriterien KI für den konkreten Anwendungsfall erfüllen muss. So kann sichergestellt werden, dass die KI den Anforderungen entspricht, bevor sie angewendet wird – oder aber, dass sie solange nicht angewendet wird, wie sie den Anforderungen nicht entspricht. Und mittels Transparenz, Überwachung und Feedback-Möglichkeiten lässt sich vermeiden, dass ein selbst-verbessernder KI-Algorithmus im Laufe der Zeit das ihm gesteckte Ziel verfehlt.

Für viele Anwendungsfälle sind derartige ethische Fragen jedoch vernachlässigbar, denken wir zum Beispiel an die Vorhersage von Maschinenausfällen oder die Extraktion strukturierter Daten aus unstrukturierten Dokumenten. Aber es ist nichtsdestotrotz gut und wichtig, Ethik und KI zusammen zu denken. Denn dies ermöglicht es uns, sicherzustellen, dass wir KI auf die bestmögliche Weise einsetzen. Denn das enorme Potential von KI gibt uns die Chance, den Status quo nachhaltig positiv zu verändern – technologisch wie ethisch.

IIIa. Einführung in TensorFlow: Realisierung eines Perzeptrons mit TensorFlow

1. Einleitung

1.1. Was haben wir vor?

Im zweiten Artikel dieser Serie sind wir darauf eingegangen, wie man TensorFlow prinzipiell nutzt. Wir wollen das Gelernte an einem einfachen Modell anwenden. Bevor wir dies jedoch tun, müssen wir die Theorie hinter dem Modell verstehen um TensorFlow richtig anwenden zu können.

Dafür bietet sich ein Adaline-Perzeptron sehr gut an. Es ist ein einfaches Modell mit nur einer Schicht, wo die Theorie verständlich ist.

1.2. Aufgabenstellung

Abb.1 Trainingsdaten: Grün \rightarrow Label 0, Rot
\rightarrow Label 1

In Abb.1 sehen wir unsere Trainingsdaten, die
zufällig generiert wurden. Alle grün markierten Datenpunkte haben das Label 0 und die rot markierten Punkte erhalten das Label 1. 

Wir möchten einen Adaline-Perzeptron entwickeln, der unsere Daten  je nach Position in die richtige Klasse zuordnet. Somit haben wir eine Aufgabe mit binärer Klassifikation

2. Grundlagen

2.1. Funktionsweise eines Perzeptrons

Ein Perzeptron ist ein mathematisches Modell, welches eine Nervenzelle beschreiben soll.

Abb.2 Schematische Darstellung einer Nervenzelle und ihren Bestandteilen

Vereinfacht funktioniert eine Nervenzelle, auch Neuron genannt, folgendermaßen: Eine Vielzahl von Reizen bzw. Eingabesignalen wird von den Dendriten aufgenommen, die dann im Kern verarbeitet werden. Wenn die verschiedenen Eingabesignale die ’richtige’ Dosis an Reizen erreichen und einen Schwellwert erreichen, dann feuert das Neuron ab und leitet ein Signal weiter. 

Für eine detaillierte Beschreibung, wie ein Perzeptron mathematisch beschrieben wird, möchte ich auf diesen Artikel hinweisen.

Wir wollen uns in diesem Artikel auf den Adaline-Algorithmus (ADAptive LINear Element) konzentrieren. Dieser ist eine Weiterentwicklung des Perzeptron. Die Besonderheit an diesem Algorithmus liegt darin, dass das Konzept der Fehlerminimierung durch Minimierung der Straffunktion der berechneten und der tatsächlichen Ergebnisse enthält. Ein weiter wesentlicher Unterschied zu einem einfachen Perzeptron ist vor allem, dass wir bei Adaline keine einfache Sprungfunktion als Aktivierungsfunktion haben, sondern eine stetige Funktion nutzen und somit eine Differenzierung/Ableitung der Aktivierungsfunktion durchführen können. Dieser Punkt ist für die Optimierung der Gewichte und des Lernens unseres Modells ein entscheidender Vorteil.

Das Schema in Abb.3 zeigt uns die Funktionsweise, wie unser Adaline-Algorithmus funktionieren soll.

Abb.3 Schematische Darstellung des Adaline-Perzeptrons

  1. Eingang: In dieser Schicht werden unsere Daten ein gepfangen und weitergeleitet
  2. Die Gewichte geben an, welchen Einfluss unsere Eingangssignale haben. Sie sind auch unsere Größe, die in unserem Algorithmus optimiert werden.
  3. Die Nettoeingabefunktion wird durch die Zusammenführung von Eingangssignalen und Gewichten erzeugt. Je nachdem wie die Eingänge und Gewichte verbunden sind,  müssen diese mathematisch korrekt multipliziert werden.
  4. Die Nettoeingabe wird dann, in die Aktivierungsfunktion eingebunden. Je nachdem welche Aktivierungsfunktion man nutzt, ändert sich die Ausgabe nach der Aktivierungsfunktion. 
  5.  In der Fehlerrückgabe werden die vorhergesagten Ausgaben mit den tatsächlichen Werten/Labels verglichen. Auch hier gibt es verschiedene Verfahren, um eine Fehlerfunktion zu bilden. 
  6. In der Optimierung werden dann auf Basis der Fehlerfunktion die Gewichte so optimiert, dass der Fehler zwischen unseren Label und den vorhergesagten Werten minimiert wird.
  7. Der Quantisierer ist ein optionales Element. Bei einer kategorischen Problemstellung bekommen wir nach der Aktivierungsfunktion eine Wahrscheinlichkeit zu der die Daten zu welchem Label zugeteilt werden. Der Quantisierer wandelt diese Wahrscheinlichkeiten zu Labeln um. Zum Beispiel haben wir einen Datensatz und unser Modell sagt voraus, dass dieser Datensatz zu 88 % das Label 1 hat. Je nachdem welche Grenze dem Quantisierer gegeben wird, teilt dieser dann den Datensatz in die entsprechende Klasse ein. Wenn wir sagen die Grenze soll 50% sein, dann sagt der Quantisierer, dass unser Datensatz Label 1 ist.

2.2. Aktivierungsfunktionen

Die Aktivierungsfunktion ist ein sehr wichtiger Bestandteil bei neuronalen Netzen. Diese bestimmen, wie sich das Ausgangssignal verhält. Es gibt eine Vielzahl von Aktivierungsfunktionen, die ihre Vor- und Nachteile haben. Wir wollen uns erstmal auf die Sigmoidfunktion konzentrieren.

Eigentlich haben wir bei der Sprungfunktion alles was wir brauchen. Wenn wir einen Schwellenwert erreichen z \geq 0, dann feuert die Sprungfunktion und das sehr abrupt. Die Sigmoidfunktion hingegen hat einen sanfteren und natürlicheren Verlauf als die Sprungfunktion. Außerdem ist sie eine stetig und differenzierbare Funktion, was sehr vorteilhaft für das Gradientenverfahren (Optimierung) ist. Daher wollen wir die Sigmoidfunktion für unsere Problemstellung nutzen.

    \begin{align*} \text{sig}(z) = \frac{1}{1 + e^{-z}}\end{align*}

Abb.4 Sigmoid-Funktion mit ihrer Ableitung und deren Sättigungsbereichen

2.3. Optimierungsverfahren

2.3.1. Fehlerfunktion

Die wohl am häufigsten genutzten Fehlerfunktionen (oder auch Ziel-, Kosten-, Verlust-, Straffunktion) sind wohl der mittlere quadratische Fehler bei Regressionen und die Kreuzentropie bei kategorischen Daten.

In unserem Beispiel haben wir Daten kategorischer Natur und eine binäre Thematik, weshalb wir uns auf die Kreuzentropie in Kombination mit der Sigmoidfunktion konzentrieren wollen.

Aus der Matrizenrechnung t (z =\boldsymbol{xw}^T) erhalten wir ein Skalar (eindimensional). Geben wir diese in die Sigmoidfunktion ein, kommen wir auf folgende Gleichung.



    \begin{align*} \text{sig}(z=\boldsymbol{xw}^T) = \frac{1}{1 + e^{-\boldsymbol{xw}^T}} \end{align*}


Hinweis: Wie in Abb.4 kann die Sigmoidfunktion nur Werte zwischen 0 und 1 erreichen, ohne diese jemals zu erreichen. Außerdem ändert sich die Funktion bei sehr großen Beträgen nur noch minimal, man spricht auch von Sättigung. Dieser Fakt ist sehr wichtig, wenn um die Optimierung der Gewichte geht. Wenn wir unsere Nettoeingabe nicht skalieren, dann kann es passieren, dass unser Modell sehr langsam lernt, da der Gradient der Sigmoidfunktion bei großen Beträgen sehr klein ist.

Bei Aufgaben mit binärer Klassifizierung hat sich die Kreuzentropie als Fehlerfunktion etabliert. Sie ist ein Maß für die Qualität eines Modells, welche eine Wahrscheinlichkeitsverteilung angibt. Je kleiner diese Größe ist, desto besser unser Modell. Es gilt also unsere Fehlerfunktion zu minimieren!

Wir wollen in einem separaten Artikel genauer auf die Kreuzentropie eingehen. Für den jetzigen Zeitpunkt soll es reichen, wenn wir die Formel vor Augen haben und was sie grob bedeutet.

P = \{p_1,p_2,\dots,p_N\} sei die ‘wahre’ Wahrscheinlichkeitsverteilung aus der Menge X = \{x_1,x_2,\dots,x_N\}, in unserem Fall, die Wahrscheinlichkeitsverteilung, ob ein Datenpunkt dem Label 0 oder 1 zugehört. Wenn wir nun unser Eingangssignal durch die Aktivierungsfunktion fließen lassen, dann erhalten wir ebenfalls eine ‘berechnete’ Wahrscheinlichkeitsverteilung die Q = \{q_1,q_2,\dots,q_N\} genannt werden soll. Um die Wahrscheinlichkeitsverteilungen p und q zu vergleichen, nutzen wir die Kreuzentropie, welche wie folgt für diskrete Daten definiert ist:

    \begin{align*}\log_2{x}&= \operatorname{ld}(x) \\H(P;Q) &= - \sum{P \cdot \operatorname{ld}(Q)}\\H(P;Q) &= -p_1 \operatorname{ld}(q_1) - p_2  \operatorname{ld}(q_2)\end{align*}

Beispiel einer binären Problemstellung. Wir haben unsere Label 0 und 1. p1 ist die Wahrscheinlichkeit, inwiefern unser Datenpunkt das Label 0 hat. Da wir die Trainingsdaten kennen, wissen wir auch das dieser Punkt zu 100 %, welches Label hat. Unser Modell hat zum Beispiel im ersten Durchgang eine Wahrscheinlichkeit von 0.8 und später 0.9 berechnet.

Fall I : P = Q Die Wahrscheinlichkeitsverteilungen P und Q sind identisch:

    \begin{align*}P &= \{p_1 = 1.0, p_2 = 0.0 \} \\Q_0 &= \{q_1 = 1.0, q_2 = 0.0 \} \\ \\H_{0}(P;Q_I) &= -1.0 \operatorname{ld}(1) -0.0 \operatorname{ld}(0.0) = 0.0\\\end{align*}

Fall II: P \neq Q Die Wahrscheinlichkeitsverteilungen P und Q sind nicht identisch:

    \begin{align*}P &= \{p_1 = 1.0, p_2 = 0.0 \} \\Q_{1} &= \{q_1 = 0.8, q_2 = 0.2 \} \\ Q_{2} &= \{q_1 = 0.9, q_2 = 0.1 \} \\ Q_{3} &= \{q_1 = 0.99, q_2 = 0.01 \} \\ \\H_{1}(P;Q_{1}) &= -1.0 \operatorname{ld}(0.8) -0.0 \operatorname{ld}(0.2) = 0.3219 \\H_{2}(P;Q_{2}) &= -1.0 \operatorname{ld}(0.9) -0.0 \operatorname{ld}(0.1) = 0.1520 \\ H_{3}(P;Q_{3}) &= -1.0 \operatorname{ld}(0.99) -0.0 \operatorname{ld}(0.01) = 0.0144\\\end{align*}

In der oberen Berechnung haben wir zum einfachen Verständnis der Kreuzentropie ein einfaches Beispiel. p_1 ist eine 100 % ige  Wahrscheinlichkeit, dass zum Beispiel unser Datensatz das Label 0 hat. Unser perfektes Modell mit Q_0 hat eine Kreuzentropie-Wert von 0. Unser zweites Modell  H_1(P;Q1) hat eine gewisse Unbestimmtheit, die sich durch eine größere Kreuzentropie H_1 = 0.1520 bemerkbar macht. Je mehr sich also unser Modell von den wirklichen Daten abweicht, desto größer ist die Kreuzentropie.

2.3.2. Optimierung nach dem Gradientenverfahren

Wenn wir es also schaffen die Kreuzentropie zu minimieren, dann erhalten wir auch ein besseres Modell! Bei der Optimierung nach dem Gradientenverfahren versuchen wir uns schrittweise an das Minimum zu bewegen.

    \begin{align*}H(P;Q) &= H(y; \varPhi(z)) \\            &= H(y; \text{sig}(z))\\             &= H(y; \text{sig}(xw))\\H' &= \frac{\partial H}{\partial w} \rightarrow Min.\end{align*}

Ziel der Optimierung ist es, dass unsere Gewichte so angepasst werden, dass sich der Fehler in unserer Fehlerfunktion minimiert. Wir leiten also die Fehlerfunktion nach w ab. 

Diese Aufgabe wird zum Glück von TensorFlow übernommen und wir müssen die Randbedingungen nur dem System geben.

Neben dem Gradientenverfahren, gibt es auch noch eine Menge anderer Optimierer, auf die wir später nochmal eingehen werden.

3. Zusammenfassung

Bevor wir TensorFlow nutzen, ist es wichtig, dass wir unser Modell verstehen. TensorFlow ist wie vieles nur ein Werkzeug, wenn man die Grundlagen nicht verstanden hat. Daher haben wir uns in diesem Artikel erstmal auf die Theorie konzentriert und ich habe dabei versucht mich auf das Wesentliche zu beschränken. 

Im nächsten Artikel werden wir dann unser Modell in TensorFlow realisieren.

PS: In einem separaten Artikel wollen später nochmal detaillierter auf Aktivierungsfunktion, Kreuzentropie und das Gradientenverfahren eingehen.

Predictive maintenance in Semiconductor Industry: Part 1

The process in the semiconductor industry is highly complicated and is normally under consistent observation via the monitoring of the signals coming from several sensors. Thus, it is important for the organization to detect the fault in the sensor as quickly as possible. There are existing traditional statistical based techniques however modern semiconductor industries have the ability to produce more data which is beyond the capability of the traditional process.

For this article, we will be using SECOM dataset which is available here.  A lot of work has already done on this dataset by different authors and there are also some articles available online. In this article, we will focus on problem definition, data understanding, and data cleaning.

This article is only the first of three parts, in this article we will discuss the business problem in hand and clean the dataset. In second part we will do feature engineering and in the last article we will build some models and evaluate them.

Problem definition

This data which is collected by these sensors not only contains relevant information but also a lot of noise. The dataset contains readings from 590. Among the 1567 examples, there are only 104 fail cases which means that out target variable is imbalanced. We will look at the distribution of the dataset when we look at the python code.

NOTE: For a detailed description regarding this cases study I highly recommend to read the following research papers:

  •  Kerdprasop, K., & Kerdprasop, N. A Data Mining Approach to Automate Fault Detection Model Development in the Semiconductor Manufacturing Process.
  • Munirathinam, S., & Ramadoss, B. Predictive Models for Equipment Fault Detection in the Semiconductor Manufacturing Process.

Data Understanding and Preparation

Let’s start exploring the dataset now. The first step as always is to import the required libraries.

There are several ways to import the dataset, you can always download and then import from your working directory. However, I will directly import using the link. There are two datasets: one contains the readings from the sensors and the other one contains our target variable and a timestamp.

The first step before doing the analysis would be to merge the dataset and we will us pandas library to merge the datasets in just one line of code.

Now let’s check out the distribution of the target variable

Figure 1: Distribution of Target Variable

From Figure 1 it can be observed that the target variable is imbalanced and it is highly recommended to deal with this problem before the model building phase to avoid bias model. Xgboost is one of the models which can deal with imbalance classes but one needs to spend a lot of time to tune the hyper-parameters to achieve the best from the model.

The dataset in hand contains a lot of null values and the next step would be to analyse these null values and remove the columns having null values more than a certain percentage. This percentage is calculated based on 95th quantile of null values.

Figure 2: Missing percentge in each column

Now we calculate the 95th percentile of the null values.

Figure 3: Missing percentage after removing columns with more then 45% Na

From figure 3 its visible that there are still missing values in the dataset and can be dealt by using many imputation methods. The most common method is to impute these values by mean, median or mode. There also exist few sophisticated techniques like K-nearest neighbour and interpolation.  We will be applying interpolation technique to our dataset. 

To prepare our dataset for analysis we should remove some more unwanted columns like columns with near zero variance. For this we can calulate number of unique values in each column and if there is only one unique value we can delete the column as it holds no information.

We have applied few data cleaning techniques and reduced the features from 590 to 444. However, In the next article we will apply some feature engineering techniques and adress problems like the curse of dimensionality and will also try to balance the target variable.

Bleiben Sie dran!!

Fuzzy Matching mit dem Jaro-Winkler-Score zur Auswertung von Markenbekanntheit und Werbeerinnerung

Für Unternehmen sind Markenbekanntheit und Werbeerinnerung wichtige Zielgrößen, denn anhand dieser lässt sich ableiten, ob Konsumenten ein Produkt einer Marke kaufen werden oder nicht. Zielgrößen wie diese werden von Marktforschungsinstituten über Befragungen ermittelt. Dafür wird in regelmäßigen Zeitabständen eine gleichbleibende Anzahl an Personen befragt, ob diese sich an Marken einer bestimmten Branche erinnern oder sich an Werbung erinnern. Die Personen füllen dafür in der Regel einen Onlinefragebogen aus.

Die Ergebnisse der Befragung liegen in einer Datenmatrix (siehe Tabelle) vor und müssen zur Auswertung zunächst bearbeitet werden.

Laufende Nummer Marke 1 Marke 2 Marke 3 Marke 4
1 ING-Diba Citigroup Sparkasse
2 Sparkasse Consorsbank
3 Commerbank Deutsche Bank Sparkasse ING-DiBa
4 Sparkasse Targobank

Ziel ist es aus diesen Daten folgende 0/1 codierte Matrix zu generieren. Wenn eine Marke bekannt ist, wird in die zur Marke gehörende Spalte eine Eins eingetragen, ansonsten eine Null.

Alle Marken ING-Diba Citigroup Sparkasse Targobank
ING-Diba, Citigroup, Sparkasse 1 1 1 0
Sparkasse, Consorsbank 0 0 1 0
Commerzbank, Deutsche Bank, Sparkasse, ING-Diba 1 0 0 0
Sparkasse, Targobank 0 0 1 1

Der Workflow um diese Datentransformation durchzuführen ist oftmals mittels eines Teilstrings einer Marke zu suchen ob diese in einem über alle Nennungen hinweg zusammengeführten String vorkommt oder nicht (z.B. „argo“ bei Targobank). Das Problem dieser Herangehensweise ist, dass viele falsch geschriebenen Wörter so nicht erfasst werden und die Erfahrung zeigt, dass falsch geschriebene Marken in vielfältigster Weise auftreten. Hier mussten in der Vergangenheit Mitarbeiter sich in stundenlangem Kampf durch die Ergebnisse wühlen und falsch zugeordnete oder nicht zugeordnete Marken händisch korrigieren und alle Variationen der Wörter notieren, um für die nächste Befragung das Suchpattern zu optimieren.

Eine Alternative diesen aufwändigen Workflow stellt die Ermittlung von falsch geschriebenen Wörtern mittels des Jaro-Winkler-Scores dar. Dafür muss zunächst die Jaro-Winkler-Distanz zwischen zwei Strings berechnet werden. Diese berechnet sich wie folgt:

d_j = \frac{1}{3}(\frac{m}{|s_1|}+\frac{m}{|s_2|}+\frac{m - t}{m})

  • m: Anzahl der übereinstimmenden Buchstaben
  • s: Länge des Strings
  • t: Hälfte der Anzahl der Umstellungen der Buchstaben die nötig sind, damit Strings identisch sind. („Ta“ und „gobank“ befinden sich bereits in der korrekten Reihenfolge, somit gilt: t = 0)

Aus dem Ergebnis lässt sich der Jaro-Winkler Score berechnen:
d_w = \d_j + (l_p (1 - d_j))
ist dabei die Jaro-Winkler-Distanz, l die Länge der übereinstimmenden Buchstaben von Beginn des Wortes bis zum maximal vierten Buchstaben und p ein konstanter Faktor von 0,1.

Für die Strings „Targobank“ und „Tangobank“ ergibt sich die Jaro-Winkler-Distanz:

d_j = \frac{1}{3}(\frac{8}{9}+\frac{8}{9}+\frac{8 - 0}{9})

Daraus wird im nächsten Schritt der Jaro-Winkler Score berechnet:

d_w = 0,9259 + (2 \cdot 0,1 (1 - 0,9259)) = 0,9407407

Bisherige Erfahrungen haben gezeigt, dass sich Scores ab 0,8 bzw. 0,9 am besten zur Suche von ähnlichen Wörtern eignen. Ein Schwellenwert darunter findet sehr viele Wörter, die sich z.B. auch anderen Wörtern zuordnen lassen. Ein Schwellenwert über 0,9 identifiziert falsch geschriebene Wörter oftmals nicht mehr.

Nach diesem theoretischen Exkurs möchte ich nun zeigen, wie sich das Ganze praktisch anwenden lässt. Da sich das Ganze um ein fiktives Beispiel handelt, werden zur Demonstration der Praxistauglichkeit Fakedaten mit folgendem Code erzeugt. Dabei wird angenommen, dass Personen unterschiedlich viele Banken kennen und diese mit einer bestimmten Wahrscheinlichkeit falsch schreiben.

Ausführen:

Nun werden die Inhalte der Spalten in eine einzige Spalte zusammengefasst und jede Marke per Komma getrennt.

Damit Sonderzeichen, Leerzeichen oder Groß- und Kleinschreibung keine Rolle spielen, werden alle Strings vereinheitlicht und störende Zeichen entfernt.

Im nächsten Schritt wird geprüft welche Schreibweisen überhaupt existieren. Dafür eignet sich eine Word-Frequency-Matrix, mit der alle einzigartigen Wörter und deren Häufigkeiten in einem Vektor gezählt wird.

Danach wird eine leere Liste erstellt, in der iterativ für jedes Element des Suchvektors ein Charactervektor erzeugt wird, der Wörter enthält, die einen Jaro-Winker Score von 0,9 oder höher besitzen.

Jetzt wird ein leerer DataFrame erzeugt, der die Zeilenlänge des originalen DataFrames besitzt sowie die Anzahl der Marken als Spaltenlänge.

Im nächsten Schritt wird nun aus den ähnlichen Wörtern mit einer oder-Verknüpfung einen String erzeugt, der alle durch den Jaro-Winkler-Score identifizierten Wörter beinhaltet. Wenn ein Treffer gefunden wird, wird in der Suchspalte eine Eins eingetragen, ansonsten eine Null.

Zuletzt wird eine Spalte erzeugt, in die eine Eins geschrieben wird, wenn keine der Marken gefunden wurde.

Nach der fertigen Berechnung der Matrix können nun die finalen KPI´s berechnet und als Report in eine .xlsx Datei geschrieben werden.

Dieses Vorgehen kann natürlich nicht verhindern, dass sich jemand mit kritischem Auge die Daten anschauen muss. In mehreren Tests ergaben sich bei einer Fallzahl von ~10.000 Antworten Genauigkeiten zwischen 95% und 100%, was bisherige Ansätze um ein Vielfaches übertrifft.9407407