深度学习入门——神经网络

小鸡
阅读711 喜欢5 算法 更新2019-5-20

之前介绍的感知机,不足的是——权重的设定工作需要人工进行。
而神经网络,的出现就是为了解决刚才的问题。具体地讲,神经网络的一 个重要性质是它可以自动地从数据中学习到合适的权重参数。

用图来表示神经网络,最左边的被称为输入层,中间被称为中间层(隐藏层),最右边被称为输出层


图3.1

图3-1中的网络一共由3层神经元构成,但实质上只有2层神经 元有权重,因此将其称为“2层网络”。有的书也会根据 构成网络的层数,把图3-1的网络称为3层网络

回到感知机的网络结构


这个感知机中接收输入信号x1,x2,输出信号为y


b是被称为偏置的参数,用于控制神经元被激活的容易程度;而w1和w2 是表示各个信号的权重的参数,用于控制各个信号的重要性

==引入新函数 h(x)==



图3-3中添加了权重为b的输入信号1。这个感知机将x1、x2、1三个信号作为神经元的输入,将其和各自的权重相乘后, 传送至下一个神经元。在下一个神经元中,计算这些加权信号的总和。==如果这个总和超过0,则输出1,否则输出0==。另外,由于偏置的输入信号一直是1, 所以为了区别于其他神经元,我们在图中把这个神经元整个涂成灰色。


激活函数

上面的h(x)会将输入信号的总和转换为输出信号,这种函数一般称为激活函数。
激活函数的作用在于决定如何来激活输入信号的总和

现在继续改写式3.2。将其分为两个阶段


先计算输入信号和偏置的总和,然后再用h(x)函数将a转换成输出y


图3.4明确激活函数的计算过程
大圈圈表示激活函数的计算过程

神经元用一个○表示。本书中,在可以明确 神经网络的动作的情况下,将在图中明确显示激活函数的计算过程,如图3-5 的右图所示。


激活函数是连接感知机和神经网络的 桥梁

激活函数

式(3.3)表示的激活函数以阈值为界,一旦输入超过阈值,就切换输出。 这样的函数称为“阶跃函数”。
因此,可以说感知机中使用了阶跃函数作为 激活函数。也就是说,在激活函数的众多候选函数中,感知机使用了阶跃函数。

如果感知机使用其他函数作为激活函数的话,那么就开启了神经网络

sigmoid函数

神经网络中经常使用的一个激活函数就是sigmoid函数


神经网络中将sigmoid函数作为激活函数,进行信号转换,转换后的信号被传输给下一个神经元。实际上,感知机和神经网络的区别就在于这个激活函数。其他方面,比如神经元的多层 连接的构造、信号的传递方法等,基本上和感知机是一样的。

迁跃函数的实现

用python写出简单的迁跃函数

def step_function(x):
if x > 0:
return 1
else:
return 0

这个简单的函数参数x只能接受实数,但是不允许数组,现在我们把它改成支持Numpy数组的实现。

def step_function2(x):
y = x > 1
return y.astype(np.int)

x = np.array([-1, 1, 2])
print(step_function2(x))

输出
[0 0 1]

上面的函数中,y = x > 1 将数组x转化为是否大于1的bool数组并赋值给y。
而 y.astype(np.int) 这个语句 将bool类型的数转化为0或1

画图表示迁跃函数

import numpy as np
import matplotlib.pylab as plt

def step_function2(x):
y = x > 1
return y.astype(np.int)

x = np.arange(-5, 5, 0.1)
y = step_function2(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) #指定y轴的范围
plt.show()


sigmoid函数的实现

依然使用python就可以简单的实现

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

这里的参数x可以是实数,也可以是Numpy数组


之所以sigmoid函数的实现能支持NumPy数组,就在于NumPy的 广播功能(见Nump库的使用)根据 NumPy 的广播功能,如果在标量和NumPy数组 之间进行运算,则标量会和NumPy数组的各个元素进行运算。

一个具体的例子


标量(例子中是1.0)和 NumPy数组之间进行了数值运 算(+、/等)。结果,标量和NumPy数组的各个元素进行了运算,运算结 果以NumPy数组的形式被输出。刚才的sigmoid函数的实现也是如此,因为np.exp(-x)会生成NumPy数组,所以1 / (1 + np.exp(-x))的运算将会在 NumPy数组的各个元素间进行

def show_sigmoid():
x = np.arange(-5, 5, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) #指定y轴范围
plt.show()

show_sigmoid()


sigmoid函数与迁跃函数的比较

==两者的不同点:==
平滑性不同,sigmoid函数比较平滑,而迁跃函数以0为界,输出发生跳跃。sigmoid函数的平滑性对神经网络的学习具有重要意义
迁跃函数的返回值只有0和1,而sigmoid函数可以返回0和1之间的任意实数。也可以这么说,感知机中神经元之间流动的是0或1的二元信号,而神经网络中流动的是连续的实数信号

==两者的相同点:==
从宏观上看,它们具有相似的形状。实际上,两者的结构均是“输入小时,输出接近0(为 0); 随着输入增大,输出向1靠近(变成1)”。也就是说,当输入信号为重要信息时, 阶跃函数和sigmoid函数都会输出较大的值;当输入信号为不重要的信息时, 两者都输出较小的值。

还有一个共同点是,不管输入信号有多小,或者有多 大,输出信号的值都在0到1之间。

两者均是非线性函数

神经网络中的激活函数必须使用非线性函数。也就是说,激活函数不能使用线性函数。

为什么呢?

因为,==使用线性函数的话,加深神经网络的层数就没有意义了==(此处联想感知机实现异或逻辑的过程)线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无 隐藏层的神经网络”

简单的例子

把线性函数h(x) = cx作为激活 函数,把y(x) = h(h(h(x)))的运算对应3层神经网络A。这个运算会进行 y(x) = c×c×c×x的乘法运算,但是同样的处理可以由y(x) = ax(注意, a = c 3)这一次乘法运算(即没有隐藏层的神经网络)来表示。

使用线性函数时,无法发挥多层网络带来的优势。因此,为了发挥叠加层所带来的优势,激活函数必须使用非线性函数

ReLU函数

sigmoid函数很早就开始使用了,而最近则使用ReLU函数(Rectified Linear Unit)

RELU函数在输入大于0时直接输出该值,在输入小于等于0时,输出0,如下所示


简单python实现

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


3层神经网络的实现

现在以3层神经网络作为对象,实现从输入到输出的(向前)处理。在代码上使用Numpy的多维数组


符号确认

重点是神经网络的运算可以作为矩阵运算打包进行。因为 神经网络各层的运算是通过矩阵的乘法运算打包进行的(从宏观 视角来考虑)

给符号定义



各层之间的信号传递实现

下面是从输入层到第一层的第一个神经元的传递过程,图中增加了表示偏置的神经元”1“。注意前置的右下角索引号只有一个,这是因为偏置神经元只有一个(神经元”1“只有一个)。




如果使用矩阵乘法运算,则可以将第一层的加权和表示成下面的式子



下面使用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])
A1 = np.dot(X, W1) + B1
print(A1)
print(sigmoid(A1))

这个运算和上一节进行的运算是一样的。W1是2×3的数组,X是元素个 数为2的一维数组。这里,W1和X的对应维度的元素个数也保持了一致。


从输入层到第一层的信号传递如上。
接下来我们实现第1层到第2层的信号传递

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)


最后第二层到输出层的传递。原理和之前的基本相同,不过最后的激活函数和之前的不同。

这里定义输出层的激活函数为“恒等函数”identity_function()函数,将输入作为输出原样输出。

输出层所用的激活函数,要根据求解问题的性质决定。一般地,回 归问题可以使用恒等函数,二元分类问题可以使用sigmoid函数, 多元分类问题可以使用softmax函数。


代码整理

已完成三层神经网络的介绍,现在整理一下代码

import numpy as np
import matplotlib.pylab as plt

class network:
def __init__(self):
self.W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
self.b1 = np.array([0.1, 0.2, 0.3])
self.W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
self.b2 = np.array([0.1, 0.2])
self.W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
self.b3 = np.array([0.1, 0.2])

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

def indentity_fun(self, x):
return x

def for_ward(self, x):
w1, w2, w3 = self.W1, self.W2, self.W3
b1, b2, b3 = self.b1, self.b2, self.b3
a1= np.dot(x, w1) + b1
z1 = self.sigmoid(a1)
a2 = np.dot(z1, w2) + b2
z2 = self.sigmoid(a2)
a3 = np.dot(z2, w3) + b3
y = self.indentity_fun(a3)
return y

net = network()
x = np.array([1.0, 0.5])
print(net.for_ward(x))

这里定义了一个network对象,初始函数init进行权重和偏置的初始化。forword函数封装了将输入信号转化为输出信号的过程

输出层的设计

神经网络可以用分类问题和回归问题上,不过需要根据实际情况改变输出层的激活函数。一般,分类函数用softmax函数,机器学习的问题大致可以分为分类和回归问题

softmax函数

分类问题中使用的softmax函数的公式如下


用神经网络图表示如下


这个公式在代码中容易产生溢出问题,导致计算失败,下面对softmax函数进行改进


这里的C’可以是任意值,并且它并不会改变计算结果,所以可以通过这个C值来降低幂级数,从而避免溢出。一般情况下设置为输入信号量的最大值

python代码实现如下

def soft(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

使用softmax函数计算神经网络输出值


softmax函数的输出值总是在0和1之间的实数,并且softmax函数的输出值总和为1,这是他的一个重要性质因此,我们又把softmax函数的输出解释为概率

上面的例子可以解释成y[0]的概率是0.018(1.8 %),y[1]的概率 是0.245(24.5 %),y[2]的概率是0.737(73.7 %)。从概率的结果来看,可以 说“因为第2个元素的概率最高,所以答案是第2个类别”。而且,还可以回 68 答“有74 %的概率是第2个类别,有25 %的概率是第1个类别,有1 %的概 率是第0个类别”。也就是说,通过使用softmax函数,我们可以用概率的(统计的)方法处理问题
不过由于指数函数的运算需要一定的计算量,因此输出层的softmax函数在实际情况中一般会被省略

输出层的神经元数量

输出层的神经元数量根据实际情况而定,对于分类问题,输出层的神经元数量一般是类别的数量。比如,对于某个输入图像,预测是图中的数字0到9中的哪一个问题,那么可以将输出层的神经元数量设为10个,分为对应0~9



:本文为斋藤康毅的《深度学习入门:基于Python的理论与实现》片段摘抄与学习笔记