メルマガ登録
当社の取締役が、「食生活を最適化アルゴリズムに委ねてみる」ことにチャレンジしました。データサイエンティストもサポートし、アルゴリズム開発からスマホアプリの実装も含めてまずはやってみる!という、超短期間でのチャレンジの舞台裏をご紹介します!
こんにちは。今回は2月に当社取締役関口がチャレンジしたファミマ 5daysチャレンジ(関口個人のnoteにリンクします) の舞台裏、特にプロジェクトの進め方や技術的側面について紹介します。ファミマ 5daysチャレンジとは、関口の思いつきから始まったプロジェクトで、当社の取締役が、「食生活を最適化アルゴリズムに委ねてみる」ことにチャレンジしました。データサイエンティストもサポートし、アルゴリズム開発からスマホアプリの実装も含めてまずはやってみる!という、超短期間でのチャレンジの舞台裏をご紹介します!
こんにちは。今回は2月に当社取締役関口がチャレンジしたファミマ 5daysチャレンジ(関口個人のnoteにリンクします) の舞台裏、特にプロジェクトの進め方や技術的側面について紹介します。ファミマ 5daysチャレンジとは、関口の思いつきから始まったプロジェクトで、
1週間ファミリーマートの商品だけで生活する
何を食べるかはアルゴリズムに任せる
というものです。なぜそんなことをするはめになったのか、結果どうなったのか、については最後にご紹介するnoteを御覧ください。僕らはそんなチャレンジをする関口向けに、アルゴリズムの設計・構築からスマホアプリの実装まで行いました。正月休みを含めて1ヶ月(実質3週間弱)、業務の空き時間とノリだけで進めた短期プロジェクトです。
1週間ファミリーマートの商品だけで生活する
何を食べるかはアルゴリズムに任せる
というものです。なぜそんなことをするはめになったのか、結果どうなったのか、については最後にご紹介するnoteを御覧ください。僕らはそんなチャレンジをする関口向けに、アルゴリズムの設計・構築からスマホアプリの実装まで行いました。正月休みを含めて1ヶ月(実質3週間弱)、業務の空き時間とノリだけで進めた短期プロジェクトです。
僕らが作ったのは、栄養価を気にしながら食事内容を提案するアプリです。
使える工数は有志の空き時間だけなので、無駄な労力を極力使わないで済むように考えた結果、以下のような Google Spreadsheet 中心の構成としました。
具体的な処理は以下のように進みます。
ちなみに、「明日食べるべき商品」は、厚生労働省が公開している日本人の食事摂取基準と、各商品の栄養データを照らし合わせて選択しています。詳細は後ほど説明します。
すべての始まりはここでした。
【関連記事】インターン生が作った旅行プランAIで実際に旅行に行ってみた
上記は、同じく取締役の塩澤が「インターン生のアルゴリズムに沿って旅行をしてみる」というチャレンジをした記事です。
関口のnoteにも書かれている通り、
同じ取締役の塩澤さんの体を張ったチャレンジに嫉妬刺激を受けたようです。
早速、社内の分析官に声をかけたところ、あれよあれよという間に話が進み、
とする事になりました。はじめは「UIにはこだわらなくてよい。スプレッドシートに商品名の一覧があれば十分」という話だったのですが、実際のユースケースを考えるとそれは現実的でない1という話になり、簡単なアプリを作ることになりました。
大まかにやることが決まったら、通常のプロジェクトと同様、マイルストーンを設定してスケジュールを確定させます。関口のスケジュールを確認させていただいたところ、毎日のように会食が入っていることがわかりました。会食中に関口一人だけにファミリーマートの商品を食べさせるわけにはいきません。そこで、直近2ヶ月程度でまだ会食の入っていない週を探し、そこをチャレンジ実施の週としたところ、以下の開発スケジュールとなりました。
日付 | 内容 |
---|---|
12/20 | 関口取締役との雑談 |
12/27 | データ収集(クローリング)と整形 |
1/7 | メンバーを集めてキックオフ・プロトタイプ開発開始 |
1/16 | アプリのプロトタイプ完成(第一弾) |
1/20 | プロトタイプの使用感調査と課題の洗い出し(プロトタイプの継続的な改善) |
1/28 | アプリ完成 |
2/3 | チャレンジ開始 |
タイトなスケジュールなうえに確保できる工数も少なく、要件も明確になっていない状態でのスタートだったので、前述の通り、可能な限り工数を削減することと修正依頼に素早く対応できるようにすることを重視し、以下の方針としました。
前述のとおり、 Google SpreadSheet をハブとして、UI(Glide アプリ)とGASが連携するようなアーキテクチャにしたので、以下ではアプリ側とGAS側の2つに分けて紹介します。
今回作成したアプリの画面は、今日のおすすめ商品タブ、今日食べたものタブ、健康度チェックタブ、の3つで構成されています。
前述の通り、画面の構築には Glide という、いわゆる「ノーコード」のサービスを使いました。
Glide はGoogle スプレッドシートと連携させて簡単にモバイルアプリを開発できるサービスで、以下のような特徴があります。
使い方はとても簡単で、著者も今回のプロジェクトで初めて触ったのですが、使い方を調べながら仮のデータと連携させ、最低限の機能を作るまで 2 時間くらいでした。 使い方は Qiitaの入門記事やMediumの記事いくつかのサイトで紹介されているので、興味のある方は触ってみてください。
今回の例でいうと、各アプリ画面(タブ)毎に、「このシートを参照してこの列をこうやって表示してね」といったようなイメージで設定していくだけで、スマホアプリ(いわゆるPWA)を作ることができます。
Google Spreadsheet をDB のように扱い、最適化結果が格納されるシートを読みにいってアプリ画面に商品一覧を表示します。 また、画面からユーザーが編集した結果は、スプレッドシートに即時反映されるようになっています。
初めて使ってみての感想になりますが、ものの数時間で上記のような見栄えの良いアプリを簡単に作ることができるのはすごいなと思いました。今回のプロジェクトはキックオフ~リリースまで1ヵ月程で、かつ業務の合間を縫って進めていたため、「あくまでプロトタイプ」という意味では Glide を使って正解だったと思います。ただ、できることには限度があり、「コードで書いたら数行なのにできない・・・」というようなケースが多々あったというのも事実です。
全体として、「Quick and Dirty」思想の進め方には適していると感じました。もし業務で使うことを考えるのであれば、早い段階でクライアントにアプリのイメージを持って欲しいとか、開発序盤で使うのがよさそうですね。
今回のアプリでは、厚生労働省が公開している日本人の食事摂取基準で定義されている、1日に摂取するべきタンパク質・脂質・カロリーの推奨量に近い栄養価を摂取できる食品メニューを選択するようにしました。
具体的には、食事メニューの選択フラグを変数として、各栄養素の推奨量と摂取量の差の総和を最小化します。
ここで
は考慮する栄養素を表すインデックス、 は商品数、 は食品 に含まれる栄養素 の量、は成人男性に対する栄養素 の一日あたりの摂取推奨量に、前日までの不足分を足したものです。日本人の食事摂取基準には、推奨量以外にも推定平均必要量や目標量、耐容上限量なども定義されていますが、簡単のため推奨量だけを考慮しました。
さて、上の式では目的関数に絶対値が含まれていて、そのままでは最適化が難しいので、新たな変数を追加することで目的変数を絶対値を使用しない形で表現します。 推奨量からの差異を表す新しい変数 を導入すると、最適化問題は以下の通り書き換えられます。
この形であれば、一般的なMIPソルバーで解くことができます。
次に、数理最適化処理の実装面について説明したいと思います。
本プロジェクトでは、なるべくコスト(時間、金銭)をかけずに実装を済ませたいということで、Google Apps Script(以下、GAS)を利用しました。GAS とは、スプレッドシートなどのGoogleが提供する各種サービスと連携可能なJavaScriptベースのプログラミング環境です。特徴として、Googleアカウントとブラウザさえあれば、無料で利用でき、かなりお手軽な点があります。筆者も、GASは初めて、JavaScriptもほとんど素人という状態だったのですが、環境の準備も含め数時間程度の作業時間で以下の一連の機能を実装できました!
以降では、それぞれ簡単に説明していきます。
GASでは、Google スプレッドシートと連携することで、シート上のデータ読み込みや書き込みを始めとした複雑な処理を行えます。ExcelにおけるVBAをイメージするとわかりやすいでしょうか。
本プロジェクトでは、レコメンド対象の全商品一覧シートの取得と、最適解として得られたメニューのレコメンド商品シートへの書き込みにより Glide 上で表示されるレコメンド商品の更新を実現しました。
スプレッドシートシートの読み込み/書き込みの例を簡単に紹介します。
// スプレッドシートの読み込み
var ss = SpreadsheetApp.getActiveSpreadsheet()
// data シートの取得
var data_sheet = ss.getSheetByName('data')
// シート中のデータ範囲(開始行, 開始列, 取得行数, 取得列数)を指定して、データを取得
var items = data_sheet.getRange(2, 1, 10, 4).getValues()
// 次の行に取得したデータをコピー
data_sheet.getRange(13, 1, 10, 4).setValues(items)
上記の例では、取得したデータをそのまま次の行にコピーしているだけですが、もちろん、取得したデータを処理して別シートに書き込むなども可能です。
GASでは、簡単な線形計画問題を実装できる LinearOptimizationService というクラスが提供されています。今回は、こちらを利用して最適化部分を実装しました。
それでは、上述の定式に基づいて、摂取タンパク質の最適化部分の実装例を説明していきます。
1.最適化エンジンのインスタンス作成
var engine = LinearOptimizationService.createEngine()
2.商品の選択フラグとなる0-1変数x_iの追加
// all_items: 全商品データ(id, 商品名, kcal, タンパク質, 脂質, 炭水化物)
// addVariable(変数名, 下限, 上限, 変数の型, [目的関数の係数])
for(var i=0; i <= all_items.length-1; i++) {
engine.addVariable('x' + i, 0, 1,
LinearOptimizationService.VariableType.INTEGER)
}
3.絶対値を外すための変数z_pの導入と目的関数の作成
// The objective is 1 * zp
engine.addVariable('zp', 0, Infinity,
LinearOptimizationService.VariableType.CONTINUOUS, 1)
4.制約の追加
// sum(p_i * x_i) - zp <= tp, tp <= zp + sum(p_i * x_i)
// tp: タンパク質の摂取目標値, p_i: 商品iに含まれるタンパク質(all_items[i][3])
// addConstraint(下限, 上限): 制約式の範囲を指定
constraint_p_gt = engine.addConstraint(tp, Infinity)
constraint_p_lt = engine.addConstraint(-Infinity, tp)
// setCoefficient(変数名, 係数): 制約式に変数の項を追加
// zp の項を追加
constraint_p_gt.setCoefficient('zp', 1)
constraint_p_lt.setCoefficient('zp', -1)
// sum(p_i * x_i) の項を追加
for(var i=0; i <= all_items.length-1; i++) {
constraint_p_gt.setCoefficient('x' + i, all_items[i][3])
constraint_p_lt.setCoefficient('x' + i, all_items[i][3])
}
5.最小化問題を解決
engine.setMinimization()
var solution = engine.solve()
var recommended = []
for (var i=0; i <= all_items.length-1; i++) {
// 得られた解において、x_i = 1 となっている商品をレコメンドメニューに追加
if (solution.getVariableValue('x' + i) == 1) {
recommended.push(all_items[i])
}
}
同様に、脂質、炭水化物についての制約も加えたモデルを解いたものを1日のメニューとしてレコメンドします。また、追加で以下の工夫を施しています。
同じ商品を許容すると毎日同じメニューが延々とレコメンドされてしまうので辛いという理由から取り入れています。今回は、1週間限定のプロジェクトだったので、1商品1回でも良いと判断しました。
今回定式化した問題は、1日単位でメニューを最適化するものであり、食べ過ぎたり食事を取れない日が発生した場合に、全期間を通してみると目標値との差が大きくなってしまうという問題への対処として実施しました。
GASでは、作成した関数に対してトリガーを設定できるので、定期実行も簡単に実現可能です。設定できるトリガーとしては、指定時間、スプレッドシートの編集時などいくつか種類がありますが、今回は毎日24:00時点で次の日のメニューをレコメンドするスクリプトを実施するようにトリガーを設定しました。以下はトリガーの追加方法です。
GASのエディタ画面から数回ポチポチするだけで追加でき、とても簡単です!
実際に1週間アプリを使用した関口からのフィードバックも含め、以下のような課題や要望がありました。
上記課題に関する量を集計してみると下記のようになりました。確かに品数のバランスは気になりますね。
UI/UXについては、以下のような要望があがりました。
今回構築したのはあくまでチャレンジのための簡易的なアプリでしたが、実際に 1 週間使ってもらったことで(仮に、今後本格的に展開していくような話があった時の)課題となりそうなものが見えてきました。例えばファミペイアプリとの連携や欠品などは、実際にアプリを使ったからこそ出てきた要求だと感じました。
今回は取締役の思いつきとノリのよいメンバで行った、「ファミマ 5days チャレンジ」の舞台裏を紹介しました。
今回は社内プロジェクトだったため、企画から開発完了まで約1ヶ月(しかも間に正月をはさむ)という、とても短いプロジェクトでしたが、企画から仮実装、ユーザーからのフィードバック、最後のチャレンジなど、各々が楽しみながら、当初のノリをキープしたまま進めることができました。
ちなみに実際の案件では、3ヵ月~半年程度かけてプロトタイプを開発し、実地検証に進むことが多いです。今回と比べて期間は長いですし、考慮すべき点も多いですが、実地検証の結果を踏まえて課題を明らかにし、更に改善を加え、というループを繰り返していくという流れは変わりません。今回のアプリもとても簡易的なものでしたが、徐々に改善していって、どこかのタイミングで世に出せたら面白いなと思います。
BrainPadでは、こんな仲間たちと一緒にクライアントのデータ活用を支援する仲間を募集しています!データサイエンティストもエンジニアも、興味のある方はお気軽にご連絡ください!
www.brainpad.co.jp
あなたにオススメの記事
2023.12.01
生成AI(ジェネレーティブAI)とは?ChatGPTとの違いや仕組み・種類・活用事例
2023.09.21
DX(デジタルトランスフォーメーション)とは?今さら聞けない意味・定義を分かりやすく解説【2024年最新】
2023.11.24
【現役社員が解説】データサイエンティストとは?仕事内容やAI・DX時代に必要なスキル
2023.09.08
DX事例26選:6つの業界別に紹介~有名企業はどんなDXをやっている?~【2024年最新版】
2023.08.23
LLM(大規模言語モデル)とは?生成AIとの違いや活用事例・課題
2024.03.22
生成AIの評価指標・ベンチマークとそれらに関連する問題点や限界を解説