「学习内容总结自 coursera 和 udacity 的深度学习课程,部分截图来自 udacity 的课件」
一.最简单的神经网络及其输出
如上图所示是最简单的神经网络模型,包含输入x,神经元(又称为感知器)和输出y。
对这个神经网络再具体一点就是下图所示,对输入的x给与权重(在其他较复杂的神经网络中,往往有很多个输入,一个较大的权重意味着神经网络认为这个输入比其它输入更重要,较小的权重意味着该数据不是那么重要)和偏置,并把中间的神经元细分为左右两部分,左半边为神经元的输入,右半边为神经元的输出。
神经元的输入为:z=wx+b,其中w表示为权重,b表示偏置;神经元的输出为a=g(z)=g(wx+b),其中g(z)表示激活函数。最终该神经网络的输出y=g(z)=g(wx+b)
二.多元(层)神经网络及其输出
拿上面这张图举个例子,x1,x2是输入层(layer0);中间的两层(layer1,layer2)称为 隐藏层,隐藏层里的单元称为 隐藏单元;y(layer3)是 输出层。所以这个多元神经网络也称为 3层神经网络。
接下来也像上面一样增加权重w和偏置b,并对神经元进行细分。
PS:[ ]内的数字表示层数,下标数字表示该层网络中的单元数
- 对于layer1: 即有(Z,W,b都是矩阵):
- 对于layer2:
- 对于layer3:
- 对于该3层网络的输出y:
- 推广至L层网络:
三.神经网络的激活函数
参考:浅谈深度学习中的激活函数
1. 什么是激活函数
上面出现的g(z)就是激活函数(activation function),激活函数不是为了“激活”什么,只是在神经网络中添加一些非线性的学习和处理能力,解决线性模型所不能解决的复杂问题。
2. 为什么使用非线性的激活函数
在深层的神经网络中,如果隐藏层仍然使用线性的激活函数,经过网络层层传递,其计算结果仍然是线性的,这与没有添加隐藏层是一样的效果,这样做的话,“深度”反而是没有意义的,并不能帮助我们解决复杂性的问题。所以在构建深度神经网络的时候,要引入非线性的因素,赋予神经元自我学习和适应的能力,从而提高模型的学习能力,能够处理复杂的非线性的数据集问题。
3. 常见的激活函数
常见激活函数有对数几率(又称作 sigmoid),tanh函数 ,ReLU函数和ReLU的变形-leakyReLU函数。
- sigmoid函数
import numpy as np
def sigmoid(x):
return 1/(1+np.exp(-x))
- tanh函数
import numpy as np
def tanh(x):
return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
- ReLU函数
import numpy as np
def relu(x):
return np.maximum(0,x)
- ReLU的变形-leaky ReLU函数
import numpy as np
def leaky_relu(x):
return np.maximum(0.01*x,x)
四.用代码实现一个简单的神经网络
运用上面的知识,实现一个简单的神经网络,它有两个输入节点,一个输出节点,使用sigmoid函数。代码实现过程:
import numpy as np
def sigmoid(x):
# 实施sigmoid函数
return 1/(1+np.exp(-x))
inputs = np.array([0.7, -0.3]) #输入
weights = np.array([0.1, 0.8]) #权重
bias = -0.1 #偏置
# 计算神经网络的输出
output = sigmoid(np.dot(inputs,weights)+bias)
五.神经网络的梯度下降
PS: 此处为了简便,忽略了偏置 b
- 误差函数
给神经网络输入x,我们需要其输出的预测值ŷ近似的等于期望值y。为了能够衡量,我们需要有一个指标来了解预测的好坏,也就是误差(error)。常用的指标有误差平方:
- 梯度下降法
要使网络预测的误差尽可能小,我们所要做的就是尽可能的使这个指标要小,即要找出它的最小值。权重 w 是让我们能够实现这个目标的调节旋钮。我们的目的是寻找权重 w 使得误差平方 E 最小。通常来说神经网络通过梯度下降法来实现这一点。
所谓梯度下降法就是,沿着能够使误差最小化的方向一步步更新权重 w 来减小误差(error)。要找到误差最小化的方向,我们需要计算误差平方对于权重 w 的梯度(对单个输入变量来说是导数,梯度是多个输入变量的导数的泛化)。
- 梯度下降的数学实现过程
我们用这个公式来更新权重 w :
其中w表示权重更新步长,式子如下:
η 表示learning rate,有些地方也用α表示。
接下来运用 链式法则求误差平方 E 对权重 w 的偏导数:
又因为
所以偏差公式化解为:
在这里定义一下误差项(error term) δ :
综上,权重 w 更新的公式为:
以上过程自己推导一边,写代码就会神清气爽:)
- 梯度下降的代码实现过程
假设只有一个输出单元,用 sigmoid 来作为激活函数 f(h),代码实现的过程如下:
import numpy as np
def sigmoid(x):
"""
计算sigmoid
"""
return 1/(1+np.exp(-x))
def sigmoid_prime(x):
"""
# sigmoid函数的导数,后面调用作为输出的梯度
"""
return sigmoid(x) * (1 - sigmoid(x))
learnrate = 0.5
x = np.array([1, 2, 3, 4])
y = np.array(0.5)
# 初始化权重
w = np.array([0.5, -0.5, 0.3, 0.1])
# 计算节点的输入
h = np.dot(w,x)
# 计算神经网络的输出
nn_output = sigmoid(h)
# 计算神经网络的误差
error = y-nn_output
# 计算误差项 sigmoid_prime(h)为输出的梯度
error_term = error*sigmoid_prime(h)
# w
del_w = learnrate*error_term*x
# 权重更新
w = w + del_w
六.神经网络的反向传播
- 反向传播
前面我们已经学习了一个简单神经网络的输出层的误差项 δ ,并用它来更新权重 w 。但对于多层神经网络(以一个两层网络为例),前面的做法更新的只是隐藏层-输出层的权重,对于输入层-隐藏层的权重,我们该如何使用误差项 δ 来更新?这个误差项又该如何计算得出?
利用链式法则我们可以计算输入层-隐藏层间权重的误差项:
因为输出层的误差项是由隐藏层的权重决定的,把这个误差项当做输入反推回网络,就能像前面逐层正向的输出结果一样逐层地反向推导出误差项。这种类似正向传输的过程我们称之为反向传播。
反向传播是训练神经网络的基本原理,因此对于构建深度学习模型,理解反向传播至关重要。- 引自udacity课件
- 反向传播的实现
反向传播的实现包括正向和反向两个操作。正向操作是利用权重和输入,从输入层到输出层,计算出预测值ŷ;反向操作则是利用预测值ŷ,从输出层到输入层,求解出误差,误差项和w(权重更新步长),最后更新权重,完成一次迭代。
代码实现过程如下:
import numpy as np
from data_prep import features, targets, features_test, targets_test
np.random.seed(21)
def sigmoid(x):
"""
Calculate sigmoid
"""
return 1 / (1 + np.exp(-x))
# Hyperparameters
n_hidden = 2 # number of hidden units
epochs = 900
learnrate = 0.005
n_records, n_features = features.shape
last_loss = None
# Initialize weights
weights_input_hidden = np.random.normal(scale=1 / n_features ** .5,
size=(n_features, n_hidden))
weights_hidden_output = np.random.normal(scale=1 / n_features ** .5,
size=n_hidden)
for e in range(epochs):
del_w_input_hidden = np.zeros(weights_input_hidden.shape)
del_w_hidden_output = np.zeros(weights_hidden_output.shape)
for x, y in zip(features.values, targets):
## 正向操作 ##
# TODO: Calculate the output
hidden_input = np.dot(x,weights_input_hidden)
hidden_output = sigmoid(hidden_input)
output = sigmoid(hidden_output)
## 反向操作##
# TODO: Calculate the network's prediction error
error = y-output
# TODO: Calculate error term for the output unit
output_error_term = error*output*(1-output)
## propagate errors to hidden layer
# TODO: Calculate the hidden layer's contribution to the error
hidden_error = weights_hidden_output*output_error_term
# TODO: Calculate the error term for the hidden layer
hidden_error_term = hidden_error*hidden_output*(1-hidden_output)
# TODO: Update the change in weights
del_w_hidden_output += output_error_term*hidden_output
del_w_input_hidden += hidden_error_term*x[:,None]
# TODO: Update weights
weights_input_hidden += del_w_input_hidden*learnrate/n_records
weights_hidden_output += del_w_hidden_output*learnrate/n_records