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

의미적으로 같은 성질의 데이터들을 공간상에 분포시켜 가시화해 본다면, 같은 의미를 가지는 데이터들은 공간 위치상으로 한곳에 모여있을 것입니다. 이렇게 데이터를 공간상에 분포시켜 놓을 수 있다면 해석 결과로써의 데이터가 얼마나 잘 해석되었는지를 시각화할 수 있고, 새로운 데이터에 대한 공간상의 위치를 통해 어떤 성질군에 해당하는지를 시각적으로 쉽게 파악할 수 있습니다.

그러나 문제는 사람이 인지하는 공간은 2차원 또는 3차원이라는 점이고, 데이터는 이보다 더 큰 차원을 갖는다는 것이 일반적입니다. 만약 3차원보다 큰 다 차원의 데이터에 대해 어떤 유사도 값이 있다고 할때, 이 유사도와 비슷한 2차원 또는 3차원의 데이터를 얻을 수 있도록 학습시킨다면 아무리 큰 차원의 데이터라도 공간상에 분포시켜 가시화할 수 있게 됩니다. 바로 이런 경우에 활용할 수 있는 매우 강력한 기술이 t-SNE입니다. SNE는 Stochastic Neighbor Embedding의 약자이고 t는 정규분포와 유사한 그래프를 나타냅니다. 아래는 t 분포의 한 예에 대한 이미지입니다.

이 t-SNE를 이용하여 GAN이나 AutoEncoder 등에서 얻어지는 잠재벡터 z를 2차원 공간상에 분포시켜보는 내용을 구체적으로 살펴보겠습니다. Python의 Scikit-Learn 라이브러리에서 제공하는 t-SNE API를 이용하고, 잠재벡터 z는 아래의 AutoEncoder 포스팅에서 소개한 신경망에서 생성된 잠재벡터 값을 이용하겠습니다.

AutoEncoder

이 글에서 제시하는 최종 결과를 얻기 위해서는 위의 글을 통해 먼저 코드를 전제로 합니다. 먼저 위의 글의 소스코드에서 작성한 AutoEncdoer 신경망을 학습 한 이후에 다음의 코드를 통해 잠재 벡터 z를 얻어올 수 있습니다.

inp = transform(test_data.data.numpy().reshape(-1,28,28))
inp = inp.transpose(0,1)
inp = inp.reshape(-1,1,28,28).to(device)

z = model.encoder(inp)
z = z.detach().cpu().numpy()
y = test_data.targets.numpy()

AutoEncoder를 이해하고 있는 사람이라면 잠재벡터 z는 Encoder가 생성한 데이터라는 것을 알고 있을 것입니다. 이제 이렇게 생성한 z를 2차원 공간상에 시각화하는 위한 t-SNE 학습은 다음 코드와 같습니다.

from sklearn.manifold import TSNE
import numpy as np

tsne = TSNE(n_components=2, verbose=1, n_iter=300, perplexity=5)
tsne_v = tsne.fit_transform(z[:6000])

데이터의 양이 너무 많으면 학습 시간이 많이 소요되므로 일단 6000개만 이용해 학습하였습니다. 학습이 완료되면 z를 2차원 상에 각 z에 해당되는 원래 이미지와 함께 공간상에 시각해 보면 다음과 같습니다.

결과를 보면, 신발은 신발끼리 바지는 바지끼리.. 윗옷은 윗옷끼리 서로 그룹핑되어 분포하고 있는 것을 볼 수 있습니다. 좀더 세밀하게 관찰해보면 같은 신발이라도 신발의 세부 분류 항목으로 다시 그룹핑된다는 것입니다. 이는 AutoEncoder의 Encoder가 생성한 잠재벡터의 품질에 따라 그 성과가 달라질 것이고, 이러한 잠재벡터의 공간상 분포 가시화는 t-SNE를 통해 시각화가 가능하다라는 것입니다.

강화학습 용어

환경(Environment)은 행위자(Agent)가 행동하는 공간입니다. 어떤 상태(State)에서 행위자의 행위(Action)에 따라 새로운 상태(State)으로의 변화와 그 행위에 대한 보상(Reward)이 만들어집니다. 연속된 행위의 처음과 종료까지를 하나의 에피소드(Episode)라고 하는데, 하나의 에피소드를 통해 얻어진 보상의 합을 수익(G)이라고 합니다. 강화학습은 바로 이 수익을 최대화시키기 위한 행위가 선택될 수 있는 정책(Policy)를 강화시키는 것이 목표입니다.

환경이 미로일때, 위의 그림은 행위자가 할 수 있는 행위(Action)입니다. 즉, 미로에서 행위자가 어떤 위치(상태;State)에 있을때 위쪽, 오른쪽, 아래쪽, 왼쪽으로 이동할 수 있는 행위를 나타냅니다. s4에서 행위자가 a1이라는 행위를 했을때 항상 s1으로 이동하는 것을 보장한다면, 이러한 환경을 결정론적 환경이라고 하며, 상태전이확률이 1이라고 합니다.

행위에 대한 가치 또는 상태에 대한 가치를 파악하기 위한 그림으로 백업다이어그램(Backup-Diagram)을 사용하는데, 위의 그림처럼 처음 상태에 대해서 취할 수 있는 행위에 대한 새로운 상태로의 전이를 표현하고 있으며, 각 상태에서의 보상값도 표시하고 있습니다. 보상은 지금 당장의 상태에서 받는 것이 아니고 행위에 대한 그 다음 상태에서 받게 된다는 것에 유의해야 합니다.

강화학습에서 환경(Environment)은 보상 시스템입니다. 다른 학습과는 다르게 강화학습은 데이터로 진행되지 않고, 환경이라는 프로그래밍 시스템을 통해 이루어진다는 점이 큰 매력입니다.

웹에서 SHP 파일 생성하기

SHP 파일은 최소 3가지 파일로 구성됩니다. 좌표 데이터가 저장된 .SHP, 이 좌표 데이터로 구성된 도형에 대한 인덱스가 저장된 .SHX, 속성 데이터가 저장된 .DBF 파일입니다.

웹 GIS에서 사용자가 공간 데이터를 활용하여 또 다른 의미의 공간 데이터를 생성해 낼 수 있을 것이고, 이 새로운 공간 데이터를 SHSP 파일 형태로 저장할 수 있다면 자신의 PC에 보관하거나, 다른 사용자와 파일 수준에서 공유할 수 있을 것입니다.

FingerEyes-Xr은 NexGen 솔루션 개발에 사용된 웹 GIS 클라이언트 라이브러리입니다. 이 FingerEyes-Xr에는 SHP 파일을 생성할 수 있는 기능을 제공하는데, 이에 대한 API를 정리해 둡니다.

먼저 생성하고자 하는 도형의 종류가 포인트인지, 폴리라인인지, 폴리곤인지를 지정하는 코드가 필요합니다. 여기서는 폴리곤입니다.

let shapeType = Xr.data.ShapeType.POLYGON;

속성 데이터의 구조를 정의하기 위해 아래의 코드가 필요합니다.

let fieldSet = new Xr.data.FieldSet();

fieldSet.add(new Xr.data.Field('field1', Xr.data.FieldType.STRING, 20));
fieldSet.add(new Xr.data.Field('field2', Xr.data.FieldType.INTEGER, 7));
fieldSet.add(new Xr.data.Field('field3', Xr.data.FieldType.FLOAT, 6, 2));

총 3개의 필드를 정의했으며 각각 문자열, 정수형, 실수형입니다. 문자열의 최대 길이는 20이며, 정수형의 최대 길이는 7이고, 실수형의 최대 길이는 6이면서 소수점 최대 길이는 2입니다.

이제 SHP 생성을 위한 팩토리를 정의합니다. 앞서 정의두었던 도형의 종류와 속성의 구조를 지정합니다.

let cntFields = fieldSet.count();
let factory = new Xr.export.ESRISHPFileFactory(fieldSet, shapeType);

이제 파일에 저장할 도형 좌표와 속성을 생성하고 팩토리에 추가합니다. 총 2개를 추가합니다.

let shape, attr;

shape = new Xr.data.PolygonShapeData([
    [
        new Xr.PointD(150267, 246895), new Xr.PointD(150367, 246895), new Xr.PointD(150367, 246995)
    ],
    [
        new Xr.PointD(150467, 247095), new Xr.PointD(150367, 247095), new Xr.PointD(150367, 247195)
    ]
]);

attr = new Xr.data.AttributeRow(-1, cntFields); // -1은 의미없음
attr.setValue(0, 'A가나다B');
attr.setValue(1, 100);
attr.setValue(2, 100.12);
let row1 = new Xr.export.RowSHP(shape, attr);

shape = new Xr.data.PolygonShapeData([
    [
        new Xr.PointD(150422, 246805), new Xr.PointD(150522, 246805), new Xr.PointD(150522, 246705)
    ]
]);

attr = new Xr.data.AttributeRow(-1, cntFields); // -1은 의미없음
attr.setValue(0, 'Hello');
attr.setValue(1, 200);
attr.setValue(2, 200.12);
let row2 = new Xr.export.RowSHP(shape, attr);

factory.addRow(row1);
factory.addRow(row2);

최종적으로 SHP 파일에 저장될 바이너리 데이터는 다음 코드를 통해 얻을 수 있습니다.

let shpObj = factory.export();

위의 shpObj 객체에는 shp, shx, dbf라는 속성이 존재하며 각각 앞서 언급한 .SHP, .SHX, .DBF 파일을 구성하는 바이너리 데이터가 ArrayBuffer의 배열로 담겨 있습니다. 실제 파일로의 저장은 아래의 코드를 통해 가능합니다. 속성 데이터에 대한 문자 인코딩은 UTF-8입니다.

