代码讲解

import random
import torch
from d2l import torch as d2l
def synthetic_data(w,b,num_examples):
    "生成 y = Wx + 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)

这段代码的主要功能是生成一组模拟的线性回归数据。数据生成的过程包括以下步骤:

  1. 导入必要的库。
  2. 定义一个函数,用于生成符合线性关系的模拟数据。
  3. 设定线性模型的真实权重和偏置。
  4. 使用定义的函数生成数据特征和标签。

接下来,我们逐行解释代码的思路和每句话的语法现象,并讨论为什么要这样写。

1. 导入必要的库

import random
import torch
from d2l import torch as d2l
思路:

这部分代码用于导入我们在代码中会用到的库和模块。

语法解释:
  • import random: 这句是导入Python标准库中的random模块,用于生成随机数。虽然这个代码段中并没有使用到random模块,但它可能在其他地方会用到。
  • import torch: 这句是导入torch库,torch是用于深度学习的一个重要框架,它提供了多维张量(tensor)操作、自动微分以及各种神经网络模块。
  • from d2l import torch as d2l: 这句是从d2l库(《动手学深度学习》这本书的代码库)中导入torch模块,并将其重命名为d2l。这样做是为了使用d2l中的一些工具函数,便于书中代码的一致性。

2. 定义一个生成模拟数据的函数

def synthetic_data(w, b, num_examples):
    "生成 y = Wx + 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))
思路:

这个函数用于生成一组符合线性关系的数据y = Wx + b + 噪声。其中,W是权重向量,b是偏置,X是输入特征矩阵,y是输出标签向量。

语法解释:
  • def synthetic_data(w, b, num_examples):: 这是定义一个函数的语句。synthetic_data是函数名,w, b, num_examples是函数的参数。w表示权重向量,b表示偏置,num_examples表示要生成的数据点的数量。

  • "生成 y = Wx + b + 噪声": 这是一行字符串,称为文档字符串(docstring),用于说明函数的作用。它解释了这个函数的功能:生成符合线性模型的带噪声数据。

  • X = torch.normal(0, 1, (num_examples, len(w))): 这里使用torch.normal生成一个形状为(num_examples, len(w))的张量X,其中每个元素是服从均值为0、标准差为1的正态分布的随机数。X代表输入特征矩阵,num_examples是样本数,len(w)是特征的数量(也就是权重向量的长度)。

  • 正态分布,也称为高斯分布(Gaussian distribution),是一种在自然和社会科学领域中非常常见的连续概率分布。它由德国数学家和天文学家高斯(Carl Friedrich Gauss)在研究测量误差时提出。正态分布的概率密度函数具有特定的数学形式,其形状为对称的钟形曲线,具有以下特点:

    1. 对称性:正态分布是关于其均值对称的,即分布的左侧和右侧是镜像对称的。

    2. 均值、中位数和众数相同:在正态分布中,这三个统计量是相等的。

    3. 68-95-99.7规则:在正态分布中,大约68%的数据值位于均值的±1个标准差范围 内,95%的数据值位于±2个标准差范围内,99.7%的数据值位于±3个标准差范围内。

      正态分布的概率密度函数(PDF)可以表示为:
      f ( x ) = 1 σ 2 π e − ( x − μ ) 2 2 σ 2 f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{(x-\mu)^2}{2\sigma^2}} f(x)=σ2π 1e2σ2(xμ)2
      其中:

    • μ \mu μ是分布的均值(mean)。

    • σ \sigma σ是分布的标准差(standard deviation),控制分布的宽度。

    • σ 2 π \sigma\sqrt{2\pi} σ2π 是使函数积分为1的归一化因子。

      正态分布广泛应用于统计学、物理学、生物学、经济学等领域,是许多统计方法的基础,如回归分析、假设检验等。

  • y = torch.matmul(X, w) + b: 使用torch.matmul进行矩阵乘法,计算y = Wx + btorch.matmul(X, w)表示矩阵X与向量w相乘,结果是一个形状为(num_examples,)的向量。然后,再加上偏置b,得到线性模型的输出y

  • y += torch.normal(0, 0.01, y.shape): 在y中加入噪声。torch.normal(0, 0.01, y.shape)生成一个形状与y相同、服从均值为0、标准差为0.01的正态分布的噪声张量,并将其加到y上,使得数据更贴近实际情况。

  • return X, y.reshape((-1, 1)): 函数返回Xyy.reshape((-1, 1))y的形状重塑为(num_examples, 1),即列向量形式,以符合后续操作的输入格式。

3. 设定线性模型的真实权重和偏置

true_w = torch.tensor([2, -3.4])
true_b = 4.2
思路:

设定线性模型的真实参数,这些参数将用于生成模拟数据。

语法解释:
  • true_w = torch.tensor([2, -3.4]): 生成一个张量true_w,它是一个一维张量,包含了线性模型的真实权重2-3.4torch.tensor是用来创建一个torch张量的函数。

  • true_b = 4.2: 设定真实的偏置true_b4.2。这也是一个标量张量。

4. 使用定义的函数生成数据特征和标签

features, labels = synthetic_data(true_w, true_b, 1000)
思路:

调用synthetic_data函数生成1000个样本的特征和标签数据。

语法解释:
  • features, labels = synthetic_data(true_w, true_b, 1000): 调用synthetic_data函数,传入之前定义好的权重true_w,偏置true_b,以及样本数1000,生成特征features和标签labelsfeatures是一个形状为(1000, 2)的张量,表示1000个样本,每个样本有2个特征;labels是一个形状为(1000, 1)的张量,表示对应的标签。

这段代码最终生成了1000个带有噪声的线性回归数据点,这些数据点可以用来训练和测试线性模型。

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

这段代码的功能是实现一个小批量随机梯度下降(SGD)的数据迭代器。数据迭代器可以按小批量随机读取数据,而不是一次性加载所有数据,这对于大数据集尤其重要。接下来,我们逐行分析代码的思路,并解释每一句的语法和作用。

1. 定义数据迭代器函数

def data_iter(batch_size, features, labels):
思路:

定义一个数据迭代器函数data_iter,用于生成指定大小的小批量数据。batch_size表示每次从数据集中抽取的样本数量,features是数据特征,labels是对应的标签。

语法解释:
  • def data_iter(batch_size, features, labels):: 定义一个名为data_iter的函数。函数接收三个参数:batch_size(小批量的大小),features(输入特征数据),和labels(对应的标签数据)。

2. 计算样本数量并生成索引

    num_examples = len(features)
    indices = list(range(num_examples))
思路:

计算数据集中样本的总数,并生成一个列表indices,其中包含所有样本的索引。这个列表将在后续的步骤中被随机打乱,以实现数据的随机读取。

语法解释:
  • num_examples = len(features): 使用len函数获取features(特征数据)的长度,即样本数量,并将其存储在变量num_examples中。

  • indices = list(range(num_examples)): range(num_examples)生成一个从0num_examples-1的整数序列,表示样本的索引。list函数将其转换为一个列表。此列表indices用于追踪样本的索引。

3. 随机打乱样本顺序

    random.shuffle(indices)
思路:

打乱索引顺序,使得样本的提取顺序随机化,避免模型训练过程中出现数据顺序偏差。

语法解释:
  • random.shuffle(indices): random.shuffle函数就地打乱列表indices的顺序,使得列表中的元素以随机顺序排列。这确保了数据被随机抽取。

4. 按小批量读取数据

    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]
思路:

通过遍历indices列表,将数据分成大小为batch_size的小批量,并逐批返回。每次循环中,提取batch_size个样本的索引,并根据这些索引从featureslabels中提取对应的小批量数据。

