REMOTE SENSING

PyTorch: TorchGeo를 이용한 지리공간 딥러닝 (1)

유병혁 2023. 1. 11. 13:06

안녕하세요? 이번 글은 시리즈 글의 첫번째로 'PyTorch의 TorchGeo를 이용한 지리공간 딥러닝' 실습을 다뤄보겠습니다. 이 글은 지리공간/금융 데이터 사이언티스트 Maurício Cordeiro(마우리시오 코르데이로) 님이 작성하신 'PyTorch의 TorchGeo를 이용한 지리공간 분석용 인공지능(1부)' 내용을 정리 및 보완한 것입니다. 원본 글은 다음 링크를 참고하시면 됩니다. 좋은 글을 공유해주신 마우리시오 코르데이로 님께 감사의 마음을 전합니다.

[1] Artificial Intelligence for Geospatial Analysis with Pytorch’s TorchGeo (Part 1)
[2] Artificial Intelligence for Geospatial Analysis with Pytorch’s TorchGeo (Part 2)
[3] Artificial Intelligence for Geospatial Analysis with Pytorch’s TorchGeo (Part 3)

 

Artificial Intelligence for Geospatial Analysis with Pytorch’s TorchGeo (Part 1)

An end-to-end deep learning geospatial segmentation project using Pytorch and TorchGeo packages

towardsdatascience.com

 

PyTorch의 TorchGeo

대부분의 딥러닝 프레임워크는 RGB 이미지용으로 개발되었고 지리공간 데이터의 특수성(예: 다중분광 밴드, 좌표계)을 고려하지 않습니다. 따라서 지리공간 딥러닝은 추가적인 복잡성을 가지게 됩니다.

 

TorchGeo(토치지오)는 지리공간 데이터에 특화된 데이터셋, 샘플러, 변환 및 사전 훈련된 모델을 제공하는 PyTorch(파이토치)의 도메인(영역) 라이브러리입니다. TorchGeo는 지리공간 데이터에서 딥러닝 모델을 쉽게 사용할 수 있도록 합니다. 또한 다중분광 위성 이미지를 위한 사전 훈련된 모델(예: Sentinel-2 위성의 전체 밴드를 사용하는 모델)을 제공하는 최초의 라이브러리이기도 합니다. PyTorch 공식 홈페이지의 소개 글은 아래 링크를 참고하시면 됩니다.

 

Geospatial deep learning with TorchGeo

TorchGeo is a PyTorch domain library providing datasets, samplers, transforms, and pre-trained models specific to geospatial data.

pytorch.org

TorchGeo는 오픈소스이므로 GitHub에서도 확인하실 수 있습니다.

 

 

TorchGeo 이해에 도움이 되는 'TorchGeo: Deep Learning with Geospatial Data' 주제 슬라이드/비디오를 아래 링크에서 보실 수 있으니 참고해 보시면 좋겠습니다(저도 TorchGeo 사용자 관점에서 콘텐츠를 계속 공유해 보겠습니다).

PyTorch의 지리공간 딥러닝 특화 라이브러리, 'TorchGeo' 소개 슬라이드/비디오입니다. pdf와 mp4 포맷으로 내려받으실 수 있습니다. 2022년 3월 15일 발표자료이며 발표자는 일리노이대 컴퓨터과학과 New Frontiers Fellow이자 TorchGeo 최대 커미터, Adam Stewart(애덤 스튜어트) 님입니다.

 

TorchGeo: Deep Learning with Geospatial Data – New Frontiers Initiative

Presenter: Adam Stewart, New Frontiers Fellow, Department of Computer Science, University of Illinois Tuesday, March 15, 2022 Slides: https://uofi.app.box.com/s/3miol5hv9l9d08r5yn5jszdt3tl7mq59 Video: https://uofi.app.box.com/s/nc64gqjy46s72geerrifmymdex

newfrontiers.illinois.edu

Environment

일단 실습 환경을 준비해 봅니다. 여기서는 기본 라이브러리가 사전 설치되어 있고 무료 GPU도 제공하는 Google Colab을 사용하겠습니다(이 글은 Colab 실습코드 링크를 포함하고 있습니다). 물론 PC에서도 실습이 가능하지만, 이를 위해서는 PyTorch, GDAL과 같은 의존성 라이브러리를 먼저 설치해야 하며 GPU와 CUDA 드라이버도 작동해야 합니다. 앞으로 실습은 되도록 Colab의 편의성을 적극 활용하겠습니다.

 

