文章目录
- 多层深度神经网络
- 一.黑箱:深层神经网络的不可解释性
- 二.多元神经网络: 层与 h ( z ) h(z) h(z)
- 三.激活函数
多层深度神经网络
从单层到多层是神经网络发展史上的重大变化,层的增加彻底将神经网络的性能提升到了另一个高度,正确理解层的意义对于我们自主构建神经网络有很重要的作用,学会利用层是避免浪费计算资源以及提升神经网络效果的关键。
一.黑箱:深层神经网络的不可解释性
首先从结构上来看,多层神经网络比单层神经网络多出了“中间层”。中间层常常被称为隐藏层(hidden layer),理论上来说可以有无限层,所以在图像表示中经常被省略。层数越多,神经网络的模型复杂度越高,一般也认为更深的神经网络可以解决更加复杂的问题。在学习中,通常我们最多只会设置3~5个隐藏层,但在实际工业场景中会更多。还记得这张图吗?当数据量够大时,现代神经网络层数越深,效果越好。
在一个神经网络中,更靠近输入层的层级相对于其他层级叫做"上层",更靠近输出层的则相对于其他层级叫做"下层"。若从输入层开始从左向右编号,则输入层为第0层,输出层为最后一层。除了输入层以外,每个神经元中都存在着对数据进行处理的数个函数。在我们的例子异或门(XOR)中,隐藏层中的函数是NAND函数和OR函数(也就是线性回归的加和函数+阶跃函数),输出层上的函数是AND函数。 对于所有神经元和所有层而言,加和函数的部分都是一致的(都得到结果 z z z),因此我们需要关注的是加和之外的那部分函数。在隐藏层中这个函数被称为激活函数,符号为 h ( z ) h(z) h(z),在输出层中这个函数只是普通的连接函数,我们定义为是 g ( z ) g(z) g(z)。我们的数据被逐层传递,每个下层的神经元都必须处理上层的神经元中的 h ( z ) h(z) h(z)处理完毕的数据,整个流程本质是一个嵌套计算结果的过程。
在神经网络中,任意层上都有至少一个神经元,最上面的是常量神经元,连接常量神经元的箭头上的参数是截距 b b b,剩余的是特征神经元,连接这些神经元的箭头上的参数都是权重 ω \omega ω。神经元是从上至下进行编号,需要注意的是,常量神经元与特征神经元是分别编号的。和从0开始编号的层数不同,神经元是从1开始编号的。在异或门的例子中,含有1的偏差神经元是1号偏差神经元,含有特征 x 1 x_1 x1的神经元则是1号特征神经元。
除了神经元和网络层,权重、偏差、神经元上的取值也存在编号。这些编号规律分别如下:
二.多元神经网络: 层与 h ( z ) h(z) h(z)
我们首先想要发问的隐藏层的作用。在之前的XOR函数中,我们提出”多层神经网络能够描绘出一条曲线 作为决策边界,以此为基础处理单层神经网络无法处理的复杂问题“,这可能让许多人产生了“是增加层 数帮助了神经网络”的错觉。实际上并非如此。 在神经网络的隐藏层中,存在两个关键的元素,一个是加和函数 ,另一个是 。除了输入层之外, 任何层的任何神经元上都会有加和的性质,因为神经元有“多进单出”的性质,可以一次性输入多个信 号,但是输出只能有一个,因此输入神经元的信息必须以某种方式进行整合,否则神经元就无法将信息 传递下去,而最容易的整合方式就是加和 。因此我们可以认为加和 是神经元自带的性质,只要增加 更多的层,就会有更多的加和。但是 的存在却不是如此,即便隐藏层上没有 (或 是一个恒 等函数),神经网络依然可以从第一层走到最后一层。让我们来试试看,在XOR中,假设隐藏层上没有 的话, 会发生什么:
xorgate = torch.tensor([0,1,1,0],dtype=torch.float32)
def AND(X): w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32) zhat = torch.mv(X,w) #下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z) andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return andhat def OR(X): w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数 值 zhat = torch.mv(X,w) #注释掉阶跃函数,相当于h(z)是恒等函数 #yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return zhatdef NAND(X): w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w) #注释掉阶跃函数,相当于h(z)是恒等函数 #yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return zhat def XOR(X): #输入值: input_1 = X #中间层: sigma_nand = NAND(input_1) sigma_or = OR(input_1) x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32) #输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1) y_and = AND(input_2) #print("NANE:",y_nand) #print("OR:",y_or) return y_andXOR(X)
很明显,此时XOR函数的预测结果与真实的xorgate不一致。当隐藏层的 是恒等函数或不存在时,叠 加层并不能够解决XOR这样的非线性问题。从数学上来看,这也非常容易理解。
从输出层到第一层:
Z 1 = W 1 ∗ X + b 1 ∑ 1 = h ( Z 1 ) ∵ h ( z ) 为恒等函数 ∴ ∑ 1 = [ σ 1 1 σ 2 1 ] = [ z 1 1 z 1 2 ] Z^1 = W^1 * X + b^1 \sum^1 = h(Z^1) \\ \because h(z)为恒等函数 \\ \therefore \sum^1 = \begin{bmatrix} \sigma_1^1\\ \sigma_2^1 \end{bmatrix} = \begin{bmatrix} z^1_1\\ z^2_1 \end{bmatrix} Z1=W1∗X+b1∑1=h(Z1)∵h(z)为恒等函数∴∑1=[σ11σ21]=[z11z12]
从第1层到输出层:
Z 2 = W 2 ∗ ∑ + B 2 [ z 2 1 ] = [ w 1 → 1 1 → 2 w 1 → 2 1 → 2 ] ∗ [ σ 1 1 σ 1 2 ] + [ b 1 → 1 1 → 2 ] . . . = x 1 W 1 + x 2 W 2 + B ,其中 W 1 , W 2 和 B 都是常数 Z^2 = W^2* \sum + B^2 \\ \begin{bmatrix} z^1_2\\ \end{bmatrix}= \begin{bmatrix} w^{1\to2}_{1\to1} w^{1\to2}_{1\to2} \end{bmatrix} * \begin{bmatrix} \sigma_1^1\\ \sigma_1^2 \end{bmatrix} + \begin{bmatrix} b^{1\to2}_{1\to1} \end{bmatrix} \\ ... \\ =x_1W_1 + x_2W_2 + B,其中W1,W2和B都是常数 Z2=W2∗∑+B2[z21]=[w1→11→2w1→21→2]∗[σ11σ12]+[b1→11→2]...=x1W1+x2W2+B,其中W1,W2和B都是常数
不难发现,最终从输出层输出的结果和第一层的输出结果 x 1 w 1 1 + x 2 w 2 2 + b 1 2 x_1w_1^1 + x_2w_2^2 + b_1^2 x1w11+x2w22+b12是类似的,只不过是乘以特征 x 1 , x 2 x_1,x_2 x1,x2的具体数值不同。在没有 h ( z ) h(z) h(z)时,在层中流动的数据被做了仿射变换(affine transformation),仿射变换后得到的依然是一个线性方程,而这样的方程不能解决非线性问题。可见,“层”本身不是神经网络解决非线性问题的关键,层上的h(z)才是。从上面的例子和数学公式中可以看出,如果 h ( z ) h(z) h(z)是线性函数,或不存在,那增加再多的层也没有用。
那是不是任意非线性函数作为 h ( z ) h(z) h(z)都可以解决问题呢?让我们来试试看,在XOR例子中如果不使用阶跃函数,而使用sigmoid函数作为 h ( z ) h(z) h(z) ,会发生什么。
def AND(X): w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w) #下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z) andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return andhatdef OR(X): w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数 值 zhat = torch.mv(X,w) #h(z), 使用sigmoid函数 sigma = torch.sigmoid(zhat) return sigmadef NAND(X): w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w) #h(z), 使用sigmoid函数 sigma = torch.sigmoid(zhat) return sigmadef XOR(X): #输入值: input_1 = X #中间层: sigma_nand = NAND(input_1) sigma_or = OR(input_1) x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32) #输出层: input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1) y_and = AND(input_2) #print("NANE:",y_nand) #print("OR:",y_or) return y_and XOR(X)
可以发现,如果将 h ( z ) h(z) h(z)换成sigmoid函数,XOR结构的神经网络同样会失效!可见,即便是使用了 ,也不一定能够解决曲线分类的问题。在不适合的非线性函数加持下,神经网络的层数再多也无法起效。所以, h ( z ) h(z) h(z)是真正能够让神经网络算法“活起来”的关键,没有搭配合适 h ( z ) h(z) h(z)的神经网络结构是无用的,而 h ( z ) h(z) h(z)正是神经网络中最关键的概念之一激活函数(activation function)。
三.激活函数
激活函数
在人工神经网络的神经元上,根据一组输入定义该神经元的输出结果的函数,就是激活函数。激活 函数一般都是非线性函数,它出现在神经网络中除了输入层以外的每层的每个神经元上。
机器学习中常用的激活函数只有恒等函数(identity function),阶跃函数(sign),sigmoid 函数,ReLU,tanh,softmax这六种,其中Softmax与恒等函数几乎不会出现在隐藏层上,Sign、Tanh几乎不会出现在输出层上,ReLU与Sigmoid则是两种层都会出现,并且应用广泛。
在这里,我们将总结性声明一下输出层的g(z)与隐藏层 的h(z)之间的区别,以帮助大家获得更深的理解:
-
- 虽然都是激活函数,但隐藏层和输出层上的激活函数作用是完全不一样的。输出层的激活函数 是为了让神经网络能够输出不同类型的标签而存在的。其中恒等函数用于回归,sigmoid函数用于 二分类,softmax用于多分类。换句说, g ( z ) g(z) g(z)仅仅与输出结果的表现形式有关,与神经网络的效果无关,也因此它可以使用线性的恒等函数。但隐藏层的激活函数就不同了,如我们之前尝试的 XOR,隐藏层上的激活函数 h ( z ) h(z) h(z)的选择会影响神经网络的效果,而线性的 h ( z ) h(z) h(z)是会让神经网络的结构失效的。
-
- 在同一个神经网络中, 与 可以是不同的,并且在大多数运行回归和多分类的神经网络时, 他们也的确是不同的。每层上的 可以是不同的,但是同一层上的激活函数必须一致。
现在我们来试试看,隐藏层上的 h ( z ) h(z) h(z)是阶跃函数,而输出层的 g ( z ) g(z) g(z)是sigmoid的情况。如果XOR网络依然有效,就证明了 g ( z ) g(z) g(z)的变化对神经网络结果输出无影响。反之,则说明 g ( z ) g(z) g(z)也影响神经网络输出结果。
#如果g(z)是sigmoid函数,而h(z)是阶跃函数
#输出层,以0.5为sigmoid的阈值
def AND(X): w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32) zhat = torch.mv(X,w) sigma = torch.sigmoid(zhat) andhat = torch.tensor([int(x) for x in sigma >= 0.5],dtype=torch.float32) return andhat
#隐藏层,OR与NAND都使用阶跃函数作为h(z)
def OR(X): w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里修改了b的数值 zhat = torch.mv(X,w) yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return yhat def NAND(X): w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w) yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32) return yhatdef XOR(X): #输入值: input_1 = X #中间层:sigma_nand = NAND(input_1) sigma_or = OR(input_1) x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32) #输出层: input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1) y_and = AND(input_2) #print("NANE:",y_nand) #print("OR:",y_or) return y_andXOR(X)
从结果可以看出,只要隐藏层的 h ( z ) h(z) h(z)是阶跃函数,XOR网络就一定能有效,这与输出层 g ( z ) g(z) g(z)是什么函数完全无关。从这里开始,若没有特别说明,当我们提到“激活函数”时,特指隐藏层上的激活函数 h ( z ) h(z) h(z)。当需要表达输出层上的激活函数时,我们需称其为“输出层激活函数”(out_activation)。