生成式对抗网络(GAN)
给定一个数据 x x x,预测它的标签 y y y。之前我们用神经网络来建模 x x x到 y y y的映射,这种模型叫判别模型。但有时候,有些数据集是没有标签的。比如给定一些照片,照片中有狗,但并没有标注狗的标签。我们无法建立判别模型,但可以学习这个数据集的分布,然后生成一些很像是来自数据集的狗的照片,它符合数据集分布,这个就是生成式学习。从数学角度解释,判别模型就是建模 p ( y ∣ x ) p(y|x) p(y∣x),而生成式学习是建模 p ( x ) p(x) p(x)也就是学习 x x x的分布。
GAN网络组成:一个生成器,一个分类器。生成器用于生成接近于数据集的图像,分类器用来判别图像是生成器生成的,还是来源于真实数据集的。我们的目的是使生成器生成的图片能够尽量真,而分类器能够分类的尽量准,这样就形成了一种对抗模式。
接下来我们将生成器生成的图片称为fake图片。对于判别器(分类器)而言,损失函数为最小化交叉熵损失函数,真实图像的标签为1,fake图像的标签为0。
D
(
x
)
D(x)
D(x)是经过sigmoid函数的预测概率。
min
D
{
−
y
log
D
(
x
)
−
(
1
−
y
)
log
(
1
−
D
(
x
)
)
}
.
\min_D \{ - y \log D(\mathbf x) - (1-y)\log(1-D(\mathbf x)) \}.
Dmin{−ylogD(x)−(1−y)log(1−D(x))}.
对与生成器,先由高斯分布生成一组数据
z
z
z,生成器经过训练后,这组数据会接近真实数据集的分布,因此得到
G
(
z
)
G(z)
G(z)。我们希望生成器生成的fake数据能够骗过分类器,也就是说,我们希望fake数据最后的预测概率
D
(
G
(
z
)
)
D(G(z))
D(G(z))接近与1,而它的真实标签其实为0,因此我们希望最大化损失函数
max
G
{
−
(
1
−
y
)
log
(
1
−
D
(
G
(
z
)
)
)
}
=
max
G
{
−
log
(
1
−
D
(
G
(
z
)
)
)
}
.
\max_G \{ - (1-y) \log(1-D(G(\mathbf z))) \} = \max_G \{ - \log(1-D(G(\mathbf z))) \}.
Gmax{−(1−y)log(1−D(G(z)))}=Gmax{−log(1−D(G(z)))}.
从上面这个式子可以看出,当
D
(
G
(
z
)
)
D(G(z))
D(G(z))接近于0的时候,损失接近于0,这会使得梯度值变化很小,不利于生成器更新。因此,如果我们将fake数据的标签给定为1的话(
y
=
1
y=1
y=1),那么就是希望最小化下面的损失,也就是使得fake数据的标签接近于1:
min
G
{
−
y
log
(
D
(
G
(
z
)
)
)
}
=
min
G
{
−
log
(
D
(
G
(
z
)
)
)
}
,
\min_G \{ - y \log(D(G(\mathbf z))) \} = \min_G \{ - \log(D(G(\mathbf z))) \},
Gmin{−ylog(D(G(z)))}=Gmin{−log(D(G(z)))},
生成器和分类器的损失函数可以合写为:
m
i
n
D
m
a
x
G
{
−
E
x
∼
Data
l
o
g
D
(
x
)
−
E
z
∼
Noise
l
o
g
(
1
−
D
(
G
(
z
)
)
)
}
.
min_D max_G \{ -E_{x \sim \text{Data}} log D(\mathbf x) - E_{z \sim \text{Noise}} log(1 - D(G(\mathbf z))) \}.
minDmaxG{−Ex∼DatalogD(x)−Ez∼Noiselog(1−D(G(z)))}.
def train(net_D,net_G,data_iter,num_epochs,lr_D,lr_G,latent_dim,data):
loss=nn.BCELoss()
Tensor=torch.FloatTensor
trainer_D=torch.optim.Adam(net_D.parameters(),lr=lr_D)
trainer_G=torch.optim.Adam(net_G.parameters(),lr=lr_G)
plt.figure(figsize=(7,4))
d_loss_point=[]
g_loss_point=[]
d_loss=0
g_loss=0
for epoch in range(1,num_epochs+1):
d_loss_sum=0
g_loss_sum=0
batch=0
for X in data_iter:
batch+=1
X=Variable(X)
batch_size=X.shape[0]
Z=Variable(Tensor(np.random.normal(0,1,(batch_size,latent_dim))))
trainer_D.zero_grad()
d_loss = update_D(X, Z, net_D, net_G, loss, trainer_D)
d_loss_sum+=d_loss
trainer_G.zero_grad()
g_loss = update_G(Z, net_D, net_G, loss, trainer_G)
g_loss_sum+=g_loss
d_loss_point.append(d_loss_sum/batch)
g_loss_point.append(g_loss_sum/batch)
从训练函数可以看出,判别器和生成器是交替更新的。
DCGAN
首先是预处理部分。ToTensor操作将像素值转换到 [ 0 , 1 ] [0,1] [0,1]之间,但是我们的生成器运用了tanh函数,输出在 [ − 1 , 1 ] [-1,1] [−1,1]之间,因此我们需要先将数据归一化(均值0.5,方差0.5),这样就可以匹配了。
其次是转置卷积层的使用。因为输入的图像size很小,利用转置卷积层可以enlarge输入的尺寸。
默认的转置卷积层网络,kernel_size
k
h
=
k
w
=
4
k_h = k_w = 4
kh=kw=4,步长
s
h
=
s
w
=
2
s_h = s_w = 2
sh=sw=2,填充
p
h
=
p
w
=
1
p_h = p_w = 1
ph=pw=1。如果输入的图像尺寸为
n
h
′
×
n
w
′
=
16
×
16
n_h^{'} \times n_w^{'} = 16 \times 16
nh′×nw′=16×16,那么输出的高和宽都会翻倍。
n
h
′
×
n
w
′
=
[
(
n
h
k
h
−
(
n
h
−
1
)
(
k
h
−
s
h
)
−
2
p
h
]
×
[
(
n
w
k
w
−
(
n
w
−
1
)
(
k
w
−
s
w
)
−
2
p
w
]
=
[
(
k
h
+
s
h
(
n
h
−
1
)
−
2
p
h
]
×
[
(
k
w
+
s
w
(
n
w
−
1
)
−
2
p
w
]
=
[
(
4
+
2
×
(
16
−
1
)
−
2
×
1
]
×
[
(
4
+
2
×
(
16
−
1
)
−
2
×
1
]
=
32
×
32.
\begin{aligned} n_h^{'} \times n_w^{'} &= [(n_h k_h - (n_h-1)(k_h-s_h)- 2p_h] \times [(n_w k_w - (n_w-1)(k_w-s_w)- 2p_w]\\ &= [(k_h + s_h (n_h-1)- 2p_h] \times [(k_w + s_w (n_w-1)- 2p_w]\\ &= [(4 + 2 \times (16-1)- 2 \times 1] \times [(4 + 2 \times (16-1)- 2 \times 1]\\ &= 32 \times 32 .\\ \end{aligned}
nh′×nw′=[(nhkh−(nh−1)(kh−sh)−2ph]×[(nwkw−(nw−1)(kw−sw)−2pw]=[(kh+sh(nh−1)−2ph]×[(kw+sw(nw−1)−2pw]=[(4+2×(16−1)−2×1]×[(4+2×(16−1)−2×1]=32×32.
我们可以通过设计kernel_size以及步长、填充来得到自己想要的输出尺寸。
最后介绍leaky ReLU。
leaky ReLU
(
x
)
=
{
x
if
x
>
0
α
x
otherwise
.
\textrm{leaky ReLU}(x) = \begin{cases}x & \text{if}\ x > 0\\ \alpha x &\text{otherwise}\end{cases}.
leaky ReLU(x)={xαxif x>0otherwise.
leaky能够解决“dying ReLU”的现象。如果一个网络的输出总是负值,那么ReLU函数就无法更新参数,leaky ReLU防止了这一问题。