티스토리 뷰

작성자 : 한양대학원 융합로봇시스템학과 유승환 석사과정 (CAI LAB)

 

 오늘은 YOLO V5 (Pytorch) 환경 셋팅모델 아키텍쳐(Backbone, Head) 분석을 하겠습니다. 그럼 YOLO v5 분석 시작~!!


링크 0) YOLO v5 Pytorch 깃헙 링크 : https://github.com/ultralytics/yolov5

 

ultralytics/yolov5

YOLOv5 in PyTorch > ONNX > CoreML > iOS. Contribute to ultralytics/yolov5 development by creating an account on GitHub.

github.com

 

링크 1) YOLO v5 custom train 예제 링크 : https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data

 

ultralytics/yolov5

YOLOv5 in PyTorch > ONNX > CoreML > iOS. Contribute to ultralytics/yolov5 development by creating an account on GitHub.

github.com

링크 2) YOLO v3 아키텍쳐 참고 블로그 : dojinkimm.github.io/computer_vision/2019/07/31/yolo-part1.html

 

Study Blog

블로그 migration중 - https://devjin-blog.com/

dojinkimm.github.io

링크 3) YOLO v5 아키텍쳐 참고 블로그 (영문) : https://medium.com/towards-artificial-intelligence/yolo-v5-explained-and-demystified-4e4719891d69

 

YOLO V5 — Explained and Demystified

YOLO V5 — Explained and Demystified, YOLO v5 architecture, intuition, YOLO, YOLOv5

medium.com


1. YOLO v5 학습 환경 셋팅하기

 1-1) 필자 PC 환경

   - OS : Ubuntu 18.04 (& Window 10)

   - CUDA : 10.2

   - cuDNN : 7.6.5

   - Anaconda환경


 1-2) 아나콘다 환경 생성

  • conda env 생성을 해줍니다. 
conda create -n yolov5 python=3.8

conda activate yolov5

 1-3) YOLO v5 깃헙 폴더 클론하기 (링크 0 참고)

  • yolo v5 깃헙 코드를 클론해줍니다.
# code clone
git clone https://github.com/ultralytics/yolov5.git

# if your yolov5 folder is lock (UBUNTU)
sudo chmod 777 -R ~/yolov5

 1-4) 필요한 라이브러리 설치하기 (yolo v5 깃헙의 requirements.txt 참고)

# update
conda update -yn base -c defaults conda

# install Lib for YOLOv5
conda install -c anaconda cython numpy pillow scipy seaborn pandas requests
conda install -c conda-forge matplotlib pyyaml tensorboard tqdm opencv 

# install pytorch
conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch

# Extra
conda install -c conda-forge onnx

(번외) ERROR: Invalid requirement: "'opencv-python"

  • 위와 같이 opencv-python 버전이 알맞게 설치되어도 에러가 발생할 수 있습니다.
  • 이럴 때는 'check_requirements' 함수가 적혀있는 부분을 주석처리 하면 됩니다!

  1-5) (선택) Pre-trained 모델 다운받기

  • 아래의 링크에서 pre-trained 모델을 다운받을 수 있습니다.
  • 저는 yolov5s.pt를 다운받았습니다.
  • pre-trained 모델 다운로드 링크github.com/ultralytics/yolov5/releases
 

Releases · ultralytics/yolov5

YOLOv5 in PyTorch > ONNX > CoreML > TFLite. Contribute to ultralytics/yolov5 development by creating an account on GitHub.

github.com


 1-6) YOLO V5 데이터셋 만들기 1 :  yaml 파일 제작

  • 이제 학습 데이터의 경로, 클래스 갯수 및 종류가 적혀 있는 yaml 파일 제작을 해야합니다.
  • 아래는 yolo v5 깃헙에서 제공하는 coco.yaml의 예시입니다.
  • 우리가 수정해야할 부분은 다음과 같습니다.
  1. train : 학습 데이터 폴더 경로 (이미지)
  2. val : 검증 데이터 폴더 경로 (이미지)
  3. nc : 학습할 클래스 갯수
  4. names : 학습할 클래스 이름들
# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]
train: ../coco128/images/train2017/
val: ../coco128/images/val2017/

# number of classes
nc: 80

# class names
names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
        'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
        'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
        'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
        'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
        'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
        'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 
        'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 
        'teddy bear', 'hair drier', 'toothbrush']

1-6) YOLO V5 데이터셋 만들기 2 : 라벨 txt 파일 제작

  • 욜로 데이터 라벨을 제작해야 합니다.
  • 아래에 라벨 툴 추천 링크를 남겨두겠습니다.
  • (C기반 Darknet YOLO V3와 다르게, 이미지 내에 학습할 클래스가 없다면, txt 파일이 필요 없습니다.)

 (추천 1) yolo mark : https://github.com/AlexeyAB/Yolo_mark

 

AlexeyAB/Yolo_mark

GUI for marking bounded boxes of objects in images for training neural network Yolo v3 and v2 - AlexeyAB/Yolo_mark

github.com

(추천 2 : 제가 사용하고 있는 라벨 툴 입니다!) labelimg : https://github.com/tzutalin/labelImg

 

tzutalin/labelImg

🖍️ LabelImg is a graphical image annotation tool and label object bounding boxes in images - tzutalin/labelImg

github.com


