본문 바로가기

케라스 창시자에게 배우는 딥러닝(DACOS)

3. 케라스와 텐서플로 소개

 

[목차]

3-1) 텐서플로란?

3-2) 케라스란?

3-3) 케라스와 텐서플로의 간략한 역사

3-4) 딥러닝 작업 환경 설정하기

3-5) 텐서플로 시작하기

3-6) 신경망의 구조: 핵심 keras API 이해하기

 

 

 

3-1) 텐서플로란?

 

텐서플로: 텐서에 대한 수학적 표현이 가능한 파이썬 기반 무료 오픈소스 머신 러닝 플랫폼

 

특징

- 미분 가능한 어떤 표현식에 대해서도 자동 그레이디언트 계산 가능

- GPU, TPU에서도 실행 가능

- 계산을 여러 머신에 쉽게 분산 가능

- 애플리케이션 실전 환경에 쉽게 배포 가능

 

 

 

3-2) 케라스란?

 

케라스: 텐서플로 위에 구축된 파이썬용 딥러닝 API

케라스와 텐서플로, 하드웨어의 동작과 기능

 

특징

- 어떤 종류의 딥러닝 모델도 쉽게 만들고 훈련할 수 있는 환경 제공

- 딥러닝 개발 관련 여러 함수, 메소드 지원 (층, 옵티마이저, 손실, 측정 지표 등)

- 다양한 사용자 층에 따라 다양한 워크플로 지원

- .fit() 호출해 프레임워크가 알아서 처리하도록 할 수 있음

- 세부 내용 제어 가능 (넘파이와 비슷)

 

 

 

3-3) 케라스와 텐서플로의 간략한 역사

 

케라스: 2015.03 출시

- 원래 씨아노(자동 미분 가능 딥러닝 라이브러리)를 위한 라이브러리

- 텐서플로 릴리스된 후 발전하여 텐서플로 사용하는 단일 백엔드 API가 됨

 

텐서플로: 2015.11 출시

- 새로운 사용자 유입시킴 > 케라스가 텐서플로 앱 만드는 사용자 친화적인 방법 제공

 

 

 

3-4) 딥러닝 작업 환경 설정하기

 

1. GPU 사용

- GPU 구입 혹은 코랩의 무료 GPU 런타임 사용

- 5~10배 가량 속도 개선

 

2. 유닉스 워크스테이션 사용

- Windows 추천하지 않음

- 우분투 듀얼 부팅 혹은 WSL 활용하여 윈도우에서 딥러닝 실행

 

3. 주피터 노트북

- 긴 실험을 작은 단위로 쪼개 독립적으로 실행 가능

- 코랩으로 상당 부분 대체할 수 있음

- 코랩에서 런타임 유형 변경을 선택하고 하드웨어 가속기를 GPU로 설정함

- 코랩에서 !pip install 패키지이름 으로 필요한 패키지 설치 가능

 

 

 

3-5) 텐서플로 시작하기

 

저수준 텐서 연산 (텐서플로)

1) 텐서

2) 텐서 연산 (메소드, 함수 등)

3) 역전파 (그레이디언트 계산)

 

고수준 딥러닝 개념 (케라스)

1) 층 (dense)

2) 손실 함수 (학습에 사용되는 피드백 신호)

3) 옵티마이저

4) 측정 지표 (이를 기반으로 모델 성능 평가)

5) 훈련 루프 (미니 배치 확률적 경사 하강법 수행)

 

 

상수 텐서와 변수

import tensorflow as tf
x = tf.ones(shape=(2, 1))
print(x)

 

텐서플로에서 어떠한 작업을 위해서는 라이브러리 호출, 그 후에 텐서를 생성해야 함

 

x = tf.random.normal(shape=(3, 1), mean=0., stddev=1.)
print(x)

 

정규분포를 따르는 랜덤한 (3, 1) 꼴의 텐서를 생성한 예

비슷하게 메소드를 변경하여 특정 숫자들 사이의 수를 랜덤추출하는 것도 가능함

 

 

이때, 텐서는 넘파이와 비슷하게 여러 가지 수학적 계산이 가능

 

