Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

JJUG CCC 2018 Fall で発表しました

皆さんこんにちは、しんどーです。

この記事はJava Advent Calendar 2018の17日目のエントリーです。
16日目はdo-m-gatoruさんの「JJUG CCC 2018 Fall」に参加してきた。でした。

はじめに

先日の12/15(土)に行われたJJUG CCC 2018 Fallに参加し、登壇させていただきました。
JJUG CCCは日本Javaユーザグループ主催のコミュニティイベントです。
参加者は登録数で1000人超と、ユーザカンファレンスとしては最大級の規模です。
Javaのイベントではありますが、ほとんどJavaに関係ないセッションもあったりでJavaユーザ以外でも十分に楽しめる内容だと思います。

発表について

朝イチで『ふつうのJavaアプリ開発のための自動テスト戦略』というタイトルで発表しました。

ふつうのJavaアプリ開発のための自動テスト戦略 / JJUG CCC 2018 Fall - Speaker Deck



前回のJJUG CCC 2018 SpringではJUnit 5について発表しており、2回連続でテストネタと、特に意識はしてなかったのですが続編的なものになってしまいました。
まだまだ間に合う!JUnit 5入門 / JJUG CCC 2018 Spring - Speaker Deck

ありがたいことに、登壇初心者枠のセッションにも関わらず多くの方にご参加いただき満席御礼となりました。僕に何をそこまで期待していたのでしょうか!?

参加してくださった方々、あとから資料を見てくださった方々には大変感謝感激です。
はてブもたくさんついてるようで、何やら恥ずかしさがこみ上げてきました。

f:id:acro-engineer:20181217003124j:plain
発表の様子

裏話的なこと

テストネタで発表しておきながら、実は僕はあまりテストが好きではありませんw
ただ嫌いなりにテスト(特にユニットテスト)のことに向き合って、いかに楽にできるか、いかに楽しく開発できるかを考えながら発表させていただきました。

CFPでは「実際のコードも交えながらお話します」とか書きましたが、結果コードは1行も出てきていませんww
「話が違うじゃねーか!」と発表中に靴を投げ込まれるのではないかと内心ビクビクしていました。

ノウハウや知識よりも、考え方や自分の想いを伝えるセッションにしたつもりですので、パッション的なものを感じていただければ何よりです。

さいごに

1年前はJJUG CCCに登壇するなんて考えもしなかったのですが、なんと今年は2回もしてしまいました。裏で多くの方に背中を押していただいて、こういう経験をできたのはエンジニアとしてとても貴重なことだと思います。

ちなみにですが、こんな超個人的な内容を会社ブログに上げさせてくれる弊社は自由だなーと思いました。


明日18日目のJava Advent Calendarの担当はnabedgeさんです。
登壇上級者からのJJUG CCCの感想が楽しみです!



Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 

少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

モノリシックなアプリケーションをマイクロサービス化したいエンジニア募集! - Acroquest Technology株式会社のWeb エンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com



ElasticsearchのRanking Evaluation APIについて整理してみた

概要

こんにちは、shin0higuchiです😊
この記事はElastic stack (Elasticsearch) Advent Calendar 2018 - Qiitaの17日目です。
ElasticsearchのRanking Evaluation APIについて整理してみました 。

バージョン情報など

Elasticsearch : 6.5.1
Kibana : 6.5.1

Ranking Evaluation APIとは

Ranking Evaluation APIは、検索クエリに対する検索結果の妥当性を評価するためのAPIです。
Elasticsearchのバージョン6.2以降で利用することができます。


突然ですが、「良い検索」とはどういったものでしょうか?
一般的に、指定した検索条件に対して、より関連度の高いドキュメントが検索結果の上位に来るほど、良い検索システムであると言えます。
このAPIでは、「関連度の高いドキュメント」を事前にテストデータセットとして定義することで、実際の検索結果を評価します。

使いどころとしては、「index時に利用しているanalyzerの設定が適切か?」「アプリケーションがElasticsearchに対して発行するクエリは適切か?」などを定量的に評価したい場合に利用するのが良いと思います。

使い方

データ登録

今回は、日本語のWikipediaデータをElasticsearchに登録しておきます。
https://dumps.wikimedia.org/jawiki/20181201/jawiki-20181201-pages-articles6.xml-p2534193p3873913.bz2
からデータをダウンロードし、Logstashを使って取り込みました。

Kibanaから確認してみたところ、wikiというindexが作成されています。

f:id:acro-engineer:20181216212547p:plain:w700
title一覧

約60万件の記事が登録されていることがわかります。

また、タイトル一覧を取得してみると、非常に多岐にわたる記事が登録されていますね。

f:id:acro-engineer:20181216213806p:plain:w700
title一覧

リクエストの形式

Ranking Evaluation APIを利用する際のリクエスト形式は次のようになります。

GET /wiki/_rank_eval
{
    "requests": [ ... ], 
    "metric": { ... }
}

詳細については後述しますが、リクエストボディは大まかに2つの要素からなります。

  • requests : 検索クエリおよび、そのクエリで検索結果の上位に来て欲しいドキュメントのリスト
  • metrics : 検索結果の評価方法の定義

では、それぞれの定義内容について説明します。

requests

requestsの形式は次の通り。

{
            "id": " ... ",
            "request": {
                "query": { ... }
            },
            "ratings": [
                { "_index": "wiki", "_id": "doc1", "rating": 0 }.
                { "_index": "wiki", "_id": "doc2", "rating": 1 },
                { "_index": "wiki", "_id": "doc3", "rating": 2 }
            ]
        }

request内にクエリを記述し、そのクエリで検索結果の上位に来て欲しいドキュメントのリストはratingsの中に記述します。indexとdocument idを指定し、ratingに関連度を記述します。ratingの数値は整数で、0であれば検索にヒットして欲しくないドキュメント(クエリに関連のないドキュメント)、数値が大きいほど関連のある(検索上位に来て欲しい)ドキュメントとなります。

上記の例だと、_idがdoc3とdoc2のものが検索結果上位に来て、doc1はヒットしないのが理想ということです。
Ranking Evaluation APIでの評価は、実際にクエリを実行した時に、ratingに定義したリストにどれだけ近いかを数値化するものです。
厳密にはこの後に説明するmetricで評価方法を決定します。

metric

評価指標としては次の4つがサポートされています。

  • Precision at K
  • Mean Reciprocal Rank (MRR)
  • Discounted Cumulative Gain (DCG)
  • Expected Reciprocal Rank (ERR)

詳細については、本記事では割愛します。
もし評価方法論について興味があれば、個人的には下記の書籍がオススメです。
情報アクセス評価方法論-酒井哲也-

この記事ではもっともシンプルなPrecision at K(以下、P@K)を用います。
これは実際の検索結果の上位Kドキュメントの適合率を表します。

metricにP@Kを設定するには下記のように記述します。

"metric": {
      "precision": {
        "k" : 5,
        "relevant_rating_threshold": 1
      }
   }

この例では、検索結果の上位5件に、rank1以上のドキュメントが何割あるかを評価します。
5件全てが、関連のあるドキュメント(rank1以上)であれば適合率は100%、つまりP@Kは1となります。

実行してみる

では、実際に今回のケースに適用してみます。

例えば検索ユーザーが、「Javascriptフレームワークに関連するドキュメントが欲しい」と考えたとしましょう。ユーザーは「Javascript Web フレームワーク」などと検索すると仮定します。このクエリで検索した時の結果を評価してみましょう。実際のリクエストは下記のようになります。

GET wiki/_rank_eval
{
  "requests": [
    {
      "id": "javascriptのフレームワークを探すクエリ例",
      "request": {
        "query": {
          "query_string": {
            "query": "Javascript Web フレームワーク"
          }
        }
      },
      "ratings": [
        {
          "_index": "wiki",
          "_id": "3440787",
          "rating": 1
        },
        {
          "_index": "wiki",
          "_id": "3387491",
          "rating": 1
        },
        {
          "_index": "wiki",
          "_id": "2611804",
          "rating": 1
        },
        {
          "_index": "wiki",
          "_id": "2922947",
          "rating": 1
        },
        {
          "_index": "wiki",
          "_id": "3672684",
          "rating": 1
        }
      ]
    }
  ],
  "metric": {
      "precision": {
        "k" : 5,
        "relevant_rating_threshold": 1
      }
   }
}

ここでは関連のあるドキュメント上位5件のIDをratingsに登録しています。
実際に実行してみると次のようなレスポンスが返ってきます。

{
  "metric_score" : 0.4,
  "details" : {
    "javascriptのフレームワークを探すクエリ例" : {
      "metric_score" : 0.4,
      "unrated_docs" : [
        {
          "_index" : "wiki",
          "_id" : "2828794"
        },
        {
          "_index" : "wiki",
          "_id" : "3447215"
        },
        {
          "_index" : "wiki",
          "_id" : "3609062"
        }
      ],
      "hits" : [
        {
          "hit" : {
            "_index" : "wiki",
            "_type" : "page",
            "_id" : "2828794",
            "_score" : 32.780136
          },
          "rating" : null
        },
        {
          "hit" : {
            "_index" : "wiki",
            "_type" : "page",
            "_id" : "3440787",
            "_score" : 32.139748
          },
          "rating" : 1
        },
        {
          "hit" : {
            "_index" : "wiki",
            "_type" : "page",
            "_id" : "3447215",
            "_score" : 31.709887
          },
          "rating" : null
        },
        {
          "hit" : {
            "_index" : "wiki",
            "_type" : "page",
            "_id" : "3609062",
            "_score" : 28.539764
          },
          "rating" : null
        },
        {
          "hit" : {
            "_index" : "wiki",
            "_type" : "page",
            "_id" : "3387491",
            "_score" : 28.265165
          },
          "rating" : 1
        }
      ],
      "metric_details" : {
        "precision" : {
          "relevant_docs_retrieved" : 2,
          "docs_retrieved" : 5
        }
      }
    }
  },
  "failures" : { }
}

"metric_score" : 0.4という結果から分かるように、適合率は40%、つまり5件中の2件のみが関連するドキュメントで、残りの3件は関連がない若しくはrateingされていないドキュメントということになります。

実際、unrated_docsというブロックを見ると、3件のドキュメントが入っています。
これは、想定していなかった(リクエスト時にrating指定していなかった)ドキュメントが、検索結果の上位5件に入ってきたことを意味します。
ドキュメントを確認してみたところ、「Vue.js」「Angular.js」などの他に、「シングルページアプリケーション」などがヒットしていました。

今回のケースでは、「シングルページアプリケーション」よりも上位に来て欲しい他のフレームワークなどもありました。
実際には、ここからanalyzerやscoringなどを調整して理想の検索結果に近づくようチューニングをしてゆくことになるでしょう。
その時に再びRanking Evaluation APIを利用して精度が上がったかどうかを客観的数値で測ることができます。
今回の記事の中では、長くなってしまうためチューニングについては割愛します。

さて、ここまでRanking Evaluation APIの使い方を簡単に説明してきました。
真面目に検索システムとして評価するとなると、通常は1つのクエリだけでなく、複数のクエリを用いて総合的に評価するでしょうし、ratingに関してもより網羅性を上げる必要があると思います。ただ、全ドキュメントにレートをつけるのは現実的ではないので、通常はどの程度までratingするものなのか、詳しい人がいたら是非話してみたいものです。

まとめ

  • Ranking Evaluation APIを使って、検索システムとしての定量的評価ができる
  • 事前のRatingなど、テストデータセットの準備が手間

以上です。ツッコミ等あれば是非お願いいたします。
お読みいただきありがとうございました。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

世界初のElastic認定エンジニアと一緒に働きたい人Wanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com

開発者が見落としがちなユーザビリティアンチパターン

こんにちは。QAエンジニアの @yuki_shiro_823 です。

このエントリはソフトウェアテスト Advent Calendar 2018の15日目の記事です。

きっかけ

私は社内で色々なプロジェクトを渡り歩き、横断的にテストを行っています。
テストをしていると、

  • 機能的には問題ないけれども、なんとなく使いづらい
  • お客様の業務に照らし合わせると、もっとこうした方がいいのでは?

という事例を時々見かけます。
「開発者にもユーザ視点で、開発時から基本的なユーザビリティを向上させられるようになってほしい!」
という思いから、いくつかアンチパターンをまとめてみました。
その一部を紹介します。

ユーザビリティの定義と本エントリで扱う範囲

ユーザビリティの定義は、ヤコブ・ニールセンによる定義やISO25010などいくつかあります。
本エントリではISO-25010の「システム/ソフトウェア製品品質」で定義されている「使用性」の一部「適切度認識性」にまつわるアンチパターンをとりあげます。
f:id:acro-engineer:20181214014259p:plain:w450

適切度認識性のアンチパターン

適切度認識性について

ISO25010では、「ニーズに適した利用かどうか認識できる度合」と定義されています。

損なわれるとどうなるか

ユーザがシステムを利用して、明示された作業及び目的の達成を実行できなくなる、と考えられます。

アンチパターン1:探し物はどこですか?

f:id:acro-engineer:20181214020750p:plain:w200

内容

表やドロップダウンリストの並びが何の順番になっているのか不明で、自分の見たいものがどこにあるのか分からない、というケースです。
業務上、最新の結果から順番に表示してほしいのに、件名の昇順になっていて使いにくいということがありました。
また、サイトを訪れる人のほとんどが日本国籍と思われるサイトで、国籍を選択するドロップダウンリストになかなか「日本」が出てこないというのもよく見ます。
f:id:acro-engineer:20181214235724p:plain

対処策

一覧であれば、業務での使用順に合わせて並んでいる、ドロップダウンリストであれば、選択肢が使用頻度の高いものから並んでいるという構成にしてほしいです。上記の例では「JP 日本」がドロップダウンリストの上位に表示されていると、ユーザにとっては使いやすいですね。
要件をヒアリングする段階で、ユーザが一覧にした内容で何をするのか、が明確にできていると後々助かります。(コーディングの段階で気づくと、修正が厄介な場合が多い問題です)

アンチパターン2:反応がない…

f:id:acro-engineer:20181214021830p:plain:w200

内容

「登録」や「検索」などのボタンをクリックしたものの、画面に何も反応がなく、処理が行われているか分からないというケースです。ユーザがボタンを連打してしまい、二重登録などが発生してしまう恐れがあります。

対処策

処理中であることが、ユーザに明確に分かるようにしてください。
こちらは、はてなブログの編集画面にある「公開する」ボタンです。
f:id:acro-engineer:20181215002713p:plain
一度「公開する」ボタンを押下すると、次の通りボタンの色が変わります。
f:id:acro-engineer:20181215004535p:plain
いかにもクリックできない様子になりました。
このように、

  • ボタンをクリックできなくする/ボタンの色を変える
  • プログレスバーなどを表示し、処理中であること示す

などがあると分かりやすくなります。
登録画面などの場合は、「登録ボタンは一度だけクリックしてください」などの注意書きがあるだけでも、連打の防止に効果があるそうです。

アンチパターン3:キャンセルのキャンセル?

f:id:acro-engineer:20181214030515p:plain:w200

内容

ユーザの操作に対して、何らかのメッセージが出るものの、次に何をしてよいか明確でない、すぐに操作が行えない、というケースです。
f:id:acro-engineer:20181215000125p:plain
パッと見ただけでは、「キャンセル」ボタンをクリックしたときに、どちらになるのか分かりません。
キャンセルをキャンセルするので、結果は「キャンセルされない」になるのですが、キャンセルがゲシュタルト崩壊しそうですね。
こちらは、キャンセルのキャンセル問題という有名なアンチパターンです。詳しく知りたい方はぜひ、以下のサイトをどうぞ。
キャンセルのキャンセル問題から考えるダイアログデザイン

対処策

メッセージの場合は、「xxxに誤りがあります。ご確認ください」など次に何をすればよいか明示するようにしてください。
また、メッセージ中の動詞とボタンの動詞をそろえるのも効果的です。

アンチパターン4:どこへも行けない

f:id:acro-engineer:20181214025231p:plain:w200

内容

ユーザがある画面から、他の画面に戻りたいと思ったときに移動する手段がないケースです。例えば、次のような画面がありました。

  • 「戻る」ボタンがない
  • ダイアログ表示で「戻る」または「閉じる」ボタンがない

f:id:acro-engineer:20181215000220p:plain:w300
こちらのキャプチャの例は、半分ネタですが、ダイアログのエリア外を押すとダイアログが消えることを知らないと、ユーザは何もできなくなってしまいます。

対処策

明確に「戻る」や「閉じる」のボタンを設けてください。
ボタンを設けない場合は、「ブラウザのバックボタンでお戻りください」など、ユーザが次の操作を行えるような指示を記載してください。

