モバイルアプリの開発上の違和感・痛みに向き合い、少しずつでも前進するための取り組み

こんにちは、モバイル基盤部の茂呂(@slightair)です。 いやー12月になって寒くなってきましたね。

この記事では最近部で始めた「アーキテクチャ課題共有会」という取り組みについて紹介したいと思います。

開発中に感じる痛み

いきなりですが、モバイルアプリの開発中に痛みを感じたり違和感を持ったことはありませんか? 痛みというと大げさかもしれませんが、例えば以下のような設計・実装上のつらみ、悩みのタネたちのことを指します。

  • 同じような記述を毎回書く必要がある
  • 採用している設計パターンにあてはめようとすると、実装しづらい場合がある
  • 必要となる場面が多い割にできないことがある
  • 適切に扱うのが難しく、使い方を間違えやすいものがある
  • 複雑で理解するのが難しい、手を入れられない

特に複数人で開発しているアプリプロジェクトだと、自分でそういう痛みを感じることもあれば、他の人からそういう声が聞こえてくるという経験があるんじゃないかと思います。

僕たちが開発しているクックパッドアプリはサービスがリリースされてからずっと開発を続けているので、その間に開発規模や環境が大きく変わっています。 例えば、以下のものが挙げられるでしょう。

  • 開発人数・規模の増加
  • 人の入れ替わり
  • サービスのリニューアル
  • アーキテクチャの変更
  • モジュール分割などによるアプリ構造の変化

また、社内だけでなく、OSの更新、プラットフォームの進化、開発ツールの進化など、外部の要因もありますね。

そのような環境の変化があるたびに、それに合わせたやり方や仕組みの見直しを行うわけですが、導入した方針や仕組みがいつも最適な選択であるとはかぎりません。 最初は良さそうに思えたものであっても、使っているうちに欠点に気がついたり、状況が変わって適さなくなったものが出てきます。そうしたものが違和感や痛みとして表面化してきます。

痛みに気づいたら整理して解消すればよい

開発環境には変化が起こり続けているので、このような痛みや違和感が出てくるのは当然のことです。気づいたら課題を整理して解消していけばよいでしょう。

このような痛みに気づいて解消していくためには、どんな些細なことでもよいので、なにか思うところがあったら声を上げやすい雰囲気づくりが大切だと思います。幸いにもクックパッドのアプリ開発の現場ではそのような空気ができているように感じています。

Slack など普段会話をしているところで「こういうところつらくない?」というコメントが流れると「それな」「わかる」「オアーッ!」というようなリアクション*1も一緒に集まってきます。 丁寧な人であれば具体的に困っている設計上の課題や仕組みについて issue に書いてくれる場合もあります。

f:id:Slightair:20201215142521p:plain
Slack での会話

気づいて声を上げるところまではよいのだが…

痛みに気づいた人の声をきっかけに議論がはじまり、具体的な解決案がすぐにまとまればよいですが、必ずしもそのようにスムーズに話が進むわけではありません。 ほとんどの場合、大筋の合意を取って修正の方針を決めるまでが難しく、時間がかかります。

そのため、新しい課題が出てきても普段の仕事をたくさん抱えている中では、致命的なものでない限りなかなか向き合えないことがあります。 今までのやり方でも進められる場合は、違和感を持ちながらもそのまま進めることができてしまうからです。 すると、良くない部分に気づいているのにその問題が埋もれていってしまい、改善が進まないという状況になってしまいます。

最近でいうと、モジュール分割*2に関連する実装上の悩みや、アーキテクチャ*3についての課題が多く出ていました。仕組みの導入や整理した後に、それを実際に使ってサービス開発を進めることではじめて見えてくる問題点はたくさんあります。

部内の乗り越えたい課題

僕の所属するモバイル基盤部は、クックパッドでのモバイルアプリの開発環境を整えるという責務を担う部です。 サービス開発が円滑に進むようにクックパッドアプリの中核部分をメンテナンスしたり、設計パターン、実装方針の決定を主導するということが業務に含まれます。

部には、古くからクックパッドアプリの開発に関わっていて現状の実装方針の経緯に詳しい人もいればそうでない人もいます。 また、画面構築やモバイルアプリの設計パターンの知識、それらに対する興味・関心、得意不得意も人それぞれです。

