Лучший алгоритм кластеризации? (просто объяснил) - PullRequest
19 голосов
/ 12 мая 2009

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

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

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

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

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

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

Ответы [ 4 ]

15 голосов
/ 12 мая 2009

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

Сначала создайте «гистограмму» слов для каждой статьи. Допустим, между всеми вашими статьями у вас есть только 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 облегчает эту задачу)

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

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

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

6 голосов
/ 02 мая 2015

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

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

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

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

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

1 голос
/ 13 мая 2009

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

  1. Почему вы группируете тексты? Вы хотите отобразить связанные документы вместе? Вы хотите исследовать свой корпус документов через кластеры?
  2. В результате вы хотите квартира или иерархическая кластеризация?
  3. Теперь у нас есть проблема сложности, в двух измерениях: во-первых, количество и тип объектов, которые вы создаете из текста - отдельные слова могут исчисляться десятками тысяч. Возможно, вы захотите попробовать выбор функции - например, взять N наиболее информативных слов или N слов, появляющихся чаще всего, после игнорирования стоп-слов .
  4. Во-вторых, вы хотите минимизировать количество раз, когда вы измеряете сходство между документами. Как правильно указывает bubaker, проверка сходства между всеми парами документов может быть слишком сложной. Если кластеризации в небольшое количество кластеров достаточно, вы можете рассмотреть K-означает кластеризацию , что в основном: выберите исходные K документов в качестве центров кластеров, назначьте каждый документ ближайшему кластеру, пересчитайте центры кластеров с помощью найти вектор документа значит, и перебрать. Это стоит только K * количество документов за итерацию. Я считаю, что есть также эвристика для сокращения необходимого количества вычислений для иерархической кластеризации.
0 голосов
/ 12 мая 2009

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

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

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

...