论文及代码详解——HRNet

news2024/7/6 21:16:04

文章目录

  • 论文详解 (High-Resolution Networks)
    • Parallel Multi-Resolution Convolutions
    • Repeated Multi-Resolution Fusions
    • Representation Head
  • 代码详解

论文:《Deep High-Resolution Representation Learning for Visual Recognition》
代码:https://github.com/HRNet.

论文详解 (High-Resolution Networks)

如下图所示,HRnet (High-Resolution Networks)由几个组件组成,包括:并行多分辨率卷积(parallel multi-resolution convolutions)重复多分辨率融合(repeated multi-resolution fusions),以及表示头(representation head)

Parallel Multi-Resolution Convolutions

我们从一个高分辨率的卷积流作为第一阶段,逐步将高分辨率到低分辨率的流逐个添加,形成新的阶段,并将多分辨率流并行连接。因此,后一阶段并行流的分辨率由前一阶段的分辨率和一个更低的分辨率组成。
在这里插入图片描述

如图2所示的一个网络结构示例,包含4个并行流,逻辑如下:
在这里插入图片描述
其中 N s r N_{sr} Nsr是一个在第 s s s个阶段,第 r r r 个resolution 的 sub-stream。

第一个stream的resolution index 是1,resolution index为 r r r的分辨率是

第一个stream分辨率的 1 2 r − 1 \frac{1}{2^{r-1}} 2r11 倍。

Repeated Multi-Resolution Fusions

融合模块的目标是在多分辨率表示之间交换信息。融合模块重复几次(例如,每4个residual units 就重复一次)。
在这里插入图片描述

让我们看一个融合3-resolution representations的例子,如图3所示。融合2个representations 和4个representations 是很容易得到的。输入由三种representations组成: { R r i , r = 1 , 2 , 3 } \{R_r^i, r=1,2,3\} {Rri,r=1,2,3} ,其中 r r r是resolution index, 相应的output representations 是 { R r o , r = 1 , 2 , 3 } \{R_r^o,r =1,2,3\} {Rro,r=1,2,3}。每个输出表示都是三个输入的转换表示的和: R r o = f 1 r ( R 1 i ) + f 2 r ( R 2 i ) + f 3 r ( R 3 i ) \mathbf{R}_r^o=f_{1 r}\left(\mathbf{R}_1^i\right)+f_{2 r}\left(\mathbf{R}_2^i\right)+f_{3 r}\left(\mathbf{R}_3^i\right) Rro=f1r(R1i)+f2r(R2i)+f3r(R3i)。 跨阶段(从阶段3到阶段4)的融合有一个额外的输出: R 4 o = f 14 ( R 1 i ) + f 24 ( R 2 i ) + f 34 ( R 3 i ) \mathbf{R}_4^o=f_{14}\left(\mathbf{R}_1^i\right)+f_{24}\left(\mathbf{R}_2^i\right)+f_{34}\left(\mathbf{R}_3^i\right) R4o=f14(R1i)+f24(R2i)+f34(R3i)

变换函数 f x r ( . ) f_{xr}(.) fxr(.)的选择取决于输入分辨率指数 x x x和输出分辨率指数 r r r。如果 x = r , f x r ( R ) = R x=r,f_{xr}(R)=R x=r,fxr(R)=R

如果 x < r , f x r ( R ) x<r,f_{xr}(R) x<r,fxr(R) 对输入的representations R R R 通过 r − x r-x rx个stride=2的3x3的卷积进行下采样。

如果 x > r x>r x>r, f x r ( R ) f_{xr}(R) fxr(R)通过bilinear upsampling进行上采样,并连接着一个1x1的卷积对通道数进行对齐。

Representation Head

我们有三种representations head,如图4所示,分别称为HRNetV1、HRNetV2和HRNetV1p。
在这里插入图片描述

  • HRNetV1

输出仅是来自high-resolution stream的表示。其他三个表示将被忽略。如图4 (a)所示。

  • HRNetV2

我们通过bilinear upsampling对低分辨率表示进行缩放,而不改变高分辨率的通道数,并将四种表示连接起来,然后进行1 × 1卷积来混合这四种表示。如图4 (b)所示。

  • HRNetV2p

我们通过将HRNetV2的高分辨率表示输出向下采样到多个级别来构建多级表示。图4 ©描述了这一点。

在本文中,我们将展示HRNetV1用于人体姿态估计,HRNetV2用于语义分割,HRNetV2p用于目标检测的结果。

