検索システムの作り方

# 検索システムとは？
本記事で紹介する検索システムは、テキスト検索システムのことです。

テキスト検索システムは、Google検索に代表されるような、キーワードを入力するとそのキーワードに関連するアイテム（Google検索の場合はWebページ）を検索することができるシステムです。

本記事では、 テキスト検索の基本的な実装方法や、**[HottyDB](https://www.hottydb.com/)** を用いてそれを実装する方法などを紹介したいと思います。

# HottyDBとは？
https://www.hottydb.com/

HottyDBとは、検索エンジンとレコメンドエンジンの機能を搭載したRDBMS（リレーショナルデータベース）です。 
SQLを用いた通常のデータ操作に加え、文書の全文検索や検索結果の機械学習ランキング（MLR）・アイテムレコメンデーションのロジックなどを全て1つのソフトウェアで担います。
 1つのソフトウェアでこれらを実現することで、面倒なデータ伝送システムの構築を一切不要にし、利用者は機械学習などの知識がなくてもSQL LIKEな命令だけでこれらを実現することができます。


## HottyDBのマネージドAPIサービス
HottyDBのマネージドAPIサービスを近日リリースする予定です。興味がある方は下記のリンクからウェイトリストへの登録をお願いします。

https://www.hottydb.com/lp/lp1.html?utm_source=qiita&utm_medium=web&utm_campaign=922f69a3cc06cdf86c18



# 検索システムの作り方

本記事では、まず最初に通常のSQL文（LIKE句）でテキスト検索を実現する方法を解説し、その後、その問題点とともにHottyDBを利用した全文検索や機械学習ランキング（ランキング学習）について解説していきたいと思います。

## 本記事の構成
1. 【準備】テーブルの作成
2. LIKE句によるテキスト検索
3. 転置インデックスを用いた全文検索機能
4. 検索結果のランキングを機械学習で最適化
5. 導入事例（HottyDBを利用した検索サービスの紹介）

## 1. 【準備】テーブルの作成
最初に、本記事で検索対象テーブルとして用いる「記事テーブル（`article`）」を下記コマンドで作成します。

```
CREATE TABLE article (
  id INT,
  title VARCHAR(256)
  likes_count INT
)
```
`id` と `title`は説明不要だと思います。
`likes_count` は記事のいいね数を表します。
機械学習によるランキング最適化で並び順を最適化する際にこちらは使います。

## 2. LIKE句によるテキスト検索
通常のSQL文でテキスト検索をする場合、LIKE句による文字列マッチを使うことになります。この節では、LIKE句を使ってテキスト検索をする方法を解説します。

### 単一キーワードでのテキスト検索
まず最初に、単一のキーワードでテキスト検索をする場合のSQLを解説します。
例えば、検索キーワード `python` を titleに含む記事を検索する場合のSQLは下記のようになります。

```
SELECT id, title
FROM article
WHERE title LIKE '%python%'
```

`%` はワイルドカードで任意の文字列にマッチするため、`title LIKE '%python%'` という条件は、titleの一部分に`python`という文字列があればその記事はマッチしたことを意味します。

`WHERE title = 'python'`（完全一致） の場合とは違い、テキスト検索はこういったテキストの部分一致を検索する技術と言っても良いでしょう。

### 複数キーワードでのテキスト検索
続いて、検索キーワードが複数になる場合のテキスト検索を解説します。
例えば `python 機械学習` という2つのキーワードの両方を含む記事を検索したい場合は、下記のようなSQLで検索可能です。

```
SELECT id, title
FROM article
WHERE title LIKE '%python%'
AND title LIKE '%機械学習%'
```

単一キーワードの場合と比較して、`AND title LIKE '%機械学習%'` の部分が増えています。
検索キーワードが3つ、4つと増えた場合も同じように条件を追加していくだけで実現可能です。


### LIKE句によるテキスト検索の問題点
LIKE句によるテキスト検索は単純ですが、いくつか問題点があります。

#### 問題点1. テーブルのフルスキャンとなる
LIKE句による文字列マッチでは、例え `title`フィールドにINDEXが貼られていたとしてもそれは使用されず、テーブルのフルスキャンをすることになります。
ですので、テーブルが大量のレコードを保持する場合などはテキスト検索に多大な時間がかかってしまいます。

#### 問題点2. 類似度計算ができない
2つ目の課題は検索キーワードとテキストの類似度計算ができない点です。
LIKE句による文字列マッチでは、マッチしたか否かの二値しかわかりません。

例えば、検索キーワードをより多く含むテキストをより高いスコアで並べ替えたいといった要求に応えることができません。

以降のテキスト検索機能では、これらの問題点を解消する方法を解説していきます。

## 3. 転置インデックスを用いた全文検索機能
### 転置インデックスとは？
転置インデックスとは、検索対象テキストをトークン（単語のようなもの）と呼ばれる短い文字列に分割し、そのトークンをキーとしてトークンを含むテキストIDのリスト（ポスティングリストと呼ぶ）を保持する方式です。

言葉だけだとわかりづらいので、具体例を見てみましょう。

| テキストID | テキスト |
| --- | --- |
| 1 | I am good |
| 2 | She is good |
| 3 | He is bad |

という3つの文書に転置インデックスを作成すると、

| トークン | ポスティングリスト |
| --- | --- |
| good | 1, 2 |
| is | 2, 3 |
| I | 1 |
| am | 1 |
| She | 2 |
| He | 3 |
| bad | 3 |

のようになります。
例えば、`good` は テキストID `1`にも`2`にも含まれるので、`good`のポスティングリストは`1, 2`となります。

ここで、`is good`を検索キーワードとしてテキスト検索する場合、
`is`のポスティングリストと、`good`のポスティングリストの両方に含まれるテキストID（つまり`2`）が検索結果として出力されます。

このように、転置インデックスを利用することで、テキスト検索時にテーブルのフルスキャンをすることなく高速に検索結果を返すことができるようになります。

また、ポスティングリストに

| トークン | ポスティングリスト |
| --- | --- |
| good | 1:3, 2:4 |

のように、テキスト内にそのトークンが出現した頻度（`1:3`はテキストID`1` には`good`が3回出現したの意味）も格納することで、類似度計算をすることも可能となります。


### HottyDBで転置インデックスを作るには？
記事テーブル`article`の`title`フィールドに`s1`という名前の転置インデックスを作成する場合、HottyDBでは下記のようなSQLコマンドを実行します。

```
CREATE SEARCH INDEX s1 ON article (title)
```

### HottyDBで転置インデックスを利用した全文検索を実行する
転置インデックスを作成したら、SELECT文で転置インデックスを利用したテキスト検索を実行することが可能です。
例えば、`title`に`機械学習`という文字列を含む記事をテキスト検索する場合、下記のようなSELECT文になります。

```
SELECT id, title, _similarity
FROM SEARCH(article, title, '機械学習') 
ORDER BY _similarity DESC
```

FROM句の後にあるSEARCHメソッドに注目してください。
SEARCH(`テーブル名`, `フィールド名`, `検索キーワード`) とすることで、転置インデックスを利用した検索結果を返すことができます。
このSEARCHメソッドの出力は、通常のテーブルと同じように扱うことができるのです！（他テーブルとJOINすることも可能だし、GROUP BYなどで集約することも可能！）

またここで、`_similarity`という見慣れないフィールドが突然出てきました。
これは、SEARCHメソッドが自動的に追加するフィールドです。
`_similarity`は検索キーワードと該当レコードとの類似度を表すスコアになっています。

`ORDER BY _similarity DESC` とすることで、類似度の高い順に検索結果を並べることが可能です。

:::note info
類似度計算はTF-IDFによるコサイン類似度で計算しています。
:::

### 単純な全文検索の問題点

転置インデックスを利用したテキスト検索では、フルスキャンを回避することができ、類似度計算もできるようになりました。
しかし、単純に類似度順に検索結果を並べることは必ずしも最適なランキングとは言えません。例えば、`article`テーブルの例では類似度だけでなく、いいね数`likes_count`も考慮に入れたランキングにしたくなりますが、単純な方法ではそれができません。

高度な検索システムでは、検索結果のランキングを類似度以外の指標も組み合わせて機械学習によりランキングを最適化しています。

次の節では、機械学習を用いて検索結果のランキングを最適化する方法を解説します。


## 4. 検索結果のランキングを機械学習で最適化
検索結果のランキングを機械学習により最適化することを **機械学習ランキング**（**MLR**) やランキング学習と呼びます。
HottyDB でこの機械学習ランキング(MLR)をする場合、以下の4つの手順を実行する必要があります。少し手順が多いですが、通常の機械学習システムの学習パイプラインを構築する手間と比べればとても簡単です。

