アラビア語検索正規化:ハムザのバリエーション、ヤー/カーフの形式、および正書法上の等価性に対するサポートが不足

こんにちは、Discourseチーム様

現在、アラビア語とペルシャ語のコンテンツが多い多言語フォーラムを運営しており、アラビア語の表記正規化に関する検索機能の重大な制限に直面しています。

:magnifying_glass_tilted_left: 問題の説明

アラビア文字には、意味的に同一の文字に対して複数のUnicode表現が含まれています。残念ながら、Discourseの現在の検索エンジンは、これらのバリアントを別個のものとして扱っているようで、不完全または誤解を招く検索結果につながっています。

例:

  • إطلاق مقامي を検索しても完全一致しか返されず、اطلاق مقاميأطلاق مقامي、または إطلاق‌مقامي を含む投稿は除外されます。
  • 同様に、ي (U+064A) を検索しても ی (U+06CC) に一致せず、ك (U+0643) は ک (U+06A9) に一致しません。これらはアラビア語/ペルシャ語の文脈では機能的に同等です。

これはハムザのバリアント(أإءؤئ)だけでなく、一般的な置換にも影響します。

文字 Unicode 推奨される正規化
أ, إ, ء, آ U+0623, U+0625, U+0621, U+0622 ا に正規化
ؤ U+0624 و に正規化
ئ U+0626 ي に正規化
ى U+0649 ي に正規化
ة U+0629 ه に正規化
ي vs ی U+064A vs U+06CC ی に正規化
ك vs ک U+0643 vs U+06A9 ک に正規化

この問題は、ユーザーがダイアクリティカルマークを省略したり、異なるキーボードレイアウトを使用したりすると、さらに悪化し、検索結果が断片化します。


:gear: 提案される解決策

インデックス作成とクエリ解析の両方で、Unicode対応の正規化レイヤーを実装することをお勧めします。これは、次の方法で実現できます。

  1. インデックス付けされたコンテンツとユーザークエリの両方を前処理して、文字のバリアントを統一します。
  2. アラビア語NLPライブラリや検索エンジン(例:Farasa、Hazm、またはカスタム正規表現ベースのマッパー)で使用されているものと同様の正規化ルールを適用します。
  3. オプションで、あいまい検索またはレーベンシュタイン距離をサポートして、ほぼ完全な一致を実現します。

正規化関数の簡単な例(Javaスタイル)を以下に示します。

public static String normalizeArabic(String text) {
  return text.replace("أ", "ا")
             .replace("إ", "ا")
             .replace("آ", "ا")
             .replace("ؤ", "و")
             .replace("ئ", "ي")
             .replace("ى", "ي")
             .replace("ة", "ه")
             .replace("ي", "ی")
             .replace("ك", "ک");
}

:folded_hands: リクエスト

この正規化をコア検索エンジンまたはプラグインとして含めることを検討していただけますでしょうか?これにより、Discourseを使用しているアラビア語およびペルシャ語コミュニティのユーザビリティが大幅に向上します。

この問題に対処する既存の回避策やプラグインがあれば、ご guidance をいただけると幸いです。

お忙しい中、このような強力なプラットフォームを構築していただき、ありがとうございます。

敬具

「いいね!」 1

search_ignore_accents サイト設定は、この問題に影響しますか?

「いいね!」 1

ご参加いただき、また議論に貢献いただきありがとうございます。

ご質問にお答えしますと、フォーラムでは search_ignore_accents 設定が有効になっています。
残念ながら、これで直面している問題は解決しません。検索結果は、正書法的に同等なアラビア文字とペルシャ文字に一致しないため、その設定にもかかわらず問題は残っています。

「いいね!」 1

これは、アラビア語とペルシャ語のサイトの検索エクスペリエンスを大幅に向上させるため、妥当なリクエストだと思います。この機能が実装されたPRをレビューしたいので、pr-welcome を付けます。

この機能に取り組む方は、すべての正規化ロジックを、アラビア語とペルシャ語のサイトでデフォルトで有効になるサイト設定(site_settings.ymllocale_default を参照)の背後に配置してください。それ以外のロケールでは、この設定はデフォルトで無効にする必要があります。コアには、アクセント付き文字に対する同様の正規化ロジックが既に存在します(lib/search.rb を参照)。これは、この機能を実装する際の参考になるでしょう。

「いいね!」 4

オサマさん、本当にありがとうございます!この提案が好評で、とても嬉しいです。

「いいね!」 2

この問題のこの部分については、Unicode 標準の NFKC (1 つを選択) 正規化について話していますか?

(私は何をしているのかさえわかりません… クッキング パイプラインで投稿テキストを正規化していると仮定していますか?)

「いいね!」 1

私は技術専門家ではありませんが、ペルシャ語とアラビア語のバイリンガルDiscourseフォーラムで検索クエリが漏れなく処理されるようにするために、この問題について調査してきました。DiscourseはPostgreSQLを使用しているため、正規化が不可欠です。ユーザーがペルシャ文字で検索しても、同じ単語がアラビア文字で保存されている場合やその逆の場合があります。適切な正規化なしでは、検索は失敗します。

私の調査と探求から学んだことに基づくと、Unicode NFKC正規化を使用することは確実な出発点です。これは、合字、表示形式、アラビア語/ペルシャ語の数字など、多くの互換性のあるケースを処理します。

