神经网络《深度学习入门-基于Python的理论与实现》 第三章


layout: post
title: 深度学习入门 基于Python的理论实现
subtitle: 第三章 神经网络
tags: [Machine learning, Reading]


第三章 神经网络

紧接着第二章的内容,我们开始介绍神经网络。上一章感知机的参数都是我们认为验证给定的,对于神经网络来说,一个重要的性质就是<font color=red>可以自动地从数据中学习到合适的权重参数。</font>

3.1 从感知机到神经网络

3.1.1 神经网络的例子

下图就是一个神经网络的例子,把最左边的一列称为<font color=red>输入层</font>,中间一列称为<font color=red>中间层</font>,中间层有时又称为<font color=red>隐藏层</font>,最右边一列称为<font color=red>输出层</font>。

NLP.png

按之前的习惯,我们把输入层称为第0层,隐藏层称为第1层,输出层称为第二层,因此在这里我们称之为两层神经网络。注意,也有些地方将其称为三层神经网络。但怎么叫都无伤大雅。上图的结构和之前感知机的结构很相似。那么他们有什么异同呢。

3.1.2 复习感知机

先回顾一下感知机的结构。

perceptron.png

感知机接收x_1,x_2两个输入信号,输出y,数学表达如下:

y=\left\{\begin{array}{ll} 0 & \left(b+w_{1} x_{1}+w_{2} x_{2} \leqslant 0\right) \\ 1 & \left(b+w_{1} x_{1}+w_{2} x_{2}>0\right) \end{array}\right.

b称为偏置,控制神经元被激活的难易程度,而\omega_1\omega_2称为权重,表示信号的重要程度。我们发现,在上图中并没有表现出偏置,我们可以做一点小小的修改,改动后的结构如下图所示:

perceptron_bias.png

在上面的表示方法中,分了两种情况来表示,表示起来并不是很方便,因此我们将原来的公式改写成下面的式子:

y = h(b+\omega_1x_1+\omega_2x_2)

h=\left\{\begin{array}{ll} 0 & \left(x \leqslant 0 \right) \\ 1 & \left(x > 0\right) \end{array}\right.

3.1.3 激活函数

上面的h(x)函数将输入信号的总和转换为输出信号,这种函数一般称为<font color=red>激活函数</font>,顾名思义,激活函数用来决定如何激活信号。
我们将上面的公式写的更详细一点:

a=b+\omega_1x_1+\omega_2x_2

y = h(a)

首先计算加权输入信号和偏执的总和,记为a,然后激活函数h(),将a转换为输出y。为了明确表示这个过程,我们得到了下面的图:

perceptron_details.png

3.2 激活函数

下来我们详细介绍激活函数,激活函数以阈值为界,一旦超过阈值,就切换输出,这样的函数被称为阶跃函数。感知机使用了阶跃函数,那么选择其他函数会怎么样呢。

3.2.1 sigmoid函数

这是非常常见的激活函数。

h(x)=\frac{1}{1+e^{-x}}

神经网络使用sigmoid作为激活函数。<font color=red>神经网络和感知机的主要区别就在于激活函数</font>。

3.2.2 阶跃函数的实现

即:

h=\left\{\begin{array}{ll} 0 & \left(x \leqslant 0 \right) \\ 1 & \left(x > 0\right) \end{array}\right.


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

这是一个最基本的实现,但是输入不能以数组的形式呈现,下面我们修改一下使之可以使用Numpy数组实现。

def step_function(x):
    y = x > 0
    
    return y.astype(np.int)

这里函数中的第二个行对输入的numpy数组判断是否大于零,大于零的返回True,反之则为False。astype将布尔值转换为整形,True变为1,False变为0。

numpy_code.png

3.2.3 阶跃函数的图形

我们用matplotlib来画图。

import numpy as np
import matplotlib.pyplot 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)
plt.show()

执行结果如下:


step_function.png

3.2.4 sigmoid函数的实现


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

绘图:

import numpy as np
import matplotlib.pyplot as plt

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

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

plt.plot(x,y)
plt.ylim(-0.1,1.1)
plt.show()

执行结果如下:


sigmoid_function.png

将他们放在一起做一个对比:

import numpy as np
import matplotlib.pyplot as plt

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

x = np.arange(-5.0,5.0,0.1)
y = sigmoid_function(x)
z = step_function(x)
plt.plot(x,y)
plt.plot(x,z,'--')
plt.ylim(-0.1,1.1)
plt.show()
sigmoid_and_step_function.png

通过上图观察,他们之间究竟有什么不同。我讲这些不同归纳为以下两点:

  • sigmoid函数更加平滑,阶跃函数以0为界发生阶跃。
  • 阶跃函数只能返回0和1,sigmoid函数返回连续的值

3.2.6 非线性函数

刚刚说过的阶跃函数和sigmoid函数都是非线性函数,因为他们不是一条直线。对于神经网络来说,<font color=red>激活函数必须使用非线性函数。否则神经网络具有的层的概念就没有意义了 。</font>

在这里举一个简单的例子。假设我们在这里用线性函数h(x) = cx为激活函数。于是y(x)=h(h(h(x)))作为三层神经网络的运算,于是得到y(x)=c^3x,我们让c^3=a,于是有y(x)=ax,这个三层网络可以用一层网络代替,那么这个三层网络就没有意义了且无论多少层都可用一层代替。

3.2.7 ReLU函数

sigmoid函数很早就开始使用了,现在使用较多的是ReLU函数(Rectified Linear Unit)。ReLU函数也很简单,输入大于0时,直接输出该值,否则输出0。可表示为:

h= \left\{\begin{array}{ll} 0 & \left(x \leqslant 0 \right) \\ x & \left(x > 0\right) \end{array}\right.

代码实现如下:

def relu(x):
    if x>0:
        return x
    else:
        return 0
def relu(x):
    return np.maxium(0,x)

ReLU函数的图像如下。

import numpy as np
import matplotlib.pyplot as plt

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

x = np.arange(-5.0,5.0,0.1)
y = relu(x)
plt.plot(x,y)
plt.ylim(-0.5,4.0)
plt.show()
relu.png

3.3 多位数组的运算

这一部分主要是矩阵乘法的部分,包括多维数组和矩阵乘法等知识。就不再赘述了。

3.3.3 神经网络的内积

现在我们使用Numpy来实现神经网络。下面的神经网络省略了偏置和激活函数,只有权重。

NLP_lite.png

结合上面的图,我们得到几个式子。

h=\left\{\begin{array}{ll} 1 \times x_1+2 \times x_2 = y_1 \\ 3 \times x_1+4 \times x_2 = y_2 \\ 5 \times x_1+6 \times x_2 = y_3 \end{array}\right.

使得X = \begin{pmatrix} x_1\\ x_2 \end{pmatrix},使得Y = \begin{pmatrix} y_1\\ y_2\\ y_3 \end{pmatrix} 于是有:

\begin{pmatrix} 1&2 \\ 3&4\\ 5&6 \end{pmatrix} \begin{pmatrix} x_1\\ x_2 \end{pmatrix} = \begin{pmatrix} y_1\\ y_2\\ y_3 \end{pmatrix}

在实现的时候要特别注意矩阵的维度。下面进行实现:

import numpy as np

def NLP_simple(x1,x2):
    X = np.array([[x1],[x2]])
    W = np.array([[1,2],[3,4],[5,6]])
    Y = np.dot(W,X)
    return Y


Y = NLP_simple(1,2)
NLP_simple_code.png

在这里是一个简单的例子,但是应该注意到的是,当神经元个数增大时,相应的只是矩阵的大小发生了变化,也是使用矩阵的乘法完成计算。

3.4 3层神经网络的实现

我们再进一步计算更复杂的神经网络。先看下面3层神经网络的结构:

NLP_3_layers.png

这个神经网络包含一个两神经元的输入层,两层隐藏层,神经元个数分别为3和2,还包含一个包含两个神经元的输出层。

3.4.1 符号确认

为了让之后的内容更加明晰,我们首先对后面出现的一些表示方法做一下说明。

symbol_description.png

如上图所示,a_{1}^{(1)}中,上面括号里的1代表这个是第一层的权重,下标的1代表这是第一个神经元。接下来,\omega_{12}^{(1)}的上标依旧代表第一层的权重,下标的12中,2代表前一层的第二个神经元,1代表后一层的第一个神经元,总结一句,就是代表前一层的第二个神经元到后一层的第一个神经元的权重。权重右下角按照<font color=red>后一层的索引号、前一层的索引号</font>的顺序排列。

3.4.2 各层间信号传递的实现

我们首先看输入层到第一层的第一个神经元的信号传递过程。如下图所示:

NLP_implement.png

于是有

a_{1}^{(1)} = x_1\omega_{11}^{(1)}+x_2\omega_{12}^{(1)} + b_{1}^{(1)}

按照之前的方法,我们将a_{2}^{(1)},a_{3}^{(1)}一起写出来,得到如下式子:

\left\{\begin{array}{ll} a_{1}^{(1)} = x_1\omega_{11}^{(1)}+x_2\omega_{12}^{(1)} + b_{1}^{(1)} \\ a_{2}^{(1)} = x_1\omega_{21}^{(1)}+x_2\omega_{22}^{(1)} + b_{2}^{(1)} \\ a_{3}^{(1)} = x_1\omega_{31}^{(1)}+x_2\omega_{32}^{(1)} + b_{3}^{(1)} \end{array}\right.

转换为矩阵乘法的形式,得到:

A^{(1)} = XW^{(1)}+B^{(1)}

其中:

A^{(1)} = \begin{pmatrix} a_{1}^{(1)}&a_{2}^{(1)}&a_{3}^{(1)} \end{pmatrix}

X = \begin{pmatrix} x_1&x_2 \end{pmatrix}

B^{(1)} = \begin{pmatrix} b_{1}^{(1)} & b_{2}^{(1)} & b_{3}^{(1)} \end{pmatrix}

W^{(1)} = \begin{pmatrix} \omega_{11}^{(1)}&\omega_{21}^{(1)}&\omega_{31}^{(1)}\\ \omega_{12}^{(1)}&\omega_{22}^{(1)}&\omega_{32}^{(1)} \end{pmatrix}

在实现时,我们首先将输入X和偏置B^{(1)}设置为某任意值。

import numpy as np

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)

对于得到的加权和我们还要代入激活函数。

def sigmoid_function(x):
    return 1/(1+np.exp(-x))
    
Z1 = sigmoid_function(A1)
layer1_code.png

具体的计算细节如下图所示:

layer1_process.png

对照着第一层的计算过程,我们写出第二层的公式和代码。

layer2_process.png
import numpy as np

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) + B
Z2 = sigmoid_function(A2)
print(A1)
layer2_code.png

第二层和第一层是完全一样的,只不过第二层的输入变成了上一层的输出。

最后是第二层到输出层的代码和示意图,分析方法和表示方法同上。只是激活函数略有不同。代码如下:

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(Y)
layer3_code.png

在这里定义了identity_function作为激活函数,这里对输入不做任何操作直接输出,这样的函数叫做恒等函数。最后,输出层的激活函数区别于隐藏层用h(x)表示,输出层的激活函数用\sigma(x)表示。最后放出输出层的示意图。

layer3_process.png

输出层激活函数的选择将在下一节详细介绍。

3.4.3 代码实现小结

这一节将之前的代码做整理。

import numpy as np

def init_parameter():   #初始化参数
    network = {}
    network['W1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]]) 
    network['W2'] = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]]) 
    network['W3'] = np.array([[0.1,0.3],[0.2,0.4]])
    
    network['B1'] = np.array([0.1,0.2,0.3])
    network['B2'] = np.array([0.1,0.2])
    network['B3'] = np.array([0.1,0.2])
    
    return network

def forward(network,x):   #运算
    Z1 = sigmoid_function(np.dot(x,network['W1'])+network['B1'])
    Z2 = sigmoid_function(np.dot(Z1,network['W2'])+network['B2'])
    Y = identity_function(np.dot(Z2,network['W3'])+network['B3'])
    
    return Y

# 实现
network = init_parameter()

x = np.array([1.0,0.5])

Y = forward(network,x)

print(Y)

运行结果如下,在这里使用init_parameter()函数初始化参数,再用forward函数整合计算过程。


NLP_full_code.png

3.5 输出层设计

神经网络可以用在分类问题和回归问题上,输出层根据情况进行选择,一般来说<font color=red>回归问题用恒等函数,分类问题用softmax函数</font>。这里回顾一下分类和回归,分类问题顾名思义是判断数据属于哪一类的问题,回归是根据输入预测一个(连续的)数值的问题。

3.5.1 恒等函数和softmax函数

恒等函数很简单,对于输入的值,不做任何改动直接输出。输出层使用恒等函数,输入信号会原封不动的输出。

接下来着重介绍softmax函数,分类问题的softmax函数可以用下面的式子表示。

y_k = \frac{exp(a_k)}{\sum_{i=1}^{n} exp(a_i)}

这个式子还是比较好理解的,也就是说第k个输出等于,e^k除以所有输入信号指数和。图示如下:

softmax.png

明白了原理接下来实现。

a = np.array([0.3,2.9,4.0])
exp_a = np.exp(a)  #分别求指数
print(exp_a)
sum_exp_a = np.sum(exp_a) #指数和
print(sum_exp_a)
Y = exp_a/sum_exp_a  #结果
print(Y)

这只是简单验证,最终我们要写成函数。

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a/sum_exp_a
    
    return y

3.5.2 实现softmax函数的注意事项

上面的实现固然没有什么问题,就是严格按照函数的定义来写的,但是实际使用的时候,会遇到各种问题,因为这是一个指数函数,所以当输入信号很大时,会出现数据溢出的问题。因此我们可以做一点改进。

y_k = \frac{exp(a_k)}{\sum_{i=1}^{n} exp(a_i)} = \frac{Cexp(a_k)}{C\sum_{i=1}^{n} exp(a_i)} = \frac{exp(a_k+logC)}{\sum_{i=1}^{n} exp(a_i+logC)} = \frac{exp(a_k+C')}{\sum_{i=1}^{n} exp(a_i+C')}

首先我们在分子分母上都乘以C,然后把C移进指数函数中,记为logC,最后把logC替换为C'。对比原来的公式,我们发现,当使用softmax函数进行计算时,加上或减去某个常数并不会改变计算结果。于是对于溢出的结果,我们尝试让每个信号都减去信号中的最大值。


def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a/sum_exp_a
    
    return y
    
a = np.array([1010,1000,990])
print(softmax(a))

于是我们让每个信号都减去信号中的最大值。

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a/sum_exp_a
    
    return y

c = a - np.max(a)
print(softmax(c))
softmax_revised.png

改进后的的代码如下:

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.5.3 softmax函数的特征

根据softmax函数的定义,我们知道softmax函数输出的总是0.0到1.0的示数,且输出值的总和总为1,因为这个性质,我们可以吧softmax函数的输出理解为“概率”。输出值最大那么就可以认为是这个类别的“概率”最大,也就完成了分类。

需要注意的是,使用softmax函数之后,各个信号的大小并没有改变,因为指数函数是单调递增的,比如a的最大值是第2个元素,那么经过softmax函数,最大的还是第2个元素。

所以一般来说,神经网络把输出值最大的神经元对应的类别作为识别的结果,以为softmax函数的使用与否并没有改变输出的大小结果,所以输出层的softmax函数可以省略。实际问题中,因为softmax函数有一定的运算量,所以一般会被省略。

3.5.4 输出层神经元的数量

对于分类问题,输出层神经元的数量和要分类的类别数相等。

总结

以上就是神经网络的理论部分内容,后面的部分是解决实际问题的例子,我想把这一部分单独提出来写。对上面的内容做一个总结:

  • 神经网络中激活函数使用平滑变化的sigmoid函数或ReLU函数。
  • 机器学习的问题大体可以分为分类问题和回归问题
  • 使用Numpy多维数组可以高效的实现神经网络。
  • 对于输出层的激活函数,回归问题用恒等函数,分类问题用softmax函数。
  • 分类问题,输出层神经元的数量和要分类的类别数相等。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容