一、人工神经元
1.1 构建人工神经元
人工神经元接受多个输入信息,对它们进行加权求和,再经过激活函数处理,最后将这个结果输出。
1.2 组成部分
- 输入(Inputs): 代表输入数据,通常用向量表示,每个输入值对应一个权重。
- 权重(Weights): 每个输入数据都有一个权重,表示该输入对最终结果的重要性。
- 偏置(Bias): 一个额外的可调参数,作用类似于线性方程中的截距,帮助调整模型的输出。
- 加权求和: 神经元将输入乘以对应的权重后求和,再加上偏置。
- 激活函数(Activation Function): 用于将加权求和后的结果转换为输出结果,引入非线性特性,使神经网络能够处理复杂的任务。常见的激活函数有Sigmoid、ReLU(Rectified Linear Unit)、Tanh等。
1.3 数学表示
如果有 n 个输入 x1,x2,x3,....,xn,权重分别为w1,w2,w3,.....,w4,偏置为b,则神经元的输出y表示为:
其中是激活函数。
二、深入神经网络
2.1 基本结构
神经网络有下面三个基础层(Layer)构建而成:
- 输入层(Input): 神经网络的第一层,负责接收外部数据,不进行计算。
- 隐藏层(Hidden): 位于输入层和输出层之间,进行特征提取和转换。隐藏层一般有多层,每一层有多个神经元。
- 输出层(Output): 网络的最后一层,产生最终的预测结果或分类结果
2.2 网络构建
我们使用多个神经元来构建神经网络,相邻层之间的神经元相互连接,并给每一个连接分配一个权重,经典如下:
2.3 全连接神经网络
全连接(Fully Connected,FC)神经网络是前馈神经网络的一种,每一层的神经元与上一层的所有神经元全连接,常用于图像分类、文本分类等任务。
2.3.1 特点
- 全连接层: 层与层之间的每个神经元都与前一层的所有神经元相连。
- 权重数量: 由于全连接的特点,权重数量较大,容易导致计算量大、模型复杂度高。
- 学习能力: 能够学习输入数据的全局特征,但对于高维数据却不擅长捕捉局部特征(如图像就需要CNN)。
2.3.2 计算步骤
-
数据传递: 输入数据经过每一层的计算,逐层传递到输出层。
-
激活函数: 每一层的输出通过激活函数处理。
-
损失计算: 在输出层计算预测值与真实值之间的差距,即损失函数值。
-
反向传播(Back Propagation): 通过反向传播算法计算损失函数对每个权重的梯度,并更新权重以最小化损失。
三、参数初始化
3.1 固定值初始化
固定值初始化是指在神经网络训练开始时,将所有权重或偏置初始化为一个特定的常数值。这种初始化方法虽然简单,但在实际深度学习应用中通常并不推荐。
3.1.1 全零初始化
将神经网络中的所有权重参数初始化为0。
方法:将所有权重初始化为零。
缺点:导致对称性破坏,每个神经元在每一层中都会执行相同的计算,模型无法学习。
应用场景:通常不用来初始化权重,但可以用来初始化偏置。
# 全0参数初始化
linear = nn.Linear(in_features=6, out_features=4)
# 初始化权重参数
nn.init.zeros_(linear.weight)# 结果
# Parameter containing:
# tensor([[0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0.]], requires_grad=True)
3.1.2 全1初始化
全1初始化会导致网络中每个神经元接收到相同的输入信号,进而输出相同的值,这就无法进行学习和收敛。所以全1初始化只是一个理论上的初始化方法,但在实际神经网络的训练中并不适用。
# 全1参数初始化
linear = nn.Linear(in_features=6, out_features=4)
# 初始化权重参数
nn.init.ones_(linear.weight)# 结果
# Parameter containing:
# tensor([[1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1.]], requires_grad=True)
3.1.3 任意常数初始化
将所有参数初始化为某个非零的常数(如 0.1,-1 等)。虽然不同于全0和全1,但这种方法依然不能避免对称性破坏的问题。
# 固定值参数初始化
linear = nn.Linear(in_features=6, out_features=4)
# 初始化权重参数
nn.init.constant_(linear.weight, 0.63)# 结果
# Parameter containing:
# tensor([[0.6300, 0.6300, 0.6300, 0.6300, 0.6300, 0.6300],[0.6300, 0.6300, 0.6300, 0.6300, 0.6300, 0.6300],[0.6300, 0.6300, 0.6300, 0.6300, 0.6300, 0.6300],[0.6300, 0.6300, 0.6300, 0.6300, 0.6300, 0.6300]], requires_grad=True)
3.2 随机初始化
方法:将权重初始化为随机的小值,通常从正态分布或均匀分布中采样。
应用场景:这是最基本的初始化方法,通过随机初始化避免对称性破坏。
代码演示:随机分布之均匀初始化
# 均匀分布随机初始化
linear = nn.Linear(in_features=6, out_features=4)
# 初始化权重参数
nn.init.uniform_(linear.weight)# 结果
# Parameter containing:
# tensor([[0.4080, 0.7444, 0.7616, 0.0565, 0.2589, 0.0562],[0.1485, 0.9544, 0.3323, 0.9802, 0.1847, 0.6254],[0.6256, 0.2047, 0.5049, 0.3547, 0.9279, 0.8045],[0.1994, 0.7670, 0.8306, 0.1364, 0.4395, 0.0412]], requires_grad=True)
代码演示: 正态分布初始化
# 正太分布初始化
linear = nn.Linear(in_features=6, out_features=4)
# 初始化权重参数
nn.init.normal_(linear.weight, mean=0, std=1)# 结果
# Parameter containing:
# tensor([[ 1.5321, 0.2394, 0.0622, 0.4482, 0.0757, -0.6056],[ 1.0632, 1.8069, 1.1189, 0.2448, 0.8095, -0.3486],[-0.8975, 1.8253, -0.9931, 0.7488, 0.2736, -1.3892],[-0.3752, 0.0500, -0.1723, -0.4370, -1.5334, -0.5393]], requires_grad=True)
3.3 Xavier 初始化
也叫做Glorot初始化。
方法:根据输入和输出神经元的数量来选择权重的初始值。权重从以下分布中采样:
或者
优点:平衡了输入和输出的方差,适合 Sigmoid 和 Tanh 激活函数。
应用场景:常用于浅层网络或使用 Sigmoid 、Tanh 激活函数的网络。
代码演示:
import torch
import torch.nn as nndef test007():# Xavier初始化:正态分布linear = nn.Linear(in_features=6, out_features=4)nn.init.xavier_normal_(linear.weight)print(linear.weight)# Xavier初始化:均匀分布linear = nn.Linear(in_features=6, out_features=4)nn.init.xavier_uniform_(linear.weight)print(linear.weight)if __name__ == "__main__":test007()# 结果
Parameter containing:
tensor([[-0.4838, 0.4121, -0.3171, -0.2214, -0.8666, -0.4340],[ 0.1059, 0.6740, -0.1025, -0.1006, 0.5757, -0.1117],[ 0.7467, -0.0554, -0.5593, -0.1513, -0.5867, -0.1564],[-0.1058, 0.5266, 0.0243, -0.5646, -0.4982, -0.1844]], requires_grad=True)Parameter containing:
tensor([[-0.5263, 0.3455, 0.6449, 0.2807, -0.3698, -0.6890],[ 0.1578, -0.3161, -0.1910, -0.4318, -0.5760, 0.3746],[ 0.2017, -0.6320, -0.4060, 0.3903, 0.3103, -0.5881],[ 0.6212, 0.3077, 0.0783, -0.6187, 0.3109, -0.6060]], requires_grad=True)
3.4 He初始化
也叫kaiming 初始化。
方法:专门为 ReLU 激活函数设计。权重从以下分布中采样:
优点:适用于 ReLU 和 Leaky ReLU 激活函数。
应用场景:深度网络,尤其是使用 ReLU 激活函数时。
代码演示:
import torch
import torch.nn as nndef test006():# He初始化:正态分布linear = nn.Linear(in_features=6, out_features=4)nn.init.kaiming_normal_(linear.weight, nonlinearity="relu")print(linear.weight)# He初始化:均匀分布linear = nn.Linear(in_features=6, out_features=4)nn.init.kaiming_uniform_(linear.weight, nonlinearity="relu")print(linear.weight)if __name__ == "__main__":test006()# 结果
Parameter containing:
tensor([[ 1.4020, 0.2030, 0.3585, -0.7419, 0.6077, 0.0178],[-0.2860, -1.2135, 0.0773, -0.3750, -0.5725, 0.9756],[ 0.2938, -0.6159, -1.1721, 0.2093, 0.4212, 0.9079],[ 0.2050, 0.3866, -0.3129, -0.3009, -0.6659, -0.2261]], requires_grad=True)Parameter containing:
tensor([[-0.1924, -0.6155, -0.7438, -0.2796, -0.1671, -0.2979],[ 0.7609, 0.9836, -0.0961, 0.7139, -0.8044, -0.3827],[ 0.1416, 0.6636, 0.9539, 0.4735, -0.2384, -0.1330],[ 0.7254, -0.4056, -0.7621, -0.6139, -0.6093, -0.2577]], requires_grad=True)
四、激活函数
激活函数的作用是在隐藏层引入非线性,使得神经网络能够学习和表示复杂的函数关系,使网络具备非线性能力,增强其表达能力。
4.1 sigmoid
Sigmoid激活函数是一种常见的非线性激活函数,特别是在早期神经网络中应用广泛。它将输入映射到0到1之间的值,因此非常适合处理概率问题。
4.1.1 公式
Sigmoid函数的数学表达式为:
其中,e 是自然常数(约等于2.718),x 是输入。
4.1.2 特征
- 将任意实数输入映射到 (0, 1)之间,因此非常适合处理概率场景。
- sigmoid函数一般只用于二分类的输出层。
- 微分性质: 导数计算比较方便,可以用自身表达式来表示:
4.1.3 缺点
- 梯度消失:
- 在输入非常大或非常小时,Sigmoid函数的梯度会变得非常小,接近于0。这导致在反向传播过程中,梯度逐渐衰减。
- 最终使得早期层的权重更新非常缓慢,进而导致训练速度变慢甚至停滞。
- 信息丢失:输入100和输入10000经过sigmoid的激活值几乎都是等于 1 的,但是输入的数据却相差 100 倍。
- 计算成本高: 由于涉及指数运算,Sigmoid的计算比ReLU等函数更复杂,尽管差异并不显著。
4.2 tanh
tanh(双曲正切)是一种常见的非线性激活函数,常用于神经网络的隐藏层。tanh 函数也是一种S形曲线,输出范围为(−1,1)。
4.2.1 公式
tanh数学表达式为:
4.2.2 特征
- 输出范围: 将输入映射到(-1, 1)之间,因此输出是零中心的。相比于Sigmoid函数,这种零中心化的输出有助于加速收敛。
- 对称性: Tanh函数关于原点对称,因此在输入为0时,输出也为0。这种对称性有助于在训练神经网络时使数据更平衡。
- 平滑性: Tanh函数在整个输入范围内都是连续且可微的,这使其非常适合于使用梯度下降法进行优化。
4.2.3 缺点
- 梯度消失: 虽然一定程度上改善了梯度消失问题,但在输入值非常大或非常小时导数还是非常小,这在深层网络中仍然是个问题。
- 计算成本: 由于涉及指数运算,Tanh的计算成本还是略高,尽管差异不大。
4.3 ReLU
ReLU(Rectified Linear Unit)是深度学习中最常用的激活函数之一,它的全称是修正线性单元。ReLU 激活函数的定义非常简单,但在实践中效果非常好。
4.3.1 公式
ReLU 函数定义如下:
即ReLU对输入x进行非线性变换:
4.3.2 特征
- 计算简单:ReLU 的计算非常简单,只需要对输入进行一次比较运算,这在实际应用中大大加速了神经网络的训练。
- ReLU 函数的导数是分段函数:
- 缓解梯度消失问题:相比于 Sigmoid 和 Tanh 激活函数,ReLU 在正半区的导数恒为 1,这使得深度神经网络在训练过程中可以更好地传播梯度,不存在饱和问题。
- 稀疏激活:ReLU在输入小于等于 0 时输出为 0,这使得 ReLU 可以在神经网络中引入稀疏性(即一些神经元不被激活),这种稀疏性可以提升网络的泛化能力。
4.3.3 缺点
神经元死亡:由于ReLU在x≤0时输出为0,如果某个神经元输入值是负,那么该神经元将永远不再激活,成为“死亡”神经元。随着训练的进行,网络中可能会出现大量死亡神经元,从而会降低模型的表达能力。
4.4 LeakyReLU
Leaky ReLU是一种对 ReLU 函数的改进,旨在解决 ReLU 的一些缺点,特别是Dying ReLU 问题。Leaky ReLU 通过在输入为负时引入一个小的负斜率来改善这一问题。
4.4.1 公式
Leaky ReLU 函数的定义如下:
其中, 是一个非常小的常数(如 0.01),它控制负半轴的斜率。这个常数 是一个超参数,可以在训练过程中可自行进行调整。
4.4.2 特征
- 避免神经元死亡:通过在 x<=0 区域引入一个小的负斜率,这样即使输入值小于等于零,Leaky ReLU仍然会有梯度,允许神经元继续更新权重,避免神经元在训练过程中完全“死亡”的问题。
- 计算简单:Leaky ReLU 的计算与 ReLU 相似,只需简单的比较和线性运算,计算开销低。
4.4.3 缺点
- 参数选择: 是一个需要调整的超参数,选择合适的 值可能需要实验和调优。
- 出现负激活:如果 设定得不当,仍然可能导致激活值过低。
4.5 softmax
Softmax激活函数通常用于分类问题的输出层,它能够将网络的输出转换为概率分布,使得输出的各个类别的概率之和为 1。Softmax 特别适合用于多分类问题。
4.5.1 公式
假设神经网络的输出层有n个节点,每个节点的输出为,则 Softmax 函数的定义如下:
4.5.2 特征
-
将输出转化为概率:通过Softmax,可以将网络的原始输出转化为各个类别的概率,从而可以根据这些概率进行分类决策。
-
概率分布:Softmax的输出是一个概率分布,即每个输出值$$\text{Softmax}(z_i)$$都是一个介于0和1之间的数,并且所有输出值的和为 1:
-
突出差异:Softmax会放大差异,使得概率最大的类别的输出值更接近1,而其他类别更接近0。
-
在实际应用中,Softmax常与交叉熵损失函数Cross-Entropy Loss结合使用,用于多分类问题。在反向传播中,Softmax的导数计算是必需的。
4.5.3 缺点
1、数值不稳定性:在计算过程中,如果的数值过大,可能会导致数值溢出。因此在实际应用中,经常会对进行调整,如减去最大值以确保数值稳定。
解释:
是一个非正数,那么的值就位于0到1之间,有效避免了数值溢出。
这中调整不会改变Softmax的概率分布结果,因为从数学的角度讲相当于分子、分母都除以了。
2、难以处理大量类别:Softmax在处理类别数非常多的情况下(如大模型中的词汇表)计算开销会较大。
如何选择
隐藏层
- 优先选ReLU;
- 如果ReLU效果不咋地,那么尝试其他激活,如Leaky ReLU等;
- 使用ReLU时注意神经元死亡问题, 避免出现过多神经元死亡;
- 不使用sigmoid,尝试使用tanh;
输出层
- 二分类问题选择sigmoid激活函数;
- 多分类问题选择softmax激活函数;
- 回归问题选择identity激活函数;