AI/혼자공부하는머신러닝딥러닝

[DL] 07-2 심층 신경망

inthyes 2024. 2. 21. 23:13

2개의 층

아래와 같이 텐서플로의 케라스 패키지를 임포트하고 패션 MNIST를 불러온다.

from tensorflow import keras
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()

 

이미지의 픽셀값을 0~255 범위에서 0~1 사이로 변환하고, 28 * 28 크기의 2차원 배열을 784 크기의 1차원 배열로 펼친다.

마지막으로 사이킷런의 train_test_split() 함수로 훈련 세트와 검증 세트로 나눈다. 

from sklearn.model_selection import train_test_split
train_scaled = train_input / 255.0
train_scaled = train_scaled.reshape(-1, 28 * 28)
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size = 0.2, random_state = 42)

 

 

은닉층(hidden layer)에는 활성화 함수가 표시되어 있다. 활성화 함수는 신경망 층의 선형 방정식의 계산 값에 적용하는 함수이다.

출력층에 적용하는 활성화 함수는  종류가 제한되어 있다. 이진 분류일 경우 시그모이드 함수를 사용하고 다중 분류일 경우 소프트맥스 함수를 사용한다. 이에 비해 은닉층의 활성화 함수는 비교적 자유롭다.

 

시그모이드 활성화 함수를 사용한 은닉층과 소프트맥스 함수를 사용한 출력층을 케라스의 Dense 클래스로 만들어보자.

케라스에서 신경망의 첫 번째 층은 반드시 ihnput_shape 매개변수로 입력의 크기를 지정해주어야 한다.

dense1 = keras.layers.Dense(100, activation = 'sigmoid', input_shape = (784,))
dense2 = keras.layers.Dense(10, activation = 'softmax')

 

dense1이 은닉층이고 100개의 뉴런을 가진 밀집층이다. 활성화 함수를 'sigmoid'로 지정했고 input_shape 매개변수에서 입력의 크기를(784, )로 지정했다. 

은닉층의 뉴런 개수를 정하는 데는 특별한 기준이 없기 때문에 몇 개의 뉴런을 두어야 할지 판단하기 위해서는 상당한 경험이 필요하다.

 

여기에서 한 가지 제약 사항이 있다면 적어도 출력층의 뉴런보다는 많게 만들어야 한다. 클래스 10개에 대한 확률을 예측해야 하는데 이전 은닉층의 뉴런이 10개보다 적다면 부족한 정보가 전달되기 때문이다.

 

dense2는 출력층이다. 10개의 클래스를 분류하므로 10개의 뉴런을 두었고 활성화 함수는 소프트맥스 함수로 지정했다.

 

심층 신경망 만들기

앞에서 만든 dense1과 dense2 객체를 Sequential 클래스에 추가하여 심층 신경망(Deep Neural Network, DNN)을 만들 수 있다.

model = keras.Sequential([dense1, dense2])

 

Sequential 클래스의 객체를 만들 때 여러 개의 층을 추가하려면 이와 같이 dense1과 dense2를 리스트로 만들어 전달한다. 이 리스트는 가장 처음 등장하는 은닉층에서 마지막 출력층의 순서로 나열해야 한다.

 

인공 신경망의 강력한 성능은 층을 추가하여 입력 데이터에 대해 연속적인 학습을 진행하는 능력에서 나온다. 앞에서 학습한 선형 회귀, 로지스틱 회귀, 결정 트리 등 다른 머신러닝 알고리즘들과 대조되는 부분이다.

 

케라스는 모델의 summary() 메서드를 호출하면 층에 대한 유용한 정보를 얻을 수 있다.

model.summary()

맨 첫 줄에 모델의 이름이 나오고 이 모델에 들어 있는 층이 순서대로 나열된다. 이 순서는 맨 처음 추가한 은닉층에서 출력층의 순서대로 나열된다.

층마다 층 이름, 클래스, 출력 크기, 모델 파라미터 개수가 출력된다. 층을 만들 때 name 매개변수로 이름을 지정할 수 있다. 층 이름을 지정하지 않을 경우 케라스가 자동으로 'dense'라고 이름을 붙인다.

 

출력 크기를 보면 (None, 100)이다. 첫 번째 차원은 샘플의 개수를 나타내며 샘플 개수가 아직 정의되어 있지 않기 때문에 None으로 출력된다. 이는 케라스 모델의 fit() 메서드가 미니배치 경사 하강법을 사용하기 때문이다.

 

케라스의 기본 미니배치 크기는 32개이다. 이 값은 fit() 메서드에서 batch_size 매개변수로 바꿀 수 있다. 

따라서 샘플 개수를 고정하지 않고 어떤 배치 크기에도 유연하게 대응할 수 있도록 None으로 설정한다. 이렇게 신경망 층에 입력되거나 출력되는 배열의 첫 번째 차원을 배치 차원이라고 한다.

두 번째 차원에서의 100은 압축된 특성을 말한다. 은닉층의 뉴런 개수를 100개로 두었으니 100개의 출력이 나오는 것이다.

 

summary() 메서드의 마지막에는 총 모델 파라미터 개수와 훈련되는 파라미터 개수가 동일하게 79,510개로 나온다. 은닉층과 출력층의 파라미터 개수를 합친 값이다. 그 아래 훈련되지 않은 파라미터(Non-trainable params)는 0으로 출력되어 있다. 간혹 경사 하강법으로 훈련되지 않는 파라미터를 가진 층이 있는데 이런 층의 파라미터 개수가 여기에 나타나게 된다.

 

층을 추가하는 다른 방법

Seqeuntial 클래스에 층을 추가하는 다른 방법도 존재한다.

앞에서는 Dense 클래스의 객체 dense1, dense2를 만들어 Sequential 클래스에 전달하였다. 다음처럼 Sequential 클래스의 생성자 안에서 바로 Dense 클래스의 객체를 만드는 경우가 많다.

model = keras.Sequential([keras.layers.Dense(100, activation = 'sigmoid', input_shape = (784,), name = 'hidden'),
                          keras.layers.Dense(10, activation = 'softmax', name = 'output')], name = '패션 MNIST 모델')

 

 

model.summary()

이 방법은 편리하지만 아주 많은 층을 추가하려면 Sequential 클래스 생성자가 매우 길어진다. 또 조건에 따라 층을 추가할 수 없다는 단점을 갖는다. Sequential 클래스에서 층을 추가할 때 가장 널리 사용하는 방법은 모델의 add() 메서드이다.

 

이 방법은 다음과 같이 객체의 add()메서드를 호출하여 층을 추가한다.

model = keras.Sequential()
model.add(keras.layers.Dense(100, activation = 'sigmoid', input_shape = (784,)))
model.add(keras.layers.Dense(10, activation = 'softmax'))

Dense 클래스의 객체를 따로 변수에 담지 않고 바로 add() 메서드로 전달하여 한눈에 추가되는 층을 볼 수 있고 프로그램 실행 시 동적으로 층을 선택하여 추가할 수 있다.

 

모델을 훈련하기 위해 compile()메서드를 수행한다.

model.compile(loss = 'sparse_categorical_crossentropy', metrics = 'accuracy')
model.fit(train_scaled, train_target, epochs = 5)

훈련 세트에 대한 성능을 보면 추가된 층이 성능을 향상시켰다는 것을 확인할 수 있다. 인공 신경망에 몇 개의 층을 추가하더라도 compile() 메서드와 fit() 메서드의 사용법은 동일하다. 

 

렐루 함수

초창기 인공 신경망의 은닉층에 많이 사용된 활성화 함수는 시그모이드 함수였다. 하지만 이 함수는 오른쪽과 왼쪽 끝으로 갈수록 그래프가 누워있기 때문에 올바른 출력을 만드는데 신속하게 대응하지 못한다. 특히 층이 많은 심층 신경망일수록 그 효과가 누적되어 학습을 더 어렵게 한다.

이를 개선하기 위해 렐루 함수가 제안되었다. 렐루함수는 입력이 양수일 경우 마치 활성화 함수가 없는 것처럼 그냥 입력을 통과시키고 음수일 경우에는 0으로 만든다.

 

패션 MNIST 데이터는 28 * 28 크기이기 때문에 인공 신경망에 주입하기 위해 넘파이 배열의 reshape() 메서드를 사용해 1차원으로 펼쳤다. 직접 1차원으로 펼쳐도 되지만 케라스에서는 이를 위한 Flatten층을 제공한다.

 

첫 번째 등장하는 Flatten 클래스에 포함된 모델 파라미터는 0개이다. 케라스의 Flatten층을 신경망 모델에 추가하면 입력값의 차원을 짐작할 수 있다. 

입력 데이터에 대한 전처리 과정을 가능한 모델에 포함시키는 것이 케라스 API의 철학 중 하나이다.

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
train_scaled = train_input / 255.0
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size = 0.2, random_state = 42)

 

모델을 컴파일하고 훈련하는 것은 다음 코드처럼 이전과 동일하다.

model.compile(loss = 'sparse_categorical_crossentropy', metrics = 'accuracy')
model.fit(train_scaled, train_target, epochs = 5)

 

시그모이드 함수를 사용했을 떄와 비교하면 성능이 조금 향상되었다. 크지 않지만 렐루 함수의 효과가 존재함을 확인할 수 있다.

 

검증 세트에서의 성능도 아래 코드를 통해 확인 가능하다.

model.evaluate(val_scaled, val_target)

 

은닉층을 추가하지 않은 경우(0.8439)에 비해 몇 퍼센트 성능이 향상되었다.

 

옵티마이저

하이퍼파라미터는 모델이 학습하지 않아 사람이 지정해주어야 하는 파라미터이다.

추가할 은닉층의 개수는 모델이 학습하는 것이 아니라 우리가 지정해 주어야 할 하이퍼파라미터이다. 이와 더불어 뉴런 개수, 활성화 함수, 층의 종류, 배치 사이즈 매개변수, 에포크 매개변수도 하이퍼파라미터이다.

 

compile() 메서드에서는 케라스의 기본 경사 하강법 알고리즘인 RMSprop을 사용했다. 케라스는 다양한 경사 하강법 알고리즘을 제공한다. 이들을 옵티마이저라고하며 이 또한 하이퍼파라미터이다.

 

SGD 옵티마이저를 사용하려면 compile() 메서드의 optimizer 매개변수를 'sgd'로 지정해야한다.

이 옵티마이저는 tensorflow.okeras.optimizers 패키지 아래 SGD 클래스로 구현되어 있다.

'sgd' 문자열은 이 클래스의 기본 설정 매개변수로 생성한 객체와 동일하다.

 

SGD 외에도 다양한 옵티마지어가 존재한다.

기본 경사 하강법 옵티마이저는 모두 SGD 클래스를 제공한다. SGD 클래스의 momentum 매개변수의 기본값은 0이다. 이를 0보다 큰 값으로 지정하면 마치 이전의 그레이디언트를 가속도처럼 사용하는 모멘텀 최적화를 사용한다. 보통 momentum 매개변수는 0.9 이상을 지정한다.

 

아래와 같이 SGD 클래스의 nesterov 매개변수를 기본값 False에서 True로 바꾸면 네스테로프 모멘텀 최적화(또는 네스테로프 가속 경사)를 사용한다.

sgd = keras.optimizers.SGD(momentum=0.9, nesterov=True)

 

네스테로프 모멘텀은 모멘텀 최적화를 2번 반복하여 구현한다. 대부분의 경우 네스테로프 모멘텀 최적화가 기본 확률적 경사 하강법보다 나은 성능을 제공한다.

 

모델이 최적점에 가까이 갈수록 학습률을 낮출 수 있다. 이렇게 하면 안정적으로 최적점에 수렴할 가능성이 높다. 이런 학습률을 적응적 학습률(adaptive learning rate)이라고 한다. 이런 방식들은 학습률 매개변수를 튜닝하는 수고를 덜 수 있다는 장점을 갖는다.

 

적응적 학습률을 사용하는 대표적인 옵티마이저는 Adagrad와 RMSprop이다.

각각 compile() 메서드의 optimizer 매개변수에 'adagrad'와 'rmsprop'으로 지정할 수 있다. optimizer 매개변수의 기본값이 'rmsprop'이다. 이 두 옵티마이저의 매개변수를 바꾸고 싶다면 SGD와 같이 Adagrad와 RMSprop 클래스 객체를 만들어 사용하면 된다.

 

Adam은 모멘텀 최적화와 RMSprop의 장점을 접목한 것이다. Adam은 RMSprop과 함께 맨처음 시도해 볼 수 있는 좋은 알고리즘이다.

적응적 학습률을 사용하는 이 3개의 클래스는 learning_rate 매개변수의 기본값으로 모두 0.001을 사용한다.

 

Adam 클래스의 매개변수 기본값을 사용해 패션 MNIST 모델을 훈련하는 코드는 아래와 같다.

먼저 모델을 생성한다.

model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape = (28, 28)))
model.add(keras.layers.Dense(100, activation = 'relu'))
model.add(keras.layers.Dense(10, activation = 'softmax'))

 

compile() 메서드의 optimizer를 'adam'으로 설정하고 5번의 에포크 동안 훈련한다.

model.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics = 'accuracy')
model.fit(train_scaled, train_target, epochs = 5)

기본 RMSprop을 사용했ㅇ르 때와 거의 같은 성능을 보임을 알 수 있다.

 

검증 세트에서의 성능도 확인해보자.

model.evaluate(val_scaled, val_target)

환경마다 조금씩 차이가 있을 수 있지만 여기서는 기본 RMSprop보다 조금 나은 성능임을 확인할 수 있었다.

'AI > 혼자공부하는머신러닝딥러닝' 카테고리의 다른 글

[DL] 07-3 신경망 모델 훈련  (0) 2024.02.22
[DL] 07-1 인공 신경망  (0) 2024.02.21
[ML] 06-3 주성분 분석  (1) 2024.01.26
[ML] 06-2 k-평균  (1) 2024.01.23
[ML] 06-1 군집 알고리즘  (1) 2024.01.23