7.2. Mroongaにおける検索スコア

7.2.1. IN NATURAL LANGUAGE MODEにおける検索スコアの算出方法

IN NATURAL LANGUAGE MODEでの検索スコアはクエリと文書の類似度になります。スコアは以下の手順に従って算出されます。:

  1. クエリをトークンに分割する
  2. マッチしないトークンを除去する
  3. トークン毎の重みを計算する
  4. 重みが大きい上位N個のトークンを取り出す
  5. 上位N個のトークンについて、トークンが出現する文書に重みを足し、すべての重みを足したものがクエリと文書の類似度となる

具体例を使って説明します。以下のテーブルとデータを用意します。:

SET NAMES UTF8;
CREATE TABLE diaries (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  content TEXT,
  FULLTEXT INDEX(content)
) ENGINE mroonga DEFAULT CHARSET UTF8;

INSERT INTO diaries (content) VALUES("It'll be fine tomorrow as well.");
INSERT INTO diaries (content) VALUES("It'll rain tomorrow.");
INSERT INTO diaries (content) VALUES("It's fine today. It'll be fine tomorrow as well.");
INSERT INTO diaries (content) VALUES("It's fine today. But it'll rain tomorrow.");

このとき、以下のように「fine today」で類似文書検索したとします。検索の結果は以下となります。:

mysql> SELECT *, MATCH (content) AGAINST ("fine today") AS score
    ->        FROM diaries
    ->        WHERE MATCH (content) AGAINST ("fine today")
    ->        ORDER BY MATCH (content) AGAINST ("fine today") DESC;
+----+--------------------------------------------------+--------+
| id | content                                          | score  |
+----+--------------------------------------------------+--------+
|  3 | It's fine today. It'll be fine tomorrow as well. | 131073 |
|  4 | It's fine today. But it'll rain tomorrow.        | 131073 |
+----+--------------------------------------------------+--------+
2 rows in set (0.01 sec)

このときのスコア"131073"の算出の具体的な挙動は以下となります。

7.2.1.1. クエリをトークンに分割する

クエリ「fine today」をトークンに分割します。デフォルトトークナイザーは以下の二つのトークンにします。

  • fine
  • today

7.2.1.2. マッチしないトークンを除去する

今回はすべてのトークンがどれかの文書に含まれているのでここでは取り除かれません。

  • fine: 文書id = 1,3,4
  • today: 文書id = 3,4

7.2.1.3. トークン毎の重みを計算する

  • fine: 116508(= 1048576 / 9)
  • today: 131072(= 1048576 / 8)

(= ...)が重みの計算式です。 "1048576"(= 2 ** 20)は文書全体に含まれるトークン数を表しています。 本当はテーブルなどから情報を取得してくるなどして正しい値を使いたいところですが、今のところ固定値になっています。

"1048576 / 8"の分母の"8"はトークン「today」を含む文書数になります。

"1048576 / 9"の分母の"9"はトークン「fine」を含む文書数になります。

"8"と"9"はトークンを含む文書がいくつあるかの概算値です。本当は「today」を含む文書数は"2",「fine」を含む文書数は"3"ですが、概算値なのでずれています。

概算値は以下のように確認できます。

SELECT mroonga_command("select diaries-content --query '_key:fine OR _key:today' --output_columns _key, index --limit -1") AS groonga_response;

The retrieval result of the above query is as follows:

[[[2],[["_key","ShortText"],["index","diaries"]],["FINE",9],["TODAY",8]]]

7.2.1.4. 重みが大きい上位N個のトークンを取り出す

Nは以下の式で求めています。N = マッチしたトークン数 / 8 + 1

今回は「マッチしたトークン数」が2なので「2 / 8 + 1 = 1」で N = 1です。

マッチしたトークンを重み順に並び替えると以下の通りです。

  • today: 131072(= 1048576 / 8)
  • fine: 116508(= 1048576 / 9)

このうち上位1件なのでtoday: 131072(= 1048576 / 8)だけが残ります。

7.2.1.5. 上位N個のトークンについて、トークンが出現する文書に重みを足す

  • today: 文書id = 3,4

Finally document id 3,4 are hit, the similarity score between query and document(id=3) is 131072 + 1 = 131073 ("1" is the number of occurrence of token "today" in document).

The similarity score between query and document(id=4) is the same as the score between query and document(id=3).