Quantcast
Channel: クックパッド開発者ブログ
Viewing all 726 articles
Browse latest View live

Tokyo Machine Learning Kitchen

$
0
0

Hello, I'm @lunardog. I work in Cookpad's Research and Development team as a machine learning researcher. I also host Tokyo Machine Learning Kitchen events. If you'd like to attend, you can sign up using meetup.com or connpass.

I first heard about Cookpad back in 2012 when I joined Tokyo Rails Meetup. At the time, Rails was immensely popular in the developer community and many coders, if not working with Rails in their day jobs, would learn Rails and build hobby projects in Rails after hours. I remember being quite impressed by the talks, especially the ones by Cookpad employees. I was amazed by Cookpad's hospitality and willingness to share the event space, the expertise of the staff and even contribute to open source projects. Cookpad was truly the cornerstone of the Rails community for Tokyo.

f:id:lunardog:20180625165758j:plain (A talk at the Tokyo ML event)

Fast forward to now. Machine Learning in general and Deep Learning in particular is the hot topic. I joined Cookpad's newly created Research and Development department as a Machine Learning researcher. Cookpad is, as it was those years ago, a dynamic, fast-paced company with a positive attitude to open source and sharing knowledge, be it recipes, open-source code or datasets for machine learning researchers. I have decided to make it my mission to contribute to the community of Deep Learning researchers in Tokyo by organizing Tokyo Machine Learning Kitchen events. Much like Tokyo Rails, the ML Kitchen events are a place where professionals and hobbyists can get together to share their interests, network and enjoy a good meal.

The focus of ML Kitchen events is networking between practitioners of Machine Learning. You can expect presentations on:

  • professional or personal ML-related projects
  • machine learning, deep learning, data science walkthroughs
  • paper reviews: own or found somewhere
  • reviews of machine learning frameworks and libraries
  • really interesting talks about math and statistics

To further facilitate networking, Cookpad provides snacks prepared in the kitchen where the event takes place. This is why it's called "ML Kitchen", by the way. It takes place in a kitchen.

Talks are in English, but it's not a problem to do a talk in Japanese, if the slideshow is in English. We usually have a keynote talk, followed by a snack break and a series of lightning talks. We end the evening with more networking, exchanging of business cards, eating and drinking.

f:id:lunardog:20180624183031j:plain

If you'd like to share something with the Machine Learning community in Tokyo, contact me through our group page or meetup or raise an issue in our github repository.

Hope to see you soon!


目的に向かって作り込む、ユーザーを動かすためのデザインの考え方

$
0
0

投稿開発部のデザイナー平塚です。クックパッドにレシピを投稿してくれるユーザーのための機能やサービス全般のデザインを担当しています。
今回は最近投稿開発部で行なったキャンペーンのLPのデザインを具体例に、私の考えるデザインプロセスをご紹介します。

施策の概要

日頃からクックパッドを利用してくれているユーザーに感謝を込めて、投稿した自分のレシピでレシピ本を作れるというキャンペーンを実施しました。 伝える手段としてキャンペーンLPを作成し、対象者にはメールで告知しました。

作成したLPはこちら

LPデザイン

デザインコンセプトを決める際に考えること

大きく分けて、ユーザーの体験とクックパッドが伝えたいことの2つを考えてデザインに落とし込んでいきます。

A. ユーザーの体験を整理する

まず、LPを見たユーザーにどうしてほしいかを考え整理していきます。
ユーザーにレシピ本を作ることが目的だと思ってもらうことを考える上で意識しました。ユーザーストーリーをもとに要点を洗い出します。

ユーザーストーリー

LPのゴールは「あのレシピ載せたいな!」と思って応募してくれることで、それを実現するためには、LPを見たユーザーに以下の3点が起こることが重要になりそうです。

  • 1.面白そう!魅力的!私も作りたいと思う
  • 2.手元にどんなものが届くのかがイメージできる
  • 3.どうやって参加できるのかが分かる

この3点をベースに構成していきます。

1. 面白そう!魅力的!私も作りたいと思わせるには?

LPを開いてすぐにユーザーの興味を掴めるよう、ファーストビューでどんな嬉しい体験ができるのかが想像できる状態を目指します。
キャッチコピーはユーザーインタビューやターゲットユーザーに近い社内スタッフの意見をもとに決定しました。 またレシピ本のイメージ写真をキャッチコピーの直下に置くことで、訴求する体験と合わせてレシピ本をユーザーがより想像しやすくなるようにしました。

2. 手元にどんなものが届くのかをイメージさせるには?

さらにレシピ本のイメージを膨らませ、ユーザーの頭の中でレシピ本がより鮮明になるようにします。
レシピ本のサイズや質感、厚み、それにどんな情報が載せられるのかが分かる写真を用意しました。

3. どうやって参加できるのかを伝えるには?

レシピ本に興味を持ってもらえたところで、自分はキャンペーンに参加できるのか?どうやって参加するのか?を説明します。
応募条件 → 締め切り → 具体的な流れ という順で重要な情報から先に記載していき、説明が一通り完了したところで応募導線を配置しました。

これらをもとにワイヤーフレームを起こして、情報の優先順位付けや読ませるもの・見せるものの棲み分けをしていきます。

ワイヤーフレーム

ワイヤーフレームができたらチームで議論を進めます。
キャッチコピーについてはディレクターがテコ入れしてくれてとても良くなりました。

B. 伝えたい世界観を整理する

ワイヤーフレームの作成と同時並行で、デザインコンセプトを固めていきます。
クックパッドが伝えたいこと・ユーザーに感じて欲しいことを整理し、ビジュアルに落とし込みます。 今回は洗い出した要点の中でも「面白そう!魅力的!私も作りたいと思う」部分をさらに掻き立てるようなデザインを目指しました。

キーワードの洗い出し

まず「何を感じたらレシピ本を作りたいと思うか」を考え言語化します。
ユーザーインタビューでレシピ本について話してくれた内容をもとにキーワードを洗い出しました。 そこから、紙ならではの魅力や、普段ユーザーが料理メモやノートに整理するときに思っていることを中心にキーワードを選定しました。

  • レシピ本
  • ぬくもり感
  • 手作り感
  • 手書き感

レシピ本キャンペーンの世界観を可視化するために、選定したキーワードを表現できる要素は何かを考えていきます。

紙の質感、ぬくもり感

レシピ本を手にとってページをめくる時に感じる紙ならではの質感・ぬくもり感を表現したいと思い、テクスチャの素材で表現できないか考えました。
雑誌や料理本を参考にしたり、Photoshopのテクスチャの素材を探して決めました。

手作り感、手書き感

これは丸みがあって人が書きそうなフォントで表現しようと考えました。
ただユーザーインタビューで見せていただいた普段書く料理メモやノートのような雑な感じとは差別化したいので、字として崩れすぎていない・きれいで憧れるようなフォント選びを意識しました。
出せるだけフォントを集めてPhotoshopで並べ、スマホ画面でのサイズ感、カーニングの扱い、ひらがな・漢字・数字のバランスなどを見て厳選していきます。

レシピ本の質感と手書き感から連想するイメージが見えたのでそれに合う色を探します。
基準としては「明るい印象を持ちつつ、落ち着きがある」で、前述で決めたデザインとの相性から緑を連想し、彩度低め・明度高めでカラーパレットを作成しました。
最終的にクックパッドのデザインシステムに定義されている Mint #40b2aaが考えていたものと近かったため、カラーパレットとしました。

クックパッドであることが分かる

LPなのでクックパッドであることが分かる要素も重要です。
ここまで考えたデザインがクックパッドらしさと少し離れているなと感じたので、色を足すことにしました。
ここでもクックパッドのデザインシステム Mint #40b2aaと相性の良い Orange #ff9933を選定し、LPデザインのアクセントカラーとして使用しました。

まとめ

ユーザーの体験整理と伝えたい世界観の整理を同時並行することで、デザインコンセプトは行き過ぎていないか、逆に体験を意識しすぎてメッセージ性がないものになっていないかバランスを見ながら進めることができます。

デザインコンセプトを考える

今回はキャンペーンLPを具体例にあげましたが、このデザインプロセスはアプリUIなど他のデザインでも同じように考えることができます。
デザインには様々な手法があり、今回ご紹介したものが万能ということではありませんが、自分の中ではデザインしやすいやり方なので数ある方法の一例として捉えていただければと思います。

最後になりますが、クックパッドではより良いユーザー体験を届けていきたい!というデザイナーを募集中です。 クックパッド株式会社 採用情報

Firebase ML Kitで自作のカスタムモデルを使って料理・非料理画像を判定できるようにした

$
0
0

会員事業部の山下(@farmanlab)です。 Androidエンジニアとしてクックパッドアプリの開発を担当しています。

今回はGoogle I/O 2018で新しく発表されたML Kitをクックパッドのデータで学習したモデルを使って検証した話をします。

機械学習モデルの利用にあたって、研究開発部の菊田(@yohei_kikuta)の協力の元で検証を行いました。

これからお話する内容がイメージしやすいよう、 クックパッドの料理・非料理を判別するモデルを動かした実機デモをお見せします。

これは料理と判定された確率がfood、料理ではないと判定された確率がnon-foodというラベルのスコアで表示されているデモです。 (非)料理画像において(non-)foodのラベルのスコアが大きくなり正しく判別できていることが分かります。

f:id:farmlanlabdev:20180704182402g:plain

  • モデルは MobileNetV2
  • tensorflow-gpu==1.7.1で学習してTOCOでTensorFlow Liteのモデルを作成
  • アプリ側では入力データやモデルの重みはfloatで処理

ML Kitとは

ML Kitとはモバイルアプリ向けに機械学習機能を組み込むことができるSDKです。 Firebaseの機能の一つとして提供されており、Android/iOSに簡単に導入することができます。 大まかに以下のような特徴があります。

  • デフォルトで以下の機能が利用可能

    • 文字認識
    • 画像ラベリング
    • バーコードスキャン
    • 顔検出
    • ランドマーク認識
  • オンデバイスモードとクラウドモードで利用可能

オンデバイスモードでは端末にモデルをダウンロードすることでオフラインで動作し、無料で使うことができます。 クラウドモードはCloud Vision APIを使ってオンデバイスよりも精度の高い情報を得ることができる代わりに Firebaseの課金プランをBlazeにする必要があり、一定回数以上の利用は有料です。

  • 独自のカスタムモデルを利用可能

デフォルトで提供される機能以外に独自の機械学習モデルを利用することができます。 ちなみにカスタムモデルの利用にあたってはFirebaseの課金プランをBlazeにする必要はありません。

  • Android Neural Networks API(NNAPI)との連携

ML KitはAndroid8.1で導入されたNNAPIとの連携がSDKに含まれているため、 開発者がNNAPIに関するコードを書く必要がありません。

ML Kitとカスタムモデルを導入するまでの流れ

以下のステップでカスタムモデルをML Kitで使えるようにします

  1. アプリの依存関係にML Kitを追加
  2. TensorFlowの機械学習モデルをTensorFlow Liteのモデルに変換する
  3. FirebaseでTensorFlow Liteのモデルをホストする
  4. アプリでFirebaseのモデルをバンドルする
  5. バンドルしたモデルを利用して推論する

1と3については公式に詳しいので、説明はそちらに譲ります。 ここでは2と4と5について掘り下げていきましょう。

ML Kitはクイックスタートサンプルが用意されているので、 このサンプルに元にしつつ、自分たちのモデルを動かす上で必要になるポイントも加えて説明していきます。

2.TensorFlowの機械学習モデルをTensorFlow Liteのモデルに変換する

サンプルを動かす場合はGitHubのレポジトリmobilenet_quant_v1_224.tfliteが用意されているので、特に準備をする必要はありません。

自分のモデルを使う場合は、一言で言えばTOCOというTensorFlowのモデルからTensorFlow Liteのモデルに変換するツールを使えばいいのですが、自分で学習したモデルを使うには注意を要します。 ここではその部分を詳しく解説します。

今回はクックパッドで使われている料理・非料理判別モデルを実装します。 モデルはサンプルに倣って基本的にはMobileNetV1を使います。 冒頭で示したようにMobileNetV2でも実装ができていますが、これはV1の実装ができれば(モデルアーキテクチャ以外)全く同様にできるため、ここでは試行錯誤の過程を紹介する意味でもV1の話をします。 それぞれのモデルの詳細はこの記事では解説しませんが、学習済みのモデルはV1はこちらV2はこちらにあります。

自分たちが準備したデータでモデルを学習する部分には新しいことはなく、tensor-for-poets-2のコードtensorflow/hubのコードがそのまま使えます。 今回は料理・非料理の二値分類を対象としました。

ここで作成したTensorFlowのモデルからML Kitで適切に動作するTensorFlow Liteのモデルを作るところで苦戦したので、気をつけるべき点と共に手順を紹介します。

TensorFlow Liteモデル(.tflite)の作り方

ここでは、TensorFlowで学習して作成した xxx.pbファイルから model.tfliteファイルへ変換することを考えます。 例えば、model_graph.pbmodel.tfliteファイルに変換するコマンドは以下のようになるイメージです。

toco \
  --input_file=/tmp/model_graph.pb \
  --input_format=TENSORFLOW_GRAPHDEF \
  --output_file=/tmp/model.tflite \
  --output_format=TFLITE \
  --input_arrays=input \
  --output_arrays=final_result

このように、TensorFlowでモデルの学習をする場合はそのままTOCOを使えば.tfliteを作れるため、変換それ自体に困難はありません。 ただし、TensorFlowのバージョン依存性が強いので注意が必要です。 この記事における我々の結果は全てtensorflow-gpu==1.7.1で実行したものとなります。

他のフレームワークでモデルを学習する場合は一旦TensorFlowのモデルに変換する必要がありますが、変換用のライブラリは色々出てるので、標準的なoperationのみを使っていれば可能だと思います。 後述しますが、TensorFlow Liteではでサポートしているoperationはまだ限定的なので、特殊なoperationを含むモデルを使う場合は自分でTensorFlow Lite側の実装をする必要があります。

また、コンバーターであるTOCOは重みの量子化などのオプションも有していて、これを使ってfloatで重みを扱うモデルから量子化して扱うモデルを作ることもできます(正確には、Fake quantizationという、重みはuint8で扱うが出力はfloat32として扱う機能が提供されています)。

サンプルと同じようにやってみて上手くいかなかった話

単純に考えれば、サンプルで動いているモデルに基づき、自分たちのデータを使って再学習したモデルをTOCOを使って.tfliteファイルに変換するだけで上手くいくはずです。 ML Kitのサンプルではmobilenet_quant_v1_224.tfliteという重みが量子化されたMobileNetV1が使われているので、とりあえずMobileNetV1の量子化バージョンMobilenet_1.0_224_quantから再学習したretrained_graph.pbを使いfood-non-food.tfliteを作成します。 変換コマンドは以下のものを使用しました。

IMAGE_SIZE=224
toco \
  --allow_custom_ops \
  --input_file=/tmp/retrained_graph.pb \
  --input_format=TENSORFLOW_GRAPHDEF \
  --output_file=/tmp/food-non-food.tflite \
  --output_format=TFLITE \
  --input_shapes=1,${IMAGE_SIZE},${IMAGE_SIZE},3 \
  --mean_values=128 \
  --std_values=128 \
  --inference_type=QUANTIZED_UINT8 \
  --input_arrays=input \
  --output_arrays=final_result

先程の例と比べると入力のshape指定や値の標準化なども入っています。 オプションの--allow_custom_opsに関しては、これをつけないとcustom opがないというエラーが出るのでつけています。 「それでは動かないのでは?」という自然な疑問が湧きますが、一方でサンプルで動いているモデルと同じだ(と思われる)ので動くだろうという期待もそれほど悪くないものに思えます。

しかしながら、結果はダメで、サンプルのモデルだけ置き換えると例えばDidn't find custom op for name Dequantizeなどというエラーを吐きます。 これはTensorFlow Lite側で計算グラフのoperationが実装されていないことを意味しています。 operationが無いということで、選択肢は自分で頑張って実装するかサポートされているoperationだけでモデルを作るかです。 そもそもちゃんと動くか分からない状況なので、試すまでのスピードや余計なバグの原因を混入させないという意図で、後者の方法で進めることにしました。

以降では重みをfloatで扱うモデル(以下floatモデル)をどのようにすれば動かせるかを紹介しますが、そもそもサンプルにおいて量子化されたモデルがどのように動いているかに関してはよく分かっていません。

floatモデルを動かすまでの試行錯誤

まず試したのは、floatモデルをfake quantizationして扱うという方法です。 ML Kitのサンプルが量子化されたモデルを扱っているのでこれが既存のスクリプトを書き換えずに実行する近道に思えます。 Mobilenet_1.0_224を元に再学習したretrained_graph.pbを以下のコマンドで量子化されたfood-non-food.tfliteに変換します。

IMAGE_SIZE=224
toco \
  --input_file=/tmp/retrained_graph.pb \
  --input_format=TENSORFLOW_GRAPHDEF \
  --output_file=/tmp/food-non-food.tflite \
  --output_format=TFLITE \
  --input_shapes=1,${IMAGE_SIZE},${IMAGE_SIZE},3 \
  --mean_values=128 \
  --std_values=128 \
  --default_ranges_min=0 \
  --default_ranges_max=6 \
  --inference_type=QUANTIZED_UINT8 \
  --input_arrays=input \
  --output_arrays=final_result

オプションのdefault rangeがfake quantizationに必要な情報で、活性化レイヤーでの値の取りうる範囲を指定して量子化の際の情報として使います。 理想的には学習時の結果を保持して使うものですが、MobileNetV1はReLU6を使っているためこのように指定できます。

これで作ったモデルはエラーは吐きませんが、予測のスコアが[0.7,0.3]辺りをうろついてあまり変化しないという結果になりました。 この結果から推察するに入力の画像の取り扱いや重みがちゃんと入ってるかなどが怪しいところですが、いくつか調べてみても解決法は見つかりませんでした。 ML Kitの世界に行ってしまうとどこに問題があるか(モデル変換にバグがあるのかアプリ側にバグがあるのか)のデバッグが難しいということもあります。

ということで残りは素直にfloatモデルを作ってアプリ側でもfloatモデルを扱うように変更するという方法です。 モデル変換は以下で実施しました。

IMAGE_SIZE=224
toco \
  --input_file=/tmp/retrained_graph.pb \
  --input_format=TENSORFLOW_GRAPHDEF \
  --output_file=/tmp/food-non-food.tflite \
  --output_format=TFLITE \
  --input_shapes=1,${IMAGE_SIZE},${IMAGE_SIZE},3 \
  --mean_values=128 \
  --std_values=128 \
  --inference_type=FLOAT \
  --input_arrays=input \
  --output_arrays=final_result

これで得られたモデルをそのまま動かすとInput 0 should have 150528 bytes, but found 602112 bytesというエラーに遭遇します。 モデル的にはfloat32で扱うところを入力としてはuint8を想定しているために不整合が起こっているように見えます。

これは元々アプリ側では量子化されたものを扱おうとしていたのだから自然なエラーだと思われ、アプリ側を適切に変更すれば動くことが期待できます。 いずれにせよ、モデルを作る側にできるのはここまでなので、以降でこのモデルのアプリへの取り込みとアプリ側でどのように扱えば正しく動かせるのかを説明していきます。

4.アプリでFirebaseのモデルをバンドルする

では、作成したモデルをML Kitに取り込んでみましょう。 ここでの手順は他のカスタムモデルを取り込む手順と違いはありません。

ML Kitでカスタムモデルを使う際の大まかな構成は以下のようになっています。 ML Kit構成

アプリにモデルをバンドルするときにはFirebaseModelManagerを利用します。

FirebaseModelManagerに定義されている、FirebaseLocalModelSourceFirebaseCloudModelSourceのインスタンスをそれぞれ引数に取る registerLocalModelSourceregisterCloudModelSourceメソッドを使って利用するモデルのバンドルを行います。 もちろん、ローカルモデルのみ、クラウドモデルのみを利用することも可能です。

ローカルモデルの指定

val localSource = FirebaseLocalModelSource.Builder("food-non-food")
    .setAssetFilePath("food-non-food.tflite")
    .build()

Builderのコンストラクタにはモデルを識別するための任意の文字列を渡します。 Assetsフォルダ内のtffileを参照する場合にはsetAssetFilePathを、それ以外のフォルダを参照する場合はsetFilePathでファイルを指定します。

クラウドモデルの指定

val conditions = FirebaseModelDownloadConditions.Builder().requireWifi().build()
val cloudSource = FirebaseCloudModelSource.Builder("food-non-food")
    .setInitialDownloadConditions(conditions)
    .setUpdatesDownloadConditions(conditions)
    .enableModelUpdates(true)
    .build()

FirebaseModelDownloadConditionクラスでCloudモデルをダウンロードするための条件を設定することができます。 FirebaseCloudModelSource.Builderのコンストラクタにはステップ3でFirebaseにホストしたモデルの名前を指定します。 enableModelUpdatesをtrueにするとFirebaseにホストしたモデルに更新があった場合にモデルをFirebaseから更新するようになります。 この仕組みのおかげでアプリをアップデートすることなく最新の学習モデルを利用することが可能です。

モデルの登録

FirebaseModelManager.getInstance().apply {
    registerLocalModelSource(foodNonFoodLocalSource)
    registerLocalModelSource(attractivenessLocalSource)
    registerCloudModelSource(foodNonFoodCloudSource)
    registerCloudModelSource(attractivenessCloudSource)
}

