我们前面所看到的模型都是在做判别,比如图片分类和语音识别,模型接受一个输入,输出数值或者类别。更进一步地说,如果我们希望机器能够达到比分析和推理更高的层次,就需要设计一个可以“创造”事物的模型。
举例来说,机器如果可以画出一只狗,就意味着它清楚狗的全部细节,如果只是让它去识别狗的照片,那么它可能并不知道很多细节,只是知道具有哪些特征的不是狗,“创造”比“识别”需要更多的智能。
所以,人工智能的一个目的就是要创造,我们将这样的模型叫做生成式模型。(从数学上来说,与统计学习中根据后验分布来区分生成式模型和判别式的模型是一致的。)
在这里,我将用尽可能简单的方式(尽可能少使用公式)讲解变分自编码器,一种重要的生成式模型。
在前面的《简单的自编码器》中曾经说过,自编码器包含了两部分,一部分叫做编码过程,它接收数据,得到一个低维表示(稀疏表示),用来做去噪和降维;另一部分叫做解码,接受一个向量,用来做生成数据。
如图,自编码器在训练完成后,就可以拆做独立的两部分,投入到不同的任务中去使用。
但是,我们在使用Decoder去生成图片时,并不清楚对输入有着怎样的限制,效果往往会糟糕,我们如果随便输入一个向量,就可以生成我们想要的图片,这在普通的Autoencoder中是不可能的。同时,我们想在生成式模型中使得输入向量与输出结果存在一定的关系,比如说,向量
生成了一片树叶,向量
生成了三片树叶,介于向量
之间的向量
可以生成两片树叶,这也是在以前的模型中无法做到的。
解决该问题的一个思路是,是将大的噪声添加到编码过程中,使得生成图片的向量Z具有一定的分布,一片树叶的原本对应着一个向量
,但在添加噪声,并经过训练之后,一片树叶和三片树叶的图片均对应着某个分布,处于这两个分布交叉部分的向量就有可能生成两片树叶的图片,因为从数值点到分布的转化就意味着要和两边都相似。同时,我们单纯利用解码器作为生成器的时候,需要给定服从该分布的随机向量,这样就有更大的可能性生成我们想要的图片。
最简单的办法是将隐藏的编码变为标准的高斯分布:
如图,在decoder阶段,新的编码变为了
。
正态分布由均值和标准差确定,这两部分不可能人为确定,
都可以从神经网络中学到,
是从另外一个高斯分布中采样得到。其中,均值
就是我们原本的编码,
就是我们额外需要神经网络学到的量。
如果只是添加这样的结构,不对其他部分做任何改变,那么在网络的自动学习过程中,就会将学习而来的
直接变为零,新的编码等于旧的编码。这样就和以前的编码器一样,并不具有上述优点。
我们希望保持这样的结构,但同时也希望对这样的结构作出限制,较为优雅的方式就是更改我们的Loss,在Loss中添加惩罚项,使得Z的编码分布更像标准的正态分布。要达到这一目的,即编码分布越像标准正态分布,Loss就越小,就可以使用KL散度(相对熵),计算当前分布与标准正态分布的差异。所以,我们要在度量生成图和原有图的autoencoder中添加一个约束项:
只需要利用微积分的知识,就可以将其化简为:
这个公式是从KL散度中直接推导而来,似乎并不好解释。但是我们可以拆成两项来直观的理解,关于
的项,是为了保证当
为1的时候,这一项对Loss的贡献为零,同时尽可能平滑;关于均值
的项,则可以被理解为均值
的正则化项,使得模型偏好于均值为零的参数。
到目前为止,我们就可以粗略地实践一个非常简单的VAE,首先我们面临
的采样问题,我们可以从利用keras的后端,生成服从标准正态分布的变量,并将其写作一个函数,模型每向前传播一次,就调用一次:
from keras import backend as K
def sampling(args):
z_mean, z_log_sigma = args
epsilon = K.random_normal(shape=(dim,),mean=0, std=1)
return (z_mean + (z_sigma) * epsilon)
接下来,我们需要定义好Loss,需要利用在隐层的变量,所以我们仍然将其写作一个函数,在模型内部去调用它:
from keras import binary_crossentropy
def vae_loss(x, y):
ent_loss = binary_crossentropy(x,y)
kl_loss =-0.5* K.mean(1+ z_sigma*z_sigma - K.square(z_mean) \
- K.log(z_sigma*z_sigma), axis=-1)
return ent_loss + kl_loss
接下来,我们可以很方便地写出我们的模型:
from keras import models,layers
from keras.layers import Input,Dense,Lambda
from keras.models import Model
def VAE(shape,inter_dim,dim):
x = Input(shape=shape)
hidden = Dense(inter_dim, activation='relu')(x)
z_mean = Dense(dim)(hidden)
z_sigma = Dense(dim)(hidden)
z = Lambda(sampling, output_shape=(dim,))([z_mean,z_sigma])
decoder_hidden = Dense(inter_dim,activation='relu')(z)
y = Dense(shape, activation='sigmoid')(decoder_hidden)
vae = Model(x, y)
vae.compile(optimizer='adam', loss=vae_loss)
return(vae)
这就成为了一个简单的自编码器,经过训练之后,我们可以将decoder的部分拆分出来,并利用一些随机变量去生成图片。
本文暂时没有评论,来添加一个吧(●'◡'●)