본문 바로가기

Python

텐서플로(tensorflow)를 이용한 인공신경망 구현

반응형
# 텐서플로를 이용한 인공신경망 구현

import tensorflow as tf

# MNIST data set 로드하기
'''
    MNIST 데이터 셋
        - 손으로 쓴 글씨 (0~9까지)의 이미지 
        - 훈련 데이터와 테스트 데이터를 반환
        - 각각 입력 이미지와 해당 이미지의 레이블로 구성됨 
'''
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

# 이미지 데이터를 float32로 변경하기
'''
    astype()함수 사용해서 이미지 데이터 자료형 변경
    => 신경망 모델의 입력으로 사용하기 위함 
'''
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')

# 28 * 28 = 784 펼치기(reshape)
'''
    2차원 배열에서 1차원 배열로 변환 (전처리)
        - MNIST 데이터셋 이미지 : 각각 28 * 28 크기의 2차원 배열임
        - 신경망 : 입력으로 사용하기 위해서는 1차원 배열 형태로 변환해야함
        - -1 : 행의 크기는 자동으로 결정, 
          784 : 열의 크기로 지정
        - x_train이 6만개의 이미지, 각 이미지 크기가 28*28이라면 
          reshape([-1, 784])를 적용하면 6만 * 784 크기의 2차원 배열로 변환됨
          - 각 행은 하나의 이미지에 해당됨 
          - 픽셀 값들이 일렬로 나열된 형태로 저장     
'''
x_train, x_test = x_train.reshape([-1, 784]), x_test.reshape([-1, 784])

# 입력값의 정규화(normalize) : [0, 255] => [0, 1]
'''
    # MNIST 데이터 세트의 이미지는 픽셀 값(0~255)
        - 이미지 데이터를 신경망 모델에 입력으로 사용할 때, 
          일반적으로 픽셀값 범위를 0부터 1사이로 제한하는 것이 성능이 즣음 
          => 가중치 업데이트와 같은 연산에서도 안정성을 높여줌 
'''
x_train, x_test = x_train / 255 , x_test / 255

#One-hot encoding 레이블로 변경
'''
    one_hot은 다중 클래스 분류 문제를 해결하기 위함
        - 신경망 모델에서는 다중 클래스 분류를 수행하기 위해서는 출력값 적절하게 설정해야함 
        - 각 클래스에 대한 확률 출력하기 위해서 범주형 데이터 형식인 one-hot 인코딩을 사용함
        - 각 클래스에 해당하는 인덱스에 1을 표시하고, 나머지 인덱스는 0을 표시
            - 각 클래스의 레이블을 벡터 형태로 표시
            - 변환된 레이블 벡터는 신경망 모델의 출력과 비교하여 정확성 개선, 손실함수 계산하는 작업에 사용됨 
'''
y_train, y_test = tf.one_hot(y_train, depth=10), tf.one_hot(y_test, depth=10)

# print(y_train)
# print(x_train)

# ---전처리 완료---

# 학습에 사용되는 파라미터 정의
'''
    * 학습률 
        - 경사 하강법(gradient descent) 알고리즘 
            - 한번에 업데이트 되는 가중치의 크기를 조절하는 역할을함
        - 학습률 0~1 사이로 설정됨
            - 너무 적으면 속도 느려짐, 너무 크면 문제
    * 학습 횟수
        - 전체 데이터 세트를 몇 번 반복하여 학습할지 나타냄 
        - 너무 크면 과적합(overfitting)의 가능성   
    
    * 배치 개수 
        - 한번에 모든 데이터를 처리하는 것이 아니라
          일부 데이터를 묶어서 처리함 -> 메모리 사용량 줄이고, 계산 속도 향상 

    * display_step
        - 손실 함수 값은 모니터링 지표중 하나
        - 몇 번째 학습마다 손실 함수의 값을 출력할지 결정
        - 1: 매 학습마다 출력 
    
    * input_size
        - 입력의 크기는 이미지의 특징 개수
        - MNIST 데이터 세트는 이미지 크기가 28 * 28 = 784 픽셀
    
    * hidden1_size1
        - 은닉층(hidden layer)의 크기 
        - 신경망 모델의 은닉층은 입력과 출력 사이에 있는 층으로 ,
          중간에 특성을 학습하고 추출하는 역할을 함
          
    * output_size
        - 모델의 출력층 노드의 크기
        - 숫자 0부터 9까지 총 10개의 클래스를 분류 (다중 클래스 분류) 
        
'''
learning_rate = 0.001   # 학습률
num_epochs = 30          # 학습 횟수
batch_size = 256        # 배치 개수
display_step = 1        # 손실함수 출력 주기
input_size = 784        # 28 * 28
hidden1_size = 256     # 은닉층의 크기 - 노드의 개수
hidden2_size = 256
output_size = 10        # 출력층의 킈기

# 학습 전 데이터 섞기(shuffle)

'''
    * from_tensor_slices()
        - 텐서들을 슬라이스하여 데이터셋을 생성하는 함수 
        - 이미지와 해당 레이블로 이루어진 훈련 데이터
            - 묶어서 데이터셋을 생성함 
    * shuffle()
        - 데이터 섞기
        - 모델이 데이터의 순서에 의존하지 않고 학습되도록 하기 위함  
    * batch()
        - 데이터셋을 배치로 나눔
        - 여러 개의 데이터 샘플을 함께 처리
        - 메모리 효율성과 속도 개선 
'''
train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_data = train_data.shuffle(60000).batch(batch_size)

