欢迎光临散文网 会员登陆 & 注册

鱼书(深度学习入门)第三章:神经网络

2023-07-19 22:58 作者:棒棒x  | 我要投稿

一、从感知机到神经网络

    神经网络的结构如下图所示。我们把最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层。中间层有时也称为隐藏层。“隐藏”一词的意思是,隐藏层的神经元(和输入层、输出层不同)肉眼看不见。

神经网络的例子

    在上一章感知器中,我们有以下的数学表达式。

    我们将其改写为以下形式:

    当我们考虑h(x)何时等于0或者1的情况时,我们便引入了激活函数这个概念。刚才登场的h(x)函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。如“激活”一词所示,激活函数的作用在于决定如何来激活输入信号的总和。当我们在神经元示意图中加入激活函数时,便如下图所示。

加入激活函数后的神经元

    下面我们介绍几个常用的激活函数。

    1.阶跃函数

    当一个激活函数以阈值为界,一旦输入超过阈值,就切换输出。 这样的函数称为“阶跃函数”。

    下面给出的代码可以绘制出阶跃函数的图形。

示例代码:

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()

图形如下:

阶跃函数的图形

    要在代码中实现阶跃函数,使用以下代码即可。

def step_function(x): 

    y = x > 0 

    return y.astype(np.int_)

    需要注意的是,对NumPy数组进行不等号运算后,数组的各个元素都会进行不等号运算,生成一个布尔型数组。这里,数组x中大于0的元素被转换为True,小于等于0的元素被转换为False,从而生成一个新的数组y。可以用astype()方法转换NumPy数组的类型。astype()方法通过参数指定期望的类型,这个例子中是np.int型。Python中将布尔型转换为int型后,True会转换为1,False会转换为0。

    2.sigmoid函数

    神经网络中经常使用的一个激活函数就是下式表示的sigmoid函数(sigmoid function)。实际上,上一章介绍的感知机和接下来要介绍的神经网络的主要区别就在于这个激活函数。其他方面,比如神经元的多层连接的构造、信号的传递方法等,基本上和感知机是一样的。

sigmoid函数

    下面给出绘制sigmoid函数的代码。

示例代码:

import numpy as np

import matplotlib.pylab as plt

def sigmoid(x): #展示sigmoid函数

    return 1/(1+np.exp(-x)) #此处numpy有对应的广播机制

x=np.arange(-5.0,5.0,0.1)

y=sigmoid(x)

plt.plot(x,y)

plt.ylim(-0.1,1.1) #指定y轴的范围

plt.show()

其图形如下:

sigmoid函数的图形

    使用以下函数即可在代码中实现sigmoid函数:

def sigmoid(x): #sigmoid函数

    return 1/(1+np.exp(-x)) #此处numpy有对应的广播机制

3.阶跃函数与sigmoid函数的异同

    首先注意到的是“平滑性”的不同。sigmoid函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以0为界,输出发生急剧性的变化。sigmoid函数的平滑性对神经网络的学习具有重要意义。

    另一个不同点是,相对于阶跃函数只能返回0或1,sigmoid函数可以返回连续的实数。也就是说,感知机中神经元之间流动的是0或1的二元信号,而神经网络中流动的是连续的实数值信号。

    接着说一下阶跃函数和sigmoid函数的共同性质。阶跃函数和sigmoid函数虽然在平滑性上有差异,但是如果从宏观视角看,可以发现它们具有相似的形状。实际上,两者的结构均是“输入小时,输出接近0(为0);随着输入增大,输出向1靠近(变成1)”。也就是说,当输入信号为重要信息时,阶跃函数和sigmoid函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值。还有一个共同点是,不管输入信号有多小,或者有多大,输出信号的值都在0到1之间。

    阶跃函数和sigmoid函数还有其他共同点,就是两者均为非线性函数。 sigmoid函数是一条曲线,阶跃函数是一条像阶梯一样的折线,两者都属于 非线性的函数。

    一个重要的性质是:神经网络的激活函数必须使用非线性函数。因为使用线性函数的话,加深神经网络的层数就没有意义了。线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。

    4.ReLU函数

    在神经网络发展的历史上,sigmoid函数很早就开始被使用了,而最近则主要使用ReLU(Rectified Linear Unit)函数。ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0。其可以表达为以下式子与图形。

ReLU函数

    

ReLU函数图形

    其函数实现如下:

def relu(x): #展示EeLU函数

    return np.maximum(0,x)

    这里使用了NumPy的maximum函数。maximum函数会从输入的数值中选择较大的那个值进行输出。

二、NumPy多维数组的运算

    简单地讲,多维数组就是“数字的集合”,数字排成一列的集合、排成长方形的集合、排成三维状或者(更加一般化的)N维状的集合都称为多维数组。

示例代码

    上面的代码生成了一个3 × 2的数组B。3 × 2的数组表示第一个维度有3个元素, 第二个维度有2个元素。由示例可知,数组的维数可以通过np.dim()函数获得,数组的形状可以通过实例变量shape获得。二维数组也称为矩阵(matrix)。数组的横向排列称为行(row),纵向排列称为列(column)。

    矩阵的乘法这里就不赘述了,但是与线性代数中的运算法则相同,在矩阵的乘积运算中,对应维度的元素个数要保持一致,如下图所示:

    下面介绍使用Numpy快速的计算矩阵的内积。

示例代码:

X=np.array([1,2]) #numpy矩阵点乘

print(X.shape)

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

print(W)

print(W.shape)

Y=np.dot(X,W)

print(Y)

    如上所示,使用np.dot(多维数组的点积),可以一次性计算出Y 的结果。如果不使用np.dot,就必须单独计算Y 的每一个元素(或者说必须使用for语句),非常麻烦。

三、三层神经网络的实现

    1.符号确认

    在介绍神经网络的实现之前,我们先引入几个符号。下面是权重符号表示的图示:

权重的符号

    2.各层之间信号的传递

    下图展示了从输入层到第1层的信号传递。

从输入层到第1层的信号传递

    使用矩阵表示,激活函数使用sigmoid函数的代码如下:

def sigmoid(x): #sigmoid函数

    return 1/(1+np.exp(-x)) #此处numpy有对应的广播机制

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

Z1=sigmoid(A1)

print(A1) #[0.3 0.7 1.1]

print(Z1) #[0.57444252 0.66818777 0.75026011]

    只要将第一层的输出结果作为第二层的输入,即可很方便的实现第1层到第2层的信号传递,如下图所示。

第1层到第2层的信号传递

代码如下:

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)

print(W2.shape)

print(B2.shape)

A2=np.dot(Z1,W2)+B2

Z2=sigmoid(A2)

print(A2) #[0.51615984 1.21402696]

print(Z2) #[0.62624937 0.7710107 ]

    最后是第2层到输出层的信号传递。不过,最后的激活函数和之前的隐藏层有所不同。我们定义了identity_function()函数(也称为“恒等函数”),并将其作为输出层的激活函数。恒等函数会将输入按原样输出。另外,图中输出层的激活函数用σ()表示,不同于隐藏层的激活函数h()。如下图所示:

从第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)

print(A3) #[0.31682708 0.69627909]

print(Y)  #[0.31682708 0.69627909]

    3.代码实现小结

    我们已经介绍完了3层神经网络的实现。现在我们把之前的代码实现全部整理一下。按照惯例,我们权重记为大写字母W1,其他的(偏置或中间结果等)都用小写字母表示。其代码如下:

def sigmoid(x): #sigmoid函数

    return 1/(1+np.exp(-x))

def identity_function(x): #输出层的激活函数为恒等函数

    return x

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)

    这里定义了init_network()和forward()函数。init_network()函数会进行权重和偏置的初始化,并将它们保存在字典变量network中。这个字典变量network中保存了每一层所需的参数(权重和偏置)。forward()函数中则封装了将输入信号转换为输出信号的处理过程。

四、输出层的设计

    神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出层的激活函数。回归问题可以使用恒等函数,二元分类问题可以使用 sigmoid函数,多元分类问题可以使用 softmax函数。一般而言,回归问题用恒等函数,分类问题用softmax函数。

    1.恒等函数与softmax函数

    恒等函数很好理解,如下图所示:

恒等函数

    而softmax函数的分子是输入信号ak的指数函数,分母是所有输入信号的指数函数的和。可以用下式表示:


softmax函数

由于其利用到了上层的所有输入,其信号传递图如下所示:

softmax函数的信息传递

    softmax函数的代码实现如下:

def softmax(a):

    exp_a=np.exp(a) 

    sum_exp_a=np.sum(exp_a)

    y=exp_a/sum_exp_a

    return y

    2.实现 softmax函数时的注意事项

     softmax函数的实现在计算机的运算上有一定的缺陷。这个缺陷就是溢出问题。softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。我们可以使用下式进行改进:

softmax函数改进式

        在进行softmax的指数函数的运算时,加上(或者减去)某个常数并不会改变运算的结果。这里的C’可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。其代码实现如下所示:

def softmax(a):

    c=np.max(a)

    exp_a=np.exp(a-c) #溢出对策

    sum_exp_a=np.sum(exp_a)

    y=exp_a/sum_exp_a

    return y

    3.softmax函数的特征

    softmax函数的输出是0.0到1.0之间的实数。并且,softmax函数的输出值的总和是1。输出总和为1是softmax函数的一个重要性质。正因为有了这个性质,我们才可以把softmax函数的输出解释为“概率”。这里需要注意的是,即便使用了softmax函数,各个元素之间的大小关系也不会改变。这是因为指数函数(y = exp(x))是单调递增函数。一般而言,神经网络只把输出值最大的神经元所对应的类别作为识别结果。并且,即便使用softmax函数,输出值最大的神经元的位置也不会变。

    4.输出层的神经元数量

    输出层的神经元数量需要根据待解决的问题来决定。对于分类问题,输出层的神经元数量一般设定为类别的数量。对于手写数字识别问题,其输出可表示为下图:

输出层的神经元对应各个数字


鱼书(深度学习入门)第三章:神经网络的评论 (共 条)

分享到微博请遵守国家法律