そのため、改善の業務を進める上で部にもいくつか乗り越えたい課題がありました。

  • 現状のアーキテクチャや実装がどういう状態になっているのか、全員が認識している状態になる
  • 実際に基盤のツールやアーキテクチャを利用した際のサービス開発時の問題点を把握し、理解できるようになる
  • 現状認識の統一を図り、改善の方向性の意思統一ができるようになる

これらの課題は、アサインされたモバイル基盤部の業務を単純に遂行するだけでは落としやすいものばかりです。 浮かび上がってくる課題の理解を深め、真摯に解決に取り組み改善を進めていけるようなフローが必要だと考え始めました。

課題をきちんと拾って向き合う時間を作ろう

せっかく上がってきた課題や意見が Slack コメントや issue のままそこで止まらないように、定期的にきちんと課題に向き合う時間を作って運用してみることにしました。 前述の通り、アプリのアーキテクチャに関する話題が多かったので「アーキテクチャ課題共有会」という名前で始めています。

とりあえず以下のようなルールで始めてみました。

  • アプリのリポジトリとは別に、設計上の課題のみが集まるリポジトリを作り管理する
  • 毎週30分、ひとつ課題をとりあげて背景の理解とその解決方法について議論する
  • 参加者持ち回りで、課題の整理と可能であればその解決案を考えて事前にまとめてきてもらい、課題共有会の最初に説明してもらう
  • 議論中別の話題に発展したら、次回以降につづけるようにする
  • 対応方針が決まったら、やるだけの状態になるのでアサインする

このやり方で 2,3ヶ月運用してみて、うまく回っているところと改善できそうなところの両方がありますが、すでにいくつかの課題の解決に役立てる事ができています。

実際に上がってきた課題の例と課題共有会での進行

この期間で上がった課題の例をひとつ挙げると、「ユーザ情報を表現するオブジェクトの取得方法とその扱いがイマイチ」というものがありました。 この課題をテーマに議論することになりました。

アプリの機能や画面表示において、ログインしているかどうか、有料会員かどうかなどのステータスに依存するものはとても多いです。 このようなユーザ情報を取得したいケース、その使い方を整理して、イマイチな部分を改善しようということになりました。

事前準備

その回の担当者は事前に課題を以下のように整理してきました。

  • ユーザ情報の変化に追従する必要のある画面とそうでない画面があり、ほとんど後者である
  • 現状の仕組みでは全ての場面でユーザ情報の変化を意識しないといけない作りであり、複雑になってしまっている
  • モジュール分割の初期の仕組みに乗っているが、今は別の適切な実装手段があるので、新しい方法に乗り換えるべき

このような、現状とその問題点、それに対する解決案が参加者に共有された状態で議論を行います。 もしキャッチアップの段階で疑問点があれば、議論に入る前に解消します。

議論

課題の整理が済んだら提案された解決方法を評価したり、具体的な実装方法について話し合います。 議論中に別の課題が浮かんできたら元の議論が発散しないように注意し、次以降の課題共有会で話せるように内容を残しておきます。

この回では、ユーザ情報の変化を意識せずに各画面を実装するための仕組みの導入と、新しい実装手段への置き換えを行うことに決まりました。

方針の決定と導入

議論で修正方針が固まったら、どのような話が行われ、どのような結論に至ったかをまとめ、決定事項として残すようにします。 その決定事項をもとに、具体的な作業に落とした issue をアプリのリポジトリに作成するようにします。 あとは通常の修正と同じようにPRを作成し、レビューのステップを踏んで導入完了となります。

f:id:Slightair:20201215142621p:plain
課題共有会後の記録の例

課題共有会の良いところ、これから改善できそうなところ

アーキテクチャ課題共有会を始めたことで良かった点と今後の改善点を紹介します。

定期的に課題に向き合い、前に進めるようになった

まず、定期的に時間を取って課題に向き合うことにより、確実に前に進むことができるようになりました。 また、議題に上がった内容やそれに対して決めた「現時点の方針」をきちんと整理して残せるようになりました。

前述したように、開発環境や状況に応じて最適な戦略や方針は常に変わり続けるので、現時点で何を考えどういう方針を取ったのか記録として残るのが大切なところです。

毎週課題共有会を開いているので、方針が決まってあとはやるだけという状態のものがいくつも生み出せていますが、今度は決まったものの実装待ちが溜まりがちという問題も起きています。 前には進んでいるので今は一旦よしとしています。

参加者の負担を減らしつつ、個人の関心を高められる

