다중 분류 신경망을 만들기 위해서는 소프트맥스(softmax)함수와 크로스 엔트로피(cross entropy)손실함수라는 새로운 개념을 알아야 합니다.
다중분류의 개념
다중 분류의 문제점 및 소프트맥스 함수
만약 활성화 출력의 값이 위와 같다면 즉 , 각 출력의 합이 1이 아니라면 비교 하기 쉽지 않습니다. 우리는 확률과 같이 각각의 출력을 뽑아내고 싶습니다. 어떻게 하면 될까요 ??
이때 softmax 함수의 유용함이 나옵니다.
소프트맥스 함수를 적용해 출력강도를 정규화합니다.
softmax의 정의 :
이와 같이 소프트맥스함수에는 출력층에서 계산된 선형출력이 필요합니다.
시그모이드 공식에서 선형출력(z)를 뽑아 낼 수 있습니다.
※정리
다중분류에서 통과한 값들은 소프트맥스 함수를 거치며 적절한 확률값으로 변한다. 이 확률값을 이용해 가중치 절편을 업데이트 하기 위한 손실함수가 크로스 엔트로피이다.
크로스 엔트로피
> 사실 크로스 엔트로피는 로지스틱 손실함수를 일반화 한 것입니다.
크로스 엔트로피 손실 함수를 미분 .
-흐름도-
손실함수를 z1에 대해 미분해 봅시다.
여기서 주의할 점은 출력 a1,a2,a3 모두 z1의 함수이고 손실 함수 L이 a1,a2,a3의 함수이므로 다음과 같이 연쇄법칙이 나타 납니다.
여기서 손실함수 L에 대해 a1,a2,a3로 미분하여 다시 적용하면,
위와 같이 적을 수 있습니다.
이제 a1,a2,a3에 대한 z1의 미분을 해봅시다.
첫번째로 a1에 대한 도함수를 구해보면 다음과 같은 방법으로 구할 수 있습니다.
똑같은 방법으로 a2,a3 에 대한 도함수를 구해보면 ,
이렇게 해서 모든 도함수를 원래의 식에 대입하면
이와 같은 결과가 나오는 것을 확인 할 수 있었다.
다중분류 신경망의 구현
1.softmax 함수를 추가합니다.
> 다중분류에서는 마지막 출력층에 소프트맥스 함수를 써야됩니다.
def sigmoid(self, z):
z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
a = 1 / (1 + np.exp(-z)) # 시그모이드 계산
return a
def softmax(self, z):
# 소프트맥스 함수
z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
exp_z = np.exp(z)
return exp_z / np.sum(exp_z, axis=1).reshape(-1, 1)
※소프트맥스 함수의 기본적인 작동 방식 흐름
2.forpass() 메서드에 사용된 activation()을 sigmoid()로 변경합니다.
3.가중치 초기화
> 이진분류에서는 출력층의 뉴런이 1개 이므로 가중치 w2의 크기는 (은닉층 뉴런갯수 ,1) 이 었지만 다중분류에서는 가중치 w2의 크기는(은닉층의 뉴런개수, 클래스 개수) 이고 b2의 크기는 클래스의 개수 입니다.
4.fit() 메서드 수정
> 가중치를 초기화하는 init_weight()메서드를 넘겨주고 , y,y_val이 다중 분류에서는 2차원 행렬이므로 열벡터로 변환하는 코드를 지워야합니다.
def fit(self, x, y, epochs=100, x_val=None, y_val=None):
np.random.seed(42)
self.init_weights(x.shape[1], y.shape[1]) # 은닉층과 출력층의 가중치를 초기화합니다.
# epochs만큼 반복합니다.
for i in range(epochs):
loss = 0
print('.', end='')
# 제너레이터 함수에서 반환한 미니배치를 순환합니다.
for x_batch, y_batch in self.gen_batch(x, y):
a = self.training(x_batch, y_batch)
# 안전한 로그 계산을 위해 클리핑합니다.
a = np.clip(a, 1e-10, 1-1e-10)
# 로그 손실과 규제 손실을 더하여 리스트에 추가합니다.
loss += np.sum(-y_batch*np.log(a))
self.losses.append((loss + self.reg_loss()) / len(x))
# 검증 세트에 대한 손실을 계산합니다.
self.update_val_loss(x_val, y_val)
5. training() 메서드 수정
> activation() 을 softmax() 로 변경합니다.
6. predict() 메서드 수정
> predict()메서드에서는 정방향 계산에서 얻은 출력중 가장 큰값의 인덱스가 예측 클래스입니다. (예측할때는 소프트맥스 함수를 거치지 않아도 됩니다.)
def predict(self, x):
z = self.forpass(x) # 정방향 계산을 수행합니다.
return np.argmax(z, axis=1)
7. score()메소드 수정.
predict 메서드 결과와 타겟 y의 클래스를 비교합니다. 이를 위해 y의 행에 따라 가장 큰 인덱스를 사용합니다.
8. 검증 손실 계산.
활성화 함수를 softmax()로 변경하고 로지스틱 손실을 크로스엔트로피 손실로 변경합니다.
지금까지 만든 다중분류 신경만 클래스로 이미지를 분류 해보겠습니다.
그 전에 이미지 데이터 세트를 불러와야 겠지요? 데이터 세트를 불러오기 위해서 텐서플로우에 내장된 패션 MNIST 데이터 세트를 활용해 보겠습니다.
(x_train_all, y_train_all), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
print(x_train_all.shape, y_train_all.shape)
=============================
(60000, 28, 28) (60000,)
이것은 28*28 이미지가 총 60000개 있다는것을 의미합니다.
imshow 함수를 이용해 샘플 이미지를 확인해 보겠습니다. 어떤 이미지가 있을까요 ?
import matplotlib.pyplot as plt
plt.imshow(x_train_all[0],'gray')
plt.show()
자그러면 타겟내용도 확인해 볼까요?
print(y_train_all[:10])
======================
[9 0 0 3 0 2 7 2 5 5]
각 숫자의 의미는 무엇일까요 ? 이숫자들은 특정 옷의 종류에 따라 각가 일대일 대응된 숫자들입니다.
자 그럼, 타겟과 어떤 이미지들이 존재하는지 알아 봤으니 훈련/테스트 세트를 만들어 봅시다.
from sklearn.model_selection import train_test_split
x_train,x_val,y_train,y_val = train_test_split(x_train_all,y_train_all,stratify=y_train_all,
test_size=0.2,random_state=777)
훈련세트와 검증세트를 동일한 비율의 타겟 레이블로 나누었습니다. 다음으로 입력데이터를 정규화 해보겠습니다. 우리가 전에 유방암 데이터 세트를 활용했을때도 훈련세트와 검증세트 모두 정규화했던것을 떠올리시면 될거 같습니다. 이미지 데이터는 픽셀마다 0~255사이의 값을 가지므로 이값을 255로 나누어주면 0~1사이로 맞춥니다.
x_train = x_train / 255
x_val = x_val / 255
앞에서 본것처럼 이미지의 데이터는 28*28 2차원 배열입니다. 하지만 앞에서 만든 클래스는 1차원 배열의 샘플을 기대합니다. 데이터의 차원이 맞지 않습니다. 어떻게 할까요 ?
간단합니다. reshape() 함수를 이용해 데이터 차원을 바꾸어주면 됩니다.
x_train = x_train.reshape(-1,784)
x_val = x_val.reshape(-1,784)
타겟 데이터를 준비하고 다중 분류 신경망을 훈련합니다.
-원-핫-인코딩-
앞에서 보듯이 타겟값에는 0~9사이의 정수 값들이 들어 있었습니다. 안타깝게도 ML모델에서는 이러한 경우를 제대로 학습할 수 없습니다. 오직 0과1로만 구분되어야하기 때문입니다. 이러한 안타까운일이 발생하지 않도록 해줄수 있는것이 one-hot-encoding 입니다.
원 핫 인코딩은 케라스의 to-categorical을 이용하여 사용하면 쉽습니다.
y_train_encoded = tf.keras.utils.to_categorical(y_train)
y_val_encoded=tf.keras.utils.to_categorical(y_val)
print(y_train_encoded.shape,y_val_encoded.shape)
==================
(48000, 10) (12000, 10)
print(y_train[0],y_train_encoded[0])
=======================
6 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
자이제 훈련 데이터와 타겟데이터의 전처리를 완료 했으니 우리가 앞에서 만들었던 MultiClassNetwork 클래스로 다중 분류 신경망을 훈련해보겠습니다. !
fc = MultiClassNetwork(units=100,batch_size=256)
fc.fit(x_train,y_train_encoded,x_val=x_val,y_val=y_val_encoded,epochs=40)
fc.score(x_val,y_val_encoded)
=====================
0.8150833333333334
약 81퍼센트의 정확도를 보이는군요 . 뭐 높은 수치긴하지만 실전에서 쓰이기는 무리가 있을 거 같습니다. 이렇기 때문에 우리는 딥러닝 고급 패키지를 사용해야 합니다. 자 이제 본격적으로 텐서플로를 사용해 봅시다. !
케라스 API
> 케라스는 인공신경망 모델을 만들기 위해 Sequential 클래스와 완전 연결층을 만들기 위한 Dense를 제공 합니다.
Sequential 클래스는 이름의 의미 그대로 '순차적으로 층을 쌓은 신경망 모델'이고 , Dense 클래스는 모델에 포함된 완전 연결층입니다. 즉,위의 인공신경망은 은직층과 출력층을 Dense 클래스의 객체로 구성하고 각각의 객체를 Sequential 클래스 객체에 추가하면 완성됩니다.
※ Sequential 클래스의 사용법
완전 연결 신경망을 만드려면 Sequential 클래스와 Dense 함께 사용합니다. 대표적으로 두가지 방법이 있습니다.
첫번재는 Sequential 객체를 생성할때 층을 추가하는것 , 두번째는 객체 생성후 add() 메서드를 사용하여 층을 추가하는것입니다.
※Dense 클래스 사용법.
-뉴런의 개수를 지정하는 매개변수 unit
-활성화 함수를 지정하는 매개변수 activation
Dense(100,activation='sigmoid')
Dense 클래스를 이용해 Sequential객체를 잘 만들었다면 모델의 최적화 알고리즘과 손실 함수를 설정해야합니다.
즉, 다중분류에서는 최적화 알고리즘으로는 '경사하강법' , 손실함수는 '크로스엔트로피'를 사용해야합니다.
* 최적화 알고리즘을 지정하는 매개변수 : optimizer
* 손실함수를 지정하는 매개변수 : loss
Sequential 클래스의 compile() 메서드를 사용하여 최적화 알고리즘과 손실함수를 지정합니다.
model.compile(optimizer='sgd,loss='categorical_crossentropy')
자 그럼 케라스로 다중분류 신경망을 만들어 봅시다.
1.모델 생성하기
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential()
2. 은닉층과 출력층 추가하기.
model.add(Dense(100,activation='sigmoid',input_shape=(784,)))
#은닉층에 input_shape매개변수에 입력 데이터의 크기를 지정. 28x28이므로 일렬로 펼쳤으므로 784
model.add(Dense(10,activation='softmax'))
3.최적 알고리즘 & 손실함수 지정하기
model.compile(optimizer='sgd',loss='categorical_crossentropy',metrics=['accuracy'])
4.훈련하기
history = model.fit(x_train,y_train_encoded,epochs=40,validation_data=(x_val,y_val_encoded))
※history 객체에 history 딕셔너리에는 여러 측정 지표가 들어있습니다. 이것을 이용해 손실과 정확도를 그래프로 알아보겠습니다.
print(history.history.keys())
=========================
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])
5.손실 & 정확도 확인하기
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train_loss', 'val_loss'])
plt.show()
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train_accuracy', 'val_accuracy'])
plt.show()
6.검증세트 정확도 확인하기.
> evaluate() 메소드 사용하면 손실값과 metrics 매개변수 추가한 측정지표를 계산하여 반환합니다.
loss, accuracy = model.evaluate(x_val, y_val_encoded, verbose=0)
accuracy
=============
0.86325
(참고로 verbose는 진행상황을 나타내주는 파라미터입니다. 1로 설정하면 진행상황이 보입니다.)
'DNN > 딥러닝' 카테고리의 다른 글
순환신경망(RNN) (0) | 2021.07.08 |
---|---|
합성곱 신경망 (CNN) (0) | 2021.07.07 |
다층 신경망 (0) | 2021.07.06 |
훈련 노하우 (0) | 2021.07.05 |
로지스틱 회귀 (0) | 2021.06.23 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!