代码详解

描述网络结构的核心代码文件在lib/models/seg_hrnet.py 文件中。
下面将详细解读该文件中的代码。


conv3x3
定义了一个3x3的卷积,当stride=1时,输出大小不变。

def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False) # 当stride默认为1时,输出大小不变

BasicBlock

class BasicBlock(nn.Module):
    expansion = 1
    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.relu = nn.ReLU(inplace=relu_inplace)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x): # x: [b,inplanes,h,w]
        residual = x

        out = self.conv1(x) # [b,planes,h,w] 3x3,stride=?,大小可能改变
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out) # [b,planes,h,w]  3x3 stride=1, 大小不变
        out = self.bn2(out)
        if self.downsample is not None:
            residual = self.downsample(x)  # 可能进行下采样操作

        out = out + residual # 残差连接
        out = self.relu(out)

        return out

Bottleneck

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM)

        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM)

        self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1,
                               bias=False)
        self.bn3 = BatchNorm2d(planes * self.expansion,
                               momentum=BN_MOMENTUM)

        self.relu = nn.ReLU(inplace=relu_inplace)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):  # x: [b,inplanes,h,w]
        residual = x

        out = self.conv1(x) # [b,planes,h,w] 1x1  大小不变
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)# [b,planes,h,w] 3x3,stride=? 大小可能改变
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)# [b,planes*expansion,h,w] 1x1 大小不变
        out = self.bn3(out)

        if self.downsample is not None: # 可能进行下采样
            residual = self.downsample(x)

        out = out + residual # 残差连接
        out = self.relu(out)

        return out
blocks_dict = {
    'BASIC': BasicBlock,
    'BOTTLENECK': Bottleneck
}

HighResolutionModule

