2017년 7월 11일 화요일

윈도우즈 10에서 우분투 앱 설치 및 실행

이 글은 윈도우즈 10에서 우분투 설치 및 실행하는 방법을 간단히 소개한다. 이와 관련된 글은 다음을 참고하였다.

한 컴퓨터에서 우분투와 윈도우를 사용하기 위한 방법은 다음과 같다.

  • 멀티부트 설치
  • VMware에 우분투 설치
  • 도커에 우분투 설치

멀티부트가 성능면에서는 제일 좋지만, 설치가 번거롭기 이를데 없다. VMware는 무거운 가상머신을 설치하고, 우분투를 설치해 한다. 도커는 설치가 손쉬우나, 가상머신처럼 약간의 성능 손실을 감수해야 한다.

MS가 최근 샌드박스에 우분투 앱 설치하는 방법을 릴리즈했다. 앱설치하는 방식으로 설치가 쉽다. 샌드박스도 가상머신이라 성능손실은 있다. 다만, 앱스토어에 우분투 앱을 제공한다는 것에 놀라는 사람이 많다. 한번 사용해 볼만한 방식이라 관련 내용을 남긴다.

2017년 7월 10일 월요일

딥러닝 GAN 기반 image-to-image 모델

이 글은 딥러닝 GAN(Generative Adversarial Network. 생성 대립 신경망)모델에 기반한 image-to-image를 소개한다. 관련 논문은 다음 링크를 참고한다. 신경망의 종류와 개념 소개에 대해서는 여기를 참고한다.
1. 소개
이 글은 조건적 adversarial 네트워크를 이용한 GAN에 기반해, 이미지-이미지 생성 문제에 대한 범용 솔루션을 제안한다. 이러한 네트워크는 입력 이미지에서 출력 이미지로의 매핑을 학습할 수 있다. 이 접근법은 레이블 맵에서 사진을 합성하고, 가장자리 맵에서 객체를 재구성하고, 다른 작업 중에서 이미지를 페인팅하는 데 효과적이다. 

GAN기반 image-to-image

2. 내용
GAN은 랜덤 노이즈 벡터 z에서 출력 이미지 y : G : z → y 로의 매핑을 학습하는 생성 모델이다. 대조적으로 조건부 GAN은 관찰된 이미지 x와 랜덤 노이즈 벡터 z에서 y : G : {x, z} → y 로의 매핑을 학습한다. 생성기 G는 "가짜"이미지 감지 시, 훈련된 discrimintor D에 의해 "실제"이미지와 유사한 이미지를 출력하도록 훈련된다. 다음 그림은 훈련과정을 보여준다.

Training conditional GAN

G는 목적함수를 최소화하기 위해 반복 훈련하다. 이 목적함수는 D가 최대화하려는 것에 비해 대조된다. 

G∗ = arg minG maxD LcGAN (G, D)
LGAN(G, D)=Ey∼pdata(y) [log D(y)] + Ex∼pdata(x),z∼pz(z)[log(1 − D(G(x, z))]

다음은 이를 통해 훈련된 image-image net을 이용하여, 이미지를 생성한 예이다.
손실 차이에 따른 결과의 품질

3. 마무리
GAN은 사람의 인지적 활동을 모사하는 데 탁월한 성능을 보여준다. 특히, 예술 분야에서 GAN은 많은 관심을 받고 있는 흥미로운 학습 방법이다. 

레퍼런스





2017년 7월 4일 화요일

딥러닝 기반 3차원 객체 인식 VoxNet

이 글은 3차원 점군에서 객체를 인식하는 일반적인 접근법인 복셀을 이용한 VoxNet에 관한 소개이다. 이 글은 Dimatura VoxNet 레퍼런스를 참고하였다.

1. 머리말
VoxNet은 3차원 점군을 복셀로 근사화시켜, CNN을 이용해 형상을 인식한다.

2. 설치
VoxNet은 Theano와 Lasagne 를 기반으로 개발되었다. path.py, scikit-learn 모듈이 필요하다. 다음과 같이 설치한다.
git clone git@github.com:dimatura/voxnet.git
cd voxnet
pip install --editable .

3. 훈련
데이터는 ModelNet 10을 사용하였다. 이는 3D ShapeNet 프로젝트에서 얻은 것이다. 복셀화를 손쉽게 하기 위해, .mat 파일을 이용하였다. 데이터셋은 다음과 같이 얻는다.
# scripts/download_shapenet10.sh
wget http://3dshapenets.cs.princeton.edu/3DShapeNetsCode.zip 
unzip 3DShapeNetsCode
python convert_shapenet10.py 3DShapeNets
훈련은 다음과 같이 실행한다.
cd scripts/
python train.py config/shapenet10.py shapenet10_train.tar
훈련 결과는 metrics.jsonl 파일에 저장된다.

4. 테스트
테스트는 다음과 같이 실행한다.
python test.py config/shapenet10.py shapenet10_test.tar --out-fname out.npz
가시화는 다음과 같이 실행한다.
python output_viz.py out.npz shapenet10_test.tar out.html


레퍼런스



2017년 7월 2일 일요일

딥러닝 기반 3차원 비전 객체 인식 PointNet 분석

이 글은 최근 발표된 딥러닝 기반 3차원 비전 객체 인식 기술인 PointNet을 분석해 본다. 이 글은 아래 레퍼런스를 참고한다.
PointNet 분류 결과

1. 머리말
PointNet은 CVPR 2017 컨퍼런스에서 발표된 arXiv 기술을 기반으로 한다. 스캔 결과로 얻어지는 3차원 점군을 인식하기 위해서, 많은 연구자들은 점군을 복셀(voxel) 형식으로 변환한다. 복셀은 점군을 요약하기에는 좋으나, 빈공간이 많이 발생하여, 비효율적인 부분이 있다. PointNet은 입력 시 포인트 순열의 불변성을 이용한다. PointNet을 통해, 객체 분류, 세그먼테이션, 스캔 장면의 의미 해석 등에 필요한 아키텍처를 제공한다. PointNet은 간단하지만, 매우 효율적으로 3차원 점군에서 객체를 인식한다. PointNet은 ShapeNet Part Dataset을 기반으로 훈련을 하였다.


2. 설치하기
PointNet은 텐서플로우 1.01, h5py, CUDA 8.0, cuDNN 5.1, 우분투 14.04를 사용한다.
만약, h5py 가 없다면, 다음과 같이 패키지를 설치한다. PointNet은 HDF5 를 사용하며, 관련된 상세 내용는 여기를 참고하길 바란다.
sudo apt-get install libhdf5-dev
sudo pip install h5py
참고로, 아나콘다에서는 다음과 같이 설치하면 된다.
conda install h5py

3. 신경망 훈련하기
다음과 같이 실행한다.
python train.py
로그 파일은 log폴더에 저장된다. 점군은 HDF5 파일로 ModelNet40 형식으로 다운로드 된다(416MB). 각 점군은 2048 포인트들을 담고 있다.
다음과 같이 학습 결과를 텐서보드를 통해 확인할 수 있다.
tensorboard --logdir log
훈련후에는 다음과 같이 정확도를 평가할 수 있다.
python evaluate.py --visu
만약, 미리 준비한 점군이 있다면, utils/data_prep_util.py 유틸리티 함수를 통해 HDF5 파일을 읽고 쓸 수 있다.
참고로, 훈련을 위해서는 ShapeNetPart 데이터(약 1.08GB)를 다음과 같이 다운로드해야 한다.
cd part_seg
sh download_data.sh
훈련과정 및 결과는 다음과 같다.

다음은 인식한 객체 정확도이다.
참고로, 훈련과정에서 사용된 포인트 클라우드는 이미지로 저장되어 확인할 수 있다.



4. 데이터 구조 분석
훈련용 데이터 파일은 총 6개이며, (3 x 2048 x 2048) 데이터셋으로 포인트클라우드(점군)와 라벨 데이터셋(2048)이 저장되어 있다. 테스트용 데이터는 총 2개이다. 파일포맷은 HDF5이다.
훈련용 데이터셋(3 x 2048 x 2048) HDF5파일

5. 신경망 구조 분석
PointNet 신경망 구조는 다음과 같다. n 개 포인트 클라우드 (점군)을 입력한다. 입력 및 특징 변환을 수행 하고, max pooling을 통해 특징을 일반화한다. 출력으로 m 개 클래스 스코어가 분류된다. 신경망 구조는 분류(classification), 세그먼테이션(segmentation) 네트워크로 구성되어 있다. 세그먼트 네트워크는 분류 네트워크를 확장하였다. Batchnorm(Batch Normalization)은 ReLU 함수를 적용한다. Dropout은 분류 네트워크의 마지막 mlp(multi layer perception. 다층 레이어 퍼셉트론)에만 적용하였다.
PointNet 구조

분류 네트워크 구조는 다음과 같다.
1. n개의 3차원 포인트 좌표값이 input points로 입력
   1) T-Net 으로 3x3 텐서 변환
   2) matrix multiply 연산 처리
2. 변환된 n x 3 데이터가 mlp 64x64로 전달되어, n x 64 텐서로 출력됨
3. feature transform을 통해 계산된 n x 64 텐서 출력
4. mlp 64x128x1024 로 변환된어 n x 1024 텐서로 출력
5. max pooling 을 통해 일반화된 특징 벡터 1024 출력
6. mlp 512 x 256 x k 로 출력해 score 벡터 k 계산

세그먼트 네트워크 구조는 다음과 같다.
1. 분류 네트워크의 1 ~ 4번까지 그래프 구조는 재활용됨
2. n x 1088 텐서가 mlp 512x256을 통해 point feature 텐서 n x 128 로 출력
3. n x 128 텐서가 mlp 128 x m 을 통해 n x m 텐서로 출력

6. 소스코드 분석
텐서플로우를 사용하고 파이썬으로 코딩된 전체 소스코드는 261라인이다. 좀 더 깊은 이해를 위해, 소스코드의 주요 부분을 확인해 보자. 

MAX_NUM_POINT = 2048   # 입력 포인트 갯수
NUM_CLASSES = 40       # 클래스 갯수
BN_INIT_DECAY = 0.5    # 학습 속도 

# 훈련용, 테스트용 포인트 클라우드 데이터 준비
TRAIN_FILES = provider.getDataFiles( \
    os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt'))
TEST_FILES = provider.getDataFiles(\
    os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt'))

def train():
    with tf.Graph().as_default():
        with tf.device('/gpu:'+str(GPU_INDEX)):   # 신경망 계산 시 GPU 사용
            pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) 

            # prediction 모델 구성
            pred, end_points = MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay)
            loss = MODEL.get_loss(pred, labels_pl, end_points)

            # 정확도 모델 구성
            correct = tf.equal(tf.argmax(pred, 1), tf.to_int64(labels_pl))
            accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE)

            # 최적화 옵션에 따른 모델 정의
            if OPTIMIZER == 'momentum':
                optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=MOMENTUM)
            elif OPTIMIZER == 'adam':
                optimizer = tf.train.AdamOptimizer(learning_rate)      # 아담 최적화 모델
            train_op = optimizer.minimize(loss, global_step=batch)   # 오차 최소화 모델
            
        sess.run(init, {is_training_pl: True})     # 세션 시작

        for epoch in range(MAX_EPOCH):     # 세대별 학습
            train_one_epoch(sess, ops, train_writer)   # 훈련
            eval_one_epoch(sess, ops, test_writer)     # 평가
            
            if epoch % 10 == 0:
                save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt"))   # 모델 저장

def train_one_epoch(sess, ops, train_writer):
    np.random.shuffle(train_file_idxs)   # 임의로 서플링함
    
    for fn in range(len(TRAIN_FILES)):
        current_label = np.squeeze(current_label)  # 라벨값
        
        for batch_idx in range(num_batches):       # 배치 갯수만큼 루프
            rotated_data = provider.rotate_point_cloud(current_data[start_idx:end_idx, :, :])  # 회전 변환
            jittered_data = provider.jitter_point_cloud(rotated_data)  # 지터 처리
            feed_dict = {ops['pointclouds_pl']: jittered_data,
                         ops['labels_pl']: current_label[start_idx:end_idx],
                         ops['is_training_pl']: is_training,}   # 피드값 입력
            summary, step, _, loss_val, pred_val = sess.run([ops['merged'], ops['step'],

                ops['train_op'], ops['loss'], ops['pred']], feed_dict=feed_dict)   # 세션 실행

def eval_one_epoch(sess, ops, test_writer):
    for fn in range(len(TEST_FILES)):
        current_label = np.squeeze(current_label)
        num_batches = file_size // BATCH_SIZE
        for batch_idx in range(num_batches):
            feed_dict = {ops['pointclouds_pl']: current_data[start_idx:end_idx, :, :],
                         ops['labels_pl']: current_label[start_idx:end_idx],
                         ops['is_training_pl']: is_training}
            summary, step, loss_val, pred_val = sess.run([ops['merged'], ops['step'],  # 예측
                ops['loss'], ops['pred']], feed_dict=feed_dict)
            pred_val = np.argmax(pred_val, 1)
            correct = np.sum(pred_val == current_label[start_idx:end_idx])

