【AI】Pytorch_损失函数&优化器

建议点赞收藏关注!持续更新至pytorch大部分内容更完。
本文已达到10w字,故按模块拆开,详见目录导航。

整体框架如下
数据及预处理
模型及其构建
损失函数及优化器


损失函数

损失函数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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七灵微

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值