フレクトのクラウドblog re:newal

http://blog.flect.co.jp/cloud/からさらに引っ越しています

Azure Bot Serviceを試してみる。

エンジニアのオクダです。こんにちは。今回のテーマは「Azure Bot Service」です。

前回は「Alexaスキルのデザイン・開発・公開」についてお話しさせて頂きました。 今回は「Azure Bot Service」についてお話しさせて頂きます。Azure Bot ServiceとはMicrosoftが提供しているサービスです。 上記のリンクでは「ボット開発向けに特化されたマネージド サービス」と記載されており、自然言語処理は、AzureのLanguage Understandingサービスである、LUISが行っています。 今回はこのLUISについて調査しました。

まず日本語対応ですが、対応言語を見てみると、日本語のサポートされている機能は以下の3つです。

✔︎ 事前構築済みのエンティティ

✔︎ フレーズ リストのレコメンデーション

✔︎ テキスト分析(キー フレーズのみ)

事前構築済みのエンティティとは、単語を変数として格納する器で、エンティティを組み合わせてインテントを作成していきます。

事前構築済みのエンティティの例:

  • job_type = [アプリ開発, フロントエンド, データサイエンス, 組み込み系, …]

  • search = [探します, 探しています, 調べます, 検索しています, どれですか, …]

事前構築済みのエンティティを使用したインテントの例:

  • {job_type}の仕事を{search}

事前構築済みのエンティティが利用可能であれば、プログラムでの自然言語処理が可能です。 例えば「アプリ開発の仕事を探しています。」と言う発話があった場合、

アプリ開発の仕事を探しています。」と言う文章は分解されて解釈され、

{job_type}="アプリ開発"、{search}="探しています"

が変数として取り出されます。

LUISはこのようなインテントを複数個まとめてLanguage Modelを構築しています。 このLanguage Modelがユーザの発話を解析し、どのインテントにヒットしたかをスコア(score)として返します。

エンティティ無しインテントの場合

まず、エンティティ無しで、LUISで英語と日本語のLanguage Modelを作成してデプロイしてみました。 作成した日本語のLanguage Modelに対して「自然言語処理の仕事を探しています」と言う文章を評価した結果は下記の通りです。

{
  "query": "自然言語処理の仕事を探しています",
  "topScoringIntent": {
    "intent": "求人情報を入手する",
    "score": 0.70248
  },
  "intents": [
    {
      "intent": "求人情報を入手する",
      "score": 0.70248
    },
    {
      "intent": "仕事に応募する",
      "score": 0.06822772
    },
    {
      "intent": "None",
      "score": 0.018654
    },
    {
      "intent": "メール送信",
      "score": 0.00519312872
    }
  ],
  "entities": []
}

同じような文章で英語では99%のスコアが出ているのに対し、日本語では70%のスコアしか出ていませんが、topScoringIntentとして狙い通りのインテントが抽出されています。

エンティティを使用してLanguage Modelを作成

それでは、次にエンティティを使用してLanguage Modelを作成しました。作成したModelの評価結果は、下記の通りです。

{
  "query": "メールを3時に送って",
  "topScoringIntent": {
    "intent": "仕事に応募する",
    "score": 0.08809486
  },
  "intents": [
    {
      "intent": "仕事に応募する",
      "score": 0.08809486
    },
    {
      "intent": "求人情報を入手する",
      "score": 0.06960007
    },
    {
      "intent": "None",
      "score": 0.0618566237
    },
    {
      "intent": "メール送信",
      "score": 0.06026428
    }
  ],
  "entities": [
    {
      "entity": "3時",
      "type": "builtin.datetime",
      "startIndex": 4,
      "endIndex": 5,
      "score": 0.9534087
    },
    {
      "entity": "メール",
      "type": "e-mail",
      "startIndex": 0,
      "endIndex": 2,
      "resolution": {
        "values": [
          "メール"
        ]
      }
    },
    {
      "entity": "送っ て",
      "type": "send",
      "startIndex": 7,
      "endIndex": 9,
      "resolution": {
        "values": [
          "送って"
        ]
      }
    }
  ]
}

エンティティは期待通りに取り出されているようです。しかし「メールを3時に送って」のインテントに対し、scoreは「仕事に募集する」が0.088、「メール送信」が0.060でした。当然「メール送信」が選ばれるべきですが、期待外れの結果となりました。これでは、実用的ではありませんね。

Stackoverflowを通じてMicrosoftに問い合わせてみる

上記の問題をStackoverflowに投稿したところ、Microsoftのサポートエンジニアから返事が返ってきました。問い合わせた内容については下記リンクをご覧下さい。

stackoverflow.com

要約すると「日本語はまだ完全にサポートされていません。...途中省略...2019年8月にメジャーアップデートされるでしょう。」と言うことでした。

感想

今回、AzureのLanguage Understandingサービスである、LUISについて調査を行ってみましたが、対応言語の中に日本語が含まれているにも関わらず、事前構築済みのエンティティを使用した場合、正しく自然言語処理されないことがわかりました。 「日本語対応」を謳うからには、もう少し機能強化してほしいものです。 今年8月のメジャーアップデートに期待したいと思います。

Mobilenet v2とInception v4の転移学習

みなさんこんにちは。技術開発室の岡田です。

前回の投稿では、エッジデバイス上での画像認識技術についてご紹介しました。

cloud.flect.co.jp


今回は、この中で紹介した画像認識技術の一つである、Image Classification(物体認識)に用いるモデル(分類機)の作成方法についてご紹介します。

画像認識の分野では転移学習と呼ばれる方法でモデルを作成することが多いです。転移学習は既存のモデルを利用してモデル作成を行うもので、今回も転移学習を用いてモデルを作成しますが、転移学習については多くの文献が存在しています。このため転移学習を用いたモデルの作成方法自体はリファレンスを示すことにし、その中でもあまり文献が発見できなかったMobilenet v2, Inception v4のモデルを転移学習で作成する方法を説明します。また、簡単にではありますが、Mobilenet v1/v2, Inception v3/v4の性能比較もしてみたいと思います。

なお、本記事ではTensorflow のモデル作成になります。

画像認識のニューラルネットワーク

現在、主に画像認識の精度の向上や処理速度の向上、軽量化(容量削減)を目的として、多くの画像認識用のニューラルネットワークが提案されています。
画像認識においては、畳み込みニューラルネットワーク(Convolutional Neural Networks, CNN)の画像認識精度が高いことが知られており、これを組み込んだ構成のニューラルネットワークが一般的に使われるようになっています。代表的なものは下表のようなものがあります。詳細は、良くまとまっている文献が多くあります*1ので、ここでの説明は割愛させていただきます。

ネットワーク ざっくり概要
VGG16/19 畳み込み層とプーリング層から構成されるシンプルなモデル。もともとは層の深さが性能にどのような影響を与えるのかを検証することが目的でシンプルな構成としていた。(たとえば16層のVGG16や19層のVGG19を作って検証するなど) しかし、現在でもそのシンプルさから多くの技術者がこれをベースのネットワークが好んで使われることがあるとのこと。
Inception Inception モジュールと呼ばれるネットワークを重ねてCNNを作っている。このモジュール内では入力を分岐して(層を横に広げて)異なるサイズのフィルターで畳込みを行い、最後にその結果を結合する構成になっている。
ResNet residualモジュールと呼ばれるスキップ構造を導入することで層の深いネットワークにおいて効率的な学習を可能にしている。
Mobilenet 空間方向とチャネル方向に独立に畳み込みを行うことで軽量化を図ったネットワーク。エッジデバイス用にはこのネットワークを用いるのが一般的。

今回は、この中でもInceptionとMobilenetの転移学習を行います。

転移学習

機械学習における学習とはネットワークの最適な重みを探索することです。より良い精度のモデルを作成するためには、多くの学習データ、時間、計算機リソースを用い、より良い重みを探していく必要があります。しかし、多くの場合はこれを行うための十分なデータが集められない、時間や計算機リソースに制約があるなどの問題が発生します。
転移学習は、すでに学習済みのモデルを用いることでより効率的にモデルを作成する方法です。似たような問題領域で学習したモデルは、似たような新しい問題領域にも適応できるであろうという仮説のもと、学習済みモデルの一部を変更して新しい問題を解くモデルを作成します。これにより一から学習を行うよりも少ない時間で精度の高いモデルを作ることができるようになります。「将棋の羽生さんはチェスもかなり強い!(参考)それと同じ!」というのと同じようなことです。・・・と、私は説明されたことがありますが、わかったような、わからないような・・・。とはいえ、転移学習自体は論文等でも多く言及されており、実績もあるものですので、そちらをご確認いただくといいかもしれません*2

ところで、転移学習はその方法で大きく二つにわけることができます(分類の仕方によって三つの場合もある)。
一つは、学習済みのモデルを特徴抽出器として用いて、最後の出力層を学習し直すもの。もう一つは、学習済みモデルの最後の出力層より手前のいくつかの層も学習し直すもの。後者はファインチューニングと呼ばれたりもします。これらの詳細は、使いどころや注意点など含めこのページにわかりやすく記載されていますので一読されると良いかと思います。今回は、前者の学習済みのモデルを特徴抽出器として用いる方で転移学習を行います。

転移学習実践

ここから、実際に転移学習を行っていきますが、はじめに示したとおり、ここはざっくりとした説明とリファレンスのみです。

おそらく、転移学習を行う一番簡単な方法は、Googleがcodelabで公開しているTensorFlow For Poetsを実践することだと思います。これにより、Mobilenet v1, Inception v3の転移学習を行うことができます。まずは騙されたと思って、やってみてください。おそらく1時間もかからずに新しいモデルを作成することができます。このチュートリアルでは、花を分類する例題になっていますが、ご自身で分類させたい画像データをお持ちでしたら、例題を真似て適当なフォルダ構成で画像ファイルを配置してください。そして、'python -m scripts.retrain'の'--image_dir'オプションに画像データのフォルダを指定してあげれば、あなただけの新しいモデルを作成することができます。

いかがでしょうか、できましたでしょうか?
もしこれが初めて作成したモデルでしたら、結構精度高く分類してくれるので驚くのではないかと思います。

では次に、このTensorFlow For Poetsを拡張して、Mobilenet v2, Inception V4の転移学習もできるようにしていきましょう。

tensorflow-for-poets2でMobilenet v2, Inception V4の転移学習

tensorflow-for-poets2は転移学習のためのチュートリアル用のスクリプトではありますが、大変高機能です。たとえば、学習データを増幅させるData Augmentationをサポートしていたり、転移学習を高速化するためのキャッシュ機構を持っていたりします。一方で、現時点で対応しているモデルはMobilenet v1とinception v3のみであり、長い間対応モデルの追加はされていません。このため、より新しい精度の高いモデルの転移学習には使うことができません。

そこで、ここではコードを少しハックして他のモデルも使えるようにしてみることにします。ただし、言うまでもないと思いますが、これはあくまでもチュートリアルスクリプトを実験的に改変しているものです。プロダクション用途に使用できるかは別途検証が必要になることにご注意ください。また、今回は2019/7/7時点で最新のtensorflow-for-poets2のソースを対象とします。

tensorflow-for-poets2をcloneすると、scriptsというフォルダが作成されます。今回はこの中の次のファイルを改変します。

  • retrain.py

retrain.pyの改変

retrain.pyは転移学習を行う処理が記載されたスクリプトです。
色々と難しい内容が含まれていますが、今回変更する部分はcreate_model_info関数です。
この中で if architecture == 'inception_v3'などの条件分岐が記載されているのが、各転移学習の転移元のモデルを定義する部分になっています。
このため、この条件分岐のところに使いたいモデルの情報を追記すれば良さそうです。
Mobilenet v1の定義部分

  elif architecture.startswith('mobilenet_'):
    parts = architecture.split('_')
    <略>
    input_mean = 127.5
    input_std = 127.5

の後ろに次に内容を追記します。

  elif architecture.startswith('inception_v4'):
    data_url = 'https://storage.googleapis.com/download.tensorflow.org/models/inception_v4_2016_09_09_frozen.pb.tar.gz'
    bottleneck_tensor_name = 'InceptionV4/Logits/Logits/BiasAdd:0'
    bottleneck_tensor_size = 1001
    input_width = 299
    input_height = 299
    input_depth = 3
    resized_input_tensor_name = 'input:0'
    model_file_name = 'inception_v4_2016_09_09_frozen.pb'
    input_mean = 128
    input_std = 128
  elif architecture.startswith('mobilenetv2'):
    data_url = 'https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz'
    bottleneck_tensor_name = 'MobilenetV2/Predictions/Reshape:0'
    bottleneck_tensor_size = 1001
    input_width = 224
    input_height = 224
    input_depth = 3
    resized_input_tensor_name = 'input:0'
    model_base_name = 'mobilenet_v2_1.4_224_frozen.pb'
    model_dir_name = './'
    model_file_name = os.path.join(model_dir_name, model_base_name)
    input_mean = 127.5
    input_std = 127.5  

転移学習の開始

スクリプトの変更が完了したら、早速転移学習を行ってみたいと思います。スクリプトの実行方法はオリジナルのtensorflow-for-poet2と同じです。必要なパラメータを書き換えてください。

Inception v4 の例は次のとおりです。

python3.6 -m scripts.retrain \
  --bottleneck_dir=bottlenecks_inc4 \
  --model_dir=models/inceptionv4 \
  --summaries_dir=output/training_summaries_inc4/ \
  --output_graph=output/retrained_graph_inc4.pb \
  --output_labels=output/retrained_labels_inc4.txt \
  --architecture=inception_v4 \
  --image_dir=DATASET/TRAIN \
  --how_many_training_steps=2000


