# 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的耗時。

## 方法
### 最佳化Noise Level(尋找t*)
測試Noise Level在[0.001, 0.300]區間分類器的穩健性,發現穩健性最高的Noise Level是0.10,在0.10之後模型穩健性開始出現下滑的趨勢(加入的Noise太多導致DDPM無法較正確地恢復原本的圖片),但0.10需要經過100步的推理步驟,速度太慢了,因此在速度與準確性的權衡下決定t* = 0.04,大幅提升性能的前提下也能有不錯的效果。

不過要注意的是t\* = 0.04並不是一個通用的最佳解,可能會因為分類器要分類的樣本的不同而有影響,因為對抗攻擊對不同樣本的攻擊效果有所不同,因此在實作上需要根據不同的樣本決定出不同的t*。
### 驗證
對比未經處理的ResNet101、NoiseDefense(只做了Forward Process,有一定的防禦效果)、對抗訓練、以及經過DDPM前處理的ResNet101在受到對抗攻擊前後的準確性以及穩健性。這邊是使用知道分類器模型內部參數的adaptive attacks來做測試。

從結果來看,可以發現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
## 程式碼測試
前面都是載入套件&實驗資料
### 定義擴散模型

使用2D UNET用於分割影像,並設定了影像解析度,輸入/輸出的RGB通道,UNet 區塊中使用的 ResNet 層數量,UNet區塊的輸出通道數。
### 定義噪音程序

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

使用了 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)):在子圖中展示影像。