Bir matris ile metinler (kümeleme) çok Karşılaştırması

5 Cevap php

Ben metinler arasındaki ilişkiyi hesaplamak için aşağıdaki PHP fonksiyonu var:

function check($terms_in_article1, $terms_in_article2) {
    $length1 = count($terms_in_article1); // number of words
    $length2 = count($terms_in_article2); // number of words
    $all_terms = array_merge($terms_in_article1, $terms_in_article2);
    $all_terms = array_unique($all_terms);
    foreach ($all_terms as $all_termsa) {
        $term_vector1[$all_termsa] = 0;
        $term_vector2[$all_termsa] = 0;
    }
    foreach ($terms_in_article1 as $terms_in_article1a) {
        $term_vector1[$terms_in_article1a]++;
    }
    foreach ($terms_in_article2 as $terms_in_article2a) {
        $term_vector2[$terms_in_article2a]++;
    }
    $score = 0;
    foreach ($all_terms as $all_termsa) {
        $score += $term_vector1[$all_termsa]*$term_vector2[$all_termsa];
    }
    $score = $score/($length1*$length2);
    $score *= 500; // for better readability
    return $score;
}

Değişken $terms_in_articleX metninde tüm tek sözcükleri içeren bir dizi olmalıdır.

Ben 20.000 metinlerin bir veritabanı var varsayarak, bu işlev tüm bağlantıları üzerinden çalıştırmak için çok uzun bir zaman alacaktı.

Nasıl bu süreci hızlandırabilir? Bunun yerine, her zaman yalnızca iki metni karşılaştırarak büyük bir matris içine tüm metinler eklemek gerekir? Eğer tercihen PHP kod ile bazı yaklaşımlar olsaydı harika olurdu.

Bana yardımcı olur umarım. Şimdiden teşekkürler!

5 Cevap

Bunu ekleyerek metni ayırabilirsiniz. Basit bir örnek: preg_match_all(/\w+/, $text, $matches); Tabii gerçek bölme sadece desen düzeltmek, o kadar basit ... ama mümkün değil :)

Word_id (int), text_id (int), word_count (int): table id (int birincil değiştirmemesi), değer (varchar benzersiz) ve bunun gibi bağlantı tablo oluşturun. Sonra yarma metinden sonra yeni değerleri ile tabloları doldurun.

Son olarak hızlı bir şekilde DB endeksli tamsayılar (kimlikleri) ile çalışan, istediğiniz bu veri herşeyi yapabiliyor.

UPDATE: Here are the tables and queries:

CREATE TABLE terms (
    id int(11) NOT NULL auto_increment, value char(255) NOT NULL,
    PRIMARY KEY  (`id`), UNIQUE KEY `value` (`value`)
);

CREATE TABLE `terms_in_articles` (
    term int(11) NOT NULL, 
    article int(11) NOT NULL, 
    cnt int(11) NOT NULL default '1',
    UNIQUE KEY `term` (`term`,`article`)
);


/* Returns all unique terms in both articles (your $all_terms) */
SELECT t.id, t.value 
FROM terms t, terms_in_articles a 
WHERE a.term = t.id AND a.article IN (1, 2);

/* Returns your $term_vector1, $term_vector2 */
SELECT article, term, cnt 
FROM terms_in_articles 
WHERE article IN (1, 2) ORDER BY article;

/* Returns article and total count of term entries in it ($length1, $length2) */
SELECT article, SUM(cnt) AS total 
FROM terms_in_articles 
WHERE article IN (1, 2) GROUP BY article;

/* Returns your $score wich you may divide by ($length1 / $length2) from previous query */
SELECT SUM(tmp.term_score) * 500 AS total_score FROM 
(
    SELECT (a1.cnt * a2.cnt) AS term_score 
    FROM terms_in_articles a1, terms_in_articles a2 
    WHERE a1.article = 1 AND a2.article = 2 AND a1.term = a2.term
    GROUP BY a2.term, a1.term
) AS tmp;

Eh, şimdi, umarım bu yardımcı olur? 2 son sorguları görevi gerçekleştirmek için yeterlidir. Diğer sorgular ihtimale vardır. Tabii, "en popüler terimleri" vb gibi daha istatistikleri güvenebilirsiniz ..