Mobilenet v2の例は次のとおりです。

python3.6 -m scripts.retrain \
  --bottleneck_dir=bottlenecks_mnv2 \
  --model_dir=models/mobilenetv2 \
  --summaries_dir=output/training_summaries_mnv2/ \
  --output_graph=output/retrained_graph_mnv2.pb \
  --output_labels=output/retrained_labels_mnv2.txt \
  --architecture=mobilenetv2 \
  --image_dir=DATASET/TRAIN \
  --how_many_training_steps=2000

推論実行

転移学習が完了したら推論を実行してうまく学習できているかを確認します。こちらもスクリプトの実行方法はオリジナルのtensorflow-for-poet2と同じです。必要なパラメータを書き換えてください。

Inception v4 の例は次のとおりです。

python3.6 -m scripts.label_image \
  --graph=output/retrained_graph_inc4.pb \
  --labels=output/retrained_labels_inc4.txt \
  --image=DATASET/TEST/Abyssinian/Abyssinian_13.jpg \
  --input_layer=input \
  --output_layer=final_result \
  --input_width=299 \
  --input_height=299

Mobilenet v2の例は次のとおりです。

python3.6 -m scripts.label_image \
  --graph=output/retrained_graph_mnv2.pb \
  --labels=output/retrained_labels_mnv2.txt \
  --image=DATASET/TEST/Abyssinian/Abyssinian_13.jpg \
  --input_layer=input \
  --output_layer=final_result \
  --input_width=224 \
  --input_height=224

どうでしょうか、ご期待の推論がなされているのではないかと思います。

性能評価

さて、せっかく転移学習でMobilenet v2もInception v4のモデルも作れるようになりましたので、Mobilenet v1, Inception v3と性能比較してみます。
データセットはObject Detectionのデータセットとしてよく参照されるOxford petを使います。このデータセットには37カテゴリーのかわいいペットの画像が200枚ずつ格納されています。今回は、トレーニング用に180枚、テスト用に20枚という内訳で分割してトレーニングを行います。
f:id:Wok:20190707232607p:plain



また、各モデルの転移元のモデルは次のものを使っています。

モデル 転移元
Inception v3 http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz
Inception v4 https://storage.googleapis.com/download.tensorflow.org/models/inception_v4_2016_09_09_frozen.pb.tar.gz
Mobilenet v1 http://download.tensorflow.org/models/mobilenet_v1_1.0_224_frozen.tgz
Mobilenet v2 https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz

レーニングは2000エポックで統一しています。本当なら学習曲線で確認しながらが良いですが、今回は簡易評価なので手抜きをしています。

次の表が各モデルの性能評価の結果です。

モデル accuracy precision recall f1
Inception v3 0.93 0.94 0.93 0.93
Inception v4 0.92 0.93 0.92 0.92
Mobilenet v1 0.90 0.91 0.90 0.90
Mobilenet v2 0.94 0.94 0.94 0.94

※各値はmacroの値です。

今回はMobilenet v2がすべての値で成績がよく最も良いモデルになったようです。ただし、上記のとおり、チューニング等甘いところがありますのでご参考程度と考えてください。プロジェクトで使う場合は、実際にいくつかのモデルを作成してみて、どれがユースケースに適しているかを確認して使用するようにしてください。

今回の結果のConfusion Matrixもつけておきます。字が細かくてすみません。

f:id:Wok:20190708031249p:plain
f:id:Wok:20190708031257p:plain

f:id:Wok:20190708031151p:plain
f:id:Wok:20190708031312p:plain

最後に

Image Classificationに用いるモデル(Mobilenet v1/v2, Inception v3/v4)の作成方法についてご紹介しました。作成したMobilenetのモデルは、コンバータを用いてtensorflow liteやtensorflowjsのモデルに変換してエッジデバイス上でも使うことができるようになりますので、興味があれば是非チャレンジしてみてください。(まだ計画していませんが、エッジデバイス上で動かすまでについても投稿するかもしれません。)
今回は、前回に比べていくらか技術的な内容を盛り込めたかと思います。次回もこんな感じで記載していこうと思います。

*1:たとえば、名著として名高い「ゼロから作る Deep Learning(斎藤 康毅著)」など

*2:https://arxiv.org/abs/1411.1792

video要素の再生中のシーンを印刷する

こんにちは、エンジニアの藤野です。

HTMLへの動画の埋め込みにvideo要素が使われるようになって久しいですが、これをブラウザの機能を用いて印刷するとどのようになるかご存知でしょうか?

とあるお客様から「動画の再生中のシーンを印刷したい」という要望を受け、調査したところ、各ブラウザによって結果が異なることが分かりました。

本記事では各ブラウザの動作と、「再生中のシーンを印刷する」方法を紹介します。

各ブラウザでの動作

下記環境で動作を確認しました。

macOS Mojave 上の

Windows 10 上の

  • Edge

確認に用いたコードは下記のとおりです。

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<video controls id="v">
    <source src="v.mp4" type="video/mp4">
    Sorry, your browser doesn't support embedded videos.
</video>
</body>
</html>

v.mp4は、エッジデバイス上での画像認識で弊社岡田が投稿した「AndoroidでImage Classification」の動画ファイルです。

それでは各ブラウザでの動作を見ていきましょう。

Firefox

動画部分が真っ白になります。

ブラウザでの表示: Firefox ブラウザでの表示

印刷時の表示: Firefox 印刷時の表示

Chrome

動画部分が真っ白になります。

ブラウザでの表示: Chrome ブラウザでの表示

印刷時の表示: Chrome 印刷時の表示

ときどき表示できる場合もあったのですが、その条件は分かりませんでした。

Edge

実はEdgeでは再生中のシーンがそのまま印刷できます。 つまり、FirefoxChromeで同様の動作を実装すればよいことになります。

ブラウザでの表示: Edge ブラウザでの表示

印刷時の表示: Edge 印刷時の表示

実装の方針

  1. video要素をソースとしたcanvas要素を作成
  2. 画面と印刷時でvideo要素とcanvas要素の表示を切り替える

Canvasは要素の作成時にvideo要素をソースとすることができます。

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

これを用いることで、video要素の現在の再生中の画像をcanvasとして表示できます。 そして、画面と印刷時でvideo要素とcanvas要素の表示を切り替えることで、再生中のシーンを印刷します。

canvasへの表示

drawImageメソッドでcanvasを描画します。 また、beforeprint eventを使って、再生中のシーンが描画されるようにします。

window.addEventListener('beforeprint', drawCanvas);

function drawCanvas(){
    let video = document.getElementById("v");
    let canvas = document.getElementById("c");
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
}

表示の切り替え

メディアクエリを使って画面(screen)と印刷(print)の表示を切り替えます。 CSS@mediaルールを使うとシンプルに実装できます。