1-6) YOLO V5 데이터셋 만들기 : 최종

  • 다음과 같이 데이터셋 폴더 생성을 합니다.

     1. 전체 데이터 폴더

        (1) 이미지 데이터 폴더

          - train 이미지 데이터 폴더

          - val 이미지 데이터 폴더

 

        (2) 텍스트 라벨 폴더

            - train 텍스트 라벨 폴더

            - val 텍스트 라벨 폴더

 

데이터 폴더 구조 예시 (출처 : https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data)


 1-7) YOLO V5 학습 진행 및 인자 설명

python train.py --data coco.yaml --cfg yolov5s.yaml --weights '' --batch-size 64
                                         yolov5m                                40
                                         yolov5l                                24
                                         yolov5x                                16

 

  • -- data : data yaml 파일 경로 (데이터셋 정보가 적힌 yaml 파일)

 

  • -- weights : Pre-Trained 모델 파일 경로 (pt 형식 파일)
  • 아무런 값을 적지 않으면 ('') 랜덤한 weight 값으로 초기화 및 학습 진행
  • (깃헙 공식 오피셜, pre-train을 추천합니다.)
  • pre-trained 파일이란? : 학습 파라미터(weight와 bias)가 잘 초기화된 파일
  • pre-trained를 할거면 위 링크에서 다운받은 weight file 이름을 적으면 됩니다. (ex : --weights yolov5s.pt) 
  • Transfer Learning (전이학습) 은 아래의 링크를 참고하세요! (Backbone만 고정, Backbone과 Head 모두 고정 옵션을 제공)
  • 전이학습 하는 방법 : github.com/ultralytics/yolov5/issues/1314
 

Transfer Learning with Frozen Layers · Issue #1314 · ultralytics/yolov5

🚀 Feature This guide explains how to freeze YOLOv5 layers when transfer learning. Transfer learning is a useful way to quickly retrain a model on new data without having to retrain the entire netwo...

github.com

 

  • -- batch-size : 배치 사이즈 값
  • 컴퓨터 GPU 성능에 맞게 설정하시면 됩니다.

 

  •   -- cfg : yolo v5 아키텍쳐 yaml 파일 경로
  • yolo v5는 s, m, l, x의 4가지 버전이 있음
  • s가 가장 가벼운 모델
  • x가 가장 무거운 모델
  • 당연히 s가 성능이 제일 낮지만 FPS가 가장 높고, x가 성능이 제일 높지만 FPS는 가장 낮습니다.


 1-8) 이미지로 테스트 해보기

  • 해당 결과는 runs/detect/exp/ 위치에 저장됩니다.
python detect.py --source data/images --weights yolov5s.pt --conf 0.25

 

  • -- source : 테스트 이미지 (혹은 폴더) 경로
  •  -- weights : 학습이 완료된 weight 파일 경로 (pt 형식)
  • train.py에서 weight 파일 저장 경로를 수정하지 않았다면, ~/yolov5/runs/train/exp/weights/ 경로에 best.pt와 last.pt가 있습니다. best.pt는 가장 성능이 좋은 weight 파일이고, last.pt는 최신 weight 파일입니다.

 

  • -- conf : conf_threshold 값 (0 ~ 1 사이의 값)
  • class score가 설정한 값을 넘겨야, 바운딩 박스를 그립니다.

