使用神经网络来拟合数据

神经网络

概念

深度学习的核心就是神经网络,其是一种通过简单函数的组合来表示复杂函数的数学实体,这些复杂函数的基本构件就是神经元

单层神经网络

上述介绍了神经网络的基本概念,引出了神经元,而神经元核心也就是输入的线性变换,最简单的便是正比例函数,输入乘以一个数字【权重】,然后再加上一个数字【偏置】,然后应用一个非线性函数,而这个非线性函数便是激活函数

由此而构成的一个函数其数学表达式为o=f(wx+b),其中x是输入,w是权重,b是偏置,f是激活函数

而o和x可以是简单的标量,也可以是复杂的向量,w和b也是如此,但是当b为向量时,其输入的维度必须与权重匹配,而上述这样的表示式便是单层的神经网络

多次神经网络

既然已经知道了单层神经网络,那多层神经网络便是将一层的神经网络的输入(x)通过上一层神经网络的输出(o)来表示,如下

x_1 =f(w_0 * x + b_0)

x_2=f(w_1 * x_1 + b_1)

····

y=f(w_n * x_n + b_n)

同时每一层的激活函数可以完全不同,每一层神经网络都可以发挥自己的特性

激活函数

通过上文我们已经知道了什么是激活函数,这里我们详细介绍一下激活函数

激活函数主要在torch.nn模块中,这是一个专门用于神经网络的模块,也是这次主要的介绍对象

类型

限制输出范围--Tanh()

class torch.nn.Hardtanh(

min_val: float = -1.0,

max_val: float = 1.0,

inplace: bool = False

)

公式:

minval:范围最小值

maxval:范围最大值

inplace:布尔型,是否进行原地操作,即直接修改输入的这里,而不创立新的张量

压缩输出范围--Sigmoid()

class torch.nn.Sigmoid()

公式:

整流线性单元--ReLU()

class torch.nn.ReLU(inplace: bool = False)

公式:

inplace:布尔型,是否进行原地操作,即直接修改输入的这里,而不创立新的张量

LeakyReLU()

class torch.nn.LeakyReLU(negative_slope: float = 1e-2, inplace: bool = False)

公式:

negative_slope:用以控制负斜率的角度,负斜率是处理负输入值,默认为0.01

inplace:布尔型,是否进行原地操作,即直接修改输入的这里,而不创立新的张量

PReLU()

class torch.nn.PReLU(num_parameters: int = 1, init: float = 0.25, device=None, dtype=None)

公式:

num_parameters:需要学习的参数的数量

init:表示变量的初始值

device:运行设备

dtype:数据类型

其与上面的LeakyReLU()的区别在于该激活函数的可以学习

SELU()

class torch.nn.SELU(inplace: bool = False)

公式:

其中

inplace:布尔型,是否进行原地操作,即直接修改输入的这里,而不创立新的张量

GELU()

class torch.nn.GELU(approximate: str = "none")

approximate:选择使用合适的算法,默认为none,精确模式 (approximate='none');近似模式 (approximate='tanh' )

参数模式 计算方法 核心特点 典型应用场景
精确模式 x*Φ(x),其中Φ(x)为标准正态分布的累积分布函数 ,通常通过误差函数erf计算。 计算精度高,但涉及特殊函数 erf,计算开销较大。 对数值精度要求极高的实验或理论研究。
近似模式 计算速度快,仅由初等运算构成,易于优化,在多数硬件(尤其是GPU)上性能更好。精度与精确公式差异极小,通常不影响模型收敛和效果。 绝大多数实际训练和推理的默认选择

在实际模型训练中,通常将approximate设为'tanh',这是兼顾效率与效果的最佳实践

SiLU()

class torch.nn.SiLU(inplace: bool = False)

公式:

其中是Sigmoid()函数

inplace:布尔型,是否进行原地操作,即直接修改输入的这里,而不创立新的张量

主要应用--以图像处理为主
代表函数 图像处理中的主要应用场景 核心特点与说明
ReLU 绝大多数CNN的隐藏层默认选择 计算高效,缓解梯度消失,但可能导致"神经元死亡"。
LeakyReLU/PReLU 尝试解决ReLU"神经元死亡"问题 为负输入引入一个小梯度,PReLU的斜率可学习。
SELU/GELU/SiLU 部分现代先进架构(如Vision Transformer) 具有自归一化或更平滑的特性,性能可能更好但计算稍复杂。
Sigmoid 网络输出层(用于二分类、像素级分类) 将输出压缩到(0,1),可直接解释为概率。
Tanh 现已较少用于CNN隐藏层,多见于RNN 输出以0为中心,但仍有梯度消失问题。
Softmax 网络输出层(用于图像多分类) 将输出归一化为概率分布,总和为1。

而对于ReLU()函数及其变种函数

激活函数 核心特性与设计意图 典型应用场景/备注
LeakyReLU 针对ReLU的"神经元死亡"问题,为负输入引入一个固定的小斜率α(如0.01),确保梯度始终非零。 通用CNN/MLP,作为ReLU的直接替代品,性能稳定。α通常设为0.01。
PReLU 将LeakyReLU的斜率α变为可学习的参数,让网络自适应决定负区的最佳斜率,灵活性更强。 希望模型自行决定负区特性时。需要留意,增加少量可学习参数。
SELU 具有自归一化特性:在特定权重初始化下,能保持数据在前向/反向传播中的均值和方差稳定。 深层全连接网络。需配合LeCun正态初始化等特定初始化,效果才显著。
GELU 通过概率视角设计:用输入相对于其他输入的"百分比"来对其进行门控。平滑、非单调。 Transformer架构的默认选择(如BERT、GPT、Vision Transformer)。性能出色。
SiLU Google发现,是平滑、非单调的激活函数。可看作x与其"门控"sigmoid的乘积,有自门控特性。 替代ReLU的强力候选者,在某些任务上优于ReLU。Swish特指β=1的版本。