if (isIE()) {
    saveToFile_IE('a.shp', shpObj.shp);
    saveToFile_IE('a.shx', shpObj.shx);
    saveToFile_IE('a.dbf', shpObj.dbf);
} else {
    saveToFile_Chrome('a.shp', shpObj.shp);
    saveToFile_Chrome('a.shx', shpObj.shx);
    saveToFile_Chrome('a.dbf', shpObj.dbf);
}

위의 코드에서 언급된 isIE, saveToFile_IE, saveToFile_Chrome 함수는 다음 글을 통해 상세히 파악할 수 있습니다. 참고로 코드 중 기존의 type: ‘text/plain’을 type: ‘application/zip’으로 변경했습니다.

웹에서 Javascript 만으로 텍스트 파일 생성

웹에서 JavaScript만으로 데이터 압축하여 파일로 저장하기

몇 일전에 웹에서 자바스크립트만으로 압축 파일의 압축을 해제하는 내용을 정리했습니다. 해당 글은 아래와 같습니다.

Javascript 기반의 압축 라이브러리, jszip

이제는 다시 웹에서 사용자가 만든 어떤 데이터를 하나의 압축 파일로 만들 필요가 있어, 앞서 살펴본 압축 라이브러리를 이용해 바이너리 데이터와 텍스트 데이터를 각각 file.bin과 file.txt라는 파일명으로 하여 하나의 a.zip 파일로 압축한 후 사용자의 PC에 다운로드 하는 코드를 정리합니다.

먼저 압축하고자 하는 바이너리 데이터를 아래의 코드처럼 준비합니다.

let buffer = new ArrayBuffer(8);
let dataview = new DataView(buffer);

dataview.setInt32(0, 9438);
dataview.setFloat32(4, 3224.3224);

위의 데이터는 file.bin이라는 파일명으로 압축파일에 존재하도록 아래처럼 코드를 추가합니다.

let zip = new JSZip();
zip.file("file.bin", buffer);

앞서 언급했듯, 바이너리 뿐만 아니라 텍스트 파일도 압축 파일에 추가해 봅니다. 아래처럼요.

zip.file("file.txt", 'Hello한글Hi!');

이제 이렇게 압축된 내용을 a.zip 파일로 저장하는 코드는 다음과 같습니다.

let zipFileName = 'a.zip';
zip.generateAsync({ type: "blob" }).then(
    function (blob) {
        if (isIE()) {
            saveToFile_IE(zipFileName, blob);
        } else {
            saveToFile_Chrome(zipFileName, blob);
        }
    }
);

못보던 isIE와 saveToFile_IE, saveToFile_Chrome 함수가 보입니다. 이 놈들은 아래의 글을 참고하시면 파악할 수 있답니다.

웹에서 Javascript 만으로 텍스트 파일 생성

JavaScript로 Excel 파일 읽기

서버측의 기술없이 클라이언트 기술만으로 로컬에 저장된 엑셀 파일을 다룰 수 있는 라이브러리인 sheetjs에 대한 다양한 기능 중 엑셀 파일을 읽는 기능에 대한 JavaScript 코드를 정리해 봅니다.

이 라이브러리를 별도로 다운로드 받지 않고도 CDN을 통해 이용할 수 있습니다. 아래는 제가 이용한 CDN 접근입니다.


읽고자 하는 엑셀 파일은 다음과 같습니다.

위의 엑셀 파일을 선택하고, 선택된 엑셀 파일을 JSON 형식으로 변환해 콘솔에 출력하는 DOM 요소는 다음과 같습니다.


위의 input DOM 요소를 통해 파일을 선택할 때 발생하는 이벤트 코드는 다음과 같습니다.

function readExcel() {
    let input = event.target;
    let reader = new FileReader();

    reader.onload = function () {
        let data = reader.result;
        let workBook = XLSX.read(data, { type: 'binary' });

        workBook.SheetNames.forEach(function (sheetName) {
            console.log('SheetName: ' + sheetName);

            let rows = XLSX.utils.sheet_to_json(workBook.Sheets[sheetName]);
            console.log(JSON.stringify(rows));
        })
    };

    reader.readAsBinaryString(input.files[0]);
}

실행하고, 엑셀 파일을 선택해 보면 다음과 같은 내용이 콘솔에 표시되는 것을 확인할 수 있습니다.

SheetName: #인구수
[
   {
      "지역명":"무안군 무안읍",
      "남자":8599,
      "여자":7900,
      "가구수":6968,
      "노인수":732,
      "아저씨":1359,
      "어린이":4627
   },
   {
      "지역명":"무안군 일로읍",
      "남자":3659,
      "여자":3243,
      "가구수":3526,
      "노인수":8711,
      "아저씨":8758,
      "어린이":4099
   },

   ....

   {
      "지역명":"무안군 운남면",
      "남자":7357,
      "여자":7667,
      "가구수":3624,
      "노인수":8110,
      "아저씨":8042,
      "어린이":9618
   }
]

sheetjs 라이브러리는 엑셀을 읽는 기능 뿐만 아니라 생성도 가능하며 정교한 처리가 가능합니다.