# Denoising Diffusion Probabilistic Models As a Defense Against Adversarial Attacks https://github.com/ankile/Adversarial-Diffusion ## 簡介 類似之前看過的DiffPure,也是利用去噪Diffusion Models作為樣本進入分類器的前處理,不過採用的Diffusion Models是DDPM而非Score-based Generative Models,並且對Noise Level做最佳化,決定t* = 0.04(40個推理步驟),降低Reverse Process的耗時。 ![](https://hackmd.io/_uploads/BJsedheBh.png) ## 方法 ### 最佳化Noise Level(尋找t*) 測試Noise Level在[0.001, 0.300]區間分類器的穩健性,發現穩健性最高的Noise Level是0.10,在0.10之後模型穩健性開始出現下滑的趨勢(加入的Noise太多導致DDPM無法較正確地恢復原本的圖片),但0.10需要經過100步的推理步驟,速度太慢了,因此在速度與準確性的權衡下決定t* = 0.04,大幅提升性能的前提下也能有不錯的效果。 ![](https://hackmd.io/_uploads/SkWCG2gS3.png) 不過要注意的是t\* = 0.04並不是一個通用的最佳解,可能會因為分類器要分類的樣本的不同而有影響,因為對抗攻擊對不同樣本的攻擊效果有所不同,因此在實作上需要根據不同的樣本決定出不同的t*。 ### 驗證 對比未經處理的ResNet101、NoiseDefense(只做了Forward Process,有一定的防禦效果)、對抗訓練、以及經過DDPM前處理的ResNet101在受到對抗攻擊前後的準確性以及穩健性。這邊是使用知道分類器模型內部參數的adaptive attacks來做測試。 ![](https://hackmd.io/_uploads/S1JB_CxHn.png) 從結果來看,可以發現Diffusion Models在幾乎不影響分類起準確性的前提下,有效地提升了分類器穩健性,並且相較於其他方法擁有更佳的效果。 ## 改善空間 ### 調整Noise Scheduler參數 閱讀程式碼時發現在呼叫DDPM時需要先定義一個Noise Scheduler如下 ``` noise_scheduler = DDPMScheduler(num_train_timesteps=1000) ``` 這裡所下的參數1_1000是指將Timestep分為1000個步驟(1000個Noise Level) 以這論文所提的T\* = 0.04為例,要經過的迭代次數就是0.04 * 1000 = 40次 而迭代次數最直接影響到的就是模型在去噪時的推理速度,也很有可能會影響到最終生成/去噪的效果 ### 嘗試不同的Noise Scheduler https://huggingface.co/docs/diffusers/v0.3.0/en/api/schedulers Diffusers文件中有提供多種不同的Noise Scheduler ### 和分類器一起訓練? 可以考慮將分類器和Diffusion一起訓練,如果在Diffusion Models的訓練中考量了對分類器的影響,理應可以提升Reverse後結果的準確性,不過缺點就是損失了原本前處理與分類器解耦合的優點,但仍然保留了面對新的對抗攻擊不需要重新訓練的特性(相較於對抗訓練)。 ### 硬體加速(較難執行) 雖然在最佳化Noise Level、找到合適的t*值之後可以在維持差不多的準確性下大幅提升效能,但Diffusion Model的速度依然有點慢,之後或許可以參考Stable Diffusion等生成式模型對Diffusion Models的優化來加速Reverse Process。 或者可以使用Speed Is All You Need透過GPU最佳化加速Diffusion Models的推理速度(?) https://arxiv.org/abs/2304.11267 https://www.techbang.com/posts/105841-google-stable-diffusion ## TODO 藉由程式碼更深入理解DDPM https://github.com/huggingface/blog/blob/main/annotated-diffusion.md https://zhuanlan.zhihu.com/p/599286621 https://colab.research.google.com/github/huggingface/notebooks/blob/main/diffusers/diffusers_intro.ipynb https://medium.com/ai-blog-tw/%E9%82%8A%E5%AF%A6%E4%BD%9C%E9%82%8A%E5%AD%B8%E7%BF%92diffusion-model-%E5%BE%9Eddpm%E7%9A%84%E7%B0%A1%E5%8C%96%E6%A6%82%E5%BF%B5%E7%90%86%E8%A7%A3-4c565a1c09c ## 程式碼測試 前面都是載入套件&實驗資料 ### 定義擴散模型 ![](https://hackmd.io/_uploads/BklniRlH3.png) 使用2D UNET用於分割影像,並設定了影像解析度,輸入/輸出的RGB通道,UNet 區塊中使用的 ResNet 層數量,UNet區塊的輸出通道數。 ### 定義噪音程序 ![](https://hackmd.io/_uploads/Syd_6RgH3.png) DDPMScheduler模組實現了一個動態散佈式點過程(擴散的係數) noise_scheduler的1000為生成噪音的係數 sample_image = dataset[0][0].unsqueeze(0)是從資料集中獲取一個樣本影像,並使用 unsqueeze(0) 方法在第 0 維度上增加一個維度,以符合 add_noise 方法的輸入要求。(應該類似把噪音疊加上去 noisy_image = noise_scheduler.add_noise(sample_image, noise, timesteps)接收3個參數(樣本影像,噪音和,時間步長)給樣本影像 ### train ![](https://hackmd.io/_uploads/HywFmJ-Sn.png) 使用了 AdamW 優化器和餘弦學習率調度器(暫時不懂)來訓練模型 def make_grid(images, rows, cols)用於將一組影像排列成網格。該函式接收三個參數,分別是影像列表、網格的行數和列數。函式會將影像按照指定的行數和列數排列成網格。 def evaluate(config, epoch, pipeline)用於評估模型並生成樣本影像。該函式接收三個參數,分別是設定配置、當前的訓練輪次和 DDPMPipeline 物件。函式中的操作包括從隨機噪音中生成一批樣本影像,將這些影像排列成網格,並將網格影像保存到磁碟中。 ### training loop 大部分為制式化的加速訓練模組 應該不太重要 ### 加噪音 不太重要 只寫了將原圖加噪音的code ### 去躁 @torch.no_grad():使用 torch.no_grad() 裝飾器,表示在這個函式中不需要計算梯度。 def denoise(img, ts, progress=True)::定義了一個去噪函式 denoise,該函式接收一個含噪音的影像 img、時間步長 ts 和一個進度條標誌 progress(預設為 True)。 noise_scheduler.set_timesteps(1_000):設定噪音調度器的時間步長為 1,000。 img_reconstruct = img.reshape(1, 3, config.image_size, config.image_size).to("cuda"):將影像 img 重新塑形並移至 CUDA 設備上,作為重構影像的起點。 for t in tqdm(noise_scheduler.timesteps[-ts:], disable=not progress)::使用進度條迭代噪音調度器的時間步長。 model_output = trained_model(img_reconstruct, t).sample:使用訓練好的模型 trained_model 對 img_reconstruct 進行預測,獲得模型的輸出。 img_reconstruct = noise_scheduler.step(model_output, t, img_reconstruct).prev_sample:使用噪音調度器對 img_reconstruct 進行反向擴散,獲得上一時間步的影像重構。 return img_reconstruct:返回最終的影像重構結果。 img_reconstruct = denoise(noisy_image, noise_level):使用 denoise 函式對加入噪音的影像 noisy_image 進行去噪,得到影像的重構結果 img_reconstruct。 reconstructed = img_reconstruct.detach().cpu():將重構結果 img_reconstruct 從 CUDA 設備移至 CPU。 show(reconstructed):使用 show 函式展示去噪後的重構影像 reconstructed。 fig, axs = plt.subplots(1, 3, figsize=(16, 4)):創建一個大小為 (16, 4) 的子圖。 images = dict(...):建立一個字典 images,其中包含了原始影像、加噪影像和重構影像。 for i, (title, image) in enumerate(images.items())::使用迴圈遍歷 images 字典中的項目。 axs[i].imshow(show(image)):在子圖中展示影像。