728x90
반응형

리눅스 서버에 CUDA 세팅하기

 이전 포스팅에서 우분투 서버에 주피터 노트북을 세팅하는 방법에 대해 알아보았다. 이번 포스트에서는 우분투 서버에 CUDA를 세팅하고, Tensorflow를 이용해 CUDA 세팅이 제대로 되었는지 확인하는 방법에 대해 알아보도록 하겠다.

 

 

 

 

1. Tensorflow가 GPU를 정상적으로 인식하고 있는지 확인

  • Tensorflow를 설치하였더라도, GPU를 사용하기 위해서는 사용하고자 하는 Tensorflow 버전에 맞는 CUDA와 Cudnn을 설치해야 한다.
  • Tensorflow가 현재 인지하고 있는 사용 가능한 CPU, GPU를 확인하는 방법은 다음과 같다.
from tensorflow.python.client import device_lib
device_lib.list_local_devices()

  • 위 사진을 보면, JSON 형태로 CPU와 GPU가 출력된다. 만약 CPU와 GPU가 1개 이상인 경우 "/device:CPU:0", "/device:CPU:1", "/device:CPU:2",..., "/device:GPU:0", "/device:GPU:1", "/device:GPU:2", ...와 같은 식으로 N개의 해당하는 device와 기종 설명 등 간략한 정보가 출력되게 된다.
  • 해당 번호를 기반으로 Tensorflow 사용 시, 원하는 장비를 선택할 수 있다.

 

 

 

 

2. 필요한 CUDA와 cuDNN 버전 확인하기

  • CUDA와 cuDNN을 설치하기 전에 자신이 사용하려고 하는 Tensorflow 버전을 구체적으로 알아야한다.
  • 프로그래밍에 익숙하지 않은 사람이라면, 최신 라이브러리일수록 좋은 것이라고 단순하게 생각할 수 있는데, 최신 버전은 알려지지 않은 오류가 존재할 가능성이 높은 안정되지 않은 상태일 수 있기 때문에, 이러한 이슈를 알아보고 설치하는 것을 추천한다.
  • 이번 포스트에서는 tensorflow-2.4.0을 사용할 것이므로, 그에 해당하는 CUDA와 cuDNN을 설치하도록 하겠다.
  • Tensorflow 버전별 CUDA와 cuDNN 정보는 Tensorflow 공식 홈페이지를 보면 알 수 있다.
  • https://www.tensorflow.org/install/source
 

소스에서 빌드  |  TensorFlow

소스에서 TensorFlow pip 패키지를 빌드하고 Ubuntu Linux 및 macOS에 설치합니다. 명령어는 다른 시스템에도 적용될 수 있지만, Ubuntu 및 macOS용으로만 테스트되었으며 지원됩니다. 참고: 잘 테스트되고

www.tensorflow.org

  • 해당 사이트를 보면, tensorflow-2.4.0에 해당하는 GPU는 cuDNN 8.0, CUDA 11.0임을 알 수 있다.

 

 

 

 

3. CUDA 다운로드하기

  • CUDA와 cuDNN 다운 및 설치 방법은 이전 포스트 "텐서플로우(Tensorflow)와 tensorflow-gpu 설치방법"과 매우 유사하며, 이번에는 리눅스 버전으로 다운을 받아보도록 하자.
  • 자신이 원하는 버전에 해당하는 CUDA를 다운로드하기 위해선 아래 홈페이지로 이동하면 된다.
  • https://developer.nvidia.com/cuda-toolkit-archive
 

CUDA Toolkit Archive

Previous releases of the CUDA Toolkit, GPU Computing SDK, documentation and developer drivers can be found using the links below. Please select the release you want from the list below, and be sure to check www.nvidia.com/drivers for more recent production

