GEMINI LABEN
FLASH35 — Gemini 3.5 Flashが一般提供となり、gemini-flash-latestが指す実体になりました。日常的な生成を速く安価にこなせますAGENTS — Gemini APIのManaged Agentsがpublic previewに。Google管理の隔離Linuxサンドボックスで自律的なエージェントを構築できますMEDIA — Nano Banana 2 Lite(画像)とGemini Omni Flash(動画・会話的編集)がAI Studio・API・Enterprise Agent Platformで利用可能にTTS — gemini-3.1-flash-tts-previewの音声生成がstreamGenerateContent経由でストリーミング対応になりましたTRANSLATE — 新しい音声モデルが70以上の言語を自動判別し、話者の自然な抑揚を保ったままライブ音声翻訳を行いますSPENDCAP — AI Studioにプロジェクト単位の費用上限が導入され、支出を安全側に抑えられるようになりましたFLASH35 — Gemini 3.5 Flashが一般提供となり、gemini-flash-latestが指す実体になりました。日常的な生成を速く安価にこなせますAGENTS — Gemini APIのManaged Agentsがpublic previewに。Google管理の隔離Linuxサンドボックスで自律的なエージェントを構築できますMEDIA — Nano Banana 2 Lite(画像)とGemini Omni Flash(動画・会話的編集)がAI Studio・API・Enterprise Agent Platformで利用可能にTTS — gemini-3.1-flash-tts-previewの音声生成がstreamGenerateContent経由でストリーミング対応になりましたTRANSLATE — 新しい音声モデルが70以上の言語を自動判別し、話者の自然な抑揚を保ったままライブ音声翻訳を行いますSPENDCAP — AI Studioにプロジェクト単位の費用上限が導入され、支出を安全側に抑えられるようになりました
記事一覧/高度な活用
高度な活用/2026-07-15上級

分類ラベルの近ミスはリトライで直らない — 閉じた語彙を正規化する層を挟んだ実測ノート

responseSchema の enum を外したラベルをリトライで直そうとすると同じ近ミスが返ります。壁紙アプリの30分類バッチで外れ方の分布を測り、別名表と埋め込み最近傍で正規化する受け入れ層を挟んだ実装と実測値をまとめました。

Gemini API183構造化出力12画像分類4gemini-embedding-26個人開発86コスト最適化26

プレミアム記事

壁紙アプリに投稿された画像を30カテゴリへ振り分けるバッチを回していたとき、responseSchemaenum に無い文字列が返ってくること自体は覚悟していました。想定していなかったのは、その外れ値をリトライで直そうとすると、二度目もほとんど同じ言葉が返ってくることです。「風景」を期待した画像に "scenery" が返り、もう一度投げても "scenery"。モデルは壊れているのではなく、その画像に対しては本気でその語がいちばん近いと考えていました。

つまりこれはランダムな揺らぎではなく、語彙のズレでした。ズレをリトライで殴っても、往復のコストと待ち時間が増えるだけで、返ってくる答えは変わりません。個人開発でバッチを夜間に流している身としては、この一往復が積み重なると翌朝の完了時刻にそのまま効いてきます。ここでは、外れ方をまず測り、リトライの前に「正規化する層」を挟んだときに何がどれだけ変わったのかを、実測値とともに残しておきます。

enum が無視される仕組みそのものと最初の回避策は、以前 responseSchema の enum 指定なのに違う文字列が返る現象の記事 にまとめました。本稿はその続きにあたり、「バリデーションして1回リトライする」という素直な対処が、規模が出たときになぜ割に合わなくなるのかを掘り下げます。

リトライを疑う前に、外れ方の分布を見た

対処を決める前に、まず一度だけ全件を素通しして、enum を外したラベルがどう外れているのかを数えました。判断材料が「たまに変な値が返る」という体感のままだと、正しい打ち手が選べないからです。

対象は壁紙8,142枚、responseSchema に30カテゴリの enum を指定した1パスの結果です。enum に一致しなかったのは312件(全体の約3.8%)。その312件を手作業で見ていくと、外れ方はきれいに3つの山に分かれました。

外れ方件数例(返ってきた値 → 本来のラベル)
別名・表記ゆれ(機械的に解決可能)231件scenery → 風景、night view → 夜景、花 → 植物
意味は近いが別名表に無い49件「渓谷の写真」→ 自然、「街の明かり」→ 夜景
本当に曖昧・複数該当32件「自然・動物」複合、抽象度が高く判断保留すべきもの

231件、つまり約74%は、モデルが返した語と手元の正解ラベルが「同じものを別の言葉で言っている」だけでした。ここに気づいた瞬間、リトライという発想が的外れだと分かりました。同じ画像を投げ直しても、モデルの語彙観は変わらないので、二度目も "scenery" が返ってきます。直すべきは受け取り側であって、モデルへの問い合わせ回数ではありませんでした。

もう一つ大事だったのは、いちばん右下の32件です。これは正規化してはいけないグループでした。機械的に最も近いラベルへ寄せてしまうと、本来「その他」や人手確認に回すべき画像を、それらしいカテゴリに押し込んでしまいます。分類の信頼を落とすのは、実はこの押し込みのほうです。これは本番運用でいちばん静かに効く落とし穴で、精度の数字には表れません。

近ミスを機械的に解く二段構え