损失函数

类型

Pytorch nn的模块也包括损失函数类型

均方误差损失--nn.MSELoss

class torch.nn.MESLoss(reduction: str = "mean")

def forward(self, input: Tensor, target: Tensor)

公式:loss = mean(y_pred - y_true)^2

reduction:指定应用输出的缩减方式,"none"表示不进行缩减;"mean"表示求取平均值;"sum"表示求取输出的和

input:需要计算损失函数的张量

target:计算出损失函数的张量

L1损失--L1Loss

核心计算预测值与真实值之间绝对误差的平均值 / 总和,因对异常值鲁棒、计算简单,常应用于对误差 "线性惩罚" 的回归场景,也是SmoothL1Loss的基础。因此通常也使用SmoothL1Loss。

class torch.nn.L1Loss(reduction: str = "mean")

def forward(self, input: Tensor, target: Tensor)

公式:

其中N为样本总数(像素总数)

reduction:指定应用于输出的缩减方式,"none"表示不进行缩减;"mean"表示求取平均值;"sum"表示求取输出的和

input:需要计算损失函数的张量

target:计算出损失函数的张量

平滑L1损失--nn.SmoothL1Loss

class torch.nn.SmoothL1Loss(reduction: str = "mean", beta: float = 1.0)

def forward(self, input: Tensor, target: Tensor)

公式:其中

reduction:指定应用于输出的缩减方式,"none"表示不进行缩减;"mean"表示求取平均值;"sum"表示求取输出的和

beta:指定L1损失和L2损失之间切换的阈值

input:需要计算损失函数的张量

target:计算出损失函数的张量

感知损失--nn.PerceptualLoss

公式:假设:预训练CNN的特征提取函数

:图形x在第l层的特征图

:模型预测图像;:真实图像

其中:第l层1特征图的通道/高度/宽度

由于PerceptualLoss特别复杂,需要通过CNN采集特征,因此这里对其进行轻量化,其与L1Loss使用相同的函数,但是L1Loss损失直接对源数据进行损失计算,而PerceptualLoss需要先采集特征,再使用L1Loss计算损失,这个损失是特征之间的损失,更加精确

复制代码
import torch
import torch.nn as nn
import torchvision.models as models

class PerceptualLoss(nn.Module):
    def __init__(self, device='cpu'):  # 嵌入式端默认CPU/NPU
        super().__init__()
        # 1. 加载轻量预训练模型:MobileNetV2(替代VGG,减少参数)
        mobilenet = models.mobilenet_v2(pretrained=True).to(device)
        # 2. 提取浅层特征层(第2层,对应纹理/边缘,计算量小)
        self.feature_extractor = nn.Sequential(*list(mobilenet.features)[:2]).to(device)
        # 3. 冻结参数(仅提取特征,不训练)
        for param in self.feature_extractor.parameters():
            param.requires_grad = False
        # 4. 损失函数:L1(比MSE更鲁棒,嵌入式端运算更快)
        self.l1_loss = nn.L1Loss(reduction='mean')

    def forward(self, pred, target):
        # pred/target: [B, 3, H, W](归一化到0~1,符合预训练模型输入)
        # 提取特征
        pred_feat = self.feature_extractor(pred)
        target_feat = self.feature_extractor(target)
        # 计算特征层的L1损失(感知损失)
        return self.l1_loss(pred_feat, target_feat)

# 嵌入式端使用示例(模拟图像超分场景)
device = torch.device('cpu')  # 嵌入式端无GPU,用CPU/NPU
perceptual_loss = PerceptualLoss(device=device)

# 模拟输入:批量1,3通道,64x64(嵌入式端小尺寸)
pred_img = torch.randn(1, 3, 64, 64).to(device)
target_img = torch.randn(1, 3, 64, 64).to(device)

loss = perceptual_loss(pred_img, target_img)
print(f"PerceptualLoss (MobileNetV2版): {loss.item():.4f}")
交叉熵损失--nn.CrossEntropyLoss

class torch.nn.CrossEntropyLoss(weight: Optional[Tensor] = None, ignore_index: int = -100, reduction: str = "mean", label_smoothing: float = 0.0)

def forward(self, input: Tensor, target: Tensor)

公式:对于二分类场景,真实标签,其中模型预测为正类的概率为p

