用梯度下降法简单实现一元线性拟合

核心概念

  • 梯度:函数在某一点的最大方向导数,表示函数在该点上升最快的方向

  • 梯度向量:函数的某一点处沿着一个向量的方向导数最大(即取得梯度),则该向量为梯度向量

  • 训练轮数:迭代次数,完整的使用训练集中的所有样本进行一次训练称之为一轮训练

  • 批次大小:每次迭代使用的样本数

  • 学习率:每次迭代的步长

  • 损失函数:用于评估模型的好坏的某一种误差度量函数

  • 梯度下降法:沿着梯度向量的反方向移动,直至找到函数的最小值。在训练模型中,这里的梯度指损失函数的梯度

数学原理

已知数据集:

D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x n , y n ) } D = \{(x_1, y_1), (x_2, y_2), \cdots, (x_n, y_n)\} D={(x1,y1),(x2,y2),,(xn,yn)}

假如我们需要拟合一条直线,那模型的方程应该是:

f ( x ) = k x + b f(x) = kx + b f(x)=kx+b

我们可以通过最小化残差平方和来确定 k k k b b b的值,这样一来损失函数:

g ( k , b ) = ∑ i = 1 n ( f ( x i ) − y i ) 2 = ∑ i = 1 n ( k x i + b − y i ) 2 g(k, b) = \sum_{i=1}^{n} (f(x_i) - y_i)^2 = \sum_{i=1}^{n} (kx_i + b - y_i)^2 g(k,b)=i=1n(f(xi)yi)2=i=1n(kxi+byi)2

这里需要知道的是原本模型的参数就作为损失函数的自变量。

求损失函数的偏导数和梯度向量:

∂ g ( k , b ) ∂ k = ∑ i = 1 n 2 ( f ( x i ) − y i ) ⋅ x i ∂ g ( k , b ) ∂ b = ∑ i = 1 n 2 ( f ( x i ) − y i ) ∇ g ( k , b ) = [ ∂ g ( k , b ) ∂ k ∂ g ( k , b ) ∂ b ] \begin{aligned} \frac{\partial g(k, b)}{\partial k} &= \sum_{i=1}^{n} 2(f(x_i) - y_i) \cdot x_i \\ \frac{\partial g(k, b)}{\partial b} &= \sum_{i=1}^{n} 2(f(x_i) - y_i) \\ \nabla g(k, b) &= \begin{bmatrix} \frac{\partial g(k, b)}{\partial k} \\ \frac{\partial g(k, b)}{\partial b} \end{bmatrix} \end{aligned} kg(k,b)bg(k,b)g(k,b)=i=1n2(f(xi)yi)xi=i=1n2(f(xi)yi)=[kg(k,b)bg(k,b)]

假如训练轮数是 E E E,学习率是 η \eta η,那么梯度下降法的迭代公式是:

[ k i b i ] = [ k i − 1 b i − 1 ] − η ∇ g ( k i − 1 , b i − 1 ) \begin{bmatrix} k_i \\ b_i \end{bmatrix} = \begin{bmatrix} k_{i-1} \\ b_{i-1} \end{bmatrix} - \eta \nabla g(k_{i-1}, b_{i-1}) [kibi]=[ki1bi1]ηg(ki1,bi1)

在上述步骤中需要初始化 k = k 0 k=k_0 k=k0 b = b 0 b=b_0 b=b0,然后不断迭代 E E E次,时间复杂度是 O ( n ⋅ E ) O( n \cdot E) O(nE)

代码实现

Python代码如下:

import matplotlib.pyplot as plt
import random

# 模拟梯度下降法
example_func = lambda x: 25 * x - 19                  # 模板函数,样本点添加了噪声后可能会有所偏差

points = [(x, example_func(x) + random.gauss(0, 1)) for x in (random.uniform(-10, 10) for _ in range(100))]  # 生成数据集
k, b = 5, 3                                           # f(x) = k * x + b,初始化时k和b的值随意设置
learning_rate = 5e-6                                  # 学习率
epochs = 200000                                       # 训练轮数

# 损失函数: g(k, b) = \sum_{i=1}^{n} (f(x_i) - y_i)^2   # 残差平方和