方針はシンプルです。out-of-set のラベルが来たら、まず決定的な別名表で引き、引けなければ埋め込みの最近傍で寄せ、それでも自信が持てなければ正規化せずに保留へ回す。モデルへの再問い合わせは、この保留の中でも「もう一度だけ視点を変えて訊く価値がある」ものに限定します。

別名表は、実際に観測した外れ値からしか作りません。想像で網羅しようとすると保守できない辞書になるので、逸脱ログに出てきた語だけを、確認しながら少しずつ canonical へ結びつけていきます。

import unicodedata
from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_API_KEY")
 
# 手元の正解語彙(30カテゴリのうち一部を抜粋)
CANONICAL = ["風景", "夜景", "植物", "動物", "自然", "抽象", "宇宙", "海", "その他"]
 
# 実際に逸脱ログで観測した語だけを結びつける。想像で足さない
ALIASES = {
    "scenery": "風景", "landscape": "風景", "けしき": "風景",
    "night view": "夜景", "夜の街": "夜景",
    "花": "植物", "flower": "植物", "plant": "植物",
    "animal": "動物", "nature": "自然",
}
 
def _norm(s: str) -> str:
    # 全角半角・大文字小文字・前後空白の揺れを吸収してから引く
    return unicodedata.normalize("NFKC", s).strip().lower()
 
_ALIAS_INDEX = {_norm(k): v for k, v in ALIASES.items()}

第一段の別名表で引けたものは、そのまま canonical として受け入れます。ここで解決できるのが先ほどの231件の大半です。

第二段は、別名表に無い語(49件のグループ)を gemini-embedding-2 で最近傍のカテゴリへ寄せます。ポイントは、いちばん近いカテゴリとの距離だけで決めず、一位と二位の差(マージン)を見ることです。マージンが小さいときは「どちらとも言えない」ので、無理に寄せずに保留へ落とします。

import numpy as np
 
def _embed(texts: list[str]) -> np.ndarray:
    resp = client.models.embed_content(
        model="gemini-embedding-2",
        contents=texts,
        config=types.EmbedContentConfig(task_type="SEMANTIC_SIMILARITY"),
    )
    vecs = np.array([e.values for e in resp.embeddings], dtype=np.float32)
    # コサイン類似度で比べるため単位ベクトルへ
    return vecs / np.linalg.norm(vecs, axis=1, keepdims=True)
 
# canonical 側のベクトルは起動時に一度だけ作って使い回す
_CANON_VECS = _embed(CANONICAL)
 
def nearest_canonical(label: str, margin: float = 0.06):
    v = _embed([label])[0]
    sims = _CANON_VECS @ v            # 各カテゴリとの類似度
    order = np.argsort(sims)[::-1]
    top, second = order[0], order[1]
    if sims[top] - sims[second] < margin:
        return None, float(sims[top])  # 一位と二位が僅差 → 寄せない
    return CANONICAL[top], float(sims[top])

この二段を、一つの受け入れゲートにまとめます。戻り値には「どう解決したか」の経路を必ず添えて、後でログとして残せるようにしておきます。経路が見えないと、別名表を育てるべきか、マージンを調整すべきかの判断ができません。

def resolve_label(raw: str):
    if raw in CANONICAL:
        return raw, "in_set"
 
    hit = _ALIAS_INDEX.get(_norm(raw))
    if hit:
        return hit, "alias"
 
    canon, sim = nearest_canonical(raw)
    if canon is not None and sim >= 0.82:
        return canon, f"embedding:{sim:.3f}"
 
    # ここまで来たものは機械的に確定しない。保留に回す
    return None, "unresolved"

resolve_labelNone を返した画像だけが、人手確認か、視点を変えた再問い合わせの候補になります。ここで初めて、限定的にモデルへ投げ直す価値が出てきます。全件を無条件にリトライするのとは、往復回数がまるで違います。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
8,142枚のバッチで enum を外したラベルは312件、その約74%が別名で機械的に解決できる近ミスだった外れ方の分布
リトライではなく別名表と gemini-embedding-2 の最近傍で out-of-set ラベルを正規化する二段構えの受け入れ層の実装
モデルへの再問い合わせを312件から82件へ絞った設計と、あえて正規化せず保留に回す曖昧ケースの境界線
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

高度な活用2026-07-10
カテゴリを30から34に増やした日 — 8,000枚を全部かけ直さない差分再分類の設計
分類カテゴリを追加したとき、既存資産を全件かけ直す必要はありません。埋め込みで影響ラベルを絞り、確信度マージンで境界資産を拾う差分再分類の実装と実測値をまとめました。
高度な活用2026-07-10
停止するモデルで作った画像は、二度と同じものが作れない — 再生成可能性を台帳で管理する
8月17日に停止する Gemini の画像生成モデルで作ったアセットは、停止後は同じ手順で再現できません。何が再生成可能で何が凍結資産なのかを台帳で切り分け、移行前に判定を終えるための設計と実装をまとめました。
高度な活用2026-07-08
複数アプリのFAQを1つの File Search ストアに同居させる — customMetadata と chunk 設定で検索を分ける
複数アプリのFAQを1つの File Search ストアにまとめると、metadataFilter が無言で空を返したり、回答がチャンク境界で割れて引用されなかったりします。customMetadata の設計、AIP-160 フィルタ構文の落とし穴、chunkingConfig の実測チューニングを実装ベースでまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →