web-dev-qa-db-ja.com

同様の結果を見つけて、類似度でソートする方法は?

類似度順に並べられたレコードを照会するにはどうすればよいですか?

例えば。 「Stock Overflow」を検索すると戻ります

  1. スタックオーバーフロー
  2. SharePointオーバーフロー
  3. 数学オーバーフロー
  4. 政治的オーバーフロー
  5. VFXオーバーフロー

例えば。 「LO」を検索すると、次が返されます。

  1. パブロ・ピカソ
  2. ミケランジェロ
  3. ジャクソン・ポロック

私が助けが必要なもの:

  1. より良い結果を得るために、検索エンジンを使用してMySQLテーブルにインデックスを付けて検索する

    • PHPで Sphinx 検索エンジンを使用する

    • PHPで Lucene エンジンを使用する

  2. フルテキストインデックスを使用して、類似または含まれる文字列を検索する


うまくいかないもの

  • レーベンシュタイン距離 は非常に不安定です。 ( [〜#〜] udf [〜#〜]クエリ
    「犬」を検索すると、次のことがわかります:
    1. 沼地
    2. 大きい
    3. エコー
  • LIKEはより良い結果を返しますが、同様の文字列は存在しますが、長いクエリに対しては何も返しません
    1. 教義
    2. ドガラル
    3. ドグマ
67
Robinicks

完全な文字列を別の完全な文字列に対して検索する場合、レーベンシュタイン距離は良いかもしれませんが、文字列内のキーワードを探している場合、このメソッドは必要な結果を返さない場合があります。さらに、SOUNDEX関数は英語以外の言語には適していないため、非常に制限されています。 LIKEで逃げることはできますが、実際には基本的な検索用です。達成したい他の検索方法を調べてください。例えば:

Lucene をプロジェクトの検索ベースとして使用できます。ほとんどの主要なプログラミング言語で実装されており、非常に高速で汎用性があります。この方法は、部分文字列だけでなく、文字の転置、接頭辞、および接尾辞(すべて組み合わせたもの)も検索するため、おそらく最適です。ただし、個別のインデックスを保持する必要があります(ただし、CRONを使用して、独立したスクリプトからインデックスを更新することもあります)。

または、MySQLソリューションが必要な場合、フルテキスト機能はかなり優れており、ストアドプロシージャよりも確かに高速です。テーブルがMyISAMでない場合、一時テーブルを作成してから、全文検索を実行できます。

CREATE TABLE IF NOT EXISTS `tests`.`data_table` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(2000) CHARACTER SET latin1 NOT NULL,
  `description` text CHARACTER SET latin1 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;

データジェネレータ を使用して、自分で作成したくない場合にランダムデータを生成します...

**[〜#〜] note [〜#〜]**:大文字と小文字を区別した検索を実行するには、列タイプをlatin1_binにする必要がありますlatin1では大文字と小文字が区別されません。 Unicode文字列の場合、大文字と小文字を区別する検索にはutf8_bin、大文字と小文字を区別しない検索にはutf8_general_ciをお勧めします。

DROP TABLE IF EXISTS `tests`.`data_table_temp`;
CREATE TEMPORARY TABLE `tests`.`data_table_temp`
   SELECT * FROM `tests`.`data_table`;

ALTER TABLE `tests`.`data_table_temp`  ENGINE = MYISAM;

ALTER TABLE `tests`.`data_table_temp` ADD FULLTEXT `FTK_title_description` (
  `title` ,
  `description`
);

SELECT *,
       MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE) as `score`
  FROM `tests`.`data_table_temp`
 WHERE MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE)
 ORDER BY `score` DESC;

DROP TABLE `tests`.`data_table_temp`;

詳細については MySQL APIリファレンスページ をご覧ください。

これの欠点は、文字の転置や「似たような音」の単語を検索しないことです。

**[〜#〜] update [〜#〜]**

Luceneを検索に使用すると、このジョブがPHPスクリプト(ig "cd/path/to/script)を実行するcronジョブ(すべてのWebホストにこの「機能」がある)を作成する必要があります。 ; php searchindexer.php ")インデックスを更新します。数千の「ドキュメント」(行、データなど)のインデックス作成には数秒、場合によっては数分かかることもありますが、これはすべての検索ができるだけ速く実行されるようにするためです。したがって、サーバーで実行される遅延ジョブを作成することもできます。それは一晩であるか、次の1時間で、これはあなた次第です。 PHPスクリプトは次のようになります。

$indexer = Zend_Search_Lucene::create('/path/to/lucene/data');

Zend_Search_Lucene_Analysis_Analyzer::setDefault(
  // change this option for your need
  new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

$rowSet = getDataRowSet();  // perform your SQL query to fetch whatever you need to index
foreach ($rowSet as $row) {
   $doc = new Zend_Search_Lucene_Document();
   $doc->addField(Zend_Search_Lucene_Field::text('field1', $row->field1, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::text('field2', $row->field2, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someValue', $someVariable))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someObj', serialize($obj), 'utf-8'))
  ;
  $indexer->addDocument($doc);
}

// ... you can get as many $rowSet as you want and create as many documents
// as you wish... each document doesn't necessarily need the same fields...
// Lucene is pretty flexible on this

$indexer->optimize();  // do this every time you add more data to you indexer...
$indexer->commit();    // finalize the process

次に、これが基本的な検索方法です(基本検索)。

$index = Zend_Search_Lucene::open('/path/to/lucene/data');

// same search options
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
   new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');

$query = 'php +field1:foo';  // search for the Word 'php' in any field,
                                 // +search for 'foo' in field 'field1'

$hits = $index->find($query);

$numHits = count($hits);
foreach ($hits as $hit) {
   $score = $hit->score;  // the hit weight
   $field1 = $hit->field1;
   // etc.
}

Java[〜#〜] php [〜#〜] 、および 。Net のLuceneに関する素晴らしいサイトがあります。

結論として各検索方法にはそれぞれ長所と短所があります:

  • あなたは Sphinx検索 に言及しましたが、あなたのウェブホスト上でデーモンを実行できる限り、それはとても良いように見えます。
  • Zend Luceneでは、データベースのインデックスを再作成するためにcronジョブが必要です。ユーザーにはまったく透過的ですが、これは、新しいデータ(または削除されたデータ!)がデータベースのデータと常に同期しているわけではないため、ユーザー検索ですぐに表示されないことを意味します。
  • MySQLのFULLTEXT検索は優れた高速ですが、最初の2つのパワーと柔軟性をすべて提供するわけではありません。

何かを忘れたり見逃した場合は、お気軽にコメントしてください。

83
Yanick Rochon

1。類似性

MySQLのレーベンシュタインについては、 www.codejanitor.com/wp/2007/02/10/levenshtein-distance-as-a-mysql-stored-function

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance 
FROM table 
WHERE 
    LEVENSHTEIN(column, 'search_string') < distance_limit
ORDER BY distance DESC

2。含む、大文字と小文字を区別しない

MySQLのLIKEステートメントを使用します。これはデフォルトで大文字と小文字を区別しません。 %はワイルドカードであるため、search_stringの前後に任意の文字列が存在する場合があります。

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%"

3。含む、大文字と小文字を区別する

MySQL Manual が役立ちます:

デフォルトの文字セットと照合はlatin1とlatin1_swedish_ciであるため、非バイナリ文字列比較ではデフォルトで大文字と小文字が区別されません。つまり、col_name LIKE 'a%'で検索すると、Aまたはaで始まるすべての列値が取得されます。この検索で​​大文字と小文字を区別するには、オペランドの1つが大文字と小文字を区別する照合またはバイナリ照合を持っていることを確認してください。たとえば、両方ともlatin1文字セットを持つ列と文字列を比較する場合、COLLATE演算子を使用して、いずれかのオペランドにlatin1_general_csまたはlatin1_bin照合順序を設定できます...

私のMySQLセットアップはlatin1_general_csまたはlatin1_binをサポートしていませんが、バイナリutf8では大文字と小文字が区別されるため、照合utf8_binを使用することはうまくいきました。

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%" COLLATE utf8_bin

2。/3.レーベンシュタイン距離でソート

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance // for sorting
FROM table 
WHERE 
    column_name LIKE "%search_string%"
    COLLATE utf8_bin // for case sensitivity, just leave out for CI
ORDER BY
    distance
    DESC
21
opatut

類似性の定義はセマンティックな類似性のようです。そのため、このような類似性関数を作成するには、セマンティック類似性尺度を使用する必要があります。問題の作業範囲は数時間から数年と異なる場合があるため、作業を開始する前に範囲を決定することをお勧めします。類似関係を構築するためにどのデータを持っているのかわかりませんでした。ドキュメントのデータセットとクエリのデータセットにアクセスできると仮定します。単語の共起から始めることができます(条件付き確率など)。 ストップワード のリストが非常に人気があるという理由だけで、ほとんどのワードに関連していることがすぐにわかります。条件付き確率のリフトを使用すると、ストップワードは処理されますが、少数の場合(ほとんどの場合)、関係がエラーになりやすくなります。 Jacard を試すこともできますが、対称であるため、見つからない多くの関係があります。次に、ベースWordから短い距離でのみ表示されるリレーションを検討します。一般的なコーパス(例:ウィキペディア)とユーザー固有(例:彼のメール)に基づいて関係を検討できます(また、検討する必要があります)。

非常にまもなく、すべての測定値が良好であり、他の測定値よりもいくつかの利点がある場合、多くの類似性測定値があります。

そのような手段を組み合わせるために、問題を分類問題に還元したいと思います。

単語のパリのデータセットを作成し、「関連する」というラベルを付ける必要があります。大きなラベル付きデータセットを作成するには、次のことができます。

  • ポジティブのために、既知の関連語のソース(古き良きウィキペディアカテゴリなど)を使用する
  • 関連するものとして知られていないWordのほとんどは関連していません。

次に、ペアの特徴として持っているすべてのメジャーを使用します。これで、教師付き分類問題の領域にいます。ニーズに応じて評価されたデータセットに分類子を構築し、ニーズに合った類似度を取得します。

3
DaL