Teil 1: Praktischer Einstieg in Maschinelles Lernen

Einstieg

Ich beschäftige mich derzeit privat intensiv mit dem Thema „maschinelles Lernen“. Ein – wie ich finde – sehr interessantes Gebiet, mit dem wir uns in Zukunft vermehrt beschäftigen dürfen. Egal ob Online-Beratungen, Chat-Bots oder das Auswerten diversen Eingaben – immer mehr Schritte in unserem alltäglichen Leben werden durch KI automatisiert.

Und eben weil dieses Verfahren immer mehr an Bedeutung gewinnt, möchte ich dieses auch verstehen.

Mein Lernverfahren stärkt sich immer auf zwei großen Bausteinen – der Theorie und der Praxis. Darum ist die Quellenbeschaffung immer der erste Schritt bei mir. So einfach es für die Theorie auch sein mag, umso schwerer ist es praxisnahe Beispiele zu bekommen.

Vor allem bei einem so essentiellen Thema habe ich folgendes Problem: Es bringt mir nichts, wenn ich in 3 Zeilen Code eine Bilderkennung schreiben kann – wenn diese nur mit irgendwelchen Libs agiert. Hier ist das Lernergebnis meiner Meinung nach gleich null. 

Und mal ehrlich: 

Nicht nur, dass der Großteil der Beispiel im Netz auf Python zurückgreift, gibt es auch zusätzlich kein (oder nur wenige) Beispiele, in denen keine vorgefertigten Libs verwendet werden.

Um dem ein wenig entgegen zu wirken, schreibe ich euch hier mein eigenes „Beispiel“ zusammen und was ich mir dabei gedacht habe. Ziel ist es, ohne irgendwelche Libraries ein Konstrukt von maschinellem Lernen zu erstellen. Damit sollte dann auch Anfängern der Einstieg etwas leichter fallen.

Quellenbeschaffung

Primär splitte ich meine Quellen sehr gerne ich PDFs/Bücher und Videos. Deshalb bin ich auch recht schnell auf folgende Grundlagen gestoßen.

Buch: 

David Kriesel, 2007, Ein kleiner Überblick über Neuronale Netze,  http://www.dkriesel.com

Video(serie):

Machine Learning – Tutorial von „The Morpheus Tutorials“ (direkter Link)

Hiermit bewaffnet habe ich mir zunächst einen Überblick in die trockene Theorie gemacht. Und was soll ich sagen? Neben einem rauchenden Kopf habe ich nun einen kleinen Überblick über das Thema, auch ohne die Videos oder das Buch (bis dato) komplett gelesen zu haben.

Zeit das Vorhaben zu spezifizieren

Zunächst muss eine Idee her. Was kann man neben dem Theorie durchstöbern erstellen, um die neuen Erkenntnisse direkt umsetzen zu können? Im Netz findet man viele Verwendungszwecke von maschinellem Lernen.

Ich finde die Erkennung von Handschrift bei Tablets sehr interessant. Jeder kennt es: mit einem Stift auf einem Tablet rum kritzeln und es wird in Text bzw. Buchstaben umgewandelt. Gesagt, geplant.

Um das Ganze (zunächst) etwas einzuschränken, lege ich mich auf eine Schrifterkennung für die Zahlen von 0 bis 9 fest.

Python?! – Nein, danke..

Ich bin weder Verfechter, noch absoluter Fan von Python. Umso mehr bin ich an Javascript, PHP, TypeScript, etc. interessiert. Deshalb liegt es Nahe, das Ganze auf Web-Basis umzusetzen. Um das Kompilieren von Typescript zu umgehen, lege ich mich direkt auf Javascript fest. Die Möglichkeiten der Ein- und Ausgabe werden in HTML umgesetzt.

WICHTIG

1) Ich habe das weder studiert noch gelernt – alles was ich hier schreibe bezieht sich darauf, wie ich es verstanden habe. Eine Gewährleistung für 100%ige Richtigkeit gibt es nicht!

2) Die Website wird im Grunde nur aus ein wenig JavaScript und HMTL entstehen, die Daten an sich werden im Hintergrund nicht gespeichert, dass kann sich jeder selbst überlegen. Ich zeige nur an den geeigneten Stellen eine Möglichkeit Werte abzulegen.

„Supervised“ oder „unsupervised“

Nun, zunächst eine kurze Erklärung zum Unterschied der beiden:

Unter dem überwachtem Lernen (supervised) versteht man das Arbeiten mit gelabelten Trainingsdaten. Sprich wir kennen für eine spezielle Eingabe auch die resultierende Ausgabe.

