2017년 3월 22일에 나온 아직도 따끈따끈한 논문입니다. GitHub링크는 아래와 같습니다.

https://github.com/luanfujun/deep-photo-styletransfer

간단히 논문에 대해 요약을 하자면 아래와 같습니다.


기존까지 있었던 image style transfer(유명한 neural-style 등)은 이미지에 스타일을 입히는 게 얼추 가능은 했지만, style faithfulness가 약하거나 photorealism이 약했습니다. photorealism이 약한 이유는 사진 내부에 있는 직선 성분과 택스쳐 성분이 왜곡되기 때문이고, style faithfulness가 약한 이유는 예를 들어서 sky style은 sky에 매칭이 되야 하고, building style은 building에 매칭되어야 하는데 이게 잘 안돼서 연관없는 것이 매칭되었기 때문입니다.  따라서 Deep Photo Style Transfer는 이것들을 해결하고자 뭐 여러가지 적용했습니다. 그래서 이전의 알고리즘과는 달리 위 두 가지를 충족시키는 좋은 알고리즘이 되었다 뭐 이정도입니다. 그래서 실제로 이걸 돌려보면 사진의 전체적인 분위기(시각, 계절, 날씨, 택스쳐 재배치 등)이 제대로 됩니다.


아래가 그 예시입니다. (원본을 GitHub에서 링크 따온 것입니다.) 왼쪽부터 순서대로 원본, 스타일, 결과물입니다.



코드가 GtiHub에 있기 때문에 돌려볼려고 clone도 받고 Octave도 설치해서 라플라시안 돌리고 그러려고 했는데... GPU 램 부족하다고 안 되네요 쩝... 나중에 좋은 장비 구축하면 그때가서 돌려보겠습니다.


p.s. 논문은 대충 읽어봤는데 처음해보는 리뷰라 그런지 잘 안 되네요 쿨럭...  나중에 시간이 되면 더 꼼꼼히 읽어서 리뷰해보겠습니다

  1. 스터디중 2017.10.24 12:52

    안녕하세요. 리뷰 잘 읽었습니다.
    한가지 질문을 드려도 될까요?
    segmentation mask에 맞게 transfer 되는건 알겠는데, 그럼 다른 mask 는 transfer에 전혀 관여를 안하는건가요?

    • makeapp 2017.10.28 00:12 신고

      간단하게 논문 조금 훑어보고 예제를 살펴본 정도라서, 저도 정확하게 답변드릴순 없을 거 같습니다. 직접 논문 읽어보시고, 프로그램 실행해보시는게 좋을 거 같습니다.

오랜만에 글을 써봅니다. 원래 CNN으로 CIFAR-10 작업을 하고 있는데, Batch Normalization과 기타 다른 기법도 정리해야 하기도 하고 좀 바쁜 일도 있어서 아직 딥러닝 파트는 글을 다 못 썻네요. 아무튼 이번 글에서는 딥러닝을 활용하여 꾸린 동영상 화질을 개선해볼 겁니다.


이 괴랄한 짓을 하게 된 계기는 다음과 같습니다. 제가 아이유 팬인지라 이번에 아이유 콘서트 포토북을 샀는데, 포토북에 같이 담겨 있는 DVD의 화질이 480p로 괴랄한 관계로 이것을 어떻게든 개선해서 영상을 보겠다는 덕질로부터 시작하게 되었습니다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ


아 참고로, 이 개선을 하겠다고 좀 머리 굴려보고 이렇게 컴퓨터 굴려봤는데도 개선의 큰 효과는 거두지 못했습니다. 원체 해상도가 낮았던 영상인지라...제 방법을 그대로 적용하실 분은 가능하면 720p 이상에 작은 세부사항이 없는 영상(눈이 2px정도이면 이건 확대해서 다듬어도 개선이 안 됩니다. 사람이 직접 건드리거나, 모자이크를 살리는 딥러닝을 돌려야 합니다.)에서 적용하시는 걸 권해드립니다.


일단 먼저 예시로 한 프레임을 보도록 합시다. 원본 영상은 720x480입니다.


무려 2017년에...4K도 거의 대중화가 된 이 세상에서 480p 영상이라니....

해상도가 워낙 낮아서 폰 화면에서 보는 거 아니라면 좀 보기 껄그러운 수준의 해상도입니다.


자, 이번엔 Image Super Resolution과 OpenCV의 filter2D 기능을 사용하여 좀 더 깔끔하게 하기 위해 sharpen까지 준 프레임입니다.

(Tistory의 업로드 최대 폭이 900px인지라 조금 이미지가 작게 올라갑니다. 원본 사이즈는 1440x960입니다.)


솔직히 이것도 그닥 보기 편한 품질은 아니지만, 그나마 원본 영상의 프레임보다는 많이 개선된 편입니다.



자, 그러면 이제 어떻게 동영상의 품질을 개선했는지 간단하게 설명해보겠습니다.


기본적으로 제 컴퓨터 환경은 다음과 같습니다. 이 환경에서 7,000프레임 정도 되는 영상을 업스케일링 & sharpen을 줬을 때 1시간 30~40분 정도 걸리니, 제 컴퓨터의 GPU(연산은 CPU가 아니라 GPU로 돌렸습니다. 이게 더 빠릅니다.)보다 안 좋으신 분은 좀 더 시간이 걸릴 것을 알고 자는 시간에 돌리는게 정신건강에 매우 이롭습니다.


CPU: i7-5820K

GPU: GTX 970

RAM: DDR4 16GB

SSD: Toshiba Q Series 256GB (Pro 버전인지 아닌지는 기억이 안 나네요. 그냥 기본적인 SSD 성능을 낸다고 보시면 됩니다.)

OS: Ubuntu Gnome 16.04.1 LTS


1. 기본적인 프로그램을 설치한다.

아래는 필수적으로 필요한 프로그램입니다. 구글에 검색하면 다 나오니 알아서 까시면 됩니다.

- CUDA, cuDNN

- Torch

- FFMPEG

- Python Libraries: numpy, Theano, OpenCV, etc(프로그램 돌릴 때 없다고 뜨는 거 그것들 설치해주시면 됩니다. 자세히 기억은 안 나네요)


2. waifu2x를 git에서 clone해온다. 그리고 waifu2x에 필요한 필수적인 프로그램들을 설치한다.(README 참조)

간단하게 waifu2x를 설명해드리자면, 원래는 애니메이션이나 일러스트 등을 업스케일링하는 딥러닝입니다. 근데 어차피 딥러닝 모델은 학습만 하면 되니까, 일반적인 사진을 업스케일링할 수 있는 photo model도 있더라고요.


https://github.com/nagadomi/waifu2x


waifu2x의 READM.md를 읽어보시면 아시겠지만, 거기에도 영상을 업스케일링하는 방법이 간단하게 소개되어 있습니다. 저는 거기서 조금 더 응용해서 적용했습니다.


3. 아래 Python 코드를 다운받아서 어디에 저장해둔다.

이 코드는 좀 안 좋은 영상의 경우엔 초점이 흐릿하게 보이기 때문에 이때 좀 초점을 쨍하게 만들어 주기 위해 sharpen을 주는 코드입니다. 영상 업스케일링에 필수적인 코드는 아니니 필요하신 분만 사용하시면 됩니다.


Sharpen.py


* 함수를 보면 sharpen_filter와 sharpen_deblur 두 가지 종류가 있는데, 각각 적용해보시고 맘에 드시는 거 하나 골라서 함수명만 바꿔주시면 됩니다. 저 같은 경우에는 좀 어두운 환경(콘서트장)에서는 sharpen_filter가 더 적합했고, 약간 몽상적이면서 밝은 분위기 영상에는 sharpen_deblur가 채도도 살짝 높여주면서 영상을 괜찮게 만들어 줬습니다.


4. 영상을 프레임 단위로 쪼개어 png 파일로 저장한다.

명령어는 다음과 같습니다. 어디에 폴더 하나 만드시고 거기에 저장하시면 됩니다. 참고로 이미지 파일들이 동영상 파일의 10배 가까이 용량을 차지하기 때문에 용량이 넉넉한 곳을 마련해두시는게 좋습니다.

ffmpeg -i 동영상파일 -f image2 %08d.png


5. 영상에서 오디오 파일을 추출한다.

나중에 개선된 프레임을 다시 합칠 때에는 오디오가 없는 상태이기 때문에, 이때 오디오를 넣어주기 위해서 원본 영상에서 오디오를 추출해냅니다.

ffmpeg -i 동영상파일 -acodec copy 오디오파일


6. 프레임 목록을 만듭니다.

아래 명령어만 입력하시면 됩니다.

find "$(cd 이미지폴더; pwd)" -name "*.png" | sort > frames.txt


7. (선택) 프레임에 sharpen을 줍니다.

선택 사항이며, 화질구지인 480p에서는 어느정도 효과를 얻었습니다.

python Sharpen.py경로 frames.txt경로


8. 프레임을 업스케일링합니다.

제일 중요한 부분입니다. 먼저 새 프레임 폴더를 만들고, torch를 이용해 waifu2x를 돌립니다. 노이즈 레벨은 3정도가 적당했습니다.


mkdir new_frames

th waifu2x폴더/waifu2x.lua -force_cudnn 1 -model_dir models/photo -m noise_scale -noise_level 3 -resume 1 -l frames.txt 경로 -o new_frames/%08d.png


* waifu2x 옵션은 이외에도 여러가지가 있고, GPU 대신에 CPU에서 돌릴 수 있는 옵션 등이 있으므로 명령어 옵션을 체크하고 커스텀해주시면 됩니다.


여기서 95% 이상의 시간이 소요됩니다.


9. 프레임과 오디오 파일을 합쳐서 동영상을 만듭니다.

ffmpeg -f image2 -framerate fps수 -i new_frames/%08d.png -i 오디오파일 -r 오디오파일fps수 -vcodec mpeg4 -acodec copy -crf 16 video.mp4


이외에 많은 ffmpeg 옵션이 있으므로 구글링 해보시는 것을 권해드립니다. 코덱도 mpeg4 외에 좀 더 용량이 작은 libx264를 쓸 수는 있으나, 윈도 환경에서 VLC를 제외하고 곰플레이어, 윈도 기본 내장 플레이어 등에서 정상적으로 동영상이 재생 안 되는 것으로 확인되어 가능하면 용량이 좀 더 크더라도(대략 1.5배?) mpeg4를 쓰는 것이 정신건강에 더 좋을 거 같습니다. 아니면 libx264 호환 윈도 코덱을 까시면 됩니다.


이렇게 하면 그나마 20% 정도 개선된 동영상을 얻으실 수 있으며 원본 영상의 해상도가 더 높을 수록, 세부 요소가 더 적은 영상일 수록 더 큰 개선 효과를 얻으실 수 있을 거 같습니다. 따로 동영상 프레임 추출부터 재합성까지 자동화하는 Bash용 스크립트도 하나 만들어뒀는데, 이것은 나중에 GitHub에 정리해서 업로드하겠습니다.


+ 추가: 정리해서 업로드했습니다. https://github.com/AMakeApp/DVD2FHD

오늘의 주제는 Neural Network 최적화입니다. Sigmoid보다 좀 더 효율적이라고 알려져 있는 ReLU, 초기 Weight를 좀 더 효율적으로 초기화시키는 Xavier Initializer, Dropout을 다뤄볼 것입니다.


먼저 Neural Network가 최적화되었음을 확인할 수 있게 간단히 Train & Test가 가능한 MNIST(Mixed National Institute of Standards and Technology database) 데이터셋을 사용합니다. MNIST는 숫자 손글씨를 28x28의 흑백 이미지와 그 숫자 손글씨가 어떤 숫자를 나타내는지 label이 붙어있는 데이터셋입니다. TensorFlow에서 제공하는 MNIST는 55,000개의 train set, 5,000개의 validation set, 10,000개의 test set으로 구성되어 있습니다. train set 이미지를 모아서 보면 아래 이미지와 같습니다.


네 뭐.... 엄청 많습니다. ㅋㅋㅋ


이제 본론으로 돌아와서, 이 MNIST에서 주어지는 train set을 학습시켜서 Neural Network가 test set의 이미지 숫자를 정상적으로 인식해내게 하면 됩니다. 먼저 가장 먼저 했던 Softmax로 위를 분류해보겠습니다. 모델의 종류만 다르고 실제로 학습 & 테스트 시에 dropout_rate 유무를 제외하고는 모든 코드가 같기 때문에 간결한 설명을 위해서 각 모델에 대한 설명만 하겠습니다. 코드는 첨부된 Jupyter Notebook 파일 또는 Python 파일을 참조해주시기 바랍니다.


기본적으로 image는 28x28(=768)의 데이터(1과 0으로 음영 구분), label은 10개의 배열(image에 맞는 숫자 index에 표기)이므로 아래와 같이 input placeholder를 짭니다.

X = tf.placeholder(tf.float32, [None, 28*28], name="x")
Y = tf.placeholder(tf.float32, [None, 10], name="y")

Xavier Initializer를 제외한 모든 Weight 초기화는 tf.random_normal로 했고, 최적화 모델은 기존에 쓰던 GradientDescentOptimizer보다 더 좋은 AdamOptimizer를 사용하였습니다. learning_rate는 0.001입니다.


1. Softmax

DNN Optimization(Softmax).ipynb

DNN Optimization(Softmax).py

간단히 28*28 행렬에서 곱을 하여 10 행렬을 만들어야 하기 때문에 아래와 같이 모델을 만듭니다.

w = tf.Variable(tf.zeros([28*28, 10]), name="Weight")
b = tf.Variable(tf.zeros([10]), name="Bias")

H = tf.nn.softmax(tf.matmul(X, w) + b)

이후 이 모델을 학습시키고 test set을 돌리면 다음과 같은 결과가 나옵니다. Epoch는 55,000개의 train set의 반복한 횟수입니다. (최적화 기술에 대한 비교를 위해 모든 기타 옵션을 똑같이 고정했기 때문에, Softmax의 경우 random_normal 대신 0으로, 최적화 모델도 rate랑 모델도 바꿔보면 92% 정도까지 나옵니다.)

Epoch: 1
Test Accuracy: 0.4371
Epoch: 2
Test Accuracy: 0.5924
Epoch: 3
Test Accuracy: 0.7421
Epoch: 4
Test Accuracy: 0.7831
Epoch: 5
Test Accuracy: 0.8081
Epoch: 6
Test Accuracy: 0.8268
Epoch: 7
Test Accuracy: 0.8429
Epoch: 8
Test Accuracy: 0.858
Epoch: 9
Test Accuracy: 0.8761
Epoch: 10
Test Accuracy: 0.8849
Epoch: 11
Test Accuracy: 0.892
Epoch: 12
Test Accuracy: 0.8962
Epoch: 13
Test Accuracy: 0.9006
Epoch: 14
Test Accuracy: 0.9036
Epoch: 15
Test Accuracy: 0.905


2. 4-Layer DNN, ReLU

DNN Optimization(4-Layer DNN, ReLU).ipynb

DNN Optimization(4-Layer DNN, ReLU).py DNN Optimization(4-Layer DNN, ReLU).ipynb

이번엔 지난 번에 배운 NN을 적용하고, activation function으로 sigmoid 대신 더 효율적이라고 알려져 있는 ReLU를 사용합니다. ReLU는 sigmoid와는 다르게 치역의 범위가 [0, ∞] 이며 간단하게 max(0, x)으로 나타낼 수 있습니다. 어떻게 보면 sigmoid의 1보다 좀 더 가중치를 두게 하여 더 효율적인 학습을 하게 했다고 볼 수 있을 거 같습니다. 모델은 다음과 같습니다.

def nn_layer(input_data, output_size):
    W = tf.Variable(tf.random_normal([input_data.get_shape().as_list()[1], output_size]))
    B = tf.Variable(tf.random_normal([output_size]))
    return tf.matmul(input_data, W) + B

with tf.name_scope("Layer2"):
    L2 = tf.nn.relu(nn_layer(X, 14*14))
    
with tf.name_scope("Layer3"):
    L3 = tf.nn.relu(nn_layer(L2, 14*14))
    
with tf.name_scope("Layer4"):
    H = nn_layer(L3, 10)


실행 결과는 다음과 같습니다. 위의 Softmax보다 좀 더 개선된 모습을 볼 수 있습니다.

Epoch: 1
Test Accuracy: 0.8417
Epoch: 2
Test Accuracy: 0.8828
Epoch: 3
Test Accuracy: 0.8995
Epoch: 4
Test Accuracy: 0.9103
Epoch: 5
Test Accuracy: 0.9194
Epoch: 6
Test Accuracy: 0.9253
Epoch: 7
Test Accuracy: 0.9283
Epoch: 8
Test Accuracy: 0.9276
Epoch: 9
Test Accuracy: 0.935
Epoch: 10
Test Accuracy: 0.9351
Epoch: 11
Test Accuracy: 0.9375
Epoch: 12
Test Accuracy: 0.9401
Epoch: 13
Test Accuracy: 0.9404
Epoch: 14
Test Accuracy: 0.9389
Epoch: 15
Test Accuracy: 0.9427


3. 4-Layer DNN, ReLU, Xavier Initializer

DNN Optimization(4-Layer DNN, ReLU, Xavier Init).ipynb

DNN Optimization(4-Layer DNN, ReLU, Xavier Init).py

과거의 Deep Learning에서는 Weight의 초기값을 단순히 random으로 초기화하거나 학습이 진행만 된다면 0으로 초기화하기도 하였습니다. 그러나 이 초기값 초기화의 중요성이 대두되면서 Xavier Initializer와 같은 새로운 초기화 알고리즘이 등장하게 되었습니다. 꽤 효과적인 Xavier Initializer는 다음과 같은 알고리즘으로 초기화를 합니다.

def xavier_init(n_inputs, n_outputs, uniform=True):
  """Set the parameter initialization using the method described.
  This method is designed to keep the scale of the gradients roughly the same
  in all layers.
  Xavier Glorot and Yoshua Bengio (2010):
           Understanding the difficulty of training deep feedforward neural
           networks. International conference on artificial intelligence and
           statistics.
  Args:
    n_inputs: The number of input nodes into each output.
    n_outputs: The number of output nodes for each input.
    uniform: If true use a uniform distribution, otherwise use a normal.
  Returns:
    An initializer.
  """
  if uniform:
    # 6 was used in the paper.
    init_range = math.sqrt(6.0 / (n_inputs + n_outputs))
    return tf.random_uniform_initializer(-init_range, init_range)
  else:
    # 3 gives us approximately the same limits as above since this repicks
    # values greater than 2 standard deviations from the mean.
    stddev = math.sqrt(3.0 / (n_inputs + n_outputs))
    return tf.truncated_normal_initializer(stddev=stddev)

실제로는 TensorFlow에 구현되어 있기 때문에 아래와 같이 간단하게 사용하여 모델을 작성할 수 있습니다. (본 강의에서는 w만 Xavier Initializer 쓴 거 같던데, 전 그냥 bias까지 썻습니다. 어차피 큰 차이는 없는 거 같기에...)

