偏置是神经元中常用的输入项。和其他输入元素一样,神经元会给偏置一个权重,该权重与其他权重用同样的方式来训练。在关于神经网络的各种文献中,偏置有两种表示形式。一种表示形式是将其表示为输入向量,例如对于n维向量的输入,在向量的开头或结尾处增加一个元素,构成一个n + 1维的向量。1的位置与网络无关,只要在所有样本中保持一致即可。另一种表示形式是,首先假定存在一个偏置项,将其独立于输入之外,其对应一个独立的权重,将该权重乘以1,然后与样本输入值及其相关权重的点积进行加和。这两者实际上是一样的,只不过分别是两种常见的表示形式而已。
设置偏置权重的原因是神经元需要对全0的输入具有弹性。网络需要学习在输入全为0的情况下输出仍然为0,但它可能做不到这一点。如果没有偏置项,神经元对初始或学习的任意权重都会输出0 × 权重 = 0。而有了偏置项之后,就不会有这个问题了。如果神经元需要学习输出0,在这种情况下,神经元可以学会减小与偏置相关的权重,使点积保持在阈值以下即可。
图5-3用可视化方法对生物的大脑神经元的信号与深度学习人工神经元的信号进行了类比,如果想要做更深入的了解,可以思考一下你是如何使用生物神经元来阅读本书并学习有关自然语言处理的深度学习知识的[5]。
图5-3 感知机与生物神经元
用数学术语来说,感知机的输出表示为f (x),如下:
公式5-1 阈值激活函数
提示
输入向量(X)与权重向量(W)两两相乘后的加和就是这两个向量的点积。这是线性代数在神经网络中最基础的应用,对神经网络的发展影响巨大。另外,通过现代计算机GPU对线性代数操作的性能优化来完成感知机的矩阵乘法运算,使得实现的神经网络变得极为高效。
此时的感知机并未学到任何东西,不过大家已经获得了非常重要的结果,我们已经向模型输入数据并且得到输出。当然这个输出可能是错误的,因为还没有告诉感知机如何获得权重,而这正是最有趣的地方所在。
提示
所有神经网络的基本单位都是神经元,基本感知机是广义神经元的一个特例,从现在开始,我们将感知机称为一个神经元。
在Python中,计算神经元的输出是很简单的。大家可以用numpy的dot函数将两个向量相乘:
>>> import numpy as np
>>> example_input = [1, .2, .1, .05, .2]
>>> example_weights = [.2, .12, .4, .6, .90]
>>> input_vector = np.array(example_input)
>>> weights = np.array(example_weights)
>>> bias_weight = .2
>>> activation_level = np.dot(input_vector, weights) +
... (bias_weight * 1) ⇽--- 这里bias_weight * 1只是为了强调bias_weight和其他权重一样:权重与输入值相乘,区别只是bias_weight的输入特征值总是1
>>> activation_level
0.674
接下来,假设我们选择一个简单的阈值激活函数,并选择0.5作为阈值,结果如下:
>>> threshold = 0.5
>>> if activation_level >= threshold:
... perceptron_output = 1
... else:
... perceptron_output = 0
>>> perceptron_output)
1
对于给定的输入样本example_input和权重,这个感知机将会输出1。如果有许多example_input向量,输出将会是一个标签集合,大家可以检查每次感知机的预测是否正确。
大家已经构建了一个基于数据进行预测的方法,它为机器学习创造了条件。到目前为止,权重都作为任意值而被我们忽略了。实际上,它们是整个架构的关键,现在我们需要一种算法,基于给定样本的预测结果来调整权重值的大小。
感知机将权重的调整看成是给定输入下预测系统正确性的一个函数,从而学习这些权重。但是这一切从何开始呢?未经训练的神经元的权重一开始是随机的!通常是从正态分布中选取趋近于零的随机值。在前面的例子中,大家可以看到从零开始的权重(包括偏置权重)为何会导致输出全部为零。但是通过设置微小的变化,无须提供给神经元太多的能力,神经元便能以此为依据判断结果何时为对何时为错。
然后就可以开始学习过程了。通过向系统输入许多不同的样本,并根据神经元的输出是否是我们想要的结果来对权重进行微小的调整。当有足够的样本(且在正确的条件下),误差应该逐渐趋于零,系统就经过了学习。
其中最关键的一个诀窍是,每个权重都是根据它对结果误差的贡献程度来进行调整。权重越大(对结果影响越大),那么该权重对给定输入的感知机输出的正确性/错误性就负有越大的责任。
假设之前的输入example_input对应的结果是0:
>>> expected_output = 0
>>> new_weights = []
>>> for i, x in enumerate(example_input):
... new_weights.append(weights[i] + (expected_output -
... perceptron_output) * x) ⇽--- 例如,在上述的第一次计算中,new_weight = 0.2 + (0 - 1) × 1 = −0.8
>>> weights = np.array(new_weights)
>>> example_weights ⇽--- 初始权重
[0.2, 0.12, 0.4, 0.6, 0.9]
>>> weights ⇽--- 新的权重
[-0.8 -0.08 0.3 0.55 0.7]
这个处理方法将同一份训练集反复输入网络中,在适当的情况下,即使是对于之前没见过的数据,感知机也能做出正确的预测。
上面使用了一些随机数字做例子。我们把这个方法应用到一个具体问题上,来看看如何通过仅向计算机展示一些标记样本来教它学会一个概念。
接下来,我们将让计算机理解逻辑或(OR)。如果一个表达式的一边或另一边为真(或两边都为真),则逻辑或语句的结果为真。这个逻辑非常简单。对于以下这个问题,我们可以手动构造所有可能的样本(在现实中很少出现这种情况),每个样本由两个信号组成,其中每个信号都为真(1)或假(0),如代码清单5-1所示。
代码清单5-1 逻辑或问题
>>> sample_data = [[0, 0], # False, False
... [0, 1], # False, True
... [1, 0], # True, False
... [1, 1]] # True, True
>>> expected_results = [0, # (False OR False) gives False
... 1, # (False OR True ) gives True
... 1, # (True OR False) gives True
... 1] # (True OR True ) gives True
>>> activation_threshold = 0.5
我们需要一些工具,numpy可以用来做向量(数组)乘法,random用来初始化权重:
>>> from random import random
>>> import numpy as np
>>> weights = np.random.random(2)/1000 # Small random float 0 < w < .001
>>> weights
[5.62332144e-04 7.69468028e-05]
这里还需要一个偏置:
>>> bias_weight = np.random.random() / 1000
>>> bias_weight
0.0009984699077277136
然后将其传递到流水线中,计算得到4个样本的预测结果,如代码清单5-2所示。
代码清单5-2 感知机随机预测
>>> for idx, sample in enumerate(sample_data):
... input_vector = np.array(sample)
... activation_level = np.dot(input_vector, weights) +
... (bias_weight * 1)
... if activation_level > activation_threshold:
... perceptron_output = 1
... else:
... perceptron_output = 0
... print('Predicted {}'.format(perceptron_output))
... print('Expected: {}'.format(expected_results[idx]))
... print()
Predicted 0
Expected: 0
Predicted 0
Expected: 1
Predicted 0
Expected: 1
Predicted 0
Expected: 1
随机的权重值对这个神经元没有多大帮助,我们得到1个正确、3个错误的预测结果。接下来我们让网络继续学习,并在每次迭代中不只是打印1或0,而是同时更新权重值,如代码清单5-3所示。
代码清单5-3 感知机学习
>>> for iteration_num in range(5):
... correct_answers = 0
... for idx, sample in enumerate(sample_data):
... input_vector = np.array(sample)
... weights = np.array(weights)
... activation_level = np.dot(input_vector, weights) +
... (bias_weight * 1)
... if activation_level > activation_threshold:
... perceptron_output = 1
... else:
... perceptron_output = 0
... if perceptron_output == expected_results[idx]:
... correct_answers += 1
... new_weights = []
... for i, x in enumerate(sample): ⇽--- 这就是使用魔法的地方。当然还有一些更高效的方法来实现,不过我们还是通过循环来强调每个权重是由其输入(xi)更新的。如果输入数据很小或为零,则无论误差大小,该输入对该权重的影响都将会很小。相反,如果输入数据很大,则影响会很大
... new_weights.append(weights[i] + (expected_results[idx] -
... perceptron_output) * x)
... bias_weight = bias_weight + ((expected_results[idx] -
... perceptron_output) * 1) ⇽--- 偏置权重也会随着输入一起更新
... weights = np.array(new_weights)
... print('{} correct answers out of 4, for iteration {}'
... .format(correct_answers, iteration_num))
3 correct answers out of 4, for iteration 0
2 correct answers out of 4, for iteration 1
3 correct answers out of 4, for iteration 2
4 correct answers out of 4, for iteration 3
4 correct answers out of 4, for iteration 4
哈哈!这个感知机真是个好学生。通过内部循环更新权重,感知机从数据集中学习了经验。在第一次迭代后,它比随机猜测(正确率为1/4)多得到了两个正确结果(正确率为3/4)。
在第二次迭代中,它过度修正了权重(更改了太多),然后通过调整权重来回溯结果。当第四次迭代完成后,它已经完美地学习了这些关系。随后的迭代将不再更新网络,因为每个样本的误差为0,所以不会再对权重做调整。
这就是所谓的收敛。当一个模型的误差函数达到了最小值,或者稳定在一个值上,该模型就被称为收敛。有时候可能没有这么幸运。有时神经网络在寻找最优权值时不断波动以满足一批数据的相互关系,但无法收敛。在5.8节中,大家将看到目标函数(objective function)或损失函数(loss function)如何影响神经网络对最优权重的选择。
基本感知机有一个固有缺陷,那就是,如果数据不是线性可分的,或者数据之间的关系不能用线性关系来描述,模型将无法收敛,也将不具有任何有效预测的能力,因为它无法准确地预测目标变量。
早期的实验在仅基于样本图像及其类别来进行图像分类的学习上取得了成功。这个概念在早期很激动人心,但很快受到了来自明斯基(Minsky)和佩珀特(Papert)的考验[6],他们指出感知机在分类方面有严重的局限性,他们证明了如果数据样本不能线性可分为独立的组,那么感知机将无法学习如何对输入数据进行分类。
线性可分的数据点(如图5-4所示)对感知机来说是没有问题的,而存在类别交叉的数据将导致单神经元感知机原地踏步,学习预测的结果将不比随机猜测好,表现得就像是在随机抛硬币。在图5-5中我们就无法在两个类(分别用点和叉表示)之间画一条分割线。
图5-4 线性可分的数据
图5-5 非线性可分的数据
感知机会用线性方程来描述数据集的特征与数据集中的目标变量之间的关系,这就是线性回归,但是感知机无法描述非线性方程或者非线性关系。
局部极小值与全局极小值
当一个感知机收敛时,可以说它找到了一个描述数据与目标变量之间关系的线性方程。然而,这并不能说明这个描述性线性方程有多好,或者说代价有多“小”。如果有多个解决方案,即存在着多个可能的极小代价,它只会确定一个由权重初始值决定的、特定的极小值。这被称为局部极小值,因为它是在权重开始的地方附近找到的最优值(最小的代价)。它可能不是全局极小值,因为全局极小值需要搜索所有可能的权重值。在大多数情况下,无法确定是否找到了全局极小值。
很多数据值之间的关系不是线性的,也没有好的线性回归或线性方程能够描述这些关系。许多数据集不能用直线或平面来线性分割。因为世界上的大多数数据不能由直线或平面来清楚地分开,明斯基和佩珀特发表的“证明”让感知机被束之高阁。
但是有关感知机的想法并不会就此消亡。Rumelhardt McClelland的合作成果[7](Geoffrey Hinton也参与其中)展示了可以使用多层感知机的组合来解决异或(XOR)问题[8],此后感知机再次浮出水面。之前,大家使用单层感知机解决的或(OR)问题属于比较简单的问题,也没有用到多层反向传播。Rumelhardt McClelland的关键突破是发现了一种方法,该方法可以为每个感知机适当地分配误差。他们使用的是一种叫反向传播的传统思想,通过这种跨越多个神经元层的反向传播思想,第一个现代神经网络诞生了。
基本感知机有一个固有的缺陷,即如果数据不是线性可分的,则模型不会收敛到具有有效预测能力的解。
注意
代码清单5-3中的代码通过一个单层感知机解决了或问题。代码清单5-1代码中感知机学到的0和1组成的表格是二元逻辑或的输出结果。异或问题稍微改变了一下该数据表,以此来教感知机如何模拟一个独占的逻辑或门。如果改变一下最后一个示例的正确结果,将1(真)改为0(假),从而将其转换为逻辑异或,这个问题就会变得困难许多。在不向神经网络添加额外神经元的情况下,每类(0或1)样本将是非线性可分的。在二维特征向量空间中,这些类彼此呈对角线分布(类似于图5-5),所以无法通过画一条直线来区分出数据样本1(逻辑真)和0(逻辑假)。
尽管神经网络可以解决复杂的非线性问题,但是那时计算成本太高,用两个感知机和一堆花哨的反向传播数学算法来解决异或问题被认为是对宝贵计算资源的极大浪费,因为这个问题只需要用一个逻辑门或一行代码就能解决。事实证明,它们不适合被广泛使用,所以只能再次回到学术界和超级计算机实验的角落。由此开启了第二次“人工智能寒冬”,这种情况从1990年持续到2010年左右[9]。后来,随着计算能力、反向传播算法和原始数据(例如猫和狗的标注图像[10])的发展,昂贵的计算算法和有限的数据集都不再是障碍。第三次神经网络时代开始了。
下面我们来看看他们发现了什么。
和大多数伟大的思想一样,好的思想最终都会浮出水面。人们发现只要对感知机背后的基本思想进行扩展,就可以克服之前的那些限制。将多个感知机集合到一起,并将数据输入一个(或多个)感知机中,并且以这些感知机的输出作为输入,传递到更多的感知机,最后将输出与期望值进行比较,这个系统(神经网络)便可以学习更复杂的模式,克服了类的线性不可分的挑战,如异或问题。其中的关键是:如何更新前面各层中的权重?
接下来我们暂停一下,将这个过程的一个重要部分形式化。到目前为止,我们已经讨论了误差和感知机的预测结果与真实结果的偏离程度。测量这个误差是由代价函数或损失函数来完成的。正如我们看到的,代价函数量化了对于输入“问题”(x)网络应该输出的正确答案与实际输出值(y)之间的差距。损失函数则表示网络输出错误答案的次数以及错误总量。公式5-2是代价函数的一个例子,表示真实值与模型预测值之间的误差:
公式5-2 真实值与预测值之间的误差
训练感知机或者神经网络的目标是最小化所有输入样本数据的代价函数。
公式5-3 希望最小化的代价函数
接下来大家会看到还有一些其他种类的代价函数,如均方误差,这些通常已经在神经网络框架中定义好了,大家无须自己去决定哪些是最好的代价函数。需要牢记的是,最终目标是将数据集上的代价函数最小化,这样此处给出的其他概念才有意义。
辛顿(Hinton)和他的同事提出一种用多层感知机同时处理一个目标的方法。这个方法可以解决线性不可分问题。通过该方法,他们可以像拟合线性函数那样去拟合非线性函数。
但是如何更新这些不同感知机的权重呢?造成误差的原因是什么?假设两个感知机彼此相邻,并接收相同的输入,无论怎样处理输出(连接、求和、相乘),当我们试图将误差传播回到初始权重的时候,它们(输出)都将是输入的函数(两边是相同的),所以它们每一步的更新量都是一样的,感知机不会有不同的结果。这里的多个感知机将是冗余的。它们的权重一样,神经网络也不会学到更多东西。
大家再来想象一下,如果将一个感知机作为第二个感知机的输入,是不是更令人困惑了,这到底是在做什么?
反向传播可以解决这个问题,但首先需要稍微调整一下感知机。记住,权重是根据它们对整体误差的贡献来更新的。但是如果权重对应的输出成为另一个感知机的输入,那么从第二个感知机开始,我们对误差的认识就变得有些模糊了。
如图5-6所示,权重w1i通过下一层的权重(w1j)和(w2j)来影响误差,因此我们需要一种方法来计算w1i对误差的贡献,这个方法就是反向传播。
图5-6 包含隐藏权重的神经网络
现在是时候停止使用“感知机”这个术语了,因为之后大家将改变每个神经元权重的更新方式。从这里开始,我们提到的神经元将更通用也更强大,它包含了感知机。在很多文献中神经元也被称为单元或节点,在大多数情况下,这些术语是可以互换的。
所有类型的神经网络都是由一组神经元和神经元之间的连接组成的。我们经常把它们组织成层级结构,不过这不是必需的。如果在神经网络的结构中,将一个神经元的输出作为另一个神经元的输入,就意味着出现了隐藏神经元或者隐藏层,而不再只是单纯的输入层、输出层。
图5-7中展示的是一个全连接网络,图中没有展示出所有的连接,在全连接网络中,每个输入元素都与下一层的各个神经元相连,每个连接都有相应的权重。因此,在一个以四维向量为输入、有5个神经元的全连接神经网络中,一共有20个权重(5个神经元各连接4个权重)。
感知机的每个输入都有一个权重,第二层神经元的权重不是分配给原始输入的,而是分配给来自第一层的各个输出。从这里我们可以看到计算第一层权重对总体误差的影响的难度。第一层权重对误差的影响并不是只来自某个单独权重,而是通过下一层中每个神经元的权重来产生的。虽然反向传播算法本身的推导和数学细节非常有趣,但超出了本书的范围,我们对此只做一个简单的概述,使大家不至于对神经网络这个黑盒一无所知。
反向传播是误差反向传播的缩写,描述了如何根据输入、输出和期望值来更新权重。传播,或者说前向传播,是指输入数据通过网络“向前”流动,并以此计算出输入对应的输出。要进行反向传播,首先需要将感知机的激活函数更改为稍微复杂一点儿的函数。
图5-7 全连接神经网络
到目前为止,大家一直在使用阶跃函数作为人工神经元的激活函数。但是接下来会发现,反向传播需要一个非线性连续可微的激活函数[11],如公式5-4中常用的sigmoid函数所示,现在每个神经元都会输出介于两个值(如0和1)之间的值:
公式5-4 sigmoid函数
为什么激活函数需是非线性的
因为需要让神经元能够模拟特征向量和目标变量之间的非线性关系。如果神经元只是将输入与权重相乘然后做加和,那么输出必然是输入的线性函数,这个模型连最简单的非线性关系都无法表示。
之前使用的神经元阈值函数是一个非线性阶跃函数。所以理论上只要有足够多的神经元就可以用来训练非线性关系模型。
这就是非线性激活函数的优势,它使神经网络可以建立非线性关系模型。一个连续可微的非线性函数,如sigmoid,可以将误差平滑地反向传播到多层神经元上,从而加速训练进程。sigmoid神经元的学习速度很快。
还有许多其他的激活函数,如双曲正切函数和修正线性单元函数,它们各有优劣,适用于不同的神经网络结构,大家将在之后的章节中学习。
为什么要求可微呢?如果能够计算出这个函数的导数,就能对函数中的各个变量求偏导数。这里的关键是“各个变量”,这样就能通过接收到的输入来更新权重!
首先用平方误差作为代价函数来计算网络的误差,如公式5-5所示[12]:
公式5-5 均方误差
然后利用微积分链式法则计算复合函数的导数,如公式5-6所示。网络本身只不过是函数的复合(点积之后的非线性激活函数)。
公式5-6 链式法则
接下来可以用这个公式计算每个神经元上激活函数的导数。通过这个方法可以计算出各个权重对最终误差的贡献,从而进行适当的调整。
如果该层是输出层,借助于可微的激活函数,权重的更新比较简单,对于第j个输出,误差的导数如下[13]:
公式5-7 误差导数
如果要更新隐藏层的权重,则会稍微复杂一点儿,如公式5-8所示:
公式5-8 前一层的导数
在公式5-7中,函数f(x)表示实际结果向量,f(x)j表示该向量第j个位置上的值,yi、yj是倒数第二层第i个节点和输出第j个节点的输出,连接这两个节点的权重为wij,误差代价函数对wij求导的结果相当于用α(学习率)乘以前一层的输出再乘以后一层代价函数的导数。公式5-8中δl表示L层第l个节点上的误差项,前一层第j个节点到L层所有的节点进行加权求和[14]。
重要的是要明确何时更新权重。在计算每一层中权重的更新时,需要依赖网络在前向传播中的当前状态。一旦计算出误差,我们就可以得到网络中各个权重的更新值,但仍然需要回到网络的起始节点才能去做更新。否则,如果在网络末端更新权重,前面计算的导数将不再是对于本输入的正确的梯度。另外,也可以将权重在每个训练样本上的变化值记录下来,其间不做任何更新,等训练结束后再一起更新,我们将在5.1.6节中讨论这项内容。
接下来将全部数据输入网络中进行训练,得到每个输入对应的误差,然后将这些误差反向传播至每个权重,根据误差的总体变化来更新每个权重。当网络处理完全部的训练数据后,误差的反向传播也随之完成,我们将这个过程称为神经网络的一个训练周期(epoch)。我们可以将数据集一遍又一遍地输入网络来优化权重。但是要注意,网络可能会对训练集过拟合,导致对训练集外部的新数据无法做出有效的预测。
在公式5-7和公式5-8中,α表示学习率。它决定了在一个训练周期或一批数据中权重中误差的修正量。通常在一个训练周期内α保持不变,但也有一些复杂的训练算法会对α进行自适应调整,以加快训练速度并确保收敛。如果α过大,很可能会矫枉过正,使下一次误差变得更大,导致权重离目标更远。如果α设置太小会使模型收敛过慢,更糟糕的是,可能会陷入误差曲面的局部极小值。
本文摘自《自然语言处理实战 利用Python理解、分析和生成文本》
在本书中,读者不仅会学习这些系统的内部工作原理,还会学习相关的理论和实践技能,并创建自己的算法或模型。基本计算机科学概念无缝地转换为方法和实践的坚实基础。从一些久经考验的经典方法(如TF-IDF)开始,再深入到NLP相关的深层神经网络,作者带领读者对于自然语言处理的核心方法开启了一段清晰的体验之旅。
语言是人类建立共识的基础。人们之间交流的不仅有事实,还有情感。通过语言,人们获得了经验领域之外的知识,并通过分享这些经验来构建理解的过程。通过本书,大家将会深入理解自然语言处理技术的原理,有朝一日可能创建出能通过语言来了解人类的系统。
页面更新:2024-05-19
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号