2009-05-12 17 views
19

Wyobraźmy sobie następujący problem:Najlepszy algorytm grupowania? (Prosto wyjaśnione)

  • Masz bazę danych zawierającą około 20.000 tekstów w tabeli o nazwie „Artykuły”
  • Chcesz połączyć związanych te stosując algorytm klastrowania w celu wyświetlenia wyroby związanych razem
  • algorytm należy do płaskiej klastrów (nie hierarchicznej)
  • Odnośne artykuły powinny być wstawiony do tabeli „związanej”
  • algorytmu klasteryzacji powinien zdecydować, czy dwa lub mor Artykuły E są związane lub nie oparte na tekstach
  • Chcę kod w PHP, ale przykłady z pseudo kod lub innych języków programowania są ok, zbyt

mam zakodowane pierwszy projekt z czekiem function (), co daje "prawda", jeśli dwa artykuły wejściowe są powiązane i "fałsz", jeśli nie. Reszta kodu (wybór artykułów z bazy danych, wybór artykułów do porównania, wstawienie powiązanych) również jest kompletny. Może też poprawisz resztę. Ale najważniejszą kwestią, która jest dla mnie ważna, jest funkcja check(). Byłoby wspaniale, gdybyś mógł opublikować poprawki lub zupełnie inne podejścia.

PODEJŚCIE 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); 
      } 
     } 
    } 
} 
?> 

PODEJŚCIE 2 [tylko sprawdzić()]

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

Chciałbym również powiedzieć, że wiem, że istnieje wiele algorytmów grupowania ale na każdej stronie jest tylko opis matematyczny, który jest dla mnie nieco trudny do zrozumienia. Więc kodowanie przykładów w (pseudo) kodzie byłoby świetne.

Mam nadzieję, że możesz mi pomóc. Z góry dziękuję!

+1

Istnieje WordPress Wtyczki (tak, fuj, wiem, oszczędź mi tego), że roboty zaskakująco dobrą pracę na to, że rzeczywiście wykonać rozsądny klastrów (zazwyczaj robią tfidf z tyłu słów z k-średnich lub coś w ten sposób) i można ich użyć do inspiracji (niektóre z nich to open source w ramach MIT). –

+2

Myślę, że Anony-Mousse ma rację: klastrowanie nie jest tutaj idealnym narzędziem. Jeśli każdy dokument należy do tylko jednego klastra, to masz problem z dokumentami znajdującymi się w pobliżu granic klastra, które są * bardziej podobne * do dokumentów w innych pobliskich klastrach niż do większości dokumentów w ich własnym klastrze. –

Odpowiedz

15

Najbardziej standardowy sposób, w jaki mogę to zrobić na danych tekstowych takich jak Ty, polega na użyciu techniki "worka słów".

Najpierw utwórz "histogram" słów dla każdego artykułu. Powiedzmy, że między wszystkimi twoimi artykułami, masz tylko 500 unikalnych słów między nimi. Histogram będzie wówczas wektorem (Array, List, Whatever) o wielkości 500, gdzie dane to liczba wystąpień każdego słowa w artykule. Więc jeśli pierwszy spot w wektorze reprezentowane słowo „pytanie”, a słowo pojawiło się 5 razy w artykule, wektor [0] byłoby 5:

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

Teraz, aby porównać dwa żadnych artykułów, to jest całkiem proste. Po prostu pomnożyć dwa wektory:

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

(Przepraszam za korzystanie Pythona zamiast PHP, moje PHP jest zardzewiałe i korzystanie z zamkiem sprawia, że ​​nieco łatwiej)

Jest to podstawowa idea. Zwróć uwagę, że wartość progowa jest częściowo arbitralna; prawdopodobnie będziesz chciał znaleźć dobry sposób na znormalizowanie iloczynu punktowego twoich histogramów (to będzie prawie musiało uwzględnić gdzieś długość artykułu) i zdecydować, co uważasz za "powiązane".

Ponadto nie powinieneś umieszczać każdego słowa na histogramie. Ogólnie rzecz biorąc, chcesz uwzględnić te, które są używane w sposób pół-często: nie w każdym artykule ani w jednym artykule. Pozwala to zaoszczędzić trochę na histogramie i zwiększa wartość twoich relacji.

Nawiasem mówiąc, ta technika jest opisana bardziej szczegółowo here

+0

Dziękuję bardzo! Próbowałem kodować swoje podejście w PHP i oto wynik: http://paste.bradleygill.com/index.php?paste_id=9290 Mam nadzieję, że PHP jest jeszcze wystarczająco dobry, aby powiedzieć, czy jest to poprawne, czy nie. – caw

+1

Wydaje mi się, że jest poprawne, jednak w zależności od aplikacji, poważnie chcesz rozważyć utrzymanie stanu wektorów terminologicznych. Rozważ także podzielenie wyniku przez długość artykułu razy długość artykułu b. W przeciwnym razie pojawi się tendencja do długich artykułów, które są tylko nieznacznie powiązane. – Albinofrenchy

+0

Przepraszam, na pewno głupie pytanie, ale co dokładnie masz na myśli mówiąc: "rozważ utrzymywanie stanu wektorów terminów". W drugim punkcie: czy masz na myśli "wynik $ = wynik $/długość_a * długość_b" lub "wynik $ = wynik = ($ długość_a * dług_)"? Prawdopodobnie pierwszy, prawda? – caw

0

Co robi funkcja similar_text zwany w podejściu # 1 wyglądać? Myślę, że to, o czym mówisz, to nie klastrowanie, ale metryka podobieństwa. Naprawdę nie mogę poprawić podejścia White Walloun :-) do histogramu - interesujący problem przy czytaniu.

