據(jù)增強與特征對齊實戰(zhàn)指南)
簡介基于生成對抗網(wǎng)絡GAN的行人重識別Python實現(xiàn)完整覆蓋數(shù)據(jù)預處理、模型訓練、日志記錄與結果可視化流程適用于計算機視覺方向畢業(yè)設計、課程設計及入門進階。項目代碼結構清晰包含main、model、utils等核心模塊配套介紹文檔、實驗報告PPT/PDF及多組訓練日志與效果圖便于對照理解GAN在行人重識別任務中的實際應用與調(diào)參思路。壓縮包共96個文件以jpg圖像、py源碼、txt說明為主另有yml配置、png圖表、md文檔及許可證文件整體大小約36.97MB。已有160人瀏覽學習適合具有一定Python與深度學習基礎、希望快速搭建行人重識別實驗環(huán)境或開展二次開發(fā)的開發(fā)者。下載解壓后建議將項目重命名為英文路徑再運行可依此實現(xiàn)數(shù)據(jù)增強、網(wǎng)絡結構替換等個性化擴展。1. GAN行人重識別不是玄學是數(shù)據(jù)增強和特征對齊的實活用GAN深度學習生成對抗網(wǎng)絡解決行人重識別ReID問題聽起來像兩件事硬湊真正跑過之后才知道它處理的是跨攝像頭特征漂移的核心痛點同一件衣服在不同攝像頭下顏色、亮度、視角全變了模型很容易把同一個人判成兩類。這份Python源碼把生成對抗網(wǎng)絡作為數(shù)據(jù)增強與特征對齊模塊接進行人重識別主流程覆蓋從數(shù)據(jù)預處理到訓練出圖的完整鏈路。它適合正在做畢設、課程設計的學生也適合想快速理解GAN落地方式的從業(yè)者。源碼里main.py、model.py、utils.py、ops.py分工清楚不需要從零搭模型先跑通再改自己的數(shù)據(jù)集就行。下面按“原理—部署—調(diào)參—避坑—二次開發(fā)”的順序把可復現(xiàn)的操作直接放出來。2. 生成對抗在行人重識別里的位置給數(shù)據(jù)補樣本給特征對齊鋪路2.1 行人重識別難的不是“認出人”而是認出同一個人的不同外觀行人重識別本質(zhì)是圖像檢索問題給定一張query查詢圖在gallery底庫里找出同一個ID的圖片。它和分類任務最大的區(qū)別在于訓練時模型見過每個ID但測試時出現(xiàn)的行人往往是訓練集里沒見過的。換句話說ReID要學的是“身份特征”而不是“這個人是誰”這就要求網(wǎng)絡提取的特征跟攝像頭視角、光照條件解耦。實際數(shù)據(jù)里這種變化有多夸張以公開數(shù)據(jù)集為例同一個演員穿同一件衣服在攝像頭A下是正午強光、顏色飽和到攝像頭B變成傍晚暗光、整體偏藍綠再切到攝像頭C可能只有一個背影連人臉都看不見。視覺差異大到你很難相信這是同一個人。傳統(tǒng)的解法是設計顏色直方圖、紋理特征這些手工描述子后來換成了CNN自動提取特征但CNN同樣會偷懶。訓練數(shù)據(jù)里如果某個ID只在特定攝像頭下出現(xiàn)網(wǎng)絡就會傾向于記住“這一個攝像頭風格下的外觀”而不是抽象出更穩(wěn)定的身份信息。這種偷懶在深度學習里叫捷徑學習損失函數(shù)上根本看不出來一跨域就露餡。GAN介入的動機就在這里。既然樣本在不同域之間差異大那就主動構造這種差異讓模型提前適應這就是生成對抗網(wǎng)絡在ReID里的價值。它不改變識別網(wǎng)絡的推理邏輯而是在訓練數(shù)據(jù)層面把域的多樣性補出來或者在特征層面逼迫模型對齊不同域的表達。理解這一層后面看代碼和調(diào)參就有方向了。2.2 GAN在ReID里的兩個常規(guī)用法風格遷移與特征對齊ReID里用GAN常見做法大約分兩派。第一派是圖像風格遷移代表思路是CamStyle和SPGAN。先訓練一個圖像翻譯模型把攝像頭A下的行人圖像轉(zhuǎn)換成攝像頭B的風格再把生成的圖片混進訓練集。原始數(shù)據(jù)里本來只有各個攝像頭各自的風格經(jīng)過風格遷移后模型能看到“A攝像頭視角、B攝像頭色調(diào)”的混合樣本數(shù)據(jù)多樣性翻倍跨域能力也會隨之提升。我在實際調(diào)這類方案時有個感受純風格遷移生成的圖邊緣容易發(fā)虛背景結構也會變形。所以成熟的實現(xiàn)里通常還會疊加循環(huán)一致性損失要求生成完再還原回去和原圖盡量一致這樣能保住行人輪廓和背景布局。SPGAN那套思路則更進一步在生成時加了一個“自相似性”約束保證生成前后的身份信息一致防止風格變了之后人也被換了張臉。第二派是特征對齊。生成器不再直接生成圖片而是通過對抗訓練約束特征分布判別器負責判斷特征是從真實圖片提取的還是從生成圖片提取的。當判別器分不清時說明特征已經(jīng)跨越了域差異這往往比單純堆數(shù)據(jù)更高效因為特征空間的維度比像素空間小得多收斂速度也更快。這兩條路不是非此即彼實際項目經(jīng)常先用風格遷移擴充數(shù)據(jù)再在網(wǎng)絡末端掛一個判別器做特征對齊代價是訓練鏈路變長、超參數(shù)變多。2.3 這份源碼里GAN是怎么接進來的拿到源碼后建議重點看四個文件model.py定義生成器和判別器的結構ops.py封裝卷積和反卷積這類基礎算子utils.py負責數(shù)據(jù)加載和工具函數(shù)main.py把整個訓練流程串起來。從dcgan.yml這個環(huán)境配置文件名和main.py的調(diào)用關系看生成對抗部分按DCGAN那一套約定搭的訓練循環(huán)也是標準GAN的交替更新方式先固定生成器訓練判別器再固定判別器訓練生成器循環(huán)往復。我一般會先按三步確認一個GAN-ReID源碼的架構第一步打開main.py搜索optimizer相關代碼確認生成器和判別器是不是分開優(yōu)化。如果只有一個優(yōu)化器同時管兩個網(wǎng)絡訓練基本上不會穩(wěn)定。第二步搜索loss計算部分看對抗損失用的是BCELoss還是MSELoss。DCGAN系列常用BCELoss而一些改進版本會用最小二乘損失放緩梯度消失。第三步看有沒有身份損失或三元組損失。如果只有對抗損失生成器只負責“像圖”不負責“像這個人”最后生成的樣本對識別任務幫助有限。這套檢查步驟大概十分鐘就能走完但對理解后面參數(shù)怎么調(diào)很有幫助。ops.py里還有幾個值得留意的點比如卷積層往往用stride2替代池化生成器里的反卷積層會逐層把特征圖放大回原尺寸。這些算子的輸出通道數(shù)和kernel_size決定了生成圖像的清晰度如果發(fā)現(xiàn)生成圖有棋盤格紋路多半是反卷積重疊區(qū)域處理不當這是GAN落地里很經(jīng)典的結構性坑。2.4 判別器不只是判真假還要“認識”人有一個容易忽略的點ReID里的判別器如果只輸出真/假生成器只需要學會像素層面像圖根本不用關心圖里是誰。這在行人重識別里是不夠的常見解法是在判別器后面再接一個輔助身份分類頭或者讓判別器同時判斷“真不真”和“是不是同一個人”。后者需要把訓練樣本改成三元組或?qū)Ρ葘Φ男问綄崿F(xiàn)上更麻煩但身份約束更直接。源碼日志里有G loss和D loss的記錄從數(shù)值走勢能看出兩個損失經(jīng)常此消彼長這就是對抗訓練的本質(zhì)判別器太強生成器梯度消失圖片糊成一片生成器太強判別器輸出失去參考意義訓練白跑。所以在ReID場景里我不會只看loss收斂還會定期把生成器的輸出存成圖片肉眼看它生成的行人輪廓是否清晰、衣服顏色是否合理。這個習慣能省掉大量無效調(diào)試時間第五章里要講的幾個坑也和這點直接相關。3. 把源碼跑起來環(huán)境、數(shù)據(jù)預處理與訓練三件事3.1 環(huán)境安裝用dcgan.yml把依賴一次裝齊第一步永遠是環(huán)境。壓縮包里給了dcgan.yml這是conda的環(huán)境導出文件鎖定了一整套項目依賴。我習慣先把壓縮包解壓到一個純英文路徑再用conda創(chuàng)建環(huán)境conda env create -f dcgan.yml conda activate dcgan第一行依據(jù)dcgan.yml里的依賴列表創(chuàng)建虛擬環(huán)境包括Python解釋器版本、PyTorch、OpenCV這些核心庫第二行激活環(huán)境。經(jīng)驗是不要直接跑在base環(huán)境里深度學習庫的版本錯位會導致各種莫名其妙的算子報錯尤其在CUDA版本不一致時癥狀還不一樣。用VSCode跑的話記得在右下角把解釋器切到dcgan這個虛擬環(huán)境否則你裝了半天依賴運行時用的還是另一個環(huán)境。裝完先驗證環(huán)境再往下一步走import torch print(torch.__version__) print(torch.cuda.is_available())如果cuda.is_available()返回False說明當前PyTorch和顯卡驅(qū)動不匹配訓練時會全部跑在CPU上。這個模型不算大CPU也能跑但速度會慢很多臨時調(diào)參還好真要訓練完一個完整實驗會非常煎熬建議先把驅(qū)動和CUDA版本對齊。如果顯卡比較老也可以考慮裝對應老版本的PyTorch不要盲目追求最新版本。3.2 數(shù)據(jù)預處理resizeImage.py與prepare.py先過一遍數(shù)據(jù)是ReID項目里最容易被忽視的一環(huán)。項目里提供了resizeImage.py和prepare.py兩個腳本前者的作用是把原始圖片統(tǒng)一尺寸后者的作用是把圖片路徑和身份標簽整理成訓練需要的索引格式。我建議第一次使用老老實實按順序跑python resizeImage.py --input_dir ./data/raw --output_dir ./data/resized --size 256 128 python prepare.py --data_dir ./data/resized --output ./data/index.txt第一句把原始圖縮放到256x128這是行人重識別里比較常用的寬高比接近人體站立形態(tài)第二句生成index.txt每一行記錄一個圖片路徑和它對應的身份標簽。注意resize的尺寸要和模型輸入尺寸一致不然訓練時tensor形狀對不上報錯會非常直接。源碼里還有一個changeIndex.py它的作用是在數(shù)據(jù)集標簽不連續(xù)時重新編號。很多公開數(shù)據(jù)集的ID編號是0、1、2這樣連續(xù)的但自己采集的數(shù)據(jù)經(jīng)常是亂的不重編號喂給模型容易出邊界問題。跑完預處理、訓練前還有一步很關鍵的抽檢。prepare.py生成的標簽文件先頭尾各看幾行head -n 5 ./data/index.txt觀察標簽格式是否規(guī)范。如果數(shù)據(jù)集的圖片命名是“ID_攝像頭編號_幀號”這種規(guī)則腳本大概率是按規(guī)則解析的如果命名不規(guī)范標簽就會錯位同一個ID被當兩個ID、或者兩個ID被并成一個ID的情況都有可能發(fā)生。這類錯誤在loss曲線上不容易暴露等到測試階段才發(fā)現(xiàn)就晚了。我一般在預處理后會隨機抽幾張圖人工確認圖片和標簽對得上再進入訓練環(huán)節(jié)。3.3 訓練啟動main.py的參數(shù)與一條可用的啟動命令環(huán)境裝好、數(shù)據(jù)準備好就可以啟動訓練了。main.py是入口核心參數(shù)主要是三個trainsize控制batch sizelearn_rate控制學習率epochs控制迭代輪數(shù)。我一般會這樣起python main.py --trainsize 4 --learn_rate 0.0001 --epochs 16trainsize4意味著每次喂4張圖給網(wǎng)絡顯存占用小訓練穩(wěn)定learn_rate0.0001是GAN任務里比較常見的起步值太大容易振蕩epochs先設16確認流程能跑通再往上加。跑起來之后終端會輸出每個iteration的G loss和D loss同時日志會寫進文本文件目錄下的log1.txt、log2.txt就是這類產(chǎn)物。如果機器顯存比較充裕比如12G以上可以試試trainsize8或16。源碼里也看到過size16配learn_rate0.0002的實驗記錄那張圖從文件名就能看出來loss走勢明顯比size4陡峭但這不代表效果更好。第一次跑通不建議一上來就開大batch一方面顯存風險高另一方面GAN對batch size變化敏感小batch先驗證代碼邏輯最穩(wěn)。訓練結束后工作目錄里會多出幾張結果圖類似result1.jpg、result2.jpg這是程序自動保存的生成器輸出。到這里先別急著宣布成功按下一章的方式讀日志、對比中間圖確認訓練曲線是健康的再繼續(xù)。4. 訓練參數(shù)落到日志size、學習率與迭代輪數(shù)怎么配4.1 batch size與學習率項目日志里的兩組經(jīng)驗值壓縮包里的結果圖命名本身就是一份調(diào)參記錄trainsize4learn0.0001、trainsize16learn0,0002。這就是兩組典型的對比實驗一次是小batch配小學習率一次是大batch配稍大學習率。從日志看size4時每輪步數(shù)多、loss曲線平緩適合觀察模型是否在收斂size16時單步信息量大、收得快但對學習率敏感得多。GAN任務里batch size不能直接參考分類任務的經(jīng)驗。分類任務里大batch提升訓練速度但GAN里batch太大容易讓判別器在單個batch內(nèi)看到過多模式梯度方差變大。我通常把batch size理解為“判別器每次更新能參考多少真實分布信息”size4時看到樣本少決策邊界粗糙但訓練穩(wěn)定size16時更貼近真實分布卻對學習率更挑剔配0.0002就容易振蕩。學習率的經(jīng)驗區(qū)間是這樣G和D共用同一個優(yōu)化器配置時0.0001到0.0002是安全區(qū)間。低于0.0001訓練太慢高于0.0003很容易出現(xiàn)G loss驟升、D loss歸零的訓練失衡。如果換成更深的backbone學習率還要再往下調(diào)常見做法是每提升一檔網(wǎng)絡深度學習率減半。顯存占用上size4大概在2G級別size16大概在8G以上具體看backbone復雜度這個數(shù)字僅供參考訓練時可以用nvidia-smi實時盯一眼顯存余量。4.2 日志文件的讀法G loss、D loss和生成圖的三角驗證終端輸出只是冰山一角。項目運行時產(chǎn)生的log1.txt、log2.txt、日志2.txt、日志3.txt建議每輪都保留下來。我讀日志時會同時跟蹤兩條線G loss和D loss。正常訓練里兩條線是交替起伏的幅度逐漸變小最后收斂到同一個數(shù)量級??匆欢文M日志感受一下iter 100 | G loss: 1.3241 | D loss: 0.8762 | lr: 0.0001 iter 200 | G loss: 1.5012 | D loss: 0.6543 | lr: 0.0001 iter 300 | G loss: 1.2867 | D loss: 0.7104 | lr: 0.0001這段日志里D loss在0.65到0.88之間波動G loss在1.28到1.50之間波動兩者都在緩慢變化但幅度可控屬于“還在對抗、沒崩”的狀態(tài)。D loss長期趨近于0則說明判別器太強生成器的梯度幾乎消失后續(xù)生成的樣本會越來越敷衍兩邊同漲同跌也不正常說明兩個網(wǎng)絡都沒有從對方那里學到有效信息。真正健康的對抗是兩條線像兩條擰在一起的繩子有起伏但不會有一方長期碾壓另一方。只看日志也不夠。生成器輸出圖要定期檢查項目把每個階段的結果圖都存下來比如result1.jpg、result2.jpg對比不同輪次的生成圖能看到質(zhì)量變化。如果圖從模糊逐漸變得清晰說明對抗訓練在推進如果前幾輪還清楚、后面反而崩成噪點多半是訓練失衡或?qū)W習率太高回去翻日志多半能找到D loss在某個輪次斷崖下跌的記錄。日志、結果圖、識別指標三個信號互相印證這件事在調(diào)GAN時怎么強調(diào)都不過分。4.3 輪數(shù)不是越多越好10輪和16輪的中段對比項目里有個文件名是“16-10輪size4迭代”內(nèi)容是訓練到第10輪時的生成效果。這個命名透露出一個關鍵習慣訓練中段一定要保存中間狀態(tài)。很多新手是把訓練跑完才看結果發(fā)現(xiàn)效果不行只能從頭再來連問題是“欠擬合”還是“訓崩了”都分不清。保存中間輪次的生成圖和模型權重等于給整個訓練過程拍了X光片。下面用表格對比一下兩組典型配置的觀察點配置現(xiàn)象判斷size4, lr0.0001loss平緩、生成圖逐步清晰穩(wěn)定適合長訓size16, lr0.0002loss下降快、中段有反彈有效但風險高建議早停這張表的意思是size16配0.0002不是不能用而是它收斂快、波動也大需要中段介入觀察。如果第10輪生成圖已經(jīng)不錯第16輪反而變差說明后段訓練出現(xiàn)過擬合或模式坍塌應該以第10輪的權重為準而不是無腦用最后一個epoch。實操上我一般每5輪存一個checkpoint存的時候同時在文件名里標注loss均值和該輪生成的樣例圖這樣最后無論如何都有后悔藥可吃。5. 避坑指南從中文路徑到生成器不收斂的四個實戰(zhàn)坑5.1 中文路徑導致的數(shù)據(jù)加載失敗現(xiàn)象解壓后直接雙擊運行圖片一張都沒加載進來報FileNotFoundError或者“cant open image”數(shù)據(jù)量為0。原因項目在Windows下對中文路徑的編碼處理與系統(tǒng)默認編碼不一致尤其是路徑里帶“畢業(yè)設計”“源碼”這類中文目錄名時圖片讀取階段就會掛掉。壓縮包里的“項目必讀.txt”特意強調(diào)這一點說明踩中的人不少。解決解壓后立即重命名為純英文路徑路徑里不要有空格也不要放在容易被網(wǎng)盤同步干擾的目錄下。數(shù)據(jù)集目錄名同樣保持英文因為prepare.py把路徑寫進index.txt后再被讀取中文路徑會經(jīng)過二次編碼問題會被放大。每次換機器跑項目第一件事就是檢查路徑這個習慣能替我省掉大量排查時間。5.2 訓練剛開始就顯存不足現(xiàn)象訓練啟動幾秒后終端刷出CUDA out of memory程序直接退出。原因trainsize設太大或者顯卡本來就只有4G左右顯存。這個項目在size4時占用不大但調(diào)到size16后顯存占用會直線上升小顯存卡基本必爆。解決把trainsize降到4或2。另外訓練時關掉瀏覽器、IDE這些占顯存的程序。如果確實需要大batch可以試試把輸入圖從256x128降到224x112顯存占用大約能下降四分之一但要注意和resizeImage.py里的尺寸保持一致否則模型輸入維度對不上。改代碼時盡量復用中間變量避免在forward里保留太多臨時tensor也能擠出一點顯存余量。5.3 生成器輸出灰色塊或噪點現(xiàn)象訓練結束生成的圖片看起來像馬賽克甚至一整片灰完全看不到人形。原因最常見的是生成器輸出層的激活函數(shù)和預處理時的歸一化范圍不匹配。數(shù)據(jù)歸一化到[-1,1]時輸出層用sigmoid那圖整體發(fā)灰歸一化到[0,1]時輸出層用tanh就會出現(xiàn)過曝一樣的白斑。另一種情況是訓練沒收斂但如果圖是全灰而不是模糊人形優(yōu)先懷疑歸一化。解決先查預處理代碼里歸一化到哪個范圍再查生成器最后一層用的什么激活函數(shù)兩者對齊。源碼里ops.py靠近輸出層的位置就能看到這層的定義改一行代碼的事排查起來卻可能花掉一整個下午。如果歸一化和激活函數(shù)都對那就去看訓練日志確認生成器是否在中途loss飆升過。從頭跑一遍訓練之前先拿一張測試圖過一遍生成器前向用肉眼確認輸出張量的數(shù)值范圍這條檢查步驟能過濾掉一半以上的圖像異常問題。5.4 D loss歸零生成器開始“摸魚”現(xiàn)象日志里D loss一路跌到0.00xG loss反而緩慢上升生成圖從清晰變得模糊。原因判別器在對抗中完全碾壓了生成器導致生成器梯度消失。觸發(fā)條件通常是學習率太大或者判別器每輪更新次數(shù)太多、學得太快。生成器梯度沒了之后它就不再更新輸出的圖退化成固定模式甚至噪聲。解決把學習率降到0.0001以下或者讓判別器每兩次更新才允許生成器更新一次給生成器追趕的空間。我自己的習慣是看到D loss連續(xù)低于0.1時先降學習率再把判別器的更新頻率調(diào)低等D loss回到0.5到0.8區(qū)間再恢復原設置。這條對ReID任務特別重要因為生成器一旦摸魚后面風格遷移出來的全是模糊色塊喂給識別網(wǎng)絡只會幫倒忙。GAN的損失函數(shù)設計是一門取舍判別器太弱生成圖粗糙判別器太強生成器罷工調(diào)到兩者平衡才是這個任務里真正費時間的部分。6. 把GAN結果接進識別流程一種順手好用的驗證方式訓練完生成器之后不要停留在“看圖”這一步。更實用的做法是把生成器接進數(shù)據(jù)加載流程里做在線增強然后用識別指標驗證GAN到底有沒有起作用。import torch def augment_with_generator(batch, generator, augment_ratio0.5): batch: 原始訓練批次張量 generator: 訓練好的生成器, eval模式 augment_ratio: 每個batch里被增強的樣本比例 batch batch.clone() n_aug int(batch.size(0) * augment_ratio) if n_aug 0: return batch with torch.no_grad(): gen_part generator(batch[:n_aug]) # 原圖與生成圖加權融合, 減弱風格強度, 避免模型過擬合生成分布 batch[:n_aug] (batch[:n_aug] gen_part) / 2.0 return batch這段代碼里的augment_ratio控制增強強度0.5表示每個batch里一半樣本參與風格融合加權平均這種做法是我比較常用的折中方案比起直接用生成圖替換原圖它的擾動更溫和訓練前期不容易把模型帶偏。真正用的時候生成器要先切到eval模式并關閉梯度否則前向過程會額外占用顯存。驗證方法也很直接固定隨機種子同一份測試集分別跑“不做增強”和“做增強”兩組實驗訓練到相同輪數(shù)后對比mAP或者Rank-1指標。差異在2個點以上說明GAN真的在幫識別網(wǎng)絡不足1個點就要回頭檢查生成器質(zhì)量問題多半出在生成樣本身份信息丟失上。這一步做完整條流程才算閉環(huán)答辯時也有具體數(shù)據(jù)可講。從那以后我每次訓練ReID模型都會先看一眼生成器的中間圖再決定要不要把增強接進訓練管線。先看生成圖、再看日志、最后跑指標這個順序幫我避開了無數(shù)次無效訓練。希望幫到你。本文還有配套的精品資源點擊獲取