最後に

4点のアンチパターンを紹介しましたが、気を抜くと誰しもやってしまいがちなものだと思います。
何かの折にふと思い出してもらい、ユーザビリティの高いシステムを作るヒントにしてもらえると幸いです。

Acroquest Technologyでは、キャリア採用を行っています。


  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
モノリシックなアプリケーションをマイクロサービス化したいエンジニア募集! - Acroquest Technology株式会社のWeb エンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

最初に知っておけば良かったFilebeatの設定

こんにちは。

最近、Filebeatによるログ収集について興味を持ちつつ、
いろいろと調べながら使っているsawaです。

この記事は、Elastic stack (Elasticsearch) Advent Calendar 2018 - Qiitaの、13日目の記事になります。

はじめに

Elastic Stackを使ったログ収集を行うには、
Logstashを使う、Beatsシリーズを使うといった、選択肢があります。

本記事では、Filebeatを使う際に知っておくと良い設定を紹介したいと思います。

Filebeatとは?

f:id:acro-engineer:20181212180846j:plain

Filebeatには次のような特徴があります。

  1. データ収集ツールとして、軽量に扱える。
  2. 用途が限定されているため、シンプルに使える。
  3. ミドルウェアに応じたプラグインを適用することで、自動でダッシュボードの生成までも行える。

詳細についてはドキュメントに記載があります。
www.elastic.co

大量のログファイルを収集する際に陥りがちな問題

手元にある大量のファイルをElasticsearchにインデクシングする必要があり、Filebeatを使いました。
ひとまずFilebeatをデフォルト設定のまま使ったところ、うまく収集できず、Filebeatのログにこんな出力がされました。

A:

Harvester could not be started on new file: /var/log/foo_xxxx.log, 
Err: error setting up harvester: Harvester setup failed. Unexpected file opening error: 
Failed opening /var/log/foo_xxxx.log: open /var/log/foo_xxxx.log: too many open files

B:

