梯度下降和反向传播: 能改(上)
时间:2023-2-27 23:20 作者:wen 分类: AI
上节课, 我们通过顶点坐标公式, 求解出抛物线最低点的w坐标, 得到了让误差代价最小的w, 但是我们也通过算一笔账, 说明了这种一步到位求解的方式固然是好, 但是在输入特征过多, 样本数量过大的时候, 却非常消耗计算资源, 现在我们就来看看另外一种更常用的方式, 其实在上节课中也暗示了这一点, 我们用了一个"挪"字来描述这个过程, 抛物线最低点坐标的寻找过程, 其实不必一步到位, 大可以采用一点点挪动的方式, 比如, 一开始w的值是2, 如果误差在最低点左边, 就需要不断的把w调大, 而在右边的时候不断把w调小, 而具体实施起来也很方便, 我们使用"斜率", 一个开口向上的抛物线最低点的斜率是0, 而左边的斜率是负数, 右边的斜率是正数, 所以现在我们的目的很明确, 想办法得到代价函数的曲线, 在当前w取值这个点上的斜率, 这样我们就可以判断当前w取值, 是在最低点的左边还是右边, 然后不断调整w直到到达最低点, 也就是说,得到一个让误差代价最小的w, 我们之前已经证明了误差e和w形成的代价函数, 是一个标准的一元二次函数.
一元二次函数的斜率怎么求?
第一种方法: 我们看到均方误差曲线是一个弯的, 我们此刻更像是上帝视角, 看见了它的全部, 而当我们盯着一个点不断变小, 变小, 当我们足够小的时候, 同样显而易见的是直线是直的, 这种"直"是宏观的"弯曲"在微观中的一种近似, 而一个直线的斜率就十分好求了, 我们取这个"直线上"某个点, 如果横坐标是w, 那么纵坐标e就是:aw^2+bw+c, 我们再取附近右边的一个点, 比如这个点和第一个点横坐标距离为◁w, 那么这个坐标横坐标为: w+◁w, 纵坐标是: a(w+◁w)^2+b(w+◁w)+c, 那这个直线的斜率自然就是这两个点的纵坐标的差值除以横坐标的差值:
(a(w+◁w)^2+b(w+◁w)+c - (aw^2+bw+c))/(w+◁w-2w) = (2aw◁w+a◁w^2+b◁w)/◁w = 2aw+a◁w+b, 当◁w无穷小时, ◁w自然会湮灭, 所以这个点的斜率就是: 2aw+b
在曲线中, 某处的斜率我们一般把它称之为导数, 或许你以前没有学过导数, 但是现在, 你学过了. 用纵坐标的差值除以横坐标的差值, 并取极限, 这其实就是导数的定义, 所以这种求导的方法也称之为定义法. 实际上用定义法, 原则上可以求解出任意一个函数任意一点的导数(可导的时候), 当然你需要仔细的, 小心的取做计算.
第二种方法: 其实更简单, 计算的难度也更小一点, 既然定义法可以得到一个函数的导数, 而我们都知道数学里常用的函数也就那么多, 我们全部求出来做成一张表格形成固定的公式, 然后用的时候去查询岂不美哉, 没错, 确实有人做出来了, 但想想似乎又不对劲, 虽然常用函数就那么多, 但是他们的组合又是无穷无尽的, 而对于这些无穷无尽的组合函数, 我们不可能用有限的生命投入到无限的事情中, 毕竟传统功夫, 点到为止, 当然我们总是能琢磨出一些新的规律, 实际上不论怎样的函数组合都逃不脱三种基本的形式, 加法, 乘法, 和复合, 我们的方差代价函数就是一个明显的通过乘法和加法组合起来的函数,
aw^2是一个常函数和一个幂函数(2次幂)的乘法,
常函数的导数查询公式可知等于0, 导数为: 0
幂函数的导数是把幂放到前面, 再把幂减1,导数为: 2w
而利用导数的乘法法则, 两个函数乘法的导数是第一个函数的导数乘以第二个函数再加上第二个函数的导数乘以第一个函数,导数为: 0w^2 + 2wa = 2aw
在直线中斜率(导数)是个常数, 因为斜率(导数)一直没有变化, 而在抛物线中斜率(导数)和自变量有关, 如此, 我们也就可以知道代价函数曲线每个点的斜率.
我们小蓝的神经元终于可以根据代价函数的斜率是否大于0, 来决定w的调整行为了.
那么问题来了, 每次调整多少合适呢?
按照目前的经验和直觉来看, 每次只能调整一点点, 不能调多, 不如我们就试试每次调整0.01吧. 这样好像可以, 但是我们发现下降的过程有点慢, 在最低处还反复震荡, 因为不论当前w是几, 调整幅度都是呆板的0.01, 那么有没有更聪明一点的方式呢?
有, 当w距离最低点比较远的时候, 我们其实希望他能快一点, 而逐渐接近最低点的时候, 我们喜欢它慢下来, 这样就能即加快下降速度, 又能在最低点处稳如老狗, 同时我们发现距离最低点越远的地方, 这个斜率的绝对值越大, 而越近的地方越小, 当接近最低点的时候这个值几乎为0, 而最低点斜率它就是0, 是分界点. 而斜率在左右的符号又正好不同, 这可太好了, 刚好可以利用斜率的值来做这件事情, 让w每次直接减去这个点斜率的值, 如此, 当前w在右边的时候斜率是正数, w减去一个正数向小调整, 在左边时斜率是负数, w减去一个负数向大调整, 同时也做到了距离最低点比较远的时候斜率大(绝对值),调整得多, 大刀阔斧, 比较近的时候斜率小(绝对值), 调整的少, 精雕细琢. 运行一下调整过程的时候, 你会发现: 此时此刻, 恰如彼时彼刻, 在Rosenblatt感知器中做参数调整的时候, 也发现了这个问题, 调整过程太震荡, 无法收敛, 但是没关系, 方法还是一样的, 考虑个斜率也乘上一个比较小的学习率alpha调和一下, 比如alpha=0.1, 这次调整过程又快, 在最低点又稳. 这种根据曲线不同处"斜率"去调整w的方式, 有就是所谓的"梯度下降".
# dataset.py 样本数据获取
import numpy as np
def get_beans(counts):
xs = np.random.rand(counts)
xs = np.sort(xs)
ys = [1.2 * x + np.random.rand() / 10 for x in xs]
return xs, ys
# sgd.py
# 斜率下降 随机梯度下降
import dataset
from matplotlib import pyplot as plt
m = 100
xs,ys = dataset.get_beans(m)
# 配置图像
plt.title('Size-Toxicity Function', fontsize=12) # 设置图像名称
plt.xlabel("Bean Size") # 设置横坐标的名字
plt.ylabel("Toxicity") # 设置纵坐标的名字
w = 0.1
for _ in range(m):
for i in range(m):
x = xs[i]
y = ys[i]
# 代价函数
# e0= x0**2 * w**2 + (-2x0*y0) +y0**2
# a = x^2
# b = -2*x*y
# c = y^2
# 斜率 k = 2aw+b
k = 2 * (x ** 2) * w + (-2 * x * y)
alpha = 0.1
w = w - alpha * k
print(w,end='\n');
plt.clf() # 清空窗口
plt.scatter(xs, ys)
y_pre = w * xs
plt.xlim(0, 1)
plt.ylim(0, 1.2)
plt.plot(xs, y_pre)
plt.pause(0.01) # 暂停0.01秒
# y_pre = w * xs
# print(y_pre)
# plt.plot(xs, y_pre)
# plt.show()
标签: 人工智能