Pytorch 快速入门:从安装到基本使用
一、开篇:为什么是 PyTorch
一句话点睛:PyTorch = NumPy 一样好写 + 自动求导 + GPU 加速。
PyTorch 的三大优势:
- 动态图 —— 代码怎么写,图就怎么建,支持原生
if/else、for,调试像普通 Python 一样 - 学术主流 —— 顶会论文开源代码 90% 用 PyTorch,复现门槛低
- Python 生态无缝 —— 和 NumPy、Matplotlib、Scikit-learn 无缝集成
本文目标:用 PyTorch 写一个神经网络,拟合 y = x 2 y = x^2 y=x2,把整个流程跑通。
读完本文你能掌握:
- 用
uv5 分钟装好 PyTorch - Tensor 的核心操作
- 4 个最常用的激活函数
nn.Module搭建神经网络的标配套路- 标准训练循环模板(Autograd 部分下一篇再讲)
📝 阅读路线
本文是 PyTorch 系列第 1 篇,注重「能跑通」。下一篇会专门讲自动微分(Autograd)和反向传播——也就是loss.backward()这一行的魔法来自哪里。
二、环境安装:用 uv 5 分钟搞定
2.1 为什么用 uv 而不是 conda?
如果你还在用 conda 装 PyTorch,强烈建议你试试 uv。它是一个用 Rust 写的极速 Python 包管理器,相比 pip/conda:
| 维度 | pip | conda | uv |
|---|---|---|---|
| 安装速度 | 慢 | 很慢 | 极快(10~100x) |
| 磁盘占用 | 中 | 大 | 极小 |
| 锁文件 | 需 pipenv/poetry | 无 | 原生支持 uv.lock |
| Python 版本管理 | 无 | 内置 | 内置 |
装一次就回不去。
2.2 安装 uv
可以参考我的笔记:uv学习笔记
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
irm https://astral.sh/uv/install.ps1 | iex
# 使用 pip 安装
pip install uv
# 验证安装成功
uv --version
2.3 创建项目 + 虚拟环境
uv init pytorch-quickstart
cd pytorch-quickstart
uv venv
# 激活虚拟环境
# macOS / Linux:
source .venv/bin/activate
# Windows:
.venv\Scripts\activate
💡 为什么要虚拟环境?
不同项目用不同版本的 PyTorch(甚至 Python),不隔离就会冲突。uv venv创建的.venv目录是项目本地的,删了就能干净地重装。
2.4 安装 PyTorch
先去 PyTorch 官网 选你的配置,下面对应最常见的两种情况。
CPU 版(新手推荐,先跑通再说):
uv pip install torch torchvision torchaudio
GPU 版(有 NVIDIA 显卡、追求速度):
先看你的 CUDA 驱动版本:
nvidia-smi # 右上角 "CUDA Version: 12.x" 就是支持的最高版本
比如你的驱动支持 CUDA 12.1:
uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
如果想用 uv add 命令去安装的话,你需要把你的 pyproject.toml 文件改为下面的样子,然后直接去 uv add torch torchvision torchaudio。注意,cu126 按照你的gpu可以换成合适的版本,比如我的显卡是 5080,那么 cu126 就会报错,无法使用 gpu 来进行加速,但我换成了 cu130 就不会报错了。
[project]
name = "nlp"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"torch",
"torchvision",
"torchaudio",
]
[[tool.uv.index]]
name = "pytorch-gpu"
url = "https://download.pytorch.org/whl/cu126"
explicit = true # 关键:这确保只有明确指定该索引的包才去这里找
# 强制指定这三个包去 pytorch-gpu 索引找(这步最关键)
[tool.uv.sources]
torch = { index = "pytorch-gpu" }
torchvision = { index = "pytorch-gpu" }
torchaudio = { index = "pytorch-gpu" }
⚠️ CUDA 版本对应关系
装的 PyTorch 自带的 CUDA 不能超过nvidia-smi显示的版本。比如显示 12.4,可以装 cu121、cu124,但不能装 cu118(11.8 低于 12.4 反而没事,但 cu118 时代太久了没必要)。装错版本会报libcudart.so not found。
2.5 验证安装
import torch
print("PyTorch 版本:", torch.__version__)
print("CUDA 是否可用:", torch.cuda.is_available())
if torch.cuda.is_available():
print("当前 GPU:", torch.cuda.get_device_name(0))
💡 国内下载慢?
临时换清华源:uv pip install torch --index-url https://pypi.tuna.tsinghua.edu.cn/simple永久配置可在
~/.config/pip/pip.conf(Linux/Mac)或%APPDATA%\pip\pip.ini(Windows)里加。
三、Tensor:深度学习的「乐高积木」
如果把神经网络比作一栋房子,Tensor 就是砖块。PyTorch 的一切运算,本质上都是 Tensor 之间的操作。
3.1 什么是 Tensor
一句话:Tensor = NumPy ndarray + GPU 支持 + 自动求导支持。
import torch
import numpy as np
# NumPy 数组
a = np.array([1, 2, 3])
# PyTorch 张量
b = torch.tensor([1, 2, 3])
print(type(a), type(b)) # <class 'numpy.ndarray'> <class 'torch.Tensor'>
Tensor 的「维度」从 0 开始数:
| 维度 | 名称 | 例子 | 常见用途 |
|---|---|---|---|
| 0 维 | 标量(scalar) | tensor(3.14) |
损失值 |
| 1 维 | 向量(vector) | tensor([1, 2, 3]) |
偏置、一条样本的特征 |
| 2 维 | 矩阵(matrix) | tensor([[1, 2], [3, 4]]) |
一批样本的特征 |
| 3 维 | 张量 | torch.randn(2, 3, 4) |
序列(batch × seq_len × hidden) |
| 4 维 | 张量 | torch.randn(2, 3, 28, 28) |
图像(batch × channel × H × W) |
3.2 创建 Tensor 的 5 种姿势
import torch
# 姿势 1: 从 Python 列表创建
t1 = torch.tensor([1, 2, 3])
# 姿势 2: 创建全 0 / 全 1 / 未初始化
t2 = torch.zeros(2, 3) # 2x3 全 0
t3 = torch.ones(2, 3) # 2x3 全 1
t4 = torch.empty(2, 3) # 2x3 未初始化(值是内存里残留的垃圾,速度快)
# 姿势 3: 创建等差 / 等距序列
t5 = torch.arange(0, 10, 2) # tensor([0, 2, 4, 6, 8])
t6 = torch.linspace(0, 1, 5) # tensor([0.00, 0.25, 0.50, 0.75, 1.00])
# 姿势 4: 随机初始化
t7 = torch.rand(2, 3) # 均匀分布 [0, 1)
t8 = torch.randn(2, 3) # 标准正态分布 N(0, 1)
# 姿势 5: 从 NumPy 转过来
import numpy as np
arr = np.array([1, 2, 3])
t9 = torch.from_numpy(arr) # 共享内存!改一个另一个也变
⚠️ torch.empty 不是 torch.zeros
empty不初始化数据,速度比zeros快。如果你打算立刻覆盖它的值,用empty性能更好;否则用zeros避免读到垃圾值。
3.3 Tensor 的常用属性
import torch
t = torch.randn(2, 3, 4) # shape: (2, 3, 4)
print(t.shape) # torch.Size([2, 3, 4]) ← 形状
print(t.ndim) # 3 ← 维度数
print(t.numel()) # 24 ← 元素总数
print(t.dtype) # torch.float32 ← 数据类型
print(t.device) # cpu ← 所在设备
print(t.element_size()) # 4 ← 每个元素占多少字节
print(t.itemsize) # 4 ← 等价于 element_size()
print(t.nbytes) # 96 ← 总字节数 = numel * element_size
print(t.requires_grad) # False ← 是否需要梯度
💡 常用属性速查
属性 含义 典型用途 shape/size()形状(每维的大小) 维度检查、reshape 前 ndim/dim()维度数 快速判断几维 tensor numel()/nelement()元素总数 统计参数量、算 FLOPs dtype数据类型 精度控制、类型转换 device所在设备 多卡训练、设备迁移 element_size()单元素字节数 估算显存占用 nbytes总字节数 = numel() * element_size()requires_grad是否开启梯度追踪 冻结参数、推理 is_leaf是否为叶子节点 下一篇 Autograd 会细讲
3.4 索引与切片
下面的所有例子都用这个 3×3 矩阵:
import torch
t1 = torch.tensor([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
print(t1.shape, t1.ndim) # torch.Size([3, 3]) 3
普通索引
用 tensor[行, 列] 取单个元素:
print(t1[0, 0]) # tensor(1) # 第 0 行第 0 列
print(t1[1, 2]) # tensor(6) # 第 1 行第 2 列
print(t1[-1, -1]) # tensor(9) # 负索引从后往前数
范围索引(切片)
用 start:stop:step 切出子矩阵:
print(t1[:2, :2])
# tensor([[1, 2],
# [4, 5]])
print(t1[1:, 1:]) # 右下角 2×2
# tensor([[5, 6],
# [8, 9]])
print(t1[::2, ::2]) # 步长为 2
# tensor([[1, 3],
# [7, 9]])
列表索引(花式索引)
用整数列表同时取多个不相邻的元素——这是和列表最大的区别:
# 配对索引:取 (0,0)、(1,1)、(2,2) 三个对角线元素
print(t1[[0, 1, 2], [0, 1, 2]])
# tensor([1, 5, 9])
💡 花式索引的「配对」规则
上面[[0,1,2], [0,1,2]]不是「先取行再取列」,而是把两个列表逐元素配对成坐标:(0,0)、(1,1)、(2,2),结果是一个 1D 张量。
列表与普通索引混用
列表索引可以和切片/标量混用,维度不同,会触发广播机制。
# 取第 0、1、2 行的「第 0 列」
print(t1[[0, 1, 2], 0])
# tensor([1, 4, 7])
布尔索引
用比较运算生成布尔 mask,再筛选元素:
print(t1 > 5)
# tensor([[False, False, False],
# [False, False, True],
# [ True, True, True]])
print(t1[t1 > 5])
# tensor([6, 7, 8, 9])
布尔索引的杀手级用法:一行就能完成「按条件赋值」:
# 把所有 > 5 的元素都改成 0
t1[t1 > 5] = 0
print(t1)
# tensor([[1, 2, 3],
# [4, 5, 0],
# [0, 0, 0]])
3.6 广播机制
两个形状不同的 tensor 也能做运算——小的那个会被「虚拟」地扩展到匹配大的那个:
a = torch.ones(3, 4) # shape: (3, 4)
b = torch.tensor([10, 20, 30, 40]) # shape: (4,)
c = a + b # 广播后 b 变成 (3, 4)
print(c)
# tensor([[11., 21., 31., 41.],
# [11., 21., 31., 41.],
# [11., 21., 31., 41.]])
广播规则:从最后一个维度往前比对,每个维度要么相等、要么其中一个是 1、要么其中一个不存在。满足就广播,不满足就报错。
3.7 常用数学运算
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
# 逐元素运算
a + b # 加
a * b # 逐元素乘(注意:不是矩阵乘!)
a ** 2 # 平方
# 矩阵乘
A = torch.randn(2, 3)
B = torch.randn(3, 4)
C = A @ B # shape: (2, 4)
C2 = A.matmul(B) # 等价写法
C3 = torch.mm(A, B) # 等价写法(只支持 2D)
# 聚合运算
t = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
t.sum() # 全部求和: tensor(10.)
t.sum(dim=0) # 沿第 0 维求和: tensor([4., 6.])
t.sum(dim=1) # 沿第 1 维求和: tensor([3., 7.])
t.mean() # 平均: tensor(2.5000)
t.mean(dim=0) # 沿第 0 维平均: tensor([2., 3.])
t.max() # 最大值
t.argmax() # 最大值的索引
💡
dim怎么记dim=0沿着「第 0 维的方向压」——把行压掉,留下列的统计。dim=1同理压掉列。dim永远是「被消除的维度」。
3.8 与 NumPy 互转
# Tensor → NumPy
t = torch.ones(3)
arr = t.numpy()
print(type(arr)) # <class 'numpy.ndarray'>
# NumPy → Tensor
arr2 = np.array([1, 2, 3])
t2 = torch.from_numpy(arr2)
⚠️ 共享内存陷阱
torch.from_numpy()和tensor.numpy()转换出来的对象共享底层内存。修改一个,另一个也跟着变!如果要断开共享,用.clone():t2 = torch.from_numpy(arr2).clone() # 独立副本
3.9 搬上 GPU
一行代码搞定:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = torch.rand(2, 3)
x = x.to(device) # ← CPU → GPU
y = torch.rand(2, 3, device=device) # 创建时直接指定
⚠️ GPU 上的 Tensor 不能直接转 NumPy
报错:TypeError: can't convert cuda:0 device type tensor to numpy。对策:先
.cpu()再.numpy(),或先.detach()再转。x.cpu().numpy() # 先搬回 CPU
四、激活函数:给网络注入「非线性」
4.1 为什么需要激活函数?
先想一个问题:没有激活函数,多层神经网络和一层等价吗?
答案是等价。假设每层都是 y = W x + b y = Wx + b y=Wx+b,三层堆起来就是 y = W 3 W 2 W 1 x + … y = W_3 W_2 W_1 x + \dots y=W3W2W1x+…——本质上还是线性变换,表达能力极差。
激活函数就是给每一层「加点弯弯绕绕」,让网络能拟合任意复杂的函数(万能逼近定理)。
4.2 4 个最常用的激活函数
4.2.1 ReLU(隐藏层默认首选)
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
relu = torch.nn.ReLU()
x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
print(relu(x)) # tensor([0., 0., 0., 1., 2.])
特点:
- 计算极快(就是比大小)
- 梯度不会饱和(正区间梯度恒为 1,缓解梯度消失)
- 死亡 ReLU 问题:负区间梯度恒为 0,对应神经元「死掉」再也不更新
💡 ReLU 的小变种
nn.LeakyReLU(0.01):负区间给个小的正斜率nn.PReLU():斜率也是可学习参数nn.GELU():Transformer 标配,曲线更平滑
4.2.2 Sigmoid(二分类输出层)
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
sigmoid = torch.nn.Sigmoid()
print(sigmoid(torch.tensor([0.0]))) # tensor([0.5000])
特点:
- 输出范围 ( 0 , 1 ) (0, 1) (0,1),天然适合表示「概率」
- 严重缺点:两端梯度接近 0,导致梯度消失——深层网络基本不用它做隐藏层
- 隐藏层几乎不用,只在二分类输出层偶尔使用
4.2.3 Tanh(零中心化的 Sigmoid)
tanh ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+e−xex−e−x
tanh = torch.nn.Tanh()
print(tanh(torch.tensor([0.0]))) # tensor([0.])
特点:
- 输出范围 ( − 1 , 1 ) (-1, 1) (−1,1),零中心(比 Sigmoid 训练更稳定)
- 同样存在梯度消失
- RNN 类模型偶尔用,主战场已让给 ReLU
4.2.4 Softmax(多分类输出层)
Softmax ( x i ) = e x i ∑ j e x j \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}} Softmax(xi)=∑jexjexi
softmax = torch.nn.Softmax(dim=1)
logits = torch.tensor([[1.0, 2.0, 3.0]])
print(softmax(logits))
# tensor([[0.0900, 0.2447, 0.6652]])
特点:
- 把一组数归一化成概率分布(和为 1)
- 配
nn.CrossEntropyLoss使用时,模型里不要再加 Softmax(loss 内部自带)
4.3 选型速查表
| 函数 | 公式 | 输出范围 | 适用位置 | 备注 |
|---|---|---|---|---|
| ReLU | max ( 0 , x ) \max(0, x) max(0,x) | [ 0 , + ∞ ) [0, +\infty) [0,+∞) | 隐藏层默认 | 简单、高效 |
| LeakyReLU | max ( 0.01 x , x ) \max(0.01x, x) max(0.01x,x) | ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) | 隐藏层 | 解决死亡 ReLU |
| GELU | x ⋅ Φ ( x ) x \cdot \Phi(x) x⋅Φ(x) | ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) | Transformer 隐藏层 | 平滑、效果好 |
| Sigmoid | 1 1 + e − x \frac{1}{1+e^{-x}} 1+e−x1 | ( 0 , 1 ) (0, 1) (0,1) | 二分类输出层 | 隐藏层禁用 |
| Tanh | e x − e − x e x + e − x \frac{e^x-e^{-x}}{e^x+e^{-x}} ex+e−xex−e−x | ( − 1 , 1 ) (-1, 1) (−1,1) | RNN 隐藏层 | 零中心 |
| Softmax | e x i ∑ e x j \frac{e^{x_i}}{\sum e^{x_j}} ∑exjexi | ( 0 , 1 ) (0, 1) (0,1) 和为 1 | 多分类输出层 | 配 CrossEntropyLoss |
五、搭建神经网络:torch.nn 模块
5.1 nn.Module:所有模型的「祖宗」
PyTorch 里所有神经网络都继承自 nn.Module。它要做的两件事:
- 在
__init__里声明用哪些层 - 在
forward里定义数据怎么流过这些层
import torch
import torch.nn as nn
class MyNet(nn.Module):
def __init__(self):
super().__init__() # ← 必须!初始化父类
self.fc1 = nn.Linear(784, 128) # 输入层 → 隐藏层
self.fc2 = nn.Linear(128, 64) # 隐藏层 → 隐藏层
self.fc3 = nn.Linear(64, 10) # 隐藏层 → 输出层
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x) # 输出层一般不加激活(交给 loss 处理)
return x
model = MyNet()
print(model)
# MyNet(
# (fc1): Linear(in_features=784, out_features=128, bias=True)
# (fc2): Linear(in_features=128, out_features=64, bias=True)
# (fc3): Linear(in_features=64, out_features=10, bias=True)
# (relu): ReLU()
# )
5.2 简单模型用 nn.Sequential
如果你的网络是层与层顺序串接(没有分支、跳跃连接),可以用 nn.Sequential 一行搞定:
model = nn.Sequential(
nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
)
5.3 常用层速查
# 全连接层
nn.Linear(in_features, out_features)
# 2D 卷积(图像)
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0)
# 池化(下采样)
nn.MaxPool2d(kernel_size=2)
nn.AvgPool2d(kernel_size=2)
# 循环神经网络(序列)
nn.RNN(input_size, hidden_size, num_layers=1)
nn.LSTM(input_size, hidden_size, num_layers=1)
nn.GRU(input_size, hidden_size, num_layers=1)
# 正则化
nn.Dropout(p=0.5) # 训练时随机失活,推理时自动关闭
nn.BatchNorm1d(num_features) # 1D 批归一化(全连接后)
nn.BatchNorm2d(num_features) # 2D 批归一化(卷积后)
# 展平
nn.Flatten() # 把 (B, C, H, W) → (B, C*H*W)
5.4 损失函数速查
损失函数衡量「模型预测值」和「真实值」的差距——训练的目标就是让这个差距越来越小。
# 回归任务:预测连续值
nn.MSELoss() # 均方误差,最常用
# 多分类任务:类别互斥(如手写数字 0~9)
nn.CrossEntropyLoss() # 内部 = LogSoftmax + NLLLoss,**输入是 logits(不加 Softmax)**
# 二分类任务:是 / 不是
nn.BCEWithLogitsLoss() # 内部 = Sigmoid + BCELoss,**输入是 logits**,比单独组合更稳定
# 兼容性更好的版本(输出已经是概率时)
nn.BCELoss() # 二分类交叉熵,输入需先过 Sigmoid
nn.NLLLoss() # 负对数似然,输入需先过 LogSoftmax
⚠️ 配对关系
nn.CrossEntropyLoss配原始 logits(网络最后输出层不要加 Softmax)nn.BCEWithLogitsLoss配原始 logits(网络最后输出层不要加 Sigmoid)- 这两个「WithLogits」版本数值更稳定,是 PyTorch 官方推荐写法
5.5 优化器速查
优化器决定「拿到梯度后,参数怎么更新」。
import torch.optim as optim
# 最经典:随机梯度下降
optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# 默认首选:Adam(自适应学习率,大多数场景直接用就行)
optim.Adam(model.parameters(), lr=1e-3)
# Transformer 时代标配:AdamW(Adam + 正确的权重衰减)
optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
💡 三个关键超参
lr(学习率):最敏感的超参。太大学不到东西(loss 震荡),太小学得太慢。一般从1e-3试起。momentum(动量):SGD 专属,常见 0.9。Adam 不需要(内部已经处理了)。weight_decay(权重衰减):L2 正则的强度,防过拟合。常用1e-4~1e-2。
5.6 设备迁移:把模型也搬上 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 模型搬上 GPU
model = MyNet().to(device)
# 数据搬上 GPU(必须在同一个设备上才能运算)
x = x.to(device)
y = y.to(device)
# 现在可以正常用了
pred = model(x)
loss = loss_fn(pred, y)
⚠️ 设备必须对齐
模型在 GPU、数据在 CPU → 报错。模型在 CPU、数据在 GPU → 报错。整个训练 pipeline 上的所有 tensor 都必须在同一个 device 上。
六、跑通第一个神经网络:拟合 y = x 2 y = x^2 y=x2
理论说了一堆,是时候写个完整可运行的例子了。
6.1 目标
用 PyTorch 搭一个 3 层 MLP(多层感知机),学习函数:
y = x 2 , x ∈ [ − 1 , 1 ] y = x^2, \quad x \in [-1, 1] y=x2,x∈[−1,1]
不依赖任何数据集——我们自己造数据,零干扰,专注模型搭建本身。
6.2 完整代码
新建 fit_x2.py:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 解决中文乱码问题
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体(黑体)
matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题
# ============== 1. 准备数据 ==============
# 真实的函数关系: y = x^2
x_train = torch.linspace(-1, 1, 200).reshape(-1, 1) # shape: (200, 1)
y_train = x_train ** 2 # shape: (200, 1)
# ============== 2. 定义模型 ==============
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(1, 64),
nn.Tanh(), # 隐藏层用 Tanh 拟合平滑曲线
nn.Linear(64, 64),
nn.Tanh(),
nn.Linear(64, 1),
)
def forward(self, x):
return self.net(x)
model = MLP()
print("模型结构:")
print(model)
# ============== 3. 损失函数 + 优化器 ==============
loss_fn = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# ============== 4. 训练循环 ==============
losses = []
epochs = 3000
for epoch in range(epochs):
# 前向传播
pred = model(x_train)
loss = loss_fn(pred, y_train)
# 反向传播 + 参数更新(下一篇详解)
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses.append(loss.item())
if (epoch + 1) % 500 == 0:
print(f"Epoch {epoch+1:4d} | loss = {loss.item():.6f}")
# ============== 5. 可视化结果 ==============
with torch.no_grad():
y_pred = model(x_train)
plt.figure(figsize=(10, 4))
# 左图:拟合效果
plt.subplot(1, 2, 1)
plt.scatter(x_train.numpy(), y_train.numpy(), s=10, alpha=0.5, label="True (y = x²)")
plt.plot(x_train.numpy(), y_pred.numpy(), 'r-', lw=2, label="MLP prediction")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.title("MLP 拟合 y = x²")
plt.grid(True, alpha=0.3)
# 右图:loss 曲线
plt.subplot(1, 2, 2)
plt.plot(losses)
plt.xlabel("Epoch")
plt.ylabel("MSE Loss")
plt.title("Training Loss")
plt.yscale("log")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("fit_x2.png", dpi=120, bbox_inches="tight")
print("已保存 fit_x2.png")
6.3 跑起来
uv run python fit_x2.py
预期输出:
模型结构:
MLP(
(net): Sequential(
(0): Linear(in_features=1, out_features=64, bias=True)
(1): Tanh()
(2): Linear(in_features=64, out_features=64, bias=True)
(3): Tanh()
(4): Linear(in_features=64, out_features=1, bias=True)
)
)
Epoch 500 | loss = 0.001234
Epoch 1000 | loss = 0.000123
Epoch 1500 | loss = 0.000045
Epoch 2000 | loss = 0.000023
Epoch 2500 | loss = 0.000014
Epoch 3000 | loss = 0.000009
已保存 fit_x2.png
最终结果如图,左图为拟合效果,右图为 loss 曲线。
七、小结 & 下一步
一张图总结
[uv 安装 PyTorch]
↓
[Tensor 基础操作] ─── 乐高积木
↓
[激活函数] ─── 引入非线性
↓
[nn.Module 搭网络] ─── 把积木拼成房子
↓
[训练循环 5 行] ─── 房子不断修缮
↓
[第一个 MLP 跑通!]
本文关键 API 速记
| 类别 | API | 一句话 |
|---|---|---|
| 创建 | torch.tensor() / zeros() / randn() |
造数据 |
| 形状 | tensor.shape / reshape() / view() |
维度变换 |
| 设备 | tensor.to(device) |
CPU ↔ GPU |
| 模型 | class Net(nn.Module): def forward(self, x) |
搭网络 |
| 损失 | nn.MSELoss() / CrossEntropyLoss() |
衡量差距 |
| 优化 | torch.optim.Adam(model.parameters(), lr=1e-3) |
更新参数 |
推荐下一步阅读
- 📖 PyTorch 官方 Quickstart —— 30 分钟官方版
- 📖 PyTorch 官方 Learn the Basics —— 4 个子模块系统讲
- 📖 《深度学习:PyTorch 实战》—— 国内口碑不错的中文书
- 📖 我的学习笔记
如果本文对你有帮助,欢迎点赞、收藏、转发 👍
有任何问题,评论区见!
更多推荐


所有评论(0)