차원 축소

머신러닝으로 풀고자 하는 문제에 대한 입력값, 즉 특성은 그 상황에 따라 몇개에서 수백, 수천만개 이상으로 이루어질 수 있습니다. 그렇다고 이 모든 특성이 문제 해결을 위해 중요한 것은 아닙니다. 어떤 특성은 문제 해결에 미치는 영향이 미미하거나 아예 없는 경우도 있습니다. 이럴때 문제 해결에 미미한 영향을 가지는 특성은 제거하는 것은 해당 문제를 풀 수 있는 가능성을 높여준다는 점에서 매우 의미가 큽니다. 문제 해결의 가능성을 높여지는 것 뿐만이 아니라 불필요한 특성을 줄여줌으로써 학습 속도가 향상되며, 더 적은 학습 데이터만으로도 높은 정확도의 결과를 얻을 수 있습니다. 또한 특성을 2개 또는 3개로 줄임으로써 우리에게 익숙한 2차원과 3차원의 공간에 데이터를 시각화하여 어떤 통찰을 얻을 수도 있습니다.

이 글은 특성수를 줄이는, 즉 차원을 축소하는 방법에 몇가지를 언급합니다. 구체적인 예로 다음과 같은 3개의 특성으로 구성된 3차원의 데이터를 특성 2개인 2차원의 데이터로 축소함에 있어서, 최대한 원래 데이터가 가지고 있는 좋은 특성을 유지하도록 하는데, 이는 통계학적으로는 분석을 최대한 보존하는 방향이기도 합니다. 먼저 특성을 줄일 대상이 되는 원본 데이터를 구성하고 이를 3차원으로 시각화하는 코드는 다음과 같습니다.

from sklearn.datasets import make_swiss_roll
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

X, t = make_swiss_roll(n_samples=500, noise=0.1, random_state=3224)

fig = plt.figure(figsize=(6, 5))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=t, cmap=plt.cm.hot)
plt.show()

위 코드의 결과는 다음과 같습니다.

데이터는 사이킷런에서 제공하는 데이터셋 중 스위스롤로 스펀지 케익을 롤케익처럼 둘둘 말아 놓은 형태인데요, 이를 평면으로 펴서 시각화하는 코드와 그 결과는 다음과 같습니다.

plt.scatter(t, X[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

스위스롤 데이터셋에 대한 차원축소에 대한 최고의 결과는 바로 위의 결과라고도 할 수 있습니다. 하지만 이러한 해석은 2차원상의 시각화라는 관점에서만 국한된 결과입니다.

이제 실제 차원축소에 대한 다양한 방법을 코드로 살펴보겠습니다. 먼저 PCA입니다.

from sklearn.decomposition import PCA
X_pca_reduced = PCA(n_components=2, random_state=42).fit_transform(X)
plt.scatter(X_pca_reduced[:, 0], X_pca_reduced[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

다음은 PCA의 비선형 버전인 Kernel PCA입니다.

from sklearn.decomposition import KernelPCA

lin_pca = KernelPCA(n_components=2, kernel="linear")
rbf_pca = KernelPCA(n_components=2, kernel="rbf", gamma=0.05)
sig_pca = KernelPCA(n_components=2, kernel="sigmoid", gamma=0.001, coef0=1)

plt.figure(figsize=(11, 4))
for subplot, pca, title in (
        (131, lin_pca, "Linear kernel"), 
        (132, rbf_pca, "RBF kernel, $\gamma=0.05$"), 
        (133, sig_pca, "Sigmoid kernel, $\gamma=10^{-3}, r=1$")):
    X_reduced = pca.fit_transform(X)
    if subplot == 132:
        X_reduced_rbf = X_reduced
    
    plt.subplot(subplot)
    plt.title(title, fontsize=14)
    plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap=plt.cm.hot)
    plt.xlabel("$z_1$", fontsize=18)
    if subplot == 131:
        plt.ylabel("$z_2$", fontsize=18, rotation=0)

plt.show()

위의 코드는 3가지 종류의 커널에 대한 PCA 결과를 표현하고 있습니다.

다음은 LLE 알고리즘을 사용한 차원축소입니다. LLE는 Locally Linear Embedding으로 국소적인(지역적인) 샘플 데이터간 거리를 최대한 보존하며 차원을 축소합니다. 코드 및 그 결과는 아래와 같습니다.

from sklearn.manifold import LocallyLinearEmbedding

lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=3224)
X_reduced = lle.fit_transform(X)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

다음은 MDS로 다차원 스케일링(Multi-Dimensional Scaling)의 약자로, 샘플 데이터 간의 거리르 보존하면서 차원을 축소합니다. 코드 예와 그 결과는 다음과 같습니다.

from sklearn.manifold import MDS
mds = MDS(n_components=2, random_state=3224)
X_reduced_mds = mds.fit_transform(X)

plt.scatter(X_reduced_mds[:, 0], X_reduced_mds[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

다음은 Isomap을 이용한 차원축소이며, 이는 각 샘플에서 가장 가까운 샘플 간의 거리, 보다 정확히는 Geodesic Distance를 유지하면서 차원을 축소합니다. 코드와 그 결과는 다음과 같습니다.

from sklearn.manifold import Isomap
isomap = Isomap(n_components=2)
X_reduced_isomap = isomap.fit_transform(X)

plt.scatter(X_reduced_isomap[:, 0], X_reduced_isomap[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

더 많은 차원축소 방법이 존재하지만 이 글에서는 마지막으로 t-SNE을 이용한 차원감소를 소개합니다. 주로 시각화에 많이 사용되며 군집화된 결과를 시각적으로 표현합니다. 코드와 결과는 다음과 같습니다.

from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=3224)
X_reduced_tsne = tsne.fit_transform(X)

plt.scatter(X_reduced_tsne[:, 0], X_reduced_tsne[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

t-SNE를 이용한 다른 글은 아래의 링크를 참조하시기 바랍니다.

잠재벡터(Latent) z의 공간분포 시각화(Visualization)

간단한 tar 파일 사용

tar는 많은 파일을 하나로 묶어 주는 프로그램 입니다. 묶어줄때 압축 옵션을 지정하면 압축도 해줍니다 아래의 이미지를 토대로 예를 들어 보겠습니다..

위의 그림 중 TILES_20200626와 vworld 디렉토리의 모든 파일을 해당 디렉토리 구조를 유지하면서 tiles.tar이라는 파일 하나로 묶고자할때 아래처럼 tar 명령어를 수행합니다.

tar -cvf tiles.tar ./TILES_20200626 ./vworld

하나로 묶여진 tar 파일을 원하는 컴퓨터에 복사해 다시 복원하는 tar 명령은 다음과 같습니다.

tar -xvf tiles.tar

옵션으로 z를 지정하면 압축을 수행합니다. 위의 경우 이미 압축된 이미지 파일들을 하나로 묶는 경우이므로 압축 옵션을 지정하지 않았습니다.

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

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

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

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

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

수집된 데이터를 활용하여 AI 학습하기에 앞서 가장 먼저 해야 할 것은 수집된 데이터를 개략적으로 살펴보는 일입니다. 이 글은 간단하지만 의미있는 데이터셋을 개략적으로 살펴보는 것에 대한 내용을 살펴봅니다.

간단하지만 의미있는 데이터셋은 Kaggle에서 제공하는 전복(Abalone) 데이터셋이며 다운로드 받은 파일은 CSV 형식으로 파일을 열어 그 일부를 보면 다음과 같습니다.

내용을 보면 일반적인 첫줄에 컬럼명이 아닌 바로 데이터값으로 시작하는 것과 총 9가지의 컬럼값으로 구성되어 있다는 것을 파악할 수 있습니다.

이제 이 데이터를 파이선을 통해 개략적으로 살펴보도록 하겠습니다.

pandas를 사용하여 파일을 불러오는 코드로 시작합니다.

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 인자를 통해 컬럼의 의미를 파악할 수 있으면서 식별자로 사용할 수 있는 이름을 지정해 줍니다. 총 9개인데, 각각의 의미는 ‘성별’, ‘키’, ‘지름’, ‘높이’, ‘전체무게’, ‘몸통무게’, ‘내장무게’, ‘껍질무게’, ‘껍질의고리수’입니다.

개략적인 내용 파익으로 이 데이터셋의 실제 내용 중 시작부분을 살펴보는 코드입니다.

print(raw_data.head())

결과는 다음과 같습니다.

다음은 전체적인 데이터의 구성을 살펴보는 코드입니다.

print(raw_data.info())

결과는 다음과 같은데, 총 4177개의 전복 데이터가 있으며 각 컬럼 데이터의 타입과 Null 값이 아닌 데이터의 개수 정보를 파악할 수 있습니다. sex 컬럼의 데이터 타입은 object인데, 이는 문자열이기 때문입니다.

앞서 sex가 문자열인데, 이는 전복의 성별값입니다. I는 유충, M은 수컷, F는 암컷인데, 이 sex에 대한 정보를 좀더 살펴보기 위한 코드입니다.

print(raw_data['sex'].value_counts())

결과는 다음과 같은데, 수컷(M)이 1528개, I가 1342개, F가 1307라는 것을 알 수 있습니다.

다음은 데이터에 대한 간단한 통계를 확인하기 위한 코드입니다.

print(raw_data.describe())

결과는 다음과 같습니다.

각 컬럼에 대한 데이터수, 평균, 편차, 최대값, 최소값, 25%/50%/75%에 대한 백분위수(Percentile)가 제공됩니다.

끝으로 각 컬럼에 대한 히스토그램을 살펴보는 코드입니다.

import matplotlib.pyplot as plt

raw_data.hist(bins=10)
plt.show()

결과는 다음과 같습니다.

입력 1개와 출력 2개에 대한 선형회귀 신경망 구성 (복합 출력 / 다중 출력 신경망 모델)

다음과 같은 구조의 신경망을 구현에 대한 내용이다.

위의 신경망을 통해 판단할 수 있는 것은 입력값은 1개이고 출력값이 2개이므로 각각의 텐서구조는 [x], [y1, y2]라는 것이다. 신경망의 마지막 은닉층의 뉴런개수는 출력 개수와 동일하므로 2개이다.

코드를 보자. 먼저 필요한 패키지의 임포트이다. 파이토치를 사용한 예이다.

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.init as init

데이터를 준비한다.

num_data = 4000

x = torch.Tensor(num_data,1)
init.uniform_(x,-10,10)

noise = torch.FloatTensor(num_data,2)
init.normal_(noise, std=1)

def func1(x): return 4*x+5 
def func2(x): return 7*x+3 

y1 = func1(x)
y2 = func2(x)

y_noise = torch.Tensor(num_data,2)
y_noise[:,0] = y1[:,0] + noise[:,0]
y_noise[:,1] = y2[:,0] + noise[:,1]

데이터셋의 구성 개수는 4000개로 했다. 입력값(x)에 대해 2개의 출력값을 위한 선형공식이 9와 10 라인에 보인다. 12-17라인은 데이터에 잡음을 추가한 것이다. 잡음이 추가된 데이터를 통해 가중치(기울기)인 4, 7과 편향(y절편)인 5, 3을 결과를 얻어내면 된다. 아래는 이를 위한 학습 코드다.

model = nn.Linear(1,2)
loss_func = nn.L1Loss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
label = y_noise
num_epoch = 2000

for i in range(num_epoch):
    optimizer.zero_grad()
    output = model(x)
    loss = loss_func(output, label)
    loss.backward()
    optimizer.step()

    if i%10 == 0:
        print(loss.data)

param_list = list(model.parameters())
print(param_list[0], param_list[1])

결과는 다음과 같다.

tensor(28.9745)
tensor(27.7681)
tensor(26.5639)
tensor(25.3614)
tensor(24.1623)
tensor(22.9677)
tensor(21.7777)
.
.
.
tensor(0.7945)
tensor(0.7945)
tensor(0.7945)
Parameter containing:
tensor([[3.9986],
        [7.0006]], requires_grad=True) Parameter containing:
tensor([4.9845, 3.0214], requires_grad=True)

총 2000번 학습 시켰고, 그 결과로 손실값이 약 27로 시작해서 약 0.79 줄었다. 그리고 결과는 4, 7 그리고 5, 3에 근사한 값이 나온것을 알 수 있다.

이 코드를 통해 알아낸 것은 1개의 특성을 통해 그보다 더 많은 2개의 특성을 얻어내야 하는 이 경우에는 입력 데이터가 상대적으로 많아야 한다는 것이다. 이 경우는 4000개이다. 아울러 적당한 손실함수를 사용해야 한다. 위의 예제는 L1 손실함수를 사용했지만 평균제곱오차 손실함수를 사용하면 더 적은 데이터(이 부분은 확인이 필요함)와 반복학습이 가능하다.

여기서 입력 데이터와 분석 결과를 그래프로 시각화 해보자. 해당 코드는 아래와 같다. 지금까지의 코드에서 마지막에 붙이면 된다.

import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(15,15))
plt.scatter(x.numpy(),y_noise[:,0].numpy(),s=3,c="gray")
plt.scatter(x.numpy(),y_noise[:,1].numpy(),s=3,c="black")

x = np.arange(-10, 10, 0.01)
plt.plot(x, func1(x), linestyle='-', label="func1", c='red')
plt.plot(x, func2(x), linestyle='-', label="func2", c='blue')

plt.axis([-10, 10, -30, 30])
plt.show()

그래프는 다음과 같다.

회색점과 검정색점은 입력 데이터이고, 빨간색선과 파란색선은 선형회귀 결과를 표시한 것이다.