課題共有会のはじめのキャッチアップをすばやく行うために、事前準備を行うようにしていました。 この準備はある程度手間のかかるものですが、持ち回りで準備をすることで、負担をうまく分散することができています。 負担の分散だけでなく、各個人で課題の深い部分に気づいたり意識をするようになって、議論を重ねるたびに話が進めやすくなる利点もありました。 また、長年の開発によって積もってきた実装方法や課題、歴史的経緯についても理解を深める機会にもなっています。

議論しているとあっという間に時間が過ぎてしまうので、なるべく小さい課題に分割してから議題として選ぶようにしていますが、それぞれの根となる課題は繋がっていることも多いのです。

アプリの設計方針、戦略についての意識合わせができる

当初の狙い通り、目線や今後の戦略についての意識合わせにも役立ちそうでした。 また、アプリ開発の知識量やアプリの経験値の埋め合わせにも効いています。

僕たちは自分たちのアプリの設計の議論からはじめてしまいましたが、もしモバイルアプリの設計パターンの知識が少なかったりメンバー間で大きく差がありそうな状況であれば、一般的な設計パターンの学習からはじめるのがよさそうにも思いました。 僕たちもこの取り組みを続けるうちにあまり良い答えが見つからなくなってきたら、途中で学習に切り替えようと思っています。

部の課題に関係したこともあり、基本的に今はモバイル基盤部のメンバーのみで課題共有会をやっていますが、取り上げる課題によってゲストを呼び、詳しい状況を聞いたり意見をもらったりしています。 アプリで採用する方針の決定にサービス開発者の現場の視点も入れた方が、より実際の利用に合った方針の選択が期待できるので、他の部署からの参加も呼びかけていきたいと思っています。

より形式的な課題管理の方法を試したい

課題共有会の取り組みはお試しだったため、ゆるーい運用ではじめてみましたが、段々と形になり始めているのでもう少し議題の選び方や粒度をしっかりしていけるとより有益な会にできそうと感じています。 情報の整理方法についても、何を書いて残すべきかはっきりするようなテンプレートを整備するなど、もっと形式的にやっても良さそうに感じています。 Issueのラベルで状態を示すようにしたり、いろいろな工夫をちょうど試しているところです。

まとめ

最近モバイル基盤部で始めた、アーキテクチャや開発環境に対する課題を共有し議論して前に進める取り組みについて紹介しました。 こうして書いてみると「課題を整理して時間を取って議論して記録を残そうね」という当たり前の部分が多く、チームによっては自然に行っていたプロセスかもしれませんが、やり始めてみたらしっくりはまってきたプロセスだったため、紹介しました。

このように様々な形でモバイルアプリ開発の環境を改善していきたいモバイルアプリエンジニアの方、またはこのような開発環境でクックパッドのサービス開発にじっくり取り組んでみたいモバイルアプリエンジニアの方がいましたら、ぜひご連絡ください。

*1:カスタム絵文字を作っておくと便利です

*2:〜霞が関〜 クックパッドiOSアプリの破壊と創造、そして未来 - https://techconf.cookpad.com/2019/kohki_miki.html

*3:2020年のクックパッドAndroidアプリのアーキテクチャ事情 - https://techlife.cookpad.com/entry/2020/11/17/110000

分析用ログデータに対する品質保証としての異常検知

クックパッドでデータにまつわるあれやこれやをずっとやってる佐藤です。分析・調査に仮説検証にデータパイプラインにと色々やってました。ちなみに先日はCyberpunk2077休暇をとるなどという呑気なことをしていたら、この記事でやりたかったことがほぼできそうなサービスがAWSから発表されて頭を抱えながら書いています。

そのログはどこまで信頼できるのか

クックパッドではサービス改善のためにWebサイトやアプリからログを収集して開発を行っています。これらのログは集計された後、ダッシュボードの形で可視化されてサービス開発者たちの意思決定を支えています。
クックパッドのログ基盤はログ送信側(クライアントサイド)もログ格納側(DWHサイド)も十分に整っており、いつでも必要であれば簡単にログを送信・集計するだけの仕組みができあがっています。

f:id:ragi256:20201216120246p:plain
アプリログにおける大雑把なログ収集の図
(注:例として上図を載せましたが当記事の内容はアプリに限りません)

