STEP 4:データ拡張とバッチ処理

🔄 STEP 4: データ拡張とバッチ処理

torchvision.transformsとAlbumentationsでデータ拡張を実装し、
効率的なデータパイプラインを構築します

📋 このステップで学ぶこと

  • データ拡張の重要性と目的
  • torchvision.transformsの基本(反転、回転、色調整、正規化)
  • Albumentationsライブラリの活用
  • カスタムデータセットの作成(torch.utils.data.Dataset)
  • DataLoaderによる効率的なバッチ処理
  • 実践:画像分類用データパイプラインの構築

🎯 1. データ拡張とは何か?

データ拡張(Data Augmentation)とは、「手持ちの画像データを加工して、疑似的に新しいデータを作り出す」技術です。

深層学習は大量のデータが必要ですが、データを集めるのは時間もお金もかかります。そこで、既存のデータを「反転」「回転」「色調整」などで変換して、データの量を増やすのがデータ拡張です。

1-1. データ拡張の具体例

📷 1枚の猫画像から10枚に増やす例

元の画像が1枚あれば、以下のような加工で「見た目は違うけど同じ猫」の画像を何枚も作れます。

【データ拡張の例】 元画像:猫の写真1枚 ↓ 様々な変換を適用 1. 元の画像(そのまま) 2. 水平反転(左右を入れ替え) 3. 10度回転(少し傾ける) 4. 明るさ+20%(少し明るく) 5. 色相変更(色味を変える) 6. ランダムクロップ(一部を切り出し) 7. ガウシアンブラー(少しぼかす) 8. 反転+回転(組み合わせ) 9. 反転+明るさ変更 10. 回転+クロップ 結果:1枚 → 10枚に増加!

1-2. なぜデータ拡張が必要なのか?

データ拡張には、主に3つの重要な効果があります。

💡 データ拡張の3つの効果

① データ不足の解消
1,000枚しかない画像を、拡張で10,000枚相当に増やせます。データ収集のコストを削減できます。

② 過学習(Overfitting)の防止
同じデータばかり見ていると、モデルが「訓練データだけに適応」してしまいます。拡張でデータを多様にすることで、この問題を防ぎます。

③ 汎化性能の向上
様々な条件(角度、明るさ、位置)のデータで学習すると、実際の環境でも安定して動作するモデルになります。

【データ拡張がないと起きる問題】 例:猫の分類モデルを学習する場合 データ拡張なし: ・訓練データ:猫が画像の中央にいる写真ばかり ・テスト時:猫が右端にいる写真 → 認識失敗! データ拡張あり: ・訓練データ:ランダムクロップで猫が様々な位置に ・テスト時:猫がどこにいても → 認識成功!

1-3. 主なデータ拡張手法一覧

手法 何をするか 効果 主な用途
水平反転 画像を左右反転 向きへの不変性 一般的な画像分類
垂直反転 画像を上下反転 向きへの不変性 衛星画像、医療画像
回転 指定角度で回転 角度への不変性 文字認識、物体検出
ランダムクロップ ランダムな位置を切り出し 位置への不変性 画像分類全般
色調整 明るさ・コントラスト変更 照明条件への対応 屋外撮影画像
ガウシアンブラー 画像をぼかす ピンぼけへの対応 実環境での使用
ノイズ追加 ランダムノイズを加える ロバスト性向上 低画質画像への対応
⚠️ データ拡張の注意点

データ拡張は「何でもやればいい」わけではありません。タスクに適した拡張を選ぶことが重要です。

例:手書き数字認識(MNIST)
・水平反転 → NG!「6」が「9」のように見えてしまう
・垂直反転 → NG!数字が上下逆になる
・軽い回転(±15度)→ OK!手書きの傾きをシミュレート

🔧 2. torchvision.transforms – PyTorchの標準ツール

torchvision.transformsは、PyTorchが公式に提供するデータ拡張ライブラリです。PyTorchを使うなら、まずこれを覚えましょう。

2-1. 準備:インストールとインポート

Google Colabを使う場合、PyTorchとtorchvisionは最初から入っています。ローカル環境の場合は以下でインストールします。

# PyTorchとtorchvisionのインストール(ローカル環境のみ) pip install torch torchvision

基本的なインポート