Failed to connect to backoff(elasticsearch(http://localhost:9200)): Get http://localhost:9200: 
dial tcp [::1]:9200: socket: too many open files

Aはログファイルを開けない事象、BはElasticsearchへの接続が行えない事象を指しています。

例えば、3,000個のログファイルを読み込んでしばらくすると、オープンファイル数が1,000近くになることがFilebeatのログから分かりました。
それにより、OSのファイルディスクリプタのソフトリミットである1,024近くになっていたことが分かりました。
ファイルディスクリプタを使う数を減らしたいですね。

Filebeatにおける収集の仕組みと対策

対策するために、まず、Filebeatによる収集の仕組みを押さえます。

収集の仕組み

Filebeatは、以下の流れでログを収集します。

f:id:acro-engineer:20181213103928j:plain

①収集対象ファイルの一覧取得
 デフォルト10秒間隔で、タイムスタンプが新しいファイルを収集対象に入れます。
 (一覧取得間隔はfilebeat.ymlの「scan_frequency」で変更可能)
 一覧に入ったファイルは、②で収集開始します。

②収集の開始(ファイルハンドラの確保)
 1ログファイルにつき1つずつのファイル収集のためのインスタンスが立ち上がります。
 これを「ハーベスタ」といいます。

③更新の検知と送信
 デフォルト1秒間隔で、新しい行が追加されていれば差分を外部に送信します。
 (更新の検知間隔はfilebeat.ymlの「backoff」で変更可能)

④収集の終了(ファイルハンドラの解放)
 filebeat.ymlで定義された条件を満たすと、ハーベスタによる収集が終了します。


上記の④で収集を終了(ファイルハンドラを解放する)条件の設定は以下のドキュメントで紹介されています。
 Log input | Filebeat Reference [6.5] | Elastic

  • 一定時間以上の更新が無い場合(filebeat.ymlの「close_inactive」で変更可能)
  • ファイルが削除された場合
  • ファイルがリネームされた場合
  • ファイルがEOFに達した場合
  • ファイルハンドラを作って一定時間が経過したら強制的に解放

これら設定は、ユースケースに応じて柔軟にカスタマイズが必要ですね。
Filebeatを利用する前にチェックしてみてください。

今回の場合は5分以上更新が無いログファイルなので、「一定時間以上の更新が無い場合」(close_inactive)の時間を短縮することでファイルディスクリプタの枯渇によるエラーの発生を抑えることができそうです。
(もちろん、OSの設定を変更するという手段もあります)

以下の条件を満たすログを収集する場合は、close_inactiveの短縮が有効と言えるでしょう。

  • 更新頻度がさほど少ない
  • サイズが小さめ
  • 1,000以上ある大量の数のログが収集対象

「close_inactive」による対策

「close_inactive」を具体的に紹介します。
デフォルトは5m(5分間)で、単位に分(m)や秒(s)を指定可能です。

短くすることでより早くファイルハンドラが解放されるようになります。
ファイルハンドラが解放されることで、待ち状態の次のファイルが次々と読み込まれていきます。

ただし、短くし過ぎると、次に更新が掛かった後にファイルハンドラを再取得するまでのタイムラグが生じます。
(①で説明したscan_frequency分待つ必要があります)

今回の検証では、1mに短縮してみます。

  close_inactive: 1m

今回はこれで解決できました。
この設定を先に知っておけば良かったですね。

最後に

Filebeatを使うために知っておけば良かった設定について紹介しました。
Filebeatを使う際に、こんな設定あったなと、思い出して、このページを見て頂ければ幸いです。

それではまた。

Acroquest Technologyでは、キャリア採用を行っています。


  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
ユーザに最高の検索体験を提供したいエンジニアWanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com

LambdaからRekognitionを使ってみたら意外なところでハマった話

本エントリは、AWS #2 Advent Calendar 2018の12日目です。

こんにちは! 気付けばAcroquestに入社してもう10年目、最近はAWSを使った開発やDevOpsの活動に携わっているiidaです。

今回は、少し前に上司から「Amazon Rekognition Imageを使うことになるかも知れない」と言われて触ってみたら、意外なところで嵌った話をしたいと思います。

AWS Rekognitonとは

人工知能機械学習に基づく画像認識・画像分析サービスです。こう書くと何やら難しそうですが、百聞は一見にしかず、AWSコンソールのデモを見てみましょう。

f:id:acro-engineer:20181209232237p:plain:w800

これは「オブジェクトとシーンの検出」で、画像中にどんなオブジェクト(CarやPersonなど)があるのか、あるいはどんなシーン(TransportationやSportsなど)なのかを検出し、その位置まで教えてくれるようです。

私は機械学習についてはほとんど知識がありませんが、それでもこのようは画像解析ができるようになるなんて、すごい世の中になったものですね!

Pythonから呼んでみる

さてさて、このようなサービスがあるということは分かりましたが、エンジニアとしてはやはり自分の書いたコードから使ってみたいものですよね!

ということで、早速PythonからRekognitionを呼び出すコードを書いてみました。

import json
import boto3

# ローカルの画像ファイルを読み込む。
with open('sample.jpeg', 'rb') as image_file:
    image_bytes = image_file.read()
# Rekognitionのラベル検出を呼び出す。
rekognition = boto3.client('rekognition', 'ap-northeast-1')
response = rekognition.detect_labels(Image={'Bytes': image_bytes})
print(json.dumps(response, indent=2))

たったこれだけ。簡単!

f:id:acro-engineer:20181209231438j:plain:w600

試しにこの画像を解析してみましょう。先日のHappy360(全体査定)の時の一コマです。どんなレスポンスが返って来たのかというと…。

{
  "Labels": [
    {
      "Name": "Human",
      "Confidence": 99.71063232421875,
      "Instances": [],
      "Parents": []
    },
    {
      "Name": "Person",
      "Confidence": 99.71063232421875,
      "Instances": [
        {
          "BoundingBox": {
            "Width": 0.31008321046829224,
            "Height": 0.6783191561698914,
            "Left": 0.626418948173523,
            "Top": 0.31440863013267517
          },
          "Confidence": 99.71063232421875
        },
            :(以下省略)

なるほど、Nameがオブジェクトやシーンの種類で、BoundingBoxが画像中の位置のようです。HumanとPersonの違いがよく分かりませんが、Humanは「人が写っている」ということを表しているのでしょうか?

結果を画像に描画してみる

これだけだと分かりづらいので、デモみたいにラベルを画像に描画してみましょう。

import cv2

# (ここに先ほどのコードが入ります。)

# CV2で画像ファイルを読み込む。
np_image = cv2.imread('sample.jpeg')
height, width = np_image.shape[:2]

# ラベルの中から人と思われるものを探して四角で囲う。
for label in response['Labels']:
    if label['Name'] not in ['People', 'Person', 'Human']:
        continue

    for person in label['Instances']:
        box = person['BoundingBox']
        x = round(width * box['Left'])
        y = round(height * box['Top'])
        w = round(width * box['Width'])
        h = round(height * box['Height'])
        cv2.rectangle(np_image, (x, y), (x + w, y + h), (255, 255, 255), 3)
        cv2.putText(np_image, label['Name'], (x, y - 9),
                    cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3)

cv2.imwrite('./sample_result.jpeg', np_image)

BoundingBoxの値は、画像の幅と高さに対する割合となっているので、少し計算が必要です。これを実行してみると…。

f:id:acro-engineer:20181209231555j:plain:w600

Great! 手前の2人だけでなく、奥にひっそりといる人もちゃんと検出されていますね!

Lambdaから実行してみる

ここまで来たら、Lambda上でも動かしてみたくなりますよね!(えっ、ならないですか?)

S3にアップロードされた画像ファイルを自動で解析するLambda関数を書いてみました。

import os

import boto3
import cv2

s3 = boto3.resource('s3')


def handle_request(event, content):

    # S3にアップされた画像の情報を取得する。
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    object_key = event['Records'][0]['s3']['object']['key']
    file_name = os.path.basename(object_key)

    # 画像をRekognitionで解析する。
    rekognition = boto3.client('rekognition')
    response = rekognition.detect_labels(Image={
        'S3Object': {
            'Bucket': bucket_name,
            'Name': object_key
        }
    })

    # S3から画像をダウンロードする。
    tmp_dir = os.getenv('TMP_DIR', '/tmp/')
    bucket = s3.Bucket(bucket_name)
    bucket.download_file(object_key, tmp_dir + file_name)

    # 検出した人物に枠を描画する。
    image = cv2.imread(tmp_dir + file_name)
    height, width = image.shape[:2]

    for label in response['Labels']:
        if label['Name'] not in ['People', 'Person', 'Human']:
            continue

        for person in label['Instances']:
            box = person['BoundingBox']
            x = round(width * box['Left'])
            y = round(height * box['Top'])
            w = round(width * box['Width'])
            h = round(height * box['Height'])
            cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), 3)
            cv2.putText(image, label['Name'], (x, y - 4),
                        cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3)

    cv2.imwrite(tmp_dir + file_name, image)

    # S3に描画後の画像をアップロードする。
    bucket.upload_file(tmp_dir + file_name, 'result/' + file_name)
    os.remove(tmp_dir + file_name)

ちなみに、CV2(opencv-python)は非Pure Pythonなので、Amazon Linux上でビルドまたはpip installしたものをデプロイパッケージに含めるか、Serverless Framworkを使用する場合はServerless Python RequirementsプラグインでdockerizePipを有効にする必要があります(ここでも少し嵌った)。

デプロイパッケージが50MB近くになってしまいましたが、無事デプロイが完了したので、早速S3バケットに画像ファイルをアップロードしてみます。すると…。

'Instances': KeyError
Traceback (most recent call last):
  File "/var/task/person_detector.py", line 44, in handle_request
    for person in label['Instances']:
KeyError: 'Instances'

あれ? ラベルにInstancesが無いって怒られてしまいました。ローカルで動かした時にはこんなエラーは出なかったのですが、そんなこともあるんですかね?

        if label['Name'] not in ['People', 'Person', 'Human'] or 'Instances' not in label:
            continue

突貫ですが、処理するラベルの条件式をこんな風に変えてみました。

f:id:acro-engineer:20181209231438j:plain:w600

すると、出力フォルダに画像はできたのですが、ローカルでは描画されていたラベルがありません。何故だ?!

    response = rekognition.detect_labels(Image={'Bytes': image_bytes})
    print(json.dumps(response, indent=2))

こうなったらログ出力しかありません。Rekognitionからのレスポンスを表示してみると…。

{
  "Labels": [
    {
      "Name": "Person",
      "Confidence": 99.57161712646484
    },
    {
      "Name": "Human",
      "Confidence": 99.57161712646484
    },
        :(以下省略)

なんと、ローカルで実行した時よりも取得できる情報が少ないではありませんか!

こんなことが起こり得るのかと思って調べてみたところ、どうやら古いバージョンのBoto3だとこれらの情報しか取得できないようです。あれ、そう言えばLambdaではデフォルトでBoto3が使えますが、あれってもしかして…。

f:id:acro-engineer:20181209231248j:plain:w600

これだ!

確かに言われてみれば当たり前ですが、今まで意識したことがなかったので盲点でした。バージョンによって関数の有無があるのは想像できますが、レスポンスが異なるなんてこともあるんですね。

f:id:acro-engineer:20181209231555j:plain:w600

その後、最新のboto3とbotocore、そしてurllib3もデプロイパッケージに含めたところ、ローカルと同じくラベルの描画された画像が出力されました。(デプロイパッケージが50MBを超えてしまいましたが。。)

Amazon Rekognitionだけでなく、普段使っているAWS Lambdaについても理解を深められ、とても良い勉強になったと思います。LambdaからRekognitionを使おうと思っている方はお気を付け下さい! 以上、AWS #2 Advent Calendar 2018の12日目でした!

Acroquest Technologyでは、キャリア採用を行っています。


  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
ユーザに最高の検索体験を提供したいエンジニアWanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com

Vega利用時によくある7つの疑問

こんにちは、今年7月にElastic Certified Engineer認定を取得したHiroshi Yoshioka です。

この記事は Elastic stack (Elasticsearch) Advent Calendar 2018 の11日目の記事になります。

はじめに

今日のお題はVegaです。
VegaはKibanaの可視化の幅を拡大する優れたフレームワークなのですが、Elastic公式ドキュメントに情報が少ないため、取っつきやすいとは言えません。

そこで本記事では、Vegaを利用時によくある7つの疑問について回答します。 ※Vega-Liteについては扱いませんのでご了承ください。

1. そもそもVegaって何者?

VegaはインタラクティブなVisualizationを作成するための宣言型Web言語。
JSONCSVを読み込み、様々なチャートを可視化することができます。

Vega公式ページにあるサンプルを見るとすぐに分かりますが、表現できるVisualizationの種類が非常に豊富です。

例えば、このチャートを開いてみてください。
一見普通の散布図に見えますが、丸いシンボルをドラッグすると、時間軸を変更することができる!

Vegaを利用すると、このようなインタラクティブな表現をKibanaで実現できるのです。 vega.github.io

2. KibanaとVegaの関係は?

Kibana Ver.6.2.0からVisualizeにVegaを利用できるようになりました。
Vegaで作成したチャートは、Visualizeとして保存したりDashboardへ配置することが可能です。
※Ver.6.2.0以前のKibanaでVegaを利用するには、別途プラグインのインストールが必要になります。

3. 手取り早くKibana上で動くVegaを見るには?

Kibana 6.4.0以降、ワンクリックで可視化可能なサンプルデータが付属しており、VegaのVisualizeも含まれています。
こちらを利用しましょう。(本記事では6.5.0を使用します。)

まず以下をセットアップ。サンプルデータ投入画面を開きます。

  • Elasticsearch 6.5.0
  • Kibana 6.5.0

f:id:acro-engineer:20181211010713p:plain
サンプルデータ投入画面

Sample flight dataの「Add」をクリックして、航空データを投入します。

f:id:acro-engineer:20181211011120p:plain
サンプルデータ投入画面

View dataをクリックしてDashboardをオープン。

f:id:acro-engineer:20181211011233p:plain
航空データダッシュボード

右下にVegaで作られたVisualize(世界地図)があるのでEdit Visualizationを選択。 世界地図にプロットされた空港にマウスカーソルを合わせると、フライト情報のPathとツールチップが表示されます。

f:id:acro-engineer:20181211011658p:plain
航空データのVega Visualize

4. Vegaの公式ページのサンプルがKibanaで動作しないのはなぜ?

Vegaの公式ページには、Vega最新バージョンに関するドキュメントが公開されています。
2018/12/11時点では、Vegaの最新は4.4.0。一方で、Kibana 6.5.0に組み込まれているのはVega 3.3.1です。
だいぶ、バージョンに開きがありますね。

公式ページには、Vega 4.Xで追加された機能を用いたサンプルも公開されていますが、当然ながら、Vega 3.3.1が組み込まれたKibanaでは動作しません。

5. Kibanaに組み込まれたVegaのバージョンを確認する方法は?

ChromeでVegaのVisualize画面を表示し、デベロッパーツールを開きます。続いて、Console画面で以下のコマンドを実行してください。

VEGA_DEBUG

Vegaは3.3.1、Vega-Liteは2.4.0が組み込まれていることが分かります。

f:id:acro-engineer:20181211012945p:plain
Kibanaに組み込まれたVegaのバージョンを確認する

6. ダッシュボードでVegaを操作してダッシュボード全体にフィルタをかける方法は?

ダッシュボードに配置された通常のVisualizeは、クリックすることでContents Filterを作成し、ダッシュボード全体にフィルタをかけることができます。
Vegaの場合も同様にフィルタをかける方法はあるのでしょうか。

はい、あります。

公式ドキュメントには書かれていませんが、Kibana 6.4.0以降ではVegaからContents Filterの追加/削除を行う機能(関数)が組み込まれています。

# ダッシュボードにContents Filterを追加する
# query    :フィルタ内容
# index-pattern  :フィルタするインデックスパターン名
kibanaAddFilter( <query>, <index-pattern>)

先ほど開いた、航空データのVega Visualizeにフィルタ機能を追加してみましょう。

Vegaコードを開き、signals配下に以下を追加して保存します。

    {
      name: filter
      on: [
        {
          events: @airport:click 
          update: '''
            kibanaAddFilter({
                      match: {
                      OriginAirportID : {
                        query: datum.key,
                        type: 'phrase'
                    }}},'kibana_sample_data_flights')
          '''
        }
      ]
    }

f:id:acro-engineer:20181211015802p:plain
Vegaにフィルタ機能を追加

航空データダッシュボードを開いてフィルタ機能を確認してみましょう。
フィルタを実行する前です。データは298件。

f:id:acro-engineer:20181211020053p:plain
Vegaにフィルタ機能を追加したダッシュボード

地図上の適当なシンボルにカーソルを合わせて、クリック。

f:id:acro-engineer:20181211020152p:plain
Vegaにフィルタ機能を追加したダッシュボード

何やら、他のVisualizeの様子が変わりました。

f:id:acro-engineer:20181211020222p:plain
Vegaにフィルタ機能を追加したダッシュボード

ダッシュボード上部をみてみると、Contents Filterが追加されてデータが5件に絞り込まれていることが分かります。

f:id:acro-engineer:20181211020248p:plain
Vegaにフィルタ機能を追加したダッシュボード

他にも、指定したフィルタを解除する関数や、

kibanaRemoveFilter( <query>, <index-pattern>)

全てのフィルタを解除する関数。

kibanaRemoveAllFilters()

TimePickerを操作する関数も用意されています。

kibanaSetTimeFilter(<start>, <end>) 

7. Vegaをデバッグするには?

Vegaのデバッグデベロッパーツールにて「VEGA_DEBUG」オブジェクトを介して行います。 代表的なコマンドを紹介します。

dataの内容を表示する

# valuesの内容を表示
VEGA_DEBUG.view.data('values')

dataの内容をテーブル表示する

# valuesの内容をテーブル表示
console.table(VEGA_DEBUG.view.data('values'))

signalの値を取得する

# heightの値を取得
VEGA_DEBUG.view.signal('height')

signalの内容を任意の値に変更して実行する

# heightの値を100変更
VEGA_DEBUG.view.signal('height',100).run();

scaleの内容を表示する

# scale yの内容を表示
VEGA_DEBUG.view._runtime.scales.y

最後に

本記事ではVega利用におけるよくある7つの疑問に回答しました。 あとは文法を覚えれば(これが大変なのですが)、Vegaを自在に扱うことができるはずです。

Let's enjoy vega life !





Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

ユーザに最高の検索体験を提供したいエンジニアWanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com

Serverless Frameworkで AWS Lambda Layers を試してみる

こんにちは、最近のクラウドシステム開発は、ほぼサーバーレス・アーキテクチャで対応している takanorig です。
この記事は Serverless Advent Calendar 2018 の9日目の記事です。

昨日は、システムテスト自動化カンファレンス2018で、「マイクロサービスのテスト自動化 with Karate」という内容で発表をしてました。
上記はテスト自動化に関する内容ですが、こちらも、サーバーレス・アーキテクチャの関連があって利用を始めたもの。
当初、このKarateを使っての内容を書こうかと思っていたのですが、今回のタイトルの内容の方が自分的に優先度が高くなったので、内容を切り替えての投稿です。

f:id:acro-engineer:20181210075313j:plain

はじめに

先日のre:Invent2018で AWS Lambda Layers が発表されましたが、「これはPJで早速導入したい」と思った内容でした。
社内のプロジェクトでは、主にServerless Frameworkを使ってServerlessなサービスの開発を行っているのですが、Lambda Layers を使えるのかな?と思って確認したところ、Serverless Frameworkでも既に対応されていました!(対応が早い)。

ということで、本エントリーでは、Serverless Frameworkで、AWS Lambda Layers を試してみます。

※今回の内容は、AWS Lambda/Serverless Framework 自体の基本的な知識は有していることを前提にしています。

AWS Lambda Layers とは?

既にご存知の方も多いかもしれませんが、簡単に、どういうものか説明しておきます。

従来のLambdaでは、共通モジュール(共通で利用するファンクションやライブラリなど)がある場合に、それを利用する各ファンクションと合わせて、それぞれごとにパッケージングしてデプロイすることが必要でした。その分、パッケージのサイズが肥大化したり、その分デプロイに時間がかかってしまう、という課題がありました。

Lambdaでも、それなりの規模の開発になってくると、当然、共通モジュールなども増えてきますよね。

これに対して、Lambda Layers を使うことで、上記のような課題を解決できる状況になりました。Lambda Layers は、共通モジュールをひとつのzipファイルにまとめてアップロードし、各ファンクションから呼び出すことができるようになります。

Serverless Framework での Lambda Layers の利用

公式サイトの情報として、以下が参考になります。

環境

今回は、以下のような環境で確認をしています。
Layersの機能を利用するためには、Serverless Frameworkは、1.34.0以上であることが必要です。

環境 バージョン
Mac macOS Mojave 10.14.1
node 10.14.1
npm 6.4.1
Serverless Framework 1.34.1
言語 Python 3.6.0

前準備

今回、Pandasを利用した処理を試してみます。
そのためには、予め Amazon Linux 環境でビルドしたファイルを用意する必要があります。

まず、Pandasをビルドするに、ディレクトリ構成を以下のようにしています。

sls-layers-example
  └── layers
      └── pandas
          ├── package.sh
          └── requirements.txt

それぞれのファイルの中身は、以下のようになります。

  • package.sh
#!/bin/sh

export OUTPUT_DIR="python"

rm -rf ${OUTPUT_DIR} && mkdir -p ${OUTPUT_DIR}

docker run --rm -v $(pwd):/var/task -w /var/task lambci/lambda:build-python3.6 \
    pip install -r requirements.txt -t ${OUTPUT_DIR} 

ここでは、簡単に Amazon Linux の環境でビルドできるように、 lambci/docker-lambda を利用しています。
これを使うと、Dockerを利用してビルドができますね。

  • requirements.txt
pandas==0.23.4

上記の準備ができたら、Pandasのビルドを行います。

$ cd /{path}/sls-layers-example
$ cd layers/pandas
$ ./package.sh
Collecting pandas==0.23.4 (from -r requirements.txt (line 1))
・・・
Installing collected packages: six, python-dateutil, pytz, numpy, pandas
Successfully installed numpy-1.15.4 pandas-0.23.4 python-dateutil-2.7.5 pytz-2018.7 six-1.11.0

上記のように、ビルドが成功すればOKです。

serverless.yml / Lambda関数 の作成

先ほどのディレクトリ構成に対して、以下のように serverless.ymlanalysis_lambda.py を追加します。

sls-layers-example
  ├── analysis_lambda.py
  ├── layers
  │   └── pandas
  │       ├── package.sh
  │       └── requirements.txt
  └── serverless.yml
  • serverless.yml
service: sls-layers-example

frameworkVersion: ">=1.34.0 <2.0.0"

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-west-2

package:
  individually: true

# AWS Lambda Layers
layers:
  pandas:
    path: layers/pandas
    name: ${self:service}-pandas
    compatibleRuntimes:
      - python3.6
    allowedAccounts:
      - '*'

# AWS Lambda Functions
functions:
  analyze:
    handler: analysis_lambda.analyze
    layers:
      - {Ref: PandasLambdaLayer}
#     - arn:aws:lambda:${self:provider.region}:xxxxxxxxxxx:layer:{service名}-{layer名}:{バージョン}

既に登録されているLayerを使う場合は、ARNの形式で指定する必要がありますが、同じserverless.ymlで参照する場合は、「PandasLambdaLayer」(Layerに応じてCloudFormationで名前が設定される)のような名称で参照ができます。

  • analysis_lambda.py
# -*- coding: utf-8 -*-
import pandas as pd

def analyze(event, context):
    names = ['Bob','Jessica','Mary','John','Mel']
    births = [968, 155, 77, 578, 973]

    BabyDataSet = list(zip(names,births))

    df = pd.DataFrame(data = BabyDataSet, columns=['Names', 'Births'])
    print(df)

ここでは簡単に、配列から、DataFrameを利用して、データセットを作成しています。

デプロイ

ここまでくれば、あとはデプロイするだけです。

$ cd /{path}/sls-layers-example
$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (609 B)...
Serverless: Uploading service .zip file to S3 (32.64 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
・・・・

Pandasのライブラリの部分は、32.64MBありますね。
それなりのサイズになっています。

デプロイ結果確認

AWSコンソールにログインして、デプロイ結果を見てみましょう。
以下のように、キチンと「Layer」に登録されており、Lambda関数からも参照できているようです。

f:id:acro-engineer:20181210071340p:plain
Layers

ビルドしたPandasが、Layerとして登録されています。

f:id:acro-engineer:20181210071407p:plain
Function

Layerに登録されたPandasが、「参照されるレイヤー」の部分で確認できます。

テスト

デプロイされたLambdaをテストしてみます。

f:id:acro-engineer:20181210071527p:plain
Test

f:id:acro-engineer:20181210071550p:plain
Result

AWSコンソールの機能で、テストを実行してみたところ、きちんと実行ができたようです!

注意点

Layersを使う場合の制限は、予め把握した上で利用する必要があります。
特に注意すべき点は、以下の内容でしょう。

  • Layerは、5つまで。
  • デプロイ可能なサイズは、圧縮したサイズ:50 MB/解凍した際にLayerも含めたサイズ:250 MB

詳細は、以下から確認ができます。

まとめ

ServerlesFrameworkから、AWS Lambda Layers を使ってみました。

これで、各関数で共通的に必要となる共通モジュールの管理が、より整理でき、効率的な開発ができそうです。
また、Layersでは、バージョンごとに保存されるようにもなっているので、複数の関数で、同時に変更ができないような場合でも対応できそうです。



Acroquest Technologyでは、キャリア採用を行っています。