しかし、ペルシャ語とアラビア語のテキストの場合、NFKCだけでは不十分です。これは、視覚的および意味的に同等でありながら、バイナリレベルで異なるいくつかの重要な文字バリアントを正規化しません。

以下に、私の調査と探求を通じて到達した手順と洞察を概説します。


:wrench: 全体的な設計戦略

  1. まずUnicode NFKC正規化を適用して、合字、表示形式、数字の統一を処理します。
  2. 次にカスタム文字マッピングを定義された順序で適用します(例:ハムザバリアントをアラビア語のヤより前に正規化)。
  3. ストレージと検索で正規化ポリシーを分離します。
    • 正規のストレージには保守的なプロファイルを使用します(ZWNJを保持し、意味的なシフトを回避します)。
    • 検索には寛容なプロファイルを使用します(ZWNJを無視し、ハムザバリアントを統一し、数字を正規化します)。
  4. すべてのマッピングは、データベースの中央マッピングテーブルまたはアプリケーションのRubyハッシュを介して設定可能である必要があります。

:one: 正規化プロファイル

:green_circle: 保守的(ストレージ用)

  • 最小限の変換
  • NFKCを適用
  • アラビア語のKaf/Yaをペルシャ語の同等物に正規化
  • 発音記号を削除
  • ZWNJを保持
  • original_text + normalized_conservativeとして保存

:blue_circle: 寛容(検索用)

  • 積極的なマッチング
  • すべての保守的なルールを適用
  • ZWNJを削除/無視
  • ハムザバリアントを基本文字に正規化
  • すべての数字をASCIIに変換
  • オプションでTaa Marbuta → Hehを統一
  • クエリの前処理に使用

:two: 包括的なマッピングテーブル

ソース ターゲット Unicode 注釈
ك ک U+0643 → U+06A9 アラビア語のKaf → ペルシャ語のKaf
ي ی U+064A → U+06CC アラビア語のYa → ペルシャ語のYa
ى ی U+0649 → U+06CC 末尾のYaバリアント
أ, إ, ٱ ا Various → U+0627 ハムザ形式 → Alef
ؤ و U+0624 → U+0648 ハムザWaw
ئ ی U+0626 → U+06CC ハムザYa
ء U+0621 削除または保持(設定可能)
ة ه U+0629 → U+0647 Taa Marbuta → Heh(オプション)
ۀ هٔ U+06C0 ↔ U+0647+U+0654 複合形式を正規化
ڭ گ U+06AD → U+06AF 地域バリアント
U+200C ZWNJ:保守的では保持、寛容では削除
٤, ۴ 4 U+0664, U+06F4 → ASCII 数字を正規化
発音記号 U+064B–U+0652 すべてのharakatを削除
ZWJ U+200D 可視化されない結合文字を削除
複数スペース 単一スペース スペーシングを正規化

:three: 高速マッピングスニペット(SQLまたはRuby用)

ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (寛容では削除)
Harakat (U+064B..U+0652) → 削除
ZWJ (U+200D) → 削除

:four: PostgreSQLでの実装

  • text_normalization_mapテーブルを作成します。
  • パフォーマンスのためにregexp_replaceまたはTRANSLATEチェーンを使用します。
  • オプションでPL/PythonまたはPL/v8でUnicodeサポートを実装します。
  • 同じロジックを使用して、保存されたコンテンツと受信したクエリの両方を正規化します。

インデックス戦略

  • 正規インデックス用にnormalized_conservativeを保存します。
  • normalize_persian_arabic(query, 'permissive')でクエリを正規化します。
  • 寛容な検索を使用する場合、インデックスは同じプロファイルと一致する必要があります。
  • オプションで、クロス比較のために両方のバージョンを保存します。

:five: Rubyハッシュの例(Discourse用)

NORMALIZATION_MAP = {
  "ك" => "ک",
  "ي" => "ی",
  "ى" => "ی",
  "أ" => "ا",
  "إ" => "ا",
  "ٱ" => "ا",
  "ؤ" => "و",
  "ئ" => "ی",
  "ة" => "ه",
  "ۀ" => "هٔ",
  "۴" => "4",
  "٤" => "4",
  "\u200C" => "", # ZWNJ
  "\u200D" => "", # ZWJ
}

:six: パフォーマンスと実用的な注意点

  1. アプリケーションレイヤーでNFKCを適用します(例:Ruby unicode_normalize(:nfkc))。
  2. 保守的プロファイルと寛容プロファイルで別々のインデックスを使用します。
  3. 特に設定されていない限り、意味的に重要な文字(例:ハムザ、Taa Marbuta)の強制的なマッピングは避けます。
  4. 実際のフォーラムデータでA/Bテストを実行して、ヒット率と偽陽性を測定します。
  5. 各マッピングを理由と例で文書化します。
  6. RubyとSQLの両方で各マッピングの単体テストを定義します。

:seven: 最終推奨事項

  • ベースとしてUnicode NFKCを使用します。
  • カスタムマッピングレイヤーで拡張します。
  • ストレージと検索でデュアルプロファイルを維持します。
  • アプリケーションとデータベースレイヤーの両方に正規化を実装します。
  • すべてのマッピングを文書化し、テストします。
  • 適切なインデックス(GIN + to_tsvector)を正規化された列に構築します。