def nn_layer(name, input_data, output_size):
    W = tf.get_variable(name=name + "_W",
                        shape=[input_data.get_shape().as_list()[1], output_size],
                        initializer=tf.contrib.layers.xavier_initializer())
    B = tf.get_variable(name=name + "_B",
                        shape=[output_size],
                        initializer=tf.contrib.layers.xavier_initializer())
    return tf.matmul(input_data, W) + B

with tf.name_scope("Layer2"):
    L2 = tf.nn.relu(nn_layer("L2", X, 14*14))
    
with tf.name_scope("Layer3"):
    L3 = tf.nn.relu(nn_layer("L3", L2, 14*14))
    
with tf.name_scope("Layer4"):
    H = nn_layer("L4", L3, 10)

실행 결과는 다음과 같습니다. 이전과는 달리, 엄청나게 개선된 모습을 볼 수 있습니다.

Epoch: 1
Test Accuracy: 0.9509
Epoch: 2
Test Accuracy: 0.972
Epoch: 3
Test Accuracy: 0.9754
Epoch: 4
Test Accuracy: 0.9773
Epoch: 5
Test Accuracy: 0.9724
Epoch: 6
Test Accuracy: 0.9776
Epoch: 7
Test Accuracy: 0.9772
Epoch: 8
Test Accuracy: 0.977
Epoch: 9
Test Accuracy: 0.9791
Epoch: 10
Test Accuracy: 0.9791
Epoch: 11
Test Accuracy: 0.9773
Epoch: 12
Test Accuracy: 0.9807
Epoch: 13
Test Accuracy: 0.9811
Epoch: 14
Test Accuracy: 0.9757
Epoch: 15
Test Accuracy: 0.9787


4. 4-Layer DNN, ReLU, Xavier Initializer, Dropout

DNN Optimization(4-Layer DNN, ReLU, Xavier Init, Dropout).ipynb

DNN Optimization(4-Layer DNN, ReLU, Xavier Init, Dropout).py

이번에는 Dropout이라는 기술을 써볼 겁니다. 아주 간단합니다. Neural Network에서 일정 비율의 node들을 비활성화 시킨 채로 학습을 진행하고, 이후에 test 할 때 모두 활성화시키는 기술입니다. 실제로 효과가 꽤 있다고는 하는 거 같다만, 여기서는 그렇게 눈에 띄게 효과가 나타나진 않습니다. 모델과 결과는 다음과 같습니다.

def nn_layer(name, input_data, output_size):
    W = tf.get_variable(name=name + "_W",
                        shape=[input_data.get_shape().as_list()[1], output_size],
                        initializer=tf.contrib.layers.xavier_initializer())
    B = tf.get_variable(name=name + "_B",
                        shape=[output_size],
                        initializer=tf.contrib.layers.xavier_initializer())
    return tf.matmul(input_data, W) + B

dropout_rate = tf.placeholder(tf.float32)

with tf.name_scope("Layer2"):
    _L2 = tf.nn.relu(nn_layer("L2", X, 14*14))
    L2 = tf.nn.dropout(_L2, dropout_rate)
    
with tf.name_scope("Layer3"):
    _L3 = tf.nn.relu(nn_layer("L3", L2, 14*14))
    L3 = tf.nn.dropout(_L3, dropout_rate)
    
with tf.name_scope("Layer4"):
    _H = nn_layer("L4", L3, 10)
    H = tf.nn.dropout(_H, dropout_rate)
Epoch: 1
Test Accuracy: 0.9436
Epoch: 2
Test Accuracy: 0.9665
Epoch: 3
Test Accuracy: 0.9705
Epoch: 4
Test Accuracy: 0.9711
Epoch: 5
Test Accuracy: 0.9767
Epoch:  DNN Optimization(6-Layer DNN, ReLU, Xavier Init, Dropout, Ensemb6
Test Accuracy: 0.9754
Epoch: 7
Test Accuracy: 0.9774
Epoch: 8
Test Accuracy: 0.9802
Epoch: 9
Test Accuracy: 0.9807
Epoch: 10
Test Accuracy: 0.9781
Epoch: 11
Test Accuracy: 0.9798
Epoch: 12
Test Accuracy: 0.9795
Epoch: 13
Test Accuracy: 0.9805
Epoch: 14
Test Accuracy: 0.9804
Epoch: 15
Test Accuracy: 0.979


5. 6-Layer DNN, ReLU, Xavier Initializer, Dropout

DNN Optimization(6-Layer DNN, ReLU, Xavier Init, Dropout).ipynb

DNN Optimization(6-Layer DNN, ReLU, Xavier Init, Dropout).py

모델은 4번과 같으며 여기서 14*14 행렬로 레이어를 2개 더 늘렸습니다. 미미한 정확도 상승을 확인할 수 있습니다.

Epoch: 1
Test Accuracy: 0.9351
Epoch: 2
Test Accuracy: 0.9634
Epoch: 3
Test Accuracy: 0.9696
Epoch: 4
Test Accuracy: 0.9729
Epoch: 5
Test Accuracy: 0.9743
Epoch: 6
Test Accuracy: 0.9754
Epoch: 7
Test Accuracy: 0.975
Epoch: 8
Test Accuracy: 0.9782
Epoch: 9
Test Accuracy: 0.9776
Epoch: 10
Test Accuracy: 0.9776
Epoch: 11
Test Accuracy: 0.9772
Epoch: 12
Test Accuracy: 0.9787
Epoch: 13
Test Accuracy: 0.9795
Epoch: 14
Test Accuracy: 0.9794
Epoch: 15
Test Accuracy: 0.9793


6. 6-Layer DNN, ReLU, Xavier Initializer, Dropout, Ensemble(5 NNs)

DNN Optimization(6-Layer DNN, ReLU, Xavier Init, Dropout, Ensemb

DNN Optimization(6-Layer DNN, ReLU, Xavier Init, Dropout, Ensemb

이번엔 Ensemble(앙상블)을 해보겠습니다. Dropout보다 간단한 기술인데(기술이라고 해야하나..), 여러 개의 NN을 학습시켜서 여러 개의 NN에 test data를 넣고 predicted label을 평균 내는 등의 방법으로 정확도를 좀 더 개선하는 방법입니다. 강의에서는 3% 정도까지 정확도가 상승된다고 하셨는데, 실제로 제가 돌려봤을 때에는 0.5% 정도? 정확도가 상승하는 것을 확인할 수 있었습니다.

NN: 0 Epoch: 1 Test Accuracy: 0.9483 NN: 0 Epoch: 2 Test Accuracy: 0.9664 NN: 0 Epoch: 3 Test Accuracy: 0.9707 NN: 0 Epoch: 4 Test Accuracy: 0.9713 NN: 0 Epoch: 5 Test Accuracy: 0.9752 NN: 0 Epoch: 6 Test Accuracy: 0.9779 NN: 0 Epoch: 7 Test Accuracy: 0.9764 NN: 0 Epoch: 8 Test Accuracy: 0.9766 NN: 0 Epoch: 9 Test Accuracy: 0.9776 NN: 0 Epoch: 10 Test Accuracy: 0.9744 NN: 0 Epoch: 11 Test Accuracy: 0.9775 NN: 0 Epoch: 12 Test Accuracy: 0.9772 NN: 0 Epoch: 13 Test Accuracy: 0.9796 NN: 0 Epoch: 14 Test Accuracy: 0.9798 NN: 0 Epoch: 15 Test Accuracy: 0.9786 NN: 1 Epoch: 1 Test Accuracy: 0.9525 NN: 1 Epoch: 2 Test Accuracy: 0.959 NN: 1 Epoch: 3 Test Accuracy: 0.9666 NN: 1 Epoch: 4 Test Accuracy: 0.972 NN: 1 Epoch: 5 Test Accuracy: 0.9736 NN: 1 Epoch: 6 Test Accuracy: 0.9736 NN: 1 Epoch: 7 Test Accuracy: 0.9746 NN: 1 Epoch: 8 Test Accuracy: 0.9771 NN: 1 Epoch: 9 Test Accuracy: 0.9771 NN: 1 Epoch: 10 Test Accuracy: 0.9766 NN: 1 Epoch: 11 Test Accuracy: 0.978 NN: 1 Epoch: 12 Test Accuracy: 0.9788 NN: 1 Epoch: 13 Test Accuracy: 0.9787 NN: 1 Epoch: 14 Test Accuracy: 0.98 NN: 1 Epoch: 15 Test Accuracy: 0.9787 NN: 2 Epoch: 1 Test Accuracy: 0.9537 NN: 2 Epoch: 2 Test Accuracy: 0.966 NN: 2 Epoch: 3 Test Accuracy: 0.9742 NN: 2 Epoch: 4 Test Accuracy: 0.9731 NN: 2 Epoch: 5 Test Accuracy: 0.976 NN: 2 Epoch: 6 Test Accuracy: 0.9777 NN: 2 Epoch: 7 Test Accuracy: 0.9748 NN: 2 Epoch: 8 Test Accuracy: 0.978 NN: 2 Epoch: 9 Test Accuracy: 0.9766 NN: 2 Epoch: 10 Test Accuracy: 0.9784 NN: 2 Epoch: 11 Test Accuracy: 0.9787 NN: 2 Epoch: 12 Test Accuracy: 0.9767 NN: 2 Epoch: 13 Test Accuracy: 0.9813 NN: 2 Epoch: 14 Test Accuracy: 0.9804 NN: 2 Epoch: 15 Test Accuracy: 0.9815 NN: 3 Epoch: 1 Test Accuracy: 0.9547 NN: 3 Epoch: 2 Test Accuracy: 0.9677 NN: 3 Epoch: 3 Test Accuracy: 0.9722 NN: 3 Epoch: 4 Test Accuracy: 0.9708 NN: 3 Epoch: 5 Test Accuracy: 0.9728 NN: 3 Epoch: 6 Test Accuracy: 0.9753 NN: 3 Epoch: 7 Test Accuracy: 0.9773 NN: 3 Epoch: 8 Test Accuracy: 0.9783 NN: 3 Epoch: 9 Test Accuracy: 0.977 NN: 3 Epoch: 10 Test Accuracy: 0.9775 NN: 3 Epoch: 11 Test Accuracy: 0.9795 NN: 3 Epoch: 12 Test Accuracy: 0.9794 NN: 3 Epoch: 13 Test Accuracy: 0.9792 NN: 3 Epoch: 14 Test Accuracy: 0.9809 NN: 3 Epoch: 15 Test Accuracy: 0.9802 NN: 4 Epoch: 1 Test Accuracy: 0.9469 NN: 4 Epoch: 2 Test Accuracy: 0.9663 NN: 4 Epoch: 3 Test Accuracy: 0.9673 NN: 4 Epoch: 4 Test Accuracy: 0.9742 NN: 4 Epoch: 5 Test Accuracy: 0.9723 NN: 4 Epoch: 6 Test Accuracy: 0.9739 NN: 4 Epoch: 7 Test Accuracy: 0.9763 NN: 4 Epoch: 8 Test Accuracy: 0.9772 NN: 4 Epoch: 9 Test Accuracy: 0.9758 NN: 4 Epoch: 10 Test Accuracy: 0.9797 NN: 4 Epoch: 11 Test Accuracy: 0.9783 NN: 4 Epoch: 12 Test Accuracy: 0.9798 NN: 4 Epoch: 13 Test Accuracy: 0.9805 NN: 4 Epoch: 14 Test Accuracy: 0.9808 NN: 4 Epoch: 15 Test Accuracy: 0.9804


Ensemble Accuracy: 0.9846

이렇게 간단히 MNIST를 사용하여 딥러닝에서 사용되는 여러 기술을 적용해보고 테스트를 해봤습니다. 다음 번에는 CNN, RNN 등을 나갈 예정입니다.


*Ensemble은 생각보다 TensorFlow에서 구현하는 방법이 잘 나오지 않아서 아래 링크를 참조했습니다.

https://github.com/Hvass-Labs/TensorFlow-Tutorials/blob/master/05_Ensemble_Learning.ipynb

  1. Study 2017.10.17 11:39

    DNN 구현할 때 Output Data로 14×14를 넣으신 이유는 Layer를 2번 돌리기 때문일까요?

    • makeapp 2017.10.28 00:10 신고

      네, 중간 레이어 크기를 적당하게 잡아주기 위해서 적당히 14x14를 넣었습니다.

아주 간단한 Deep Learning에 대한 기초를 이전 글들을 통해 쌓아봤습니다. 이제 본격적으로 NN(Neural Network)를 시작할 겁니다. (기대되네요 >_<)


먼저 간단한 NN으로 다층 퍼셉트론을 이용한 XOR 문제를 해결해볼 것입니다. XOR(eXclusive OR: 베타적 논리합)은 논리 연산 중 하나입니다. 컴퓨터 프로그래밍에서는 A^B로 나타낼 수 있습니다. 두 변수가 있고, 이를 XOR 했을 때 결과는 다음 표와 같습니다.


B

A^B

 False

False

False

True

False

True

False

True

True

True

True

False


OR 연산과 다른 점은 A와 B를 연산했을 때 True가 아니라 False가 나온다는 점입니다. True를 1, False를 0이라고 두고 결과값 True를 파란색 점, False를 빨간색 점이라고 두면 다음과 같은 그래프로 나타낼 수 있습니다.

보시다시피, 이렇게 나와있는 형태의 경우 직선으로는 두 종류를 정확도 100%로 분할할 수 없습니다. 따라서 선형 분류가 불가능하다는 소리인데, 이런 경우가 XOR 말고도 꽤 있습니다. 따라서 이런 문제에서는 sigmoid 함수(뉴런)를 한 번만 사용하는 것이 아닌 여러 번을 겹쳐 사용하여 문제를 해결합니다. 간단히 아래와 같이 NN을 설계해보겠습니다.

(그래프 표기는 http://www.asimovinstitute.org/neural-network-zoo/를 따랐습니다. 넘나 이뻐서... 앞으로도 그래프는 이것을 따를 예정입니다)

노란색 노드: 입력, 초록색 노드: 은닉층, 주황색 노드: 출력

노란색 노드를 제외하고는 모두 sigmoid를 가지고 있는 연산노드입니다. 각 노드를 왼쪽부터 순서대로 Layer 1(노란색), Layer 2(초록색), Layer 3(주황색)이라고 하고 각 층마다 행렬로 묶어서 연산을 하면 좀 더 간단하게 연산을 나타낼 수 있습니다. 아래가 그 모습입니다.


이 상태에서 Layer 2, Layer 3에 sigmoid를 적용하면 끝인거죠. 참 쉽죠?


다음으로 이 네트워크를 어떻게 최적화시킬지 알아보겠습니다. 물론 TensorFlow 상에선 cross-entropy를 적용하고 GradientDescentOptimizer를 적용하면 바로 해결됩니다. 그래도 간단히 최적화시키는 기법인 Backpropagation을 알아가면 좋을 거 같아서 한 번 알아보겠습니다.


Backpropagation(역전파법)은 다음과 같은 과정으로 이뤄집니다.

1. Forward Propagation: 임의의 작은 수로 Network의 Weight를 초기화합니다. 그리고 학습 데이터를 입력하여 출력을 만들어냅니다.

2. label과 출력의 에러를 계산해냅니다. 그리고 이를 기반으로 Weight에 대한 미분값을 구합니다.


3. 계산된 에러의 미분값을 이용해 학습률(Learning rate)를 정하고 학습률만큼 수정된 Weight를 구해 업데이트합니다. 그리고 네트워크가 일정한 정확도

     에 도달할 때까지 이 과정을 반복합니다.


수식으로 나타내보고 싶지만, 이게 여간 쉬워보이진 않아서(...) 나중에 기회가 되면 수식으로 정리해보겠습니다.


이렇게 기초적인 NN에 대해 정리를 해봤습니다. 아래 코드를 통해서 이를 직접 테스트할 수 있습니다. Jupyter Notebook 파일도 첨부합니다.

Hello, NN!.ipynb

import tensorflow as tf
import numpy as np

x = [[0, 0], [0, 1], [1, 0], [1 ,1]]
y = [[0], [1], [1], [0]]

X = tf.placeholder(tf.float32, [None, 2], name="X-Input")
Y = tf.placeholder(tf.float32, [None, 1], name="Y-Input")

W1 = tf.Variable(tf.random_uniform([2, 2], -1., 1.), name="W1")
W2 = tf.Variable(tf.random_uniform([2, 1], -1., 1.), name="W2")
B1 = tf.Variable(tf.zeros([2]), name="B1")
B2 = tf.Variable(tf.zeros([1]), name="B2")

with tf.name_scope("Layer2") as scope:
    L2 = tf.sigmoid(tf.matmul(X, W1) + B1)
with tf.name_scope("Layer3") as scope:
    L3 = tf.sigmoid(tf.matmul(L2, W2) + B2)

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

with tf.name_scope("Train") as scope:
    train = tf.train.GradientDescentOptimizer(0.5).minimize(cost)

with tf.name_scope("Accuracy") as scope:
    accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.floor(L3 + 0.5), Y), tf.float32))
    accuracy_summ = tf.summary.scalar("Accuracy", accuracy)

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

merged = tf.summary.merge_all()
writer = tf.summary.FileWriter("./logs/", sess.graph)

for step in xrange(2001):
    summary, _ = sess.run([merged, train], feed_dict={X: x, Y: y})
    writer.add_summary(summary, step)
    if step % 500 == 0:
        print sess.run(cost, feed_dict={X: x, Y: y})

print sess.run(accuracy, feed_dict={X: x, Y: y})


추가적으로 설명하자면, Deep Learning에서 Accuracy 등의 변화를 좀 더 편하게 살피고, 네트워크의 구조를 살피기 좋게 TensorFlow에서는 TensorBoard라는 것을 제공합니다. 따라서 앞으로 다루는 NN에 대해서는 TensorBoard를 적용해서 편리하게 네트워크를 보려고 합니다. 그래서 이번 기회에 이 TensorBoard를 소개하고자 합니다. TensorBoard 사용방법은 다음과 같습니다.


1. 그래프에 표기하고 싶은 변수들의 이름을 정한다

X = tf.placeholder(tf.float32, [None, 2], name="X-Input")


2. 기록하고 싶은 부분(Accuracy, Cost, Layer 등)을 scop를 통해 묶는다.

with tf.name_scope("Train") as scope:


3. Scalar 그래프 또는 Histogram 그래프로 보고 싶은 변수는 아래와 같이 이름과 함께 등록해둔다.

cost_summ = tf.summary.scalar("Cost", cost)


4. 모든 Summaries를 Merge하고, 이 Summaries를 기록할 Writer를 만든다. 이때 저장될 log의 경로를 정합니다.

merged = tf.summary.merge_all()

writer = tf.summary.FileWriter("./logs/", sess.graph)


5. merged된 것을 돌리고 매 step마다 writer에 추가합니다.

summary, _ = sess.run([merged, train], feed_dict={X: x, Y: y})

writer.add_summary(summary, step)


6. TensorBoard를 이용해 기록된 log, 네트워크의 graph를 확인합니다. 이때 경로 상에 ','가 있는 경우 그걸 기점으로 경로가 나눠져버리니 이 점 고려하셔야 합니다. (의도는 여러 개의 log를 볼 수 있게 ','를 통해 여러 경로를 받으려는 거 같은데, ""로 경로를 하나 감싸도 이러는 거 보니 버그 맞는 듯) 만약 로그가 정상적으로 로딩이 안 되면, --debug를 붙여서 TensorBoard의 상태를 확인해보시면 됩니다.

tensorboard --logdir=./logs/


아래는 위 코드를 실행시키고 TensorBoard로 Cost, Accuracy, NN의 Graph를 확인한 내용입니다.



이렇게 간단한 NN을 끝내봤습니다. 다음엔 ReLU를 할 거 같습니다.


오늘은 머신러닝 데이터셋 중에서 유명한 붓꽃(iris) 데이터셋을 이용하여 여러 개의 sigmoid 함수를 써서 다변수의 classification을 해보겠습니다.
데이터셋은 다음의 링크(http://ai-times.tistory.com/418)에서 다운받으실 수 있습니다.


일단 먼저 붓꽃 데이터셋은 4개의 항목(SepalLength, SepalWidth, PetalLength, PetalWidth)과 그에 맞는 붓꽃의 종류(Species)가 있습니다. 이때 각 항목에 맞는 데이터를 집어 넣고 그 항목에 맞는 붓꽃은 무슨 종류인지 알아 내려면 이 항목들이 어떤 특성을 가지고 있는지 확인해야 합니다. 따라서 각 항목별로 그래프를 그려서 분포를 확인해봅니다.

Sepal Width, Sepal Length 그래프에서 versicolor 종과 virginica 종이 섞여서 분포하는 것을 제외하고는 대부분 각 종마다 특정 좌표에 많이 모여있다는 게 보입니다. 이런 경우에는 Softmax Classification을 적용하여 종을 알고 싶은 붓꽃의 항목 데이터를 입력하여 붓꽃의 종을 알아내는 인공지능을 만들어 낼 수 있는 것이죠.


자, 그러면 이제 프로그래밍을 해볼 차례입니다. 우리의 목표는 붓꽃의 항목 데이터를 받아서 붓꽃의 종(3가지)을 알아내는 겁니다. 이때 Logistic Regression은 어떤 특정 항목에 대한 확률을 보여주는 방법이기 때문에 3가지 종에 각각 Logistic Regression을 적용해주면 됩니다. 즉, setosa인지 아닌지 확률을 판단하는 Logistic Regression, versicolor인지 아닌지 확률을 판단하는 Logistic Regression, virginica인지 아닌지 확률을 판단하는 Logistic Regression을 각각 만들어서 각 함수에 항목 데이터를 입력해서 각 종에 대한 확률을 구하고, 그 확률이 가장 높은 게 그 종이 되는 겁니다.


그러나 이렇게 각 종마다 Logistic Regression을 적용하면 output이 모두 같게 (예: (0.9, 0.9, 0.9)) 나올 수도 있고, 성능도 조금 떨어지기 때문에 각 종에 맞을 확률의 합이 1(예: (0.7, 0.2, 0.1))로 나오도록 sigmoid를 확장시켜 Softmax라는 새로운 식이 등장하게 되었습니다. Softmax는 다음과 같습니다.

이때 K=2라면 원래 sigmoid 형태의 함수가 나오게 되죠.



자, 그러면 이제 프로그래밍을 해볼까요? 일단 총 3개의 종이 있고, 4개의 항목이 있으니 이에 대한 3행 4열의 weight 행렬을 만들어야 합니다. 그리고 3행의 bias 행렬도 필요하겠죠.


이때 input은 ?행(데이터 개수) 4열이기 때문에 편의상 W를 Tranpose 해주고 곱한 다음에 b도 Tranpose 하고 더하면 됩니다. 즉, 간단하게 4행 3열의 weight, 3열의 bias 행렬을 생성해서 사용하면 됩니다.


다음으로는 이 W, b를 최적화시켜서 최대한 올바른 Softmax 함수가 나오도록 해 줄 cost값을 도출할 함수가 필요합니다. 이 함수를 Cross Entropy 함수라고 합니다. 이 함수는 다음과 같습니다.


sigmoid에서 Softmax가 나온 것처럼 이것도 sigmoid에서 쓰인 cost함수랑 비슷하게 생겼습니다. TensorFlow에서 위 식을 직접 구현해서 써도 되긴 하지만, TensorFlow Tutorial에서 MNIST for ML Beginners 문서를 보면 불안정하다고 나와있으니 tf.nn.softmax_cross_entropy_with_logits 함수를 사용할 것입니다. (Note that in the source code, we don't use this formulation, because it is numerically unstable. Instead, we apply tf.nn.softmax_cross_entropy_with_logits on the unnormalized logits (e.g., we call softmax_cross_entropy_with_logits on tf.matmul(x, W) + b), because this more numerically stable function internally computes the softmax activation. In your code, consider using tf.nn.(sparse_)softmax_cross_entropy_with_logits instead)


이제 Softmax 알고리즘은 다 준비되었습니다. 위 내용을 기반으로 프로그래밍을 해보면 아래와 같은 코드가 나옵니다. (이번에는 Jupyter Notebook을 이용하여 코딩을 해서 따로 이 파일도 첨부하겠습니다.)


softmax_classification.ipynb

import tensorflow as tf
import pandas as pd
import numpy as np

keys = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth']
data = pd.read_csv("iris.csv")
data = data.drop('caseno', axis=1)

species = list(data['Species'].unique())
data['class'] = data['Species'].map(lambda x: np.eye(len(species))[species.index(x)])

testset = data.sample(50)
trainset = data.drop(testset.index)

X = tf.placeholder(tf.float32, [None, 4])
Y = tf.placeholder(tf.float32, [None, 3])

W = tf.Variable(tf.zeros([4, 3]), name="Weight")
b = tf.Variable(tf.zeros([3]), name="Bias")

H = tf.nn.softmax(tf.matmul(X, W) + b)

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(H, Y, name="Cross_Entropy"))
optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(H, 1), tf.argmax(Y, 1)), tf.float32))

init = tf.initialize_all_variables()

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

trainset_class = [y for y in trainset['class'].values]

for step in xrange(1001):
    sess.run(optimizer, feed_dict={X: trainset[keys].values,
                                   Y: trainset_class})
    if step % 100 == 0:
        print step, sess.run(accuracy, feed_dict={X: trainset[keys].values,
                                                  Y: trainset_class})
        print sess.run(cross_entropy, feed_dict={X: trainset[keys].values,
                                                 Y: trainset_class})
# Check which data is error
# trainset_result = sess.run(H, feed_dict={X: trainset[keys].values})

# error_data = []

# for x in xrange(trainset.shape[0]):
#     if np.argmax(trainset_result[x], 0) != np.argmax(trainset_class[x], 0):
#         error_data.append(trainset.values[x])
        
# print error_data

# Check testdata's accuracy
print sess.run(accuracy, feed_dict={X: testset[keys].values,
                                    Y: [y for y in testset['class'].values]})

# Test which species accords this data
species = ["setosa", "versicolor", "virginica"]

sample = data.sample(1)
result = species[np.argmax(sess.run(H, feed_dict={X: sample[keys].values}))]
print "The flower which has", sample[keys].values, "may be", result
if result == sample['Species'].values:
    print "Correct!"
else:
    print "Incorrect!"

실행시켜보면 거의 평균적으로 정확도가 98% 정도 나옵니다.


이렇게 Softmax Classification을 끝내봤습니다. 다음에는 TensorBoard를 소개하면서 NN을 시작하지 않을까 싶네요.


본 글은 필자가 Ubuntu 16.04.1 64bit 기반의 Unity와 Gnome을 약 6개월 정도 써보고 기타 다른 Linux를 깔면서 겪은 각종 문제들을 정리해둔 것입니다. 아마 계속 업데이트 될 거 같네요.


1. 부팅 문제

부팅이 실패하는 경우는 원인이 무진장 많습니다. 그러나 제가 경험해보니 원인은 거의 하나에 편중되어 있는 거 같더라고요. 그 원인을 순위별로 나타내서 해결법까지 말씀드려보겠습니다.


- 1위. 그래픽 문제

특히, Nvidia 그래픽 카드(왜 리누즈 토르발스가 Nvidia를 욕하는지 알 수 있는 경우입니다)를 쓰는 유저의 경우 처음에 OS를 깔 때 아예 Live CD 조차 GUI 부팅이 안 되는 경우가 있습니다. 또는 데스크탑에서 리눅스 잘 쓰다가 각종 패키지를 업데이트했을 경우(특히 아마 유저가 개발자라면 더 심할 거 같습니다) 기존의 리눅스 그래픽 드라이버와 충돌이 나는지 부팅이 안 되는 경우가 있습니다. 이런 경우에는 제 경험상 아래와 같은 현상이 나타납니다.


    - 1. Live CD로 부팅할 때 리눅스 펭귄 8마리 정도(모니터 크기, 해상도에 따라 다를 겁니다)가 떠 있고 그 이후 어떠한 그래픽의 변화도 일어나지 않는다.

        ↳ CentOS Live CD로 부팅시 발생했습니다.

    - 2. 부팅 로그가 뜨다가 'ACPI PCC Probe Failed' 등 probe 뭐시기가 실패했다, 오류가 났다는 등의 로그가 뜨고 진행이 안 된다.

        ↳ 거의 웬만한 Linux를 깔거나 깔려있는 상태에서 부팅할 때 발생했습니다.

    - 3. 부팅은 정상적으로 된 거 같은데 정확한 계정 정보로 로그인을 시도했음에도 불구하고 로그인 화면이 끊임없이 반복되고 메인 화면으로 이동할 수 없다.

        ↳ Ubuntu Unity에서 Nvidia Driver를 깐 직후 또는 특정 상황(업데이트를 했던가 등)이후에 재부팅했을 경우 발생했습니다.

    - 4. 부팅 화면이 정상적으로 뜨다가 로그인 화면이 나와야 하는데 모니터 백라이트가 깜빡거리면서 정상적으로 GUI 화면이 뜨지 않는 경우

        ↳ Ubuntu Gnome에서 Nvidia Driver를 깐 이후 특정 상황(업데이트를 했던가 등) 이후에 재부팅했을 경우 발생했습니다.


