技術資料:嗜好学習と多目的最適化を用いたパーソナライズ献立推薦システム 

#html

iframe src="(http://dic515s1.pu-toyama.ac.jp/IIE.IS.WIKI/250605_wiki_lab.php?plugin=attach&pcmd=open&file=interactive_report.html&refer=%E8%BE%BB%E3%81%95%E3%82%93%E5%8D%92%E8%AB%96)" width="100%" height="800px" style="border:1px solid #ccc;"></iframe>

↑技術資料これです

目次 

1. 目的 

食に対する個人の嗜好は、特定の食材の好みから調理法、食事の時間帯に至るまで、非常に多様である。従来の推薦システムは栄養バランスやコストといった静的な指標を主軸としていたが、これだけではユーザー一人ひとりの満足度を最大化することは難しい。本研究では、ユーザーからの評価履歴を基に、コンテンツベースフィルタリング(TF-IDF)を用いてユーザーの「味の好み」を学習する嗜好モデルを構築する。そして、この嗜好モデルと多目的最適化(遺伝的アルゴリズム NSGA-II)を組み合わせることで、「コスト」「調理時間」「ユーザーの好み」といった、時に相反する複数の目的を同時に満たす、パーソナライズされた献立を推薦するシステムの構築を目的とする。

2. システム概要 

本システムは、ユーザーの嗜好を学習する機械学習エンジンと、最適な献立を探索する多目的最適化エンジンから構成される。全体の処理フローは以下の通り。

ユーザー情報入力: 2.1献立作成(Content-Based Filtering).py のGUIを通じて、ユーザーは身体情報、アレルギー、健康上の懸念、および献立作成で重視する目的(例:「コスト」と「好みスコア」)を入力する。

レシピデータのフィルタリング: システムは、入力されたアレルギー・病気の制約に基づき、約400件のレシピデータベースから不適切なレシピを予め除外する。

嗜好モデルの構築:

多目的最適化による献立生成:

結果の出力と可視化:

3. 実際の人間が利用する場合の想定フロー 

献立生成の実行: ユーザーは 2.1献立作成(Content-Based Filtering).py を実行し、GUIの案内に従って自身の情報や要望を入力する。

Webサーバーの起動: 献立生成が完了した後、server1(GraphicalRecipes).py を実行し、ローカルWebサーバーを起動する。

献立の確認と比較: ユーザーはWebブラウザで http://127.0.0.1:5000 にアクセスし、提案された複数の献立候補を比較検討し、その日の気分に合ったものを選択する。

調理と食事: 選択した献立を調理し、食事を楽しむ。

評価とフィードバック: (将来的な拡張)食事後、ユーザーはWebアプリケーション上で各レシピや献立全体の満足度を評価する。この評価は cdijnklmn_extracted_with_headers.csv に記録される。

継続的な学習: 次回、ユーザーが献立を生成する際には、更新された評価ファイルが読み込まれる。このループを繰り返すことで、システムはユーザーの好みをより深く学習し、提案の精度を継続的に向上させていく。

4. 使用するファイル全部 

扱うデータ用途ファイル名ファイルの場所
システム制御GUIによるユーザー設定、嗜好学習、多目的最適化による献立生成2.1献立作成(Content-Based Filtering).py/code
Webサーバー生成された献立をブラウザで表示するためのWebサーバー機能2.1server(Content-Based Filtering).py/code
データ前処理(任意)レシピデータに時間帯フラグを自動付与するユーティリティadd_flags.py/code
実験用(任意)実験結果の評価CSVを自動生成するユーティリティcreate_feedback.py/code
実験用(任意)実験結果を分析・集計するユーティリティanalyze_results.py/code
入力データ各レシピの栄養素・コスト・タグ等の情報recipe_noX.csv/code/data/hyouka/
設定データGUIで入力したユーザー情報やアレルギー設定を保存menu_creation_settings.json/code
学習データユーザーの評価履歴を記録し、嗜好学習に利用cdijnklmn_extracted_with_headers.csv/code
出力データ生成された献立候補群の詳細情報をJSON形式で保存all_details.json/code/static/
出力データ3Dグラフ描画用のノード・リンク情報graph_data.json/code/static/
出力データ献立の日数をWebサーバーに渡すための中間ファイルparams.json/code/static/
出力データ遺伝的アルゴリズムのパレート解の分布を可視化した画像palate.png/code
Webページ用テンプレート3Dグラフを表示するメインページのHTMLgraph_viewer.html/code/templates/
Webページ用テンプレート献立詳細を表示するHTMLdetails_template.html/code/templates/


