https://qiita.com/mrpepper/items/cba1a78589846d28aaa0
これの続き。なお、「行司ぃ」というJevっぽい画像対応 SystemOne は、
https://gyojy.hakadoru.ai/
画像に注がれる眼差しを分解する
画像を扱うAIには、「何が写っているか」を文章で説明する使い方がある。この言い方がどのくらい一般的かわからないけど、Description という芸ですね。これをやる時の LLM の脳みその中身を、うまいこと畳み込んで、Jevっぽくショートカットして出してみようと思うのだ。
たとえば、画像に対して「赤い傘」と入力し、その傘の上に点を置く。同じ画像を開いたまま「奥の自転車」「机の上のカップ」と検索語を変え、それぞれの位置を得る。オブジェクトの抜き出しは OpenCV とかを使ってもらうとして、画像と言葉から代表点の座標と確信度を得る、というのは SystemOne しぐさではないか。モデルが画像と語句を処理する途中に現れる情報を利用する、という技である。
現段階の実験版での出力は点である。対象の輪郭を切り出すセグメンテーションや、対象全体を囲む矩形の検出までは実装していない。LLM のようにちゃんと常識を具えていながら、爆速・シンプル、それが SystemOne だと思うのだ。
要するに、超軽量に「チラ見」ができるAI、SystemOne じゃなぁい? というわけで、以下はさほど具体的に示すわけではないのでわけわかめかもしれないが、チラ裏なので気にしないでほしい。分からなければ「実装したよ!」というお知らせを待っていただいて、フォローとハートマークを押して、静かにブラバでOK。
位置は返れど、「そもそもない」
画像内の候補位置に点数を付け、一番高い場所を選ぶだけなら、必ずどこかが選ばれる。探しているものが写っていない場合でも、最大値の位置はどこかに存在する。
初期の実験でも、この問題が起きた。赤い円や青い四角を置いた画像で、存在しない「紫の星」を検索すると、画像上の「言いたいことはわかる」感じの場所に点が出た。
使う立場からすると、誤った点が出るよりも、その点をどれくらい信頼してよいか分からないことのほうが問題だ。答えがなさそうな時に、低い信頼度を返せれば、どんな使い方をするにせよ、使いようはあるものだ。
そこで、実験の目標を「対象が不存在のとき、位置候補に低い信頼度を付ける」に置いた。数値を確率として厳密に校正することより、まず不存在の候補を見分ける実用的な信号を得ることを優先した。
位置取得の中間状態を、そのまま信頼度にも使う
信頼度を得るために、もう一度モデルへ「本当に写っていますか」と質問すると、推論の回数が増える。別の検証モデルを呼ぶ場合にも、追加の処理が必要になる。それでは SystemOne ぽくない。
今回の実装では、位置取得に使った推論の中間状態から、存在を支持するスコアも計算する。各検索語の位置取得につき、モデルのforwardは1回である。その出力から、位置候補と信頼度の両方を取り出す。
画像と検索語
│
▼
画像・語句をモデルが処理する
│
└─ 中間のアテンション情報
├─ 位置用ヘッドの最大点 → 画像上の位置候補
└─ 複数ヘッドの特徴 → 存在を支持するスコア
これは「計算が一切増えない」という意味ではない。中間状態を取り出し、GPUから必要な情報を転送し、小さな分類器でスコアを求める処理は増える。ただし、信頼度を得るための追加のモデルforwardは行わない。
また、後述する写真の評価手順では、対象を列挙するために文章生成を行う。その列挙工程と、各対象の位置・信頼度を求める工程は区別している。
アテンション情報から、位置候補を得る仕組み
今回の実験では、384×384ピクセルの人工画像を用いた。画像を12×12、合計144個の位置に対応するグリッドとして扱った。検索語を含む入力の所定のトークン位置から、各視覚トークンへ向かうアテンションを取得する。実装では、ユーザーメッセージの終了マーカーの直前の位置を参照している。
アテンションは、層ごとヘッドごとに違っている。みんな違ってみんないい。複数のヘッドを平均すると、画像の端に強い値が出て、対象の位置を選べない例など、それぞれに特徴的な挙動を示す。そこで、初期の開発画像で位置の命中数を比べ、位置取得に使うヘッドを選んだ。これは、おそらくVLM固有の癖を把握してそれに合わせて作るしか無いだろう。
そのヘッドのアテンションが最大となるセルを選び、セルの中心を画像座標へ変換する。グリッドの高さをH、幅をW、最大セルの添字をj、画像サイズを幅Iw・高さIhとすると、座標は次のようになる。
x = (j % W + 0.5) × Iw / W
y = (floor(j / W) + 0.5) × Ih / H
このやり方だと、点の分解能はグリッドに制約される。対象の精密な輪郭や、ピクセル単位の中心を直接求めているわけではない。もっとずっと精度の上がるやり方も考えられるが、実験用実装ではまずはコンセプトを実証するために急ぎたい。
アテンションの強さも、その場所が正解である確率ではない。今回扱っているのは、モデル内部から読み出した謎信号と、その謎信号を粗く集計して特定した位置である。
単一のアテンションの量だけでは、不存在を見分けにくかった
最初に試した不存在の指標は、位置用ヘッドが画像へ向けるアテンションの総量だった。対象が見つからないときは画像へのアテンションが弱くなるのではないか、という仮説である。
しかし、この量は構図や検索語の言い換えによって揺れてしまうようだった。初期の配置変更画像では、不存在の「紫の星」を探させた8クエリのうち7クエリが存在扱いとなった。一方、対象を取り除いた別の画像群では、不存在24クエリのうち存在扱いは1クエリだけだった。
画像もしくは検索語によって判別の結果が大きく変わるため、単純な閾値を安定した信頼度として扱うことはできなそうだった。アテンションが一点に鋭く集まっていても、その点が存在する対象を指しているとは限らない。
複数のヘッドから、小さな存在判別器を作る
次の実装では、位置用ヘッドだけを見る方法と、複数のヘッドを見る方法を比較した。対象の位置に強いヘッドと、対象の存在の兆候となる信号を持つヘッドが、同じである必要はない。
存在判別の候補として、複数の層から約200のヘッドを調べた。学習用データを使い、画像へのアテンション総量が存在・不存在とどれくらい対応するかを比較した。強いほど存在に対応する場合と、弱いほど存在に対応する場合の両方を考慮し、8ヘッドを選んだ。
選んだ各ヘッドから、次の6特徴を作る。
| 特徴 | 読み取る情報 |
|---|---|
| 画像へのアテンション総量 | 画像トークンへ割り当てられたアテンションの大きさ |
| 正規化後の最大値 | アテンションが一つのセルへ集中する度合い |
| 正規化したエントロピー | 画像上でアテンションが広がる度合い |
| 上位2セルの差 | 最有力セルと次点の差 |
| 位置候補の近傍にあるアテンション量 | 位置用ヘッドが選んだ点の周囲3×3セルへの集中 |
| 最大点と位置候補の距離 | 各ヘッドの最大点と、位置用ヘッドの点の一致度 |
合計48特徴を標準化し、L2正則化したロジスティック回帰へ入力する。式で表すと、標準化後の特徴ベクトルzに対し、スコアsは次の形になる。
s = sigmoid(b + w・z)
追加学習するのは、この小さな分類器の係数である。VLM本体の重みは更新していない。特徴の標準化にも、学習用データの平均と標準偏差だけを使う。
学習時には、画像生成時の配置記録から対象の存在・不存在を教える。実際にスコアを計算するときは、もちろん正解ラベルや物体の名前は渡さない。中間のアテンション配列とグリッド寸法だけから特徴を作る。もっとも、そのアテンション配列自体は画像と検索語を処理した結果であり、検索語の影響が消えるわけではない。
存在と不存在を、同じ構図の中で比較する
不存在の画像だけ背景や配置が違うと、対象の有無ではなく、その違いを学習してしまう可能性がある。今回の人工画像では、同じ構図で対象を置く場合と取り除く場合を揃えた。
対象は「赤い円」「青い四角」「緑の三角」の3種類である。それぞれの有無を変え、1構図につき全8通りの画像を作った。さらに、同じ色で形が違う物、同じ形で色が違う物を妨害図形として置いた。
最初のデータは8構図・64画像で、各画像に通常名3語とその言い換え3語をクエリした。合計384クエリとなる。
| 用途 | 構図 | クエリ数 |
|---|---|---|
| 学習 | 0〜3 | 192 |
| 係数の正則化と判定基準の選択 | 4〜5 | 96 |
| 最初の評価 | 6〜7 | 96 |
同じ構図から作った対象削除画像や、その画像への言い換えクエリを、別の分割へ流さないようにした。
判定基準は、設定選択用データで、不存在を高信頼度にする率が5%以下となる範囲から、存在する対象を最も多く残す条件を選んだ。この5%は、そのデータで設定を選ぶための条件である。将来の入力でも誤りが5%以下になるという保証ではない。
なお、位置用ヘッドは先行する人工画像の探索で選んだものを固定している。この実験で再選択したのは、存在判別に使うヘッドと分類器の設定である。
不存在の指標を低くし、存在する対象の値を守る
最初の評価用2構図には、存在48クエリ・不存在48クエリが含まれている。
| 方法 | 不存在を低信頼度にした数 | 存在を高信頼度として残した数 |
|---|---|---|
| 位置用ヘッドのアテンション総量 | 48/48 | 0/48 |
| 位置用ヘッドの空間特徴 | 43/48 | 1/48 |
| 複数ヘッドの特徴 | 46/48 | 47/48 |
不存在を低くするだけなら、バカみたいな頓知だが、すべてを低信頼度にする方法でも達成できる。そのため、存在する対象をどれくらい残せたかを同時に見る必要がある。
複数ヘッドの方法では、不存在の大部分を低くし、存在する対象の大部分を残せた。ただし、この96クエリは2構図から作ったものである。独立した96枚の一般画像で評価した結果ではない。
続いて、新しい乱数で作った8構図・64画像を用意し、係数も判定基準も変更せずに適用した。既知の6語に加え、学習に出ていない不存在語句「赤い三角」「青い円」「緑の四角」をクエリし、合計576クエリを評価した。
| 追加評価の対象 | 不存在を低信頼度にした数 | 存在を高信頼度として残した数 |
|---|---|---|
| 既知の検索語・新しい構図 | 187/192 | 181/192 |
| 学習にない不存在語句 | 182/192 | 存在例なし |
| 合計 | 369/384(96.1%) | 181/192(94.3%) |
この追加評価では、不存在なのに高信頼度になったのは15クエリ、存在するのに低信頼度になったのは11クエリだった。
これらは色付き図形という限定した画像分布での値である。学習にない3語句には存在例を用意していないため、その語句の対象が写っている場合の性能は、この結果からは分からない。
存在スコアと、位置の正しさは別のもの
今回のスコアが学習したのは、対象の存在・不存在である。点が正しい場所にあるかどうかを直接学習したわけではない。
最初の評価で高信頼度として残った存在47クエリのうち、候補点が対象の箱内に入ったのは36クエリだった。対象が存在することを支持できても、点がその対象に乗っていない例は残っている。
また、現在の点の評価は、生成時に記録した対象の外接矩形内に点が入るかで判定している。円や三角形では、矩形内でも図形そのものの外に点が落ちることがある。輪郭内への命中を評価した数値とは異なっていて、例えば、後続プロセスで OpenCV での物体抽出をしようとするにはまだまだ使えない。
出力では、候補座標と存在スコアを分けている。実装の主なフィールドは次の通りである。
| フィールド | 意味 |
|---|---|
candidate_point |
位置用ヘッドから得た候補座標 |
presence_score |
対象の存在を支持する実験用スコア |
presence_status |
判定基準を満たすsupported、または保留を表すuncertain
|
score_calibrated |
現在はfalse。校正済み確率ではない |
uncertainは「対象は存在しない」という意味ではない。低いスコアには、本当の不存在だけでなく、モデルが存在を捉え損ねた場合も含まれている。
0から1の値でも、そのまま確率とは呼べない
ロジスティック回帰の出力は0から1の範囲になる。しかし、0.9という値が出た結果の90%が正しいことは、出力の形式だけでは保証されない。
最初の評価では、不存在スコアの中央値が約0.000955、存在スコアの中央値が約0.999967だった。数値が両端に寄っていても、それだけで高い確実性が証明されるわけではない。実際に、不存在を高く評価する例もあった。
実際の正解率に対応する確率として提供できるようにするためには、用途に近い別のデータで校正と評価を行う必要があるだろう。その場合にも、対象の存在確率を別に求める手法が有効なのか、返した位置の正解確率を求めるほうが筋がよいのかを試行錯誤する必要がある。
追加の計算には、どれくらい時間がかかったか
実験環境はRTX 4070 SUPERの12GB GPUで、位置取得時にアテンション情報を読み出す設定を使った。
| 測定対象 | 実測値 | 備考 |
|---|---|---|
| CPU上の特徴計算とスコア算出 | 平均約0.3ミリ秒 | 同一入力で1,000回計測。GPUからのデータ転送は含まない |
| キャッシュ利用時の信頼度追加処理 | 中央値約1.5ミリ秒 | 12クエリ。中間状態のGPUからCPUへのデータ転送を含む |
| 同じ12クエリの位置取得全体 | 中央値約113.9ミリ秒 | 信頼度算出を含む。モデルロードと画像の初回処理は除く |
追加forwardを行わず、既存の位置取得に軽量な追加計算だけで結果が得られることが確認できたのは、素晴らしい。
同じ画像を何度も探るときのためのKVキャッシュ
画像と検索文の共通部分を毎回処理し直す代わりに、その部分をKVキャッシュに保持し、検索語ごとの差分を続けて処理する実装も作った。
これは、検索語を変えても推論が不要になるという意味ではない。画像を含む共通部分の処理結果を再利用し、各検索語については続きを計算する。現在の実験実装では、各クエリでキャッシュを複製して使っているため、その処理やメモリにもそれなりの計算量がかかる。
存在スコアを加えたキャッシュ経路の12クエリでは、全入力を処理した場合と高・低の判定が12件すべて一致した。ただし、スコアの最大差は約0.0892であり、数値の完全一致ではない。
この12クエリには、不存在6クエリと存在6クエリが含まれる。不存在のうち5クエリが低信頼度となり、存在の6クエリはすべて高信頼度だった。不存在なのに高くなる誤りは、キャッシュを使う経路にも残っていた。
一般写真では、やっぱり人間
図形で精度を上げ続けて過適応するのはナンノコッチャわからないので、実現性が見えたところで、一般写真への応用に進むのだ。まずは、VLMへ次の問いを渡す。
何が写っているか、主たる被写体以外も含めて、列挙してください。
列挙された名称を検索語として、対象ごとの位置と存在スコアを求める。
この方法では、目立つ主被写体だけでなく、小さな物や背景の要素も評価対象になる。もちろん、VLMが列挙したものが正解とは限らないが、少なくともVLMが認識したものをポイントする、ということができるようになれば、それはびっくりするほどユートピアなのである。
というわけで
なぜこんなことを書き連ねているかというと、トークンが尽きちゃったのである。ClaudeもCodexも、寝ちゃったのである。というわけで、ClaudeのWeekly上限が、あと3時間後に解除されるので、また頑張るぞい。
とりあえず、今開発している領域だと、AIに結果だけ要求しても何も進まないので、僕が提案し、彼らが爆速で実装し、僕が仮説を示し、彼らが丁寧に検証する、という共同作業が必要なので(というか、AI駆動開発って、やっていると、自然とそういう領域に吸い込まれていく気がする)頑張るぞい。