1-3 动手学深度学习v2-线性回归的从零开始实现-笔记

手动创建训练数据集

根据带有噪声的线性模型构造一个人造数据集。我们使用线性模型参数 w = [ 2 , − 3.4 ] T \pmb{w} = [2,-3.4]^{T} w=[2,3.4]T b = 4.2 b = 4.2 b=4.2和噪声项 ϵ \epsilon ϵ生成数据集及其标签:
y = X w + b + ϵ \pmb{y} = \pmb{Xw}+b+\epsilon y=Xw+b+ϵ

%matplotlib inline # 在plot的时候,默认嵌入到notebook里面
import random # 随机梯度下降/随机初始权重 会用到
import torch
from d2l import torch as d2l # torch的一些模版# 生成 y = Xw + b + 噪声
def synthetic_data(w, b, num_examples):     X = torch.normal(0, 1, (num_examples, len(w)))# 均值为0,标准差为1的随机数,样本数(行数)为num_examples,列数为列向量w的行数y = torch.matmul(X, w) + b # y = Xw + by += torch.normal(0, 0.01, y.shape)# 为了让问题难一点,我们引入了随机噪声,均值为0,标准差为0.01,矩阵形状长的和y一样,效果也就是在y现有基础上,每个位置上的元素加上随机初始化的噪声值return X, y.reshape((-1, 1))#返回数据样本矩阵X,标签向量ytrue_w = torch.tensor([2, -3.4]) # 这个人造数据集真实的w
# 创建一个列向量
true_b = 4.2 # 这个人造数据集真实的b
features, labels = synthetic_data(true_w, true_b, 1000)
# 相当于是创建了1000个样本,每个样本有两个特征,这些所有的样本的真实w和b为上述,同时也引入了随机噪声print('feature:', features[0], '\nlabel:', labels[0])
# 输出第0个样本的特征和标签,即具体样本值长什么样,就是X,即对应的标签是多少,即y

y.reshape((-1, 1))这行代码在Python中通常用于NumPy数组或类似的数据结构,其目的是改变数组y的形状(shape)。这里的reshape方法用于给数组一个新的形状,而不改变其数据。

  • -1在reshape方法中被用作一个特殊值,它表示该维度的大小应该被自动计算,以便保持数组中元素的总数不变。换句话说,-1可以被理解为“自动推断出的大小”相当于我最后只要得到一个列向量就可以,具体有多少行,我一开始并不关心,直接先用-1替代,自动帮我算好就行
  • (1)表示新的形状应该有一个列,这意味着你想将数组y转换成一个列向量。
  • 总结一下,y.reshape((-1, 1))的作用是将y转换为一个列向量。如果y原本是一个一维数组,这个操作会使其变成一个二维数组,其中有多行但只有一列。

上述代码输出结果:

feature: tensor([-1.3127, -1.2715]) # 第0个样本的两个特征 (列向量)
label: tensor([5.8931]) # 它对应的标签值

plot出来看一下:

d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),1);
# 把特征的第一列(所有样本的第二个特征)拿出来     把标签拿出来,即y
# detach是指在python的一些版本中需要把它从pytorch的计算图中detach分离出来(但仍指向原始数据),才能通过.numpy()转成numpy数组,这是为了避免绘图操作影响梯度计算。
# scatter 绘制散点图
# x坐标:特征的第一列(所有样本的第二个特征) 
# y坐标:标签值,即y
# 最后的参数1可能指的是散点的大小。在matplotlib的scatter函数中,可以通过s参数指定点的大小。如果这里的1确实意在控制点的大小,那么它指的是使用很小的点来绘图。# 这行代码的作用是:使用features的第二个特征和labels作为坐标,绘制一个散点图,其中每个点的大小为1。这可以帮助可视化特征与标签之间的关系,是数据分析和机器学习中常用的一种方法。

请添加图片描述
我们可以看出,人造的数据集是具有线性相关性的。


实现随机抽取指定批量大小的样本的方法

定义一个data_iter函数,该函数接受批量大小特征矩阵标签向量作为输入,生成大小为batch_size的小批量。

def data_iter(batch_size, features, labels):num_examples = len(features) # 从特征矩阵的行数中得到样本数indices = list(range(num_examples)) # 生成每个样本的索引 0-n-1 再转成python list# 这些样本是随机读取的,没有特定的顺序random.shuffle(indices) # 把生成的索引list(即indices)的元素顺序完全打乱 这样我后面就可以用一个随机的顺序去访问样本for i in range(0, num_examples, batch_size): # 从0开始,到num_examples结束,步长为batch_sizebatch_indices = torch.tensor(indices[i:min(i + batch_size,num_examples)])# 从i开始,到min(i + batch_size,num_examples)结束,# 一般情况下就是i~i + batch_size,只有i + batch_size超出样本总数了,才会用到num_examplesyield features[batch_indices], labels[batch_indices] # yield 返回batch_size = 10for X, y in data_iter(batch_size, features, labels):print(X, '\n', y)break

在这段代码中,yield关键字的作用是把data_iter函数变成一个生成器(generator)。
生成器是一种特殊的迭代器,它允许你逐步产生(生成)值,而不是一次性返回所有值。使用yield的好处是它可以在每次产生一批数据时,暂停函数的执行,等到下一次迭代请求时再继续,从而节省内存并允许实时处理数据
运行一下,输出是:

tensor([[-2.7191e-01,  4.7745e-01],[ 9.1430e-01,  1.3391e+00],[ 1.1257e+00,  1.4340e+00],[ 4.5012e-01, -7.8356e-01],[ 6.7112e-01,  1.1518e-03],[ 1.2587e+00, -4.4198e-01],[ 5.3823e-01,  1.7816e-01],[ 5.5030e-01,  2.0622e-01],[-4.2540e-01,  8.0550e-01],[ 7.9017e-01, -1.3217e+00]]) tensor([[ 2.0360],[ 1.4640],[ 1.5675],[ 7.7668],[ 5.5319],[ 8.2097],[ 4.6658],[ 4.6141],[ 0.6039],[10.2707]])

由输出结果可知,我们成功实现了从总样本特征矩阵中随机抽取一个指定batch_size大小的小批量样本及其标签。


定义初始化模型参数

# 将w随机初始化为一个均值为0,标准差为0.01的,大小为2行1列,requires_grad=True表示我们需要计算梯度
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# 将偏差b初始化为0,requires_grad=True表示我们需要计算梯度
b = torch.zeros(1, requires_grad=True) 

定义模型

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# 虽然按理来说,y_hat和y应该元素个数一样,但是可能一个是行向量,一个是列向量,这里把他们统一成一样的,# 这样就可以直接在y和y_hat之间进行元素对元素的操作了,比如计算差值或者逐元素的乘法。# 这里没有作均值 没有除以样本总数# 我自己理解这里为什么没有除以样本总数,总结来说有几点:# 1. 作者不想让这个函数多一个样本数量这一传入参数# 2. 这里是将y_hat和y按位相减,然后再把差值平方,再除以二分之一,仔细看看,这里算出来的其实是一个列向量,而不是一个标量,# 其实就是算出来y的每一位的平方,还没有求和的操作,这是放到最后‘训练过程’中完成。# 3. 损失值后面是要求导用的,我们思考一下,被求导的这个数,它乘或除一个常数,这个常数是会一直跟着它的,不会丢掉。# 所以,这里少了一个除法算平均的过程,但是放到算导数(即梯度)的时候,记得除一下,也不迟,这里不除,还能让函数显得更简洁。

定义优化算法

def sgd(params, lr, batch_size):
# params 参数的list,包括w和b
# lr 学习率# 小批量随机梯度下降with torch.no_grad():     # 不需要计算梯度(更新的时候不要参与梯度计算)for param in params:  # 对参数里的每一个参数param -= lr * param.grad / batch_size  # 这里写batch_size其实是在算均值  param.grad参数的梯度param.grad.zero_() # 手动把梯度设置成0,下一次计算梯度的时候就不会和这次相关了

为什么更新的时候不要参与梯度计算?

在参数更新时不参与梯度计算的原因是为了避免更新过程中对参数梯度的计算。当我们使用梯度下降法更新参数时,我们的目的是根据当前梯度来调整参数值,以最小化损失函数。这一过程应该是一个简单的数学操作,不应该被视为模型的一部分或影响模型的梯度计算图。

如果在参数更新时允许计算梯度,那么更新操作本身(如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上,而不是替换它。

为什么需要梯度累加?

梯度累加提供了一种灵活的方式来处理不同的训练需求,比如:

  • 内存限制:对于非常大的模型或非常大的输入数据,可能没有足够的内存一次性处理整个批次的数据。在这种情况下,可以将一个大的批次分成几个小批次,分别计算每个小批次的梯度,并让它们累加起来,最后进行一次参数更新。
  • 不同来源的梯度:在一些复杂的模型训练中,可能希望从不同的损失函数或数据集中累积梯度,然后基于累积的梯度进行一次参数更新。

训练过程

lr = 0.03
num_epochs = 3  # 把整个数据扫三遍
net = linreg    # 模型
loss = squared_loss # 均方损失for epoch in range(num_epochs): # 遍历轮次for X, y in data_iter(batch_size, features, labels): # 拿出一个批量大小的X和yl = loss(net(X, w, b), y)  # 把X,w,b放进模型做预测,然后和标签值y算出损失# l的行数为批量大小,列数为1,l是向量,不是标量l.sum().backward() # l中的所有元素被加到一起求和,然后求梯度 l.sum()这才是损失函数的值,也即损失值的平方和sgd([w,b], lr,batch_size) # 利用梯度对w和b进行更新。# 这里的batch_size不是特别对的 因为万一样本数不能整除batch_size,最后一块可能不足batch_size的大小# 对数据扫完一遍后,我们来评估一下进度,而且评估的操作是不需要计算梯度的,所以我们把它放在no_grad里面with torch.no_grad():train_l = loss(net(features, w, b), labels) # 用完整的特征矩阵评估损失print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}') # 把损失print出来,因为上面定义的损失函数计算中没有算平均数,所以这里要手动调用mean算一下

控制台输出结果:

epoch 1, loss 0.031483
epoch 2, loss 0.000118
epoch 3, loss 0.000053

我们可以看到,随着轮次的增大,损失在越来越小。

比较真实参数和通过训练学到的参数来评估训练的成功程度:

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

控制台输出结果:

w的估计误差:tensor([-9.6321e-05, -6.9380e-05], grad_fn=<SubBackward0>)
b的估计误差:tensor([0.0006], grad_fn=<RsubBackward1>)

可以看到真实参数和通过训练学到的参数的差距已经越来越小了。

调整超参数的取值,看看效果如何

将学习率从0.03调整为0.001

(需要重新初始化 w \pmb{w} w b b b,这样就可以不会跟着上一次的梯度结果来了,因为如果 w \pmb{w} w b b b没变,那么梯度会一直累加)

控制台输出:

epoch 1, loss 13.930480
epoch 2, loss 11.320221
epoch 3, loss 9.199821

我们发现,当学习率特别小的时候,过了3轮epoch,损失值还是特别大。

我们可以增大轮次再看看,将epoch改为10,重新初始化 w \pmb{w} w b b b,再跑一遍。
控制台输出:

epoch 1, loss 13.925218
epoch 2, loss 11.315891
epoch 3, loss 9.196276
epoch 4, loss 7.474352
epoch 5, loss 6.075387
epoch 6, loss 4.938728
epoch 7, loss 4.015125
epoch 8, loss 3.264555
epoch 9, loss 2.654557
epoch 10, loss 2.158751

我们可以看到,跑了10个epoch之后,损失还是很大,不如我们之前正常学习率的效果。

将学习率从0.03调整为10

(需要重新初始化 w \pmb{w} w b b b,这样就可以不会跟着上一次的梯度结果来了,因为如果 w \pmb{w} w b b b没变,那么梯度会一直累加)
控制台输出如下:

epoch 1, loss nan
epoch 2, loss nan
epoch 3, loss nan
epoch 4, loss nan
epoch 5, loss nan
epoch 6, loss nan
epoch 7, loss nan
epoch 8, loss nan
epoch 9, loss nan
epoch 10, loss nan

我们发现,当学习率太大的时候,求导的时候可能会除0,或者有无限的值出现,会让loss变成not a number。

备注

该模型计算损失的时候是这样的:

def squared_loss(y_hat, y):# 均方损失return (y_hat - y.reshape(y_hat.shape))**2 / 2# 虽然按理来说,y_hat和y应该元素个数一样,但是可能一个是行向量,一个是列向量,这里把他们统一成一样的,# 这样就可以直接在y和y_hat之间进行元素对元素的操作了,比如计算差值或者逐元素的乘法。# 这里没有作均值 没有除以样本总数

并没有算平均损失,即没有除去样本总数,在这里其实是没有影响的,因为它在后面计算梯度的时候,是这样的:

def sgd(params, lr, batch_size):
# params 参数的list,包括w和b
# lr 学习率# 小批量随机梯度下降with torch.no_grad():     # 不需要计算梯度(更新的时候不要参与梯度计算)for param in params:  # 对参数里的每一个参数param -= lr * param.grad / batch_size  # 这里写batch_size其实是在算均值  param.grad参数的梯度param.grad.zero_() # 手动把梯度设置成0,下一次计算梯度的时候就不会和这次相关了

而且这里的方法,其实不是对线性回归模型的忠实实现,更多的是对小批量随机梯度下降法的忠实实现。
请添加图片描述
也可以这么解释:乘除常数的效果,是会伴随着梯度的计算的,不会因为梯度的计算而消失。所以我当初计算损失的时候忘记除了,在梯度下降的时候记得除一下,也是一样的(前面忘了,后面补上),而且,其实这里李沐老师是刻意让自己写的损失函数,不受到样本数量的影响,从而他刻意不在前面除,留到后面除
而且损失函数那边也除了也“不对”,因为损失函数那边始终算出来的是一个向量,并没有去计算平方和,而是放到训练的过程中去计算损失的平方和,然后再合理的在优化算法里面去补一下前面漏的除法操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/251849.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC实现对网页的访问,在请求控制器中创建处理请求的方法

目录 测试HelloWorld RequestMapping注解 RequestMapping注解的位置 RequestMapping注解的value属性 RequestMapping注解的method属性 SpringMVC支持路径中的占位符&#xff08;重点&#xff09; SpringMVC获取请求参数 1、通过ServletAPI获取 2、通过控制器方法的形参…

Unity3D实现坦克大战

一、效果图演示 二、逻辑剖析 从界面上&#xff1a; 需要一个Canvas满屏对着用户&#xff0c;该Canvas上展示用户的游戏数据&#xff0c;比如血条。需要一个Canvas放在蓝色坦克上方&#xff0c;也需要实时对着用户&#xff0c;显示敌人的血条信息两个坦克一个平面Plane放草地…

C++并发编程 -2.线程间共享数据

本章就以在C中进行安全的数据共享为主题。避免上述及其他潜在问题的发生的同时&#xff0c;将共享数据的优势发挥到最大。 一. 锁分类和使用 按照用途分为互斥、递归、读写、自旋、条件变量。本章节着重介绍前四种&#xff0c;条件变量后续章节单独介绍。 由于锁无法进行拷贝…

三层交换组网实验(华为)

思科设备参考&#xff1a;三层交换组网实验&#xff08;思科&#xff09; 一&#xff0c;技术简介 三层交换技术的出现&#xff0c;解决子网必须依赖路由器进行管理的问题&#xff0c;解决传统路由器低速、复杂所造成的网络瓶颈问题。一个具有三层交换功能的设备可简单理解为…

蓝桥杯每日一题-----数位dp练习

题目 链接 参考代码 写了两个&#xff0c;一个是很久以前写的&#xff0c;一个是最近刚写的&#xff0c;很久以前写的时候还不会数位dp所以写了比较详细的注释&#xff0c;这两个代码主要是设置了不同的记忆数组&#xff0c;通过这两个代码可以理解记忆数组设置的灵活性。 im…

npm ERR! code CERT_HAS_EXPIRED

执行npm i报错&#xff1a; npm ERR! code ETIMEDOUT npm ERR! syscall connect npm ERR! errno ETIMEDOUT npm ERR! network request to https://registry.npmjs.org/react-redux failed, reason: connect ETIMEDOUT 104.16.2.35:443 npm ERR! network This is a problem rel…

python常用pandas函数nlargest / nsmallest及其手动实现

目录 pandas库 Series和DataFrame nlargest和nsmallest 用法示例 代替方法 手动实现 模拟代码 pandas库 是Python中一个非常强大的数据处理库&#xff0c;提供了高效的数据分析方法和数据结构。它特别适用于处理具有关系型数据或带标签数据的情况&#xff0c;同时在时间…

十分钟学会用springboot制作微信小程序富文本编辑器

1.1 富文本模型设计 在构建富文本编辑器系统时&#xff0c;首先需要设计一个合适的富文本模型。 CREATE TABLE IF NOT EXISTS rich_texts (id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(255),content TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );这个表包括…

【算法与数据结构】647、516、LeetCode回文子串+最长回文子序列

文章目录 一、647、回文子串二、516、最长回文子序列三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、647、回文子串 思路分析&#xff1a;判断一个字符串是否为回文串那么必须确定回文串的所在区间&#xff0c;而一维…

(2)(2.13) Rockblock Satellite Modem

文章目录 前言 1 支持的MAVLink命令信息 2 设置 3 使用方法 4 数据成本 5 参数 前言 &#xff01;Note 该功能仅适用于 ArduPilot 4.4 或更高版本&#xff0c;并且要求飞行控制器支持 LUA 脚本(LUA Scripts)。 RockBLOCK 卫星调制解调器可实现与 ArduPilot 飞行器的全球…

如何将pdf转换成ppt?掌握这个方法就简单多了

有时候&#xff0c;PDF文件的布局和设计可能需要进行微调或重新排版&#xff0c;以适应PPT的特定格式和风格。那么怎么pdf怎么转ppt呢&#xff1f;为了更方便地对布局、字体、图像和其他元素进行编辑和调整&#xff0c;以符合PPT的需求&#xff0c;我们可以直接通过pdf在线转pp…

SQL Server之DML触发器

一、如何创建一个触发器呢 触发器的定义语言如下&#xff1a; CREATE [ OR ALTER ] TRIGGER trigger_nameon {table_name | view_name}{for | After | Instead of }[ insert, update,delete ]assql_statement从这个定义语言我们可以知道如下信息&#xff1a; trigger_name&…

Linux项目自动化构建工具之make/Makefile演示gcc编译

文章目录 一、背景二、如何使用&#xff1f;三、原理四、关于make的问题五、再次理解/编写makefile依赖关系依赖方法 六、原理讲解项目清理makefile是支持变量的取消执行make后显示命令依赖方法可以多行 一、背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备…

nop-entropy可逆计算入门(1)

第1步&#xff1a;从大佬的gitee&#xff1a;https://gitee.com/canonical-entropy/nop-entropy下载源码&#xff0c;进行本地编译&#xff0c;具体编译看项目下的readme,想偷懒的可以下载我编译后的jar&#xff0c;放到自己的maven仓库 https://pan.baidu.com/s/1p9MOh40MJ2m…

mac如何实现升级node版本、切换node版本

一、 查看node所有版本&#xff08;前提:安装了nodejs&#xff09; npm view node versions二、安装指定node版本 sudo n 版本号三、检查目前安装了哪些版本的node&#xff0c;会出现已安装的node版本 n四、切换已安装的node版本 sudo n 版本号其他命令 1、sudo npm cache…

Centos 内存和硬盘占用情况以及top作用

目录 只查看内存使用情况&#xff1a; 内存使用排序取前5个&#xff1a; 硬盘占用情况 定位占用空间最大目录 top查看cpu及内存使用信息 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&…

Matlab数字图像处理——图像复原与滤波算法应用方法

图像处理领域一直以来都是计算机科学和工程学的一个重要方向&#xff0c;图像复原则是其中一个重要的研究方向之一。图像复原旨在通过运用各种滤波算法&#xff0c;对图像进行去噪、恢复和改善&#xff0c;以提高图像的质量和可视化效果。在本文中&#xff0c;我们将介绍如下内…

python Flask 写一个简易的 web 端程序(附demo)

python Flask 写一个简易的 web 端程序 &#xff08;附demo&#xff09; 介绍简单介绍装饰器 app.route("/") 进阶增加接口设置端口 静态网页核心代码完整代码 介绍 Flask 是一个用于构建 Web 应用程序的轻量级 Python Web 框架。它设计简单、易于学习和使用&#x…

ReactNative实现宽度变化实现的动画效果

效果如上图所示&#xff0c;通过修改设备宽度实现动画效果 import React, {useRef, useEffect, useState} from react; import {Animated, Text, View, Image} from react-native;const FadeInView props > {const fadeAnim useRef(new Animated.Value(0)).current;React…

list基本使用

list基本使用 构造迭代器容量访问修改 list容器底层是带头双向链表结构&#xff0c;可以在常数范围内在任意位置进行输入和删除&#xff0c;但不支持任意位置的随机访问&#xff08;如不支持[ ]下标访问&#xff09;&#xff0c;下面介绍list容器的基本使用接口。 template <…