5. システムの実行方法 

事前準備(初回のみ推奨)

ライブラリのインストール: ターミナルで pip install pandas numpy pymoo PySimpleGUI scikit-learn matplotlib を実行する。

時間帯フラグの付与: python add_flags.py を実行し、各レシピデータに時間帯情報を自動で付与する。

献立データの生成

python "2.1献立作成(Content-Based Filtering).py" を実行する。

表示されるGUIの案内に従い、人数、身体情報、アレルギー、重視する目的などを入力する。

Webサーバーの起動

献立生成が完了したら、python server1(GraphicalRecipes).py を実行する。

ブラウザで確認

ターミナルに表示されるURL(例: http://127.0.0.1:5000)にウェブブラウザでアクセスする。

6. 使用アルゴリズムの理論的背景 

本システムは、目的を達成するために、大きく分けて2つのアルゴリズムを中核技術として利用している。

6.1. 嗜好学習:コンテンツベースフィルタリング(TF-IDF & コサイン類似度) 

ユーザーの嗜好を学習するため、本システムではコンテンツベースフィルタリングの手法を採用している。これは、ユーザーが過去に高く評価したアイテムと「内容が似ている」アイテムを推薦するアプローチである。

特徴のベクトル化(TF-IDF): まず、各レシピを「食材」と「タグ(例:主菜, is_breakfast)」の集合として捉える。しかし、コンピュータは単語のままでは類似性を計算できないため、TF-IDF (Term Frequency-Inverse Document Frequency) を用いて、これらの特徴を数値ベクトルに変換する。

類似度の計算(コサイン類似度): 次に、ユーザーの評価履歴(cdijnklmn_extracted_with_headers.csv)を基に、「ユーザーの好みプロファイルベクトル」を生成する。これは、高評価したレシピのベクトルを足し合わせ、低評価したレシピのベクトルを引き算するような形で合成される。 最後に、このプロファイルベクトルと、全レシピのベクトルとのコサイン類似度を計算する。これは、ベクトル同士の「向き」がどれだけ似ているかを示す指標であり、この類似度が高いレシピほど、ユーザーの好みに合致する可能性が高いと判断され、高い「好みスコア」が与えられる。

6.2. 多目的最適化:NSGA-IIとパレート最適解 

献立作成は、単一の目的だけでは評価できない複雑な問題である。本研究では、以下の目的を同時に最適化する必要がある。

目的1: コスト をできるだけ安くしたい (最小化)

目的2: 調理時間 をできるだけ短くしたい (最小化)

目的3: 好みスコア をできるだけ高くしたい (最大化)

これらの目的は互いに「トレードオフ」の関係にある。このような問題では、全ての目的で最良となる「唯一の完璧な解」は通常存在しないため、「どの解にも支配されていない、優秀な解」の集合である**「パレート最適解」**を求めることがゴールとなる。

本研究では、このパレート最適解を効率的に探索するため、遺伝的アルゴリズムの一種であるNSGA-II (Non-dominated Sorting Genetic Algorithm II) を採用した。NSGA-IIは、生物の進化を模倣し、「交叉」や「突然変異」といった操作を繰り返しながら、解の集団全体を徐々に真のパレートフロントへと進化させていく。

7. 主要プログラムの詳細解説 

(このセクションでは、2.1献立作成(Content-Based Filtering).py や server1(GraphicalRecipes).py などの主要なスクリプトについて、コードの構成や各関数の役割を一つ一つ詳細に記述します。)
7.1. 2.1献立作成(Content-Based Filtering).py 役割 このスクリプトは、本システムの中核をなすメインエンジンである。以下の機能を順次実行する。

GUIを通じてユーザーから個人情報や要望(制約条件)を受け取る。

全レシピデータから、ユーザーの制約に基づき不適切なものを除外する。

ユーザーの過去の評価履歴を基に、機械学習(TF-IDF)を用いて各レシピの「好みスコア」を算出する。

遺伝的アルゴリズム(NSGA-II)を用い、「コスト」「時間」「好みスコア」といった複数の目的を同時に満たす最適な献立の組み合わせを複数探索する。

最終的な献立候補を、Webアプリケーションで可視化できるJSON形式で出力する。

プログラムの構成と処理フロー このスクリプトは、大きく分けて8つの論理的なステップで構成されている。

ステップ1: 初期設定とライブラリのインポート

import PySimpleGUI as sg import pandas as pd import numpy as np import json import os import re from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.core.problem import ElementwiseProblem from pymoo.optimize import minimize

# (その他、標準ライブラリ)

SETTINGS_FILE = 'menu_creation_settings.json'

ステップ2: ユーザー情報の入力 (GUI)

# (layout1, layout2, ... といったGUIのレイアウト定義)

# ユーザーごとの情報入力ループ for i in range(ninzu):

   # ... (個人情報入力ウィンドウの表示と値の取得) ...
   
   # 日本人の食事摂取基準に基づき、個人の推定エネルギー必要量(EER)を計算
   # 例: 30-49歳男性、身体活動レベル「普通」の場合
   # 基礎代謝基準値: 22.3, 身体活動レベル: 1.75
   # EER = 22.3 * 体重(kg) * 1.75
   eer = fma * actlevel 
   
   # PFCバランスの目標値を計算 (例: たんぱく質13%, 脂質20-30%, 炭水化物50-65%)
   rtanpakulist.append((eer * 0.13) / 4) # たんぱく質 (g)
   rsisitulist.append((eer * 0.20) / 9) # 脂質 (g)
   rtansuilist.append((eer * 0.50) / 4) # 炭水化物 (g)
   # ... (アレルギー・病気入力ウィンドウの表示と値の取得) ...

ステップ3: 食材データベースの構築とアレルギー検索の準備

# 全レシピの食材情報を格納するリスト qij = [['' for _ in range(25)] for _ in range(R)]

# 全レシピファイルをループし、食材名(5列目)のみを抽出 for j in i_range:

   try:
       df = pd.read_csv(...)
       for m in v:
           qij[j][m] = df.iloc[m, 4]
       # ... (不要な文字列の除去) ...
   except:
       continue

# 検索用のDataFrameを作成 pd_name = pd.DataFrame(data=qij, index=i_range) pd_name.insert(0, 'name', yi) pd_name.insert(0, 'number', i_range)

# 特定の食材を含むレシピ番号をリストアップする関数 def kensaku(x, y):

   c = []
   for j in range(24):
       a = pd_name[j].str.contains(x, na=False)
       b = pd_name.loc[a, 'number']
       c = b.values.tolist()
       y.extend(c)
   return y

ステップ4: レシピデータベースの構築とフィルタリング

# レシピ詳細を格納するリストを初期化 recipe_details_list = [] for idx in range(R):

   details = {
       'original_index': idx, 'レシピの名前': yi[idx], '主菜フラグ': sigmai[idx],
       # ... (コスト、時間、栄養素など全データを格納) ...
       'meal_type_flag': meal_type_i[idx] 
   }
   recipe_details_list.append(details)

# 全情報を格納したマスターDataFrameを作成 df_recipe = pd.DataFrame(recipe_details_list)

# アレルギー・病気情報に基づいて除外するレシピのインデックスを取得 if arerugi: # arerugiリストには除外すべきレシピ番号が格納されている

   indices_to_drop = df_recipe[df_recipe['original_index'].isin(arerugi)].index
   df_recipe.drop(indices_to_drop, inplace=True)
   df_recipe.reset_index(drop=True, inplace=True)

ステップ5: 嗜好学習モデルの構築 (TF-IDF)

# 1. 各レシピの特徴をテキスト化 corpus = [] for i, recipe_row in df_recipe.iterrows():

   ingredients = [ing for ing in qij[recipe_row['original_index']] if str(ing) not in ["nan", "NAN", ""]]
   context_tags = []
   if recipe_row['主菜フラグ'] == 1:
       context_tags.append("is_main_dish")
   if recipe_row['meal_type_flag'] == 1:
       context_tags.append("is_breakfast")
   # ... (他のタグも同様に追加) ...
   full_features = ingredients + context_tags
   corpus.append(" ".join(full_features))

# 2. TF-IDFでベクトル化 vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform(corpus)

# 3. 評価履歴から好みスコアを計算 if os.path.exists(feedback_file):

   feedback_df = pd.read_csv(feedback_file, ...)
   # ... (高評価/低評価レシピのベクトルを重み付けして合成し、user_profile_vectorを作成) ...
   
   # 4. コサイン類似度を計算
   taste_scores = cosine_similarity(tfidf_matrix, user_profile_vector).flatten()

ステップ6: 多目的最適化問題の定義 (Pymoo)

class SubsetProblem(ElementwiseProblem):

   def __init__(self, obj1_vals, obj2_vals, n_max, ...):
       # 目的関数の数=2, 制約条件の数=5 で初期化
       super().__init__(n_var=len(obj1_vals), n_obj=2, n_constr=5, **kwargs)
       # ... (コスト、時間、カロリーなどのデータをインスタンス変数として保持) ...
   def _evaluate(self, x, out, *args, **kwargs):
       # xは、レシピを選ぶ(True)/選ばない(False)を示すブール配列
       
       # 目的関数の計算
       # obj1_valsはコストのリスト、obj2_valsは好みスコアのリストなど
       f1 = np.sum(self.obj1_vals[x])
       f2 = np.sum(self.obj2_vals[x])
       out["F"] = [f1, f2]
       # 制約条件の計算 (違反している場合に正の値になるように設計)
       # g1: たんぱく質が目標値に達しているか
       g1 = self.tanpakumin * self.day - np.sum(self.f0[x])
       # g2: 脂質が目標値に達しているか
       g2 = self.sisitumin * self.day - np.sum(self.f1[x])
       # g3: 炭水化物が目標値に達しているか
       g3 = self.tansuimin * self.day - np.sum(self.f2[x])
       # g4: カロリーが目標値-200kcalより低いか (下限)
       g4 = (self.eer * self.day) - 200 - np.sum(self.cal[x])
       # g5: カロリーが目標値+200kcalより高いか (上限)
       g5 = np.sum(self.cal[x]) - ((self.eer * self.day) + 200)
       out["G"] = [g1, g2, g3, g4, g5]

ステップ7: 遺伝的アルゴリズムの実行

# アルゴリズムのインスタンスを生成 algorithm = NSGA2(

   pop_size=R, # 集団のサイズはレシピ数と同等に設定
   sampling=MySampling(),
   crossover=BinaryCrossover(),
   mutation=MyMutation(),
   eliminate_duplicates=True

)

