抗網(wǎng)絡(luò)強(qiáng)化自編碼器異常檢測)
做無監(jiān)督異常檢測的人基本都跟自編碼器打過交道。數(shù)據(jù)不均衡、異常樣本永遠(yuǎn)稀缺很多人第一個(gè)想到的方案就是把正常圖片送去訓(xùn)練一個(gè)自編碼器然后用重建誤差來打分。但實(shí)際跑下來你會(huì)發(fā)現(xiàn)問題很明顯自編碼器對(duì)異常圖像往往也能“重建”個(gè)大概因?yàn)閴嚎s再放大的過程會(huì)把很多局部細(xì)節(jié)直接抹平異常區(qū)域被當(dāng)作噪聲處理掉了重建誤差并沒有想象中那么大。GANomaly這個(gè)工作就是在2018年專門針對(duì)這個(gè)問題提出來的方案核心是用生成對(duì)抗網(wǎng)絡(luò)GAN去強(qiáng)化“壓縮-重建”過程中的表征約束讓異常在潛在空間里就露出馬腳。這篇文章我會(huì)把GANomaly的原理和源碼一起拆開講從網(wǎng)絡(luò)結(jié)構(gòu)、三個(gè)損失函數(shù)到PyTorch訓(xùn)練循環(huán)和異常分?jǐn)?shù)計(jì)算全部過一遍。1. GANomaly到底在解決什么問題1.1 純自編碼器做異常檢測為什么不夠自編碼器AutoEncoderAE的訓(xùn)練目標(biāo)很直接輸入一張圖經(jīng)過編碼器壓成低維向量再經(jīng)過解碼器還原要求輸出和輸入盡量一致。正常樣本和異常樣本都喂進(jìn)去之后理論上異常樣本重建誤差會(huì)更大。問題在于自編碼器學(xué)習(xí)到的潛在向量通常只保留全局結(jié)構(gòu)信息而很多異常恰恰是局部細(xì)節(jié)層面的比如劃痕、污點(diǎn)、局部的形狀畸變。假如一張圖片上有一個(gè)很小但很明顯的黑點(diǎn)編碼器把這個(gè)圖片壓到低維空間時(shí)黑點(diǎn)這個(gè)局部特征很可能就“溶”進(jìn)了背景統(tǒng)計(jì)里解碼器自然也不會(huì)把它恢復(fù)出來。如果再用傳統(tǒng)自編碼器重建誤差可能只集中在那個(gè)黑點(diǎn)周圍但經(jīng)過卷積和池化后這個(gè)誤差會(huì)很分散最后算MSE時(shí)反而不明顯。說白了純自編碼器做異常檢測的核心問題是它的潛在空間沒有經(jīng)過“對(duì)抗式”的約束模型可以找到一個(gè)很平庸的編碼方式讓正常和異常在重建誤差上沒那么容易區(qū)分。有人會(huì)問那直接把重建誤差換成感知損失Perceptual Loss行不行用預(yù)訓(xùn)練VGG提取特征再算L2距離確實(shí)比像素MSE好一些但還是解決不了關(guān)鍵問題數(shù)據(jù)本身是高度不平衡的模型仍然只需要“過得去”的重建效果就夠了不會(huì)刻意去區(qū)分哪些輸入應(yīng)該被判定為異常。這就像一個(gè)人練字如果只要求“整體看起來差不多”他永遠(yuǎn)不會(huì)發(fā)現(xiàn)某個(gè)筆畫其實(shí)是錯(cuò)的。1.2 GANomaly的核心機(jī)制壓縮再壓縮GANomaly的思路很巧妙它不是直接去優(yōu)化重建誤差而是把“輸入圖像”和“重建圖像”分別壓縮到潛在空間然后比較這兩個(gè)潛在向量的差異。換句話說一張圖進(jìn)來先被生成器內(nèi)部的編碼器壓成潛在向量z再從z解碼回圖像接著把這個(gè)重建圖像再喂給另一個(gè)編碼器E得到第二個(gè)潛在向量z_hat。如果輸入是正常樣本z和z_hat應(yīng)該很接近如果輸入是異常樣本壓縮-重建-再壓縮這條鏈路會(huì)在某個(gè)環(huán)節(jié)無法完整保留信息導(dǎo)致z和z_hat產(chǎn)生明顯的偏移。這個(gè)機(jī)制的直觀理解可以類比成讓一個(gè)秘書用一段話概括一份文件然后讓另一個(gè)秘書根據(jù)這段概括反推原文要點(diǎn)再讓記錄員把兩版要點(diǎn)對(duì)比。如果原文件內(nèi)容都在正常范圍內(nèi)兩個(gè)秘書的工作結(jié)果會(huì)高度一致一旦文件里混入了一個(gè)不合常規(guī)的條目第一個(gè)秘書可能會(huì)忽略它或者第二個(gè)秘書在“反推”時(shí)憑經(jīng)驗(yàn)?zāi)X補(bǔ)成正常內(nèi)容那最后對(duì)比出的差異就會(huì)暴露問題。關(guān)鍵的創(chuàng)新點(diǎn)在于它用GAN的對(duì)抗訓(xùn)練把重建過程約束在訓(xùn)練集的流形上。生成器不僅僅要把圖像重建得像還要讓生成的圖像在判別器眼里“真”。這樣異常區(qū)域想要在重建圖中被悄悄抹掉就不再那么容易了因?yàn)榕袆e器會(huì)把那種看起來“虛假”的重建結(jié)果打回去逼著生成器保留更多輸入中的真實(shí)細(xì)節(jié)。1.3 與AnoGAN、EGBAD等方法的關(guān)鍵差別在做異常檢測的GAN方案里GANomaly不是第一個(gè)。早些時(shí)候有AnoGAN用訓(xùn)練好的GAN把測試圖像反演到潛在空間再計(jì)算重建誤差還有EGBAD用雙向GAN學(xué)習(xí)圖像與潛在向量的聯(lián)合分布。這些方法的問題在于推理階段需要額外的優(yōu)化迭代測試一張圖要跑多次梯度下降又慢又不穩(wěn)定。GANomaly直接把推理過程設(shè)計(jì)成了前向傳播不需要反演優(yōu)化。訓(xùn)練階段學(xué)會(huì)了從圖像到潛在向量的映射測試時(shí)只需要把圖像喂進(jìn)去算一下z和z_hat的差值就得到異常分?jǐn)?shù)。這在實(shí)際場景里非常重要尤其是工業(yè)質(zhì)檢這種可能需要實(shí)時(shí)處理的場景AnoGAN這種每次測試都要迭代幾百步的方法幾乎沒法落地。方法是否需要測試時(shí)優(yōu)化異常分?jǐn)?shù)來源主要短板AnoGAN是重建圖與原始圖的殘差測試耗時(shí)大訓(xùn)練流程復(fù)雜EGBAD否重建誤差 判別器特征誤差模型結(jié)構(gòu)復(fù)雜訓(xùn)練不穩(wěn)定GANomaly否潛在向量z與z_hat的距離依賴訓(xùn)練數(shù)據(jù)的純正性從上面的對(duì)比能看出來GANomaly在“檢測效果”和“使用成本”之間找了一個(gè)很實(shí)際的平衡點(diǎn)。這也是它后續(xù)能成為無監(jiān)督異常檢測領(lǐng)域基線的直接原因。2. 網(wǎng)絡(luò)結(jié)構(gòu)拆解生成器、判別器與重編碼器2.1 生成器G的結(jié)構(gòu)與潛在向量zGANomaly里的生成器G不是一個(gè)單純的Decoder它內(nèi)部其實(shí)包含了一個(gè)Encoder和一個(gè)Decoder。論文里把這個(gè)生成器寫作G_E G_DG_E負(fù)責(zé)把輸入圖像壓縮成潛在向量zG_D負(fù)責(zé)把z還原成圖像。如果以64x64的RGB圖像輸入為例G_E通常是4個(gè)卷積層每層stride為2通道數(shù)從64一路翻到512最后flatten成一個(gè)固定長度的特征向量經(jīng)過一個(gè)線性層輸出100維的z。解碼器G_D則是對(duì)稱的先通過一個(gè)全連接層把z映射回特征圖的大小再用4個(gè)轉(zhuǎn)置卷積逐級(jí)上采樣恢復(fù)成64x64x3的圖像。注意這里輸出層一般用Tanh激活因?yàn)檩斎雸D像在預(yù)處理時(shí)要?dú)w一化到[-1, 1]區(qū)間而不是0到1。這個(gè)結(jié)構(gòu)設(shè)計(jì)最關(guān)鍵的一點(diǎn)是潛在向量z的維度只有100維遠(yuǎn)小于原始圖像的空間大小。編碼器必須把最重要的結(jié)構(gòu)信息和語義信息放進(jìn)這100個(gè)維度里。如果輸入是一張正常樣本編碼器可以很從容地完成這個(gè)“摘要”任務(wù)如果輸入屬于異常類別某些局部特征在壓縮過程中要么被忽略要么被錯(cuò)誤地當(dāng)成其他正常模式重建出來就會(huì)和原始輸入產(chǎn)生偏差。2.2 判別器D的雙路輸出大多數(shù)GAN的判別器只輸出一個(gè)真/假概率標(biāo)量GANomaly的判別器在此基礎(chǔ)上多輸出了一組特征向量。它提取的是判別器在判定真?zhèn)芜^程中學(xué)到的中間層特征用這個(gè)特征向量來衡量“輸入圖像”和“重建圖像”在判別器視角下的距離。為什么需要這個(gè)特征純粹依賴潛在向量做異常評(píng)分有一個(gè)隱患潛在向量z是高度抽象的結(jié)果可能丟掉太多細(xì)節(jié)。如果判別器能提供一個(gè)更偏“感知層”的特征那對(duì)于局部級(jí)別的異常比如一條很細(xì)的裂紋感知特征的差異可能會(huì)比潛在向量的差異更敏感。所以在源碼實(shí)現(xiàn)里判別器倒數(shù)第二層的特征會(huì)被單獨(dú)拉出來反向傳播給生成器使用。這個(gè)雙路輸出的設(shè)計(jì)也可以理解為判別器既要告訴生成器“你重建的圖像像不像真的”又要告訴生成器“你的重建圖在特征層面和原圖差在哪”。這種雙重約束讓GANomaly在保留全局結(jié)構(gòu)的同時(shí)也能保留不少局部紋理信息。2.3 重編碼器E為什么不能省這是我讀源碼時(shí)最想強(qiáng)調(diào)的一個(gè)點(diǎn)為什么不能直接復(fù)用生成器里的編碼器G_E非要另起爐灶再定義一個(gè)E網(wǎng)絡(luò)從幾何意義上講G_E是生成器內(nèi)部的組件它的參數(shù)會(huì)隨著生成器一起更新目標(biāo)是把輸入圖像編碼成一個(gè)“方便解碼器重建”的潛在向量。這個(gè)潛在向量的分布已經(jīng)被訓(xùn)練過程塑造成了解碼器最舒服的形狀。如果測試時(shí)用同一個(gè)G_E來編碼重建圖得到的z_hat和z很可能會(huì)天然接近異常信號(hào)的區(qū)分度就被削弱了。單獨(dú)用一個(gè)E網(wǎng)絡(luò)就不同了。E的目標(biāo)是獨(dú)立地把重建圖編碼成一個(gè)潛在表示它不受解碼器“舒適區(qū)”影響。雖然源碼里E在訓(xùn)練階段不更新參數(shù)但它的隨機(jī)初始化特征映射已經(jīng)提供了一個(gè)相對(duì)獨(dú)立的度量視角。可以這樣想G_E是“作者”視角的摘要E是“讀者”視角的摘要對(duì)比兩種視角的差異遠(yuǎn)比只站在一個(gè)視角里來回打轉(zhuǎn)更有信息量。2.4 三個(gè)損失的數(shù)學(xué)表達(dá)與源碼對(duì)應(yīng)GANomaly的總損失函數(shù)由三部分構(gòu)成L L_adv λ_con * L_con λ_enc * L_enc其中論文設(shè)定的λ_con 1λ_enc 50。這說明編碼損失的權(quán)重最高是模型優(yōu)化的核心目標(biāo)。對(duì)抗損失L_adv的形式比較標(biāo)準(zhǔn)。判別器希望把真實(shí)圖片判定為1把生成器重建的圖片判定為0生成器則希望重建圖片被判定為1。對(duì)應(yīng)源碼里生成器的對(duì)抗損失是把G(x)送入判別器后與全1標(biāo)簽計(jì)算BCELoss。這里用非飽和損失形式而不是直接最小化log(1-D(G(x)))因?yàn)楹笳咴谂袆e器太強(qiáng)時(shí)會(huì)有嚴(yán)重的梯度消失問題。上下文損失L_con就是重建圖像和原始圖像之間的L1距離L_con E[||x - G(x)||_1]為什么用L1不用L2L2損失傾向于把誤差平均分配到整張圖片上生成的圖像會(huì)顯得模糊L1損失對(duì)局部區(qū)域的偏差保留了更大的梯度信號(hào)重建出來的圖像邊緣更銳利。編碼損失L_enc是整個(gè)模型最具辨識(shí)度的部分L_enc E[||G_E(x) - E(G(x))||_2]這里用了L2距離原因是潛在向量本身是一個(gè)連續(xù)且維度較低的空間L2能更直接地度量兩個(gè)點(diǎn)在歐氏空間中的偏移程度。如果某個(gè)輸入圖像是異常的它的編碼z和重建圖再編碼得到的z_hat在L2距離上會(huì)明顯大于正常樣本的情況。3. 源碼解析從數(shù)據(jù)加載到訓(xùn)練全流程3.1 項(xiàng)目目錄與PyTorch基礎(chǔ)版本說明我用的是PyTorch版本的GANomaly實(shí)現(xiàn)這是社區(qū)里比較常見的復(fù)現(xiàn)版本。項(xiàng)目結(jié)構(gòu)很清晰主要包含data目錄、model目錄和train/val腳本。數(shù)據(jù)部分的核心是自定義Dataset類在初始化時(shí)讀取所有訓(xùn)練圖片的路徑并在__getitem__里做預(yù)處理。預(yù)處理包含三件事Resize到64x64、ToTensor、Normalize到均值為0.5、標(biāo)準(zhǔn)差為0.5。這意味著圖像像素值會(huì)落在-1到1之間。驗(yàn)證集和測試集不需要單獨(dú)打標(biāo)簽異常檢測場景是無監(jiān)督的。你只需要提供一個(gè)“正常樣本”列表用于訓(xùn)練測試時(shí)把所有樣本正常異常都送進(jìn)去按照異常分?jǐn)?shù)排序后計(jì)算AUC就行。3.2 生成器與判別器核心代碼生成器類在PyTorch里定義得很直接。Encoder部分用卷積層逐步下采樣Decoder部分用ConvTranspose2d上采樣。下面這段是核心結(jié)構(gòu)class Generator(nn.Module): def __init__(self, nz100, nc3): super().__init__() self.encoder nn.Sequential( nn.Conv2d(nc, 64, 4, 2, 1, biasFalse), nn.BatchNorm2d(64), nn.ReLU(True), nn.Conv2d(64, 128, 4, 2, 1, biasFalse), nn.BatchNorm2d(128), nn.ReLU(True), nn.Conv2d(128, 256, 4, 2, 1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(True), nn.Conv2d(256, 512, 4, 2, 1, biasFalse), nn.BatchNorm2d(512), nn.ReLU(True), ) self.fc1 nn.Linear(512 * 4 * 4, nz) self.decoder nn.Sequential( nn.ConvTranspose2d(nz, 512, 4, 1, 0, biasFalse), nn.BatchNorm2d(512), nn.ReLU(True), nn.ConvTranspose2d(512, 256, 4, 2, 1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(True), nn.ConvTranspose2d(256, 128, 4, 2, 1, biasFalse), nn.BatchNorm2d(128), nn.ReLU(True), nn.ConvTranspose2d(128, 64, 4, 2, 1, biasFalse), nn.BatchNorm2d(64), nn.ReLU(True), nn.ConvTranspose2d(64, nc, 4, 2, 1, biasFalse), nn.Tanh() ) def forward(self, x): z self.encode(x) x_hat self.decode(z) return x_hat, z def encode(self, x): x self.encoder(x) x x.view(x.size(0), -1) return self.fc1(x) def decode(self, z): z z.view(z.size(0), z.size(1), 1, 1) return self.decoder(z)判別器使用的卷積層相對(duì)更少因?yàn)樗恍枰敵觥罢鎮(zhèn)闻袛唷焙汀疤卣飨蛄俊眱蓸訓(xùn)|西。倒數(shù)第二層得到一個(gè)256維的特征向量這個(gè)特征會(huì)作為編碼損失的一部分被反向傳播。class Discriminator(nn.Module): def __init__(self, nc3): super().__init__() self.conv1 nn.Conv2d(nc, 64, 4, 2, 1, biasFalse) self.conv2 nn.Conv2d(64, 128, 4, 2, 1, biasFalse) self.conv3 nn.Conv2d(128, 256, 4, 2, 1, biasFalse) self.fc1 nn.Linear(256 * 8 * 8, 1024) self.fc2 nn.Linear(1024, 1) self.leaky nn.LeakyReLU(0.2, True) def forward(self, x): x self.leaky(self.conv1(x)) x self.leaky(self.conv2(x)) x self.leaky(self.conv3(x)) x x.view(x.size(0), -1) x self.leaky(self.fc1(x)) features x out torch.sigmoid(self.fc2(x)) return out, features3.3 訓(xùn)練循環(huán)和損失計(jì)算訓(xùn)練循環(huán)的核心思路是先更新判別器再更新生成器。每一步都從同一個(gè)批次的數(shù)據(jù)出發(fā)分別算出三個(gè)損失。下面這段代碼是每輪epoch中訓(xùn)練生成器時(shí)的縮影# 輸入x是歸一化到[-1,1]的圖片 x_hat, z netg(x) # 生成重建圖和潛在向量 f_x netd(x) # 判別器對(duì)真實(shí)圖片的特征 f_x_hat netd(x_hat) # 判別器對(duì)重建圖的特征 z_hat nete(x_hat) # 外部編碼器對(duì)重建圖的編碼 real_label torch.ones(x.size(0), 1) fake_label torch.zeros(x.size(0), 1) # 對(duì)抗損失生成器要讓重建圖被判別為真 err_g_adv criterion(netd(x_hat)[0], real_label) # 上下文損失重建圖和原圖的L1距離 err_g_con criterion_l1(x_hat, x) # 編碼損失潛在向量z和重編碼z_hat之間的L2距離 err_g_enc criterion_l2(z_hat, z) err_g err_g_adv lambda_con * err_g_con lambda_enc * err_g_enc err_g.backward() optimizer_g.step()判別器的更新相對(duì)常規(guī)只不過它需要同時(shí)處理真實(shí)圖片和重建圖片兩組輸出。真實(shí)圖片的標(biāo)簽為1重建圖片的標(biāo)簽為0。判別器訓(xùn)練到位之后生成器會(huì)越來越難騙過它這會(huì)反向逼迫生成器提高重建質(zhì)量。3.4 訓(xùn)練時(shí)E到底做什么、不做什么源碼里最容易被忽略的就是E網(wǎng)絡(luò)。很多人第一次讀代碼時(shí)會(huì)以為三個(gè)網(wǎng)絡(luò)都參與了訓(xùn)練但實(shí)際上E在訓(xùn)練過程中只被調(diào)用沒有優(yōu)化器去更新它的參數(shù)。也就是說E網(wǎng)絡(luò)從頭到尾保持一個(gè)隨機(jī)初始化狀態(tài)它作為一個(gè)固定的“度量器”存在。這一點(diǎn)如果理解錯(cuò)整個(gè)模型的設(shè)計(jì)邏輯就歪了。E不參與訓(xùn)練意味著它不會(huì)去“討好”生成器也不會(huì)對(duì)正常樣本產(chǎn)生偏好。它的隨機(jī)投影提供了一種無偏的度量方式。測試階段計(jì)算異常分?jǐn)?shù)時(shí)z_hat正是因?yàn)閬碜赃@個(gè)“獨(dú)立視角”才更能反映出重建過程中的信息損失。4. 推理與評(píng)估異常分?jǐn)?shù)與AUC4.1 測試流程與異常分?jǐn)?shù)計(jì)算訓(xùn)練完成后測試階段不再需要判別器只保留生成器G和編碼器E。對(duì)每一張測試圖片計(jì)算z G_E(x)即生成器內(nèi)部編碼器對(duì)原始圖的壓縮結(jié)果x_hat G(z)重建圖像z_hat E(x_hat)外部編碼器對(duì)重建圖的壓縮結(jié)果異常分?jǐn)?shù) A(x) ||z - z_hat||_1。分?jǐn)?shù)越高說明這張圖片在“壓縮-重建-再壓縮”過程中丟失的信息越多越可能是異常。這個(gè)打分邏輯在源碼里實(shí)現(xiàn)起來非常短def anomaly_score(inputs): with torch.no_grad(): x_hat, z netg(inputs) z_hat nete(x_hat) scores torch.mean(torch.abs(z - z_hat), dim1) return scores我自己的經(jīng)驗(yàn)是計(jì)算分?jǐn)?shù)時(shí)用L1還是L2差別不大重要的是在同一個(gè)模型、同一個(gè)數(shù)據(jù)集內(nèi)部保持一致性。不同訓(xùn)練輪次得到的分?jǐn)?shù)尺度不一樣所以跨模型比較時(shí)要小心。4.2 用scikit-learn計(jì)算AUC的實(shí)操拿到所有測試圖片的異常分?jǐn)?shù)后評(píng)估就變成了一個(gè)標(biāo)準(zhǔn)的二分類排序問題。正常樣本和異常樣本都有對(duì)應(yīng)的真實(shí)標(biāo)簽直接用sklearn的roc_auc_score計(jì)算即可from sklearn.metrics import roc_auc_score scores [] labels [] for img, label in test_loader: score anomaly_score(img) scores.extend(score.cpu().numpy()) labels.extend(label.numpy()) auc roc_auc_score(labels, scores)注意這里的標(biāo)簽不能用于訓(xùn)練只能在評(píng)估環(huán)節(jié)使用。在實(shí)際工廠質(zhì)檢場景里異常樣本往往不會(huì)超過5%AUC只反映排序能力最終還得根據(jù)誤檢率和漏檢率的業(yè)務(wù)要求去選擇具體閾值。4.3 一個(gè)可視化調(diào)試方法只看AUC數(shù)字有時(shí)會(huì)掩蓋問題。我習(xí)慣把正常樣本和異常樣本的異常分?jǐn)?shù)分布畫出來看看兩個(gè)分布的重疊程度。分?jǐn)?shù)分布圖比單一數(shù)值更直觀。另一個(gè)更實(shí)用的可視化手段是把重建圖x_hat和原圖x做像素級(jí)差異熱力圖diff torch.abs(x - x_hat).mean(dim1, keepdimTrue)把diff放大后疊加在原圖上就能直觀看到異常區(qū)域集中在圖片的哪個(gè)位置。GANomaly雖然本身不輸出分割掩碼但重建差的分布往往能非常準(zhǔn)確地指示異常位置。有一次我在一個(gè)表面劃痕數(shù)據(jù)集上調(diào)試AUC只有0.82畫了熱力圖才發(fā)現(xiàn)模型把圖片角落的正常紋理誤判成了異常后來通過清理訓(xùn)練數(shù)據(jù)里的少量臟樣本AUC直接提到了0.95以上。5. 復(fù)現(xiàn)與調(diào)參中的避坑清單5.1 關(guān)鍵訓(xùn)練參數(shù)速查表我整理了一份可以直接參考的參數(shù)配置基于PyTorch實(shí)現(xiàn)和64x64圖像輸入。參數(shù)建議值說明輸入尺寸64x64源碼里線性層維度寫死換尺寸需同步改結(jié)構(gòu)z維度100太小可能丟細(xì)節(jié)太大會(huì)讓異常判別變鈍batch_size64低于16容易訓(xùn)練崩潰顯存不夠可降到32優(yōu)化器Adamlr設(shè)為0.0002betas設(shè)為(0.5, 0.999)epoch15-30數(shù)據(jù)量大時(shí)建議配合早停λ_con1論文默認(rèn)值λ_enc50論文默認(rèn)值編碼損失權(quán)重最大這里要單獨(dú)提醒一句如果你換成了128x128或更高分辨率的輸入生成器里那些channel數(shù)和kernel size不一定直接適用。尤其是全連接層的輸入維度需要根據(jù)卷積輸出的feature map大小重新計(jì)算。當(dāng)時(shí)我改分辨率時(shí)沒注意這個(gè)直接報(bào)了一堆shape mismatch的錯(cuò)。5.2 高頻問題與排查建議訓(xùn)練GANomaly最常見的故障是訓(xùn)練不收斂、生成器loss不降、重建圖像全黑或全灰、AUC一直徘徊在0.5附近。下面這些是基于實(shí)際經(jīng)驗(yàn)總結(jié)的排查路徑問題現(xiàn)象可能原因處理建議生成器loss震蕩劇烈判別器訓(xùn)練太快降低學(xué)習(xí)率或者每更新一次判別器后再更新兩次生成器重建圖像模糊上下文損失權(quán)重過低適當(dāng)調(diào)大λ_con檢查是否忘了用L1損失AUC接近0.5訓(xùn)練數(shù)據(jù)里混入了少量異常樣本可視化訓(xùn)練集剔除明顯偏離正常分布的數(shù)據(jù)測試分?jǐn)?shù)整體偏高輸入預(yù)處理不一致確認(rèn)訓(xùn)練和測試都用[-1,1]歸一化不是[0,1]潛在向量z和z_hat始終接近E網(wǎng)絡(luò)被意外更新檢查優(yōu)化器列表確認(rèn)nete參數(shù)沒有傳進(jìn)去還有一個(gè)很多人容易踩的坑BatchNorm在batch size為1時(shí)會(huì)直接崩掉。如果你用單張圖片做推理一定要把網(wǎng)絡(luò)切到eval模式關(guān)閉BatchNorm的更新邏輯。5.3 讓GANomaly落地更穩(wěn)的幾個(gè)改進(jìn)GANomaly本身是個(gè)基線方法不等于不能改進(jìn)。實(shí)際項(xiàng)目中以下幾個(gè)改動(dòng)在保持簡單性的同時(shí)會(huì)帶來比較明顯的效果提升。第一個(gè)是把E網(wǎng)絡(luò)從“隨機(jī)初始化且不訓(xùn)練”改成“使用ImageNet預(yù)訓(xùn)練的backbone”。實(shí)驗(yàn)下來尤其在紋理類異常檢測上預(yù)訓(xùn)練E網(wǎng)絡(luò)能讓異常分?jǐn)?shù)的穩(wěn)定性提升不少。這是因?yàn)殡S機(jī)初始化的CNN特征分布不穩(wěn)定而預(yù)訓(xùn)練網(wǎng)絡(luò)提取的特征本身就具備一定的通用語義能力。第二個(gè)改動(dòng)是在上下文損失里融合SSIM結(jié)構(gòu)相似度。L1損失對(duì)像素絕對(duì)值差異敏感但對(duì)紋理結(jié)構(gòu)的變化不敏感SSIM能捕捉局部結(jié)構(gòu)中細(xì)微的變化對(duì)劃痕、微小形變這類異常的區(qū)分更有效。常見做法是讓L1和SSIM損失各占一半權(quán)重。第三個(gè)改動(dòng)是把測試階段的重建過程做成多次采樣取平均。輸入同一張圖像由于Dropout或數(shù)據(jù)增強(qiáng)的存在重建結(jié)果會(huì)有輕微波動(dòng)算異常分?jǐn)?shù)時(shí)跑3次取平均可以壓低噪聲對(duì)分?jǐn)?shù)的影響。這個(gè)辦法在圖像噪聲較大的工業(yè)相機(jī)場景下效果很直觀。說回源碼層面如果你想把GANomaly用到自己的數(shù)據(jù)集上我建議不要直接照搬訓(xùn)練20個(gè)epoch的做法。先用一個(gè)小子集跑通完整流程確認(rèn)重建效果肉眼可見地清晰、異常分?jǐn)?shù)能區(qū)分開再逐步放大數(shù)據(jù)量。我在實(shí)際使用中發(fā)現(xiàn)GANomaly對(duì)訓(xùn)練數(shù)據(jù)的“純正程度”要求比普通自編碼器高很多數(shù)據(jù)里一旦混入幾條異常樣本判別器會(huì)很早學(xué)到取巧的重建策略后續(xù)AUC怎么調(diào)都上不去。最后再分享一個(gè)小技巧測試階段千萬不要忘了把模型切到eval模式否則BatchNorm帶來的統(tǒng)計(jì)量偏移會(huì)讓異常分?jǐn)?shù)出現(xiàn)系統(tǒng)性偏差這個(gè)坑我踩過不止一次。