합성공 신경망을 알아보기전에 먼저 합성곱 연산이 어떻게 진행되는지 알아봅시다.
합성곱(컨볼루젼)
- 합성곱은 두 함수에 적용하여 새로운 함수를 만드는 수학 연산자 입니다.
그림을 보며 합성곱이 어떻게 진행되는지 알아 봅시다.
이런식으로 나머지도 한칸씩 움직여가며 연산하면 됩니다.
하지만 주의할점이 있습니다. 합성곱신경망은 합성곱을 쓰지 않는 답니다...!
대부분의 딥러닝 패키지들은 합성곱 신경망을 만들때 합성곱이 아니라 교차 상관을 사용합니다.
교차상관
교차상관연산은 합성곱연산과 크게 다르지않습니다. 오히려 더 간단하지요.
우리는 합성곱 연산을 할때 가중치 배열을 뒤집었던것을 기억 하시나요 ? 교차상관에서는 배열을 뒤집지 않아도 됩니다. 바로 계산하면 됩니다.!
왜 교차상관을 사용할까요 ? 그 이유는 모델을 훈련할때 가중치 배열을 초기화 하는과정을 생각해 보면됩니다.
우리는 가중치 배열을 초기화할때 먼저, 가중치를 무작위 값으로 초기화했고 , 모든 샘플에 대하여 정방향과 역방향 계산을 수행했습니다.
이미 배열이 무작위로 초기화 되어 있어 굳이 배열을 뒤집을 필요가 없었던 것이지요..!
패딩(padding)과 스트라이드(stride)
패딩은 원본배열의 양 끝에 빈 원소를 추가하는 것입니다.
스트라이드는 미끄러지는 배열의 간격을 조절하는 것을 말합니다.
이 두개념이 어떻게 적용되는지에 따라 밸리드 패딩 , 풀 패딩 , 세임 패딩으로 분류됩니다.
밸리드(valid) 패딩
밸리드 패딩은 원본 배열에 패딩을 추가하지 않고 미끄러지는 배열이 원본 배열의 끝으로 갈 때까지 교차 상관을 수행합니다. 이로 인해 밸리드 패딩의 결과로 얻는 배열의 크기는 항상 원본 배열보다 작게 됩니다. (즉, 원본 배열의 원소가 합성곱 연산에 참여하는 정도가 다릅니다.)
풀(full) 패딩
원본 배열의 원소가 연산에 동일하게 참여어떻게 해야될까요 ? 간단합니다.
원본배열의 양끝에 0을 추가하면됩니다. 이런 방법때문에 풀패딩을 제로(zero)패딩이라 불리기도 합니다.
세임 (same) 패딩
세임 패딩은 출력 배열의 길이와 원본배열의 길이를 동일하게 합니다.
풀 패딩과 동일하게 원본의 양 끝에 zero 패딩을 추가합니다.
이렇게 1차원 배열에서의 합성곱 연산을 확인해봤습니다. 이제 2차원 배열에서 합성곱을 수행해 봅시다.
수행방향은 원본 배열의 왼쪽에서 오른쪽 , 위에서 아래쪽에서 1칸씩 이동합니다.
*텐서플로우로 합성곱 수행하기.
tensorflow에서 2차원 합성곱을 수행하는 함수는 Con2d() 입니다. 이 함수는 입력을 4차원 배열을 기대합니다. (입력이미지의 높이와 너비외에 더많은 차원 필요)
입력과 가중치에 세임패딩을 적용하여 합성곱 수행합니다. (합성곱 신경망에서는 가중치를 필터 or 커널이라 부릅니다.)
풀링 연산
풀링이란 특성 맵을 스캔하며 최대값을 고르거나 평균값을 계산하는 것입니다.
합성공 신경망에서 합성곱이 일어나는 합성곱층 , 풀링이 일어나는 풀링층으로 나눌 수 있습니다.
두층에서 만들어진 결과를 특성 맵이라 합니다.
Max Pooling
앞에서 풀링은 최댓값을 고르거나 평균값을 계산하는 방법이 있었습니다. 여기서 최댓값을 고르는 방식을 맥스풀링방식이라 합니다.
Average Pooling
평균풀링은 풀링영역의 평균값을 계산합니다.
합성곱 신경망의 구조
ReLU 함수
이전까지 은닉층에 시그모이드 함수를 활성화 함수로 사용했습니다. 출력층은 이진 분류일 경우 시그모이드 함수를 그대로 사용하였지만 다중분류일 경우 소프트맥스 함수를 사용했습니다. 기억나시나요 ? 합성곱 신경망에 적용되는 활성화 함수는 주로 ReLU 함수를 사용합니다. 렐루 함수는 0보다 큰값은 그대로 통과시키고 0보다 작은 값은 0으로 만듭니다.
합성곱 신경망에서 일어나는 일들과 구조 .
합성곱 신경망내부에서는 어떤일들이 일어날까요?
1. 합성곱 신경망에 주입될 입력 데이터에는 채널이 있습니다.
> 채널이란 이미지의 픽셀이 가진 색상을 표현하기 위해 필요한 정보를 말합니다. 흔히 RGB라 말하기도 합니다.
2. 합성곱층에서 일어나는 일
> 이미지의 모든 채널에 합성곱이 한 번에 적용되어야 하므로 커널의 마지막 차원은 입력 채널의 갯수와 동일 해야합니다.
보통 커널의 크기는 3x3 or 5x5 입니다.
그림으로 보면 훨씬 이해 하기 쉽습니다.
3. 풀링층에서 일어나는 일
> 합성곱층을 통해 만들어진 특성 맵에 활성화 함수로 렐루 함수를 적용하고 풀링을 적용합니다.
4. 특성 맵을 펼쳐 완전 연결 신경망에 주입합니다.
전체 구조를 보면 다음과 같습니다.
자 이제 새롭게 안것을 바탕으로 합성곱 신경망을 구현해보겠습니다.
1. 합성곱 적용하기
> conv2d() 함수를 통해 합성곱 후 self.conv_b를 더합니다. 절편은 커널마다 하나씩 필요합니다.
conv2d()로 만들어지는 특성 맵의 크기는 28x28x10, 크기가 10인 1차원 배열 self.conv_b는 브로드 캐스팅됩니다.
def forpass(self,x):
c_out = tf.nn.conv2d(x,self.conv_w,strides=1,padding='SAME')+self.conv_b
2. ReLU 함수 적용하기
> 합성곱 계산을 수행한 다음에 렐루 함수를 적요하여 합성곱층을 완성시킵니다.
def forpass(self,x):
...
r_out = tf.nn.relu(c_out)
...
3.풀링 적용하고 완전 연결층을 수정합니다.
> max_pool2d() 함수를 사용하여 2x2 크기의 풀링을 적용합니다. 이단계에서 만들어진 특성 맵은 14x14x10 입니다.
def forpass(self, x):
# 3x3 합성곱 연산을 수행합니다.
c_out = tf.nn.conv2d(x, self.conv_w, strides=1, padding='SAME') + self.conv_b
# 렐루 활성화 함수를 적용합니다.
r_out = tf.nn.relu(c_out)
# 2x2 최대 풀링을 적용합니다.
p_out = tf.nn.max_pool2d(r_out, ksize=2, strides=2, padding='VALID')
# 첫 번째 배치 차원을 제외하고 출력을 일렬로 펼칩니다.
f_out = tf.reshape(p_out, [x.shape[0], -1])
#완전 연결층과 연결하는 과정
z1 = tf.matmul(f_out, self.w1) + self.b1 # 첫 번째 층의 선형 식을 계산합니다
a1 = tf.nn.relu(z1) # 활성화 함수를 적용합니다
z2 = tf.matmul(a1, self.w2) + self.b2 # 두 번째 층의 선형 식을 계산합니다.
return z2
4. 역방향 계산 구현하기
> 역방향 계산을 구현해보기 앞서 우리는 tensorflow의 자동 미분 기능에 대해 알아봅시다. 자동미분 기능을 사용하면 임의의 파이썬 코드나 함수에 대한 미분값을 계산 할 수 있습니다.
다음과 같은 파이썬 코드가 주어졌다면 ,
def training(self, x, y):
m = len(x) # 샘플 개수를 저장합니다.
with tf.GradientTape() as tape:
z = self.forpass(x) # 정방향 계산을 수행합니다.
# 손실을 계산합니다.
loss = tf.nn.softmax_cross_entropy_with_logits(y, z)
loss = tf.reduce_mean(loss)
즉 , training() 메서드에서 forpass() 메서드를 호출하여 정방향 계산을 수행한 다음 tf.nn.softmax_cross_entropy_with_logits()함수를 호출하여 정방향 계산의 결과 z와 타겟 y를 기반으로 손실값을 계산합니다. 이렇게 하면 크로스엔트로피 손실과 그레디언트 계산을 올바르게 처리 해 주므로 편리합니다. softmax_cross_entropy_with_logits()함수는 배치의 각 샘플에 대한 손실을 반환하므로 reduce_mean()함수로 평균을 계산해줍니다.
5) 그레디언트 계산하기
> tape.gradient () 메서드를 사용하면 그레디언트를 자동으로 계산할 수 있습니다. 합성곱층의 가중치와 절편인 con_w 와 con_b를 포함하여 그레디언트가 필요한 가중치 리스트로 나열합니다.
def training(self,x,y ):
...
weights_list = [self.conv_w, self.conv_b,
self.w1, self.b1, self.w2, self.b2]
# 가중치에 대한 그래디언트를 계산합니다.
grads = tape.gradient(loss, weights_list)
# 가중치를 업데이트합니다.
self.optimizer.apply_gradients(zip(grads, weights_list))
6) 옵티마이저 객체를 만들어 가중치 초기화합니다.
> fit() 메서드에서 경사하강법을 구현하기 위해 SGD 옵티마이저를 사용합니다.
7)init_weights() 메서드를 수정합니다.
> 지금까지 넘파이로 난수를 만들어 가중치를 초기화 했습니다. 하지만 신경망 모델이 너무 커지면 손실 함수도 복잡해지기 때문에 출발점에 다라 결과가 달라질 수 있습니다. 이러한 문제점을 극복하기 위해 glorot_uniform()을 사용합니다.
-glorot_uniform()
자 이제 합성곱 신경망을 훈련시켜 보겠습니다.
1. 데이터 세트 불러오기
(x_train_all,y_train_all) , (x_test,y_test) = tf.keras.datasets.fashion_mnist.load_data()
2. 훈련데이터 세트를 훈련 세트와 검증 세트로 나누기
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)
3. 타겟을 원-핫-인코딩으로 변환하기
y_train_encoded = tf.keras.utils.to_categorical(y_train)
y_val_encoded = tf.keras.utils.to_categorical(y_val)
4.입력데이터 준비하기
x_train = x_train.reshape(-1,28,28,1)
x_val = x_val.reshape(-1,28,28,1)
x_train.shape
==================
(48000, 28, 28, 1)
5.입력 데이터 표준화 전처리 하기
x_train = x_train /255
x_val = x_val /255
6.모델 훈련하기
cn = ConvolutionNetwork(n_kernels=10,units=100,batch_size=128,learning_rate=0.01)
cn.fit(x_train,y_train_encoded,x_val=x_val,y_val=y_val_encoded,epochs=20)
7.훈련,검증 손실 그래프 그리고 검증세트 정확도 확인하기.
import matplotlib.pyplot as plt
plt.plot(cn.losses)
plt.plot(cn.val_losses)
plt.ylabel('loss')
plt.xlabel('iteration')
plt.legend(['train_loss','val_loss'])
plt.show()
케라스로 합성곱 신경망 만들기
1.필요한 클래스 임포트
from tensorflow.keras.layers import Conv2D,MaxPooling2D,Flatten,Dense
2.합성곱층 쌓기
conv1 = tf.keras.Sequential()
conv1.add(Conv2D(10,(3,3),activation='relu',padding='same',input_shape=(28,28,1)))
3.풀링층 쌓기
conv1.add(MaxPooling2D((2,2)))
4.완전 연결층에 주입할 수 있도록 특성 맵 펼치기
conv1.add(Flatten())
5.완전 연결층 쌓기
conv1.add(Dense(100,activation='relu')) #100개의 뉴런
conv1.add(Dense(10,activation='softmax')) #10개의 클래스
6.모델 구조 살펴보기
conv1.summary()
합성곱 신경망 모델 훈련하기
conv1.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])
history = conv1.fit(x_train,y_train_encoded,epochs=20,validation_data=(x_val,y_val_encoded))
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()
손실함수와 정확도의 그래프를 보면 특정 에포크를 지나면서 크게 흔들리는것을 알 수있습니다. 이는 과대 적합이 일찍 발생했음을 의미합니다. 이러한 일을 극복 하려면 어떻게 해야 될까요 ?
드롭아웃
신경망에서 과대적합을 줄이는 방법 중 하나는 드롭아웃입니다. 드롭아웃은 무작위로 일부 뉴런을 비 활성화 시킵니다. 이렇게되면 특정 뉴런에 과도하게 의존하지 않아 과대적합을 줄일 수 있습니다.
앞에서 만든 모델에서 합성곱층과 완전 연결층 사이에 드롭아웃 층을 추가하여 과대적합을 방지합니다.
from tensorflow.keras.layers import Dropout
conv2 = tf.keras.Sequential()
conv2.add(Conv2D(10, (3, 3), activation='relu', padding='same', input_shape=(28, 28, 1)))
conv2.add(MaxPooling2D((2, 2)))
conv2.add(Flatten())
conv2.add(Dropout(0.5))
conv2.add(Dense(100, activation='relu'))
conv2.add(Dense(10, activation='softmax'))
conv2.summary()
=====================================================================
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 28, 28, 10) 100
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 10) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 1960) 0
_________________________________________________________________
dropout (Dropout) (None, 1960) 0
_________________________________________________________________
dense_2 (Dense) (None, 100) 196100
_________________________________________________________________
dense_3 (Dense) (None, 10) 1010
=================================================================
Total params: 197,210
Trainable params: 197,210
Non-trainable params: 0
=============================================================================
conv2.compile(optimizer='adam', loss='categorical_crossentropy',
metrics=['accuracy'])
history = conv2.fit(x_train, y_train_encoded, epochs=20,
validation_data=(x_val, y_val_encoded))
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()
loss, accuracy = conv2.evaluate(x_val, y_val_encoded, verbose=0)
print(accuracy)
===================
0.9210000038146973
'DNN > 딥러닝' 카테고리의 다른 글
Do it 딥러닝을 읽고.. (0) | 2021.07.22 |
---|---|
순환신경망(RNN) (0) | 2021.07.08 |
다중 분류 신경망 (0) | 2021.07.06 |
다층 신경망 (0) | 2021.07.06 |
훈련 노하우 (0) | 2021.07.05 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!