pytorch GAN手寫體識別

這是一個github的教程须喂,我用自己的理解復(fù)述一遍趟妥,方面記憶

一庇勃、關(guān)于生成對抗網(wǎng)絡(luò)的第一篇論文是Generative Adversarial Networks,這是2014年發(fā)表的一篇論文

GAN模型的基本原理

二倒彰、隨機產(chǎn)生噪聲數(shù)據(jù)(至于為什么要使用噪聲作為輸入數(shù)據(jù)可以參考這里對抗生成網(wǎng)絡(luò)GAN為什么輸入隨機噪聲?), 通過生成器變成一個假數(shù)據(jù)(Fake Data)一般來說是圖片莱睁。

三待讳、生成器的目標(biāo)是盡力生成和目標(biāo)數(shù)據(jù)分布一致的假數(shù)據(jù), 而判別模型目標(biāo)是分辨真數(shù)據(jù)和假數(shù)據(jù), 抽象來說可以看下圖:


GAN原理

假設(shè)數(shù)據(jù)是一維數(shù)據(jù),如圖所示仰剿,上方的圖形(綠線创淡,虛線)表示數(shù)據(jù)的密度分布。綠線表示真實數(shù)據(jù)分布酥馍,大虛線表示GAN生成數(shù)據(jù)的分布辩昆,而小虛線表示一個判別函數(shù)(你可以近似看成Sigmoid)。

(a)圖中由于沒有迭代旨袒,導(dǎo)致判別函數(shù)只能大致判斷真數(shù)據(jù)和假數(shù)據(jù)汁针。
(b)圖中經(jīng)過訓(xùn)練,可以看出判別函數(shù)已經(jīng)可以大致判斷出真數(shù)據(jù)和假數(shù)據(jù)分布(你可以理解為>0.5表示假數(shù)據(jù),<0.5表示真數(shù)據(jù))砚尽。
(c)圖中施无,經(jīng)過不斷學(xué)習(xí),GAN生成的數(shù)據(jù)和真實分布已經(jīng)很接近了,這是判別函數(shù)已經(jīng)很難區(qū)分真實數(shù)據(jù)和假數(shù)據(jù)分布了必孤。
(d)最終猾骡,真數(shù)據(jù)和假數(shù)據(jù)分布一致,判別函數(shù)無法判斷(恒為0.5)敷搪,這是GAN生成的數(shù)據(jù)和真實數(shù)據(jù)分布已經(jīng)一致兴想,可以達(dá)到以假亂真。

1. 加載必要的包

這里使用的pytorch==1.3.0, torchvision==0.4.1

import os
import numpy as np

import torchvision.transforms as transforms # 對數(shù)據(jù)進(jìn)行轉(zhuǎn)化赡勘,歸一化等操作
from torchvision.utils import save_image

from torch.utils.data import DataLoader # 加載數(shù)據(jù)嫂便,變成一個類似迭代器的東西
from torchvision import datasets # 再帶的數(shù)據(jù)數(shù)據(jù)集,一般都是常用的數(shù)據(jù)集

import torch.nn as nn
import torch.nn.functional as F
import torch

2. 參數(shù)預(yù)設(shè)

這里使用一個字典opt保存所需變量

opt = {}
opt['n_epochs'] = 200 # 迭代次數(shù)
opt['batch_size'] = 64 # 每個batch的樣本數(shù)
opt['lr'] = 0.0002 # 優(yōu)化器adam的學(xué)習(xí)率
opt['b1'] = 0.5 # 優(yōu)化器adam的梯度的一階動量衰減 momentum
opt['b2'] = 0.999 # 優(yōu)化器adam的梯度的二階動量衰減 momentum

opt['latent_dim'] = 100  # latent(潛)空間的維數(shù), 可以理解為噪聲數(shù)據(jù)的維度
opt['img_size'] = 28 # 輸入數(shù)據(jù)是一個1*28*28的灰度圖片
opt['channels'] = 1 # RGB通道個數(shù),這里是1個通道的灰度圖
opt['sample_interval'] = 400 # 圖像采樣間隔(做記錄)

# 輸入圖片大小 1*28*28
img_shape = (opt['channels'], opt['img_size'], opt['img_size'])
# 如果GPU可以使用, 則先在這里立個flag
cuda = True if torch.cuda.is_available() else False
print(cuda)
# GPU可以使用的話使用GPU的FloatTensor
Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor 

3.加載數(shù)據(jù)