对于多分类场景,假设类别数为C,真实标签是独热编码形式y(输入不需要转化为独热编码,函数(自动转化),模型预测的概率分布为p(由softmax得到,满足)

weight:张量,给每个类别手动设置的重新缩放权重。

ignore_index:指定一个被忽略且不计入输入梯度的目标值。

reduction:指定应用于输出的缩减方式,"none"表示不进行缩减;"mean"表示求取平均值;"sum"表示求取输出的和

label_smoothing:一个在 [0.0, 1.0] 范围内的浮点数。指定计算损失时的平滑程度,其中 0.0 表示不进行平滑。

input:需要计算损失函数的张量

target:计算出损失函数的张量

骰子损失--nn.DiceLoss

在pytorch中没有,但是其经常与交叉熵损失(CrossEntropyLoss)一起使用,或者直接使用monai库

二分类/单通道分割

公式:设预测概率图P(经过Sigmoid归一化到[0,1]),真实标签图为T(0表示背景,1表示目标),为平滑项,通常取1e-6,其中

:预测与真实标签的交集即预测与真实标签逐元素相乘后求和()

:预测结果和真实标签的元素和()

其中分别为第c类的预测概率图和真实标签图(真实标签转为独热编码)

复制代码
import torch
import torch.nn as nn

class DiceLoss(nn.Module):
    def __init__(self, eps=1e-6):
        super(DiceLoss, self).__init__()
        self.eps = eps

    def forward(self, pred, target):
        # pred: [batch_size, 1, H, W](经过 Sigmoid 后)
        # target: [batch_size, 1, H, W](0/1 标签)
        intersection = (pred * target).sum()  # 交集求和
        union = pred.sum() + target.sum()     # 并集求和
        dice_coeff = (2 * intersection + self.eps) / (union + self.eps)
        return 1 - dice_coeff

# 使用示例
dice_loss = DiceLoss()
pred = torch.sigmoid(torch.randn(2, 1, 32, 32))
target = torch.randint(0, 2, (2, 1, 32, 32), dtype=torch.float32)
loss = dice_loss(pred, target)
print(f"DiceLoss: {loss.item():.4f}")
多通道分割

对于C个类别的分割任务,其中分别为第c类的预测概率图和真实标签图(真实标签转为独热编码--one_hot)

复制代码
class MultiClassDiceLoss(nn.Module):
    def __init__(self, num_classes=3, eps=1e-6):
        super(MultiClassDiceLoss, self).__init__()
        self.num_classes = num_classes
        self.eps = eps

    def forward(self, pred, target):
        # pred: [batch_size, num_classes, H, W]
        # target: [batch_size, H, W](类别索引,如 0,1,2)
        total_loss = 0.0
        # 真实标签转为独热编码
        target_onehot = nn.functional.one_hot(target.long(), num_classes=self.num_classes)
        target_onehot = target_onehot.permute(0, 3, 1, 2).float()  # [B, C, H, W]

        for c in range(self.num_classes):
            pred_c = pred[:, c:c+1, :, :]  # 第 c 类的预测概率
            target_c = target_onehot[:, c:c+1, :, :]  # 第 c 类的真实标签
            intersection = (pred_c * target_c).sum()
            union = pred_c.sum() + target_c.sum()
            dice_coeff = (2 * intersection + self.eps) / (union + self.eps)
            total_loss += (1 - dice_coeff)
        
        return total_loss / self.num_classes  # 平均每个类的损失

# 使用示例
multi_dice_loss = MultiClassDiceLoss(num_classes=3)
pred = torch.softmax(torch.randn(2, 3, 32, 32), dim=1)
target = torch.randint(0, 3, (2, 32, 32))
loss = multi_dice_loss(pred, target)
print(f"MultiClassDiceLoss: {loss.item():.4f}")
掩码损失--nn.MaskLoss
二值掩码损失--nn.BCEWithLogitsLoss

class torch.nn.SmoothL1Loss(weight: Optional[Tensor] = None, reduction: str = "mean", pos_weight: Optional[Tensor] = None)

def forward(self, input: Tensor, target: Tensor)

公式:其中:真实掩码在(i,j)像素的标签(0/1);:模型预测该像素为目标的概率(Sigmoid输出)

weight:张量,给每个类别手动设置的重新缩放权重。

reduction:指定应用于输出的缩减方式,"none"表示不进行缩减;"mean"表示求取平均值;"sum"表示求取输出的和

pos_weight:要与目标进行广播的正样本权重,必须是一个在类别维度上与类别数量大小相等的张量。

input:需要计算损失函数的张量

target:计算出损失函数的张量
对于大小为 [N,C,H,W] 的目标(其中N是批量大小),大小为 [N,C,H,W] 的 pos_weight 将为每个批次元素应用不同的 pos_weight,或者大小为 [C,H,W] 的 pos_weight 将在批次中应用相同的 pos_weight。

Dice-BCE混合MaskLoss

其本质就是DiceLoss和BCEWithLogitsLoss的混合,这样的组合即解决了不平衡,也保证了稳定性,同时其计算量也较小,针对于一些嵌入式设备也可以很好的承受

公式:其中为权重系数(通常去0.5,平衡两者,但是也可以根据自己的侧重点另外取值)

复制代码
class DiceBCEMaskLoss(nn.Module):
    def __init__(self, alpha=0.5, eps=1e-6):
        super().__init__()
        self.alpha = alpha
        self.eps = eps
        self.bce = nn.BCEWithLogitsLoss(reduction='mean')

    def dice_loss(self, pred, target):
        # 计算Dice系数(无额外参数,轻量化)
        intersection = (torch.sigmoid(pred) * target).sum()
        union = torch.sigmoid(pred).sum() + target.sum()
        return 1 - (2 * intersection + self.eps) / (union + self.eps)

    def forward(self, pred_mask, true_mask):
        bce_loss = self.bce(pred_mask, true_mask)
        dice_loss = self.dice_loss(pred_mask, true_mask)
        return self.alpha * bce_loss + (1 - self.alpha) * dice_loss

## 创建模型
mask_loss = DiceBCEMaskLoss(alpha=0.5)
pred_mask = torch.randn(2, 1, 64, 64)
true_mask = torch.randint(0, 2, (2, 1, 64, 64)).float()
# rand_like创建一个与true_mask相同维度大小的张量,范围为[0,1)
# 对于true_mask的元素,括号中的元素小于0.1则清零,反之则为保持(为1)
# true_mask * (torch.rand_like(true_mask) < 0.1)主要是为了定义一个是由少部分为目标,大部分为背景的一个图像
true_mask = true_mask * (torch.rand_like(true_mask) < 0.1).float() 

## 创建损失函数
loss = mask_loss(pred_mask, true_mask)
print(f"Dice-BCE MaskLoss: {loss.item():.4f}")
焦点损失--nn.FocalLoss

算是上面的交叉熵损失CrossEntropyLoss的改进版,通过 "降低易分类样本的权重、聚焦难分类样本",解决分类任务中 "多数类主导损失、少数类被忽略" 的问题

公式:模型预测为正类的概率为(其中正类为p,负类为1-p),其主要是在CrossEntropyLoss的基础上添加了调制系数和平衡因子

其中:类别平衡因子

:聚焦参数(常取为2),该值越大,对易样本(模型预测为正类的概率高的样本)的压制越强

复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F


class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2.0, num_classes=2, reduction='mean'):
        super().__init__()
        self.alpha = alpha  # 类别平衡因子
        self.gamma = gamma  # 聚焦参数
        self.num_classes = num_classes
        self.reduction = reduction  # 聚合方式

    def forward(self, logits, targets):
        # logits: [B, C](模型输出,未过Softmax/Sigmoid)
        # targets: [B](类别索引,0~C-1)
        B, C = logits.shape

        # 1. 计算真实类别的预测概率p_t
        if self.num_classes == 2:
            # 二分类:用sigmoid计算p_t
            p = torch.sigmoid(logits).squeeze(1)
            p_t = torch.where(targets == 1, p, 1 - p)
            alpha_t = torch.where(targets == 1, self.alpha, 1 - self.alpha)
        else:
            # 多分类:用softmax计算p_t
            p = F.softmax(logits, dim=1)
            p_t = p[range(B), targets]
            # 多分类α:可自定义,这里简化为均匀α=1/C
            alpha_t = torch.ones_like(p_t) / self.num_classes

        # 2. 计算聚焦系数((1-p_t)^γ)
        focal_weight = torch.pow(1 - p_t, self.gamma)

        # 3. 计算FocalLoss核心
        # 用clamp避免log(0)导致数值爆炸
        loss = -alpha_t * focal_weight * torch.log(torch.clamp(p_t, 1e-6, 1.0))

        # 4. 聚合损失
        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        else:
            return loss


