JS - Artificial Intelligence und Machine Learning

Artificial Intelligence (AI) und Machine Learning (ML) mit JavaScript

Artificial Intelligence (AI) und Machine Learning (ML) sind immer wieder heiß diskutierte und auch zukunftsträchtige Themen. Zunächst sollen die beiden Begriffe im Folgenden aber voneinander abgegrenzt werden.

Artificial Intelligence (AI)

Artificial Intelligence (dt. „künstliche Intelligenz“) ist ein Bereich der Computerwissenschaften, bei dem es darum geht, dass Maschinen, also Computer, den Menschen imitieren können. Das betrifft z.B. das Sprechen, das Denken, aber auch die Planung und das Lernen. Computer sollen dabei eine Intelligenz besitzen, die es ihnen ermöglicht selbst zu lernen, ohne, dass sie speziell programmiert werden.

Hier gilt es aber auch zwischen der Narrow AI / Weak AI und der Strong AI zu unterscheiden. Während die Narrow AI darauf ausgerichtet ist, die menschliche Intelligenz zu simulieren (z.B. Navigation, Textkorrektur, Sprachassistenten, Vorschläge), geht es bei der Strong AI darum, die menschliche Intelligenz zu kopieren (z.B. denken, planen, lernen und kommunizieren wie ein Mensch).

Machine Learning (ML)

Machine Learning ist ein Teil von Artificial Intelligence. Wie der Name sagt, geht es dabei um das Lernen bzw. das Trainieren der Maschine, also der künstlichen Intelligenz. In der traditionellen Programmierung verarbeitet man Daten mit einem bekannten Algorithmus zu einem Ergebnis. Beim Machine Learning hingegen werden aus Daten und einem bereits bekannten Ergebnis neue Algorithmen erstellt. So wird eine künstliche Intelligenz (Artificial Intelligence) aufgebaut, die im besten Fall irgendwann in der Lage ist, selbst neue Algorithmen zu erstellen und sich so selbst zu trainieren.

AI / ML mit JavaScript

JavaScript zählt vielleicht nicht unbedingt zu den am häufigsten verwendeten Sprachen für die Programmierung von AI bzw. für das ML. Dabei gibt es aber dennoch eine ganze Reihe an möglichen Anwendungsfällen, in denen man mit einer, auf JavaScript basierenden, AI die Funktionalität und die Benutzererfahrung einer Website verbessern kann. An dieser Stelle nur einige Beispiele:

  • Chatbots
  • Personalisierte Empfehlungen / Inhalte
  • Sprachgesteuerte Interaktion
  • Automatisierte Kundenbetreuung
  • Browser-Spiele
  • Datenvorhersagen und -analysen
  • Bilderkennung und -kategorisierung

Dies ist nur eine Handvoll Möglichkeiten für den Einsatz von JavaScript im Bereich Artificial Intelligence / Machine Learning. Nachfolgend wird das Beispiel „personalisierte Empfehlungen / Inhalte“ aufgegriffen. Anhand eines kleinen Skripts soll verdeutlicht werden, wie AI / ML in Verbindung mit JavaScript eine Website verbessern kann.

Ein Beispiel für AI / ML mit JavaScript

Angenommen man ist der Betreiber eines Onlineshops und möchte den Absatz durch, auf den jeweiligen User zugeschnittene, Artikel steigern (Cross- / Up-Selling) – in diesem Fall hat man mit Artificial Intelligence und einem entsprechenden Machine Learning nahezu unbegrenzte Möglichkeiten.

Diese alle im Detail und mit entsprechender Dynamic aufzuzeigen, würde den Rahmen dieses Artikels deutlich sprengen. Das folgende Beispiel beschränkt sich daher auf das Ausspielen von empfohlenen Artikeln auf Basis von Daten anderer Nutzer und darauf, ob sie sich den Artikel angesehen haben oder nicht. Das Beispiel soll lediglich dazu dienen, einen ersten kleinen Eindruck von und Einstieg in die künstliche Intelligenz zu vermitteln.

Ausgangslage

