[이 글은 "Do it 딥러닝 입문" 책을 보고 공부한 내용을 복습하고자 정리한 글입니다.]
Convolution Neuron Network(CNN)
합성곱 연산이란 ?
원본 배열과 특정배열(reversed)을 자릿수마다 곱하고 더한 결과가 새로운 배열의 하나의 원소 값으로 만드는 연산
* 합성곱에서는 교차상관을 사용한다
why? -> 우리는 가중치를 초기화할 때 가중치 값을 랜덤 하게 주기 때문에 reverse는 무의미하다.
패딩과 스트라이드
패딩
Valid - 원본 배열의 원소가 합성곱 연산에 참여하는 정도가 다름(원본 배열 0번째 원소가 합성곱 연산에 참여하는 횟수는 1회)
full - 원본 배열의 원소가 모두 합성곱 연산에 골고루 참여하기 위해 배열 양 끝에 빈 원소를 적절히 추가한다.
same - 합성곱 연산을 통해 나온 새로운 배열이 원본 배열과 같은 크기를 가지게 배열 양 끝에 빈 원소를 적절히 추가한다.
스트라이드
- 연산에 사용되는 배열이 합성곱 연산을 할 때 어느 간격으로 연산을 시행하는지 조절
텐서플로우에서 합성곱
- 텐서플로우에서는 conv2d로 2차원 배열의 합성곱 연산을 지원한다.
- conv2d는 4차원 배열을 기대함.
입력 이미지의 차원
- 이미지의 (배치(개수), 높이, 너비, 채널 개수)
가중치의 차원
- (높이, 너비, 채널, 가중치 개수)
*텐서에서는 패딩옵션을 대문자로 사용
*가중치를 커널 또는 필터로 부른다.
합성곱 층, 풀링층
- 합성곱 신경망에서는 합성곱층과 풀링층, 완전 연결층이 있다.
*풀링이란? 특성맵을 스캔해서 최댓값 or 평균값을 계산하는 것
전체적인 플로우
1. 이미지와 커널의 합성곱 수행 (결과: 특성맵)
2. 특성맵을 활성화 함수(relu) 적용
3. 풀링층에서 풀링 (결과 : 크기가 작아진 특성맵)
4. 3의 특성맵을 완전 연결층의 입력으로 계산
5. 은닉층을 통과하여 출력
import tensorflow as tf
import numpy as np
class ConvolutionNetwork:
def __init__(self, n_kernels=10, units= 10, batch_size =32, learning_rate= 0.1):
self.n_kernels = n_kernels #합성곱 커널의 개수
self.kernel_size = 3 #커널의 사이즈
self.optimizer = None #옵티마이저
self.conv_w = None #합성곱의 가중치
self.conv_b = None #합성곱의 절편
self.units = units #은닉층의 뉴런개수
self.batch_size = batch_size #배치 크기
self.w1 = None #은닉층의 가중치
self.b1 = None
self.w2 = None
self.b2 = None
self.a1 = None #은닉층의 활성화 출력
self.losses=[] #훈련 손실
self.val_losses = [] #검증 손실
self.lr = learning_rate #학습률
def forpass(self, x): #정방향 연산
#3X3 합성공 연산을 수행
c_out = tf.nn.conv2d(x, self.conv_w, padding='SAME', strides=1) + 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
def init_weights(self, input_shape, n_classes):
g = tf.initializers.glorot_uniform()
self.conv_w = tf.Variable(g((3,3,1,self.n_kernels)))
self.conv_b = tf.Variable(np.zeros(self.n_kernels), dtype=float)
n_features = 14 * 14 * self.n_kernels #28 * 28 이미지를 ksize 2로 strides 2를 하면 14*14개의 특성맵이 10개가 나옴
self.w1 = tf.Variable(g((n_features, self.units))) #특성개수 , 은닉층 뉴런 크기
self.b1 = tf.Variable(np.zeros(self.units), dtype=float)
self.w2 = tf.Variable(g((self.units, n_classes))) #은닉층 크기, 클래스 개수
self.b2 = tf.Variable(np.zeros(n_classes),dtype=float) #클래스 개수
def fit(self, x, y , epochs = 100, x_val=None , y_val = None):
self.init_weights(x.shape[0], y.shape[1]) #x의 개수와 , 원핫 코딩 된 y의 개수
self.optimizer = tf.optimizers.SGD(learning_rate=self.lr)
#epcchs
for i in range(epochs):
print("에포크",i, end = ' ')
batch_losses = []
for x_batch, y_batch in self.gen_batch(x,y):
print('.', end='')
self.training(x_batch, y_batch)
#배치 손실을 기록합니다.
batch_losses.append(self.get_losses(x_batch,y_batch))
print()
#배치 손실 평균을 내어 훈련 손실값으로 저장
self.losses.append(np.mean(batch_losses))
#검증 세트에 대한 손실을 계산
self.val_losses.append(self.get_losses(x_val,y_val))
#미니 배치 제너레이터 함수
def gen_batch(self, x, y):
bins = len(x) // self.batch_size #배치 회수
indexes = np.random.permutation(np.arange(len(x))) #인덱스를 섞음
x = x[indexes]
y = y[indexes]
for i in range(bins):
start = self.batch_size * i
end = self.batch_size * (i + 1)
yield x[start:end], y[start:end] #batch_size만큼 슬라이싱하여 반환
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)
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))
def predict(self, x):
z = self.forpass(x)
return np.argmax(z.numpy(), axis = 1) #가장 큰 인덱스를 반환
def score(self, x, y):
#예측과 타깃 열 벡터를 비교하여 True의 비율을 반환
return np.mean(self.predict(x) == np.argmax(y, axis=1))
def get_losses(self, x, y):
z = self.forpass(x)
#손실을 계산해서 저장
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y,z))
return loss.numpy()
#텐서플로의 tape 기능 살펴보기
x = tf.Variable(np.array([1.0, 2.0, 3.0]))
with tf.GradientTape() as tape:
y = x ** 3 + 2 * x +5
print(tape.gradient(y,x))
(x_train_all, y_train_all), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
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=42)
#y값 원-핫 인코딩으로 변경
y_train_encoded = tf.keras.utils.to_categorical(y_train)
y_val_encoded = tf.keras.utils.to_categorical(y_val)
x_train = x_train.reshape(-1, 28,28,1)
x_val = x_val.reshape(-1,28,28,1)
print()
#이미지 전처리
x_train = x_train / 255
x_val = x_val /255
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)
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()
cn.score(x_val,y_val_encoded)
x_test = x_test.reshape(-1,28,28,1)
y_test_encoded = tf.keras.utils.to_categorical(y_test)
x_test = x_test/255
cn.score(x_test,y_test_encoded)
image = tf.keras.preprocessing.image
img = image.load_img('a.jpg',target_size = (28,28))
plt.imshow(img)
class_names =['티셔츠/윗도리','바지', '스웨터','드레스','코트','샌들','셔츠','스니커즈','가방','앵글부츠']
img_tensor = image.img_to_array(img)
#shirt = np.resize(img_tensor,(1,28,28,1))
shirt = shirt.reshape(1,28,28,1)
shirt = shirt / 255
pre = cn.predict(shirt)
print(class_names[pre.argmax()])
느낀 점 : 이제까지 수행해온 의류분류기로 셔츠를 분류했을 때는 가방, 신발 등 잘못 분류했었는데 이번에는 상의 정도로 맞추는 모습을 볼 수 있었다. 다음에는 케라스를 이용해서 분류를 해보겠다.
'빅데이터 | 머신러닝 | 딥러닝 > 딥러닝' 카테고리의 다른 글
[딥러닝 기초] recurrent neural network (using LSTM, keras) (0) | 2020.06.15 |
---|---|
[딥러닝 기초] Convolution Neuron Network for keras (0) | 2020.06.15 |
[딥러닝 기초] 다층 신경망을 통해 의류분류기 만들어보기(2) (using keras) (1) | 2020.05.14 |
[딥러닝 기초] 다층 신경망을 통해 의류분류기 만들어보기 (4) | 2020.05.12 |
[딥러닝 기초] 다층 신경망 (MLPClassifier) (0) | 2020.05.08 |