넘파이: 값 대체, 할당이 가능한 변수

텐서: 연산은 가능하나 특정 값을 새로 할당하거나 변경할 수 없는 상수

라는 점에서 차이남

 

 

v = tf.Variable(initial_value=tf.random.normal(shape=(3, 1)))
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy=
array([[-0.27607322],
       [-2.4914272 ],
       [ 1.2171313 ]], dtype=float32)>
 

텐서 연산 위해 tf.Variable() 메소드 존재

이를 이용해서 모델을 훈련하며 텐서의 상태를 지속적으로 업데이트할 수 있음

() 안에는 변수처럼 취급할 텐서의 초깃값에 대한 정보를 넣어줌

 
v.assign(tf.ones((3, 1)))
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy=
array([[1.],
       [1.],
       [1.]], dtype=float32)>

 

tf.assign() 메소드로 변수 취급하는 텐서의 값 조정 가능

위 예제에서는 텐서의 상태를 모두 1로 변경해 줌

 

 

v[0, 0].assign(3.)
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy=
array([[3.],
       [1.],
       [1.]], dtype=float32)>
v.assign_add(tf.ones((3, 1)))
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy=
array([[4.],
       [2.],
       [2.]], dtype=float32)>

 

특정 요소에 접근해 값 수정 가능,

텐서에 텐서를 더하는 작업도 가능

 

 

GradientTape API 다시 살펴보기

input_var = tf.Variable(initial_value=3.)
with tf.GradientTape() as tape:
   result = tf.square(input_var)
gradient = tape.gradient(result, input_var)

 

텐서의 변수화를 진행하고, 

tape. 메소드를 사용해 그레이디언트를 계산하는 코드

tape.gradient( result, input_var ) 의 형태

 

input_const = tf.constant(3.)
with tf.GradientTape() as tape:
   tape.watch(input_const)
   result = tf.square(input_const)
gradient = tape.gradient(result, input_const)

 

입력 텐서가 상수인 경우에도 그레이디언트 계산 가능

단 tape.watch() 를 호출해 추적한다는 것을 알려줘야 함

( 훈련 가능한 변수만을 감시해서 비용을 줄이기 위해서 )

 

time = tf.Variable(0.)
with tf.GradientTape() as outer_tape:
    with tf.GradientTape() as inner_tape:
        position =  4.9 * time ** 2
    speed = inner_tape.gradient(position, time)
acceleration = outer_tape.gradient(speed, time)

 

이계도 그레이디언트도 계산 가능

위 예제에서는 (위치, 시간) 을 (결과, 입력) 으로 두고 한번 그레이디언트를 계산한 뒤

(속도, 시간)에 대한 그레이디언트를 한번 더 구하도록 하고 있음.

 

 

텐서플로 선형 분류기

num_samples_per_class = 1000
negative_samples = np.random.multivariate_normal(
    mean=[0, 3],
    cov=[[1, 0.5],[0.5, 1]],
    size=num_samples_per_class)
positive_samples = np.random.multivariate_normal(
    mean=[3, 0],
    cov=[[1, 0.5],[0.5, 1]],
    size=num_samples_per_class)

 

2000개의 샘플이 두 그룹으로 나뉘도록 생성해줌

 

 

이런 그림이 나왔을 때

 

input_dim = 2
output_dim = 1
W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim)))
b = tf.Variable(initial_value=tf.zeros(shape=(output_dim,)))

 

입력은 속성 2개, 출력은 속성 1개라고 정의해준 다음 각각 가중치에 대한 텐서를 변수화함

 

 

배치 훈련 루프 방식으로 손실을 계산

learning_rate = 0.1

def training_step(inputs, targets):
    with tf.GradientTape() as tape:
        predictions = model(inputs)
        loss = square_loss(targets, predictions)
    grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss, [W, b])
    W.assign_sub(grad_loss_wrt_W * learning_rate)
    b.assign_sub(grad_loss_wrt_b * learning_rate)
    return loss

 

정방향 패스 함수를 사용하여 입력에 대한 예측치를 계산함

