본문 바로가기
목차
Python

[PyTorch] Composition-torchvision.transforms.v2

by ds31x 2025. 6. 16.
728x90
반응형

Transform Composition은

  • 여러 개의 개별 변환들을 전략적으로 조합한 Pipeline을 만들어서
  • 더 다양하고 효과적인 데이터 증강을 수행 가능케 함.

v2에서는 다음과 같은 조합 방식을 제공.

  • Compose(순차적 적용),
  • RandomChoice(배타적 선택),
  • RandomApply(조건부 그룹 적용),
  • RandomOrder(순서 랜덤화 적용)

https://docs.pytorch.org/vision/main/transforms.html#composition

 

Transforming and augmenting images — Torchvision main documentation

Shortcuts

docs.pytorch.org

관련 gist

https://gist.github.com/dsaint31x/325b71e69a62e71b77a1ae96c3fe906c

 

dl_composition-torchvision-transforms-v2.ipynb

dl_composition-torchvision-transforms-v2.ipynb. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 


Prerequisites

colab에서 다음의 코드들을 수행시켜서 이미지를 다운로드하고 아래의 예제 코드를 수행할 수 있도록 함:

img_path = "assets/astronaut.jpg"  
img_url = "https://raw.githubusercontent.com/pytorch/vision/main/gallery/assets/astronaut.jpg"

!mkdir -p assets  
!curl -o {img_path} {img_url}

from torchvision.io import decode_image

original_img = decode_image(img_path)
print(f" {type(original_img) = }\n \
{original_img.dtype = }\n \
{original_img.shape = }")

 

다음은 이미지 출력을 위한 plot 함수임:

더보기
# https://github.com/pytorch/vision/tree/main/gallery/
# 위의 torchvision관련 예제들의 display를 위한 plot함수를 그대로 가져옴.

import matplotlib.pyplot as plt
import torch
from torchvision.utils import draw_bounding_boxes, draw_segmentation_masks
from torchvision import tv_tensors
from torchvision.transforms.v2 import functional as F


def plot(imgs, figsize=None, row_title=None, **imshow_kwargs):
    if not isinstance(imgs[0], list):
        # Make a 2d grid even if there's just 1 row
        imgs = [imgs]

    num_rows = len(imgs)
    num_cols = len(imgs[0])
    _, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False, figsize=figsize)
    for row_idx, row in enumerate(imgs):
        for col_idx, img in enumerate(row):
            boxes = None
            masks = None
            if isinstance(img, tuple):
                img, target = img
                if isinstance(target, dict):
                    boxes = target.get("boxes")
                    masks = target.get("masks")
                elif isinstance(target, tv_tensors.BoundingBoxes):
                    boxes = target
                else:
                    raise ValueError(f"Unexpected target type: {type(target)}")
            img = F.to_image(img)
            if img.dtype.is_floating_point and img.min() < 0:
                # Poor man's re-normalization for the colors to be OK-ish. This
                # is useful for images coming out of Normalize()
                img -= img.min()
                img /= img.max()

            img = F.to_dtype(img, torch.uint8, scale=True)
            if boxes is not None:
                img = draw_bounding_boxes(img, boxes, colors="yellow", width=3)
            if masks is not None:
                img = draw_segmentation_masks(img, masks.to(torch.bool), colors=["green"] * masks.shape[0], alpha=.65)

            ax = axs[row_idx, col_idx]
            ax.imshow(img.permute(1, 2, 0).numpy(), **imshow_kwargs)
            ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])

    if row_title is not None:
        for row_idx in range(num_rows):
            axs[row_idx, 0].set(ylabel=row_title[row_idx])

    plt.tight_layout()

 


Compose

역할:

  • 여러 변환(transformations)들을 순차적으로 연결하여 하나의 Pipeline으로 구성.
  • 변환들을 체인(chain) 형태로 조합하여 복합적인 전처리 과정 구현
  • 첫 번째 변환의 출력이 두 번째 변환의 입력이 되는 순차 실행
  • 데이터 전처리 Pipeline의 기본 구조 제공

사용 용도:

  • 전체 데이터 전처리 Pipeline 구성
    • Resize → Augmentation → Tensor로 변환 → Normalization
  • 복잡한 transform sequence를 하나의 객체로 관리
  • 학습용과 검증용 Pipeline 분리 구성
  • 재사용 가능한 변환 조합 생성
  • 코드 가독성 및 유지보수성 향상
  • 실험에서 일관된 전처리 과정 보장
  • 모듈화된 데이터 처리 워크플로우 구축