# transforms:データ拡張のためのモジュール from torchvision import transforms # PIL:Pythonの画像処理ライブラリ # torchvisionはPIL形式の画像を扱う from PIL import Image # 画像読み込み img = Image.open(‘sample.jpg’) print(type(img)) # <class ‘PIL.Image.Image’>

2-2. 基本的な変換(個別に理解する)

まずは、よく使う変換を1つずつ見ていきましょう。

① ToTensor – 画像をテンソルに変換

PyTorchで画像を扱うには、PIL画像を「テンソル」という形式に変換する必要があります。

# ToTensor()の役割: # 1. PIL画像 → PyTorchテンソルに変換 # 2. ピクセル値を 0〜255 → 0〜1 に正規化 # 3. 軸の順序を (高さ, 幅, チャンネル) → (チャンネル, 高さ, 幅) に変更 transform_to_tensor = transforms.ToTensor() img_tensor = transform_to_tensor(img) print(img_tensor.shape) # torch.Size([3, 224, 224]) # [3, 224, 224] = [チャンネル数, 高さ, 幅]

② Resize – サイズ変更

# Resize((高さ, 幅)):指定サイズにリサイズ # なぜ必要? → CNNは固定サイズの入力が必要なことが多い transform_resize = transforms.Resize((224, 224)) img_resized = transform_resize(img) # 224×224はImageNetの標準サイズ # 事前学習モデルを使う場合、このサイズが推奨される

③ CenterCrop – 中央を切り出し

# CenterCrop(サイズ):画像の中央からサイズ×サイズを切り出し # 縦横比を維持したまま中央部分だけ使いたい場合に便利 transform_center_crop = transforms.CenterCrop(224) img_cropped = transform_center_crop(img) # 例:300×400の画像 → 中央の224×224を切り出し

④ RandomCrop – ランダムに切り出し(データ拡張)

# RandomCrop(サイズ):ランダムな位置から切り出し # 毎回異なる位置から切り出すので、データ拡張効果がある transform_random_crop = transforms.RandomCrop(224) img_random_cropped = transform_random_crop(img) # 同じ画像でも、呼び出すたびに異なる結果になる

2-3. 反転と回転

水平反転(左右反転)

# RandomHorizontalFlip(p=確率) # p=0.5 → 50%の確率で左右反転、50%の確率でそのまま transform_hflip = transforms.RandomHorizontalFlip(p=0.5) img_hflipped = transform_hflip(img) # 最もよく使われるデータ拡張の1つ # 左右対称な物体(顔、動物、車など)に有効

垂直反転(上下反転)

# RandomVerticalFlip(p=確率) # 衛星画像や医療画像など、上下の概念がない場合に使用 transform_vflip = transforms.RandomVerticalFlip(p=0.5) img_vflipped = transform_vflip(img) # 注意:顔認識など、上下が重要なタスクでは使わない

回転

# RandomRotation(degrees=角度) # degrees=30 → -30度〜+30度の範囲でランダムに回転 transform_rotation = transforms.RandomRotation(degrees=30) img_rotated = transform_rotation(img) # 手書き文字認識などで有効 # 角度の範囲はタスクに応じて調整

アフィン変換(複合変換)

# RandomAffine:回転 + 平行移動 + スケール を同時に適用 transform_affine = transforms.RandomAffine( degrees=30, # 回転:-30〜+30度 translate=(0.1, 0.1), # 平行移動:画像サイズの10%まで scale=(0.9, 1.1) # スケール:90%〜110% ) img_affine = transform_affine(img) # 複数の変換を1つにまとめられるので便利

2-4. 色調整

ColorJitter – 色のランダム変更

# ColorJitter:明るさ、コントラスト、彩度、色相をランダムに変更 transform_color = transforms.ColorJitter( brightness=0.2, # 明るさ:±20% contrast=0.2, # コントラスト:±20% saturation=0.2, # 彩度:±20% hue=0.1 # 色相:±10%(0〜0.5の範囲) ) img_color = transform_color(img) # 照明条件の変化に対応するモデルを作るのに有効 # 屋外撮影画像では特に重要

グレースケール化

# RandomGrayscale(p=確率):ランダムにグレースケール化 transform_gray = transforms.RandomGrayscale(p=0.1) # 10%の確率 img_gray = transform_gray(img) # カラー情報に依存しすぎないモデルを作るのに有効

ガウシアンブラー

# GaussianBlur:画像をぼかす transform_blur = transforms.GaussianBlur( kernel_size=5, # カーネルサイズ(奇数) sigma=(0.1, 2.0) # ぼかしの強さの範囲 ) img_blurred = transform_blur(img) # ピンぼけした画像にも対応できるモデルを作る

2-5. 正規化 – 最も重要な処理

正規化(Normalize)は、ピクセル値を特定の平均・標準偏差に揃える処理です。これは「データ拡張」ではありませんが、非常に重要です。

🔢 正規化とは?

ToTensor()で 0〜1 に変換された値を、さらに「平均0、標準偏差1」付近に変換します。

計算式:normalized = (元の値 – mean) / std

# ImageNet事前学習モデルを使う場合の正規化 # この値はImageNetの統計から計算されたもの transform_normalize = transforms.Normalize( mean=[0.485, 0.456, 0.406], # RGB各チャンネルの平均 std=[0.229, 0.224, 0.225] # RGB各チャンネルの標準偏差 ) # 注意:ToTensorの後に適用する必要がある img_tensor = transforms.ToTensor()(img) # まず0〜1に変換 img_normalized = transform_normalize(img_tensor) # 次に正規化
💡 なぜ正規化が重要なのか?

① 学習の安定化:値が大きすぎたり小さすぎたりすると、勾配が爆発または消失してしまいます。正規化で値を適切な範囲に揃えることで、安定した学習ができます。

② 収束の高速化:正規化されたデータは学習が速く進みます。

③ 転移学習の必須条件:ResNetやEfficientNetなどの事前学習モデルは、ImageNetの統計で正規化されたデータで学習されています。同じ正規化を使わないと、性能が大幅に低下します。

2-6. Compose – 変換を組み合わせる

実際には、複数の変換を順番に適用します。transforms.Composeを使うと、変換をパイプライン(連続した処理)としてまとめられます。

訓練用の変換(データ拡張あり)

# 訓練用:データ拡張を含む train_transform = transforms.Compose([ # 1. リサイズ(256×256に) transforms.Resize((256, 256)), # 2. ランダムクロップ(224×224を切り出し) # リサイズ→クロップで、位置のバリエーションを作る transforms.RandomCrop(224), # 3. 水平反転(50%の確率) transforms.RandomHorizontalFlip(p=0.5), # 4. 回転(±15度) transforms.RandomRotation(degrees=15), # 5. 色調整 transforms.ColorJitter(brightness=0.2, contrast=0.2), # 6. テンソルに変換(0〜1に正規化) transforms.ToTensor(), # 7. ImageNetの統計で正規化 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

テスト用の変換(データ拡張なし)

# テスト用:データ拡張は使わない test_transform = transforms.Compose([ # 1. リサイズのみ(224×224に) transforms.Resize((224, 224)), # 2. テンソルに変換 transforms.ToTensor(), # 3. 正規化(訓練時と同じ値を使う) transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])
⚠️ 訓練とテストで変換を分ける理由

訓練時:データ拡張を使って、多様な条件を学習させる
テスト時:公平な評価のため、データ拡張は使わない

テスト時にもデータ拡張を使うと、毎回結果が変わってしまい、正確な評価ができません。

2-7. 変換を適用する完成コード(実行用)

※ コードが横に長い場合は横スクロールできます

# データ拡張を適用する完全なコード from torchvision import transforms from PIL import Image import matplotlib.pyplot as plt # サンプル画像をダウンロード import urllib.request url = “https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg” urllib.request.urlretrieve(url, “sample.jpg”) # 画像を読み込み img = Image.open(‘sample.jpg’) # 訓練用の変換を定義 train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 変換を適用 img_transformed = train_transform(img) print(f”変換後の形状: {img_transformed.shape}”) print(f”変換後の値の範囲: {img_transformed.min():.2f} 〜 {img_transformed.max():.2f}”) # 同じ画像に4回変換を適用(毎回違う結果) fig, axes = plt.subplots(1, 4, figsize=(16, 4)) for i in range(4): # 可視化用に正規化を除いた変換 vis_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.2, contrast=0.2), ]) img_aug = vis_transform(img) axes[i].imshow(img_aug) axes[i].set_title(f’Augmented {i+1}’) axes[i].axis(‘off’) plt.tight_layout() plt.show()

実行結果(例):

変換後の形状: torch.Size([3, 224, 224]) 変換後の値の範囲: -2.12 〜 2.64

🚀 3. Albumentations – より高度なデータ拡張

Albumentationsは、torchvisionより高速で機能豊富なデータ拡張ライブラリです。特にKaggleコンペティションで人気があります。

3-1. Albumentationsの特徴

🌟 Albumentationsが人気な理由

① 高速:torchvisionより2〜3倍速い(NumPyベースで最適化)
② 豊富な変換:80種類以上の変換が使える
③ 物体検出・セグメンテーション対応:Bounding BoxやMaskも同時に変換できる
④ Kaggleで実績:多くのKaggle優勝者が使用

3-2. インストールと基本的な使い方

# Albumentationsのインストール pip install albumentations

基本的な書き方

AlbumentationsはOpenCV形式(NumPy配列)の画像を使います。torchvisionとは入力形式が異なるので注意してください。

# インポート import albumentations as A # 変換を定義するモジュール from albumentations.pytorch import ToTensorV2 # PyTorchテンソル変換 import cv2 # 画像読み込み用 import numpy as np # 画像読み込み(OpenCV形式 = NumPy配列) image = cv2.imread(‘sample.jpg’) # OpenCVはBGR順序なので、RGBに変換 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

変換の定義と適用

# 変換を定義 transform = A.Compose([ A.Resize(224, 224), # リサイズ A.HorizontalFlip(p=0.5), # 水平反転(50%) A.Rotate(limit=30, p=0.5), # 回転(±30度、50%の確率) A.RandomBrightnessContrast(p=0.2), # 明るさ・コントラスト調整 A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # 正規化 ToTensorV2() # PyTorchテンソルに変換 ]) # 変換を適用 # 注意:結果は辞書形式で返される transformed = transform(image=image) image_tensor = transformed[‘image’] # 辞書から画像を取り出す print(image_tensor.shape) # torch.Size([3, 224, 224])
⚠️ torchvisionとAlbumentationsの違い

torchvision:
・入力:PIL Image
・出力:直接テンソルを返す
・使い方:img_tensor = transform(img)

Albumentations:
・入力:NumPy配列(OpenCV形式)
・出力:辞書形式で返す
・使い方:img_tensor = transform(image=img)['image']

3-3. Albumentationsの高度な変換

Albumentationsには、torchvisionにはない変換も多数あります。

# 高度な変換の例 # ShiftScaleRotate:平行移動 + スケール + 回転 を同時に A.ShiftScaleRotate( shift_limit=0.1, # 平行移動:±10% scale_limit=0.1, # スケール:±10% rotate_limit=15, # 回転:±15度 p=0.5 # 50%の確率で適用 ) # HueSaturationValue:HSV色空間での調整 A.HueSaturationValue( hue_shift_limit=20, # 色相:±20 sat_shift_limit=30, # 彩度:±30 val_shift_limit=20, # 明度:±20 p=0.5 ) # GaussNoise:ガウシアンノイズを追加 A.GaussNoise(var_limit=(10.0, 50.0), p=0.2) # CoarseDropout:画像の一部をランダムに消す(Cutout) A.CoarseDropout( max_holes=8, # 穴の最大数 max_height=16, # 穴の最大高さ max_width=16, # 穴の最大幅 p=0.2 )

3-4. torchvision vs Albumentations 比較

項目 torchvision Albumentations
速度 普通 速い(2〜3倍)
変換の種類 約30種類 80種類以上
物体検出対応 限定的 完全対応(BBox同時変換)
セグメンテーション 非対応 完全対応(Mask同時変換)
使いやすさ シンプル やや複雑
PyTorch統合 完璧 良好(ToTensorV2必要)
🎯 どちらを使うべきか?

torchvisionがおすすめ:
・画像分類タスク
・PyTorchに慣れていない初心者
・シンプルに実装したい場合

Albumentationsがおすすめ:
・物体検出、セグメンテーションタスク
・Kaggleコンペティション
・速度が重要な場合
・高度な変換が必要な場合

📦 4. カスタムデータセットの作成

PyTorchで自分のデータを使うには、Datasetクラスを作成する必要があります。