손실을 평균 제곱 오차 손실 함수를 이용하여 (실제 값- 예측 값)에 제곱근을 씌움

그리고 가중치에 대한 손실의 그레이디언트를 구함

해당 함수가 호출될 때마다 이 손실을 반환함

 

for step in range(40):
    loss = training_step(inputs, targets)
    print(f"{step}번째 스텝의 손실: {loss:.4f}")

 

위의 배치 훈련 루프를 40번동안 돌게 해서 가중치를 지속적으로 업데이트함.

출력 결과를 확인하여 손실이 안정되는 지점을 찾을 수 있음.

 

 

최종적으로 위와 같은 선형 모델과 이에 따른 분류 결과를 시각화할 수 있음 

 

 

 

3-6) 신경망의 구조: 핵심 keras API 이해하기

 

1. 층

: 대부분 가중치라는 층의 상태를 가짐

 

밀집 연결 층, 완전 연결 층, 밀집 층, 순환 층 등 다양한 종류의 층마다 적절한 텐서 포맷과 데이터를 처리하는 방식이 다름.

 

케라스의 Layer 클래스를 통해 구현됨

from tensorflow import keras

class SimpleDense(keras.layers.Layer):

    def __init__(self, units, activation=None):
        super().__init__()
        self.units = units
        self.activation = activation

    def build(self, input_shape):
        input_dim = input_shape[-1]
        self.W = self.add_weight(shape=(input_dim, self.units),
                                 initializer="random_normal")
        self.b = self.add_weight(shape=(self.units,),
                                 initializer="zeros")

    def call(self, inputs):
        y = tf.matmul(inputs, self.W) + self.b
        if self.activation is not None:
            y = self.activation(y)
        return y

 

코드로는 이렇게 구현할 수 있음

자기 자신과 입력 데이터의 형태에 따라 가중치 값을 랜덤하게 설정해두고 층을 빌드함

 

*** 모든 층은 특정 크기의 입력 텐서를 받아 특정 크기의 출력 텐서만 반환할 수 있음.

 

 

모델 구조를 정한 후 컴파일 진행

model = keras.Sequential([keras.layers.Dense(1)])
model.compile(optimizer="rmsprop",
              loss="mean_squared_error",
              metrics=["accuracy"])

 

다음과 같이 컴파일 진행 시엔 옵티마이저, 손실함수, 측정 지표 선정 필요.

model.compile(optimizer=keras.optimizers.RMSprop(),
              loss=keras.losses.MeanSquaredError(),
              metrics=[keras.metrics.BinaryAccuracy()])

 

동일한 내용으로 다음과 같이도 코드 작성 가능

이러한 경우에는 () 안의 객체들에 대해 상세히 설정할 수 있다는 장점을 가지고 있음.

 

*** 이때, 데이터 타입에 따라 손실 함수를 선정하는 것이 매우 중요함.

 

history = model.fit(
    inputs,
    targets,
    epochs=5,
    batch_size=128
)

 

.fit() 메소드를 사용하여 데이터로 모델을 훈련시킴

 

 

검증 데이터로 모니터링

model = keras.Sequential([keras.layers.Dense(1)])
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=0.1),
              loss=keras.losses.MeanSquaredError(),
              metrics=[keras.metrics.BinaryAccuracy()])

indices_permutation = np.random.permutation(len(inputs))
shuffled_inputs = inputs[indices_permutation]
shuffled_targets = targets[indices_permutation]

num_validation_samples = int(0.3 * len(inputs))
val_inputs = shuffled_inputs[:num_validation_samples]
val_targets = shuffled_targets[:num_validation_samples]
training_inputs = shuffled_inputs[num_validation_samples:]
training_targets = shuffled_targets[num_validation_samples:]
model.fit(
    training_inputs,
    training_targets,
    epochs=5,
    batch_size=16,
    validation_data=(val_inputs, val_targets)
)

 

모델이 잘 훈련 되었는지 확인하기 위해 훈련 데이터셋 내부에서 검증 데이터셋을 따로 빼서 해당 셋에 대한 모델의 적용 결과를 모니터링하기도 함.