食品成分表のデータをLlamaIndexで与えた上で、GPT-4.0でレシピを考えてもらい、成分表で合致する食材を抽出して出力する
あれこれやってはみたもののあんまり上手くいかなかったので、適当に与える程度では上手くいかなさそうだよというのがこの記事の趣旨です。覚え書きみたいなもんだが。
🔍 目的
GPT-4.0で「卵焼き」のレシピを教えてなどとするといい感じに食材と重量と作り方を出力してくれるのですが、詳細な栄養価については知識がないので出力できません。日本標準食品成分表(以下、食品成分表と呼称)について概要はわかるが、個別のデータがない。でも、栄養価計算などを行おうと思ったら、それら食品に関する知識というのは必要不可欠です。
そこでデータを与えられるLlamaIndexというライブラリを利用して、食品成分表のデータを与えたらどうなるかというのが今回の目的でした。
📗 参考にした記事
LlamaIndexは簡単に言えば、データをインデックス化した上で外部からEmbedded、埋め込むことのできるライブラリであり、適用することでGPTが食品成分表のデータを利用して回答を返してくれるようになります。
データを与える方向では考えていたもののいい感じの解決策がなかったのですが、この記事を読んで必要としていたのはまさにこれだと思いました。
💻 適用したコード
といっても、ほぼ上記の記事のコードを参考に部分的に作り替えている程度です。
import os
# 環境設定
os.environ["OPENAI_API_KEY"] = "自分のAPIキーを入れる"
# ログ出力
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True)
from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, LLMPredictor, PromptHelper, ServiceContext, QuestionAnswerPrompt, download_loader
from langchain.chat_models import ChatOpenAI
# data_textディレクトリ読み込み
documents = SimpleDirectoryReader("data_text").load_data()
# LLMやパラメータをカスタマイズ
llm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0, model_name="gpt-4"))
max_input_size = 8000
num_output = 248
max_chunk_overlap = 0.1
prompt_helper = PromptHelper(max_input_size, num_output, max_chunk_overlap)
service_context = ServiceContext.from_defaults(
llm_predictor=llm_predictor,
prompt_helper=prompt_helper
)
index = GPTVectorStoreIndex.from_documents(
documents,
service_context=service_context
)
# プロンプトのテンプレート
RECIPE_PROMPT_TMPL = (
'''
あなたは管理栄養士です。下記の食品成分表のデータをもとにして質問に答えてください。
--------------------\n
{context_str}" #コンテキストとして食品成分表のデータが与えられる
--------------------\n
次のような段階を踏んでレシピを提示してください。
段階を終えた上で次の段階に進むようにしてください。
1. {query_str}の一般的なレシピを考えてください。
生の食材及び調味料を利用して作ることが前提です。
この時点では与えられた食品成分表のデータは無視をして、GPTの側で一般的と考えられるレシピを提示してください。
2. レシピの食材を与えたデータと照らし合わせて該当する食品を抽出してください。
検索にはfoodNameを利用して、その他のキーは参考程度にしてください。
参考にというのは、塩を利用するのに醤油が選ばれるなどといったことを回避するのに、栄養価を参考にしてくださいという意味です。
また、味付けなどに利用される調味料は<調味料類>と文字列に含まれるオブジェクトを利用してください。
漢字表記されているものは読み方も考慮した検索を行うようにしてください。
また名称に正規表現で利用される<>などの記号が含まれる場合があり、クエリの結果に影響を及ぼす可能性があるため、
これらを考慮した上で正しく抽出できるように抽出してください。
3. 検索された項目の中から一般的に利用されていると考えられる食材を優先して利用するようにしてください。
たとえば、卵焼きで砂糖を使う場合に、黒砂糖よりはてんさい糖や上白糖が使われるはずです。
なお、醤油はこいくちしょうゆ、塩は調味料類の食塩を利用するようにしてください。
4. 使用した食材は一般的に卵焼きで利用される食品と言えそうでしょうか?
またもし異なるのであれば、より適切な食品に置き換えて再度提示してください。
不適切な食品が選ばれている場合は、次回の抽出時にその食品は含めないクエリで再度抽出を図り、同じ食品が抽出されないようにしてください。
また、foodNameには正規表現で利用される<>などの文字列が含まれることから、
クエリに影響を及ぼす可能性があるため、影響が出ないような対応をとるようにしてください。
なお、栄養価が含まれていないことがありますが、優先するべきは一般的に利用される食品であるかどうかになります。
5. これらの段階を経て最終的に抽出されたレシピが、1.で示された一般的なレシピに合致しているかを改めて判定してください。
沿っていなければ再度2.から抽出するようにしてください。
6. 抽出された食材の食品IDと各食材の使用量をグラム重量(g)で表記してください。
7. 最後に抽出されたfoodIdのfoodNameがレシピの材料として適切であるかを再度確認して、
不適切であれば3.から再抽出、適切であれば完了としてください。
'''
)
RECIPE_PROMPT = QuestionAnswerPrompt(RECIPE_PROMPT_TMPL)
query_engine = index.as_query_engine(similarity_top_k=50, text_qa_template=RECIPE_PROMPT)
# 例: 「低カロリーで高タンパクの食材を使ったレシピを教えてください」というリクエストに対する処理
response = query_engine.query("卵焼き")
print(response)
プロンプトエンジニアリング全くわかってないのだが、GPT自身に出力した結果を再度検証させないとよろしくないと思い始め、試行錯誤した結果このような形になりました。(といって、これでどれだけよくなったのかというと微妙)
📊 結果
スクショ撮ってなかったパターン(ポンコツ)
そのうち載せます
❓問題
GPTからレシピに含まれると考えられる材料の返答はいい感じなのだけど、それを食品成分表に収載されている食品と紐づけると、明らかにそうじゃないという食品が抽出されてしまうことが挙げられます。
食塩に対して、カップ麺の減塩タイプが抽出されたり🙄
💡 対応
問題への対応として、クエリで抽出される食品のデータの数を増やしてみたり、そもそもレシピに一般的に使われる食品のみに元のデータを削ってみたり、プロンプトでGPT自身に出力した結果を検証させてみたり。
// similrity_top_kを50個にしてみた
index.as_query_engine(similarity_top_k=50, text_qa_template=RECIPE_PROMPT)
部分的にいい感じになった部分もあれば、そうでない部分も依然として残っておりという感じでした(スクショ撮っていないのがあれなんだが)
本当なら何度も試したいんですが、API利用するのお金かかるんですよね、、、結果が出せてこれでいける!と思えるならいいんですけど、試している段階でお金を消費していく一方(白目)
余談
ちなみに調べる目的は上記に書いたとおりですが、背景としては個人開発しているアプリに適用したいというのが根本にあります。
アプリの機能として、食材を組み合わせてレシピを作成し、そのレシピの栄養価から特定の指標の算出を行う機能を設けているのですが、基本的にユーザー側が考えて入力して値を確かめて調整していくという操作をせざるを得ないという難点がありました。
各食材の栄養価を知るために食品成分表の食品と紐付けさえできたら、そのレシピの栄養価や、栄養価から算出される特定の指標も表示できるようになります。この紐付けをクライアント側で行うのは個人ではなかなか難しく思えて、これだけいい感じにレシピを返してくれるのだから、むしろGPTに食品成分表のデータを与えて該当の食品として抽出してもらった方がいいのでは?と思っていました。
この辺りまでは考えていたのですが、ではどのように与えるかといったところでリーズナブルな解決策が見当たらず止まっていました。Twitterで流れてきたEmbeddedと書かれた記事を読んだときに、「あ、これだ」と思って手を出した次第です。
ちなみに与える食品成分表のデータをどこから得たかというと、下記。公開されているのまじで感謝です。部分的に値の表記に誤りがあったので、プルリク出せたらいいなぁと思っている。