2019/05/13 - [Study/인공지능학습] - [인공지능] coral usb accelerator + Raspberry pi zero w

2019/05/16 - [Study/인공지능학습] - [인공지능] Coral USB Accelerator와 Edge TPU




지난 포스팅에서 구글의 Coral USB Accelerator를 구입하고 간단한 모델을 돌려본 것을 정리해보았다.

이번 포스팅에서는 Coral USB Accelerator(이하 coral)의 공식 홈페이지를 참조하여 요구 사항 및

엣지 컴퓨팅에 사용되는 Tesorflow Lite에 대해 간단하게 살펴보고자 한다.


각각의 공식 홈페이지는 다음과 같다.


Coral
Tensorflow Lite


Coral USB Accelerator


Coral 시리즈는 지금 소개하고자 하는 USB Accelerator 외에 완전한 보드 형태인 Coral Dev Board가

있으나 이번 포스팅에서는 USB Accelerator에 대해서만 다루도록 하겠다. 다른 부분에 대해서는 공식홈페이지를

참고하기 바란다.


우선 데이터시트를 보면 coral은 구글에서 자체적으로 만든 프로세서를 사용하며 USB 3.0 Type C 소켓을

지원하고 데비안 기반의 리눅스에서 작동을 한다. 하지만 데비안에서 파생된 우분투 리눅스도 사용 가능하며

x86-64 또는 ARMv8 명령어 집합을 포함하는 ARM32/64 기반의 프로세서에서 사용이 가능하다.


이 기준에 따르면 라즈베리파이 zero 시리즈는 ARMv6 명령어 셋 기반이라 지원이 안되지만 홈페이지에도

나와 있듯이 비공식적으로는 사용 가능하다. 또한 coral은 USB 3.0을 지원하지만 라즈베리파이는 아직 2.0만을

지원하기 때문에 속도면에서 손실이 있다.


coral을 사용하기 위해서는 Edge TPU runtime과 Python 라이브러리를 설치해야 하는데 기본적으로

Python 3.5 버전을 지원하며 만일 3.6 버전 이상인 경우 install.sh 스크립트 파일의 맨 마지막 줄에서

python3.5 대신 python3으로 수정한 후 설치하면 된다. 라이브러리가 설치되는 동안 coral이 연결된

상태였다면 설치 후 다시 연결을 해주어야 coral이 작동한다.


또한 이 과정에서 클록 주파수를 최대로 사용할 것인지를 묻는데 만일 최대로 사용하게 되면 추론 속도가

2배가 되지만 대신에 전력 소모량이 많아지며 발열이 증가한다. 심지어는 최대로 할 경우에는 손닿지 않는

곳에 장치를 두거나 부상을 막기 위해서는 기본 속도를 사용하라고 경고하고 있을 정도니 발열이 꽤나 심한
것 같다. 클록 주파수 설정은 단순히 라이브러리 설치를 다시 진행하여 변경할 수 있지만 2019년 4월 이전에

설치한 경우별도의 업데이트 과정을 거쳐야 한다.


coral에는 흰색 LED가 하나 장착이 되어있는데 신호는 매우 단순하다. 그냥 불이 들어와 있으면 초기 상태인

것이고 연산을 수행하면 LED가 점멸한다. 이게 끝이다…


coral을 작동시키기 위해서는 USB를 통해 적어도 5V 전압에 500mA의 전류를 공급해주어야 한다. 내가 

라즈베리파이 zero w를 사용하면서 usb 허브에 coral을 물릴 수밖에 없었는데 이 때 전원 공급이 가장 

우려되었지만 별도의 외부 전원 없이 coral이 정상 동작 하였다. 혹시 전원 공급이 약해서 속도가 느린가 하고 

usb 허브에 외부 전원을 넣어 봤으나 느린 건 매한가지였다…-.-


이번 포스팅에서는 여기까지만 다루기로 한다. 기타 상세한 내용에 대해해서는 공식 홈페이지를 참고하기바란다.


TensorFlow models on the Edge TPU - 개요

엣지 컴퓨팅에 사용되는 TensorFlow Lite는 TensorFlow 공식 홈페이지에 소개가 되어있지만 Coral

홈페이지에도 TensorFlow 관련 내용이 있어 먼저 Coral 홈페이지의 내용부터 정리를 해보겠다.

Edge TPU의 경우 낮은 전력을 소모하면서 빠른 신경망 연산을 처리해야 한다는 제약이 있기 때문에

조금 특별한 신경망 연산 기능과 구조를 가져야 한다. 때문에 Edge TPU에서도 CNN과 같은 심층 신경망을

실행할 수 있지만 TensorFlow Lite라는 특별한 버전만 지원되며 Edge TPU에 맞게 컴파일 되어야 한다.


TensorFlow Lite는 TensorFlow의 경량 버전으로 TensorFlow Lite의 모델과 인터프리터 커널의
바이너리 사이즈를 줄임으로써 모바일 기기나 임베디드 시스템에서 추론의 속도를 높였다. 다만 모델을 바로
훈련 시키지는 못하며 TensorFlow에서 훈련시킨 모델을 TensorFlow Lite converter라는 툴을 이용하여

변환해야 한다.


Edge TPU에 대한 최적화 과정에서 Quantizing이라는 용어가 나오는데 이는 사이즈를 줄이기 위해

가중치나 활성함수의 출력에 사용되는 32비트 부동 소수점 타입을 8비트 고정 소수점 타입으로 변환하는

과정이라고 한다. 이 과정을 통해 모델이 더 작고 빨라지게 되며, 비록 정밀도가 떨어지긴 하지만 추론의

정확도에는 큰 영향을 미치지 않는다고 한다.


이러한 이유로 새로 모델을 만들고 훈련시키는 과정이 조금 복잡한데 구글에서 새로운 데이터 셋으로 재학습을

하면 Edge TPU에서 사용 가능한 TensorFlow 모델들을 다수 제공하고 있으므로 필요한 경우 이를 활용하면

시간을 절약할 수 있을 것이다.


Edge TPU work process출처 : https://coral.withgoogle.com/docs/edgetpu/models-intro/



하지만 직접 모델을 만들어 Edge TPU에서 사용하려고 한다면 다음의 요구 조건을 충족시켜야 한다.


  • 텐서 파라미터는 8비트 고정 소수점 타입이어야 한다.
  • 텐서의 크기는 컴파일 시에 그 크기가 고정되어있어야 한다.
  • bias 텐서 같은 모델 파라미터 역시 컴파일 시에 크기가 고정되어있어야 한다.
  • 텐서들은 3차원 이하여야 한다. 만일 3차원 이상의 크기를 가지는 텐서를 사용할 경우 가장 안쪽의
    3개 차원만이 1보다 큰 크기를 갖게 된다.
  • 모델은 Edge TPU를 지원하는 연산만 사용 가능하다.


이 조건이 충족되지 않아도 컴파일은 되지만 Edge TPU 상에서는 일부만 실행될 것이다. 이렇게 조건을 충족하지
않는 연산이 있는 경우에는 모델의 그래프가 Edge TPU에서 실행 가능한 부분과 그렇지 않은 부분으로 나뉘게

되는데 이 중 Edge TPU에서 지원하지 않는 부분은 CPU에서 실행된다.


여기서 주의할 점은 현재의 Edge TPU 컴파일러는 이러한 분할을 한 번만 할 수 있기 때문에 만일 첫 번째

연산이 Edge TPU에서 실행 가능한 연산이고 두 번째 연산이 실행 불가능한 연산이라면 두 번째 이후의

모든 연산은 비록 Edge TPU에서 실행 가능하다 하더라도 모두 CPU에서 실행되어 버린다.


출처 : https://coral.withgoogle.com/docs/edgetpu/models-intro/



이렇게 CPU에서 실행되는 연산이 포함되면 당연히 100% Edge TPU에서 실행될 때보다 추론이 늦어지므로

Edge TPU에서 실행 가능한 연산만 포함되도록 노력해야 한다(참고로 Edge TPU 컴파일러는 컴파일이 종료되면 

Edge TPU에서 실행된 연산과 CPU에서 실행된 연산의 수를 알려준다고 한다).


앞서도 언급했지만 밑바닥부터 모델을 만드는 시간과 노력을 절약하기 위해서는 구글에서 이미 Edge TPU와

호환되도록 만들어놓은 모델을 재학습하여 사용하면 되는데 이 때 사용되는 기술이 Transfer Learning(이전 학습
또는 전이 학습) 혹은 fine tuning(세부 조정)이라는 기술이다. 또 다른 방법으로 Edge TPU 장비에서 직접
재학습을 수행하는 Python API를 이용한 weight imprinting이라는 방법이 있다.

Transfer Learning은 소개의 범위를 벗어나므로 더 상세한 내용은 Coral 홈페이지를 참조하기 바란다.

이렇게 만들어진 모델을 Edge TPU에서 실행하기 위해서는 Edge TPU runtime과 API 라이브러리가 호스트
시스템(라즈베리파이 등)에 설치가 되어있어야 한다. 하지만 Coral 제품 중 Coral Dev Board나 SoM 등은 
이미 이러한 것들이 설치되어 있어 설치 과정을 생략하고 바로 사용 가능하다.

현재 Coral 제품에서는 C++과 Python 기반의 API가 사용 가능하다.


정리

이렇게 간단하게나마 Coral Accelerator의 하드웨어적인 측면과 소프트웨어적인 측면의 개요를 정리해 보았다.
중요하게 알아야 할 것은 모바일 기기 또는 임베디드 시스템을 위해 만들어진 것이라 원하는 모든 것을 할 수는
없다는 점이다. 물론 이러한 기기들의 컴퓨팅 파워가 더 올라갈 수는 있겠지만 저전력이라는 매리트를 포기하지
않는 한은 한계가 있을 것이다.

추후 추가로 정리하겠지만 TensorFlow Lite에서 제공하고 있는 모델은 총 5가지가 있는데 다음의 모델들이다.

  • Image classification
  • Object detection
  • Smart reply
  • Pose estimation
  • Segmentation

  • 아직은 처음 두가지만 재학습 없이 테스트해보았지만 나머지 모델들도 확인을 해보고 싶다. 특히 Pose estimation은
    활용도가 꽤 있을 것 같다.

    처음에는 그저 호기심으로 인한 지름으로 시작했는데 하나하나 정리하다보니 나같이 머신러닝이나 딥러닝을 
    앝은 수준에서 시작해보려는 사람들에게는 좋은 학습 대상이 될 수 있을 것 같다. 특히나 ‘인공지능? 어따 써먹지?
    하는 사람들은 몸소 느끼면서 배울 수 있을 것 같다.

    일단 무작정 머리에 들어오지도 않는 책만 읽는 것 보다는 라즈베리파이와 Coral 그리고 TF Lite에서 주어진
    모델들을 이용하여 뭔가 하나 만들어보는 쪽으로 올해의 목표를 수정해야겠다.


    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요

    Tensorflow









    소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10


    지난 포스팅에서 살펴보았던 cifar10_input.py는 데이터를 불러와서 이미지를 임의 조작한 후 배치 사이즈 크기로
    나누어 담아 리턴해주는 기능을 하였다. 전체 프로세스의 가장 첫 단계라고도 할 수 있다.


    오늘 살펴볼 cifar10.py는 가장 핵심적인 소스로 모델과 네트워크를 구성하는 내용이 주를 이루고 있다.
    그만큼 코드의 길이도 전체 소스 중 가장 길다.


    차근차근 살펴보도록 하자.


    cifar10.py

    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function
    

    지난 포스팅과 마찬가지로 위 3줄의 import 문은 python 2와 3의 호환성을 위한 것이다.


    # OS의 자원을 사용할 수 있게 해주는 모듈
    import os
    # 정규표현식을 사용할 수 있게 해주는 모듈
    import re
    # python interpreter에 의해 관리되는 변수나 함수에 접근할 수 있도록 해주는 모듈
    import sys
    # tar 압축을 핸들링할 수 있게 해주는 모듈
    import tarfile
    

    여러 다양한 기능을 사용할 수 있게 해주는 import문들이다. os, sys, tarfile 등은 원격으로 cifar10 데이터셋을
    다운로드 받기 위해 쓰인다.


    # six 모듈은 python 2와 3의 함수를 함께 사용할 수 있도록 해줌. urllib는 URL 관련 모듈로 역시
    # 데이터 셋 다운로드에 사용된다.
    from six.moves import urllib
    #텐서플로우 모듈
    import tensorflow as tf
    
    # 지난 포스팅에서 살펴본 cifar10_input.py 참조
    import cifar10_input
    

    몇가지 모듈이 추가로 import 되었으나 대부분 CIFAR10 데이터 셋을 원격으로 다운로드 받기 위한 것으로 이미
    별도로 데이터 셋을 다운로드 받아두었다면 무시해도 좋을 것이다.


    소스 앞부분에 영문으로 중요한 함수에 대한 설명이 주석으로 달려있다. 일단 간단하게 그 내용을 살펴보면 
    다음과 같다.


    • inputs, labels = distorted_inputs( )
      : 학습에 사용할 데이터를 불러온다. 이 함수 안에서 cifar10_input.py에 있는 distorted_inputs( )
      함수를 호출하여 처리한다.
    • predictions = inference(inputs)
      : 파라미터로 전달되는 모델(inputs)에 대한 추론을 계산하여 추측한다.
    • loss = loss(predictions, labels)
      : 해당 라벨에 대해 예측값에 대한 총 손실 값을 구한다.
    • train_op = train(loss, global_step)
      : 학습을 수행한다.


    위의 4개 함수가 가장 핵심적인 내용이라 할 수 있다.
    이제 전체 코드를 차근차근 살펴보자.


    # tf.app.flags.FLAGS는 상수의 성격을 갖는 값을 관리하는 기능을 한다.
    # tf.app.flags.DEFINE_xxx 함수를 이용하여 첫 번째 파라미터에 사용할 이름을 넣고
    # 두 번째 파라미터에 사용할 값을 설정하면 이후 'FLAGS.사용할 이름' 형식으로 그 값을
    # 사용할 수 있다. 아래 첫 번째 코드의 경우 FLAGS.batch_size라는 코드로 128이라는 값을
    # 사용할 수 있다.
    FLAGS = tf.app.flags.FLAGS
    
    # Basic model parameters.
    tf.app.flags.DEFINE_integer('batch_size', 128,
                                """Number of images to process in a batch.""")
    tf.app.flags.DEFINE_string('data_dir', '/tmp/cifar10_data',
                               """Path to the CIFAR-10 data directory.""")
    tf.app.flags.DEFINE_boolean('use_fp16', False,
                                """Train the model using fp16.""")
    
    # Global constants describing the CIFAR-10 data set.
    # cifar10_input.py에 정의되어있던 값들
    IMAGE_SIZE = cifar10_input.IMAGE_SIZE  #24
    NUM_CLASSES = cifar10_input.NUM_CLASSES  #10
    NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN  #50000
    NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL #10000
    
    
    # Constants describing the training process.
    # tf.train.ExponentialMovingAverage에서 사용할 가중치 값
    MOVING_AVERAGE_DECAY = 0.9999     # The decay to use for the moving average.
    # 학습 속도 감소 후의 epoch 수
    NUM_EPOCHS_PER_DECAY = 350.0      # Epochs after which learning rate decays.
    # 학습률 감소를 위한 변수
    LEARNING_RATE_DECAY_FACTOR = 0.1  # Learning rate decay factor.
    # 초기 학습률
    INITIAL_LEARNING_RATE = 0.1       # Initial learning rate.
    


    음…수학을 깊이 들어가긴 싫지만 얼레벌레 그냥 넘어가는 것도 그러니 몇 가지 개념은 좀 알아보고 가자.


    Exponential Moving Average

    우선 이동평균(Moving Average)라는 것은 특정 기간동안 내에 측정된 값의 평균을 의미한다.
    이 이동평균에는 단순이동평균, 가중이동평균, 그리고 여기서 사용하는 지수이동평균이 있는데
    이 지수이동평균은 가장 최근 값에 더 큰 가중치를 두어 평균을 계산하는 방식이라고 한다.


    일단 위 코드 중 MOVING_AVERAGE_DECAY 이후의 설정들은 모두 학습률 조정을 위한 것으로
    train( ) 함수에서 사용을 하게 된다. 기본적으로 학습이 진행됨에 따라 학습률을 기하급수적으로 감소시켜
    나가는 방법을 취하고 있다. 자세한 내용은 train( ) 함수 설명에서 다시 한 번 분석해보자.


    # If a model is trained with multiple GPUs, prefix all Op names with tower_name
    # to differentiate the operations. Note that this prefix is removed from the
    # names of the summaries when visualizing a model.
    # 멀티 GPU를 사용하여 병렬 처리할 때 작업 이름을 구분하기 위한 구분자...언제 써볼 수 있을까...ㅠ.ㅠ
    TOWER_NAME = 'tower'
    
    # CIFAR-10의 데이터 경로
    DATA_URL = 'https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz'
    


    이제부터는 함수를 하나 하나 살펴보도록 하겠다.


    _activation_summary(x)

    # 이 함수는 전체적으로 각 레이어들을 텐서 보드에 시각화하기 위한 summary를 만드는 작업을 한다.
    """Helper to create summaries for activations.
      Creates a summary that provides a histogram of activations.
      Creates a summary that measures the sparsity of activations.
      Args:
        x: Tensor
      Returns:
        nothing
      """
      # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training
      # session. This helps the clarity of presentation on tensorboard.
      tensor_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', x.op.name)
      tf.summary.histogram(tensor_name + '/activations', x)
      tf.summary.scalar(tensor_name + '/sparsity',
                                           tf.nn.zero_fraction(x))
    


    _variable_on_cpu(name, shape, initializer)

    # 파라미터로 전달받은 값을 이용하여 CPU를 이용하여 처리할 변수를 생성
    """Helper to create a Variable stored on CPU memory.
      Args:
        name: name of the variable
        shape: list of ints
        initializer: initializer for Variable
      Returns:
        Variable Tensor
      """
    # 0번째 CPU를 사용하겠다고 지정
      with tf.device('/cpu:0'):
    # python의 3항 연산 FLAGS.use_fp16이 true이면 tf.float16을 사용하고 false이면 
    # else 뒤의 tf.float32를 사용. 이 코드에서는 FLAGS.use_fp16를 false로 설정했으므로
    # tf.float32를 사용하게 됨
        dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
    # 파라미터로 전달된 변수가 이미 존재하면 재활용하고 존재하지 않으면 새로 만든다.
    # 참고로 tf.Variable는 무조건 새로운 변수를 만든다. 자세한 사용법은 아래 링크 참조
    # https://tensorflowkorea.gitbooks.io/tensorflow-kr/content/g3doc/how_tos/variable_scope/
        var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype)
      return var
    


    _variable_with_weight_decay(name, shape, stddev, wd)

    # 위의 _variable_on_cpu(name, shape, initializer) 함수를 이용하여 정규화 처리를 한 변수를 생성.
    """Helper to create an initialized Variable with weight decay.
      Note that the Variable is initialized with a truncated normal distribution.
      A weight decay is added only if one is specified.
      Args:
        name: name of the variable
        shape: list of ints
        stddev: standard deviation of a truncated Gaussian
        wd: add L2Loss weight decay multiplied by this float. If None, weight
            decay is not added for this Variable.
      Returns:
        Variable Tensor
      """
    # 데이터 타입 설정
    # 세 번째 파라미터는 초기화 함수를 리턴하여 넘기는 것으로 truncated_normal_initializer는
    # 정규분포 기반의 초기화 함수로 표준편차의 양 끝단을 잘라낸 값으로 새로운 정규분포를 만들어 
    # 초기화 한다.
      dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
      var = _variable_on_cpu(
          name,
          shape,
          tf.truncated_normal_initializer(stddev=stddev, dtype=dtype))
    
    # L2 정규화 처리를 위한 코드. wd(아마도 Weight Decay)값이 None이 아닌 경우 if문
    # 안의 코드를 수행하여 정규화 처리를 하고 그래프에 추가한다.
    # tf.nn.l2_loss는 전달받은 텐서의 요소들의 제곱의 합을 2로 나누어 리턴한다.
      if wd is not None:
        weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')
        tf.add_to_collection('losses', weight_decay)
      return var
    


    위 함수들은 실제로 학습을 진행하면서 결과 값을 예측하는 과정에 사용되는 함수들이다.
    자세한 내용들은 올바른 예측을 하기 위한 알고리즘을 구성하는 수학적인 내용이 포함되어있어
    당장에는 이해가 쉽지 않다. 예를 들어 tf.truncated_normal_initializer의 경우 정규분포
    그래프에서 2개 이상의 표준편차를 제거한 값들로 새롭게 만들어진 그래프로 초기화 한다고 해석이
    되는데 사실 내용자체도 이해가 되지 않고 더 심각한 것은 수학적 개념이 포함된 영어를 해석하자니
    제대로 해석이 되었는지도 모르겠다…ㅠ.ㅠ 일단은 학습을 최적화 시키고자 하는 목적으로 이러한
    장치들을 사용한다는 것만 알아두면 되겠다.


    distorted_inputs()

    # cifar10_input.py에 있는 같은 이름의 함수를 이용하여 학습할 데이터를 불러온다.
    """Construct distorted input for CIFAR training using the Reader ops.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      Raises:
        ValueError: If no data_dir
      """
    
    # 데이터 경로가 지정되어있지 않으면 에러~
      if not FLAGS.data_dir:
        raise ValueError('Please supply a data_dir')
    
    # 데이터 경로를 조합하여 최종적으로 사용할 이미지와 라벨을 가져옴
      data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
      images, labels = cifar10_input.distorted_inputs(data_dir=data_dir,
                                                      batch_size=FLAGS.batch_size)
    
    # FLAGS.use_fp16 값이 true이면 이미지와 라벨 텐서의 요소들을 tf.float16 타입으로 형변환 한다.
    # 하지만 코드에는 False로 지정되어있으므로 무시.
      if FLAGS.use_fp16:
        images = tf.cast(images, tf.float16)
        labels = tf.cast(labels, tf.float16)
      return images, labels
    


    inputs(eval_data)

    # 역시 cifar10_input.py에 있는 같은 이름의 함수를 이용하여 평가할 데이터를 불러온다.
    # eval_data라는 파라미터가 추가된 것 외에는 distorted_inputs 함수와 내용 동일
    """Construct input for CIFAR evaluation using the Reader ops.
      Args:
        eval_data: bool, indicating if one should use the train or eval data set.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      Raises:
        ValueError: If no data_dir
      """
      if not FLAGS.data_dir:
        raise ValueError('Please supply a data_dir')
      data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
      images, labels = cifar10_input.inputs(eval_data=eval_data,
                                            data_dir=data_dir,
                                            batch_size=FLAGS.batch_size)
      if FLAGS.use_fp16:
        images = tf.cast(images, tf.float16)
        labels = tf.cast(labels, tf.float16)
      return images, labels
    


    inference(images)

    # 이 소스의 핵심으로 예측을 위한 모델을 구성하는 함수
    """Build the CIFAR-10 model.
      Args:
        images: Images returned from distorted_inputs() or inputs().
      Returns:
        Logits.
      """
      # We instantiate all variables using tf.get_variable() instead of
      # tf.Variable() in order to share variables across multiple GPU training runs.
      # If we only ran this model on a single GPU, we could simplify this function
      # by replacing all instances of tf.get_variable() with tf.Variable().
      #
      # conv1
    # convolution 레이어 1
      with tf.variable_scope('conv1') as scope:
    # 커널(필터) 초기화 : 5 X 5 크기의 3채널 필터를 만들며 64개의 커널을 사용한다.
        kernel = _variable_with_weight_decay('weights',
                                             shape=[5, 5, 3, 64],
                                             stddev=5e-2,
                                             wd=None)
        conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    
    


    이 부분은 CNN의 핵심이며 가장 중요한 부분이므로 좀 더 상세하게 알아보자.
    일단 필터(커널보다 친숙하므로 앞으로는 ‘필터’로만 표기하겠다. 또한 원칙적으로는 bias까지 +되어야 완성된
    필터라 할 수 있으나 우선은 bias를 무시하고 생각해보자)가 하는 역할이 무엇인지부터 알아보면 말 그대로 
    이미지에서 지정된 영역의 특징만을 ‘걸러내는’ 역할을 한다. 


    그러면 어떤 방식으로 특징을 걸러내는가?
    바로 머신러닝이나 딥러닝을 처음 배울때 배웠던 xW + b의 함수를 사용해서 처리한다. 일단 bias는 무시하기로
    했으니 xW만 생각해본다면 입력받은 이미지에서 필터와 겹치는 부분을 x라 보고 해당 위치의 필터를 W라 보아
    x1* W1 + x2 * W2 + … + xn * Wn이 되는 것이다. 만약 3 X 3 필터를 사용하였다면 아래와 같이 계산할 수
    있다.


    x1 * W1 + x2 * W2 + x3 * W3 + ... x9 * W9
    


    여기에 만일 입력 채널(이미지의 색상 채널)이 3이라면 각 채널마다 위의 계산을 적용한 후 각 채널별 출력값을
    최종 더해서 하나의 feature map을 만들게 된다. 결국 하나의 필터가 하나의 feature map을 만들게 되므로
    만일 필터를 여러개 사용한다면 feature map의 개수도 필터의 개수와 동일하게 만들어지고 이 수가 곧 
    feature map의 채널이 된다(그리고 이 각각의 채널에 bias를 +하게 된다). 


    이 내용을 이해 못해 수없이 구글링을 했으나 적절한 자료를 찾지 못했는데 아래 이미지를 보고 쉽게 이해할 수 있었다.


    CNN Filter feature map

    이미지 출처 : http://taewan.kim/post/cnn/


    이 코드를 가지고 계산을 해보면 24 X 24 크기의 3채널 이미지를 입력으로 받아 5 X 5 크기의 3채널 필터 64개를
    사용하고 padding이 원본 크기와 동일한 출력이 나오도록 SAME으로 지정되었으므로 24 X 24 크기에 64 채널을
    가진 출력이 나올 것이다. 여기에 배치 사이즈가 128이므로 최종 출력 텐서의 shape는 [128, 24, 24, 64]가 된다.


    # 바이어스 초기화
    # 채널의 개수가 64개이므로 bias도 64개 설정. biases는 64개의 요소가 0.0으로 채워진
    # vector
        biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    
    # 가중치 + 바이어스. biases는 conv의 마지막 차수와 일치하는 1차원 텐서여야 한다.
        pre_activation = tf.nn.bias_add(conv, biases)
    
    # 활성화 함수 추가
        conv1 = tf.nn.relu(pre_activation, name=scope.name)
    
    # 텐서 보드에서 확인하기 위한 호출
        _activation_summary(conv1)
    
      # pool1
    # 풀링 레이어 1
    # pooling은 간단하게 말해 이미지를 축소하는 단계로 필터로 주어진 영역 내에서 특정한 값(평균,최대,최소)을
    뽑아내는 작업이다. 일단 최대값을 뽑는 것이 가장 성능이 좋다고 하여 max pooling을 주로 사용한단다.
    # 이 코드에서는 필터 크기가 3 X 3이므로 이 영역에서 가장 큰 값만을 뽑아 사용한다. stride는 2를 사용한다.
      pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                             padding='SAME', name='pool1')
      # norm1
    # local response normalization라는 정규화 처리인데 ReLu 사용시 에러율 개선에 
    # 효과가 있다고 하는데 이 부분은 좀 더 확인이 필요함
      norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                        name='norm1')
    
      # conv2
    # convolution 레이어 2
      with tf.variable_scope('conv2') as scope:
        kernel = _variable_with_weight_decay('weights',
                                             shape=[5, 5, 64, 64],
                                             stddev=5e-2,
                                             wd=None)
        conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
        pre_activation = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(pre_activation, name=scope.name)
        _activation_summary(conv2)
    
      # norm2
    # local response normalization 2
      norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                        name='norm2')
      # pool2
    # 풀링 레이어 2
      pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                             strides=[1, 2, 2, 1], padding='SAME', name='pool2')
    
      # local3
    # fully connected layer 
      with tf.variable_scope('local3') as scope:
        # Move everything into depth so we can perform a single matrix multiply.
        reshape = tf.reshape(pool2, [FLAGS.batch_size, -1])
        dim = reshape.get_shape()[1].value
        weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                              stddev=0.04, wd=0.004)
        biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
        local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
        _activation_summary(local3)
    
      # local4
    # fully connected layer 2
      with tf.variable_scope('local4') as scope:
        weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                              stddev=0.04, wd=0.004)
        biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
        local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
        _activation_summary(local4)
    
      # linear layer(WX + b),
      # We don't apply softmax here because
      # tf.nn.sparse_softmax_cross_entropy_with_logits accepts the unscaled logits
      # and performs the softmax internally for efficiency.
    # softmax layer
      with tf.variable_scope('softmax_linear') as scope:
        weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                              stddev=1/192.0, wd=None)
        biases = _variable_on_cpu('biases', [NUM_CLASSES],
                                  tf.constant_initializer(0.0))
        softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
        _activation_summary(softmax_linear)
    
      return softmax_linear
    


    이 함수의 코드는 Convolutional layer > ReLu layer > Pooling Layer > Norm layer > Convolutional layer 
    > ReLu layer > Norm layer > Pooling layer > Fully connected layer > Fully connected layer > 
    Softmax layer의 순으로 구성이 되어있는데 이 중 Norm layer가 정확히 어떤 역할을 하는지는 아직 잘 모르겠다.
    일단 ReLu를 보조하는 것 같은데 더 알아봐야겠다.


    loss(logits, labels)

    # 손실 값 계산을 위한 함수
    # 아래 주석에서 보이듯 logits 파라미터는 inference() 함수의 리턴 값이고 labels는 distorted_input()
    # 또는 input() 함수의 리턴 튜플 중 labels 부분이다. cross entropy를 이용하여 loss를 구한다.
    """Add L2Loss to all the trainable variables.
      Add summary for "Loss" and "Loss/avg".
      Args:
        logits: Logits from inference().
        labels: Labels from distorted_inputs or inputs(). 1-D tensor
                of shape [batch_size]
      Returns:
        Loss tensor of type float.
      """
      # Calculate the average cross entropy loss across the batch.
    # 여기서는 sparse_softmax_cross_entropy_with_logits 함수가 사용되고 있는데
    # softmax_cross_entropy_with_logits와의 차이라면 softmax_cross_entropy_with_logits
    # 함수가 확률분포를를 따른다면 sparse_softmax_cross_entropy_with_logits는 독점적인 확률로
    # label이 주어진다고 하는데...무슨 의미인지 잘 모르겠다...ㅠ.ㅠ 확인이 필요한 내용
      labels = tf.cast(labels, tf.int64)
      cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
          labels=labels, logits=logits, name='cross_entropy_per_example')
      cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
      tf.add_to_collection('losses', cross_entropy_mean)
    
      # The total loss is defined as the cross entropy loss plus all of the weight
      # decay terms (L2 loss).
      return tf.add_n(tf.get_collection('losses'), name='total_loss')
    


    _add_loss_summaries(total_loss)

    # 텐서 보드에 손실값 표시를 위해 손실 값에 대한 summary 추가하고
    # 손실값들의 이동 평균을 구하여 리턴. 여기서 사용하는 이동 평균은 가장 최근 값에 가중치를 두는
    # tf.train.ExponentialMovingAverage을 사용하여 구한다.
    """Add summaries for losses in CIFAR-10 model.
      Generates moving average for all losses and associated summaries for
      visualizing the performance of the network.
      Args:
        total_loss: Total loss from loss().
      Returns:
        loss_averages_op: op for generating moving averages of losses.
      """
      # Compute the moving average of all individual losses and the total loss.
      loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
      losses = tf.get_collection('losses')
      loss_averages_op = loss_averages.apply(losses + [total_loss])
    
      # Attach a scalar summary to all individual losses and the total loss; do the
      # same for the averaged version of the losses.
      for l in losses + [total_loss]:
        # Name each loss as '(raw)' and name the moving average version of the loss
        # as the original loss name.
        tf.summary.scalar(l.op.name + ' (raw)', l)
        tf.summary.scalar(l.op.name, loss_averages.average(l))
    
      return loss_averages_op
    


    train(total_loss, global_step)

    # 학습을 실행시키는 함수
    """Train CIFAR-10 model.
      Create an optimizer and apply to all trainable variables. Add moving
      average for all trainable variables.
      Args:
        total_loss: Total loss from loss().
        global_step: Integer Variable counting the number of training steps
          processed.
      Returns:
        train_op: op for training.
      """
      # Variables that affect learning rate.
    # 미리 정의한 변수들을 이용하여 러닝 rate를 조정할 파라미터를 결정한다. 
      num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
      decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)
    
      # Decay the learning rate exponentially based on the number of steps.
    # 학습 step이 증가할 수록 러닝 rate를 기하급수적으로 감소시키도록 처리한다.
    # tf.train.exponential_decay 함수는 아래 식의 결과를 리턴한다.
    # INITIAL_LEARNING_RATE * LEARNING_RATE_DECAY_FACTOR ^ (global_step / decay_steps)
      lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                      global_step,
                                      decay_steps,
                                      LEARNING_RATE_DECAY_FACTOR,
                                      staircase=True)
      tf.summary.scalar('learning_rate', lr)
    
      # Generate moving averages of all losses and associated summaries.
      loss_averages_op = _add_loss_summaries(total_loss)
    
    # Optimizer 설정 및 텐서 보드에 표시하기 위한 summary 생성 후 추가
      # Compute gradients.
      with tf.control_dependencies([loss_averages_op]):
        opt = tf.train.GradientDescentOptimizer(lr)
        grads = opt.compute_gradients(total_loss)
    
      # Apply gradients.
      apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)
    
      # Add histograms for trainable variables.
      for var in tf.trainable_variables():
        tf.summary.histogram(var.op.name, var)
    
      # Add histograms for gradients.
      for grad, var in grads:
        if grad is not None:
          tf.summary.histogram(var.op.name + '/gradients', grad)
    
      # Track the moving averages of all trainable variables.
      variable_averages = tf.train.ExponentialMovingAverage(
          MOVING_AVERAGE_DECAY, global_step)
      variables_averages_op = variable_averages.apply(tf.trainable_variables())
    
    # tf.control_dependencies 함수는 오퍼레이션간의 의존성을 지정하는 함수로 with와 함께
    # 사용하면 파라미터로 전달된 오퍼레이션이 우선 수행된 뒤 다음 문장, 여기서는 with문 아래 있는
    # train_op = tf.no_op(name='train')이 수행된다. 
      with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
        train_op = tf.no_op(name='train')
    
    # 이미 알다시피 여기까지는 그저 그래프를 만든 것 뿐, 이제 tf.Session을 통해 run을 하면
    # 이전까지 구성된 그래프가 실행된다. 실제로 실행시키는 내용은 cifar10_tranin.py에 들어있다. 
      return train_op
    


    maybe_download_and_extract()

    # 웹사이트로부터 CIFAR-10 데이터 셋을 다운로드 받아 사용할 경로에 압축을 풀게 하는 함수
    # 이미 별도로 데이터 셋을 받아놓은 경우는 필요 없음
    """Download and extract the tarball from Alex's website."""
      dest_directory = FLAGS.data_dir
      if not os.path.exists(dest_directory):
        os.makedirs(dest_directory)
      filename = DATA_URL.split('/')[-1]
      filepath = os.path.join(dest_directory, filename)
      if not os.path.exists(filepath):
        def _progress(count, block_size, total_size):
          sys.stdout.write('\r>> Downloading %s %.1f%%' % (filename,
              float(count * block_size) / float(total_size) * 100.0))
          sys.stdout.flush()
        filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath, _progress)
        print()
        statinfo = os.stat(filepath)
        print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
      extracted_dir_path = os.path.join(dest_directory, 'cifar-10-batches-bin')
      if not os.path.exists(extracted_dir_path):
        tarfile.open(filepath, 'r:gz').extractall(dest_directory)
    


    정리


    핵심적인 내용들이 대부분 들어있는 소스이다보니 잊어버린 내용 되찾으랴 또 생소한 API 확인하랴 시간이
    많이 걸렸다.


    단지 시간만 많이 걸린 것이면 그나마 다행이지만 꽤 많은 부분을 이해하지 못한다는 것은 참으로 난감한 일이
    아닐 수 없다…ㅠ.ㅠ 그래도 기본적인 CNN의 흐름을 따라 어찌어찌 정리는 했지만 여전히 확인해야 할 내용들이
    많이 남아있다. 특히나 API의 경우 기본적으로 파라미터나 리턴 값들이 텐서를 기반으로 하고 있는데다가 설명
    또한 수학적인 내용이나 용어들을 포함하고 있다보니 java나 python 같은 프로그래밍 언어의 API 문서를
    대하는 것과는 그 이해의 차원이 다르다.


    일단 중요한 고비는 넘겼으니 다음 포스팅에서 학습을 진행하기 위한 메인 소스인 cifar10_train.py를
    살펴보고 그 다음 마지막으로 cifar10_eval.py를 살펴본 후 이 소스 코드에 등장했던 API들을 모두
    차근차근 번역해봐야겠다.

    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요





    목차

    1. 소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10 ~ cifar10_input.py (이번 글)





    소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10


    나름 직장 동료들과 열심히 공부를 하고 있고 또 이 딥러닝이라는 분야의 공부를 시작한지도 어언 1년이 다되간다.
    하지만 한 때 유행했던 유머처럼 ‘딥러닝을 글로만 배웠어요~’인 상태이다보니 제대로 뭔가를 알고 있는 것인지
    감조차 오지 않았다. 그래서 이제야 비로소 예제 코드를 돌려보기로 했다. 


    다만 그저 샘플 소스를 다운로드 받고 실행하고 끝! 하는 것이 아닌 적어도 소스 코드가 어떤 의미인지는 알고
    돌려보기로 했다. 그 시작으로 CNN쪽에 있는 CIFAR-10 예제를 대상으로 삼았다.


    처음에는 함께 공부하는 직장 동료들과 직독직해 식으로 소스를 분석해보려고 했으나…
    이런 상황을 ‘자만심 오졌다리~’라고 표현해야 하나…처음 import부터 막혀서 쩔쩔매다가 일단 내가
    분석을 좀 하고 내용을 공유하기로 한 것이다.


    이러한 형편이니 혹시라도 잘못된 내용이 있으면 따끔한 충고 부탁드린다…^^;;


    cifar10_input.py


    # sys.path 상의 가장 상위 모듈을 import 하는 것을 보장해 줌. 
    from __future__ import absolute_import
    # /연산자와 더불어 // 연산자 사용 가능, / 연산자는 실수형을 리턴, // 연산자는 몫 부분만 정수로 리턴
    from __future__ import division
    # print 함수에 ()를 사용할 수 있게 함
    from __future__ import print_function
    


    __future __의 의미 : Python 2에서 Python 3 함수를 사용할 수 있게 해줌
    위의 3줄은 Python 2와 Python 3의 호환성을 위한 import이다.


    # OS의 자원을 사용할 수있게 해주는 모듈
    import os
    
    # six(2 * 3)는 Python 2와 Python 3에서 차이나는 함수들을 함께 사용할 수 있게 해줌
    # xrange는 3에서는 range
    from six.moves import xrange  # pylint: disable=redefined-builtin
    # 아기다리고기다리던 텐서플로우
    import tensorflow as tf
    


    데이터를 읽어들이기 위해 OS 자원을 사용하도록 해주고 range의 하위호환성을 위해 xrange를 import 했으며
    마지막으로 텐서플로우를 import 함


    IMAGE_SIZE = 24
    


    32 X 32 사이즈의 이미지를 랜덤하게 24 X 24 사이즈로 Corp함으로써 전체 데이터셋의 크기가 커진다.


    NUM_CLASSES = 10
    NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
    NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000
    


    CIFAR-10 데이터 셋 관련 상수로 총 10개의 클래스(비행기, 자동차, 새, 고양이, 사슴, 개, 개구리, 말, 배, 트럭)가
    있으며 학습을 위한 데이터 50000건 테스트를 위한 데이터 10000건으로 구성된다.


    이 파일에는 총 4개의 함수가 있으며 각각 다음과 같다.

    • read_cifar10(filename_queue) : 파일 이름 목록을 받아와 CIFAR-10의 바이너리 데이터를 읽고 파싱하여 단일 오브젝트 형태로 반환한다. 이 오브젝트에는 height, width, depth, key, label, uint8image 등의 필드가 있다.
    • _generate_image_and_label_batch(image, label, min_queue_examples, batch_size, shuffle) : image와 label들을 담은 배치용 queue를 만들어 리턴한다.
    • distorted_inputs(data_dir, batch_size) : 데이터셋 확대를 위한 이미지 왜곡 작업을 진행한다.
      read_cifar10 함수를 호출하여 그 리턴 값을 가지고 작업한다. 학습 시 사용.
    • inputs(eval_data, data_dir, batch_size) : 평가를 위한 input에 사용하며 역시 read_cifar10
      함수를 호출하여 사용하며 Crop 외에 다른 조작은 하지 않는다. 


    이미 코드에 영문 주석이 다 있지만 추가로 한글 주석을 추가하며 알아보자.


    distorted_inputs(data_dir, batch_size)

    def distorted_inputs(data_dir, batch_size):
      """Construct distorted input for CIFAR training using the Reader ops.
      Args:
        data_dir: Path to the CIFAR-10 data directory.
        batch_size: Number of images per batch.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      """
    
    # os.path.join 함수는 전달받은 파라미터를 이어 새로운 경로를 만드는 함수
    # 아래 코드는 이 함수에서 파라미터로 받은 data_dir 경로와 그 경로 아래에 있는
    # CIFAR-10의 이미지 파일이 담긴 data_batch_1.bin ~ data_batch_5.bin의
    # 5개 파일에 대한 전체 경로를 요소로 하는 벡터(텐서)를 만드는 것이다.
      filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
                   for i in xrange(1, 6)]
    
    # 만일 배열 내에 파일 경로가 없으면 에러 발생
      for f in filenames:
        if not tf.gfile.Exists(f):
          raise ValueError('Failed to find file: ' + f)
    
    # string_input_producer 함수는 필수 파라미터인 첫 번째 파라미터에 string 타입의 요소로 만들어진 
    # 텐서 타입을 받아서 각 요소 문자열로 구성된 Queue 형태로 리턴을 해준다.
      # Create a queue that produces the filenames to read.
      filename_queue = tf.train.string_input_producer(filenames)
    
      with tf.name_scope('data_augmentation'):
        # Read examples from files in the filename queue.
    # 아래 설명할 read_cifar10 함수로부터 라벨, 이미지 정보 등을 포함한 
    # CIFAR10Record 클래스 타입을 톨려받는다.
        read_input = read_cifar10(filename_queue)
    
    # cast 함수는 첫 번째 인자로 받은 텐서 타입의 파라미터를 두 번째 인자로 받은
    # 데이터 타입의 요소를 가진 텐서로 돌려준다.
        reshaped_image = tf.cast(read_input.uint8image, tf.float32)
    
        height = IMAGE_SIZE
        width = IMAGE_SIZE
    
        # Image processing for training the network. Note the many random
        # distortions applied to the image.
    
        # Randomly crop a [height, width] section of the image.
    # tf.random_crop 함수는 첫 번째 파라미터로 받은 텐서타입의 이미지들을 
    # 두 번째 파라미터로 받은 크기로 무작위로 잘라 첫 번째 받은 파라미터와 같은 rank의
    # 텐서 형태로 돌려준다. 
        distorted_image = tf.random_crop(reshaped_image, [height, width, 3])
    
        # Randomly flip the image horizontally.
    # 좌우를 랜덤하게 뒤집은 형태의 텐서를 돌려준다.
        distorted_image = tf.image.random_flip_left_right(distorted_image)
    
        # Because these operations are not commutative, consider randomizing
        # the order their operation.
        # NOTE: since per_image_standardization zeros the mean and makes
        # the stddev unit, this likely has no effect see tensorflow#1458.
    # 밝기와 콘트라스트를 랜텀하게 변형시킨 텐서를 돌려준다.
        distorted_image = tf.image.random_brightness(distorted_image,
                                                     max_delta=63)
        distorted_image = tf.image.random_contrast(distorted_image,
                                                   lower=0.2, upper=1.8)
    # random_crop부터 random_contrast까지는 데이터 셋 확장을 위해 이미지를 임의 조작하는
    # 과정이다.
    
        # Subtract off the mean and divide by the variance of the pixels.
    # 이미지를 표준화 하는 과정인 듯한데...어려워서 패쓰~
        float_image = tf.image.per_image_standardization(distorted_image)
    
        # Set the shapes of tensors.
    # 텐서의 shape 설정
        float_image.set_shape([height, width, 3])
        read_input.label.set_shape([1])
    
        # Ensure that the random shuffling has good mixing properties.
    # 전체 테스트용 이미지의 40%, 즉, 총 50000개의 테스트 이미지 중 20000개를 사용
        min_fraction_of_examples_in_queue = 0.4
        min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN *
                                 min_fraction_of_examples_in_queue)
        print ('Filling queue with %d CIFAR images before starting to train. '
               'This will take a few minutes.' % min_queue_examples)
    
      # Generate a batch of images and labels by building up a queue of examples.
    # 배치 작업에 사용할 128개의 이미지를 shuffle하여 리턴함
      return _generate_image_and_label_batch(float_image, read_input.label,
                                             min_queue_examples, batch_size,
                                             shuffle=True)
    


    read_cifar10(filename_queue)

    """Reads and parses examples from CIFAR10 data files.
      Recommendation: if you want N-way read parallelism, call this function
      N times.  This will give you N independent Readers reading different
      files & positions within those files, which will give better mixing of
      examples.
      Args:
        filename_queue: A queue of strings with the filenames to read from.
      Returns:
        An object representing a single example, with the following fields:
          height: number of rows in the result (32)
          width: number of columns in the result (32)
          depth: number of color channels in the result (3)
          key: a scalar string Tensor describing the filename & record number
            for this example.
          label: an int32 Tensor with the label in the range 0..9.
          uint8image: a [height, width, depth] uint8 Tensor with the image data
      """
    
    # 이 함수의 리턴 값은 CIFAR10Record라는 class임 pass는 비어있는 클래스 선언 시 사용
    # 이미 아는 바와 같이 텐서플로우의 Session.run이 실행되기 전까지는 비어있는 클래스이며
    # Session.run이 실행된 이후에야 데이터 파일의 레코드들이 클래스에 들어가게 된다.
      class CIFAR10Record(object):
        pass
      result = CIFAR10Record()
    
    # label_bytes는 말 그대로 라벨의 길이이고 1byte이다.
    # result.height는 이미지의 높이
    # result.width는 이미지의 넓이
    # result.depth는 이미지를 구성하는 색상 채널
    # image_bytes 결국 이미지를 구성하는 총 byte 수는 높이 * 넓이 * 색상 채널
    
      # Dimensions of the images in the CIFAR-10 dataset.
      # See http://www.cs.toronto.edu/~kriz/cifar.html for a description of the
      # input format.
      label_bytes = 1  # 2 for CIFAR-100
      result.height = 32
      result.width = 32
      result.depth = 3
      image_bytes = result.height * result.width * result.depth
    
    # 모든 레코드는 라벨과 라벨에 해당하는 이미지로 구성되어있으므로  
    # 전체 레코드 크기는 label_bytes + image_bytes로 고정
      # Every record consists of a label followed by the image, with a
      # fixed number of bytes for each.
      record_bytes = label_bytes + image_bytes
    
    # tf.FixedLengthRecordReader는 파일로부터 고정길이의 레코드를 출력해주는 클래스
    # 생성 시 첫 번째 파라미터는 읽어올 레코드의 바이트 수
      # Read a record, getting filenames from the filename_queue.  No
      # header or footer in the CIFAR-10 format, so we leave header_bytes
      # and footer_bytes at their default of 0.
      reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
    
    # Queue 타입(FIFO)의 자료 구조를 파라미터로 받아 그 안의 레코드로부터
    # Key와 Value를 받아오는 처리. key는 레코드가 포함된 파일명과 index의 구성으로
    # 되어있으며, value는 사용할 라벨과 이미지가 포함된 텐서임.
      result.key, value = reader.read(filename_queue)
    
      # Convert from a string to a vector of uint8 that is record_bytes long.
    # byte 타입의 문자열을 숫자형 벡터로 변환. 첫 번째 인자는 문자열로 구성된 텐서이며
    # 모든 요소들은 동일한 길이여야 함. 두 번째 인자는 변환할 데이터 타입
      record_bytes = tf.decode_raw(value, tf.uint8)
    
      # The first bytes represent the label, which we convert from uint8->int32.
    # 첫 번째 인자로 받은 텐서를 두 번째 인자로 받은 데이터 타입으로 형변환 함.
    # 즉, 아래 코드는 위에서 구성된 record_bytes에서 첫 번째 바이트를 가져와 int32
    # 타입으로 변환하여 리턴한다. 따라서 result.label은 1바이트 크기의 int32 타입 요소를
    # 갖는 벡터이다.
      result.label = tf.cast(
          tf.strided_slice(record_bytes, [0], [label_bytes]), tf.int32)
    
      # The remaining bytes after the label represent the image, which we reshape
      # from [depth * height * width] to [depth, height, width].
    # tf.reshape는 첫 번째 파라미터의 shape를 두 번째 파라미터로 받은 형태로 바꾼다.
    # 아래 코드의 첫 번째 인자는 record_bytes에서 첫 바이트인 라벨을 제외한 나머지
    # 바이트(이미지 부분)를 가져와 [3, 32, 32] 형태의 shape로 바꾼다. 
      depth_major = tf.reshape(
          tf.strided_slice(record_bytes, [label_bytes],
                           [label_bytes + image_bytes]),
          [result.depth, result.height, result.width])
      # Convert from [depth, height, width] to [height, width, depth].
    # tf.transpose는 첫 번째 파라미터로 받은 텐서의 각 차원 값을 두 번째 파라미터로 전달받은
    # 순서로 바꾼 텐서를 리턴한다. 위의 depth_major의 shape는 [3, 32, 32]이다.
    # 즉, shape의 0번째 요소는 3, 1번째 요소는 32, 2번째 요소는 32이다. 이 것을 두 번째
    # 파라미터처럼 인덱스를 [1, 2, 0]로 바꾸는 것이므로 1 번째 요소인 32가 맨 앞으로, 다음으로
    # 2 번째 요소인 32가 오고 0번째 요소인 3은 맨 마지막으로 가게 되는 것이다.
    # 결국 최초에 [depth, height, width]의 순서가 [height, width, depth]가 된다.
      result.uint8image = tf.transpose(depth_major, [1, 2, 0])
    
    # 테스트 코드 시작 ##############################################
    # 원본 코드에는 없는 내용이지만 아래 코드를 이용하여 간단하게 데이터를 정상적으로 불러왔는지
    # 확인할 수 있다. 아래 코드를 싫행하면 총 100개의 이미지가 10 X 10 형태로 배열된 1개의 이미지가
    # 만들어지며, label, key, value 값을 확인할 수 있다.
    # 이 코드를 사용하려면 matplotlib.pyplot을 import해야 한다.
     fig, ax = plt.subplots(10, 10, figsize=(10, 10))
      with tf.Session() as sess:
          coord = tf.train.Coordinator()
          threads = tf.train.start_queue_runners(coord=coord, sess=sess)
    
          for i in range(10):
              for j in range(10):
                  print(sess.run(result.label), sess.run(result.key), sess.run(value))
                  img = sess.run(result.uint8image)
                  ax[i][j].set_axis_off()
                  ax[i][j].imshow(img)
    
          dir = os.path.abspath("cifar10_image")
          plt.savefig(dir + "/" + "image")
          print(dir)
    
          coord.request_stop()
          coord.join(threads)
    # 테스트 코드 끝 ############################################
    
      return result
    


    _generate_image_and_label_batch(image, label, min_queue_examples, batch_size, shuffle)

    """Construct a queued batch of images and labels.
      Args:
        image: 3-D Tensor of [height, width, 3] of type.float32.
        label: 1-D Tensor of type.int32
        min_queue_examples: int32, minimum number of samples to retain
          in the queue that provides of batches of examples.
        batch_size: Number of images per batch.
        shuffle: boolean indicating whether to use a shuffling queue.
      Returns:
        images: Images. 4D tensor of [batch_size, height, width, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      """
      # Create a queue that shuffles the examples, and then
      # read 'batch_size' images + labels from the example queue.
    # 각각 배치를 생성하는 코드로 shuffle_batch는 무작위로 뒤섞은 배치를 생성하며
    # batch는 입력 텐서와 레코드 순서가 동일한 배치를 생성한다. 배치 생성 시 16개의
    # thread를 사용한다.
      num_preprocess_threads = 16
      if shuffle:
        images, label_batch = tf.train.shuffle_batch(
            [image, label],
            batch_size=batch_size,
            num_threads=num_preprocess_threads,
            capacity=min_queue_examples + 3 * batch_size,
            min_after_dequeue=min_queue_examples)
      else:
        images, label_batch = tf.train.batch(
            [image, label],
            batch_size=batch_size,
            num_threads=num_preprocess_threads,
            capacity=min_queue_examples + 3 * batch_size)
    
      # Display the training images in the visualizer.
    # 텐서보드에서 이미지를 보여주긴 위한 코드
      tf.summary.image('images', images)
    
    # 배치 과정을 거친 이미지와 라벨의 최종 shape는 각각 [128, 32, 32, 3]과 [128]이다.
      return images, tf.reshape(label_batch, [batch_size])
    


    inputs(eval_data, data_dir, batch_size)

    """Construct input for CIFAR evaluation using the Reader ops.
      Args:
        eval_data: bool, indicating if one should use the train or eval data set.
        data_dir: Path to the CIFAR-10 data directory.
        batch_size: Number of images per batch.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      """
    
    # eval_data 값에 따라 학습용 데이터를 불러올지 평가용 데이터를 불러올지 결정한다.
      if not eval_data:
        filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
                     for i in xrange(1, 6)]
        num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN
      else:
        filenames = [os.path.join(data_dir, 'test_batch.bin')]
        num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_EVAL
    
    # 이후 코드는 이미지 변형 (random_flip_left_right, random_brightness,
    # random_contrast) 처리를 제외하고는 distorted_inputs(data_dir, batch_size)
    # 함수와 동일하다.
      for f in filenames:
        if not tf.gfile.Exists(f):
          raise ValueError('Failed to find file: ' + f)
    
      with tf.name_scope('input'):
        # Create a queue that produces the filenames to read.
        filename_queue = tf.train.string_input_producer(filenames)
    
        # Read examples from files in the filename queue.
        read_input = read_cifar10(filename_queue)
        reshaped_image = tf.cast(read_input.uint8image, tf.float32)
    
        height = IMAGE_SIZE
        width = IMAGE_SIZE
    
        # Image processing for evaluation.
        # Crop the central [height, width] of the image.
        resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image,
                                                               height, width)
    
        # Subtract off the mean and divide by the variance of the pixels.
        float_image = tf.image.per_image_standardization(resized_image)
    
        # Set the shapes of tensors.
        float_image.set_shape([height, width, 3])
        read_input.label.set_shape([1])
    
        # Ensure that the random shuffling has good mixing properties.
        min_fraction_of_examples_in_queue = 0.4
        min_queue_examples = int(num_examples_per_epoch *
                                 min_fraction_of_examples_in_queue)
    
      # Generate a batch of images and labels by building up a queue of examples.
      return _generate_image_and_label_batch(float_image, read_input.label,
                                             min_queue_examples, batch_size,
                                             shuffle=False)
    


    정리


    늘 어처구니 없는 실수가 따라다닌다.
    CIFAR-10 홈페이지에 가면 다음과 같이 데이터 셋이 3가지 버전이 있다.

    • CIFAR-10 python version
    • CIFAR-10 Matlab version
    • CIFAR-10 binary version (suitable for C programs)


    나는 Tensorflow가 python 기반으로 코딩이 되므로 당연히 python versiond을 받아야 한다고 생각했다.
    그런데 python 버전을 사용하여 코드를 실행하다보니 뭔가 이상했다. 간간히 데이터를 제대로 불러왔는지
    확인하기 위한 print문에 이상한 결과가 찍히는 것이다. CIFAR10Record 클래스의 멤버들에 대한 shape나
    rank는 물론 중간에 시험삼아 100개의 이미지를 출력한 것도 모든 이미지가 깨져서 나왔다.



    주말 2일을 고민하다가 문득 원래의 코드에는 파일명을 가져올 때 .bin이라는 확장자가 있었는데 내가 사용하는
    데이터 파일에는 확장자가 없는 것을 발견했다. 그리고 겨우 내가 잘못된 버전의 데이터 셋을 받았다는 것을 
    깨달았다…ㅠ.ㅠ


    새로 받은 버전의 데이터 셋은 아래와 같이 이미지가 정상적으로 나왔다.



    이제 겨우 파일 하나 분석해봤을 뿐인데 벌써 지친다…특히나 텐서라는 개념과 행렬 연산 그리고 Tensorflow의
    지연 실행이라는 메커니즘은 정말 적응이 안된다…ㅠ.ㅠ 다음 포스팅에서는 cifar10.py 파일을 분석해보자.

    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요


    Android Things : TensorFlow 예제


    Android Things 홈페이지의 글만 번역을 하다가 너무 지루해서 예제 하나를 돌려보기로 했다.
    기왕지사 돌리는 것, 조금은 있어보이는 것으로 돌려보자 하고 TensorFlow 예제를 돌려보기로 했다.
    뭐, 내가 하는 일이 늘 그렇듯이 한번에 잘 되지는 않았다. 몇가지 실수와 실행 결과를 살펴보도록 하겠다.


    TensorFlow 예제를 위한 준비


    Android Thinsg의 TensorFlow 예제는 다음 링크에서 다운로드 받을 수 있다.


    https://github.com/androidthings/sample-tensorflow-imageclassifier


    이 예제는 대기 상태에서 LED가 점등되고 버튼을 누르면 카메라로 이미지를 촬영하여 촬영된 이미지를
    TensorFlow의 모델로 전달하게 된다. 그러면 TensorFlow에서 이 이미지를 분석하여 그 결과를 logcat이나
    모니터가 연결된 경우 모니터로 출력으 해주는 것이다. 스피커가 연결된 경우 결과 문자열을 읽어(text-to-speech)
    스피커로도 출력을 해주나 이 부분은 성공하지 못했다.


    그밖의 준비사항 및 회로 연결은 위 링크를 참고하시기 바란다.


    기본적으로 라즈베리 파이3에 Android Things를 설치한 상태여야 하며 Android Studio에서 인식할 수 있도록
    개발PC와 USB로 연결이 되어있어야 한다. 물론 TensorFlow 예제 실행을 위해 네트워크도 연결이 되어있어야 한다.
    로그캣으로도 결과를 확인할 수 있다고는 하나 모니터가 연결된 경우 촬영된 이미지와 함께 결과 분석을 함께 볼 수
    있어 더 좋다.


    몇가지 실수


    우선 회로를 구성할 때 사용한 빵판이 미니 빵판으로 전원부가 별도로 없는 빵판이었다. 그 생각을 미처 못하고 전원과
    GND를 가로로 나란히 연결했다가 몽창 태워먹을 뻔했다…-.- 다행히 예전에 4족보행 로봇 만들 때 짧게 잘라놓은
    기존 빵판의 전원부 조각이 있어서 이를 이용하여 전원을 연결하였더니 정상적으로 회로가 작동하였다.


    다음으로 발생한 문제는 Android Studio를 통해 정상적으로 앱을 설치하였는데 대략 다음과 같은 오류가 발생을
    하였다.

    05-20 17:48:47.254 1771-1771/com.example.androidthings.button E/AndroidRuntime: FATAL EXCEPTION: main 
    Process: com.example.androidthings.button, PID: 1771
    java.lang.IllegalAccessError: Method 'void com.google.android.things.userdriver.InputDriver$Builder.<init>(int)' is inaccessible to class 'com.google.android.things.contrib.driver.button.ButtonInputDriver' (declaration of 'com.google.android.things.contrib.driver.button.ButtonInputDriver' appears in /data/app/com.example.androidthings.button-1/base.apk)
       at com.google.android.things.contrib.driver.button.ButtonInputDriver.build(ButtonInputDriver.java:98)
       at com.google.android.things.contrib.driver.button.ButtonInputDriver.register(ButtonInputDriver.java:82)
       at com.example.androidthings.button.ButtonActivity.onCreate(ButtonActivity.java:64)
       at android.app.Activity.performCreate(Activity.java:6662)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
       at android.app.ActivityThread.-wrap12(ActivityThread.java)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6077)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
    


    이 문제는 아래 링크에서 확인할 수 있엇는데 결론은 Android Things Developer Preview의 최신 버전을 
    사용하라는 것이었다(링크한 페이지에서는 DP4 버전이 언급되어있지만 현재 최종 버전은 DP4.1 이다).


    https://github.com/androidthings/sample-button/issues/1


    여기까지 다 조치를 하였고 이제 세 번째 문제에 봉착을 하게 된다. 바로 아래와 같은 오류가 발생한 것이다.

    Android things Cannot capture image. Camera not initialized.
    


    내용을 보면 알겠지만 카메라를 사용할 수 없다는 메시지였다. 확인해보니 카메라를 라즈베리파이에 연결하는데
    뒤집어서 연결을 하였다. 접촉 핀이 있는 부분이 랜 포트 반대 방향으로 향해야 하는데 랜포트 방향으로
    꽂아버렸던 것이다. 암튼 간단하게 바로 꽂아서 이 부분도 통과~


    그리고 드디어 Run!!!


    TesorFlow 샘플 실행


    우선 처음 실행을 하게 되면 다음과 같이 2단계의 승인 과정을 거치게 된다.


    모두 Allow를 선택하면 아래 이미지와 같은 상태로 stand by하게 된다. 이 상태에서 회로도에 있는 푸시 버튼을
    클릭하게 되면 LED가 잠깐 꺼지고 카메라 촬영이 진행되며 모니터에 촬영된 이미지와 함께 분석 내용이 파란 
    영역에 표시된다.


    간단하게 책상 위에 이것 저것 놓고 촬영을 해보았는데 결과는 영 신통치 않다. 일단 카메라가 8MP로 해상도가 낮아
    결과물의 품질도 좋지 않고 그래서 그런지 제대로 맞추는 것이 없다…-.- 이 예제의 소개에 보면 예제에 쓰인
    TesorFlow의 학습이 Google의 시작 모델을 통해 이루어졌다는 것으로 봐서 초기 제품의 한계라고 봐야 할 것
    같다.


    가장 충격적인 사실은 내가 샤워 캡을 닮았다는 것이다…ㅠ.ㅠ


    테스트 결과는 아래와 같다.


    몇가지 팁


    사실상 Android Things 플랫폼을 설치해보고 실제로 응용 앱은 처음 설치를 해보았다.
    그러다보니 이 설치된 앱을 어떻게 종료하거나 실행시키는지 또는 어떻게 삭제하는지를 전혀 몰랐다.
    그래서 간단하게 구글링한 결과를 정리한다.


    • 앱 종료 : 만일 키보드가 연결되어있다면 esc 키를 누르면 앱이 종료된다. 커맨드라인 상에서는 다음과 같이
      명령어를 입력한다.
      adb shell am force-stop <package-name>
      		
    • 앱 실행 : 커맨드라인에 다음과 같이 입력한다(TesorFlow 예제 기준).
      adb shell am start -n com.example.androidthings.imageclassifier/.ImageClassifierActivity
      		
    • 앱 삭제 : 커맨드라인에 다음과 같이 입력한다.
      adb uninstall <package name>
      		


    일단 이정도만으로도 기본적인 사용은 가능하다.


    정리


    아직 소스는 분석해보지 않았고 그냥 라즈베리파이에 앱을 설치해서 기동해본 것 뿐이다. 개발자가 직접
    학습을 진행할 수 있는지 등의 여부는 아직 확인을 못했다. 일단 이 첫 결과만 놓고 보면 아직은 갈 길이
    멀다는 생각이 든다. 시간 나는대로 소스도 좀 들여다 봐야 할 것 같다. 그래도 심심풀이 땅콩으로는 꽤
    괜찮았다.





    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요


    Logistic 회귀의 cost function


    지금까지 해온 것 처럼 로지스틱회귀의 비용함수 역시 어설프나마 문돌이식 수학으로 이해해 보려고 했다. 그래서 
    일주일이 넘는 시간을 투자했으나 이번 시도는 앞선 시도들 보다 더 이해하기가 어려운 내용이었다. 때문에 이번
    포스팅에서는 로지스틱회귀의 비용함수 이해에 필요한 몇가지 개념에 대한 설명과 지난 일주일간의 검색 과정에서
    발견한 몇가지 팁을 설명하는 것 정도로 마무리 해야 할 것 같다.


    몇 가지 개념


    이미 많은 사람들이 알고 있다시피 기존 선형회귀에서 사용하던 비용함수를 로지스틱회귀에서 그대로 사용할 경우
    그래프의 형태가 아래와 같이 표현된다고 한다(함수를 통해 아래 형태의 그래프를 그려보려고 무진 애를 썼으나
    결국 실패했다).


    선형회귀분석에서 이미 언급했지만 비용 함수라는 것은 예측값과 실제 값의 오차에 대한 제곱 평균을 구하는 것이고
    이를 표현하는 함수에 대한 미분을 통해 기울기가 최소가 되는 지점을 찾는 과정이라고 하였다. 그리고 선형회귀분석의
    경우 이 비용함수의 그래프가 U자 형으로 하나의 최소 기울기만이 존재하는 형태였다. 그러나 로지스틱회귀의 가설 
    함수를 선형회귀의 비용함수에 바로 대입하는 경우에는 위의 그래프와 같이 기울기가 0이되는 지점이 다수 발생한다.
    하지만 위의 그래프에서 실제로 최솟값을 갖는 위치는 빨간 점으로 표시된 위치이고 이 지점을 global minimum
    이라 말하며 파란색 선으로 표시된 위치들은 비록 기울기는 0이지만 진정한 의미에서의 최솟값은 아니며 이 지점을
    local minimum으로 부른다. 문제는 컴퓨터는 계산을 하면서 ‘기울기가 0인 지점’만을 찾게 되기에 local minimum
    에서 계산을 멈추는 문제가 발생하게 되는 것이다.


    이렇게 하나의 최솟값만을 구할 수 있는 형태의 함수를 convex 함수라고 하고 다수의 local minimum이 존재하는
    함수를 non-convex 함수라 한다.


    이와같은 이유로 해서 로지스틱회귀에서 사용할 비용함수는 convex 함수가 될 수 있도록 다시 만들어져야 한다.


    그래서…로지스틱회귀의 비용함수는?


    이미 많은 자료들에서 그 내용을 설명하고 있으니 굳이 내가 재차 설명할 필요는 없을 것 같다. 가장 많이 언급되는
    홍콩과기대 김성훈 교수님의 슬라이드를 링크하는 것으로 대신한다.


    http://hunkim.github.io/ml/lec5.pdf


    다만 이 자료에서 주의할 점이 New cost function for logistic섹션의 함수 표기 중 H(𝑥)는 H(X)로 표기되어야
    하는 것이 아닌가 싶은데 바로 다음 장에서 H(𝑥)가 로지스틱회귀 가설함수임을 표시하고 있으므로 그렇게 이해하면
    될 것 같다.


    결국 실제의 값이 1인 경우와 0인 경우 각각에 대해 로그를 취함으로써 convex 함수 형태를 만들어 비용을 최소화 할
    수 있도록 만든 것이다. 간단하게 최종 결과만 보자.


    위 식은 다음과 같이 해석된다. 우선 H(𝑥)는 로지스틱회귀의 가설함수임을 주지하자. 그리고 𝑦는 실제의 값이다.
    이 실제의 값이 1일 경우 비용함수는 다음과 같이 변한다.



    그리고 이 식의 그래프는 다음과 같다.



    이 그래프의 x축은 가설함수의 결과 값이고 y축은 비용함수의 결과값이다. 그래프를 보면 직관적으로 알 수 있듯이 
    가설 함수의 결과 값이 실제 𝑦의 값인 1인 경우 비용 함수는 최솟값 0을 얻을 수 있다. 하지만 가설함수의 결과 값이
    0이 될 경우 비용함수의 결과는 ∞에 수렴하게 되어 엄청난 패널티를 받게 되는 것이다.


    반대로 실제 𝑦값이 0인 경우는 이와는 반대로 진행된다. 먼저 𝑦가 0인 경우 비용함수는 다음과 같이 변하며



    그래프는 다음과 같다.



    이 경우에는 𝑦가 1일 때와 그래프 방향이 반대가 되어 가설함수의 결과 0일 경우 0이 1일 경우 ∞가 되어 역시나 최소
    비용을 찾아낼 수 있게 되는 것이다.


    desmos 살펴보기


    사실 앞서 정리한 내용은 이미 많은 곳에서 더 잘 설명을 하고 있는내용이다보니 굳이 또다시 정리하기가 조금 민망할
    정도이다. 그래서 더더욱 non-convex 함수를 그래프로 구현해보고자 했는데 수학적 지식이 일천하다보니 실패하고
    말았다. 대신에 이 과정에서 좋은 사이트 하나를 발견하여 소개한다. 함수를 그래프로 표현해주는 웹 기반 서비스이며
    이름은 desmos이다. 김성훈 교수님의 자료 중 시그모이드 함수와 그래프를 표시한 부분이 바로 desmos 화면이다.


    링크 : https://www.desmos.com


    웹 서비스 뿐만 아니라 모바일 앱으로도 같은 서비스를 제공하고 있으며 별다른 비용은 없는 것으로 보인다.
    사용법은 비교적 단순한데 아래 동영상을 참고해서 설명을 이해하시도록…



    먼저 사이트 접속 후 상단의 ‘Start Graphing’ 버튼을 클릭하면 시작할 수 있다. 회원 가입을 하면 작업했던 그래프를
    저장하고 다음에 또 사용할 수도 있다.


    우선 시작하게 되면 좌측 영역을 클릭하여 그래프를 그리고자 하는 함수 식을 입력한다. 미지수가 1개인 함수는 바로 
    그래프가 그려지지만 미지수가 2개 이상인 경우 기본 미지수를 제외한 나머지에 대해서는 직접 값을 조작할 수 있도록
    슬라이드를 생성할 수 있도록 해준다. 처음 식인 W = wx + b에 대해 w와 b는 슬라이드를 생성하였다.


    슬라이드는 하단의 슬라이드 컨트롤을 클릭하면 값의 범위와 변화 단게를 설정할 수 있다. 최소 -10 ~ 최대 10, 0.1씩
    변화와 같은 식으로 설정한다. 그리고 슬라이드 왼편의 플레이 버튼을 클릭하면 범위 내에서 변화 단계만큼씩 자동으로
    변화하면서 그래프를 보여준다.


    그리고 이미 생성한 함수를 다시 사용할 수 있다. 두 번째 식인 c = (W-r)에서 W는 앞서 생성한 함수이다. 만일
    W 함수의 미지수 슬라이드를 변경하면 W를 사용한 c 함수의 그래프도 같이 변화하는 것이 보일 것이다.


    이상이 기본적인 사용법이다.


    사실 이 서비스를 이용하여 non-convex 그래프를 그려보려 했지만 뭔가 잘 안되었다. 아래 화면은 내가 시도한
    흔적이다.



    두 번째 이미지가 non-convex 그래프를 시도한 것인데…뭔가 잘못했나보다…ㅠ.ㅠ


    정리


    이미 이전에도 겪은 바이지만 무리하게 수학적인 이해를 추구하다보니 불필요하게 많은 시간들을 소모하는 경향이
    있다. 그렇다고 완벽한 결론을 얻는 것도 아니고. 아무래도 앞으로는 적당히 수위를 조절해 가면서 버릴 것은 버리고
    취할 것은 취해야겠다. 뭐 그래도 궁금한 것은 풀고 넘어가야 직성이 풀리니…


    그래도 회귀 분석을 진행하면서 많은 수학적 개념에 대해 이해하게 된 것은 나름 성과라면 성과이다. 특히나 로지스틱
    회귀의 경우 이후 신경망에 대한 학습에 매우 중요한 단서를 제공하므로 가능하면 꼼꼼히 알아 둘 필요도 있다고 판단
    된다. 동영상 강의도 이제 곧 신경망쪽으로 넘어갈테니 기존 내용을 조금 더 훑어보며 복습을 좀 해야겠다.








    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요


    다항 로지스틱 회귀의 이해


    처음 텐서플로우 공부를 시작하면서 단순선형회귀에서 다중선형회귀로 넘어가는 과정에서 상당히 혼란스러웠었다.
    단순한 1차 방정식의 형태에서 독립 변수가 늘어난 것만으로도 엄청나게 골머리를 싸매야 했다. 그리고 이제 이항
    로지스틱회귀에서 다항로지스틱회귀로 넘어가려고 한다. 이번에도 역시 기대를 저버리지 않고 뇌이랑이 메워질만큼
    알 듯 모를 듯한 수식들을 붙들고 씨름을 하게 만들었다.


    설명은 단순한데 그 단순한 것을 제대로 이해하기 위해서는 상당한 공을 들여야 하는 것이 이 바닥인가보다…ㅠ.ㅠ


    준비운동 - sigmoid 복습


    먼저 이전 포스팅인 이항로지스틱회귀에서 언급되었던 식 몇개를 되살려보자. 거기에는 두 가지 개념이 등장했는데
    바로 오즈 비(ODDS Ratio)와 이 오즈 비에 자연로그를 씌운 로짓(logit) 변환이었다. 


    오즈 비는 어떤 일이 발생하지 않을 확률에 대한 발생 확률의 비로 발생 확률을 p로 본다면 전체 확률 1에서 발생
    확률 p를 뺀 것이 바로 발생하지 않을 확률 1-pr가 되므로 오즈 비는 아래와 같은 식으로 표현된다.




    그리고 여기에 자연로그를 취해 아래와 같이 로짓 변환을 마무리 한다. (왜 이런 짓을 하는지는 이전 글인 ‘로지스틱
    (Logistic) 회귀 함수 살펴보기’
    를 참조하라~)




    이런 과정을 통해 다음과 같은 등식을 얻을 수 있었다.




    이 등식을 확률 p에 대해 정리하게 되면 우리가 익히 들어왔던 시그모이드 함수의 식이 나타난다.


    메인이벤트 - 다항로지스틱회귀


    이제 본격적으로 다항로지스틱회귀에 대해 살펴보도록 하자. 앞서 살펴본 로지스틱회귀는 결과가 1 또는 0이 나오는
    이항로지스틱회귀였다. 이항로지스틱회귀에 대해 조금 더 부연을 하자면 실제로 이항로지스틱회귀를 통해 나오는
    결과 값은 0 ~ 1 사이의 실수이다. 그렇기 때문에 기준점을 하나 두어 그 기준점보다 크면 1로 기준점보다 작으면
    0으로 처리하는 것이다. 이 때 이 기준점을 cutoff value라고 하며 0 ~ 1 사이의 값에 대한 기준점이브로 보통
    cutoff value는 0.5를 사용한다고 한다.


    다항로지스틱회귀의 경우 결과 항이 3개 이상인 경우이다. 현실적인 예를 들자면 최근 대선을 앞두고 쉴새없이 여론
    조사 자료들이 뉴스에 보도된다. 이 때 문-안 2자 대결에 대한 여론조사가 이항로지스틱회귀라면 5명의 후보자들에
    대해 모두 조사를 하게 되면 다항로지스틱회귀가 되는 것이다. 이 다항로지스틱회귀 역시 결과는 확률 값이며 따라서
    각각의 항에 대한 모든 결과으의 합은 1( = 100%)이 된다.


    다항로지스틱을 유도하는 식은 이항로지스틱의 경우와 동일하다. 각각의 항에 대해 이항로지스틱을 수행한 후 이 
    결과를 일반화 하여 식을 만들게 된다.


    대통령 선거를 예로 조금 더 상세하게 들어가보자. 다항로지스틱회귀를 통해 분류를 해야 하는 지지자들은 문, 안, 홍,
    유, 심의 5 부류이다(다행이 후보자들 성이 다 다르다…^^). 이 경우 각각에 대해 이항로지스틱을 수행한다 함은 바로
    다음과 같다(편의상 이하 순서대로 A,B,C,D,E로 부르도록 하자).


    1. A를 지지하는 사람과 지지하지 않는 사람의 이항
    2. B를 지지하는 사람과 지지하지 않는 사람의 이항
    3. C를 지지하는 사람과 지지하지 않는 사람의 이항
    4. D를 지지하는 사람과 지지하지 않는 사람의 이항
    5. E를 지지하는 사람과 지지하지 않는 사람의 이항


    그런데 여기서 약간의 문제가 있다. 이항일 경우에는 오즈 비가 쉽게 나왔다. 경우의 수가 p 또는 1-p로 쉽게 식을
    만들 수 있었는데 경우의 수가 많다보니 우리같은 수포자는 여지없이 멘붕에 빠지고 만다…ㅠ.ㅠ


    엄밀하게 말하자면 이런 경우에는 각각의 결과를 서로간에 비교를 해야 한다. A와 B를, A와 C를… B와 C를…C와 D…
    하지만 이렇게 하자니 결과 항이 많아질수록 복잡해지고 일이 커진다. 누누히 강조하거니와 수학은 곧 단축이 
    미덕이다…^^ 현명하신 우리의 수학자들께서는 여기서도 지혜를 발휘하셨다. 그 방법인 즉슨…기준이 되는 하나의
    항을 정하고 그 항에 대한 나머지 항들 각각의 비율을 계산하도록 한 것이다. 즉, 편의상 맨 마지막의 E를 기준으로
    했다고 가정하면,


    1. A에 대해 p(A) / p(E)
    2. B에 대해 p(B) / p(E)
    3. C에 대해 p(C) / p(E)
    4. D에 대해 p(D) / p(E)


    E에 대한 E의 확률은 의미가 없으니 생략하게 되어 총 4개, 다시 말해 전체 항의 갯수를 K개라고 하면 K-1개의 식이
    만들어진다. 그리고 이항로지스틱에서와 마찬가지로 자연로그를 취하면 다음과 같이 표현할 수 있다.


    이와 같은 식을 항의 갯수가 K일 경우로 일반화 시켜보면 다음과 같이 표현할 수 있다.




    잠깐 되돌아가서 보면 특정 항에 대한 각 항의 비만을 계산했는데(A와 E, B와 E, C와 E, D와 E) 그럼 다른 항들
    간의 비는 어떻게 확인해야 할까? 매우 간단하다. 이미 E에 대한 각 항목의 비를 구해놓았기 때문에 간단한 로그
    법칙으로 구할 수 있다. 만일 B에 대한 A의 비를 구한다면 아래 식과 같이 하면 된다.


    파이널 라운드 - sigmoid에서 softmax로


    늘상 수학적인 문제에서 길을 잃는다. 이 포스팅을 쓰기 시작한 것이 4월 14일 경이었는데 바로 전 섹션까지가 그 때
    쓴 글이다. 이렇게 써내려가면서 나는 내가 sigmoid 함수와 softmax 함수의 상관 관계를 이해하고 있다고 오해했다.
    다항로지스틱회귀를 통해 나온 식으로 자연스럽게 softmax 함수가 유도될 것으로 생각했는데 나의 착각이었다.
    가장 근접하게 접근한 것이 위키피디아의 로지스틱 회귀 항에 나오는 로그-선형 모델 항목인데, 이 로그-선형 모델
    자체를 모르니 알듯 모를듯한 혼란만 계속되었다. 그렇다고 다시 로그-선형모델로 거슬러 올라갈 수도 없고…


    결국 그렇게 매우 유사하게 생긴 두 함수 사이의 관계를 밝혀본답시고 또 일주일이 그렇게 흘러가버리고 말았다.
    그리고 찾은 답은 sigmoid에서 softmax를 유도할 수 없다면 거꾸로 softmax로부터 sigmoid를 이끌어내보자는
    것이다. 이미 sigmoid가 softmax의 특수한 형태 (결과 항이 2개인 경우의 특수한 형태)라는 것은 검색 과정에서
    알 수 있었다. 다만 그 과정을 유도할 수 없었을 뿐. 그리고 앞서 말했듯이 이제 거꾸로 한 번 거슬러 올라가보고자 한다.


    우선 주의할 것은 이전 포스팅에서도 언급한 바가 있듯이 치환에 주의하라는 것이다. 이 치환의 마법으로 동일한
    식이 다양한 형태로 등장하게 된다. 내가 검색을 하며 찾았던 softmax 함수에 대한 표현만 해도 아래 이미지와 같이
    다양했다.




    하지만 이 것들이 모두 동일한 의미의 식이라는 것… 이쯤 되면 이 건 수학보다는 문학에 가깝다…-.-
    일단 변수와 지수, 밑, 첨자까지 너무 많은 알파벳과 그리스 문자가 뒤섞여 있으면 혼란만 더할 것이니 가장 단순한
    식을 기준으로 진행해 가보자. 바로 김성훈 교수님의 유튜브 강좌에 나온 식이다.




    군더더기 없이 깔끔해서 좋다^^. 일단 이 식 자체에 대해 간단하게 설명을 해보자. 위의 식에서는 생략이 되었지만
    softmax 함수에서 각 항을 의미하는 아래 첨자 i의 범위는 우변의 분모에 있는 시그마의 밑이 j의 범위와 동일하다.
    즉 i 항에 대한 값 / 모든 결과항에 대한 값의 합이라고 간단하게 말할 수 있을 것이다. 


    다시 말해 전체에 대한 부분의 비율이므로 각 항에 대한 결과로써의 좌변의 값은 각 항에 대해 1보다 작은 값이
    출력되며 모든 항에 대한 결과값의 총 합은 1(=100%)가 된다.


    사실 이정도만 이해하면 그냥 다음 진도 뽑아도 된다. 그래도 궁금하다. 단지 결과 항이 늘어는 것 뿐이고 또 웬만한
    검색 결과에도 sigmoid 함수가 softmax 함수의 특수한 형태라고 하니 두 함수의 관계를 한 번 짚고 넘어가고 싶은
    것이다. 그래서 일주일도 넘는 시간을 투자했고…ㅠ.ㅠ 완변하진 않지만 지금부터 한 번 살펴보자.


    위에 적은 식은 i(또는 j)가 임의의 갯수인 경우의 식이다. 그런데 만일 i가 0과 1 두가지의 경우밖에 없는 경우를
    가정해보자 그렇다면 각각의 항에 대해 다음과 같이 표현할 수 있을 것이다.




    이 때 y0이 0인 경우를 생각해보면 식은 다음과 같이 바뀐다.




    첫 번째 줄의 가장 오른쪽 식을 보면 바로 sigmoid 함수가 나온다. 이렇게 뭔가 어설프지만 softmax와 sigmoid
    함수가 모종의 은밀한 관계(?)가 있다는 것을 알 수는 있었다…-.-


    정리


    겨우 풀어놓긴 했지만 여전이 뭔가 덜 풀린듯한 느낌이다. 하지만 이 것만 붙잡고 시간을 보낼 수 없으니 이정도에서
    마무리 하고자 한다. 사실 이 과정에서 중요한 것은 식이 어떻게 도출되느냐가 아니라 딥 러닝으로 가는 과정에서
    그 단초를 살짝 엿볼 수 있었다는 점이다.


    텐서플로우에서의 hello world라고 하는 MNIST 분석이라든지 이미지 분석 등이 따지고 보면 결국 확률의 문제다.
    어떤 이미지가 개인지 고양이인지를 판단한다는 것은 곧 학습 데이터를 기반으로 개일 확률과 고양이일 확률을
    구하는 것에 다름 아닌 것이다.



    갈길은 멀고 마음은 급하지만 그래도 차근차근 황소 걸음으로 가보련다. 다음 포스팅에서는 다항로지스틱회귀의
    cost 함수인 cross-entropy에 대해 알아보도록 하겠다.






    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요




    텐서플로우를 이용한 로지스틱(Logistic) 회귀

    로지스틱 회귀에 들어오면서 시그모이드 함수에 난데없이 등장한 자연상수 e에 대한 정체를 밝히느라 무려 2주를
    보냈다. 물론 그 과정이 쓸모없는 일은 아니었지만 꽤나 고민스럽고 답답한 시간들이었다. 이제 어느정도 그 정체를
    밝혔으니 본격적으로 로지스틱 회귀 실습을 진행해보도록 하자.


    샘플 데이터는 http://www.dodomira.com/2016/02/12/logistic-regression-in-r/ 블로그를 참조하였고
    자료 원본 경로는 http://www.ats.ucla.edu/stat/data/binary.csv 이다.


    자료 불러오기


    주제에서 조금 벗어나는 이야기지만 현재 나는 Docker for MAC에서 TensorFlow 이미지를 설치한 상태이고
    따라서 Docker를 통해 Jupyter 환경에서 작업을 진행 중이다. 이런 환경에서 작업의 불편을 조금이라도 줄이기
    위해 Docker 컨테이너에서 MAC OSX의 디렉토리에 접근하여 데이터 파일을 읽어들이도록 사전 작업이 필요하였다. 


    Docker for MAC은 기본적으로 File Sharing이라는 기능을 제공하는데 작업표시줄에 떠있는 아이콘을 클릭하면
    표시되는 메뉴에서 'Preferences...'를 선택하면 설정 창이 표시되고 설정 창의 두 번째 탭에 'File Sharing'이 있다.
    기본적으로 /Users,/Volumes, /private, /tmp가 등록이 되어있다. 추가로 등록도 가능하지만 기본적으로 등록된
    경로의 하위 경로는 등록하지 못한다.


    이렇게 등록한 후 docker run 명령어를 통해 컨테이너를 실행할 때 '-v [MAC 경로]:[docker 경로]' 형식으로
    옵션을 추가하면 된다.


    예)
    docker run -it -v /Volumes/Data1/Tesorflow:/data -p 8888:8888 -p 6006:6006 \
    gcr.io/tensorflow/tensorflow


    위과 같이 한 경우 Docker 컨테이너 쉘로 진입하면 cd /data로 MAC 디렉토리에 접근이 가능하다. 이번 예제에서는
    이와 같이 데이터 파일을 /data로부터 읽어 사용할 것이다.


    파일을 읽어 출력한 결과는 아래와 같다.

    import tensorflow as tf
    import numpy as np
    
    xy = np.loadtxt('/data/sample.txt', unpack=True, dtype='float32')
    
    x_data = xy[0:-1]
    y_data = xy[-1]
    
    print (x_data)
    print (y_data)
    
    출력 결과 ---------------------------------------------------------------
    
    [[ 1.          1.          1.         ...,  1.          1.          1.        ]
     [ 3.79999995  6.5999999   8.         ...,  4.5999999   7.          6.        ]
     [ 3.6099999   3.67000008  4.         ...,  2.63000011  3.6500001
       3.8900001 ]
     [ 3.          3.          1.         ...,  2.          2.          3.        ]]
    [ 0.  1.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  1.  0.  1.  0.  0.  0.
      0.  1.  0.  1.  0.  0.  1.  1.  1.  1.  1.  0.  0.  0.  0.  1.  0.  0.
      0.  0.  1.  1.  0.  1.  1.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  1.
      0.  1.  0.  0.  0.  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  1.  0.  1.  0.  0.  0.  0.  1.  0.  0.  0.  0.  1.
      0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  0.  0.
      1.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  0.
      0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  0.
      1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.
      0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  1.  0.  0.  1.
      0.  0.  0.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.
      0.  1.  0.  1.  0.  0.  1.  0.  0.  1.  0.  0.  0.  1.  0.  0.  0.  0.
      1.  0.  1.  0.  0.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      1.  1.  1.  0.  1.  1.  0.  0.  0.  0.  1.  1.  1.  0.  0.  1.  1.  0.
      1.  0.  1.  0.  0.  1.  0.  1.  1.  1.  0.  0.  0.  0.  1.  0.  1.  1.
      0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.  0.
      1.  0.  0.  0.  0.  0.  0.  1.  0.  1.  1.  1.  1.  0.  0.  0.  0.  0.
      0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.  0.  0.  0.  1.  0.  1.
      0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  1.  0.
      1.  1.  0.  0.  1.  0.  0.  0.  0.  0.  1.  1.  1.  1.  0.  0.  0.  1.
      0.  0.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  1.  1.  1.  1.  1.  0.
      0.  0.  0.  0.]

    로지스틱 회귀 시작하기

    우선 기본적인 흐름은 김성훈 교수님의 동영상 강좌를 기준으로 따라가도록 하겠다.
    위에서 이미 데이터를 준비해 놓았으므로 이후 진행을 살펴보자.


    먼저 학습 이후에 테스트를 위한 임의의 값을 넣을 수 있도록 x와 y에 대한 placeholder를 생성한다.

    X = tf.placeholder(tf.float32)
    Y = tf.placeholder(tf.float32)


    그리고 각 독립변수 (X)에 대한 기울기 변수 W를 만들자. 위 출력 결과를 보면 알 수 있듯이 x_data은 1행 3열의
    벡터이다. 즉 len(x_data)의 값은 3이 될 것이다.

    W = tf.Variable(tf.random_uniform([1, len(x_data)], -1.0, 1.0))


    다음으로 선형 회귀에서 사용했던 가설함수를 만들고...

    h = tf.matmul(W, X)


    이 식은 선형 회귀에 대한 가설 함수이므로 이 것을 이용하여 이전 포스팅에서 열심히 공부한 로지스틱 회귀에 적합한
    가설 함수를 새롭게 만든다.

    hypothesis = tf.div(1., 1. + tf.exp(-h))


    로지스틱 회귀의 가설 함수 공식은 이미 알아본 바가 있으므로 tensorflow의 API만 간단히 살펴보자.


    1. tf.div : 굳이 설명할 것도 없이 첫 번째 인자를 두 번째 인자로 나누는 함수이다.
    2. tf.exp : exp라는 함수는 tensorflow 뿐만 아니라 수학 계산을 위해 만들어진 대부분의 프로그램 및 언어에서 대체로 동일한 이름으로 사용되며 자연상수 e의 거듭제곱을 의미한다. 괄호 안의 파라미터만큼 거듭제곱을 하는 것이다. 따라서 이 식은 e^(-W * X)로 우리가 알고있는 로지스틱 회귀 함수의 분모에 보여지는 식이 된다.


    이렇게 가설 함수를 만들었으니 이번에는 cost 함수를 만들 차례이다. 로지스틱 회귀에서의 cost 함수에 대해서는
    별도로 정리한 적은 없으나 다른 사이트의 훌륭한 자료들을 참고로 식만 살펴보자(이 블로그에서는 다음에 자세히
    다뤄보도록 하겠다).

    with tf.name_scope("cost") as scope:
    cost = -tf.reduce_mean(Y*tf.log(hypothesis) + (1 - Y)*tf.log(1 - hypothesis))
    cost_summ = tf.scalar_summary("cost", cost)
    cost_summ = tf.summary.scalar("cost", cost)


    앞서 말했듯이 자세한 내용은 뒤로 미루고 간단하게 몇가지만 짚고 넘어가자.


    우리는 로지스틱 회귀가 결과로써 0 또는 1 사이의 값을 갖도록 한다고 했다. 그 기준에 따라 Y가 0일 경우와
    Y가 1일 경우 각각 다른 cost 함수가 적용되는데 이 것을 두 개의 식으로 사용하기 복잡하므로 위와 같은 식을
    만들었다. Y가 1이라면 tf.reduce_mean의 파라미터로 들어가있는 덧셈식의 오른쪽 항이 0이되고 Y가 0이라면
    반대로 왼쪽항이 사라져 둘 중 한 식만 적용이 되는 것이다.


    그밖에 제곱 평균(tf.reduce_mean)을 하는 것은 선형 회귀와 동일하다.
    그리고 아래와 같이 학습 계획을 세우는 부분 역시 이전의 선형 회귀외 동일하다.

    a = tf.Variable(0.1)
    optimizer = tf.train.GradientDescentOptimizer(a)
    train = optimizer.minimize(cost)


    그리고 이미 알고 있겠지만 tensorflow의 Variable들은 초기화를 하기 전까지는 아무것도 아니다~
    변수를 초기화 하고 Session을 생성하고 학습을 진행하는 것까지 한번에 가보자!


    학습 과정에서는 결과에 대해 프린트도 하고 TesorBoard용 로그도 찍도록 내용을 추가하였다.
    참고로 TensorBoard를 이용하기 위한 API가 변경되었다. 관련 내용은 글 말미에 간단하게 정리하겠다.

    init = tf.global_variables_initializer()
    
    with tf.Session() as sess:
    merged = tf.merge_all_summaries()
    merged = tf.summary.merge_all()
    writer = tf.train.SummaryWriter("/logs", sess.graph_def)
    writer = tf.summary.FileWriter("/logs", graph=sess.graph)
    
    sess.run(init)
    for step in range(20000):
    sess.run(train, feed_dict = {X:x_data, Y:y_data})
    if step % 200 == 0 :
    summary = sess.run(merged, feed_dict = {X:x_data, Y:y_data})
    writer.add_summary(summary, step)
    print(step, sess.run(cost, feed_dict = {X:x_data, Y:y_data}), sess.run(W))
    
    correct_prediction = tf.equal(tf.floor(hypothesis + 0.5), Y)
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
    print("Accuracy: ", accuracy.eval({X:x_data, Y:y_data}))
    
    
    (0, 3.9219272, array([[ 0.56839818,  0.54351538,  0.33454522,  0.35521972]], dtype=float32))
    (200, 0.58839369, array([[ 0.26840821,  0.14631633, -0.08747143, -0.65875155]], dtype=float32))
    (400, 0.58729297, array([[ 0.12495378,  0.15614627, -0.05624871, -0.66897833]], dtype=float32))
    (600, 0.58630282, array([[-0.01207304,  0.15915234, -0.02467063, -0.66446435]], dtype=float32))
    (800, 0.58538932, array([[-0.14362514,  0.16177623,  0.00590925, -0.65985072]], dtype=float32))
    (1000, 0.58454657, array([[-0.26996911,  0.16426219,  0.03536379, -0.65547818]], dtype=float32))
    ...
    중간 생략
    ...
    (17400, 0.57432276, array([[-3.30464649,  0.22630024,  0.74326813, -0.56382245]], dtype=float32))
    (17600, 0.57432127, array([[-3.30992198,  0.22641255,  0.74449652, -0.56368262]], dtype=float32))
    (17800, 0.57432002, array([[-3.31500411,  0.2265207 ,  0.74568129, -0.56354982]], dtype=float32))
    (18000, 0.57431865, array([[-3.31990051,  0.22662495,  0.74682134, -0.56342   ]], dtype=float32))
    (18200, 0.57431746, array([[-3.32461858,  0.22672606,  0.74791992, -0.56329668]], dtype=float32))
    (18400, 0.57431638, array([[-3.32916546,  0.22682308,  0.74897873, -0.56317711]], dtype=float32))
    (18600, 0.57431531, array([[-3.33354449,  0.22691627,  0.74999893, -0.56306177]], dtype=float32))
    (18800, 0.57431442, array([[-3.33776498,  0.22700654,  0.75098211, -0.56295192]], dtype=float32))
    (19000, 0.57431358, array([[-3.34183216,  0.22709353,  0.75192851, -0.5628444 ]], dtype=float32))
    (19200, 0.57431275, array([[-3.34575057,  0.22717719,  0.75284076, -0.56274116]], dtype=float32))
    (19400, 0.57431203, array([[-3.34952545,  0.22725755,  0.753721  , -0.56264317]], dtype=float32))
    (19600, 0.57431132, array([[-3.35316253,  0.22733469,  0.75456882, -0.5625475 ]], dtype=float32))
    (19800, 0.57431066, array([[-3.356668  ,  0.22740975,  0.75538445, -0.56245506]], dtype=float32))
    ('Accuracy: ', 0.70749998)


    유튜브 강좌에 있던 짧은 샘플로는 강좌에서와 거의 유사한 결과를 얻었다. 그런데 이렇게 별도로 가져온 데이터를
    사용하나 cost가 영 줄어들지를 않는다...ㅠ.ㅠ 게다가 정확도도 0.7 수준...
    참고로 TensorBoard로도 확인 가능하도록 했으니 확인이나 한 번 해보자. 출력한 cost 값의 챠트는 다음과 같다.


    그리고 그래프는 아래와 같다.


    아직 나의 수준에서는 뭔가 제대로 된 결과가 나오기는 힘든가보다. 조금 더 열심히 공부를 해봐야겠다...ㅠ.ㅠ

    앞서 말했듯이 전체적인 진행을 김성훈 교수님의 유튜브 강의를 기반으로 이번 실습을 진행을 했다. 그런데 tensorflow를 1.0으로
    업그레이드한 탓인지 TensorBoard 설정을 위한 API 일부가 모두 바뀌었다. 일단 코드 상에 강의에 등장하는 이전 버전의 API는
    모두 주석 처리하여 남겨두었다. 간단하게 정리하면 변화는 다음과 같다. 

    • tf.scalar_summary("cost", cost) -> tf.summary.scalar("cost", cost)
    • tf.merge_all_summaries() -> tf.summary.merge_all()
    • tf.train.SummaryWriter("/logs", sess.graph_def) -> tf.summary.FileWriter("/logs", graph=sess.graph)



    마지막으로 위 실습을 진행한 Jupyter 노트북과 샘플 데이터 파일을 첨부한다.


    sample.txt

    chapter2_logistic_regression.py.ipynb


    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요


    로지스틱 회귀 분석 이해를 위한 수학 지식


    들어가는 말


    앞서 단순 선형 회귀에서 다중 선형 회귀로 넘어갈 때도 단지 변수 하나가 더 추가되었다는 이유만으로 한참을
    헤맸다. 그런데 로지스틱 회귀는… 나를 지옥으로 몰고갔다…ㅠ.ㅠ


    이놈에 로지스틱 회귀 모형을 이해하기 위해 장장 2주간의 기간에 걸쳐 로지스틱 회귀, 자연상수 e, 자연로그,
    로그의 일반 성질 등을 공부해야 했고 대략 열 서너 편의 유튜브 강좌와 구글과 네이버를 통해 수많은 자료를 
    검색하여 대략적으로는 감을 잡았다. 하지만 여전히 답을 찾지 못한 내용들이 조금 있다.


    사실 우리가 중고등학교 때 수학을 배운 것과 같은 방법으로 그저 공식 하나 외우고 그 공식을 사용하면 그뿐이다.
    그리고 애초에 이 공부를 시작하면서 깊숙한 원리를 깨우친다기 보다는 텐서플로우라는 툴을 잘 사용할 수 있을
    정도만 공부를 하자는 목표가 있었다(단지 그 목표만을 따르자면 sigmoid라는 API만 알면 된다).


    하지만 예전에 공부를 할 때도 그랬거니와 지금 이 시점에도 왜 그리 그 ‘공식’이란 것이 어떤 의미인지가 궁금한지…
    그걸 그냥 지나치자니 화장실에서 뒷처리 않하고 나온 듯 찜찜하기가 이를데 없다. 그래서 비록 수학과는 거리가 먼
    천상 문돌이 이지만 한 번 도전해보기로 했다.


    지금부터는 문돌이 식으로 해석한 로지스틱 회귀에 대한 설명이다. 위키피디아의 로지스틱 회귀 항목을 기준으로
    문돌이의 관점에서 설명을 한 번 해보도록 하겠다.


    로지스틱 회귀 모델 개요

    우선 위키피디아에서 정의한 로지스틱 함수는 다음과 같다.


    이 함수는 다른 말로 시그모이드(sigmoid) 함수라고 하며 그 모양이 S자와 유사한 성질이 있다.
    지금부터 이 함수가 어떻게 도출되는지에 대해 알아볼 것이다.


    이전까지 알아본 선형 회귀 분석의 경우 그 모델을 위해 만들어지 함수들은 모두 다음과 같은1차 함수들이었다. 

    특히 다중 선형 회귀식은 벡터를 이용하여 다음과 같이 표현하기도 했다.



    이 1차 함수가 표현하는 것은 독립 변수들(보통은 x로 표현)이 변화해 갈 때 종속 변수( 보통 y로 표현)가 어떻게 변화해
    가는지를 알아보는 것이 목적이었다. 그리고 이 때 독립 변수 x와 종속 변수 y는 모두 그 범위가 음의 무한대(이하 -∞)에서 

    양의 무한대(이하 ∞)까지로 제약이 없었다.


    그런데…
    때에 따라서는 연속적으로 변화하는 어떤 값들에 대해 두 개의 결과만을 확인할 필요가 있었다.
    예를 들면 흡연량과 폐암 발병에 대한 관계를 보면 연속적으로 변화하는 흡연량에 대해 그 결과로 ‘폐암에 걸렸다’와
    ‘폐암에 걸리지 않았다’라는 두 개의 결과만이 필요한 경우 또는 소득 수준을 독립 변수로 했을 때 어느 정도 이상의
    소득이 있으면 ‘투표를 했고’ 그 이하인 경우에는 ‘투표를 안했다’더라라는 식으로 말이다.


    이러한 경우 수학적 처리를 위해 흔히 구하고자 하는 종속 변수의 값을 1로 놓고 그 반대의 경우를 0으로 놓아 y가
    1또는 0의 값을 갖도록 모델을 만든다. 그리고 이러한 독립 변수를 한 건 한 건 검사를 하게 되면 종속 변수 y가 0 
    또는 1의 값만을 표현하게 되지만 무수히 많은 독립 변수를 검사하게 되면 그 결과는 확률로 표현할 수 있게 된다.


    즉, 하루에 담배 10개피를 피운 사람 a를 조사했더니 폐암에 걸렸다(1). 그런데 하루에 담배 10개피를 피운 사람을
    10명 조사했더니 6명은 폐암에 걸렸으나 4명은 걸리지 않았다. 결국 10개피의 담배를 피운 사람은 60%의 확률로
    폐암에 걸렸다고 말할 수 있는 것이다.


    이처럼 조사 횟수가 많아지게 되면 종속 변수 y는 확률로 표현할 수 있게 되는 것이며 이럴 경우에도 종속 변수의
    값은 0 ~ 1의 범위(확률상 0% ~ 100%)를 넘지 않는다. 


    위키피디아의 로지스틱 회귀 항목 첫 줄에 설명되어있는 내용이 바로 이러한 의미이다.

    “독립 변수의 선형 결합을 이용하여 사건의 발생 가능성을 예측하는데 사용되는 통계 기법이다.”


    로지스틱 함수 도출

    그런데 이 과정에서 문제가 있다. 독립 변수의 선형 식은 그 결과가 -∞에서 ∞까지 제약이 없는데 우리는 0과 1사이의
    값만을 얻기를 원한다. 


    다시 한 번 담배 이야기로 돌아가보자. 하루에 담배 5개피를 피는 사람을 기준으로 1의 값을 얻기 위해 기울기(W)는 
    편의상 1로 놓고 b는 -4로 초기 설정을 해보자. 그러면 y = 1 X 5 -4로 y = 1의 결과를 얻을 수 있다. 하지만 담배
    개비 수가 10개비로 늘어나면 y = 1 X 10 - 4가되어 y = 6으로 1을 훌쩍 넘어버린다. 기존의 선형 식으로는 이러한
    조건을 절대 만족할 수 없는 것이다.


    일단 선형 식이 0 ~ 1 사이의 값을 가질 수 없음이 분명해졌으니 그렇다면 종속 변수쪽을 -∞에서 ∞의 범위로 확장을
    할 수는 없을까? 바로 이러한 발상에서 로지스틱 함수가 시작된다.


    앞서 설명한 내용 중 종속 변수 y는 x에 대한 조사의 수가 증가하게 되면 확률값을 갖게 된다고 하였다. 사례를 1건만
    조사하는 경우는 없으니 결국 y는 확률의 값을 갖게 될 것이다. 그렇다면 우리가 어떤 확률 p에 대해 -∞에서 ∞값을 
    출력하는 새로운 함수를 만들면 이 함수는 곧 기존의 선형 함수와 같다고 할 수 있어 새로운 등식을 만들 수 있을 
    것이다. 이 때 굳이 p에 대한 함수를 만들어야 하는 것은 우리가 최종적으로 만들어진 함수를 통해 확률 값(p)를 
    얻어야 하기 때문이다.


    이렇게 하기 위해 2가지 과정이 필요한데 그 것이 바로 odds 비라는 것과 log이다.


    odds비는 쉽게 말해 실패 확률에 대한 성공 확률의 비율이다. 성공 확률을 p라고 한다면 실패 확률은 1-p가 된다.
    이렇게 보았을 때 odds 비는 아래 식과 같이 표현할 수 있다.


    p는 0에서 1사이의 값을 가지므로 위 식을 계산해보면 p가 가장 작은 0일 경우 0 / 1 - 0이 되어 0의 값을 갖게 되고
    p가 가장 큰 1이 되는 경우 1 / 1-1 = 1 / 0이 되어 무한대가 된다(프로그래밍에서는 분모가 0인 경우 에러가 발생하나
    수학에서는 극한의 개념을 도입하면 1/0(에 수렴)으로 생각할 수 있어 무한대로 정의한다).


    이렇게 일단 odds 비를 적용하면 p에 대하여 0부터 양의 무한대까지를 범위로 갖는 새로운 함수를 생각할 수 있다.
    하지만 최솟값이 0이므로 아직 음의 무한대를 만족하지 못한다. 이 음의 무한대를 범위에 포함시키기 위해 이번에는
    log를 취하게 되는데 log 중에서도 자연상수 e를 밑으로 하는 자연로그를 취하게 된다(밑이 e인 로그를 특별히 ln
    이라는 기호를 사용하여 표시한다).


    오래 전에 배웠던 로그의 계산을 되새겨보자. 위 로그 식에서 밑은 e이고 진수는 앞서 보았던 odds비의 계산 식이다.
    그리고 odds비의 계산 식은 0과 ∞의 범위를 갖는다. 이 점을 생각한다면 odds 비에 자연로그를 취한 값의 범위는 
    다음과 같다고 할 수 있다.

    계산을 해보면 일단 자연상수 e라는 값은 나중에 설명을 하겠지만 대략 2.71828…인 무리수이다. 이 무리수 e를
    몇제곱 했을 때 0이 될까? 상식적으로 분수의 분모가 무한히 커질 때 그 분수는 0에 가까워진다는 것은 잘 알것이다.
    즉 e를 x제곱했을 때 0이 나오는 x는 -∞가 되는 것이다(e^-∞란 1 / e^ ∞과 동일하므로). 한편 e를 x 제곱했을 때 
    ∞가 나오는 x는 바로 ∞란 것은 보다 쉽게 알 수 있을 것이다.


    결국 odds 비를 구하고 거기에 자연로그를 취했더니 확률 p에 대해 -∞부터 ∞를 범위로 갖는 함수가 만들어졌다. 
    그러면 이제 다음과 같이 표현할 수 있다.


    하지만 우리의 최종 목적은 이 함수를 통해 확률 p를 찾는 것이므로 이 함수를 p에 대한 함수로 만들어야 한다.
    다음과 같이 진행해보자.




    정리

    어찌어찌 로지스틱 회귀 함수 도출 과정을 살펴보았다. 위 내용을 간단하게 정리를 해보면 다음과 같다.

    1. 무한의 값을 갖는 선형 회귀 식을 통해 두 개의 값(0또는 1)만을 갖는 결과를 가져와야 한다.
    2. 두 개의 결과만을 도출하는 과정이 연속되면 확률이 되며 확률 또한 범위는 0부터 1까지로 제한된다.
    3. 0부터 1까지로 값이 제한된 확률 값의 범위를 0부터 ∞로 확장하기 위해 odds 비를 취한다.
    4. 0부터 ∞까지로 확장된 결과를 다시 -∞에서 ∞로 확장하기 위해 odds 비의 식에 자연로그를 취한다(이 과정을 logit 변환이라고도 한다).
    5. 이제 양쪽의 식이 모두 -∞에서 ∞까지의 값을 갖게 되므로 등식이 성립하고 이 등식을 변형하면 우리가 원하는 로지스틱 함수가 도출된다.


    그런데 여전히 이해가 안가는 부분은 이 과정에서 등장하는 자연상수 e와 이 e를 밑으로 하는 자연로그의 역할이다.
    일단 얼추 생각하기에는 자연상수 e가 성장에 관한 연구(베르누이의 복리 식)로부터 탄생했다는 것, 그리고 e가 갖는
    미분상의 특징(이 부분은 아직 이해 불가…-.-) 때문에 자연상수를 사용한 것으로 보이는데…


    암튼 자연상수 e는 문돌이와 이과충을 구분하는 가장 핵심적인 개념이라고 하니 사실 뭐 몰라도 흠될 것은 없지만
    AI를 공부하고자 하는데 그러한 장벽을 두고 간다는 것도 내키지 않는 문제이다. 자연상수 e와 관련된 것은 앞으로도
    계속 그 의미를 이해하도록 노력할 것이다. 그래서 다음 글에서는 불쌍한 문돌이들을 위해 간단하게나마 자연상수 e를
    조금 정리해보도록 하고 오늘은 여기서 마치겠다.

    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요

    • 박성환 2017.09.13 23:49  댓글주소  수정/삭제  댓글쓰기

      열정적인 포스트 잘 봤습니다!

      같은 문돌이 개발 지망생으로서 화이팅입니다!!

    • 송지은 2017.09.21 17:20  댓글주소  수정/삭제  댓글쓰기

      이해가 쉽게 잘 설명해주셔서 감사합니다!!! 대학원을 준비하고있는 입장에서 많은 도움주셔서 감사드립니다.

      • 마즈다 2017.09.22 11:24 신고  댓글주소  수정/삭제

        도움이 되셨다니 다행입니다만 혹시나 잘못된 지식으로 혼란을 드리지나 않을까 걱정도 되네요. 대학원 준비 잘 하시고 나중에 좋은 지식과 정보 공유해주세요^^

    • 문정원 2017.12.23 02:26  댓글주소  수정/삭제  댓글쓰기

      저도 문돌이 개발지망생ㅠㅠ.
      설명 잘 읽었습니다 !!

    • 눈곰이 2018.03.23 23:08 신고  댓글주소  수정/삭제  댓글쓰기

      독립변수의 선형결합을 이용하여 사건의 발생 가능성을 예측한다는 부분에서요, "독립 변수의 선형 결합"이 어떤 의미인지 설명 부탁드립니다 ㅠㅠ

      • 마즈다 2018.03.24 13:38 신고  댓글주소  수정/삭제

        직관적으로는 쉬운 내용인데 오히려 설명이 어렵네요^^ 일단 "선형 결합"의 정의는 링크로 대신합니다. https://ko.wikipedia.org/wiki/%EC%84%A0%ED%98%95%EA%B2%B0%ED%95%A9

        결과적으로 말해 우리가 처음 머신러닝을 접하면서 다수의 독립변수 x에 대해 가중치 w를 곱해서 1차 식을 만들었죠. 바로 y = wx + b와 같은...

        이 독립변수가 늘어나면서 w1x1 + w2x2 + ... wnxn의 표기가 복잡하니 이 것을 백터의 곱으로 만들고 y = WX + b (W = w1 ~ wn, X = x1 ~ xn)와같이 표현했습니다. 이 것이 곧 선형 결합이라고 보면 될 것 같습니다.

        그저 우리가 익히 알고 있는 식의 수학적 표현일 뿐입니다^^.

        댓글로 설명하려니 표현도 잘 안되고 좀 그렇네요^^;;;

    • z1 2018.05.23 15:39  댓글주소  수정/삭제  댓글쓰기

      설명 정말 이해가 잘됐습니다! 감사해요~

    • 셔니 2018.10.13 01:32  댓글주소  수정/삭제  댓글쓰기

      우와 감사합니다ㅠㅠ 덕분에 이해가 잘 되었어요

      • 마즈다 2018.10.13 11:15 신고  댓글주소  수정/삭제

        도움이 되셨다니 다행입니다. 혹시 제 글에 실수가 있을지 모르니 다른 전문가분들의 글들도 잘 참고하셔서 확실한 지식을 쌓으시기 바랍니다~




    텐서플로우를 이용한 다중 선형 회귀

    단순 선형 회귀가 쉽기에 다중 선형 회귀도 쉬운 줄 알았다...하지만 결코 쉽지 않다...ㅠ.ㅠ 물론 독립변수만 늘어났을 뿐 대부분의 식을 그대로 사용해도 되므로 

    그냥 그렇게만 알고 넘어가면 이보다 쉬운 것도 없다. 하지만 수학적 사고방식이 모자란 문돌이에게는 변수가 하나 늘어난다는 것은 천지가 개벽하는 변화다.


    예를들어 단순 선형 회귀는 그 결과를 그래프를 통해서 시각적으로 쉽게 확인이 가능했다. 그런데 다중 선형 회귀는 도대체 시각적으로 어떻게 표현해야 할지를 모르겠다. 

    그나마 독립변수가 2개인 경우는 3차원그래프로 설명을 해놓은 곳이 많아 그러려니 했는데 3개 이상부터는 도대체 어찌 할 수 있는지 할 수는 있는 것인지...ㅠ.ㅠ


    일단은 cost 함수로 성공 여부를 가늠하면서 한 번 진행해보기로 했다.


    일단 사용한 데이터는 아래 문서의 16쪽에 있는 예제의 데이터이다.

     https://ita.kaist.ac.kr/data/board/ITAMATLAB09_02.pdf


    단순 선형 회귀에서 확장

    # '텐서플로우 첫걸음'의 예제에서 구현된 변수. 여기서는 직접 회귀 분석에 사용되지는 않고 산점도 행렬을 그리는데만 사용됨
    # num_points = 1000
    vectors_set = [[2.0, 3.0, 3.2],[1.3, 1.1, 3.0],[2.4, 3.5, 3.6],[1.5, 2.5, 2.6],[0.6, 1.9, 0.6],[2.0, 2.8, 3.5],
                   [1.0, 1.3, 2.1], [2.0, 3.3, 3.4], [1.3, 2.0, 2.8], [0.9, 1.0, 2.3]
                   ]
    
    #vectors_set2 = [[36.2, 206.2, 1.0, 32.0],[39.0, 218.6, 4.0, 39.0],[81.7, 264.6, 4.0, 41.0],[39.0, 330.5, 2.0, 33.0],
    #               [68.3, 334.7, 3.0, 37.0],[106.3, 365.6, 4.0, 31.0],[123.9, 379.3, 5.0, 35.0],[114.8, 456.4, 6.0, 29.0], 
    #               [97.0, 502.7, 3.0, 27.0], [100.2, 531.0, 7.0, 36.0]
    #               ]

    최초에 "텐서플로우 첫걸음"의 예제 노트북으로 시작을 했기에 코드역시 기본적으로는 예제와 동일한 구조다.

    y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]
    x1_data = [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0]
    x2_data = [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]
    #x3_data = [v[3] for v in vectors_set]

    참고 : 위에서 기존에 사용하던 vectors_set이라는 변수를 그대로 살려놓았는데 그 이유는 다중 회귀 분석을 시각화 하는 방법 중 산점도 행렬을 그리기 위해서다. 

    다중 회귀를 구성하는 각 변수들의 관계를 볼 수 있는 산점도 행렬을 만들 수 있도록 데이터를 구조화 해주는 pandas라는 라이브러리를 찾아서 사용해 보았는데. 

    이 pandas에서 데이터를 만들 때 조금 더 편하게 사용할 수 있다. 아래 코드를 수행했을 때 처음 나오는 그래프가 바로 산점도 행렬이다.

    import matplotlib.pyplot as plt
    import pandas as pd
    
    df = pd.DataFrame(vectors_set)
    
    pd.tools.plotting.scatter_matrix(df)
    plt.tight_layout()
    plt.show()
    
    plt.plot(x1_data, y_data, 'ro')
    plt.show()
    plt.plot(x2_data, y_data, 'b+')
    plt.show()
    #plt.plot(x3_data, y_data, 'cs')
    #plt.show()


    그리고 다음과 같이 가설 함수를 만든다. 다중 회귀 분석의 가설함수는 단순 회귀 분석의 가설함수에서 추가되는 독립 변수에 대해 W * X를 추가로 더해주면 된다. 

    즉 기존 단순 선형 회귀의 가설 함수가 y = W * x + b였다면 독립변수가 2개인 다중 회귀의 가설함수는 y = W1 * x1 + W2 * x2 + b의 형식이 되는 것이다. 이런 식으로 

    독립변수가 늘어남에 따라 가설함수는 다음과 같이 표현할 수 있다.


    위 예제 데이터는 독립변수가 2개이기 때문에 아래와 같이 가설 함수를 만들 수 있다.

    import tensorflow as tf
    
    W1 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
    W2 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
    #W3 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
    #b = tf.Variable(tf.random_uniform([1], 88.0, 89.0))
    b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
    y = W1 * x1_data + W2 * x2_data + b

    가설 함수 단순화 하기

    위와 같은 진행은 다중 선형 회귀 가설 함수의 일반 공식에서 보는 바와 같이 독립변수가 늘어나는 만큼 공식이 한도 끝도 없이 늘어날 수가 있다. 그래서 이 공식을 조금 더 

    간단하게 표현하기 위해 행렬의 곱을 이용하여 표현할 수 있다. 이 행렬 곱의 성질을 이용하면 b까지도 간단하게 정리할 수 있다. 아래 공식과 같이 표현이 가능한 것이다.

    독립변수가 2개인 경우


    독립변수가 3개인 경우


    b까지 포함시킨 경우


    위 방법을 이용하여 가설함수를 다시 구성하면 다음과 같이 간단하게 정리할 수 있게된다. 먼저 두 개의 독립변수를 하나의 벡터로 만들어보자

    x_data = [[3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
              [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
    y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]

    x1_data, x2_data 두개의 변수가 x_data 하나의 변수에 모두 포함되었다. y_data는 그냥 사용하면 된다. 다음은 W1과 W2도 하나로 합쳐보자

    W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))
    b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))

    바뀐 부분은 tf.random_uniform의 첫번째 파라미터가  [1]에서 [1, 2]로 바뀐 것이다. 즉, 하나의 행만 가지고 있던 배열 형태에서 1행 2열 형태의 벡터로 바꾸었다. 

    아직까지는 b는 그대로 사용을 하자.


    이제 행렬 곱셈을 해보자. 텐서플로우에서 행렬 곱셈을 해주는 함수는 matmul이다.

    y = tf.matmul(W, x_data) + b

    y = W1 * x1_data + W2 * x2_data + b라는 긴 공식이 y = tf.matmul(W, x_data) + b로 짧아졌다. 새삼 수학적 사고의 대단함을 느끼는 순간이었다.

    b까지 단순화 시키려면 다음과 같이 하면 된다.

    import tensorflow as tf
    
    x_data = [[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
              [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
              [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
    y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]
    
    W = tf.Variable(tf.random_uniform([1, 3], -1.0, 1.0))
    y = tf.matmul(W, x_data)

    정말 단순해졌다.

    이후 진행은 단순회귀 분석과 동일하다. loss 함수를 정의하고 최적화 방법은 학습속도 0.01인 그래디언트 디센트를 사용한다.

    loss = tf.reduce_mean(tf.square(y - y_data))
    optimizer = tf.train.GradientDescentOptimizer(0.01)
    train = optimizer.minimize(loss)

    그리고 모든 변수를 초기화하고 세션을 시작한다.

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)

    일단 10000번 정도 반복을 하면서 1000번 째마다 현재 step과 W와 loss값을 출력하여 보았다. 행렬 곱을 이용하여 식을 단순화 하기 전에는 W1, W2, b의 값이 

    각각 별개의 배열로 출력되었으나 식을 단순화 한 후에는 W가 하나의 벡터로 전체 값을 모두 표시해준다. 

    for step in range(10000):
        sess.run(train)
        if step % 1000 == 0 :
            print(step, sess.run(W), sess.run(loss))
    (0, array([[ 0.77657259,  0.304876  ,  0.05037546]], dtype=float32), 0.10678037)
    (1000, array([[-0.0653717 ,  0.31436804,  0.32436851]], dtype=float32), 0.01154841)
    (2000, array([[-0.24788393,  0.33180454,  0.3721334 ]], dtype=float32), 0.0072818799)
    (3000, array([[-0.28713715,  0.33556959,  0.38239372]], dtype=float32), 0.0070845596)
    (4000, array([[-0.29557922,  0.3363795 ,  0.38460028]], dtype=float32), 0.0070754252)
    (5000, array([[-0.29739448,  0.33655375,  0.38507465]], dtype=float32), 0.0070749982)
    (6000, array([[-0.2977854 ,  0.33659121,  0.38517687]], dtype=float32), 0.0070749833)
    (7000, array([[-0.29786775,  0.33659837,  0.38519898]], dtype=float32), 0.007074981)
    (8000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)
    (9000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)

    일단 결과는 위와 같이 나왔는데 이 결과는 비용함수의 결과가 상당히 낮은 값임에도 불구하고 예제 데이터가 있던 문서에서 정리한 결과 값과 상당한 차이를 보인다. 

    왜 차이가 나는지 어떻게 이 차이를 줄이도록 조정할 수 있는지는 아직 잘 모르겠다.


    문서 상의 결과값 : y(x1,x2) = -0.4503 + 0.3067 * x1 + 0.4589 * x2


    정리

    이번 포스팅 내용울 3줄 정리해보도록 하자.

    1. 다중 선형 회귀는 단순 선형 회귀에서 독립변수의 수만 늘어난 형태이다.
    2. 가설함수는 y = W1 * x1 + W2 * x2 + ... Wn * xn + b의 형태이다.
    3. 비용함수는 단순 회귀 분석과 동일하다.

    적절한 값을 구한다는 목적만 생각한다면 달리 어려운 부분은 없다. 하지만 처음 단순 선형 회귀를 정리할 때 그래프를 보면서 변화를 비교했던 것이 이해를 돕는데 

    도움이 되었기에 다중 선형 회귀에서도 그런 방법이 없을까 생각했는데 독립변수 2개까지는 3차원 그래프로 표현을 하는 내용들이 간혹 보였지만 독립변수가 3개 이상인 

    경우에는 그래프로 표시 하는 내용을 전혀 찾을 수가 없었다. 일단 크게 중요한 부분은 아니기에 호기심을 누르고 그냥 넘어가도록 하자. 


    전체 글은 Jupyter Notebook에서 작성되었으며 전체 Notebook 파일도 아래 첨부한다.

    chapter2_multi_regression.py.ipynb


    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요

    • 조인태 2017.10.03 20:30  댓글주소  수정/삭제  댓글쓰기

      제 생각에는 독립변수간에 상관성이 있는 자료가 아닌가 생각됩니다. 종속변수에 대한 독립변수들이 서로 독립적이지 않은 경우인거죠. 예를 들면 성적에 대해 공부시간과 출석률로 분석을 한다면, 출석률과 공부시간이란 서로 독립적이라고 하긴 좀 힘든 경우인 것 처럼요.


    Docker에서 TensorFlow 1.0 업그레이드 하기

    최근 텐서플로우가 1.0을 발표하면서 많은 분들이 1.0 업그레이드에 여념이 없다. 이런 시류에 편승해서 나도
    Docker 이미지로 설치한 텐서플로우 0.12.1 버전을 1.0.0 버전으로 업그레이드를 해보기로 했다.


    사실 뭐가 어떻게 변했는지도 모르고 그냥 남들 하니까 따라한다…ㅠ.ㅠ
    게다가 Docker 자체를 잘 모르는 상태에서 달리 뭘 어찌해야 하는지도 잘 모르겠고. 텐서플로우 홈페이지에 가도
    그냥 기존 코드를 1.0에 맞게 변경하는 내용만 있고…Installing 항목에는 기존과 달라진 내용은 안보이고…
    가장 상투적인 것이 가장 확실하다고 했던가. 그냥 기존 버전 삭제 후 재설치 하기로 했다.


    그 과정에서도 많은 시행착오를 거쳤지만 굳이 그 모든 실수를 다 거론할 필요는 없을 것 같아 깔끔한 성공 과정만
    정리한다.



    텐서플로우 버전 확인

    우선 업그레이드를 하고 나서 제대로 업그레이드가 되었는지 확인이 필요하니 먼저 텐서플로우 버전을 확인하는
    방법을 확인해보았다. 간단하게 아래 그림과 같이 코드를 입력하면 된다. 이 화면은 jupyter Notebook 화면이다.



    오호라 0.12.1 버전이 현재 내가 사용하는 버전이구나. 업그레이드 해야지~


    Docker에 설치된 텐서플로우 이미지 확인

    콘솔 창에서 아래 이미지와 같이 명령어를 입력하면 현재 설치된 모든 이미지의 목록이 나온다. 나는 이번에 처음
    텐서플로우를 설치하면서 Docker를 사용했기에 이미지가 달랑 하나다. 만약 많은 이미지가 있다면 아래의 코드와
    같이 필터링 할 수 있다.

    docker images | grep tensorflow
    




    Docker에 설치된 이미지 삭제

    역시 아래 코드와 같이 입력하면 된다. 

    docker rmi [이미지 ID]
    

    그런데…뭔가 에러가 떴다. 정지된 컨테이너에서 이 이미지를 사용하고 있단다. 정지했으면 좀 풀어주지…-.-




    Docker에서 컨테이너 조회 및 삭제 그리고 다시 이미지 삭제

    아래와 같이 입력하면 현재 프로세스로 떠있는 모든 컨테이너의 목록을 확인한다.

    docker ps -a
    



    나는 현재 3개의 컨테이너가 프로세스로 등록되었고 모두 정지된 상태이며 사용하는 이미지는 모두 텐서플로우다.
    가차없이 모두 죽여버리자!

    docker rm $(docker ps -a -q)
    



    다시 한 번 docker ps -a 명령을 실행하면 이제 목록에는 아무 것도 보이지 않는다.
    이어서 바로 다시 한 번 이미지를 삭제해보자. 아래 그림과 같이 삭제 과정이 표시된다.





    텐서플로우 이미지 설치

    일단 1.0.0을 설치해야 하니까 뒤에 태그를 명시적으로 지정해주어야 할 것 같아서 아래와 같이 명령어를 입력했다.

    docker run -it -p 8888:8888 -p 6006:6006 gcr.io/tensorflow/tensorflow:1.0.0-devel
    

    음…그런데 이거 이미지 다 설치되고 나면 자동으로 jupyter가 실행되어야 하는데…이상하게 shell로 들어가버린다.
    뭐냐? 이러면 나 흔들린다…ㅠ.ㅠ 여기서 뭘 어째야 하는거냐…?



    일단 당황하지 않고~ 텐서플로우 홈페이지에 있었던 것처럼 다시 한 번 해본다.

    docker run -it -p 8888:8888 -p 6006:6006 gcr.io/tensorflow/tensorflow
    

    아하~! 별다른 태그를 주지 않으니까 자동으로 가장 최신의 버전이 설치되는군~
    그리고 바로 Jupyter가 실행되면서 로그인에 필요한 토큰과 함께 Jupyter URL이 표시된다.



    docker images를 통해 이미지 목록을 다시 한 번 조회해보면 2개의 이미지가 보인다. 위에 보이는 것은
    나중에 설치한 이미지이고 아래 보이는 것은 1.0.0-devel 태그를 붙여 설치한 이미지이다.




    최종 확인

    마지막으로 Jupyter Notebook을 하나 만들어 다시 한 번 버전을 확인해보자.




    이렇게 뭐로 가도 서울로 가면 된다고 어찌어찌 텐서플로우 1.0.0을 설치하였다~

    블로그 이미지

    마즈다

    이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

    댓글을 달아 주세요