タグ: Solr

[Solr]ChatGPTにスキーマを作らせてみた

Solrによる検索環境を構築するにあたって、スキーマの設定ファイルを作るところは意外と面倒です。
その部分を試しに ChatGTP に手伝ってもらいました。

以下の内容で依頼しました。

Apache Solr で以下の文書のインデックスを作成します。デフォルトのスキーマ設定にスキーマAPIを使って必要な設定を追加するための JSON ファイルを作成してください。
インデックス対象の文書は以下の形式の JSON ファイルで与えられます。
[{“id”:”10″,”type”:”官公庁”,”area”:”住之江区”,”name”:”軽自動車検査協会大阪主管事務所”,”address”:”住之江区南港東3-4-62″,”address_p”:”34.6164938333333,135.438210722222″},
{“id”:”11″,”type”:”官公庁”,”area”:”住之江区”,”name”:”大阪陸運支局なにわ自動車検査登録事務所”,”address”:”住之江区南港東3-1-14″,”address_p”:”34.6190439722222,135.442191833333″}]

インデックス対象は、いつもの大阪の施設情報です。具体例として与えたのは2レコード分だけですが、以下のように適切なスキーマを作ってくれました。

以下は、Apache Solr でこの JSON ファイルをインデックスするためのスキーマ定義の例です。この例は、id、type、area、name、address、address_p の6つのフィールドをインデックスすることを想定しています。

{
  "add-field": [
    {
      "name": "id",
      "type": "string",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    },
    {
      "name": "type",
      "type": "string",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    },
    {
      "name": "area",
      "type": "string",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    },
    {
      "name": "name",
      "type": "text_general",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    },
    {
      "name": "address",
      "type": "text_general",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    },
    {
      "name": "address_p",
      "type": "location_rpt",
      "indexed": true,
      "stored": true,
      "required": true,
      "multiValued": false
    }
  ]
}

このスキーマ定義によって、以下のようにインデックスを作成することができます。

  1. Apache Solrの管理画面にアクセスします。
  2. 左側のメニューから「コアの作成」をクリックして、新しいコアを作成します。
  3. 新しいコアの名前を入力し、「コアを作成する」をクリックします。
  4. 新しいコアの管理画面に移動し、「スキーマ」をクリックします。
  5. 「スキーマ」のページで、「スキーマAPI」をクリックし、「スキーマ定義の追加」をクリックします。
  6. 上記のスキーマ定義をコピーして、「スキーマ定義の追加」のテキストボックスに貼り付けます。
  7. 「送信」をクリックしてスキーマを追加します。
  8. 左側のメニューから「ドキュメント」をクリックして、新しいドキュメントを追加します。
  9. 新しいドキュメントのJSON形式のデータを入力し、「追加」をクリックしてインデックスを作成します。
  10. インデックスが作成されたことを確認するために、左側のメニューから「クエリ」をクリックして、クエリを実行して結果を確認します。

短い文字列には string、 長めの文字列には text_general、緯度経度には location_rpt とデータの内容に合わせてフィールドタイプを選んでくれているように見えます。 address_pという名前がヒントになったかもしれないので hoge にして試してみましたが、やはり location_rpt になりました。

このようにインデックス作成までの準備の部分を AI に手伝ってもらえると、いろいろと楽になりそうです。


[Solr]Admin UIでフィールドタイプの編集をできるようになった

Solr 9.1.0 から Admin UI でフィールドタイプの定義を編集ができるようになりました。
それ以前はフィールド定義の追加はできるもののフィールドタイプには手を付けることができない状態でした。

上のスナップショットは8.11.0のAdmin UIです。

9.1.0のAdmin UI。
“Manipulate Field Type” というボタンが増えています。このボタンを押すとフィールドタイプを追加するためのコマンドのテンプレートがテキストエリアとして表示されます。

プルダウンを”Delete FieldType Template”に変更したところ。
指定のフィールドタイプを削除するためのコマンドを編集できます。

プルダウンを”Replace FieldType Template”に変更したところ。
指定のフィールドタイプの定義を書き換えることができます。

従来から存在していたフィールドに対する操作が追加のみであったことを考えると、フィールドタイプに対する操作が一気に充実したことになります。

Solr 9.1.0 ではこれ以外にもAdmin UIへの機能追加がいくつかあったので、順次ご紹介していければと思います。


[Solr] Tagger Handler で日本語テキストにタグ付け

前回紹介した Tagger Handler を使って日本語のテキストタグ付けを試してみました。

