특정 코드를 결합하여 yolov5-7.0 앵커 박스(앵커) 생성 메커니즘을 이해합니다.

최근 yolov5-7.0에 대해 심도 있게 공부하고 있는데, 공식 코드가 비교적 읽기 쉽다고 느껴서 네트워크 구조에 대한 이해가 더 잘 된 것 같아요. , 그리고 이것을 이해하고 싶어서 진행했습니다. 전후의 코드를 잘 살펴보십시오. 이제 학습 성과를 기록하겠습니다. 개인적인 의견이니 궁금한 점 있으면 정정해주세요

1. 앵커 프레임 메커니즘 분류

Anchor, A Priori Box, Preselected Box는 모두 동일한 것으로, 입력 데이터가 특징 추출 단계를 거치게 되면 일반적으로 높은 수준의 특징 맵을 얻기 위해 데이터 양을 줄이기 위해 다운 샘플링을 한 후 다시 이러한 고수준 특징 맵은 손실 계산에 사전 설정된 앵커 박스와 레이블 지상 실제값을 사용하고 그래디언트 역전파를 기반으로 네트워크의 매개변수를 업데이트하며 네트워크의 매개변수가 위치와 카테고리를 직접 식별할 수 있도록 점진적으로 반복됩니다. 최종적으로 인식 효과가 가장 좋은 업데이트된 매개변수가 사용됩니다. 저장 후 특정 유형의 대상을 식별하기 위해 네트워크에 대한 네트워크 가중치 파일을 얻게 됩니다.

따라서 Anchor Box는 일반적으로 특징 추출의 마지막 단계에서 미리 설정되며 이 단계에서는 저해상도 특징 맵을 생성합니다.yolov5s에서는 Detect의 마지막 레이어에서 생성된 80 x 80, 40 x 40, 20 x 20 특징입니다. .map을 선택한 다음 기능 맵을 기반으로 앵커 상자를 미리 설정합니다. 앵커박스 관련해서 직접 보실 수 있는 정보는 yolov5s.yaml 에 있습니다.
여기에 이미지 설명을 삽입하세요.

2. 앵커박스는 어떻게 작동하나요?

그렇다면 이 앵커 박스는 정확히 어떻게 작동합니까? 특징 추출의 마지막 단계인 Detect 코드를 살펴보겠습니다(models/yolo.py).

class Detect(nn.Module):
    # YOLOv5 Detect head for detection models
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode

    def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

    def forward(self, x):
        z = []  # inference output
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            if not self.training:  # inference
                if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)

                if isinstance(self, Segment):  # (boxes + masks)
                    xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)
                    xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)
                else:  # Detect (boxes only)
                    xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
                    xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, self.na * nx * ny, self.no))

        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)

    def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')):
        d = self.anchors[i].device
        t = self.anchors[i].dtype
        shape = 1, self.na, ny, nx, 2  # grid shape
        y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t)
        yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x)  # torch>=0.7 compatibility
        grid = torch.stack((xv, yv), 2).expand(shape) - 0.5  # add grid offset, i.e. y = 2.0 * x - 0.5
        anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape)
        return grid, anchor_grid

Detect 클래스에는 앵커 정보가 이 클래스에서 사용됨을 나타내는 앵커 매개변수 입력이 있는 것을 볼 수 있습니다. 클래스의 앵커와 관련된 코드 중 하나는 다음과 같습니다.

self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)

다른 하나는 전달 함수 아래의 _make_grid 함수입니다. 하지만 Forward 함수는 학습 단계에서 이 함수를 실행하지 않으므로 특징 맵에서 앵커 박스가 어떻게 생성되는지 이해하기 어렵습니다.

if not self.training:  # inference
    if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
    	self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)

그러면 Detect 클래스의 훈련 단계에서 실행되는 코드는 다음과 같습니다.

class Detect(nn.Module):
    # YOLOv5 Detect head for detection models
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode

    def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

    def forward(self, x):
        z = []  # inference output
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
        
        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)

앵커와 밀접하게 접촉하는 유일한 함수인register_buffer() 함수에 대해 말씀 드리고 싶은데요, 이전에는 Detect 클래스가 포함된 BaseModel 및 탐지 모델 클래스를 살펴봤지만 레이아웃과 관련된 작업은 보지 못했습니다. 앵커 박스.

3. Register_buffer() 함수

Register_buffer() 함수는 앵커를 네트워크의 매개변수로 고정할 수 있으며 이 함수에 의해 전달된 매개변수는 훈련 반복에 따라 변경되지 않으며 출력은 네트워크 훈련이 끝날 때 모델과 함께 저장됩니다. Register_buffer() 함수와 Register_parameter() 함수, nn.Parameter(), model.state_dict(), model.parameters() 및 model.buffers()의 함수와 차이점을 살펴볼 수 있습니다. Pytorch 모델에 표시되는 매개변수 및 버퍼