4-1. Datasetクラスの基本構造

PyTorchのDatasetクラスには、必ず実装しなければならない3つのメソッドがあります。

📋 Datasetクラスの3つの必須メソッド

① __init__(self, …):初期化。ファイルパス、ラベル、変換などを設定
② __len__(self):データセットのサイズ(データ数)を返す
③ __getitem__(self, idx):インデックスに対応する1つのデータを返す

基本的なテンプレート

# torch.utils.data.Datasetを継承して独自のクラスを作る import torch from torch.utils.data import Dataset from PIL import Image import os class CustomImageDataset(Dataset): “””カスタム画像データセットのテンプレート””” def __init__(self, image_dir, labels, transform=None): “”” 初期化メソッド Args: image_dir: 画像が保存されているディレクトリのパス labels: 各画像に対応するラベルのリスト transform: 適用するデータ拡張(transformsオブジェクト) “”” self.image_dir = image_dir self.labels = labels self.transform = transform # ディレクトリ内の画像ファイル一覧を取得 self.image_files = os.listdir(image_dir) def __len__(self): “”” データセットのサイズを返す DataLoaderがバッチを作る際に必要 “”” return len(self.image_files) def __getitem__(self, idx): “”” 指定されたインデックスのデータを1つ返す Args: idx: データのインデックス(0からlen-1まで) Returns: image: 画像(テンソル) label: ラベル(整数) “”” # 画像ファイルのパスを作成 img_name = self.image_files[idx] img_path = os.path.join(self.image_dir, img_name) # 画像を読み込み(RGB形式に変換) image = Image.open(img_path).convert(‘RGB’) # 対応するラベルを取得 label = self.labels[idx] # 変換(データ拡張)を適用 if self.transform: image = self.transform(image) return image, label

4-2. 実践:猫 vs 犬データセット

よくあるフォルダ構造に対応したデータセットクラスを作ってみましょう。

【想定するフォルダ構造】 data/ ├── train/ │ ├── cats/ │ │ ├── cat.0.jpg │ │ ├── cat.1.jpg │ │ └── … │ └── dogs/ │ ├── dog.0.jpg │ ├── dog.1.jpg │ └── … └── test/ ├── cats/ └── dogs/

ステップ1:必要なインポート

# 必要なモジュールをインポート import torch from torch.utils.data import Dataset from torchvision import transforms from PIL import Image import os import glob # ファイルパターンマッチング用

ステップ2:クラスの定義

class CatDogDataset(Dataset): “””猫 vs 犬の画像分類データセット””” def __init__(self, data_dir, transform=None): “”” Args: data_dir: data/train や data/test のパス transform: データ拡張 “”” self.data_dir = data_dir self.transform = transform # 画像パスとラベルを格納するリスト self.image_paths = [] self.labels = [] # 猫の画像を追加(ラベル = 0) # glob.glob()でパターンに一致するファイルパスを取得 cat_paths = glob.glob(os.path.join(data_dir, ‘cats’, ‘*.jpg’)) self.image_paths.extend(cat_paths) # リストに追加 self.labels.extend([0] * len(cat_paths)) # 0を猫の数だけ追加 # 犬の画像を追加(ラベル = 1) dog_paths = glob.glob(os.path.join(data_dir, ‘dogs’, ‘*.jpg’)) self.image_paths.extend(dog_paths) self.labels.extend([1] * len(dog_paths))

ステップ3:必須メソッドの実装

def __len__(self): “””データ数を返す””” return len(self.image_paths) def __getitem__(self, idx): “””idx番目のデータを返す””” # パスから画像を読み込み img_path = self.image_paths[idx] image = Image.open(img_path).convert(‘RGB’) # ラベルを取得 label = self.labels[idx] # 変換を適用 if self.transform: image = self.transform(image) return image, label

完成コード(実行用)

※ コードが横に長い場合は横スクロールできます

# 猫 vs 犬データセットの完全な実装 import torch from torch.utils.data import Dataset from torchvision import transforms from PIL import Image import os import glob class CatDogDataset(Dataset): “””猫 vs 犬の画像分類データセット””” def __init__(self, data_dir, transform=None): self.data_dir = data_dir self.transform = transform self.image_paths = [] self.labels = [] # 猫の画像(ラベル0) cat_paths = glob.glob(os.path.join(data_dir, ‘cats’, ‘*.jpg’)) self.image_paths.extend(cat_paths) self.labels.extend([0] * len(cat_paths)) # 犬の画像(ラベル1) dog_paths = glob.glob(os.path.join(data_dir, ‘dogs’, ‘*.jpg’)) self.image_paths.extend(dog_paths) self.labels.extend([1] * len(dog_paths)) print(f”データセット作成完了: 猫 {len(cat_paths)}枚, 犬 {len(dog_paths)}枚”) def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img_path = self.image_paths[idx] image = Image.open(img_path).convert(‘RGB’) label = self.labels[idx] if self.transform: image = self.transform(image) return image, label # 使用例 train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # データセット作成 # train_dataset = CatDogDataset(‘data/train’, transform=train_transform) # データの取得例 # image, label = train_dataset[0] # print(f”画像の形状: {image.shape}”) # torch.Size([3, 224, 224]) # print(f”ラベル: {label}”) # 0(猫)または 1(犬)

⚡ 5. DataLoader – 効率的なバッチ処理

DataLoaderは、Datasetからデータをバッチ(まとめて)で効率的に読み込むためのツールです。

5-1. なぜDataLoaderが必要なのか?

🔄 DataLoaderの4つの役割

① バッチ処理:1枚ずつではなく、32枚、64枚などまとめて処理(GPUを効率的に使える)
② シャッフル:エポックごとにデータの順序をランダム化(過学習防止)
③ 並列読み込み:複数のCPUコアでデータを同時に読み込み(高速化)
④ メモリ効率:全データをメモリに載せず、必要な分だけ読み込む

5-2. DataLoaderの基本的な使い方

from torch.utils.data import DataLoader # DataLoaderの作成 train_loader = DataLoader( train_dataset, # 作成したDataset batch_size=32, # バッチサイズ(1回に読み込むデータ数) shuffle=True, # シャッフルするか(訓練時はTrue) num_workers=4, # 並列読み込みのワーカー数 pin_memory=True # GPU転送を高速化 )

DataLoaderを使ったデータの取得

# forループでバッチごとにデータを取得 for images, labels in train_loader: print(f”画像バッチの形状: {images.shape}”) # torch.Size([32, 3, 224, 224]) # [バッチサイズ, チャンネル, 高さ, 幅] print(f”ラベルバッチの形状: {labels.shape}”) # torch.Size([32]) # 32個のラベル # ここでモデルの訓練処理を行う # outputs = model(images) # loss = criterion(outputs, labels) # … break # 1バッチだけ表示して終了

5-3. DataLoaderのパラメータ詳細

パラメータ 説明 推奨値・注意点
batch_size 1回に読み込むデータ数 16〜128(GPUメモリによる)
shuffle データをシャッフルするか 訓練時True、テスト時False
num_workers 並列読み込みのワーカー数 CPUコア数(4〜8)。Colabでは2〜4
pin_memory メモリをピン留め GPU使用時はTrue(転送高速化)
drop_last 余ったデータを捨てるか 訓練時True(バッチサイズを一定に)
💡 バッチサイズの決め方

小さいバッチサイズ(8〜16):
・GPUメモリが少ない場合
・学習が不安定になりやすい
・1エポックの処理時間が長い

大きいバッチサイズ(64〜128):
・GPUメモリが多い場合
・学習が安定する
・1エポックの処理時間が短い
・ただし、学習率の調整が必要な場合も

目安:まず32で試して、GPUメモリに余裕があれば64、128と増やす

5-4. 訓練・テスト用DataLoaderの作成(完成コード)

# 訓練用とテスト用のDataLoaderを作成する完全なコード from torch.utils.data import DataLoader from torchvision import transforms # 訓練用の変換(データ拡張あり) train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # テスト用の変換(データ拡張なし) test_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # データセット作成(実際にはdata/train, data/testが必要) # train_dataset = CatDogDataset(‘data/train’, transform=train_transform) # test_dataset = CatDogDataset(‘data/test’, transform=test_transform) # DataLoader作成 # train_loader = DataLoader( # train_dataset, # batch_size=32, # shuffle=True, # 訓練時はシャッフル # num_workers=4, # pin_memory=True, # drop_last=True # バッチサイズを一定に # ) # test_loader = DataLoader( # test_dataset, # batch_size=32, # shuffle=False, # テスト時はシャッフルしない # num_workers=4, # pin_memory=True, # drop_last=False # ) # 訓練ループの例 # for epoch in range(num_epochs): # for images, labels in train_loader: # # GPUに転送 # images = images.to(device) # labels = labels.to(device) # # # 訓練処理 # # …

