建议点赞收藏关注!持续更新至pytorch大部分内容更完。
本文已达到10w字,故按模块拆开,详见目录导航。
本节目录
- 损失函数
-
- 创建损失函数 (共18个)
-
- nn.CrossEntropyLoss
- nn.NLLLoss
- BCE Loss
- BCEwithLogitsLoss
- nn.L1Loss
- nn.MSELoss
- nn.SmoothL1Loss
- nn.PoissonNLLLoss
- nn.KLDivLoss
- nn.MarginRankingLoss
- nn.MultiLabelMarginLoss
- nn.SoftMarginLoss
- nn.MultiLabelSoftMarginLoss
- nn.MultiMarginLoss
- nn.TripletMarginLoss
- nn.HingeEmbeddingLoss
- nn.CosineEmbeddingLoss
- nn.CTCLoss
- 设置损失函数超参数
- 选择损失函数
- 优化器(共10个)
- 优化算法
- 迭代训练
- 模型应用
损失函数
损失函数loss,代价函数cost,目标函数objective
交叉熵=信息熵+相对熵
(1)熵用来描述一个事件的不确定性,不确定性越大则熵越大.
(2)信息熵是自信息的期望,自信息用于衡量单个输出单个事件的不确定性.
(3)相对熵又叫做KL散度,用于衡量两个分布之间的差异,即两个分布之间的距离,但不是距离的函数,不具备对称性。
下图公式中都是,P是真实的分布,Q是模型输出的分布,Q需要逼近、拟合P的分布.
(4)交叉熵用于衡量两个分布之间的相似度.
(5)P是真实的分布,即训练集中样本的分布。由于训练集是固定的,概率分布也是固定的,H§是个常数.故优化交叉熵等价于优化相对熵。
上图是一个伯努利分布,可以发现当概率是0.5时熵最大(为0.69),0.69这个数字经常会碰到:模型训练坏了、模型的训练第一个iteration,此时不具备判断能力,因为对于任何输出判别的概率都是1/2。
创建损失函数 (共18个)
nn.CrossEntropyLoss
nn.CrossEntropyLoss(weight=None, ignore_index=- 100, reduction='mean' )
'''
size_average=None, reduce=None,这俩个参数已废弃
weight:各类别loss设置权值
ignore_index:忽略某个类别而不计算它的loss
reduction:计算模式,可为none/sum/mean
none 逐个元素计算这么多个loss
sum 所有元素求和,返回标量
mean 加权平均,返回标量
'''
交叉熵这里的不是按照书中公式所计算的,这里其实是nn.LogSoftmax()与nn.NLLLoss()结合进行计算的。它采用softmax对数据进行了归一化,把数据值归一化到概率输出的形式。交叉熵损失函数常常用于分类任务中。交叉熵是衡量两个概率分布之间的差异,交叉熵值越低,表示两个概率分布越近。
运用softmax可以将输出值转换归一化到0~1之间作为概率。
首先上图 这里x是输出的一个概率值,class是一个类别值。
第二式中的(分式)就是softmax的公式,对比第一式即交叉熵公式,可知 这个xi是已经取到的,所以p(xi)=1,并且第二式只是计算其中一个的交叉熵,故也没有N项求和符号。
当调用这个方法时传入了weight参数,则计算公式为第三式
# fake data
#二分类任务,输出神经元2个,batchsize是3,即三个样本:[1,2] [1,3] [1,3]
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
#dtype必须是long,有多少个样本,tensor(1D)就有多长。而且这里的类别label也是从0开始标号的
target = torch.tensor([0, 1, 1], dtype=torch.long)
# ----------------------------------- CrossEntropy loss: reduction -----------------------------------
flag = 0
# flag = 1
if flag:
# def loss function
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)
#输出为[1.3133, 0.1269, 0.1269] 1.5671 0.5224
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
input_1 = inputs.detach().numpy()[idx] # [1, 2]两个神经元
target_1 = target.numpy()[idx] # [0]
# 第一项
x_class = input_1[target_1]
# 第二项 map指对其每一个神经元进行运算
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 -----------------------------------
flag = 0
# flag = 1
if flag:
# def loss function
'''
有多少个类别weights这个向量就要设置多长,
每一个类别都必须设置weight,
如果不想关注这个weight,就将其设置为1就不会变的。
如果类别是mean,则每个weight数值多大不重要,重要在于他们之间的比例。
结果可以看出来,原先得到的loss基础上,如果数据label==0则用weight[0]=1倍,故与之前相等。而label==1的后两个数据的loss变成了之前的2倍(因为weight==2)
在sum模式下,weight==2也代表label==1的这一个数据就占两份1在分母中,在本例中为 三个数据loss之和/(1+2+2)
'''
weights = torch.tensor([1, 2], dtype=torch.float)
# weights = torch.tensor([0.7, 0.3], dtype=torch.float)
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)
#权值为[1,2],则输出[1.3133, 0.2539, 0.2539] 1.8210 0.3642
# target=[0,1,1]所以对应0的需要乘以权值1,对应1的需要乘以权值2.
# 1.3133是1.3133*1=1.3133,0.2530是0.1269*2=0.2539,0.2530是0.1269*2=0.2539.
#0.3642是1.8210/(1+2+2)=0.3642,分母是权值的份数(5).
# --------------------------------- compute by hand :手动计算
flag = 0
# flag = 1
if flag:
weights = torch.tensor([1, 2], dtype=torch.float)
#weights_all=5 计算sum公式中除掉的分母,份数
weights_all = np.sum(list(map(lambda x: weights.numpy()[x], target.numpy()))) # [0, 1, 1] ---> # [1 2 2]
mean = 0
loss_sep = loss_none.detach().numpy()
for i in range(target.shape[0]):
#输出每一个样本在这平均值loss中贡献的数目
x_class = target.numpy()[i]
tmp = loss_sep[i] * (weights.numpy()[x_class] / weights_all)
mean += tmp
print(mean)
nn.NLLLoss
nn.NLLLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='mean')
'''
实现负对数似然函数中的负号功能,简单来说就是取负号
'''
# fake data
#二分类任务,输出神经元2个,batchsize是3,即三个样本:[1,2] [1,3] [1,3]
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
#dtype必须是long,有多少个样本,tensor(1D)就有多长。
target = torch.tensor([0, 1, 1], dtype=torch.long)
# ----------------------------------- 2 NLLLoss -----------------------------------
flag = 0
# flag = 1
if flag:
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("NLL Loss", loss_none_w, loss_sum, loss_mean)
#输出[-1,-3,-3] -7 -2.3333
# target[0, 1, 1]
#-1是因为第一个样本[1,2]是第0类,因此只对第一个神经元输出操作:-1*1。
#-3是因为第二个样本[1,3]是第1类,因此只对第二个神经元输出操作:-1*3。
#-3是因为第三个样本[1,3]是第1类,因此只对第二个神经元输出操作:-1*3。
#求mean时的分母是权重weight的和。
BCE Loss
xn是模型输出的概率取值,yn是标签(二分类中对应0或1)。
nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
'''
二分类交叉熵损失函数
注意:输入值取值在[0,1],利用sigmoid函数可实现
'''
# ----------------------------------- 3 BCE Loss -----------------------------------
flag = 0
# flag = 1
if flag:
#同样是二分类,每个样本有两个神经元
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float) #4个样本
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float) #float类型,每个神经元一一对应去计算loss,而不是之前是整个向量去计算loss
target_bce = target
#一定要加上sigmoid,将输入值取值在[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)
#输出[[0.3133, 2.1269], [0.1269, 2.1269], [3.0486, 0.0181], [4.0181, 0.0067]] 11.7856 1.4732
#每个神经元一一对应去计算loss,因此loss个数是2*4=8个
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
x_i = inputs.detach().numpy()[idx, idx]
y_i = target.numpy()[idx, idx] #
# loss
# l_i = -[ y_i * np.log(x_i) + (1-y_i) * np.log(1-y_i) ] # np.log(0) = nan
l_i = -y_i * np.log(x_i) if y_i else -(1-y_i) * np.log(1-x_i)
# 输出loss
print("BCE inputs: ", inputs)
print("第一个loss为: ", l_i) #0.3133
BCEwithLogitsLoss
σ是sigmoid函数
nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
'''
结合sigmoid与二分类交叉熵
注意:网络最后不要加sigmoid函数,再加这个值就不准确了
这个损失函数存在的原因:有的时候不希望模型里有sigmoid在最后一层。
多了一个参数pos_weight 正样本权值
(1)pos_weight用于均衡正负样本,正样本的loss需要乘以pos_weight。
比如正样本100个,负样本300个,此时可以将pos_weight设置为3,就可以实现正负样本均衡。
(2)pos_weight里是一个tensor列表,需要和标签个数相同,比如现在有一个多标签分类,类别有200个,那么 pos_weight 就是为每个类别赋予的权重值,长度为200
(3)如果现在是二分类,只需要将正样本loss的权重写上即可,比如我们有正负两类样本,正样本数量为100个,负样本为400个,我们想要对正负样本的loss进行加权处理,将正样本的loss权重放大4倍,通过这样的方式缓解样本不均衡问题:
'''
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([4]))
# ----------------------------------- 4 BCE with Logis Loss -----------------------------------
# flag = 0
flag = 1
if flag:
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) 不能加sigmoid!!!
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)
#输出[[0.3133, 2.1269], [0.1269, 2.1269], [3.0486, 0.0181], [4.0181, 0.0067]] 11.7856 1.4732
# --------------------------------- pos weight
# flag = 0
flag = 1
if flag:
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([1], dtype=torch.float) #设置为1时,loss输出与原先一致。
pos_w = torch.tensor([3], dtype=torch