Meilleur algorithme de regroupement? (expliquée simplement)

Imaginez le problème suivant:

  • vous avez une base de données contenant environ 20.000 textes dans un tableau appelé "articles"
  • vous voulez connecter les articles apparentés en utilisant un algorithme de regroupement afin d'afficher les articles apparentés ensemble
  • l'algorithme doit faire un clustering plat (pas hiérarchique)
  • les articles correspondants doivent être insérés dans le tableau "liés"
  • l'algorithme de regroupement devrait décider si deux ou plus d'articles sont liés ou non basés sur les textes
  • je veux le code en PHP, mais des exemples avec le pseudo code ou d'autres langages de programmation sont ok, trop

j'ai codé une première ébauche avec une vérification de fonction() qui donne "true" si les deux articles d'entrée sont liés et "false" si non. Le reste du code (sélection des articles de la base de données, Sélection des articles à comparer, insertion des articles correspondants) est également complet. Peut-être que tu peux améliorer le reste aussi. Mais le point principal qui est important pour moi est la fonction check(). Donc, ce serait génial si vous pouviez afficher quelques améliorations ou des approches complètement différentes.

approche 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);
            }
        }
    }
}
?>

APPROCHE 2 [seul 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;
    }
}
?>

je voudrais dire aussi que je sais qu'il y a beaucoup d'algorithmes pour le clustering mais sur chaque site il n'y a que la description mathématique qui est un peu difficile à comprendre pour je. Ainsi, coder des exemples en (pseudo) code serait génial.

j'espère que vous pourrez m'aider. Merci à l'avance!

19
demandé sur Anony-Mousse 2009-05-12 18:38:50

4 réponses

la façon la plus standard que je connaisse pour faire cela sur les données de texte comme vous avez, est d'utiliser la technique du "sac de mots".

tout d'abord, créez un 'histogramme' de mots pour chaque article. Disons entre tous vos articles, vous avez seulement 500 mots uniques entre eux. Alors cet histogramme va être un vecteur(Tableau, Liste, peu importe) de taille 500, où les données sont le nombre de fois que chaque mot apparaît dans l'article. Ainsi, si la première place dans le vecteur représenté le mot "demande", et ce mot est apparu 5 fois dans l'article, vecteur[0] serait de 5:

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

Maintenant, pour comparer deux articles, c'est assez simple. Nous multiplions simplement les deux vecteurs:

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

(Désolé d'utiliser python au lieu de PHP, mon PHP est rouillé et l'utilisation de zip rend cela un peu plus facile)

C'est l'idée de base. Remarquez que la valeur de seuil est semi-arbitraire; vous voudrez probablement trouver un bon moyen de normaliser le produit de points de vos histogrammes (ceci il faudra presque tenir compte de la longueur de l'article quelque part) et décider ce que vous considérez comme "lié".

en outre, vous ne devriez pas simplement mettre chaque mot dans votre histogramme. Vous voudrez, en général, inclure ceux qui sont utilisés semi-fréquemment: pas dans chaque article ni dans un seul article. Vous économisez ainsi un peu de frais généraux sur votre histogramme et augmentez la valeur de vos relations.

Par ailleurs, cette technique est décrite plus en détail ici

15
répondu Albinofrenchy 2009-05-12 19:35:52

Peut-être le clustering est une mauvaise stratégie ici?

si vous voulez afficher similaires articles,utiliser recherche de similarité au lieu de.

pour les articles de texte, c'est bien compris. Il suffit d'insérer vos articles dans une base de données de recherche de texte comme Lucene, et utilisez votre article actuel comme requête de recherche. Dans Lucene, il existe un requête MoreLikeThis qui exécute exactement ceci: find articles similaires.

le Clustering est un mauvais outil, parce que (en particulier avec les exigences), tous les article doivent être mises en cluster, et les éléments connexes serait le même pour chaque objet dans le cluster. S'il y a des valeurs aberrantes dans la base de données - un cas très probable - elles pourraient ruiner votre regroupement. En outre, grappes peut être très grand. Il n'y a pas de contrainte de taille, l'algorithme de clustering peut décider de mettre la moitié de vos données même cluster. Donc vous avez 10000 articles reliés pour chaque article dans votre base de données. Avec la recherche de similarité, vous pouvez juste obtenir le top-10 articles similaires pour chaque document!

Dernier point mais non le moindre: oubliez PHP pour la mise en cluster. Il n'est pas conçu pour cela, et pas assez performant. Mais vous pouvez probablement accéder assez bien à un index de lucene à partir de PHP.

6
répondu Anony-Mousse 2015-05-02 10:42:27

je crois que vous avez besoin de prendre certaines décisions de conception sur le clustering, et continuer à partir de là:

  1. pourquoi regroupez-vous les textes? Voulez-vous afficher des documents connexes ensemble? Vous souhaitez explorer votre corpus de documents via des clusters?
  2. en conséquence, voulez-vous plat ou hiérarchiques clustering?
  3. nous avons maintenant la question de la complexité, en deux dimensions: d'abord, le nombre et le type de fonctionnalités que vous créez à partir de le texte - mots individuels peuvent comptent par dizaines de milliers. Vous pouvez essayer quelques-uns sélection des caractéristiques - comme prendre les N mots les plus informatifs, ou les N mots qui apparaissent le plus souvent, après avoir ignoré arrêt mots.
  4. Deuxièmement, vous voulez minimiser le nombre de fois où vous mesurez la similarité entre des documents. Comme bubaker le souligne à juste titre, la vérification de la similitude entre toutes les paires de documents peut être exagérée. Si groupage dans un petit nombre de clusters est assez, vous pouvez considérer K-signifie clustering, qui est essentiellement: choisir un document K initial comme centre de cluster, assigner chaque document au cluster le plus proche, recalculer les centres de cluster en trouvant des moyens de vecteur de document, et itérer. Cela ne coûte que K*nombre de documents par itération. Je crois qu'il y a aussi des heuristiques pour réduire le nombre nécessaire de calculs pour le regroupement hiérarchique aussi bien.
1
répondu Yuval F 2009-05-13 08:17:22

Que les similar_text fonction appelée en Approche #1? Je pense que ce à quoi vous faites référence n'est pas un regroupement, mais une métrique de similarité. Je ne peux pas vraiment améliorer L'approche du Walloun Blanc :-) histogramme - un problème intéressant pour faire de la lecture sur.

Cependant vous mettre en œuvre check(), il faut l'utiliser pour faire au moins 200M de comparaisons (la moitié de