# ANN 클래스 정의
'''
    * 내트웍스의 가중치와 편향 변수 초기화
        - tf.Variable()로 정의
        - 은닉층의 가중치와 편향
    
    * return logists
       - 신경망의 예측값
    
    * 데코레이터(decorator)
        - 파이썬에서 함수나 클래스를 수정하거나 기능을 추가할 때 사용되는 문법적인 요소임   
        - 기존의 함수나 클래스를 변경하지 않으면서 새로운 기능을 추가함   
'''

class ANN(object):
    def __init__(self):
        self.W1 = tf.Variable(tf.random.normal(shape=[input_size, hidden1_size]))
        self.b1 = tf.Variable(tf.random.normal(shape=[hidden1_size]))
        self.W2 = tf.Variable(tf.random.normal(shape=[hidden1_size, hidden2_size]))
        self.b2 = tf.Variable(tf.random.normal(shape=[hidden2_size]))
        self.W_output = tf.Variable(tf.random.normal(shape=[hidden2_size, output_size]))
        self.b_output = tf.Variable(tf.random.normal(shape=[output_size]))

    # 활성함수 적용
    def __call__(self, x):
        H1_output = tf.nn.relu(tf.matmul(x, self.W1) + self.b1)
        H2_output = tf.nn.relu(tf.matmul(H1_output, self.W2) + self.b2)
        logits = tf.matmul(H2_output, self.W_output) + self.b_output
        return logits

# Loss Function(손실함수) Cross-Entropy
'''
    * 데코레이터(decorator)
        - 파이썬에서 함수나 클래스를 수정하거나
          기능을 추가할 때 사용되는 문법임
    * TensorFlow에서의 @tf.function 데코레이터
        - 그래프로 함수를 변환하여 성능을 향상시키는 역할을 함
    * Reduce mean()
        - 평균 손실은 모델의 전체 손실을 나타내는 지표로 사용됨
        - 텐서의 손실 평균을 계산하는 함수임
            - 교차 엔트로피 손실의 평균을 계산해서 반환함
            - 예측값 logits와 실제 레이블 y 사이의 교차 엔트로피 손실 계산 
'''

@tf.function
def cross_entropy(logits, y):
    return tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))

# Adam
'''
    * Adam
        1) 최적화 알고리즘을 구현한 TensorFlow의 옵티마이저 클래스
        2) 자동 미분을 활용하여 모델의 가중치를 업데이트하는 알고리즘
        3) 학습률 - 각 파라미터 업데이트 크기를 결정하는 요소 
'''
optimizer = tf.optimizers.Adam (learning_rate)

# 최적화를 위한 함수
'''
    train_step() 함수
        1) 모델, 입력 데이터 x, 실제 레이블 y를 사용하여
           한단계의 학습을 수행하고, 모델의 변수들을 업데이트 하는 역할을 함 
        2) tf.GradientTape()
            - 블록 안에서의 모델의 연산들을 기록함
            - 입력 데이터x를 받아서 예측값 y_pred을 계산함
            - 이것을 손실 함수에 적용하여 손실 loss를 계산함
        3) tape.gradient()
            - 손실에 대한 모델의 변수들의 gradient(기울기) 계산
        4) optimizer.apply_gradients()
            - 계산된 gradients(기울기) 사용해서 모델의 변수들을 업데이트함
            - 옵티마이저에 의해 그레디언트 기반 최적화 알고리즘을 수행 
'''
@tf.function
def train_step(model, x, y):
    with tf.GradientTape() as tape:
        y_pred = model(x)
        loss = cross_entropy(y_pred, y)
    gradients= tape.gradient(loss, vars(model).values())
    optimizer.apply_gradients(zip(gradients, vars(model).values()))

# 모델의 정확도 정의
'''
    예측값 y_pred와 실제 레이블 y를 사용하여
    모델의 정확도를 계산하는 역할
    => 모델의 성능을 평가하고 모델의 예측 결과를 정량적으로 측정할 수 있음
        - tf.equal() 
            - tf.argmax() : 가장 큰 원소의 인덱스를 가져옴
            - 예측값과 실제 레이블이 일치하는지 여부를 계산함 
                - 일치하면 true 아니면 false 반환 
        - tf.cast()
            - 데이터 타입을 float32 타입으로 변경
            - true = 1.0 / false = 0.0 으로 표현 
'''

@tf.function
def compute_accuracy(y_pred, y):
    correct_prediction = tf.equal(tf.argmax(y_pred, 1), tf.argmax(y,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    return accuracy

# ANN 모델 객체 생성
ANN_model = ANN()

# model optimization
for epoch in range(num_epochs):
    average_loss = 0

    # 총 배치 개수 = 전테 데이터셋을 배치 크기로 나눔
    total_batch = int(x_train.shape[0] / batch_size)

    #optimize for ebery batch
    for batch_x, batch_y in train_data:
        # 손실(loss) 업데이트
        _, current_loss = train_step(ANN_model, batch_x, batch_y), cross_entropy(ANN_model(batch_x), batch_y)
        # 평균 손실값 계산
        average_loss += current_loss / total_batch

    # 매 epoch마다 출력
    if epoch % display_step == 0:
        print("Epoch: %d, Loss: %f" %((epoch+1), average_loss))

# 정확도 출력
print("Accuracy : %f" %(compute_accuracy(ANN_model(x_test)), y_test))

아직 미완성. 추후 보완 예정. 

반응형