Python - Pytorch

Table of Contents

张量

在pytorch中,神经网络的输入、输出、以及网络的参数等都采用了“张量”的数据结构。张量与numpy中的多维数组(numpy.ndarray)非常类似,区别在于张量可以在GPU或其他硬件上运行。

  • 成员函数名称末尾加上 _ ,会采用替换模式,也即该成员函数直接修改张量对象本身。
  • .item() 返回其具体数值,该方法仅适用于大小为1(也即只含有一个元素)的张量。
  • .view() 功能为reshape该张量(保持元素个数不变),其参数为新的shape。特别地,值为-1的维度可以通过元素总数不变推导得出。
  • 定义一个张量时,可以通过参数 device 参数指定该张量存储于CPU还是GPU。也可以通过其成员函数 .to() 来转换。
  • 定义一个张量时,参数 requires_grad=True 表示需要计算并存储对于该张量的梯度。该张量的函数均带有 grad_fn 属性。对于其任一函数,当调用其成员函数 .backward() 时,该函数对于该张量自变量的梯度将会被计算并保存在该张量的 .grad 属性中。
  • 有时候需要停止跟踪记录某个张量(记为 x )的梯度(比如在更新网络参数的权重系数时),可以采用以下任一方式:
    1. 调用该张量的成员函数 x.requires_grad_(False)
    2. 调用该张量的成员函数 x.detach() 会产生一个新的张量,其属性 requires_grad=False
    3. 采用with语句,也即 with torch.no_grad():

与 numpy.ndarray 相互转换

  • 张量的成员函数 .numpy() 返回对应的 numpy.ndarray 。但是,该方法仅适用于存储在CPU的张量。
  • 函数 torch.from_numpy() 可以将一个 numpy.ndarray 转换为张量。

值得注意的是,当一个张量存储在CPU上时,该张量与其对应的 numpy.ndarray 是连接着的,也即对其中一个的修改会导致另一个也会被修改。

神经网络

典型训练过程

  • 构建含有一些可学习参数(或权重)的神经网络。
  • 遍历训练数据集。
  • 让输入通过网络得到输出。
  • 计算预测误差(或损失函数)。
  • 通过反向传播,计算并存储网络各个参数的梯度。
  • 根据各个参数的梯度对其进行调整,如 weight = weight - learning_rate x gradient

正向传播与反向传播

神经网络的训练包括两个步骤

正向传播
神经网络根据输入对输出进行最佳预测。
反向传播
神经网络根据预测误差调整其参数。

当调用误差张量的成员函数 .backward() 时,开始反向传播,整个网络将被微分。Pytorch的自动差分引擎 torch.autograd 会对计算每个属性 requires_grad=True 的网络参数的梯度并将其累积在该网络参数的 .grad 属性中。所以,在反向传播开始之前,需要清空神经网络各个参数和反向传播的梯度缓冲区。

然后,需要从 torch.optim 中加载一个优化器(如随机梯度下降,SGD)。通过调用该优化器的 .step() 方法启动梯度下降,优化器会根据每个网络参数的梯度(存储于其 .grad 属性中)来对其进行调整。

定义网络

可以使用 torch.nn 包来构建神经网络。

  • 基类 torch.nn.Module 包含
    • 网络的各个层(作为类属性),如输入层、输出层、隐藏层等。
    • 成员函数 .forward
    • 成员函数 .zero_grad ,用于清空神经网络中所有参数的梯度缓冲区。相当于整个网络中属性 requires_grad=True 的张量均调用成员函数 .grad.zero_()
  • 构建神经网络可以通过定义以 torch.nn.Module 为基类的派生类完成。
  • 只需要在派生类中重载成员函数 .forwardtorch.autograd 就会自动为网络各个参数定义 .backward() 函数。
  • 所构建的神经网络的可学习参数由所述派生类的成员函数 .parameters() 返回。
  • torch.nn 包中定义了很多损失函数(如 torch.nn.MSELoss)。

