训练一个网络的步骤以及一些小问题

一、训练一个网络的步骤以及一些小问题

以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)。
  • 评估模式会关闭某些特定的层,如DropoutBatchNorm层,使它们的行为适应于推理,而不是训练。
  • 在评估模式下,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()

  1. 优化器管理参数: 优化器(如torch.optim.SGDtorch.optim.Adam)管理着模型的所有参数,因此它知道哪些参数需要清除梯度。调用optimizer.zero_grad()会自动处理所有这些参数。
  2. 简洁和一致: 使用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)有不同的含义和用途:

  1. a.detach()

    • 这个方法会创建一个新的张量,它是原始张量a的副本,但是这个副本不会参与梯度计算。换句话说,它与计算图分离了,因此不会在反向传播中跟踪梯度。
    • a.detach()通常用于将张量从当前的计算图中分离出来,以防止影响梯度计算。这在你需要固定某些参数或中间结果,但又要继续进行前向计算时非常有用。
  2. a.require_grad_(False)

    • 这个方法会改变原始张量arequires_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张量的主要区别在于它们存储数据的位置和支持的操作类型:

  1. 存储位置

    • CPU张量:存储在系统的主内存中,也就是CPU可以访问的RAM中。
    • GPU张量:存储在GPU的显存中,这通常意味着它们可以利用GPU的并行处理能力来加速计算。
  2. 计算能力

    • CPU张量可以使用CPU进行计算,适合于小规模或不需要并行处理的任务。
    • GPU张量可以利用NVIDIA的CUDA技术进行高速并行计算,适合于大规模数值计算,尤其是在深度学习和科学计算中。
  3. 性能

    • GPU张量在执行矩阵运算和向量运算时通常比CPU张量快得多,因为GPU设计用于处理大量并行任务。
    • CPU张量在执行需要复杂逻辑控制的任务时可能更有优势,因为这些任务不一定能从GPU的并行处理中获益。
  4. 内存大小

    • GPU的显存通常比系统的RAM要小,这意味着非常大的张量可能需要分批处理或使用CPU。
  5. 数据传输

    • 在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上执行。

  • 24
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值