# 最適化を実行 res = minimize(

   problem,
   algorithm,
   ('n_gen', gen), # 世代数(計算回数)を指定
   seed=1,
   verbose=True

)

ステップ8: 献立の最終組み立てとJSON出力

# パレート解のループ for idx, p_indices in enumerate(parate):

   # STEP 1: レシピを時間帯ごとに仕分ける
   breakfast_pool, lunch_pool, dinner_pool, other_pool = [], [], [], []
   for recipe_index in p_indices:
       flag = df_recipe.iloc[recipe_index]['meal_type_flag']
       if flag == 1: breakfast_pool.append(recipe_index)
       # ... (昼食、夕食、その他も同様) ...
   
   # STEP 2: 時間帯を考慮して献立を組み立てる
   daily_menus = []
   for d_idx in range(day):
       day_menus_split = {"朝食": [], "昼食": [], "夕食": []}
       # 朝食の割り当て (専用プールを優先し、足りなければ予備プールから補充)
       if breakfast_pool:
           day_menus_split["朝食"].append(breakfast_pool.pop(0))
       elif other_pool:
           day_menus_split["朝食"].append(other_pool.pop(0))
       # ... (昼食、夕食も同様に割り当て) ...
       
       # STEP 3: 詳細情報を取得してJSON形式にまとめる
       # ... (レシピ名や栄養素、画像URLなどを取得し、整形) ...
   
   all_candidates_details.append(...)

