首页人工智能Pytorch【深度学习(PyTorch...

【深度学习(PyTorch篇)】17.实现线性回归


本系列文章配套代码获取有以下两种途径:

  • 通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj 提取码:mnsj
  • 前往GitHub获取
https://github.com/returu/PyTorch





线性回归(Linear Regression)是一种利用线性函数对自变量(特征)和因变量之间的关系进行建模的方法,利用数理统计中的回归分析来确定两种或两种以上变量间相互依赖的定量关系。其是机器学习中一种广泛使用的基本回归算法。
线性回归的一般形式可以表示为y = wx +b +e,其中x是自变量(特征),y是因变量,w是特征的权重,b是偏置,误差e服从均值为0的正态分布。
线性回归的目标是找到一组权重w和偏置b,使得对于给定的自变量x,预测的因变量与实际值之间的误差(损失函数)尽可能小。
01

参数估计:


本次使用梯度下降算法更新参数wb来最小化损失函数(均方误差),最终得到wb的值。
  • 均方误差:
均方误差(Mean Squared Error,MSE)损失函数是一种常用的回归损失函数,用于衡量模型预测值与真实值之间的差异。其具体计算方式是将每个样本的预测值与真实值之差进行平方,然后对所有样本取平均。
数学表达式如下:

  • 梯度下降算法:

梯度下降算法是一种优化算法,主要用于求解模型参数。它的核心思想是通过迭代的方式,沿着函数的梯度方向移动一定的距离,以逐步逼近函数的最小值。
具体来说,梯度下降算法的步骤如下:
  • 初始化参数:初始化模型的参数,这些参数通常是随机初始化的;
  • 计算损失函数:该函数通常表示预测值与真实值之间的差异;
  • 计算梯度:计算损失函数关于模型参数的梯度,这个梯度指示了参数应该调整的方向;
  • 更新参数:根据计算出的梯度,按照一定的学习率来更新模型参数。学习率控制了每次更新的幅度,如果学习率过大,可能会导致算法不收敛;如果学习率过小,则可能会导致算法收敛速度过慢;
  • 重复迭代:重复执行步骤2-4,直到达到某个停止条件,例如梯度向量的幅值接近0,或者损失函数的变化小于某个阈值,或者达到预设的最大迭代次数。

02

实现线性回归:


本次将根据梯度下降算法逐步实现线性回归。
  • 1、生成并可视化数据:
# 设置随机数种子,保证运行结果一致
torch.manual_seed(2024)

def get_fake_data(num):
    """
    生成随机数据,y = 0.5 * x +30并加上一些随机噪声
    """

    x = torch.randint(low = -5, high=30, size=(num,)).to(dtype=torch.float32)
    y = 0.5 * x +30 + torch.randn(num,)
    return x,y

x , y = get_fake_data(num=11)

# 可视化数据
fig = plt.figure()
plt.plot(x , y , 'o')
可视化效果如下所示:

  • 2、定义梯度函数:
为了计算损失对参数的导数,可以应用链式法则,先计算损失对于输入(即模型输出)的导数,再乘以模型对参数的导数。
# 定义模型:
def model(x , w , b):
    return w * x + b

# 定义损失函数:
def loss_fn(y_p , y):
    squared_diffs = (y_p - y)**2
    return squared_diffs.mean()

# 计算导数:
def dloss_fn(y_p , y):
    dsq_diffs = 2*(y_p - y)/y_p.shape[0]
    return dsq_diffs

def dmodel_dw(x ,w ,b):
    return x

def dmodel_db(x , w , b):
    return 1.0

# 返回关于w和b的损失梯度的函数:链式法则
def grad_fn(x , y , y_p , w , b):
    dloss_dtp = dloss_fn(y_p , y)
    dloss_dw = dloss_dtp * dmodel_dw(x ,w ,b)
    dloss_db = dloss_dtp * dmodel_db(x ,w ,b)

    return torch.stack([dloss_dw.sum() , dloss_db.sum()])

  • 3、求解参数值:
上一步已经定义了参数的梯度函数,接下来就可以从参数的初始值对其进行迭代更新。迭代停止条件采用固定次数的迭代。
# 初始化参数:
w = torch.ones(())
b = torch.zeros(())

# 循环训练:
def training_loop(n_epochs , learning_rate , params , x , y):
    for epoch in range(1 , n_epochs+1):
        w,b = params

        y_p = model(x,w,b)
        loss = loss_fn(y_p ,y)
        grad = grad_fn(x , y , y_p , w , b)

        params = params -learning_rate*grad

        if epoch % 10 ==0:
            print(f"Epoch : {epoch} , Loss : {loss}")

            print(f"Params : {params} , Grad : {grad}")
            print("-----")

    return params
首先将迭代次数设置为500,学习率设置为0.02,调用循环训练:
training_loop(n_epochs=500 , learning_rate=0.02 , params=torch.tensor([1.0 , 0.0]) , x=x , y=y)
运行之后,发现此时训练过程完全崩溃了,迭代50次后损失Loss就变成了无穷。
这是因为我们设置的学习率值太大了,导致参数更新时波动过大,使得优化过程不稳定。
Epoch :10 , Loss :9.902059470433812e+17
Params : tensor([-5.1232e+08, -2.3935e+07]) , Grad : tensor([2.9021e+10, 1.3558e+09])
-----
Epoch :20 , Loss :3.3471030602793666e+35
Params : tensor([-2.9786e+17, -1.3916e+16]) , Grad : tensor([1.6873e+19, 7.8827e+17])
-----
Epoch :30 , Loss : inf
Params : tensor([-1.7318e+26, -8.0907e+24]) , Grad : tensor([9.8097e+27, 4.5830e+26])
-----
Epoch :40 , Loss : inf
Params : tensor([-1.0068e+35, -4.7039e+33]) , Grad : tensor([5.7033e+36, 2.6645e+35])
-----
Epoch :50 , Loss : nan
Params : tensor([nan, nan]) , Grad : tensor([nan, nan])
。。。。。。
。。。。。。

  • 4、改进模型:
(1)、减小学习率:
首先,在循环训练中设置较小的学习率:
training_loop(n_epochs=5000 , learning_rate=1e-4 , params=torch.tensor([1.0 , 0.0]) , x=x , y=y)
此时,可以看到模型优化过程已经比较稳定。但是损失Loss下降的很慢,最后接近停滞(即使训练迭代了5000次)。
为了解决该问题,可以使用学习率自适应方法来改进模型效果。但是本次不采用这种较为复杂的处理方式,而是从输入数据入手改进模型效果。
(2)、归一化输入数据:
在使用较小的学习率进行训练时,可以看到参数wb的梯度相差有几十倍。这意味着wb存在于不同的比例空间中,在这种情况下,如果学习率足够大,能够有效更新其中一个参数,但是对于另一个参数就会变得不稳。这也就意味着我们无法稳定的更新wb两个参数。
对于上述问题归一化数据就可以很好的解决,这样wb两个参数的梯度就不会相差太大
本次将输入自变量x乘以0.1,将学习率设置为0.01,并将迭代次数较少为1000次:
training_loop(n_epochs=1000 , learning_rate=1e-2 , params=torch.tensor([1.0 , 0.0]) , x=x*0.1 , y=y)
此时可以看到参数更新的较为稳定,程序也基本学得w=0.5b=30
  • 5、可视化迭代效果:
为更好地查看循环训练过程中参数更新情况,本次使用IPython 库动态可视化每次迭代得到的参数值。
from IPython import display

def training_loop(n_epochs , learning_rate , params , x , y):
    for epoch in range(1 , n_epochs+1):
        w,b = params

        y_p = model(x,w,b)
        loss = loss_fn(y_p ,y)
        grad = grad_fn(x , y , y_p , w , b)

        params = params -learning_rate*grad

        if epoch % 100 ==0:
            display.clear_output(wait=True)
            plt.plot(x , y_p)

            plt.plot(x , y , 'o')

            # 获取xy轴的范围
            xmin,xmax,ymin,ymax = plt.axis()

            plt.text(x=xmax*0.2,
                     y=ymax*0.9,
                     s=f"{w*0.1:.3f}x+{b:.3f}")

            plt.show()
            plt.pause(0.5)

    return params
最终得到线性拟合模型。



更多内容可以前往官网查看

https://pytorch.org/


本篇文章来源于微信公众号: 码农设计师

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments