every Tech Blog

株式会社エブリーのTech Blogです。

SQLとLLMを用いた食トレンド予測

はじめに

1ヶ月間株式会社エブリーでデータサイエンティストとしてインターンをしている中村です。

私が配属された「デリッシュリサーチ」チームでは、デリッシュキッチンの膨大な検索ログデータを抽出・加工して、メーカー・小売の意思決定を支援しています。

本インターンでは、アプリ内の検索データから未来の「食トレンドワード」の予測に挑戦しました。

開発背景

食品業界では新商品の企画から販売までに時間がかかるため、企画の段階で「販売時期のトレンド」を正確に予測することがビジネスの成否を大きく左右します。

一般的に、トレンドが本格化するまでには、感度の高い層の検索行動などに「先行指標」が現れます。
そこで私たちは、「デリッシュキッチン」の膨大な検索ログデータにこのトレンドの”予兆”が現れるのではないか、という仮説を立てました。

今回のインターンでは、この仮説に基づきデータドリブンに未来のトレンドを予測するという課題に挑みました。

トレンド予測のパイプライン

今回作成したコードは月に1度実行され、前月までの検索データの推移をもとにトレンド予測を行います。
大きな流れとして、「1. SQLによる候補の抽出」と「2. LLMによる絞り込み」という2つのステップで構成しました。

1. SQLによる絞り込み

まず、SQLクエリを用いて、検索データ全体からトレンドの兆候を示す可能性のあるワードを絞り込みます。
ここでの目的は、再現率(Recall)を重視し、ポテンシャルのある単語を可能な限り拾い上げることです。

当初、仮説ベースでクエリを設計しましたが期待したような出力は得られませんでした。
そこで、過去のトレンドワードのデータを分析し、ブーム発生前の共通パターンを特定する帰納的アプローチに切り替えました。

過去のトレンドワードの流行のきっかけと推移を調査し、トレンド候補として取得したい時期を設定し各ワードがその時期に結果に含まれるようクエリを設計しました。

分析の結果、これらのワードには以下のような2つの特徴が共通することが判明しました。

  • 検索数が少ない:流行前は世間的に認知が低いため検索数が一般的な料理ワードと比較して少ない傾向にありました。
  • 検索頻度スコアの最大を更新:流行の兆しが見られているタイミングでアプリ内でも検索頻度スコアが過去最高を更新していたことが判明しました。
    (注) 検索頻度スコア:全検索ワード1000回あたりの特定のワードの検索回数

過去トレンド例:せいろ

過去トレンドの例として、せいろのトレンド推移を紹介します。
せいろは2024年9月にレシピ本が出版されたことをきっかけにブームとなり、デリッシュキッチン内でも急上昇を見せています。

しかし、トレンド化する予兆が全くなかったわけではありません。
2023年6月以前はほとんど検索されていなかったものの、インフルエンサーの投稿などから注目が集まり2023年7月~2024年1月の多くの月で検索頻度が過去最高を更新しています。

このように多くの過去トレンドワードでは大流行する前に先述した2つの特徴を持つトレンド化の予兆を示す時期があることが判明し、十分クエリで絞り込み可能と考えました。

先述した2つの条件をクエリに落とし込み候補を約1500件まで絞り込みました。

次に、この結果を分析したところ「バレンタイン」や「秋刀魚」といった季節性要因で検索が増加したワードが多数含まれていました。
これらはトレンドと異なるため周期的なパターンを検出するロジックを作成し、これらを除外する処理を追加しました。
この処理によってデータは約900件にまで絞り込めました。

後述するLLMでの絞り込みではデータ数に比例したコストがかかるため、絞り込んだ全てのワードを使うことはできません。
そこで昨年からの検索数の増加量を基準に並び替えを行い上位100件を"トレンドワード候補"として使用しました。

2. LLMによる絞り込み

クエリによる絞り込みでは正解の単語を確実に取得することを重視しているため、中にはデリッシュキッチンのSNS経由など他の要因で検索が増加した単語が含まれています。
そこで、各候補ワードの定性的な評価を行うため、LLMを用いた分類ステップを導入しました。

LLMには、Web検索機能を用いて各単語の背景(定義、メディアでの扱われ方、SNSでの話題性など)を調査させた上で、以下の5つのトレンドタイプに分類するタスクを実行させます。
この中でfuture(high)に分類されたワードを、最終的に使用します。

  • past : 過去に流行したもの
  • ongoing : 現在流行しているもの
  • future(low) : 今後流行する可能性があるが、現時点では限定的
  • future(high) : 流行の兆しがあり、今後大きなインパクトが期待されるもの
  • stable : 一過性の流行ではなく、社会に定着しているもの

最後に出力用にデータの整形を行います。

