搭建环境
开头总是装库。PyTorch 用 pip 装就行,顺便带上 numpy 和 matplotlib:
pip install torch torchvision
pip install numpy matplotlib
线性回归:最简模型
第一个程序用线性回归,拟合带噪声的直线。生成数据、定义模型、训练、画图,一条龙。代码直给:
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
# 生成训练数据
x = torch.linspace(0, 10, 100).unsqueeze(1)
y = 2 * x + 1 + torch.randn(100, 1) * 0.5
# 定义模型
class LinearModel(nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
self.linear = nn.Linear(1, 1)
def forward(self, x):
return self.linear(x)
# 创建模型实例
model = LinearModel()
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 训练模型
epochs = 100
for epoch in range(epochs):
# 前向传播
outputs = model(x)
# 计算损失
loss = criterion(outputs, y)
# 反向传播
optimizer.zero_grad()
loss.backward()
# 更新参数
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# 测试模型
with torch.no_grad():
predicted = model(x)
# 可视化结果
plt.scatter(x.numpy(), y.numpy(), label='Original data')
plt.plot(x.numpy(), predicted.numpy(), 'r-', label='Fitted line')
plt.legend()
plt.show()
print("Hello World! AI 模型训练完成")
Loss 下降到 0.1 左右就能看出红线基本穿过散点。没什么难点,SGD 调调学习率就行。
拐点:加上非线性
线性回归只能画直线,遇到抛物线立刻抓瞎。这时候就得靠神经网络引入非线性了。这段代码在之前基础上加了一个隐藏层,用 ReLU 激活,拟合 y = x² 加噪声的数据:
import torch
import torch.nn as nn
import torch.optim as optim
# 生成非线性数据
x = torch.linspace(-1, 1, 100).unsqueeze(1)
y = x.pow(2) + 0.2 * torch.randn(100, 1)
# 定义神经网络模型
class NeuralNet(nn.Module):
def __init__(self):
super(NeuralNet, self).__init__()
self.hidden = nn.Linear(1, 10)
self.output = nn.Linear(10, 1)
def forward(self, x):
x = torch.relu(self.hidden(x))
x = self.output(x)
return x
# 创建模型实例
model = NeuralNet()
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 训练模型
epochs = 1000
for epoch in range(epochs):
outputs = model(x)
loss = criterion(outputs, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# 测试模型
with torch.no_grad():
predicted = model(x)
# 可视化结果
import matplotlib.pyplot as plt
plt.scatter(x.numpy(), y.numpy(), label='Original data')
plt.plot(x.numpy(), predicted.numpy(), 'r-', label='Neural network prediction')
plt.legend()
plt.show()
训练 1000 轮,损失降到 0.1 左右,曲线基本贴合抛物线。这里用 SGD 依然可行,但如果收敛慢,试试 Adam 会快很多——实际用的时候我一般直接上 Adam。
网络结构其实很简单:输入 -> 隐藏层(10 个神经元,ReLU) -> 输出。核心就两点:层间线性变换加激活函数,以及反向传播自动算梯度。不用自己推导链式法则,PyTorch 全包了。
手写数字识别:CNN 初接触
MNIST 是图像分类的 Hello World。数据加载用 torchvision 一行搞定,预处理做 ToTensor 和归一化:
import torch
import torchvision
import torchvision.transforms as transforms
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 加载 MNIST 数据集
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False)
# 查看数据
import matplotlib.pyplot as plt
import numpy as np
# 函数:显示图像
def imshow(img):
img = img / 2 + 0.5 # 反归一化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 获取一批训练数据
dataiter = iter(trainloader)
images, labels = next(dataiter)
# 显示图像
imshow(torchvision.utils.make_grid(images))
print('标签:', ' '.join(f'{labels[j]}' for j in range(4)))
模型是一个两层卷积加两层全连接的简单 CNN:
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 卷积层
self.conv1 = nn.Conv2d(1, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
# 池化层
self.pool = nn.MaxPool2d(2, 2)
# 全连接层
self.fc1 = nn.Linear(64 * 12 * 12, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 64 * 12 * 12)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
# 创建模型实例
net = Net()
print(net)
print(net) 能看到每一层的形状和参数数量。接下来训练 5 个 epoch,打印每个批次损失:
import torch.optim as optim
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# 训练模型
epochs = 5
for epoch in range(epochs):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 获取输入
inputs, labels = data
# 清零梯度
optimizer.zero_grad()
# 前向传播
outputs = net(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 统计损失
running_loss += loss.item()
if i % 100 == 99:
print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f}')
running_loss = 0.0
print('训练完成')
训练后测试:
# 测试模型
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'测试准确率:{100 * correct / total:.2f}%')
# 查看预测结果
dataiter = iter(testloader)
images, labels = next(dataiter)
# 显示图像
imshow(torchvision.utils.make_grid(images))
print('真实标签:', ' '.join(f'{labels[j]}' for j in range(4)))
# 预测
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('预测标签:', ' '.join(f'{predicted[j]}' for j in range(4)))
准确率在 97% 左右,再跑几轮能到 99%。这个模型很小,参数量不到 1M,拿 CPU 跑也很快。
站在 Rust 的角度看 Python AI
写 Rust 的人初看 Python AI 栈,有几个明显感受:
- 开发节奏:Python 写起来确实快,不用跟生命周期和所有权较劲。不过 PyTorch 的底层是 C++/CUDA,性能瓶颈不在这里。
- 生态碾压:torchvision、transformers、diffusers……现成轮子太多,想做个东西几乎不用从零造轮子。Rust 的 ML 生态还在早期,tch-rs 之类的绑定能用,但顺滑程度差远了。
- 动态类型的两面:一方面写起来随性,不用定义一大堆 struct;另一方面,没类型检查,复杂项目容易出一眼看不出的 bug。我会尽量加类型标注,比如
def forward(self, x: torch.Tensor) -> torch.Tensor:,再配合 mypy 做静态检查,算是折中。 - 内存与部署:Python 的内存管理是自动的,模型训练完保存 state_dict,线上推理可以用 TorchScript 或 ONNX 导出,再用 Rust 的 tract 或 sonos 跑,速度不错,内存也可控。这个组合挺实用。
如果从 Rust 转过来学 Python AI,个人觉得几点比较有用:
- 系统编程的经验对理解张量布局、设备(CPU/GPU)管理、数据加载的流水线很有帮助,不是黑盒瞎调。
- 保持写 Rust 时养成的模块化习惯,把数据加载、模型定义、训练循环拆开,比全塞在一个 Jupyter 单元格里强。
- 尽早接触实战项目,最好结合自己领域的数据,比看十遍教程管用。
- Python 和 Rust 不是非此即彼:用 Python 做实验和原型,用 Rust 做高性能推理或后处理,各取所长。
结尾
从线性回归到 CNN,算是在 Python AI 的世界里走了一圈。最大的感触是:语言只是工具,生态和库才是真正决定开发效率的东西。Rust 的确定性让人安心,Python 的灵活省时间,两者结合反而更顺手。
代码都在这,拿过去跑一跑,改一改网络结构或超参数,比看再多理论都实际。