しかし一方で、送り続けているログの管理・保守にはここ数年課題を感じています。例えば、iOSアプリのダッシュボードを見ていて、去年の6月に突然トップページのDAUだけが激増していることに気付いたとします。しかし、この原因を見付けることは非常に難しいのです。
クックパッドではWebもアプリも多くの人々が開発者として関わっています。このため、誰かがいつかどこかに加えた変化によってアプリのログへ気づかないうちに影響を及ぼしていたということが起こりえます。自分たちの担当する領域で普段は見ない数値を確認してみたら、実は半年前に大きく動いていた。だが特に何かをした記憶がない。こういったケースではどのように原因を特定すればよいでしょうか? KPIに直結しない数値・特定の条件に限定して算出した数値・実数ではなく比率に変換した数値などで、後になってから気づくことが多くありました。

より快適なサービス開発を行うためには、安心してサービスに関わる数値を確認できる状態でなければなりません。そのためにはこういったログに関する課題は解決する必要があります。そこでまず、ログの品質を保証するためにどんなことができるか考えた結果、ログデータの異常検知に取り組むこととなりました。

どうやって開発をすすめるのか

今回、異常検知をやるにあたっていくつか当初から決めていたことがありました。

  1. 作り込みすぎない、とりあえず使える状態を目指す
  2. 全体をパーツとして作ってできる限り交換可能にする
  3. 異常検知そのものだけではなく全体フローの最適化を重視する

これらは異常検知という仕組みが、あくまでもログの品質維持の取り組みのひとつに過ぎないことが理由となっています。もし試してみて全然だめそうだったり、より有望そうな他の手段が思いつけばいつでもピボットしたいと考えていました。
一方で既存の研究分野で培われた時系列モデルやアルゴリズムは、いつかどこかで試してみるタイミングがやってくるとも考えていました。そうなった時、いつでも任意のポイントに対する差し替えが可能となるよう、機能分割のタイミングを逃さないよう開発を進めることとしました。
こういった事情があり、最初から「scikit-leanで回帰モデルを試す」「Prophetを利用する」といった手法ありきの取り組みや「異常を検知したらそれで終わり」といった姿勢を取らないように注意していました。全体的な検知フローを重視していかにしてログの品質保証に繋がるかを考えての方針です。

この方針のもと、異常検知の仕組みは次の3つのステップの順で開発を行っています。

Step.1 MVPを作って自分で試す

まず本当に異常検知すると嬉しいのかどうかを半信半疑になって確認する必要があります。ログの異常検知をすると決まった時点で、DWHに蓄積された各種ログの集計内容を監視して上振れ・下振れなどの変化を監視することは決定していました。ただし、この時点では変化点検知(change point detect)か外れ値検知(outlier detect)かはまだ決まっていません。

最も手軽に異常検知をしようと思った時、DWHからデータを引っ張ってきて、既存の異常検知ライブラリを使って判定するのは時間がかかりすぎるように感じました。
そこでまずMVPとして、SQLのみで異常検知することにしました。最も基本的な時系列モデルはちょっとしたSQLで書くことができるため、ここをベースラインとしてまず仕組み全体を作り上げてしまうことを考えます。
ベースラインとして採用したのは過去n日間の平均・標準偏差を利用した予測です。

f:id:ragi256:20201216120545p:plain
仮に過去平均7日間、σ係数を3と置いたときの図

これは集計済みテーブルさえ用意されていればwindow関数で手軽に書くことができます。もし予測範囲に収まらなかった場合、(少々行儀が悪いですが)ゼロ割を使って無理やりSQLをエラーにします。

  select
    data_date
    , uu
    -- uu range: μ ± 3 * σ
    , case when uu between week_avg - 3 * week_stdev and week_avg + 3 * week_stdev
      then 1    -- pass
      else uu/0 -- assert(ゼロ割)
    end alert
  from (
      select
        data_date -- 対象テーブルにある日付カラム
        , uu      -- 異常検知を行いたい対象となる数値のカラム(ここでは仮にuu)
        -- ↓平均と標準偏差の計算に当日は含まないため微妙にずれる
        , avg(uu) over (partition by uu order by data_date rows between 8 preceding and 1 preceding) as avg
        , stddev_pop(uu) over (partition by uu order by data_date rows between 8 preceding and 1 preceding) as stddev

      from
        $alert_target_table -- 異常検知をしかける対象のテーブル
      where
        data_date >= current_date - interval '8 days'
  )
  where
    data_date = current_date

このSQLをバッチジョブとして毎朝実行させ1、ジョブがゼロ割エラーでコケたらSlackに通知を流すようにしました。

