「このレシピは何人分?」を機械学習で推定する

研究開発部の原島です。在宅勤務中は部のメンバーと 3 時にラジオ体操をしています。今日はラジオ体操の話はおいといてレシピの分量の話をします。

1 人分、2 個分、三枚分、約 4 皿、5 杯くらい、18 cm タルト型、...

クックパッドの一部のレシピは 1 人分のカロリーが計算されています。計算されたカロリーは検索結果の絞り込みや献立の作成などに使用されています。

ここでポイントとなるのは「1 人分」というところです。

レシピには、下図のように、その分量が記入されています。クックパッドの全レシピのうち、大体 50% のレシピの分量は「N 人分」という表記です。これらのレシピは、レシピ全体のカロリーを N で割ることで、1 人分のカロリーが計算できます。

f:id:jharashima:20200225174134p:plain
レシピの分量

一方、残りの 50% のレシピの分量は「N 人分」という表記ではありません。その半分(全体の 25%)はそもそも表記がありません。これは単に分量の記入が任意のためです。では、残りの 25% はどうなっているのでしょうか?

答えは様々です。「M 個分」や「M 枚分」、「M 皿分」のように「人」ではない助数詞による表記もあれば、「M cm タルト型」や「直径 M cm シフォン型」、「M cm 丸形 L 個」のように何らかの型の大きさとその個数による表記もあります。

こういった分量を「N 人分」に換算するにはどうすれば良いのでしょうか。同じ「M 個分」でもマフィンのレシピとケーキのレシピで N は違うでしょう。また、M は全角の時も半角の時も漢数字の時もあります。接頭辞や接尾辞も付いたり付かなかったりします。さて、どうすれば良いのでしょうか。

人手で頑張る

やっぱりまずはこれでしょう。人間の能力はすごいです。どんな表記でもちゃんとパースして、常識的な N に換算することができます。悩んだ時もレシピのタイトルや完成写真、各材料の分量などを参考にして、違和感のない N を選択することができます。

実際、1 日に十数レシピ(1 年で数千レシピ)の分量が人手で「N 人分」に換算されています。これは、配信記事で取り上げるレシピのカロリーを計算する過程などで換算されるものです。換算の際は、まず、一人のアノテーターが仮の N を決定します。そして、別のアノテーターがチェックして、最終的な N を決定します。

なお、チェック時のアノテーター間の一致率は大体 67% です。また、残りの 33% もそれほど大きな違いがないケース(e.g., 一人が N = 3、もう一人が N = 4)が多いです。これは、そもそも N の候補が少ないことが幸いしています。クックパッドのレシピは家庭用のものが多いので、大体 75% のレシピで N は 1 〜 4 です。

このように、できるのであれば、人手で換算するのが一番です。ただ、表記が「N 人分」でない(かつ、表記はある)レシピは数十万品あります。これらの分量を全て人手で換算するのはさすがに大変です。1 年で数千レシピを換算する今のペースでは大体 100 年くらいかかりそうです(その間に新しいレシピが投稿されるので、実際はもっとかかりそうです)。

機械学習を試す

そこで、機械学習の出番です。最近はライブラリやマネージドサービスが充実し、マイクロサービス化も促進されたので、機械学習をサービスで利用するハードルがぐっと下がりましたね。もちろんまだハードルはありますが、4 〜 5 年前と比較するとだいぶ楽になりました。機械学習は誰でも利用できる手段になりつつあります。

さて、今回開発したモデルは二つあります。一つ目は下図の single-source model です。このモデルはレシピの分量(もしくは、レシピのタイトル)を入力として N を出力します。より具体的な挙動は以下の通りです。

  1. 分量(もしくは、タイトル)をサブワードに分割
  2. 分割されたサブワードをエンコーダーに入力
  3. エンコーダーの結果を全結合層に入力
  4. softmax でいずれかの N(後述する実験では 1 〜 20)に分類

f:id:jharashima:20200305093505p:plain
single-source model

二つ目は下図の multi-source model です。このモデルは分量とタイトルの両方を入力として N を出力します。より具体的な挙動は以下の通りです。入力が複数になったことと、エンコーダーの出力を concat すること以外は single-source model と同じです。

  1. 分量とタイトルをそれぞれサブワードに分割
  2. 分割されたサブワードを各エンコーダーに入力
  3. エンコーダーの出力を concat して全結合層に入力
  4. softmax でいずれかの N に分類

f:id:jharashima:20200305093256p:plain
multi-source model

かなりシンプルなモデルではないでしょうか?単に、分量かタイトル(もしくは、分量とタイトル)の情報から N を推定するというだけです。表記のパースは最初から諦めて、サブワードに分割してニューラルネットワークに突っ込んでいます。

