Featured image of post PyTorch 学习笔记

PyTorch 学习笔记

学习记录 PyTorch,记录开发学习过程

PyTorch 实操笔记

1. 简单介绍

  • PyTorch 是一个深度学习框架,主要用于研究和生产环境
  • 由Meta AI(Facebook)人工智能研究小组开发的一种基于Lua编写的Torch库的Python实现的深度学习库,目前被广泛应用于学术界和工业界,相较于Tensorflow2.x,PyTorch在API的设计上更加简洁、优雅和易懂。

2. 前置技术依赖


3. 环境配置

  1. 查看显卡 cuda 版本
1
2
# windows 命令行中输入命令查看显卡信息
nvidia-smi

PyTorch 向下兼容 CUDA 版本,根据 CUDA 版本安装合适的 PyTorch 版本:

  1. 安装或更新 CUDA

2.1 通过官网安装 CUDA

CUDA 官网 下载并安装合适版本的 CUDA

2.2 Conda 安装 CUDA 工具包

  • 这种方式可以为每个 Conda 环境单独安装 CUDA 版本,避免版本冲突
1
2
3
4
5
6
7
8
# CUDA 
conda install cuda -c nvidia

# 指定版本安装,例如安装 CUDA 11.8.0
conda install cuda -c nvidia/label/cuda-11.8.0

# 安装完成后,使用以下命令测试安装成功
nvcc -V
  1. 安装 PyTorch

根据官网提供的安装命令安装 PyTorch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 示例:安装带有 CUDA 13.0 支持的 PyTorch 
pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu130

# 验证 CUDA PyTorch
import torch

print(torch.__version__)
print(torch.version.cuda)
print(torch.backends.cudnn.version())
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0))

4. 基础用法

4.1 张量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 张量创建
## 使用数据创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)

##使用 NumPy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)

## 使用已有张量创建
x_ones = torch.ones_like(x_data)  # retains the properties of x_data
x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data

## 通过随机或常量创建
shape = (2, 3,)
rand_tensor = torch.rand(shape) # [0,1]随机分布
rand_tensor = torch.randn(shape)# 正态分布
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

# 张量属性
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}")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 张量操作

## 移动到 GPU(如果可用)
if torch.cuda.is_available():
    tensor = tensor.to("cuda")
print(f"Device tensor is stored on: {tensor.device}")


## 加法操作

import torch
x = torch.rand(4, 3) 
y = torch.rand(4, 3) 
# 方式1
print(x + y)

# 方式2
print(torch.add(x, y))

# 方式3 in-place,原值修改
y.add_(x) 
print(y)

## 索引操作

# 取第二列
y-x[0,:]
y+=1 # 源 tensor 也被修改
操作是否创建新张量是否共享内存是否原地修改备注
b = a仅复制引用
b = a.clone()深拷贝,新建
b.copy_(a)原地复制数据
b = a[:]浅拷贝(切片共享内存)
b = a[[0, 2]]花式索引,深拷贝
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
## 维度变换

# torch.view() 新建视图张量,和原张量共享内存
x = torch.randn(4, 4)
y = x.view(16) # 展平为一维张量
z = x.view(-1, 8) # 自动计算行数,列数为8 自动计算为(2,8)


# 先 clone 再 view,避免共享内存 
x = torch.randn(4, 4)
y = x.clone().view(16) # 展平为一维张量
# clone操作会被记录在计算图中,梯度回传到副本时也会传到源 Tensor

## 取值操作
# 可以使用 .item() 来获得这个 value,而不获得其他结构

x = torch.randn(1) 
print(type(x)) # tensor
print(type(x.item())) # float
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
## 广播机制
# 当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

#output:
tensor([[1, 2]])
tensor([[1],
        [2],
        [3]])
tensor([[2, 3],
        [3, 4],
        [4, 5]])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
## 节省内存

before = id(Y) # id() 函数获取内存地址
Y = Y + X
id(Y) == before # False # Y 已经被重新分配了内存

# 原地操作,节省开销

## 切片

before = id(X)
X[:] = X + Y
# or X += Y
id(X) == before # True  内存地址未变

4.2 自动求导

PyTorch 中,所有神经网络的核心是 autograd 包。autograd包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义 ( define-by-run )的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。

1
2
3
4
5
6
7
8
# 设置 torch,Tensor 的 requires_grad 属性为 True ,表示需要计算梯度
x = torch.ones(2, 2, requires_grad=True)
# 调用 .backward() 进行反向传播 计算所有梯度,并累加到 .grad 属性中
y = x + 2
z = y * y * 3
out = z.mean()# 计算均值
out.backward()
print(x.grad) # [[4.5, 4.5], [4.5, 4.5]]

backward_1

  • 只有标量函数(即输出为单个元素的张量)的 .backward() 方法才能被调用。如上例中,out 为 z 的均值,z 中每一个元素对 out 的梯度贡献均等,因此 x.grad 中的元素均为 18/4=4.5 。
