李沐《动手学深度学习》 | 线性神经网络-线性回归

发布于:2025-04-11 ⋅ 阅读:(42) ⋅ 点赞:(0)

线性回归

回归:建模一个或多个自变量与因变量之间的关系

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 xRd中,并将所有权重放到向量 x ∈ R d x\in R^d xRd中,可以用点积简洁表示模型 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} XRn×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(yy^)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)=n1i=1nl(i)(w,b)=n1i=1n21(y(i)y^(i))2=n1i=1n21(y(i)wTx(i)b)2=2n1∣∣yXwb2,这里的(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中:

  1. 扩展矩阵:在 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)
  2. 扩展权重向量:将 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∣∣yXw2(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(2n1yXw2),求出 w ∗ = ( X T X ) − 1 X T Y w^* = (X^TX)^{-1}X^TY w=XTX)1XTY

这里推荐视频:https://www.bilibili.com/video/BV1xk4y1B7RQ

3.深度学习的基础优化算法

随机梯度下降

最常见的优化方法:梯度下降 => 核心是不断在损失函数递减的方向上更新参数来降低误差

  1. 挑选一个随机初始值 w 0 w_0 w0,开始不断更新 w 0 w_0 w0使其接近最优解。
  2. 更新法则为: w t = w t − 1 − η ∂ L ∂ w t − 1 w_t = w_{t-1}-\eta \frac{\partial L}{ \partial w_{t-1}} wt=wt1ηwt1L η \eta η学习率,是步长的超参数

超参数hyperparameter:预先手动指定,不通过模型训练。可以调整但不在训练过程中更新的参数

调参hyperparameter tuning:选择超参数的过程

超参数通常是我们根据训练迭代(验证数据集上评估)的结果来调整的

学习率的选择

  1. 学习率不能选太小,太小每一次走的步长有限,需要更新很多次的梯度,而计算梯度是一个很贵的事情,我们希望梯度计算的次数较少(更新参数前需要遍历整个数据集)
  2. 学习率也不能选太大,步子迈大了可能导致一直在震荡而不是在下降

小批量随机梯度下降

在实际情况中,很少直接选择随机梯度下降,因为每一次计算梯度需要对损失函数求导,而损失函数需要遍历整个训练集。

在实际中最常见的版本是小批量随机梯度下降,核心是每次更新参数时随机抽取固定数量的一小批样本,至于固定数量是多少,这是另一个重要的超参数。

步骤

随机抽取小批量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) b1iIbL(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]Tb=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
定义优化算法 - 小批量随机梯度下降

首先需要超参数学习率与批量大小,所以我们将其作为输入lrbatch_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 的张量(如 wb )会被跟踪,当这些数据参与计算时,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=B1i=1B(y^iyi)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=paramlrparam.grad

完整训练流程
  1. 前向传播 :输入数据通过模型计算预测值。
  2. 计算损失 :比较预测值和真实值的差异(如均方误差、交叉熵等)。
  3. 反向传播 :调用 loss.backward() 计算梯度。
  4. 梯度清零 :调用 trainer.zero_grad() 清空历史梯度。
  5. 参数更新 :调用 trainer.step() 根据梯度更新参数。

网站公告

今日签到

点亮在社区的每一天
去签到