Search it.

主に検索についてあれやこれや書いていくブログです。

Elasticsearchを理解するためにLuceneを使ってみる

背景

もっとElasticsearchを理解できるように、基本から復習していきます。

(いつまで続くわからないけど)第1回はLuceneを使ってみるです。

ElasticsearchはApache Luceneがベースになっているので、Elasticsearchを基本から復習・理解していくには必然的にLuceneのことを理解する必要がありますが、まあまあ難しいのでまずはLuceneを使ってみるところから始めます。

免責

Elasticsearchについて調べたことをまとめています。また、JAVAは初心者です。

おそらく間違えていることがあると思うので、間違いに気づいたらご連絡いただけると嬉しいです。

環境

準備

luceneを追加する

Mavenを使います。

まずdependencyluceneを追加します。追加するバージョンはともに9.4.1です。

追加の仕方、注意点は以下を参照。

ryook.hatenablog.jp

lucene-in-5-minutesをやってみる

ぐぐったらチュートリアルを見つけたので、本のタイトルとisbnコード(両方文字列)を検索するサンプルを試します。

www.lucenetutorial.com

コード

package tutorial;  
  
import org.apache.lucene.analysis.standard.StandardAnalyzer;  
import org.apache.lucene.document.Document;  
import org.apache.lucene.document.Field;  
import org.apache.lucene.document.StringField;  
import org.apache.lucene.document.TextField;  
import org.apache.lucene.index.DirectoryReader;  
import org.apache.lucene.index.IndexReader;  
import org.apache.lucene.index.IndexWriter;  
import org.apache.lucene.index.IndexWriterConfig;  
import org.apache.lucene.queryparser.classic.ParseException;  
import org.apache.lucene.queryparser.classic.QueryParser;  
import org.apache.lucene.search.IndexSearcher;  
import org.apache.lucene.search.Query;  
import org.apache.lucene.search.ScoreDoc;  
import org.apache.lucene.search.TopDocs;  
import org.apache.lucene.store.ByteBuffersDirectory;  
import org.apache.lucene.store.Directory;  
  
  
import java.io.IOException;  
  
public class Main {  
    public static void main(String[] args) throws IOException, ParseException {  
  
        // index の作成
        StandardAnalyzer analyzer = new StandardAnalyzer();  
  
        Directory directory = new ByteBuffersDirectory();  
        IndexWriterConfig config = new IndexWriterConfig((analyzer));  
  
        // ドキュメントの追加
        IndexWriter w = new IndexWriter(directory, config);  
        addDoc(w, "Lucene in Action", "193398817");  
        addDoc(w, "Lucene for Dummies", "55320055Z");  
        addDoc(w, "Managing Gigabytes", "55063554A");  
        addDoc(w, "The Art of Computer Science", "9900333X");  
        w.close();  
  
        // 検索
        // query  の作成
        String queryString = args.length > 0 ? args[0]: "lucene";  
  
        Query q = new QueryParser("title", analyzer).parse(queryString);  
  
        // 検索実行  
        int hitsPerPage = 10;  
        IndexReader reader = DirectoryReader.open(directory);  
        IndexSearcher searcher = new IndexSearcher(reader);  
        TopDocs docs = searcher.search(q, hitsPerPage);  
        ScoreDoc[] hits = docs.scoreDocs;  
  
        // 結果の表示
        System.out.println("Found " + hits.length + " hits.");  
        for (int i=0;i<hits.length;i++){  
            int docId = hits[i].doc;  
            Document d = searcher.doc(docId);  
            System.out.println((i+1) + "." + d.get("isbn") + "\t" + d.get("title"));  
        }  
  
        reader.close();  
    }  
  
    private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {  
        Document doc = new Document();  
        doc.add(new TextField("title", title, Field.Store.YES));  
        doc.add(new StringField("isbn", isbn, Field.Store.YES));  
        w.addDocument(doc);  
    }  
}

index作成

インデックスは、ByteBuffersDirectoryを使って、メモリのヒープ領域に作成します。

ドキュメントの追加

analyzerはStandardAnalyzerを使っています。これは、 StandardTokenizer と LowerCaseFilter と StopFilterが使われます。 雑にいうと、英文の場合は空白で区切られ、すべて小文字として扱われます。

ドキュメントはDocumentに対して、Fieldインスタンスを追加します。各フィールドのフィールドタイプ、フィールド名、値、オプションはFieldクラスで管理しているようです。

まだ中身までちゃんと追えてませんが、文字列処理やindex(luceneでは正確にはsegment)への書き込みはIndexWriterクラスが行います。

queryの作成

当初Mavenlucene-coreだけ追加していたのですが、QueryParserが存在せずに少しハマりました。 QueryParserはlucene-coreではなくて、lucene-queryparser moduleなので忘れずに追加します。

検索実行

検索の流れとしては以下のようになっています。

  1. DirectoryReaderクラスのopenメソッドで、対象とするDirectoryインスタンスを指定
  2. IndexReaderインスタンスを指定して、IndexSearcherクラスのインスタンスを作成
  3. IndexSearcherクラスのsearchメソッドでQueryインスタンスを渡す

実行結果

Found 2 hits.
1.193398817 Lucene in Action
2.55320055Z Lucene for Dummies

検索できた!

indexをファイルに永続化する

String indexDirectoryPath = "./index/";  
Directory directory = FSDirectory.open(Paths.get(indexDirectoryPath));

directoryをByteBuffersDirectoryからFSDirectoryに変更します。

これを実行すると作成したインデックスが永続化されています。pathで指定したindexディレクトリができていて中にファイルが作られています。

中身に関しては以下のブログを参照してください。(自分もまだ理解してない...) https://www.m3tech.blog/entry/2021/08/24/162205

index
├── _0.cfe
├── _0.cfs
├── _0.si
├── segments_1
└── write.lock

(おまけ)インデックスの設定

ここでは細かい説明は省きますが、luceneのインデックスにドキュメントを追加する際にはいくつかのステップをふみます。

ざっくり書くと

  • メモリ上のバッファに追加する
  • バッファがいっぱいになると新しいセグメントをつくる
  • セグメントが一定数になればマージする といったステップがあります。

これらの設定はIndexWriterConfigでやるようです。

IndexWriterConfig config = new IndexWriterConfig(analyzer);

LogMergePolicy policy = new LogDocMergePolicy();  
policy.setMergeFactor(10);  
config.setMergePolicy(policy);  
config.setMaxBufferedDocs(100);  
config.setRAMBufferSizeMB(256.0);

Elasticsearchの設定はここで使われているんだろうなという予想です。

感想

Elasticsearch的にあの処理はこれを使っているのか〜というのがなんとなく見えてきました。 具体的にどういう実装になってるんだろう?というのは引き続き確認していきます。

参考

po3rin.com

IntelliJ IDEAでmavenプロジェクトに依存ライブラリが import できないときの対処法

背景

タイトルどおりIntelliJ IDEAでmavenプロジェクト開発中に追加した依存ライブラリがimportできなかった。

とても単純な問題だったけど、はじめてのmavenでハマったので解決方法めも。

環境

現象

依存ライブラリを追加したのにライブラリをimportするとCannot resolve symbol 'library name'になった。

pom.xml

<dependencies>  
    <dependency>
        <groupId>org.apache.lucene</groupId>  
        <artifactId>lucene-core</artifactId>  
        <version>9.4.1</version>  
    </dependency>
</dependencies>

java

import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  
public class main {  
  
}

probrem

Cannot resolve symbol 'lucene'

そもそもの依存ライブラリ追加方法

  1. pom.xmlを開く
  2. ⌘ N を押して、Add Dependecy... クリック
  3. 追加したいライブラリ名を検索して追加

pleiades.io

解決方法

project reloadを行う。

手順 1. pom.xml 右クリック 2. maven > project reloadをクリック

解決。

情報検索 :検索エンジンの実装と評価 15章の「Web検索」

「情報検索:検索エンジンの実装と評価」(Buttcher本) Advent Calendar2020の24日目の記事です。

adventar.org

情報検索 :検索エンジンの実装と評価 15章の「Web検索」をまとめます。

15章は広範なトピックについて扱っているため全体的にかいつまんでまとめていきます。 この章ではWeb検索におけるIRシステムについて取り扱います。

クエリについて

