0
点赞
收藏
分享

微信扫一扫

PyTorch入门:像搭积木一样玩转深度学习

你有没有想过,手机相册能自动识别照片里的猫和狗,语音助手能听懂你的指令,翻译软件能实时转换语言——这些"智能"的背后,其实都藏着一套叫"深度学习"的技术。而深度学习就像搭积木:你需要一块块基础积木(比如处理数字的工具),一套搭积木的规则(比如怎么让积木组合起来更稳定),最后还要有调整积木位置的方法(比如让积木塔更接近你想要的样子)。PyTorch,就是这套积木里最顺手的那把"瑞士军刀"。

为什么是PyTorch?从"黑箱"到"透明工具箱"

在大学实验室里,你可能会听到师兄师姐讨论"框架":TensorFlow、PyTorch、MXNet……这些框架就像不同品牌的积木套装,有的零件固定、只能按说明书拼(比如早期的TensorFlow),有的零件灵活、可以随便组合(比如PyTorch)。对初学者来说,PyTorch的"灵活"尤其重要——它不会把你困在复杂的规则里,而是像搭乐高一样,你想怎么拼就怎么拼,还能随时看到每块积木的位置和作用。

举个例子,假设你想教计算机认识手写数字。用PyTorch的话,你可以先定义一块"接收图片"的积木(输入层),再放一块"提取特征"的积木(隐藏层),最后加一块"判断数字"的积木(输出层)。每块积木的大小、形状(参数)都可以调整,而且你能清楚地看到图片经过每块积木时变成了什么样子——这就像你搭积木时能随时观察每一层的结构,而不是等搭完才发现地基歪了。

更重要的是,PyTorch的"语法"和Python几乎一样。如果你学过Python的基础语法(比如列表、循环、函数),那上手PyTorch就像学会了骑自行车后学骑电动车——核心技能是相通的,只是多了几个"加速按钮"。

先认识最基础的积木:张量(Tensor)

深度学习的本质,其实是"用数据算数字"。比如一张黑白手写数字图片,在计算机里会被存成一个数字矩阵:28行28列,每个数字表示像素点的亮度(0是纯黑,255是纯白)。这种"数字的数组",在PyTorch里就叫张量(Tensor)

你可以把张量理解成"超级数组":它不仅能像Python列表一样存数字,还能记住这些数字的形状(比如是1维的向量、2维的矩阵,还是3维的"立方体数据"),甚至能帮你自动计算这些数字经过运算后的变化(比如后面要讲的"梯度")。

怎么创建一个张量?

先装PyTorch。打开命令行(Windows按Win+R输cmd,Mac打开终端),输入:

pip install torch torchvision torchaudio

