前回紹介した 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 は後者です。
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 に限定できました。
こんにちは。開発担当のマットです。
今日の記事で、FizzBuzzという海外の子供の遊びと、プログラミングについて話をしたいと思います。
FizzBuzz とは、とても単純な子供のゲームです。 1 から数字を数え上げるゲームだけです。
1,2,3,4,5,6,7… などなど
ただし、3で割れる数を “Fizz” という
そして、5で割れる数を “Buzz” という
1,2,Fizz,4,Buzz,6,7… の感じで。
なお、3でも、5でも割れる場合、 “FizzBuzz” という
1,2,Fizz,4,Buzz,6,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17… などなど
基本的に、二人で遊んで、交合に数字をできるだけ早く言うのがルールです。
間違えてしまうと負けになります。
FizzBuzz をコードで作るのはとても簡単で、初心者でもできますので、是非一緒にやってみましょう。
僕は Javascript でやりたいと思います。
Javascript は Chrome のコンソールに直接書いてすぐに実行できますので、便利です。
まずは、1 から 99 までの for ループを作りましょう。
そのループの中で、数字をそのまま書き出します。(Fizz や Buzz は後でやります)
コンソールで実行する場合、 console.log を使いますが、プログラミング言語によって、echo や print なども使えます。
これをコンソールで実行する場合、1 から 99 までの数字が全部出力されます。
3 で割れる場合、”Fizz”
5 で割れる場合、”Buzz”
3 と 5 で割れる場合、”FizzBuzz”
そして、どれにも当てはまらない場合、 数字そのまま
それをコードにそのまま書くと、以下のような感じになります。
結果を見ると・・・
ダメですね…
15 の場合、Fizz も Buzz も FizzBuzz も出てしまった。何というバグ。
なお、コードが整っていないですね。バグを修正しようと思っても、更にわかりにくくなりそう…
まず、「出力値」の変数を作りましょう。その変数に空っぽな文字列も入れてみます。
var output = “”;
のような感じで。
もし、3で割れる場合、その output 変数に “Fizz” を追加しましょう。
もし、5で割れる場合、その output 変数に “Buzz” を追加しましょう。
これで、Fizz も Buzz も FizzBuzz も、対応できますよね。
なお、上記のチェック後、その output の変数の文字列が空っぽであれば、数字を書き出せばいいとなりますよね。
結果を見ると・・・
完璧です!
なお、とてもわかりやすいコードになりましたので、ゲームのルールを変更したバージョンもすぐに作成できますね。
何かのプログラムやスクリプトを作る場合、書き方は様々あります。
ただし、プログラマーは日常、わかりやすくて簡潔なコードを書くことはとても大事です。
直感でいくと、バグだらけのわかりにくいコードになる恐れはありますので、何かの挑戦に取り組む前に、進め方を一度考えてから進んだほうがいいでしょうね。
これからも頑張っていきましょう!
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 を使っています。
円グラフの内訳が降順になっていないのが気になるところですが、こういう仕様のようです。