下面是一个非常简单的神经网络。该神经网络只包含一个全连接层和一个激活函数relu。包括两个成员函数, TRAINTEST 。顾名思义,前者用于从训练数据集中加载数据,并训练神经网络中的可学习参数,并把所有可学习参数的最终权重(训练结果)保存在文件中;后者从权重文件中读取并加载所有可学习参数的权重,然后从测试数据集中加载数据,计算误差,从而完成对训练结果(可学习参数的权重)的测试。

class NEURAL_NETWORK(torch.nn.Module):
    def __init__(self, IN_FEATURES, OUT_FEATURES):
        super(NEURAL_NETWORK, self).__init__()
        self.FC = torch.nn.Module.Linear(IN_FEATURES, OUT_FEATURES)

    def forward(self, INPUT):
        return torch.nn.function.relu(self.FC(INPUT))

    def TRAIN(self, DATALOADER_FOR_TRAIN):
        OPT = torch.optim.Adam(self.parameters(), LEARNING_RATE)
        LEARNING_RATE_SCHEDULER = torch.optim.lr_scheduler.ExponentialLR(OPT, gamma=0.9)
        for EPOCH_INDEX in range(EPOCH_NUM):
            for SAMPLE in DATALOADER_FOR_TRAIN:
                SAMPLE = SAMPLE.to(torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu"))
                VALUE_PREDICTED = self.forward(SAMPLE)
                VALUE_IDEAL = CALC_VALUE_IDEAL(SAMPLE)
                LOSS = torch.nn.MSELoss(VALUE_PREDICTED, VALUE_IDEAL)
                LOSS.requires_grad_(True)
                OPT.zero_grad()
                LOSS.backward()
                OPT.step()
            LEARNING_RATE_SCHEDULER.step()
            torch.save(self.state_dict(), "NEURAL_NETWORK_WEIGHT.pth")

    def TEST(self, DATALOADER_FOR_TEST):
        self.load_state_dict(torch.load("NEURAL_NETWORK_WEIGHT.pth", False))
        with torch.no_grad():
            for SAMPLE in DATALOADER_FOR_TEST:
                SAMPLE = SAMPLE.to(torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu"))
                VALUE_PREDICTED = self.forward(SAMPLE)
                VALUE_IDEAL = CALC_VALUE_IDEAL(SAMPLE)
                LOSS = torch.nn.MSELoss(VALUE_PREDICTED, VALUE_IDEAL)

数据集

自定义的数据集可以通过定义以(torch.utils.data.Dataset)为基类的派生类完成。具体地,需要重载如下三个函数:

  • __init__(self): 载入数据;
  • __getitem__(self, index): 根据索引返回数据集中相应的样本点;
  • __len(self)__: 返回数据集的大小,也即数据集中样本点的数量。

Pytorch支持多种格式的数据集,下面是一个从HDF5文件中加载数据集的例子。

class DATASET(torch.utils.data.Dataset):
    def __init__(self, FILE_NAME, SAMPLE_NUM):
        super(DATASET, self).__init__()
        f = h5py.File(FILE_NAME, 'r')
        self.sample = torch.tensor(f.get("GROUP_NAME"))
        self.sample_num = SAMPLE_NUM
        f.close()

    def __getitem__(self, INDEX):
        return self.sample[INDEX]

    def __len__(self):
        return self.sample_num

数据加载器

基于自定义的数据集,可以使用类 torch.utils.data.DataLoader 来生成相应的数据加载器。如

from torch.utils.data import Dataset, DataLoader
dataloader = DataLoader(dataset=DATASET, batch_size=BATCH_SIZE, shuffle=SHUFFLE, num_workers=NUM_WORKERS)

其中,

  • DATASET 为自定义的数据集;
  • BATCH_SIZE 为每个batch中含有的样本点个数;
  • SHUFFLE 为布尔型变量,用于指定数据集是否会被随机打乱顺序;
  • NUM_WORKERS 为用于数据加载的线程个数。