오늘은 YOLO V5 (Pytorch) 환경 셋팅 및 모델 아키텍쳐(Backbone, Head) 분석을 하겠습니다. YOLO v5 환경 셋팅 및 학습에 관한 글은 있지만, 아키텍쳐를 자세하게 분석한 글은 거의 없네요... 아무래도 논문이 아직 안나왔기 때문에, 깃헙 코드로만 아키텍쳐를 분석해야해서 관련 글이 없나 봅니다. 그럼 YOLO v5 분석 시작~!!
링크 0) YOLO v5 Pytorch 깃헙 링크 : https://github.com/ultralytics/yolov5
링크 1) YOLO v5 custom train 예제 링크 : https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data
링크 2) YOLO v3 아키텍쳐 참고 블로그 : dojinkimm.github.io/computer_vision/2019/07/31/yolo-part1.html
링크 3) YOLO v5 아키텍쳐 참고 블로그 (영문) : https://medium.com/towards-artificial-intelligence/yolo-v5-explained-and-demystified-4e4719891d69
1. YOLO v5 학습 환경 셋팅하기
1-1) 필자의 학습 환경
- OS : Ubuntu 18.04 (& Window 10)
- CUDA : 10.2
- cuDNN : 7.6.5
- Anaconda환경
1-2) 아나콘다 환경 생성
필자는 아나콘다 환경에서 학습을 진행할겁니다. 그러므로 conda env 생성을 해줍니다. (제가 아나콘다를 사용하는 이유는 학습 모델의 종류마다 사용하는 torch 및 torchvision 버전이 달라서입니다.) 이름은 아무거나 지어도 상관없고, 파이썬 버전은 3.8 이상으로 환경을 생성하면 됩니다. 저는 환경 이름을 yolov5라고 지었습니다.
conda create -n yolov5 python=3.8
conda activate yolov5
1-3) YOLO v5 깃헙 폴더 클론하기 (링크 0 참고)
이제 yolo v5 깃헙 폴더를 클론해줍니다.
# if your OS is UBUNTU
sudo git clone https://github.com/ultralytics/yolov5.git
# if your OS is WINDOW
git clone https://github.com/ultralytics/yolov5.git
# if your yolov5 folder is lock (UBUNTU)
sudo chmod 777 -R ~/yolov5
# move to yolov5 folder
cd ~/yolov5
1-4) 필요한 라이브러리 설치하기 (yolo v5 깃헙의 requirements.txt 참고)
torch는 1.6.0 버전 이상, torchvisino은 0.7.0 이상이여야 합니다! (아래의 명령어를 사용하면 자동으로 요구 조건에 만족하는 버전을 설치합니다.) window에서도 동일한 명령어로 설치가 됩니다.
# Conda commands (in place of pip) ---------------------------------------------
conda update -yn base -c defaults conda
conda install -yc anaconda numpy opencv matplotlib tqdm pillow ipython
conda install -yc conda-forge scikit-image pycocotools tensorboard
conda install -yc spyder-ide spyder-line-profiler
# Ubuntu 일 경우
conda install -yc pytorch pytorch torchvision
# window 일 경우
conda install pytorch torchvision cudatoolkit=10.2 -c pytorch
conda install -yc conda-forge protobuf && pip3 install onnx==1.6.0
1-5) (선택) Pre-trained 모델 다운받기
아래의 링크에서 pre-trained 모델을 다운받을 수 있습니다.
* pre-trained 모델 다운로드 링크 : https://github.com/ultralytics/yolov5/releases/tag/v3.0
1-6) YOLO V5 데이터셋 만들기 1 : yaml 파일 제작
이제 학습 데이터의 경로, 클래스 갯수 및 종류가 적혀 있는 yaml 파일 제작을 해야합니다. 아래는 yolo v5 깃헙에서 제공하는 coco.yaml의 예시입니다. 우리가 수정해야할 부분은 다음과 같습니다.
- train : 학습 데이터 폴더 경로 (이미지)
- val : 검증 데이터 폴더 경로 (이미지)
- nc : 학습할 클래스 갯수
- 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/train2017/
# 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
- (추천 2 : 제가 사용하고 있는 라벨 툴 입니다!) labelimg : https://github.com/tzutalin/labelImg
1-6) YOLO V5 데이터셋 만들기 : 최종
다음과 같이 데이터셋 폴더 생성을 합니다.
1. 전체 데이터 폴더
(1) 이미지 데이터 폴더
- train 이미지 데이터 폴더
- val 이미지 데이터 폴더
(2) 텍스트 라벨 폴더
- train 텍스트 라벨 폴더
- val 텍스트 라벨 폴더
데이터 폴더 구조 예시 (출처 : https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data)
1-7) YOLO V5 학습 진행 및 인자 설명
python3 train.py --data coco.yaml --cfg yolov5s.yaml --weights '' --batch-size 64
yolov5m 40
yolov5l 24
yolov5x 16
-- data : yaml 파일 경로 (데이터셋 정보가 적힌 yaml 파일)
-- weights : Pre-Trained 모델 파일 경로 (pt 형식 파일)
* 아무런 값을 적지 않으면 base부터 학습 진행 (= Backbone도 학습한다는 의미)
* 데이터셋이 적고 학습 시간이 많지 않을 경우에는 pre-trained를 추천
* pre-trained를 할거면 위 링크에서 다운받은 weight file 이름을 적으면 됩니다. (ex : --weights yolov5s.pt)
-- batch-size : 배치 사이즈 값
* 컴퓨터 GPU 성능에 맞게 설정하시면 됩니다.
-- cfg : yolo v5 아키텍쳐 yaml 파일 경로
* yolo v5는 s, m, l, x의 4가지 버전이 있음
* s가 가장 가벼운 모델
* x가 가장 무거운 모델
* 당연히 s가 성능이 제일 낮지만 FPS가 가장 높고, x가 성능이 제일 높지만 FPS는 가장 낮습니다.
YOLO v5 성능 비교표 (출처 : https://github.com/ultralytics/yolov5)
1-8) 이미지로 테스트 해보기
python3 detect.py --source ./inference/images/ --weights yolov5s.pt --conf 0.4
-- source : 테스트 이미지 (혹은 폴더) 경로
-- weights : 학습이 완료된 weight 파일 경로 (pt 형식)
* train.py에서 weight 파일 저장 경로를 수정하지 않았다면, ~/yolov5/runs/exp0/weights/ 경로에 best.pt와 last.pt가 있습니다. best.pt는 가장 성능이 좋은 weight 파일이고, last.pt는 최신 weight 파일입니다.
-- conf : conf_threshold 값 (0 ~ 1 사이의 값)
* class score가 설정한 값을 넘겨야, 바운딩 박스를 그립니다.
테스트 예시 (출처 : https://github.com/ultralytics/yolov5)
2. YOLO v5 아키텍쳐 분석하기
YOLO v5도 일반적인 Object Detection의 구성과 큰 차이점은 없습니다. 크게 Backbone과 Head 부분으로 구성됩니다. 이 구성은 ~/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 등)이 무엇을 의미하는지 모르겠습니다... 추후 이해하게 되면 작성하도록 하겠습니다.
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
-- 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 값 생성을 합니다.
* y1 : Short-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
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
* round 함수 : wikidocs.net/21113
먼저 백본의 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-x는 s에 비해 당연히 더 많은 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-x는 s에 비해 큰 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 층도 더 깊다는 것을 알 수 있습니다. 헤드에서 주의 깊게 봐야할 모듈은 Concat과 Detect 입니다. 먼저 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 loss는 bounding 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
giou loss는 utils/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/
* obj, cls 수식 알아보기 : wdprogrammer.tistory.com/50
classification loss는 class가 3개 이상일 경우에만 적용됩니다. 객체가 탐지되었을 때, 탐지된 객체의 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.5와 mAP_0.5:0.95가 나옵니다. 이들의 의미를 알아보겠습니다!
* AP란?? : bskyvision.com/465
(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.95) 즉 0.5~0.95 사이의 IOU threshold 값을 0.05 씩 값을 변경해서 측정한 mAP의 평균값입니다. IoU의 threshold 값이 mAP_0.5보다 높기 때문에, 수치는 mAP_0.5보다 낮게 나옵니다.
'etc > FastCampus 챌린지' 카테고리의 다른 글
[패스트캠퍼스 수강 후기] 컴퓨터비전인강 100% 환급 챌린지 47 회차 (0) | 2020.12.04 |
---|---|
[패스트캠퍼스 수강 후기] 컴퓨터비전인강 100% 환급 챌린지 46 회차 (0) | 2020.12.03 |
[패스트캠퍼스 수강 후기] 컴퓨터비전인강 100% 환급 챌린지 44 회차 (0) | 2020.12.01 |
[패스트캠퍼스 수강 후기] 컴퓨터비전인강 100% 환급 챌린지 43 회차 (0) | 2020.11.30 |
[패스트캠퍼스 수강 후기] 컴퓨터비전인강 100% 환급 챌린지 42 회차 (0) | 2020.11.29 |