目录
神经网络对于输入数据进行分类和识别的能力很强。神经网络非常强大,可以轻易地使用它来完成NLP聊天机器人里的输入文本分类、文档摘要甚至小说作品生成任务。
感知机
感知机的基本思路是简单地模仿活性神经元细胞的运行原理。当电信号通过树突进入细胞核时,会逐渐积聚电荷。达到一定电位后,细胞就会被激活,然后通过轴突发出电信号。然而,树突之间具有一定的差异化。由于细胞对通过某些特定树突的信号更敏感,因此在这些通路中激活轴突所需要的信号更好。
其中有一个值得注意的关键概念,那就是决定细胞何时激活时细胞如何对输入信号进行加权。神经元会在其声明周期内的决策过程中动态调整这些权重。
感知机算法最初的羡慕是教会机器识别图像。最初的感知机是光接收器和电位器的集合体,而不是现代意义的计算机。最初的想法是取一幅图像的特征,并为每项特征赋予相应的权重,用于度量其重要性。输入图像的每项特征都是图像的一小部分。
通过将图像暴露给光接收器栅格,每个光接收器都会接收图像的一小部分。特定的光接收器所能接收的图像亮度将决定它发给相关“树突”的信号强度。
每个树突都有一个电位器形式的权值。当输入信号足够强时,它就会把信号传递给“原子核”的主体“细胞”。一旦所有电位器发出的信号达到一定程度的阈值,感知机会激活轴突,表示当前展示的图像是正向匹配。如果对于给定的图像没有激活,那就是一个负向匹配,类似于识别一个图像是不是热狗。
数字感知机
神经元里的过程本质上就是从数据集中选取一个样本,并将其展示给算法,然后让算法判断“是”或“不是”,这就是目前要做的事。我们所需要完成的第一步是确定样本的特征。选择适合的特征是机器学习中一个极具挑战性的部分。在“一般性”的机器学习问题中,如预测房价,特征可能是房屋面积、最终售价和所在地区的邮政编码。
在图像识别的实验中,特征是每个像素的强度值,每个照片接收器接收一个像素。然后为每个特征分配权重。
一般来说,把单个特征表示为
,其中i是整数。所有特征的集合表示为X,表示一个向量:
类似的,每个特征的权重表示为
,其中i对应于该权重关联的特征x的下标,所有权重可统一表示为一个向量W:
有了这些特征,只需将每个特征乘以对应的权重,然后将这些乘积求和:
这里有一个缺少的部分是是否激活神经元的阈值。一旦加权和超过某个阈值,感知机就输出1,否则就输出0.
还可以使用一个简单的阶跃函数来表示这个阈值:
认识偏置
偏置是神经元中常用的输入性,和其他输入元素一样,神经元会给偏置一个权重,该权重与其他权重用同样的方式来训练。偏置一般有两种表现形式:一种是将其表示为输入向量,例如对于n维向量的输入,在向量的开头或结尾处增加一个元素,构成一个n+1维的向量。1的位置与网络无关,只要在所有样本中保持一致即可;另一种表示形式是,首先假定存在一个偏置项,将其独立于输入之外,其对应一个独立的权重,将该权重乘以1,然后与样本输入值及其相关权重的点积进行加和。这两者实际上是一样的,只不过分别是两种常见的表现形式而已。
设置偏置权重 的原因是神经元需要对全0的输入具有弹性。网络需要学习在输入全为0的情况下输出仍然为0,但它可能做不到这一点。如果没有偏置项,神经元对初始或学习的任意权重都会输出0*权重=0。而有了偏置项之后,就不会有这个问题了。如果神经元需要学习输出0,在这种情况下,神经元可以学会减小与偏置相关的权重,使点积保持在阈值以下即可。
下图可视化的展示了生物的大脑神经元的信号与深度学习人工神经元的信号进行了类比:
用数学术语来说,感知机的输出表示为f(x):
此时的感知机并未学到任何东西,我们已经向模型输入数据并且得到输出,当然这个输出可能是错误的,因为还没有告诉感知机如何获得权重。
Python版神经元
在Python中,计算神经元的输出是很简单的,可以用numpy的dot函数将两个向量相乘:
import numpy as np
example_imput=[1,0.2,0.1,0.05,0.2]
example_weights=[0.2,0.12,0.4,0.6,0.90]
input_vector=np.array(example_imput)
weights=np.array(example_weights)
bias_weight=0.2
activation_level=np.dot(input_vector,weights)+(bias_weight*1)
print(activation_level.round(5))
接下来,假设我们选择一个简单的阈值激活函数,并选择0.5作为阈值,结果如下:
threshold=0.5
if activation_level>=threshold:
perceptron_output=1
else:
perceptron_output=0
print(perceptron_output)
对于给定的输入样本example_input和权重,这个感知机将会输出1.如果有许多example_input向量,输出将会是一个标签集合。
“课堂时间”
现在已经构建了一个基于数据进行预测的方法,它为机器学习创造了条件。到目前为止,权重都作为任意值而被忽略了。实际上,它们是整个架构的关键,现在需要一种算法,基于给定样本的预测结果来调整权重值的大小。
感知机将权重的调整看成事给定输入下预测系统正确性的一个函数,从而学习这些权重。未经训练的神经元的权重一开始是随机的!通常是从正态分布中选取趋近于零的随机值。在前面的例子中,可以看到从零开始的权重(包括偏置权重)为何会导致输出全部为零。但是通过设置微小的变化,无须提供给神经元太多的能力,神经元便能以此为依据判断结果何时为对何时为错。
然后就可以开始学过程了。通过向系统输入许多不同的样本,并根据神经元的输出是否是我们想要的结果来对权重进行微小的调整。当有足够的样本(且在正确的条件下),误差应该逐渐接近于0,系统就经过了学习。
其中最关键的一个诀窍是:每个权重都是根据它对结果误差的贡献程度来进行调整。权重越大对结果影响越大,那么该权重对给定输入的感知机输出的正确性/错误性就负有越大的责任。
假定上面的例子中输入example_input对应的结果是0:
expected_output=0
new_weights=[]
for i,x in enumerate(example_imput):
new_weights.append(weights[i]+(expected_output-perceptron_output)*x)
weights=np.array(new_weights)
#初始权重
print(example_weights)
#新的权重
print(weights)
这个处理方法将同一份训练集反复输入网络中,在适当情况下,即使对于之前没见过的数据,感知机也能做出正确的预测。
有趣的逻辑学习问题
上面用了一些随机数字做例子,我们可以把这个方法应用到一个具体问题上,来看如何通过仅向计算机展示一些标记样本来教它学会一个概念。
接下来,让计算机理解逻辑或(OR),如果一个表达式的一边或另一边为真(或两边都为真),则逻辑或语句的结果为真。这个逻辑非常简单。对于以下这个问题,我们可以手动构造所有可能的样本,每个样本由两个信号组成,其中每个信号都为真(1)或假(0):
sample_data=[[0,0],
[0,1],
[1,0],
[1,1]]
expected_results=[0,1,1,1]
activation_threshold=0.5
下面使用numpy来做向量(数组)乘法,random用来初始化权重:
from random import random
import numpy as np
weight=np.random.random(2)/1000
print(weight)
这里还需要一个偏置:
bias_weight=np.random.random()/1000
print(bias_weight)
然后将其传递到流水线中,计算得到4个样本的预测结果:
for idx,sample in enumerate(sample_data):
input_vector=np.array(sample)
activation_level=np.dot(input_vector,weight)+(bias_weight*1)
if activation_level>activation_threshold:
perceptron_output=1
else:
perceptron_output=0
print("样本:",perceptron_output)
print("预测:",expected_results[idx])
print()
可以看到,随机的权重值对这个神经元没有多大帮助,我们得到了一个正确、3个错误的预测结果。
接下来,让网络继续学习,并在每次迭代中不止是打印1和0,而是同时更新权重:
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=correct_answers+1
new_weights=[]
#通过循环来强调每个权重是由其输入(xi)更新的,如果输入数据很小或为零,则无论误差大小,该输入对该权重的影响都会很小,相反的,如果输入数据很大,则影响会很大
for i,x in enumerate(sample):
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))
通过内部循环更新权重,感知机从数据集中学习了经验。在第一次迭代后,它比随机猜测(正确率为1/4)多得到了两个正确结果(3/4)。
在第二次迭代中,它过度修正了权重,然后通过调整权重来回溯结果。当第4次迭代完成后,它完美的学习了关系,随后的迭代不再更新网络,因为每个样本的误差为0,所以不会再对权重进行调整。
这就是所谓的收敛,但一个模型的误差函数达到了最小值,或者稳定在一个值上,该模型就被称为收敛。有时候会不幸运,有时神经网络在寻找最优权值时不断波动以满足一批数据的相互关系,但无法收敛。
下一步
基本感知机有一个固有缺陷,那就是如果数据不是线性可分的,或者数据之间的关系不能用线性关系来描述,模型将无法收敛,也将不具有任何有效预测的能力,因为它无法准确的预测目标向量。
仅基于样本图像及其类别进行图像分类的学习上取得了成功,但是感知机在分类方面有严重缺陷,如果数据样本不能呢个线性可分为独立的组,那么感知机将无法学习如何对输入数据进行分类。
线性可分的数据点对感知机来说是没有问题的,比如下图中的情况:
而存在了类别交叉的数据,将导致单神经元感知机原地踏步,学习预测的结果将不比随机猜测好,表现得就像是在随机抛硬币。
感知机会用线性方程来描述数据集的特征与数据集中的目标变量之间的关系,这也就是线性回归,但是感知机无法描述非线性方程或者非线性关系。
很多数据值之间的关系不是线性的,也没有好的线性回归或线性方程能够描述这些关系。许多数据集不能用直线或者平面来线性分割。
代价函数
对感知机背后的基本思想进行扩展,就可以克服之前的那些限制,将多个感知机集合在一起,并将数据输入一个(或多个)感知机中,这个系统(神经网络)就可以学习更复杂的模式,克服了类的线性不可分的挑战,如异或问题。
如果要测量误差和感知机的预测结果与真实结果的偏离程度。测量误差是有代价函数或损失函数来完成的,代价函数量化了对于输入“问题”(x)网络应该输出的正确答案与实际输出值(y)之间的差距。损失函数则表示网络输出错误答案的次数以及错误总量。下面的公式是代价函数的一个例子,表示真实值与模型预测值之间的误差:
训练感知机或神经网络的目标是最小化所有输入样本数据的代价函数:
还有一些其他种类的代价函数,如均方误差,这些通常已经在神经网络框架中定义好了。
反向传播算法
有一种用多层感知机同时处理一个目标的方法,它可以解决线性不可分问题,通过该方法,可以像拟合线性函数那样去拟合非线性函数。
假设两个感知机彼此相邻,并接收相同的输入,无论怎样处理输出(链接、求和、相乘),当我们试图将误差传播回到初始权重的时候,它们(输出)都将是输入的函数,两边是相同的,所以它们每一步的更新量都是一样的,感知机不会有不同的结果。这里的多个感知机将是冗余的。它们的权重一样,神经网络也不会学到更多东西。
如果将一个感知机作为第二个感知机的输入,就更令人困惑。
反向传播可以解决上面这个问题,但首先要稍微调整一下感知机。权重是根据它们对整体误差的贡献来更新的,但是如果权重对应的输出成为另一个感知机的输入,那么从第二个感知机开始,我们对误差的认识就变得有些模糊了。
如下图,权重通过下一次的权重(
)和(
)来影响误差,因此我们需要一种方法来计算
对误差的贡献,这个方法就是反向传播。
从这里开始,神经元将更通用也更强大,它包含了感知机。
在很多文献中,神经元也被成为单元或节点。
所有类型的神经网络都是由一组神经元和神经元之间的连接组成的。我们经常把它们组织成层级结构,不过这不是必须的。如果在神经网络的结构中,将一个神经元的输出作为另一个神经元的输入,就意味着出现了隐藏神经元或者隐藏层,而不再只是单纯的输入层、输出层。
上图是一个全连接网络(并没有展示出素有的连接),在全连接网络中,每个输入元素都与下一层的各个神经元相连,每个连接都有相应的权重。因此,在一个以四维向量为输入、有4个神经元的全连接神经网络中,一共有20个权重(5个神经元各连接4个权重)。
感知机的每个输入都有一个权重,第二层神经元的权重不是分配给原始输入的,而是分配给来自第一层的各个输出。从这里可以看到计算第一层权重对总体误差的影响的维度。第一层权重对误差的影响并不是只来自于某个单独权重,而是通过下一层中每个神经元的权重来产生的。
反向传播是误差反向传播的缩写,描述了如何根据输入、输出和期望值来更新权重。传播(前向传播)是指输入数据通过网络“向前”流动,并以此计算出输入对应的输出。要进行反向传播,首先需要将感知机的激活函数更改为稍微复杂一些的函数。
反向传播需要一个非线性连续可微的激活函数,如下面的函数所示,现在每个神经元都会输出介于两个值(如0和1)之间的值。
为什么激活函数需要是非线性的:
因为需要让神经元能够模拟特征向量和目标向量之间的非线性关系。如果神经元只是将输入与权重相乘然后做加和,那么输出必然是输入的线性函数,这个模型连最简单的非线性关系都无法表示。
神经元阈值函数是一个非线性阶跃函数,理论上只要有足够多的神经元就可以用来训练非线性关系模型。
这就是非线性激活函数的优势,它使神经网络可以建立非线性关系模型。一个连续可微的非线性函数,如sigmoid,可以将误差平滑的反向传播到多层神经元上,从而加速训练进程。sigmoid神经元的学习速度很快。
还有许多其他的激活函数,如双曲正切函数和修正线性单元函数,它们各有优劣,适用于不同的神经网络结构。
要求可微是因为这样能够计算出这个函数的导数,就能对函数中的各个变量求偏导数。这里的关键是“各个变量”,这样就能通过接收到的输入来更新权重。
求导
首先用平方误差作为代价函数来计算网络的误差:
然后利用微积分链式法则计算复合函数的导数:
网络本身不过是函数的复合(点积之后的非线性激活函数)
接下来可以用上述公式计算每个神经元上激活函数的导数。通过这个方法可以计算出各个权重对最终误差的贡献,从而进行适当的调整。
如果该层是输出层,借助可微的激活函数,权重的更新比较简单,对于第j个输出,误差的导数如下:
如果要更新隐藏层的权重,则会稍微复杂一点:
在求导公式中,函数f(x)表示实际结果向量,表示该向量第j个位置上的值,
、
是倒数第二层第i个节点和输出第j个节点的输出,链接这两个节点的权重为
,误差代价函数对
求导的结果相当于用
(学习率)乘以前一层的输出再乘以后一层代价函数的导数。更新权重的公式中
表示L层第l个节点上的误差项,前一层第j个节点到L层所有的节点进行加权求和。
重要的是要明确何时更新权重。在计算每一层中权重的更新时,需要依赖网络在前向传播中的当前状态,一旦计算出误差,我们就可以得到网络中各个权重的更新值,但仍然需要回到网络的起始节点才能去做更新。否则,如果在网络末端更新权重,前面计算的导数将不再是对于本输入的正确的梯度。另外,也可以将权重在每个训练样本上的变化值记录下来,其间不做任何更新,等训练结束后再一起更新。
接下来将全部数据输入网络中进行训练,得到每个输入对应的误差,然后将这些误差反向传播至每个权重,根据误差的总体变化来更新每个权重。当网络处理完全部分的训练数据后,误差的反向传播也将随之完成,我们将这个过程称为神经网络的一个训练周期。我们可以将数据集一遍又一遍地输入网络来优化权重。但要注意,网络可能会对训练集过拟合,导致对训练集外部的新数据无法做出有效预测。
误差曲面
学习率决定了在一个训练周期或一批数据中权重中误差的修正量。通常在一个训练周期内学习率保持不变,但也有一些复杂的训练算法会对其进行自适应调整,以加快训练速度并确保收敛。如果学习率过大,很可能会矫枉过正,使下一次误差变得更大,导致权重离目标更远。如果学习率设置太小会使模型收敛过慢,更糟糕的是,可能会陷入误差曲面的局部最小值。
训练神经网络的目标是通过寻找最佳参数(权重)来最小化代价函数,不是针对任何单个数据的误差,是最小化所有误差的代价。
早期,均方误差是一个很常用的代价函数。给定一组输入和一组预期输出,如果把误差当做可能的权重的函数,依此绘制一张图,则图中存在一个最接近零的点,这个点就是最小值——模型在这个点上误差最小。
这个最小值将是针对给定训练样本得到最优输出的一组权重集合。它经常被表示成一个三维的碗状形状,其中两维表示权重向量,第三维表示误差,如下图:
这是个简化版本的描述,不过其表示的含义在高维空间上同样适用。
类似的,还可以针对训练集上所有输入的所有可能的权重的函数绘制出误差曲面。这里需要稍微调整一下误差函数,在给定重集合的情况下,聚合所有输入前向传播产生的误差。
这样可以得到一个具有最小值的误差曲面,它由权重集合决定,这组权重描述了一个最适合整个训练集的模型。
不同类型的误差曲面
算法在每个周期的训练中使用梯度下降算法来最小化误差,每次在某个方向上对权重的调整都可能减小下次的误差,这就是凸误差曲面。
但误差函数所在的曲面并非总是一个平滑的碗,误差曲面可能有许多坑坑洼洼,这就是非凸误差曲面。如下图:
上图仍然用二维输入来表示权重。不过这和10维、100维输入在概念上是一样的。在高维空间中,可视化已经不具有意义,所以我们只能相信数学。一旦开始使用神经网络,误差曲面的可视化就变得不再重要。
在非凸误差曲面情况下,模型优化的结果取决于权重的随机初始值,由于无法行局部最小值中走出来,当训练结束时,不同的权重初始值可能会导致模型收敛于完全不同的结果。
在更高维的空间中,网络依然会伴随着局部极小值问题。
多种梯度下降算法
把所有训练样本的误差聚合起来然后再做梯度下降的训练方法称为批量学习,一批是训练数据的一个子集。但是在批量学习中误差曲面对于整个批示静态的,如果从一个随机的起始点开始,得到的很可能是某个局部极小值,从而无法看到其他的权重值的更优解。这里有两种方法来避开这个陷阱:
第一种方法是随机梯度下降法。在随机梯度下降中,不同去查看所有的训练样本,而是在输入每个训练样本后就去更新网络权重。在这个过程中,每次都会重新排列训练样本的顺序,这样将为每个样本重新绘制误差曲面,由于每个相异的输入都可能有不同的预期答案,因此大多数样本的误差前面都不一样。对于每个样本来说,仍然使用梯度下降法来调整权重。不过不用再每个训练周期结束后聚合所有误差再做权重调整,而是针对每个样本都会去更新一次权重。其中的关键点是,每一步都在向假定的极小值前进。
使用正确的数据和超参数,在向这个波动误差曲面的各个最小值前进时,可以更容易地得到全局极小值。如果模型没有进行适当的调优,或者训练数据不一致,将导致原地踏步,模型无法收敛,也学不到任何东西。不过在实际应用中,随机梯度下降法在大多数情况下都能有效避免局部极小值。这种方法的缺点是计算速度比较慢。计算前向传播和反向传播,然后针对每个样本进行权重更新,这在本来已经很慢的计算过程的基础上又增加了很多时间开销。
第二种方法,也是更常见的方法,是小批量学习。在小批量学习中,会传入训练集的一个小的子集,并按照批量学习中的误差聚合方法对这个子集对应的误差进行聚合。然后对每个子集按批将其进行反向传播并更新权重。下一批会重复这个过程,直到训练集处理完成为止,这就重新构成了一个训练周期。这是一种折中的办法,它同时具有批量学习(快速)和随机梯度下降(具有弹性)的优点。
Keras:用Python实现神经网络
用原生Python来编写神经网络是一个有趣的尝试,还有助于理解神经网络中的各种概念。但是Python在计算速度上有一定的缺陷,不过有些Python的库可以用来提高运算速度,比如PyTorch、Theano、TensorFlow、Lasagne、Keras等。
Keras是一个高级封装器,封装了面向Python的API。API接口可以与3个不同的后端库相兼容:Theano、谷歌的TensorFlow和微软的CNTK。这几个库都在底层实现了基本的神经网络单元和高度优化的线性代数库,可以用于处理点积、以支持高效的神经网络矩阵乘法运算。
下面以简单的异或问题,看看如何利用Keras训练这个网络:
import numpy as np
#keras的基础模型类
from keras.api.models import Sequential
#Dense是神经元的全连接层
from keras.api.layers import Dense,Activation
#随机梯度下降
from keras.api.optimizers import SGD
X_train=np.array([[0,0],
[0,1],
[1,0],
[1,1]])
y_train=np.array([[0],
[1],
[1],
[0]])
model=Sequential()
#全连接隐藏层包含10个神经元
num_neurons=10
#仅在第一层使用input_dim,后面的其它层会自动计算前一次的输出形状
model.add(Dense(num_neurons,input_dim=2))
model.add(Activation('tanh'))
#输出层包含一个神经元,输出结果是二分类值(0或1)
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.summary()
model.summary()提供了网络参数及各阶段权重数(Param \#)的概览。其中:10个神经元,每个神经元有3个权重,其中两个是输入向量的权重(输入向量中的每个值对应一个权重),还有一个是偏置对应的权重,所以一共有30个权重需要学习。输出层中有10个权重,分别与第一层的10个神经元一一对应,再加上一个偏置权重,所以该层共有11个权重。
sgd=SGD(learning_rate=0.1)
model.compile(loss='binary_crossentropy',optimizer=sgd,metrics=['accuracy'])
SGD是之前导入的随机梯度下降优化器,模型用它来最小化误差或者损失。learning_rate是学习速率,与每个权重的误差的导数结合使用,数值越大模型的学习速度越快,但可能会使模型无法找到全局极小值,数值越小越精确,但会增加训练时间,并使模型更容易陷入局部最小值。损失函数本身也定义为一个参数,在这里用的是binary_crossentropy。metrics参数是训练过程中输出流的选项列表。用compile方法进行编译,此时还没开始训练模型,只对权重进行了初始化,下面的代码是随机初始状态来预测,当然得到的结果只是随机猜测:
print(model.predict(X_train))
predict方法将给出最后一层的原始输出,在这个例子中是由sigmoid函数生成的。
下面接着训练:
当网络一遍遍地学习这个小数据集时,它就“学会了”什么是异或。
在这个经过训练的模型上再次调用predict会产生更好的结果:
下面是保存这个异或模型的代码:
import h5py
model_structure=model.to_json()
with open('basic_model.json','w') as json_file:
json_file.write(model_structure)
model.save_weights('basic_weights.h5')
训练好的权重必须被单独保存,第一部分只保存网格结构。在后面重新加加载网络结构时必须对其重新实例化。
同样也有对应的方法来重新实例化模型,这样做预测时不必再去重新训练模型。模型的运行时间取决于硬件性能和模型的复杂度。
展望
随着神经网络在整个深度学习领域的广泛应用,在这些系统的细节上展开了大量的研究:
- 各种激活函数(如sigmoid、修正线性单元、双曲正切);
- 选择一个适合的学习率来调整误差的影响;
- 通过使用动量模型动态调整学习率来快速找到全局最小值;
- 使用dropout,在给定的训练路径上随机选择一则权重丢弃,防止模型过拟合;
- 权重正则化,对权重进行人为修正,避免某个权重相比于其余权重过大或过小
归一化:格式化输入
神经网络接收的输入是向量形式,无论数据内容是什么,神经网络都能执行运算,不过其中有一点需要注意,那就是输入归一化。这是许多机器学习模型的真谛。假设一个房屋分类的例子,预测在市场上的销售情况。现在只有两个数据点:卧室数量和最终售价。这个数据可以表示为一个向量。例如:以27.5万成交的两居室可以表示为:
input_vec=[2,275000]
当网络在这个数据中进行学习时,第一层中,为了与取值较大的价格平等竞争,卧室对应的权重必须快速增大。因此,常见的做法是将数据归一化。使不同样本中的每个元素都能保留有效信息。归一化也能确保每个神经元在具有相同取值范围的输入值下工作,同一个输入向量中不同元素的输入范围相同。有几种常用的归一化方法,如取值归一化、特征缩放和变异系数。最终目标是在不丢失信息的情况下,将样本中每个元素的取值范围调整为[-1,1]或[0,1]。
在NLP中可以不必过于担心这一点,因为TF-IDF、独热编码和Word2vec已经做过归一化。如果输入向量没有经过归一化,例如使用原始词频或计数时,就需要牢记这一点。