Pytorch不调包实现线性回归

本案例使用人造数据集,不调用包实现简单的线性回归案例,使用随机梯度下降算法,体会pytorch训练模型的过程。

为什么要从零开始对项目进行构建,因为作者认为线性回归项目是一个完整的包含所有深度学习需要经过的步骤,在分批量处理数据的时候,我们实现的就是dataloader的分批量处理数据,构建自己的dataset,损失函数和随机梯度下降算法都需要自己去定义书写,在求解时候需要自己去制定过程完成训练,所以理清每一步对后续的学习十分有帮助。

总体来看,得到一个能够线性回归的模型需要以下几步:

1.数据集构建(得到数据集)。

2.数据集分析 --> 这一步可能为之后进一步数据分析做铺垫。

3. 定义损失函数和优化算法。

4.以小批次多次训练,输出损失并记录。

接下来我们讲一下代码实现:

1.首先构造人造数据集:

import torch as th
import matplotlib.pyplot as plt
import random

#自己构造一个数据集
def self_create_dataset(weight,bias,num_examples):
    x_inputs = th.normal(0,1,(num_examples,len(weight)))
    y_data = th.matmul(x_inputs,weight) + bias
    y_data +=th.rand(len(y_data)) #随机噪声
    return  x_inputs ,y_data.reshape((-1,1))  #有必要进行一下维度转换

weights = th.Tensor([3.4,1])
bias = th.Tensor([1]) 
x_inputs, y_data = self_create_dataset(weights,bias,1000)

构造完成后我们要定义一下初始化的变量,以及要构造一个分批次处理的函数(dataloader):

#进行一些初始化操作
batch_size = 10
weight = th.normal(0,1,(2,1),out=None)
weight.requires_grad_(True)
bias = th.rand(1)
bias.requires_grad_(True)

## 写一个类似于dataloader的分类器
def batch_cooperate(x_inputs,y_data,batch_size):
    num_examples = len(y_data)
    indices = [x for x in range(num_examples)]
    random.shuffle(indices)  #这一行也蛮重要的,打乱顺序
    for i in range(0,num_examples,batch_size):
        #batch_visit = th.tensor( indices[i:min(i+batch_size,num_examples)] )
        batch_visit = indices[i:min(i+batch_size,num_examples)] 
    yield x_inputs[batch_visit] , y_data[batch_visit]

这里我们可以去深入理解一下这个批处理是如何实现的:总体上来说其思路是利用一个随机打乱的从0-num_example - 1排列好的列表,按照batch_size一次一次将其从features和labels中中拿出。简而言之就是每次拿出batch_size个,但并不按照原有顺序,这个地方是使用了yield函数。

PS:

yield 关键字在 Python 中用于定义生成器(generator)函数。生成器函数允许您创建一个特殊类型的迭代器,称为生成器。与普通函数不同,生成器函数在执行过程中可以暂停并在之后的某个时刻继续执行,而不会丢失之前的状态。这使得生成器非常适合处理大量数据或需要在迭代过程中进行复杂计算的场景。当生成器函数被调用时,它不会立即执行,而是返回一个生成器对象。使用 next() 函数或在 for 循环中调用生成器对象时,生成器函数将开始执行,直到遇到 yield 关键字。一旦遇到 yield,函数会返回 yield 后面的值,并暂停执行。当再次调用生成器对象时,它将从上次暂停的地方继续执行。

生成器函数的主要优点之一是它们具有更低的内存占用。由于生成器是惰性计算的,即只在需要时计算下一个值,因此在处理大型数据集或无限序列时特别有用。这使得生成器成为内存效率较高的解决方案。

2.我们可以看看人造数据集长啥样(数据分析工作):

#可以看一下数据的分布,进行数据分析
plt.figure(1)
plt.scatter(x_inputs[:,0].detach().numpy(),y_data.detach().numpy(),1)

3-4. 定义损失函数和优化算法并完成训练:

## 损失函数模块
def costs(y_pred,y_true):
    #计算公式 1/2 * (y' - y)**2  这里先不算平均
    return 0.5 * (y_pred.reshape(y_true.shape) - y_true)**2

## 定义模型
def model(w,x,b):
    return th.matmul(x,w)+b

def random_gradient(x_inputs, y_data, weight,bias,alpha):
    for epoch in range(1000):
        for x_train, y_train in batch_cooperate(x_inputs,y_data,batch_size):
            y_pred = model(weight, x_train, bias)
            loss = costs(y_pred,y_train)
            loss.sum().backward()
            with th.no_grad():  #这个地方不加的话会陷入无限的梯度循环计算,导致报错
                weight -=alpha*weight.grad/batch_size
                bias -=alpha*bias.grad/batch_size
                weight.grad.zero_()
                bias.grad.zero_()
        with th.no_grad():
            losses = costs(model(weight,x_train,bias) , y_train).sum() / batch_size
            print(losses)

 这里有几个需要注意的地方:

第一个就是with th.no_grad(): 的使用,此语句的使用使得在这条语句里的所有计算都不再继续计算梯度,避免了pytorch中图的重复积累浪费内存,甚至无法运行出结果的情况。值得注意的是,这里有一个问题还卡了笔者较长时间,就是使用该语句后weight和bias的梯度从何而来,这里笔者查阅资料得到了一个认识,就是loss是关于weight和bias的函数,在执行loss.sum().backward()的时候,所有有关weight和bias的梯度都已经存在图中,在没有grad.zero_(),这些变量并不会清零,所以后面可以继续使用。

第二个就是对loss的认识,因为pytorch对梯度的积累会通过有向图进行,所以我们的loss不可以是一个矢量,我们在深度学习对其求损失的时候,永远要把其处理成一个标量进行反向传播,这也就是为什么要执行loss.sum(),进行求和。

第三个就是我们在进行运算时候,要注意各个张量维度的一致性,这个是新手极容易忽略的,所以笔者在代码书写的时候总会出现一些reshape操作将两个张量化为相同,才能满足后续的运算。

1000次求解结果图 

(笔者暂时就想到这么多,之后还会进行补充) 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值