補足することがあれば、回帰問題でなく、分類問題としていることくらいでしょうか。これは、タルトやケーキなどのレシピにおける N が 8 や 6、4 のことが多かったからです。最初は回帰問題としていたのですが、N を連続値とするより離散値とするメリットの方が大きそうでした。

正解率は?

さて、このシンプルなモデルがどこまで通用するのでしょうか。同僚の @himkt にも手伝ってもらって、実験してみました。

実験には、分量表記が「N 人分」でない(かつ、表記がある)5,279 品のレシピを使用しました。これらを人手で「N 人分」に換算し、その 80% を訓練データに、10% を開発データに、10% をテストデータに使用しました。

モデルの設定は以下の通りです。

  • 埋め込み層: 20 次元
  • エンコーダー: 20 次元の LSTM(2 層)
  • 全結合層
    • single-source model: 20 次元(2 層)
    • multi-source model: 40 次元と 20 次元(それぞれ 1 層)

次元数などはいずれも開発データで調整しました。また、サブワードの分割には sentencepiece を、その学習にはクックパッドの 310 万品のレシピを使用しました。

タイトル  分量  正解率
RE 47%
ML (single) 28%
ML (single) 63%
ML (multi) 62%

結果は上表の通りです。RE(regular expression)はベースラインで、正規表現にもとづいて N を決定しました。具体的には、「M 個分」や「M 枚分」から M を抽出し、そのまま N としました。一方、ML(machine learning)は提案手法で、N は 1 〜 20 としました。また、正解率は初期値が異なる 5 回の平均値です。

表を見ると、分量の情報を使用したモデルの正解率は 62 〜 63% でした。アノテーター間の初見の一致率が大体 67% なので、なかなか良い正解率といえるのではないでしょうか。一方、タイトルの情報だけを使用したモデルの正解率は 28% でした。さすがに分量の情報を使用せずに推定するのは無理がありそうです。

意外だったのは、分量の情報のみを使用した single-source model の正解率が multi-source model の正解率より高かったことです。本質的には、タイトルの情報を使用せずに N を推定するのは不可能です。上でも言及したように、同じ「M 個分」でもタイトルが「マフィン」のレシピと「ケーキ」のレシピで N は違うでしょう。

single-source model が multi-source model に勝利(その差は 1% ですが)した理由はおそらく二つあります。一つ目は分量の情報のみでも、ある程度は、人数分が推定できたことです。ベースラインの正解率が 47% だったことから、47% のデータは「M 個分」などの M がそのまま正解の N だったことが分かります。こういった場合はタイトルの情報が必要なかったのかもしれません。

もう一つは、単に、multi-source model がタイトルの情報をうまく利用できなかった可能性があるということです。ベースラインで対応できなかった 53% のデータではタイトルの情報が有用に思われます。しかし、今回の実験では、二つのエンコーダーを学習させるには訓練データが少なかったのかもしれません。

以上を踏まえて、今は single-source model を試用しつつ、multi-source model を改善しているところです。訓練データを追加していけばどこかで multi-source model が勝利するのではないかなと予想しています。

タイトル 分量 正解 single multi
素朴なレーズンパン 1 斤 8 8 8
ツナポテトのミニコロッケ☆お弁当にも 8 個分 4 8 4
甘さ控えめのクッキー 鉄板 1 枚分 16 10 10

最後に成功例と失敗例を紹介します。一つ目の例は single-source model と multi-source model の両方が正解しました。「1 斤」を「8 人分」と換算したのはなかなか面白いです。二つ目の例は multi-source model だけが正解しました。「ミニコロッケ」は「2 個分」で「1 人分」と学習してくれたのでしょうか。三つ目の例は両方とも不正解でした。この場合、材料欄の情報(e.g., 各材料の分量)も利用しなければ正解するのは難しそうです。この辺りも今後の課題です。

おわりに

まとめると、クックパッドのレシピの約 25% は分量の表記が「N 人分」ではない(かつ、表記はある)ものの、その 60%(全体の 15%)は機械学習で N を推定できます。残りの 40%(全体の 10%)は訓練データを追加するなり、材料欄の情報を利用することで、N を推定できる可能性があります。

モデル自体を改善するのも一つの手です。Transformer や BERT(流行ってますね)を利用するのもありかもしれません。ただ、運用面を考慮するとモデルは軽量なものが良いので、この辺りは悩ましいところでもあります。やっぱり訓練データを追加するのが一番シンプルで、現実的な気もします。

ところで、最近、複数の学生から「新卒採用ってもう始まっていますか」という質問をいただきました。始まっております。機械学習や自然言語処理、画像認識で毎日の料理を楽しみにすることに興味がある方は、是非、以下のページからご応募ください。お待ちしております。

info.cookpad.com

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/