2023年4月

前言

在我们介绍人工神经元的时候提到了人工神经元模型是模仿生物体的神经元进行设计的模型。在那一篇文章中我们了解到生物神经元中的电信号在达到阈值时才会输出电信号给下一个神经元。人工神经元为了模仿生物神经元这一特征在人工神经元中加入了激活函数(Activation functions)

这篇文章你将会了解:

  • 什么是激活函数
  • 常用的激活函数
  • 使用python编写带有激活函数的神经元完成非线性回归

什么是激活函数

由于我们之前的神经元只有一段线性函数y=wx+b,没有激活函数的神经网络实质上是一个线性回归模型,只能解决线性可分的问题,而对于线性不可分的任务我们之前的神经元就无能为力了,这时候我们就引入了激活函数,使得我们的函数变得不线性也就是变成非线性函数,这可以增加模型泛化能力。

例如我们不可能用一条直线去拟合这种分布的数据:

而曲线可以

所以激活函数能够帮助你的神经元完成更加复杂的工作。

并且我们人类思考问题的过程并不是完美的拟合,而是离散的分类,我们会给食物能否吃饱分成多类:能吃饱,不能吃饱,能吃几分饱(这也是我们根据经验得出的新类),而我们并不会去使用一个食物的大小和饱腹程度的函数去判断是否能吃饱。

我们把之前线性函数的输出放入激活函数中计算后的值做出最后的输出

常见的激活函数

sigmoid

$$ sigmoid(x) = \frac{1}{1+e^{-x} } $$

sigmoid激活函数特点:

  • 把(−∞,+∞)的数映射到(0,1)之间,因此它对每个神经元的输出进行了归一化;
  • Sigmoid 函数非常合适用于将预测概率作为输出的模型。因为概率的取值范围是 0 到 1。
  • 函数处处可导。
  • 函数很快逼近y=0和y=1,导数无限小,这会导致权重更新的非常慢
  • 一般用于二分类的输出层

sigmoid的导函数

$$ \frac{\partial sigmoid(x)}{\partial x} = sigmoid(x)(1-sigmoid(x)) $$

Tanh

$$ Tanh(x) = \frac{e^{x}-e^{-x}}{e^{x}+e^{-x} } $$

Tanh激活函数特点:

  • 函数是将取值为 (−∞,+∞) 的数映射到(−1,1) 之间;
  • 在0附近的导数相比sigmoid更大,收敛速度比sigmoid快
  • 函数处处可导。
  • 函数很快逼近y=0和y=1,导数无限小,这会导致权重更新的非常慢

Tanh的导函数

$$ \frac{\partial Tanh(x)}{\partial x} = 1-Tanh(x)^{2} $$

ReLU

$$ ReLU(x) = \begin{cases} 0 & x\leqslant 0 \\ x & x>0 \end{cases} $$

ReLU激活函数特点:

  • 计算简单高效,相比sigmoid、tanh没有指数运算
  • 相比sigmoid、tanh更符合生物学神经激活机制
  • 收敛速度较快,大约是 sigmoid、tanh 的 6 倍
  • 在x<0时导数为0,这会导致权重参数无法得到更新,这被称为神经元死亡,或者梯度消失

ReLU的导函数

$$ \frac{\partial ReLU(x)}{\partial x} = \begin{cases} 0 & x\leqslant 0 \\ 1 & x>0 \end{cases} $$

使用python编写带有激活函数的神经元完成非线性回归

只使用Sigmoid完成

# 导入numpy库
import numpy as np
from matplotlib import pyplot as plt


def sigmoid(x):
    return 1/(1+np.exp(-x))


def sigmoid_deriv(a):
    return a*(1-a)


def getdata(count):
    """获取指定数量的数据"""
    xs = np.random.rand(count)
    xs = np.sort(xs)
    ys = np.zeros(count)
    for i in range(count):
        x = xs[i]
        yi = 0.7*x+(0.5-np.random.rand())/50+0.5
        if yi > 0.8:
            ys[i] = 1
    return xs, ys


data = getdata(100)
xs = data[0]
ys = data[1]

w = np.random.rand(1)
b = np.random.rand(1)
z = w*xs+b
a = sigmoid(z)

plt.xlim(-0.1, 1)
plt.ylim(-0.1, 3)
plt.scatter(xs, ys)
plt.plot(xs, a)
plt.show()

for j in range(1000):
    for i in range(len(xs)):
        x = xs[i]
        y = ys[i]
        # 前向传播
        z = w*x+b
        a = sigmoid(z)#加入激活函数
        e = (y - a)**2

        alpha = 0.05

        # 反向传播
        deda = -2*(y-a)
        dadz = sigmoid_deriv(a)#加入激活函数后本质还是复合函数求导
        dzdw = x
        dzdb = 1

        dedw = deda*dadz*dzdw
        w = w - alpha*dedw
        dedb = deda*dadz*dzdb
        b = b - alpha*dedb

z = w*xs+b
a = sigmoid(z)
plt.xlim(0, 1)
plt.ylim(0, 3)
plt.scatter(xs, ys)
plt.plot(xs, a)
plt.show()

在上一篇文章中我们使用了化简后的Rosenblatt感知器进行回归预测,在这篇文章中我们将了解损失函数、梯度下降算法以及梯度下降算法的的实现,且加入偏置项系数b。

损失函数

损失函数(Loss Function):定义在单个样本上,算的是一个样本的误差.

代价函数(Cost Function):定义在整个训练集上,算的是所有样本的误差,也就是损失函数的平均。

机器学习中几乎所有的模型都会有损失函数,损失函数描述的是:

预测值和样本数据的实际目标值之间的误差与预测函数的系数之间的关系

均方误差

均方误差代价函数其表达式如下:

$$ e(w,b) = \frac{1}{n}\sum_{i=0}^{n}(y_{i}-(wx_{i}+b))^2 $$

其中y_i表示样本数据的实际目标值表示wx_i+b预测值根据样本数据x_i计算出的预测值。

我们可以发现这个图像是一个开口向上的二元二次函数图像,既然是一个开口向上的二元二次函数,那么我们是不是可以找到一个wb使得整体误差最小(全局最优解)呢?是的在机器学习中正是这样。

寻找最低点的方法有

  • 正规方程

    $$ w=\frac{\sum_{i=0}^{n}(x_{i}y_{i})}{\sum_{i=0}^{n}x_{i}^2} $$

    推导过程是使用了二元一次函数的顶点坐标公式

  • 梯度下降

梯度下降

梯度下降(Gradient descent)的可以类比为一个下山的过程,如图所示函数看似为一座山,山中全是雾,有一个人在山上任意一个位置想要快速找到水,由于山上全是雾,人只能看见脚下的路,那么人可以判断脚下土地的坡度判断自己是否为下坡路,每走一步调整自己的方向,每次向坡度最大的方向走,就走到最低点的位置喝到水。

数学中实现

在数学中具有一个概念叫做梯度,梯度描述的就是一个面上某个点的瞬时变化率。和斜率类似,其实梯度就是斜率更广的说法。

既然描述的是面上某个点的瞬时变化率,那么我们就可以对他进行求导。我们要得到w最小同时b最小的一个点,我们只需要分别对w和b求偏导,当某个点的梯度大于零,对应的偏导数大于零我们只需要让对应的值减去alpha(alpha>0)倍的偏导数,偏导数为正,对应值往小的调整;当某个点的梯度小于零,对应的偏导数小于零我们只需要让对应的值减去alpha(alpha>0)倍的偏导数,偏导数为负,对应值往大的调整。

  • 为什么要在偏导数上乘以一个alpha

    alpha叫做学习率,在上一篇文章中遇到过。在调整w和b的过程中如果没有学习率alpha它将可能无法或者很慢的回到最低点,乘以一个alpha可以加快或者调慢速度

$$ \xi =\xi -\alpha \times \frac{\partial f(\xi )}{\partial \xi } $$

随机梯度下降算法

随机梯度下降算法(SGD)取出其中一个样本的数据依次进行梯度下降

反向传播

我们已经知道了梯度下降算法,但是我们如何获得求得损失函数中w和b的偏导呢。其实高中数学足以

观察公式:

$$ e(w,b) = (y_{i}-(wx_{i}+b))^2 $$

使用复合函数的求导法则:

$$ \frac{\partial e}{\partial w}=2(y_{i}-b-wx_{i})\cdot (-x_{i}) $$

$$ \frac{\partial e}{\partial b}=2(y_{i}-b-wx_{i})\cdot (-1) $$

调整w和b

$$ w =w -\alpha \times \frac{\partial e}{\partial w } $$

$$ b =b -\alpha \times \frac{\partial e}{\partial b } $$

代码实现

将使用SGD实现

首先先引入需要用到的库

import numpy
from matplotlib import pyplot as plt

引入numpy数学库以方便我们的数学计算

引入matplotlib中的pyplot模块进行画图操作

生成数据以便进行训练

def getdata(count):
    """获取指定数量的数据"""
    x = np.random.rand(count)  # 生成区间[0,1)的随机数
    x = np.sort(x)  # 进行从小到大排序
    y = [-1.5*xx+np.random.rand()/3+1 for xx in x]  # 使用列表推导式生成对应的值
    return x, y

创建一个名为getdata的函数随机生成有共同特征的数据,有一个参数count来接收获取数据的数量

获取100个数据

data = getdata(100)
xs = data[0]
ys = data[1]

编写预测函数

w = np.random.rand(1)
b = np.random.rand(1)
z = w*xs+b

绘制图像

plt.xlim(0, 1)
plt.ylim(0, 3)
plt.scatter(xs, ys)
plt.plot(xs, z)
plt.show()

随机梯度下降算法和反向传播

for j in range(500):
    for i in range(len(xs)):
        x = xs[i]
        y = ys[i]

        z = w*x+b
        e = (y - z)**2#损失函数
        alpha = 0.05
        
        #反向传播和随机梯度下降
        dedz = -2*(y-z)
        dzdw = x
        dzdb = 1
        dedw = dedz*dzdw
        w = w - alpha*dedw
        dedb = dedz*dzdb
        b = b - alpha*dedb
        
        """
        你也可以这么写
        dedw = -2*(y-z)*x
        w = w - alpha*dedw
        dedb = -2*(y-z)*1
        b = b - alpha*dedb
        """

再次绘制拟合后的图像

z = w*xs+b
plt.xlim(0, 1)
plt.ylim(0, 3)
plt.scatter(xs, ys)
plt.plot(xs, z)
plt.show()

可以看到已经完美拟合

完整代码

# 导入numpy库
import numpy as np
from matplotlib import pyplot as plt


def getdata(count):
    """获取指定数量的数据"""
    x = np.random.rand(count)  # 生成区间[0,1)的随机数
    x = np.sort(x)  # 进行从小到大排序
    y = [-1.5*xx+np.random.rand()/3+1 for xx in x]  # 使用列表推导式生成对应的值
    return x, y


data = getdata(100)
xs = data[0]
ys = data[1]

w = np.random.rand(1)
b = np.random.rand(1)
z = w*xs+b

plt.xlim(0, 1)
plt.ylim(0, 3)
plt.scatter(xs, ys)
plt.plot(xs, z)
plt.show()

for j in range(500):
    for i in range(len(xs)):
        x = xs[i]
        y = ys[i]

        z = w*x+b
        e = (y - z)**2
        alpha = 0.05

        dedz = -2*(y-z)
        dzdw = x
        dzdb = 1

        dedw = dedz*dzdw
        w = w - alpha*dedw
        dedb = dedz*dzdb
        b = b - alpha*dedb
        """
        你也可以这么写
        dedw = -2*(y-z)*x
        w = w - alpha*dedw
        dedb = -2*(y-z)*1
        b = b - alpha*dedb
        """

z = w*xs+b
plt.xlim(0, 1)
plt.ylim(0, 3)
plt.scatter(xs, ys)
plt.plot(xs, z)
plt.show()