Google Colab

Google Colab(구글 코랩)은 Google 리서치팀에서 개발한 제품입니다. Colab은 호스팅된 Jupyter Notebook 서비스로, 누구나 브라우저를 통해 임의의 Python 코드를 작성하고 실행할 수 있으며 GPU를 포함한 컴퓨팅 리소스를 무료로 사용할 수 있습니다. Google Colab 공식 홈페이지와 소개 글은 다음 링크를 참고해 보시면 좋겠습니다. *한빛미디어의 글은 특히나 유익한 것 같습니다.

 

Google Colaboratory

 

colab.research.google.com

 

구글 코랩(Google Colab)으로 머신러닝 프로그램 돌리기 : github 예제 有

구글 코랩은 웹 브라우저에서 텍스트와 프로그램 코드를 자유롭게 작성할 수 있는 일종의 온라인 텍스트 에디터 입니다. 온라인 에디터 역할 외에도 내 느린 컴퓨터 대신에 CPU와 램을 제공해줍

hongong.hanbit.co.kr

Colab에서 프로젝트에 필요한 rasteriotorchgeo 패키지를 설치합니다. pip의 명령에서 -q는 출력을 줄이는 옵션입니다.

  • rasterio(래스터리오): 지리공간 래스터 데이터(geospatial raster data)의 입출력 지원
  • torchgeo(토치지오): 지리공간 데이터(geospatial data)에 특화된 PyTorch 도메인(영역) 라이브러리
# rasterio와 torchgeo 패키지 설치
!pip install rasterio -q
!pip install torchgeo -q

출력에서 torch 버전과 관련된 오류를 경험하실 수 있는데요, 패키지 업그레이드 관련해서는 아래 글에서 정리하겠습니다.

output:
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
torchtext 0.14.0 requires torch==1.13.0, but you have torch 1.12.1 which is incompatible.
torchaudio 0.13.0+cu116 requires torch==1.13.0, but you have torch 1.12.1 which is incompatible.

패키지를 불러와 버전을 확인해 볼까요? rasterio는 1.3.4(gdal 3.5.3), torchgeo는 0.3.1(torch 1.12.1) 버전임을 확인합니다.

# 패키지 불러오기
import rasterio as rio
import torchgeo

# 패키지 버전확인
print(rio.__version__)
print(rio.__gdal_version__)
print(torchgeo.__version__)
output:
1.3.4
3.5.3
0.3.1

아래는 rasterio의 경고 로깅 메시지를 무시하는 코드로, 실습에는 영향을 미치지 않습니다.

import logging
logger = logging.getLogger("rasterio")
logger.setLevel(logging.ERROR) # ERROR보다 낮은 로깅 메시지 무시

참고로, Logging Levels(로깅 레벨)은 다음과 같습니다.

지표수(Earth Surface Water) 데이터셋

지표수 데이터셋(CC BY 4.0 라이선스)은 세계 여러 지역의 이미지 패치와 워터 마스크를 제공합니다. 데이터셋은 공간해상도가 10m인 Sentinel-2(센티널 2호) 위성의 광학 영상을 사용합니다. 이 데이터셋의 또다른 장점은 결과를 비교할 수 있는 성능 벤치마크가 있다는 것입니다. 데이터셋과 관련해서는 아래 논문을 참고하시면 됩니다.

Luo, X., Tong, X., & Hu, Z. (2021). An applicable and automatic method for earth surface water mapping based on multispectral images. International Journal of Applied Earth Observation and Geoinformation, 103, 102472.

이미지 출처: https://github.com/xinluo2018/WatNet

 

wget과 unzip 명령어를 사용하여 데이터셋(dset-s2.zip)을 Colab에 직접 다운로드하고 압축을 해제할 수 있습니다.

# 지표수 데이터셋 다운로드 및 압축 해제
!wget https://zenodo.org/record/5205674/files/dset-s2.zip
!unzip dset-s2.zip

