梯度下降从放弃到入门

简介: 梯度 在向量微积分中,标量场的梯度是一个向量场。标量场中某一点上的梯度指向标量场增长最快的方向,梯度的长度是这个最大的变化率。 在三维直角坐标系中表示为: $$ \nabla \varphi = \left( \frac{\partial \varphi}{\partial x}, \frac{.

梯度

在向量微积分中,标量场的梯度是一个向量场。标量场中某一点上的梯度指向标量场增长最快的方向,梯度的长度是这个最大的变化率。

在三维直角坐标系中表示为:

$$ \nabla \varphi = \left( \frac{\partial \varphi}{\partial x}, \frac{\partial \varphi}{\partial y}, \frac{\partial \varphi}{\partial z}\right) = \frac{\partial \varphi}{\partial x}\vec{i} + \frac{\partial \varphi}{\partial y}\vec{j} + \frac{\partial \varphi}{\partial z}\vec{k} $$

梯度下降

梯度下降是一个用来求函数最小值的算法,在机器学习所有优化算法当中,梯度下降应该算是最受关注并且用的最多的一个算法。

梯度下降背后的思想是:开始时我们随机选取一个参数的组合$(\theta_0,\theta_1,\cdots,\theta_n)$,计算代价函数$J(\theta)$,然后我们寻找下一个能让代价函数值下降最多的参数组合。我们持续这么做,直到找到一个局部最小值,因为我们并没有尝试完所有的参数组合,所以不能确定我们得到局部最小值是否便是全局最小值,不同的初始参数组合,可能会找到不同的局部最小值。如果我们知道代价函数是一个凸函数,那么我们就可以保证我们找到的局部最小点就是全局最小点。

通过梯度的定义,我们知道,梯度的方向是增长(上升)最快的方向,那么梯度的反方向便是让代价函数下降程度最大的方向。

定义$\alpha$为学习率(learning rate),它决定了我们沿着能让代价函数下降程度最大的方向更新的步长。

$$ \theta_j = \theta_j - \alpha \frac{\partial J(\theta)}{\partial \theta_j} $$

每走一步,我们都要重新计算函数在当前点的梯度,然后选择梯度的反方向作为走下去的方向。随着每一步迭代,梯度不断地减小,到最后减小为零。

线性回归

假定我们有一批数据点,需要根据这些点找出最适合的线性方程$y = ax + b$。

X = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Y = [0.199, 0.389, 0.580, 0.783, 0.980, 1.177, 1.380, 1.575, 1.771]

gd_points

也就是要找出最合适的参数$(a, b)$,使得直线到所有点的距离尽可能小。他可以转化为求解代价函数1

$$ J(a, b) = \sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right)^2 $$

的最小值。

我们也可以令$J(a, b)$为误差平方和(SSE)的平均数,为了后续求导计算方便,我们还可以再把他除以2,即代价函数2

$$ J(a, b) = \frac{1}{2m}\sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right)^2 $$

如果$(a, b)$在代价函数2上取得最小值,也必在代价函数1上取的最小值。

其中

$$ x^{(i)}, y^{(i)} $$

为已知数。

$$ \frac{\partial}{\partial a}J(a,b) = \frac{\partial}{\partial a}\left(\frac{1}{2m}\sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right)^2\right) = \frac{1}{m}\sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right)x^{(i)} $$

$$ \frac{\partial}{\partial b}J(a,b) = \frac{\partial}{\partial b}\left(\frac{1}{2m}\sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right)^2\right) = \frac{1}{m}\sum_{i=1}^m\left(ax^{(i)} + b - y^{(i)}\right) $$

利用梯度下降法,我们很容易实现对应的迭代解法。

$$ a := a - \alpha \frac{\partial}{\partial a}J(a,b) $$

$$ b := b - \alpha \frac{\partial}{\partial b}J(a,b) $$

def gd(X, Y, alpha=0.01, epsilon=1e-8):
    m = len(X)
    a, b, sse2 = 0, 0, 0
    while True:
        grad_a, grad_b = 0, 0
        for i in range(m):
            diff = a * X[i] + b - Y[i]
            grad_a += X[i] * diff
            grad_b += diff

        grad_a = grad_a / m
        grad_b = grad_b / m

        a -= alpha * grad_a
        b -= alpha * grad_b

        sse = 0
        for j in range(m):
            sse += (a * X[j] + b - Y[j]) ** 2 / (2 * m)

        if abs(sse2 - sse) < epsilon:
            break
        else:
            sse2 = sse
    return a, b

测试代码如下:

a, b = gd(X, Y, 0.05, 1e-6)

print 'y = {0} * x + {1}'.format(a, b)
# y = 0.193958973089 * x + 0.0161212366801
x = np.array(X)
plt.plot(x, Y, 'o', label='Original data', markersize=5)
plt.plot(x, a * x + b, 'r', label='Fitted line')
plt.show()

gd_points_fit

多变量线性回归

上例,我们演示了一元线性回归梯度下降的迭代解法,更一般地,我们考虑n个变量的情况,我们有m条记录。

origin_matrix

为了不失一般性和简化公式,我们令 $\left.x_0^{(i)} = 1\right|_{i \in (1,2,\cdots,m)}$

我们需要找到一个假设$h$,使得应用到上述数据,其代价函数最小。

$$ h_{\theta}(x) = \theta_0x_0 + \theta_1x_1 + \theta_2x_2 + \cdots + \theta_nx_n $$

或者

$$ h_{\theta}(x) = \sum_{j=0}^n\theta_jx_j = \begin{bmatrix} \theta_0 & \theta_1 & \cdots & \theta_n \end{bmatrix} \cdot \begin{bmatrix} x_0 \\ x_1 \\ \vdots \\ x_n \\ \end{bmatrix} = \theta^TX $$

其中,$x_0 = 1$, $\theta_0$为偏置。

$$ J(\theta) = \frac{1}{2m}\sum_{i=1}^m\left(h_{\theta}\left(x^{(i)}\right) - y^{(i)}\right)^2 $$

则$J(\theta)$梯度为

_2017_11_06_2_19_28

$$ \frac{\partial J(\theta)}{\partial\theta_j} = \frac{\partial}{\partial\theta_j}\left(\frac{1}{2m}\sum_{i=1}^m\left(h_{\theta}\left(x^{(i)}\right) - y^{(i)}\right)^2\right) = \frac{1}{m}\sum_{i=1}^m\left(h_{\theta}\left(x^{(i)}\right) - y^{(i)}\right)x_j^{(i)} $$

利用梯度下降法,我们很容易实现对应的迭代解法。

$$ \theta_j := \theta_j - \alpha \frac{\partial}{\partial\theta_j}J(\theta) $$

def gd(X, Y, alpha=0.01, epsilon=1e-6):
    m, n = len(X), len(X[0])
    theta = [1 for i in range(n)]
    sse2 = 0
    while True:
        gradient = [0 for i in range(n)]
        for j in range(n):
            for i in range(m):
                hypothesis = sum(X[i][jj] * theta[jj] for jj in range(n))
                loss = hypothesis - Y[i]
                gradient[j] += X[i][j] * loss
            gradient[j] = gradient[j] / m
            
        for j in range(n):
            theta[j] = theta[j] - alpha * gradient[j]

        sse = 0
        for i in range(m):
            loss = sum(X[i][jj] * theta[jj] for jj in range(n)) - Y[i]
            sse += loss ** 2
        sse = sse / (2 * m)

        if abs(sse2 - sse) < epsilon:
            break
        else:
            sse2 = sse
    return theta

之所以不在同一个循环里面同时计算梯度和更新$\theta$,是因为正确的做法需要保证$\theta$同时更新。

# 取x_0 = 1
X = [(1, 1.), (1, 2.), (1, 3.), (1, 4.), (1, 5.), (1, 6.), (1, 7.), (1, 8.), (1, 9.)]
Y = [0.199, 0.389, 0.580, 0.783, 0.980, 1.177, 1.380, 1.575, 1.771]

b, a = gd(X, Y, 0.05, 1e-6)

print 'y = {0} * x + {1}'.format(a, b)
# y = 0.193953964855 * x + 0.01615274985

更优雅的实现

之前一直有个疑问,不清楚为什么用显卡可以提高优化算法的执行效率,直到我接触到了如下代码:

loss = np.dot(X, theta) - Y
gradient = np.dot(X.T, loss) / m
theta -= alpha * gradient

