神经网络基础—基于梯度下降法实现神经网络

一、神经网络的基本含义

根据蔡自兴所著的《人工智能原理及应用》的定义:人工神经网络是由大量的人工神经元互相连接,模拟人脑神经系统的结构和功能。

主要分为三层:输入层、隐藏层和输出层。

输入层是输入我们的样本数据,通过隐藏层的计算得到输出层的结果。

其中有几个很重要的函数需要了解:

(1)激活函数:

由于我们进行学习时很多场景并不一定是线性关系,很多是非线性的关系。但是我们人工神经网络利用的还是线性回归的思想。

比如:y = w1 * x1 + w2 *x2 + w3 * x3 。 y与x1、x2、x3三个自变量有关系,每个自变量对y的影响程度是由w1,w2,w3决定的。但是这个模型明显是一个线性的结构。但是很多场景并不是线性的,因此需要一个函数 p = f(y),将y这样一个线性的结果加入非线性的因素。

因此,这个函数就是激活函数。

常见的激活函数有:sigmod 函数:f(x) =  1 / (1 + e^-x)  这个函数主要用于二分类问题。

                               softmax函数:如果是多分类问题可以用softmax函数。 

                               还有tanh、ReLU等激活函数。可以参考https://blog.csdn.net/kangyi411/article/details/78969642

(2)损失函数

损失函数(loss function)也叫代价函数(cost function),是神经网络优化的目标函数。

一定要注意这句话:神经网络训练或者优化的过程就是最小化损失函数的过程(损失函数值小了,对应预测的结果和真实结果的值就越接近)

其实损失函数就是对学习过程的一个评价。每次学习得到一个[w1, w2, w3],在该[w1, w2, w3]下的损失值越大其实离真实结果就越远。

因此,梯度下降法其实是对损失函数进行梯度下降的。

二、案例

比如我们现在有一个图片数据集,这些图片有三个类别,分别是猫、狗、鸡。我们使用独热码对猫、狗、鸡进行编码:比如猫就是(1,0,0),狗是(0,1,0),鸡是(0,0,1)

假如每个图片数据都是一个1行2列的结构(其实在实际的图片识别中并不会有这么简单的结构,比如28*28,28行28列一共有784个子数据)。假设有张图片X,肉眼判断是一只鸡,X = [0.6, 0.9](1行2列) 我们想通过输入一个X=[0.6, 0.9],得到一个输出,即y = [0,0,1](X是一只鸡),该怎么办呢?我们用梯度下降法来进行学习。

(1)先设置好X和y的数据,如下。

import numpy as  np
X = np.array([[0.6, 0.9]]) #这个是输入层
y = np.array([0, 0, 1]) #这个是目标输出

(2)写个神经网络

接下来我们先写一个简单的神经网络,只有输入层和输出层,都没有隐藏层,主要是为了理解神经网络的基本工作原理。

具体每个代码的含义我都写在了注释上,可以看注释进行理解:

# Sigmoid 二分类激活函数
# Softmax 多分类激活函数
def _softmax(x):
    exp_x = np.exp(x)
    return exp_x / np.sum(exp_x)


# 损失函数:指导参数进行调整的方向性指导
def cross_entropy_error(p, y):
    delta = 1e-7  # 为了防止log(p)中的p为0导致函数报错,因此加了一个delta,这是一个很小的数
    return np.sum(-y * np.log(p + delta))



# 这是一个简单的神经网络
class simpleNet:

    def __init__(self):
        np.random.seed(0)
        # 定义权值
        self.W = np.random.randn(2, 3) 
        # 设置初始的权值,由于我们输入一个1行2列的数据,想得到一个1行3列的数据结果,因此要生成1个2行3列的权值矩阵

    def forward(self, x):
        # 这是在做点乘,将输入的x与权值W进行点乘得到一个1行3列的输入值。 
        return np.dot(x, self.W)

    def loss(self, x, y):
        z = self.forward(x)
        # 由于forward是点乘,是一个线性模型。需要加入激活函数来增加非线性的因素
        # 由于本场景是一个多分类问题,采用了softmax函数作为激活函数
        p = _softmax(z)
        # 这是损失函数
        loss = cross_entropy_error(p, y)
        return loss

重点说一下cross_entropy_error(p,y)函数:这是损失函数,采用了交叉熵损失函数

常见的损失函数可以参考:https://zhuanlan.zhihu.com/p/58883095

损失函数用来评价模型的预测值真实值不一样的程度,因此cross_entropy_error(p,y) 这个函数中的p 其实就是神经网络的输出值,y是正确答案,使用cross_entropy_error(交叉熵损失函数)来判断一下输出值与正确答案之间的差距。

(3)用梯度下降法来找到最合适的权值W

这里我们采用数值微分的梯度下降法来进行说明。还是先看代码。对着代码我们讲解思路。

def numerical_gradient(f, x):

    h = 1e-4 #0.0001

    grad = np.zeros_like(x)  # 生成一个与X形状一样的 0 矩阵


    # nditer 对象有另一个可选参数 op_flags。
    # 默认情况下,nditer 将视待迭代遍历的数组为只读对象(read-only)
    # 为了在遍历数组的同时,实现对数组元素值得修改,必须指定 read-write 或者 write-only 的模式。
    # https://www.runoob.com/numpy/numpy-terating-over-array.html
    it = np.nditer(x, flags = ['multi_index'], op_flags = ['readwrite'])


    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]

        x[idx] = float(tmp_val) + h
        fxh1 = f(x)

        x[idx] = float(tmp_val) - h
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val

        it.iternext()


    return grad

第一个:numerical_gradient(f, x),需要传入两个参数,分别是f 和 x。其中 f 是损失函数, x为权重(2行3列)。

为了让一些初学者理解梯度下降就简单说一下梯度下降的原理哈

首先,我们的目标是什么?是不是希望我们的模型足够强大,分类识别率最高。那么就需要输出值与正确答案之间的差距越小越好,因此损失函数的值越小越好。为了让我们的损失函数值能尽快到最小,我们的学习网络要选择下降速度最快的方向进行学习。

那我们来解释一个numerical_gradient(f, x)函数:我们的思路就是要求出每个方向的梯度,也就是导数,才能知道在这个点到底哪个方向的下降速度最快

h = 1e-4 就是一个很小很小的值。

我们使用一个while循环来求x的梯度:其实就是一个求导的过程。大家看代码应该能看懂的。

grad就是存储导数的,它也是一个2行3列的形状。

def gradient_descent(f, init_x, lr=0.01, step_num=10000):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x

第二个:gradient_descent(f, init_x, lr=0.01, step_num=10000)   f就是损失函数,init_x就是初始的权值,lr是学习效率,stem_num是梯度下降的次数。

grad = numerical_gradient(f, x)  第一次循环的时候 传入损失函数f 和初始权值init_x

由于numerical_gradient函数返回的就是梯度。

更新权重 x = x -  lr * grad。这个方法就是将x进行更新,再进入循环。

重复step_num次,最终得到一个权重x

三、进行学习

net = simpleNet()
f = lambda w : net.loss(X, y)
# 等价于:
# def f(w):
#     return net.loss(X, y)

print(net.W)
dw = gradient_descent(f, net.W)
print(dw)
print(net.W)

Y_predict = net.forward(X)
print(Y_predict)
print(_softmax(Y_predict))
print(net.loss(X, y))

net 就是我们的简单神经网络。定义个f为损失函数,就是net.loss函数,参数是输入层X和正确答案y

dw = gradient_descent(f, net.W) 输入损失函数f和初始权值,在这个初始权值下,计算X得到预测值y_predict,通过y_predict 与正确答案y的比较得到损失值。 dw就是经过10000次梯度下降后的权重值

不断降低损失值,得到新的w,重复step_num次,得到的最终w。

step_num可以自己设置。

四、运行结果

我们先看看初始的net.W和学习完后的dw和net.W的值

这是初始的net.W
[[ 1.76405235  0.40015721  0.97873798]
 [ 2.2408932   1.86755799 -0.97727788]]
这是dw
[[ 0.01517964 -0.79217197  3.91993987]
 [-0.38241586  0.07906423  3.43452494]]
这是学习后的net.W
[[ 0.01517964 -0.79217197  3.91993987]
 [-0.38241586  0.07906423  3.43452494]]

可以看出dw和net.W是相等的。

我们使用初始的net.W来学习X,我们看看Y_predict是多少。与正确答案(0,0,1)差多少

net = simpleNet()
f = lambda w : net.loss(X, y)
# 等价于:
# def f(w):
#     return net.loss(X, y)

print(net.W)
Y_predict = net.forward(X)
print(Y_predict)
print(_softmax(Y_predict))
print(net.loss(X, y))

运行结果:

[[ 3.07523529  1.92089652 -0.2923073 ]]   这是预测值Y_predict
[[0.74088333 0.23357527 0.0255414 ]]  用激活函数softmax后可以看出,明显第一个概率高,因此这个结果是猫(1,0,0)。但是实际是鸡。分类错误
3.6674507891066104  损失值较大,达到了3.66

加入梯度下降法后计算出来的新的W,对X进行预测我们看一下结果:

[[-0.33506649 -0.40414538  5.44303637]]  这是预测值Y_predict
[[0.00307618 0.00287085 0.99405297]] 用激活函数softmax后可以看出,第三个概率极高,第一个第二个概率及较低,因此这个结果是鸡(0,0,1)
0.005964682257980716  损失值很小

上述结果是step_num 为10000时的结果。如果把step_num 改成3000是什么结果呢?

[[0.11696196 0.01044543 4.57641712]]
[[0.01131998 0.01017621 0.9785038 ]]
0.021730502167228764

分类虽然也正确,但是概率较10000次还是低了一点。但是也完全可以接受。

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值