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

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

「Einsteinのアドバイス」をカスタム項目に実装するには

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

Dreamforce'19のキーノートビデオを見ていると、Einstein関連機能がユーザにアドバイスする場面がしばしば登場します。例えば以下の動画では、Einsteinのおなじみの挿絵と共に、商談の見込み度数とその算定根拠がアドバイスされます。

https://www.youtube.com/watch?v=emxF6yuC9Q8&t=618

これらの機能は、Salesforce標準オブジェクトの結合関係に基づいて作成された機械学習モデルから導き出されたものと考えられます。標準オブジェクトとはつまり取引先(Account)とか商談(Opportunity)のことで、それらの関係は、例えば以下のようになっています。

https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_erd_majors.htm

Sales Cloudやその他の製品ではこれらの機械学習モデルが最初から有効になっており、入力されたデータを観察しているわけです。

一方で、カスタムオブジェクトや、標準オブジェクトに追加されたカスタム項目については、このような機能は実装されていません。しかしEinstein Discoveryを設定することで、開発者が意図した「Einsteinのアドバイス」を表示させることができます。今回はこの手順についてお話しさせていただきたいと思います。

Einstein Discoveryとは

Einstein Discoveryは、Einstein Analyticsの製品機能の一部です。本稿執筆時点では、以下のEinstein Analytics価格表の中の機能リストにEinstein Discoveryの名称があります。

https://www.salesforce.com/editions-pricing/einstein-analytics/

その機能はデータ分析と予測モデルの作成適用に大きく分類できますが、今回のお話では予測モデルの作成適用が主な対象となります。

ところでその「予測モデル」ですが、Einstein Discoveryでサポートしている予測モデルの種類は、現在時点では以下の2つです。(https://help.salesforce.com/articleView?id=bi_edd_model_about.htm)

  • Classification Use Case: logistic regression
  • Numeric Use Case: linear regression

え?たった2つ?と思われたかもしれません。この道の有名ライブラリScikit-learnのlinear_modelモジュールなどと比較すると少ないように思いますが、Einstein Discoveryは、最も基本的なミニマム構成を、ユーザにわかりやすく機能実装しているということなのでしょう。

シナリオを決める

ところで、そもそもカスタムオブジェクトのアドバイスって、何でしょうか?
いえいえ、予め決まっている話は何もありません。まずはこのシナリオから考えなければなりません。

動作検証ということで、以下のような、これ以上ない簡単なシナリオを考えてみます。

  • 入力値は Category ただひとつで、データ型は文字列で、値は A または B のみ。
  • 出力値はこれまたただひとつ SpeculatedValue で、データ型は数値。
  • 「Categoryが A だったら、SpeculatedValue は 4.0 付近に分布し、Categoryが B だったら、SpeculatedValue は 1.0 付近に分布する」という真理を仮に設定する。
  • Category が入力されたときに、その入力値が A だったら約 4.0 を、入力値が B だったら約 1.0 を予測値としてEinsteinにアドバイスしてもらう。

本来であれば、この「真理」は誰にもわからない秘密の法則で、多数のデータを用いて発見していくものですが、ここでははじめに設定します。なんとも回りくどい話で、かつミニマムですので全く味気がありません。しかしここまで自明なシナリオなら、意図したアドバイス(=予測結果)になっているかどうか簡単に確かめられるでしょう。ちょうどテストコードを書いて本来の機能コードの機能を確認していくような作業でしょうか。

レーニングデータを作る

次に、仮設の真理に基づくデータを作成します。

Einstein Discoveryでは最低400件のデータを必要とします。(https://help.salesforce.com/articleView?id=bi_edd_limits.htm)

今回は以下のようなデータを作成してみました。

date        category  value
2018-01-01  A         3.5435852645574193
2018-01-02  B         1.275152805839512
2018-01-03  A         5.40014361858529
2018-01-04  B         0.6804527791760495
2018-01-05  A         3.212071974642821
2018-01-06  B         0.02215664060029765
2018-01-07  A         2.6168577420923445
2018-01-08  B         1.5697355255078034
2018-01-09  A         3.738544793192011
(以下同様に、2年分、730件)

要するに category が A だったら value が 4.0 を中心に、B だったら 1.0 を中心に分布しているだけです。プロットすると以下のようになりました。まさに仮設した真理から作り出されたデータです。Einstein Discoveryの予測モデル作成機能はきっと容易にこの真理を見つけ出してくれるはずです。

f:id:masashi-sato-flect:20200109162618p:plain

Einstein Analyticsデータセットを作成

次にEinstein Analyticsにこのデータをインポートします。

Einstein Analyticsには無償の開発者エディションがありますので、今回はこれを利用しました。

https://developer.salesforce.com/promotions/orgs/analytics-de

ログイン後、以下のTrailheadの手順にならって作成したCSVをインポートします。

予測モデルの作成

ここからインポートしたデータをもとに予測モデルを作っていくわけですが、はじめにインポートしたデータセットの「Create Story」を選択します。

f:id:masashi-sato-flect:20200109162709p:plain

すると設定項目をいろいろ問い合わせる一連のダイアログが表示されます。

最初は Start an Einstein Discovery Story ですが、ここは以下のように設定します。

f:id:masashi-sato-flect:20200109162729p:plain

Story Goalと言われても困ってしまいますね。これはEinstein Discovery固有の概念と考えたほうが良いと思います。予測モデルの期待動作は言わずとしれた予測で、これ自体の評価は「正確かどうか」に尽きるでしょう。しかしここで言うStory Goalは、予測対象の数値を「どうしたいのか」という、予測モデル作成の背景に関する質問なのです。売上や利益なら「最大化したい」とか、故障率や返品率なら「最小化したい」という意図があると思いますが、その意図を入力します。

なぜこういう設定が必要なのでしょうか?それはEinstein Discoveryが「現状成果の構成要因は何か?もっと良くするにはどうすればよいのか?」を探るためのツールとして設計されており、予測モデルは「こうすればいいよ」というアドバイスを出力するために使われることになっているからです。

ここでは「Valueを最大化する」という目標を設定します。その他に、この予測モデルの名称と、Einstein Analytics上での保存先を設定します。

次は What type of story are you interested in? ですが、今回のゴールは予測値をアドバイスしてもらうことですので、 Insights & Predictions を選択します。

f:id:masashi-sato-flect:20200109162749p:plain

次は How would you like to select the fields for your story? ですが、ここでは Automated を選択します。

f:id:masashi-sato-flect:20200109162858p:plain

以上を入力すると「Story」が出来上がります。Einstein Discoveryでは、これがすなわち、予測モデルです。

f:id:masashi-sato-flect:20200109162920p:plain

どんな予測モデルを作ったの?

出来上がった予測モデルを観察してみましょう。クリックすると、以下のような画面になります。

f:id:masashi-sato-flect:20200109162940p:plain

機械学習に多少の覚えのある筆者でしたが、この画面を読み解くには、Einstein Discoveryの設計背景を少々洞察する必要がありました。この図はつまり、category の選択が、 value 最大化という目的に「大きく影響している」と言っているのです。もちろんそうなるようにデータを作ったのだから当然ですが、この「Story(=予測モデル)」、何を根拠に数値を出しているのでしょうか?

そのタネ明かしは、「R Code」の中にありました。

f:id:masashi-sato-flect:20200109163019p:plain

ポイントは以下の部分です。

converters <- list(
    `date` = converter.date(c("2018/01" 中略),
    `category` = converter.text(c("B", "A"), 中略)
)

predict <- function(data) {

    predictions <- (
        2.3825128790222143 +
        (data[[1]] == 0 & data[[2]] == 0) * 0.11883377753580698 +
        (data[[1]] == 0 & data[[2]] == 1) * 0.11883377753580698 +
        中略
        (data[[2]] == 0) * -1.4558662462697816 +
        (data[[2]] == 1) * 1.4458832575039133
    )

筆者はR言語には詳しくありませんが、上記のコードが以下のような処理を実行していることは容易に読み取れます。

  • 平均 2.3825128790222143
  • category が B なら、 -1.4558662462697816 (=~ 0.92)
  • category が A なら、 1.4458832575039133 (=~ 3.83)
  • あとは category と date によって、細かい値調整

まさに最初に仮設した真理をだいたい反映する内容になっていると言えます。 これこそが予測モデルの正体のようです。 Einstein Discoveryがやっていたことは、モデル作成という名の、係数調整だったのです。(言われてみれば、Linear Regressionのトレーニングとは、そういう作業にほかなりませんね。。。)

予測モデルをデプロイする

予測モデルはできました。次はデプロイ作業です。デプロイとはつまり、Salesforceのカスタムオブジェクトにこの予測モデルを仕掛け、どこかの項目に「A」や「B」と入力されたら、予測値を別の項目に設定するように仕掛ける作業ということになります。このためには、カスタムオブジェクトを定義する必要があります。

まず以下のような項目を持つカスタムオブジェクト Csv01__c を定義します。例によって確実な動作確認のための味気ない最低限の内容です。

Nane           Auto Number
Category__c    Text(16)
FinalValue__c  Number(9,9)

FinalValue__cという項目ですが、これはEinstein Discoveryに予測に対する実測値を伝えるための項目です。これについては後述します。

カスタムオブジェクトを定義したら、以下の「Deploy Model」をクリックして作業を開始します。

f:id:masashi-sato-flect:20200109163210p:plain

新規デプロイを選択し、適当な名前をつけます。

f:id:masashi-sato-flect:20200109163227p:plain

次に、予測モデルを適用するカスタムオブジェクトとして「Csv01__c」を指定します。

f:id:masashi-sato-flect:20200109163249p:plain

次に、予測モデル作成で使用したデータの項目と、カスタムオブジェクトの項目の対応関係を設定します。

(なお、ここで「date - Create Date」の対応関係が設定されているのはなぜかと思われたかもしれませんが、これは「最低2項目の対応関係が必要」というEinstein Discoveryの制約をかわすための措置ですので、ここでは気にしないでください。)

f:id:masashi-sato-flect:20200109163303p:plain

次は予測モデルの予測結果を設定する項目の指定です。Einstein DiscoveryではaiPredictionFieldと呼ばれる特別なメタデータ属性を設定した読み取り専用項目に予測結果を設定します。既存項目を指定することもできるようですが、ここでは項目名を入力してEinstein Discoveryに適当な新規項目を追加してもらうことにしましょう。

f:id:masashi-sato-flect:20200109163320p:plain

次は予測モデルを適用するレコード条件の設定ですが、ここでは「全レコードにひとつの予測モデルを適用」を指定します。

f:id:masashi-sato-flect:20200109163336p:plain

次に「ユーザが入力する、予測モデルの入力情報となる項目はどれか」を指定します。今回の場合は category の1項目だけです。

f:id:masashi-sato-flect:20200109163609p:plain

次に指定するのは、「実測値」です。ここでは、カスタムオブジェクト作成時に設定した項目 FinalValue__c を指定します。また、 0 を超える実測値が入力された場合に、実績値の入力が完了したと認識するように設定します。実測値を指定することにより、予実比較のレポートを作成することができます。これについては後述します。

f:id:masashi-sato-flect:20200109163628p:plain

ようやくすべての設定が完了しました。しばらく待つと、デプロイ完了のメッセージが表示されます。

f:id:masashi-sato-flect:20200109163654p:plain

Salesforce Platform側の準備

以上でEinstein Discovery側の準備は完了しましたので、次はSalesforce Platform側の作業です。

まずはじめに、Einstein Discoveryと、カスタムオブジェクトを操作するユーザが必要な項目にアクセスできるように項目レベルセキュリティを設定します。ここで注意ですが、Einstein Discoveryは「Analytics Cloud Integration User」というプロファイルですので、これに対しても項目レベルセキュリティを設定する必要があります。ここでは以下のように設定しました。

Field               System Administrator  Analytics Cloud Integration User
---------------------------------------------------------------------------
SpeculatedValue__c  read only             read only
FinalValue__c       read write            read only

ページレイアウトにも項目を追加しておきます。

「Einsteinのアドバイス」のUIを追加する

次は冒頭にご紹介したような「Einstienのアドバイス」を表示する画面上の導線設定です。これはカスタムオブジェクトのLightning Record Pagesを設定することによって行います。

Lightning Componentsから「Einstein Predictions」をドラッグしてオブジェクト詳細の右側にドロップし、このコンポーネントの設定「Prediction」に、作成した予測モデルの名称 csv01 SpeculatedValue を設定します。

f:id:masashi-sato-flect:20200109163719p:plain

実験してみる

以上で長い準備作業が完了しました。早速テストしてみましょう。カスタムオブジェクトを新規作成し、Categoryに「A」と入力して保存します。

f:id:masashi-sato-flect:20200109163737p:plain

するとEinsteinのアドバイスが表示されました。「予測値は3.95で、そのうちCategoryがAであることによる要因が1.45」と、前述のR言語のコードに書かれた係数値の通りの内容です。

f:id:masashi-sato-flect:20200109163752p:plain

なお、この段階でSpeculatedValueの表示更新が滞る現象が何度か観察されましたが、原因についてはわかりませんでした。APIでクエリしてみると、以下のように正しい値が取得できました。

f:id:masashi-sato-flect:20200109163811p:plain

次にCategoryをBに変更してみましょう。こうすると「予測値は1.01で、CategoryがBであることが要因であり、改善のためにはCategoryをAにすることが考えられる」という内容のアドバイスになりました。

f:id:masashi-sato-flect:20200109163825p:plain

実に長い道のりでしたが、ようやく当初の目標通りEinsteinのアドバイス機能をカスタム実装することができました。

実測値との比較

詳細は割愛しますが、カスタムオブジェクトのFinalValueを設定することで、実測値との比較も可能です。以下のようなダッシュボードを作成してくれます。

f:id:masashi-sato-flect:20200109163922p:plain

ここでは予測モデルが適用された2つのレコードについて、 SpeculatedValue に設定された Predicted Value と FinalValue に設定された Actual Value がプロットされ、予測が概ね正確に行われていることが観察できます。また一定期間運用すれば、 Accuracy Trend で予測精度の時系列変化を観察することもできるでしょう。

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

AWS 認定 機械学習 – 専門知識に合格しました

新年明けまして、おめでとうございます。2020年最初のFlect Blog!本年もよろしくお願いします。

技術開発室エンジニアのオクダです。今回は昨年受験した「AWS 認定 機械学習 – 専門知識」について、受験レポートと試験勉強内容ついて解説していきたいと思います。

AWS 認定 機械学習 – 専門知識とは

AWS 認定 機械学習 – 専門知識の公式サイトでは、以下の内容が記載されています。

AWS 認定 機械学習 – 専門知識は、開発あるいはデータサイエンスの業務を行っている方を対象としています。この試験では、与えられたビジネス上の問題に対する機械学習ソリューションを設計、実装、デプロイ、維持する受験者の能力が検証されます。

認定によって検証される能力

  • 与えられたビジネスの問題に対し、適切な ML アプローチを選び、その理由を説明できる
  • ML ソリューションの実装に適した AWS のサービスを選択する
  • スケーラビリティ、コスト効率、信頼性、安全性に優れた ML ソリューションを設計し、実装する

推奨される知識と経験

  • AWS クラウドでの ML/深層学習ワークロードの開発、設計、実行における、1~2 年の経験
  • 基本的な ML アルゴリズムの基となる考えを表現する能力
  • 基本的なハイパーパラメータ最適化の実践経験
  • ML および深層学習フレームワークの使用経験
  • モデルトレーニングのベストプラクティスを実行する能力
  • デプロイと運用のベストプラクティスを実行する能力

試験の準備

実践的な経験ほど優れた準備はありません。関連する AWSレーニングコースやその他のリソースが多くあります。認定準備のために追加の知識とスキルを習得するのに役立ちます。認定試験で評価された能力の関連情報については、試験ガイドを確認してください。

aws.amazon.com

と記載されています。ふむふむといった感じです。

ことの始まり

弊社でも、機械学習案件が増えてきて、別に資格を取得していないと、仕事ができない職種(医師、弁護士、会計士、建築士)ではないが、 案件を受ける際に、資格を持っていると、お客様への信頼はより一層深まることは、間違いありません。 機械学習の資格と言っても色々存在します。例えば、

今回は、我々の仕事に直結している「AWS 認定 機械学習 – 専門知識」の受験体験とその勉強方法について解説していきます。

試験勉強(どういったことに取り組んできたか)

以前、弊社では週に1度2ヶ月くらいかけて、Sagemaker Builtin Algorithmをモブプログラミング形式でレビューしていきました。 実際にJupyterNotebookを起動して、Sagemaker のBuiltin Algorithmを動かしていくことで、理解を深めていきました。 Sagemaker Builtin Algorithmを使用するにあたり注意が必要なのは、CPU、GPU使用料が当然かかってくるので 指示通りGPU使って、何回も繰り返しトレーニングしたりすると、アッと言う間に高額請求されるので、気を付けてください。

2019年11月にも入り、機械学習の勉強しなきゃな〜、と思い、まずは、AWS機械学習についての公式ドキュメントを読んで、理解することにしました。

https://docs.aws.amazon.com/ja_jp/machine-learning/latest/dg/what-is-amazon-machine-learning.html

Amazon Machine Learning」は枯れたサービスではありますが、機械学習を勉強する資料としては役に立ちました。 左のカラムの「Amazon Machine Learning とは。」から「Amazon Machine Learning のリファレンス」まで、一通り読破しました。所用日数約1週間。 結構なボリュームで、各トピックに関して、詳しく書かれています。

など、モデルを作成する上で、気を付けなければいけないことなどが記載されており、機械学習に関して知識と理解が深まりました。

さて、自分なりに機械学習に対して理解も深まってきたので、模擬試験を受けることにしました。 AWS公式の無料の模擬試験は10題しかなく(解答付き)、有料の模擬試験も20題でこちらは解答がついていないので、 サードパーティの模擬試験を漁ることにしました。 以前、模擬試験を漁っていた時には、Udemyとか有料会員向けの模擬試験も対応されていませんでしたが、 2019年11月に入り、チラホラ「AWS 機械学習 対策試験問題」が公開されてきました。 そこでUdenyの他にも、インターネット上で見つけた模擬問題を解いていきました。

模擬問題の中にはどう考えても解答が間違えている問題もありましたが、問題集の粗探しをしながら、11月末の日程で「AWS 認定 機械学習 – 専門知識」認定試験を申し込みを行いました。

AWS 認定 機械学習 – 専門知識」受験レポート

11月末の試験当日、試験会場に行き、受付を行う。まず、試験会場で、

  • 試験中不正行為は行いません。
  • 試験中他の人に迷惑となるような行為を一切行いません。
  • 試験問題等、試験中に知り得た内容は口外しません。

等の同意書にサインをして、受付に提出。

身分証明として、顔写真付きのIDカードと、クレジットカード等の証明書を2点提出する。僕の場合は、運転免許証と在留カード(どちらも顔写真付き)

IDカードが確認できたら、写真付きでない証明書を返され、Webカメラで顔写真撮影される。撮られた写真はその場で確認される。

撮影後、電子端末に名前を記載する。記載中、電子端末に指が触れるとやり直しとなるので気を付ける。

署名後、荷物、腕時計、ポケットの中の物全て、所定のロッカーに入れる。

受付の人に「お手洗いも済み、準備ができましたら、お声かけください。」と言われるので「準備できました。」と伝えると、再度、腕時計をしていないか等、確認され、試験室に案内される。

試験室に持って入れるのは、顔写真付きIDカードとロッカーキーのみ。

受付の人が試験室の端末を立ち上げ、受験者の名前が合っているのを確認し、準備ができたら試験開始。試験時間170分。問題数65問。

日本語で、受験したが、問題文の日本語が怪しい場合は、画面左上に「English」ボタンがあるので、それを押すと原文を確認することができる。

Machine Learningの場合、AccurateとPrecisionがどう訳されるか、不安要素が多々あったので、問題文を見ながら、適宜原文を確認した。

試験時間100分ぐらいで、全問解答し、20分ぐらい見直しをして、見直し終了ボタンをクリック。

  • なぜ、この試験を受験しようと思いましたか?
  • 試験会場は快適でしたか?
  • 試験勉強期間はどれくらいでしたか?

等々聞かれるので、正直に回答し、試験終了ボタンをクリック。

試験終了確認画面が表示され、無事に「合格」という文字を確認できました。

試験室を退室し、受付に現在の時刻と試験終了しましたのサインをした後、ロッカーから自分の荷物を取り出し、ロッカーキーを受付に返却して試験会場をあとにした。

試験時間が午前中だったので、同日夜20時に試験結果をダウンロードして確認できるようになっていた。

試験結果がこちら

f:id:flect170:20191223133216p:plain
AWS 認定 機械学習 – 専門知識「認定資格証明書」

ふり返って、どうか

今回、本格的に試験勉強を始めてから、受験までについて、ご報告させていただきました。

期間が短かったこともあり、試験対策に重点を置いて勉強してきましたが、機械学習の資格を取得する上で

においては、AWSに限らず、他社のサービスでも共通した知識と経験が必要となるので、これから仕事で機械学習を利用されることを考えている人は、是非勉強すると良いと思います。

  • 開発および運用に関するベストプラクティスに従う能力

に関しては、AWSのサービスを利用した実装、テスト、結果分析方法についての知識と経験とその解決方法が問われるので、他のサービスを利用されるエンジニアには、あまり関係がありません。 AWSのサービスを利用して機械学習を利用される場合は「AWS 認定 アソシエイト」レベルの知識と実務経験が必要となってくるので、まずは、そちらの試験を受験されることをお勧めします。

最後までお読みいただきありがとうございました。皆様にとってOlympic Yearである2020年が良い年になることを願っています。

きゅうり画像で回帰問題を解いてみた

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

前回の投稿では、AWS re:invent2019のレポートをしました。 いやー、楽しいイベントだったなー。来年も行きたいなー(と書き続けたら行かせてもらえないだろうか。ちなみに増員はしてもらえそうな雰囲気だった。)

cloud.flect.co.jp

さて、今回から私の担当している機械学習に関する投稿に戻りますが、今回は「画像から回帰問題を解く」をテーマにしたいと思います。

はじめに

画像を用いた機械学習といえば分類問題(Classification)が一般的ですが、実は回帰問題(Regression)を解くこともできます。 有名なところでは「顔の画像から年齢を推定する」というものが有ります。こちらの記事で詳しく紹介されている方がおられますので、ご参照ください。

FLECTでも同様の技術を用いたソリューションを提供しています。 詳しくは述べられないのですが、ざっくり一例をご紹介しますと、下記の図はとある工業製品の摩耗状態を画像から判定するモデルになります。 この工業製品の摩耗状態はとある指標で定量的に10段階で計測できるのですが、図の横軸が計測した正しい摩耗状態、縦軸が画像から推定した摩耗状態となります。かなりしっかりと推定ができているように見えると思います。

f:id:Wok:20191223195757p:plain

上図ではテストサンプルの件数が多くわかりにくいですが、推定した摩耗状態と実際の摩耗状態の誤差をバイオリンチャートで表現すると次の図のようになっています。 縦軸が0を中心に摩耗状態の段階誤差の範囲を示しています。ほぼ1段階以内の誤差で推定することができています。 (自画自賛ですがすごいと思ってる!興味のある方はご連絡を。)

f:id:Wok:20191223195822p:plain

この回帰の技術に関連して、ちょっとした実験をしてみましたのでご紹介します。

実験の動機

まず、実験をしてみた動機からご説明します。

みなさんもGoogleなどで検索していただくとわかると思いますが、画像で回帰問題を解くことについてWeb上にはそれほど多くの情報がありません。大体が上記ブログと同様に年齢推定の話です。 FLECTでも回帰問題を扱うことになりブログで情報発信をしようと思ってその時にその理由がわかったのですが、業務上扱っているデータは当然あるのですがブログで扱えるような公のデータがないのです(私の観測範囲では)。 なので、実験的に何かをしてブログを書くというのが難しかったのではないかと考えています。

で、我々も同じ理由でだいぶ長い間ブログを書けずにいたのですが、あるとき、ぱっと、あることを思い出しました。 そういえば、昔、きゅうりの仕分け(ランクづけ)を画像分類でやっていた人がいたなぁ、と。

それからずっと、このきゅうりのランクを回帰で推定できないか?というのが頭から離れずにいたのですが、この年末幸運にも少し時間が空いたので早速実験をしてみた、ということになります。

ということで、以下、きゅうりのランクを回帰で推定する話をつらつらと書いていきます。 なお、今回用いたソースコード(notebook)は文末のgit repositoryに格納しておきます。

きゅうりの仕分けとデータセット

ご存知の方も多いと思いますが、自動車部品メーカーに務められていた方が退職して、Deep Learningを用いてきゅうりの仕分けをするシステムを作りました。 当時だいぶバズって、Googleのブログにも特集されています。詳細はこの記事を見ていただくのが良いかと思います。

cloudplatform-jp.googleblog.com

この仕分けシステムでは、2LからCまで、きゅうりを9つのランクに仕分けることができるそうです。 そして、この学習用のデータはなんとgitで公開されているのです(Creative Commons)。ありがたい!

github.com

今回は、この中でもprototype_2の学習用のデータを用いて実験を行いました。 なお、このprototype_2は上下側面の3方向からの撮影したデータとなっていますが、今回は上から撮影したもののみを用いることにしました。 また、画像の中には、手が写り込んでいたりする写真などが含まれていますが、これらは事前に取り除きました(OTHERラベルがついている)。

一応、分布を確認するとこんな感じに各クラス800枚前後でまんべんなく格納されています。ちなみに横軸の数値は2L〜Cまでのランクに対応づいています。縦軸は画像数。

f:id:Wok:20191220152417p:plain

なお、本データはCIFAR10の形式で格納されていますので、読み出し方はCIFAR10のサイトかgit repositoryを参考にしてください。

ネットワーク

今回はresnet v2を用いてやってみました。ポイントはFlatten()のあとにsoftmaxではなくreluを活性化関数とした全結合層を置くことです。 また、今回はCIFAR10と同じ画像なので、Inputのサイズは32x32x3になります。

    <略>
    x = AveragePooling2D(pool_size=8)(x)
    x = Flatten()(x)
    x = Dense(8, activation='relu')(x)
    outputs = Dense(1)(x)

モデル全体は次のような感じです。 なお、ある程度エイヤで作っているネットワークなので、チューニングの余地は有りますので、興味のある方は適当にいじってみるといいと思います。

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_8 (InputLayer)            (None, 32, 32, 3)    0                                            
__________________________________________________________________________________________________
conv2d_218 (Conv2D)             (None, 32, 32, 16)   448         input_8[0][0]                    
__________________________________________________________________________________________________
batch_normalization_197 (BatchN (None, 32, 32, 16)   64          conv2d_218[0][0]                 
__________________________________________________________________________________________________
activation_197 (Activation)     (None, 32, 32, 16)   0           batch_normalization_197[0][0]    
__________________________________________________________________________________________________
conv2d_219 (Conv2D)             (None, 32, 32, 16)   272         activation_197[0][0]             
__________________________________________________________________________________________________
batch_normalization_198 (BatchN (None, 32, 32, 16)   64          conv2d_219[0][0]                 
__________________________________________________________________________________________________
activation_198 (Activation)     (None, 32, 32, 16)   0           batch_normalization_198[0][0]    
__________________________________________________________________________________________________
conv2d_220 (Conv2D)             (None, 32, 32, 16)   2320        activation_198[0][0]             
__________________________________________________________________________________________________
batch_normalization_199 (BatchN (None, 32, 32, 16)   64          conv2d_220[0][0]                 
__________________________________________________________________________________________________
activation_199 (Activation)     (None, 32, 32, 16)   0           batch_normalization_199[0][0]    
__________________________________________________________________________________________________
conv2d_222 (Conv2D)             (None, 32, 32, 64)   1088        activation_197[0][0]             
__________________________________________________________________________________________________
conv2d_221 (Conv2D)             (None, 32, 32, 64)   1088        activation_199[0][0]             
__________________________________________________________________________________________________
add_64 (Add)                    (None, 32, 32, 64)   0           conv2d_222[0][0]                 
                                                                 conv2d_221[0][0]                 
__________________________________________________________________________________________________
batch_normalization_200 (BatchN (None, 32, 32, 64)   256         add_64[0][0]                     
__________________________________________________________________________________________________
activation_200 (Activation)     (None, 32, 32, 64)   0           batch_normalization_200[0][0]    
__________________________________________________________________________________________________
conv2d_223 (Conv2D)             (None, 32, 32, 16)   1040        activation_200[0][0]             
__________________________________________________________________________________________________
batch_normalization_201 (BatchN (None, 32, 32, 16)   64          conv2d_223[0][0]                 
__________________________________________________________________________________________________
activation_201 (Activation)     (None, 32, 32, 16)   0           batch_normalization_201[0][0]    
__________________________________________________________________________________________________
conv2d_224 (Conv2D)             (None, 32, 32, 16)   2320        activation_201[0][0]             
__________________________________________________________________________________________________
batch_normalization_202 (BatchN (None, 32, 32, 16)   64          conv2d_224[0][0]                 
__________________________________________________________________________________________________
activation_202 (Activation)     (None, 32, 32, 16)   0           batch_normalization_202[0][0]    
__________________________________________________________________________________________________
conv2d_225 (Conv2D)             (None, 32, 32, 64)   1088        activation_202[0][0]             
__________________________________________________________________________________________________
add_65 (Add)                    (None, 32, 32, 64)   0           add_64[0][0]                     
                                                                 conv2d_225[0][0]                 
__________________________________________________________________________________________________
batch_normalization_203 (BatchN (None, 32, 32, 64)   256         add_65[0][0]                     
__________________________________________________________________________________________________
activation_203 (Activation)     (None, 32, 32, 64)   0           batch_normalization_203[0][0]    
__________________________________________________________________________________________________
conv2d_226 (Conv2D)             (None, 32, 32, 16)   1040        activation_203[0][0]             
__________________________________________________________________________________________________
batch_normalization_204 (BatchN (None, 32, 32, 16)   64          conv2d_226[0][0]                 
__________________________________________________________________________________________________
activation_204 (Activation)     (None, 32, 32, 16)   0           batch_normalization_204[0][0]    
__________________________________________________________________________________________________
conv2d_227 (Conv2D)             (None, 32, 32, 16)   2320        activation_204[0][0]             
__________________________________________________________________________________________________
batch_normalization_205 (BatchN (None, 32, 32, 16)   64          conv2d_227[0][0]                 
__________________________________________________________________________________________________
activation_205 (Activation)     (None, 32, 32, 16)   0           batch_normalization_205[0][0]    
__________________________________________________________________________________________________
conv2d_228 (Conv2D)             (None, 32, 32, 64)   1088        activation_205[0][0]             
__________________________________________________________________________________________________
add_66 (Add)                    (None, 32, 32, 64)   0           add_65[0][0]                     
                                                                 conv2d_228[0][0]                 
__________________________________________________________________________________________________
batch_normalization_206 (BatchN (None, 32, 32, 64)   256         add_66[0][0]                     
__________________________________________________________________________________________________
activation_206 (Activation)     (None, 32, 32, 64)   0           batch_normalization_206[0][0]    
__________________________________________________________________________________________________
conv2d_229 (Conv2D)             (None, 16, 16, 64)   4160        activation_206[0][0]             
__________________________________________________________________________________________________
batch_normalization_207 (BatchN (None, 16, 16, 64)   256         conv2d_229[0][0]                 
__________________________________________________________________________________________________
activation_207 (Activation)     (None, 16, 16, 64)   0           batch_normalization_207[0][0]    
__________________________________________________________________________________________________
conv2d_230 (Conv2D)             (None, 16, 16, 64)   36928       activation_207[0][0]             
__________________________________________________________________________________________________
batch_normalization_208 (BatchN (None, 16, 16, 64)   256         conv2d_230[0][0]                 
__________________________________________________________________________________________________
activation_208 (Activation)     (None, 16, 16, 64)   0           batch_normalization_208[0][0]    
__________________________________________________________________________________________________
conv2d_232 (Conv2D)             (None, 16, 16, 128)  8320        add_66[0][0]                     
__________________________________________________________________________________________________
conv2d_231 (Conv2D)             (None, 16, 16, 128)  8320        activation_208[0][0]             
__________________________________________________________________________________________________
add_67 (Add)                    (None, 16, 16, 128)  0           conv2d_232[0][0]                 
                                                                 conv2d_231[0][0]                 
__________________________________________________________________________________________________
batch_normalization_209 (BatchN (None, 16, 16, 128)  512         add_67[0][0]                     
__________________________________________________________________________________________________
activation_209 (Activation)     (None, 16, 16, 128)  0           batch_normalization_209[0][0]    
__________________________________________________________________________________________________
conv2d_233 (Conv2D)             (None, 16, 16, 64)   8256        activation_209[0][0]             
__________________________________________________________________________________________________
batch_normalization_210 (BatchN (None, 16, 16, 64)   256         conv2d_233[0][0]                 
__________________________________________________________________________________________________
activation_210 (Activation)     (None, 16, 16, 64)   0           batch_normalization_210[0][0]    
__________________________________________________________________________________________________
conv2d_234 (Conv2D)             (None, 16, 16, 64)   36928       activation_210[0][0]             
__________________________________________________________________________________________________
batch_normalization_211 (BatchN (None, 16, 16, 64)   256         conv2d_234[0][0]                 
__________________________________________________________________________________________________
activation_211 (Activation)     (None, 16, 16, 64)   0           batch_normalization_211[0][0]    
__________________________________________________________________________________________________
conv2d_235 (Conv2D)             (None, 16, 16, 128)  8320        activation_211[0][0]             
__________________________________________________________________________________________________
add_68 (Add)                    (None, 16, 16, 128)  0           add_67[0][0]                     
                                                                 conv2d_235[0][0]                 
__________________________________________________________________________________________________
batch_normalization_212 (BatchN (None, 16, 16, 128)  512         add_68[0][0]                     
__________________________________________________________________________________________________
activation_212 (Activation)     (None, 16, 16, 128)  0           batch_normalization_212[0][0]    
__________________________________________________________________________________________________
conv2d_236 (Conv2D)             (None, 16, 16, 64)   8256        activation_212[0][0]             
__________________________________________________________________________________________________
batch_normalization_213 (BatchN (None, 16, 16, 64)   256         conv2d_236[0][0]                 
__________________________________________________________________________________________________
activation_213 (Activation)     (None, 16, 16, 64)   0           batch_normalization_213[0][0]    
__________________________________________________________________________________________________
conv2d_237 (Conv2D)             (None, 16, 16, 64)   36928       activation_213[0][0]             
__________________________________________________________________________________________________
batch_normalization_214 (BatchN (None, 16, 16, 64)   256         conv2d_237[0][0]                 
__________________________________________________________________________________________________
activation_214 (Activation)     (None, 16, 16, 64)   0           batch_normalization_214[0][0]    
__________________________________________________________________________________________________
conv2d_238 (Conv2D)             (None, 16, 16, 128)  8320        activation_214[0][0]             
__________________________________________________________________________________________________
add_69 (Add)                    (None, 16, 16, 128)  0           add_68[0][0]                     
                                                                 conv2d_238[0][0]                 
__________________________________________________________________________________________________
batch_normalization_215 (BatchN (None, 16, 16, 128)  512         add_69[0][0]                     
__________________________________________________________________________________________________
activation_215 (Activation)     (None, 16, 16, 128)  0           batch_normalization_215[0][0]    
__________________________________________________________________________________________________
conv2d_239 (Conv2D)             (None, 8, 8, 128)    16512       activation_215[0][0]             
__________________________________________________________________________________________________
batch_normalization_216 (BatchN (None, 8, 8, 128)    512         conv2d_239[0][0]                 
__________________________________________________________________________________________________
activation_216 (Activation)     (None, 8, 8, 128)    0           batch_normalization_216[0][0]    
__________________________________________________________________________________________________
conv2d_240 (Conv2D)             (None, 8, 8, 128)    147584      activation_216[0][0]             
__________________________________________________________________________________________________
batch_normalization_217 (BatchN (None, 8, 8, 128)    512         conv2d_240[0][0]                 
__________________________________________________________________________________________________
activation_217 (Activation)     (None, 8, 8, 128)    0           batch_normalization_217[0][0]    
__________________________________________________________________________________________________
conv2d_242 (Conv2D)             (None, 8, 8, 256)    33024       add_69[0][0]                     
__________________________________________________________________________________________________
conv2d_241 (Conv2D)             (None, 8, 8, 256)    33024       activation_217[0][0]             
__________________________________________________________________________________________________
add_70 (Add)                    (None, 8, 8, 256)    0           conv2d_242[0][0]                 
                                                                 conv2d_241[0][0]                 
__________________________________________________________________________________________________
batch_normalization_218 (BatchN (None, 8, 8, 256)    1024        add_70[0][0]                     
__________________________________________________________________________________________________
activation_218 (Activation)     (None, 8, 8, 256)    0           batch_normalization_218[0][0]    
__________________________________________________________________________________________________
conv2d_243 (Conv2D)             (None, 8, 8, 128)    32896       activation_218[0][0]             
__________________________________________________________________________________________________
batch_normalization_219 (BatchN (None, 8, 8, 128)    512         conv2d_243[0][0]                 
__________________________________________________________________________________________________
activation_219 (Activation)     (None, 8, 8, 128)    0           batch_normalization_219[0][0]    
__________________________________________________________________________________________________
conv2d_244 (Conv2D)             (None, 8, 8, 128)    147584      activation_219[0][0]             
__________________________________________________________________________________________________
batch_normalization_220 (BatchN (None, 8, 8, 128)    512         conv2d_244[0][0]                 
__________________________________________________________________________________________________
activation_220 (Activation)     (None, 8, 8, 128)    0           batch_normalization_220[0][0]    
__________________________________________________________________________________________________
conv2d_245 (Conv2D)             (None, 8, 8, 256)    33024       activation_220[0][0]             
__________________________________________________________________________________________________
add_71 (Add)                    (None, 8, 8, 256)    0           add_70[0][0]                     
                                                                 conv2d_245[0][0]                 
__________________________________________________________________________________________________
batch_normalization_221 (BatchN (None, 8, 8, 256)    1024        add_71[0][0]                     
__________________________________________________________________________________________________
activation_221 (Activation)     (None, 8, 8, 256)    0           batch_normalization_221[0][0]    
__________________________________________________________________________________________________
conv2d_246 (Conv2D)             (None, 8, 8, 128)    32896       activation_221[0][0]             
__________________________________________________________________________________________________
batch_normalization_222 (BatchN (None, 8, 8, 128)    512         conv2d_246[0][0]                 
__________________________________________________________________________________________________
activation_222 (Activation)     (None, 8, 8, 128)    0           batch_normalization_222[0][0]    
__________________________________________________________________________________________________
conv2d_247 (Conv2D)             (None, 8, 8, 128)    147584      activation_222[0][0]             
__________________________________________________________________________________________________
batch_normalization_223 (BatchN (None, 8, 8, 128)    512         conv2d_247[0][0]                 
__________________________________________________________________________________________________
activation_223 (Activation)     (None, 8, 8, 128)    0           batch_normalization_223[0][0]    
__________________________________________________________________________________________________
conv2d_248 (Conv2D)             (None, 8, 8, 256)    33024       activation_223[0][0]             
__________________________________________________________________________________________________
add_72 (Add)                    (None, 8, 8, 256)    0           add_71[0][0]                     
                                                                 conv2d_248[0][0]                 
__________________________________________________________________________________________________
batch_normalization_224 (BatchN (None, 8, 8, 256)    1024        add_72[0][0]                     
__________________________________________________________________________________________________
activation_224 (Activation)     (None, 8, 8, 256)    0           batch_normalization_224[0][0]    
__________________________________________________________________________________________________
average_pooling2d_8 (AveragePoo (None, 1, 1, 256)    0           activation_224[0][0]             
__________________________________________________________________________________________________
flatten_8 (Flatten)             (None, 256)          0           average_pooling2d_8[0][0]        
__________________________________________________________________________________________________
dense_15 (Dense)                (None, 8)            2056        flatten_8[0][0]                  
__________________________________________________________________________________________________
dense_16 (Dense)                (None, 1)            9           dense_15[0][0]                   
==================================================================================================
Total params: 848,497
Trainable params: 843,281
Non-trainable params: 5,216
__________________________________________________________________________________________________

レーニン

ネットワークを定義できたら、トレーニングを開始します。損失関数は今回はMSEにしています。 また、全サンプル数は7861で、これをTraining, Validation, Testでそれぞれ6:2:2に分割しています。 またこちらもエイヤですがエポック数は200にしています。トレーニングの結果は次の通り。 青線がlossでオレンジ線がval_lossです。大体130エポックで収束しているようですね。

f:id:Wok:20191220160258p:plain

評価

では、トレーニングしたモデルで評価してみましょう。 推定ランクをプロットした図です。なかなかうまく推定できているように見えます。 f:id:Wok:20191220160321p:plain

ただ、やはり、サンプル数が多いのでどの程度分散しているのかわかりにくいですね。 なので、こちらのバイオリンチャートを見ていただきましょう。 f:id:Wok:20191220160335p:plain

ご覧になっておわかりになると思いますが、ほぼ正しいランクの位置をてっぺんに正規分布していますね。 やや裾が重いところがあり、他のクラスと認識されてしまっているものが有るようですが、そこそこ正しく推定されたと見て良いと思います。 (作成者の方もおっしゃっていますが、人間がやっているのでランク付けもある程度えいやなところがあるらしいので。)

TensorFlowでディープラーニングによる『キュウリ』の仕分け | Workpiles

今回の場合は、各分類クラス間が離れていないため、人間がやっても“2LとL”や“LとBL”の判別は難しい(けっこう適当)だったりします。

といことで、今回の実験はひとまず成功したかなと思います。画像からの回帰、みなさんもチャレンジしてみてはいかがでしょうか。 結果をシェアしてもらえるとありがたいです。

しきい値調整

推定されたランクは浮動小数点となっていますが、実際のランクは整数値です。 この整数値に変換するためにしきい値を決める必要が有ります。 デフォルトで小数点第1位が5のところでしきい値にしてもいいですが、このしきい値をいい感じに調整してくれる方法も有ります。 今回は画像から回帰をすることろまでが主題となりますので、そこまでは踏み込みません。また機会があればご紹介します。

余談ですが、、、

resnet v2でクラス分類させたら、全然クラス分類のほうが精度が高かったです。 タスクによって解き方をしっかり使い分けることが重要です。この辺の話も機会があれば別途。

最後に

今回は、きゅうりの画像のランク付けを題材に、画像から回帰問題を解く方法についてご紹介しました。 最初にご紹介したとおり、画像から連続値を推定する技術は様々な分野に応用可能であり、FLECTでも実績があります。 ぜひご活用いただければありがたいと思います。

次回は、また別の技術をご紹介する予定です。ではでは。

リンク

github.com

HerokuでTensorFlowのAPIをホストした話

この記事は、Heroku Advent Calendar 201920日目の記事です。

19日目は@mttrsさんさんによる「Heroku Changelog 2019」でした。 21日目は@sizukutamagoさんです。

こんにちは、技術開発室の藤野です。 今回はHeroku上でTensorFlowの推論を行うWeb APIを作った話についてです。

はじめに

今回の話のあらましは、TensorFlowでディープラーニングのモデルを作ったので、Web APIとして使えるようにしたい、というものです。 APIはリクエストに対して推論結果を返すシンプルなもので、なるべく早く構築しデプロイや運用の手間は省きたいと思っていました。

これに対して、Herokuは下記の利点があります。

  • インフラ、ミドルウェアが用意されており、デプロイが容易
  • 運用のための仕組みがあらかじめ提供されている
  • CI/CDの機能を利用できる

一方で、ディープラーニングアルゴリズムGPU等を使って高速化することが一般的だと思います。 Herokuではこのようなアクセラレータは利用できず、CPUのみで推論処理を行うことになります。

そこで、今回はこのような推論処理を含むAPIをHeroku上で動かしたときのパフォーマンスはどのくらいなのか?を主眼に見ていきたいと思います。

続きを読む

CariotはNew RelicとAmazon EventBridgeにトラスト構想の夢を見る

こんにちは、遠藤@Cariot事業部です。
今回はCariot SREチームの一員として投稿します。

はじめに

フレクトが提供している車のIoTサービス「Cariot (Car+IoT)」では、先日New Relic社と共同でプレスリリースを発表し、サービスの性能に関する透明性を高めるべく、取り組みをはじめました。

www.flect.co.jp

わたしの所属するCariot事業部 製品開発部でも、上記、大きなビジョンの実現を目指しながら、少しずつできることを進めています。
今回は、現在進行中の取り組みについて紹介します。

続きを読む

Auth0の話

こんにちは。としやさんじゃない方の齋藤です。Advent Calendar の季節がやってきました。

qiita.com

HerokuのDynoやアドオンの構成は、定番がありつつもプロジェクトごとの微妙な色彩の違いにフィットさせる必要があり、いつも座組みに頭を悩ませるところでありますが、個人的にはカードゲームのデッキ構築みたいなものだと思っています。Creditぎりぎりまで強いカードを少しでも多く入れたい。

ずっと前から存在は知っていたAuth0にようやく触れる機会が取れたので、検討している方に少しでもヒントになればと思ってAuth0の紹介をしてみたいと思います。

Auth0とは

f:id:shns:20191217224440p:plain https://auth0.com/jp/

IDaaS/CIAMとして最もポピュラーなサービスの一つで、たとえば認証・認可、セキュリティにまつわる 面倒な実装をせず、強力な認証基盤をすぐさま自身のアプリに取り込むことができます。

Documentationにある Overview を見ると、

Auth0 provides authentication and authorization as a service.(Auth0は認証と認可をサービスとして提供します)

https://auth0.com/docs/getting-started/overview

とありますが、のみならず

  • ログイン履歴をダッシュボードで見れたり
  • 会員機能にまつわるメール機能は一通り備えていたり
  • 既存の認証基盤とつなげやすかったり

といったように、使いやすさや拡張性、開発者目線の機能が豊富です。

特徴

特にSDK、サンプルコード、対応するソーシャルログインなど、品揃えが圧倒的。

f:id:shns:20191217224856p:plain f:id:shns:20191217231118p:plain

それと今年はLINEやSign in with Appleが標準で提供されたりしました。

auth0.com auth0.com

また、個人的な印象としては、管理者向けのダッシュボードがとにかく始めやすいように作られているので、従来のエンタープライズ系SSO製品と比べて、0を1にする部分の苦しみから開発者が圧倒的に解き放たれる感があります。OpenAMの複雑な管理者画面の遷移の仕方を覚えたり、ちょっと設定を変えたいだけなのに/var/lib/tomcat/webapps界隈に足を運ばないと行けなかったり・・といった手間はないので大事なコードに集中できる感は圧倒的に勝っています。

引き算の美学

いつもシステム構成の下敷きとして定期的に見直しているpostに以下があります。

https://engineering.videoblocks.com/web-architecture-101-a3224e126947

上のポンチ絵には出てこないのですが、初学を終えてこの絵が分かる頃に ちょうど認証認可の仕組みの理解が必要になるタイミングがあると思うのですよね。

Javaでいうと、スレッドプログラミングができたら中級者みたいなそんな感じかな・・(個人の意見です)

現代のWebアプリにはログイン機能は当たり前のようにありますが、自前で作る場合は思ったよりも機能が爆発したり、OpenAMなどのオープンソースを使うにしても非機能要件を考慮した結果それなりにインフラ面が大掛かりになってしまったりと、トータルで見たときに手間が大きくなってしまうことがあります。

一方で、ID基盤が提供するところのログインというユーザ体験そのものとしては、

  • かなりの頻度で通る(実装次第ですが)
  • 複数経路があり、エントリーポイントが多い
  • 比較的早い回遊タイミングで到達されることが多い

といった点があり、最初から入れるにはそこそこ大玉で、かといって後から入れるには 全体への基盤改修リスクが大きい、といった結果として跳ね返ってきます。

コンバージョンを継続的に改善したいB2C系サービスや、 顧客情報は集めたいけど顧客のストレスを増やしたくないと考えているサービスにとって、 導線検討やセキュリティ面の考慮というのは避けて通れない。

こういった手間がかかるが逃げられない、といったジレンマが 強力な標準提供サービス(SDK、テンプレートコードなど)によって 抽象化ないしスキップできてしまうのがAuth0の良いところかなと。

CRMとの親和性

CIAMとして見て、やはりCRMとの親和性の高さがポイントの1つかなと思っています。

ユースケースとしては、認証・認可の仕組みとして利用しつつ、 Auth0ログイン時などにログをイベントとして受け取れるので、KafkaやAmazon Eventbridgeを配管して function codeやバッチ処理からCRM・MAに取り込み、ネイティブにプッシュ配信、など。

auth0.com

Log driven なので、セキュリティオートメーションでも重宝しますね。

あるいはCRM上のビジネスユーザは別のテナントなりアプリケーションで SAML FederatedにSSOしてWebアプリ側にも行き来しやすくする、といったアプローチも取れます。

Auth0の良いところは、Rulesを使って認証時の様々なフック処理を書けるところです。 こんな感じで。

f:id:shns:20191217225549p:plain

管理者のコンソールでコード片のデバッグが出来るのは地味に強い。それはそれとして、Auth0標準のカスタムデータベースとソーシャルログインのユーザをマージする前提でWebアプリを組むときや、外のサービスを呼びつつid_tokenに必要な情報を詰め込みたいとかログインのうち何回かは外部DBからの最新情報を取ってきてキャッシュしたいとか、色々なユースケースで活躍します。

主要なイベントについてはテンプレートがあるので、流用開発ベースで進めることができ、どれくらい手間がかかるのかの見立てが立てやすいところがポイント。

収集・集約・アプローチのタッチポイントをAddonインストール一発で作れるのは非常にありがたいところなのではないでしょうか。

Add-onsとしての利用

今年、yonyonさんとSWTTでHerokuの話をしたとき※には認証周りの話題はしなかったのですが、当然Herokuのアプリで認証込みでサービスを作るときにはAuth0が第一の選択肢になってきます。

見積から開発・運用まで!Herokuの基本とTips - Qiita

当たり前ですがHerokuのユーザで各ステージのアプリにシングルサインオンできるし、テナントのユーザ権限もHerokuユーザに付与できるので、ちょっと管理者ユーザとして他の人に見てほしいときに、Collaboratorとして招待できますし。

Add-onプランとしては以下があります。

https://elements.heroku.com/addons/auth0

  • Free
  • Silver
  • Gold
  • Enterprise B2C

AD連携やマルチデータソース戦略を取るわけでなければ、意外とキャップは少なくFreeで出来ることが多いです。なのでステージ構成としては、development,stagingはFree/Silverで、productionから GoldもしくはB2C Enterpriseという構成などでも良いかなと思います。

Planを考えるときの閾値については、MAU(Monthly Active Users)がベースとなります。つまり、月1回でもログインすればそのユーザは1カウントとして計算され、プランごとの上限値はそのMAUで決まります。

f:id:shns:20191217230432p:plain

この例で言うと、Enterprise B2C の 10,000 Usersに対して先月は2,500ユーザ。まだまだ余裕があります。

一応 Dashboardのトップでもログインユーザのサマリが確認できるのですが、各月のアクティブユーザについては以下から当月のQuotaを参照できます。

User Dropdown > Account Usage > Home > Quota Utilization > Regular Active Users

なお、Auth0でユーザ登録する(Database User)とソーシャルログインを併用しており、両者をマージルールで1ユーザとしてマージした場合には、どちらのログインを利用してもMAUは1カウントになります。逆になると別カウントなので、マージ可否は要件として決めておいた方が良いです。

リミットを超えてきそうな場合にはプランの引き上げ(再検討)が必要になりますが、最初からGold以上で行くなら最初にキャパシティ設計をしっかりやっておきます。だいたいHerokuはスモールスタートもしくは3-5年くらいを目安に非機能設計しておくのが多いと思いますが、予想よりサービスがうまくいくと、それでも不足するケースもあります。

しかし、重要なのはビジネス成功に伴ってPlanを拡張するというポジティブな拡張。

とはいえ、RFMっぽくlast_loginが一定以上な「休眠ユーザ向け」にキャンペーンを打って月のログインがスパイクするようなケースなど、予期しきれないユーザ激増などもあると思うので、適度に流量と容量は見ておいた方が良いです。

モニタリングの選択肢としては、シンプルにダッシュボードを見るようにするのが手っ取り早いですが、

  • Log->Amazon Eventbridge IntegrationからSQSに貯めて、一定量キューが溜まったらMAU計算して通知するfunctionを書く
  • 1-off Dyno + Management APIで定期的に以下略

あたりができると良いと思います。思いついただけで作ったわけではないけど・・・

Freeで出来ることが非常に多いアドオンなので、とりあえず使ってみてから考えればよいかなと思います。

小西さんの書いた弊社過去ブログやサンプルサイトにも載っているので、ご参考までに。

ちょっとだけ気になるところ

ドキュメントやリファレンスはオープンかつそれなりに充実しているものの、使ってみて多少癖のあるところや認証基盤ならではの沼っぽいハマり方をするケースも時々あります。

よくあるトラブルシューティングやベストプラクティスがわかるようなナレッジベースが充実するとより良いな・・・と思ってます。

とはいえこれだけの機能をそれなりに小ささでも使うことができるのはやっぱりサービスとしての力があるのだと思いますし、こまめなアップデートも結構好きなので、たぶん来年も追っかけていくとは思います。それでは。

Image processing algorithm - Index -

技術開発室の馮 志聖(マイク)です。

I will list some image processing algorithm. And I will introduce detail from next time. It is important for understand how computer know the real world. Computer vision include acquiring, processing, analyzing and understanding digital images.

Edge detection

  • Canny
    f:id:fengchihsheng:20191213145831p:plain
    Canny
  • Sobel
    f:id:fengchihsheng:20191213145906p:plain
    Sobel X
    f:id:fengchihsheng:20191213145926p:plain
    Sobel Y
    f:id:fengchihsheng:20191213145948p:plain
    Sobel
  • Prewitt
    f:id:fengchihsheng:20191213150011p:plain
    Prewitt
  • Laplacian
    f:id:fengchihsheng:20191213150037p:plain
    Laplacian
  • Laplacian of Gaussian
    f:id:fengchihsheng:20191213150057p:plain
    Laplacian of Gaussian

Edge detection (RGB)

f:id:fengchihsheng:20191216101605p:plain
Edge detection (RGB)
f:id:fengchihsheng:20191216101626p:plain
Edge detection (RGB)
f:id:fengchihsheng:20191216101646p:plain
Edge detection (RGB)

Background subtraction

  • Differential
    f:id:fengchihsheng:20191213150431p:plain
    Differential
  • Otsu
    f:id:fengchihsheng:20191213150453p:plain
    Otsu
  • Binary
    f:id:fengchihsheng:20191213150512p:plain
    Binary
  • Adaptive Mean Thresholding
    f:id:fengchihsheng:20191213150532p:plain
    Adaptive Mean Thresholding
  • Adaptive Gaussian Thresholding
    f:id:fengchihsheng:20191213150546p:plain
    Adaptive Gaussian Thresholding

Corner detection

  • Harris operator
    f:id:fengchihsheng:20191213154003p:plain
    Harris operator
  • Shi and Tomasi
    f:id:fengchihsheng:20191213154026p:plain
    Shi and Tomasi
  • Features from accelerated segment test (FAST)
    f:id:fengchihsheng:20191213163337p:plain
    Features from accelerated segment test (FAST)

Blob detection

  • Laplacian of Gaussian (LoG)
    f:id:fengchihsheng:20191213173044p:plain
    Laplacian of Gaussian (LoG)
  • Difference of Gaussians (DoG)
    f:id:fengchihsheng:20191213173106p:plain
    Difference of Gaussians (DoG)
  • Determinant of Hessian (DoH)
    f:id:fengchihsheng:20191213173125p:plain
    Determinant of Hessian (DoH)
  • Maximally stable extremal regions (MSER)
    f:id:fengchihsheng:20191213180130p:plain
    Maximally stable extremal regions (MSER)
  • Optical character recognition (OCR)
    f:id:fengchihsheng:20191213182619p:plain
    Optical character recognition (OCR)

Ridge detection

  • Hessian matrix
    f:id:fengchihsheng:20191216095911p:plain
    Hessian matrix

Hough transform

  • Generalized Hough transform
    f:id:fengchihsheng:20191216113113p:plain
    Generalized Hough transform
  • Hough transform
    f:id:fengchihsheng:20191216115716p:plain
    Hough transform
  • Line Segment Detector (LSD)
    f:id:fengchihsheng:20191216120038p:plain
    Line Segment Detector (LSD)
  • Fast Line Detector (FLD)
    f:id:fengchihsheng:20191216120057p:plain
    Fast Line Detector (FLD)

Structure tensor

  • Gradient Structure tensor (GST)
    f:id:fengchihsheng:20191216145635p:plain
    Gradient Structure tensor (GST)

Feature description

  • Oriented FAST and rotated BRIEF (ORB)
    f:id:fengchihsheng:20191217105404p:plain
    Oriented FAST and rotated BRIEF (ORB)
  • AgastFeatureDetector
    f:id:fengchihsheng:20191217112104p:plain
    AgastFeatureDetector
  • Features from accelerated segment test (FAST)
    f:id:fengchihsheng:20191217112133p:plain
    Features from accelerated segment test (FAST)
  • Maximally stable extremal regions (MSER)
    f:id:fengchihsheng:20191217112200p:plain
    Maximally stable extremal regions (MSER)
  • KAZE Features
    f:id:fengchihsheng:20191217112231p:plain
    KAZE Features
  • Accelerated-Kaze Features (A-Kaze)
    f:id:fengchihsheng:20191217105341p:plain
    Accelerated-Kaze Features (A-Kaze)
  • Binary Robust Invariant Scalable Keypoints (BRISK)
    f:id:fengchihsheng:20191217105322p:plain
    Binary Robust Invariant Scalable Keypoints (BRISK)
  • Scale-invariant feature transform (SIFT)
    f:id:fengchihsheng:20191217105256p:plain
    Scale-invariant feature transform (SIFT)
  • Speeded up robust features (SURF)
    f:id:fengchihsheng:20191217112304p:plain
    Speeded up robust features (SURF)
  • Histogram of oriented gradients (HOG)
    f:id:fengchihsheng:20191216151117p:plain
    Histogram of oriented gradients (HOG)
  • Local Binary Pattern (LBP)
    f:id:fengchihsheng:20191217132849p:plain
    Local Binary Pattern (LBP)
  • Grey Level Co-occurrence Matrices (GLCM)
    f:id:fengchihsheng:20191216150811p:plain
    GLCM black space

Signal processing

  • Fast Fourier transform (FFT)
    f:id:fengchihsheng:20191217140122p:plain
    Fast Fourier transform (FFT) Magnitude Spectrum
    f:id:fengchihsheng:20191217140431p:plain
    High Pass Filtering
  • Discrete Fourier transform (DFT)
    f:id:fengchihsheng:20191217140451p:plain
    Discrete Fourier transform (DFT) Magnitude Spectrum
    f:id:fengchihsheng:20191217140755p:plain
    Low Pass Filtering

Shape detection

  • Morphological Snakes
    f:id:fengchihsheng:20191217132032p:plain
    Morphological Active Contours without Edges (MorphACWE)
    f:id:fengchihsheng:20191217132107p:plain
    Morphological Geodesic Active Contours (MorphGAC)

最後に

I will update more and more algorithm in this page. If I add new blog for detail algorithm, I will also update here too.