웹 GIS 엔진, FingerEyes-Xr에서 CAD 도면 시각화

지리정보 분야에서 CAD 파일은 수치지도의 구축 및 공유을 위한 목적으로 현재까지도 활발하게 사용되고 있습니다. 흔히 많이 사용되는 SHP 파일보다 더욱 다양한 공간 정보를 ‘지도’라는 개념으로 제공하는데요. 이러한 지도로써의 정보를 그대로 사용자에게 제공할 수 있도록 FingerEyes-Xr을 이용하여 CAD 지도를 웹에서 바로 보여줄 수 있습니다. 이러한 FingerEyes-Xr에서 제공하는 CAD 도면 시각화 기능의 장점은 다음과 같습니다.

사용자는 위의 장점을 웹이라하는 환경에서 모두 얻을 수 있습니다. 아래의 영상은 실제 CAD 도면 하나를 FingerEyes-Xr에서 불러와 웹에서 시각화하는 내용입니다.

CAD 파일을 통해 제공되는 지형지물에 대한 모든 정보를 제공하고 있는데요. 해당 정보는 POLYLINE, LINE, CIRCLE, ARC, TEXT는 물론이고 BLOCK도 제공합니다. 위의 기능 시연에서 사용한 수치지도는 국가공간정보포털에서 제공하는 수치지형도를 이용하였습니다.

단순 선형 회귀에 대한 2가지 접근

잡음이 섞인 샘플 데이터가 선형이라고 가정할때, 이 선형 모델은 기울기와 절편이라는 값으로 정의됩니다. 이 기울기와 절펀에 대한 값을 구하는 방법은 다양한데, 이 글에서는 2가지 접근 방법을 언급합니다. 먼저 잡음이 섞인 샘플 데이터는 다음과 같습니다.

import numpy as np
import matplotlib.pyplot as plt

X = 10 * np.random.rand(100,1)
y = 3.7 - 2.5 * X + np.random.randn(100,1)
plt.scatter(X, y)
plt.show()

위의 코드는 샘플 데이터에 대한 시각화 코드도 포함하고 있는데, 그 결과는 다음과 같습니다.

이제 위의 샘플 데이터에 대한 선형회귀 방법 중 하나인 정규방정식(Normal Equation)에 대한 코드는 다음과 같습니다.

X_b = np.c_[np.ones((100,1)), X]
w = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
print(w)

plt.scatter(X, y)
drawLine(w[1], w[0])
plt.show()

분석된 절편과 기울기에 대한 출력 및 결과 모델의 선형은 다음과 같습니다.

[[ 3.76686801]
 [-2.50677558]]

아울러 정규방정식은 다음과 같습니다.

    $$(X^{T}X)^{-1}X^{T}y$$

다음은 사이킷런에서 제공하는 LinearRegression 클래스를 이용한 방법입니다.

from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X, y)
w = [model.intercept_[0], model.coef_[0][0]]
print(w)

plt.scatter(X, y)
drawLine(w[1], w[0])
plt.show()

분석된 절편과 기울기에 대한 출력 및 결과 모델의 선형은 다음과 같습니다.

[[ 3.69686801]
 [-2.50677558]]

위의 코드에서 절편과 기울기를 통해 그래프를 그리는 함수인 drawLine은 다음과 같습니다.

def drawLine(m, b):
    X = np.arange(0, 11)
    y = [m * x + b for x in X]
    plt.plot(X, y)

혼돈행렬(Confusion Matrix)와 정밀도, 재현률, F1점수

이 글은 한빛미디어의 핸즈온 머신러닝을 수업자료로써 파악하면서 이해한 바를 짧게 요약한 글입니다. 요즘 이 책을 통해 머신러닝을 다시 접하고 있는데, 체계적이고 좋은 내용을 제공하고 있고, 나 자신을 위한 보다 명확한 이해를 돕고자 이 글을 작성 작성합니다. 요즘 제가 블로그에 올리는 머신러닝 관련 글은 대부분 이 책의 내용에 대한 나름대로의 해석을 토대로 합니다. 보다 자세한 내용은 해당 도서를 참고하기 바랍니다.

이글은 훈련된 예측 모델을 평가하기 위한 지표인 정밀도, 재현률, F1에 대한 내용입니다. 이러한 평가 지표는 혼돈행렬이라는 데이터를 토대로 계산되는데요, 먼저 혼돈행렬을 구하기 위해 학습 데이터셋이 필요하며, 0~9까지의 숫자를 손으로 작성한 MINIST를 사용하고, 이 손글씨가 7인지에 대한 예측 모델을 예로 합니다. MNIST 데이터셋을 다운로드 받고, 레이블 데이터를 재가공합니다.

from sklearn.datasets import fetch_openml
import numpy as np

mnist = fetch_openml('mnist_784', version=1, data_home='D:/__Temp__/_')

X, y = mnist["data"], mnist["target"]
y = y.astype(np.uint8)

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
y_train_7 = (y_train == 7)
y_test_7 = (y_test == 7)

예측 모델은 SGDClassifier를 사용합니다.

from sklearn.linear_model import SGDClassifier

model = SGDClassifier(random_state=3224)

혼돈 행렬을 얻기 위해 다음 코드를 실행합니다.

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

y_train_pred = cross_val_predict(model, X_train, y_train_7, cv=3)
cf = confusion_matrix(y_train_7, y_train_pred)
print(cf)
[[53223   512]
 [  726  5539]]

cross_val_predict 함수는 아직 전혀 학습이 되지 않은 모델을 지정된 교차검증 수만큼 학습시킨 뒤 예측값을 반환합니다. 이렇게 얻은 예측값과 실제 값을 비교해서 얻은 혼돈행렬의 결과에 대한 상세한 이미지는 아래와 같습니다.

위의 그림에서 표에 담긴 4개의 값은 발생횟수입니다. TN과 TP의 값은 옳바르게 예측한 횟수이고 FN과 FP는 잘못 예측한 횟수입니다. 즉, FN과 FP가 0일때 모델은 완벽하다는 의미입니다.

이제 위의 혼돈행렬에서 정밀도(Precision)와 재현률(Recall), F1점수에 대한 수식은 다음과 같습니다.

    $$Precision=\frac{TP}{TP+FP}, Recall=\frac{TP}{TP+FN},F1=2\times\frac{Precision \times Recall}{Precision+Recall}$$

정밀도와 재현률이 서로 상반관계에 있습니다. 즉, 정밀도가 높으면 재현률이 떨어지며 재현률이 높아지면 정밀도가 떨어지는 경향이 있습니다. F1은 이런 상반관계에 있는 정밀도와 재현률을 묶어 평가하고자 하는 지표입니다.

비록 정밀도와 재현률, F1점수는 매우 단순해 계산하기 쉬우나 다음의 코드를 통해서도 쉽게 얻을 수 있습니다.

from sklearn.metrics import precision_score, recall_score, f1_score
p = precision_score(y_train_7, y_train_pred)
print(p)
r = recall_score(y_train_7, y_train_pred)
print(r)
f1 = f1_score(y_train_7, y_train_pred)
print(f1)
0.9153858866303091
0.8841181165203511
0.8994803507632347

회귀분석의 네가지 방법, 선형회귀/의사결정트리/랜덤포레스트/SVM

회귀분석은 다수의 특징값을 입력으로 하나의 특징값(실수값)을 산출하는 것입니다. 세가지 방법이 있는데, 선형회귀(Linear Regression)과 의사결정트리(Decision Tree) 그리고 렌덤 포레스트(Random Forest)입니다. 하나의 주제를 정하고 이 3가지 방법을 통해 회귀분석을 테스트해 보도록 하겠습니다. 이 글에서 적용한 회귀 분석 주제는 전복의 나이를 예측하는 것으로 전복의 ‘성별’, ‘키’, ‘지름’, ‘높이’, ‘전체무게’, ‘몸통무게’, ‘내장무게’, ‘껍질무게’를 입력하면 ‘껍질의 고리수’를 예측한 뒤 예측된 ‘껍질의 고리수’에 1.5를 더하면 전복의 나이가 된다고 합니다.

이에 대한 전복의 데이터셋은 kaggle에서 쉽게 다운로드 받을 수 있습니다. 일단 다운로드 받은 데이터셋을 다음과 같은 코드를 통해 전처리하여 특징과 레이블로 구분하고, 학습용과 테스트용으로 구분합니다.

# 데이터 파일 로딩
import pandas as pd
raw_data = pd.read_csv('./datasets/datasets_1495_2672_abalone.data.csv', 
        names=['sex', 'tall', 'radius', 'height', 'weg1', 'weg2', 'weg3', 'weg4', 'ring_cnt'])
    #names=['성별', '키', '지름', '높이', '전체무게', '몸통무게', '내장무게', '껍질무게', '껍질의고리수']
