wangwenguang commited on
Commit
aed9c73
·
verified ·
1 Parent(s): c8a7586

Upload 16 files

Browse files
model/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ #
model/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (149 Bytes). View file
 
model/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (150 Bytes). View file
 
model/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (148 Bytes). View file
 
model/__pycache__/resnet_backbone.cpython-310.pyc ADDED
Binary file (5.77 kB). View file
 
model/__pycache__/resnet_backbone.cpython-311.pyc ADDED
Binary file (9.66 kB). View file
 
model/__pycache__/resnet_backbone.cpython-38.pyc ADDED
Binary file (5.82 kB). View file
 
model/__pycache__/unet_resnet.cpython-310.pyc ADDED
Binary file (3.08 kB). View file
 
model/__pycache__/unet_resnet.cpython-311.pyc ADDED
Binary file (5.27 kB). View file
 
model/__pycache__/unet_resnet.cpython-38.pyc ADDED
Binary file (3.06 kB). View file
 
model/__pycache__/unet_training.cpython-310.pyc ADDED
Binary file (5.41 kB). View file
 
model/__pycache__/unet_training.cpython-311.pyc ADDED
Binary file (9.55 kB). View file
 
model/__pycache__/unet_training.cpython-38.pyc ADDED
Binary file (5.37 kB). View file
 