@media screen {
    video { display: block; }
    canvas { display: none; }
}
@media print {
    video { display: none; }
    canvas { display: block; }
}

試してみる

Firefox

ブラウザでの表示: Firefox ブラウザでの表示2

印刷時の表示: Firefox 印刷時の表示2

印刷した瞬間のシーンが表示できました。

Chrome

ブラウザでの表示: Chrome ブラウザでの表示2

印刷時の表示: Chrome 印刷時の表示2

印刷した瞬間のシーンが表示できました。

終わりに

無事、印刷時にvideo要素の表示を再現することが出来ました。 ブラウザでwebページを印刷したときの表示は、配置の調整はあるものの大体そのままという先入観があったので、今回は勉強になりました。

最終的に作成したコードは下記のとおりです。

<!DOCTYPE html>
<html>
<head>
    <style>
        @media screen {
            video { display: block; }
            canvas { display: none; }
        }
        @media print {
            video { display: none; }
            canvas { display: block; }
        }
    </style>
    <script type="text/javascript">
        window.addEventListener('beforeprint', drawCanvas);

        function drawCanvas(){
            let video = document.getElementById("v");
            let canvas = document.getElementById("c");
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
        }
    </script>
</head>
<body>
<video controls id="v">
    <source src="v.mp4" type="video/mp4">
</video>
<canvas id="c"></canvas>
</body>
</html>

本記事の作成にあたって、下記ページを参考にさせていただきました。

videoタグの動画をcanvasを使って表示する

エッジデバイス上での画像認識

みなさんはじめまして。2019年1月にフレクトへ入社した技術開発室の岡田です。 今回は自己紹介も兼ねて、私が担当しているエッジデバイス上での画像認識技術について、概要をご紹介したのちに作成したサンプルをいくつかご紹介したいと思います。

画像認識技術

近年の深層学習の盛り上がりに伴い、多種多様な画像認識技術が生まれてきています。 その中でも次の3つが代表的なものになるかと思います。

  • Image Classification (物体認識)
  • Object Detection (物体検出)
  • Semantic Segmentation

Image Classification とは画像のクラス分類を行う技術です。手書きの数字を認識する問題(MNIST)などが有名ですのでご存じの方は多いかと思います。 たとえば、手書き数字よりもう少し高度な例だと、次の画像を入力すると「ねこ」と判定してくれたりします。 f:id:Wok:20190602220740p:plain

Object Detectionは、画像内に写っている物体の種類と位置を特定する技術です。 先程のねこの画像を入力するとこのような結果を返してくれます。 f:id:Wok:20190602220626p:plain

社内勉強会ではNintendo Switch ゼルダの伝説に登場する「シーカーストーン」の「ウツシエ」と類似の技術(推測)と説明してみましたが、 なかなかピンと来なかったみたいです。残念。一応、参考までにURL貼っておきます。 シーカーストーン ウツシエ - Google 検索

Semantic Segmentationは、さらに細かくピクセル単位でクラス分類を行う技術です。 ふたたび先程のねこの画像を入力として使ってみますと、次のような結果が帰ってきます。見事にねこと背景をピクセル単位に分類して塗り分けてくれていますね。

f:id:Wok:20190602221555p:plain

その他、顔認識やポーズ(姿勢)検出など面白い技術があります。 また、画像認識技術から話が少しそれますが、深層学習により画像からノイズを除去したり、ぼやけた画像を鮮明にする、絵のスタイルを変換するなど、画像の加工や画像の生成を行う技術もあります。

次の例は、社内勉強会ネタとして弊社社長の黒川の写真をスタイル変換にかけてみたものです。あまりうまく行かなかったのですがせっかくなので載せておきます。 f:id:Wok:20190602200654p:plain

エッジデバイス上での実現例

さて、今回は深層学習を用いた画像認識技術の中で代表的なImage Classification, Object Detection, Semantic Segmentationについて、 エッジデバイス上での実現例をご紹介したいと思います。実験的な要素も含んだものとなっていますがご容赦ください。 なお、以下、全てエッジデバイスAndroidスマホ)上で動作させたものとなっています。

Image Classification on Edge Device (Android)

スマホのカメラ画像を分析し、何が写っているかをリアルタイムに判定します。 この動画では、あひるといぬを見分けています。画面上部に判定結果とその確からしさ(1.0が最大)が表示されています。


Andoroidでの画像認識

Object Detection on Edge Device (Android)

スマホのカメラ画像を分析し、何がどこに写っているかをリアルタイムに判定します。 この動画では、あひるといぬがどこに写っているかを判定し、赤枠で囲みます。


AndoroidでObject Detection

Semantic Segmentation Object Detection on Edge Device (Android)

スマホのカメラ画像を分析し、何がどこに写っているかをリアルタイムにピクセル単位で判定します。 この動画では、あひるといぬをピクセル単位で判定して、領域をオーバーレイで表示しています。 ただし、精度も低く処理ももたついていますね。これについてはもっと改良が必要です。


AndroidでSemantic Segmentation

(応用例) Auto Shutter

被写体が特定の条件を満たす写り方をしたときに自動的に撮影する機能です。 この動画の例では、あひるが正面に向いたことを検出して自動的に撮影します。


画像認識技術を活用したオートシャッター機能

最後に

今回は、深層学習による画像認識技術の概要とエッジデバイスでの実現例をご紹介しました。 この記事を通じて、私を含めFlectの活動(の一部)を知っていただけたら幸いです。 次回はもう少し技術的に踏み込んだ内容をご紹介したいと思います。

多量のリクエストを送信するには

エンジニアの佐藤です。こんにちは。今回は、とある技術検証を通じて筆者が経験したドタバタについて書かせていただきたいと思います。

きっかけ

先月だったか、「毎秒X万のHTTPSリクエストを遅延なく処理するための実装要件を教えてほしい。」という要望が筆者に寄せられました。世間のメジャーエンドポイントがどのくらいのアクセスを想定しているのかわかりませんが、X万というのは筆者には未経験のゾーンで、試してみるしかないと思いました。

