将大量文本(聚类)与矩阵进行比较

时间:2009-05-23 15:21:36

标签: php cluster-analysis text-comparison

我有以下PHP函数来计算文本之间的关系:

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

变量$terms_in_articleX必须是包含文本中出现的所有单个单词的数组。

假设我有一个包含20,000个文本的数据库,这个函数需要很长时间来运行所有连接。

如何加快这一过程?我应该将所有文本添加到一个巨大的矩阵中,而不是总是只比较两个文本吗?如果您有一些代码方法,最好是在PHP中,这将是很好的。

我希望你能帮助我。提前谢谢!

5 个答案:

答案 0 :(得分:4)

您可以在添加文本时拆分文本。简单示例:preg_match_all(/\w+/, $text, $matches);当然真正的分裂不是那么简单......但可能,只需更正模式:)

创建表id(int primary autoincrement),value(varchar unique)和link-table,如下所示:word_id(int),text_id(int),word_count(int)。然后在拆分文本后用新值填充表格。

最后,您可以使用所需的任何数据,快速使用数据库中的索引整数(ID)进行操作。

更新: 以下是表格和查询:

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;

嗯,现在,我希望,这会有所帮助吗?最后两个查询足以执行您的任务。其他查询以防万一。当然,你可以计算更多的统计数据,如“最受欢迎的术语”等......

答案 1 :(得分:1)

编辑:试图更明确:

  1. 首先,将每个术语编码为一个 整数。你可以使用字典 关联数组,如下所示:

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

    这样,您可以替换字符串 用整数计算 计算。例如,你可以 代表“云”这个词 数字5,然后使用索引5 数组的存储计数 单词“云”。请注意我们只 在这里使用关联数组搜索, 不需要CRC等。

  2. 将所有文本存储为矩阵,最好是sparse one
  3. 使用feature selection (PDF)
  4. 也许以更快的语言使用本机实现。
  5. 我建议你首先使用带有大约20个簇的K-means,这样就得到了哪个文档在另一个附近的粗略草图,然后只比较每个簇内的对。假设统一大小的群集,这可以改善与20*200 + 20*10*9的比较次数 - 大约6000次比较而不是19900次。

答案 2 :(得分:1)

这是原始功能的略微优化版本。它产生完全相同的结果。 (我在维基百科的两篇文章中运行它,有10000多个术语,每个都运行20次:

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

以下是代码:

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

(顺便说一句。不包括将所有单词拆分成数组所需的时间。)

答案 3 :(得分:0)

如果您可以使用简单的文本而不是数组进行比较,并且如果我理解您的目标所在,您可以使用levenshtein php函数(通常用于给谷歌类似的'你有没有意思是......?'在php搜索引擎中的功能)。

它以与您使用相反的方式工作:返回两个字符串之间的差异。

示例:

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

但我不确切知道这是否会提高执行速度..但也许是的,你可以取出许多foreach循环和array_merge函数。

修改

一个简单的速度测试(是一个30秒的错误脚本,它不是100%准确的呃):

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';

打印:以 0.36765 秒结束

第二次测试:

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

打印:以 0.05023 秒结束

所以,是的,似乎更快。 尝试使用许多数组项目(以及levenshtein的许多单词)会很高兴

<强> 2°修改

使用相似的文本,速度似乎等于levenshtein方法:

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

打印:以 0.05988 秒结束

但它可能需要超过255个char:

  

还要注意这个的复杂性   算法是O(N ** 3),其中N是   最长字符串的长度。

并且,甚至可以以百分比形式返回相似的值:

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

又一个编辑

如何创建数据库函数,直接在sql查询中进行比较,而不是检索所有数据并循环它们?

如果您正在运行Mysql,请查看this one(手工制作的levenshtein函数,仍然是255个char限制) 否则,如果您使用的是Postgresql,this other one(许多应该重新评估的函数)

答案 4 :(得分:0)

另一种方法是潜在语义分析,它利用大量数据来查找文档之间的相似之处。

它的工作方式是通过获取文本的共同矩阵并将其与语料库进行比较,实质上为您提供文档在“语义空间”中的抽象位置。这将加快您的文本比较,因为您可以在LSA语义空间中使用欧几里德距离比较文档。这是非常有趣的语义索引。因此,添加新文章不会花费更长的时间。

我不能给出这种方法的具体用例,只是在学校里学到了它,但知识搜索似乎是算法的开源实现。

(对不起,这是我的第一篇文章,因此无法发布链接,只需查看)