Pytorch学习-损失函数和正则化

损失函数

说明

一般来说,监督学习的目标函数由损失函数和正则化项组成。(Objective = Loss + Regularization)

Pytorch中的损失函数一般在训练模型时候指定。

注意Pytorch中内置的损失函数的参数和tensorflow不同,是y_pred在前,y_true在后,而Tensorflow是y_true在前,y_pred在后。

对于回归模型,通常使用的内置损失函数是均方损失函数nn.MSELoss 。

对于二分类模型,通常使用的是二元交叉熵损失函数nn.BCELoss (输入未经过sigmoid激活函数之后的结果需要对y_pred加sigmod激活) 或者 nn.BCEWithLogitsLoss (y_pred自动经过nn.Sigmoid激活函数) 。

对于多分类模型,一般推荐使用交叉熵损失函数 nn.CrossEntropyLoss。 (y_true需要是一维的,是类别编码。y_pred自动经过nn.Softmax激活。)

此外,如果多分类的y_pred经过了nn.LogSoftmax激活,可以使用nn.NLLLoss损失函数(The negative log likelihood loss)。 这种方法和直接使用nn.CrossEntropyLoss等价。

一、内置损失函数

import numpy as np
import pandas as pd
import torch 
from torch import nn
import torch.nn.functional as F

y_pred = torch.tensor([[10.0,0.0,-10.0],[8.0,8.0,8.0]])
y_true = torch.tensor([0,2])

#直接使用交叉熵损失函数
loss_1 = nn.CrossEntropyLoss()(y_pred,y_true)
print('CrossELOSS',loss_1)

#等价于先计算nn.logsoftmax激活,在调用NLLLoss
y_pred_logsoftmax = nn.LogSoftmax(dim =  1)(y_pred)

loss_2 = nn.NLLLoss()(y_pred_logsoftmax,y_true)
print('NLLloss',loss_2)

输出:

CrossELOSS tensor(0.5493)
NLLloss tensor(0.5493)
import torch.nn as nn
m = nn.Sigmoid()
loss = nn.BCELoss()
inputt = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
loss_3 = loss(m(inputt), target)
print(loss_3)
loss1 = nn.BCEWithLogitsLoss()
loss_4 = loss1(inputt,target)
print(loss_4)
tensor(1.0520, grad_fn=<BinaryCrossEntropyBackward>)
tensor(1.0520, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)

内置的损失函数一般有类的实现和函数的实现两种形式。

如:nn.BCE 和 F.binary_cross_entropy 都是二元交叉熵损失函数,前者是类的实现形式,后者是函数的实现形式。

实际上类的实现形式通常是调用函数的实现形式并用nn.Module封装后得到的。

一般我们常用的是类的实现形式。它们封装在torch.nn模块下,并且类名以Loss结尾。

常用的一些内置损失函数说明如下。

nn.MSELoss(均方误差损失,也叫做L2损失,用于回归)

nn.L1Loss (L1损失,也叫做绝对值误差损失,用于回归)

nn.SmoothL1Loss (平滑L1损失,当输入在-1到1之间时,平滑为L2损失,用于回归)

nn.BCELoss (二元交叉熵,用于二分类,输入没有经过nn.Sigmoid激活,对不平衡数据集可以用weigths参数调整类别权重)

nn.BCEWithLogitsLoss (二元交叉熵,用于二分类,输入经过nn.Sigmoid激活)

nn.CrossEntropyLoss (交叉熵,用于多分类,要求label为稀疏编码类别号,输入经过nn.Softmax激活,对不平衡数据集可以用weigths参数调整类别权重)

nn.NLLLoss (负对数似然损失,用于多分类,要求label为稀疏编码类别号,,输入未经过nn.LogSoftmax激活)

nn.CosineSimilarity(余弦相似度,可用于多分类)

nn.AdaptiveLogSoftmaxWithLoss (一种适合非常多类别且类别分布很不均衡的损失函数,会自适应地将多个小类别合成一个cluster)

更多损失函数的介绍参考如下知乎文章:

《PyTorch的十八个损失函数》

https://zhuanlan.zhihu.com/p/61379965

二、自定义损失函数

自定义损失函数接收两个张量y_pred,y_true作为输入参数,并输出一个标量作为损失函数值。

也可以对nn.Module进行子类化,重写forward方法实现损失的计算逻辑,从而得到损失函数的类的实现。

下面是一个Focal Loss的自定义实现示范。Focal Loss是一种对binary_crossentropy的改进损失函数形式。

它在样本不均衡和存在较多易分类的样本时相比binary_crossentropy具有明显的优势。

它有两个可调参数,alpha参数和gamma参数。其中alpha参数主要用于衰减负样本的权重,gamma参数主要用于衰减容易训练样本的权重。

从而让模型更加聚焦在正样本和困难样本上。这就是为什么这个损失函数叫做Focal Loss。

详见《5分钟理解Focal Loss与GHM——解决样本不平衡利器》

https://zhuanlan.zhihu.com/p/80594704

在这里插入图片描述

class FocalLoss(nn.Module):
    
    def __init__(self,gamma = 2.0,alpha = 0.75):
        super().__init__()
        self.gamma = gamma
        self.alpha = alpha
        
    def forward(self,y_pred,y_true):
        bce = nn.BCELoss(reduction= "none")(y_pred,y_true)
        p_t = (y_true * y_pred) + ((1 - y_true) * (1 - y_pred))
        alpha_factor = y_true * self.alpha + (1 - y_true) * (1 - self.alpha)
        modulating_factor = torch.pow(1.0 - p_t,self.gamma)
        loss = torch.mean(alpha_factor * modulating_factor * bce)
        return loss
#困难样本
y_pred_hard = torch.tensor([[0.5],[0.5]])
y_true_hard = torch.tensor([[1.0],[0.0]])

#容易样本
y_pred_easy = torch.tensor([[0.9],[0.1]])
y_true_easy = torch.tensor([[1.0],[0.0]])

focal_loss = FocalLoss()
bce_loss = nn.BCELoss()

print("focal_loss(hard samples):", focal_loss(y_pred_hard,y_true_hard))
print("bce_loss(hard samples):", bce_loss(y_pred_hard,y_true_hard))
print("focal_loss(easy samples):", focal_loss(y_pred_easy,y_true_easy))
print("bce_loss(easy samples):", bce_loss(y_pred_easy,y_true_easy))

#可见 focal_loss让容易样本的权重衰减到原来的 0.0005/0.1054 = 0.00474
#而让困难样本的权重只衰减到原来的 0.0866/0.6931=0.12496

# 因此相对而言,focal_loss可以衰减容易样本的权重。



输出:

focal_loss(hard samples): tensor(0.0866)
bce_loss(hard samples): tensor(0.6931)
focal_loss(easy samples): tensor(0.0005)
bce_loss(easy samples): tensor(0.1054)

三、自定义L1和L2正则化

通常认为L1 正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择。

而L2 正则化可以防止模型过拟合(overfitting)。一定程度上,L1也可以防止过拟合。

下面以一个二分类问题为例,演示给模型的目标函数添加自定义L1和L2正则化项的方法。

这个范例同时演示了上一个部分的FocalLoss的使用。

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import torch 
from torch import nn
import torch.nn.functional  as F
from torch.utils.data import Dataset,DataLoader,TensorDataset
import torchkeras
%matplotlib inline
%config InlineBackend.figure_format = 'svg'


n_positive,n_negative = 200,6000

#生成正样本, 小圆环分布
r_p = 5.0 + torch.normal(0.0,1.0,size = [n_positive,1]) 
theta_p = 2*np.pi*torch.rand([n_positive,1])
Xp = torch.cat([r_p*torch.cos(theta_p),r_p*torch.sin(theta_p)],axis = 1)
Yp = torch.ones_like(r_p)

#生成负样本, 大圆环分布
r_n = 8.0 + torch.normal(0.0,1.0,size = [n_negative,1]) 
theta_n = 2*np.pi*torch.rand([n_negative,1])
Xn = torch.cat([r_n*torch.cos(theta_n),r_n*torch.sin(theta_n)],axis = 1)
Yn = torch.zeros_like(r_n)

#汇总样本
X = torch.cat([Xp,Xn],axis = 0)
Y = torch.cat([Yp,Yn],axis = 0)


#可视化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0],Xp[:,1],c = "r")
plt.scatter(Xn[:,0],Xn[:,1],c = "g")
plt.legend(["positive","negative"]);

在这里插入图片描述
1、数据管道
ds = TensorDataset(X,Y)
ds_train,ds_valid = torch.utils.data.random_split(ds,[int(len(ds)*0.7),len(ds)-int(len(ds)*0.7)])
dl_train = DataLoader(ds_train,batch_size=100,shuffle=True)
dl_valid = DataLoader(ds_valid,batch_size=100)

2、搭建模型
class DNNmodel(torchkeras.Model):

def __init__(self):
    super().__init__()
    self.fc1 = nn.Linear(2,4)
    self.fc2 = nn.Linear(4,8)
    self.fc3 = nn.Linear(8,1)
    
def forward(self,x):
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    y = nn.Sigmoid()(self.fc3(x))
    return y

model = DNNmodel()
model.summary(input_shape=(2,))
在这里插入图片描述
3、训练模型

# 准确率
def accuracy(y_pred,y_true):
    y_pred = torch.where(y_pred>0.5,torch.ones_like(y_pred,dtype = torch.float32),
                      torch.zeros_like(y_pred,dtype = torch.float32))
    acc = torch.mean(1-torch.abs(y_true-y_pred))
    return acc


#L2 正则化
def L2Loss(model,alpha):
    l2_loss = torch.tensor(0.0,requires_grad = True)
    for name,parma in model.named_parameters():
        if 'bias' not in name:
            l2_loss = l2_loss + (0.5*alpha * torch.sum(torch.pow(parma,2)))
    return l2_loss

def L1Loss(model,beta):
    l1_loss = torch.tensor(0.0,requires_grad = True)
    for name,parma in model.named_parameters():
        if 'bias' not in name:
            l1_loss = l1_loss + beta * torch.sum(torch.abs(param))
    return l1_loss

def focal_loss_with_regularization(y_pred,y_true):
    focal = FocalLoss()(y_pred,y_true)
    l2_loss = L2Loss(model,0.001)
    l1_loss = L1Loss(model,0.001)
    total_loss = focal + l2_loss + l1_loss
    return total_loss

model.compile(loss_func=focal_loss_with_regularization,
             optimizer=torch.optim.Adam(model.parameters(),lr = 0.01),
             metrics_dict={'accuracy':accuracy})

dfhistory = model.fit(30,dl_train=dl_train,dl_val=dl_valid,log_step_freq=30)
        
# 结果可视化
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0],Xp[:,1], c="r")
ax1.scatter(Xn[:,0],Xn[:,1],c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");

Xp_pred = X[torch.squeeze(model.forward(X)>=0.5)]
Xn_pred = X[torch.squeeze(model.forward(X)<0.5)]

ax2.scatter(Xp_pred[:,0],Xp_pred[:,1],c = "r")
ax2.scatter(Xn_pred[:,0],Xn_pred[:,1],c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

在这里插入图片描述

四、通过优化器实现L2正则化

如果仅仅需要使用L2正则化,那么也可以利用优化器的weight_decay参数来实现。

weight_decay参数可以设置参数在训练过程中的衰减,这和L2正则化的作用效果等价。

before L2 regularization:

gradient descent: w = w - lr * dloss_dw

after L2 regularization:

gradient descent: w = w - lr * (dloss_dw+betaw) = (1-lrbeta)w - lrdloss_dw

so (1-lr*beta)is the weight decay ratio.

weight_params = [param for name, param in model.named_parameters() if "bias" not in name]
bias_params = [param for name, param in model.named_parameters() if "bias" in name]

optimizer = torch.optim.SGD([{'params': weight_params, 'weight_decay':1e-5},
                             {'params': bias_params, 'weight_decay':0}],
                            lr=1e-2, momentum=0.9)
  • 17
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CutMix是一种数据增强技术,可以在训练神经网络时,将两个不同的图像混合在一起,生成一个新的图像。这种技术可以增加模型的鲁棒性和泛化能力。 以下是使用PyTorch实现CutMix数据增强的代码: ```python import torch import numpy as np import random def cutmix_data(x, y, alpha=1.0): lam = np.random.beta(alpha, alpha) batch_size = x.size()[0] index = torch.randperm(batch_size) y_a, y_b = y, y[index] bbx1, bby1, bbx2, bby2 = rand_bbox(x.size(), lam) x[:, :, bbx1:bbx2, bby1:bby2] = x[index, :, bbx1:bbx2, bby1:bby2] lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (x.size()[-1] * x.size()[-2])) return x, y_a, y_b, lam def rand_bbox(size, lam): W = size[2] H = size[3] cut_rat = np.sqrt(1. - lam) cut_w = np.int(W * cut_rat) cut_h = np.int(H * cut_rat) # uniform cx = np.random.randint(W) cy = np.random.randint(H) bbx1 = np.clip(cx - cut_w // 2, 0, W) bby1 = np.clip(cy - cut_h // 2, 0, H) bbx2 = np.clip(cx + cut_w // 2, 0, W) bby2 = np.clip(cy + cut_h // 2, 0, H) return bbx1, bby1, bbx2, bby2 ``` 正则交叉熵损失函数是一种可以减少标签噪声对模型训练的影响的损失函数。以下是使用PyTorch实现正则交叉熵损失函数的代码: ```python import torch.nn.functional as F def reg_cross_entropy_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean', reg_lambda=0.1): logp = F.log_softmax(input, dim=1) loss = F.nll_loss(logp, target, weight, size_average, ignore_index, reduce, reduction) reg_loss = torch.mean(torch.sum(torch.square(torch.exp(logp)), dim=1)) return loss + reg_lambda * reg_loss ``` 在调用此函数时,您可以指定reg_lambda参数来控制正则化的程度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值