机器学习 一:最小点与梯度下降,Python实现

本专题依然是学习笔记,如果有误,还请大佬指正。以往我对ML,DL方面的认知是零散的,读研后很幸运获得了系统学习这方面知识的机会,所以将会在这里把我所理解的机器学习知识点做相关记录,希望也能够帮到大家~

OK 进入正题:梯度下降

若按学科划分,其实梯度下降应该是一个优化问题。但是Optimization这门课实在让人头疼(一定是老师的问题 lol),所以梯度下降就作为机器学习的第一节讲吧。

一、一些必要概念的简单介绍(主要是引入主题,粗略看下,如果都了解,跳过也没事)

众所周知,机器学习:由特定输入,计算机经过某种模型自主学习优化模型参数,得到目标输出。在这个定义中,我们可以得到机器学习的几大要素:输入数据(input data),目标输出(outputs),预测模型(hypothesis models),损失函数(loss function)和一个未知的最佳模型(true model)。机器学习就是通过输入数据获得预测输出,通过损失函数衡量获得的预测输出与真实情况的差异,从而将模型的参数进行调整,使得这种差异越来越小,直到“理论上的”最佳模型。为什么说“理论上”的呢?因为在实际应用中,几乎是不可能得到最优的模型(参数)的,我们只能无限接近于这个最优解。 

那么上述的“预测模型”和“最佳模型”是啥呢?为了方便理解,可以简单理解为是一个非常复杂的数学函数 f: R^n\rightarrow R^m(输入n维数据,得到m维输出)。举个简单的例子:

输入数据为1维 X=[1,2,3,4],实际的目标值为Y=[2,4,6,8]。显然,X,Y为线性关系:Y=2X,那么f(X)=2X就是我们的模型。

但模型的初始化是随机的,即我们最初得到的模型也许是Y=wX。(就假设是线性模型吧)