저는 데이터셋을 Google 드라이브에 업로드한 상태입니다. 드라이브 엑세스 후 데이터셋을 압축 해제하겠습니다.

# Google 드라이브 엑세스 후 데이터셋 압축 해제
from google.colab import drive
drive.mount('/gdrive', force_remount=True)
!unzip -qq "/gdrive/My Drive/GEODATA/dset-s2.zip"

압축을 풀면 데이터셋은 다음과 같은 구조를 가지고 있습니다. tra는 훈련셋(training set)을 나타내고 val은 검증셋(validation set)을 나타냅니다. 접미사 scene과 truth는 이미지 패치와 워터 마스크(ground truth)를 구분하기 위한 것입니다.

참고로 기계학습 문제에서 훈련 데이터는 매개변수(가중치와 편향)의 학습에 이용하고, 검증 데이터는 하이퍼파라미터의 성능을 평가하는데 이용합니다. 시험 데이터는 범용 성능을 확인하기 위해서 마지막에 이용합니다(밑바닥부터 시작하는 딥러닝 222p 발췌).

  • 훈련 데이터: 매개변수 학습
  • 검증 데이터: 하이퍼파라미터 성능 평가
  • 시험 데이터: 신경망의 범용 성능 평가

샘플을 열기 위해 훈련 이미지(tra_scene), 훈련 마스크(tra_truth)를 리스트에 로드(각 64장으로 확인)합니다. pathlib 패키지는 경로를 문자열이 아닌 객체로 다룹니다. 아래 코드는 'dset-s2' 경로가 존재하는지 확인하고, 이것이 True가 아니면 AssertError를 발생합니다.

from pathlib import  Path

root = Path('dset-s2')
assert root.exists()

train_imgs = list((root/'tra_scene').glob('*.tif'))
train_masks = list((root/'tra_truth').glob('*.tif'))
print(len(train_imgs))  # 훈련 이미지(tra_scene) 수
print(len(train_masks)) # 훈련 마스크(tra_truth) 수
outout:
64
64

훈련 이미지 / 훈련 마스크는 이름별로 일치하므로, 리스트를 정렬하여 동기화 상태를 유지합니다.

# 이미지/마스크 정렬, 동기화 유지
train_imgs.sort(); train_masks.sort()

for i in range(0, 5):
    print(train_imgs[i])
    print(train_masks[i])
output:
dset-s2/tra_scene/S2A_L2A_20190125_N0211_R034_6Bands_S1.tif
dset-s2/tra_truth/S2A_L2A_20190125_N0211_R034_S1_Truth.tif
dset-s2/tra_scene/S2A_L2A_20190125_N0211_R034_6Bands_S2.tif
dset-s2/tra_truth/S2A_L2A_20190125_N0211_R034_S2_Truth.tif
dset-s2/tra_scene/S2A_L2A_20190125_N0211_R034_6Bands_S3.tif
dset-s2/tra_truth/S2A_L2A_20190125_N0211_R034_S3_Truth.tif
dset-s2/tra_scene/S2A_L2A_20190206_N0211_R067_6Bands_S1.tif
dset-s2/tra_truth/S2A_L2A_20190206_N0211_R067_S1_Truth.tif
dset-s2/tra_scene/S2A_L2A_20190206_N0211_R067_6Bands_S2.tif
dset-s2/tra_truth/S2A_L2A_20190206_N0211_R067_S2_Truth.tif

Xarray를 사용하여 인덱스를 열어 보겠습니다.

import xarray as xr

# 0번째 인덱스 열기
idx = 0
img = xr.open_rasterio(train_imgs[idx])
mask = xr.open_rasterio(train_masks[idx])

자, 다음 코드는 Matplotlib 패키지를 사용할 텐데요, Colab은 현재 Matplotlib 3.2.2 버전을 제공하고 있습니다. 이것은 구 버전으로 실습과정에서 다음과 같은 오류를 경험하실 수 있습니다. 따라서 패키지 업그레이드가 필요합니다.

output:
ImportError: cannot import name '_png' from 'matplotlib' (/usr/local/lib/python3.8/dist-packages/matplotlib/__init__.py)