# 二分类
focal_loss = FocalLoss(alpha=0.75, gamma=2, num_classes=2)
logits = torch.tensor([[1.2, -0.5], [-0.3, 2.1]], dtype=torch.float32)
targets = torch.tensor([0, 1], dtype=torch.long)
loss = focal_loss(logits, targets)
print(f"FocalLoss(二分类): {loss.item():.4f}")

# 3类缺陷
focal_loss_multi = FocalLoss(alpha=0.3, gamma=2, num_classes=3)
logits_multi = torch.tensor([[1.0, 0.5, -0.2], [-0.1, 2.0, 0.8]], dtype=torch.float32)
targets_multi = torch.tensor([0, 2], dtype=torch.long)
loss_multi = focal_loss_multi(logits_multi, targets_multi)
print(f"FocalLoss(多分类): {loss_multi.item():.4f}")
交并比损失--nn.IoULoss

核心直接优化预测框与真实框的交并比,这让损失函数与检测任务的核心评估指标完全对齐,能显著提升边界框的回归精度。其中IoU是衡量两个框(预测框​、真实框)重叠程度的核心指标,取值范围 [0,1],越接近 1 表示重叠度越高

公式:

其中:平滑项,通常取1e-6,一般都需要取值,避免并集为0时的除零错误

框格式:一般都使用xyxy格式即左上x轴,左上y轴,右下x轴,右下y轴,需要统一预测框和真实框的格式

输入和输出的维度都统一为[N,4],N表示输入和输出的个数,4表示该框的格式为xyxy

复制代码
import torch
import torch.nn as nn


class IoULoss(nn.Module):
    def __init__(self, eps=1e-6, reduction='mean'):
        super().__init__()
        self.eps = eps  # 避免除零
        self.reduction = reduction

    def forward(self, pred_boxes, target_boxes):
        # 1. 提取坐标
        x1_p, y1_p, x2_p, y2_p = pred_boxes[:, 0], pred_boxes[:, 1], pred_boxes[:, 2], pred_boxes[:, 3]
        x1_t, y1_t, x2_t, y2_t = target_boxes[:, 0], target_boxes[:, 1], target_boxes[:, 2], target_boxes[:, 3]

        # 2. 计算交集坐标
        x1_inter = torch.max(x1_p, x1_t)
        y1_inter = torch.max(y1_p, y1_t)
        x2_inter = torch.min(x2_p, x2_t)
        y2_inter = torch.min(y2_p, y2_t)

        # 3. 计算交集面积
        inter_area = (x2_inter - x1_inter).clamp(min=0) * (y2_inter - y1_inter).clamp(min=0)

        # 4. 计算预测框/真实框面积
        pred_area = (x2_p - x1_p).clamp(min=0) * (y2_p - y1_p).clamp(min=0)
        target_area = (x2_t - x1_t).clamp(min=0) * (y2_t - y1_t).clamp(min=0)

        # 5. 计算并集面积 + 平滑项
        union_area = pred_area + target_area - inter_area + self.eps

        # 6. 计算IoU和IoULoss
        iou = inter_area / union_area
        iou_loss = 1 - iou

        # 7. 聚合损失
        if self.reduction == 'mean':
            return iou_loss.mean()
        elif self.reduction == 'sum':
            return iou_loss.sum()
        else:
            return iou_loss


# 工业缺陷检测:单框回归
iou_loss = IoULoss(eps=1e-6)
# 模拟输入--两个框
pred_boxes = torch.tensor([[10, 20, 30, 40], [50, 60, 70, 80]], dtype=torch.float32)
target_boxes = torch.tensor([[12, 22, 32, 42], [51, 61, 71, 81]], dtype=torch.float32)
loss = iou_loss(pred_boxes, target_boxes)
print(f"IoULoss: {loss.item():.4f}")
中心距交并比损失--nn.DIoULoss