제가 디버깅하면서 작성한 기록은 다음과 같습니다.

class Detect(nn.Module):
    # YOLOv5 Detect head for detection models
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode

    def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        """anchors"""
        """ register_buffer的参数不参与梯度更新,最终随模型保存输出,此处把传入的anchors参数的数据通过register_buffer传入到网络中,"""
        """torch.tensor(anchors).float()
                tensor([[ 10.,  13.,  16.,  30.,  33.,  23.],
                        [ 30.,  61.,  62.,  45.,  59., 119.],
                        [116.,  90., 156., 198., 373., 326.]])
                ipdb> torch.tensor(anchors).float().view(self.nl, -1, 2)
                tensor([[[ 10.,  13.],
                        [ 16.,  30.],
                        [ 33.,  23.]],

                        [[ 30.,  61.],
                        [ 62.,  45.],
                        [ 59., 119.]],

                        [[116.,  90.],
                        [156., 198.],
                        [373., 326.]]])
                ipdb> self.anchor_grid
                [tensor([]), tensor([]), tensor([])]
                ipdb> anchors
                [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]]
                """
        # import ipdb;ipdb.set_trace()
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

        """ Detect层对输入的三个下采样倍数的数据分别采用三个全连接层输出
        self.m=
            ModuleList(
            (0): Conv2d(128, 18, kernel_size=(1, 1), stride=(1, 1))
            (1): Conv2d(256, 18, kernel_size=(1, 1), stride=(1, 1))
            (2): Conv2d(512, 18, kernel_size=(1, 1), stride=(1, 1))
            )
            其中输出维度 self.no * self.na,即此处的 18,表示每个维度三种尺度的锚框 x ( 类别 + xywh + score) = 3 x 6
        """
        
    def forward(self, x):
        """self.state_dict()
                anchors
                m.0.weight
                m.0.bias
                m.1.weight
                m.1.bias
                m.2.weight
                m.2.bias
        """
        
        """对应8,16,32倍下采样输出的特征图
            x:  x[0].shape
                torch.Size([1, 128, 32, 32]) 
                ipdb> x[1].shape
                torch.Size([1, 256, 16, 16])
                ipdb> x[2].shape
                torch.Size([1, 512, 8, 8])
        """
        z = []  # inference output
        import ipdb;ipdb.set_trace()
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            """经过全连接层后数据维度
            x[0].shape 
                        = torch.Size([1, 18, 32, 32])
                x[1].shape 
                        = torch.Size([1, 18, 16, 16])
                x[2].shape 
                        = torch.Size([1, 18, 8, 8])
            """
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            
            """训练阶段 对应8,16,32倍下采样输出的特征图在Detect类输出的数据维度
                x:x[0].shape
                        torch.Size([1, 3, 32, 32, 6])
                        ipdb> x[1].shape
                        torch.Size([1, 3, 16, 16, 6])
                        ipdb> x[2].shape
                        torch.Size([1, 3, 8, 8, 6])
            """
            
            if not self.training:  # inference
                if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)

                if isinstance(self, Segment):  # (boxes + masks)
                    xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)
                    xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)
                else:  # Detect (boxes only)
                    xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
                    """推断流程预测xywh"""
                    xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, self.na * nx * ny, self.no))

        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)

    def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')):
        d = self.anchors[i].device
        t = self.anchors[i].dtype
        shape = 1, self.na, ny, nx, 2  # grid shape
        y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t)
        yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x)  # torch>=0.7 compatibility
        grid = torch.stack((xv, yv), 2).expand(shape) - 0.5  # add grid offset, i.e. y = 2.0 * x - 0.5
        anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape)
        return grid, anchor_grid

위의 디버깅 상황은 또한 앵커 박스가 매개변수를 통해 네트워크에 등록되는 것을 충분히 설명할 수 있습니다.forward() 함수에서는 Detect 클래스 구조의 매개변수 상황인 self를 살펴보았는데, 그 중 self.state_dict() ----- -> 앵커, m.0.weight, m.0.bias, m.1.weight, m.1.bias, m.2.weight, m.2.bias에는 앵커가 포함됩니다.

4. 앵커박스의 특정 생성

그런 다음 앵커 상자의 특정 세대는 다음과 같습니다.

