趋近智
本指南侧重于卷积自编码器(CAE)的实际实现。CAE对具有空间层级的数据(如图像)尤其有效,因为它们利用卷积层来获取局部模式,并使用池化层来创建空间不变表示。本动手操作指南将指导您构建和训练一个CAE,使用常规数据集进行图像重建。在此示例中将使用Keras API和TensorFlow后端,假设您已设置好可用的深度学习环境。
首先,请确保您已安装TensorFlow。我们将使用Fashion-MNIST数据集,它是MNIST的一个稍复杂替代品,由28x28的服装灰度图像组成。
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# 加载Fashion-MNIST数据集
(x_train, _), (x_test, _) = keras.datasets.fashion_mnist.load_data()
# 将像素值归一化到[0, 1]范围,并为Conv2D输入重塑
def preprocess_data(x):
x = x.astype('float32') / 255.0
# 添加通道维度(灰度)
x = np.reshape(x, (len(x), 28, 28, 1))
return x
x_train = preprocess_data(x_train)
x_test = preprocess_data(x_test)
print(f"x_train 形状: {x_train.shape}")
print(f"x_test 形状: {x_test.shape}")
将数据归一化到[0, 1]范围是常见做法,并且在重建任务中,与二元交叉熵(Binary Crossentropy)或均方误差(Mean Squared Error)等损失函数配合良好。添加通道维度是必需的,因为TensorFlow/Keras中的Conv2D层需要形状为(batch_size, height, width, channels)的输入张量。
CAE通常由一个编码器(将输入图像映射到压缩的潜在表示)和一个解码器(从该表示重建图像)组成。
编码器: 编码器使用Conv2D层(通常与ReLU激活配对)来提取特征。MaxPooling2D层用于减小空间维度(高度和宽度),同时通过学习到的滤波器提升表示能力。我们逐步降低空间分辨率并增加滤波器数量。
解码器: 解码器模仿编码器结构,但使用上采样层来增加空间分辨率并减少滤波器数量。Conv2DTranspose层常用于上采样。这些层执行转置卷积,有效地学习如何反转Conv2D和MaxPooling2D层的下采样过程。或者,可以使用UpSampling2D后接Conv2D层。最后一层通常使用sigmoid激活来输出[0, 1]范围内的像素值,与我们归一化的输入数据相匹配。
我们来定义一个简单的CAE模型:
latent_dim = 32 # 瓶颈层的维度
# --- 编码器 ---
encoder_inputs = keras.Input(shape=(28, 28, 1))
x = layers.Conv2D(32, (3, 3), activation='relu', padding='same', strides=2)(encoder_inputs) # 28x28 -> 14x14
x = layers.Conv2D(64, (3, 3), activation='relu', padding='same', strides=2)(x) # 14x14 -> 7x7
# 潜在瓶颈层/解码器起始所需的形状信息
shape_before_flattening = tf.keras.backend.int_shape(x)[1:] # (7, 7, 64)
# 我们可以直接使用Conv2D层作为瓶颈层,或者展平后使用Dense层
# 这里,使用Conv2D来尽可能保留空间结构信息
encoder_output = layers.Conv2D(latent_dim, (3, 3), activation='relu', padding='same')(x) # 瓶颈层: 7x7xlatent_dim
encoder = keras.Model(encoder_inputs, encoder_output, name="encoder")
# encoder.summary() # 可选:查看编码器结构
# --- 解码器 ---
decoder_inputs = keras.Input(shape=(7, 7, latent_dim)) # 输入形状与编码器输出相匹配
x = layers.Conv2DTranspose(64, (3, 3), activation='relu', padding='same', strides=2)(decoder_inputs) # 7x7 -> 14x14
x = layers.Conv2DTranspose(32, (3, 3), activation='relu', padding='same', strides=2)(x) # 14x14 -> 28x28
decoder_outputs = layers.Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x) # 28x28 -> 28x28x1
decoder = keras.Model(decoder_inputs, decoder_outputs, name="decoder")
# decoder.summary() # 可选:查看解码器结构
# --- 自编码器(编码器 + 解码器) ---
autoencoder_input = keras.Input(shape=(28, 28, 1))
encoded = encoder(autoencoder_input)
decoded = decoder(encoded)
autoencoder = keras.Model(autoencoder_input, decoded, name="autoencoder")
autoencoder.summary()
请注意使用padding='same',它有助于保持卷积后的空间维度,使架构设计略微简化。Conv2D和Conv2DTranspose中的strides=2分别处理下采样和上采样。这里的瓶颈是一个小特征图 ()。
这是组合自编码器架构的可视化:
此图显示了从输入图像通过编码器的下采样卷积层到达瓶颈层,再通过解码器的上采样转置卷积层到达重建输出图像的流程。
我们需要编译autoencoder模型,指定优化器和损失函数。由于我们的像素值归一化到[0, 1]范围,并且我们将重建视为预测每个像素强度的概率,因此二元交叉熵(binary_crossentropy)是一个合适的损失函数。均方误差(mse)也是图像重建的常见选择。我们将使用Adam优化器。
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
# 训练自编码器
epochs = 20 # 根据需要调整
batch_size = 128
history = autoencoder.fit(x_train, x_train, # 输入和目标相同
epochs=epochs,
batch_size=batch_size,
shuffle=True,
validation_data=(x_test, x_test)) # 使用测试集进行验证
在训练过程中,模型学习最小化输入图像(x_train)与通过编码器再通过解码器重建的图像之间的差异。监控验证损失(val_loss)有助于检查过拟合。
我们来可视化训练过程:
# 绘制训练和验证损失值
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label='Train Loss', color='#1c7ed6')
plt.plot(history.history['val_loss'], label='Validation Loss', color='#fd7e14')
plt.title('模型训练损失')
plt.ylabel('损失 (二元交叉熵)')
plt.xlabel('周期')
plt.legend(loc='upper right')
plt.grid(True, linestyle='--', alpha=0.6)
plt.show()
卷积自编码器的训练和验证损失曲线。理想情况下,两条曲线都会下降并收敛。
评估自编码器重建效果的主要方式是目视检查其在未见数据(测试集)上的输出。
# 使用训练好的自编码器重建测试集图像
reconstructed_imgs = autoencoder.predict(x_test)
# 显示原始图像和重建图像
n = 10 # 要显示的图像数量
plt.figure(figsize=(20, 4))
for i in range(n):
# 显示原始图像
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
if i == 0:
ax.set_title("原始图像")
# 显示重建图像
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(reconstructed_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
if i == 0:
ax.set_title("重建图像")
plt.suptitle('原始图像 vs. 重建图像(测试集)')
plt.show()
您应该会看到,重建图像是原始图像的有些模糊的版本,捕获了主要特征但丢失了一些细节。这是预料之中的,因为瓶颈层迫使模型学习压缩表示。重建质量在很大程度上取决于模型架构、潜在维度的大小、数据集复杂度以及训练时长。
本示例为实现CAE提供了一个起点。您可以通过以下方式进行更多尝试:
UpSampling2D后接Conv2D代替Conv2DTranspose。latent_dim如何影响重建质量和压缩程度。x_train_noisy)添加噪声,同时保留干净图像(x_train)作为目标。这通常会带来更好的特征提取。encoder部分,从图像中提取压缩特征,用于分类等后续任务。通过构建和操作CAE,您在设计适用于空间数据的架构方面获得了实际经验,并理解了通过压缩进行表示学习中的权衡。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造