1. 모델 분석/Object Detection

[RetinaNet] 2. Explaining overall RetinaNet model and Code analysis

M_AI 2021. 5. 31. 18:19

M_AI 입니다.이전 포스팅인 Anchor Box의 간략한 설명에 이어서 본격적으로 RetinaNet의 모델 및 코드 설명과 이전에 다룬 앵커 박스 코드에 대해서 다루도록 해보겠습니다!!

 

이전글 ☞ https://yhu0409.tistory.com/2

 

[RetinaNet] 1. Anchor Box

안녕하세요! M_AI 입니다. ( 네이버 블로그로 운영하다가 그냥 티스토리로 넘어왔습니다! 그래서 그냥 내용만 복붙해서 올리도록 하겠습니다! ) ​ 제 블로그 첫 글인데요, 이전까지 Image Processing

yhu0409.tistory.com


이 글을 읽으면 좋을 것 같은 사람들!

- 기본적인 분류(classfication) 모델을 Tensorflow 2.x 버전으로 구현할 줄 아는 사람

- Clssification을 넘어서 Object detection을 하길 원하는데 오픈 소스를 봐도 이해가 가질 않는 사람.

- 오픈 소스를 이해하더라도 입맛대로 수정하는 방법을 모르는 사람.

 

본 게시글의 목적

※ 본 게시글은 딥러닝 모델의 상세한 이론적 설명보다는 모델 구조를 바탕으로 코드 분석하고 이해하는 데에 목적에 있습니다!

※ 상세한 이론을 원하면 다른 곳에서 찾아보시길 바랍니다!

 

 

본 게시글의 특징

① Framework : Tensorflow 2.x

② 학습 환경

- OS : Ubuntu

- GPU : RX570 (AMD)

③ Batch size = 1

④ Kaggle의 RSNA Pneumonia Detection Challenge 데이터셋 사용하여 Chest X-ray에서 폐렴 검출 (Pneumonia detection) 목적.

https://www.kaggle.com/c/rsna-pneumonia-detection-challenge

 

RSNA Pneumonia Detection Challenge

Can you build an algorithm that automatically detects potential pneumonia cases?

www.kaggle.com

⑤ 게시글에서는 반말


 

2. Explaining overall RetinaNet model and Code Analysis

2.1. Model

해당 모델이 처음 언급된 논문은 "Focal Loss for Dense Object Detection"[1]이며, 2018년도에 public된 논문이다.

 

제목만 보아도 알다시피 해당 논문의 주요 내용은 새로운 모델에 대한 설명이 아닌, 새로운 loss function인 "Focal loss"에 대한 설명이다.

 

그래서인지 논문[1]에서 나오는 RetinaNet 모델 구조(Figure 4)에 대한 설명이 매우 부실하였다. 생략된 padding, stride, filter, activation function 등의 정보로 인해 별도로 Resnet[2]과 FPN[3]을 별도로 찾아보아야 했다.

 

Figure 4. RetinaNet Architecture on RetinaNet paper (출처 : Focal Loss for Dense object Detection [1])

 

 

2.1.1. ResNet

논문[1]과 RetinaNet의 예제 코드[4]에서는 ResNet50을 사용하고 있으며, 해당 모델에 대한 구조는 다음 (Figure 5)와 같다.

Figure 5. ResNet Architecture (출처 : Deep Residual Learning for Image Recognition [2])

 

논문[2]에서, 50-layer 이상 모델에서는 미만 모델과 달리 각 convolution layer에서 1X1 filter가 추가되어있으며, 이에 대한 명칭은 ‘bottleneck skip connection’이라 한다. (Figure 6 참고)

 

 

Figure 6. Residual function (출처 : Deep Residual Learning for Image Recognition[2])

 

논문[2]에 이렇게 나와 있음에도 불구하고 activation function과 batch normalization, padding 여부 등에 대한 설명이 언급되지 않는데, 이에 대한 것은 아래 github 사이트[5]에 명확히 나와 있다.

