이전 내용
케라스의 사전 훈련 모델 사용하기
tf.keras.applications 패키지에 준비되어 있는 사전 훈련된 모델을 코드 한 줄로 불러올 수 있기 때문에, GoogleNet이나 ResNet 같은 표준 모델을 직접 구현할 필요가 없다.
아래 코드는 이미지넷 데이터셋에서 사전 훈련된 ResNet-50 모델을 로드하는 예이다.
model = tf.keras.applications.resnet50.ResNet50(weights='imagenet')
▶ 이 코드는 ResNet-50 모델을 만들고 이미지넷 데이터셋에서 사전 훈련된 가중치를 다운로드한다.
이 모델을 사용하려면 이미지가 적절한 크기인지 확인해야 하는데, ResNet-50 모델은 224*224 픽셀 크기의 이미지를 기대하며, 모델이 다르면 기대하는 크기가 다를 수 있다.
따라서, 케라스의 Resizing 층을 사용해 샘플 이미지 두 개의 크기를 목표 크기에 맞도록 이미지를 잘라내는 방식으로 바꿔본다.
# 샘플 이미지 로드
images = load_sample_images()['images']
# 이미지를 텐서로 변환
images_tensor = tf.convert_to_tensor(images, dtype=tf.float32)
# Resizing 레이어 적용
images_resized = tf.keras.layers.Resizing(
height=224, width=224, crop_to_aspect_ratio=True
)(images_tensor)
print(images_resized.shape) # 출력 결과 확인
▶ 2개의 샘플 이미지, 224*224 크기, 3개의 채널 (RGB)
사전 훈련된 모델은 이미지가 적절한 방식으로 전처리되었다고 가정하며, 경우에 따라 0에서 1사이 또는 -1에서 1사이의 입력을 기대한다. 이를 위해 모델마다 이미지를 전처리해주는 preprocess_input() 함수를 제공하며, 이 함수는 이 예제의 이미지처럼 원본 픽셀값이 0에서 255사이라고 가정한다.
inputs = tf.keras.applications.resnet50.preprocess_input(images_resized)
# 사전 훈련된 모델 사용해 예측 수행
Y_proba = model.predict(inputs)
Y_proba.shape
▶ 출력 Y_proba는 행이 하나의 이미지이고 열이 하나의 클래스 (여기에서는 1,000개의 클래스)인 행렬이다.
최상위 K개의 예측을 클래스 이름과 예측 클래스의 추정 확률을 출력하려면 decode_predictions() 함수를 사용한다. 이 함수는 각 이미지에 대해 최상위 K개의 예측을 담은 리스트를 반환하며, 각 예측은 클래스 ID(class_id), 이름(name), 확률(y_proba)을 포함한 튜플이다.
top_K = tf.keras.applications.resnet50.decode_predictions(Y_proba, top=3)
for image_index in range(len(images)):
print(f'Image #{image_index}')
for class_id, name, y_proba in top_K[image_index]:
print(f' {class_id} - {name:12s} {y_proba:.2%}')
사전 훈련된 모델을 사용한 전이 학습
이미지 분류기를 훈련하고 싶지만 밑바닥부터 훈련할만큼 충분한 데이터가 없거나, 이미지넷에 없는 이미지 클래스를 감지하는 이미지 분류기가 필요한 경우에도 사전 훈련된 모델을 사용해 전이 학습을 수행할 수 있다.
그 예로 사전 훈련된 Xception 모델을 사용해 꽃 이미지를 분류하는 모델을 훈련해본다.
먼저 텐서플로 데이터셋을 사용해 데이터를 적재한다.
import tensorflow_datasets as tfds
dataset, info = tfds.load('tf_flowers', as_supervised=True, with_info=True)
dataset_size = info.splits['train'].num_examples
class_names = info.features['label'].names
n_classes = info.features['label'].num_classes
print(dataset_size)
print(class_names)
print(n_classes)
▶ with_info=True 로 지정하면 데이터셋에 관한 정보를 얻을 수 있다.
이 데이터셋에는 'train' 세트만 있고 테스트 세트, 검증 세트는 없기 때문에 훈련 세트를 나누어야 한다.
tfds.load()를 다시 호출하여 데이터셋의 처음 10%를 테스트 세트로 사용하고 다음 15%를 검증 세트로, 나머지 75%는 훈련 세트로 나눈다.
test_set_raw, valid_set_raw, train_set_raw = tfds.load(
'tf_flowers',
split=['train[:10%]', 'train[10%:25%]', 'train[25%:]'],
as_supervised=True)
세 데이터셋 이미지를 배치로 묶기 전에 이미지 크기가 동일한지 Resizing 층을 사용하여 확인해본다. 또한 Xception 모델에 맞도록 이미지를 전처리하기 위해 tf.keras.applications.xception.preprocess_input() 함수를 호출하고, 마지막으로 훈련 세트를 섞고 프리페칭한다.
batch_size = 32
preprocess = tf.keras.Sequential([
tf.keras.layers.Resizing(height=224, width=224, crop_to_aspect_ratio=True),
tf.keras.layers.Lambda(tf.keras.applications.xception.preprocess_input)
])
train_set = train_set_raw.map(lambda X, y: (preprocess(X), y))
train_set = train_set.shuffle(1000, seed=42).batch(batch_size).prefetch(1)
valid_set = valid_set_raw.map(lambda X, y: (preprocess(X), y)).batch(batch_size)
test_set = test_set_raw.map(lambda X, y: (preprocess(X), y)).batch(batch_size)
valid_set의 첫 번째 배치의 처음 9개 이미지를 표시해본다.
# valid_set의 첫 번째 배치에 처음 9개의 이미지 표시.
plt.figure(figsize=(12, 12))
for X_batch, y_batch in valid_set.take(1):
for index in range(9):
plt.subplot(3, 3, index + 1)
plt.imshow((X_batch[index] + 1) / 2) # imshow()를 위해 0-1로 조정.
plt.title(f"Class: {class_names[y_batch[index]]}")
plt.axis("off")
plt.show()
▶ 각 배치는 32개의 이미지를 담고 있고 모두 224*224 픽셀이며, 각 픽셀값의 범위는 -1에서 1까지 이다.
이 데이터셋은 크지 않으므로 최종 모델에 추가할 데이터 증식 모델을 만들어본다. 훈련하는 동안 이미지를 수평으로 랜덤하게 뒤집고, 약간 회전하고, 명암을 조절하는 방식으로 데이터 증식 모델을 만든다.
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip(mode='horizontal', seed=42),
tf.keras.layers.RandomRotation(factor=0.05, seed=42),
tf.keras.layers.RandomContrast(factor=0.2, seed=42)
])
증식 후 아까 9개의 이미지를 다시 표시해본다.
# 증식 후 처음 9개의 이미지 동일하게 표시.
plt.figure(figsize=(12, 12))
for X_batch, y_batch in valid_set.take(1):
X_batch_augmented = data_augmentation(X_batch, training=True)
for index in range(9):
plt.subplot(3, 3, index + 1)
# imshow()를 위해 이미지 크기를 0-1 범위로 조정.
# 데이터 증식으로 인해 일부 값이 범위를 벗어날 수 있으므로
# (예: 이 경우 RandomContrast) 결과를 해당 범위로 클리핑.
plt.imshow(np.clip((X_batch_augmented[index] + 1) / 2, 0, 1))
plt.title(f"클래스: {class_names[y_batch[index]]}")
plt.axis("off")
plt.show()
▶ 데이터 증식을 추가하고 싶다면 훈련 세트에 대한 전처리 함수를 바꿔 훈련 이미지를 랜덤하게 변환하면 된다.
- tf.image.random_crop() 함수: 이미지를 랜덤하게 자름.
- tf.image.random_flip_left_right() 함수: 이미지를 수평으로 랜덤하게 뒤집음.
이미지넷에서 사전 훈련된 Xception 모델을 로드하는 데, include_top=False로 지정하여 네트워크의 최상 층에 해당하는 전역 평균 풀링 층과 밀집 출력 층을 제외시킨다. 이 기반 모델의 출력을 바탕으로 새로운 전역 평균 풀링 층을 추가하고 그 뒤에 클래스마다 하나의 유닛과 소프트맥스 활성화 함수를 가진 밀집 출력 층을 놓는다. 마지막으로 케라스의 Model로 이를 모두 감싼다.
base_model = tf.keras.applications.xception.Xception(weights='imagenet',
include_top=False)
avg = tf.keras.layers.GlobalAvgPool2D()(base_model.output) # 또는 GlobalAveragePooling2D
output = tf.keras.layers.Dense(n_classes, activation='softmax')(avg)
model = tf.keras.Model(inputs=base_model.input, outputs=output)
훈련 초기에는 사전 훈련된 층의 가중치를 동결하는 것이 좋다.
for layer in base_model.layers:
layer.trainable = False
이제 모델을 컴파일하고 훈련해본다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.9)
model.compile(loss='sparse_categorical_crossentropy', optimizer=optimizer,
metrics=['accuracy'])
history = model.fit(train_set, validation_data=valid_set, epochs=3)
이제 새로운 최상위 층의 가중치가 나쁘지 않으므로 기반 모델의 최상위 부분을 다시 훈련할 수 있게 만들고 학습 속도를 낮추어 훈련을 계속할 수 있다.
# 잔차 유닛 중 7번째 유닛의 시작 부분 동결 해제
for layer in base_model.layers[56:]:
layer.trainable = True
# 층 동결이나 해제 시에는 모델 다시 컴파일 필요
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.9)
model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
metrics=["accuracy"])
history = model.fit(train_set, validation_data=valid_set, epochs=10)
분류와 위치 추정
사진에서 물체의 위치를 추정하는 것은 회귀 작업으로 나타낼 수 있다. 물체 주위에 바운딩 박스를 예측하는 것인데, 일반적인 방법은 물체 중심의 수평, 수직 좌표와 높이, 너비 즉, 네 개의 숫자를 예측하는 것이다.
이 때문에 모델을 크게 바꿀 필요는 없고, 전역 평균 풀링 층 위에 네 개의 유닛을 가진 두 번째 밀집 출력 층을 추가하고 MSE 손실을 사용해 훈련한다.
base_model = tf.keras.applications.xception.Xception(weights='imagenet',
include_top=False)
avg = tf.keras.layers.GlobalAvgPool2D()(base_model.output) # 또는 GlobalAveragePooling2D
class_output = tf.keras.layers.Dense(n_classes, activation='softmax')(avg)
loc_output = tf.keras.layers.Dense(4)(avg)
model = tf.keras.Model(inputs=base_model.input, outputs=[class_output, loc_output])
model.compile(loss=['sparse_categorical_crossentropy', 'mse'],
loss_weight=[0.8, 0.2],
optimizer=optimizer, metrics=['accuracy'])
꽃 데이터셋은 꽃 주위에 바운딩 박스를 가지고 있지 않기 때문에 직접 만들어 추가(레이블 작업)해야 하는데, 레이블을 만드는 것은 머신러닝 프로젝트에서 가장 어렵고 비용이 많이 드는 작업이다. 그렇기 때문에 시간을 들여 적절한 도구를 찾는 것이 좋다.
이미지에 바운딩 박스를 추가하기 위한 오픈 소스 이미지 레이블 도구로
- VGG Image Annotator
- LabelImg
- OpenLabeler
- ImgLab
등이 있다. 만약 처리해야 할 이미지가 매우 많다면 아마존 메커니컬 터크와 같은 크라우드소싱 플랫폼을 고려해볼 수 있으나, 많은 시간과 노력이 필요하다.
꽃 데이터셋의 모든 이미지에 대해 바운딩 박스가 준비되었다고 가정하고, 클래스 레이블, 바운딩 박스와 함께 전처리된 이미지의 배치가 하나의 원소인 데이터셋을 만들어야 한다. 각 원소는 (images, (class_labels, bounding_boxes)) 형태의 튜플이 된다. 모델을 훈련할 준비를 마친 다음에는 MSE를 모델 훈련을 위한 손실 함수로 사용할 수 있다. 하지만 모델이 바운딩 박스를 얼마나 잘 예측하는지 평가하는 데 아주 좋은 지표는 아니다.
바운딩 박스에 널리 사용되는 지표는 IoU(intersection over union)이며, 이 값은 예측한 바운딩 박스와 타깃 바운딩 박스 사이에 중첩되는 영역을 전체 영억으로 나눈 것이다. 케라스에서는 tf.keras.metrics.MeanIoU에 구현되어 있다.
# 임의의 타깃 바운딩 박스를 사용하여 모델 훈련
# (실제에서는 대신 적절한 타깃을 생성해야 함).
def add_random_bounding_boxes(images, labels):
fake_bboxes = tf.random.uniform([tf.shape(images)[0], 4])
return images, (labels, fake_bboxes)
fake_train_set = train_set.take(5).repeat(2).map(add_random_bounding_boxes)
model.fit(fake_train_set, epochs=2)
다음 내용
[출처]
핸즈 온 머신러닝
'[파이썬 Projects] > <파이썬 딥러닝, 신경망>' 카테고리의 다른 글
[딥러닝] RNN & CNN(feat. 시카고 교통국 데이터셋) - 1 (0) | 2024.11.30 |
---|---|
[딥러닝] 객체 탐지, 객체 추적 (0) | 2024.11.29 |
[딥러닝] 합성곱 신경망을 사용한 컴퓨터 비전 (0) | 2024.11.27 |
[딥러닝] 텐서플로 데이터셋 프로젝트 (0) | 2024.11.27 |
[딥러닝] 케라스의 전처리 층 (0) | 2024.11.26 |