【個人開発】AWSサーバーレスアーキテクチャで機械学習とボケ。フロントエンドはVue.jsで遊ぶ
【個人開発】AWSサーバーレスアーキテクチャで機械学習とボケ。フロントエンドはVue.jsで遊ぶ:
エンジニア歴もそれなり。
社内の中堅どころとして、このままのスキル・ポジション・給与で現状に甘んじていていいものか?
ふと、そんなことを思ったのが、昨年暮れ。
いやいや、いいわけがない。
むしろ、このままでは非常にマズイ、と一念発起し、手始めに業務ではなかなか触れることがなかった
ネット記事やサンプルコードを眺めていてもピンとこなかったので、一から自分で作ってみました、という話。
写真で一言。ボケてみるよりボケさせて - BOKESASETE
https://bokesasete.me/
ボケさせて(BOKESASETE)は、ユーザーのアップロードした写真にAIが一言ボケてくれるWEBサービスです。
「ボケてみるより、ボケさせて」をコンセプトに、機械学習による画像解析技術(
自分でうまくボケられない人。筆者。
エンジニアである筆者が、自身のスキルアップの一環で作り始めました。
ボケさせて(BOKESASETE)は、AWSのサービス群を活用した、いわゆる
以下、利用サービス一覧。
ボケさせて(BOKESASETE)のフロントエンドは、
学習・導入のしやすさから、フレームワークは
基本的には、ユーザーがアップロードした写真と事前に登録しておいたお題画像とを比較し、類似性の最も高い画像を抽出、その画像に紐づくボケを表示させる、というもの。
といって、写真全体の色合いが類似していれば良い、というわけでもない。
重要なのは「雰囲気」であって、なんとなく雰囲気の似ている写真を検出したい、というのが今回の要件でした。
しかしながら、マニュアルを漁ってみても、なかなかしっくりくるAPIは見当たらず、、
結局、どうしたかというと、
大まかな流れとしては、
以下、写真アップロード時のラベル検出例(
また、
というのも、年齢まで含めてフィルタリングしてしまうと、一気にボケの抽出範囲が限定されてしまうし、年齢に関係なくボケ抽出した方が、(いい意味で)ハプニング が期待できたからです。
なお、お題画像については、筆者御用達のWEBサイト「ボケて(bokete)」から、その多くを引用させて頂いております。この場を借りてお礼申し上げます。
コンテンツ配信は基本的に
HTMLやjs,css類の公開コンテンツはS3に設置、それらを
ユーザーによってシェア(公開)された画像やテキストデータは別バケットにJSON化して設置し、それらをフロントエンド(
また、処理の必要な部分(例えば写真アップロードなど)は、
ボケさせて(BOKESASETE)では、ボケの詳細ページURLを各SNSにシェアしやすいようサイト設計していますが、TwitterやFacebookで実際にシェアしてみると、残念ながら全く機能しませんでした。
Vue.js初学者が迷う、HTMLの仕様やSEO(Googlebot)のことを調べてみた。
こちらの記事で詳しくまとめて頂いていますが、Googleのbotはある程度JSがレンダリングされるようなのですが、SNS系は絶望的なご様子。
対策として、シェア専用の静的HTMLページ(
その上で、このシェア用ページのJavascript側で
こんな具合です。
先に
不審に思い、
調べてみると、これはGoogleのbotが比較的古いバージョンのブラウザを採用していることに起因していて、
ただし、これは
参考
SPAサイトがGoogleにインデックスされない時の解決法
続きまして、
とにかくクセが強い印象。
例えば、どういうところかというと、
これはそもそも、
もともとやりたかったのはFailoverではなくて、Failoverを利用したサイトメンテナンス切替でした。
事前にメインコンテンツ用のバケットとメンテナンス用のバケットを用意しておいて(それぞれ
CloudFront側のキャッシュ設定の問題など疑いましたが、どうしてもうまくいかず。
結局、
参考
[アップデート] CloudFrontのみでオリジンフェイルオーバーが出来るようになりました!
仕組みとしては、メインコンテンツ用のオリジン(
まぁ、これは当然なんですが。
もともと
キャッシュの有効期間を極端に短くする方法もとれたかもしれませんが、もはやCloudFront利用している意味がないよな、とか思い、最終的には先に触れたように、
たぶん、設定反映が完了するまでに20分くらいかかる。
環境によるのかもしれませんが、ちょっと時間がかかり過ぎな印象。
これでかなり時間を食い潰されたと思います。
と、いろいろ書きましたが、CloudFrontはサーバーレスアーキテクチャの中核を成す、最も重要なサービスの一つ、と言っても過言ではないです。
ありがとうございます。
続きまして、
ボケさせて(BOKESASETE)では、会員登録や認証まわりの管理を
結論、これはコンソール側から、以下フローにて各属性のアクセス権限を設定変更して解決することができました。
ユーザープール > 全般設定 > アプリクライアント > 詳細を表示 > 属性の読み込みおよび書き込みアクセス権限を設定する
ここです。分かりにくい。
これは未解決です。
ユーザー認証まわりには、
とりあえず、下記のように、スクリプト側で泥臭くエラーメッセージを上書いて乗り切っているのですが、こうするしかないのでしょうか・・。
どなたか、いい方法をご存知でしたら教えてやってください。
何度試しても、迷惑メールフィルタを疑っても、メールが届かない。
散々デバッグした挙句、最後の手段「公式をちゃんと読む」で見事解決。
https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/request-production-access.html
ちゃんと書いてありました。
新しいAWSアカウントでは、サンドボックスモードの解除申請が必要なようです。
参考
AWS SESをちゃんと使うためにやるべきこと
ちゃんと読もう。
ユーザー間のコミュニケーション機能強化と、様子をみて(iOS, Android)アプリとかも作ってみたい。
あと、ボケ(お題画像)の収集を自動化できたら面白いかもしれない。
本当にコンソールから数STEP操作するだけで環境が出来上がって、
まさに至れり尽くせりでした。
ただ、コスト面に関して、エンジニアはシビアにとらえておくべきだと思う。
今回、一から自分で作ってみて、
これは個人開発だからこそ、(ポケットマネーにつき)より強く意識できたことで、本当にやってみてよかったです。
サーバーレス化で、アプリケーション×インフラの垣根がほとんどなくなって、インフラ構築はむしろ、エンジニア(プログラマ)の作業領域となる未来が訪れるかもしれません。
でも、それ自体はそれほど大きな問題ではなくて、基礎知識のあるエンジニアであれば、対応していけるものと思います。
その反面、コスト面にまで配慮した仕様設計・クライアント提案を行うことは難しい。
そのためには当然、経験と知識、それから、常に最新の技術トレンドを追うモチベーションの高さ が求められるように思います。
今後は、というより今後も、そういったエンジニアは重宝されるのではないでしょうか。
いずれにしろ、いちエンジニアとして、今までよりももっと守備範囲を広げていかなければなりません。
精進します。
エンジニア歴もそれなり。
社内の中堅どころとして、このままのスキル・ポジション・給与で現状に甘んじていていいものか?
ふと、そんなことを思ったのが、昨年暮れ。
いやいや、いいわけがない。
むしろ、このままでは非常にマズイ、と一念発起し、手始めに業務ではなかなか触れることがなかった
サーバーレス × 機械学習 × SPAについて、勉強してみることにした。ネット記事やサンプルコードを眺めていてもピンとこなかったので、一から自分で作ってみました、という話。
作ったもの
写真で一言。ボケてみるよりボケさせて - BOKESASETE
https://bokesasete.me/
どういうサービス?
ボケさせて(BOKESASETE)は、ユーザーのアップロードした写真にAIが一言ボケてくれるWEBサービスです。「ボケてみるより、ボケさせて」をコンセプトに、機械学習による画像解析技術(
Amazon Recognition)を利用し、数ある殿堂入りレベルのボケの中から一番 “しっくりくる” ボケを抽出します。
誰のため?(対象)
自分でうまくボケられない人。筆者。
何のため?(目的)
エンジニアである筆者が、自身のスキルアップの一環で作り始めました。
利用している技術(AWS)
ボケさせて(BOKESASETE)は、AWSのサービス群を活用した、いわゆるサーバーレスアーキテクチャです。以下、利用サービス一覧。
コンテンツ配信
- Route 53
- CloudFront
- Certificate Manager
- S3
API/処理系
- API Gateway
- Lambda(ランタイム:Python 3.7)
画像解析/機械学習
- Rekognition
会員登録
- Cognito
メール
- Simple Email Service
ストレージ&データベース
- DynamoDB
- S3
ログ/定期実行
- CloudWatch
フロントエンド(SPA)
ボケさせて(BOKESASETE)のフロントエンドは、シングルページアプリケーション(SPA)として実装しています。学習・導入のしやすさから、フレームワークは
Vue.jsを採用しました。
利用ライブラリなど
- Vue.js
- Vue-CLI(Vue.jsでアプリケーションをつくるための開発ツール)
- Vuetify(マテリアルデザインのUIコンポーネント)
- Amplify(AWS公式のライブラリ)
どうやってボケさせてるの?
基本的には、ユーザーがアップロードした写真と事前に登録しておいたお題画像とを比較し、類似性の最も高い画像を抽出、その画像に紐づくボケを表示させる、というもの。Recognitionを使えば、同一人物の検出はとても簡単でしたが、要件的に顔の類似性が重要なわけではありませんでした。といって、写真全体の色合いが類似していれば良い、というわけでもない。
重要なのは「雰囲気」であって、なんとなく雰囲気の似ている写真を検出したい、というのが今回の要件でした。
しかしながら、マニュアルを漁ってみても、なかなかしっくりくるAPIは見当たらず、、
結局、どうしたかというと、
Recognitionのラベル検出機能(一部、表情解析機能も)を利用しました。大まかな流れとしては、
- 各元ネタ(お題)の画像解析結果をDB(
DynamoDBを利用)に保存しておく - ユーザーから写真アップロードされた際、同様の画像解析処理(ラベル検出)を行う
- 検出したラベルにマッチするお題画像をフィルタリング、重み付けを行う
- 最も点数の高かった画像(ボケ)を抽出して返す
以下、写真アップロード時のラベル検出例(
API GatewayからLambda起動想定)lambda_function.py
import boto3
import base64
REGION = 'ap-northeast-1'
rekognition = boto3.client('rekognition', REGION)
def lambda_handler(event, context):
# Base64にエンコードされているので、ここでデコード
img = base64.b64decode(event['body'])
try:
# 画像解析
labels = detect_labels(img)
... 略
# ラベルの取得
def detect_labels(img):
response = rekognition.detect_labels(Image={
'Bytes': img,
})
return response
boto3を利用すれば、とても簡単に実装できます。また、
Recognitionの解析機能は非常に優秀で、被写体の年齢層までかなりの精度で取得できたのですが、これはサービスの特性上、無視することにしました。というのも、年齢まで含めてフィルタリングしてしまうと、一気にボケの抽出範囲が限定されてしまうし、年齢に関係なくボケ抽出した方が、(いい意味で)ハプニング が期待できたからです。
なお、お題画像については、筆者御用達のWEBサイト「ボケて(bokete)」から、その多くを引用させて頂いております。この場を借りてお礼申し上げます。
コンテンツ配信について
コンテンツ配信は基本的にRoute53 × CloudFront(ACM) × S3で行なっています。HTMLやjs,css類の公開コンテンツはS3に設置、それらを
CloudFrontでキャッシュさせる、というよくみるパターン。ユーザーによってシェア(公開)された画像やテキストデータは別バケットにJSON化して設置し、それらをフロントエンド(
Vue.js)から非同期で読み込み、レンダリングさせる仕様としました。また、処理の必要な部分(例えば写真アップロードなど)は、
API Gateway経由でLambda起動させて対応しています。
ハマったところ・苦労した点
SPAはOGPまわりの挙動が絶望的
ボケさせて(BOKESASETE)では、ボケの詳細ページURLを各SNSにシェアしやすいようサイト設計していますが、TwitterやFacebookで実際にシェアしてみると、残念ながら全く機能しませんでした。Vue.js初学者が迷う、HTMLの仕様やSEO(Googlebot)のことを調べてみた。
こちらの記事で詳しくまとめて頂いていますが、Googleのbotはある程度JSがレンダリングされるようなのですが、SNS系は絶望的なご様子。
対策として、シェア専用の静的HTMLページ(
もちろん適切にmeta,og設定済みのやつ)を記事ごとに用意し、シェアする際はこのURLがシェアされるようコンテンツ側を調整。その上で、このシェア用ページのJavascript側で
UserAgentを参照し、ボットからのアクセスならばそのままレスポンス、それ以外のアクセスは、本来アクセスさせたいボケ詳細URLにリダイレクトさせる、という仕様で乗り切りました。こんな具合です。
詳細ページ.html
... 略
<script>
var userAgent = window.navigator.userAgent.toLowerCase();
if(userAgent.indexOf('facebookexternalhit') === -1 && userAgent.indexOf('twitterbot') === -1) {
location.href = '{本来アクセスさせたいURL}';
}
</script>
... 略
SSR(サーバサイドレンダリング)の導入も検討しましたが、このためだけに全体構成変更するのは気が引けたので今回は見送りました。
とはいえ、GoogleがSPAサイトをインデックスしてくれない
先にGoogleのbotはある程度JSがレンダリングされると書いたのですが、Fetch as Googleでインデックス登録しても、一向にインデックスされない、という状況に陥りました。不審に思い、
Fetch as Googleのレンダリング表示を確認してみると、なんとも真っ白なページが・・。調べてみると、これはGoogleのbotが比較的古いバージョンのブラウザを採用していることに起因していて、
Vue.jsで作成したSPAのコンテンツがうまくレンダリングされていないようでした。ただし、これは
babel-polyfillを利用することで簡単に解決します。
babel-polyfillのインストール
# npm install babel-polyfill
webpack.base.conf.jsの変更
webpack.base.conf.js
module.exports = {
// ..略
entry: {
app: ['babel-polyfill', './src/main.js']
},
// ..略
SPAサイトがGoogleにインデックスされない時の解決法
続きまして、
CloudFrontのクセがすごい
とにかくクセが強い印象。例えば、どういうところかというと、
Route53のFailoverがうまく動作しない
これはそもそも、primary,secoundaryともにCloudFrontディストリビューションを指定していたのが問題なのかもしれないのですが、どこをどういじってもうまく機能しなかった。もともとやりたかったのはFailoverではなくて、Failoverを利用したサイトメンテナンス切替でした。
事前にメインコンテンツ用のバケットとメンテナンス用のバケットを用意しておいて(それぞれ
CloudFrontでキャッシュさせる)、メンテナンス時にはコンソールから意図的にRoute53のヘルスチェックを失敗させ、サイト全体を自動的にメンテナンスページに切り替える、というフローを想定していました。CloudFront側のキャッシュ設定の問題など疑いましたが、どうしてもうまくいかず。
結局、
CloudFrontのOrigin Groupsを利用したFailoverでやりたいことを実現できました。参考
[アップデート] CloudFrontのみでオリジンフェイルオーバーが出来るようになりました!
仕組みとしては、メインコンテンツ用のオリジン(
primary)のバケットポリシーを書き換えて、Access Denied(403)を意図的に発生させ、メンテナンス用のオリジン(secoundary)にFailoverさせる、というものです。
キャッシュ強すぎ
まぁ、これは当然なんですが。もともと
CloudFrontでS3に書き出したシェアコンテンツを直接HTMLで配信する予定だったのですが、キャッシュが強くて変更内容(特に削除)がなかなか反映されない問題が発生しました。キャッシュの有効期間を極端に短くする方法もとれたかもしれませんが、もはやCloudFront利用している意味がないよな、とか思い、最終的には先に触れたように、
Vue.js側で直接S3上のJSONファイルを参照、レンダリングさせる構成としました。
設定反映がすこぶる遅い
たぶん、設定反映が完了するまでに20分くらいかかる。環境によるのかもしれませんが、ちょっと時間がかかり過ぎな印象。
これでかなり時間を食い潰されたと思います。
と、いろいろ書きましたが、CloudFrontはサーバーレスアーキテクチャの中核を成す、最も重要なサービスの一つ、と言っても過言ではないです。
ありがとうございます。
続きまして、
Cognitoでカスタム属性に書き込みできない
ボケさせて(BOKESASETE)では、会員登録や認証まわりの管理をCognitoにお任せしているのですが、なぜかカスタム属性追加した項目に、データが登録できない状況に陥りました。結論、これはコンソール側から、以下フローにて各属性のアクセス権限を設定変更して解決することができました。
ユーザープール > 全般設定 > アプリクライアント > 詳細を表示 > 属性の読み込みおよび書き込みアクセス権限を設定する
ここです。分かりにくい。
Cognitoから返されるエラーメッセージのカスタマイズ(翻訳)
これは未解決です。ユーザー認証まわりには、
aws-amplifyを利用しているのですが、デフォルトではエラーメッセージが全て英語になっていて、このメッセージをカスタマイズ(翻訳)する方法が不明でした。とりあえず、下記のように、スクリプト側で泥臭くエラーメッセージを上書いて乗り切っているのですが、こうするしかないのでしょうか・・。
Signin.vue
// ..略
Auth.signIn(
username,
password
)
.then(user => {
console.log(user)
})
.catch(err => {
console.log(err)
//this.errorMsg = err.message
this.errorMsg = '認証できませんでした。'
})
// ..略
SESのサンドボックスモード
何度試しても、迷惑メールフィルタを疑っても、メールが届かない。散々デバッグした挙句、最後の手段「公式をちゃんと読む」で見事解決。
https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/request-production-access.html
ちゃんと書いてありました。
新しいAWSアカウントでは、サンドボックスモードの解除申請が必要なようです。
参考
AWS SESをちゃんと使うためにやるべきこと
ちゃんと読もう。
今後の展開
ユーザー間のコミュニケーション機能強化と、様子をみて(iOS, Android)アプリとかも作ってみたい。あと、ボケ(お題画像)の収集を自動化できたら面白いかもしれない。
最後に
Dockerを初めて触ったときにも衝撃を受けましたが、AWSによるサーバーレス環境構築は予想以上に簡単で驚かされました。本当にコンソールから数STEP操作するだけで環境が出来上がって、
Lambdaを使えば、簡単なAPIならエディタすら使わずにできてしまう。また、それらのログ監視や、データ永続化の仕組みももちろん用意されている。まさに至れり尽くせりでした。
ただ、コスト面に関して、エンジニアはシビアにとらえておくべきだと思う。
今回、一から自分で作ってみて、
AWSのような従量課金制だと、コストに対するリスクのとり方が、コンテンツの作り・サービス選定・仕様設計に大きく影響してくる(というか考慮しないといけない)と、つくづく思いました。これは個人開発だからこそ、(ポケットマネーにつき)より強く意識できたことで、本当にやってみてよかったです。
サーバーレス化で、アプリケーション×インフラの垣根がほとんどなくなって、インフラ構築はむしろ、エンジニア(プログラマ)の作業領域となる未来が訪れるかもしれません。
でも、それ自体はそれほど大きな問題ではなくて、基礎知識のあるエンジニアであれば、対応していけるものと思います。
その反面、コスト面にまで配慮した仕様設計・クライアント提案を行うことは難しい。
そのためには当然、経験と知識、それから、常に最新の技術トレンドを追うモチベーションの高さ が求められるように思います。
今後は、というより今後も、そういったエンジニアは重宝されるのではないでしょうか。
いずれにしろ、いちエンジニアとして、今までよりももっと守備範囲を広げていかなければなりません。
精進します。
コメント
コメントを投稿