본문 바로가기
Data Science/딥러닝

3장 신경망 시작하기1

by 코딩은 잼있어 2020. 8. 31.
728x90

3. 신경망 시작하기

이번 포스트는 신경망이 가장 많이 사용되는 세 종류 문제인 이진 분류, 다중 분류, 회귀에 배운 것들을 적용해봅시다.

3.1 신경망의 구조

신경망 훈련에는 다음 요소들이 관련되있습니다.

  • 네트워크(또는 모델)를 구성하는
  • 입력 데이터와 그에 상응하는 타깃
  • 학습에 사용할 피드백 신호를 정의하는 손실함수
  • 학습진행 방식을 결정하는 옵티마이저

아래는 4가지 요소들의 관계를 나타낸 그림입니다.

img

3.1.1 층 : 딥러닝의 구성단위

층은 대부분 가중치라는 층의 상태를 가집니다. 가중치는 경사하강법에 의해 학습되는 하나 이상의 텐서이며 여기에 네트워크가 학습한 지식이 담겨있습니다.

층마다 적절한 텐서 포멧과 데이터 처리 방식이 다릅니다.

2D 텐서(samples, features)가 저장된 벡터데이터 : 완전 연결층(fully connected layer)이나 밀집층(dense layer)

3D 텐서(samples, timesteps, features)가 저장된 시퀀스 데이터 : LSTM과 같은 순환층(recurrent layer)

4D텐서로 저장되 있는 이미지 데이터 : 2D합성곱층(convolution layer)

from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(32, input_shape=(784,)))    # input = 784차원, output = 32차원
model.add(layers.Dense(10))                        # input = 32차원, output = 10차원

첫 번째 층은 차원이 784인 2D 텐서만 입력으로 받는 층을 만들었습니다. (배치차원이 0번째 축은 지정하기 않기 때문에 어떤 배치 크기도 입력으로 받을 수 있습니다.) 이 층은 첫번째 차원 크기가 32로 변환된 텐서를 출력할것입니다.

두 번째 층은 input_shape를 지정하지 않았습니다. 앞 층의 출력크기를 입력 크기로 자동으로 채택합니다.

3.1.2 모델 : 층의 네트워크

네트워크 구조는 가설 공간(hypothesis space)을 정의합니다. 네트워크 구조를 선택함으로써 가설 공간(가능성이 있는 공간)을 입력 데이터에서 출력 데이터로 매핑하는 일련의 텐서 연산으로 제한하게 됩니다. 이러한 가설 공간에서 우리가 해야 할것은 오차를 최소로 만드는 가중치를 찾는것입니다.

쉽게 말해서 최적의 결과를 목표로 가능성있는 공간을찾아 가중치를 업데이트 해나가는것

3.1.3 손실함수와 옵티마이저 : 학습과정을 조절하는 열쇠

손실함수 (loss fuction), 목적함수(objective function) : 훈련하는 동안 최소화될 값입니다. 손실함수가 작을수록 학습 결과 성공의 좌표가 됩니다. 대표적인 손실함수로서 평균제곱오차와 cross entropy가 있습니다.

옵티마이저(optimizer) : 손실함수를 기반으로 네트워크가 어떻게 갱신해나갈지 결정합니다. 실제 가중치 변화를 주어 파라미터를 갱신해나간다고 생각하면 됩니다. ex) 경사하강법

문제에 맞게 올바른 목적함수를 선택하는 것은 아주 중요합니다. 네트워크가 손실을 최소화 하기 위해 편법을 사용할 수 있기 때문입니다. 그래서 문제별로 올바른 손실함수(목적함수)를 선택하는 간단한 지침이 있습니다.

  • 2개의 클래스가 있는 분류문제 : 이진 크로스엔트로피(binary crossentropy)
  • 여러개의 클래스가 있는 분류문제 : 범주형 크로스엔트로피(categorical crossentropy)
  • 회귀문제 : 평균 제곱 오차
  • 시퀀스 학습문제 : CTC(Connection Temporal Classification)

3.2 케라스 소개

pass

3.3 딥러닝 컴퓨터 셋팅

pass

3.4 영화 리뷰 문제 : 이진 분류 문제 예제

이번 예제에서는 리뷰 텍스트 기반으로 영화 리뷰를 긍정 or 부정으로 분류하는 방법을 배우겠습니다.

3.4.1 IMDB 데이터셋

리뷰 5만개로 이루어진 IMDB 데이터셋은 훈련 데이터 2만5천개와 테스트 2만5천개로 나눠어 있고 각각 50% 씩 부정리뷰, 긍정리뷰로 이루어져 있습니다.

# IMDB 데이터셋 로드
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

3.4.2 데이터 준비

딥러닝 학습에는 숫자 리스트를 주입할수 없습니다. imdb데이터셋의 경우 숫자리스트로 구성되어 있기 때문에 딥러닝 학습을 위해 텐서로 바꿔줘야합니다.

대표적인 방법으로 숫자 리스트를 one-hot encoding하여 0과 1의 벡터로 변환합니다.

예를 들어 [3, 5]를 인덱스 3과 5의 위치는 1이고 그 외는 모두 0인 N차원의 벡터로 변환합니다.

# 1.정수 시퀀스를 이진 행렬로 인코딩하기
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
    # 크기가 (len(sequences), dimension))이고 모든 원소가 0인 행렬을 만듭니다
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.  # results[i]에서 특정 인덱스의 위치를 1로 만듭니다
    return results

# 훈련 데이터를 벡터로 변환합니다
x_train = vectorize_sequences(train_data)
# 테스트 데이터를 벡터로 변환합니다
x_test = vectorize_sequences(test_data)

# 레이블을 쉽게 벡터로 바꿀 수 있습니다.
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

3.4.3 신경망 모델 만들기

이런 문제에 잘 동작하는 네트워크의 종류는 relu 활성화 함수를 사용한 완전 연결층 (즉, layers.Dense(16, activation='relu'))을 쌓은것 입니다.

여기서 Dense 층에 전달한 매개변수 16은 은닉유닛의 갯수입니다.

딥러닝 구조에는 입력층, 은닉층, 출력층이 존재합니다. 입력층과 출력층은 각각 입력을 받고 출력을 내보내는 역할을 하고 은닉층의 경우 각 층사이마다 존재하는 W(가중치)를 통해 강화 or 약화시키는 역할을 하며 최종적으로는 오차가 최소 값이되도록 갱신해나갑니다.

일반적으로 은닉층의 갯수가 많을수록 정확도가 높아지고 복잡한 표현을 학습할 수 있지만 계산 비용이 커지고 잘못된 패턴학습을 할수도 있습니다.

그래서 Dense 층을 쌓을때는 항상 두가지 고려해야할 사항이 있습니다.

  1. 얼마나 많은 층을 사용할 것인가?
  2. 각 층에 얼마나 많은 은닉 유닛을 둘 것인가 ?

일단 이 예제에선 아래 구조로 진행합니다.

  1. 16개의 은닉 유닛을 가진 2개의 은닉층
  2. 현재 리뷰의 감정을 스칼라 값의 예측으로 출력하는 세 번째 출력
model = modelsSequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

코드를 보면 알수 있듯이 중간의 2개의 은닉층의 활성화 함수는 relu를 사용하고, 출력층의 경우에는 확률출력을 위해 sigmiod함수를 사용합니다.

활성화함수는 무엇이고? 왜 필요할까 ??

relu와 같은 활성화 함수(또는 비선형성)가 없다면 Dense층은 선형적인 연산인 접곰과 덧셈으로 구성됩니다. (output = W*x + b)

그러므로 활성화 함수가 없다면 입력에 대한 선형변환만을 학습하게 되고, 층을 깊게 쌓아도 선형변환의 집합일 뿐입니다. 즉 층을 추가해도 가설공간은 확장되지 않습니다.

가설공간을 풍부하게 만들어 층을 깊게 만드는 장점을 살리기 위해선 비선형성 또는 활성화함수를 추가하여 층의 선형성을 방지해야하만 합니다. 그리고 relu는 딥러닝에서 가장 인기있는 활성화함수 입니다.

relu 함수

relu는 음수를 0으로 만드는 함수입니다

img

sigmoid 함수

0과 1사이를 출력하는 함수로 1에 가까울수록 긍정일 가능성이 높음을 의미합니다.

img

3.4.4 훈련 및 검증

훈련 검증을 위한 데이터 세트를 만들자

x_val = x_train[:10000]            
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

해당 문제는 이진 분류문제니까 손실함수로 binary_crossentropy 손실이 적합합니다. 만약 다중 모델로서 확률을 출력하는 모델을 만들때는 cross_entropy가 적합합니다.

# 모델 훈련하기
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])

history = model.fit(partial_x_train,                # 훈련을 위한 데이터
                    partial_y_train,
                    epochs=20,        
                    batch_size=512,
                    validation_data=(x_val, y_val))    # 검증을 위한 데이터

모델을 512개의 샘플씩 미니 배치를 만든뒤, 20번의 훈련을 반복합니다.

img

위 그래프를 보면 알수있듯이 약 4번째 에포크부터 검증이 훈련결과를 역전하는걸 보실수 있습니다.

훈련데이터의 손실은 0, 정확도는 1에 수렴하지만 검증 데이터의 경우 4번째 에포크부터 역전되는 결과를 보실수 있습니다.

이 경우에는 훈련세트에서는 잘 동작하지만 처음 보는 데이터에서 잘 작동하지 않는경우인데, 이를 과대적합(overfitting)되었다고 합니다.

과대적합의 경우 모델에 훈련데이터가 과도하게 최적화되어 훈련데이터에 특화된 표현을 학습하므로 훈련 세트 이외의 데이터에는 일반화하지 못합니다.

그래서 해당 데이터의 경우 네번의 에포크 동안만 훈련하는게 더 좋습니다.

3.4.5 훈련된 모델로 새로운 데이터 예측하기

위에서 발생한 과대 적합을 방지하기 위해 4번의 에포크만 진행합니다. 또한 훈련데이터를 훈련과 검증을 나누지 않고 훈련데이터 전부를 훈련시킨 모델을 활용하여 새로운 데이터를 예측해봅시다.

from keras.datasets import imdb
import numpy as np
from keras import models
from keras import layers

# 1. 학습을 위한 입력형식만들기
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

# 정수 시퀀스를 이진행렬로 변환
def vectorize_sequences(sequences, dimension=10000):
    # 크기가 (len(sequences), dimension))이고 모든 원소가 0인 행렬을 만듭니다
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.  # results[i]에서 특정 인덱스의 위치를 1로 만듭니다
    return results

# 훈련 데이터를 벡터로 변환합니다
x_train = vectorize_sequences(train_data)
# 테스트 데이터를 벡터로 변환합니다
x_test = vectorize_sequences(test_data)

# 레이블을 벡터로 바꿉니다
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

# 2. 모델 만들기
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)

results = model.evaluate(x_test, y_test)
print("results ==> ", results)
print("predict ==> ",model.predict(x_test))
# 훈련 결과 88%의 정확도를 달성했습니다.
results ==>  [0.2935598969650269, 0.8840000033378601]
# 1에 가까울수록 긍정일 확률을 예측할수 있습니다.
predict ==>  [[0.22437403]
             [0.99993825]
             [0.9492523 ]
             ...
             [0.12415996]
             [0.1043655 ]
             [0.6680283 ]]
728x90