# 多分辨率模块
class HighResolutionModule(nn.Module):
    def __init__(self, num_branches, blocks, num_blocks, num_inchannels,
                 num_channels, fuse_method, multi_scale_output=True):
        super(HighResolutionModule, self).__init__()
        # 首先检查分支数量是否正确
        self._check_branches(
            num_branches, blocks, num_blocks, num_inchannels, num_channels)

        self.num_inchannels = num_inchannels
        self.fuse_method = fuse_method
        self.num_branches = num_branches

        self.multi_scale_output = multi_scale_output
        # 创建分支
        self.branches = self._make_branches(
            num_branches, blocks, num_blocks, num_channels)
        # 创建融合层
        self.fuse_layers = self._make_fuse_layers()
        self.relu = nn.ReLU(inplace=relu_inplace)

    # 检查分支数是否和num_blocks、num_channels、num_inchannels 数量相等,如果不等则报错
    def _check_branches(self, num_branches, blocks, num_blocks,
                        num_inchannels, num_channels):
        if num_branches != len(num_blocks):
            error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format(
                num_branches, len(num_blocks))
            logger.error(error_msg)
            raise ValueError(error_msg)

        if num_branches != len(num_channels):
            error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format(
                num_branches, len(num_channels))
            logger.error(error_msg)
            raise ValueError(error_msg)

        if num_branches != len(num_inchannels):
            error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format(
                num_branches, len(num_inchannels))
            logger.error(error_msg)
            raise ValueError(error_msg)

    '''
    功能: 创建一个分支
    branch_index: 分支的索引
    block: 基本卷积模块
    num_block: 一个列表,num_block[branch_index]表示当前分支的block个数
    num_channels: 一个列表,num_channels[branch_index] 表示当前分支的输出通道数
    num_inchannels: 一个列表,num_inchannles[branch_index] 表示当前分支的输入通道数
    '''
    def _make_one_branch(self, branch_index, block, num_blocks, num_channels,
                         stride=1):
        downsample = None
        # 如果步长不为1 或者 输入通道数不等于扩张后的通道数
        if stride != 1 or \
           self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion:
            # 定义降采样
            downsample = nn.Sequential(
                # 1x1的卷积扩张通道数
                # 通道数:num_inchannels[branch_index]->num_channels[branch_index] * block.expansion
                nn.Conv2d(self.num_inchannels[branch_index],
                          num_channels[branch_index] * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                # BN
                BatchNorm2d(num_channels[branch_index] * block.expansion,
                            momentum=BN_MOMENTUM),
            )

        # 在Layers中添加一个Block (有刚才定义的downsample)
        layers = []
        layers.append(block(self.num_inchannels[branch_index],
                            num_channels[branch_index], stride, downsample))
        # 更新:当前分支的 输入通道数=输出通道数*expansion
        self.num_inchannels[branch_index] = \
            num_channels[branch_index] * block.expansion
        # 遍历当前分支的所有block, 并添加到layers中
        for i in range(1, num_blocks[branch_index]):
            layers.append(block(self.num_inchannels[branch_index],
                                num_channels[branch_index]))

        return nn.Sequential(*layers) # 返回新创建分支的所有layers

    # 创建所有的分支
    def _make_branches(self, num_branches, block, num_blocks, num_channels):
        branches = []
        # 创建num_branches 个分支
        for i in range(num_branches):
            branches.append(
                self._make_one_branch(i, block, num_blocks, num_channels))

        return nn.ModuleList(branches)
        # 返回一个ModelList,包含num_branches个分支,每个分支又包含num_block个模块

    # 创建一个fuse层
    def _make_fuse_layers(self):
        # 如果分支数是1,则不需要进行融合
        if self.num_branches == 1:
            return None

        num_branches = self.num_branches
        num_inchannels = self.num_inchannels
        fuse_layers = []
        # 如果是多尺度的输出,则遍历num_branches个。否则遍历1次。
        # i 表示输出分辨率 r
        for i in range(num_branches if self.multi_scale_output else 1):
            fuse_layer = []
            # j表示输入分辨率 x
            for j in range(num_branches):
                # 如果输入分辨率x大于输出分辨率r
                if j > i:
                    fuse_layer.append(nn.Sequential(
                        # 1x1的卷积,将通道数变成 输出分辨率的通道数
                        nn.Conv2d(num_inchannels[j],
                                  num_inchannels[i],
                                  1,
                                  1,
                                  0,
                                  bias=False),
                        # BN
                        BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM)))
                # 如果输入分辨率x等于输出分辨率r
                elif j == i:
                    fuse_layer.append(None) # 则不做任何操作,恒等映射
                # 如果输入分辨率x小于输出分辨率r
                else:
                    conv3x3s = [] # 3x3的卷积
                    for k in range(i-j): # 则通过r-x个 stride=2 的3x3的卷积
                        if k == i - j - 1: # 如果是最后一个3x3的卷积
                            num_outchannels_conv3x3 = num_inchannels[i] # 则输出通道数等于输出分辨率的通道数
                            conv3x3s.append(nn.Sequential(
                                # 3x3, stride=2,pad =1  大小缩小2倍
                                nn.Conv2d(num_inchannels[j],
                                          num_outchannels_conv3x3,
                                          3, 2, 1, bias=False),
                                # BN
                                BatchNorm2d(num_outchannels_conv3x3, 
                                            momentum=BN_MOMENTUM)))
                        else: # 如果不是最后一个3x3的卷积
                            num_outchannels_conv3x3 = num_inchannels[j] # 则输出通道数是输入分辨率的通道数
                            conv3x3s.append(nn.Sequential(
                                # 3x3, stride=2,pad =1  大小缩小2倍
                                nn.Conv2d(num_inchannels[j],
                                          num_outchannels_conv3x3,
                                          3, 2, 1, bias=False),
                                # BN
                                BatchNorm2d(num_outchannels_conv3x3,
                                            momentum=BN_MOMENTUM),
                                nn.ReLU(inplace=relu_inplace)))
                    fuse_layer.append(nn.Sequential(*conv3x3s))
            fuse_layers.append(nn.ModuleList(fuse_layer))

        return nn.ModuleList(fuse_layers)

    def get_num_inchannels(self): # 获取输入通道数
        return self.num_inchannels

    def forward(self, x): # 前向传播
        # 如果只有一个分支
        if self.num_branches == 1:
            return [self.branches[0](x[0])]
        # 如果有多个分支
        for i in range(self.num_branches):
            x[i] = self.branches[i](x[i]) # 把x[i] 输入到第i个分支中
            # 得到的x[i]分别是i个分支的输出

        x_fuse = []
        # i 表示输出分辨率的index
        for i in range(len(self.fuse_layers)): # 遍历所有的fuse_layer
            # 初始化:对于输出分辨率i的输出 y
            # 用输入分辨率索引为0进行初始化 x[0]/fuse_layers[i][0](x[0])
            # 这样做的目的是因为多个分支的输出是相加的
            y = x[0] if i == 0 else self.fuse_layers[i][0](x[0])
            # j: 表示输入分辨率的index
            for j in range(1, self.num_branches):
                # 如果输入分辨率=输出分辨率,则直接进行恒等映射
                if i == j:
                    y = y + x[j]
                # 输入分辨率 < 输出分辨率 (分辨率index大的,实际的图片分辨率小)
                elif j > i:
                    width_output = x[i].shape[-1] # 输出分辨率的宽
                    height_output = x[i].shape[-2] # 输出分辨率的高
                    y = y + F.interpolate( # interpolate 上采样
                        self.fuse_layers[i][j](x[j]), # 1x1的卷积改变通道数
                        size=[height_output, width_output],
                        mode='bilinear', align_corners=ALIGN_CORNERS)
                # 输入分辨率 > 输出分辨率
                else:
                    y = y + self.fuse_layers[i][j](x[j]) # 3x3的卷积,stride=2 进行降采样
            x_fuse.append(self.relu(y))
        return x_fuse

HighResolutionNet

# 多分辨率网络
class HighResolutionNet(nn.Module):

    def __init__(self, config, **kwargs):
        global ALIGN_CORNERS
        extra = config.MODEL.EXTRA
        super(HighResolutionNet, self).__init__()
        ALIGN_CORNERS = config.MODEL.ALIGN_CORNERS

        # stem net
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1,
                               bias=False)
        self.bn1 = BatchNorm2d(64, momentum=BN_MOMENTUM)
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1,
                               bias=False)
        self.bn2 = BatchNorm2d(64, momentum=BN_MOMENTUM)
        self.relu = nn.ReLU(inplace=relu_inplace)
        # 以HRNet48中的数据为例子
        '''stage 1'''
        self.stage1_cfg = extra['STAGE1']
        num_channels = self.stage1_cfg['NUM_CHANNELS'][0] # 输出通道数 64
        block = blocks_dict[self.stage1_cfg['BLOCK']] # BottleNeck
        num_blocks = self.stage1_cfg['NUM_BLOCKS'][0] # 4
        # Bottleneck, in_ch=64, out_ch= 64, numblocks=4, stride=1
        self.layer1 = self._make_layer(block, 64, num_channels, num_blocks) # [b,4*64,h,w]
        stage1_out_channel = block.expansion*num_channels # 64*4
        '''stage 2'''
        self.stage2_cfg = extra['STAGE2']
        num_channels = self.stage2_cfg['NUM_CHANNELS'] # [48, 96]
        block = blocks_dict[self.stage2_cfg['BLOCK']] # Basic
        num_channels = [
            num_channels[i] * block.expansion for i in range(len(num_channels))]
        # num_channels=[48*4,96*4]
        self.transition1 = self._make_transition_layer(
            [stage1_out_channel], num_channels) # (64*4,[48*4,96*4])
        self.stage2, pre_stage_channels = self._make_stage(
            self.stage2_cfg, num_channels)
        '''stage 3'''
        self.stage3_cfg = extra['STAGE3']
        num_channels = self.stage3_cfg['NUM_CHANNELS']
        block = blocks_dict[self.stage3_cfg['BLOCK']]
        num_channels = [
            num_channels[i] * block.expansion for i in range(len(num_channels))]
        self.transition2 = self._make_transition_layer(
            pre_stage_channels, num_channels)
        self.stage3, pre_stage_channels = self._make_stage(
            self.stage3_cfg, num_channels)
        '''stage 4'''
        self.stage4_cfg = extra['STAGE4']
        num_channels = self.stage4_cfg['NUM_CHANNELS']
        block = blocks_dict[self.stage4_cfg['BLOCK']]
        num_channels = [
            num_channels[i] * block.expansion for i in range(len(num_channels))]
        self.transition3 = self._make_transition_layer(
            pre_stage_channels, num_channels)
        self.stage4, pre_stage_channels = self._make_stage(
            self.stage4_cfg, num_channels, multi_scale_output=True)
        
        last_inp_channels = np.int(np.sum(pre_stage_channels))

        self.last_layer = nn.Sequential(
            # 1x1 stride=1 大小不变
            nn.Conv2d(
                in_channels=last_inp_channels,
                out_channels=last_inp_channels,
                kernel_size=1,
                stride=1,
                padding=0),
            BatchNorm2d(last_inp_channels, momentum=BN_MOMENTUM),
            nn.ReLU(inplace=relu_inplace),

            nn.Conv2d(
                in_channels=last_inp_channels,
                out_channels=config.DATASET.NUM_CLASSES,
                kernel_size=extra.FINAL_CONV_KERNEL,
                stride=1,
                padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0)
        )

    '''
    功能: 生成转换层
    num_channels_pre_layer: 上一层的通道数的列表
    num_channels_cur_layer: 当前层的通道数的列表
    '''
    def _make_transition_layer(
            self, num_channels_pre_layer, num_channels_cur_layer):
        num_branches_cur = len(num_channels_cur_layer) # 当前层的分支数
        num_branches_pre = len(num_channels_pre_layer) # 上一层的分支数

        transition_layers = []
        # i: 当前层的分支索引
        for i in range(num_branches_cur):
            # 当前层的分支索引<= 上一层的分支数
            if i < num_branches_pre:
                # 对于相同分支:当前层通道数和上一层的通道数不相等
                if num_channels_cur_layer[i] != num_channels_pre_layer[i]:
                    transition_layers.append(nn.Sequential(
                        # 转换通道数,大小不变
                        nn.Conv2d(num_channels_pre_layer[i],
                                  num_channels_cur_layer[i],
                                  3,
                                  1,
                                  1,
                                  bias=False),
                        BatchNorm2d(
                            num_channels_cur_layer[i], momentum=BN_MOMENTUM),
                        nn.ReLU(inplace=relu_inplace)))
                # 对于相同分支:当前层通道数和上一层的通道数相等
                else:
                    transition_layers.append(None) # 则不需要转换,直接映射
            # 当前层的分支索引(i)> 上一层的分支数(num_branches_pre)
            else:
                conv3x3s = []
                for j in range(i+1-num_branches_pre): # j:[0,1,...,i-num_branches_pre]
                    # 输入通道是上一层最后一个分支的输出通道
                    inchannels = num_channels_pre_layer[-1]
                    # 输出通道是当前层的输出通道
                    outchannels = num_channels_cur_layer[i] \
                        if j == i-num_branches_pre else inchannels

                    conv3x3s.append(nn.Sequential(
                        # stride=2 大小变小
                        nn.Conv2d(
                            inchannels, outchannels, 3, 2, 1, bias=False),
                        BatchNorm2d(outchannels, momentum=BN_MOMENTUM),
                        nn.ReLU(inplace=relu_inplace)))

                transition_layers.append(nn.Sequential(*conv3x3s))

        return nn.ModuleList(transition_layers)

    '''
    功能: 创建一个分支
    block : 类型 例如Bottleneck(expansion =4) 或 BasicBlock(expansion =1)
    inplanes: 输入通道数
    planes: 输出通道数
    blocks: block的个数
    '''
    def _make_layer(self, block, inplanes, planes, blocks, stride=1):
        downsample = None
        # 定义降采样
        # 如果stride 不为1 或者 输入通道数 不等于 输出通道数*扩张率
        if stride != 1 or inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                # 通过1x1的卷积改变通道数
                # 通过stride 改变大小
                nn.Conv2d(inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM),
            )

        layers = []
        layers.append(block(inplanes, planes, stride, downsample))
        '''
        当stride不为1时:
        ========
        如果block的类型是Basic Block: (expansion = 1)
        -------第一个block
        输入x: [b,inplanes,h,w] 
        conv1 (3x3,stride=?,out_ch=planes) 输出大小可能改变
        conv2 (3x3,stride=1,out_ch=planes)  输出大小不变
        downsample(x) : 
            Conv2d (1x1, stride=? out_ch=planes) 输出大小可能改变
        输出y=conv2 + downspaple(x) 
            大小:根据stride变化
            通道数:planes
        --------剩下的block
        inplanes变成planes
        然后通过(blocks-1)个block 
            大小:stride=1  不变
            通道数:planes
        *******总结
        大小:根据stride变化
        通道数:planes
        
        ========
        如果block的类型是BottleNeck: (expension =4 )
        --------第一个Block
        输入x: [b,inplanes,h,w] 
        conv1 (1x1,stride=1,out_ch=planes) 大小不变
        conv2 (3x3,stride=?,out_ch=planes) 大小可能改变
        conv3 (1x1,stride=1,out_ch=planes*expansion) 大小不变
        downsample (x):
            Conv2d (1x1, stride=? out_ch=planes*expansion) 输出大小可能改变
        输出y=conv3 + downspaple(x)
            大小:根据stride 变化
            通道:planes*expansion
        --------剩下的Block
        inplanes变成planes*expansion 
        然后通过(blocks-1)个block ,对于每一个block
            stride=1 -> 大小固定不变
            通道数:planes * expansion =4* planes
        ********总结
        大小:根据stride变化
        通道数:planes*expansion
        '''
        inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(inplanes, planes))

        return nn.Sequential(*layers)

    '''
    功能: 创建一个stage
    '''
    def _make_stage(self, layer_config, num_inchannels,
                    multi_scale_output=True):
        num_modules = layer_config['NUM_MODULES']
        num_branches = layer_config['NUM_BRANCHES']
        num_blocks = layer_config['NUM_BLOCKS']
        num_channels = layer_config['NUM_CHANNELS']
        block = blocks_dict[layer_config['BLOCK']]
        fuse_method = layer_config['FUSE_METHOD']

        modules = []
        # 4个stage的modules的数量分别是:1,1,4,3
        for i in range(num_modules):
            # 只在最后一个module中使用multi_scale_output
            if not multi_scale_output and i == num_modules - 1:
                reset_multi_scale_output = False
            else:
                reset_multi_scale_output = True
            # 每一个module中都是一个 HighResolutionModule
            modules.append(
                HighResolutionModule(num_branches,
                                      block,
                                      num_blocks,
                                      num_inchannels,
                                      num_channels,
                                      fuse_method,
                                      reset_multi_scale_output)
            )
            num_inchannels = modules[-1].get_num_inchannels() # 当前模型的输入通道数

        return nn.Sequential(*modules), num_inchannels

    def forward(self, x): # x: [b,3,h,w]
        x = self.conv1(x) # x: [b,64,h/2,w/2]
        x = self.bn1(x)
        x = self.relu(x)
        x = self.conv2(x) # x: [b,64,h/4,w/4]
        x = self.bn2(x)
        x = self.relu(x)
        '''stage 1'''
        x = self.layer1(x)
        '''stage 2'''
        x_list = []
        for i in range(self.stage2_cfg['NUM_BRANCHES']): # 2个分支
            if self.transition1[i] is not None:
                x_list.append(self.transition1[i](x)) # 首先创建transition 层
            else:
                x_list.append(x)
        y_list = self.stage2(x_list)  # 输入transitino 层,生成一个stage
        '''stage 3'''
        x_list = []
        for i in range(self.stage3_cfg['NUM_BRANCHES']): # 3个分支
            if self.transition2[i] is not None:
                if i < self.stage2_cfg['NUM_BRANCHES']: # 如果当前阶段分支index < 上一阶段的分支数
                    x_list.append(self.transition2[i](y_list[i])) # 输入transition的是上一个stage的对应分支的输出
                else:
                    x_list.append(self.transition2[i](y_list[-1])) # 输入transition的是上一个stage的最后一个分支的输出
            else:
                x_list.append(y_list[i])
        y_list = self.stage3(x_list)
        '''stage 4'''
        x_list = []
        for i in range(self.stage4_cfg['NUM_BRANCHES']): # 4个分支
            if self.transition3[i] is not None:
                if i < self.stage3_cfg['NUM_BRANCHES']: # 如果当前阶段分支index < 上一阶段的分支数
                    x_list.append(self.transition3[i](y_list[i]))# 输入transition的是上一个stage的对应分支的输出
                else:
                    x_list.append(self.transition3[i](y_list[-1]))# 输入transition的是上一个stage的最后一个分支的输出
            else:
                x_list.append(y_list[i])
        x = self.stage4(x_list)

        # Upsampling
        x0_h, x0_w = x[0].size(2), x[0].size(3) # stage 4 输出的第1个branch的宽高
        # 然后将stage 4 输出的第2,3,4个branch的宽高都上采样到相同的大小
        x1 = F.interpolate(x[1], size=(x0_h, x0_w), mode='bilinear', align_corners=ALIGN_CORNERS)
        x2 = F.interpolate(x[2], size=(x0_h, x0_w), mode='bilinear', align_corners=ALIGN_CORNERS)
        x3 = F.interpolate(x[3], size=(x0_h, x0_w), mode='bilinear', align_corners=ALIGN_CORNERS)
        # 在通道维度上进行连接
        x = torch.cat([x[0], x1, x2, x3], 1)

        x = self.last_layer(x)

        return x

    # 初始化权重
    def init_weights(self, pretrained='',):
        logger.info('=> init weights from normal distribution')
        # 用随机生成的数初始化
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.normal_(m.weight, std=0.001)
            elif isinstance(m, BatchNorm2d_class):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
        # 采用预训练权重初始化
        if os.path.isfile(pretrained):
            pretrained_dict = torch.load(pretrained)
            logger.info('=> loading pretrained model {}'.format(pretrained))
            model_dict = self.state_dict()              
            pretrained_dict = {k: v for k, v in pretrained_dict.items()
                               if k in model_dict.keys()}
            for k, _ in pretrained_dict.items():
                logger.info(
                    '=> loading {} pretrained model {}'.format(k, pretrained))
            model_dict.update(pretrained_dict)
            self.load_state_dict(model_dict)

