본문 바로가기

ML

[딥러닝의 정석]03. 텐서플로로 신경망 구현하기-2

728x90

너무 렉이 심해서 나눠버렸습니다.

 

3.10 텐서 플로에서 로지스틱 회귀 모델 지정하기

다시 1장의 기억을 되돌려 봅시다.

 MNIST데이터셋

우리는 손으로 쓴 숫자의 set을 학습시켜, 신경망이 해당 수를 정확한 수로 인지할 수 있게 하는 게 목표였습니다.

28* 28 에 흑백으로 손으로 쓰인 숫자들을 input으로 받아, 이 이미지 입력이 대상 분류의 하나(0~9중 하나)에 속할 확률을 계산하는

로지스틱 회귀 모델을 만들어봅시다.

 

모델은 망 연결의 가중치를 나타내는 행렬 W

입력 x가 i번째 분류에 속하는지를 추정하기 위한 bias에 해당하는 벡터 b

그리고 0~9중 하나에 속할 확률로 표현하기위해 확률분포를 만들 수 있는 소프트맥스 출력층을 이용해 구현해보려고 한다.

 

이는 매우 간단한 모델로 그림으로 나타내면 이러하다

 

28*28 = 784 크기의 입력층으로 28*28의 이미지를 입력받아 

0~9까지(10가지) 일 확률을 나타낸 10개의 출력층 , 2개의 층으로 구성되어있다.

 

로지스틱 회귀 모델을 만들기 위해 4단계를 거친다

0. mnist데이터셋 불러오기

1. 추론 inference : 주어진 미니 배치로 출력 분류에 대한 하나의 확률분포를 만든다

2. 손실 loss : 오차 함수(교차 엔트로피 손실 함수 이용)의 값을 계산한다.

3. 학습 training : 모델 파라미터들의 경사 계산과 모델의 갱신을 담당 -> 최적 파라미터를 찾음

4. 평가 evaluate : 모델의 효용성을 결정 

 

여기서 손실함수(=오차함수) 에 대해서 잠깐 알아보면

 

 

0. mnist이미지 불러오기 + 오류 메시지 우회로 무시하기 + 텐서 플로우 import
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)



 

1. 추론 : 소프트 맥스 층계산 y-hat(확률분포로 표현)
def inference(x): 

	#tf.get_variable에 사용될 initializer로 shape에 맞추어 모든값을 0으로 초기화
    init=tf.constant_initializer(value=0)
    
    #W:가중치 , b: bias로 출력층의 출력은 총(output) 10개라 bias도 10개 
    # x로 주로 [none, 784]가 입력되니까 [none x 10] + b [1 x 10]? = logit
    W = tf.get_variable("W", [784, 10],initializer=init) 
    b = tf.get_variable("b", [10],initializer=init) 
    
    #tf.nn.softmax(logits,axis,name) 
    #softmax = tf.exp(logits)/tf.reduce_sum(tf.logits,axis)
    
    #1 x 10의출력아녀??? 10개의 결과따로따로? [y1 = e^z1/z1~z10의값 y2 y3 .... y10]
    #그렇긴한데 none x 10이긴함..?? 미니배치라서 
    output = tf.nn.softmax(tf.matmul(x, W) + b)
    
    return output

 

일단 저는 이런 식으로 이해했습니다.

 

 

 

2. 손실 : 오차 함수로 오차값을 계산

위에서 나온 확률분포 계산(output = none x 10 )과 진짜 답인 y( none x 10 )를 매개변수로 받습니다.

def loss(output, y):

	#보니까 y도 none x 10이고 output도 none x 10임
    dot_product = y * tf.log(output)
    
    # axis=None은 기본값으로 모든 요소의 값을 합산하여 1개의 스칼라값을 반환
    # axis=0은 x축을 기준으로 여러 row를 한 개로 합침
    # axis=1은 y축을 기준으로 row 별로 존재하는 column들의 값을 합쳐 1개로 축소
    # 일반적으로 축(axis) i를 따라 축소하면 텐서 i번째 차원이 크기 1로 축소된다
    

    xentropy = -tf.reduce_sum(dot_product, reduction_indices=1)
    
    #axis=none이라서 scalar값?임
    loss = tf.reduce_mean(xentropy) 
    return loss

 

 