语法解释:
  • for i in range(0, num_examples, batch_size):: 这是一个for循环,range(0, num_examples, batch_size)生成一个从0num_examples的整数序列,步长为batch_sizei的取值表示当前批次开始的索引。

  • batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)]): 这一行代码从indices中提取当前小批量的索引,并将其转换为torch张量。min(i + batch_size, num_examples)确保在最后一个批次时,不会越界。

  • yield features[batch_indices], labels[batch_indices]: yield语句会返回一个小批量的特征和标签,并暂停函数的执行状态,直到下次调用迭代器时再继续。这与return不同,yield允许函数返回多个值(一个接一个),且不需要全部生成完再返回,节省内存。

5. 设置小批量大小并迭代数据

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
思路:

设定batch_size为10,然后使用data_iter迭代器获取一个小批量的数据,并打印出来。

语法解释:
  • batch_size = 10: 将batch_size设置为10,表示每次从数据集中抽取10个样本。

  • for X, y in data_iter(batch_size, features, labels):: 使用for循环迭代data_iter生成的批次数据。每次循环时,X表示特征数据的小批量,y表示对应的标签数据。

  • print(X, '\n', y): 打印当前批次的小批量特征和标签数据。\n用于换行,以便特征和标签分开显示。

  • break: break语句中断循环,这里是为了只查看第一个小批量的数据而终止循环。

这段代码通过data_iter函数实现了小批量数据的随机读取功能,为后续的模型训练提供了基础。yield的使用使得迭代器可以逐批生成数据,而不需要一次性加载所有数据,从而节省内存并提高计算效率。

w = torch.normal(0, 0.01,size=(2,1),requires_grad=True)
b = torch.zeros(1,requires_grad=True)

这段代码的主要目的是初始化线性模型的参数 w(权重)和 b(偏置),并设置它们为需要计算梯度,以便在模型训练时可以通过反向传播算法进行参数更新。接下来,我将详细解释每一行的语法以及这些语句的作用。

1. 初始化权重向量 w

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
思路:

这行代码用于创建一个形状为 (2, 1) 的张量 w,其中的每个元素是服从均值为 0、标准差为 0.01 的正态分布的随机数。并且设置 w 需要计算梯度(即 requires_grad=True),以便在训练过程中更新 w 的值。

语法解释:
  • torch.normal(0, 0.01, size=(2, 1)):

    • torch.normalPyTorch 中用于生成符合正态分布(高斯分布)的随机数的函数。
    • 第一个参数 0 是正态分布的均值。
    • 第二个参数 0.01 是正态分布的标准差。
    • size=(2, 1) 指定生成的张量的形状为 (2, 1),即包含 2 行 1 列的矩阵。
  • requires_grad=True:

    • 这是 torch.tensor 中的一个重要参数。当 requires_grad=True 时,PyTorch 会追踪这个张量上的所有操作,以便在后续的反向传播(backpropagation)过程中计算梯度。
    • 这样,w 可以在训练过程中根据损失函数的梯度进行优化。

2. 初始化偏置 b

b = torch.zeros(1, requires_grad=True)
思路:

这行代码用于创建一个标量张量 b,初始值为 0,并且设置为需要计算梯度。b 通常作为线性模型中的偏置项。

语法解释:
  • torch.zeros(1):

    • torch.zerosPyTorch 中用于生成全为 0 的张量的函数。
    • 1 表示生成一个包含单个元素的张量,即一个标量张量。
  • requires_grad=True:

    • w 一样,b 也设置了 requires_grad=True,表示需要在训练过程中计算它的梯度,以便优化偏置的值。

通过这两行代码,模型的参数 wb 被初始化并设置为可训练的张量。w 被初始化为一个服从均值为 0、标准差为 0.01 的正态分布的随机数矩阵,形状为 (2, 1),表示一个线性模型中的权重向量。而 b 被初始化为 0,表示模型的偏置。两者都设置了 requires_grad=True,以便在训练过程中,通过计算损失函数对 wb 的梯度,来更新它们的值,使模型能够更好地拟合数据。