def forward(self, x):
"""输入x是进入Detect的三个尺度的特征图"""
        z = []  # inference output
        for i in range(self.nl):
        """self.nl=3表示Detect对应三个尺度用以处理三层特征图的网络结构,
        	self.m是对应三个尺度特征图的网络结构,对应不同的输入数据维度,输出维度都是18,其中输出维度 self.no * self.na,即此处的 18,表示每个维度三种尺度的锚框 x ( 类别 + xywh + score) = 3 x 6
        	self.m=
            ModuleList(
            (0): Conv2d(128, 18, kernel_size=(1, 1), stride=(1, 1))
            (1): Conv2d(256, 18, kernel_size=(1, 1), stride=(1, 1))
            (2): Conv2d(512, 18, kernel_size=(1, 1), stride=(1, 1))
            )"""
            x[i] = self.m[i](x[i])  # conv
            """对应的输入特征图在对应的m的网络结构中进行计算
            	x[0].shape 
                        = torch.Size([1, 18, 32, 32])
                x[0].shape 
                        = torch.Size([1, 18, 16, 16])
                x[2].shape 
                        = torch.Size([1, 18, 8, 8])"""
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            """改变数据输出维度便于后续处理
            	x:x[0].shape
                        torch.Size([1, 3, 32, 32, 6])
                        ipdb> x[1].shape
                        torch.Size([1, 3, 16, 16, 6])
                        ipdb> x[2].shape
                        torch.Size([1, 3, 8, 8, 6])
                 维度变换:1表示batch_size;3表示3种尺度的锚框;32,32表示特征图维度;6表示预测结果,含类别 + x、y、w、h + score
            """

yolov5s.yaml의 앵커 정보를 살펴보세요.

anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

그 중 10, 13, 16, 30, 33, 23은 각각 앵커 상자의 미리 설정된 세 가지 크기를 나타내며 앵커 상자의 너비와 높이를 나타냅니다. 다른 두 행은 동일하여 8배, 16배에 해당합니다. 및 각각 32번 샘플링 배수의 출력 특징 맵의 미리 설정된 앵커 상자이므로 각 스케일의 출력 특징 맵은 세 가지 스케일의 앵커 상자의 출력 특징 맵을 생성하고 세 가지 스케일의 특징 맵은 총 9개의 스케일의 앵커 박스를 생성합니다.

따라서 Detect 클래스를 통해 특징맵을 기반으로 데이터를 예측했는데, 미리 설정된 앵커를 곱한 특징맵 데이터이기 때문에 일부 설명망에서는 목표 위치의 오프셋을 예측하는 것으로 이해할 수 있다. 상자 매개변수는 대상의 위치를 ​​가져오지만 대상의 위치를 ​​직접 예측하지는 않습니다.

그 중 데이터의 앵커박스 정보를 얻을 때
실제로 BaseModel 클래스의 _forward_once 함수에서 데이터 정보를 처리합니다.

class DetectionModel(BaseModel):
	......
	def forward(self, x, augment=False, profile=False, visualize=False):
        if augment:
            return self._forward_augment(x)  # augmented inference, None
        return self._forward_once(x, profile, visualize)  # single-scale inference, train

class BaseModel(nn.Module):
    # YOLOv5 base model
    def forward(self, x, profile=False, visualize=False):
        return self._forward_once(x, profile, visualize)  # single-scale inference, train

    def _forward_once(self, x, profile=False, visualize=False):
        y, dt = [], []  # outputs
        for m in self.model:
        """self.model是构建好的网络结构,输入x是实际数据"""
            if m.f != -1:  # if not from previous layer
                x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layers
            if profile:
                self._profile_one_layer(m, x, dt)
            x = m(x)  # run
            y.append(x if m.i in self.save else None)  # save output
            if visualize:
                feature_visualization(x, m.type, m.i, save_dir=visualize)
        return x
        .......

여기서 x 출력은 train.py의 pred 출력과 동일합니다.

with torch.cuda.amp.autocast(amp):
    pred = model(imgs)  # forward
    loss, loss_items = compute_loss(pred, targets.to(device))  # loss scaled by batch_size```

Anchor Box를 시각화하고 싶다면 pred를 처리하는 것을 추천하고, 구체적인 처리 방법은 Detect 클래스에서 훈련하지 않은 코드를 참고하는 것이 좋다. 처음에 시도해 봤는데 그릴 수 있다.

사전 훈련된 모델을 사용하는 경우 사전 훈련된 모델은 일정량의 데이터로 훈련되었으며 네트워크 매개변수에는 대상에 대한 특정 인식 기능이 있으므로 앵커 상자 표시 효과가 좋지 않을 수 있습니다.

5. 요약

yolov5s에서 앵커는 미리 설정된 앵커 박스 스케일 정보를 Register_buffer() 함수를 통해 최종 감지 네트워크 계층에 매개변수로 등록합니다. 타겟의 위치 정보는 앵커 박스 매개변수와 특징 맵 데이터를 곱하여 얻어지기 때문에 네트워크는 앵커 박스를 기준으로 한 위치 정보를 예측하는데, 이는 앵커 박스의 위치 오프셋으로 이해할 수 있습니다. 타겟의 위치 오프셋을 위한 미리 설정된 앵커 박스는 타겟 카테고리 식별 및 위치 예측을 달성하는 데 사용됩니다.

Supongo que te gusta

Origin blog.csdn.net/qq_44442727/article/details/131294909
Recomendado
Clasificación