1. 모델 분석/Object Detection

[RetinaNet] 3. Prepare the Dataset

M_AI 2021. 5. 31. 20:10

M_AI 입니다. RetinaNet의 모델 및 코드 설명과 이전에 다룬 앵커 박스 코드에 이어서, 이번 글에는 데이터셋 준비하는 방법을 알려드리도록 하겠습니다!

여기서는 추가적인 앵커 박스의 사용법과 데이터셋 준비하는 코드를 분석 및 설명하도록 하겠습니다!

이전글 : [RetinaNet] 2. Explaining overall RetinaNet model and Code analysis

https://yhu0409.tistory.com/3

 

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

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

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

⑤ 게시글에서는 반말


3. Prepare the Dataset

3.1. Explaining the Dataset

3.1.1. Dataset

 

본 게시글에서 사용될 본 데이터는 항상 언급했다시피, RSNA에서 kaggle competition을 위해 오픈한 폐렴 X-ray 데이터셋이다.

해당 데이터셋의 용량은 총 3.68GB이며, 파일 구성으로는 (1024, 1024, 1) 크기의 26,684개의 훈련용 데이터셋과 3,000개의 테스트 데이터셋, 그리고 환자 ID, 훈련용 데이터에 대한 폐렴 유무 레이블과 폐렴 위치정보가 저장된 csv파일이 있다.

이 csv 파일은 (Figure 15)와 같다.

