PyTorch深度学习笔记(3):多层神经网络的正向传播

本文主要以异或门问题为例子,介绍多层神经网络。我们将从上一篇文章中介绍的单层神经网络出发,学习或门、非与门、异或门问题,了解单层神经网络在面对非线性问题(e.g. 异或门问题)时的局限性,从而引出多层神经网络强大及其复杂性。我们将介绍多层神经网络的编号方式、其嵌套运算的本质、以及激活函数对于一个多层神经网络的重要性。

或门、非与门问题

在介绍使用单层神经网络解决二分类问题的时候,我们用了与门(andgate)的例子,并使用符号函数转化线性模型结果 z,配合恰当设置的权重向量 w,实现了模型的正确分类。其代码如下:

x0 x1 x2 andgate
1 0 0 0
1 1 0 0
1 0 1 0
1 1 1 1

在与门问题中,只有当x1和x2两个特征值同为1时,输出1;其他情况,输出0。

import torch

# 定义输入数据
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)

# 定义真实标签值
y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)

def AND(X):
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32) # 定义权重向量
    z_hat = torch.mv(X, w)
    y_hat = torch.tensor([int(x) for x in z_hat>0], dtype=torch.float32)
    return y_hat
y_hat = AND(X)
print("预测标签:", y_hat)
print("真实标签:", y)

预测标签: tensor([0., 0., 0., 1.])
真实标签: tensor([0., 0., 0., 1.])

由于与门问题只有两个特征值,我们用可视化的方式呈现。我们将两个特征值分别用x轴和y轴表示,分类为1的点被显示为红色,而分类为0的点被显示为紫色。

import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use("seaborn-v0_8-whitegrid")
sns.set_style("white")

plt.figure(figsize=(5,3))
plt.title("AND GATE", fontsize=12)

plt.scatter(X[:,1], X[:,2], c=y, cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=0.4, axis="y") # 显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) # r让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0)
AND GATE

回到我们的二分类模型,符号函数的公式是:
y = \left\{ \begin{aligned} 1 & &\text{if } z \gt 0 \\ 0 & &\text{if } z \le 0 \end{aligned} \right.

由于z = w_1 x_1 + w_2 x_2 + b,上述公式还可以写成:
y = \left\{ \begin{aligned} 1 & &\text{if } w_1 x_1 + w_2 x_2 + b \gt 0 \\ 0 & &\text{if } w_1 x_1 + w_2 x_2 + b \le 0 \end{aligned} \right.

从可视化的角度来说,我们的分类模型是通过 0.15 x_1 + 0.15 x_2 - 0.2 = 0 这条直线将所要被预测的点分为两类:位于直线上方的点被预测为标签1,下方的点被预测为标签0。我们将这条直线也在图上展示:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

plt.style.use("seaborn-v0_8-whitegrid")
sns.set_style("white")

plt.figure(figsize=(5,3))
plt.title("AND GATE", fontsize=12)

plt.scatter(X[:,1], X[:,2], c=y, cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=0.4, axis="y") # 显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) # r让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0)

x1 = np.arange(-1, 4)
plt.plot(x1, (0.2-0.15*x1)/0.15, c="k", linestyle="--") 
AND GATE 的决策边界

从图上可以看出,我们设置的权重向量得到的分割直线(通常被称作决策边界),能够将与门问题中的两类标签完美的分开。下面我们尝试将类似的决策边界应用到或门非与门异或门问题中。

【或门】

x0 x1 x2 orgate
1 0 0 0
1 1 0 1
1 0 1 1
1 1 1 1

在或门问题中,只要x1和x2两个特征值中有一个为1,就输出1;否则,输出0。

import torch

# 定义输入数据
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)

# 定义真实标签值
y = torch.tensor([0, 1, 1, 1], dtype=torch.float32)

def OR(X):
    w = torch.tensor([-0.08,0.15,0.15], dtype=torch.float32) # 定义权重向量
    z_hat = torch.mv(X, w)
    y_hat = torch.tensor([int(x) for x in z_hat>0], dtype=torch.float32)
    return y_hat

OR(X)

tensor([0., 1., 1., 1.])

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

plt.style.use("seaborn-v0_8-whitegrid")
sns.set_style("white")

plt.figure(figsize=(5,3))
plt.title("OR GATE", fontsize=12)

plt.scatter(X[:,1], X[:,2], c=y, cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=0.4, axis="y") # 显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) # r让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0)

x1 = np.arange(-1, 4)
plt.plot(x1, (0.08-0.15*x1)/0.15, c="k", linestyle="--") 
OR GATE

【非与门】

x0 x1 x2 nandgate
1 0 0 1
1 1 0 1
1 0 1 1
1 1 1 0

在非与门问题中,只有当x1和x2两个特征值同时为1时,输出0;其他情况,输出1。

import torch

# 定义输入数据
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)

