대회 소개
Global Wheat Detection
Can you help identify wheat heads using image analysis?
You are attempting to predict bounding boxes around each wheat head in images that have them. If there are no wheat heads, you must predict no bounding boxes.
캐글 주소 : https://www.kaggle.com/c/global-wheat-detection/overview
데이터셋에 대한 논문 : https://arxiv.org/abs/2005.02162
패키지 불러오기
1 |
|
하이퍼파라미터 설정
1 |
|
이미지 확인하기
1 |
|
1 |
|
- 이미지의 크기가 (1024,1024)로 매우 크다.
int32
,float32
로 이미지 데이터 타입을 바꿔서 메모리 소모량을 최소화하자.- float32 보다 float16으로 하면 안되는가
- 모델 학습 시
gradient update
할 때, 소수점 아래 미세한 차이가 결과적으로 큰 차이를 가져온다. float16
은 소수점 4번째까지만 저장하게 되고, 그 아래의 값들은 모두 버리게 되어 정보가 손실된다. 모델 성능에까지 영향을 주게 된다.- 따라서 이미지의 데이터 타입은 보통
float32
으로 설정해준다.
- 모델 학습 시
1 |
|
1 |
|
데이터 전처리
Detection
대회에서 항상 기억해야할 전처리- 데이터셋 만들기(불러오기)
- Data Loader만들기
데이터셋 만들기(불러오기)
train
데이터프레임을 보면,bbox
의 칼럼값이str
으로 들어가 있다.- 각 값을
x, y, w, h
칼럼에 각각 넣어주는 처리를 해주자. - 아래 코드에서
np.stack()
함수를 이용하면 쉽게 데이터 프레임의 칼럼으로 집어 넣을 수 있다. dtypes
을 찍어봐서x, y, w, h
값들이 숫자형태로 들어갔는지 확인하자.- Detection 문제에서 사용할 수 있는 모델(
efficientdet
,fastrcnn
등)에 따라 bounding box의 전처리가 조금씩 달라질 수 있다. - 지금은
fastrcnn
모델을 사용할 것이므로 이에 맞는 전처리를 해줘야 한다.
- Detection 문제에서 사용할 수 있는 모델(
1 |
|
image_id | width | height | bbox | source | x | y | w | h | |
---|---|---|---|---|---|---|---|---|---|
0 | b6ab77fd7 | 1024 | 1024 | [834.0, 222.0, 56.0, 36.0] | usask_1 | 834.0 | 222.0 | 56.0 | 36.0 |
1 | b6ab77fd7 | 1024 | 1024 | [226.0, 548.0, 130.0, 58.0] | usask_1 | 226.0 | 548.0 | 130.0 | 58.0 |
2 | b6ab77fd7 | 1024 | 1024 | [377.0, 504.0, 74.0, 160.0] | usask_1 | 377.0 | 504.0 | 74.0 | 160.0 |
3 | b6ab77fd7 | 1024 | 1024 | [834.0, 95.0, 109.0, 107.0] | usask_1 | 834.0 | 95.0 | 109.0 | 107.0 |
4 | b6ab77fd7 | 1024 | 1024 | [26.0, 144.0, 124.0, 117.0] | usask_1 | 26.0 | 144.0 | 124.0 | 117.0 |
1 |
|
1 |
|
데이터셋 살펴보기
1 |
|
1 |
|
- train셋에서 총 행의 개수가 147793이지만, unique한 개수는 3373개이다. 즉, 한 이미지에 여러 bounding box 값들이 있다는 의미이다.
- Detection 모델에 데이터를 넣어줄 때 관례적으로 bounding box의
[xmin,ymin, xmax,ymax]
입력받는다.(왼쪽 위, 오른쪽 아래 점) - 현재 train 데이터프레임의
bbox
칼럼에서는[xmin, ymin, width, height]
값으로 들어가 있으므로 좌표 역시 전처리 해줘야 한다. 모델 예측한 ouput값은[xmin,ymin, xmax,ymax]
로 나오므로, 역시 제출 시 처리를 해줘야 한다는 것을 까먹지 말자.
이미지에 Bounding Box 그려보기
1. 이미지에 하나의 bounding box 그려보기
1 |
|
1 |
|
2. 한 이미지에 해당하는 모든 bounding box 그려보기
1 |
|
교차 검증을 위해 Train, Valid 데이터셋 나누기
- 현재 이미지마다 바운딩박스의 개수가 모두 다르다.
- 먼저 바운딩박스 개수에 대한 정보를 추가하자. 또한, 현재 데이터에는
source
칼럼에 벼 종류에 대한 정보가 있다. 참고로 벼 종류의 유니크한 개수는 7이다. 이러한 벼 종류 역시 골고루 나눠지게 해야 한다. - 바운딩박스의 개수 및 벼 종류에 따라 구간을 나눠서 카테고리를 나눠보자.
- 예를 들어 0-15개의 바운딩 박스를 갖는 이미지는 a 클래스, 16-30개의 바운딩박스를 갖는 이미지는 b 클래스 …
1. image_id 데이터 가져오기
1 |
|
image_id | |
---|---|
0 | b6ab77fd7 |
1 | b6ab77fd7 |
2 | b6ab77fd7 |
3 | b6ab77fd7 |
4 | b6ab77fd7 |
... | ... |
147788 | 5e0747034 |
147789 | 5e0747034 |
147790 | 5e0747034 |
147791 | 5e0747034 |
147792 | 5e0747034 |
147793 rows × 1 columns
2. image_id 별로 bounding box 개수 count하기
1 |
|
bbox_count | |
---|---|
image_id | |
00333207f | 55 |
005b0d8bb | 20 |
006a994f7 | 25 |
00764ad5d | 41 |
00b5fefed | 25 |
... | ... |
ffb445410 | 57 |
ffbf75e5b | 52 |
ffbfe7cc0 | 34 |
ffc870198 | 41 |
ffdf83e42 | 39 |
3373 rows × 1 columns
3. source 정보 추가하기
1 |
|
1 |
|
bbox_count | source | |
---|---|---|
image_id | ||
00333207f | 55 | arvalis_1 |
005b0d8bb | 20 | usask_1 |
006a994f7 | 25 | inrae_1 |
00764ad5d | 41 | inrae_1 |
00b5fefed | 25 | arvalis_3 |
... | ... | ... |
ffb445410 | 57 | rres_1 |
ffbf75e5b | 52 | arvalis_1 |
ffbfe7cc0 | 34 | arvalis_1 |
ffc870198 | 41 | usask_1 |
ffdf83e42 | 39 | arvalis_1 |
3373 rows × 2 columns
4. bbox_count, source 정보를 합친 stratify_group 칼럼 만들기
- bounding box의 개수의 범위 정보와 source 정보가 담겨져 있는 stratify_group 칼럼 생성한다.
- stratify_group은 source 종류와 바운딩박스 개수를 15로 나누었을 때의 나머지에 대한 정보가 들어간다.
1 |
|
bbox_count | source | stratify_group | |
---|---|---|---|
image_id | |||
00333207f | 55 | arvalis_1 | arvalis_1_3 |
005b0d8bb | 20 | usask_1 | usask_1_1 |
006a994f7 | 25 | inrae_1 | inrae_1_1 |
00764ad5d | 41 | inrae_1 | inrae_1_2 |
00b5fefed | 25 | arvalis_3 | arvalis_3_1 |
... | ... | ... | ... |
ffb445410 | 57 | rres_1 | rres_1_3 |
ffbf75e5b | 52 | arvalis_1 | arvalis_1_3 |
ffbfe7cc0 | 34 | arvalis_1 | arvalis_1_2 |
ffc870198 | 41 | usask_1 | usask_1_2 |
ffdf83e42 | 39 | arvalis_1 | arvalis_1_2 |
3373 rows × 3 columns
5. stratify_group의 개수 파악하기
1 |
|
1 |
|
6. fold 칼럼을 추가하여 데이터 나누기
- 먼저 fold의 기본값을 0으로 설정하고, StratifiedKFold을 이용하여
stratify_group
기준으로 0~4의 값으로 나눠준다.
1 |
|
bbox_count | source | stratify_group | fold | |
---|---|---|---|---|
image_id | ||||
00333207f | 55 | arvalis_1 | arvalis_1_3 | 0 |
005b0d8bb | 20 | usask_1 | usask_1_1 | 0 |
006a994f7 | 25 | inrae_1 | inrae_1_1 | 0 |
00764ad5d | 41 | inrae_1 | inrae_1_2 | 0 |
00b5fefed | 25 | arvalis_3 | arvalis_3_1 | 0 |
... | ... | ... | ... | ... |
ffb445410 | 57 | rres_1 | rres_1_3 | 0 |
ffbf75e5b | 52 | arvalis_1 | arvalis_1_3 | 0 |
ffbfe7cc0 | 34 | arvalis_1 | arvalis_1_2 | 0 |
ffc870198 | 41 | usask_1 | usask_1_2 | 0 |
ffdf83e42 | 39 | arvalis_1 | arvalis_1_2 | 0 |
3373 rows × 4 columns
1 |
|
image_id | bbox_count | source | stratify_group | fold | |
---|---|---|---|---|---|
0 | 00333207f | 55 | arvalis_1 | arvalis_1_3 | 1 |
1 | 005b0d8bb | 20 | usask_1 | usask_1_1 | 3 |
2 | 006a994f7 | 25 | inrae_1 | inrae_1_1 | 1 |
3 | 00764ad5d | 41 | inrae_1 | inrae_1_2 | 0 |
4 | 00b5fefed | 25 | arvalis_3 | arvalis_3_1 | 3 |
... | ... | ... | ... | ... | ... |
3368 | ffb445410 | 57 | rres_1 | rres_1_3 | 1 |
3369 | ffbf75e5b | 52 | arvalis_1 | arvalis_1_3 | 1 |
3370 | ffbfe7cc0 | 34 | arvalis_1 | arvalis_1_2 | 3 |
3371 | ffc870198 | 41 | usask_1 | usask_1_2 | 4 |
3372 | ffdf83e42 | 39 | arvalis_1 | arvalis_1_2 | 4 |
3373 rows × 5 columns
7. fold별로 stratify_group의 개수 파악하기
- fold 별로 잘 나누어졌는지 확인한다.
1 |
|
1 |
|
데이터 로더 만들기
1 |
|
Bounding Box를 Tensor로 만들 때 주의해야할 점
- 데이터로더의
__getitem__
의 마지막 부분을 보면, boxes값들을 tensor로 만들어주는 코드가 있다. - 밑의 예시를 보며 각각의 코드들이 어떤 역할을 하는지 이해할 필요가 있다.
1 |
|
1 |
|
1 |
|
1 |
|
- unpacking operator를 사용하면 값들의 순서가 바뀐 것을 볼 수 있다. 이렇게 바뀐 순서를 다시 원래대로 돌려주기 위해
permute
함수가 사용된 것이다.
1 |
|
1 |
|
- 코드를 더 간단하게 만들기 위해
squeeze
를 사용해도 괜찮다.
1 |
|
1 |
|
transforms 함수 만들기
format = pascal_voc
:어느 데이터셋에 대한 bbox 내용을 가져올 것인지label_fields
: 우리가 만든 labels- albumentations 참고 : https://albumentations.ai/docs/examples/example_bboxes/
1 |
|
"Stack" 관련 에러 날 경우
RuntimeError: stack expects each tensor to be equal size, but got [49, 4] at entry 0 and [26, 4] at entry 1
- 이러한 에러가 날 시,
DataLoader
설정 시collate_fn
옵션을 넣어줘야 한다. - 특정 상황에서 batch별로 데이터가 잘 안묶여서 생기는 에러이므로, 이러한 오류가 나면 아래와 같은 함수를 설정하여 옵션에 넣어주자.
1 |
|
1 |
|
학습하기
모델 불러오기
- 모델은
fasterrcnn_resnet50_fpn
을 사용한다.- fpn : feature pyramid network로, 기존 모델 구조에서 보다 발전된 형태이다. 참고 : https://eehoeskrap.tistory.com/300
- Backbone은
resnet50
을 사용한다.
- 모델의 구조를 보면,
Dropout
,BatchNormalization
등의 레이어 층을 볼 수 있다.Dropout
: 모델이 train 데이터셋을 통해 중요한 노드(요소)들을 집중적으로 학습하다 보니, test셋과 같은 새로운 데이터가 왔을 때 예측을 잘 하지 못하는 경우가 발생한다. train셋에 과적합 되었기 때문이다. 이를 개선하기 위해Dropout
옵션을 정해주는 것이다. 또한 매번 다른 노드들로 학습을 하다 보니, 앙상블의 효과도 얻을 수 있다.- 만약 Dropout(0.3)이면, 0.3만큼의 임의의 노드를 사용하지 않는다는 의미이다.
- 이 때 0이 되지 않은 0.7에 해당하는 값은 (1/0.7) 만큼 scale이 된다. 따라서 (1/0.7 = 1.4286…)이 되는 것이다.
BatchNorm
: 배치로 들어오는 데이터에 규제를 넣어주는 것이다. 학습 속도 및 학습 성능에 영향을 준다.- 참고 블로그 : https://gaussian37.github.io/dl-pytorch-snippets/
1 |
|
1 |
|
.
.
.
1 |
|
모델 out_features 바꿔주기
- 모델 구조를 보면, 마지막
box_predictor
를 보면cls_score
의out_features
가 91이다. - 현재 우리는 wheat / background 라는 두 개의 class를 분류하는 문제를 갖고 있으므로,
out_features=2
로 바꿔줘야 한다. bbox_pred
역시 class 개수에 따라 바꿔준다. 로 자동적으로 바뀐다.torch.nn.Linear
로 바꿀 시,cls_score
,bbox_pred
모두 바꿔줘야 하고,torchvision.models.detection.faster_rcnn.FastRCNNPredictor
로 접근하여 바꿀 시,bbox_pred
는cls_score*4
로 자동적으로 바뀐다.
방법 1
1 |
|
1 |
|
방법 2
1 |
|
1 |
|
1 |
|
1 |
|
.
.
.
1 |
|
업데이트할 Parameters 설정
- model의 parameters 중,
requires_grad=True
, 즉 freeze가 안된 파라미터들을 가져와서 학습하겠다는 의미이다. Epoch
를 돌며 경사하강법으로 학습할 파라미터이다.
1 |
|
1 |
|
Optimizer 설정
AdamW
등 Detection 문제에서 평균적으로 더 좋은 성능을 보이는 옵티마이저가 있지만, 지금은Adam
을 사용해보자.
1 |
|
Averager 클래스 설정
- Averager는 loss 값을 계산할 때 사용한다.
Faster Rcnn
모델에서 내부적으로 손실값을 계산할때 사용하는 class로, 이미 구현이 되어있다.- 모델마다 이러한 Averager들이 조금씩 다를 수 있으므로, 다른 모델을 사용할 때 검색해서 한번 찾아보자.
1 |
|
Validation 함수
- pytorch의
faster-rcnn
모델을 사용할 시, model.eval()은 loss가 아닌 예측 바운딩박스 좌표값들을 돌려준다. 우선은with torch.no_grad()
만 적용하여 validation loss를 계산해보자.
참고 : https://stackoverflow.com/questions/60339336/validation-loss-for-pytorch-faster-rcnn
1 |
|
모델 학습하기
- 모델은 5번의 교차검증으로 훈련한다.
단일 모델 훈련 코드
1 |
|
교차검증 훈련
- 5번의 교차 검증을 진행한다. 아래 출력창을 보면 로스값이 떨어지는 것을 확인할 수 있다.
1 |
|
1 |
|
1 |
|
1 |
|
모델 저장하기
- 모델을 저장하는 방법은 여러 가지이다.
-
- 모든 epoch마다 모델을 저정하는 방법
-
- EarlyStopping을 사용하여 최소 loss 값을 보이는 모델만 저장하는 방법
-
- 사용할 방법을 코드로 구현해서 적용하면 된다.
마무리
Object Detection - 이진 분류 대회
에 대한 기본적인 Base line 코드를 공부해봤다.- 교차검증을 위해 Train, Valid 셋을 나눌 때,
stratify_group
을 만들었는데, 이는 최대한 데이터 분포를 고르게 하여 특정 fold에서 모델이 과적합 되지 않게 하기 위함이었다. - 모델 성능을 더 높이기 위한 여러 방법들을(하이퍼 파라미터 조정, 데이터 전처리, 모델 바꿔서 학습해보기 등) 더 공부해보고 적용해보자.
-
저장한 모델로 test 셋 예측 및 제출하기까지의 방법은
Weighted Boxes Fusion
글을 참고하면 된다. - Test데이터 추론에는 Pre-trained
faster-rcnn
모델을 5번의 교차검증으로 훈련한 후,weighted boxes fusion
방법을 적용했다. - 리더보드 점수는 private : 0.5870, public : 0.6735
- 참고 : 리더보드상 1위 private : 0.6897, 1위 public : 0.7746
참고 자료
- FasterRCNN 학습 코드 참고 : https://www.kaggle.com/pestipeti/pytorch-starter-fasterrcnn-train
- albumentations 참고 : https://albumentations.ai/docs/examples/example_bboxes/
- Dropout, BatchNorm 참고 : https://gaussian37.github.io/dl-pytorch-snippets/
- StratifyKFold 참고 : https://www.kaggle.com/shonenkov/wbf-approach-for-ensemble
- Pytorch FasterRCNN Validation 참고 : https://stackoverflow.com/questions/60339336/validation-loss-for-pytorch-faster-rcnn