print(raw_data[:7])

# 레이블 분리
data_ring_cnt = raw_data[["ring_cnt"]]
data = raw_data.drop("ring_cnt", axis=1)

# 범주형 특징(sex)에 대한 원핫 인코딩
from sklearn.preprocessing import OneHotEncoder
data_cat = data[["sex"]]
onehot_encoder = OneHotEncoder()
data_cat_onehot = onehot_encoder.fit_transform(data_cat)
print(onehot_encoder.categories_)

# 범주형 특징 제거
data = data.drop("sex", axis=1)

# 범주형 필드가 제거되어 수치형 특징들에 대해 0~1 구간의 크기로 조정
#from sklearn.preprocessing import StandardScaler 
#minmax_scaler = StandardScaler()
#data = minmax_scaler.fit_transform(data)

# 원핫인코딩된 범주형 특징과 스케일링된 수치형 특징 및 레이블 결합
import numpy as np
data = np.c_[data_cat_onehot.toarray(), data, data_ring_cnt]

data = pd.DataFrame(data, columns=['sex_F', 'sex_I', 'sex_M', 'sex_''tall', 'radius', 'height', 'weg1', 'weg2', 'weg3', 'weg4', 'ring_cnt'])
print(data[:7])

# 학습 데이터와 테스트 데이터 분리 
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(data, test_size=0.1, random_state=47)

# 입력 특징과 레이블의 분리
train_data = train_set.drop("ring_cnt", axis=1)
train_data_label = train_set["ring_cnt"].copy()
test_data = test_set.drop("ring_cnt", axis=1)
test_data_label = test_set["ring_cnt"].copy()

위의 코드는 아래의 글들을 참조하여 파악할 수 있습니다.

분석가 관점에서 데이터를 개략적으로 살펴보기

계층적 샘플링(Stratified Sampling)

OneHot 인코딩(Encoding) 및 스케일링(Scaling)

다소 험난한 데이터셋의 준비가 끝났으니, 이제 이 글의 본질인 세가지 회귀분석 방식을 하나씩 살펴보겠습니다. 먼저 선형회귀입니다. 참고로 기계학습 과정에 대한 시간 대부분을 차지 하는 작업은 이러한 데이터의 수집과 가공 및 정제이며, 그 다음으로 컴퓨터(GPU)를 통한 모델의 학습입니다. 사람이 관여하는 시간은 상대적으로 매우 적습니다. 물론 모델을 직접 설계하는 경우라면 달라질 수 있겠으나, 역시 데이터 작업과 GPU의 학습 시간은 상대적으로 많이 소요됩니다.

from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(train_data, train_data_label)

from sklearn.metrics import mean_squared_error
some_predicted = model.predict(test_data)
mse = np.sqrt(mean_squared_error(some_predicted, test_data_label))
print('평균제곱근오차', mse)

학습된 모델을 테스트 데이터 셋으로 평가한 오차는 다음과 같습니다.

평균제곱근오차 1.9166262592968584

다음은 의사결정트리 방식입니다.

from sklearn.tree import DecisionTreeRegressor
model = DecisionTreeRegressor()
model.fit(train_data, train_data_label)

from sklearn.metrics import mean_squared_error
some_predicted = model.predict(test_data)
mse = np.sqrt(mean_squared_error(some_predicted, test_data_label))
print('평균제곱근오차', mse)
평균제곱근오차 2.8716565747410656

세번째로 랜덤 포레스트 방식입니다.

from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor()
model.fit(train_data, train_data_label)

from sklearn.metrics import mean_squared_error
some_predicted = model.predict(test_data)
mse = np.sqrt(mean_squared_error(some_predicted, test_data_label))
print('평균제곱근오차', mse)
평균제곱근오차 2.0828589571564127

마지막으로 SVM(Support Vector Manchine) 방식입니다.

from sklearn import svm
model = svm.SVC()
model.fit(train_data, train_data_label)
평균제곱근오차 2.537753216441624

이 글의 데이터셋에 대해서는 선형회귀 방식이 가장 오차가 작은 것은 것을 알 수 있습니다. 하지만 이 글에서 테스트한 3가지 모델의 하이퍼파라메터는 기본값을 사용하였습니다. 하이퍼파라메터의 세부 튜팅을 수행하면 결과가 달라질 수 있습니다.