http://ethereon.github.io/netscope/#/gist/db945b393d40bfa26006

 

Netscope

 

ethereon.github.io

 

Keras로 구현된 Resnet50 코드는 "eremo2002"님께서 포스팅하신 https://eremo2002.tistory.com/76 [6]에 무척 잘 나와있다.

 

ResNet50 구현해보기

케라스를 이용하여 ResNet50을 구현하였다. ResNet 50-layer 네트워크 구조는 다음과 같다. 그리고 레이어가 50개 이상인 버전에서는 오른쪽과 같은 bottleneck skip connection 구조를 사용한다. 케라스에서

eremo2002.tistory.com

ResNet50 구현해보기

 

사실 본 게시글과 코드에서는 keras에서 ImageNet으로 사전 학습된 Resnet50을 가져와 학습시키기 때문에 ResNet50 모델 코딩은 따로 할 필요가 없다.

 

정 궁금하다면 아래 코드를 실행해보기 바란다.

backbone = keras.applications.ResNet50(include_top=False, input_shape=(512, 512, 3))
backbone.summary()

 

 

그럼 왜 ResNet Model을 설명하는가?

 

 

이에 대한 답은

전반적인 모델 구조의 이해를 위해 feature map 크기가 어디서 절반으로 감소하는지에 대해 파악해야 하기 때문!

 

기본적은 object detection 모델은 사전 학습된 분류(classification) 모델을 가져와 수정한 후에 사용하기 때문에, Backbone으로 사용할 모델 또한 어떤 구성인지 파악해야만 한다.

 

 

2.1.2. FPN

다음으로는 FPN으로, object detection에서 서로 다른 크기를 가지는 object들을 효과적으로 detection하기 위하여 고안된 방안으로, bottom-up과 top-down 방식을 결합하여 feature map들을 skip connection으로 연결하는 방식이다. (Figure 7-(d))

Figure 7. 4 feature pyramid architecture (출처 : Feature Pyramid Networks for Object Detection [3])

 

기존의 방식인 (Figure 7-(a), (b), (c))은 bottom-up 방식으로, 상위 feature map에는 핵심적인 정보들을 가지고 있지만,

 

매우 추상적이며 위치적 정보가 상당히 손실되어있다. 이를 보완하기 위해 top-down 방식을 도입하여, upsampling과 기존의 feature map과 skip connection하여 손실된 정보를 추가한다.

 

또한 추가적으로 (Figure 8)와 같이 upsampling은 2x로 진행되며, skip connection 전에 1x1 convolution이 진행된다.

Figure 8. Skip connection (출처 : Feature Pyramid Networks for Object Detection [3])

 

이후 각 Pyramid output에서 3x3 convolution 과정이 있지만, 이는 아래 전체적인 RetinaNet에서 다루도록 하겠다.

 

 

2.1.3. RetinaNet

Figure 9. RetinaNet Architecture ( C의 헤드 이미지 출처 : Focal loss for Dense obeject Detection [1])

 

(Figure 9)은 RetinaNet의 전체적인 모델 구조이다.

단계별 과정은 다음과 같다.

 

(Figure 9-A) : ResNet

ResNet50에서의 Conv 3, Conv 4, Conv 5의 결괏값을 FPN으로 전달한다. 이 과정에서1x1의 256개 필터로zero-padding과 stride =1로 convolution한다.

 

(Figure 9-B) : FPN

여기서도 두 과정으로 나뉜다.

1. Pyramid 3, 4, 5 생성

ResNet50으로부터 전달받은 처리된 Conv 3, Conv 4, Conv 5의 결괏값을 이용하여 Pyramid3, 4, 5를 생성한다.

이 과정에서 Pyramid 5는 전달받은 Conv5 결괏값이며, Pyramid 4는 2x upsampling한 Pyramid 5와 전달받은 Conv4의 결괏값과 합쳐진 것이다.

