import numpy as np
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.optimizers as optimizers
import tensorflow.keras.metrics as metrics
import tensorflow.keras.datasets as datasets
from tensorflow.keras import Sequential
import matplotlib.pyplot as plt
from PIL import Image
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
import os
tf.random.set_seed(22)
np.random.seed(22)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert tf.__version__.startswith('2.')
#保存一张画布-画布中图片的数量可自行设置
def save_images(imgs, name):
#设置画布大小-控制要可视化的结果图片的数量
new_im = Image.new('L', (448, 896))
#遍历输入图片的索引
index = 0
#将图片填入画布(i,j)代表位置
for i in range(0, 448, 28):
for j in range(0,896, 28):
#选取图片
im = imgs[index]
#将张量转换成图片
im = Image.fromarray(im, mode='L')
#放入画布中的位置
new_im.paste(im, (i, j))
#继续遍历下一张图片
index += 1
#保存到本地,name为文件名
new_im.save(name)
batchsz = 512
lr = 1e-3
#均值和方差向量长度-提取内部特征的个数-10就相当于从训练集中提取10个内部特征
z_dim = 10
#加载数据集
(x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
#查看返回的对象:numpy.ndarray
print('x_train type:\n',type(x_train))
#查看返回的对象形状:(60000,28,28)-60000张训练图片 单通道
print('x_train shape:\n',x_train.shape)
#用numpy转换数据类型并归一化
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.
#构建数据集对象-只用到特征值不用标签
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)
class VAE(keras.Model):
def __init__(self):
super(VAE, self).__init__()
# Encoder
self.fc1 = layers.Dense(128)
self.fc2 = layers.Dense(z_dim) # get mean prediction
self.fc3 = layers.Dense(z_dim)
# Decoder
self.fc4 = layers.Dense(128)
self.fc5 = layers.Dense(784)
def encoder(self, x):
h = tf.nn.relu(self.fc1(x))
print('公共层输出的形状:\n',h.shape)
# get mean
mu = self.fc2(h)
print('均值的形状:\n',mu.shape)
print('均值的值:\n',mu)
# get variance
log_var = self.fc3(h)
print('方差的形状:\n',log_var.shape)
print('方差的值:\n',log_var)
return mu, log_var
def decoder(self, z):
out = tf.nn.relu(self.fc4(z))
print('解码器第一层经过relu后的形状:\n',out.shape)
print('解码器第一层经过relu后的值:\n',out)
out = self.fc5(out)
print('解码器第二层输出的形状:\n',out.shape)
print('解码器第二层输出的值:\n',out)
return out
def reparameterize(self, mu, log_var):
#获取标准正太分布
eps = tf.random.normal(log_var.shape)
print('标准正态分布的形状:\n',eps.shape)
print('标准正态分布的值:\n',eps)
std = tf.exp(log_var*0.5)
print('重参数后的方差形状:\n',std.shape)
print('重参数后的方差值:\n',std)
z = mu + std * eps
print('重参数后的z的形状:\n',z.shape)
print('重参数后的z的值:\n',z)
return z
def call(self, inputs, training=None):
# [b, 784] => [b, z_dim], [b, z_dim]
mu, log_var = self.encoder(inputs)
# reparameterization trick
z = self.reparameterize(mu, log_var)
x_hat = self.decoder(z)
return x_hat, mu, log_var
model = VAE()
model.build(input_shape=(4, 784))
optimizer = keras.optimizers.Adam(lr)
for epoch in range(1):
for step, x in enumerate(train_db):
#将一个batch展平:(512,28,28)->(512,784)
x = tf.reshape(x, [-1, 784])
#创建梯度环境
with tf.GradientTape() as tape:
#获得重建样本 均值和方差 每个样本都对应一个均值和方差向量 长度都为10 重建损失也是一个样本一个样本计算的,最后在 整个batch上计算重建损失的平均值以及每个样本KL散度的平均值
x_rec_logits, mu, log_var = model(x)
#重建损失
rec_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=x_rec_logits)
rec_loss = tf.reduce_sum(rec_loss) / x.shape[0]
# 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_sum(kl_div) / x.shape[0]
#总loss
loss = rec_loss + 1. * kl_div
#计算梯度
grads = tape.gradient(loss, model.trainable_variables)
#更新梯度
optimizer.apply_gradients(zip(grads, model.trainable_variables))
#每10个step输出信息
if step % 10 == 0:
print(epoch, step, 'kl div:', float(kl_div), 'rec loss:', float(rec_loss))
#-----------------每一次epoch训练后都测试一次----------------、
#用测试测试集测试-只测试一个batch的数据-512张图片-分布到32*16的画布上 注意训练时一个epoch的所有batch都参与训练 测试时只选取了测试数据集10000/512->向上取整=20个batch中的其中一个
#用测试集是获取模型的最终输出,其中会有用重参数化采样隐变量z的中间过程,都封装在整个模型中的前向传播中了,下面那个方法是直接从标准正太分布中采样隐变量,它完全空白的,而测试集有了一个编码器生成隐变量的过程,相当于训练得到了正确的和原始数据对应的内部特征分布,所以重参数采样它采样到的到部分是正确的特征,所以恢复(重建)的图片和原始图片是相近的
x = next(iter(test_db))
x = tf.reshape(x, [-1, 784])
x_hat_logits, _, _ = model(x)
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)
save_images(x_hat, 'vae_images2/rec_epoch%d.png' % epoch)
#从正态分布中直接采样,直接使用生成模型生成样本、
# evaluation
z = tf.random.normal((batchsz, z_dim))
logits = model.decoder(z)
x_hat = tf.sigmoid(logits)
#恢复形状和反归一化
x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() *255.
#恢复整型数据类型
x_hat = x_hat.astype(np.uint8)
#保存
save_images(x_hat, 'vae_images2/sampled_epoch%d.png'%epoch)
print语句可以忽略
测试集重建图片:
epoch0
epoch10
epoch100
epoch199
生成图片:
epoch10
epoch100
epoch199