실제로 위의 상황들은 아마 Nvidia 그래픽카드를 쓰시면서 업데이트를 자주하시거나 저처럼 개발자이시면 꽤 많이 만나 볼 수 있지 않을까 싶습니다. 해결 방안은 다음과 같습니다.


    - 1,2번의 경우에는 grub 창(Ubuntu, Advanced Options, Memory Test 등의 옵션이 있고 방향키로 옵션을 선택할 수 있는 화면)에서 원하는 옵션

       (맨 위에 있는 디폴트 옵션 추천)에 선택을 둔 다음에 엔터 누르시지 마시고 e(edit)를 누르셔서 명령어를 수정하셔야 합니다. edit 창 잘 들어오셨으면

       각종 부팅을 위한 grub 명령어들이 모여있는 창이 뜰텐데 여기서 quiet splash 있는 줄에서 splash 뒤에 적절한 곳에 nomodeset을 넣어주시면 됩니

       다. 그리고 나서 F10 또는 Ctrl+X를 통해 부팅을 시도하시면 일단 부팅은 제대로 될 것입니다. 이후에 grub 옵션을 따로 건드셔서 그 옵션에

       nomodeset을 넣어주시면 추가적으로 큰 문제는 발생하지 않으실 겁니다.

    - 3번의 경우엔 Nvidia Driver를 깔 때 Driver 내부에 있는 OpenGL도 같이 설치하셨거나 아니면 업데이트로 인해 드라이버가 충돌난 것으로 보입니다.

       일단 로그인 창까지 오셨으면 Alt+Shift+F1을 누르셔서 CLI 콘솔 모드로 접속하셔야 합니다. 이후에는 계정 정보 입력하시고 Nvidia Graphic Driver

       Installer 설치하시던 것처럼 그대로 설치 진행하시면 됩니다. 이때 OpenGL 옵션이 보인다면 No를 선택하셔서 설치를 막으셔야 합니다. 경험상 apt 등

       으로 설치한 Nvidia Driver는 꽤나 충돌이 잘 나고 문제가 많았기 때문에 저는 Nvidia 공식 홈페이지에서 제공하는 .run 확장자의 드라이버를 추천드립

       니다. .run 확장자의 드라이버를 까는 방법은 콘솔 모드로 들어가셔서 Ubuntu Unity의 경우 'sudo systemctl stop lightdm', Ubuntu Gnome의

       경우 'sudo systemctl stop gdm'으로 GUI 서비스 죽인 후 root 계정으로 드라이버 파일을 실행해서 설치하시면 됩니다. 시간이 나면 나중에 싹 다 정리

       해서 리눅스에서 그래픽카드 드라이버 설치 방법도 안내해드리겠습니다.

    - 4번의 경우엔 일단 GUI 조차 제대로 뜨지 않았고, 게다가 제 컴에서만 그러는지는 모르겠는데 Alt+Shift+F1 등이 먹히지도 않아서 CLI 콘솔 창으로 접속

       이 불가능하더라고요. 그래서 먼저 재부팅을 하신 다음에 grub 창으로 가야합니다. 만약에 grub창이 뜨지 않으시면 리눅스가 부팅화면 뜨기 직전(저 같

       은 경우엔 부팅 화면 직전에 '-'이 깜빡거리는데 그때)부터 grub 옵션창이 뜨기 전까지 Shift 키를 그대로 쭉 누르고 있으면 grub 창이 뜹니다. 여기서 맨

       위에 디폴트 옵션 가시고 e(edit)를 누르셔서 명령어를 수정하셔야 합니다. 명령어 수정창이 뜨면 거기서 splash 있는 줄까지 오신 다음에 'quiet splash

       vt.handoff=7(또는 $~handoff 있는 명령어)'(아마 이 세 개가 다 연속적으로 있을 겁니다.)를 다 지우시고 거기에 'single'을 입력하시고 Ctrl+X 또는

       F10으로 부팅하시면 콘솔창이 뜹니다. 이후엔 위에서 설명한 Nvidia Driver 까는 방법을 그대로 따라하셔서 드라이버를 다시 까시면 됩니다.


- 2위. 디스크 파티션 문제

저 같은 경우엔 NTFS의 하드를 부팅 시에 fstab을 통해 자동 마운트를 시키는데 이게 윈도를 쓰다가 리눅스 부팅할 때 가끔씩 디스크 내부에서 꼬이는 건지 마운트 자체가 에러가 나서 fstab도 오류나고 이로 인해 부팅 자체가 멈춰버리는 경우가 있습니다. 이런 경우에는 제 경험상 아래와 같은 현상이 나타납니다.


    - 1. 부팅화면 잘 뜨고 프로그래스 바 같은 것도 잘 돌아가는 등 그래픽이 정상적으로 동작하다가 어느 순간 멈춰버리는 경우

    - 2. 부팅하다가 또는 부팅할 때 로그를 켜뒀을 때 부팅 오류라면서 Emergency Mode로 들어가는 경우