Figure 15. RSNA Pneumonia dataset CSV file (출처: https://www.kaggle.com/c/rsna-pneumonia-detection-challenge )

 

해당 데이터셋의 파일 형식 DICOM으로, DIgital Image Communication in Medicine의 약어이다. 이는 이름에서 알 수 있다시피 의료 분야에서 사용되는 파일 형식으로, 의료 영상에 대한 표준 규격을 제공하며, 용이한 데이터의 전송을 위한 규격이다. 해당 데이터를 열어보기 위해 “pydicom”이라는 라이브러리가 필요하다.

 

훈련용 데이터셋에서의 분류로는 불투명한 폐렴 영상(Lung Opacity), 투명한 폐렴 영상(No Lung Opacity/ Not Normal), 정상인(Normal)으로 3가지로 나뉘지만, 본 게시글에서는 object detection으로, 불투명한 폐렴 영상만 사용할 것이다. 여기서 테스트 데이터에는 별도의 레이블이 없어 학습과 테스트 시에서 제외하며, 26,684개 훈련용 데이터에서 폐렴 위치정보가 있는 9,555개의 데이터만 사용한다. 클래스는 1개만 사용하므로 Num_classes = 1 이다. (Figure 16)

하지만 여기서는 한 환자에 대한 폐렴 위치가 2개 이상인 경우도 있으므로, 정리를 위해서 딕셔너리(dictionary)를 활용할 것이다.

그러므로 불투명한 폐렴 영상(Lung Opacity)에서 전체 환자 수는 6,012명이다. 본 게시글에서는 (Train : Valid) 비율을 (0.8 : 0.2)로 진행할 것이다. (원래는 Test까지 고려해야하지만 데이터가 적은 관계로 valid로 대체한다.)

 

Figure 16. Classes distribution in the training dataset

 

 

csv에서 저장된 폐렴 위치정보 형태는 박스의 좌상단 좌표 x, y와 높이와 너비이다. 이전 장에서 언급한 앵커 박스 좌표는 박스의 중앙 좌표 x, y와 높이와 너비로 구성되어있는데, 이와 맞추어주기 위해 폐렴 위치정보 좌표를 좌상단 좌표 x, y에서 중앙 좌표 x, y로 수정해야만 한다. 해당 수식은 아래와 같다.

 

3.1.2. Data Augmentation and Resizing

훈련 데이터가 10,000개도 안 되는 적은 양이기에, 이를 해결하기 위해 데이터를 증강한다.

방식으로는 “albumentations” 라이브러리를 사용하여 (Figure 16)과 같이 좌우 반전, CLAHE[8]로 처리하여 데이터를 증강한다.

(CLAHE에 대해 잘 모르는 사람은 이 블로그 링크를 통해 확인하길 바란다.)

https://blog.naver.com/samsjang/220543360864

 

[25편] CLAHE

이미지 프로세싱 & 컴퓨터 비전 OpenCV-Python 강좌 25편 : CLAHE 필요환경: 파이썬 3.6.x, ...

blog.naver.com

또한 빠른 학습을 위해 영상 데이터의 높이와 너비를 절반인 512로 감소시킨다.

하지만 지난 2장에서 2.2.2. RetinaNet model 코드 분석에서 def get_backbone():을 설명하면서 사전 학습된 모델을 사용하기에 입력 채널을 3채널 영상으로 맞춰야 한다고 하였다.

최종적으로 입력 데이터의 형태는 (1024, 1024, 1)에서 (512, 512, 3)으로 변경해야 한다.

또한, 입력 영상이 감소하는 만큼 GT 박스의 좌표도 같은 비율로 감소해야만 한다.

 

Figure 16. Data augmentation by flip ( 출처 :  https://www.kaggle.com/c/rsna-pneumonia-detection-challenge )

 

 

3.1.3. Label Encoding

이 3장에서 가장 중요한 부분으로, 이는 1, 2장에서 설명한 입력 영상에 대한 모든 앵커 박스에 레이블링하는 과정이다.

2장 (Figure 14. 클래스 개수가 2인 RetinaNet의 최종 예측값 형태)를 참고하면, 입력 영상에 대한 RetinaNet 최종 예측값(prediction value)의 형태가 (1, N, 박스의 4개 좌표 + 클래스 개수)라고 하였다. ( N은 앵커 박스 총 개수 )

딥러닝에서 실제값(true)과 예측값(prediction)의 loss를 구하기 위해서는 둘 다 같은 형태(차원)를 지녀야 하기에, 각 영상 데이터마다 모든 앵커 박스의 좌표와 각 앵커 박스 마다 클래스를 표시해주어야한다. 이러한 과정을 Label Encoding이라 한다.

 

즉, 하나의 영상 데이터에 대해서 (1, N, 박스의 4개 좌표 + 클래스 개수) 차원의 레이블이 있어야 한다는 것이다. 여기서는 (1, 49104, 5)이다.

 

해당 과정은 앵커 박스에 대한 설명이 다시 나오는데, 1장에서 상세하게 설명하지 않은 이유가 해당 장에서 설명하기 위함이다.

1, 2장에서는 앵커 박스의 간단한 역할과 생성하는 방법만 설명했지만, 여기서는 어떻게 사용하는 지에 관해 설명하도록 하겠다.

우선 Ground Truth Box는 실제 object이 있는 박스 좌표를 의미하며, 여기서는 줄여서 GT 박스라고 하겠다.

 

그리고 IoU는 Intersection over Union의 약어로 정의로는 “교집합 영역 넓이 / 합집합 영역 넓이”이다. 아래 (Figure 17)을 보도록 하자.

 

Figure 17. IoU between the ground truth box and one anchor box

 

빨간 박스는 위에서 언급한 실제 object가 위치한 박스이며, 앵커 박스는 입력 영상에 대해 특정 영역을 나타내는 박스이다.

여기서 IoU는 두 박스가 얼마나 겹치나를 의미하며 값이 작을수록 겹치는 정도가 적으므로, 해당 앵커 박스는 객체를 적게 포함하고 배경을 많이 포함하는 영역이라고 할 수 있다.

 

RetinaNet에서는

  • IoU의 경계값은 0.5와 0.4로 설정한다.
  • IoU가 0.5 이상이면 해당 앵커 박스에는 객체가 있다고 판단한다.
  • IoU가 0.4 미만이면 해당 앵커 박스는 객체가 없는 배경이라고 판단한다.

 

 

그렇다면 IoU가 0.4 이상이고 0.5 미만이면 무엇일까?

 

위의 설명대로면, 0.4 이상이고 0.5 미만인 IoU를 가지는 앵커 박스는 객체는 가지고 있지만, 배경도 어느 정도 가지고 있는 어중간한 영역이라고 해석 가능하다.

 

그렇다면 이러한 영역은 어떻게 처리해야 할까?

 

그에 대한 답은 매우 간단하다

 

"무시가 답!"

 

사소한 영역까지 학습에 포함되면 객체를 정확히 탐지하는 데에 방해되므로 그저 무시한다.

 

이때, 각 박스에 대한 레이블링은 다음과 같다.

 

Positive 박스에는 해당 영역에 있는 객체의 클래스 정수 숫자(0, 1, 2, ... num classes -1)로 레이블링,

Negative 박스에는 –1,

Ignorance 박스에는 –2로 레이블링한다.

 

즉, 깔끔하게 정리하면 다음과 같다.

 

IoU >= 0.5인 앵커 박스 Positive anchor box 0 ~ (Number of class -1)
0.4 <= IoU < 0.5인 앵커 박스 Ignorance anchor box -2
IoU < 0.4인 앵커 박스 Negaitive anchor box -1

 

이렇게 각 앵커 박스마다 4개의 좌표를 레이블링하고, 입력 영상 데이터에 존재하는 M개의 GT 박스와 IoU를 계산하여 클래스 정수 숫자를 레이블링하면 (1, N, 5)의 형태가 완성된다.

(여기서 5는 ‘박스의 4개 좌표 + 클래스 정수 숫자 1개’이다.)

 

여기서 잠깐!


위에서는 (1, N, 박스의 4개 좌표 + 클래스 개수)의 레이블이 있어야 한다고 했는데, 왜 (1, N, 5) 형태가 나올까?

 

(1, N, 박스의 4개 좌표 + 클래스 개수)에서 ‘클래스 개수’는 사실 벡터로 표현되기에 개수가 많은 것이다.

 

그렇다면 label encoding 과정에서 (1, N, 5) 말고 바로 (1, N, 박스의 4개 좌표 + 클래스 개수)로 왜 만들지 않는가?

 

라고 의문이 들텐데, 이에 대한 의문은 4장인 Loss function에서 다루도록 하겠다. 미리 짧게 말하자면, loss function 과정에서 앵커 박스의 레이블이 one-hot 인코딩 과정을 거쳐 벡터가 되어 loss가 계산된다. (Negative와 Ignorance의 값인 –1과 –2는 벡터가 되면 0이 된다.)

자세한 내용은 다음 ReitinaNet 4장에서 다루도록 하겠다.

 

즉, 여기서의 최종 레이블 형태는 (1, N, 5) 이다.

if Negative, class number = -1

elif Ignorance, class number = -2

elif Positive, class number = 0 ~ (number of classes -1)

 

 

다음은 박스의 4개 좌표를 어떻게 변환해야 하는지 설명한다.

해당 좌표는 그대로 사용하는 것이 아니라, 앵커 박스와 GT 박스 간의 ‘offset’을 계산하여 사용한다.

 

그렇다면 "offset"이 무엇인가?

 

 

offset은 단순하게 말하면 두 박스 간의 거리차를 의미하는데 연산 식은 다음과 같다. 해당 식은 Faster R-CNN[9]이나 SSD[10]와 같은 논문에도 언급된다.

하지만 Faster R-CNN, SSD, RetinaNet 등 실제 object detection 코드를 읽어보면 offset 계산과정이 다르다는 것을 알 수 있다. 실제 코드에서는 분산(variance)가 등장하여 다음과 나누어진다. 분산은 x, y, w, h에 대해서 [0.1, 0.1, 0.2, 0.2]로 설정된다.

이러한 과정은 논문과 코드에서 설명이 없어 명확히 이해하기는 어려웠으나, 본인은 일종의 표준화(standardization) 과정으로 이해했다. 이에 대한 설명은 다음 블로그에서 괜찮은 설명이 되있으므로 참고하길 바란다.[11]

https://leimao.github.io/blog/Bounding-Box-Encoding-Decoding/

 

Bounding Box Encoding and Decoding in Object Detection

Hello Underworld.

leimao.github.io

 

3.2. Code Analysis

3.2.1. Compute IoU

(Figure 18)은 두 박스 간의 IoU를 계산하는 함수이며, 이를 위해서 앵커 박스의 좌표 형태인

(X_center, Y_center, Width, Height)에서 (X_min, Y_min, X_max, Y_max)로 변경해 주어야 한다. 이를 위해 변경하는 함수 또한 필요하다.

 

Figure 18. 좌표 변환 함수 코드와 두 박스 그룹 간의 IoU 계산 함수 코드  ( 출처 : keras.io)

 

def xywh_to_corners(boxes):
해당 함수는 (x, y, w, h) 형태의 박스 좌표를 (xmin, ymin, xmax, ymax) 형태의 박스 좌표로 변경하도록 하는 함수이다.
(X_min, Y_min, X_max, Y_max) = (X_center - Width/2), (Y_center - Height/2), (X_center + Width/2), (Y_center + Height/2)
def comput_iou(boxes1, boxes2)
해당 함수는 두 박스 그룹 간의 IoU를 계산하는 함수이다.
boxes1은 앵커 박스 그룹이, boxes2는 GT 박스 그룹이 들어간다.
앵커 박스 수가 N개, GT 박스 수가 M개라고 한다면, (N, M) IoU 행렬이 반환된다.
① 우선, 용이한 계산을 위해 각각 def xywh_to_corners(boxes)을 사용하여 형태를 변경해 준다.
② lu : 두 박스의 겹치는 부분에서 좌측 상단 좌표
③ rd : 두 박스의 겹치는 부분에서 우측 하단 좌표
④ intersection : 두 박스의 겹치는 부분의 너비와 높이
⑤ intersection_area : 두 박스의 겹치는 부분의 곱
⑥ boxes1_area : 앵커 박스 그룹의 넓이 계산
⑦ boxes2_area : GT 박스 그룹의 넓이 계산
⑧ union_area : 두 박스 간의 합집합 넓이 계산
⑨ return : 두 박스 그룹 간의 각각의 IoU가 계산된 (N, M) 행렬

 

 

3.2.2. LabelEncoder

이 함수는 이 3장에서 매우 중요한 함수로써, 위에서 언급한 내용의 모든 것들을 포괄한다고 할 수 있다. 해당 클래스 코드를 확인할 수 있도록 아래에 임의의 코드를 작성하였으니, 개인 PC에서 python으로 실행해보고 결과와 코드를 비교하길 바란다.

Figure 19. Label Encoder 함수 ( 출처 :  keras.io)

 

class LabelEncoder 멤버 변수
① _anchor_box GT 박스와 앵커 박스와의 IoU 계산을 위해서 생성한 앵커 박스들을 저장하는 함수
② _box_variance GT 박스와 앵커 박스의 offset 계산을 위한 분산
각각 x, y, w, h에 대한 분산을 의미한다.

 

class LabelEncoder 멤버 메소드
① _compute_box_target 앵커 박스 그룹(N)과 GT 박스 그룹(M) 간의 IoU를 구하여 (N, M) 행렬에서의 각 위치의 앵커 박스마다의 최대 IoU를 찾아, IoU가 0.5 이상이면 Positive, IoU가 0.4 미만이면 Negative 앵커 박스, 그리고 그사이이면 Ignorance 앵커 박스를 찾는다.
반환으로는 IoU 최댓값을 가지는 GT 박스의 인덱스와 positive 앵커 박스, ignorance 앵커 박스의 mask로 반환한다.
② _compute_box_target GT 박스와 앵커 박스 간의 offset을 계산하는 메소드.
3.1.3절 마지막 수식 참고
③ encode 박스 좌표 레이블을 위의 메소드 위의 메소드를 모두 아울러서 최종 박스 좌표 레이블로 변환한다.
클래스 레이블을 positive면 해당 객체 클래스에 해당하는 0~(num_classes-1) 사이 숫자를 레이블하고, negative면 –1, ignorance면 –2로 레이블 한다.


이후 [x, y, w, h, class]에 맞게, 박스 좌표 레이블과 클래스 레이블을 옆으로 붙여 하나의 레이블로 만들어 반환한다.

 

LabelEncoder 테스트 코드

필히, 결괏값과 코드를 비교해보길 바란다.

## LabelEncoder Test
anchor = [[65, 66, 70, 50], [120, 125, 30, 50], [80, 145, 30, 50], [63, 78, 50, 64], [85, 155, 25, 45], [50,30, 70, 40]]
true = [[65, 80, 50, 64], [90, 150, 20, 40]]
cls_ids = [0, 1]
anchor, true = tf.cast(anchor, dtype = "float32"), tf.cast(true, dtype = "float32")
cls_ids = tf.cast(cls_ids, dtype=tf.float32)
iou_matrix = compute_iou(anchor, true)
print(f"IoU\n{iou_matrix}\n")

match_iou, ignore_iou = 0.5, 0.4
#iou_matrix = [[0.644, 0.30], [0.14, 0.450], [0.54, 0.30], [0.20, 0.60],[0.2, 0.30], [0.24, 0.70]]
max_iou = tf.reduce_max(iou_matrix, axis = 1)
print(f"Max IoU\n{max_iou}\n")

#### max IoU index of Anchor box
matched_gt_idx = tf.argmax(iou_matrix, axis=1)
print(f"matched_gt_idx\n{matched_gt_idx}\n")

positive_mask = tf.greater_equal(max_iou, match_iou)
negative_mask = tf.less(max_iou, ignore_iou)
ignore_mask = tf.cast(tf.logical_not(tf.logical_or(positive_mask, negative_mask)), dtype = tf.float32)
#print(ignore_mask)
positive_mask = tf.cast(positive_mask, dtype=tf.float32)
negative_mask = tf.cast(negative_mask, dtype=tf.float32)

print(f"positive_mask\n{positive_mask}\n")
print(f"negative_mask\n{negative_mask}\n")
print(f"ignore_mask\n{ignore_mask}\n")

#### GT boxes with Anchor box having max IoU
matched_gt_boxes = tf.gather(true, matched_gt_idx)
print(f"matched_gt_boxes\n{matched_gt_boxes}\n")

matched_gt_cls_ids = tf.gather(cls_ids, matched_gt_idx)
print(f"matched_gt_cls_ids\n{matched_gt_cls_ids}\n")

#### To find object class label not relation with ignorance and negative anchors
cls_target = tf.where(tf.not_equal(positive_mask, 1.0), -1.0, matched_gt_cls_ids)
print(f"cls_target\n{cls_target}\n")

#### To find ignorance anchors
cls_target = tf.where(tf.equal(ignore_mask, 1.0), -2.0, cls_target)
print(f"cls_target\n{cls_target}\n")

cls_target = tf.expand_dims(cls_target, axis=-1)
print(f"Final_cls_label\n{cls_target}\n")

def compute_box_target(anchor_boxes, matched_gt_boxes):
    box_variance = tf.convert_to_tensor([0.1, 0.1, 0.2, 0.2], dtype=tf.float32)
    box_variance
    """Transforms the ground truth boxes into targets for training"""
    box_target = tf.concat(
        [
        (matched_gt_boxes[:, :2] - anchor_boxes[:, :2]) / anchor_boxes[:, 2:],
        tf.math.log(matched_gt_boxes[:, 2:] / anchor_boxes[:, 2:]),
        ],
        axis=-1,
    )
    box_target = box_target / box_variance
    return box_target
box_target = compute_box_target(anchor, matched_gt_boxes)
print(f"Final_box_label\n{box_target}\n")

label = tf.concat([box_target, cls_target], axis=-1)

label = tf.reshape(label, [1,tf.shape(label)[0], tf.shape(label)[1]])
print(f"Final_label\n{label}\n")

 

 

3.2.3. Organize patients data using Dictionary and Resizing

3.1.1절에서 csv 파일을 설명하면서 한 환자에 대한 폐렴 위치가 2개 이상인 경우도 있다고 언급하였다. 이를 정리하기 위해 python 자료형 중 하나의 ‘dictionary’를 사용한다.

( 해당 함수는 keras에 없는 코드로, 본 게시글에서 사용되는 데이터셋에 따라 본인이 작성한 코드이다. )

 

정리하는 형태는 {환자id : [ [박스 좌표1], [박스 좌표2],... ] } 형태로 value에 list 자료형으로 두어, 이 list에 하나의 박스 좌표를 가지는 list를 append하여 이중 list 형식으로 진행한다. 반복문을 이용하여 딕셔너리 자료형에 이미 동일한 환자 Id가 존재하면 기존에 value에 append로 추가하고, 새로운 환자 id면 이중 list로 박스 좌표를 추가한다.

 

그리고 동시에 영상 데이터와 박스 좌표를 3.1.2절에서 언급한 대로 (1024, 1024, 1)에서 (512, 512, 3)로 변환해준다.

 

(Figure 20)에서 첫 번째 박스에 있는 ratio는 원본 영상 크기에서 리사이징된 영상 크기에 대한 비율이며 숫자로는 1024/512 = 2이다.

 

반환값으로는 리사이징된 영상들이 있는 list와 환자 id와 폐렴 위치정보 좌표가 있는 dictionary를 반환한다. 이때, 이 둘을 하나의 list로 묶여서 반환한다.

해당 코드는 아래에 설명할 class에서 사용된다.

 

Figure 20. make_dictionary 함수 코드( 출처 : 본인 )

 

 

3.2.4. Preprocessor

(Figure 21) 코드로 해당 코드는 RetinaNet 훈련과 테스트에 사용될 데이터셋을 생성하기 위한 코드이다. (사실 명칭은 ‘Generator_dataset’이 더 맞는 표현이지만, 코딩 초기에 위의 make_dictionary 코드에서 리사이즈와 섞으면서 정해진 거라 수정이 불가피했다. 해당 클래스는 keras에 없는 코드로, 본 게시글에서 사용되는 데이터셋에 따라 본인이 작성한 코드이다.)

해당 클래스는 2개의 멤버 변수와 3개의 멤버 메소드가 존재한다.

 

FIgure 21. Preprocessor 클래스 코드 ( 출처 : 본인 )

 

class Preprocessor 멤버 변수
① num_train 훈련 데이터 개수. 여기서는 4,809개
② total_dataset def make_dictionary 으로 생성한 전처리된 데이터셋이다.

 

class Preprocessor 멤버 메소드
① train_preprocessor 6,012개중 4,809번째까지 훈련 데이터셋으로 분리한다.
또한, 데이터 augmentation을 위해 albumentations 라이브러리를 통하여 좌우 반전과 CLAHE를 하였다. 이후 영상 데이터에 대해서는 정규화를 위해 255로 나누어준다.
이때, 각 영상 데이터에 대해 label encoding을 하기 전에, 박스 좌상단 좌표 x, y에서 박스 중앙 좌표 x, y로 변환을 해준다. 이후에 LabelEncoder 클래스를 이용하여 label encoding을 진행한다.
또한, return을 사용하여 반환하면 메모리 부족 현상(Out-Of-Memory)가 발생할 수도 있으므로 이를 위해서 제너레이터[12]를 사용하였다. 이는 선택 사항(option)이 아닌 필수이며 제너레이터에 익숙치 않은 사람들은 익숙해지길 바란다. (제너레이터에 대한 설명이 있는 사이트는 해당 표 아래에 링크를 두겠다.)
② valid_preprocessor 이는 4,810번째부터 마지막 데이터까지 valid 데이터셋으로 분리하였으며, 이는 모델 validation loss 측정을 위한 데이터셋이므로 별도의 agumentation을 진행하지 않는다. 그 이외에는 ① train_preprocessor과 같다.
③ test_preprocessor 이는 valid 데이터셋을 훈련이 다 된 RetinaNet에 대한 테스트를 위해 별도로 만든 함수로, ② valid_preprocessor는 다르게 255로 나눠주는 정규화와 label encoding을 진행하지 않고, 원본 영상 데이터와 폐렴 위치정보만 yield한다.

 

 

3.2.5. Generate Dataset

해당 함수는 텐서플로우에 내장된 tf.data.Dataset.from_generator를 이용하여 데이터셋을 형성한다. tf.data.Dataset.from_generator는 제너레이터 함수(return이 아닌 yield를 사용하는 함수)를 통해 데이터셋을 생성하며, 이는 OOM을 방지하기 위해서이다. 그렇기에 데이터셋 생성하는 속도가 매우 빠르다. 이에 대한 설명은 텐서플로우 홈페이지[13]과 이를 잘 설명한 블로그[14]를 참고하길 바란다.

 

[13] 텐서플로우 홈페이지

https://www.tensorflow.org/api_docs/python/tf/data/Dataset

 

tf.data.Dataset  |  TensorFlow Core v2.5.0

Represents a potentially large set of elements.

www.tensorflow.org

 

[14] tf.data.Dataset.from_generator 잘 설명한 블로그

https://jins-sw.tistory.com/14

 

대용량 훈련 데이터 처리 - Generator로 TF Dataset 만들기

1. 너무 큰 데이터 tf.data.Dataset는 Tensorflow의 훈련 데이터를 다룰 때 참 편리합니다. Padding, Batch, Shuffle, Map 등을 다 지원해주니까요. 일단 데이터를 Dataset으로 변환만 시키면 그다음부터는 아주..

jins-sw.tistory.com

 

FIgure 22. Dataset generator 코드  ( 출처 : 본인 )

 


이번 장에서는 부족했던 앵커 박스의 추가 설명과

데이터를 RetinaNet의 최종 예측값과 같은 형태(차원)의 레이블을 생성하는 방법을 코드와 함께 설명하였다.

 


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

[8] "25편 CLAHE", "옥수별", 2015.11.18 , https://blog.naver.com/samsjang/220543360864

[9] "Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks", "Shaoqing Ren, Kaiming He, Ross Girshick, Jian Sun", 2016, arXiv, cs.CV

[10] "SSD: Single Shot MultiBox Detector", "Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, Scott Reed, Cheng-Yang Fu, Alexander C. Berg", 2015, arXiv, cs.CV

[11] "Bounding Box Encoding and Decoding in Object Detection", "Lei Mao", https://leimao.github.io/blog/Bounding-Box-Encoding-Decoding/

[12] "40.1 제너레이터와 yield 알아보기", 코딩 도장, https://dojang.io/mod/page/view.php?id=2412

[13] "tf.data.Dataset", Tensorflow, https://www.tensorflow.org/api_docs/python/tf/data/Dataset

[14] "대용량 훈련 데이터 처리 - Generator로 TF Dataset 만들기", "새옹지인", https://jins-sw.tistory.com/14