Javascript의 Promise API 요약

Promise는 코드의 실행 흐름에서 비동기처리를 유연하게 처리하기 위한 API입니다. 코드를 통해 살펴보겠습니다.

function test(callback) {
    setTimeout(() => {
        callback();
    }, 2000);
}

function callback() {
    console.log('Hello!')
}

test(callback);

위의 코드는 2초 뒤에 콘솔에 Hello!를 출력합니다. 이 코드를 Promise API로 대체하면 아래와 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(); 
        }, 2000);
    })
}

function callback() {
    console.log('Hello!');
}

test().then(callback);

좀더 완전한 Promise API를 위한 위의 코드에서 확장된 코드를 살펴보면 아래와 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let bOK = true;

            console.log('1');            
            if(bOK) {
                resolve(1); // [Fulfilled State] 1, 2를 콘솔에 출력하고 적당한 시점에 then으로 넘겨준 함수를 호출해 줌           
            } else {
                reject(-1); // [Rejected State] 1, 2를 콘솔에 출력하고 적당한 시점에 예외를 발생시킴
            }
            console.log('2'); 
        }, 2000);
    })
}

function callback(result) {
    console.log('Hello! : ' + result)
}

let p = test(); // [Pending State]
p.then(callback); // Promise의 resolve 호출에 의해 callback이 호출됨

Promise는 3가지 상태를 갖는다고 합니다. Promise 객체가 생성되어 사용될 준비가 된 Pending, 비동기 처리에 의해 원하는 올바른 결과를 얻어와 그 결과를 정상적으로 처리하고자 resolve가 호출된 Fulfilled 상태, 무언가 잘못되어 예외로 처리하고자 reject가 호출된 Rejected 상태입니다. 위의 짧은 코드를 살펴보면, 어떤 이 3가지 상태에 대한 시점을 확인할 수 있습니다. resolve와 reject는 각각 성공과 실패에 대한 결과값을 얻을 수 있는 객체를 전달할 수 있습니다. 위의 코드에서는 각각 1와 -1을 넘겨주고 있습니다. 주목할 점은 resolve와 reject는 동시에 같이 실행될 수 없으며 배타성을 갖습니다. 즉, resolve가 호출되면 reject 코드가 호출된다고 해도 실행되지 않으며, reject 코드가 호출되면 resolve가 호출되어도 reject가 호출되지 않습니다. 또 하나는 resolve에 의해 실행되는 callback은 resolve가 호출될때 즉시 실행되지 않습니다. 위의 코드의 주석을 보면 console.log(‘1’)과 consloe.log(‘2’)의 위치 사이에 각각 resolve와 reject가 있는데.. 실행순서는 console.log(‘1’)과 console.log(‘2)가 먼저 실행되고 resolve 또는 reject에 연결된 함수가 호출된다는 점입니다. 이 글의 마지막으로.. reject가 호출되면 예외를 던지게 됩니다. 위의 코드는 적당한 예외에 대한 처리를 하고 있지 않습니다. 이 예외 처리까지 포함된 코드는 다음과 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let bOK = false;

            if(bOK) {
                resolve(1);           
            } else {
                reject(-1); // Promise의 catch로 넘겨준 함수를 호출해 줌
            }
        }, 2000);
    })
}

function callback(result) {
    console.log('Hello! : ' + result)
}

function callbackError(result) {
    console.log('Oh my god! : ' + result)
}

test().then(callback, callbackError).catch(callbackError); // then과 catch는 Promise 객체를 반환해줌
// test().then(callback, callbackError); // 바로 위의 코드와 동일함

reject 함수의 호출로 인해 Promise 객체의 catch로 넘겨준 함수를 호출할 수 있게 되어 적절한 예외 처리가 가능해 집니다.

Promise는 사실 기반 API입니다. 즉, Promise를 기반으로 상대적으로 상위 레벨의 API를 통해 Promise를 사용하게 되는데요. async/await나 fetch가 바로 그것인데요, fetch는 아래의 글을 참고하시기 바랍니다.

ES6의 Fetch API

matplotlib의 등치선(contour) 표현하기

등치선은 공간상에 분포하는 동일한 값을 가지는 인접한 지점을 연속적으로 이어 구성한 선입니다. 대기 확산 모델을 시각화하기 위한 매우 효과적인 방법입니다.

아래의 그림은 contour 그래프를 구성하기 위한 코드로써 작성한 예제에 대한 결과입니다.