model/resnet_backbone.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch.nn as nn
3
+
4
+
5
+
6
+ def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
7
+ """3x3 convolution with padding
8
+
9
+ Args:
10
+ in_planes (int): 输入通道数
11
+ out_planes (int): 输出通道数
12
+ stride (int): 卷积步幅,默认为 1
13
+ groups (int): 卷积分组数,默认为 1
14
+ dilation (int): 卷积扩张系数,默认为 1
15
+
16
+ Returns:
17
+ nn.Conv2d: 定义的 3x3 卷积层
18
+ """
19
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
20
+ padding=dilation, groups=groups, bias=False, dilation=dilation)
21
+
22
+
23
+ def conv1x1(in_planes, out_planes, stride=1):
24
+ """1x1 convolution
25
+ Args:
26
+ in_planes (int): 输入通道数
27
+ out_planes (int): 输出通道数
28
+ stride (int): 步幅,默认为 1
29
+
30
+ Returns:
31
+ nn.Conv2d: 定义的 1x1 卷积层
32
+ """
33
+ return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
34
+
35
+ class Bottleneck(nn.Module):
36
+ expansion = 4
37
+
38
+ def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
39
+ base_width=64, dilation=1, norm_layer=None):
40
+ """
41
+ 初始化 Bottleneck 模块
42
+
43
+ Args:
44
+ inplanes (int): 输入通道数
45
+ planes (int): 输出通道数(卷积层的通道数)
46
+ stride (int): 卷积步幅,默认为 1
47
+ downsample (nn.Module, optional): 用于调整输入尺寸的层(例如:当步幅不为 1 时,需要下采样)
48
+ groups (int): 分组卷积的数量,默认为 1
49
+ base_width (int): 基础宽度,影响每层的宽度
50
+ dilation (int): 卷积扩张系数,默认为 1
51
+ norm_layer (nn.Module, optional): 归一化层类型,默认为 `nn.BatchNorm2d`
52
+ """
53
+ super(Bottleneck, self).__init__()
54
+
55
+ # 如果没有提供 norm_layer,则使用 BatchNorm2d
56
+ if norm_layer is None:
57
+ norm_layer = nn.BatchNorm2d
58
+
59
+ # 计算宽度,groups 会影响每个卷积层的通道数
60
+ width = int(planes * (base_width / 64.)) * groups
61
+
62
+ # 第一层 1x1 卷积,作用是降维
63
+ self.conv1 = conv1x1(inplanes, width)
64
+ self.bn1 = norm_layer(width) # 归一化层
65
+ # 第二层 3x3 卷积,作用是进行空间下采样
66
+ self.conv2 = conv3x3(width, width, stride, groups, dilation)
67
+ self.bn2 = norm_layer(width) # 归一化层
68
+ # 第三层 1x1 卷积,作用是升维
69
+ self.conv3 = conv1x1(width, planes * self.expansion)
70
+ self.bn3 = norm_layer(planes * self.expansion) # 归一化层
71
+
72
+ # 激活函数 ReLU
73
+ self.relu = nn.ReLU(inplace=True)
74
+
75
+ # 下采样层,默认为 None
76
+ self.downsample = downsample
77
+ # 存储步幅
78
+ self.stride = stride
79
+
80
+ def forward(self, x):
81
+ """
82
+ 正向传播
83
+
84
+ Args:
85
+ x (Tensor): 输入张量
86
+
87
+ Returns:
88
+ Tensor: 输出张量
89
+ """
90
+ identity = x # 存储输入张量,用于跳跃连接(residual connection)
91
+
92
+ # 通过第一个 1x1 卷积层
93
+ out = self.conv1(x)
94
+ out = self.bn1(out)
95
+ out = self.relu(out)
96
+
97
+ # 通过第二个 3x3 卷积层
98
+ out = self.conv2(out)
99
+ out = self.bn2(out)
100
+ out = self.relu(out)
101
+
102
+ # 通过第三个 1x1 卷积层
103
+ out = self.conv3(out)
104
+ out = self.bn3(out)
105
+
106
+ if self.downsample is not None:
107
+ identity = self.downsample(x)
108
+
109
+ # 跳跃连接:将输入(identity)与输出相加
110
+ out += identity
111
+
112
+ # 激活函数(ReLU)再次作用在输出上
113
+ out = self.relu(out)
114
+
115
+ return out
116
+
117
+
118
+ class ResNet(nn.Module):
119
+ def __init__(self, block, layers, num_classes=1000):
120
+ super(ResNet, self).__init__()
121
+
122
+ # 设置初始通道数,供残差模块使用(后续根据 expansion 自动更新)
123
+ self.inplanes = 64
124
+
125
+ # 输入通道数为 3,输出为 64,7x7 大卷积核,步长为 2,padding 为 3,不使用 bias,为一个CBR模块
126
+ self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
127
+ self.bn1 = nn.BatchNorm2d(64)
128
+ self.relu = nn.ReLU(inplace=True)
129
+
130
+ # 最大池化层,3x3 核,步长为 2,无 padding,向上取整模式
131
+ self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=0, ceil_mode=True) # change
132
+
133
+ # 构建四个 stage 的残差网络,每个 stage 包含 layers[i] 个残差块
134
+ self.layer1 = self._make_layer(block, 64, layers[0])
135
+ self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
136
+ self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
137
+ self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
138
+
139
+ # 平均池化,用于分类场景下整合空间信息(此实现中未启用)
140
+ self.avgpool = nn.AvgPool2d(7)
141
+ # 分类输出全连接层:输入通道为最后一层的输出通道(含 expansion),输出为类别数
142
+ self.fc = nn.Linear(512 * block.expansion, num_classes)
143
+
144
+ # 模块初始化
145
+ for m in self.modules():
146
+ if isinstance(m, nn.Conv2d):
147
+ # He 初始化:N(0, sqrt(2/n)),适合 ReLU 激活函数
148
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
149
+ m.weight.data.normal_(0, math.sqrt(2. / n))
150
+ elif isinstance(m, nn.BatchNorm2d):
151
+ # BN 权重初始化为 1,偏置初始化为 0
152
+ m.weight.data.fill_(1)
153
+ m.bias.data.zero_()
154
+
155
+ def _make_layer(self, block, planes, blocks, stride=1):
156
+ """
157
+ 构建一个残差层(stage),由多个残差块 block 组成。
158
+ - block: 残差块类型(如 Bottleneck 或 BasicBlock)
159
+ - planes: 当前 stage 的基础输出通道数
160
+ - blocks: 当前 stage 包含的残差块数量
161
+ - stride: 第一个 block 的步长(用于空间下采样)
162
+ """
163
+ downsample = None
164
+ # 如果输入通道与输出通道不一致,或 stride ≠ 1,则需要下采样对齐
165
+ if stride != 1 or self.inplanes != planes * block.expansion:
166
+ downsample = nn.Sequential(
167
+ nn.Conv2d(self.inplanes, planes * block.expansion,
168
+ kernel_size=1, stride=stride, bias=False),
169
+ nn.BatchNorm2d(planes * block.expansion),
170
+ )
171
+ layers = []
172
+ # 第一个 block:可能包含下采样和通道数调整
173
+ layers.append(block(self.inplanes, planes, stride, downsample))
174
+ # 更新当前输入通道数为新的输出通道数
175
+ self.inplanes = planes * block.expansion
176
+
177
+ # 其余 block:不下采样,通道保持一致
178
+ for i in range(1, blocks):
179
+ layers.append(block(self.inplanes, planes))
180
+ return nn.Sequential(*layers)
181
+
182
+ def forward(self, x):
183
+ # print("输入数据:", x.shape)
184
+ """
185
+ 定义前向传播过程,输出多个阶段的特征图(适合用于下游任务如 FPN、检测、分割等)
186
+ """
187
+ x = self.conv1(x)
188
+ x = self.bn1(x)
189
+ feat1 = self.relu(x) # 输出特征图
190
+ # print("feat1:", feat1.shape)
191
+
192
+ x = self.maxpool(feat1)
193
+ # print("池化层:", x.shape)
194
+
195
+ feat2 = self.layer1(x) # stage 1 输出
196
+ feat3 = self.layer2(feat2) # stage 2 输出
197
+ feat4 = self.layer3(feat3) # stage 3 输出
198
+ feat5 = self.layer4(feat4) # stage 4 输出
199
+
200
+ # 返回所有层的中间特征图(而非分类结果),常用于特征提取任务
201
+ return [feat1, feat2, feat3, feat4, feat5]
202
+
203
+
204
+ def resnet50(**kwargs):
205
+ # 构建一个 ResNet-50 模型实例,使用 Bottleneck 结构
206
+ # 每个 stage 分别包含 3, 4, 6, 3 个残差块(符合 ResNet-50 配置)
207
+ # 额外参数通过 kwargs 传入 ResNet 构造函数(如 num_classes, input_shape 等)
208
+ model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
209
+
210
+ # 删除全局平均池化层(avgpool)和全连接层(fc),以去除分类模块
211
+ # 模型将仅保留主干部分(stem + stage1~stage4),适合特征提取任务
212
+ del model.avgpool
213
+ del model.fc
214
+
215
+ # 返回去除分类头的 ResNet-50 模型,用于下游任务(如检测、分割)
216
+ return model
model/unet_resnet.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ from model.resnet_backbone import resnet50
5
+
6
+ # 定义一个 U-Net 解码模块(上采样模块)
7
+ class unetUp(nn.Module):
8
+ def __init__(self, in_size, out_size):
9
+ """
10
+ 构造函数
11
+ 参数:
12
+ - in_size: 输入通道数(等于上采样特征图通道数 + 跳跃连接特征图通道数)
13
+ - out_size: 输出通道数(经过卷积后的输出通道数)
14
+ """
15
+ super(unetUp, self).__init__()
16
+ # 第一层卷积:输入通道为 in_size,输出通道为 out_size,卷积核大小 3x3,padding=1 保持尺寸不变
17
+ self.conv1 = nn.Conv2d(in_size, out_size, kernel_size=3, padding=1)
18
+ # 第二层卷积:输入通道和输出通道均为 out_size,进一步特征提取
19
+ self.conv2 = nn.Conv2d(out_size, out_size, kernel_size=3, padding=1)
20
+ # 上采样层:使用双线性插值将特征图放大两倍(scale_factor=2)
21
+ self.up = nn.UpsamplingBilinear2d(scale_factor=2)
22
+ # ReLU 激活函数,inplace=True 表示原地操作,节省内存
23
+ self.relu = nn.ReLU(inplace=True)
24
+
25
+ def forward(self, inputs1, inputs2):
26
+ """
27
+ 前向传播
28
+ 参数:
29
+ - inputs1: 编码器部分通过跳跃连接传来的特征图(高分辨率)
30
+ - inputs2: 来自上一级解码器的输出(低分辨率,需要上采样)
31
+ """
32
+ # 先对 inputs2 进行上采样,然后与 inputs1 在通道维度上进行拼接
33
+ # 拼接后的通道数为 in_size
34
+ outputs = torch.cat([inputs1, self.up(inputs2)], 1)
35
+ # 第一次卷积 + ReLU
36
+ outputs = self.conv1(outputs)
37
+ outputs = self.relu(outputs)
38
+ # 第二次卷积 + ReLU
39
+ outputs = self.conv2(outputs)
40
+ outputs = self.relu(outputs)
41
+ # 返回该解码模块的输出特征图
42
+ return outputs
43
+
44
+
45
+ # 定义 U-Net 主体结构
46
+ class Unet(nn.Module):
47
+ def __init__(self, num_classes=21):
48
+ """
49
+ 构造函数
50
+ 参数:
51
+ - num_classes: 最终分类的类别数(用于语义分割任务)
52
+ """
53
+ super(Unet, self).__init__()
54
+
55
+ # 使用 ResNet50 作为编码器,提取多尺度特征
56
+ self.resnet = resnet50() # 假设 resnet50() 返回 5 层特征:feat1 ~ feat5
57
+ # 编码器输出的特征通道数(每层输出的特征图通道数)
58
+ in_filters = [192, 512, 1024, 3072] # 通常是低到高的顺序(层级)
59
+ # 解码器每一层希望恢复到的输出通道数
60
+ out_filters = [64, 128, 256, 512] # 解码层输出通道数逐步降低
61
+
62
+ # 定义 4 层上采样模块(从深到浅)
63
+ # 每层通过双线性插值上采样 + 拼接 + 两次卷积 + ReLU
64
+ self.up_concat4 = unetUp(in_filters[3], out_filters[3]) # 最深层输出
65
+ self.up_concat3 = unetUp(in_filters[2], out_filters[2]) # 次深层
66
+ self.up_concat2 = unetUp(in_filters[1], out_filters[1]) # 中间层
67
+ self.up_concat1 = unetUp(in_filters[0], out_filters[0]) # 最浅层
68
+
69
+ # 对最后一层解码器的输出再上采样一倍并做两次卷积处理,进一步提升分辨率
70
+ self.up_conv = nn.Sequential(
71
+ nn.UpsamplingBilinear2d(scale_factor=2), # 上采样
72
+ nn.Conv2d(out_filters[0], out_filters[0], kernel_size=3, padding=1), # 卷积
73
+ nn.ReLU(),
74
+ nn.Conv2d(out_filters[0], out_filters[0], kernel_size=3, padding=1), # 卷积
75
+ nn.ReLU(),
76
+ )
77
+ # 最后一层卷积,用于生成最终每类像素的得分图(通道数=num_classes)
78
+ self.final = nn.Conv2d(out_filters[0], num_classes, 1)
79
+
80
+ def forward(self, inputs):
81
+ """
82
+ 前向传播
83
+ 参数:
84
+ - inputs: 输入图像(通常为 RGB 图像,形状为 B×3×H×W)
85
+
86
+ 返回:
87
+ - final: 每个像素点在 num_classes 个类别上的预测(未Softmax)
88
+ """
89
+ # 编码器提取五层特征图(假设顺序为由浅到深)
90
+ [feat1, feat2, feat3, feat4, feat5] = self.resnet.forward(inputs)
91
+
92
+ # 解码过程:由深到浅依次上采样 + 融合跳跃连接特征
93
+ up4 = self.up_concat4(feat4, feat5) # 使用 feat5 上采样后与 feat4 融合
94
+ up3 = self.up_concat3(feat3, up4) # 再将上一层上采样后与 feat3 融合
95
+ up2 = self.up_concat2(feat2, up3) # 同理
96
+ up1 = self.up_concat1(feat1, up2) # 最浅层融合
97
+
98
+ # 可选的最终进一步上采样(恢复到输入图尺寸)
99
+ if self.up_conv != None:
100
+ up1 = self.up_conv(up1)
101
+
102
+ # 最终生成每类的预测图
103
+ final = self.final(up1)
104
+ return final
model/unet_training.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from functools import partial
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+
8
+
9
+ def CE_Loss(inputs, target, cls_weights, num_classes=21):
10
+ n, c, h, w = inputs.size() # 输入大小,n=batch_size,c=类别数,h=高度,w=宽度
11
+ nt, ht, wt = target.size() # 目标大小,nt=batch_size,ht=目标图像的高度,wt=目标图像的宽度
12
+
13
+ # 如果输入和目标的大小不一致,则进行插值操作
14
+ if h != ht and w != wt:
15
+ inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True)
16
+
17
+ # 将输入和目标调整为合适的形状
18
+ temp_inputs = inputs.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c) # 调整为 [n*h*w, c]
19
+ # temp_inputs = inputs.view(-1, c) # 变为 [n*h*w, c]
20
+ temp_target = target.view(-1) # 展平目标标签为 [n*h*w]
21
+
22
+ # 计算交叉熵损失
23
+ CE_loss = nn.CrossEntropyLoss(weight=cls_weights, ignore_index=num_classes)(temp_inputs, temp_target)
24
+ return CE_loss
25
+
26
+
27
+ """
28
+ 这段代码实现了 Focal Loss,这是一个改进版的交叉熵损失,
29
+ 专门用于处理类别不平衡问题。它通过对 困难样本(即模型不确定的样本)
30
+ 施加更高的权重来增强模型对这些样本的关注,尤其是在训练过程中遇到类不平衡时。
31
+ """
32
+ def Focal_Loss(inputs, target, cls_weights, num_classes=21, alpha=0.5, gamma=2):
33
+ n, c, h, w = inputs.size() # 输入的尺寸,n=batch_size,c=类别数,h=高度,w=宽度
34
+ nt, ht, wt = target.size() # 目标标签的尺寸,nt=batch_size,ht=目标高度,wt=目标宽度
35
+
36
+ # 如果输入和目标的尺寸不一致,则进行插值调整
37
+ if h != ht and w != wt:
38
+ inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True)
39
+
40
+ # 展平输入和目标,使其适应交叉熵损失函数的要求
41
+ temp_inputs = inputs.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c)
42
+ # temp_inputs = inputs.view(-1, c) # 输入变为 [n*h*w, c]
43
+ temp_target = target.view(-1) # 目标变为 [n*h*w]
44
+
45
+ # 计算交叉熵损失,使用 `reduction='none'` 返回每个像素的损失
46
+ logpt = -nn.CrossEntropyLoss(weight=cls_weights, ignore_index=num_classes, reduction='none')(temp_inputs,
47
+ temp_target)
48
+ pt = torch.exp(logpt) # 计算每个像素属于真实类别的概率
49
+
50
+ # 如果 alpha 存在,则乘上 alpha 来调节正负样本的权重
51
+ if alpha is not None:
52
+ logpt *= alpha
53
+
54
+ # 计算 Focal Loss
55
+ loss = -((1 - pt) ** gamma) * logpt
56
+
57
+ # 对所有像素的损失求平均
58
+ loss = loss.mean()
59
+ return loss
60
+
61
+
62
+ """
63
+ 这段代码实现了 Dice Loss,它是用于计算图像分割任务中的性能度量指标,
64
+ 尤其适用于数据类别不平衡的场景。Dice 系数衡量的是预测结果和真实标签之间的重叠程度,
65
+ 通常用于语义分割任务中,特别是在医学图像分割等领域。
66
+ """
67
+ def Dice_loss(inputs, target, beta=1, smooth=1e-5):
68
+ n, c, h, w = inputs.size() # 输入的尺寸:n=batch_size, c=类别数, h=高度, w=宽度
69
+ nt, ht, wt, ct = target.size() # 目标标签的尺寸:nt=batch_size, ht=高度, wt=宽度, ct=类别数
70
+
71
+ # 如果输入和目标的尺寸不一致,进行插值
72
+ if h != ht and w != wt:
73
+ inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True)
74
+
75
+ # 对输入和目标进行转换,保证计算时的一致性
76
+ temp_inputs = torch.softmax(inputs.transpose(1, 2).transpose(2, 3).contiguous().view(n, -1, c), -1)
77
+ # temp_inputs = torch.softmax(inputs.permute(0, 2, 3, 1).contiguous().view(n, -1, c), -1)
78
+ temp_target = target.view(n, -1, ct)
79
+
80
+ # 计算真正例 (tp),即真实类别和预测类别的交集
81
+ tp = torch.sum(temp_target[..., :-1] * temp_inputs, axis=[0, 1])
82
+ # 计算假正例 (fp),即预测为类别而真实标签为背景
83
+ fp = torch.sum(temp_inputs, axis=[0, 1]) - tp
84
+ # 计算假负例 (fn),即真实标签为类别而预测为背景
85
+ fn = torch.sum(temp_target[..., :-1], axis=[0, 1]) - tp
86
+
87
+ # 计算 Dice 系数,beta 控制对假负样本的惩罚程度
88
+ score = ((1 + beta ** 2) * tp + smooth) / ((1 + beta ** 2) * tp + beta ** 2 * fn + fp + smooth)
89
+ # 计算 Dice Loss
90
+ dice_loss = 1 - torch.mean(score)
91
+ return dice_loss
92
+
93
+
94
+ def weights_init(net, init_type='normal', init_gain=0.02):
95
+ def init_func(m):
96
+ classname = m.__class__.__name__ # 获取模块的类名
97
+ if hasattr(m, 'weight') and classname.find('Conv') != -1: # 如果是卷积层
98
+ if init_type == 'normal':
99
+ torch.nn.init.normal_(m.weight.data, 0.0, init_gain) # 正态分布初始化
100
+ elif init_type == 'xavier':
101
+ torch.nn.init.xavier_normal_(m.weight.data, gain=init_gain) # Xavier初始化
102
+ elif init_type == 'kaiming':
103
+ torch.nn.init.kaiming_normal_(m.weight.data, a=0, mode='fan_in') # Kaiming初始化
104
+ elif init_type == 'orthogonal':
105
+ torch.nn.init.orthogonal_(m.weight.data, gain=init_gain) # 正交初始化
106
+ else:
107
+ raise NotImplementedError('initialization method [%s] is not implemented' % init_type)
108
+ elif classname.find('BatchNorm2d') != -1: # 如果是批量归一化层
109
+ torch.nn.init.normal_(m.weight.data, 1.0, 0.02) # 正态分布初始化,均值1,标准差0.02
110
+ torch.nn.init.constant_(m.bias.data, 0.0) # 偏置初始化为0
111
+
112
+ print('initialize network with %s type' % init_type) # 打印初始化方法
113
+ net.apply(init_func) # 将初始化函数应用到网络的每一层
114
+
115
+
116
+ def get_lr_scheduler(lr_decay_type, lr, min_lr, total_iters, warmup_iters_ratio=0.05, warmup_lr_ratio=0.1,
117
+ no_aug_iter_ratio=0.05, step_num=10):
118
+ # 根据学习率衰减类型(lr_decay_type)选择不同的学习率调度方式
119
+ def yolox_warm_cos_lr(lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter, iters):
120
+ """
121
+ YOLOX特定的余弦衰减带热身策略的学习率调度函数。
122
+ 根据当前的训练迭代次数 (iters) 来动态调整学习率。
123
+
124
+ 参数:
125
+ lr: 当前的学习率
126
+ min_lr: 最小学习率
127
+ total_iters: 总的训练迭代次数
128
+ warmup_total_iters: 热身阶段的迭代次数
129
+ warmup_lr_start: 热身阶段开始时的学习率
130
+ no_aug_iter: 不进行数据增强的迭代次数
131
+ iters: 当前的训练迭代次数
132
+
133
+ 返回:
134
+ 调整后的学习率
135
+ """
136
+ if iters <= warmup_total_iters:
137
+ # 在热身阶段,使用一个二次函数来逐步增加学习率
138
+ lr = (lr - warmup_lr_start) * pow(iters / float(warmup_total_iters), 2) + warmup_lr_start
139
+ elif iters >= total_iters - no_aug_iter:
140
+ # 如果迭代数接近总迭代数,且接近不使用数据增强的迭代次数,则将学习率设置为最小学习率
141
+ lr = min_lr
142
+ else:
143
+ # 在其他阶段,使用余弦衰减函数来调整学习率
144
+ lr = min_lr + 0.5 * (lr - min_lr) * (
145
+ 1.0 + math.cos(
146
+ math.pi * (iters - warmup_total_iters) / (total_iters - warmup_total_iters - no_aug_iter))
147
+ )
148
+ return lr
149
+
150
+ def step_lr(lr, decay_rate, step_size, iters):
151
+ """
152
+ 逐步衰减的学习率调度函数。
153
+
154
+ 参数:
155
+ lr: 当前的学习率
156
+ decay_rate: 衰减率,用来控制每个step后学习率的减少比例
157
+ step_size: 每次衰减发生的步数
158
+ iters: 当前的训练迭代次数
159
+
160
+ 返回:
161
+ 调整后的学习率
162
+ """
163
+ if step_size < 1:
164
+ raise ValueError("step_size must above 1.")
165
+ # 计算经过了多少个衰减步
166
+ n = iters // step_size
167
+ # 按照衰减率调整学习率
168
+ out_lr = lr * decay_rate ** n
169
+ return out_lr
170
+
171
+ # 根据学习率衰减类型选择合适的调度函数
172
+ if lr_decay_type == "cos":
173
+ # 如果选择的是余弦衰减(cos),计算热身阶段的迭代次数和学习率
174
+ warmup_total_iters = min(max(warmup_iters_ratio * total_iters, 1), 3)
175
+ warmup_lr_start = max(warmup_lr_ratio * lr, 1e-6)
176
+ no_aug_iter = min(max(no_aug_iter_ratio * total_iters, 1), 15)
177
+ # 使用partial函数将固定参数传给yolox_warm_cos_lr
178
+ func = partial(yolox_warm_cos_lr, lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter)
179
+ else:
180
+ # 否则,使用逐步衰减的策略
181
+ # 计算每次衰减的速率
182
+ decay_rate = (min_lr / lr) ** (1 / (step_num - 1))
183
+ # 计算每步的衰减步长
184
+ step_size = total_iters / step_num
185
+ # 使用partial函数将固定参数传给step_lr
186
+ func = partial(step_lr, lr, decay_rate, step_size)
187
+
188
+ # 返回选择好的学习率调度函数
189
+ return func
190
+
191
+
192
+ def set_optimizer_lr(optimizer, lr_scheduler_func, epoch):
193
+ # 使用学习率调度函数 lr_scheduler_func,根据当前的 epoch 获取对应的学习率
194
+ lr = lr_scheduler_func(epoch)
195
+
196
+ # 遍历优化器的所有参数组(optimizer.param_groups 是一个包含所有参数组的列表)
197
+ for param_group in optimizer.param_groups:
198
+ # 将当前参数组的学习率(lr)更新为调度函数计算得到的学习率
199
+ param_group['lr'] = lr