아래와 같이 Matplotlib을 최신 버전(3.6.2)로 업그레이드해 주시면 됩니다. '런타임 > 런타임 다시 시작'을 적용한 후, 데이터셋 압축 해제 이후부터 실습을 재개해 주시면 됩니다.

# matplotlib 업그레이드
!pip install --upgrade matplotlib -q

이미지/마스크 결과를 출력합니다. 반가운 그림이죠?!

import matplotlib.pyplot as plt

_, axs = plt.subplots(1, 2, figsize=(15, 6))

# 이미지 플롯
rgb = img.data[[2, 1, 0]].transpose((1, 2, 0))/3000
axs[0].imshow(rgb.clip(min=0, max=1))

# 마스크 플롯
axs[1].imshow(mask.data.squeeze(), cmap='Blues')

RasterDataset 객체
압축 해제된 원본 데이터셋이 있으므로 신경망(neural network)에 로드할 준비를 할 수 있습니다. 이에 앞서 Sentinel-2 위성 이미지의 화소 값을 반사율 값으로 변환하는 식을 다음 함수로 정의합니다.

# REFLECTANCE(반사율) = DN(화소) / 10000 
def scale(item: dict):
    item['image'] = item['image'] / 10000
    return item

TorchGeo에서 제공하는 RasterDataset 클래스의 인스턴스(객체)를 생성하고 특정 디렉토리를 가리킵니다.

from torchgeo.datasets import RasterDataset, unbind_samples, stack_samples

# 훈련 이미지
train_imgs = RasterDataset(root=(root/'tra_scene').as_posix(), crs='epsg:3395', res=10, transforms=scale)

 

RasterDataset의 파라미터 중 root는 데이터셋의 루트 디렉토리, crs는 변환할 좌표계(CRS: coordinate reference system), res는 CRS 단위의 데이터셋 해상도, transforms는 입력 샘플을 가져와 변환된 버전을 반환하는 함수/반환(function/transform)을 지칭합니다. *.as_posix()의 역할은 아래 이미지를 참고하시면 됩니다.

CRS(Coordinate Reference System)는 EPSG:3395로 지정하고 있습니다. TorchGeo는 모든 이미지가 동일한 CRS에 로드되도록 요구합니다. 그러나 데이터셋의 패치들은 다양한 UTM 투영이 있으며 TorchGeo의 기본 동작은 탐지된 첫번째 CRS를 기본값으로 사용하는 것입니다. 이 경우, 전세계 다양한 지역에 대처할 수 있는 CRS에 알려야 합니다. 패치 내에서 위도의 큰 차이로 인한 변형을 최소화하기 위해 프로젝트의 기본 CRS로 World Mercator를 선택했습니다.

 

EPSG.io: Coordinate Systems Worldwide

EPSG.io: Coordinate systems worldwide (EPSG/ESRI), preview location on a map, get transformation, WKT, OGC GML, Proj.4. https://EPSG.io/ made by @klokantech

epsg.io

Sampler 객체

데이터셋에서 신경망에 공급할 수 있는 훈련 패치를 만들려면 고정 크기의 샘플을 선택해야 합니다. Samplers(샘플러)는 데이터셋을 인덱싱하고 한번에 하나의 쿼리를 검색하는데 사용됩니다. NonGeoDataset의 경우 데이터셋 객체를 정수로 인덱싱할 수 있으며 PyTorch의 내장 샘플러로도 충분합니다. GeoDataset의 경우 데이터셋 객체에는 인덱싱을 위한 경계 상자(bounding box)가 필요합니다. 이러한 이유로, 자체 GeoSampler 구현을 정의합니다. TorchGeo에는 많은 샘플러가 있지만, 여기서는 RandomGeoSampler 클래스를 사용해 보겠습니다.

기본적으로 샘플러는 원본 이미지에 속하는 고정 크기의 임의 경계 상자를 선택합니다. 그런 다음 이러한 경계 상자는 RasterDataset에서 우리가 원하는 이미지 부분을 쿼리하는데 사용됩니다. 샘플러를 만들려면, 다음 줄만 입력하면 됩니다. 이 샘플러는 512x512 픽셀 이미지(size)를 반환하고 에폭 길이(length)는 100입니다.