這里使用torch自帶的手寫體數(shù)據(jù)集
# 輸出圖片(GAN生成假圖片文件夾)
os.makedirs("images", exist_ok=True)
# 訓(xùn)練數(shù)據(jù)文件夾
os.makedirs("mnist", exist_ok=True)
# 下載圖片數(shù)據(jù)
dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt['img_size']), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]
        ),
    ),
    batch_size=opt['batch_size'], # 每次取出數(shù)據(jù)量
    shuffle=True,
)

4.定義模型

# 生成器模型
class Generator(nn.Module):
  def __init__(self):
    super(Generator, self).__init__()
  
    # 簡化代碼,將Linear闸与,BatchNorm1d毙替, LeakyReLU裝在一起
    def block(in_feat, out_feat, normalize=True):
      layers = [nn.Linear(in_feat, out_feat)]
      if normalize:
        layers.append(nn.BatchNorm1d(out_feat, 0.8))
      layers.append(nn.LeakyReLU(0.2, inplace=True)) # inplace一個原地操作
      # 是對于Conv2d這樣的上層網(wǎng)絡(luò)傳遞下來的tensor直接進(jìn)行修改,好處就是可以節(jié)省運算內(nèi)存践樱,不用多儲存變量y
      return layers
  
    # 這里前面加*相當(dāng)于在Sequential中extend
    self.model = nn.Sequential(
        *block(opt['latent_dim'], 128, normalize=False),
        *block(128,256),
        *block(256,512),
        *block(512, 1024),
        nn.Linear(1024, int(np.prod(img_shape))), # np.prod攤開
        nn.Tanh()
    )
  def forward(self, data):
    img = self.model(data)
    img = img.view(img.size(0), *img_shape) # 因為輸出數(shù)據(jù)應(yīng)該為一張圖片厂画,所以需要將Reshae變?yōu)閳D片(圖片數(shù), channel,長拷邢, 寬)
    return img

# 定義判別模型
class Discriminator(nn.Module):
  def __init__(self):
    super(Discriminator, self).__init__()
    
    self.model = nn.Sequential(nn.Linear(int(np.prod(img_shape)), 512), # np.prod將圖片展開成一維向量
                               nn.LeakyReLU(0.2, inplace=True),
                               nn.Linear(512, 256),
                               nn.LeakyReLU(0.2, inplace=True),
                               nn.Linear(256, 1),
                               nn.Sigmoid(),
                              )
  def forward(self, img):
    img_flat = img.view(img.size(0), -1) # img.size(0)表示每個batch圖片數(shù)量
    # 拉成 圖片1 維度相乘 1*28*28
    #          圖片n 維度相乘
    validity = self.model(img_flat)
    return validity
需要說明的是block前面加*表示把block中的層安順序平鋪開,等價于下面的代碼:
 self.model = nn.Sequential(
        *block(opt['latent_dim'], 128),
        *block(128,256),)

# 等價于下面的代碼
self.model = nn.Sequential(
        # 這是一個block
        nn.Linear(in_feat, 128)
        layers.append(nn.BatchNorm1d(out_feat, 0.8))
        layers.append(nn.LeakyReLU(0.2, inplace=True))
        # 這是第二個block
        nn.Linear(128, 256)
        layers.append(nn.BatchNorm1d(256, 0.8))
        layers.append(nn.LeakyReLU(0.2, inplace=True))
)

4.損失函數(shù)和優(yōu)化器

# 判別器損失函數(shù)
adversarial_loss = torch.nn.BCELoss()

# 初始化生成器和判別器
generator = Generator()
discriminator = Discriminator()
# 如果CPU可以用
if cuda:
    generator.cuda()
    discriminator.cuda()
    adversarial_loss.cuda()

# 優(yōu)化器
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt['lr'], betas=(opt['b1'], opt['b2']))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt['lr'], betas=(opt['b1'], opt['b2']))
到此為止基本的模型定義就算完成了,我們現(xiàn)在有數(shù)據(jù)集dataloader(一個迭代器)跋理,generator生成器對象蔑匣, discriminator判別器對象鸦致,下面我們將訓(xùn)練模型

5.訓(xùn)練模型

