19 Stimmen

Bester Clustering-Algorithmus? (einfach erklärt)

Stellen Sie sich das folgende Problem vor:

  • Sie haben eine Datenbank mit etwa 20.000 Texten in einer Tabelle namens "Artikel".
  • Sie möchten die verwandten Artikel mithilfe eines Clustering-Algorithmus miteinander verbinden, um verwandte Artikel gemeinsam anzuzeigen
  • Der Algorithmus sollte ein flaches Clustering durchführen (nicht hierarchisch)
  • Die verwandten Artikel sollten in die Tabelle "Verwandte" eingefügt werden.
  • Der Clustering-Algorithmus sollte anhand der Texte entscheiden, ob zwei oder mehr Artikel miteinander verwandt sind oder nicht
  • Ich möchte in PHP programmieren, aber Beispiele mit Pseudocode oder anderen Programmiersprachen sind auch in Ordnung

Ich habe einen ersten Entwurf mit einer Funktion check() kodiert, die "true" ausgibt, wenn die beiden eingegebenen Artikel miteinander verwandt sind, und "false", wenn nicht. Der Rest des Codes (Auswahl der Artikel aus der Datenbank, Auswahl der Artikel, mit denen verglichen werden soll, Einfügen der verwandten Artikel) ist ebenfalls vollständig. Vielleicht können Sie auch den Rest noch verbessern. Aber der wichtigste Punkt, der mir wichtig ist, ist die Funktion check(). Es wäre also toll, wenn Sie einige Verbesserungen oder ganz andere Ansätze posten könnten.

ANFAHRT 1

<?php
$zeit = time();
function check($str1, $str2){
    $minprozent = 60;
    similar_text($str1, $str2, $prozent);
    $prozent = sprintf("%01.2f", $prozent);
    if ($prozent > $minprozent) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}
$sql1 = "SELECT id, text FROM articles ORDER BY RAND() LIMIT 0, 20";
$sql2 = mysql_query($sql1);
while ($sql3 = mysql_fetch_assoc($sql2)) {
    $rel1 = "SELECT id, text, MATCH (text) AGAINST ('".$sql3['text']."') AS score FROM articles WHERE MATCH (text) AGAINST ('".$sql3['text']."') AND id NOT LIKE ".$sql3['id']." LIMIT 0, 20";
    $rel2 = mysql_query($rel1);
    $rel2a = mysql_num_rows($rel2);
    if ($rel2a > 0) {
        while ($rel3 = mysql_fetch_assoc($rel2)) {
            if (check($sql3['text'], $rel3['text']) == TRUE) {
                $id_a = $sql3['id'];
                $id_b = $rel3['id'];
                $rein1 = "INSERT INTO related (article1, article2) VALUES ('".$id_a."', '".$id_b."')";
                $rein2 = mysql_query($rein1);
                $rein3 = "INSERT INTO related (article1, article2) VALUES ('".$id_b."', '".$id_a."')";
                $rein4 = mysql_query($rein3);
            }
        }
    }
}
?>

APPROACH 2 [nur check()]

<?php
function square($number) {
    $square = pow($number, 2);
    return $square;
}
function check($text1, $text2) {
    $words_sub = text_splitter($text2); // splits the text into single words
    $words = text_splitter($text1); // splits the text into single words
    // document 1 start
    $document1 = array();
    foreach ($words as $word) {
        if (in_array($word, $words)) {
            if (isset($document1[$word])) { $document1[$word]++; } else { $document1[$word] = 1; }
        }
    }
    $rating1 = 0;
    foreach ($document1 as $temp) {
        $rating1 = $rating1+square($temp);
    }
    $rating1 = sqrt($rating1);
    // document 1 end
    // document 2 start
    $document2 = array();
    foreach ($words_sub as $word_sub) {
        if (in_array($word_sub, $words)) {
            if (isset($document2[$word_sub])) { $document2[$word_sub]++; } else { $document2[$word_sub] = 1; }
        }
    }
    $rating2 = 0;
    foreach ($document2 as $temp) {
        $rating2 = $rating2+square($temp);
    }
    $rating2 = sqrt($rating2);
    // document 2 end
    $skalarprodukt = 0;
    for ($m=0; $m<count($words)-1; $m++) {
        $skalarprodukt = $skalarprodukt+(array_shift($document1)*array_shift($document2));
    }
    if (($rating1*$rating2) == 0) { continue; }
    $kosinusmass = $skalarprodukt/($rating1*$rating2);
    if ($kosinusmass < 0.7) {
        return FALSE;
    }
    else {
        return TRUE;
    }
}
?>

Ich möchte auch sagen, dass ich weiß, dass es viele Algorithmen für Clustering gibt, aber auf jeder Website gibt es nur die mathematische Beschreibung, die für mich etwas schwer zu verstehen ist. Beispiele in (Pseudo-)Code wären also großartig.

Ich hoffe, Sie können mir helfen. Vielen Dank im Voraus!

15voto

Albinofrenchy Punkte 424

Die gängigste Methode, die ich kenne, um dies bei Textdaten wie den von Ihnen vorliegenden zu tun, ist die Verwendung der "Bag of Words"-Technik.

Erstellen Sie zunächst ein "Histogramm" der Wörter für jeden Artikel. Nehmen wir an, Sie haben in allen Ihren Artikeln nur 500 eindeutige Wörter. Dann wird dieses Histogramm ein Vektor (Array, Liste, was auch immer) der Größe 500 sein, wobei die Daten die Anzahl der Vorkommen jedes Wortes im Artikel sind. Wenn also der erste Punkt im Vektor das Wort "gefragt" darstellt und dieses Wort 5 Mal in dem Artikel vorkommt, wäre vector[0] gleich 5:

for word in article.text
    article.histogram[indexLookup[word]]++

Der Vergleich zwischen zwei Artikeln ist ziemlich einfach. Wir multiplizieren einfach die beiden Vektoren:

def check(articleA, articleB)
    rtn = 0
    for a,b in zip(articleA.histogram, articleB.histogram)
        rtn += a*b
    return rtn > threshold

(Entschuldigung für die Verwendung von Python anstelle von PHP, mein PHP ist eingerostet und die Verwendung von zip macht das Ganze etwas einfacher)

Das ist der Grundgedanke. Beachten Sie, dass der Schwellenwert halb willkürlich ist. Sie werden wahrscheinlich einen guten Weg finden wollen, um das Punktprodukt Ihrer Histogramme zu normalisieren (dabei muss die Artikellänge irgendwo berücksichtigt werden) und entscheiden, was Sie als "verwandt" betrachten.

Außerdem sollten Sie nicht einfach jedes Wort in Ihr Histogramm aufnehmen. Im Allgemeinen sollten Sie die Wörter aufnehmen, die halbwegs häufig verwendet werden: Nicht in jedem Artikel und auch nicht in nur einem Artikel. Das erspart Ihnen ein wenig Overhead bei Ihrem Histogramm und erhöht den Wert Ihrer Beziehungen.

Übrigens, diese Technik wird ausführlicher beschrieben aquí

6voto

Vielleicht Clustering ist die falsche Strategie hier?

Wenn Sie Folgendes anzeigen möchten ähnlich Artikel, verwenden. Ähnlichkeitssuche stattdessen .

Bei Textartikeln ist dies selbstverständlich. Geben Sie Ihre Artikel einfach in eine Textsuchdatenbank wie Lucene ein und verwenden Sie Ihren aktuellen Artikel als Suchanfrage. In Lucene gibt es eine Abfrage aufgerufen MoreLikeThis die genau das tut: ähnliche Artikel finden.

