소개
데이터 볼륨이 계속 증가함에 따라 머신 러닝 모델 교육에서 일반적인 접근 방식 중 하나는 배치 훈련입니다. 이 방법은 데이터셋을 더 작은 부분집합 또는 "배치"로 나누어 순차적으로 모델에 제공하는 방식입니다.
이 게시물에서는 데이터셋을 배치로 분할하는 세 가지 다른 기술을 탐구할 것입니다:
- 대형 텐서 생성
- HDF5로 부분 데이터 로드
- Python 제너레이터 사용
예를 들어, 모델이 소리 기반 탐지기라고 가정하겠지만, 논의된 방법은 광범위하게 적용 가능합니다. 이 예시가 구체적이긴 하지만, 핵심 단계인 데이터 분할, 전처리 및 반복은 보편적으로 관련이 있습니다. 이러한 기술은 이미지 파일, SQL 쿼리의 표 또는 HTTP 응답과 같은 다양한 데이터 소스와 함께 사용할 수 있습니다. 여기서의 초점은 프로세스 자체입니다.
각 방법을 평가하기 위해 다음 요소를 고려할 것입니다:
- 코드 품질
- 메모리 사용량
- 시간 효율성
시작하기 전에, 가격이 점점 비싸지고 기능이 줄어드는 Postman의 적합한 대체 수단이 필요한 API 테스팅을 하고 있다면, APIDog이 당신의 이상적인 선택입니다!

Apidog은 Postman과 유사한 API 관리 및 검증을 위해 설계된 협업 플랫폼이지만, 날짜 처리를 더 쉽게 만드는 추가 기능이 있습니다. 다음은 APIDog이 어떻게 도움을 줄 수 있는지입니다:
데이터셋의 맥락에서 배치란 무엇인가요?
배치는 일반적으로 데이터의 부분집합을 나타내는 입력-출력 쌍 (X[i], y[i])입니다. 소리 기반 탐지기의 경우, 모델은 처리된 오디오 시퀀스를 입력으로 받아 특정 이벤트가 발생할 확률을 출력합니다. 이 경우, 배치는 다음으로 구성됩니다:
- X[t] - 시간 창 내에서 샘플링된 처리된 오디오 트랙을 나타내는 행렬
- y[t] - 이벤트 발생을 나타내는 이진 라벨.
여기서 t는 시간 윈도우를 나타냅니다(그림 1).

데이터셋 분할을 위한 다양한 접근 방식 비교
접근 방식 #1 - 대형 텐서 사용
모델은 2D 텐서 형태로 입력을 받습니다. 배치 처리를 수용하기 위해 텐서의 랭크를 증가시킬 수 있으며, 세 번째 차원은 배치 크기를 나타냅니다. 이 과정의 단계는 다음과 같습니다:
- 입력 데이터 로드 (X).
- 해당 라벨 로드 (y).
- X와 y를 더 작은 배치로 나누기.
- 각 배치에 대해 특징 추출 (예: 스펙트로그램).
- 처리된 배치 X[t]와 y[t] 결합.
하지만 이 접근 방식이 이상적이지 않을 수 있는 이유는 무엇일까요? 더 잘 이해하기 위해 예제 구현을 살펴보겠습니다.

이 접근 방식은 "모두 한 번에 로드하고 결과를 나중에 처리한다"로 요약될 수 있습니다.
"대형 텐서" 접근 방식의 장단점
X를 독립적인 데이터셋으로 취급하는 것이 유익해 보일 수 있지만, 이 방법에는 몇 가지 단점이 있습니다:
1.메모리 한계: 전체 데이터셋을 RAM에 로드하면 문제가 발생할 수 있으며, 특히 사용 가능한 메모리가 모든 데이터를 수용하기에 충분하지 않은 경우입니다.
2.배치 차원 경직성: X의 첫 번째 차원은 배치 크기를 나타내는 데 사용되지만, 이는 단순한 관례일 뿐입니다. 누군가가 이 순서를 변경하기로 결정하면 (예: 마지막 차원을 배치로 사용), 코드를 수정해야 합니다.
3.배치 추적: X.shape[0]가 정확한 배치 수를 제공하지만, 여전히 현재 배치를 추적하기 위한 보조 변수가 필요하므로 코드가 복잡해집니다.
4.중복 함수: 이 디자인은 X와 y를 슬라이스하고 결합하기 위한 get_batch 함수를 요구하며, 이로 인해 코드는 불필요하게 복잡하게 됩니다.
접근 방식 #2 - HDF5를 사용한 배치 로드
모든 데이터를 RAM에 로드하는 문제를 해결하는 한 가지 방법은 필요에 따라 데이터의 일부만 로드하는 것입니다. 데이터가 파일에 저장되어 있는 경우, 전체 데이터셋 대신 작은 섹션을 로드하고 작업하는 것이 합리적입니다.
CSV 파일의 경우, Pandas의 read_csv
함수에서 skiprows
및 nrows
인수를 사용하면 파일의 특정 부분을 로드할 수 있습니다.
하지만 이 방법이 모든 시나리오를 제대로 처리할 수 있을까요? 예를 들어, 매우 크고 복잡한 데이터를 다루고자 할 때, 즉 오디오 파일의 경우, Pandas의 read_csv 함수를 사용하여 skiprows
나 nrow
를 처리하는 것이 적절하지 않을 수 있습니다. 그래서 여기 또 다른 방법인 계층적 데이터 포맷(HDF5)이 있습니다.
이 형식은 여러 배열의 저장을 지원하며, NumPy 배열과 유사하게 액세스하고 조작할 수 있는 편리한 방법을 제공합니다.
예를 들어, HDF5 파일에 저장된 '오디오' 및 '라벨'이라는 데이터셋과 함께 작업할 수 있습니다. Python 라이브러리인 h5py는 이 형식을 관리하는 데 유용한 도구입니다.

이제 우리의 데이터가 더 관리하기 쉬워졌고, 전체 품질도 향상되었습니다:
- 이전의 get_batch 함수는 데이터를 효율적으로 계산하고 검색하는 더 실용적인 버전으로 대체되었습니다.
- 이제 X 텐서를 인위적으로 수정할 필요가 없습니다.
- get_batch(X, y, t)에서 get_batch(filename, t)로 변경함으로써, 데이터 접근을 추상화하고 X와 y를 네임스페이스에 유지할 필요성을 없앴습니다.
- 이제 데이터셋이 단일 파일로 통합되어, 데이터와 레이블을 별도의 파일에서 가져올 필요가 없습니다.
- 샘플링 비율(fs)은 HDF5 속성을 통해 데이터셋 파일의 일부로 포함되어, 별도의 인수로 전달할 필요가 없습니다.
이러한 개선에도 불구하고, 여전히 두 가지 도전 과제가 남아 있습니다:
- 새로운 get_batch 함수는 자신의 상태를 추적하지 않기 때문에 여전히 루프를 사용하여 t를 조절해야 합니다. 함수가 루프의 크기를 알 방법이 없으므로, 데이터 크기를 미리 확인해야 하며, 이는 두 번째 함수인 get_number_of_batches의 생성을 필요로 합니다.
- 이 설정이 더 좋긴 하지만, 완전히 상태를 보존하는 get_batch 함수의 우아함이 부족하여, 프로세스를 더욱 단순화할 수 있습니다.
접근 방식 #3 – 제너레이터 사용
제너레이터란 무엇인가요?
제너레이터는 반복자 객체를 반환하는 함수입니다. 모든 결과를 미리 계산하는 대신, 이러한 반복자는 다음 요청을 기다리며 한 번에 하나씩 데이터를 제공합니다. 이렇게 하면 대규모 데이터셋을 효율적으로 처리하는 데 이상적인 선택이 됩니다.
반복되는 패턴을 식별해 봅시다:
모든 데이터를 한 번에 로드하는 대신, 데이터의 일부만 순차적으로 접근하고 처리하며 전달하면 됩니다. Python은 제너레이터 형태로 이를 해결할 수 있는 솔루션을 제공합니다.
제너레이터는 세 가지 방법으로 구현할 수 있습니다:
리스트 내포와 유사하지만 대괄호 대신 괄호를 사용하는 제너레이터 표현식 사용 (예: (i for i in iterable)).
return 대신 yield를 사용하는 제너레이터 함수 생성.
사용자 정의 iter_(또는 getitem_) 및 _next_ 메서드를 가진 클래스를 정의.
이 시나리오에서 yield 키워드는 우리의 요구를 위해 자연스럽게 적합하여, 데이터를 관리 가능한 조각으로 처리하고 반환할 수 있게 해 줍니다.