1
2
3
4
5
6
7
8
# .detach() 方法 分离目标张量出计算图,防止跟踪,阻断反向传播

x = torch.randn(3, requires_grad=True)
y = x * 2
z = y.mean()
# 分离 z 出计算图
z_detached = (z.detach())+1
z_detached.backward() # 报错,z_detached 不在计算图中
1
2
3
4
5
6
7
## Function 类
# Function 对象表示一个张量操作,并且知道如何计算该操作的前向传播和反向传播。它们与张量通过 `grad_fn` 连接。
x = torch.randn(3, requires_grad=True)
y = x + 2
print(y.grad_fn)  # <AddBackward0 object at ...>
z = y * y * 3
print(z.grad_fn)  # <MulBackward0 object at ...>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## 梯度清零
# 默认情况下,每次调用 .backward() 时,梯度会累加到 .grad 属性中。因此,在反向传播之前,通常需要将梯度清零。
x = torch.randn(3, requires_grad=True)
y = x.sum()
y.backward()
print(x.grad)  # 输出梯度 tensor([1., 1., 1.])
z=y*2
x.grad.zero_()  # 清零
z.backward()
print(x.grad)  # tensor([2., 2., 2.])
# 如果不清零 累加为 tensor([3., 3., 3.])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
## 雅可比矩阵向量积
# 对于非标量输出,可以通过向 .backward() 传递一个与输出形状相同的张量来计算雅可比矩阵向量积(Jacobian-vector product)。
x = torch.randn(3, requires_grad=True)
print(x)

y = x * 2
i = 0
while y.data.norm() < 1000:
    y = y * 2
    i = i + 1
print(y)
print(i)

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

# output:

tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
## no_grad 环境
# 在 `with torch.no_grad()` 环境中,所有计算都不会被跟踪
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)

# output:
True
True
False

## 修改 tensor 的数值,不被 autograd 记录(即不会影响反向传播), 则对 tensor.data 进行操作:
x = torch.ones(1,requires_grad=True)

print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外

y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播

y.backward()
print(x) # 更改data的值也会影响tensor的值 
print(x.grad)

# output:
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])

4.3 并行计算 CUDA

我们可以使用 .cuda() 方法将模型、张量或数据移动到 GPU 上进行计算:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 选择指定显卡 
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 设置默认使用的 GPU 设备为 0 号

## 使用 0,1 号两块显卡
CUDA_VISIBLE_DEVICES=0,1 python your_code.py
# os.environ["CUDA_VISIBLE_DEVICES"] = "0,1"

# 将模型移动到 GPU
model = MyModel()
model.cuda()  # 或者 model.to('cuda')
# 将张量移动到 GPU
tensor = tensor.cuda()  # 或者 tensor.to('cuda')

for image,label in data_loader:
    image = image.cuda()
    label = label.cuda()
    # 将图像和标签移动到 GPU 上进行训练

# 多卡训练

## 单机多卡 DP DataParallel

model = Net()
model.cuda() # 模型显示转移到CUDA上

if torch.cuda.device_count() > 1: # 含有多张GPU的卡
	model = nn.DataParallel(model) # 单机多卡DP训练

# 指定使用的GPU卡号

model = nn.DataParallel(model, device_ids=[0,1]) # 使用第0和第1张卡进行并行训练

#通过DP进行分布式多卡训练的方式容易造成负载不均衡,有可能第一块GPU显存占用更多,因为输出默认都会被gather到第一块GPU上。

## 多机多卡 DDP DistributedDataParallel

## 针对每个GPU,启动一个进程,然后这些进程在最开始的时候会保持一致(模型的初始化参数也一致,每个进程拥有自己的优化器),同时在更新模型的时候,梯度传播也是完全一致的,这样就可以保证任何一个GPU上面的模型参数就是完全一致的,所以这样就不会出现DataParallel那样显存不均衡的问题。

## GROUP:进程组,默认情况下,只有一个组,一个 job 即为一个组,也即一个 world。(当需要进行更加精细的通信时,可以通过 new_group 接口,使用 world 的子集,创建新组,用于集体通信等。)

## WORLD_SIZE:表示全局进程个数。如果是多机多卡就表示机器数量,如果是单机多卡就表示 GPU 数量。

## RANK:表示进程序号,用于进程间通讯,表征进程优先级。rank = 0 的主机为 master 节点。 如果是多机多卡就表示对应第几台机器,如果是单机多卡,由于一个进程内就只有一个 GPU,所以 rank 也就表示第几块 GPU。

## LOCAL_RANK:表示进程内,GPU 编号,非显式参数,由 torch.distributed.launch 内部指定。例如,多机多卡中 rank = 3,local_rank = 0 表示第 3 个进程内的第 1 块 GPU。

import os
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader, distributed
from torch import nn, optim
import argparse