Clustering ist das falsche Werkzeug, denn (insbesondere bei Ihren Anforderungen), chaque Artikel müssen in ein Cluster eingeordnet werden, und die zugehörigen Elemente sind für jedes Objekt im Cluster gleich. Wenn es in der Datenbank Ausreißer gibt - ein sehr wahrscheinlicher Fall -, könnten diese Ihre Clusterbildung zunichte machen. Außerdem, Cluster können sehr groß sein . Es gibt keine Größenbeschränkung, der Clustering-Algorithmus kann beschließen, die Hälfte Ihres Datensatzes in denselben Cluster aufzunehmen. Sie haben also 10000 verwandte Artikel für jeden Artikel in Ihrer Datenbank. Mit der Ähnlichkeitssuche können Sie einfach die 10 ähnlichsten Artikel für jedes Dokument finden!

Last but not least: Vergessen Sie PHP für Clustering. Es ist dafür nicht ausgelegt und nicht performant genug. Aber Sie können wahrscheinlich gut genug auf einen Lucene-Index von PHP aus zugreifen.

1voto

Yuval F Punkte 20547

Ich glaube, Sie müssen einige Design-Entscheidungen über Clustering treffen und von dort aus weitermachen:

  1. Warum clustern Sie Texte? Wollen Sie verwandte Dokumente gemeinsam anzeigen? Möchten Sie Ihren Dokumentenkorpus mit Hilfe von Clustern erkunden?
  2. Wollen Sie also Wohnung o hierarchisch Clustering?
  3. Nun geht es um die Komplexität, und zwar in zweierlei Hinsicht: Erstens um die Anzahl und Art der Merkmale, die Sie aus dem Text erstellen - einzelne Wörter können in die Zehntausende gehen. Sie können versuchen, einige Merkmalsauswahl - wie z. B. die N informativsten Wörter oder die N am häufigsten vorkommenden Wörter, nachdem sie ignoriert wurden Stoppwörter .
  4. Zweitens möchten Sie die Anzahl der Messungen der Ähnlichkeit zwischen den Dokumenten minimieren. Wie bubaker richtig feststellt, kann die Überprüfung der Ähnlichkeit zwischen allen Dokumentenpaaren zu viel sein. Wenn das Clustern in eine kleine Anzahl von Clustern ausreicht, können Sie Folgendes in Betracht ziehen K-Mittelwert-Clustering Das bedeutet im Wesentlichen: Auswahl von K Ausgangsdokumenten als Clusterzentren, Zuordnung jedes Dokuments zum nächstgelegenen Cluster, Neuberechnung der Clusterzentren durch Ermittlung der Mittelwerte der Dokumentvektoren und Iteration. Dies kostet nur K*Anzahl der Dokumente pro Iteration. Ich glaube, es gibt auch Heuristiken, um die Anzahl der erforderlichen Berechnungen für hierarchisches Clustering zu reduzieren.

0voto

bubaker Punkte 2249

Was bedeutet die similar_text Funktion, die in Ansatz 1 aufgerufen wird, aussehen? Ich glaube, Sie meinen nicht das Clustering, sondern eine Ähnlichkeitsmetrik. Ich kann den Ansatz von White Wallouns :-) Histogramm nicht wirklich verbessern - ein interessantes Problem, über das man etwas lesen sollte.

Wie auch immer Sie die check() müssen Sie damit mindestens 200M Vergleiche durchführen (die Hälfte der 20000^2 ). Die Abgrenzung für "verwandte" Artikel kann die in der Datenbank gespeicherten Artikel einschränken, scheint aber zu willkürlich zu sein, um alle nützlichen Textcluster zu erfassen,

Mein Ansatz wäre, Folgendes zu ändern check() um die "Ähnlichkeits"-Metrik zurückzugeben ( $prozent o rtn ). Schreiben Sie die 20K x 20K Matrix in eine Datei speichern und ein externes Programm verwenden, um ein Clustering durchzuführen, um die nächsten Nachbarn für jeden Artikel zu ermitteln, die Sie dann in die related Tisch. Ich würde das Clustering in R - gibt es eine schöne Lehrgang für das Clustering von Daten in einer Datei, die R de php .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X