-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
sunwill
authored and
sunwill
committed
Nov 22, 2017
0 parents
commit 9423844
Showing
71 changed files
with
751 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# 对抗生成网络 | ||
|
||
**对抗式生成网络(Generative Adversarial Network)** | ||
|
||
arXiv:[https://arxiv.org/abs/1406.2661](https://arxiv.org/abs/1406.2661) | ||
|
||
**条件生成对抗网络(Conditional Generative Adversarial Nets)** | ||
|
||
arXiv:https://arxiv.org/abs/1411.1784 | ||
|
||
+ [机器之心-GAN的完整理论推导与实现](https://github.com/jiqizhixin/ML-Tutorial-Experiment) | ||
|
||
+ [GAN完整理论推导与实现](https://www.jiqizhixin.com/articles/2017-10-1-1) | ||
|
||
一篇很好地理论推导证明,语言通俗易懂,公式清晰明了,非常值的一读。 | ||
|
||
+ [台大李宏毅GAN讲解视频](https://www.youtube.com/watch?v=0CKeqXl5IY0)(需要翻墙) | ||
|
||
## github | ||
|
||
+ [nightrome/really-awesome-gan](https://github.com/nightrome/really-awesome-gan) | ||
|
||
+ [wiseodd/generative-models](https://github.com/wiseodd/generative-models) | ||
|
||
+ [bstriner/keras-adversarial](https://github.com/bstriner/keras-adversarial/) | ||
|
||
+ [kvfrans/generative-adversial](https://github.com/kvfrans/generative-adversial) | ||
|
||
+ [osh/KerasGAN](https://github.com/osh/KerasGAN) | ||
|
||
+ [Eyyub/tensorflow-cdcgan](https://github.com/Eyyub/tensorflow-cdcgan) | ||
|
||
+ [soumith/ganhacks(训练gan的有效方法)](https://github.com/soumith/ganhacks) | ||
|
||
## 通俗语言解释如何训练 | ||
|
||
我们共同训练生成器和辨别器,让他们变得强壮,通过反复训练防止其中一个网络比另一个网络强大太多。 | ||
|
||
为什么轮回训练网络使双方共同变强而不是单独训练让他们的性能更强大? | ||
|
||
如果其中一个网络太强大,另外一个会因能力太差而导致两个网络性能都弱化。一个网络不知道自己在跟低级的网络竞争而导致其认为自己很高级。自作聪明的网络就会对低级的网络过拟合。 | ||
|
||
**训练辨别器** | ||
|
||
给它一张训练集中的图片和一张生成器生成的图片,如果得到的是生成图片辨别器应该输出 0,如果是真实的图片应该输出 1。 | ||
|
||
从技术性的角度:交叉熵的损失可以由最优控制器弥补,小菜一碟! | ||
|
||
**训练生成器** | ||
|
||
生成器必须努力让辨别器在得到它生成的图片后输出 1。 | ||
|
||
现在,这有一个有趣的部分。 | ||
|
||
假设生成器生成了一张图片,辨别器认为这张图片有 0.4 的概率是真实图片。生成器如何调整它生成的图片来增加这个概率,比如说增加到 0.41? | ||
|
||
答案就是: | ||
|
||
为训练生成器,辨别器不得不告诉生成器如何调整从而使它生成的图片变得更加真实。 | ||
|
||
生成器必须向辨别器寻求建议! | ||
|
||
直观来说,辨别器告诉生成器每个像素应调整多少来使整幅图像更真实一点点。 | ||
|
||
技术上来说,通过反向传播辨别器输出的梯度来调整生成图片。以这种方式训练生成器,你将会得到与图片形状一样的梯度向量。 | ||
|
||
如果你把这些梯度加到生成的图片上,在辨别器看来,图片就会变得更真实一点。 | ||
|
||
但是我们不仅仅把梯度加到图片上。 | ||
|
||
相反,我们进一步反向传播这些图片梯度成为组成生成器的权重,这样一来,生成器就学习到如何生成这幅新图片。 | ||
|
||
我重复一遍,为生成好的图片,你必须向老师展示你的工作,得到反馈! | ||
|
||
如果辨别器不帮助生成器的话,那就太残酷了,因为生成器实际做的工作比辨别器更艰难,它生成图片! | ||
|
||
这就是生成器如何被训练的。 | ||
|
||
就像这样,来回训练生成器和辨别器,直到达到一个平衡状态。 | ||
|
||
如果你很困惑,这是在初期盲目状态下,两个网络努力学习对话的直观感受: | ||
|
||
G:我有一张人脸图片,它跟你以前见过的相比,足够真实吗? | ||
|
||
D:比较真实但也比较像是你生成的图片。(对真实图片,辨别器产生 0.4 的概率) 我不太确定但我猜你给我的应该是一张生成的图片。 | ||
|
||
G:你猜对了!是我生成的一张图片。我应该怎样调整来让它更真实呢? | ||
|
||
D:让我想一下 (实际上在大脑里在做反向传播运算) 我认为你应该往图片里添加一对眼睛,人脸图片通常会包含眼睛。 | ||
|
||
(技术上来说:我认为你应该增加第 0 号像素的灰度值增加 1,第 1 号像素的灰度值减少 5 个,..., 第 4095 个像素的灰度值增加 8 个) | ||
|
||
G:收到 (反向传播那些梯度给所有的权重) | ||
|
||
**Dumbness** | ||
|
||
以上是一段比较初级的对话。双方都很白目。辨别器甚至不确定面部是否应该包含眼睛。它甚至说生成的没有眼睛的图片真实!(一个高级的辨别器对这张图片一定会说不,因为一张人脸图片肯定会包含眼睛!) | ||
|
||
经过一段时间的训练,它们会变得越来越聪明,直到他们达到非常高级的最优状态。 | ||
|
||
这里是两网络在最优高级状态学习时对话的直观感受: | ||
|
||
G:我有一张人脸图片,它跟你以前见过的人脸图片相比足够真实吗? | ||
|
||
D:这张图片真的很真实 (对真实图片,辨别器会产生 0.5 的概率) 但是这张图片是不是真的,我完全没有头绪。因为显而易见的是,你在生成真实图片上做的太好了。 | ||
|
||
G:这是我生成的一张图片。我知道这已经是真实的了但是我想要更多,我应该如何调整来使它变的更真实? | ||
|
||
D:让我想一下 (实际上大脑里在做反向传播) 我认为你的图片已经有了我认为需要有的部分。我看起来非常真实。显然你的图片包含眼睛,嘴巴,耳朵,头发,图片里是一张年轻男孩的脸。我不认为我有建议的东西。但是如果你想的话,可以把年轻男孩的胡须去掉。 | ||
|
||
(技术上来说,我认为你第 0 个像素灰度值增加 6,第 1 个像素灰度值减少 7,...,第 4095 个像素灰度值增加 2。) | ||
|
||
G:收到 (反向传播那些梯度给所有的权重) | ||
|
||
**Cleverness** | ||
|
||
它们变的高级之后,生成器会生成真实的图片,辨别器不再能辨别生成的图片。 | ||
|
||
它们在无人监督的情况下也都能理解胡须,眼睛,嘴巴,头发,年轻的脸庞。 | ||
|
||
你已经达到了一种平衡。 | ||
|
||
如果你持续不断的教导生成器如何使照片更加真实,就会很可能过拟合,就像辨别器会认为一个小男孩根本就不应该有胡子一样。辨别器会产生这样的想法,但是这可能不对。就像你不应太过依赖老师的意见一样。继续训练也不会得到任何东西。 | ||
|
||
**结论** | ||
|
||
两个网络并不是一直都在斗争,它们不得不协同合作以达到共同的目标。在整个训练过程中,辨别器不得不教导生成器如何在生成的数据上微做调整。同时它也一直都在学习如何做一个更好的老师。 | ||
|
||
它们共同变强,在理想状态下,会达到一种平衡。 | ||
|
||
|
||
## 训练收敛性问题 | ||
|
||
GAN的主要问题之一就是它的收敛性问题。即使优化了GAN的架构,也不能保证训练的稳定性。随着训练轮数的增多,并不能保证模型的效果越来越好,你不知道何时停止训练。也就是说损失函数和图像质量不相关。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
@author:sunwill | ||
A implemention of conditional genertive adversarial network using keras | ||
reference paper: | ||
arXiv:https://arxiv.org/abs/1411.1784 | ||
""" | ||
import math | ||
import numpy as np | ||
from PIL import Image | ||
from keras.datasets.mnist import load_data | ||
from keras.optimizers import Adam | ||
from keras.layers import concatenate | ||
from keras.models import Sequential,Model | ||
from keras.layers import Dense, Reshape, Conv2D, UpSampling2D, Input, Flatten, LeakyReLU, Dropout | ||
from keras.losses import binary_crossentropy | ||
from keras.utils import plot_model, to_categorical | ||
|
||
image_size = 28 | ||
image_channel = 1 | ||
|
||
latent_size = 100 | ||
y_dim = 10 | ||
batch_size = 64 | ||
epochs = 30 | ||
learning_rate = 2e-4 | ||
|
||
|
||
def generator(): | ||
cnn = Sequential() | ||
|
||
cnn.add(Dense(1024, input_dim=latent_size+y_dim, activation='tanh')) | ||
cnn.add(Dense(128 * 7 * 7, activation='tanh')) | ||
cnn.add(Reshape((7, 7, 128))) | ||
|
||
# upsample to (14, 14, ...) | ||
cnn.add(UpSampling2D(size=(2, 2))) | ||
cnn.add(Conv2D(256, 5, padding='same', | ||
activation='tanh', | ||
kernel_initializer='glorot_normal')) | ||
|
||
# upsample to (28, 28, ...) | ||
cnn.add(UpSampling2D(size=(2, 2))) | ||
cnn.add(Conv2D(128, 5, padding='same', | ||
activation='tanh', | ||
kernel_initializer='glorot_normal')) | ||
|
||
# take a channel axis reduction | ||
cnn.add(Conv2D(1, 2, padding='same', | ||
activation='tanh', | ||
kernel_initializer='glorot_normal')) | ||
|
||
input1 = Input(shape=(latent_size, )) | ||
input2 = Input(shape=(y_dim,)) | ||
inputs = concatenate([input1, input2], axis=1) | ||
outs = cnn(inputs) | ||
|
||
return Model(inputs=[input1, input2], outputs=outs) | ||
|
||
|
||
def discriminator(): | ||
|
||
cnn = Sequential() | ||
|
||
cnn.add(Conv2D(32, 3, padding='same', strides=2, | ||
input_shape=(28, 28, image_channel+y_dim))) | ||
cnn.add(LeakyReLU()) | ||
cnn.add(Dropout(0.3)) | ||
|
||
cnn.add(Conv2D(64, 3, padding='same', strides=1)) | ||
cnn.add(LeakyReLU()) | ||
cnn.add(Dropout(0.3)) | ||
|
||
cnn.add(Conv2D(128, 3, padding='same', strides=2)) | ||
cnn.add(LeakyReLU()) | ||
cnn.add(Dropout(0.3)) | ||
|
||
cnn.add(Conv2D(256, 3, padding='same', strides=1)) | ||
cnn.add(LeakyReLU()) | ||
cnn.add(Dropout(0.3)) | ||
|
||
cnn.add(Flatten()) | ||
cnn.add(Dense(1, activation='sigmoid')) | ||
|
||
inputs = Input(shape=(image_size, image_size, image_channel+y_dim)) | ||
outs = cnn(inputs) | ||
|
||
return Model(inputs=inputs, outputs=outs) | ||
|
||
|
||
def disc_on_gen(g, d): | ||
|
||
input1 = Input(shape=(image_size, image_size, y_dim)) | ||
input2 = Input(shape=(latent_size,)) | ||
input3 = Input(shape=(y_dim,)) | ||
|
||
g_out = g([input2, input3]) | ||
d_input = concatenate([g_out, input1], axis=3) | ||
d.trainable = False | ||
outs = d(d_input) | ||
model = Model(inputs=[input1, input2, input3], outputs=outs) | ||
return model | ||
|
||
|
||
def combine_images(images): | ||
num = images.shape[0] | ||
images = np.reshape(images, (-1, 28, 28)) | ||
width = int(math.sqrt(num)) | ||
height = int(math.ceil(float(num)/width)) | ||
shape = images.shape[1:3] | ||
image = np.zeros((height*shape[0], width*shape[1]), | ||
dtype=images.dtype) | ||
for index, img in enumerate(images): | ||
i = int(index/width) | ||
j = index % width | ||
image[i*shape[0]:(i+1)*shape[0], j*shape[1]:(j+1)*shape[1]] = img[:, :] | ||
return image | ||
|
||
|
||
def random_sample(x, y, batch_size): | ||
x_bs = [] | ||
y_bs = [] | ||
i = 0 | ||
while (i < batch_size): | ||
rand = np.random.randint(0, x.shape[0]) | ||
x_bs.append(x[rand]) | ||
y_bs.append(y[rand]) | ||
i += 1 | ||
return np.array(x_bs), np.array(y_bs) | ||
|
||
|
||
def train(): | ||
(X_train, y_train), (X_test, y_test) = load_data() | ||
y_train = to_categorical(y_train) | ||
print X_train.shape ## (60000,28,28) | ||
print y_train.shape ## (60000,10) | ||
|
||
num_samples = X_train.shape[0] | ||
|
||
X_train = np.expand_dims(X_train, axis=3) | ||
X_train = (X_train.astype(np.float32) - 127.5) / 127.5 | ||
|
||
g = generator() | ||
d = discriminator() | ||
d_on_g = disc_on_gen(g, d) | ||
|
||
g_optimiper = Adam(lr=learning_rate) | ||
d_optimizer = Adam(lr=learning_rate) | ||
|
||
g.compile(loss=binary_crossentropy, optimizer=g_optimiper) | ||
d_on_g.compile(loss='binary_crossentropy', optimizer=g_optimiper) | ||
|
||
d.trainable = True | ||
d.compile(loss='binary_crossentropy', optimizer=d_optimizer, metrics=['accuracy']) | ||
|
||
plot_model(g, to_file='./model/cgan_generator.png', show_shapes=True) | ||
plot_model(d, to_file='./model/cgan_discriminator.png', show_shapes=True) | ||
plot_model(d_on_g, to_file='./model/cgan.png', show_shapes=True) | ||
p = 0 | ||
for epoch in range(epochs): ## 多轮训练 | ||
print 'epoch {}/{}'.format(epoch + 1, epochs) | ||
|
||
for i in range(num_samples / batch_size): ## 在每一轮迭代里面训练 | ||
## 随机生成高斯噪声 | ||
noise = np.random.uniform(-1, 1, size=(batch_size, latent_size)) | ||
## 随机采样真实图片 | ||
x_bs, y_bs = random_sample(X_train, y_train, batch_size) | ||
|
||
generate_images = g.predict([noise, y_bs], verbose=0) | ||
# print generate_images.shape | ||
## 每经过500次训练输出生成图像 | ||
if i % 500 == 0: | ||
images = combine_images(generate_images) | ||
images = images * 127.5 + 127.5 | ||
Image.fromarray(images.astype(np.uint8)).save('./images/generated_{}_{}.png'.format(str(epoch + 1), i)) | ||
|
||
## 训练判别器 | ||
xs = np.concatenate([generate_images, x_bs]) | ||
ys = np.concatenate([y_bs, y_bs]) | ||
ys = np.reshape(ys, newshape=[-1, 1, 1, y_dim]) | ||
ys = np.tile(ys, [1, 28, 28, 1]) | ||
X = np.concatenate([xs, ys], axis=3) | ||
|
||
y = [0] * batch_size + [1] * batch_size | ||
|
||
d_loss, acc = d.train_on_batch(X, y) | ||
if i % 100 == 0: | ||
print 'epoch {}, iter {},d_loss = {}, acc = {}'.format(epoch + 1, i, d_loss, acc) | ||
## 训练生成器,此时需要固定判别器 | ||
|
||
d.trainable = False | ||
|
||
g_loss = d_on_g.train_on_batch([ys[:batch_size], noise, y_bs], [1] * batch_size) | ||
if i % 100 == 0: | ||
print 'epoch {}, iter {},g_loss = {} '.format(epoch + 1, i, g_loss) | ||
|
||
d.trainable = True | ||
if i%500 == 0: | ||
noise = np.random.uniform(-1, 1, size=(100, latent_size)) | ||
ys = np.zeros(shape=(100, y_dim)) | ||
for c in range(10): | ||
ys[c * 10:(c + 1) * 10, c] = 1 | ||
generate_images = g.predict([noise, ys]) | ||
images = combine_images(generate_images) | ||
images = images * 127.5 + 127.5 | ||
Image.fromarray(images.astype(np.uint8)).save('./logs/cgan_{}.png'.format(p)) | ||
p += 1 | ||
|
||
g.save_weights('./images/cgan_generator.h5'.format(epoch)) | ||
d.save_weights('./images/cgan_discriminator.h5'.format(epoch)) | ||
|
||
|
||
def generate(batch_size, flag=True): | ||
g = generator(latent_size) | ||
g.compile(optimizer=Adam(lr=learning_rate), loss='binary_crossentropy') | ||
g.load_weights('./logs/generator.h5') | ||
if flag: ##生成多张图片,选出最好的几张图片 | ||
d = discriminator(image_size, image_channel) | ||
d.compile(optimizer=Adam(lr=learning_rate), loss='binary_crossentropy') | ||
d.load_weights('./logs/discriminator.h5') | ||
noise = np.random.uniform(-1, 1, size=(batch_size * 10, latent_size)) | ||
generate_images = g.predict(noise) | ||
d_pred = d.predict(generate_images) | ||
index = np.reshape(np.arange(0, batch_size * 10), (-1, 1)) | ||
index_with_prob = list(np.append(index, d_pred, axis=1)) | ||
index_with_prob.sort(key=lambda x: x[0], reverse=True) | ||
nices = np.zeros(shape=((batch_size,) + generate_images.shape[1:])) | ||
for i in range(batch_size): | ||
idx = int(index_with_prob[i][0]) | ||
nices[i] = generate_images[idx] | ||
images = combine_images(nices) | ||
else: | ||
noise = np.random.uniform(-1, 1, size=(batch_size, latent_size)) | ||
generate_images = g.predict(noise) | ||
images = combine_images(generate_images) | ||
|
||
Image.fromarray(images).save('./generated_images.png') | ||
|
||
|
||
train() | ||
|
||
# generate(64) | ||
|
Oops, something went wrong.