본문 바로가기
머신러닝

파이썬과 케라스를 이용한 간단한 딥러닝 +

by Seungyoon1786 2024. 11. 28.

학교 수업에서 진행한 머신러닝 강의 복습 및 리뷰. 우선 딥러닝의 성능 비교를 위해서 회귀 모형을 만들고, 깊이를 추가해 나가면서 그에 따른 성능(mse, loss)을 비교해 보겠다.

 

01_01_Simple_regression_simulation.ipynb
0.25MB

 

 

 


1. 회귀 모형

성능의 비교를 위해서 우선 간단한 회귀 모형을 작성한다. 추가로, 분석에 사용할 데이터 셋도 임의로 작성한다.

# 0. 사용할 패키지 불러오기
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error
import random
import tensorflow as tf
import os

# 난수 고정
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
os.environ['TF_DETERMINISTIC_OPS'] = '1'

# 1. 데이터셋 생성하기
x_train = np.random.random((1000, 1))
y_train = x_train * 2 + np.random.random((1000, 1)) / 3.0 # Y = 2X + ~(error값)

x_test = np.random.random((100, 1))
y_test = x_test * 2 + np.random.random((100, 1)) / 3.0

 

훈련 데이터를 Y = 2X + (오차값)의 형태로 생성한 이유는 딥러닝 모델을 포함한 회귀 모델이 명확한 선형 관계를 가진 데이터에서 얼마나 잘 학습하고 예측하는지를 평가하기 위함이다. + 노이즈(error값)으로 무작위성 추가. 

 

샘플 데이터가 선형이라서 선형 회귀가 높을 것으로 예상된다. -> 딥러닝 학습이 선형 회귀와 유사하면 성공!

 

x_train = x_train.reshape(1000,)
y_train = y_train.reshape(1000,)
x_test = x_test.reshape(100,)
y_test = y_test.reshape(100,)

x_train.shape
x_train

 

선형 회귀를 진행 하기 위해 1차원으로 데이터 형식을 바꿔준다. ( p.cov, np.var, np.mean 은 1차원 데이터를 받음, 2차원으로 회귀 시 추가 코드 필요)

# 2. 모델 구성하기 (회귀식의 추정)
w = np.cov(x_train, y_train, bias=False)[0,1] / np.var(x_train)
b = np.average(y_train) - w * np.average(x_train)

print (w, b) # y = b + wx

 

회귀 계수 (기울기, 가중치): 2.011783420867384 절편: 0.1632288690923035

 

Y=2.0117X+0.1632로 회귀식을 쓸 수 있다. 데이터를 생성할 때처럼 2와 가까운 기울기가 나타난다.

 

# 3. 모델 평가하기
y_predict = w * x_test + b
mse = mean_squared_error(y_test, y_predict)
print('mse : ' + str(mse))

 

평균 제곱 오차 (MSE): 0.00858277851953264 

실제 값과 예측값 사이의 평균제곱오차 값이다. 낮을 수록 예측을 잘한단 뜻인데, 0.01로 좋은 성능을 보여준다. 그렇다면, 딥러닝을 어떤 성능을 보여줄 것 인가?

2. 딥러닝의 경우

# 0. 사용할 패키지 불러오기
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
import random

# 2. 모델 구성하기
model = Sequential()

model.add(Dense(1, input_dim=1)

# 3. 모델 학습과정 설정하기
model.compile(optimizer='sgd', loss='mse')
model.summary()

 

Sequential()을 사용하여 모델을 초기화한후, Dense 레이어를 추가한다.

  • units=1: 출력 뉴런의 수를 1로 설정
  • input_dim=1: 입력 특성의 수를 1로 설정
  • activation='relu': 활성화 함수로 ReLU를 사용 - 범주형일 경우 출력을 시그모이드로 사용하면 됨
  • optimizer='sgd': 확률적 경사 하강법(SGD)을 옵티마이저로 사용
  • loss='mse': 손실 함수로 평균 제곱 오차(MSE)를 사용

간단하게 나타내 보자면, X1 -(가중치 w)-> Y1 의 형태이다. 두 레이어 사이의 파라미터 개수는  (이전 레이어 길이 + 현재 필터 넓이)*이전 필터 크기 이다. 그러니까 (1+1)*1 의 형태로 나타낼 수 있으며 2개의 파라미터 개수를 구할 수있다. 요약 본으로 정답을 확인해본다.

Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense (Dense)                        │ (None, 1)                   │               2 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 2 (8.00 B)
 Trainable params: 2 (8.00 B)
 Non-trainable params: 0 (0.00 B)

 

이제 모델을 학습시켜 보겠다.

# 4. 모델 학습시키기
hist = model.fit(x_train, y_train, epochs=50, batch_size=64) # epochs은 몇개씩 짜를지,
w2, b2 = model.get_weights()
print (w2, b2)

 

해당 과정시 각 Epoch마다 손실 함수(loss)값이 출력되는데 손실 값이 감소하거나 안정화되면 모델이 수렴하고 있음을 나타낸다. 

 

이때 epochs란, 전체 데이터셋을 모델이 완전히 한 번 학습하는 주기를 뜻한다. 현재 50으로 설정되있으니, 전체 데이터셋을 50번 반복하여 학습한다. 너무 많은 epoch 설정은 과적합을, 너무 작으면 충분히 학습되지 않을 수 있음을 유의하자. 

잘 그린 그림

 

 이때 한번 실행하는 데이터 개수를 batch size, 그럼 1000개의 데이터가 있으니까 1000/64를 해서 16의 배치를 가진다. 실행 결과를 보면 쉽게 이해가능하다. 이때 loss값이 줄어들지 않고 들쭉날쭉한 것을 보아 모델이 제대로 학습되지 않음을 알 수 있다.

 

 배치 크기도 설정에 장단점이 있는데, 너무 작게 잡으면 학습이 세밀하게 이루어지고 메모리 사용량이 작으나, 너무 오래걸리고 노이즈에 민감하게 반응할 수 있다(과적합 발생 가능). 너무 크면 빠른 속도와 많은 샘플을 사용해 안정적이나, 메모리 사용이 늘고 과소적합이 발생가능하다. 

Epoch 1/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3022
Epoch 2/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.1533  
Epoch 3/50

(생략)

16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0136 
Epoch 48/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0134 
Epoch 49/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0133 
Epoch 50/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0131 
[[1.8283669]] [0.25974143]

 

Y=1.830905⋅X+0.25841138의 식을 가진다고 볼 수 있다.

 

# 5. 학습과정 살펴보기
%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(hist.history['loss'])
plt.ylim(0.0, 1.5)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()

# 6. 모델 평가하기
loss1 = model.evaluate(x_test, y_test, batch_size=32)
print('loss : ' + str(loss1))

 

일때 mse값으로 loss : 0.011221873573958874를 가진다. 해당 딥러닝 모델은 은닉층도 없고, 활성함수 설정도 하지 않은 상태이다.

# 2. 모델 구성하기(은닉층 1개 포함)
model = Sequential()
model.add(Dense(64, input_dim=1, activation='relu'))
model.add(Dense(1))

# 3. 모델 학습과정 설정하기
model.compile(optimizer='rmsprop', loss='mse')

model.summary()

 

이번에는 은닉층, hidden layer를 하나 추가 해보겠다. model.add(Dense(64, input_dim=1, activation='relu')) 부분이 추가된 은닉층이며, 64개의 노드를 가진다. 1개의 차원, 활성함수는 relu를 사용하여 비선형 변환을 수행, 복잡한 패턴을 학습하도록 한다.

 

model.add(Dense(1))는 출력층이다. 뉴런의 개수가 1개이다. 파라미터 개수를 구해보자면, (1+1)*64 + (64+1)*1 이므로 128 + 65 = 193개의 파라미터수를 가진다.

 

Model: "sequential_10"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense_19 (Dense)                     │ (None, 64)                  │             128 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_20 (Dense)                     │ (None, 1)                   │              65 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 193 (772.00 B)
 Trainable params: 193 (772.00 B)
 Non-trainable params: 0 (0.00 B)

 

학습을 시켜보면, 코드와 결과는 다음과 같다.

# 4. 모델 학습시키기
hist = model.fit(x_train, y_train, epochs=50, batch_size=64)

# 5. 학습과정 살펴보기
%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(hist.history['loss'])
plt.ylim(0.0, 1.5)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()

# 6. 모델 평가하기
loss2 = model.evaluate(x_test, y_test, batch_size=32)
print('loss : ' + str(loss2))

'''Epoch 1/50
Epoch 1/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 17ms/step - loss: 1.6683
Epoch 2/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 1.1339  
Epoch 3/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.7986 
Epoch 4/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.5357 

...

Epoch 16/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0099 
Epoch 17/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0098 
Epoch 18/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0098 
Epoch 19/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0098 

loss : 0.008531514555215836'''

 

epoch 가 수행됨에 따라 훈련 손실 값이 감소하여 Epoch 17 이후로 0.0098 수준에서 수렴하게 된다. 0.0085~ 부분은 테스트 손실 값(mse)이다.

손실 값은 초기에 급격히 감소한후, 일정 수준에서 수렴하는 형태를 보인다. 처음 1.6에서 점점 줄어들어 0.0098까지 수렴한다.

 

그럼 동일한 은닉층을 하나 더 만들면 어떨까?

# 2. 모델 구성하기(은닉층 2개 포함)
model = Sequential()
model.add(Dense(64, input_dim=1, activation='relu'))
model.add(Dense(64, activation='relu')) # relu로 비선형변환
model.add(Dense(1))

# 3. 모델 학습과정 설정하기
model.compile(optimizer='sgd', loss='mse')

model.summary()

'''Model: "sequential_5"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense_9 (Dense)                      │ (None, 64)                  │             128 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_10 (Dense)                     │ (None, 64)                  │           4,160 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_11 (Dense)                     │ (None, 1)                   │              65 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 4,353 (17.00 KB)
 Trainable params: 4,353 (17.00 KB)
 Non-trainable params: 0 (0.00 B)'''

 

 은닉층을 하나더 추가하였다. 이전같은 첫번째 레이어를 가지며 (128개의 파라미터), 2번째 Dense 레이어는  입력 차원이 64, 출력 차원이 64이기에 (64+1)*64 =4160이다. 출력 차원이 64개의 파라미터를 가지니, 총 4,353개의 파라미터를 가진다!

 

각층을 정리하면 다음과 같다.

a. 은닉층 1 (dense_9)

  • 64개의 뉴런을 포함한 은닉층으로, 입력 차원은 1이며 ReLU 활성화 함수를 사용해 비선형 변환을 수행.

b. 은닉층 2 (dense_10)

  • 또 다른 64개의 뉴런을 포함하며, 이전 은닉층의 출력(64 차원)을 입력으로 받음.
  • ReLU 활성화 함수를 사용해 추가적인 비선형 변환을 수행.

c. 출력층 (dense_11)

  • 출력 뉴런 1개를 포함하며, 최종 예측 값을 반환.
  • 활성화 함수가 지정되지 않았으므로, 기본적으로 선형 활성화 함수가 사용됨.

이제 마지막으로 모델을 학습 시키고 평가해보자.

# 4. 모델 학습시키기
hist = model.fit(x_train, y_train, epochs=50, batch_size=64)

# 5. 학습과정 살펴보기
%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(hist.history['loss'])
plt.ylim(0.0, 1.5)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()

# 6. 모델 평가하기
loss3 = model.evaluate(x_test, y_test, batch_size=32)
print('loss : ' + str(loss3))

'''Epoch 1/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 28ms/step - loss: 0.9047
Epoch 2/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.1672  
Epoch 3/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0974  
Epoch 4/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - loss: 0.0793  

(생략)

Epoch 38/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0099 
Epoch 39/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0098 
Epoch 40/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0098 
Epoch 41/50
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 0.0098 ..

loss : 0.008721329271793365'''

 

점차 안정되가기 시작하더니 39번째에 이르러서 0.098로 수렴하였다. 이때 더 낮아지지 않는 이유는 데이터의 노이즈(오차 값)의 수준 때문으로 완전히 제거할 수 없기 때문이다. 테스트 손실 값이 더 낮을 걸 볼때 일반화 성능이 높게 나타난다고 볼 수 있다.

정리하자면 다음과 같다.

print('mse : ' + str(mse)) # 회귀 모형
print('loss1 : ' + str(loss1)) #은닉 X
print('loss2 : ' + str(loss2)) #은닉 1개
print('loss3 : ' + str(loss3)) # 은닉층 2개

'''mse : 0.00858277851953264
loss1 : 0.012480237521231174
loss2 : 0.008531514555215836
loss3 : 0.008721329271793365'''

 

은닉층을 적절히 사용하니 선형회귀와 비슷한 결과가 도출 되었다. 그리고 너무 복잡한 모형은 오히려 좋은 성능을 보장 하지 않는다는 것도 알 수 있다.