核心知识点梳理
1. GPU 训练核心概念
表格
| 知识点 | 说明 |
|---|---|
| 设备指定(device) | 通过torch.device("cuda" if torch.cuda.is_available() else "cpu")自动选择 GPU/CPU |
.to(device)方法 |
- 张量:返回新的设备上的张量(原张量不变)- 模型:直接修改模型参数的设备(无返回值) |
| 设备一致性要求 | 模型、输入张量、标签张量必须在同一设备,否则报RuntimeError |
| 常见错误原因 | 1. 模型在 GPU,张量在 CPU;2. 部分张量迁移设备,部分未迁移;3. 忘记迁移标签张量 |
2. __call__魔术方法核心概念
表格
| 知识点 | 说明 |
|---|---|
| 核心作用 | 让类的实例可以像函数一样被调用(instance()等价于instance.__call__()) |
| 内部逻辑 | 调用实例时自动执行__call__方法,可结合__init__保存的状态实现灵活逻辑 |
| 典型应用场景 | 1. 自定义模型封装(模拟 nn.Module 的调用逻辑);2. 带状态的函数(如计数器) |
与__init__的区别 |
__init__在实例化时执行(初始化),__call__在实例调用时执行(核心逻辑) |
python
pip install torch torchvision
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
# ==================== 知识点1:GPU训练核心实现 ====================
print("=== 1. GPU训练核心演示 ===")
# 步骤1:检测并指定计算设备
# 自动判断是否有可用GPU,无则用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"当前使用设备:{device}")
# 打印GPU信息(如有)
if torch.cuda.is_available():
print(f"GPU名称:{torch.cuda.get_device_name(0)}")
print(f"GPU数量:{torch.cuda.device_count()}")
# 步骤2:准备示例数据(回归任务)
# 生成模拟数据:y = 2x1 + 3x2 + 4(带噪声)
torch.manual_seed(42) # 固定随机种子
X = torch.randn(1000, 2) # 特征:1000样本,2特征
y = 2*X[:,0] + 3*X[:,1] + 4 + 0.1*torch.randn(1000) # 标签
y = y.unsqueeze(1) # 转为(1000,1)维度
# 步骤3:数据迁移到指定设备(关键!)
# 注意:张量的.to(device)返回新张量,需重新赋值
X = X.to(device)
y = y.to(device)
# 步骤4:定义简单的线性回归模型
class LinearModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(2, 1) # 2输入→1输出
def forward(self, x):
return self.linear(x)
# 步骤5:模型迁移到指定设备(关键!)
model = LinearModel().to(device) # 模型.to(device)直接修改自身
# 步骤6:定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 步骤7:训练模型(确保所有数据/模型在同一设备)
epochs = 50
model.train()
for epoch in range(epochs):
# 前向传播
y_pred = model(X)
loss = criterion(y_pred, y)
# 反向传播+优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 打印进度
if (epoch+1) % 10 == 0:
print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}")
# 验证模型参数(接近2,3,4)
print("\n模型训练后参数:")
for name, param in model.named_parameters():
print(f"{name}: {param.data.cpu().numpy().squeeze()}")
# ==================== 知识点2:常见GPU错误演示 ====================
print("\n=== 2. 常见GPU错误演示 ===")
# 错误场景1:模型在GPU,张量在CPU
model_gpu = LinearModel().to("cuda" if torch.cuda.is_available() else "cpu")
X_cpu = torch.randn(10, 2) # 故意留在CPU
try:
model_gpu(X_cpu)
except RuntimeError as e:
print(f"错误1(设备不一致):{e.args[0][:100]}...")
# 错误场景2:部分张量迁移,部分未迁移
X_gpu = X_cpu.to(device)
y_cpu = torch.randn(10, 1) # 标签留在CPU
try:
y_pred = model_gpu(X_gpu)
loss = criterion(y_pred, y_cpu)
except RuntimeError as e:
print(f"错误2(标签设备不一致):{e.args[0][:100]}...")
# 正确做法:所有张量都迁移
X_gpu = X_cpu.to(device)
y_gpu = y_cpu.to(device)
y_pred = model_gpu(X_gpu)
loss = criterion(y_pred, y_gpu)
print("修复后:计算正常,损失值 =", loss.item())
# ==================== 知识点3:__call__方法核心实现 ====================
print("\n=== 3. __call__方法核心演示 ===")
# 示例1:带状态的计数器(基础用法)
class Counter:
def __init__(self, start=0):
self.count = start # 初始化状态
def __call__(self, step=1):
# 调用实例时执行的逻辑
self.count += step
return self.count
# 使用__call__方法
counter = Counter(start=5)
print(f"初始计数:{counter.count}")
print(f"调用counter():{counter()}") # 等价于counter.__call__()
print(f"调用counter(3):{counter(3)}")
print(f"最终计数:{counter.count}")
# 示例2:模拟nn.Module的__call__逻辑(进阶)
class CustomModel:
def __init__(self, in_dim, out_dim):
# 初始化模型参数(模拟nn.Linear)
self.weight = torch.randn(out_dim, in_dim).to(device)
self.bias = torch.randn(out_dim).to(device)
self.in_dim = in_dim
self.out_dim = out_dim
def forward(self, x):
# 前向传播逻辑(模拟nn.Module的forward)
return x @ self.weight.T + self.bias
def __call__(self, x):
# 调用实例时自动执行:数据校验 + 前向传播
# 1. 数据设备校验
if x.device != self.weight.device:
x = x.to(self.weight.device)
print(f"自动将输入迁移到{self.weight.device}")
# 2. 维度校验
if x.shape[1] != self.in_dim:
raise ValueError(f"输入维度错误!预期{self.in_dim},实际{x.shape[1]}")
# 3. 执行前向传播
return self.forward(x)
# 使用自定义模型(模拟nn.Module的调用方式)
custom_model = CustomModel(in_dim=2, out_dim=1)
test_x = torch.randn(5, 2) # CPU张量
# 调用实例(自动触发__call__)
output = custom_model(test_x)
print(f"\n自定义模型输出:\n{output.cpu().detach().numpy()}")
# 示例3:结合GPU训练的__call__应用
class Trainer:
def __init__(self, model, criterion, optimizer, device):
self.model = model
self.criterion = criterion
self.optimizer = optimizer
self.device = device
self.loss_history = []
def __call__(self, dataloader, epochs=10):
# 调用Trainer实例时执行训练逻辑
self.model.train()
for epoch in range(epochs):
total_loss = 0.0
for X_batch, y_batch in dataloader:
# 确保批次数据在指定设备
X_batch = X_batch.to(self.device)
y_batch = y_batch.to(self.device)
# 训练步骤
y_pred = self.model(X_batch)
loss = self.criterion(y_pred, y_batch)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
total_loss += loss.item() * X_batch.size(0)
avg_loss = total_loss / len(dataloader.dataset)
self.loss_history.append(avg_loss)
if (epoch+1) % 5 == 0:
print(f"Train Epoch [{epoch+1}/{epochs}], Avg Loss: {avg_loss:.4f}")
return self.loss_history
# 使用Trainer(实例像函数一样调用)
dataset = TensorDataset(X, y)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
trainer = Trainer(model, criterion, optimizer, device)
loss_history = trainer(dataloader, epochs=20) # 调用__call__方法
print(f"\n训练损失历史(最后5个):{loss_history[-5:]}")
# ==================== 知识点4:综合实战(GPU+__call__) ====================
print("\n=== 4. 综合实战:GPU训练+自定义__call__模型 ===")
# 定义带__call__的完整MLP模型
class MLP(nn.Module):
def __init__(self, in_dim, hidden_dim, out_dim):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, out_dim)
)
self.to(device) # 初始化时自动迁移到指定设备
def forward(self, x):
return self.layers(x)
def __call__(self, x):
# 重写__call__(增强nn.Module的默认__call__)
# 1. 自动迁移输入到模型设备
if x.device != next(self.parameters()).device:
x = x.to(next(self.parameters()).device)
print(f"自动迁移输入到{next(self.parameters()).device}")
# 2. 执行默认的forward逻辑
return super().__call__(x)
# 初始化MLP并训练
mlp = MLP(in_dim=2, hidden_dim=16, out_dim=1)
mlp_optimizer = optim.Adam(mlp.parameters(), lr=0.001)
mlp_trainer = Trainer(mlp, criterion, mlp_optimizer, device)
# 调用trainer(__call__)训练MLP
mlp_loss = mlp_trainer(dataloader, epochs=15)
print(f"MLP训练完成,最终损失:{mlp_loss[-1]:.4f}")
# 验证MLP的__call__自动设备迁移
test_x_cpu = torch.randn(10, 2) # CPU张量
test_y_pred = mlp(test_x_cpu) # 自动迁移到GPU
print(f"MLP输出设备:{test_y_pred.device}")
print(f"MLP输出形状:{test_y_pred.shape}")
代码关键部分解释
1. GPU 训练核心
- 设备指定 :
torch.device("cuda" if torch.cuda.is_available() else "cpu")是跨平台兼容的标准写法; - 张量迁移 :
X = X.to(device)必须重新赋值(因为张量的.to()返回新对象); - 模型迁移 :
model = LinearModel().to(device)直接修改模型参数的设备(无需重新赋值); - 错误避免 :所有参与计算的张量(输入、标签、中间张量)必须和模型在同一设备,可通过
x.device检查。
2. __call__方法核心
- 基础计数器 :
Counter类通过__call__实现调用时计数,保留self.count状态; - 模拟 nn.Module :
CustomModel的__call__包含数据校验 + 设备自动迁移,模拟 PyTorch 模型的调用逻辑; - Trainer 类 :将训练逻辑封装到
__call__,让训练器实例像函数一样被调用(trainer(dataloader)); - 重写 nn.Module 的__call__ :
MLP类重写__call__,增强默认逻辑(自动设备迁移)。
总结
-
GPU 训练关键:
- 用
.to(device)统一模型和所有张量的设备,张量需重新赋值,模型直接调用; - 设备不一致是最常见错误,可通过
x.device检查,或在__call__中自动迁移; - 跨平台开发时务必用
torch.cuda.is_available()判断 GPU 是否可用。
- 用
-
__call__方法核心:- 让实例像函数一样调用,同时保留内部状态(如模型参数、计数);
- 可增强 PyTorch 模型的默认行为(如自动设备迁移、数据校验);
- 适合封装训练逻辑、自定义模型等场景,让代码更简洁。
-
综合技巧:
- 将设备迁移逻辑封装到
__call__,可避免重复的.to(device)代码; - 训练器类结合
__call__,让训练流程更简洁(trainer(dataloader)); - 所有涉及 GPU 的代码需做好兼容性(CPU/GPU 自动切换)。
- 将设备迁移逻辑封装到