在定义线性模型的权重 w 时,选择将其设置为 2x1 形状的张量是基于具体的任务和数据的特征维度。下面详细解释为什么选择 2x1 形状的权重矩阵,以及这与数据的特征维度和模型的结构之间的关系。

背景

假设我们正在构建一个线性回归模型,其数学表达式为:
[ y = Xw + b ]

其中:

  • ( X ) 是输入特征矩阵。
  • ( w ) 是权重向量。
  • ( b ) 是偏置。
  • ( y ) 是输出标签。

权重矩阵的形状

  1. 特征维度与权重矩阵的形状

    在你的代码中,特征矩阵 X 是通过以下代码生成的:

    X = torch.normal(0, 1, (num_examples, len(w)))
    

    这里 len(w) 是权重矩阵 w 的行数,意味着特征矩阵 X 的列数等于权重矩阵 w 的行数。换句话说,特征矩阵 X 的每一行都有与权重向量 w 相同的维度。

    • w 的形状是 (2, 1),其中 2 表示特征的数量,1 表示输出的数量(即每个样本有一个输出)。
  2. 为什么 w(2, 1)

    • 特征数量:在这个例子中,我们假设特征矩阵 X 有 2 列,因此权重矩阵 w 的行数是 2。这表示我们有 2 个特征,因此每个特征都有一个对应的权重。
    • 输出数量w 的列数为 1 表示我们的模型输出一个值。这通常是回归任务中的单一预测值。即使我们有多个特征,模型最终的预测输出是一个标量(单一值)。
  3. 模型的结构

    • 线性模型:在简单的线性回归模型中,预测值是特征和权重的线性组合加上偏置项:
      [ y = Xw + b ]
    • 维度匹配:为了使得矩阵乘法有效,X 的列数(特征数量)必须与 w 的行数匹配。X(num_examples, 2),所以 w 应该是 (2, 1),以使得 torch.matmul(X, w) 的结果是一个形状为 (num_examples, 1) 的张量,与 y 的期望形状一致。

示例

假设我们有 1000 个样本,每个样本有 2 个特征:

  • 特征矩阵 X 的形状是 (1000, 2)
  • 权重矩阵 w 的形状是 (2, 1)

计算 y 时:
[ y = Xw + b ]

  • X 的形状是 (1000, 2)
  • w 的形状是 (2, 1)
  • 结果 Xw 的形状是 (1000, 1),即 1000 个样本,每个样本一个预测值。

这样设置权重矩阵 w(2, 1) 是为了符合特征维度与模型输出的要求,使得线性回归模型能够正确地进行矩阵乘法并生成预测值。

def linereg(X,w,b):
    "线性回归模型"
    return torch.matmul(X,w) + b

这段代码定义了一个简单的线性回归模型的函数 linereg。这个函数计算线性回归模型的预测值,具体的数学表达式是 ( y = Xw + b )。下面我将详细解释每一部分的含义以及语法。

函数定义

def linereg(X, w, b):
    "线性回归模型"
    return torch.matmul(X, w) + b
1. 函数声明
def linereg(X, w, b):
  • def 关键字用于定义一个函数。
  • linereg 是函数名,表示“线性回归”。
  • (X, w, b) 是函数的参数:
    • X 是输入特征矩阵,形状应为 (num_examples, num_features)
    • w 是权重矩阵,形状应为 (num_features, 1)
    • b 是偏置,通常是标量(形状为 (1,)),也可以是 (1, 1) 的张量。
2. 计算预测值
return torch.matmul(X, w) + b
  • torch.matmul(X, w):

    • torch.matmulPyTorch 中用于矩阵乘法的函数。它计算矩阵 X 和矩阵 w 的乘积。
    • X 的形状是 (num_examples, num_features)w 的形状是 (num_features, 1),矩阵乘法的结果是一个形状为 (num_examples, 1) 的张量,表示每个样本的线性组合。
  • + b:

    • 这是将偏置 b 加到每个样本的预测值上。b 的形状是 (1,)(1, 1),在广播机制的帮助下,会被加到每个样本的预测值上。