DIoULoss在IoU基础上加入中心距离惩罚项,即使预测框与真实框无交集,也能通过 "中心距离" 产生梯度,迫使框快速对齐

公式:

其中IoU表示预测框与真实框的交并比为

表示预测框中心与真实框中心的欧式距离平方

表示预测框和真实框的最小包围框对角线长度的平方

复制代码
import torch
import torch.nn as nn


class DIoULoss(nn.Module):
    def __init__(self, eps=1e-6, reduction='mean'):
        super().__init__()
        self.eps = eps  # 避免除零,嵌入式必加
        self.reduction = reduction  # 嵌入式优先mean,减少运算

    def forward(self, pred_boxes, target_boxes):
        # 1. 提取坐标并校验
        x1_p, y1_p, x2_p, y2_p = pred_boxes[:, 0], pred_boxes[:, 1], pred_boxes[:, 2], pred_boxes[:, 3]
        x1_t, y1_t, x2_t, y2_t = target_boxes[:, 0], target_boxes[:, 1], target_boxes[:, 2], target_boxes[:, 3]

        # 坐标范围校验,假设图像宽度128
        x1_p = torch.clamp(x1_p, min=0)
        y1_p = torch.clamp(y1_p, min=0)
        x2_p = torch.clamp(x2_p, max=127)
        y2_p = torch.clamp(y2_p, max=127)

        # 确保x1<=x2,y1<=y2
        x1_p, x2_p = torch.min(x1_p, x2_p), torch.max(x1_p, x2_p)
        y1_p, y2_p = torch.min(y1_p, y2_p), torch.max(y1_p, y2_p)
        x1_t, x2_t = torch.min(x1_t, x2_t), torch.max(x1_t, x2_t)
        y1_t, y2_t = torch.min(y1_t, y2_t), torch.max(y1_t, y2_t)

        # 2. 计算IoU
        # 交集坐标
        x1_inter = torch.max(x1_p, x1_t)
        y1_inter = torch.max(y1_p, y1_t)
        x2_inter = torch.min(x2_p, x2_t)
        y2_inter = torch.min(y2_p, y2_t)
        # 交集面积(无交集时为0)
        inter_area = (x2_inter - x1_inter).clamp(min=0) * (y2_inter - y1_inter).clamp(min=0)
        # 预测框/真实框面积
        pred_area = (x2_p - x1_p) * (y2_p - y1_p)
        target_area = (x2_t - x1_t) * (y2_t - y1_t)
        # IoU计算
        union_area = pred_area + target_area - inter_area + self.eps
        iou = inter_area / union_area

        # 3. 计算中心距离d²和包围框对角线D²
        # 中心坐标
        cx_p, cy_p = (x1_p + x2_p) / 2, (y1_p + y2_p) / 2
        cx_t, cy_t = (x1_t + x2_t) / 2, (y1_t + y2_t) / 2
        # 中心距离平方
        d2 = (cx_p - cx_t) ** 2 + (cy_p - cy_t) ** 2
        # 最小包围框对角线平方
        x1_c = torch.min(x1_p, x1_t)
        y1_c = torch.min(y1_p, y1_t)
        x2_c = torch.max(x2_p, x2_t)
        y2_c = torch.max(y2_p, y2_t)
        D2 = (x2_c - x1_c) ** 2 + (y2_c - y1_c) ** 2 + self.eps  # 加eps避免除零

        # 4. 计算DIoULoss
        diou = iou - (d2 / D2)
        diou_loss = 1 - diou

        # 5. 聚合损失
        if self.reduction == 'mean':
            return diou_loss.mean()
        elif self.reduction == 'sum':
            return diou_loss.sum()
        else:
            return diou_loss


# 工业裂纹检测,xyxy格式
diou_loss = DIoULoss(eps=1e-6)
# 模拟预测框
pred_boxes = torch.tensor([[10, 20, 30, 40], [50, 60, 70, 80]], dtype=torch.float32)
# 模拟真实框
target_boxes = torch.tensor([[12, 22, 32, 42], [51, 61, 71, 81]], dtype=torch.float32)
loss = diou_loss(pred_boxes, target_boxes)
print(f"DIoULoss(工业裂纹检测): {loss.item():.4f}")
边界损失--nn.BoundaryLoss

核心解决普通分割损失(如DiceLoss、CrossEntropyLoss)"只关注像素类别、忽略边界细节" 的问题 ------ 通过聚焦图像中目标与背景的边界像素,强制模型学习边界的精细特征,最终提升分割结果的边缘清晰度。

边界掩码加权

公式:

或者直接进行更简单的加权形式

其中BCE表示上述的二值掩码损失nn.BCEWithLogitsLoss

B表示边界像素集合

表示权重(边界像素=k,k大于等于2;非边界=1)

表示边界像素数量;N表示总像素数量

工业上一般混合DiceLoss损失使用

复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