from torchgeo.samplers import RandomGeoSampler

# 훈련 이미지 샘플러
train_sampler = RandomGeoSampler(train_imgs, size=(512, 512), length=100)

size(크기)는 우리가 원하는 훈련 패치의 모양이고 length(길이)는 이 샘플러가 1에폭(epoch)으로 제공할 패치의 수입니다.

참고로, 에폭(epoch)은 하나의 단위입니다. 1에폭은 학습에서 훈련 데이터를 모두 소진했을 때의 횟수에 해당합니다. 예컨대 훈련 데이터 10,000개를 100개의 미니배치로 학습할 경우, 확률적 경사 하강법을 100회 한복하면 모든 훈련 데이터를 '소진'한 게 됩니다. 이 경우 100회가 1에폭이 됩니다.

 

기계학습 문제는 훈련 데이터를 사용해 학습합니다. 더 구체적으로 말하면 훈련 데이터에 대한 손실 함수의 값을 구하고, 그 값을 최대한 줄여주는 매개변수를 찾아냅니다.

 

이렇게 하려면 모든 훈련 데이터를 대상으로 손실 함수의 값을 구해야 합니다. 많은 데이터를 대상으로 일일이 손실 함수를 구하는 것은 현실적이지 않습니다. 이런 경우 데이터의 일부를 추려 전체의 '근사치'로 이용할 수 있습니다. 신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습하는데 이 일부를 미니배치(mini-batch)라고 합니다. 가령 60,000장의 훈련 데이터 중에서 100장을 무작위로 뽑아 그 100장만을 사용하여 학습하는 것입니다. 이러한 학습 방법을 미니배치 학습이라고 합니다. *배치(batch)는 하나로 묶음 입력 데이터를 지칭합니다. 이미지가 지폐처럼 다발로 묶여 있다고 생각하면 됩니다(밑바닥부터 시작하는 딥러닝 103p 발췌).

 

결과는 image, crs, 그리고 bbox 항목이 잇는 딕셔너리(dictionary)가 됩니다.

import torch 

# 랜덤 시드 고정
torch.manual_seed(0)

bbox = next(iter(train_sampler))
sample = train_imgs[bbox]
print(sample.keys())
print(sample['image'].shape)
output:
dict_keys(['image', 'crs', 'bbox'])
torch.Size([6, 512, 512])

이미지의 형태(shape)는 (6, 512, 512)입니다. 샘플러에 대해 지정한 대로 6개 분광 채널(spectral channels)과 512 행(rows)과 512 열(columns)의 크기(size)임을 뜻하니다. 데이터셋 설명에 따르면 6개 채널은 (순서대로) Blue, Green, Red, NIR, Swir1, Swir2입니다. Matplotlib은 형태(높이, 너비, 채널)의 배열을 예상하며 채널은 R, G, B(순서대로)입니다. transpose 명령어를 사용하여 차원(축)의 순서를 변경합니다.

arr = torch.clamp(sample['image'], min=0, max=1).numpy()
rgb = arr.transpose(2, 1, 0)[:, :, [2, 1, 0]] # Natural Color

plt.imshow(rgb*3)

torch.clamp는 input 내 모든 요소를 [min, max] 범위로 고정합니다. Band RGB Composites(밴드 RGB 합성)는 Natural Color(자연색)에 해당하는 [2, 1, 0], 즉 [Red, Green, Blue]입니다.

Color Infrared의 경우에는 Band RGB Composites를 [3, 2, 1], 즉 [NIR, Red, Green]로 변경해 주시면 됩니다.

arr = torch.clamp(sample['image'], min=0, max=1).numpy()
rgb = arr.transpose(2, 1, 0)[:, :, [3, 2, 1]] # Color Infrared

plt.imshow(rgb*3)

이번 글은 TorchGeo에서 RasterDataset과 Sampler 사용법을 위주로 학습해 봤습니다. 다음 글에서는 마스크에 대한 RasterDataset을 생성하고 이미지/마스크를 IntersectionDataset으로 결합하는 방법을 학습해 보겠습니다.

 

PyTorch-TorchGeo를_이용한_지리공간 딥러닝_1.ipynb

Colaboratory notebook

colab.research.google.com