李沐动手学深度学习 - 线性回归代码最详细讲解
linereg 函数实现了线性回归模型的预测计算。它通过矩阵乘法将特征矩阵 X 与权重矩阵 w 相乘,并加上偏置 b,计算得到每个样本的预测值。
代码讲解
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. 导入必要的库
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)在研究测量误差时提出。正态分布的概率密度函数具有特定的数学形式,其形状为对称的钟形曲线,具有以下特点:
-
对称性:正态分布是关于其均值对称的,即分布的左侧和右侧是镜像对称的。
-
均值、中位数和众数相同:在正态分布中,这三个统计量是相等的。
-
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π1e−2σ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 + b
。torch.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))
: 函数返回X
和y
。y.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.4
。torch.tensor
是用来创建一个torch
张量的函数。 -
true_b = 4.2
: 设定真实的偏置true_b
为4.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
和标签labels
。features
是一个形状为(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)
生成一个从0
到num_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
个样本的索引,并根据这些索引从features
和labels
中提取对应的小批量数据。
语法解释:
-
for i in range(0, num_examples, batch_size):
: 这是一个for
循环,range(0, num_examples, batch_size)
生成一个从0
到num_examples
的整数序列,步长为batch_size
。i
的取值表示当前批次开始的索引。 -
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.normal
是PyTorch
中用于生成符合正态分布(高斯分布)的随机数的函数。- 第一个参数
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.zeros
是PyTorch
中用于生成全为0
的张量的函数。1
表示生成一个包含单个元素的张量,即一个标量张量。
-
requires_grad=True
:- 和
w
一样,b
也设置了requires_grad=True
,表示需要在训练过程中计算它的梯度,以便优化偏置的值。
- 和
通过这两行代码,模型的参数 w
和 b
被初始化并设置为可训练的张量。w
被初始化为一个服从均值为 0
、标准差为 0.01
的正态分布的随机数矩阵,形状为 (2, 1)
,表示一个线性模型中的权重向量。而 b
被初始化为 0
,表示模型的偏置。两者都设置了 requires_grad=True
,以便在训练过程中,通过计算损失函数对 w
和 b
的梯度,来更新它们的值,使模型能够更好地拟合数据。
在定义线性模型的权重 w
时,选择将其设置为 2x1
形状的张量是基于具体的任务和数据的特征维度。下面详细解释为什么选择 2x1
形状的权重矩阵,以及这与数据的特征维度和模型的结构之间的关系。
背景
假设我们正在构建一个线性回归模型,其数学表达式为:
[ y = Xw + b ]
其中:
- ( X ) 是输入特征矩阵。
- ( w ) 是权重向量。
- ( b ) 是偏置。
- ( y ) 是输出标签。
权重矩阵的形状
-
特征维度与权重矩阵的形状
在你的代码中,特征矩阵
X
是通过以下代码生成的:X = torch.normal(0, 1, (num_examples, len(w)))
这里
len(w)
是权重矩阵w
的行数,意味着特征矩阵X
的列数等于权重矩阵w
的行数。换句话说,特征矩阵X
的每一行都有与权重向量w
相同的维度。w
的形状是(2, 1)
,其中2
表示特征的数量,1
表示输出的数量(即每个样本有一个输出)。
-
为什么
w
是(2, 1)
- 特征数量:在这个例子中,我们假设特征矩阵
X
有 2 列,因此权重矩阵w
的行数是 2。这表示我们有 2 个特征,因此每个特征都有一个对应的权重。 - 输出数量:
w
的列数为 1 表示我们的模型输出一个值。这通常是回归任务中的单一预测值。即使我们有多个特征,模型最终的预测输出是一个标量(单一值)。
- 特征数量:在这个例子中,我们假设特征矩阵
-
模型的结构
- 线性模型:在简单的线性回归模型中,预测值是特征和权重的线性组合加上偏置项:
[ 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.matmul
是PyTorch
中用于矩阵乘法的函数。它计算矩阵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)
-
特征矩阵 ( X ) 和权重矩阵 ( w ) 的维度:
- X 的形状为 (num_examples, num_features)
- w 的形状为 (num_features, 1)
-
矩阵乘法的条件:
- 要进行矩阵乘法 A ⋅ B A \cdot B A⋅B ,矩阵 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 的矩阵乘法是有效的。
-
结果:
- 乘法 X ⋅ w X \cdot w X⋅w 的结果是一个形状为 ( n u m _ e x a m p l e s , 1 ) (num\_examples, 1) (num_examples,1) 的张量,表示每个样本的线性组合。
错误的矩阵乘法顺序:torch.matmul(w, X)
-
维度不匹配:
- ( w ) 的形状是 ( (num_features, 1) )
- ( X ) 的形状是 ( (num_examples, num_features) )
- 如果尝试执行 ( w \cdot X ) 的乘法,矩阵 ( w ) 的列数是 1,但矩阵 ( X ) 的行数是 ( num_examples ),这不符合矩阵乘法的条件。
-
结果不符合预期:
- 即使维度匹配(如果 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=2n1∑i=1n(yi−y^i)2
其中:
- y i y_i yi 是实际值
- y ^ i \hat{y}_i y^i 是预测值
- n n n 是样本数量
在这个公式中,1/2
的因子用于简化梯度的计算,因为在求梯度时,平方操作的导数会产生一个额外的 2。这样做可以使得最终的梯度计算更为简便。
示例
假设 y_hat
和 y
是两个形状为 (3, 1)
的张量(3 个样本,每个样本一个预测值):
y_hat
是模型的预测值。y
是实际标签。
使用 squared_loss(y_hat, y)
计算损失时:
- 首先,
y
会被调整为与y_hat
相同的形状。 - 然后,计算每个样本的预测误差的平方。
- 最后,将每个平方差除以 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
函数实现了小批量随机梯度下降的核心步骤。具体来说,它根据学习率和计算出的梯度更新模型的每个参数,并在每次更新后清除参数的梯度。以下是每个步骤的作用:
- 禁用梯度计算:为了避免在参数更新时误触发梯度计算。
- 遍历参数:逐个更新每个模型参数。
- 更新参数:通过减去学习率缩放后的平均梯度来调整参数。
- 清除梯度:为下一次计算准备,防止梯度累加。
这是神经网络训练中的一个基本步骤,通过逐步优化模型的参数,使模型更好地拟合训练数据。
完整代码如下所示:
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}'
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)