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

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

TeamsやZoomでカメラ画像を加工する方法 その3 アニメ風画像に変換するの巻

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

前回と前々回の投稿では、TeamsやZoomでカメラ画像を加工する方法をご紹介しました。これらの投稿では、笑顔や感情(表情)を検出してニコニコマークを表示するデモをご紹介しています。

cloud.flect.co.jp

今回、もう少し拡張して、画像をアニメ風に変換して表示する実験をしてみたので、ご紹介します。最初にネタバレしておくと、リアルタイムでアニメ風画像に変換するにはCPUだとちょっとラグが大きすぎて使いづらいかなと思います。(GPUだとマシになるのかは試してない。)

f:id:Wok:20200402051722p:plain

それでは早速ご紹介いたします。

アニメ風画像変換

半年ほど前にニュースメディアにも取り上げられていたようなので、ご存知の方も多いかと思いますが、写真をアニメ風に変換する手法が下記のページで公開されています。

github.com

このUGATITでは、単純なimage2imageのスタイル変換とは違い、GeneratorとDiscriminatorを用いるいわゆるGANの技術をベースに、独自のAdaLINという機能を追加することで形状の変化にも対応ができるようになったようです。

In our work, we propose an Adaptive Layer-Instance Normalization (AdaLIN) function to adaptively select a proper ratio between IN and LN. Through the AdaLIN, our attention-guided model can flexibly control the amount of change in shape and texture.

詳細は、本論文*1や解説記事*2を見ていただくとして、上記ページに公開されているトレーニング済みのモデルを使って、実際に変換してみるとこんな感じになります。

f:id:Wok:20200402042837p:plain

被写体が遠いとあまりうまく変換してくれないようです。また、おじさんはあまりうまく対応できていないみたいです。トレーニングで用いられたデータセットも上記ページで公開されていますが、若い女性に偏っているようなので、これが原因かと思われます。(私はまだおじさんではない、、、というのは無理があるか。)

実装の概要

上記のとおり、被写体(人物の顔)に近い画像(≒顔が画面の大部分を占める)にする必要がありそうです。今回は、前々回までにご紹介した顔検出機能により顔の場所を特定し、その場所を切り出してUGATITで変換をかけるという手順でやってみました。

f:id:Wok:20200402045508p:plain

実装の詳細は、下記で言及するリポジトリをご参照ください。*3

環境構築

前回までの記事を参考に、v4l2loopbackや、顔認識のモデルなどを準備しておいてください。

また、前回までと同様に、下記のリポジトリからスクリプトをcloneして、必要なモジュールをインストールしてください。

$ git clone https://github.com/dannadori/WebCamHooker.git
$ cd WebCamHooker/
$ pip3 install -r requirements.txt

UGATITのトレーニング済みモデルの配置

UGATITの公式ではTensorflow版とPyTorch版のソースコードが提供されていますが、トレーニング済みのモデルはTensorflow版しかないようです。これを取得して展開してください。なお、WindowsLinuxの通常のzip展開ツールだと展開に失敗するようです。Windowsを用いる場合は7zipだとうまく行くという報告がissueに上がっています。また、Macだと問題は発生しないようです。なお、Linuxは解決策は不明です・・・。*4

一応、正常に動くモデルのハッシュ値(md5sum)を記載しておきます。(多分ここが一番のつまずきポイントなので。)

$ find . -type f |xargs -I{} md5sum {}
43a47eb34ad056427457b1f8452e3f79  ./UGATIT.model-1000000.data-00000-of-00001
388e18fe2d6cedab8b1dbaefdddab4da  ./UGATIT.model-1000000.meta
a08353525ecf78c4b6b33b0b2ab2b75c  ./UGATIT.model-1000000.index
f8c38782b22e3c4c61d4937316cd3493  ./checkpoint

これらのファイルを上記git からcloneしたフォルダの、UGATIT/checkpointに格納します。このような感じになっていればOKです。

$ ls UGATIT/checkpoint/ -1
UGATIT.model-1000000.data-00000-of-00001
UGATIT.model-1000000.index
UGATIT.model-1000000.meta
checkpoint

ビデオ会議をしてみよう!

実行は次のように行います。オプションが一つ追加されてます。

  • input_video_num には実際のウェブカメラのデバイス番号を入れてください。/dev/video0なら末尾の0を入力します。
  • output_video_dev には仮想ウェブカメラデバイスのデバイスファイルを指定してください。
  • anime_mode はTrueにしてください。

なお、終了のさせ方はctrl+cでお願いします。

$ python3 webcamhooker.py --input_video_num 0 --output_video_dev /dev/video2 --anime_mode True

上のコマンドを実行するとffmpegが動き、仮想カメラデバイスに映像が配信されはじめます。

前回と同様に、ビデオ会議をするときにビデオデバイスの一覧にdummy〜〜というものが現れると思うのでそれを選択してください。 これはTeamsの例です。画面上右上にワイプで変換元の画像も表示しています。思ったよりちゃんとアニメ風に変換されて配信されますね。ただし、とても重く、ちょっと古めのPCだと1秒1コマとかそういうレベルです。*5 普通に運用するには厳しいかもしれません。いまのところは出落ち要員かな。いずれGPUでも試してみたいと思います。

f:id:Wok:20200402051909g:plain

最後に

在宅勤務が長引き、気軽なコミュニケーションがなかなか難しいかもしれませんが、こういった遊び心をビデオ会議に持ち込んで、会話を活性化させるのもいいのではないかと思っています。 もっといろいろできると思いますので、みなさんもいろいろ試してみてください。

*1:https://arxiv.org/abs/1907.10830

*2:【論文紹介】U-GAT-IT ざくっと理解するにはこのスライドが良さそう。

*3:2020/4/2現在、注ぎ足しつぎ足しで作っているので、ソースコードが汚い。どこかでリファクタリングします

*4:いずれも2020/4/2現在

*5:Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, 32G RAM

TeamsやZoomでカメラ画像を加工する方法 その2 Tensorflowで感情分析の巻

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

前回はTeamsやZoomでカメラ画像を加工する方法をご紹介し、 笑顔を検出してニコニコマークを表示するデモをご紹介いたしました。

cloud.flect.co.jp

今回は、これを少し拡張して、AI(Tensorflow)でリアルタイムに感情分析することに挑戦しましたので、ご紹介いたします。 具体的には、次のように、画像に写った人物の表情から悲しみや怒りと言った感情を読み取り、それに合わせた画像を画面に表示します。これでビデオ会議で言葉を発しなくても感情を伝えることができそうですね。(いや待て、、)

f:id:Wok:20200401172359g:plain

前提

前回の記事を参考に、v4l2loopbackなどを設定しておいてください。

Webカメラのフックの拡張

今回は、カメラで取られた人物の感情を分析して、対応する画像をビデオストリーム上の映像に表示します。 感情分析にはTensorflowを用いますが、次のサイトでトレーニング済みのモデルがMITライセンスで提供されているので、これを利用させてもらいましょう。

github.com

まず、最初に前回と同様に下記のリポジトリから、スクリプトをcloneして、必要なモジュールをインストールしてください。

$ git clone https://github.com/dannadori/WebCamHooker.git
$ cd WebCamHooker/
$ pip3 install -r requirements.txt

次に、先程のサイトから感情分析用のトレーニング済みのモデルを取得します。 なお、今回は、適切な画像を表示するために、性別判断も同時に行おうと思います。

$ wget https://github.com/oarriaga/face_classification/raw/master/trained_models/emotion_models/fer2013_mini_XCEPTION.110-0.65.hdf5 -P models # 感情分析用のモデル
$ wget https://github.com/oarriaga/face_classification/raw/master/trained_models/gender_models/simple_CNN.81-0.96.hdf5 -P models/ # 性別判定用のモデル

また、画像を再びいらすとやさんからお借りしましょう。

$ wget https://4.bp.blogspot.com/-8DirG_alwXo/V5Xc1SMykvI/AAAAAAAA8u4/krI2n_SWimUBGEyMWCw5kZZ-HzoUKrY8ACLcB/s800/pose_sugoi_okoru_woman.png -P images/
$ wget https://4.bp.blogspot.com/-EBpxVigkCCY/V5Xc1CHSeEI/AAAAAAAA8u0/9XIAzDJaQNU3HIiXi4PCPK3aMip3aoGyACLcB/s800/pose_sugoi_okoru_man.png -P images/

