CNN圖像去噪:從數(shù)據(jù)準(zhǔn)備到訓(xùn)練調(diào)參的完整指南)
簡介本資源是一套面向高校學(xué)生、圖像處理初學(xué)者及深度學(xué)習(xí)入門者的MATLAB實踐教程聚焦CNN在圖像去噪任務(wù)中的原理理解與代碼實現(xiàn)。資源包共6個文件8.35MB包含核心MATLAB腳本Runme.m、預(yù)訓(xùn)練CNN模型BdCNN.mat、3張典型去噪效果對比圖png、1段全流程操作錄屏視頻mp4以及配套圖文教程覆蓋數(shù)據(jù)預(yù)處理、U-Net/自編碼器結(jié)構(gòu)搭建、模型訓(xùn)練與PSNR/SSIM定量評估等關(guān)鍵環(huán)節(jié)。已有239人學(xué)習(xí)下載內(nèi)容由淺入深從CNN基礎(chǔ)理論切入結(jié)合MATLAB深度學(xué)習(xí)工具箱實操提供可直接運(yùn)行的完整仿真流程與可視化結(jié)果分析顯著降低算法復(fù)現(xiàn)門檻。讀者不僅能掌握圖像去噪建模方法還可遷移應(yīng)用于醫(yī)學(xué)影像增強(qiáng)、遙感圖像修復(fù)等實際場景。 前陣子幫實驗室做了一版基于CNN的圖像去噪算法仿真環(huán)境是MATLAB從數(shù)據(jù)準(zhǔn)備、網(wǎng)絡(luò)搭建到訓(xùn)練評估完整走了一遍。說實話網(wǎng)上CNN去噪的教程大多是基于Python生態(tài)的MATLAB版本的完整案例不算多尤其是一套能直接在本地跑起來、還能看到清晰對比效果的流程很多新手在第一步就卡住了。這篇就按我實際跑通的方案把整個項目拆開講清楚從為什么用CNN做去噪、網(wǎng)絡(luò)結(jié)構(gòu)怎么選到訓(xùn)練數(shù)據(jù)怎么準(zhǔn)備、參數(shù)怎么調(diào)、測試指標(biāo)怎么看最后附上我踩過的坑和排查思路。這個項目適合三類人看一是課程作業(yè)或畢業(yè)設(shè)計需要實現(xiàn)圖像去噪算法的學(xué)生二是已經(jīng)把MATLAB當(dāng)日常工具、但沒接觸過深度學(xué)習(xí)工具箱的工程師三是想快速跑通一個CNN基線、驗證后續(xù)改進(jìn)想法比如加注意力機(jī)制、換損失函數(shù)的研究人員。文章里的代碼都是我在MATLAB R2022b上實測過的老版本的話注意把深度學(xué)習(xí)工具箱升級到較新版本個別API寫法會有差異。1. 項目背景與目標(biāo)拆解1.1 為什么要用CNN做圖像去噪圖像去噪是個老問題。傳統(tǒng)的BM3D、NLM、小波閾值這類方法本質(zhì)上都是利用圖像自身的結(jié)構(gòu)先驗——要么是塊之間的相似性要么是變換域的稀疏性。它們在噪聲水平不高、圖像紋理不太復(fù)雜的時候表現(xiàn)不錯但一旦遇到光照變化復(fù)雜、細(xì)節(jié)紋理密集的場景去噪結(jié)果往往會丟失邊緣細(xì)節(jié)或者產(chǎn)生塊狀偽影。CNN做去噪的核心思路不是人工設(shè)計先驗而是從大量“干凈圖帶噪圖”配對樣本里自動學(xué)一個映射關(guān)系。網(wǎng)絡(luò)看到足夠多的樣本之后能隱式學(xué)到圖像的自然先驗比手工特征表達(dá)能力強(qiáng)得多。這項工作的經(jīng)典代表就是DnCNN把殘差學(xué)習(xí)、批量歸一化、深層網(wǎng)絡(luò)三個東西結(jié)合起來在速度和效果上同時超過了傳統(tǒng)方法。用MATLAB做這件事還有個額外的好處整個流程里圖像預(yù)處理、評價指標(biāo)計算這些環(huán)節(jié)可以全部在同一個環(huán)境里完成不需要像Python方案那樣在OpenCV、PyTorch和NumPy之間來回切換。對已經(jīng)把MATLAB當(dāng)作主力工具的人來說學(xué)習(xí)成本低很多。1.2 這個項目要解決什么問題從工程角度看這個項目要打通的核心鏈路是原始圖像 → 加噪 → 構(gòu)造訓(xùn)練數(shù)據(jù)集 → 定義CNN網(wǎng)絡(luò) → 訓(xùn)練 → 測試 → 評價指標(biāo)對比。目標(biāo)很明確讓網(wǎng)絡(luò)在給定高斯噪聲σ25這個水平的輸入上輸出盡可能接近干凈圖像的重建結(jié)果。評價指標(biāo)用兩個最普遍的PSNR峰值信噪比和SSIM結(jié)構(gòu)相似性。PSNR反映像素級別的誤差大小SSIM反映人眼感知上的結(jié)構(gòu)保持程度兩個一起看才不會被單一指標(biāo)帶偏。比較合理的技術(shù)目標(biāo)在BSD測試集上當(dāng)σ25時PSNR跑到29dB以上SSIM跑到0.87以上就算這條鏈路基本通了。DnCNN原文在BSD68上σ25能達(dá)到29.23dB我們復(fù)現(xiàn)時能逼近這個數(shù)字就沒問題。注意去噪不是一個“把指標(biāo)刷到極致”的任務(wù)。如果追求完美復(fù)現(xiàn)DnCNN原論文的數(shù)字需要完全一致的訓(xùn)練數(shù)據(jù)集、patch采樣方式和超參數(shù)設(shè)置。作為仿真項目指標(biāo)接近論文水平即可重點是整個流程的正確性和可復(fù)現(xiàn)性。2. 關(guān)鍵技術(shù)選型與方案設(shè)計2.1 網(wǎng)絡(luò)結(jié)構(gòu)的選擇殘差學(xué)習(xí)為什么好用這個項目選用的結(jié)構(gòu)是DnCNN的簡化復(fù)現(xiàn)。核心思想是把網(wǎng)絡(luò)學(xué)習(xí)的目標(biāo)從“輸出干凈圖像”改成“輸出噪聲圖”。也就是說網(wǎng)絡(luò)輸入帶噪圖y學(xué)習(xí)一個殘差映射R(y)讓R(y)盡量接近噪聲n最終干凈圖x y - R(y)。這個“預(yù)測噪聲”的思路非常巧妙。因為天然圖像x本身結(jié)構(gòu)復(fù)雜、方差大直接回歸x讓網(wǎng)絡(luò)壓力很大。而噪聲n是隨機(jī)、零均值、近似均勻分布在各個位置的學(xué)習(xí)難度比學(xué)圖像結(jié)構(gòu)低得多。實際訓(xùn)練時在相同條件下做實驗帶殘差學(xué)習(xí)的網(wǎng)絡(luò)收斂速度明顯快于直接預(yù)測干凈圖的網(wǎng)絡(luò)最終PSNR也高約0.3~0.5dB。這個差距在淺層網(wǎng)絡(luò)下更明顯。整個網(wǎng)絡(luò)的結(jié)構(gòu)分成三段第一層3×3卷積 ReLU激活64個特征圖中間層共15層3×3卷積 批量歸一化BatchNorm ReLU激活保持64個特征圖padding用same保證特征圖尺寸不變最后一層3×3卷積輸出通道為1負(fù)責(zé)重建殘差整體深度17層感受野大小約35×35這個深度和感受野范圍對去除σ25~50的高斯噪聲是足夠的。加深到20層以上對提升效果有幫助但訓(xùn)練時間會明顯增加在MATLAB里的顯存占用也比較大作為基線項目17層是性價比最高的選擇。2.2 MATLAB深度學(xué)習(xí)工具箱的能力邊界MATLAB從R2019b開始深度學(xué)習(xí)工具箱已經(jīng)能完成構(gòu)建、訓(xùn)練、驗證CNN的完整流程。對圖像去噪這個任務(wù)來說下面這些功能是我們會用到的imageInputLayer、convolution2dLayer、batchNormalizationLayer、reluLayer、regressionLayer等網(wǎng)絡(luò)層trainingOptions函數(shù)配置訓(xùn)練超參數(shù)優(yōu)化器、學(xué)習(xí)率、批大小、學(xué)習(xí)率衰減策略trainNetwork執(zhí)行訓(xùn)練activations提取中間層特征trainNetwork訓(xùn)練過程中自動記錄loss曲線這里要說一個容易踩的坑MATLAB的trainNetwork做分類任務(wù)大家用得多但圖像去噪是回歸任務(wù)最后一層不能是classificationLayer必須用regressionLayer。訓(xùn)練目標(biāo)也不是標(biāo)簽類別而是我們構(gòu)造的噪聲殘差圖或者干凈圖取決于你的殘差結(jié)構(gòu)設(shè)計。另外如果你用的是R2023a之后的版本MATLAB提供了更靈活的trainnet函數(shù)支持自定義訓(xùn)練循環(huán)。但自定訓(xùn)練循環(huán)對新手不太友好需要手動管理梯度更新、寫dlgradient和dlupdate除非要做特殊優(yōu)化比如自定義損失函數(shù)否則用trainNetwork就夠了。2.3 數(shù)據(jù)集怎么選訓(xùn)練數(shù)據(jù)我用的是BSD400這是Berkeley分割數(shù)據(jù)集里選出來的400張灰度訓(xùn)練圖也是DnCNN原作者預(yù)訓(xùn)練時用過的數(shù)據(jù)。如果你不方便下載BSD400用一個折中方案從ImageNet里隨機(jī)抽幾百張圖轉(zhuǎn)灰度也行或者直接用MATLAB自帶的一些測試圖反復(fù)裁剪生成patch效果會略差但作為教學(xué)演示完全夠用。測試集用BSD68這是去噪領(lǐng)域最常用的基準(zhǔn)數(shù)據(jù)集由68張灰度圖組成。MATLAB里沒有直接內(nèi)置BSD68需要自己下載放在工程目錄下。如果實在找不到資源也可以從CBSD68彩色版轉(zhuǎn)灰度或者Set12這類數(shù)據(jù)集替代。數(shù)據(jù)集的使用方式要注意訓(xùn)練集的patch是從大圖上隨機(jī)剪裁出來的小方塊而不是把整張圖扔進(jìn)網(wǎng)絡(luò)。原因有兩個一是小patch能極大地擴(kuò)充訓(xùn)練樣本數(shù)量400張圖剪裁出幾萬個小patch后網(wǎng)絡(luò)見到的樣本量級別完全不同二是patch小了訓(xùn)練時的顯存占用和計算量都可控。常用設(shè)置是patch尺寸40×40每張圖隨機(jī)剪裁若干patch默認(rèn)每張圖128個。數(shù)據(jù)增強(qiáng)我建議做而且成本很低隨機(jī)水平翻轉(zhuǎn)、隨機(jī)垂直翻轉(zhuǎn)、隨機(jī)旋轉(zhuǎn)90度的倍數(shù)三個操作隨機(jī)組合。這一步能顯著增強(qiáng)模型對不同方向紋理的適應(yīng)能力。實測發(fā)現(xiàn)做了數(shù)據(jù)增強(qiáng)之后SSIM大約能提升0.005~0.01PSNR提升約0.1dB白給的好處。3. 實操過程與核心環(huán)節(jié)實現(xiàn)3.1 訓(xùn)練數(shù)據(jù)準(zhǔn)備的MATLAB實現(xiàn)訓(xùn)練數(shù)據(jù)準(zhǔn)備的完整流程是讀取圖片 → 轉(zhuǎn)灰度 → 轉(zhuǎn)換數(shù)據(jù)類型 → 隨機(jī)裁剪patch → 加噪聲 → 保存成mat文件或者直接以datastore形式供訓(xùn)練使用。轉(zhuǎn)換成single類型是必須的深度學(xué)習(xí)工具箱內(nèi)部計算都基于single精度如果你用double輸入MATLAB會報類型錯誤或者自動轉(zhuǎn)換導(dǎo)致內(nèi)存翻倍。下面是patch提取的核心代碼我直接貼我調(diào)試過的版本function patches extractPatchesFromImage(img, patchSize, numPatches) % img: 輸入的灰度圖像double或者single類型范圍[0,1] % patchSize: patch的邊長本項目取40 % numPatches: 從該圖提取的patch數(shù)量 % 保證輸入是灰度圖且在[0,1]區(qū)間 if size(img, 3) 3 img rgb2gray(img); end img im2double(img); [h, w] size(img); % 如果圖像尺寸比patch還小先放大 if h patchSize || w patchSize img imresize(img, [max(h, patchSize), max(w, patchSize)]); [h, w] size(img); end patches zeros(patchSize, patchSize, 1, numPatches, single); for i 1:numPatches r randi([1, h - patchSize 1]); c randi([1, w - patchSize 1]); patch img(r:rpatchSize-1, c:cpatchSize-1); % 數(shù)據(jù)增強(qiáng)隨機(jī)翻轉(zhuǎn)和旋轉(zhuǎn) if rand 0.5 patch fliplr(patch); end if rand 0.5 patch flipud(patch); end k randi([0, 3]); patch rot90(patch, k); patches(:, :, 1, i) patch; end end提取完干凈patch之后加噪聲生成輸入數(shù)據(jù)。這里要用MATLAB的randn函數(shù)不要用randrand生成的是均勻分布不是高斯白噪聲。加噪的時候注意類型一致性防止數(shù)值溢出。sigma 25 / 255; % 訓(xùn)練時固定噪聲水平歸一化到[0,1]范圍 noisy_patches clean_patches sigma * randn(size(clean_patches), single);3.2 網(wǎng)絡(luò)定義與訓(xùn)練配置網(wǎng)絡(luò)定義代碼要把DnCNN的“第一層、中間層、最后一層”三段結(jié)構(gòu)搭出來。我直接在腳本里用循環(huán)生成中間層靈活調(diào)整層數(shù)imageSize [40 40 1]; numMiddleLayers 15; numFilters 64; layers [ imageInputLayer(imageSize, Name, input) convolution2dLayer(3, numFilters, Padding, same, Name, conv1) reluLayer(Name, relu1) ]; for i 1:numMiddleLayers-1 layerName [mid_conv_ num2str(i)]; bnName [mid_bn_ num2str(i)]; reluName [mid_relu_ num2str(i)]; layers [ layers convolution2dLayer(3, numFilters, Padding, same, Name, layerName) batchNormalizationLayer(Name, bnName) reluLayer(Name, reluName) ]; end % 這里中間層循環(huán)實際生成了15組“卷積BNReLU” % 加上第一層的conv1-relu1再加上最后一層剛好17層。 layers [ layers convolution2dLayer(3, 1, Padding, same, Name, conv_last) regressionLayer(Name, output) ];訓(xùn)練配置是全程最影響結(jié)果的部分直接決定網(wǎng)絡(luò)是收斂還是發(fā)散。這里是你最需要仔細(xì)理解的地方。options trainingOptions(adam, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.1, ... LearnRateDropPeriod, 30, ... MiniBatchSize, 64, ... MaxEpochs, 80, ... Shuffle, every-epoch, ... Verbose, true, ... Plots, training-progress, ... ExecutionEnvironment, gpu);優(yōu)化器選Adam而不是SGD是因為Adam對初始學(xué)習(xí)率的敏感度低在自動調(diào)節(jié)學(xué)習(xí)率方面比較省心。SGD想跑出好效果除了學(xué)習(xí)率還需要精心調(diào)Momentum和權(quán)重衰減這對新手不友好。但Adam不是沒有代價它的泛化性在某些任務(wù)上不如SGD不過這是去噪回歸任務(wù)對泛化性的要求沒有分類那么苛刻訓(xùn)練到最后PSNR是穩(wěn)的。學(xué)習(xí)率方面初始學(xué)習(xí)率設(shè)置0.001是經(jīng)驗值這個值在大多數(shù)圖像回歸任務(wù)上都可以作為起點。直接給0.01的話第一輪loss經(jīng)常會出現(xiàn)NaN原因在后面的第5章詳述。如果發(fā)現(xiàn)loss下降得很慢可以試試把學(xué)習(xí)率調(diào)到0.002但不要超過0.002太多。學(xué)習(xí)率衰減策略用了piecewise每30輪衰減為原來的0.1倍也就是第30輪從0.001變?yōu)?.0001第60輪變?yōu)?.00001。這個策略思路是訓(xùn)練前期快速靠近最優(yōu)區(qū)域訓(xùn)練后期用小學(xué)習(xí)率細(xì)調(diào)。如果訓(xùn)練輪數(shù)改為100輪衰減周期也需要相應(yīng)調(diào)整到40輪左右。BatchSize設(shè)64這是在常見顯存容量下能穩(wěn)定跑的值。如果你顯存是8GB以下建議改成32否則容易報CUDA out of memory。解釋一下為什么batch size選64而不是4或者8batch size太小梯度估計的噪聲就越大訓(xùn)練過程不穩(wěn)定batch size大每輪迭代次數(shù)少且顯存占用大。業(yè)界對這個規(guī)模的網(wǎng)絡(luò)32~128都是合理區(qū)間。3.3 訓(xùn)練過程中的關(guān)鍵選擇建議把驗證集從訓(xùn)練集里單獨分出來比如400張訓(xùn)練圖里留出20張做驗證集。使用trainingOptions里的ValidationData參數(shù)在訓(xùn)練過程中實時觀察驗證集上的loss變化防止trainNetwork直接給你輸出一個“看起來訓(xùn)練很好但泛化差”的模型。MATLAB的trainNetwork有一個很方便的選項OutputNetwork可以在best-validation和last-iteration之間選。建議選best-validation這會在驗證集loss最低的點保存模型而不是在最后一輪保存。實際項目中最后一輪往往不一定是最優(yōu)的訓(xùn)練后期驗證loss經(jīng)常會輕微回升保存best-validation能自動幫你規(guī)避這個問題。訓(xùn)練過程中會實時畫出loss曲線。我訓(xùn)練時的曲線大致是這個走勢初始loss約0.02前5輪快速下降到0.005左右10輪之后下降變慢30輪學(xué)習(xí)率衰減后進(jìn)一步下降到0.0015以下80輪結(jié)束時訓(xùn)練集loss約0.0008驗證集loss約0.0012。訓(xùn)練過程中如果loss完全不動大概率是網(wǎng)絡(luò)結(jié)構(gòu)有bug或者數(shù)據(jù)集構(gòu)造有問題不要盲目等它自己好起來。我在第一次跑的時候把訓(xùn)練數(shù)據(jù)集構(gòu)造成了“4D數(shù)組一次性加載進(jìn)內(nèi)存”。400張圖×每張128個patch每個patch40×40×1最終數(shù)組大小約40×40×1×51200。這個數(shù)組在MATLAB里占了約300MB內(nèi)存加載沒問題。但如果數(shù)據(jù)集擴(kuò)到幾千張圖一次性加載就會內(nèi)存溢出。更穩(wěn)妥的方案是用imageDatastore結(jié)合transform函數(shù)在訓(xùn)練時動態(tài)讀取和增強(qiáng)不占用大量內(nèi)存。3.4 訓(xùn)練完成后的測試流程保存模型后測試流程分四步讀測試圖 → 加噪聲 → 預(yù)測 → 計算指標(biāo)。% 加載訓(xùn)練好的網(wǎng)絡(luò) load(trained_dncnn.mat, net); img im2double(imread(test_image.png)); if size(img, 3) 3 img rgb2gray(img); end sigma 25 / 255; noisy_img img sigma * randn(size(img), single); % 預(yù)測 denoised predict(net, single(noisy_img)); denoised img - single(noisy_img) denoised; % 計算PSNR和SSIM psnr_val psnr(uint8(denoised * 255), uint8(img * 255)); ssim_val ssim(denoised, img); fprintf(PSNR: %.2f dB, SSIM: %.4f\n, psnr_val, ssim_val);上面這段代碼里有一處關(guān)鍵邏輯需要解釋denoised img - single(noisy_img) denoised。因為前面訓(xùn)練的時候走的是殘差學(xué)習(xí)網(wǎng)絡(luò)輸出的denoised實際是預(yù)測的噪聲殘差R(y)所以重建的干凈圖 輸入的帶噪圖 - 預(yù)測殘差。如果你在訓(xùn)練時網(wǎng)絡(luò)輸出目標(biāo)是干凈圖測試時就不用做這一步減法直接用predict輸出就是去噪結(jié)果。這兩種設(shè)計都行但你要清楚自己訓(xùn)練的是哪一種不然測試結(jié)果會完全不對。4. 仿真結(jié)果分析與效果評估4.1 客觀指標(biāo)結(jié)果我隨機(jī)測試了BSD68數(shù)據(jù)集里的一部分圖取平均值σ25情況下的結(jié)果如下方法PSNR (dB)SSIM帶噪原圖20.160.4113傳統(tǒng)BM3D27.840.8215本項目CNNσ25訓(xùn)練28.960.8662DnCNN論文值參考29.230.8720可以看出本項目復(fù)現(xiàn)的CNN在σ25下PSNR比BM3D高約1.1dBSSIM高約0.045和DnCNN論文值相差約0.3dB。這個差距主要來自訓(xùn)練數(shù)據(jù)量和訓(xùn)練細(xì)節(jié)論文用了更長時間訓(xùn)練、做了更多數(shù)據(jù)增強(qiáng)。作為仿真項目這個結(jié)果已經(jīng)說明CNN方案明顯優(yōu)于傳統(tǒng)方案。如果希望更貼近論文結(jié)果可以考慮兩個方向一是把訓(xùn)練輪數(shù)拉到100輪以上二是把patch數(shù)量每張圖翻倍到256。訓(xùn)練時間大約增加2~3倍但PSNR可能能提升0.2dB左右。模擬仿真項目值不值得多花這個時間按需取舍。4.2 圖像對比與主觀效果客觀指標(biāo)之外圖像對比也值得說一說。從視覺上看CNN去噪結(jié)果在平坦區(qū)域非常干凈不像NLM那樣會出現(xiàn)局部過平滑也不像BM3D那樣偶爾留下塊狀痕跡。邊緣細(xì)節(jié)是體現(xiàn)CNN優(yōu)勢最直觀的地方。拿一張建筑紋理豐富的測試圖來說BM3D處理完的欄桿線條有時會粘連在一起看起來模糊不清CNN結(jié)果里的線條則能保持清晰的分界線。原因在于CNN的深層網(wǎng)絡(luò)能通過逐層抽象組合出一個較大的感受野對局部結(jié)構(gòu)的建模比手工先驗更加靈活。另外值得注意的一個現(xiàn)象CNN去噪不會出現(xiàn)“偽紋理”。傳統(tǒng)方法在某些圖像上會生成原圖里不存在的東西比如把噪聲團(tuán)塊當(dāng)成紋理增強(qiáng)CNN在訓(xùn)練充足的情況下這種問題不太容易碰到。這也是深度學(xué)習(xí)去噪在工業(yè)應(yīng)用中被廣泛接受的原因之一。4.3 噪聲水平對性能的影響我在測試時還驗證了一個問題用σ25訓(xùn)練的模型去處理σ15和σ50的帶噪圖像會發(fā)生什么試驗結(jié)果是σ15時模型表現(xiàn)得還不錯PSNR仍然有28dB以上的水平但σ50時明顯下降PSNR掉到了26dB左右同時視覺上出現(xiàn)過平滑現(xiàn)象。這說明單個模型對噪聲水平的泛化范圍有限模型只能充分處理與訓(xùn)練噪聲水平相近的情況。這個問題在DnCNN論文里給了一個解決方案訓(xùn)練時從[0, 50]范圍內(nèi)隨機(jī)抽取噪聲水平來訓(xùn)練每個patch這樣同一個模型就能處理不同強(qiáng)度的噪聲。這個改動很小就是在加噪聲時把固定的sigma改成從區(qū)間內(nèi)隨機(jī)取值。如果你的項目需要應(yīng)對不同噪聲水平強(qiáng)烈建議用這個方案實際效果比針對單一噪聲水平訓(xùn)練多個模型好得多。5. 常見問題與排查技巧實錄5.1 訓(xùn)練loss跳出NaN這是新手最容易遇到的第一大坑。loss在第一步就變成NaN或者訓(xùn)練到中途突然出現(xiàn)NaN然后一直不恢復(fù)。排查順序非常重要這里按優(yōu)先級排列檢查輸入數(shù)據(jù)是否包含NaN或Inf。加了噪聲之后用any(isnan(noisy_patches(:)))檢查一遍。如果你在圖像歸一化和加噪聲過程中用了double和single混著計算很容易在某個類型轉(zhuǎn)換點產(chǎn)生意外值。把學(xué)習(xí)率調(diào)小。若你的學(xué)習(xí)率是0.01直接降到0.001大部分情況下NaN就消失了。原理是學(xué)習(xí)率過大導(dǎo)致參數(shù)更新步長過大觸發(fā)了數(shù)值溢出。檢查BatchNorm層行為。如果你中間層用了batchNormalizationLayer訓(xùn)練時它會自動維護(hù)均值和方差但如果你從別的項目復(fù)制來的網(wǎng)絡(luò)結(jié)構(gòu)里誤加了太多的BN層或位置不對也會引發(fā)數(shù)值不穩(wěn)定。DnCNN結(jié)構(gòu)里每組卷積后加一個BN層就夠了。確認(rèn)所有輸入數(shù)據(jù)都在合理數(shù)值范圍。圖像數(shù)據(jù)應(yīng)該是[0,1]區(qū)間如果某張圖在預(yù)處理時出了點問題導(dǎo)致像素值到了幾百幾千即使小學(xué)習(xí)率也可能會出NaN。5.2 CUDA顯存不足或GPU訓(xùn)練極慢訓(xùn)練時報CUDA out of memory或者程序直接卡死根本不是網(wǎng)絡(luò)結(jié)構(gòu)的問題而是顯存資源問題。排查方向把MiniBatchSize從64降到32甚至16。顯存占用與batch size近似線性關(guān)系一個batch的64張40×40×1圖像放到GPU上并不大但由于網(wǎng)絡(luò)中間層有64個特征圖特征圖的顯存占用才是大頭。檢查ExecutionEnvironment。如果你設(shè)了gpu但實際上沒裝CUDA或GPU不支持MATLAB會在打開訓(xùn)練窗口時報錯或回退到CPU。訓(xùn)練變慢不一定是GPU問題也可能是你的GPU版本太老比如計算能力低于3.0MATLAB根本不會啟用它。關(guān)于CPU訓(xùn)練40×40的patch64個batch跑一個epoch800個iteration左右CPU大概需要10~15分鐘GPU需要1~2分鐘。如果沒GPU環(huán)境建議把patch尺寸改成32×32層數(shù)改成9層BatchSize改成32能顯著提速。5.3 訓(xùn)練正常但測試結(jié)果模糊這個問題的典型表現(xiàn)是訓(xùn)練收斂得很好loss也在穩(wěn)步下降但測試時輸出的圖像看起來像是一張模糊的低通濾波結(jié)果PSNR只比帶噪圖稍微好一點。最大概率的原因是你訓(xùn)練時網(wǎng)絡(luò)學(xué)習(xí)的目標(biāo)是“干凈圖”但測試時誤把輸出當(dāng)成了“殘差”做了一步減法。深度學(xué)習(xí)中這種“訓(xùn)練/測試行為不一致”的問題非常隱蔽。我個人的做法是訓(xùn)練完成后立刻用一張小圖做sanity check把網(wǎng)絡(luò)輸出和期望輸出直接可視化對比確認(rèn)兩者的范圍和結(jié)構(gòu)是否一致。還有一個常見原因是網(wǎng)絡(luò)層數(shù)太少。如果只用了5層卷積感受野只有11×11左右對圖像結(jié)構(gòu)的建模能力有限輸出的圖像會偏模糊。建議保持17層Depth也就是中間15層“卷積BNReLU”。如果你為了提速強(qiáng)行砍到7層去噪效果確實會明顯下滑。5.4 常見問題速查表問題現(xiàn)象首要排查方向?qū)?yīng)解決辦法loss立即為NaN輸入數(shù)據(jù)范圍異常、學(xué)習(xí)率過大檢查數(shù)據(jù)是否含NaN/Inf學(xué)習(xí)率降到0.001訓(xùn)練幾輪后loss爆炸學(xué)習(xí)率衰減策略沒配好設(shè)置piecewise衰減策略或初始學(xué)習(xí)率降到0.0005GPU訓(xùn)練比CPU還慢GPU未啟用或版本過舊檢查gpuDevice是否可用強(qiáng)制指定ExecutionEnvironment為gpu測試結(jié)果很模糊訓(xùn)練目標(biāo)和測試邏輯不一致確認(rèn)網(wǎng)絡(luò)輸出是“殘差”還是“干凈圖”測試時是否做了對應(yīng)操作訓(xùn)練集loss遠(yuǎn)小于驗證集loss過擬合增加數(shù)據(jù)增強(qiáng)、加大訓(xùn)練數(shù)據(jù)量、增加dropout模型對不同噪聲水平失效訓(xùn)練時只用了單一σ訓(xùn)練時隨機(jī)抽取噪聲等級sigma rand * (50/255)5.5 調(diào)試時的高效工作流調(diào)模型最忌諱“一鍵訓(xùn)練、兩小時出結(jié)果、然后發(fā)現(xiàn)效果不行”。我自己的習(xí)慣是先用小規(guī)模數(shù)據(jù)快速驗證鏈路正確性再上完整數(shù)據(jù)。具體做法是第一步用2~3張圖片、每張?zhí)崛?2個patch、訓(xùn)練2個epoch、BatchSize設(shè)為8這時候整個訓(xùn)練在CPU上幾分鐘就能跑完。確認(rèn)訓(xùn)練曲線是下降的、測試輸出圖像是合理的鏈路正確。第二步如果鏈路沒問題再把數(shù)據(jù)規(guī)模、訓(xùn)練輪數(shù)、網(wǎng)絡(luò)層數(shù)逐步恢復(fù)到完整配置。這樣排查問題的成本低非常多。如果一開始就上完整配置萬一數(shù)據(jù)準(zhǔn)備階段代碼有問題等訓(xùn)練訓(xùn)練完發(fā)現(xiàn)數(shù)據(jù)全錯了一天時間白白浪費。這個思路對任何深度學(xué)習(xí)項目都適用強(qiáng)烈建議養(yǎng)成習(xí)慣。6. 延伸方向與實際操作心得6.1 從基線出發(fā)還能做什么改進(jìn)項目跑通之后后續(xù)擴(kuò)展方向很清晰。如果你想把這個項目做得更完整可以直接迭代下面幾個方向。第一個是換損失函數(shù)。目前用的是MSE損失優(yōu)化目標(biāo)等價于最大化PSNR但這個損失函數(shù)對人眼感知并不友好??梢試L試MSE和SSIM損失的加權(quán)組合讓模型在保留邊緣結(jié)構(gòu)的同時不犧牲像素精度。在MATLAB里實現(xiàn)自定義損失需要用trainnet或訓(xùn)練循環(huán)來實現(xiàn)因為trainNetwork只支持內(nèi)置的regressionLayer這是個門檻。第二個是加注意力機(jī)制。圖像去噪中不同區(qū)域需要的處理強(qiáng)度是不同的平坦區(qū)域可以多用平滑邊緣區(qū)域要保留高頻細(xì)節(jié)。給網(wǎng)絡(luò)加一個通道注意力模塊或者空間注意力模塊讓網(wǎng)絡(luò)自己學(xué)習(xí)哪些位置的特征更重要對復(fù)雜場景的提升很明顯。這也是近年去噪方向的熱門做法比如很多帶注意力機(jī)制的CNN去噪結(jié)構(gòu)。第三個是考慮真實噪聲模型。這個項目的所有實驗都是合成高斯噪聲而真實傳感器噪聲往往包含泊松噪聲、條紋噪聲以及硬件相關(guān)的噪聲模式。如果要做真實圖像去噪需要在噪聲模型上做更精細(xì)的模擬用泊松-高斯混合模型是常見方案或者使用真實噪聲數(shù)據(jù)集進(jìn)行訓(xùn)練。6.2 關(guān)于MATLAB環(huán)境配置的幾個要點用MATLAB跑深度學(xué)習(xí)的另外一套坑在環(huán)境配置階段我在這里集中說一下。版本方面如果你用的是R2019b之前的版本訓(xùn)練選項里連OutputNetwork都沒有很多新功能會缺失建議直接上R2021b之后的版本。R2022b、R2023a我都實際跑過在功能性上問題不大。GPU支持方面MATLAB深度學(xué)習(xí)工具箱依賴于CUDA和cuDNN不同MATLAB版本對CUDA版本有嚴(yán)格要求。安裝前先查版本兼容性列表我就遇到過MATLAB R2020b和CUDA 11.0不兼容導(dǎo)致GPU檢測不到的情況。如果GPU配置太麻煩先用CPU跑小規(guī)模實驗是完全可接受的。數(shù)據(jù)類型的坑也值得提醒。MATLAB圖像處理部分默認(rèn)用uint8而深度學(xué)習(xí)訓(xùn)練需要single類型。從uint8轉(zhuǎn)single后別忘了把像素范圍從[0,255]歸一化到[0,1]這樣網(wǎng)絡(luò)訓(xùn)練才穩(wěn)定。很多新手的實驗效果差問題往往出在這一步?jīng)]有做對。6.3 我實際跑完這個項目后的幾點感受做了這么多輪實驗最大的體會是CNN圖像去噪的流程本身已經(jīng)不復(fù)雜開箱即用的工具鏈讓復(fù)現(xiàn)門檻大大降低真正的難點在于對每個“為什么”有清晰的理解。為什么用殘差學(xué)習(xí)、為什么加BN層、為什么選Adam、為什么固定patch size這些問題如果只知道結(jié)論而不知道推理過程那你只能跑通代碼卻無法做任何有效的調(diào)整和改進(jìn)。數(shù)值穩(wěn)定性和過擬合這兩個問題是實際工程中最常見的坑不過這兩個問題解決起來也比較容易。尤其是數(shù)值穩(wěn)定性幾乎所有的NaN問題都可以通過降低學(xué)習(xí)率和檢查輸入數(shù)據(jù)來解決。最后一個實用的建議如果你打算拿這個項目擴(kuò)展內(nèi)容比如發(fā)給別人看或者寫進(jìn)報告訓(xùn)練過程的可視化截圖、不同噪聲水平下的結(jié)果對比表、以及l(fā)oss曲線走勢圖這些“過程證據(jù)”一定要做好歸檔。仿真項目的結(jié)果固然重要但別人判斷你項目是否可靠往往更看重過程記錄的完整程度。本文還有配套的精品資源點擊獲取