【個人開発】AWSサーバーレスアーキテクチャで機械学習とボケ。フロントエンドはVue.jsで遊ぶ

【個人開発】AWSサーバーレスアーキテクチャで機械学習とボケ。フロントエンドはVue.jsで遊ぶ:

エンジニア歴もそれなり。

社内の中堅どころとして、このままのスキル・ポジション・給与で現状に甘んじていていいものか?

ふと、そんなことを思ったのが、昨年暮れ。

いやいや、いいわけがない。

むしろ、このままでは非常にマズイ、と一念発起し、手始めに業務ではなかなか触れることがなかったサーバーレス × 機械学習 × SPAについて、勉強してみることにした。

ネット記事やサンプルコードを眺めていてもピンとこなかったので、一から自分で作ってみました、という話。


作ったもの



og.png


写真で一言。ボケてみるよりボケさせて - 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を採用しました。



logo.png



利用ライブラリなど

  • Vue.js
  • Vue-CLI(Vue.jsでアプリケーションをつくるための開発ツール)
  • Vuetify(マテリアルデザインのUIコンポーネント)
  • Amplify(AWS公式のライブラリ)


どうやってボケさせてるの?

基本的には、ユーザーがアップロードした写真と事前に登録しておいたお題画像とを比較し、類似性の最も高い画像を抽出、その画像に紐づくボケを表示させる、というもの。

Recognitionを使えば、同一人物の検出はとても簡単でしたが、要件的に顔の類似性が重要なわけではありませんでした。

といって、写真全体の色合いが類似していれば良い、というわけでもない。

重要なのは「雰囲気」であって、なんとなく雰囲気の似ている写真を検出したい、というのが今回の要件でした。

しかしながら、マニュアルを漁ってみても、なかなかしっくりくるAPIは見当たらず、、

結局、どうしたかというと、Recognitionラベル検出機能(一部、表情解析機能も)を利用しました。

大まかな流れとしては、

  1. 各元ネタ(お題)の画像解析結果をDB(DynamoDBを利用)に保存しておく
  2. ユーザーから写真アップロードされた際、同様の画像解析処理(ラベル検出)を行う
  3. 検出したラベルにマッチするお題画像をフィルタリング、重み付けを行う
  4. 最も点数の高かった画像(ボケ)を抽出して返す
といった具合です。

以下、写真アップロード時のラベル検出例(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でキャッシュさせる、というよくみるパターン。



bo.png


ユーザーによってシェア(公開)された画像やテキストデータは別バケットに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のレンダリング表示を確認してみると、なんとも真っ白なページが・・。



スクリーンショット 2019-01-31 16.23.06.png


調べてみると、これは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側のキャッシュ設定の問題など疑いましたが、どうしてもうまくいかず。

結局、CloudFrontOrigin Groupsを利用したFailoverでやりたいことを実現できました。

参考
[アップデート] CloudFrontのみでオリジンフェイルオーバーが出来るようになりました!

仕組みとしては、メインコンテンツ用のオリジン(primary)のバケットポリシーを書き換えて、Access Denied(403)を意図的に発生させ、メンテナンス用のオリジン(secoundary)にFailoverさせる、というものです。


キャッシュ強すぎ

まぁ、これは当然なんですが。

もともとCloudFrontS3に書き出したシェアコンテンツを直接HTMLで配信する予定だったのですが、キャッシュが強くて変更内容(特に削除)がなかなか反映されない問題が発生しました。

キャッシュの有効期間を極端に短くする方法もとれたかもしれませんが、もはやCloudFront利用している意味がないよな、とか思い、最終的には先に触れたように、Vue.js側で直接S3上のJSONファイルを参照、レンダリングさせる構成としました。


設定反映がすこぶる遅い

たぶん、設定反映が完了するまでに20分くらいかかる。

環境によるのかもしれませんが、ちょっと時間がかかり過ぎな印象。

これでかなり時間を食い潰されたと思います。

と、いろいろ書きましたが、CloudFrontはサーバーレスアーキテクチャの中核を成す、最も重要なサービスの一つ、と言っても過言ではないです。

ありがとうございます。

続きまして、


Cognitoでカスタム属性に書き込みできない

ボケさせて(BOKESASETE)では、会員登録や認証まわりの管理をCognitoにお任せしているのですが、なぜかカスタム属性追加した項目に、データが登録できない状況に陥りました。

結論、これはコンソール側から、以下フローにて各属性のアクセス権限を設定変更して解決することができました。

ユーザープール > 全般設定 > アプリクライアント > 詳細を表示 > 属性の読み込みおよび書き込みアクセス権限を設定する



スクリーンショット 2019-01-29 10.45.05.png


ここです。分かりにくい。


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のような従量課金制だと、コストに対するリスクのとり方が、コンテンツの作り・サービス選定・仕様設計に大きく影響してくる(というか考慮しないといけない)と、つくづく思いました。

これは個人開発だからこそ、(ポケットマネーにつき)より強く意識できたことで、本当にやってみてよかったです。

サーバーレス化で、アプリケーション×インフラの垣根がほとんどなくなって、インフラ構築はむしろ、エンジニア(プログラマ)の作業領域となる未来が訪れるかもしれません。

でも、それ自体はそれほど大きな問題ではなくて、基礎知識のあるエンジニアであれば、対応していけるものと思います。

その反面、コスト面にまで配慮した仕様設計・クライアント提案を行うことは難しい。

そのためには当然、経験と知識、それから、常に最新の技術トレンドを追うモチベーションの高さ が求められるように思います。


今後は、というより今後も、そういったエンジニアは重宝されるのではないでしょうか。

いずれにしろ、いちエンジニアとして、今までよりももっと守備範囲を広げていかなければなりません。

精進します。

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)