文章目录
线性回归
回归:建模一个或多个自变量与因变量之间的关系
1.确定模型
案例:我们希望开发一个能预测房屋价格的模型
假设1:影响房价的关键因素是卧室个数,卫生间个数和居住面积,记为 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3
假设2:成交价是关键因素的加权和 y = w 1 x 1 + w 2 x 2 + w 3 x 3 + b y=w_1x_1+w_2x_2+w_3x_3+b y=w1x1+w2x2+w3x3+b
权重 w 1 , w 2 , w 3 w_1,w_2,w_3 w1,w2,w3和偏差 b b b的实际值在后面决定
权重决定了每个特征队我们预测值的影响。偏置是值当所有特征值都取0时,预测值应该是多少。
上述的式子可以看成输入特征的一个仿射变换。
仿射变换的特点是通过加权和特征进行线性变换,并通过偏置项进行平移。
目标变换:确定一下目标,我们希望开发一个房屋价格模型,根据假设问题变成了:
给定一个数据集,目标是寻找模型的权重w和偏置b,使得根据模型做出的预测大体符合数据中的真实价格。输出的预测值由输入特征通过线性模型的仿射变换确定,仿射变换由所选权重和偏置决定。
扩展到一般情况
给定d维输入 x = [ x 1 , x 2 , … , x d ] T x=[x_1,x_2,…,x_d]^T x=[x1,x2,…,xd]T,d个特征
线性模型有一个d维权重和一个标量偏差 w = [ w 1 , w 2 , . . . , w d ] T w=[w_1, w_2,...,w_d]^T w=[w1,w2,...,wd]T, b
输出是输入的加权和 y ^ = w 1 x 1 + w 2 x 2 + . . . + w d x d + b \hat y=w_1x_1+w_2x_2+...+w_dx_d+b y^=w1x1+w2x2+...+wdxd+b,通常使用尖角符号表示y的估计值
将所有特征放到向量 x ∈ R d x\in R^d x∈Rd中,并将所有权重放到向量 x ∈ R d x\in R^d x∈Rd中,可以用点积简洁表示模型 y ^ = w T x + b \hat y=w^Tx+b y^=wTx+b。
在这个式子中 x x x对应于单个数据样本的特征,整个数据集n个样本可以用矩阵 X ∈ R n × d X\in R^{n \times d} X∈Rn×d。其中X的每一行是一个样本,每一列是一种特征。
对于特征集合X,预测值 y ^ ∈ R n \hat y \in R^n y^∈Rn可以通过矩阵-向量乘法表示为 y ^ = X w + b \hat y =Xw+b y^=Xw+b
现在我们已经有一个模型了,接下来还需要两个工具
① 一种模型质量的度量方式
② 一种能够更新模型以提高模型预测质量的方法
2.衡量预估质量-损失函数
衡量方法:损失函数,比较真实值和预估值,比如房屋售价和估价
损失函数能够量化目标的实际值与预测值之间的差距,通常会选择非负数作为损失,且数值越小表示损失越小。
回归问题中最常用的损失函数是平方误差函数: l ( y , y ^ ) = 1 2 ( y − y ^ ) 2 l(y,\hat y) = \frac{1}{2}(y-\hat y)^2 l(y,y^)=21(y−y^)2,这里的 1 2 \frac{1}{2} 21不会带来本质的差别,只是为了损失函数求导后常数系数为1,形式上稍微简单一点。
为了度量模型再整个数据集上的预测质量,我们需要计算在n个样本上的损失均值: L ( X , y , w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( y ( i ) − y ^ ( i ) ) 2 = 1 n ∑ i = 1 n 1 2 ( y ( i ) − w T x ( i ) − b ) 2 = 1 2 n ∣ ∣ y − X w − b ∣ ∣ 2 L(X,y,w,b)=\frac{1}{n}\sum_{i=1}^n l^{(i)}(w,b) = \frac{1}{n}\sum_{i=1}^n\frac{1}{2}( y^{(i)}-\hat y^{(i)})^2 = \frac{1}{n}\sum_{i=1}^n\frac{1}{2}(y^{(i)}-w^Tx^{(i)}-b)^2 = \frac{1}{2n} ||y-Xw-b||^2 L(X,y,w,b)=n1∑i=1nl(i)(w,b)=n1∑i=1n21(y(i)−y^(i))2=n1∑i=1n21(y(i)−wTx(i)−b)2=2n1∣∣y−Xw−b∣∣2,这里的(i)表示第i个样本。
目标变换:现在我们的目标变成了寻找一个w*、b*使得损失函数L的值最小: w , b = a r g min w , b L ( X , y , w , b ) w,b= arg \min_{w,b}L(X,y,w,b) w,b=argminw,bL(X,y,w,b)
解析解: 线性回归的解可以用一个公式简单的表示,此类解叫做解析解(显示解)
<fo为了简化计算,通常将偏置项 b b b合并到权重向量 w w w中:
- 扩展矩阵:在 X X X的最后一列添加一列全1,得到新的设计矩阵: X = [ X 1 ] X=\begin{bmatrix} X&1 \end{bmatrix} X=[X1]维度 n × ( d + 1 ) n \times (d+1) n×(d+1)
- 扩展权重向量:将 b b b添加到 w w w的末尾,得到新的权重向量 w = [ w b ] w = \begin{bmatrix} w \\ b \\ \end{bmatrix} w=[wb]维度 ( d + 1 ) × 1 (d+1) \times 1 (d+1)×1
这样损失函数可以表示为 1 2 n ∣ ∣ y − X w ∣ ∣ 2 \frac{1}{2n} ||y-Xw||^2 2n1∣∣y−Xw∣∣2(Xw是一个 n × 1 n \times 1 n×1的向量)
因为这是一个线性模型,所以损失是一个凸函数,所以最优解满足求导结果为0。
可以通过求导来计算最小的w: ∂ ∂ w ( 1 2 n ∥ y − X w ∥ 2 ) \frac{\partial}{\partial w} ( \frac{1}{2n} \lVert y - Xw \rVert^2 ) ∂w∂(2n1∥y−Xw∥2),求出 w ∗ = ( X T X ) − 1 X T Y w^* = (X^TX)^{-1}X^TY w∗=(XTX)−1XTY
3.深度学习的基础优化算法
随机梯度下降
最常见的优化方法:梯度下降 => 核心是不断在损失函数递减的方向上更新参数来降低误差
- 挑选一个随机初始值 w 0 w_0 w0,开始不断更新 w 0 w_0 w0使其接近最优解。
- 更新法则为: w t = w t − 1 − η ∂ L ∂ w t − 1 w_t = w_{t-1}-\eta \frac{\partial L}{ \partial w_{t-1}} wt=wt−1−η∂wt−1∂L η \eta η学习率,是步长的超参数
超参数hyperparameter:预先手动指定,不通过模型训练。可以调整但不在训练过程中更新的参数
调参hyperparameter tuning:选择超参数的过程
超参数通常是我们根据训练迭代(验证数据集上评估)的结果来调整的
学习率的选择
- 学习率不能选太小,太小每一次走的步长有限,需要更新很多次的梯度,而计算梯度是一个很贵的事情,我们希望梯度计算的次数较少(更新参数前需要遍历整个数据集)
- 学习率也不能选太大,步子迈大了可能导致一直在震荡而不是在下降
小批量随机梯度下降
在实际情况中,很少直接选择随机梯度下降,因为每一次计算梯度需要对损失函数求导,而损失函数需要遍历整个训练集。
在实际中最常见的版本是小批量随机梯度下降,核心是每次更新参数时随机抽取固定数量的一小批样本,至于固定数量是多少,这是另一个重要的超参数。
步骤
随机抽取小批量B个样本 i 1 , i 2 , . . . , i b i_1,i_2,...,i_b i1,i2,...,ib,然后计算小批量的损失均值 1 b ∑ i ∈ I b L ( x i , y i , w ) \frac{1}{b}\sum_{i \in I_b} L(x_i,y_i,w) b1∑i∈IbL(xi,yi,w)来近似整个数据集的损失均值。
从线性回归到深度网络
线性模型可以看作是单层神经网络,输入为 x 1 , . . . , x d x_1,...,x_d x1,...,xd,输入层中的输入数(特征维度)为d;网络的输出为 o 1 o_1 o1,输出层中的输出数是1。
在计算层数时不考虑输入层,所以下图神经网络的层数为1。
线性回归从0开始实现
这里只使用张量和自动求导,在之后的章节中会充分利用深度学习框架的优势,介绍更简单的实现方式。
构造一个人造数据集
创建数据集
使用线性模型参数 w = [ 2 , − 3 , 4 ] T 、 b = 4.2 w=[2,-3,4]^T、b=4.2 w=[2,−3,4]T、b=4.2和噪声项 ϵ \epsilon ϵ生成数据集及其标签: y = X w + b + c y=Xw+b+c y=Xw+b+c
变量x:从均值为0、标准差为1的正态分布中采样,生成形状为 (num_examples, len(w)) 的矩阵,表示 num_examples个样本(矩阵行数),每个样本有len(w) 个特征(矩阵列数)。
返回值reshape((-1, 1)),将数组转换为二维列向量,形状为(n,1)的二维数组
def synthetic_data(w, b, num_examples): #@save
# 从0-1的离散正态分布中抽取随机数,返回一个张量
X = torch.normal(0, 1, (num_examples, len(w)))
# 矩阵乘法,通过线性变换 y = Xw + b 生成目标值
y = torch.matmul(X, w) + b # torch.Size([10])
# 引入均值为0、标准差为0.01的高斯噪声,模拟真实数据中的随机扰动。
y += torch.normal(0, 0.01, y.shape) # torch.Size([10])
# 将 y 转换为二维列向量(形状n,1)
return X, y.reshape((-1, 1))
# 这里调用函数生成1000个样本
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
问题1:为什么需要转换维列向量(-1,1)
这里先随便生成10个数字,发现原始的y是一个一维数组,转换后y是一个10行1列的矩阵格式。
- 输入特征X的形状一般为(样本数, 特征数)(例如(1000, 2))。
- 标签y的形状应为 (样本数, 1),保持维度一致,之后计算才方便。
def synthetic_data(w, b, num_examples): #@save
# 从均值为0、标准差为1的正态分布中采样,生成形状为
# (num_examples, len(w)) 的矩阵,表示 num_examples 个样本,每个样本有 len(w) 个特征。
#
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
print("原始 y 的形状:", y,y.shape)
y = y.reshape(-1,1)
print("转换后 y 的形状:", 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, 10)
原始 y 的形状: tensor([ 1.8253, 0.7458, 4.7804, 3.6786, -1.0490, 0.3278, 4.2530, 7.2494,
3.8678, 10.9984]) torch.Size([10])
转换后 y 的形状: torch.Size([10, 1])
可视化数据集
features表示的每一行是一个二维数据(两个特征)的样本,labels中的每一行表示一个标签值
d2l.set_figsize()
:设置绘图的尺寸大小,不传参数则使用默认值
d2l.plt.scatter绘制二维散点图,将数据的第二个维度(特征)作为 x 轴坐标,将标签数据作为 y 轴坐标。参数 1是指定散点的大小。
features[:, 1].detach().numpy()
表示从数据features中,选择每个样本的第二个特征并转换维为NumPy 数组的形式
labels.detach().numpy()
表示将标签数据转换为NumPy 数组。
features, labels = synthetic_data(true_w, true_b, 1000)
#随机的一个样本数据
#features: tensor([1.7741, 0.0435])
#label: tensor([7.6021])
print('features:', features[0],'\nlabel:', labels[0])
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);
#显示当前绘制的图像
d2l.plt.show()
问题1: 为什么要使用 detach()函数
这里移除detach()方法,代码仍然可以运行并得到类似的结果。detach()方法确保 PyTorch 张量能安全转换为 NumPy 数组,避免梯度计算干扰可视化
问题2: 运行时报错No module named ‘d2l’
解决办法: 进入到项目所在环境,使用pip install d21
安装
读取数据-随机抽取样本
训练模型时要对数据集进行遍历,每次抽取小批量样本并使用它更新模型,所以需要定义一个抽取函数。抽取函数需要能满足随机取样。
def data_iter(batch_size, features, labels):
num_examples = len(features)
# 创建一个从 0 到 num_examples-1 的整数列表,代表所有样本的索引。
indices = list(range(num_examples))
# 打乱索引列表,确保数据的随机性
random.shuffle(indices)
# 开始取小批量样本
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
# 防止最后一次抽样时样本不足batch_size的情况
indices[i: min(i + batch_size, num_examples)])
# 调用一次next返回一次数据
yield features[batch_indices], labels[batch_indices]
# 测试代码,取10个样本
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 30)
batch_size = 10
for X,y in data_iter(batch_size,features,labels):
print(X,'\n',y)# 一共30个样本,每次取10个,会打印三次
模型定义
模型参数初始化
从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重。
torch.zeros(1)
表示生成一个一维向量,形状为(1,)
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
定义模型
模型输入X数据集,w权重的矩阵,b偏置量,输出预测的结果y向量
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
定义损失函数
这里使用的均方损失函数 1 2 ( y ^ − y ) 2 \frac{1}{2}(\hat y - y)^2 21(y^−y)2,需要比较真实值 y y y和预测值 y ^ \hat y y^之间的差距
# 均方损失
def squared_loss(y_hat, y): #@save
# 防止形状不匹配,我们统一一下
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定义优化算法 - 小批量随机梯度下降
首先需要超参数学习率与批量大小,所以我们将其作为输入lr
,batch_size
。目标是求损失的梯度,接着朝减少损失的方向更新参数,所以还需要参数w,b
,我们将其一起传递给params
参数。
θ n e w = θ o l d − η y ^ b a t c h _ s i z e \theta_{new} = \theta_{old} - \eta\frac{\hat y}{batch\_size} θnew=θold−ηbatch_sizey^,其中 y ^ \hat y y^表示当前的小批量梯度,之前求损失函数的时候没有取均值,所以我们在这里取均值运算也就是除以batch_size
。手动将当前梯度值设置成0,这样下一次计算梯度时不会和上一次相关。
PyTorch 默认会累积梯度
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params: #一次更新参数w和b
param -= lr * param.grad / batch_size
param.grad.zero_()
问题1:grad属性是什么
grad
是张量的一个属性,用于保存损失函数对该向量的导数。只有当张量的 requires_grad=True
时,PyTorch 才会跟踪其梯度,并生成 grad
属性。
训练
其实之前我们都忽略了一个问题,我们不断朝着减少损失的方向更新参数,那么什么时候停下来呢?理想的情况是找到让损失最小值的地方停下来,但通常难以实现。
常见的做法是设置一个轮次的超参数epoch
,我们参数更新epoch
轮就可以了。每一轮中,使用data_iter
函数遍历整个数据集,并将训练数据集中的所有样本都使用一次去更新参数(扫一遍数据)。
训练的实现基本都是两层训练
- 外层循环
epoch
:
控制训练的总轮数,每轮会遍历整个数据集一次。 - 内层循环:
按小批量batch_size
迭代数据,每次处理一个批次的数据去更新参数。
向量的sum
函数表示将其分量求和为一个标量,原因是PyTorch 的 backward()
需要对标量求梯度。调用 backward()
函数自动计算损失对参数 w
和 b
的梯度,并存储在 w.grad 和
b.grad`中
lr = 0.03
num_epochs = 3
net = linreg # 这里为了方便之后替换其他的模型,我们将线性模型赋值给net变量
loss = squared_loss # loss为均方损失
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward() # sum 将当前批次的损失求和,将形状从 (batch_size, 1) 转换为标量。
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}')
问题1:backward怎么知道参数变量是w和b的?
设置了 requires_grad=True
的张量(如 w
、 b
)会被跟踪,当这些数据参与计算时,PyTorch 会动态记录所有操作,形成一个有向无环图。调用 loss.backward
时,PyTorch会自动沿着计算图反向传播 ,然后计算被跟踪向量的梯度,并赋值给该向量的 grad
属性。
with torch.no_grad()
会创建一个上下文环境,在该环境中计算的所有张量都不会被跟踪其操作历史,并且梯度信息也不会被记录。
代码总结
import random #导入Python内置的random模块,用于生成随机数和随机操作。
import torch #导入PyTorch深度学习框架,提供张量计算、自动求导和神经网络构建功能。
from d2l import torch as d2l
# 构造一个人造数据集
def synthetic_data(w, b, num_examples): #@save
# 从均值为0、标准差为1的正态分布中采样,生成形状为
# (num_examples, len(w)) 的矩阵,表示 num_examples 个样本,每个样本有 len(w) 个特征。
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
y = y.reshape(-1,1)
return X, y.reshape(-1, 1)
# 小批量取样
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]
# 定义模型,模型输入X数据集,w权重,b偏置量,输出预测的结果y向量
def linreg(X, w, b): #@save
return torch.matmul(X, w) + b
# 均方损失
def squared_loss(y_hat, y): #@save
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 优化算法
def sgd(params, lr, batch_size): #@save
with torch.no_grad():
for param in params: #一次更新参数w和b
param -= lr * param.grad / batch_size
param.grad.zero_()
# true_w = torch.tensor([2, -3.4])
# true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
batch_size = 10
lr = 0.03
num_epochs = 3
net = linreg # 这里为了方便之后替换其他的模型,我们将线性模型赋值给net变量
loss = squared_loss # loss为均方损失
# 初始化参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
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}')
线性回归的简洁实现
前面从0开始实现线性回归,我们只使用了
(1)通过张量来进行数据存储和线性代数;
(2)通过自动微分来计算梯度。
实际上,由于数据迭代器、损失函数、优化器和神经网络层很常用, 现代深度学习库也为我们实现了这些组件,可以直接使用。
1.生成数据集
直接使用apid2l.synthetic_data()
生成数据集
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
2.读取数据集
iter(data_iter)
生成一个全新的迭代器,next
取该迭代器的第一批数据(10个样本数据+10个预测值)
*
在函数调用中的解包作用,可以解包一个 ** 可迭代对象(如列表、元组) ** ,将其元素作为位置参数传递。 *data_arrays
这里表示将传入的元组(features, labels)解包为 features
、 labels
两个参数。
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
# 得到pytorch的dataseat
dataset = data.TensorDataset(*data_arrays)
# 每次随机挑选batch_size个样本出来
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
# (features, labels) 组合成一个list传递
data_iter = load_array((features, labels), batch_size)
print(data_iter) #<torch.utils.data.dataloader.DataLoader object at >
print(next(iter(data_iter)))
一般做法,数据都是存在硬盘中的,不需要将所有的数据一次性读完到内存里。
3.模型定义
对于标准深度学习模型,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。
我们首先定义一个模型变量 net
,它是一个 Sequential
类的实例。 Sequential
类将多个层串联在一起。 当给定输入数据时, Sequential
实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。
在线性回归的例子中,我们的模型只包含一个层,因此实际上不需要 Sequential
。 但是由于以后几乎所有的模型都是多层的,在这里使用 Sequential
会让你熟悉“标准的流水线”。
线性回归使用的是线性层(全连接层),它的每一个输入都通过矩阵-向量乘法得到它的每个输出。
全连接层在 Linear
类中定义,需要传递两个参数。第一个参数是输入特征的形状,我们这里是两个特征,所以是2。第二个参数指定输出特征的形状,输出特征形状为单个标量,所以为1。
nn.Linear(2, 1)
作用 : 将输入数据从2维映射到1维。
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
4.初始化模型参数
在使用net之前,需要初始化模型参数。
这个神经网络只有一层全连接层(除去输入层),所以通过net[0]
就可以获取到这个Linear
,然后使用 weight.data
和 bias.data
方法访问参数。
在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。
print(net[0].weight.data) # tensor([[-0.3005, -0.2747]])
net[0].weight.data.normal_(0, 0.01) # 使用正态分布替换原始值
print(net[0].weight.data) # tensor([[-0.0023, -0.0087]])
net[0].bias.data.fill_(0) # 使用fill_填充原始值
5.定义损失函数
计算均方误差使用的是 MSELoss
类, 计算当前批次(batch)内所有样本的损失平均值。
在 PyTorch 中, nn.MSELoss
默认使用均值模式( reduction='mean'
),即对批量数据取平均: M S R = 1 B ∑ i = 1 B ( y ^ i − y i ) 2 MSR = \frac{1}{B}\sum_{i=1}^B(\hat y_{i}-y_{i})^2 MSR=B1∑i=1B(y^i−yi)2 ,B为批量大小
loss = nn.MSELoss()
6.定义优化算法
小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在 optim模块中实现了该算法的许多变种。
这里实例化SGD(stochastic gradient descent,SGD)实例,参数要指定优化的参数( net.parameters()
获取)以及优化算法所需超参数字典,小批量随机梯度下降只需要设置 lr
的值。
print(net.parameters())# <generator object Module.parameters at 0x0000027423D0B920>
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
7.训练
在每个迭代周期里,我们将完整遍历一次数据集( train_data
), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:
- 通过调用
net(X)
生成预测并计算损失l
(前向传播)。 - 通过进行反向传播来计算梯度。
- 通过调用优化器来更新模型参数。
为了更好的衡量训练效果,我们计算每个迭代周期后的损失,并打印它来监控训练过程。
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
# net传入输入数据,自动计算预测值
l = loss(net(X) ,y) # 这里已经求和了
# 清空优化器跟踪的所有模型参数的梯度 每个轮次epoch要重新使用优化器计算
# 如果不手动清零,梯度会随着每个批次(batch)不断累积,导致参数更新错误。
trainer.zero_grad()
l.backward() # 先对每个分量计算梯度
trainer.step() # 进行一次模型参数更新
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
# epoch 1, loss 0.000196
# epoch 2, loss 0.000101
# epoch 3, loss 0.000101
trainer.step()
可以想象之前自定义函数sgb
的内部, 根据当前梯度更新模型参数: p a r a m = p a r a m − l r ⋅ p a r a m . g r a d param=param−lr⋅param.grad param=param−lr⋅param.grad
完整训练流程
- 前向传播 :输入数据通过模型计算预测值。
- 计算损失 :比较预测值和真实值的差异(如均方误差、交叉熵等)。
- 反向传播 :调用
loss.backward()
计算梯度。 - 梯度清零 :调用
trainer.zero_grad()
清空历史梯度。 - 参数更新 :调用
trainer.step()
根据梯度更新参数。