概要
データセット(Dataset)とは、機械学習・ディープラーニングモデルの学習・検証・テストに使用するデータの集合です。機械学習において「データはモデルの原料」であり、良質なデータセットなくして高精度なモデルは実現できません。「Garbage in, garbage out(粗悪な入力からは粗悪な出力しか生まれない)」という原則がAI開発では特に重要です。
データセットは通常、以下の三つに分割して使用します。
| 分割 | 割合(目安) | 用途 |
|---|---|---|
| 訓練セット(Train) | 60〜80% | モデルのパラメータ学習に使用 |
| 検証セット(Validation) | 10〜20% | ハイパーパラメータ調整・過学習確認 |
| テストセット(Test) | 10〜20% | 最終的な性能評価(学習には使わない) |
組み込み・エッジAI向けのデータセット構築では、デプロイ環境(工場内・屋外・車内など)に即したデータ収集が特に重要で、ドメインシフト(学習環境と実環境の乖離)が性能低下の主要因となります。
歴史・背景
機械学習用データセットは学術研究の発展とともに整備されてきました。
代表的なデータセットと影響:
| 年 | データセット | 規模 | 影響 |
|---|---|---|---|
| 1998年 | MNIST(手書き数字) | 7万枚 | 画像認識の入門標準 |
| 2009年 | ImageNet | 1400万枚、1000クラス | ディープラーニング革命の基盤 |
| 2014年 | COCO | 33万枚、80クラス | 物体検出・セグメンテーションの標準 |
| 2017年 | Open Images | 900万枚、600クラス | 大規模ラベル付き画像 |
| 2017年 | AudioSet | 200万クリップ | 音声分類の基盤 |
| 2019年 | MVTec AD | 5354枚(産業異常検知) | 工業外観検査の標準 |
| 2020年 | Common Voice | 多言語音声 | オープン音声認識 |
| 2021年 | LAION-400M | 4億画像-テキストペア | 生成AIの基盤 |
技術仕様
データセットの種類(タスク別)
データセットの分類
画像系
├── 画像分類: ImageNet, CIFAR-10/100, Flowers102
├── 物体検出: COCO, Pascal VOC, Open Images
├── セグメンテーション: ADE20K, Cityscapes
└── 異常検知: MVTec AD, VisA, BTAD
音声系
├── 音声認識: LibriSpeech, Common Voice, FLEURS
├── ウェイクワード: Google Speech Commands
└── 環境音: ESC-50, AudioSet
時系列・センサー系
├── 振動/故障: CWRU Bearing, FEMTO Bearing
├── 活動認識: UCI HAR, PAMAP2
└── ECG/医療: MIT-BIH Arrhythmia
自然言語処理
├── 分類: SST-2, AG News
├── 質疑応答: SQuAD, Natural Questions
└── 翻訳: WMT, FLORES
データセットの品質指標
import numpy as np
from collections import Counter
def analyze_dataset_quality(labels, data):
"""データセットの品質を分析"""
# 1. クラス分布の確認(クラス不均衡)
class_counts = Counter(labels)
total = len(labels)
print("=== クラス分布 ===")
for cls, count in sorted(class_counts.items()):
print(f" クラス{cls}: {count}枚 ({count/total*100:.1f}%)")
# 不均衡比の計算
max_count = max(class_counts.values())
min_count = min(class_counts.values())
imbalance_ratio = max_count / min_count
print(f"不均衡比: {imbalance_ratio:.1f}x")
if imbalance_ratio > 10:
print("警告: クラス不均衡が大きい。"
"オーバーサンプリングや重み付けを検討してください")
# 2. データ統計
if data is not None:
print("\n=== データ統計 ===")
print(f" 平均: {np.mean(data):.3f}")
print(f" 標準偏差: {np.std(data):.3f}")
print(f" 最小値: {np.min(data):.3f}")
print(f" 最大値: {np.max(data):.3f}")
return class_counts, imbalance_ratio
アノテーション形式
物体検出データセットのアノテーションには複数の形式があります。
# COCO形式(JSON)
{
"images": [
{"id": 1, "file_name": "img001.jpg",
"width": 640, "height": 480}
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1,
"bbox": [100, 50, 200, 150], # [x, y, width, height]
"area": 30000,
"iscrowd": 0
}
],
"categories": [
{"id": 1, "name": "person"},
{"id": 2, "name": "car"}
]
}
# YOLO形式(テキスト、各行が1つのオブジェクト)
# <class_id> <x_center> <y_center> <width> <height>
# 全て正規化座標(0〜1.0)
# 0 0.512 0.324 0.312 0.425
# Pascal VOC形式(XML)
"""
<annotation>
<filename>img001.jpg</filename>
<size><width>640</width><height>480</height></size>
<object>
<name>person</name>
<bndbox>
<xmin>100</xmin><ymin>50</ymin>
<xmax>300</xmax><ymax>200</ymax>
</bndbox>
</object>
</annotation>
"""
動作原理
データ拡張(Data Augmentation)
限られたデータから学習を効果的にするため、既存データを変換して擬似的にデータ量を増やします。
import albumentations as A
import cv2
import numpy as np
# 画像分類向けデータ拡張パイプライン
classification_transform = A.Compose([
# 幾何学的変換
A.RandomRotate90(p=0.5),
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.2),
A.ShiftScaleRotate(
shift_limit=0.1, scale_limit=0.2,
rotate_limit=15, p=0.5
),
# 色調変換
A.RandomBrightnessContrast(
brightness_limit=0.2, contrast_limit=0.2, p=0.4
),
A.HueSaturationValue(
hue_shift_limit=10, sat_shift_limit=30, p=0.3
),
A.ColorJitter(p=0.3),
# ノイズ・ぼかし
A.GaussNoise(var_limit=(10, 50), p=0.2),
A.MotionBlur(blur_limit=5, p=0.2),
# 正規化
A.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
# 物体検出向けデータ拡張(バウンディングボックスも変換)
detection_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.3),
A.GaussNoise(p=0.2),
A.RandomCrop(width=512, height=512, p=0.5),
], bbox_params=A.BboxParams(
format='coco', # [x, y, w, h]
label_fields=['category_ids'],
min_area=100, # 小さすぎるBOXを除外
min_visibility=0.5 # 見切れが半分以上のBOXを除外
))
PyTorchデータローダーの実装
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os
import json
class CustomImageDataset(Dataset):
"""カスタムデータセットクラス"""
def __init__(self, root_dir, split='train', transform=None):
self.root_dir = root_dir
self.transform = transform
self.samples = []
self.class_to_idx = {}
# クラスディレクトリからサンプルを収集
classes = sorted(os.listdir(
os.path.join(root_dir, split)
))
self.class_to_idx = {cls: i for i, cls in enumerate(classes)}
for cls in classes:
cls_dir = os.path.join(root_dir, split, cls)
for fname in os.listdir(cls_dir):
if fname.endswith(('.jpg', '.png', '.jpeg')):
self.samples.append({
'path': os.path.join(cls_dir, fname),
'label': self.class_to_idx[cls]
})
print(f"{split}セット: {len(self.samples)}枚, "
f"{len(classes)}クラス")
def __len__(self):
return len(self.samples)
def __getitem__(self, idx):
sample = self.samples[idx]
image = Image.open(sample['path']).convert('RGB')
if self.transform:
image = self.transform(image)
return image, sample['label']
# データセット作成
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])
train_dataset = CustomImageDataset('./data', 'train', train_transform)
train_loader = DataLoader(
train_dataset,
batch_size=32,
shuffle=True,
num_workers=4,
pin_memory=True # GPU転送を高速化
)
エッジAI向けデータ収集
エッジデバイスでのデータ収集は、Edge Impulseなどのプラットフォームを使って行えます。
# Edge Impulse Python SDKでのデータアップロード
import requests
import json
def upload_sample_to_edge_impulse(
api_key, label, data, sampling_freq=100
):
"""センサーデータをEdge Impulseにアップロード"""
payload = {
"protected": {
"ver": "v1",
"alg": "none",
"iat": int(time.time())
},
"signature": "0000",
"payload": {
"device_name": "my_sensor",
"device_type": "IMU",
"interval_ms": 1000 / sampling_freq,
"sensors": [
{"name": "accX", "units": "m/s2"},
{"name": "accY", "units": "m/s2"},
{"name": "accZ", "units": "m/s2"}
],
"values": data.tolist()
}
}
response = requests.post(
'https://ingestion.edgeimpulse.com/api/training/data',
headers={
'x-api-key': api_key,
'x-label': label,
'Content-Type': 'application/json'
},
data=json.dumps(payload)
)
return response.status_code == 200
用途・ユースケース
エッジAI向け主要データセット一覧
画像・ビジョン:
| データセット | クラス数 | サイズ | 用途 |
|---|---|---|---|
| CIFAR-10 | 10 | 60K枚 | 軽量分類ベンチマーク |
| Google Speech Commands | 35 | 105K | ウェイクワード検出 |
| COCO(検出) | 80 | 330K枚 | 物体検出標準 |
| MVTec AD | 15 | 5354枚 | 工業異常検知 |
| UC Merced(航空写真) | 21 | 2100枚 | ドローン・農業 |
センサー・時系列:
| データセット | センサー | サンプル数 | 用途 |
|---|---|---|---|
| UCI HAR | 加速度・ジャイロ | 10K+ | 活動認識 |
| CWRU Bearing | 振動センサー | 多数 | 軸受け故障診断 |
| MIT-BIH | ECG | 47名分 | 不整脈検知 |
組み込み向けデータセット構築のポイント
工業向け組み込みAIでは、標準データセットではなく自前でデータ収集することがほとんどです。
データ収集のベストプラクティス:
1. 実運用環境と同じ条件でデータ収集
(照明・角度・距離・季節変動を考慮)
2. 多様なバリエーションを含める
(複数の作業者・複数ロット・複数時間帯)
3. エッジケース(境界事例)を意識的に収集
4. 検収条件を事前に設定(精度・再現率の目標)
5. アノテーションガイドラインを文書化
実装・開発のポイント
クラス不均衡への対処
import torch
from torch.utils.data import WeightedRandomSampler
def create_balanced_sampler(dataset):
"""クラス不均衡をサンプリングで解消"""
class_counts = Counter([sample[1] for sample in dataset])
weights = {cls: 1.0 / count
for cls, count in class_counts.items()}
sample_weights = [weights[sample[1]] for sample in dataset]
sampler = WeightedRandomSampler(
weights=sample_weights,
num_samples=len(sample_weights),
replacement=True
)
return sampler
# 損失関数の重み付けでも対処可能
class_weights = torch.FloatTensor(
[1.0 / class_counts[i] for i in range(num_classes)]
)
criterion = nn.CrossEntropyLoss(weight=class_weights)
データセットの検証
def validate_dataset(dataset_dir):
"""データセットの整合性チェック"""
issues = []
for split in ['train', 'val', 'test']:
split_dir = os.path.join(dataset_dir, split)
if not os.path.exists(split_dir):
issues.append(f"分割ディレクトリ不存在: {split}")
continue
# 各クラスのサンプル数確認
class_counts = {}
for cls in os.listdir(split_dir):
cls_dir = os.path.join(split_dir, cls)
if os.path.isdir(cls_dir):
count = len([f for f in os.listdir(cls_dir)
if f.endswith(('.jpg', '.png'))])
class_counts[cls] = count
if count < 10:
issues.append(
f"{split}/{cls}: サンプル数が少ない ({count}枚)"
)
print(f"{split}: {sum(class_counts.values())}枚, "
f"{len(class_counts)}クラス")
if issues:
print("\n問題点:")
for issue in issues:
print(f" - {issue}")
else:
print("\nデータセット検証OK")
return len(issues) == 0
他技術との比較
| データ源 | コスト | 量 | 品質 | ドメイン適合性 |
|---|---|---|---|---|
| 公開データセット | 無料 | 多い | 高い | 汎用(要ドメイン適応) |
| 合成データ | 中 | 制限なし | 中(リアリズムの問題) | 高い |
| 実機収集 | 高い | 限られる | 最高 | 最高 |
| データ購入 | 非常に高い | 中〜多い | 高い | 中〜高い |
データセットは学習済みモデルと並んでエッジAI開発の根幹をなす資産です。適切なデータセット設計と収集なしには、物体検出・異常検知のいずれも実用水準の精度に達しません。TinyML向けには特に収集コストを意識した効率的なデータ戦略が求められます。