1. （準備1）MLRテンプレートの作成
2. （準備2）MLRモデルの作成
3. （推論）機械学習ランキングの推論（並び替え）
4. （学習）機械学習ランキングの学習

もちろん準備の手順は初回に一度実行すればよく、通常時は推論と学習を繰り返し実行するだけです。
先に推論を行うのは、推論結果を学習に使う必要があるからです。

ここでの例では、「検索時の類似度」と「記事のいいね数」を特徴量とし、クリックされたレコードを正解とする機械学習ランキングを行う手順を紹介していきます。

### 4-1. （準備1）MLRテンプレートの作成
最初の準備はMLRテンプレートと呼ばれるSELECT文のテンプレートを作成することです。
MLRテンプレートとは、機械学習ランキングを行う対象のテーブルデータを生成するテンプレートのことです。

言葉だけだとわかりづらいと思うので、実際にMLRテンプレートを作成するコマンドを見てみましょう。

```
CREATE MLR_TEMPLATE t1 
KEY(id)
SELECT id, title, likes_count, _similarity
FROM SEARCH(article, title, ?)
```

1行目では、`t1`という名前のMLRテンプレートの作成を宣言しています。

2行目は、3行目以降のSELECT文の出力フィールドのうち、レコードのIDとなるものをKEYとして指定しています。
これは機械学習ランキングの学習フェーズで正解データ（Clickを正解とする）を指定するときに重要となります。