まずコレクション(osaka_shisetsu_tagger)を作成します。

bin/solr create -c osaka_shisetsu_tagger

スキーマを API で設定します。
indexAnalyzer と queryAnalyzer の設定を日本語の形態素解析を使うものに変更しています。
Solr リファレンスに書かれている通り、 indexAnalyzer のフィルタチェーンの最後に ConcatenateGraphFilterFactory を指定するのがミソです。

curl -X POST -H 'Content-type:application/json'  http://localhost:8983/solr/osaka_shisetsu_tagger/schema -d '{
  "add-field-type":{
    "name":"tag",
    "class":"solr.TextField",
    "postingsFormat":"FST50",
    "omitNorms":true,
    "omitTermFreqAndPositions":true,
    "indexAnalyzer":{
      "tokenizer":{
         "class":"solr.JapaneseTokenizerFactory", "mode":"search" },
      "filters":[
        {"class":"solr.JapaneseBaseFormFilterFactory"},
        {"class":"solr.JapanesePartOfSpeechStopFilterFactory", "tags":"lang/stoptags_ja.txt"},
        {"class":"solr.CJKWidthFilterFactory"},
        {"class":"solr.StopFilterFactory", "ignoreCase":"true", "words":"lang/stopwords_ja.txt"},
        {"class":"solr.JapaneseKatakanaStemFilterFactory", "minimumLength":"4"},
        {"class":"solr.LowerCaseFilterFactory"},
        {"class":"solr.ConcatenateGraphFilterFactory", "preservePositionIncrements":false }
      ]},
    "queryAnalyzer":{
      "tokenizer":{
         "class":"solr.JapaneseTokenizerFactory", "mode":"search" },
      "filters":[
        {"class":"solr.JapaneseBaseFormFilterFactory"},
        {"class":"solr.JapanesePartOfSpeechStopFilterFactory", "tags":"lang/stoptags_ja.txt"},
        {"class":"solr.CJKWidthFilterFactory"},
        {"class":"solr.StopFilterFactory", "ignoreCase":"true", "words":"lang/stopwords_ja.txt"},
        {"class":"solr.JapaneseKatakanaStemFilterFactory", "minimumLength":"4"},
        {"class":"solr.LowerCaseFilterFactory"}
      ]}
    },

  "add-field":{"name":"name", "type":"text_ja"},
  "add-field":{"name":"name_tag", "type":"tag", "stored":false },
  "add-copy-field":{"source":"name", "dest":["name_tag"]}
}'

TaggerRequestHandler の設定は英語のときと同じ。

curl -X POST -H 'Content-type:application/json' http://localhost:8983/solr/osaka_shisetsu_tagger/config -d '{
  "add-requesthandler" : {
    "name": "/tag",
    "class":"solr.TaggerRequestHandler",
    "defaults":{"field":"name_tag"}
  }
}'

タグ用のデータとして、いつもの大阪の施設情報を利用します。
以下のようなタグ区切りのデータです。

$ head -n 3 /tmp/osaka_shisetsu20140106_noheader.txt 
158	34.6164938333333	135.438210722222	http://lodosaka.hozo.jp/class/施設情報	官公庁	国の機関	住之江区	軽自動車検査協会大阪主管事務所	軽自動車検査協会大阪主管事務所	住之江区南港東3-4-62	
157	34.6190439722222	135.442191833333	http://lodosaka.hozo.jp/class/施設情報	官公庁	国の機関	住之江区	大阪陸運支局なにわ自動車検査登録事務所	大阪陸運支局なにわ自動車検査登録事務所	住之江区南港東3-1-14	
381	34.6109641111111	135.491388722222	http://lodosaka.hozo.jp/class/施設情報	官公庁	国の機関	住吉区	住吉税務署	住吉税務署	住吉区住吉2丁目17番37号	http://www.nta.go.jp/osaka/guide/zeimusho/osaka/sumiyoshi/index.htm

各レコードの1番目(ID)、2番目(緯度)、3番目(経度)、4番目(URL)、8番目(施設名)、10番目(住所)をインデックスします。
タグとして利用するのは施設名です。

bin/post -c osaka_shisetsu_tagger -type text/csv \
  -params 'optimize=true&maxSegments=1&separator=%09&encapsulator=%00&fieldnames=id,latitude,longitude,url,,,,name,,address,' \
  /tmp/osaka_shisetsu20140106_noheader.txt

タグ付けしてみます。