주요 파라미터:

  • transforms (Sequence[Callable]): 순서대로 적용할 변환들의 리스트
    • list 또는 tuple 형태로 변환들을 순서대로 나열
    • 각 변환은 callable 객체여야 함:
      • v2에서 torch.nn.Module의 subclass 객체이길 권함.
    • 순서가 중요함 (첫 번째부터 마지막까지 순차 실행)

변환 로직:

  • 입력 데이터에 대해 변환들을 순서대로 적용
  • output = transform_n(...transform_2(transform_1(input)))
  • 중간 단계에서 오류 발생 시 전체 파이프라인 중단
  • 각 변환의 출력 타입이 다음 변환의 입력 타입과 호환되어야 함

특징:

  • Sequential execution: 변환들이 순서대로 연속 실행
  • Type compatibility: v2에서는 다양한 타입 (이미지, bbox, mask 등) 지원
  • No TorchScript support: TorchScript 사용 시 torch.nn.Sequential 권장
  • Deterministic order: 항상 동일한 순서로 변환 적용
  • Error propagation: 하나의 변환 실패 시 전체 파이프라인 실패
  • Memory efficient: 중간 결과를 저장하지 않고 즉시 다음 변환에 전달

사용 예시:

from torchvision.transforms.v2 import (
    Compose, RandomResizedCrop, RandomHorizontalFlip, 
    ColorJitter, ToImage, ToDtype, Normalize, Resize, CenterCrop,
    RandomRotation, RandomApply, GaussianBlur
)
import torch

# === 학습용 데이터 전처리 파이프라인 (v2) ===
train_transform = Compose([
    # 1. 기하학적 변환 (데이터 증강)
    RandomResizedCrop(224),                   # v2: bbox, mask도 함께 변환 가능
                                              # → 스케일 불변성 학습, 객체 위치 다양화

    RandomHorizontalFlip(p=0.5),              # v2: 모든 TVTensor 타입 지원
                                              # → 좌우 대칭성 학습, 데이터 2배 효과

    # 2. 색상 변환 (robust 특징 학습)
    ColorJitter(                             # v2: 더 빠른 구현
        brightness=0.4,                      # brightness ±40% 변화
        contrast=0.4,                        # contrast ±40% 변화  
        saturation=0.4,                      # saturation ±40% 변화
        hue=0.1                              # hue ±10% 변화
    ),                                       # 다양한 조명 조건 변화에 robust한 모델

    # 3. v2 텐서 변환 (새로운 방식)
    ToImage(),                              # PIL/numpy → TV Image tensor
    ToDtype(torch.float32, scale=True),     # dtype 변환 + [0,255] → [0,1] 스케일링
                                            # v2에서는 ToTensor() 대신 이 조합 사용

    # 4. 표준화 (학습 안정성 향상)
    Normalize(                              # v2: 배치 처리 개선
        mean=[0.485, 0.456, 0.406],         # RGB 채널별 평균값 (ImageNet 통계)
        std=[0.229, 0.224, 0.225]           # RGB 채널별 표준편차 (ImageNet 통계)
    )                                       # → 입력값 분포 안정화, 수렴 속도 향상
])
transformed_imgs = [train_transform(original_img) for _ in range(4)]
plot([original_img] + transformed_imgs)

# === 검증/테스트용 파이프라인 (v2, 일관된 전처리) ===
val_transform = Compose([
    # 1. Deterministic Resize
    Resize(256),                           # v2: antialias 옵션 개선
                                           # → 입력 크기 표준화

    # 2. 중앙 크롭 (가장 중요한 영역 추출)
    CenterCrop(224),                      # v2: 다양한 입력 형태 지원
                                          # → 객체가 중앙에 위치한다는 가정

    # 3. v2 방식 텐서 변환
    ToImage(),                            # v2의 새로운 변환 방식
    ToDtype(torch.float32, scale=True),   # 명시적 dtype 및 스케일 변환
    Normalize(                            # 동일한 정규화 (필수!)
        mean=[0.485, 0.456, 0.406], 
        std=[0.229, 0.224, 0.225]
    )
])
transformed_imgs = [val_transform(original_img) for _ in range(4)]
plot([original_img] + transformed_imgs)

# === Object Detection용 파이프라인 (v2의 강점) ===
from torchvision.transforms.v2 import RandomIoUCrop, SanitizeBoundingBoxes

detection_transform = Compose([
    RandomIoUCrop(),                      # v2 only: bbox 고려한 크롭
    RandomHorizontalFlip(p=0.5),          # bbox도 함께 변환
    SanitizeBoundingBoxes(),              # v2 only: 유효하지 않은 bbox 제거
    ToImage(),
    ToDtype(torch.float32, scale=True),
])                                        # 이미지+bbox+mask 동시 변환

