사람에 대한 Detection, Segmentation @A.I-TestBed

딥러닝 모델을 활용하여 어떤 이미지에서 사람의 위치를 찾아 내는 방식은 Detection과 Segmentation으로 분류할 수 있으며, Segmentation은 다시 Instance Segmentation과 Semantic Segmentation으로 나뉩니다.

Detection은 이미지에서 사람의 위치를 사각형 영역으로 잡아주는 방식이고 Segmentation은 이미지에서 사람에 해당하는 위치를 화소(Pixel)단위로 잡아줍니다. Segmentation 방식 중 Instance는 하나의 이미지에 여러명의 사람이 있다면 각 사람(각 Instance 별로)에 대해 분리해 픽셀 위치를 잡아주고, Semantic은 이미지에서 사람이라는 의미(Semantic)를 가지는 픽셀들을 잡아줍니다.

Segmentation 방식은 Detection 보다 학습과 메모리 소비가 훨씬 더 많이 소요되며, 훈련 데이터 중 레이블을 만들기가 훨씬 어렵습니다. Detection의 레이블은 사람에 해당하는 사각형 영역만을 지정하면 되지만, Segmentation의 레이블은 사람에 해당하는 픽셀을 모두 지정해줘야 하기 때문입니다.

Detection 방식의 모델은 매우 다양한데, 대표적으로 R-CNN 등이 있으며, Segmentation 방식의 모델에는 R-CNN을 통해 먼저 위치를 사각형 영역으로 잡고 다시 이 사각형 영역에 대해서 사람에 해당하는 픽셀을 잡는 Mask R-CNN이 있으며, 그냥 처음부터 이미지의 모든 픽셀에 대해 사람인지를 잡는 FCN 모델이 있습니다. Mask R-CNN 모델은 Instance Segmentation 방식이고 FCN 모델은 Semantic Segmentation 방식입니다. FCN 모델은 Semantic Segmentation 방식의 가장 기본이 되는 모델로 이 모델을 기본으로 Semantic Segmentation 방식을 더 개선한 다양한 모델이 파생되었습니다.

아래의 동영상은 머신러닝을 테스트하기 위한 TestBed 웹페이지로써 Detection과 Segmentation에 대한 기능을 보여줍니다.

참고로 위의 동영상에서 Detection과 Instance Segmentation의 결과에 대해 추론(hypothesis)값이 90% 이상으로 잡았습니다. 이 값을 좀더 내린다면 사람으로 잡지 못한 이미지의 부분에 대해서도 검출될 것입니다.

PyTorch를 이용한 간단한 머신러닝

머신러닝을 위한 라이브러리 중 파이토치를 이용한 기계학습을 정리해 봅니다. 학습의 주제는 손글씨로 써진 숫자 인식입니다. 먼저 학습을 위한 데이터가 필요한데요. MNIST 데이터를 사용합니다. MNIST는 아래의 그림처럼 테스트 데이터로 60,000개의 손글씨 이미지와 각 이미지에 해당하는 숫자가 무엇인지를 나타내는 60,000개의 라벨값이 있습니다. 또한 이 학습 데이터를 이용해 학습된 모델을 테스트하기 위한 테스트로 손글씨 이미지와 라벨 데이터가 각각 10,000개씩 존재합니다. 이미지 한장의 크기는 28×28 픽셀입니다.

코드는 PyTorch와 MNIST에서 숫자 이미지를 가져오기 위한 라이브러리를 사용하기 위해 아래처럼 import 문으로 시작합니다.

import torch
import torchvision

데이터를 통한 훈련을 위해 한번에 60,000개씩 훈련해도 되지만, 학습의 효율과 메모리 사용을 줄이기 위해 Mini-Batch 방식을 이용합니다. 여기서는 미니배치의 크기로 1000을 사용합니다. 그리고 MNIST로부터 훈련 데이터와 테스트 테이터를 다운로드하고 다운로드된 데이터로부터 미니배치만큼 데이터를 로딩하기 위해 다음과 같은 코드를 추가합니다.

batch_size = 1000

mnist_train = torchvision.datasets.MNIST(root="MNIST_data/", train=True, transform=torchvision.transforms.ToTensor(), download=True)
mnist_test = torchvision.datasets.MNIST(root="MNIST_data/", train=False, transform=torchvision.transforms.ToTensor(), download=True)

data_loader = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, drop_last=True)

머신러닝은 CPU 보다 행렬 연산에 최적화된 GPU를 통해 수행하는 것이 효율적입니다. 즉, Tensor를 GPU에 올려 연산을 수행한다는 의미입니다. 이를 위해 아래의 코드를 추가합니다.

device = torch.device("cuda:0")

이제 손글씨 이미지를 입력하고, 이 이미지가 의미하는 것이 무엇인지를 학습시키기 위한 모델을 정의합니다. 이미지 한장은 28×28 크기이므로 이를 범용적인 입력 데이터로써 받기 위해 각 화소값을 입력값으로 합니다. 즉, 입력값은 28×28인 784개이고, 출력값은 해당 이미지가 어떤 숫자인지에 대한 0~9까지의 확률값이므로 총 10 개입니다. 이를 위한 신경망 모델을 아래처럼 정의합니다. 아래의 코드가 신경망 모델에 대한 구성 코드입니다. 참고로 여기서 사용하는 신경망은 입력층과 출력층으로만 구성되므로 매우 단순합니다. bias=True라는 것은 가중치 외에도 편향값도 사용한다는 의미입니다.

linear = torch.nn.Linear(784, 10, bias=True).to(device)

각 훈련은 손실값만큼 가중치와 편향값을 최적의 값으로 보정하게 됩니다. 이때 손실값으로 Cross Entroy Error를 사용합니다. 이와 함께 출력층에서는 사용하는 활성화함수로는 Softmax 함수를 사용합니다. 파이토치에서는 이를 위해 torch.nn.CrossEntropyLoss를 제공하는데, 이 클래스는 내부적으로 Softmax와 Cross Entroy Error 둘 다 적용해 줍니다. 그리고 훈련에 대한 손실값을 최소화하기 위해 최적의 가중치와 편향값을 찾기 위해 경사하강법을 사용하는데, Hyper-Parameter인 학습율을 0.1로 정했습니다. 이에 대한 코드는 다음과 같습니다.

loss = torch.nn.CrossEntropyLoss().to(device)
SDG = torch.optim.SGD(linear.parameters(), lr=0.1)

아래는 1 Epoch(미니배치로 전체 훈련 데이터 처리 단위)에서 몇번의 미니배치가 반복되는지, 그리고 몇번의 Epoch 만큼 훈련할 것인지에 대한 각각의 변수에 대한 코드입니다.

total_batch = len(data_loader) # 60 = 60000 / 1000 (total / batch_size)
training_epochs = 10

아래는 훈련에 대한 코드입니다.

for epoch in range(training_epochs):
    total_cost = 0

    for X, Y in data_loader:
        X = X.view(-1, 28 * 28).to(device)
        Y = Y.to(device)
        
        hypothesis = linear(X)
        cost = loss(hypothesis, Y)

        SDG.zero_grad()
        cost.backward()
        SDG.step()

        total_cost += cost 

    avg_cost = total_cost / total_batch
    print("Epoch:", "%03d" % (epoch+1), "cost =", "{:.9f}".format(avg_cost))

위의 코드에서 5번은 (1000, 1, 28, 28) 크기의 텐서를 (1000, 784) 크기의 텐서로 변경해 줍니다. 6번 코드는 이미지 데이터에 대한 라벨값인데, One-Hot 인코딩이 아닌 0~9까지의 값으로 이미지에 대한 의미를 나타냅니다. 8번과 9번은 각각 입력 이미지에 대한 추정값을 얻고, 추정값과 라벨값인 참값 사이의 오차값을 계산합니다. 11번~13번은 오차역전파기법을 이용하여 가중치와 편향값을 보정하는 코드입니다. 18번은 1 에폭마다 손실값이 얼마나 나오는지를 확인하게 되는데, 옳바른 학습이라면 이 손실값은 큰 그림에서 봤을때 점차적으로 줄어들어야 합니다. 아래는 이러한 손실값을 에폭의 반복에 대해 표현한 그래프입니다.

마지막으로 아래의 코드는 위의 훈련을 통해 얻어진 가중치값과 편향값을 테스트 데이터에 적용해 얼마만큼의 정확도가 나오는지 확인하는 코드입니다.

with torch.no_grad():
    X_test = mnist_test.data.view(-1, 28 * 28).float().to(device)
    Y_test = mnist_test.targets.to(device)
    prediction = linear(X_test)
    correct_prediction = torch.argmax(prediction, 1) == Y_test
    accuracy = correct_prediction.float().mean()
    print("Accuracy: ", accuracy.item())

실제 이 코드를 통해 학습해 보면 대략 90% 정도의 정확도를 얻을 수 있습니다. 높은 정확도라고 할수는 없지만, 단순이 이미지의 화소값을 특징으로 일렬로 구성한, 즉 이미지라는 2차원적인 개념을 전혀 고려하지 않고 얻은 정확도라는 점에서 상당이 인상적인데요. 하지만 90%라는 정확도를 개선하기 위해 이미지라는 2차원적인 개념까지 고려하고 반영한 CNN을 이용하면 정확도를 99% 이상으로 올릴 수 있게 됩니다. 99%의 정확도는 인간의 평균 정확도를 넘어선 값입니다.

아나콘다(Anaconda) 설치

만약 기존에 Python이 설치되어 있다면 Uninstall(설치 디렉토리 안의 추가로 설치한 패키지는 남아 있으므로 직접 삭제 해야함)하고 관련된 시스템 속성에서 Path를 제거한다. 그리고 Anaconda 사이트에서 설치본을 다운로드 받아 설치한다. 별도의 사용자 설정없이 모두 Next로 설치를 진행하고 Path를 아래처럼 추가한다. (4개 추가했음)

폴더를 하나 만들고, 명령창에서 해당 폴더로 이동한 후 다음처럼 입력한다.

jupyter notebook

해당 폴더에서 웹으로 다양한 파이선 코드를 테스트해볼 수 있게 된다.

Latex로 수식에 대한 표현 정리

덧셈, 뺄셈, 곱, 나누기

$$A + B - C \times D \div E$$

    $$A + B - C \times D \div E$$

분수

$$\frac{A}{B}$$

    $$\frac{A}{B}$$

$$\sum_{i=0}^{n}{log{(i)}}$$

    $$\sum_{i=0}^{n}{log{(i)}}$$

미분

$$\frac{df(x)}{dx} = \lim_{x\to0} \frac{f(x+h)-f(x)}{h}$$

    $$\frac{df(x)}{dx} = \lim_{x\to0} \frac{f(x+h)-f(x)}{h}$$

적분

$$\int_{0}^{1}{f(x)dx}$$

    $$\int_{0}^{1}{f(x)dx}$$

행렬

$$A=\begin{bmatrix}
0 & 1 & 2\\
3 & 4 & 5\\
6 & 7 & 8
\end{bmatrix}
$$

    $$A=\begin{bmatrix} 0 & 1 & 2\\  3 & 4 & 5\\  6 & 7 & 8 \end{bmatrix} $$

로그

$$x=a^{y} \Longleftrightarrow y=log_{a}{x}$$

    $$x=a^{y} \Longleftrightarrow y=log_{a}{x}$$

참고 URL

GeoService-Xr의 특장점, “Geocoding 기능”

GIS 시스템은 Location, 즉 위치를 기반으로 운영되는 시스템입니다. 위치를 나타내는 가장 중요하고, 가장 많이 활용되는 것은 무엇일까요? 좌표일까요? 아닙니다. 바로 주소입니다. 이미 컴퓨터, GPS가 발명되기 전부터, 그리고 좌표라는 개념이 존재하기 이전부터 주소는 사용되어 왔습니다.

즉, 사용자는 주소를 통해 자신이 원하는 위치를 빠르게 찾고, 그 위치를 중심으로 GIS의 여러가지 기능을 실행해 사용자가 원하는 원하는 결과를 얻습니다. 이처럼 주소는 사용자에게 있어 위치에 대한 매우 중요한 지표가 되어 줍니다.

그런데 주소는 자주 변경됩니다. 새로운 건물이나 택지가 생기고 사라지거나 신도시의 출현과 기존 행정구역들이 통폐합 등의 상황에서 말입니다. 또한 우리나라는 도로명주소와 지번 주소가 혼재되어 있습니다. 그리고 주소는 제법 긴 글자들로 구성되는데 이 긴 글자를 입력할 때 사용자마다 다르게 입력하는 경우가 많습니다.

