딥러닝
Ch3
나몬
2024. 3. 24. 23:34
3.1 텐서플로란?
- 텐서플로(TensorFlow)는 구글에서 만든 파이썬 기반의 무료 오픈 소스 머신 러닝 플랫폼
- 미분 가능한 어떤 표현식에 대해서도 자동으로 그레이디언트를 계산할 수 있으므로 머신 러닝에 매우 적함
- CPU 뿐만 아니라 고도로 병렬화된 하드웨어 가속기인 GPU와 TPU에서도 실행할 수 있음
- 텐서플로 프로그램은 C++, (브라우저 기반 어플리케이션을 위한) 자바스크립트(JavaScript), (모바일 장치나 임베디드 장치(embedded device)에서 실행하는 어플리케이션을 위한) 텐서플로 라이트(TensorFlow Lite) 등과 같은 다른 런타임(runtime)에 맞게 변환할 수 있음
3.2 케라스란?
- 케라스는 텐서플로 위에 구축된 파이썬용 딥러닝 API로 어떤 종류의 딥러닝 모델도 쉽게 만들고 훈련할 수 있는 방법을 제공
케라스와 텐서플로: 텐서플로는 저수준 텐서 컴퓨팅 플랫폼이고 케라스는 고수준 딥러닝 API다
3.3 케라스와 텐서플로의 간략한 역사
생략
3.4 딥러닝 작업 환경 설정하기
머신러닝 코드를 실행하기 위해선 GPU를 사용하는게 효율적
따라서 추가적인 GPU를 구매하기 싫다면 colab이나 클라우드 컴퓨팅 자원이 필요
코랩은 무료!
3.5 텐서플로 시작하기
신경망 훈련은 다음과 같은 개념을 중심으로 진행
- 첫째, 모든 현대적인 머신 러닝의 기초가 되는 인프라인 저수준 텐서 연산에는 다음과 같은 텐서플로 API로 변환
- 텐서(신경망의 상태를 저장하는 특졀한 텐서(변수)도 포함)
- 덧셈, relu, matmul 같은 텐서 연산
- 수학 표현식의 그레이디언트를 계산하는 방법인 역전파(텐서플로의 GradientTape객체를 통해 처리됨)
- 둘째, 고수준 딥러닝 개념. 이는 다음과 같은 케라스 API로 변환
- 모델을 구성하는 층
- 학습에 사용하는 피드백 신호를 정의하는 손실 함수
- 학습 진행 방법을 결정하는 옵티마이저
- 정확도처럼 모델의 성능을 평가하는 측정 지표
- 미니 배치 확률적 경사 하강법을 수행하는 훈련 루프
상수 텐서와 변수
- 모두 1 또는 0인 텐서
import tensorflow as tf
x = tf.ones(shape=(2, 1))
print(x)
x = tf.zeros(shape=(2, 1))
print(x)
- 랜덤 텐서
# 평균이 0이고 표준 편차가 1인 정규분포에서 랜덤한 값으로 만든 텐서
x = tf.random.normal(shape=(3, 1), mean=0., stddev=1.)
print(x)
# 0과 1사이의 균등 분포(uniform distribution)에서 뽑은 랜덤한 값으로 만든 텐서
x = tf.random.uniform(shape=(3, 1), minval=0., maxval=1.)
print(x)
- 넘파이 배열과 텐서플로 텐서 사이의 큰 차이점은 텐서플로 텐서에는 값을 할당할 수 없다
- 모델을 훈련하려면 모델의 상태, 즉 일련의 텐서를 업데이트 해야한다. 이를 위해 변수가 있다.
- 텐서플로 변수 만들기
v = tf.Variable(initial_value=tf.random.normal(shape=(3, 1)))
print(v)
- 텐서플로 변수에 값 할당하기
v.assign(tf.ones((3, 1)))
- 변수 일부에 값 할당하기
v[0, 0].assign(3.)
- assign_add() 사용하기 - assign_add()와 assign_sub()은 각각 +=, -=과 동일
v.assign_add(tf.ones((3, 1)))
텐서 연산
- 기본적인 수학 연산
a = tf.ones((2, 2))
b = tf.square(a) # 제곱을 계산
c = tf.sqrt(a) # 제곱근을 계산
d = b + c # 두 텐서를 더한다.(원소별 연산)
e = tf.matmul(a, b) # 두 텐서의 점곱을 계산
e *= d # 두 텐서를 곱한다.
※ 앞의 연산이 모두 바로 실행된다. 넘파이처럼 언제든지 현재 결과값을 출력할 수 있는데 이를 즉시 실행(eager execution) 모드라고 부른다.
GradientTape API 다시 살펴보기
- GradientTape사용하기
input_var = tf.Variable(initial_value=3.)
with tf.GradientTape() as tape:
result = tf.square(input_var)
gradient = tape.gradient(result, input_var)
※ gradient = tape.gradient(loss, weights)와 같이 가중치에 대한 모델 손실의 그레이디언트를 계산하는데 가장 널리 사용되는 방법
- 상수 텐서 입력과 함께 GradientTape 사용하기
input_const = tf.constant(3.)
with tf.GradientTape() as tape:
tape.watch(input_const) # 상수 텐서의 경우 tape.watch()를 호출하여 추적한다는 것을 수동으로 알려주어야 함
result = tf.square(input_const)
gradient = tape.gradient(result, input_const)
- 그레이디언트 테이프를 중첩하여 이계도 그레이디언트 계산하기
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)
※ 이를 통해 속도와 가속도를 구할수 있음
엔드 투 엔드 예제
텐서플로 선형 분류기
- 2D 평면에 두 클래스의 랜덤한 포인트 생성하기
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, 2) 크기의 한 배열로 쌓기
inputs = np.vstack((negative_samples, positive_samples)).astype(np.float32)
- (0과 1로 구성된)타깃 생성하기
targets = np.vstack((np.zeros((num_samples_per_class, 1), dtype="float32"),
np.ones((num_samples_per_class, 1), dtype="float32")))
- 두 클래스의 포인트를 그래프로 그리기
import matplotlib.pyplot as plt
plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:, 0])
plt.show()
2D 평면에 놓인 두 클래스의 랜덤한 포인트
- 선형 분류기의 변수 만들기
input_dim = 2 # 입력은 2D 포인트
output_dim = 1 # 출력 예상은 샘플당 하나의 점수(0에 가까우면 클래스 0으로 예측, 1에 가까우면 클래스 1로 예측)
W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim)))
b = tf.Variable(initial_value=tf.zeros(shape=(output_dim,)))
- 정방향 패스 함수
def model(inputs):
return tf.matmul(inputs, W) + b
※ 어떤 입력 포인트 [x,y]가 주어지면 예측 값은 prediction = [[w1], [w2]] · [x,y] + b = w1 * x + w2 * y + b가 됨
- 평균 제곱 오차 손실 함수
def square_loss(targets, predictions):
# per_sample_loosses는 targets나 predictions와 크기가 같은 텐서이며 각 샘플의 손실 값을 담고 있다.
per_sample_losses = tf.square(targets - predictions)
# 샘플당 손실 값을 하나의 스칼라 손실 값으로 평균한다. reduce_mean함수가 이런 작업을 수행
return tf.reduce_mean(per_sample_losses)
- 훈련 스텝 함수
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}")
- 선형 모델이 훈련 데이터 포인트를 어떻게 분류하는지 그려보기
predictions = model(inputs)
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5)
plt.show()
훈련 입력에 대한 모델의 예측: 훈련 타깃과 매우 비슷하다
- 직선으로 나타낸 선형 모델
# 직선을 그리기 위해 -1~4 사이에 일정한 간격을 가진 100개의 숫자를 생성
x = np.linspace(-1, 4, 100)
# 사실 100개의 x 축 좌표를 만들 필요 없이 시작과 종료 위치만 있어도 됩니다.
# x = [-1, 4]
y = - W[0] / W[1] * x + (0.5 - b) / W[1] # 사용할 직선의 방정식
plt.plot(x, y, "-r") # 직선을 그린다("-r"은 빨간색을 의미)
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5) # 동일한 그래프에 모델의 예측을 나타낸다.
plt.show()
직선으로 나타낸 선형 모델
3.6. 신경망의 구조: 핵심 Keras API 이해하기
- 층: 딥러닝의 구성 요소
- 신경망의 기본 데이터 구조는 층(layer)
- 어떤 종류의 층은 상태가 없지만 대부분의 경우 가중치(weight)라는 층의 상태를 가짐
- (samples, features) 크기의 랭크-2 텐서에 저장된 간단한 벡터 데이터는 밀집 연결 층(densely connected layer)으로 처리하는 경우가 많음(케라스에서는 Dense 클래스에 해당)
- 이를 완전 연결 층(fully connected layer) 또는 밀집 층(dense layer)이라고도 부름
- (samples, timesteps, features) 크기의 랭크-3 텐서에 저장된 시퀀스 데이터는 일반적으로 LSTM같은 순환 층(recurrent layer)이나 1D 합성곱 층(convoulution layer)(Conv 1D)으로 처리
- Layer의 서브클래스(subclass)로 구현한 Dense 층
from tensorflow import keras
class SimpleDense(keras.layers.Layer): # 모든 케라스 층은 Layer클래스를 상속
def __init__(self, units, activation=None):
super().__init__()
self.units = units
self.activation = activation
def build(self, input_shape): # build() 메서드에서 가중치를 생성
input_dim = input_shape[-1]
self.W = self.add_weight(shape=(input_dim, self.units), # add_weight()는 가중치를 간편하게 만들 수 있는 메서드
initializer="random_normal")
self.b = self.add_weight(shape=(self.units,),
initializer="zeros")
def call(self, inputs): # call() 메서드에서 정방향 패스 계산을 정의
y = tf.matmul(inputs, self.W) + self.b
if self.activation is not None:
y = self.activation(y)
return y
my_dense = SimpleDense(units=32, activation=tf.nn.relu) # 앞서 정의한 층의 인스턴스를 만든다.
input_tensor = tf.ones(shape=(2, 784)) # 테스트 입력을 만든다.
output_tensor = my_dense(input_tensor) # 이 입력으로 층을 함수처럼 호출
print(output_tensor.shape)
자동 크기 추론: 동적으로 층 만들기
- 층 호환(layer compatibility) 개념은 모든 층이 특정 크기의 입력 텐서만 받고, 특정 크기의 출력 텐서만 반환한다는 사실을 의미
- 다음 예를 생각해 보자
from tensorflow.keras import layers
layer = layers.Dense(32, activation="relu") # 32개의 출력을 가진 밀집 층
- 이 층은 첫 번째 차원이 32인 텐서를 반환
- 입력으로 32차원의 벡터를 기대하는 후속 층에만 연결할 수 있음
- 층에서 모델로
- 모델의 구조는 가설 공간(hypothesis space)을 정의
- '컴파일' 단계: 학습 과정 설정
- 모델 구조를 정의하고 난 후 다음 세가지를 더 선택해야 함
- 손실 함수(loss function)(목적 함수(objective function)): 훈련 과정에서 최소화할 값
- 현재 작업에 대한 성공의 척도. 옵티마이저(optimizer): 손실 함수를 기반으로 네트워크가 어떻게 업데이트될지 결정
- 특정 종류의 확률적 경사 하강법(SGD)으로 구현. 측정 지표(metric): 훈련과 검증 과정에서 모니터링할 성공의 척도
- compile()메서드는 훈련 과정을 설정
- 이 메서드의 매개변수는 optimizer, loss, metrics(리스트)
- 모델 구조를 정의하고 난 후 다음 세가지를 더 선택해야 함
model = keras.Sequential([keras.layers.Dense(1)]) # 선형 분류기를 정의한다.
model.compile(optimizer="rmsprop", # 옵티마이저 이름을 지정. 여기서는 RMSprop
loss="mean_squared_error", # 손실 이름을 평균 제곱 오차로 지정
metrics=["accuracy"]) # 측정 자료를 리스트로 지정. 여기서는 정확도
- 다음과 같이 매개별수를 인스턴스 객체로 지정할 수도 있음
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.MeanSquaredError(),
metrics=[keras.metrics.BinaryAccuracy()])
fit() 메서드 이해하기
- fit() 메서드는 훈련 루프를 구현
- 다음은 fit()메서드의 주요 매개변수
- 훈련할 데이터(입력과 타깃): 일반적으로 넘파이 배열이나 텐서플로 Dataset 객체로 전달
- 훈련할 에포크(epoch)횟수: 전달할 데이터에서 훈련 루프를 몇 번이나 반복할지 알려 줌
- 미니 배치 경사 하강법의 각 에포크에서 사용할 배치 크기: 가중치 업데이트 단계에서 그레이디언트를 계산하는 데 사용될 훈련 샘플 개수를 말함
- 넘파이 데이터로 fit()메서드 호출하기
history = model.fit(
inputs, # 입력 샘플(넘파이 배열)
targets, # 훈련 타깃(넘파이 배열)
epochs=5, # 이 데이터에서 훈련 루프를 다섯 번 반복
batch_size=128 # 훈련 루프는 128개의 샘플 배치로 이 데이터를 순회
)
- fit()을 호출하면 History 객체가 반환
- 이 객체는 딕셔너리인 history 속성을 가지고 있음
- 이 딕셔너리는 "loss"또는 특정 측정 지표 이름의 키와 각 에포크 값의 리스트를 매핑
history.history
검증 데이터에서 손실과 측정 지표 모니터링하기
- 새로운 데이터에 모델이 어떻게 동작하는지 예상하기 위해 훈련 데이터의 일부를 검증 데이터(validation data)로 떼어 놓는 것이 표준적인 방법
- validation_data 매개변수 사용하기
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]
# 훈련 입력과 타깃의 30%를 검증용으로 떼어 놓는다.(검증 손실과 측정 지표 계산을 위해 훈련 데이터에서 이 샘플을 제외시켜 보관)
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)
)
- 훈련이 끝난 후 검증 손실과 측정 지표를 계산하고 싶다면 evaluate() 메서드를 사용할 수 있음
loss_and_metrics = model.evaluate(val_inputs, val_targets, batch_size=128)
추론: 훈련한 모델 사용하기
- 모델을 훈련하고 나면 이 모델을 사용하여 새로운 데이터에서 예측을 만들게 됨. 이를 추론(inference)이라고 부름
- 간단한 방법은 모델의 __call__()메서드를 호출하는 것
predictions = model(new_input) # 넘파이 배열이나 텐서플로 텐서를 받고 텐서플로 텐서를 반환
- 이 방법은 new_input에 있는 모든 입력을 한 번에 처리
- 추론을 하는 더 나은 방법은 predict() 메서드를 사용하는 것
predictions = model.predict(val_inputs, batch_size=128) # 넘파이 배열이나 Dataset 객체를 받고 넘파이 배열을 반환
print(predictions[:10])
3.7 요약
- 텐서플로는 CPU, GPU, TPU에서 실행할 수 있는 업계 최강의 수치 컴퓨팅 프레임워크. 미분 가능한 어떤 표현식의 그레이디언트도 자동으로 계산할 수 있음. 여러 가지 장치에 배포할 수 있고, 자바스크립트를 포함하여 다양한 종류의 런타임에 맞도록 프로그램을 변환할 수 있음
- 케라스는 텐서플로에서 딥러닝을 수행하기 위한 표준 API로, 이 책에서 사용하는 라이브러리
- 텐서플로의 핵심 객체는 텐서, 변수, 텐서 연산, 그레이디언트 테이프
- 케라스의 핵심 클래스는 Layer
- 층은 가중치와 연산을 캡슐화
- 이런 층을 조합하여 모델을 만듦
- 모델을 훈련하기 전에 옵티마이저, 손실, 측정 지표를 선택하여 model.compile()메서드에 지정
- 미니 배치 경사 하강법을 실행하는 fit()메서드로 모델을 훈련할 수 잇음. 또한, 이 메서드를 사용하여 모델이 훈련 과정에서 본 적 없는 검증 데이터에 대한 손실과 측정 지표를 모니터링할 수 있음
- 모델을 훈련하고 나면 model.predict() 메서드를 사용하여 새로운 입력에 대한 예측을 만듦