抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

深度学习入门(四):3 层神经网络的实现

2020 年 3 月 29 日

深度学习入门(四):3 层神经网络的实现

编者按:本文节选自图灵程序设计丛书 《深度学习入门》一书中的部分章节。


现在我们来进行神经网络的实现。这里我们以图 1 的 3 层神经网络为对象,实现从输入到输出的(前向)处理。在代码实现方面,使用上一节介绍的 NumPy 多维数组。巧妙地使用 NumPy 数组,可以用很少的代码完成神经网络的前向处理。



图 1 3 层神经网络:输入层(第 0 层)有 2 个神经元,第 1 个隐藏层(第 1 层)有 3 个神经元,第 2 个隐藏层(第 2 层)有 2 个神经元,输出层(第 3 层)有 2 个神经元


符号确认

在介绍神经网络中的处理之前,我们先导入 $w^{(1)}{12}a^{(1)}{1}$ 等符号。这些符号可能看上去有些复杂,不过因为只在本节使用,稍微读一下就跳过去也问题不大。


本节的重点是神经网络的运算可以作为矩阵运算打包进行。因为神经网络各层的运算是通过矩阵的乘法运算打包进行的(从宏观视角来考虑),所以即便忘了(未记忆)具体的符号规则,也不影响理解后面的内容。


我们先从定义符号开始。请看图 2。图 2 中只突出显示了从输入层神经元 到后一层的神经元 的权重。


如图 2 所示,权重和隐藏层的神经元的右上角有一个“(1)”,它表示权重和神经元的层号(即第 1 层的权重、第 1 层的神经元)。此外,权重的右下角有两个数字,它们是后一层的神经元和前一层的神经元的索引号。比如,$w^{(1)}{12}x_2a^{(1)}{1}$ 的权重。权重右下角按照“后一层的索引号、前一层的索引号”的顺序排列。



图 2 权重的符号


各层间信号传递的实现

现在看一下从输入层到第 1 层的第 1 个神经元的信号传递过程,如图 3 所示。



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


图 3 中增加了表示偏置的神经元“1”。请注意,偏置的右下角的索引号只有一个。这是因为前一层的偏置神经元(神经元“1”)只有一个 1


1 任何前一层的偏置神经元“1”都只有一个。偏置权重的数量取决于后一层的神经元的数量(不包括后一层的偏置神经元“1”)。——译者注


为了确认前面的内容,现在用数学式表示 $a^{(1)}{1}a^{(1)}{1}$ 通过加权信号和偏置的和按如下方式进行计算。


$a^{(1)}{1}=w^{(1)}{11}x_1+w^{(1)}{12}x_2+b^{(1)}{1}\quad\quad\quad\quad\quad(3.8)$


此外,如果使用矩阵的乘法运算,则可以将第 1 层的加权和表示成下面的式(3.9)。


其中, 如下所示。


$\begin{aligned}&\boldsymbol{A}^{(1)}=\Bigl(a_1^{(1)}~~~a_2^{(1)}~a_3^{(1)}\Bigr),~\boldsymbol{X}=\Bigl(x_1~x_2\Bigr),~\boldsymbol{B}^{(1)}=\Bigl(b_1^{(1)}~~~b_2^{(1)}~~~b_3^{(1)}\Bigr)\&\boldsymbol{W}^{(1)}=\begin{pmatrix}w^{(1)}{11}&w^{(1)}{21}&w^{(1)}{31}\w^{(1)}{12}&w^{(1)}{22}&w^{(1)}{32}\end{pmatrix}\end{aligned}$


下面我们用 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,)
<b>A1 = np.dot(X, W1) + B1</b>
复制代码


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


接下来,我们观察第 1 层中激活函数的计算过程。如果把这个计算过程用图来表示的话,则如图 4 所示。



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


如图 4 所示,隐藏层的加权和(加权信号和偏置的总和)用 表示,被激活函数转换后的信号用 表示。此外,图中 表示激活函数,这里我们使用的是 sigmoid 函数。用 Python 来实现,代码如下所示。


<b>Z1 = sigmoid(A1)</b>
print(A1) # [0.3, 0.7, 1.1]print(Z1) # [0.57444252, 0.66818777, 0.75026011]
复制代码


这个 sigmoid() 函数就是之前定义的那个函数。它会接收 NumPy 数组,并返回元素个数相同的 NumPy 数组。


下面,我们来实现第 1 层到第 2 层的信号传递(图 5)。


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,)
<b>A2 = np.dot(Z1, W2) + B2</b><b>Z2 = sigmoid(A2)</b>
复制代码


除了第 1 层的输出(Z1)变成了第 2 层的输入这一点以外,这个实现和刚才的代码完全相同。由此可知,通过使用 NumPy 数组,可以将层到层的信号传递过程简单地写出来。



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


最后是第 2 层到输出层的信号传递(图 6)。输出层的实现也和之前的实现基本相同。不过,最后的激活函数和之前的隐藏层有所不同。


def identity_function(x):    return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])B3 = np.array([0.1, 0.2])
<b>A3 = np.dot(Z2, W3) + B3</b><b>Y = identity_function(A3)</b> # 或者Y = A3
复制代码


这里我们定义了 identity_function() 函数(也称为“恒等函数”),并将其作为输出层的激活函数。恒等函数会将输入按原样输出,因此,这个例子中没有必要特意定义 identity_function()。这里这样实现只是为了和之前的流程保持统一。另外,图 6 中,输出层的激活函数用 表示,不同于隐藏层的激活函数 读作 sigma)。



图 6 从第 2 层到输出层的信号传递


输出层所用的激活函数,要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用 sigmoid 函数,多元分类问题可以使用 softmax 函数。关于输出层的激活函数,我们将在下一节详细介绍。


代码实现小结

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


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,从输出到输入方向)的处理。


至此,神经网络的前向处理的实现就完成了。通过巧妙地使用 NumPy 多维数组,我们高效地实现了神经网络。


图书简介https://www.ituring.com.cn/book/1921



相关阅读


深度学习入门(一):神经网络


深度学习入门(二):激活函数


深度学习入门(三):多维数组的运算


2020 年 3 月 29 日 19:25366

评论

发布
暂无评论
发现更多内容

产品经理训练营第二周作业-利益相关者

隋泽

产品经理训练营

JavaScript06 - 操作符

桃夭十一里

JavaScript

吐血整理:推荐几款顶级好用的IDEA插件

Silently9527

Java intellij-idea idea插件

云游戏的那些事儿!读《大厂们的下一件大事儿》有感

李忠良

28天写作

微信直播也有跳舞小姐姐了 | 视频号28天(17)

赵新龙

28天写作

产品 0 期 - 第一章作业

让时间说真话

产品经理

Scrum Patterns:Sprint计划会(译)

Bruce Talk

敏捷 译文 Agile Scrum Patterns

【函数计算实践】一个应用案例

程序员架构进阶

阿里云 架构 项目实战 函数计算 28天写作

JavaScript08 - 数组

桃夭十一里

JavaScript

认识产品经理

ALone

从明天起做一个早起的人

IT蜗壳-Tango

七日更

架构师训练营第四周作业 - 命题作业

阿德儿

线程的三种等待唤醒机制(面试必问)

hepingfly

Java 线程 等待唤醒

LeetCode题解:105. 从前序与中序遍历序列构造二叉树,Simple O(n) without map,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

学习“利益相关者”后对自己工作的一点思考

隋泽

时间复杂度与常见排列算法

Changing Lin

算法

「产品经理训练营」作业02:利益相关方识别

狷介

产品经理训练营

Python 中 lru_cache 的使用和实现

zikcheng

Python 源码分析 LRU

JavaScript07 - 流程控制语句

桃夭十一里

JavaScript

Java-可重入锁

hepingfly

Java 可重入锁

开发质量提升系列:checklist投产检查列表(上)

罗小龙

代码质量 28天写作 checklist

「架构师训练营 4 期」 第四周 - 002

凯迪

架构师训练营 4 期 第4周

引花眠

架构师训练营 4 期

JVM 垃圾收集算法

看山

JVM 垃圾回收算法

【JS】Array.from() 将伪数组转换成数组

学习委员

JavaScript js ES6 array 28天写作

【CSS】页面顶部阴影

学习委员

CSS 前端 html/css CSS小技巧 28天写作

CSS(二)——CSS核心基础

程序员的时光

CSS 程序员 七日更 28天写作

【JS】Array.of() 创建数组

学习委员

JavaScript 前端 js ES6 28天写作

请用思维导图画出架构师训练营所有技术知识点

DL

批判性思维自修课(一)

石君

28天写作 批判性思维

GNUCash 2: 缺点

lidaobing

GNUCash 28天写作

Study Go: From Zero to Hero

Study Go: From Zero to Hero

深度学习入门(四):3 层神经网络的实现-InfoQ