Architecture위성영상 토지피복 분류 개선기 · 3/3

손실 함수 조합과 진단 도구로 오분류 원인을 숫자로 잡기

토지피복 분류 모델의 손실 함수 조합 계획(B1~C8)과 Fisher Ratio·AUC·Gradient Heatmap 같은 학습 진단 도구로 meadow가 왜 가장 많이 틀리는지 실측하고, 그 원인이 스펙트럼 중첩을 넘어 라벨 일관성 문제라는 걸 확인한 기록.

2026-07-2920 min read
#컴퓨터 비전#손실함수#위성영상#세그멘테이션#모델진단

지난 글까지 입력 특징(NDVI, HSV) 쪽을 정리했으니, 이번에는 손실 함수 조합 계획과 "지금 모델이 정확히 어디서 왜 틀리는지"를 숫자로 확인한 진단 결과를 정리한다.

손실 함수 조합 계획

각 손실이 보는 단위가 다르다는 건 딥러닝 시리즈의 손실 함수 정리에서 다뤘다. 여기서는 그 손실들을 이 프로젝트에 어떻게 조합할지를 계획한다.

  • B1 (CE + Dice): 확률도 맞추고 전체 영역도 맞추는 가장 무난한 기본 조합. 여기서부터 시작한다.
  • C1 (+ Focal): 헷갈리는 픽셀(주로 경계선)을 더 집중적으로 학습시킨다.
  • C2 (+ Tversky): 얇은 도로나 작은 건물이 끊기는 걸 강하게 방지한다 - FN(누락)에 더 큰 가중치.
  • C3 (+ Boundary): 외곽 테두리를 아주 날카롭게 자른다.
  • C4 (+ Lovász): mIoU 점수 자체를 직접 끌어올린다.
  • C6 (+ PixelCL): 경계선 라벨에 노이즈가 많아도 특징 공간 단위로 뭉쳐서 내성을 확보한다.
  • C7 (Margin-CE + Dice): 숲/초지처럼 색상이 비슷한 클래스 사이에 완충 지대를 만든다.
  • C8 (전부 조합): Margin-CE로 클래스 간 완충 지대를 확보하고, Tversky로 미세 객체를 살리며, Lovász로 mIoU를 직접 올리고, PixelCL로 특징을 뭉쳐서 경계선 오분류와 미세 객체 누락을 동시에 잡는 걸 목표로 한다.

B1부터 순서대로 붙여가며, 각 조합이 실제로 어떤 지표를 개선하는지 확인할 계획이다.

학습 검증·시각화 모듈

손실을 바꾸기 전에, 지금 모델이 정확히 어디서 왜 틀리는지부터 숫자로 확인할 수 있는 도구가 필요했다.

Fisher Ratio - 클래스가 특징 공간에서 얼마나 벌어져 있는가

모델은 이미지를 최종 클래스 확률로 뱉기 전에, 내부적으로 각 픽셀을 고차원 특징 벡터(embedding)로 표현한다. 두 클래스가 진짜 잘 구분되는 모델이라면, 이 특징 공간에서 두 클래스의 벡터들이 서로 멀리 떨어진 덩어리를 이뤄야 한다. Fisher Ratio와 AUC는 둘 다 "이 벌어짐 정도"를 숫자 하나로 재는 도구인데, 재는 방식이 다르다.

**Fisher Ratio(피셔 판별비)**는 통계학의 선형판별분석(LDA)에서 온 개념으로, 정의는 다음과 같다.

Fisher Ratio = 클래스 간 분산 (between-class variance) / 클래스 내 분산 (within-class variance)
            = (클래스A 평균 - 클래스B 평균)² / (클래스A 분산 + 클래스B 분산)

분자는 "두 클래스의 중심이 얼마나 멀리 떨어져 있는가", 분모는 "각 클래스 내부가 얼마나 퍼져 있는가(뭉쳐있지 않고 흩어져 있는가)"를 뜻한다. 두 클래스 중심이 아무리 멀어도 각 클래스 내부가 그만큼 넓게 퍼져 있으면 결국 겹쳐 보이므로, Fisher Ratio는 "거리"가 아니라 **"내부 산포 대비 상대적인 거리"**를 잰다. 값이 클수록 두 클래스가 확실하게 갈라져 있다는 뜻이다. 지금 측정값은 3.42다.

