📋 このステップで学ぶこと
- データ拡張の重要性と目的
- 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:データ拡張の選択(基礎)
以下のタスクでは、どのデータ拡張を使うべきですか?また、使わない方がいい拡張はありますか?理由とともに答えてください。
- 手書き数字認識(MNIST)
- 衛星画像の建物検出
- 顔認識システム
解答:
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)を学んでいきます。このステップで学んだデータパイプラインを使って、実際にモデルを訓練します!