数据集 :FashionMNIST数据集,包含10种不同类型的衣服、鞋子、包等灰度图片,图片大小为28*28.其中有60000张训练集,10000张测试集。
加载FashionMINST数据集
利用keras.datasets.fashion_mnist.load_data()可以下载FashionMINST数据集:
from tensorflow import keras
import numpy as np
import tensorflow as tf
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"]='2'
batchsz = 50
#下载FashionMINST数据集
(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.
#只需要图片数据,不需要样本标签
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)
构建自编码网络(AE)
编码网络encoder和解码网络decoder分别由三层全连接构成;节点数分别为encoder(256,128,20)和decoder(128,256,784),其中784是将输入的28*28图片打平成列向量的长度。
class AE(Model):
def __init__(self):
super(AE, self).__init__()
#创建Encoder网络
self.encoder = Sequential([
Dense(256, activation=tf.nn.relu),
Dense(128, activation=tf.nn.relu),
Dense(20)
])
#创建Decoder网络
self.decoder = Sequential([
Dense(128, activation=tf.nn.relu),
Dense(256, activation=tf.nn.relu),
Dense(784)
])
def call(self, inputs, training=None, mask=None):
#前向传播函数
#编码获得隐藏向量h,其中[b, 784]->[b, 20]
h = self.encoder(inputs)
#解码获得重建图片x_hat,其中[b, 20]->[b, 784]
x_hat = self.decoder(h)
return x_hat
训练AE
优化器:Adam,学习率:0.01;
损失函数:tf.nn.sigmoid_cross_entropy_with_logits()
if __name__=="__main__":
#创建网络对象
model = AE()
#指定输入大小
model.build(input_shape = (4, 784))
#打印网络信息
model.summary()
#创建优化器,此处学习率lr=0.01
optimizer = optimizers.Adam(lr=0.01)
for epoch in range(10): # 训练
for step, x in enumerate(train_db):
#输入图片拉成一个向量(打平),[b, 28*28]->[b, 784]
x = tf.reshape(x, [-1, 784])
#构建梯度记录器
with tf.GradientTape() as tape:
#前向计算获得重建的图片
x_rec_logits = model(x)
#计算重建图片与输入之间的损失函数
rec_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=x_rec_logits)
#计算均值
rec_loss = tf.reduce_mean(rec_loss)
#自动求导2个子网络的梯度
grads = tape.gradient(rec_loss, model.trainable_variables)
#自动跟新,更新两个自网络的参数
optimizer.apply_gradients(zip(grads, model.trainable_variables))
#每隔100次打印一次训练误差
if step & 100 == 0:
print(epoch, step, float(rec_loss))
测试AE:重建图片
测试集选择50张图片进行重建。
#重建图片,从测试集采样一批图片
x = next(iter(test_db)) # 测试
#图片拉成列向量送入AE进行重建
logits = model(tf.reshape(x, [-1, 784]))
#将输出转换为像素值,使用sigmoid函数
x_hat = tf.sigmoid(logits)
#将列向量reshape成图片
x_hat = tf.reshape(x_hat, [-1, 28, 28])
#拼接前50张原图+重建的前50张原图
x_concat = tf.concat([x[:50], x_hat[:50]], axis=0)
#恢复为0~255
x_concat = x_concat.numpy()*255.
#转换为整型
x_concat = x_concat.astype(np.uint8)
#保存图片
save_images(x_concat, 'ae_images/dense_rec_epoch_%d.png' % epoch)
其中save_images()函数如下:
from PIL import Image
import numpy as np
def save_images(imgs, 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 = imgs[index]
im = Image.fromarray(im, mode='L')
new_im.paste(im, (i, j))
index += 1
new_im.save(name)
重建结果
左侧五列为原始图片,右侧五列为重建图片。
上面是将原始图像拉成一个列向量进行训练和测试,下面采用原始图像进行训练和测试
构建卷积自编码
编码网络和解码网络分别采用三层卷积层的操作,进行网络构建。
class AE(Model):
def __init__(self):
super(AE, self).__init__()
self.encoder = Sequential([
Conv2D(filters=3, kernel_size=3, padding='same', activation=relu),
Conv2D(filters=6, kernel_size=3, padding='same', activation=relu),
Conv2D(filters=9, kernel_size=3, padding='same')
])
self.decoder = Sequential([
Conv2D(filters=6, kernel_size=3, padding='same', activation=relu),
Conv2D(filters=3, kernel_size=3, padding='same', activation=relu),
Conv2D(filters=1, kernel_size=3, padding='same')
])
def call(self, inputs, training=None, mask=None):
h = self.encoder(inputs)
output = self.decoder(h)
return output
训练卷积自编码
直接输入图片,不用拉伸为列向量。
if __name__=="__main__":
model = AE()
optimizer = optimizers.Adam(lr=0.01)
for epoch in range(2): # 训练
for step, x in enumerate(train_db):
x = np.expand_dims(x, axis=-1)
with tf.GradientTape() as tape:
x_rec_logits = model(x)
rec_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=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, step, float(rec_loss))
测试卷积AE:重建图片
x = next(iter(test_db)) # 测试
x = np.expand_dims(x, axis=-1)
logits = model(x)
x, logits = np.squeeze(x), np.squeeze(logits)
x_hat = tf.sigmoid(logits)
x_concat = tf.concat([x[:50], x_hat[:50]], axis=0)
x_concat = x_concat.numpy()*255.
x_concat = x_concat.astype(np.uint8)
save_images(x_concat, 'ae_images/cnn_rec_epoch_%d.png' % epoch)
两种自编码的方式略有不同,仅供参考。
参考书籍:《TensorFlow深度学习——深入理解人工智能算法设计》 作者:龙良曲