文章目录
- 一、权值初始化
- 1.2Kaiming初始化
- 1.4 十种权重初始化方法
- 2.损失函数
- 2.1损失函数初步介绍
- 2.2交叉熵损失CrossEntropyLoss
- 2.3 剩余的17种损失函数介绍
一、权值初始化
在搭建好网络模型之后,一个重要的步骤就是对网络模型中的权值进行初始化。适当的权值初始化可以加快模型的收敛,而不恰当的权值初始化可能引发梯度消失或者梯度爆炸,最终导致模型无法收敛。
1.1 梯度消失与梯度爆炸
梯度消失:如果导数小于1,那么随着网络层数的增加,梯度更新信息会朝着指数衰减的方式减少这就是梯度消失。梯度消失时,越靠近输入层的参数w越是几乎纹丝不动。
梯度爆炸:在反向传播过程中需要对激活函数进行求导,如果导数大于1,那么随着网络层数的增加,梯度更新将会朝着指数爆炸的方式增加。梯度爆炸时,越是靠近输入层的参数w越是上蹿下跳。
二者问题问题都是因为网络太深,网络权值更新不稳定造成的。本质上是因为梯度反向传播中的连乘效应(小于1连续相乘多次)。
1.2 Xavier初始化
方差一致性原则:让每一个网络层输出值的方差尽量等于1 ,主要针对饱和激活函数,如sigmoid,tanh。
我们先尝试通过手动设置均匀分布来初始化:
def initialize(self):
for m in self.modules():
# 判断这一层是否为线性层,如果为线性层则初始化权值
if isinstance(m, nn.Linear):
# 计算均匀分布的上限、下限
a = np.sqrt(6 / (self.neural_num + self.neural_num))
# 把a变换到 tanh,计算增益。观察数据输入到激活函数之后,标准差的变化
tanh_gain = nn.init.calculate_gain('tanh')
a *= tanh_gain
# 均匀分布初始化权重
nn.init.uniform_(m.weight.data, -a, a)
这里用到一个函数nn.init.calculate_gain(nonlinearity,param=None)
作用:是计算激活函数的方差变化尺度,就是输入数据的方差除以经过激活函数之后的输出数据的方差。
- nonlinearity:表示激活函数的名称,如tanh。
- param: 表示激活函数的参数,如Leaky
ReLU的negative_slop。
可以看到输出结果稳定在0.6左右:
我们再来直接调用Pytorch 提供的 Xavier 初始化方法
nn.init.xavier_uniform_(tensor, gain=1.0)
def initialize(self):
for m in self.modules():
if isinstance(m, nn.Linear):
# Xavier初始化权重
tanh_gain = nn.init.calculate_gain('tanh')
nn.init.xavier_uniform_(m.weight.data, gain=tanh_gain)
输出结果同上。
注意:2012年AlexNet出现之后,非饱和函数relu也用到了神经网络中,而Xavier初始化对于relu就不好使了,这会导致输出方差越来越大,层数多了依然会出现爆炸现象。如下图所示:
1.2Kaiming初始化
针对上述问题,何凯明等人就提出了针对Relu这种非饱和函数的Kaming初始化方法.
nn.init.kaiming_normal_(tensor, a=0, mode=‘fan_in’, nonlinearity=‘leaky_relu’)
def initialize(self):
for m in self.modules():
if isinstance(m, nn.Linear):
#Pytorch提供的初始化方法
nn.init.kaiming_normal_(m.weight.data)
输出结果表明,Relu输出梯度爆炸问题得到了解决
1.4 十种权重初始化方法
Pytorch中提供了许多权重初始化方法,可以分为下面四大类
-
针对饱和激活函数(sigmoid, tanh):Xavier均匀分布, Xavier正态分布
-
针对非饱和激活函数(relu及变种):Kaiming均匀分布, Kaiming正态分布
-
三个常用的分布初始化方法:均匀分布,正态分布,常数分布 三个特殊的矩阵初始化方法:正交矩阵初始化,单位矩阵初始化,稀疏矩阵初始化
2.损失函数
2.1损失函数初步介绍
损失函数: 衡量模型输出与真实标签的差异.而我们谈损失函数的时候,往往会有三个概念:损失函数,代价函数,目标函数.
函数名 | 定义 |
---|---|
损失函数(Loss Function) | 计算一个样本的模型输出与真实标签的差异Loss = f(y^,y) |
代价函数 (Cost Function) | 计算整个样本的模型输出与真实标签的差异,是所有样本损失函数的平均值 |
目标函数(objective Function) | 代价函数加上正则项.实际上就直接说成损失函数 |
2.2交叉熵损失CrossEntropyLoss
nn.CrossEntropyLoss
function:nn.LogSoftmax()与nn.NULLLoss()结合,进行交叉熵计算
主要参数:
weight:各类别的loss设置权限
ignore_index:忽略某个类别
reduction:计算模式,可为none/sum/mean
none:逐个元素计算
sum:返回所有元素求和,返回标量
mean:加权平均,返回标量(默认)
注意:使用nn.LogSoftmax()将概率归一化,应为交叉熵损失函数一般用在分类任务当中,而分类任务通常需要计算两个输出的概率值,所以交叉熵损失函数用来衡量两个概率分布之间的差异,交叉熵值越低,说明两个概率分布越近。
熵之间的关系
熵:用来描述整个概率分布的不确定性,熵越大,不确定性越高
信息熵 :自信息用于描述单个事件的不确定性,信息熵就是求自信息的期望
相对熵:也被称为 KL 散度,用于衡量两个分布的相似性(距离)
交叉熵 = 信息熵 + 相对熵
优化交叉熵:等价于优化相对熵
这里是引用
# 构建虚拟数据
# 这里就是模型预测的输出, 这里是两个类,可以看到模型输出是数值,我们得softmax一下转成分布
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
# 标签。这里的类型必须是long, 两个类0和1
target = torch.tensor([0, 1, 1], dtype=torch.long)
# ---------------- CrossEntropy loss: reduction ----------------
# 三种模式的损失函数
loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')
# forward
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)
reslt shown below:
路人贾的手写算法:
# --------------------------------- compute by hand
idx = 0
input_1 = inputs.detach().numpy()[idx] # [1, 2]
target_1 = target.numpy()[idx] # [0]
# 第一项
x_class = input_1[target_1]
# 第二项
sigma_exp_x = np.sum(list(map(np.exp, input_1)))
log_sigma_exp_x = np.log(sigma_exp_x)
# 输出loss
loss_1 = -x_class + log_sigma_exp_x
print("第一个样本loss为: ", loss_1)
her result:
接下来我们对每个样本进行权值缩放:
# ----------------------------------- weight -----------------------------------
# def loss function
weights = torch.tensor([1, 2], dtype=torch.float)
# weights = torch.tensor([0.7, 0.3], dtype=torch.float)
#有几个类,weight就要设几个值
loss_f_none_w = nn.CrossEntropyLoss(weight=weights, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=weights, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("\nweights: ", weights)
print(loss_none_w, loss_sum, loss_mean)
# --------------------------------- compute by hand
weights = torch.tensor([1, 2], dtype=torch.float)
weights_all = np.sum(list(map(lambda x: weights.numpy()[x], target.numpy()))) # [0, 1, 1] # [1 2 2]
mean = 0
loss_f_none = nn.CrossEntropyLoss(reduction='none')
loss_none = loss_f_none(inputs, target)
loss_sep = loss_none.detach().numpy()
for i in range(target.shape[0]):
x_class = target.numpy()[i]
tmp = loss_sep[i] * (weights.numpy()[x_class] / weights_all)
mean += tmp
print(mean)
result below:
设置的时候,target那里第一个标签为0,权重为1;后两个标签为1,权重为2。所以分母不再是3个样本,而是1+2+2, 毕竟后两个样本权为2, 一个样本顶第一个的这样的2个。
可以观察到,第一个loss没有变;第二个和第三个loss变为原来的2倍
所以mean模式下求平均不是除以样本的个数,而是样本所占的权值的总份数。
加权之后:sum直接求和,mean需要求加权平均
2.3 剩余的17种损失函数介绍
# ----------------------------------- 1 NLLLoss -----------------------------------
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.NLLLoss(weight=weights, reduction='none')
loss_f_sum = nn.NLLLoss(weight=weights, reduction='sum')
loss_f_mean = nn.NLLLoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("\nweights: ", weights)
print("NLL Loss", loss_none_w, loss_sum, loss_mean)
# ----------------------------------- 2 BCE Loss -----------------------------------
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float)
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float)
target_bce = target
# itarget 将输入值压缩到[0-1]
inputs = torch.sigmoid(inputs)
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.BCELoss(weight=weights, reduction='none')
loss_f_sum = nn.BCELoss(weight=weights, reduction='sum')
loss_f_mean = nn.BCELoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)
# view
print("\nweights: ", weights)
print("BCE Loss", loss_none_w, loss_sum, loss_mean)
…
# --------------------------------- 17 CTC Loss -----------------------------------------
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
S = 30 # Target sequence length of longest target in batch
S_min = 10 # Minimum target length, for demonstration purposes
# Initialize random batch of input vectors, for *size = (T,N,C)
inputs = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# Initialize random batch of targets (0 = blank, 1:C = classes)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(inputs, target, input_lengths, target_lengths)
print("CTC loss: ", loss)
本文参考
系统学习Pytorch笔记六:模型的权值初始化与损失函数介绍[PyTorch 学习笔记] 4.2 损失函数 - 知乎 (zhihu.com)
https://jrs0511.blog.csdn.net/article/details/129091637 路人贾