Einem User sollen Artikel vorgeschlagen werden, die er noch nicht gesehen hat und die gerne von anderen Usern angesehen wurden. Damit das Beispiel nicht zu komplex wird, liegen Daten von 20 Usern vor. Zu jedem User gibt es einen Datensatz, der jeweils die ID von einem von vier möglichen Artikeln enthält (itemID) sowie die Information darüber, ob der User den Artikel angesehen hat oder nicht (viewed).

const userInteractions = [
  { uID: 1, itemID: 1, viewed: 1 },
  { uID: 2, itemID: 1, viewed: 1 },
  { uID: 3, itemID: 2, viewed: 0 },
  { uID: 4, itemID: 3, viewed: 1 },
  { uID: 5, itemID: 1, viewed: 1 },
  { uID: 6, itemID: 2, viewed: 1 },
  { uID: 7, itemID: 4, viewed: 0 },
  { uID: 8, itemID: 2, viewed: 1 },
  { uID: 9, itemID: 1, viewed: 0 },
  { uID: 10, itemID: 3, viewed: 1 },
  { uID: 11, itemID: 2, viewed: 0 },
  { uID: 12, itemID: 3, viewed: 0 },
  { uID: 13, itemID: 4, viewed: 1 },
  { uID: 14, itemID: 1, viewed: 1 },
  { uID: 15, itemID: 3, viewed: 1 },
  { uID: 16, itemID: 2, viewed: 1 },
  { uID: 17, itemID: 4, viewed: 0 },
  { uID: 18, itemID: 1, viewed: 1 },
  { uID: 19, itemID: 2, viewed: 1 },
  { uID: 20, itemID: 3, viewed: 0 }
];

Hinweis: Für die Verarbeitung dieser Daten, bis hin zur letztendlichen Empfehlung eines oder mehrerer Artikel, wird in diesem Beispiel das JavaScript von TensorFlow als Unterstützung verwendet. Machine Learning basiert auf Mathematik. Je komplexer die Daten und / oder das gewünschte Ergebnis, desto komplexer kann auch die dahinterstehende Mathematik werden. Hierfür gibt es einige Libraries, die einem viel Arbeit abnehmen – wie in diesem Fall TensorFlow.

Der Tensor

Im nächsten Schritt wird mittels TensorFlow ein Tensor erstellt. Machine Learning basiert auf Mathematik. Ein Tensor ist ein Begriff aus der linearen Algebra.

In der linearen Algebra wird zwischen einem Scalar, einem Vektor, einer Matrix und einem Tensor unterschieden. Diese Begriffe tauchen auch im Machine Learning auf.

Ein Scalar ist eine einfache Ziffer, also bspw. die 1. Dies ist die kleinste Einheit, aus der sich Vektoren, Matrizen und Tensoren zusammensetzen.

Bei einem Vektor handelt es sich, übersetzt auf die Programmierung, um ein 1-dimensionales Array, z.B.:

[ 1, 2, 3]

Bei einer Matrix hingegen handelt es sich um ein mehrdimensionales Array, z.B.:

[ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]

Bei Matrizen wird weiterhin in der linearen Algebra noch in weitere Unterformen unterschieden. Diese hier alle aufzulisten und zu erklären, würde den Artikel sprengen bzw. weit vom Thema weggehen.

Ein Tensor ist mehr oder weniger ein Oberbegriff. Ein Tensor kann ein 0-dimensionaler Scalar sein, ein 1-dimensionaler Vektor oder eine 2-dimensionale Matrix. Er kann aber auch, wieder übersetzt auf die Programmierung, ein n-dimensionales Array sein. Damit ist ein Tensor also bspw. auch eine Zusammensetzung aus mehreren Matrizen.

Im Beispiel aus diesem Artikel ist der Tensor, den wir brauchen, ein Array, dass mittels 0 und 1 pro User darstellt, welche Artikel er gesehen hat. Nachfolgend die Ausgabe (mittels tensor.print()) des Tensors aus dem Beispiel in diesem Beitrag:

[[1, 0, 0, 0],
[1, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 1, 0],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 1],
[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0]]

Aus den Daten, die in der Ausgangslage des Beispiels beschrieben sind, lässt sich ablesen, dass wir insgesamt vier Artikel haben. Die Property itemID steht jeweils für einen Artikel, der vom jeweiligen User angesehen wurde oder nicht (viewed). Da es nur itemIDs von 1 – 4 gibt, resultieren daraus maximal vier unterschiedliche Artikel. Diese werden in o.g. Tensor durch die vier Spalten je Array dargestellt. Jede Reihe in diesem Array entspricht dabei einem User aus den Daten der Ausgangslage. D.h. o.g. Tensor zeigt je User alle vier Artikel und ob er den jeweiligen Artikel gesehen (= 1) hat oder nicht (= 0).

Dieser Tensor wird mit folgendem Code erzeugt:

const usersCount = Math.max(...userInteractions.map(interaction => interaction.uID));
const itemsCount = Math.max(...userInteractions.map(interaction => interaction.itemID));
let tensor = tf.zeros([usersCount, itemsCount]);
const tensorArr = tensor.arraySync();

userInteractions.forEach(interaction => {
  tensorArr[interaction.uID - 1][interaction.itemID - 1] = interaction.viewed;
});

tensor = tf.tensor(tensorArr);

In o.g. wird zunächst der maximale Wert an Usern (= Wie viele unterschiedliche User gibt es in unseren Daten?) ermittelt. Zusätzlich wird ebenfalls die maximale Anzahl an Artikeln ermittelt (= Wie viele unterschiedliche itemIDs gibt es in unseren Daten?).

Die Ergebnisse werden an die zeros-Funktion von TensorFlow übergeben. Die Funktion erstellt ein Array in der o.g. Form, d.h. eine Reihe pro User mit jeweils vier Spalten für die Items / Artikel. Dieses Array ist zunächst allerdings ausschließlich mit Nullen gefüllt. Um nun die Information darüber zu speichern, welcher User welchen Artikel angesehen hat, wird jeweils die viewed-Property aus den Ausgangsdaten, innerhalb eines forEach an entsprechender Stelle im Array eingefügt.

Das dann vorliegende, multidimensionale Array, wird zuletzt noch mit der tensor-Funktion von TensorFlow in einen tatsächlichen Tensor umgewandelt, mit dem TensorFlow dann weiterarbeiten und trainieren kann. Das ist wichtig, da TensorFlow dem fertigen Tensor (tensor-Variable aus o.g. Code) entsprechende Eigenschaften und Funktionen hinzufügt. Es handelt sich damit also nicht mehr lediglich um ein multidimensionales Array, sondern um ein Tensor-Objekt.

Das Training

Bisher wurden die Daten lediglich für das eigentliche Training, das Machine Learning, vorbereitet. Nun wird trainiert… Dafür wird in diesem Beispiel die folgende Funktion geschrieben:

async function train() {
  const model = tf.sequential();

  model.add(tf.layers.dense({ units: 64, inputShape: [itemsCount], activation: 'relu' }));
  model.add(tf.layers.dense({ units: 1, activation: 'sigmoid' }));
  model.compile({ optimizer: 'adam', loss: 'binaryCrossentropy', metrics: ['accuracy'] });

  const inputs = tf.tensor(tensor.arraySync());
  const outputs = tf.tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
  await model.fit(inputs, outputs, { epochs: 20 });

  return model;
}

Zuerst wird mit der sequential-Funktion von TensorFlow ein sequenzielles Model erstellt. Dabei handelt es sich um ein Model, bei dem aus dem Input eines Layers der Output eines anderen Layers erstellt wird. Mit model.add() werden diese Layer dem Model hinzugefügt.

Für den ersten Layer wird u.a. eine inputShape gesetzt. Dabei handelt es sich im Beispiel um die Anzahl an möglichen Artikeln zum Vorschlagen (= 4). Außerdem wird als Aktivierungsfunktion die Rectified Linear Units (ReLU) Funktion verwendet. Das ist eine häufig verwendete Funktion in Machine Learning Models. Die Funktion gibt bei jedem negativen Input 0 zurück und andernfalls den tatsächlichen Wert.