**AUC(Area Under the Curve)**는 원래 이진 분류기의 ROC 곡선 아래 면적을 뜻한다 - 분류 임계값을 계속 바꿔가며 "이 임계값에서 두 클래스를 얼마나 잘 갈랐는가"를 전부 종합한 값으로, 0.5면 완전히 랜덤(구분 못함), 1.0이면 완벽하게 구분한다는 뜻이다. 여기서 쓴 linear AUC는, 모델의 내부 특징 벡터 위에 아주 단순한 선형 분류기(linear probe) 하나만 새로 얹어서 두 클래스를 구분시켜보고 그 분류기의 AUC를 잰 값이다 - "복잡한 비선형 분류기를 굳이 안 써도, 특징 공간에 직선 하나만 그으면 두 클래스가 갈라지는가"를 확인하는 셈이다. 지금 측정값은 0.964로, 특징 공간에서 거의 완벽에 가깝게 선형 분리가 된다는 뜻이다.

두 지표를 같이 보는 이유는 서로 확인하는 방식이 다르기 때문이다 - Fisher Ratio는 평균·분산이라는 분포의 모양으로, AUC는 실제로 분류기를 하나 학습시켜서 그 성능으로 확인한다. 두 값이 같은 방향(둘 다 높음/둘 다 낮음)으로 나오면 "특징 공간에서는 실제로 잘 갈라져 있다"는 결론에 더 확신을 가질 수 있다.

Gradient Heatmap (학습 집중도 지도)

역전파(backpropagation)는 손실에서부터 입력 방향으로 거슬러 올라가며 "이 픽셀을 이렇게 바꾸면 손실이 얼마나 줄어드는가"라는 기울기(gradient)를 계산한다. 이 gradient의 절댓값 크기를 픽셀별로 모아 원본 이미지 위에 색으로 얹으면, 모델이 지금 어디에 학습 에너지를 쏟고 있는지가 그대로 지도가 된다.

픽셀별 |∂Loss/∂feature| 를 채널 방향으로 합산 → 0~1로 정규화 → 히트맵 오버레이

빨강(gradient 큼)  = 아직 손실이 크게 남아있어 강하게 학습되는 중인 영역
파랑(gradient 작음) = 이미 잘 맞혀서 추가로 밀어붙일 필요가 없는 영역

기대하는 정상 패턴은 "경계선·혼동 클래스 주변이 빨갛고, 넓은 단일 클래스 내부는 파란" 모습이다. 이렇게 나오면 Focal Loss 등이 실제로 의도한 대로 어려운 픽셀에 학습을 집중시키고 있다는 뜻이다. 반대로 빨간 영역이 이미지 전체에 무작위로 흩어져 있다면, 어려운 영역에 집중이 안 되고 있다는 신호이므로 손실 조합을 다시 봐야 한다.

Prediction Difference Mask (예측 차이 마스크)

기존 모델(baseline)과 새 모델의 예측을 픽셀 단위로 XOR(다른지 여부)로 비교하면 "달라진 픽셀 전체"는 나오지만, 그 자체로는 뭐가 좋아졌는지 알 수 없다. 그래서 달라진 픽셀을 정답 라벨의 거리 지도(경계로부터의 거리)와 연결 성분 크기 기준으로 세 종류로 나눠 색을 다르게 입힌다.

바뀐 픽셀 하나마다:
  경계에서 먼 곳(내부)에서 바뀜       → "영역 안쪽 재분류" (파랑)
  경계에서 가까운 곳(수 px 이내)에서 바뀜 → "테두리 정리" (초록)
  기존엔 없던 작은 연결 성분이 새로 생김  → "미세 객체 복원" (주황, 예: 놓치던 좁은 도로)

이렇게 나누면 "mIoU가 0.002 올랐다"는 숫자 뒤에 실제로 어떤 종류의 개선이 있었는지 - 그냥 큰 덩어리 안쪽 라벨이 흔들린 것뿐인지, 정말 원하던 경계 선명화·미세 객체 복원이 일어난 것인지 - 를 구별할 수 있다. 지표 숫자만으로는 안 보이는 "무엇이 좋아졌는지"를 시각적으로 확인하는 용도다.

Cosine Similarity Heatmap (특징 유사도 지도)

기준 픽셀 하나를 정하고, 주변 모든 픽셀의 특징 벡터와의 코사인 유사도를 계산해 지도로 그린다. 경계선을 잘 학습한 모델이라면 경계를 넘는 순간 유사도가 완만하게 떨어지는 게 아니라 예를 들어 0.95→0.1처럼 칼로 자른 듯 뚝 떨어져야 한다 - 유사도 지도의 "낙차가 급격한 선"이 실제 경계와 얼마나 일치하는지로 경계 학습 품질을 확인한다.

현재 상태: 클래스별 스펙트럼과 오분류 패턴

진단 도구로 확인한 실측 결과다(68개 검증 이미지 기준, Raw DN 중앙값).

1. 클래스별 스펙트럼 특징

classGT 비중RGBNIRNDVI
forest15.8%1982217235053651+0.270
meadow21.4%2542266139814077+0.171
field20.4%2664273238994167+0.155
water1.5%1884215637662504+0.081
ground19.7%3323320947154174+0.075
building10.1%2898303250423621+0.062
road11.2%3151313246573640+0.046

2. 핵심: 클래스가 스펙트럼상 3개 그룹으로 뭉친다

  • 식생 연속체: forest(0.270) → meadow(0.171) ≈ field(0.155). NDVI가 매끄러운 그라데이션이라 클래스 사이에 뚜렷한 경계가 없다.
  • 나지/불투수 덩어리: ground(0.075) ≈ building(0.062) ≈ road(0.046). RGB·NDVI가 거의 동일해서 스펙트럼만으로는 구분이 안 된다.
  • water: NIR이 2504로 유일하게 뚝 낮아서 스펙트럼상 홀로 분리된다 - 그래서 IoU가 0.789로 전체 클래스 중 가장 높다.

3. FP 픽셀은 정확히 두 클래스 "사이"에 위치한다 (재추론 없이 실측)

  • forest→meadow: 421만 px, 혼동 픽셀 NDVI +0.232 (forest 0.270과 meadow 0.171의 딱 중간)
  • meadow→field: 400만 px, 혼동 픽셀 NDVI +0.202 (meadow 0.171 ≈ field 0.155, 거의 동일한 값)
  • meadow↔ground: 670만 px, 혼동 픽셀 NDVI 0.10~0.13 (meadow 0.171과 ground 0.075의 중간)
  • road↔ground: 420만 px, 혼동 픽셀 NDVI 0.044~0.047 (road 0.046 ≈ ground 0.075, 사실상 구분 불가)

오분류가 무작위로 흩어져 있는 게 아니라, 스펙트럼상 두 클래스의 중간값에 정확히 몰려 있다는 뜻이다 - 모델의 "실수"가 아니라 애초에 스펙트럼만으로는 답이 없는 구간이라는 신호에 가깝다.

4. 클래스별 FP·Precision

클래스FP(px)Precision
meadow11.8M0.704 (최악)
ground7.4M0.799
field6.0M0.854
road4.3M0.798
forest3.2M0.886 (최소)
building2.9M0.854
water0.4M0.878

5. 클래스별 종합 지표 (IoU 내림차순)

클래스GT 비중IoUPrecisionRecallF1FP(M)FN(M)
water1.5%0.7890.8780.8860.8820.350.33
field20.4%0.7800.8540.9000.8765.963.87
building10.1%0.7660.8540.8820.8682.892.26
forest15.7%0.7480.8860.8270.8563.185.15
road11.2%0.6690.7980.8040.8014.294.13
ground19.7%0.6610.7990.7930.7967.437.72
meadow21.4%0.5370.7040.6930.69911.7712.41

meadow가 GT 비중은 가장 크면서(21.4%) IoU는 가장 낮다(0.537) - forest·field·ground 세 클래스 모두와 스펙트럼이 겹치는 "가운데 낀" 클래스이기 때문이다. water가 IoU 1위인 것과 정반대 이유다: water는 NIR 하나로 스펙트럼상 완전히 분리되고, meadow는 어느 그룹에도 확실히 속하지 못한다.

meadow 혼동을 픽셀 단위로 뜯어보면

meadow의 오분류는 특정 한 클래스가 아니라 "초록/자연 계열" 전반에 고르게 퍼져 있다.

방향대상비율
FN(놓침)→field10.3%
FN(놓침)→ground8.9%
FN(놓침)→forest6.9%
FP(오탐)forest→11.1%
FP(오탐)ground→8.1%
FP(오탐)field→7.4%

FN과 FP 비율이 거의 대칭이다 - 이건 "모델이 한쪽으로 치우쳐 틀린다"가 아니라 경계 자체가 모호해서 양방향으로 새는 패턴이다.

처음 시도한 "당연한" 해법은 실패했다

meadow가 최다 클래스인데도 성능이 제일 나쁘니, 직관적으로는 "meadow 클래스 가중치를 올리면 되지 않을까" 싶었다. 실제로 class weight를 3.0까지 올려서 학습해봤는데(별도 실험), 결과는 오히려 하락했다 - 최다 클래스에 가중치를 더 주니 FP만 폭증한 것이다. 픽셀 수 부족이 원인이 아니라는 게 이 실험으로도 재확인됐다.

더 근본적인 원인: 라벨 자체가 일관되지 않았다

FN·FP 대칭 패턴의 원인을 찾다가, 실제 GT(정답 라벨) 이미지를 여러 장 육안으로 대조해봤다. meadow(초지)와 field(밭)가 원본 영상에서는 텍스처가 똑같은데 라벨만 갈려 있는 경우가 다수 확인됐다 - 인접한 두 밭 중 하나는 meadow로, 다른 하나는 field로 라벨링돼 있는 식이다. 라벨 작업자마다 기준이 달랐다는 뜻이다.

이건 손실 함수나 모델 구조로 해결할 수 있는 문제가 아니다 - 정답 자체가 일관되지 않으면 모델이 배울 일관된 패턴이 없다. 실제로 서로 다른 구조(백본 교체, dual 구성 등) 여러 개를 시도해봐도 meadow는 전부 0.530.54 부근에서 막혔는데, 이 천장이 모델 성능이 아니라 라벨 노이즈 자체에서 비롯된다는 걸 보여주는 정황이다. KOMPSAT 검증셋 전체를 봐도 0.710.73 부근에서 여러 모델이 동시에 정체되는 현상이 있었고, 라벨을 직접 확인해보면 실제로 다른 클래스가 정답으로 보이는 경우(예: 논을 ground로 라벨링했는데 모델이 field로 예측한 게 오히려 맞아 보이는 경우)가 있었다 - 검증 지표 자체에 라벨 노이즈로 인한 천장이 있다는 뜻이다.

다음 단계

이 진단으로 확인한 건 두 가지다. 하나는 meadow의 낮은 성능이 스펙트럼 중첩 때문이라는 것, 다른 하나는 그보다 더 근본적으로 라벨 자체의 일관성 문제가 깔려 있다는 것이다. 우선순위는 다음과 같이 잡았다.

  1. NIR 채널 추가(모델적 1순위): 가장 큰 혼동원인 forest(교목은 NIR 반사율이 뚜렷이 높다)와의 분리부터 기대해본다.
  2. 손실 함수 조합: Tversky(C2)로 meadow 쪽 recall을 강조하거나, Margin-CE(C7)로 forest/meadow/ground 사이에 완충 지대를 만드는 조합을 B1 기준선 위에 순서대로 얹어본다.
  3. 라벨 정제: 가장 근본적이지만 가장 손이 많이 가는 방법이다 - meadow/field 경계를 재라벨링하는 작업이 어쩌면 가장 큰 지렛대일 수 있다는 걸 이번 진단으로 확인했다.

class weight를 올리는 손쉬운 방법은 이미 실패로 확인됐으니, 다음 글에서는 C2·C7·C8 순으로 실험해 meadow IoU가 실제로 오르는지, 그리고 그 개선이 라벨 노이즈 천장 안에서 의미가 있는 수준인지를 함께 확인한다.