뭐 저야 이게 파티션이 망가진 게 아니고 일종의 버그 같은 거라서 저는 되게 쉽게 해결했지만 아닌 분들도 있게 추가적으로 제가 아는 한에서 해결 방안을 더 알려드리겠습니다. 1,2번에 대한 해결 방안은 아래과 같습니다.


    - grub 창에 들어가서(자동으로 안 뜨면 부팅 할 때 Shift 키 누르고 있기) Advanced Options -> 리눅스 커널 버전-recovery mode에 들어가시면

       부팅이 됩니다. 그 다음에 잘 보시면 파티션 오류 잡는 거 있는데 그거 실행해주시면 아마 될 겁니다.(이건 제가 진짜 파티션이 망가져 본 적이 없어서 제대

       로 해본 적이 없긴 하네요.

    - 저 같이 리눅스 기본 파티션 말고도 다른 것을 추가적으로 fstab으로 자동 마운트하시고 윈도와 함께 듀얼 부팅하신다면 그냥 윈도 다시 한 번 부팅하셨다

       가 뭐 이대로 끝내기는 좀 아쉬우니 문제있는 해당 디스크 윈도에서 잘 인식되는지 확인하시고 디스크를 탐색기로도 열어보시고 다시 리눅스 들어가시면

       됩니다(...).


2. 사용중 문제

그래도 윈도나 맥보다는 안정성이 조금 떨어져서 은근 문제를 많이 겪었으나 딱히 그렇게 굵직하게 사용상에 큰 지장을 주는 경우는 많이 못 봤습니다. 제가 경험하고 해결한 문제들만 보여드리겠습니다.


- 1. 키보드 & 마우스가 사용 중에 갑자기 멈춰버린다.

↳ 답이 없습니다(...). 다른 USB 포트에 키보드와 마우스를 끼워보시고, 그래도 안 살아난다면 강제로 재부팅을 하는 수 밖에 없습니다. 아마 고질적으로 가끔

     씩 Ubuntu에서 발생하는 문제로 보이는데 저도 아직까지 완벽한 해결책은 찾지 못했습니다.

- 2. Firefox를 실행하는데 분명히 다른 Firefox 프로세스가 실행되고 있지 않음에도 제대로 실행할 수 없다.

↳ 이건 설치하고 초기 실행할 때 제대로 안 했기 때문에 그럽니다. 사실 apt 등으로 firefox를 설치하신다면 죽어도 만날 수 없는 오류라고 전 생각합니다. 저

     같은 경우는 처음에 Firefox Developer Edition을 /opt/폴더 내부에 옮겨서 설치한 후에 'root' 권한으로 Firefox를 실행했는데 이때 초기 설정 파일들

     이 'root' 권한으로 생성되어서 이후에 일반 'user' 권한으로 기존의 Firefox를 실행하면 그 파일들을 불러오지 못해서 발생하는 오류라고 봅니다.

     (Chrome/Chromium은 이걸 알고 있었는지 'root' 계정으로는 죽어도 실행 못하게 만들었더라고요. 왜 그런가 했더니 이런 빅픽쳐가...)  해결방안은

     먼저 firefox를 지우시고(Developer Edition은 그 폴더 자체를 삭제) 이후에는 아래 폴더들을 완벽하게 삭제하시고 재설치하시면 됩니다.

     ~/.mozilla/firefox/

     /etc/firefox/

     /usr/lib/firefox/

     /usr/lib/firefox-addons/

  1. 벌래 2019.03.31 02:07

    해결 방법들 감사합니다

3번째로 지금까지 했던 Linear Regression과 Logisitc Classification 중 Linear Regression의 확장판인 Multiple Linear Regression을 해보려고 합니다. 방식은 아주 간단합니다. 지금까지 했던 Linear Regression에서 변수를 x 하나에서 여러 개의 x로 확장하면 되는 겁니다.


실생활의 예를 들어서 실습해보면 좋을 거 같았는데, 생각보다 Multiple Linear Regression의 데이터셋을 구하기가 어려워서 그냥 제가 임의로 식을 하나 잡고 데이터셋을 랜덤으로 제작해서 만들어봤습니다. 아래는 변수가 2개인(x1, x2) 3D 함수입니다. 3D는 일반적으로 (x, y, z)로 표현하기 때문에 아래의 예시들도 다 그렇게 좌표를 뒀습니다.

이 식을 그려보면 다음과 같은 3D plane이 나오게 됩니다.

이것을 기반으로 np.random.normal(API 문서에는 정규 분포(가우시안 분포) 기반이라고 하더군요)을 통해 데이터셋 99개를 뽑아봤습니다. 그 데이터셋을 그려보면 다음과 같습니다.

아래는 위의 함수 하나 가지고 plane 그려내고 random으로 dataset을 뽑아내는 코드입니다.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

# z = 10x - 4y + 7

#draw 3D graph
fig = plt.figure()

x, y = np.meshgrid(np.arange(-10, 10), np.arange(-10, 10))

a1 = fig.add_subplot(111, projection="3d")
a1.plot_surface(x, y, 10 * x - 4 * y + 7, rstride=1, cstride=1, cmap=cm.Blues)

a1.set_xlabel("X")
a1.set_ylabel("Y")
a1.set_zlabel("Z")

plt.show()

# data maker
fig = plt.figure()

coordinates = []

for i in xrange(100):
    x = np.random.normal(0.0, 1.)
    y = np.random.normal(0.0, 1.0)
    z = 10 * x - 4 * y + 7 +  np.random.normal(0.0, 0.1)
    coordinates.append([x, y, z])

a2 = fig.add_subplot(111, projection="3d")
a2.scatter([v[0] for v in coordinates], [v[1] for v in coordinates], [v[2] for v in coordinates])

a2.set_xlabel("X")
a2.set_ylabel("Y")
a2.set_zlabel("Z")

plt.show()

np.savetxt("dataset.txt", coordinates, fmt="%f")

데이터를 뽑아냈으면 이제 학습해야 겠죠? 아래는 Multiple Linear Regression을 학습하는 코드입니다.

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

data = np.loadtxt("dataset.txt", unpack=True, dtype="float32")

x_data = np.transpose(data[0:-1])
y_data = np.transpose(data[-1])

y_data = np.reshape(y_data, [len(y_data), 1])

X = tf.placeholder("float", [None, 2])
Y = tf.placeholder("float", [None, 1])

w = tf.Variable(tf.random_uniform([2, 1], -2.0, 2.0))
b = tf.Variable(tf.random_uniform([1, 1], -2.0, 2.0))

y = tf.matmul(X, w) + b

loss = tf.reduce_mean(tf.square(y - Y))
optimizer = tf.train.GradientDescentOptimizer(0.1)
train = optimizer.minimize(loss)

init = tf.initialize_all_variables()

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

for step in xrange(1001):
    sess.run(train, feed_dict={X: x_data, Y: y_data})
    if step % 100 == 0:
        print step, sess.run(w), sess.run(b)
        print "loss:", sess.run(loss, feed_dict={X: x_data, Y: y_data})

# Graph
fig = plt.figure()

x1, x2 = np.meshgrid(np.arange(-10, 10), np.arange(-10, 10))

x = np.transpose([x1.flatten(), x2.flatten()])

result = sess.run(y, feed_dict={X: x})

a1 = fig.add_subplot(111, projection="3d")
a1.scatter(data[0], data[1], data[2], c='r')
# 원함수 출력 원할 경우 
#a1.plot_surface(x1, x2, 10 * x1 - 4 * x2 + 7, linewidth=0, rstride=1, cstride=1, cmap=cm.Blues)
a1.plot_surface(x1, x2, np.reshape(result, x1.shape), linewidth=0, rstride=1, cstride=1, cmap=cm.Greens)

a1.set_xlabel("X1(X)")
a1.set_ylabel("X2(Y)")
a1.set_zlabel("Y(Z)")

plt.show()

이 코드를 돌리게 되면, 생각보다 수렴이 아주 빠른 건지 저 같은 경우 특정 데이터셋에 대해 100번만 학습해도 cost값이 0.0100895으로 내려가고, 이후에 전혀 내려가지 않더라고요. 그렇게해서 구해보니 w는 [[ 9.99918079] [-4.00411749]], b는 [[ 6.9931798]]가 나와서 최초 함수값과 거의 가까워졌습니다. 아래는 데이터셋과 TensorFlow로 학습된 함수를 그려본 것입니다. 거의 일치해서 데이터셋이 본 함수에 묻혔음을 알 수 있습니다.

조금 더 확실하게 확인하기 위해서 최초 함수와 학습된 함수를 그려봤습니다. 완전 덮은 것이 보입니다. 실제로 덮어졌기 때문에 pyplot창에서 이리저리 grid를 움직이다보면 앞면엔 학습된 함수인 초록색이, 뒷면엔 최초 함수인 파란색이 보입니다.

x가 더 필요하시다면 많이 늘리셔서 그대로 적용하시면 됩니다. 단, 차원이 올라가기 때문에 실제로는 x를 2개까지만 하는게 시각화하기에 적절합니다.


이렇게 Mutiple Linear Regression을 마무리해봤습니다. 다음 번에는 유명한 붓꽃(iris) 데이터셋을 통해 Softmax Classification을 해보겠습니다. 보통은 Multiple Logisitc Classification 대신 Softmax Classification(Multinomial Logistic Regression(Classification))이라고 하는 거 같더군요.

어쩌다가 소설 '개미'를 읽게 되어서 그 유명한 개미수열을 한 번 Python으로 구해보고 싶어져서 한 번 구해봤습니다.


일단 이 수열은 원래 읽고 말하기 수열입니다. 한국에서는 소설 '개미'로 인해 유명해져서 개미 수열이라고 부른다고 하군요. 재미난 점(?)이 서양(아마 서양쪽이 맞을 겁니다)과는 다르게 우리나라에서는 '1'을 '일이 한 개'라고 읽고, 서양에서는 '1'을 '한 개의 일'(one 1)라고 읽기 때문에 실제로 소설이 한국어로 번역될 때 한국 사정을 고려하다보니 한국에 출판된 '개미'에 나오는 수열은 읽고 말하기 수열를 뒤집은 꼴입니다. 예를 들어서 원래 읽고 말하기 수열은

1,

11,

21,

1211,

...


이렇게 이어지는데 반면에 한국에 출판된 '개미' 에서는

1,

12,

1121,

...


이렇게 나옵니다. 뭐 이 구조 특성상 프로그래밍하는 데도 순서만 바꾸면 되는지라 저는 한국 소설에 나온 방식이 더 맘에 들고 실제로 '개미수열'하면 이렇게 알려져 있는 경우가 더 많은 거 같아서 후자의 방식을 선택하여 프로그래밍 해봤습니다.


Python 코드는 다음과 같습니다. 일반적인 터미널 출력과 더불어 파일 출력도 가능하게 만들었습니다.

#-*- coding: utf-8 -*-

n = input("출력할 수열의 끝 번호: ")
isSave = raw_input("파일로 저장할 것인가요?(Y/N)(기본: N): ")

if isSave == "Y":
    f = open("./Les-fourmis_sequence.txt", "w")

s = "1"
for i in xrange(n):
    if isSave == "Y":
        f.write(s + "\n")
    else :
        print "\t" + str(i + 1) + ": " + s

    x = s[0]
    count = 1

    s_dev = ""

    for j in xrange(1, len(s)):
        if s[j] != x:
            s_dev += str(x) + str(count)

            x = s[j]
            count = 1
        else:
            count += 1

    s = s_dev + str(x) + str(count)

if isSave == "Y":
    f.close()

이 수열의 특성상 거의 기하급수적으로 길이가 길어지기 때문에 1번째부터 15번째 수까지만 보여드리겠습니다.

1
11
12
1121
122111
112213
12221131
1123123111
12213111213113
11221131132111311231
12221231123121133112213111
1123112131122131112112321222113113
1221311221113112221131132112213121112312311231
11221131122213311223123112312112221131112113213111213112213111
122212311223113212223111213112213111211223123113211231211131132111311222113113
...


이 수열의 재미난 성질이 있다면

1. 초기값(1)이 달라지지 않는다면 각 수에서 1, 2, 3을 제외한 다른 숫자들은 나올 수 없다.

↳ 상당히 흥미로운 이야기입니다. 나중에 가능하면 한 번 증명해보고 싶네요.

2. 초기값이 22를 제외한 임의의 수이면 수열은 끊임없이 길어진다.

↳ 간단히 생각해보면 간단한 이야기입니다. 먼저 초기값이 어떠한 수이던 간에 2번째 수부터는 무조건 짝수입니다. 근데 초기값이 한 숫자로만 이뤄져있으면

     당연하게 2번째 수는 무조건 두 자리일 수밖에 없습니다. 따라서 22이면 22, 22, 22, ...으로만 이어집니다. 즉, 초기값이 22가 아니라면 어떻게든 그 수열

     의 길이는 끊임없이 길어지게 되어 있습니다.

3. 초기값이 22를 제외한 임의의 수이고 각 수열의 일반항의 길이를 Li라고 하면

라고 합니다. 이 상수를 콘웨이 상수라고 하며 대수적 수라고 하네요. 나중에 시간나면 이놈도 한 번 구해보면 나름 재미있을 거 같습니다. ㅋㅋㅋ


지금 이 글 쓰면서 수열의 100번째 수까지 구해서 저장하고 있는데... 이런 짓은 안 하시는게 정신건강에 좋습니다. 파일 용량이 텍스트 주제 5GB를 넘어가고 있네요...(물론 저는 1부터 200억 사이에 있는 소수를 구하면서 11GB에 육박하는 텍스트 파일을 가지고 있긴 힙니다 ㅋㅋㅋ) 게다가 시간도 만만찮게 오래 걸립니다. 대충 지금 실행시간 20분 넘어간 거 같네요. 한 번 나중에 시간 복잡도 O()를 구해보는 것도 좋을 거 같네요.


(참고: https://ko.wikipedia.org/wiki/%EB%B0%94%EC%9D%B4%EC%96%B4%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%8A%A4_%ED%95%A8%EC%88%98)


어쩌다가 우연히 이 함수를 알게 되었는데, 그냥 개인 취향으로 맘에 들어서 언젠간 한 번 그려보고 싶었던 놈이였습니다. 오늘 새벽에 멍 때리다가 갑자기 생각나서 python numpy를 활용해서 그려봤네요. 거의 수학 공식 그대로를 코드로 바로 옮겨서 쓸 수 있다는 게 맘에 들었습니다. 실제로 이걸 C/C++, Java로 계산하자면(까마득...) 확실히 python이 편하긴 하네요.


코드는 다음과 같습니다.


import numpy as np import matplotlib.pyplot as plt a = 0.5 # 0 < a < 1 b = 5 # ab > 1 + 3/2 * pi x = np.arange(-5, 5, 0.0001) y = np.zeros(2 * 5 * 10000, dtype="float128") for n in xrange(1000): y += np.power(a, n) * np.cos(np.power(b ,n) * np.pi * x) if n % 100 == 0: print "Now: ", n plt.grid(True) plt.xlim(-5, 5) plt.ylim(-3, 3) plt.plot(x, y, "black", linewidth=0.1) # linewidth 조정 가능 plt.savefig("Weierstrass Function.png", dpi=1200) # 그냥 보시려면 plt.show()


이미지로 그려봤습니다. 나름 적절히 linewidth 타협을 봐서 세세하게 잘 보이면서 너무 얇지도 않게(?) 그렸습니다. 너무 얇다고 생각하시는 분은 linewidth를 0.1보다 크게 세팅하시면 됩니다.

* 그리다보면 warning으로 overflow가 뜰 수 있습니다. np.power의 특성 덕분인지는 모르겠으나 그래프 그리는데에는 큰 지장은 주지 않아서 그대로 사용했습니다. 원래는 a 초기값에서 step마다 a 자신을 초기값과 곱해서 늘려가게 짜보았으나 이건 overflow가 나버리면 아예 작동에 심각한 지장을 줘서 일단은 np.power로 했습니다. 따로 제대로된 해결책을 찾아봐야 할 것 같습니다.

Linear Regression에 이어서 두 번째로는 Logistic Regression을 해보려고 합니다. 원래는 여러 개의 x가 존재하는 Linear Regression도 해보려고 했으나, Logistic (Regression) Classification까지 한 이후에 여러 개의 x가 존재하는 Linear Regression과 Logistic (Regression) Classification을 동시에 하는게 더 좋을 거 같네요. 조금 응용해서 확장하는 수준이라서 큰 어려움은 없을 거 같아서 말이죠.


간단하게 Logistic Regression이 왜 나왔는지부터 시작하는게 좋을 듯 합니다. 예를 들어서 스팸 메일과 햄 메일(정상 메일)을 분류한다고 합시다. 스팸 메일일 경우에는 1, 햄 메일일 때는 0을 출력하게 머신 러닝을 구현하고 싶다고 하고, 이것을 Linear Regression을 통해서 해결해보려고 합시다. 일단 데이터가 다음과 같은 경우일 때를 고려해봅시다.


 메일 고유값

1 

3 

9 

4 

100 

스팸 여부 

0 

0

1 


이때의 Linear Regresssion을 구하면 다음과 같은 그래프가 나오게 됩니다. y = 0.5를 중심으로 이보다 크면 스팸이고 아래이면 햄이라고 간주됩니다.


이때 x = 4일 경우를 보면 Linear Regression으로 구한 그래프에서 스팸(1)이 아니라 햄(0)이라고 인식되게 점의 y좌표가 0.5보다 아래에 있습니다. 이러한 문제를 해결하기 위해서 이러한 Binary Classification에서는 Linear Regression보다는 훨씬 더 정확하게 나오는 Logistic Regression을 사용합니다.


Logistic Regression의 기본 모델은 다음과 같습니다. sigmoid 함수라고도 하죠.

이를 그래프로 그려보면 다음과 같습니다. (a = 1, b = 0)

그래프의 y절편은 0.5이며, y = 0과 y = 1을 점근선으로 가집니다. 이를 이용해서 머신러닝을 시켜보면 꽤 정확한 결과가 나온다고 합니다.


Linear Regression 때와 같이 이 함수도 최적화를 거쳐서 제대로된 함수로 만드는 과정이 필요합니다. 따라서 cost값을 계산을 해야합니다. 기존의 Linear Regression에서는 다음과 같은 cost 함수를 사용했었습니다. 

함수의 형태가 달라졌으니, 한 번 시험삼아 위의 값을 입력해 cost 함수의 그래프를 그려봅시다.

?! 그려보니 우리가 예상하던 U자 형태의 도형이 아닌 괴이한 형태의 도형이 나왔습니다. TensorFlow에서 사용하는 GradientDescentOptimizer, 일명 경사내려가기최적화기이라고 할 수 있는 이 알고리즘은 말 그대로 경사를 타고 내려가면서 최솟값을 찾는 방법이라고 합니다. 따라서 이 cost 함수를 그대로 적용해버리면 최적화된 함수가 나오지 않을 수도 있죠. 그래서 우리는 새로운 cost 함수를 정의해야 합니다.


먼저 우리는 학습할 데이터에서 한 데이터가 스팸(1)인 경우, 그 데이터를 함수에 넣었을 때 스팸이라고 나올 때는 cost를 가장 적게, 햄에 가깝게 나올 때에는 cost를 매우 많이 줘야합니다. 반대로 한 데이터가 햄(0)인 경우, 그 데이터를 함수에 넣었을 때 햄이라고 나올 때는 cost를 가장 적게 , 스팸에 가깝게 나올 때에는 cost를 매우 많이 줘야합니다. 이러한 특성을 생각하면 다음의 cost 함수를 생각해볼 수 있습니다.

이러한 cost 함수가 나올 수 있는 까닭은 log(x)는 점근선으로 x = 0을 가지기 때문에, 원래 데이터와 함수의 계산된 값이 정반대로 나올 수록 cost를 무지무지 많이 줄 수 있기 때문입니다. 이 함수를 정리하면 다음과 같은 수식을 얻을 수 있습니다.

한 번 어떻게 나오는 지 그래프를 그려봅시다.

오오, 생각했던 대로 그릇 모양이 나와서 GradientDescentOptimizer가 최적화된 함수를 잘 구할 수 있는 모양이 나왔네요! 이제 TensorFlow에서 Logistic (Regression) Classification을 해봅시다. 코드는 다음과 같습니다.

여기서 y 함수가 바로 sigmoid 함수를 나타낸 것인데, TensorFlow에 이미 자체적으로 내장되어 있는 tf.sigmoid 함수를 써도 됩니다.  


* 보통 Logistic Regression 하면 단순히 위에 설명한 함수를 그대로 넣어서 training을 하는데, 저 같은 경우는 위와 같이 돌리게 되면 log의 특성에 따라서 nan이 나오는 경우가 발생해서(...) tf.clip_by_value(num, min, max)를 통해 값을 잘라냈습니다. 잘라내지 않으면 정상적으로 training이 되지 않고요. (이 문제 땜에 은근 골치 아파서 + 귀차니즘 발생으로 2달간 글을 안 썻던 겁니다 읍읍)

import tensorflow as tf import matplotlib.pyplot as plt import numpy as np x_data = [1, 3, 9, 4, 100] y_data = [0, 0, 1, 1, 1] a = tf.Variable(tf.random_uniform([1], 1.0, 2.0)) b = tf.Variable(tf.random_uniform([1], 1.0, 2.0)) X = tf.placeholder(tf.float32) Y = tf.placeholder(tf.float32) y = tf.div(1., 1. + tf.exp(-a * X + b)) loss = -tf.reduce_mean(Y * tf.log(tf.clip_by_value(y, 1e-8, 1.)) + (1 - Y) * tf.log(tf.clip_by_value(1 - y, 1e-8, 1.))) optimizer = tf.train.GradientDescentOptimizer(0.5) train = optimizer.minimize(loss) init = tf.initialize_all_variables() sess = tf.Session() sess.run(init) for step in xrange(2500): sess.run(train, feed_dict={X: x_data, Y: y_data}) if step % 100 == 0: print step, sess.run(loss, feed_dict={X: x_data, Y: y_data}), sess.run(a), sess.run(b) # Show result t = np.arange(0., 101., 0.01) plt.grid(True) plt.xlim(0, 101) plt.ylim(-0.5, 1.5) plt.plot(t, sess.run(y, feed_dict={X: t}), "g") # Train datas plt.plot(x_data, y_data, "ro") # Learing result plt.plot(x_data, sess.run(y, feed_dict={X: x_data}), "go") plt.show()


위 예제를 돌려서 그래프를 그려보면 다음과 같이 이쁘게 잘 나옵니다.

빨간색 점: 데이터셋

초록색 그래프: 학습된 그래프

초록색 점: 데이터셋에서 x를 넣었을 때의 값

이게 바로 Logistic Regression이고, 여기서 Classification을 하시려면 단순히 0.5를 기점으로 1에 가까운 지, 0에 가까운 지 분류하시면 됩니다.


이렇게 간단하게 Logistic Regression을 다뤄봤습니다. 다음 시간에는 다 변수의 Linear Regression, Logistic Regression을 다뤄볼 생각입니다. 다 변수인 만큼 차원도 많아져서 변수 두 개까지만 간단히 다뤄서 3D 그래프 그려보는게 이해에도 좋고 편할 거 같네요. 




+ Recent posts