# 最終的なJSONファイルへの書き出し with open('static/all_details.json', 'w', ...) as f:

   json.dump(all_candidates_details, f, ...)

8. 実験設定と結果 

構築した献立推薦システムの有効性を検証するため、特性の異なる2名の仮ユーザーを対象としたシミュレーション実験を行った。

8.1. 実験1:制約条件の遵守テスト 

- 目的: ユーザーが設定したアレルギーや健康上の制約を、システムが確実に遵守できるか検証する。

- 実験設定:

- 結果: ユーザーAの設定で3日分の献立を生成させ、提案された全21品のレシピを検証した結果、アレルギー食材である「卵」を含むものは0件、塩分量が基準値2.0gを超えたものも0件であり、制約遵守率は100%となった。

検証項目総レシピ数違反件数制約遵守率
:---:---:---:---
卵アレルギー21品0品100%
高血圧(塩分≦2.0g)21品0品100%

- 考察: この結果から、本システムのアレルギー・病気情報に基づくレシピのフィルタリング機能が、ユーザーの安全性を確保する上で、極めて正確かつ確実に動作していることが実証された。

8.2. 実験2:嗜好学習能力の検証 

- 目的: ユーザーからの評価フィードバックに基づき、システムの提案内容が改善されるかを定量的に検証する。

