PlanC(The Last One!)

.·2020년 11월 19일

졸업작품 회고록

목록 보기
4/7
post-thumbnail

10월말

tensorflow.js 로 모델을 만들기로결정!

Tensorflow.js 란

자바스크립트로 머신 러닝 모델을 개발 및 학습시키고 브라우저나 Node.js에 배포하는 라이브러리다.
즉, 서버가 아닌 클라이언트에서도 머신러닝, 딥러닝을 통한 학습 결과를 도출할 수 있다.

웹 기반의 그래픽 라이브러리인 "웹(WebGL) API" 를 통해 모든 GPU를 지원해 실제로 속도 개선 효과가 있다.

장점

1. 자바스크림트 언어만으로 DeepLearning, ML 등의 구현이 가능
2. 웹,앱 등 어플리케이션에서 바로 구현이 가능
3. 데이터 트레픽이 발생하지 않음
4. 네트워크가 없어 즉시 동작하여 네트워크가 없어도 동작함

TensorFLow.js 는 텐서플로우가 할 수 있는 거의모든 일을 할 수 있다.
그러나 Tensorflow.js의 대상 환경에는 일반적으로 딥러닝 학습에 사용되는 대형 엔비디아서버 GPU에 비해, 활용 가능한 GPU메모리가 많지 않으므로 브라우저 에서 실행되도록 하기 위해서는 모델의 크기를 줄여야 할 수 있다.
실제로 램이 8GB이고 내장 GPU가 없는 나의 컴퓨터에서 학습시키기 위해선 기존에 tensorflow로 만들었던 모델의 크기를 많이 줄여야 했다.
layer 6계층 이상은 힘들어 보였다.

1. convolution 레이어 구축

    function makeCNN() {
        const model = tf.sequential();
        model.add(tf.layers.conv2d({
            filters: 32,
            kernelSize: 3,
            activation: 'relu',
            inputShape: [imageSize, imageSize, 3],
            strides: 1,
            kernelInitializer: 'varianceScaling'
        }));
        model.add(tf.layers.conv2d({
            filters: 64,
            kernelSize: 3,
            activation: 'relu'
        }));
        model.add(tf.layers.maxPooling2d({
            poolSize: [2, 2]
        }));
        model.add(tf.layers.flatten());
        model.add(tf.layers.dense({
            units: 128,
            activation: 'relu'
        }));
        model.add(tf.layers.dense({
            units: label.length,
            activation: 'softmax'
        }));
        model.compile({
            loss: 'categoricalCrossentropy',
            optimizer: tf.train.adam(),
            metrics: ['accuracy']
        });
        console.log(model.summary());
        return model;
    }

이게 내가 웹브라우저로 돌릴수 있는 모델정도이다.

input data size도 기존128->24 로 줄여 학습을 시켜야 했다.
고양이 4종 기준으로 dataset 10개를 넣어 학습시켰을때, 정확도가 90%정도로, 나쁘지 않게 나와 신기했다.

모델 아키텍쳐 설명

const model = tf.sequential();
model.add(tf.layers.conv2d({
            filters: 32,
            kernelSize: 3,
            activation: 'relu',
            inputShape: [imageSize, imageSize, 3],
            strides: 1,
            kernelInitializer: 'varianceScaling'
        }));

사용한 sequential()모델은 각 레이어에 정확히 하나의 입력텐서와 하나의 출력텐서가 있는 일반 레이어 스택에 적합하다. 즉, 층을 하나씩 쌓는 것이다.
첫번째 convolution 신경망에는 input 이미지의 형태를 넣어둔다.
필터 32개, 커널사이즈 3, relu 함수, 간격은 1로 파라미터를 정의했다.

 model.add(tf.layers.maxPooling2d({
            poolSize: [2, 2]
        }));

MaxPooling Layer 로 영역의 최대값을 활용하여 다운샘플링을 진행한다.

model.add(tf.layers.flatten());

2D 형태의 필터를 1D 벡터 형태로 평평하게 하여, 마지막 layer에 인풋으로 넣을 수 있도록 flatten 시켰다.
1차원으로 각각의 필터를 펼쳐, 이미지의 특징으로 이용하겠다는 의미이다.
2424 이미지 사이즈로 시작해 max-pooling을 한 번 거쳤으니 1212
총 224개의 픽셀을 가지고 있는 셈이다.
overfitting을 방지하기 위해, flattent()은 맨 마지막 단계에서 해야한다.

 model.compile({
            loss: 'categoricalCrossentropy',
            optimizer: tf.train.adam(),
            metrics: ['accuracy']
        });

손실함수로 categoricalCrossentropy를 사용했다.
모델의 최종 결과값이 확률 분포일때 사용하는 함수이다.
모델의 마지막층에서 만들어진 확률분포와 , 우리가 주어준 진짜 레벨에서주어진 확률분포 사이의 오류를 측정한다.
즉, 실제 주어진 label과 비교했을때 얼마나 비슷한지를 예측하는 숫자를 만들어 낸다.

주요 function 3개 소개

2. makeDataset() 데이터 전처리과정

 function makeDataset(xs, ys, img) {
  
        return tf.tidy(() => {
            const t = tf.browser.fromPixels(img).resizeNearestNeighbor([imageSize, imageSize]).div(255.0).expandDims();
            (xs == undefined) ? xs = t : xs = xs.concat(t);
            label.forEach((className, index) => {
                if (className == img.id) {
                    const l = tf.oneHot(index, label.length).expandDims();
                    (ys == undefined) ? ys = l : ys = ys.concat(l);
                }
            });
            console.log(xs);
            console.log(ys);    
            return [xs, ys];
        });
        
    }

(1) xs값 구하기

const t = tf.browser.fromPixels(image) :
데이터 정규화(normalization)

  • 변경전 (img)
img id="Russianblue" src="blob:http://localhost:4000/9504234b-170f-45dd-88c4-bcd211dcf4bb"
  • 변경후 (tensorflow.js api 함수 에 의해 img를 변형시킨 변수t)
 t = {kept: false, isDisposedInternal: false, shape: Array(4), dtype: "float32", size: 1728,}

=> t 에 대한 정보를 찍어보면 img픽셀값이 들어와 이런 배열형태로 변형됬다는 것을 알 수 있다. 0~255 사이의 값으로 이루어진 이미지값을 0~1 사이의 값으로 바꾸는 과정이다.

(2) ys값 구하기

const l = tf.oneHot(index, label.length).expandDims();
라벨 값 원핫 인코딩으로 변경

  • 여기서 index 는 Bengal, Boombey, Ragdoll, Russianblue 를 의미한다. 각 0,1,2,3
    label.length 는 총4종이니 4이다.
  • One-hot Encoding 이 머신러닝 에서는 변수의 성질을 파악하여 적절히 숫자로 변형해 주는 역활을 한다. 여기서는 4개의 식별이 필요하니 [[[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]]] 이렇게 0 또는 1로만 이루어진 벡터로 값을 수정해야 한다.
 l = {kept: false, isDisposedInternal: false, shape: Array(2), dtype: "int32", size: 4,}

(3) return [xs, ys];


xs,ys 가 각각 t,l 로 변경된 값으로 리턴해준다.
이는 신경망 훈련에 사용할 입력과 출력 데이터이다.

3. train() 학습시키기

 async function train() {
        btnBoard.style.display = 'none';
        testBoard.style.display = 'block';
        await console.log('Prepare model...');
        model = await makeCNN();
        await console.log('Model loaded.');
        let xs, ys;
        for (img of imgAry) {
            [xs, ys] = await makeDataset(xs, ys, img);
        }
        await console.log('Dataset ready.');
        await model.fit(xs, ys, {
            epochs: 10,
            callbacks: { onBatchEnd }
        });
        await console.log('Done');
    }

위의 makedataset()에 의해 데이터셋이 만들어 지면,
훈련이 이루어지는데, 이때 Neural Network는 주어진 입력에 대해 주어진 출력값에 더 가까운 값을 출력하게 된다.
에포크(epoch)은 알고리즘이 전체 훈련 데이터셋을 반복해서 학습하는 횟수 의미이다. 여기서는 빠른 학습을 위해 10으로 잡았다.
batch size는 하나의 배치안에 존재하는 training example 개수이다.

epoch = 10 , dataset개수 = 각10개 로 학습시킨 결과

상당히 정확도가 괜찮타...!

4. predict() 예측하기

async function predict() {
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = 'multiple';
        input.onchange = evt => {
            console.log(`Added ${evt.target.files.length} test images.`);
            for (let f of evt.target.files) {
                const url = URL.createObjectURL(f);
                const img = new Image();
                img.onload = async () => {
                    const x = await tf.browser.fromPixels(img).resizeNearestNeighbor([imageSize, imageSize]).div(255.0).expandDims();
                    const predict = await model.predict(x).data();
                    const prob = await Math.max.apply(null, predict);
                    const className = label[predict.indexOf(prob)];
                    console.log('My prediction : %s', className);
                    console.log('Probability : %s', prob.toFixed(4));
                    const d = document.createElement('div');
                    d.innerHTML = `${className} ${prob.toFixed(2) * 100}%`;
                    testBoard.appendChild(img);
                    testBoard.appendChild(d);
                }
                img.src = url;
            }
        }
        input.click();
    }

특정 입력에 대해 Neural Network가 출력(예측)하는 값을 얻을 수 있다.

나는 웹브라우저에서 파일을 열어 이미지를 로드하는 방식이니까 html 의 createElement() 속성을 사용하여 input태그를 추가해 file로 설정해준다.

const x = await tf.browser.fromPixels(img).resizeNearestNeighbor([imageSize, imageSize]).div(255.0).expandDims();

처음에 이미지 전처리 할때와 같은 방법으로 예측할 이미지를 전처리 하여 x변수에 넣어주고,model.predict(x) 함수로 예측해 준다.
그다음 예측된 결과를 잘 처리하여, className(예측결과)과 prob(%)로 표시해 준다.


예측결과 이렇게 95%의 확률로 ragdoll이 올바르게 잘 나왔다!


쫌 아쉬운 점은, 학습시킨 데이터 셋이 아니면 정확도가 쫌 떨어진다는 것이다.
그래도 이렇게 간단한 신경망에 적은 데이터 셋으로 학습시킨 결과 치고 매우 만족스럽다.

python으로 만든 모델을 커팅하지 않고 tensorflow.js로 성공적으로 옮길수 있는 방법이 있을까...?

profile
yi

0개의 댓글