データや分析を提供する目的は、企業の意思決定支援です。
そのためには、単に単語リストを提供するだけでは不十分であり、そのワードの定義や分類の根拠を説明する必要があります。

先ほどの分類ステップでLLMには分類結果と同時に、その判断に至った具体的な理由や背景情報をテキストで生成させています。
その説明を入力にLLMに要約を作成させ、表示用の説明文としました。

ここでは具体例を掲載することはできませんが、韓国ブームや健康志向といったマクロな社会潮流と一致する単語を複数抽出できており、本手法の有効性を確認できました。

技術的な工夫

非同期処理の活用

LLMによる分類ステップでは、100件の候補ワードを処理する必要がありました。
当初、APIリクエストを同期的に逐一実行していたため、1ワードあたり約3分、全体で約5時間を要し開発イテレーションの大きなボトルネックとなっていました。
これでは、プロンプトチューニングを行う上でも実際の実行でも問題となります。そこでPythonの非同期処理を用いて並列でリクエストを送信しました。

ただし、OpenAI APIにはレート制限が存在します。
短時間にリクエストが集中するとエラーが返されるため、リトライ処理の実装が不可欠です。
今回はtenacityライブラリを活用し、リクエスト失敗時に最大6回まで再試行するロジックを組み込み、処理の安定性を確保しました。

これらの対応により、全体の処理時間を大幅に短縮でき、プロンプトチューニングや本番実行を短時間で行えるようになりました。

@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def call_gpt(search_word: str, prompt_template: str, schema: dict,date_formatted: str,recipe_master_attention:str, model_name: str = "gpt-5-mini-2025-08-07") -> tuple:
    try:
        prompt = prompt_template.format(research_word=search_word,date_formatted=date_formatted,recipe_master_attention=recipe_master_attention)
        response = await client.responses.create(
            model=model_name,
            tools = [{
                "type": "web_search",
                "user_location":{
                    "type": "approximate",
                    "country": "JP",
                    "city": "Tokyo",
                    "region": "Tokyo"
                },
            }],
            input=[
                {"role": "system", "content": "あなたは、食のトレンドを専門とするリサーチャーです。"},
                {"role": "user", "content": prompt},
            ],
            text = schema
        )
        res_dict = json.loads(response.output_text)
        res_dict["search_word"] = search_word
        return res_dict, response.usage
    except Exception as e:
        print(f"❌ Failed to analyze word: {search_word}, Error: {e}")
        raise e

分類根拠の説明

OpenAI APIは構造化出力をサポートしており、指定したスキーマでレスポンスを受け取ることができます。
これを利用して、トレンドタイプに加えてその分類の根拠もテキスト形式で出力させています。

分類根拠を出力させることでLLMの推論の過程を理解することができ、プロンプトチューニングが効率化されるだけでなく、その内容を要約してクライアント向けの説明文を生成することも可能になりました。

今後の課題

今回の分析である程度期待した精度の出力を得ることに成功しましたが、予測精度と提供価値をさらに高めるために、2つの改善点が考えられます。

LLMは検索データを考慮していない

現状、LLMによる分類のステップでは、プロンプトにアプリ内の検索数の推移を含めていません。
LLMに検索増という事実だけでなく生のデータを与えることで、検索増の要因の考察の精度が上昇することが期待されます。

過去の予測を考慮した出力

クライアントである企業にとっての価値は、「まだ見ぬトレンドの種」をいち早く知ることです。
その点で、過去に提示した単語が数ヶ月後に再び表示されると、「新しい発見がない」という印象を与えかねません。
現在のクエリでは、一度候補に入ると3ヶ月間は必ず"トレンド候補"となります。この期間が適切であるかは考慮する必要があります。
対策として、一度予測として提示した単語をフィルタリングするといった出力制御ロジックを組み込むことで、常に新鮮で多様な「未来のヒント」を提供できるようになると考えています。

まとめ

アプリ内の検索データをもとに、SQLを用いた定量的な候補抽出とLLMを用いた定性的な評価によって食トレンドを予測する機能を実装しました。
今回の実装は、LLMのweb検索機能を使用しているため、過去データでの性能検証ができません。
現在の予測が正しいかは数ヶ月後になってみないとわかりません。
予測には海外で流行している料理などもあり、今後日本で話題となることを期待しています。

今回のインターンでは、丁寧なコードレビューや毎日のフィードバックを元に、開発を改善しひとつの機能を実装することができました。
膨大なデータから価値を創造した体験を経て、データサイエンティストとして働く上での解像度が劇的に高まりました。
特に整備されたデータ基盤のもと試行錯誤を繰り返したことで、技術的に成長し、普段の勉強や研究では得られないような業務上の知識を多く得られました。