DCGAN——菜鳥系列 model.py
參考
[1] DGAN程式碼簡讀
[2]基於DCGAN的動漫頭像生成神經網路實現
https://blog.csdn.net/sinat_33741547/article/details/77871170
[3] batch norm原理及程式碼詳解
- 部落格: https://blog.csdn.net/qq_25737169/article/details/79048516
- 視訊:https://www.bilibili.com/video/av15405597?from=search&seid=8881273700429864348
- 部落格:https://www.cnblogs.com/eilearn/p/9780696.html
說明:2重點在程式碼,3重點在理論(但是一點都不枯燥),1有點像兩個的結合。推薦2的系列視訊以及資料,很精品,且免費。推薦看完視訊再看3,而且3中有很多聯立起來的理論,奉為神作不過分。
[3] tensorflow,tensorboard視覺化網路結構
[4] tensorflow變數作用域
https://www.cnblogs.com/MY0213/p/9208503.html
[5] 此接[4],同時解決了當初jupyter notebook的困惑
https://blog.csdn.net/Jerr__y/article/details/70809528#commentBox
[6] 啟用函式 ReLU、Leaky ReLU、PReLU 和 RReLU
[7] conv2d函式簡介
[8] DCGAN 原始碼分析
[9] DCGAN程式碼簡單讀,裡面有對應訓練效果,這裡看中了某個實現效果…so,做個記錄先
[10] 一個寫的還算詳細,但是排版和我彼此彼此的部落格——題目:DCGAN
[11] tensorflow儲存下載模型
說明:作者正在逐漸熟悉markdown…勿噴
[12] mmp的視訊,早看到可以少走N多個坑(注:博主自己理了一遍程式碼以後,找論文時找到的視訊,講的對我來說正正好)看完視訊,完善了部分程式碼註釋
[13] B站找的視訊
- 其實到最後,疑惑的也就部分tensorflow框架以及程式碼了,所以此處學習一波回來再去完善一下唉
- https://www.bilibili.com/video/av19360545/?p=1
程式碼解釋
引入模組
from __future__ import division
import os
import time
import math
from glob import glob
import tensorflow as tf
import numpy as np
from six.moves import xrange
from ops import *
from utils import *
- from _ future_ import division 這句話當python的版本為2.x時生效,可以讓兩個整數數字相除的結果返回一個浮點數(在python2中預設是整數,python3預設為浮點數)。
- glob可以以簡單的正則表示式篩選的方式返回某個資料夾下符合要求的檔名列表。[1]
conv_out_size_same 函式
def conv_out_size_same(size, stride):
return int(math.ceil(float(size) / float(stride)))
- math.ceil(): ceil() 函式返回數字的上入整數,即向上取整
- stride : 步幅
- 整個函式返回了一個整形值,
class DCGAN
init
def __init__(self, sess, input_height=108, input_width=108, crop=True,
batch_size=64, sample_num = 64, output_height=64,output_width=64,
y_dim=None, z_dim=100, gf_dim=64, df_dim=64,
gfc_dim=1024, dfc_dim=1024, c_dim=3, dataset_name='default',
input_fname_pattern='*.jpg', checkpoint_dir=None, sample_dir=None, data_dir='./data'):
- 具體引數用到再細談,此處注意特殊的那個: y_dim就好
- 以下給出對應註釋
"""
Args:
sess: TensorFlow session
batch_size: The size of batch. Should be specified before training.
y_dim: (optional) Dimension of dim for y. [None]
z_dim: (optional) Dimension of dim for Z. [100]
gf_dim: (optional) Dimension of gen filters in first conv layer.[64]
df_dim: (optional) Dimension of discrim filters in first conv layer[64]
gfc_dim: (optional) Dimension of gen units for for fully connected layer. [1024]
dfc_dim: (optional) Dimension of discrim units for fully connected layer. [1024]
c_dim: (optional) Dimension of image color. For grayscale input, set to 1. [3]
"""
self.sess = sess
self.crop = crop
self.batch_size = batch_size
# 測試要用到,由G生成,看效果
self.sample_num = sample_num
self.input_height = input_height
self.input_width = input_width
self.output_height = output_height
self.output_width = output_width
self.y_dim = y_dim
# Gnerator最開始輸入的噪音資料點的維度
self.z_dim = z_dim
# generator的中卷積網路的filter的維度,以下同理
self.gf_dim = gf_dim
self.df_dim = df_dim
# generator全連線的維度
self.gfc_dim = gfc_dim
self.dfc_dim = dfc_dim
# batch normalization : deals with poor initialization helps gradient flow
self.d_bn1 = batch_norm(name='d_bn1')
self.d_bn2 = batch_norm(name='d_bn2')
if not self.y_dim:
self.d_bn3 = batch_norm(name='d_bn3')
self.g_bn0 = batch_norm(name='g_bn0')
self.g_bn1 = batch_norm(name='g_bn1')
self.g_bn2 = batch_norm(name='g_bn2')
if not self.y_dim:
self.g_bn3 = batch_norm(name='g_bn3')
self.dataset_name = dataset_name
self.input_fname_pattern = input_fname_pattern
self.checkpoint_dir = checkpoint_dir
self.data_dir = data_dir
- 這段就是賦值而已
- batch_norm 的水很深,需要一定功夫研究,具體參考[3],
- 此處貼出作者的一份回答:如果在每一層之後都歸一化成0-1的高斯分佈(減均值除方差)那麼資料的分佈一直都是高斯分佈,資料分佈都是固定的了,這樣即使加更多層就沒有意義了,深度網路就是想學習資料的分佈發現規律性,BN就是不讓學習的資料分佈偏離太遠,詳細細節你可以去看論文。beta gama都是學習的,程式碼裡他們定義的是variable, trainable是True。——個人感覺很有道理
- 具體學習詳看[3]
if self.dataset_name == 'mnist':
self.data_X, self.data_y = self.load_mnist()
self.c_dim = self.data_X[0].shape[-1]
else:
data_path = os.path.join(self.data_dir, self.dataset_name, self.input_fname_pattern)
#返回的資料型別是list
self.data = glob(data_path) #用它可以查詢符合自己目的的檔案
if len(self.data) == 0:
raise Exception("[!] No data found in '" + data_path + "'")
np.random.shuffle(self.data) #打亂資料
#讀取一張圖片
imreadImg = imread(self.data[0])
#check if image is a non-grayscale image by checking channel number
if len(imreadImg.shape) >= 3:
self.c_dim = imread(self.data[0]).shape[-1]
else:
self.c_dim = 1
if len(self.data) < self.batch_size:
raise Exception("[!] Entire dataset size is less than the configured batch_size")
- DCGAN的構造方法除了設定一大堆的屬性之外,還要注意區分dataset是否是mnist,因為mnist是灰度影象,所以應該設定channel = 1( self.c_dim = 1 ),如果是彩色影象,則 self.c_dim = 3 or self.c_dim = 4[1]
- load_mnist為自定義方法,方法實現如下
- 其餘程式碼均有註釋,整個程式碼塊只是在讀入資料集,並進行對應的可能報錯處理。
def load_mnist(self):
data_dir = os.path.join(self.data_dir, self.dataset_name) #用於路徑拼接檔案路徑
'''
train-images-idx3-ubyte:
參考網址:https://www.jianshu.com/p/84f72791806f
簡介:這是IDX檔案格式,是一種用來儲存向量與多維度矩陣的檔案格式
'''
fd = open(os.path.join(data_dir,'train-images-idx3-ubyte')) #建立檔案物件
loaded = np.fromfile(file=fd,dtype=np.uint8) #numpy 讀取檔案
trX = loaded[16:].reshape((60000,28,28,1)).astype(np.float) #對應的資料處理
fd = open(os.path.join(data_dir,'train-labels-idx1-ubyte'))
loaded = np.fromfile(file=fd,dtype=np.uint8)
trY = loaded[8:].reshape((60000)).astype(np.float)
fd = open(os.path.join(data_dir,'t10k-images-idx3-ubyte'))
loaded = np.fromfile(file=fd,dtype=np.uint8)
teX = loaded[16:].reshape((10000,28,28,1)).astype(np.float)
fd = open(os.path.join(data_dir,'t10k-labels-idx1-ubyte'))
loaded = np.fromfile(file=fd,dtype=np.uint8)
teY = loaded[8:].reshape((10000)).astype(np.float)
# 將結構資料轉化為ndarray, 例子詳見: https://www.jb51.net/article/138281.htm
# 可以理解為:兩個變數用法同一份記憶體,只是資料格式不同
trY = np.asarray(trY)
teY = np.asarray(teY)
# 陣列拼接
#concatenate([a, b])
#連線,連線後ndim不變,a和b可以有一維size不同,但size不同的維度必須是要連線的維度
X = np.concatenate((trX, teX), axis=0)
y = np.concatenate((trY, teY), axis=0).astype(np.int)
# seed
# http://www.cnblogs.com/subic/p/8454025.html
# 簡單介紹:
seed = 547
np.random.seed(seed)
np.random.shuffle(X) #np.random.shuffle
np.random.seed(seed)
np.random.shuffle(y)
y_vec = np.zeros((len(y), self.y_dim), dtype=np.float)
for i, label in enumerate(y):
y_vec[i,y[i]] = 1.0
return X/255.,y_vec
- 具體參考註釋內容
#是否為灰度
self.grayscale = (self.c_dim == 1)
#自定義的方法
self.build_model()
- 無特殊說明
build_model(self)
def build_model(self):
#可以理解為申明瞭一個(batch_size,y_dim)大小的地方,用於mini_batc時往裡面喂對應的資料。
if self.y_dim:
self.y = tf.placeholder(tf.float32, [self.batch_size, self.y_dim], name='y')
else:
self.y = None
#is_crop為要不要裁剪影象 [True or False]
#所以此處是判斷影象是否裁剪過
# https://www.jianshu.com/p/3e46ce8e7ddd
if self.crop:
# 把輸出維度定義好
image_dims = [self.output_height, self.output_width, self.c_dim]
else:
image_dims = [self.input_height, self.input_width, self.c_dim]
#道理同上面解釋,真實圖片的輸入[batchsize,height,width,c_dim]
self.inputs = tf.placeholder(
tf.float32, [self.batch_size] + image_dims, name='real_images')
inputs = self.inputs
# 噪音資料(None表示多少都可以,實際用到再填充batch)
self.z = tf.placeholder(
tf.float32, [None, self.z_dim], name='z')
# 位於 opt.py裡
# histogram_summary = tf.histogram_summary
# 將z在tensorboard裡視覺化
self.z_sum = histogram_summary("z", self.z)
# 以下為網路結構
self.G = self.generator(self.z, self.y)
#真實資料
self.D, self.D_logits = self.discriminator(inputs, self.y, reuse=False)
# 測試網路
self.sampler = self.sampler(self.z, self.y)
# G產生的資料
self.D_, self.D_logits_ = self.discriminator(self.G, self.y, reuse=True)
# 同上視覺化
self.d_sum = histogram_summary("d", self.D)
self.d__sum = histogram_summary("d_", self.D_)
# opt.py
# image_summary = tf.image_summary
# tensorboar內影象視覺化 : https://blog.csdn.net/dxmkkk/article/details/54925728
self.G_sum = image_summary("G", self.G)
顯而易見,網路結構才是重點,其他都是陪襯。大致介紹參考[1]:
self.generator 用於構造生成器; self.discriminator 用於構造鑑別器; self.sampler 用於隨機取樣(用於生成樣本)。這裡需要注意的是, self.y 只有當dataset是mnist的時候才不為None,不是mnist的情況下,只需要 self.z 即可生成samples
sigmoid_cross_entropy_with_logits(x, y) (內置於build_model)
def sigmoid_cross_entropy_with_logits(x, y):
try:
return tf.nn.sigmoid_cross_entropy_with_logits(logits=x, labels=y)
except:
return tf.nn.sigmoid_cross_entropy_with_logits(logits=x, targets=y)
- [1] sigmoid_cross_entropy_with_logits 函式被重新定義了,是為了相容不同版本的tensorflow。該函式首先使用sigmoid activation,然後計算cross-entropy loss。
- 這個函式有自己的定義,可以參考: https://blog.csdn.net/QW_sunny/article/details/72885403
- 作者為對此函式進行過深瞭解,僅限於清楚大致處理過程。
承接上面程式碼
# 真實圖片的鑑別器損失
# D_logits,鑑別網路對真實資料的評分, tf.ones_like(self.D),與D大小一樣的1.
# tf.nn.sigmoid(h3), h3
# 可以理解為兩引數分別為x,y。 x是網路產生,y是實際希望,然後通過這個函式計算出一個損失值。不同於一般的平方求和,此函式解決了sigmoid梯度緩慢的問題,並且另外考慮了溢位的問題。
self.d_loss_real = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits, tf.ones_like(self.D)))
# 虛假圖片損失
self.d_loss_fake = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits_, tf.zeros_like(self.D_)))
# 生成器損失
self.g_loss = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits_, tf.ones_like(self.D_)))
# 視覺化
self.d_loss_real_sum = scalar_summary("d_loss_real", self.d_loss_real)
self.d_loss_fake_sum = scalar_summary("d_loss_fake", self.d_loss_fake)
# 總的鑑別器損失,越小越好
self.d_loss = self.d_loss_real + self.d_loss_fake
- sigmoid_cross_entropy_with_logits的output不是一個數,而是一個batch中每個樣本的loss,所以一般配合tf.reduce_mean(loss)使用
- self.g_loss 是生成器損失; self.d_loss_real 是真實圖片的鑑別器損失; self.d_loss_fake 是虛假圖片(由生成器生成的fake images)的損失; self.d_loss 是總的鑑別器損失。
# 視覺化
self.g_loss_sum = scalar_summary("g_loss", self.g_loss)
self.d_loss_sum = scalar_summary("d_loss", self.d_loss)
# 所有的變數
t_vars = tf.trainable_variables()
# 尋找所有以d_開頭的變數,優化時的引數(例如:如果是梯度下降,我們需要將權重和loss傳進去,這裡d_vars代表generator裡要優化的變數)
self.d_vars = [var for var in t_vars if 'd_' in var.name]
self.g_vars = [var for var in t_vars if 'g_' in var.name]
# 儲存節點
self.saver = tf.train.Saver()
- [1] tf.trainable_variables() 可以獲取model的全部可訓練引數,由於我們在定義生成器和鑑別器變數的時候使用了不同的name,因此我們可以通過variable的name來獲取得到 self.d_vars (鑑別器相關變數), self.g_vars (生成器相關變數)。
- self.saver = tf.train.Saver() 用於儲存訓練好的模型引數到checkpoint。
discriminator
def discriminator(self, image, y=None, reuse=False):
# 變數共享,我理解為: 即使函式被重複呼叫,其共享的節點不會重複定義、宣告 [5]
with tf.variable_scope("discriminator") as scope:
if reuse:
scope.reuse_variables()
# 如果沒有這一維
if not self.y_dim:
# conv2d簡介具體看 [7],但第二個引數的介紹是不對的,補上[8].
'''
引數簡介:
第一個引數input:具有[batch, in_height, in_width, in_channels]這樣的shape
第二個引數filter:conv2d中輸出的特徵圖個數,是個1維的引數,即output_dim, output_dim是 conv2d函式的第二個入參,由外部傳入。比如,下面的這句話,表示h1 是輸入,通過卷積之後,輸出的特徵圖個數為gf_dim* *4,這裡gf_dim = 128,則輸出 特徵圖為128*4=512個。即這裡一共有512個卷積核。
第三個引數strides:卷積時在影象每一維的步長,這是一個一維的向量,長度4。[1, d_h, d_w, 1],其中第一個對應一次跳過batch中的多少圖片,第二個d_h對應一次跳過圖片中 多少行,第三個d_w對應一次跳過圖片中多少列,第四個對應一次跳過影象的多少個通道。這 裡直接設定為[1,2,2,1]。即每次卷積後,影象的滑動步長為2,特徵圖會縮小為原來的 1/4。
第四個引數padding:string型別的量,只能是"SAME","VALID"其中之一,這個值決定了不 同的卷積方式
第五個引數:use_cudnn_on_gpu:bool型別,是否使用cudnn加速,預設為true
第六個引數:name=None
'''
# df_dim 作為類的引數傳進來
# df_dim: (optional) Dimension of discrim filters in first conv layer[64]
# lrelu 在opt.py 中
# 輸入是一個 image
h0 = lrelu(conv2d(image, self.df_dim, name='d_h0_conv'))
h1 = lrelu(self.d_bn1(conv2d(h0, self.df_dim*2, name='d_h1_conv')))
h2 = lrelu(self.d_bn2(conv2d(h1, self.df_dim*4, name='d_h2_conv')))
h3 = lrelu(self.d_bn3(conv2d(h2, self.df_dim*8, name='d_h3_conv')))
#全連線層,-1表示不確定大小。 linear 在opt.py中,自己構建的。
h4 = linear(tf.reshape(h3, [self.batch_size, -1]), 1, 'd_h4_lin')
return tf.nn.sigmoid(h4), h4
else:
# y_dim = 10
yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim])
# image被當作引數傳進來的引數,Builder裡是inputs.
# 原型在opt裡,基於concat函式基礎上修改。這裡並沒有理解為什麼要這樣操作....
# 這裡程式碼以及構建不難,但是這裡所有的concat尚且不瞭解原因。
x = conv_cond_concat(image, yb)
h0 = lrelu(conv2d(x, self.c_dim + self.y_dim, name='d_h0_conv'))
h0 = conv_cond_concat(h0, yb)
h1 = lrelu(self.d_bn1(conv2d(h0, self.df_dim + self.y_dim, name='d_h1_conv')))
h1 = tf.reshape(h1, [self.batch_size, -1])
h1 = concat([h1, y], 1)
h2 = lrelu(self.d_bn2(linear(h1, self.dfc_dim, 'd_h2_lin')))
h2 = concat([h2, y], 1)
h3 = linear(h2, 1, 'd_h3_lin')
return tf.nn.sigmoid(h3), h3
- [1] 下面是discriminator(鑑別器)的具體實現。
- 首先鑑別器使用 conv (卷積)操作,啟用函式使用 leaky-relu ,每一個layer需要使用batch normalization。tensorflow的batch normalization使用 tf.contrib.layers.batch_norm 實現。
- 如果不是mnist,則第一層使用 leaky-relu+conv2d ,後面三層都使用 conv2d+BN+leaky-relu ,最後加上一個one hidden unit的linear layer,再送入sigmoid函式即可;
- 如果是mnist,則 yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim]) 首先給y增加兩維,以便可以和image連線起來,這裡實際上是使用了conditional GAN(條件GAN)的思想。
- x = conv_cond_concat(image, yb) 得到condition和image合併之後的結果,然後 h0 = lrelu(conv2d(x, self.c_dim + self.y_dim, name=‘d_h0_conv’)) 進行卷積操作。第二次進行 conv2d+leaky-relu+concat 操作。第三次進行 conv2d+BN+leaky-relu+reshape+concat