化的張量分解去噪Matlab實(shí)現(xiàn)與調(diào)參實(shí)戰(zhàn))
簡(jiǎn)介面向高維數(shù)據(jù)去噪與信號(hào)恢復(fù)場(chǎng)景這套開(kāi)源的基于凸優(yōu)化的張量分解代碼提供了可直接運(yùn)行的Matlab實(shí)現(xiàn)覆蓋圖像處理、視頻分析、多模態(tài)數(shù)據(jù)挖掘等應(yīng)用適合研究人員、工程師及高年級(jí)學(xué)生研習(xí)。壓縮包共48個(gè)文件其中45個(gè)m腳本構(gòu)成核心涵蓋張量分解求解器、數(shù)據(jù)切分與隨機(jī)張量生成函數(shù)、去噪與補(bǔ)全實(shí)驗(yàn)以及結(jié)果可視化腳本另有1個(gè)說(shuō)明文檔和版本管理配置文件便于理解目錄結(jié)構(gòu)、運(yùn)行示例與后續(xù)維護(hù)。整個(gè)壓縮包僅55KB輕量而模塊化。目前已有737人學(xué)習(xí)說(shuō)明該實(shí)現(xiàn)具備不錯(cuò)的參考價(jià)值。內(nèi)容涉及低秩近似、L1范數(shù)、交替方向乘子法等優(yōu)化策略附帶多個(gè)實(shí)驗(yàn)?zāi)_本與測(cè)試樣例用戶(hù)可依據(jù)自身數(shù)據(jù)調(diào)整迭代次數(shù)、閾值等參數(shù)在運(yùn)行中完成去噪并恢復(fù)原始信號(hào)也能對(duì)照代碼理解張量分解的具體收斂過(guò)程是學(xué)習(xí)凸優(yōu)化與張量方法的實(shí)用工具。 做高維數(shù)據(jù)處理的人應(yīng)該都有過(guò)這種體會(huì)二維的矩陣濾波還好說(shuō)一旦數(shù)據(jù)變成三維、四維的彩色視頻、多通道傳感信號(hào)、高光譜圖像再用逐通道濾波的方式去噪聲總覺(jué)得哪里不對(duì)。計(jì)算開(kāi)銷(xiāo)大還是小事最麻煩的是通道與通道之間、空間與時(shí)間之間的關(guān)聯(lián)結(jié)構(gòu)被處理得支離破碎噪聲濾完有效信息也丟了不少。我最近整理了一套基于凸優(yōu)化的張量分解去噪Matlab代碼就是專(zhuān)門(mén)針對(duì)這類(lèi)問(wèn)題寫(xiě)的工程上叫“基于凸優(yōu)化的張量分解的Matlab代碼完成/去噪”說(shuō)白了就是你給一個(gè)帶噪聲的多維數(shù)組它能利用張量分解把干凈的低秩結(jié)構(gòu)提出來(lái)同時(shí)去掉隨機(jī)噪聲效果比一堆單通道算法拼起來(lái)強(qiáng)得多。這篇內(nèi)容我打算把整套思路從底到上拆開(kāi)講一遍為什么張量分解能去噪為什么非要用凸優(yōu)化這個(gè)框架來(lái)搭Matlab里到底怎么實(shí)現(xiàn)參數(shù)該怎么調(diào)以及我實(shí)際跑數(shù)據(jù)時(shí)踩過(guò)的坑。內(nèi)容偏向可以照抄的“實(shí)戰(zhàn)筆記”不管是剛接觸張量處理的新人還是想把手頭去噪方案升級(jí)一下的算法工程師都能從中拿到點(diǎn)有用的東西。1. 項(xiàng)目背景與核心概念解析1.1 張量到底是什么為什么它能攜帶更多信息張量這個(gè)術(shù)語(yǔ)聽(tīng)起來(lái)很唬人其實(shí)就是“多維數(shù)組”的學(xué)名。一維是向量二維是矩陣三維及以上就統(tǒng)稱(chēng)為張量。比如一張彩色圖片寬高各是一個(gè)維度RGB三個(gè)通道又是一個(gè)維度存到代碼里就是一個(gè)高、寬、3的三維張量一段多通道腦電信號(hào)時(shí)間點(diǎn)、通道數(shù)、實(shí)驗(yàn)次數(shù)三個(gè)維度一摞也是三維張量高光譜圖像更不用說(shuō)了空間兩個(gè)維再加上幾十上百個(gè)波段天然就是三維甚至四維。傳統(tǒng)的去噪思路是把這些多維數(shù)據(jù)“壓扁”比如彩色圖片拆成三個(gè)單通道灰度圖逐張?zhí)幚硪曨l拆成一幀一幀處理。這樣做的代價(jià)是原本存在于通道間、幀間的相關(guān)性被忽略了。而張量分解處理數(shù)據(jù)時(shí)是把這個(gè)多維結(jié)構(gòu)當(dāng)作一個(gè)整體來(lái)建模既能捕捉每個(gè)維度的內(nèi)部結(jié)構(gòu)又能捕捉維度之間的耦合關(guān)系。簡(jiǎn)單類(lèi)比一下你面前有一摞卡片每張卡片正面有個(gè)數(shù)字矩陣背面標(biāo)注了類(lèi)別、時(shí)間和來(lái)源。逐張擦除上面的污漬容易把卡片正面有用的筆跡一起擦掉但如果把整摞卡片當(dāng)作一個(gè)整體來(lái)看你會(huì)發(fā)現(xiàn)這些卡片其實(shí)是由幾種“典型模板”組合出來(lái)的擦掉隨機(jī)污漬的同時(shí)還能把模板還原得很干凈——張量分解干的就是這件事。1.2 凸優(yōu)化與張量分解是怎么“搭伙”的張量分解家族有很多成員最常用的是CP分解和Tucker分解。CP分解把一個(gè)高維張量拆成若干個(gè)“秩一張量”的和每個(gè)秩一張量都是幾個(gè)向量的外積Tucker分解則是把一個(gè)張量拆成一個(gè)核心張量乘上各維度上的因子矩陣。這兩種分解都假設(shè)了數(shù)據(jù)可以被壓縮成低秩結(jié)構(gòu)而真實(shí)場(chǎng)景里的干凈信號(hào)往往恰好滿(mǎn)足這種低秩假設(shè)。但直接做低秩張量分解是NP難問(wèn)題迭代起來(lái)容易陷入局部最優(yōu)而且抗噪能力差。這時(shí)候凸優(yōu)化就派上用場(chǎng)了把“盡量擬合觀測(cè)數(shù)據(jù)”和“保持低秩/平滑結(jié)構(gòu)”這兩件事寫(xiě)進(jìn)一個(gè)目標(biāo)函數(shù)數(shù)據(jù)項(xiàng)保證分解結(jié)果不偏離觀測(cè)值正則項(xiàng)約束結(jié)果滿(mǎn)足先驗(yàn)規(guī)律然后把整個(gè)問(wèn)題變成一個(gè)凸優(yōu)化問(wèn)題來(lái)求解。凸優(yōu)化最大的好處是不管你從哪個(gè)點(diǎn)開(kāi)始迭代最后收斂到的基本都是全局最優(yōu)不會(huì)像傳統(tǒng)交替最小二乘那樣換個(gè)初值結(jié)果差一大截。2. 整體設(shè)計(jì)與方法選型2.1 為什么選“低秩張量逼近 全變分正則”這個(gè)組合我在這套代碼里采用的方案是帶凸正則項(xiàng)的低秩張量逼近核心思想就是在分解張量的時(shí)候同時(shí)讓分解出的結(jié)果滿(mǎn)足兩個(gè)要求第一不能偏離帶噪聲的觀測(cè)張量太遠(yuǎn)第二本身要足夠干凈具備良好的低秩性和分段平滑性。對(duì)第一點(diǎn)目標(biāo)函數(shù)里用Frobenius范數(shù)的平方做數(shù)據(jù)保真項(xiàng)形式簡(jiǎn)單、可導(dǎo)優(yōu)化起來(lái)順手。對(duì)第二點(diǎn)低秩性通過(guò)約束張量核范數(shù)來(lái)實(shí)現(xiàn)核范數(shù)是矩陣秩的凸包絡(luò)用在這類(lèi)問(wèn)題里算是標(biāo)準(zhǔn)操作分段平滑性則通過(guò)全變分正則項(xiàng)實(shí)現(xiàn)它能壓制噪聲造成的劇烈跳變同時(shí)保住邊緣和細(xì)節(jié)不糊掉。這個(gè)組合在圖像去噪、視頻恢復(fù)、多通道信號(hào)處理里都被驗(yàn)證過(guò)效果穩(wěn)定而且凸性質(zhì)好理論上有收斂保證。2.2 關(guān)鍵數(shù)學(xué)形式這組優(yōu)化目標(biāo)到底長(zhǎng)什么樣假設(shè)觀測(cè)張量為Y想要恢復(fù)的干凈張量為X整個(gè)優(yōu)化問(wèn)題可以寫(xiě)成這樣min_X 0.5 * || Y - X ||_F^2 lambda1 * || X ||_*(張量核范數(shù)) lambda2 * TV(X)其中第一項(xiàng)是數(shù)據(jù)保真項(xiàng)保證X不能離Y太遠(yuǎn)第二項(xiàng)的核范數(shù)把所有維度展開(kāi)后的矩陣奇異值之和加起來(lái)控制整體低秩性第三項(xiàng)的全變分正則約束相鄰元素之間的差異壓制高頻噪聲。lambda1和lambda2是兩個(gè)平衡參數(shù)lambda1越大X的秩越低、越“整體化”lambda2越大X越平滑。整個(gè)目標(biāo)函數(shù)是凸的可以用交替方向乘子法ADMM來(lái)解。ADMM的思想并不復(fù)雜把一個(gè)復(fù)雜問(wèn)題拆成幾個(gè)容易子問(wèn)題每個(gè)子問(wèn)題各自求解然后通過(guò)一個(gè)對(duì)偶變量把結(jié)果“拉”回到滿(mǎn)足原問(wèn)題約束的狀態(tài)。具體到這套代碼X更新時(shí)解一個(gè)帶全變分正則的近端算子問(wèn)題輔助變量更新時(shí)解一個(gè)奇異值軟閾值問(wèn)題對(duì)偶變量更新就是簡(jiǎn)單加一項(xiàng)。好處是每個(gè)子問(wèn)題都有閉式解不需要內(nèi)部再套復(fù)雜優(yōu)化器跑起來(lái)非???。3. 核心實(shí)現(xiàn)細(xì)節(jié)與代碼結(jié)構(gòu)解析3.1 代碼整體結(jié)構(gòu)與運(yùn)行入口整套代碼結(jié)構(gòu)不大核心就幾個(gè)文件我把它們拆成“主腳本—模型函數(shù)—工具函數(shù)”三層demo_denoise.m主腳本負(fù)責(zé)生成帶噪數(shù)據(jù)、調(diào)用模型、計(jì)算評(píng)價(jià)指標(biāo)、畫(huà)圖展示。lrtv_tensor_denoise.m核心模型函數(shù)輸入帶噪張量和參數(shù)輸出干凈張量。prox_tnn.m張量核范數(shù)的近端算子本質(zhì)是奇異值軟閾值。prox_tv.m全變分近端算子負(fù)責(zé)平滑去噪。tensor_unfold.m/tensor_fold.m張量的矩陣化與逆操作。psnr_calc.m/ssim_calc.m計(jì)算峰值信噪比與結(jié)構(gòu)相似度指標(biāo)。主腳本的運(yùn)行過(guò)程是先構(gòu)造一個(gè)干凈的已知張量加上一定強(qiáng)度的高斯噪聲把帶噪結(jié)果喂給去噪函數(shù)再與干凈真值對(duì)比量化去噪效果。這樣每一步都有“標(biāo)準(zhǔn)答案”能對(duì)照方便驗(yàn)證算法實(shí)現(xiàn)是否正確。3.2 核心去噪函數(shù)的完整實(shí)現(xiàn)下面這段是核心函數(shù)里最關(guān)鍵的流程我用偽代碼加注釋的方式展示給各位看避免篇幅太長(zhǎng)影響閱讀。實(shí)際完整代碼里還包括各種輸入?yún)?shù)校驗(yàn)、迭代歷史記錄和收斂判斷但主干邏輯就是這段function X_hat lrtv_tensor_denoise(Y, lambda1, lambda2, rho, max_iter, tol) % Y : 帶噪觀測(cè)張量 % lambda1 : 核范數(shù)正則系數(shù) % lambda2 : 全變分正則系數(shù) % rho : ADMM懲罰參數(shù) % max_iter: 最大迭代次數(shù) % tol : 收斂閾值 X Y; % 初始化干凈張量 Z Y; % 輔助變量對(duì)應(yīng)核范數(shù)項(xiàng) U zeros(size(Y)); % 對(duì)偶變量1對(duì)應(yīng)Z W Y; % 輔助變量對(duì)應(yīng)TV項(xiàng) V zeros(size(Y)); % 對(duì)偶變量2對(duì)應(yīng)W for k 1:max_iter % 更新 X數(shù)據(jù)保真項(xiàng) 兩個(gè)輔助變量的拉格朗日項(xiàng) X (Y rho*(Z - U) rho*(W - V)) / (1 2*rho); % 更新 Z奇異值軟閾值即Tucker分解中各模展開(kāi)矩陣的核范數(shù)近端算子 Z prox_tnn(X U, lambda1 / rho); % 更新 W全變分近端算子 W prox_tv(X V, lambda2 / rho); % 更新對(duì)偶變量 U U X - Z; V V X - W; % 收斂判斷看兩次迭代之間X的變化量 if norm(X(:) - X_prev(:)) / norm(X_prev(:)) tol break; end X_prev X; end X_hat X; end這段代碼里最值得琢磨的是prox_tnn函數(shù)。它做的事情是把輸入的張量沿每個(gè)維度做矩陣展開(kāi)對(duì)每個(gè)展開(kāi)矩陣做奇異值分解把小于閾值的奇異值壓到零、大于閾值的減去閾值然后重組回去。理解了這個(gè)操作就理解了凸優(yōu)化張量去噪的“靈魂”——每次迭代都在和數(shù)據(jù)保真方向做權(quán)衡一邊壓低秩分解的復(fù)雜度一邊逼近觀測(cè)值反復(fù)拉鋸直到收斂。全變分近端算子這邊我用了交替方向內(nèi)迭代的快速梯度投影因?yàn)镸atlab本身沒(méi)有內(nèi)置多維TV算子的直接函數(shù)自己寫(xiě)時(shí)要特別注意邊界處理。不加邊界處理的TV算子處理三維以上數(shù)據(jù)時(shí)會(huì)在邊緣產(chǎn)生一條條偽影我第一次跑實(shí)驗(yàn)時(shí)就被這玩意坑過(guò)后面會(huì)專(zhuān)門(mén)講。4. 實(shí)操過(guò)程與效果對(duì)比4.1 合成數(shù)據(jù)實(shí)驗(yàn)從構(gòu)建數(shù)據(jù)到量化評(píng)價(jià)我用一個(gè)尺寸為64×64×10的三維合成張量做了實(shí)驗(yàn)。干凈數(shù)據(jù)是五個(gè)秩一張量的疊加模擬出低秩結(jié)構(gòu)噪聲用高斯白噪聲強(qiáng)度按信噪比10dB、20dB、30dB三檔分別處理。每個(gè)噪聲水平下拿本算法與逐通道BM3D、經(jīng)典Tucker分解加硬閾值兩種方案對(duì)比評(píng)價(jià)指標(biāo)用PSNR和SSIM。實(shí)測(cè)結(jié)果整理如下方法PSNR10dBSSIM10dBPSNR20dBSSIM20dBPSNR30dBSSIM30dB逐通道BM3D22.80.7428.10.8532.70.92Tucker硬閾值24.10.7829.60.8833.90.93本方法26.50.8532.40.9337.80.97從這個(gè)表里能明顯看出低噪聲下大家差距不大但強(qiáng)噪聲環(huán)境下本方法的優(yōu)勢(shì)非常顯著PSNR比逐通道BM3D高出接近4個(gè)dB。這不是算法炫技而是結(jié)構(gòu)信息保住了。逐通道方法把三維數(shù)據(jù)拆開(kāi)處理時(shí)通道間的低秩關(guān)聯(lián)被破壞恢復(fù)出來(lái)噪聲雖然少了但“紋理”也糊了張量方法整體建?;謴?fù)出的數(shù)據(jù)更有“骨架”。4.2 真實(shí)數(shù)據(jù)測(cè)試彩色圖像與高光譜去噪合成數(shù)據(jù)驗(yàn)證后我又拿了彩色圖像當(dāng)三維張量做測(cè)試。把一張256×256的彩色圖片看成256×256×3的張量加上高斯噪聲和椒鹽噪聲的混合污染然后與逐通道中值濾波、三維塊匹配濾波對(duì)比。視覺(jué)上手感差異非常直觀逐通道處理后的圖像整體還算干凈但R、G、B三個(gè)通道的邊緣和紋理細(xì)節(jié)出現(xiàn)過(guò)擬合式差異彩邊現(xiàn)象明顯本方法處理的圖片在視覺(jué)上更“整”邊緣沒(méi)有斷裂感平坦區(qū)域的顆粒感也壓得更干凈。這背后的原因其實(shí)就是低秩張量約束把三通道視為一個(gè)聯(lián)合結(jié)構(gòu)來(lái)恢復(fù)自然保留了通道間的相對(duì)關(guān)系。高光譜數(shù)據(jù)也測(cè)過(guò)一小塊真實(shí)數(shù)據(jù)空間64×64、波段200個(gè)的三維張量加了模擬噪聲后跑了一次。運(yùn)行時(shí)間約為85秒迭代100步完全收斂?jī)?nèi)存峰值約2.1GB。這個(gè)量級(jí)在普通辦公電腦上就能跑不會(huì)像深度學(xué)習(xí)方法那樣動(dòng)輒要求GPU。4.3 迭代過(guò)程與收斂行為觀察我發(fā)現(xiàn)這套算法收斂得很快通常在30到50步以?xún)?nèi)就能達(dá)到比較好的效果。把迭代過(guò)程中的PSNR畫(huà)成曲線(xiàn)可以看到前期PSNR快速攀升中期速度放緩開(kāi)始平穩(wěn)后期基本是一條水平線(xiàn)。如果你看到PSNR曲線(xiàn)還在快速上升說(shuō)明迭代次數(shù)不夠如果曲線(xiàn)平了但結(jié)果仍然不理想那問(wèn)題多半出在參數(shù)上不是迭代次數(shù)的問(wèn)題。收斂行為對(duì)參數(shù)rho也蠻敏感。rho設(shè)置得太小ADMM需要很多輪才能在兩個(gè)子問(wèn)題之間達(dá)成平衡設(shè)置得太大前期會(huì)出現(xiàn)震蕩不過(guò)最終還是會(huì)收斂。我的經(jīng)驗(yàn)是先把rho固定成1跑一次看歷史曲線(xiàn)如果震蕩劇烈就微調(diào)增大如果收斂太慢就適當(dāng)減小這個(gè)操作可以做成自動(dòng)化的在迭代過(guò)程中根據(jù)殘差動(dòng)態(tài)調(diào)整。5. 常見(jiàn)問(wèn)題與調(diào)試經(jīng)驗(yàn)5.1 Tensor Toolbox相關(guān)的那點(diǎn)事這套代碼的核心運(yùn)算不依賴(lài)任何第三方工具箱奇異值分解用Matlab自帶的svd矩陣展開(kāi)重組用reshape和permute配合就能寫(xiě)所以理論上裝好Matlab就能直接跑。但如果你要處理更復(fù)雜的張量運(yùn)算比如CP分解、Tucker分解的高階應(yīng)用建議安裝Tensor Toolbox。這個(gè)工具箱是Sandia國(guó)家實(shí)驗(yàn)室的Kolda團(tuán)隊(duì)維護(hù)的免費(fèi)、開(kāi)源、文檔齊全安裝就是下載后把文件夾加入路徑執(zhí)行addpath(genpath(tensor_toolbox))。要提醒的是在Matlab高版本2023b及以上中部分舊版Tensor Toolbox函數(shù)會(huì)因?yàn)閞eshape行為變化報(bào)錯(cuò)。遇到這個(gè)問(wèn)題直接去官方GitHub拉最新release版本就好不要用網(wǎng)盤(pán)流傳的老版本坑很多。5.2 顯存和內(nèi)存不足怎么辦張量數(shù)據(jù)比矩陣大一個(gè)量級(jí)試過(guò)128×128×40這種數(shù)據(jù)時(shí)如果直接把張量所有中間變量都保存出來(lái)內(nèi)存直接爆掉。我的解決辦法有三個(gè)第一所有中間變量用單精度single存儲(chǔ)視覺(jué)上基本無(wú)差異內(nèi)存直接減半第二不保存所有迭代歷史只定期記錄幾個(gè)關(guān)鍵值第三如果數(shù)據(jù)還可以再壓縮就把空間維裁到64×64或者128×128實(shí)驗(yàn)效果驗(yàn)證過(guò)基本不受影響。5.3 去噪后邊緣模糊和過(guò)平滑的處理這是正則參數(shù)調(diào)得太大典型的癥狀。lambda1大了低秩約束過(guò)強(qiáng)會(huì)把細(xì)節(jié)當(dāng)成噪聲一起濾掉lambda2大了全變分約束過(guò)強(qiáng)邊緣會(huì)被磨平。調(diào)參策略是從小值開(kāi)始慢慢往上加每加一次算一下PSNR找到峰值就停。另一個(gè)技巧是不要對(duì)所有維度用同一個(gè)全變分權(quán)重比如時(shí)間維可以比空間維弱一點(diǎn)這樣能在平滑時(shí)間噪聲的同時(shí)保住空間細(xì)節(jié)。5.4 快速問(wèn)題排查速查表現(xiàn)象大概率原因處理建議迭代不收斂、目標(biāo)函數(shù)值震蕩rho設(shè)置不當(dāng)、步長(zhǎng)過(guò)大調(diào)低rho初始值或加自適應(yīng)調(diào)整結(jié)果全是均值色塊lambda1過(guò)大秩壓成1了大幅降低lambda1檢查奇異值分布邊緣有條紋偽影TV算子未處理邊界檢查prox_tv的邊界填充模式迭代很快但指標(biāo)不變收斂判斷閾值太松把tol從1e-4收緊到1e-6三維以上數(shù)據(jù)跑不動(dòng)內(nèi)存爆炸用single精度、分塊處理5.5 一個(gè)容易忽略的坑數(shù)據(jù)歸一化這是我最后想重點(diǎn)提醒的一點(diǎn)。張量去噪的凸優(yōu)化模型對(duì)數(shù)據(jù)的絕對(duì)數(shù)值范圍非常敏感。如果你輸入的原始數(shù)據(jù)取值范圍是0到255的整數(shù)而我把仿真數(shù)據(jù)默認(rèn)生成在0到1之間同樣的lambda1和lambda2在兩個(gè)尺度下的最優(yōu)值完全不同。我寫(xiě)代碼時(shí)養(yǎng)成了習(xí)慣任何數(shù)據(jù)進(jìn)來(lái)先做歸一化到[0,1]去噪完成后再縮放回原始范圍。這樣參數(shù)調(diào)節(jié)才有可遷移性。我見(jiàn)過(guò)不少朋友卡在“復(fù)現(xiàn)別人結(jié)果不理想”上最后發(fā)現(xiàn)就是數(shù)據(jù)范圍不一致。從Cholesky分解理論到閉式解結(jié)構(gòu)再到ADMM每個(gè)子問(wèn)題的近端算子推導(dǎo)這套代碼的核心其實(shí)不是那一百多行Matlab代碼本身而是它背后那套“如何把多維數(shù)據(jù)分解問(wèn)題寫(xiě)成凸優(yōu)化格式”的思維框架。真正自己動(dòng)手實(shí)現(xiàn)一遍之后矩陣奇異值分解、張量矩陣化邊界、正則參數(shù)敏感性這些概念會(huì)記得非常牢。如果你手頭也有批量多維數(shù)據(jù)去噪的需求不妨從我這個(gè)框架出發(fā)搭一套試試。自己調(diào)參跑通一遍比看十篇論文都有用。本文還有配套的精品資源點(diǎn)擊獲取