get_seg_model

# 获取分割的模型
def get_seg_model(cfg, **kwargs):
    model = HighResolutionNet(cfg, **kwargs) # 定义模型
    model.init_weights(cfg.MODEL.PRETRAINED) # 初始化权重
    return model

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/910271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL报错:Incorrect integer value: ‘None‘ for column ‘managerId‘ at row 1

错误如下&#xff1a; 今天在建表的时候突然报错 上网查询了原因&#xff0c;把None改成NULL就行了。 5以上的版本如果是空值应该要写NULL&#xff0c;这种问题一般mysql 5.x上出现。 改完后就运行正常了

07 mysql5.6.x docker 启动, 无 config 目录导致客户端连接认证需要 10s

前言 呵呵 最近再一次 环境部署的过程中碰到了这样的一个问题 我基于 docker 启动了一个 mysql 服务, 然后 挂载出了 数据目录 和 配置目录, 没有手动复制配置目录出来, 所以配置目录是空的 然后 我基于 docker 启动了一个 nacos, 配置数据库设置为上面的这个 mysql 然后 启…

【2023年11月第四版教材】《第6章-项目管理概论》(第二部分)

《第6章-项目管理概论》&#xff08;第二部分&#xff09; 3 项目经理的角色3.1 项目经理的影响力范围3.2 项目经理领导力风格 4 价值驱动的项目管理知识体系4.1 开发生命周期类型 5 五大过程组6 五个过程组和十大知识领域 3 项目经理的角色 3.1 项目经理的影响力范围 范围影…

