李沐-动手学深度学习-线性回归的从零实现

代码:pycharm

#matplotlib inline
import random
import torch
from d2l import torch as d2l


############3.2.1生成数据集
#1.生成合成数据集
def synthetic_data(w,b,num_examples):
    """生成y=Xw+b+噪声"""
    X = torch.normal(0,1,(num_examples,len(w)))  ##生成 X,他是一个均值为0,方差为1的随机数,他的大小: 行为num_examples,列为w的长度表示多少个feature
    y = torch.matmul(X,w)+b
    y += torch.normal(0,0.01,y.shape)##加入一些噪音,均值为0 ,方差为0.01,形状和y是一样
    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)##synthetic_data这个函数返回的是特征和标签,相当于分别吧真实的房屋‘关键因素’和对应的‘房价’列出来了
#features中的每一行都包含一个二维数据样本, labels中的每一行都包含一维标签值(一个标量)
print('features:',features[0],'\nlabel:',labels[0])


''''''
#2.生成第二个特征features[:, 1]和labels的散点图, 可以直观观察到两者之间的线性关系。
d2l.set_figsize()
##detach()分离出数值,不再含有梯度;
##scatter()函数的最后一个1是绘制点直径大小
d2l.plt.scatter(features[:,(0)].detach().numpy(),labels.detach().numpy(),1)
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)
d2l.plt.show()
''''''


#############3.2.2读取数据集
#1.定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。 每个小批量包含一组特征和标签。
def data_iter(batch_size,features,labels):
    num_examples = len(features)
    indices = list(range(num_examples)) #生成索引集合index,随机读取。range随机生成0 —(n-1),然后转化成python的list
    #这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)# 将下标全都打乱,打乱之后就可以随机的顺序去访问一个样本
    for i in range(0,num_examples,batch_size):#每次从0开始到num_examples,每隔batch_size个取一个
        batch_indices = torch.tensor(
            indices[i:min(i+batch_size,num_examples)])#切片操作:创建一个批次的索引,i为批次的起始索引,batch_size为批次大小,num_examples为总样本数。
            #min(i+batch_size,num_examples)最后一个批次中防止索引过界。如果数据集中的样本数不足以填满一个完整批次,则只取到最后一个样本的索引。
        yield features[batch_indices],labels[batch_indices]#yield生成器函数,一种特殊的函数,在迭代过程中逐步产生值,而不是一次性返回所有结果。
        # 在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。
        #然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

batch_size=10
#2.直观感受一下小批量运算:读取第一个小批量数据样本并打印
    #迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器有两个基本的方法:iter() 和 next().迭代器对象可以使用常规for语句进行遍历:
for X,y in data_iter(batch_size,features,labels):
    print(X,'\n',y)
    break



#############3.2.3 初始化模型参数
#用小批量随机梯度下降优化我们的模型参数之前, 需要先有一些参数。我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0
w = torch.normal(0,0.01,size=(2,1),requires_grad=True)#requires=true是指需要计算梯度
b = torch.zeros(1,requires_grad=True)
#在初始化参数之后,要更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数



#############3.2.4 定义模型
#定义模型,将模型的输入和参数同模型的输出关联起来
def linreg(X,w,b):
    """线性回归模型"""
    return torch.matmul(X,w)+b



#############3.2.5定义损失函数
#需要计算损失函数的梯度,所以我们应该先定义损失函数.将真实值y的形状转换为和预测值y_hat的形状相同。
def squared_loss(y_hat,y):
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape))**2/2