3行目以降は機械学習ランキングで並び替えをしたいSELECT文を指定します。
このSELECT文では、以下のフィールドを含める必要があります。
- 機械学習ランキングの特徴量となるフィールド（例の場合`likes_count`と`_similarity`）
- 検索結果のレスポンスで使うフィールド（例の場合`id`, `title`, `likes_count`）
- レコードのIDとなり、2行目のKEYで指定しているフィールド（例の場合`id`）

また、4行目のSEARCHメソッド内の `?`に注目してみましょう。
`?`はプレースホルダーを意味し、テンプレート作成時点では値を決められない定数（この場合検索キーワード）を仮置きすることができます。
 MLR**テンプレート**という名前の謂れはここから来ています。
プレースホルダーは何個でも設定することができ、検索キーワード以外にもWHERE句の条件を仮置きすることなども可能です。
このプレースホルダーは、のちの機械学習ランキングの推論時に挿入することになります。



### 4-2. （準備2）MLRモデルの作成
続いて、先ほど作成したMLRテンプレート`t1`を指定し、MLRモデル`m1`を作成します。
MLRモデルでは、MLRテンプレートの出力フィールドのうち、特徴量として使うフィールド名を指定します（この場合、`likes_count`と`_similarity`）。

```
CREATE MLR_MODEL m1 WITH t1 (likes_count, _similarity)
```

:::note info
文字列型のフィールドを指定した場合は、自動的にカテゴリ型の特徴量として扱われます。
:::

準備は以上で完了です。それでは実際に機械学習ランキングの推論と学習を行うフェーズを見てみましょう。

### 4-3. （推論）機械学習ランキングの推論（並び替え）
機械学習ランキングの学習は、推論結果を利用して実行するので先に推論フェーズの手順を解説します。
機械学習ランキングにおける推論とは、機械学習により最適化されたランキングでレコードを並び替えることを意味します。

推論は、下記のようなSELECT文を実行することで行われます（検索キーワードを`機械学習`としたテキスト検索）。

```
SELECT id, title, likes_count, _similarity, 
  _request_id, _key_id, _score
FROM MLR(m1, '機械学習')(30, 0)
```

3行目のFROM句から見ていきましょう。見慣れない`MLR`というメソッドが登場しています。
このMLRメソッドを実行することで、機械学習ランキングの推論が実行されます。
1つ目の引数`m1`では、準備2で作成したMLRモデル名を指定しています。
2つ目の引数は、準備1で作成したMLRテンプレートのプレースホルダーに定数をセットするものです。準備1では、検索キーワードの部分をプレースホルダーにしていたので、そこに`機械学習`という値をセットしていることになります。
プレースホルダーを複数記述していた場合には、`MLR(m1, '機械学習', 'xxx', 'yyy', ...)`のように、複数セットすることができます。
MLRメソッドの次のカッコに`(30, 0)` というものがありますが、これは(LIMIT, OFFSET)を意味しています。
LIMITは検索結果を何件取得するか、OFFSETは何件目から取得開始するか？という意味です。

つまり、MLRメソッドの出力は、MLRテンプレートのプレースホルダーに値をセットした状態でSELECT文を実行し、それを準備2で作成したMLRモデルで並び替えたもの、ということになります。