Für den zweiten Layer wird hingegen die Sigmoidfunktion als Aktivierungsfunktion verwendet. Ohne zu sehr in mathematische Details zu gehen, sei an der Stelle nur erklärt, dass die Sigmoidfunktion in diesem Beispiel dem neuronalen Netz eine Nichtlinearität hinzufügt. Sie nimmt die Eingabewerte entegegen und bildet sie in einem Bereich zwischen 0 und 1 ab. Somit erhalten wir ganz am Ende eine entsprechende Gewichtung der Artikel.

Nachdem die Layer dem Model hinzugefügt werden, wird dieses kompiliert. D.h. es wird auf das Training vorbereitet. Im Beispiel geschieht das mit der compile-Funktion, der ein Optimierer, ein Verlust und eine Metrik zugewiesen werden. Als Optimierer wird der Adam (Adaptive Moment Estimation) Optimierer verwendet. Adam ist ein Optimierungsalgorithmus, der verwendet werden kann, um die Gewichtungen iterativ anhand von Trainingsdaten zu aktualisieren.

Als Verlustfunktion wird Binary Cross Entropy verwendet. Binary Cross Entropy wurde entwickelt, um die Unähnlichkeit zwischen der vorhergesagten Wahrscheinlichkeitsverteilung und den wahren binären Kennzeichnungen eines Datensatzes zu messen. Sie vergleicht also jede der vorhergesagten Wahrscheinlichkeiten mit der tatsächlichen Ausgabe. Anschließend wird eine Punktzahl errechnet, welche die Wahrscheinlichkeiten auf Grundlage von deren Abstand zum erwarteten Wert bestraft.

Für die Metrik wird die Genauigkeit (Accuracy) verwendet. Die Genauigkeit ist eine Metrik, mit der man misst, wie oft das Ergebnis korrekt vorhersagt wurde. Die Genauigkeit wird einfach berechnet, indem die Anzahl der korrekten Vorhersagen durch die Gesamtzahl der Vorhersagen dividiert wird.

Zuletzt wird das Model mit der fit-Funktion für die übergebene Anzahl an Epochen trainiert. Dafür wird zuvor das Array des Eingangs erstellten Tensors übergeben sowie ein Array mit gleicher Länge für den Output.

Anmerkung: Die in diesem Beispiel verwendeten Funktionen, Metriken, usw. sind kein Allheilmittel für Machine Learning. Das ist ein vereinfachtes Beispiel und je nachdem, was man erreichen möchte und welche Daten man besitzt, können andere Vorgehensweisen besser geeignet sein. In diesem Artikel geht es nicht darum, Machine Learning von A-Z beizubringen, sondern er dient als Anreiz, sich mit dem Thema näher zu befassen und Ideen für Machine Learning zu generieren.

Empfehlungen abrufen

Nachdem es jetzt im Beispiel-Skript Daten als Ausgangslage sowie ein Training gibt, müssen die Empfehlungen zum Schluss noch abgerufen werden. Dazu wird dem Skript noch der folgende Code hinzugefügt:

function generateRecommendations(model, userId) {
  const userViewed = tensor.arraySync()[userId - 1];
  const predictions = model.predict(tensor).arraySync();

  const recommendedArticles = predictions.map((prediction, index) => ({
    itemID: userInteractions[index].itemID,
    predict: prediction >= .5
  })).filter(article => !userViewed[article.itemID - 1] && article.predict);

  const uniqueRecommendedArticles = [...new Map(recommendedArticles.map(item => [item['itemID'], item])).values()];

  return uniqueRecommendedArticles;
}

let cnt = 0;

async function getRecommendations() {
  const model = await train();
  const recommendations = generateRecommendations(model, 1);

  if (recommendations.length === 0 && cnt <= 50) {
    console.log('No recommendations available');
    cnt++;
    getRecommendations();
  } else {
    console.log('recommendations:', recommendations);
  }
}

getRecommendations();

Wie o.g. wird die Funktion getRecommendations aufgerufen. Diese trainiert bei jedem Aufruf zunächst das Model und ruft dann aus dem Model die Artikelvorschläge ab (generateRecommendations-Funktion). Damit keine Endlosschleife entsteht, wird die getRecommendations-Funktion maximal 50x durchlaufen bzw. so lange, bis mindestens ein Artikelvorschlag existiert.

