# CPU版本(推荐新手)
pip install torch torchvision matplotlib pillow
# GPU版本(如果有NVIDIA显卡)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
想象一下搭积木:
TensorFlow(静态图):就像乐高积木
PyTorch(动态图):就像橡皮泥
张量就是多维数组,是PyTorch中所有数据的基本形式:
每次运行代码时,PyTorch都会实时构建计算图,这意味着:
PyTorch会自动计算梯度,你只需要:
.backward()
方法# 导入必要的库
import torch
import torch.nn as nn # 神经网络模块
import torch.optim as optim # 优化器
import torchvision # 计算机视觉工具包
import torchvision.transforms as transforms # 数据变换
import matplotlib.pyplot as plt
import numpy as np
print("PyTorch版本:", torch.__version__)
# 检查是否有GPU可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("使用设备:", device)
# 创建张量的几种方法
print("=== 张量创建 ===")
# 从Python列表创建
tensor1 = torch.tensor([1, 2, 3, 4])
print("从列表创建:", tensor1)
# 创建零张量
zeros = torch.zeros(3, 4) # 3行4列的零矩阵
print("零张量形状:", zeros.shape)
# 创建随机张量
random_tensor = torch.randn(2, 3) # 2行3列的随机数矩阵
print("随机张量:\n", random_tensor)
# 张量运算
print("\n=== 张量运算 ===")
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
# 基本运算
print("加法:", a + b)
print("乘法:", a * b)
print("矩阵乘法:", torch.dot(a, b))
# 形状操作
matrix = torch.randn(6, 1)
print("原始形状:", matrix.shape)
reshaped = matrix.view(2, 3) # 重塑为2行3列
print("重塑后形状:", reshaped.shape)
# 移动到GPU(如果可用)
if torch.cuda.is_available():
gpu_tensor = a.to(device)
print("GPU张量设备:", gpu_tensor.device)
# 自动求导示例
print("=== 自动求导演示 ===")
# 创建需要求导的张量
x = torch.tensor([2.0], requires_grad=True) # 告诉PyTorch需要计算梯度
print("输入 x:", x)
# 定义一个简单函数:y = x^2 + 2x + 1
y = x**2 + 2*x + 1
print("输出 y:", y)
# 反向传播,计算梯度
y.backward() # 自动计算dy/dx
# 查看梯度
print("梯度 dy/dx:", x.grad)
print("理论值(2x+2):", 2*2 + 2) # 当x=2时,导数应该是6
# ⚠️ 重要提示:每次backward()后,梯度会累加
# 如果要重新计算,需要清零梯度
x.grad.zero_()
# 定义一个简单的神经网络类
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
"""
初始化网络结构
input_size: 输入特征数量
hidden_size: 隐藏层神经元数量
output_size: 输出类别数量
"""
super(SimpleNet, self).__init__()
# 定义网络层
# 第一层:输入层到隐藏层
self.layer1 = nn.Linear(input_size, hidden_size)
# 激活函数
self.relu = nn.ReLU()
# 第二层:隐藏层到输出层
self.layer2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
"""
前向传播过程
这里定义数据如何在网络中流动
"""
# 输入 -> 第一层 -> 激活函数
out = self.layer1(x)
out = self.relu(out)
# 激活后的结果 -> 第二层
out = self.layer2(out)
return out
# 创建网络实例
# 假设输入784个特征(28×28像素的图片),10个类别(0-9数字)
net = SimpleNet(input_size=784, hidden_size=128, output_size=10)
print("网络结构:")
print(net)
# 测试网络
# 创建一个批次的假数据 (batch_size=5, features=784)
test_input = torch.randn(5, 784)
output = net(test_input)
print("\n输入形状:", test_input.shape)
print("输出形状:", output.shape)
print("输出(前5个类别的得分):", output[0][:5])
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss() # 分类问题常用的损失函数
optimizer = optim.SGD(net.parameters(), lr=0.01) # 随机梯度下降优化器
print("=== 训练循环演示 ===")
# 模拟训练数据
# 假设我们有100个样本,每个样本784个特征,标签是0-9
fake_data = torch.randn(100, 784)
fake_labels = torch.randint(0, 10, (100,)) # 随机生成0-9的标签
# 简单的训练循环
for epoch in range(5): # 训练5个轮次
# 前向传播
outputs = net(fake_data)
# 计算损失
loss = criterion(outputs, fake_labels)
# 反向传播和优化
optimizer.zero_grad() # 清零之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
print(f"轮次 {epoch+1}, 损失: {loss.item():.4f}")
print("简单训练完成!")
现在让我们构建一个完整的图像分类项目,识别CIFAR-10数据集中的10种物体。
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
from torch.utils.data import DataLoader
# 设置随机种子,确保结果可重现
torch.manual_seed(42)
# 检查设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
# ==================== 数据准备 ====================
print("正在下载和准备数据...")
# 数据预处理
# 为什么要这样处理:
# 1. ToTensor(): 将PIL图片转换为张量,并缩放到[0,1]
# 2. Normalize(): 标准化数据,加速训练收敛
transform_train = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
# 下载CIFAR-10数据集
# 第一次运行会下载数据,大约160MB
trainset = torchvision.datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform_train
)
testset = torchvision.datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform_test
)
# 创建数据加载器
# batch_size=32 意思是每次训练使用32张图片
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)
testloader = DataLoader(testset, batch_size=32, shuffle=False)
# CIFAR-10的类别名称
classes = ['飞机', '汽车', '鸟', '猫', '鹿', '狗', '青蛙', '马', '船', '卡车']
print(f"训练集大小: {len(trainset)}")
print(f"测试集大小: {len(testset)}")
print(f"类别数量: {len(classes)}")
# ==================== 数据可视化 ====================
def show_sample_images():
"""显示示例图片"""
# 获取一批数据
dataiter = iter(trainloader)
images, labels = next(dataiter)
# 反标准化,用于显示
def denormalize(tensor):
# 反向操作之前的标准化
mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)
return tensor * std + mean
# 显示前8张图片
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
for i in range(8):
img = denormalize(images[i])
img = torch.clamp(img, 0, 1) # 确保像素值在[0,1]范围内
row, col = i // 4, i % 4
axes[row, col].imshow(img.permute(1, 2, 0)) # 转换通道顺序
axes[row, col].set_title(f'标签: {classes[labels[i]]}')
axes[row, col].axis('off')
plt.tight_layout()
plt.savefig('sample_images.png', dpi=150, bbox_inches='tight')
plt.show()
# 显示示例图片
show_sample_images()
# ==================== 定义CNN模型 ====================
class CIFAR10Net(nn.Module):
def __init__(self):
super(CIFAR10Net, self).__init__()
# 卷积层部分
# 第一个卷积块
self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # 输入3通道,输出32通道
self.conv2 = nn.Conv2d(32, 32, 3, padding=1) # 32->32通道
self.pool1 = nn.MaxPool2d(2, 2) # 2x2最大池化
# 第二个卷积块
self.conv3 = nn.Conv2d(32, 64, 3, padding=1) # 32->64通道
self.conv4 = nn.Conv2d(64, 64, 3, padding=1) # 64->64通道
self.pool2 = nn.MaxPool2d(2, 2) # 2x2最大池化
# 全连接层部分
# 计算:32x32输入 -> 池化后16x16 -> 再池化后8x8
# 所以最后特征图大小是 8x8x64 = 4096
self.fc1 = nn.Linear(64 * 8 * 8, 512) # 4096 -> 512
self.fc2 = nn.Linear(512, 10) # 512 -> 10类别
# Dropout防止过拟合
self.dropout = nn.Dropout(0.5)
self.relu = nn.ReLU()
def forward(self, x):
# 第一个卷积块
x = self.relu(self.conv1(x))
x = self.relu(self.conv2(x))
x = self.pool1(x)
# 第二个卷积块
x = self.relu(self.conv3(x))
x = self.relu(self.conv4(x))
x = self.pool2(x)
# 展平为一维
x = x.view(-1, 64 * 8 * 8)
# 全连接层
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
# 创建模型实例
net = CIFAR10Net().to(device)
print("模型结构:")
print(net)
# 计算模型参数数量
total_params = sum(p.numel() for p in net.parameters())
print(f"\n模型总参数数量: {total_params:,}")
# ==================== 训练函数 ====================
def train_model(net, trainloader, epochs=5):
"""训练模型"""
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 记录训练过程
train_losses = []
train_accuracies = []
print("开始训练...")
print("=" * 50)
for epoch in range(epochs):
running_loss = 0.0
correct_predictions = 0
total_samples = 0
# 设置为训练模式
net.train()
for batch_idx, (inputs, labels) in enumerate(trainloader):
# 将数据移到GPU(如果可用)
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = net(inputs)
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 统计信息
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total_samples += labels.size(0)
correct_predictions += (predicted == labels).sum().item()
# 每100个批次打印一次进度
if batch_idx % 100 == 99:
print(f'轮次 [{epoch+1}/{epochs}], '
f'批次 [{batch_idx+1}/{len(trainloader)}], '
f'损失: {running_loss/100:.4f}')
running_loss = 0.0
# 计算每轮的准确率
epoch_accuracy = 100 * correct_predictions / total_samples
epoch_loss = running_loss / len(trainloader)
train_losses.append(epoch_loss)
train_accuracies.append(epoch_accuracy)
print(f'轮次 {epoch+1} 完成 - 准确率: {epoch_accuracy:.2f}%')
print("-" * 50)
print("训练完成!")
return train_losses, train_accuracies
# ==================== 测试函数 ====================
def test_model(net, testloader):
"""测试模型性能"""
net.eval() # 设置为评估模式
correct = 0
total = 0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad(): # 测试时不需要计算梯度
for inputs, labels in testloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = net(inputs)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 计算每个类别的准确率
c = (predicted == labels).squeeze()
for i in range(labels.size(0)):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
# 总体准确率
overall_accuracy = 100 * correct / total
print(f'测试集总体准确率: {overall_accuracy:.2f}%')
# 每个类别的准确率
print("\n各类别准确率:")
for i in range(10):
if class_total[i] > 0:
accuracy = 100 * class_correct[i] / class_total[i]
print(f'{classes[i]}: {accuracy:.2f}%')
return overall_accuracy
# ==================== 开始训练 ====================
print("开始训练模型(这可能需要几分钟)...")
train_losses, train_accuracies = train_model(net, trainloader, epochs=3)
# ==================== 测试模型 ====================
print("\n开始测试模型...")
test_accuracy = test_model(net, testloader)
# ==================== 可视化结果 ====================
def plot_training_history(train_losses, train_accuracies):
"""可视化训练过程"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# 损失曲线
ax1.plot(train_losses)
ax1.set_title('训练损失')
ax1.set_xlabel('轮次')
ax1.set_ylabel('损失')
# 准确率曲线
ax2.plot(train_accuracies)
ax2.set_title('训练准确率')
ax2.set_xlabel('轮次')
ax2.set_ylabel('准确率 (%)')
plt.tight_layout()
plt.savefig('training_history.png', dpi=150, bbox_inches='tight')
plt.show()
# 如果有训练历史就绘制
if len(train_losses) > 0:
plot_training_history(train_losses, train_accuracies)
# ==================== 预测示例 ====================
def predict_samples():
"""展示一些预测结果"""
net.eval()
dataiter = iter(testloader)
images, labels = next(dataiter)
images, labels = images.to(device), labels.to(device)
# 进行预测
with torch.no_grad():
outputs = net(images)
_, predicted = torch.max(outputs, 1)
# 移回CPU用于显示
images = images.cpu()
labels = labels.cpu()
predicted = predicted.cpu()
# 反标准化
def denormalize(tensor):
mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)
return tensor * std + mean
# 显示前8个预测结果
fig, axes = plt.subplots(2, 4, figsize=(15, 8))
for i in range(8):
img = denormalize(images[i])
img = torch.clamp(img, 0, 1)
row, col = i // 4, i % 4
axes[row, col].imshow(img.permute(1, 2, 0))
# 标题显示真实标签和预测结果
true_label = classes[labels[i]]
pred_label = classes[predicted[i]]
color = 'green' if labels[i] == predicted[i] else 'red'
axes[row, col].set_title(f'真实: {true_label}\n预测: {pred_label}',
color=color, fontsize=10)
axes[row, col].axis('off')
plt.tight_layout()
plt.savefig('prediction_results.png', dpi=150, bbox_inches='tight')
plt.show()
# 显示预测结果
predict_samples()
# ==================== 保存模型 ====================
# 保存训练好的模型
torch.save(net.state_dict(), 'cifar10_model.pth')
print("\n模型已保存为 'cifar10_model.pth'")
print("\n" + "="*50)
print(" 项目完成!")
print("✅ 成功训练了一个图像分类器")
print("✅ 学会了PyTorch的基本用法")
print("✅ 理解了动态计算图的优势")
print("="*50)
使用设备: cuda
正在下载和准备数据...
训练集大小: 50000
测试集大小: 10000
类别数量: 10
模型总参数数量: 1,250,858
开始训练...
==================================================
轮次 [1/3], 批次 [100/1563], 损失: 1.8234
轮次 [1/3], 批次 [200/1563], 损失: 1.6789
...
轮次 1 完成 - 准确率: 45.67%
--------------------------------------------------
轮次 2 完成 - 准确率: 62.34%
--------------------------------------------------
轮次 3 完成 - 准确率: 71.23%
--------------------------------------------------
训练完成!
开始测试模型...
测试集总体准确率: 68.45%
各类别准确率:
飞机: 72.3%
汽车: 78.1%
鸟: 58.9%
猫: 51.2%
鹿: 65.4%
狗: 59.7%
青蛙: 76.8%
马: 71.5%
船: 79.2%
卡车: 71.4%
模型已保存为 'cifar10_model.pth'
项目完成!
✅ 成功训练了一个图像分类器
✅ 学会了PyTorch的基本用法
✅ 理解了动态计算图的优势
sample_images.png
: 数据集示例图片training_history.png
: 训练过程可视化prediction_results.png
: 预测结果展示cifar10_model.pth
: 训练好的模型文件原因: GPU显存不足
解决方法:
# 方法1:减小批次大小
trainloader = DataLoader(trainset, batch_size=16, shuffle=True) # 从32改为16
# 方法2:使用CPU
device = torch.device("cpu")
解决方法:
device = torch.device("cuda")
epochs=1
改进方法:
# 1. 增加训练轮次
train_model(net, trainloader, epochs=10)
# 2. 调整学习率
optimizer = optim.Adam(net.parameters(), lr=0.0001) # 降低学习率
# 3. 数据增强
transform_train = transforms.Compose([
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomCrop(32, padding=4), # 随机裁剪
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
# 加载模型
net = CIFAR10Net()
net.load_state_dict(torch.load('cifar10_model.pth'))
net.eval() # 设置为评估模式
from PIL import Image
def predict_single_image(image_path):
# 加载和预处理图片
image = Image.open(image_path).convert('RGB')
transform = transforms.Compose([
transforms.Resize((32, 32)), # CIFAR-10是32x32像素
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
image_tensor = transform(image).unsqueeze(0) # 添加批次维度
# 预测
with torch.no_grad():
output = net(image_tensor)
_, predicted = torch.max(output, 1)
return classes[predicted.item()]
# 使用示例
# result = predict_single_image('my_image.jpg')
# print(f"预测结果: {result}")