ちなみにその時点で筆者が過去に経験していた、最も多数のリクエストを処理するエンドポイントは、Heroku(https://www.heroku.com/)で実装しました。難しいことはしていません。Dynoをぐぐぐと増やしていけば、あっさり目標要件を満たしました。今回もHerokuでやったらどうかと答えたのですが、AWS指定とのこと。筆者はロードバランサとECSで検証サイトを作成しました。これを外から多量のリクエストを発信して検証すればいいと思いました。

心配事

とは言え、そんなに素直に行かないだろうなとも思っていました。心配したのは以下の2点です。

  1. そもそも、狙ったリクエスト数を発信できるか。
  2. エンドツーエンドで計測できるか。

一般的にサーバーサイドは、どんな実装であれ、可能な限り高い効率でリクエストを処理することに最適化されています。効率の悪いサーバーなど、誰も欲しがらないからです。しかしクライアント側もそうとは限りません。ブラウザであれアプリであれ、1箇所から多量にリクエストが発信される実用上のシナリオというのは存在しません。(もしそうなら、設計がまちがっているのです。)需要が無いということは、いざやろうとすると、仕様だけ見ていてはわからない落とし穴がある可能性があるということです。

もう一つの「計測できるか」ですが、単純にサーバーサイドでカウントして「毎秒X万回達成!」とやったとしても、それだけでは期待したユーザー体験を検証したことにはなりません。ひょっとしたらその瞬間、個々のリクエストはあり得ないぐらい待たされているかもしれないからです。となるとクライアント側にもそれなりの計測機能を実装して数字を収集しなければなりません。すぐにビッグデータ的スケールになってしまいます。

備え

多数のリクエストを送信するクライアント群をどうやって構成するか、また、個々のリクエストが要求を満たすことをどうやって計測するか。筆者のアイディアは以下のようなものでした。

  1. 非同期的にリクエストする。
  2. 時間的に分散させる。
  3. 計測結果を一定時間メモリにプールし、まとめてレポートする。

1. 非同期的にリクエストする

今回はHTTPSリクエストでしたので、リクエストを送信し、レスポンスを検証して完了です。つまり受信待機があるわけです。受信待機があるということは、直列的にリクエスト処理をつなげては目標を達成することが難しいということでしょう。仮にレスポンス検証まで0.01秒だったら、毎秒100回で頭打ちです。「スレッド処理」というささやきが聞こえてきますが、やたらに多数のスレッドを上げるわけにも行きません。スレッド切り替えの負荷が相対的に高まり、これまた頭打ちになるからです。なのでプログラム処理上は、レスポンスを待機することなくリクエストを投げられる仕様にしなければなりません。

筆者が今回使ったのは、Apache HTTP ComponentsのHttpAsyncClient(https://hc.apache.org/httpcomponents-asyncclient-4.1.x/index.html)でした。このライブラリではIOリアクターと呼ばれるスレッドプールでリクエストを処理します。ここへ、処理負荷とネットワーク遅延を勘案し、今回は論理コアの10倍のスレッドを設定します。また、タイムアウトも忘れずに設定します。(拙稿「アウトバウンドリクエストはAWS NAT Gatewayに握りつぶされていた」http://cloud.flect.co.jp/entry/2018/08/24/130542 参照)

IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
    .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 10)
    .setConnectTimeout(5 * 1000)
    .setSoTimeout(5 * 1000)
    .build();
PoolingNHttpClientConnectionManager connManager =
    new PoolingNHttpClientConnectionManager(ioReactor, ...
CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
            .setConnectionManager(connManager)
            .build();

// 上記の初期化が終わったhttpClientに対し、以下繰り返す。
HttpGet hg = new HttpGet(url);
// サーバーの負荷試験なので、キャッシュは無効に
hg.setHeader("Cache-Control", "no-cache");
httpClient.execute(hg, new FutureCallback<HttpResponse>(){
    public void completed(HttpResponse hRes) {
        try {
            // ここへ完了処理を書く
        }
        catch (Exception ex) {...}
    } // end of completed

これでレスポンスを待たずに、設定個数のリクエストを連続的に送信できる仕掛けができました。

2. 時間的に分散させる

非同期化によっていくらでも詰め込めるからといって、一気に多数のリクエストを送信するのは良くないと思います。現実世界ではX万台のクライアントをエミュレートしているのですから、揃うわけがないのです。今回は0.1秒ごとに、全体の1/10のリクエストを送信し、さらにこのインスタンスを複数個起動することにしました。

3. 計測結果を一定時間メモリにプールし、まとめてレポートする

次に計測ですが、基本的な数値として、何回リクエストして、いくつレスポンスを受け取ったのか、また、遅延時間(注:ここでの「遅延時間」は、いわゆるレスポンスタイムではなく、レスポンスボディに含まれる時刻とレスポンス時点のクライアント時刻の差分。込み入った話になりますので、詳細は割愛します。)の平均はどれだけだったのかを、5秒に1回計測します。また、別途全てのリクエストの遅延時間を記録し、これも5秒ごとにgzip圧縮してGoogle Cloud Storageに保存しました。

いざテストしてみると。。。

一通りプログラムが動作したところで、いよいよお待ちかねのパフォーマンステストです。起動パラメターを変化させて毎秒あたりのリクエストを増やしていきました。ところが、リクエスト回数が毎秒20回を超えたあたりで予想外の事象が発生しました。

リクエスト送信個数は増えるのに、レスポンス受信個数は増えなくなったのです。

何かのボトルネックにあたったようでした。他のメトリクスも確認したところ、「CPU利用率は低いまま」「メモリ使用量がわずかずつ増えている」の2点が確認されました。サーバー側はどうかというと、依然として涼しい顔。つまり受け取ったリクエストは楽勝で返していました。これらを総合すると、 クライアント側で、何らかの理由で送信リクエストが実際にネットワークデータ送信されることなく積み上がっている、と考えられます。 いったいなぜこういうことになってしまったのでしょうか。

方々、疑う

筆者が最初に考えたのは、「インスタンスあたりの時間あたりの送信パケット上限に到達したのでは?」ということでした。冒頭に書いた通り、一つのインスタンスから一つのエンドポイントへ毎秒20回HTTPリクエストが送信されるなど、普通は無いわけです。強いて言うなら、DOSアタックなどの悪事ということになります。何らかの予防線があってもおかしくありません。また、過去にはホスティング業者も認識していなかった制約を踏んだ記憶もあります。(拙稿「AWS EC2 "悪い" EIPにご用心」http://blog.flect.co.jp/cloud/2015/11/aws-ec2-eip-c3c5.html)こういう暗黙の制約があってもおかしくありません。

それならばと、インスタンス数を50まで増やしてみました。すると合計リクエスト回数は1,000まで上昇したものの、やはりそこで頭打ちとなりました。予防線の存在を確信した筆者でしたが、目標リクエスト数X万のためにはいったいどれだけのインスタンスが要ることかと、半分諦めの心境でした。

GCPでも同じ上限に

しかし、、、いくらググっても、アウトバウンドリクエストのインスタンスあたりの上限というマニュアルも、ブログも、出てきません。さらに、筆者がかつてUDPプロトコルで似たような実験をした時の記憶から考えると、低すぎるリミットに思えました。

筆者は思い切って、クライアントの実装環境をGCPへと変えてみました。(GCPを選んだ理由は特になく、使い慣れていただけです。)すると、、、やはりインスタンスあたり毎秒20リクエストで頭打ちでした。いくら何でも、2つの環境でぴったり同じというのはおかしいような気がします。

原因はライブラリのリミット

次に筆者が疑ったのは、使用したライブラリに設定された制限です。冒頭に書いた通り、サーバー側であればパフォーマンスは高度に最適化されている一方、クライアントから大量リクエストするシナリオは世間的には稀。何らかの足枷があるのではないかと思ったのです。結果として、これが原因でした。

たどり着いた以下のAapache HTTP Componentsオフィシャルページには、次のように書かれていました。 https://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d5e393

Per default this implementation will create no more than 2 concurrent connections per given route and no more 20 connections in total.

それならばと以下のように設定すると、リクエスト数が頭打ちになる現象は解消されました。

PoolingNHttpClientConnectionManager connManager =
    new PoolingNHttpClientConnectionManager(ioReactor, ...
connManager.setMaxTotal(Integer.MAX_VALUE);
connManager.setDefaultMaxPerRoute(Integer.MAX_VALUE);

これにて無事、目標とする毎秒X万件のHTTPSリクエストが実行でき、必要となるサーバー要件が確認されました。

本当に正しく多数のリクエストを送信していたのか

前述の通り、今回の検証では全てのリクエストについて遅延時間を計測しています。毎秒X万リクエストの全てですから、検証時間中のリクエストの合計数はかなりの数です。それらの遅延時間の分布はどうなっているのでしょうか。Google Cloud Storageに保存されている記録をBigQueryにインポートして調べてみましょう。詳細は割愛しますが、前述の通りここでいう遅延時間とは、レスポンス直後のシステム時刻とレスポンスボディに書かれた時刻の差分のことで、もしクライアントサイドからのリクエスト時刻の分布が時間的に偏りなく一様ならば、遅延時間も0秒から3秒まで均一な分布になると予想されていました。

予備調査によると遅延時間の最小値は50ミリ秒、最大値は3650ミリ秒でした。そこで遅延時間を0 ~ 100, 100 ~ 200, ..., 3600 ~ 3700のスロットで分け、それぞれについてスロット内での平均と標準偏差を計算してみました。

結果は下記のグラフの通り、両エッジのわずかなサンプルを除くと分布には何の偏りも見られませんでした。スロットの大きさを10ミリ秒にもしてみましたが、傾向は同じでした。

f:id:masashi-sato-flect:20190430123935p:plain
遅延時間の分布

検証用のクライアントプログラムは、目論見通り、時間的に一様に、かつ緻密に、リクエストを発信してくれたようです。

最後までお読みくださり、ありがとうございました。

Alexaスキルのデザイン・開発・公開

エンジニアのオクダです。こんにちは。今回のテーマは「Alexaスキルのデザイン・開発・公開」です。

今回お話する内容は

  • Amazon Alexaと応用開発

  • スキル「全国の郵便番号検索」

  • スキル「西暦・元号変換」

  • スキル「よく当たるタロット占い」

  • 審査と公開

です。

みなさんはスマートスピーカーに話しかけてみたことはありますか?Alexa (アレクサ)はアマゾンエコーの実装技術の名前です。最近は音声認識技術が向上し、応用開発が現実的になってきました。

Amazon Alexaと応用開発

Alexaは、以下のような仕掛けになっています。

Alexaの概要図
Alexaの概要図

応用開発の全体的な仕掛け

Alexaデバイスの一つであるEcho Dotが受信した音声は、アマゾンのAlexaに送信されます。ここで音声から文字への変換と文法解析が行われ、AWS Lambdaなどの機能を呼び出すというのが基本的な流れです。 最初に決めなければならないのがinvocationNameです。

ウェイクワードとも呼ばれ、Alexaを自分のスキルに向かすための言葉となります。注意しなければならないのは、 英数字は使用できないことです(漢数字はOK)。また もう既に登録されているinvocationNameは使用できません。 認識されにくい言葉も使用できない(弊社名「フレクト」はfreakedと誤認される)。さらに、 助詞「に」「は」を含むinvocationNameも使用できません。

invocationNameが検知されたら、以後ユーザーが話した内容がコールバックされてきますが、そのままテキストで通知されるわけではありません。事前に拾いたい文書表現をIntentとSlotという形で登録しておき、これに該当するものだけが処理対象になります。図示すると以下のようになります。

音声からテキスト変換の流れ

この図ではPostCodeSearchという名前のIntentが定義され、「の郵便番号を」の周辺に4つの{}書きされたSlotが定義されています。Alexaはこの文法構造に該当する発話を捉え、Lambda関数をコールバックしてきます。

なお、intent名に使用できるのは英字大文字小文字とアンダースコアのみで、数字は使用できません。

Intent Slotsの作成

Alexaでいうslotとは、Synonym(同義語)を含んだ単語群の総称です。カスタムslotだけでなく、標準slotも利用可能です。他にも「日付」を表すAMAZON.DATE、「数字」を表すAMAZON.NUMBERなどがあります。

なお、intent同様、slot名に使用できるのは英字大文字小文字とアンダースコアのみで、数字は使用できません。intent名との重複も許可されていません。

ValuesとSynonymの定義

Slotに含まれる実単語をvaluesと呼びます。ひとつのslotに複数のvalueを指定することができます。valuesには、Synonym(同義語)も登録出来ます。

Lambdaの実装

Lambdaの実装の前に、Alexa Developer ConsoleのEndpointをLambdaに登録する必要があります。Lambdaのリソース名をAlexa Developer Consoleに登録する必要もあります。 相互のEndpointを登録するとAlexa SkillからLambdaのmain handlerである、lambda_handlerにeventが送られてきます。 そのeventにはsession(その会話中の情報を保持、明示的に保持しなければ空)、context(System/device情報、Viewport情報、Geolocation情報(位置情報を許可))、request(request type情報やintent情報)が含まれています。

Alexa Skillから送られてきたevent['request']['intent']['name']やevent['request']['intent']['slots']['slot name']['value']の値によって、Alexaにspeech outさせたい言葉をtextで指定します。 2つ前の会話内容をsessionに登録したり、device情報やGeolocation情報を使用して、speech outさせる言葉を決定することも可能です。

次に具体的にスキルの開発方法について説明します。

スキル「全国の郵便番号検索」

(https://www.amazon.co.jp/dp/B07PBXH5Y9)

日本郵政が公開している住所と郵便番号の対応表を使って、住所に対応する郵便番号や、その逆を答えてくれるスキルです。

Lambda関数からS3にアクセスする必要がありますので、AWSLambdaBasicExecutionRoleとAmazonS3FullAccessをLambda関数のRoleとして設定します。

Lambda関数内の処理では、intentの特徴より、住所から郵便番号を読み上げるか、郵便番号から住所を読み上げるかを決定します。

スキル「西暦・元号変換」

(https://www.amazon.co.jp/dp/B07PYQ3Z94)

西暦から元号を、または元号から西暦を答えてくれるスキルです。日本人のみなさんなら、特に公文書記述でよく必要となる暦の変換です。 このスキルを開発している途中で、新元号「令和」が発表されました。Alexaが事前に知るはずもない、完全に新しい単語ということですが、このような場合はSlotにValueを登録することで認識を改善することができます。 f:id:flect170:20190421151149p:plain

例えば、Slot Valueに「令和」が登録されていない場合、Alexaは既知単語のどれかに当てはめようとし、失敗します。f:id:flect170:20190421151343p:plain

このような場合、Slot Valueに「令和」を登録することで、このValueへと単語認識を誘導することができます。 f:id:flect170:20190421151501p:plain このノウハウは活用シーンがいろいろあるのではないでしょうか。

スキル「よく当たるタロット占い」

(https://www.amazon.co.jp/dp/B07PPN25MT)

このスキルでタロット占いをお願いすると、本日のタロットカードを1枚引いてくれます。

画像付きEchoは何が違うか?

Echo ShowやEcho Spotなど、Amazon製のディスプレイ付きスマートスピーカーは、音声に加えて映像を表示させることができます。(ただし、ディスプレイがあってもiPhoneAndroidには画像を表示することが出来ません。) 画像を表示させるには「alexa presentation language」を使用します。

画像対応デバイスの認識

f:id:flect170:20190421151828p:plain
画像対応デバイスの認識

画像非対応デバイスEcho Dotの場合、Echo Dotからイベントが送られ、そのコンテキストのsupportedInterfacesが空になっています。 画像対応デバイスEcho Spotの場合、Echo Spotからイベントが送られ、そのコンテキストのsupportedInterfacesにAlexa.Presentation.APL属性が含まれています。

f:id:flect170:20190421152022p:plain
画像の出し方

画像の出し方は、画像対応デバイスであると判断した後、Lambdaが本文で表示させる内容をAmazon Alexaに送信します。それと同時にS3に登録されたimageのパスも送信され、S3に保存された画像をAlexaに送信し、Echo Spotで表示するという仕掛けになっています。この場合はAlexaに通知するパスが無認証でアクセスできるように配慮する必要があります。

審査と公開

開発が完了したスキルを公開するには、アマゾンによる審査を受けなければなりません。

ここでは筆者が経験した代表的な指摘事項とその対応方法について記載します。

指摘事項その1。スキルの呼び出し名

呼び出し名が一般的である。呼び出し名がAmazonが指定するフォーマットに合っていない、スキルの呼び出し名に、助詞や接続詞を使用できないなどがある。

対応方法

呼び出し名をAmazonが指定するフォーマットに変更する。

指摘事項その2。スキルの説明

スキルの説明には、ユーザーがスキルを使用する際に十分に理解できるように説明しなければならない。 Alexa標準機能と混同するような説明を記載してはいけないなどがある。

対応方法

Amazonからの指示通りに、スキルの説明文を修正。

指摘事項その3。サンプルフレーズ

サンプルフレーズ1番目には「アレクサ、...を開いて」にしなければならない。 言い回しの異なるサンプル発話が不足している。 サンプル発話に記載したフレーズは必ずIntentに登録しなければならない。などがあります。

対応方法

サンプルフレーズ1番目を「アレクサ、...を開いて」に変更したり、 Amazonの指示通りに、サンプルフレーズに使用している言い回しをSlotに追加したりして対応した。

指摘事項その4。Slot関連

Slotに使用できない記号、Slotに使用できるが正しく認識できない記号などがあります。 記号「(」、「)」や「〜」や「−」をSlotに使用することはできません。 Slotにスペースを使用することはでき出来ますが、実際のSpeech to Textではスペースは文字として変換されません。

対応方法

Intentから記号を全て削除しました。

指摘事項その5。標準Intent関連

AMAZON.CancelIntentには、開発者には見えない既存のSlotが含まれています。そのSlotをAMAZON.StopIntentに登録してはいけません。 AMAZON.DATEをSlot typeとして使用している状態で、特定の言葉を話すとスキルがエラーを返します。

対応方法

同じSlot valueが複数の標準Intentに使用されないようにする。 AMAZON.DATEとして認識して欲しくない場合は、別途カスタムSlotを定義したり、スキルがエラーを返さないようにLambdaを修正しなければならない。

指摘事項その6。終了処理

スキルがタスクを完了した後、ユーザーへのプロンプトが提示されていないにもかかわらずセッションが開いたままになっている。 ユーザーが2回連続無言だとエラーになる。などがあります。

対応方法

スキルがリクエストを完了した後、ユーザーの入力を求めるプロンプトを提示していない場合はセッションをクローズします。 Lambdaでイベントのリクエストタイプが"SessionEndedRequest"の時、終了処理を入れてあげます。

指摘事項その7。プライバシー

スキルで、ユーザーの生年月日など個人の情報を収集する場合、法的に適切なプライバシーポリシーを提示しなければなりません。

対応方法

「誕生日を教えて下さい」から「調べたい年月日を教えて下さい」に変更して対応しました。

指摘事項その8。著作権

Amazonにとって知的財産権の保護は非常に重要です。コンテンツの内容を使用する権利を持っていることを示す文書を提示してください。

対応方法その1。

コンテンツ変更により、著作物を使用を諦めました。

対応方法その2。

著作物の商用利用が可能な場合、著作元URLをスキルの詳細説明欄に貼り付けました。

感想

今回、Amazon Alexaを使用して、Alexaスキルの開発・公開を行いましたが、開発する苦労を改めて実感しました。 しかし、音声アシスト分野の利用はこれから需要が見込まれるので、引き続き、調査・開発に取り組んでいきたいと考えています。

Heroku Metrics について

Heroku Adevent Calendar 2018 の18日目の記事です。17日目はsilverskyvicto さんの「Heroku のアドオンを自作する方法を見てみた」でした。

なお、15日目は弊社代表取締役 黒川 幸治 による寄稿でした。 Re:Invent から帰ってきてすぐにHerokuの記事書いて…となかなか精力的なエンジニアっぽい感じですが、肩書は社長です。

Heroku の強み

Heroku を使いはじめてから気づく魅力の一つに、標準で用意されている機能の豊富さが挙げられます。たとえば Postgres/Redisアドオンや Github とのインテグレーション、Pipelines/Heroku CIでのCI/CD、CLIAPIなどなど…

マネージドという言葉でIaaSプラットフォームもたくさんの機能を複雑な設定なく利用できるようになりましたが、開発者がプログラミングに集中する、それを実現するための万全な体制をプラットフォームが引き受ける。言うなれば引き算の美学は、まさしく Heroku が作り上げてきたものです。Heroku Metrics も同じ考え方の元で用意された標準機能の一つと考えられます。

今回紹介するHeroku Metrics は、ほとんど導入コストなしに、アプリケーションのパフォーマンス監視ないし必要に応じた通知を行える機能です。DynoやAddonのPlanを柔軟に変えられるためか、Metrics自体の機能をそのまま使わずにカットオーバーを目指しているプロジェクトを見かけることがあったので、どういうことができるんだろう・ひとまず使ってみましょうという思いから今回の記事を作成しました。

Heroku Metrics でできること

1. メトリクス監視

f:id:shns:20181216161802p:plain

アプリケーションの 「Overview」の左上部にある「Metrics」が Heroku Metrics によるモニタリングレポート。イメージとしては Zabbix なり CloudWatchのダッシュボードに近いと思いますが、エージェントを設定したりダッシュボード自体を作る必要はなし。いくつかの手順を踏むだけで使えるようになります。

以下は、いくつかのダッシュボードの例です。管理者用サイト、マイページのデモサイト、ショッピングサイトと用途はそれぞれ。develop, staging, production の違いだったり、JVM , PHP, Ruby など言語の違いによってもある程度これらの数値は変わってきます。いずれにしても、アプリケーション開発者はほとんど何も実装や設定をせずに使い始めることができます。

f:id:shns:20181217105422p:plain f:id:shns:20181217105438p:plain f:id:shns:20181217105452p:plain

メトリクス

以下のメトリクスが見られます。はじめは Events あたりで Platform のイベントと、Dyno restart などを見ながら、それ以外のメトリクスの動き方をじっくり眺めるのが良いんじゃないかと思います。

  • Events
  • Memory Usage
  • Response Time
  • Throughput
  • Dyno Load

JVMメトリクス

アプリケーションのRuntime によって追加のメトリクスも用意されていて、JVM だと以下も追加で閲覧可能。

  • Heap Memory Usage
  • Non-Heap Memory Usage
  • Aggregate Time Spent in Garbage Collection
  • Aggregate Garbage Collections

設定の仕方

前述の通り、設定自体はほとんどすることがなく、 Dynoが動いていればダッシュボードに Metrics の情報が表示されるようになります。

対象のDynoの選択

f:id:shns:20181216170741p:plain

上の選択リストで対象のDynoを選択。

メトリクス期間の選択

f:id:shns:20181216170730p:plain

こちらの選択リストでメトリクスの期間を選択します。選べるのは「過去2時間/過去24時間/24~48時間前/48時間前〜72時間前/過去3日間/過去7日間」のいずれか。Cloud Watch が15ヶ月くらいだったと思いますが、稼働中だったりサービス稼動目標があるアプリなんかだとよほど遡及せざるを得ない理由が無い限り、そこまでの期間は必要ないですね。

メトリクスの設定

f:id:shns:20181216170327p:plain

歯車アイコンからメトリクスの設定が可能で、ダッシュボードの見た目だったりRuntimeごとのメトリクスをここで有効化します。GAになっているJVM以外にも、2018/12時点で Public Beta の Ruby / Node.js。Dev Center の Getting Started 通り、適宜 push して様子を見ていればいつの間にかメトリクスに上がってくるようになります。

アラート(閾値・通知方法)の設定

「Configure Alerts」 から、Response Time と Failed Requestsについてはしきい値を設定して、超えた場合にメール通知を行うことも可能です。

f:id:shns:20181216170313p:plain

AWS あたりだと、一度 SNS に渡してから LambdaなりSQSなりに繋げることが多いと思いますが、こちらは「アプリのMember全員に送信」「5通までの任意のメールアドレス」あとは PagerDuty に一度渡してポリシー適用というのが可能です。投げ先としては運用チームのメーリングリストなり、SlackのEmailAppを設定済みの監視Channelなりを選ぶのがシンプルだと思います。

ちょっとだけ注意したいのは、Addonをいくつか使っていると通知系が渋滞気味になるので、ちゃんと通知系の設定の粒を揃えておくべきということです。たとえばNewRelic/PagerDuty/Bugsnag/PapertrailとHerokuMetricsでそれぞれ通知の系があるのであれば、通知やサマリー送付などの頻度・条件を揃えておくか、ひと目でわかるように一覧化しておきましょう。時間差があると障害対応時に錯綜することがあるので。

ユースケース

1. 本番の稼動監視

アプリケーションの運用保守用途に使います。 PagerDutyやPingdom、Bugsnagと組み合わせることもありますが、基本的には Alert 設定までは必須で。設定先としては、上に挙げたような、監視用MLへの通知、Slackへの通知。

Alert の時間間隔は「1分/5分/10分」から選べますが、連携システムがある場合はそちらに合わせることが多いです。AWS側が詳細モニタリングを有効化しているなら、1分に合わせたり、など。

上に書いたメトリクス一覧のうち、見ることが圧倒的に多いのは Events と Throughput 。 Memory Usage とかは本番稼働後というよりは設計/実装時のサイジングの検証時に見ることが多いです。まず Events で事象や時系列を確認したら、 Throughput をみながら、問題箇所を特定していきます。

ポイントとしては、プラットフォーム/アーキテクチャレイヤーでの解決が可能かどうかの切り分けをすること。

もしDynoやAddonのPlanを変えてもエラーが出続ける場合は、ソースコードのロジックに何か問題がある場合があります。コードレベルの問題特定だったら、 NewRelic に任せましょう。…というと敷居が上がりそうなのですが、Heroku標準でここまでできている部分がほとんどで学習コストがほぼほぼゼロと言って良いものなのと、Heroku Metrics と NewRelic で役割分担ができるので、そこまで大変ではないと思います。RailsだとScout使うと良いって話もあるのですが、HerokuMetricsとNewRelicで慣れてしまっているならそれでも良いし、bullet とか特定用途で結構Gem入れちゃってたりすることもあると思うので、うまい棲み分けが正直知りたいところ。

2. 非機能系テスト用

パフォーマンステストと監視テスト、障害復旧テストあたりで使います。stagingまたはtest用環境を用意して、本番想定の状態でテストを行い、しきい値が適切か、きちんとアラートが飛ぶか、または通知後の復旧手順が正しく踏めたかを検証する際に利用します。

なお、必須ではないと思います。ミニマムプロジェクトや検証用途では必ずしも非機能周りはスコープになるとは限らないし、Heroku の拡張性や柔軟性の高さから、順次考えていけばよい、その時に設定すれば良い、という考え方もできるので。ただ、Dynoのコレクションについてはメモリやネットワーク、GPUなり特化型のインスタンスが用意されているというよりは事前定義済みのプランからの選択になるので、スケールアップ/ダウンの勘どころは IaaS と比べて少し変わってくるかなと思ってます。

最後に

cloud.flect.co.jp

  • 早く容易に導入ができる(インフラ基盤、ミドルウェアまで提供されているため)
  • 柔軟かつ早いスケーラビリティが実現できる
  • 運用が容易にできる
  • Salesforceとの親和性がよい 等々

Heroku Metrics は縁の下の力持ちな存在として、Herokuでのアプリケーション運用を支える重要なサービスであり、黒川が書いたようにHerokuを使う価値をわかりやすく体現した存在であると思います。簡単に使い始められるので、ぜひ使ってみてください。