〖TensorFlow2.0笔记18〗自编码器Auto-Encoders以及实战!

自编码器Auto-Encoders以及实战!

一. 自编码器Auto-Encoders

1.1. 回顾之前的知识

1.2. 无监督学习

  • 机器学习的三大方向:
  • 为什么需要无监督学习?
  • 通过上面了解了为什么需要无监督学习,下面看一下无监督学习的一种形态!注意:其实无监督并不是没有目标,它也是有目标的,比如看下面的auto-encode,它的目标是什么?它的目标就是它自己,这么一想其实unsupervised learning也是非常有道理的。
  • 比如下面MNIST数据集,输入这样的图片,经过这样一个神经网络NN(encoder),得到一个输出,再经过一个网络(decoder);也可以如下图中的理解。这就是auto-encoder,特别简单。
  • 可视化可以参考这个网站,比较直观的感受784-3784维降低到3维):降维度可视化网站链接

1.3. Auto-Encoders中loss function如何设计

  • 介绍如下

1.4. PCA和Auto-encoder对比

  • 效果对比:
  • 再看一个可视化的效果

二. Auto-Encoders的变种

2.1. 变种1: 原图像中引入噪声

  • 下面讲解一下auto-encoder的变种吧!首先比较简答的是:容易想到如果只是在像素级别里重建,这样没有发现一些更深层次的东西,那你很有可能只是记住一些特征,比如这个数字,你可能直接把这个像素给记住了;那么为了防止记住怎么办?我们给原图像加一些噪声

2.2. 变种2: 借助Drop out

  • Drop outHintton大神发明的,Drop out也是现在一种比较常用的技术,但是他并没有发表在paper上面,据说是Hintton在给他的学生上课的时候随口提出来的小trick(技巧),帮助你提升神经网络性能的技巧。
  • 看下图:首先在Training的时候回有目的的,让这部分的连接断开,具体的操作我们手动的让这部分的权值暂时设置为0,就是相当于暂时断掉了。这样可以理解为:给你的信息有限,这样就迫使你的系统更加的robust,你就不会依赖于所有的神经元,而是部分的神经元。然后你再Test的时候:就会让你的这个任务变得更加的简单了,所有的Drop out设置都恢复了。

2.3. 预备知识相对熵

  • 下面的资料来自哈工大刘远超老师的PPT资料,表示感谢!同时参考知乎大神https://zhuanlan.zhihu.com/p/26486223

  • 信息量: 是对信息的度量,就跟时间的度量是秒一样,当我们考虑一个离散的随机变量x的时候,当我们观察到的这个变量的一个具体值的时候,我们接收到了多少信息呢?多少信息用信息量来衡量,我们接受到的信息量跟具体发生的事件有关。

  • 信息的大小跟随机事件的概率有关。越小概率的事情发生了产生的信息量越大,如湖南产生的地震了;越大概率的事情发生了产生的信息量越小,如太阳从东边升起来了(肯定发生嘛,没什么信息量)。这很好理解!

  • 例子: 脑补一下我们日常的对话:师兄走过来跟我说,立波啊,今天你们湖南发生大地震了。我:啊,不可能吧,这么重量级的新闻!湖南多低的概率发生地震啊!师兄,你告诉我的这件事,信息量巨大,我马上打电话问问父母什么情况。又来了一个师妹:立波师兄,我发现了一个重要情报额,原来德川师兄有女朋友额 德川比师妹早进一年实验室,全实验室同学都知道了这件事。我大笑一声:哈哈哈哈,这件事大家都知道了,一点含金量都没有,下次八卦一些其它有价值的新闻吧!orz,逃~ 因此一个具体事件的信息量应该是随着其发生概率而递减的,且不能为负。

  • 但是这个表示信息量函数的形式怎么找呢? 随着概率增大而减少的函数形式太多了!不要着急,我们还有下面这条性质
    如果我们有俩个不相关的事件 xxyy,那么我们观察到的俩个事件同时发生时获得的信息应该等于观察到的事件各自发生时获得的信息之和,即:
    h(x,y)=h(x)+h(y)h(x, y)=h(x)+h(y)

  • 由于 xyx,y 是俩个不相关的事件,那么满足 p(x,y)=p(x)p(y)p(x, y)=p(x)*p(y).

  • 根据上面推导,我们很容易看出 h(x)h(x) 一定与 p(x)p(x) 的对数有关(因为只有对数形式的真数相乘之后,能够对应对数的相加形式,可以试试)。因此我们有信息量公式如下:h(x)=log2p(x)h(x)=-\log _{2} p(x)

  • 下面解决俩个疑问? 1. 为什么有一个负号:其中负号是为了确保信息一定是正数或者是0,总不能为负数吧!2. 这是因为,我们只需要信息量满足低概率事件 xx 对应于高的信息量。那么对数的选择是任意的。我们只是遵循信息论的普遍传统,使用2作为对数的底。

  • 信息论中的熵的概念;

2.3.1. 信息熵

2.3.2. 交叉熵

2.3.3. KL散度又称相对熵

  • 取值范围:[0,+][0, +\infty ],当两个分布接近相同的时候,取值为0;当两个分布差异越来越大的时候KL散度值就会越来越大。
  • 其中:DKL(PQ)=xXP(x)log1Q(x)xXP(x)log1P(x)=xXP(x)logQ(x)+xXP(x)logP(x)=xXP(x)logP(x)Q(x)\begin{aligned} \mathrm D_{K L}(\mathrm P \| \mathrm Q) &=\sum_{x \in \mathrm X} \mathrm P(x) \log \frac{1}{\mathrm Q(x)}-\sum_{x \in \mathrm X}\mathrm P(x) \log \frac{1}{\mathrm P(x)}\\ &={- \sum_{x \in \mathrm X} \mathrm P(x) \log {\mathrm Q(x)}+\sum_{x \in \mathrm X}\mathrm P(x) \log {\mathrm P(x)}} \\ &=\sum_{x \in \mathrm X}\mathrm P(x) \log \frac{ \mathrm P(x)}{\mathrm Q(x)} \\ \end{aligned}

2.3.4. 相对熵具体例子

2.3.5. 相对熵的性质

2.3.6. JS散度

2.4. 变种3:Adversarial Auto-Encoders(比较有名)

  • 这个是比较有名的,这个跟后面要介绍的GAN非常相关了,这个直接把GAN中的技术应用到这里Auto-Encoders了,
  • 但是实际上最原始的auto-encoders大家发现;比如你从 [1,1][-1,1]变换 h 分布的时候,重建出来的图片它可能是一样的,就是没有呈现出这种分布过来,这样的话就比价烦人了,那么怎么解决这个问题呢?Adversarial Auto-Encoders 是怎样解决这个问题的呢?如下图添加了一个 Discriminator,还没有讲到 GAN,现在可以理解为一个识别器(鉴别器)
  • 我们假设一个分布,高斯分布,均值为 00,方差为 11,我们希望你属于一个这样的分布,如果你没有输入这个分布,我们鉴别你,我们把真实的 zz 和希望得到的 zz' 都送给这个网络做鉴别这个差距,如果差距大,就是不属于预设的这个分布,我们输出一个fake,我们这个分布属于预设的分布,就是属于这个高斯分布,我们输出这个 11。通过这样的话就能迫使就能是我们中间得到的这个hidden vector 除了能完成重建工作之外,还能符合我们想要的分布,这样就很好解决上一张图片中所说的不平衡的问题(分布不均匀,乱七八糟)。
  • Adversarial Auto-Encoder 额外添加了一个 Discriminator (可以理解为一个识别器)
  • 由这个Adversarial Auto-Encoders引出这节课的重点所在。
  • sample() 是不可微的,不能进行反向传播;
  • 之前介绍了Adversarial Auto-Encoders,我们现在从神经网络的角度理解一下。

2.5. 对比Auto-Encoder和Adversarial Auto-Encoder的区别

  • VAE 重建的效果好一些。
  • VAE除了重建的效果比AE好一些,更重要的是作为一个stochasticstochastic模型,它学习的是q(h)q(h),通过从q(h)q(h)samplesample出不同的hh,这样就可以重建或者说用来做生成的模型,如果h是二维的[h0,h1][h0, h1]h0h_{0}属于h0N(μ0,σ02)h_{0} \sim N\left(\mu_{0}, \sigma_{0}^{2}\right)的分布, h1h_{1}属于h1N(μ0,σ12)h_{1} \sim N\left(\mu_{0}, \sigma_{1}^{2}\right)的分布,知道了这2个分布,我们就改变 μ0,σ0,μ1,σ1\mu_{0},\sigma_{0}, \mu_{1},\sigma_{1},我们再samplesample一下,samplesample出来新的hh, hh再送入后面的decoderdecoder,就能得到 x\overline{x},通过多个samplesample之后得到不同的hh,也可以得到不同的x\overline{x},这样的话就可以实现生成。
  • 看下图横座标h0h_{0}改变,纵座标h1h_{1}改变,改变以后相当于上面说的分布改变了,那么sample生成的图片就有随机性在里面。

三. 基本的Auto-Encoders实战

3.1. 训练部分代码实现

  • python 代码如下:
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from PIL import Image
from matplotlib import pyplot as plt
from tensorflow.keras import Sequential, layers


tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多张image保存达到一张image里面去。
def save_images(img, name):
    new_im = Image.new('L', (280, 280))

    index = 0
    for i in range(0, 280, 28):
        for j in range(0, 280, 28):
            im = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定义超参数
h_dim = 20         # 把原来的784维护降低到20维度;
batchsz = 512      # fashion_mnist
lr = 1e-4

# 数据集加载
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.

# we do not need label auto-encoder大家可以理解为无监督学习,标签其实就是本身,和自己对比;
train_db = tf.data.Dataset.from_tensor_slices(x_train)
train_db = train_db.shuffle(batchsz * 5).batch(batchsz)

test_db = tf.data.Dataset.from_tensor_slices(x_test)
test_db = test_db.batch(batchsz)


print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)

# 搭建模型
class AE(keras.Model):
    # 1. 初始化部分
    def __init__(self):
        super(AE, self).__init__()   # 调用父类的函数

        # Encoders编码, 网络
        self.encoder = Sequential([
            layers.Dense(256, activation=tf.nn.relu),
            layers.Dense(128, activation=tf.nn.relu),
            layers.Dense(h_dim)

        ])

        # Decoders解码,网络
        self.decoder = Sequential([
            layers.Dense(128, activation=tf.nn.relu),
            layers.Dense(256, activation=tf.nn.relu),
            layers.Dense(784)

        ])

    # 2. 前向传播的过程
    def call(self, inputs, training=None):
        # [b, 784] ==> [b, 10]
        h = self.encoder(inputs)
        # [b, 10] ==> [b, 784]
        x_hat = self.decoder(h)

        return x_hat

# 创建模型
model = AE()
model.build(input_shape=(None, 784))      # 注意这里面用小括号还是中括号是有讲究的。建议使用tuple
model.summary()

optimizer = keras.optimizers.Adam(lr=lr)
# optimizer = tf.optimizers.Adam() 都可以;

for epoch in range(1000):

    for step, x in enumerate(train_db):

        # [b, 28, 28] => [b, 784]
        x = tf.reshape(x, [-1, 784]).numpy()

        with tf.GradientTape() as tape:
            x_rec_logits = model(x)

            # 把每个像素点当成一个二分类的问题;
            rec_loss = tf.losses.binary_crossentropy(x, x_rec_logits, from_logits=True)
            # rec_loss = tf.losses.MSE(x, x_rec_logits)
            rec_loss = tf.reduce_mean(rec_loss)

        grads = tape.gradient(rec_loss, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))


        if step % 100 ==0:
            print('epoch: %3d, step:%4d, loss:%9f' %(epoch, step, float(rec_loss)))

  • 测试结果如下:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/autoencoder.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
Model: "ae"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sequential (Sequential)      multiple                  236436    
_________________________________________________________________
sequential_1 (Sequential)    multiple                  237200    
=================================================================
Total params: 473,636
Trainable params: 473,636
Non-trainable params: 0
_________________________________________________________________
epoch:   0, step:   0, loss: 0.692311
epoch:   0, step: 100, loss: 0.276746
epoch:   1, step:   0, loss: 0.274721
epoch:   1, step: 100, loss: 0.250346
epoch:   2, step:   0, loss: 0.249346
epoch:   2, step: 100, loss: 0.219126
epoch:   3, step:   0, loss: 0.212125
epoch:   3, step: 100, loss: 0.192399
epoch:   4, step:   0, loss: 0.191749
epoch:   4, step: 100, loss: 0.177625
epoch:   5, step:   0, loss: 0.176804
epoch:   5, step: 100, loss: 0.169778
epoch:   6, step:   0, loss: 0.167599
epoch:   6, step: 100, loss: 0.159327

3.2. 测试部分代码实现

  • 那么如何进行测试呢?对于 auto-encoder 或者说 unsupervise learning;测试的时候我们只需要 test 或者叫做 evaluation
        if step % 100 ==0:
            print('epoch: %3d, step:%4d, loss:%9f' %(epoch, step, float(rec_loss)))
		##################添加的代码##################
        # evaluation
        x = next(iter(test_db))                   # 我们从test_db中取出一张图片;
        x_shape = tf.reshape(x, [-1, 784]).numpy()
        logits = model(x_shape)                         # 经过auto-encoder重建的效果。
        x_hat= tf.sigmoid(logits)                 # 变化到0-1之间

        # [b, 784] => [b, 28, 28]       还原成原始尺寸;
        x_hat = tf.reshape(x_hat, [-1, 28, 28])   # 重建得到的图片;

        # [b, 28, 28] => [2b, 28, 28]   和原图像拼接起来;
        x_concat = tf.concat([x, x_hat], axis=0)  # 最外的维度拼接;
        x_concat = x_hat
        x_concat = x_concat.numpy() * 255.        # 把数据numpy取出来,变成0-255区间;
        x_concat = x_concat.astype(np.uint8)      # 还原成numpy保存图片的格式;
        save_images(x_concat, 'ae_image/rec_epoch_%d.png' %epoch) # 每个epoch保存一次。

3.3. 重建结果对比

  • 重建得到的图片和原始图片进行对比:
原始图片 重建图片

注意: 我们发现重建的图片相对原始图片存在模糊的现象,棱角美誉原图像那么锐利。这是因为 auto-encoder 求出来的 loss 是根据 distance (它会追求你的整体的 loss 变的更小,所以会存在模糊掉,所以图片的保真度不是特别好,这其实也是 GAN 存在的现象!)

四. Adversarial Auto-Encoders实战

4.1. 训练部分代码实现

4.1.1. 构造模型

# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders编码, 网络
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小网路1:均值(均值和方差是一一对应的,所以维度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小网路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解码,网络
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder传播的过程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var       	  # 注意这里看成取了一个log函数;

    # decoder传播的过程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        var = tf.exp(log_var)         # 去掉log, 得到方差;
        std = var**0.5                # 开根号,得到标准差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 进行还原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向传播不同之外,还有一个额外的约束;这个约束放在损失函数中;
        # 这个约束使得你的mu, var更接近正太分布;所以我们把mu, log_var返回;
        return x_hat, mu, log_var

注意:Variational auto-encoder 除了前向传播不同之外,还有一个额外的约束;这个约束放在损失函数中;这个约束使得你的 mu, var 更接近正太分布;所以我们把 mu, log_var 返回;

4.1.2. 注意损失函数部分

  • 这里主要是加入了 KL 散度的计算(这里都是以 ee 为底): Let p(x)=N(μ1,σ1) and q(x)=N(μ2,σ2)\text { Let } p(x)=N\left(\mu_{1}, \sigma_{1}\right) \text { and } q(x)=N\left(\mu_{2}, \sigma_{2}\right),可以得到:
    KL(p,q)=p(x)logq(x)dx+p(x)logp(x)dx=12log(2πσ22)+σ12+(μ1μ2)22σ2212(1+log2πσ12)=logσ2σ1+σ12+(μ1μ2)22σ2212\begin{aligned} K L(p, q) &=-\int p(x) \log q(x) d x+\int p(x) \log p(x) d x \\ &=\frac{1}{2} \log \left(2 \pi \sigma_{2}^{2}\right)+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}}-\frac{1}{2}\left(1+\log 2 \pi \sigma_{1}^{2}\right) \\ &=\log \frac{\sigma_{2}}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}} -\frac{1}{2}\end{aligned}

  • 在我们这里 p(x)=N(μ1,var)p(x)=N\left(\mu_{1}, \sqrt{\operatorname{var}}\right)q(x)=N(μ2=0,σ2=1)q(x)=N\left(\mu_{2}=0, \sigma_{2}=1\right) 代入可得:
    KL(p,q)=logσ2σ1+σ12+(μ1μ2)22σ2212=log1σ1+σ12+μ12212=logσ1+σ12+μ12212=12(2logσ1(σ12+μ12)+1)=12(logσ12(σ12+μ12)+1)\begin{aligned} K L(p, q) &=\log \frac{\sigma_{2}}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}}-\frac{1}{2}\\ &=\log \frac{1}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\mu_{1}^{2}}{2 } -\frac{1}{2} \\ &= -\log \sigma_{1}+\frac{\sigma_{1}^{2}+\mu_{1}^{2}}{2 } -\frac{1}{2}\\ &= -\frac{1}{2}\left( 2 \log \sigma_{1} -(\sigma_{1}^{2}+\mu_{1}^{2}) + 1 \right) \\ &= -\frac{1}{2}\left( \log \sigma_{1}^2 -(\sigma_{1}^{2}+\mu_{1}^{2}) + 1 \right)\end{aligned}

  • 代码如下:

# compute kl divergence (mu, var) ~ N(0, 1): 我们得到的均值方差和正太分布的;
# 链接参考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

4.1.3. 完整的代码+训练结果

  • 完整的代码如下:
import os
import tensorflow as tf
from tensorflow import keras
from PIL import Image
from matplotlib import pyplot as plt
from tensorflow.keras import Sequential, layers

import numpy as np


tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多张image保存达到一张image里面去。
def save_images(img, name):
    new_im = Image.new('L', (280, 280))

    index = 0
    for i in range(0, 280, 28):
        for j in range(0, 280, 28):
            im = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定义超参数
batchsz = 256      # fashion_mnist
lr = 1e-4

# 数据集加载
(x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.

# we do not need label auto-encoder大家可以理解为无监督学习,标签其实就是本身,和自己对比;
train_db = tf.data.Dataset.from_tensor_slices(x_train)
train_db = train_db.shuffle(batchsz * 5).batch(batchsz)

test_db = tf.data.Dataset.from_tensor_slices(x_test)
test_db = test_db.batch(batchsz)


print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)


# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders编码, 网络
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小网路1:均值(均值和方差是一一对应的,所以维度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小网路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解码,网络
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder传播的过程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var

    # decoder传播的过程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        std = tf.exp(log_var)         # 去掉log, 得到方差;
        std = std**0.5                # 开根号,得到标准差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 进行还原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向传播不同之外,还有一个额外的约束;
        # 这个约束使得你的mu, var更接近正太分布;所以我们把mu, log_var返回;
        return x_hat, mu, log_var


model = VAE()
model.build(input_shape=(128, 784))
optimizer = keras.optimizers.Adam(lr=lr)

for epoch in range(100):

    for step, x in enumerate(train_db):

        x = tf.reshape(x, [-1, 784])
        with tf.GradientTape() as tape:
            # shape
            x_hat, mu, log_var = model(x)

            # 把每个像素点当成一个二分类的问题;
            rec_loss = tf.losses.binary_crossentropy(x, x_hat, from_logits=True)
            # rec_loss = tf.losses.MSE(x, x_rec_logits)
            rec_loss = tf.reduce_mean(rec_loss)

            # compute kl divergence (mu, var) ~ N(0, 1): 我们得到的均值方差和正太分布的;
            # 链接参考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
            kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
            kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

            loss = rec_loss + 1. * kl_div

        grads = tape.gradient(loss, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))

        if step % 100 ==0:
            print('epoch: %3d, step:%4d, kl_div: %5f, rec_loss:%9f' %(epoch, step, float(kl_div), float(rec_loss)))
  • 训练结果:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/vae.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
epoch:   0, step:   0, kl_div: 0.000742, rec_loss: 0.697121
epoch:   0, step: 100, kl_div: 0.022200, rec_loss: 0.541916
epoch:   0, step: 200, kl_div: 0.027947, rec_loss: 0.447276
epoch:   1, step:   0, kl_div: 0.025808, rec_loss: 0.442256
epoch:   1, step: 100, kl_div: 0.022090, rec_loss: 0.410343
epoch:   1, step: 200, kl_div: 0.019631, rec_loss: 0.399730
epoch:   2, step:   0, kl_div: 0.020915, rec_loss: 0.378364
epoch:   2, step: 100, kl_div: 0.019483, rec_loss: 0.365527
epoch:   2, step: 200, kl_div: 0.017862, rec_loss: 0.357729
epoch:   3, step:   0, kl_div: 0.018322, rec_loss: 0.350828
epoch:   3, step: 100, kl_div: 0.018883, rec_loss: 0.339817
epoch:   3, step: 200, kl_div: 0.017213, rec_loss: 0.347372
epoch:   4, step:   0, kl_div: 0.016629, rec_loss: 0.344929
epoch:   4, step: 100, kl_div: 0.016194, rec_loss: 0.325347
epoch:   4, step: 200, kl_div: 0.014990, rec_loss: 0.327286
epoch:   5, step:   0, kl_div: 0.015161, rec_loss: 0.326509
epoch:   5, step: 100, kl_div: 0.014672, rec_loss: 0.321315

4.2. 测试代码实现

import os
import tensorflow as tf
from tensorflow import keras
from PIL import Image
from matplotlib import pyplot as plt
from tensorflow.keras import Sequential, layers
import numpy as np

tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多张image保存达到一张image里面去。
def save_images(img, name):
    new_im = Image.new('L', (280, 280))

    index = 0
    for i in range(0, 280, 28):
        for j in range(0, 280, 28):
            im = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定义超参数
batchsz = 256      # fashion_mnist
lr = 1e-4

# 数据集加载
(x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.

# we do not need label auto-encoder大家可以理解为无监督学习,标签其实就是本身,和自己对比;
train_db = tf.data.Dataset.from_tensor_slices(x_train)
train_db = train_db.shuffle(batchsz * 5).batch(batchsz)

test_db = tf.data.Dataset.from_tensor_slices(x_test)
test_db = test_db.batch(batchsz)


print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)


# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders编码, 网络
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小网路1:均值(均值和方差是一一对应的,所以维度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小网路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解码,网络
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder传播的过程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var

    # decoder传播的过程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        std = tf.exp(log_var)         # 去掉log, 得到方差;
        std = std**0.5                # 开根号,得到标准差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 进行还原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向传播不同之外,还有一个额外的约束;
        # 这个约束使得你的mu, var更接近正太分布;所以我们把mu, log_var返回;
        return x_hat, mu, log_var

model = VAE()
model.build(input_shape=(128, 784))
optimizer = keras.optimizers.Adam(lr=lr)

for epoch in range(100):

    for step, x in enumerate(train_db):

        x = tf.reshape(x, [-1, 784])
        with tf.GradientTape() as tape:
            # shape
            x_hat, mu, log_var = model(x)

            # 把每个像素点当成一个二分类的问题;
            rec_loss = tf.losses.binary_crossentropy(x, x_hat, from_logits=True)
            # rec_loss = tf.losses.MSE(x, x_rec_logits)
            rec_loss = tf.reduce_mean(rec_loss)

            # compute kl divergence (mu, var) ~ N(0, 1): 我们得到的均值方差和正太分布的;
            # 链接参考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
            kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
            kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

            loss = rec_loss + 1. * kl_div

        grads = tape.gradient(loss, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))

        if step % 100 ==0:
            print('epoch: %3d, step:%4d, kl_div: %5f, rec_loss:%9f' %(epoch, step, float(kl_div), float(rec_loss)))


    # evaluation 1: 从正太分布直接sample;
    z = tf.random.normal((batchsz, z_dim))                              # 从正太分布中sample这个尺寸的
    logits = model.decoder(z)                                           # 通过这个得到decoder
    x_hat = tf.sigmoid(logits)
    x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() * 255.
    logits = x_hat.astype(np.uint8)                                     # 标准的图片格式;
    save_images(x_hat, 'vae_images/sampled_epoch%d.png' %epoch)         # 直接sample出的正太分布;

    # evaluation 2: 正常的传播过程;
    x = next(iter(test_db))
    x = tf.reshape(x, [-1, 784])
    x_hat_logits, _, _  = model(x)                       # 前向传播返回的还有mu, log_var
    x_hat = tf.sigmoid(x_hat_logits)
    x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() * 255.
    x_hat = x_hat.astype(np.uint8)                       # 标准的图片格式;
    # print(x_hat.shape)
    save_images(x_hat, 'vae_images/rec_epoch%d.png' %epoch)
  • 测试结果:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/vae.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
epoch:   0, step:   0, kl_div: 0.000742, rec_loss: 0.697121
epoch:   0, step: 100, kl_div: 0.022200, rec_loss: 0.541916
...
...
epoch:  98, step:   0, kl_div: 0.009120, rec_loss: 0.284501
epoch:  98, step: 100, kl_div: 0.009150, rec_loss: 0.283306
epoch:  98, step: 200, kl_div: 0.009058, rec_loss: 0.294833
epoch:  99, step:   0, kl_div: 0.009023, rec_loss: 0.283009
epoch:  99, step: 100, kl_div: 0.009181, rec_loss: 0.296823
epoch:  99, step: 200, kl_div: 0.008963, rec_loss: 0.289650
Process finished with exit code 0
原始图片 重建图片 sample重建

注意: 我们发现重建的图片相对原始图片存在模糊的现象,棱角美誉原图像那么锐利。这是因为 auto-encoder 求出来的 loss 是根据 distance (它会追求你的整体的 loss 变的更小,所以会存在模糊掉,所以图片的保真度不是特别好,这其实也是 GAN 存在的现象!)

五. 需要全套课程视频+PPT+代码资源可以私聊我!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章