Pyramid 3은 2x upsampling한 Pyramid 4와 전달받은 Conv3 결괏값과 합쳐진 것이다.

 

2. 새로운 Pyramid 3, 4, 5, 6, 7 생성

새로운 Pyramid 3, 4, 5는 이전의 Pyramid 3, 4, 5에서 3x3의 256개 필터로 zero-padding과 stride =1로 convolution하여 생성한 feature map이다.

pyramid 6은 새로운 pyramid 5에서 3x3의 256개 필터로 zero-padding과 stride =2로 convolution하여 생성한 feature map이다.

pyramid 7은 pyramid 6에서 3x3의 256개 필터로 zero-padding과 stride =2로 convolution하여 생성한 feature map이다.

이렇게 생성된 Pyramid 3, 4, 5, 6 ,7의 feature map이 다음 헤드로 전달된다.

 

(Figure 9-C) : 분류 헤드와 박스 regression 헤드

헤드 구조는 일반적인 분류(classification) 모델에서 볼 수 없는 구조, object detection에서만 볼 수 있다.

헤드의 종류는 총 2가지로, 앵커 박스가 배경 영역인지(Background)와 객체를 포함하는 영역인지(Foreground)를 분류하는 분류 헤드(classification head)와 앵커 박스 위치 좌푯값을 regression하는 박스 regression 헤드(box regression head)가 있다.

 

1. Classification head

분류 헤드의 결과 필터 개수는 (9 * 클래스 개수)인데, 여기서 9는 한 위치에서 생성되는 앵커 박수의 수이다. 필터의 개수가 이러한 이유는 앵커 박스에서 배경 영역인지 특정 클래스의 객체를 포함하는 영역인지를 판단하기 위해서이다.

예를 들어 설명을 더 하자면, 한 앵커 박스에 대해서 (배경 or 클래스 1 객체 판단), (배경 or 클래스 2 객체 판단), (배경 or 클래스 3 객체 판단), ..., (배경 or 클래스 N 객체 판단)하여 각 클래스별에 대한 예측값을 도출한다.

 

2. Box regression head

박스 regression 헤드의 결과 필터 개수는 (9 * 4)인데, 여기서도 마찬가지로 9는 앵커 박스의 개수이며, 4는 박스 나타내는 좌표 개수로 구성은 박스의 중앙 좌표 x와 y, 그리고 박스의 너비와 높이로 구성되어있다.

즉, 각 앵커 박스에 대한 좌푯값을 나타내기 위해 9*4개로 설정되었다.

$$( X_{center}, Y_{center}, Width, Height)$$

 

(Figure 9-D) : 최종 Feature를 위한 feature 변환

계층별 Pyramid feature map이 각 헤드를 통해 도출된 feature map을 분류 feature map끼리 이어붙이고, 박스 regression feature map끼리 이어붙이고, 최종적으로 박스 regression feature map과 분류 feature map을 옆으로 이어붙인다.

 

2.2. Code analysis

코드 분석으로는 위에서 언급했듯이, keras에서 제공하는 RetinaNet 예제 코드 https://keras.io/examples/vision/retinanet 로 진행하지만 일부 코드는 본인이 사용하는 데이터셋에 맞게 일부 수정하였다.

 

2.2.1. Anchor 박스

우선 1장에서 다룬 앵커 박스 생성에 대한 코드부터 다룰 것이다. 앵커 박스는 4개의 실수형으로 구성되어있으며, 데이터셋마다 다르지만 여기서는 앵커 박스의 중앙 좌표 x, y, 너비와 높이를 의미한다.

Figure 10. class AnchorBox 코드 : 앵커 박스 생성하는 코드 ( 출처 : keras.io)

 

해당 코드는 클래스이며 클래스명은 AnchorBox이다. 여기에는 6개 멤버 변수와 3개의 멤버 메소드가 있다.