利用矩阵运算,代码可以变的相当简洁,而且可以利用显卡多核的优势。

推导过程如下:

gradient_loss
gradient_compute

完整代码如下

import numpy as np

def gd(X, Y, alpha=0.01, epsilon=1e-6):
    m, n = np.shape(X)
    theta = np.ones(n)
    sse2 = 0
    Xt = np.transpose(X)
    while True:
        hypothesis = np.dot(X, theta)
        loss = hypothesis - Y
        
        sse = np.dot(loss.T, loss) / (2 * m)
        if abs(sse2 - sse) < epsilon:
            break
        else:
            sse2 = sse

        gradient = np.dot(Xt, loss) / m
        theta -= alpha * gradient
        
    return theta
# 取x_0 = 1
X = [(1, 1.), (1, 2.), (1, 3.), (1, 4.), (1, 5.), (1, 6.), (1, 7.), (1, 8.), (1, 9.)]
Y = [0.199, 0.389, 0.580, 0.783, 0.980, 1.177, 1.380, 1.575, 1.771]

b, a = gd(X, Y, 0.05, 1e-6)

print 'y = {0} * x + {1}'.format(a, b)
# y = 0.193953964855 * x + 0.01615274985

最终实现

在上面的使用上,我们需要调用方主动为每一个$x_0$赋值为1,其实这一部分完全可以在算法里面实现,简化调用方的使用成本。

import numpy as np

def bgd(X, Y, alpha=0.01, epsilon=1e-8, trace=True):
    m = len(X)
    _X = np.column_stack((np.ones(m), X))
    m, n = np.shape(_X)
    theta, sse2, cnt = np.ones(n), 0, 0
    Xt = _X.T
    
    while True:
        loss = np.dot(_X, theta) - Y

        sse = np.dot(loss.T, loss) / (2 * m)
        if abs(sse - sse2) < epsilon:
            break
        else:
            sse2 = sse

        if trace:
            print "[ Epoch {0} ] theta = {1}, loss = {2}, error = {3})".format(cnt, theta, loss, sse)

        gradient = np.dot(Xt, loss) / m
        theta -= alpha * gradient
        cnt += 1
        
    return theta
X = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Y = [1.99, 3.89, 5.80, 7.83, 9.80, 11.77, 13.80, 15.75, 17.71]

b, a = gd(X, Y, 0.05, 1e-6)

print 'y = {0} * x + {1}'.format(a, b)
目录
相关文章
|
9月前
|
算法
选择和努力那个更重要?贝叶斯告诉你答案
选择和努力那个更重要?贝叶斯告诉你答案
|
5月前
|
机器学习/深度学习 算法 关系型数据库
PyTorch深度强化学习中蒙特卡洛策略梯度法在短走廊环境(CartPole-v0)中的实战(超详细 附源码)
PyTorch深度强化学习中蒙特卡洛策略梯度法在短走廊环境(CartPole-v0)中的实战(超详细 附源码)
30 0
|
11月前
|
算法 Python
算法创作|随机出10道题并计算正确率问题解决方法
算法创作|随机出10道题并计算正确率问题解决方法
84 2
|
12月前
|
机器学习/深度学习 算法
非凸函数上,随机梯度下降能否收敛?网友热议:能,但有条件,且比凸函数收敛更难
非凸函数上,随机梯度下降能否收敛?网友热议:能,但有条件,且比凸函数收敛更难
|
机器学习/深度学习 PyTorch TensorFlow
深度学习基础之三分钟轻松搞明白tensor到底是个啥
再不入坑就晚了,深度神经网络概念大整理,最简单的神经网络是什么样子?
440 0
深度学习基础之三分钟轻松搞明白tensor到底是个啥
|
算法 Python
通俗易懂讲解梯度下降法!
前言:本篇文章用讲解+实战的形式,浅显易懂讲解“梯度下降”,拥有高中数学知识即可看懂。
|
机器学习/深度学习 算法 C++
7分钟搞懂逻辑回归的来龙去脉
7分钟搞懂逻辑回归的来龙去脉
|
存储 机器学习/深度学习 算法
K近邻算法哪家强?KDTree、Annoy、HNSW原理和使用方法介绍
K近邻算法哪家强?KDTree、Annoy、HNSW原理和使用方法介绍