developer.nvidia.com

  • 해당 웹사이트에서 CUDA Toolkit 11.0을 클릭하도록 하자.

  • 이번에 Tensorflow를 세팅하고자 하는 서버는 오프라인 우분투 18.04이므로, 다음과 같은 버튼을 클릭하여 설치 파일을 받도록 하자.

  • 위와 같이 누르는 경우, 우리에게 익숙한 파일 다운로드 버튼이 아닌 리눅스 환경에서 파일을 다운로드할 수 있는 명령어가 출력된다.

  • 위 명령어에서 첫 줄인 "wget ~"은 CUDA 설치 파일을 다운로드하는 것이며, 두 번째 줄은 해당 파일을 실행하는 것이다.
  • 인터넷이 되는 우분투 환경으로 이동하여, 이전에 만들어놨던 Download 디렉터리로 이동해서 해당 파일을 다운로드하도록 하자.
wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda_11.0.2_450.51.05_linux.run

  • 다운 완료 후, 파일이 Download 디렉터리에 설치 파일이 존재하는 것을 확인하도록 하자.
ll

  • 성공적으로 CUDA 11.0 설치 파일이 다운된 것을 볼 수 있다.

 

 

 

 

4. cuDNN 다운로드하기

 

cuDNN Archive

NVIDIA cuDNN is a GPU-accelerated library of primitives for deep neural networks.

developer.nvidia.com

  • cuDNN을 보면, CUDA 버전에 맞는 것이 무엇인지 볼 수 있다.
  • cuDNN은 CUDA Deep Neural Network의 약자로, 딥러닝 관련 연산을 빠르게 해주는 라이브러리들이 모여있는 패치 파일이라고 생각하면 된다.
  • cuDNN 파일은 windows와 마찬가지로 CUDA 설치 후, 압축을 푼 cuDNN 파일을 설치된 CUDA 파일에 덮어 씌워주면 된다.
  • 설치하고자 하는 운영체제에 맞는 cuDNN을 다운로드하도록 하자. 우분투는 단순하게 Linux로 다운로드하여도 상관없다.
  • cuDNN은 CUDA와 달리 NVIDIA 홈페이지 로그인이 필요하므로, 해당 작업 진행 후, cuDNN을 받도록 하자.

  • 로그인 후, 해당 버튼을 누르면 "cudnn-11.0-linux-x64-v8.0.5.39.tar" 또는 뒤에 ".solitairetheme8" 라고 붙은 파일이 다운로드하여진다. 이 경우 윈도우 다운로드 파일에 받아지기 때문에 리눅스로 이동시키는 과정이 필요하다.
  • CUDA를 설치하고자 하는 리눅스 서버와 로컬 PC가 FTP가 뚫려 있는 경우 파일질라(FILEZILLA)와 같은 무료 FTP 클라이언트를 이용하는 것이 가장 직관적인 방법이며, USB를 이용하여 파일을 이동시킬 수도 있다.
  • 해당 포스트는 wsl2를 이용하여 해당 과정을 진행하므로, c드라이브에 "Linux_mv"라는 이름의 임의의 디렉터리를 생성하고, cuDNN 파일을 이동시켰다.
cd /mnt/c/Linux_mv
mv cudnn-11.0-linux-x64-v8.0.5.39.tgz /home/gooopy123/Download
cd
cd Download
ll

 

 

 

 

 이제 CUDA 설치를 위한 기본적인 준비가 되었다. 다음 포스트에서는 CUDA 설치를 진행해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트까지 모델 컴파일에 대해 알아보았다. 이번 포스트에서는 모델을 학습에 대해 자세히 알아보도록 하자.

 

 

모델 학습과 학습 과정 확인

0. 이전 코드 정리

# Import module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout, Flatten)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))
# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])

 

 

 

 

1. 모델 학습

# 모델 학습하기
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size = 5000,
                    validation_data=(valid_images, valid_labels))
Epoch 1/100
11/11 [==============================] - 2s 185ms/step - loss: 1.5537 - accuracy: 0.4795 - val_loss: 0.4806 - val_accuracy: 0.8638
Epoch 2/100
11/11 [==============================] - 1s 52ms/step - loss: 0.3852 - accuracy: 0.8864 - val_loss: 0.2727 - val_accuracy: 0.9204
Epoch 3/100
11/11 [==============================] - 1s 53ms/step - loss: 0.2276 - accuracy: 0.9327 - val_loss: 0.1879 - val_accuracy: 0.9406
Epoch 4/100
11/11 [==============================] - 1s 53ms/step - loss: 0.1617 - accuracy: 0.9522 - val_loss: 0.1460 - val_accuracy: 0.9558
Epoch 5/100
11/11 [==============================] - 1s 52ms/step - loss: 0.1213 - accuracy: 0.9650 - val_loss: 0.1222 - val_accuracy: 0.9618

...

Epoch 96/100
11/11 [==============================] - 1s 54ms/step - loss: 2.8528e-05 - accuracy: 1.0000 - val_loss: 0.1078 - val_accuracy: 0.9804
Epoch 97/100
11/11 [==============================] - 1s 59ms/step - loss: 2.9069e-05 - accuracy: 1.0000 - val_loss: 0.1080 - val_accuracy: 0.9806
Epoch 98/100
11/11 [==============================] - 1s 56ms/step - loss: 2.7108e-05 - accuracy: 1.0000 - val_loss: 0.1082 - val_accuracy: 0.9806
Epoch 99/100
11/11 [==============================] - 1s 51ms/step - loss: 2.8243e-05 - accuracy: 1.0000 - val_loss: 0.1086 - val_accuracy: 0.9806
Epoch 100/100
11/11 [==============================] - 1s 50ms/step - loss: 2.6565e-05 - accuracy: 1.0000 - val_loss: 0.1086 - val_accuracy: 0.9804
  • 이전 포스팅까지는 model.fit(train_set, train_label, epochs)만 설정하였었으나, 이번 포스팅에서는 model.fit(train_set, train_label, epochs, batch_size, validation_data)로 처음 보는 인자들이 여럿 등장한 것을 볼 수 있다.
  • train_set, train_label, epochs는 여러 번 본 인자이므로 넘어가고, batch_size와 validation_data를 위주로 설명해보겠다.
  • 아직 epochs에 대한 개념이 헷갈린다면 다음 포스팅을 참고하기 바란다.
    참고: "머신러닝-6.2. 최적화(3)-학습단위(Epoch, Batch size, Iteration)

 

 

 

 

2. Batch size

  • model.fit() 함수 안에 batch_size라는 인자가 추가된 것을 볼 수 있다.
  • batch_size는 전체 데이터셋을 한 번에 학습시키자니, 데이터가 너무 크기 때문에 메모리 과부하와 속도 저하 문제가 발생하므로, 데이터를 Batch size만큼 쪼개서 학습을 시키는 것을 의미한다.
  • 학습 단위에 대한 보다 자세한 설명은 다음 포스팅을 참고하기 바란다.
  • Batch size는 전체 데이터셋을 Batch size로 나눴을 때, 나머지가 생기지 않은 크기로 만드는 것이 좋다.
  • Batch size를 너무 크게 하면, 메모리 과부하가 발생하기 쉬우나, 더 많은 데이터를 보고 파라미터를 결정하므로 학습이 안정적으로 진행된다.
  • Batch size를 너무 작게 하면, 자주 파라미터 갱신이 발생하므로 학습이 불안정해진다.
  • Batch size를 얼마나 잡느냐에 대해선 정답이 없다고 할 수 있는데, 어떤 사람들은 자신의 머신이 가진 메모리를 넘지 않는 선에서 Batch size를 최대로 잡는 것이 좋다고 하고, 또 다른 사람은 32보다 큰 미니배치를 사용해선 절대 안된다고도 했다.
  • 양쪽 다 주장이 꽤 탄탄하므로, 미니 배치를 크게도 해보고 작게도 해보며, 결과를 비교해보도록 하자.

 

2.1. Batch size의 효과

  • Batch size는 학습에 걸리는 시간과 학습에 소모되는 메모리에 큰 영향을 미친다.
  • 얼마나 차이가 나는지 확인하기 위해 Batch size를 설정하지 않은 학습과 설정하지 않은 학습을 비교해보도록 하자.

 A. Batch size를 지정하지 않는 경우

from time import time
start = time()

history = model.fit(train_images, train_labels,
                    epochs=100,
                    validation_data=(valid_images, valid_labels))
...
>>> print("코드 동작 시간: {} minutes".format(round((time() - start)/60, 2)))
코드 동작 시간: 6.39 minutes
  • time 모듈의 time() 함수는 1970년 1월 1일 0시 0분 0초 이후 경과한 시간을 초 단위로 반환하는 함수로, time함수의 차를 이용해서, 특정 구간에서 소모된 시간을 알 수 있다.
  • Batch size를 사용하지 않고, 모든 데이터를 한 번에 학습시키는 경우, 6.39분이 소모된 것을 볼 수 있다.

B. Batch size를 지정한 경우

start = time()

history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels))
...
>>> print("코드 동작 시간: {} minutes".format(round((time.time() - start)/60, 2)))
코드 동작 시간: 1.0 minutes
  • Batch size를 사용하자, 학습에 소모된 시간이 1.0분으로 6배 이상 감소한 것을 볼 수 있다.
  • 이번에 학습에 사용한 MNIST는 그다지 큰 데이터도 아님에도 소모 시간 차이가 이렇게 크게 발생한 것을 볼 때, 이보다 더 큰 데이터를 다루게 되는 실제 상황에서 Batch size 지정은 필수임을 알 수 있다.

 

 

 

 

3. validation data

  • 이전 학습에서 validation_data를 지정하지 않았듯, 검증 셋을 지정하지 않아도 학습을 하는 데는 문제가 없지만, 검증 셋이 존재한다면, 매 학습 때마다 모델을 평가하여, 최적의 모델을 만들어내는데 큰 도움이 된다.
  • 검증 셋에 대해 헷갈리는 분을 위해 이전 포스트 링크를 걸어놓도록 하겠다.
    (참고: "Tensorflow-3.2. 이미지 분류 모델(2)-검증 셋(Validation set)")
  • validation data는 위 방법처럼 검증 셋을 미리 뽑고, 학습을 진행하는 방법도 있지만 자동으로 검증 셋을 뽑아놓는 방법도 존재한다.
  • model.fit() 함수의 파라미터에 validation_split이라는 인자가 존재하는데, 이는 float으로 지정할 수 있으며, 데이터를 섞기(Shuffle) 전에 지정한 비율만큼의 데이터를 검증 셋으로 사용한다.
  • 그러나, 참고 포스팅에서 보듯 검증 셋을 대표성 있게 추출하는 것은 매우 중요하므로, 사전에 검증 셋을 미리 추출하는 것을 추천한다.
  • 이밖에도 validation_steps, validation_batch_size, validation_freq와 같은 검증 셋 관련 파라미터들이 더 존재하지만, 이들에 대해서는 추후 다루도록 하겠다.

 

 

 

 

 지금까지 모델의 학습(Fit)에 대해 알아보았다. 이전에 fit()에 사용했던 파라미터들은 학습을 위해 필요한 최소한의 파라미터들이었다면, 이번에 사용한 파라미터들은 가장 일반적으로 사용되는 파라미터들이다. 

 fit() 함수는 이밖에도 샘플별 가중치 조정(sample_weight)이나, 특정 클래스에 대한 가중치 조정(class_weight)과 같은 다양한 기능들을 더 가지고 있다. 그러나, 이들까지 모두 다루기는 쉽지 않고, 이번에 다룬 내용만 알더라도 Tensorflow를 적당히 다루는데 지장이 없으므로, 여기까지 학습을 하고, 나중에 필요하다면 더 자세히 다뤄보도록 하겠다.

 다음 포스트에선 학습 과정에서 나온 Log들을 분석하는 History 객체의 사용법에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 모델을 생성해보고, 생성된 모델의 정보를 살펴보았다. 이번 포스트에서는 모델을 컴파일에 대해 학습해보도록 하겠다.

 

 

