5.8 합성곱 신경망으로 MNIST에서 순환 반복 끝내기
우리는 합성곱 신경망이 이미지를 분석하는데 적절하다는 사실에 대해 알게되었다
그렇다면, 3장에서 구현한 MNIST학습망을 더욱 발전시켜 볼 수도 있을 것이다.
다음과 같이 2개의 CNN, 2개의 풀링층을 통과한 뒤 완전 연결층(fully connected, dropout, p-=0.5)*과 소프트맥스층 으로 구성되어 있다.
3단원의 구조와 비교해보면 다음과 같다.
conv와 pool 가 추가되었으므로, 둘에 대한 함수가 추가되어야 한다
이전에는 hidden layer의 기능을 layer함수가 수행하였다.
conv함수
def conv2d(input, weight_shape, bias_shape):
#가중치 분산 = 2/n( n: 뉴런으로 들어가는 입력의 수 = insize)
insize = weight_shape[0]*weight_shape[1]*weight_shape[2]
weight_init = tf.random_normal_initializer(stddev=(2.0/insize)**0.5)
#필터
W = tf.get_variable("W",weight_shape,initializer=weight_init)
#bias 초기값 0
bias_init = tf.constant_initializer(value=0)
b = tf.get_variable("b",bias_shape,initializer=bias_init)
#합성곱 연산 수행
conv_out = tf.nn.conv2d(input,W,strides=[1,1,1,1],padding='SAME')
#합성곱 연산 수행 결과에 bias를 더한 후, 활성화 함수(relu)를 적용하여 return
return tf.nn.relu(tf.nn.bias_add(conv_out,b))
tf.nn.conv2d()함수는 앞장에 나왔듯
tf.nn.conv2d(input, filter, strides, padding)
이와 같은 구조를 가진다.
input은 N(미니배치 수)*h*w*d인 4차원 텐서이며,
filter는 W로 e*e*d*k의 크기를 가진다
stride는 1,1,1,1 이므로 모든 차원에 대해 1만큼이동 하고
padding은 same이므로 output이 input값과 동일하게 높이, 너비가 보존된다.
max pooling함수
def max_pool2d(input, k=2):
return tf.nn.max_pool(input,ksize=[1,k,k,1],strides=[1,k,k,1],padding='SAME')
tf.nn.max_pool() 함수는 다음과 같다
input 맥스풀링을 적용할 input
ksize : 윈도우의 크기(필터처럼)
strides 윈도우의 이동
또한, 실제로 은닉층들을 수행해주던 inference함수또한 수정해 주어야 한다.
keep_prob는 dropout시 확률 p를 의미한다.
def inference(x,keep_prob):
x = tf.reshape(x, shape=[-1,28,28,1])
with tf.variable_scope("conv_1"):
conv_1 = conv2d(x,[5,5,1,32],[32])
pool_1 = max_pool2d(conv_1)
with tf.variable_scope("conv_2"):
conv_2 = conv2d(pool_1,[5,5,32,64],[64])
pool_2 = max_pool2d(conv_2)
with tf.variable_scope("fc"):
pool_2_flat = tf.reshape(pool_2,[-1,7*7*64])
fc_1 = layer(pool_2_flat,[7*7*64,1024],[1024])
#dropout
fc_1_drop = tf.nn.dropout(fc_1,keep_prob)
with tf.variable_scope("output"):
output = layer(fc_1_drop,[1024,10],[10])
return output
weight shape에 대해서 설명하자면,
padding = SAME이므로 입력과 출력의 h * w 가 같게출력됩니다.
max pooling층의 크기가 2이므로, 파라미터를 1/2크기로 축소하겠네요
즉 conv2d를 수행하면 결과값(out)은 N * h(in)*w(in)*k(필터수) 가 되고
padding이 same이면 입력값과 결과값의 h와 w가 동일 h(out)/w(out) == h(in)/w(in)
결과값의 채널(볼륨/depth)는 필터수와 같게됩니다
pooling층을 통과하면 풀링층 k의 사이즈에 따라서 다르지만 여기서는 2 이므로
입력값의 h, w가 절반이 되는것을 제외하고 채널수는 같게 나오게됩니다.
코드를 짤 때 항상 주의해야 하는 점 / 지키지 않으면 오류
- CNN에서는 입력의 채널수 == 필터의 채널수
- 이며, 출력의 채널 수 == 필터의 갯수
또한 fully connected층에서는 3단원에서 hidden layer로 사용한 함수를 사용하므로
블럭모양인 input값을 행렬값으로 변형시켜 입력합니다.
그 후 dropout을 적용 한 뒤 output(softmax층) 을 통과시켜 리턴합니다
보통 CNN구조를 그림으로 나타내면 이와 같은 그림으로 많이 표현됩니다.
*여기서 참고할 사항은 softmax층은 0~9까지의 수일 확률을 출력하므로 출력층을 10(0~9는 10개)개로 줄이게됩니다.
하지만 layer함수를 사용하는데, 이는 fc에서도 사용되므로 softmax함수를 사용할 수 없게됩니다
따라서 여기서는 출력층의 수만 10개로 줄이는 역할을 하고
리턴된 output을 loss함수에 넣어 loss함수내에서 softmax함수를 통과시키게됩니다
(loss함수에서 실행하는 이유는 성능을 향상시키기 위해서 이다 )
실질적으로는 처음 시작할때 나왔던 그림처럼 output 층에서 softmax함수가 적용되는 것은 아니라고 할 수 있습니다.
전체코드
import tensorflow as tf
old_v = tf.logging.get_verbosity()
tf.logging.set_verbosity(tf.logging.ERROR)
from tensorflow.examples.tutorials.mnist import input_data
tf.set_random_seed(777) # reproducibility
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#x를 받아 y값(예측값)을 반환하는 함수
def layer(input, weight_shape, bias_shape):
weight_stddev = (2.0/weight_shape[0])**0.5
w_init = tf.random_normal_initializer(stddev=weight_stddev)
bias_init = tf.constant_initializer(value=0)
W = tf.get_variable("W", weight_shape,initializer=w_init)
b = tf.get_variable("b", bias_shape,initializer=bias_init)
return tf.nn.relu(tf.matmul(input, W) + b)
#2개의 은닉층을 수행하는 함수
def inference(x,keep_prob):
x = tf.reshape(x, shape=[-1,28,28,1])
with tf.variable_scope("conv_1"):
conv_1 = conv2d(x,[5,5,1,32],[32])
pool_1 = max_pool2d(conv_1)
with tf.variable_scope("conv_2"):
conv_2 = conv2d(pool_1,[5,5,32,64],[64])
pool_2 = max_pool2d(conv_2)
with tf.variable_scope("fc"):
pool_2_flat = tf.reshape(pool_2,[-1,7*7*64])
fc_1 = layer(pool_2_flat,[7*7*64,1024],[1024])
#dropout
fc_1_drop = tf.nn.dropout(fc_1,keep_prob)
with tf.variable_scope("output"):
output = layer(fc_1_drop,[1024,10],[10])
return output
def conv2d(input, weight_shape, bias_shape):
insize = weight_shape[0]*weight_shape[1]*weight_shape[2]
weight_init = tf.random_normal_initializer(stddev=(2.0/insize)**0.5)
W = tf.get_variable("W",weight_shape,initializer=weight_init)
bias_init = tf.constant_initializer(value=0)
b = tf.get_variable("b",bias_shape,initializer=bias_init)
conv_out = tf.nn.conv2d(input,W,strides=[1,1,1,1],padding='SAME')
return tf.nn.relu(tf.nn.bias_add(conv_out,b))
def max_pool2d(input, k=2):
return tf.nn.max_pool(input,ksize=[1,k,k,1],strides=[1,k,k,1],padding='SAME')
#출력층을 소프트맥스를 수행하며 오차를 계산하는 함수
def loss(output, y):
xentropy = tf.nn.softmax_cross_entropy_with_logits(logits=output, labels=y)
loss = tf.reduce_mean(xentropy)
return loss
#정확성을 평가하는 함수
def evaluate(output, y):
correct_prediction = tf.equal(tf.argmax(output, 1),tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
tf.summary.scalar("Validation Error:", (1-accuracy))
return accuracy
# 경사하강법으로 학습을 진행하는 함수
def training(cost, global_step):
tf.summary.scalar("cost", cost)
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train_op = optimizer.minimize(cost, global_step=global_step)
return train_op
#실행부분
# 파라미터
learning_rate = 0.01
training_epochs = 1000
batch_size = 100
display_step = 1
with tf.Graph().as_default():
# mnist data image : 학습할 데이터 input shape 28*28=784
#none은 minibatch의 사이즈
x = tf.placeholder("float", [None, 784])
# 0-9 숫자인식 => 10개 클래스
# 미니배치에 대한 정답 (만약 해당 그림이 0의 그림일 경우 y0:1 y1:0 y2:0 .... y9:0) 이게 1개에 대한 거고 미니배치니까 여러개
y = tf.placeholder("float", [None, 10])
keep_prob = tf.placeholder(tf.float32)
output = inference(x,keep_prob)
#실제 정답과 계산한 output오차계산
cost = loss(output, y)
global_step = tf.Variable(0, name='global_step',trainable=False)
train_op = training(cost, global_step)
#정확도
eval_op = evaluate(output, y)
#시각화용도
summary_op = tf.summary.merge_all()
saver = tf.train.Saver()
#세션시작
sess = tf.Session()
summary_writer = tf.summary.FileWriter("logistic_logs2/",graph_def=sess.graph_def)
#변수 초기화
init_op = tf.global_variables_initializer()
sess.run(init_op)
# 학습주기
for epoch in range(training_epochs):
avg_cost = 0.
# 데이터셋 ( 전체 데이터 양 ) / 배치사이즈( 한번에 넣어줄 데이터의 부분집합 사이즈 ) = 전체 데이터를 다 예제로 줘보려면 몇번넣어야하나
total_batch = int(mnist.train.num_examples/batch_size)
# Loop over all batches
# 전체 데이터를 전부 돌려봐야하니까 반복해야함
for i in range(total_batch):
#미니배치로 x,y에 해당하는 데이터와 답으로 나눠서 학습
mbatch_x, mbatch_y = mnist.train.next_batch(batch_size)
# 학습 하기
feed_dict = {x : mbatch_x, y : mbatch_y, keep_prob:0.5}
sess.run(train_op, feed_dict=feed_dict) #training 함수를 돌리려면 x,y가 필요
# 평균손실 계산
minibatch_cost = sess.run(cost,feed_dict=feed_dict)
avg_cost += minibatch_cost/total_batch
# Display logs per epoch step 한 에폭시가 끝나면 검증데이터를 이용하여 오류율을 출력한다
if epoch % display_step == 0:
val_feed_dict = {x : mnist.validation.images, y : mnist.validation.labels, keep_prob:1}
accuracy = sess.run(eval_op,feed_dict=val_feed_dict)
print ("Validation Error:",(1 - accuracy))
#시각화를 위한 친구들
summary_str = sess.run(summary_op,feed_dict=feed_dict)
summary_writer.add_summary(summary_str,sess.run(global_step))
saver.save(sess, "logistic_logs2/model-checkpoint", global_step=global_step)
print ("Optimization Finished!")
#검증이 끝나면 테스트 데이터를 통해 정확도를 출력한다
test_feed_dict = {x : mnist.test.images, y : mnist.test.labels, keep_prob:1}
accuracy = sess.run(eval_op, feed_dict=test_feed_dict)
print ("Test Accuracy:", accuracy)
실행부분의 feed_dict에 keep_prob(drop out시 probability)값도 추가해야 하는 것을 명심합시다
주로, 학습할때는 0.5~0.7 , 테스트나 검증시에는 1 값을 사용합니다.
결국 Adam최적화(4단원) 도구를 통해 신경학습망을 학습시킬 수 있는데, 결과는 다음과 같다
결과
99.16% 정도로 더 성능이 향상된 것을 볼 수 있습니다.( 책에서는 99.4정도까지 나온다고 하네요)
5.9 더 견고한 모델을 만드는 이미지 전처리 파이프라인
MNIST의 데이터는 매우 잘 정돈(모든 이미지가 서로 비슷하도록 전처리)되어 있다.
- 손으로 쓴 숫자들이 같은 방식으로 완벽하게 잘려져 있음
- 흑백이어서 색의 수차가 없음
하지만 자연이미지는 전처리 되어있지 않고 매우 지저분하다
이미지를 쉽게 학습시키기 위해서는 여러가지 전처리 작업을 진행해야한다.
가장 많이 사용되는 것은 "화이트닝*" 이다.
화이트닝 : 모든 픽셀에서 평균을 뺴고 분산을 단위 1로 정규화하여 중심점을 0으로 맞추는 것으로, 이미지 사이의 동적 범위에서 잠재적 차이들을 수정할 수 있게 해준다.
근사화된 이미지당 화이트닝 (approximate per-image whitening)
tf.image.per_image_whitening(image)
그 외에도 이미지를 자르거나 뒤집거나 채도와 밝기를 수정할 수 있다.
tf.random_crop(value, size, seed=None, name=None)
tf.image.random_flip_up_down(image, seed=None)
tf.image.random_flip_left_right(image, seed=None)
tf.image.transpose_image(image)
tf.image.random_brightness(image, max_delta, seed=None)
tf.image.random_contrast(image, lower, upper, seed=None)
tf.image.random_saturation(image, lower, upper, seed=None)
tf.image.random_hue(image, max_delta, seed=None)
이러한 변환들은 자연 이미지에 표현된 다른 분산에 대해 견고한 신경망을 만드는 것을 돕는다.
5.10 배치 정규화(batch normalization)로 학습 가속하기
배치정규화를 이용해 전방향/합성망 신경망의 학습을 가속할 수 있다. -> 참고로 normalization은 2단원(L2/L1/dropout)에서도 나왔다
배치 정규화의 이면은 다음과 같다
정규화된 구조는 안정적이다. 하지만 벽돌이 무작위로 이동하면 탑은 불안정해지다가 무너진다
신경망을 학습시킬 때 이러한 현상이 발생한다.
신경망의 가중치를 학습시키는 과정에서 최하층 뉴런들의 출력분포가 이동한다.
최하층의 출력 분포가 바뀌면 최상층은 예측방법 학습 뿐 아니라 분포의 변화를 자체적으로 수용해야하는데,
이는 학습을 느리게하고 신경망 층을 악화시킨다.
배치 정규화의 효과는 다음과 같다
- 학습 속도 개선(학습률을 높일 수 있으므로)
- 가중치 초깃값 의존성 낮아짐
- 과적합을 방지
- Gradient Vanishing problem해결
배치 정규화는 활성화 함수의 출력값을 정규화 하는 작업이다.
1. 활성화 함수를 통과하기 전 한 층으로 들어오는 로짓 벡터(z)를 가로챈다
2. 평균을 빼고 표준편차로 나누어 미니배치의 모든 예제에서 로짓 벡터의 각 요소를 정규화한다.
-> 학습 시의 미니배치를 한 단위로 정규화 하는 것으로 분포의 평균이 0, 분산이 1이 되도록 한다.
3. 정규화된 입력 x가 주어지면 두 개의 파라미터로 표현 능력을 복원하기 위해 Affine변환을 사용한다.
*Affine변환 : fully connected 층으로 변환하는 것
이렇게 되면 데이터 분포가 덜 치우치게 되어 정규화가 이루어진다.
배치정규화를 이용한다면 L2/dropout등을 사용하지 않아도 된다.
하나의 합성곱층의 배치 정규화
def conv_batch_norm(x, n_out, phase_train):
beta_init = tf.constant_initializer(value=0.0,dtype=tf.float32)
gamma_init = tf.constant_initializer(value=1.0,dtype=tf.float32)
beta = tf.get_variable("beta", [n_out], initializer=beta_init)
gamma = tf.get_variable("gamma", [n_out], initializer=gamma_init)
batch_mean, batch_var = tf.nn.moments(x, [0,1,2], name='moments')
ema = tf.train.ExponentialMovingAverage(decay=0.9)
ema_apply_op = ema.apply([batch_mean, batch_var])
ema_mean, ema_var = ema.average(batch_mean),ema.average(batch_var)
def mean_var_with_update():
with tf.control_dependencies([ema_apply_op]):
return tf.identity(batch_mean),tf.identity(batch_var)
mean, var = control_flow_ops.cond(phase_train,mean_var_with_update, lambda: (ema_mean, ema_var))
normed = tf.nn.batch_norm_with_global_normalization(x, mean, var, beta, gamma, 1e-3, True)
return normed
비 합성곱 전방층에 대한 배치 정규화
def layer_batch_norm(x, n_out, phase_train):
beta_init = tf.constant_initializer(value=0.0,dtype=tf.float32)
gamma_init = tf.constant_initializer(value=1.0,dtype=tf.float32)
beta = tf.get_variable("beta", [n_out], initializer=beta_init)
gamma = tf.get_variable("gamma", [n_out], initializer=gamma_init)
batch_mean, batch_var = tf.nn.moments(x, [0], name='moments')
ema = tf.train.ExponentialMovingAverage(decay=0.9)
ema_apply_op = ema.apply([batch_mean, batch_var])
ema_mean, ema_var = ema.average(batch_mean),ema.average(batch_var)
def mean_var_with_update():
with tf.control_dependencies([ema_apply_op]):
return tf.identity(batch_mean),tf.identity(batch_var)
mean, var = control_flow_ops.cond(phase_train,mean_var_with_update, lambda: (ema_mean, ema_var))
x_r = tf.reshape(x, [-1, 1, 1, n_out])
normed = tf.nn.batch_norm_with_global_normalization(x_r,
mean, var, beta, gamma, 1e-3, True)
return tf.reshape(normed, [-1, n_out])
'ML' 카테고리의 다른 글
[딥러닝의 정석]07.시퀀스 분석을 위한 모델 (0) | 2020.08.03 |
---|---|
[딥러닝의 정석] 05. 합성곱 신경망-1 (0) | 2020.08.02 |
[딥러닝의 정석] 04. 경사 하강법을 넘어서 (0) | 2020.07.27 |
[딥러닝의 정석]03. 텐서플로로 신경망 구현하기-2 (0) | 2020.07.21 |
[딥러닝의 정석]03. 텐서플로로 신경망 구현하기 (0) | 2020.07.18 |