
第1回ではElasticSearchの簡単な概要について説明しました。
この記事では夏目漱石:「坊っちゃん」のElasticSearch Index(データベース)を作成して、検索できるようになるまでを解説します。
1. ElasticSearchでRAG用データベースを構築する意義
前回のおさらいになります。
RAGで用いる検索エンジンにはいくつか選択肢がありました:
検索エンジン | RAG用途での適合性 | 長所 | 短所 |
---|---|---|---|
ElasticSearch | ⭐⭐⭐⭐⭐ | 全文検索に最適化、豊富な言語解析、スケーラビリティ高 | リソース消費が大きい |
ベクトルDB (Faiss, Milvus) | ⭐⭐⭐⭐ | セマンティック検索に特化、高速 | テキスト検索機能が限定的 |
RDB + 全文検索拡張 | ⭐⭐⭐ | 既存システムとの統合が容易 | 高度な言語処理が限定的 |
ElasticSearchではRAGの検索に必要な機能は全て1つにまとまっているので環境構築が楽です。
2. ElasticSearchの環境構築
まずは環境構築を行いましょう。
(1) Docker と Docker Compose がインストールされていることを確認
docker --version
docker-compose --version
(2) プロジェクトディレクトリを作成
mkdir elasticsearch-demo
cd elasticsearch-demo
(3) dockerfile と docker-compose.yml ファイルを作成
以下の2つのファイルを作成し、同じフォルダに配置してください。
# dockerfile
FROM docker.elastic.co/elasticsearch/elasticsearch:8.18.0
RUN bin/elasticsearch-plugin install analysis-kuromoji
# docker-compose.yml
version: "3.8"
services:
elasticsearch:
build:
context: .
dockerfile: Dockerfile
ports:
- "9200:9200"
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es_data:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.18.0
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
ports:
- 5601:5601
volumes:
es_data:
driver: local
(4) Docker Compose でコンテナを起動
docker-compose up -d
上記のコマンドで、ElasticsearchとKibanaのコンテナが起動します。「-d」オプションでバックグラウンド実行します。
(5) 動作確認
起動から少し時間をおいてから、以下のコマンドでElasticsearchの状態を確認します:
curl http://localhost:9200
(6) 必要なPythonライブラリのインストール
今回は elasticsearch-dsl を用いて、pythonコードで完結したElasticSearchの環境構築を行います。
elasticsearch-dslは、PythonでElasticsearchの検索クエリの構築、インデックスの定義、マッピングの管理、ドキュメントの操作などを、より宣言的かつ直感的に行えるようにします。
以下のpythonライブラリをインストールしてください。
dependencies = [
"elasticsearch==8.11.0",
"elasticsearch-dsl==8.11.0",
]
3. 「坊っちゃん」テキストのインデックス化手順
早速 elasticsearch-dslを用いて、Indexを作成していきましょう。
以下のpythonスクリプトを実行してみてください:
from tqdm import tqdm
from elasticsearch_dsl import Document, Text, Integer, connections, analyzer, Index
from elasticsearch.helpers import bulk
INDEX_NAME = "botchan"
TEXT_DATA_PATH = "<path to botchan.txt>" # https://www.aozora.gr.jp/cards/000148/files/752_14964.html からテキストデータをダウンロードしてください
# Elasticsearchに接続
es = connections.create_connection(hosts=["http://localhost:9200"], timeout=20)
# 0. Analyzerの定義
ja_analyzer = analyzer(
"ja_analyzer",
tokenizer="kuromoji_tokenizer",
filter=["kuromoji_baseform", "kuromoji_part_of_speech", "ja_stop"],
)
# 1. Mappingの定義 (≒ スキーマの定義)
class BotchanLine(Document):
line = Text(analyzer=ja_analyzer)
line_number = Integer()
class Index:
name = INDEX_NAME
# 2. Indexの作成
if es.indices.exists(index=INDEX_NAME):
es.indices.delete(index=INDEX_NAME)
print(f"Index {INDEX_NAME} deleted.")
index = Index(INDEX_NAME)
index.settings(
number_of_shards=1,
number_of_replicas=0,
analysis={
"analyzer": {
"ja_analyzer": {
"type": "custom",
"tokenizer": "kuromoji_tokenizer",
"filter": ["kuromoji_baseform", "kuromoji_part_of_speech", "ja_stop"],
}
}
},
)
index.document(BotchanLine)
index.create()
# 3. 「坊っちゃん」のデータをドキュメントとして登録する
docs = []
with open(TEXT_DATA_PATH, "r") as f:
for i, line in tqdm(enumerate(f)):
line = line.strip()
if len(line) > 0:
doc = BotchanLine(meta={"id": i}, line=line, line_number=i)
docs.append(doc.to_dict(include_meta=True))
bulk(es, docs, index=INDEX_NAME)
順番に解説していきます。
3-1. 「0. Analyzerの定義」
Elasticsearchで日本語のテキストを効果的に検索するためには、適切なアナライザーを使用することが重要です。
日本語は英語と違い、単語の区切りが明確ではなく、活用形も複雑なため、特別な処理が必要になります。
アナライザーは以下の3つの処理を行います:入力テキスト → 文字フィルター → トークナイザー → トークンフィルター → インデックス登録/検索用トークン
- 文字フィルター: 特定の文字を置換または削除します(例:HTML特殊文字の処理)
- トークナイザー: テキストをトークン(単語)に分割します
- トークンフィルター: 分割されたトークンを加工します(例:小文字化、ストップワード除去)
ja_analyzer = analyzer(
"ja_analyzer",
tokenizer="kuromoji_tokenizer",
filter=["kuromoji_baseform", "kuromoji_part_of_speech", "ja_stop"],
)
ここでは、以下のようなkuromojiプラグインを導入しています:
kuromoji_tokenizer
: 日本語テキストを適切に分割するための形態素解析器です。「私は学校に行きました」を「私」「は」「学校」「に」「行き」「まし」「た」のように分解します。kuromoji_baseform
: 活用形を基本形に変換します。例えば「行きました」→「行く」のように変換し、異なる活用形でも同じ単語として検索できるようにします。kuromoji_part_of_speech
: 品詞情報を利用したフィルタリングができるようにします。ja_stop
: 「は」「が」「の」などの日本語のストップワード(検索に重要でない一般的な単語)を除外します。
analyzerやtokenizerの動作確認をしたい場合には、以下のようなcurlコマンドをターミナルで叩きます:
# analyzerの動作確認
curl -X GET "localhost:9200/botchan/_analyze" -H 'Content-Type: application/json' -d'
{
"analyzer": "ja_analyzer",
"text": "坊っちゃん、走れ!"
}'
# tokenizerの動作確認
curl -X GET "localhost:9200/botchan/_analyze" -H 'Content-Type: application/json' -d'
{
"analyzer": "kuromoji_tokenizer",
"text": "私は学校に行きました"
}'
`
3-2. 「Mappingの定義」
Mappingは、RDBでいうテーブル定義に相当します。各フィールドのデータ型や検索方法を指定します。
class BotchanLine(Document):
line = Text(analyzer=ja_analyzer)
line_number = Integer()
class Index:
name = INDEX_NAME
ここでは:
Document
クラスを継承して、「坊っちゃん」の各行を表すドキュメント型を定義しています。line
: テキスト本文を格納するフィールドで、先ほど定義した日本語アナライザーを使用します。line_number
: 行番号を整数型で格納します。class Index
: ドキュメントが属するインデックス名を指定します。
Elasticsearchはスキーマレスですが、明示的にマッピングを定義することで、データの取り扱い方を最適化できます。
3-3. 「Indexの作成」
Indexは、Elasticsearchのデータ構造の単位で、RDBのデータベースに相当します。
if es.indices.exists(index=INDEX_NAME):
es.indices.delete(index=INDEX_NAME)
print(f"Index {INDEX_NAME} deleted.")
index = Index(INDEX_NAME)
index.settings(
number_of_shards=1,
number_of_replicas=0,
analysis={
"analyzer": {
"ja_analyzer": {
"type": "custom",
"tokenizer": "kuromoji_tokenizer",
"filter": ["kuromoji_baseform", "kuromoji_part_of_speech", "ja_stop"],
}
}
},
)
index.document(BotchanLine)
index.create()
ここでは:
- 既存のインデックスがあれば削除します(再実行時の対応)。
number_of_shards=1
: シングルノード構成のため、シャード数を1に設定。number_of_replicas=0
: レプリカは作成しない設定。analysis
: アナライザーの設定を再度明示的に指定しています。これにより、インデックスレベルでアナライザーが正しく構成されます。index.document(BotchanLine)
: 先ほど定義したマッピングをインデックスに適用します。index.create()
: 実際にインデックスを作成します。
3-4 「坊っちゃんのデータをドキュメントとして登録」
最後に、テキストファイルからデータを読み込み、Elasticsearchにドキュメントとして登録します。
docs = []
with open(TEXT_DATA_PATH, "r") as f:
for i, line in tqdm(enumerate(f)):
line = line.strip()
if len(line) > 0:
doc = BotchanLine(meta={"id": i}, line=line, line_number=i)
docs.append(doc.to_dict(include_meta=True))
bulk(es, docs, index=INDEX_NAME)
ここでは:
- テキストファイルを1行ずつ読み込みます。
- 各行に対して
BotchanLine
オブジェクトを作成し、IDとして行番号を設定します。 docs
リストに全ドキュメントを格納した後、bulk
関数を使って一括登録します。
一括登録(bulk)を使用することで、1件ずつ登録するよりも大幅にインデックス作成のパフォーマンスが向上します。処理の進捗状況はtqdm
ライブラリで可視化しています。
3-5 検索の動作確認
最後に、「坊っちゃん」というキーワードで簡単な検索を実行して、動作確認してみます:
curl -X GET "http://localhost:9200/botchan/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"match": {
"line": "坊っちゃん"
}
}
}'
検索結果が返ってきたらIndexの作成に成功しています🎉
おわりに
お疲れ様でした。
今回は「坊っちゃん」を例にして、ElasticSearchを使ったRAG用データベースの構築方法を解説しました。
適切なインデックス設計、特に日本語のテキスト解析設定が重要なポイントです。
次回はElasticSearchでの複雑なクエリを用いた検索方法を紹介していきます。それでは!

コメント