Erstellen Sie eine inhaltsbasierte Empfehlungsmaschine in JS

Maschinelles Lernen ist schon lange auf meinem Radar, aber ich habe mich nie wirklich an die Arbeit gemacht und tatsächlich angefangen, es zu lernen. Das ist bis vor kurzem. Ich bin ein serieller Lerner und da nichts geplant war, entschied ich mich, etwas maschinelles Lernen in Angriff zu nehmen. Ich habe es mir zur Aufgabe gemacht, eine Empfehlungsmaschine zu erstellen. Wir interagieren jeden Tag mit ihnen, über soziale Medien und Online-Shopping sowie an so vielen anderen Orten. Ich habe einen einfachen Datensatz aus dem Internet verwendet, der aus 20 Bildern mit den Ergebnissen einer Google Vision-API-Anfrage bestand. Mein Ziel war es, andere Bilder aus der Sammlung zu empfehlen, wenn ein einzelnes Bild ausgewählt wird.

Mir ist klar, dass Python wahrscheinlich die bessere Wahl für diese Aufgabe gewesen wäre, aber ich kenne Javascript sehr gut und wollte nicht die zusätzliche Last, die Engine in einer Sprache zusammensetzen zu müssen, mit der ich nicht 100 % vertraut bin.

Laut Wikipedia ist eine inhaltsbasierte Empfehlungsmaschine:

Empfehlungsmaschinen sind aktive Filtersysteme, die die Informationen, die ein Benutzer erhält, auf der Grundlage von Informationen, die über einen Benutzer bekannt sind, personalisieren. In unserem Fall handelt es sich bei diesen Informationen um das ursprünglich ausgewählte Bild und die Daten, die von Google Vision zurückgegeben wurden.

Am Ende dieses Artikels können wir einem Benutzer basierend auf seiner anfänglichen Bildauswahl weitere Bilder empfehlen.

Die Vor- und Nachteile

Bevor wir durchgehen, wie. Lassen Sie uns darüber sprechen, warum. Es gibt einen Grund, warum diese Art von Engine so beliebt ist, aber es wird auch Gründe geben, sie nicht zu verwenden.

Vorteile

  • Im Gegensatz zu anderen Methoden benötigt die inhaltsbasierte Filterung keine Daten anderer Benutzer, da die Empfehlungen benutzerspezifisch sind. Dadurch wird das Problem von Kaltstarts bei begrenzten Daten vermieden
  • Das Modell erfasst die spezifischen Interessen der Benutzer und kann so Nischenartikel empfehlen, die bei anderen Benutzern möglicherweise nicht beliebt sind

Nachteile

  • Das Modell kann nur Empfehlungen basierend auf bestehenden Interessen aussprechen. Dadurch werden die Empfehlungen auf bekannte Interessen beschränkt und die Erweiterung der Interessen des Nutzers verhindert
  • Sie sind auf die Richtigkeit der Labels angewiesen
  • Berücksichtigt nicht die Macken eines Benutzers. Sie mögen etwas, aber nur unter ganz bestimmten Umständen.

Wie funktionieren inhaltsbasierte Empfehlungsmaschinen

Eine inhaltsbasierte Empfehlungsmaschine arbeitet mit Daten, die ein Benutzer bereitstellt (in unserem Fall die Auswahl eines Bildes). Anhand dieser Daten können wir dem Nutzer Vorschläge machen.

In unserem Fall durchläuft unser Skript die folgenden Schritte:

  1. Schulung
    • Formatieren Sie Daten in einen verwendbaren Zustand
    • TF-IDF berechnen und Vektoren aus den formatierten Dokumenten erstellen
    • Ähnliche Dokumente berechnen
  2. Verwenden Sie trainierte Daten, um eine Empfehlung basierend auf der Bildauswahl des Benutzers auszusprechen.

Bevor wir mit dem Schreiben unserer Empfehlungsmaschine beginnen, müssen wir über einige Schlüsselkonzepte sprechen. Wie werden wir nämlich entscheiden, welche Daten wir empfehlen?

Die Begriffe Term Frequency (TF) und Inverse Document Frequency (IDF) werden verwendet, um die relative Wichtigkeit eines Begriffs zu bestimmen. Damit können wir das Konzept der Kosinusähnlichkeit verwenden, um zu bestimmen, was zu empfehlen ist. Wir werden diese im gesamten Artikel besprechen.

TF ist einfach die Häufigkeit, mit der ein Wort in einem Dokument vorkommt. Die IDF ist die Häufigkeit eines Begriffs in einem ganzen Korpus von Dokumenten. Es zeigt die Seltenheit eines Wortes an und hilft, die Punktzahl seltenerer Begriffe zu erhöhen. TD-IDF wird verwendet, weil es nicht nur den isolierten Begriff, sondern auch den Begriff innerhalb des gesamten Dokumentenkorpus berücksichtigt. Dieses Modell kombiniert die Wichtigkeit des Wortes im Dokument (lokale Wichtigkeit) mit der Wichtigkeit des Wortes im gesamten Korpus (globale Wichtigkeit)