def setup(rank, world_size):
    # 初始化进程组
    dist.init_process_group(
        backend='nccl',          # GPU 通常用 nccl, CPU 可用 gloo
        init_method='env://',    # 使用环境变量初始化
        world_size=world_size,   # 进程总数
        rank=rank                # 当前进程编号
    )

def cleanup():
    dist.destroy_process_group()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--local_rank", type=int)
    args = parser.parse_args()

    # 1️⃣ 设置 GPU 设备
    local_rank = args.local_rank
    torch.cuda.set_device(local_rank)

    # 2️⃣ 初始化分布式环境
    setup(local_rank, world_size=int(os.environ["WORLD_SIZE"]))

    # 3️⃣ 创建模型并移动到对应 GPU
    model = nn.Linear(10, 1).to(local_rank)

    # 4️⃣ 使用 DDP 包装模型
    model = DDP(model, device_ids=[local_rank], output_device=local_rank)

    # 5️⃣ 数据加载(使用 DistributedSampler)
    dataset = torch.utils.data.TensorDataset(torch.randn(100, 10), torch.randn(100, 1))
    sampler = distributed.DistributedSampler(dataset)
    dataloader = DataLoader(dataset, batch_size=8, sampler=sampler)

    # 6️⃣ 定义优化器与损失
    optimizer = optim.SGD(model.parameters(), lr=0.01)
    loss_fn = nn.MSELoss()

    # 7️⃣ 训练循环
    for epoch in range(5):
        sampler.set_epoch(epoch)  # 保证每个 epoch 数据不同
        for x, y in dataloader:
            x, y = x.to(local_rank), y.to(local_rank)
            pred = model(x)
            loss = loss_fn(pred, y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        if local_rank == 0:  # 只在主进程打印日志
            print(f"Epoch {epoch} loss: {loss.item():.4f}")

    cleanup()

if __name__ == "__main__":
    main()

4.4 概率

1
2
3
4
5
6
7
# from torch.distributions import multinomial
import torch
from torch.distributions import multinomial
fair_probs = torch.ones([6]) / 6 # 公平骰子的概率分布
multinomial.Multinomial(10000, fair_probs).sample() # 掷10000次骰子的结果
counts / 1000  # 相对频率作为估计值
# tensor([0.1670, 0.1680, 0.1670, 0.1670, 0.1650, 0.1660])

4.5 查看函数用法

1
2
?# 在 Jupyter Notebook 中使用 ? 查看函数用法
torch.sum?

5. 深度学习整体流程

阶段核心内容关键说明
1. 数据准备加载与预处理数据使用 DatasetDataLoader,进行划分(train/val/test)与归一化、增强等处理
2. 模型定义构建神经网络结构继承 nn.Module,在 forward() 定义前向传播
3. 损失函数选择优化目标常用:分类用 CrossEntropyLoss,回归用 MSELoss
4. 优化器设置定义参数更新规则AdamSGD,指定学习率与权重衰减
5. 训练循环前向传播 → 计算损失 → 反向传播 → 更新参数核心训练逻辑,包含梯度清零与反向传播
6. 验证评估在验证集上测试模型性能关闭梯度计算,评估准确率、损失等指标
7. 模型保存保存最佳模型权重使用 torch.save(model.state_dict(), path)
8. 测试与推理使用训练好的模型预测加载权重后在新数据上进行 model.eval() 推理

5.1 线性回归

  1. 线性模型

1

  1. 损失函数

2

  1. 梯度下降

3

5.2 模型预测

给定“已学习”的线性回归模型 $\hat{\mathbf{w}}^{\mathrm{T}} \mathbf{x} + \hat{b}$ , 现在我们可以通过房屋面积$x_1$和房龄$x_2$来估计一个(未包含在训练数据中的)新房屋价格。 给定特征估计目标的过程通常称为预测(prediction)或推断(inference)。

  1. 矢量化加速
  • 标量计算:逐个样本计算预测值 (for 循环)
  • 矢量化计算:通过矩阵运算一次性计算多个样本的预测值
  • 对计算进行矢量化,时处理整个小批量的样本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
n=100000
a=torch.ones([n])
b=torch.ones([n])

# for
c = torch.zeros(n)
for i in range(n):
    c[i] = a[i] + b[i]

# 矢量化
d = a + b
  1. 正态分布与平方损失
  • 随机变量 $x$ 服从均值为 $\mu$ 、方差为 $\sigma^2$ 的正态分布,记为 $x \sim \mathcal{N}(\mu, \sigma^2)$ ,其概率密度函数为: $$p(x) = \frac{1}{\sqrt{2\pi\sigma^2}}\exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)$$
1
2
3
4

def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma**2)
    return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)

5. 常见问题与解决办法


6. tips


7. 参考资料


潇洒人间一键仙
使用 Hugo 构建
主题 StackJimmy 设计