实验任务二:PyTorch练习¶
Pytorch练习代码¶
PyTorch入门¶
目标 通过本次实验,你将掌握以下内容:
- PyTorch 框架的核心组件(张量、自动求导、神经网络模块)。
- 基础张量操作与API的使用。
- 使用张量操作和梯度下降法实现线性回归模型
1. 环境准备¶
确保已安装 PyTorch(本节推荐使用最新版本,后续章节按需要选择合适版本):
使用 conda 安装(需提前安装 Anaconda)¶
conda install pytorch torchvision torchaudio -c pytorch
或使用 pip 安装(根据CUDA版本选择)¶
pip install torch torchvision
2. 数据操作¶
首先,我们导入 torch。请注意,虽然它被称为PyTorch,但我们应该导入 torch 而不是 pytorch
张量表示由一个数值组成的数组,这个数组可能有多个维度; 可以通过张量的 shape 属性来访问张量的形状 和张量中元素的总数
要改变一个张量的形状而不改变元素数量和元素值,可以调用 reshape 函数
使用全0、全1、其他常量或者从特定分布中随机采样的数字
通过提供包含数值的 Python 列表(或嵌套列表)来为所需张量中的每个元素赋予确定值
常见的标准算术运算符(+、-、、/ 和 *)都可以被升级为按元素运算
我们也可以把多个张量 连结(concatenate) 在一起
X = torch.arange(12, dtype=torch.float32).reshape((3, 4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
通过 逻辑运算符 构建二元张量
对张量中的所有元素进行求和会产生一个只有一个元素的张量
即使形状不同,我们仍然可以通过调用 广播机制 (broadcasting mechanism) 来执行按元素操作
tensor([[0],
[1],
[2]]) tensor([[0, 1]])
tensor([[0, 1],
[1, 2],
[2, 3]])
可以用 [-1] 选择最后一个元素,可以用 [1:3] 选择第二个和第三个元素
为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值
转换为 NumPy 张量
将大小为1的张量转换为 Python 标量
Tensor on GPU
# 以下代码只有在PyTorch GPU版本上才会执行
if torch.cuda.is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor
x = x.to(device) # 等价于 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型
3.自动求导¶
假设我们想对函数\(\(y=2x^⊤x\)\) 关于列向量x求导
在我们计算y关于x的梯度之前,我们需要一个地方来存储梯度
现在让我们计算y
通过调用反向传播函数来自动计算y关于x每个分量的梯度
现在让我们计算x的另一个函数
深度学习中 ,我们的目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和
将某些计算移动到记录的计算图之外。
当我们调用detach()时,会返回一个新的张量,这个张量的requires_grad属性为False,不会有梯度grad。
x.grad.zero_()
y = x * x
u = y.detach() #u的requires_grad属性为False
z = u * x
z.sum().backward()
print(x.grad == u)
即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
print(a.grad == d / a)
4.从零实现线性回归模型¶
我们将从零开始实现整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器
根据带有噪声的线性模型构造一个人造数据集。 我们使用线性模型参数w=[2,−3.4]⊤、b=4.2和噪声项ϵ 生成数据集及其标签:y=Xw+b+ϵ
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
features 中的每一行都包含一个二维数据样本,labels 中的每一行都包含一维标签值(一个标量)
使用 matplotlib 来绘制散点图。我们这里展示第二个特征(Feature 1)与标签之间的关系。
可以使用 pip install matplotlib 命令进行安装
import matplotlib.pyplot as plt
# 绘制散点图
plt.figure(figsize=(8, 6)) # 设置图表大小
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), s=1) # 绘制散点图
plt.xlabel('Feature 1') # 设置x轴标签
plt.ylabel('Label') # 设置y轴标签
plt.title('Scatter plot of Feature 1 vs Labels') # 设置图表标题
plt.show() # 显示图表
定义一个data_iter 函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
初始化模型参数并定义模型
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
def linreg(X, w, b):
"""线性回归模型。"""
return torch.matmul(X, w) + b
损失函数(Loss Function):是衡量模型预测值与真实值之间差异的数学表达式,是模型优化的核心目标。
它量化了模型的预测错误程度,并通过反向传播驱动参数更新。
线性回归通常使用均方误差(Mean Squared Error, MSE),计算预测值与真实值的平方差的平均值。
优化算法:Pytorch中优化器的功能为管理和更新模型中可学习参数的值,使得模型输出更接近真实标签
自动求导模块可以根据损失函数对模型的参数进行求梯度运算。
优化器会获取得到的梯度,然后利用一些策略去更新模型的参数,最终使得损失函数的值下降。
我们这里采用最简单的随机梯度下降方法。
def sgd(params, lr, batch_size):
"""小批量随机梯度下降。"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
训练过程
lr = 0.03 #学习率,可自行调整观察训练结果
num_epochs = 3 #训练轮数
net = linreg
loss = squared_loss #损失函数
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y)
l.sum().backward() #反向传播更新loss
sgd([w, b], lr, batch_size)
with torch.no_grad(): #
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
比较真实参数和通过训练学到的参数来评估训练的成功程度
5.线性回归模型的简单实现¶
import numpy as np
import torch
from torch.utils import data
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
调用框架中现有的API来读取数据
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器。"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))
使用PyTorch框架的预定义好的层并初始化模型参数
from torch import nn
net = nn.Sequential(nn.Linear(2, 1)) #线性层
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
PyTorch中计算均方误差使用的是MSELoss类,也称为平方L2范数
实例化 SGD 优化器实例
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
#trainer.zero_grad():优化器的常用方法,清空梯度
#trainer.step():优化器的常用方法,在损失反向传播后更新参数
思考:模仿第四节的训练过程,使用上述介绍的pytorch API补全训练过程
import numpy as np
import torch
from torch import nn
from torch.utils import data
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器。"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
net = nn.Sequential(nn.Linear(2, 1)) #线性层
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
#TODO:计算损失
#TODO:清空梯度
#TODO:反向传播
#TODO:优化器更新参数
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')