실제 pointnet 신경망 정의는 다음 폴더 아래에 있다.

이 중에 pointnet_cls, pointnet_seg 가 분류, 세그먼테이션 신경망을 구성하는 모듈이다. pointnet_cls.py는 전체 98라인이고, pointnet_seg.py는 115라인이다. 설명은 주석으로 처리하였으니, 앞의 신경망 구조와 비교해 확인해 보자.

# pointnet_cls.py
def placeholder_inputs(batch_size, num_point):
    pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3))
    labels_pl = tf.placeholder(tf.int32, shape=(batch_size))
    return pointclouds_pl, labels_pl


def get_model(point_cloud, is_training, bn_decay=None):
    """ Classification PointNet, input is BxNx3, output Bx40 """
    with tf.variable_scope('transform_net1') as sc:
        transform = input_transform_net(point_cloud, is_training, bn_decay, K=3)
    point_cloud_transformed = tf.matmul(point_cloud, transform)  # 점군 변환
    input_image = tf.expand_dims(point_cloud_transformed, -1)    # 차원 확장

    net = tf_util.conv2d(input_image, 64, [1,3],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv1', bn_decay=bn_decay)   # conv1. 64 1 x 3 컨볼류션 적용
    net = tf_util.conv2d(net, 64, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv2', bn_decay=bn_decay)   # conv2. 64 1 x 1 컨볼루션 적용

    with tf.variable_scope('transform_net2') as sc:
        transform = feature_transform_net(net, is_training, bn_decay, K=64)  # 변환
    end_points['transform'] = transform
    net_transformed = tf.matmul(tf.squeeze(net, axis=[2]), transform)  # matmul 계산
    net_transformed = tf.expand_dims(net_transformed, [2])             # 텐서 확장

    net = tf_util.conv2d(net_transformed, 64, [1,1],           
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv3', bn_decay=bn_decay)    # conv3. 64 1 x 1 컨볼루션 적용
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv4', bn_decay=bn_decay)    # conv4. 128 1 x 1 컨볼루션 적용
    net = tf_util.conv2d(net, 1024, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv5', bn_decay=bn_decay)    # conv5. 1024 1 x 1 컨볼루션 적용

    # Symmetric function: max pooling
    net = tf_util.max_pool2d(net, [num_point,1],                   # max pooling
                             padding='VALID', scope='maxpool')

    net = tf.reshape(net, [batch_size, -1])                            # reshape
    net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,
                                  scope='fc1', bn_decay=bn_decay)                     # fc1 512
    net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training,            # dp1. dropout 0.7
                          scope='dp1')
    net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,  
                                  scope='fc2', bn_decay=bn_decay)                     # fc2 256
    net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training,
                          scope='dp2')                                                       # dp2. dropout 0.7
    net = tf_util.fully_connected(net, 40, activation_fn=None, scope='fc3')    # fc3. 40 

def get_loss(pred, label, end_points, reg_weight=0.001):
    """ pred: B*NUM_CLASSES,
        label: B, """
    loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=pred, labels=label)  # softmax 
    classify_loss = tf.reduce_mean(loss)                                                           # reduce mean

    transform = end_points['transform']  # B x K x K
    K = transform.get_shape()[1].value    # transform
    mat_diff = tf.matmul(transform, tf.transpose(transform, perm=[0,2,1]))
    mat_diff -= tf.constant(np.eye(K), dtype=tf.float32)
    mat_diff_loss = tf.nn.l2_loss(mat_diff)                           # loss 계산


# pointnet_seg.py
def get_model(point_cloud, is_training, bn_decay=None):
    """ Classification PointNet, input is BxNx3, output BxNx50 """
    with tf.variable_scope('transform_net1') as sc:
        transform = input_transform_net(point_cloud, is_training, bn_decay, K=3)
    point_cloud_transformed = tf.matmul(point_cloud, transform)
    input_image = tf.expand_dims(point_cloud_transformed, -1)

    net = tf_util.conv2d(input_image, 64, [1,3],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv1', bn_decay=bn_decay)
    # 여기까지는 분류 네트워크와 구조 동일

    net = tf_util.conv2d(concat_feat, 512, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv6', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 256, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv7', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv8', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv9', bn_decay=bn_decay)

    net = tf_util.conv2d(net, 50, [1,1],
                         padding='VALID', stride=[1,1], activation_fn=None,
                         scope='conv10')
    net = tf.squeeze(net, [2]) # B x N x C


    return net, end_points


레퍼런스