File size: 7,569 Bytes
c2b1b26 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | """
改进的损失函数 - 更好地处理类别不平衡
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class ImprovedFocalLoss(nn.Module):
"""
改进的Focal Loss,支持类别权重
"""
def __init__(self, alpha=None, gamma=2, ignore_index=255, reduction='mean', class_weights=None):
"""
Args:
alpha: 类别权重列表,如果为None则使用平衡权重
gamma: 聚焦参数
ignore_index: 忽略的索引
reduction: 降维方式
class_weights: 类别权重tensor,shape为(num_classes,)
"""
super().__init__()
if alpha is None:
alpha = [1.0, 1.0] # 默认平衡权重
if isinstance(alpha, (list, tuple)):
alpha = torch.tensor(alpha, dtype=torch.float32)
self.alpha = alpha
self.gamma = gamma
self.ignore_index = ignore_index
self.reduction = reduction
self.class_weights = class_weights
def forward(self, inputs, targets):
"""
Args:
inputs: 预测值 (N, C, H, W)
targets: 目标值 (N, H, W)
"""
# 处理多输出格式
if isinstance(inputs, dict):
inputs = inputs['out']
# 忽略指定索引
if self.ignore_index is not None:
mask = targets != self.ignore_index
targets = targets[mask]
inputs = inputs.permute(0, 2, 3, 1)[mask]
else:
inputs = inputs.permute(0, 2, 3, 1).contiguous().view(-1, inputs.size(1))
targets = targets.view(-1)
# 计算交叉熵
ce_loss = F.cross_entropy(inputs, targets, reduction='none', weight=self.class_weights)
pt = torch.exp(-ce_loss)
# 应用alpha权重
if self.alpha is not None:
if self.alpha.device != targets.device:
self.alpha = self.alpha.to(targets.device)
alpha_t = self.alpha[targets]
focal_loss = alpha_t * (1 - pt) ** self.gamma * ce_loss
else:
focal_loss = (1 - pt) ** self.gamma * ce_loss
if self.reduction == 'mean':
return focal_loss.mean()
elif self.reduction == 'sum':
return focal_loss.sum()
else:
return focal_loss
class WeightedDiceLoss(nn.Module):
"""
加权Dice Loss,给不同类别不同的权重
"""
def __init__(self, num_classes=2, smooth=1e-6, class_weights=None):
"""
Args:
num_classes: 类别数
smooth: 平滑系数
class_weights: 类别权重tensor,shape为(num_classes,)
"""
super().__init__()
self.num_classes = num_classes
self.smooth = smooth
self.class_weights = class_weights
def forward(self, inputs, targets):
"""
Args:
inputs: 预测值 (N, C, H, W)
targets: 目标值 (N, H, W)
"""
# 处理多输出格式
if isinstance(inputs, dict):
inputs = inputs['out']
# 将预测转换为概率
inputs = torch.softmax(inputs, dim=1)
# 创建one-hot编码
targets_one_hot = F.one_hot(targets, num_classes=self.num_classes)
targets_one_hot = targets_one_hot.permute(0, 3, 1, 2).float()
# 计算每个类别的dice系数
intersection = (inputs * targets_one_hot).sum(dim=(2, 3)) # (N, C)
union = inputs.sum(dim=(2, 3)) + targets_one_hot.sum(dim=(2, 3)) # (N, C)
dice_score = (2. * intersection + self.smooth) / (union + self.smooth) # (N, C)
# 计算每个类别的dice loss
dice_loss_per_class = 1 - dice_score # (N, C)
# 应用类别权重
if self.class_weights is not None:
if self.class_weights.device != dice_loss_per_class.device:
self.class_weights = self.class_weights.to(dice_loss_per_class.device)
# 对每个类别应用权重
weighted_dice_loss = dice_loss_per_class * self.class_weights.unsqueeze(0)
dice_loss = weighted_dice_loss.mean()
else:
dice_loss = dice_loss_per_class.mean()
return dice_loss
class ImprovedSeaweedSegmentationLoss(nn.Module):
"""
改进的浒苔分割损失函数
更好地处理类别不平衡,特别是背景像素远多于浒苔像素的情况
"""
def __init__(self, num_classes=2, focal_alpha=None, focal_gamma=2,
dice_weight=0.5, focal_weight=1.0,
background_weight=1.0, foreground_weight=2.0):
"""
Args:
num_classes: 类别数
focal_alpha: Focal Loss的alpha参数(类别权重)
focal_gamma: Focal Loss的gamma参数
dice_weight: Dice Loss的权重
focal_weight: Focal Loss的权重
background_weight: 背景类别的权重(类别0)
foreground_weight: 前景类别(浒苔)的权重(类别1)
"""
super().__init__()
# 创建类别权重
class_weights = torch.tensor([background_weight, foreground_weight], dtype=torch.float32)
# 创建Focal Loss的alpha权重
if focal_alpha is None:
# 默认给前景更高的权重
focal_alpha = [background_weight, foreground_weight]
self.focal_loss = ImprovedFocalLoss(
alpha=focal_alpha,
gamma=focal_gamma,
class_weights=class_weights
)
self.dice_loss = WeightedDiceLoss(
num_classes=num_classes,
class_weights=class_weights
)
self.dice_weight = dice_weight
self.focal_weight = focal_weight
self.num_classes = num_classes
def forward(self, inputs, targets):
"""计算组合损失"""
focal_loss = self.focal_loss(inputs, targets)
dice_loss = self.dice_loss(inputs, targets)
total_loss = self.focal_weight * focal_loss + self.dice_weight * dice_loss
return {
'total_loss': total_loss,
'focal_loss': focal_loss,
'dice_loss': dice_loss
}
def calculate_class_weights_from_dataset(dataset, num_classes=2):
"""
从数据集中计算类别权重
用于平衡类别不平衡问题
"""
print("正在计算类别权重...")
total_pixels = 0
class_counts = torch.zeros(num_classes)
for i in range(len(dataset)):
sample = dataset[i]
mask = sample['mask']
# 统计每个类别的像素数
for c in range(num_classes):
class_counts[c] += (mask == c).sum().item()
total_pixels += mask.numel()
# 计算权重:总像素数 / (类别数 * 该类别的像素数)
class_weights = total_pixels / (num_classes * class_counts + 1e-6)
# 归一化权重
class_weights = class_weights / class_weights.sum() * num_classes
print(f"类别像素统计: {class_counts.tolist()}")
print(f"类别权重: {class_weights.tolist()}")
return class_weights
|