在 Sklearn 中,数据是 Numpy 数组;在 PyTorch 中,一切皆为 Tensor。
概念:多维矩阵,支持 GPU 加速,是深度学习的“血液”。
与 Numpy 对比:为什么不能直接用 Numpy?(自动求导、GPU支持)。
Tensor 可以记住“自己是怎么算出来的”
import torch
a = torch.tensor([1, 2, 3])
b = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
常用参数:
dtype:数据类型
device:cpu / cuda
requires_grad=True
torch.zeros(3, 4)
torch.ones(2, 2)
torch.randn(3, 3) # 正态分布
torch.rand(3, 3) # 均匀分布
查看 Tensor
基本属性
x.shape # torch.Size([2, 3])
x.ndim # 维度数
x.numel() # 元素个数
x.dtype # 数据类型
x.device # 在 CPU 还是 GPU
a + b
a - b
a * b
a / b
a @ b # 矩阵乘
torch.matmul(a, b)
torch.mean(x)
torch.sum(x)
torch.max(x)
torch.min(x)
x.view(3, 2)
x.reshape(3, 2)
x.T
x.transpose(0, 1)
x.permute(1, 0)
x.unsqueeze(0) # 增加维度
x.squeeze() # 删除 size=1 的维度
x[0]
x[:, 1]
x[0, 1]
x[x > 0] # 布尔索引
x_np = x.numpy()
x_t = torch.from_numpy(x_np)
** 共享内存,改一个另一个也会变**
x.float()
x.long()
x.int()
x = x.to("cuda")
x = x.to("cpu")
或者
x = x.cuda()
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward()
print(x.grad)
with torch.no_grad():
y = model(x)
Dataset 管“数据是什么”,DataLoader 管“数据怎么喂给模型”
Dataset → 定义 “一条数据是什么” DataLoader → 定义 “怎么批量、并行、随机加载”
Dataset 是一个“可被索引的数据集合”
你只需要实现 2 个东西
__len__()
__getitem__(index)
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __init__(self, data, labels):
self.data = data
self.labels = labels
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
x = self.data[idx]
y = self.labels[idx]
return x, y
只要能 dataset[i],PyTorch 就能用
class ImageDataset(Dataset):
def __init__(self, img_paths, transform=None):
self.img_paths = img_paths
self.transform = transform
def __getitem__(self, idx):
img = Image.open(self.img_paths[idx])
if self.transform:
img = self.transform(img)
return img
def __len__(self):
return len(self.img_paths)
Dataset 不关心 batch、不关心 shuffle、不关心多进程,它只负责回答一个问题:“第 i 条数据是什么?”
一个把 Dataset 变成“可直接 for 循环训练”的工具
from torch.utils.data import DataLoader
dataloader = DataLoader(
dataset,
batch_size=32,
shuffle=True
)
for epoch in range(epochs):
for x, y in dataloader:
output = model(x)
batch_size
每次喂给模型多少条数据
越大:
越小:
shuffle
是否打乱数据
训练集必须 True
验证 / 测试集一般 False
num_workers
使用多少个子进程加载数据
建议经验值:
CPU 核数 / 2drop_last
丢掉最后一个不满 batch 的数据
常用于:
pin_memory
加快 CPU → GPU 拷贝
collate_fn
Dataset = “第 i 条数据是什么”
DataLoader = “怎么成批喂给模型”
Dataset 只实现
__len__和__getitem__训练集必须
shuffle=TrueGPU 慢,先看
num_workers
Transforms 是对“单条样本”做的可组合预处理操作
from torchvision import transforms
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
transforms.ToTensor()
PIL / ndarray → Tensor
像素值:0~255 → 0~1
维度顺序:HWC → CHW
transforms.Resize(256)
transforms.CenterCrop(224)
transforms.RandomCrop(224)
CenterCrop:验证 / 测试集
RandomCrop:训练集(增强)
transforms.RandomHorizontalFlip(p=0.5)
transforms.RandomRotation(15)
transforms.ColorJitter(
brightness=0.2,
contrast=0.2,
saturation=0.2,
hue=0.1
)
模拟:
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
含义:
x' = (x - mean) / std
训练集:
train_transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
验证 / 测试集:
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
class MyDataset(Dataset):
def __init__(self, transform=None):
self.transform = transform
def __getitem__(self, idx):
img = Image.open(...)
if self.transform:
img = self.transform(img)
return img
nn.Module 是 PyTorch 中“所有模型与层的基类
神经网络 = 一个 nn.Module
网络中的每一层 = 一个 nn.Module
甚至 loss 函数,也常是 nn.Module
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
# 1. 定义层(参数在这里创建)
def forward(self, x):
# 2. 定义前向传播
return x
必须继承 nn.Module
必须调用 super().__init__()
前向逻辑只写在 forward()
nn.Module 的核心能力model.parameters()
返回所有 requires_grad=True 的 Tensor
优化器直接接管
model.train()
model.eval()
影响:
model.to("cuda")
nn.Linear(in_features, out_features)
用途:
图像 / 特征图核心
nn.Conv2d(
in_channels,
out_channels,
kernel_size,
stride=1,
padding=0
)
nn.MaxPool2d(2)
nn.AvgPool2d(2)
作用:
nn.BatchNorm2d(num_features)
nn.LayerNorm(normalized_shape)
区别(直觉版):
nn.Dropout(p=0.5)
训练时随机失活
推理时自动关闭
nn.ReLU()
特点:
计算快
不饱和
大多数任务首选
nn.LeakyReLU(0.01)解决:
ReLU 死亡问题
nn.Sigmoid()
用途:
nn.Tanh()
输出范围:[-1, 1]
比 Sigmoid 好一点,但仍易饱和
nn.Softmax(dim=1)
和 CrossEntropyLoss 不要一起用
模型参数与拟合过程
训练 = 让模型参数一步步调整,使 Loss 变小
输入数据 x
↓
模型 forward(x)
↓
得到预测 ŷ
↓
Loss(ŷ, y) ← 衡量“错得有多离谱”
↓
backward() ← 算参数该怎么改
↓
optimizer.step() ← 真正改参数
Loss 用一个数,量化“模型当前有多差”
nn.MSELoss()
公式直觉:
(预测 - 真实)²
特点:
nn.BCELoss()
⚠️ 要求:
Sigmoid 后的概率nn.BCEWithLogitsLoss()
等价于:
Sigmoid + BCELoss
但:
nn.CrossEntropyLoss()
# 正确示例
logits = model(x) # [B, C]
loss = criterion(logits, y) # y: [B]
nn.CrossEntropyLoss(weight=class_weights)
Optimizer 根据梯度,决定“参数怎么改、改多少”
torch.optim.SGD(
model.parameters(),
lr=0.01,
momentum=0.9
)
特点:
可解释性强
但收敛慢
torch.optim.Adam(
model.parameters(),
lr=1e-3
)
优点:
optimizer.zero_grad() # 1. 清空旧梯度
loss.backward() # 2. 反向传播
optimizer.step() # 3. 更新参数
for x, y in dataloader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()
这是 PyTorch 与 Sklearn 最大的不同。Sklearn 的一行 .fit(),在 PyTorch 中需要展开为以下 5步标准代码块:
images, labels = images.to(device), labels.to(device)outputs = model(images) (模型预测)loss = criterion(outputs, labels) (计算差距)optimizer.zero_grad() (清除上一步的残留)loss.backward() (求导) -> optimizer.step() (更新参数)images, labels = images.to(device), labels.to(device)
把数据搬到和模型同一设备
CPU ↔ GPU 不允许混算
outputs = model(images)
调用 model.forward()
自动构建计算图
中间结果被“记账”
loss = criterion(outputs, labels)
把预测误差压缩成一个标量
Loss 是反向传播的“起点”
optimizer.zero_grad()
PyTorch 的梯度是 累加的
不清零 → 梯度叠加 → 参数乱飞
loss.backward()
optimizer.step()
loss.backward()
param.gradoptimizer.step()
model.train() # 训练模式
for epoch in range(num_epochs):
for images, labels in dataloader:
# 1. 数据传输
images = images.to(device)
labels = labels.to(device)
# 2. 梯度清零
optimizer.zero_grad()
# 3. 前向传播
outputs = model(images)
# 4. 计算损失
loss = criterion(outputs, labels)
# 5. 反向传播 & 更新
loss.backward()
optimizer.step()
为什么要评估模式?
训练时和推理时,模型行为并不一样
受影响的层主要是:
model.eval()
correct = 0
total = 0
val_loss = 0.0
with torch.no_grad():
for images, labels in val_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item()
preds = outputs.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
accuracy = correct / total
preds = outputs.argmax(dim=1)
acc = (preds == labels).float().mean()
适合:
Precision:预测为正的有多准
Recall:正样本找回了多少
F1:二者平衡
from sklearn.metrics import classification_report
torch.save(model.state_dict(), "model.pth")
torch.save(model, "model.pth")
model = MyModel()
model.load_state_dict(torch.load("model.pth"))
model.to(device)
model.eval()
torch.save({
"epoch": epoch,
"model": model.state_dict(),
"optimizer": optimizer.state_dict(),
"loss": loss
}, "checkpoint.pth")
ckpt = torch.load("checkpoint.pth")
model.load_state_dict(ckpt["model"])
optimizer.load_state_dict(ckpt["optimizer"])
start_epoch = ckpt["epoch"] + 1
# =========================
# PyTorch 线性回归(完整示例)
# =========================
import torch
import torch.nn as nn
import torch.optim as optim
# -------------------------
# 1. 构造训练数据
# -------------------------
# 假设真实关系是:y = 2x + 1
# x: 输入特征,形状是 [样本数, 特征数]
x = torch.tensor([[1.0],
[2.0],
[3.0],
[4.0]])
# y: 真实标签
y = torch.tensor([[3.0],
[5.0],
[7.0],
[9.0]])
# -------------------------
# 2. 定义线性回归模型
# -------------------------
# nn.Linear 本质就是 y = x * w + b
# in_features=1 -> 每个样本 1 个特征
# out_features=1 -> 输出 1 个值
model = nn.Linear(in_features=1, out_features=1)
# -------------------------
# 3. 定义损失函数
# -------------------------
# 均方误差(Mean Squared Error)
# loss = mean((y_pred - y_true)^2)
criterion = nn.MSELoss()
# -------------------------
# 4. 定义优化器
# -------------------------
# 使用随机梯度下降(SGD)
# model.parameters() 会返回 w 和 b
# lr 是学习率,控制每一步走多大
optimizer = optim.SGD(model.parameters(), lr=0.01)
# -------------------------
# 5. 训练模型
# -------------------------
epochs = 1000
for epoch in range(epochs):
# (1)前向传播
# 把 x 喂给模型,得到预测值 y_pred
y_pred = model(x)
# (2)计算损失
# 衡量预测值和真实值之间的差距
loss = criterion(y_pred, y)
# (3)梯度清零
# PyTorch 的梯度是累加的,所以每轮都要清零
optimizer.zero_grad()
# (4)反向传播
# 自动计算 loss 对 w 和 b 的梯度
loss.backward()
# (5)更新参数
# 根据梯度和学习率更新 w 和 b
optimizer.step()
# 每 100 轮打印一次损失
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss = {loss.item():.6f}")
# -------------------------
# 6. 查看训练后的参数
# -------------------------
# model.parameters() 中依次是 w 和 b
w, b = model.parameters()
print("\n训练完成后的参数:")
print("w =", w.item())
print("b =", b.item())
# -------------------------
# 7. 使用模型进行预测
# -------------------------
# 用训练好的模型预测一个新值
x_test = torch.tensor([[5.0]])
y_test_pred = model(x_test)
print("\n预测结果:")
print(f"当 x = 5 时,预测 y = {y_test_pred.item():.2f}")
# =========================
# PyTorch 逻辑回归(完整示例)
# =========================
import torch
import torch.nn as nn
import torch.optim as optim
# -------------------------
# 1. 构造训练数据
# -------------------------
# 二分类数据:输入 x, 标签 y
# 我们用一个简单规则:y = 1 if x > 2 else 0
x = torch.tensor([[1.0],
[2.0],
[3.0],
[4.0]])
y = torch.tensor([[0.0],
[0.0],
[1.0],
[1.0]])
# -------------------------
# 2. 定义逻辑回归模型
# -------------------------
# 逻辑回归本质是:
# y_pred = sigmoid(x * w + b)
class LogisticRegression(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(1, 1) # 1 个特征 -> 1 个输出
def forward(self, x):
# sigmoid 激活函数把输出映射到 [0,1],表示概率
return torch.sigmoid(self.linear(x))
model = LogisticRegression()
# -------------------------
# 3. 定义损失函数
# -------------------------
# 二分类任务用二元交叉熵损失
criterion = nn.BCELoss() # Binary Cross Entropy Loss
# -------------------------
# 4. 定义优化器
# -------------------------
optimizer = optim.SGD(model.parameters(), lr=0.1)
# -------------------------
# 5. 训练模型
# -------------------------
epochs = 1000
for epoch in range(epochs):
# 前向传播
y_pred = model(x)
# 计算损失
loss = criterion(y_pred, y)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss = {loss.item():.6f}")
# -------------------------
# 6. 查看训练后的参数
# -------------------------
w, b = model.linear.parameters()
print("\n训练完成后的参数:")
print("w =", w.item())
print("b =", b.item())
# -------------------------
# 7. 使用模型进行预测
# -------------------------
# 输出的是概率
x_test = torch.tensor([[1.5],
[2.5],
[3.5]])
y_test_prob = model(x_test)
# 转换为 0 或 1 分类
y_test_pred = (y_test_prob >= 0.5).float()
print("\n预测结果:")
for xi, yi_prob, yi_pred in zip(x_test, y_test_prob, y_test_pred):
print(f"x={xi.item():.1f}, 概率={yi_prob.item():.3f}, 预测类别={int(yi_pred.item())}")
# =========================
# PyTorch 简单神经网络 MNIST 手写数字预测
# =========================
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# -------------------------
# 1. 数据预处理 & 加载
# -------------------------
# MNIST 图片大小 28x28,灰度图
# 转换为 Tensor 并归一化到 [0,1]
transform = transforms.Compose([
transforms.ToTensor()
])
# 下载训练集和测试集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
# DataLoader 批量读取
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
# -------------------------
# 2. 定义神经网络模型
# -------------------------
# 简单的全连接神经网络:
# 输入层 28*28=784 -> 隐藏层 128 -> 输出层 10
class SimpleNN(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(28*28, 128) # 输入层 -> 隐藏层
self.relu = nn.ReLU() # 激活函数
self.fc2 = nn.Linear(128, 10) # 隐藏层 -> 输出层
def forward(self, x):
x = x.view(x.size(0), -1) # 展平图片 [batch, 28*28]
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x) # 输出 logits
return x
model = SimpleNN()
# -------------------------
# 3. 定义损失函数 & 优化器
# -------------------------
# 多分类问题用 CrossEntropyLoss(内部自带 softmax)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# -------------------------
# 4. 训练模型
# -------------------------
epochs = 5
for epoch in range(epochs):
model.train() # 训练模式
running_loss = 0.0
for images, labels in train_loader:
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
running_loss += loss.item()
print(f"Epoch [{epoch+1}/{epochs}], Loss: {running_loss/len(train_loader):.4f}")
# -------------------------
# 5. 测试模型准确率
# -------------------------
model.eval() # 测试模式
correct = 0
total = 0
with torch.no_grad(): # 测试时不计算梯度
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1) # 取最大值的索引作为预测类别
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f"\n测试集准确率: {100 * correct / total:.2f}%")
# -------------------------
# 6. 使用模型预测单张图片
# -------------------------
import matplotlib.pyplot as plt
# 随机取一张测试图片
image, label = test_dataset[0]
plt.imshow(image.squeeze(), cmap='gray')
plt.title(f"真实标签: {label}")
plt.show()
# 模型预测
model.eval()
with torch.no_grad():
output = model(image.unsqueeze(0)) # 增加 batch 维度
pred = torch.argmax(output, dim=1).item()
print(f"模型预测: {pred}")