神经网络的一个重要的性质就是它可以自动地从数据中学习到合适的权重参数。
感知机与神经网络存在很多的共同点。
下图为一种简单的全连接网络形式
其中,我们把最左边一列称为输入层,最右边一列称为输出层,中间的一列称为中间层(隐藏层)。
一般,我们将把输入信号的总和转换为输出信号的函数称为激活函数(activation function)。
如上图所示,b为偏置,w1、w2为权重,神经元的○中明确显示了激活函数的计算过程,即信号的加权总和为节点a,然后节点a被激活函数h()转换成节点y。
def step_function(x):
if x > 0:
return 1
else:
return 0
这个实现虽然简单,易于理解,但是参数x只能接受实数(浮点数),不允许参数取Numpy数组,可考虑下面的实现方法。
def step_function(x):
y = x > 0
return y.astype(np.int)
上面的代码会将输入的numpy数组的各个元素通过y = x > 0
转换为True或者False,此时y是一个布尔型数组,然后利用astype()方法转换为Numpy数组的类型。astype()方法通过参数指定期望的类型,这个例子中是np.int型,将布尔型转换为int型后,True会转换为1,False转换为0.
下面来看一看节约函数的图形
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype=np.int)
x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴的范围
plt.show()
其中np.arange(-5.0, 5.0, 0.1)
在-5.0到5.0的范围内,以0.1为单位,生成NumPy数组。step_function()以该NumPy数组为参数,对数组的各个元素执行阶跃函数的运算,并以数组形式返回运算结果。对数组x、y进行绘图。
def sigmoid(x):
return 1 / (1 + np.exp(-x))
之所以sigmoid寒素的实现能够支持NumPy数组,在于NumPy的广播功能,如果标量和NumPy数组之间进行运算,则标量会和NumPy数组的各个元素进行运算。
下面把sigmoid函数画出来
import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.arange([1.0, 2.0, 3.0])
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
sigmoid和阶跃函数的对比:
ReLU函数
在看relu之前,我们先明确线性函数及非线性函数这个概念。即上述学到的阶跃函数和sigmoid函数均为非线性函数。函数本来是输入某个值后会返回一个值的转换器。向这个转换器输入某个值后,输出值是输入值的常数倍的函数称为线性函数。因此线性函数表现为一条笔直的直线。神经网络的激活函数必须要使用非线性函数。原因在于:如果使用线性函数,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”,无法发会多层网络带优势。为了具体地(稍微直观地)理解这一点,我们来思 考下面这个简单的例子。这里我们考虑把线性函数h(x) = cx作为激活 函数,把y(x) = h(h(h(x)))的运算对应3层神经网络A。这个运算会进行 y(x) = c×c×c×x的乘法运算,但是同样的处理可以由y(x) = ax(注意, a = c 3)这一次乘法运算(即没有隐藏层的神经网络)来表示。简而言之如果使用线性函数,加深神经网络变得毫无意义。
ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0。
def relu(x):
return np.maximum(0, x)
这里使用了numpy的maximum函数。maximum函数会从输入的数值中选择较大的那个值进行输出。
3层神经网络:输入层(第0层)有2个神经元,第1个隐藏层(第1层)有3个神经元,第2个隐藏层有2个神经元,输出层(第3层)有2个神经元。
图中增加了表示偏置的神经元“1”.注意,偏置的右下角的索引号只有一个。这是因为前一层的偏置神经元(神经元“1”)只有一个。通过加权信号和偏 置的和按如下方式进行计算。
如果使用矩阵的乘法运算,可以将第一层的加权和表示为下面的式子。
下面我们用NumPy多维数组来实现式(3.9),这里将输入信号、权重、 偏置设置成任意值。
X = np,array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1
W1是2×3的数组,X是元素个 数为2的一维数组。这里,W1和X的对应维度的元素个数也保持了一致。
接下来观察第1层中激活函数的计算过程。
隐藏层的加权和(加权信号和偏置的总和)用a表示,被 激活函数转换后的信号用z表示。此外,图中h()表示激活函数,这里我们使用的是sigmoid函数。用Python来实现,代码如下所示。
Z1 = sigmoid(A1)
print(A1) # [0.3, 0.7, 1.1]
print(Z1) # [0.57444252, 0.66818777, 0.75026011]
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
最后是第2层到输出层的信号传递。输出层所使用的激活函数与前两层有所不同。这里使用了恒等函数。
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) # 或者Y = A3
这里我们定义了identity_function()函数(也称为“恒等函数”),并将 其作为输出层的激活函数。恒等函数会将输入按原样输出,因此,这个例子 中没有必要特意定义identity_function()。这里这样实现只是为了和之前的流程保持统一。
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(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([1.0, 0.5])
y = forward(network, x)
print(y) # [ 0.31682708 0.69627909]
这里定义了init_network()和forward()函数.init_network()函数会进 行权重和偏置的初始化,并将它们保存在字典变量network中。这个字典变量network中保存了每一层所需的参数(权重和偏置)。forward()函数中则封装了将输入信号转换为输出信号的处理过程。
另外,这里出现了forward(前向)一词,它表示的是从输入到输出方向 的传递处理。后面在进行神经网络的训练时,我们将介绍后向(backward,从输出到输入方向)的处理。