FirebaseModelManagerのインスタンスを取得して、登録メソッドで渡します。 ML Kitの構成にもあるように複数の機械学習モデルを利用することも可能です。 ここでは料理・非料理の判別モデルと料理の魅力度推定モデルを登録しています。

推論モデルの指定

val options = FirebaseModelOptions.Builder()
    .setLocalModelName("food-non-food")
    .setCloudModelName("food-non-food")
    .build()
val interpreter = FirebaseModelInterpreter.getInstance(options)

FirebaseModelOptionsクラスで推定を行う機械学習モデルの指定を行います。 Firebase(Cloud|Local)ModelSource.Builderのコンストラクタに指定した名前を指定することで、 FirebaseModelManagerに登録した機械学習モデルを使用することができます。

このFirebaseModelOptionsを使って、実際に推定を行うFirebaseModelInterpreterのインスタンスを取得します。

5.モデルを使って推定する

いよいよ、カスタムモデルを使って推定を行います。

入出力のデータを指定する

FirebaseModelInputOutputOptionsを使って、学習モデルのinputとoutputのデータを指定します。

クイックスタートサンプルではbyte値を扱うコードが紹介されていますが、 今回利用する学習モデルはfloat値を扱うように作成しているので、floatの多次元配列がinputデータになります。

outputデータは画像がモデルによって予測されるカテゴリのいずれかの確率であるfloat値の多次元配列(softmaxの出力)です。 カテゴリ一覧を表すテキストファイルをassetsフォルダなどに配置して読み込みます。 今回は、料理・非料理判別モデルのカテゴリを表す

food
non-food

という内容のテキストをlabel.txtというファイル名でassetsフォルダに配置したと仮定します。

val labelList = activity.assets.open("label.txt").reader().use {
    it.readText()
}.split(System.lineSeparator())

val ioOptions = FirebaseModelInputOutputOptions.Builder()
    .setInputFormat(0, FirebaseModelDataType.FLOAT32, intArrayOf(1, 224, 224, 3))
    .setOutputFormat(0, FirebaseModelDataType.FLOAT32, intArrayOf(1, labelList.size))
    .build()

Bitmapからinputデータを作成する

まずはinputデータを格納するための多次元配列を作成します。 なお、執筆時点でのML KitはByteBufferには対応していますが、FloatBufferには対応していませんでした。

// 定数値val IMAGE_MEAN = 128val IMAGE_STD = 128.0f// inputデータを格納する配列を作成val imageData = Array(1) { Array(224) { Array(224) { FloatArray(3) } } }

val imageValues = IntArray(224 * 224)

// 224×224にリサイズしたBitmapからpixel値を取得
resizedBitmap.getPixels(imageValues, 0, resizedBitmap.width, 0, 0, resizedBitmap.width, resizedBitmap.height)
var pixel = 0for (i in0 until 224) {
    for (j in0 until 224) {
       imageValues[pixel++].let {
           imageData[0][i][j][0] = (Color.red(it) - IMAGE_MEAN) / IMAGE_STD
           imageData[0][i][j][1] = (Color.green(it) - IMAGE_MEAN) / IMAGE_STD
           imageData[0][i][j][2] = (Color.blue(it) - IMAGE_MEAN) / IMAGE_STD
       }
    }
}

val inputs = FirebaseModelInputs.Builder()
            .add(imageData)
            .build()

次に学習モデルをTOCOで作成したときの IMAGE_SIZE=224に合わせて224×224サイズにリサイズし、リサイズしたBitmapからpixel値を取り出します。

ここで重要なのが、pixelの各RGB値に対して、IMAGE_MEANとIMAGE_STDを使って演算をしている点です。 tfliteへの変換時に

toco \
  ...
  --mean_values=128 \
  --std_values=128 \
  ...

とMEANとSTDの値を指定しているので、モデル推論時にいい感じにやってくれるように思います。 しかし、実際には予め計算した値をinputとして与える必要があります。 こうして得られたinputの多次元配列データを FirebaseModelInputs.Builderaddメソッドに渡してやります。

推論結果を得る

入出力のデータを指定するで指定したoptionと、Bitmapからinputデータを作成するで得たinputデータを FirebaseModelInterpreterrunメソッドに渡すと推論が実行されます。

interpreter.run(inputs, options)
.addOnSuccessListener { outputs ->val result = outputs.getOutput<Array<FloatArray>>(0)[0]
    result.mapIndexed { index, value ->
        Pair(labelList[index], value)
    }
}

addOnSuccessListenerが受け取るTaskからgetOutputすることで推論結果を得ることができます。 今回はfloatのモデルを使ったのでgetOutputで得られる型はfloatの多次元配列です。 ここではlabel.txtで指定したカテゴリのインデックスと出力値をマッピングしています。

結果

実際に実機で動かしたデモをお見せします。

一つ目は冒頭でもお見せした料理・非料理判別です。 MobilenetV1とMovileNetV2の両方で実装しましたが、大きく変わるところはないので後者の結果のみを改めてお見せします。 実機デモ1

餃子やグラタンやパスタといった料理画像ではfoodのスコアが高くなり、ゴリラや紫陽花などの非料理画像ではnon-foodのスコアが高くなっていることが確認できます。

二つ目は魅力度推定です。 これは学習データとして料理の見栄えを5段階評価(数字が高いほど見栄えが良い)したものを準備し、回帰モデルを学習したものになります。 こちらはMobileNetV1のみで実装しましたのでその結果をお見せします。 実機でも2

数枚でかつ主観的な評価とはなりますが相対的に見栄えの良いと思われる画像に高いスコアが付与されており、モデルが期待通りに動いていることが確認できます。

ということで自分たちで作成した分類モデルを回帰モデルがML Kitを使って実機で動かすことができました! 今回は動かすことが目的であったため正答率や処理速度などの各種指標はまだ詳細には調べていませんが、これは単なるアプリのプロファイリングの話なので難しいところはありません。

まとめ 

今回、Google I/O 2018で発表された最新技術であるML Kitの現状をクックパッドの機械学習モデルを使って検証した話をしました。

ML Kitはまだβ機能として提供されているので、対応しているモデルのオペレータが少なかったり、 量子化されたモデルを上手く動かす情報が不足していたり、発展途上であることは確かです。 しかし、一度モデルを構築してしまえばオンデバイスで動作させることができますし、Firebase経由でモデルのアップデートも簡単にできます。

一方でオンデバイスで動作することは、常に最新のモデルを利用するようにコントロールできないということでもあるので、 設計する上で注意しなければなりません。

今後ますます機械学習を活用したサービスや事例が多く出てくると思いますし、 ML Kitは機械学習機能をモバイルに組み込むための有効な手段の一つであると感じました。

クックパッドではML Kitのような最新技術を利用したモバイルアプリ開発や研究開発がしたい!というエンジニアを募集しています。

興味がある方は採用ページ、または@farmanlab or @yohei_kikutaまで!

x3 Speed Up Android CI at Cookpad

$
0
0

海外事業部の松尾(@Kazu_cocoa)です。こちらは、私たちの x3 Speed Up Android CI at Cookpadに公開した記事の日本語訳です。英語でご覧になりたい方は原文を一読ください。

以下に登場するAndroidアプリは海外版のクックパッドアプリとそれにまつわる環境を指します。国内のものと構成も異なるところがありますので、そのあたりを頭に入れつつ読んでいただければと思います。


この記事では、現在のAndroidアプリ開発向けCI環境の紹介とその環境構築の流れを紹介します。現在の環境では、GitHub上に作成されたプルリクエストへのプッシュ毎にビルド・テストの実行含めて処理が 7分程度で完了します。これらの処理にはAPKの作成、各種テストの実行が含まれます。

以前は、私たちのCIはプッシュ毎に 合計で25分程度かかっていました。そのころは、2つの役割の異なるJenkins Jobを並列実行させていました。合計25分はそれらを含めた合計値です。

以下では、どのようにしてこのような環境を構築したのかを知ることができます。テストの実行環境として、エミュレータの並列起動とそれを用いたテスト実行の話を載せています。この記事ではCIにおけるビルド/テスト環境に焦点を当てるため、他の話は行いません。

マルチモジュール、1000以上のテストケース

まずは対象となるAndroidプロジェクトの概要の共有です。

Androidアプリ

  • 20個のモジュールを保持
  • unit/instrumented/Espressoを含む、合計1000以上のテストケースを保持
  • ビルド毎、社内配布用のapkを配布

現在のCI環境

現在のCI環境は以下で構成されています。

  • AWS上に構築されたJenkins環境
    • Master/Slave構成(Android向けのJenkins Slaveには i3.metal instanceを利用)
    • Jenkins環境はAndroid以外のプロジェクトも利用
  • GitHubへのプッシュ毎に実行されるタスク
    • unit/instrumented/Espresso testsの実行
      • 取得可能なものはカバレッジの取得も含む
    • 必要なapkのビルド
  • テスト端末
    • 合計14個のエミュレータを作成・起動し、並列にAndroid APIの必要な instrumented/Espresso テストを全て実行
  • ガイド

擬似的な Gradleタスクを用いて上記を模倣すると、以下のような処理がプッシュ毎に実行されています。

./gradlew clean
./gradlew checkLicenses
./gradlew testAllUnitAndInstrumentedTests
./gradlew assembleOurInternalApk
./gradlew uploadApkForInternal

GitHubへのプッシュを契機に以下のような流れで処理が Jenkins Slave 上で実行されます。

GitHub <----> Jenkins (master) <-----> Jenkins (slave)
                                        Build/All Tests

旧CI環境

変更する前の環境は以下です。

  • AWS上に構築されたJenkins環境
    • Master/Slave構成(Android向けのJenkins Slaveには i3.metal instance 以外を利用)
    • このJenkins環境はAndroid以外のプロジェクトも利用
  • GitHubへのプッシュ毎に実行されるタスク
    • unit/instrumented/Espresso testsの実行
      • 取得可能なものはカバレッジの取得も含む
    • 必要なapkのビルド
  • テスト端末
  • ガイド

旧CI環境では、以下のように2つの Jenkins Slave を用意し、実施するテストを分ていました。

GitHub <----> Jenkins (master) <-----> Jenkins (slave) 1
                        |              Build/non-UIテスト
                        |------------> Jenkins (slave) 2
                                        Build/UIテスト

上の図に出ている non-UIテストとは、unit/instrumentation-based JUnitを指す、UI描画を含まないテストをさします。これらにはGenymotion Cloudを使い、いくつかのビルド含む18~20分かけてビルドしていました。 UIテストとは、Espressoを使ったテストです。OpenSTFを使いテストを実行していました。いくつかのビルド含む、合計で15分程度かかるジョブです。

2つの異なるJenkins job環境

UIテストはOpenSTFを用いて実行していた、と上記で書いています。

AWS上では、ARMベースのAndroidエミュレータしか実行できませんでした。そのため、UIテストは特に実行/描画速度が遅く、UIの描画を含むテストを安定して実行することが厳しかったです。私たちの環境では、Genymotionも多少なりもとも制限を有していました。そのため、UI描画が不要なテストと、UI描画が必要なテストは分けて実行していました。

各々のJenkins jobは並列して実行されるようにしていました。それぞれ、15分程度は少なくとも実行環境まで時間がかかります。

このくらいの待ち時間になると少しコーヒーで一息つくことができる感じですね。

f:id:kazucocoa:20180705183344j:plain

よりJenkins Jobを細かく分割などすれば時間は短縮することはできますが、Jobの管理などが複雑になってきます。

なぜAWSを使っているか

現在、私たちは可能な限り自分たちで物理リソースを保守したくありません。私たちはAWSを主に利用しています。

強力な物理マシンを用意しそれをCIとして利用できれば今回と同様のことを実現できるでしょう。その場合は環境を拡大するさい、そのつど端末購入が必要になってきます。現在のiOSビルド環境に似たようなものですね。それは保守コストや将来的にもチーム拡大の足枷になるとふんでいました。

そのため、私たちはAWS上で利用可能なそのようなマシンを待っていました。

AWSのベアメタルインスタンス

昨年の終わり頃、Bare Metal Instances for EC2が発表されました。その環境下では、Androidのx86エミュレータによる実行が可能になると期待できていました。Androdエミュレータ向けのVMアクセラレーションの各種機能を利用できることも期待できました。

少し、x86エミュレータについて説明を残しておきます。

Googleは公式のAndroidエミュレータを公開しています。そこから、私たちはいくつかのCPUアーキテクチャ上で動作するエミュレータを利用することが可能です。その中で、 x86とはInetel Processor上で動作可能なものを指します。

AWS上では、元々はARMエミュレータだけが利用可能で、x86エミュレータは利用不可能でした。一方、ベアメタルインスタンスではx86エミュレータも利用可能です。

新しい環境

Amazon EC2 Bare Metal Instances の一般向け公開をお知らせしますによってベアメタルインスタンスが利用可能になったのち、私たちはJenkins Slaveとしてその環境を検証し、使いはじめました。その途中でいくつか問題にぶつかったので、その経験を共有したいと思います。

i3.metalでAndroid環境を構築する

i3.metalの設定

現在、ベアメタルインスタンスは入手可能な地域が限られています。まずは利用したい地域でベアメタルインスタンスを入手可能か確認してください。 ベアメタルインスタンスはEC2向けの通常のインスタンス起動ウィザードから作ることが可能です。ウィザード起動後、AMIを選択してウィザードを次に進めてください。表示されるインスタンス一覧の最下部に i3.metalの表記を見つけることができるはずです。その i3.metalがこのベアメタルインスタンスです。i3.metal選択後、各々の環境にあった設定を選んでいき、インスタンスを作成してください。

なお、以下ではUbuntuをベースとしたAMIで動作を確認しています。そのほかの環境では手順が異なるところがあるかもしれませんのでお気をつけください。 i3.metalは起動までに少し時間がかかります。その間、エスプレッソでも飲みながら待ちましょう。

f:id:kazucocoa:20180705183349j:plain

Android SDKの入手

まずは Android SDK コマンドラインツールを取得する必要があります。ダウンロードページからLinux向けのものを入手してください。 i3.metalインスタンス上でそれらを展開した後、 ANDROID_HOMEANDROID_TOOLSの設定を忘れずに行なってください。

各種アクセラレーションを有効にする

i3.metalを使った大きな理由は、先でも少し述べたようにAndroidエミュレータの各種アクセラレーション機能を使うことです。この機能はARMエミュレータでは利用できません。そのため、上で述べたように私たちはOpenSFTやGenymotionをAndroid CIに使っていました。

VMアクセラレーション

Configure VM accelerationでは、VMアクセラレーション環境の構築を知ることができます。

私たちはUbuntuベースのi3.metalインスタンスを構築しています。そのため、Ubuntu KVM Installationに沿って環境構築を進めました。環境構築の後、以下のような入力に対して出力が得られたのであればでは、VMアクセラレーション環境環境の構築は完了です!

$ sudo /home/ubuntu/android/tools/emulator -accel-check
  # accel:ad
  # 0
  # KVM (version 12) is installed and usable.
  # accel

SwiftRenderによる描画のアクセラレーション

描画に対するアクセラレーション環境はいくつかの種類提供されています。 VMアクセラレーションだけでinstrumented testsに対する高速化は十分です。ただ、UIの描画のからむEspressoのテストも含んでくるとそれでは足りません。この環境構築が必要となってきます。

この中で、私は1つ問題に出くわしました。

OpenGL 関係の描画問題

まず、私は hostモードを使ってみました。OpenGLが利用可能であれば i3.metalでも macOSなど同様の描画が可能だと期待したためです。しかし、Could not initialize OpenglES emulationというエラーに出くわし、エミュレータを起動することはできませんでした。

Elastic GPUを用いることができるかと期待しましたが、この時は利用することができませんでした。

次に、私はエミュレータの -no-windowモードを利用しました。エミュレータの起動には成功したのですが、Espressoのテストを実行すると以下の例外が発生してテストが中断されました。

E AndroidRuntime: DeadSystemException: The system died; earlier logs will point to the root cause
W System.err: java.lang.Throwable: tname=main - android.os.DeadSystemException
W System.err:    at adgh.a(PG:17)
W System.err:    at adgh.uncaughtException(PG:20)
W System.err:    at java.lang.Thread.dispatchUncaughtException(Thread.java:1955)
W System.err: Caused by: java.lang.RuntimeException: android.os.DeadSystemException
W System.err:    at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1442)
W System.err:    at android.app.ContextImpl.registerReceiver(ContextImpl.java:1394)
W System.err:    at android.app.ContextImpl.registerReceiver(ContextImpl.java:1382)
W System.err:    at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:609)
W System.err:    at ajmt.a(Unknown Source:10)
W System.err:    at akmp.a(Unknown Source:117)
W System.err:    at akma.a(Unknown Source:12)
W System.err:    at akmu.a(Unknown Source:7)
W System.err:    at aklm.a(Unknown Source:8)
W System.err:    at ajqf.a(Unknown Source:2)
W System.err:    at ajpo.handleMessage(Unknown Source:11)
W System.err:    at android.os.Handler.dispatchMessage(Handler.java:106)
W System.err:    at android.os.Looper.loop(Looper.java:164)
W System.err:    at android.app.ActivityThread.main(ActivityThread.java:6494)
W System.err:    at java.lang.reflect.Method.invoke(Native Method)
W System.err:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
W System.err:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
W System.err: Caused by: android.os.DeadSystemException
W System.err:    ... 17 more
I Process : Sending signal. PID: 3501 SIG: 9

これはQtの問題だと予測したので、apt-get install xorg openboxQT_QPA_PLATFORM='offscreen'を試してみました。しかしそれでも改善はしませんでした。

解決策を試行錯誤している中で、最終的にSwiftRenderが助けてくれました。no-windowオプションと合わせてエミュレータを起動した結果、正しくエミュレータが起動し、Espressoのテスト実行も完了することができました。 Configuring graphics acceleration on the command lineにも書かれている通り、これは swiftshader_indirectモードによるエミュレータ起動で利用することができるものでした。このライブラリはOpenGL ESをCPU処理を使い実現するものです。

まとめ

ここの章は少し長かったので軽くまとめます。

  • VMアクセラレーションを有効にするためにKVMをインストールする
  • 描画のアクセラレーションを有効にするためにSwiftRenderを有効にする
    • このライブラリは $ANDROID_HOME/emulator/lib64/gles_swiftshaderに見つけることもできます
  • エミュレータは "-no-boot-anim -no-snapshot-save -netfast -noaudio -accel on -no-window -gpu swiftshader_indirect"のオプションで起動する

ここで、 i3.metalインスタンスのAndorid向けビルド環境構築の説明は終わりです。こちらをJenkins Slaveとして利用可能にすると、Android向け Jenkinsインスタンスとして利用可能になります。

最後に1つ、 i3.metalインスタンスの良い点を以下にあげておきます。実は、個人的には以下の環境を手に入れることがベアメタルインスタンスの、もう一つの大きな目的でした。これにより、短時間のAndroidテスト環境を構築することができます。

CI上でテストを並列実行する

i3.metalインスタンスは非常に高性能です。72コアや、大量のメモリを搭載しています。そのため、苦無く複数エミュレータを同時に起動可能です。

エミュレータの複数同時起動により、Androidテストを実行するときに必要となる adb installの1つのエミュレータに対する実行頻度を下げることができます。shardingの機能も利用することができます。エミュレータの起動数を上げることで、テストの並列実行数をあげることもできます。

テスト対象となるapkのインストール時間はAndroidテストの実行の中で時間を必要とする処理です。私たちのプロジェクトにはモジュールが20個あります。その中で、14個のモジュールにAndroidテストが存在します。そのため、14モジュール分のandroidTest実行における adb installが処理される必要があります。それが直列で行われる場合、それなりに時間がかかってしまいます。それを解決するために、各々のモジュール1つに対して1つ専用のエミュレータを作成し、並列してそれらを実行できるようにしました。

以下のGIFアニメーションが並列実行の例です。現在は、このような状況が合計で14個、Jenkins Slave上で動作しています。並列実行を達成するためにcomposerswarmerを利用しました。(感謝も含めて、何か問題や追加したい機能があればPRを送るなども行いたいですね。)

f:id:kazucocoa:20180705183355g:plain

以下のスクリプトは複数のサブプロセスを操作するbashスクリプトの例です。これにより、シェルのサブプロセスでバックグラウンド実行するAndoridテストの終了を待ち、結果を集計してJenkinsの成功/失敗をまとめることができるようになっています。

function wait_and_get_exit_codes() {
    children=("$@")
    EXIT_CODE=0
    for job in "${children[@]}"; do
       echo "PID => ${job}"
       CODE=0;
       wait ${job} || CODE=$?
       if [[ "${CODE}" != "0" ]]; then
           EXIT_CODE=1;
       fi
    done
}

EXIT_CODE=0
children_pids=()

# composerスクリプトを `&` 指定で実行
children_pids+=("$!")

wait_and_get_exit_codes "${children_pids[@]}"

exit "${EXIT_CODE}" # いずれかのサブプロセスの終了コードが1の場合だと、ここが1になる

Conclusion

読んでいただいてありがとうございます。

上記により、 i3.metalインスタンスを使ったAndroid CIの環境構築方法を知ることができました。また、複数エミュレータ起動によるテストの並列実行の様子も見ることができたかと思います。

これらの対策により、AndroidのCI環境は今までよりも高速に、安定するようになりました。全てのプッシュに対するCI実行は7分程度で完了します。まだチューニングの余地はあるので、必要であればより最適化する余地もあります。ベアメタルインスタンスは価格がほかのインスタンスよりも高価なので、より効率的な利用ができるようにしていくことも将来的な挑戦でもあります。