Kosinusähnlichkeit ist eine Metrik, die verwendet wird, um die Ähnlichkeit von Dokumenten unabhängig von der Größe zu bestimmen. Mathematisch misst es den Kosinuswinkel zwischen 2 Vektoren. In unserem Kontext sind die Vektoren Objekte, die den Begriff als Schlüssel und die TF-IDF als Wert enthalten. Der Wert wird auch als Betrag des Vektors bezeichnet.

1. Ausbildung

Der erste Schritt beim "Training" unserer Engine besteht darin, die Daten in eine Struktur zu formatieren, die verwendbar und einfach zu verwalten ist. Die Labeldaten, die von Google Cloud Vision zurückgegeben werden, sehen in etwa so aus:

{
  "1.jpg": [
    {
      "locations": [],
      "properties": [],
      "mid": "/m/0c9ph5",
      "locale": "",
      "description": "Flower",
      "score": 0.9955990314483643,
      "confidence": 0,
      "topicality": 0.9955990314483643,
      "boundingPoly": null
    },
    {
      "locations": [],
      "properties": [],
      "mid": "/m/04sjm",
      "locale": "",
      "description": "Flowering plant",
      "score": 0.9854584336280823,
      "confidence": 0,
      "topicality": 0.9854584336280823,
      "boundingPoly": null
    },
    [...]
  ]
}

1.a Formatierung

Für diese Übung beschäftigen wir uns nur mit dem Schlüssel der obersten Ebene des Objekts (1.jpg ) und die description von jedem der Objekte im Array. Aber wir wollen alle Beschreibungen in einer einzigen Zeichenfolge. So können wir sie später leichter bearbeiten.

Wir möchten, dass sich die Daten in einem Array von Objekten wie dem folgenden befinden:

const formattedData = [
  {
    id: '1.jpg',
    content: 'flower flowering plant plant petal geraniaceae melastome family geranium wildflower geraniales perennial plant' 
  }
]

Um unsere Daten zu formatieren, führen wir sie durch die folgende Funktion. Dadurch wird ein Array aller Daten zurückgegeben, die wir benötigen, um unsere Engine weiter zu trainieren. Wir verwenden Object.entries damit wir leichter iterieren können. MDN gibt Folgendes an:

Dann durchlaufen wir das Array, das mit bt Object.entries erstellt wurde Zupft die notwendigen Eigenschaften und fügt sie zu einem desc hinzu Reihe. Schließlich fügen wir den Inhalt des desc hinzu Array und schreibe es in content Eigentum. Diese formatted array ist unser Korpus.

const formatData = data => {
  let formatted = [];

  for (const [key, labels] of Object.entries(data)) {
    let tmpObj = {};
    const desc = labels.map(l => {
      return l.description.toLowerCase();
    });

    tmpObj = {
      id: key,
      content: desc.join(" ")
    };

    formatted.push(tmpObj);
  }

  return formatted;
};

1.b TF-IDF und Vektoren

Wie oben erwähnt, ist die TF nur die Häufigkeit, mit der ein Begriff in einem Dokument vorkommt.

Zum Beispiel:

// In the data set below the TF of plant is 3
{ 
  id: '1.jpg',
  content: 'flower flowering plant plant petal geraniaceae melastome family geranium wildflower geraniales perennial plant' 
}

Die IDF ist etwas komplexer zu erarbeiten. Die Formel lautet:

In Javascript geht das mit:

var idf = Math.log((this.documents.length) / docsWithTerm );

Wir brauchen nur die obigen Werte (TF und IDF), damit wir den TF-IDF berechnen können. Es ist einfach TF multipliziert mit der IDF.

const tdidf = tf * idf;

Der nächste Schritt in unserem Prozess besteht darin, die TF-IDF unserer Dokumente zu berechnen und einen Vektor zu erstellen, der den Begriff als Schlüssel und den Wert (Vektor) als TF-IDF enthält. Wir stützen uns auf natural und vector-object npm-Pakete, damit wir dies einfach tun können. tfidf.addDocument wird unseren content tokenisieren Eigentum. Die tfidf.listTerms -Methode listet unsere neu verarbeiteten Dokumente auf und gibt ein Array von Objekten zurück, die TD, IDF und TD-IDF enthalten. Uns geht es aber nur um die TF-IDF.

