read

YOLOv8 Concrete Crack Detection using Instance Segmentation #3

- About Training the model


Review

이전 게시글
지난 글에서는 모델 학습에 앞선 데이터 셋 준비에 관한 글을 작성하였다. 데이터 셋이 준비가 완료되면 모델 학습을 수행하면 된다. 다만, 모델의 학습 결과를 보고 반복적으로 데이터 셋 정제 및 보강 > 모델 학습의 과정을 수행해야 하므로, 단 번에 될 수는 없다.


History

학습환경 구축 과정
위 링크에 포함된 본 시리즈의 첫 번째 게시글과 같이 학습 환경이 완전히 준비되었다면, 아래 과정에 따라 그저 학습을 수행하기만 하면 된다.
학습 수행 > 학습 결과 분석 > 데이터 보강 > 학습 수행, 반복.
본 과정에서 대부분의 시간은 학습 수행에서 소요되기에 학습을 수행함에 있어 사람이 직접 할 수 있는 것은 ‘기다림’이다.
모델 학습에 있어 GPU의 성능이 크게 영향을 미친다는 사실을 알고 있었음에도 나의 경우 여건이 되지 않아 평소에 사용하던 MacBook Air 2020(Intel)로 처음 학습을 수행하게 되었다. 조언을 하자면, 사양이 좋지 않은 경우라면 모델 학습은 깔끔하게 포기하고 학습된 타인의 모델을 이용해라. 절대 나와 같은 멍청한 짓을 하지 않기를 바란다.
아래 사진과 같이 나의 경우 6회 이상 학습이 불가하였고, 20분이 소요되었으며 추론 결과, mAP도 의미도 없던 수준이었던 것으로 기억한다(물론 학습 환경과 파라미터만의 문제라기 보다는 낮은 품질의 데이터셋과의 복합적인 결과일 것임을 직감했다).
학습하다 뻗어버리는 경우가 많았지만, 그럼에도 학습을 기다리며 보낸 시간이 한참인데,, 이과정은 가비지라 판단되어 사진도 많이 남겨 놓지 않았기에 글에 첨부할 수 없음이 아쉬울 따름이다.

About data set

초기 모델의 추론 결과 위와 같이 모델의 추론 결과는 처참했다.

초기 학습 결과 당시에도 단순 하드웨어적인 문제가 아닐 것임을 직감했지만, 구체적으로 무엇이 문제인지 알 수는 없었기에 학습 결과에서 인사이트를 얻고자 하였다.
YOLO의 경우 학습을 수행하게 되면 결과에 대한 그래프를 자동적으로 그려주기에 분석하기에 매우 용이하다. 이중 train/box_loss 등을 보면, W 형태가 나타나는데 나는 이것이 학습 데이터의 절대적인 부족에 기인하는 것으로 판단하였다. 가령, 최초 학습에는 학습을 진행하여 높은 수치가 나오지만, 이후 추가학습을 통해 점점 보정되기를 반복하며 특정 값에 수렴해야 하지만, 나의 경우 이러한 반복이 일어나지 않았기 때문이다. 당시에 하드웨어적인 제한은 극복할 수 있는 상황이 아니었기 때문에 나는 이러한 문제가 해결되기 전에 앞서 데이터 셋을 늘이고 정제하는 것에 시간을 투자해야 함을 알았다.
경우에 따라 그것이 오답일지 모르더라도 이처럼 분석에 근거한 접근론은 나를 성장시켜 줄 수 있을 것이라 확신하였다.

training on roboflow 데이터 셋에 문제가 있다는 것을 확신하기 위해서 roboflow에서 지원하는 모델 학습 기능을 이용하여 성능을 살펴 보았다. 로보플로우에서 학습된 모델의 mAP 역시 0.45에 못 미치는 수준이었으며, 이것의 loss 그래프와 이전의 학습 결과를 비교하였을 때, 일정 값에 점차 수렴되는 것은 확인할 수 있었으나 조악한 수준인 것에 근거하여 데이터셋에 문제가 있음을 확신하였다. 또한 같은 데이터셋에 대하여 다른 결과를 보였으므로 하드웨어적인 한계와는 별개로 train configuration의 학습 파라미터의 설정에도 문제가 있을 것이라 판단하였다. 이러한 과정을 반복하여 결국 이전 게시글과 같은 학습 데이터 셋을 구축하게 된 것이다.

parameter

