Pythonでデータセットをバッチに分割する方法

Pythonでデータセットをバッチに分割する手法を学び、メモリ効率、処理速度、モデルトレーニングを改善しましょう。
2 分読
How to Split Dataset Into Batches blog image

このガイドでは以下を学びます:

  • バッチ処理の定義
  • データセットをバッチ処理する理由
  • Pythonでデータセットをバッチに分割する方法
  • データセットのmap()関数におけるバッチ処理オプションの活用法

さっそく見ていきましょう!

バッチとは何か?

機械学習やデータ処理の世界において、バッチとは単にデータセットのサブセットに過ぎません。バッチは通常、大量のデータを効率的に処理するために使用されます。データセット全体を一度に処理する代わりに、データはより小さなチャンク(バッチとも呼ばれる)に分割されます。各バッチは独立して処理できるため、メモリ使用量の削減と計算効率の向上に役立ちます。

例えば、CSV形式のサンプルデータがあるとします:

id,name,age,score
1,John,28,85
2,Jane,34,90
3,Bob,25,72
4,Alice,30,88
5,Charlie,29,91
6,David,35,79
7,Eve,22,95
8,Frank,31,82
9,Grace,27,86
10,Hannah,26,80

上記のデータセットの一部は以下の通りです:

6,デイビッド,35,79
7,イブ,22,95
8,フランク,31,82
9,グレース,27,86
10,ハンナ,26,80

このバッチは元のデータセットの5行目から10行目までを切り出します。

バッチ処理によるデータセット処理の利点

データ取得手法を用いて作成したデータセットがあると仮定します。そのプロセスに不慣れな場合は、データセットの作成方法に関するガイドを参照してください。

では、なぜこのデータセットをバッチ処理する必要があるのでしょうか?以下の利点があるためです:

  • メモリ効率:データセット全体をメモリに読み込む代わりに、一度に処理する量を小さく管理しやすいサイズに分割
  • 処理速度の向上:バッチ処理は並列化が可能であり、大規模データセットの処理時間を短縮します。
  • 機械学習モデルのトレーニング改善:重みを段階的に更新することで機械学習モデルのトレーニングを支援し、より安定した高速な収束を実現します。
  • スケーラビリティの向上:メモリに一括で収まらない大規模データセットへの処理拡張を容易にします。

データセットをバッチに分割する方法:トップ5のアプローチ

データセットをバッチに分割する最適なPython手法を探る前に、これらのアプローチを評価する基準を明確にすべきです。考慮すべき重要な要素は以下の通りです:

  • 実装: 簡単な例でアプローチの使用方法を示すスニペット。
  • 適用シナリオ:データセット分割手法が適用可能な実世界の状況。
  • 入力:分割戦略がサポートするデータセットファイルの種類とデータ構造。
  • 長所:その手法が提供する利点。
  • 短所:手法の制限や欠点。

それでは一つずつ分析していきましょう!

アプローチ #1: 配列スライシング

配列スライシングは、データセットを小さく扱いやすいバッチに分割するシンプルな手法です。リストや配列などのシーケンスで表されるデータセットをスライスしてチャンクに分割するという考え方です。

👨‍💻実装例:

def create_batches(data, batch_size):
    return [data[i:i + batch_size] for i in range(0, len(data), batch_size)]

# 使用例
data = list(range(1, 51))  # サンプルデータセット
batches = create_batches(data, batch_size=5)

print(batches) 

# 出力: [[1, 2, 3, 4, 5], ..., [46, 47, 48, 49, 50]]

🎯シナリオ:

  • メモリ制限が最小限のデータ前処理タスク
  • 管理可能なインメモリチャンクを必要とする並列データ処理タスク
  • データパイプラインにおける簡易バッチ処理

🔠入力:

  • Pythonのリスト、配列、タプル
  • Numpy配列
  • CSVデータ(メモリ内に列のリストとして読み込まれたもの)
  • リストまたは配列に変換されたPandas DataFrame

👍長所:

  • シンプルで実装が容易
  • 外部ライブラリが不要
  • バッチサイズを直接制御可能

👎デメリット:

  • 利用可能なメモリに制限される
  • 非常に大規模なデータセットや複雑なデータ構造に対応しない
  • データシャッフルにはカスタムロジックが必要

アプローチ #2: ジェネレータ

Pythonのジェネレータを使用すると、データセットをバッチに分割し、1回に1バッチずつyield(生成)できます。この仕組みに慣れていない方のために説明すると、ジェネレータはイテレータのように振る舞う特殊な関数です。データを直接返す代わりに、yieldキーワードを使用してイテレータオブジェクトを生成します。これにより、for ループやnext()関数を使用してバッチを順次アクセスできるようになります。

👨‍💻 実装例:

def data_generator(data, batch_size):
    for i in range(0, len(data), batch_size):
        yield data[i:i + batch_size]

# 使用例
data = list(range(1, 51))  # サンプルデータセット
for batch in data_generator(data, batch_size=5):
    print(batch)

# 出力: 
# [1, 2, 3, 4, 5]
# ...
# [46, 47, 48, 49, 50]

🎯シナリオ:

  • データパイプラインにおけるバッチ処理
  • 大規模データの前処理および拡張タスク
  • データパイプラインにおける単純から複雑なバッチ処理

🔠入力:

  • リスト、配列、タプル
  • NumPy配列
  • ディスクから各バッチを読み込み可能なファイルベースデータセット

👍長所:

  • メモリに完全読み込みせずに大規模データセットを処理可能
  • 最小限の設定で実装が容易
  • 制御されたオンデマンドのデータ読み込みが可能

👎デメリット:

  • 追加のシャッフル処理がない限り、データの順序に制限される
  • 動的または変動するバッチサイズには非効率的
  • 特にマルチスレッド処理では並列処理に最適でない可能性あり

アプローチ #3: PyTorch DataLoader

PyTorchのDataLoaderクラスは、データセットを効率的に扱いやすいバッチに分割するのに役立ちます。データセット処理に特化したデータ構造として、シャッフルや並列データ読み込みなどの便利な機能も提供します。

DataLoaderは、データセットを表す別のPyTorchデータ構造であるTensorDatasetと連携することに注意してください。具体的には、TensorDatasetは次の2つの引数を受け取ります:

  1. inps: 入力データ(通常はテンソル形式)。
  2. tgts: 入力データに対応するラベルまたはターゲット値。これも通常はテンソルとして指定されます。

TensorDatasetは データターゲットをペア化し、DataLoaderによってバッチ処理とトレーニングのために読み込まれるようになります。

👨‍💻実装例:

from torch.utils.data import DataLoader, TensorDataset
import torch

# シンプルなデータセットを定義するデータ
inputs = torch.arange(1, 51).float().reshape(-1, 1)  # 1Dテンソルデータセット(入力)
targets = inputs ** 2  # 入力値の二乗(回帰タスクをシミュレート)

# TensorDatasetとDataLoaderの作成
dataset = TensorDataset(inputs, targets)
dataloader = DataLoader(dataset, batch_size=5, shuffle=True)

# DataLoader を反復処理
for batch in dataloader:
    print(batch)

# サンプル出力:
# [tensor([[46.],
#         [42.],
#         [25.],
#         [10.],
#         [34.]]), tensor([[2116.],
#         [1764.],
#         [ 625.],
#         [ 100.],
#         [1156.]])]
# ...
# [tensor([[21.],
#         [ 9.],
#         [ 2.],
#         [38.],
#         [44.]]), tensor([[ 441.],
#         [  81.],
#         [   4.],
#         [1444.],
#         [1936.]])] 

🎯シナリオ:

  • PyTorchでの機械学習モデルのトレーニングとテスト
  • バイアスのないトレーニングバッチのためのデータシャッフル
  • 深層学習タスク向け大規模データ処理

🔠入力:

  • PyTorchTensorDataセットに読み込まれたカスタムデータセット
  • Tensor形式の表形式データおよび数値配列

👍メリット:

  • バッチ処理とシャッフル機能による大規模データセットへの最適化
  • 並列データ読み込みをサポートし、バッチ取得を高速化
  • PyTorchモデルおよびトレーニングループとのシームレスな連携
  • GPU処理に対応

👎デメリット:

  • PyTorchが必要
  • データをテンソルに変換する必要がある
  • 機械学習以外のバッチ処理タスクには不向き

アプローチ #4: TensorFlowbatch() メソッド

TensorFlowDatasetbatch() メソッドは、データセットをバッチに分割します。このメソッドはデータセットを小さなチャンクに分割し、並列化、処理順序の制御、命名などの機能を備えています。

機械学習ライブラリとして、TensorFlowはシャッフル、リピート、プリフェッチなどの追加機能も提供します。

👨‍💻実装例:

import tensorflow as tf

# サンプルデータセットの作成
inputs = tf.range(1, 51, dtype=tf.float32)  # 1次元テンソルデータセット (入力)
targets = inputs ** 2  # 入力値の二乗 (回帰タスクをシミュレート)

# 入力とターゲットを tf.data.Dataset に変換
inputs_dataset = tf.data.Dataset.from_tensor_slices(inputs)
targets_dataset = tf.data.Dataset.from_tensor_slices(targets)

# 入力とターゲットを結合してデータセットを作成
dataset = tf.data.Dataset.zip((inputs_dataset, targets_dataset))

# バッチ化されたデータセットを生成
batched_dataset = dataset.batch(batch_size=5)

for batch in batched_dataset:
    print(batch)

# 出力:
# (<tf.Tensor: shape=(5,), dtype=float32, numpy=array([1., 2., 3., 4., 5.], dtype=float32)>, <tf.Tensor: shape=(5,), dtype=float32, numpy=array([ 1.,  4.,  9., 16., 25.], dtype=float32)>)
# ...
# (<tf.Tensor: shape=(5,), dtype=float32, numpy=array([46., 47., 48., 49., 50.], dtype=float32)>, <tf.Tensor: shape=(5,), dtype=float32, numpy=array([2116., 2209., 2304., 2401., 2500.], dtype=float32)>)

🎯シナリオ:

  • PyTorchでの機械学習モデルのトレーニングとテスト
  • バイアスのないトレーニングバッチのためのデータシャッフル
  • ディープラーニングタスク向け大規模データ処理

🔠入力:

  • TensorFlowtf.data.Dataset オブジェクト
  • NumPy配列(データセットに変換可能)
  • TFRecordファイル(TensorFlowで大規模データセットの保存に一般的に使用される特殊なバイナリファイル形式)

👍長所:

  • 効率的なメモリ使用に最適化
  • モデルトレーニングおよび評価のための TensorFlow エコシステムとのシームレスな統合
  • シャッフル、プリフェッチ、その他の有用な機能をサポート
  • 画像、テキスト、構造化データなど多様なデータ形式をサポート

👎デメリット:

  • TensorFlowが必要
  • より複雑なデータセットの場合、データを適切にフォーマットおよび前処理するために追加の設定が必要になる可能性がある
  • 小規模なデータセットのバッチ処理にオーバーヘッドが生じる可能性がある

アプローチ #5: HDF5 形式

HDF5は、特に階層的なデータ構造を扱う際に、大規模データセットを管理するための広く採用されているデータ形式です。大規模なデータセットをチャンクに分割し、効率的に保存することをサポートします。

h5py Pythonライブラリは、HDF5ファイルを操作しNumPyデータ構造として読み込むためのツールを提供します。これにより、特定のデータスライスやセグメントをオンデマンドでアクセスすることで、データセットのバッチ処理が可能になります。

👨‍💻実装例:

import h5py
import numpy as np

# HDF5ファイルからデータをバッチ処理で読み込み
def load_data_in_batches(batch_size=10):
    # HDF5ファイルを開く
    with h5py.File("dataset.h5", "r") as f:
        inputs = f["input"]
        targets = f["target"]

        # ディスクからイテレータでデータをバッチ処理
        for i in range(0, len(data), batch_size):
            yield inputs[i:i+batch_size], targets[i:i+batch_size]

# バッチを反復処理
for batch_data, batch_target in load_data_in_batches():
    print("入力バッチ:", batch_input)
    print("ターゲットバッチ:", batch_target)

# 出力:
# 入力バッチ: [[ 1]
#  [ 2]
#  [ 3]
#  [ 4]
#  [ 5]
#  [ 6]
#  [ 7]
#  [ 8]
#  [ 9]
#  [10]]
# Target Batch: [[  1]
#  [  4]
#  [  9]
#  [ 16]
#  [ 25]
#  [ 36]
#  [ 49]
#  [ 64]
#  [ 81]
#  [100]]
#  ...
# 入力バッチ: [[41]
#  [42]
#  [43]
#  [44]
#  [45]
#  [46]
#  [47]
#  [48]
#  [49]
#  [50]]
# ターゲットバッチ: [[1681]
#  [1764]
#  [1849]
#  [1936]
#  [2026]
#  [2116]
#  [2209]
#  [2304]
#  [2401]
#  [2500]]

🎯シナリオ:

  • メモリに完全に読み込めない非常に大規模なデータセットに最適
  • 多次元データの処理に有用
  • 機械学習タスク向けに、効率的で圧縮された形式でディスクからデータを保存・取得するのに適している

🔠入力:

  • HDF5ファイル

👍長所:

  • HDF5はデータ圧縮とチャンキングをサポートし、大規模データセットのストレージ要件を削減
  • 大規模データセットの一部に対して、全てをメモリに読み込まずに効率的なランダムアクセスを可能にする
  • 単一ファイルに複数のデータセットを保存可能で、複雑なデータセットに最適
  • NumPy、TensorFlow、PyTorchなど多くの科学ライブラリでサポートされている

👎デメリット:

  • HDF5フォーマットに関する追加設定と知識が必要
  • HDF5ファイルを扱う完全なAPIにはh5pyライブラリに依存
  • 全てのデータセットがHDF5形式で利用可能ではない

その他の解決策

上記の方法はデータセットをバッチ分割する最良の手法の一つですが、他にも有効な解決策が存在します。

別の解決策として、Hugging Faceのデータセットライブラリの利用が挙げられます。これにより、データセット全体をバッチ処理しながら変換を適用するために必要な機能がすべて備わります。batched=Trueを設定することで、以下に示す例のように、データセットを手動でスライスすることなくバッチレベルの変換を定義できます:

from datasets import load_dataset

# サンプルデータセットを読み込み
dataset = load_dataset("imdb", split="train")

# バッチ処理関数を定義
def process_batch(batch):
    # 単純なトークン化処理
    return {"tokens": [text.split() for text in batch["text"]]}

# バッチ処理を適用
batched_dataset = dataset.map(process_batch, batched=True, batch_size=32)

データセット のmap() 関数でbatched=Trueオプションを使用すると、トークン化などの変換処理をバッチ単位で適用するのに最適です。

map(batched=True)の使用はメモリ使用量を最小化し変換ワークフローを高速化するため、バッチ処理において非常に効率的です。この方法は特にNLPや機械学習タスクにおけるテキスト・表形式データの処理に有用です。

まとめ

本ガイドでは、Pythonでデータをスライスする最適な手法、ライブラリ、ソリューションについて解説しました。目的は、大規模なデータセットを扱いやすい部分に分割し、データ処理を簡素化・高速化することです。

どの手法を選択する場合でも、上記の解決策はすべて、対象となるデータを含むデータセットへのアクセスを前提としています。科学研究用に無料で利用可能なデータセットもありますが、常にそうとは限りません。

金融から映画データまで幅広いカテゴリのデータセットが必要な場合は、Bright Dataのデータセットマーケットプレイスをご覧ください。人気サイトからの数百のデータセットにアクセスでき、以下のように分類されています:

これらの既製オプションがニーズに合わない場合は、カスタムデータ収集サービスをご検討ください。

さらに、Bright DataではWeb Scraper APIや スクレイピングブラウザなど、強力なスクレイピングツールを幅広く提供しています。

これらのデータセットを探索するには、Bright Dataアカウントを無料で作成してください!