이제 루프가 함수 내에 포함되어 있습니다. yield 문을 사용하여 (X[t], y[t]) 쌍은 get_batches가 t - 1번 호출되었을 때만 반환됩니다. 이는 모델 훈련 코드가 루프의 상태를 관리할 필요성을 제거합니다. 함수는 호출 간 상태를 유지하여 사용자가 수동 배치 인덱스 없이도 배치 간 반복할 수 있게 합니다.
제너레이터 반복자는 데이터가 처리되면서 점차적으로 비어가는 컨테이너에 비유할 수 있습니다. 각 반복에서 배치가 검색되므로, 프로세스는 모든 데이터가 소비될 때까지 계속되며, 명시적인 인덱싱이나 정지 조건이 필요하지 않습니다.
성능: 시간과 메모리
코드 품질에 초점을 맞추어 시작했으며, 이는 솔루션이 개발되는 방식과 밀접하게 연관되어 있습니다. 그러나 대규모 데이터셋을 처리할 때 자원 제한을 고려하는 것도 똑같이 중요합니다.
그림 2는 우리가 논의한 세 가지 방법을 사용하여 배치를 전달하는 데 필요한 시간을 보여 줍니다. 관찰된 바와 같이, 데이터 처리 및 전송에 필요한 시간은 모든 방법에서 거의 동일하게 유지됩니다. 모든 데이터를 한 번에 로드하고 이를 배치로 나누는 것이든, 시작부터 점진적으로 처리하는 것이든, 결과를 얻는 데 필요한 전체 시간은 거의 동일합니다. 이는 SSDs를 사용하기 때문일 수 있으며, 이는 더 빠른 데이터 접근을 제공합니다. 그럼에도 불구하고 선택한 접근 방식은 전체 시간 성능에 미미한 영향을 미치는 것으로 보입니다.

그림 2. 시간 성능 비교: 빨간 실선은 데이터를 메모리에 로드하고 계산을 수행하는 데 소요되는 시간을 나타냅니다. 빨간 점선은 데이터가 이미 미리 계산되어 있다고 가정했을 때, 슬라이스를 처리하는 루프에만 소요된 시간을 나타냅니다.
초록 점선은 HDF5 파일에서 배치를 로드하는 데 소요된 시간을 나타내며, 파란색 대쉬-점선은 제너레이터 사용 시 성능을 나타냅니다. 빨간 선들을 비교해보면, RAM에 데이터를 한 번 로드한 후 접근하는 데 드는 비용이 미미하다는 사실이 명백합니다. 데이터가 로컬에 있을 때, 다양한 방법 간의 차이는 상대적으로 작습니다.
그림 3. 메모리 사용량 비교: 첫 번째 접근 방식은 가장 높은 메모리 소비를 보여 주며, 1시간 길이의 오디오 샘플을 처리할 때 메모리 오류가 발생합니다. 반면 청크 기반 로드 방법은 배치 크기에 따라 메모리 할당을 제어하여 RAM 사용량이 안전한 한계를 유지하도록 합니다.

그림 3. 메모리 소비 비교: 이 그림은 Python 스크립트가 사용한 사용 가능한 RAM의 비율을 보여 주며, 스크립트를 다음 명령으로 실행하여 측정됩니다:
python idea.py & top -b -n 10 > capture.log;cat capture.log | egrep python > analysis.log,
그 후 분석됩니다.
관찰 및 통찰:
두 번째 및 세 번째 방법 간 비교는 메모리 사용량에서 큰 차이가 없음을 보여주며, 이는 제너레이터 반복자를 구현하는 선택이 메모리 발자국에 영향을 미치지 않음을 나타냅니다. 이 발견은 중요한 점을 강조합니다: 일반적으로 제너레이터는 시간과 메모리를 관리하는 효율성으로 추천되지만, 본질적으로 자원 소비를 줄이지는 않습니다.
핵심 요소는 데이터 접근 효율성과 데이터를 관리 가능한 부분으로 처리하는 능력입니다.
HDF5 파일을 활용하는 것은 빠른 데이터 접근을 허용하고, 모든 데이터를 한 번에 로드하는 것을 피할 수 있는 유연성을 제공하므로 유리합니다. 동시에 제너레이터를 포함하면 코드 가독성과 품질이 높아집니다.
부분 데이터 로딩용 HDF5의 사용과 제너레이터 반복자를 결합하는 것이 세 번째 방법으로 가장 효과적인 접근으로 보입니다. 이 조합은 메모리 관리와 코드 명확성을 최적화합니다.
Python에서 데이터셋을 분할하는 방법 (예시)
Python에서는 데이터 유형과 사용하는 프레임워크에 따라 다양한 방법을 사용하여 데이터셋을 배치로 분할할 수 있습니다. 아래는 여러 가지 일반적인 접근 방식입니다:
- Python만 사용: 간단한 루프 또는 제너레이터를 사용하여 리스트나 배열을 분할합니다.
- NumPy: numpy.array_split을 사용하여 배열을 분할합니다.
- PyTorch: DataLoader를 사용하여 신경망에서 효율적으로 배치합니다.
- TensorFlow: tf.data.Dataset을 사용하여 효율적으로 배치 및 데이터 파이프라인을 처리합니다.
- Pandas: 리스트 내포나 루프를 사용하여 DataFrame을 분할합니다.
1. 간단한 Python 함수 사용
리스트나 NumPy 배열 형태의 데이터셋이 있는 경우, 사용자 정의 기능을 사용하여 데이터를 배치로 분할할 수 있습니다.
def split_into_batches(data, batch_size): """지정된 크기의 배치로 데이터를 분할합니다.""" for i in range(0, len(data), batch_size): yield data[i:i + batch_size] # 사용 예 dataset = [i for i in range(100)] # 예시 데이터셋 batch_size = 10 batches = list(split_into_batches(dataset, batch_size)) # 배치 출력 for batch in batches: print(batch)
2. numpy.array_split 사용
데이터셋이 NumPy 배열 형태인 경우, numpy.array_split() 함수를 사용하여 데이터셋을 배치로 분할할 수 있습니다.
import numpy as np # 예시 데이터셋 dataset = np.arange(100) # 배치로 분할 batch_size = 10 batches = np.array_split(dataset, len(dataset) // batch_size) # 배치 출력 for batch in batches: print(batch)
3. torch.utils.data.DataLoader 사용 (PyTorch)
PyTorch를 사용하고 있다면, DataLoader를 사용하여 데이터셋을 쉽게 배치할 수 있습니다. 이는 데이터를 섞고 배치할 수 있습니다.
import torch from torch.utils.data import DataLoader, TensorDataset # 예시 데이터셋 data = torch.arange(100) labels = torch.arange(100) # 레이블도 있다고 가정 # TensorDataset 생성 dataset = TensorDataset(data, labels) # DataLoader를 사용하여 배치로 분할 batch_size = 10 dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) # 배치 출력 for batch_data, batch_labels in dataloader: print(batch_data, batch_labels)
예시: 인덱서를 얻기 위한 간단한 리스트
import math import torch import torch.nn as nn X = torch.rand(1000,10, 4) batch_size = 64 num_batches = math.ceil(X.size()[0]/batch_size) X_list = [X[batch_size*y:batch_size*(y+1),:,:] for y in range(num_batches)] print(X_list[0].size())
4. tensorflow.data.Dataset 사용 (TensorFlow)
TensorFlow의 경우, tf.data.Dataset API는 데이터셋을 배치하는 고성능 방법을 제공합니다.
import tensorflow as tf # 예시 데이터셋 dataset = tf.data.Dataset.range(100) # 배치로 분할 batch_size = 10 batched_dataset = dataset.batch(batch_size) # 배치 출력 for batch in batched_dataset: print(batch.numpy())
5. pandas를 사용한 DataFrame 처리
데이터셋이 pandas DataFrame인 경우, 청킹을 통해 배치로 분할할 수 있습니다.
import pandas as pd # 예시 데이터셋 data = pd.DataFrame({'A': range(100), 'B': range(100)}) # 배치로 분할 batch_size = 10 batches = [data[i:i+batch_size] for i in range(0, data.shape[0], batch_size)] # 배치 출력 for batch in batches: print(batch)
결론
이번 포스트에서는 데이터를 배치로 분할하고 처리하는 세 가지 방법을 탐구하고, 성능과 전체 코드 품질을 비교했습니다. 제너레이터가 단독으로 효율성을 높이지는 않지만, 더 우아하고 가독성 있는 솔루션에 기여한다는 것을 관찰했습니다. 궁극적으로 각 접근 방식의 효과는 시간과 메모리 제약에 의해 영향을 받습니다.
어떤 접근 방식이 가장 매력적으로 보이나요?
데이터 형식과 처리 요구 사항에 가장 적합한 방법을 선택하세요.
행운을 빕니다!
Python에서 데이터셋을 분할에 대한 FAQ
Python에서 데이터셋을 배치로 나누는 방법은?
Python에서 데이터셋을 배치로 나누려면 NumPy나 PyTorch와 같은 라이브러리를 사용할 수 있습니다. 다음은 NumPy를 사용한 간단한 예시입니다:
import numpy as np def create_batches(data, batch_size): return np.array_split(data, np.ceil(len(data) / batch_size)) # 사용 예시 data = np.arange(10) # 샘플 데이터셋 batches = create_batches(data, 3) print(batches)
이 함수는 데이터셋을 지정된 크기의 배치로 분할합니다.
내 데이터셋을 어떻게 나눠야 할까요?
데이터셋을 분할할 때는 다음과 같은 전략을 고려하세요:
- 무작위 분할: 데이터셋을 섞고 훈련, 검증 및 테스트 세트로 나눕니다.
- 계층적 분할: 각 부분집합이 원본 데이터셋과 같은 타겟 클래스 분포를 유지하도록 합니다.
- 시간 기반 분할: 시계열 데이터의 경우, 순서를 유지하기 위해 시간에 따라 나눕니다.
공통적인 방법은 이 방법들의 조합을 사용하여 각 부분집합에서 대표 샘플을 보장하는 것입니다.
데이터셋을 80 20으로 분할하는 방법은?
데이터셋을 80% 훈련, 20% 테스트로 나누려면 sklearn.model_selection
모듈의 train_test_split
함수를 사용할 수 있습니다:
from sklearn.model_selection import train_test_split data = ... # 당신의 데이터셋 train_data, test_data = train_test_split(data, test_size=0.2, random_state=42)
이 코드는 데이터를 무작위로 나누어 80%를 train_data
에, 20%를 test_data
에 할당합니다.
대형 데이터셋에 적합한 배치 크기를 어떻게 선택하나요?
대형 데이터셋에 적합한 배치 크기를 선택할 때는 여러 가지 고려 사항이 있습니다:
- 메모리 제약: 배치 크기가 GPU 또는 CPU 메모리 한계 내에 있어야 합니다.
- 훈련 안정성: 작은 배치 크기는 더 안정적인 훈련을 가져올 수 있지만, 훈련 시간이 증가할 수 있습니다.
- 학습 동역학: 큰 배치 크기는 훈련 속도를 높일 수 있지만, 일반화가 저하될 수 있습니다.
일반적인 접근 방식은 32 또는 64의 배치 크기로 시작하여 성능과 자원 가용성에 따라 조정하는 것입니다. 최적의 배치 크기를 찾는 것은 실험하는 것이 가장 중요합니다.
결론을 짓기 전에, 가격이 점점 비싸지고 기능이 줄어드는 Postman의 적합한 대체 수단이 필요한 API 테스팅을 하고 있다면, APIDog이 당신의 이상적인 선택입니다!

Apidog은 Postman과 유사한 API 관리 및 검증을 위해 설계된 협업 플랫폼이지만, 날짜 처리를 더 쉽게 만드는 추가 기능이 있습니다. 다음은 APIDog이 어떻게 도움을 줄 수 있는지입니다: