# 8.8 深度卷積對抗網路 DCGAN是一個使用深度卷積為主架構的GAN模型。它比普通的GAN更能提取圖片的特徵等細節,故生成能力會比使用普通全連接層 (Dense)的GAN要來的優秀。另外也因為CNN訓練參數比單純Dense層來的少一點,所以訓練速度基本上會比較快。 1. 取消所有pooling層。G網路中使用轉置卷積(transposed convolutional layer)進行上取樣,D網路中用加入stride的卷積代替pooling。 2. 在 D 和 G 中均使用 batch normalization 3. 去掉 FC 層,使網路變為 全卷積網路 (Conv2D) 4. G網路中使用 ReLU 作為啟用函式,最後一層使用 tanh 5. D網路中使用 LeakyReLU 作為啟用函式 ![螢幕擷取畫面 2023-12-23 204642](https://hackmd.io/_uploads/HJcRLLEDp.png) ```python= import torch from torch import nn import numpy as np import torchvision.datasets as dset import torchvision.transforms as transforms import torch.optim as optim from torchvision.utils import save_image import matplotlib.pyplot as plt import matplotlib.animation as animation # 可以用多圖製作成 animation : So cool import torchvision.utils as vutils """ workflow: 1.下載mnist 2.資料前處理(正規化) 3.建構生成器跟鑑別器 4.先訓練鑑別器讓他具有一定的判識能力(凍結 G ) 5.再同時訓練 G,D 6.Predict 結果 """ class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() self.main = nn.Sequential( # input is (1) x 28 x 28 nn.Conv2d(1, 64, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), # state size. (64) x 14 x 14 nn.Conv2d(64, 128, 4, 2, 1, bias=False), nn.BatchNorm2d(128), nn.LeakyReLU(0.2, inplace=True), # state size. (128) x 7 x 7 nn.Conv2d(128, 256, 3, 2, 1, bias=False), nn.BatchNorm2d(256), nn.LeakyReLU(0.2, inplace=True), # state size. (256) x 4 x 4 nn.Conv2d(256, 1, 4, 1, 0, bias=False), # state size. (1) x 1 x 1 nn.Sigmoid() ) def forward(self, input): return self.main(input) class Generator(nn.Module): def __init__(self, z_dim): super(Generator, self).__init__() self.main = nn.Sequential( # input is Z, going into a convolution nn.ConvTranspose2d( z_dim, 256, 4, 1, 0, bias=False), nn.BatchNorm2d(256), nn.ReLU(True), # state size. (256) x 4 x 4 nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False), nn.BatchNorm2d(128), nn.ReLU(True), # state size. (128) x 8 x 8 nn.ConvTranspose2d( 128, 64, 4, 2, 1, bias=False), nn.BatchNorm2d(64), nn.ReLU(True), # state size. (64) x 16 x 16 nn.ConvTranspose2d( 64, 1, 4, 2, 3, bias=False), # state size. (64) x 28 x 28 nn.Tanh() ) def forward(self, input): return self.main(input) class main(): def __init__(self, batch_size=64, z_dim=100, epochs=100, lr=0.001): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # gpu device self.batch_size = batch_size self.z_dim = z_dim self.epochs = epochs self.lr = lr self.G = Generator(self.z_dim).to(self.device) self.D = Discriminator().to(self.device) self.criterion = nn.BCELoss() # loss self.G_optimizer = optim.Adam(self.G.parameters(), lr = self.lr) # optimizer self.D_optimizer = optim.Adam(self.D.parameters(), lr = self.lr) self.G_losses = [] self.D_losses = [] self.img_list = [] def get_dataloader(self): """取得資料集""" # Pytorch 的 dataset 就是 dataloader,可以直接用 transforms 的方式 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean = (0.5,), std = (0.5,)), ]) # 取得 Mnist 的 Dataset train_set = dset.MNIST(root='./mnist_data/', train=True, transform=transform, download=True) test_set = dset.MNIST(root='./mnist_data/', train=False, transform=transform, download=False) # 導入 Data_loader train_loader = torch.utils.data.DataLoader( dataset = train_set, batch_size = self.batch_size, shuffle=True ) test_loader = torch.utils.data.DataLoader( dataset = test_set, batch_size = self.batch_size, shuffle=False ) return train_loader, test_loader def D_train(self, x): """ D 要學會如何分辨 真實 / 假 圖片,所以要計算 Real Loss 跟 Fake Loss """ # 清空Grad 不然梯度會累加 self.D.zero_grad() # 訓練辨識真實圖片: x_real = x.to(self.device) # 64,1,28,28 y_real = torch.ones(self.batch_size,).to(self.device) # 64 個 1 x_real_predict = self.D(x_real) # 64,1,1,1 D_real_loss = self.criterion(x_real_predict.view(-1), y_real) # view make [64,1,1,1] -> [64] and cal loss D_real_loss.backward() # 丟到 Grad 自動計算倒傳遞 # 訓練辨識假圖片: noise = torch.tensor(torch.randn(self.batch_size, self.z_dim,1 , 1, device=self.device)) # torch.Size([64, 100, 1, 1]) y_fake = torch.zeros(self.batch_size,).to(self.device) # 64 個 0 x_fake = self.G(noise) # 64,1,28,28 x_fake_predict = self.D(x_fake) # 64,1,1,1 D_fake_loss = self.criterion(x_fake_predict.view(-1), y_fake) # view make [64,1,1,1] -> [64] and cal loss D_fake_loss.backward() # 丟到 Grad 自動計算倒傳遞 # 這裡只計算 D 的 loss D_total_loss = D_real_loss + D_fake_loss # Total Loss 是全部的 Loss 方便查看 self.D_optimizer.step() # auto gradient return D_total_loss.data.item() def G_train(self): """ 清空 grad 並且從 noise 生成圖片,指定成 label: 1,因為要騙 D 說他是正確的,但是 D 不會被騙 所以他 loss 一開始會很高 """ # 清空Grad 不然梯度會累加 self.G.zero_grad() # 訓練生成真實圖片 noise = torch.tensor(torch.randn(self.batch_size, self.z_dim,1 , 1, device=self.device)) # torch.Size([64, 100, 1, 1]) y_target = torch.ones(self.batch_size,).to(self.device) # 64 個 1 x_fake = self.G(noise) # 生成假圖片 y_fake = self.D(x_fake) # 獲得 D 的分數 # 這裡只計算 G 的 loss G_loss = self.criterion(y_fake.view(-1), y_target) G_loss.backward() self.G_optimizer.step() return G_loss.data.item() def Draw_plot(self): plt.figure() plt.title(" D Loss, G Loss / Iteration ") plt.plot(self.G_losses, label='G') plt.plot(self.D_losses, label='D') plt.xlabel("Iteration") plt.ylabel("Loss") plt.legend() plt.show() def Draw_Anim_Image(self): fig = plt.figure(figsize=(8,8)) plt.axis("off") ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in self.img_list] ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000, blit=True) #ani.save('test.gif',writer='imagemagick', dpi=100 ) plt.show() def Train(self): # 打印出訓練資訊 print("\nBasic GAN Implement\n") print("Star Training\t",end='') if self.device.type == 'cuda': print("\tUse {}\n".format(torch.cuda.get_device_name(0))) else: print("\tUse CPU\n") # 取得 data loader train_loader, test_loader = self.get_dataloader() # 總共訓練 epochs 次 for epoch in range(self.epochs): # 從 DataLoader 獲得資料,Batch_size 是 64 所以每次獲得的 train_x = [64,1,28,28],但是我們不需要 label 所以先不使用 for id, (train_x,_) in enumerate(train_loader): # 有些 Loader 沒有64張會報錯,最值觀就這樣解決,捨棄掉最後一組 Loader if(len(train_x)==64): # D_train 跟 G_train 都會返回當前 loss,這邊將他儲存起來 self.D_losses.append( self.D_train(train_x) ) self.G_losses.append( self.G_train() ) # 每隔50次 顯示當前 Loss 的平均值 (老實說我不知道這樣是否標準,不過很直觀拉) if(id%50==0): print('[{:03}/{:03}]\t[{:03}/{:03}]\t Loss D: {:.4f} \tLoss G: {:.4f}'.format(epoch+1, self.epochs, id, len(train_loader), np.mean(self.D_losses), np.mean(self.G_losses))) # 每隔10次產生一張假圖,為了做動畫用 if(id%10==0): with( torch.no_grad() ): noise = torch.tensor(torch.randn(self.batch_size, self.z_dim,1 , 1, device=self.device)) fake = self.G(noise).detach().cpu() # 透過 detach() 可以不影響倒傳遞的狀況下拿到 Tensor,並且從 gpu 中取出 self.img_list.append(vutils.make_grid(fake, padding=0, normalize=True)) # 將圖片存下製作GIF self.Draw_plot() # 畫出訓練的曲線圖 self.Draw_Anim_Image() # 將多個輸出的圖轉成動畫 if __name__ == '__main__': # batch_size=64, z_dim=100, epochs=1000, lr=0.001 train = main(64, 100, 1, 0.001 ).Train() ``` ![DCGAN_loss_curve](https://hackmd.io/_uploads/HkCMDINP6.png)