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