ai-station-code/wudingpv/taihuyuan_roof/schedulers/losses.py

335 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@project:
@File : losses
@Author : qiqq
@create_time : 2023/1/3 14:37
"""
import math
import os
# os.environ["CUDA_VISIBLE_DEVICES"] = "1"
import numpy as np
import torch
from torch import nn
import torch.nn.functional as F
####farseg里边的针对不平衡提出来的loss
#实际上既是在一般的交叉熵前乘了一个权重因子
def softmax_focalloss(y_pred, y_true, ignore_index=255, gamma=2.0,alpha=0.25,eps = 1e-8, normalize=False):
"""
Args:
y_pred: [N, #class, H, W]
y_true: [N, H, W] from 0 to #class
gamma: scalar
focal loss的原公式 -aplha*1-pt**gmma*log(pt)
Returns:
"""
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index, reduction='none')
with torch.no_grad():##.你tm的为啥torch.no_grad()这还怎么在训练的时候用 ??难道是为了节省显存??我不理解
p = y_pred.softmax(dim=1)
modulating_factor = ((1 +eps- p).pow(gamma))*alpha #权重
valid_mask = ~ y_true.eq(ignore_index)
masked_y_true = torch.where(valid_mask, y_true, torch.zeros_like(y_true)) # 我觉得这个可能还是为了去选择那些不是ignorindex的去参与计算
#不对把你tm把255的也弄成0了tm这不就和背景一样了吗 但是好像在前边算cross_entropy时候就没有计算ignor的既是把它的loss做0
'''
torch.where(conditionab)其中
输入参数condition条件限制如果满足条件则选择a否则选择b作为输出
'''
#这个是干啥的..
modulating_factor = torch.gather(modulating_factor, dim=1, index=masked_y_true.unsqueeze(dim=1)).squeeze_(dim=1)
'''
torch.gather:
index实际上是索引具体是行还是列的索引要看前面dim 的指定,index的大小就是输出的大小
举个例子:
输入:[[1,2,3],[4,5,6] index [[0 ,1],[2 ,0]] dim=1 输出:[[1 ,2],[6,4]]
gather在one-hot为输出的多分类问题中可以把最大值坐标作为index传进去然后提取到每一行的正确预测结果这也是gather可能的一个作用。
在这里dim1就是类别方向比如batch,channel,h,w dim1就是channel方向
torch.gather相当于以类别为索引把modulating_factor对应的取出来
这里的
'''
scale = 1.
if normalize:
scale = losses.sum() / (losses * modulating_factor).sum()
losses = scale * (losses * modulating_factor).sum() / (valid_mask.sum() + p.size(0)) # focalloss里的对于困难样本的加权
#我不太明白为什么这里/的时候嗨哟啊+batch 这个valid_mask.sum()不久包含了batch在内的所有的样本点吗
return losses
#用类实现了一边
class FocalLoss(nn.Module):
def __init__(self,alpha=0.25,gamma=2,eps=1e-8,ignore_index=255):
super(FocalLoss, self).__init__()
self.alpha=alpha
self.gamma=gamma
self.eps=eps
self.ignore_index=ignore_index
def forward(self,y_pred, y_true):
losses = F.cross_entropy(y_pred, y_true, ignore_index=self.ignore_index, reduction='none')
p = y_pred.softmax(dim=1)
modulating_factor = ((1 + self.eps - p).pow(self.gamma)) * self.alpha # 权重
valid_mask = ~ y_true.eq(self.ignore_index)
masked_y_true = torch.where(valid_mask, y_true, torch.zeros_like(y_true)) # 我觉得这个可能还是为了去选择那些不是ignorindex的去参与计算
# 不对把你tm把255的也弄成0了tm这不就和背景一样了吗 但是好像在前边算cross_entropy时候就没有计算ignor的既是把它的loss做0
'''
torch.where(conditionab)其中
输入参数condition条件限制如果满足条件则选择a否则选择b作为输出
'''
# 这个是干啥的..
modulating_factor = torch.gather(modulating_factor, dim=1, index=masked_y_true.unsqueeze(dim=1)).squeeze_(dim=1)
'''
torch.gather:
index实际上是索引具体是行还是列的索引要看前面dim 的指定,index的大小就是输出的大小
举个例子:
输入:[[1,2,3],[4,5,6] index [[0 ,1],[2 ,0]] dim=1 输出:[[1 ,2],[6,4]]
gather在one-hot为输出的多分类问题中可以把最大值坐标作为index传进去然后提取到每一行的正确预测结果这也是gather可能的一个作用。
在这里dim1就是类别方向比如batch,channel,h,w dim1就是channel方向
torch.gather相当于以类别为索引把modulating_factor对应的取出来
这里的
'''
losses = (losses * modulating_factor).sum() / (valid_mask.sum() + p.size(0)) # focalloss里的对于困难样本的加权
# 我不太明白为什么这里/的时候嗨哟啊+batch 这个valid_mask.sum()不久包含了batch在内的所有的样本点吗
return losses
def cosine_annealing(lower_bound, upper_bound, _t, _t_max):
'''
'''
return upper_bound + 0.5 * (lower_bound - upper_bound) * (math.cos(math.pi * _t / _t_max) + 1)
def poly_annealing(lower_bound, upper_bound, _t, _t_max):
factor = (1 - _t / _t_max) ** 0.9
return upper_bound + factor * (lower_bound - upper_bound)
def linear_annealing(lower_bound, upper_bound, _t, _t_max):
factor = 1 - _t / _t_max
return upper_bound + factor * (lower_bound - upper_bound)
def annealing_softmax_focalloss(y_pred, y_true, t, t_max, ignore_index=255, gamma=2.0,
annealing_function=cosine_annealing):
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index, reduction='none')
with torch.no_grad():
p = y_pred.softmax(dim=1)
modulating_factor = (1 - p).pow(gamma)
valid_mask = ~ y_true.eq(ignore_index) # 没有的话这个可以忽略
masked_y_true = torch.where(valid_mask, y_true, torch.zeros_like(y_true))
modulating_factor = torch.gather(modulating_factor, dim=1, index=masked_y_true.unsqueeze(dim=1)).squeeze_(dim=1)
# 截止到这里还是和focalloss那边差不多
normalizer = losses.sum() / (losses * modulating_factor).sum() # 大概就是论文种那个z 其实这个/换成h*w就成了mean loss了
scales = modulating_factor * normalizer # modulating_factor对应原文的那个1-pir 这个scales对应原文的1/z*1-pir
if t > t_max: # 大概的意思是整个训练过程比如1000轮 t_max=500轮 就是500以内我的factor就是不断呈现一个余弦函数变化然后500-100轮 我的factor就固定了
scale = scales
else:
scale = annealing_function(1, scales, t,
t_max) # 比起一般的focalloss从头到位一直是modulating_factor我这里是动态加权从1开始一直到modulating_factor
losses = (losses * scale).sum() / (valid_mask.sum() + p.size(0))
return losses
#######ohem根据loss大小进行困难样本挖掘
#别人复现的版本1 #https://blog.csdn.net/m0_61139217/article/details/127084869
class OhemCELoss(nn.Module):
"""
Online hard example mining cross-entropy loss:在线难样本挖掘
if loss[self.n_min] > self.thresh: 最少考虑 n_min 个损失最大的 pixel
如果前 n_min 个损失中最小的那个的损失仍然大于设定的阈值,
那么取实际所有大于该阈值的元素计算损失:loss=loss[loss>thresh]。
否则,计算前 n_min 个损失:loss = loss[:self.n_min]
"""
def __init__(self, thresh=0.7, n_min=10, ignore_lb=255, *args, **kwargs):
super(OhemCELoss, self).__init__()
# self.thresh = -torch.log(torch.tensor(thresh, dtype=torch.float)).cuda() # 将输入的概率 转换为loss值
self.thresh = -torch.log(torch.tensor(thresh, dtype=torch.float)) # 将输入的概率 转换为loss值
self.n_min = n_min
self.ignore_lb = ignore_lb
self.criteria = nn.CrossEntropyLoss(ignore_index=ignore_lb, reduction='none') # 交叉熵
def forward(self, logits, labels):
N, C, H, W = logits.size()
loss = self.criteria(logits, labels).view(-1)
loss, _ = torch.sort(loss, descending=True) # 排序#从大到下(降序)
if loss[self.n_min] > self.thresh: # 当loss大于阈值(由输入概率转换成loss阈值)的像素数量比n_min多时取所以大于阈值的loss值
loss = loss[loss > self.thresh] #当钱min个的loss都大于阈值的时候loss只取那些大于阈值的所有loss
else:
loss = loss[:self.n_min] #当前n_min个中有小于阈值的就取前nim个loss
return torch.mean(loss)
#别人复现的版本2#https://blog.csdn.net/qq_40035462/article/details/123448323
#原来是paddle写的 我给硬改成pytorch
class OhemCrossEntropyLoss(nn.Module):
"""
Implements the ohem cross entropy loss function.
Args:
thresh (float, optional): The threshold of ohem. Default: 0.7.
min_kept (int, optional): The min number to keep in loss computation. Default: 10000.
ignore_index (int64, optional): Specifies a target value that is ignored
and does not contribute to the input gradient. Default ``255``.
"""
def __init__(self, thresh=0.7, min_kept=10, ignore_index=255):
super(OhemCrossEntropyLoss, self).__init__()
self.thresh = thresh # 概率阈值,真实类别预测概率比阈值低的被认为是难样本
self.min_kept = min_kept # 最少用于计算损失的像素点数量
self.ignore_index = ignore_index # 忽略计算损失的标签
self.EPS = 1e-5 # 防止数值计算出错
def forward(self, logit, label):
"""
Forward computation.
Args:
logit (Tensor): Logit tensor, the data type is float32, float64. Shape is
(N, C), where C is number of classes, and if shape is more than 2D, this
is (N, C, D1, D2,..., Dk), k >= 1.
label (Tensor): Label tensor, the data type is int64. Shape is (N), where each
value is 0 <= label[i] <= C-1, and if shape is more than 2D, this is
(N, D1, D2,..., Dk), k >= 1.
"""
if len(label.shape) != len(logit.shape):
label = torch.unsqueeze(label, 1)
# get the label after ohem
n, c, h, w = logit.shape
label = label.reshape((-1,))
valid_mask = (label != self.ignore_index).to(torch.int64)
num_valid = valid_mask.sum()
label = label * valid_mask #以上操作对ignor进行的将ignor255的index变成0
prob = F.softmax(logit, dim=1) # 计算预测的概率
prob = prob.transpose(0,1).reshape((c, -1)) #pytorc中transpose一次只能调换两个维度
if self.min_kept < num_valid and num_valid > 0: #如果最少的采样点比num_valid有效点要少
# let the value which ignored greater than 1
prob = prob + (1 - valid_mask) #让那些ignor位置255的概率都大于1
# get the prob of relevant label
label_onehot = F.one_hot(label, c) #维度batch*h*w3有多少像素点就有多少行
label_onehot = label_onehot.transpose(1, 0) #换个维度
prob = prob * label_onehot # 真实类别对应的预测概率
prob = torch.sum(prob, dim=0) #这样就得到了对应类的prob
threshold = self.thresh
if self.min_kept > 0:
index = prob.argsort() #argsort()函数是对数组中的元素进行从小到大排序,并返回相应序列元素的数组下标
threshold_index = index[min(len(index), self.min_kept) - 1] #在 像素点的个数与最小采样之间选择最小值 threshold_index #这一步和上一步结合起来相当于取低x个最大的
threshold_index = int(threshold_index.cpu().numpy())
if prob[threshold_index] > self.thresh: #如果这里前threshold_index
threshold = prob[threshold_index]
kept_mask = (prob < threshold).to(torch.int64) # 根据阈值选择参与计算的像素点
label = label * kept_mask
valid_mask = valid_mask * kept_mask
# make the invalid region as ignore
label = label + (1 - valid_mask) * self.ignore_index #怎么又变回来了带有255
# label = label.reshape((n, 1, h, w)) #原
# valid_mask = valid_mask.reshape((n, 1, h, w)).to(torch.float32)
label = label.reshape((n, h, w))
valid_mask = valid_mask.reshape((n, h, w)).to(torch.float32)
loss = F.cross_entropy(
logit, label, ignore_index=self.ignore_index,reduction='none')
loss = loss * valid_mask
avg_loss = torch.mean(loss) / (torch.mean(valid_mask) + self.EPS)
label.stop_gradient = True
valid_mask.stop_gradient = True
return avg_loss
def setup_seed(seed=0):
import torch
import os
import numpy as np
import random
torch.manual_seed(seed) # 为CPU设置随机种子
np.random.seed(seed) # Numpy module.
random.seed(seed) # Python random module.
############################################################################
#2022.12.8我感觉废了
if __name__ == '__main__':
setup_seed()
# input = torch.randn(2, 3, 4, 4, dtype=torch.float32)
input = torch.randn(1, 2, 4, 4, dtype=torch.float32)
# target = torch.randint(3, (1, 4, 4), dtype=torch.int64)
# target = torch.randint(3, (2, 4, 4), dtype=torch.int64)
target = torch.tensor(([[[1, 1, 1, 0],
[1, 0, 0, 1],
[0, 1, 0, 0],
[1, 1, 0, 0]]]))
# target = torch.tensor(([[[1, 1, 2, 1],
# [2, 0, 1, 0],
# [2, 0, 255, 2],
# [2, 2, 0, 1]],
# [[2, 2, 1, 0],
# [1, 0, 0, 0],
# [0, 255, 0, 1],
# [1, 2, 1, 1]]]))
print("---------pred----------")
print(input)
print("---------target----------")
print(target)
# print("__________softmax___________")
# soft = F.softmax(input, dim=1)
# print(soft)
# print("__________log___________")
# log = torch.log(soft)
# print(log)
# print("++++++++++++++++++++++++++++++++++++++++++++++")
# print("++++++++++++ohem1++++++++++++++++++++++++++++++++++")
# ohem1=OhemCELoss()
# ohemloss1 =ohem1(input,target)
#
# print(ohemloss1)
# print("++++++++++++ohem2++++++++++++++++++++++++++++++++++")
# ohem2 = OhemCrossEntropyLoss()
# ohemloss2 = ohem2(input, target)
#
# print(ohemloss2)
# #卧槽,这两个算出来的结果一样?????那用谁都可以???
# lossmodel=FocalLoss()
# backloss = lossmodel(input, target)
backloss = softmax_focalloss(input, target)
#
print(backloss)
#
#