数学背景

对于线性回归模型,其预测值的计算公式为:

y = Xw + b

  • X 是特征矩阵,每行表示一个样本,每列表示一个特征。
  • w 是权重向量,每个特征都有一个对应的权重。
  • b 是偏置项,通常是一个标量,用于调整模型的输出。

示例

假设我们有以下数据:

  • X 是一个形状为 (3, 2) 的张量,表示有 3 个样本,每个样本有 2 个特征。
  • w 是一个形状为 (2, 1) 的张量,表示每个特征有一个对应的权重。
  • b 是一个形状为 (1,) 的张量,表示模型的偏置。

使用 linereg(X, w, b) 计算得到的结果是一个形状为 (3, 1) 的张量,其中每个元素是对应样本的预测值。

linereg 函数实现了线性回归模型的预测计算。它通过矩阵乘法将特征矩阵 X 与权重矩阵 w 相乘,并加上偏置 b,计算得到每个样本的预测值。这个函数是实现线性回归模型的核心部分,用于生成模型的预测输出。

在进行线性回归模型计算时,矩阵的乘法顺序非常重要,因为它涉及到特征矩阵、权重矩阵以及它们的维度。具体地,对于线性回归模型 y = Xw + b ,我们需要确保矩阵乘法的顺序和维度匹配正确。下面我将详细解释为什么使用 torch.matmul(X, w) 是正确的,而不是 torch.matmul(w, X)

矩阵乘法的顺序和维度

在进行矩阵乘法时,矩阵的维度必须匹配。对于线性回归模型,我们需要计算的是:
y = X w + b y = Xw + b y=Xw+b

其中:

  • X 是特征矩阵,形状为 ( n u m _ e x a m p l e s , n u m _ f e a t u r e s ) (num\_examples, num\_features) (num_examples,num_features)
  • w 是权重矩阵,形状为 ( n u m _ f e a t u r e s , 1 ) (num\_features, 1) (num_features,1)
  • b 是偏置项,形状通常为 ( 1 , ) (1,) (1,) ( 1 , 1 ) (1, 1) (1,1)
正确的矩阵乘法顺序:torch.matmul(X, w)
  1. 特征矩阵 ( X ) 和权重矩阵 ( w ) 的维度

    • X 的形状为 (num_examples, num_features)
    • w 的形状为 (num_features, 1)
  2. 矩阵乘法的条件

    • 要进行矩阵乘法 A ⋅ B A \cdot B AB ,矩阵 A 的列数必须等于矩阵 B \的行数。
    • 在这里,X 的列数是 n u m _ f e a t u r e s num\_features num_features,而 w 的行数也是 n u m _ f e a t u r e s num\_features num_features。因此, X 和 w 的矩阵乘法是有效的。
  3. 结果

    • 乘法 X ⋅ w X \cdot w Xw 的结果是一个形状为 ( n u m _ e x a m p l e s , 1 ) (num\_examples, 1) (num_examples,1) 的张量,表示每个样本的线性组合。
错误的矩阵乘法顺序:torch.matmul(w, X)
  1. 维度不匹配

    • ( w ) 的形状是 ( (num_features, 1) )
    • ( X ) 的形状是 ( (num_examples, num_features) )
    • 如果尝试执行 ( w \cdot X ) 的乘法,矩阵 ( w ) 的列数是 1,但矩阵 ( X ) 的行数是 ( num_examples ),这不符合矩阵乘法的条件。
  2. 结果不符合预期

    • 即使维度匹配(如果 w 和 X 的维度恰好可以乘),结果的形状和含义都与线性回归模型的需求不符。

数学背景

