Delete model
Browse files- model/__init__.py +0 -1
- model/__pycache__/__init__.cpython-310.pyc +0 -0
- model/__pycache__/__init__.cpython-311.pyc +0 -0
- model/__pycache__/__init__.cpython-38.pyc +0 -0
- model/__pycache__/resnet_backbone.cpython-310.pyc +0 -0
- model/__pycache__/resnet_backbone.cpython-311.pyc +0 -0
- model/__pycache__/resnet_backbone.cpython-38.pyc +0 -0
- model/__pycache__/unet_resnet.cpython-310.pyc +0 -0
- model/__pycache__/unet_resnet.cpython-311.pyc +0 -0
- model/__pycache__/unet_resnet.cpython-38.pyc +0 -0
- model/__pycache__/unet_training.cpython-310.pyc +0 -0
- model/__pycache__/unet_training.cpython-311.pyc +0 -0
- model/__pycache__/unet_training.cpython-38.pyc +0 -0
- model/resnet_backbone.py +0 -216
- model/unet_resnet.py +0 -104
- model/unet_training.py +0 -199
model/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
model/__pycache__/__init__.cpython-310.pyc
DELETED
|
Binary file (149 Bytes)
|
|
|
model/__pycache__/__init__.cpython-311.pyc
DELETED
|
Binary file (150 Bytes)
|
|
|
model/__pycache__/__init__.cpython-38.pyc
DELETED
|
Binary file (148 Bytes)
|
|
|
model/__pycache__/resnet_backbone.cpython-310.pyc
DELETED
|
Binary file (5.77 kB)
|
|
|
model/__pycache__/resnet_backbone.cpython-311.pyc
DELETED
|
Binary file (9.66 kB)
|
|
|
model/__pycache__/resnet_backbone.cpython-38.pyc
DELETED
|
Binary file (5.82 kB)
|
|
|
model/__pycache__/unet_resnet.cpython-310.pyc
DELETED
|
Binary file (3.08 kB)
|
|
|
model/__pycache__/unet_resnet.cpython-311.pyc
DELETED
|
Binary file (5.27 kB)
|
|
|
model/__pycache__/unet_resnet.cpython-38.pyc
DELETED
|
Binary file (3.06 kB)
|
|
|
model/__pycache__/unet_training.cpython-310.pyc
DELETED
|
Binary file (5.41 kB)
|
|
|
model/__pycache__/unet_training.cpython-311.pyc
DELETED
|
Binary file (9.55 kB)
|
|
|
model/__pycache__/unet_training.cpython-38.pyc
DELETED
|
Binary file (5.37 kB)
|
|
|
model/resnet_backbone.py
DELETED
|
@@ -1,216 +0,0 @@
|
|
| 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
DELETED
|
@@ -1,104 +0,0 @@
|
|
| 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
DELETED
|
@@ -1,199 +0,0 @@
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|