Im Gegensatz dazu steht das unüberwachte Lernen, bei dem wir Daten in den Algorithmus geben, und dieser diese klassifiziert, ohne das bereits von Daten die wirkliche Klassifizierung bekannt ist.

Für unser Beispiel ist das überwachte Lernen perfekt. Wir starten mit ein paar Eingaben, die wir mit dem Tatsächlichen Wert labeln. Danach geben wir eine Eingabe hinzu und schauen, was der Algorithmus damit macht.

grober Aufbau (Mockup)

(1) Es wird einen Bereich geben, in dem man mit der Maus die Zahlen zeichnen kann.
(2) Um die Daten zu labeln, benötigen wir natürlich auch eine Input-Box.
(3) Zur Speicherung der Daten und des Labels verwenden wir einen Button, ebenso wie zum
(4) Anstoßen der Auswertung für Test-Daten und 
(5) zum Löschen des derzeit gezeichneten Bereiches.

Gesamter Code

Den Code zum gesamten Projekt könnt ihr euch in meinem Github-Repository anschauen.

zunächst das Langweilige

Ein Mockup in eine Website umzusetzen kann, je nach Komplexität interessant werden. Jedoch wollen wir uns nicht wirklich lange damit aufhalten, deshalb hier eine mögliche Umsetzung:

Um das Canvas-Element editierbar zu machen, habe ich aus folgendem Post das Herangehen verwendet und für meinen speziellen Fall angepasst:
Create a drawing app with HTML5 canvas and javascript

index.html

<link rel="stylesheet" type="text/css" href="styles.css">
<div class="wrapper">
    <canvas id="canvas" width="75" height="75"></canvas>
    <div class="option-wrapper">
        <div class="option-label">
            <input id="label" type="number" />
            <button onclick="addTestData()">Eingabe labeln</button>
        </div>
        <div class="option-testing">
            <button onclick="predict()">Testen</button>
            <input id="output" type="number"></div>
        </div>
    </div>
</div>
<button class="erase" onclick="erase()">Löschen</button>

<script src="script.js"></script>

styles.css

.erase {
margin-top: 20px;
width: 75px;
margin-left: calc(25vw + 20px);
}

.wrapper {
width: 50vw;
margin-top: 10vh;
margin-left: 25vw;
padding: 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
border: 1px solid black;
}

canvas {
border: 1px solid black;
}

.option-wrapper {
width: calc(50vw - 100px);
display: flex;
flex-direction: column;
flex-wrap: wrap;
}

.option-label {
margin-bottom: 39px;
}

.option-label *:first-child,
.option-testing *:first-child {
width: 20vw;
margin-right: 20px;
}

.option-label *:not(:first-child),
.option-testing *:not(:first-child) {
width: 22vw;
}

.option-label,
.option-testing {
display: flex;
flex-direction: row;
}

script.js

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
var flag = false;
var prevX = 0;
var currX = 0;
var prevY = 0;
var currY = 0;
var testData = [];
var dot_flag = false;

function init() {
canvas.addEventListener("mousemove", function (e) {
findxy('move', e)
}, false);
canvas.addEventListener("mousedown", function (e) {
findxy('down', e)
}, false);
canvas.addEventListener("mouseup", function (e) {
findxy('up', e)
}, false);
canvas.addEventListener("mouseout", function (e) {
findxy('out', e)
}, false);
}

function draw() {
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.stroke();
ctx.closePath();
}

function erase() {
ctx.clearRect(0, 0, 75, 75);
}

function findxy(res, e) {
if (res == 'down') {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;

flag = true;
dot_flag = true;
if (dot_flag) {
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(currX, currY, 3, 3);
ctx.closePath();
dot_flag = false;
}
}

if (res == 'up' || res == "out") {
flag = false;
}

if (res == 'move') {
if (flag) {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
draw();
}
}
}

init();

das System von maschinellem Lernen

Die meisten Tutorials lassen auf eine folgende Beschreibung schätzen:

Wir haben eine Eingabe, die mittels super-dooper-programmierer Voodoos zu einer Ausgabe gewandelt wird. 

So simpel – so unverständlich. Verstärkt wird dies mit ewig vielen Mathematischen Gleichungen und einem Wust von i’s, k’s, j’s, Omegas, Zetas, Deltas und Tappas. (<.<)

Also hier eine hoffentlich simplere Erklärung in Zusammenhang mit unserem Beispiel:

Wir haben unsere Zeichenbox mit 75×75 Pixeln. Standardmäßig ist jeder dieser Pixel weiß. Wenn wir nun darauf zeichnen, werden einige Pixel eingefärbt (schwarz). Zeichen wir z.B. eine „1“, bekommen wir ein Array mit 5625 Elementen (Wert zwischen 0 und 255). Dieses Array können wir, da wir es ja wissen, mit einer 1 Labeln. Angenommen wir machen das für alle Zahlen von 0 bis 9. Dann haben wir folgenden Daten-Pool:

[
{label: 0, data: [..(5625)..]},
{label: 1, data: [..(5625)..]},
{label: 2, data: [..(5625)..]},
{label: 2, data: [..(5625)..]},
{label: 3, data: [..(5625)..]},
{label: 4, data: [..(5625)..]},
{label: 5, data: [..(5625)..]},
{label: 6, data: [..(5625)..]},
{label: 7, data: [..(5625)..]},
{label: 8, data: [..(5625)..]},
{label: 9, data: [..(5625)..]},
]

Der Input eines maschinellen Algorithmus ist meinst eine Matrix. Hier besteht unsere Matrix aus dem Array mit 5625 Elementen, d.h. wir hätten eine 5625-dimensionalen Eingabe. Auf ein entsprechendes Diagramm verzichte ich an der Stelle, da es schon beim vierten Element meinen und vermutlich auch euren Horizont übersteigt.

Was wissen wir nun?

Unsere Daten spezifizieren ein Label. Wie kommen wir nun für einen ungelabelten Input auf ein entsprechendes Label anhand unserer Testdaten?

Hierfür gibt es ebenfalls viele verschiedene Vorgehensweisen, wobei ich fürs erste bei der euklidischen Distanz und dem knn-Algorithmus hängen geblieben bin.

Die euklidische Distanz berechnet einen Abstand zwischen Zwei Punkten.

Es wird für jede Eigenschaft der Punkte der Wert voneinander subtrahiert und anschließend quadiert, daraus die Summe gebildet und am Ende die Wurzel gezogen.

Quadrat? Wurzel? Was?

Am besten lässt sich das an einem Beispiel erklären:

Wir haben die Punkte

  • A = (1, 2, 3)
  • B = (1, 2, 4)
  • C = (-2, 2, 3)

Wie weit sind diese Punkte voneinander entfernt?

Wenn wir die Punkte betrachten, ist der Unterschied von A und B die letzte Eigenschaft (Differenz = 1). Beim Vergleich von A und C ist es die erste Eigenschaft mit einer Differenz von -3. Differenz von MINUS 3? Ist das jetzt besser oder schlechter? Da wir einen Abstand von Punkten berechnen, bei dem ein Punkt auch mal grafisch gesehen „unter“ einem anderen liegen kann, interessiert uns nur der Betrag des Abstandes. Und genau das macht die euklidische Distanz mit den vielen Quadraten und dem Wurzeln ziehen. Probiert es aus und spielt damit ein wenig rum 😉

Distanz A zu B:

Math.sqrt(Math.pow(1 - 1, 2) + Math.pow(2 - 2, 2) + Math.pow(3 - 4, 2)); // 1

Distanz A zu C:

Math.sqrt(Math.pow(1 - (-2), 2) + Math.pow(2 - 2, 2) + Math.pow(3 - 3, 2)); // 3

Sprich Punkt C ist von Punkt A weiter entfernt als Punkt B. Sollte soweit einleuchten, oder?

was k*nn der knn?

Der KNN-Algorithmus nimmt die Eingabe und berechnet für alle Testbeispiel die Distanz, sprich wie weit unterscheidet sich die Eingabe von unseren bisherigen Werten. Hiervon nimmt der die „k“-nähsten Elemente und schaut sich deren Label an. Zurückgegeben wird das Label mit der Mehrheit.

Bsp:

Eingabe: {label: ?, data:[….]}

Distanzen: 

[
{label: 1, distanz: 1000},
{label: 2, distanz: 430},
{label: 2, distanz: 130},
{label: 3, distanz: 220},
{label: 4, distanz: 550},
{label: 4, distanz: 1111},
]

Bei einem K von 3 würden wir folgende Distanzen betrachten:

[
{label: 2, distanz: 430},
{label: 2, distanz: 130},
{label: 3, distanz: 220},
]

Dadurch haben wir die Mehrheit bei 2 und würden den Input mit 2 labeln.

That’s it. – Soweit zur Theorie – hier nun die Umsetzung in Code 🙂

Teil 2: Praktischer Einsteig in maschinelles Lernen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.