더보기

참고)

 

3. 학습 : 경사 하강법 수행
def training(cost, global_step):

	#경사하강법
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    
    #트레이닝 단계를 위한 카운터변수 : global_step //counter니까 0으로 초기화, 학습불가
    #train_op는 시스템 내에서 트레이닝 가능한 weights와 글로벌 단계의 진행을 업데이트한다 
    train_op = optimizer.minimize(cost,global_step=global_step) 
    return train_op

기록하는 함수 추가(tf.summary.scalar) -> 경사 하강법을 모델을 학습시킬 때마다 요약된 통계를 기록하면서 실행

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

 

 

4. 평가 : 검증/테스트 데이터셋으로 모델 평가
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))
    return accuracy

테스트 셋 , 검증 데이터로 검증하는 방법에 대해서는 2단원에서 자세히 설명하지 않았으므로 저도 넘어갑니다..

 

여기에도

기록하는 함수 추가(tf.summary.scalar) -> 경사 하강법을 모델을 학습시킬 때마다 요약된 통계를 기록하면서 실행

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

 

실제로 실행해보기
# 파라미터
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])
    
    
    
    
    output = inference(x)
    
    #실제 정답과 계산한 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_logs/",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} 
            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}
            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_logs/model-checkpoint", global_step=global_step)
            
            
        print ("Optimization Finished!")
        
        #검증이 끝나면 테스트 데이터를 통해 정확도를 출력한다 
        test_feed_dict = {x : mnist.test.images, y : mnist.test.labels}
        accuracy = sess.run(eval_op, feed_dict=test_feed_dict)
        print ("Test Accuracy:", accuracy)

최초실행

 

이쯤에서 멈췄습니다

92.2% 정도의 정확성을 보여줍니다.

텐서 보드로 시각화 한 모습

가장 낮은 지점은 둘 다 step 25.85k

Cost = 0.1713 / Validation_Error = 0.03

 

 

선풍기만 틀고 했더니 컴 전원이 나갔어요... 에어컨 틀고 그 밑에 두고 했습니다

 

그래프

 

 

 

 

 

3.12 텐서 보드로 계산 그래프와 학습 시각화 하기

텐서보드 사용법입니다.

1. 새로운 터미널을 켬

셸 -> 새로운 탭

터미널 2개

 

2. 가상 환경 시작 

conda activate tensorflow //가상환경이름

2.  텐서 보드 실행

tensorboard --logdir=<절대경로>

저기 적힌 경로에 생성되므로

tensorboard --logdir=/Users/iyu-jeong/logistic_logs/

해주면,

 

이런 식으로 뜨는데 chrome창에

http://localhost:6006/을 치면 

 

 

 

만약 오류가 나실 경우 저는

pip install tb-nightly

이거 설치해주니까 되더라고요 

 

3.13 텐서 플로에서 MNIST를 위한 다층 모델 만들기

 

위에서는 출력층과 입력 층만 있었는데

이제 ReLU뉴런을 사용하는 은닉층을 2개 넣어서 전방향 모델을 구성해보자

 

아래에서 보여줄 함수를 그림으로 표현하자면 대충 이렇다 (bias는 혼란을 줄이기 위해 표현하지 않음)

 

총함수는 다음과 같습니다

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):
    with tf.variable_scope("hidden_1"):
        hidden_1 = layer(x, [784, 256], [256]) 
    with tf.variable_scope("hidden_2"):
        hidden_2 = layer(hidden_1, [256, 256], [256])
    with tf.variable_scope("output"):
        output = layer(hidden_2, [256, 10], [10])
    return output


#출력층을 소프트맥스를 수행하며 오차를 계산하는 함수 
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])
    
    
    
    
    output = inference(x)
    
    #실제 정답과 계산한 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} 
            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}
            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}
        accuracy = sess.run(eval_op, feed_dict=test_feed_dict)
        print ("Test Accuracy:", accuracy)

 

 

로지스틱 회귀 일 때와 달라진 함수는 3개입니다.

 

 

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)

