PyTorch 入门学习笔记

目录

[1 张量](#1 张量)

1)张量的初始化和属性

2)张量操作

[3)使用 NumPy 进行桥接](#3)使用 NumPy 进行桥接)

[2 torch.autograd](#2 torch.autograd)

1)背景

[2)在 PyTorch 中的使用](#2)在 PyTorch 中的使用)

[3)Autograd 的微分机制](#3)Autograd 的微分机制)

4)计算图原理

[3 神经网络](#3 神经网络)

1)定义网络

2)损失函数

3)反向传播

4)权重更新


PyTorch 是由 Meta(原 Facebook) 开源的深度学习框架,是基于 Python 的科学计算包,主要服务于两大用途:(1)作为 NumPy 的替代方案,支持 GPU 及其他硬件加速器的高性能计算,显著提升张量运算效率;(2)作为自动微分库,为神经网络训练提供高效的梯度计算与反向传播支持。

1 张量

张量(Tensor)是 PyTorch 中多维数据的主要载体,类似于 NumPy 的 ndarray,但支持 GPU 加速计算和自动微分功能,是神经网络中数据和梯度计算的基础单位。

1)张量的初始化和属性

张量有以下几种创建方式:

  • 直接用数据创建,
  • 用 numpy 数组创建,
  • 用另一个张量创建,
  • 用常量或随机值创建。例如:
python 复制代码
import torch
import numpy as np
 
# 用数据创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
 
# 用数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
 
# 用另一个张量创建
x_ones = torch.ones_like(x_data) # 保持 x_data 的属性
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # 覆盖 x_data 的类型
print(f"Random Tensor: \n {x_rand} \n")
 
# 用常量或随机值次创建
shape = (2, 3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
 
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")

运行结果如下:

张量属性描述了它们的形状、数据类型和存储它们的设备。

python 复制代码
tensor = torch.rand(3, 4)
 
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

运行结果如下:

2)张量操作

PyTorch 提供了超过100 种张量操作,涵盖数学运算、线性代数、数据切片等常见需求。这些操作均可在 GPU 上运行,且速度通常远快于 CPU 。如果您使用的是 Colab,可以通过 Edit ------ Notebook Settings 来分配。

python 复制代码
# 将张量移动到 GPU 上(如果支持)
if torch.cuda.is_available():
  tensor = tensor.to('cuda')
  print(f"Device tensor is stored on: {tensor.device}")

尝试一些操作(如果您熟悉 NumPy API,则会发现 Tensor API 使用起来相对更容易):

python 复制代码
# 类似 numpy 的标准索引和切片
tensor = torch.ones(4, 4)
tensor[:,1] = 0
print(tensor)
 
# 可以用 torch.cat 将张量沿指定维度连接起来(还有类似功能 torch.stack)
t1 = torch.cat([tensor, tensor, tensor], dim=1)#0表示第一个维度
print(t1)
 
# 计算元素级乘积
print(f"tensor.mul(tensor) \n {tensor.mul(tensor)} \n")
# 也可以这样写
print(f"tensor * tensor \n {tensor * tensor}")
 
# 计算两个张量之间的矩阵乘法
print(f"tensor.matmul(tensor.T) \n {tensor.matmul(tensor.T)} \n")
# 也可以这样写
print(f"tensor @ tensor.T \n {tensor @ tensor.T}")
 
# 原地操作,带有 _ 后缀的操作是原地操作(例如 x.copy_(y) 和 x.t_() 会直接修改 x 的值)
print(tensor, "\n")
tensor.add_(5)
print(tensor)

3)使用 NumPy 进行桥接

CPU 上的张量和 NumPy 数组可以共享它们的底层内存位置,更改其中一个将会更改另一个。

python 复制代码
# 张量转换为 NumPy 数组
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
 
# 张量的变化会反映在 NumPy 数组上
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
 
# NumPy 数组转张量
n = np.ones(5)
t = torch.from_numpy(n)
 
# NumPy 数组的修改会同步到张量
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")

运行结果如下:

2 torch.autograd

torch.autograd 是 PyTorch 的自动微分引擎,为神经网络训练提供核心支持。

1)背景

神经网络(Neural Networks, NNs)是由一系列嵌套函数组成的计算模型,这些函数对输入数据执行运算。这些函数由参数(包括权重和偏置)定义 ,在 PyTorch 中,这些参数以张量的形式存储。

神经网络的训练过程分为两个阶段:

前向传播(Forward Propagation)

在前向传播过程中,神经网络基于输入数据计算其预测输出。数据依次经过网络中的每一层函数,最终生成预测结果。

反向传播(Backward Propagation)