위의 결과를 위해서는 먼저 시각화 대상이 되는 데이터가 필요합니다. 공간상에 균일하게 분포하는 값에 대한 데이터인데요. 데이터 구성을 위한 코드는 다음과 같습니다.

import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np

npts = 100
x = np.random.uniform(-2, 2, npts)
y = np.random.uniform(-2, 2, npts)
z = x * np.exp(-x**2 - y**2)

ngridx = 100
ngridy = 100
xi = np.linspace(-2.2, 2.2, ngridx)
yi = np.linspace(-2.2, 2.2, ngridy)

triang = tri.Triangulation(x, y)
interpolator = tri.LinearTriInterpolator(triang, z)
Xi, Yi = np.meshgrid(xi, yi)
zi = interpolator(Xi, Yi)

위의 코드를 통해 2차원(x, y) 공간 상에 분포하는 값(z)으로 변수 xi, yi, zi를 얻게 됩니다. 이 3개의 변수를 이용해 등치선을 표현할 수 있는데, 그 코드는 다음과 같습니다.

plt.contour(xi, yi, zi, levels=15, linewidths=0.5, colors='k')

cntr = plt.contourf(xi, yi, zi, levels=15, cmap="RdBu_r")
plt.colorbar(cntr)
plt.plot(x, y, 'ko', ms=3)

plt.show()

위의 코드 중 1번이 지정한 levels 수에 맞게 등치선(검정색 선)을 표현하며, 3번 코드는 등치값들을 포함하는 영역을 지정한 levels 수에 맞게 표현하는 코드입니다.

Python의 matplotlib에서 주석(Annotation)을 한글로 표기하기

matplotlib는 강력한 그래프 출력을 위한 라이브러리이고, 다양한 환경과 언어에서 지원됩니다. 그래프는 어떤 정보를 전달하기 위한 목적을 가지므로, 전달하고자 하는 내용을 효과적으로 표기할 수 있어야 하는데요. matplotlib에서는 텍스트나 화살표와 같은 지시선 등을 그래프 상에 표기할 수 있습니다.

아래의 코드는 그래프 위에 일반적인 텍스트와 지시선을 표기하는 파이썬의 코드입니다.

import matplotlib.pyplot as plt

plt.tight_layout()
plt.axis([0,100,0,100])
plt.plot(20, 20, "bs")
plt.plot(50, 50, "bs")
plt.plot(80, 80, "bs")


plt.annotate('중앙값', xy=(50, 50), xytext=(50,20),
            fontsize=14, ha='center',
            arrowprops=dict(facecolor='black', width=1, shrink=0.1, headwidth=10))

plt.text(25, 80, '상자 속에 글자', fontsize=24, bbox=dict(boxstyle='square', color='lightgray'))

plt.show()

결과는 다음과 같습니다.

파이썬에서 matplotlib에서 사용하는 기본 폰트가 영문이므로, 아래처럼 원하는 한글 폰트를 직접 지정하여 한글을 표기할 수 있습니다. 아래의 코드는 그래프를 출력하기 이전인 위의 코드에서 2번줄에 추가하면 됩니다.

from matplotlib import font_manager

font_fname = 'C:/Users/Dip2K/AppData/Local/Microsoft/Windows/Fonts/XOU_XWA.ttf'
font_family = font_manager.FontProperties(fname=font_fname).get_name()
print(font_family)
plt.rcParams["font.family"] = font_family

차원 축소

머신러닝으로 풀고자 하는 문제에 대한 입력값, 즉 특성은 그 상황에 따라 몇개에서 수백, 수천만개 이상으로 이루어질 수 있습니다. 그렇다고 이 모든 특성이 문제 해결을 위해 중요한 것은 아닙니다. 어떤 특성은 문제 해결에 미치는 영향이 미미하거나 아예 없는 경우도 있습니다. 이럴때 문제 해결에 미미한 영향을 가지는 특성은 제거하는 것은 해당 문제를 풀 수 있는 가능성을 높여준다는 점에서 매우 의미가 큽니다. 문제 해결의 가능성을 높여지는 것 뿐만이 아니라 불필요한 특성을 줄여줌으로써 학습 속도가 향상되며, 더 적은 학습 데이터만으로도 높은 정확도의 결과를 얻을 수 있습니다. 또한 특성을 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를 지정하면 압축을 수행합니다. 위의 경우 이미 압축된 이미지 파일들을 하나로 묶는 경우이므로 압축 옵션을 지정하지 않았습니다.