最後に、検証・本番環境構築にKohei Suzukiさん、 Takayuki Watanabeさんの協力もいただきました。ありがとうございます。

Firebaseを活用したiOSアプリ開発事例

$
0
0

こんにちは。新規サービス開発部の中村です。

最近Komercoで販売されている鉄のフライパンが欲しいです。クリエイターさんたちの作品は見ているだけで本当に楽しいですね。

そんなKomercoはバックエンドにFirebaseを活用していますが、実は弊社からKomercoの他にもFirebaseを活用したサービス「Cookin'」をリリースしています。

本稿ではCookin'のFirebaseを活用した事例についてご紹介します。

Cookin'とは

Cookin'は料理動画撮影アプリです。手順ごとに3秒間取るだけで1本の料理動画が投稿できるサービスとして、2017年12月にiOSアプリとしてリリースしています。

このサービスの特徴は、簡単に料理動画が作成できるほかに、投稿から斬新なアイデアを得られたり、コメント欄から料理のコツやポイントを気軽に質問できるところです。

料理をしながら動画撮影するのは難しいですが、慣れると楽しいです。

App Store リンク

誠に勝手ではございますが、Cookin’は提供サービスの見直しにともない、2018年7月末をもちまして、提供を終了いたします。ご利用のお客様には、ご迷惑をおかけいたしますことを深くお詫び申し上げます。サービス終了に関するご案内はこちらをご覧ください。

FIrebaseの活用事例

先ほど述べた通り、Cookin'のバックエンドはFirebaseを活用しています。Firebaseには様々なプロダクトが用意されていますが、Cookin'では以下のプロダクトを活用しています。

アプリの開発とテスト ユーザー層の拡大と利用促進
Authentication Cloud Messaging
Cloud FIrestore Google Analytics for Firebase
Cloud Storage
Cloud Functions

以降ではこれらの概要と活用事例をご紹介します。

Authentication

Authenticationは安全な認証システムを提供しています。メールアドレスとパスワードの組み合わせ、電話番号認証、匿名認証、Google、Twitter、Facebook、Githubのログイン等をサポートしています。

活用事例

ユーザーの認証に匿名認証を活用しています。これにより、データベースやストレージのセキュリティーを堅牢にしつつ、初回起動時のアカウント作成プロセスを省略することでユーザーの離脱を防いでいます。

匿名ユーザーは必要に応じてメールアドレスとパスワードでの認証に切り替えることも可能です。

匿名ユーザーのアカウントを停止したい場合は一手間必要になります。詳細はこちらのブログ記事をご覧ください。

Cloud Firestore

Cloud FirestoreはNoSQLドキュメントデータベースです。オフラインの場合でもデータにアクセスして変更を加えることができ、オンラインに復帰すると自動的に変更したデータを同期します。スケーリングは自動的に行われ、セキュリティールールを書くことでセキュリティーを堅牢にできます。

活用事例

  • 投稿情報、コメント、ユーザー情報等を保存しています。
  • 投稿画面に閲覧しているユーザーのアイコンをリアルタイムで表示しています。このようなリアルタイム機能を素早く実装できることも大きな特徴です。

    閲覧ユーザーアイコンのキャプチャ

  • ユーザーページの投稿一覧は、クエリ機能を利用して全体の投稿から特定のユーザーの投稿を取得して表示しています。また、クエリを利用してソートやページングを実装しています。

    全体の投稿から特定のユーザーの投稿を取得するクエリの例

    postsRef.whereField("authorID", isEqualTo: user.id)

    ユーザーページのキャプチャ

現時点ではCloud Firestoreにバックアップ機能が提供されていないため、バックアップする場合は自前で行う必要があります。

Cloud Storage

Cloud Storageは写真や動画等の容量が大きなファイルを保存できます。Cloud Firestoreと同様オフラインサポートと高いスケーラビリティを備え、セキュリティールールを書くことでセキュリティーを堅牢にできます。

活用事例

  • 動画やサムネイル画像を保存しています。

  • セキュリティールールの例を上げると、Cloud Storageにアップロードされる動画のファイルサイズを1MB以下に制限するために、下記のセキュリティールールを書いています。

セキュリティールールの例

service firebase.storage {
  match /b/{bucket}/o {
    match /version/1 {
      match /video/{videoID}/file/{file} {
      
        // 認証済みユーザーのみ動画ファイルの読み込みが可能
        allow read: if request.auth != null;
        
        // 動画ファイルが書き込まれる際の条件
        allow write: if (request.auth != null && request.resource == null)
                     || (request.auth != null &&
                     // 1MB 以上の動画ファイルは許可しない
                     request.resource.size < 1 * 1024 * 1024 &&
                     request.resource.contentType.matches('video/.*'));
      }
    }
  }
}

Cloud Functions

Cloud FunctionsはCloud FirestoreやCloud Storageへのデータの追加や変更、またはHTTPSリクエストによりトリガーされたイベントに応じてバックエンドコードを自動的に実行できます。

活用事例

  • プッシュ通知やSlackへのメッセージ送信に活用しています。例えば、ある投稿に新しいコメントが書き込まれたときに、投稿者にコメントが書き込まれたことをプッシュ通知で知らせたり、アプリから不適切な投稿が報告されたときにSlackにその報告を流しています。

    Cloud Functionは1つのイベントを元に複数回トリガーされることがあるため、関数は何回実行されても問題ないように実装しておく必要があります。詳細はこちらのブログ記事をご覧ください。

  • 関数のディレクトリ構造・命名は以下のようにしています。これにより、コードを修正をするときにどのイベントから実行される関数なのか把握しやすくしています。

それぞれのイベントから実行される関数が明確になります。

├── functions
│   ├── auth
│   ├── db
│   │   ├── comment
│   │   │   └── onCreate.ts
│   │   ├── feedback
│   │   │   └── onCreate.ts
│   │   ├── post
│   │   │   └── onCreate.ts
│   │   └── report
│   │       └── onCreate.ts
│   └── storage
├── index.ts
├── test

どのイベントから実行される関数なのかファイルの中を確認しないと分からないため把握しにくいです。

├── functions
│   ├── notifyPost.ts
│   ├── notifyComment.ts
│   ├── notifyReport.ts
├── index.ts
├── test

関数のテスト

テストフレームワークはJestを利用しています。

テスト方法はオフラインモードとオンラインモードの2種類あり、オフラインモードで行う場合、データベースの書き込みを全てスタブしなければならないため、オンラインモードで行っています。

オンラインモードではデータベースへの書き込みやユーザーの作成などが実際に行われ、テストコードがその結果を検査できるように、テスト専用のFirebaseプロジェクトとやり取りするテストを作成します。

注意点として、Firebase Test SDKのfirebase-functions-testnpmモジュールをテスト専用のFirebaseプロジェクトの構成値で初期化した後、メイン関数ファイルをモジュールとしてインポートしなければなりません。

この順序を守らないと予期しないFirebaseプロジェクトとやり取り(書き込み・読み込み)が行われてしまうことがありました。

防止策として、JestのsetupFilesのタイミングでfirebase-functions-testnpmモジュールを初期化するようにしています。

Cloud Messaging

Cloud Messagingは、iOS、Android、ウェブ(JavaScript)クライアントアプリに通知メッセージを送信することができます。

活用事例

Cloud FunctionsからAdmin FCM APIを利用してプッシュ通知を送信しています。

ユーザーが料理を始めたことを知らせる通知自分の投稿にコメントが届いたことを知らせる通知の2種類を送信しています。

Google Analytics for Firebase

Google Analytics for Firebaseは、最大で500種類のイベントに関するレポートを無料で無制限に生成できます。Firebaseのコンソールからダッシュボードを見ることができます。

活用事例

ユーザーの動画閲覧から動画投稿までのファネルを作成して活用しています。

その他のライブラリ

以降はFirebaseの他に活用しているライブラリの一部をご紹介します。

Pring

データの読み込み・書き込みにPringを活用しています。

PringはCloud FirestoreとCloud StorageのO/Rマッパーで、Cloud FirestoreとCloud StorageのAPIを意識せずにデータの読み込み・書き込みができます。

Pringを活用すると開発スピードを加速させることができます。

FilterCam

iOS SDKのCore Image FIlterを適用した動画を簡単に作成することができます。

撮影した料理がより魅力的になるように彩度と中間色の明るさをやや上げたフィルターをFilterCamに適用して活用しています。

適用前(左) 適用後(右)

まとめ

本稿を通してiOSアプリのバックエンドにFirebaseを活用した事例をご紹介しました。

Firebaseは開発が活発で新しい機能が追加され続けているので、今後もより使いやすくなることが期待できます。プロダクトの性質に合わせて、うまく活用できれば大きなメリットが得られるのではないでしょうか。

最後になりましたが、冒頭に記載しました通り、Cookin'は7月末をもちまして提供を終了いたします。これまでCookin'を支えてくれた皆さまに感謝申し上げます。ありがとうございました。

新規決済手段導入に際し、なるべく丁寧にテストケースを作成した話

$
0
0

会員事業部の日高尚美(@natan3)です。 半年前になりますが、クックパッドでは Android ユーザ向けにプレミアムサービスの決済手段の一つとして Google Play 決済を導入しました。

ユーザに新たな機能を提供する前には、何らかの形で開発者側での検証が必要です。

Google Play 決済導入バージョンのリリースは、ユーザのお金を扱うこともあり、不具合が起きた際にサービス全体の信用に関わる、非常にリスクの高いリリースでした。 それに伴い、検証もできる限り万全に行わなければなりません。

そのため、なるべく丁寧にテストケースを作成し、それをもとに検証を実施することで新機能が期待通りに実装されていることを担保しました。 丁寧にテストケースを作成したから、というだけではもちろんありませんが、リリースから半年経った今でも Google Play 決済周りの目立った不具合はまだ見つかっておりません。

今回作成したテストケースの紹介

今回作成したテストケースの一部をご紹介します。 実際に利用したものと表現を変えてはいますが、雰囲気は伝わるかなと思います。

f:id:nano-041214:20180718194510p:plain

含まれている項目は以下のとおりです。

  • 各画面で起こりうる状況の組み合わせ(上図で言うところの前提条件)
  • 前提条件を再現するシナリオ
  • シナリオを達成するための手順
  • 手順に対応した期待する振る舞い
  • 期待する振る舞いが得られたかどうかのチェック欄

これを元に動作を確認しました。

それぞれのケースにて、想定した通りの画面が表示されていれば問題ありません。 しかし、実装によっては、期待する振る舞いが得られない場合も出てきます。

テストケースにて期待する振る舞いが本来あるべき姿なので、その場合には実装に手を入れる必要があります。 すべての操作に対して期待する振る舞いが得られる状態にすることが、テストケース作成の目的であり、検証のゴールとなります。

Google Play 決済のテストケース作成までの流れ

なにもないところからいきなりテストケースを作成することは難しいため、 画面遷移図とそれらの分岐条件、各 API の返す異常系一覧をもとに、テストケースを作成しました。

テストケースを作成する流れは以下のようになります。

  1. 各画面で起こりうる状況を整理する
  2. 前提条件を再現するシナリオを作成する
  3. そのシナリオをなぞるための手順を埋める
  4. その手順を行うことで期待されるアプリの振る舞いを埋める

以降の文章で、各項目について解説します。

各画面で起こりうる状況を整理する

テストケースにおいて重要なのは 網羅性です。

そのため、画面遷移図の各分岐である API 接続箇所や画面遷移時にて起こりうるユーザ状態など、前提条件を構成する各状況を洗い出します。

実際の遷移図ではありませんが以降の理解のために Google Play 決済導入時の画面遷移のイメージ図を添えておきます。

f:id:nano-041214:20180718194451j:plain

Google Play 決済導入プロジェクト初期リリースではクックパッドにログイン済みの無料ユーザのみを対象としていたため *1、 各分岐において以下のような状況の組み合わせを、検証すべき条件として考えました。

  • ユーザの状態がどうか
    • ログイン済みユーザ or ゲストユーザ
    • プレミアムサービス会員ユーザ or 無料ユーザ
  • Android のクックパッドアプリのバージョンが決済対応バージョン以降かどうか
  • 決済可能端末かどうか
    • クックパッドがプリインストールされているらくらくフォンなどの場合、Google Play ストアアプリがないため Google Play 決済も利用できない
  • 異常な操作をした場合でも、何らかの方法で本来あるべき状態に復帰可能かどうか
    • この画面を表示している状態で Web からクックパッドのユーザ登録を解除してみる
    • この画面を表示している状態で Web からクレジットカード決済でプレミアムサービスになってみる
    • ここでフライトモードにしてみる

もちろん全ての組み合わせが成立するわけではありません。 例えば決済可能端末かどうかについては、決済不可能端末を伝える画面にて遷移がストップするため、以降の画面については考えない、といった具合です。

前提条件を再現するシナリオを作成する

それぞれの状況が洗い出せると、それらを組み合わせることでユーザが何をしようとしたかのシナリオが書き出せます。

状況の羅列にはなりますが、シナリオを作成しておくことで見通しが良くなり、仕様の考慮漏れについて気づきやすくなります。

例えば、

「ログイン済みユーザが Google Play 決済用画面を開いた状態で Web からクレジットカード決済でプレミアムサービスになった場合、
 Google Play で決済するボタンを押すと既に課金済みであることを示すダイアログが表示される」

といったものです。

そのシナリオをなぞるための手順を埋める

シナリオまで埋め終わると安心してしまいがちですが、書き手以外が再現可能なレベルで手順を埋めるまでがテストケース作成です。

テストケースは、一度作成すると、製作者本人が望もうが望まなかろうが再利用される可能性が高いです。 現にクックパッドでは、作成されたテストケースは今後のテストケース作成の参考にできるよう、社内で共有されています。

コードや文章に限らず、テストケースも、書いた本人ですら暫く経つと書いた内容やなぜこう書いたかを忘れがちです。 テストケースが雑に書かれていた場合には、この記述が当時何のために書かれていたのかを考古学する必要が出てきます。

そのため、なるべく丁寧に書きましょう。

先程の

「ログイン済みユーザが Google Play 決済用画面を開いた状態で Web からクレジットカード決済でプレミアムサービスになった場合、
 Google Play で決済するボタンを押すと既に課金済みであることを示すダイアログが表示される」

というシナリオの例に対しての手順を具体的に書くと、

  1. 無料ユーザでログインした状態でアプリのトップページを開く
  2. サイドメニューのプレミアムサービス会員登録導線をタップする
  3. Google Play 決済を利用するボタンを押す
  4. ブラウザアプリを起動し Web からクックパッドのトップページにアクセスする
  5. Web から先ほどと同一のユーザでログインする
  6. Web のトップページのプレミアムサービス会員登録導線を開く
  7. Web の登録ページからクレジットカード決済を利用してプレミアムサービスに登録する
  8. アプリの画面に戻り、プレミアムサービスに Google Play で決済するボタンを押す

といったものになります。

その手順を行うことで期待されるアプリの振る舞いを埋める

こちらも先程の手順同様、丁寧に書く必要があります。

先程の手順の例に対応するアプリの振る舞いとしては以下のようになります。

  1. 無料ユーザ向けのトップページが表示される
  2. プレミアムサービスを訴求する画面が表示される
  3. Google Play 決済用の画面が表示される
  4. Web 版のクックパッドのゲスト向けページが表示される
  5. 先ほどと同一ユーザでログインしたトップページが表示される
  6. Web のプレミアムサービスを訴求する画面が表示される
  7. プレミアムサービス登録完了ページが表示される
  8. 既にプレミアムサービス登録済みであることを伝えるダイアログが表示される

異常系であれば、どのような操作をすれば正常系に復帰できるかを伝える画面が出ていることを期待します。 そのため、ある操作の流れで何らかの異常を伝える画面を見たユーザが、その内容を読んだ際に混乱しないかについても確認ができます。

テストケースを作成して得られた良い副作用

テストケースを作成して得られる一番の良い副作用は、仕様の考慮漏れに気づけることです。

テストケース作成前は、Google Play ストアによる決済用システムダイアログが出ているときにブラウザアプリから別な決済手段にてプレミアムサービスを登録したらどうなるのか? というケースについて考えられていませんでした。

Google Play ストアの決済用システムダイアログが出てから決済処理完了までは、クックパッドではその経過状況を知ることが不可能です。 そのため、残念ながら決済完了時に Google Play の購読情報をクックパッドに送る際に二重課金が起きていることを伝えることになります。 幸いにも既に別で考慮していたケースと同様にユーザに重複課金を伝え、問い合わせを促す画面を表示できるような実装になっておりました。

また、テストケースを作成することで、検証の手順の理解や進捗が属人化しなくなるため検証を分担したりダブルチェックしたりできます。

加えて、きちんとやったから大丈夫、という安心感がえられます。

もちろんリリースして運用に入るまで何が起こるかはわからないですが、 最大限やることはやった!という安心感を持ってリリースできるので精神衛生上良いです。

テストケース作成期間について

テストケース作成に着手してから、検証し、修正完了するまでに 1ヶ月ほどかかりました。 開発期間全体のおよそ 1/3 もかかってしまった理由は、テストケース作成と並行して API 実装を始めとした他の作業などもやっていたためです。

しかし、効率的な開発を目指す上で、必ずしもテストケース作成期間を短くする必要はないと考えています。

というのも、仕様を煮詰めながらテストケースも整理していくことで、結果的に考慮漏れによる実装の手戻りを減らすことができるからです。 仕様を煮詰めている段階でテストケースも同時に作成していくことは、回り回って効率的に開発を進めることができると感じました。

最後に

検証を怠ることは、ユーザに検証をさせることと同義です。

もちろんすべてのケースを想定したテストケースを作成するのは不可能です。 しかし、ユーザにサービスを提供する以上、リリースまでに最善をつくすことに価値があると考えております。

テストケースは技術部品質向上グループチーム(以下 QIT: Quality Improvement Team)と密に協力して作成しました。 QIT はクックパッドのアプリを利用したユーザが技術的な問題で残念な思いをする体験を減らすための専門家集団です。

このように、クックパッドでは新機能リリースプロジェクトのために様々な分野のエンジニアが一丸となって取り組みます。

そのため、クックパッドではアプリケーションの品質向上に興味のあるエンジニアや、企画から、実装、テストケース作成までを一貫して行うエンジニアリングに興味のあるエンジニアを募集中です。 興味がある方は採用ページまで!

*1:現在はクックパッドに会員登録、もしくはログインしていないユーザでも Google Play 決済を利用してプレミアムサービスを利用できます

スマートまな板による料理支援

$
0
0

研究開発部アルバイトの佐藤です。今日はアルバイト期間中に取り組んでいたまな板にレシピを表示する装置について紹介します。

背景

レシピ本をキッチンに持ち込む以外にも、キッチンでスマホ上から検索することによってレシピを見る機会が増えています。しかし、キッチン内でタブレット端末やスマホでレシピを見る問題点として以下が挙げられます。

  • デバイスが水や油で汚れず、レシピが見やすい位置に置きたいが、スペースの都合上難しい
  • 汚れた手で端末の画面を料理中に触って操作しなくてはならない

また、最近ではAmazon EchoやGoogle HomeなどのスマートスピーカーでCookpadのレシピを検索し、タブレット端末やスマホでレシピを保存することができます。その発展として、レシピの読み上げやEcho Showなどの端末を用いたレシピ表示なども考えられますが、端末と同様に映像などの表示位置が固定されてしまうという問題が挙げられます。また、音声での入力の他にジェスチャなどもとりいれることができればより視覚的な操作も可能ではないかと考えられます。

このような問題に対して取り組んでいるプロジェクトはいくつかあります。例として2つのプロジェクトを紹介します。

こちらのプロジェクトではユーザーの動作やキッチン台の上のものを認識して、端末に現在の動作に合わせた作業内容を表示します。切り方の動画の再生なども端末上で行っていますが、再生するには端末を操作する必要があります。また、この装置では切っている食材を認識するためにまな板自身にセンサなどを取り付ける必要があります。

こちらの論文ではカメラ・プロジェクタ・対話ロボットを連携させた調理支援システムが提案されていますが、対話ロボット1台、カメラ2台、プロジェクタ3台とかなり大掛かりなシステムとなっています。

スマートまな板

f:id:sss3p:20180720111838p:plain

そこで図のように天井に装置を設置することによりキッチン用品には非接触のスマートまな板を開発しました。このまな板の特徴はまな板には何も手を加えないことです(つまり、正確にはスマートまな板でなく、レシピプロジェクターです)。具体的には次のことを目標に開発しました。

  • プロジェクターでレシピや操作画面をまな板に投影し、視線の移動の少ない情報提供
  • 作業台の上に装置を置かないことによる広い作業スペースの提供
  • webカメラとRaspberry Piによる画像処理で人の手を検知し、画面に触らない操作

今回の対象者

以下のような問題に困っている料理初心者を対象ユーザとしました。

  • にんじん、たまねぎ、じゃがいもの剥き方・切り方がわからない
  • だしのとり方がわからない
  • ケーキをどのようにデコレーションすれば良いかわからない

今回実装した機能

上記の様な料理初心者に対して、にんじん、たまねぎ、じゃがいも、だしが材料にすべて入っている肉じゃがの調理とケーキのデコレーションを支援するような機能を実装しました。 料理初心者への支援としてまな板に映像の投影を行い、次のようなものを視覚的に提供しました。

  • にんじん、たまねぎ、じゃがいもの剥き方・切り方の手順動画:文字だけでは中々習得の難しい包丁の具体的な使い方を、まな板の左上に動画を流すことで視覚的に伝える
  • 材料と調味料のチェックボックス:何が準備できていて次に何を準備すべきか判断しやすいようにする
  • 手順表示:一文ずつ手順を表示していく。また、時間が手順に書いてあった場合はタイマーを起動する
  • デコレーションケーキの下書き:デコレーションケーキの下書きを投影し、デコレーションの位置ガイドとして使えるようにする

実装方法

webカメラ・小型プロジェクター・Raspberry Piを用いて実装を行いました。 開発言語はPython3、使用ライブラリはtkinter、opencv2です。 詳細は以下のようになっています。

使用物名 型名など
webカメラ Logicool HD720p
小型プロジェクター iOCHOW iO4 ミニ プロジェクター
Raspberry Pi Raspberry Pi 3, raspbian gnu/linux 9
Python3 Python 3.5.3
tkinter version 8.6
opencv2 version 3.4.1

手の認識

簡易的なデモ機の実装としてカラートラッキングを用いました。具体的には手の肌色をトラッキングすることで手の位置を捉えて、画面操作ができるように実装しました。

デモ

デモ中の写真をいくつか紹介します。

  • スタート画面 f:id:sss3p:20180720111936j:plain
  • メニュー選択 f:id:sss3p:20180720112025j:plainf:id:sss3p:20180720112045j:plain
  • 材料一覧表示 f:id:sss3p:20180720112117j:plain
  • 手順表示 f:id:sss3p:20180720112244j:plain

気づき

実装したスマートまな板では視線の移動の少ない情報提供、広い作業スペースの提供、画面に触らない操作を実現することができました。 実際に実装してみて気づいたことは以下です。

  • 視線の移動の少ない情報提供:
    • 切り方動画をまな板の左上に表示することによって、動画を確認しながら作業することができた
    • 作業によってはまな板に投影するよりもキッチンの壁に投影したほうが良い場合もある(まな板に投影したほうが良い場合は食材を切るときの動画での切り方確認で、キッチンの壁に投影したほうが良い場合はレシピ表示とタイマー)
  • 広い作業スペースの提供:
    • スマホやタブレット端末を作業台の上に置く必要がないため、広い作業スペースを確保できた
  • 画面に触らない操作:
    • 今までレシピを確認するために画面操作する度に手を拭いたり洗ったりしていたことがなくなった

デモ

実際にスマートまな板を数人に体験していただきました。体験後、頂いた意見をいくつか紹介します。

  • 材料チェックリストがまな板の上で操作できるのは便利
  • まな板の上で切り方動画を見られるのは面白い
  • 実用化するんだったら、スマートスピーカーと組み合わせて提供する情報や選択肢によって音声か映像か使い分けたほうがよさそう

また、いくつかの改善点や追加機能がみつかりました。

  • ユーザーの動作によって投影位置を変える機能(ユーザーの作業している場所を検知して、作業の邪魔にならないようなスペースへ画面を移動・縮小させる)
  • まな板の上に置かれた材料を認識したレシピ検索
  • まな板の上に置かれた材料の重さを概算し、レシピで指定されている重さによって切り方を投影する機能
  • 自動でデコレーションの下書きを拡大縮小したり移動したりしてケーキに下書きを合わせてくれる機能

現在の実装では以下の問題が発生しています。

  • 肌色の位置を手の位置と認識しているため、色の似ている木のまな板などを誤認識
  • 指先や指を認識していないため手首などを誤認識するなど認識精度が低い そのため、手の認識専用デバイスを利用しない場合はニューラルネットワークを用いて手を手としてラベル付したり、手の形を認識するなどの実装に変更することが考えられます。 また、Leap Motionなどの外部デバイスを用いて手の認識を行うということも考えられます。

まとめ

キッチンでレシピを確認するときに、視線の移動の少ない情報提供、広い作業スペースの提供、画面に触らない操作を実現できるスマートまな板の開発に取り組みました。

実際に実装することによって、提案の有用性や改善点を見つけることができました。

今後の展開としてはスマートまな板を用いたアプリの開発などが考えられます。具体的には切り方動画をまな板で再生できることや材料・手順のまな板への投影を用いて、子供・初心者向け料理学習アプリなどを実装することにより、より料理初心者への支援ができると考えられます。

Chaos Engineering やっていく宣言

$
0
0

技術部のヨシオリです。

Netflix が Chaos Engineering の論文を公開して 2 年ほど経ちました。
クックパッドは最近、 Chaos Engineering を導入する事を決めました。
この記事ではその背景を紹介したいと思います。

そもそも Chaos Engineering とは

Netflix では Failure Injection Testing として、営業時間中に意図的に障害を起す事をやっていました。Chaos Monkey というインスタンスとサービスを落すものから Chaos Gorilla、Kong という availability zone や region 単位で障害を発生させるものなどです。

その経験から Chaos Engineering というものが提唱されました。
Principles of Chaos Engineeringによれば

Chaos Engineering is the discipline of experimenting on a distributed system in order to build confidence in the system’s capability to withstand turbulent conditions in production.

と定義されています。
意訳すると「本番環境の分散システムが過酷な状況でも耐えれるとの確信を得るために、実験するという取り組み」とかでしょうか?

分散システムはマイクロサービスと置きかえるとイメージしやすいと思います。複数のマイクロサービスが相互に呼び出し、協調して動くシステムでは一つのサービスがクラッシュしただけでシステム全体が壊れるような事になっていてはいけません。そうなると、ユーザーに届けられる価値が減ってしまいビジネス的にも問題です。
もちろんそうならないように作るべきですが、それでも予想不可能な事は起こります。それを知るためにコントロールされた障害を投入し、知見を広げたり、確信を得たりするのです。

そのための実験は下記のステップで進めます。

  1. 正常な振る舞いをしているかどうかを測定可能な値として定義する。
  2. この正常な状態が通常時と障害エミュレート時の両方で継続することを仮定する。
  3. サーバクラッシュ、ディスク異常、ネットワークエラーなどの現実世界で起こりえる障害をエミュレートする。
  4. 1で定義した値を通常時と障害エミュレート時で比較し検証していく。

詳しくは上記論文やリンク先を見ていただくとして、凄くザックリと纏めてしまうと、
システムにコントロールされた障害をエミューレートし、それでも壊れない事を検証していく
と、思ってもらえれば良いかと思います。
元々、昔から似たような事をやっているサービスはありましたが Netflix がそこに Chaos Engineering と名前と付け原則などをわかりやすく纏めた感じですね。

必要になった背景

クックパッドでは 2014 年ころからマイクロサービスに取り組んで来ました。

そして個々の Web アプリケーションはコンテナ技術で仮想化し、コンテナオーケストレーションツールとして ECS を使い運用しています。

また、サービス間の通信に関してもサービスメッシュの導入などを行なっています。

その結果、今ではチーム数も増加し、開発規模も大きくなっています。結果として( 管理画面を提供するサービスなどを除いても) 大小 80 個近くのサービスがそれぞれお互いに緩くではありますが連携し動いています(僕も数を調べてビックリしました)。

さすがにこれだけのサービスが連携して動いているとどこかで発生した障害がどこまで影響するのか把握するのは容易ではありません。
A というモバイルアプリが叩いている B という API の裏で通信している C が必要としているデータ取得のための D のレスポンス時間が遅くなって、結果として A の応答が悪くなったのだが、原因が D だとは思っていなかった……的な事も発生します。

何故やるのか

上記ブログのマイクロサービス導入背景にもありますが、昔のようにひとつの巨大なアプリケーションを運用するようなスタイルではプロダクト開発の規模の拡大やスピードに限界があり、マイクロサービスアーキテクチャを採用するようになりました。分散システムとして Web サービスを実装する事により単一の複雑なアプリケーションからは開放されましたが個々のサービス間の連携は複雑になりました。

Chaos Engineering の導入によってサービスの耐障害性に自信を持てるようにします。日常的に障害をエミュレートする事によってサービスの耐障害性が高いことを開発者に要求します。
ソフトウェアテストなどの文脈では良く言われますが、不具合は発見が遅れれば遅れるほど、その不具合を修正するコストはかさむことになります。
可用性の高いシステムを作るために、不具合を早く発見し改善していくために Chaos Engineering を導入していきます。

付随して考えている事

人は自分が想像出来るものにしか対処出来ません。バグというのは大体が想定外の入力によって発生します。そこでもっと想像力を働かせろ的な精神論を言っていても良くはなりません。Chaos Engineering のように実際にそういった状況を作る事が大事だと思っています。
例えばクックパッドでは各サービスがどんどん AWS の Spot インスタンスで動くように移行していっています。これはサーバはいつ落ちても良いようにしておかなくてはいけないし、バックグラウンドジョブは落ちたら再実行出来るようにしておかなくてはいけない事を開発者に強制します。
でも、それらは実は Spot インスタンスで動くからやらなければいけないものではありません。耐障害性の高いシステムを作るためにはやらなければいけない事を Spot インスタンスの環境にする事によって強制するようになっただけです。
さきほども書いたように人は自分が想像出来るものにしか対処出来ません。Chad Fowler が Immutable Infrastructure を提唱したけれども、開発者がそれを真に実行出来るようになったのは Docker という環境のおかげというのと同じです。

最後に

現在、クックパッドでは Hako や ECS を使ったコンテナ環境の整備が進み、サービスメッシュの導入によりサービス間の通信を集中管理出来るようになりました。これにより、Envoy proxy を利用してサービス間通信で障害をエミュレートしたり、それらの設定を hako で行えるようになったりと環境は整いました。
まだまだクックパッドのマイクロサービス群の正常な状態( steady-state )をどう定義していくかなどやらなければいけない事は色々あります。
個人的にはこの規模のマイクロサービス群を扱っていく環境は国内ではそんなに多くはなく大変面白い環境だと思ってます。
クックパッドでは一緒に Chaos Engineering を導入していく仲間を募集しています
このエントリを読んでご興味をお持ちいただけた方は、ぜひともご応募ください。


【開催レポ】Cookpad Tech Kitchen #16 コメルコテックバナシ〜新規事業開発のリアル〜

$
0
0

こんにちは。広報部のとくなり餃子大好き( id:tokunarigyozadaisuki)です。

2018年7月19日に、Cookpad Tech Kitchen #16 コメルコテックバナシ〜新規事業開発のリアル〜を開催いたしました。クックパッドでは、Cookpad Tech Kitchenを通して、技術やサービス開発に関する知見を定期的に発信しています。

f:id:tokunarigyozadaisuki:20180802173248j:plain
ホワイトボードと発表練習中の星川

第16回は2018年6月26日にリリースいたしました、料理が楽しくなるマルシェアプリ「Komerco-コメルコ-」の開発裏話をテーマとし、Firebase, Algolia など利用している技術の話はもちろん、新規事業のサービス開発について、デザインの観点からもお話をさせていただきました。 本ブログを通してご来場いただけなかったみなさまにも、当日の様子をお届けしたいと思います!

料理が楽しくなるマルシェアプリ「Komerco-コメルコ-」

f:id:tokunarigyozadaisuki:20180802173546p:plain

「モノとの出会いで、料理はもっと楽しくなる」

「Komerco-コメルコ-」は、料理道具、うつわ、カトラリー、リネン雑貨などの“料理が楽しくなるモノ”が買えるマルシェアプリです。自身の手で創るモノで「料理を楽しんで欲しい」と願うクリエイターさんが出品した作品を、スマートフォンアプリから直接購入することができます。また、作品のこだわりやストーリーを紹介する「コメルコバナシ」などの記事コンテンツも提供しております。 みなさんもぜひアプリをダウンロードして、とっておきのモノを見つけてみてくださいね!

Komerco - コメルコ - by クックパッド

Komerco - コメルコ - by クックパッド

  • Cookpad Inc.
  • ショッピング
  • 無料

発表プログラム

「Komerco-コメルコ-を支える技術」

はじめに、2017年に中途入社したiOSエンジニアの星川より「Komerco-コメルコ-を支える技術」というタイトルで、「Komerco-コメルコ-」で利用している技術についてお話しいたしました。

speakerdeck.com

「Firestore と Cloud Storage を用いたアプリでの画像の扱い方」

2017年に新卒でクックパッドへ入社した三浦からは、「Komerco-コメルコ-」でのユーザーから投稿される画像の圧縮やリサイズに関して、サンプルアプリを用いながら画像の投稿、取得フローについてご紹介しました。

speakerdeck.com

「Effective Firestore Security」

2017年に中途入社したiOSエンジニアの岸本からは、Firebase Cloud Firestoreの "セキュリティ"に焦点を当てて、問題となるケースの紹介、セキュリティを意識したモデル設計、セキュリティルールの実践的な書き方をお話させていただきました。 speakerdeck.com

「ゼロからはじめるサービスのデザイン」

2017年から新卒でクックパッドへ入社し、現在「Komerco-コメルコ-」のリードデザイナーとしてブランディングからサービスの体験、Web・アプリのUIなどデザイン全般と開発を担う藤井からは立ち上げからリリースまで、サービス全体のデザインを見るにあたって取り組んだことや、仕組みづくり、デザインの観点からサービス開発のリアルをお話いたしました。

speakerdeck.com

付箋形式でお答えするQ&Aディスカッション

Cookpad Tech Kitchenでは参加者のみなさまからの質問を付箋で集めています。 ほんの一部ではありますが、当日は下記のような質問に回答いたしました。

f:id:tokunarigyozadaisuki:20180802173252j:plain
たくさんのご質問ありがとうございました!

Q: Firestoreのデメリットは? A: スキーマが無い。エクスポート出来ない(分析の為にスクリプトで対応している)

Q: Cloud Functionsでの失敗をどの様にハンドリングしている? A: 不整合のあるデータを集めてきて一括でパッチするスクリプトがある

Q: rulesを書きはじめるタイミングは? A: 最初は緩かった。リリースする数ヶ月前ぐらいからでもよいのでは。とはいえ最初から書けるなら書いた方が良い

Q: rulesを書けなかったのはどういう時? A: 制限がある。リスト、マップの中の値が何かをチェック出来なかった。他のコレクション・ドキュメントを参照したり出来なかった。お金に関する部分はCloud Functionsに寄せている

Q: UIから直接モデル(Firestore)を操作していましたが、テストはどうしていますか? A: 外部のモデルフレームワークに則っているからそっちで任せている。モデルに実装を寄せると自由度が落ちる

f:id:tokunarigyozadaisuki:20180802173259j:plain

シェフの手作り料理

Cookpad Tech Kitchen ではイベントに参加してくださったみなさまにおもてなしの気持ちを込めて、シェフ手作りのごはんをご用意しております!食べながら飲みながらカジュアルに発表を聞いていただけるように工夫しています。今回お越しいただけなかった方も、ぜひ次のイベントはご参加くださいね。

f:id:tokunarigyozadaisuki:20180802174125j:plainf:id:tokunarigyozadaisuki:20180802234907j:plain
オリジナルロゴ寿司ケーキとシェフ特製の料理

f:id:tokunarigyozadaisuki:20180803080331j:plain
乾杯の様子

おわりに

クックパッドではKomerco事業部はもちろん、その他新規事業、レシピサービス事業などに携わる新しい仲間を募集しています。ご興味がある方はぜひご応募ください!お待ちしています。

www.wantedly.com

http:// https://info.cookpad.com/careers

次回のCookpad Tech Kitchen のテーマは「北欧で最新のインタラクションデザインを学んできた話」。8月22日 (水)に開催予定です! クックパッドでは他にも様々なイベントを企画しておりますため、今後のイベント情報についてご興味がある方は、ぜひConnpassのメンバー登録をポチっとお願いいたします!みなさまにお会いできることを楽しみにしております。

cookpad.connpass.com

iOSDC Japan 2018 に2名が登壇&ブースでお待ちしております!

$
0
0

こんにちは!広報部のとくなり餃子大好き( id:tokunarigyozadaisuki)です。

毎日異常気象が続いていますね。猛暑に豪雨…みなさん、体調管理には十分気をつけてくださいね。

さて、iOSと周辺技術を題材としたカンファレンス、iOSDC Japan 2018が今年も8月30日(木)〜9月2日(日)に開催されますね!

クックパッドは、昨年同様プラチナスポンサーをさせていただいておりますので、ブースを出展いたします。また、弊社エンジニア@giginet@slightairが登壇し、@sgr-ksmtが当日スタッフとして関わってくれます。カンファレンスには、他にも多くの社員が参加いたしますので、会場でクックパッド社員をお見かけの際には、お声がけいただけますと嬉しいです。

登壇スケジュール

クックパッドの社員2名は、カンファレンスの3日目と4日目に登壇いたします。 以下、スケジュールと登壇内容のご紹介です。

3日目 9月1日(土)

11:20〜 Track A 三木 康暉(@giginet):詳解Fastfile

プロジェクトが大規模化していくと、さまざまな業務を自動化したくなってきます。同時にロジックが増え、特定の人しかメンテできなくなったFastfileにお悩みの方も多いでしょう。 このトークでは、実際の活用事例を交えながら、大規模プロジェクトにおける効果的なFastfileの書き方、プロジェクトの雑務自動化についてをお話しします。 そのほか、fastlaneコミッターによる明日から使える実践的なtipsも数多くお伝えします。

コメント

Fastfileについて30分も何を話すんだ、と我ながら不安ですが、なかなか他所で聞けない知見を盛りだくさんにしたいと考えていますので、fastlaneを運用している方だけではなく、業務改善に興味がある方全てに役立つ内容にしたいと思っています!

16:20〜 Track C 茂呂 智大(@slightair):動作確認のための社内アプリ配信サービスを新たに作った話

アプリの開発中にビルドしたアプリをCrashlyticsBetaやDeployGateなどにアップロードし、手元で動作確認できるようにしているチームは多いと思います。 僕たちもそういったサービスを使ってきましたが、様々な課題が出てきたため自分たちの使い方にあったシステムを新しく作りました。 どういった課題がありどういうツールを用意したのか、そしてどうリリースフローが改善されたか話します。

コメント

組織に合わせてどういう仕組みやツールを用意したのか、それによってどう開発環境を改善できたかという話ができればと思っています。がんばります。

4日目 9月2日(日)

16:10〜 Track A 三木康暉(@giginet):🀄

Swiftの様々な言語機能を使って麻雀を遊んでみましょう! Swiftyな麻雀ライブラリの実装や、和了判定のアルゴリズムなどについてお話しします。

コメント

最終日の最後で、皆様お疲れだと思うので、頭を使わずに聞けるLTになると良いなと思います。僕はすでに準備で疲れています。

ブース

iOSDC Japan 2018 では、ブースの出展をいたします。グッズの配布はもちろんですが、今回は、みなさんに楽しんでいただける特別プログラムを予定しておりますので、乞うご期待……! ぜひ、お立ち寄りくださいね。

おわりに

発表内容へのご質問やクックパッドにご興味をお持ちの方は、お気軽にブースまでお越しください。みなさまにお会いできることを楽しみにしております。

機械学習を用いてユーザーのご意見分類業務を効率化した話

$
0
0

こんにちは。研究開発部の @vanhuyzです。機械学習・自然言語処理を中心に研究開発しています。 今回は機械学習を活用してユーザーからのご意見を 81 のカテゴリーに自動分類し、ユーザーサポートスタッフによる手動分類の工数を半分にできた話を紹介したいと思います。

背景

クックパッドは現在約 5,500 万人の国内月間ユーザーがあり、日々ユーザーからたくさんのご意見やご要望を頂いています。創業してからユーザーの声を大事に扱う文化があり、どのご意見も一度目を通すようにユーザーサポートスタッフが努力しています。ご意見はスタッフによってさらに分類され、必要に応じてディレクターやエンジニアに振り分けられています。

例えば、こんな感じのご意見が来ています。「このレシピは簡単なので、子供とやってみました。楽しかったです」や「機種変更して、ログイン出来ません」や「もっと具体的な内容でも検索できるようにしてほしいです。例えば、冷蔵庫の余りもので作りたいときに何種類かの食材でも検索できると助かります。」などです。

現在、ご意見に対して 100 以上のカテゴリーが登録されています。例えば、ポジティブ・ネガティブ・検索関連・ログイン関連・つくれぽ・不具合などです。

f:id:vanhuyz:20180808154458p:plain

ユーザーサポートスタッフが社内システムを使い、ご意見を読んでカテゴリーリストから適切なカテゴリーを選んだ上で、システムが Slack の適切なチャネルに転送してくれます。これにより、関係するディレクター・エンジニアはご意見を読むことができ、すぐその場で議論します。 ただし、ご意見の数が多く、カテゴリーも多いため、毎日の分類作業が大変で、ボトルネックとなっていました。そこで、自動分類の仕組みを導入したいと考えました。

ご意見分類におけるチャレンジ

ご意見分類をパッと見ると簡単そうかもしれませんが、実はそうではありません。

  • まず、カテゴリー数が多いです。現在は 100 カテゴリー以上があり、今後もどんどん増えていきます (open set problem)。

  • 次に、1つのご意見が複数カテゴリーに所属することがあります (multi-label problem)。例えば、「つくれぽを投稿するのは楽しいし、もらえるのも嬉しいですね。」というご意見は「ポジティブ」と「つくれぽ」に入っています。

  • また、蓄積されたデータは不均衡 (imbalanced data) です。汎用的なカテゴリー(例えば、ポジティブなご意見・不具合報告)のご意見が非常に多いのに対し、最近リリースしたばかりのサービス(Amazon Echo Skill, storeTV など)のご意見はまだ多くはありません。また、クローズされたサービスも多数存在し、それらに関するカテゴリーはいらなくなります。

取り組みについて

以上の3つのチャレンジに向けて、どのように解決したのかを簡単に説明します。今回は Google の Rules of Machine Learningを参考にしながらプロジェクトを進めました。43 ルールもありますが、その中で以下の 3 つを気にして進めました。

1. Don’t be afraid to launch a product without machine learning.
(機械学習を使わないことを恐れるな!)

辞書ベースの手法を試しました。例えば、「探、調べ、検索、キーワード」という単語が含まれるご意見は「検索関連」と判断されます。この方法だと、かなり高い精度が得られますが、再現率が低いです。そして、100 以上のカテゴリーの一つずつに辞書を作るのがあまり現実的ではないので、他の方法を試すことになりました。

2. Choose machine learning over a complex heuristic.
(複雑なルールになってきたら機械学習を選択!)

multi-label problem 対応で、1つのカテゴリーに相応する1つのバイナリ分類器を作りました。例えば、「ポジティブ」カテゴリーは ポジティブ-or-not 分類器になります。最終的にデータが少なすぎるカテゴリーを除いて合計 81 の分類器を作成しました。将来もしカテゴリーが増えたら、そのカテゴリーに相応する分類器を作れば良いので、open set problem も対処できます。 imbalanced data problem (負例が正例より圧倒的に多い)に関しては、正例はそのままにし、負例は正例と同数になるように無作為に選びました。

f:id:vanhuyz:20180808154522p:plain

3. Keep the first model simple and get the infrastructure right.
(シンプルなモデルを保ち、インフラを適切に保つ)

今回はサポートベクターマシン (SVM) を採用し、scikit-learn で実装しました。F1 Score を 85% 以上になるまでチューニングしました。このぐらいの精度でも十分なので、早い段階でインフラを整備し、本番環境に導入しました。もちろん100% の性能を達成することは難しいため、完全に自動分類で手作業をなくすことではなく、分類業務へサポートという形で導入しています。

以下は導入後の管理画面のイメージで、結果的にご意見サジェストという形で導入しています。

f:id:vanhuyz:20180808154629p:plain

導入前はスタッフが 100 以上のカテゴリーの中から探さないといけないのに対し、導入後は数少ない「もしかしてカテゴリー」の中だけ選択すれば良いので、非常に楽になりました。(「もしかしてカテゴリー」になかった場合は、従来通り、全カテゴリーの中から探すことです。)実際に計測したら工数を半分まで減らすことができたという結果になりました。 また、分類するスタッフに聞いたところ、昔はメンバーによって判断のばらつきがありましたが、今は直感的に分類精度が上がったという話もありました。

また、学習モデルの精度を上げるために、再学習の仕組みを導入しました。選択されていない「もしかしてカテゴリー」は学習の負例となり、Rundeck/Jenkins で毎週定期的に training コンテナを起動し、モデルを再学習しています。新しいモデルができたら、inference コンテナがそのモデルに切り替えるように設定します。仕組みは以下のイメージです。

f:id:vanhuyz:20180808154645p:plain

これによって、状況が変わっても、モデルを劣化なく長期的に使うことができます。これからしばらく運用し、いつか完全に自動分類できるかもしれません。

最後に

今回は機械学習を用いてご意見分類業務を効率化した話を紹介しました。 クックパッドでは機械学習を活用したくさんの問題を解決しています。 このエントリを読んでご興味をお持ちいただけた方は、ぜひとも採用ページからご応募ください。

リリース間近の新規事業「クックパッドマート」の立ち上げの話

$
0
0

こんにちは、買物事業部のデザイナー兼エンジニアの長野です。

現在買物事業部では、クックパッドマートという新規サービスの開発を進めています。この夏にいよいよリリースを予定しており、先日 プレスリリースを発表しました。

クックパッドマートは、今年の1月に私を含めて3名の小さなチームでサービスづくりを開始しました(8/13現在:10名)。チーム発足から半年をかけて様々な検証を行い、サービスを形にしてきたので、本記事ではそのプロセスの一部を下記の流れでご紹介したいと思います。

  1. クックパッドマートとは
  2. サービスが解決したい課題
  3. サービスが提供する価値
  4. 価値仮説に至るまでのプロセス

1. クックパッドマートとは

クックパッドマートは、料理が楽しみになるような食材を、スマホアプリから簡単に注文することができる、生鮮食品のECサービスです。

地域の精肉店や鮮魚店、野菜農家、ベーカリーなどの「こだわり食材」をアプリでまとめて注文できます。 「焼きたてパン」や「朝採れ野菜」などの新鮮な食材を、販売店から集荷した当日に受け取ることができ、1品からでも送料は無料。毎回必要な分だけを手軽に購入することができます。

商品の受け取りは、地域の様々な店舗・施設等に設置された「受け取り場所」の中から好きな場所を選び、好きな時間に受け取ることが可能です。そのため、日中忙しくて買い物をする時間がない方でも、新鮮なこだわり食材を手軽に入手することができるサービスです。

ティザーサイトはこちら

2. サービスが解決したい課題

クックパッドマートは、私たちが普段の買い物に抱える下記のような課題を解決しようとしています。

おいしい食材を新鮮な状態で手に入れることの難しさ

世の中の大半の人は普段の食材はスーパーで購入することが多いと思います。何でも揃うスーパーはとても便利ですが、一方で、スーパーの品揃え次第で食生活が決まってしまうのも事実です。 街にはこだわりを持って厳選された食材を扱う専門店もありますが、多くの人がその存在を知らない、もしくは知っていてもそれらの店舗を回る手間や時間をかけられない、というのが実情だと思います。

まとめ買いせざるを得ない環境と仕組み

仕事をしていたり忙しい人ほど、普段から買い物にかけられる時間は少なく、週末に1週間分をまとめ買いする人も多いのではないでしょうか。 また、ECサービスを利用する場合も、配送コストをカバーするための「最低注文金額」が設定されていることがほとんどです。 そのため、たとえ新鮮な状態で購入したとしても、結果的に家庭の冷蔵庫で鮮度を落としてしまうのが現実です。

3. サービスが提供する価値

クックパッドマートは、これらの課題を解決するため、以下の3つの軸で価値を実現しようとしています。

提供する3つの価値
クックパッドマートが提供する3つの価値

品質が良いこと

おいしい食材は食卓を豊かにし、毎日の楽しみを増やすことができるというのは、あまり疑いようがないのではないでしょうか。 クックパッドマートは、こだわりを持っておいしい食材を届けたいと考えている販売店や生産者の方にご協力をいただき、自信を持っておすすめできる食材だけを商品として扱います。 また、おいしいものをおいしいうちに食べることも大切です。出荷当日に配送し、数日で使いきれる量だけを注文できるサービスとすることで、それを実現しようとしています。

調達コストが低いこと

クックパッドマートはアプリで手軽に注文でき、1品でも送料は無料。受け取りは自宅ではなく、近所の配送拠点を選択する形をとります。 使いきれる量の食材を、いつもの帰り道で好きな時間に受け取れることで、食材の調達にかかるコスト(時間や手間)を減らします。

調理コストが低いこと

献立を考えながら買い物をするのは、時間もかかり、毎日のこととなればストレスを感じる人も多いです。クックパッドマートは、実際の調理例を見ながら食材を注文でき、献立決めと買い物が一度に完結する体験を提供します。 また、新鮮な食材はシンプルな調理だけで十分においしいので、良い食材が手に入れば結果的に調理は楽になるとも考えています。

4. 価値仮説に至るまでのプロセス

上記の「サービスが提供する価値」の仮説に至るまでに繰り返してきた様々な検証のプロセスをご紹介します。

スプレッドシートによる買物代行テスト

チーム発足当初に描いていたサービスイメージは、買い物の手間やストレスを減らすことを目的とした、シンプルな買物代行サービスでした。 そこで、普段の買い物を誰かに代行してもらえることにどれだけの価値を感じられるのか、またそれを実現するにはどのような課題があるのかを検証するため、まずは実際にチームメンバーで社員の普段の買い物を代行するテストを実施することから、プロジェクトを始めました。

最初のテストで用意したシステムはごくシンプルです。Googleスプレッドシートで作った発注書と、全ての注文をまとめた買い出しリストの2つで、実装期間は約3日ほど。注文したい人は、発注書のテンプレートをコピーし、欲しい食材の注文数を記入後、チームのメーリングリストに共有する、というものでした。

スプレッドシートの発注書と買い出しリスト
実際に使用したスプレッドシートの発注書と買い出しリスト

目的の価値検証ができる最も簡単な方法を選んだことで、チーム発足後わずか2週間で最初のテストを実施することができました。

結果どうだったか

初日だけで20名以上の社員から注文が入り、レジ袋20袋以上の量の食材をチームメンバー3人で買い出しに行きました。

このようなテストを1回実施してみただけでも、本当に山ほどの課題とサービス改善のヒントが得られました。その一部を以下に紹介します。(実際はこの形式のテストを数回実施しました)

  • 普段買っているスーパーの商品と同等のものを届けても、あまり有り難みを感じてもらえない
  • 1:1の買い物代行はスケールが難しい上に、コストが見合わなそう
  • 複数店舗での買い出しと、それらの店舗を巡る配送を分ける仕組みは良さそう
  • まとめて買い出した後の個別仕分けは恐ろしく大変で効率化が必須
  • 注文時に量感がわからず、持ち帰れないほどの量を頼んでしまう人がいる
  • 鮮魚はその日のオススメをおまかせで選び、食べ方まで提案したところ、とても評判が良かった
  • おいしい!と実感できた人から、自然発生的にレビューが届いた(チームのSlackチャンネルに写真と感想が寄せられた)

買い出しテスト後の仕分けの様子
最初の買い出し後の仕分けの様子(カオスです。。。)

このテストを経て、

  • おいしさに自信を持っておすすめできる商品だけを届ける
  • 1:1の買い物代行ではなく、1:Nのルート配送の仕組みを構築する
  • おいしい食べ方の提案まで含めた、買っただけで終わらない買い物体験を作る

といったサービスのコンセプトが明確になりました。

プロトタイプアプリによるテスト

上記のスプレッドシートによるテストは単発で行ったため、使う社員も少しイベントごとのような意識になってしまうという課題がありました。そこで、

  • 定常的に注文を受け付けられる仕組みの構築
  • 習慣的に使ってもらうための課題の洗い出し

を次の検証のステップとしました。

習慣的に利用してもらうために、スプレッドシートを手動で管理する形式を卒業し、簡単なWebアプリケーションを構築しました。この時も、機能は最低限に絞り、デザインも凝り過ぎずに、最短でテストを開始できる方法を意識して開発を進めました。アプリケーションの実装は約1週間ほど、コンテンツの準備などを含めるとリリースまで2週間ほどの期間でテストを開始しました。

Webアプリケーションの画面
実際のWebアプリケーションの画面

結果どうだったか

このタイミングから、社員はいつでもクックパッドマートで食材を注文できるようになり、毎週火・木の2回、注文された食材をオフィスに配送するテストが今現在も続けられています。

日常的に配送が行われることで、利用する社員も普段の買い物の手段の一つとしてクックパッドマートを使ってくれるようになりました。初回注文からのリピート率は約7割で、ほぼ毎回注文をしてくれるヘビーユーザーも出てきています。日常使いするほど、アンケートで厳しめのご意見をもらえることも多く、サービス改善に必要なフィードバックを常に得られる状況を作ることができました。

また、定常的に配送オペレーションを回すことで、配送の度にオペレーション上の新たな課題が見つかり、日々改善が進められています。社内テストを繰り返すことによって磨き上げが進んだオペレーションの例をいくつかご紹介します。

  • チームメンバー自ら買い出しに出向いていたものが、徐々に社外に委託することができるようになり、一部では配送員が直接店舗から商品を受け取る仕組みもできてきた
  • 商品に貼り付けるラベルに記載する情報の改善により、仕分けミスや受け取り間違いが起こりにくいシステム構築が進んだ
  • 当初ユーザーの不安要素に多く上がった保冷の問題も、配送時に常に温度計測を行い、検証を繰り返すことにより、食材に適した温度を維持しての配送が実現できてきた

チームMTGの様子
オペレーション改善を行うチームの配送振り返りMTGの様子

配送オペレーションのように複雑性の高いものは、トライを繰り返せる環境が常にあることが改善スピードに直結します。常時社員から注文が入る状態を作れていることで、配送の仕組みもどんどんブラッシュアップされ、サービス全体としての品質向上を進めることができています。

社外ユーザーテスト

上記の社内テストを日常的に実施するのと並行して、社外のターゲットユーザー層の方にご協力いただくユーザーテストも繰り返し実施してきました。 目的は、社員としてのバイアスが無い、より一般的なユーザーの率直な反応をみることです。

ユーザーテストは、毎回下記のような形式で実施しています。

  • ターゲットに近いユーザーのリクルーティング
  • 社内テストで運用中のプロトタイプアプリを使ってもらう
  • 参加できるチームメンバー全員、別室でユーザーテストの様子を観察する(参加できなかったメンバーは録画でキャッチアップする)
  • チーム全員で分析・振り返りをする

インタビュー観察部屋の様子
インタビュー観察部屋の様子

インタビュー後の分析の様子
インタビュー後の分析の様子

結果どうだったか

社内テストで使っているシステムと同じものを見せた場合でも、前提知識やバックグラウンドが異なる社外ユーザーからは、やはり社員とは異なる反応がたくさん得られました。その例をいくつかご紹介すると、

  • オフィス配送のイメージが持てない環境にいるユーザーも多い

    • 比較的近距離に住む社員が多いクックパッドに比べ、通勤時間が長く、持ち帰りがより現実的でない
    • 勤務先がオフィスビルとは限らず、小規模な店舗勤務など、配送拠点になるイメージが持てない
    • 会社の雰囲気的に買い物をしている姿を他の人に見られたくないという感覚の人も多い
  • 商品の品質(おいしさ)がアプリ上では判断できない

    • クックパッド社員は背景知識やクチコミにより初回注文のハードルがそこまで高くなかったが、一般ユーザーがサービスの品質を信頼して初めて注文するハードルはその何倍も高そう

このような結果はメンバー全員で受け止め、オフィスを配送先のメインとして考えていた方針を転換したり、おいしさを伝えるための方法をアプリ内に留まらずに広くアイデア出しをして検討したりと、プロジェクト全体の方向性修正に役立てています。

モバイルアプリへの移行

このように様々な検証を経て、徐々に社外リリースの方向性や価値仮説が定まってきたところで、これまでの知見を盛り込んだリリース版iOSアプリの開発を開始しました。

これまでのプロトタイプアプリのコードやデザインは捨てて、システム設計からすべてリニューアルする形式で開発を進めています。テストと改善をスピード感を持って繰り返してきたプロトタイプアプリは、どうしても不要なコードや現在の要件に合わない設計が残っているので、リリース要件が固まったこのタイミングで刷新する判断をしました。

今現在は社内テストも全てリリース版のiOSアプリに移行しており、社外リリースに向けた磨き込みを進めています。

まとめ

このように、ミニマムなテストで価値検証を繰り返すプロセスを愚直に続け、ようやく社外にリリースできる形のサービスとしてまとまってきたのが今です。

サービスの登場人物が多く(注文ユーザー・販売店・配送員・受け取り拠点)、食材というリアルなモノが介在する複雑性の高いサービスなので、実際にテストを回しながら品質を上げていくプロセスの有用性を強く実感しています。 またこのようなプロセスは、インターネット上にとどまらない買い物体験全体をデザインしている感覚が強く、サービス開発者としてとても刺激的で面白いです。

社外リリース後も、まだまだ価値検証と改善のプロセスは続きます。もしあなたの街にクックパッドマートの受け取り場所ができたら、ぜひ利用して、フィードバックをください。(東京都渋谷区・目黒区・世田谷区の一部エリアから順次リリース予定)

また、この記事を通して、クックパッドマートのサービス開発にご興味を持っていただけた方がいらっしゃいましたら、ぜひ一緒にサービスを作りましょう! エンジニア・デザイナーはもちろん、様々な職種で、一緒にサービスを作り上げる仲間を募集しています(募集要項: エンジニアオープンポジション)。ご応募おまちしております。

自作キーボード沼 自由研究ノート

$
0
0

こんにちは!広報部のとくなり餃子大好き( id:tokunarigyozadaisuki)です。

クックパッドのSlackには無数のオープンチャンネルが存在していますが、最近盛り上がりを見せているのが「#keyboards」というチャンネル。先週末コミックマーケットが開催されていたためここ最近はその話でもちきりの様子でしたが、普段から大事な仕事道具であるキーボードにこだわりを持った社員が日々情報交換をしています。興味本位で社員のキーボードをのぞいでみると、ピカピカ光るものから、カチカチッと音がなるもの、アルファベットも数字も書いていないもの……その多彩さにびっくりします。 そこで、クックパッドエンジニアの最近のキーボード事情を調査してみました! 

なお、HHKBやRealforceはクックパッドでは当たり前だったので、紹介は割愛いたします。

クックパッドエンジニアのキーボード

@takai

f:id:tokunarigyozadaisuki:20180814150420j:plain

キーボード概要

名称:Keebio Fourier
スイッチ:Cherry MX 茶軸(45g)
レイアウト :40%スプリットキーボード 

なぜ今のキーボードにしたのか

自作するならスプリットキーボードで、かつコンパクトな感じに仕上げたいと思っていたところ、Keebio の Fourier を見つけて、「ミニマムでストイック、まさに自分のためのキーボードだ」と思いました。

気に入っているポイント

40%キーボードって、ほどよく不便で楽しいじゃないですか。数字キーが無いわけですから、使うにあたって工夫する必要があります。そこを自分好みにカスタマイズして使いこなしてると「俺すごい」という気持ちになれるんです。キーキャップのカラーリングもこだわりのポイントで、Signature Plastics社のGRANIT KEYSETにインスパイアされました。好みのレイアウトだと、キーキャップのセットが売っていなかったので、キーキャップ単位で購入したりと、そこは妥協せずに頑張りました。

@slightair

f:id:tokunarigyozadaisuki:20180814150437j:plain

キーボード概要

名称:Ergo42
スイッチ:Cherry MX 赤軸(45g)
レイアウト:7x4格子配列スプリットキーボード

なぜ今のキーボードにしたのか

少し前にスプリットキーボードに挑戦してみよう、でもキーが減るのはちょっと怖いなと思い、比較的キーが多いViterbi Keyboard(7x5格子配列)を作って使い始めたのですが、想像と違ってキーを余らせてしまっていました。 また同じキー配置で文字を打ちたいので、自宅と会社の間で持ち歩いていたのですが、面倒くさくなってきてもう一台作りたいなと考えていました。 そんなところに一行少ないだけでちょうどよく使えそうなErgo42の開発キットの販売がはじまったので飛びついてしまいました。国産なので注文してすぐ届きました。

気に入っているポイント

はんだ付けは大変ですが、キーキャップやキーマップなど、自分好みにいじることができるのが楽しいですね。特に、基板の底にLEDを配置して光らせるUnderglow(アンダーグロウ)が気に入っています。キーキャップの色は黒を基本に、特殊キーなどを青系にしていて、光の色もそれに合わせています。みんなから声かけられるようになったし、目を引く、やったぜ! という気持ちです。

@eisuke

f:id:tokunarigyozadaisuki:20180814150442j:plain

キーボード概要

名称:TMK Alps64
スイッチ:Alps SKCM SALMON
レイアウト:60%キーボード

なぜ今のキーボードにしたのか

ビンテージキーボードが好きで、以前はIBMマシンで使用されていたバックスプリング式キーボードを使っていました。60%くらいのコンパクトさで自分好みのキータッチのキーボードを作ろうと思い、CHERRY軸を触ってみたのですが自分にはしっくりこなかったので別のものを探していました。その中で、80〜90年代に生産された多くのキーボードに使用されていたAlps軸が気になりました。特にサーモン(ピンク)軸が気になったので、サーモン軸を使っている Apple Extended Keyboard をオークションで購入し、解体してキースイッチを取り出し自作してみました。

気に入っているポイント

タクティカルキーボードの少しだけカチッとくる、この独特で軽めな押し心地がいいですね。Alps軸の古代パーツ感も気に入っています。 

@ragi256

f:id:tokunarigyozadaisuki:20180814150453j:plain

キーボード概要

名称:Helix
スイッチ:Kailhロープロファイル 赤軸(45g)とKailhロープロファイル 茶軸(45g)の併用
レイアウト:6x5格子配列スプリットキーボード

なぜ今のキーボードにしたのか

昔から特殊な形状のキーボードが好きで、Dactylキーボードを作っているブログ記事を見たときに自分でもDactylを作ってみたいと思いました。しかし、はんだ付けもやったことがない自分には難しそうだったので、一旦難易度を下げて簡単なキーボードを作ることにしました。Helixに決めた理由は、以前から一度試してみたかったKailhのロープロファイルスイッチが使えるからです。

気に入っているポイント

親指を使うキーだけ押し心地を変えたかったので、親指周りだけを茶軸にして他は赤軸にしました。キーキャップは、デフォルトの刻印セットに加えて無地の白と黒を見た目で覚えやすいように配置しています。スプリットキーボードなので、椅子の肘掛けに肘先を置いて使えるのもいいですね。

@uzzu

f:id:tokunarigyozadaisuki:20180814171034p:plain

キーボード概要

名称:NIZ keyboard Plum 75 EC Keyboard
スイッチ:静電容量無接点方式(35g)
レイアウト:75%キーボード

なぜ今のキーボードにしたのか

社会人になってから、主にRealforce 86Uを8年くらい使っていました。特に困ってもいなかったのですが気分転換をしたくなり、Realforceの打鍵感が好きだったのでそこはあまり変わらず、それでいて、いじりやすくて不要なキーが無いコンパクトなものを探していて、NIZ keyboardにしました。他の皆さんのキーボードとは違い、組まれた状態で売られているので自作とは言えないですね……。

気に入っているポイント

75%である事(ファンクションキーは欲しいけど十字キーとハードウェアキーと操作キーはいらない)、さらに右側の特殊キーを十字キーに変えられるというのが、まさに自分の需要に合っていて気に入っています。加えて、静電容量無接点スイッチなのにCherry MX互換のキーキャップが採用されているので、他の自作キーボードと同様にキーキャップも変えられますし、バネを付けることでキーの重さが変えられるんです。Realforceの偏荷重モデルの重さを参考にしつつ、気になる所の重さを調整して自己最適化しています。今後、キーキャップは変更していきたいと思っています。

@ayemos

f:id:tokunarigyozadaisuki:20180814150747j:plain

キーボード概要

名称:WASD Keyboard
スイッチ:Cherry MX 青軸(50g)
レイアウト:60%キーボード

なぜ今のキーボードにしたのか

大学生の時HHKBを使っていたのですが、ErgoDoxで組まれた自作キーボードを見たとき、自分好みのキーキャップに変えられるものがほしいと思いました。

気に入っているポイント

深夜に酔った勢いで作ったので気に入ってるとかこだわりとかはないですね。漢字を使ったキーキャップデザインを自作しました。

最後に

いかがでしたでしょうか。以前はHHKBシリーズを使っていて、そこから自作キーボードの門を叩いたという社員が多いように感じました。自分にとって最適なキーボードを求めて細かいところから自作する人、見た目の可愛さを求めて工夫する人と、こだわりは様々。インタビューしていてとても楽しかったです。徐々に詳しくなってきましたよ! Gherkinってキーボードをつくってみたいと思っています。

今回紹介しきれなかった社員の自作キーボードについてはまた次回。お楽しみに! 

Cloud Firestoreのrulesをテストする

$
0
0

Komerco事業部エンジニアの岸本(id: sgrksmt)です。今日でちょうど入社1年が経ち、現在Komerco -コメルコ-(以下、Komerco)の開発を担当しています。
入社前はお世話になっていたこの技術ブログに自分が投稿する日がくるとは...。

Komercoは、「料理が楽しくなるマルシェアプリ」というコンセプトの元、料理が楽しくなる器やカトラリー、リネン雑貨等を出品/購入できるサービスで、現在はiOS版のアプリケーションを提供しています。

今年2月のCookpad Tech Conf2018や先日催したCookpad Tech Kitchen#16などでもお伝えしてきていますが、現在KomercoではバックエンドでFirebaseを活用しています。
その中で、最近僕が仕組みづくりとして取り組んでいるCloud Firestoreのセキュリティルールのテストの方法についてご紹介します。

Cloud Firestoreのrules

Cloud Firestore(以下、Firestore)では、各プラットフォームで提供しているSDKやREST API経由でデータを安全に読み書きできるよう、セキュリティルールを記述することができます。
主にFirebase Authenticationでの認証を活用しつつ、どのような条件下でドキュメントを読み取ることができるか、書き込むことができるかを設定し、ユーザーのデータを保護します。
また余談にはなりますが、Realtime DBとCloud Storageにもセキュリティルールがあります。

セキュリティルールをしっかり設定しないと、本来読み取られてはいけないデータが悪意のある第三者に読み取られてしまったり、特定のフィールドを書き換えられてしまうといったことが起こります。
一方で、それを防ぐために全てのルールを閉じて、API経由でのみ読み書きできるようにして堅牢にすることも可能ですが、そうするとCloud Firestoreの次のような利点を享受しにくくなります。

  • リアルタイムでの情報の取得
  • オフラインから復帰したときに、ローカルでの変更を書き込む

なので、実際にアプリケーション開発をしていく場合は全ルールを閉じる運用よりは、ルールを適切に設定して運用していくことが多くなるかと思います。

Firestore rulesの確認が大変

Firestoreのrulesを書いて、期待通りに動作するのかを確かめつつ開発をしていきますが、これが非常に 大変かつ面倒だったりします。 変更をした後、毎回デプロイして、浸透するまで数分待って、正しく動作するかクライアント側で動作させて確かめるのはかなり非効率ですし、 エラーの内容も「permission-denied」程度のものしかないので何が原因か掴みづらいです。
一応、rulesの構文自体が間違っているかどうかはデプロイ前に検出してくれるので、構文がおかしくなっている場合は気づけますが、typoなんてしてしまった日にはなかなか気づきづらく、デプロイと確認だけで日が暮れてしまうこともありえます。
"もっと効率的にrulesを書いて確認したい..."

そう思いながら今年の前半を過ごしていたのですが、2018年の5月末頃にFirestore rulesの動作を書き込む前に確認できる「シミュレータ」の機能がFirebaseのコンソール上に搭載されました。

rulesのシミュレーター

Firestoreのrulesシミュレーターは、FirestoreのProjectからDatabase→Firestore→ルールとたどり、この部分をクリックすることで利用することができます。

f:id:sgrksmt:20180815180611p:plain

開くと、このような画面になっており、指定したドキュメントのパスに対して、readやwriteのルールを記述されたルールを基にシミュレートして確認することができます。

f:id:sgrksmt:20180815180716p:plain

また、認証情報も指定したテストができるので、認証ありきの条件もシミュレートすることが可能です。
以前私がQiitaに掲載した記事でも操作方法など書いていますのでよければ併せて御覧ください。

シミュレータを用いると以下のメリットがあります。

  • 公開前に条件をシミュレートして確認ができるので、誤った条件のものをデプロイしてしまう心配がない
  • すぐにシミュレートして試せるので、デプロイしてから数分待って、、といった具合に時間をロスすることがない
  • 失敗した場合に、何が原因でどこで失敗しているのか指摘してくれるので、原因がわかりやすい
  • 書き込みに関するシミュレートの場合、実際のDBに書き込みが行われるわけではないので、DBを汚してしまうことがない

ただ、シミュレータでのrulesの動作の確認はコンソール上で簡単に確認ができる反面、以下のデメリットがあります

  • listのオペレーションに関して、queryに関する条件の確認ができない
  • getAfter関数を用いた条件の確認ができない
  • 2つ以上のドキュメントの書き込みに関するテストができない
  • 数百行になってくるとコンソールがやや重たくなるので編集時にストレスがかかる

今後開発を進めていく上で、継続的にrulesが正しく動作するかを確かめられる環境がないと、新機能の追加や大幅な改修、リファクタリングに耐えられないので、
Firestoreのrulesが正しく動くかどうかのテストを構築していくことにしました。


Firestore rulesのテスト

ここからが本題になります。大まかな流れとしては

  • 開発環境、本番環境とは別の、 テスト環境用のFirebase Projectを準備する
  • テスト環境にfirestore.rulesファイルをにデプロイする。
  • テストを書き、実際にテスト環境に対してドキュメントの読み書きを行い、Firestore rulesが期待通りの動作をするかをテストする
  • 手元でテストを実行できる他、CI経由でも継続的にテストが行えるようにする

となります。順に、簡単なテストの例も交えつつ説明していきます。

構成

テストの構成としては、次のようになっています。

f:id:sgrksmt:20180815180802p:plain

普段のアプリケーション開発では開発環境用に必要なものをデプロイしたり、DBの読み書きを行っています。
テストのときは、 テスト用のFirebase Projectを準備し、そこにrulesをデプロイしています。
そして、テストを実行するときは、向き先をテスト環境のFirebase Projectにし、その環境のFirestoreのDBに対して読み書きを実行し、rulesのテストをします。
また、テストは手元からCLI経由で実行する他に、GitHub上にPullRequestが作成された時や、materブランチにマージされたタイミングで、CI経由でテストを実行するようにしています。

また、この記事では詳しく触れませんが、Komercoでは同様に、CloudFunctionsに関連するテストも同様に、テスト環境のFirebase Project上で行っています。

準備

テストを書くにあたり、jestを利用しているので、npmもしくはyarnにて追加します。
jestは、Facebook社がOSSとして提供している、JavaScriptでユニットテストを行うためのフレームワークです。RSpecのような記述が可能となります。
今回はjestを使った場合でのテストの紹介となりますが、テストのフレームワークは任意のものでも構いません。

また、テストに関連するファイルは、 test/ディレクトリ以下に配置していきます。 KomercoではCloud Functionsのテストもあるので、 test/rules/ディレクトリ以下に配置しています。

Firebaseの初期化をする

テストで使うFirebaseの初期化をします。
Cloud Functions等でFirebaseを扱うときは、adminSDKが使えるようadmin権限での初期化をすることが多いのですが、
admin権限でFirebaseを初期化してしまうと、設定したrulesに関係なく管理者権限にて読み書きが可能となってしまうので、Webアプリケーションと同様の初期化を行います。

webhelper.tsを任意のテストディレクトリ以下に配置し、以下のように記述します。

import * as firebase from'firebase'const config ={
  apiKey: 'API_KEY',
  authDomain: 'AUTH_DOMAIN',
  databaseURL: 'DATABASE_URL',
  projectId: 'PROJECT_ID',
  storageBucket: 'STORAGE_BUCKET',
  messagingSenderId: 'SERNDER_ID'}

firebase.initializeApp(config)const auth = firebase.auth()const firestore = firebase.firestore()const settings ={ timestampsInSnapshots: true}
firestore.settings(settings)export{ firebase, auth, firestore }

各種configの変数はご自身のテスト環境用のプロジェクトのIDを指定してください。(基本的には.env等から参照することになると思います。)
これら初期化した変数は次のようにテストファイルでimportして使用します。

import * as WebHelper from'./helper/webhelper'const postRef = WebHelper.firestore.collection('post').doc()

モデルを定義する

ドキュメントのモデル定義をします。
今回は例として、Postドキュメントの定義をします。

exportenum Path {
  Post ='/posts'}exportinterface Post {
  title: string,
  body: string,
  authorID: string,
  isPublished: boolean}

次項では、このPostドキュメントのcreateオペレーションに関するテストの例をご紹介します。
(すべてのオペレーションの例を紹介したいのですが、長くなるので割愛します。)

テストを書いていく

前提条件として

  • Firebase Authenticationにて認証されたユーザー
  • 定義したパラメータを全て有している
  • post.authorIDが、認証ユーザーのuidと一致する

という条件のもと、書き込みが正常に行われるのを期待するケースと、失敗し、permission-deniedがエラーとして返却されるのを期待するケースを記述します。
(失敗するケースは複数想定されますが、ここでは1つのケースに絞ります。)
posts.test.tsを作成し、次のように記述します。

import * as WebHelper from'./helper/webhelper'enum Path {
  Post ='/posts'}interface Post {
  title: string,
  body: string,
  authorID: string,
  isPublished: boolean}const makePostDocument =(authorID: string)=>{return<Post>{
    title: 'test post',
    body: 'test post body',
    authorID: authorID,
    isPublished: true}}const permissionDeniedError ={ code: 'permission-denied'}

describe('post document rules',()=>{
  jest.setTimeout(10000)let postCollectionRef: WebHelper.firebase.firestore.CollectionReference
  beforeAll(()=>{
    postCollectionRef = WebHelper.firestore.collection(Path.Post)})

  describe('write',()=>{
    describe('create',async()=>{let authUser: any

      beforeEach(async()=>{
        authUser =await WebHelper.auth.signInAnonymously()})

      afterEach(async()=>{await WebHelper.auth.signOut()})

      describe('when authorID is equal to auth.uid',()=>{
        test('should be succeeded',async()=>{const post = makePostDocument(authUser.user.uid)await expect(postCollectionRef.doc().set(post)).resolves.toBeUndefined()})})

      describe('when authorID is not equal to auth.uid',()=>{
        test('should be failed',async()=>{
          expect.assertions(1)const post = makePostDocument('xxxxxxxxxxx')await expect(postCollectionRef.doc().set(post)).rejects.toMatchObject(permissionDeniedError)})})})})})

(モックデータの作成に関する処理、モデル定義等は別途別のファイルに分割するほうが良いですが、今回は例を示すために同一のファイル内に置いています。) ここまでで実行すると、rulesがまだ書けていない場合はテストが通らないと思います。
次にこのテストを通過するための正しいrulesを記述します。

service cloud.firestore {
  match /databases/{database}/documents {function isAuthenticated(){return request.auth !=null;}function incomingData(){return request.resource.data;}

    match /posts/{postID}{
      allow create: if isAuthenticated()&& incomingData().keys().hasAll(requiredFields())&& incomingData().authorID == request.auth.uid;function requiredFields(){return['title','body','authorID','isPublished'];}}}}

これをテスト環境にデプロイし、再度テストを実行することで、テストが通るようになります。
(注: 紹介のため割愛しているテストケースがありますので、実際にはもう少しテストケースが多く、厚いものになると思います。また、rules自体も、各種フィールドのvalidationを行ったりと複雑になるかと思います)

事前にテストデータを作成する

テストを書いていると、セキュリティルールの制約を受けずにテストデータを作成したり、
CloudFunctionsで生成したり、事前にDBに格納しているのが前提で、読み出すだけのデータを準備したいケースがでてきます。
その場合はadmin権限でFirebaseを初期化したものを別途用意し、rulesの息のかからないところでテストデータを作成します。
adminhelper.tsというファイルを作成し、そこでAdmin SDKの初期化を行います。

import * as admin from'firebase-admin'

admin.initializeApp({ credential: admin.credential.cert(require('admin sdk jsonのpath'))})const auth = admin.auth()const firestore = admin.firestore()const settings ={ timestampsInSnapshots: true}
firestore.settings(settings)export{ admin, auth, firestore }

(注: admin用のservice accountの取扱には注意です。 外部に漏れぬように対策する必要があります)
参考: サーバーに Firebase Admin SDK を追加する

これにより、事前にPostドキュメントを作成し、そのPostドキュメントが取得可能かどうかのテストが次のように記述できます。

import * as WebHelper from'./helper/webhelper'import * as AdminHelper from'./helper/adminhelper'enum Path {
  Post ='/posts'}interface Post {
  title: string,
  body: string,
  authorID: string,
  isPublished: boolean}const permissionDeniedError ={ code: 'permission-denied'}const savePostDocument =async(isPublished: boolean)=>{const postRef = AdminHelper.firestore.collection(Path.Post).doc()await postRef.set({
    title: 'test post',
    body: 'test post body',
    authorID: 'xxxxxxxx',
    isPublished: isPublished
  })return postRef
}

