
做圖像描述看圖說話這幾年我一直有個很深的體會模型能“說”并不難難的是讓它“說到點子上”。前兩天看到一篇來自某歐洲高校計算機視覺實驗室的新工作團隊把方法命名為RNS全稱是 Reranking with Negative Sampling翻譯過來就是“負樣本引導的重排序”。如果你也在做圖像理解、多模態(tài)生成或者正在被模型“一本正經(jīng)胡說八道”的問題折磨這篇內(nèi)容值得認真讀完。RNS解決的問題非常具體在模型已經(jīng)能生成多個候選描述的情況下怎么從這些候選中挑出那個真正貼合圖片內(nèi)容的句子而不是只挑一個“讀起來順口但其實看圖不靠譜”的結果。它把一個常被忽略的環(huán)節(jié)——句子選擇從“按生成概率直接取最高”升級成了“用專門的排序器綜合判斷”。概念不復雜工程上也容易落地但效果提升卻非常明顯。下面我把它的來龍去脈、核心原理和復現(xiàn)要點完整拆一遍。1. 看圖說話的問題到底卡在哪1.1 一個看似簡單卻一直被低估的任務圖像描述image captioning的任務一句話就能說清楚給模型一張圖讓它輸出一句通順自然、且內(nèi)容與圖像高度一致的自然語言描述。但真做起來的難度遠超預期。它不像圖像分類那樣只輸出一個標簽也不像機器翻譯那樣輸入輸出都是文本。圖像描述要求模型同時具備三件事看懂圖上有哪些物體和關系、把這些視覺信息組織成符合語法的句子、保證句子里的每個關鍵語義都能在圖像里找到對應點。這三件事任何一環(huán)掉鏈子輸出就會出問題。我在實際項目中見過太多“看起來還行”的模型語法完全正確句子也流暢但描述的對象和圖像內(nèi)容對不上號。比如一張“白色轎車停在超市門口”的圖模型寫成了“一輛公交車行駛在公路上”。這種錯誤不是模型不會說英文不是詞匯量不夠而是它在“選擇該說什么”這個環(huán)節(jié)出了問題。1.2 三個典型錯誤幻覺對象、重復片段、語義漂移如果長期看模型生成的描述文本會發(fā)現(xiàn)錯誤類型其實高度集中。最常見的是幻覺對象圖片里根本沒有出現(xiàn)的東西被模型大言不慚地寫進了句子。模型在訓練時看到“貓”和“沙發(fā)”經(jīng)常一起出現(xiàn)于是只要視覺特征里有類似沙發(fā)紋理的信號它就可能憑空補出一個“貓”來。第二個高頻問題是重復片段。自回歸解碼器逐字生成時如果某個 token 的概率分布不夠尖銳很容易陷入局部循環(huán)輸出“a dog and a dog and a dog…”這種毫無信息量的重復句子長度越長越明顯。很多入門項目里都能看到這類輸出根源在于解碼策略和訓練目標都沒有對“全局連貫性”做足夠的約束。第三類問題叫語義漂移。句子前半段還在描述正確的對象生成到后半段話題突然跳走。比如“一個穿紅色衣服的女孩在公園里跑步手里拿著一個手機”后半截的“手機”就是憑空跳出來的。這類問題最隱蔽因為每個局部片段單獨看都正常合在一起就是錯。1.3 為什么數(shù)據(jù)更多、模型更大也不一定能解決很多人第一反應是模型不行那就加數(shù)據(jù)、加參數(shù)。可現(xiàn)實是即便模型規(guī)模翻倍、訓練數(shù)據(jù)增加上述三類錯誤依然會存在只是頻率降低不會根除。原因在于這些錯誤的本質是“解碼和選擇機制”不夠好而不是視覺表征不夠強。你可以把生成描述理解成兩步第一步是從圖像里提取足夠好的信息第二步是把這些信息組織成一句最優(yōu)的句子?,F(xiàn)有的大規(guī)模預訓練模型第一步通常已經(jīng)做得相當好但第二步里的“最優(yōu)句子選擇”傳統(tǒng)做法往往只是按生成概率做個貪心解碼或 beam search本質上是局部最優(yōu)不是全局最優(yōu)。也就是說模型可能早就“想”出了正確描述但在最終選擇路徑時把它丟掉了。這正是 RNS 選擇切入的地方不動表征不改主干模型專門去優(yōu)化“從多個候選里挑出最好那個”的選擇環(huán)節(jié)。2. RNS的整體設計與核心思路2.1 一句話理解RNS先多生成再按可靠性挑RNS 的思路用一個比方能講明白現(xiàn)在公司要招人你不再讓候選人只交一份簡歷就定結果而是先讓所有人做一輪開放式筆試產(chǎn)出一批候選方案再由資歷更深的評審團從“實際做事能力、專業(yè)匹配度、表達是否靠譜”等多個維度綜合打分選出最終錄用的人。放到圖像描述場景里筆試環(huán)節(jié)就是讓訓練好的自回歸解碼器生成 N 個候選句子評審環(huán)節(jié)就是用一個專門的重排序器給每個候選句子打一個“可靠性分數(shù)”最后選分數(shù)最高的句子作為最終輸出。這個兩階段框架并不算新思想信息檢索領域的 re-ranking 早就在用。RNS 的獨特之處在于它對重排序器的訓練方式做了針對性設計核心是引入了高質量的負樣本。傳統(tǒng)的重排序器只會見過“候選句子與參考句子像不像”這樣的正樣本比較信號RNS 會額外構造大量“與圖片描述部分相關、但關鍵信息出錯”的負樣本逼著重排序器學會辨認那些看起來順眼、實際上與圖像不一致的句子。2.2 負樣本不是隨便抄幾句壞話負樣本的質量直接決定排序器的上限。如果只在海里隨便抽幾句完全無關的話當負樣本模型很快就能學會——那也太簡單了句子和圖像完全不相關一眼就能看出來。這種負樣本訓練出來的排序器拿到真實場景里沒有任何實戰(zhàn)能力因為真實候選句都是“有點相關、但細節(jié)出錯”的類型。RNS 構造負樣本的核心原則是“難負樣本”也就是和正樣本高度相似、但存在關鍵語義錯誤的句子。常用做法有三種。第一種是對象級替換把參考描述中的核心名詞替換成同類別或視覺特征相近的另一個詞。比如“一只貓趴在窗臺上”改成“一只狗趴在窗臺上”。第二種是局部語序打亂把正常的句法結構破壞讓模型看到“語序正確性”也是一種重要的判斷信號。第三種是跨圖錯配把另一張圖片的、語法完全正確的參考描述拿過來當作當前圖片的負樣本。這類負樣本在訓練中非常有效能迫使排序器學會真正去理解“圖像內(nèi)容”與“句子語義”之間的對應關系而不只是依賴句子本身的質量。2.3 可靠性分數(shù)如何計算重排序器給候選句子打分不能只看一個信號至少要綜合三路信息。第一路是生成似然分數(shù)。解碼器給這個候選句子分配的概率能反映出模型自己覺得它順不順但這項分數(shù)單獨用會偏向高頻套話所以只能作為基礎分。第二路是視覺-語言匹配分數(shù)。用一個跨模態(tài)編碼器把圖像特征和候選句子的文本特征做交叉注意力判斷句子描述的內(nèi)容在圖像里有沒有對應區(qū)域。第三路是負樣本辨別分數(shù)。排序器在訓練時見過大量難負樣本會學會一套“哪些細節(jié)最容易出錯”的敏感度這部分信號能讓排序器主動懲罰那些“看起來合理、實際是幻覺”的候選句。最終分數(shù)是這三路信號的加權融合。融合權重不需要太復雜固定比例或者加一個簡單的門控網(wǎng)絡都可以。我在復現(xiàn)時先用固定比例比如 0.3、0.4、0.3跑通流程再根據(jù)驗證集效果做小幅調整效果就足夠好。2.4 為什么比端到端微調更穩(wěn)看圖說話領域有一個很常見的優(yōu)化路線是用強化學習直接優(yōu)化 CIDEr 之類的指標例如 self-critical 方法。它的問題在于訓練方差大、超參數(shù)敏感經(jīng)常出現(xiàn)指標在訓練集上漲、驗證集開始震蕩或者生成的句子為了押中 n-gram 導致嚴重重復。這種“直接拿指標當獎勵”的方式相當于讓一個新手去考滿分卷過程很容易走偏。RNS 把生成和選擇解耦生成器繼續(xù)用穩(wěn)定成熟的交叉熵訓練排序器單獨用排序損失訓練。每個模塊的任務都更簡單訓練也就更穩(wěn)定。工程上這還有個額外好處排序器和生成器可以分別迭代。今天換一個更強的生成器排序器不用重頭訓練排序器想加一個新打分信號生成器也不用動。模塊化帶來的是實打實的迭代效率提升。當然RNS 也不是沒有代價。它需要額外維護一個重排序模型推理時也要先把 N 個候選句子都生成出來再打分計算量比單次貪婪解碼要高。但在精度優(yōu)先的業(yè)務場景里這一點點成本完全劃算。3. 關鍵細節(jié)與實操要點3.1 候選生成階段的參數(shù)控制RNS 的效果上限很大程度取決于候選句子質量。如果 beam search 出來的 16 個候選本身就全是爛句子那排序器再強也挑不出金子。所以第一步不是調排序器而是先把生成階段調好。beam size 我建議設成 8 到 16。太小候選多樣性不夠太大排序器的計算壓力會明顯增加。長度懲罰length penalty也值得專門調我常用 alpha 在 0.8 到 1.2 之間重點觀察候選句里有沒有因為長度懲罰過低導致的長句崩壞。另外一定要開啟重復懲罰。如果你用的解碼器支持 repetition_penalty 之類的參數(shù)建議設到 1.2 以上。否則生成階段就會把“重復片段”這類錯誤大量帶進候選池排序器需要花很大力氣去糾正等于白白浪費擬合能力。3.2 重排序器的結構與輸入重排序器本身不需要很復雜。我用的是一個輕量級的雙向上下文編碼器輸入由三個部分組成候選句子的 token 序列、圖像的區(qū)域特征或者圖像級特征視任務規(guī)模而定、以及一個表示“候選句子來自第幾個 beam”的序號特征。序號特征這個細節(jié)容易忽略但實測對結果有正貢獻因為 beam 里靠前的候選句通常整體質量更穩(wěn)排序器可以學習到這個先驗。訓練損失用成對排序損失pairwise ranking loss。目標很簡單正樣本的分數(shù)比負樣本的分數(shù)至少高出 margin。代碼里可以寫成 max(0, margin - score_pos score_neg)。margin 我推薦 0.2 到 0.5太小會讓排序器“無所謂”太大則可能導致訓練早期梯度太強、模型不穩(wěn)定。訓練數(shù)據(jù)構造環(huán)節(jié)要特別提醒正樣本和負樣本必須來自同一幅圖。如果你把 A 圖的正樣本和 B 圖的負樣本配對模型學到的是“哪句話更順”而不是“哪句話更貼合這張圖”會嚴重影響最終效果。3.3 與其他優(yōu)化路線的對比為了讓你對 RNS 的定位更清楚我用一張表橫向對比幾條常見的優(yōu)化路線。方法路線核心思想優(yōu)點缺點生成概率重排直接用解碼器的生成概率選 top-1實現(xiàn)簡單、零額外成本偏向局部高頻詞忽視全局語義一致性強化學習微調用評估指標當獎勵直接優(yōu)化生成器能讓模型對齊指標方差大、容易重復、超參數(shù)敏感兩階段人工篩選生成大量候選后人工挑最好的上限高不可自動化無法落地RNS生成候選 負樣本引導排序器自動挑穩(wěn)定、可解釋、模塊化多一個排序器訓練和推理成本從這張表可以看出來RNS 并不是要在生成模型上和別人硬拼參數(shù)它贏在“選擇”這件事做得系統(tǒng)化。這也是我在業(yè)務項目里最認可它的地方。4. 一個可落地的最小復現(xiàn)流程4.1 準備一套小規(guī)模數(shù)據(jù)先說數(shù)據(jù)。圖像描述模型需要圖文對數(shù)據(jù)。如果你沒有現(xiàn)成的內(nèi)部數(shù)據(jù)建議直接從某個公開的圖像描述數(shù)據(jù)集中切一個子集出來先跑通流程再逐步擴大規(guī)模。我在這里用一個虛構代稱 U 指代那個數(shù)據(jù)集具體是誰不重要關鍵是它的格式要包含圖像文件路徑、每張圖對應多條人工描述、以及劃分好的訓練集/驗證集/測試集。小規(guī)模驗證項目M 我采用的是 2 萬張圖、每張圖 5 條參考描述。這個體量足夠看到一個趨勢又不至于讓你卡在算力上。數(shù)據(jù)準備好后記得做三件事把圖片統(tǒng)一縮放到相同尺寸把描述文本全部切成 token過濾掉出現(xiàn)頻率過低的詞。詞匯表太大會讓模型學不扎實建議控制在 5000 到 10000 個詞的規(guī)模。4.2 訓練生成模型第一階段的生成模型可以用一個輕量結構視覺編碼器負責把圖片轉成特征序列解碼器負責把特征序列轉化為描述文本。訓練目標就是標準的交叉熵。import torch import torch.nn as nn # 示意image_feats.shape [batch, num_regions, feat_dim] # target_ids.shape [batch, seq_len] def train_generator(model, image_feats, target_ids, vocab_size): # 解碼器基于圖像特征和已生成的前綴 token 預測下一個 token logits model(image_feats, target_ids[:, :-1]) # [batch, seq_len-1, vocab_size] loss nn.CrossEntropyLoss(ignore_index0)( logits.reshape(-1, vocab_size), target_ids[:, 1:].reshape(-1) ) return loss這個階段不需要太長驗證集上 CIDEr 曲線開始走平就可以停。之后我建議再用一個輕量級的強化信號做幾個 epoch 的微調但它不是必須的。如果你的硬性評價指標壓力不大跳過這一步完全可行。4.3 訓練RNS排序器生成模型訓練好后固定權重開始構造排序器訓練數(shù)據(jù)。每一張訓練圖片走一遍 beam search得到 N 個候選句子再從參考描述里隨機抽一個作為正樣本用前面提到的三類策略構造負樣本。def train_ranker(ranker, image_feats, pos_ids, neg_ids, margin0.3): pos_score ranker(image_feats, pos_ids) # 正樣本可靠性分數(shù) neg_score ranker(image_feats, neg_ids) # 負樣本可靠性分數(shù) loss torch.relu(margin - pos_score neg_score).mean() return loss訓練排序器時有一個操作細節(jié)值得強調候選句子要從生成模型實際采樣出來而不是從參考描述里隨機切斷或復制。原因很簡單推理階段排序器面對的一定是生成模型自己吐出來的句子如果訓練時用的是“干凈的人工描述”分布就錯位了。這一點很多人第一次實現(xiàn)時會踩坑。4.4 推理階段接入重排序推理流程比訓練更簡單。原來你可能直接用 beam search 的 top-1 作為結果現(xiàn)在改成先生成 N 個候選句子逐句過一遍排序器最后取排序器分數(shù)最高的那句。def infer_with_ranker(generator, ranker, image_feats, beam_size16): candidates, scores generator.beam_search(image_feats, beam_sizebeam_size) # 對每個候選句子計算 RNS 綜合分數(shù) final_scores [ 0.3 * scores[i] 0.4 * ranker.visual_match(image_feats, candidates[i]) 0.3 * ranker.discriminative_score(image_feats, candidates[i]) for i in range(len(candidates)) ] best_idx torch.tensor(final_scores).argmax() return candidates[best_idx]這一步整體替換成本很低不需要改動生成模型內(nèi)部結構也不需要重新訓練主模型。我在實際項目中從決定接入到跑通線上效果只用了不到一周絕大多數(shù)時間都花在調負樣本構造策略上。5. 常見問題與排查技巧實錄5.1 候選都是爛句子重排救不回來這是最先要排除的情況。如果你把 beam search 的結果完整打印出來發(fā)現(xiàn) 16 個句子里有 14 個都是重復或明顯偏題的那說明問題在生成階段而不是排序器。先回去加大 repetition_penalty適當調低溫度或者檢查生成模型是不是訓練沒收斂。一個實用的判斷標準是候選池里至少有三分之一句子人眼看著是“有潛力但有小錯”的。如果達不到就別先折騰排序器。5.2 排序器在驗證集上反而變差典型反應是生成器沒動加了排序器后指標反而掉了。這時候先別懷疑 RNS 的思路大概率是負樣本構造出了問題。負樣本太簡單排序器學到的判別邊界特別粗糙真實候選句在它眼里全都差不多負樣本太難排序器又會被一些極端case帶偏出現(xiàn)分數(shù)震蕩。我的經(jīng)驗是先做一個最簡單的版本只用“跨圖錯配”這一種負樣本跑通流程保證排序器確實有效然后再逐步加入對象替換和語序打亂。這樣一旦效果變差你立刻知道是哪種負樣本引入的問題。5.3 指標漲了人眼看著別扭CIDEr 這類指標本質上是 n-gram 匹配度它天生偏好高頻出現(xiàn)的套話。排序器在訓練時如果過度擬合 CIDEr就會傾向于給那些“常見但平庸”的句子打高分。這會導致一個尷尬局面客觀指標漲了 2 個點但人眼評測時感覺模型變得更套路化。我的解決辦法是訓練排序器時不要把參考句子當唯一天花板而是在 loss 里加入一個正則項如果候選句子包含的圖像級對象與視覺檢測模塊給出的高置信度對象列表完全不匹配就額外懲罰。這個信號不用很強但能有效拉住模型讓它別為了指標好看而丟掉真實視覺信息。5.4 常見問題速查表現(xiàn)象可能原因排查方向候選句子反復出現(xiàn)同一詞組解碼階段重復懲罰不夠調高 repetition_penalty增大 beam size排序器分數(shù)分布太集中幾乎分不出高低負樣本太簡單替換成難負樣本提高對象級替換比例訓練排序器時 loss 遲遲不降margin 設置過大或數(shù)據(jù)配對錯誤降低 margin檢查是否同圖配對推理速度不符合要求候選數(shù)量多排序器計算量大減小 beam size給排序器做量化或蒸餾指標漲但人工評測差過度擬合 n-gram 指標加入視覺對象一致性正則我在自己業(yè)務場景里復現(xiàn)這套思路時最值錢的不是模型結構而是把“選出好句子”這一件小事真正當成獨立工程問題來對待。RNS 給我的最大啟發(fā)是當模型輸出不穩(wěn)定時先別急著加參數(shù)、加數(shù)據(jù)試著把生成和選擇拆開看看是不是選擇環(huán)節(jié)太隨意了。這個思路不僅能用在圖像描述上機器翻譯、文本摘要、對話系統(tǒng)其實都有同樣的問題。最后再分享一個小技巧負樣本的質量直接決定排序器上限這個環(huán)節(jié)非常值得你花最多的時間去調試。每調整一次負樣本構造策略都先在驗證集上用一個小樣本池跑一輪對比穩(wěn)定了再去全量訓練效率會高很多。