$ wget https://4.bp.blogspot.com/-HJ0FUQz67AA/XAnvUxSRsLI/AAAAAAABQnM/3XzIWzvW6L80aGB-geaHvAQETlJTAwkYQCLcBGAs/s800/business_woman2_4_think.png -P images/
$ wget https://3.bp.blogspot.com/-S7iQQCOgfWY/XAnvQWwBGtI/AAAAAAABQmc/z7yIqGjIQr88Brc_QNdOGsrJRLvqY1hcQCLcBGAs/s800/business_man2_4_think.png -P images/

$ wget https://4.bp.blogspot.com/-PQQV4wfGlNI/XAnvQBMeneI/AAAAAAABQmU/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png] -P images/
$ wget https://3.bp.blogspot.com/-QcDbWqQ448I/XAnvUT4TMDI/AAAAAAABQnE/_H4XzC4E93AEU2Y7fHMDBjri1drdyuAPQCLcBGAs/s800/business_woman2_2_shock.png -P images/

$ wget https://3.bp.blogspot.com/-dSPRqYvIhNk/XAnvPdvjBFI/AAAAAAABQmM/izfRBSt1U5o7eYAjdGR8NtoP4Wa1_Zn8ACLcBGAs/s800/business_man1_4_laugh.png -P images/
$ wget https://1.bp.blogspot.com/-T6AOerbFQiE/XAnvTlQvobI/AAAAAAABQm8/TYVdIfxQ5tItWgUMl5Y0w8Og_AZAJgAewCLcBGAs/s800/business_woman1_4_laugh.png -P images/

$ wget https://4.bp.blogspot.com/-Kk_Mt1gDKXI/XAnvS6AjqyI/AAAAAAABQm4/LQteQO7TFTQ-KPahPcAqXYannEArMmYfgCLcBGAs/s800/business_woman1_3_cry.png -P images/
$ wget https://4.bp.blogspot.com/-3IPT6QIOtpk/XAnvPCPuThI/AAAAAAABQmI/pIea028SBzwhwqysO49pk4NAvoqms3zxgCLcBGAs/s800/business_man1_3_cry.png -P images/

$ wget https://3.bp.blogspot.com/-FrgNPMUG0TQ/XAnvUmb85VI/AAAAAAABQnI/Y06kkP278eADiqvXH5VC0uuNxq2nnr34ACLcBGAs/s800/business_woman2_3_surprise.png -P images/
$ wget https://2.bp.blogspot.com/-i7OL88NmOW8/XAnvQacGWuI/AAAAAAABQmY/LTzN4pcnSmYLke3OSPME4cUFRrLIrPsYACLcBGAs/s800/business_man2_3_surprise.png -P images/

$ cp images/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png]  images/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png

上記のうち、最後のコマンドは、ファイル名にゴミ(末尾のカギカッコ)がついているので取り除いているだけです。

実行は次のように行います。オプションが一つ追加されてます。

  • input_video_num には実際のウェブカメラのデバイス番号を入れてください。/dev/video0なら末尾の0を入力します。
  • output_video_dev には仮想ウェブカメラデバイスのデバイスファイルを指定してください。
  • emotion_mode はTrueにしてください。

なお、終了のさせ方はctrl+cでお願いします。

$ python3 webcamhooker.py --input_video_num 0 --output_video_dev /dev/video2 --emotion_mode True

上のコマンドを実行するとffmpegが動き、仮想カメラデバイスに映像が配信されはじめます。

ビデオ会議をしてみよう!

前回と同様に、ビデオ会議をするときにビデオデバイスの一覧にdummy〜〜というものが現れると思うのでそれを選択してください。 これはTeamsの例です。表情に合わせて画面上部の文字列が変化し、合わせて対応する画像が表示されますね。大成功です。

f:id:Wok:20200401172359g:plain

最後に

在宅勤務が長引き、気軽なコミュニケーションがなかなか難しいかもしれませんが、こういった遊び心をビデオ会議に持ち込んで、会話を活性化させるのもいいのではないかと思っています。 もっといろいろできると思いますので、みなさんもいろいろ試してみてください。

参考

Tensorflowによる感情分析については次のサイトを参考にさせていただきました。 (このサイトではtensorflowjsでの紹介をされております)

book.mynavi.jp

TeamsやZoomでカメラ画像を加工する方法

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

現在FLECTでは、東京都知事の要請を受け、新型コロナウイルス感染予防対策および拡散防止のため原則在宅勤務となっております。 同じような対応を取られている企業様も多く、様々な困難があるかと思いますが、ぜひ力を合わせてこの難局を乗り越えていきたいと考えています。

さて、原則在宅勤務が長引いてくると、日頃行われていた何気ない会話ができないなど、ストレスも溜まってくることも考えられます。 そんな状況で一つでもクスっと笑えて息抜きができる状況が作れればよいなという思いで、一つ小ネタをご紹介します。

内容は、マイクロソフトのTeamsやZoomなどのビデオ会議において、ウェブカメラをフックして加工して配信する方法です。 私がLinux使いのため、今回はLinuxでのご紹介となります。他のプラットフォームも何れ何処かで紹介されると思います。

なお、「一つでもクスっと笑えて息抜きができる状況」を作るのも時と場合を選びますので、そこは自己責任でお願いします(^_^)/。

f:id:Wok:20200331140931g:plain

前提

大抵のLinuxシステムで問題なく動くと思いますが、私が作業した環境はDebianのBusterです。

$ cat /etc/debian_version
10.3

また、python3が入っていないようでしたら、導入しておいてください。

$ python3 --version
Python 3.7.3

関連ソフトウェアのインストール

仮想ウェブカメラデバイス

今回はv4l2loopbackという仮想ウェブカメラデバイスを用います。 github.com

仮想ウェブカメラデバイスと、実際のウェブカメラを識別する必要があるので、まずは実際のウェブカメラのデバイスファイルを確認しておきます。 下記の例だと、video0とvideo1が実際のウェブカメラに割り当てられているようです。

$ ls /dev/video*
/dev/video0  /dev/video1

それでは、v4l2loopbackを導入しましょう。 まずはgit cloneしてmakeしてインストールしてください。

$ git clone https://github.com/umlaeute/v4l2loopback.git
$ cd v4l2loopback
$ make
$ sudo make install

次に、モジュールをロードします。このとき、特にchromeで認識させるためには、exclusive_caps=1をつけてあげる必要があるようです。https://github.com/umlaeute/v4l2loopback/issues/78

sudo modprobe v4l2loopback exclusive_caps=1

これでモジュールがロードされたと思いますので、デバイスファイルを確認しておきましょう。下記の例だとvideo2が追加されていますね。

$ ls /dev/video*
/dev/video0  /dev/video1  /dev/video2
ffmpeg

仮想ウェブカメラデバイスにデータを送り込むにはffmpegを用いるのが一番簡単です。 apt-getなどでさくっと導入しておいてください。

Webカメラのフックと映像の配信

今回は、笑顔を検出したら画像加工を施してみようと思います。 笑顔を検出したら、映像上に笑顔マークを表示します。

まず、次のリポジトリのファイルをcloneしてモジュールをインストールしてください。

$ git clone https://github.com/dannadori/WebCamHooker.git
$ cd WebCamHooker/
$ pip3 install -r requirements.txt

ここからcascadeファイルを入手します。cascadeファイルの詳細はopencvの公式でご確認ください。 https://github.com/opencv/opencv/tree/master/data/haarcascades

$ wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml -P models/
$ wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_smile.xml -P models/

笑顔マークをいらすとやさんからお借りしましょう。

$ wget https://4.bp.blogspot.com/-QeM2lPMumuo/UNQrby-TEPI/AAAAAAAAI7E/cZIpq3TTyas/s160/mark_face_laugh.png  -P images/

こんな感じのフォルダ構成になってるといいです。

$ ls -1
haarcascade_frontalface_default.xml
haarcascade_smile.xml
mark_face_laugh.png
webcamhooker.py

実行は次のように行います。 --input_video_num には実際のウェブカメラのデバイス番号を入れてください。/dev/video0なら末尾の0を入力します。 --output_video_dev には仮想ウェブカメラデバイスのデバイスファイルを指定してください。 なお、終了のさせ方はctrl+cでお願いします。

$ python3 webcamhooker.py --input_video_num 0 --output_video_dev /dev/video2

上のコマンドを実行するとffmpegが動き、仮想カメラデバイスに映像が配信されはじめます。

ビデオチャットをしてみよう!

ビデオチャットをするときにビデオデバイスの一覧にdummy〜〜というものが現れると思うのでそれを選択してください。 これはTeamsの例。左右がそれぞれの参加者の画面だと思ってください。 左側が今回の仮想カメラデバイスを使っているユーザです。右側が受信側です。 ニッコリすると笑顔マークが出ますね。大成功(^_^)/。

f:id:Wok:20200331140931g:plain

最後に

対面のコミュニケーションが難しい今、ビデオチャットを使ってもっと楽しめたらいいですね。 今回は笑顔を検出して画像を加工する例で示しましたが、opencvやその他のツールを使って工夫次第でいろんな加工ができると思います。 ぜひいろいろ試してみてください!

参考

opencvでの笑顔検出はこちらを参考にさせていただきました。

qiita.com

opencvでの画像貼り付けはこちらを参考にさせていただきました。

qiita.com

AWS Lambdaを使って機械学習トレーニングの前処理を高速化した話

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

私は主に機械学習&AI案件を担当しており、クラウドインテグレーションを主な生業としているFLECTには珍しく、クラウドとは少し距離を置いたところにいるエンジニアでした。 具体的な経験としては、GCPインスタンスを立ち上げてモデルをトレーニングするくらい。 しかし、去年の12月の re:Invent2019でSageMakerの大幅アップデートがあり*1機械学習&AIのエンジニアもクラウドを使いこなせなければならない時代になったのだなぁと思うに至りまして、一念発起して、ここ1〜2ヶ月は結構AWSの勉強してました。 結果、この度認定資格(Associate3種)を取得いたしました。わーい(にっこり)。

これでクラウドエンジニアとしての入り口くらいには立てたかな? f:id:Wok:20200306104716p:plain

ということで、クラウド初心者ではあるのですが、ひとつ勉強がてらにとAWS Lambdaを使って機械学習の前処理の高速化を行ったので、これについて投稿します。

機械学習の前処理とAWS Lambda

機械学習のモデル開発では前処理という重要なステップがあります。 一言に前処理といってもこれはかなり広い概念で、対象データの抽出、データの結合、各種変換、テストデータの分割などが含まれます。各種変換の中には、より高い精度のモデルを作成するために与えられた特徴量からより良い特徴量を生成するFeature Engineeringと呼ばれる処理をすることも含まれます。 典型的なパターンでは、前処理のなかでも最初の方で必要なデータだけを抽出してデータ量を減らし、その後に続く処理の計算コストを抑えるという流れが良いとされています。*2

画像の分類モデルを開発する場合も同様に、最初に重複する画像を排除するなどして無駄な計算コストがかからないようにします。また、これはモデルの性能に影響が出る場合があるので、性能と計算コストとのトレードオフで実施するかを決めることになりますが、必要以上に大きなサイズの画像のリサイズを初めの方に行うことで、後に続く画像処理の計算コストを減らしたりもします。

私が関わっているある画像分類プロジェクトでは、前処理でどうしても人手を介さなければならないところがあるのですが、この場合でも予め、重複している画像を排除し、無駄に大きな画像を作業可能な大きさにリサイズすることで、人による作業コストも減らすことができます。 ただ、この画像のリサイズに結構な時間がかかるため、毎月大量に提供される学習用の画像データをリサイズする間、待ち時間が発生してしまうという課題がありました。 これまでは、単純に32コアのサーバを用意して、フル回転でリサイズ処理をすることで時間を短縮するようにしていましたが、それでも場合によっては数時間レベルの待ち時間が発生することがあり、帰宅前に処理を流してから帰る、なんてこともしていました。

ところが、AWS Lamdaはデフォルトで1000並列まで並列処理できると。それもサーバレスで。(AWSに詳しい方からすると、いまさら、、、という話かもしれませんが。) 1000並列なら単純計算でざっくり30倍くらいの速度で処理ができるはずです。処理時間が1/30になる!試してみたい!!ということで、評価してみました。

評価環境

今回は、手元にあった画像80739枚をリサイズする処理で評価してみたいと思います。 また、ざっくりとした、構成図は次のような感じです。 f:id:Wok:20200309125529p:plain

一方(左側)は、32コアのサーバで処理をします。*3。 ローカルディスクから画像を読み出してリサイズして書き戻します。なお、CPUネックになることは確認済みです(ドライブネックにはなっていない)。

もう一方(右側)は、AWS LambdaをAPI Gateway経由で呼び出せるようにし、クライアントからこれをコールすることで処理をします。 1000並列!とイキっていましたが、並列度の上限の1000を使い切ると別で行っている処理に影響が出てしまう可能性があるので、今回は300並列を上限として実施することにします。それでも10倍近い高速化はできると思われます。 画像は、S3から読み出して、リサイズして、書き戻します。

なお、今回使用したコードは次のリポジトリに置いてあります。

github.com

結果と考察

実際にリサイズをさせてみた結果です。 f:id:Wok:20200309120732p:plain

32コアサーバでは852秒かかっていたのですが、AWS Lambdaでは198秒で完了しました。 かなり高速化できたのですが、見込んでいた10倍の速度には程遠かったです。残念。

今回のクライアントは1画像ごとに1API(Lamda)を呼び出しているのですが、 AWS Lambdaは、並列実行数の上限を超えるリクエストを受け付けると例外を発生させてしまうので、一気に全画像数のリクエストを送信することができません。 今回は、クライアントからの呼び出しを300並列に抑えており(300のうち1つ終わったら次のリクエストを送るという形。)、この結果、HTTPの送受信のオーバヘッドが見えてきてしまっているのだろうと思います。

改善版と評価

そこで、次は、今回の画像の枚数を300で割った数分だけ、1回のリクエストでリサイズ要求すれば、HTTPのオーバヘッドも見えなくなるだろうと考えました。つまり、300個のリクエストで終わるように呼び出し方を変える。 しかし、API Gatewayタイムアウトが最大29秒という制限があり、1回のリクエストで多くの画像を処理しているとタイムアウトしてしまいます。 そこで、1200分割して投げれば1回のリクエストあたり大体25秒程度で完了することが実験的に特定できたので、この分割でHTTPのオーバヘッドを見えづらくすることとしました。 さらに、AWS Lambdaの中でもシーケンシャルに実行するとS3とのやり取りでオーバヘッドが見えてくると考えられるので、AWS Lambda内部も並列実行可能にします。これなら、かなり目論見に近い高速化ができるのではないだろうか。 結果がこちらです。左が32コアサーバでの処理時間。真ん中が1リクエストあたり1画像を処理するAWS Lambdaの場合の処理時間。右が今回の改善版の処理時間です。 f:id:Wok:20200309123428p:plain

今回の改善版は103秒で処理を完了させることができました。10倍とまではいきませんでしたがかなり近い値まで高速化できました。 まだチューニングの余地もあると思いますが、ざっくりと目論見の成果が得られたので、今回の評価はここまでとしたいと思います。

考察

今回、大量の並列実行が可能なAWS Lambdaを用いて、機械学習で困っていた前処理にかかる時間を短縮してみました。 結果、正しく並列度を上げる構成にすれば並列度に応じた高速化が実現できることがわかりました。(アムダールさん万歳。) この事実は当たり前といえばそれまでですが、AWS Lambdaはマネージドサービスであり、並列度を簡単に低コストで上げることができるので、より強力な考え方になると思います。

なお、機械学習の前処理の高速化というところだと、AWS EMRとか使う話もあるようですが、これはリソースの確保が必要で比較的ハードルが高いと思いますので、手間をかけずに高速化するにはAWS Lambdaでも十分かなという印象です。

最後に

今回は、私の担当に関するということで機械学習の前処理を対象にして実験してみましたが、特にこれに限定される効果ではありません。私は、このLambdaで並列実行する方式で、Tensorflowの画像分類モデルを動かし、大量のデータのAIによる事前ラベリングを高速に実施させています。これについても機会があれば、ご紹介したいと思います。

皆様も、並列実行可能ではあるがハードウェアの制限により並列度を上げられないという処理があるようでしたら一度お試しになると良いかと思います。

それでは。

余談

今回、短期間でAWS 認定資格 Associate3種を取ったのですが、これは、FLECTにはAWSSalesforceなどの各資格を取得するための支援プログラムがあること、そしてFLECTには有識者も多数在籍していることが有利に働いたかなと思っています。 ポイントを抑えた効率のよい勉強をするためには、環境って重要ですよね。

*1:AWS re:Invent2019のSageMaker関連のレポートはこちら。cloud.flect.co.jp

*2:参考:前処理大全 技術評論社 前処理だけで1冊の本に仕立てている。平易な文章ながら基本を網羅している印象。個人的にはベストプラクティスを「〜〜するとよいでしょう。」と優しい口調で諭してくれるところが好き。

*3:ここでは、300プロセスを32コア上に載せている。簡易的な評価なのでコンテキストスイッチのオーバーヘッドとか細かいことは無視しています。32プロセスだとIOの間コアが遊ぶので、まぁどっこいどっこいと信じてる。

SalesforceのプラットフォームイベントをPostmanから発行する

こんにちは、技術開発室の藤野です。

プラットフォームイベント(Platform Events)はSalesforceでイベント駆動型のアーキテクチャを実現する仕組みです。 特に、Salesforce外部からのレコード操作を疎結合にする手段として有用なのではと個人的に注目しています。

詳しくは公式ガイドを参照してください。

本記事では例として下記のシチュエーションを実現する一連の手順を紹介します。

  • Salesforceは「デバイス」カスタムオブジェクトを持つ
  • 「デバイス」カスタムオブジェクトは「シリアルナンバー」「状態」カスタム項目を持つ
  • バイスの識別は「シリアルナンバー」カスタム項目で行う
  • 「状態」カスタム項目をイベントプラットフォーム経由で更新する
  • REST APIで上記イベントを公開(publish)する

目次:

  • カスタムオブジェクトの作成
  • プラットフォームイベントの作成
  • プロセスの作成
  • 接続アプリケーションの作成
  • イベントの発行によるレコードの更新
    • 更新対象レコードの準備
    • PostmanでのREST APIの送信
  • まとめ
  • 補足

カスタムオブジェクトの作成

まず、イベントで更新するカスタムオブジェクトを作成します。 シリアルナンバーと状態を持つだけの単純なものです。

Salesforce上で[設定]-タブの[オブジェクトマネージャ]-[作成]-[カスタムオブジェクト]と進み、新規カスタムオブジェクトを作成します。

続きを読む

Amazon Alexaスキル内課金を公開したらEcho Show 5が届きました!

こんにちは、技術開発室のオクダです。本日のテーマは「Amazon Alexaスキル内課金を公開したらEcho Show 5が届きました!」についてお話しします。

f:id:flect170:20200131145950j:plain
Echo Show 5が10個、会社に届きました!

ことの発端

Amazonでは、2ヶ月〜3ヶ月毎に、スキル開発キャンペーンが実施されています。昨年11月にスキル開発キャンペーンを確認すると

「10月1日~12月10日までにスキル内課金機能を公開スキル(新規スキル・既存スキルどちらでも可)に実装し、かつキャンペーンにお申し込みをいただいた方には、Echo Show 5をもれなくプレゼントします。複数スキルが該当した場合は、その数分のEcho Show 5をプレゼント(最大1開発者につき10台まで)!」

developer.amazon.com

というものでした。「お〜、なんと太っ腹。」と感じた私は、これはスキル内課金を勉強して、Echo Show 5も貰えるのなら「一石二鳥」と思い、既存の公開済みスキルにスキル内課金機能を組み込み公開申請することにしました。

スキル内課金の概要と設計

スキル内課金には「買い切り型」「サブスクリプション型」「消費型」の3種類が存在します。

  • 買い切り型: 買い切り型は、制限されているスキル内の機能やコンテンツにアクセスできるようにします。買い切り型には有効期限がありません。

  • サブスクリプション型: 一定期間プレミアムコンテンツまたは機能にアクセスできるようにします。サブスクリプションをキャンセルするまで、繰り返し課金できます。

  • 消費型: 何度も課金、消費できるコンテンツや機能です。たとえば、ゲームのヒント、ゲーム内通貨、または追加ライフなどです。

ということです。作成前の設計で、どのスキル内課金を採用するか考えた際、「買い切り型」と「サブスクリプション型」はキャンセル処理を作成しなければならない。購入しているユーザーがいる限り、こちらの都合でスキルのサービスを打ち切ることができないし、スキルを公開し続けている限りは、スキルのメンテナンスも行っていかないといけない。それって面倒だな。と考えた私は「消費型」のスキル内課金を作成することにしました。

支払設定と納税情報

スキル内課金を作成して公開した場合、そのスキルをユーザーが使用してスキル内課金の支払いに応じると、作成者には当然収益が発生いたします。

スキル内課金を作成する場合は、スキル内課金を作成する前に、まず「支払い設定」と「納税情報」の提出が必要となってきます。

「支払い設定」と「納税情報」は、Alexa Developer Consoleの「支払い」から設定することができます。

f:id:flect170:20200130113341p:plain

支払い設定に進むと、2段階認証が求められます。

f:id:flect170:20200130114903p:plain

指示通りに、電話番号と入力し「ワンタイムパスワードを送信」を押すと、「支払い情報」入力画面が表示されますので、お手持ちの銀行口座情報を入力します。

f:id:flect170:20200130130906p:plain

入力が終わったら保存を押して、次に「税に関する情報」タブを押します。

f:id:flect170:20200130132345p:plain
スクリーンショットは ステータス:完了 となっていますが、最初訪れたときは、 ステータス:未登録 です。

「ロイヤリティ収入の納税者確認インタビュー」と「サービス収入の納税者確認インタビュー」の2つがありますが、スキル内課金の納税情報は「ロイヤリティ収入の納税者確認インタビュー」の承認だけで大丈夫です。

「ロイヤリティ収入の納税者確認インタビュー」入力画面です。

f:id:flect170:20200130140017p:plain

名前住所等を記入します。「私は米国以外のTINを持っています」は「税制上の居住国から発行を受けたされた納税者IDを指定」と書いてあるので、恐らく日本であれば「マイナンバー」がそれに該当すると思います。

マイナンバー」を所有していない場合は、「TINが指定されていない場合、米国源泉所得に対する法定源泉税率(30%)の減免措置は適用されません。」と警告されます。マイナンバーを登録することをお勧めします。

そして、必要事項を全て記入したら、オンライン上で署名して送信します。記入事項が全て正しければ、後日「ロイヤリティ収入の納税者確認インタビュー」の ステータス:完了 に変わるはずです。

(注記: 筆者は米国Social Security Numberを持っているので、即座に ステータス:完了 に変わりました。日本国のマイナンバーを登録した場合については、今回は確認しておりません。)

スキルの作成

「支払設定」と「納税情報」の登録が終わったら、スキルの作成に取り掛かります。

スキルの作成に関しては、

Alexaスキルのデザイン・開発・公開 - フレクトのクラウドblog re:newal

に詳しく、記載しておりますので、そちらのご確認よろしくお願いします。

スキル内課金の実装

今回は、スキル内課金を既存のスキルに組み込むので、スキル内にスキル内商品を登録しなければなりません。なぜ、商品と呼ばれているのか?1つのスキルに複数のスキル内課金を登録でき、ユーザーはお金を支払って購入するからです。

スキルの作成画面から「スキル内商品」を選択します。

f:id:flect170:20200130143402p:plain

画面右上の「スキル内商品の作成」を押します。

f:id:flect170:20200130143931p:plain

「新しいスキル内商品を作成」画面で「スキル内商品名」を入力し「スキル内商品の種類を選択」で今回は「消費型」を選択します。入力が終わったら再度、画面右上の「スキル内商品の作成」を押します。

f:id:flect170:20200130144155p:plain

スキル内商品画面です。商品IDは自動的に割り振られています。商品IDはコードでの開発の際に必要になってきます。

サポートされている言語を「日本語」を選択し「編集」を押します。

f:id:flect170:20200130172104p:plain

表示名は既に入力済みなので、それ以外を全て入力。アイコン画像をドラッグ&ドロップで登録後、右上の「保存」ボタンを押します。

f:id:flect170:20200130155455p:plain

「価格設定と購入可能状況」は、日本語スキルなので、 Amazon.co.jp を選択し、価格を日本円で設定します。今回は「99 ¥ JPY」と最安値を設定しています。99円より安い価格設定はできません。

「テスト」購入する際のテスト手順をここに記載します。全て入力が完了したら、右上の「保存」ボタンを押します。

f:id:flect170:20200130172346p:plain

スキル内商品画面に戻ると、先ほど作成した商品が「リンク可能な商品」に登録されており、商品右端のアクションの「有効化」をクリックします。

すると画面のようになり「リンクした商品」に作成した商品が一覧として表示されます。有効化しておかないとコードから呼び出すことができません。

f:id:flect170:20200130161417p:plain

開発者コンソールでスキルにスキル商品を組み込む

スキル商品を購入してもらうため、購入フローに移動するためのインテントを追加する必要があります。

今回追加したカスタムインテントは、購入用のインテントと返金用のインテントです。

購入しますか?の質問に対して「はい」「いいえ」とユーザーが言うと予想されるので、ビルドインインテントである「AMAZON.YesIntent」と「AMAZON.NoIntent」を追加します。

f:id:flect170:20200130164141p:plain

AMAZON.YesIntent」「AMAZON.NoIntent」には「はい」「いいえ」以外に「そうです」「いいよ」「そうじゃない」のような一般的な肯定語、否定語が含まれており、別途カスタムインテントを作成する必要がないので便利です。

標準ビルトインインテント | Alexa Skills Kit

AWS Lambdaコードにスキル商品を組み込む

スキル作成時に「Alexa-hostedスキル」として作成すると、開発者コンソールの「コードエディタ」で、AWS Lambdaコードを編集、デプロイできます。

しかし、Persistent Attributes(永続属性)を保存するのにAmazon DynamoDBを使用するなど、手の込んだことをする場合は「ユーザー定義のプロビジョニング」でスキルを作成することをお勧めします。

詳しいAWS Lambdaコードの実装に関しては、

Alexaスキルのデザイン・開発・公開 - フレクトのクラウドblog re:newal

に詳しく、記載しておりますので、そちらのご確認よろしくお願いします。

AWS Lambdaコードに購入リクエストハンドラーを追加します。下記コードでは「購入しますか?」の質問に対し、ユーザーが「はい」と応答した場合のコードです。

f:id:flect170:20200203084532p:plain
購入リクエストハンドラー

SendRequestDirectiveのnameに "Buy" を指定します。

payloadのInSkillProductにスキル商品であるproductIDを渡します。これにより、購入フローが呼び出され、購入処理に入ります。

tokenは必須ですが、値は任意で何でも構いませんが、推測されにくいものにしましょう。

スキル開発者は購入リクエストを呼び出すだけで、特に購入処理を作成する必要がありません。

次は、AWS Lambdaコードに購入レスポンスハンドラーを追加します。下記コードでは、購入レスポンスハンドラーから購入処理結果を取得しています。

f:id:flect170:20200201100123p:plain
購入レスポンスハンドラー

まず、購入処理が正常に行われたか確認します。確認方法は、request.status.code == "200" であることを確認します。"200" でない場合は「購入に失敗しました」というメッセージを表示するようにしています。

次に、request.payload.get("purchaseResult") が ACCEPTED.value であるか ALREADY_PURCHASED.value であるかを確認しています。購入処理が受理された、またはもう既に購入済みである場合、サービスを提供しています。それ以外の場合は、購入処理が却下されたということで、スキルの終了処理を行っています。

次は、AWS Lambdaコードに返金リクエストハンドラーを追加します。下記コードではユーザーが「返金して」の発話に対し、スキルが返金リクエストを行います。

f:id:flect170:20200203084540p:plain
返金リクエストハンドラー

SendRequestDirectiveのnameに "Cancel" を指定します。

payloadのInSkillProductにスキル商品であるproductIDを渡します。これにより、返金フローが呼び出され、返金処理に入ります。

tokenは必須ですが、値は任意で何でも構いませんが、推測されにくいものにしましょう。

スキル開発者は返金リクエストを呼び出すだけで、特に返金処理を作成する必要がありません。

最後に、AWS Lambdaコードに返金レスポンスハンドラーを追加します。下記コードでは、返金レスポンスハンドラーから返金処理結果を取得しています。

f:id:flect170:20200203084547p:plain
返金レスポンスハンドラー

購入処理と同様、返金処理が正常に行われたか確認します。確認方法は、request.status.code == "200" であることを確認します。"200" でない場合は「キャンセルリクエスト処理中にエラーが発生しました。」というメッセージを表示するようにしています。

返金処理が正常に処理された場合、返金レスポンスのpayloadから返金処理が正しく行われたか確認し、その後の処理を割り振っています。

購入リクエスト、返金リクエストを追加した際の注意点

スキル開発で、1つ前の会話を記録させるためにSession Attributes(セッション属性)を使用して開発している場合、購入処理や返金処理に移動した際、一旦それまでの会話のセッションは切断されてしまいます。購入処理や返金処理が終了し、レスポンスが返ってきた時には、新たなセッションが作成されています。購入処理や返金処理の前の会話を保持しておきたい場合は、Persistent Attributes(永続属性)に変更してAmazon DynamoDBに会話内容を保存するようにしましょう。

いよいよ、公開申請

スキルを作成し、AWS Lambdaの実装も終わりましたら、いよいよ公開申請です。

スキルの公開申請に関して、詳しくは

Alexaスキルのデザイン・開発・公開 - フレクトのクラウドblog re:newal

をご覧ください。スキル内課金を含むスキルを公開申請するときは、いくつか異なる点があります。

まず、Alexa Developer Console画面の、公開 > スキルのプレビュー > プライバシーとコンプライアンス で「このスキルを使って何かを購入をしたり、実際にお金を支払うことができますか? *」の質問に対し、

「はい」を選択しなければなりません。

f:id:flect170:20200131090857p:plain

あとは、無料スキル公開申請と同じですが、最初に説明した「支払設定」と「納税情報」、先ほど説明した「プライバシーとコンプライアンス」に不備があると、認証 > 検証 でエラーメッセージが表示されますので気を付けてください。

認証 > 検証 も無事に成功しましたら、いよいよ、申請です。申請画面で新たに追加された選択項目についてご説明いたします。

f:id:flect170:20200131091303p:plain

以前は「公開の詳細設定」と言う項目がありませんでした。今までは認定されると即公開となっていましたが「公開の詳細設定」で「認定と公開」か「認定のみ」を選べるようになりました。

新たに設けられた「認定のみ」の使いどころは、例えば、○月×日の夜中0時からスキルを公開して、先着10名様にスキル内で使用できるスペシャルアイテムをプレゼントみたいな使い方が可能です。

スキルの認定作業は昼間しか行われておらず、今までは、いつ公開されるかは認定されるまではわかりませんでした。「認定のみ」が選択できるようになり、公開開始日時をこちらでコントロールできるのは、非常に有り難い機能です。

「公開の詳細設定」を選択したら右上の「はい、審査を申請します」ボタンを押します。審査は大抵は翌営業日に行われ、問題なければ審査日と同じ日に認定を受けることができます。

f:id:flect170:20200206165952p:plain
公開承認を受けると開発者宛に上記のようなemailが届きます。

Alexaスキルの認定に関するフィードバック

ここでは「認定に関するフィードバック」について、スキル内課金特有のフィードバックを、具体例を交えながら説明していきます。

多数のフィードバックがあり長くなりますが、参考になると思いますので書かせていただきます。

その1

フィードバック:スキル内課金機能を提供しているスキルは、スキルコンテンツの一部を無料で提供する、もしくはスキルの無料トライアルのバージョンを提供する必要があります。スキル内課金についての詳細はこちらを参照してください。

商品、コンテンツ、サービスを購入できるスキルの要件 | Alexa Skills Kit

原因:作成したスキルが、スキル呼び出し後、スキル商品を購入しないとサービスを利用できないスキル構成になっていました。

対処方法:スキルを1日3回までは無料で利用できるように変更し、4回目からスキル商品の購入を促すように変更しました。

その2

フィードバック:スキル内課金商品のアップセル中に金額情報の提示はできません。スキルの商品購入プロンプトと同様の内容を繰り返さないよう、適宜削除、及び変更してください。

スキルガイドラインでは直接説明されていませんが、以下の文脈から必要ということなのでしょう。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:金額は購入フロー後、Alexa側で提示するので、スキル側では金額を表示しないでください。ということです。

対処方法:購入リクエストから購入フローを呼び出す際に表示していたメッセージから金額を全て削除しました。

その3

フィードバック:スキル内課金商品のアップセル中には、スキルは消費型の課金商品について、明確な数量や時間等の情報を提供する必要があります。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:消費型の課金商品を作成したため、まとめ買いができたりするので、数量等の情報を提供してください。と指摘を受けました。消費型故の指摘内容です。

対処方法:今更、買い切り型やサブスクリプション型に変更するのは面倒だったので、1日4回目からでないと購入リクエストできない。購入後直ちにサービスが実施されスキル商品の在庫がいつも0である状態を説明し、数量情報を提示する義務は無いと説明し、再申請を行いました。

その4

フィードバック:申請いただいたスキルはユーザーに対し、直接スキル内課金商品を購入する方法を提供していません。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:スキル内課金商品の購入方法を提示していませんでした。

対処方法:スキル内課金商品購入ヘルプインテントを作成し、ユーザーが購入ヘルプインテントを呼び出した際に「1日4回以上ご利用になった場合のみ、購入することができます。」というメッセージを追加しました。

その5

フィードバック:申請いただいたスキルはユーザーに対し、購入したスキル内課金商品の返金やキャンセルについての情報を提供していません。

スキルコードにISPのサポートを追加する | Alexa Skills Kit

原因:スキル内課金は消費型であり返金処理は必要ないと考えていたが、返金情報を提供してください。と指摘されました。

対処方法:このスキル内課金商品は常に在庫を抱えない状態になっているので、返金情報を提供する義務はありません。と再申請を行ったが「例え在庫が常に存在しない状態のスキルであっても、返金処理を提供する義務はあり、その情報を提供していないスキルを公開認定することはできません。」と言われました。渋々、返金リクエスインテントを作成し、返金情報を提供するメッセージを追加しました。

反省点:常に在庫が発生しない状況であれば、返金処理を作る必要はないと考えていましたが、甘かったです。初動態勢、スキル内課金の設計をミスしてしまいました。

その6

フィードバック:言い回しの異なるサンプル発話が不足しています。スキルを確実に起動させ、多くのユーザーのリクエストに対応するためには、サンプル発話を充実させる必要があります。今回ご提示いただいたサンプル発話のみでは、スキルが確実に起動しない可能性があります。以下のようなサンプル発話を追加して、再度ご申請ください。

サンプル発話とカスタムスロットタイプの値を作成する際のベストプラクティス | Alexa Skills Kit

原因:スキル内課金「しりとり待った」を購入しますか?の質問に対し、AMAZON.YesIntentとAMAZON.NoIntentしか用意していなかったので、ユーザーが「購入します」「購入する」「購入しません」「購入しない」と言った場合、購入リクエストやキャンセルリクエストが呼ばれなかった。

対処方法:AMAZON.YesIntentに「購入します」「購入する」とAMAZON.NoIntentに「購入しません」「購入しない」を追加し、ユーザーが「購入します」や「購入しません」と言った場合でも、購入リクエストやキャンセルリクエストを呼び出されるように変更しました。

補足:ビルドインインテントにカスタムサンプルフレーズを追加できるので便利です。

その7

フィードバック:申請いただいたスキルは、一度スキルを終了し、再度起動した際に、すでに購入されているスキル内課金商品の使用をユーザーに提供していません。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:サービスを提供した後、スキル内商品は消費されているという説明がありませんでした。

対処方法:その4 の追加で、スキル内課金商品購入ヘルプインテントが呼び出された際のメッセージに「この商品は消費型であり1回利用すると、この商品は消費され無くなります。」というメッセージを追加しました。

その8

フィードバック:スキル審査の結果、下記のスキル内課金商品についてアップセルが提供されていないと判断されました。現在、スキルから提示される情報だけでは、ユーザーがどのようにスキル内課金商品を購入できるかが不明瞭です。購入プロセスへ誘導するためのフレーズをスキルからのプロンプトにご提示ください。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:その2 とその3 に対応しましたが、その結果として、スキル内課金商品説明だけでなく、購入プロセスに誘導するフレーズをメッセージとして提示してくださいと指摘されました。

対処方法:購入リクエストハンドラーを呼び出すためのフレーズを、スキル内課金商品購入ヘルプインテントが呼び出された際のメッセージに追加しました。

その9

フィードバック:次のインテント名を使用してスキルを起動した際、スキルの応答にリクエストとの関連性がありません。

原因:しりとりスキルで、ユーザーがしりとりの途中で「返金」と発話した際に「んで終わる言葉を言いましたね。あなたの負けです。しりとりを終了します。」としりとりを終了してしまい、返金処理に応じなかったのが原因でした。

対処方法:ユーザーが「返金」と言った場合に、返金リクエストハンドラーを呼び出し、返金処理に応じるように変更しました。

その10

フィードバック:スキルのホームカードにスキル内課金商品の金額を含めないようにしてください。

原因:その2 と類似却下理由。金額は返金フロー後、Alexa側で提示するので、スキル側では金額を表示しないでください。ということです。

対処方法:返金リクエストから返金フローを呼び出す際に表示していたメッセージから金額を全て削除しました。

その11

フィードバック:スキル内課金商品のアップセル中に、そのスキル内課金商品をスキルでどのように使用できるかの情報を提供する必要があります。「1回だけしりとりを待ったできます。」という情報だけでは、1回負けた後も継続することが可能ということがユーザーにわからない可能性があります。課金商品の機能がアップセルで明確にわかるよう修正してください。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:「しりとりを待った、を購入すると、1回だけしりとりを待ったできます。」という説明が不適切と判断されました。

対処方法:「しりとりを待った、を購入すると、しりとりに負けた時、1回だけしりとりをやり直すことができます。」に変更しました。

その12

フィードバック:スキルは、スキル内課金商品の購入が正常に完了したにも関わらず、その商品の機能をユーザーに提供していません。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:ユーザーがスキル内課金「しりとり待った」を購入後、即しりとりを終了し、再度しりとりスキルを起動し「ん」で終わる言葉でわざと負けた場合に、購入したはずの「しりとり待った」を使用できませんでした。

対処方法:スキル内課金商品の在庫状況を「AWS Lambdaコードにスキル商品を組み込む」で説明したPersistent Attributes(永続属性)を保存するようにAmazon DynamoDBを使用しました。

「Alexa-hostedスキル」で作成した場合、Alexa Developer ConsoleにhostされたAWS Lambdaを使用するため、自前で用意したAmazon DynamoDBへのアクセスは困難となります。

「ユーザー定義のプロビジョニング」でスキルを作成した場合、AWS LambdaもAmazon DynamoDBも同じユーザーであるためIAMロール設定一発で、Amazon DynamoDBへのアクセスが自由にできます。

Persistent Attributes(永続属性)を使用するスキルを作成する場合は、「ユーザー定義のプロビジョニング」でスキルを作成することをお勧めします。

その13

フィードバック:申請いただいたスキルは、ユーザーが購入した消費型の課金商品の使用可能な残量を示すオプションを提供していません。

スキル内課金の認定ガイド | Alexa Skills Kit

原因:その3 と類似却下理由。購入後、スキル側がサービスを提供して終了するスキルは問題無かったが、「しりとり」のように商品購入後、しりとり再開直後にユーザーの意思でスキルを終了できてしまう場合は、再開後、課金商品の残量を示す義務がありました。

対応方法:スキル内課金商品の在庫をAmazon DynamoDBに保存し、再開後、課金商品の残量をAmazon DynamoDBから取得しユーザーに提示するように変更しました。

その14

フィードバック:申請いただいたスキルは、アップセルメッセージが提供された後の取引のキャンセルが適切に処理されていません。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:ユーザーが課金商品を「購入する」と言い、スキルが「...同意して、購入しますか?」と確認した後、ユーザーが「いいえ」と言った場合に「そうですか、お役に立てず残念です。」とスキルを終了してしまった。

対応方法:スキルを終了する前に「購入はキャンセルされました。」というメッセージを追加しました。

その15

フィードバック:スキル内課金商品は、無料版のスキルに何らかの機能が追加される必要があります。スキル内で負けた際にもアップセル後に購入せずに単語を発話するとしりとりが続けられました。上記を修正、アップデートした上で再申請してください。

原因:ユーザーがしりとりに負けた後、スキルが課金商品を購入するか確認した際に「AMAZON.YesIntent」と「AMAZON.NoIntent」以外の言葉を話しかけた場合、しりとりを再開できてしまったのが問題でした。

対応方法:しりとりに負けた後、課金商品を購入するか確認中の時は「AMAZON.YesIntent」と「AMAZON.NoIntent」だけを受け付けるようにし、その他の言葉を話しかけられた場合は、再度「...同意して、購入しますか?」と確認するように変更しました。

その16

フィードバック:スキル審査の結果、下記のスキル内課金商品についてアップセルが提供されていないと判断されました。スキルを申請する際には、すべてのスキル内課金商品にアクセス出来るよう、開発者ポータル上のテストの手順でそちらの手順の記載をお願いします。アップセルを通さず、購入プロンプトが返ってきます。アップセルを追加してください。またその際には消費型の課金商品について、明確な数量等の情報を一緒に提供する必要があります。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:購入リクエスインテントが呼ばれたら直ぐに購入フローに移っていた。購入フローに移る前にその課金商品について説明しなければならなかった。

対応方法:購入リクエスインテントが発話された場合は、購入リクエストハンドラーを呼び出して購入フローに移動する前に、その課金商品についての説明を追加し、必要に応じて課金商品の在庫がある場合は、その残量をお知らせするようにしました。

その17

フィードバック:スキルは、ユーザーがアップセルに同意したにも関わらず、スキル内課金プロセスを正常に完了する事が出来ませんでした。スマートフォンのAlexaアプリや Alexa for PC ではスキル内商品を購入できません。ただ、ユーザーが購入済みのコンテンツを利用することは可能です。そのため、4回目以上使用の場合は、Echoデバイス側で商品の購入ができて、且つスマートフォン端末で購入済みの商品を使用できるようにする必要があります。ご確認後、修正をお願いします。

スキル内課金の優れたカスタマーエクスペリエンスのデザイン | Alexa Skills Kit

原因:「ここどこなの」というスキルを無料公開していた時は、位置情報が取得できるスマートフォンのAlexaアプリのみ利用可能にしていたが、Alexaアプリからはスキル内課金商品が購入できないため、1日4回以上利用することができなくなっていました。

解決方法:Echoデバイス側でも「ここどこなの」のスキル利用を有効にし、スキル内課金商品の購入のみ利用可能としました。購入したユーザー情報と課金商品の在庫数はAmazon DynamoDBに格納し、スマートフォンのAlexaアプリでは1日4回以上利用する場合は、購入したユーザー情報と課金商品の在庫数を確認後、購入済みの場合のみ、現在の位置情報から住所をお知らせするサービスを提供するように変更しました。

その18

フィードバック:Amazonにとって知的財産権の保護は非常に重要です。スキル内で使用されているコンテンツを販売する権利があることを確認できませんでした。販売する権利を持っていることを示す文書を提示してください。

原因:「年度別新入社員」というスキルで、年度別の新入社員のタイプを説明するスキルを作成しました。元ネタは「公益財団法人・日本生産性本部」の「新入社員意識調査・特徴とタイプ」を引用させていただいていました。

www.jpc-net.jp

以前同名のスキルを無料スキルとして公開していた時は「公益財団法人・日本生産性本部」より「我々が公開しているコンテンツに関して引用していただいて問題ございません。」とお墨付きをいただいていたが、有料スキルでの引用に関して、今回尋ねたところ「有料コンテンツでの引用は認めておりません。」との回答でした。

対応方法:前回スキルを公開した時は、あっさりコンテンツ利用を認めていただいたので、今回も認めていただけると思ったが、甘かったです。「年度別新入社員」のスキル内課金化を断念しました。

スキル内課金の組み込んだスキル一覧

  • サブリナの通訳

Amazon CAPTCHA

  • 全国の郵便番号検索

Amazon CAPTCHA

  • よく当たるタロット占い

Amazon CAPTCHA

https://www.amazon.co.jp/Flect-%E8%A5%BF%E6%9A%A6%E3%83%BB%E5%85%83%E5%8F%B7%E5%A4%89%E6%8F%9B/dp/B07PYQ3Z94/

  • 意地悪しりとり

https://www.amazon.co.jp/Flect-%E6%84%8F%E5%9C%B0%E6%82%AA%E3%81%97%E3%82%8A%E3%81%A8%E3%82%8A/dp/B0823JL3RK/

  • よく分かる全国の天気

Amazon CAPTCHA

  • ボー読み般若心経

https://www.amazon.co.jp/Flect-%E3%83%9C%E3%83%BC%E8%AA%AD%E3%81%BF%E8%88%AC%E8%8B%A5%E5%BF%83%E7%B5%8C/dp/B07PMNK53T/

  • 星座占い

Amazon CAPTCHA

  • 太陽系星座チェック

Amazon CAPTCHA

  • ここどこなの

https://www.amazon.co.jp/Flect-%E3%81%93%E3%81%93%E3%81%A9%E3%81%93%E3%81%AA%E3%81%AE/dp/B07Q5D55G8/

2020年1月27日(月)、アマゾンジャパン合同会社から、Echo Show 5が10個届きました。

f:id:flect170:20200131145509j:plain
アマゾンジャパン合同会社より荷物が届きました。

f:id:flect170:20200131145923j:plain
荷送人はAlexaスキル開発者キャンペーンです。

f:id:flect170:20200131145935j:plain
確かにEcho Show 5が10個入っていました。

f:id:flect170:20200131145950j:plain
勢い余って、Echo Show 5を10個、ピラミッド状に並べてみました。

スキル内課金を作成した感想

今回、Alexaスキル開発者キャンペーンが引き金で、Echo Show 5欲しさに、スキル内課金の勉強がてら既存の無料スキルにスキル内課金を組み込み、公開申請しました。

無料スキル公開申請同様、スキル内課金の公開申請ならではの、フィードバック(申請却下)をたくさん頂きました。

今回もサンドバック状態で滅多打ちにされましたが、雨にも負けず、風にも負けず、めでたく10個のスキル内課金が組み込まれたスキルを無事に公開することができました。

このブログを最後まで、読んでいただき、ありがとうございます。AMAZON Alexaに限らず、音声アシストを作成依頼したいところをお探しの場合は、是非弊社までご連絡ください。

クラウド最安 GPU で Jupyter Notebook を実行するには

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

皆さんは GPU を使っていますか?筆者の周辺では年を追うごとに GPU の需要が高まっています。用途のほとんどはディープラーニングのモデルトレーニングです。特に画像系AI機能のモデルを作成する場合は、 GPU は必須です。

しかしこの GPU には問題があります。価格です。AWS などのクラウドベンダー各社は GPU インスタンスをラインアップしていますが、CPU インスタンスと比較するとかなりの高価格です。(設備の調達価格や電気料金を考えると仕方ないのかもしれませんが。。)最小販売単位が大きいことも悩みどころで、AWS と Azure は最低 4 CPU 構成からとなっています。本番稼働では必要でしょうが、学習用には過剰なのです。

筆者の知る限り、 GPU を一番小口で提供しているのは GCP で、1コアの n1-standard-1 に NVIDIA Tesla T4 をひとつ接続する構成が最小です(昨年までは K80 が最安だったが、最近 T4 が値下げされた)。これまで筆者はこのぐらいのインスタンスで Jupyter Notebook (以下、 notebook)を使って各種の手元検証をしていました。しかしこのような使い方では稼働率が低く、高額な GPU がしばしばアイドルになっていることを心苦しく思っていました。

この状況を改善するひとつの方法は「余剰割引」の利用です。AWS なら Spot Instance、 GCP なら Preemptible GPU になります。通常価格の半額以下で調達できます。ただしこの余剰割引にも問題があり、ベンダの都合で突然解放されてしまうのです(直前に予告通知はある)。元々待機状態の設備の格安提供なので、ベンダの都合でいつでも取り上げられる約束なのです。その時が来たら、ローカルストレージも全て解放されてしまいます。手元検証作業をしていたら突然中止、設定やり直し、というのは、心理的にもよろしくありません。

廉価な余剰割引を、安心して使う方法はないものでしょうか。

バッチ化、コンテナ化、GKE ジョブ化 で挑戦

そこで筆者が思いついた解決方法は、以下のようなものです。

  • Notebook は廉価な CPU インスタンスで基本調査した後、バッチ実行する。
  • このバッチ実行をコンテナ化する。
  • このコンテナを、余剰割引 GPU (preemptible GPU) ノードで GKE ジョブとして実行する。

図に書くと以下のような感じです。
f:id:masashi-sato-flect:20200131130140p:plain

GKE (Google Kubernetes Engine) は GCP が提供する Kubernetes ランタイムです。 今回は GCPGPUを利用しますので、KubernetesGCP を使うのが最も手間がないと考えました。

こうすれば、以下のメリットが得られるはずです。

  • GPU がアイドルでも preemptible GPU なので出費が少ない。
  • Kaggle や巷ブログに豊富に出回っている notebook をそのまま評価できる。
  • ジョブ実行中にノードが解放されたら(この動作は preemption と呼ばれる)、ノード障害として検出され、 GKE がジョブを自動で再実行する。

