實(shí)戰(zhàn):用 LLaMA-Factory 讓視覺大模型學(xué)會(huì)無人機(jī)航拍目標(biāo)檢測(cè)(附避坑指南))
前言把檢測(cè)任務(wù)交給視覺語言模型VLM做是最近很多項(xiàng)目的嘗鮮方向——不用改架構(gòu)、不用設(shè)計(jì)檢測(cè)頭只要構(gòu)造好對(duì)話數(shù)據(jù)大模型就能看圖說話式地輸出目標(biāo)框。但真正上手之后你會(huì)發(fā)現(xiàn)微調(diào)能跑通不代表推理能用。本文完整記錄一次 Qwen2-VL-7B 在無人機(jī)航拍圖像上的微調(diào)實(shí)踐從數(shù)據(jù)構(gòu)造、訓(xùn)練參數(shù)選擇到推理階段翻車的四類問題與切圖補(bǔ)救方案全部踩過、全部給出解法。可直接抄配置。一、項(xiàng)目概況項(xiàng)目配置任務(wù)無人機(jī)俯視航拍城市問題識(shí)別7 類目標(biāo)檢測(cè)基礎(chǔ)模型Qwen2-VL-7B-Instruct微調(diào)框架LLaMA-FactoryLoRA / SFT硬件AutoDL 單卡 vGPU 32GB原圖分辨率3840 × 2160數(shù)據(jù)規(guī)模11469 張有目標(biāo) 9293 空標(biāo)簽 21767 個(gè)類別店外經(jīng)營(yíng)、占道經(jīng)營(yíng)、屋頂亂堆物料、臨街亂堆物料、暴露垃圾、工地?fù)P塵未苫蓋、交通標(biāo)線不清晰。其中屋頂亂堆物料與臨街亂堆物料位置不同、店外經(jīng)營(yíng)與占道經(jīng)營(yíng)核心區(qū)別是有無實(shí)體店面——這些易混淆類的設(shè)計(jì)會(huì)在 Prompt 里顯式給出。二、數(shù)據(jù)準(zhǔn)備2.1 先看數(shù)據(jù)分布再?zèng)Q定訓(xùn)練策略類別照片數(shù)框數(shù)屋頂亂堆物料501412749交通標(biāo)線不清晰23816551暴露垃圾22734077占道經(jīng)營(yíng)15633567店外經(jīng)營(yíng)10441508臨街亂堆物料10291412工地?fù)P塵未苫蓋306506合計(jì)1361030370兩個(gè)結(jié)論直接決定了后面的坑類別極不均衡最大類是最小類的 16 倍。這是后來模型只學(xué)會(huì)猜占道經(jīng)營(yíng)的直接原因??諛?biāo)簽負(fù)樣本只保留了 20%2176 張。這是后來誤檢率偏高的直接原因。建議空標(biāo)簽比例提到 40%~50%且必須是困難負(fù)樣本路面、斑馬線、陰影、路邊堆物、停車位、廣告牌、水漬、反光——這些才是誤檢的真正來源。2.2 標(biāo)注格式轉(zhuǎn)換原始標(biāo)注是 YOLO 格式歸一化的中心點(diǎn)坐標(biāo) 寬高而Qwen2-VL 原生支持的是絕對(duì)像素坐標(biāo) [x1, y1, x2, y2]轉(zhuǎn)換時(shí)注意兩點(diǎn)YOLO 的 (cx, cy, w, h) 都是 0~1 歸一化值要先乘原圖寬高再轉(zhuǎn)左上/右下角點(diǎn)空標(biāo)簽樣本保留 has_issuefalse 的對(duì)話輸出空列表讓模型學(xué)會(huì)沒有問題就輸出空。2.3 數(shù)據(jù)格式ShareGPT 圖像路徑LLaMA-Factory 的 VLM 訓(xùn)練數(shù)據(jù)用 ShareGPT 格式images 字段放圖片的絕對(duì)路徑{ conversations: [ { from: human, value: image請(qǐng)分析這張無人機(jī)航拍圖像…… }, { from: gpt, value: {\has_issue\: true, \issue_count\: 2, \issues\: [{\type\: \占道經(jīng)營(yíng)\, \bbox\: [1213, 856, 1892, 1290]}, {\type\: \暴露垃圾\, \bbox\: [2431, 1520, 2688, 1745]}]} } ], images: [/root/autodl-tmp/VLM_Fine_tuning/LLaMA-Factory/data/images/xxx.jpg] }再在 dataset_info.json 里注冊(cè) urban_train / urban_val 即可。2.4 Prompt 設(shè)計(jì)這步?jīng)Q定輸出格式的穩(wěn)定性System Prompt負(fù)責(zé)定義任務(wù)、類別邊界和易混淆區(qū)分User Prompt負(fù)責(zé)強(qiáng)制 JSON 輸出結(jié)構(gòu)我這里是few-shot可以讓vlm做參考你是一個(gè)專業(yè)的城市管理問題識(shí)別助手用于分析無人機(jī)俯視航拍圖像。 圖像來源為固定高度的無人機(jī)俯拍目標(biāo)以俯視角度呈現(xiàn)可能較小 同一圖像中可能存在多個(gè)同類或不同類問題也可能不存在任何問題。 你需要識(shí)別以下七類城市管理問題 1. 店外經(jīng)營(yíng)有實(shí)體店面經(jīng)營(yíng)物品從店鋪門口向外延伸擺放…… 2. 占道經(jīng)營(yíng)在馬路、人行道或小道上擺放經(jīng)營(yíng)無對(duì)應(yīng)實(shí)體店面…… 3. 屋頂亂堆物料建筑屋頂存在雜亂堆放的物品…… 4. 臨街亂堆物料臨街區(qū)域雜亂堆放建筑材料、雜物或貨物…… 5. 暴露垃圾生活垃圾、廢棄物等垃圾類物品未經(jīng)覆蓋或容納直接暴露在外…… 6. 工地?fù)P塵未苫蓋建筑工地的物料、土方或沙石未進(jìn)行覆蓋苫蓋…… 7. 交通標(biāo)線不清晰道路交通標(biāo)線存在磨損、褪色或模糊不清…… 注意事項(xiàng) - 店外經(jīng)營(yíng)和占道經(jīng)營(yíng)的核心區(qū)別在于有無實(shí)體店面 - 臨街亂堆物料和暴露垃圾的核心區(qū)別在于堆放物是否為垃圾類物品 - 屋頂亂堆物料和臨街亂堆物料的區(qū)別在于堆放位置是否在建筑屋頂 請(qǐng)嚴(yán)格按照指定JSON格式輸出結(jié)果不要輸出任何額外內(nèi)容。請(qǐng)分析這張無人機(jī)航拍圖像識(shí)別其中存在的城市管理問題 并嚴(yán)格按照以下JSON格式輸出 { has_issue: true或false, issue_count: 問題數(shù)量, issues: [ { type: 問題類型中文名稱, bbox: [x1, y1, x2, y2] } ] } 其中bbox為目標(biāo)在圖像中的絕對(duì)像素坐標(biāo)[x1,y1]為左上角[x2,y2]為右下角。 如果圖像中不存在任何問題has_issue輸出falseissue_count輸出0issues輸出空列表。幾個(gè)實(shí)測(cè)有效的細(xì)節(jié)易混淆類的判別標(biāo)準(zhǔn)要寫進(jìn) Prompt核心區(qū)別在于……比讓模型自己悟有效得多bbox 明確聲明絕對(duì)像素坐標(biāo)并在 User Prompt 里定義左上/右下角減少模型自由發(fā)揮不要輸出任何額外內(nèi)容這句話能顯著降低 JSON 前后混入廢話的概率。三、訓(xùn)練配置3.1 關(guān)鍵訓(xùn)練參數(shù)調(diào)了數(shù)十遍效果最佳版### model model_name_or_path: /path/to/Qwen2-VL-7B-Instruct template: qwen2_vl ### method stage: sft finetuning_type: lora lora_target: all-linear lora_rank: 64 lora_alpha: 128 lora_dropout: 0.05 ### 關(guān)鍵解凍視覺側(cè) freeze_vision_tower: false # 解凍視覺編碼器 freeze_multi_modal_projector: false # 解凍投影層 freeze_language_model: false ### dataset dataset: urban_train eval_dataset: urban_val cutoff_len: 8192 image_max_pixels: 1003520 # 約 1120×896 image_min_pixels: 1024 ### train per_device_train_batch_size: 1 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 lr_scheduler_type: cosine warmup_ratio: 0.05 num_train_epochs: 3.0 bf16: true gradient_checkpointing: true flash_attn: fa2 eval_steps: 500 save_steps: 1003.2 這些參數(shù)為什么這么設(shè)每一行背后都是一次翻車freeze_vision_tower: false——最重要的一行。第一版訓(xùn)練把視覺塔凍結(jié)了結(jié)果模型的眼睛完全沒參與學(xué)習(xí)坐標(biāo)定位一塌糊涂。解凍視覺編碼器和投影層讓視覺特征真正向定位任務(wù)對(duì)齊是坐標(biāo)精度提升最明顯的單點(diǎn)改動(dòng)。lora_rank: 64初版 16。rank16 對(duì)看圖描述夠用對(duì)輸出像素級(jí)坐標(biāo)容量明顯不足框的大小和位置都很隨意。定位任務(wù)建議從 32 起步條件允許直接 64。alpha 保持 2 倍 rank 關(guān)系。image_max_pixels: 1003520約 1120×896。這是 32GB 顯存下能穩(wěn)定跑的分辨率上限。注意一個(gè)關(guān)鍵事實(shí)原圖 3840×2160送進(jìn)模型會(huì)被下采樣到這個(gè)尺度——原圖里 10×10 像素的小目標(biāo)下采樣后只剩 1~2 個(gè) patch。這個(gè)矛盾就是后面所有推理問題的根源訓(xùn)練參數(shù)解決不了只能靠推理方案補(bǔ)救見第五節(jié)。batch_size 1 × 梯度累積 8。32GB 顯存下 7B 模型 圖像輸入實(shí)際 batch 只能開 1用梯度累積把有效 batch 撐到 8。配合 gradient_checkpointing 和 flash_attn: fa2顯存才壓得住。learning_rate: 1e-4。首輪從零訓(xùn) LoRA 用 1e-4 沒問題但如果是從已有 checkpoint 續(xù)訓(xùn)這個(gè)學(xué)習(xí)率偏危險(xiǎn)建議降到 1e-5 ~ 3e-5。3.3 訓(xùn)練結(jié)果單個(gè) epoch 約 7 小時(shí) 20 分鐘10321 張訓(xùn)練圖epoch 1 結(jié)束 train_loss 0.4676eval_loss 從 0.4302 一路降到 0.3771收斂平穩(wěn)訓(xùn)練 3 epoch中途服務(wù)器關(guān)機(jī)過一次從 checkpoint 的 LoRA 權(quán)重續(xù)跑步數(shù)從頭計(jì)。四、整圖推理四類問題一次翻車現(xiàn)場(chǎng)訓(xùn)練指標(biāo)很健康但拿整張 3840×2160 原圖直接推理效果離可用還差得遠(yuǎn)框不準(zhǔn)框整齊地堆在一起、大小一致但就是不貼目標(biāo)——模型在猜一個(gè)看起來合理的坐標(biāo)而不是在做幾何回歸框偏大普遍比真實(shí)目標(biāo)大 3~4 倍誤檢把馬路紋理、陰影檢成暴露垃圾占道經(jīng)營(yíng)漏檢小目標(biāo)密集的圖檢出數(shù)量明顯不足。根因分析這部分是全文最值錢的認(rèn)知VLM 不是檢測(cè)模型。傳統(tǒng)檢測(cè)器YOLO、DINO有 FPN 多尺度特征金字塔和 anchor 機(jī)制專治小目標(biāo)Qwen2-VL 的視覺編碼器把任意尺寸輸入映射到固定數(shù)量的 patch token小目標(biāo)在下采樣后特征幾乎被淹沒。它輸出 bbox 的方式是讓語言模型把坐標(biāo)說出來——本質(zhì)是序列預(yù)測(cè)不是幾何回歸??蛘R排列、大小一致正是語言模型在按統(tǒng)計(jì)規(guī)律輸出坐標(biāo)格式的典型癥狀。剩下的問題來自數(shù)據(jù)誤檢空標(biāo)簽只占 20%且缺路面/陰影/斑馬線這類困難負(fù)樣本類別偏科第一輪訓(xùn)練 1 個(gè) epoch 時(shí)模型只輸出占道經(jīng)營(yíng)——多數(shù)類樣本多模型學(xué)到了猜多數(shù)類永遠(yuǎn)不會(huì)太差的捷徑典型的類別不平衡 欠訓(xùn)練組合。五、切圖推理不改一行訓(xùn)練代碼效果大幅提升既然小目標(biāo)的矛盾是下采樣后特征被淹沒那就在推理側(cè)把目標(biāo)在輸入圖中的相對(duì)尺寸放大——把原圖切塊逐塊推理坐標(biāo)還原后合并去重。5.1 方案設(shè)計(jì)原圖 3840×2160 切成2×3 6 塊每塊約 1280×960目標(biāo)相對(duì)尺寸放大約 2.4 倍塊間保留15% overlap防止目標(biāo)正好壓在切割邊界上被截?cái)嘈∧繕?biāo)特別密集的圖進(jìn)一步切成15 塊每塊獨(dú)立推理后把坐標(biāo)歸一化 → 映射回子圖像素尺度 → 加子圖偏移量還原為原圖坐標(biāo)最后NMS 去重去掉 overlap 區(qū)域產(chǎn)生的重復(fù)框。5.2 核心代碼import cv2 import numpy as np def slice_inference(image, predict_fn, rows2, cols3, overlap0.15): 切圖推理逐塊推理 坐標(biāo)還原 NMS 合并 H, W image.shape[:2] # 帶重疊的切塊每塊的實(shí)際起止范圍 ys np.linspace(0, H, rows 1).astype(int) xs np.linspace(0, W, cols 1).astype(int) pad_y, pad_x int(H * overlap), int(W * overlap) all_boxes, all_scores, all_labels [], [], [] for i in range(rows): for j in range(cols): y0, y1 max(0, ys[i] - pad_y), min(H, ys[i 1] pad_y) x0, x1 max(0, xs[j] - pad_x), min(W, xs[j 1] pad_x) tile image[y0:y1, x0:x1] tw, th tile.shape[1], tile.shape[0] for box, score, label in predict_fn(tile): x1_, y1_, x2_, y2_ box # 模型偶爾輸出 0~1 相對(duì)坐標(biāo)統(tǒng)一轉(zhuǎn)成子圖像素坐標(biāo) if max(x1_, y1_, x2_, y2_) 1.5: x1_, y1_, x2_, y2_ x1_*tw, y1_*th, x2_*tw, y2_*th # 加上子圖左上角偏移還原到原圖坐標(biāo)系 all_boxes.append([x1_x0, y1_y0, x2_x0, y2_y0]) all_scores.append(score) all_labels.append(label) keep nms(all_boxes, all_scores, iou_thr0.5) # 去掉重疊區(qū)重復(fù)框 return [(all_labels[k], all_boxes[k]) for k in keep]坐標(biāo)還原的完整鏈路模型輸出相對(duì)子圖→ 歸一化判斷 → 乘子圖實(shí)際尺寸 → 加偏移量 → 原圖坐標(biāo) → NMS。實(shí)測(cè)中模型輸出坐標(biāo)制式偶爾漂移有時(shí)絕對(duì)像素、有時(shí) 0~1 相對(duì)坐標(biāo)所以代碼里保留了一次 1.5 的判斷做自適應(yīng)。5.3 效果小目標(biāo)的定位與識(shí)別精度明顯改善密集小目標(biāo)場(chǎng)景的漏檢也大幅緩解整體達(dá)到可演示水平。代價(jià)是推理次數(shù)變成 6~15 倍需要按業(yè)務(wù)延遲做權(quán)衡。六、避坑清單都是真金白銀換來的坐標(biāo)系要全鏈路對(duì)齊。訓(xùn)練數(shù)據(jù)是原圖絕對(duì)像素坐標(biāo)模型看到的是縮放后的圖推理時(shí)模型輸出有時(shí)是 0~1 相對(duì)坐標(biāo)——每個(gè)環(huán)節(jié)都顯式確認(rèn)坐標(biāo)制式能省掉一整天的 debug。視覺塔必須解凍。freeze_vision_tower: false不然模型的眼睛不參與學(xué)習(xí)定位全靠蒙。LoRA rank 別低于 32。定位任務(wù)對(duì)容量比描述任務(wù)敏感得多16 會(huì)明顯不夠用??諛?biāo)簽不是垃圾數(shù)據(jù)是壓制誤檢的主力。20% 不夠40%~50% 更穩(wěn)且優(yōu)先補(bǔ)充長(zhǎng)得像目標(biāo)的困難負(fù)樣本。類別不平衡要在數(shù)據(jù)層處理過采樣少數(shù)類 / 加權(quán) loss否則模型會(huì)走猜多數(shù)類的捷徑。密集目標(biāo)的 JSON 輸出可能被 max_new_tokens 截?cái)嘁粓D 15 個(gè)目標(biāo)的 JSON 很容易超 1024 token漏檢排查時(shí)記得檢查這一項(xiàng)——雖然多數(shù)時(shí)候主因還是小目標(biāo)特征太弱模型根本沒看見。OpenCV 畫中文標(biāo)簽會(huì)變方塊。服務(wù)器裝 fonts-wqy-zenhei或者改用 PIL 繪制中文。長(zhǎng)訓(xùn)會(huì)遇上斷點(diǎn)續(xù)訓(xùn)。云端實(shí)例關(guān)機(jī)中斷很常見從 checkpoint 的 LoRA 權(quán)重續(xù)跑即可續(xù)訓(xùn)時(shí)把學(xué)習(xí)率降到 1e-5 ~ 3e-5防止把已收斂的權(quán)重沖飛。訓(xùn)練 loss 正常 ≠ 推理可用。SFT 收斂只說明模型學(xué)會(huì)了輸出格式VLM 做檢測(cè)的天花板由架構(gòu)決定沒有 FPN就沒有小目標(biāo)精度上限的保證。七、后續(xù)優(yōu)化方向按投入產(chǎn)出排序推理后處理零訓(xùn)練成本NMS 的 IoU 閾值從 0.5 收緊到 0.35按面積過濾明顯異常的框超過原圖 30% 基本是誤檢小于 0.01% 基本是噪聲。YOLO 兩階段流水線最推薦用現(xiàn)成的 YOLO 格式標(biāo)注直接訓(xùn)一個(gè) YOLOv8負(fù)責(zé)在哪里Qwen2-VL 只對(duì) YOLO 框出的 crop 做分類確認(rèn)負(fù)責(zé)是什么。檢測(cè)器的 FPN 結(jié)構(gòu)天然適配小目標(biāo)VLM 的語義理解能力用在刀刃上現(xiàn)有標(biāo)注零額外處理。GRPO 強(qiáng)化訓(xùn)練長(zhǎng)期在 SFT 基礎(chǔ)上設(shè)計(jì) IoU 獎(jiǎng)勵(lì) 分類獎(jiǎng)勵(lì) 格式獎(jiǎng)勵(lì)讓定位從語言模型猜坐標(biāo)進(jìn)化到真正優(yōu)化幾何精度。前提是先做 rule-based reward 離線驗(yàn)證否則 bbox 格式還漂著就上 RL會(huì)直接學(xué)歪??偨Y(jié)這次實(shí)踐完整的路線是ShareGPT 格式構(gòu)造帶嚴(yán)格 JSON 輸出模板的檢測(cè)數(shù)據(jù) → LLaMA-Factory 上解凍視覺塔的 LoRA 微調(diào)rank64→ 面對(duì)高分辨率小目標(biāo)用切圖推理 坐標(biāo)還原 NMS 補(bǔ)救。最想留給你的一句話VLM 做檢測(cè)微調(diào)解決的是輸出格式和語義對(duì)齊解決不了小目標(biāo)幾何定位——后者要么靠切圖推理緩解要么老實(shí)上兩階段流水線。認(rèn)清這個(gè)邊界能幫你省下大量調(diào)參時(shí)間。如果這篇對(duì)你有幫助歡迎點(diǎn)贊收藏踩過別的坑也可以在評(píng)論區(qū)交流。我做的不一定是最好的歡迎大家討論~環(huán)境說明Qwen2-VL-7B-Instruct / LLaMA-Factory / AutoDL vGPU 32GB / PyTorch flash-attn