f:id:ragi256:20201216121116p:plain
バッチがコケるととりあえずこのエラー通知がSlackに流れる

MVPだけあって当初は大量に誤報が鳴り、ほぼ毎回アラートがあがるのでこのままでは使い物にならないことがわかりました。ですが、このときアラートの精度に関しては一切考えず一旦ワークフローを固めることを考えました。仮にこの誤報が減り、今鳴っているアラートが正しい異常検知の結果であったとした場合、自分は次に何をしたくなるだろうかと考えます。
実際、自分がアラートを見た時には「これは誤報か?確報か?」と毎回調べていましたので次に何をするかは「アラートが何故なったのかを調べる」ということがわかっていました。異常検知アラートの作成者以外も「なぜ異常検知のアラートが鳴ったのか?」を容易に知ることができる状態にしておく必要があります。そこで異常検知している様子がわかりやすくなるように下記のようなグラフを作成し、自動更新がされるように準備しました。

f:id:ragi256:20201216121245p:plain
異常検知の様子をわかりやすく可視化するためのグラフ、オレンジと青が上限・下限で緑がn日間平均

同時にこのグラフを作ったことで何故こんなにも誤報が大量発生したのかも発覚しました。過去に収集していたが今はもう使わなくなったログ・送信条件が厳しく流量の少ないログなどが多く含まれていたため、異常検知に用いるには欠損点が多く不安定な時系列データとなっていたためでした。そこで品質を保証する意義のあるログは「多くのユーザーに」「ある一定期間は使われている」ログと見なして流量と取得日数をフィルターすることにしました。
Step1の始めに「外れ値検知か変化点検値か決めていない」と書きましたが、この時点で外れ値検知ではなく変化点検知を行うことに決定しました。この異常検知システムによって検知したいのは後から対処しようのないサービスの瞬間的な異常ではなく、ログに関する実装の修正を必要とするような開発時点でのエラー・修正ミス・抜け漏れなどを捉えたかったためです。

\ グラフ
f:id:ragi256:20201216121500p:plain
f:id:ragi256:20201216121519p:plain

こういった工夫により、平均と標準偏差という最も素朴な基準でも誤報を減らすことができるようになりました。ハイパーパラメータはモデルではなくモデルに投入するデータのほうにもあったようです。ここでフィルターに用いる各種パラメータをSQLから分離させて対象とするテーブルごとに変更できるようにしておきます。

Step.2 他の人にも使ってもらえるように触りやすい仕組みを整える

Step1の状態では異常検知の仕組みを作った自分しか扱い方がわからず、アラートがきても何をどうしたら良いかわからない状態でした。この仕組みをサービスの開発にも活かすためには、多くの人に使ってもらえるようアラートが鳴ったらどうするかわかりやすいインターフェイスにする必要があります。
そこで、より異常検知した状況をつかみやすく、その後のリアクションをとりやすくするために通知内容を改善することにしました。

通知内容を改善するにあたって、これまでのただエラーを流していただけの状態を改修する必要がありました。そこでまず、バッチジョブの中身を修正し2つの処理に分割することにします。この分割で片方の処理の持つ責務を「異常を検知すること」、もう片方の処理の持つ責務を「検知した内容をどうにかして伝えること」にわけます。こうすることで仮にSlack以外のツールに通知を流す場合や、通知先はSlackのまま異常検知方法を切り替えるといった作業をしやすくなります。
そして、Slackへの通知を行う処理としてSlack WorkflowのWebhookを利用することにしました。このSlack WorkflowはSlack上でステップやタスクを実施してもらうことで定形的なプロセスを自動処理しやすくする仕組みです。また、外部アプリやサービスとの連携も豊富なため、Workflow内のステップで起こしたアクションを外部に渡すことができます。 通常のWebhookでは単純に情報をSlackへ流すだけとなってしまい、検知に対するアクションをとってもらいにくいと考えてWorkflowを採用しました。

f:id:ragi256:20201216121713p:plain
Workflow builderで作成、フォームを2つ加えて後述のGoogleSpreadsheetと連携させている

上図のようにフローを組むことでSlackへ情報を流すとともにリアクションをとってもらえるようになります。今回このWorkflowで設定した異常検知に対するリアクションとは「対応の方針を決める」「なぜその方針に決定したのか理由を書く」の2つです。異常検知が正しく働いたとして、それでも何も対応する必要のないケースもあります。なので「このアラートは無視する、古いバージョンからのログなので放って置いても困らないため」「このアラートはきちんと調査をする、重要指標が減少していてもしも本当に落ち込んでいたら緊急事態のため」と書き込んでもらうことにしました。

f:id:ragi256:20201216121946p:plain
アラートが鳴ったときに流れるメッセージ
f:id:ragi256:20201216122007p:plain
上記メッセージのボタンに反応した後に続くメッセージ

この通知内容によって開発者にログの異常に関するリアクションをとってもらって、「このログ異常は何故起きたのだろう?」「このアラートは無視していいものだろうか」と考えてもらおうというのが狙いです。
このSlack Workflowへの通知切り替えを再度自分でも使ってみて、アラート量的にも対応負荷的にも問題なさそうと感じたあたりでStep3に移りました。

Step.3 他の人にも使ってもらう

いよいよ自分だけではなく誰か別の人にも使ってもらう段階です。手始めに社内用ブログに上記取り組みを投稿して軽く共有し、クックパッドアプリのiOSやAndroidエンジニアが集まるチャンネルで使ってもらうこととしました。
その週にはアラートが鳴り、何度かアラートの対応をしてもらうことができました。しかしすべてがスムーズに進んだというわけもなく、いくつかの改善点がSlackでの会話から浮かび上がりました。

f:id:ragi256:20201216122135p:plain
早速フィードバックがもらえている様子

Slack Workflowという多くの人の目に見える形でアラートをセットしたことで、このようなやりとりをSlack上でこなすことができるようになりました。
また、リアクションをしてもらった結果は自動でGoogleSpreadsheetに溜め込まれていきます。こちらのシートに溜まった知見を元に今後のアラートの改善にもつなげていこうと考えています。  

f:id:ragi256:20201216122221p:plain
2つのフォームから書き込まれた内容が貯まるシート

これから

冒頭にも書きましたとおり今年の re:inventでAWSからAmazon Lookout for Metricsが発表されました。こちらはまだプレビュー版ですが、今回作った異常検知フローをそのまま置き換えることができるかもしれません。幸いにして今回のフローはアルゴリズムやチューニングに注力することなく、最小の労力をもって「ログの品質を保つためにはどんな仕組みが必要となるか?」の模索した解決案の一つに留まっていました。このため最終的な唯一の課題解決手法ではなく、むしろ課題を理解するためのプロトタイプに近く、実際に運用してみることで品質維持のために求められる多くの要素を知ることができました。

  1. 古いバージョンのログをどうするか
  2. 流量の多いログと少ないログの両方同時に監視すると発生する変化量の差をどうするか
  3. 既存の時系列解析や異常検知の研究手法で使われているアルゴリズムやモデルをいつ・どうやって・どう判断しながら組み込んでいくか
  4. そもそも「異常検知」では応急措置的な対応しかできないが、品質維持のために根本的対策や事前防止策をとることはできないか
  5. (他多数)

これらの要素を元に今回作成したシステムとAmazon Lookout for Metricsを比較することでより良い解決策と改善フローを実行できると考えています。

ログの異常というのは本来は起きてほしくない状況ではあります。知らず知らずの内にそのログ異常が起こっていて後から困るという自体を防ぐために、変化点検知作業を自動化する仕組みを整えることができました。まだまだ実用上では粗い点もありますが、漸進的開発をしやすい開発方針をとってきたのでこれからも徐々に改善していくことで「クックパッドではこうやってログの品質を保証しています」と言い切れるデータ基盤を目指していきます。


  1. SQLとバッチジョブの実行に関しては弊社OSSのKuroko2bricolageを利用しています。

キッチンでの微細な重量変化を捉えるには?

こんにちは.研究開発部の鈴本 (@_meltingrabbit) です.

クックパッドの研究開発部では,ユーザーの課題を解決する手段をスマホの中からスマホの外(実世界)に拡張しようとチャレンジしています. 特に,料理を「作る」時の課題を解決するため,様々なデバイスを開発し,調理に関する知識と組み合わせることで,新たな調理支援の方策を切り開こうとしています.

その中の一例として,キッチンのワークトップやコンロでの微細な重量変化が取得できるデバイスをフルスクラッチで構築しました. 本稿ではその取り組みについてご紹介します.

調理中/調理後に知りたい情報?