테스트 예시 (출처 : https://github.com/ultralytics/yolov5)


[Error : DistributionNotFound: The 'pycocotools>=2.0' distribution was not found and is required by the application]

  • detect.py를 실행시켰는데 위와 같은 에러가 나올 수 있습니다.
  • 현재 pycocotools는 다운로드 에러가 발생하는데 왜 발생하는지 모르겠네요...
  • 학습할 때 pycocotools는 필요 없으니 line 167의 check_requirements()를 주석처리 해줍시다!

2. YOLO v5 아키텍쳐 분석하기

 YOLO v5도 일반적인 Object Detection의 구성과 큰 차이점은 없습니다. 크게 BackboneHead 부분으로 구성됩니다. 이 구성은 ~/yolov5/models/ 경로에 있는 yolov5s.yaml 파일 등에서 좀 더 자세히 확인할 수 있습니다 . 먼저 전반적인 설명을 하고 나서, YOLO v5의 레이어 모듈, 백본 그리고 헤드에 관해 설명하겠습니다.


 Backbone이미지로부터 Feature map을 추출하는 부분으로, CSP-Darknet를 사용합니다. YOLO v4의 백본과 유사합니다. YOLO v3의 Backbone은 Darknet53으로 CSP가 적용되지 않습니다. 특이하게도 YOLO v5의 backbone은 종류가 4가지나 됩니다. 제일 작고 가벼운 yolo v5-s부터 m, l, x 까지 포함해서 총 4가지 버전이 있습니다. 

 

 Head는 추출된 Feature map을 바탕으로 물체의 위치를 찾는 부분입니다. 흔히 말하는 Anchor Box(Default Box)를 처음에 설정하고 이를 이용하여 최종적인 Bounding Box를 생성합니다. YOLO v3와 동일하게 3가지의 scale에서 바운딩 박스를 생성합니다. (8 pixel 정보를 가진 작은 물체, 16 pixel 정보를 가진 중간 물체, 32 pixel 정보를 가진 큰 물체를 인식 가능) 또한 각 스케일에서 3개의 앵커 박스를 사용합니다. 그러므로 총 9개의 앵커 박스가 있습니다.

 

 4가지 버전 중  yolo v5-s 아키텍쳐 분석을 해보겠습니다. 아키텍쳐에 관한 코드는 ~/yolov5/models/ 경로에 있습니다. 2개의 코드가 중심입니다.

 

   (1) yolo.py : 욜로 아키텍쳐에 관한 코드입니다. 이 코드를 통해 욜로 아키텍쳐가 생성됩니다. 

 

   (2) common.py : 욜로 아키텍쳐를 구성하는 모듈(레이어)에 관한 코드입니다. 이 코드에 conv, BottleneckCSP 등등 욜로 모듈들이 적혀있습니다.

 

yolo.py를 실행시키면 아래와 같이 yolo v5 s의 아키텍쳐 구조가 나옵니다. yolov5s.yaml과 크게 다른 점은 없습니다. (아마 경로상의 문제 때문에 yolo.py의 위치를 ~/yolov5/models/ 에서 ~/yolov5/로 옮겨야 작동이 될 것 입니다.)

Using CUDA device0 _CudaDeviceProperties(name='GeForce GTX 1080 Ti', total_memory=11178MB)
           device1 _CudaDeviceProperties(name='GeForce GTX 1080 Ti', total_memory=11178MB)


                 from  n    params  module                                  arguments                     
  0                -1  1      3520  models.common.Focus                     [3, 32, 3]                    
  1                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]                
  2                -1  1     19904  models.common.BottleneckCSP             [64, 64, 1]                   
  3                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]               
  4                -1  1    161152  models.common.BottleneckCSP             [128, 128, 3]                 
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]              
  6                -1  1    641792  models.common.BottleneckCSP             [256, 256, 3]                 
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]              
  8                -1  1    656896  models.common.SPP                       [512, 512, [5, 9, 13]]        
  9                -1  1   1248768  models.common.BottleneckCSP             [512, 512, 1, False]          
 10                -1  1    131584  models.common.Conv                      [512, 256, 1, 1]              
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']          
 12           [-1, 6]  1         0  models.common.Concat                    [1]                           
 13                -1  1    378624  models.common.BottleneckCSP             [512, 256, 1, False]          
 14                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]              
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']          
 16           [-1, 4]  1         0  models.common.Concat                    [1]                           
 17                -1  1     95104  models.common.BottleneckCSP             [256, 128, 1, False]          
 18                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]              
 19          [-1, 14]  1         0  models.common.Concat                    [1]                           
 20                -1  1    313088  models.common.BottleneckCSP             [256, 256, 1, False]          
 21                -1  1    590336  models.common.Conv                      [256, 256, 3, 2]              
 22          [-1, 10]  1         0  models.common.Concat                    [1]                           
 23                -1  1   1248768  models.common.BottleneckCSP             [512, 512, 1, False]          
 24      [17, 20, 23]  1    229245  Detect                                  [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]

Model Summary: 191 layers, 7.46816e+06 parameters, 7.46816e+06 gradients

 2.1) YOLO v5 s' Module

   우선 각 모듈에 관한 설명을 하겠습니다.  common.py를 기반으로 작성합니다. 각 모듈 클래스의 forward 함수를 기반으로 분석했습니다.

 

   -- Focus : 이 모듈은 아직 이해하지 못했습니다. 주석에 따르면 x(b,c,w,h) -> y(b,4c,w/2,h/2) 과 같이 값이 변화한다고 하는데, 변수들(b, c, w, h 등)이 무엇을 의미하는지 모르겠습니다... 추후 이해하게 되면 작성하도록 하겠습니다.

-> b : batch_size, c : channel, w : width, h : height를 의미합니다!!

class Focus(nn.Module):
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)

    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))

 

  -- Conv : 일반적인 conv + batch_norm 레이어입니다. forward 함수를 보면, 이 레이어는 conv 연산을 한 후에 batch Normalization 과정을 거쳐줍니다. 활성화 함수로는 Hard swish 함수를 사용합니다.

class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.Hardswish() if act else nn.Identity()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))

 

    * Hard swish 함수는 최근에 나온 함수여서 그런지 관련 글이 없네요. 추후 torch 라이브러리를 뜯어보면서 분석해보겠습니다. 일단 swish에 관련 글을 첨부합니다 : https://yeomko.tistory.com/39

 

갈아먹는 딥러닝 기초 [1] Activation Function(활성화 함수) 종류

들어가며 딥 러닝 기초 개념들을 복습하면서 관련 내용들을 정리해보려 합니다. 가장 먼저 각 활성화 함수별로 간단한 특징과 사용처 정도를 짚고 넘어가겠습니다. 자세한 개념들은 직접 검색��

yeomko.tistory.com

  -- Bottleneck

 이 모듈은 ResNet에서 동일하게 사용한 Short-cut Connection이 있는 블록입니다. 아래의 그림과 같은 구조입니다. 

Bottleneck 구조

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super(Bottleneck, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

 

  -- BottleneckCSP

 이 모듈은 YOLO v5의 핵심입니다. 여기에서는 4개의 conv layer가 생성됩니다.

    * conv1, conv4 : conv + batch_norm 레이어

    * conv2, conv3 : conv 레이어 (batch_norm 적용 x)

 

 그 다음 CSP 구조 답게 2개의 y 값 생성을 합니다.

   * y1Short-Connection으로 연결된 conv1 -> conv3 연산 값

   * y2 : 단순히 conv2를 연산한 값

 

마지막으로 y1과 y2 값을 합치고, conv4 레이어를 통과하여 연산을 합니다.

class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super(BottleneckCSP, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        self.act = nn.LeakyReLU(0.1, inplace=True)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))

CSP 구조

  -- SPP

 이 모듈은 YOLOv3-SPP에서 사용했던 Spatial Pyramid Pooling Layer 입니다.  spatial bins 으로 5*5, 9*9, 13*13 피쳐맵을 사용했으며, 최종적으로 5 + 9 + 13 = 27의 크기로 고정된 1차원 형태의 배열을 생성하여, Fully Connected Layer에서 입력으로 들어갈 수 있게 합니다. 

class SPP(nn.Module):
    # Spatial pyramid pooling layer used in YOLOv3-SPP
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super(SPP, self).__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

 

  -- Concat

 이 모듈은 단순히 2개의 conv 레이어 연산 값을 합치는 것이라고 보면 됩니다.

class Concat(nn.Module):
    # Concatenate a list of tensors along dimension
    def __init__(self, dimension=1):
        super(Concat, self).__init__()
        self.d = dimension

    def forward(self, x):
        return torch.cat(x, self.d)

  -- torch.nn.modules.upsampling.Upsample

 이 모듈도 단순히 업샘플링하는 토치의 기본 라이브러리 함수입니다. 구조 값을 따르면 피쳐맵의 각 배열의 갯수를 2배로 올려줍니다.

    * 공식 링크 :  https://pytorch.org/docs/master/generated/torch.nn.Upsample.html

 

Upsample — PyTorch master documentation

Shortcuts

pytorch.org


  2.2) YOLO v5 s' BackBone

   특이하게도 YOLO v5의 backbone은 종류가 4가지나 됩니다. s(small), m(medium), l(large), x(extra인가...?ㅎㅎ) 버전이 있고, s가 backbone 크기가 가장 작고(=layers 수가 가장 적고) 빠르며, x가 크기가 가장 크고 느립니다.

 

   Backbone들은 2가지 변수로 결정됩니다. 바로 yaml 파일에 있는 "depth_multiple" (model depth multiple)과 "width_multiple"  (layer channel multiple) 입니다. 

 

   당연히 yolo v5-s의 depth&width_multiple이 가장 작고(depth_multiple : 0.33, width_multiple : 0.50), x의 depth&width_multiple은 (1.33, 1.25)로 가장 큽니다. 


2.2 - (1) Depth_Multiple 

  먼저 depth_multiple이 모델의 구조를 어떻게 변화시키는지 보겠습니다. 결론부터 얘기하자면, depth_multiple 값이 클수록 BottleneckCSP 모듈(레이어)이 더 많이 반복되어, 더 깊은 모델이 됩니다. 모든 설명은 yolo.py 코드를 기반으로 설명합니다. 먼저  parse_model 함수를 봅시다. (함수 일부만 가져왔습니다.)

def parse_model(d, ch):  # model_dict, input_channels(3)
    logger.info('\n%3s%18s%3s%10s  %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments'))
    anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
    # print("depth_multiple : %s" %gd)
    na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors  # number of anchors
    no = na * (nc + 5)  # number of outputs = anchors * (classes + 5)

  위의 코드를 보면, yaml 파일에서 읽어온 depth_multiple 값이 gd라는 변수에 저장되는 것을 볼 수 있습니다. (저와 같이 print문을 활용해서 변수 값들이 어떻게 출력되는지 보시면, 구조를 이해하는데 매우 좋습니다.) 이제 변수 gd는 depth gain의 변수인 n을 구하는데에 사용됩니다. 

layers, save, c2 = [], [], ch[-1]  # layers, savelist, ch out
    for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, args
        m = eval(m) if isinstance(m, str) else m  # eval strings
        for j, a in enumerate(args):
            try:
                args[j] = eval(a) if isinstance(a, str) else a  # eval strings
            except:
                pass

        n = max(round(n * gd), 1) if n > 1 else n  # depth gain
        # print("depth_gain : %s" %n)

  depth gain의 변수 n을 계산하기 위해서 2개의 변수를 사용합니다. 위에서 설명한 gd(=depth_multiple)값yaml 파일에서 number라고 적혀있는 n값을 사용합니다. n(number) 값을 설명하기 위해서 yolov5s.yaml 파일을 보겠습니다. 

# yaml file
# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, BottleneckCSP, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, BottleneckCSP, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, BottleneckCSP, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
   [-1, 3, BottleneckCSP, [1024, False]],  # 9
  ]

  yaml 파일에 적힌 구조는 yolo v5s~x 모두 동일합니다. Focus, Conv, SPP 모듈은 number 값이 1이고, BottleneckCSP 모듈만이 number 값을 3, 9를 가집니다. 이 차이를 알아두시고, 다시 depth gain 변수 n을 구하는 코드를 봅시다. 

n = max(round(n * gd), 1) if n > 1 else n  # depth gain

  depth gain 변수 n을 계산하기 위해 python의 max(최대값 찾기)와 round(반올림) 함수를 사용합니다. 각각의 함수에 대한 설명은 아래의 링크에 첨부하겠습니다.

  * max 함수 : pydole.tistory.com/entry/Python-max-min-sum-%EB%82%B4%EC%9E%A5%ED%95%A8%EC%88%98

 

[Python] max, min, sum 내장함수

파이썬 내장함수 - max(), min(), sum() max - 반복가능한 객체의 가장 큰 요소 값을 리턴 min - 반복가능한 객체의 가장 작은 요소 값을 리턴 sum - 반복가능한 객체의 요소 값의 합. (Default값 : 0) 1. 리..

pydole.tistory.com

  * round 함수 : wikidocs.net/21113

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

   먼저 백본의 number 변수 n과 depth_multiple 변수 gd를 곱하고 round 함수에 의해 소수점 둘째 자리에서 반올림 합니다. 그 후, max 함수를 통해 n*gd 값과 정수 1 중에서 큰 값을 선택합니다. 이것을 yolo v5-s 기준으로 좀 더 직관적으로 설명해보겠습니다.

 

  Focus, Conv, SPP 모듈은 n(number) 값이 1이기 때문에, n*gd = 0.33이 되고, round 함수에 의해 소수점 둘째 자리에서 반올림하여 0.3이 됩니다. (yolo v5-s의 depth_multiple 값은 0.33입니다!) round(n*gd, 1)=0.3과 정수 1 중에서 1이 큰 값입니다. 즉 Focus, Conv, SPP 모듈은 n(depth gain) 값이 1이 됩니다. 

 

  반면에 BottleneckCSP 모듈은 n(number)값을 3, 9를 가집니다. n=3일 때는, n*gd = 0.99 -> 소수점 둘째 자리에서 반올림하여 1.0이 됩니다. 즉, BottleneckCSP의 n(depth gain) 값은 1이 됩니다.

 

  그리고 n=9일때는 n*gd = 2.97 -> 소수점 둘째 자리에서 반올림하여 3.0이 됩니다. 즉 BottleneckCSP의 n(depth gain) 값은 3이 됩니다. 

 

  위에서 구한 n(depth gain) 값들은 다음의 코드에서 사용됩니다. 

m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args)  # module
# print("module : %s" %m_)

  코드를 직관적으로 보면, 해당 모듈들의 n(depth gain)값 만큼 반복합니다. 비교를 하기 위해, n(number)=3인 BottleneckCSP와 n=9인 BottleneckCSP의 구조를 가져왔습니다. (이들의 구조는 저와 같이 변수 "m_"을 print하면 확인할 수 있습니다.)

number : 3
depth_gain : 1
module : BottleneckCSP(
  (cv1): Conv(
    (conv): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): Hardswish()
  )
  (cv2): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (cv3): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (cv4): Conv(
    (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): Hardswish()
  )
  (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (act): LeakyReLU(negative_slope=0.1, inplace=True)
  (m): Sequential(
    (0): Bottleneck(
      (cv1): Conv(
        (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
      (cv2): Conv(
        (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
    )
  )
)
number : 9
depth_gain : 3
module : BottleneckCSP(
  (cv1): Conv(
    (conv): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): Hardswish()
  )
  (cv2): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (cv3): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (cv4): Conv(
    (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): Hardswish()
  )
  (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (act): LeakyReLU(negative_slope=0.1, inplace=True)
  (m): Sequential(
    (0): Bottleneck(
      (cv1): Conv(
        (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
      (cv2): Conv(
        (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
    )
    (1): Bottleneck(
      (cv1): Conv(
        (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
      (cv2): Conv(
        (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
    )
    (2): Bottleneck(
      (cv1): Conv(
        (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
      (cv2): Conv(
        (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): Hardswish()
      )
    )
  )
)

  무언가 엄~청 길어보이지만 핵심만을 보면 됩니다. 바로 "(n) : Bottleneck" 입니다. n(number)=3을 가지는 BottleneckCSP(0) : Bottleneck -> 하나만 반복됩니다.

 

   반면에 n(number)=9를 가지는 BottleneckCSP(0) ~ (2) : Bottleneck -> 3번 반복됩니다. 

 

  이렇게 n(number)과 gd(depth_multiple) 값으로 n(depth gain) 값을 계산하고, n(depth gain) 값에 따라 해당하는 모듈들이 반복됩니다. depth_multiple 값이 큰 yolov5-xs에 비해 당연히 더 많은 layers를 가지게 되어, 더 깊은 모델이 됩니다. 


2.2 - (2) Width_Multiple 

  width_multiple은 depth_multiple에 비해 간단합니다. 결론부터 얘기하자면, yaml 파일의 첫번재 args 값과 width_multiple 값을 곱한 값이 해당 모듈의 채널 값으로 사용됩니다. 즉, Width_Multiple 값이 증가할 수록 해당 레이어의 conv 필터 수가 증가합니다 .이번에도 yolo v5-s를 기준으로 설명하겠습니다. yolo v5-s의 width_multiple 값은 0.5입니다. 그리고 이 값은 변수 gw에 저장됩니다. 

# yaml file
# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, BottleneckCSP, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, BottleneckCSP, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, BottleneckCSP, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
   [-1, 3, BottleneckCSP, [1024, False]],  # 9
  ]
def parse_model(d, ch):  # model_dict, input_channels(3)
    logger.info('\n%3s%18s%3s%10s  %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments'))
    anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
    # print("depth_multiple : %s" %gd)
    print("width_multiple : %s" %gw)

  변수 gw 외에도 1개 더 알아야 하는 변수가 있습니다. 바로 c2입니다. c2는 yaml 파일의 args의 첫번째 변수입니다. 예를 들어 Focus 모듈args로 [64, 3]을 가지고 있고, 그 중 첫번째 값인 64가 c2 값이 됩니다. 

        if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]:
            c1, c2 = ch[f], args[0]
            print("args [0] : %s" %c2)

  변수 gw와 c2는 아래와 같이 make_divisible 함수에 의해 계산되는데, 코드로 알아보겠습니다.

            c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
            print("make_divisible_c2 : %s" %c2)
            
            
################### utils 폴더 안에 있는 general.py 코드 ####################

def make_divisible(x, divisor):
    # Returns x evenly divisble by divisor
    return math.ceil(x / divisor) * divisor

  단순하게 생각해서, gw와 c2를 곱한다고 보면 됩니다. yolo v5-s에서 계산된 c2 값을 보면 아래와 같습니다.

                 from  n    params  module                                  arguments
width_multiple : 0.5
args [0] : 64
make_divisible_c2 : 32
  0                -1  1      3520  models.common.Focus                     [3, 32, 3]
args [0] : 128
make_divisible_c2 : 64
  1                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]
args [0] : 128
make_divisible_c2 : 64
  2                -1  1     19904  models.common.BottleneckCSP             [64, 64, 1]
args [0] : 256
make_divisible_c2 : 128
  3                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]
args [0] : 256
make_divisible_c2 : 128
  4                -1  1    161152  models.common.BottleneckCSP             [128, 128, 3]
args [0] : 512
make_divisible_c2 : 256
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
args [0] : 512
make_divisible_c2 : 256
  6                -1  1    641792  models.common.BottleneckCSP             [256, 256, 3]
args [0] : 1024
make_divisible_c2 : 512
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]
args [0] : 1024
make_divisible_c2 : 512
  8                -1  1    656896  models.common.SPP                       [512, 512, [5, 9, 13]]
args [0] : 1024
make_divisible_c2 : 512
  9                -1  1   1248768  models.common.BottleneckCSP             [512, 512, 1, False]

  결론을 얘기하면, yaml 파일의 첫번째 args 인자와 width_multiple 값을 곱한 값이 해당 모듈의 채널 값이 됩니다.  yolo v5-xs에 비해 큰 width_multiple 값을 지니므로, 각 모듈의 채널 수가 가장 많습니다.

 

  여기까지가 YOLO v5의 backbone에 대한 설명입니다. 이정도만 이해하시면 당신은 yolo v5의 backbone 마스터 입니다!


이 부분은 추후에 좀 더 공부를 하고 나서 정리하겠습니다. 

# result of yolo.py (yolo v5 s' backbone)

  i              from  n    params  module                                  arguments                     
  0                -1  1      3520  models.common.Focus                     [3, 32, 3]                    
  1                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]                
  2                -1  1     19904  models.common.BottleneckCSP             [64, 64, 1]                   
  3                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]               
  4                -1  1    161152  models.common.BottleneckCSP             [128, 128, 3]                 
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]              
  6                -1  1    641792  models.common.BottleneckCSP             [256, 256, 3]                 
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]              
  8                -1  1    656896  models.common.SPP                       [512, 512, [5, 9, 13]]        
  9                -1  1   1248768  models.common.BottleneckCSP             [512, 512, 1, False]          

 

  -- P1, P3의 의미 : Pooling의 약자이다. 각 레이어의 args 배열에서 2번째 위치에 있는 2는 stride의 값으로, stride=2를 주면 피쳐맵의 가로, 세로의 값은 2배로 줄고, 높이의 값은 2배로 증가하는 pooling 효과를 보여준다. 다시 정리하면 아래와 같다.

 

  ex) 0-P1/2 

    * 0 : 백본에서 0번째 레이어

    * P1 : 백본에서 첫번째 Pooling Layer

    * /2 : 원본 이미지에서 2배만큼 Pooling 됨

 

  -- yaml 파일에서의 arguments의 의미 : [다음 레이어 ouput의 fitter(높이) 값, ?, stride 값]

    * SPP에선 1번째 위치의 값은 spatial bins 으로 5*5, 9*9, 13*13 피쳐맵을 의미

     * ?라고 적은 것은 아직 분석 중

 

  -- yolo.py 결과에서의 arguments의 의미 : [현재 레이어의 input의 fitter 값, 현재 레이어의 output의 fitter 값, ?, stride 값]


  2.3) YOLO v5 s' Head

# yolov5s.yaml
# YOLOv5 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, BottleneckCSP, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, BottleneckCSP, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, BottleneckCSP, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, BottleneckCSP, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]
# yolo.py result (YOLOv5-s'HEAD)

  i              from  n    params  module                                  arguments  
 10                -1  1    131584  models.common.Conv                      [512, 256, 1, 1]
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  1    378624  models.common.BottleneckCSP             [512, 256, 1, False]
 14                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  1     95104  models.common.BottleneckCSP             [256, 128, 1, False]
 18                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  1    313088  models.common.BottleneckCSP             [256, 256, 1, False]
 21                -1  1    590336  models.common.Conv                      [256, 256, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  1   1248768  models.common.BottleneckCSP             [512, 512, 1, False]
 24      [17, 20, 23]  1    229245  Detect                                  [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]

  드디어 YOLO v5의 헤드 공략입니다! yaml 파일을 보면 헤드도 [from, number, module, args] 으로 구성되있는 것을 확인할 수 있습니다. 그리고 {Conv, Upsample, Concat, BottleneckCSP}이 한 블록이라고 생각하면 될 것 같습니다. 이러한 블록들이 총 4개가 있고, 마지막의 Detect 부분에서 연결됩니다.

 

  헤드에서는 BottleneckCSP 만이 number 값이 3으로, depth_multiple 값에 따라 더 많이 반복될 수 있습니다. 즉 yolo v5-x가 s에 비해 Head 층도 더 깊다는 것을 알 수 있습니다. 헤드에서 주의 깊게 봐야할 모듈은 ConcatDetect 입니다. 먼저 Concat에 대해 분석해보겠습니다.


2.3 - (1) Concat 모듈 

  Concat 모듈을 보면 다음과 같은 구성으로 되어있는 것을 볼 수 있습니다.

[[-1, 6], 1, Concat, [1]],  # cat backbone P4

  여기에서 중요하게 볼 것은 바로 첫 인자 [-1, 6]입니다. 다시 저 블록을 가져와보겠습니다.

  [[-1, 1, Conv, [512, 1, 1]], # head p5 (N 10)
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, BottleneckCSP, [512, False]],  # 13

  여기에서 Concat의 의미는 Concat의 바로 직전 층인 nn.Upsamlple 층i=6인 BottleneckCSP 층결합을 의미합니다. 이 부분은 yolo.py에서 Concat에 해당하는 if 문에서 ch값들을 print 해보면 알 수 있습니다.

        elif m is Concat:
            print("ch_list : %s" %ch)
            for x in f:
                if x == -1:
                    print("x : %s" %x)
                    print(ch[-1])
                else:
                    print("x+1 : %s" %str(x+1))
                    print(ch[x+1])

            c2 = sum([ch[-1 if x == -1 else x + 1] for x in f])

            print("c2 : %s" %c2)

  Concat을 정리하자면 다음과 같습니다.

 

  * 첫 번째 블록의 Concat 부분 : 백본의 P4와 결합 (yolo.py 기준 i=6인 BottleneckCSP)

 

  * 두 번째 블록의 Concat 부분 : 백본의 P3와 결합 -> 작은 물체 검출 (yolo.py 기준 i=4인 BottleneckCSP)

 

  * 세 번째 블록의 concat 부분 : 헤드의 P4와 결합 -> 중간 물체 검출 (yolo.py 기준 i=14인 Conv)

 

  * 네 번째 블록의 concat 부분 : 헤드의 P5와 결합 -> 큰 물체 검출 (yolo.py 기준 i=10인 Conv)

 

  그런데 왜 백본 P3과 결합하면 작은 물체를 검출 하고, 헤드 P5와 결합하면 큰 물체를 검출하는지는 이해를 못했습니다ㅜㅜ 이 부분은 추후 공부하고 업로드하겠습니다


2.3 - (2) Detect 모듈 

  Detect 모듈을 보면 다음과 같습니다.

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)

  정말 말 그대로 i=17, 20, 23인 레이어를 종합하여 Detect 하는 것입니다. 더 자세한 설명은 필요가 없을 것 같네요!


3. 앵커 박스 분석하기

 3.1) 앵커 박스 값 계산하기

   yolo v5에서 default로 사용하는 앵커 박스는 코코 데이터 기반의 값입니다. 즉 우리의 커스텀 데이터에서는 앵커 박스 값이 적절하지 않을 수 있습니다. 그래서 커스텀 데이터셋에 알맞는 앵커 박스 값을 계산을 해야할 때가 오는데, 이 코드는 ~/yolov5/utils/ 폴더 안에 'general.py' 코드의 kmean_anchors() 함수에 있습니다. 사용법은 파이썬으로 진입해서 다음의 명령어를 입력하면 됩니다.

from utils.general import *; _ = kmean_anchors(path='data/custom.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True)

   kmean_anchors 함수의 인자는 다음과 같습니다.

   (1) path : data yaml 파일 경로

   (2) n : 생성할 앵커박스 갯수 (우리는 9개의 앵커박스를 생성)

   (3) img_size : 이미지 크기

   (4) thr, gen, verbose : 이 부분은 아직 파악을 못했습니다. 추후 공부해서 업로드하겠습니다.

 

   그 결과 output은 다음과 같이 나옵니다. 이 값을 model.yaml 파일의 anchor 부분에 넣어주면 됩니다.

thr=0.25: 0.9893 best possible recall, 8.67 anchors past thr
n=9, img_size=640, metric_all=0.558/0.870-mean/best, past_thr=0.573-mean: 25,36,  33,33,  49,33,  40,46,  57,50,  25,116,  64,71,  87,62,  103,96

4. 최적화 분석하기

 4.1) LOSS 함수분석

    (1) GIoU (giou_loss)

        giou lossbounding box에 관한 loss 함수입니다. giou loss를 이해하기 전에, IOU loss가 무엇인지 보고 오면 좋습니다! 결론부터 얘기하면, IOU loss = 1 - IOU 입니다. 수식적인 의미는 나중에 차차 알아가기로...!

        * IOU loss 관련 링크 : m.blog.naver.com/PostView.nhn?blogId=sogangori&logNo=221009464294&proxyReferer=https:%2F%2Fwww.google.com%2F

 

Segmantion Network에서 엔트로피 대신 IoU를 로스로 학습시키기

Optimizing Intersection-Over-Union in Deep Neural Networks for Image Segmentation 논문에 대...

blog.naver.com

        giou lossutils/general.py의 compute_loss 함수에 나와있습니다. 1 - giou 값 = giou loss 입니다.

giou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True)  # giou(prediction, target)
lbox += (1.0 - giou).mean()  # giou loss

 

        GIOU를 어떻게 구하는가?는 general.py의 bbox_iou 함수에 나와있습다. 역시 코드가 설명이 제일 깔끔하네요ㅎㅎ

