趋近智
构建一个完整的数据处理流程涉及从原始数据(为简化起见,将合成数据)开始,最终得到准备好输入模型的数据批次。这个过程需要创建一个自定义 Dataset,定义数据转换,并将所有内容封装在一个 DataLoader 中。
假设我们有一个数据集,包含特征向量 (vector)和对应的二元分类标签(0 或 1)。在本次练习中,我们将使用 PyTorch 张量直接生成这些数据。这避免了文件输入/输出的复杂性,让我们能够专注于数据处理机制。
import torch
import torch.utils.data as data
from torchvision import transforms
# 生成合成数据
num_samples = 100
num_features = 10
# 创建随机特征向量(例如,传感器读数)
features = torch.randn(num_samples, num_features)
# 创建随机二元标签(0 或 1)
labels = torch.randint(0, 2, (num_samples,))
print(f"Shape of features: {features.shape}") # 输出: torch.Size([100, 10])
print(f"Shape of labels: {labels.shape}") # 输出: torch.Size([100])
print(f"First 5 features:\n{features[:5]}")
print(f"First 5 labels:\n{labels[:5]}")
这使我们得到了两个张量:features 包含 100 个样本,每个样本有 10 个特征;labels 包含对应的 100 个标签。
Dataset现在,我们需要使用 PyTorch 的 Dataset 类来组织这些数据。我们将创建一个自定义类,它继承自 torch.utils.data.Dataset 并实现两个重要方法:
__len__(self): 返回数据集中样本的总数。__getitem__(self, idx): 返回指定索引 idx 处的样本(特征和标签)。我们还将添加一个 __init__ 方法来存储数据并可选地接受转换。
class SyntheticDataset(data.Dataset):
"""一个用于合成特征和标签的自定义数据集。"""
def __init__(self, features, labels, transform=None):
"""
参数:
features (Tensor): 包含特征数据的张量。
labels (Tensor): 包含标签的张量。
transform (callable, optional): 可选的样本转换。
"""
# 基本检查,确保特征和标签具有相同的样本数量
assert features.shape[0] == labels.shape[0], \
"特征和标签的样本数量必须一致"
self.features = features
self.labels = labels
self.transform = transform
def __len__(self):
"""返回样本总数。"""
return self.features.shape[0]
def __getitem__(self, idx):
"""
根据给定索引获取特征向量和标签。
参数:
idx (int): 要获取的样本索引。
返回:
tuple: (特征, 标签),其中 feature 是特征向量,label 是对应的标签。
"""
# 获取原始特征和标签
feature_sample = self.features[idx]
label_sample = self.labels[idx]
# 创建一个样本字典(或元组)
sample = {'feature': feature_sample, 'label': label_sample}
# 如果存在转换,则应用转换
if self.transform:
sample = self.transform(sample)
# 返回可能已转换的样本
# 常见做法是分别返回特征和标签
return sample['feature'], sample['label']
# 暂时实例化不带转换的数据集
raw_dataset = SyntheticDataset(features, labels)
# 测试获取一个样本
sample_idx = 0
feature_sample, label_sample = raw_dataset[sample_idx]
print(f"\nSample {sample_idx} - Feature: {feature_sample}")
print(f"Sample {sample_idx} - Label: {label_sample}")
print(f"Dataset length: {len(raw_dataset)}") # 输出: 100
此时,raw_dataset 包含我们的数据并知道如何提供单个样本。
通常,原始数据不适合直接输入神经网络 (neural network)。我们可能需要规范化特征、转换数据类型或应用数据增强(特别是对于图像)。torchvision.transforms 提供了方便的工具。即使我们的数据不是图像,我们也可以定义自定义转换或使用对张量进行操作的现有转换。
让我们定义一个简单转换流程:
torch.float32(模型输入的良好做法)。torch.long(损失函数 (loss function)如 CrossEntropyLoss 常需要的)。由于 torchvision.transforms 主要为图像(PIL 图像或张量)设计,将其直接应用于像我们 sample 这样的字典需要一些封装。我们将为此创建自定义可调用类或 lambda 函数。
# 计算用于规范化的均值和标准差(跨数据集)
feature_mean = features.mean(dim=0)
feature_std = features.std(dim=0)
# 如果任何特征的标准差为零,则避免除以零
feature_std[feature_std == 0] = 1.0
# 为字典样本格式定义自定义转换类/函数
class ToTensorAndType(object):
"""将特征转换为 FloatTensor,将标签转换为 LongTensor。"""
def __call__(self, sample):
feature, label = sample['feature'], sample['label']
return {'feature': feature.float(), 'label': label.long()}
class NormalizeFeatures(object):
"""规范化特征张量。"""
def __init__(self, mean, std):
self.mean = mean
self.std = std
def __call__(self, sample):
feature, label = sample['feature'], sample['label']
# 应用规范化: (张量 - 均值) / 标准差
normalized_feature = (feature - self.mean) / self.std
return {'feature': normalized_feature, 'label': label}
# 组合转换
data_transforms = transforms.Compose([
ToTensorAndType(),
NormalizeFeatures(mean=feature_mean, std=feature_std)
])
# 实例化带转换的数据集
transformed_dataset = SyntheticDataset(features, labels, transform=data_transforms)
# 测试获取一个转换后的样本
sample_idx = 0
transformed_feature, transformed_label = transformed_dataset[sample_idx]
print(f"\n--- 转换后的样本 {sample_idx} ---")
print(f"原始特征:\n{features[sample_idx]}")
print(f"转换后特征:\n{transformed_feature}")
print(f"原始标签: {labels[sample_idx]} (dtype={labels.dtype})")
print(f"转换后标签: {transformed_label} (dtype={transformed_label.dtype})")
# 验证规范化(对于第一个样本的特征,均值应接近 0,标准差应接近 1)
print(f"转换后特征均值: {transformed_feature.mean():.4f}") # 应用数据集范围的规范化后应接近 0
注意到由于规范化,特征值已发生改变,并且特征和标签的数据类型现在分别是 torch.float32 和 torch.int64 (LongTensor)。
DataLoader最后一步是使用 DataLoader。它接收我们的 Dataset 实例,并处理批处理、数据混洗以及可能的并行数据加载。
# 创建 DataLoader
batch_size = 16 # 以 16 个样本的批次处理数据
shuffle_data = True # 在每个 epoch 开始时打乱数据
num_workers = 0 # 用于数据加载的子进程数量。0 表示数据加载在主进程中进行。
# 在非 Windows 平台上,通常可以将 num_workers 设置为 > 0 进行并行加载
# import os
# if os.name != 'nt': # 检查是否非 Windows
# num_workers = 2
data_loader = data.DataLoader(
transformed_dataset,
batch_size=batch_size,
shuffle=shuffle_data,
num_workers=num_workers
)
# 迭代 DataLoader 以获取批次数据
print(f"\n--- 迭代 DataLoader (batch_size={batch_size}) ---")
# 获取一个批次
feature_batch, label_batch = next(iter(data_loader))
print(f"Type of feature_batch: {type(feature_batch)}")
print(f"Shape of feature_batch: {feature_batch.shape}") # 输出: torch.Size([16, 10])
print(f"Shape of label_batch: {label_batch.shape}") # 输出: torch.Size([16])
print(f"Data type of feature_batch: {feature_batch.dtype}") # 输出: torch.float32
print(f"Data type of label_batch: {label_batch.dtype}") # 输出: torch.int64
# 你可以像这样遍历所有批次(例如,在一个训练 epoch 中)
# print("\n遍历几个批次:")
# for i, (batch_features, batch_labels) in enumerate(data_loader):
# if i >= 3: # 显示前 3 个批次
# break
# print(f"Batch {i+1}: Features shape={batch_features.shape}, Labels shape={batch_labels.shape}")
# # 在真实的训练循环中,你会将 batch_features 输入到模型
DataLoader 产生批次,其中第一个维度对应于 batch_size。我们的特征批次形状为 [16, 10],标签批次形状为 [16]。数据类型反映了我们应用的转换。
我们可以可视化刚刚创建的流程:
此图表展示了从原始张量到自定义
Dataset的演进过程,在数据获取时应用转换(__getitem__),最后使用DataLoader生成适合模型训练的混洗批次。
你现在已成功使用 PyTorch 的核心数据工具构建了数据处理流程。你创建了一个 Dataset 来封装你的数据,应用了必要的 transforms,并使用 DataLoader 高效地生成批次。这种结构化方法是 PyTorch 项目中处理数据的基础,确保你的模型接收正确格式的数据并促进高效训练。这个流程现在已准备好集成到我们将在下一章构建的训练循环中。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•