在反向传播过程中,神经网络根据预测误差按比例调整参数。具体做法是从输出层开始反向遍历网络,计算误差关于各层参数的导数(即梯度),并利用梯度下降法优化参数。

如果把前向传播就类比成按菜谱做菜,反向传播则是根据成品味道反推和调整菜谱配方。

2)在 PyTorch 中的使用

来看一个简单的训练示例,本例从 torchvision 加载一个预训练的 resnet18 模型。创建一个随机数据张量来模拟一张3通道、高宽均为64的图像,并初始化其对应的标签为随机值。预训练模型中的标签形状为(1,1000)。注意:本教程仅适用于 CPU 环境,无法在 GPU 设备上运行(即使将张量移至 CUDA)。

python 复制代码
import torch
from torchvision.models import resnet18, ResNet18_Weights
model = resnet18(weights=ResNet18_Weights.DEFAULT)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)

将输入数据传入模型 ,经过每一层的计算得到预测结果 ------这就是前向传播

python 复制代码
prediction = model(data) # 前向传播

然后计算误差(损失值) ,也就是计算预测值与真实标签之间的差异度量。然后再将这个误差反向传播 到整个网络中。调用**.backward()** 方法会触发反向传播过程。此时 Autograd 引擎会自动计算每个模型参数的梯度(梯度表示函数在某一点的导数或偏导数,描述了张量随着其输入变化的变化率),并将它们存储在参数的 .grad 属性中。

python 复制代码
loss = (prediction - labels).sum() # 计算损失
loss.backward() # 反向传播

接下来加载一个优化器 ,优化器的技术本质是一个能自动调整模型参数的算法 ,目标是让预测结果越来越准 ,通俗点说就是告诉模型怎么改、改多少。这个例子中使用了随机梯度下降法(SGD),并设置学习率为0.01,动量参数为0.9。我们要将模型的所有参数都注册到该优化器中。

python 复制代码
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

然后调用 .step() 方法来执行梯度下降 。优化器会根据每个参数存储在 .grad 属性中的梯度值来调整这些参数。

python 复制代码
optim.step()  # 执行梯度下降

以上就是训练神经网络所需的所有基本步骤

3)Autograd 的微分机制

autograd 是如何收集梯度的呢?创建两个张量 a 和 b,并设置 requires_grad=True。这相当于告诉 autograd:请跟踪 所有对这两个张量的。

python 复制代码
import torch
 
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

假设 a 和 b 是神经网络的参数,Q 是误差值。在训练神经网络时,我们需要计算误差关于参数的梯度(a 的梯度应为9倍 a 的平方,b 的梯度应为-2倍的 b):

当调用 Q.backward() 时,autograd 会计算 梯度并将结果存储在各个张量的 .grad 属性中。

由于 Q 是向量,这里需要显式传入一个 gradient 参数。这个参数是与 Q 形状相同的张量,代表 Q 对自身的梯度,即:

等价的做法是将 Q 聚合为标量再隐式调用 backward,例如 Q.sum().backward()。

现在梯度已存入 a.grad 和 b.grad 中:

python 复制代码
# 验证梯度是否正确
print(9*a**2 == a.grad)  # 检查 a 的梯度
print(-2*b == b.grad)    # 检查 b 的梯度

4)计算图原理

autograd 通过有向无环图( DAG **)**记录数据(张量)和执行的所有运算(包括生成的新张量)。这个 DAG 由 Function 对象构成:

  • 叶子节点:输入张量(如初始参数)

  • 根节点:输出张量(如损失值)

通过从根节点回溯 到叶子节点,autograd 能利用链式法则自动计算梯度。

前向传播,autograd 会同步完成两件事:

  • 执行运算:计算输出张量

  • 维护 DAG:记录 运算对应的梯度函数(grad_fn)

反向传播,当调用 .backward() 时,autograd 会:

  • 从每个 grad_fn 计算梯度
  • 将梯度累加到对应张量的 .grad 属性中
  • 通过链式法则一直传播到叶子张量

在神经网络中,不计算梯度的参数通常被称为冻结参数。如果事先知道某些参数不需要计算梯度(通过减少自动微分计算提升性能),那么"冻结"模型的一部分就会非常有用。在微调时,我们通常会冻结模型的大部分参数,仅修改分类器层以适配新的标签预测。

3 神经网络

可以使用 torch.nn 包来构建神经网络。nn 模块是基于 autograd 来定义模型并进行微分计算的。一个 nn.Module 包含若干网络层,以及一个 forward(input) 方法,该方法返回网络的输出结果。

