:從工程結構到模型訓練全解析)
簡介行人重識別ReID是計算機視覺中的關鍵任務目標是在不同攝像頭下準確匹配同一行人。這套Python工程代碼包面向深度學習初學者和視覺研究者提供了從數(shù)據(jù)準備到模型評估的完整實現(xiàn)覆蓋Market-1501等常用數(shù)據(jù)集的處理流程。壓縮包共11個文件全部為Python腳本大小約18KB包含數(shù)據(jù)加載與增強data_manager.py、dataset_loader.py、骨干網絡ResNet.py、損失函數(shù)losses.py、優(yōu)化器配置optimizers.py以及評估指標eval_metrics.py等模塊結構清晰適合按模塊逐步研讀便于定位數(shù)據(jù)、模型、優(yōu)化等核心邏輯。目前已有150人學習下載。通過分析源碼讀者可以掌握CNN提取行人外觀特征的方法理解triplet loss、center loss在度量學習中的作用并學會使用mAP、Rank-1等指標評估模型。代碼簡潔可運行兼顧理論講解與工程實踐是課程設計、論文復現(xiàn)或入門ReID研究的高性價比參考。1. 行人重識別是什么一個 zip 里裝的是一整套跨鏡檢索系統(tǒng)在商超、園區(qū)、地鐵站跨攝像頭找人靠人眼回看錄像動輒一兩個小時行人重識別Person Re-IdentificationReID就是讓深度學習模型學會“只看一次之后在幾百個陌生人里把 ta 找回來”。一個名為“基于深度學習的行人重識別.zip”的工程包解壓開通常不是一個訓練好的模型文件而是一整套從數(shù)據(jù)集組織、訓練到檢索評估的代碼管線這也是它和普通 demo 包最本質的區(qū)別。給定一張 query 圖模型輸出一個能區(qū)分“同一個人在不同攝像頭下長什么樣”的特征向量再和全庫 gallery 特征算相似度并排序。適合正在做檢測/分類、想轉入檢索方向的工程師或研究生目標是能復現(xiàn)、能改參、能部署。2. 先看懂工程結構再動手ReID 源碼包的標準套路與數(shù)據(jù)流拿到一個 ReID 的 zip多數(shù)版本的目錄結構都長得很像這要感謝 Market1501 和后來 BoT、TransReID 這些開源工程養(yǎng)成的社區(qū)習慣。先別急著跑 train.py花二十分鐘把目錄和數(shù)據(jù)流捋清楚后面能少踩一半坑。這個環(huán)節(jié)不依賴具體代碼風格任何基于 PyTorch 的 ReID 實戰(zhàn)項目案例基本都跑不出下面這套約定。2.1 一個典型 ReID 工程有什么從 train.py 到 evaluate.py常見工程解壓后頂層會有 train.py、evaluate.py 兩個入口以及 scripts、configs、datasets、models、losses、samplers、utils 這幾個目錄。scripts 是啟動用的 shell 腳本configs 是參數(shù)配置datasets 負責數(shù)據(jù)集加載和目錄校驗models 是骨干網絡和脖子模塊losses 是交叉熵、三元組等損失定義samplers 是批次采樣器utils 里是日志和評估工具。train.py 負責訓練evaluate.py 負責加載權重、抽取特征并計算 CMC/mAP。# 解壓并查看頂層結構先確認沒有嵌套目錄 unzip 基于深度學習的行人重識別.zip -d ReID cd ReID ls -R . | head -60 # 正常會看到 configs/ datasets/ models/ losses/ samplers/ utils/ train.py evaluate.py這段命令的作用是把工程解壓到 ReID 目錄并用 ls -R 遞歸列出結構head -60 控制輸出長度。如果解壓出來帶中文目錄名或者多嵌套了一層同名文件夾先用 mv 把它提到頂層否則后面相對路徑會全部失效。多數(shù)工程用相對路徑讀數(shù)據(jù)集目錄層級錯一位報錯信息還不會直接指到路徑上而是先以“找不到圖片”的形式出現(xiàn)。在 config 或啟動腳本里通常能看到這幾個核心參數(shù)數(shù)據(jù)根目錄 data_root、輸出目錄 save_dir、批次大小 batch_size、學習率 lr、訓練輪數(shù) epochs、骨干網絡名稱 arch、損失類型 loss_type、設備 device。這些參數(shù)大多能從命令行覆蓋意味著不改代碼也能換配置。判斷一個工程是否成熟一是看 evaluate.py 里有沒有獨立的評估協(xié)議二是看 datasets 里有沒有針對 Market1501 的標準目錄處理如果都不具備說明它只是一堆臨時腳本拼起來的復現(xiàn)價值有限別在它身上花太多時間。2.2 數(shù)據(jù)怎么流動一張行人圖到特征向量的完整鏈路在 ReID 里一張行人圖從讀入磁盤到輸出特征向量經過五個環(huán)節(jié)讀取圖片、數(shù)據(jù)增強、骨干網絡提取特征、降維層壓縮特征、L2 歸一化。訓練階段在降維后還要掛分類頭和三元組損失推理階段則直接用歸一化后的特征做余弦相似度。下面這段代碼把五個環(huán)節(jié)拆開方便對照工程里每一步在做什么。# 特征提取流水線示意讀圖 - 增強 - backbone - neck - L2歸一化 import torch import torchvision.transforms as T from PIL import Image # 1. 讀圖統(tǒng)一轉 RGB img Image.open(query/0002_c1s1_000451_00.jpg).convert(RGB) # 2. 推理增強縮放、中心裁剪、歸一化訓練還會加隨機擦除和翻轉 transform T.Compose([ T.Resize([256, 128]), # ReID 通用輸入尺寸高 256寬 128 T.CenterCrop([256, 128]), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) x transform(img).unsqueeze(0) # [1, 3, 256, 128] # 3. backbone 輸出 2048 維全局池化特征 # 4. neck 降到 512并做 BN推理時用 BN 后的結果即可 # 5. 最后做 L2 歸一化后續(xù)算余弦相似度 feat torch.nn.functional.normalize(neck_feat, dim1)這段代碼是工程里推理部分的最小形態(tài)。Resize 到 [256, 128] 是 Market1501 上約定俗成的輸入比例因為它匹配行人瘦長的外形先驗改成正方形會掉點。CenterCrop 在推理時是為了對齊訓練時的隨機裁剪統(tǒng)計。歸一化沿用 ImageNet 的 mean 和 std幾乎所有 ReID 工程都直接使用不必自行調整。代碼里的 neck_feat 是占位寫法實際是 backbone 輸出經過全局平均池化再過 BNNeck 的結果。實際工程里這五個環(huán)節(jié)會被封裝進 dataset 的getitem和 model 的 forwardDataLoader 的 num_workers 影響讀圖速度pin_memory 能減少數(shù)據(jù)從 CPU 拷貝到 GPU 的時間。調參時這些是常規(guī)項先確認它們沒有問題再懷疑算法本身。2.3 為什么行人重識別是「分類 度量」的混合問題剛上手的人最容易問訓練時明明用交叉熵把每個人 ID 當成一個類別來分為什么推理時輸出的不是類別而是一個向量這正是 ReID 和圖像分類的關鍵區(qū)別。分類任務里一張圖對應一個固定標簽ReID 里訓練集和測評集的人 ID 幾乎完全不相交模型必須學習“一個人的外觀特征如何跨攝像頭保持穩(wěn)定”這個能力只能靠度量學習來約束。常見做法是同時掛兩個頭一個 ID 分類頭用交叉熵逼著特征具有類別判別力一個三元組損失用難樣本挖掘把同一 ID 的特征拉近、不同 ID 的特征推遠。兩個頭共享 backbone 和 neck訓練結束后丟掉分類頭只保留特征提取部分。一句話概括交叉熵教模型“見過誰”三元組教模型“誰和誰像”兩者缺一結果都有明顯短板——只有交叉熵難樣本區(qū)分不開只有三元組訓練不穩(wěn)且收斂慢。在 zip 工程里判斷它做沒做度量學習最明顯的標志是有沒有 sampler 目錄和 triplet loss 定義。只有分類頭、沒有采樣器的版本通常是從分類項目改過來的半成品。像動手深度學習這類入門書里的分類代碼拿到 ReID 這里必須要補兩塊一個是難樣本采樣器一個是評估協(xié)議缺了它們訓練出來的模型很難在跨攝像頭檢索上拿到可用指標。3. 復現(xiàn)訓練全流程從 Market1501 到跑通 train.py 的最小命令這章直奔“照著做能跑通”。深度學習 PyTorch 生態(tài)里ReID 的復現(xiàn)難度主要不在網絡結構而在數(shù)據(jù)劃分和訓練參數(shù)對很多人來說這也是第一次理解“數(shù)據(jù)和指標一起決定模型好壞”。這里說的“跑通”不只是 loss 在下降而是訓練結束后 evaluate.py 能算出和論文同量級的 Rank-1 和 mAP。3.1 數(shù)據(jù)不“改壞”Market1501 的目錄約定Market1501 是 ReID 最常用的公開數(shù)據(jù)集包含 1501 個行人、32668 個檢測框采集自 6 個攝像頭。它發(fā)布時已經按訓練集、查詢集和候選集劃分好目錄名是 bounding_box_train、query 等。復現(xiàn)時最容易犯的錯是把這些目錄重命名或者在訓練集里混入 query。目錄結構一旦改動評估腳本按約定路徑找不到數(shù)據(jù)指標直接崩掉。Market1501 的關鍵目錄及用途如下目錄/文件內容本階段是否需要bounding_box_train訓練集751 個 ID必需query查詢集3368 張圖必需bounding_box_test候選集 gallery必需gt_queryquery 對應的標準答案標注re-ranking 時需要gt_bbox測試集行人框標注復現(xiàn)論文對比時需要# 假設工程根目錄是 ReID把數(shù)據(jù)集整理成工程能認的格式 cd ReID mkdir -p data/market1501 # 用軟鏈接而不是復制避免占雙倍硬盤也防止誤改原始文件 ln -s /path/to/Market1501/bounding_box_train data/market1501/bounding_box_train ln -s /path/to/Market1501/query data/market1501/query ln -s /path/to/Market1501/bounding_box_test data/market1501/bounding_box_test ln -s /path/to/Market1501/gt_query data/market1501/gt_query ln -s /path/to/Market1501/gt_bbox data/market1501/gt_bbox軟鏈接的 path 要根據(jù)實際解壓位置調整不要照抄。這里的關鍵是保持目錄名固定很多工程在 dataset 代碼里硬編碼了這些名字你改了它就得跟著改源碼。Market1501 的文件名本身帶標簽比如 0002_c1s1_000451_00.jpg前四位是行人 IDc1s1 是攝像頭號和場景號。有經驗的人會用文件名前四位統(tǒng)計訓練集 ID 數(shù)量如果比 751 少多半是解壓或移動過程中丟了文件。3.2 環(huán)境配置與最小訓練腳本在跑訓練前先把環(huán)境確認到位。ReID 對庫版本不挑剔但 torch 和 torchvision 的版本要配套CUDA 版本對應錯import torch 直接報錯。如果你手頭只有普通臺式機或筆記本沒法用 GPU也可以把 device 改成 cpubatch_size 調到 8 試跑一個 epoch能完整走通流程就算環(huán)境通過。# 創(chuàng)建虛擬環(huán)境并安裝依賴這是最省心的組合之一 conda create -n reid python3.8 -y conda activate reid pip install torch1.13.1 torchvision0.14.1 pip install numpy opencv-python pandas tqdm tensorboardtorch 1.13.1 配 torchvision 0.14.1 在多數(shù)顯卡驅動下都能直接裝到對應 CUDA 版本不必追新。opencv 用來讀圖和做數(shù)據(jù)增強tqdm 顯示進度tensorboard 看訓練曲線后面排錯會用到。深度學習環(huán)境配置到這里就夠了ReID 本身沒有額外依賴。訓練腳本的主體邏輯在多數(shù)工程里一致構建數(shù)據(jù)加載器、構建模型、定義損失、循環(huán) epoch。下面這段是清洗后的核心流程和真實工程對照著看能快速定位自己改壞的地方。# 訓練主流程核心片段P×K 采樣 ResNet50 BNNeck import torch from torch import nn from torch.utils.data import DataLoader # 假設 dataset 已按 Market1501 讀入train_set 返回 img, pid, camid # P×K 采樣P 個身份每人 K 張圖batch P*K sampler RandomIdentitySampler(train_set, num_instances4) loader DataLoader(train_set, batch_size64, samplersampler, num_workers4, pin_memoryTrue) model build_model(archresnet50, num_classes751, last_stride1, neckbnneck) model.cuda() # 雙頭損失交叉熵 三元組Adam 時 lr 建議從 3.5e-4 起步 criterion_id nn.CrossEntropyLoss() criterion_tri TripletLoss(margin0.3) optimizer torch.optim.Adam(model.parameters(), lr3.5e-4, weight_decay5e-4) lr_scheduler WarmupMultiStepLR(optimizer, milestones[40, 90], gamma0.1, warmup_epochs10) for epoch in range(120): model.train() for imgs, pids, _ in loader: imgs imgs.cuda() feats, logits model(imgs) # feats 給三元組logits 給分類頭 loss_id criterion_id(logits, pids) loss_tri criterion_tri(feats, pids) loss loss_id loss_tri optimizer.zero_grad() loss.backward() optimizer.step()這段代碼是 BoT 風格的訓練設置很多 zip 里的 train.py 就是它的完整版加命令行解析。batch_size 等于 64 時RandomIdentitySampler 默認是 16 個身份乘每人 4 張圖保證每個 batch 里同 ID 有足夠樣本供三元組挖掘。Adam 學習率 3.5e-4 配合 warmup前 10 個 epoch 線性升溫第 40 和 90 epoch 衰減 0.1 倍。loss_id 和 loss_tri 直接相加是最常見的組合部分工程會乘 0.5 權重差別不大。這里有個容易忽略的細節(jié)last_stride1。ResNet50 默認最后一級下采樣 stride 是 2ReID 為了保留更多空間細節(jié)會把它改成 1特征圖分辨率翻倍rank-1 通常能漲 2 到 3 個點。如果你的工程里沒這個參數(shù)訓練一次后指標偏低先檢查它。別讓 codex 跑深度學習模板代碼時替你悄悄把這類關鍵參數(shù)“優(yōu)化”掉ResNet50 的 last_stride、BNNeck、P×K 采樣這三個東西缺一個都算不上合格的 ReID 工程。3.3 訓練中途看什么日志里 loss 和 Acc 的含金量訓練日志通常長這樣“epoch 5, loss 3.21, acc 62.4, lr 0.00021”。新手最容易盯著 loss其實更該看定期在驗證集上算出來的 Rank-1 和 mAP。交叉熵的 acc 反映分類頭學得怎么樣但 acc 高不代表檢索排序好因為檢索要看候選集里的整體排序質量。epoch 10 loss 1.98 acc 78.5 cmc:63.2/79.1 mAP:52.4 lr 0.00018 epoch 40 loss 0.42 acc 92.3 cmc:85.6/93.4 mAP:72.8 lr 0.00018 epoch 90 loss 0.18 acc 96.1 cmc:89.2/95.7 mAP:78.1 lr 0.000035這份日志是典型的收斂過程第 10 個 epoch 時 mAP 已過半說明骨架和采樣器接對了第 40 個 epoch 時 acc 超過 92但 CMC 還在漲說明度量頭仍有潛力第 90 個 epoch 后學習率衰減兩個指標繼續(xù)爬升。如果在第 40 個 epoch 時 mAP 還不到 50多半是數(shù)據(jù)加載錯了或者采樣器沒生效而不是訓練不夠。先看數(shù)據(jù)再調模型這比加訓練輪數(shù)更管用。4. 模型選型與關鍵策略ResNet50 BNNeck 隨機擦除為什么是標配看完訓練流程自然會問這個工程里的模型結構能不能換先說結論能換但得不償失的情況很多。在 ReID 這個方向模型選型和訓練策略綁定得很緊深度學習模型那塊動一個參數(shù)后面的指標和顯存可能全變。4.1 三套主流骨架的取舍ResNet50、Swin/TransReID 與輕量網絡骨架參數(shù)量復現(xiàn)難度適用階段典型收益ResNet50約 25M低絕大多數(shù)工程默認穩(wěn)定和論文易對齊ResNet50 IBN約 25M中跨數(shù)據(jù)集泛化域泛化更好Swin-T / ViT-Base約 28M/86M中高追求 SOTA指標上線更高訓練敏感MobileNetV3/輕量骨干約 7M中邊緣部署速度優(yōu)先精度下降可接受ResNet50 是 ReID 圈事實上的“工業(yè)標準”論文對比都在它上面跑復現(xiàn)時最容易對齊指標。Swin 和 ViT 骨架在行人重識別上能到更高精度但對學習率、warmup、數(shù)據(jù)增強更敏感訓練不穩(wěn)是常態(tài)新手拿它當?shù)谝话嫒菀追嚒BN-Net 結構在跨數(shù)據(jù)集測試時優(yōu)勢明顯如果你后續(xù)要在別的監(jiān)控場景上直接用它比原版 ResNet50 更值得。輕量網絡適合深度學習模型部署的場景。實時檢索系統(tǒng)里攝像頭數(shù)量多單卡推理壓力大MobileNetV3 能把單張圖的推理時間壓到幾毫秒代價是 Rank-1 降 5 到 8 個點。對工程落地來說這個差距通常能用 re-ranking 或更好的數(shù)據(jù)增強補回來一部分。選型沒有絕對答案但第一版復現(xiàn)我一般只會用 ResNet50因為它把變量控制得最少。4.2 BNNeck、隨機擦除和 P×K 采樣各自解決什么問題這三個名字在 ReID 論文里頻繁出現(xiàn)作用各不相同。BNNeck 解決的是交叉熵和三元組對特征分布要求不一致的問題交叉熵希望特征的范數(shù)大、類別邊界明顯三元組希望特征落在超球面上、分布緊湊。BNNeck 在 neck 輸出的兩個分支上分別做處理分類頭分支加 BN度量分支不加損失可以在各自舒適的分布上優(yōu)化。沒有它兩個損失會互相拉扯訓練曲線震蕩明顯。隨機擦除Random Erasing是 ReID 里性價比最高的數(shù)據(jù)增強。行人經常被遮擋或者被其他行人擋住隨機擦除一塊矩形區(qū)域讓模型不要把注意力全壓在某個局部部件上。它和普通分類任務里的 CutOut 類似但擦除比例和時間點對 ReID 更敏感。P×K 采樣則是難樣本挖掘的基礎隨機采樣 P 個身份每個身份取 K 張圖這樣每個 batch 內部天然形成正負樣本對。下面是一段典型配置# 隨機擦除與 P×K 采樣的典型參數(shù) from random_erasing import RandomErasing train_transform T.Compose([ T.Resize([256, 128]), T.RandomCrop([256, 128], padding10), # 先隨機裁剪模擬位移 T.RandomHorizontalFlip(p0.5), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), RandomErasing(probability0.5, sh0.2, mean[0.4914, 0.4822, 0.4465]), ]) # 采樣器參數(shù)每 batch 16 個身份每個身份 4 張圖 sampler RandomIdentitySampler(dataset, num_instances4, batch_size64)擦除概率 0.5、遮擋面積比例上限 0.2這兩個數(shù)字是多數(shù)論文調試出來的穩(wěn)定區(qū)間。如果你碰到擦除后模型收斂變慢先降低 probability 而不是取消擦除取消后 Rank-1 通常掉 2 個點左右。RandomCrop 的 padding10 也很關鍵它給模型提供輕微位移不變性配合擦除能明顯提升跨攝像頭魯棒性。4.3 數(shù)據(jù)并行與 batch size 的關系batch size 在 ReID 里不是簡單的顯存問題它直接影響采樣質量。P×K 采樣下batch size 是 P 和 K 的乘積64 的 batch 對應 16 個身份、每個身份 4 張圖。如果把 batch size 降到 32常見操作是改成 8 個身份乘每人 4 張但 8 個身份會減少難樣本挖掘的覆蓋面指標在訓練中期就能看出差距。顯存不足時優(yōu)先降 K 而不是降 P比如 16 個身份乘每人 2 張batch 還是 32身份覆蓋面保住三元組只損失一點。# 單卡顯存不夠時的啟動參數(shù)示例 python train.py --batch-size 32 --num-instances 2 --lr 3.5e-4 # 多卡時先用 torchrun 指定可用 GPU再按卡數(shù)等比放大 batch torchrun --nproc_per_node2 train.py --batch-size 128 --lr 6e-4多卡訓練時 batch size 翻倍學習率也要相應調大常見做法是線性縮放。兩張卡從 64 變 128學習率從 3.5e-4 調到 6e-4不是簡單翻倍具體倍數(shù)靠驗證集微調。數(shù)據(jù)并行時注意每個 GPU 上的 batch 要獨立做 P×K 采樣不少工程在分布式改造時把采樣器丟了默認隨機指標掉得莫名其妙。5. ReID 復現(xiàn)避坑指標對不上、顯存不足和數(shù)據(jù)集損壞的 4 個真實問題這一章是給“訓練能跑但結果不對勁”的人準備的。下面的問題我在不同工程里見過多次每條都按現(xiàn)象、原因、解決的順序寫可以直接照著排查。5.1 坑zip 解壓后路徑亂套訓練一啟動就報“找不到文件”現(xiàn)象解壓、整理目錄后運行 train.py報錯 FileNotFoundError但路徑看起來是對的或者在 windows 上解壓后訓練正常換到 Linux 上同一份代碼報錯。原因這個 zip 工程在壓縮時可能帶了中文目錄名或者內部多嵌套了一層文件夾。Windows 上路徑大小寫不敏感掩蓋了問題Linux 上目錄里文件名大小寫不一致直接失效。還有一部分工程的 config 里寫的是相對路徑從不同目錄啟動 train.py 結果完全不同。解決解壓后第一件事是 cd 進工程目錄執(zhí)行 ls確認數(shù)據(jù)目錄真實位置然后用軟鏈接統(tǒng)一指向原始 Market1501不要復制重命名。啟動命令固定從工程根目錄執(zhí)行不要用 python /path/to/train.py 這種跨目錄方式。如果數(shù)據(jù)文件名有中文或空格先用 rename 批量清掉ReID 代碼普遍不做中文路徑處理。5.2 坑訓練 loss 正常下降但 Rank-1 和論文差 10 個點以上現(xiàn)象訓練日志里 loss 從 4 降到 0.3acc 到 95 以上但 evaluate.py 算出來的 Rank-1 只有 70 出頭論文里同配置是 89 以上。原因最大的可能性是評估階段和訓練階段數(shù)據(jù)預處理不一致。訓練用隨機裁剪、隨機翻轉、隨機擦除評估應該只用 Resize 加 CenterCrop。如果評估代碼里帶了 RandomCrop 或漏了 CenterCrop特征分布對不上指標直接崩。另一個常見原因是 query 和 gallery 劃分錯了有些工程會把 query 圖片也塞進 gallery檢索時自己檢索自己mAP 虛高但 Rank-1 偏低兩者矛盾時基本就是數(shù)據(jù)劃分問題。解決先固定隨機種子然后把 evaluate.py 里的預處理和訓練預處理逐行對比。再檢查評估代碼里是否在初始化 dataset 時用了 train 模式有些 dataset 類會在 mode 為 train 時自動加隨機增強。最后統(tǒng)計 query 和 gallery 是否有重疊文件名有就說明劃分腳本有問題回去看數(shù)據(jù)準備那步。5.3 坑batch size 調小后反而不收斂或者收斂極慢現(xiàn)象顯存不夠把 batch_size 從 64 調到 24 或 16結果訓練 30 個 epoch 后 loss 還在 2 以上mAP 不到 30。原因ReID 的 batch 不適合直接“整體縮放”。batch_size64 對應 16 個身份乘 4 張圖縮到 24 如果還是隨機采樣成 24 個不同身份每個身份只有 1 張圖三元組完全失效BN 層的統(tǒng)計量也因為 batch 太小而漂移。這屬于典型的“參數(shù)改了但沒改配套結構”。解決如果顯存只夠 batch 24改成 8 個身份乘每人 3 張圖至少保證每個身份有 3 張正樣本。同時把 BN 層改為凍結狀態(tài)或使用較小的 batch norm momentum比如 momentum 從 0.1 改成 0.01。再配合梯度累積每 2 個 batch 更新一次參數(shù)等效 batch 48這樣既保住采樣結構又不至于讓 BN 統(tǒng)計量亂跳。5.4 坑GPU 顯存直接 OOM連一個 epoch 都跑不完現(xiàn)象啟動訓練后幾秒鐘報 CUDA out of memory然后進程退出。有些人把 batch_size 調到 4 能跑但訓練完全失去意義。原因顯存爆掉不一定全是 batch size 的鍋。輸入圖片尺寸如果從 [256,128] 被改成 [384,192]顯存占用翻 2.25 倍backbone 如果用 ResNet50 的 last_stride2特征圖尺寸比 last_stride1 小一半占用更少但丟細節(jié)。還有一個隱藏項是 DataLoader 的 num_workers 開太多每多一個 worker 都會復制數(shù)據(jù)到共享內存峰值顯存看似不變但內存帶寬吃緊。解決先看這個 zip 工程里默認的 Resize 是不是被改成了大尺寸統(tǒng)一回到 [256,128]。再確認 last_stride 是否等于 1它影響顯存和精度的平衡。顯存仍不夠時用梯度累積替代調小 batch或者開 torch.cuda.amp 混合精度顯存能降一半ReID 訓練對 fp16 的敏感度比檢測低得多基本可以無痛用上。6. 驗證與進階用一張 query 手工走一遍檢索再談值不值得做訓練完不是終點你得親眼看到檢索結果才敢把它交給下游。這一章只提供一個最小驗證鏈路以及這條路繼續(xù)往深走的幾個方向。6.1 手工驗證加載權重、抽特征、算余弦相似度用 evaluate.py 能看到整體指標但看單張圖的檢索排序能更直觀地判斷模型學到的是“外觀”還是“某個攝像頭下的背景”。手工驗證只需要加載模型、抽 query 特征、遍歷 gallery 算相似度。# 手工檢索驗證輸出前 5 個結果的文件名 import torch import torch.nn.functional as F model.eval() with torch.no_grad(): q_feat F.normalize(model(transform_query(img_query).cuda())[1], dim1) g_feats [] # gallery 特征列表由 evaluate 階段緩存得到 names [] # 對應的文件名 for g_img, name in gallery_loader: f F.normalize(model(g_img.cuda())[1], dim1) g_feats.append(f); names.extend(name) g_feats torch.cat(g_feats, dim0) sims torch.mm(q_feat, g_feats.t())[0] # 余弦相似度矩陣 top5 torch.topk(sims, 5).indices.tolist() print([names[i] for i in top5])這段代碼里 model 的輸出取索引 [1]是因為前向返回了 (feats, logits) 或 (feats, pooled_feats) 兩個值具體取哪個看工程定義。驗證時用 L2 歸一化后的特征做矩陣乘法等價于余弦相似度。如果 top5 里出現(xiàn)了和 query 同一個 ID 但不同攝像頭的結果說明模型學到了跨攝像頭不變性如果 top5 全是同一個攝像頭下的圖大概率模型偷懶了它學到的是環(huán)境背景不是行人本身。6.2 進階方向re-ranking、多尺度測試與部署形態(tài)這個小流程跑通后你可以按預算選進階方向。re-ranking 是性價比最高的一項它把 gallery 內部的結構關系考慮進去通常能再漲 2 到 5 個點 mAP但推理時要把整個 gallery 特征送入內存計算適合離線檢索場景。多尺度測試是另一個穩(wěn)定技巧把同一張圖縮成 192x96、256x128、320x160 三份分別抽特征再拼接漲點不多但穩(wěn)定幾乎不花訓練成本。更接近交付形態(tài)的做法是把特征抽取封裝成 ONNX 或 TensorRT 的部署接口前接檢測模型后接向量數(shù)據(jù)庫做近似最近鄰檢索。ReID 模型本身不復雜但工程鏈路比訓練長得多。我第一次完整跑通這套鏈路時最深的教訓是不要在訓練腳本里省評估代碼訓練和評估的預處理、數(shù)據(jù)劃分必須同一個來源否則指標永遠是自欺欺人。先把這套手工驗證跑通再談優(yōu)化這條路值得投入它能幫你把零散的檢測和目標跟蹤串成真正能用的跨鏡檢索系統(tǒng)。希望幫到你。本文還有配套的精品資源點擊獲取