1.三层神经网络的实现:
这篇使用Numpy数组来实现3层神经网络从输入到输出的前向处理过程。
参考之前提到的感知机的输入输出过程,神经网络的前向处理过程可以图示为:

可以看到神经网络其实就是通过叠加感知机得到的。
上面的图表示的是一个3层神经网络,其中:
- 输入层(layer 0)有2个神经元;
- 第一个隐藏层(layer 1)有3个神经元;
- 第二个隐藏层(layer 2)有2个神经元;
- 输出层(layer 3)有2个神经元。
两个隐藏层中的h(x)
就是之前提到的激活函数,可以使用sigmoid函数或者relu函数。
而输出层的σ(x)
表示的是输出层的激活函数,它与其他层的激活函数不同。
2.输出层的设计:
输出层使用的激活函数,根据求解问题的性质决定。回归问题可以使用恒等函数,分类问题可以使用softmax函数。
2.1 恒等函数
恒等函数就是将输入按原样输出。
# 定义恒等函数
def identity_function(x):
return x
2.2 softmax函数
假设输出层共有n个神经元,计算第k个神经元的输出yk。

也就是说,softmax函数的分子是输入信号ak的指数函数,分母是所有输入信号的指数函数的和。
因此,softmax函数的输出为[0.0,1.0]之间的实数,并且输出值的总和是1。正是由于softmax函数输出总和是1这个重要的性质,我们才可以把softmax函数的输出解释为“概率”。
因为指数函数(y=exp(x))函数是单调递增函数,因此对输出值使用softmax函数也不会改变值的相对大小。
用python实现就是:
# 定义softmax函数
def softmax(x):
exp_x = np.exp(x)
sum_exp_x = np.sum(exp_x)
y = exp_x / sum_exp_x
return y
这里需要注意的是,上述代码虽然实现了softmax函数的运算,但是还存在一定的缺陷问题,也就是溢出问题。
softmax函数在实现中要进行指数函数的运算,但是此时指数函数的值很容易变的很大,例如exp(1000)会返回一个表示无穷大的inf。因此在这些超大值之间进行运算,结果会出现不确定的情况。
>>> import numpy as np
>>> a = np.array([1010,1000,990])
>>> np.exp(a) / np.sum(np.exp(a))
__main__:1: RuntimeWarning: overflow encountered in exp
__main__:1: RuntimeWarning: invalid value encountered in true_divide
array([nan, nan, nan])
针对溢出问题,会将softmax函数进行如下改进:

在进行softmax函数运算的时候,加上(或者减去)某个常数并不会改变运算的结果,这里的C'
可以使用任何值,但是为了防止溢出一般使用输入信号中的最大值。
>>> import numpy as np
>>> a = np.array([1010,1000,990])
>>> np.exp(a) / np.sum(np.exp(a))
__main__:1: RuntimeWarning: overflow encountered in exp
__main__:1: RuntimeWarning: invalid value encountered in true_divide
array([nan, nan, nan])
>>> c = np.max(a)
>>> np.exp(a-c) / np.sum(np.exp(a-c))
array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
因此用python实现可以改进为:
# 定义softmax函数
def softmax(x):
c = np.max(x)
exp_x = np.exp(x-c) # 防止溢出策略
sum_exp_x = np.sum(exp_x)
y = exp_x / sum_exp_x
return y
3.3层神经网络的代码实现:
import numpy as np
# 定义sigmoid函数
def sigmoid_function(x):
return 1 / (1 + np.exp(-x))
# 定义relu函数
def relu_function(x):
return np.maximum(0, x)
# 定义恒等函数
def identity_function(x):
return x
# 定义softmax函数
def softmax(x):
c = np.max(x)
exp_x = np.exp(x-c) # 防止溢出策略
sum_exp_x = np.sum(exp_x)
y = exp_x / sum_exp_x
return y
# 定义参数初始化函数
def init_network():
network = {}
network['w1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
network['b1'] = np.array([0.1,0.2,0.3])
network['w2'] = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
network['b2'] = np.array([0.1,0.2])
network['w3'] = np.array([[0.1,0.3],[0.2,0.4]])
network['b3'] = np.array([0.1,0.2])
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_function(a1)
a2 = np.dot(z1,w2) + b2
z2 = sigmoid_function(a2)
a3 = np.dot(z2,w3) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([0.1,0.5])
y = forward(network,x)
print(y)
运算结果为
[0.31234736 0.6863161 ]
可以将隐藏层中的激活函数替换为ReLU函数,也可以将输出层的激活函数替换为softmax函数。
Reference:
《Deep Learning from Scratch》