19

次の問題を想像してください。

  • 「articles」というテーブルに約 20,000 のテキストを含むデータベースがあります。
  • 関連記事をまとめて表示するために、クラスタリングアルゴリズムを使って関連記事をつなぎたい
  • アルゴリズムはフラット クラスタリングを行う必要があります (階層的ではありません)。
  • 関連記事は「関連」テーブルに挿入する必要があります
  • クラスタリング アルゴリズムは、テキストに基づいて、2 つ以上の記事が関連しているかどうかを判断する必要があります。
  • PHPでコーディングしたいが、疑似コードや他のプログラミング言語を使ったサンプルでもOK

2 つの入力記事が関連している場合は「true」を返し、そうでない場合は「false」を返す関数 check() を使用して最初のドラフトをコーディングしました。残りのコード (データベースからの記事の選択、比較対象の記事の選択、関連記事の挿入) も完了しています。たぶん、残りも改善できます。しかし、私にとって重要なポイントは関数 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 [check() のみ]

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

4 に答える 4

15

あなたが持っているようなテキストデータでこれを行うために私が知っている最も標準的な方法は、「単語のバッグ」手法を使用することです。

まず、記事ごとに単語の「ヒストグラム」を作成します。すべての記事の間に、500 の固有の単語しかないとします。次に、このヒストグラムはサイズ 500 のベクトル (配列、リスト、何でも) になり、データは各単語が記事に表示される回数です。したがって、ベクトルの最初のスポットが「asked」という単語を表し、その単語が記事に 5 回出現した場合、vector[0] は 5 になります。

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

さて、任意の 2 つの記事を比較するのは非常に簡単です。単純に 2 つのベクトルを乗算します。

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

(PHP の代わりに python を使用して申し訳ありません。私の PHP は錆びており、zip を使用すると少し簡単になります)

これが基本的な考え方です。しきい値が半任意であることに注意してください。おそらく、ヒストグラムの内積を正規化する良い方法を見つけて (これは記事の長さを考慮に入れる必要があります)、何を「関連」と見なすかを決定する必要があります。

また、すべての単語をヒストグラムに入れるだけではいけません。一般に、使用頻度が半端ないものを含める必要があります。すべての記事や 1 つの記事だけではありません。これにより、ヒストグラムのオーバーヘッドが少し節約され、関係の価値が高まります。

ちなみに、このテクニックはこちらで詳しく説明されています

于 2009-05-12T15:29:55.097 に答える
6

ここでは、クラスタリングが間違った戦略ではないでしょうか?

類似の記事を表示したい場合は、代わりに類似検索を使用してください。

テキスト記事の場合、これはよく理解されています。記事を Lucene などのテキスト検索データベースに挿入し、現在の記事を検索クエリとして使用するだけです。Lucene には、まさにこれを実行する と呼ばれるクエリMoreLikeThisがあります: 類似記事を検索します。

クラスタリングは間違ったツールです。なぜなら (特に要件がある場合)、すべての記事を何らかのクラスターに配置する必要があるためです。関連するアイテムは、クラスター内のすべてのオブジェクトで同じになります。データベースに外れ値がある場合 (非常に可能性の高いケース)、クラスタリングが台無しになる可能性があります。さらに、クラスタは非常に大きくなる場合があります。サイズの制約はありません。クラスタリング アルゴリズムによって、データ セットの半分が同じクラスタに配置される場合があります。したがって、データベース内の記事ごとに 10000 件の関連記事があります。類似検索なら、文書ごとに上位10件の類似アイテムを取得できます!

最後になりましたが、クラスタリング用の PHP は忘れてください。このために設計されておらず、十分なパフォーマンスがありません。しかし、おそらく PHP から lucene インデックスに十分にアクセスできます。

于 2015-05-02T10:42:27.027 に答える
1

クラスタリングについていくつかの設計上の決定を下す必要があると思います。そこから続行します。

  1. なぜテキストをクラスタ化するのですか? 関連文書をまとめて表示しますか?クラスターを介してドキュメント コーパスを探索しますか?
  2. その結果、フラットなクラスタリングと階層的なクラスタリングのどちらが必要ですか?
  3. ここで、2 つの次元での複雑さの問題があります。まず、テキストから作成する機能の数と種類です。個々の単語は数万に及ぶ場合があります。ストップ ワードを無視した後、最も有益な N 語、または最も頻繁に出現する N 語を取得するなど、いくつかの機能選択を試してみることをお勧めします。
  4. 次に、ドキュメント間の類似性を測定する回数を最小限に抑えたいと考えています。bubaker が正しく指摘しているように、ドキュメントのすべてのペア間の類似性をチェックするのは多すぎるかもしれません。少数のクラスタへのクラスタリングで十分な場合は、K-means クラスタリングを検討できます。これは基本的に、最初の K 個のドキュメントをクラスタの中心として選択し、すべてのドキュメントを最も近いクラスタに割り当て、ドキュメント ベクトルの平均を見つけてクラスタの中心を再計算し、繰り返します。これは、反復ごとに K* 個のドキュメントしかかかりません。階層的クラスタリングに必要な計算数を減らすためのヒューリスティックもあると思います。
于 2009-05-13T08:17:22.177 に答える
0

similar_textアプローチ #1 で呼び出される関数はどのようなものですか? あなたが言及しているのはクラスタリングではなく、類似性メトリックだと思います。White Walloun の :-) ヒストグラム アプローチを実際に改善することはできません。これは興味深い問題です。

をどのように実装check()しても、それを使用して少なくとも 2 億回 (の半分20000^2) の比較を行う必要があります。「関連する」記事のカットオフは、データベースに保存するものを制限する可能性がありますが、テキストの有用なクラスタリングをすべてキャッチするには恣意的すぎるようです。

check()私のアプローチは、「類似性」メトリック($prozentまたは)を返すように変更することrtnです。マトリックスをファイルに書き込み、20K x 20K外部プログラムを使用してクラスタリングを実行して、各記事の最近傍を特定します。これをrelatedテーブルに読み込むことができます。でクラスタリングを行いますR-から実行されているファイル内のデータをクラスタリングするための優れたチュートリアルがあります。Rphp

于 2009-05-12T17:26:47.513 に答える