ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

DSPyによるマルチモーダルタスクのプロンプト自動調整

こんにちは。R&Dチームの浅野です。

LLMを活用することで自然言語によってソフトウェアの挙動を変えることができるというのは改めてすごいことですね。OpenAIやGoogleなどが提供する商用APIはモデルのライフサイクルが速く、モデルが変わるごとにプロンプト調整が必要になることが多いため、プロダクトでLLMを活用した機能開発を進めていく上で辛いポイントの1つです。

DSPy は、LLMを活用したアプリを宣言的に記述することにより、プロンプト最適化をアルゴリズムに任せることができ、手作業による試行錯誤から解放してくれる、という夢のようなフレームワークです。 dspy.ai

DSPyの活用方法については多くの記事が出ています。そのほとんどがテキスト入出力のタスクであり、画像を入力とするマルチモーダルタスクにおいてプロンプトの最適化がどのように働くのかを記載した資料はあまり見つからなかったので、ここで書いていきたいと思います。

実験条件

なお、マルチモーダルLLMに人の数をカウントさせるのは執筆時点ではあまり筋が良くないので注意してください。専用の人物検出モデルを使ったほうがより安価で正確です。あくまでわかりやすい例としての実験設定です。

画像の取り扱い

DSPyはv3になって画像inputに対応しました。dspy.Image を使うことで裏側でよしなにLLMと接続してくれます。ローカルのOllamaで稼働しているGemma3 4bモデルで、画像に写っている人数を答えさせるフローは次のように記述できます。

import dspy
from PIL import Image

image = Image.open("image.jpg")

lm = dspy.LM('openai/gemma3:4b', api_base='http://localhost:11434/v1', api_key='not_needed')
dspy.configure(lm=lm)

program = dspy.ChainOfThought(
    "image: dspy.Image -> number_of_people: int"
)(image=dspy.Image.from_PIL(image))

データセット

アルゴリズムによってプロンプトを最適化するには、データセットが必要です。

  • 0人から10人(以上)写っている写真をそれぞれ5-8枚、合計68枚を準備
  • train/val/testで40/13/15枚にsplit
  • dspy.Exampleというデータ型に変換 Data Handling - DSPy

評価指標

GEPAは、タスクの成功度合いを示す評価指標だけでなく、実行時のトレースなどより詳細な情報を活用して最適化の精度を高めることができる手法です。ここでは、dspy.ChainOfThoughtモジュールを実行したときに出力に含まれるreasoningの内容を最適化に使うようにします。また、10人以上の場合は10人と答えて、というフィードバックも追加します。

def count_exact_match_with_feedback(example, pred, trace=None, pred_name=None, pred_trace=None):
    correctness = (example.number_of_people == pred.number_of_people)
    feedback_text = f"""Correct answer is {example.number_of_people}. 
    Your answer is {pred.number_of_people}. Your reasoning is: {pred.reasoning}. 
    """
    if correctness:
        feedback_text += 'Your answer is correct! '
    else:
        feedback_text += 'Your answer is incorrect. Think about how you could have correct answer. '
    if pred.number_of_people >= 10:
        feedback_text += 'If number of people in the image >= 10, answer 10.'

    return dspy.Prediction(score=correctness, feedback=feedback_text)

これにより、人数カウントが正解したかどうかだけでなく、タスクを実行するモデルがなぜそのような推論を行ったのか、という情報を活用することができるようになります。

例えば、正解が3人のところを4人と誤答した、という情報だけでは、なぜモデルが間違えたのかは想像するしかありません。「窓に写った人はカウントしないで」とか、「人形は含めないで」など当てずっぽうでプロンプトを改善していく必要があります。しかし、モデルのreasoningとして「画像の端に靴の一部が写っているため4人とした」といった内容が含まれている場合、「身体全体が写っている場合のみカウントする」という文言をプロンプトに入れることで精度が上がることが期待できます。

また、モデルが12人という回答をした場合には、10人以上だったら10人と回答すべきである、というフィードバックがあるため、そういった注意事項をプロンプトに適切に組み込むことができるようになります。

なお、執筆時点では、プロンプトを最適化するLLMは画像を直接「見る」ことはできません。あくまで上記のようなテキスト情報をもとにしてどのように修正すべきか判断をしていきます。上記ではタスク実行モデルのreasoning内容を使いましたが、それ以外にも画像の詳細なCaptioningをデータセットに含めることで、より精度を高めることができるかもしれません。将来的に最適化を実行するLLMがNativeにテキスト以外のInputを扱うことができるようなアップデートがあるとよいですね。

プロンプト自動調整

training budget = “light”の設定でGEPAによる最適化を実行しました。validation data + test dataの28枚で精度評価したところ、最適化前の精度 28.6%から42.9%に上昇しました。最終的に得られたプロンプトは下記です。

自動調整されたプロンプト(クリックして展開) You are an assistant tasked with accurately counting the number of people in an image.

Here's the detailed instruction:

  1. Systematic and Multi-Pass Image Analysis:

    Carefully analyze the entire provided image to identify all distinct human individuals. Employ a systematic scanning approach (e.g., left-to-right, top-to-bottom, then repeat) to ensure no person is missed.

    • First Pass (Obvious Individuals): Identify all clearly visible people in the foreground and midground.
    • Second Pass (Less Obvious Individuals - Be Extremely Thorough): Re-scan the entire image, mentally dividing it into sections (e.g., quadrants), and meticulously search for individuals who might be:
      • Partially obscured: Only a head, a limb, a shoulder, or a portion of their body is visible.
      • Distant or small: People who are far away and appear very small in the frame.
      • Blending with the background: Individuals whose clothing might make them less obvious.
      • Behind objects: People hidden by furniture, trees, vehicles, etc.
      • Reflections: Do NOT count reflections.
  2. Count and Verify the Total Number of People:

    • Sum up all the distinct human individuals you identify.
    • Perform a final verification pass to ensure each person is counted only once.
  3. Apply the Specific Capping Rule for number_of_people output:

    • If the total count of people is 10 or greater, the number_of_people should be exactly 10.
    • If the total count of people is less than 10, provide the exact, accurate count.
  4. Provide a reasoning: Clearly explain how you arrived at your count. This reasoning should:

    • List each identified person: Briefly describe their appearance and location.
    • Explicitly mention any partially obscured individuals that contributed to the count.
    • State the final actual total count (before capping).
    • If the capping rule was applied, explicitly mention it.

    Example of reasoning for a capped count: "The image shows a large group of people... I counted 12 people. As per the rule, if the number of people is 10 or greater, the answer is 10."

人間による介入が一切ないにも関わらず、プロンプトは非常によくできていると感じます。それでも精度は42.9%と十分ではありませんが、これは推論に使ったモデルGemma3 4bの能力の限界をこえるタスクをやらせようとしている、と解釈できます。実際、よりパラメータ数の大きいGemma3 27bモデルを使うと80%の精度が出ます。

一般にLLMアプリの精度が出ない場合、プロンプトや構築した処理フローが悪いのか、モデルの能力が足りないのかの切り分けは簡単ではありません。しかし、DSPyによって自動でプロンプトを追い込めるので、プロンプトで何とかなる問題なのか判断がつきやすいことは非常に大きなベネフィットです。プロンプトだけではどうにもならないのであれば、別のモデルにスイッチして再度最適化を実行するか、タスク分解を見直して処理フローを改善した上で最適化を実行する、といったアクションに移ることができます。

まとめ

画像に写っている人数をカウントする、というマルチモーダルタスクを通じて、DSPyのプロンプト自動調整が動く様子を確認しました。プロンプトを改善するLLMは(現時点では)画像を直接理解しているわけではなく、あくまでテキスト情報をもとにどのように修正すべきかを判断しています。ここでどんな情報を与えるかが精度に大きく効いてきそうです。

DSPyを活用することで、モデルを更新する必要がある場合にも、同じデータセットで最適化を再度走らせるだけ。もう手作業で試行錯誤する必要はなくなります。これでLLMを活用した機能開発が捗りますね!


ユニファでは一緒に働く仲間を探しています!

jobs.unifa-e.com