Elasticsearchがどんなものか全く知らなかったので、インストールして触ってみました。
準備
まずローカルに動作環境を構築します。
OS: Windows 11 Home 22H2
Engine: elasticsearch 8.10.2
Tool: kibana 8.10.2
Language: C#
以下のサイトからelasticsearchをダウンロードしてzipを適当なフォルダに解凍します。
https://www.elastic.co/jp/downloads/elasticsearch
また、以下のサイトからkibanaをダウンロードしてzipを適当なフォルダに解凍します。
kibanaはElasticsearchと連携してデータを探索、可視化、分析するツールだそうです。
https://www.elastic.co/jp/downloads/kibana
configファイルの設定は、ローカル環境で色々試すだけなのでelasticsearch.ymlのsecurityをfalseにしたのと、Elasticsearchの起動でOutOfMemoryが出たのでjvm.optionsをイジりました。
xpack.security.enabled: false
-Xms4g
-Xmx4g
また、コマンドプロンプトを立ち上げてelasticsearch-8.10.2\binに移動し、以下のコマンドを叩いてプラグインをインストールします。
elasticsearch-plugin install analysis-kuromoji
elasticsearch-plugin install analysis-icu
コマンドプロンプトを立ち上げてelasticsearch-8.10.2\binに移動し、Elasticsearchを起動させます。
elasticsearch.bat
ブラウザを起動させて以下のURLを叩き、諸情報が表示されればOKです。
http://localhost:9200/
Elasticsearchが起動が終わったら、別のコマンドプロンプトを立ち上げてkibana-8.10.2\binに移動し、kibanaを起動させます。
kibana.bat
ブラウザを起動させて以下のURLを叩き、ページが表示されればOKです。
http://localhost:5601/
NGramを試してみる
以下のC#プログラムを実行するとTokenizerにNGramを利用したインデックス設定が作成されます。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.10.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
</ItemGroup>
</Project>
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);
var isa = new IndexSettingsAnalysis();
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
MinGram = 1,
MaxGram = 10
});
// Tokenizers
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_ngram_tokenizer"
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
.Settings(s => s
.Index(
new IndexSettings()
{
Analysis = isa,
MaxNgramDiff = 10
})
)
);
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
kibanaのDev Toolsを叩いて先ほど作成したインデックス設定を確認します。
GET /my-index/_settings
settings配下にC#プログラムから作成したインデックス設定があることを確認できました。
続いてNGramのTokenizerの動きを確認します。先ほどC#プログラムを実行して作成したcustom_ngram_analyzerを使って、「ロキソプロフェン錠60mg」をトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。
GET /my-index/_analyze
{
"analyzer": "custom_ngram_analyzer",
"text": "ロキソプロフェン錠60mg"
}
MaxGram = 10でNGramTokenizerを作成しているので最大10文字でトークン化されていることが確認できます。
kuromojiを試してみる
今度は日本語に対応したkuromojiのTokenizerを使ってみます。以下のC#プログラムを実行するとcustom_kuromoji_analyzerが作成されます。
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);
var isa = new IndexSettingsAnalysis();
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
MinGram = 1,
MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
Mode = KuromojiTokenizationMode.Search,
DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_kuromoji_tokenizer",
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
.Settings(s => s
.Index(
new IndexSettings()
{
Analysis = isa,
MaxNgramDiff = 10
})
)
);
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。
GET /my-index/_analyze
{
"analyzer": "custom_kuromoji_analyzer",
"text": "ロキソプロフェン錠60mg"
}
kuromojiを使って読み仮名に変換してみる
続いてkuromojiのTokenFilterを使って読み仮名に変換します。以下のC#プログラムを実行するとcustom_kuromoji_yomi_analyzerが作成されます。
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);
var isa = new IndexSettingsAnalysis();
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
MinGram = 1,
MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
Mode = KuromojiTokenizationMode.Search,
DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_kuromoji_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_kuromoji_tokenizer",
Filter = new List<string>{ "kuromoji_readingform" }
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
.Settings(s => s
.Index(
new IndexSettings()
{
Analysis = isa,
MaxNgramDiff = 10
})
)
);
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_yomi_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。
GET /my-index/_analyze
{
"analyzer": "custom_kuromoji_yomi_analyzer",
"text": "ロキソプロフェン錠60mg"
}
不要な文字を削除してみる
CharFilterを使って不要な文字を削除してみます。
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);
var isa = new IndexSettingsAnalysis();
// Charfilters
isa.CharFilters = new CharFilters();
isa.CharFilters.Add("custom_char_filter", new PatternReplaceCharFilter()
{
Pattern = "[0-9]|mg",
Replacement = ""
});
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
MinGram = 1,
MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
Mode = KuromojiTokenizationMode.Search,
DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
Tokenizer = "custom_kuromoji_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_kuromoji_tokenizer",
Filter = new List<string>{ "kuromoji_readingform" }
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
.Settings(s => s
.Index(
new IndexSettings()
{
Analysis = isa,
MaxNgramDiff = 10
})
)
);
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_yomi_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。
GET /my-index/_analyze
{
"analyzer": "custom_kuromoji_yomi_analyzer",
"text": "ロキソプロフェン錠60mg"
}
全角の数字とmgが削除されていますね。
さらにEdgeNGramTokenFilterをかけます。
isa.TokenFilters.Add("custom_edge_ngram_filter", new EdgeNGramTokenFilter()
{
MinGram = 1,
MaxGram = 10
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_kuromoji_tokenizer",
Filter = new List<string>{ "kuromoji_readingform", "custom_edge_ngram_filter" }
});
先頭文字からのNGramでトークン化されます。
合わせてcustom_ngram_analyzerとcustom_kuromoji_analyzerにもCharFilterを設定しました。
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_ngram_tokenizer",
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_kuromoji_tokenizer",
});
インデックスを作成して検索してみる
ここまでで3つのAnalyzerを作成しました。最後にcustom_ngram_analyzerとcustom_kuromoji_yomi_analyzerを使って医薬品検索用のインデックスを作成してみましょう。
医薬品のマスターは以下のサイトから全件分ファイル(ZIP:898KB)をダウンロードします。
https://www.ssk.or.jp/smph/seikyushiharai/tensuhyo/kihonmasta/kihonmasta_04.html
以下のプログラムにて、custom_ngram_analyzerとcustom_kuromoji_yomi_analyzerをフィールドとマッピングさせてインデックスを作成します。
※CSVのパスはご自身の環境に合わせ書き換えください。
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);
var isa = new IndexSettingsAnalysis();
// Charfilters
isa.CharFilters = new CharFilters();
isa.CharFilters.Add("custom_char_filter", new PatternReplaceCharFilter()
{
Pattern = "[0-9]|mg",
Replacement = ""
});
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
MinGram = 1,
MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
Mode = KuromojiTokenizationMode.Search,
DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
isa.TokenFilters.Add("custom_edge_ngram_filter", new EdgeNGramTokenFilter()
{
MinGram = 1,
MaxGram = 10
});
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_ngram_tokenizer",
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_kuromoji_tokenizer",
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "custom_kuromoji_tokenizer",
Filter = new List<string>{ "kuromoji_readingform", "custom_edge_ngram_filter" }
});
isa.Analyzers.Add("keyword_search_analyzer", new CustomAnalyzer()
{
CharFilter = new List<string>{ "custom_char_filter" },
Tokenizer = "whitespace"
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
.Settings(s => s
.Index(
new IndexSettings()
{
Analysis = isa,
MaxNgramDiff = 10
})
)
.Mappings(map => map
.Properties(
new Properties<YakName>()
{
{ "yakNameNgram", new TextProperty()
{
Analyzer = "custom_ngram_analyzer",
SearchAnalyzer = "keyword_search_analyzer"
}
},
{ "yakNameKana", new TextProperty()
{
Analyzer = "custom_kuromoji_yomi_analyzer",
SearchAnalyzer = "keyword_search_analyzer"
}
}
}
)
)
);
using (StreamReader reader = new StreamReader(@"C:\es\y_ALL20230920.csv", Encoding.GetEncoding("Shift_JIS")))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
string[] values = line.Split(',');
var yak_name = values[4].Trim(new char[] { '"' });
if (String.IsNullOrEmpty(yak_name))
{
continue;
}
var yakName = new YakName
{
YakNameNgram = yak_name,
YakNameKana = yak_name
};
var indexRes = await client.IndexAsync(yakName, "my-index");
}
}
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
public class YakName
{
public string YakNameNgram { get; set; }
public string YakNameKana { get; set; }
}
数分でインデックスの作成が終わると思います。
終わったら以下のコマンドをkibanaのDev Toolsで叩いて、薬品を検索してみましょう。
GET /my-index/_search
{
"query": {
"bool": {
"should": [
{ "match": { "yakNameNgram": "恵美須" } }
]
}
},
"sort" : [{"_score":"desc"}]
}
GET /my-index/_search
{
"query": {
"bool": {
"should": [
{ "match": { "yakNameKana": "ショウサンギン" } }
]
}
},
"sort" : [{"_score":"desc"}]
}