从业务层的代码出发,去排查通用框架代码崩溃的问题

目录 1、问题说明 1.1、Release下崩溃&#xff0c;Debug下很难复现 1.2、用Windbg打开dump文件&#xff0c;发现崩溃在通用的框架代码中 2、进一步分析 2.1、使用IDA查看汇编代码尝试寻找崩溃的线索 2.2、在Windbg中查看相关变量的值 2.3、查看最近代码的修改记录&#…

渗透和红队快速打点工具

&#x1f525; POC-bomber &#x1f984; POC bomber 是一款漏洞检测/利用工具&#xff0c;旨在利用大量高危害漏洞的POC/EXP快速获取目标服务器权限 本项目收集互联网各种危害性大的 RCE 任意文件上传 反序列化 sql注入 等高危害且能够获取到服务器核心权限的漏洞POC/EXP…

【C++STL基础入门】深入浅出string类的比较(compare)、复制(copy)

文章目录 前言一、比较1.比较运算符2.compare函数 二、复制1.copy函数 总结 前言 本系列STL使用VS2022C20版本 在C标准库中&#xff0c;string类是一个功能强大的字符串处理类&#xff0c;提供了丰富的操作函数。本文将详细介绍string类的比较、复制、查找字串、返回字串、交…

d3dx9_43.dll如何修复?找不到d3dx9_43.dll怎么办

d3dx9_43.dll文件通常与DirectX 9运行时库一起安装在用户的计算机上。当用户运行需要DirectX 9支持的应用程序时&#xff0c;操作系统会自动加载d3dx9_43.dll文件&#xff0c;并提供所需的功能。如果缺少或损坏了该文件&#xff0c;用户可能会遇到无法运行应用程序、崩溃或显示…

如何使用CSS实现一个全屏滚动效果(Fullpage Scroll)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现全屏滚动效果的CSS和JavaScript示例⭐ HTML 结构⭐ CSS 样式 (styles.css)⭐ JavaScript 代码 (script.js)⭐ 实现说明⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦…

深入理解Linux内核--Ext2和Ext3文件系统

Ext2的一般特征 类Unix操作系统使用多种文件系统。尽管所有这些文件系统都有少数POSIX API(如state())所需的共同的属性子集&#xff0c;但每种文件系统的实现方式是不同的。 Linux的第一个版本是基于MINIX文件系统的。当Linux成熟时&#xff0c;引入了扩展文件系统(Extended …

深入理解CAS和Atomic工具类

CAS CAS&#xff08;Compare And Swap&#xff0c;比较交换&#xff09;指的是对于一个变量&#xff0c;比较它的内存的值与期望值是否相同&#xff0c;如果相同则将内存值修改为新的指定的值。即CAS包括两个步骤&#xff1a;1.比较内存值与期望值是否相同&#xff1b;2.相同则…