📝 練習問題

問題1:データ拡張の選択(基礎)

以下のタスクでは、どのデータ拡張を使うべきですか?また、使わない方がいい拡張はありますか?理由とともに答えてください。

  1. 手書き数字認識(MNIST)
  2. 衛星画像の建物検出
  3. 顔認識システム
解答:

1. 手書き数字認識(MNIST)

推奨する拡張:
・軽い回転(±15度程度):手書きの傾きをシミュレート
・平行移動:位置のばらつきに対応
・軽いスケール変換(±10%):文字サイズの違いに対応

避けるべき拡張:
・水平反転(×):「6」が「9」のように見える
・垂直反転(×):数字が上下逆になる
・強い色変換(×):グレースケール画像なので不要

2. 衛星画像の建物検出

推奨する拡張:
・水平反転:建物の向きは関係ない
・垂直反転:真上から撮影なので上下の概念がない
・回転(全方向):建物は様々な角度で写る
・明るさ・コントラスト調整:時間帯や天候の変化に対応

理由:衛星画像は真上から撮影されるため、上下左右の概念がありません。

3. 顔認識システム

推奨する拡張:
・水平反転:左右対称なので有効
・軽い回転(±5度):顔の傾きに対応
・明るさ・コントラスト調整:照明条件に対応
・軽いズーム:顔サイズの変化に対応

避けるべき拡張:
・垂直反転(×):顔が上下逆になることはない
・強い回転(×):顔が大きく傾くことは稀
・強い色変換(×):肌の色が不自然に変わる

問題2:torchvision.transformsの実装(中級)

以下の要件を満たすデータ拡張パイプラインを実装してください。

  • 画像を256×256にリサイズ
  • ランダムに224×224を切り出し
  • 50%の確率で水平反転
  • ±10度の範囲でランダム回転
  • 明るさとコントラストをランダムに±20%変更
  • テンソルに変換し、ImageNetの統計で正規化
解答:
from torchvision import transforms from PIL import Image # 訓練用の変換パイプライン train_transform = transforms.Compose([ # 1. 256×256にリサイズ transforms.Resize((256, 256)), # 2. ランダムに224×224を切り出し transforms.RandomCrop(224), # 3. 50%の確率で水平反転 transforms.RandomHorizontalFlip(p=0.5), # 4. ±10度の範囲でランダム回転 transforms.RandomRotation(degrees=10), # 5. 明るさ・コントラストを±20%変更 transforms.ColorJitter(brightness=0.2, contrast=0.2), # 6. テンソルに変換(0〜1に正規化) transforms.ToTensor(), # 7. ImageNetの統計で正規化 transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) # 使用例(サンプル画像で確認) import urllib.request url = “https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg” urllib.request.urlretrieve(url, “sample.jpg”) img = Image.open(‘sample.jpg’) img_transformed = train_transform(img) print(f”変換後の形状: {img_transformed.shape}”) print(f”値の範囲: {img_transformed.min():.2f} 〜 {img_transformed.max():.2f}”)

実行結果:

変換後の形状: torch.Size([3, 224, 224]) 値の範囲: -2.12 〜 2.64

問題3:カスタムデータセットの実装(応用)

以下のディレクトリ構造に対応するカスタムデータセットクラスを実装してください。クラス名は自動的にフォルダ名から取得するようにしてください。

