张量
张量是一种特殊的数据结构,与数组和矩阵非常相似。在 PyTorch 中,我们使用张量对模型的输入和输出以及模型的参数进行编码。
张量与NumPy 的ndarray类似,不同之处在于张量可以在 GPU 或其他硬件加速器上运行。事实上,张量和 NumPy 数组通常可以共享相同的底层内存,从而无需复制数据,张量还针对自动微分进行了优化,如果您熟悉 ndarrays,那么您就会熟悉 Tensor API。如果没有,那就跟随吧!
import torch
import numpy as np
初始化张量
张量可以通过多种方式初始化。看看下面的例子:
直接来自数据
张量可以直接从数据创建。数据类型是自动推断的。
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
来自 NumPy 数组
张量可以从 NumPy 数组创建。
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
从另一个张量:
新张量保留参数张量的属性(形状、数据类型),除非显式覆盖。
x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.8823, 0.9150],
[0.3829, 0.9593]])
使用随机值或常数值:
shape是张量维度的元组。在下面的函数中,它确定输出张量的维数。
shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
Random Tensor:
tensor([[0.3904, 0.6009, 0.2566],
[0.7936, 0.9408, 0.1332]])
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
张量的属性
张量属性描述了它们的形状、数据类型以及存储它们的设备。
tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
张量运算
这里全面描述了 100 多种张量运算,包括算术、线性代数、矩阵操作(转置、索引、切片)、采样等。
这些操作中的每一个都可以在 GPU 上运行(速度通常高于 CPU)。如果您使用 Colab,请通过转至运行时 > 更改运行时类型 > GPU 来分配 GPU。
默认情况下,张量是在 CPU 上创建的。我们需要使用方法显式地将张量移动到 GPU .to(在检查 GPU 可用性之后)。请记住,跨设备复制大张量在时间和内存方面可能会很昂贵!
# 将张量专用到gpu进行运算
```python
if torch.cuda.is_available():
tensor = tensor.to("cuda")
尝试列表中的一些操作。如果您熟悉 NumPy API,您会发现 Tensor API 使用起来非常简单。
标准的类似 numpy 的索引和切片:
```python
tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
连接张量您可以使用它torch.cat来沿给定维度连接一系列张量。另请参见torch.stack,这是另一个与 略有不同的张量连接运算符torch.cat。
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
算术运算
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` returns the transpose of a tensor
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)
# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
单元素张量如果您有一个单元素张量,例如通过将张量的所有值聚合为一个值,您可以使用以下方法将其转换为 Python 数值item():
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
12.0 <class 'float'>
就地运算 将结果存储到操作数中的操作称为就地运算。它们由_后缀表示。例如:x.copy_(y)、x.t_()、 将会改变x。
print(f"{tensor} \n")
tensor.add_(5)
print(tensor)
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.]])
笔记
In-place operations 可以节省一些内存,但在计算导数时可能会出现问题,因为历史记录会立即丢失。因此,不鼓励使用它们。
与 NumPy 的桥梁
CPU 和 NumPy 数组上的张量可以共享其底层内存位置,改变其中一个就会改变另一个。
张量到 NumPy 数组
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
张量的变化反映在 NumPy 数组中。
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
NumPy 数组转换为张量
n = np.ones(5)
t = torch.from_numpy(n)
NumPy 数组中的变化反映在张量中。
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
数据集和数据加载器
用于处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望数据集代码与模型训练代码分离,以获得更好的可读性和模块化性。PyTorch 提供了两个数据原语:torch.utils.data.DataLoader和torch.utils.data.Dataset 允许您使用预加载的数据集以及您自己的数据。 Dataset存储样本及其相应的标签,并DataLoader围绕 进行迭代Dataset以方便访问样本。
PyTorch 域库提供了许多预加载的数据集(例如 FashionMNIST),它们子类化torch.utils.data.Dataset并实现特定于特定数据的函数。它们可用于对您的模型进行原型设计和基准测试。您可以在这里找到它们:图像数据集、 文本数据集和 音频数据集
加载数据集
以下是如何从 TorchVision 加载Fashion-MNIST数据集的示例。Fashion-MNIST 是 Zalando 文章图像的数据集,由 60,000 个训练示例和 10,000 个测试示例组成。每个示例包含一个 28×28 灰度图像和来自 10 个类别之一的关联标签。
我们使用以下参数加载FashionMNIST 数据集:
root是存储训练/测试数据的路径,
train指定训练或测试数据集,
download=True如果 上没有数据,则从 Internet 下载数据root。
transform并target_transform指定特征和标签转换
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()
)
如下,运行后加载数据过程:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/26421880 [00:00<?, ?it/s]
0%| | 65536/26421880 [00:00<01:12, 366034.86it/s]
1%| | 229376/26421880 [00:00<00:38, 684873.31it/s]
3%|3 | 884736/26421880 [00:00<00:10, 2481647.02it/s]
7%|6 | 1835008/26421880 [00:00<00:05, 4208931.24it/s]
13%|#3 | 3440640/26421880 [00:00<00:03, 7177552.93it/s]
23%|##3 | 6094848/26421880 [00:00<00:01, 12236602.40it/s]
31%|###1 | 8224768/26421880 [00:00<00:01, 13544879.04it/s]
37%|###7 | 9895936/26421880 [00:01<00:01, 14156027.95it/s]
44%|####3 | 11534336/26421880 [00:01<00:01, 14608940.92it/s]
54%|#####3 | 14155776/26421880 [00:01<00:00, 17389560.03it/s]
62%|######1 | 16252928/26421880 [00:01<00:00, 17589694.34it/s]
68%|######8 | 18055168/26421880 [00:01<00:00, 17139468.01it/s]
75%|#######4 | 19791872/26421880 [00:01<00:00, 17163042.84it/s]
81%|########1 | 21528576/26421880 [00:01<00:00, 16903624.79it/s]
91%|#########1| 24117248/26421880 [00:01<00:00, 19091471.08it/s]
100%|#########9| 26312704/26421880 [00:01<00:00, 19035749.70it/s]
100%|##########| 26421880/26421880 [00:01<00:00, 13493046.15it/s]
Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/29515 [00:00<?, ?it/s]
100%|##########| 29515/29515 [00:00<00:00, 330385.76it/s]
Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
0%| | 0/4422102 [00:00<?, ?it/s]
1%|1 | 65536/4422102 [00:00<00:11, 364325.69it/s]
5%|5 | 229376/4422102 [00:00<00:06, 683672.36it/s]
14%|#4 | 622592/4422102 [00:00<00:02, 1688465.21it/s]
33%|###2 | 1441792/4422102 [00:00<00:00, 3363222.72it/s]
57%|#####7 | 2523136/4422102 [00:00<00:00, 5144795.23it/s]
100%|##########| 4422102/4422102 [00:00<00:00, 5428079.80it/s]
Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
0%| | 0/5148 [00:00<?, ?it/s]
100%|##########| 5148/5148 [00:00<00:00, 31988558.51it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
迭代和可视化数据集
我们可以Datasets像列表一样手动索引:training_data[index]。我们用来matplotlib可视化训练数据中的一些样本。
labels_map = {
0: "T-Shirt",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(training_data), size=(1,)).item()
img, label = training_data[sample_idx]#加载数据
figure.add_subplot(rows, cols, i)
plt.title(labels_map[label])
plt.axis("off")
plt.imshow(img.squeeze(), cmap="gray")#显示样本图像
plt.show()
踝靴、衬衫、包、踝靴、裤子、凉鞋、外套、凉鞋、套头衫
为您的文件创建自定义数据集
自定义 Dataset 类必须实现三个函数:init、len__和__getitem。看看这个实现;FashionMNIST 图像存储在一个目录中img_dir,它们的标签单独存储在一个 CSV 文件中annotations_file。
在接下来的部分中,我们将详细介绍每个函数中发生的情况。
import os
import pandas as pd
from torchvision.io import read_image
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):#获取数据,idx为数据索引
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:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
init 函数在实例化 Dataset 对象时运行一次。我们初始化包含图像、注释文件和两种转换的目录
labels.csv 文件如下所示:
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
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
len 函数返回数据集中的样本数。
例子:
def __len__(self):
return len(self.img_labels)
getitem 函数加载并返回给定索引处的数据集的样本idx。基于索引,它识别图像在磁盘上的位置,使用 将其转换为张量read_image,从 中的 csv 数据中检索相应的标签self.img_labels,调用它们的转换函数(如果适用),并返回张量图像和相应的标签一个元组。
def __getitem__(self, idx):
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:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
使用 DataLoaders 准备数据以进行训练
它Dataset检索我们的数据集的特征并一次标记一个样本。在训练模型时,我们通常希望以“小批量”方式传递样本,在每个时期重新整理数据以减少模型过度拟合,并使用 Python 来加速multiprocessing数据检索。
DataLoader是一个可迭代对象,它通过一个简单的 API 为我们抽象了这种复杂性。
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)
遍历 DataLoader
我们已将该数据集加载到 中DataLoader,并且可以根据需要迭代数据集。下面的每次迭代都会返回一批train_features和train_labels(batch_size=64分别包含特征和标签)。因为我们指定了shuffle=True,所以在迭代所有批次后,数据将被打乱(为了更细粒度地控制数据加载顺序,请查看Samplers)。
# Display image and label.
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}")
Feature batch shape: torch.Size([64, 1, 28, 28])
Labels batch shape: torch.Size([64])
Label: 5