Jednak zaimplementujesz check(), musisz użyć go do dokonania co najmniej 200M porównań (połowa z 20000^2). Odcięcia dla „powiązanych” Artykuły mogą ograniczyć to, co zapisać w bazie danych, ale wydaje się zbyt arbitralne złapać wszystkie użyteczne grupowanie tekstów,

Moje podejście byłoby zmodyfikować check() zwrotu „podobieństwo” metryczne ($prozent lub rtn). Zapisz macierz 20K x 20K w pliku i użyj zewnętrznego programu, aby przeprowadzić grupowanie w celu zidentyfikowania najbliższych sąsiadów dla każdego artykułu, które można załadować do tabeli related. Zrobiłbym klastrowanie w R - istnieje niezła tutorial dla klastrowania danych w pliku o numerze R z php.

+0

Funkcja similar_text() "oblicza podobieństwo między dwoma ciągami, jak opisano w Oliver [1993]". Tak, masz rację, to raczej miara podobieństwa. Ale potrzebujesz sprawdzania podobieństwa do grupowania, prawda? – caw

1

wierzę, trzeba podjąć pewne decyzje projektowe dotyczące klastrów, a stamtąd dalej:

  1. Czemu grupowanie tekstów? Czy chcesz wyświetlać powiązane dokumenty? Czy chcesz zgłębić korpus swojego dokumentu przez klastry?
  2. W związku z tym, czy chcesz grupować w klastrze flat lub ?
  3. Teraz mamy problem złożoności, w dwóch wymiarach: po pierwsze, liczba i typ funkcji, które tworzysz z tekstu - pojedyncze słowa mogą być liczone w dziesiątkach tysięcy. Możesz wypróbować jakieś feature selection - takie jak pobranie N najbardziej pouczających słów lub N słów pojawiających się najwięcej razy, po zignorowaniu stop words.
  4. Po drugie, chcesz zminimalizować liczbę pomiarów podobieństwa między dokumentami. Jak prawidłowo wskazuje buldog, sprawdzenie podobieństwa między wszystkimi parami dokumentów może być zbyt duże. Jeśli wystarczy grupowanie w niewielką liczbę klastrów, możesz rozważyć: K-means clustering, czyli: wybrać początkowe dokumenty K jako centra klastrów, przypisać każdy dokument do najbliższego klastra, ponownie obliczyć centra klastra, znajdując wektory dokumentu i iterować. To kosztuje tylko K * liczby dokumentów w iteracji. Sądzę, że istnieją także heurystyki umożliwiające zmniejszenie wymaganej liczby obliczeń dla hierarchicznego grupowania.
+0

Dzięki, dobre pytania! 1) Chcę wyświetlać powiązane dokumenty. 2) Algorytm powinien grupować na płasko. 3) Byłoby to przydatne, gdyby teksty były długie, ale w moim przypadku artykuły zawierają maksymalnie 510 znaków. Więc to nie jest naprawdę konieczne, prawda? 4) Podejście z k-środkami brzmi dobrze, ale potrzebuję wielu klastrów i nowe klastry muszą być ciągle tworzone. Czy mogę jednak użyć K-średnich? – caw

+0

Możesz użyć K-średnich, gdzie K jest bardzo duże. Kosztem jest sprawdzenie podobieństwa każdego dokumentu z każdym z centrów klastrów. "Stale twórz dla mnie nowe dźwięki klastrów, takie jak hierarchiczne odgórne klastrowanie, ale możesz pracować w kilku epokach - zacznij od małego K, uruchamiaj K-środki, aż się zbiegnie i używaj tych klastrów. Później zwiększyć K, k-średnich powtórzona od początku, i korzystać z wynikających klastrów itp –

+0

Och, ja nie wiem, jak k oznacza dokładnie działa.Jeśli to działa, nie mogę go użyć, ponieważ nie znam liczby centrów klastra. Mam bazę artykułów z wiadomościami i wszystkie artykuły dotyczące tego samego tematu powinny być pogrupowane. – caw

5

Być może tworzenie klastrów to zła strategia tutaj?

Jeśli chcesz wyświetlić podobnych artykuły, użytku podobieństwo wyszukiwania zamiast.

W przypadku artykułów tekstowych jest to dobrze zrozumiałe. Wystarczy umieścić swoje artykuły w bazie danych wyszukiwania tekstów, takich jak Lucene, i użyj bieżącego artykułu jako kwerendy wyszukiwania.W Lucene istnieje query called MoreLikeThis, który wykonuje dokładnie to: znajdowanie podobnych artykułów.

Clusterowanie jest niewłaściwym narzędziem, ponieważ (w szczególności z Twoimi wymaganiami), każdy artykuł musi być umieszczony w jakiejś grupie; a powiązane elementy będą takie same dla każdego obiektu w klastrze. Jeśli w bazie danych znajdują się wartości odstające - bardzo prawdopodobny przypadek - mogą zrujnować grupowanie. Ponadto, klastry mogą być bardzo duże. Nie ma ograniczenia rozmiaru, algorytm grupowania może zdecydować o umieszczeniu połowy zestawu danych w tym samym klastrze. Masz więc 10000 powiązanych artykułów dla każdego artykułu w twojej bazie danych. Dzięki wyszukiwaniu podobieństw możesz po prostu uzyskać 10 najlepszych podobnych elementów dla każdego dokumentu!

Last but not least: zapomnieć PHP dla klastrów. Nie jest do tego przystosowany i nie jest wystarczająco wydajny. Ale prawdopodobnie można uzyskać dostęp do lucene indeks z PHP wystarczająco dobrze.

Powiązane problemy