[AI] 基于梯度下降法的一次函数拟合

前言

分析了下自己的技能树,打算点一些 AI 方向的 perk。久闻吴恩达机器学习的大名,因此先从他的教程看起。

目前学到梯度下降,但还没有敲过任何代码——正好今天时间比较充裕,遂决定进行一些小小的实践。

准备 training set

首先需要生成 training set。

y=wx+b+ϵ

如上。我的想法是,先随机生成 wb,对于在 [xMin,xMax] 等间距取值的 x,计算 y=wx+b,这样 yx 就成一次函数关系了。

不过实际情况一般会更复杂一些——数据并非严格地遵循一次函数的规律,而是近似地符合该规律,因此我在 y 上面叠加了一个扰动量 ϵ,并引入了扰动因子 disturbanceFac 来控制扰动的大小。

当扰动因子为 1 时,生成的数据如下:
image

梯度下降

我们的模型可以表示为 f(x)=w1x+b1,使用平方误差代价函数,得到:

J(w1,b1)=12mi=1m(w1x(i)+b1y(i))2

分别计算出 J(w1,b1)w1b1 的偏导数:

Jw1=1mi=1m(w1x(i)+b1y(i))x(i)

Jb1=1mi=1m(w1x(i)+b1y(i))

化简得到:

Jw1=1m(w1i=1mx(i)2+b1i=1mx(i)i=1mx(i)y(i))

Jb1=1m(w1i=1mx(i)+mb1i=1my(i))

其中的 i=1mx(i)i=1my(i)i=1mx(i)2i=1mx(i)y(i) 可以通过预计算得到,不需要重复计算。

每次 epoch,我们遵循如下规则对 w1b1 进行更新:

{w1w1αJw1b1b1αJb1

重复上述过程,直至收敛。

教程中并没有给出判断收敛的定量指标,这里我采取的策略是检测 |Jw1||Jb1| 是否均小于预先设定的阈值 convThreshold,如果是则认为收敛,否则继续迭代。

完整代码

import matplotlib.pyplot as plt
import numpy as np
import random
# 0. initial params
wMin, wMax = 0, 10.0
bMin, bMax = 0, 10.0
xMin, xMax = 1, 10
m = 16
alpha = 0.001
convThreshold = 0.001
disturbanceFac = 1
# 1. generate training set
# y = wx + b + eps
# eps: disturbance, range [-epsK, epsK], epsK = disturbanceFac * (xMax - xMin) * w/ (2 * (m - 1))
w = random.uniform(wMin, wMax)
b = random.uniform(bMin, bMax)
epsK = disturbanceFac * (xMax - xMin) * w / (2 * (m - 1))
eps = np.array([random.uniform(-epsK, epsK) for _ in range(m)])
print("(w, b) = ({}, {})".format(w, b))
x = np.linspace(xMin, xMax, m)
y = w * x + b + eps
plt.scatter(x, y, marker="x", c="r")
plt.grid()
print("Press enter to start training..")
input()
# 2. perform gradient descent algorithm to predict w, b
# w1, b1 are estimated values of w, b
w1, b1 = 0.0, 0.0
# 2.1 precompute some reusable terms
xSum = 0
ySum = 0
xSqSum = 0
xySum = 0
for i in range(m):
xSum += x[i]
ySum += y[i]
xSqSum += x[i] * x[i]
xySum += x[i] * y[i]
# 2.2 perform algorithm
epoch = 0
partialW1 = 100
partialB1 = 100
while abs(partialW1) > convThreshold or abs(partialB1) > convThreshold:
print("epoch #{}".format(epoch))
print("{} {}".format(w1, b1))
partialW1 = 1 / m * (w1 * xSqSum + b1 * xSum - xySum)
partialB1 = 1 / m * (w1 * xSum + m * b1 - ySum)
w1, b1 = w1 - alpha * partialW1, b1 - alpha * partialB1
epoch += 1
# 3. display result
x1 = np.linspace(xMin, xMax, m)
y1 = w1 * x1 + b1
plt.plot(x1, y1)
plt.show()

测试结果

disturbanceFac=1

image

disturbanceFac=3

image

disturbanceFac=5

image

一点感悟

话说 C 语言的 HelloWorld 就是打印 HelloWorld,OpenGL 的 HelloWorld 是绘制三角形,单片机的 HelloWorld 是点灯……本文大概就是 AI 的 HelloWorld了。

放在以前,要想求解这样的问题,我只能用暴力搜索解决,然而一旦试图去使用暴搜,就会陷入这样的困境:搜索的范围该设多大?搜索的精度(步进长度)又该是多少?对于任意取值的 w,b 来说,搜索的范围应当是 (,),为了获得尽可能精确的结果,步进长度应当 0,这在程序上根本无法实现……

但是,用梯度下降法 “一瞬间” 就能得到精度极高的结果。

简直——太神奇了!

2024-09-12 UPD

突然想到,目前唯一的问题在于它是一种启发式算法——启发式算法都很玄学,因为其运行效率依赖于数据的特征以及选取的参数,不具备时间复杂度这样能相对确切地描述算法运行效率的指标。

posted @   ZXPrism  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示