前期回顾
一、权值初始化
1.1 梯度消失与梯度爆炸
1.2 Xavier初始化
nn.init.xavier_uniform_(tensor, gain=1.0)
1.3 Kaiming初始化
nn.init.kaiming_normal_(tensor, a=0, mode=‘fan_in’, nonlinearity=‘leaky_relu’)
1.4 十种权重初始化方法
二、损失函数
2.1 损失函数初步介绍
2.2 交叉熵损失CrossEntropyLoss
nn.CrossEntropyLoss
2.3 剩余的17种损失函数介绍
(1)nn.NLLLoss
(2)nn.BCELoss
(3)nn.BCEWithLogists Loss
(4)nn.L1Loss
(5)nn.MSELoss
(6)nn.SmoothL1Loss
(7)nn.PoissonNLLLoss
(8)nn.KLDivLoss
(9)nn.MarginRankingLoss
(10)nn.MultiLabelMarginLoss
(11)nn.SoftMarginLoss
(12)nn.MultiLabelSortMarginLoss
(13)nn.MultiMarginLoss(hingLoss)
(14)nn.TripletMarginLoss
(15)nn.HingeEmbeddingLoss
(16)nn.CosineEmbeddingLoss
(17)nn.CTCLoss
总结
Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
Pytorch学习笔记(3):图像的预处理(transforms)
Pytorch学习笔记(4):模型创建(Module)、模型容器(Containers)、AlexNet构建
Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)
在搭建好网络模型之后,一个重要的步骤就是对网络模型中的权值进行初始化。适当的权值初始化可以加快模型的收敛,而不恰当的权值初始化可能引发梯度消失或者梯度爆炸,最终导致模型无法收敛。
梯度消失:如果导数小于1,那么随着网络层数的增加,梯度更新信息会朝着指数衰减的方式减少这就是梯度消失。梯度消失时,越靠近输入层的参数w越是几乎纹丝不动。
梯度爆炸:在反向传播过程中需要对激活函数进行求导,如果导数大于1,那么随着网络层数的增加,梯度更新将会朝着指数爆炸的方式增加。梯度爆炸时,越是靠近输入层的参数w越是上蹿下跳。
二者问题问题都是因为网络太深,网络权值更新不稳定造成的。本质上是因为梯度反向传播中的连乘效应(小于1连续相乘多次)。
方差一致性原则:让每一个网络层输出值的方差尽量等于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**)
作用:是计算激活函数的方差变化尺度,就是输入数据的方差除以经过激活函数之后的输出数据的方差。
可以看到输出结果稳定在0.6左右:
我们再来直接调用Pytorch 提供的 Xavier 初始化方法
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就不好使了,这会导致输出方差越来越大,层数多了依然会出现爆炸现象。如下图所示:
针对上述的问题,何恺明等大佬就提出了针对ReLU这种非饱和函数的Kaming初始化方法 。
具体代码如下:
def initialize(self):
for m in self.modules():
if isinstance(m, nn.Linear):
#Pytorch提供的初始化方法
nn.init.kaiming_normal_(m.weight.data)
# 下面这个是手动写出的,这两句话其实作用一样,不过自己写还得计算出标准差
# nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))
输出结果我们可以发现,妈妈再也不用担心使用Relu激活发生爆炸了~
Pytorch里面提供了很多权重初始化的方法,可以分为下面的四大类:
损失函数: 衡量模型输出与真实标签的差异。而我们谈损失函数的时候,往往会有三个概念: 损失函数, 代价函数, 目标函数。
功能:nn.LogSoftmax()与nn.NULLLoss()结合,进行交叉熵计算
主要参数:
注意:使用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)
输出结果:
每个样本的loss、loss之和、loss取平均如下图所示
手动计算是否准确:
# --------------------------------- 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)
输出结果:
和之前相同
接下来,我们再对每个样本进行权值的缩放
代码如下:
# ----------------------------------- 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)
输出结果:
设置的时候,target那里第一个标签为0,权重为1;后两个标签为1,权重为2。所以分母不再是3个样本,而是1+2+2, 毕竟后两个样本权为2, 一个样本顶第一个的这样的2个。
可以观察到,第一个loss没有变;第二个和第三个loss变为原来的2倍
所以mean模式下求平均不是除以样本的个数,而是样本所占的权值的总份数。
加权之后:sum直接求和,mean需要求加权平均
功能:实现负对数似然函数中的负号功能
主要参数:
具体代码如下:
# ----------------------------------- 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)
输出结果:
第一个值的标签是0,则对第一个数据的index=0的位置取负号
第二个和第三个值得标签是1,则对第二个和第三个数据的index=1的位置取负号
功能:二分类交叉熵
yn是标签,yn = 0或者yn = 1
主要参数:
注意事项:输入值取值在[0,1],需要符合概率取值
具体代码如下:
# ----------------------------------- 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)
输出结果:
每个神经元一一计算loss
BCELoss中我们发现,如果输入数据不在[0,1]区间内就会报错,针对这一问题提出了nn.BCEWithLogists Loss
功能:结合Sigmoid与二分类交叉熵
主要参数:
注意事项:网络最后不加sigmoid函数
具体代码如下:
# ---------------------------- 3 BCE with Logis 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
# inputs = torch.sigmoid(inputs)
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.BCEWithLogitsLoss(weight=weights, reduction='none')
loss_f_sum = nn.BCEWithLogitsLoss(weight=weights, reduction='sum')
loss_f_mean = nn.BCEWithLogitsLoss(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(loss_none_w, loss_sum, loss_mean)
输出结果:
更改权重:
# --------------------------------- pos weight
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
# inputs = torch.sigmoid(inputs)
weights = torch.tensor([1], dtype=torch.float)
pos_w = torch.tensor([3], dtype=torch.float) # 3
loss_f_none_w = nn.BCEWithLogitsLoss(weight=weights, reduction='none', pos_weight=pos_w)
loss_f_sum = nn.BCEWithLogitsLoss(weight=weights, reduction='sum', pos_weight=pos_w)
loss_f_mean = nn.BCEWithLogitsLoss(weight=weights, reduction='mean', pos_weight=pos_w)
# 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("\npos_weights: ", pos_w)
print(loss_none_w, loss_sum, loss_mean)
若pos_weight设置为3
则对正样本所在的神经元乘3这个系数
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float)
则对样本标签为1的位置进行3倍操作
我们来对比一下输出结果:
功能:计算input与target之差的绝对值
主要参数:
具体代码如下:
set_seed(1) # 设置随机种子
# ------------------------------------ 4 L1 loss ----------------------------------------------
inputs = torch.ones((2, 2))
target = torch.ones((2, 2)) * 3
loss_f = nn.L1Loss(reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nL1 loss:{}".format(inputs, target, loss))
输出结果:
功能:计算input与target之差的平方
主要参数:
具体代码如下:
inputs = torch.ones((2, 2))
target = torch.ones((2, 2)) * 3
loss_f = nn.L1Loss(reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nL1 loss:{}".format(inputs, target, loss))
# ---------------------- 5 MSE loss ---------------------------------------
loss_f_mse = nn.MSELoss(reduction='none')
loss_mse = loss_f_mse(inputs, target)
print("MSE loss:{}".format(loss_mse))
输出结果:
功能:创建一个标准,如果绝对元素误差低于β,则使用平方项,否则使用L1项。它对异常值的敏感度低于torch.nn.MSELoss,并且在某些情况下可以防止爆炸梯度
主要参数:
采用这种平滑的损失函数可以减轻离群点带来的影响。
具体代码如下:
# --------------------------------- 6 Smooth L1 loss --------------------------------------------
inputs = torch.linspace(-3, 3, steps=500)
target = torch.zeros_like(inputs)
loss_f = nn.SmoothL1Loss(reduction='none')
loss_smooth = loss_f(inputs, target)
loss_l1 = np.abs(inputs.numpy())
plt.plot(inputs.numpy(), loss_smooth.numpy(), label='Smooth L1 Loss')
plt.plot(inputs.numpy(), loss_l1, label='L1 loss')
plt.xlabel('x_i - y_i')
plt.ylabel('loss value')
plt.legend()
plt.grid()
plt.show()
输出:
功能:泊松分布的负对数似然损失函数, 分类里面如果发现数据的类别服从泊松分布,可以使用这个损失函数
主要参数:
具体代码如下:
# -------------------------------- 7 Poisson NLL Loss ----------------------------------------------
inputs = torch.randn((2, 2))
target = torch.randn((2, 2))
loss_f = nn.PoissonNLLLoss(log_input=True, full=False, reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nPoisson NLL loss:{}".format(inputs, target, loss))
输出:
功能:计算KLD(divergence),KL散度,相对熵
主要参数:
注意事项:需提前将输入计算log-probabilities,如通过nn.logsoftmax()
具体代码如下:
# ------------------------------ 8 KL Divergence Loss ----------------------------------------------
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]])
inputs_log = torch.log(inputs)
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)
loss_f_none = nn.KLDivLoss(reduction='none')
loss_f_mean = nn.KLDivLoss(reduction='mean')
loss_f_bs_mean = nn.KLDivLoss(reduction='batchmean')
loss_none = loss_f_none(inputs, target)
loss_mean = loss_f_mean(inputs, target)
loss_bs_mean = loss_f_bs_mean(inputs, target)
print("loss_none:\n{}\nloss_mean:\n{}\nloss_bs_mean:\n{}".format(loss_none, loss_mean, loss_bs_mean))
输出:
功能:计算两个向量之间的相似度,用于排序任务
特别说明:该方法计算两组数据之间的差异,返回一个n*n的loss矩阵,类似于相关性矩阵那种。
主要参数:
计算公式如下:
loss(x,y)=max(0,−y∗(x1−x2)+margin)
具体代码段如下:
# ----------------------------- 10 Margin Ranking Loss --------------------------------------------
x1 = torch.tensor([[1], [2], [3]], dtype=torch.float)
x2 = torch.tensor([[2], [2], [2]], dtype=torch.float)
target = torch.tensor([1, 1, -1], dtype=torch.float)
loss_f_none = nn.MarginRankingLoss(margin=0, reduction='none')
loss = loss_f_none(x1, x2, target)
print(loss)
输出:
(图片来源:翻滚的小@强)
功能:多标签边界损失函数, 这是一个多标签分类,就是一个样本可能属于多个类,和多分类任务还不一样。(多标签问题)
举例:四分类任务,样本x属于0类和3类,标签:[0,3,-1,-1],不是[1,0,0,1]
主要参数:
具体代码如下:
# ------------------------- 10 Multi Label Margin Loss ----------------------------------
x = torch.tensor([[0.1, 0.2, 0.4, 0.8]])
y = torch.tensor([[0, 3, -1, -1]], dtype=torch.long)
loss_f = nn.MultiLabelMarginLoss(reduction='none')
loss = loss_f(x, y)
print(loss)
输出:
解释:我们希望标签所在的神经元要比非标签所在的神经元的输出值要尽量的大,当这个差大于1了, 我们根据max(0, 1-差值),才发现不会有损失产生, 当这个差值小或者非标签所在的神经元比标签所在神经元大的时候,都会产生损失。 所以上面那个例子,我们想让第0个神经元的值要比第1个,第2个大一些,第3个神经元的值要比第1个,第2个大一些,这才能说明这个样本属于第0类和第3类,才是我们想要的结果。
功能:计算二分类的logistic损失
主要参数:
具体代码如下:
# -------------------------------- 11 SoftMargin Loss -----------------------------------------
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float)
loss_f = nn.SoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("SoftMargin: ", loss)
输出:
功能:SoftMarginLoss多标签版本
主要参数:
具体代码如下:
# --------------------------- 12 MultiLabel SoftMargin Loss -----------------------------------------
inputs = torch.tensor([[0.3, 0.7, 0.8]])
target = torch.tensor([[0, 1, 1]], dtype=torch.float)
loss_f = nn.MultiLabelSoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("MultiLabel SoftMargin: ", loss)
输出:
(图片来源:翻滚的小@强)
功能:计算多分类的折页损失
主要参数:
具体代码如下:
# --------------------------- 13 Multi Margin Loss ----------------------------------------
x = torch.tensor([[0.1, 0.2, 0.7], [0.2, 0.5, 0.3]])
y = torch.tensor([1, 2], dtype=torch.long)
loss_f = nn.MultiMarginLoss(reduction='none')
loss = loss_f(x, y)
print("Multi Margin Loss: ", loss)
输出:
功能:计算三元组损失,人脸验证中常用
主要参数:
三元组在做这么个事情, 我们在做人脸识别训练模型的时候,往往需要把训练集做成三元组(A, P, N), A和P是同一个人, A和N不是同一个, 然后训练我们的模型
具体代码如下:
# ------------------- 14 Triplet Margin Loss -----------------------------------------
anchor = torch.tensor([[1.]])
pos = torch.tensor([[2.]])
neg = torch.tensor([[0.5]])
loss_f = nn.TripletMarginLoss(margin=1.0, p=1)
loss = loss_f(anchor, pos, neg)
print("Triplet Margin Loss", loss)
输出:
功能:计算两个输入的相似性,常用于非线性embedding和半监督学习
主要参数:
具体代码如下:
# ---------------------------- 15 Hinge Embedding Loss -----------------------------------------
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
loss_f = nn.HingeEmbeddingLoss(margin=1, reduction='none')
loss = loss_f(inputs, target)
print("Hinge Embedding Loss", loss)
输出:
功能:采用余弦相似度计算两个输入的相似性
主要参数:
具体代码如下:
# -------------------------- 16 Cosine Embedding Loss -----------------------------------------
x1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
x2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([[1, -1]], dtype=torch.float)
loss_f = nn.CosineEmbeddingLoss(margin=0., reduction='none')
loss = loss_f(x1, x2, target)
print("Cosine Embedding Loss", loss)
功能:计算CTC损失,解决时序类数据的分类
主要参数:
具体代码如下:
# --------------------------------- 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)
输出:
分类问题
回归问题: nn.L1Loss, nn.MSELoss, nn.SmoothL1Loss
时序问题:nn.CTCLoss
人脸识别问题:nn.TripletMarginLoss
半监督Embedding问题(输入之间的相似性): nn.MarginRankingLoss, nn.HingeEmbeddingLoss, nn.CosineEmbeddingLoss
本文参考:
系统学习Pytorch笔记六:模型的权值初始化与损失函数介绍
[PyTorch 学习笔记] 4.2 损失函数 - 知乎 (zhihu.com)