一、训练一个网络的步骤以及一些小问题
以pytorch为例:
1、定义一个网络
# 定义一个简单的神经网络
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(28*28, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = x.view(-1, 28*28) # 展平操作
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
2、超参数设置
# 超参数设置
batch_size = 64
learning_rate = 0.001
num_epochs = 10
3、数据加载和预处理
# 数据加载和预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 可以根据自己的需要自定义dataset,自定义的dataset需继承自data.Dataset,重写__init__、__getitem__、__len__方法
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
4、实例化模型,定义损失函数和优化器
# 实例化模型,定义损失函数和优化器
model = SimpleNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
5、训练
def train():
# 模型进入训练模式,等于model.train(True)
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
for inputs, labels in train_loader:
# 清除之前的梯度
optimizer.zero_grad()
# 前向传播计算模型的预测
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
running_loss += loss.item()
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")
6、验证/评估
def validate():
# 模型进入评估模式,等于model.train(False)
model.eval()
correct = 0
total = 0
with torch.no_grad(): # 在推理时关闭自动梯度计算
for inputs, labels in test_loader:
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy of the model on the test images: {100 * correct / total} %')
二、一些小问题
想调整某个项目的代码,遇到的一些小问题,查了查GPT和kimi。
1、model.eval()
VS. with torch.no_grad()
self.model.train(False)
/model.eval()
:
- 这个方法将模型设置为评估模式(evaluation mode)。
- 评估模式会关闭某些特定的层,如
Dropout
和BatchNorm
层,使它们的行为适应于推理,而不是训练。 - 在评估模式下,
Dropout
层会停止随机丢弃神经元,BatchNorm
层会使用全局统计数据而不是批次统计数据。
with torch.no_grad():
:
- 这个上下文管理器会关闭自动梯度计算(autograd)。
- 关闭梯度计算可以减少内存使用,提高计算效率,因为不需要为每个操作构建计算图。
- 在推理(inference)或验证(validation)过程中使用
torch.no_grad()
可以避免不必要的内存占用和计算,尤其是在大型模型上。
2、model.zero_grad()
VS optimizer.zero_grad()
在深度学习中,优化器(Optimizer)是用于调整神经网络的权重和偏置参数的算法。loss.backward()
计算损失函数相对于模型参数的梯度,然后 optimizer.step()
根据这些梯度更新模型参数,使得损失函数的值逐渐减小。
# Tres
self.paras = [{'params': self.net.parameters(), 'lr': self.lr}]
# solver不是常见的命名惯例,优化器通常被命名为optimizer或者opt。
self.solver = torch.optim.Adam(self.paras, weight_decay=self.weight_decay)
# 在train()中
self.net.zero_grad()
# 另一个模型
params = self.model.parameters()
self.optimizer = torch.optim.AdamW(params, config.lr, weight_decay=1e-4)
# 在train()中
self.optimizer.zero_grad()
在PyTorch中,清除梯度通常是通过调用optimizer.zero_grad()
方法来实现的,而不是通过直接调用模型的zero_grad()
方法。这是因为optimizer.zero_grad()
会遍历优化器所管理的所有参数并将它们的梯度置零,这种方式更常用和推荐。
为什么使用optimizer.zero_grad()
- 优化器管理参数: 优化器(如
torch.optim.SGD
或torch.optim.Adam
)管理着模型的所有参数,因此它知道哪些参数需要清除梯度。调用optimizer.zero_grad()
会自动处理所有这些参数。 - 简洁和一致: 使用
optimizer.zero_grad()
可以确保所有被优化的参数梯度都被正确清除,这样的代码更简洁和一致,不容易遗漏某些参数。
使用model.zero_grad()
的情况
在一些特殊情况下,例如有多个优化器分别处理不同的参数组,可能会使用model.zero_grad()
。但是这不是常见的做法,并且需要确保确实需要这种粒度的控制。
3、loss有必要加载到cuda上吗?
self.l1_loss = torch.nn.L1Loss().cuda()
在 PyTorch 中,损失函数(如 nn.MSELoss
)不需要显式地加载到 CUDA 上。原因是损失函数只是一个操作符,当你在计算损失时,输入的张量会自动在它们所在的设备上执行计算。也就是说,如果输入张量在 GPU 上,那么损失函数的计算也会在 GPU 上进行;如果输入张量在 CPU 上,那么损失函数的计算就会在 CPU 上进行。
只需要确保模型和输入数据在同一个设备上(例如 GPU),损失计算就会自动在该设备上进行。
4、loss、标签、预测值的展示及保存
1、loss
# 获得loss的python标量(标量Scalar是一个不可分割的单一数值),方便后续的打印和记录
epoch_loss.append(loss.item())
2、标签及预测值
for t in range(self.epochs):
epoch_loss = []
pred_scores = []
gt_scores = []
for img, label in self.train_data:
img = img.detach().cuda()
label = label.detach().cuda()
pred = model_target(paras['target_in_vec'])
pred_scores = pred_scores + pred.cpu().tolist()
gt_scores = gt_scores + label.cpu().tolist()
pred.cpu().tolist()
这个操作实际上是将张量 pred
从 GPU 上移动到 CPU 上,并将其转换为一个 Python 列表。这个操作不会影响原始的 pred
张量,而是创建了一个新的 Python 列表,其中包含了 pred
张量的值。因此,如果你后续对 pred
张量进行操作,不会受到 pred.cpu().tolist()
的影响。
5、detach()和require_grad(False)
在PyTorch中,a.detach()
和a.require_grad_(False)
有不同的含义和用途:
-
a.detach()
:- 这个方法会创建一个新的张量,它是原始张量
a
的副本,但是这个副本不会参与梯度计算。换句话说,它与计算图分离了,因此不会在反向传播中跟踪梯度。 a.detach()
通常用于将张量从当前的计算图中分离出来,以防止影响梯度计算。这在你需要固定某些参数或中间结果,但又要继续进行前向计算时非常有用。
- 这个方法会创建一个新的张量,它是原始张量
-
a.require_grad_(False)
:- 这个方法会改变原始张量
a
的requires_grad
属性为False
。这意味着在反向传播时,PyTorch不会为这个张量计算梯度。 - 如果一个张量的
requires_grad
属性为True
,那么在反向传播时,PyTorch会计算它的梯度。通过将requires_grad
设置为False
,你可以告诉PyTorch忽略这个张量的梯度计算,这可以节省内存和计算资源。
- 这个方法会改变原始张量
虽然a.detach()
和a.require_grad_(False)
都可以防止梯度计算,但它们的行为有所不同:
a.detach()
返回一个新的张量,而原始张量a
保持不变。a.require_grad_(False)
直接修改原始张量a
的属性。
在某些情况下,这两个操作的效果可能相似,但它们并不完全等价。a.detach()
更适合于你想要保留原始张量a
参与梯度计算的情况,同时创建一个新的不参与梯度计算的张量副本。而a.require_grad_(False)
则直接修改原始张量,使其不再参与梯度计算。
总结来说,a.detach()
和a.require_grad_(False)
都可以防止梯度计算,但它们的作用方式和使用场景略有不同。
6、GPU张量和CPU张量
在PyTorch中,GPU张量和CPU张量的主要区别在于它们存储数据的位置和支持的操作类型:
-
存储位置:
- CPU张量:存储在系统的主内存中,也就是CPU可以访问的RAM中。
- GPU张量:存储在GPU的显存中,这通常意味着它们可以利用GPU的并行处理能力来加速计算。
-
计算能力:
- CPU张量可以使用CPU进行计算,适合于小规模或不需要并行处理的任务。
- GPU张量可以利用NVIDIA的CUDA技术进行高速并行计算,适合于大规模数值计算,尤其是在深度学习和科学计算中。
-
性能:
- GPU张量在执行矩阵运算和向量运算时通常比CPU张量快得多,因为GPU设计用于处理大量并行任务。
- CPU张量在执行需要复杂逻辑控制的任务时可能更有优势,因为这些任务不一定能从GPU的并行处理中获益。
-
内存大小:
- GPU的显存通常比系统的RAM要小,这意味着非常大的张量可能需要分批处理或使用CPU。
-
数据传输:
- 在GPU和CPU之间传输数据需要时间,因此在GPU上进行计算之前,需要将数据从CPU内存传输到GPU显存。
相互转换:
在PyTorch中,可以使用.to()
方法或.cuda()
方法将张量从CPU移动到GPU,反之亦然。
-
将CPU张量移动到GPU:
tensor_cpu = torch.randn(10, 10) tensor_gpu = tensor_cpu.to('cuda') # 或者 tensor_cpu.cuda()
-
将GPU张量移动回CPU:
tensor_cpu_again = tensor_gpu.to('cpu') # 或者 tensor_gpu.cpu()
使用.to()
方法时,你可以指定目标设备类型和设备索引(如果有多个GPU):
tensor_gpu = tensor_cpu.to('cuda:0') # 移动到第一个GPU
请注意,要使用GPU张量,你需要有一个支持CUDA的NVIDIA GPU,并且安装了相应的CUDA工具包和PyTorch版本。此外,不是所有的PyTorch操作都支持GPU加速,有些操作可能需要在CPU上执行。