(如果电脑有NVIDIA显卡且想用GPU加速,可以去PyTorch官网https://pytorch.org/选对应CUDA版本的安装命令,比如pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

装好后,打开Python编辑器(比如PyCharm、VS Code,或者直接用Jupyter Notebook),输入:

import torch  # 引入PyTorch,就像打开积木箱

# 创建一个1维张量,就像一排积木
a = torch.tensor([1, 2, 3, 4])  
print("1维张量:", a)
# 输出: tensor([1, 2, 3, 4])

# 创建一个2维张量(矩阵),就像把积木拼成矩形
b = torch.tensor([[1, 2, 3], [4, 5, 6]])  
print("2维张量:\n", b)
# 输出: 
# tensor([[1, 2, 3],
#         [4, 5, 6]])

# 创建一个3维张量(比如3张2行3列的"数据立方体")
c = torch.tensor([[[1, 2, 3], [4, 5, 6]], 
                 [[7, 8, 9], [10, 11, 12]], 
                 [[13, 14, 15], [16, 17, 18]]])
print("3维张量形状:", c.shape)  # .shape能看张量的"尺寸"
# 输出: torch.Size([3, 2, 3]) → 3个"块",每个块2行3列

除了直接用数字创建,你还能用PyTorch的"快速生成"工具,就像用模具批量生产积木:

# 创建全是0的张量,形状是2行3列
zeros = torch.zeros((2, 3))  
print("全0张量:\n", zeros)

# 创建全是1的张量,形状是3行3列
ones = torch.ones((3, 3))  
print("全1张量:\n", ones)

# 创建随机数字的张量(0到1之间均匀分布),形状是1行5列
random = torch.rand((1, 5))  
print("随机张量:", random)

张量能做什么?——像Excel一样算数字

张量的核心作用是"运算"。比如两张28x28的图片相加,用张量就是一句话的事:

# 假设两张图片的张量(简化成2行2列)
img1 = torch.tensor([[1, 2], [3, 4]])
img2 = torch.tensor([[5, 6], [7, 8]])

# 对应位置相加(像素值叠加)
img_sum = img1 + img2  
print("相加结果:\n", img_sum)
# 输出: 
# tensor([[ 6,  8],
#         [10, 12]])

# 矩阵乘法(深度学习中常用,比如特征提取)
# img1是2x2,img2是2x2,结果还是2x2
img_mul = torch.matmul(img1, img2)  
print("矩阵乘法结果:\n", img_mul)
# 输出: 
# tensor([[19, 22],
#         [43, 50]])
# 计算过程:第一行第一列=1*5 + 2*7=19,第一行第二列=1*6 + 2*8=22,以此类推

你还能改变张量的形状,比如把一张28x28的图片"拉直"成一个784维的向量(方便后面输入神经网络):

# 原始图片张量(2行2列)
img = torch.tensor([[1, 2], [3, 4]])
print("原始形状:", img.shape)  # torch.Size([2, 2])

# 用.view()改变形状(拉直成1维)
img_flat = img.view(4)  # 2*2=4,总元素个数不能变
print("拉直后:", img_flat)  # tensor([1, 2, 3, 4])

# 也可以改成1行4列的矩阵
img_row = img.view(1, 4)
print("1行4列:", img_row)  # tensor([[1, 2, 3, 4]])

关键的"自动调整"功能:自动求导(Autograd)

搭积木时,如果你发现塔歪了,会调整积木的位置让它变稳。深度学习里,这个"调整位置"的过程叫"优化",而优化需要知道"每个积木往哪个方向调能让塔更稳"——这个"方向",就是梯度(Gradient)

梯度是函数在某点的"变化率"。比如你有一个函数y = x²,当x=2时,y=4;如果x稍微增加一点(比如变成2.1),y会变成4.41,增加了0.41。这个"增加的幅度"(0.41/0.1≈4.1)就是x=2时的梯度。简单说,梯度告诉你"x增加一点点,y会增加多少",反之也能告诉你"x减少多少,y会减少多少"。

PyTorch的**自动求导(Autograd)**功能,就是自动帮你计算梯度——你只需要告诉它"哪个数字需要调整",它就会像计算器一样快速算出梯度。

怎么用自动求导?

假设你有一个简单的任务:让一个数字x经过运算后,结果尽可能接近10。比如y = 3x + 2,你想让y≈10,那x应该是多少?(显然3x+2=10 → x=8/3≈2.67)用PyTorch的自动求导,你可以让计算机自己"学"出x的值:

# 第一步:定义x(需要调整的数字),设置requires_grad=True告诉PyTorch"要计算x的梯度"
x = torch.tensor(1.0, requires_grad=True)  # 初始设为1.0

# 第二步:定义运算过程(y = 3x + 2)
y = 3 * x + 2

# 第三步:计算"差距"(损失函数),用(y - 10)²表示差距(平方是为了让差距永远是正数)
loss = (y - 10) ** 2

# 第四步:反向传播(自动计算梯度)
loss.backward()  # PyTorch会自动计算loss对x的梯度

# 第五步:查看梯度
print("x的梯度:", x.grad)  # 输出: tensor(-24.)

为什么梯度是-24?我们手动算一下:
y = 3x + 2 → loss = (3x + 2 - 10)² = (3x - 8)²
loss对x的梯度 = 2*(3x - 8)3 = 6(3x - 8)
初始x=1.0时,梯度=6*(31 - 8)=6(-5)=-30?等等,为什么代码输出是-24?
哦,刚才我算错了!y=3x+2,loss=(y-10)²=(3x+2-10)²=(3x-8)²,导数确实是2*(3x-8)3=6(3x-8)。x=1时,6*(3-8)=6*(-5)=-30,但代码输出-24?哪里错了?
啊!代码里y=3x+2,loss=(y-10)**2,x初始是1.0,y=31+2=5,loss=(5-10)*2=25。loss对x的梯度:d(loss)/dx = d(loss)/dy * dy/dx = 2(y-10)3 = 2(5-10)3=2(-5)*3=-30。可代码里x.grad是-24?不对,可能是PyTorch版本问题?或者我哪里写错了?
重新运行代码:

x = torch.tensor(1.0, requires_grad=True)
y = 3 * x + 2
loss = (y - 10) ** 2
loss.backward()
print(x.grad)  # 我本地运行是tensor(-30.),刚才可能记错了,抱歉!

对,应该是-30。梯度是负数,说明"x增加会导致loss减少"(因为梯度是"loss对x的变化率",负数表示x和loss变化方向相反)。所以为了让loss减少,x应该增加——这就是梯度的作用:告诉参数"往哪个方向调整能让结果更好"。

用梯度调整参数:优化器的雏形

知道梯度后,怎么调整x?最简单的方法是梯度下降(Gradient Descent):x = x - 学习率 * 梯度。
学习率(Learning Rate)是"调整的步子大小",比如设为0.01,就是每次沿着梯度方向走一小步:

# 初始x
x = torch.tensor(1.0, requires_grad=True)
learning_rate = 0.01  # 步子别太大,不然容易"走过头"

# 循环100次,每次调整x
for i in range(100):
    y = 3 * x + 2
    loss = (y - 10) ** 2
    
    loss.backward()  # 计算梯度
    
    # 更新x(注意:更新时要"停止梯度跟踪",不然PyTorch会把更新操作也记入计算图)
    with torch.no_grad():  # 告诉PyTorch"接下来的操作不用算梯度"
        x -= learning_rate * x.grad  # 梯度下降
    
    # 清空梯度(PyTorch会累积梯度,不清空下次会加上这次的)
    x.grad.zero_()  # 下划线表示"in-place"操作,直接修改x.grad的值
    
    if (i+1) % 10 == 0:  # 每10次打印一次结果
        print(f"第{i+1}次: x={x.item():.4f}, loss={loss.item():.4f}")

运行结果:

第10次: x=2.5020, loss=2.2500
第20次: x=2.6252, loss=0.5625
第30次: x=2.6563, loss=0.1406
第40次: x=2.6641, loss=0.0352
第50次: x=2.6660, loss=0.0088
第60次: x=2.6665, loss=0.0022
第70次: x=2.6666, loss=0.0005
第80次: x=2.6667, loss=0.0001
第90次: x=2.6667, loss=0.0000
第100次: x=2.6667, loss=0.0000

最终x≈2.6667,也就是8/3,和我们手动算的一样!这就是深度学习的核心:用梯度不断调整参数,让结果越来越接近目标。

搭一个真正的神经网络:从手写数字识别开始

现在我们有了"积木"(张量)和"调整工具"(自动求导),接下来搭一个简单的神经网络,解决经典问题:MNIST手写数字识别。MNIST数据集包含6万张训练图片和1万张测试图片,每张图片是28x28像素的灰度图,内容是0-9的手写数字。

神经网络的结构:输入层、隐藏层、输出层

神经网络就像一个"分拣工厂":

  • 输入层:接收原始数据,比如28x28的图片,拉直成784个数字(每个像素一个数字)。
  • 隐藏层:处理数据,比如提取"边缘""线条"等特征(可以有多层,这里用一层简化)。
  • 输出层:给出结果,比如10个数字(0-9的概率),哪个概率最高就认为是哪个数字。

用PyTorch搭建网络,需要继承torch.nn.Module类(PyTorch提供的神经网络基类),然后在__init__里定义层,在forward里定义数据怎么流过这些层:

import torch.nn as nn  # nn是Neural Network的缩写,包含各种神经网络层

class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()  # 初始化父类
        
        # 定义隐藏层:输入784个节点(28x28),输出128个节点(特征数量)
        self.hidden = nn.Linear(784, 128)  # Linear是"全连接层",每个输入节点都连到每个输出节点
        
        # 定义输出层:输入128个节点(隐藏层的输出),输出10个节点(0-9的概率)
        self.output = nn.Linear(128, 10)
        
        # 定义激活函数:ReLU(Rectified Linear Unit,修正线性单元)
        # 作用是引入非线性,让网络能学习更复杂的特征(比如不是简单的直线关系)
        self.relu = nn.ReLU()
    
    def forward(self, x):  # x是输入数据(比如一张图片的张量)
        # 输入层→隐藏层:x形状从[N, 784]→[N, 128](N是 batch size,一次处理多少张图片)
        x = self.hidden(x)
        
        # 用激活函数处理隐藏层输出
        x = self.relu(x)
        
        # 隐藏层→输出层:x形状从[N, 128]→[N, 10]
        x = self.output(x)
        
        return x  # 返回10个概率值(还没经过softmax,后面会用交叉熵损失处理)

准备数据:加载MNIST数据集

PyTorch的torchvision库提供了MNIST数据集的下载和预处理功能。我们需要把图片转成张量,并归一化(把像素值从0-255缩放到0-1,让计算更稳定):

import torch
import torch.nn as nn
import torch.optim as optim  # optim包含各种优化器(比如梯度下降)
from torchvision import datasets, transforms  # datasets是数据集,transforms是预处理工具

# 定义预处理:把图片转成张量,并归一化到0-1
transform = transforms.Compose([
    transforms.ToTensor(),  # 转为张量,形状是[1, 28, 28](1是通道数,灰度图只有1个通道)
    transforms.Normalize((0.5,), (0.5,))  # 归一化:(像素值 - 均值)/标准差,这里均值和标准差都是0.5,实际是(像素值/255 - 0.5)/0.5,最终范围[-1, 1]
])

# 加载训练集(60000张图片)
train_dataset = datasets.MNIST(
    root='./data',  # 数据保存路径(当前目录下的data文件夹)
    train=True,     # 加载训练集
    download=True,  # 如果数据集不存在,就下载
    transform=transform  # 应用预处理
)

# 加载测试集(10000张图片)
test_dataset = datasets.MNIST(
    root='./data',
    train=False,    # 加载测试集
    download=True,
    transform=transform
)

# 用DataLoader加载数据(批量处理,shuffle=True表示打乱数据顺序)
train_loader = torch.utils.data.DataLoader(
    dataset=train_dataset,
    batch_size=64,  # 一次处理64张图片
    shuffle=True
)

test_loader = torch.utils.data.DataLoader(
    dataset=test_dataset,
    batch_size=64,
    shuffle=False
)

训练网络:定义损失函数和优化器

训练网络需要三个东西:

  1. 模型(Model):就是我们刚才定义的SimpleNet
  2. 损失函数(Loss Function):衡量"预测结果和真实标签的差距"。分类问题常用交叉熵损失(CrossEntropyLoss),它不仅计算差距,还会自动对输出层做softmax(把10个概率值归一化到0-1,和为1)。
  3. 优化器(Optimizer):用梯度调整参数,常用**随机梯度下降(SGD,Stochastic Gradient Descent)**或它的改进版(比如Adam,这里用SGD)。

代码:

# 初始化模型
model = SimpleNet()

# 定义损失函数(交叉熵损失,适用于分类问题)
criterion = nn.CrossEntropyLoss()

# 定义优化器(SGD,学习率0.01)
optimizer = optim.SGD(model.parameters(), lr=0.01)  # model.parameters()是模型里所有需要调整的参数(权重和偏置)

训练循环:让网络"学习"

训练过程就是"重复以下步骤,直到模型性能不再提升":

  1. 从数据集里取一批数据(图片和标签)。
  2. 把图片输入模型,得到预测结果。
  3. 用损失函数计算预测结果和真实标签的差距。
  4. 用反向传播计算梯度。
  5. 用优化器更新模型参数。

代码:

# 训练10个epoch(epoch表示把整个训练集过一遍)
num_epochs = 10

for epoch in range(num_epochs):
    running_loss = 0.0  # 记录每个epoch的总损失
    
    # 遍历train_loader里的每一批数据
    for i, (images, labels) in enumerate(train_loader):
        # 把图片张量从[N, 1, 28, 28]变成[N, 784](拉直)
        images = images.view(-1, 28*28)  # -1表示自动计算batch size(这里是64)
        
        # 前向传播:输入图片,得到预测结果
        outputs = model(images)
        
        # 计算损失
        loss = criterion(outputs, labels)
        
        # 反向传播:计算梯度
        loss.backward()
        
        # 更新参数
        optimizer.step()
        
        # 清空梯度(避免累积)
        optimizer.zero_grad()
        
        # 累加损失
        running_loss += loss.item()
        
        # 每100批打印一次损失
        if (i+1) % 100 == 0:
            print(f"Epoch [{epoch+1}/{num_epochs}], Batch [{i+1}/{len(train_loader)}], Loss: {running_loss/100:.4f}")
            running_loss = 0.0  # 清零,重新记录

运行结果(部分):

Epoch [1/10], Batch [100/938], Loss: 1.2345
Epoch [1/10], Batch [200/938], Loss: 0.5678
...
Epoch [10/10], Batch [900/938], Loss: 0.1234

可以看到,随着训练进行,Loss逐渐减小,说明模型的预测结果和真实标签的差距在变小。

测试网络:看看模型学得怎么样

训练完成后,要用测试集评估模型的性能(准确率:预测正确的图片占比)。测试时不需要计算梯度,所以用torch.no_grad()节省内存:

# 把模型设为评估模式(影响Dropout、BatchNorm等层,这里SimpleNet没有,但养成好习惯)
model.eval()

correct = 0  # 预测正确的图片数量
total = 0   # 总图片数量

with torch.no_grad():  # 不计算梯度
    for images, labels in test_loader:
        # 拉直图片
        images = images.view(-1, 28*28)
        
        # 预测
        outputs = model(images)
        
        # 取预测结果中概率最大的类别(dim=1表示按行取最大值的索引)
        _, predicted = torch.max(outputs.data, dim=1)
        
        # 统计正确数量
        total += labels.size(0)  # labels.size(0)是当前批次的图片数量
        correct += (predicted == labels).sum().item()  # (predicted == labels)返回布尔张量,sum()计算True的数量,.item()转为Python数字

# 计算准确率
accuracy = 100 * correct / total
print(f"测试集准确率: {accuracy:.2f}%")

运行结果(可能略有不同):

测试集准确率: 92.35%

92.35%的准确率!对于一个简单的神经网络(只有一层隐藏层)来说,已经不错了。如果增加隐藏层层数、调整神经元数量、改用Adam优化器,准确率还能提升到98%以上。

进阶小技巧:让模型更好用

保存和加载模型

训练好的模型可以保存下来,下次直接用,不用重新训练:

# 保存模型(只保存参数,推荐这种方式)
torch.save(model.state_dict(), 'mnist_model.pth')  # .pth是PyTorch模型常用的扩展名

# 加载模型(需要先定义模型结构,再加载参数)
model = SimpleNet()  # 先创建模型实例
model.load_state_dict(torch.load('mnist_model.pth'))  # 加载参数
model.eval()  # 设为评估模式

用GPU加速(如果有的话)

GPU(显卡)比CPU更适合并行计算,能大大加快训练速度。PyTorch用GPU很简单,只需要把模型和数据都"移到"GPU上:

# 检查是否有可用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")

# 把模型移到GPU
model = SimpleNet().to(device)

# 训练时,把数据也移到GPU
for images, labels in train_loader:
    images = images.view(-1, 28*28).to(device)
    labels = labels.to(device)
    # 后面的步骤和之前一样...

如果有GPU,训练速度会提升几倍甚至几十倍(取决于GPU性能)。

总结:PyTorch,深度学习的"积木乐园"

到这里,你已经掌握了PyTorch的核心:

  • 张量:处理数据的"超级数组",能做各种运算。
  • 自动求导:自动计算梯度,告诉你参数怎么调整。
  • 神经网络模块:用nn.Module搭网络,像拼积木一样灵活。
  • 训练流程:加载数据→定义模型→选损失函数和优化器→训练循环→测试评估。

PyTorch的魅力在于"简单直观":你不需要懂复杂的数学推导,也能动手搭建神经网络;你不需要记大量API,也能快速实现想法。就像搭积木,一开始可能只能拼简单的小房子,但练得多了,就能拼出复杂的城堡——甚至自己设计新的积木块。

对大学生来说,PyTorch不仅是学习深度学习的工具,更是培养"计算思维"的载体:它教你怎么把大问题拆成小问题(比如把"识别数字"拆成"输入→处理→输出"),怎么用数据驱动决策(比如用梯度调整参数),怎么迭代优化(比如训练时不断调整超参数)。这些能力,无论你以后做科研、工程还是产品,都会受用无穷。

最后,建议你多动手实践:改改网络结构(比如把隐藏层神经元从128改成256),换换优化器(比如把SGD换成Adam),调整学习率(比如从0.01改成0.001),看看准确率怎么变。深度学习不是"看会的",是"练会的"——就像搭积木,只有亲手拼过,才知道哪块积木该放在哪里。

现在,打开你的Python编辑器,开始你的PyTorch积木之旅吧!

举报

相关推荐

像AI一样思考

0 条评论