/**
* Generates the TF-IDF of each term in the document
* Create a Vector with the term as the key and the TF-IDF as the value
* @example - example vector
* {
*   flowers: 1.2345
* }
*/
const createVectorsFromDocs = processedDocs => {
  const tfidf = new TfIdf();

  processedDocs.forEach(processedDocument => {
    tfidf.addDocument(processedDocument.content);
  });

  const documentVectors = [];

  for (let i = 0; i < processedDocs.length; i += 1) {
    const processedDocument = processedDocs[i];
    const obj = {};

    const items = tfidf.listTerms(i);

    for (let j = 0; j < items.length; j += 1) {
      const item = items[j];
      obj[item.term] = item.tfidf;
    }

    const documentVector = {
      id: processedDocument.id,
      vector: new Vector(obj)
    };

    documentVectors.push(documentVector);
  }

Jetzt haben wir ein Array von Objekten, das die ID des Bildes enthält (1.jpg ) als ID und unseren Vektor. Unser nächster Schritt ist die Berechnung der Ähnlichkeiten zwischen den Dokumenten.

1.c Berechnung von Ähnlichkeiten mit Cosinus-Ähnlichkeit und Skalarprodukt

Der letzte Schritt in der Phase „Training“ besteht darin, die Ähnlichkeiten zwischen den Dokumenten zu berechnen. Wir verwenden den vector-object Paket erneut, um die Kosinus-Ähnlichkeiten zu berechnen. Nach der Berechnung schieben wir sie in ein Array, das die Bild-ID und alle empfohlenen Bilder aus dem Training enthält. Schließlich sortieren wir sie so, dass das Element mit der höchsten Kosinus-Ähnlichkeit an erster Stelle im Array steht.

/**
* Calculates the similarities between 2 vectors
* getCosineSimilarity creates the dotproduct and the 
* length of the 2 vectors to calculate the cosine 
* similarity
*/
const calcSimilarities = docVectors => {
  // number of results that you want to return.
  const MAX_SIMILAR = 20; 
  // min cosine similarity score that should be returned.
  const MIN_SCORE = 0.2;
  const data = {};

  for (let i = 0; i < docVectors.length; i += 1) {
    const documentVector = docVectors[i];
    const { id } = documentVector;

    data[id] = [];
  }

  for (let i = 0; i < docVectors.length; i += 1) {
    for (let j = 0; j < i; j += 1) {
      const idi = docVectors[i].id;
      const vi = docVectors[i].vector;
      const idj = docVectors[j].id;
      const vj = docVectors[j].vector;
      const similarity = vi.getCosineSimilarity(vj);

      if (similarity > MIN_SCORE) {
        data[idi].push({ id: idj, score: similarity });
        data[idj].push({ id: idi, score: similarity });
      }
    }
  }

  // finally sort the similar documents by descending order
  Object.keys(data).forEach(id => {
    data[id].sort((a, b) => b.score - a.score);

    if (data[id].length > MAX_SIMILAR) {
      data[id] = data[id].slice(0, MAX_SIMILAR);
    }
  });

  return data;

Unter der Haube der getCosineSimilarity Methode macht eine Reihe von Dingen.

Es erzeugt das Punktprodukt, diese Operation nimmt 2 Vektoren und gibt eine einzelne (skalare) Zahl zurück. Es ist eine einfache Multiplikation jeder Komponente in beiden Vektoren, die zusammenaddiert werden.

a = [1.7836, 3]
b = [4, 0.5945]

a.b = 1.7836 * 4 + 3 *0.5945 = 8.9176

Mit dem berechneten Skalarprodukt müssen wir nur die Vektorwerte jedes Dokuments auf skalare Werte reduzieren. Dies geschieht, indem die Quadratwurzel jedes Werts multipliziert mit sich selbst addiert wird. Die getLength Methode unten führt diese Berechnung durch.

const getLength = () => {
  let l = 0;

  this.getComponents().forEach(k => {
    l += this.vector[k] * this.vector[k];
  });

  return Math.sqrt(l);
}

Die tatsächliche Kosinus-Ähnlichkeitsformel sieht folgendermaßen aus:

und in Javascript sieht das so aus:

const getCosineSimilarity = (vector) => {
  return this.getDotProduct(vector) / (this.getLength() * vector.getLength());
}

Das Training ist abgeschlossen!!

2. Erhalten Sie unsere Empfehlungen

Nachdem wir die Trainingsphase abgeschlossen haben, können wir einfach die empfohlenen Bilder aus den Trainingsdaten anfordern.

const getSimilarDocuments = (id, trainedData) => {
  let similarDocuments = trainedData[id];

  if (similarDocuments === undefined) {
    return [];
  }

  return similarDocuments;
};

Dadurch wird ein Array von Objekten zurückgegeben, das die empfohlenen Bilder und ihren Kosinus-Ähnlichkeitswert enthält.

// e.g
[ { id: '14.jpg', score: 0.341705472305971 },
  { id: '9.jpg', score: 0.3092133517794513 },
  { id: '1.jpg', score: 0.3075994367748345 } ]

Abschluss

Ich hoffe, du konntest folgen. Ich habe so viel aus dieser Übung gelernt und sie hat mein Interesse am maschinellen Lernen wirklich geweckt.