【问题标题】:Best clustering algorithm? (simply explained)最好的聚类算法? (简单解释)
【发布时间】:2010-10-25 14:03:37
【问题描述】:

想象以下问题:

  • 您有一个数据库,其中包含一个名为“articles”的表中的大约 20,000 条文本
  • 您希望使用聚类算法连接相关文章,以便一起显示相关文章
  • 算法应该进行平面聚类(非分层)
  • 相关文章应插入“相关”表中
  • 聚类算法应根据文本判断两篇或多篇文章是否相关
  • 我想用 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;
    }
}
?>

我还想说,我知道有很多聚类算法,但在每个站点上都只有数学描述,这对我来说有点难以理解。所以(伪)代码中的编码示例会很棒。

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

【问题讨论】:

  • 有一些 WordPress 插件(是的,糟糕,我知道,别管我了)在这方面做得非常好,它们实际上执行了合理的聚类(通常他们使用k-means 或类似的东西),您可以使用它们来获得灵感(其中一些是 MIT 下的开源)。
  • 我认为 Anony-Mousse 是对的:集群在这里并不是理想的工具。如果每个文档仅属于 1 个集群,那么您会遇到一个问题,即集群边界附近的文档与其他附近集群中的文档更相似,而不是它们自己集群中的大多数文档。

标签: algorithm text cluster-analysis data-mining text-mining


【解决方案1】:

我所知道的对文本数据执行此操作的最标准方法是使用“词袋”技术。

首先,为每篇文章创建一个单词的“直方图”。假设在所有文章之间,它们之间只有 500 个唯一词。那么这个直方图将是一个大小为 500 的向量(数组,列表,随便),其中数据是每个单词在文章中出现的次数。因此,如果向量中的第一个点表示“asked”这个词,并且该词在文章中出现了 5 次,那么 vector[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 更容易)

这是基本思想。注意阈值是半任意的;您可能希望找到一种对直方图的点积进行归一化的好方法(这几乎必须在某处考虑文章长度)并决定您认为“相关”的内容。

此外,您不应该只将每个单词都放入直方图中。通常,您会希望包含那些使用频率不高的那些:不是在每篇文章中,也不是在仅一篇文章中。这为您节省了一些直方图开销,并增加了关系的价值。

顺便说一下,这个技术有更详细的描述here

【讨论】:

  • 非常感谢!我尝试用 PHP 编写您的方法,结果如下:paste.bradleygill.com/index.php?paste_id=9290 我希望您的 PHP 仍然足以说明它是否正确。
  • 在我看来是正确的,但是,根据您的应用程序,您真的需要考虑保持术语向量的状态。此外,考虑将分数除以文章 a 的长度乘以文章 b 的长度。否则,您将看到仅与边缘相关的长文章存在偏差。
  • 抱歉,这当然是一个愚蠢的问题,但是“考虑保持术语向量的状态”到底是什么意思。关于第二点:您的意思是“$score = $score/$length_a*$length_b”还是“$score = $score/($length_a*$length_b)”?应该是第一个吧?
  • 我的意思是,不要在您要比较两篇文章时创建该向量,而是在任何人保存文章并将其存储在数据库中时生成该向量。第二点:你想要'$score = $score/($length_a*$length_b)'。如果您查看我上面提供的链接,它会更多地说明您应该这样做的原因(您基本上可以找到两个向量之间的“角度”)
  • 感谢您的快速回复。现在应该终于正确了:paste.bradleygill.com/index.php?paste_id=9326
【解决方案2】:

方法#1 中调用的similar_text 函数是什么样的?我认为您所指的不是聚类,而是相似性度量。我无法真正改进 White Walloun 的 :-) 直方图方法 - 一个值得阅读的有趣问题。

无论您如何实现check(),您都必须使用它进行至少 2 亿次比较(20000^2 的一半)。 “相关”文章的截断可能会限制您在数据库中存储的内容,但似乎过于随意而无法捕捉所有有用的文本聚类,