logit (z) = tf.matmul(input, W) + b를 계산한 후 

 relu함수에 로짓을 넣어 계산한 값을 return 해주는 layer함수 

 

layer함수를 은닉층 2개와 출력층 1개에 대하여 차례로 실행한 후 출력층에서 결과로 나온 y값=output(relu함수 적용)을 리턴한다

def inference(x):
    with tf.variable_scope("hidden_1"):
        hidden_1 = layer(x, [784, 256], [256]) 
    with tf.variable_scope("hidden_2"):
        hidden_2 = layer(hidden_1, [256, 256], [256])
    with tf.variable_scope("output"):
        output = layer(hidden_2, [256, 10], [10])
    return output

 

output을 손실 함수에 대입하는데

이는 손실(교차 엔트로피 손실을 이용)을 계산하는 동시에 소프트맥스를 수행하는 함수를 사용해서 손실을 계산한다.

def loss(output, y):
    xentropy = tf.nn.softmax_cross_entropy_with_logits(logits=output, labels=y) 
    loss = tf.reduce_mean(xentropy)
    return loss

 

 

전체 식으로 돌려보면 결과는 다음과 같다 

 

<결과>

97.9% 정도의 정확도로 동작합니다.

 

결과 시각화

그래프로 보니 확실히 성능이 더 좋은 것이 보이네요

 

<그래프>

 

 

이때,

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)

 

심층 신경망의 주의할 점

 

파라미터 초기화

심층 신경망의 성능은 해당 파라미터들의 효과적인 초기화에 크게 좌우된다

어려운 기본 확률적 경사 하강법으로 최적화하는 심층 신경망의 오차 곡면에는 많은 특징이 있는데,

이 문제는 모델에서 층의 수가 증가함에 따라 악화된다 

일단 현재로서는 초기화를 좋게 맞추는 것이 문제를 해결하는 하나의 방법이다 (다른 방법은 다음장)

 

파라미터 초기화가 중요한 이유를 그림으로 보자면

공들의 시작 위치가 파라미터의 처음 위치이다

모든 공들은 다른 지점에서 시작했지만 이들이 경사 하강법으로 각자의 최저구간을 찾아간다면

https://www.youtube.com/watch?v=IHZwWFHWa-w

최종엔 이렇게 될 것이고 

이 그림에서 보다시피 모든 공이 해당 함수의 진짜 최저점을 찾아 가는 건 아니다

이 그림에선 왼쪽 부분에서 시작한 공들만 최저 지점을 찾아갔다

 

이 그림을 보면 공이 어디서 시작했느냐가 중요한 역할을 했다고 볼 수 있다.

바로 이게 파라미터 초기화가 중요한 이유다

 

 

연구 결과로 하면 ReLU단위에 대한 연구에서는 망에서 가중치 분산은 2/n 이 돼야 한다고 설명한다

 n은 뉴런으로 들어가는 입력의 수다

 

예를 들어 위의 layer함수에서 tf.random_normal_initializer를 tf.random_uniform_initilizer로 바꾸면 성능이 심하게 저하된다고 한다.

 

 

저는 가중치 분산을 조금 다르게 해서 어떻게 달라지는지 한번 확인해 봤습니다.

1. weight_stddev = (4.0/weight_shape[0])**0.5

시작

최종

캡쳐를 까먹음.. 근데 2일때랑 결과가 거의같네요

 

텐서보드

 

2.  weight_stddev = (10.0/weight_shape[0])**0.5

 

시작

최종

텐서보드

 

 

정확도 88퍼정도

 

3.  weight_stddev = (20.0/weight_shape[0])**0.5

 

최종

텐서보드

 

정확도 77퍼정도

 

 

 

분산값이 왜 2/n에서 가장 좋은지는 모르겠지만

-> 실험적 결과라고 합니다 아직 수학적으로 증명은 못했고 그냥 그렇구나 라네요

여튼 같은 코드여도 분산이 달라지면 매우 성능이 낮은 모델이 나오는 걸 보아

분산의 값과 초기값이 매우 중요한 건 알 수 있었습니다.

 

728x90