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

584 lines
25 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 : farsegloss
@Author : qiqq
@create_time : 2022/11/25 14:54
farseg论文里的F-A模块对于loss的优化
"""
import torch
import torch.nn.functional as F
import math
from torch import nn
def softmax_focalloss(y_pred, y_true, ignore_index=255, gamma=2.0, normalize=False):
"""
Args:
y_pred: [N, #class, H, W]
y_true: [N, H, W] from 0 to #class
gamma: scalar
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 - p).pow(gamma) #权重
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这不就和背景一样了吗
'''
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
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
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, n_min, ignore_lb=255, *args, **kwargs):
super(OhemCELoss, self).__init__()
self.thresh = -torch.log(torch.tensor(thresh, dtype=torch.float)).cuda() # 将输入的概率 转换为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]
else:
loss = loss[:self.n_min]
return torch.mean(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我感觉废了
#########################以下作废,准备重写
####从此处开始往下到main 有关自己损失函数优化的全部作废......
def sampleloss(y_pred, y_true, ignore_index=255, gamma=2.0, normalize=False):
#
# 在定义标签的时候背景必须是0
# 想法loss分成两部分
# 所有前景样本的loss+部分困难背景样本的loss,然后背景像素的调制系数在参照farseg
# 其中困难的定义是prob<0.5 (是个超参数,暂定)
# y_pred: [N, #class, H, W]
# y_true: [N, H, W] from 0 to #class
#
# :return:
#
pass
# #1.首先把所有前景样本取出来
# 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)
# 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)
#2.把符合条件的背景样本取出来
def sample_foreground(y_pred, y_true, ignore_index=255,backgroud=0, gamma=2.0, normalize=False):
# 把所有前景样本取出来。形成专门的前景loss
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index, reduction='none') #这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask1 = torch.ones_like(y_true) #
# valid_mask1 = ~ y_true.eq(ignore_index) #ignor的像素找出来
# valid_mask2= ~ y_true.eq(backgroud) #背景像素找出来
# # valid_mask=valid_mask1.eq(valid_mask2)
valid_mask=~ y_true.eq(ignore_index) & ~ y_true.eq(backgroud) #背景和ignor的像素的位置设置为false
foregroundcount= valid_mask[valid_mask==True].shape[0] #统计前景像素的个数
foregmask = torch.where(valid_mask, mask1, torch.zeros_like(y_true)) #
losses = (losses * foregmask).sum() / (foregroundcount ) # #这一部分的loss是把前景和背景的loss都给计算出来了。。我们只要前景的所以背景的就是
# 我不太明白为什么这里原来/的时候要 p.size(0)+batch 这个valid_mask.sum()不久包含了batch在内的所有的样本点吗...所以我把原来的+ p.size(0)去掉了
# foregmask是前景的就是1 不是前景的就是0这样就把原来loss所有的loss中的前景的loss提取出来了
return losses
def sample_hardbackgroundversion1(y_pred, y_true, ignore_index=255,threshold=0.5, backgroud=0,gamma=2.0, normalize=False):
# 采样困难的背景像素。设置小于阈值的就算是困难的 .整个训练过程中一直保持0.5不变
# 把所有符合阈值的背景像素。形成专门的背景loss
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask = torch.ones_like(y_true)
valid_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud) # 背景和ignor的像素的位置设置为false背景的设置为true
threshold_mask =p[:,0,:,:]<threshold #,在预测的背景里边大于阈值的就属于容易,所以不要,小于阈值的是困难
backgroundmask = torch.where(valid_mask, mask, torch.zeros_like(y_true)) #
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask, torch.zeros_like(y_true)) #
# backgroundloss = losses * backgroundmask
background_thresholdloss = losses * backgroundthresholdmask
count=backgroundthresholdmask[backgroundthresholdmask==1].shape[0]
finalloss =background_thresholdloss.sum()/count
# backgroundmask 不是背景就是1不是背景的前景和ignor的就是0这样就把原来所有的loss中的背景的loss提取出来了
return finalloss
def sample_hardbackgroundversion2(y_pred, y_true, t, t_max,ignore_index=255,backgroud=0, annealing_function=cosine_annealing):
'''
采样困难的背景像素。设置小于阈值的就算是困难的
想仿照farseg做一个动态阈值
'''
# 把所有符合阈值的背景像素。形成专门的背景loss
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask1 = torch.ones_like(y_true) #
valid_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud) # 背景和ignor的像素的位置设置为false
if t > t_max: # 大概的意思是整个训练过程比如1000轮 t_max=500轮 就是500以内我的factor就是不断呈现一个余弦函数变化然后500-100轮 我的factor就固定了
threshold = 0.5
else:
threshold = annealing_function(0.8, 0.5, t,
t_max)
threshold_mask = p[:, 0, :, :] < threshold
backgroundmask = torch.where(valid_mask, mask1, torch.zeros_like(y_true)) #
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask, torch.zeros_like(y_true)) #
# backgroundloss = losses * backgroundmask
background_thresholdloss = losses * backgroundthresholdmask
count = backgroundthresholdmask[backgroundthresholdmask == 1].shape[0]
finalloss = background_thresholdloss.sum() / count
return finalloss
def foreground_sample_hardbackgroundversion1(y_pred, y_true, ignore_index=255,threshold=0.5, backgroud=0,gamma=2.0, normalize=False):
'''我还就不信了。分开写不行合起来:在做判断规则的时候直接一部到位,前景的和符合条件的背景的'''
# 把所有前景样本取出来。形成专门的前景loss
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask1 = torch.ones_like(y_true) #
#1.前景的mask
fore_mask = ~ y_true.eq(ignore_index) & ~ y_true.eq(backgroud) #
foregmask = torch.where(fore_mask, mask1, torch.zeros_like(y_true)) #
#2.符合条件的背景的mask
#背景mask
background_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud)
backgroundmask = torch.where(background_mask, mask1, torch.zeros_like(y_true)) #
#背景中符合条件的mask
threshold_mask = p[:, 0, :, :] < threshold #
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask, torch.zeros_like(y_true)) #
#
#3.前景的mask和符合条件的背景的mask
totmask=foregmask.eq(1) | backgroundthresholdmask.eq(1)
total_mask = torch.where(totmask, mask1, torch.zeros_like(y_true))
valcount= total_mask[total_mask==1].shape[0]
finaloss = (losses * total_mask).sum() / (valcount)
return finaloss
def foreground_sample_hardbackgroundversion2(y_pred, y_true, t, t_max,ignore_index=255,backgroud=0, annealing_function=cosine_annealing):
'''我还就不信了。分开写不行合起来:在做判断规则的时候直接一部到位,前景的和符合条件的背景的'''
# 把所有前景样本取出来。形成专门的前景loss
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask1 = torch.ones_like(y_true) #
#1.前景的mask
fore_mask = ~ y_true.eq(ignore_index) & ~ y_true.eq(backgroud) #
foregmask = torch.where(fore_mask, mask1, torch.zeros_like(y_true)) #
#2.符合条件的背景的mask
#背景mask
background_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud)
backgroundmask = torch.where(background_mask, mask1, torch.zeros_like(y_true)) #
#背景中符合条件的mask
if t > t_max: # 大概的意思是整个训练过程比如1000轮 t_max=500轮 就是500以内我的factor就是不断呈现一个余弦函数变化然后500-100轮 我的factor就固定了
threshold = 0.5
else:
threshold = annealing_function(0.99, 0.5, t,
t_max)
threshold_mask = p[:, 0, :, :] < threshold #
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask, torch.zeros_like(y_true)) #
#
#3.前景的mask和符合条件的背景的mask
totmask=foregmask.eq(1) | backgroundthresholdmask.eq(1)
total_mask = torch.where(totmask, mask1, torch.zeros_like(y_true))
valcount= total_mask[total_mask==1].shape[0]
finaloss = (losses * total_mask).sum() / (valcount)
return finaloss
###########################################################
##关于损失函数优化的###########重写
'''
1.第一版本的思想
对于前背景不平衡的问题:
前景像素全部参与计算 ,背景只选择困难的参与计算,
背景的困难样本选择:设置一个阈值 比如0.5 预测的背景概率小于0.5的就算是困难的
阈值设置为动态阈值用一个cos函数 前x个epoch 从0.99到0.5x epoch后固定为0.5有点farseg的思想
2.第二版本的思想
关于困难样本挖掘的问题:
基本和focal loss差不多
1.先根据第一版的把前景样本和符合阈值的背景样本采出来
2.然后用focal loss里边的关于困难样本挖掘的1-pr对所有的进行加权,其中加权方式也可以按照动态加权
'''
def fb_loss_version1(y_pred, y_true, t, t_max,ignore_index=255,backgroud=0, gamma=2.0, annealing_function=cosine_annealing):
'''这个基本代码没什么问题了,可以不动了'''
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask = torch.ones_like(y_true) # #通用的mask
fore_mask = ~ y_true.eq(ignore_index) & ~ y_true.eq(backgroud) # 形成前景的maskboole的 #非背景和ignore的就是前景像素
foregmask = torch.where(fore_mask, mask, torch.zeros_like(y_true)) # #形成前景的mask0 1形式的
# 2.背景中符合阈值的
background_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud) #背景的mask boole类型
backgroundmask = torch.where(background_mask, mask, torch.zeros_like(y_true)) # #背景的mask int (0,1)类型
# 背景中符合条件的mask
if t > t_max: # 大概的意思是整个训练过程比如1000轮 t_max=500轮 就是500以内我的factor就是不断呈现一个余弦函数变化然后500-100轮 我的factor就固定了
threshold = 0.5
else:
threshold = annealing_function(0.99, 0.5, t,
t_max)
threshold_mask = p[:, 0, :, :] < threshold # ,像素预测为背景的概率小于阈值的则为背景困难的 #boole类型的
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask, torch.zeros_like(y_true)) # int 0 1 类型的 #最终的符合阈值的background的样本
#
# 3.前景的mask和符合条件的背景的mask是前景或者是符合要求的背景 #如果摊上了所有背景样本都符合阈值的情况那就是原来的loss
totmask = foregmask.eq(1) | backgroundthresholdmask.eq(1)
total_mask = torch.where(totmask, mask, torch.zeros_like(y_true))
valcount = total_mask[total_mask == 1].shape[0]
finaloss = (losses * total_mask).sum() / (valcount)
return finaloss
def fb_loss_version2(y_pred, y_true, t, t_max,ignore_index=255,backgroud=0, gamma=2.0, annealing_function=cosine_annealing):
losses = F.cross_entropy(y_pred, y_true, ignore_index=ignore_index,
reduction='none') # 这一部分的loss是把前景和背景的loss都给计算出来了
with torch.no_grad():
p = y_pred.softmax(dim=1)
mask = torch.ones_like(y_true) # #通用的mask
fore_mask = ~ y_true.eq(ignore_index) & ~ y_true.eq(backgroud) # 形成前景的maskboole的 #非背景和ignore的就是前景像素
foregmask = torch.where(fore_mask, mask, torch.zeros_like(y_true)) # #形成前景的mask0 1形式的
# 2.背景中符合阈值的
background_mask = ~ y_true.eq(ignore_index) & y_true.eq(backgroud) # 背景的mask boole类型
backgroundmask = torch.where(background_mask, mask, torch.zeros_like(y_true)) # #背景的mask int (0,1)类型
# 背景中符合条件的mask
if t > t_max: # 大概的意思是整个训练过程比如1000轮 t_max=500轮 就是500以内我的factor就是不断呈现一个余弦函数变化然后500-100轮 我的factor就固定了
threshold = 0.5
else:
threshold = annealing_function(0.99, 0.5, t,
t_max)
threshold_mask = p[:, 0, :, :] < threshold # ,像素预测为背景的概率小于阈值的则为背景困难的 #boole类型的
backgroundthresholdmask = torch.where(threshold_mask, backgroundmask,
torch.zeros_like(y_true)) # int 0 1 类型的 #最终的符合阈值的background的样本
#
# 3.前景的mask和符合条件的背景的mask是前景或者是符合要求的背景 #如果摊上了所有背景样本都符合阈值的情况那就是原来的loss
totmask = foregmask.eq(1) | backgroundthresholdmask.eq(1)
total_mask = torch.where(totmask, mask, torch.zeros_like(y_true))
valcount = total_mask[total_mask == 1].shape[0]
#在version1的基础上
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))
'''
torch.where(conditionab)其中
输入参数condition条件限制如果满足条件则选择a否则选择b作为输出
'''
modulating_factor = torch.gather(modulating_factor, dim=1, index=masked_y_true.unsqueeze(dim=1)).squeeze_(dim=1)
'''
modulating_factor=(1 - p).pow(gamma)是个全通道的调制系数 和ypred的尺寸一样 但是我们想要的是一个单通道的
也就是每个pix的一个权重也就是仙子我们的每个有效的位置的pix的loss已经有了 就差权重了
torch.gather(modulating_factor, dim=1, index=masked_y_true.unsqueeze(dim=1)).squeeze_(dim=1)
就是根据gt的index索引从相应的dim维度去取对应的factor的值比如某个位置 gt是2 那就从dim上的第二个位置取他的factor
'''
finaloss = (modulating_factor*(losses * total_mask)).sum() / (valcount)
return finaloss
if __name__ == '__main__':
setup_seed()
# input = torch.randn(2, 3, 4, 4, dtype=torch.float32)
input = torch.randn(1, 3, 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(([[[2, 1, 1, 2],
[2, 255, 2, 2],
[2, 0, 255, 2],
[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)
# backloss =foreground_sample_hardbackgroundversion1(input,target)
backloss =softmax_focalloss(input,target)
print(backloss)
#
#
#
# if __name__ == '__main__':
#
# setup_seed()
#
# input = torch.randn(1, 3, 4, 4, dtype=torch.float32)
# target = torch.randint(3, (1, 4, 4), dtype=torch.int64)
#
# 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("-----------------------------")
# # softmax_focalloss=softmax_focalloss(input,target)
#
#
# # tmx = 10000
# # for i in range(tmx):
# # loss = annealing_softmax_focalloss(input, target, t=i, t_max=tmx)
# # # softmax_focalloss=softmax_focalloss(input,target)
#
#
# # import matplotlib.pyplot as plt
# #
# # _t_max=300
# # ylist= []
# # xlist=[]
# # for i in range(_t_max):
# # y=cosine_annealing(0.1,0.5,_t=i,_t_max=_t_max)
# # ylist.append(y)
# # xlist.append(i)
# #
# # plt.figure()
# # plt.plot(xlist,ylist)
# # plt.show()
# #
#
#
# if __name__ == '__main__':
#
# target = torch.tensor(([[[2, 1, 1, 2],
# [2, 255, 2, 2],
# [2, 0, 255, 2],
# [1, 1, 0, 0]]]))
#
# epoch =300
# for i in range(epoch):
# # input = torch.randn(2, 3, 4, 4, dtype=torch.float32)
# input = torch.randn(1, 3, 4, 4, dtype=torch.float32)
#
#
# 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)
# backloss =sample_hardbackgroundversion2(input,target,t=i+1, t_max=50)
#
# print(backloss)
# #
#
#