for epoch in range(epochs):                           # O(N * E)
    k_grad, b_grad = 0, 0
    for x, y in points:                               # 把整个数据集看作一个batch
        k_grad += 2 * (k * x + b - y) * x             # d(g(k, b)) / d(k)
        b_grad += 2 * (k * x + b - y)                 # d(g(k, b)) / d(b)
    # 沿着梯度向量的反方向移动,梯度向量:[[k_grad], [b_grad]]
    k -= learning_rate * k_grad
    b -= learning_rate * b_grad
    if epoch % 20000 == 0:
        print(f"Epoch {epoch}: k = {k}, b = {b}")
print(f"Final: k = {k}, b = {b}")

# 最小二乘法对照
x_avg = sum(x for x, _ in points) / len(points)
y_avg = sum(y for _, y in points) / len(points)
k_best = sum((x - x_avg) * (y - y_avg) for x, y in points) / sum((x - x_avg) ** 2 for x, _ in points)
b_best = y_avg - k_best * x_avg
print(f"Best: k = {k_best}, b = {b_best}")

plt.plot([x for x, _ in points], [y for _, y in points], 'ro')           # 散点
plt.plot([x for x, _ in points], [k * x + b for x, _ in points], '-')    # 直线
plt.show()

需要注意的是,这里没有划分训练集和验证集,因为可以用最小二乘法来验证模型的好坏。
代码中的example_func是一个模板函数,由于后续添加了噪声,所以拟合的直线不会完全与模板函数重合。
代码中的learning_rateepochs是需要调整的超参数。如果学习率过高,可能会导致梯度爆炸。
代码中的points是一个数据集,这里的数据集是随机生成的,实际应用中应该是真实的数据集。
代码处理可视化的部分使用了matplotlib库,需要提前安装。其余部分均是Python内置的库。
代码中的计算过程均在CPU完成,仅供学习参考。

控制台输出:

Epoch 0: k = 5.617168567733822, b = 2.9736399575387327
Epoch 20000: k = 25.004648079906683, b = -19.018192641629117
Epoch 40000: k = 25.00464807956941, b = -19.018192687654867
Epoch 60000: k = 25.00464807956941, b = -19.018192687654867
Epoch 80000: k = 25.00464807956941, b = -19.018192687654867
Epoch 100000: k = 25.00464807956941, b = -19.018192687654867
Epoch 120000: k = 25.00464807956941, b = -19.018192687654867
Epoch 140000: k = 25.00464807956941, b = -19.018192687654867
Epoch 160000: k = 25.00464807956941, b = -19.018192687654867
Epoch 180000: k = 25.00464807956941, b = -19.018192687654867
Final: k = 25.00464807956941, b = -19.018192687654867
Best: k = 25.004648079569343, b = -19.018192687656658

绘制的图像如下:在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当前非线性拟合和多元拟合的工具较少,这是针对常用的拟合算法,开发的一款数据拟合为主的软件。包括线性拟合的各种算法,非线性拟合的各种算法,以及多元拟合的各种算法。其中提供了很多非线性方程的模型,以满足不同的需求,也可以制定自己所需要的指定非线性方程模型的,采用最先进的初始值估算算法,无需初始值就可以拟合自己想要的非线性方程模型各个模块的介绍如下。 1.线性拟合算法模块 根据最小二乘拟合算法,对输入的数据进行变量指定次方的拟合。同时可对自变量或因变量进行自然对数和常用对数的转换后再拟合。根据实际情况,开发了单调性拟合以针对各种定量分析的用途。同时开发了,针对一组数据,得到最高相关系数的自动拟合功能,由程序自动选择拟合次数以及自变量和因变量的数据格式。 2.非线性拟合算法模块 根据非线性方程的特点,开发了最先进的智能初始值估算算法,配合LM迭代算法,进行非线性方程的拟合。只需要输入自变量和因变量,就可以拟合出所需要的非线性方程。拟合相关系数高,方便快捷。并借助微粒群算法,开发了基于微粒群的智能非线性拟合算法,拟合出方程的相关系数相当高,甚至会出现过拟合现象。 3.多元拟合算法模块 根据最小二乘算法的原理开发了多元线性拟合算法,同时开发了能够指定变元次数的高次多元线性拟合。由于多元变量的情况下函数关系复杂,采用高次多元线性拟合能有效提高拟合效果而不会出现过拟合现象。同时针对每个变元可能最合适的拟合次数不一定都一样,开发了自适应高次多元拟合算法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值