dataset
对于单个样本
dataloader
批量样本
Dataset 存储样本和它们相应的标签,DataLoader 在 Dataset 基础上添加了一个迭代器,迭代器可以迭代数据集,以便能够轻松地访问 Dataset 中的样本(变为mini-batch形式,多个样本组合成mini-batch,random,保存在gpu中等)
支持下标索引获取样本,主要是拿出mini-batch(一组数据,训练时使用)
batch可以利用并行计算(向量计算),随机梯度下降每次一个样本时间过长(虽然随机性较好)
均衡性能和时间:mini-batch epoch:一个epoch中所有样本都参与了训练 batch-size:每次训练时用到的样本数量 iteration:batch的个数
如果可以使用下标获取dataset样本和知道dataset长度,则DataLoader可以自动生成mini-batch数据集
pytorch还提供部分预加载数据集
torch.utils.data.Dataset
https://pytorch.org/text/stable/datasets.html
构造数据集(init()函数中)的两种选择
(1)在init中加载整个数据集,用getitem时将第[i]个样本传出去
(2)数据集较大的情况:比如图片:可能只是加载图片的路径列表,图像分割时可能输出y也很大,则也使用文件名
防止显存超出
使用torchvision导入预加载的数据集
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
自定义数据集加载
自定义的数据集类必须实现三个函数: init, len, 和 getitem
getitem 从数据集中给定的索引 idx 处加载并返回一个样本
函数 len 返回我们数据集中的样本数。
# import gzip
# import numpy as np
# import os
# import requests
# # 下载 Fashion MNIST 数据集
# def download_fashion_mnist(base_url, filename, destination):
# if not os.path.exists(destination):
# os.makedirs(destination)
# filepath = os.path.join(destination, filename)
# if not os.path.exists(filepath):
# url = base_url + filename
# response = requests.get(url)
# with open(filepath, 'wb') as f:
# f.write(response.content)
# return filepath
# # 解压 gz 文件
# def extract_gz(filepath, destination):
# with gzip.open(filepath, 'rb') as f_in:
# with open(destination, 'wb') as f_out:
# f_out.write(f_in.read())
# return destination
# # 读取二进制文件并转换为 numpy 数组
# def load_fashion_mnist_images(filepath):
# with open(filepath, 'rb') as f:
# data = f.read()
# images = np.frombuffer(data, dtype=np.uint8, offset=16).reshape(-1, 28, 28)
# return images
# def load_fashion_mnist_labels(filepath):
# with open(filepath, 'rb') as f:
# data = f.read()
# labels = np.frombuffer(data, dtype=np.uint8, offset=8)
# return labels
# base_url = 'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/'
# destination_dir = 'fashion_mnist'
# train_images_path = download_fashion_mnist(base_url, 'train-images-idx3-ubyte.gz', destination_dir)
# train_labels_path = download_fashion_mnist(base_url, 'train-labels-idx1-ubyte.gz', destination_dir)
# test_images_path = download_fashion_mnist(base_url, 't10k-images-idx3-ubyte.gz', destination_dir)
# test_labels_path = download_fashion_mnist(base_url, 't10k-labels-idx1-ubyte.gz', destination_dir)
# train_images = load_fashion_mnist_images(extract_gz(train_images_path, 'train-images-idx3-ubyte'))
# train_labels = load_fashion_mnist_labels(extract_gz(train_labels_path, 'train-labels-idx1-ubyte'))
# test_images = load_fashion_mnist_images(extract_gz(test_images_path, 't10k-images-idx3-ubyte'))
# test_labels = load_fashion_mnist_labels(extract_gz(test_labels_path, 't10k-labels-idx1-ubyte'))
# print(f'Train images shape: {train_images.shape}')
# print(f'Train labels shape: {train_labels.shape}')
# print(f'Test images shape: {test_images.shape}')
# print(f'Test labels shape: {test_labels.shape}')
# print(train_labels)
import gzip
import numpy as np
import os
import requests
# 下载 Fashion MNIST 数据集
def download_fashion_mnist(base_url, filename, destination):
if not os.path.exists(destination):
os.makedirs(destination)
filepath = os.path.join(destination, filename)
if not os.path.exists(filepath):
url = base_url + filename
response = requests.get(url)
with open(filepath, 'wb') as f:
f.write(response.content)
return filepath
# 解压 gz 文件
def extract_gz(filepath, destination):
with gzip.open(filepath, 'rb') as f_in:
with open(destination, 'wb') as f_out:
f_out.write(f_in.read())
return destination
# 读取二进制文件并转换为 numpy 数组
def load_fashion_mnist_images(filepath):
with open(filepath, 'rb') as f:
data = f.read()
images = np.frombuffer(data, dtype=np.uint8, offset=16).reshape(-1, 28, 28)
return images
def load_fashion_mnist_labels(filepath):
with open(filepath, 'rb') as f:
data = f.read()
labels = np.frombuffer(data, dtype=np.uint8, offset=8)
return labels
# 保存 numpy 数组到文件
def save_numpy_array(data, filepath):
np.save(filepath, data)
# 加载保存的 numpy 数组
def load_numpy_array(filepath):
return np.load(filepath)
base_url = 'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/'
destination_dir = 'fashion_mnist'
# 下载并解压文件
train_images_path = download_fashion_mnist(base_url, 'train-images-idx3-ubyte.gz', destination_dir)
train_labels_path = download_fashion_mnist(base_url, 'train-labels-idx1-ubyte.gz', destination_dir)
test_images_path = download_fashion_mnist(base_url, 't10k-images-idx3-ubyte.gz', destination_dir)
test_labels_path = download_fashion_mnist(base_url, 't10k-labels-idx1-ubyte.gz', destination_dir)
train_images = load_fashion_mnist_images(extract_gz(train_images_path, os.path.join(destination_dir, 'train-images-idx3-ubyte')))
train_labels = load_fashion_mnist_labels(extract_gz(train_labels_path, os.path.join(destination_dir, 'train-labels-idx1-ubyte')))
test_images = load_fashion_mnist_images(extract_gz(test_images_path, os.path.join(destination_dir, 't10k-images-idx3-ubyte')))
test_labels = load_fashion_mnist_labels(extract_gz(test_labels_path, os.path.join(destination_dir, 't10k-labels-idx1-ubyte')))
# 保存 numpy 数组到文件
save_numpy_array(train_images, os.path.join(destination_dir, 'train_images.npy'))
save_numpy_array(train_labels, os.path.join(destination_dir, 'train_labels.npy'))
save_numpy_array(test_images, os.path.join(destination_dir, 'test_images.npy'))
save_numpy_array(test_labels, os.path.join(destination_dir, 'test_labels.npy'))
# 加载保存的 numpy 数组
loaded_train_images = load_numpy_array(os.path.join(destination_dir, 'train_images.npy'))
loaded_train_labels = load_numpy_array(os.path.join(destination_dir, 'train_labels.npy'))
loaded_test_images = load_numpy_array(os.path.join(destination_dir, 'test_images.npy'))
loaded_test_labels = load_numpy_array(os.path.join(destination_dir, 'test_labels.npy'))
print(f'Loaded train images shape: {loaded_train_images.shape}')
print(f'Loaded train labels shape: {loaded_train_labels.shape}')
print(f'Loaded test images shape: {loaded_test_images.shape}')
print(f'Loaded test labels shape: {loaded_test_labels.shape}')
Loaded train images shape: (60000, 28, 28)
Loaded train labels shape: (60000,)
Loaded test images shape: (10000, 28, 28)
Loaded test labels shape: (10000,)
transforms,target
图片大小,通道
标签one-hot
target_transform=Lambda(lambda y: …)
Lambda 是 PyTorch 中的一种方便的工具,可以用来创建简单的转换。这里 Lambda 包装了一个匿名函数(lambda 函数),这个函数将标签 y 转换为 one-hot 编码。
lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1)
这个 lambda 函数接收一个标签 y,并进行如下操作:
torch.zeros(10, dtype=torch.float): 创建一个长度为 10 的零向量,数据类型为 torch.float。这个向量代表了 10 个类别的初始化状态,全部设置为 0。
.scatter_(0, torch.tensor(y), value=1): 使用 scatter_ 方法在零向量上进行散射操作。
0 是沿着的维度(这里是第一维度)。
torch.tensor(y) 是需要设置为 1 的位置。
value=1 是要散射的值,即将标签 y 对应的位置的值设置为 1。
因此,lambda 函数会将一个标量标签 y 转换为一个长度为 10 的 one-hot 编码向量。例如,如果 y 是 3,那么生成的向量会是 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]。
例子
假设标签 y 是 5,执行 lambda 函数的步骤如下:
创建一个零向量:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
使用 scatter_ 方法将第 5 个位置设置为 1:[0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
最终输出的就是一个 one-hot 编码的向量。
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
#__init__传入数据集目录,标注文件,如果有对应图片,传入对应图片目录,
# __len__返回整个数据集的大小
import os
import pandas as pd
from torchvision.io import read_image
from torch.utils.data import Dataset
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
#获取照片的文件名(在数据标注的.csv中),拼接为路径
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
#transfrom:归一化,通道处理等,padding等
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
dataset = CustomImageDataset("")
参数
num_workers:取决于cpu个数,shuffle打乱数据
sampler采样
drop_last丢弃最后的一个小批次,如果不是batch_size的整数倍
collate_fn对sampler采样的小批次再处理,比如padding,输入输出都是一个batch
test一般不用shuffle
将该数据集加载到 DataLoader 中,并可以根据需要迭代该数据集。每次迭代都会返回一批 train_features 和 train_labels (分别包含 batch_size=64 个特征和标签)。因为我们指定了 shuffle=True,在我们遍历所有批次后,数据会被打乱
# Dataset 每次加载一组我们数据集的特征和标签样本。在训练一个模型时,我们通常希望以 "小批量" 的方式传递样本,在每个训练周期重新打乱数据以减少模型的过拟合,并使用 Python 的 multiprocessing 来加快数据的加载速度。
from torch.utils.data import DataLoader
#
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
# 显示图像和标签。
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")
示例:
from __future__ import print_function, division
import os
import torch
import pandas as pd #用于更容易地进行csv解析
from skimage import io, transform #用于图像的IO和变换
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils
# 忽略警告
import warnings
warnings.filterwarnings("ignore")
plt.ion() # interactive mode
<contextlib.ExitStack at 0x237f2d27fd0>
#数据集为csv文件
landmarks_frame = pd.read_csv('faces/face_landmarks.csv')
landmarks_frame.head(5)
#查看其中一张图片
n = 3
img_name = landmarks_frame.iloc[n, 0]
# Series' object has no attribute 'as_matrix'
landmarks = landmarks_frame.iloc[n, 1:].values
landmarks = landmarks.astype('float').reshape(-1, 2)#矩阵的每一行我i散点图坐标
print('Image name: {}'.format(img_name))
print('Landmarks shape: {}'.format(landmarks.shape))
print('First 4 Landmarks: {}'.format(landmarks[:4]))
Image name: 110276240_bec305da91.jpg
Landmarks shape: (68, 2)
First 4 Landmarks: [[ 42. 140.]
[ 45. 161.]
[ 51. 180.]
[ 61. 200.]]
def show_landmarks(image, landmarks):
"""显示带有地标的图片"""
plt.imshow(image)
plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
plt.pause(0.001) # pause a bit so that plots are updated
plt.figure()
show_landmarks(io.imread(os.path.join('faces/', img_name)),
landmarks)
plt.show()
#编写数据集类
# 在 __init__中读取csv的文件内容,在 __getitem__中读取图片(节省空间)
class FaceLandmarksDataset(Dataset):
"""面部标记数据集."""
def __init__(self, csv_file, root_dir, transform=None):
"""
csv_file(string):带注释的csv文件的路径。
root_dir(string):包含所有图像的目录。
transform(callable, optional):一个样本上的可用的可选变换
"""
self.landmarks_frame = pd.read_csv(csv_file)
self.root_dir = root_dir
self.transform = transform
def __len__(self):
return len(self.landmarks_frame)
def __getitem__(self, idx):
img_name = os.path.join(self.root_dir,
self.landmarks_frame.iloc[idx, 0])
image = io.imread(img_name)
landmarks = self.landmarks_frame.iloc[idx, 1:]
landmarks = np.array([landmarks])
landmarks = landmarks.astype('float').reshape(-1, 2)
sample = {'image': image, 'landmarks': landmarks}
if self.transform:
sample = self.transform(sample)
return sample
face_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
root_dir='faces/')
fig = plt.figure()
for i in range(len(face_dataset)):
sample = face_dataset[i]
print(i, sample['image'].shape, sample['landmarks'].shape)
ax = plt.subplot(1, 4, i + 1)
plt.tight_layout()
ax.set_title('Sample #{}'.format(i))
ax.axis('off')
show_landmarks(**sample)
if i == 3:
plt.show()
break
0 (324, 215, 3) (68, 2)
1 (500, 333, 3) (68, 2)
2 (250, 258, 3) (68, 2)
3 (434, 290, 3) (68, 2)
很多神经网络假定图片的尺寸相同,所以需要对图片预处理:三个转换: * Rescale:缩放图片 * RandomCrop:对图片进行随机裁剪。这是一种数据增强操作 * ToTensor:把numpy格式图片转为torch格式图片 (我们需要交换坐标轴).
__init__方法:
该方法用于初始化Rescale类的实例。
接受一个参数output_size,该参数可以是整数或元组。如果是整数,表示将图像的较小边缘缩放到这个大小,保持纵横比。如果是元组,表示将图像缩放到与output_size匹配的大小。
__call__方法:
这是一个特殊方法,允许类的实例像函数一样被调用。
接受一个参数sample,其中包含图像和相应的地标。sample是一个字典,包含键为’image’和’landmarks’的项。
从sample中提取图像和地标。
图像缩放操作:
获取输入图像的高度 h 和宽度 w。
如果output_size是整数,计算新的高度和宽度new_h,new_w,以保持原始图像的纵横比。如果图像的高度大于宽度,较小的边将被缩放到output_size,并调整另一边以保持纵横比。反之亦然。
如果output_size是元组,直接将新的高度和宽度设置为output_size。
将新的高度和宽度转换为整数。
使用scikit-image库的transform.resize函数进行图像的实际缩放操作:
transform.resize函数用于将图像缩放到指定的大小(new_h, new_w)。
将新的图像存储在变量img中。
调整地标坐标:
将地标的坐标按照图像缩放的比例进行调整。这是因为图像的缩放会导致地标坐标的变化。
通过乘以[new_w / w, new_h / h]来调整每个地标的坐标。
返回结果:
将新的图像和调整后的地标作为字典返回,键为’image’和’landmarks’。
class Rescale(object):
"""将样本中的图像重新缩放到给定大小。.
Args:
output_size(tuple或int):所需的输出大小。 如果是元组,则输出为
与output_size匹配。 如果是int,则匹配较小的图像边缘到output_size保持纵横比相同。
"""
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
self.output_size = output_size
def __call__(self, sample):
image, landmarks = sample['image'], sample['landmarks']
h, w = image.shape[:2]
if isinstance(self.output_size, int):
if h > w:
new_h, new_w = self.output_size * h / w, self.output_size
else:
new_h, new_w = self.output_size, self.output_size * w / h
else:
new_h, new_w = self.output_size
new_h, new_w = int(new_h), int(new_w)
img = transform.resize(image, (new_h, new_w))
# h and w are swapped for landmarks because for images,
# x and y axes are axis 1 and 0 respectively
landmarks = landmarks * [new_w / w, new_h / h]
return {'image': img, 'landmarks': landmarks}
class RandomCrop(object):
"""随机裁剪样本中的图像.
Args:
output_size(tuple或int):所需的输出大小。 如果是int,方形裁剪是。
"""
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
if isinstance(output_size, int):
self.output_size = (output_size, output_size)
else:
assert len(output_size) == 2
self.output_size = output_size
def __call__(self, sample):
image, landmarks = sample['image'], sample['landmarks']
h, w = image.shape[:2]
new_h, new_w = self.output_size
top = np.random.randint(0, h - new_h)
left = np.random.randint(0, w - new_w)
image = image[top: top + new_h,
left: left + new_w]
landmarks = landmarks - [left, top]
return {'image': image, 'landmarks': landmarks}
class ToTensor(object):
"""将样本中的ndarrays转换为Tensors."""
def __call__(self, sample):
image, landmarks = sample['image'], sample['landmarks']
# 交换颜色轴因为
# numpy包的图片是: H * W * C
# torch包的图片是: C * H * W
image = image.transpose((2, 0, 1))
return {'image': torch.from_numpy(image),
'landmarks': torch.from_numpy(landmarks)}
# image.transpose((2, 0, 1)) 是一个 NumPy 数组的操作,它用于交换数组的维度顺序。在这个特定的例子中,假设image是一个三维的 NumPy 数组,表示图像,其形状为 (height, width, channels)。
# (2, 0, 1) 意味着进行维度的转换,将原来的维度顺序 (height, width, channels) 转换为 (channels, height, width)。
# 2 表示原数组的第三个维度(通道维度)会变成新数组的第一个维度。
# 0 表示原数组的第一个维度(高度)会变成新数组的第二个维度。
# 1 表示原数组的第二个维度(宽度)会变成新数组的第三个维度。
# 图像的短边调整为256,然后随机裁剪(randomcrop)为224大小的正方形。
# 也就是说,我们打算组合一个Rescale和 RandomCrop的变换
scale = Rescale(256)
crop = RandomCrop(128)
composed = transforms.Compose([Rescale(256),
RandomCrop(224)])
# 在样本上应用上述的每个变换。
fig = plt.figure()
sample = face_dataset[65]
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(type(tsfrm).__name__)
show_landmarks(**transformed_sample)
plt.show()
scale = Rescale(256)
crop = RandomCrop(156)
composed = transforms.Compose([Rescale(256),
RandomCrop(224)])
# 在样本上应用上述的每个变换。
fig = plt.figure()
sample = face_dataset[65]
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(type(tsfrm).__name__)
show_landmarks(**transformed_sample)
plt.show()
Dateset可以使用索引拿出一个数据,DataLoder可以拿出一个mini-batch