모델 컴파일

0. 이전 코드 정리

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout, Flatten)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))

 

 

 

 

1. 모델 컴파일

# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])
  • 모델을 어떤 방식으로 학습시킬지 결정하는 과정이다.
  • 모델 컴파일에서 지정하는 주요 항목은 최적화 방법인 옵티마이저(Optimizer)와 손실 함수(loss)이다.
  • 추가로, 훈련과 평가 시 계산할 지표를 추가로 지정할 수 있다(metrics).

 

 

 

 

2. Optimizer

  • 최적화 방법인 Optimizer는 경사 하강법(GD)을 어떤 방법으로 사용할지를 정한다고 생각하면 된다.
  • Optimizer를 정하는 이유는 Optimizer 방법을 무엇을 선택하느냐에 따라 최적해를 찾아가는 속도가 크게 달라진다.
  • 경사 하강법(GD)은 기본적으로 4가지 문제가 존재하며, 이는 다음과 같다.
    (좀 더 자세히 알고 싶은 사람은 다음 포스팅: "머신러닝-6.1. 최적화(2)-경사하강법의 한계점"을 참고하기 바란다.)
  1. 데이터가 많아질수록 계산량이 증가함
  2. Local minimum 문제
  3. Plateau 문제
  4. Zigzag 문제
  • 위 문제들을 간단하게 말하면, 경사 하강법이 가진 구조적 단점으로 인해, 최적해를 제대로 찾아가지 못하거나, 찾는 속도가 늦어진다는 것이다.
  • 이를 해결하기 위해선 데이터셋에 맞는 Optimizer를 사용해야 하며, 단순하게 가장 많이 사용하는 Optimizer가 Adam이므로, Adam을 사용하는 것은 그다지 추천할 수 없는 방법이다.
# Optimizer는
model.compile(optimizer = "Adam",
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])
  • 위 방법으로 Optimizer를 하게 되면, 코드는 단순하지만, 학습률, Momentum과 같은 Optimizer 고유의 하이퍼 파라미터를 수정할 수 없다. 
# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])
  • 위 방법으로 Optimizer를 잡아줘야, 각종 하이퍼 파라미터를 수정할 수 있다.
  • keras.optimizers. 뒤에 원하는 optimizer를 넣으면 된다.

 

 

 

 

3. Optimizer의 종류

  • Optimizer는 기본적으로 SGD를 기반으로 하므로, 확률적 추출을 통해 경사 하강법을 시행한다.
  • Optimizer는 크게 Momentum 방식(관성 부여)과 Adagrad 방식(상황에 따른 이동 거리 조정)으로 나뉜다.
  • Momentum 방식과 Adagrad 방식을 하나로 합친 방법이 Adam과 Nadam이다.
  • 다른 Optimizer를 사용함으로 인해 최적해를 찾아가는 방법이 달라지게 되고, 그로 인해 학습 속도가 바뀌게 된다.
  • Local minimum 문제는 무작위 가중치 초기화로 인해 발생할 가능성이 매우 낮다.
  • 단순하게 Adam만 고집하지 말고, 여러 Optimizer를 사용하길 바란다.
  • Optimizer와 경사하강법에 대한 상세한 설명을 보고자 한다면, 다음 포스트를 참고하기 바란다.
  • 참고: "머신러닝-6.0. 최적화(1)-손실함수와 경사하강법"

