趋近智
tf.distribute.Strategy 概述提升模型复杂性通常会遇到计算瓶颈。训练大型模型需要大量时间与内存资源。缓解这些限制的一种有效方法,特别是在NVIDIA GPU(Volta架构及更高版本)和Google TPU等现代硬件加速器上,就是混合精度训练。
其主要思想是:对计算的某些部分策略性地使用较低精度的浮点数,具体来说是16位浮点数(float16 或半精度),同时将重要部分保持在标准的32位单精度(float32)。这种精度的“混合”旨在实现平衡:获得float16的速度和内存优势,同时保持通常与float32相关的数值稳定性与精度。
标准的深度学习模型主要使用float32来存储权重、激活值和计算梯度。每个float32数字占用32位内存。相比之下,float16仅使用16位。
单精度(float32)与半精度(float16)浮点数的内存占用对比。
然而,这种效率是以牺牲相较于float32更小的数值范围和精度为代价的。float16的较小范围使其在训练期间更容易出现两个主要的数值问题:
Inf)或非数字(NaN)值,破坏训练过程的稳定性。为应对这些数值挑战并使训练成为可能,混合精度通常采用两种主要方法:
维护float32主权重: 虽然层内计算(如矩阵乘法)通常使用float16输入和输出来提高速度,但模型的权重主副本仍保持在float32中。梯度更新,尽管可能使用float16激活值和梯度计算,但会累积到这些float32主权重中。这可以防止由于小的梯度更新被反复直接应用于float16权重而可能发生的精度损失。用于计算的float16权重是在前向传播之前通过对float32主权重进行类型转换生成的。
损失缩放: 为防止梯度在float16范围内下溢(变为零),在反向传播开始之前,损失值会乘以一个大的缩放因子。这会按比例放大所有中间梯度。在优化器将这些梯度应用于float32主权重之前,它们会被取消缩放(除以相同的缩放因子),恢复到原始大小。
Inf或NaN梯度),则减小该因子。这通常更受欢迎,因为它会自动找到接近最优的缩放比例,无需手动调整。混合精度训练的流程,显示了类型转换、计算、损失缩放和权重更新。
TensorFlow提供了一个简单直接的API,通过tf.keras.mixed_precision来启用混合精度训练。最简单的方式是设置全局策略。
import tensorflow as tf
# 检查GPU是否支持Tensor Core
# (NVIDIA GPU计算能力7.0或更高版本)
# TPU本身也支持混合精度。
# 设置全局策略为 'mixed_float16'
# 这会自动为兼容的Keras层启用混合精度
tf.keras.mixed_precision.set_global_policy('mixed_float16')
print(f"Compute dtype: {tf.keras.mixed_precision.global_policy().compute_dtype}")
print(f"Variable dtype: {tf.keras.mixed_precision.global_policy().variable_dtype}")
# 像往常一样构建您的Keras模型
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(28, 28), name='input'),
# Flatten层没有计算密集型操作,数据类型策略对其影响不大
tf.keras.layers.Flatten(),
# 全连接层计算将使用float16,权重保留在float32中
tf.keras.layers.Dense(128, activation='relu', name='dense_1'),
# 输出层可能保持float32以保证数值稳定性,具体取决于设置
# Keras策略会自动处理标准层(如带有softmax的全连接层)的此问题
tf.keras.layers.Dense(10, activation='softmax', name='output')
])
# 检查层的Dtype
dense_layer = model.get_layer('dense_1')
print(f"Dense layer compute dtype: {dense_layer.compute_dtype}")
print(f"Dense layer variable dtype: {dense_layer.variable_dtype}")
# 输出层通常默认为float32计算以保证稳定性,特别是softmax
output_layer = model.get_layer('output')
print(f"Output layer compute dtype: {output_layer.compute_dtype}")
# 编译模型 - 默认优化器会自动处理损失缩放
# 当使用model.fit()与混合精度策略时
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.summary()
# 现在,当您调用model.fit()时,Keras将自动:
# 1. 将输入转换为float16,用于兼容的层。
# 2. 以float16执行计算(例如,矩阵乘法)。
# 3. 将主权重保留在float32中。
# 4. 在梯度计算期间应用动态损失缩放。
# 5. 在将梯度应用于float32权重之前取消其缩放。
当您将全局策略设置为'mixed_float16'时,Keras层会自动调整:
Dense、Conv2D、循环层)将使用float16执行其计算,并期望float16输入。它们的内部变量数据类型(用于权重)仍保持为float32。BatchNormalization或最终Softmax激活等敏感操作的层,即使在混合精度策略下,也可能默认以float32进行计算以确保数值稳定性。这种行为通常是自动且有益的。model.fit()训练循环会自动将优化器封装在tf.keras.mixed_precision.LossScaleOptimizer中,该优化器处理动态损失缩放。如果您正在编写自定义训练循环,则需要手动管理损失缩放。这需要使用tf.keras.mixed_precision.LossScaleOptimizer,它会封装您的常规优化器。您可以使用它在计算梯度之前缩放损失,并在应用梯度之前取消其缩放。
# 自定义混合精度训练循环示例代码
# 假设 'optimizer' 是您的基本优化器(例如 tf.keras.optimizers.Adam)
# 假设 'model' 和 'loss_fn' 已定义
# 策略 'mixed_float16' 必须全局设置
# 封装优化器以进行损失缩放
scaled_optimizer = tf.keras.mixed_precision.LossScaleOptimizer(optimizer)
@tf.function
def train_step(inputs, targets):
with tf.GradientTape() as tape:
predictions = model(inputs, training=True) # 前向传播使用混合精度
# 如有需要,确保损失计算在float32中完成
loss = loss_fn(targets, predictions)
# 缩放损失
scaled_loss = scaled_optimizer.get_scaled_loss(loss)
# 使用缩放后的损失计算梯度
scaled_gradients = tape.gradient(scaled_loss, model.trainable_variables)
# 在应用前取消梯度缩放
gradients = scaled_optimizer.get_unscaled_gradients(scaled_gradients)
# 使用LossScaleOptimizer应用梯度(更新float32权重)
scaled_optimizer.apply_gradients(zip(gradients, model.trainable_variables))
return loss
# 在您的训练循环中:
# for batch_data in dataset:
# inputs, targets = batch_data
# loss_value = train_step(inputs, targets)
# print(f"Step loss: {loss_value.numpy()}")
混合精度训练在以下情况下最有益:
务必验证启用混合精度不会对您特定任务的模型最终精度产生负面影响,尽管在大多数情况下,由于正则化效应,影响微乎其微甚至略有积极作用。请监测训练中损失的NaN值,这可能表示损失缩放或特定操作中存在数值稳定性问题。
总之,混合精度是TensorFlow中一种强大优化手段,它通过运用专用硬件能力,可以大幅减少训练时间与内存使用。它与Keras API的集成使其在许多标准模型架构中都相对容易实现。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造