$ curl -X POST   'http://localhost:8983/solr/osaka_shisetsu_tagger/tag?overlaps=NO_SUB&tagsLimit=5000&fl=id,name,address&wt=json&indent=on'   -H 'Content-Type:text/plain' -d '昨日は阪急梅田駅で降りて梅田市税事務所で手続きをしました'
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "tagsCount":2,
  "tags":[{
      "startOffset":3,
      "endOffset":8,
      "ids":["5421"]},
    {
      "startOffset":12,
      "endOffset":19,
      "ids":["11486"]}],
  "response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"11486",
        "name":"梅田市税事務所",
        "address":["北区梅田1-2-2-700 大阪駅前第2ビル7階"]},
      {
        "id":"5421",
        "name":"阪急梅田駅"}]
  }}

(「阪急梅田駅」の住所が無いのは元データがそうなっているからです)


[Solr] Tagger Handler

Solr には Tagger Handler というテキストへのタグ付けの機能があります。
タグ付けはかなり幅の広い技術で、高度な自然言語処理を用いてアノテーションを与える手法もある一方で、辞書として与えられた単語を単純に抽出する手法もあります。Solr の Tagger Handler は後者です。

Tagger Handler ではSolrのインデックスを辞書として使い、Tagger Handler へ送られたテキスト内に含まれる辞書の単語をタグ付けして出力します。与えられたキーワードがインデックスされたドキュメントに含まれるかどうかを調べる検索処理と対照的ですが、要素の技術は共通しています。

Solr リファレンスには Tagger Handler のチュートリアルが含まれているので、それに沿って動作を確認してみます。

まず geonames というコレクションを作ります。

bin/solr create -c geonames

スキーマを API で定義します。

curl -X POST -H 'Content-type:application/json'  http://localhost:8983/solr/geonames/schema -d '{
  "add-field-type":{
    "name":"tag",
    "class":"solr.TextField",
    "postingsFormat":"FST50",
    "omitNorms":true,
    "omitTermFreqAndPositions":true,
    "indexAnalyzer":{
      "tokenizer":{
         "class":"solr.StandardTokenizerFactory" },
      "filters":[
        {"class":"solr.EnglishPossessiveFilterFactory"},
        {"class":"solr.ASCIIFoldingFilterFactory"},
        {"class":"solr.LowerCaseFilterFactory"},
        {"class":"solr.ConcatenateGraphFilterFactory", "preservePositionIncrements":false }
      ]},
    "queryAnalyzer":{
      "tokenizer":{
         "class":"solr.StandardTokenizerFactory" },
      "filters":[
        {"class":"solr.EnglishPossessiveFilterFactory"},
        {"class":"solr.ASCIIFoldingFilterFactory"},
        {"class":"solr.LowerCaseFilterFactory"}
      ]}
    },

  "add-field":{"name":"name", "type":"text_general"},
  "add-field":{"name":"name_tag", "type":"tag", "stored":false },
  "add-copy-field":{"source":"name", "dest":["name_tag"]}
}'

Tagger Handler を API で設定します。

curl -X POST -H 'Content-type:application/json' http://localhost:8983/solr/geonames/config -d '{
  "add-requesthandler" : {
    "name": "/tag",
    "class":"solr.TaggerRequestHandler",
    "defaults":{"field":"name_tag"}
  }
}'

http://download.geonames.org/export/dump/cities1000.zip
からサンプルデータをダウンロードし、/tmp に展開しておきます。
サンプルデータを geonames コレクションに投入します。

bin/post -c geonames -type text/csv \
  -params 'optimize=true&maxSegments=1&separator=%09&encapsulator=%00&fieldnames=id,name,,alternative_names,latitude,longitude,,,countrycode,,,,,,population,elevation,,timezone,lastupdate' \
  /tmp/cities1000.txt

タグ付けの動作を確認します。

$ curl -X POST \
  'http://localhost:8983/solr/geonames/tag?overlaps=NO_SUB&tagsLimit=5000&fl=id,name,countrycode&wt=json&indent=on' \
  -H 'Content-Type:text/plain' -d 'Hello New York City'
{
  "responseHeader":{
    "status":0,
    "QTime":1},
  "tagsCount":1,
  "tags":[{
      "startOffset":6,
      "endOffset":19,
      "ids":["5128581"]}],
  "response":{"numFound":1,"start":0,"docs":[
      {
        "id":"5128581",
        "name":["New York City"],
        "countrycode":["US"]}]
  }}