İşte orijinal işlevi biraz optimize edilmiş versiyonu. Bu aynı sonuçları üretir. (I 10000 + terimler ve benzeri 20 çalışan her ile Wikipedia'da iki makalelerde çalıştırın:

check():
test A score: 4.55712524522
test B score: 5.08138042619
--Time: 1.0707

check2():
test A score: 4.55712524522
test B score: 5.08138042619
--Time: 0.2624

İşte kod:

function check2($terms_in_article1, $terms_in_article2) {
    $length1 = count($terms_in_article1); // number of words
    $length2 = count($terms_in_article2); // number of words

    $score_table = array();
    foreach($terms_in_article1 as $term){
        if(!isset($score_table[$term])) $score_table[$term] = 0;
        $score_table[$term] += 1;
    }
    $score_table2 = array();
    foreach($terms_in_article2 as $term){
        if(isset($score_table[$term])){
            if(!isset($score_table2[$term])) $score_table2[$term] = 0;
            $score_table2[$term] += 1;
        }
    }
    $score =0;
    foreach($score_table2 as $key => $entry){
        $score += $score_table[$key] * $entry;
    }
    $score = $score / ($length1*$length2);
    $score *= 500;
    return $score;
}

(Btw. diziler içine tüm kelimeleri bölmek için gereken süre dahil değildir.)

EDIT: daha açık olmaya çalışıyorum:

  1. First, encode every term into an integer. You can use a dictionary associative array, like this:

       $count = 0;
        foreach ($doc as $term) {
          $val = $dict[$term];
          if (!defined($val)) {
            $dict[$term] = $count++;
          }
          $doc_as_int[$val] ++;
        }
    

    This way, you replace string calculations with integer calculations. For example, you can represent the word "cloud" as the number 5, and then use the index 5 of arrays to store counts of the word "cloud". Notice that we only use associative array search here, no need for CRC etc.

  2. , Bir matris olarak tüm metni saklanabilir mı, tercihan bir sparse one.
  3. Kullan feature selection (PDF).
  4. Belki daha hızlı bir dilde bir yerel uygulamasını kullanabilirsiniz.
  5. Ben size yaklaşık 20 kümeleri ile ilk kullanımı K-araçları önermek, bu şekilde belge başka yakın olan bir taslak olsun, ve sonra her küme içinde yalnızca çiftleri karşılaştırın. Yaklaşık 6000 karşılaştırmalar yerine 19900 - eşit boyutlu küme varsayarsak, bu 20*200 + 20*10*9 karşılaştırmalar sayısını artırır.

Almak için başka bir yaklaşım belgeler arasında benzerlik bulmak için bir veri külliyatını güçlendirir Latent Semantic Analiz olurdu.

Çalışır yolu metnin ortak oluşumu matris alma ve Corpus karşılaştırarak, aslında bir 'semantik boşluk' belgenizin soyut bir konumu ile size sunarak olduğunu. Eğer LSA Semantik uzayda Öklid mesafe kullanarak belgeleri karşılaştırmak gibi bu metin karşılaştırma hızlandırır. Bu oldukça eğlenceli anlamsal indeksleme bulunuyor. Böylece yeni yazılar ekleyerek çok daha uzun sürmez.

Ben sadece okulda öğrendim ama KnowledgeSearch algoritması bir açık kaynak uygulaması olduğunu görünür olan, bu yaklaşımın belirli bir kullanım durumunda veremez.

(Üzgünüz, onun benim ilk yazı, bu yüzden link açamazsınız, sadece onu aramak)

Eğer karşılaştırmak için yerine dizilerin basit metin kullanabilirsiniz, ve ben hedef olduğu doğru anlaşıldığı takdirde, sen google gibi vermek için genellikle kullanılan (levenshtein php fonksiyonunu kullanabilirsiniz eğer anlam mı ... php arama motorlarında? 'işlevi).

Bu konum kullanılarak ters şekilde çalışır: iki dizeleri arasındaki farkı döndürür.

Örnek:

<?php
function check($a, $b) {
    return levenshtein($a, $b);
}

$a = 'this is just a test';
$b = 'this is not test';
$c = 'this is just a test';

echo check($a, $b) . '<br />';
//return 5
echo check($a, $c) . '<br />';
//return 0, the strings are identical
?>

Ama bu yürütme hızını artırmak eğer tam olarak bilmiyorum .. ama belki evet, take-out birçok foreach döngüleri ve array_merge işlevi.

EDIT:

Hız için bir basit bir test (30-saniye-wroted-script,% 100 eh accurated değil):

function check($terms_in_article1, $terms_in_article2) {
    $length1 = count($terms_in_article1); // number of words
    $length2 = count($terms_in_article2); // number of words
    $all_terms = array_merge($terms_in_article1, $terms_in_article2);
    $all_terms = array_unique($all_terms);
    foreach ($all_terms as $all_termsa) {
        $term_vector1[$all_termsa] = 0;
        $term_vector2[$all_termsa] = 0;
    }
    foreach ($terms_in_article1 as $terms_in_article1a) {
        $term_vector1[$terms_in_article1a]++;
    }
    foreach ($terms_in_article2 as $terms_in_article2a) {
        $term_vector2[$terms_in_article2a]++;
    }
    $score = 0;
    foreach ($all_terms as $all_termsa) {
        $score += $term_vector1[$all_termsa]*$term_vector2[$all_termsa];
    }
    $score = $score/($length1*$length2);
    $score *= 500; // for better readability
    return $score;
}


$a = array('this', 'is', 'just', 'a', 'test');
$b = array('this', 'is', 'not', 'test');

$timenow = microtime();
list($m_i, $t_i) = explode(' ', $timenow);

for($i = 0; $i != 10000; $i++){
    check($a, $b);
}
$last = microtime();
list($m_f, $t_f) = explode(' ', $last);
$fine = $m_f+$t_f;
$inizio = $m_i+$t_i;
$quindi = $fine - $inizio;
$quindi = substr($quindi, 0, 7);
echo 'end in ' . $quindi . ' seconds';

Yazıcı: 0.36765 saniye sonu

Ikinci test:

<?php
function check($a, $b) {
    return levenshtein($a, $b);
}

$a = 'this is just a test';
$b = 'this is not test';

$timenow = microtime();
list($m_i, $t_i) = explode(' ', $timenow);
for($i = 0; $i != 10000; $i++){
    check($a, $b);
}
$last = microtime();
list($m_f, $t_f) = explode(' ', $last);
$fine = $m_f+$t_f;
$inizio = $m_i+$t_i;
$quindi = $fine - $inizio;
$quindi = substr($quindi, 0, 7);
echo 'end in ' . $quindi . ' seconds';
?>

Yazıcı: 0.05023 saniye sonu

So, yes, seem faster. Would be nice to try with many array items (and many words for levenshtein)

2°EDIT:

Benzer bir metin ile hız levenshtein yöntemine eşit gibi görünüyor:

<?php
function check($a, $b) {
    return similar_text($a, $b);
}

$a = 'this is just a test ';
$b = 'this is not test';

$timenow = microtime();
list($m_i, $t_i) = explode(' ', $timenow);
for($i = 0; $i != 10000; $i++){
    check($a, $b);
}
$last = microtime();
list($m_f, $t_f) = explode(' ', $last);
$fine = $m_f+$t_f;
$inizio = $m_i+$t_i;
$quindi = $fine - $inizio;
$quindi = substr($quindi, 0, 7);
echo 'end in ' . $quindi . ' seconds';
?>

Yazıcı: 0.05988 saniye sonu

Ama fazla 255 karakter alabilir:

Note also that the complexity of this algorithm is O(N**3) where N is the length of the longest string.

ve hatta yüzdesi similary değer döndürebilir:

function check($a, $b) {
    similar_text($a, $b, $p);
    return $p;
}

Yet another edit

Peki bunun yerine tüm veri ve döngü onları alınmasıyla, sql sorgu doğrudan karşılaştırmak yapmak için, bir veritabanı işlevi oluşturmak?

If youre running Mysql, give a look at this one (hand-made levenshtein function, still 255 char limit) Else, if youre on Postgresql, this other one (many functions that should be evalutate)