2021년 10월 26일 화요일

Tensorflow GPU 실행 시 CuDNN 버전 문제 해결 방법

이 글은 Tensorflow GPU 실행 시 CuDNN 버전 문제 해결 방법을 간략히 설명한다. 텐서플로우나 케라스 실행 시 다음과 같은 에러를 만났을 때 해결방법이다. 

CuDNN library needs to have matching major version

우선 버전 에러 해결에 적합한 텐서플로우 버전을 아래 링크에서 확인한다.
가상환경(virtualenv)을 사용한다는 가정하에, 기존 가상환경은 삭제하고, 다음과 같이 다시 설치한다. 
pip install tensorflow=2.4.0
pip install tensorflow-gpu=2.4.0
pip install keras==2.4.0
pip install jupyter

이제 쥬피터 노트북에서 실행하면 다음 같은 딥러닝 코드가 잘 실행되는 것을 확인할 수 있다.

# LSTM for international airline passengers problem with regression framing
import numpy
import matplotlib.pyplot as plt
from pandas import read_csv
import math
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error

import tensorflow as tf
tf.config.list_physical_devices('GPU') 
...

레퍼런스

2021년 10월 21일 목요일

3차원 VFX 특수효과 영화 제작시 사용되는 Blender와 Fusion Studio 소개

이 글은 3차원 특수효과 영화 제작시 사용되는 Blender와 Fusion Studio 에 대한 간략한 소개이다. 영화나 영상 특수효과에서 기존에는 Adobe나 Autodesk 제품군을 많이 사용했었다. 하지만, 가성비 좋은 블랜더와 퓨전 스튜디오가 블록버스터 영화, 광고, 티비에 크게 사용이 되면서, 이 도구에 대한 관심이 높아졌다. 

블랜더는 오픈소스 기반 3차원 모델링, 애니메이션 및 특수효과 처리 소프트웨어이며, 퓨전은 특수효과, 후처리, 렌더링 전용 소프트웨어이다. 

이 도구들을 활용하면, 촬영한 영상에 블랜더에서 3D로 모델링된 UFO를 합성하고, 퓨전에서 섬광이나 화염과 같은 후처리 특수효과를 합성할 수 있다.
블랜더
퓨전 스튜디오

사용방법은 다음과 같다.
퓨전 스튜디오

다음은 이런 도구를 사용해 만든 영화들이다.

2021년 10월 13일 수요일

트랜스포머 딥러닝 모델의 이해 및 간단한 사용방법

이 글은 자연어 처리 번역에 매우 유용한 트랜스포머 딥러닝 모델의 이해 및 간단한 사용방법을 공유한다.



트랜스포머 모델 개념
논문 'Attention Is All You Need'는 변환기와 시퀀스-투-시퀀스 아키텍처에 대해 설명한다. Sequence-to-Sequence(Seq2Seq)는 문장의 단어 시퀀스와 같은 주어진 시퀀스를 다른 시퀀스로 변환하는 신경망이다.

Seq2Seq 모델은 한 언어의 단어 시퀀스가 ​​다른 언어의 다른 단어 시퀀스로 변환되는 번역에 특히 좋다. 이 유형의 모델에 널리 사용되는 LSTM(장단기 메모리)은 시퀀스에 의미를 부여할 수 있다. 예를 들어 문장은 단어의 순서가 문장을 이해하는 데 중요하기 때문에 순서에 따른 의미를 훈련시킬 수 있다.

Seq2Seq 모델은 인코더와 디코더로 구성된다. 인코더는 입력 시퀀스를 가져와 더 높은 차원 공간(n차원 벡터)에 매핑한다. 출력 시퀀스는 다른 언어, 기호, 입력 사본 등일 수 있다.

'Attention Is All You Need'라는 논문은 Transformer라는 아키텍처를 소개한다. LSTM과 마찬가지로 Transformer는 두 부분(Encoder 및 Decoder)으로 한 시퀀스를 다른 시퀀스로 변환하는 아키텍처이지만,  RNN(Recurrent Neural Networks) 없이 어텐션(주의) 메커니즘만 있는 아키텍처로 성능을 개선할 수 있음을 증명했다. 

이 모델의 인코더는 왼쪽에 있고 디코더는 오른쪽에 있다. Encoder와 Decoder는 모두 여러 번 서로 겹쳐질 수 있는 모듈로 구성되며, Nx 로 설명된다. 모듈이 주로 Multi-Head Attention 및 Feed Forward 레이어로 구성되어 있다. 문자열을 직접 사용할 수 없기 때문에 입력과 출력(목표 문장)은 먼저 n차원 공간에 매핑된다.

시퀀스가 모델에 공급되는 방식을 기억할 수 있는 순환 네트워크가 없기 때문에 시퀀스의 모든 단어/부분에 상대적인 위치를 정의해야 한다. 이러한 위치는 각 단어의 포함된 표현(n차원 벡터)에 추가된다.

모델의 실행 순서 예는 다음과 같다. 
  • 전체 인코더 시퀀스(예. 프랑스어 문장)를 입력하고 디코더 입력으로, 첫 번째 위치에 문장 시작 토큰만 있는 빈 시퀀스를 사용한다. 
  • 해당 요소는 디코더 입력 시퀀스의 두 번째 위치에 채워지며,  문장 시작 토큰과 첫 번째 단어/문자가 들어 있다.
  • 인코더 시퀀스와 새 디코더 시퀀스를 모두 모델에 입력한다. 출력의 두 번째 요소를 가져와 디코더 입력 시퀀스에 넣는다.
  • 번역의 끝을 표시하는 문장 끝 토큰을 예측할 때까지 이 작업을 반복한다.
사용 예시
사용 예시를 간단히 확인해 본다. 이를 위해 아래 링크를 참고하도록 한다.



2021년 10월 12일 화요일

pyLiDAR-SLAM 사용하기

이 글은 라이다 스캔을 통해 얻은 데이터를 이용해 실시간 SLAM 처리하는 알고리즘을 파이썬으로 라이브러리화한 pyLiDAR-SLAM 사용 방법을 간략히 공유한다.


설치
우분투 리눅스 운영체제에서 실행하므로, 아래 링크 등을 참고해 실행 환경을 미리 준비한다.
다음 메뉴얼을 참고해 설치한다. 의존하고 있는 패키지 pycp_icp (python3 binding 필요), pyviz3d, opencv, g2o, open3d 모두 메뉴얼을 참고해 설치한다.

설치 시 여러가지 에러가 발생한다. 아래를 참고해 해결해 나간다.
  • 설치하기 전에 패키지 의존 에러가 발생할 수 있으므로, 가상환경을 터미널에서 아래와 같이 활성화한다. 
pip install --force-reinstall virtualenv
pip3 install --force-reinstall virtualenv
virtualenv venv_pylidar
source ./venv_pylidar/bin/activate 
  • cmake 처리 시 virtualenv를 제대로 인식해 python3를 못찾을 수 있다. 이 경우, 아래와 같이 경로를 지정해 준다.
cmake .. -DPYTHON_EXECUTABLE=/home/ktw/Projects/pyLiDAR-SLAM/venv_pylidar/bin/python3 

  • 의존 패키지가 사용하는 github 소스코드 빌드 처리를 위해, github 로그인을 위한 계정, RSA 암호 키 등을 아래 링크를 참고해 터미널에서 설정한다.

  • 몇몇 의존 패키지에서 사용하는 라이브러리에 에러가 발생할 수 있다. 아래는 이와 관련해 설치한 라이브러리이다.
pip install hydra-core --upgrade
pip install pygame 
pip install PyOpenGL
pip install PyOpenGL-accelerate
pip install omegaconf 

  • Eigen 패키지에서 버전 차이로 인해 Quaternion에서 컴파일 에러가 발생할 수 있다. 아래와 같이 처리한다.

/* .def("x", (NonConstCoeffReturnType (Eigen::Quaterniond::*) () const) &Eigen::Quaterniond::x())
        .def("y", (NonConstCoeffReturnType (Eigen::Quaterniond::*) () const) &Eigen::Quaterniond::y())
        .def("z", (NonConstCoeffReturnType (Eigen::Quaterniond::*) () const) &Eigen::Quaterniond::z())
        .def("w", (NonConstCoeffReturnType (Eigen::Quaterniond::*) () const) &Eigen::Quaterniond::w()) */

설치가 끝나면 다음과 같다.

사용
사용 방법을 확인하기 위해 예제 데이터를 프로젝트 폴더에 다운받고, 다음을 실행한다.
python3 run.py -m num_workers=1 slam/initialization=NI slam/preprocessing=none slam/odometry=ct_icp_robust_shaky, icp_odometry slam.odometry.viz_debug=True slam/loop_closure=none slam/backend=none dataset=rosbag dataset.main_topic=horizontal_laser_3d dataset.accumulate_scans=True  dataset.file_path=./b3-2016-04-05-15-51-36.bag hydra.run.dir=./TEST_DOC

혹은 아래와 같이 실행할 수도 있다.
python3 run.py num_workers=1 slam/initialization=NI slam/preprocessing=none slam/odometry=ct_icp_robust_shaky slam.odometry.viz_debug=True slam/loop_closure=none slam/backend=none dataset=rosbag dataset.main_topic=horizontal_laser_3d dataset.accumulate_scans=True  dataset.file_path=./b3-2016-04-05-15-51-36.bag hydra.run.dir=./TEST_DOC

참고로, 이 프로그램의 형식은 다음과 같다.
python3 run.py num_workers=1 /  # The number of process workers to load the dataset
    slam/initialization=NI /              # The initialization (NI=No Initialization / CV=Constant Velocity)
    slam/preprocessing=grid_sample /    # Preprocessing on the point clouds
    slam/odometry=icp_odometry /        # The Odometry algorithm
    slam.odometry.viz_debug=True /      # Whether to launch the visualization of the odometry
    slam/loop_closure=none /            # The loop closure algorithm selected (none by default)
    slam/backend=none /                   # The backend algorithm (none by default)
    dataset=rosbag /                           # The dataset selected (a simple rosbag here)
    dataset.main_topic=horizontal_laser_3d /    # The pointcloud topic of the rosbag 
    dataset.accumulate_scans=True /                 # Whether to accumulate multiple messages  
    dataset.file_path=./b3-2016-04-05-15-51-36.bag /    #  The path to the rosbag file 
    hydra.run.dir=./TEST_DOC        #  The log directory where the trajectory will be saved

실행 시 다음과 같이 hydra 패키지에서 에러가 발생할 수 있다.

사용 후에는 반듯이 가상환경을 빠져나와 패키지 설치 환경이 오염되지 않도록 한다.
deactivate

레퍼런스

2021년 10월 7일 목요일

임베디드보드 및 웹 기반 딥러닝 모델 실행을 위한 Tensorflow Lite 사용하기

이 글은 임베디드보드에서 딥러닝 모델 실행을 위한 Tensorflow Lite 및 Tensorflow JS (TFJS) 사용 방법을 간략히 공유한다. 이 내용은 딥러닝 기반 에지(Edge) 컴퓨팅 구현이나 가벼운 웹브라우저 기반 딥러닝 응용 서비스 개발에 도움이 된다. 

머리말
텐서플로우 라이트와 TFJS는 개발자가 모바일, 임베디드 장치, IoT, 웹브라우저에서 딥러닝 모델을 실행할 수 있도록, 모델을 최적화하는 도구이다. 특징은 다음과 같다.
  • 특정 장치에서 실행되도록 딥러닝 모델을 최적화함. 모델 크기 및 전력 소모 축소.
  • 다중 플랫폼 지원. 안드로이드, iOS, 임베디드, Javascript 웹브라우저, 리눅스 등.
  • 하드웨어 가속 지원.

실행 개념
텐서플로우 라이트 실행 환경은 목표하는 플랫폼에 따라 다르다. 예를 들어, 임베디드 중 아두이노에서 개발한다면, 이에 대한 라이브러리가 준비되어 있어서, 이를 사용하면 된다. 
사용은 매우 쉬운 편인데, 기존 텐서플로우 딥러닝 모델을 작업한 후, 라이트버전으로 변환하면 된다. 변환함수를 당연히 제공하고, 변환시 모델의 실수 데이터 형식은 정수로 자동 변환된다. 참고로, 정수는 실수보다 크기가 4배 적고, 처리 속도는 훨씬 빠르다. 이런 이유로, GPU가 없는 컴퓨터에서도 딥러닝 모델을 빠르게 실행시킬 수 있다. 물론, 이 변환 후에는 실수형 연산 함수는 사용할 수 없다(API 호환성). 몇몇 플랫폼은 연산가속칩이 있다. 라이트는 이 기능을 이용할 수 있다.

변환된 모델은 FlatBuffers형식인 .tflite 확장자인 파일로 저장된다. 이 파일은 메타데이터를 포함할 수 있고, 이 경우, 텐서플로우 라이트 API를 사용하지 않고, 파일 로딩 후 바로 실행할 수 있다.

이제, 각 실행환경에서 사용방법을 확인해 본다.

CoLab기반 딥러닝 모델 생성 및 최적화
CoLab은 구글에서 지원하는 머신러닝 협업 및 연구용 플랫폼으로 대부분 기능이 무료이다. 
여기서는 CoLab을 사용해 MNIST를 이용해 CNN모델을 만들고, 이를 라이트버전으로 처리해본다. 직접 실행여기를 참고한다. 

다음 코드를 CoLab에 입력해 실행해 본다. 최적화하는 부분인 TFLiteConverter 함수 사용만 다르고, 나머지는 일반적인 텐서플로우 코딩과 같다는 것을 알 수 있다.

# 라이브러리 로딩
import logging
logging.getLogger("tensorflow").setLevel(logging.DEBUG)

import tensorflow as tf
from tensorflow import keras
import numpy as np
import pathlib

# MNIST 로딩
mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# 입력 이미지 정규화 
train_images = train_images / 255.0
test_images = test_images / 255.0

# 딥러닝 모델 구조 정의
model = keras.Sequential([
  keras.layers.InputLayer(input_shape=(28, 28)),
  keras.layers.Reshape(target_shape=(28, 28, 1)),
  keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),
  keras.layers.MaxPooling2D(pool_size=(2, 2)),
  keras.layers.Flatten(),
  keras.layers.Dense(10)
])

