Pytorch梯度理解+自定义损失函数

        这篇笔记的出发点:在自定义损失函数时,发现同样是交叉熵损失函数,我自己写的和库本身的数值是一样的,但是训练时,二者效果有很大差别,于是怀疑是自己对梯度的理解没到位,由此写下这篇笔记。

1. tensor的is_leaf属性

主要是为了节省内存,如果1个tensor的is_leaf属性为False,说明他不是叶子节点,是中间变量,那么就不会保存他的梯度

''' 1. 用户自己建立的tentor, is_leaf属性都是True, 无论required_grad是否为True 
    2. 只有float类型才有梯度,因此需要 a = torch.tensor(a, dtype = torch.float) '''

x1 = torch.tensor(1.,requires_grad=True)
x2 = torch.tensor(2.,requires_grad=True)

w11 = torch.tensor(0.1)
w12 = torch.tensor(0.2)
print(x1.is_leaf)   # True
print(w11.is_leaf)  # True

2. grad_fn的属性不影响反向传播

后来发现:我的交叉熵损失函数和库本身的,反向传播值是一样的,

虽然grad_fn不同,库本身是grad_fn=<NllLoss2DBackward0>
                               我的是    grad_fn=<DivBackward0>

后来验证过,我的和他的交叉熵损失函数,对训练结果是没有影响的,当时可能是我函数没写对

class my_CEloss(nn.Module):
    def __init__(self) -> None:
        super().__init__()
    def forward(self,input,label):
        result = 0
        for i in range(label.size(0)):
            # mini_batch里面的1个sample
            loss = 0
            pred = input[i,:,:]
            tar = label[i,:].float()
            num = len(tar)
            for j in range(len(tar)):
                if int(tar[j])==0:
                    loss += -torch.log(torch.exp(pred[0,j])/(torch.exp(pred[0,j])+torch.exp(pred[1,j])))
                else:
                    loss += -torch.log(torch.exp(pred[1,j])/(torch.exp(pred[0,j])+torch.exp(pred[1,j])))
            loss /= num # 对应nn.CrossEntroptLoss()的reduction = 'mean'
            result += loss # 求和1个mini_batch的总loss
        return result/label.size(0) # 返回每个sample的平均值

fn1 = nn.CrossEntropyLoss()
fn2 = my_CEloss()

x = torch.randn(2,2,3,requires_grad=True)
x_ = x
y = torch.randint(0,2,(2,3))
y_ = y

loss1 = fn1(x,y)
loss2 = fn2(x_,y_)
print(loss1)
print(loss2)    
# tensor(1.1663, grad_fn=<NllLoss2DBackward0>)
# tensor(1.1663, grad_fn=<DivBackward0>)
loss1.backward()
loss2.backward()
print(x.grad)
print(x_.grad)  # 梯度值是一样的

3. 维度变换不影响反向传播

4. 通常都是scalar反向传播,但是向量也可以反向传播

''' 向量tentor反向传播 '''
x = torch.tensor([1,2,3],dtype= torch.float,requires_grad=True)
y = 2*x
print(y)       # tensor([2., 4., 6.], grad_fn=<MulBackward0>)
y.backward(gradient=torch.ones_like(x))
print(x.grad)  # tensor([2., 2., 2.])

<think>嗯,用户想知道如何在PyTorch自定义损失函数。首先,我需要回想一下PyTorch的基础知识。损失函数在训练神经网络时非常重要,它计算预测值和真实值之间的差异,然后通过反向传播更新模型参数。 PyTorch已经提供了很多内置的损失函数,比如MSELoss、CrossEntropyLoss这些。但有时候用户可能需要特定的损失函数,这时候就需要自己实现。那怎么开始呢? 首先,自定义损失函数通常有两种方式:一种是作为函数直接定义,另一种是继承nn.Module类来定义。作为函数的话,比较简单,直接写一个接受输入和目标,返回损失值的函数。不过可能不够灵活,特别是如果需要保存参数的话,可能还是用类的方式更好。 比如,用户想实现一个自定义的MSE损失,可能先考虑用函数的方式。比如定义一个my_mse_loss,输入是output和target,然后用PyTorch的张量操作计算均值平方差。然后可以测试一下这个函数和内置的是否一致。 不过更常见的做法是继承nn.Module,这样可以利用PyTorch的自动求导功能。比如创建一个MyLoss类,继承自nn.Module,然后在forward方法里实现计算逻辑。这时候需要注意,所有的运算都应该用PyTorch的张量操作,这样才能保持计算图,便于反向传播。 另外,用户可能需要注意梯度的问题。比如在实现某些复杂的损失函数时,可能需要手动计算梯度,但大多数情况下,PyTorch的自动求导就能处理。所以应该尽量使用PyTorch提供的函数,比如torch.mean、torch.sum这些,而不是用Python原生的操作。 举个例子,比如实现一个自定义的Huber损失。Huber损失结合了MSE和MAE,在误差较小时用平方,较大时用绝对值。这时候在forward方法里,需要根据误差的大小选择不同的计算方式,使用torch.where这样的条件判断。同时要确保这些操作都是张量运算,以支持批处理。 还有,用户可能想给自定义损失函数添加可学习的参数。这时候需要在__init__方法里定义这些参数为nn.Parameter,这样它们在训练过程中会被优化器自动更新。例如,一个自适应的损失函数,里面有权重参数需要调整,就可以这样实现。 另外,关于使用自定义损失函数的步骤,用户需要知道如何将其应用到训练循环中。在初始化模型、优化器之后,实例化自定义损失函数,然后在每个batch计算输出后,调用损失函数得到loss,接着backward()和optimizer.step()。 可能需要注意的常见问题包括:确保损失函数的输出是一个标量,否则在调用backward()时会出错;检查输入输出的形状是否匹配;还有,使用detach()或者requires_grad的设置是否正确,避免不必要的计算图保存。 比如,如果用户的损失函数涉及到其他模型的输出,或者需要固定某些部分不更新,这时候要注意梯度是否应该被保留。此外,数值稳定性也是一个问题,比如在计算log时加上epsilon防止除零错误。 总结一下步骤的话,大概是:确定损失函数形式,选择用函数还是类的方式实现,用PyTorch张量操作编写计算过程,处理梯度问题,最后在训练中调用并测试效果。可能需要通过例子来详细说明,这样用户更容易理解。 可能用户是刚开始接触PyTorch,所以需要给出具体的代码示例,说明两种实现方式,并比较它们的区别。比如函数式的简单例子,和基于nn.Module的例子,以及包含可学习参数的例子。同时提醒注意事项,比如保持计算图、确保输出为标量等。 此外,用户可能不清楚什么时候该用哪种方式。比如,简单的无参数损失可以用函数,复杂或需要参数的用类。同时,测试自定义损失是否正确,可以通过对比已知实现的结果,比如比较自定义MSE和官方MSE的输出是否一致。 最后,提醒用户在实际训练中监控损失值,确保没有出现NaN或者异常情况,特别是在自定义损失函数中可能存在数学运算问题,比如除以零或者对数负数等。</think>在PyTorch自定义损失函数是优化模型行为的重要手段。以下是分步说明: --- ### 一、自定义损失函数的两种方式 #### 1. **函数式定义(无参数)** ```python import torch def my_custom_loss(output, target): # 计算均方误差 (MSE) loss = torch.mean((output - target)**2) return loss ``` #### 2. **类式定义(支持可学习参数)** ```python import torch.nn as nn class MyHuberLoss(nn.Module): def __init__(self, delta=1.0): super().__init__() self.delta = delta # 可设置为可学习参数 def forward(self, output, target): error = output - target abs_error = torch.abs(error) # Huber损失公式 loss = torch.where( abs_error < self.delta, 0.5 * error**2, self.delta * (abs_error - 0.5 * self.delta) ) return torch.mean(loss) ``` --- ### 二、关键步骤与注意事项 1. **张量操作** 所有计算必须使用PyTorch张量运算(如`torch.mean`, `torch.sum`),而非NumPy或Python原生操作,以保留梯度计算能力。 2. **输出为标量** 损失函数必须返回标量值(单个数值),否则调用`.backward()`时会报错。 3. **可学习参数** 若需自定义参数(如权重),需在类中通过`nn.Parameter`定义: ```python class WeightedMSE(nn.Module): def __init__(self, alpha): super().__init__() self.alpha = nn.Parameter(torch.tensor(alpha)) # 可学习参数 def forward(self, output, target): loss = self.alpha * torch.mean((output - target)**2) return loss ``` 4. **数值稳定性** 涉及除法、对数等操作时,添加极小值`eps`防止数值溢出: ```python def stabilized_log_loss(output, target): eps = 1e-8 loss = -torch.log(output + eps) * target return loss.mean() ``` --- ### 三、在训练循环中使用 ```python # 初始化 model = MyModel() criterion = MyHuberLoss(delta=1.5) # 自定义损失函数 optimizer = torch.optim.Adam(model.parameters()) # 训练循环 for epoch in range(epochs): for inputs, targets in dataloader: outputs = model(inputs) loss = criterion(outputs, targets) # 调用自定义损失 optimizer.zero_grad() loss.backward() optimizer.step() ``` --- ### 四、验证自定义损失函数 与内置函数对比验证: ```python output = torch.randn(3, 5) target = torch.randn(3, 5) # 对比MSE实现 custom_mse = my_custom_loss(output, target) official_mse = nn.MSELoss()(output, target) print(torch.allclose(custom_mse, official_mse)) # 应输出True ``` --- ### 五、常见问题 - **梯度断裂**:若需阻止梯度传播到某部分,使用`.detach()`。 - **输入维度**:确保`output`和`target`形状匹配。 - **设备兼容性**:若模型在GPU上,损失函数需处理相同设备的数据。 通过灵活组合张量操作,可轻松实现如对比损失、自定义加权损失等复杂需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值