# 訓(xùn)練模型
for epoch in range(opt['n_epochs']):
    for i, (imgs, _) in enumerate(dataloader): # 這里不需要類標(biāo)簽
        # Variable和Tensor在新版本中已經(jīng)合并
        valid = Tensor(imgs.size(0), 1).fill_(1.0) # 真實圖片類標(biāo)簽設(shè)為1.0
        valid.requires_grad=False # 不能更新梯度
        fake = Tensor(imgs.size(0), 1).fill_(0.0) # 假圖片類標(biāo)簽設(shè)為0.0
        fake.requires_grad=False
        
        # 將數(shù)據(jù)轉(zhuǎn)成cuda的tensor,加速
        # imgs.type() = 'torch.FloatTensor'
        # real_imgs.type() = 'torch.cuda.FloatTensor'
        real_imgs = imgs.type(Tensor)
        
        optimizer_G.zero_grad() # 生成模型的優(yōu)化器梯度清零
        
        # 訓(xùn)練生成器
        # 產(chǎn)生噪聲(輸入)數(shù)據(jù) 64張100維噪聲
        noise = Tensor(np.random.normal(0, 1, (imgs.shape[0], opt['latent_dim'])))
        # 生成假圖片
        fake_img = generator(noise)
        # 更新生層模型
        g_loss = adversarial_loss(discriminator(fake_img), valid)
        g_loss.backward() # 返回梯度
        optimizer_G.step() # 更新權(quán)重
        
        optimizer_D.zero_grad() # 判別模型的優(yōu)化器梯度清零
        # 訓(xùn)練判別器
        real_loss = adversarial_loss(discriminator(real_imgs), valid) # 真實圖片的損失
        # detach返回一個新的張量,它與當(dāng)前圖形分離抛人。fake_img結(jié)果永遠(yuǎn)不需要梯度
        fake_loss = adversarial_loss(discriminator(fake_img.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2 # 去一個均值作為損失
        d_loss.backward()
        optimizer_D.step()
        # 打印損失
        if i%500 == 0:
             print(
                   "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
                  % (epoch, opt['n_epochs'], i, len(dataloader), d_loss.item(),
                  g_loss.item())
          )
        # 選擇前25個圖片保存
        batches_done = epoch * len(dataloader) + i
        if batches_done % opt['sample_interval'] == 0:
      save_image(fake_img.data[:25], "images/%d.png" % batches_done, 
                          nrow=5, normalize=True)

下面是訓(xùn)練第0次(epoch * len(dataloader) + i),10000次和100000次的結(jié)果,可以看出若持續(xù)訓(xùn)練脐瑰,結(jié)果會更加逼近真實數(shù)據(jù)

0.png
10000.png
100000.png

下面給出完整的代碼

import os
import math
import numpy as np
import argparse

import torchvision.transforms as transforms
from torchvision.utils import save_image

from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable

import torch.nn as nn
import torch.nn.functional as F
import torch

opt = {}
opt['n_epochs'] = 200 # 迭代次數(shù)
opt['batch_size'] = 64
opt['lr'] = 0.0002 # adam: 學(xué)習(xí)率
opt['b1'] = 0.5 # adam: 梯度的一階動量衰減 momentum
opt['b2'] = 0.999 # adam: 梯度的一階動量衰減 momentum

opt['latent_dim'] = 100  # latent空間的維數(shù)
opt['img_size'] = 28
opt['channels'] = 1
opt['sample_interval'] = 400 # 圖像采樣間隔(做記錄)

# 輸入圖片大小
img_shape = (opt['channels'], opt['img_size'], opt['img_size'])
cuda = True if torch.cuda.is_available() else False
print(cuda)

os.makedirs("images", exist_ok=True)
os.makedirs("mnist", exist_ok=True)
# 下載圖片
dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt['img_size']), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]
        ),
    ),
    batch_size=opt['batch_size'],
    shuffle=True,
)
# 如果CPU可以用
if cuda:
    generator.cuda()
    discriminator.cuda()
    adversarial_loss.cuda()
Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor

# 生成器模型
class Generator(nn.Module):
  def __init__(self):
    super(Generator, self).__init__()
  
    # 整個作為一層
    def block(in_feat, out_feat, normalize=True):
      layers = [nn.Linear(in_feat, out_feat)]
      if normalize:
        layers.append(nn.BatchNorm1d(out_feat, 0.8))
      layers.append(nn.LeakyReLU(0.2, inplace=True)) # inplace一個原地操作
      # 是對于Conv2d這樣的上層網(wǎng)絡(luò)傳遞下來的tensor直接進(jìn)行修改妖枚,好處就是可以節(jié)省運算內(nèi)存,不用多儲存變量y
      return layers
  
    # 這里前面加*相當(dāng)于在Sequential中extend
    self.model = nn.Sequential(
        *block(opt['latent_dim'], 128, normalize=False),
        *block(128,256),
        *block(256,512),
        *block(512, 1024),
        nn.Linear(1024, int(np.prod(img_shape))), # np.prod攤開
        nn.Tanh()
    )
  def forward(self, data):
    img = self.model(data)
    img = img.view(img.size(0), *img_shape) # 將平鋪的數(shù)據(jù)變?yōu)閳D片(圖片數(shù), 長苍在, 寬绝页,channel)
    return img