# Intersection area
inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
	(torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)

# Union Area
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1

union = (w1 * h1 + 1e-16) + w2 * h2 - inter
iou = inter / union  # iou

if GIoU or DIoU or CIoU:
	cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width
	ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height
	if GIoU:  # Generalized IoU https://arxiv.org/pdf/1902.09630.pdf
		c_area = cw * ch + 1e-16  # convex area
		return iou - (c_area - union) / c_area  # GIoU

    (2) obj (objectness loss), cls (classification loss)

        objectness loss와 class loss로는 BCEwithLogitsLoss를 사용합니다. 이 또한 general.py에서 확인할 수 있습니다. BCEwithLogitsLoss는 class가 2개인 경우에 사용하는 loss function인 BCE에 sigmoid layer를 추가한 것입니다. (BCE는 Binary Cross Entropy의 약자입니다.) 

        * BCE란? : nuguziii.github.io/dev/dev-002/

 

[PyTorch] 자주쓰는 Loss Function (Cross-Entropy, MSE) 정리

PyTorch에서 제가 개인적으로 자주쓰는 Loss Function (Cross Entropy, MSE) 들을 정리한 글입니다.

nuguziii.github.io

        * obj, cls 수식 알아보기 : wdprogrammer.tistory.com/50 

 

[object detection] YOLO 모델의 원리

