损失函数、梯度下降,深度学习的基础全打通!

2021-01-08 15:23:52 浏览数 (1)

点击上方蓝字,关注并星标,和我一起学技术。

大家好,我们今天来继续聊聊深度学习

在上一篇文章当中我们简单介绍了感知机和神经网络的一个关系,对神经网络有了一个粗浅的理解。其实神经网络并没有大家想的那么难,每个神经元之间的数据传输以及计算方式都是确定的。仅仅知道神经网络的结构还是不够的,更重要的是这个网络它究竟是如何学习的,这才是我们要了解的重点。

今天本文将会继续介绍神经网络的一些基础内容。

损失函数

在早年的时候,深度学习这个概念并没有从机器学习当中单独拆分出来,神经网络也是机器学习模型的一种,也是一个部分。只是后来由于神经网络在很多方面的表现非常出色,远远胜过了传统模型。尤其是AlphaGo战胜了李世石之后,引来了外界大量的关注,从而得到了迅猛的发展。

既然神经网络也是机器学习的一个部分,那么神经网络模型同样需要损失函数。损失函数的作用是量化模型当前的性能,由于是程序执行,我们需要有一个明确的指标告诉我们模型的能力究竟如何。另外模型训练也需要一个学习的目标,缩小损失函数就是模型学习的目标。

深度学习常用的损失函数也是两种,和机器学习一样,我们简单复习一下:

均方差

均方差一般用来回归模型,它可以写成:

E = frac12sum_{k}(y_k-hat{y_k})^2

这里的k表示样本的维度,

y_k

表示的是样本第k维的真实值,而

hat{y_k}

模型在k维度上的输出。我们可以用Python自己实现一个:

代码语言:javascript复制
def mean_square_err(y, y_p):
    return 0.5 * np.sum((y - y_p) ** 2)

交叉熵

交叉熵我们用得非常多,但是很多人不知道它是怎么来的,关于交叉熵的前因后果我曾经也写过相关的文章,大家感兴趣可以点击下方传送门了解一下。

机器学习基础——详解机器学习损失函数之交叉熵

简单来说,交叉熵一般被用作分类问题的评估,对于分类问题,我们一般神经网络面临的是一个one-hot的向量。正确的类别是1,其他均为0,而神经网络输出的结果是样本落在每一个类别当中的概率。我们希望得到的结果当然是正确的概率越大越好,而错误的越小越好,交叉熵就可以很好地完成这个目的。

它的公式可以写成:

E = -sum_{k}y_klog hat{y_k}

用代码实现交叉熵同样不难:

代码语言:javascript复制
def cross_entropy_err(y, y_p):
    delta = 1e-7
    return -np.sum(y * np.log(y_p   delta))

这里我们给y_p加上了一个delta是为了防止y_p等于0的情况出现,这样的话log计算会没有意义。

mini-batch学习

由于深度学习的使用场景的数据量往往很大,比如在推荐系统当中,我们的训练样本量至少是百万起步。对于这么大的数据量我们显然是不可能采用全量学习的,每次的训练必须要进行采样。一般来说我们每次采样的样本数量大概在100~1000的区间内,根据实际情况自行调整。

对于如此庞大的样本量来说,模型训练非常多批次的训练才可以遍历完所有的样本。我们把一次样本的全部遍历称作一个epoch,而单词的小数据样本称为一个batch。一般来说对于一次训练,epoch是确定的,一般在10左右。而batch数量是不确定的,是要根据训练数据量来调整的。

梯度

深度学习和机器学习一样,也是通过梯度的方式来调整神经网络当中的参数从而达到学习的目的。那么求梯度就是必须的,大家可能会感到疑惑,我们数学上来求导求梯度是非常方便的,但是在程序当中我们如何实现这个功能呢?

其实很简单,导数原本的定义就是微分,也就是f(x h) - f(x - h) / 2h的值,也就是一个极小的h带来的斜率变化。我们可以利用Python函数式编程的概念,很容易实现求导的函数。

代码语言:javascript复制
def get_diff(f, x):
    h = 1e-5
    return (f(x h) - f(x-h)) / (2 * h)

这里的参数f代表的就是我们需要求导的函数,这样我们就适配了所有函数。

导数求出来了,梯度自然也就好求了,梯度本质上的定义其实是函数对于各个变量偏导组成的向量。比如我们的样本是

(x_0, x_1)

,在这一点的梯度就是

(frac{partial f}{partial x_0}, frac{partial f}{partial x_1})

。所以我们只需要对求导的函数稍加改动就得到了我们求梯度的代码:

代码语言:javascript复制
def get_gradient(f, x):
    h = 1e-5
    grad = np.zero_like(x)
    
    for i in range(x.size):
        fx1 = f(x[i] - h)
        fx2 = f(x[i]   h)
        grad[i] = (fx1 - fx2) / (2 * h)
    return grad

梯度下降法

梯度求解出来了之后,很自然地就会想到的就是梯度下降法。我相信熟悉机器学习的同学对于这一点应该已经非常熟悉了,两者的本质都是一样的。

对于参数

x_0

而言,当我们通过样本得到了它的梯度之后,我们就可以对它的值进行调整,往梯度下降的方向调整。即:

x_0 - eta frac{partial f}{partial x_0}

。这里的

eta

是一个参数,表示学习率,用来控制梯度下降的速度,防止陷入局部最优解当中。

由于神经网络的参数空间比较大,维度比较高,所以它陷入的局部最优解不是我们理解的某一个低谷,而是一个不是最低,但是在各个方向的梯度均为0的这么一个点,它在空间的形状也会比较复杂。由于整个函数成一个类似马鞍的形状, 所以这个局部最优点称为鞍点。

比如在下图当中,红色点的部分各个方向的梯度均为0,但是它显然不是函数的最低点。但是通过梯度下降法到达这个点之后就无法再进行更新了。

对于落入鞍点的情况我们目前还没有比较好的策略,唯一能做的就是对学习率以及损失函数进行调整,但是这并不能根本上解决这个问题。这也是很多人说神经网络比较玄学的原因,因为很多事情不可控。我们知道发生的原因,但是却没办法解决。

深度学习本身就是从机器学习当中衍生而来,所以它们有一些概念类似或者是雷同是非常正常的。大家也不要有心理负担,可以把神经网络当成是一类模型而不是一个独特的领域来理解和学习,并没有大家想的那么难。

今天的文章就到这里,衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、在看、转发

0 人点赞