YOLO 모델의 학습에 있어서 파라미터를 수정하는 방법은 가령, train(epoch=100, lr=0.001, batch=16)와 같이 train() 메소드의 파라미터 값을 주는 방식과, train(config = '[YOUR CONFIG FILE].yaml')과 같이 모델의 ‘백본’이 저장된 config.yaml 파일에 직접 접근하여 수정하고, 이를 메소드의 파라미터로 지정해 주는 방식이 있다.
전자의 방식을 선택한다면, 모델은 기본적으로 제공하는 config 파일에서 메소드가 던져 주는 파라미터 값만 변경하고 나머지는 디폴트 값으로 학습을 시작하고, 후자를 선택한다면, config 파일을 직접 작성하며 모든 파라미터를 하나씩 다 정해줄 수 있다는 장점이 있지만, 그만큼 많은 이해도를 필요로 한다는 것에 주의해야 한다.

나의 경우 기본적으로 제공되는 모델의 파라미터 default 값이 나의 목표에 적합하지 않다고 판단하였기에 이를 직접 수정하기를 택했고, 이때부터 백본에 대한 이해, 각 파라미터의 기능 등에 대해 완전히 이해하기 위해 실패하고 다시 부딪히며 꽤나 많은 고생을 했다. 또 이과정에서 YOLO 메소드의 구동 매커니즘과 프로세스에 대한 전반적인 이해도가 매우 상승했다.

역시 나는 머리 깨져가며 부딪히는 게 어울리는 것 같다.

config.yaml default 위는 YOLO의 디폴트 config 파일의 일부이다.

config.yaml custom 위는 커스텀 config 파일을 만들던 시기 중 후반부 파일의 일부 내용이다. 모델 학습에 사용되는 하이퍼 파라미터는 수도 없이 많지만, 직접 하나씩 공부하고 사용해 보며 수많은 결과와 조합을 분석하였을 때 유의미한 변화를 줄 수 있는 것은 optimizer에 사용되는 함수가 무엇인지, learning_rate(lr로 축약)를 학습 결과에 따라 얼마나, 그리고 cos_lr 등 어떤 방식을 선택할 것인지, weight_decay를 얼마나 줄 것인지, drop_out은 적용할 것인지 등 일부에 불과하다고 판단하였고, 이는 학습 결과와 환경에 따라 변화를 주되 default 값을 가져가는 것이 유리하다고 판단하였다. 이 과정에서 정말 많은 시간을 보냈던 것 같다. save_dir을 보면 results23이 되기까지 정말 고생이 많았다.

p.s. 에포크와 모델의 퍼포먼스가 반드시 비례하지는 않기에 보다 효율적인 피드백과 학습에 소요되는 시간을 줄이기 위해 ‘early_stopping’기법을 사용하여 최종적으로는 epochs = 300, patience = 30으로 주어 30번 이상 학습하여도 좋은 결과가 나오지 않는다면, 학습이 종료되도록 하여 피드백을 수행하였다.

많은 노력에도 불구하고 파라미터 조작에 따라 큰 성능 향상을 보이지 않아 좌절하던 때에 ‘ray tune‘이라는 것을 알게 되었다. 이는 모델의 하이퍼 파라미터 튜닝을 수행하는 도구로, 여태 내가 한 것을 자동으로 해주는 기능이었다. 내심 기뻤지만 정말 허무했다. ㅋㅋㅋ;;
당혹스러운 마음을 추스르고 어찌저찌 레이 튜닝까지 수행하였지만, 결과는 더 조악해졌다. 지금 생각해도 공식적으로 배포가 된 도구인데, 정상적으로 사용했을 때 나같은 초심자가 한 것보다 결과가 안 좋을 수가 있는지 의문이다. 막막하면서도 한 편으로는 안도했던 것 같다, 내가 한 것이 아무 의미 없던 것은 아니라는 생각에.

레이 튜닝을 포기하고는 다시 하이퍼 파라미터 조작을 시도 했고, 결국 내가 할 수 있는 최선의 조합을 찾았다. 더불어 보다 좋은 성능을 위해 시도할 수 있는 방법을 고민하다 fine_tuning이라는 아주 유용한 기능을 찾을 수 있었다. 이는 사전에 학습된 모델에 데이터를 추가하며 가중치를 학습하는 것으로, 개념적으로는 단순해 보일 수 있지만 아주 좋은 효과를 보여 준다.