調理において,重要な情報とは何でしょうか?

  • 分量(重量,体積)
  • 火加減(熱量,温度)
  • 加熱時間(時間)
  • 味付けの濃さ(塩分などの濃度,調味料などの重さ)
  • 焼き色(色,温度)

... などなど,様々なことに目を配りながら,日々調理していると思います.

今回は重量に着目してみます. 調理中の重量変化が取得できると,どういった調理支援が考えられるでしょうか?

いくつか考えてみます.

まな板上にあるカット中の肉や野菜の重量がわかれば,何人前の料理を作っているかがわかります. それによって,その分量に合わせて微修正されたレシピを提示する,といったサポートが考えられます.

また,鍋やフライパンなどに入れた調味料の分量が自動で計量されていたら,今回の味付けが記録できるかもしれません. 食べた後に,「今回はちょっと味が濃かったなぁ」など感じたときに,その時の調理を振り返られるかもしれません.

さらに,例えば唐揚げは,揚げているときに鶏肉の重量が徐々に減少していくことが知られています. この重量変化をつぶさに捉えることができれば,「今がベストな揚げ終わりのタイミングだ!」などと教えてあげることができるかもしれません.

以上のようなモチベーションから,キッチンワークトップの重量を高精度に取得してみよう,と思い立ちました.

調理支援のために必要な重量センサとは

調理支援のための重量センサについて考えます.

上で記したようなことを実現しようとするのであれば,少なくとも小さじ1杯,つまり5 g程度の重量測定精度が不可欠です. そして,ワークトップにのる可能性がある物(コンロや鍋や食材,加えて人間の押し込み荷重など)を考えていくと,最大荷重は最低でも50 kg程度が求められます.

とすると,,,最大荷重は50 kgと仮定して,ダイナミックレンジが1/10,000!?の重量センサを欲している,というわけです. そんな精度でかつキッチンに設置できるものなど,そう簡単には手に入りません.

残された道は,フルスクラッチでの自作. 自作するために,重量計測に対する要求仕様を以下のように策定しました.

  • 測定精度は5 g以下で,最小測定分解能はその1/10以上
  • ダイナミックレンジは可能な限り広く.最大許容荷重は50 kgを超えることを目標
  • サンプリング周波数は高ければ高いほどよい.1,000 Hz程度は超えたい
  • 精度要求が厳しければ,リニアリティとオフセットドラフト,温度特性は妥協する.一方でヒステリシス特性やリピータビリティは重視する

また,既存のシステムキッチンを改造して重量センサを埋め込むのはハードルが高いため,すでにあるキッチンワークトップの上に設置し,その上で調理することにしました. そうすると,次のような構造的な要求が発生します.

  • 既存のキッチンワークトップに設置し,その上で調理するため,可能な限り背の低い形状にする
  • IHコンロとまな板とその他をその上に置くことを想定するので,天板の大きさは1,000 x 600 mm程度にする

得られたデータはリアルタイムに解析し,調理支援という形で調理する人にフィードバックできる必要があります. 高精度・高頻度に計測されたデータを後から抽出でき,解析できる,といったシステムでは,想定している調理支援には使えません. そこで,さらに次のようなシステム要求が追加されました.

  • 取得データはリアルタイムで社内システムで利用できる(例えばAWS IoT Coreに送信できる,など)

これらの要求を満たす重量センサをフルスクラッチしていきます.

実装

最初に実装結果を記します.

下図のようなものが完成しました.

先述したとおり,システムキッチン自体を改造するのは難しいため,既存のキッチンのワークトップに乗せて使用します. IHコンロ,まな板,はかりが乗っているアルミ天板の上の重量が高精度かつ高頻度に測定できます.

f:id:meltingrabbit:20201209030436j:plain
開発した重量センサ

達成されたスペックは,次のとおりです.

  • 測定精度は,設計値は4 gで,実際に使ってみると2 g程度はありそう
  • 許容最大荷重の実効値が54 kgで,絶対最大定格の実効値が84 kg
  • 最高計測周波数が100 kHz(実質的に使えるのはせいぜい数kHz程度か?)

構造の概要は下図に示すとおりで,上から,超低頭ネジ,アルミ天板,カーボンロッド,アルミ治具,荷重センサ(ロードセル),ネジ,アルミ治具,ネジ,アルミフレーム,です. 天板の四隅を荷重センサで支持,計量する構造となっています. (したがって,今後のソフトウェアの改良で重量分布も取得可能になる予定)