class AnchorBox멤버 변수
① _areas 해당 변수는 각 계층에 대한 앵커 박스의 넓이를 의미한다.
32 * 32 = P3에 대한 앵커 박스 넓이
64 * 64 = P4에 대한 앵커 박스 넓이
128 * 128 = P5에 대한 앵커 박스 넓이
256 * 256 = P6에 대한 앵커 박스 넓이
512 * 512 = P7에 대한 앵커 박스 넓이
② aspect_ratios 앵커 박스의 종횡비를 의미한다.
0.5 = 1:2의 직사각형
1.0 = 1:1의 정사각형
2.0 = 2:1의 직사각형
③ scales ①번에서 각 계층에 대한 앵커 박스의 넓이에 대해 3개의 scale을 주어, 서로 다른 3개의 넓이를 만든다. 1장에서 언급했듯이 (Figure 3)에서 같은 색상의 앵커 박스는 넓이가 동일하다.
2^0 = 1.0 -> ①과 동일한 넓이
2^(1/3 )= 1.259... -> ① * 1.259배의 넓이의 앵커 박스
2^(1/3) = 1.587... -> ① * 1.587배의 넓이의 앵커 박스
④ _num_anchors 한 지점에서 생성되는 좌표의 개수 : 9
len(self.aspect_ratios) * len(self.scales) = 3 * 3 = 9
⑤ _strides 앵커 박스가 생성되는 중앙점을 계산하기 위한 값.
이는 입력 영상에 대해서 샘플링되는 비율을 의미하기도 한다. (Figure 6-B 참고)
2^3 = 8 -> P3에 대한 입력 영상에 비율
2^4 = 16 -> P4에 대한 입력 영상에 비율
2^5 = 32 -> P5에 대한 입력 영상에 비율
2^6 = 64 -> P6에 대한 입력 영상에 비율
2^7 = 128 -> P7에 대한 입력 영상에 비율
⑥ _anchor_dims 멤버 메소드 _compute_dims() 결과값.
각 계층별 생성되는 9개의 앵커 박스를 list로 저장.
자세한 건 멤버 메소드에서 설명하도록 한다.

 

멤버 메소드
① _compute_dims 해당 메소드는 5개의 각 계층별에 생성되는 9개의 앵커 박스를 생성한다.
- 구성 형태는 (1, 1, 9, 2)의 shape을 가지는 5개의 Tensor가 list에 저장되어 있다.
첫 번째 Tensor는 P3에 대한 앵커 박스
두 번째 Tensor는 P4에 대한 앵커 박스
세 번째 Tensor는 P5에 대한 앵커 박스
네 번째 Tensor는 P6에 대한 앵커 박스
다섯 번째 Tensor는 P7에 대한 앵커 박스


Tensor 구성
1. 종횡비 1:2의 1배 앵커 박스의 너비와 높이
2. 종횡비 1:2의 1.259배 앵커 박스의 너비와 높이
3. 종횡비 1:2의 1.587배 앵커 박스의 너비와 높이
4. 종횡비 1:1의 1배 앵커 박스의 너비와 높이
5. 종횡비 1:1의 1.259배 앵커 박스의 너비와 높이
6. 종횡비 1:1의 1.587배 앵커 박스의 너비와 높이
7. 종횡비 2:1의 1배 앵커 박스의 너비와 높이
8. 종횡비 2:1의 1.259배 앵커 박스의 너비와 높이
9. 종횡비 2:1의 1.587배 앵커 박스의 너비와 높이
② _get_anchors argument : feature_height, feature_width, level
해당 메소드는 한 계층에 대한 전체 앵커 박스의 좌표를 생성한다.
입력으로 특정 한 계층의 feature 크기와 해당 계층의 레벨(ex. P3이면 level =3)을 받는다.
여기서 feature_height는 해당 계층에서 세로만큼의 앵커 박스의 수고, feature_width는 가로만큼의 앵커 박스의 수이다.