data/ ├── train/ │ ├── cat/ │ │ ├── cat.0.jpg │ │ └── cat.1.jpg │ ├── dog/ │ │ ├── dog.0.jpg │ │ └── dog.1.jpg │ └── bird/ │ ├── bird.0.jpg │ └── bird.1.jpg └── test/ ├── cat/ ├── dog/ └── bird/
解答:
import torch from torch.utils.data import Dataset from PIL import Image import os import glob class AnimalDataset(Dataset): “””汎用的な画像分類データセット””” def __init__(self, root_dir, split=’train’, transform=None): “”” Args: root_dir: dataディレクトリのパス split: ‘train’ または ‘test’ transform: データ拡張 “”” self.root_dir = root_dir self.split = split self.transform = transform # data/train または data/test のパス data_dir = os.path.join(root_dir, split) # クラス名を取得(フォルダ名をソート) self.classes = sorted(os.listdir(data_dir)) # クラス名 → インデックスの辞書 # {‘bird’: 0, ‘cat’: 1, ‘dog’: 2} self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)} # 画像パスとラベルのリスト self.samples = [] for class_name in self.classes: class_dir = os.path.join(data_dir, class_name) if os.path.isdir(class_dir): # jpg, jpeg, pngに対応 for ext in [‘*.jpg’, ‘*.jpeg’, ‘*.png’]: for img_path in glob.glob(os.path.join(class_dir, ext)): label = self.class_to_idx[class_name] self.samples.append((img_path, label)) print(f”データセット作成完了: {len(self.samples)}枚”) print(f”クラス: {self.classes}”) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] image = Image.open(img_path).convert(‘RGB’) if self.transform: image = self.transform(image) return image, label # 使用例 # train_dataset = AnimalDataset(‘data’, split=’train’, transform=train_transform) # test_dataset = AnimalDataset(‘data’, split=’test’, transform=test_transform)

ポイント:

os.listdir()でフォルダ名を自動取得
sorted()でクラス順序を固定(毎回同じ順序になる)
class_to_idx辞書でクラス名→数値ラベルを管理
・複数の拡張子(jpg, jpeg, png)に対応

問題4:DataLoaderの最適化(応用)

以下のDataLoaderは訓練が非常に遅いです。問題点を指摘し、最適化してください。

train_loader = DataLoader( train_dataset, batch_size=8, shuffle=True, num_workers=0, pin_memory=False )

環境:GPU: NVIDIA RTX 3080(10GB)、CPU: 8コア、データセット: 50,000枚

解答:

問題点の分析:

① batch_size=8:小さすぎる
→ RTX 3080(10GB)なら、もっと大きいバッチサイズが使える
→ 小さいバッチは1エポックの処理回数が多くなり、遅くなる

② num_workers=0:シングルスレッドで読み込み
→ CPUが8コアあるのに1コアしか使っていない
→ データ読み込みがボトルネックになる

③ pin_memory=False:GPU転送が遅い
→ ピン留めメモリを使わないと、GPU転送に時間がかかる

最適化後のDataLoader:

train_loader = DataLoader( train_dataset, batch_size=64, # 8 → 64に増加(GPUメモリに余裕あり) shuffle=True, num_workers=8, # 0 → 8(CPUコア数に合わせる) pin_memory=True, # True(GPU転送を高速化) drop_last=True, # バッチサイズを一定に保つ prefetch_factor=2 # 先読みバッファ(num_workers > 0の時に有効) )

期待される改善:

・バッチサイズ増加:約8倍の処理効率
・並列読み込み:データ読み込みがボトルネックではなくなる
・ピン留めメモリ:GPU転送が高速化

総合的に5〜10倍の高速化が期待できます。

📝 STEP 4 のまとめ

✅ このステップで学んだこと

1. データ拡張の基本
・既存の画像を変換して、データを増やす技術
・過学習防止、汎化性能向上に効果的
・タスクに適した拡張を選ぶことが重要

2. torchvision.transforms
・PyTorchの標準データ拡張ツール
・反転、回転、色調整、正規化など
・Compose()で複数の変換を組み合わせる

3. Albumentations
・torchvisionより高速で機能豊富
・物体検出、セグメンテーションに対応
・Kaggleで人気

4. カスタムデータセット
・torch.utils.data.Datasetを継承
・__init__, __len__, __getitem__ の3メソッドを実装

5. DataLoader
・バッチ処理、シャッフル、並列読み込み
・batch_size, num_workers, pin_memoryを適切に設定

💡 重要ポイント

データ拡張とDataLoaderは、深層学習の訓練効率に直接影響する重要な技術です。

次のSTEP 5からは、いよいよ高度なCNNアーキテクチャ(ResNet)を学んでいきます。このステップで学んだデータパイプラインを使って、実際にモデルを訓練します!

📝

学習メモ

コンピュータビジョン(CV) - Step 4

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE