9

私は現在、用語 Web サービスのあいまい検索の実装に取り​​組んでおり、現在の実装を改善する方法についての提案を探しています。共有するにはコードが多すぎますが、思慮深い提案を促すには説明で十分だと思います。読むのは大変だと思いますが、助けていただければ幸いです。

まず、用語は基本的に名前 (または用語) の数です。単語ごとに、スペースでトークンに分割し、各文字を反復処理してトライに追加します。ターミナル ノード (イチゴの文字 y に達したときなど) では、マスター ターム リストへのインデックスをリストに格納します。そのため、ターミナル ノードは複数のインデックスを持つことができます (イチゴのターミナル ノードは「イチゴ」と「イチゴ アレルギー」に一致するため)。

実際の検索に関しては、検索クエリもスペースごとにトークンに分割されます。検索アルゴリズムはトークンごとに実行されます。検索トークンの最初の文字は一致する必要があります (したがって、traw はいちごと一致しません)。その後、連続する各ノードの子を調べます。一致する文字を持つ子があれば、検索トークンの次の文字で検索を続けます。子が指定された文字と一致しない場合は、検索トークンの現在の文字を使用して子を調べます (したがって、それを進めません)。これはあいまいな部分なので、「stwb」は「strawberry」に一致します。

検索トークンの最後に到達すると、そのノードの残りのトライ構造を検索して、すべての潜在的な一致を取得します (マスター ターム リストへのインデックスはターミナル ノードにのみあるため)。これをロールアップと呼びます。BitSet に値を設定してインデックスを保存します。次に、単純に各検索トークンの結果から BitSet を取得します。次に、anded BitSet から最初の 1000 または 5000 のインデックスを取得し、それらが対応する実際の用語を見つけます。レーベンシュタインを使用して各用語をスコアリングし、スコアで並べ替えて最終結果を取得します。

これはかなりうまく機能し、かなり高速です。ツリーには 39 万を超えるノードと、110 万を超える実際の用語名があります。しかし、このままでは問題があります。

たとえば、「car cat」を検索すると、望ましくない場合でも Catheterization が返されます (検索クエリが 2 つの単語であるため、結果は少なくとも 2 つになるはずです)。これは簡単に確認できますが、2 つの単語であるため、カテーテル挿入手順のような状況には対処できません。理想的には、心臓カテーテル法のようなものと一致させたいと考えています.

これを修正する必要性に基づいて、いくつかの変更を考え出しました。1 つは、深さ/幅が混在する探索でトライを通過することです。基本的に、キャラクターが一致する限り、深さを優先します。一致しなかった子ノードは優先キューに追加されます。優先キューは、トライの検索中に計算できる編集距離によって順序付けられます (文字の一致がある場合、距離は同じままであり、そうでない場合は 1 増加するため)。これにより、各単語の編集距離が得られます。BitSet は使用しなくなりました。代わりに、Terminfo オブジェクトへのインデックスのマップです。このオブジェクトには、クエリ フレーズのインデックスと用語フレーズ、およびスコアが格納されます。検索が「car cat」で、一致する用語が「Catheterization procedure」の場合 用語フレーズ インデックスは、クエリ フレーズ インデックスと同様に 1 になります。「Cardiac Catheterization」の場合、語句インデックスはクエリ フレーズ インデックスと同様に 1,2 になります。ご覧のとおり、後で単語フレーズ インデックスとクエリ フレーズ インデックスの数を確認するのは非常に簡単です。それらが少なくとも検索語数と等しくない場合は、それらを破棄できます。

その後、単語の編集距離を合計し、単語句インデックスに一致する単語を単語から削除し、残りの文字を数えて真の編集距離を取得します。たとえば、「イチゴ アレルギー」という用語に一致し、検索クエリが「ストロー」であった場合、イチゴのスコアは 7 になります。その場合、用語フレーズ インデックスを使用して用語からイチゴを除外し、カウントするだけです。 「アレルギー」(スペースを除く)で 16 のスコアを取得します。

これにより、期待どおりの正確な結果が得られます。ただし、速度が遅すぎます。以前は 1 単語の検索で 25 ~ 40 ミリ秒を取得できましたが、今では 0.5 秒にもなる可能性があります。これは主に、TermInfo オブジェクトのインスタンス化、.add() 操作、.put() 操作の使用、および多数の一致を返さなければならないという事実によるものです。各検索を 1000 件の一致のみを返すように制限することはできますが、「car」の最初の 1000 件の結果が「cat」の最初の 1000 件の一致のいずれかに一致するという保証はありません (110 万以上の用語があることを思い出してください)。

cat のような単一のクエリ ワードの場合でも、多数の一致が必要です。これは、'cat' を検索すると、検索が car に一致し、その下のすべてのターミナル ノードがロールアップされるためです (これは非常に多くなります)。ただし、結果の数を制限すると、編集距離ではなく、クエリで始まる単語が強調されすぎてしまいます。したがって、カテーテル法などの単語は、コートなどの単語よりも含まれる可能性が高くなります。

では、基本的に、2 番目の実装で修正された問題をどのように処理できるかについて何か考えはありますか? 物事を明確にするために選択したコードを含めることができますが、巨大なコードの壁を投稿したくありませんでした.

4

2 に答える 2

3

うわー…タフなもの。

では、lucene を実装してみませんか? あなたのような問題に関しては、これは最高の最新技術です。

しかし、私はいくつかの考えを共有したい...

あいまいさはわら*のようなものではなく、いくつかの単語のタイプミスです。そして、行方不明/間違った文字ごとに、距離に 1 が追加されます。

一般的に、部分一致 (ワイルドカード) とあいまいさを同時に持つことは非常に困難です。

一般に、トークン化は良い考えです。

また、すべてが取得するデータに大きく依存します。ソース ファイルにスペル ミスがありますか?それとも検索クエリにのみスペル ミスがありますか?

多次元の範囲ツリーを使用したかなり優れた実装を見てきました。

しかし、上記のすべてを達成したいのであれば、グラフ セットと適切なインデックス作成アルゴリズムをうまく組み合わせる必要があると思います。

たとえば、ゴマのようなセマンティック データベースを使用し、ドキュメントをインポートするときに、すべてのトークンとドキュメントをノードとしてインポートできます。次に、ドキュメント内の位置などに応じて、加重関係を追加できます。

次に、bk ツリーなどの効率的なあいまい一致を実行できる構造のトークンが必要です。mysql データベースでトークンのインデックスを作成し、ビットごとの比較関数を実行して違いを取得できると思います。一致するすべてのビットを返す関数があります。文字列を ascii に変換し、ビットをグループ化すると、非常に高速に処理できます。

ただし、トークンを文字列に一致させた場合は、仮想の完全一致アンチティを構築し、セマンティック データベースに最も近い隣接要素をクエリできます。

部分一致を達成するためにトークン化するときは、単語を部分単語に分割する必要があります。

ただし、ワイルドカード マッチ (プレフィックス、サフィックス、またはその両方) も実行できますが、あいまいさはありません。

単語全体またはトークンのさまざまな連結を索引付けすることもできます。

ただし、これをサポートする特別な bk-tree 実装があるかもしれませんが、私は見たことがありません。

于 2010-10-31T16:08:21.657 に答える
2

私は何年も前にスペル修正を何度も繰り返しましたが、これが基本的な方法の最近の説明です。基本的に、正しい単語の辞書はトライにあり、検索は単純な分枝限定法です。私は、深さ優先探索を繰り返し使用しました。距離を増やすたびに、より多くのトライが歩くことになります。そのため、距離が短い場合のコストは、基本的に距離の指数関数になります。したがって、深度と幅を組み合わせた検索を実行しても、それほど節約にはなりませんが、はるかに複雑です。

(余談ですが、医師が「アセチルサリチル酸」を綴る方法がいくつもあることに驚かれることでしょう。)

私はあなたのトライのサイズに驚いています。受け入れられる単語の基本的な辞書はおそらく数千です。次に、一般的な接頭辞と接尾辞があります。構造がトライなので、サブトライをつなげてスペースを大幅に節約できます。基本プレフィックスのトライがメインディクショナリに接続できるように、メインディクショナリのターミナルノードは一般的なサフィックスのトライに接続できます(実際にはサイクルを含めることができます)。言い換えれば、トライは有限状態マシンに一般化することができます。それはあなたに多くの柔軟性を与えます。

それにもかかわらず、パフォーマンスの問題があります。パフォーマンスの問題の良いところは、問題が悪化すればするほど、見つけやすくなることです。私はこれを指摘するStackOverflowの本当の害虫でした。このリンクは、それを行う方法を説明し、詳細な例へのリンクを示し、いくつかの一般的な神話を払拭しようとします。一言で言えば、最適化できる何かをするのに多くの時間を費やすほど、それを一時停止して見てみると、それを実行していることに気付く可能性が高くなります。私の疑惑は、答えを得るだけでなく、誇張されたデータ構造の操作に多くの時間が費やされていることです。これは一般的な状況ですが、サンプルが問題を直接指摘するまでは何も修正しないでください。

于 2010-10-21T17:58:27.943 に答える