【校招VIP】前端基础之post和get

考点介绍&#xff1a; get和post 是网络基础&#xff0c;也是每个前端同学绕不过去的小问题&#xff0c;但是在校招面试中很多同学在基础回答中不到位&#xff0c;或者倒在引申问题里&#xff0c;就丢分了。 『前端基础之post和get』相关题目及解析内容可点击文章末尾链接查看…

7个改变玩法规则的ChatGPT应用场景

ChatGPT因各种原因受到了广泛关注&#xff1a;ChatGPT可以充当各种改善生活改进工作的小助手&#xff0c;如内容写手、客户支持、语言翻译、编码专家等等。只需在你的聊天内容中添加适当的提示&#xff0c;人工智能将为你提供各项支持。[1] 1.ChatGPT作为内容写手 通过AI的帮助…

《Linux从练气到飞升》No.16 Linux 进程地址空间

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

掌握AI助手的魔法工具:解密Prompt(提示)在AIGC时代的应用「上篇」

在当今的AIGC时代&#xff0c;我们面临着越来越多的人工智能技术和应用。其中一个引人注目的工具就是Prompt&#xff08;提示&#xff09;。它就像是一种魔法&#xff0c;可以让我们与AI助手进行更加互动和有针对性的对话。那么&#xff0c;让我们一起来了解一下Prompt&#xf…

QA

1. 这是什么意思&#xff1f; label_viz[:,:,::-1] 这段代码看起来像是Python中处理图像的代码片段。让我来为您解释一下&#xff1a; 1. label_viz&#xff1a;这可能是一个二维数组&#xff08;通常是NumPy数组&#xff09;&#xff0c;代表图像上的标签或类别信息的可视化…

线程面试题-1

看的博客里面总结的线程的八股文 1、线程安全的集合有哪些&#xff1f;线程不安全的呢&#xff1f; 线程安全的&#xff1a; Hashtable&#xff1a;比HashMap多了个线程安全。 ConcurrentHashMap:是一种高效但是线程安全的集合。 Vector&#xff1a;比Arraylist多了个同步化…

Ubuntu本地快速搭建web小游戏网站,并使用内网穿透将其发布到公网上

文章目录 前言1. 本地环境服务搭建2. 局域网测试访问3. 内网穿透3.1 ubuntu本地安装cpolar内网穿透3.2 创建隧道3.3 测试公网访问 4. 配置固定二级子域名4.1 保留一个二级子域名4.2 配置二级子域名4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互联网&am…

SpringBoot中乐观锁的实现 (精简demo)

使用场景: 当要更新一条数据时&#xff0c;希望这条数据没有被别人更新&#xff0c;也就是说实现线程安全的数据更新 1. 数据库新增version字段, int类型, 默认值为0 2. 引入依赖 <!--mybatis拦截器--> <dependency><groupId>com.baomidou</groupId>&…

Nginx使用keepalived配置VIP

VIP常用于负载均衡的高可用&#xff0c;使用VIP可以给多个主机绑定一个IP&#xff0c;这样&#xff0c;当某个负载应用挂了之后&#xff0c;可以自动切到另一个负载。 我这里是在k8s环境中做的测试&#xff0c;集群中有6个节点&#xff0c;我给140和141两个节点配置VIP。 1. 安…

【leetcode 力扣刷题】移除链表元素 多种解法

移除链表元素的多种解法 203. 移除链表元素解法①&#xff1a;头节点单独判断解法②&#xff1a;虚拟头节点解法③&#xff1a;递归 203. 移除链表元素 题目链接&#xff1a;203.移除链表元素 题目内容&#xff1a; 理解题意&#xff1a;就是单纯的删除链表中所有值等于给定的…