結果を先にお話しすると、この仕組みは一通り動作しました。筆者は今後はこの方法で GPU 技術の評価をしていきたいと考えています。もしかしたら同じような悩みをお持ちの方もいらっしゃるかもしれないと思いましたので、以下にその手法を説明させていただきます。

Notebook をバッチ実行する

これは実は楽勝です。Jupyter Notebook がインストールされた環境で、シェルコマンドで以下のように打つだけです。

jupyter nbconvert --ExecutePreprocessor.timeout=600 --to notebook --execute mynotebook.ipynb

Pythonコードで実行することもできます。

import nbformat
from nbconvert.preprocessors import ExecutePreprocessor

with open(LOCAL_FILE_PATH) as fIn:
    nb = nbformat.read(fIn, as_version=4)
    ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
    ep.preprocess(nb)
    with open(PROCESSED_FILE_PATH, 'w', encoding='utf-8') as fOut:
        nbformat.write(nb, fOut)

既定のタイムアウトは短い(60秒)ので、指定するようにします。

バッチ実行をコンテナ化する

Jupyter Notebook のコンテナは各種ありますが、今回は以下の TensorFlow 公式コンテナを使用しました。

  • tensorflow/tensorflow:latest-gpu-py3-jupyter

この上で、以下のような Python スクリプトを実行します。環境変数で Cloud Storage の所在を受け取ってダウンロード、 Notebook を実行してアップロードして返すだけの簡単な内容です。

import os
from google.cloud import storage
# https://nbconvert.readthedocs.io/en/latest/execute_api.html#executing-notebooks-using-the-python-api-interface
import nbformat
from nbconvert.preprocessors import ExecutePreprocessor

def download_blob(bucket_name, source_blob_name, destination_file_name):
    # 中略
    # https://cloud.google.com/storage/docs/downloading-objects
# end of download_blob

def upload_blob(bucket_name, source_file_name, destination_blob_name):
    # 中略
    # https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-python
# end of upload_blob

def main():
    LOCAL_FILE_PATH     = 'test.ipynb'
    PROCESSED_FILE_PATH = 'test_processed.ipynb'
    BUCKET_NAME         = os.environ['BUCKET_NAME']
    DOWNLOAD_FILE_PATH  = os.environ['DOWNLOAD_FILE_PATH']
    UPLOAD_FILE_PATH    = os.environ['UPLOAD_FILE_PATH']

    download_blob(
        BUCKET_NAME,
        DOWNLOAD_FILE_PATH,
        LOCAL_FILE_PATH
    )

    with open(LOCAL_FILE_PATH) as fIn:
        nb = nbformat.read(fIn, as_version=4)
        ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
        ep.preprocess(nb)
        with open(PROCESSED_FILE_PATH, 'w', encoding='utf-8') as fOut:
            nbformat.write(nb, fOut)
            upload_blob(
                BUCKET_NAME,
                PROCESSED_FILE_PATH,
                UPLOAD_FILE_PATH
            )
        # end of with fOut
    # end of with fIn
# end of main

if __name__ == "__main__":
    # execute only if run as a script
    main()

コンテナの Dockerfile は以下のようになりました。こちらも上記の Python スクリプトを動かすだけの簡単な内容です。

FROM tensorflow/tensorflow:latest-gpu-py3-jupyter
COPY requirements.txt /tf
COPY tf_exec.py /tf
RUN pip install --upgrade pip
RUN pip install -r /tf/requirements.txt
ENTRYPOINT python /tf/tf_exec.py

requirements.txt は、Cloud Storage の SDK だけです。

google-cloud-storage

以下コマンドでコンテナを作って Container Registry に上げます。

docker build --force-rm --tag=test:latest.
gcloud auth configure-docker
docker tag test:latest gcr.io/${PROJECT_ID}/${REPOSITORY_NAME}:latest
docker push gcr.io/${PROJECT_ID}/${REPOSITORY_NAME}:latest

コンテナを、preemptible GPU ノードで GKE ジョブとして実行する

このステップは少々苦労しました。以下のような課題がありました。

  • ジョブに GCP の権限を付与する必要があった。
  • CPU ノードも用意する必要があった。
  • ジョブが preemptible GPU ノードに配置されるように指定する必要があった。

ジョブに GCP の権限を付与する必要があった

今回実行するコンテナは Cloud Storage をアクセスしますので、専用のサービスアカウントを作成し、 Storage Object Admin のロールを付与しました。Container Registry もアクセスしますが、こちらは Cloud Storage の権限がそのまま適用されるとありますので、追加作業はありません。

このサービスアカウントを、GKE ノードを作成するときに指定します(後述)。

CPU ノードも用意する必要があった

Kubernetesではシステム管理のコンテナが常時活動しており、これらを実行しているインスタンスで preemption が発生すると、回復までにより多くの時間(5~10分ぐらい?)を要します。ダウンタイムを減らすには、常時起動の管理ノードと preemptible なノードの両方を用意します。管理ノードには小さな廉価なインスタンスを使用しました。

ジョブが preemptible GPU ノードに配置されるように指定する必要があった

Kubernetes は taint (忌避要件?)と toleration (忌避要件許容?)という2つの概念を用いてジョブのアサイン先を条件設定することができます。今回の場合は、以下の作戦を取ります。

  • Preemptible ノードに taint 「cloud.google.com/gke-preemptible="true":NoSchedule」を設定し、システム管理コンテナがスケジュールされないようにする。
  • ジョブスケジュールに以下の条件設定をする。

以上の諸要件を勘案し、まず GKE クラスタと管理ノードを作成します。

gcloud container clusters create @{TEST_ID} \
  --zone=us-west1-b \
  --machine-type=e2-small \
  --num-nodes=1 \
  --disk-size=10GB

次にこのクラスタに preemptible GPU ノードを追加します。

gcloud container node-pools create ppool \
  --zone=us-west1-b \
  --cluster=@{TEST_ID} \
  --num-nodes=1 \
  --machine-type=n1-standard-1 \
  --disk-size=50GB \
  --preemptible \
  --accelerator=type=nvidia-tesla-k80,count=1 \
  --node-taints=cloud.google.com/gke-preemptible="true":NoSchedule \
  --service-account=@{TEST_ID}@${PROJECT_ID}.iam.gserviceaccount.com

最後の4つのコマンドオプションに注意してください。上からそれぞれ以下のような意味があります。

  • Preemptible GPU インスタンス
  • NVIDIA Tesla K80 を1つ使用
  • Taint の設定
  • Cloud Storage へのアクセス権を付与したサービスアカウントをノードに設定

コンテナが起動したら、ノードインスタンスに NVIDIA ドライバをインストールする daemonSet を仕掛けます

kubectl apply -f daemonset-preloaded.yaml

以上で準備は完了です。以下のジョブを実行します。

apiVersion: batch/v1
kind: Job
metadata:
  name: ${TEST_ID}gpu
spec:
  template:
    spec:
      containers:
      - name: ${TEST_ID}
        image: gcr.io/${PROJECT_ID}/${REPOSITORY_NAME}:latest
        resources:
          limits:
            nvidia.com/gpu: 1
        env:
        - name: BUCKET_NAME
          value: ${BUCKET_NAME}
        - name: DOWNLOAD_FILE_PATH
          value: ${SOURCE_NOTEBOOK_PATH}
        - name: UPLOAD_FILE_PATH
          value: ${DESTINATION_NOTEBOOK_PATH}
      restartPolicy: Never
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: cloud.google.com/gke-preemptible
                operator: In
                values: 
                - "true"
      tolerations:
      - key: cloud.google.com/gke-preemptible
        operator: Equal
        value: "true"
        effect: NoSchedule
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
  backoffLimit: 0

以下の部分に注意してください。

resources:
    limits:
        nvidia.com/gpu: 1

この指定をしないと、コンテナから GPU が見えなくなってしまいます。

このバッチで実行した notebook の冒頭では、 TensorFlow から GPU が使用できる状態になっていることが確認できました。

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

ようやく当初目論見だった preemptible GPU ノードで Kubernetes ジョブ実行ができました。

Preemption からの復帰時間はどのぐらいか

Preemption は GCP 側が起こしてくるので意図して発生させることはできませんが、同等のイベントを発生させてテストすることができます

gcloud compute instances simulate-maintenance-event gke-${TEST_ID}-ppool-xxxx

実際のダウンタイムはどのくらいになるのでしょうか?筆者が今回調査の際に経験したダウンタイムは、最低2分、長いものでは10分ほどでした。結構長いですね・・・。運用については順次調整していきたいと思います。

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