여기서는 단계별로 설명하겠다.
해당 계층에 해당하는 앵커 박스의 각 중심 좌표(x, y) 찾기
2. ① _compute_dims로 구한 _anchor_dims에서 해당 계층에 맞는 앵커 박스를 선택하여 위에서 구한 각 중심 좌표과 매치시켜 (x, y, width, height) 형태로 구성한다.


예시) 너비과 높이이 2이고, level이 7인 feature map이 (x,y, width, height)로 된 앵커 박스를 36개 생성 시
코드 결과 및 앵커 박스 생성 시각화 (Figure 11 참고)


(아래 숫자는 2,2,7을 넣었을 rx, ry center, dims, anchors, )
rx : [0.5 1.5]
ry : [0.5 1.5]


centers_1 : (X_center, Y_center)
[[[ 64. 64.]
[192. 64.]]


[[ 64. 192.]
[192. 192.]]]


centers_2 :
[[[[ 64. 64.]]
[[192. 64.]]]


[[[ 64. 192.]]
[[192. 192.]]]]


centers_3 : (X_center, Y_center)
[[[[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]
[ 64. 64.]]


[[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]
[192. 64.]]]


[[[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]
[ 64. 192.]]


[[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]
[192. 192.]]]]


dims : (Width, Height)
[[[[ 362.03867 724.07733]
[ 456.14014 912.2803 ]
[ 574.70056 1149.4011 ]
[ 512. 512. ]
[ 645.0796 645.0796 ]
[ 812.7493 812.7493 ]
[ 724.07733 362.03867]
[ 912.2803 456.14014]
[1149.4011 574.70056]]


[[ 362.03867 724.07733]
[ 456.14014 912.2803 ]
[ 574.70056 1149.4011 ]
[ 512. 512. ]
[ 645.0796 645.0796 ]
[ 812.7493 812.7493 ]
[ 724.07733 362.03867]
[ 912.2803 456.14014]
[1149.4011 574.70056]]]


[[[ 362.03867 724.07733]
[ 456.14014 912.2803 ]
[ 574.70056 1149.4011 ]
[ 512. 512. ]
[ 645.0796 645.0796 ]
[ 812.7493 812.7493 ]
[ 724.07733 362.03867]
[ 912.2803 456.14014]
[1149.4011 574.70056]]


[[ 362.03867 724.07733]
[ 456.14014 912.2803 ]
[ 574.70056 1149.4011 ]
[ 512. 512. ]
[ 645.0796 645.0796 ]
[ 812.7493 812.7493 ]
[ 724.07733 362.03867]
[ 912.2803 456.14014]
[1149.4011 574.70056]]]]


anchors : (Xcenter, Ycenter, Width, Height)
[[[[ 64. 64. 362.03867 724.07733]
[ 64. 64. 456.14014 912.2803 ]
[ 64. 64. 574.70056 1149.4011 ]
[ 64. 64. 512. 512. ]
[ 64. 64. 645.0796 645.0796 ]
[ 64. 64. 812.7493 812.7493 ]
[ 64. 64. 724.07733 362.03867]
[ 64. 64. 912.2803 456.14014]
[ 64. 64. 1149.4011 574.70056]]


[[ 192. 64. 362.03867 724.07733]
[ 192. 64. 456.14014 912.2803 ]
[ 192. 64. 574.70056 1149.4011 ]
[ 192. 64. 512. 512. ]
[ 192. 64. 645.0796 645.0796 ]
[ 192. 64. 812.7493 812.7493 ]
[ 192. 64. 724.07733 362.03867]
[ 192. 64. 912.2803 456.14014]
[ 192. 64. 1149.4011 574.70056]]]




[[[ 64. 192. 362.03867 724.07733]
[ 64. 192. 456.14014 912.2803 ]
[ 64. 192. 574.70056 1149.4011 ]
[ 64. 192. 512. 512. ]
[ 64. 192. 645.0796 645.0796 ]
[ 64. 192. 812.7493 812.7493 ]
[ 64. 192. 724.07733 362.03867]
[ 64. 192. 912.2803 456.14014]
[ 64. 192. 1149.4011 574.70056]]


[[ 192. 192. 362.03867 724.07733]
[ 192. 192. 456.14014 912.2803 ]
[ 192. 192. 574.70056 1149.4011 ]
[ 192. 192. 512. 512. ]
[ 192. 192. 645.0796 645.0796 ]
[ 192. 192. 812.7493 812.7493 ]
[ 192. 192. 724.07733 362.03867]
[ 192. 192. 912.2803 456.14014]
[ 192. 192. 1149.4011 574.70056]]]]
③ get_anchors 해당 메소드는 ② _get_anchors 메소드를 이용하여 P3부터 P7까지의 모든 앵커 박스를 리스트에 append한 후에 Tensor로 변환시켜 반환한다.


예시로 입력 영상 크기가 (256, 256)이면 각 계층에서의 앵커 박스 수는 다음과 같고, 반환하는 값의 shape은 (전체 앵커 박스 수, 4)이다.
P3 : 9216
P4 : 2304
P5 : 576
P6 : 144
P7 : 36
전체 앵커 박수 수 : 12276 = 9216 + 2304 + 576 + 144 + 36

 

 

FIgure 11. 높이와 너비가 2인 7계층 feature map에서의 앵커 박스 생성 과정

 

2.2.2. RetinaNet model

위의 (Figure 9)은 Keras에서 제공하는 RetinaNet 예제 코드를 기반으로 제작되었기에, 이와 비교하면서 아래 코드를 분석할 것이다.

(Figure 9)과 같이 크게 4단계로 나누어져 있다.

Figure 12. RetinaNet 코드 : RetinaNet 모델을 구성하는 코드 ( 출처 : keras.io)

www.tensorflow.org/api_docs/python/tf/keras/applications/ResNet50

A. def get_backbone():
(Figure 9-A) 참고
Tensorflow의 Keras에 ImageNet으로 사전 학습(Pre-trained)된 ResNet50을 backbone으로 가져온다. weights = "imagenet" 은 사전 학습된 모델의 weights를 가져온다는 의미이다.[7] 자세한 것은 위의 텐서플로우 홈페이지 링크 참고(tf.keras.applications.ResNet50)


input_shape = [None, None, 3]의 의미는 입력 영상으로 높이와 너비에 대한 제한을 두지 않겠다는 의미이다.
다만, 채널은 3채널만 받아야 한다. 그러한 이유는 사전 학습된 ResNet50이 3채널 컬러 영상에 대해 학습되었기 때문에 채널 수를 맞춰야한다.
본 게시글은 1채널인 x-ray 데이터셋으로 학습해야하지만, 채널 수를 맞추기 위해서 opencv를 통해 3채널로 변경한다. (이에 대해서는 4장 데이터셋 준비에서 다루도록 한다. )


해당 함수의 목적은 사전 학습된 ResNet50에서 Conv 3, 4, 5계층에서 결괏값을 반환하는 것이다.


중간의 "backbone.trainable = trainable_flag"은 전이 학습 (transfer learning)시에 백본의 weights 값을 고정(freeze)여부를 선택하는 항목이다.
본 게시글에서는 백본 weights를 고정(freeze)하고 나머지 계층(layer)를 학습할 예정이다. 이에 대한 내용은 5장에 다룰 예정이다.

 

B. class FeaturePyramid(keras.layers.Layer)
해당 클래스는 (Figure 9-B)를 그대로 코드로 나타낸 것으로, 위의 A. def get_backbone() 함수를 통해 ResNet50을 backbone을 생성하고, 이에 대해서 Conv 3, 4, 5계층의 결괏값을 받아서 Pyramid 3, 4, 5, 6, 7을 생성한다.

 

C. def build_head(output_filters, bias_init):
해당 함수는 헤드를 생성한다. 입력 변수로 output_filters와 bias_init를 받는데, 이들은 각각 헤드 마지막 레이어의 결과 필터 개수와 bias의 초깃값을 의미한다.


(Figure 9-C)를 보다시피 5개의 Pyramid의 각 feature map 분류 헤드와 박스 regression 헤드가 붙어있는데, 각 feature map 크기가 모두 다르므로 입력 크기가 [None, None, 256]으로 설정되어있다.


그리고 stride =1, zero-padding, 256개의 3x3 conv과 ReLu 활성함수가 4번 반복되어, 반복문으로 처리하였고, 마지막으로 분류 헤드와 박스 regression 헤드의 필터 개수가 다르므로 입력 변수로 output_filters를 받는다.


다음 코드인 class RetinaNet를 보면 분류 헤드에는 (9 * 클래스 개수)를 입력받고, 박스 regression 헤드에는 (9 * 4)를 입력으로 받는다.


또한, 분류 헤드에서 bias_init으로 특정값으로 초기화하였는데, 논문[1]에 나와 있다시피 일반적으로 이진 분류에서는 각 클래스에 대해 동일한 확률로 초기화한다고 하였다. Object detection에서는 배경 영역(Background)이 객체를 포함하는 영역(Foreground)보다 우세하기 때문에 학습에 편향이 생긴다. 이를 해결하기 위해 prior를 도입하여, 두 클래스에 대한 확률 모두를 0.01로 초기화하고자 한다. 본 코드에서는 -np.log((1 - 0.01) / 0.01)으로 설정되어있는데, 이는 softmax에 대해서 도출된 값으로 도출 과정은 (Figure 12)와 같다.


박스 regression 헤드에서 초기화는 박스의 네 좌표를 모두 동일한 확률로 설정해도 상관없으므로, “zero”를 초기화하였다.

 

Figure 13. prior_probability 계산식

 

D. class RetinaNet(keras.Model):
해당 클래스는 위의 코드를 모두 아울러 A, B, C를 최종적으로 구성하는 동시에, Figure 9-D 과정을 수행한다.


(필히 Figure 9-D와 비교하며 아래 설명글을 읽기 바란다.)


반복문 for문은 features에 대해 진행하는데, features는 P3부터 P7까지의 feature map을 의미하며, 차례대로 분류 feature는 클래스 개수만큼의 열로 변환(reshape)하고 밑으로 이어붙이고, 박스 좌표 feature는 좌표 수인 4열로 변환하고 이어붙인다. 그리고 최종적으로 concat 함수를 통해 수 feature는 박스 feature 옆에 분류 feature를 이여 최종 예측값(prediction value)를 도출한다.


그 결과 [X_center, Y_center, Width, Height, class_1, class_2, class_3, ...., class_N] 이다.


(Figure 14)는 클래스 갯수(Num_classes)가 2일 때, 입력 영상이 RetinaNet의 모델을 거쳐 최종적으로 도출되는 예측값(prediction value) 형태이며, shape = (1, N, 6)을 가진다.


1은 Batch_size, N은 전체 앵커 박스 수, 6은 박스의 '4'개 좌표 + 클래스 갯수 '2'


각 줄마다 각기 다른 앵커 박스의 4개 좌표와 각 클래스의 score가 포함되어있다.


아래 코드는 class RetinaNet에서 for문부터의 코드를 이해하기 위해 작성한 임의의 코드로, 복붙하여 직접 테스트하고 결과보길 원한다.


"""
데이터가 RetinaNet에서 Anchor 박스 클래스 레이블과 좌표레이블과의 계산을 손쉽게 하기 위해
reshape하는 것을 보여주기 위한 Test Code
여기서는 class가 1
"""



 

Figure 14. 클래스 갯수가 2인 RetinaNet의 최종 예측값 형태

 

 

"""
데이터가 RetinaNet에서 Anchor box 클래스 레이블과 좌표레이블과의 계산을 손쉽게 하기 위해
reshape하는 것을 보여주기 위한 Test Code
"""

# P6 class # (4, 4, 9)
num_class = 1
cls6 = [i for i in range(1, 145)]
cls6 = tf.cast(cls6, dtype = "float32")
cls6 = tf.reshape(cls6, [4, 4, 9])
print(f"cls6\n{cls6}")

# P6 box # (4, 4, 36)
box6 = []
for i in range(1, 145):
    for j in range(1,5):
        box6.append(i + 0.1*j)
box6 = tf.cast(box6, dtype = "float32")
box6 = tf.reshape(box6, [4, 4, 36])
print(f"\nbox6\n{box6}")


# P7 class # (2, 2, 9)
cls7 = [i+144 for i in range(1, 37)]
cls7 = tf.cast(cls7, dtype = "float32")
cls7 = tf.reshape(cls7, [2, 2, 9])
print(f"\ncls7\n{cls7}")

# P7 box # (2, 2, 36)
box7 = []
for i in range(1, 37):
    for j in range(1,5):
        box7.append(i+144 + 0.1*j)
box7 = tf.cast(box7, dtype = "float32")
box7 = tf.reshape(box7, [2, 2, 36])
print(f"\nbox7\n{box7}")

cls_feature = [cls6, cls7]
box_feature = [box6, box7]

cls_outputs = []
box_outputs = []
num_class = 1

for p in range(2):
    h = tf.shape(cls_feature[p])[1]
    w = tf.shape(cls_feature[p])[2]
    box_outputs.append(tf.reshape(box_feature[p], [1, -1, 4]))
    cls_outputs.append(tf.reshape(cls_feature[p], [1, -1, num_class]))

box_outputs = tf.concat(box_outputs, axis=1)
cls_outputs = tf.concat(cls_outputs, axis=1)
output = tf.concat([box_outputs, cls_outputs], axis=-1)
print(f"\noutput\n{output}")

 

 


코드를 이해하기 위해서는 (Figure 9)에서 데이터가 어떻게 변하는가에 대해 명확하게 파악해야만 한다.

 

꼭 (Figure 9)과 비교해가며 코드를 읽어보길 바란다.

 

 

3장에서는 object detection을 위한 데이터셋 준비 방법을 설명하도록 하겠다. 이를 위해서 (Figure 14)의 형태롤 머릿속에 남겨두길 바란다.



Reference

[1] "Focal Loss for Dense Object Detection", "Tsung-Yi Lin and Priya Goyal and Ross Girshick and Kaiming He and Piotr Dollár", 2018, arXiv, cs.CV

[2] "Deep Residual Learning for Image Recognition", "Kaiming He and Xiangyu Zhang and Shaoqing Ren and Jian Sun", 2015, arXiv, cs.CV

[3] "Feature Pyramid Networks for Object Detection", "Tsung-Yi Lin and Piotr Dollár and Ross Girshick and Kaiming He and Bharath Hariharan and Serge Belongie", 2017, arXiv, cs.CV

[4] “Object Detection with RetinaNet”, “Srihari Humbarwadi”, 2020.05.17., keras, “https://keras.io/examples/vision/retinanet/

[5] Resnet50, http://ethereon.github.io/netscope/#/gist/db945b393d40bfa26006

[6] “ResNet50 구현해보기”, “eremo2002”, 2019.01.23., https://eremo2002.tistory.com/76

[7] "tf.keras.applications.ResNet50", https://www.tensorflow.org/api_docs/python/tf/keras/applications/ResNet50

 

'1. 모델 분석 > Object Detection' 카테고리의 다른 글

[RetinaNet] 5. Training the Model and Result  (0) 2021.05.31
[RetinaNet] 4. Loss Function  (0) 2021.05.31
[RetinaNet] 3. Prepare the Dataset  (0) 2021.05.31
[RetinaNet] 1. Anchor Box  (2) 2021.05.31