基础知识点
神经网络NN(Neural Netwarks)
深度学习(Deep Learning)
神经元(Neuron)
深层神经网络(Deep Neural Networks,DNNs)
神经网络有下面三个基础层(Layer)构建而成:
-
输入层(Input): 神经网络的第一层,负责接收外部数据,不进行计算。
-
隐藏层(Hidden): 位于输入层和输出层之间,进行特征提取和转换。隐藏层一般有多层,每一层有多个神经元。
-
输出层(Output): 网络的最后一层,产生最终的预测结果或分类结果
全连接(Fully Connected,FC)神经网络
是前馈神经网络的一种,每一层的神经元与上一层的所有神经元全连接,常用于图像分类、文本分类等任务。所有相邻神经元之间通过权重和偏置连接的网络也称之为全连接神经网络。
特点
-
全连接层: 层与层之间的每个神经元都与前一层的所有神经元相连。
-
权重数量: 由于全连接的特点,权重的数量较大,容易导致计算量大、模型复杂度高。
-
学习能力: 能够学习输入数据的全局特征,但对于高维数据却不擅长捕捉局部特征。
计算步骤
-
数据传递: 输入数据经过每一层的计算,逐层传递到输出层。
-
激活函数: 每一层的输出通过激活函数处理。
-
损失计算: 在输出层计算预测值与真实值之间的差距,即损失函数值。
-
反向传播(Back Propagation): 通过反向传播算法计算损失函数对每个权重的梯度,并更新权重以最小化损失。
人工神经元(Artificial Neuron)
——组成部分:输入(Inputs)、权重(Weights)、偏置(Bias)、加权求和、激活函数(Activation Function)
-
输入(Inputs): 代表输入数据,通常用向量表示,每个输入值对应一个权重。
-
权重(Weights): 每个输入数据都有一个权重,表示该输入对最终结果的重要性。
-
偏置(Bias): 一个额外的可调参数,作用类似于线性方程中的截距,帮助调整模型的输出。
-
加权求和: 神经元将输入乘以对应的权重后求和,再加上偏置。
-
激活函数(Activation Function): 用于将加权求和后的结果转换为输出结果,引入非线性特性,使神经网络能够处理复杂的任务。常见的激活函数有Sigmoid、ReLU(Rectified Linear Unit)、Tanh等。
箭头上带有两个信息:权重w和偏置b;权重和神经元的值相乘再加上偏置,经过某个激活函数后的值作为下个神经元的输入
参数初始化
神经网络的参数初始化是训练深度学习模型的关键步骤之一。初始化参数(通常是权重和偏置)会对模型的训练速度、收敛性以及最终的性能产生重要影响。
固定值初始化
固定值初始化是指在神经网络训练开始时,将所有权重或偏置初始化为一个特定的常数值。(不推荐)
1.全零初始化
将神经网络中的所有权重参数初始化为0
方法:将所有权重初始化为零。
缺点:导致对称性破坏,每个神经元在每一层中都会执行相同的计算,模型无法学习。
应用场景:通常不用来初始化权重,但可以用来初始化偏置
import torch
import torch.nn as nn
def test():
#全0参数初始化
linear =nn.Linear(in_features=6,out_features=4)
#初始化权重参数
nn.init.zeros_(linear.weight)
print(linear.weight)
pass
if __name__=="__main__":
test()
2.全1初始化
全1初始化会导致网络中每个神经元接收到相同的输入信号,进而输出相同的值,这就无法进行学习和收敛。(不适用)
import torch
import torch.nn as nn
def test():
#全1参数初始化
linear =nn.Linear(in_features=5,out_features=3)
#初始化权重参数
nn.init.ones_(linear.weight)
print(linear.weight)
pass
if __name__=="__main__":
test()
3.任意常数初始化
不能避免对称性破坏的问题
import torch
import torch.nn as nn
def test():
#固定值参数初始化
linear =nn.Linear(in_features=5,out_features=3)
#初始化权重参数
nn.init.constant_(linear.weight,0.8)
print(linear.weight)
pass
if __name__=="__main__":
test()
随机初始化
这是最基本的初始化方法,通过随机初始化避免对称性破坏.
方法:将权重初始化为随机的小值,通常从正态分布或均匀分布中采样
import torch
import torch.nn as nn
def test():
#均匀分布随机初始化
linear=nn.Linear(in_features=2,out_features=3)
#初始化权重参数
nn.init.uniform_(linear.weight,0,0.5)
print(linear.weight)
pass
if __name__ == "__main__":
test()
import torch
import torch.nn as nn
def test():
#正态分布随机初始化
linear=nn.Linear(in_features=2,out_features=3)
#初始化权重参数
nn.init.normal_(linear.weight,mean=0,std=1)
print(linear.weight)
pass
if __name__ == "__main__":
test()
Xavier 初始化
也叫做Glorot初始化
方法:根据输入和输出神经元的数量来选择权重的初始值。权重从以下分布中采样:
其中 是当前层的输入神经元数量,是输出神经元数量。
优点:平衡了输入和输出的方差,适合Sigmoid和 Tanh激活函数。
应用场景:常用于浅层网络或使用Sigmoid 、Tanh 激活函数的网络。
import torch
import torch.nn as nn
def test():
#Xavier初始化:正态分布
linear = nn.Linear(in_features=2,out_features=3)
nn.init.xavier_normal_(linear.weight)
print(linear.weight)
#Xavier初始化:均匀分布
linear = nn.Linear(in_features=2,out_features=3)
nn.init.xavier_uniform_(linear.weight)
print(linear.weight)
pass
if __name__ == "__main__":
test()
He初始化
也叫kaiming 初始化
方法:专门为 ReLU 激活函数设计。权重从以下分布中采样:
其中是当前层的输入神经元数量。
优点:适用于ReLU和Leaky ReLU激活函数。
应用场景:深度网络,尤其是使用 ReLU 激活函数时。
import torch
import torch.nn as nn
def test():
#He初始化:正态分布
linear = nn.Linear(in_features=2,out_features=3)
nn.init.kaiming_normal_(linear.weight,nonlinearity='relu')
print(linear.weight)
#He初始化:均匀分布
linear = nn.Linear(in_features=2,out_features=3)
nn.init.kaiming_uniform_(linear.weight,nonlinearity='relu')
print(linear.weight)
pass
if __name__ == "__main__":
test()
常见的初始化方法
-
Zeros:生成初始化为0的张量的初始化器。
-
Ones:生成初始化为1的张量的初始化器。
-
Constant:生成初始化为常量值的张量的初始化器。
-
RandomNormal:生成具有正态分布的张量的初始化器。
-
RandomUniform:生成具有均匀分布的张量的初始化器。
-
TruncatedNormal:生成截断正态分布的初始化器。
-
VarianceScaling:能够根据权重的形状调整其缩放比例的初始化器。
-
Orthogonal:生成随机正交矩阵的初始化器。
-
Identity:生成单位矩阵的初始化器。
-
lecun_uniform:LeCun uniform initializer。
-
glorot_normal:Glorot normal initializer,也称为Xavier normal initializer。
-
glorot_uniform:Glorot uniform initializer,也称为Xavier uniform initializer。
-
he_normal:He normal initializer。
-
lecun_normal:LeCun normal initializer。
-
he_uniform:He uniform variance scaling initializer。
在使用Torch构建网络模型时,每个网络层的参数都有默认的初始化方法,同时还可以通过以上方法来对网络参数进行初始化
激活函数(Activation Function)
激活函数的作用是在隐藏层引入非线性,使得神经网络能够学习和表示复杂的函数关系,使网络具备非线性能力,增强其表达能力。
如果没有激活函数,神经网络的每一层都是线性变换,整个网络就相当于一个线性模型,无论网络的深度如何,都只能解决线性可分问题,无法解决复杂的非线性问题。
如何选择激活函数
隐藏层
-
优先选ReLU;
-
如果ReLU效果不咋地,那么尝试其他激活,如Leaky ReLU等;
-
使用ReLU时注意神经元死亡问题, 避免出现过多神经元死亡;
-
不使用sigmoid,尝试使用tanh;
输出层
-
二分类问题选择sigmoid激活函数;
-
多分类问题选择softmax激活函数
1.sigmoid
Sigmoid激活函数是一种常见的非线性激活函数,特别是在早期神经网络中应用广泛。它将输入映射到0到1之间的值,因此非常适合处理概率问题。
Sigmoid函数的数学表达式为:
-
将任意实数输入映射到 (0, 1)之间,因此非常适合处理概率场景。
-
sigmoid函数一般只用于二分类的输出层。
-
微分性质: 导数计算比较方便,可以用自身表达式来表示:
缺点:
-
梯度消失:
-
在输入非常大或非常小时,Sigmoid函数的梯度会变得非常小,接近于0。这导致在反向传播过程中,梯度逐渐衰减。
-
最终使得早期层的权重更新非常缓慢,进而导致训练速度变慢甚至停滞。
-
-
信息丢失:输入100和输入10000经过sigmoid的激活值几乎都是等于 1 的,但是输入的数据却相差 100 倍。
-
计算成本高: 由于涉及指数运算,Sigmoid的计算比ReLU等函数更复杂,尽管差异并不显著。
import torch
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 用来配置Python中的matplotlib库的图表显示设置,确保在显示中文字符及负号时不会出现问题
#即解决中文乱码问题
def test():
#1行2列绘制图像
_,t=plt.subplots(1,2)
#绘制图像
x=torch.linspace(0,10,100)
y=torch.sigmoid(x)
#网格
t[0].grid(True)
t[0].set_title('sigmoid')
t[0].set_xlabel('x')
t[0].set_ylabel('y')
#在第一行第一列绘制函数曲线
t[0].plot(x,y)
x=torch.linspace(0,10,100,requires_grad=True)
# y=torch.sigmoid(x)*(1-torch.sigmoid(x))
#自动求导
torch.sigmoid(x).sum().backward()
t[1].grid(True)
t[1].set_title("sigmoid 函数导数曲线图", color="red")
t[1].set_xlabel("x")
t[1].set_ylabel("y")
#用自动求导的结果绘制曲线图
t[1].plot(x.detach().numpy(), x.grad.detach().numpy())
plt.show()
pass
if __name__=="__main__":
test()
2.tanh
tanh(双曲正切)是一种常见的非线性激活函数,常用于神经网络的隐藏层。tanh 函数也是一种S形曲线,输出范围为(−1,1)。
tanh数学表达式为:
-
输出范围: 将输入映射到$$(-1, 1)$$之间,因此输出是零中心的。相比于Sigmoid函数,这种零中心化的输出有助于加速收敛。
-
对称性: Tanh函数关于原点对称,因此在输入为0时,输出也为0。这种对称性有助于在训练神经网络时使数据更平衡。
-
平滑性: Tanh函数在整个输入范围内都是连续且可微的,这使其非常适合于使用梯度下降法进行优化。
缺点:
-
梯度消失: 虽然一定程度上改善了梯度消失问题,但在输入值非常大或非常小时导数还是非常小,这在深层网络中仍然是个问题。
-
计算成本: 由于涉及指数运算,Tanh的计算成本还是略高,尽管差异不大。
import torch
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 用来配置Python中的matplotlib库的图表显示设置,确保在显示中文字符及负号时不会出现问题
def test():
#1行2列绘制图像
_,t=plt.subplots(1,2)
#绘制图像
x=torch.linspace(0,10,100)
y=torch.tanh(x)
#网格
t[0].grid(True)
t[0].set_title('tanh 函数曲线图')
t[0].set_xlabel('x')
t[0].set_ylabel('y')
#在第一行第一列绘制函数曲线
t[0].plot(x,y)
x=torch.linspace(0,10,100,requires_grad=True)
# y=torch.tanh(x)*(1-torch.tanh(x))
#自动求导
torch.sigmoid(x).sum().backward()
t[1].grid(True)
t[1].set_title("sigmoid 函数导数曲线图", color="red")
t[1].set_xlabel("x")
t[1].set_ylabel("y")
#用自动求导的结果绘制曲线图
t[1].plot(x.detach().numpy(), x.grad.detach().numpy())
plt.show()
pass
if __name__=="__main__":
test()
3.ReLU
ReLU 函数定义如下:
即ReLU对输入x进行非线性变换:
-
计算简单:ReLU 的计算非常简单,只需要对输入进行一次比较运算,这在实际应用中大大加速了神经网络的训练。
-
ReLU 函数的导数是分段函数:
-
缓解梯度消失问题:相比于 Sigmoid 和 Tanh 激活函数,ReLU 在正半区的导数恒为 1,这使得深度神经网络在训练过程中可以更好地传播梯度,不存在饱和问题。
-
稀疏激活:ReLU在输入小于等于 0 时输出为 0,这使得 ReLU 可以在神经网络中引入稀疏性(即一些神经元不被激活),这种稀疏性可以提升网络的泛化能力。
缺点:
如果某个神经元输入值是负,那么该神经元将永远不再激活,成为“死亡”神经元。随着训练的进行,网络中可能会出现大量死亡神经元,从而会降低模型的表达能力。
import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 用来配置Python中的matplotlib库的图表显示设置,确保在显示中文字符及负号时不会出现问题
def test():
x=torch.linspace(-100,10,500)
y=F.relu(x)
_,t=plt.subplots(1,2)
t[0].plot(x.numpy(),y.numpy())
#网格
t[0].grid()
t[0].set_title('relu 函数曲线图')
t[0].set_xlabel('x')
t[0].set_ylabel('y')
x=torch.linspace(-100,10,500,requires_grad=True)
F.relu(x).sum().backward()
t[1].plot(x.detach().numpy(),x.grad.detach().numpy())
t[1].grid()
t[1].set_title("relu 函数导数曲线图", color="red")
t[1].lines[0].set_color("red")
t[1].set_xlabel("x")
t[1].set_ylabel("x.grad")
plt.show()
pass
if __name__=="__main__":
test()
4.LeakyReLU
Leaky ReLU是一种对 ReLU 函数的改进,旨在解决 ReLU 的一些缺点,特别是Dying ReLU 问题。Leaky ReLU 通过在输入为负时引入一个小的负斜率来改善这一问题。
Leaky ReLU 函数的定义如下:
其中, 是一个非常小的常数(如 0.01),它控制负半轴的斜率。这个常数是一个超参数,可以在训练过程中可自行进行调整。
-
避免神经元死亡:通过在 区域引入一个小的负斜率,这样即使输入值小于等于零,Leaky ReLU仍然会有梯度,允许神经元继续更新权重,避免神经元在训练过程中完全“死亡”的问题。
-
计算简单:Leaky ReLU 的计算与 ReLU 相似,只需简单的比较和线性运算,计算开销低。
缺点:
是一个需要调整的超参数,选择合适的值可能需要实验和调优。
如果设定得不当,仍然可能导致激活值过低。
import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 用来配置Python中的matplotlib库的图表显示设置,确保在显示中文字符及负号时不会出现问题
def test():
x=torch.linspace(-100,10,500)
#设置leaky_relu
slope=0.1#斜率
y=F.leaky_relu(x,slope)
#一行两列
_,t=plt.subplots(1,2)
t[0].plot(x.numpy(),y.numpy())
#添加标题和坐标轴标签
t[0].plot(x,y)
t[0].set_title('leaky_relu 函数曲线图')
t[0].set_xlabel('x')
t[0].set_ylabel('y')
t[0].grid(True)
x=torch.linspace(-100,10,500,requires_grad=True)
F.leaky_relu(x,slope).sum().backward()
t[1].plot(x.detach().numpy(),x.grad)
t[1].set_title("leaky_relu 函数导数曲线图", color="red")
t[1].lines[0].set_color("red")
t[1].set_xlabel("x")
t[1].set_ylabel("x.grad")
t[1].grid(True)
plt.show()
pass
if __name__=="__main__":
test()
5.softmax
Softmax激活函数通常用于分类问题的输出层,它能够将网络的输出转换为概率分布,使得输出的各个类别的概率之和为 1。其本质上是一种归一化函数,可以将一组任意的实数值转化为在[0, 1]之间的概率值,因为softmax将它们转换为0到1之间的值,所以它们可以被解释为概率。如果其中一个输入很小或为负,softmax将其变为小概率,如果输入很大,则将其变为大概率,但它将始终保持在0到1之间。Softmax 特别适合用于多分类问题。
假设神经网络的输出层有n个节点,每个节点的输出为,则 Softmax 函数的定义如下:
只有当分类是互斥的,才可以在分类器中使用softmax函数,也就是说只能是多元分类(即数据只有一个标签),而不能是多标签分类(即一条数据可能有多个标签)。
import torch
def test():
x=torch.tensor([1,2])
softmax=torch.exp(x)/torch.sum(torch.exp(x))
print(softmax)#tensor([0.2689, 0.7311])
pass
if __name__=="__main__":
test()
import torch
import torch.nn as nn
import torch.nn.functional as F
# 关闭科学计数法打印
torch.set_printoptions(sci_mode=False)
def test():
y1 = torch.tensor([-1.0, 1.0, -3.0, 3000000000.0])
print(torch.max(y1))
x = torch.tensor([[-1.0, 1.0, -3.0, 300000.0], [-2, 3, -3, 9]])
y = F.softmax(x, dim=1)
print(y)
pass
if __name__ == "__main__":
test()
损失函数
-
当输出层使用softmax多分类时,使用交叉熵损失函数;
-
当输出层使用sigmoid二分类时,使用二分类交叉熵损失函数, 比如在逻辑回归中使用;
-
当功能为线性回归时,使用smooth L1损失函数或均方差损失-L2 loss
线性回归损失函数
1.MAE损失
MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异
-
n是样本的总数。
-
是第 i 个样本的真实值。
-
是第i个样本的预测值。
-
是真实值和预测值之间的绝对误差。
特点:
1. 鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。
2. 物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。
应用场景:MAE通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。
import torch
import torch.nn as nn
#初始化MAE损失函数
mae_loss = nn.L1Loss()
y_true=torch.tensor([1.0,2.0,3.0,4.0,5.0])
y_pred=torch.tensor([2.0,3.0,4.0,5.0,6.0])
loss=mae_loss(y_true,y_pred)
print(loss,loss.item())#tensor(1.) 1.0
2.MSE损失
MSE(Mean Squared Error,均方误差)通常也被称为 L2-Loss, 通过对预测值和真实值之间的误差平方取平均值,来衡量预测值与真实值之间的差异。
- n是样本的总数。
- 是第 i个样本的真实值。
- 是第 i个样本的预测值。
- 是真实值和预测值之间的误差平方。
特点:
1. 平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。
2. 凸性:MSE 是一个凸函数,这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。
import torch
import torch.nn as nn
#初始化MSE损失函数
mse_loss = nn.MSELoss()
y_true=torch.tensor([1.0,2.0,3.0,4.0,5.0])
y_pred=torch.tensor([2.0,3.0,4.0,5.0,5.5])
loss=mse_loss(y_true,y_pred)
print(loss,loss.item())#tensor(0.8500) 0.8500000238418579
3.SmoothL1Loss
SmoothL1Loss可以做到在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失。
x 表示预测值和真实值之间的误差,即。
所有样本的平均损失为:
特点:
1. 平滑过渡:当误差较小时,损失函数表现为 L2 Loss(平方惩罚);当误差较大时,损失函数逐渐向 L1 Loss过渡。这种平滑过渡既能对大误差有所控制,又不会对异常值过度敏感。
2. 稳健性:对于异常值更加稳健,同时在小误差范围内提供了较好的优化效果。
import torch
import torch.nn as nn
#创建模型的预测值和真实值
predictions = torch.Tensor([1.0,2.0,3.0])
targets=torch.Tensor([1.8,2.0,3.2])
#计算损失方式1
smooth_loss=nn.SmoothL1Loss()
loss1=smooth_loss(predictions,targets)
#计算损失方式2
loss2=nn.functional.smooth_l1_loss(predictions,targets)
print(loss1,loss2)#tensor(0.1133) tensor(0.1133)
交叉熵损失函数 CrossEntropyLoss
使用在输出层使用softmax激活函数进行多分类时,一般都采用交叉熵损失函数。
- C 是类别的总数。
- y是真实标签的one-hot编码向量,表示真实类别。
- 是模型的输出(经过 softmax 后的概率分布)。
- 是真实类别的第 i 个元素(0 或 1)。
- 是预测的类别概率分布中对应类别 i 的概率。
特点:
1. 概率输出:CrossEntropyLoss 通常与 softmax 函数一起使用,使得模型的输出表示为一个概率分布(即所有类别的概率和为 1)。
2. 惩罚错误分类:该损失函数在真实类别的预测概率较低时,会施加较大的惩罚,这样模型在训练时更注重提升正确类别的预测概率。
3. 多分类问题中的标准选择:在大多数多分类问题中,CrossEntropyLoss 是首选的损失函数。
应用场景:
CrossEntropyLoss 广泛应用于各种分类任务,包括图像分类、文本分类等,尤其是在神经网络模型中。
import torch
import torch.nn as nn
# 假设有三个类别,模型输出是未经softmax的logits
logits = torch.tensor([[1.5, 2.0, 0.5], [0.5, 1.0, 1.5]])
# 真实的标签
labels = torch.tensor([1, 2]) # 第一个样本的真实类别为1,第二个样本的真实类别为2
# 初始化CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
# 计算损失
loss = criterion(logits, labels)
print(loss,loss.item())#tensor(0.6422) 0.6422001123428345
在这个例子中,CrossEntropyLoss 直接作用于未经 softmax 处理的 logits 输出和真实标签,PyTorch 内部会自动应用 softmax 激活函数,并计算交叉熵损失。
二分类交叉熵损失函数 BCELoss
CrossEntropyLoss 的简化版本,使用在输出层使用sigmoid激活函数进行二分类时。
log的底数一般默认为e,y是真实类别目标,根据公式可知L是一个分段函数 :
以上损失函数是一个样本的损失值,总样本的损失值是求损失均值即可。
import torch
import torch.nn as nn
# y 是模型的输出,已经被sigmoid处理过,确保其值域在(0,1)
y = torch.tensor([[0.7], [0.2], [0.9], [0.7]])
# targets 是真实的标签,0或1
t = torch.tensor([[1], [0], [1], [0]], dtype=torch.float)
# 计算损失方式一:
bceLoss = nn.BCELoss()
loss1 = bceLoss(y, t)
#计算损失方式二: 两种方式结果相同
loss2 = nn.functional.binary_cross_entropy(y, t)
print(loss1, loss2)#tensor(0.4723) tensor(0.4723)