from torchvision import tv_tensors  # we'll describe this a bit later, bare with us

boxes = tv_tensors.BoundingBoxes(
    [
        [15, 10, 370, 510],
        [275, 340, 510, 510],
        [130, 345, 210, 425]
    ],
    format="XYXY", canvas_size=original_img.shape[-2:])

# SanitizeBoundingBoxes가 기대하는 형태:
# 1. Dictionary: {"image": img, "boxes": boxes, "labels": labels}
# 2. Two-tuple: (img, {"boxes": boxes, "labels": labels})
input_data = {
    "image": original_img,
    "boxes": boxes,
    "labels": torch.tensor([1, 2, 3]), # dummy label
}

transformed_data = [detection_transform(input_data) for _ in range(4)]
dispaly = [(input_data["image"], input_data["boxes"])]
for d in transformed_data:
  dispaly.append((d["image"], d["boxes"]))
plot(dispaly,figsize=(10,2))

 


RandomChoice

역할:

  • 제공된 변환들 중에서 하나를 랜덤하게 선택하여 적용하는 확률적 컨테이너 변환
  • 여러 변환 옵션 중 실행 시마다 하나만 선택적으로 실행
  • 변환 다양성을 위한 선택적 분기 구조 제공
  • 상호 배타적인 변환들 중 하나를 확률적으로 적용

사용 용도:

  • 서로 다른 증강 전략을 랜덤하게 선택 적용
  • 상호 배타적인 변환들 중 하나만 적용 (예: 회전 vs 뒤집기)
  • 데이터 증강의 다양성 증대 without 모든 변환 동시 적용
  • 특정 변환들의 확률 가중치 조절
  • 실험에서 다양한 증강 전략의 무작위 테스트
  • 계산 비용 제어 (여러 변환 중 하나만 실행)
  • 모델이 다양한 변환 패턴에 노출되도록 훈련

주요 파라미터:

  • transforms (Sequence[Callable]): 선택 가능한 변환들의 리스트
    • 각 변환은 독립적으로 실행 가능해야 함
    • 동일한 입출력 타입을 가져야 함
  • p (Optional[List[float]]): 각 변환이 선택될 확률 가중치
    • None: 모든 변환이 동일한 확률 (기본값)
    • 리스트 합이 1이 아니어도 자동으로 정규화됨
    • 특정 변환에 더 높은 확률 부여 가능

변환 로직:

  • 확률 분포에 따라 변환 중 하나를 랜덤 선택
  • 선택된 변환만 실행하고 나머지는 무시
  • p가 None이면 uniform distribution으로 선택
  • p가 주어지면 weighted random selection

특징:

  • Mutually exclusive: 여러 변환 중 정확히 하나만 실행
  • Stochastic selection: 매번 다른 변환이 선택될 수 있음
  • Weighted probability: 변환별 선택 확률 조절 가능
  • No TorchScript support: 스크립팅 불가능
  • Single execution: 하나의 변환만 실행되므로 계산 효율적
  • Dynamic behavior: 실행 시마다 다른 동작 가능

사용 예시:

from torchvision.transforms.v2 import (
    RandomChoice, RandomHorizontalFlip, RandomVerticalFlip,
    RandomRotation, ColorJitter, Compose, GaussianBlur,
    RandomGrayscale, RandomPerspective, ElasticTransform,
    RandomAffine, RandomAdjustSharpness, RandomSolarize
)

# === 기본: 균등 확률 선택 (v2) ===
basic_augment = RandomChoice([
    RandomHorizontalFlip(p=1.0),         # v2: TVTensor 지원
    RandomVerticalFlip(p=1.0),           # v2: 배치 처리 개선
    RandomRotation(degrees=15),          # v2: 더 빠른 구현
    ColorJitter(brightness=0.3)          # v2: 성능 최적화
])                                    # → 각각 25% 확률로 선택됨
                                      # → 매번 정확히 하나의 변환만 적용

transformed_imgs = [basic_augment(original_img) for _ in range(4)]
plot([original_img] + transformed_imgs)

# === 가중 확률 선택 (v2 도메인 특성 반영) ===
weighted_augment = RandomChoice([
    RandomHorizontalFlip(p=1.0),          # v2: 모든 타입 호환
    RandomRotation(degrees=10),           # v2: interpolation 개선
    ColorJitter(brightness=0.2),          # v2: 메모리 효율성 향상
    lambda x: x                           # Identity 변환 (원본 유지)
], p=[0.4, 0.2, 0.2, 0.2])            # 40%, 20%, 20%, 20% 확률 분배
                                      # → 수평 뒤집기를 가장 선호
transformed_imgs = [weighted_augment(original_img) for _ in range(4)]
plot([original_img] + transformed_imgs)

# === v2 전용 변환들을 활용한 선택 ===
v2_exclusive_choice = RandomChoice([
    # v2에서 크게 개선된 변환들
    ElasticTransform(                  # v2: 성능 대폭 개선
        alpha=50.0, 
        sigma=5.0
    ),
    RandomPerspective(                 # v2: 더 정확한 구현
        distortion_scale=0.2, 
        p=1.0
    ),
    GaussianBlur(                      # v2: 다양한 커널 크기 지원
        kernel_size=(3, 7), 
        sigma=(0.1, 2.0)
    )
], p=[0.3, 0.3, 0.4])              # v2 최적화의 이점 활용
transformed_imgs = [v2_exclusive_choice(original_img) for _ in range(4)]
plot([original_img] + transformed_imgs)

# === 복합 데이터 타입에서의 활용 ===
multitype_choice = RandomChoice([
    # 이미지만 변환
    ColorJitter(brightness=0.3),       # 색상은 이미지에만 적용

    # 이미지+bbox+mask 모두 변환  
    RandomHorizontalFlip(p=1.0),       # v2: 모든 타입 동시 변환
    RandomRotation(degrees=15),        # v2: bbox 좌표도 정확히 변환
])                                 # → v2의 멀티타입 지원 활용

transformed_imgs = [multitype_choice(original_img,boxes) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === 수정된 실시간 데이터 로딩 (v2) ===
class V2Dataset(Dataset):
    def __init__(self, image_paths, bbox_data=None):
        self.image_paths = image_paths
        self.bbox_data = bbox_data

        # v2의 다양한 변환 전략
        self.augment = RandomChoice([
            Compose([                        # 가벼운 v2 변환
                RandomHorizontalFlip(p=1.0), # 33% × 100% = 33%
                ColorJitter(brightness=0.2)  # OK (p 파라미터 없음)
            ]),
            Compose([                        # 중간 v2 변환  
                RandomRotation(degrees=10),  # OK (p 파라미터 없음)
                GaussianBlur(kernel_size=3)  # OK (p 파라미터 없음)
            ]),
            Compose([                        # 강한 v2 변환
                RandomPerspective(
                    distortion_scale=0.1, 
                    p=1.0,
                    ),                       # 33% × 100% = 33%
                RandomAdjustSharpness(
                    sharpness_factor=1.5, 
                    p=1.0,
                    )                        # 33% × 100% = 33%
            ])
        ])

    def __getitem__(self, idx):
        image = load_image(self.image_paths[idx])
        bbox = self.bbox_data[idx] if self.bbox_data else None

        # v2: 이미지와 bbox를 동시에 변환
        if bbox is not None:
            return self.augment(image, bbox)   # 멀티타입 입력 지원
        else:
            return self.augment(image)         # 단일 타입 입력
  • Dataset 작성의 경우로, 단순 transform으로 넘기지 않은 경우임.

RandomApply

역할:

  • 여러 변환(transformations)들을 하나의 그룹으로 묶어 지정된 확률로 전체 그룹을 적용하거나 건너뛰는 컨테이너 변환
  • 복합적인 데이터 증강 파이프라인을 확률적으로 제어
  • 전체 변환 시퀀스를 단위로 하는 조건부 적용
  • 여러 변환의 조합 효과를 일괄적으로 관리

사용 용도:

  • 강한 데이터 증강을 선택적으로 적용하여 오버피팅 방지
  • 복잡한 변환 조합의 적용 빈도 제어 (예: 색상 왜곡 + 블러 조합)
  • 학습 초기에는 약한 증강, 후기에는 강한 증강 적용 전략
  • 특정 변환 그룹이 특정 데이터에만 유효할 때 조건부 적용
  • 계산 비용이 높은 변환들을 선택적으로 적용하여 효율성 향상
  • 모델이 원본 이미지와 변환된 이미지 모두에 적응하도록 균형 조절
  • 데이터 증강 강도를 동적으로 조절하는 커리큘럼 학습

주요 파라미터:

  • transforms (Sequence[Callable] | ModuleList): 적용할 변환들의 리스트
    • list 또는 tuple: 일반적인 사용법
    • torch.nn.ModuleList: TorchScript 호환성을 위한 권장 방식
    • 순서대로 연속 적용됨 (Compose와 유사)
  • p (float): 전체 변환 그룹이 적용될 확률
    • 0.5: 50% 확률로 전체 그룹 적용 (기본값)
    • 0.0: 항상 원본 유지 (그룹 비활성화)
    • 1.0: 항상 전체 그룹 적용

변환 로직:

  • 확률 p에 따라 전체 Transform 그룹의 적용 여부 결정
  • 적용 시: 모든 Transform을 순서대로 연속 실행
    • 각각의 Transform의 확률이 있을 경우
    • 독립적으로 개별 확률에 따라 개별 실행이 결정됨.
  • 비적용 시: 원본 이미지 그대로 반환
  • 다시 한번 강조하지만, 개별 Transform의 랜덤성은 각 변환 내부에서 독립적으로 처리 (중요!)

특징:

  • Group-level randomness: 개별 변환이 아닌 그룹 단위로 확률 적용
  • All-or-nothing: 그룹 내 모든 변환이 함께 적용되거나 모두 건너뜀
    • 단 그룹내의 개별 Transform의 확률이 있을 경우 영향 받음.
  • Compositional: 내부 변환들의 순차적 조합 효과
  • TorchScript compatible: ModuleList 사용 시 스크립팅 가능
  • Nested structure: 다른 RandomApply 내부에 중첩 사용 가능
  • Performance control: 계산 비용 높은 변환의 선택적 적용

사용 예시:

from torchvision.transforms.v2 import (
    RandomApply, ColorJitter, GaussianBlur, RandomGrayscale,
    RandomPerspective, ElasticTransform, RandomAffine,
    RandomAdjustSharpness, RandomSolarize, RandomPosterize,
    ToImage, ToDtype
)
import torch

# === 기본: v2 색상 변환 그룹의 조건부 적용 ===
color_group = RandomApply([
    ColorJitter(                          # v2: 더 빠른 색상 조정
        brightness=0.4,                   # brightness 변화
        contrast=0.4,                     # contrast 변화
        saturation=0.4,                   # saturation 변화  
        hue=0.1                           # hue 변화
    ),
    GaussianBlur(                         # v2: 개선된 블러 구현
        kernel_size=(3, 7),               # 커널 크기 범위 지정 가능
        sigma=(0.1, 2.0)                  # 시그마 범위 지정 가능
    )
], p=0.3)                         # 30% 확률로 전체 그룹 적용
                                  # 즉, 70%는 원본, 30%는 색상+블러

transformed_imgs = [color_group(original_img) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === TorchScript 호환 버전 (v2 배포용) ===
scripted_transforms = RandomApply(
    torch.nn.ModuleList([                 # v2에서도 TorchScript 지원
        ColorJitter(brightness=0.2),      # 실제 적용 확률: 50% × 100% = 50%
                                          # v2: 더 나은 성능, p 파라미터 없음
        RandomGrayscale(p=0.2)            # 실제 적용 확률: 50% × 20% = 10% 주의!
                                          # v2: 배치 처리 지원
    ]), 
    p=0.5                                 # 50% 확률로 전체 그룹 적용
)
test_script =  torch.jit.script(scripted_transforms)  # v2에서도 스크립팅 가능
                                         # torch.nn.ModuleList 덕분 

transformed_imgs = [test_script(original_img) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === v2에서 크게 개선된 변환들의 그룹 적용 ===
v2_strong_augment = RandomApply([
    # v2에서 성능이 대폭 향상된 변환들
    RandomPerspective(                   # 실제 적용 확률: 10% × 100% = 10%
        distortion_scale=0.2,            # v2: 더 정확하고 빠른 원근 변환
        p=1.0                            # 그룹 선택 시 100% 적용
    ),
    ElasticTransform(                    # 실제 적용 확률: 10% × 100% = 10%
        alpha=50.0,                      # v2: 메모리 효율성 대폭 개선
        sigma=5.0                        # p 파라미터 없음 (항상 적용)
    ),
    RandomAffine(                        # 실제 적용 확률: 10% × 100% = 10%
        degrees=10,                      # v2: 보간 품질 향상
        translate=(0.1, 0.1),            # p 파라미터 없음 (항상 적용)
        scale=(0.9, 1.1)
    )
], p=0.1)                                # 10%만 적용 (강한 변형)
transformed_imgs = [v2_strong_augment(original_img) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === 멀티타입 데이터를 위한 v2 그룹 ===
multitype_group = RandomApply([
    RandomHorizontalFlip(p=1.0),         # 실제 적용 확률: 40% × 100% = 40%
                                         # v2: image+bbox+mask 동시 변환
    RandomRotation(degrees=15),          # 실제 적용 확률: 40% × 100% = 40%
                                         # v2: 모든 타입의 좌표 정확히 계산
    # 이미지에만 적용되는 변환
    ColorJitter(brightness=0.3)          # 실제 적용 확률: 40% × 100% = 40%
                                         # bbox/mask에는 영향 없음
], p=0.4)                          # v2의 멀티타입 지원 활용

transformed_imgs = [multitype_group(original_img,boxes) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === v2 전용 고급 변환 그룹 ===
advanced_v2_group = RandomApply([
    # v2에서 새로 추가되거나 크게 개선된 변환들
    RandomAdjustSharpness(              # v2: 선명도 조정 최적화
        sharpness_factor= 2.0           # 범위 지정 가능
    ),                                 
    RandomSolarize(                     # v2: Solarization효과 개선
        threshold=128
    ),
    RandomPosterize(                    # v2: Posterization효과 최적화
        bits=4
    )
], p=0.25)                         # 25% 확률로 고급 효과 적용
# 실제 확률:
# - RandomAdjustSharpness: 25% × 50% = 12.5%
# - RandomSolarize: 25% × 50% = 12.5%
# - RandomPosterize: 25% × 50% = 12.5%
# 모두 의도한 것보다 절반으로 줄어듦!

transformed_imgs = [advanced_v2_group(original_img) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === Object Detection 전용 v2 그룹 ===
from torchvision.transforms.v2 import RandomIoUCrop, SanitizeBoundingBoxes

detection_group = RandomApply([
    RandomIoUCrop(                        # v2 only: IoU 기반 크롭
        min_scale=0.8,
        max_scale=1.0,
        min_aspect_ratio=0.5,
        max_aspect_ratio=2.0
    ),
    SanitizeBoundingBoxes(),              # v2 only: 유효하지 않은 bbox 정리
], p=0.6)                                # Detection 전용 증강
# 실제 확률:
# - RandomIoUCrop에서 Random은 크롭 위치/크기를 랜덤하게 선택한다는 의미: 100% 발생.
# - SanitizeBoundingBoxes 도 100%발생.
# 최종적으로 모두 60%확률로 일어남.

transformed_data = [detection_group(input_data) for _ in range(4)]
dispaly = [(input_data["image"], input_data["boxes"])]
for d in transformed_data:
  dispaly.append((d["image"], d["boxes"]))
plot(dispaly)

# === 효율적인 전체 파이프라인 구성 ===
complete_v2_pipeline = Compose([
    # 1. v2 기본 변환
    RandomHorizontalFlip(p=0.5),          # 실제 적용 확률: 50%

    # 2. v2 조건부 색상 그룹 (p=0.7)
    RandomApply([
        ColorJitter(brightness=0.4, contrast=0.4),  # 실제 적용 확률: 70% × 100% = 70%
        RandomGrayscale(p=1.0)                      # 실제 적용 확률: 70% × 100% = 70%
    ], p=0.7),

    # 3. v2 조건부 기하학적 그룹 (p=0.3)  
    RandomApply([
        RandomRotation(degrees=10),                 # 실제 적용 확률: 30% × 100% = 30%
        RandomPerspective(distortion_scale=0.1, p=1.0)  # 실제 적용 확률: 30% × 100% = 30%
    ], p=0.3),

    # 4. v2 텐서 변환
    ToImage(),                            # 실제 적용 확률: 100% (항상 적용)
    ToDtype(torch.float32, scale=True),   # 실제 적용 확률: 100% (항상 적용)
])

transformed_imgs = [complete_v2_pipeline(original_img) for _ in range(4)]

plot([original_img] + transformed_imgs)

# === 배치 처리를 위한 v2 그룹 ===
batch_optimized_group = RandomApply([
    # v2는 배치 텐서를 더 효율적으로 처리
    ColorJitter(brightness=0.3, contrast=0.3),    # 배치 단위 최적화
    GaussianBlur(kernel_size=3),                  # 배치 블러 연산 최적화
    RandomAdjustSharpness(sharpness_factor=1.2)   # 배치 처리 지원
], p=0.5)
# 실제 확률:
# - ColorJitter: 50% × 100% = 50%           
# - GaussianBlur: 50% × 100% = 50%          
# - RandomAdjustSharpness: 50% × 50%(기본p) = 25%  주의!

RandomOrder (작성중)

역할:

  • 여러 변환(transformations)들을 매번 다른 랜덤한 순서로 적용하는 컨테이너 변환
  • 변환 순서에 따른 편향(order bias)을 제거하여 더 균형잡힌 데이터 증강
  • 동일한 변환 조합으로 순서만 바꿔 다양한 결과 생성
  • 변환간 상호작용 효과의 다양성 확보
  • 순서 의존성이 강한 변환들의 공정한 조합

사용 용도:

  • 색상 변환과 블러 효과의 순서에 따른 결과 차이 해결 (ColorJitter → Blur vs Blur → ColorJitter)
  • 회색조 변환과 색상 조정의 순서 편향 제거 (Grayscale 전후 ColorJitter 효과 차이)
  • 기하학적 변환들의 누적 왜곡 패턴을 랜덤화하여 다양성 증가
  • 선명도/흐림 효과와 다른 변환들의 순서 의존성 해결
  • Detection 모델에서 이미지+bbox+mask 변환의 순서 편향 제거
  • 동일한 증강 강도로 더 많은 변화 조합 생성하여 데이터 효율성 향상
  • 특정 변환 순서에 과적합되는 것을 방지

주요 파라미터:

  • transforms (Sequence[Callable]): 랜덤 순서로 적용할 변환들의 리스트
    • 모든 변환이 매번 적용됨 (100% 적용율)
    • 순서만 매번 랜덤하게 셔플됨
    • 개별 변환의 내부 확률(p)은 각각 독립적으로 작동
    • n개 변환 → n! 가지 순서 조합 가능

변환 로직:

  • 매 호출마다 주어진 변환 리스트의 순서를 랜덤하게 셔플
  • 셔플된 순서대로 모든 변환을 순차적으로 적용
  • 각 변환의 내부 랜덤성(확률 p)은 변환 순서와 독립적으로 작동
  • 모든 변환이 반드시 적용됨 (건너뛰기 없음)
  • 순서 셔플링은 torch.randperm 기반으로 재현 가능

특징:

  • Order randomization: 변환 적용 순서만 랜덤화, 변환 자체는 모두 적용
  • Bias elimination: 고정 순서에 따른 편향 효과 제거
  • Deterministic randomness: 시드 설정으로 재현 가능한 순서 생성
  • No TorchScript support: 스크립팅 지원 안함 (여전한 제한사항)
  • Full application: 그룹 내 모든 변환이 100% 적용 (확률 제어 불가)
  • Nested compatibility: RandomApply 내부에서 사용 가능
  • Multi-type support: v2의 이미지+bbox+mask 동시 변환 지원
  • Production ready: 상용 환경에서 안전하게 사용 가능

사용 예시:

from torchvision.transforms.v2 import (
    RandomOrder, ColorJitter, 
    GaussianBlur, RandomAdjustSharpness,
   )

# === 기본 사용: 색상 변환 순서 랜덤화 ===
color_random_order = RandomOrder([
    ColorJitter(brightness=0.4, contrast=0.4),
    GaussianBlur(kernel_size=3, sigma=1.0),
    RandomAdjustSharpness(sharpness_factor=1.5)  # p=0.5 기본값
])

# 가능한 순서 조합: 3! = 6가지
# 1) ColorJitter → GaussianBlur → RandomAdjustSharpness
# 2) ColorJitter → RandomAdjustSharpness → GaussianBlur  
# 3) GaussianBlur → ColorJitter → RandomAdjustSharpness
# 4) GaussianBlur → RandomAdjustSharpness → ColorJitter
# 5) RandomAdjustSharpness → ColorJitter → GaussianBlur
# 6) RandomAdjustSharpness → GaussianBlur → ColorJitter

# 실제 적용 확률:
# - ColorJitter: 100% (순서만 변함)
# - GaussianBlur: 100% (순서만 변함)
# - RandomAdjustSharpness: 50% (내부 p=0.5, 순서는 변함)

# === 확률 제어: RandomApply로 감싸기 ===
from torchvision.transforms.v2 import RandomApply

probabilistic_order = RandomApply([
    RandomOrder([
        ColorJitter(brightness=0.5, contrast=0.5),
        RandomGrayscale(p=1.0),
        GaussianBlur(kernel_size=5)
    ])
], p=0.7)  # 70% 확률로 랜덤 순서 적용

# 실제 확률:
# - 70% 확률: 3개 변환을 랜덤 순서로 모두 적용
# - 30% 확률: 원본 이미지 그대로
# - ColorJitter: 70% × 100% = 70%
# - RandomGrayscale: 70% × 100% = 70%  
# - GaussianBlur: 70% × 100% = 70%

# === Detection용: 멀티타입 순서 랜덤화 ===
from torchvision.transforms.v2 import RandomHorizontalFlip, RandomRotation

detection_order = RandomOrder([
    RandomHorizontalFlip(p=1.0),        # 이미지+bbox+mask
    RandomRotation(degrees=15),         # 이미지+bbox+mask
    ColorJitter(brightness=0.3)         # 이미지만
])

# 모든 변환이 랜덤 순서로 적용되지만
# 좌표 변환의 정확성은 v2가 보장

# === 순서 민감성 해결 예시 ===
# 문제: 회색조 변환 후 색상 조정은 효과 없음
from torchvision.transforms.v2 import Compose

bias_prone_fixed = Compose([
    RandomGrayscale(p=1.0),             # 먼저 회색조로
    ColorJitter(brightness=0.5)         # 색상 조정 효과 없음!
])

# 해결: 순서 랜덤화로 편향 제거
bias_free_random = RandomOrder([
    RandomGrayscale(p=0.5),             # 50% 확률
    ColorJitter(brightness=0.5)         # 순서에 따라 효과 다름
])

# RandomOrder로 두 순서 모두 경험:
# 1) RandomGrayscale → ColorJitter: 회색조 후 색상 (제한적 효과)
# 2) ColorJitter → RandomGrayscale: 색상 후 회색조 (풍부한 효과)

# === 강력한 증강 파이프라인 ===
from torchvision.transforms.v2 import RandomSolarize, RandomPosterize

strong_augmentation = RandomApply([
    RandomOrder([
        ColorJitter(brightness=0.6, contrast=0.6, saturation=0.4, hue=0.1),
        GaussianBlur(kernel_size=(5, 9), sigma=(1.0, 3.0)),
        RandomAdjustSharpness(sharpness_factor=2.0),
        RandomSolarize(threshold=128),
        RandomPosterize(bits=4)
    ])
], p=0.4)  # 40% 확률로 강한 증강 적용

# 5개 변환의 5! = 120가지 순서 조합
# 매번 다른 순서로 적용되어 극도로 다양한 결과

# === 실용적인 단계별 파이프라인 ===
complete_pipeline = Compose([
    # 1단계: 기하학적 변환 (순서 고정)
    RandomHorizontalFlip(p=0.5),

    # 2단계: 색상 변환 그룹 (순서 랜덤)
    RandomApply([
        RandomOrder([
            ColorJitter(brightness=0.4, contrast=0.4),
            RandomAdjustSharpness(sharpness_factor=1.5),
            RandomGrayscale(p=0.2)  # 20% 확률로 회색조
        ])
    ], p=0.8),  # 80% 확률로 색상 그룹 적용

    # 3단계: 효과 변환 그룹 (순서 랜덤)  
    RandomApply([
        RandomOrder([
            GaussianBlur(kernel_size=(3, 7)),
            RandomSolarize(threshold=128),
            RandomPosterize(bits=4)
        ])
    ], p=0.3),  # 30% 확률로 효과 그룹 적용

    # 4단계: 최종 변환 (순서 고정)
    ToImage(),
    ToDtype(torch.float32, scale=True)
])

# === 성능 최적화된 파이프라인 ===
# 빠른 변환을 앞에, 무거운 변환을 뒤에 배치
optimized_random_order = RandomOrder([
    ColorJitter(brightness=0.3, contrast=0.3),     # 빠름
    RandomAdjustSharpness(sharpness_factor=1.2),   # 중간
    GaussianBlur(kernel_size=(5, 9))               # 무거움
])

# RandomOrder가 순서를 섞더라도 평균적으로 성능 예측 가능

# === 시드 기반 재현 가능한 사용 ===
import torch

# 재현 가능한 랜덤 순서
torch.manual_seed(42)
reproducible_order = RandomOrder([
    ColorJitter(brightness=0.4),
    GaussianBlur(kernel_size=3),
    RandomAdjustSharpness(sharpness_factor=1.5)
])

# 같은 시드로 호출하면 같은 순서 보장
torch.manual_seed(42)
result1 = reproducible_order(image)
torch.manual_seed(42) 
result2 = reproducible_order(image)
# result1과 result2는 동일한 변환 순서 적용

# === 커스텀 조건부 RandomOrder ===
class ConditionalRandomOrder:
    """확률 제어가 가능한 RandomOrder 래퍼"""
    def __init__(self, transforms, p=1.0):
        self.random_order = RandomOrder(transforms)
        self.p = p

    def __call__(self, *inputs):
        if torch.rand(1) < self.p:
            return self.random_order(*inputs)
        return inputs[0] if len(inputs) == 1 else inputs

# 사용 예시
custom_order = ConditionalRandomOrder([
    ColorJitter(brightness=0.3),
    GaussianBlur(kernel_size=3),
    RandomAdjustSharpness(sharpness_factor=1.5)
], p=0.6)  # 60% 확률로 랜덤 순서 적용

 

제약사항:

  • TorchScript 미지원: 여전히 스크립팅 불가능 (v0.21.0 기준.)
  • 확률 제어 불가: RandomApply로 감싸서 해결 필요
  • 전체 적용: 모든 변환이 100% 적용되므로 계산 비용 고려

마이그레이션 가이드:

# 낮은 버전에서 베타 경고 하려면.
# torchvision.disable_beta_transforms_warning()  # 더 이상 불필요
728x90