# 필기체 이미지 분류 모델 학습. 1세대만 처리.
model.compile(optimizer='adam',
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.fit(
  train_images,
  train_labels,
  epochs=1,
  validation_data=(test_images, test_labels)
)

# TFLite버전으로 모델 변환
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

tflite_models_dir = pathlib.Path("/tmp/mnist_tflite_models/") # 폴더 생성
tflite_models_dir.mkdir(exist_ok=True, parents=True)

tflite_model_file = tflite_models_dir/"mnist_model.tflite"
tflite_model_file.write_bytes(tflite_model)  # TFLite 파일로 파일 저장

# 최적화 옵션 켜서 모델 저장
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()
tflite_model_quant_file = tflite_models_dir/"mnist_model_quant.tflite"
tflite_model_quant_file.write_bytes(tflite_quant_model) 

!ls -lh {tflite_models_dir}  # CoLab 확인해 보면, 실제 최적화된 모델은 기존 모델의 1/4임

이제 저장된 TFLite 모델을 실행해보자. 아래 코드를 CoLab에 입력해 실행해 본다.
interpreter = tf.lite.Interpreter(model_path=str(tflite_model_file))  
interpreter.allocate_tensors()            # 최적화 안된 TFLite

interpreter_quant = tf.lite.Interpreter(model_path=str(tflite_model_quant_file))
interpreter_quant.allocate_tensors()   # 최적화된 TFLite

test_image = np.expand_dims(test_images[0], axis=0).astype(np.float32)  # 첫번째 테스트 이미지

input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]

interpreter.set_tensor(input_index, test_image)
interpreter.invoke()  # 모델 실행
predictions = interpreter.get_tensor(output_index)  # 예측 결과 얻기

import matplotlib.pylab as plt

plt.imshow(test_images[0])
template = "True:{true}, predicted:{predict}"
_ = plt.title(template.format(true= str(test_labels[0]),
                              predict=str(np.argmax(predictions[0]))))
plt.grid(False)

TFLite 모델 정확도를 평가해 보자. 아래 코드를 입력해 본다.

# TF Lite 모델 평가용 유틸리티 함수
def evaluate_model(interpreter):
  input_index = interpreter.get_input_details()[0]["index"]
  output_index = interpreter.get_output_details()[0]["index"]

  # 테스트 데이터셋 예측
  prediction_digits = []
  for test_image in test_images:
    # 전처리는 배치로 32비트 실수를 모델 데이터 형식으로 변환하도록 함
    test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
    interpreter.set_tensor(input_index, test_image)

    # 모델 예측 실행
    interpreter.invoke()

    # 후처리는 가장 높은 정확도 probability 클래스를 찾음
    output = interpreter.tensor(output_index)
    digit = np.argmax(output()[0])
    prediction_digits.append(digit)

  # 그라운드 참값과 차이 비교
  accurate_count = 0
  for index in range(len(prediction_digits)):
    if prediction_digits[index] == test_labels[index]:
      accurate_count += 1
  accuracy = accurate_count * 1.0 / len(prediction_digits)

  return accuracy

print(evaluate_model(interpreter))
print(evaluate_model(interpreter_quant))

실제 평가해 보면, 각각 0.9671, 0.9669로 별 차이가 없다.

Javascript 웹 기반 딥러닝 실행
자바스크립트 웹 기반 딥러닝 실행을 위해, TFJS 라이브러리를 사용한다. 우선, 자바스크립트 node.js 를 이용해 딥러닝 모델을 작성한 후 실행해 본다(참고 - 여기와 TFJS Task API).

우선 터미널에서 아래 명령을 실행한다. 
mkdir tfjs
cd tfjs
npm install @tensorflow/tfjs-node
npm install @tensorflow/tfjs-node-gpu

demo.js 코드를 아래와 같이 작성한다.
const tf = require('@tensorflow/tfjs');  // tfjs 임포트

const model = tf.sequential();               // 시퀀스 모델 생성
model.add(tf.layers.dense({units: 100, activation: 'relu', inputShape: [10]}));    
model.add(tf.layers.dense({units: 1, activation: 'linear'})); 
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});

const xs = tf.randomNormal ([100, 10]);   // 학습 입력 데이터
const ys = tf.randomNormal ([100, 1]);     // 결과 라벨 데이터

model.fit (xs, ys, 
{epochs : 100, 
callbacks : {onEpochEnd : (epoch, log) => console.log ( epoch + " = " + log.loss )} 
});  // 학습

터미널에서 아래 명령을 실행한다. 학습이 잘 되는 것을 확인할 수 있다.
node demo.js

웹브라우저에서 딥러닝 모델을 사용하는 방법을 확인해 보기 위해, 우분투 리눅스 터미널에서 아래 명령을 실행하고 관련 예제를 다운로드한다.

git clone https://github.com/tensorflow/tfjs-models
cd tfjs-models

테스트할 딥러닝 모델을 다운로드 설치한다. 기타 필요한 모델은 여기서 확인한다.
npm i @tensorflow-models/coco-ssd
npm i @tensorflow-models/mobilenet
npm i @tensorflow-models/deeplab

yarn 프로젝트 관리 및 빌드 도구를 설치한다. yarn에 대한 설명은 여기를 참고하라.
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
sudo npm install --global cross-env

패키지를 빌드한다.
yarn
yarn presubmit

설치된 딥러닝 모델 테스트를 위해 아래 명령을 실행한다.
cd coco-ssd
cd demo
yarn
yarn build-deps

yarn run
watch

웹서버가 실행되었다면, http://localhost:1234/ 에서 결과를 확인해 본다. 다음과 같이 정상적으로 웹브라우저에서 딥러닝이 실행되는 것을 확인할 수 있다.

COCO-SSD 모델의 demo 폴더 구조는 다음과 같다. 

주요 코드는 다음과 같다. 아래 index.html에서 웹화면 레이아웃을 정의한다.
 
<!doctype html>
<html>

<body>
  <h1>TensorFlow.js Object Detection</h1>
  <select id='base_model'>
    <option value="lite_mobilenet_v2">SSD Lite Mobilenet V2</option>
    <option value="mobilenet_v1">SSD Mobilenet v1</option>
    <option value="mobilenet_v2">SSD Mobilenet v2</option>
  </select>
  <button type="button" id="run">Run</button>
  <button type="button" id="toggle">Toggle Image</button>
  <div>
    <img id="image" />
    <canvas id="canvas" width="600" height="399"></canvas>
  </div>
</body>
<script src="index.js"></script>

</html>

실제 동작코드는 index.js 자바스크립트에 코딩된다.
import '@tensorflow/tfjs-backend-cpu';      // tfjs 패키지 임포트
import '@tensorflow/tfjs-backend-webgl';

import * as cocoSsd from '@tensorflow-models/coco-ssd';  // coco-ssd 모델 임포트

import imageURL from './image1.jpg';      // 폴더에 정의된 이미지 임포트
import image2URL from './image2.jpg';

let modelPromise;

window.onload = () => modelPromise = cocoSsd.load();

const button = document.getElementById('toggle');   // 토글버튼 클릭시 이미지 교체
button.onclick = () => {
  image.src = image.src.endsWith(imageURL) ? image2URL : imageURL;
};

const select = document.getElementById('base_model');
select.onchange = async (event) => {
  const model = await modelPromise;   // 모델 선택 변경 시 모델 재로딩
  model.dispose();
  modelPromise = cocoSsd.load(
      {base: event.srcElement.options[event.srcElement.selectedIndex].value});
};

const image = document.getElementById('image');
image.src = imageURL;

const runButton = document.getElementById('run');
runButton.onclick = async () => {   // 실행 버튼 클릭시, 이미지 입력 후 모델 예측 실행
  const model = await modelPromise;
  console.log('model loaded');
  console.time('predict1');
  const result = await model.detect(image);
  console.timeEnd('predict1');


  const c = document.getElementById('canvas');  // 캔버스 요소 획득 후 이미지 렌더링
  const context = c.getContext('2d');
  context.drawImage(image, 0, 0);
  context.font = '10px Arial';

  console.log('number of detections: ', result.length);   // 예측 결과 이미지 및 라벨 렌더링
  for (let i = 0; i < result.length; i++) {
    context.beginPath();
    context.rect(...result[i].bbox);
    context.lineWidth = 1;
    context.strokeStyle = 'green';
    context.fillStyle = 'green';
    context.stroke();
    context.fillText(
        result[i].score.toFixed(3) + ' ' + result[i].class, result[i].bbox[0],
        result[i].bbox[1] > 10 ? result[i].bbox[1] - 5 : 10);
  }
};

TFJS 라이브러리가 대부분의 딥러닝 모델 사용 과정을 캡슐화하여, 실제 사용되는 함수는 그리 많지 않음을 알 수 있다. 직접 모델을 훈련하고, 웹에서 서비스를 제공하기 위한 목적으로 아래 Tutorial을 참고할 수 있다.

마무리
TFLite를 이용하면, GPU가 없는 다양한 플랫폼에서 적은 메모리와 정확도 손실로 딥러닝 모델을 실행할 수 있다.

레퍼런스

2021년 10월 6일 수요일

CNN 딥러닝 파라메터에 따른 예측 정확도 변화 확인

