%matplotlib inline
import random
import torch
from d2l import torch as d2l
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))
#这个返回X,y
#真实的模型参数
true_w=torch.tensor([2,-3.4])
true_b=4.2
features,labels=synthetic_data(true_w,true_b,1000)
#将生成的X,y赋给features,labels
在这段代码中,features
和 X
是同一个对象的两个名称,它们表示生成的特征矩阵,即输入数据集中的特征部分。同样地,labels
和 y
也是同一个对象的两个名称,它们表示生成的标签,即数据集中的目标值部分。
具体来说:
-
features
和X
都表示形状为(num_examples, len(w))
的特征矩阵,其中num_examples
是样本数量,len(w)
是特征数量。它们包含了每个样本的特征信息。 -
labels
和y
都表示形状为(num_examples, 1)
的标签向量,其中num_examples
是样本数量。它们包含了每个样本对应的目标值。
在 synthetic_data
函数中:
-
X
表示特征矩阵,使用torch.normal
生成,服从均值为0、标准差为1的正态分布。 -
y
表示标签向量,通过计算线性关系y = Xw + b
得到,并在此基础上添加了服从均值为0、标准差为0.01的正态分布噪声。
因此,features
和 X
是相同的特征矩阵,labels
和 y
是相同的标签向量,它们在功能上是一致的,只是在命名上有所不同。
print('features',features[0],'\nlabel',labels[0])
features tensor([-0.0055, 1.8867])
label tensor([-2.2313])
d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),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]
batch_size=10
for X,y in data_iter(batch_size,features,labels):
print(X,'\n',y)
break
tensor([[-0.3633, -1.6565],
[ 1.8698, 0.2385],
[-0.3722, 0.1533],
[-0.7317, 0.7230],
[ 0.7867, -0.3687],
[ 1.3138, 0.3436],
[ 0.8347, -0.8331],
[ 0.0134, 2.7274],
[ 0.0826, -2.3189],
[ 1.3994, -0.3417]])
tensor([[ 9.1042],
[ 7.1250],
[ 2.9343],
[ 0.2554],
[ 7.0254],
[ 5.6730],
[ 8.7017],
[-5.0567],
[12.2584],
[ 8.1573]])
小批量梯度下降(Mini-batch Gradient Descent)是梯度下降算法的一种变体,用于优化机器学习模型的参数。与标准的梯度下降相比,小批量梯度下降在更新模型参数时不是使用整个训练数据集的梯度,而是使用数据集的一个小子集(称为“批量”或“小批量”)的梯度。这个小子集的大小就是所谓的批量大小(batch_size
)。
理解 batch_size
:
batch_size
是指每次迭代中所用的样本数量。在小批量梯度下降中,整个训练数据集被分成若干个大小相等的批次,每个批次包含的样本数量即为 batch_size
。例如,如果整个数据集有1000个样本,batch_size
设置为10,则整个数据集会被划分为100个大小为10的批次。
小批量梯度下降的步骤:
-
初始化参数: 随机初始化模型的参数(例如权重和偏置)。
-
分批次处理数据集: 将整个训练数据集划分为若干个大小为
batch_size
的批次。 -
对每个批次进行计算: 对于每个批次,计算该批次数据的损失函数,并根据损失函数计算参数的梯度。
-
更新模型参数: 根据计算得到的梯度,使用优化算法(如梯度下降)来更新模型的参数。
-
重复迭代: 重复以上步骤,直到满足停止条件(如达到最大迭代次数或损失函数收敛)。
优点:
-
计算效率高: 相较于批量梯度下降,小批量梯度下降可以利用现代计算设备的并行计算能力,提高训练速度。
-
更稳定的更新方向: 每次迭代使用的梯度估计更具有代表性,有助于避免训练过程中出现的局部极小值。
总结:
小批量梯度下降通过批量处理数据,结合了标准梯度下降和随机梯度下降的优点,既保持了计算效率,又保证了更新方向的稳定性,因此被广泛应用于深度学习和其他机器学习任务中。
核心就是:与标准的梯度下降相比,小批量梯度下降在更新模型参数时不是使用整个训练数据集的梯度,而是使用数据集的一个小子集(称为“批量”或“小批量”)的梯度。
#初始化模型参数
w=torch.normal(0,0.01,size=(2,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)
#requires_grad=True 表示设置偏置为可自动求导的状态,以便在训练过程中更新偏置参数时,能够计算偏置的梯度。
这里的 w
和 b
是模型的参数,是模型需要学习的变量,而 true_w
和 true_b
则是真实的模型参数,是用来生成数据的参数。它们之间的区别如下:
-
w
和b
:w
和b
是模型的参数,通常用来表示线性模型中的权重和偏置。在训练过程中,模型通过优化算法(如梯度下降)不断调整w
和b
的值,使得模型的预测结果更接近真实标签。
-
true_w
和true_b
:true_w
和true_b
是真实的模型参数,用于生成模拟数据时的参数。在数据生成过程中,使用了这些参数来构建一个线性关系,生成模拟的特征和标签数据。
总的来说,w
和 b
是模型在训练过程中学习得到的参数,而 true_w
和 true_b
则是用于模拟数据生成的真实参数。在实际应用中,我们通常无法直接获得真实的模型参数,而是通过训练模型来拟合数据,从而得到模型参数 w
和 b
。
def linreg(X,w,b):
"""线性回归模型"""
return torch.matmul(X,w)+b
def squared_loss(y_hat,y):
""" 均方损失 """
return (y_hat-y.reshape(y_hat.shape))**2/2
#实际上就是一损失函数
def sgd(params, lr, batch_size):
"""
小批量随机梯度下降(Stochastic Gradient Descent,SGD)
参数:
params: iterable of torch.Tensor
待优化参数的集合,例如模型的权重和偏置。
lr: float
学习率(learning rate),用于控制参数更新的步长。
batch_size: int
批量大小(batch size),用于计算梯度的样本数量。
返回值:
None
注意:
这个函数会修改传入参数中的梯度值,并更新参数的数值。
"""
with torch.no_grad():
# 遍历所有待优化参数
for param in params:
# 根据梯度和学习率更新参数值(使用小批量梯度下降算法)
param -= lr * param.grad / batch_size
# 清零参数的梯度,以便进行下一次迭代
param.grad.zero_()
当我们计算梯度的时候,我们需要缓存前向传播过程中大量的中间输出,因为在反向传播pytoch自动计算梯度时需要用到这些值。而我们在测试时,我们不需要计算梯度,那么也就意味着我们不需要在forward的时候保存这些中间输出。此外,在测试阶段,我们也不需要构造计算图(这也需要一定的存储开销)。Pytorch为我们提供了一个上下文管理器,torch.no_grad,在with torch.no_grad() 管理的环境中进行计算,不会生成计算图,不会存储为计算梯度而缓存的中间值。
lr=0.01
num_epochs=10
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()
sgd([w,b],lr,batch_size)
with torch.no_grad():
train_1=loss(net(features,w,b),labels)
print(f'epoch{epoch+1},loss{float(train_1.mean()):f}')
epoch1,loss2.254035
epoch2,loss0.318367
epoch3,loss0.045817
epoch4,loss0.006705
epoch5,loss0.001033
epoch6,loss0.000198
epoch7,loss0.000072
epoch8,loss0.000053
epoch9,loss0.000050
epoch10,loss0.000050
为什么更新的时候不要参与梯度计算?
在参数更新时不参与梯度计算的原因是为了避免更新过程中对参数梯度的计算。当我们使用梯度下降法更新参数时,我们的目的是根据当前梯度来调整参数值,以最小化损失函数。这一过程应该是一个简单的数学操作,不应该被视为模型的一部分或影响模型的梯度计算图。
如果在参数更新时允许计算梯度,那么更新操作本身(如param -= lr * param.grad / batch_size)会被认为是模型的一部分,并且会影响后续梯度的计算,这显然是我们不希望的。因此,使用torch.no_grad()上下文管理器暂时禁用梯度计算,确保这一更新操作不会影响到计算图和后续梯度的计算。
为什么需要手动地把梯度设置成0?
在PyTorch中,梯度是累加的。这意味着每次调用.backward()时,计算得到的梯度会被加到已存在的梯度上。这样设计是出于计算效率和便利性的考虑,特别是在需要计算复杂表达式导数时。然而,这也意味着在每次进行参数更新之前,我们需要手动将梯度清零,以防止梯度信息在不同批次之间相互干扰。(这里是在参数更新之后清零,其实也是一个意思)
如果不将梯度归零,则每次执行.backward()时,梯度会在原有的基础上累加,导致每一批数据的梯度不是基于其自身的损失计算的,而是包含了前面所有批次的梯度信息,这将导致参数更新方向错误,严重影响模型训练的效果。
如何理解“在PyTorch中,梯度是累加的。这意味着每次调用.backward()时,计算得到的梯度会被加到已存在的梯度上”?
在PyTorch中,梯度累加的机制是指,当你对一个计算图中的张量调用.backward()方法时,得到的梯度不会替换掉张量当前的.grad属性值,而是会加上去。这个设计主要是为了方便在一些特定的场景下,比如在RNN(递归神经网络)的训练中,或者当你想要在一个批次中累加多个子批次(mini-batches)的梯度时。
这里是一个简单的例子来说明这个概念:
假设有一个参数张量param,其初始梯度(如果有的话)是0。当你第一次对某个损失函数loss1调用loss1.backward()时,param的梯度会根据loss1对param的导数被计算并存储在param.grad中。现在,如果你再次对另一个损失函数loss2调用loss2.backward()而没有在这两次调用之间手动清零param.grad,那么loss2对param的导数就会加到param.grad上,而不是替换它。
为什么需要梯度累加?
梯度累加提供了一种灵活的方式来处理不同的训练需求,比如:
内存限制:对于非常大的模型或非常大的输入数据,可能没有足够的内存一次性处理整个批次的数据。在这种情况下,可以将一个大的批次分成几个小批次,分别计算每个小批次的梯度,并让它们累加起来,最后进行一次参数更新。
不同来源的梯度:在一些复杂的模型训练中,可能希望从不同的损失函数或数据集中累积梯度,然后基于累积的梯度进行一次参数更新。
为什么需要梯度累加?
梯度累加提供了一种灵活的方式来处理不同的训练需求,比如:
内存限制:对于非常大的模型或非常大的输入数据,可能没有足够的内存一次性处理整个批次的数据。在这种情况下,可以将一个大的批次分成几个小批次,分别计算每个小批次的梯度,并让它们累加起来,最后进行一次参数更新。
不同来源的梯度:在一些复杂的模型训练中,可能希望从不同的损失函数或数据集中累积梯度,然后基于累积的梯度进行一次参数更新。
原文链接:https://blog.youkuaiyun.com/weixin_45798993/article/details/136032226
参考了这位博主的文章,他写得非常好。
print(f'w的估计误差:{true_w-w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b-b}')
w的估计误差:tensor([9.6560e-05, 3.9816e-04], grad_fn=<SubBackward0>)
b的估计误差:tensor([0.0004], grad_fn=<RsubBackward1>)
深度学习项目的基本流程。让我们详细解释每个步骤,并看看如何在实际项目中应用它们:
1. 生成数据(或者准备数据)
这是深度学习项目的第一步。数据可以从各种来源获取,例如文件、数据库或通过数据生成器。
示例代码:
import torch
# 生成数据集
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features = torch.randn((1000, 2))
labels = torch.matmul(features, true_w) + true_b
labels += torch.randn(labels.shape) * 0.01 # 加入一些噪声
2. 定义模型
选择和定义模型结构。对于简单的线性回归,这是一个线性模型;对于更复杂的任务,可能需要使用深度神经网络。
示例代码:
def linreg(X, w, b):
return torch.matmul(X, w) + b
3. 定义超参数
设置训练过程中使用的超参数,如学习率、批量大小和训练的轮数。
示例代码:
lr = 0.03 # 学习率
num_epochs = 3 # 训练轮数
batch_size = 10 # 批量大小
4. 定义损失函数
选择适合的损失函数来衡量模型的性能。常见的损失函数包括均方误差(MSE)和交叉熵损失。
示例代码:
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
5. 定义优化算法
选择优化算法来更新模型参数,例如随机梯度下降(SGD)、Adam等。
示例代码:
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
param.grad.zero_()
6. 初始化模型参数
初始化模型参数,通常使用随机数或其他适当的分布。
示例代码:
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
7. 训练模型
将数据分成批量,并在每个批量上计算损失、执行反向传播和更新参数。重复这些步骤多个轮数,直到模型收敛。
示例代码:
from torch.utils.data import DataLoader, TensorDataset
# 将数据集转换为DataLoader
dataset = TensorDataset(features, labels)
data_iter = DataLoader(dataset, batch_size, shuffle=True)
for epoch in range(num_epochs):
for X, y in data_iter:
l = squared_loss(linreg(X, w, b), y).sum()
l.backward()
sgd([w, b], lr, batch_size)
with torch.no_grad():
train_l = squared_loss(linreg(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
总结
这些步骤是构建和训练深度学习模型的基本流程。每个步骤都涉及具体的任务和决策,从数据准备到模型训练,每一步都对最终模型的性能和效果至关重要。通过这些步骤,可以系统地设计、实现和优化深度学习项目。