본문 바로가기

ML

[딥러닝의 정석]05. 합성곱 신경망-2

728x90

 

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구조를 그림으로 나타내면 이와 같은 그림으로 많이 표현됩니다. 

fc층에 도달하면 모양이 블록이 아닌 평면임을 알 수 있음 

 

더보기

*여기서 참고할 사항은 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)에서도 나왔다

배치 정규화의 이면은 다음과 같다 

정규화 <--- 비정규화

정규화된 구조는 안정적이다. 하지만 벽돌이 무작위로 이동하면 탑은 불안정해지다가 무너진다

 

신경망을 학습시킬 때 이러한 현상이 발생한다. 

신경망의 가중치를 학습시키는 과정에서 최하층 뉴런들의 출력분포가 이동한다.

최하층의 출력 분포가 바뀌면 최상층은 예측방법 학습 뿐 아니라 분포의 변화를 자체적으로 수용해야하는데,

이는 학습을 느리게하고 신경망 층을 악화시킨다.

https://youtu.be/IHZwWFHWa-w

 

배치 정규화의 효과는 다음과 같다

  • 학습 속도 개선(학습률을 높일 수 있으므로)
  • 가중치 초깃값 의존성 낮아짐
  • 과적합을 방지
  • Gradient Vanishing problem해결

 

 https://sacko.tistory.com/44

배치 정규화는 활성화 함수의 출력값을 정규화 하는 작업이다.

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

 

 

 

 

 

 

728x90