Showing Posts From
PyTorch
PyTorch 读取 CSV 数据:pandas 转 Tensor、自定义 Dataset 与 DataLoader
快速读取(小数据) 先用 pandas 读 CSV,再一次性转成 Tensor: import pandas as pd import torchdf = pd.read_csv("house.csv")# 前 N-1 列是特征,最后一列是标签 x = torch.tensor(df.iloc[:, :-1].values, dtype=torch.float32) y = torch.tensor(df.iloc[:, -1].values, dtype=torch.float32)print(x.shape, y.shape) # torch.Size([N, features]) torch.Size([N])iloc[:, :-1] 取除最后列之外的所有列,iloc[:, -1] 取最后一列。 自定义 Dataset(正式训练推荐) 当数据量大或需要在线增强时,继承 Dataset 类实现懒加载: import pandas as pd import torch from torch.utils.data import Datasetclass CSVDataset(Dataset): def __init__(self, path): self.data = pd.read_csv(path) def __len__(self): return len(self.data) def __getitem__(self, index): row = self.data.iloc[index] x = torch.tensor(row[:-1].values, dtype=torch.float32) y = torch.tensor(row[-1], dtype=torch.float32) return x, y必须实现三个方法:__init__:加载/读取数据 __len__:返回样本总数 __getitem__:返回第 index 个样本DataLoader 分批读取 from torch.utils.data import DataLoaderdataset = CSVDataset("house.csv")loader = DataLoader( dataset, batch_size=32, shuffle=True, num_workers=4 # 多进程预取,Windows 下注意加 if __name__ == "__main__" )for x_batch, y_batch in loader: print(x_batch.shape) # [32, features] print(y_batch.shape) # [32]DataLoader 自动处理:打乱数据(shuffle=True) 按 batch_size 分批 多线程预取(num_workers)完整训练示例 import torch import torch.nn as nn from torch.utils.data import DataLoadermodel = nn.Sequential( nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 1) )optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.MSELoss()dataset = CSVDataset("house.csv") loader = DataLoader(dataset, batch_size=8, shuffle=True)for epoch in range(100): for x, y in loader: pred = model(x).squeeze() loss = criterion(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 == 0: print(f"Epoch {epoch}, Loss: {loss.item():.4f}")训练集与验证集分割 from torch.utils.data import random_splitdataset = CSVDataset("house.csv")train_size = int(0.8 * len(dataset)) val_size = len(dataset) - train_sizetrain_set, val_set = random_split(dataset, [train_size, val_size])train_loader = DataLoader(train_set, batch_size=32, shuffle=True) val_loader = DataLoader(val_set, batch_size=32)处理标签类型 回归任务用 float32,分类任务用 long: # 回归:y 是连续值 y = torch.tensor(row[-1], dtype=torch.float32)# 分类:y 是类别编号 y = torch.tensor(int(row[-1]), dtype=torch.long)分类时对应的 loss 是 nn.CrossEntropyLoss(),回归时用 nn.MSELoss()。
波士顿房价预测是回归还是分类?多元 vs 多输出的区别
有人问:"波士顿房价预测(Boston Housing)是多分类问题吧?" 不是。这是回归问题(Regression)。顺带把三个常混的概念都拆一下——多元回归、多输出回归、多分类。 波士顿房价预测的本质 数据集大致:CRIM RM LSTAT PTRATIO ... MEDV(房价,单位万美元)0.00632 6.575 4.98 15.3 ... 24.00.02731 6.421 9.14 17.8 ... 21.60.03237 6.998 2.94 18.7 ... 33.4输入:13 个特征 输出:MEDV = 房价,一个连续值(可以是 18.3、24.7、33.4...)输出是连续数字 → 回归,不是分类。 什么时候会变成分类 自己把房价切段: 0 – 15 → "低价" 15 – 30 → "中价" 30+ → "高价"这样任务就变成了三分类——但这已经不是原始 Boston Housing 任务了,是你人为改造的。原任务永远是回归。 三个概念区分 1. 多元回归(Multiple Regression) 多个输入特征 → 一个输出。 波士顿房价就是这种:13 个特征 → 1 个房价。 import torch.nn as nnmodel = nn.Sequential( nn.Linear(13, 64), nn.ReLU(), nn.Linear(64, 1), # 输出 1 维 ) loss_fn = nn.MSELoss()"多元"指的是输入的元数(features 多),不是输出多。 2. 多输出回归(Multi-output Regression) 多个输入 → 多个连续输出。 例子:预测未来一栋房子的售价 + 出租金 + 涨幅百分比——3 个连续输出。 model = nn.Sequential( nn.Linear(13, 64), nn.ReLU(), nn.Linear(64, 3), # 输出 3 维 ) loss_fn = nn.MSELoss() # 依然是 MSE,输出是向量3. 多分类(Multi-class Classification) 输入 → 多个类别中选一个。 MNIST 手写数字识别是典型: model = nn.Sequential( nn.Linear(784, 128), nn.ReLU(), nn.Linear(128, 10), # 10 类 ) loss_fn = nn.CrossEntropyLoss() # 内部含 softmax标签是类别编号(整数 0-9),损失是 CrossEntropy。 4. 顺带:多标签分类(Multi-label Classification) 输入 → 多个类别可同时为真。 例子:一张图片可能同时含"猫、狗、汽车"三个标签。 model = nn.Sequential( nn.Linear(784, 128), nn.ReLU(), nn.Linear(128, 3), nn.Sigmoid(), # 每维独立 0-1 ) loss_fn = nn.BCELoss() # 二元交叉熵每维独立判 0 或 1。 四类对照表任务类型 输出个数 输出类型 最后一层 损失 例子多元回归 1 连续 Linear(_, 1) MSELoss Boston Housing 房价多输出回归 K 连续 Linear(_, K) MSELoss 同时预测房价 + 租金二分类 1 0/1 Linear(_, 1)+Sigmoid BCELoss 垃圾邮件识别多分类 K 类别编号 Linear(_, K) CrossEntropyLoss MNIST 数字识别多标签分类 K K 个 0/1 Linear(_, K)+Sigmoid BCELoss 图片多标签Boston Housing 数据集的争议 顺带一提——波士顿房价数据集本身有伦理问题。数据里有个特征 B(黑人人口相关变量)设计上带有种族偏见,2022 年 scikit-learn 明确 deprecated 了这个数据集: # 老代码,会警告 from sklearn.datasets import load_boston# 新推荐 from sklearn.datasets import fetch_california_housing现在教学上一般用 California Housing 或 Ames Housing 数据集替代——同样是回归任务,没有伦理争议、样本更多。 用 California Housing 的替代示例 from sklearn.datasets import fetch_california_housing import torch, torch.nn as nn from torch.utils.data import DataLoader, TensorDatasetdata = fetch_california_housing() X = torch.tensor(data.data, dtype=torch.float32) y = torch.tensor(data.target, dtype=torch.float32).unsqueeze(1)loader = DataLoader(TensorDataset(X, y), batch_size=64, shuffle=True)model = nn.Sequential( nn.Linear(8, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1), ) optim = torch.optim.Adam(model.parameters(), lr=1e-3) loss_fn = nn.MSELoss()for epoch in range(20): for x, target in loader: optim.zero_grad() pred = model(x) loss = loss_fn(pred, target) loss.backward() optim.step() print(f"epoch {epoch}: loss={loss.item():.4f}")跑起来 20 轮 loss 会稳定下降——回归任务的经典流程。 一句话总结 波士顿房价 = 回归任务(输出连续价格),属于"多元回归"(多个输入 → 一个输出),不是"多输出回归"、也不是"多分类"。教学优先用 California Housing 数据集,避开原数据集的伦理争议。
PyTorch 学习路线:从 Tensor 到训练完整模型
学习阶段规划阶段 时间 目标Python 基础 3~5 天 能写循环、函数、类NumPy 基础 2 天 理解矩阵运算PyTorch Tensor 3 天 会张量计算和 GPU 迁移Autograd 自动求导 2 天 理解梯度和 backwardnn.Module 基础 3 天 会定义 MLP训练流程 3 天 会完整训练和评估CNN 5 天 图片分类Transformer 基础 7 天 理解 Attention项目实战 长期 YOLO、PointNet 等约一个月可以达到能读懂主流论文代码的水平。 为什么从线性回归开始 # 目标:给出面积和房间数,预测房价 area = [80, 100, 120, 150] rooms = [2, 3, 3, 4] price = [120, 180, 220, 300]神经网络就是找一个函数 f,使得 f(area, rooms) ≈ price。PyTorch 的作用是:定义 f 的结构(model) 自动计算 f 的梯度(autograd) 按梯度更新参数,让预测更准确(optimizer)核心训练循环 import torch import torch.nn as nn# 1. 数据 x = torch.tensor([[80, 2], [100, 3], [120, 3], [150, 4]], dtype=torch.float32) y = torch.tensor([120, 180, 220, 300], dtype=torch.float32)# 2. 定义模型 model = nn.Sequential( nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 1) )# 3. 定义 loss 和 optimizer criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)# 4. 训练循环 for epoch in range(200): pred = model(x).squeeze() # 前向传播 loss = criterion(pred, y) # 计算 loss optimizer.zero_grad() # 清空上一步的梯度 loss.backward() # 反向传播,计算梯度 optimizer.step() # 按梯度更新参数 if epoch % 50 == 0: print(f"Epoch {epoch}: loss = {loss.item():.2f}")四步缺一不可:zero_grad() → 清除旧梯度(PyTorch 默认累积梯度) backward() → 计算所有参数对 loss 的偏导数 step() → 把参数沿梯度方向更新一步Tensor 基础操作 import torcha = torch.tensor([1.0, 2.0, 3.0]) b = torch.zeros(3, 4) c = torch.randn(2, 3) # 标准正态分布# 形状操作 c.shape # torch.Size([2, 3]) c.reshape(3, 2) c.unsqueeze(0) # 增加维度: [1, 2, 3] c.squeeze() # 去掉维度为 1 的维# GPU device = "cuda" if torch.cuda.is_available() else "cpu" c = c.to(device)常用 Loss 函数任务 Loss 调用回归 MSELoss nn.MSELoss()二分类 BCELoss nn.BCEWithLogitsLoss()多分类 CrossEntropy nn.CrossEntropyLoss()常用 Optimizer # Adam:自适应学习率,大多数情况首选 optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)# AdamW:Adam + 权重衰减,Transformer 常用 optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)# SGD:经典,配合 momentum optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)保存和加载模型 # 保存权重 torch.save(model.state_dict(), "model.pth")# 加载权重 model.load_state_dict(torch.load("model.pth")) model.eval() # 推理时关闭 dropout 和 BatchNorm 的训练行为入门建议用 model.state_dict() 而不是直接 torch.save(model, ...),后者会把整个模型类序列化,跨环境加载容易出问题。
PyTorch 点云分割:PointNet 到 Point Transformer 的模型选型
三种任务类型任务 输入 输出 典型场景语义分割 点云 每个点的类别 地面/树木/汽车/行人实例分割 点云 每个点的实例 ID 区分两辆不同车辆部件分割 单个物体点云 零件归属 椅子的腿/靠背/坐垫主流模型 PointNet(最经典) 输入 N×3,直接用 MLP 逐点提取特征,全局池化后再输出分割结果: import torch import torch.nn as nnclass PointNet(nn.Module): def __init__(self, num_classes): super().__init__() self.mlp = nn.Sequential( nn.Linear(3, 64), nn.ReLU(), nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 256) ) self.cls = nn.Linear(256, num_classes) def forward(self, x): # x: [B, N, 3] feature = self.mlp(x) # [B, N, 256] out = self.cls(feature) # [B, N, num_classes] return out预测时取 argmax 得到每个点的标签: pred = out.argmax(dim=-1) # [B, N]其他模型选型模型 核心 适合场景PointNet++ 局部邻域分层学习 室内、激光雷达、工业检测DGCNN KNN 建图 + EdgeConv 精度要求高的分类/分割Point Transformer Transformer Attention SOTA,大型场景MinkowskiNet 稀疏体素 + Sparse CNN 超密点云,速度快数据格式 点云数据通常有三种格式: xyz # 坐标 [x, y, z] xyzrgb # 坐标 + 颜色 [x, y, z, r, g, b] xyz+intensity # 激光雷达:坐标 + 反射强度读入后转 Tensor: import numpy as np import torchpoints = np.load("scan.npy") # (N, 3) x = torch.tensor(points, dtype=torch.float32) # [N, 3] x = x.unsqueeze(0) # [1, N, 3] add batch dimLoss 函数 点云分割本质是逐点分类,直接用 CrossEntropyLoss: criterion = nn.CrossEntropyLoss()# pred: [B, num_classes, N] or reshape to [B*N, num_classes] # label: [B, N]pred_flat = pred.view(-1, num_classes) # [B*N, num_classes] label_flat = label.view(-1) # [B*N]loss = criterion(pred_flat, label_flat)常用公开数据集数据集 场景 用途ShapeNet Part 单物体 部件分割S3DIS 室内 语义分割SemanticKITTI 自动驾驶 激光雷达语义分割ScanNet RGB-D 室内 场景理解工程推荐流程 点云采集 ↓ 预处理(去噪、下采样、法向量估计) ↓ PointNet++ / Point Transformer 训练 ↓ 输出分割结果 ↓ 后处理(聚类、过滤小区域)入门推荐从 PointNet 开始,结构最简单,容易在本地小数据上跑通。确认流程后再换 PointNet++ 提升精度。
PyTorch 训练基础:Tensor、自动求导、训练循环与模型保存
Tensor Tensor 是 PyTorch 的核心数据结构,类似 NumPy 数组但支持 GPU 运算: import torch# 0 维标量 t0 = torch.tensor(3.14)# 1 维向量 t1 = torch.tensor([1.0, 2.0, 3.0])# 2 维矩阵 t2 = torch.zeros(3, 4)# 移到 GPU if torch.cuda.is_available(): t2 = t2.cuda()Dynamic Graph 与 Autograd PyTorch 使用动态计算图(Define-by-Run),每次前向传播都重新构建计算图,便于调试和条件分支。 开启梯度追踪: x = torch.tensor(2.0, requires_grad=True) y = x ** 2 + 3 * xy.backward() # 反向传播 print(x.grad) # dy/dx = 2x + 3 = 7.0标准训练循环 model = MyModel() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loss_fn = torch.nn.CrossEntropyLoss()for epoch in range(100): for x_batch, y_batch in dataloader: pred = model(x_batch) # 1. 前向传播 loss = loss_fn(pred, y_batch) # 2. 计算损失 optimizer.zero_grad() # 3. 清空上一步梯度 loss.backward() # 4. 反向传播 optimizer.step() # 5. 更新参数 print(f"Epoch {epoch}, Loss: {loss.item():.4f}")zero_grad() 必须在 backward() 前调用,否则梯度会累加。 损失函数选择任务 损失函数 激活函数二分类 BCELoss Sigmoid多分类 CrossEntropyLoss 无(内置 Softmax)回归 MSELoss 无# 二分类:Logistic Regression model = torch.nn.Sequential( torch.nn.Linear(10, 1), torch.nn.Sigmoid() ) loss_fn = torch.nn.BCELoss()# 多分类 model = torch.nn.Sequential( torch.nn.Linear(10, 5) # 5 个类别 ) loss_fn = torch.nn.CrossEntropyLoss() # 内部含 Softmax# 回归 model = torch.nn.Linear(10, 1) loss_fn = torch.nn.MSELoss()优化器 # SGD(基础,需手动调 lr) optimizer = torch.optim.SGD(model.parameters(), lr=0.01)# SGD + Momentum(更稳定) optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# Adam(最常用,lr=0.001 通常无需调整) optimizer = torch.optim.Adam(model.parameters(), lr=0.001)# AdamW(Transformer 推荐,带权重衰减修正) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)大多数情况从 Adam + lr=0.001 开始,效果不好再调。 保存与加载模型 推荐:只保存参数(state_dict) # 保存 torch.save(model.state_dict(), "model.pth")# 加载 model = MyModel() model.load_state_dict(torch.load("model.pth")) model.eval() # 切换到推理模式保存完整模型(不推荐,依赖类定义路径): torch.save(model, "model_full.pth") model = torch.load("model_full.pth")导出 ONNX ONNX 格式可在 TensorFlow、ONNX Runtime、TensorRT 等框架中使用: dummy_input = torch.randn(1, 3, 224, 224) # 与实际输入形状一致torch.onnx.export( model, dummy_input, "model.onnx", input_names=["input"], output_names=["output"], opset_version=17 )导出后可用 onnxruntime 推理,速度通常比 PyTorch 原生快。 推理模式 model.eval()with torch.no_grad(): # 禁用梯度计算,节省内存 output = model(input_tensor)推理时必须调 model.eval() 和 torch.no_grad(),否则 BatchNorm 和 Dropout 行为与训练时不同。
torch.autocast 混合精度加速:bfloat16 vs float16 怎么选
torch.autocast 是 PyTorch 混合精度(AMP)的核心 API——把部分算子从 FP32 切到 BF16 / FP16,推理速度和训练吞吐都能翻倍,显存也能降 30-50%。 最简用法 推理: import torchmodel = model.cuda().eval()with torch.no_grad(), torch.autocast("cuda", dtype=torch.bfloat16): output = model(input.cuda())训练: model = model.cuda().train() optimizer = torch.optim.AdamW(model.parameters())for x, y in loader: x, y = x.cuda(), y.cuda() optimizer.zero_grad() with torch.autocast("cuda", dtype=torch.bfloat16): pred = model(x) loss = loss_fn(pred, y) loss.backward() optimizer.step()关键:autocast 只包在 forward + loss 计算里,backward 和 optimizer.step() 在外面。 autocast 到底改了什么 进入 autocast 上下文后,PyTorch 会自动选择每个算子的精度:算子类别 会被降精度Linear / Matmul ✅ 用 BF16/FP16Conv2d / Conv3d ✅ 用 BF16/FP16GEMM 类操作 ✅ 用 BF16/FP16Softmax ❌ 保持 FP32(防溢出)LayerNorm ❌ 保持 FP32Loss ❌ 保持 FP32Reduction ❌ 保持 FP32PyTorch 有个内置白名单/黑名单,你不用手动 .half()、不用改模型结构。 bfloat16 vs float16 BF16(bfloat16):✅ 动态范围和 FP32 一样(8 位 exponent),不会像 FP16 那样容易溢出/下溢 ✅ 不需要 GradScaler(训练时不用缩放梯度) ✅ 稳定性接近 FP32,一般不影响 loss ⚠️ 需要 Ampere 及以上(A100、RTX 30 系及以后)——旧卡不支持 ⚠️ 比 FP16 精度低(7 位 mantissa)FP16(float16):✅ 所有现代 GPU 都支持(Volta 起) ✅ 稍快一点(在某些卡上) ⚠️ 容易 NaN(动态范围窄) ⚠️ 必须配 torch.cuda.amp.GradScaler() ⚠️ 大模型容易训崩该选谁:硬件 训练 推理A100 / H100 BF16 BF16RTX 30 / 40 系 BF16 BF16RTX 20 系 / V100 FP16 + GradScaler FP16GTX 10 系及以下 不支持,用 FP32 不支持没有理由用 FP16 训练时选 BF16——除非你的卡不支持。 训练带 GradScaler(FP16 必须) scaler = torch.cuda.amp.GradScaler()for x, y in loader: optimizer.zero_grad() with torch.autocast("cuda", dtype=torch.float16): pred = model(x) loss = loss_fn(pred, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()GradScaler 把 loss 放大再反传,避免小梯度被 FP16 归零。BF16 用了反而干扰训练,别加。 常见坑 1. 输入没上 GPU with torch.autocast("cuda", ...): output = model(input) # input 在 CPU,报错务必: output = model(input.cuda())2. 和 .half() 混用 别这样: model.half() # 全模型转 FP16 with torch.autocast("cuda", dtype=torch.bfloat16): output = model(input)冲突。要么全模型 .half() 不用 autocast,要么用 autocast 不 .half()。混合精度靠 autocast 就够了。 3. CPU 上 autocast 不等价 with torch.autocast("cpu", dtype=torch.bfloat16):CPU 上 autocast 支持有限,别期望和 GPU 一样效果。CPU 推理想加速走 torch.compile 或 ONNX Runtime。 4. 自定义 op 不支持 BF16 第三方 op / native extension 如果没写 BF16 kernel,autocast 会 fallback 到 FP32——不是报错,只是没加速效果。 5. 推理不用 GradScaler # 推理 with torch.no_grad(), torch.autocast("cuda", dtype=torch.bfloat16): output = model(x)torch.no_grad() 和 autocast 一起用最省显存。 加速效果参考 Transformer 类模型(LLaMA 7B、Qwen 8B 之类)BF16 vs FP32:推理速度:1.8 – 2.3x 显存占用:约 50% 精度损失:几乎无(bf16 动态范围和 fp32 一致)CNN 训练(YOLO 之类):训练吞吐:1.3 – 1.7x 显存节省:30 – 45% mAP:几乎无差一句话总结 Ampere 及以上 GPU 就用 torch.autocast("cuda", dtype=torch.bfloat16),无需 GradScaler、稳定性接近 FP32。老卡 / V100 才考虑 FP16 + GradScaler。别和 model.half() 混用。
