文章目录
第 9 章:模型保存与加载
9.1 保存模型的方式
9.1.1 保存整个模型(包括结构和权重)
使用 Keras 的 save
方法可以便捷地将整个模型保存为 HDF5 格式,这一过程会把模型的架构信息、权重参数以及优化器状态等全部打包存储。例如:
from keras.models import Sequential
from keras.layers import Dense
# 构建一个简单的模型
model = Sequential()
model.add(Dense(units=64, activation='relu', input_dim=10))
model.add(Dense(units=1, activation='sigmoid'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 训练模型(假设已有训练数据 x_train 和 y_train)
model.fit(x_train, y_train, epochs=10, batch_size=32)
# 保存整个模型
model.save('my_model.h5')
这里创建了一个简单的二分类模型,完成训练后,通过 model.save('my_model.h5')
语句将模型完整保存下来。
- 保存模型的优点与适用场景:
- 优点:这种保存方式的最大优势在于方便快捷,后续重新加载模型时,无需再次构建模型架构,直接就能使用,极大地简化了模型的复用流程,尤其适用于快速恢复模型进行预测、评估或继续训练的场景。例如,在一个实时预测系统中,模型定期更新训练,保存整个模型可以确保在需要更新线上模型时,迅速替换,减少系统停机时间。
- 适用场景:当模型结构相对固定,不需要频繁修改,且需要在不同的环境或项目中完整复用模型时,保存整个模型是最佳选择。比如在科研项目中,团队成员之间共享已经训练好的模型,直接加载整个模型可以避免因构建模型差异导致的错误,提高协作效率。
9.1.2 只保存模型权重
除了保存整个模型,Keras 还提供了 save_weights
方法专门用于保存模型的权重。例如:
# 假设已有训练好的模型 model
model.save_weights('my_model_weights.h5')
此时仅存储了模型的权重矩阵值,而不包含模型的架构信息。
- 应用场景与与保存整个模型的区别:
- 应用场景:在一些情况下,模型的结构是已知的,或者需要灵活调整模型结构,如进行迁移学习中的微调操作。先保存权重,后续可以加载到不同结构的基础模型上进行针对性训练。例如,在图像分类任务中,已经有一个在大规模通用图像数据集上预训练的卷积神经网络模型,想要将其权重迁移到一个针对特定领域(如医学影像)的模型结构上,只需保存预训练模型的权重,再加载到新构建的医学影像分类模型中,就能利用预训练模型学习到的通用特征,快速适应新任务,节省训练时间。
- 与保存整个模型的区别:保存整个模型包含了所有信息,再次使用时一步到位;而只保存权重则需要在加载时重新构建模型架构,操作相对繁琐,但提供了更多的灵活性,适合对模型结构有进一步优化需求的场景,特别是在探索不同模型变体对性能影响时,能快速切换模型结构而无需重复保存不同结构的完整模型。
9.2 加载模型
9.2.1 加载整个模型的方法
使用 load_model
函数可以轻松加载之前保存的 HDF5 格式的模型。例如:
from keras.models import load_model
# 加载保存的整个模型
loaded_model = load_model('my_model.h5')
# 使用加载后的模型进行预测(假设已有测试数据 x_test)
predictions = loaded_model.predict(x_test)
# 评估模型性能
loss, accuracy = loaded_model.evaluate(x_test, y_test)
print(f"加载模型后的测试损失: {loss}, 测试准确率: {accuracy}")
这里通过 load_model
函数将之前保存的 my_model.h5
模型重新加载到内存中,然后直接用于预测和性能评估,整个过程简单直接,与保存模型时的便捷性相呼应,能快速恢复模型的全部功能。
9.2.2 加载模型权重并应用于新模型结构
当只保存了模型权重时,加载权重到新模型结构需要先构建与原模型兼容的新模型,再使用 load_weights
方法。例如:
from keras.models import Sequential
from keras.layers import Dense
# 构建一个新的模型结构(需与保存权重的模型结构兼容)
new_model = Sequential()
new_model.add(Dense(units=64, activation='relu', input_dim=10))
new_model.add(Dense(units=1, activation='sigmoid'))
# 加载之前保存的权重
new_model.load_weights('my_model_weights.h5')
# 编译新模型(注意:需与原模型编译参数一致,如优化器、损失函数等)
new_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 使用加载权重后的新模型进行预测等操作
predictions = new_model.predict(x_test)
这里构建了一个新模型,其结构与保存权重的模型相同,然后加载权重,并且按照原模型的编译方式进行编译,确保模型能正常运行。
-
如何在不同模型结构中加载已保存的权重:在实际应用中,如果想要在略有差异的模型结构中加载权重,需要确保关键层的结构匹配。例如,原模型有一个
Conv2D
层,过滤器数量为 32,卷积核大小为(3, 3)
,新模型中对应的Conv2D
层可以有不同的后续层连接,但这一层的过滤器数量和卷积核大小应尽量保持一致,否则可能导致加载权重失败或模型运行异常。对于不匹配的层,可以通过一些技巧,如将原模型不相关的层权重舍弃,或者对新模型不匹配的层进行随机初始化,再加载匹配层的权重,以实现部分权重的迁移利用。 -
案例:微调预训练模型的权重:假设我们有一个在 ImageNet 数据集上预训练的 ResNet50 模型,想要将其应用于花卉分类任务。首先加载预训练模型:
from keras.applications.resnet50 import ResNet50
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
这里 include_top=False
表示不加载原模型用于 ImageNet 分类的顶层全连接层,因为我们的花卉分类任务类别与 ImageNet 不同。接着构建新的顶层:
from keras.layers import GlobalAveragePooling2D, Dense
from keras.models import Model
x = base_model.output
x = GlobalAveragePooling2D()(x)
predictions = Dense(10, activation='softmax')(x) # 假设花卉有 10 个类别
model = Model(inputs=base_model.input, outputs=predictions)
然后冻结预训练模型的底层权重:
for layer in base_model.layers:
layer.trainable = False
这一步使得在初始训练阶段,仅新构建的顶层权重会被更新,利用预训练模型学习到的通用图像特征。之后加载预训练模型的权重:
base_model.load_weights('resnet50_weights.h5') # 假设已下载预训练权重到本地
最后对模型进行编译并训练:
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_val, y_val))
经过一段时间训练,当新顶层权重初步收敛后,可以选择性地解冻部分底层权重,再次进行训练,进一步微调模型,使其更好地适应花卉分类任务,提升模型性能。
9.3 模型部署准备
9.3.1 模型保存格式与部署环境的兼容性
不同的部署环境对模型保存格式有不同的要求。HDF5 格式虽然在 Keras 生态系统内使用广泛,但在一些移动端或嵌入式设备上,可能无法直接支持。例如,在 iOS 系统开发的移动端应用中,更倾向于使用 Core ML 格式的模型;而在 Android 系统中,TensorFlow Lite 格式则更为适配,这些专门的格式能够更好地与移动端的硬件加速(如 GPU 或特定的神经网络芯片)相结合,提高模型运行效率。此外,一些云端部署环境,如 AWS Lambda,可能对模型的加载速度和内存占用有严格要求,需要将模型转换为更紧凑、加载更快的格式,以满足实时性需求。
9.3.2 为模型部署进行必要的优化与转换
为了适应不同的部署环境,通常需要对模型进行优化与转换。以将 Keras 模型转换为 TensorFlow Lite 格式用于 Android 部署为例:
import tensorflow as tf
# 假设已有训练好的 Keras 模型 model
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# 保存转换后的 TensorFlow Lite 模型
with open('model.tflite', 'w') as f:
f.write(tflite_model)
在转换过程中,还可以进行一些优化操作,如量化。量化是将模型中的浮点型参数转换为整型,减少内存占用和计算量,提高模型运行速度:
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimize_for_size() # 进行尺寸优化,包括量化等操作
tflite_model = converter.convert()
经过这样的优化转换,模型就能更高效地在 Android 设备上运行,满足移动端实时性、低功耗的需求。
- 案例:将 Keras 模型转换为适合移动端部署的格式:假设我们开发了一个基于 Keras 的手写数字识别模型,想要部署到 iOS 和 Android 移动端应用中。首先,按照上述方法将模型转换为 Core ML 格式用于 iOS:
import coremltools
# 假设已有训练好的 Keras 模型 model
coreml_model = coremltools.conversions.keras.convert(model)
coreml_model.save('handwriting_recognition.mlmodel')
对于 Android 端,将其转换为 TensorFlow Lite 格式并优化:
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimize_for_size()
tflite_model = converter.convert()
with open('handwriting_recognition.tflite', 'w') as f:
f.write(tflite_model)
然后,在移动端开发中,iOS 应用通过集成 handwriting_recognition.mlmodel
,利用 Core ML 框架加载模型进行预测;Android 应用则引入 handwriting_recognition.tflite
,结合 TensorFlow Lite 库实现同样的功能,使得模型能够在移动端流畅运行,为用户提供便捷的手写数字识别服务。
第 10 章:高级主题
10.1 自定义模型与层
10.1.1 深入理解 Keras 的模型构建机制
Keras 提供了两种主要的模型构建方式:序贯模型(Sequential
)和函数式模型(Functional
)。序贯模型以线性堆叠的方式构建网络,简洁直观,适用于简单的层叠架构,如经典的多层感知机(MLP)。而函数式模型更为灵活,它通过明确指定输入和输出张量之间的函数关系,能够构建复杂的非线性结构,支持多输入、多输出以及共享层等高级特性,可应对如多模态数据融合、目标检测等复杂任务。
在模型构建过程中,Keras 层(如 Dense
、Conv2D
、LSTM
等)是核心组件,每个层对输入数据执行特定的变换,通过权重矩阵、激活函数等实现特征提取与转换。模型训练时,基于反向传播算法,利用损失函数对模型参数求偏导数,再由优化器更新参数,逐步调整模型以拟合数据。理解这些基础构建模块和训练原理,是深入自定义模型与层的关键。
10.1.2 自定义模型类的方法与步骤
自定义模型类通常继承自 Keras 的 Model
基类。首先,在 __init__
方法中定义模型的层结构,例如:
from keras.models import Model
from keras.layers import Dense, Input
class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
self.input_layer = Input(shape=(100,))
self.hidden_layer = Dense(64, activation='relu')
self.output_layer = Dense(10, activation='softmax')
这里定义了一个简单的包含输入层、隐藏层和输出层的模型。接着,在 call
方法中实现数据的前向传播逻辑:
def call(self, x):
x = self.hidden_layer(x)
return self.output_layer(x)
通过这种方式,当输入数据 x
传入模型时,会依次经过隐藏层和输出层的处理得到最终预测结果。最后,实例化并使用自定义模型,如同使用普通 Keras 模型一样:
model = MyModel()
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=10, batch_size=32)
10.1.3 自定义层的详细实现,包括前向传播与反向传播
自定义层同样继承自 Keras 的基类,如 Layer
。以实现一个简单的平方运算层为例,在 __init__
方法中可以进行层的初始化操作:
from keras.layers import Layer
class SquareLayer(Layer):
def __init__(self):
super(SquareLayer, self).__init__()
在 call
方法中实现前向传播,对输入数据执行平方运算:
def call(self, x):
return x ** 2
对于需要训练的自定义层,还需定义 compute_output_shape
方法来指定输出形状,并在反向传播中计算梯度。假设层有可训练参数,如权重 w
,在 __init__
中初始化:
class TrainableSquareLayer(Layer):
def __init__(self):
super(TrainableSquareLayer, self).__init__()
self.w = self.add_weight(shape=(1,), initializer='random_normal', trainable=True)
在 call
中结合权重进行前向传播:
def call(self, x):
return self.w * x ** 2
compute_output_shape
方法:
def compute_output_shape(self, input_shape):
return input_shape
在反向传播中,Keras 会自动根据定义的前向传播操作,利用链式法则计算梯度,以更新层中的可训练参数,实现自定义层在模型训练中的功能适配与优化。
10.2 多 GPU 训练
10.2.1 多 GPU 训练的原理与优势
多 GPU 训练的核心原理是将模型训练的计算负载分配到多个 GPU 上,以加速训练过程。在深度学习中,模型训练涉及大量的矩阵运算,如卷积、全连接层的计算等,这些运算在 GPU 上能够利用其并行计算能力得到高效执行。通过多 GPU 并行,不同 GPU 可以同时处理不同批次的数据(数据并行),或者不同 GPU 负责模型的不同部分计算(模型并行),从而大幅缩短训练时间。
其优势显著,对于大规模深度学习模型,如拥有数亿甚至数十亿参数的神经网络,单 GPU 训练可能需要数周甚至数月时间,而多 GPU 训练能将时间缩短至数天甚至数小时,使得研究人员能够更快地迭代模型,探索不同的架构和超参数设置,加速科研与项目开发进程。
10.2.2 在 Keras 中配置和使用多 GPU 进行训练的方法
在 Keras 中,使用多 GPU 训练可以借助 tf.distribute.MirroredStrategy
(基于 TensorFlow 后端)。首先,导入相关模块:
import tensorflow as tf
然后,定义策略:
strategy = tf.distribute.MirroredStrategy()
在策略上下文内构建和编译模型:
with strategy.scope():
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)))
model.add(MaxPooling2D((2, 2)))
# 后续模型层构建...
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
训练时,使用常规的 fit
方法:
model.fit(x_train, y_train, epochs=10, batch_size=32)
这里,MirroredStrategy
会自动将模型参数复制到各个 GPU 上,并在训练过程中协调数据并行计算,确保每个 GPU 处理不同批次数据,最后合并梯度更新模型参数,实现高效的多 GPU 协同训练。
10.2.3 处理多 GPU 训练中的数据并行与模型并行问题
- 数据并行:如上述使用
MirroredStrategy
主要实现的数据并行方式,关键在于确保不同 GPU 上的数据批次划分合理,避免数据加载不均衡。同时,要注意在反向传播时,正确合并来自不同 GPU 的梯度,以保证模型参数的准确更新。通常,Keras 和底层 TensorFlow 框架会自动处理这些细节,但在复杂场景下,如使用自定义训练循环,需要深入了解梯度合并机制,手动实现同步操作。 - 模型并行:模型并行相对复杂,它将模型的不同层或模块分配到不同 GPU 上。例如,一个很深的神经网络,可以将前半部分卷积层放在 GPU 1,后半部分全连接层放在 GPU 2。但这样做可能导致 GPU 之间的数据传输开销增大,因为中间结果需要在 GPU 间传递。在设计模型并行架构时,要权衡计算负载均衡与数据传输成本,优化层的分配策略,确保整体训练效率提升。可以通过分析模型各部分的计算时间和数据依赖关系,合理拆分模型结构,实现高效的模型并行训练。
10.3 分布式训练
10.3.1 分布式训练的概念与应用场景
分布式训练旨在利用多台计算设备(如多个服务器节点、工作站等)协同训练深度学习模型,突破单机计算资源的限制。与多 GPU 训练聚焦于单台机器内的并行不同,分布式训练面向集群环境,可整合大量的 CPU、GPU 资源。
应用场景广泛,在工业界,面对海量的用户数据,如电商平台的用户行为数据、社交媒体的文本图片数据,用于推荐系统、广告投放模型等的训练,单机根本无法处理如此大规模的数据和复杂的模型,分布式训练成为必然选择。在科研领域,探索超大规模神经网络,如用于模拟宇宙演化、蛋白质结构预测的模型,需要分布式训练来加速研究进程,挖掘更深层次的知识。
10.3.2 使用 Keras 进行分布式训练的框架与工具(如 Horovod 等)
Horovod 是一个流行的用于分布式深度学习训练的开源框架,它可以与 Keras 无缝集成。首先,安装 Horovod:
pip install horovod
导入相关模块:
import horovod.keras as hvd
初始化 Horovod 环境:
hvd.init()
配置 GPU 资源(如果有):
config = tf.ConfigProto()
config.gpu_options.visible_devices = hvd.local_rank()
tf.Session(config=config)
在 Horovod 上下文内构建和编译模型:
with hvd.DeltaGradientsOptimizer(optimizer='adam'):
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)))
model.add(MaxPooling2D((2, 2)))
# 后续模型层构建...
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
训练时,使用 Horovod 适配的 fit
方法:
model.fit(x_train, y_train, epochs=10, batch_size=32)
Horovod 通过 Ring AllReduce 算法高效地在集群节点间同步梯度,确保模型参数在分布式环境下的一致更新,实现高效的大规模分布式训练。
10.3.3 案例:在分布式环境中训练大规模深度学习模型
假设我们要训练一个用于医学影像诊断的大型卷积神经网络,数据分布在一个由 10 台服务器组成的集群上,每台服务器配备 4 个 GPU。首先,使用 Horovod 初始化集群环境,确保各节点间通信正常。然后,在每台服务器上,按照上述 Horovod 与 Keras 集成的方法构建模型:
import horovod.keras as hvd
hvd.init()
# 配置 GPU 等资源...
with hvd.DeltaGradientsOptimizer(optimizer='adam'):
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(512, 512, 3)))
model.add(MaxPooling2D((2, 2)))
# 大量卷积、池化、全连接层构建...
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
在数据加载方面,利用分布式文件系统(如 Ceph、GlusterFS 等),确保每台服务器能快速访问到所需的影像数据块。训练时,启动集群训练任务,Horovod 会协调各节点的计算,将数据批次分配到不同服务器的 GPU 上,同步梯度更新,经过数小时甚至数天的训练,得到一个高精度的医学影像诊断模型,能够准确识别病变区域、疾病类型等关键信息,为医疗诊断提供有力支持。
10.4 迁移学习
10.4.1 迁移学习的原理与优势
迁移学习基于一个关键认知:不同但相关领域的任务之间存在可共享的知识与特征。例如,在图像分类中,通用的图像特征如边缘、纹理、形状等,无论是识别自然场景中的物体,还是特定领域如工业产品缺陷检测,这些基础特征提取方式有相似性。其原理是先在一个大规模、通用数据集上训练模型(称为预训练模型),学习到这些通用特征,然后将预训练模型的部分或全部参数迁移到目标任务模型上。
优势明显,对于数据量有限的目标任务,如小众领域的分类问题,从头训练模型容易过拟合,而迁移学习可以借助预训练模型的知识,快速适应目标任务,减少训练时间与数据需求。同时,能提升模型的泛化能力,因为预训练模型已在海量数据上学习到稳健的特征表示,迁移后有助于目标模型应对复杂多变的实际场景。
10.4.2 使用 Keras 中的预训练模型进行迁移学习的方法
Keras 提供了多种预训练模型,如基于 ImageNet 数据集的 VGG16
、ResNet50
等用于图像分类,以及基于大规模文本语料库的预训练词嵌入模型(如 word2vec
、GloVe
等)用于自然语言处理。以 ResNet50
在图像分类迁移学习为例:
from keras.applications.resnet50 import ResNet50
from keras.layers import GlobalAveragePooling2D, Dense
from keras.models import Model
# 加载预训练的 ResNet50 模型,不包括顶层用于 ImageNet 分类的全连接层
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
# 冻结预训练模型的底层权重,使其在初始训练阶段不更新
for layer in base_model.layers:
layer.trainable = False
# 添加自定义顶层,用于目标任务分类
x = base_model.output
x = GlobalAveragePooling2D()(x)
predictions = Dense(10, activation='softmax')(x) # 假设目标任务有 10 个类别
model = Model(inputs=base_model.input, outputs=predictions)
# 编译并训练模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_val, y_val))
这里先加载预训练的 ResNet50
,冻结底层权重,再构建适配目标任务的顶层,利用预训练模型提取的通用特征,快速训练出针对特定图像分类任务的模型。
10.4.3 案例:在图像分类、自然语言处理等任务中的迁移学习实践
- 图像分类:在花卉识别任务中,数据量相对较小,难以从头训练出高精度模型。利用
VGG16
预训练模型,加载其权重,去除顶层,根据花卉的类别数(如 5 种花卉对应 5 个类别)构建新的顶层全连接层,冻结VGG16
大部分底层卷积层权重,经过少量数据训练,模型就能在花卉识别上达到较高准确率,识别出玫瑰、百合等不同花卉品种。 - 自然语言处理:在情感分析任务中,基于大规模文本语料训练的预训练词嵌入模型(如
word2vec
)可以为文本提供初始的语义表示。首先将文本分词,然后将每个单词映射为对应的词向量,输入到一个简单的循环神经网络(如LSTM
)或卷积神经网络中。通过迁移词嵌入模型学到的语义知识,即使情感分析训练数据有限,模型也能快速捕捉文本的情感倾向,判断文本是积极、消极还是中性,提升情感分析的准确性与效率。
10.5 生成对抗网络(GANs)
10.5.1 GANs 的基本原理与架构
GANs 由生成器(Generator)和判别器(Discriminator)两个核心组件构成。生成器的目标是学习真实数据的分布,生成尽可能逼真的假数据;判别器则负责区分输入的数据是来自真实数据集还是由生成器生成的假数据。
在训练过程中,两者进行对抗博弈。生成器生成一批假数据,与真实数据一同送入判别器,判别器输出判断结果(真或假),并根据判别误差反向传播更新自身权重,以提高判别准确率。同时,生成器依据判别器的反馈,反向传播更新自身权重,使得生成的假数据更能骗过判别器。如此反复迭代,生成器不断提升生成假数据的逼真度,判别器不断增强判别能力,最终达到一个动态平衡,生成器能够生成与真实数据极为相似的数据。
10.5.2 在 Keras 中构建和训练简单 GANs 的方法
以生成手写数字图像为例,首先构建判别器:
from keras.models import Sequential
from keras.layers import Conv2D, LeakyReLU, Flatten, Dense
discriminator = Sequential()
discriminator.add(Conv2D(64, (3, 3), strides=(2, 2), padding='same', input_shape=(28, 28, 1)))
discriminator.add(LeakyReLU(alpha=0.2))
discriminator.add(Conv2D(128, (3, 3), strides=(2, 2), padding='same'))
discriminator.add(LeakyReLU(alpha=0.2))
discriminator.add(Flatten())
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
接着构建生成器:
from keras.models import Sequential
from keras.layers import Dense, Reshape, Conv2DTranspose, Activation
generator = Sequential()
generator.add(Dense(128, activation='relu', input_dim=100))
generator.add(Dense(7 * 7 * 128, activation='relu'))
generator.add(Reshape((7, 7, 128)))
generator.add(Conv2DTranspose(64, (3, 3), strides=(2, 2), padding='same'))
generator.add(Activation('relu'))
generator.add(Conv2DTranspose(1, (3, 3), strides=(2, 2), padding='same'))
generator.add(Activation('tanh'))
接下来,将生成器和判别器组合成 GAN 模型。在训练 GAN 时,需要先固定判别器的权重,让生成器学习如何欺骗判别器。
from keras.models import Model
from keras.optimizers import Adam
# 固定判别器的权重
discriminator.trainable = False
# 定义 GAN 模型的输入
gan_input = Input(shape=(100,))
# 通过生成器生成图像
x = generator(gan_input)
# 将生成的图像输入判别器
gan_output = discriminator(x)
# 构建 GAN 模型
gan = Model(gan_input, gan_output)
gan.compile(optimizer=Adam(lr=0.0002, beta_1=0.5), loss='binary_crossentropy')
下面是训练 GAN 的主循环:
import numpy as np
from keras.datasets import mnist
from tqdm import tqdm
# 加载 MNIST 数据集
(x_train, _), (_, _) = mnist.load_data()
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32')
x_train = (x_train - 127.5) / 127.5 # 归一化到 [-1, 1]
batch_size = 32
epochs = 100
for epoch in tqdm(range(epochs)):
for _ in range(len(x_train) // batch_size):
# 生成随机噪声作为生成器的输入
noise = np.random.normal(0, 1, (batch_size, 100))
# 生成假图像
generated_images = generator.predict(noise)
# 随机选择一批真实图像
real_images = x_train[np.random.randint(0, x_train.shape[0], batch_size)]
# 合并真实图像和假图像
combined_images = np.concatenate([real_images, generated_images])
# 为真实图像和假图像创建标签
labels = np.concatenate([np.ones((batch_size, 1)), np.zeros((batch_size, 1))])
# 训练判别器
discriminator.trainable = True
d_loss = discriminator.train_on_batch(combined_images, labels)
# 生成新的随机噪声
noise = np.random.normal(0, 1, (batch_size, 100))
# 为生成器的训练创建标签(全部标记为真实)
misleading_targets = np.ones((batch_size, 1))
# 固定判别器的权重,训练生成器
discriminator.trainable = False
g_loss = gan.train_on_batch(noise, misleading_targets)
print(f'Epoch {epoch + 1}/{epochs} - D Loss: {d_loss[0]:.4f}, G Loss: {g_loss:.4f}')
10.5.3 案例:使用 GANs 生成手写数字图像
在上述代码中,我们已经完成了一个简单的 GAN 模型的构建和训练,用于生成手写数字图像。下面我们可以进一步可视化生成的图像,看看训练的效果。
import matplotlib.pyplot as plt
# 生成一些随机噪声
noise = np.random.normal(0, 1, (16, 100))
# 使用训练好的生成器生成图像
generated_images = generator.predict(noise)
# 反归一化图像
generated_images = (generated_images + 1) / 2.0
# 可视化生成的图像
plt.figure(figsize=(4, 4))
for i in range(generated_images.shape[0]):
plt.subplot(4, 4, i + 1)
plt.imshow(generated_images[i].reshape(28, 28), cmap='gray')
plt.axis('off')
plt.show()
在这个案例中,我们使用 MNIST 手写数字数据集训练了一个 GAN 模型。生成器学习到了手写数字的分布,能够生成看起来像真实手写数字的图像。通过不断调整 GAN 的架构、超参数(如学习率、批次大小、训练轮数等),可以进一步提高生成图像的质量。
需要注意的是,GAN 的训练过程通常比较不稳定,可能会出现模式崩溃(生成器只生成有限的几种模式)、判别器和生成器的性能不平衡等问题。在实际应用中,可以采用一些技巧来缓解这些问题,例如使用更复杂的架构(如 DCGAN、WGAN 等)、调整学习率策略、添加正则化等。
此外,GANs 在很多领域都有广泛的应用,除了图像生成,还包括图像到图像的转换(如风格迁移、图像修复)、数据增强、视频生成等。随着研究的不断深入,GANs 的性能和应用范围也在不断拓展。