본문 바로가기
Programming/Python

[DL] tensorflow, Deep Neural Network(DNN)

[deep learning 개괄 설명]

Deep Learning(딥러닝): 생물학적 신경망을 추상화한 인공신경망.

 

신호(미지수) -> 전달 통로 -> 활성화 함수(임계값 도달) -> 0 or 1 출력

input layer -> hidden layer(생략 可)  -> output layer

 

인공신경망은 가중치 찾는 것이 중요!

why?) 인공신경망 학습 = 가충치 찾는 과정::  환경변수 지정 -> 신경망 실행 -> 예측값, 실제값 비교 ->  가중치 값 수정

                                                                                                                     ↘반복 구간

1) 환경변수 지정

Hyper parameter 조정 (사람이 지정해줘야 하는 값.)

epoch: 학습 데이터 소모 단위

batch size: 하나 batch마다 주는 data sample size   

(성능과 상관관계X but 메모리 한계와 속도 저하 때문에 한번 epoch에서 ∀ data 학습 불가)

 

2) 신경망 실행 -> 3) 예측값(y_hat), 실제값(y) 비교 -> 4) 가중치  delta 값을 조정하여 근사

 

 

Activation Function(활성화 함수) examples

ex) Sigmoid Function.   -> 로지스틱 회귀 말고 인공지능 망으로 분류 可  but 기울기 소모 문제 있음(심화내용)

ex) Step Function(계단 함수)

ex) ReLu Function(렐루 함수): 학습속도 빠르고, 잘 되어 가장 많이 사용

ex) Softmax Function: 출력값으로 벡터값 얻고싶을 때

ex) Loss Functon(손실 함수): MSE값을 줄여나가는 것

       = Cost Function(비용함수)    가중치에 따라 MSE 평가. 신경망 성능 '나쁨' 나타내는 지표

현 신경망이 훈련 데이터 얼마나 잘 처리하지 못하는지를 나타내는 지표

Cross Entropy Error(CEE; 교차 엔트로피 오차): 특정 클래스에 속할 정보량 확률 합. 범주형 데이터 분류할 때 주로 사용.

총합은 1 

미분: 매개변수 값 아주 조금 변경시 손실함수 변화 양상

 -> GDA(경사하강법):가중치에 대한 손실함수 최솟값 위치 찾기위해 함수 미분해서 최적화 -> coss(cost) 최소화

 

Learning Rate(학습률): GDA에서 손실함수 최솟값 위치 찾기위해 이동한 거리 비율

매우 크게 설정 ->  최솟값 못찾고 explode(발산) or vanish(손실). or local minimum에 도달 -> local minima problem

매우 작게 설정 ->  taking time too much and 도달못하고 미완성 종료

 

 

Optimizer: 최솟값 찾기위한 다양한 GDA

(확률적 경사하강법, Momentum, Adagrad;Adaptive Gradient, Adam; momentum + Adgrad)

 

 

역전파 (Backpropagation): residual error를 후방(backward)으로 다시 보내서 최적 weight, bias 학습 기법

 


포스트 배경지식: 로지스틱 회귀, 경사하강법

이번 포스트는 Deep Learning의 핵심적인 기본 내용을 담고 있다. 제대로 이해해야한다!

 

 

pip install tensorflow #설치부터 합시다.
from tensorflow import keras  # 고수준 API

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
# 28 x 28 x 60000 사진 데이터 MNIST 로드. 내장된 학습용 데이터
# load_data() <- train, target 자동으로 나눠줌

print(train_input.shape, train_target.shape)
print(test_input.shape, test_target.shape)

(60000, 28, 28) (60000,)      <- 사진 하나가 28x28 크기이고 이게 60k개 있는 형태의 데이터이다. 그래서 3차원.

(10000, 28, 28) (10000,)

train과 test data size 확인

 

 

import matplotlib.pyplot as plt

fig, axs = plt.subplots(1, 10, figsize=(10,10))
for i in range(10):
    axs[i].imshow(train_input[i], cmap='gray_r')
    axs[i].axis('off')
plt.show()

 

매트롤립으로 사진 10개 샘플 호출

 

print([train_target[i] for i in range(10)])

[9, 0, 0, 3, 0, 2, 7, 2, 5, 5]   <- 각 사진에 배정된 숫자 리스트 출력

 

기준: 패션 item

0: 티셔츠

1: 바지

2: 스웨처

3: 드레스

4: 코트

5: 샌달

6: 셔츠

7: 스니커즈

8: 가방

9: 앵클부츠

 

 

import numpy as np

print(np.unique(train_target, return_counts=True))

(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8), array([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000],
      dtype=int64))

 

0~9까지 각각 6000개씩 들어있다.

 

 

 

 

로지스틱 회귀로 패션 아이템 분류하기

샘플이 60k개이므로 한번에 훈련하기보다 샘플 하나씩 꺼내서 훈련하는 방법이 더 효율적이다.

-> 확률적 경사 하강법 사용 (SGD Classfier)

 

SGD classfier는 2차원 입력을 다루지 못하므로 1차원으로 변환해준다.

train_scaled = train_input / 255.0  # 이미지 픽셀값 0~255범위를 0~1사이 값으로 스케일링
train_scaled = train_scaled.reshape(-1, 28*28) # 28*28 설정시 sample개수 유지하고 1차원으로 합쳐짐
print(train_scaled.shape)                      # 즉, 사진 크기를 넣어야 1차원으로 합쳐진다.

(60000, 784)   <- 784 픽셀로 이뤄진 60k samples. 

 

 

SGDClassifer, cross_validate 함수로 교차검능 성능 확인

from sklearn.model_selection import cross_validate
from sklearn.linear_model import SGDClassifier

sc = SGDClassifier(loss='log_loss', max_iter=5, random_state=42)

scores = cross_validate(sc, train_scaled, train_target, n_jobs=-1)
print(np.mean(scores['test_score']))

0.7899375

 

반복횟수(max_iter)는 여기서 5로 지정했다. 9나 20을 넣어도 성능이 크게 다르지 않다.

 

 

 

 

 

 

딥러닝 모형으로 패션 아이템 분류하기

위에서는 교차검증을 사용했지만 딥러닝은 교차검증 잘 사용않고 검증세트 별도로 덜어내어 사용한다.

why?) data set is huge enough -> 검증 점수 안정적.   & 교차검증 수행 훈련시간 너무 걸림. (며칠 걸릴 수 있음)

import tensorflow as tf
from sklearn.model_selection import train_test_split

train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42) #데이터 20%를 검증으로 분리.
    
print(train_scaled.shape, train_target.shape)  # 훈련세트
print(val_scaled.shape, val_target.shape)  # 검증세트

(48000, 784) (48000,)

(12000, 784) (12000,)

 

훈련세트로 모형만들고 검증세트로 훈련모델 평가하자

 

밀집층(dense layer) 형성을 통해 데이터와 10개의 뉴런을 Dense로연결해야한다. (784개가 10개의 뉴런에 연결되어야 함)

Dense(뉴런개수, 뉴런 출력에 적용할 활성화 함수, 입력크기)

dense = keras.layers.Dense(10, activation='softmax', input_shape=(784,))

뉴런 개수 = 10  <- 10개의 패션 item 분류하는 상황이기 때문

함수 = 'softmax' <- 뉴런에 출력되는 값을 확률로 바꾸기 위함. 다중분류에 적합. 이진분류는 sigmoid 적용

 (!) 분류가 아닌 회귀문제인 경우, 아무 함수도 지정하지 않는다.(출력이 임의의 어떤 숫자이기 때문이다.)

input_shape = (784,)   <- 입력값 크기. 10개 뉴런이 각각 몇 개의 입력을 받는지 튜플로 지정. 784개 픽셀 입력될 것.

                                  첫 dense는 필수입력!

 

 

-> 이게 신경망 층을 만든 것이다. 이제 이 dense layer를 지닌 deep learning(신경망) model을 만들자. -> Sequential()

model = keras.Sequential(dense)

model: 신경망 모델

입력값 dense: 밀집층 입력

 

 

 

Keras model 훈련 전, Loss function(손실 함수)설정을 해야한다.   -> model.compile()로 수행

다중분류 손실함수를 설정.

model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

이진분류 loss: binary_crossentropy

다중분류 loss: categorical_crossentropy  <- 현재 이 예제가 하고 있는 것

sparse가 붙은 이유: target data가 정수값으로만 구성됨. 실수 단위가 아닌 정수 하나 사용이라서 sparse(희소) 붙음

accuracy가 붙은 이유: Keras model은 훈련 때마다 epoch단위 손실값 출력하는데 정확도 지표도 같이 출력하도록 설정

(lose가 줄고 accuracy가 높으면 좋다.)

 

딥러닝 모델 훈련하기: input, target, epochs 횟수 입력

model.fit(train_scaled, train_target, epochs=5)  # 5번 반복

Epoch 1/5
1500/1500 [==============================] - 1s 771us/step - loss: 0.6070 - accuracy: 0.7950
Epoch 2/5
1500/1500 [==============================] - 1s 755us/step - loss: 0.4749 - accuracy: 0.8398
Epoch 3/5
1500/1500 [==============================] - 1s 750us/step - loss: 0.4508 - accuracy: 0.8481
Epoch 4/5
1500/1500 [==============================] - 1s 765us/step - loss: 0.4368 - accuracy: 0.8524
Epoch 5/5
1500/1500 [==============================] - 1s 868us/step - loss: 0.4305 - accuracy: 0.8546
<keras.src.callbacks.History at 0x1d4194205d0>

 

-> 이 결과는 작동할 때마다 랜덤 훈련으로 작동되어 다르게 나오지만 최적점이 수렴한다면 큰 문제는 아니다.

 

 

 

 

 

훈련이 잘되었으니 검증 data에서 model 성능을 평가하자: evaluate()

model.evaluate(val_scaled, val_target)

375/375 [==============================] - 0s 699us/step - loss: 0.4452 - accuracy: 0.8457
[0.44523027539253235, 0.8457499742507935]  <- 이 부분은 loss, accuracy의 소숫점 detail info를 보여준다.

 

 

점수: 검증set < 훈련set     인것이 일반적

 

83% 정확도 출력!

 

 

 

 

 

 

 

 

 

이 model의 성능을 더 향상시키려면? -> Deep Neural Network(DNN; 심층신경망)을 구축해야한다.

즉, 위 model input layer와 output layer 사이에 layer 추가를 해야하는데 이게 hidden layer(은닉층)이다.

위에서는 dense layer(밀집층) 1개를 형성했었다.

 

 

 

데이터 기본 세팅 remind

from tensorflow import keras

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
#자동으로 train, test set 쪼개기

from sklearn.model_selection import train_test_split

train_scaled = train_input / 255.0          # 255 픽셀을 0과 1사이값으로 표준화 
train_scaled = train_scaled.reshape(-1, 28*28)   # 3차원을 1차원으로 변환

train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42) #검증 세트 위한 분리

 

 

 

hidden layer, output layer 지정

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

dense1: hidden layer이자 100개 뉴런의 dense layer. 뉴런 개수 제약은 없으나 경험적 지식 필요.

단, output 개수보다 적으면 부족한 정보가 전달되므로 항상 output보다는 개수가 많게 설정한다.

 

dense2: output layer. 10개 분류값이므로 10뉴런에 다중분류이므로 softmax 함수 지정.

 

 

 

 

 

DNN만들기: dense여러개이면 리스트로 입력. 단, output layer은 무조건 마지막에 배치!

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

Deep learning의 강력한 장점이 바로 이 부분이다. 층을 추가함으로써 연속적인 학습이 가능하다는 점!

 

 

 

 

 

 

내가 만든 모형의 정보를 보자: summary()

model.summary()

 

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 100)               78500     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1010      
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

 

 

param = parameter

여기서의 78500: 784 x 100(뉴런개수) + 100(각 뉴런의 절편)

Output Shape의 None: sample size가 정의되지 않았기 때문. 100(10)은 뉴런 개수

784 픽셀값이 hidden layer를 통과하면서 100개의 특성으로 압축되었다는 의미

 

output의 param 1010 = 100(앞의 층 100개 뉴런) x 10 (기존 출력 뉴런개수) + 10(각 뉴런의 절편)

 

 Total paramas = sum(Param)

Non-trainable params: 간혹 경사하강법으로 훈련되지 않는 파라미터 층 개수. 여기선 0개.

 

현재까지의 요약: 층을 추가하기위해 Dense에 dense1, dense 2.. 등을 만들어 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 모델')  # name option으로 무엇인지 명료하게 표시한다.

model.summary()

Model: "패션 MNIST 모델"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 hidden (Dense)              (None, 100)               78500     
                                                                 
 output (Dense)              (None, 10)                1010      
                                                                 
=================================================================
Total params: 79510 (310.59 KB)
Trainable params: 79510 (310.59 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________

 

 

수치는 지금까지 한 것과 같다. 이 방법은 보기 편하고 명료하지만 아주 많은 층을 추가하려면 sequential()가 너무 길어짐.

그래서 가장 널리 무난하게 쓰이는 add()를 이용한다.

 

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

model.summary()

결과는 위와 동일하다.

 

 

 

complie()을 통한 loss function 설정 후, fit()으로 훈련: 코드 내용은 처음 것과 동일하다.

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

Epoch 1/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.5721 - accuracy: 0.8061
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.4141 - accuracy: 0.8509
Epoch 3/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3778 - accuracy: 0.8640
Epoch 4/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3550 - accuracy: 0.8719
Epoch 5/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3382 - accuracy: 0.8774
<keras.src.callbacks.History at 0x225e4db6850>

 

 

 

그러나 처음 것보다 loss가 더 줄고 정확도가 더 상승했다.

여기서 층이 아무리 많아도 complie과 fit 코드는 위와 같이 동일하게 작성하여 작동한다.

(Karas API의 장점이다)

 

 

 

 

그런데 초창기 딥러닝 은닉층에 자주 쓰인 활성화 함수 sigmoid는 출력이 느리다는 문제점이 있다.

(그래프가 누워있어서 올바른 출력 신속 대응 불가. 많은 신경망일수록 더더욱 어려움)

 

ReLU(렐루함수) =  max(0,z)             <- 기존 sigomid 문제를 개선하기위해 만들어진 활성화 함수

ReLU (Rectified Linear Unit) = max(0, z)

입력이 양수 -> 그냥 그대로 출력.

입력이 음수 -> 0을 출력

 

이 특성은 image data 처리에 아주 효율적이다.

 

 

 

Flatten: 1차원으로 변환(전처리) 과정을 model에 포함하는 기능 (Keras API의 의도이다.)

이 경우, 앞의 데이터 전처리 과정에서 reshape 부분을 생략 可

 

 

 

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

train_scaled = train_input / 255.0  # reshape scaled 코드부분이 생략되었다!

train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42)

model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28))) # input shape를 제일 처음에 입력!
model.add(keras.layers.Dense(100, activation='relu'))  # ReLU 사용!
model.add(keras.layers.Dense(10, activation='softmax'))   # 나머지 코드가 깔끔해짐

model.summary()

 

Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         
                                                                 
 dense_4 (Dense)             (None, 100)               78500     
                                                                 
 dense_5 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79510 (310.59 KB)
Trainable params: 79510 (310.59 KB)
Non-trainable params: 0 (0.00 Byte)
______________________________________________________________

 

 

Flatten은 모형 성능 기여하는 바가 없으니 param = 0. 그러나 layer 사이에 추가되므로 layer라고 칭해짐

Flatten이 코드에 있으면 입력값의 차원을 짐작할 수 있게한다는 장점 有

 

 

 

 

complie()을 통한 loss function 설정 후, fit()으로 훈련: 코드 내용은 처음 것과 동일하다.

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

Epoch 1/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.5296 - accuracy: 0.8142
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3901 - accuracy: 0.8600
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3542 - accuracy: 0.8730
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3331 - accuracy: 0.8789
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3183 - accuracy: 0.8849
<keras.src.callbacks.History at 0x225e4e91f50>

 

 

sigmoid 사용했을 때보다 lose가 줄고 accuracy가 상승하였다!

 

 

 

 

검증세트에서 확인해보자!

model.evaluate(val_scaled, val_target)


375/375 [==============================] - 1s 2ms/step - loss: 0.3567 - accuracy: 0.8749
[0.35672488808631897, 0.874916672706604]

 

 

맨 처음 검증세트 결과보다 상향된 값이다.

 

 

 

 

 

Optimizer 옵티마이저: 다양한 hyper-parameter 조정을 통해 최적 model 찾기

keras: 미니배치 32개 경사하강법 default 사용.    fit()에서 batch size, epoch 조정 可

complie(): keras 기본 경사하강법(RMSprop) 사용.

 

 

기본 최적법 SGD(확률 경사하강법) 사용하기: 기존 complie()에 optimizer 옵션 설정 추가

model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics='accuracy')
### 위와 아래의 코드는 동일한 내용이다. ###
sgd = keras.optimizers.SGD()
model.compile(optimizer=sgd, loss='sparse_categorical_crossentropy', metrics='accuracy')

원래 아래의 것 처럼 sgd로 객체 설정을 하고 입력하는게 정석이지만, 'sgd'만 입력해도 자동으로 객체 형성해서 반영한다.

위의 것은 sgd default 옵션을 사용하는 경우이고 밑의 것은 추가 옵션을 지정하는 경우에 결국 사용해야한다.

 

여기서 default learning rate는 0.01이다. 이처럼 세부 옵션 변경하려면 sgd 객체화는 필수이다.

sgd = keras.optimizers.SGD(learning_rate=0.1)
## 추가 옵션
sgd = keras.optimizers.SGD(momentum=0.9, nesterov=True)

여기서 default momentum은 0이다. 여기서 값을 0보다 크게 지정하면 gradient 가속도처럼 momentum optimization이 된다. 지정하는 경우 보통은 0.9를 사용한다.

여기서 default nesterov = True인데 False를 지정하는 경우, nesterove momentum optimization(네스테로프 가속 경사)를 사용한다. 이 최적화는 모멘텀 최적화를 2번 반복하여 구현한다. 보통 네스테로프 모멘텀 최적화가 기본 확률적 경사하강법보다 더 나은 성능을 제공한다.

 

 

 

Adaptive Learning Rate(적응적 학습률): learning rate가 training에 따라 조정되는 것

The performance of the model on the training dataset can be monitored by the learning algorithm and the learning rate can be adjusted in response

 

이를 구현하는 것은 Adagrad와 RMSprop가 있다. optimizer 기본값은 RMSprop이지만, sgd경우와 같이 각 세부 설정이 필요하다면 객체화를 하여 사용해야한다. (default learning rate = 0.001)

rmsprop = keras.optimizers.RMSprop()   # RMSprop
model.compile(optimizer=rmsprop, loss='sparse_categorical_crossentropy', metrics='accuracy')
 
adagrad = keras.optimizers.Adagrad()   # Adagrad
model.compile(optimizer=adagrad, loss='sparse_categorical_crossentropy', metrics='accuracy')

 

momentum 최적화 장점 + RMSprop 장점 = Adam도 있다.  (default learning rate = 0.001)

RMSprop와 Adam은 맨 처음 시도하기에 좋은 알고리즘이다. 아래의 코드는 Adam 최적화를 적용한 경우이다.

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'))

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
# adam이 설정된 것에 주목한다.

model.fit(train_scaled, train_target, epochs=5)

Epoch 1/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.5222 - accuracy: 0.8177
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3932 - accuracy: 0.8588
Epoch 3/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3521 - accuracy: 0.8732
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3264 - accuracy: 0.8823
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3089 - accuracy: 0.8872
<keras.src.callbacks.History at 0x2259b199510>

 

RMSprop 사용할 때보다 더 좋은 결과가 출력되었다. (경우에 따라서 별 차이 없을 수도 있고 결과는 다양하다.)

 

 

검증 세트에 적용하면 

model.evaluate(val_scaled, val_target)

375/375 [==============================] - 0s 1ms/step - loss: 0.3748 - accuracy: 0.8678
[0.3747647702693939, 0.8678333163261414]

 

이 경우 기존 RMSprop결과보다 좋지 않다.  (경우에 따라서 RMSprop보다 더 좋게 출력되는 경우도 있다.)

 

 

 

728x90
반응형