- 実験設定:

- 結果: 学習前後で提案された献立候補群における「好み合致率」を算出した結果、以下の表に示す通り、顕著な改善が見られた。

評価対象提案レシピ総数好み合致レシピ数好み合致率
:---:---:---:---
学習前42 品4 品9.52%
学習後189 品46 品24.34%

- 考察: 学習前に9.52%であった好み合致率は、一度のフィードバック学習を経ることで24.34%へと約2.5倍に向上した。24.34%という絶対値は一見して高いとは言えないかもしれない。しかし、これはシステムが①栄養バランス、②コスト、③好みスコアという、時に相反する複数の目的の最適なバランス点を探索した結果である。特に「コスト」を抑えるという要求は、特定の食材を必要とする「好み」の実現とトレードオフの関係にある。このような多角的かつ厳しい制約下で、一度の学習で好みの反映率が2.5倍に向上したという事実は、本システムの嗜好学習メカニズムが有効に機能していることを明確に示している。

8.3. 総合結論 

以上の実験結果から、本研究で開発したシステムは、ユーザーの安全性を担保する制約遵守能力と、対話を通じて提案の質を向上させる嗜好学習能力を両立していることが実証された。これは、本システムが単なるレシピ推薦に留まらず、各ユーザーに寄り添い、継続的に最適化されていくパーソナライズド・システムとしての有効性を持つことを示唆するものである。


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS