2026年5月の半ば、運営している iOS / Android の壁紙アプリの「画像カテゴリ自動分類」パイプラインを少しずつ作り変えていました。きっかけは、Gemini 3 Pro の安定度が日に日に上がってきて、2.5 Pro と比べて何がどれくらい違うのかを自分の現場の数字で確かめたくなったことです。
個人で壁紙アプリを長く運営していると、モデルを乗り換える判断はベンチマーク記事よりも自分の運用ログに従うほうが外さない、というのが実感です。今回も 3 Pro と 2.5 Pro のどちらかを先に選ぶのではなく、3週間まるごと両方を並行運用して、同じ画像に対して2つのレスポンスを並べてログに残す——という愚直なやり方を選びました。本稿はその実装メモです。
なぜ並行投入という形を取ったか
社外向けのベンチマーク数字よりも、自分のドメイン(壁紙画像)で起きる挙動の差を測るほうが、開発判断には直接効きます。
特に壁紙アプリのカテゴリ分類は、写真・イラスト・抽象・パターン・ミニマル・宇宙・自然・幾何学のように互いに近接するカテゴリが多く、人間が見ても揺れる境界が存在します。ここで「3 Pro のほうが偉い」と言い切るには、同じ画像で同じプロンプトで同じ温度のレスポンスを2つ取り、人が見て差分の質を判断する必要があります。
並行運用のための構造は単純で、Cloud Run 上の Python ジョブから google-genai SDK を 2回叩き、結果を BigQuery に model_id 列付きで突っ込むだけです。リクエスト単価が安いので、3週間で 2.5 Pro + 3 Pro 合計 約11万枚を流して、追加コストは数千円台に収まりました。
from google import genai
from google.genai import types
client = genai.Client(api_key=API_KEY)
def classify(image_bytes: bytes, model: str) -> dict:
resp = client.models.generate_content(
model=model,
contents=[
types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg"),
"この画像を以下の8カテゴリのいずれか1つに分類し、JSON で返してください。"
"{category, confidence(0-1), reasoning(40字以内)}",
],
config=types.GenerateContentConfig(
response_mime_type="application/json",
temperature=0.2,
thinking_config=types.ThinkingConfig(thinking_budget=0)
if model.startswith("gemini-2.5") else None,
),
)
return resp.parsedthinking_budget=0 を 2.5 Pro 側だけに付けているのは、Pro 同士で挙動を揃えるためです。3 Pro はデフォルトで控えめな思考を入れるので、ここで差が出にくくなるよう調整しました。
3週間で見えた3つの差
精度・コスト・遅延の3軸で、3週間ぶんのログを並べたときに気付いたことを整理します。
ひとつ目は、境界カテゴリの強さです。3 Pro は「ミニマルかつ自然」「幾何学かつ抽象」のような中間領域で、より安定した第1候補を出してきました。2.5 Pro は中間領域で confidence が 0.55〜0.7 あたりに集中しがちで、人手でレビューに回したいケースが約13%ありました。3 Pro 単独では同じ箇所のレビュー率が約7%でした。
ふたつ目は、reasoning の自然さです。これは数字ではなく読み心地の話で、3 Pro の reasoning は「淡い青の被覆と中心の余白」のように観察的な短文を返す傾向が強く、2.5 Pro は「ミニマル」「抽象」のような単語の羅列に寄りがちでした。アプリ内の管理画面に reasoning を「分類根拠」として表示している身からすると、3 Pro のほうが運営側のレビュー判断が速くなる感覚があります。
みっつ目は、遅延の体感です。1枚あたりの p50 レイテンシは 2.5 Pro が約 1.8 秒、3 Pro が約 2.1 秒で、3 Pro が 15〜20% ほど遅い印象でした。並列に20本走らせれば吸収できるので、バッチ用途では問題になっていません。リアルタイムにユーザーが操作するフローでは、まだ 2.5 Pro Flash も併用する余地が残っていると感じます。
ぶつかった壁と、運用で吸収したこと
並行運用の途中、結果の不一致が一定割合で発生したときに、どちらを採用するかを決める「タイブレーカ層」をどう書くかで、私自身、何度か実装をやり直しました。個人開発だと、こうした地味な調整こそ後から一番効いてきます。
最初は両モデルの confidence を比較して高いほうを採用する単純な実装にしていたのですが、3 Pro の confidence が控えめに出る傾向があり、3 Pro の reasoning のほうが明らかに良くても 2.5 Pro 側に倒れてしまうケースが目に付きました。AdMob のリワード動画でアンロックされる「テーマ別ロックフォルダ」のラインナップに、これが影響することが分かったのは2週目の終わりです。
最終的には、confidence 差が 0.15 未満のときは 3 Pro を優先、それ以外は数値どおり、というドメイン依存のルールに落ち着きました。万能ではないですが、サンプリングでレビューした 500 件では人間の判断と整合する割合が 91% まで上がりました。
このタイブレーカ層を書いていて改めて感じたのは、汎用ベンチマークの優劣と、自分のドメインで実際に効くチューニングは別物だということです。壁紙という狭い領域では、公開スコアの数ポイント差よりも、confidence の出方の癖を運用ルールに織り込むほうがずっと効きました。confidence 差のしきい値(今回は 0.15)も、最初から決め打ちにはできず、500件のサンプルレビューと突き合わせながら少しずつ動かして合わせ込んでいます。
ログを溜める側のテーブル設計
並行運用で一番効いたのは、結果を後から比較できる粒度で記録しておいたことです。BigQuery 側は次の最小スキーマで運用していました。
CREATE TABLE wallpaper_classification (
image_id STRING NOT NULL,
model_id STRING NOT NULL, -- gemini-3-pro / gemini-2.5-pro
category STRING NOT NULL,
confidence FLOAT64 NOT NULL,
reasoning STRING,
latency_ms INT64,
cost_usd NUMERIC,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);image_id + model_id で複合主キー相当に扱い、同じ画像に対する両モデルの判定が常に2行で残るようにしています。cost_usd 列を入れているのは、3週目で「実際に分類1枚あたりのコストはいくらだったか」を、思考トークン込みで実測したかったからです。
実測値は、2.5 Pro が画像1枚あたり約 0.0026 USD、3 Pro が約 0.0034 USD でした。3 Pro の単価は 30% ほど高いものの、レビュー率が 13% → 7% に下がった分の人件工数を考えると、運用全体の単価としてはむしろ下がっている印象です。AdMob のリワード経由でアンロックされる「特集テーマ」を組むときに、分類精度の高さが直接ラインナップ精度につながるので、ここは数字以上の効きがあります。
今後どう運用していくか
3週間の並行投入を経て、しばらくは「3 Pro を主、2.5 Pro を影」という構成で続ける予定です。
具体的には、当面の本番ジョブは 3 Pro 単独に切り替え、2.5 Pro は週次バッチで 10% の影シャドウ実行に回します。3 Pro 単独に倒したとしても、2.5 Pro のログが残っていれば、後から差分が説明できますし、Gemini 側のアップデートが入ったときの回帰検出にもそのまま使えます。
技術選定で意識しているのは、「これで決まり」と思った時点で観察を止めないことです。現時点で 3 Pro を主に置くと決めながらも、2.5 Pro Flash を含む他モデルのログを残し続ける。この余白が、Gemini 側のアップデートで挙動が変わったときの回帰検出にもそのまま効いてくるので、長く運営する個人開発者には合っているのだと思います。
次は、Gemini 3 Pro と Imagen 4 を組み合わせた「画像→意味タグ→候補画像」のレコメンドエンジンに同じ並行投入の枠組みを当てる予定です。同じく現場の数字で淡々と判断していきます。お読みいただきありがとうございました。