describe('post document rules',()=>{
  jest.setTimeout(10000)let postCollectionRef: WebHelper.firebase.firestore.CollectionReference
  beforeAll(()=>{
    postCollectionRef = WebHelper.firestore.collection(Path.Post)})

  describe('read',()=>{
    describe('get',()=>{
      afterEach(async()=>{await WebHelper.auth.signOut()})

      describe('when user is authenticated',()=>{
        describe('try to get valid document',()=>{
          test('should be succeeded',async()=>{await WebHelper.auth.signInAnonymously()const mockPostRef =await savePostDocument(true)await expect(postCollectionRef.doc(mockPostRef.id).get()).resolves.toBeDefined()})})

        describe('try to get invalid document',()=>{
          test('should be failed',async()=>{
            expect.assertions(1)await WebHelper.auth.signInAnonymously()const mockPostRef =await savePostDocument(false)await expect(postCollectionRef.doc(mockPostRef.id).get()).rejects.toMatchObject(permissionDeniedError)})})})

      describe('when user is not authenticated',()=>{
        test('should be failed',async()=>{
          expect.assertions(1)const mockPostRef =await savePostDocument(true)await expect(postCollectionRef.doc(mockPostRef.id).get()).rejects.toMatchObject(permissionDeniedError)})})})})// ...writeのrule})

今回追加した、postドキュメントのgetに関するルールを次のように追加することで、上記テストが通るようになります。

service cloud.firestore {
  match /databases/{database}/documents {function isAuthenticated(){return request.auth !=null;}function incomingData(){return request.resource.data;}function existingData(){return resource.data;}

    match /posts/{postID}{
      allow get: if isAuthenticated()&& existingData().isPublished;
      allow create: if isAuthenticated()&& incomingData().keys().hasAll(requiredFields())&& incomingData().authorID == request.auth.uid;function requiredFields(){return['title','body','authorID','isPublished'];}}}}

料金面はどうか

実際にテストデータを作って、読み書きを行うので、コスト(料金)がかかるのでは、、と思われるかもしれませんが、
Komercoではrulesのテストに加え、Cloud Functionsのテストも実施しています。Pull Requestが作成される、あるいはmasterブランチが作成されたときにテストを実施していますが、 料金はほとんどそこまでかかっていません。

また、先にも触れましたが、テストを実行する環境と、普段開発している環境を分けているので、開発環境のDBが汚染されたりすることもありません。

Firestoreのルールのテストを書くことでのメリット

テストを書くことによるメリットとしては

  • アプリケーションを手動で動かしたり、シミュレータに頼ることなく(継続的に)rulesが正しく動作するか確かめることができる
  • rulesの変更が容易になる(書き換えた結果、正しくない場合にテストがあることで検知することができる。)

が挙げられます。普段のアプリケーション開発と同様で、テストがあることで、後の変更にも強くなり、rulesを変更することに臆することもなく、また高速に動作検証が行えるようになります。
また、事前にセキュリティを意識したドキュメントの設計がしやすくなり、見通しも良くなるので最近の開発では新機能の追加や改善に伴ってドキュメントの設計をするときは、同時にrulesのテストも記述し開発をしています。

さいごに

Firestoreのrulesに関して、より継続的に確認が行えるためのテストについてご紹介しました。
今回ご紹介したサンプルコードをGitHubにて公開していますので、よければ併せて御覧ください。

今後公式からテストする手段が提供される可能性もありますし、オフライン(ローカル)でのテストが提供される可能性もありますが、
現状Komercoではオンラインテストにて実施して、Firestore rulesの継続的なテストを行えるようにしています。
また、こうした事例に関わらず、Komerco開発チームではFirebaseと向き合って、その上でサービス提供ができるよう努めていきます。


クックパッドでは、Firebaseなど最新技術に興味がある!技術的に挑戦してサービスをより良くしたい!というエンジニアを募集しています。

builderscon tokyo 2018 にクックパッド社員が1名登壇いたします! 

$
0
0

こんにちは! 広報部のとくなり餃子大好き( id:tokunarigyozadaisuki)です。

朝晩を中心に少しずつ秋の気配を感じられるようになりましたが、夏日かと思うと台風が2つも。雨の被害がこれ以上、拡がりませんように。みなさんも寒暖の差で体調など崩されないようお気をつけください。

さて、2018年9月6日(木)〜2018年9月8日(土)は「知らなかった、を聞く」をテーマとした技術を愛する全てのギーク達のお祭り、「builderscon tokyo 2018」が開催されますね! 

クックパッドからは、技術部の小野 大器(@taiki45)が登壇させていただきます。セッションの内容は公式ページにて英語で紹介しておりますが、本ブログでは本人のコメント付きで、日本語にてご紹介いたします。 講演は日本語で行いますので、ご興味のある方はぜひご参加ください。

セッション情報

2日目 9月7日(金)12:30〜 イベントホール

Building and operating a service mesh at mid-size company

概要説明 クックパッドでのサービスメッシュの導入事例をお話します。大規模な環境ではなくても、サービスメッシュはマイクロサービス周辺の技術的課題の解決に対して効果があります。Envoy proxy を利用してスモールスタートで構築・導入する事例を基に、サービスメッシュに関係する知見や手法、導入後の効果について紹介する予定です。

コメント

マイクロサービス環境において、通信の失敗のハンドリングや障害発生時の対応等、サービス間の通信で悩んだ人は多いのではないでしょうか。このトークでは、以前とは違うアプローチでサービス間の通信周りの課題に対応するサービスメッシュについてお話します。 本セッションの前にはなんと Envoy proxy の作者である Matt のトークもあるので、そちらと一緒に本セッションも楽しんでもらえるとうれしいです。マイクロサービス間の通信に悩んでいる方やこれから悩みそうな方にぜひ。


登壇時の発表内容等に関してご質問がある方は小野に、他にも数名参加いたしますので、会場でクックパッド社員をお見かけの際には、ぜひお声がけくださいね! 


レシピの画像検索に必要な技術

$
0
0

研究開発部の @ayemosです。ダイエット中です。

画像検索とは

検索という言葉からは、いくつかの単語を入力してそれを含む文章を検索するという体験を自然と連想できるかと思います。このような検索の体験の第一歩は、ユーザーが欲しい情報に対して単語の列としてクエリを構築するということから始まります。例えば、「支払うべき住民税の額」という情報を得たい時には「目黒区 住民税」というクエリを自ら考えて入力するといったようにです。

単語列を利用した文章検索を行う際、検索クエリが来るたびに検索対象となる文章を上から下まで全探索するのでは時間がかかってしまいます。そのためしばしば全探索の代わりに転置インデックスを用いて高速化された探索手法を用います。転置インデックスは端的に単語からそれを含む文章(とその位置)をマップする情報であり、これを用いることで、単語の列をクエリとして、それらのすべてあるいは一部を含む文章を高速に引いてくることができます。

一方で例えば、名前を知らない植物や料理を見た時、「赤い 小さい 花」「タイ 屋台 辛い麺」のような単語によるクエリを構築し、欲しい情報にたどり着くのは簡単ではありません。このような場合は例えば、スマートフォンで撮った写真をクエリとして利用した画像検索ができると良いでしょう。しかしながら、単語 =>文書 の転置インデックスを基礎とした検索手法はテキストを主としたデータセットに対しては有効ですが、画像や音声の検索にそのまま転用することはできません。

このような単語列によるクエリの構築にまつわる問題を解決する1つの方法が、画像の特徴量を用いた類似検索です。画像をクエリとした研究は1990年ごろから活発に行われており、[1]にはその経緯と最近の動向がよくまとまっています。そこで今回は、クックパッドのデータを用いて、レシピの写真を利用した類似画像検索を実現した手法について紹介します。

類似画像検索

特徴量を用いた類似検索とは、クエリ画像(以後クエリ)と検索対象画像(以後コンテンツ)からそれぞれ特徴量を抽出し、あらかじめ定義された特徴量同士の類似度の指標に基づいて、クエリから抽出された特徴量と”近い”特徴量を持つコンテンツを特定するという手法です。

検索の準備としてまず、検索対象となるデータ(コンテンツ)に対してその特徴量を抽出し、必要であればインデックスを構築します。ここでいうインデックスについては後に説明しますが、類似な特徴量を持つコンテンツを効率的に見つけるための仕組みであり、文章検索のための転置インデックスとは異なります。

検索の実行時には、入力されたクエリから同様に特徴量を抽出し、それに近いコンテンツを見つけます。この作業を最近傍探索と呼びます。画像を入力とした類似画像検索においては、クエリとコンテンツがともに画像であるため、クエリとコンテンツに対して同じ特徴量抽出器を用いることが多いでしょう。

https://cacoo.com/diagrams/jO707b3qI3EwfwrH-0E135.png

またクエリとコンテンツに対して異なる特徴量抽出器を用いることで、音声から画像を検索したり、画像からテキストデータを検索したりすることも可能です[2]。ただしこの2つの特徴量抽出器は互いに同じ特徴量空間において、似た(似ていない)コンテンツに対して近い(遠い)特徴量を抽出する必要があります。

https://cacoo.com/diagrams/jO707b3qI3EwfwrH-9C154.png

前置きが長くなりましたが、ここからは料理写真の類似画像検索を実現した具体的な方法について説明していきます。

特徴量抽出器の作成

まずは料理写真の特徴量を抽出する方法を説明します。画像の特徴量としては、RGBの統計値やSIFT特徴量などの静的な特徴量や、多層のニューラルネットワークとして構成された画像認識モデルの中間層の出力値を用いることがありますが、今回は画像認識モデルを利用しました。

画像認識モデルを用いた特徴量抽出を行う最も手軽な手法の1つが、ImageNetのデータを用いて構築されたILSVRCデータセットの分類タスクに対して学習されたモデルを利用することです。ILSVRCデータセットは端的にいえば1000種類にあらかじめ分類された大量の画像が含まれるデータセットです。これを分類するために学習されたモデルはそのラベルに該当する写真をよく分類することができるだけでなく、そのような分類を行う過程で、一般的な物体を分類するのに必要な特徴量を画像から抽出することが期待できます。

https://cacoo.com/diagrams/jO707b3qI3EwfwrH-548AE.png

実際に画像認識モデルを応用する多くのケースにおいて、ILSVRCデータセットに対して学習した画像認識モデルを利用し、その分類レイヤーのみを再学習することで必要なタスクに最適化するという手法が取られます。しかしながら、タスクの領域を料理画像に限定する場合など、タスクのドメインによる変化が大きい場合、この手法が有効でない場合があります。その意味で、ILSVRCデータセットは一般的な物体の写真の認識を目的としていますから、料理に関する詳細な特徴を画像から抽出するのに必ずしも適しているとはいえません。

では実際にILSVRCデータセットに対して学習されたInceptionV3モデルが抽出する特徴量を見てみましょう。特徴量としては、最終層付近のPooling層の出力(2048次元)を用います。クックパッドに掲載されていない私の作った料理の写真をクエリとして、クックパッドのレシピ写真の中から近い特徴量を持つ写真を集めてみます。

クエリ画像候補レシピ123
f:id:ayemos:20180828140834j:plain

(ゴーヤチャンプルー)

Cpicon鮭ソテーの味噌マヨ丼 by SHIORINGO☆Cpiconケチャップとマヨネーズで簡単豚肉炒め by KKYYOOCpiconスタミナ満点★夏野菜ドライカレー by chxa_juni
f:id:ayemos:20180828143802j:plain

(グリーンカレー)

Cpicon゚・。+☆ナスのあんかけ゚・。+☆ by ゆーちょこCpicon納豆とオクラと長芋となめ茸のサラダ! by ☆sohta☆Cpiconマヨ少なめポテトサラダ by ◉さっちゃん◉
f:id:ayemos:20180828133246j:plain

(チーズとハムのホットサンド)

Cpiconカボチャの煮物でパンプキンケーキ by ayutomoyaCpiconケチャチーズトースト♡ by chisafuCpicon豆乳黒糖フレンチトースト☆ by okanao

おおむね質感や色味、構図の似た写真が抽出されているように見えますが、料理の種類や品目を見分けるのに有効な特徴量を選択的に捉えられているようには見えません。

そこで今回は、クックパッドに投稿されたレシピのデータを利用し、料理画像により最適化された画像認識モデルを構築しました。まずは、そのようなモデルを学習するためのデータをどのように収集したのかを説明します。

ラベル付きデータの質と量について

クックパッドには現在280万品以上のレシピが公開されており、その大部分について

  • トップ写真
  • タイトル
  • 手順
    • 本文
    • 写真
  • 材料
    • 名前
    • 分量
  • レシピのアクセス/クリックログ
    • 到達した際の検索クエリ

といったメタデータが付与されています。ここから教師あり学習に利用するラベル付きデータを生成することができると考えられます。今回は、似た料理あるいは同じ料理の写真を抽出する画像検索(を行うための画像認識モデルの学習)を目的とするため、 料理のカテゴリあるいは 料理名をよく表すデータを得ることを目指します。

ところで、ラベル付きデータの有用さを測る重要な指標としてはまずその質が挙げられます。ラベル付けに間違いがなく、そのラベルの選び方もまた、対象とするドメインに対してMECEな集合を構築していることが望ましいと考えられます。

一方、ラベルを用意するのにかかるコストも見過ごせません。「和食/洋食/中華」のようなラベルはレシピあるいは料理画像にとって重要な情報である一方で、これを得るためには手作業でのラベル付が必要になります。それに対して「手順の本文の長さの合計」など、統計的な処理のみによって得られるメタデータは、それを 自動で付与できるという利点を持ちます。一方でこのデータは調理にかかる時間や手間との相関が期待できるという意味で示唆のあるデータでもあります。

このように多くの場合において、ラベル付きデータの質と、量あるいは生成コストがトレードオフとなります。従って、機械学習に利用するラベル付きデータの有用さは少なくともその質と量という視点から多面的に評価する必要があるでしょう。特に人手でラベルづけを行う場合、その速度や効率の限界により、生成できるデータのスケールに対して強い制限があるという点には注意が必要です。

ラベル付きレシピデータの大量生成

クックパッドに投稿されたレシピには、先述したとおりタイトル情報が付与されています。しかしながらこれはレシピを投稿したユーザーが入力したものであり、料理名以外の情報が含まれていたり、その表記が様々であったりと、そのままラベルに利用できる状態ではありません。

手作業で料理名ラベルを付けていくことも可能ですが、時間がかかってしまうことに加え、本来的に階層的であると思われる料理名の全体集合とその要素を定めることは難しいという事が予測できます。ただし、料理のオントロジー[3]を構築する取り組みもあるということは補足しておきます。そこで今回は、「質はそこそこ、量はたくさん」なラベル付きデータを生成することを目指し、タイトルからラベルを抽出する仕組みをルールベースで構築しました。

具体的な抽出方法の説明は省きますが、この手法を取ることでクックパッドに公開されている280万以上のレシピデータの中から、120万のレシピに対して約1800種類の料理名ラベルを付与したラベル付きデータを生成することができました。

https://cacoo.com/diagrams/jO707b3qI3EwfwrH-65516.png

モデルの学習と結果の確認

ImageNetで事前学習されたInceptionV3モデルを利用し、上記の方法で得られたラベル付き料理画像データセットの分類タスクに対して学習を行います。学習したモデルを利用してクックパッド外の料理写真の特徴量を抽出し、クックパッドのレシピの中から近い特徴量を持つものを集めてみます。

クエリ画像候補レシピ123
f:id:ayemos:20180828140834j:plain

(ゴーヤチャンプルー)

Cpicon特製☆ゴーヤチャンプル by もんきちPCpicon苦くない♪ゴーヤチャンプルー♡ by mii♡cafeCpiconゴーヤチャンプルー by 料理大好き男子
f:id:ayemos:20180828143802j:plain

(グリーンカレー)

Cpicon簡単☆本格グリーンカレー by ricorico♡Cpicon簡単☆ココナッツカレー by junutsCpiconタイ・グリーンカレー  by ちむのダンナごはん
f:id:ayemos:20180828133246j:plain

(チーズとハムのホットサンド)

Cpicon簡単‼︎ロールパンでホットサンド‼︎ by まりえcafeCpicon《ストウブ》オーブン不要ミルクちぎりパン by kurara1230Cpiconリメイク♡牛丼でホットサンドっ by 白ぽこ

ImageNetで事前学習されたモデルをそのまま使った場合に比べて、料理としてより近いものが抽出できているように見えます。この辺りは明確な指標で比較するのが難しいですが、少なくとも類似の料理画像を抽出するという目的においてより優れた特徴量抽出ができていると言えそうです。

弱教師あり学習という選択肢

ラベルの正確性をあえて犠牲にし、その引き換えに大量のデータを得るという手法が有効である場合があります。このような手法をweakly supervised learningと呼ぶことがありますが、同様な手法として最近ではInstagramのハッシュタグ情報を利用したFacebook Researchによる実験[4]が有名です。

この手法は『手動でのラベルづけコストが高く、且つラベルの付いていないデータが大量にある』というケースにおいて特に有効と考えられますが、自社でデータをもつ企業にとっては珍しい状況ではないはずです。その意味でweakly supervised learningは、現場での実用性に優れた手法の1つでしょう。

高次元ベクトルの高速な近傍探索

ところで、類似コンテンツの検索を行うためにはクエリから得られた特徴量に対して、それと距離の近いコンテンツを見つけ出す必要があります。検索対象となるコンテンツ(レシピなど)がN件あり、それぞれD次元の特徴量(ベクトル)をもつ場合、クエリに対して距離の近いコンテンツを探すためにはN件全てとの比較が必要となり、それぞれの比較に大雑把に次元数分の計算が必要になります。

これはいうなれば転置インデックスのない文章を上から下まで順番に検索しているようなものですが、検索のユースケースによってはこの計算のコストが受け入れられない場合があります。より正しくは、大きいNに対してインデックスを用いない検索を行う場合、クエリ画像からの特徴量抽出(t0)に対して探索にかかる時間(t1)が、クエリ1件の検索にかかる時間(t0+t1)において支配的になります。

https://cacoo.com/diagrams/jO707b3qI3EwfwrH-31462.png

このような場合には、t1を削減するために近似最近傍探索を用いるという手があります。ベクトルの近似最近傍探索とは、必ずしも正しい最近傍のベクトルを得られる保証がないという点を受け入れる代わりに探索を高速化する手法であり、Facebook Researchの公開するfaissやYahoo! JapanによるNGTなど、いくつかの実装が公開されています。[5]ではその問題設定に始まり各種手法の比較が行われいます。

ちなみにt0とt1をあえて区別した上で議論しましたが、t1を十分に削減できた時に今度は特徴量抽出にかかる時間(t0)がボトルネックとなりうるという点についても述べておきます。これは巨大な画像認識モデルを利用する場合は特に注意が必要です。

まとめ

一般画像認識と異なるドメインにおいて画像検索を行うために、そのドメインのデータを用いて画像認識モデルを学習することで画像検索の性能を向上する手法を紹介しました。また完全なラベル付きデータがない状況で学習を行うために取る方法として、データの質を犠牲にしつつもそれを大量生成するという選択肢を挙げ、実際に生成したデータを用いて学習した結果を示しました。

また今回は得られた特徴量を検索に利用した際の有用性のみを紹介しましたが、分類やレコメンデーションなど、その他のタスクに流用できる可能性もあります。クックパッドにはレシピという中心的なデータがありますが、その様なデータに対してこのような汎用性の期待できる特徴量を用意することで、画像検索を含む様々な応用への可能性を広げるとも考えられます。

レシピは1つの料理を作るのに必要なすべての情報を含み、その名前から写真、材料と手順に至るまで1箇所に集まっているという、饒舌で密度の高いデータです。そんなレシピのデータを使った研究開発に興味のある方は是非採用ページからご応募ください。

参考文献

[1]: https://arxiv.org/abs/1706.06064

[2]: http://im2recipe.csail.mit.edu

[3]: http://www.ls.info.hiroshima-cu.ac.jp/cgi-bin/cooking/wiki.cgi

[4]: https://research.fb.com/publications/exploring-the-limits-of-weakly-supervised-pretraining/

[5]: https://arxiv.org/abs/1610.02455

Cookpad Summer Internship 2018 5 DAY R&D を開催しました

$
0
0

Cookpad Summer Internship 2018 5 DAY R&D を開催しました

研究開発部の菊田(@yohei_kikuta)です。

20180820 - 20180824 の期間で Cookpad Summer Internship 5 DAY R&Dを開催しました。 17 名の学生の方々に参加していただき様々な講義やワークに取り組んでもらいましたが、本記事ではその内容をご紹介したいと思います。

下の写真はインターンの一コマです。 よく分からない手の動きをしてますがともかくインターンの一コマです。

20180830165359

下の写真は懇親会で振る舞われた料理です。

20180830190456

インターンは5日間という短期集中の日程でしたが、以下に挙げるようにように様々なトピックに取り組んでもらいました。

  • 1 日目
    オリエンテーションと機械学習理論の講義(自動微分と deep learning の汎化性能)
  • 2 日目
    自然言語処理の講義(自然言語処理の基礎と演習)と画像分析の講義(画像分析の基礎と演習)
  • 3 日目
    画像分析の講義(軽量モデルの理解と演習)と MLOps の講義(MLOps の概説と演習)
  • 4 日目
    個人ワーク(クックパッドのデータを使ったモデリングやウェブアプリの実装など、自分の興味あるタスクを実施)
  • 5 日目
    発表資料作成と成果発表と懇親会

濃密な講義やワークに集中することに加えて、みんなで一緒に夕食を作ったりもして、実にクックパッドらしい内容でした。

講義資料の紹介

インターンで使用した講義資料を紹介します。 演習で使用したコードはクックパッドのデータと密接に関連しているものもあるため、コードの公開は MLOps で使用したもののみです。

機械学習理論と画像分析に関する講義は菊田(@yohei_kikuta)が担当しました。 スライドを作って話すというのに気分が乗らなかったので、LaTeX で作成し講義もホワイトボードをふんだんに使いました。

講義資料が置いてある GitHub repository へのリンク

自然言語処理に関する講義は原島(http://jun-harashima.net/)が担当しました。 伝統的な自然言語処理の話から始まり、クックパッドのデータを使った様々な分析にも取り組んでもらいました。

MLOps に関する講義は林田(@chie8842)が担当しました。 機械学習をサービスに導入するためにモデル以外に必要になる要素を包括的に概説した内容になっています。

また、MLOps で取り組んだ演習のコードは使用データを公開データに差し替えた上で GitHub で公開しています。
GitHub repository へのリンク

参加者が取り組んだワークの紹介

最終日は 4 日目に取り組んだワークの内容に関する発表会で、それぞれの参加者が思い思いの発表をしてくれました。 講義で理解した自動微分を自分で実装したり、ReLU6 の 6 の数字の意図を検証したり、画像分析のモデルを flask でウェブアプリ化したり、クックパッドのデータを用いてユーザの属性推定や分類モデルを作ったり、内容は多岐にわたりました。

個人ワークに使える時間が 8 時間だけでしたが、限られた時間でも目覚ましい集中力で取り組んでもらった結果、どの参加者の発表もしっかりした内容で驚きました。 正直言って予想を上回る出来でどれも興味深く聞かせてもらいましたが、聞いていて若い人に負けじとこちらも頑張らねばならないなと思いました(個人の感想です)。

参加者の何名かは後日発表資料を公開する(資料作成時にいくつかの注意点を守った上であれば公開は推奨しますと伝えています)と言っていたので、そのうちどんな内容に取り組んだのかがウェブ上で見れるかもしれません。

R&D 単独でインターンを開催した経緯

最後になぜ R&D 単独でインターンを開催したのかの経緯を述べておきます。 去年開催した 17 day TECH INTERNSHIPにおいても機械学習に関する講義やワークは実施していました。 しかしながら、昨今の機械学習の進展に伴って要求される技術要素も専門性が高まっているので、R&D で独立したインターンを開催することにしました。

人事の方々の協力のおかげで、最初の開催にも関わらず多数の学生に応募いただき、最終的には冒頭で述べたように 17 名の学生に参加していただくことができました。 参加者の多くが既に機械学習に取り組んでいて一定度の知識があったため、インターンの内容もそれに合わせて設計することができたのでどの参加者にとっても有益なものになったのではないかなと思います。 また、今回知り合いになった参加者の方々は、今後も研究会などで会うことがあると思うので、情報交換をし合えるような関係性をぜひ継続してもらいたいと願っています。

まとめ

いかがでしたでしょうか。 今回はインターンの内容紹介でしたが、クックパッドでは機械学習を用いて新たなサービスを創り出していける方を募集しています。 興味のある方はぜひ話を聞きに遊びに来て下さい。 クックパッド株式会社 研究開発部 採用情報

OiCyサービス(開発中)の裏側 〜レシピのMRR化〜

$
0
0

f:id:ShinyaOhtani:20180907180851p:plain
Smart Kitchen Summit Japan 2018での様子 : 左からクックパッドの住、金子、大谷(私)

こんにちは、研究開発部のスマートキッチングループの大谷です。 私はスマートキッチンサービス"OiCy"の進むべき方向を考えつつ技術に落とし込む役割を担ってます。 その過程で見つかった大きな技術課題レシピのMRR化(Machine Readable Recipe)について紹介いたします。

レシピのMRR化

先日Smart Kitchen Summit Japan 2018でクックパッドはスマートキッチンレベルを発表しました。 スマートキッチンレベルは、人間がすべての調理タスクを実行するレベル0から始まり、レベルが上がるにつれキッチンの機器から人間への支援の度合いが増え、レベル4では全自動、そして最高位のレベル5では人間と機器が協調して調理できると定義しています。

クックパッドでは、OiCyサービスを通じて最高位のレベルである「人間と機器が協調して調理できるスマートキッチンの世界」を実現しようとしています。

f:id:ShinyaOhtani:20180907174003p:plain
スマートキッチンレベル : レベル5

人間と機器が協調して調理できるスマートキッチンを目指すためにまず越えなければならない課題のひとつは、 レシピを機器が理解できるように機械可読性の高いレシピ(Machine Readable Recipe)に変換すること、 すなわちレシピのMRR化です。

人間が読むのに適した現在のクックパッドレシピのままでは、機器がガスコンロの火力すら読み取ることが困難であり、 人間と機器が協調して調理することなど到底できません。 そこでクックパッドではOiCyサービスを通じてレシピをあらかじめ構造化し機械が解釈しやすい状態にしたレシピを提供しようとしています。

f:id:ShinyaOhtani:20180907174528p:plain
OiCyサービス : レシピを機器が読める形式にして提供 (開発中 2018/08現在)

人間と機器が協調するスマートキッチン

さてそれでは将来のスマートキッチンの機器がレシピから読み取りたい情報とはどのようなものでしょうか。 整理するために料理の基本工程を書き出してみます。

f:id:ShinyaOhtani:20180907173948p:plain
調理工程

他の分類もあるかもしれません。分類の方法はさておきこのように料理工程を書き出してみると、 将来、人間と協調しながら動作してほしい機器は多岐にわたることがわかります。 また機器が多岐であるため、機器が必要とするレシピ情報は更に多岐にわたることが想像されます。

例えば近い未来のコンセントモデルとしてクックパッドが発表したOiCy Tasteが行っている「下処理::下味::調味料をあわせる」では 一度に利用する調味料の種類分量をレシピから読み取ることが求められますし、 また、現代の調理機器の代表格である「本処理::加熱の種類::レンジ加熱」では、 レンジの出力(600Wなど)や時間、電子レンジに入れる材料などをレシピから読み取ることが求められるでしょう。

このような情報をすべて手作業で読み解いて準備することはレシピの数から考え現実的ではありません。 クックパッドでは自然言語処理等を使った自動化を検討しています。

調味料サーバ用途の情報抽出

f:id:ShinyaOhtani:20180907174218p:plain:w300
材料欄

クックパッドのレシピの材料欄の記入は自由度が高いため、「醤油」でも「しょうゆ」や「しょう油」と様々な記述ができます。 また一行に「醤油・みりん・酒・酢」 : 「各大さじ1」などという記述がされていることもあります。 漢字・平仮名や意味の同じ別表現を正規化されたひとつの表現として扱うにはこの記事で触れている技術を使って機器が読み取りやすい形に変換するようにしています。

あとは一行に「醤油・みりん・酒・酢」と記載されているような特殊ケースや、 「大さじ1」や「大1」など非常に豊かな多様性ある表記の多い単位部分の扱いも難しいところだったのですが、 いくつかのルールを用意することで調味料サーバOiCy Tasteでは9割以上のレシピで正しく動作するようになりました。 エラーケースとしては人間でも理解に苦しむ「オイスターソース : 小さじ1・1/2」などが残っています。 初期検討段階なのでルールベースで処理していますが、この辺は随所で今後機械学習の視点を入れて更に高性能にしたいところです。

少し余談ですが初期検討段階で意識すべきは、課題がざっくりとどのくらいの難易度で、 どんな前処理でどのくらい問題が簡単になるのかといった課題の特徴をクイックに発見することにあると考えています。

そのため初期検討段階では性能を追い求める作業はしません。かわりに性能評価のためのアノテーションと評価環境を作ることは必須であり、時間を割いて構築しています。 性能評価環境と課題の特徴に関する知見さえあればあとで機械学習などを使ってじっくり性能向上を図ることもできます。

電子レンジでの温め用途の情報抽出

f:id:ShinyaOhtani:20180907174006p:plain:w300
各ステップの文章

現代の調理機器の代表格である電子レンジについても情報抽出を試みてみました。 ルールベースの自然言語処理でレシピの各調理ステップの文章から、電子レンジを使うステップなのかどうかの判別は精度95%、再現率100%の性能であることがわかりました。

  • 例:

スープ皿に水、コンソメ、玉ねぎを入れラップをします。500Wで5分、ひっくり返して皿に7分

  → use_microwave

また同様にルールベースの自然言語処理で電子レンジの設定(ワット数と時間)や電子レンジにかける対象物を認識させてみましたところ、設定:精度90% 再現率95%, 対象物:精度81% 再現率85%の性能でした。

  • 例:

スープ皿に水、コンソメ、玉ねぎを入れラップをします。500Wで5分、ひっくり返して皿に7分

  → [500W, <300sec, 420sec>, 水+コンソメ+玉ねぎ]

MRR化の今後

将来のスマートキッチンの機器が必要とする情報は、それぞれバラバラのMRRとして用意されるのではなく、 理路整然と構造化され、情報が増えても理解しやすい構造に保存されます。詳細は述べませんがMRRは面白い構造をしています。

まだまだ情報抽出課題は山のように残っています。 もしMRR化やクックパッドの研究開発に興味がある方はぜひ私達と一緒に開発してみませんか?

【開催レポ】Cookpad Tech Kitchen #17 〜北欧で最新のインタラクションデザインを学んできた話〜

$
0
0

こんにちは。広報部のとくなり餃子大好き( id:tokunarigyozadaisuki)です。

8月22日 にCookpad Tech Kitchen #17 〜北欧で最新のインタラクションデザインを学んできた話〜を開催いたしました。クックパッドではCookpad Tech Kitchenを通して、技術やサービス開発に関する知見を定期的に発信しています。

第17回はデンマーク/コペンハーゲンの新興デザインスクール、Copenhagen Institute of Interaction Design (CIID) のサマースクールに参加した社員3名による現地レポート。世界各国から集まったデザイナー、UXエンジニア、PM、研究者……様々なバックグラウンドを持つ参加者とともに過ごした1週間で学んだ、最新のインタラクションデザインについてお話させていただきました。サマースクールの内容だけでなく、それぞれが北欧でみた面白いものやコペンハーゲンという都市の環境についてもご紹介しましたよ。

f:id:tokunarigyozadaisuki:20180904164152j:plain
デンマーク国旗をモチーフにした寿司ケーキをご用意しました!

発表プログラム

「Machine Learning for Rapid Prototyping」

はじめにお話させていただいたのは、クックパッドに新卒1期生として入社し、開発・デザイン・PMと領域を横断しながらプロダクトづくりを担当しているUXエンジニアの出口です。

f:id:tokunarigyozadaisuki:20180904164211j:plain


なぜ今回、CIIDのサマースクールへ参加したのかという背景をお話した後、アカデミックな領域だと思われがちなML技術を道具として用い、プロトタイピングを高速に行うための方法について、共有させていただきました。 下記の資料でも紹介しておりますが、弊社が取り組みを始めたDesignOpsについてのインタビュー記事は こちらです。

「Intro to Designing Interactive Spaces」

次に今年の4月にエンジニアとして新卒入社し、現在は新規サービス開発に携わっている藤坂がお話いたしました。

f:id:tokunarigyozadaisuki:20180904164215j:plain


藤坂からは、実際に出題された「国連の掲げる目標を、訪問者が理解し日常的な行動につなげるインタラクティブな空間をつくる」というメイン課題に対して、実際に作ったものをお見せしながら、インタラクティブ空間をデザインするための基礎(人々に与える効果や、プロトタイピングの手法)について、CIIDで大切にされているLIFE CENTRICな考えやものづくりのプロセスについて触れながらご紹介しました。

「Design for Behavior and Impact」

最後は2017年に新卒入社し、現在料理が楽しくなるマルシェアプリ「Komerco-コメルコ-」のリードデザイナーとしてブランディングからサービスの体験、Web・アプリのUIなどデザイン全般と開発を担う藤井の登壇です。
f:id:tokunarigyozadaisuki:20180904164228j:plain

人が行動するデザインとは何か?行動を起こすためのデザインプロセスや基礎知識(行動科学、行動バイアス)など、CIIDのマインドを通して体験したことをお話しさせていただきました。

付箋形式でお答えするQ&Aディスカッション

Cookpad Tech Kitchenでは参加者のみなさまからの質問を付箋で集めております。今回は、CIIDに関するご質問からクックパッドのデザインについてなど、たくさんのご質問をいただきました。ありがとうございました!  

f:id:tokunarigyozadaisuki:20180910105758p:plain
技術広報の外村が司会を担当いたしました

シェフの手作り料理

Cookpad Tech Kitchen ではイベントに参加してくださったみなさまにおもてなしの気持ちを込めて、シェフ手作りのごはんをご用意しており、食べながら飲みながらカジュアルに発表を聞いていただけるように工夫しています。 今回はCIIDがデンマークのデザインスクールでしたので、デンマークをイメージしたお料理をご用意させていただきました。国旗モチーフのライスケーキや伝統料理のスモーブローなどなど……。今回お越しいただけなかった方も、ぜひ次のイベントはご参加くださいね。

f:id:tokunarigyozadaisuki:20180910105213j:plainf:id:tokunarigyozadaisuki:20180904175711j:plain
メニューのご紹介とデンマーク伝統料理「スモーブロー」
f:id:tokunarigyozadaisuki:20180904164205j:plainf:id:tokunarigyozadaisuki:20180904164200j:plain
シェフ特製料理

おわりに

クックパッドではレシピサービス事業はもちろん、新規事業などに携わってくださる新しい仲間を募集しています。ご興味がある方はぜひご応募ください! お待ちしております。

https://info.cookpad.com/careers

次回のCookpad Tech Kitchen のテーマは「生鮮食品EC クックパッドマートの開発秘話」。9月26日 (水)に開催予定です! クックパッドでは他にも様々なイベントを企画しておりますため、今後のイベント情報についてご興味がある方は、ぜひConnpassのメンバー登録をポチっとお願いいたします! みなさまにお会いできることを楽しみにしております。

cookpad.connpass.com

id:mrknさん、素敵な写真をありがとうございました!

レシピ連動調味料サーバー「OiCy Taste」の設計情報を公開、解説します

$
0
0

研究開発部のスマートキッチングループ プロトタイプエンジニアの山本です。専門分野はロボティクスです。
スマートキッチングループでは、 レシピを様々な機器とつなぐスマートキッチンサービス「OiCy」の開発を進めています。今年の5月、OiCyの開発を発表した際に、コンセプトモデルレシピ連動調味料サーバー「OiCy Taste」を公開しました。クックパッドからハードウェアが発表されたことに驚いた方も少なくないかと思います。かく言う私もその一人で、「OiCy Taste」を見てクックパッドのスマートキッチンの取り組みに興味を持ち、この夏からハード系エンジニアとしてプロジェクトに加わることになりました。よろしくお願いします。 このエントリでは、このレシピ連動調味料サーバー「OiCy Taste」の設計情報を公開します。

最初に

皆さんは料理をしますか?料理を楽しんでいますか? 料理とエンジニアリングはとても良く似ていると私は考えています。レシピが設計図で、材料の調達をして、加工(料理)をして、完成品を顧客に評価してもらう。加工の過程で廃材(生ゴミ)が出るので処分が必要で、設計図通り作ったはずなのに同じになるとは限らない図面や文章に載らないノウハウがあること。エンジニアリングが好きな人間は料理にもハマれると思います。何より、家族が美味しいと言ってくれたときの喜びは、苦労して作った製品やサービスが顧客からイイネって言われたときに勝るとも劣らない喜びだと思います。 では、自分自身を顧みて、日常的に料理はしていますがその料理を楽しめているのか?と自問してみると、仕事や育児に追われて時間のない中で、料理をタスクの様に感じて必ずしも楽しめていない事に気が付きました。楽しくて当然のことなのに楽しめていない。この問題をエンジニアとしてどう技術で解決するのか?この気付きを与えてくれたのが、今年5月のクックパッドの[レシピを様々な機器とつなぐスマートキッチンサービス「OiCy」]との出会いでした。

レシピ連動調味料サーバー[OiCy Taste]の設計情報公開の意図について


動画:レシピ連動調味料サーバー「OiCy Taste」
※本品はコンセプトモデルであり現時点で発売の予定はありません。

レシピを様々な機器とつなぐスマートキッチンサービス「OiCy」では、クックパッドに投稿されたレシピを、機器が読み取り可能な形式(MRR: Machine Readable Recipe)に変換して機器に提供します。レシピのMRR化ついては、9/7の大谷のエントリ:OiCyサービス(開発中)の裏側 〜レシピのMRR化〜で紹介されています。ぜひそちらも読んでいただければと思います。
さて、レシピ連動調味料サーバー「OiCy Taste」は、スマートキッチンサービス「OiCy」を利用した調理機器のコンセプトモデルです。クックパッドで作りたい料理を検索してレシピを閲覧すると、機器側が連動して簡単な操作でそのレシピで用いる調味料を配合して出すことができます。レシピを見ながらの調味料の配合は、レシピに書かれた分量を自分が作る料理の量に合わせて計算したり、各調味料の蓋を開ける、分量を測る、蓋を閉める、といった細かな作業の繰り返しです。実際料理をしていると、これはこれで結構手間がかかる作業で億劫だったりします。また、料理をしている最中は、手が濡れていたり汚れていたりしますので、音声を使って調味料を出せると手を拭く手間を省けますし、現在している調理作業を中断する必要がなくなり作業の効率性があがります。
本機の公開時、早期の商品化を期待するポジティブな声を多くいただきました。本当にありがとうございます、スマートキッチングループ関係者はますます奮い立っております。一方残念なお知らせになりますが、現時点でクックパッドが本機を製品化して販売する計画はありません。

本機は、レシピを様々な機器とつなぐスマートキッチンサービスOiCyを実証する一つのコンセプトモデルです。クックパッドの目指しているのは『毎日の料理を楽しみにする』ことであり、クックパッドの進めているスマートキッチンは『人間と機器が協調して調理』することを、スマートキッチンレベルの最高位のレベル5に掲げています。しかし、この取組はクックパッド一社だけで達成できるものではありません。料理をする作り手の皆さん、調理家電を開発販売する電機メーカー各社、キッチンを設計施工するハウスメーカー各社など、多くの関係者の協力があって初めて実現できると考えています。今回、レシピ連動調味料サーバー「OiCy Taste」の設計情報を公開するのは、スマートキッチンのムーブメントを盛り上げ、個人企業を問わず、一緒にスマートキッチンの研究開発に取り組む仲間を増やしたいという意図があります。今回公開された情報の利用に制限はありませんので、個人企業で本機のコピー機を開発するなり改良機を開発していただいてもなんの問題もありません。むしろウェルカムですので、その時は一報をいただけるとありがたいです。

免責事項:公開された情報を利用して開発製作行為をした結果生じたトラブルや損失・損害等について、当方は一切責任を問わないものとします。

OiCy Tasteのシステム構成

f:id:ymmttks:20180911143612p:plain
OiCy Tasteのシステム構成

調味料サーバー「OiCy Taste」は、本体上のボタン操作によって選択された調味料を設定量出すことができます。また、スマホのアプリ操作やスマートスピーカーの音声入力を用いて本体に触れることなく調味料を出すこともできます。このような本機のシステムは、上図の構成で実現されています。 本機は、ボタン操作やWiFiコマンドにより指定された調味料を指定された量だけ出す様になっています。本体側には非常にシンプルな機能しか持っておらず、複雑な情報処理(レシピ情報から調味料の決定や分量計算、音声操作のための音声認識など)は、スマホアプリやクラウドサービス側で実装されています。そのため、調味料サーバー「OiCy Taste」本体には高度な情報処理を行うプロセッサや高度な認識認証を行うセンサ類は不要で比較的低コストに実現可能な構成になっています。

OiCy Tasteのハード構成

f:id:ymmttks:20180911143603p:plain
OiCy Tasteのハード構成
調味料サーバー「OiCy Taste」のハード構成は上図のようになっています。 4種類の液体調味料が独立した4本のガラスボトルに封入されていて、4つの独立したポンプでボトル内を加圧し液体調味料を押出してノズルから排出します。各ボトルとポンプをつなぐ空気流路は三股になっていて電磁弁が取り付けられています。この電磁弁は、調味料を出し終わった際にボトル内の気圧を大気圧レベルに戻せるようにするために取り付けられています。この電磁弁がないと、ボトルとノズルの間のチューブに液体調味料が残ってしまい、ノズルから残った調味料が垂れてきたりチューブ内で調味料が固まり流路閉塞をおこすなどトラブルの原因となります。
排出される調味料の分量は、ロードセルを用いて重量変化をフィードバック制御し管理されています。調味料の配合は、レシピ上で少々(=小さじ1/8、1g未満)と記載されるレベルの精度が要求されます。開発初期段階では、単純にポンプの駆動時間によるオープンループ制御も検討されていましたが、要求される精度を実現することが困難で、電磁弁を用いたボトル内気圧の減圧機構が追加されました。イメージビデオでは、4つの調味料が同時に出ている映像が流れますが、これは電磁弁をつける前の状態で、現在の仕様では重量計測が配合調味料を受ける容器一箇所で行われている都合上、複数の調味料を同時に出すことができません。安価な非接触の液体流量センサーなど、いいセンサー情報をお持ちの方は是非教えていただければと思います。 なお、4つの液体調味料は、醤油、みりん、日本酒、お酢が選ばれています。これは、クックパッドに投稿されたレシピとそのアクセス頻度などのデーターベースを活用し、最もよく利用される調味料の組み合わせとして選ばれています。
以下が、本機で使用した主要な機構部品と本体メカ部品の3Dデータ(STEP形式)です。一部の機構部品については現在入手が困難になっているものもありますので参考情報になります。また、3Dデータは重量センサーのロードセル対応前かつ電磁弁改良前の仕様になっています。

主な機構部品

OiCy Tasteの3Dデータ

OiCy Tasteの電子回路構成

f:id:ymmttks:20180911143645p:plain
OiCy Tasteの電子回路構成
調味料サーバー「OiCy Taste」の電子回路構成の概要は上図のようになっています。以下、各ブロックごとに解説していきます。実際の回路図は下にリンクしています。

①マイコンボード

機体の制御は、電子工作等でおなじみのESPr® Developer(ESP2866)ボード(以降、ESPrボード)を使用しています。WiFi通信をする装置をプロトタイピングするにあたって、ESP8266や後継のESP32は低コストで導入できる最有力のマイコンボードだと思います。ただし、電源周りで苦労することが多いデバイスですので電源回路の設計、評価に注意が必要です。

②I/O拡張

スイッチやLEDなどのUI周りは、マイコンボードからSPIインターフェイス接続によりSPI I/O ExpanderのMCP23S17を用いて拡張します。マイコンの接続されるI/Oのうち入力は、『醤油』、『みりん』、『料理酒』、『お酢』を選択する4つのボタン入力、調味料を出す『出す』ボタン入力、分量切り替えと調味料を出す操作をするジョグダイヤルで3個の入力が必要で、合計8の入力ポートになります。出力は、『醤油』、『みりん』、『料理酒』、『お酢』の選択対象表示をするLEDが4個、ジョグ操作で切り替えられる調味料の量を表示するLED(小1、小2、大1、大2、大3、大4、連続)が7個、通電状態及びWiFi接続状況を表示するLEDが1個が必要となり、合計12の出力ポートになります。従って、入出力合わせて20個のI/Oが必要になるためマイコンボード単体のI/Oが不足します。そこで、SPI接続可能なMCP23S17を使ってI/Oを拡張するわけですが、MCP23S17で拡張できるI/Oポートは16個のためこれでもポートが不足します。そこで、調味料の出す量を表示するLEDは同時には一つしか点灯しませんので、デコードICの74HC138を使って3bitのI/Oから7個のLEDを制御してポートの不足を補っています。

③モータードライバ

ポンプの駆動は、I2C接続式のモータードライバIC DRV8830を用いています。ポンプのモーターを駆動する場合はモーターの回転方向を制御できる必要はないのでこのようなモータードライバICは必須ではありませんが、I/Oポートの数が限られている中で、DRV8830を使うと複数のモーター(ポンプを)I2Cバス最大9個までぶら下げて個別に回転数を制御できます。もともとは、複数の調味料を同時に出すことを想定して、各ボトルごとに独立したポンプをもたせる設計になっていました。しかし、重量センサーによるフィードバック制御で調味料を高精度に出すようになったことから同タイミングで複数のボトルから調味料を出せる必要性はなくなり、ポンプと電磁弁のコストバランス次第ではポンプを1つにして複数電磁弁による流路切り替えにしたほうが良いかもしれません。抜本的に再設計をする機会があれば、ぜひとも再検討したいところです。

④電磁弁駆動回路

電磁弁は、マイコンボードのI/OポートからFETを介して直接駆動しています。4つの電磁弁を1つのI/Oポートで駆動している回路の都合上、4つの弁は同じタイミングでしか開閉できません。このあたりは、設計データを見渡した際に「なんでこんな微妙な仕様になっているんだろう?」と気がつくところです。なんのことはない、開発の途中で必要になって追加された機能でポートが空いてなかったという話で、ハードウェアの試作開発ではよくある話です。リバースエンジニアリングで設計の時系列を読み解くのが楽しいのはこういう発見があることだと思います。

⑤アナログ回路

アナログ回路部分では、ひずみゲージを用いた重量センサーであるロードセルの出力、増幅、A/D変換しています。ロードセルは非常にデリケートなセンサーデバイスですので、信号はインスツルメンテーションアンプを用いた差動増幅で行っています。この出力を原点調整機能付きのヴォルテージフォロアを介して、I2C出力の12bit ADCにてアナログデーターをデジタル化します。この回路ブロックは、非常にノイズに対してセンシティブなため、基板回路設計において十分なノイズ対策を擦る必要があります。回路図を見るとわかりますが、当初はこのアナログ回路の電源は①のマイコンボードから供給される3.3Vのデジタル電源と共用になっていましたが、WiFi通信をするとこのESP2866のマイコンボードは電源電圧が暴れるためアナログ回路の電源が振られるという最悪の状態になってしまいます。そこで、アナログ回路は電源に自前のLDOを抱える仕様に変更されました。

主な電気部品

OiCy Tasteの回路図(改修指示含む)

OiCy Tasteのファームウェア

本機のファームウェアは、Arudino IDEで開発されています。組み込み系のプロトタイピングツールとしてすっかり定着したArudino IDEですのでESP8266向けの開発環境構築に関する説明は他の詳細に解説しているサイトを参照してください。一般的なArudinoのコードのように、setup()関数で周辺デバイスの初期化処理と内部変数の初期化を、反復的に呼び出されるloop()でメインの処理を行っています。メインループでは、HTTPリスクエスト(WiFiコマンド)の処理と物理UIボタン操作から、実行するべき操作を抽出し、動作に反映させるという処理を行います。このメインループとは独立に、モーターや電磁弁、重量センサーなどリアルタイムに処理する必要がある部分がタイマーtimer0_ISR()により10ms周期(100Hz)で制御されます。 また、使用しているライブラリのバージョンアップによって、以前動いていたプロジェクトファイルを再ビルドするとビルドは通ってファームウェア更新はできるけれども実機で正しく動かなくなるという現象が起こります。このあたりはOSSを使っている場合に避けられない問題ですが、スマートな管理解決方法を教えていただきたいところです。 以下が、本機のソースコードになります。元々公開を前提としていないプロトタイプ検証用のソースコードですので、アドホックな実装な部分が多々あるところはご理解とご容赦をお願いします。

OiCy Tasteのファームウェアソースコード

公開設計情報のGitHubリポジトリ

https://github.com/cookpad/oicy-taste

おわりに

本エントリでは、コンセプトモデルレシピ連動調味料サーバー OiCy Tasteについて設計情報の解説をさせていただきました。
8月9日のプレスリリースで、レシピを様々な機器とつなぐスマートキッチンサービスOiCyのパートナー企業10社を発表しましたが、クックパッドでは「OiCy」と連携した製品やサービスの実用化をパートナー企業様と協力してすすめていきます。今後も、「毎日の料理を楽しくする」コンセプトモデルを開発公開していきますのでご期待下さい。

最後に、クックパッドの研究開発部では、「毎日の料理を楽しみにする」ためのスマートキッチンデバイスを開発提案する仲間を募集しています。 ご興味がある方は採用ページを是非ご覧ください。ご連絡をお待ちしております。

中途採用:プロトタイプエンジニア(ハードウェア)

Viewing all 726 articles
Browse latest View live