Подтвердить что ты не робот

Лучший алгоритм кластеризации? (просто объяснил)

Представьте себе следующую проблему:

  • У вас есть база данных, содержащая около 20 000 текстов в таблице под названием "статьи".
  • Вы хотите связать связанные с ним алгоритмы кластеризации, чтобы отображать связанные статьи вместе.
  • Алгоритм должен выполнять плоскую кластеризацию (не иерархическую)
  • Связанные статьи должны быть вставлены в таблицу "related"
  • Алгоритм кластеризации должен решить, связаны ли две или более статьи или нет на основе текстов
  • Я хочу кодировать на PHP, но примеры с псевдокодом или другими языками программирования в порядке, тоже

Я закодировал первый черновик с функцией check(), которая дает "true", если две исходные статьи связаны, а "false", если нет. Остальная часть кода (выбор статей из базы данных, выбор статей для сравнения с вставкой соответствующих) также завершен. Может быть, вы тоже можете улучшить остальные. Но главное, что важно для меня, это функция check(). Поэтому было бы замечательно, если бы вы могли опубликовать некоторые улучшения или совершенно разные подходы.

ПОДХОД 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);
            }
        }
    }
}
?>

ПОДХОД 2 [только проверка()]

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

Я также хотел бы сказать, что я знаю, что существует множество алгоритмов кластеризации, но на каждом сайте есть только математическое описание, которое мне трудно понять. Поэтому примеры кодирования в (псевдо) коде были бы большими.

Надеюсь, ты поможешь мне. Спасибо заранее!

4b9b3361

Ответ 1

Самый стандартный способ, который я знаю сделать для текстовых данных, таких как у вас, - использовать технику "мешок слов".

Сначала создайте "гистограмму" слов для каждой статьи. Скажем, между всеми вашими статьями, у вас есть только 500 уникальных слов между ними. Тогда эта гистограмма будет представлять собой вектор (массив, список, любой) размером 500, где данные - это количество раз, когда каждое слово появляется в статье. Поэтому, если первое пятно в векторе представляет слово "спросил", и это слово появилось 5 раз в статье, вектор [0] будет равен 5:

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

Теперь, чтобы сравнить любые две статьи, это довольно просто. Мы просто умножаем два вектора:

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

(Извините за использование python вместо PHP, мой PHP ржавый, а использование zip делает этот бит проще)

Это основная идея. Обратите внимание, что пороговое значение является полу-произвольным; вы, вероятно, захотите найти хороший способ нормализовать точечный продукт ваших гистограмм (это почти обязательно будет учитывать длину статьи где-то) и решить, что вы считаете "связанным".

Кроме того, вы не должны просто помещать каждое слово в свою гистограмму. В общем, вы хотите включить те, которые используются часто: не в каждой статье или только в одной статье. Это экономит немного накладных расходов на вашей гистограмме и увеличивает ценность ваших отношений.

Кстати, этот метод описан более подробно здесь

Ответ 2

Может быть, кластеризация - это неправильная стратегия здесь?

Если вы хотите отображать похожие статьи, используйте поиск подобия вместо.

Для текстовых статей это хорошо понято. Просто вставьте свои статьи в текстовую базу поиска, например Lucene, и используйте свою текущую статью в качестве поискового запроса. В Lucene существует запрос с именем MoreLikeThis, который выполняет именно это: найти похожие статьи.

Кластеризация - это неправильный инструмент, потому что (в частности, с вашими требованиями) каждая статья должна быть помещена в какой-либо кластер; и связанные элементы будут одинаковыми для каждого объекта в кластере. Если в базе данных есть выбросы - очень вероятный случай - они могут разрушить вашу кластеризацию. Кроме того, кластеры могут быть очень большими. Ограничений по размеру нет, алгоритм кластеризации может решить поставить половину вашего набора данных в один и тот же кластер. Таким образом, у вас есть 10000 связанных статей для каждой статьи в вашей базе данных. С помощью поиска подобия вы можете просто получить 10 лучших предметов для каждого документа!

И последнее, но не менее важное: забудьте PHP для кластеризации. Он не предназначен для этого, а не достаточно. Но вы, вероятно, можете получить доступ к индексу lucene с PHP достаточно хорошо.

Ответ 3

Я считаю, что вам нужно принять некоторые проектные решения о кластеризации и продолжить оттуда:

  • Почему вы кластеризуете тексты? Вы хотите отображать связанные документы вместе? Вы хотите исследовать свой корпус документа через кластеры?
  • В результате вы хотите flat или иерархической кластеризации?
  • Теперь у нас есть проблема сложности в двух измерениях: во-первых, количество и тип функций, которые вы создаете из текста, - отдельные слова могут насчитывать десятки тысяч. Возможно, вы захотите попробовать выбор функции - например, принять N наиболее информативных слов или N слов, появляющихся больше всего, после игнорирования остановить слова.
  • Во-вторых, вы хотите свести к минимуму количество случаев, когда вы измеряете сходство между документами. Как правильно указывает пузырь, проверка сходства между всеми парами документов может быть слишком большой. Если кластеризации в небольшое количество кластеров достаточно, вы можете рассмотреть кластеризация K-, которая в основном: выберите исходные документы K как кластер центров, присваивать каждому документу ближайший кластер, пересчитывать центры кластеров, находя средства вектора вектора и выполнять итерацию. Это означает только количество документов K * для каждой итерации. Я считаю, что существуют также эвристики для уменьшения необходимого количества вычислений для иерархической кластеризации.

Ответ 4

Как выглядит функция similar_text в подходе №1? Я думаю, что вы имеете в виду не кластеризацию, а метрику сходства. Я не могу улучшить на White Walloun:-) метод гистограммы - интересная проблема для чтения.

Однако вы реализуете check(), вы должны использовать его для проведения сравнений не менее 200M (половина 20000^2). Обрезание для "связанных" статей может ограничивать то, что вы храните в базе данных, но кажется слишком произвольным, чтобы поймать всю полезную кластеризацию текстов,

Моим подходом было бы изменить check(), чтобы вернуть метрику "подобия" ($prozent или rtn). Запишите матрицу 20K x 20K в файл и используйте внешнюю программу для выполнения кластеризации для определения ближайших соседей для каждой статьи, которую вы можете загрузить в таблицу related. Я бы сделал кластеризацию в R - там есть учебник для кластеризации данных в файле с R из php.