Optimizer별 최적해 수렴 속도 차이

  • 별이 최적해라고 할 때, 각종 Optimizer가 최적해를 찾아가는 방식을 시각화한 것이다.
  • 해가 n개이므로, 파라미터는 평면이 아니라 입체이며, 이 입체를 이해하기 쉽도록 2차원 등고선으로 그린 것이다.

  • 말안장 그림이라 하여, 3차원으로 최적해를 찾아가는 과정을 그린 것이다.
  • SGD는 지역 최솟값(Local minimum)에 빠져 최적해를 찾아가지 못하였다.
  • 위 두 그림의 출처는 다음과 같으며, 보다 자세한 설명을 보고 싶은 경우 해당 사이트를 참고하기 바란다.
  • ruder.io/optimizing-gradient-descent/
 

An overview of gradient descent optimization algorithms

Gradient descent is the preferred way to optimize neural networks and many other machine learning algorithms but is often used as a black box. This post explores how many of the most popular gradient-based optimization algorithms such as Momentum, Adagrad,

ruder.io

 

 

 

 

 

4. loss

  • 손실 함수는 데이터셋과 라벨 데이터의 생김새에 따라 사용하는 방법이 달라진다.
  • 기본적으로 연속형 데이터를 대상으로는 제곱 오차(SE)에서 파생된 기법을 사용하며, 범주형 데이터를 대상으로는 크로스 엔트로피 오차(CEE)에서 파생된 기법을 사용한다.
  • 클래스의 수나 Label의 형태에 따라 사용하는 방법이 조금씩 달라진다.
  • 가장 많이 사용되는 손실 함수의 사용 예는 다음과 같다.
데이터 형태 Label의 형태 손실 함수
범주형 클래스 2개 binary_crossentropy
클래스
3개 이상
원-핫 벡터 categorical_crossentropy
단순 범주형 sparse_categorical_crossentropy
연속형 mean_squared_error
(=mse)
mean_squared_logarithmic_error
(=msle)
 

Module: tf.keras.losses  |  TensorFlow Core v2.4.1

Built-in loss functions.

www.tensorflow.org

 

 

 

 

 

5. metrics

  • 평가 기준으로 모델의 학습에는 영향을 미치지 않으나, 학습 중에 제대로 학습되고 있는지를 볼 수 있다.
  • metrics에 무엇을 넣느냐에 따라 학습 시, 히스토리에 나오는 출력 Log가 달라지게 된다.
  • 일반적으로 accuracy 즉, 정확도가 사용된다.
  • 이 역시 데이터 셋에 따라 바뀌며, 손실 함수와 유사한 것을 선택하면 된다.
  • metrics에 사용하는 하이퍼 파라미터는 아래 사이트를 참고하기 바란다.
  • keras.io/api/metrics/
 

Keras documentation: Metrics

Metrics A metric is a function that is used to judge the performance of your model. Metric functions are similar to loss functions, except that the results from evaluating a metric are not used when training the model. Note that you may use any loss functi

keras.io

 

 

 

 

 지금까지 Compile을 하는 방법에 대해 알아보았다. Compile은 일반적으로 사용하는 기법을 사용하여도 큰 차이를 느끼지 못할 수도 있으나, 제대로 모델을 학습시키기 위해선 데이터의 형태에 맞는 하이퍼 파라미터를 잡아주는 것이 좋다.

 다음 포스트에서는 모델을 실제로 학습시켜보고, 그 Log를 시각화하여 최적의 Epochs을 선택하는 방법에 대해 학습해보겠다.

 

728x90
반응형
728x90
반응형

 지난 포스트에서 MNIST 데이터 셋을 사용하여, 검증 셋(Validation set)을 생성해보았다. 이번 포스트에서는 Keras의 중심인 모델을 만들어보도록 하겠다.

 

 

모델 생성

0. 이전 코드 정리

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
################################### Function ###################################

