최근에 ImageNet Sketch 데이터셋을 사용하여 딥러닝 모델을 학습하던 중,
두 가지 이미지 변환 라이브러리인 Torchvision과 Albumentations에서 미묘한 차이점을 발견하게 되었습니다.
표면적으로는 동일한 이미지 전처리 작업을 적용했지만, 내부 구현 방식의 차이로 인해 모델 학습 과정에서 로스(loss)와 성능에 차이가 생겼습니다. 이 글에서는 두 라이브러리 간의 차이점을 명확히 비교하고, 실제로 어떻게 다른지 설명드리겠습니다.
1. 변환 라이브러리의 내부 구현 차이
- 회전의 중심이 어디로 설정되는지,
- 이미지 크기 조정 방식,
- 픽셀 처리 방식 등이 다를 수 있다.
이러한 미세한 차이들이, 특히 모델이 민감하게 반응할 수 있는 스케치 이미지와 같은 데이터에서 큰 영향을 미칠 수 있습니다. 스케치 이미지에서는 선과 디테일이 중요한데, 이러한 디테일 처리 방식에서 차이가 발생해 모델의 학습 성능에 영향을 미칩니다.
2. ToTensor 변환 방식의 차이
두 라이브러리에서 텐서로 변환하는 방식에도 차이가 있습니다.
- Torchvision의 transforms.ToTensor()는 PIL 이미지를 PyTorch 텐서로 변환하는데,
이 과정에서 이미지 픽셀 값을 [0, 1] 범위로 정규화합니다.
즉, PIL.Image에서 텐서로 변환되면서 이미지가 부드럽게 스케일링됩니다. - Albumentations의 ToTensorV2()는 OpenCV 방식의 NumPy 배열을 텐서로 변환합니다. 이때, 이미지의 픽셀 값은 처음에 [0, 255] 범위를 유지하다가 변환 후에 [0, 1] 범위로 스케일링됩니다.
결과적으로, 이미지 데이터의 처리 방식이 다르며, 이는 특히 이미지의 픽셀 값 표현 방식에서 차이가 나게 됩니다. 이 차이는 모델 학습 과정에서 손실 값과 예측 정확도에 미세한 영향을 줄 수 있습니다.
3. 정규화 적용 순서의 차이
- Torchvision에서는 transforms.ToTensor() 이후에 transforms.Normalize()가 적용됩니다.
즉, 텐서로 변환된 후에 평균과 표준 편차로 정규화하는 방식입니다. - Albumentations에서는 A.Normalize()가 ToTensorV2() 이전에 적용됩니다.
즉, NumPy 배열 상태에서 정규화가 먼저 이루어지고, 그 후 텐서로 변환됩니다.
이 차이로 인해 이미지 데이터가 정규화되는 시점이 달라지고, 이는 모델이 학습하는 데이터의 분포에 영향을 미칠 수 있습니다. 정규화 적용 시점의 미묘한 차이가 손실 값의 변화로 이어질 수 있습니다.
직접 코드를 작성해서 확인 했을때는 차이는 없었다.
# %%
import os
import numpy as np
import torch
from torchvision import transforms
from albumentations import Compose, Normalize
from albumentations.pytorch import ToTensorV2
from PIL import Image
import matplotlib.pyplot as plt
# 이미지 파일을 ./test_image 폴더에서 로드하는 함수
def load_images_from_folder(folder, num_images=10):
images = []
filenames = os.listdir(folder)
for i, filename in enumerate(filenames):
if i >= num_images:
break
img_path = os.path.join(folder, filename)
img = np.array(Image.open(img_path).convert("RGB")) # 이미지 파일을 NumPy 배열로 변환
images.append((img, filename))
return images
# Torchvision 변환 적용 함수
def apply_torchvision(image: np.ndarray):
transform = transforms.Compose([
transforms.ToTensor(), # Torchvision의 ToTensor
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 정규화
])
pil_image = Image.fromarray(image) # Torchvision은 PIL 이미지를 다루므로 변환
tensor = transform(pil_image)
return tensor
# Albumentations 변환 적용 함수
def apply_albumentations(image: np.ndarray):
transform = Compose([
Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), # 정규화
ToTensorV2() # Albumentations의 ToTensorV2
])
augmented = transform(image=image)
tensor = augmented['image']
return tensor
# 시각적으로 변환된 이미지를 비교하는 함수
def plot_image_comparison(original_img, torchvision_img, albumentations_img, filename):
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(original_img)
ax[0].set_title(f'Original Image: {filename}')
ax[1].imshow(torchvision_img.permute(1, 2, 0).cpu().numpy()) # Torchvision 텐서 (C, H, W) -> (H, W, C)
ax[1].set_title('Torchvision Transform')
ax[2].imshow(albumentations_img.permute(1, 2, 0).cpu().numpy()) # Albumentations 텐서 (C, H, W) -> (H, W, C)
ax[2].set_title('Albumentations Transform')
for a in ax:
a.axis('off')
plt.show()
# 값 차이를 비교하는 함수
def compare_values(torchvision_img, albumentations_img):
diff = torch.abs(torchvision_img - albumentations_img)
mean_diff = torch.mean(diff).item()
max_diff = torch.max(diff).item()
print(f"Mean Difference: {mean_diff:.6f}")
print(f"Max Difference: {max_diff:.6f}")
# ./test_image 폴더에서 이미지 로드
image_folder = './test_image' # 이미지 폴더 경로
num_images = 10
images = load_images_from_folder(image_folder, num_images)
# 10개의 이미지에 대해 변환 및 비교
for idx, (img, filename) in enumerate(images):
# 원본 이미지
original_img = img
# Torchvision과 Albumentations 변환 적용
torchvision_img = apply_torchvision(img)
albumentations_img = apply_albumentations(img)
# 시각적으로 비교
print(f"Comparing Image: {filename}")
plot_image_comparison(original_img, torchvision_img, albumentations_img, filename)
# 값 차이 비교
print(f"Image {idx+1}:")
compare_values(torchvision_img, albumentations_img)
print("\n")
결과
4. 이미지 처리 방식의 차이
- Torchvision은 PIL.Image 형식을 주로 사용하며, 변환 과정에서 이미지를 PIL 포맷으로 처리합니다.
- 반면, Albumentations는 NumPy 배열 형식을 다루며, OpenCV를 통해 이미지를 처리합니다.
이로 인해 두 라이브러리는 이미지 데이터 처리 방식에서 차이를 보일 수 있습니다. 예를 들어, PIL과 OpenCV는 색상 공간 처리 방식이나 픽셀 값의 표현 방식에서 차이가 있을 수 있습니다. 특히 스케치 이미지처럼 미세한 픽셀 표현이 중요한 데이터에서는 이러한 차이가 모델 학습에 영향을 미칠 수 있습니다.
5. Resize 처리 방식의 차이
마지막으로, 이미지 크기 조정 방식에서 큰 차이를 발견했습니다. 스케치 이미지처럼 미세한 디테일이 중요한 데이터에서는 리사이즈 처리 방식의 차이가 더욱 두드러집니다.
- Torchvision의 transforms.Resize()는 PIL 기반으로 작동하며, 기본적으로 bilinear 또는 nearest 인터폴레이션 방식을 사용합니다. 기본 설정은 bilinear이지만, 사용자가 다른 방식으로 명시할 수 있습니다.
- Albumentations의 A.Resize()는 OpenCV 기반으로 작동하며, 기본적으로 INTER_LINEAR 인터폴레이션 방식을 사용합니다. OpenCV는 이미지 처리에 최적화된 고급 알고리즘을 제공하기 때문에, 특히 선명한 디테일을 유지하는 데 유리할 수 있습니다.
왜 차이가 발생할까?
- 인터폴레이션 방식: 두 라이브러리가 사용하는 기본적인 인터폴레이션 방식이 다릅니다. OpenCV의 INTER_LINEAR는 미세한 디테일을 잘 표현할 수 있도록 최적화된 방식이어서, 스케치 이미지의 선명함을 더 잘 보존할 수 있습니다.
- 알고리즘 차이: Albumentations는 OpenCV의 고급 이미지 처리 기능을 사용하므로, Torchvision의 PIL 기반 처리보다 더 정밀한 처리가 가능할 수 있습니다. 스케치 이미지처럼 픽셀 기반의 섬세한 이미지에서는 이러한 미세한 차이가 눈에 띌 수 있습니다.
import numpy as np
import torch
import matplotlib.pyplot as plt
from PIL import Image
from torchvision import transforms
import albumentations as A
from albumentations.pytorch import ToTensorV2
import random
import os
# Torchvision 변환 클래스
class TorchvisionTransform:
def __init__(self, is_train: bool = True):
common_transforms = [
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
]
if is_train:
self.transform = transforms.Compose(
[
# transforms.RandomHorizontalFlip(p=0.5),
# transforms.RandomRotation(15),
# transforms.ColorJitter(brightness=0.2, contrast=0.2),
] + common_transforms
)
else:
self.transform = transforms.Compose(common_transforms)
def __call__(self, image: np.ndarray) -> torch.Tensor:
image = Image.fromarray(image)
transformed = self.transform(image)
return transformed
# Albumentations 변환 클래스
class AlbumentationsTransform:
def __init__(self, is_train: bool = True):
common_transforms = [
A.Resize(224, 224),
A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
ToTensorV2()
]
if is_train:
self.transform = A.Compose(
[
# A.HorizontalFlip(p=0.5),
# A.Rotate(limit=15),
# A.RandomBrightnessContrast(p=0.2),
] + common_transforms
)
else:
self.transform = A.Compose(common_transforms)
def __call__(self, image: np.ndarray) -> torch.Tensor:
transformed = self.transform(image=image)
return transformed['image']
# 시각적으로 변환된 이미지를 비교하는 함수
def plot_image_comparison(original_img, torchvision_img, albumentations_img, idx):
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(original_img)
ax[0].set_title(f'Original Image {idx}')
ax[1].imshow(torchvision_img.permute(1, 2, 0)) # torch tensor는 (C, H, W)이므로 (H, W, C)로 변환
ax[1].set_title('Torchvision Transform')
ax[2].imshow(albumentations_img.permute(1, 2, 0))
ax[2].set_title('Albumentations Transform')
for a in ax:
a.axis('off')
plt.show()
# 이미지 파일을 ./test_image 폴더에서 로드하는 함수
def load_images_from_folder(folder, num_images=10):
images = []
filenames = os.listdir(folder)
for i, filename in enumerate(filenames):
if i >= num_images:
break
img_path = os.path.join(folder, filename)
img = np.array(Image.open(img_path).convert("RGB")) # 이미지 파일을 NumPy 배열로 변환
images.append((img, filename))
return images
# ./test_image 폴더에서 이미지 로드
image_folder = './test_image'
num_images = 10
images = load_images_from_folder(image_folder, num_images)
# Torchvision 및 Albumentations 변환 생성
torchvision_transform = TorchvisionTransform(is_train=True)
albumentations_transform = AlbumentationsTransform(is_train=True)
# 10개의 이미지에 대해 변환 및 비교
for idx, (img, filename) in enumerate(images):
# 원본 이미지
original_img = img
# Torchvision과 Albumentations 변환 적용
torchvision_img = torchvision_transform(img)
albumentations_img = albumentations_transform(img)
# 시각적으로 비교
print(f"Comparing Image: {filename}")
plot_image_comparison(original_img, torchvision_img, albumentations_img, idx)
결과
결론
스케치 이미지와 같이 미세한 디테일이 중요한 데이터에서는, 이미지 전처리 라이브러리의 차이가 모델 성능에 영향을 미칠 수 있습니다. 특히 Albumentations는 Resize 와 내부 변환 처리에서 더 세밀한 디테일을 유지하는 데 유리합니다.
Albumentations가 OpenCV 기반의 고급 이미지 처리 기능을 사용하기 때문에, 스케치 이미지의 선명한 선과 디테일을 잘 보존할 수 있습니다. 반면, Torchvision은 PIL 기반의 처리 방식으로 인해 일부 미세한 정보가 손실될 가능성이 있습니다.
따라서, 스케치 이미지처럼 미세한 차이가 중요한 상황에서는 Albumentations를 사용하는 것이 더 나은 선택일 수 있다는 결론에 도달했습니다.
'AI > 딥러닝 프로젝트' 카테고리의 다른 글
[02 Object Detection] - MMdetection 설치 및 기본사용법 (6) | 2024.10.26 |
---|---|
[01 - ImageNet Sketch 데이터] - Augmentation [2] (2) | 2024.09.26 |
[01 - ImageNet Sketch 데이터] - Grad Cam [3] (2) | 2024.09.26 |
[01 - ImageNet Sketch 데이터] - Training (resnet101) [1] (16) | 2024.09.24 |
StratifiedKFold란? (1) | 2024.09.16 |
댓글