这是一个简单的前馈神经网络(常见的神经网络还有:卷积神经网络、循环神经网络等)。它接收输入数据,依次通过多个网络层进行处理,最终产生输出结果。神经网络的标准训练流程通常包括以下步骤:

  • 定义 包含可训练参数(或称权重)的神经网络结构

  • 遍历 输入数据集

  • 将输入数据送入网络进行前向传播

  • 计算损失值(输出结果与正确结果的偏差)

  • 梯度反向传播至网络各参数

  • 使用简单更新规则调整网络权重:权重 = 权重 - 学习率 × 梯度(梯度下降算法的核心表达式)

1)定义网络

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
 
class Net(nn.Module):
 
    def __init__(self):
        super(Net, self).__init__()
        # 输入图像通道数1(灰度图),输出通道数6,5x5卷积核
        self.conv1 = nn.Conv2d(1, 6, 5)
        # 输入通道数6,输出通道数16,5x5卷积核
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全连接层(仿射变换):y = Wx + b
        # 输入维度16*5*5(16个通道,5x5特征图),输出120维
        self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5来自图像维度
        # 全连接层:120维输入,84维输出
        self.fc2 = nn.Linear(120, 84)
        # 输出层:84维输入,10维输出(对应10个类别)
        self.fc3 = nn.Linear(84, 10)
 
    def forward(self, input):
        # 卷积层C1:1输入通道,6输出通道,5x5卷积核
        # 使用ReLU激活函数,输出张量尺寸为(N, 6, 28, 28),N是批次大小
        c1 = F.relu(self.conv1(input))
        # 下采样层S2:2x2最大池化,无参数
        # 输出张量尺寸为(N, 6, 14, 14)
        s2 = F.max_pool2d(c1, (2, 2))
        # 卷积层C3:6输入通道,16输出通道,5x5卷积核
        # 使用ReLU激活函数,输出张量尺寸为(N, 16, 10, 10)
        c3 = F.relu(self.conv2(s2))
        # 下采样层S4:2x2最大池化,无参数
        # 输出张量尺寸为(N, 16, 5, 5)
        s4 = F.max_pool2d(c3, 2)
        # 展平操作:无参数,输出(N, 400)张量
        s4 = torch.flatten(s4, 1)
        # 全连接层F5:输入(N, 400),输出(N, 120)
        # 使用ReLU激活函数
        f5 = F.relu(self.fc1(s4))
        # 全连接层F6:输入(N, 120),输出(N, 84)
        # 使用ReLU激活函数
        f6 = F.relu(self.fc2(f5))
        # 输出层:输入(N, 84),输出(N, 10)
        output = self.fc3(f6)
        return output
 
 
# 实例化网络
net = Net()
print(net)

运行结果如下:

你只需定义前向传播函数反向传播函数(用于计算梯度)将通过 autograd 自动定义。在前向传播中,可以使用任何张量运算。

net.parameters() 返回模型的所有可训练参数:

注意:torch.nn 模块仅支持小批量 输入样本(不能是单个样本)。例如,nn.Conv2d 层需要接收一个4维张量,其维度为:样本数×通道数×高度×宽度。若只有单个样本,只需使用 input.unsqueeze(0) 来添加一个虚拟的批次维度。

2)损失函数

损失函数接受入参 (output, target) ,并计算一个值,该值估计输出与目标的差距。nn 包下有几个不同的损失函数,一个简单的损失函数是:nn.MSELoss,计算输出和目标之间的均方误差。

当我们调用 loss.backward() 时,整个计算图会相对于神经网络参数进行微分

3)反向传播

要实现误差反向传播,只需执行 loss.backward()。但注意需要清除已有梯度 ,否则梯度会累积到现有梯度之上。通过如下代码观察反向传播前后 conv1 层偏置的梯度变化:

python 复制代码
net.zero_grad() # 将所有参数的梯度缓冲区置零
 
print('conv1.bias.grad 反向传播之前')
print(net.conv1.bias.grad)
 
loss.backward()
 
print('conv1.bias.grad 反向传播之后')
print(net.conv1.bias.grad)

4)权重更新

实践中最简单的更新规则是随机梯度下降法(简称 SGD)

python 复制代码
weight = weight - learning_rate * gradient
 
# 可以通过简单的 Python 代码实现:
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

在使用神经网络时,我们可以使用各种不同的更新规则(SGD、 Nesterov-SGD、 Adam、 RMSProp 等)

python 复制代码
import torch.optim as optim
 
# 创建优化器
optimizer = optim.SGD(net.parameters(), lr=0.01)
 
# 训练循环中的操作
optimizer.zero_grad() # 清空梯度缓冲
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 执行更新

注意:观察梯度缓冲区是如何使用 optimizer.zero_grad() 手动设置为零的(如前面所述,梯度是累积的)。

相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习