11.1 预备知识

11.1.1 数据操作

import torch
  1. 形状

    • shape

      输出形状

    • reshape()

      更改形状,-1表示自适应。默认按行排列,必要时可先改变形状,后转置得到按列排列的结果

    • numel()

      元素数量有多少

  2. 拼接

    • torch.cat()

      在已有维度上拼接

    • torch.stack() torch.vstack() torch.stack()

      在新的维度上堆叠

  3. 逐元素操作

    • 传统运算符+ - * / **

    • sum()

      若为空则对所有元素求和;dim指定轴方向,0,1,2...表示维度从外到内

x = torch.arange(24).reshape(2,3,-1)
x

"""
tensor([[[ 0,  1,  2,  3],
         [ 4,  5,  6,  7],
         [ 8,  9, 10, 11]],

        [[12, 13, 14, 15],
         [16, 17, 18, 19],
         [20, 21, 22, 23]]])
"""

x.sum(dim=0)

"""
tensor([[12, 14, 16, 18],
        [20, 22, 24, 26],
        [28, 30, 32, 34]])
"""

x.sum(dim=1)

"""
tensor([[12, 15, 18, 21],
        [48, 51, 54, 57]])
"""

x.sum(dim=2)

"""
tensor([[ 6, 22, 38],
        [54, 70, 86]])
"""
  • 广播机制

    维度对齐,从尾部(最右边)开始逐维度比较,形状不足的张量在左边补1个维度

    维度大小为1的轴自动”复制”以匹配较大尺寸

A = torch.tensor([[1, 2, 3], 
                  [4, 5, 6]])  # (2, 3)
b = torch.tensor([10, 20, 30])  # (3,) 

result = A * b

"""
tensor([[ 10,  40,  90],
        [ 40, 100, 180]])
"""

# 广播过程:b → (1,3) → (2,3)
# 由于张量b维度为1,A的维度为2,相当于张量b先复制了一行,再与A逐元素相乘
  1. 节省内存

    对于形如X=X+Y的操作,事实上赋值前的X和赋值后的X占用了两个地方的内存(即使变量名相同),建议改为X[:]=X+Y,这样前后X的内存地址就一致了

11.1.2 自动微分

深度学习框架能够自动计算导数:先将梯度附加到想要计算偏导数的变量上,然后对目标值进行反向传播backward(),访问得到的梯度。

x = torch.arange(4)
x.requires_grad_(True)   # 等价于x = torch.arange(4, requires_grad=True)
x.grad     # 默认值为None
y = 2 * torch.dot(x,x)
y.backward()
x.grad
x.grad.zero_()     # 变量会累积梯度,在必要时需要清空

对于复合函数y=f(x), z=g(y,x),有时想控制y直接计算z关于x的梯度,则需要将y剥离出来。

y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
  1. 类型转换
类型 方法 备注
数组->张量 torch.from_numpy() 共享内存
数组->张量 torch.tensor() 仅复制
张量->数组 .numpy() 共享内存
张量->数组 .clone().numpy() 仅复制
数据框->数组 .values 内存高
数据框->数组 .to_numpy(copy=False) 内存低
数组->数据框 pd.DataFrame() -

默认张量在CPU上,若在GPU上,则先将其移到CPU上,再.cpu().numpy()

存有梯度的张量不能直接转为数组,应.detach().numpy().detach().cpu().numpy()

11.1.3 加载数据集

from torch.utils.data import TensorDataset, Dataset, DataLoader, random_split
  1. 创建数据集对象

TensorDataset(*tensor)用于将内存中的多个张量包装为一个数据集对象。

x = torch.arange(12).reshape(3,4)   # 特征
y = torch.tensor([0, 1, 0])         # 标签
dataset = TensorDataset(x,y)        # 数据集对象

也可根据抽象类Dataset自定义数据集对象,切记一定要重写__len__()__getitem__()

# 自定义数据集类
class MyDataset(Dataset):
    def __init__(self, X_data, Y_data):
        """
        初始化数据集,X_data 和 Y_data 是两个列表或数组
        X_data: 输入特征
        Y_data: 目标标签
        """
        self.X_data = X_data
        self.Y_data = Y_data

    def __len__(self):
        """返回数据集的大小"""
        return len(self.X_data)

    def __getitem__(self, idx):
        """返回指定索引的数据"""
        x = torch.tensor(self.X_data[idx], dtype=torch.float32)  # 转换为 Tensor
        y = torch.tensor(self.Y_data[idx], dtype=torch.float32)
        return x, y
  1. 加载数据集

DataLoader()用于从数据集中加载数据,并支持打乱、划分批次等操作。

loader = DataLoader(
    dataset,            # 数据集对象
    batch_size=128,     # 每个批次的样本数
    shuffle=True,       # 是否打乱数据顺序
    sampler=None,       # 抽样策略
    num_workers=4,      # 用于数据加载的进程数
    pin_memory=True,    # 是否使用固定内存(CUDA)
    drop_last=False     # 是否丢弃最后的不完整批次
)
  1. 划分数据集

random_split()用于将一整个数据集分割为几个不重合的子集。

# generator用于精细化控制每个生成器的种子
# 也可设置全局随机数种子
# torch.manual_seed(132)

# 设置随机种子保证每次分割相同
generator = torch.Generator().manual_seed(42)  # 固定随机种子

train_set, val_set, test_set = random_split(
    dataset,
    [0.7, 0.15, 0.15],           # 子集大小
    generator=generator          # 传递随机数生成器
)