2025.04.13
Part.11 텐서플로
Chapter 06. Evaluation
- 01_Tensorboard
- 02_Model Save and load
Tensorboard
TensorFlow에서 제공하는 시각화 툴. 학습하는 중간에 그래프나 여러가지 정보를 Web UI로 조회할 수 있음.
동시에 여러개의 모델을 학습시키고, 서로 비교할 때 시각화툴을 많이 사용한다.
데이터 불러오기 및 모델준비
이전에 몇번 보았던 Cifar10 데이터셋을 활용한다.
## 데이터 불러오기
class Cifar10DataLoader():
def __init__(self):
(self.train_X, self.train_y), (self.test_X, self.test_y) = tf.keras.datasets.cifar10.load_data()
self.input_shape = self.train_X.shape[1:]
def scale(self, x):
return (x / 255.0).astype(np.float32)
def preprocess_dataset(self, dataset):
(feature, target) = dataset
#scaling
scaled_x = np.array([self.scale(x) for x in feature])
#label Encoding
ohe_y = np.array([tf.keras.uitls.to_categorical(y, num_classes=10) for y in target])
return scaled_x, ohe_y.squeeze()
def get_train_dataset(self):
return self.preprocess_dataset((self.train_X, self.train_y))
def get_test_dataset(self):
return self.preprocess_dataset((self.test_X, self.test_y))
cifar10_loader = Cifar10DataLoader()
train_x, train_y = cifar10_loader.get_train_dataset()
test_x, test_y = cifar10_loader.get_test_dataset()
print(train_x.shape, train_y.shape)
print(test_x.shape, test_y.shape)
모델도 선언한다 (이전에 학습했던 커스텀 Resnet 활용)
def build_resnet(input_shape):
## Functional Modeling 방법
inputs = Input(input_shape)
net = Conv2D(32, kernel_size=3, strides=2, padding='same', activation='relu')(inputs)
net = MaxPool2D()(net)
net1 = Conv2D(64, kernel_size=1, padding='same', activation='relu')(net)
net2 = Conv2D(64, kernel_size=3, padding='same', activation='relu')(net1)
net3 = Conv2D(64, kernel_size=1, padding='same', activation='relu')(net2)
# Input의 filters size 변경
net1_1 = Conv2D(64, kernel_size=1, padding='same')(net)
# ResNet 덧셈
net = Add()([net3, net1_1])
net = MaxPool2D()(net)
net = Flatten()(net)
net = Dense(10, activation='softmax')(net)
model = tf.keras.Model(inputs=inputs, outputs=net, name='resnet_custom')
return model
model = build_resnet((32, 32, 3))
model.summary()
===========================================================================================================
Model: "resnet_custom"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) [(None, 32, 32, 3)] 0 []
conv2d (Conv2D) (None, 16, 16, 32) 896 ['input_1[0][0]']
max_pooling2d (MaxPooling2D) (None, 8, 8, 32) 0 ['conv2d[0][0]']
conv2d_1 (Conv2D) (None, 8, 8, 64) 2112 ['max_pooling2d[0][0]']
conv2d_2 (Conv2D) (None, 8, 8, 64) 36928 ['conv2d_1[0][0]']
conv2d_3 (Conv2D) (None, 8, 8, 64) 4160 ['conv2d_2[0][0]']
conv2d_4 (Conv2D) (None, 8, 8, 64) 2112 ['max_pooling2d[0][0]']
add (Add) (None, 8, 8, 64) 0 ['conv2d_3[0][0]',
'conv2d_4[0][0]']
max_pooling2d_1 (MaxPooling2D) (None, 4, 4, 64) 0 ['add[0][0]']
flatten (Flatten) (None, 1024) 0 ['max_pooling2d_1[0][0]']
dense (Dense) (None, 10) 10250 ['flatten[0][0]']
==================================================================================================
Total params: 56,458
Trainable params: 56,458
Non-trainable params: 0
__________________________________________________________________________________________________
컴파일
lr = 0.003
opt = tf.keras.optimizers.Adam(lr)
loss = tf.keras.losses.categorical_crossentropy
model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
log기록 시, Callbacks.TensorBoard 함수 사용
#fit 함수로 사용 시, callback 함수로 사용
#log_dir = 로그가 저장될 디렉토리 설정, 잘 저장하는 것이 중요
import datetime
cur_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
log_dir = "logs/fit_idea1/" + cur_time
tb_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir)
hist = model.fit(train_x, train_y,
epochs=5,
validation_data=(test_x, test_y),
callbacks = [tb_callback])
지정된 디렉터리 위치에, train, validation에 대한 log 파일이 저장된 것을 확인할 수 있다.
tensorboard 사용
log에 대한 파일저장이 완료되었으면, 이제 tensorboard를 사용하여 탐색할 수 있다. 다만, vscode에서 바로 실행은 안되고 터미널을 사용해서 접근할 수 있다.
터미널에서 아래의 코드 실행 시, 링크가 하나 나오고 이 링크를 통해서 접근가능 하다. 아래 나오는 링크 ( http://localhost:6006/ ) 를 컨트롤 클릭하게 되면 WEB으로 이동할 수 있다.
모델에 대한 그래프(계층도)나 loss나 accuracy가 어떻게 변화하는지 자동으로 보여준다.
tf.summary( ) 사용
log를 기록하기 위해, 위에서는 tensorflow.keras.callbacks.Tensorboard 함수를 이용해서 접근하였다. 이번에는 tf.summary( ) 함수를 이용해서 직접 log를 기록해 보는 방법이다
학습로직 함수 설정
loss_fn = tf.keras.losses.categorical_crossentropy
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_acc = tf.keras.metrics.CategoricalAccuracy(name='train_acc')
test_loss = tf.keras.metrics.Mean(name='test_loss')
test_acc = tf.keras.metrics.CategoricalAccuracy(name='test_acc')
@tf.function
def train_step(x, y):
with tf.GradientTape() as tape:
pred = model(x)
loss = loss_fn(y, pred) #정답값, 예측값
gradients = tape.gradient(loss, model.trainable_variables)
opt.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_acc(y, pred)
@tf.function
def test_step(x, y):
pred = model(x)
loss = loss_fn(y, pred)
test_loss(loss)
test_acc(y, pred)
tf.summary( ) 에서 파일을 기록학 조작하는 함수 사용하여 dir 경로 설정 (log 저장 경로)
train_log_dir = "logs/grad_tape/train" + cur_time
test_log_dir = "logs/grad_tape/test" + cur_time
train_writer = tf.summary.create_file_writer(train_log_dir)
test_writer = tf.summary.create_file_writer(test_log_dir)
학습로직 구현
from tqdm import tqdm
batch_size = 64
num_of_train_batch = train_x.shape[0] // batch_size
num_of_test_batch = test_x.shape[0] // batch_size
for epoch in range(10):
print(f"\nEpoch {epoch + 1}")
# train 학습
for i in tqdm(range(num_of_train_batch), desc="Training"):
idx = i * batch_size
x, y = train_x[idx : idx+batch_size], train_y[idx : idx+batch_size]
train_step(x, y)
# test 학습
for i in tqdm(range(num_of_test_batch), desc="Evaluating"):
idx = i * batch_size
x, y = test_x[idx : idx+batch_size], test_y[idx : idx+batch_size]
test_step(x, y)
# log 기록
with train_writer.as_default():
tf.summary.scalar('loss', train_loss.result(), step=epoch)
tf.summary.scalar('acc', train_acc.result(), step=epoch)
with test_writer.as_default():
tf.summary.scalar('loss', test_loss.result(), step=epoch)
tf.summary.scalar('acc', test_acc.result(), step=epoch)
print("Train Loss: {:.4f}, Acc: {:.4f} | Test Loss: {:.4f}, Acc: {:.4f}".format(
train_loss.result(), train_acc.result(),
test_loss.result(), test_acc.result()
))
train_loss.reset_states()
train_acc.reset_states()
test_loss.reset_states()
test_acc.reset_states()
학습완료 시, 미리 정해두었던 경로에 log파일이 저장된 것을 알 수 있다. 직접 기록한 log를 tensorboard에서 확인해 보면...........
동일하게 그래프가 그려지는 것을 알 수 있다. 다만, 학습자체를 fit( ) 함수를 이용하지 않고 직접 학습로직을 구현해서 진행했기 때문인지.. 모델그래프(계층도)는 보이지 않는다.
Tensorboard에 이미지데이터 기록
샘플이미지 확인
label_names = {
0: 'airplane',
1: 'automobile',
2: 'bird',
3: 'cat',
4: 'deer',
5: 'dog',
6: 'frog',
7: 'horse',
8: 'ship',
9: 'truck'
}
plt.figure(figsize=(3, 3))
plt.imshow(sample_img)
plt.title(label_names[train_y[777].argmax()])
plt.axis('off')
plt.tight_layout()
plt.show()
log 저장 진행
cur_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
log_dir = 'logs/train_data/' + cur_time
# 실행 즉시 폴더, 파일 생성됨.
file_writer = tf.summary.create_file_writer(log_dir)
for i in np.random.randint(10000, size=10):
img = train_x[i:i+1]
with file_writer.as_default():
tf.summary.image(f"Train Sample : {label_names[train_y[i].argmax()]}",img, step=0, max_outputs=10)
그 후 터미널에서 아래와 같이 tensorboard 실행한다.
WEB 접속 시 다음과 같이 사진이 잘 저장된 것을 확인할 수 있다.
LambdaCallback을 사용하여 Tensorboard에 Confusion Matrix 기록
아래의 코드에 대해서는 다시 한번 복습이 필요하다. 조금 복잡하다.
import io
from sklearn.metrics import confusion_matrix
import datetime
def plot_to_image(figure):
# 메모리상에 파일처럼 객체 저장
buf = io.BytesIO()
#PNG 형태로 메모리상에 저장
plt.savefig(buf, format='png')
#matplotlib는 종료 (메모리 절약)
plt.close(figure)
# 버퍼의 읽기포인터 0 (저장완료). 0으로 해야 읽을 수 있음 (buf.getvalue())
buf.seek(0)
image = tf.image.decode_png(buf.getvalue(), channels=4)
image = tf.expand_dims(image, 0)
return image
def plot_confusion_matrix(cm, class_names):
figure = plt.figure(figsize=(8, 8))
plt.imshow(cm)
plt.title("Confusion Matrix")
plt.colorbar()
tick_marks = np.arange(len(class_names))
threshold = cm.max() / 2.
plt.xticks(tick_marks, class_names, rotation=45)
plt.yticks(tick_marks, class_names)
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
color = "white" if cm[i, j] > threshold else "black"
plt.text(j, i, cm[i, j], horizontalalignment='center', color=color)
plt.tight_layout()
plt.ylabel("True label")
plt.xlabel("Predicted label")
return figure
def log_confusion_matrix(epoch, logs):
test_pred_raw = model.predict(test_image)
test_pred = np.argmax(test_pred_raw, axis=1)
classes = np.arange(10)
cm = confusion_matrix(test_label, test_pred, labels=classes)
figure = plot_confusion_matrix(cm, class_names=classes)
cm_image = plot_to_image(figure)
with file_writer_cm.as_default():
tf.summary.image("Confusion Matrix", cm_image, step=epoch, max_outputs=100)
cur_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
log_dir = "logs/fit/cm" + cur_time
file_writer_cm = tf.summary.create_file_writer(log_dir)
test_image = test_x[:100]
test_label = np.argmax(test_y[:100], axis=1)
cm_callback = tf.keras.callbacks.LambdaCallback(on_epoch_end=log_confusion_matrix)
model.fit(
x=train_x,
y=train_y,
epochs=5,
batch_size=32,
validation_data=(test_x, test_y),
callbacks=[cm_callback]
)
Model Save and Load
tensorflow model을 저정하는 방법과 다시 불러오는 방법에 대한 내용이다.
모델의 저장은 크게 전체를 저장하는 방법과 모델의 weights만 저장하는 방식이 있다. 후자는 모델을 다시 build(구성)해야하는 단점이 있지만, weights만 저장되므로 용량이 줄어드는 이점이 있다. 물론 모델의 구조만 저장하는 방법도 있다.
일단 이전에 학습했던, Cifar10 데이터를 로드하고 샘플모델(resnet)을 빌드해 놓는다.
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.layers import Dense, Conv2D, MaxPool2D, Flatten, Input, Add
import matplotlib.pyplot as plt
np.random.seed(777)
tf.random.set_seed(777)
## 데이터 불러오기
class Cifar10DataLoader():
def __init__(self):
(self.train_X, self.train_y), (self.test_X, self.test_y) = tf.keras.datasets.cifar10.load_data()
self.input_shape = self.train_X.shape[1:]
def scale(self, x):
return (x / 255.0).astype(np.float32)
def preprocess_dataset(self, dataset):
(feature, target) = dataset
#scaling
scaled_x = np.array([self.scale(x) for x in feature])
#label Encoding
ohe_y = np.array([tf.keras.utils.to_categorical(y, num_classes=10) for y in target])
return scaled_x, ohe_y.squeeze()
def get_train_dataset(self):
return self.preprocess_dataset((self.train_X, self.train_y))
def get_test_dataset(self):
return self.preprocess_dataset((self.test_X, self.test_y))
cifar10_loader = Cifar10DataLoader()
train_x, train_y = cifar10_loader.get_train_dataset()
test_x, test_y = cifar10_loader.get_test_dataset()
def build_resnet(input_shape):
## Functional Modeling 방법
inputs = Input(input_shape)
net = Conv2D(32, kernel_size=3, strides=2, padding='same', activation='relu')(inputs)
net = MaxPool2D()(net)
net1 = Conv2D(64, kernel_size=1, padding='same', activation='relu')(net)
net2 = Conv2D(64, kernel_size=3, padding='same', activation='relu')(net1)
net3 = Conv2D(64, kernel_size=1, padding='same', activation='relu')(net2)
# Input의 filters size 변경
net1_1 = Conv2D(64, kernel_size=1, padding='same')(net)
# ResNet 덧셈
net = Add()([net3, net1_1])
net = MaxPool2D()(net)
net = Flatten()(net)
net = Dense(10, activation='softmax')(net)
model = tf.keras.Model(inputs=inputs, outputs=net, name='resnet_custom')
return model
model = build_resnet((32, 32, 3))
model.summary()
lr = 0.003
opt = tf.keras.optimizers.Adam(lr)
loss = tf.keras.losses.categorical_crossentropy
model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
Save 함수
우선 모델의 구조, 가중치 및 optimizer 전체를 저장하는 방식이다.
## Save = 모델의 구조와 weights값 등 모델 전체를 저장하는 방식
# h5 는 모델의 확장자. h5가 tf에서 제일 널리 사용된다.
model.save('checkpoints/model.h5')
그러면 위와 같이 지정된 폴더 이름과 파일이름으로 모델이 저장된 것을 확인할 수 있다.
모델을 다시 Load 하는 방법은 아래와 같다. Load 하면 아래와 같이 저장된 모델 동일한 모델이 Load 되어진다.
## 저장된 모델의 load
load_model = tf.keras.models.load_model('checkpoints/model.h5')
load_model.summary()
Save_weights 함수
모델 전체를 저장하지 않고 학습된 모델의 weights만 저장한다. 저장공간이 절약되는 장점이 있다. 아래처럼 실행하면 동일하게 모델이 저장된다.
## model의 weights만 저장
model.save_weights('checkpoints/model_weights.h5')
weights만 저장된 파일을 Load 하는 경우 바로 Load 할 수 없다. 이전 모델과 동일한 구조의 모델을 구성해주고 나서 Weights를 Load할 수 있다.
일단 모델의 학습을 진행하고, 학습된 모델의 weights 일부와 학습되지 않은 모델의 weights를 비교해 보면 아래와 같음
# 예시 - conv2d 레이어 kernel 일부 출력
kernel_values = model.get_layer("conv2d").get_weights()[0]
print(kernel_values.shape)
print(kernel_values[0, 0, 0]) # 일부만 출력
kernel_values = initial_model.get_layer("conv2d_5").get_weights()[0]
print(kernel_values.shape)
print(kernel_values[0, 0, 0]) # 일부만 출력
학습된 모델의 Weights는 비교적 다양한 값을 가지고, 학습되지 않은 모델의 Weights는 대부분 0 근처의 값을 가지고 있음. 이렇게 학습된 모델의 Weight를 저장하고 불러오는 방법은 아래와 같음.
## 학습된 model의 weights만 저장
model.save_weights('checkpoints/model_weights.h5')
## load weights
## 동일한 구조의 모델 만들기
model_2 = build_resnet((32, 32, 3))
## 학습된 모델의 weight를 불러와서 새로만든 모델에 load
model_2.load_weights('checkpoints/model_weights.h5')
## 기존 모델의 weight와 새로만들고 weight만 load한 모델의 weight를 조회하면..
kernel_values = model.get_layer("conv2d").get_weights()[0]
print(kernel_values.shape)
print(kernel_values[0, 0, 0]) # 일부만 출력
kernel_values = model_2.get_layer("conv2d_10").get_weights()[0]
print(kernel_values.shape)
print(kernel_values[0, 0, 0]) # 일부만 출력
위와 같이 Weight가 동일하게 적용된 것을 확인할 수 있다.
Callbacks 함수를 이용한 Save
모델의 weights나 모델 자체를 epoch 진행 중에 자동으로 저장하기 위해서는 Callback함수를 활용해야 한다.
## Callback 함수 이용한 Save
# 경로 및 파일명 지정
save_path = 'checkpoints/{epoch}-{val_loss:.2f}.h5'
# save_best_only=True 가장 좋은 결과모델만 저장
checkpoint = tf.keras.callbacks.ModelCheckpoint(save_path,
monitor='val_accuracy',
save_best_only=False)
initial_model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
hist = initial_model.fit(
train_x,
train_y,
epochs=10,
validation_data = (test_x, test_y),
callbacks = [checkpoint]
)
아래처럼 처음 지정된 경로에 지정한 파일이름 형식으로 매 epoch마다 모델이 저장된 것을 확인할 수 있다.
# .h5 확장자 형식 말고도, pb형식으로도 저장할 수 있다.
'Bootcamp_zerobase > Tensorflow' 카테고리의 다른 글
Tensorflow : data 다루기 #2 (0) | 2025.04.19 |
---|---|
Tensorflow : data 다루기 #1 (2) | 2025.04.19 |
Model 학습 : 텐서플로 모델학습 (fit( ) vs Training Logic) (0) | 2025.04.13 |
SubClass 모델링 : SubClass 및 모델구현 (0) | 2025.04.07 |
Functional Modeling : Functional API 및 간단한 ResNet 구현 (0) | 2025.04.03 |