이 레포가 무엇을 하는 물건인가.
세계 각국의 위성·드론은 카메라 종류도, 찍는 해상도도, 색(밴드) 개수도 다 다르다. 보통 AI는 입력 형식이 고정이라 센서마다 따로 학습해야 하는데, UniverSat은 어떤 콘센트든 꽂히는 멀티 어댑터처럼 그 모든 형식을 하나의 모델로 받아들인다.
UniverSat의 정식 소개는 "Resolution- and Modality-Agnostic Transformers for Earth Observation" — 우리말로 풀면 "해상도와 센서 종류를 가리지 않는, 지구관측용 트랜스포머"다. 광학(일반 카메라 사진)·레이더(SAR)·초분광(수백 개 색 밴드)·고도(높낮이 지도)를 단 하나의 가중치 세트로 처리하고, 결과물의 공간 해상도(촘촘함)를 실행하는 순간에 자유롭게 지정할 수 있다.
이게 풀려는 문제는 분명하다. 지구관측 데이터는 ① 센서 종류가 다양하고, ② 한 픽셀이 담는 실제 거리(해상도)가 cm부터 수백 m까지 천차만별이고, ③ 색 밴드가 1개부터 396개까지 제각각이며, ④ 같은 곳을 1번만 찍기도, 1년에 150번 찍기도 한다. 기존 모델은 이런 다양성을 못 받아들여 센서마다 새 모델을 만들어야 했다. UniverSat은 그 벽을 허문다.
트렌딩 이유 · 경쟁 모델 대비 장점.
한마디로 "위성 AI의 만능 백본"을 표방하기 때문이다. 보통의 ViT(비전 트랜스포머)는 입력이 고정(정해진 색 채널 수, 정해진 크기·해상도)인데, 지구관측 데이터는 정반대다. UniverSat은 임의 형태의 패치를 공통 임베딩으로 사상하는 Universal Patch Encoder(UPE) 하나로 이 다양성을 흡수한다. 그 결과 네 가지가 한꺼번에 가능해진다.
| 항목 | 기존 EO 모델 | UniverSat |
|---|---|---|
| 센서 추가 | 새 센서마다 인코더 재학습/적응 | 가중치 하나로 여러 센서 조합 처리 |
| 출력 해상도 | 학습 시 고정 | 추론 때 거친 맵·원해상도·픽셀 단위 자유 선택 |
| 미세 디테일 | 패치 단위에서 뭉개짐 | 서브패치 skip으로 밭 경계·도로 보존 |
| 처음 보는 센서 | 거의 불가(재학습 필요) | 파장·해상도 메타데이터만 주면 재학습 없이 처리 |
| 다운스트림 비용 | 수천만 파라미터 디코더 필요 | 9K 파라미터 linear probe로 동급 성능 |
특히 인상적인 두 가지: 첫째, 사전학습에 전혀 쓰지 않은 초분광 센서(EnMAP)에 대해서도, 그 센서로 학습한 전용 모델(DOFA-L)을 모든 과제에서 앞섰다 — "처음 보는 데이터에 강하다"는 일반화의 증거다. 둘째, 백본을 freeze하고 위에 고작 9,000개 파라미터짜리 선형 분류기만 얹어도, 3천만~5천만 파라미터 디코더를 쓴 경쟁작과 맞먹거나 이긴다. "거대한 헤드 없이도 표현이 좋다"는 뜻이라 실무·연구 양쪽에서 화제가 됐다.
위성 종류가 늘수록 모델도 늘어난다. 광학용 따로, 레이더용 따로, 초분광용 따로… 해상도가 바뀌면 또 따로. 데이터를 억지로 리샘플링하거나 채널을 잘라 맞추다 보면 정보가 손실되고, 새 위성이 뜰 때마다 처음부터 다시 학습해야 한다.
UniverSat은 픽셀이 "어디"에 있는지뿐 아니라 "무엇"인지(파장·편파·연중 시점·실제 해상도)까지 모델에 함께 알려준다. 그래서 처음 보는 센서도 "이건 파장 0.86µm짜리 밴드야"라고 설명해주면 알아듣는다. 센서마다 새 모델을 만들 필요가 사라진다.
추론 경로 · 학습 경로 · 데이터 I/O 한눈에.
이 저장소의 영리한 설계 하나: 추론(쓰기)과 학습(만들기)의 의존성을 분리했다. 모델을 불러와 쓰는 데는 사실상 torch 하나면 되고, 무거운 학습 도구(Hydra·Lightning 등)는 학습할 때만 등장한다. 그래서 "그냥 feature만 뽑아 쓰고 싶은" 사람과 "처음부터 학습하려는" 사람의 길이 갈린다.
| 레이어 | 기술 / 버전 | 역할 |
|---|---|---|
| 언어·런타임 | Python 3.10+ | 전체 코드. |
| 추론(경량) | PyTorch ≥2.2 + huggingface_hub + safetensors | 모델 빌드·가중치 로드. hubconf.py는 Hydra 없이 torch만으로 동작. |
| 학습 프레임워크 | PyTorch Lightning ≥2.2 + torchmetrics | 학습 루프·분산학습(DDP)·정밀도 관리. |
| 설정 관리 | Hydra 1.3.2 + OmegaConf | configs/의 YAML을 합성해 실험을 조립(단일 진실원). |
| 모델 빌딩 블록 | timm · einops | 트랜스포머 블록 차용 · 텐서 축 재배열. |
| 스케줄러·로깅 | lightning-bolts · wandb · rich · tqdm | 워밍업+코사인 스케줄 · 실험 추적 · 진행 표시. |
| 지리공간 I/O | rasterio · tifffile · h5py · geopandas · tfrecord · HF datasets | GeoTIFF·HDF5·TFRecord 등 이질적 위성 포맷 읽기. |
| 수치·평가 | numpy · scipy · scikit-learn · matplotlib | linear/kNN probe · PCA 시각화. |
| 인프라 | Lightning DDP · 기본 bf16 · SLURM 제출 | 멀티GPU 학습(논문은 H100). A100/V100/CPU/MPS 프로파일 제공. |
pickle과 달리 임의 코드 실행 위험이 없어 공개 모델 배포의 표준으로 자리 잡았다. UniverSat의 Base 모델도 이 형식으로 HuggingFace에 올라가 있다.위성 패치 한 장이 들어오면 어디서 어디로 흐르는가.
먼저 독수리 시점. UniverSat의 forward(순전파)는 딱 두 단계다 — ① UPE(Universal Patch Encoder)가 제각각인 입력을 공통 토큰으로 만들고, ② Trunk(ViT 본체)가 그 토큰들을 섞어 최종 dense feature를 낸다. 핵심 파일은 src/models/networks/encoder/UniverSat.py와 UniversalPatchEncoder.py다.
이제 대표 흐름 한 줄기를 따라가자 — "광학+레이더+고도를 섞어 넣어 feature를 뽑는다". 코드 한 줄로 보면 이렇다:
# demo.ipynb / hubconf.py — 여러 센서를 dict로 섞어 한 번에 인코딩
model = UniverSat.from_pretrained("g-astruc/UniverSat").eval()
features, _ = model.encode(data, patch_size=40, output_grid=36)
# data = {"s2": ..., "s1": ..., "dsm": ...} → features: (B, 36*36, 768)
여기서 가장 중요한 부분은 UPE가 "축을 하나씩 무너뜨리며" 토큰을 만드는 방식이다. 입력은 채널 × 시간 × 세로 × 가로의 4차원인데, 이걸 한꺼번에 어텐션하면 계산량이 폭발한다(O(N²)). 그래서 Axial Cross-Attention(ACA)으로 한 번에 한 축만 어텐션해 차례차례 압축한다 — 픽셀 축 → 채널 축 → 시간 축 → 패치 축 순서로.
이 프로젝트 특유의 관례 하나: 거의 모든 정규화가 RMSNorm으로 통일돼 있고, 위치 정보는 RoPE(회전 위치 임베딩)로 주입한다. 또 채널(색 밴드)을 다룰 때, 파장이 숫자로 주어지면 MPFourier로 연속값을 임베딩하고, 레이더처럼 파장 개념이 없으면(VV·VH 같은 편파 코드) 학습된 per-channel 임베딩으로 분기한다 — "센서가 무엇이든 받아들이는" 비결이 바로 이 분기다.
학습 쪽(SSL, 자기지도학습)은 더 복잡하다. 입력의 90% 가까이를 가린 뒤(RandomMasker), 가려진 부분을 예측하게 만든다. 손실은 두 가지를 합친다: LM³(가려진 패치의 잠재 표현을 맞히되, frozen 랜덤 projection을 타겟으로 써서 표현이 한 점으로 붕괴하는 걸 막음) + cross-modal contrast(같은 땅을 본 다른 센서의 표현을 가깝게 당김 → 모달리티 불변 표현 유도).
어디부터 열어봐야 하나.
크게 세 덩어리로 보면 길이 보인다. ① 루트의 추론 진입점(hubconf.py 등) ② 설정의 단일 진실원 configs/ ③ 실제 코드 src/. 추론만 할 거면 루트 파일 3개만 봐도 된다.
| 위치 | 역할 |
|---|---|
hubconf.py | 모든 추론의 출발점. encode()가 여러 센서 dict를 받아 dense feature를 돌려준다. Hydra·Lightning 없이 torch만으로 동작하도록 분리. |
modality_registry.py | 지원 센서의 파장·해상도·서브패치 정보 표. configs/.../sensor/default.yaml에서 자동 생성된 스냅샷이라 추론 시 PyYAML도 필요 없다. |
UniversalPatchEncoder.py | 이 프로젝트의 심장. 임의 모양 패치를 축별 어텐션으로 공통 토큰화. 가장 먼저 정독할 파일. |
configs/ | 학습·평가의 모든 설정. "무엇을 바꿀 수 있나"의 목록. Base/Tiny 모델 정의도 여기. |
src/models/losses.py | 자기지도학습 손실(LM³ + contrastive). 표현 붕괴를 막는 핵심 기교가 담김. |
src/data/ | FLAIR·Pastis·Planted 등 13개 위성 데이터셋 로더. 이질적 포맷을 통일하는 법의 표본. |
이 레포에서 배울 만한 것 + 어느 파일을 펴면 되는지.
가로·세로·채널·시간이 한꺼번에 얽힌 텐서를 통째로 어텐션하면 비용이 제곱으로 폭발한다. UniversalPatchEncoder.py의 Axial Cross-Attention은 한 번에 한 축만 어텐션해 선형으로 떨어뜨린다. mean·max 풀링을 query로 써서 축을 압축하는 트릭(ACAttention.get_qkv)이 백미다. "큰 스프레드시트를 행 요약 → 열 요약으로 나눠 줄이는" 발상.
보통 위치 인코딩은 좌표(어디)만 다룬다. UniverSat은 파장·편파·연중 시점·실제 해상도(무엇)까지 인코딩한다. 연속값(파장)은 MPFourier로, 이산 코드(VV/VH)는 학습 임베딩으로 분기하는 패턴(patch_embeddings.py)이 "처음 보는 센서 일반화"의 비결이다. RoPE 1D/2D 구현도 함께 익힌다.
가린 입력을 맞히는 마스크 학습(MAE/JEPA 계열)에서 흔한 사고는 모델이 "다 똑같다"고 답해버리는 표현 붕괴(collapse)다. losses.py의 LM³은 frozen 랜덤 projection을 타겟으로 삼고 InfoNCE로 예측하게 해 이를 막는다. cross-modal contrast와 어떻게 결합하는지가 핵심 학습거리.
hubconf.py는 Hydra·Lightning·einops를 import하지 않고 torch만으로 모델을 빌드한다. YAML 설정을 modality_registry.py 파이썬 스냅샷으로 자동 생성해 추론 시 PyYAML 의존도 없앴다. "연구 코드를 어떻게 프로덕션에서 가볍게 쓰게 만드나"의 교과서적 사례.
configs/는 lightning-hydra-template 구조를 따라 데이터·모델·트레이너·손실을 YAML 조각으로 쪼갰다. 멀티 데이터셋 가중 샘플링, DDP 타임아웃 처리, 워밍업+코사인 스케줄을 실제 코드로 본다. 명령줄에서 model/network/encoder=UniverSat_Tiny처럼 부품만 바꿔 끼우는 감각을 익힐 수 있다.
예컨대 처음 보는 카메라(레지스트리에 없는 4밴드 센서)를 쓰려면, 파장·해상도·서브패치 정보만 넘기면 된다 — 재학습 0줄:
# 레지스트리에 없는 센서도 메타데이터만 주면 인코딩 (demo.ipynb 패턴)
features, _ = model.encode(
{"my_cam": tensor},
wavelengths={"my_cam": [0.49, 0.56, 0.66, 0.83]}, # 밴드별 파장(µm)
input_res={"my_cam": 3.0}, # 한 픽셀이 담는 거리(m)
output_grid=36)
추론은 가볍고, 학습은 무겁다.
좋은 소식: 그냥 feature만 뽑는 추론은 노트북에서도 돈다. 나쁜 소식: 처음부터 사전학습하려면 H100급 멀티GPU와 대용량 데이터가 필요하다. 두 길의 요구사항이 완전히 다르다.
| 용도 | 요구사항 |
|---|---|
| 추론(모델 빌드) | torch만. 작은 입력은 CPU도 가능(demo는 합성 텐서로 CPU 실행). |
| 추론(가중치 로드) | + huggingface_hub·safetensors. Base ~201M → fp32 약 0.8GB. GPU 권장. |
| 사전학습(논문) | H100 GPU 명시. 멀티GPU 예시 devices=8 max_epochs=200, 기본 bf16. |
| 데이터 용량 | 7개 EO 데이터셋(FLAIR·PASTIS·TreeSat·Planted·S2NAIP·HyperGlobal·EarthView) — 각 수십~수백 GB. 직접 받아야 함. |
| probe 평가 | 비교적 가벼움. linear/kNN probe는 lr×wd 그리드를 병렬 스윕. |
| 클러스터 | A100(fp32)·V100·CPU·MPS 프로파일 제공. SLURM 제출 지원. |
UniverSat은 "이미 졸업한 천재를 데려와 면접만 보는 것"과 같다. 그를 키우는 데(사전학습)는 엄청난 자원이 들었지만, 일단 공개된 Base 모델을 데려와 쓰는 데(추론·probe)는 책상 하나면 충분하다. 대부분의 사용자는 후자만 하면 된다.
읽기만 하지 말고 손에 익히는 단계.
Base 모델을 로드하고, 광학+레이더+고도를 섞은 입력을 넣어본다. output_grid를 9 → 36 → 180으로 바꿔가며 나오는 토큰 개수(grid²)가 어떻게 변하는지 관찰하는 게 목표. "해상도 분리"를 눈으로 확인하는 단계다.
modality_registry.list_modalities()로 지원 센서를 확인하고, Sentinel-2와 Landsat의 파장·해상도를 표로 비교해본다. 모델이 "센서를 어떻게 이해하는지"의 입력 데이터를 직접 들여다보는 연습.
rasterio로 위성 GeoTIFF를 읽어 dense feature를 뽑고, 그걸 PCA로 3차원(RGB)으로 줄여 이미지로 그려본다. 밭 경계·도로가 보존되는지 확인하면 "서브패치 skip"의 효과를 체감할 수 있다.
레지스트리에 없는 4밴드 카메라를 가정하고, wavelengths·input_res를 직접 지정해 encode()에 넘긴다. 재학습 없이 feature가 나오는 걸 확인하는, UniverSat의 정체성을 체험하는 실습.
백본을 freeze하고 model.encode() feature 위에 작은 선형 분류기만 학습해, 거대한 디코더 없이도 동급 성능이 나오는 README의 주장을 직접 재현한다. GeoBench의 작은 데이터셋(m-pv4ger 등)으로 시작 권장.
model/network/encoder=UniverSat_Tiny로 데이터 일부에 SSL 루프를 돌리고, masker/random.yaml의 fraction_drop_spatial 등을 바꿔 손실 곡선(LM³·contrast)이 어떻게 변하는지 관찰한다. SSL의 안정성/속도 감각을 기르는 단계.
한 주씩 따라가는 6주 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | ViT·지구관측 기본 개념 + demo로 첫 feature 뽑기 | README + demo.ipynb + 실습 1·2 |
| 2주차 | 어텐션/트랜스포머 복습 → Axial Attention 이해 | Attention 논문 · utils/utils_ViT.py · ACA 코드 |
| 3주차 | 위치 인코딩(RoPE)·푸리에 특징(MPFourier) | patch_embeddings.py · RoPE 해설 + 실습 4 |
| 4주차 | UPE 정독 — 축별 collapse·서브패치 skip | UniversalPatchEncoder.py · UniverSat.py |
| 5주차 | 자기지도학습(MAE/JEPA) + LM³·contrastive 손실 | losses.py · module_SSL.py · MAE/JEPA 논문 + 실습 6 |
| 6주차 | probe 평가·다운스트림 적용 + Hydra/Lightning 실험 | LP_eval.py · configs/ · GeoBench + 실습 5 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| EO (지구관측) | 위성·항공 센서로 지표를 촬영·측정한 데이터. |
| 모달리티 | 센서 종류. 광학·레이더(SAR)·초분광·고도(DSM). |
| UPE | Universal Patch Encoder. 임의 모양 패치를 공통 토큰으로 만드는 UniverSat의 핵심. |
| ACA | Axial Cross-Attention. 한 번에 한 축만 어텐션해 계산량을 선형으로 낮추는 기법. |
| GSD / 해상도 | 한 픽셀이 지표에서 차지하는 실제 거리(m/픽셀). |
| SAR | 합성개구레이더. 편파 채널 VV/VH로 표현(파장 개념 없음). |
| 초분광 | 수백 개의 좁은 색 밴드를 가진 영상(최대 396채널). |
| 서브패치 skip | 패치를 더 잘게 나눈 서브패치를 cross-attention으로 끌어와 미세 디테일을 복원하는 우회로. |
| output_grid | 최종 출력 토큰 격자의 한 변(토큰 수 = grid²). 추론 시 자유 지정. |
| MAE / JEPA | 가린 입력을 복원/예측하는 자기지도학습 계열. |
| LM³ | UniverSat의 SSL 손실. frozen 랜덤 projection 타겟을 InfoNCE로 예측해 표현 붕괴 방지. |
| InfoNCE / contrastive | 정답 쌍은 가깝게, 오답은 멀게 만드는 대조 손실. |
| RoPE | 회전 위치 임베딩. 상대 위치를 회전 변환으로 주입(1D 시간·2D 공간). |
| MPFourier | 스칼라(파장·좌표)를 학습형 푸리에 특징으로 lift하는 임베딩. |
| RMSNorm | 평균 재중심 없이 스케일만 맞추는 경량 정규화(LayerNorm 변형). |
| linear probe | 백본을 freeze하고 위에 선형 분류기만 학습해 표현 품질을 재는 평가법. |
| register token | ViT의 보조 토큰. artifact를 억제하는 용도. |
| Hydra / Lightning | 실험 설정 조립 도구 / PyTorch 학습 루프 뼈대. |