那么,在这个随机线性模型下得到的输出一定和目标值有差异,这个差异就是损失(Loss),也可称为残差(Residual)。(碎碎念:看不懂没事,But let's make it clear, 这和残差块residual block八竿子打不着的关系,OK? 只是英文名有点像而已)

损失的定义:想咋定义就咋定义~只要能让模型很好的收敛得到与实际相符合的输出,那就是好损失。但通常我们会遵循某种规范,如使用MSE(均方差)的形式等,因为这些惯用的损失通常能够很好地帮助我们得到“最佳”模型。至于为什么,我打算在下一章讲。(也许吧,我也不知道什么时候有空写下一章)。

假设我们这里就规定使用MSE作为模型的Loss,那么上面例子的Loss可以表示为:

Loss = \sum_{i=1}^{n} ||y'_i-y_i||^2=\sum_{i=1}^{n} ||x_iw-y_i||^2=||Xw-y||^2                                          

我们的目标就是要最小化这个Loss。注意,这里的变量是w而不是x哦!因为我们是要获得新模型,即改变w,x和y都是数据集里给的,看做常量。

那么怎么最小化呢?以这个例子为例,明显是一个关于w的二次函数,那对其求导,然后极小点也就是最小点就得到了(因为二次函数是个凸函数,至于凸函数是啥,就不在这里讲了,因为我懒~),so easy~。 BUT!如果这个函数很复杂呢?仅仅通过求一阶导,只能得到驻点,但不能证明它就是全局最小点呀?

答案是:通过求二阶导,这一块是凸优化的知识,如果没兴趣就跳过,至少不影响对梯度下降的理解。但我还是建议大家看一看,因为可以帮我们更好理解最小点的定义。(毕竟,打字挺累的hh)

如果二阶导在定义域上≥0恒成立,那这个驻点就是全局最小点。

如果二阶导在除了驻点外的定义域上>0恒成立,那这个驻点为严格全局最小点。

如果二阶导在驻点的邻域上>0恒成立,那这个驻点为严格局部最小点

那如果推广到高维呢?输入的X是一个n维向量,即这个模型需要接受k个输入(矩阵的每一行为一次输入):

X=\begin{pmatrix} x_{11} & ... & x_{1k}\\ ...& ... & ...\\ x_{j1}& ... & x_{jk}\\ ...& ... & ...\\ x_{n1}& ... &x_{nk} \end{pmatrix}

那得到的损失函数的一阶导,二阶导则分别称为

梯度Gradient 和 黑塞矩阵Hession Matrix。(再次提醒,黑塞矩阵看不懂没关系,我只是想提一嘴,但梯度你得知道)

梯度是一个一维向量,代表函数增长最快的方向,公式为

\bigtriangledown f(X)=\begin{pmatrix} \frac{\partial f}{\partial x_1}\\ ...\\ \frac{\partial f}{\partial x_k} \end{pmatrix}

黑塞矩阵就是二阶导推广到高维的形式,二阶导>0(≥0)对应黑塞矩阵(半)正定,公式为:

Hf(X) = \begin{pmatrix} \frac{\partial^2 f}{\partial x^2_1} & ...& \frac{\partial^2 f}{\partial x_1x_k}\\ ... & ....&... \\ \frac{\partial^2 f}{\partial x_kx_1} &... & \frac{\partial^2 f}{\partial x^2_k} \end{pmatrix}

二、梯度下降算法

说了一堆,终于进正题了,我好磨叽。。。

简单来说,梯度下降算法就是寻找函数最小值点的过程。为了方便理解,我们先考虑w是一维的。假设上述的Loss我们写成函数g(w),g(w)是一条弯弯扭扭的曲线。想象成g(w)是个巨型的上上下下的过山车,我们随机挑一点坐上去,我们会向低处滑吧,到了最低处又会因为惯性朝着上坡方向滑,然后冲上去一点点又滑回来了,最终我们会停在这个局部最低点。没问题吧。这其实就是梯度下降算法。

前文说过,梯度代表函数值增长最快的方向,那我们沿着梯度反方向走,那不就是在走向函数的最小值吗?

OK,回到n维的情况。假设我们现在出生在w_0点,我们下一步往哪里走,走多远?我们定义走的距离和方向为\bigtriangleup w = -\eta \bigtriangledown g(w),即下一步走到w=w_0+\bigtriangleup w处。

为啥这么定义\bigtriangleup w?因为,给g(w)的梯度加负号,就是沿着梯度反方向走吧。方向有了,走多远?走\eta倍的\bigtriangledown g(w)那么远。至于\eta,换个名称也许大家就熟悉了,他就是鼎鼎大名,让无数人秃头的的 学习率 learning rate!(surprise~)如何设置学习率是门学问,嗯,我也不清楚。如果学习率太大会导致梯度爆炸,太小会导致收敛很慢。但通常随大流,设置为10的-2,-3,-4,-5,-6次方就差不多够你用了。如果是深度学习,那还可以使用学习率自适应。

扯远了,回归正题。

考虑g(w)在w点处的泰勒展开式,只用展开两项就够了(唉,除了Taylor Swift, 没有一个Taylor是好人)

g(w+\bigtriangleup w) = g(w)+\bigtriangledown g(w)\bigtriangleup w+\frac{Hg(w)}{2!}\bigtriangleup w+o(w^2)

只考虑前两项,我们有

g(w+\bigtriangleup w) \approx g(w)+\bigtriangledown g(w)\bigtriangleup w

带入\bigtriangleup w = -\eta \bigtriangledown g(w),得到:

g(w-\eta \bigtriangledown g(w)) \approx g(w)-\eta ||\bigtriangledown g(w)||^2

显然可以得到

g(w-\eta \bigtriangledown g(w)) < g(w)  .............................(1)

验证了我们的想法,g(w)的值确实变小。

聪明的你一定发现,这写出来是个递归函数,那出口是什么呢?主观设定一个很小的阈值,当g(w)的变化小于这个阈值,那么就可以停止递归了。

此外还要注意,如果\eta设置的很大,那么就不能得到(1)式了,因为\bigtriangleup w变得很大,不满足泰勒公式的要求。这时候你会发现,一旦递归,下一个w可能飞到右边很远的地方,g(w)会比之前更大,由此以往,就导致了梯度爆炸。

另外再提一点。也许你会有这样的疑问:既然前面介绍最小点的时候,提到可以直接令梯度=0,求出所有的驻点,然后比一比函数在哪个驻点处的值最小,不就得出最优解了吗?为什么还要梯度下降这个“永远达不到最优解”的方法呢?

原因:首先:梯度为0只能得到驻点。但如果黑塞矩阵不定,那么这个驻点就是鞍点。第二:对于更高维的函数,对每一个驻点求黑塞矩阵其实计算量也是很大的。一个简单的神经网络都能有数十万个参数,那这个黑塞矩阵求出来得多大?第三:如果了解回归问题的闭式解closed form(在以后的博文中会讲),你就会发现,其实这个闭式解就是直接令梯度为零得到的。但这个闭式解并不是恒成立的,前提要求是X^TX可逆。如果不可逆,那将会有无穷多个驻点,那想要一个个去求对应的黑塞矩阵就更不可能了。

所以,梯度下降提供了一个能够计算任意函数的最优解的通用方法。

三、Python模拟

f(x,y)=x^2+y^2-xy进行梯度下降找局部最小点。初始点为(1,1)【这是我的作业题,不知道能不能碰到校友】

import numpy as np
import matplotlib.pyplot as plt

def f(xy):
    x = xy[0][0]
    y = xy[1][0]
    return x**2 + y**2 - x*y

def f_grad(xy):
    x = xy[0][0]
    y = xy[1][0]
    return np.array([[2*x - y],
                     [2*y - x]])
    
xy_0 = np.array([[1],
                 [1]])
xy_raw = xy_0
epoch = 3000
learning_rate = 0.1 # or 0.1 or 0.001
record_xy = []
record_xy.append(xy_0)
record_f = []
record_f.append(f(xy_0))

for e in range(epoch):
    xy_new = xy_raw - learning_rate*f_grad(xy_raw)
    f_xy_new = f(xy_new)
    
    record_xy.append(xy_new)
    record_f.append(f_xy_new)
    xy_raw = xy_new
#print(record_xy)
record_xy = np.array(record_xy)
record_f = np.array(record_f)
X = record_xy[:, 0, 0]
Y = record_xy[:, 1, 0]
Z = record_f
print(f"f = {record_f[-1]}")
print(f"local minimizor = (x,y) ({record_xy[-1][0][0]},{record_xy[-1][1][0]})")

x = np.arange(-1.1,1.1,0.1)
y = np.arange(-1.1,1.1,0.1)
x,y = np.meshgrid(x,y)
f_ = x**2 + y**2 -x*y

ax = plt.axes(projection='3d')
ax.plot_surface(x,y,f_, alpha=0.3, cmap='winter')
ax.scatter(X,Y,Z, color='r')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('f')
plt.show()

结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三头猪等于一头大猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值