예를 들어 저희 회사의 주소인 “서울특별시 성동구 아차산로7나길 18″의 경우 서울특별시 대신 그냥 서울시라고 하여 “서울시 성동구 아차산로7나길 18″라고 하기도 하고 성동구는 서울시에만 있으므로 서울시조차 생략하여 “성동구 아차산로7나길 18″라고 하거나 아차산로7나길은 성동구라는 곳에만 있으므로 성동구도 생략해 “아차산로7나길 18″이라고 입력합니다. 이러한 변칙으로 인해 GIS 시스템에 주소와 관련된 기능을 매우 융통성 있게 담아내 활용하기가 어려운 것이 사실입니다.

주소는 최신 데이터로 자주 업데이트하는 것이 좋습니다. 주소 DB는 SHP 파일 형태인데, 이 SHP 파일이 있다고 해서 주소를 좌표로, 또 좌표를 주소로 변경하는 지오코딩이나 리버스 지오코딩 기능을 바로 사용할 수 있는 것은 아닙니다. 이 기능을 주소 DB를 기반으로 직접 개발을 하거나 서비스 형태로 제공되는 OpenAPI를 Key를 발급받아 사용해야 합니다. OpenAPI 형태로 제공하는 서비스는 구글, 네이버, 카카오, VWorld 등이 있습니다. 모두 그 기능이 뛰어납니다. 그런데 문제는 이들 모두가 사용건수 제약이 있습니다. 대부분 하루에 최대 OO건만 사용할 수 있습니다. 또한 인터넷 환경이 아니면 용할 수 없습니다. 결국 Key 발급이라는 번거로운 작업과 하루당 사용할 수 있는 최대 건수 제한을 고민할 필요없이, 인터넷이 되지 않는 환경에서도 사용할 수 있는 주소와 좌표간의 변환 서비스를 위해서는 Geocoding 서비스를 직접 개발해야 합니다. 그런데, 이 Geocoing 서비스를 개발하는 것이 그렇게 간단한 것은 아닙니다. 전국 범위의 주소 데이터는 도로명주소의 경우 천만건이 넘고 지번주소의 경우에는 이보다 훨씬 더 많습니다. 즉, 이 둘을 합하면 수천만 건입니다. 또한 도로명주소와 지번주소 간의 상호 변환 기능이 필요하기도 합니다. 그리고 생략된 주소 문자열에 대해서도 좌표를 획득할 수 있어야 함과 동시에 생략된 주소에 대한 완전한 주소 문자열로 얻을 수 있어야 합니다.

이러한 지오코딩 서비스는 GIS 개발을 전문으로 하는 몇몇 회사에서 하나의 독립적인 솔루션으로 판매하고 있습니다. 반면 GeoService-Xr의 경우에는 하나의 솔루션이 아닌 하나의 기능으로 제공합니다. 전국의 도로명주소와 지번주소를 대상으로, 주소와 좌표간의 상호변환과 지번주소와 도로명주소간의 상호변환이 가능합니다. 또한 생략된 주소에 대한 좌표 및 해당하는 완전한 주소 문자열을 획득할 수 있습니다. 주소 DB에 대한 최신성 유지는 고객이 원하는 기간에 정확이 맞출 수 있습니다.

아래는 GeoService-Xr의 지오코딩 기능을 활용하여 개발된 넥스젠의 스타쿼리 기능에 대한 소개입니다.

넥스젠(NexGen)의 스타쿼리(* Query) 기능

항상 최신 주소와 위치를 원하는 사용자라면 한달에 한번 주소 DB를 업데이트하고, 최신 주소는 아니더래도 정기적으로 업데이트하고자 하는 고객분들에게는 3개월 또는 4개월에 한번씩 업데이트가 가능합니다. 또한 자체적인 주소 DB와 지오코딩 서비스를 고객의 서버에 설치한 것이므로 사용 건수나 인터넷이 되어야만 한다 등의 제약은 전혀 없습니다. 이러한 편리함과 유연함은 GeoService-Xr의 지오코딩 기능을 통해 쉽게 활용하실 수 있습니다.

GeoService-Xr은 공간 데이터를 서비스하고 여러가지 위치 데이터 기반의 기능을 기본으로 제공합니다. 지오코딩 역시 위치 기반의 기능이므로 하나의 기능으로써 제공하고 있는 것입니다. GIS 시스템의 구축을 위해 공간서버를 도입하면서 별도의 지오코딩 솔루션을 도입은 GeoService-Xr에서는 불필요합니다. Geocoding 기능은 GeoService-Xr에서는 하나의 기본 기능입니다.