f:id:meltingrabbit:20201209030534p:plain
構造図

この重量センサを開発するのには,いくつかのハードルがありました.

まずは,高精度な荷重センサの選定です.いろいろと検討した結果,工場などで用いられる産業用のロードセルを用いていますが,もともとのセンサの想定使用方法通りには使われていません.

また,ダイナミックレンジを大きくするためには,天板をできるだけ軽く作る必要がありました. なぜなら,ロードセルの許容最大荷重が例えば60 kgだとしても,天板の重さが20 kgもあると,実質的に計量可能な最大荷重は40 kgになってしまうからです. 一方で,軽く作ることに集中し,天板の剛性が十分でないと,荷重によって天板がたわみ,余計な力がロードセルに加わってしまい,正確な重量計測ができなくなります.

このサイズですと,板厚3 mm程度のアルミ板ですらおよそ5 kgの重量にもなりますが,両端単純支持では中央に20 kg程度の負荷をかけただけで50 mm程度はたわんでしまうのです. 軽い金属のアルミですら,こんなもんです. この軽量と高剛性という相反する要求を満たすために,最適な天板の厚みを計算しました. そして,アルミ板のみでは剛性が確保できないので,カーボンロッドで補強するなどしています.

カーボンロッドとアルミ天板との接着には,アクリル樹脂系の2液式接着剤を使いましたが,これも大変でした. 2液式接着剤とは,接着前に2種類の溶剤を自分で混ぜて,それが固化する前に接着面に塗りつけ接着するというものです. 今回用いた接着剤は,混合から固化までの時間が90秒だった(カーボンとアルミが接着可能で入手性の良いものがこれしかなかった)ので,小さなパーツの接着ならまだしも,1 m弱の大きなロッドに対して,

  • そこそこ大量の溶剤を均一に混ぜ合わせる
  • それをカーボンロッドとアルミ板に均一に塗る(均一でないとそこで応力集中してしまう)
  • 位置を合わせて仮固定する

を90秒以内に終わらせるというタイムアタックをしなくてはいけないのがきつかったです.

さらには,ロードセルの計測値をリアルタイムに収集し,処理するのためのソフトウェアも重要です.

今回用いたロードセルは,工場などに組み込むことを前提として,RS232Cで計測信号を出力するインターフェイスはメーカーから提供を受けることが可能でした. しかし,RS232Cなので高頻度計測にはボーレートの問題もあるし,4つのセルからの信号を時刻同期しつつ高速に収集するミドルウェアを書くのはめんどくさいなぁ,と思ってしまいました.

最終的には,ロードセルの実体はひずみゲージのブリッジ回路なので,それを駆動して出力をAD変換できるロガーを別途購入し,そこからリアルタイムにデータを取得し,適切な信号処理を施し, 社内の調理支援システムへリアルタイムへデータを送信する,というソフトウェアを自作しました.

いざ,計測

要求仕様の出どころであった小さじ1杯を計測してみました.ワークトップ上に小皿を置き,そこに小さじ1杯を投入していったときの計測結果が下の2図です.

信号処理のフィルタのチューニングを変えていて,1枚目は高頻度な成分を抽出できるような設定になっており, 2枚目はかなり強めのLPFを挟むことで質量の増分を分解しやすい設定になっています.

そのため,1枚目では,水を小皿に垂らしたときの振動がよく計測されている一方で, 2枚目ではおよそ1,5,15秒のところでおおよそ大さじ1杯の水が投入されたということが明瞭に分かる結果となっています.

同じ操作を異なる信号処理で観察すると,違った特徴が見えるのは面白いですね.

次の図は,唐揚げを揚げたときの結果です. 3つの唐揚げを鍋に投入し,それをおよそ2分半揚げ,ひとつずつ取り出した履歴です.

0〜0.5分では,ひとつひとつの唐揚げの重さが分解できています. また,0.4〜2.7分頃では,揚げている最中に唐揚げ内部の水分が蒸発して軽くなっている様が観察できます.

まとめと今後

このように,これからの研究開発のための1センシングデバイスとしての重量センサが,無事に目指していたスペックを満たして実装することができました. 実は,クックパッドではソフトウェア開発のみならず,こういったハードウェアの開発も行っているのです. 今後はこれらを使い,調理中の様々なイベントを収録し,そして社内の様々な知見や技術(ハードもソフトも)を総動員して,「毎日の料理が楽しくなる」ような調理支援の開発を目指していきます!