【深度学习笔记】神经网络


感知机是神经网络实现的一个重要思想,因此神经网络和感知机又很多共同点。关于神经网络的结构,如下图,最左边一列为输入层,中间一列为中间层(也称隐藏层),最右边一列是输出层。

激活函数

感知机的数学表达式是:

其中输入为x1 x2 ,权重为w1 w2 ,偏置为 b,在神经网络中,引入h(x),将上式改写为:

其中的h(x),我们称为激活函数,将输入信号的总和转换为输出信号(当前层网络的输出)的总和,其作用就在于决定如何激活输入信号总和,如 a 为输入信号的总和,h() 把 a 输出,记为 y。

感知机和神经网络的区别就是激活函数,激活函数是连接感知机和神经网络的桥梁。上述的激活函数是“阶跃函数”,将激活函数替换为其他函数,便构成了神经网络。

Sigmoid函数

sigmoid函数是神经网络中最常使用的一个激活函数,它的数学表达式如下:

其中e为纳皮尔常数(2.7182......),其函数图像如下:

相比阶跃函数,sigmoid函数更加平滑,其“平滑”的特点对于神经网络来说具有重要意义,感知机中的神经元只有0和1两个信号,而在神经网络中,神经元之间流动的实际上是实数值信号。sigmoid函数和阶跃函数也具有相同点,当输入信号小时,输出接近0(为0),当输入信号大时,输出接近1(为1),即输入重要信号时,输出较大值,输入不重要信号时,输出较小值,不管输入的信号大小,输出总是在0~1之间。

sigmoid实现(基于numpy)

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

ReLu函数

ReLu函数也是一个常用的激活函数,其特点是输出大于零时,直接输出该值,小于等于零时,输出0,其数学表达为:

函数图像如下:

ReLu实现(基于numpy)

def relu(x):
    return np.maximum(0, x)

为什么激活函数使用非线性函数

神经网络中,激活函数必须使用非线性函数,如果使用线性函数,那么加深网络层数将没有任何意义,这一点可以很容易通过数学进行证明。假设h(x) = cx,那么h(h(x)) = h(cx) = c2x。所以为了发挥叠加层数所带来的优势,激活函数必须用非线性函数。

神经网络的内积

神经网络在实现时,使用矩阵进行计算,所以实现时要注意矩阵元素的个数,防止矩阵计算无法进行(线性代数知识)

如下图,这里省略偏置,输入为x1=1,x2=2,x1的权重为1、3、5,x2的权重为2、4、6。输入和权重的矩阵形状分别是 1x2 和 2x3,可以正常计算,输出为 1x3 的矩阵。

上图用代码实现为:

X = np.array([2, 3])
W = np.array([[1, 3, 5], [2, 4, 6]])

Y = np.dot(X, W)
print(Y)

输出为:

[ 8 18 28]

例子:三层神经网络的实现

虽然名字叫“三层神经网络”,但并没有实际的功能,只是神经网络内积的一个例子。在神经网络中,一般输出层不使用Sigmoid和ReLu函数作为激活函数,这里我们先引入一个名为“恒等函数”的输出层激活函数,其实现如下:

# 恒等函数
def identity_function(x):
    return x

分析

首先要有三个权重矩阵W1、W2和W3,还需要两个偏置矩阵b1、b2和b3,分别对应上图第一层、第二层、第三层的权重和偏置。然后按照顺序,进行矩阵的运算。

具体实现

# 网络的一些参数(权重和偏置)
def init_network():
    network = {}
    network['W1'] = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])
    network['B1'] = np.array([0.1, 0.2, 0.5])
    
    network['W2'] = np.array([[0.1, 0.3], [0.2, 0.5], [0.4, 0.6]])
    network['B2'] = np.array([[0.2, 0.7]])
    
    network['W3'] = np.array([[0.3, 0.5], [0.4, 0.3]])
    network['B3'] = np.array([0.7, 0.9])
    
    return network

# 向前传播
def forward(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    B1, B2, B3 = network['B1'], network['B2'], network['B3']
    
    A1 = np.dot(x, W1) + B1
    Z1 = sigmoid(A1)
    
    A2 = np.dot(Z1, W2) + B2
    Z2 = sigmoid(A2)
    
    A3 = np.dot(Z2, W3) + B3
    Y = identity_function(A3)
    
    return Y

# 初始化网络
network = init_network()
# 模拟输入数据
X = np.array([0.1, 0.2])
forward(network, X)
关于 forward 函数

forward原意为“向前”,这里要表示的是神经网络的“前向传播”,即信号从输入层传到输出层,与之对应的是”反向传播“,反向传播在计算梯度进行参数更新时有重要作用。

输出层的设计

神经网络可以用来解决回归和分类问题,根据解决的问题不同,所使用的激活函数也不同,输出层常使用的激活函数如 恒等函数(回归问题)、softmax函数(分类问题)等。输出层的激活函数用σ()来表示。

恒等函数在上面的例子中我们已经见过了,就是不加处理,将数据直接输出。

softmax函数

softmax函数的数学公式如下:

其中,a为输入信号,假设输出层之前共n个神经元,σ(x)k为第k个神经元的输出,ak为第k个神经元的输入。

具体实现

sigmoid函数中存在指数函数,其图像如下,所以很容易导致溢出。

在实现softmax函数时,要对原式进行转换,防止溢出,通过数学推到,很容易可以得到:

在实际应用中,常数c通常取输入信号a的最大值。

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)
    exp_a_sum = np.sum(exp_a)
    y = exp_a / exp_a_sum
    
    return y

softmax函数的特性

softmax函数的输出值为0~1之间的实数,其输出总和为1(或者及其接近1),因此,通过使用softmax函数,我们可以通过概率的方式处理问题。即便是使用了softmax函数,各元素直接按的大小关系也是不变的(原来最大,现在还是最大),因为指数函数时单调递增函数,函数值的大小与a的大小成正比。一般而言,神经网络只把最大值对应的类目输出,作为识别的结果。因此,我们在有些时候可以省略输出层的函数,因为函数的计算需要额外的时间开销。

输出层的设计

机器学习的问题一般可以分为”学习“和”推理“两个过程,在”推理“中,一般会去掉softmax函数,但在”学习“中,softmax函数有十分重要的作用。输出层的神经元个数一般为要解决的问题的类别数量,比如手写数字识别,输出层有10个神经元,对应数字0~9。

数据的处理

正规化:把数据规范到某个范围

预处理:对网络的输入数据进行某种既定的转换

预处理在深度学习中非常有用,其有效性已经在提高识别性能和学习效率中的到了证明。很多预处理会考虑数据的整体分布,如利用数据的均值和标准差移动数据,使数据整体以0为中心分布,或者进行正规化,将数据控制在一定的范围内。将整体数据的分布形状均匀化称为数据白化。

批处理

一次性输入多个数据,这种打包式的数据,称为批。批处理可以提高运算的速度,大多数数值计算库(如numpy)对处理大型数据都进行了优化,并且当数据传输成为瓶颈时,批处理可以减轻数据总线的负荷(相对于读取数据,可以将更多的时间投入到计算中)。

声明:迟於|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 【深度学习笔记】神经网络


栖迟於一丘