リファレンスによると、Tagger Handler へのリクエストではインデックスのどのフィールドを辞書として扱うかを示す filed パラメータが必須となっていますが、このリクエストには field パラメータが含まれていません。その代わりに Tagger Handler の設定で以下を指定しているため、デフォルトで name_tag フィールドが使われます。

   "defaults":{"field":"name_tag"}
を指定しているため、デフォルトで name_tag フィールドが使われます。

複数のエンティティが含まれる場合も試してみました。

$ curl -X POST   'http://localhost:8983/solr/geonames/tag?overlaps=NO_SUB&tagsLimit=5000&fl=id,name,countrycode&wt=json&indent=on'   -H 'Content-Type:text/plain' -d 'Hello New York City. Hello Los Angeles.'
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "tagsCount":2,
  "tags":[{
      "startOffset":6,
      "endOffset":19,
      "ids":["5128581"]},
    {
      "startOffset":27,
      "endOffset":38,
      "ids":["3882428",
        "3801497",
        "3998147",
        "3998148",
        "8858843",
        "3705542",
        "3705544",
        "1705545",
        "5368361"]}],
  "response":{"numFound":10,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"3882428",
        "name":["Los Ángeles"],
        "countrycode":["CL"]},
      {
        "id":"3801497",
        "name":["Los Ángeles"],
        "countrycode":["MX"]},
      {
        "id":"3998147",
        "name":["Los Angeles"],
        "countrycode":["MX"]},
      {
        "id":"3998148",
        "name":["Los Angeles"],
        "countrycode":["MX"]},
      {
        "id":"8858843",
        "name":["Los Ángeles"],
        "countrycode":["MX"]},
      {
        "id":"3705542",
        "name":["Los Ángeles"],
        "countrycode":["PA"]},
      {
        "id":"3705544",
        "name":["Los Ángeles"],
        "countrycode":["PA"]},
      {
        "id":"1705545",
        "name":["Los Angeles"],
        "countrycode":["PH"]},
      {
        "id":"5128581",
        "name":["New York City"],
        "countrycode":["US"]},
      {
        "id":"5368361",
        "name":["Los Angeles"],
        "countrycode":["US"]}]
  }}

“New York City” と “Los Angeles” の両方がヒットしました。様々な国の “Los Angeles” がタグとして出力されています。
Tagger Handler へのリクエストにはフィルタクエリを含めることができます。

$ curl -X POST   'http://localhost:8983/solr/geonames/tag?overlaps=NO_SUB&tagsLimit=5000&fl=id,name,countrycode&wt=json&indent=on&fq=countrycode:US'   -H 'Content-Type:text/plain' -d 'Hello New York City. Hello Los Angeles.'
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "tagsCount":2,
  "tags":[{
      "startOffset":6,
      "endOffset":19,
      "ids":["5128581"]},
    {
      "startOffset":27,
      "endOffset":38,
      "ids":["5368361"]}],
  "response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"5128581",
        "name":["New York City"],
        "countrycode":["US"]},
      {
        "id":"5368361",
        "name":["Los Angeles"],
        "countrycode":["US"]}]
  }}

先程のリクエストに fq=contrycode:US を追加することで、結果を US に限定できました。


[Solr] zeppelin-solrで単語の使われ方を可視化する

zeppelin-solr の事例の1つ “Text Analysis and Term Vectors” では Solr 内のドキュメントをテキスト解析してその特徴を可視化する方法の例が示されています。これを日本語のドキュメントでやってみました。

対象は日本語版Wikipediaとします。
Streaming Expressions の analyze 関数を使うと指定した文字列を形態素解析できます。

analyze("システム構成の概要", text)

「システム構成の概要」が対象の文字列、text は text フィールドで定義されている Tokenizer でトークナイズするという意味です。 Wikipedia 用のコレクションを作るときの設定で text フィールドは Kuromoji を使った形態素解析をすると定義されています。

上の analyze の実行結果は以下の通りです。

[システム, 構成, 概要]

では解析結果を使って図示してみましょう。
“Text Analysis and Term Vectors” では、英語のドキュメントの bi-gram をカウントして多いものトップ10をグラフにしていました。ここでは、「漫画」を含む Wikipedia の記事のタイトルを形態素解析して単語をカウントし、多いものトップ20をグラフにしました。

結果は以下の通りです。
ultimate-pie-chart を使っています。
円グラフの内訳が降順になっていないのが気になるところですが、こういう仕様のようです。