我的方法是修改 check() 以返回“相似度”指标($prozentrtn)。将20K x 20K 矩阵写入文件并使用外部程序执行聚类以识别每篇文章的最近邻居,您可以将其加载到related 表中。我会在 R 中进行聚类 - 有一个很好的 tutorial 用于从 php 运行 R 的文件中聚类数据。

【讨论】:

  • 函数similar_text()“计算两个字符串之间的相似度,如 Oliver [1993] 中所述”。是的,你是对的,它是一个相似度指标。但是您需要对聚类进行相似性检查,不是吗?
【解决方案3】:

我认为您需要对集群做出一些设计决策,然后从那里继续:

  1. 为什么要对文本进行聚类?是否要一起显示相关文档?您想通过集群探索您的文档语料库吗?
  2. 因此,您想要flat 还是hierarchical 集群?
  3. 现在我们在两个方面遇到了复杂性问题:首先,您从文本中创建的特征的数量和类型 - 单个单词可能有数万个。您可能想尝试一些feature selection - 例如在忽略stop words 后取N 个信息量最大的词,或者出现次数最多的N 个词。
  4. 其次,您希望尽量减少测量文档之间相似性的次数。正如 bubaker 正确指出的那样,检查所有文档对之间的相似性可能太多了。如果聚类成少量的簇就够了,可以考虑K-means clustering,基本上就是:选择一个初始的K个文档作为聚类中心,将每个文档分配到最近的聚类,通过寻找文档向量均值重新计算聚类中心,然后迭代.每次迭代只需花费 K* 数量的文档。我相信还有一些启发式方法可以减少层次聚类所需的计算数量。

【讨论】:

  • 谢谢,好问题! 1)我想一起显示相关文档。 2)算法应该做平面聚类。 3)如果文本很长,这将很有用,但在我的情况下,文章最多包含 510 个字符。所以真的没有必要,不是吗? 4) 使用 k-means 的方法听起来不错,但我需要大量集群,并且必须不断创建新集群。不过,我可以使用 k-means 吗?
  • 您可以使用 K 均值,其中 K 非常大。代价是必须检查每个文档与每个集群中心的相似性。 “不断地创建新的集群”对我来说听起来像是一个自上而下的层次集群,但你可以在几个时期内工作——从一个小的 K 开始,运行 K-means 直到它收敛,然后使用这些集群。稍后,增加 K,从头开始重新运行 K-means,并使用生成的集群等。
  • 哦,我不知道 k-means 究竟是如何工作的。如果它像那样工作,我不能使用它,因为我不知道集群中心的数量。我有一个新闻文章数据库,所有关于同一主题的文章都应该分组。
【解决方案4】:

也许集群是错误的策略

如果您想显示 相似 文章,请改用 相似性搜索

对于文字文章,这是很好理解的。只需将您的文章插入 Lucene 等文本搜索数据库,然后使用您当前的文章作为搜索查询。在 Lucene 中,有一个 query called MoreLikeThis 正是这样做的:查找相似的文章。

集群是错误的工具,因为(特别是根据您的要求),每篇文章都必须放入某个集群;并且集群中的每个对象的相关项目都是相同的。如果数据库中存在异常值(很可能是这种情况),它们可能会破坏您的聚类。此外,集群可能非常大。没有大小限制,聚类算法可能会决定将一半的数据集放入同一个聚类中。因此,您的数据库中的每篇文章都有 10000 篇相关文章。使用相似度搜索,您只需获取每个文档的前 10 个相似项!

最后但并非最不重要的一点:忘记使用 PHP 进行集群。它不是为此而设计的,性能也不够好。但是您可能可以很好地从 PHP 访问 lucene 索引。

【讨论】:

    猜你喜欢
    • 2020-07-31
    • 2011-10-15
    • 2016-08-24
    • 2012-08-14
    • 2019-04-28
    • 2017-10-12
    • 2012-06-08
    • 2013-10-25
    • 2014-12-06
    相关资源
    最近更新 更多