class BoundaryLoss(nn.Module):
    def __init__(self, boundary_weight=5.0, eps=1e-6):
        super().__init__()
        self.boundary_weight = boundary_weight  # 边界像素权重
        self.eps = eps
        # 3x3膨胀核
        self.dilate_kernel = torch.ones(1, 1, 3, 3, dtype=torch.float32)

    def _extract_boundary_mask(self, mask):
        # 膨胀操作
        mask_dilated = F.conv2d(mask, self.dilate_kernel, padding=1)
        mask_dilated = (mask_dilated > 0).float()
        # 边界掩码 = 膨胀掩码 - 原始掩码
        boundary_mask = (mask_dilated - mask).clamp(min=0)
        return boundary_mask

    def forward(self, pred, target):
        B, C, H, W = pred.shape
        # 1. 提取边界掩码
        self.dilate_kernel = self.dilate_kernel.to(target.device)
        boundary_mask = self._extract_boundary_mask(target)
        non_boundary_mask = 1 - boundary_mask

        # 2. 计算像素级BCE损失
        pred_sigmoid = torch.sigmoid(pred)
        bce = - (target * torch.log(pred_sigmoid + self.eps) +
                (1 - target) * torch.log(1 - pred_sigmoid + self.eps))

        # 3. 加权计算边界损失:边界像素高权重,非边界低权重
        boundary_loss = (bce * boundary_mask * self.boundary_weight).sum()
        non_boundary_loss = (bce * non_boundary_mask).sum()
        # 归一化:避免边界像素数量少导致损失占比过高
        total_pixels = boundary_mask.sum() + non_boundary_mask.sum() + self.eps
        loss = (boundary_loss + non_boundary_loss) / total_pixels

        return loss

# 混合损失:BoundaryLoss + DiceLoss
class SegmentationLoss(nn.Module):
    def __init__(self, boundary_weight=5.0, dice_weight=0.5, boundary_loss_weight=0.5):
        super().__init__()
        self.boundary_loss = BoundaryLoss(boundary_weight=boundary_weight)
        self.dice_loss = DiceLoss()  # 复用之前定义的DiceLoss
        self.dice_w = dice_weight
        self.boundary_w = boundary_loss_weight

    def forward(self, pred, target):
        dice_loss = self.dice_loss(pred, target)
        boundary_loss = self.boundary_loss(pred, target)
        return self.dice_w * dice_loss + self.boundary_w * boundary_loss

class DiceLoss(nn.Module):
    def __init__(self, eps=1e-6):
        super().__init__()
        self.eps = eps

    def forward(self, pred, target):
        pred_sigmoid = torch.sigmoid(pred)
        intersection = (pred_sigmoid * target).sum()
        union = pred_sigmoid.sum() + target.sum()
        return 1 - (2 * intersection + self.eps) / (union + self.eps)

# 模拟输入
seg_loss = SegmentationLoss(boundary_weight=3.0)
pred = torch.randn(2, 1, 64, 64)
target = torch.randint(0, 2, (2, 1, 64, 64)).float()
loss = seg_loss(pred, target)
print(f"SegmentationLoss(Dice+Boundary): {loss.item():.4f}")
使用场景
损失类型 具体损失函数 核心解决问题 适用任务(工业 CV) 核心特点(嵌入式视角)
像素级基础损失 L1Loss 简单数值回归,像素级误差衡量 简单图像回归(如低精度超分) 计算量极低,嵌入式极易部署
分类专用损失 CrossEntropyLoss 普通类别平衡的分类 常规缺陷分类(背景 / 缺陷二分类) 计算量低,嵌入式易部署
FocalLoss 类别不平衡 + 难样本挖掘(如小缺陷) 工业缺陷检测(小目标 / 不平衡分类) 略增计算量(幂运算),需轻量化,嵌入式核心选型
分割专用损失 DiceLoss 分割任务类别不平衡(如缺陷掩码) 缺陷分割(裂纹 / 划痕掩码) 计算量低,嵌入式易部署
BoundaryLoss 分割边界精度优化(如缺陷边缘) 缺陷分割(裂纹 / 零件轮廓) 略增计算量(边界提取),需轻量化边界提取
检测框回归损失 SmoothL1Loss 框坐标回归(鲁棒性) 目标检测框回归(基础版) 计算量低,嵌入式易部署
IoULoss/DIoULoss 直接优化 IoU(框重叠度) 高精度缺陷检测框回归(核心) 略增计算量(面积 / 距离计算),嵌入式优先 DIoULoss
生成 / 增强专用损失 PerceptualLoss 高层语义特征匹配(视觉保真) 图像超分 / 修复(内容保真) 中等计算量(CNN 特征提取),需轻量化模型

Pytorch nn模块

正如上文所说,pytorch有一个专门用于神经网络的子模块,即nn模块,而nn模块除了上述的激活函数外,还有一个非常重要且基础的基类,也就是Module,其实Pytorch中所有神经网络模块的基类

nn.Linear

class torch.nn.Linear(in_features: int,

out_features: int,

bias: bool = True,

device=None,

dtype=None)

in_features:输入样本特征维度的大小,也是上一层的输出样本特征维度的大小

out_features:输出样本特征维度的大小,也是下一层的输入样本特征维度的大小

bias:该层神经网络是否学习一个偏置即b,默认为True

device:运行设备

dtype:数据类型

当为True时,y=wx+b

x是输入,形状为 (..., in_features)... 表示任意的批次维度。

y是输出,形状为 (..., out_features)
nn.Linear是nn.Module的一个子类,其主要是通过参数属性、权重和偏置对输入应用仿射变换,也就是我们上一篇对于温度计的表述

复制代码
import torch
import torch.optim as optim
 
def model(t_u, w, b):
    return t_u * w + b
 
def loss_fn(t_p, t_c):
    loss = (t_p - t_c) ** 2
    return loss.mean()
 
def training_loop(n_epochs, optimizer, params, train_t_c, val_t_c, train_t_u, val_t_u):
    """训练模型"""
    for epoch in range(1, n_epochs + 1):
        train_t_p = model(train_t_u, *params)
        train_loss = loss_fn(train_t_p, train_t_c)
 
        val_t_p = model(val_t_u, *params)
        val_loss = loss_fn(val_t_p, val_t_c)
 
        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()
 
        print('Epoch %d, Training Loss %f, Validation Loss %f' % (epoch, float(train_loss), float(val_loss)))
 
    return params
 
# 实际值(期望值)和输入值
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
 
# 交叉验证
t_un = t_u * 0.01
train_t_c = t_c[0:8]
val_t_c = t_c[8:12]
train_t_u = t_un[0:8]
val_t_u = t_un[8:12]
 
 
params = torch.tensor([1.0, 0.0], requires_grad = True)
learning_rate = 0.1
optimizer = optim.SGD([params], lr = learning_rate)
 
training_loop(n_epochs = 5000, optimizer = optimizer, params = params, train_t_c = train_t_c, val_t_c = val_t_c, train_t_u = train_t_u, val_t_u = val_t_u)

这里面我们是没有引入nn模块的,接下来便是引入nn模块修改它

复制代码
import torch
import torch.optim as optim
import torch.nn as nn


def training_loop(n_epochs, optimizer, model, loss_fn, train_t_c, val_t_c, train_t_u, val_t_u):
    """训练模型"""
    for epoch in range(1, n_epochs + 1):
        train_t_p = model(train_t_u)
        train_loss = loss_fn(train_t_p, train_t_c)

        val_t_p = model(val_t_u)
        val_loss = loss_fn(val_t_p, val_t_c)

        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()

        print('Epoch %d, Training Loss %f, Validation Loss %f' % (epoch, float(train_loss), float(val_loss)))

# 实际值(期望值)和输入值
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)

# 交叉验证
t_un = t_u.sum() / 11
t_un = t_u / t_un
train_t_c = t_c[0:8].unsqueeze(1)
val_t_c = t_c[8:12].unsqueeze(1)
train_t_u = t_un[0:8].unsqueeze(1)
val_t_u = t_un[8:12].unsqueeze(1)


# 构造模型
linear_model = nn.Linear(1, 1)
loss_fn = nn.MSELoss()

learning_rate = 0.01
optimizer = optim.SGD(params = linear_model.parameters(), lr = learning_rate)

training_loop(n_epochs = 5000, optimizer = optimizer, model = linear_model, loss_fn = loss_fn, train_t_c = train_t_c, val_t_c = val_t_c, train_t_u = train_t_u, val_t_u = val_t_u)

print(linear_model.weight)
print(linear_model.bias)

nn.Sequential

用于将多个网络层按顺序打包成一个整体的、可调用的模块。当输入数据"流经"这个模块时,它会严格按照你定义的顺序,依次通过每一层。同时其会自动生成forward方法,不需要独自去定义

复制代码
model = nn.Sequential(nn.Conv2d(1,20,5),nn.ReLU(),nn.Conv2d(20,64,5),nn.ReLU())

输入数据首先会传递给 Conv2d(1,20,5)。Conv2d(1,20,5)的输出将用作第一个ReLU的输入;第一个ReLU的输出将成为Conv2d(20,64,5)的输入。最后Conv2d(20,64,5)的输出将用作第二个ReLU的输入。

一个更好的对比

复制代码
import torch.nn as nn

# ========== 方法一:标准的 nn.Module 子类写法(手动定义forward) ==========
class NetManual(nn.Module):
    def __init__(self, in_features=10, out_features=2):
        super().__init__()
        self.fc1 = nn.Linear(in_features, 50)  # 第一层
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(50, 20)           # 第二层
        self.relu2 = nn.ReLU()
        self.out = nn.Linear(20, out_features) # 输出层

    def forward(self, x):
        # 必须手动写出每一层的调用顺序和连接方式
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.fc2(x)
        x = self.relu2(x)
        x = self.out(x)
        return x

model_manual = NetManual()

# ========== 方法二:使用 nn.Sequential 写法 ==========
# 优点:代码极其简洁,自动定义forward
model_seq = nn.Sequential(
    nn.Linear(10, 50),
    nn.ReLU(),
    nn.Linear(50, 20),
    nn.ReLU(),
    nn.Linear(20, 2)
)

# model_manual.out == model_seq[-1]

如果你的网络有跳跃、多分支或多输入多输出,仍然需要自定义 nn.Module并在forward中描述复杂逻辑。同时数据必须依次经过序列中的每一层,无法根据条件跳过某一层。

以下是两个完整的数据拟合代码

python 复制代码
from typing import OrderedDict

import torch
import torch.optim as optim
import torch.nn as nn
from matplotlib import pyplot as plt
import time

def training_loop(n_epochs, optimizer, model, loss_fn, train_t_c, val_t_c, train_t_u, val_t_u):
    """训练模型"""
    for epoch in range(1, n_epochs + 1):
        train_t_p = model(train_t_u)
        train_loss = loss_fn(train_t_p, train_t_c)

        val_t_p = model(val_t_u)
        val_loss = loss_fn(val_t_p, val_t_c)

        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()

        print('Epoch %d, Training Loss %f, Validation Loss %f' % (epoch, float(train_loss), float(val_loss)))

# 实际值(期望值)和输入值
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)

# 交叉验证
t_u_mean = t_u.mean()
t_u_std = t_u.std()
t_un = (t_u - t_u_mean)/t_u_std

train_t_c = t_c[0:8].unsqueeze(1)
val_t_c = t_c[8:12].unsqueeze(1)
train_t_u = t_un[0:8].unsqueeze(1)
val_t_u = t_un[8:12].unsqueeze(1)

# 构造模型
loss_fn = nn.MSELoss()
seq_model = nn.Sequential(OrderedDict([
    ('hidden_linear', nn.Linear(1, 11)),
    ('hidden_activation', nn.Tanh()),
    ('output_linear', nn.Linear(11, 1))
]))