webで使われる検索クエリは短く、多くが1,2タームで平均クエリ長が2,3タームであると言われています。これらのクエリは多様で1000万クエリのうち半数近くは1度しか出現しないロングテールとなっています。

したがって、検索エンジンのチューニングと評価ではロングテールを考慮した評価が重要です。

この多様なクエリからユーザの意図を明らかにする研究もされていて、webクエリを以下の3つに分類しています。

  • 案内型クエリ(navigational query)
  • 情報型クエリ(informational query)
  • 取引型クエリ(tranctinal query)

案内型クエリ

案内型クエリは、web上の特定のページやサイトを検索するクエリです。 たとえば、twitterを開きたい場合にURLの入力やブックマークを使わずに”twitter"と検索します。 案内型クエリは基本的にクエリに対する正解は一つです。 しかし、実際にユーザーが求めていたのは調べたサービスの別言語版であったり、サービス内の特定のページであったりする可能性があります。

情報型クエリ

情報型クエリは、特定のトピックについて何かを知ることを目的とした検索の際に用いられます。 たとえば、"サウナ 東京"のようなクエリです。この本を始め検索エンジンについて解析されるときに使われるクエリは多くが情報型クエリを意図しています。 情報型クエリは検索といったときにイメージされやすいクエリではありますが、その背後にあるニースはユーザーにより異なることがあります。 先程の"サウナ 東京"の場合、東京のおすすめのサウナを知りたいのか、今現在空いている東京のサウナが知りたいのか、東京にある特定のサウナの情報を知りたかったが名前が思いだせなかったのか、といった意図まではわかりません。

取引型クエリ

取引型クエリは、商品購入、旅行予約などwebサービスを見つけたあとにクエリの内容を実行することを意図しています。 研究によると全検索クエリの約20%が取引型クエリに該当します。

一つのクエリが複数に分類されることがあったり、同じクエリでもユーザーによって意図が違ったりしますが、 重要なことは、ユーザの意図の違いを認識することは一般的な検索エンジン以上にweb検索では重視されるということです。

ランキング

WEBは構造として、ハイパーリンクによるページ同士のリンク構造を持ちます。 このハイパーリンクによるサイトとページの関係を抽象化したものがwebグラフです。 数学的なモデルとしてみると、ページはノード、各リンクはエッジとして表現されます。

このwebグラフをの構造情報を抽出してインデクシング時に計算する静的ランキングとして活用したアルゴリズムで有名なのがPageRankです。 PageRankは、ネットサーフィンするユーザーの行動をモデルとして、Google創設者のラリー・ペイジとセグレイ・ブリンによって発明されたアルゴリズムです。

このアルゴリズムpythonで実装するつもりでいましたが、普通に間に合わなかったので後日記事にします...

評価

Web検索でも、P@10やMAPなど従来のIR評価フレームワークを適用することはできます。

一般的な検索エンジンの評価方法については14日目のmohumohuさんの記事をご覧ください。

mofumofu1729.hatenablog.com

web検索では扱う情報の多いため別の評価方法を使うと効果的です。 そのひとつが暗黙的フィードバックという方法です。

暗黙的フィードバック

暗黙的フィードバックはユーザと検索エンジの相互作用として得られます。 情報は滞在時間やクリックスルーなど様々ありますが、クリックスルーは取得しやすく重要な指標であり事例として本書であつかわれています。

クリックスルーを使った暗黙的フィードバックは、特定のクエリに関して、ユーザのクリックスルーを組み合わせることで可能になります。 これをクリックスルー曲線と呼びます。一般的にクリックスルー曲線は上位、特に1位の結果がクリックされる確率が高いです。 これは、信頼バイアスと言われる、ユーザーが検索エンジンは関連性の高い結果を一位に返ってきているはずという期待行動によりおこります。 一方でクリックされた回数が1位ではなく下位の結果になっていることも起こりえます。 この現象をクリックスルー反転といい、このときのランキングは最適でない可能性があると判断できます。

まとめ

本章はweb検索について広範なトピックを扱う章でした。web検索を作っているという人はこの本の読者の中では数としては少ないかもしれませんが、普段我々が使うweb検索について概要を知るのによい機会でした。とはいえ、この章のメインはPageRankといっても過言ではないため早くPageRankの記事を書きます。