이 글은 CNN(Convolution Neural Network) 딥러닝 파라메터에 따른 예측 정확도 변화 확인 결과를 간략히 정리한다. 딥러닝 모델은 파라메터가 어떻게 되느냐에 따라 정확도가 달라진다. 얼마나 차이가 나는지를 확인해 보기 위해, CNN 모델을 정의하고, 파라메터를 조정해 본다. 

기본 모델 정의
기본 모델은 다음과 같다. 이 모델은 주어진 이미지를 2개의 클래스로 구분한다. 
# Our input feature map is 150x150x3: 150x150 for the image pixels, and 3 for
# the three color channels: R, G, and B
img_input = layers.Input(shape=(150, 150, 3))
x = layers.Conv2D(16, 3, activation='relu')(img_input)
x = layers.MaxPooling2D(2)(x)
x = layers.Conv2D(32, 3, activation='relu')(x)
x = layers.MaxPooling2D(2)(x)
x = layers.Conv2D(64, 3, activation='relu')(x)
x = layers.MaxPooling2D(2)(x)
x = layers.Flatten()(x)
x = layers.Dense(512, activation='relu')(x)
output = layers.Dense(1, activation='sigmoid')(x)
model = Model(img_input, output)

실제 학습은 아래 CoLab으로 진행해본다.

대표적인 CNN 분류 예시

모델 파라메터 조정 결과
CNN 커널 크기와 계층수를 변화시켜보도록 한다. 모델 파라메터를 조정한 결과는 다음과 같다.


커널 3계층 표준



커널 크기 증가1



커널 크기 증가2



커널 2계층


커널 4계층
결론
결과는 CNN 필터를 배수로 증가시켜가면서 모델을 추가했을 때 정확도가 높았다. 또한, 커널 계층을 삭제하거나 증가할때 정확도나 손실은 좋아지지 않았다. 이런 파라메터는 입력 데이터 특성 등에 따라 조정해 나가야 한다. 참고로, 이런 노가다는 AutoML같은 최적 모델 선택 도구를 통해 줄일 수 있다.

레퍼런스
  • 텐서플로우 라이트 저장
  • AutoML

2021년 10월 4일 월요일

CDE 개발 지원 오픈소스 BIM 라이브러리 IFC.js 분석 및 사용방법

이 글은 CDE(Common Data Environment) 개발 지원 BIM(Building Information Modeling) 라이브러리 IFC.js 분석 및 사용방법을 간략히 설명한다. IFC는 3차원 건설 정보 모델로 건설 부재의 객체별로 형상과 속성을 저장할 수 있다. 

IFC.js는 최근 개발된 IFC관련 라이브러리로 이를 이용한 개발 사례가 조금씩 나타나고 있다.
IFC.js 활용 BIM-GIS 기반 CityLite 개발 사례

IFC.js 소개
IFC.js는 BIM 개방형 표준파일포맷인 IFC(Industry Foundation Classes)파일을 로딩, 디스플레이 및 편집할 수 있는 자바스크립트 라이브러리이다. 이 라이브러리는 안토니오 곤잘레스(Antonio Gonzalez Viegas)에 의해 개발이 주도되고 있다. 참고로, 그는 스페인 2018년 ETSAS 건축과를 졸업하고, BIM 연구 개발 경험이 많은 디지털 아키텍처이며, 재즈를 좋아한다.
IFC는 읽고 쓰기 어려운 복잡한 포맷이다. 실제 IFC 구조는 다음과 같다. 이 파일 포맷을 해석(parse, 파싱)하기 위한 여러 라이브러리가 이미 오래전부터 있었다. IFC.js는 웹기반으로 동작되는 순수 Javascript와 빠른 렌더링 성능을 지원하는 C++ WASM(Web assembly. 예제)기술을 이용해 개발되었다. 이를 통해 웹브라우져에서도 60 fps까지 렌더링 처리 된다. 그러므로, 스마트폰을 포함한 모든 컴퓨터 디스플레이에서 IFC.js 기능을 사용할 수 있다.

소스코드는 아래 github에 있으며, node, npm으로 실행할 수 있다.

설치
node.js, npm 패키지를 설치한다. 윈도우, 우분투, 리눅스 보두 가능하다.
node.js 설치(윈도우 버전 경우)

우분투 리눅스나 윈도우 파워쉘 터미널에서 아래 명령을 실행한다.
node -v
npm -v

버전이 제대로 출력되면, 아래 명령을 실행한다.
cd web-ifc-viewer
npm i yarn
npm run init-repo
npm run start

실행 화면(터미널)

실행 후 다음 링크를 클릭해, 클롬에서 열어본다.
그럼, 다음과 같은 IFC BIM viewer 화면을 확인할 수 있다. 

IFC.js 사용 방법
뷰어에서 왼쪽 side bar에서 파일 열기 메뉴를 통해, IFC 파일을 로딩할 수 있다.

주요 API 사용 방법은 다음과 같다.
// 라이브러리 임포트
import { IfcLoader } from 'web-ifc-three';
import { Scene } from 'three';

// THREE.js scene 생성
const scene = new Scene();

// IFC 로딩 및 scene 추가
const ifcLoader = new IfcLoader();
ifcLoader.load(ifcURL, (geometry) => scene.add(geometry));

이외에 BIM 속성 검색, 자료값 얻기, 섹션 표시 등 다양한 기능을 지원한다.

BIM 뷰어 화면 구조 분석
index.html은 다음과 같이 간단히 정의되어 있다. side bar와 viewer 요소가 정의되어 있다.
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="./styles.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IFC.js</title>
  </head>
  <body>
    <aside class="side-menu" id="side-menu-left"></aside>
      <div id="viewer-container"></div>
    <script type="module" src="./build/main.js"></script>
  </body>
</html>

화면 구조는 다음과 같다.

대부분의 기능은 main.js 자바스크립트에 구현되어 있다. 
이 main.js는 10만라인이 넘게 구현된 자바스크립트 파일이며, IFC로딩, 렌더링, 이벤트처리, 계산 등 대부분의 기능이 구현되어 있다.
시퀀스 구조 분석
주요 시퀀스 구조는 다음과 같다.
1. HTML 렌더링
2. VIEWER WEBGL 렌더링
3. OPEN FILE 이벤트 발생
4. loadIFC(url)
5. url이 가리키는 파일로 부터 파싱해 IFC element 데이터 구조 생성
6. 렌더링 가속을 위해 IFC element의 geometry 추출하여 메쉬(mesh) 그래픽 구조 생성
7. load 완료 시 scene 렌더링 루프에 추가
8. scene의 geometry의 vertex buffer를 WASM에 전달해 WebGL로 렌더링
 
주요 루틴은 다음과 같다.
class IFCLoader extends Loader {
  load(url, onLoad, onProgress, onError) {
    const scope = this;
    const loader = new FileLoader(scope.manager);
    loader.setPath(scope.path);
    loader.setResponseType('arraybuffer');
    loader.setRequestHeader(scope.requestHeader);
    loader.setWithCredentials(scope.withCredentials);
    loader.load(url, async function (buffer) {

      try {
        onLoad(await scope.parse(buffer));  // 1. IFC 데이터가 있는 buffer를 파싱함
      }
    }, onProgress, onError);
  }

  parse(buffer) {
    return this.ifcManager.parse(buffer);    // 2. ifcManager 객체의 parse 함수 호출
  }
}

async parse(buffer) {   // 파일 구조 해석
  if (this.state.api.wasmModule === undefined)
    await this.state.api.Init();
  await this.newIfcModel(buffer);  // Ifc모델 생성
  this.loadedModels++;
  return this.loadAllGeometry();    // Geometry 정보 해석
}

async loadAllGeometry() {
  await this.saveAllPlacedGeometriesByMaterial();   // Mesh 그래픽 구조 생성
  return this.generateAllGeometriesByMaterial();
}

async saveAllPlacedGeometriesByMaterial() {
  const flatMeshes = await this.state.api.LoadAllGeometry(this.currentWebIfcID);
  for (let i = 0; i < flatMeshes.size(); i++) {
    const flatMesh = flatMeshes.get(i);
    const placedGeom = flatMesh.geometries;
    for (let j = 0; j < placedGeom.size(); j++) {
      await this.savePlacedGeometry(placedGeom.get(j), flatMesh.expressID);
    }
  }
}

이벤트 처리는 다음과 같다.
const loadButton = createSideMenuButton('./resources/folder-icon.svg');
loadButton.addEventListener('click', () => {
  loadButton.blur();
  inputElement.click();  // IFC 파일 열기
});

const sectionButton = createSideMenuButton('./resources/section-plane-down.svg');
sectionButton.addEventListener('click', () => {
  sectionButton.blur();
  viewer.toggleClippingPlanes();  // 클리핑 처리
});

const dropBoxButton = createSideMenuButton('./resources/dropbox-icon.svg');
dropBoxButton.addEventListener('click', () => {
  dropBoxButton.blur();
  viewer.openDropboxWindow();  // 드롭박스 처리
});

const handleKeyDown = (event) => {
  if (event.code === 'Delete') {
    // viewer.removeClippingPlane();
    viewer.dimensions.delete();     // 치수 삭제
    viewer.context.ifcCamera.unlock();
  }
  if (event.code === 'KeyH') {
    viewer.context.ifcCamera.goToHomeView();  // 카메라 홈 위치로 이동
  }
  if (event.code === 'KeyD') {
    viewer.context.ifcCamera.setNavigationMode(NavigationModes.FirstPerson);  // 1인칭 시점 카메라 이동
  }
  if (event.code === 'Escape') {
    window.onmousemove = viewer.IFC.prePickIfcItem;  // Ifc 요소 선택 모드
  }
  if (event.code === "KeyP") {
    viewer.context.ifcCamera.toggleProjection();  // 카메라 프로젝션 모드
  }
};

정적 구조 분석
정적 구조를 분석하기 위해 UML(Unified Modeling Language)로 역설계해 보았다.
패키지 구조

앞서 설명된 라이브러리 이외에 애니메이션 엔진인 GSAP(GreenSock animation platform) 코드가 일부 사용되었다.
GASP

주요 클래스는 다음과 같다. IFCManager가 IfcAPI, model, parser 등 전체를 관리하고, Loader가 파일을 파싱하는 구조이다.

카메라 등 애니메이션 처리는 GreenSock을 사용한다. 치수 등 측정을 지원하기 위한 클래스가 구현되어 있다. 

main.js 모듈은 기타 벡터, 행렬 등 유틸리티 클래스와 함수가 정의되어 있다.

BIM 객체 속성 표시 기능 구현해 보기
앞서 분석된 내용을 참고하여, BIM 객체의 속성을 표시하는 기능을 간단히 구현해 본다. 우선, 각 소스코드를 아래와 같이 편집한다.

// web-ifc-viewer\example\index.html 파일을 열고, 아래와 같이 section, textarea 요소 추가
  ...
  <body>
    <aside class="side-half-menu" id="side-menu-left"></aside>

    <div id="viewer-container"></div>
    <section class="panel-botton" id="side-panel-bottom">
      <textarea class="textarea-bottom" id="textarea-bottom" spellcheck="false" readonly>test</textarea>
    </section>    
    <script type="module" src="./build/main.js"></script>
  </body>

// web-ifc-viewer\example\styles.css 파일을 열고, 아래 style을 추가함
...
.side-menu {
    z-index: 1;
    position: fixed;
    background-color: #36393F;
    height: 100vh;
}

.side-half-menu {
    z-index: 1;
    position: fixed;
    background-color: #36393F;
    height: 80vh;
}

.panel-botton {
    z-index: 1;
    position: fixed;    
    background-color: #FFB555;
    width: 100%;
    height: 20vh;    
}

.textarea-bottom {
    width: 100%;
    height: 80%; 
}

.side-sub-menu {
    width: 100%;
    height: inherit;
    margin: 2px;
}

// E:\Projects\web-ifc-viewer\example\build.js 파일을 열고, ondblclick 이벤트 위치에 아래 코드를 추가함
...
    window.onkeydown = handleKeyDown;
    window.ondblclick = async () => {
      viewer.clipper.createPlane();

      const item = await viewer.IFC.pickIfcItem(true);  // 현재 마우스 클릭 위치의 Ifc element pick해 얻기
      if(item.modelID === undefined || item.id === undefined ) return;

      const pset = await viewer.IFC.getProperties(item.modelID, item.id, true);
      console.log(pset);

      var data = "";
      const text = document.getElementById('textarea-bottom'); // 2021.10. Pset 출력 text area 획득

      for(var i = 0; i < pset.type.length; i++) {
        data = data + pset.type[i].constructor.name + '\n';
      }

      for(var i = 0; i < pset.psets.length; i++) {
        data = data + pset.psets[i].Name.value + '\n';
        for(var j = 0; j < pset.psets[i].HasProperties.length; j++) {
            data = data + '   ' + pset.psets[i].HasProperties[j].value + '\n';    
        }
      }

      text.value = data;
...

구현 결과는 다음과 같다. IFC를 로딩한 후, 벽체 등 객체를 더블클릭하면 아래 패널창에 속성값이 출력될 것이다. 
벽체 객체 선택 후 속성 출력된 모습

IFC.js 응용과 한계
IFC.js를 응용하는 방법은 다양하다. 개발자가 언급한 것처럼 CDE를 구현할 수도 있고, BIM 모델 체커를 만들 수도 있다. IFC의 Property Set에 외부 정보를 연결하면, IoT, 디지털 트윈 등을 구현할 수 있다. GIS와 연계하면 BIM-GIS 개발이 가능할 것이다. 

다만, 유사한 오픈소스 프로젝트인 xBIM과 비교를 해보면, 정보 쿼리 기능, BIM 서버 연계, 대용량 처리, 확장성 등의 개선이 필요해 보인다. 그럼에도 가장 간편하게 BIM 응용 서비스를 웹 기반으로 개발할 수 있다는 점, WASM을 이용한 그래픽 가속 기능은 큰 장점이다.

마무리
이 글에서는 간단히 CDE 개발을 지원하는 오픈소스 BIM 라이브러리인 IFC.js를 분석하고 사용방법을 확인해 보았다. IFC.js는 구조가 복잡하고 사용이 쉽지는 않다. 그럼에도 앞서 언급한 장점이 많은 라이브러리이다. 
IFC.js 기여자
레퍼런스