# Data 시각화
def show_images(dataset, label, nrow, ncol):

    # 캔버스 설정
    fig, axes = plt.subplots(nrows=nrow, ncols=ncol, figsize=(2*ncol,2*nrow))
    ax = axes.ravel()

    xlabels = label[0:nrow*ncol]

    for i in range(nrow*ncol):

        image = dataset[i]
        ax[i].imshow(image, cmap='gray')
        ax[i].set_xticks([])
        ax[i].set_yticks([])
        ax[i].set_xlabel(xlabels[i])

    # 빈 칸 없이 꽉 채우기
    plt.tight_layout()
    plt.show()
    
################################################################################
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)
train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)

 

 

 

 

1. 모델 생성

  • 이전에 만들었던 모델들에 들어간 데이터들은 1차원 배열이 n개의 row로 구성된 형태였다.
>>> train_images.shape
(60000, 28, 28)
  • 그러나, 이번 데이터셋은 28*28인 행렬이 60000개 row로 쌓인 형태다.
  • 이때 평활(Flatten)이라는 개념이 추가로 등장한다.
  • 모델을 먼저 만들어보자.
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))
  • 각 Layer를 구분하기 쉽도록 name이라는 parameter를 추가해주었다.
  • 이전과 달리 Flatten이라는 Layer가 새로 추가되었다.
  • Flatten Layer는 입력된 2차원 배열을 1차원 배열로 만들어주는 전처리용 Layer다.
  • 한 Row가 X = (1, 28, 28)인 데이터셋을 X.reshape(-1, 28*28)으로 형 변환해준다고 생각하면 된다. 
>>> X = train_images[0]
>>> X.reshape(-1, 28*28).shape
(1, 784)
  • 평활 Layer 통과는 각 Row에 적용되므로, 전체 데이터 셋의 형태가 (60000, 28, 28)에서 (60000, 784)로 바뀐다고 생각해도 좋다.

 

 

 

 

2. 은닉층 설정하기

  • 지금까지의 포스팅에선 은닉층의 수와 은닉층에 있는 노드의 수를 정할 때, 어째서 이렇게 구성하였는지 설명하지 않았다. 이번에는 은닉층을 설정할 때, 무엇을 인지한 상태로 은닉층을 만들어야 하는지에 대해 학습해보도록 하겠다.

 

2.1. 은닉층의 개수

  • 은닉층의 개수가 1개인 경우를 얕은 신경망이라고 하며, 2개 이상인 경우를 심층 신경망이라고 한다.
  • 이론적으로 입력층, 은닉층, 출력층으로 3개의 층만 있는 경우에도 뉴런수만 충분하다면 아주 복잡한 함수도 모델링할 수 있다.
    George Cybenko(1989), Approximation by superpositions of a sigmoidal function - 조지 시벤코의 시벤코 정리: 뉴런 수만 무한하다면 은닉층 하나로 어떤 함수도 근사할 수 있다.
  • 그러나, 심층 신경망은 얕은 신경망보다 적은 노드를 사용하여, 과적합 문제에서 비교적 자유롭고, 얕은 신경망보다 적은 epochs로 최적해에 수렴하게 된다.
  • 이는 은닉층의 수가 늘어날수록, 저수준에서 고수준으로 체계적으로 구조를 모델링할 수 있기 때문이다.
  • 예를 들어, 3개의 은닉층을 사용해 사람을 구분하는 모델을 만들고자 한다면, Hidden1에서는 가장 저수준의 구조인 사람과 배경을 구분하는 일을, Hidden2에서는 사람의 머리, 몸, 키 등을 구분하는 일을, Hidden3에서는 가장 고수준인 사람의 얼굴과 머리스타일을 구분하도록 모델링하게 된다.
  • 이러한 계층 구조는 심층 신경망이 새로운 데이터에 대해 일반화하는 능력도 향상하게 해 준다.
  • 계층 구조는 기존 모델에 추가 기능이 생긴 업그레이드된 모델을 만들 때, 기존 모델의 파라미터를 하위 은닉층에서 재사용해 훈련을 진행할 수 있다. 이를 전이 학습(Transfer Learning)이라 한다.

 