在数学上,对于线性回归模型:
y = Xw + b

  • 矩阵乘法 Xw 中, X 是 ( n u m _ e x a m p l e s , n u m _ f e a t u r e s ) (num\_examples, num\_features) (num_examples,num_features),而 w 是 ( n u m _ f e a t u r e s , 1 ) (num\_features, 1) (num_features,1)。乘法结果是 ( n u m _ e x a m p l e s , 1 ) (num\_examples, 1) (num_examples,1),即每个样本的预测值。
  • 偏置项 b 加到每个预测值上,使得结果的形状保持为 ( n u m _ e x a m p l e s , 1 ) (num\_examples, 1) (num_examples,1)

使用 torch.matmul(X, w) 是正确的,因为它符合矩阵乘法的规则和线性回归模型的数学定义。X 的形状与 w 的形状在矩阵乘法中正确匹配,从而得到符合模型需求的输出。torch.matmul(w, X) 是不合适的,因为它的矩阵维度不匹配,不能执行有效的矩阵乘法。

def squraed_loss(y_hat,y):
    "均方损失"
    return (y_hat - y.reshape(y_hat.shape))**2 / 2

这段代码定义了一个计算均方损失(Mean Squared Error, MSE)的函数 squared_loss。均方损失用于衡量预测值与实际值之间的差异。具体来说,这个函数计算的是损失的平方和,并对每个样本的损失进行平均。以下是对代码的详细解释:

函数定义

def squared_loss(y_hat, y):
    "均方损失"
    return (y_hat - y.reshape(y_hat.shape))**2 / 2
1. 函数声明
def squared_loss(y_hat, y):
  • def 是定义函数的关键字。
  • squared_loss 是函数名,表示“均方损失”。
  • (y_hat, y) 是函数的参数:
    • y_hat 是模型的预测值。
    • y 是实际的目标值(标签)。
2. 计算均方损失
return (y_hat - y.reshape(y_hat.shape))**2 / 2
  • y.reshape(y_hat.shape):

    • y 是实际的目标值,其形状可能与 y_hat 不一致。
    • y.reshape(y_hat.shape) 用于将 y 的形状调整为与 y_hat 相同,以便进行逐元素运算。reshape 会返回一个新的张量,具有相同的元素,但形状是 y_hat.shape
  • (y_hat - y.reshape(y_hat.shape))**2:

    • 计算预测值 y_hat 和调整形状后的实际值 y 之间的差异,并对每个元素进行平方操作。这表示每个样本的预测误差的平方。
  • / 2:

    • 对平方差进行缩放,即将每个样本的平方差除以 2。这个因子 2 是在计算均方损失时常用的缩放因子。这个因子的作用是使得梯度计算时的导数更为简单。具体来说,均方损失的导数在计算时会去掉 2,从而简化计算过程。

数学背景

均方损失的数学公式通常是:

MSE = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 \text{MSE} = \frac{1}{2n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 MSE=2n1i=1n(yiy^i)2

其中:

  • y i y_i yi 是实际值
  • y ^ i \hat{y}_i y^i 是预测值
  • n n n 是样本数量

在这个公式中,1/2 的因子用于简化梯度的计算,因为在求梯度时,平方操作的导数会产生一个额外的 2。这样做可以使得最终的梯度计算更为简便。

示例

假设 y_haty 是两个形状为 (3, 1) 的张量(3 个样本,每个样本一个预测值):

  • y_hat 是模型的预测值。
  • y 是实际标签。

使用 squared_loss(y_hat, y) 计算损失时:

  1. 首先,y 会被调整为与 y_hat 相同的形状。
  2. 然后,计算每个样本的预测误差的平方。
  3. 最后,将每个平方差除以 2,得到均方损失。

_squared_loss 函数计算均方损失(MSE),它衡量了预测值与实际值之间的误差。函数的核心步骤包括调整实际值的形状、计算预测误差的平方,并将结果除以 2。这个损失函数在回归任务中非常常见,用于评估模型的预测效果。

def sgd(params,lr,batch_size):
    "小批量随机梯度下降"
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad_zero_()

这段代码实现了一个小批量随机梯度下降(Stochastic Gradient Descent, SGD)优化算法。SGD 是一种常用的优化方法,用于通过最小化损失函数来调整模型参数。以下是对代码的详细解释:

函数定义

def sgd(params, lr, batch_size):
    "小批量随机梯度下降"
  • def:定义函数的关键字。
  • sgd:函数名,表示“随机梯度下降”。
  • params:这是一个包含模型参数的列表,每个参数都是一个 torch.Tensor,例如权重 w 和偏置 b
  • lr:学习率(learning rate),控制每次更新参数的步长。值越大,参数更新的步长越大。
  • batch_size:批量大小,表示用于计算梯度的样本数量。通常在训练过程中使用小批量样本来更新参数。

关键部分:参数更新

with torch.no_grad():
    for param in params:
        param -= lr * param.grad / batch_size
        param.grad.zero_()
1. with torch.no_grad():
  • 这一行启用了一个上下文管理器,用于临时禁用梯度计算。
  • PyTorch 中,当我们不希望 autograd(自动微分引擎)记录计算历史(如更新参数时),我们可以使用 torch.no_grad()。这不仅提高了计算效率,还避免了不必要的内存使用。
2. for param in params:
  • 这是一个 for 循环,用于遍历 params 列表中的每一个参数。params 通常是模型的所有可训练参数的集合(如权重和偏置)。
3. param -= lr * param.grad / batch_size
  • 这一行是关键的参数更新步骤。每个参数 param 都会根据计算出的梯度 param.grad 进行更新。
  • param.grad 是通过反向传播计算得到的损失函数相对于 param 的梯度。
  • 更新公式
    • param -= lr * param.grad / batch_size
    • 这里的 param.grad / batch_size 是梯度的平均值。因为梯度通常是在一个批次的数据上计算的,因此需要除以批量大小来计算平均梯度。
    • lr * param.grad / batch_size 表示按比例缩放后的梯度步长(步长由学习率 lr 控制)。
    • param -= ... 表示从当前参数值中减去缩放后的梯度,更新参数,使其朝着最小化损失的方向移动。
4. param.grad.zero_()
  • 这一行用于将当前参数的梯度清零。
  • PyTorch 中,梯度默认是累加的,因此在每次参数更新后需要手动将其清零,以免梯度累积影响下一次更新。
  • zero_() 是一个就地操作(in-place operation),它直接修改原张量,将其所有元素重置为 0

这个 sgd 函数实现了小批量随机梯度下降的核心步骤。具体来说,它根据学习率和计算出的梯度更新模型的每个参数,并在每次更新后清除参数的梯度。以下是每个步骤的作用:

  1. 禁用梯度计算:为了避免在参数更新时误触发梯度计算。
  2. 遍历参数:逐个更新每个模型参数。
  3. 更新参数:通过减去学习率缩放后的平均梯度来调整参数。
  4. 清除梯度:为下一次计算准备,防止梯度累加。

这是神经网络训练中的一个基本步骤,通过逐步优化模型的参数,使模型更好地拟合训练数据。

完整代码如下所示:

import sys; print('Python %s on %s' % (sys.version, sys.platform))
import random
import torch
from d2l import torch as d2l

def synthetic_data(w,b,num_examples):
    "生成 y = Wx + 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)
print('features:',features[0],'\n labels:',labels[0])

import matplotlib.pyplot as plt
d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),1);
plt.show()

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 linereg(X,w,b):
    "线性回归模型"
    return torch.matmul(X,w) + b
def squraed_loss(y_hat,y):
    "均方损失"
    return (y_hat - y.reshape(y_hat.shape))**2 / 2
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
lr = 0.03
num_epochs = 3
net = linereg
loss = squraed_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()
        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}')

print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}'

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