Object detection 분야에서 쓰이는 모델로는, Faster-RCNN, MobileNet, SSD 등 많은 모델이 있지만 그 중 YOLO 모델에 대해 자세히 알아보려 한다. 일단, 현 시점에서는 YOLO, YOLOv2, YOLOv3(YOLO 9000)까지 모델..

wdprogrammer.tistory.com

 

        classification loss는 객체가 탐지되었을 때, 탐지된 객체의 class가 맞는지에 대한 loss입니다. MSE와 유사하게 (판단 값 - 실제 값)^2 해서 구합니다. (위 링크의 수식을 보면 이해가 더 잘 될 것입니다.)

lcls += BCEcls(ps[:, 5:], t)  # BCE

 

        objectness loss는 class에 구분 없이 객체 탐지 자체에 대한 loss입니다. (이 loss 역시 위 링크의 수식을 보면 더 이해가 잘 될것입니다.) 객체가 있을 경우의 loss와, 없을 경우의 loss를 따로 구하는데, 각 loss에 가중치 값을 곱하여 클래스 불균형 문제를 해결합니다. 보통 객체가 있을 경우보다, 배경의 갯수가 더 많으므로...! (코드에서는 가중치를 balance 변수로 사용한 것 같은데 확실하지는 않습니다..!)

# Losses
    nt = 0  # number of targets
    np = len(p)  # number of outputs
    balance = [4.0, 1.0, 0.4] if np == 3 else [4.0, 1.0, 0.4, 0.1]  # P3-5 or P3-6
    
    ***
    
		lobj += BCEobj(pi[..., 4], tobj) * balance[i]  # obj loss

 4.2) Optimizer 분석

    optimizer의 default 값은 SGD, 추가 설정으로 Adam으로 변경할 수 있습니다. 이에 대한 설명은 구글링이 답입니다!


 4.3) mAP 분석

    train.py에서 mAP_0.5mAP_0.5:0.95가 나옵니다. 이들의 의미를 알아보겠습니다!

    * AP란?? : bskyvision.com/465

 

물체 검출 알고리즘 성능 평가방법 AP(Average Precision)의 이해

물체 검출(object detection) 알고리즘의 성능은 precision-recall 곡선과 average precision(AP)로 평가하는 것이 대세다. 이에 대해서 이해하려고 한참을 구글링했지만 초보자가 이해하기에 적당한 문서는 찾�

bskyvision.com

 

    (1) mAP_0.5

        mAP의 평균을 IoU Threshold = 0.5로 구한 값입니다. 

출처 : https://cocodataset.org/#detection-eval

 

    (2) mAP_0.5:0.95

        mAP의 평균을 다음의 IoU Threshold 값으로 구한 것입니다. (0.5, 0.55, 0.6, 0.65, ,,, , 0.9, 0.950.5~0.95 사이의 IOU threshold 값을 0.05 씩 값을 변경해서 측정한 mAP의 평균값입니다. IoU의 threshold 값이 mAP_0.5보다 높기 때문에, 수치는 mAP_0.5보다 낮게 나옵니다. 


제가 분석한 글은 여기까지 입니다! 저와 같이 YOLO V5를 공부하느라 고생 많으셨습니다~!

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31