In der generateRecommendations-Funktion werden die Vorschläge aus dem zuvor trainierten Model ausgewertet. Jeder Artikel im Model ist mit einer Gewichtung zwischen 0 und 1 versehen. In diesem Beispiel sollen Artikel nur dann empfohlen werden, wenn sie eine höhere Gewichtung haben als 0,5. Dabei werden vorher ebenfalls die Artikel aus den Empfehlungen entfernt, die der User bereits gesehen hat. Im Beispiel wird – beim Aufruf der generateRecommendations-Funktion – der User mit der ID 1 als aktueller User übergeben. D.h. Artikel, die der User mit der ID 1 schon gesehen hat, werden ihm nicht erneut vorgeschlagen.

Jeder Durchlauf der getRecommendations-Funktion endet mit einer Ausgabe in der Console der DevTools im Browser. Wurden keine Vorschläge generiert, wird dies ebenso geloggt wie eine Auflistung der finalen Artikelvorschläge. Lässt man dieses Skript mehrfach laufen, erkennt man auf der Console, dass nicht direkt beim ersten Durchlauf Ergebnisse geliefert werden. Vielmehr verhalten sich einzelne Aufrufe des Skripts z.T. unterschiedlich. Manchmal braucht das Skript mehr Durchläufe durch die getRecommendations-Funktion – bis zur Ausgabe einer finalen Liste mit Vorschlägen – manchmal geht es etwas schneller. Das zeigt, dass hier ein tatsächliches Machine Learning stattfindet, nicht bloß eine reine lineare Verarbeitung und Ausgabe der Ausgangsdaten.

Weitere Möglichkeiten

Wie o.g. ist das ein sehr vereinfachtes Beispiel und noch lange nicht das Ende des Machine Learnings und der Artificial Intelligence. TensorFlow bietet unzählige Möglichkeiten, um das Machine Learning zu unterstützen. So kann man bspw. noch weitaus genauere und u.U. auch andere Ergebnisse mit anderen Funktionen erhalten. Ebenso spielen die zugrundeliegenden Daten eine sehr wichtige Rolle bei den Ergebnissen und auch beim Training / Learning. Bei einem echten Einsatz liegen u.U. deutlich mehr Daten vor, bei denen jeder Datensatz wahrscheinlich noch deutlich komplexer ist. So können bspw. auch Faktoren wie das Alter, die Herkunft, das Geschlecht, besuchte andere Websites uvm. mit einbezogen werden. Demnach braucht man dann auch andere Funktionen, die zum besten Ergebnis führen.

Eingangs wurde erwähnt, dass man im besten Fall eine Maschine haben möchte, die sich selbst anlernt. Das ist in Ansätzen in o.g. Beispiel schon gegeben, kann aber durchaus in der Realität sehr weit vertieft werden. Auch das hier näher dazustellen, würde den Rahmen sprengen… Bspw. könnte man Daten des Users sammeln, während er die Seite verwende. Das können Daten sein, die die Fragen klären „Auf welchen Artikel hat der User geklickt?“, „Wie lange bleibt er auf welcher Seite?“, „Wo scrollt er langsamer, weil er scheinbar am Lesen ist?“, etc..

Weiter ist es auch denkbar, dass man das ML in einen Webworker auslagert. Man könnte dann – ohne große Performanceverluste – Daten aktualisieren und ergänzen, die man nahezu live von anderen Usern erfasst hat, wie z.B. „Welche Artikel schauen sich welche User gerade in diesem Moment besonders oft an?“. Die Daten anderer User und die des Users, für den man personalisierte Inhalte anzeigen möchte, kann man so sukzessive – live – dem Training hinzufügen und noch bessere Empfehlungen erhalten.

Wie Sie sehen, sind die Möglichkeiten nahezu unbegrenzt und genau das ist das Schöne an Artificial Intelligence. Wenn Sie ebenfalls eine Idee für den Einsatz von künstlicher Intelligenz auf Ihrer Website haben und dafür Unterstützung benötigen, kontaktieren Sie uns gerne über die unten zur Verfügung stehenden Kontaktmöglichkeiten. Wir freuen uns über jedes spannende Projekt in dieser zukunftsträchtigen und beinahe unbegrenzten Technologie.

Übersicht von Webentwicklung