模型训练优化是确保机器学习或深度学习模型在训练过程中能够以高效、稳定和最优的方式学习数据特征,以取得更好性能的一系列技术和方法。
8.1.1 底层优化
在深度学习中,底层优化指的是对模型训练过程中的基本组成部分,如优化算法、激活函数、权重初始化等进行优化,以提高训练速度、稳定性和模型性能。底层优化涉及许多细节,通过调整这些方面,可以显著提升模型的性能和训练效率。在实际应用中,需要根据问题的特点进行选择和调整,不断进行实验和优化。
在TensorFlow中,可以使用各种optimizer(优化)类来直接对问题进行优化,这些optimizer类将会自动计算Tensorflow Graph中的导数。但是,有时候我们想要自己写自己的optimizer,那就不能直接使用TensorFlow中的optimier了,此时可以调用lower-level(底层)函数来实现。在现实应用中,可以在compile()中不使用损失函数,而是想自定义设置train_step()和指标。例如下面是一个使用TensorFlow实现的底层优化实例,配置了一个简易的compile()优化器:
(1)首先创建Metric实例来跟踪损失和MAE得分。
(2)创建一个自定义的train_step(),用于更新这些指标的状态(通过调用update_state()实现),然后通过result()查询当前指标的平均值,通过进度条显示结果并将结果过传递给任何回调。
实例8-1:一个TensorFlow底层优化的例子(源码路径:daima/8/fit02.py)
实例文件fit02.py的具体实现代码如下所示。
import tensorflow as tf
from tensorflow import keras
import numpy as np
loss_tracker = keras.metrics.Mean(name="loss")
mae_metric = keras.metrics.MeanAbsoluteError(name="mae")
class CustomModel(keras.Model):
def train_step(self, data):
x, y = data
with tf.GradientTape() as tape:
y_pred = self(x, training=True) #向前传递
# 计算损失
loss = keras.losses.mean_squared_error(y, y_pred)
# 计算梯度变化函数gradients()
trainable_vars = self.trainable_variables
gradients = tape.gradient(loss, trainable_vars)
#更新权重
self.optimizer.apply_gradients(zip(gradients, trainable_vars))
# 更新指标(包括跟踪损失的指标)
loss_tracker.update_state(loss)
mae_metric.update_state(y, y_pred)
return {"loss": loss_tracker.result(), "mae": mae_metric.result()}
@property
def metrics(self):
# 在这里返回我们的Metric对象,这样就可以在每个epoch开始时或evaluate()开始时自动调用reset_states()
# 如果不实现此属性,则必须调用reset_states()。
return [loss_tracker, mae_metric]
#构造CustomModel的实例
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
# 不传递损失或指标。
model.compile(optimizer="adam")
#使用fit(),也可以使用回调
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.fit(x, y, epochs=5)
在上述代码中,需要使用reset_states()在每个epoch之间重置状态。否则,result()将在练开始后返回平均值,而我们通常使用的是每个时期的平均值。为了解决这个问题,需要通过metrics在模型的属性中列出要重置的任何指标。在上述实例中的模型将会在每个epoch开始fit()时或evaluate()开始时调用reset_states()。在作者电脑中执行后会输出:
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 0.3635 - mae: 0.4892
Epoch 2/5
32/32 [==============================] - 0s 1ms/step - loss: 0.2115 - mae: 0.3722
Epoch 3/5
32/32 [==============================] - 0s 1ms/step - loss: 0.2051 - mae: 0.3649
Epoch 4/5
32/32 [==============================] - 0s 1ms/step - loss: 0.1999 - mae: 0.3605
Epoch 5/5
32/32 [==============================] - 0s 1ms/step - loss: 0.1945 - mae: 0.3556
<tensorflow.python.keras.callbacks.History at 0x7f624c0a66a0>
一个常用的 PyTorch 底层优化例子是使用 GPU 加速模型训练,通过将计算工作从 CPU 移动到 GPU 上,可以显著提高训练速度。例如下面是一个使用 GPU 加速模型训练的例子。
实例8-2:PyTorch使用 GPU 加速模型训练(源码路径:daima/8/pyxun.py)
实例文件pyxun.py的具体实现代码如下所示。
import torch
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的神经网络
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(10, 5)
self.fc2 = nn.Linear(5, 2)
def forward(self, x):
x = self.fc1(x)
x = self.fc2(x)
return x
# 创建模型实例
model = SimpleNet()
# 将模型移动到 GPU 上
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 定义数据和目标
data = torch.randn(16, 10).to(device)
target = torch.randint(0, 2, (16,)).to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 在 GPU 上进行模型训练
for epoch in range(10):
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
print(f"Epoch [{epoch+1}/10], Loss: {loss.item():.4f}")
在上述代码中,模型、数据和目标都被移动到了 GPU 上进行计算。这样可以利用 GPU 的并行计算能力,加速模型训练过程。实际上,只需要在模型创建后使用 .to('cuda') 将模型移动到 GPU 上,数据和目标同样也可以使用 .to('cuda') 进行移动。
注意:为了使用 GPU 进行计算,你的电脑系统必须支持 CUDA,并且需要安装适应于电脑的 GPU 的 CUDA 工具包和 cuDNN 库。在实际应用中,GPU 加速通常可以大幅缩短训练时间,特别是在模型较复杂或数据量较大的情况下。
8.1.2 样本权重和分类权重
1. 类型权重参数class_weight
在分类模型中,我们经常会遇到如下两类问题:
- 误分类的代价很高:比如对合法用户和非法用户进行分类,将非法用户分类为合法用户的代价很高,我们宁愿将合法用户分类为非法用户,这时可以人工再甄别,但是却不愿将非法用户分类为合法用户。这时,我们可以适当提高非法用户的权重class_weight={0:0.9, 1:0.1}。
- 样本是高度失衡的:比如我们有合法用户和非法用户的二元样本数据10000条,里面合法用户有9995条,非法用户只有5条,如果我们不考虑权重,则我们可以将所有的测试集都预测为合法用户,这样预测准确率理论上有99.95%,但是却没有任何意义。
2. 样本权重参数sample_weight
样本不平衡可能会导致我们的模型预测能力下降,在遇到这种情况时,可以通过调节样本权重来尝试解决这个问题。调节样本权重的方法有两种,第一种是在class_weight使用balanced。第二种是在调用fit()函数时,通过sample_weight来自己调节每个样本权重。
在Tensorflow程序中使用方法fit()的时,可以使用样本权重参数sample_weight和分类权重参数class_weight,使用流程如下:
(1)使用sample_weight从参数data中解包;
(2)将class_weight和sample_weight传递给compiled_loss和compiled_metrics。
请看下面的实例,演示了在方法fit()中使用样本权重参数sample_weight的过程。
实例8-3:样本权重参数sample_weight(源码路径:daima/8/fit03.py)
实例文件fit03.py的主要实现代码如下所示。
class CustomModel(keras.Model):
def train_step(self, data):
# 打开data数据包,它的结构取决于模型和传递给fit()的内容。
if len(data) == 3:
x, y, sample_weight = data
else:
x, y = data
with tf.GradientTape() as tape:
y_pred = self(x, training=True) # 向前传递
#计算损失值,在'compile()中配置损失函数`.
loss = self.compiled_loss(
y,
y_pred,
sample_weight=sample_weight,
regularization_losses=self.losses,
)
#计算梯度变化函数gradients()
trainable_vars = self.trainable_variables
gradients = tape.gradient(loss, trainable_vars)
#更新权重
self.optimizer.apply_gradients(zip(gradients, trainable_vars))
# 更新metrics指标
# 在compile()中配置metrics
self.compiled_metrics.update_state(y, y_pred, sample_weight=sample_weight)
# 返回一个dict字典,字典值是metric的名字.
#这些值将包括损失(在自身指标).
return {m.name: m.result() for m in self.metrics}
# 构造并编译CustomModel的实例
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])
# 现在可以使用 样本权重参数sample_weight
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
sw = np.random.random((1000, 1))
model.fit(x, y, sample_weight=sw, epochs=3)
在上述代码中定义了一个自定义的深度学习模型 CustomModel,并重写了其中的 train_step 方法,以实现自定义的训练步骤。下面是对上述代码的具体说明:
- CustomModel 类继承了 keras.Model 类,表示它是一个自定义的模型。
- train_step 方法是重写的训练步骤,在每个训练循环中被调用。它接受一个数据包 data,其中包含输入数据和对应的标签。如果数据包含3个部分,代表输入、标签和样本权重。否则,只有输入和标签两个部分。
- 在 GradientTape 上下文中,对输入数据进行前向传播,得到预测结果 y_pred。
- 使用 compiled_loss 计算损失,这个损失函数在 compile 方法中进行了配置。
- 计算损失相对于可训练变量的梯度。
- 使用优化器更新模型的可训练变量。
- 更新模型的指标(metrics),包括在 compile 方法中配置的指标和损失。
- 返回一个字典,其中包含各个指标的名字及其计算结果。
- 在代码的后半部分,创建一个 CustomModel 实例,并使用 compile 方法配置优化器、损失函数和指标。
- 使用样本权重参数 sample_weight 调用 model.fit 方法进行模型训练。
这段代码演示了如何通过自定义模型的训练步骤,实现对训练过程的精细控制,包括损失函数、梯度计算和模型权重的更新。在作者电脑中执行后会输出:
Epoch 1/3
32/32 [==============================] - 0s 2ms/step - loss: 0.4626 - mae: 0.8329
Epoch 2/3
32/32 [==============================] - 0s 2ms/step - loss: 0.2033 - mae: 0.5283
Epoch 3/3
32/32 [==============================] - 0s 2ms/step - loss: 0.1421 - mae: 0.4378
<tensorflow.python.keras.callbacks.History at 0x7f62401c6198>
在 PyTorch 中,可以使用 torch.utils.data.WeightedRandomSampler 来创建带有样本权重的数据采样器。首先,需要计算每个类别的权重,通常可以使用逆类别出现频率来设置权重。然后,将这些权重传递给采样器,它将在每个批次中按照权重抽取样本。另外,在 PyTorch 中,可以通过在损失函数中使用权重来实现分类权重优化。例如,在交叉熵损失函数中,可以使用参数 weight 来指定不同类别的权重。
当涉及到样本权重和分类权重优化的PyTorch模型训练时,我们可以考虑一个图像分类任务,其中数据集存在类别不平衡,并且某些类别比其他类别更重要。下面是一个完整的例子,演示了如何在训练过程中应用样本权重和分类权重优化。假设我们有一个数据集,其中有3个类别(猫、狗、鸟),其中猫的样本较少,需要应用样本权重来平衡,同时鸟类在任务中被认为更重要,因此需要应用分类权重优化。
实例8-4:PyTorch使用样本权重和分类权重优化训练图像分类模型(源码路径:daima/8/pyquan.py)
实例文件pyquan.py的主要实现代码如下所示。
# 示例数据集类
class CustomDataset(Dataset):
def __init__(self, num_samples):
self.data = torch.randn(num_samples, 3, 32, 32) # 随机生成图像数据,实际应用中填充实际数据
self.targets = torch.randint(0, 3, (num_samples,)) # 随机生成标签,实际应用中填充实际标签
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx], self.targets[idx]
# 构建示例数据集
dataset = CustomDataset(num_samples=1000) # 创建包含1000个样本的数据集
# 计算类别权重
class_weights = [0.5, 1.0, 2.0] # 分别对应猫、狗、鸟的权重
# 创建带有样本权重的采样器
sampler = WeightedRandomSampler(class_weights, num_samples=len(dataset), replacement=True)
# 创建数据加载器
data_loader = DataLoader(dataset, batch_size=32, sampler=sampler)
# 定义模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc = nn.Linear(in_features=3*32*32, out_features=3)
def forward(self, x):
x = x.view(x.size(0), -1)
return self.fc(x)
model = Net()
# 使用带有权重的交叉熵损失函数
criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights))
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
# 训练模型
num_epochs = 10
for epoch in range(num_epochs):
for inputs, labels in data_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
print(f"Epoch [{epoch+1}/{num_epochs}] - Loss: {loss.item():.4f}")
print("Training complete!")
本实例演示了如何使用样本权重和分类权重优化训练一个简单的图像分类模型,注意,本实例没有直接提供完整的数据集和样本,只是创建了一个虚拟的示例数据集和样本,以便可以在你的环境中运行代码并理解如何应用样本权重和分类权重优化。