data analysis 위 사진은 모델에 학습된 데이터가 어떠한 경향을 가지는 지에 대한 인사이트를 제공하는 그래프로, 이 역시 YOLO가 자동으로 그려주는 것이다. 나는 이를 분석하여 모델에 사용된 데이터 셋에서 crack의 대부분이 사진의 중심에만 모여 있다는 점, “height = 0.4 ~ 1.0 && width = 0.0 ~ 0.6” 구간의 데이터가 부족하다는 점을 파악하고 모델에 이에 대한 데이터를 추가해 주며 모델을 fine tuning 하였다.


Final

Environments

  • OS: Windows 10

  • Mainboard: ASRock B760M Pro RS

  • BIOS: 3.04

  • Processor: 13th Gen Intel(R) Core(TM) i5-13500(20 CPUs), ~2.5GHZz

  • Memory: 16GB

  • VGA: NVIDIA Gefore RTX 4600 8GB

데이터 셋과 다른 학습 환경을 만족할 수준으로 세팅한 후에는 위와 같이 데스크톱 사양까지 맞추어 울며 겨자 먹기로 학습을 수행하였고, 아래와 같이 꽤나 준수한 성능을 최종적으로 얻을 수 있었다.


Training start!

최종 학습 시작 위 사진은 최종 학습의 시작화면이다. 지금 봐도 가슴이 뭉클하다.. 사진을 보면 나오는 수많은 단어와 값들은 모두, 하이퍼 파라미터와 그 값이다. config 파일을 확정할 때 optimizer 파라미터의 값으로 처음에는 ‘Sigmoid’ 함수를 사용하였고, 한계를 느껴 시그모이드 함수의 상위 버전인 ‘momentum‘을 사용하였음에도 만족스럽지 않아, 적응적 학습률을 가지는 ‘rms prop‘도 써보았다. 그럼에도 가시적인 성능 향상을 찾기는 쉽지 않았고, 그렇게 momentum과 rms prop의 상위 버전인 ‘Adam’ 함수를 사용하였더니 만족할 수준의 성능을 보였다.
그야 당연할 것이 momentum과 rms prop은 optimizer를 설명할 때에 가장 보폭, 방향 등의 비유로 표현되는 각 사이드의 대표적인 주자이기에, 두 요소를 혼합한 Adam이 일반적인 상황에서 가장 좋은 성능을 보이는 것은 당위적인 귀결일 것이다.
다만, 직접 도출한 최적의 결과가 정답인지 확인하기 위해 optimizer를 ‘auto’로 지정하고 학습하였을 때 사용된 그것이 Adam인 것을 보았을 때에는 정답을 찾았다는 것에서 카타르시스도 느꼈지만, “auto” 네 글자면 해결 되었을 문제라는 점, 일반적으로 좋은 성능을 보이는 Adam의 장점이 나에게도 똑같이 적용됨에 따라 특별할 것이라 생각했던 나의 경우 역시 common case에 속하는 것일 뿐이라는 것에서 찝찝한 감정을 함께 느꼈을 때에는 “괜히 어려운 길을 걸었나?” 싶은 생각이 들기도 하였다. 단순히 코딩 뿐만 아니라 본 경우, CS, 개념 및 이론 공부에 있어서 이러한 딜레마는 매번 생기는 것 같다. 물론 딜레마가 주는 찝찝함보다 고생 후에 얻은 결과가 옳았다는 것이 주는 성취감이 훨씬 크기에 문제되지 않지만 말이다. 세상이 너무 편해졌다.

Training Result

최종 학습 종료 위 사진은 최종 학습이 종료된 시점의 캡처화면이다. 모델이 30 epochs 이상 향상된 결과를 보이지 않아 조기 종료되었고, 결과적으로 52회차 epoch의 모델이 최고 성능을 보였다. 나름 거액을 투자했음에도 82회 에포크를 학습함에 있어 6.631시간이 걸렸다. 밤새 파라미터를 조정하다가 “더 이상은 할 수 있는 게 없다.” 싶은 생각과 함께 학습을 시작하고 컴퓨터에 앉아 잠들었다 깨어나서도 종료되지 않았던 기억이 난다.


Results Analysis

input / output shapes analysis

아래는 netron 앱을 이용하여 학습된 모델의 아키텍처 중 input, output0, output1의 shape에 해당하는 부분만 일부 캡처한 것이다.

input shape

output0 shape

output1 shape

  • input shape = (1, 3, 640, 640)
  • output0 shape = (1, 37, 8400)
  • output1 shape = (1, 32, 160, 160)

이때,
input shape = (배치 사이즈, RGB, 이미지 크기 x, 이미지 크기 y)
output0 shape = (배치 사이즈, 각 클래스의 예측값, 탐지 가능한 객체 수)
output1 shape = (배치 사이즈, 각 마스크 가중치, 마스크 이미지 크기 x, 마스크 이미지 크기 y)
의 텐서 차원을 의미하며, Bounding Box와 Mask를 그릴 때, Confidence를 출력할 때에도 해당 텐서 차원을 읽어서 처리하는 방식으로 프로세스가 구성된다. 가령, Bounding Box를 그릴 때에는 output0 shape의 1열의 최상위 네 개 값이 되는 x, y, w, h를 이용해서 Bounding Box를 그리며, Confidence는 그것에 후위하는 데이터를 활용하는 방식이다.


주요 Metric

모델의 학습 결과는 아래와 같다.

구분 Box Mask
Precision 0.875 0.891
Recall 0.823 0.792
mAP 50 0.906 0.809
mAP 50 - 95 0.735 0.792

PR Curve

PC Curve

  • Precision
    Box_Precision과 Mask_Precision 모두 0.9에 준하는 값으로, 목표였던 정확도 90%에는 못 미치지만 만족할 수 있는 결과였다. 이는 곧 모델이 균열이라고 외친다면, 90%는 균열이라는 것을 의미한다. 다만, 학습된 모델 역시 세상 모든 경우를 다 포함하는 것이 아니라, 제공해 준 학습 데이터 셋의 경향에 가까운 case에 대해서만 그 정확도를 나타내기에 Recall도 함께 높아야 한다는 것이 상당히 중요하다.

  • Recall
    일반적으로 Recall은 Precision에 비해 낮을 수 있지만, 그 차이가 커서는 아니 되고, 나는 겪어온 과정에서의 결과와 달리 Box_Recall, Mask_Recall 모두 그것의 Precision과 10% 내의 차이를 가진다는 점에서 만족할 수 있었다(계속 변인들을 조작하며 다시 시도했던 이유의 대부분은 Precision과 Recall 값의 차이가 극명했기 때문이었다).

  • mAP
    mAP는 모델의 성능평가에 있어 가장 일반적으로 사용되는 metric으로 모델이 신뢰도 임계값 iou에 대해 보이는 퍼포먼스가 어느 정도인지를 나타낸다. 이는 iou = 0.5(50%)일 때의 mAP인 mAP 50과,iou = 0.5에서 0.95까지 0.05 단위로 점증시켰을 때의 mAP를 평균낸 값인 mAP 50 - 95로 세분화 된다. iou가 높다는 것은 Detection 되는 객체가 적어지지만, 그만큼 더 정확하다는 의미를 가질 수 있지만, 더 정확하게 하고자 Detection 해야 할 객체를 하지 못하게 되는 단점도 있다. 반대로 iou가 낮아질 수록 객체는 더 많이 인식할 수 있지만, 그것이 정확한가에 대한 신뢰도 문제가 생길 수도 있다. 단, iou = 0.5라고 한들 절대 부족한 수치가 아님으로 mAP라고 한다면 mAP 50를 말한다고 이해하면 될 것이다(실제 내가 테스트 하였을 때에도 iou = 50 ~ 60 선이 실세계에서 가장 사용성이 좋다고 느꼈다).

    혹자가 mAP 50과 mAP 50 - 95 중 무엇이 더 좋은지에 대해 묻는다면, 나는 명확한 답변을 내리기 어려울 것이다.
    그럼에도 불구하고, 모든 공학과 기술의 모든 개념에는 이러한 trade-off 관계가 존재하기에, 상황에 따라 유연히 대처할 수 있도록 다양한 metric, 접근론에 대한 고민과 제안은 계속되어야 한다.
    또한 현대 기술과 사회가 급격하게 발전함에 따라 이러한 과제는 더욱 심화될 것이고,
    나는 그것의 해결이 공학자를 필요로 하는 근본적인 이유라 믿고 있다.


Inference

주요 Metric에 대한 모델의 퍼포먼스는 만족스러운 수준이었다. 그리고 실제로 모델의 예측 결과를 통해 이를 확인해 보았다.

나는 우선 테스트 데이터셋 일부에 대해 모델이 추론한 결과와 정답을 비교해 보기로 하였다.

정답 비교: 좌 - 정답, 우 - 추론 결과

사진에서 볼 수 있듯 채택한 비교군에 대하여 모델은 훌륭히 task를 해내었다. (0, 1)과 (0, 3)을 비교해 보면, 추가적으로 Mask 하지 않아야 할 부분도 작게나마 Mask 한 경우도 있었지만 용인가능한 수준이었으며, 반면에 정답 사진에서 제대로 Mask 하지 못한 부분을 모델이 보다 깔끔하게 Mask하는 경우도 있었음을 같은 사진과, (2, 3), (3, 3)에서도 확인할 수 있었다.

추론 결과

위 사진은 모델이 테스트 데이터에 대해 예측한 추론 결과 중 일부를 캡처한 화면이다. 사진에서 볼 수 있듯 대부분의 사진 속 Crack을 Detection 하였지만, 일부에 대해서는 Detection 하지 못한 것을 확인할 수 있었다. 테스트 데이터 셋이 하나씩 확인하기에는 어려움이 있기에 추가적인 코드 작성을 통해서 전체 테스트 데이터 셋 중 Crack을 Detection 하지 못한 사진의 개수, 비율을 확인하고자 했다.

이를 위한 방법론은 간단하게 고민으로 해결할 수 있었는데, 위 사진에서 Task가 정상적으로 수행된 데이터의 경우 Bounding Box와 Mask를 그리기 위한 crack의 위치 값이 필요하다는 점, 그리고 이는 위 사진 속 좌상단에 있는 labels 패키지에 저장된다는 점을 이용하는 것이었다. 아이디어는 Task를 수행하지 못했다면, labels 파일이 생성되지 않았을 것이기에 파일 개수의 차를 통해서 구할 수 있다는 단순한 생각이었다. 현재 상황에서 숫자만 세고 말 것이라면, 단순히 ctrl + a로 개수 확인해서 비교하고 계산만 하면 그만이지만, 추후에도 이를 고도화 하기 위해서는 추가적인 코드를 작성하는 편이 좋을 것이라 판단하였다.

import os

def count_no_detections(image_folder, label_folder):
    
    image_count = len([f for f in os.listdir(image_folder) if f.endswith(('.png', '.jpg', '.jpeg'))])
    label_count = len([f for f in os.listdir(label_folder) if f.endswith('.txt')])
    
    no_detection_count = image_count - label_count
    detection_success_rate = round(label_count / image_count, 3)
    
    return no_detection_count, detection_success_rate * 100

def save_to_file(filepath, no_detection_count, success_detection_rate):
    with open(filepath, 'w') as file:
        file.write(f"No detection count: {no_detection_count}\n")
        file.write(f"Success detection rate: {success_detection_rate}%\n")

간단한 코드를 통해 label 파일의 개수와 이미지 파일의 개수를 읽고 차를 구하고 비율을 구하는 방식으로 해결할 수 있었고, 결과는 다음과 같았다.

detect success rate 전체 테스트 데이터 셋 중 55개의 데이터에 대하여 Task를 수행하지 못하였고, 수행한 데이터의 비율을 81.6%였다. 생각보다 낮은 수치를 보였기에 Task를 수행하지 못한 데이터를 분석하고자 하였다.

missing data 위 사진은 miss한 데이터 중 일부를 캡처한 화면이다. 대부분 맨홀, 대리석 자재 바닥에 발생한 크랙 등이었으며, 이는 공공 데이터를 추가하면서 잘못 추가한 데이터였다. 그럼에도 맨홀 뚜껑과 대리석 바닥의 경우 특유의 음각 패턴 등이 들어가는 경우가 많기에 이에 대한 학습도 경우에 따라 필요할 것으로 보이나, 건물 벽면의 균열을 탐지하고자 했던 나의 목표와는 무관하기에 큰 문제가 되지는 않았다.


가량 두 개의 게시글로 나누어 쓸 법한 분량을 하나의 게시글에 쓰게 된 것 같다. 이제부터는 어플리케이션 영역으로 넘어가서 학습한 모델을 응용하여 만들었던 WebApp과 로컬 프로그램을 통해 HoloLens, Webcam, Youtube, Image 등의 소스를 본 모델로 추론해 보고자 한다.


그럼에도 불구하고,
비로소 나는 해내었다.



Profile

Seong Hun KIM

Student
Dept. of Computer Science Engineering | Yeungnam University, Repulic of Korea

yu signature

Phone 010 - 6685 - 1140
Mail tgh7544@naver.com
LinkTree https://linktr.ee/HoonC_corgi

Blog Logo

HoonC-corgi

HoonC-corgi


Published

Image

HoonC-corgi's Blog

해가 지는 곳따라 걷다 보면, 그게 내 기쁨이어라.

목록으로 돌아가기