# 定義判別函數(shù)
class Discriminator(nn.Module):
  def __init__(self):
    super(Discriminator, self).__init__()
    
    self.model = nn.Sequential(nn.Linear(int(np.prod(img_shape)), 512),
                               nn.LeakyReLU(0.2, inplace=True),
                               nn.Linear(512, 256),
                               nn.LeakyReLU(0.2, inplace=True),
                               nn.Linear(256, 1),
                               nn.Sigmoid(),
                              )
  def forward(self, img):
    img_flat = img.view(img.size(0), -1)
    # 拉成 圖片1 維度相乘
    #     圖片n 維度相乘
    validity = self.model(img_flat)
    return validity

# 判別器損失函數(shù)
adversarial_loss = torch.nn.BCELoss()

# 初始化生成器和判別器
generator = Generator()
discriminator = Discriminator()
# 優(yōu)化器
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt['lr'], betas=(opt['b1'], opt['b2']))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt['lr'], betas=(opt['b1'], opt['b2']))

# 訓(xùn)練模型
for epoch in range(opt['n_epochs']):
  for i, (imgs, _) in enumerate(dataloader):
    valid = Tensor(imgs.size(0), 1).fill_(1.0) # 真實圖片類標(biāo)簽
    valid.requires_grad=False
    
    fake = Tensor(imgs.size(0), 1).fill_(0.0) # 假圖片類標(biāo)簽
    fake.requires_grad=False
    
    # 將數(shù)據(jù)轉(zhuǎn)成cuda的tensor
    real_imgs = imgs.type(Tensor)
    
    # 訓(xùn)練生成器
    optimizer_G.zero_grad() # 生成器梯度清零
    # 產(chǎn)生噪聲數(shù)據(jù) 64張100維噪聲
    noise = Tensor(np.random.normal(0, 1, (imgs.shape[0], opt['latent_dim'])))
    # 生成噪聲圖片
    fake_img = generator(noise)
    
    # 更新生層器
    g_loss = adversarial_loss(discriminator(fake_img), valid)
    g_loss.backward()
    optimizer_G.step()
    
    # 訓(xùn)練判別器
    optimizer_D.zero_grad()
    real_loss = adversarial_loss(discriminator(real_imgs), valid)
    # detach返回一個新的張量,它與當(dāng)前圖形分離寂恬。結(jié)果永遠(yuǎn)不需要梯度
    fake_loss = adversarial_loss(discriminator(fake_img.detach()), fake)
    d_loss = (real_loss + fake_loss) / 2
    d_loss.backward()
    optimizer_D.step()
    
    if i%500 == 0:
      print(
            "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
            % (epoch, opt['n_epochs'], i, len(dataloader), d_loss.item(), g_loss.item())
          )
    
    batches_done = epoch * len(dataloader) + i
    if batches_done % opt['sample_interval'] == 0:
      save_image(fake_img.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末续誉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子初肉,更是在濱河造成了極大的恐慌酷鸦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牙咏,死亡現(xiàn)場離奇詭異臼隔,居然都是意外死亡,警方通過查閱死者的電腦和手機妄壶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門摔握,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丁寄,你說我怎么就攤上這事氨淌。” “怎么了伊磺?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵盛正,是天一觀的道長。 經(jīng)常有香客問我屑埋,道長豪筝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任雀彼,我火速辦了婚禮壤蚜,結(jié)果婚禮上即寡,老公的妹妹穿的比我還像新娘徊哑。我一直安慰自己,他們只是感情好聪富,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布莺丑。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梢莽。 梳的紋絲不亂的頭發(fā)上萧豆,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音昏名,去河邊找鬼涮雷。 笑死,一個胖子當(dāng)著我的面吹牛轻局,可吹牛的內(nèi)容都是我干的洪鸭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仑扑,長吁一口氣:“原來是場噩夢啊……” “哼览爵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镇饮,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蜓竹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后储藐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俱济,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年邑茄,在試婚紗的時候發(fā)現(xiàn)自己被綠了姨蝴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡肺缕,死狀恐怖左医,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情同木,我是刑警寧澤浮梢,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站彤路,受9級特大地震影響秕硝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洲尊,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一远豺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坞嘀,春花似錦躯护、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裁蚁。三九已至,卻和暖如春继准,著一層夾襖步出監(jiān)牢的瞬間枉证,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工移必, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留室谚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓崔泵,卻偏偏與公主長得像舞萄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子管削,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348