learning_rate = 0.01
optimizer = optim.SGD(params = seq_model.parameters(), lr = learning_rate)

epoch_start = time.time()
training_loop(n_epochs = 100, optimizer = optimizer, model = seq_model, loss_fn = loss_fn, train_t_c = train_t_c, val_t_c = val_t_c, train_t_u = train_t_u, val_t_u = val_t_u)
epoch_end = time.time()
total_time = epoch_end - epoch_start

print(f"总训练时间: {total_time:.2f} 秒")

print('output', seq_model(val_t_u))
print('answer', val_t_c)
print('hidden', seq_model.hidden_linear.weight.grad)

# 绘制结果
t_range = torch.arange(20., 90.).unsqueeze(1)  # 形状: (70, 1)
# 对t_range使用相同的标准化
t_range_normalized = (t_range - t_u_mean) / t_u_std

fig = plt.figure(dpi=100)
plt.xlabel('Fahrenheit')
plt.ylabel('Celsius')
plt.plot(t_u.numpy(), t_c.numpy(), 'o', label='Original data')
plt.plot(t_range.numpy(), seq_model(t_range_normalized).detach().numpy(), 'c-', label='Model prediction')
plt.plot(t_u.numpy(), seq_model(t_un.unsqueeze(1)).detach().numpy(), 'kx', label='Predicted points')
plt.legend()
plt.show()

下面的代码是一个拥有11个指标和1个输出的数据集,每个类型有接近5000个值

python 复制代码
from typing import OrderedDict

import torch
import torch.optim as optim
import torch.nn as nn
from matplotlib import pyplot as plt
import time

def training_loop(n_epochs, optimizer, model, loss_fn, train_t_c, val_t_c, train_t_u, val_t_u):
    """训练模型"""
    for epoch in range(1, n_epochs + 1):
        train_t_p = model(train_t_u)
        train_loss = loss_fn(train_t_p, train_t_c)

        val_t_p = model(val_t_u)
        val_loss = loss_fn(val_t_p, val_t_c)

        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()

        print('Epoch %d, Training Loss %f, Validation Loss %f' % (epoch, float(train_loss), float(val_loss)))

# 实际值(期望值)和输入值
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)

# 交叉验证
t_u_mean = t_u.mean()
t_u_std = t_u.std()
t_un = (t_u - t_u_mean)/t_u_std

train_t_c = t_c[0:8].unsqueeze(1)
val_t_c = t_c[8:12].unsqueeze(1)
train_t_u = t_un[0:8].unsqueeze(1)
val_t_u = t_un[8:12].unsqueeze(1)

# 构造模型
loss_fn = nn.MSELoss()
seq_model = nn.Sequential(OrderedDict([
    ('hidden_linear', nn.Linear(1, 11)),
    ('hidden_activation', nn.Tanh()),
    ('output_linear', nn.Linear(11, 1))
]))


learning_rate = 0.01
optimizer = optim.SGD(params = seq_model.parameters(), lr = learning_rate)

epoch_start = time.time()
training_loop(n_epochs = 100, optimizer = optimizer, model = seq_model, loss_fn = loss_fn, train_t_c = train_t_c, val_t_c = val_t_c, train_t_u = train_t_u, val_t_u = val_t_u)
epoch_end = time.time()
total_time = epoch_end - epoch_start

print(f"总训练时间: {total_time:.2f} 秒")

print('output', seq_model(val_t_u))
print('answer', val_t_c)
print('hidden', seq_model.hidden_linear.weight.grad)

# 绘制结果
t_range = torch.arange(20., 90.).unsqueeze(1)  # 形状: (70, 1)
# 对t_range使用相同的标准化
t_range_normalized = (t_range - t_u_mean) / t_u_std

fig = plt.figure(dpi=100)
plt.xlabel('Fahrenheit')
plt.ylabel('Celsius')
plt.plot(t_u.numpy(), t_c.numpy(), 'o', label='Original data')
plt.plot(t_range.numpy(), seq_model(t_range_normalized).detach().numpy(), 'c-', label='Model prediction')
plt.plot(t_u.numpy(), seq_model(t_un.unsqueeze(1)).detach().numpy(), 'kx', label='Predicted points')
plt.legend()
plt.show()
相关推荐
逐云者1232 小时前
构建高效任务中心:CDC 数据同步的工程实践与架构权衡
人工智能·架构·大模型·数据中心·cdc·任务中心·大数据同步
小桥流水---人工智能2 小时前
多模型统一导出 t-SNE 可视化数据的工程实践(1DCNN / DAN / DNN / DRN / Transformer)
人工智能·transformer·dnn
学术小白人2 小时前
IEEE出版|2026年人工智能与社交网络系统国际学术会议(AISNS 2026)
大数据·人工智能·科技·物联网·rdlink研发家
*星星之火*2 小时前
【大白话 AI 答疑】第10篇 数学可视化网站汇总
人工智能
啊阿狸不会拉杆2 小时前
《数字图像处理》-实验1
图像处理·人工智能·算法·计算机视觉·数字图像处理
糖炒狗子2 小时前
Textin模型加速器+火山引擎打造商业计划书智能体
人工智能·火山引擎
谅望者2 小时前
数据分析笔记15:Python模块、包与异常处理
开发语言·人工智能·python
小徐Chao努力2 小时前
【Langchain4j-Java AI开发】05-对话记忆管理
android·java·人工智能
lbb 小魔仙2 小时前
FP8魔力解锁:SD3.5 图像编辑、修复与增强全栈实战
人工智能·python·ai