続いて1,2行目の出力フィールドについて見ていきましょう。
1行目は`article`テーブルが持つフィールドとSEARCHメソッドの類似度なので既に説明済みです。
問題は2行目です。3つの新しいフィールドが出てきていますので、1つずつ説明していきます。

- `_request_id`: 検索リクエストを一意に特定するIDで、学習データを送信する際に利用します。
- `_key_id`: 検索結果内でアイテムを一意に特定するIDで、学習データを送信する際に利用します。MLRテンプレートでKEYに指定したフィールドの値と同じ値が返ります。
- `_score`: アイテムの機械学習ランキングにおけるスコアを返します。

以上で機械学習ランキングの推論周りの説明は終わりです。
では最後に、その検索結果を表示し、クリックされたレコードを学習させるフェーズについて説明していきます。

### 4-4. （学習）機械学習ランキングの学習
機械学習ランキングの学習では、検索結果一覧の中でクリックされたアイテムとそうでないアイテムの違いを学習させる必要があります。 
検索結果一覧自体は`_request_id`をキーとしてHottyDBの中に記憶させていますが、どの検索結果アイテムがクリックされたかは別途HottyDB側に伝えて上げる必要があります。

そのためのコマンドがINSERT MLR_POSITIVE命令です。

_request_id=`1`の検索リクエストにおいて、article.id=`4` がクリックされた場合のコマンドは次のようになります。

```
INSERT MLR_POSITIVE(m1, 1, 4)
```

1つ目の引数は準備2で作成したMLRモデルのモデル名です。
2つ目の引数は、推論時に取得した `_request_id`の値を指定します。どの検索リクエストにおいての学習なのかを指定するためです。
3つ目の引数は、クリックされた検索結果の `_key_id`の値を指定します。どの検索結果アイテムがクリックされたのかをHottyDBに伝えます。

以上で、HottyDBにおいて機械学習ランキングを実行する手順の解説は終わりです。
機械学習ランキングの手順は少々複雑で、わかりづらいところもあったかと思いますが、それでも通常の機械学習システムを構築するのと比べれば格段に簡単になっていると思います。

それでは、最後にこのHottyDBの機械学習ランキング機能を使った導入事例を紹介して、この記事は終わりにしようと思います。

## 5. 導入事例（HottyDBを利用した検索サービスの紹介）
### Hotty技術書検索
https://book.hottydb.com/

#### Hotty技術書検索とは？
[Hotty技術書検索](https://book.hottydb.com/)は、技術書の検索とレコメンデーションのサービスです。技術書のキーワード検索や関連書籍のレコメンデーションの機能をHottyDBにより実現しています。

#### 主な機能
「Hotty技術書検索の主な機能は次の3つです。

1. キーワード検索機能（テキスト検索＋機械学習ランキング）
2. 関連書籍のレコメンデーション機能（類似書籍検索）
3. 「あなたにおすすめの書籍」機能

キーワード検索機能を使うと今回紹介したテキスト検索や機械学習ランキングの機能を体感することができます。
Hotty技術書検索では、機械学習ランキングの特徴量として、書籍の価格, レビュー数, レビュー平均点, 発売年, クエリとの類似度)を利用しています。

#### Hotty技術書検索の作り方
下記の記事でHottyDBとSpringBootを使ってHotty技術書検索を作る方法をかなり具体的に解説していますので、よろしければこちらも見てみてください。

https://qiita.com/toru1055/items/a8444f128e880d8eaa6e




# さいごに
だいぶ長くなってしまいましたが、以上で本記事は終わりです。
この記事では、テキスト検索システムの作り方をLIKE句から始まり、転置インデックスを利用した全文検索、機械学習ランキング（ランキング学習）の解説をしてきました。
また、転置インデックスを利用した全文検索や機械学習ランキングの実装は、HottyDBを利用することで簡単に実装できることも紹介しました。

是非皆様もHottyDBを活用していただき、忌憚ないフィードバックをいただけると幸いです。

（フィードバックは[こちら](https://www.hottydb.com/other/contact/)まで）

## HottyDBのマネージドサービスを近日リリース予定
HottyDBのマネージドAPIサービスを近日リリースする予定です。興味がある方は下記のリンクからウェイトリストへの登録をお願いします。

https://www.hottydb.com/lp/lp2.html?utm_source=qiita&utm_medium=web&utm_campaign=922f69a3cc06cdf86c18