Machine-Learning-Mastery-PyTorch-教程-二-

62 阅读47分钟

Machine Learning Mastery PyTorch 教程(二)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

在 PyTorch 中构建多类别分类模型

原文:machinelearningmastery.com/building-a-multiclass-classification-model-in-pytorch/

PyTorch 库用于深度学习。一些深度学习模型的应用被用来解决回归或分类问题。在本教程中,你将发现如何使用 PyTorch 来开发和评估用于多类别分类问题的神经网络模型。

完成本逐步教程后,你将知道:

  • 如何从 CSV 文件加载数据并使其对 PyTorch 可用

  • 如何准备多类别分类数据以用于神经网络建模

  • 如何使用交叉验证来评估 PyTorch 神经网络模型

用我的书籍 Deep Learning with PyTorch 来启动你的项目。它提供了自学教程可运行的代码

开始吧!

在 PyTorch 中构建多类别分类模型

图片由Cheung Yin提供。版权所有。

问题描述

在本教程中,你将使用一个标准的机器学习数据集,称为鸢尾花数据集。它是一个经过充分研究的数据集,适合用于机器学习练习。它有四个输入变量;这些变量都是数值型的,单位为厘米长度。因此,它们在类似的尺度上。每个数据样本描述了一个观察到的鸢尾花的属性。目标是使用这些测量(输入特征)来分类鸢尾花的种类(输出标签)。

数据集中有三种鸢尾花种类。因此,这是一个多*类别分类问题。多类别分类问题是特殊的,因为它们需要特殊处理来指定类别。

这个数据集来源于现代统计学之父罗纳德·费舍尔(Sir Ronald Fisher)。这是一个最著名的模式识别数据集,你可以在 95%到 97%的范围内达到模型准确率。你可以将这个作为开发深度学习模型的目标。

你可以从 UCI 机器学习库下载鸢尾花数据集,并将其放在当前工作目录中,文件名为“iris.csv”。你也可以在这里下载数据集。

加载数据集

有多种方式来读取 CSV 文件。最简单的方法可能是使用 pandas 库。在读取数据集后,你需要将其拆分为特征和标签,因为在使用前你需要进一步处理标签。与 NumPy 或 PyTorch 张量不同,pandas DataFrame 只能通过iloc按索引切片:

import pandas as pd
data = pd.read_csv("iris.csv", header=None)
X = data.iloc[:, 0:4]
y = data.iloc[:, 4:]

现在,你已经加载了数据集并将属性(即,输入特征,DataFrame 中的列)分为X,将输出变量(即,种类标签)分为单列 DataFrame y

编码分类变量

种类标签是字符串,但你希望将它们转换为数字。这是因为数值数据更容易使用。在这个数据集中,三个类别标签是Iris-setosaIris-versicolorIris-virginica。将这些标签转换为数字(即编码)的一种方法是简单地分配一个整数值,如 0、1 或 2 来替代这些标签。但存在一个问题:你不希望模型认为Iris-virginicaIris-setosaIris-versicolor的总和。实际上,在统计学中,有不同的测量等级:

  • 名义数:这些数字实际上是名字。对它们进行操作没有意义。

  • 顺序数:它们表示某物的顺序。比较大小有意义,但加法或减法没有意义。

  • 区间数:它们是测量值,如今天的年份,因此减法有意义(例如,你多大了),但零值是任意的,并没有特殊含义。

  • 比率数:类似于区间数,但零是有意义的,如长度或时间的测量。在这种情况下,减法和除法都有意义,你可以说某物是两倍长。

编码后的标签是名义性的。你不想将其误认为是区间或比率数据,但你的模型不会知道。避免这种错误的一种方法是使用独热编码,它将标签从整数转换为独热向量。独热向量是一个整数向量,但只有一个值为 1,其余的都是零。在这种情况下,你将标签转换为如下:

Iris-setosa      1   0   0
Iris-versicolor  0   1   0
Iris-virginica   0   0   1

上面是一个独热编码的二进制矩阵。你不需要手动创建它。你可以使用 scikit-learn 的LabelEncoder类将字符串一致编码为整数,或使用OneHotEncoder类将其编码为独热向量:

from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False).fit(y)
print(ohe.categories_)

y = ohe.transform(y)
print(y)

从这些,你可以看到OneHotEncoder学会了这三种类别:

[array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)]

然后,字符串标签被转换为如下的独热向量:

[[1\. 0\. 0.]
 [1\. 0\. 0.]
 [1\. 0\. 0.]
...
 [0\. 0\. 1.]
 [0\. 0\. 1.]
 [0\. 0\. 1.]]

想要开始使用 PyTorch 进行深度学习吗?

现在就接受我的免费电子邮件速成课程(包含示例代码)。

点击注册,还可以获得课程的免费 PDF 电子书版。

定义神经网络模型

现在你需要一个模型,能够接收输入并预测输出,理想情况下是以独热编码向量的形式。设计一个完美的神经网络模型没有科学依据。但要知道一点——它必须接收一个 4 特征的向量,并输出一个 3 值的向量。4 个特征对应于你在数据集中拥有的内容。3 值输出是因为我们知道独热向量有 3 个元素。中间可以有任何东西,称为“隐藏层”,因为它们既不是输入也不是输出。

最简单的情况是只有一个隐藏层。我们可以这样构建一个:

[4 inputs] -> [8 hidden neurons] -> [3 outputs]

这样的设计称为网络拓扑。你应该在输出层使用“softmax”激活函数。在公式中,这意味着:

σ(zi)=ezij=13ezj \sigma(z_i) = \dfrac{e^{z_i}}{\sum_{j=1}³ e^{z_j}}

这将对值(z1,z2,z3z_1,z_2,z_3)进行归一化,并应用非线性函数,使所有三个输出的总和为 1,并且每个值都在 0 到 1 的范围内。这使得输出看起来像一个概率向量。在输出层使用 softmax 函数是多类分类模型的标志。但在 PyTorch 中,如果你将其与适当的损失函数结合使用,可以跳过这一过程。

在 PyTorch 中,你可以按如下方式构建这样的模型:

import torch
import torch.nn as nn

class Multiclass(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 8)
        self.act = nn.ReLU()
        self.output = nn.Linear(8, 3)

    def forward(self, x):
        x = self.act(self.hidden(x))
        x = self.output(x)
        return x

model = Multiclass()

该模型的输出是三个类别的“权重”。理想情况下,模型输出将使得只有一个元素是正无穷大,其余的是负无穷大,从而对输入特征属于哪个类别形成极端对比。在不完美的情况下,正如常常发生的那样,你可以期望一个好的模型告诉你其中一个值非常大,而其他值非常小。或者如果你使用 sigmoid 函数或 softmax 函数转换这些值,一个非常接近 1,其他的非常接近 0。

在这种情况下,输出的损失度量可以简单地通过测量输出与从标签转换得到的 one-hot 向量的接近程度来计算。但通常在多类分类中,你使用分类交叉熵作为损失度量。在公式中,它是:

H(p,q)=xp(x)logq(x) H(p,q) = -\sum_x p(x) \log q(x)

这意味着,给定真实概率向量p(x)p(x)和预测概率向量q(x)q(x),相似度是每个元素xxp(x)p(x)logq(x)\log q(x)的乘积之和。one-hot 向量被视为概率向量p(x)p(x),而模型输出是q(x)q(x)。由于它是 one-hot 向量,因此只有实际类别的p(x)=1p(x)=1,其他类别的p(x)=0p(x)=0。上面的总和本质上是实际类别xxlogq(x)-\log q(x)。当q(x)=1q(x)=1时,值将为 0,而当q(x)q(x)接近 0(softmax 能产生的最小值)时,logq(x)-\log q(x)接近无穷大。

以下是如何定义损失度量的方法。PyTorch 中的CrossEntropyLoss函数将 softmax 函数与交叉熵计算结合在一起,因此你不需要在模型的输出层使用任何激活函数。你还需要一个优化器,下面选择了 Adam。

import torch.optim as optim

loss_fn = nn.<span class="sig-name descname"><span class="pre">CrossEntropyLoss</span></span>()
optimizer = optim.Adam(model.parameters(), lr=0.001)

请注意,当你定义优化器时,你还需要告诉它模型参数,因为这些是优化器将要更新的内容。

现在你需要运行训练循环来训练你的模型。最基本的,你需要在循环中包含三个步骤:前向传播、反向传播和权重更新。前向传播将输入提供给模型并获得输出。反向传播从基于模型输出的损失指标开始,并将梯度传播回输入。权重更新则基于梯度来更新权重。

最简单的训练循环可以使用 for 循环实现。但你可以利用tqdm来创建进度条可视化:

import tqdm

# convert pandas DataFrame (X) and numpy array (y) into PyTorch tensors
X = torch.tensor(X.values, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)

# training parameters
n_epochs = 200
batch_size = 5
batches_per_epoch = len(X) // batch_size

for epoch in range(n_epochs):
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            X_batch = X[start:start+batch_size]
            y_batch = y[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()

基准测试模型

模型的目标从来不是与数据集本身匹配。你希望构建机器学习模型的原因是为了准备未来你将遇到的数据,这些数据目前尚未见过。你怎么知道模型能做到这一点?你需要一个测试集。它是一个与训练时使用的数据集结构相同但分开的数据集。因此,它就像是训练过程中的未见数据,你可以将其作为基准。这种评估模型的技术被称为交叉验证

通常,你不会添加测试集,而是将获得的数据集拆分为训练集和测试集。然后,你使用测试集在最后对模型进行评估。这样的基准测试还有另一个目的:你不希望你的模型过拟合。这意味着模型对训练集学习过多,未能进行泛化。如果发生这种情况,你会发现模型在测试集上表现不好。

使用 scikit-learn 可以轻松地将数据拆分为训练集和测试集。从加载数据到进行独热编码和拆分的工作流程如下:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder

# read data and apply one-hot encoding
data = pd.read_csv("iris.csv", header=None)
X = data.iloc[:, 0:4]
y = data.iloc[:, 4:]
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False).fit(y)
y = ohe.transform(y)

# convert pandas DataFrame (X) and numpy array (y) into PyTorch tensors
X = torch.tensor(X.values, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)

# split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

参数train_size=0.7shuffle=True表示随机选择数据集中 70%的样本用于训练集,而其余部分将成为测试集。

一旦完成这一操作,你需要修改训练循环,以在训练中使用训练集,并在每个训练周期结束时使用测试集进行基准测试:

n_epochs = 200
batch_size = 5
batches_per_epoch = len(X_train) // batch_size

for epoch in range(n_epochs):
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
    y_pred = model(X_test)
    ce = loss_fn(y_pred, y_test)
    acc = (torch.argmax(y_pred, 1) == torch.argmax(y_test, 1)).float().mean()
    print(f"Epoch {epoch} validation: Cross-entropy={float(ce)}, Accuracy={float(acc)}")

这就是你完成 PyTorch 深度学习模型所需的几乎所有内容。但你可能还想做更多的事情:首先,在所有训练周期结束后,你可能希望将模型回滚到你曾经达到的最佳状态,而不是最后的状态。其次,你可能希望生成一个图表,以可视化交叉熵和准确率的进展。

这并不难做。在训练循环中,你需要跟踪测试集上的准确率,并在准确率更高时保留模型的副本。同时,记得将计算的指标保存在一个列表中。然后在训练循环结束时,你恢复你见过的最佳模型,并将指标绘制为时间序列图。

在代码中,训练循环的修改如下:

import copy
import tqdm
import numpy as np

n_epochs = 200
batch_size = 5
batches_per_epoch = len(X_train) // batch_size

best_acc = - np.inf   # init to negative infinity
best_weights = None
train_loss_hist = []
train_acc_hist = []
test_loss_hist = []
test_acc_hist = []

for epoch in range(n_epochs):
    epoch_loss = []
    epoch_acc = []
    # set model in training mode and run through each batch
    model.train()
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # compute and store metrics
            acc = (torch.argmax(y_pred, 1) == torch.argmax(y_batch, 1)).float().mean()
            epoch_loss.append(float(loss))
            epoch_acc.append(float(acc))
            bar.set_postfix(
                loss=float(loss),
                acc=float(acc)
            )
    # set model in evaluation mode and run through the test set
    model.eval()
    y_pred = model(X_test)
    ce = loss_fn(y_pred, y_test)
    acc = (torch.argmax(y_pred, 1) == torch.argmax(y_test, 1)).float().mean()
    ce = float(ce)
    acc = float(acc)
    train_loss_hist.append(np.mean(epoch_loss))
    train_acc_hist.append(np.mean(epoch_acc))
    test_loss_hist.append(ce)
    test_acc_hist.append(acc)
    if acc > best_acc:
        best_acc = acc
        best_weights = copy.deepcopy(model.state_dict())
    print(f"Epoch {epoch} validation: Cross-entropy={ce}, Accuracy={acc}")

model.load_state_dict(best_weights)

当你运行它时,你会看到类似于这样的信息,其中显示了每个周期的准确率和交叉熵损失:

Epoch 0: 100%|█████████████████████| 21/21 [00:00<00:00, 850.82batch/s, acc=0.4, loss=1.23]
Epoch 0 validation: Cross-entropy=1.10, Accuracy=60.0%
Epoch 1: 100%|████████████████████| 21/21 [00:00<00:00, 3155.53batch/s, acc=0.4, loss=1.24]
Epoch 1 validation: Cross-entropy=1.08, Accuracy=57.8%
Epoch 2: 100%|████████████████████| 21/21 [00:00<00:00, 3489.58batch/s, acc=0.4, loss=1.24]
Epoch 2 validation: Cross-entropy=1.07, Accuracy=60.0%
Epoch 3: 100%|████████████████████| 21/21 [00:00<00:00, 3312.79batch/s, acc=0.4, loss=1.22]
Epoch 3 validation: Cross-entropy=1.06, Accuracy=62.2%
...
Epoch 197: 100%|███████████████████| 21/21 [00:00<00:00, 3529.57batch/s, acc=1, loss=0.563]
Epoch 197 validation: Cross-entropy=0.61, Accuracy=97.8%
Epoch 198: 100%|███████████████████| 21/21 [00:00<00:00, 3479.10batch/s, acc=1, loss=0.563]
Epoch 198 validation: Cross-entropy=0.61, Accuracy=97.8%
Epoch 199: 100%|███████████████████| 21/21 [00:00<00:00, 3337.52batch/s, acc=1, loss=0.563]
Epoch 199 validation: Cross-entropy=0.61, Accuracy=97.8%

你在训练循环中添加了更多的行,但这是有充分理由的。在你在训练集和测试集之间切换时,切换模型的训练模式和评估模式是一种好的实践。在这个特定的模型中,没有任何变化。但对于其他一些模型,这将影响模型的行为。

你在 Python 列表中收集了度量指标。你需要小心将 PyTorch 张量(即使是标量值)转换为 Python 浮点数。这样做的目的是为了复制这个数字,以便 PyTorch 不会悄悄地更改它(例如,通过优化器)。

每个 epoch 之后,你根据测试集计算准确率,并在看到准确率更高时保存模型权重。然而,当你提取模型权重时,你应该进行深度复制;否则,当模型在下一个 epoch 中更改权重时,你将丢失它们。

最后,你可以使用 matplotlib 绘制每个 epoch 的损失和准确率,如下所示:

import matplotlib.pyplot as plt

plt.plot(train_loss_hist, label="train")
plt.plot(test_loss_hist, label="test")
plt.xlabel("epochs")
plt.ylabel("cross entropy")
plt.legend()
plt.show()

plt.plot(train_acc_hist, label="train")
plt.plot(test_acc_hist, label="test")
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.legend()
plt.show()

一个典型的结果如下:

训练和验证损失

训练和验证准确率

从图中可以看到,刚开始时,训练准确率和测试准确率都很低。这是因为你的模型出现了欠拟合,表现很差。随着你继续训练模型,准确率会提高,交叉熵损失会降低。但在某一点,训练准确率高于测试准确率,实际上,即使训练准确率提高,测试准确率也可能会平稳或下降。这是模型出现了过拟合,你不希望使用这样的模型。这就是为什么你要跟踪测试准确率,并根据测试集将模型权重恢复到最佳结果。

完整示例

将所有内容整合在一起,以下是完整代码:

import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder

# read data and apply one-hot encoding
data = pd.read_csv("iris.csv", header=None)
X = data.iloc[:, 0:4]
y = data.iloc[:, 4:]
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False).fit(y)
y = ohe.transform(y)

# convert pandas DataFrame (X) and numpy array (y) into PyTorch tensors
X = torch.tensor(X.values, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)

# split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

class Multiclass(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 8)
        self.act = nn.ReLU()
        self.output = nn.Linear(8, 3)

    def forward(self, x):
        x = self.act(self.hidden(x))
        x = self.output(x)
        return x

# loss metric and optimizer
model = Multiclass()
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# prepare model and training parameters
n_epochs = 200
batch_size = 5
batches_per_epoch = len(X_train) // batch_size

best_acc = - np.inf   # init to negative infinity
best_weights = None
train_loss_hist = []
train_acc_hist = []
test_loss_hist = []
test_acc_hist = []

# training loop
for epoch in range(n_epochs):
    epoch_loss = []
    epoch_acc = []
    # set model in training mode and run through each batch
    model.train()
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # compute and store metrics
            acc = (torch.argmax(y_pred, 1) == torch.argmax(y_batch, 1)).float().mean()
            epoch_loss.append(float(loss))
            epoch_acc.append(float(acc))
            bar.set_postfix(
                loss=float(loss),
                acc=float(acc)
            )
    # set model in evaluation mode and run through the test set
    model.eval()
    y_pred = model(X_test)
    ce = loss_fn(y_pred, y_test)
    acc = (torch.argmax(y_pred, 1) == torch.argmax(y_test, 1)).float().mean()
    ce = float(ce)
    acc = float(acc)
    train_loss_hist.append(np.mean(epoch_loss))
    train_acc_hist.append(np.mean(epoch_acc))
    test_loss_hist.append(ce)
    test_acc_hist.append(acc)
    if acc > best_acc:
        best_acc = acc
        best_weights = copy.deepcopy(model.state_dict())
    print(f"Epoch {epoch} validation: Cross-entropy={ce:.2f}, Accuracy={acc*100:.1f}%")

# Restore best model
model.load_state_dict(best_weights)

# Plot the loss and accuracy
plt.plot(train_loss_hist, label="train")
plt.plot(test_loss_hist, label="test")
plt.xlabel("epochs")
plt.ylabel("cross entropy")
plt.legend()
plt.show()

plt.plot(train_acc_hist, label="train")
plt.plot(test_acc_hist, label="test")
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.legend()
plt.show()

总结

在这篇文章中,你了解了如何使用 PyTorch 开发和评估用于多类分类的神经网络。

完成本教程后,你学到了:

  • 如何加载数据并将其转换为 PyTorch 张量

  • 如何使用独热编码准备多类分类数据以进行建模

  • 如何使用 PyTorch 定义一个用于多类分类的深度学习模型

  • 如何衡量模型输出与多类分类预期结果的相似度

  • 如何在 PyTorch 模型上运行训练循环并收集评估指标

在 PyTorch 中构建回归模型

原文:machinelearningmastery.com/building-a-regression-model-in-pytorch/

PyTorch 库用于深度学习。深度学习模型的一些应用是解决回归或分类问题。

在这篇文章中,你将发现如何使用 PyTorch 开发和评估回归问题的神经网络模型。

完成这篇文章后,你将了解:

  • 如何从 scikit-learn 加载数据并将其调整为 PyTorch 模型

  • 如何使用 PyTorch 创建一个回归问题的神经网络

  • 如何通过数据准备技术提高模型性能

启动你的项目,参考我的书《使用 PyTorch 的深度学习》。它提供了自学教程可运行的代码

让我们开始吧。

在 PyTorch 中构建回归模型,照片由Sam Deng提供。保留部分权利。

数据集描述

本教程中使用的数据集是加州住房数据集

这是一个描述加州地区中位数房价的数据集。每个数据样本是一个普查街区组。目标变量是 1990 年每 100,000 美元的中位数房价,共有 8 个输入特征,每个特征描述房子的某一方面。它们分别是:

  • 中位数收入:街区组的中位数收入

  • 房屋年龄:街区组中位数房龄

  • 平均房间数:每户家庭的平均房间数

  • 平均卧室数:每户家庭的平均卧室数

  • 人口:街区组人口

  • 平均家庭成员数:每户家庭的平均成员数

  • 纬度:街区组中心纬度

  • 经度:街区组中心经度

这些数据很特殊,因为输入数据的尺度差异很大。例如,每栋房子的房间数通常很少,但每个街区的居民数通常很大。此外,大多数特征应该是正数,但经度必须是负数(因为这是关于加州的)。处理这种数据多样性对某些机器学习模型而言是一个挑战。

你可以从 scikit-learn 获取数据集,scikit-learn 是从互联网实时下载的:

from sklearn.datasets import fetch_california_housing

data = fetch_california_housing()
print(data.feature_names)

X, y = data.data, data.target

构建模型并训练

这是一个回归问题。与分类问题不同,输出变量是连续值。在神经网络中,通常在输出层使用线性激活(即没有激活),使得理论上输出范围可以是从负无穷到正无穷。

对于回归问题,你不应该期望模型完美预测值。因此,你应该关注预测值与实际值的接近程度。你可以使用均方误差(MSE)或平均绝对误差(MAE)作为损失度量。但你也可能对均方根误差(RMSE)感兴趣,因为它与输出变量具有相同的单位。

让我们尝试传统的神经网络设计,即金字塔结构。金字塔结构是使每一层中的神经元数量随着网络到达输出层而减少。输入特征数量是固定的,但你可以在第一隐藏层设置大量的神经元,并逐渐减少后续层中的数量。由于数据集中只有一个目标,最终层应该仅输出一个值。

设计如下:

import torch.nn as nn

# Define the model
model = nn.Sequential(
    nn.Linear(8, 24),
    nn.ReLU(),
    nn.Linear(24, 12),
    nn.ReLU(),
    nn.Linear(12, 6),
    nn.ReLU(),
    nn.Linear(6, 1)
)

要训练这个网络,你需要定义一个损失函数。MSE 是一个合理的选择。你还需要一个优化器,例如 Adam。

import torch.nn as nn
import torch.optim as optim

# loss function and optimizer
loss_fn = nn.MSELoss()  # mean square error
optimizer = optim.Adam(model.parameters(), lr=0.0001)

要训练这个模型,你可以使用你常用的训练循环。为了获得一个评估分数以确保模型有效,你需要将数据分为训练集和测试集。你可能还需要通过跟踪测试集的均方误差(MSE)来避免过拟合。以下是带有训练-测试拆分的训练循环:

import copy
import numpy as np
import torch
import tqdm
from sklearn.model_selection import train_test_split

# train-test split of the dataset
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)

# training parameters
n_epochs = 100   # number of epochs to run
batch_size = 10  # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)

# Hold the best model
best_mse = np.inf   # init to infinity
best_weights = None
history = []

# training loop
for epoch in range(n_epochs):
    model.train()
    with tqdm.tqdm(batch_start, unit="batch", mininterval=0, disable=True) as bar:
        bar.set_description(f"Epoch {epoch}")
        for start in bar:
            # take a batch
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(mse=float(loss))
    # evaluate accuracy at end of each epoch
    model.eval()
    y_pred = model(X_test)
    mse = loss_fn(y_pred, y_test)
    mse = float(mse)
    history.append(mse)
    if mse < best_mse:
        best_mse = mse
        best_weights = copy.deepcopy(model.state_dict())

# restore model and return best accuracy
model.load_state_dict(best_weights)

在训练循环中,tqdm用于设置进度条,在每次迭代步骤中,计算并报告 MSE。你可以通过将tqdm参数disable设置为False来查看 MSE 的变化情况。

注意,在训练循环中,每个周期是用训练集运行前向和反向步骤几次以优化模型权重,在周期结束时,使用测试集评估模型。测试集的 MSE 被记在history列表中。它也是评估模型的指标,最佳的模型存储在变量best_weights中。

运行完这个,你将得到恢复的最佳模型,并将最佳 MSE 存储在变量best_mse中。注意,均方误差是预测值与实际值之间差异平方的平均值。它的平方根,即 RMSE,可以视为平均差异,数值上更有用。

下面,你可以展示 MSE 和 RMSE,并绘制 MSE 的历史记录。它应该随着周期的增加而减少。

print("MSE: %.2f" % best_mse)
print("RMSE: %.2f" % np.sqrt(best_mse))
plt.plot(history)
plt.show()

这个模型产生了:

MSE: 0.47
RMSE: 0.68

MSE 图形如下所示。

将所有内容整合在一起,以下是完整的代码。

import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing

# Read data
data = fetch_california_housing()
X, y = data.data, data.target

# train-test split for model evaluation
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)

# Define the model
model = nn.Sequential(
    nn.Linear(8, 24),
    nn.ReLU(),
    nn.Linear(24, 12),
    nn.ReLU(),
    nn.Linear(12, 6),
    nn.ReLU(),
    nn.Linear(6, 1)
)

# loss function and optimizer
loss_fn = nn.MSELoss()  # mean square error
optimizer = optim.Adam(model.parameters(), lr=0.0001)

n_epochs = 100   # number of epochs to run
batch_size = 10  # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)

# Hold the best model
best_mse = np.inf   # init to infinity
best_weights = None
history = []

for epoch in range(n_epochs):
    model.train()
    with tqdm.tqdm(batch_start, unit="batch", mininterval=0, disable=True) as bar:
        bar.set_description(f"Epoch {epoch}")
        for start in bar:
            # take a batch
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(mse=float(loss))
    # evaluate accuracy at end of each epoch
    model.eval()
    y_pred = model(X_test)
    mse = loss_fn(y_pred, y_test)
    mse = float(mse)
    history.append(mse)
    if mse < best_mse:
        best_mse = mse
        best_weights = copy.deepcopy(model.state_dict())

# restore model and return best accuracy
model.load_state_dict(best_weights)
print("MSE: %.2f" % best_mse)
print("RMSE: %.2f" % np.sqrt(best_mse))
plt.plot(history)
plt.show()

想开始使用 PyTorch 进行深度学习吗?

现在就来参加我的免费邮件速成课程(包含示例代码)。

点击注册,还可以免费获得课程的 PDF 电子书版本。

通过预处理改进模型

在上述中,你看到 RMSE 是 0.68。实际上,通过在训练之前对数据进行打磨,RMSE 是容易改善的。这个数据集的问题在于特征的多样性:有些特征范围狭窄,有些特征范围很宽。还有一些特征是小的但正值,有些则是非常负值。这确实对大多数机器学习模型来说并不是很好。

改进的一种方法是应用标准缩放器。这就是将每个特征转换为其标准分数。换句话说,对于每个特征xx,你将其替换为

z=xxˉσx z = \frac{x – \bar{x}}{\sigma_x}

其中xˉ\bar{x}xx的均值,而σx\sigma_x是标准差。通过这种方式,每个转换后的特征都围绕 0 进行中心化,并且在一个窄范围内,大约 70%的样本在-1 到+1 之间。这可以帮助机器学习模型收敛。

你可以使用 scikit-learn 中的标准缩放器。以下是你应如何修改上述代码的数据准备部分:

import torch
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler

# Read data
data = fetch_california_housing()
X, y = data.data, data.target

# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)

# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)

请注意,标准缩放器在训练-测试拆分后应用。上面的StandardScaler是在训练集上进行拟合,但在训练集和测试集上都进行应用。你必须避免将标准缩放器应用于所有数据,因为测试集中的信息不应泄露给模型。否则,你会引入数据泄露

除此之外,几乎没有任何变化:你仍然有 8 个特征(只不过它们的值不同)。你仍然使用相同的训练循环。如果你用缩放后的数据训练模型,你应该会看到 RMSE 有所改善,例如:

MSE: 0.29
RMSE: 0.54

尽管 MSE 的历史变化形状类似,但 y 轴显示出在缩放后效果确实更好:

然而,你需要在最后小心:当你使用训练好的模型并应用到新数据时,你应该在将输入数据输入模型之前应用缩放器。也就是说,推理应按如下方式进行:

model.eval()
with torch.no_grad():
    # Test out inference with 5 samples from the original test set
    for i in range(5):
        X_sample = X_test_raw[i: i+1]
        X_sample = scaler.transform(X_sample)
        X_sample = torch.tensor(X_sample, dtype=torch.float32)
        y_pred = model(X_sample)
        print(f"{X_test_raw[i]} -> {y_pred[0].numpy()} (expected {y_test[i].numpy()})")

以下是完整的代码:

import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler

# Read data
data = fetch_california_housing()
X, y = data.data, data.target

# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)

# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)

# Define the model
model = nn.Sequential(
    nn.Linear(8, 24),
    nn.ReLU(),
    nn.Linear(24, 12),
    nn.ReLU(),
    nn.Linear(12, 6),
    nn.ReLU(),
    nn.Linear(6, 1)
)

# loss function and optimizer
loss_fn = nn.MSELoss()  # mean square error
optimizer = optim.Adam(model.parameters(), lr=0.0001)

n_epochs = 100   # number of epochs to run
batch_size = 10  # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)

# Hold the best model
best_mse = np.inf   # init to infinity
best_weights = None
history = []

for epoch in range(n_epochs):
    model.train()
    with tqdm.tqdm(batch_start, unit="batch", mininterval=0, disable=True) as bar:
        bar.set_description(f"Epoch {epoch}")
        for start in bar:
            # take a batch
            X_batch = X_train[start:start+batch_size]
            y_batch = y_train[start:start+batch_size]
            # forward pass
            y_pred = model(X_batch)
            loss = loss_fn(y_pred, y_batch)
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(mse=float(loss))
    # evaluate accuracy at end of each epoch
    model.eval()
    y_pred = model(X_test)
    mse = loss_fn(y_pred, y_test)
    mse = float(mse)
    history.append(mse)
    if mse < best_mse:
        best_mse = mse
        best_weights = copy.deepcopy(model.state_dict())

# restore model and return best accuracy
model.load_state_dict(best_weights)
print("MSE: %.2f" % best_mse)
print("RMSE: %.2f" % np.sqrt(best_mse))
plt.plot(history)
plt.show()

model.eval()
with torch.no_grad():
    # Test out inference with 5 samples
    for i in range(5):
        X_sample = X_test_raw[i: i+1]
        X_sample = scaler.transform(X_sample)
        X_sample = torch.tensor(X_sample, dtype=torch.float32)
        y_pred = model(X_sample)
        print(f"{X_test_raw[i]} -> {y_pred[0].numpy()} (expected {y_test[i].numpy()})")

当然,模型仍有改进的空间。一个方法是将目标呈现为对数尺度,或者等效地,使用平均绝对百分比误差(MAPE)作为损失函数。这是因为目标变量是房价,它的范围很广。对于相同的误差幅度,低价房更容易出现问题。你可以修改上述代码以生成更好的预测,这也是你的练习。

总结

在这篇文章中,你发现了使用 PyTorch 构建回归模型的方法。

你学会了如何通过 PyTorch 逐步解决回归问题,具体包括:

  • 如何加载和准备数据以供 PyTorch 使用

  • 如何创建神经网络模型并选择回归的损失函数

  • 如何通过应用标准缩放器提高模型的准确性

在 PyTorch 中构建单层神经网络

原文:machinelearningmastery.com/building-a-single-layer-neural-network-in-pytorch/

神经网络是一组彼此相连的神经元节点。这些神经元不仅与相邻的神经元连接,还与距离更远的神经元连接。

神经网络的主要思想是每个层中的神经元有一个或多个输入值,并通过对输入应用某些数学函数来生成输出值。一层中的神经元的输出成为下一层的输入。

单层神经网络是一种人工神经网络,其中输入层和输出层之间只有一个隐藏层。这是深度学习流行之前的经典架构。在本教程中,你将有机会构建一个仅具有一个隐藏层的神经网络。特别地,你将学习:

  • 如何在 PyTorch 中构建单层神经网络。

  • 如何使用 PyTorch 训练单层神经网络。

  • 如何使用单层神经网络对一维数据进行分类。

快速启动你的项目,参考我的书籍 Deep Learning with PyTorch。它提供了自学教程有效代码

让我们开始吧。

在 PyTorch 中构建单层神经网络。

图片由 Tim Cheung 提供。保留部分权利。

概述

本教程分为三个部分,分别是

    • 准备数据集

    • 构建模型

    • 训练模型

准备数据

神经网络简单来说是一个用某些参数近似其他函数的函数。让我们生成一些数据,看看我们的单层神经网络如何将函数近似化以使数据线性可分。稍后在本教程中,你将可视化训练过程中函数的重叠情况。

import torch
import matplotlib.pyplot as plt

# generate synthetic the data
X = torch.arange(-30, 30, 1).view(-1, 1).type(torch.FloatTensor)
Y = torch.zeros(X.shape[0])
Y[(X[:, 0] <= -10)] = 1.0
Y[(X[:, 0] > -10) & (X[:, 0] < 10)] = 0.5
Y[(X[:, 0] > 10)] = 0

数据使用 matplotlib 绘制后,呈现如下图。

...
plt.plot(X, Y)
plt.show()

想要开始使用 PyTorch 进行深度学习吗?

立即参加我的免费电子邮件速成课程(附示例代码)。

点击注册并获得课程的免费 PDF 电子书版本。

使用 nn.Module 构建模型

接下来,让我们使用 nn.Module 构建自定义的单层神经网络模块。如果需要更多关于 nn.Module 的信息,请查看之前的教程。

该神经网络包括一个输入层、一个具有两个神经元的隐藏层和一个输出层。在每一层之后,应用一个 sigmoid 激活函数。PyTorch 中还提供了其他激活函数,但该网络的经典设计是使用 sigmoid 函数。

这是你的单层神经网络的代码示例。

...

# Define the class for single layer NN
class one_layer_net(torch.nn.Module):    
    # Constructor
    def __init__(self, input_size, hidden_neurons, output_size):
        super(one_layer_net, self).__init__()
        # hidden layer 
        self.linear_one = torch.nn.Linear(input_size, hidden_neurons)
        self.linear_two = torch.nn.Linear(hidden_neurons, output_size) 
        # defining layers as attributes
        self.layer_in = None
        self.act = None
        self.layer_out = None
    # prediction function
    def forward(self, x):
        self.layer_in = self.linear_one(x)
        self.act = torch.sigmoid(self.layer_in)
        self.layer_out = self.linear_two(self.act)
        y_pred = torch.sigmoid(self.linear_two(self.act))
        return y_pred

让我们也实例化一个模型对象。

# create the model 
model = one_layer_net(1, 2, 1)  # 2 represents two neurons in one hidden layer

训练模型

在开始训练循环之前,让我们为模型定义损失函数和优化器。您将编写一个用于交叉熵损失的损失函数,并使用随机梯度下降进行参数优化。

def criterion(y_pred, y):
    out = -1 * torch.mean(y * torch.log(y_pred) + (1 - y) * torch.log(1 - y_pred))
    return out
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

现在你已经有了所有组件来训练模型。让我们训练 5000 个 epochs。您将看到神经网络在每 1000 个 epochs 后如何逼近函数的图表。

# Define the training loop
epochs=5000
cost = []
total=0
for epoch in range(epochs):
    total=0
    epoch = epoch + 1
    for x, y in zip(X, Y):
        yhat = model(x)
        loss = criterion(yhat, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        # get total loss 
        total+=loss.item() 
    cost.append(total)
    if epoch % 1000 == 0:
        print(str(epoch)+ " " + "epochs done!") # visualze results after every 1000 epochs   
        # plot the result of function approximator
        plt.plot(X.numpy(), model(X).detach().numpy())
        plt.plot(X.numpy(), Y.numpy(), 'm')
        plt.xlabel('x')
        plt.show()

在 1000 个 epochs 之后,模型近似了以下函数:

但是在 5000 个 epochs 之后,它改进到以下结果:

从中可以看出,蓝色的近似值更接近紫色的数据。正如你所见,神经网络相当好地近似了这些函数。如果函数更复杂,你可能需要更多的隐藏层或更多的隐藏层神经元,即一个更复杂的模型。

让我们也绘制图表,看看训练过程中损失是如何减少的。

# plot the cost
plt.plot(cost)
plt.xlabel('epochs')
plt.title('cross entropy loss')
plt.show()

你应该会看到:

将所有内容整合在一起,以下是完整的代码:

import torch
import matplotlib.pyplot as plt

# generate synthetic the data
X = torch.arange(-30, 30, 1).view(-1, 1).type(torch.FloatTensor)
Y = torch.zeros(X.shape[0])
Y[(X[:, 0] <= -10)] = 1.0
Y[(X[:, 0] > -10) & (X[:, 0] < 10)] = 0.5
Y[(X[:, 0] > 10)] = 0

plt.plot(X, Y)
plt.show()

# Define the class for single layer NN
class one_layer_net(torch.nn.Module):    
    # Constructor
    def __init__(self, input_size, hidden_neurons, output_size):
        super(one_layer_net, self).__init__()
        # hidden layer 
        self.linear_one = torch.nn.Linear(input_size, hidden_neurons)
        self.linear_two = torch.nn.Linear(hidden_neurons, output_size) 
        # defining layers as attributes
        self.layer_in = None
        self.act = None
        self.layer_out = None
    # prediction function
    def forward(self, x):
        self.layer_in = self.linear_one(x)
        self.act = torch.sigmoid(self.layer_in)
        self.layer_out = self.linear_two(self.act)
        y_pred = torch.sigmoid(self.linear_two(self.act))
        return y_pred

# create the model 
model = one_layer_net(1, 2, 1)  # 2 represents two neurons in one hidden layer

def criterion(y_pred, y):
    out = -1 * torch.mean(y * torch.log(y_pred) + (1 - y) * torch.log(1 - y_pred))
    return out
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# Define the training loop
epochs=5000
cost = []
total=0
for epoch in range(epochs):
    total=0
    epoch = epoch + 1
    for x, y in zip(X, Y):
        yhat = model(x)
        loss = criterion(yhat, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        # get total loss 
        total+=loss.item() 
    cost.append(total)
    if epoch % 1000 == 0:
        print(str(epoch)+ " " + "epochs done!") # visualze results after every 1000 epochs   
        # plot the result of function approximator
        plt.plot(X.numpy(), model(X).detach().numpy())
        plt.plot(X.numpy(), Y.numpy(), 'm')
        plt.xlabel('x')
        plt.show()

# plot the cost
plt.plot(cost)
plt.xlabel('epochs')
plt.title('cross entropy loss')
plt.show()

总结

在本教程中,您学习了如何构建和训练神经网络并估计函数。特别是,您学到了:

  • 如何在 PyTorch 中构建一个单层神经网络。

  • 如何使用 PyTorch 训练单层神经网络。

  • 如何使用单层神经网络对一维数据进行分类。

在 PyTorch 中为图像构建 Softmax 分类器

原文:machinelearningmastery.com/building-a-softmax-classifier-for-images-in-pytorch/

Softmax 分类器是监督学习中的一种分类器。它是深度学习网络中的重要构建模块,也是深度学习从业者中最受欢迎的选择。

Softmax 分类器适用于多类分类,它为每个类别输出概率。

本教程将教你如何为图像数据构建一个 Softmax 分类器。你将学习如何准备数据集,然后学习如何使用 PyTorch 实现 Softmax 分类器。特别是,你将学习:

  • 关于 Fashion-MNIST 数据集。

  • 如何在 PyTorch 中使用 Softmax 分类器处理图像。

  • 如何在 PyTorch 中构建和训练一个多类图像分类器。

  • 如何在模型训练后绘制结果。

启动你的项目,参考我的书籍 Deep Learning with PyTorch。它提供了 自学教程可用代码

让我们开始吧。

在 PyTorch 中为图像构建 Softmax 分类器。

图片来自 Joshua J. Cotten。保留所有权利。

概述

本教程分为三个部分:

    • 准备数据集

    • 构建模型

    • 训练模型

准备数据集

你将在这里使用的数据集是 Fashion-MNIST。它是一个经过预处理和良好组织的数据集,包含 70,000 张图像,其中 60,000 张用于训练数据,10,000 张用于测试数据。

数据集中的每个示例是一个 28×2828\times 28 像素的灰度图像,总像素数为 784。数据集有 10 个类别,每张图像被标记为一个时尚项目,并与从 0 到 9 的整数标签相关联。

该数据集可以从 torchvision 中加载。为了加快训练速度,我们将数据集限制为 4000 个样本:

from torchvision import datasets

train_data = datasets.FashionMNIST('data', train=True, download=True)
train_data = list(train_data)[:4000]

当你第一次获取 fashion-MNIST 数据集时,你会看到 PyTorch 从互联网下载它并保存到名为 data 的本地目录中:

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]
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]
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]
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]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw

上述数据集 train_data 是一个元组列表,每个元组包含一个图像(以 Python Imaging Library 对象的形式)和一个整数标签。

让我们用 matplotlib 绘制数据集中的前 10 张图像。

import matplotlib.pyplot as plt

# plot the first 10 images in the training data
for i, (img, label) in enumerate(train_data[:10]):
    plt.subplot(4, 3, i+1)
    plt.imshow(img, cmap="gray")

plt.show()

你应该能看到类似以下的图像:

PyTorch 需要数据集为 PyTorch 张量。因此,你将通过应用转换,使用 PyTorch transforms 中的 ToTensor() 方法来转换这些数据。此转换可以在 torchvision 的数据集 API 中透明地完成:

from torchvision import datasets, transforms

# download and apply the transform
train_data = datasets.FashionMNIST('data', train=True, download=True, transform=transforms.ToTensor())
train_data = list(train_data)[:4000]

在继续模型之前,我们还将数据拆分为训练集和验证集,其中前 3500 张图像为训练集,其余的为验证集。通常我们希望在拆分之前打乱数据,但为了简洁起见,我们可以跳过这一步。

# splitting the dataset into train and validation sets
train_data, val_data = train_data[:3500], train_data[3500:]

想开始使用 PyTorch 进行深度学习吗?

现在就来参加我的免费电子邮件速成课程(包含示例代码)。

点击注册,还可以获得课程的免费 PDF 电子书版。

构建模型

为了构建一个用于图像分类的自定义 softmax 模块,我们将使用来自 PyTorch 库的 nn.Module。为了简化起见,我们只构建一个层的模型。

import torch

# build custom softmax module
class Softmax(torch.nn.Module):
    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, n_outputs)

    def forward(self, x):
        pred = self.linear(x)
        return pred

现在,让我们实例化我们的模型对象。它接受一个一维向量作为输入,并对 10 个不同的类别进行预测。我们还要检查一下参数的初始化情况。

# call Softmax Classifier
model_softmax = Softmax(784, 10)
print(model_softmax.state_dict())

你应该会看到模型的权重是随机初始化的,但它的形状应类似于以下:

OrderedDict([('linear.weight',
              tensor([[-0.0344,  0.0334, -0.0278,  ..., -0.0232,  0.0198, -0.0123],
                      [-0.0274, -0.0048, -0.0337,  ..., -0.0340,  0.0274, -0.0091],
                      [ 0.0078, -0.0057,  0.0178,  ..., -0.0013,  0.0322, -0.0219],
                      ...,
                      [ 0.0158, -0.0139, -0.0220,  ..., -0.0054,  0.0284, -0.0058],
                      [-0.0142, -0.0268,  0.0172,  ...,  0.0099, -0.0145, -0.0154],
                      [-0.0172, -0.0224,  0.0016,  ...,  0.0107,  0.0147,  0.0252]])),
             ('linear.bias',
              tensor([-0.0156,  0.0061,  0.0285,  0.0065,  0.0122, -0.0184, -0.0197,  0.0128,
                       0.0251,  0.0256]))])

训练模型

你将使用随机梯度下降来训练模型,并结合交叉熵损失。让我们将学习率固定为 0.01。为了帮助训练,我们还将数据加载到数据加载器中,包括训练集和验证集,并将批量大小设置为 16。

class Softmax(torch.nn.Module):
    "custom softmax module"
    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, n_outputs)

    def forward(self, x):
        pred = self.linear(x)
        return pred

现在,让我们将所有内容结合起来,并训练我们的模型 200 个周期。

epochs = 200
Loss = []
acc = []
for epoch in range(epochs):
    for i, (images, labels) in enumerate(train_loader):
        optimizer.zero_grad()
        outputs = model_softmax(images.view(-1, 28*28))
        loss = criterion(outputs, labels)
        # Loss.append(loss.item())
        loss.backward()
        optimizer.step()
    Loss.append(loss.item())
    correct = 0
    for images, labels in val_loader:
        outputs = model_softmax(images.view(-1, 28*28))
        _, predicted = torch.max(outputs.data, 1)
        correct += (predicted == labels).sum()
    accuracy = 100 * (correct.item()) / len(val_data)
    acc.append(accuracy)
    if epoch % 10 == 0:
        print('Epoch: {}. Loss: {}. Accuracy: {}'.format(epoch, loss.item(), accuracy))

你应该会看到每 10 个周期打印一次进度:

Epoch: 0\. Loss: 1.0223602056503296\. Accuracy: 67.2
Epoch: 10\. Loss: 0.5806267857551575\. Accuracy: 78.4
Epoch: 20\. Loss: 0.5087125897407532\. Accuracy: 81.2
Epoch: 30\. Loss: 0.46658074855804443\. Accuracy: 82.0
Epoch: 40\. Loss: 0.4357391595840454\. Accuracy: 82.4
Epoch: 50\. Loss: 0.4111904203891754\. Accuracy: 82.8
Epoch: 60\. Loss: 0.39078089594841003\. Accuracy: 83.4
Epoch: 70\. Loss: 0.37331104278564453\. Accuracy: 83.4
Epoch: 80\. Loss: 0.35801735520362854\. Accuracy: 83.4
Epoch: 90\. Loss: 0.3443795442581177\. Accuracy: 84.2
Epoch: 100\. Loss: 0.33203184604644775\. Accuracy: 84.2
Epoch: 110\. Loss: 0.32071244716644287\. Accuracy: 84.0
Epoch: 120\. Loss: 0.31022894382476807\. Accuracy: 84.2
Epoch: 130\. Loss: 0.30044111609458923\. Accuracy: 84.4
Epoch: 140\. Loss: 0.29124370217323303\. Accuracy: 84.6
Epoch: 150\. Loss: 0.28255513310432434\. Accuracy: 84.6
Epoch: 160\. Loss: 0.2743147313594818\. Accuracy: 84.4
Epoch: 170\. Loss: 0.26647457480430603\. Accuracy: 84.2
Epoch: 180\. Loss: 0.2589966356754303\. Accuracy: 84.2
Epoch: 190\. Loss: 0.2518490254878998\. Accuracy: 84.2

正如你所见,模型的准确率在每个周期后都会增加,而损失则会减少。在这里,你为 softmax 图像分类器取得的准确率大约是 85%。如果你使用更多的数据并增加训练周期数,准确率可能会大大提高。现在让我们看看损失和准确率的图表。

首先是损失图表:

plt.plot(Loss)
plt.xlabel("no. of epochs")
plt.ylabel("total loss")
plt.show()

它应该类似于以下:

这里是模型准确率的图表:

plt.plot(acc)
plt.xlabel("no. of epochs")
plt.ylabel("total accuracy")
plt.show()

它类似于下面的样子:

将所有内容整合起来,以下是完整的代码:

import torch
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torchvision import datasets

# download and apply the transform
train_data = datasets.FashionMNIST('data', train=True, download=True, transform=transforms.ToTensor())
train_data = list(train_data)[:4000]

# splitting the dataset into train and validation sets
train_data, val_data = train_data[:3500], train_data[3500:]

# build custom softmax module
class Softmax(torch.nn.Module):
    def __init__(self, n_inputs, n_outputs):
        super(Softmax, self).__init__()
        self.linear = torch.nn.Linear(n_inputs, n_outputs)

    def forward(self, x):
        pred = self.linear(x)
        return pred

# call Softmax Classifier
model_softmax = Softmax(784, 10)
model_softmax.state_dict()

# define loss, optimizier, and dataloader for train and validation sets
optimizer = torch.optim.SGD(model_softmax.parameters(), lr = 0.01)
criterion = torch.nn.CrossEntropyLoss()
batch_size = 16
train_loader = DataLoader(dataset = train_data, batch_size = batch_size)
val_loader = DataLoader(dataset = val_data, batch_size = batch_size)

epochs = 200
Loss = []
acc = []
for epoch in range(epochs):
    for i, (images, labels) in enumerate(train_loader):
        optimizer.zero_grad()
        outputs = model_softmax(images.view(-1, 28*28))
        loss = criterion(outputs, labels)
        # Loss.append(loss.item())
        loss.backward()
        optimizer.step()
    Loss.append(loss.item())
    correct = 0
    for images, labels in val_loader:
        outputs = model_softmax(images.view(-1, 28*28))
        _, predicted = torch.max(outputs.data, 1)
        correct += (predicted == labels).sum()
    accuracy = 100 * (correct.item()) / len(val_data)
    acc.append(accuracy)
    if epoch % 10 == 0:
        print('Epoch: {}. Loss: {}. Accuracy: {}'.format(epoch, loss.item(), accuracy))

plt.plot(Loss)
plt.xlabel("no. of epochs")
plt.ylabel("total loss")
plt.show()

plt.plot(acc)
plt.xlabel("no. of epochs")
plt.ylabel("total accuracy")
plt.show()

总结

在本教程中,你学习了如何为图像数据构建 softmax 分类器。特别是,你学到了:

  • 关于 Fashion-MNIST 数据集。

  • 如何在 PyTorch 中使用 softmax 分类器进行图像分类。

  • 如何在 PyTorch 中构建和训练一个多类别图像分类器。

  • 如何在模型训练后绘制结果。

在 PyTorch 中构建多层感知器模型

原文:machinelearningmastery.com/building-multilayer-perceptron-models-in-pytorch/

PyTorch 库用于深度学习。深度学习确实只是大规模神经网络或多层感知器网络的另一种名称。在其最简单的形式中,多层感知器是串联在一起的一系列层。在这篇文章中,你将发现可以用来创建神经网络和简单深度学习模型的简单组件。

用我的书籍《PyTorch 深度学习》启动你的项目。它提供了自学教程可运行的代码

让我们开始吧!

在 PyTorch 中构建多层感知器模型

图片来源:Sharon Cho。部分权利保留。

概述

本文分为六部分,它们是:

  • PyTorch 中的神经网络模型

  • 模型输入

  • 层、激活和层属性

  • 损失函数和模型优化器

  • 模型训练与推理

  • 模型检查

PyTorch 中的神经网络模型

PyTorch 可以做很多事情,但最常见的用例是构建深度学习模型。最简单的模型可以使用Sequential类来定义,它只是一个线性堆叠的层串联在一起。你可以创建一个Sequential模型,并一次性定义所有层,例如:

import torch
import torch.nn as nn

model = nn.Sequential(...)

你应该在处理顺序中将所有层定义在括号内,从输入到输出。例如:

model = nn.Sequential(
    nn.Linear(764, 100),
    nn.ReLU(),
    nn.Linear(100, 50),
    nn.ReLU(),
    nn.Linear(50, 10),
    nn.Sigmoid()
)

使用Sequential的另一种方式是传入一个有序字典,你可以为每一层分配名称:

from collections import OrderedDict
import torch.nn as nn

model = nn.Sequential(OrderedDict([
    ('dense1', nn.Linear(764, 100)),
    ('act1', nn.ReLU()),
    ('dense2', nn.Linear(100, 50)),
    ('act2', nn.ReLU()),
    ('output', nn.Linear(50, 10)),
    ('outact', nn.Sigmoid()),
]))

如果你想逐层构建,而不是一次性完成所有工作,你可以按照以下方式进行:

model = nn.Sequential()
model.add_module("dense1", nn.Linear(8, 12))
model.add_module("act1", nn.ReLU())
model.add_module("dense2", nn.Linear(12, 8))
model.add_module("act2", nn.ReLU())
model.add_module("output", nn.Linear(8, 1))
model.add_module("outact", nn.Sigmoid())

在需要根据某些条件构建模型的复杂情况下,你会发现这些内容非常有帮助。

模型输入

模型中的第一层提示了输入的形状。在上面的示例中,你有nn.Linear(764, 100)作为第一层。根据你使用的不同层类型,参数可能有不同的含义。但在这个例子中,它是一个Linear层(也称为密集层或全连接层),这两个参数告诉该层的输入和输出维度。

请注意,批次的大小是隐式的。在这个示例中,你应该将形状为(n, 764)的 PyTorch 张量传入该层,并期望返回形状为(n, 100)的张量,其中n是批次的大小。

想开始使用 PyTorch 进行深度学习吗?

现在就参加我的免费电子邮件速成课程(含示例代码)。

点击注册并获得课程的免费 PDF 电子书版本。

层、激活和层属性

在 PyTorch 中定义了许多种类的神经网络层。实际上,如果你愿意,定义自己的层也很简单。以下是一些你可能经常看到的常见层:

  • nn.Linear(input, output):全连接层

  • nn.Conv2d(in_channel, out_channel, kernel_size):二维卷积层,在图像处理网络中很受欢迎。

  • nn.Dropout(probability):Dropout 层,通常添加到网络中以引入正则化。

  • nn.Flatten():将高维输入张量重塑为 1 维(每个批次中的每个样本)。

除了层,还有激活函数。这些是应用于张量每个元素的函数。通常,你会将层的输出传递给激活函数,然后再作为输入传递给后续层。一些常见的激活函数包括:

  • nn.ReLU():整流线性单元,现在最常用的激活函数。

  • nn.Sigmoid()nn.Tanh():Sigmoid 和双曲正切函数,这些是旧文献中常用的选择。

  • nn.Softmax():将向量转换为类似概率的值;在分类网络中很受欢迎。

你可以在 PyTorch 的文档中找到所有不同层和激活函数的列表。

PyTorch 的设计非常模块化。因此,你不需要在每个组件中进行太多调整。以 Linear 层为例,你只需指定输入和输出的形状,而不是其他细节,如如何初始化权重。然而,几乎所有组件都可以接受两个额外的参数:设备和数据类型。

PyTorch 设备指定了此层将在哪个位置执行。通常,你可以选择 CPU 或 GPU,或者省略它,让 PyTorch 决定。要指定设备,你可以这样做(CUDA 意味着支持的 nVidia GPU):

nn.Linear(764, 100, device="cpu")

nn.Linear(764, 100, device="cuda:0")

数据类型参数 (dtype) 指定了此层应操作的数据类型。通常,这是一个 32 位浮点数,通常你不想更改它。但如果需要指定不同的类型,必须使用 PyTorch 类型,例如:

nn.Linear(764, 100, dtype=torch.float16)

损失函数和模型优化器

神经网络模型是矩阵操作的序列。与输入无关并保存在模型中的矩阵称为权重。训练神经网络将优化这些权重,以便它们生成你想要的输出。在深度学习中,优化这些权重的算法是梯度下降。

梯度下降有很多变体。你可以通过为模型准备一个优化器来选择适合你的优化器。它不是模型的一部分,但你会在训练过程中与模型一起使用它。使用方式包括定义一个损失函数并使用优化器最小化损失函数。损失函数会给出一个距离分数,以告诉模型输出距离你期望的输出有多远。它将模型的输出张量与期望的张量进行比较,在不同的上下文中,期望的张量被称为标签真实值。因为它作为训练数据集的一部分提供,所以神经网络模型是一个监督学习模型。

在 PyTorch 中,你可以简单地取模型的输出张量并对其进行操作以计算损失。但你也可以利用 PyTorch 提供的函数,例如,

loss_fn = nn.CrossEntropyLoss()
loss = loss_fn(output, label)

在这个例子中,loss_fn 是一个函数,而 loss 是一个支持自动微分的张量。你可以通过调用 loss.backward() 来触发微分。

以下是 PyTorch 中一些常见的损失函数:

  • nn.MSELoss():均方误差,适用于回归问题

  • nn.CrossEntropyLoss():交叉熵损失,适用于分类问题

  • nn.BCELoss():二元交叉熵损失,适用于二分类问题

创建优化器类似:

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

所有优化器都需要一个包含所有需要优化的参数的列表。这是因为优化器是在模型之外创建的,你需要告诉它在哪里查找参数(即模型权重)。然后,优化器会根据 backward() 函数调用计算的梯度,并根据优化算法将其应用于参数。

这是一些常见优化器的列表:

  • torch.optim.Adam():Adam 算法(自适应矩估计)

  • torch.optim.NAdam():具有 Nesterov 动量的 Adam 算法

  • torch.optim.SGD():随机梯度下降

  • torch.optim.RMSprop():RMSprop 算法

你可以在 PyTorch 的文档中找到所有提供的损失函数和优化器的列表。你可以在文档中相应优化器的页面上了解每个优化算法的数学公式。

模型训练和推理

PyTorch 没有专门的模型训练和评估函数。一个定义好的模型本身就像一个函数。你传入一个输入张量,并返回一个输出张量。因此,编写训练循环是你的责任。一个最简单的训练循环如下:

for n in range(num_epochs):
    y_pred = model(X)
    loss = loss_fn(y_pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

如果你已经有一个模型,你可以简单地使用 y_pred = model(X) 并利用输出张量 y_pred 进行其他用途。这就是如何使用模型进行预测或推断。然而,模型不期望单个输入样本,而是一个包含多个输入样本的张量。如果模型要处理一个输入向量(即一维),你应当向模型提供一个二维张量。通常,在推断的情况下,你会故意创建一个包含一个样本的批次。

模型检查

一旦你有了模型,你可以通过打印模型来检查它:

print(model)

这将为你提供例如以下内容:

Sequential(
  (0): Linear(in_features=8, out_features=12, bias=True)
  (1): ReLU()
  (2): Linear(in_features=12, out_features=8, bias=True)
  (3): ReLU()
  (4): Linear(in_features=8, out_features=1, bias=True)
  (5): Sigmoid()
)

如果你想保存模型,你可以使用 Python 的 pickle 库。但你也可以使用 PyTorch 来访问它:

torch.save(model, "my_model.pickle")

这样,你就将整个模型对象保存到 pickle 文件中。你可以通过以下方式检索模型:

model = torch.load("my_model.pickle")

但推荐的保存模型的方式是将模型设计留在代码中,只保存权重。你可以这样做:

torch.save(model.state_dict(), "my_model.pickle")

state_dict() 函数仅提取状态(即模型中的权重)。要检索它,你需要从头开始重建模型,然后像这样加载权重:

model = nn.Sequential(...)
model.load_state_dict(torch.load("my_model.pickle"))

资源

你可以通过以下资源进一步了解如何在 PyTorch 中创建简单的神经网络和深度学习模型:

在线资源

总结

在这篇文章中,你了解了可以用来创建人工神经网络和深度学习模型的 PyTorch API。具体来说,你学习了 PyTorch 模型的生命周期,包括:

  • 构建模型

  • 创建和添加层及激活函数

  • 为训练和推断准备模型

在 PyTorch 中计算导数

原文:machinelearningmastery.com/calculating-derivatives-in-pytorch/

导数是微积分中最基本的概念之一,描述了变量输入的变化如何影响函数输出。本文旨在为那些新手提供一个关于在 PyTorch 中计算导数的高级介绍。PyTorch 提供了一个方便的方式来计算用户定义函数的导数。

当我们在神经网络中始终需要处理反向传播(这是优化参数以最小化误差以实现更高分类精度的算法)时,本文中学到的概念将在后续关于图像处理和其他计算机视觉问题的深度学习文章中使用。

通过本教程,您将学到:

  • 如何在 PyTorch 中计算导数。

  • 如何在 PyTorch 中使用 autograd 对张量进行自动求导。

  • 关于涉及不同节点和叶子的计算图,使您能够以最简单的方式计算梯度(使用链式法则)。

  • 如何在 PyTorch 中计算偏导数。

  • 如何实现针对多个值的函数的导数。

启动你的项目,使用我的书籍 Deep Learning with PyTorch。它提供了具有工作代码自学教程

让我们开始吧!

在 PyTorch 中计算导数

照片由 Jossuha Théophile 拍摄。部分权利保留。

Autograd 中的求导

PyTorch 中的自动求导模块 - autograd - 用于计算神经网络中的导数并优化参数。它主要用于梯度计算。

在我们开始之前,让我们加载一些在本教程中将要使用的必要库。

import matplotlib.pyplot as plt
import torch

现在,让我们使用一个简单的张量,并将 requires_grad 参数设置为 true。这样我们就可以进行自动求导,并让 PyTorch 使用给定值(本例中为 3.0)来计算导数。

x = torch.tensor(3.0, requires_grad = True)
print("creating a tensor x: ", x)
creating a tensor x:  tensor(3., requires_grad=True)

我们将使用一个简单的方程 y=3x2y=3x² 作为示例,并针对变量 x 求导。因此,让我们根据给定的方程创建另一个张量。此外,我们将在变量 y 上应用一个 .backward 方法,它形成一个存储计算历史的无环图,并使用 .grad 来评估给定值的结果。

y = 3 * x ** 2
print("Result of the equation is: ", y)
y.backward()
print("Dervative of the equation at x = 3 is: ", x.grad)
Result of the equation is:  tensor(27., grad_fn=<MulBackward0>)
Dervative of the equation at x = 3 is:  tensor(18.)

正如你所见,我们获得了一个正确的值 18。

计算图

PyTorch 在后台构建反向图来生成导数,张量和反向函数是图的节点。在图中,PyTorch 根据张量是否为叶节点来计算其导数。

如果张量的 leaf 属性设置为 True,PyTorch 将不会计算其导数。我们不会详细讨论反向图是如何创建和利用的,因为这里的目标是让您对 PyTorch 如何利用图来计算导数有一个高层次的了解。

因此,让我们来看看张量xy在创建后的内部情况。对于x而言:

print('data attribute of the tensor:',x.data)
print('grad attribute of the tensor::',x.grad)
print('grad_fn attribute of the tensor::',x.grad_fn)
print("is_leaf attribute of the tensor::",x.is_leaf)
print("requires_grad attribute of the tensor::",x.requires_grad)
data attribute of the tensor: tensor(3.)
grad attribute of the tensor:: tensor(18.)
grad_fn attribute of the tensor:: None
is_leaf attribute of the tensor:: True
requires_grad attribute of the tensor:: True

以及对于y

print('data attribute of the tensor:',y.data)
print('grad attribute of the tensor:',y.grad)
print('grad_fn attribute of the tensor:',y.grad_fn)
print("is_leaf attribute of the tensor:",y.is_leaf)
print("requires_grad attribute of the tensor:",y.requires_grad)
print('data attribute of the tensor:',y.data)
print('grad attribute of the tensor:',y.grad)
print('grad_fn attribute of the tensor:',y.grad_fn)
print("is_leaf attribute of the tensor:",y.is_leaf)
print("requires_grad attribute of the tensor:",y.requires_grad)

正如你所见,每个张量都被分配了一组特定的属性。

data属性存储张量的数据,而grad_fn属性告诉有关图中节点的信息。同样,.grad属性保存导数的结果。现在您已经学习了有关 autograd 和 PyTorch 计算图的一些基础知识,让我们看一看稍微复杂的方程y=6x2+2x+4y=6x²+2x+4并计算其导数。方程的导数如下所示:

dydx=12x+2\frac{dy}{dx} = 12x+2

x=3x=3处评估导数,

dydxx=3=12×3+2=38\left.\frac{dy}{dx}\right\vert_{x=3} = 12\times 3+2 = 38

现在,让我们看一看 PyTorch 是如何做到的,

x = torch.tensor(3.0, requires_grad = True)
y = 6 * x ** 2 + 2 * x + 4
print("Result of the equation is: ", y)
y.backward()
print("Derivative of the equation at x = 3 is: ", x.grad)
Result of the equation is:  tensor(64., grad_fn=<AddBackward0>)
Derivative of the equation at x = 3 is:  tensor(38.)

方程的导数为 38,这是正确的。

想要开始使用 PyTorch 进行深度学习吗?

现在就参加我的免费电子邮件速成课程(附有示例代码)。

点击注册并获得免费的课程 PDF 电子书版本。

实现函数的偏导数

PyTorch 还允许我们计算函数的偏导数。例如,如果我们需要对以下函数应用偏导数,

f(u,v)=u3+v2+4uvf(u,v) = u³+v²+4uv

其关于uu的导数为,

fu=3u2+4v\frac{\partial f}{\partial u} = 3u² + 4v

同样地,关于vv的导数如下,

fv=2v+4u\frac{\partial f}{\partial v} = 2v + 4u

现在,让我们以 PyTorch 的方式来做,其中u=3u = 3v=4v = 4

我们将创建uvf张量,并在f上应用.backward属性来计算导数。最后,我们将使用.grad相对于uv的值来评估导数。

u = torch.tensor(3., requires_grad=True)
v = torch.tensor(4., requires_grad=True)

f = u**3 + v**2 + 4*u*v

print(u)
print(v)
print(f)

f.backward()
print("Partial derivative with respect to u: ", u.grad)
print("Partial derivative with respect to v: ", v.grad)
tensor(3., requires_grad=True)
tensor(4., requires_grad=True)
tensor(91., grad_fn=<AddBackward0>)
Partial derivative with respect to u:  tensor(43.)
Partial derivative with respect to v:  tensor(20.)

具有多个值的函数的导数

如果我们有一个具有多个值的函数,并且需要计算其关于多个值的导数怎么办?为此,我们将利用 sum 属性来 (1) 生成一个标量值函数,然后 (2) 求导。这就是我们可以看到‘函数 vs. 导数’图的方式:

# compute the derivative of the function with multiple values
x = torch.linspace(-20, 20, 20, requires_grad = True)
Y = x ** 2
y = torch.sum(Y)
y.backward()

# ploting the function and derivative
function_line, = plt.plot(x.detach().numpy(), Y.detach().numpy(), label = 'Function')
function_line.set_color("red")
derivative_line, = plt.plot(x.detach().numpy(), x.grad.detach().numpy(), label = 'Derivative')
derivative_line.set_color("green")
plt.xlabel('x')
plt.legend()
plt.show()

在上述的两个plot()函数中,我们从 PyTorch 张量中提取值以便进行可视化。.detach方法不允许图进一步跟踪操作。这使得我们可以轻松地将张量转换为 numpy 数组。

总结

在本教程中,您学习了如何在 PyTorch 中实现各种函数的导数。

特别是,您学到了:

  • 如何在 PyTorch 中计算导数。

  • 如何在 PyTorch 中使用 autograd 对张量执行自动微分。

  • 有关涉及不同节点和叶子的计算图,使您能够以可能的最简单方式计算梯度(使用链式法则)。

  • 如何在 PyTorch 中计算偏导数。

  • 如何实现对多个值的函数的导数。

为 PyTorch 模型创建训练循环

原文:machinelearningmastery.com/creating-a-training-loop-for-pytorch-models/

PyTorch 提供了许多深度学习模型的构建模块,但训练循环并不包括在其中。这种灵活性允许你在训练过程中做任何你想做的事情,但某些基本结构在大多数使用场景中是通用的。

在本文中,你将看到如何创建一个训练循环,为你的模型训练提供必要的信息,并可以选择显示任何信息。完成本文后,你将了解:

  • 训练循环的基本构建块

  • 如何使用 tqdm 显示训练进度

用我的书 Deep Learning with PyTorch 启动你的项目。它提供了 自学教程实用代码

让我们开始吧。

为 PyTorch 模型创建训练循环

图片由 pat pat 提供。版权所有。

概述

本文分为三部分,分别是:

  • 深度学习模型的训练要素

  • 在训练期间收集统计数据

  • 使用 tqdm 报告训练进度

深度学习模型的训练要素

与所有机器学习模型一样,模型设计指定了操作输入并生成输出的算法。但在模型中,有些参数需要调整以实现这一目标。这些模型参数也被称为权重、偏差、内核或其他名称,具体取决于特定模型和层。训练是将样本数据输入模型,以便优化器可以调整这些参数。

当你训练一个模型时,你通常从一个数据集开始。每个数据集包含大量的数据样本。当你获得数据集时,建议将其分为两个部分:训练集和测试集。训练集进一步分为批次,并在训练循环中使用,以驱动梯度下降算法。然而,测试集用作基准,以判断你的模型表现如何。通常,你不会将训练集作为度量,而是使用测试集,因为测试集没有被梯度下降算法看到,从而可以判断你的模型是否对未见过的数据适应良好。

过拟合是指模型在训练集上表现得过于好(即,非常高的准确率),但在测试集上的表现显著下降。欠拟合是指模型甚至无法在训练集上表现良好。自然,你不希望在一个好的模型中看到这两种情况。

神经网络模型的训练是以周期为单位的。通常,一个周期意味着你遍历整个训练集一次,尽管你一次只输入一个批次。在每个周期结束时,通常会做一些例行任务,如使用测试集对部分训练好的模型进行基准测试、检查点模型、决定是否提前停止训练、收集训练统计数据等。

在每个周期中,你将数据样本以批次的形式输入模型,并运行梯度下降算法。这是训练循环中的一步,因为你在一次前向传递(即,提供输入并捕获输出)和一次反向传递(从输出评估损失指标并将每个参数的梯度反向到输入层)中运行模型。反向传递使用自动微分来计算梯度。然后,这些梯度由梯度下降算法用于调整模型参数。一个周期包含多个步骤。

复用之前教程中的示例,你可以下载数据集并将数据集拆分为两部分,如下所示:

import numpy as np
import torch

# load the dataset
dataset = np.loadtxt('pima-indians-diabetes.csv', delimiter=',')
X = dataset[:,0:8]
y = dataset[:,8]
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)

# split the dataset into training and test sets
Xtrain = X[:700]
ytrain = y[:700]
Xtest = X[700:]
ytest = y[700:]

这个数据集很小——只有 768 个样本。在这里,它将前 700 个样本作为训练集,其余的作为测试集。

这不是本文的重点,但你可以复用之前文章中的模型、损失函数和优化器:

import torch.nn as nn
import torch.optim as optim

model = nn.Sequential(
    nn.Linear(8, 12),
    nn.ReLU(),
    nn.Linear(12, 8),
    nn.ReLU(),
    nn.Linear(8, 1),
    nn.Sigmoid()
)
print(model)

# loss function and optimizer
loss_fn = nn.BCELoss()  # binary cross entropy
optimizer = optim.Adam(model.parameters(), lr=0.001)

有了数据和模型,这就是最简训练循环,每一步都有前向和反向传递:

n_epochs = 50    # number of epochs to run
batch_size = 10  # size of each batch
batches_per_epoch = len(Xtrain) // batch_size

for epoch in range(n_epochs):
    for i in range(batches_per_epoch):
        start = i * batch_size
        # take a batch
        Xbatch = Xtrain[start:start+batch_size]
        ybatch = ytrain[start:start+batch_size]
        # forward pass
        y_pred = model(Xbatch)
        loss = loss_fn(y_pred, ybatch)
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()

在内部的 for 循环中,你取数据集中的每一个批次并评估损失。损失是一个 PyTorch 张量,它记住了如何得出其值。然后你将优化器管理的所有梯度清零,并调用loss.backward()来运行反向传播算法。结果设置了所有张量的梯度,这些张量直接或间接地依赖于张量loss。随后,调用step()时,优化器将检查其管理的每个参数并更新它们。

完成所有步骤后,你可以使用测试集运行模型以评估其性能。评估可以基于不同于损失函数的函数。例如,这个分类问题使用准确率:

...

# evaluate trained model with test set
with torch.no_grad():
    y_pred = model(X)
accuracy = (y_pred.round() == y).float().mean()
print("Accuracy {:.2f}".format(accuracy * 100))

将一切整合在一起,这就是完整代码:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# load the dataset
dataset = np.loadtxt('pima-indians-diabetes.csv', delimiter=',')
X = dataset[:,0:8]
y = dataset[:,8]
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)

# split the dataset into training and test sets
Xtrain = X[:700]
ytrain = y[:700]
Xtest = X[700:]
ytest = y[700:]

model = nn.Sequential(
    nn.Linear(8, 12),
    nn.ReLU(),
    nn.Linear(12, 8),
    nn.ReLU(),
    nn.Linear(8, 1),
    nn.Sigmoid()
)
print(model)

# loss function and optimizer
loss_fn = nn.BCELoss()  # binary cross entropy
optimizer = optim.Adam(model.parameters(), lr=0.001)

n_epochs = 50    # number of epochs to run
batch_size = 10  # size of each batch
batches_per_epoch = len(Xtrain) // batch_size

for epoch in range(n_epochs):
    for i in range(batches_per_epoch):
        start = i * batch_size
        # take a batch
        Xbatch = Xtrain[start:start+batch_size]
        ybatch = ytrain[start:start+batch_size]
        # forward pass
        y_pred = model(Xbatch)
        loss = loss_fn(y_pred, ybatch)
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()

# evaluate trained model with test set
with torch.no_grad():
    y_pred = model(X)
accuracy = (y_pred.round() == y).float().mean()
print("Accuracy {:.2f}".format(accuracy * 100))

训练期间收集统计数据

上述训练循环应该适用于可以在几秒钟内完成训练的小模型。但对于较大的模型或较大的数据集,你会发现训练所需的时间显著增加。在等待训练完成的同时,你可能希望查看进度,以便在出现任何错误时中断训练。

通常,在训练过程中,你希望看到以下内容:

  • 在每一步中,你想要知道损失指标,并期望损失降低。

  • 在每一步中,你想要了解其他指标,例如训练集上的准确率,这些指标是感兴趣的但不参与梯度下降。

  • 在每个 epoch 结束时,你想要用测试集评估部分训练的模型并报告评估指标。

  • 在训练结束时,你希望能够可视化以上指标。

这些都是可能的,但是你需要在训练循环中添加更多代码,如下所示:

n_epochs = 50    # number of epochs to run
batch_size = 10  # size of each batch
batches_per_epoch = len(Xtrain) // batch_size

# collect statistics
train_loss = []
train_acc = []
test_acc = []

for epoch in range(n_epochs):
    for i in range(batches_per_epoch):
        start = i * batch_size
        # take a batch
        Xbatch = Xtrain[start:start+batch_size]
        ybatch = ytrain[start:start+batch_size]
        # forward pass
        y_pred = model(Xbatch)
        loss = loss_fn(y_pred, ybatch)
        acc = (y_pred.round() == ybatch).float().mean()
        # store metrics
        train_loss.append(float(loss))
        train_acc.append(float(acc))
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()
        # print progress
        print(f"epoch {epoch} step {i} loss {loss} accuracy {acc}")
    # evaluate model at end of epoch
    y_pred = model(Xtest)
    acc = (y_pred.round() == ytest).float().mean()
    test_acc.append(float(acc))
    print(f"End of {epoch}, accuracy {acc}")

当你收集损失和准确率到列表中时,你可以使用 matplotlib 将它们绘制出来。但要小心,你在每一步收集了训练集的统计数据,但测试集的准确率只在每个 epoch 结束时。因此,你希望在每个 epoch 中显示训练循环中的平均准确率,以便它们可以相互比较。

import matplotlib.pyplot as plt

# Plot the loss metrics, set the y-axis to start from 0
plt.plot(train_loss)
plt.xlabel("steps")
plt.ylabel("loss")
plt.ylim(0)
plt.show()

# plot the accuracy metrics
avg_train_acc = []
for i in range(n_epochs):
    start = i * batch_size
    average = sum(train_acc[start:start+batches_per_epoch]) / batches_per_epoch
    avg_train_acc.append(average)

plt.plot(avg_train_acc, label="train")
plt.plot(test_acc, label="test")
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0)
plt.show()

将所有内容整合在一起,以下是完整的代码:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# load the dataset
dataset = np.loadtxt('pima-indians-diabetes.csv', delimiter=',') # split into input (X) and output (y) variables
X = dataset[:,0:8]
y = dataset[:,8]
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)

# split the dataset into training and test sets
Xtrain = X[:700]
ytrain = y[:700]
Xtest = X[700:]
ytest = y[700:]

model = nn.Sequential(
    nn.Linear(8, 12),
    nn.ReLU(),
    nn.Linear(12, 8),
    nn.ReLU(),
    nn.Linear(8, 1),
    nn.Sigmoid()
)
print(model)

# loss function and optimizer
loss_fn = nn.BCELoss()  # binary cross entropy
optimizer = optim.Adam(model.parameters(), lr=0.0001)

n_epochs = 50    # number of epochs to run
batch_size = 10  # size of each batch
batches_per_epoch = len(Xtrain) // batch_size

# collect statistics
train_loss = []
train_acc = []
test_acc = []

for epoch in range(n_epochs):
    for i in range(batches_per_epoch):
        # take a batch
        start = i * batch_size
        Xbatch = Xtrain[start:start+batch_size]
        ybatch = ytrain[start:start+batch_size]
        # forward pass
        y_pred = model(Xbatch)
        loss = loss_fn(y_pred, ybatch)
        acc = (y_pred.round() == ybatch).float().mean()
        # store metrics
        train_loss.append(float(loss))
        train_acc.append(float(acc))
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()
        # print progress
        print(f"epoch {epoch} step {i} loss {loss} accuracy {acc}")
    # evaluate model at end of epoch
    y_pred = model(Xtest)
    acc = (y_pred.round() == ytest).float().mean()
    test_acc.append(float(acc))
    print(f"End of {epoch}, accuracy {acc}")

import matplotlib.pyplot as plt

# Plot the loss metrics
plt.plot(train_loss)
plt.xlabel("steps")
plt.ylabel("loss")
plt.ylim(0)
plt.show()

# plot the accuracy metrics
avg_train_acc = []
for i in range(n_epochs):
    start = i * batch_size
    average = sum(train_acc[start:start+batches_per_epoch]) / batches_per_epoch
    avg_train_acc.append(average)

plt.plot(avg_train_acc, label="train")
plt.plot(test_acc, label="test")
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0)
plt.show()

故事还没有结束。事实上,你可以在训练循环中添加更多代码,特别是在处理更复杂的模型时。一个例子是检查点。你可能想要保存你的模型(例如使用 pickle),这样,如果出于任何原因你的程序停止,你可以从中间重新启动训练循环。另一个例子是早停,它允许你在每个 epoch 结束时监视测试集的准确率,并在一段时间内看不到模型改进时中断训练。这是因为你可能不能进一步进行,考虑到模型的设计,而且你不想过拟合。

想要开始使用 PyTorch 进行深度学习吗?

现在就参加我的免费电子邮件快速课程(附有示例代码)。

点击注册并获取课程的免费 PDF 电子书版本。

使用 tqdm 报告训练进度

如果你运行以上代码,你会发现在训练循环运行时屏幕上打印了很多行。你的屏幕可能会很杂乱。而且你可能还想看到一个动画进度条,以更好地告诉你训练进度到了哪一步。tqdm库是创建进度条的流行工具。将以上代码转换为使用 tqdm 可以更加简单:

for epoch in range(n_epochs):
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            Xbatch = Xtrain[start:start+batch_size]
            ybatch = ytrain[start:start+batch_size]
            # forward pass
            y_pred = model(Xbatch)
            loss = loss_fn(y_pred, ybatch)
            acc = (y_pred.round() == ybatch).float().mean()
            # store metrics
            train_loss.append(float(loss))
            train_acc.append(float(acc))
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(
                loss=float(loss),
                acc=f"{float(acc)*100:.2f}%"
            )
    # evaluate model at end of epoch
    y_pred = model(Xtest)
    acc = (y_pred.round() == ytest).float().mean()
    test_acc.append(float(acc))
    print(f"End of {epoch}, accuracy {acc}")

使用tqdm创建一个迭代器,使用trange()就像 Python 的range()函数一样,并且你可以在循环中读取数字。你可以通过更新其描述或“后缀”数据访问进度条,但你必须在其内容耗尽之前这样做。set_postfix()函数非常强大,因为它可以显示任何内容。

实际上,除了trange()之外还有一个tqdm()函数,它迭代现有列表。你可能会发现它更容易使用,并且你可以重写以上循环如下:

starts = [i*batch_size for i in range(batches_per_epoch)]

for epoch in range(n_epochs):
    with tqdm.tqdm(starts, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for start in bar:
            # take a batch
            Xbatch = Xtrain[start:start+batch_size]
            ybatch = ytrain[start:start+batch_size]
            # forward pass
            y_pred = model(Xbatch)
            loss = loss_fn(y_pred, ybatch)
            acc = (y_pred.round() == ybatch).float().mean()
            # store metrics
            train_loss.append(float(loss))
            train_acc.append(float(acc))
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(
                loss=float(loss),
                acc=f"{float(acc)*100:.2f}%"
            )
    # evaluate model at end of epoch
    y_pred = model(Xtest)
    acc = (y_pred.round() == ytest).float().mean()
    test_acc.append(float(acc))
    print(f"End of {epoch}, accuracy {acc}")

以下是完整的代码(不包括 matplotlib 绘图):

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm

# load the dataset
dataset = np.loadtxt('pima-indians-diabetes.csv', delimiter=',') # split into input (X) and output (y) variables
X = dataset[:,0:8]
y = dataset[:,8]
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)

# split the dataset into training and test sets
Xtrain = X[:700]
ytrain = y[:700]
Xtest = X[700:]
ytest = y[700:]

model = nn.Sequential(
    nn.Linear(8, 12),
    nn.ReLU(),
    nn.Linear(12, 8),
    nn.ReLU(),
    nn.Linear(8, 1),
    nn.Sigmoid()
)
print(model)

# loss function and optimizer
loss_fn = nn.BCELoss()  # binary cross entropy
optimizer = optim.Adam(model.parameters(), lr=0.0001)

n_epochs = 50    # number of epochs to run
batch_size = 10  # size of each batch
batches_per_epoch = len(Xtrain) // batch_size

# collect statistics
train_loss = []
train_acc = []
test_acc = []

for epoch in range(n_epochs):
    with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
        bar.set_description(f"Epoch {epoch}")
        for i in bar:
            # take a batch
            start = i * batch_size
            Xbatch = Xtrain[start:start+batch_size]
            ybatch = ytrain[start:start+batch_size]
            # forward pass
            y_pred = model(Xbatch)
            loss = loss_fn(y_pred, ybatch)
            acc = (y_pred.round() == ybatch).float().mean()
            # store metrics
            train_loss.append(float(loss))
            train_acc.append(float(acc))
            # backward pass
            optimizer.zero_grad()
            loss.backward()
            # update weights
            optimizer.step()
            # print progress
            bar.set_postfix(
                loss=float(loss),
                acc=f"{float(acc)*100:.2f}%"
            )
    # evaluate model at end of epoch
    y_pred = model(Xtest)
    acc = (y_pred.round() == ytest).float().mean()
    test_acc.append(float(acc))
    print(f"End of {epoch}, accuracy {acc}")

总结

在本文中,你详细了解了如何为 PyTorch 模型正确设置训练循环。具体来说,你看到了:

  • 实现训练循环所需的元素是什么。

  • 训练循环如何将训练数据与梯度下降优化器连接起来

  • 如何在训练循环中收集信息并展示这些信息