2.2. 은닉층의 뉴런 개수

  • 데이터 셋에 따라 다르긴 하지만 모델에서 은닉층의 뉴런 수를 정할 땐, 다음과 같은 경향이 있다.
  • 일반적으로 첫 번째 은닉층을 제일 크게 하는 것이 도움된다.
  • 만약 한 층의 뉴런 수가 지나치게 적다면, 전달되는 정보 중 일부가 사라져 버릴 수 있다. 
  • 깔때기 형태: 은닉층의 구성은 일반적으로 각 층의 뉴런의 수를 점차 줄여가며, 깔때기처럼 구성한다. 이는 저수준의 많은 특성이 고수준의 적은 특성으로 합쳐질 수 있기 때문이다.
  • 직사각형 형태: 모든 은닉층의 뉴런 수를 같게 함. 이렇게 모델링을 하는 경우, 깔때기 형태와 동일하거나 더 나은 성능을 내는 경우도 있다고 한다.
  • 일반적으로 은닉층의 뉴런 수를 늘리는 것보다 은닉층의 수를 늘리는 쪽이 유리하다.

 

2.3. 스트레치 팬츠(Stretch pants) 방식

  • 실제 필요한 것보다 은닉층의 수와 뉴런의 수를 크게 하고, 과대 적합이 발생하지 않도록, 조기 종료를 하거나 규제 기법을 사용하는 방법
  • 말 그대로 "자기에게 맞는 바지를 찾는 것이 아닌, 큰 스트레치 팬츠를 사고 나중에 나에 맞게 줄이는 기법"이라 할 수 있다.
  • 해당 방식 사용 시, 모델에서 문제를 일으키는 병목층을 피할 수 있다.

 

 

 

 

3. 생성된 모델 정보

  • 이전 포스트까지는 단순하게 모델을 생성하고 바로 학습으로 뛰어들었지만, 이번엔 모델의 요약 정보를 보고 진행해보자.
>>> model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
Hidden1 (Dense)              (None, 300)               235500    
_________________________________________________________________
Hidden2 (Dense)              (None, 200)               60200     
_________________________________________________________________
Hidden3 (Dense)              (None, 100)               20100     
_________________________________________________________________
Output (Dense)               (None, 10)                1010      
=================================================================
Total params: 316,810
Trainable params: 316,810
Non-trainable params: 0
_________________________________________________________________
  • model.summary(): 모델의 요약 정보를 보여준다.
  • Layer (type)을 보면, 앞서 설정한 Layer의 이름과 어떤 Layer인지가 나온다.
  • Output shape에서 None은 아직 모르는 값으로, Input 될 데이터의 양을 의미한다.
  • Input Data는 2-d 배열인 행렬이지만, 1-d 배열로 학습이 진행되므로 shape은 1-d 배열인 것을 알 수 있다.
  • Param은 각 Layer별 파라미터의 수로, Dense Layer는 Input layer와 Output layer의 모든 노드를 연결하는 완전 연결 계층이기 때문에 연결된 선(Param)이 많다.
  • Hidden1은 이전 층의 Node가 784개이고, 자신의 Node가 300개이므로, 가중치(Weight)의 엣지가 784*300=235,200개 생성된다. 여기에 편향(Bias)의 엣지가 자신의 Node 수만큼 존재하므로, +300을 하여, 235,500개의 Param이 존재하게 된다.
  • 위 모델 같이 파라미터의 수가 많은 경우, 과대 적합(Overfitting)의 위험이 올라갈 수 있으며, 특히 훈련 데이터의 양이 많지 않은 경우 이 위험이 증가하게 된다.

 

 

[참고 서적]

 

 

 지금까지 모델을 생성하고, 그 정보를 보는 방법에 대해 학습해보았다. 다음 포스팅에서는 모델 컴파일과 학습을 진행해보도록 하겠다.

 

728x90
반응형

+ Recent posts