# 定义真实标签值
y = torch.tensor([1, 1, 1, 0], dtype=torch.float32)

def NAND(X):
    w = torch.tensor([0.2,-0.15,-0.15], dtype=torch.float32) # 定义权重向量
    z_hat = torch.mv(X, w)
    y_hat = torch.tensor([int(x) for x in z_hat>0], dtype=torch.float32)
    return y_hat

NAND(X)

tensor([1., 1., 1., 0.])

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

plt.style.use("seaborn-v0_8-whitegrid")
sns.set_style("white")

plt.figure(figsize=(5,3))
plt.title("NAND GATE", fontsize=12)

plt.scatter(X[:,1], X[:,2], c=y, cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=0.4, axis="y") # 显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) # r让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0)

x1 = np.arange(-1, 4)
plt.plot(x1, (0.2-0.15*x1)/0.15, c="k", linestyle="--") 
NAND GATE

异或门:无法使用直线作为决策边界

在前面的二分类例子中我们可以看到,只要选取恰当的权重向量,使用直线的决策边界即可将两个类别完美的分割。但是,在很多分类问题中,直线的决策边界无法做到完美的分类,异或门问题就是其中之一。

【异或门】

x0 x1 x2 xorgate
1 0 0 0
1 1 0 1
1 0 1 1
1 1 1 0

在异或门问题中,当x1和x2两个特征值取值一致时(同时为0或同时为1),输出0;否则,输出1。

异或门数据的可视化展示如下:

import torch

X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
y = torch.tensor([0, 1, 1, 0], dtype=torch.float32)

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

plt.style.use("seaborn-v0_8-whitegrid")
sns.set_style("white")

plt.figure(figsize=(5,3))
plt.title("XOR GATE", fontsize=12)

plt.scatter(X[:,1], X[:,2], c=y, cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=0.4, axis="y") # 显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) # r让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0)
XOR GATE

从上图即可看出,没有一条直线能够将两类标签完美的分割,理想的决策边界是一条曲线。在神经网络中,可以通过增加神经网络的中间层来实现曲线的决策边界。

多层神经网络:XOR问题

这是一个多层神经网络,除了输入层和输出层,还多了一层“中间层”。在这个网络中,数据依然是从左侧的输入层进入,特征会分别进入NANDOR两个中间层的神经元,分别获得NAND函数的结果y_{and}OR函数y_{or}的结果,接着,y_{and}y_{or}会继续被输入下一层的神经元AND,经过AND函数的处理,成为最终结果y。下面我们使用代码来实现这个结构:

import torch

X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
y = torch.tensor([0, 1, 1, 0], dtype=torch.float32)

def XOR(X):
    
    sigma_or = OR(X) # 将数据输入OR函数
    sigma_nand = NAND(X) # 将数据输入AND函数
    x0 = torch.tensor([1,1,1,1], dtype=torch.float32)
    X = torch.cat((x0.view(4,1), sigma_or.view(4,1), sigma_nand.view(4,1)), dim=1) # 中间层的数据
    
    y_hat = AND(X) # 将中间层数据输入AND函数

    return y_hat

XOR(X)

tensor([0., 1., 1., 0.])

从结果可以看出,在单层神经网络中增加了中间层之后,非线性的异或门问题被解决。叠加了多层的神经网络被称为多层神经网络。多层神经网络是神经网络在深度学习中的基本形态。

多层神经网络的不可解释性

多层神经网络的中间层通常被称为隐藏层(hidden layer)。

在一个神经网络中,更靠近输入层的层级相对于其他层级叫做"上层",更靠近输出层的则相对于其他层级叫做"下层"。若从输入层开始从左向右编号,则输入层为第0层,输出层为最后一层。除了输入层以外,每个神经元中都存在着对数据进行处理的数个函数。

在异或门例子中,隐藏层中的函数是NAND函数和OR函数(即线性回归的加和函数+符号函数),输出层上的函数是AND函数。对于所有神经元和所有层而言,加和函数的部分都是一致的(都得到一个z值),因此我们需要关注的是加和之外的那部分函数。在隐藏层中,这个函数被称为激活函数,记作h(z);在输出层中,这个函数只是普通的连接函数,定义为g(z)。我们的数据被逐层传递,每个下层的神经元都必须处理上层神经元中的h(z)处理完毕的数据,整个流程本质上是一个嵌套计算结果的过程。

多层神经网络

在神经网络中,任意层上都有至少一个神经元,最上面的是常量神经元,连接常量神经元箭头上的参数是截距b,剩余的是特征神经元,连接特征神经元的参数是权重w,神经元从上至下进行编号,需注意的是,常量神经元与特征神经元是分别编号的,且神经元是从1开始编号的。

除了神经元和网络层,权重(w)、偏差(b)、神经元上的值(z\sigma)也存在编号。编号的规律如下:

  • 神经元上的值(z\sigma):上标表示所在网络层数的编号,下标表示所在层神经元的编号;
    • 举例:z_{1}^{1}表示在神经网络第一层的第一个特征神经元;
  • 连接箭头上的权重(w):上标表示该参数所指向的下一层神经网络的层数,下标表示所连接的两个前后层神经元分别的编号(后一层的编号在前);
    • 举例:w_{12}^{1}表示指向神经网络第1层的参数,连接了第1层第1个特征神经元和第0层第2个特征神经元;
  • 连接箭头上的偏差(b):上标表示该参数所指向的下一层神经网络的层数,下标表示所连接的下层神经元的编号;
    • 举例:b_{2}^{1}表示指向神经网络第1层的参数,指向了第1层第2个特征神经元。

下图是神经网络编号示例(只编号了蓝色部分的神经元和参数):

多层神经网络的编号

根据编号说明,我们可以用数学公式来表示从输入层传入到第一层隐藏层的信号了。以XOR异或门为例子,对于仅有两个特征的单一样本而言,在第一层的第一个特征神经元中获得加和结果的式子可以表示为:
z_{1}^{1} = b_{1}^{1} + w_{11}^{1} x_{1} + w_{12}^{1}x_{2}
被该神经元中激活函数处理的公式可以写作为:
\begin{aligned} \sigma_{1}^{1} &= h(z_{1}^{1}) \\ &= h(b_{1}^{1} + w_{11}^{1} x_{1} + w_{12}^{1}x_{2}) \end{aligned}

现在,我们用矩阵来表示数据从输入层传入到第一层,并在第一层的神经元中被处理成\sigma的情况:
\begin{aligned} \mathbf{Z^{1}} = \mathbf{W^{1}} \mathbf{X} + \mathbf{B^{1}} \end{aligned}

\begin{aligned} \left[ \begin{matrix} z_{1}^{1} \\ z_{2}^{1} \end{matrix} \right] = \left[ \begin{matrix} w_{11}^{1} & w_{12}^{1} \\ w_{21}^{1} & w_{22}^{1} \end{matrix} \right] * \left[ \begin{matrix} x_{1} \\ x_{2} \end{matrix} \right] + \left[ \begin{matrix} b_{1}^{1} \\ b_{2}^{1} \end{matrix} \right] \end{aligned}

\begin{aligned} \left[ \begin{matrix} \sigma_{1}^{1} \\ \sigma_{2}^{1} \end{matrix} \right] = \left[ \begin{matrix} h \left ( w_{11}^{1} x_{1} + w_{12}^{1} x_{2} + b_{1}^{1} \right ) \\ h \left ( w_{21}^{1} x_{1} + w_{22}^{1} x_{2} + b_{2}^{1} \right ) \end{matrix} \right] \end{aligned}

从中间层到输出层:

\begin{aligned} z_{1}^{2} &= w_{11}^{2} \sigma_{1}^{2} + w_{12}^{2} \sigma_{2}^{2} + b_{1}^{2} \\ \sigma_{1}^{2} &= g(z_{1}^{2}) \\ &= g \left ( w_{11}^{2} \sigma_{1}^{1} + w_{12}^{2} \sigma_{2}^{1} + b_{1}^{2} \right ) \end{aligned}

随着神经网络的层数继续增加,或每一层上神经元数量继续增加,神经网络的嵌套和计算就会变得更加复杂。由于每两层神经网络之间就会存在一个权重矩阵\mathbf{W},权重将无法直接追踪到特征x上,这是多层神经网络无法被解释的一个重要原因。

注:

  • 在多层神经网络中,每两层神经网络之间就会存在一个权重矩阵\mathbf{W}
  • 与单层神经网络不同,多层神经网络 z_{1}^{1} = b_{1}^{1} + w_{11}^{1} x_{1} + w_{12}^{1}x_{2} 公式中的\mathbf{X}是传统线性回归方程中的特征矩阵(design matrix)X转置(Transpose)。

非线性激活函数对于多层神经网络的重要性

在神经网络的隐藏层中,存在两个关键的元素,一个是加和函数\Sigma,另一个是激活函数h(z)。除了输入层之外,任何层的任何神经元上都会有加和的性质,因为神经元有“多进单出”的性质,可以一次性输入多个信号,但是输出只能有一个,因此输入神经元的信息必须以某种方式进行整合,否则神经元就无法将信息传递下去,而最容易的整合方式就是加和。因此我们可以认为加和是神经元自带的性质,只要增加更多的层,就会有更多的加和。但是激活函数的存在却不是如此,即便隐藏层上没有激活函数(或是一个恒等函数h(z)=z),神经网络依然可以从第一层走到最后一层。

我们沿用上一小节中XOR异或门的例子,可知输出层神经元的经过加和后的值z_{1}^{2}为:

\begin{aligned} z_{1}^{2} &= w_{11}^{2} \sigma_{1}^{1} + w_{12}^{2} \sigma_{1}^{2} + b_{1}^{2} \\ &= w_{11}^{2} \cdot h \left ( w_{11}^{1} x_{1} + w_{12}^{1} x_{2} + b_{1}^{1} \right ) + w_{12}^{2} \cdot h \left ( w_{21}^{1} x_{1} + w_{22}^{1} x_{2} + b_{2}^{1} \right ) + b_{1}^{2} \end{aligned}

当激活函数h(\cdot)为恒等函数时,上述式子可以写为:
\begin{aligned} z_{1}^{2} &= w_{11}^{2} \cdot \left ( w_{11}^{1} x_{1} + w_{12}^{1} x_{2} + b_{1}^{1} \right ) + w_{12}^{2} \cdot \left ( w_{21}^{1} x_{1} + w_{22}^{1} x_{2} + b_{2}^{1} \right ) + b_{1}^{2} \\ &= \left ( w_{11}^{2} w_{11}^{1} x_{1} + w_{11}^{2} w_{12}^{1} x_{2} + w_{11}^{2} b_{1}^{1} \right ) + \left ( w_{12}^{2} w_{21}^{1} x_{1} + w_{12}^{2} w_{22}^{1} x_{2} + w_{12}^{2} b_{2}^{1} \right ) + b_{1}^{2} \\ &= (w_{11}^{2} w_{11}^{1} + w_{12}^{2} w_{21}^{1} ) x_{1} + (w_{11}^{2} w_{12}^{1} + w_{12}^{2} w_{22}^{1}) x_{2} + w_{11}^{2} b_{1}^{1} + w_{12}^{2} b_{2}^{1} + b_{1}^{2} \\ &= w_{1}' x_{1} + w_{2}' x_{2} + b' \end{aligned}

其中,

  • w_{1}' = w_{11}^{2} w_{11}^{1} + w_{12}^{2} w_{21}^{1},
  • w_{2}' = w_{11}^{2} w_{12}^{1} + w_{12}^{2} w_{22}^{1},
  • b' = w_{11}^{2} b_{1}^{1} + w_{12}^{2} b_{2}^{1} + b_{1}^{2}.

从上述公式可以看出,在激活函数为恒等函数的情况下,输出层神经元的加和值z_{1}^{2}仅是输入层特征值的加权和,该神经网络相当于一个单层神经网络,而根据我们前面介绍,单层神经网络是无法解决XOR问题的。

也就是说,当激活函数是一个线性函数时,无论给神经网络增加多少层数,都无法解决非线性问题。

当然,并非所有的非线性激活函数都有效,比如,在XOR问题中,当我们把隐藏层上的激活函数换成sigmoid时,我们会发现该神经网络失效了。因此,激活函数的选择同样也很重要。深度学习中常用的激活函数有恒等函数(identity function),符号函数(Sign),Sigmoid函数,ReLU,Tanh,Softmax这六种,其Softmax与恒等函数几乎不会出现在隐藏层上,Sign、Tanh几乎不会出现在输出层上,ReLU与Sigmoid则是两种层都会出现。

神经网络上位于同一层的激活函数必须是一样的,但不同层之间可以选择不同的激活函数。尽管都是激活函数,但隐藏层和输出层上的激活函数作用是不一样的。输出层的激活函数是为了让神经网络能够输出不同类型的标签而存在的。其中恒等函数用于回归,Sigmoid函数用于二分类,Softmax用于多分类。即,g(z)仅与输出结果的表现形式有关,与神经网络的效果无关。隐藏层的激活函数会影响神经网络的效果,因此h(z)的选择是构造一个有效神经网络的关键之一。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容