#############3.2.6 定义优化算法
#每一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params,Ir,batch_size): #params给定所有的参数,这个是一个list包含了w和b,lr是学习率
    """小批量随机梯度下降"""
    with torch.no_grad(): #这里更新的时候不需要参与梯度计算所以是no_grad
        for params in params: #参数中的每一个参数:w和b
            params -= Ir*params.grad/batch_size #梯度存在.grad中。上面的损失函数中没有求均值,所以这里除以batch_size求均值。因为乘法对于梯度是一个线性的关系,所以除以在上面损失函数那里定义和这里是一样的效果
            params.grad.zero_()#把梯度设置为0,因为pytorch不会自动的设置梯度为0,需要手动,下次计算梯度时与该次设置无关
            #pytorch 会不断累加变量的梯度,所以每更新一次参数,就要让其对应的梯度清零




#############3.2.7训练过程
#在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
#在每个迭代周期(epoch)中,我们使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs和学习率lr都是超参数
Ir = 0.03
num_epochs = 3 #epoch为3表示把整个数据扫3遍
net = linreg  #network为linreg:线性回归模型
loss = squared_loss #loss为均方损失

for epoch in range(num_epochs): #训练过程基本是两层for循环(loop),第一次for循环是对数据扫一遍
    for X,y in data_iter(batch_size,features,labels): #对于每一次扫描获取一个批量大小的X和y
        I = loss(net(X,w,b),y)  #X和y的小批量损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        I.sum().backward()## 求和本身是让l(即loss)以标量的形式表现出来(通过sum()转化标量之后在求梯度)(一般都是对一个标量进行求导,所以我们先对y进行求和再求导:见自动求导)
        sgd([w,b],Ir,batch_size)#使用参数的梯度更新参数。算完梯度之后就可以访问梯度了,使用sgd对w和b进行更新。
    with torch.no_grad():#对数据扫完一遍之后来评价一下进度,这块不需要计算梯度,所以放在no_grad里面
        train_I = loss(net(features,w,b),labels)#把整个features,整个数据传进去计算他的预测和真实的labels做一下损失
        print(f'epoch{epoch+1},loss{float(train_I.mean()):f}')
#非标量变量的反向传播:
#当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的y和x,求导的结果可以是一个高阶张量。
#然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中), 但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。

print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}')
2.注:
   生成数据集那里的画散点图代码  和  输出代码 冲突了,画图的话,输出就很慢很慢很慢很慢(可能得1小时左右),所以输出时注释掉了。
3.疑惑:
   训练过程中,
I = loss(net(X,w,b),y)  #X和y的小批量损失     

train_I = loss(net(features,w,b),labels)#把整个features,整个数据传进去计算他的预测和真实的labels做一下损失

为什么要这样做呢?x、y 和 features、labels 有什么区别呢?

### 深度学习中的线性回归 #### 实现线性回归模型 在的《动手学深度学习》教程中,通过仅使用`NDArray`和`autograd`实现线性回归训练过程[^1]。具体来说,在初始化阶段,定义了权重和偏差作为可训练参数,并设定了初始值。 为了更好地理解这个概念,下面是一个简单的Python代码片段用于展示如何构建这样的模型: ```python def linreg(X, w, b): """线性回归模型""" return nd.dot(X, w) + b ``` 此函数接收输入特征矩阵X以及两个参数w(权重)和b(偏置项)。它返回预测的目标变量Y帽,这是基于当前设定的权重和偏移计算得出的结果。 接着,对于损失函数的选择上,通常采用均方误差(MSE),因为它能够有效地衡量实际输出与预期之间的差异程度: ```python def squared_loss(y_hat, y): """均方损失""" return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 ``` 这里需要注意的是,当处理真实标签时,应该将其形状调整为与预测值相同的形式以便于后续操作。 最后,在更新规则方面,则运用到了梯度下降的思想。正如所提到的学习率决定了每次迭代过程中沿着最陡峭路径前进的距离;而负梯度则指示出了应当朝哪个方向移动才能使目标函数最小化[^2]: ```python def sgd(params, lr, batch_size): for param in params: param[:] = param - lr * param.grad / batch_size ``` 这段代码展示了简单的小批量随机梯度下降(SGD)方法,其中包含了三个主要组成部分——待优化的参数列表、选定的学习速率lr以及批次大小batch_size。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值