PyTorch-26H-3
PyTorch-26H-3
主页:https://www.freecodecamp.org/news/learn-pytorch-for-deep-learning-in-day/
youtub:https://youtu.be/V_xro1bcAuA
github:https://github.com/mrdbourke/pytorch-deep-learning
Learn PyTorch for Deep Learning: Zero to Mastery book:https://www.learnpytorch.io/
PyTorch documentation:https://pytorch.org/docs/stable/index.html
What is a classification problem? 什么是分类问题?
问题类型 | 解释 | 例子 |
---|---|---|
二元分类(Binary classification) | 目标可以是两个选项之一,例如是或否 | 根据某人的健康参数预测他是否患有心脏病。 |
多类别分类(Multi-class classification) | 目标可以是两个以上选项之一 | 确定照片中是食物、人还是狗。 |
多标签分类(Multi-label classification) | 目标可以分配多个选项 | 预测应为维基百科文章分配哪些类别(例如数学、科学和哲学)。 |
分类和回归是最常见的机器学习问题类型之一。
换句话说,获取一组输入并预测该组输入属于哪个类别。
What we’re going to cover 我们将要讨论的内容
- Architecture of a neural network classification model
神经网络分类模型的架构
Input shapes and output shapes of a classification model (features and labels)
分类模型的输入形状和输出形状(特征和标签)
Creating custom data to view, fit on and predict on
创建自定义数据以查看、拟合和预测
Steps in modelling
建模步骤
Creating a model, setting a loss function and optimiser, creating a training loop, evaluating a
model创建模型、设置损失函数和优化器、创建训练循环、评估
Saving and loading models
保存和加载模型
Harnessing the power of non-linearity
利用非线性的力量
Different classification evaluation methods
- 不同的分类评估方法
话题 | 内容 |
---|---|
0. 分类神经网络的架构 | 神经网络几乎可以具有任何形状和大小,但它们通常遵循类似的平面图。 |
1. 准备二元分类数据 | 数据几乎可以是任何东西,但首先我们将创建一个简单的二元分类数据集。 |
2.构建 PyTorch 分类模型 | 在这里我们将创建一个模型来学习数据中的模式,我们还将选择一个损失函数、优化器并构建一个特定于分类的训练循环。 |
3. 将模型拟合到数据(训练) | 我们有数据和模型,现在让我们让模型(尝试)在(训练)数据中寻找模式。 |
4. 做出预测并评估模型(推理) | 我们的模型在数据中发现了模式,让我们将它的发现与实际(测试)数据进行比较。 |
5. 改进模型(从模型角度) | 我们已经训练并评估了一个模型,但它不起作用,让我们尝试一些方法来改进它。 |
6.非线性 | 到目前为止,我们的模型只具有对直线进行建模的能力,那么非线性(非直线)线又如何呢? |
7. 复制非线性函数 | 我们使用非线性函数来帮助建模非线性数据,但是这些函数是什么样子的? |
8. 将所有内容与多类别分类结合起来 | 让我们将迄今为止为二元分类所做的一切与多类分类问题放在一起。 |
0. Architecture of a classification neural network 分类神经网络的架构
分类神经网络的一般架构:
超参数 | 二元分类 | 多类分类 |
---|---|---|
输入层形状 Input layer shape (in_features) | 与特征数量相同(例如,心脏病预测中的年龄、性别、身高、体重、吸烟状况为 5) | 与二元分类相同 |
隐藏层 Hidden layer(s) | 针对具体问题,最小值 = 1,最大值 = 无限制 | 与二元分类相同 |
每个隐藏层的神经元 Neurons per hidden layer | 具体问题具体分析,一般为 10 到 512 | 与二元分类相同 |
输出层形状 Output layer shape (out_features) | 1(一个类或另一个类) | 每类 1 张(例如,食物、人物或狗的照片各 3 张) |
隐藏层激活 Hidden layer activation | 通常是ReLU(整流线性单元),其他激活 | 与二元分类相同 |
输出激活 Output activation | Sigmoid torch.sigmoid | Softmax torch.softmax |
损失函数 Loss function | 二元交叉熵Binary crossentropy torch.nn.BCELoss | 交叉熵 torch.nn.CrossEntropyLoss |
优化器 Optimizer | SGD stochastic gradient descent ,Adam,torch.optim | 与二元分类相同 |
这个分类神经网络组件的成分列表会根据您正在处理的问题而有所不同。
1. Make classification data and get it ready 分类数据制作及准备
使用 make_circles() 中的 Scikit-Learn
方法生成两个具有不同颜色的圆圈。
1 | # conda install scikit-learn |
查看前5个X值y。
1 | print(f"First 5 X features:\n{X[:5]}") |
1 | First 5 X features: |
可视化:
1 | # Make DataFrame of circle data |
1 | X1 X2 label |
看起来每对X特征(X1和X2)都有一个标签(y)值,即 0 或 1。
这告诉我们我们的问题是二元分类,因为只有两个选项(0 或 1)。
每个类别有多少个值?
1 | # Check different labels |
1 | 1 500 |
0和1各五百个
1 | # Visualize with a plot |
如何构建 PyTorch 神经网络来将点分类为红色(0)或蓝色(1)。
在机器学习中,这个数据集通常被视为玩具问题(用于尝试和测试事物的问题)。但它代表了分类的主要关键,您有一些以数值表示的数据,并且您想要构建一个能够对其进行分类的模型,在我们的例子中,将其分成红点或蓝点。
1.1 Input and output shapes 输入和输出形状
可以设置32的batch size。使用大型 minibatch 进行训练对测试错误不利。
深度学习中最常见的错误之一是形状错误。
张量形状和张量运算不匹配将导致模型出现错误。
我们将会在整个课程中看到很多这样的情况。
输入和输出形状。
1 | # Check the shapes of our features and labels |
1 | ((1000, 2), (1000,)) |
看起来我们在每个维度的第一维度上都找到了匹配项。
有 1000 个 X 和 1000 个 y。
但是 X 的第二维度是什么?
查看单个样本(特征和标签)的值和形状通常很有帮助。
这样做将帮助您了解您希望从模型中获得什么样的输入和输出形状。
1 | # View the first example of features and labels |
1 | Values for one sample of X: [0.75424625 0.23148074] and the same for y: 1 |
这告诉我们 X 的第二个维度意味着它有两个特征(向量vector),而 y 只有一个特征(标量scalar)。
我们有两个输入和一个输出。
1.2 Turn data into tensors and create train and test splits 将数据转换为张量并创建训练和测试分割
1、将我们的数据转换成张量(现在我们的数据在 NumPy 数组中,PyTorch 更喜欢使用 PyTorch 张量)。
2、X
将我们的数据分成训练集和测试集(我们将在训练集上训练一个模型来学习和之间的模式,y
然后在测试数据集上评估这些学习到的模式)。
1 | # Turn data into tensors |
1 | (tensor([[ 0.7542, 0.2315], |
现在我们的数据是张量格式,让我们将其分成训练集和测试集。
使用 Scikit-Learn 中 train_test_split() 函数。
我们将使用test_size=0.2
(80%训练,20%测试),并且由于分割在数据中随机发生,random_state=42
因此我们使用可重现的分割。
1 | # Split data into train and test sets |
1 | (800, 200, 800, 200) |
现在有 800 个训练样本和 200 个测试样本。
2. Building a model 建立模型
模型需要分为几个部分。
1、设置与设备无关的代码(这样我们的模型可以在 CPU 或 GPU 上运行)。
2、通过子类化构建模型 nn.Module
。
3、定义损失函数和优化器。
4、创建训练循环。
1 | # Standard PyTorch imports |
1 | 'cuda' |
我们需要一个模型,能够处理我们的X
数据作为输入,并生成与我们的数据形状相同y
的输出。
换句话说,给定X
(特征feature),我们希望我们的模型预测y
(标签label)。
这种具有特征和标签的设置称为监督学习
。因为你的数据会告诉你的模型,给定某个输入,应该得到什么样的输出。
要创建这样的模型,需要处理X
和的输入和输出形状y
。
创建一个模型类:
1、子类 nn.Module
(几乎所有 PyTorch 模型都是 nn.Module
的子类)。
2、在构造函数中创建 2 个 nn.Linear
层,能够处理 X
和 y
的输入和输出形状。
3、定义一个包含模型前向传递计算的 forward()
方法。
4、实例化模型类并将其发送到目标设备。
1 | # 1. Construct a model class that subclasses nn.Module |
1 | CircleModelV0( |
唯一的重大变化是 self.layer_1
和 self.layer_2
之间发生的事情。
self.layer_1
接受 2 个输入特征 in_features=2
并产生 5 个输出特征 out_features=5
。
这被称为具有 5 个隐藏单元或神经元。该层将输入数据从 2 个特征变为 5 个特征。
这使得模型可以从 5 个数字而不是仅仅 2 个数字中学习模式,从而可能产生更好的输出。
在神经网络层中使用的隐藏单元的数量是一个超参数
(可以自己设置的值),并且没有必须使用的固定值。
通常情况下,数量越多越好,但也可能太多。您选择的数量取决于您的模型类型和您正在使用的数据集。
由于我们的数据集很小而且简单,因此我们会将其保持较小。
隐藏单元的唯一规则是下一层(在我们的例子中为 self.layer_2
)必须采用与前一层 out_features
相同的 in_features
。
这就是为什么 self.layer_2
有 in_features=5
,它从 self.layer_1
中获取 out_features=5
并对它们执行线性计算,将它们转换为 out_features=1
(与 y 相同的形状)。
与我们刚刚构建的分类神经网络类似的视觉示例。尝试在 TensorFlow Playground 网站上创建一个您自己的神经网络。
您也可以使用 nn.Sequential
执行与上述相同的操作。
1 | # Replicate CircleModelV0 with nn.Sequential |
1 | Sequential( |
这看起来比子类化简单多了 nn.Module
,为什么不总是使用呢nn.Sequential
?
nn.Sequential
对于直接计算来说非常棒,但是,正如命名空间所说,它总是按顺序运行。
因此,如果您希望发生其他事情(而不仅仅是直接的顺序计算),您将需要定义自己的自定义nn.Module
子类。
现们有一个模型,让我们看看当我们通过它传递一些数据时会发生什么。
1 | # Make predictions with the model |
1 | Length of predictions: 200, Shape: torch.Size([200, 1]) |
预测的数量与测试标签的数量相同,但是预测的形式或形状看起来与测试标签不一样。
2.1 Setup loss function and optimizer
不同类型的问题需要不同的损失函数。
回归问题(预测数字):使用平均绝对误差(MAE)损失。
二元分类问题(目前的问题),使用二元交叉熵作为损失函数。
相同的优化器函数通常可用于不同的问题空间。
随机梯度下降优化器(SGD,torch.optim.SGD()
)可用于解决一系列问题,Adam 优化器(torch.optim.Adam()
)同样适用。
损失函数/优化器 | 问题类型 | PyTorch代码 |
---|---|---|
随机梯度下降(SGD)优化器 | 分类、回归等 | torch.optim.SGD() |
Adam 优化器 | 分类、回归等 | torch.optim.Adam() |
二元交叉熵损失 | 二元分类 | torch.nn.BCELossWithLogits 或者 torch.nn.BCELoss |
交叉熵损失 | 多类别分类 | torch.nn.CrossEntropyLoss |
平均绝对误差 (MAE) 或 L1 损失 | 回归 | torch.nn.L1Loss |
均方误差 (MSE) 或 L2 损失 | 回归 | torch.nn.MSELoss |
由于我们正在处理二元分类问题,因此我们使用二元交叉熵损失函数。
PyTorch 有两种二元交叉熵实现:
1、torch.nn.BCELoss()- 创建一个损失函数,测量目标(标签)和输入(特征)之间的二元交叉熵。
2、torch.nn.BCEWithLogitsLoss() - 这与上面的相同,只是它有一个内置的 sigmoid 层 (nn.Sigmoid)
torch.nn.BCEWithLogitsLoss() 的文档指出,它比在 nn.Sigmoid
层之后使用 torch.nn.BCELoss()
更具数值稳定性。
通常,实现 2 是更好的选择。但是对于高级用法,可能希望分离 nn.Sigmoid
和 torch.nn.BCELoss()
的组合,但这超出了本笔记本的范围。
了解了这一点,让我们创建一个损失函数和一个优化器。
对于优化器,我们将使用 torch.optim.SGD()
以学习率为 0.1 来优化模型参数。
1 | # Create a loss function |
现在让我们创建一个评估指标
。
评估指标可用于提供模型运行情况的另一个视角。
如果损失函数衡量模型的错误程度,我喜欢将评估指标视为衡量模型的正确程度。
当然,您可以说这两者都在做同样的事情,但评估指标提供了不同的视角。
毕竟,在评估模型时,最好从多个角度看待事物。
有几种评估指标可用于分类问题,但让我们从准确度开始。
准确度可以通过将正确预测的总数除以预测总数来衡量。
例如,如果一个模型在 100 个预测中做出 99 个正确的预测,则准确度为 99%。
让我们编写一个函数来实现这一点。
1 | # Calculate accuracy (a classification metric) |
太棒了!我们现在可以在训练模型时使用此功能来测量其性能和损失。
3. Train model
PyTorch 训练循环步骤:
1、前向传递 Forward pass - 模型对所有训练数据进行一次遍历,执行其 forward() 函数计算 (model(x_train)
)。
2、计算损失 Calculate the loss - 将模型的输出 (预测) 与基本事实进行比较,并进行评估以查看其错误程度 (loss = loss_fn(y_pred, y_train)
)。
3、零梯度 Zero gradients - 优化器梯度设置为零 (默认情况下是累积的),因此可以为特定的训练步骤重新计算 (optimizer.zero_grad()
)。
4、对损失执行反向传播 Perform backpropagation on the loss - 针对要更新的每个模型参数 (每个参数的 require_grad=True) 计算损失的梯度。这称为反向传播,因此为“向后”(loss.backward()
)。
5、步进优化器 (梯度下降) Step the optimizer (gradient descent) - 使用 require_grad=True
更新参数,以根据损失梯度改进它们 (optimizer.step()
)。
3.1 Going from raw model outputs to predicted labels (logits -> prediction probabilities -> prediction labels) 从原始模型输出到预测标签(logits -> 预测概率 -> 预测标签)
在训练循环步骤之前,让我们看看在前向传递过程中我们的模型会产生什么结果(前向传递由方法定义forward()
)。
为此,让我们向模型传递一些数据。
1 | # View the frist 5 outputs of the forward pass on the test data |
1 | tensor([[-0.1631], |
由于我们的模型尚未经过训练,这些输出基本上是随机的。
但它们是什么?它们是我们的 forward()
方法的输出。
它实现了两层 nn.Linear()
,它在内部调用以下方程:
该方程($\mathbf{y}$)的原始输出(未修改),反过来,我们模型的原始输出通常被称为logits
。
这就是我们的模型在接受输入数据(等式中的 x 或代码中的 X_test
)时输出的内容,logits
。
然而,这些数字很难解释。
我们希望有一些数字可以与我们的真实标签相媲美。
为了将我们模型的原始输出(logits)变成这种形式,我们可以使用 sigmoid 激活函数。
1 | # Use sigmoid on model logits |
1 | tensor([[0.4593], |
看起来输出现在具有某种一致性(即使它们仍然是随机的)。
它们现在采用预测概率
的形式(我通常将其称为 y_pred_probs
),换句话说,这些值现在是模型认为数据点属于一个类或另一个类的程度。
在我们的例子中,由于我们正在处理二元分类,所以我们的理想输出是 0 或 1。
因此这些值可以被视为决策边界。
越接近0,模型越认为该样本属于0类,越接近1,模型越认为该样本属于1类。
更具体地说:
如果 y_pred_probs>= 0.5
,y=1
(第 1 类)
如果 y_pred_probs< 0.5
,y=0
(0 类)
为了将我们的预测概率转化为预测标签,我们可以对 sigmoid 激活函数的输出进行四舍五入。
1 | # Find the predicted labels (round the prediction probabilities) |
1 | tensor([True, True, True, True, True], device='cuda:0') |
现在看起来我们的模型的预测与我们的真实标签 ( y_test
) 的形式相同。
1 | y_test[:5] |
1 | tensor([1., 0., 1., 0., 1.]) |
这意味着我们将能够将模型的预测与测试标签进行比较,以了解其表现如何。
回顾一下,我们使用 sigmoid 激活函数将模型的原始输出 (logits) 转换为预测概率。
然后通过四舍五入将预测概率转换为预测标签。
注意:sigmoid 激活函数通常仅用于二分类 logits。对于多类分类,我们将考虑使用 softmax 激活函数。
将模型的原始输出传递给 nn.BCEWithLogitsLoss 时不需要使用 sigmoid激活函数(logits loss 中的“logits”是因为它适用于模型的原始 logits 输出),这是因为它内置了 sigmoid函数。
3.2 Building a training and testing loop 建立训练和测试循环
1 | torch.manual_seed(42) |
1 | Epoch: 0 | Loss: 0.71041, Accuracy: 49.50% | Test loss: 0.69582, Test acc: 53.00% |
每次数据分割的准确率几乎不超过 50%。
因为我们正在处理平衡的二元分类问题,所以这意味着我们的模型表现与随机猜测一样好(有 500 个 0 类和 1 类样本,每次预测 1 类的模型准确率都会达到 50%)。
4. Make predictions and evaluate the model 做出预测并评估模型
对于50%准确率的模型,几乎等于瞎猜。
我们将编写一些代码,从Learn PyTorch for Deep Learning仓库下载并导入helper_functions.py脚本。
它包含一个名为plot_decision_boundary()的有用函数,该函数创建了一个NumPy网格,以直观地绘制我们的模型预测某些类的不同点。
将结果可视化:
1 | import requests |
1 | # Plot decision boundaries for training and test sets |
由于数据是圆形的,因此画一条直线最多只能将其从中间切开。
用机器学习术语来说,模型拟合不足underfitting
,意味着它没有从数据中学习预测模式。
5. Improving a model (from a model perspective) 改进模型(从模型角度)
修复模型的欠拟合问题。
特别关注模型(而不是数据),我们可以通过几种方式来做到这一点。
模型改进技术 | 作用 |
---|---|
添加更多层 Add more layers | 每一层都可能增加模型的学习能力,因为每一层都能够学习数据中的某种新模式。更多层通常被称为使神经网络更深。 |
添加更多隐藏单元 Add more hidden units | 与上述类似,每层隐藏单元越多,模型的学习能力就越强。更多隐藏单元通常被称为使神经网络更宽。 |
更长训练时间(更多循环) Fitting for longer (more epochs) | 如果您的模型有更多机会查看数据,它可能会学到更多东西。 |
改变激活函数 Changing the activation functions | 有些数据无法仅用直线来拟合(就像我们所看到的),使用非线性激活函数可以帮助解决这个问题(提示,提示)。 |
改变学习率 Change the learning rate | 虽然与模型不太相关,但仍然相关,优化器的学习率决定了模型每一步应该改变多少参数,太多则模型过度修正,太少则学习不够。 |
改变损失函数 Change the loss function | 同样,虽然模型特定性不强但仍然很重要,不同的问题需要不同的损失函数。例如,二元交叉熵损失函数不适用于多类分类问题。 |
使用迁移学习 Use transfer learning | 从与您的问题领域类似的问题中获取预训练模型,并根据您自己的问题进行调整。 |
可以手动调整→超参数
机器学习为一半科学一半艺术,需要通过不断实验进行。
让我们看看如果我们在模型中添加一个额外的层,适应更长的时间(epochs=1000
而不是 epochs=100
),并将隐藏单元的数量从 5
增加到 10
,会发生什么。
我们将遵循上述相同的步骤,但会更改一些超参数。
1 | class CircleModelV1(nn.Module): |
1 | CircleModelV1( |
现在我们有了一个模型,我们将使用与之前相同的设置重新创建一个损失函数和优化器实例。
1 | # loss_fn = nn.BCELoss() # Requires sigmoid on input |
这次我们将进行更长时间的训练(epochs=1000 vs epochs=100),看看它是否能改进我们的模型。
1 | torch.manual_seed(42) |
1 | Epoch: 0 | Loss: 0.69396, Accuracy: 50.88% | Test loss: 0.69261, Test acc: 51.00% |
我们的模型训练的时间更长,并且增加了一层,但它看起来仍然没有学到比随机猜测更好的模式。
1 | # Plot decision boundaries for training and test sets |
我们的模型仍然在红点和蓝点之间画一条直线。
如果我们的模型画的是直线,那么它能模拟线性数据吗?
5.1 Preparing data to see if our model can model a straight line 准备数据,看看我们的模型是否能建模直线
创建一些线性数据来看看我们的模型是否能够对其进行建模,而不仅仅是使用一个无法学习任何东西的模型。
1 | # Create some data (same as notebook 01) |
1 | 100 |
将数据分成训练集和测试集。
1 | # Create train and test splits |
1 | 80 80 20 20 |
漂亮,让我们看看数据是什么样子的。
为此,我们将使用我们在笔记本 01 中创建的 plot_predictions() 函数。
它包含在我们上面下载的 Learn PyTorch for Deep Learning 存储库中的 helper_functions.py 脚本中。
1 | plot_predictions(train_data=X_train_regression, |
5.2 Adjusting model_1
to fit a straight line 调整 model_1
以适合直线
重新创建model_1
,但使用适合我们的回归数据的损失函数。
1 | # Same architecture as model_1 (but using nn.Sequential) |
1 | Sequential( |
将损失函数设置为nn.L1Loss()
(与平均绝对误差相同),并将优化器设置为torch.optim.SGD()
。
1 | # Loss and optimizer |
现在让我们使用常规训练循环步骤来训练模型,epochs=1000
(就像model_1
一样)。
1 | # Train the model |
1 | Epoch: 0 | Train loss: 0.75986, Test loss: 0.54143 |
好的,与分类数据上的 model_1
不同,model_2
的损失似乎实际上在下降。
让我们绘制它的预测图,看看是否如此。
请记住,由于我们的模型和数据正在使用目标设备,并且该设备可能是 GPU,因此我们的绘图函数使用 matplotlib
,而 matplotlib
无法处理 GPU 上的数据。
为了处理这个问题,当我们将所有数据传递给 plot_predictions()
时,我们将使用 .cpu()
将所有数据发送到 CPU。
1 | # Turn on evaluation mode |
模型比在直线上随机猜测要好得多。这意味着我们的模型至少具有一定的学习能力。
构建深度学习模型时,一个有用的故障排除步骤是先从尽可能小的模型开始,看看模型是否有效,然后再将其扩大。
这可能意味着从一个简单的神经网络(层数不多,隐藏神经元也不多)和一个小的数据集(就像我们制作的数据集)开始,然后在这个小例子上进行过度拟合overfitting(使模型表现得太好了),然后再增加数据量或模型 大小 / 设计 以减少过度拟合。
6. The missing piece: non-linearity 缺失的部分:非线性
由于模型具有线性层,因此它可以绘制直线(线性)。
但是我们如何赋予它绘制非直线(非线性)线条的能力呢?
6.1 Recreating non-linear data (red and blue circles) 重新创建非线性数据(红色和蓝色圆圈)
重新创建数据以从头开始。我们将使用与之前相同的设置。
1 | # Make and plot data |
太棒了!现在让我们将其分成训练集和测试集,其中 80% 的数据用于训练,20% 的数据用于测试。
1 | # Convert to tensors and split into train and test sets |
1 | (tensor([[ 0.6579, -0.4651], |
6.2 Building a model with non-linearity 建立非线性模型
可以用无限的直线(线性)和非直线(非线性)绘制什么样的图案?
到目前为止,我们的神经网络仅使用线性(直线)函数。
但我们处理的数据是非线性的(圆圈)。
当我们为模型引入使用非线性激活函数的能力时
PyTorch 有一堆现成的非线性激活函数,它们可以执行类似但不同的事情。
最常见且性能最好的一种是ReLU)(整流线性单元,torch.nn.ReLU())。
将它放在神经网络中前向传递的隐藏层之间,看看会发生什么。
1 | # Build model with non-linear activation function |
1 | CircleModelV2( |
与我们刚刚构建的分类神经网络(使用 ReLU 激活)类似的分类神经网络的视觉示例。尝试在 TensorFlow Playground 网站上创建一个您自己的神经网络。
问题:构建神经网络时,我应该把非线性激活函数放在哪里?
经验法则是将它们放在隐藏层之间,紧接着输出层,但是,没有一成不变的选择。随着您对神经网络和深度学习的了解越来越多,您会发现很多不同的组合方法。与此同时,最好不断实验、实验、再实验。
现在我们已经准备好了模型,让我们创建一个二元分类损失函数以及一个优化器。
1 | # Setup loss and optimizer |
6.3 Training a model with non-linearity 训练非线性模型
训练、模型、损失函数、优化器已准备就绪,让我们创建一个训练和测试循环。
1 | # Fit the model |
1 | Epoch: 0 | Loss: 0.69295, Accuracy: 50.00% | Test Loss: 0.69319, Test Accuracy: 50.00% |
6.4 Evaluating a model trained with non-linear activation functions 评估用非线性激活函数训练的模型
还记得我们的圆形数据是非线性的吗?好吧,让我们看看现在模型的预测结果如何,该模型已经用非线性激活函数进行了训练。
1 | # Make predictions |
1 | (tensor([1., 0., 1., 0., 0., 1., 0., 0., 1., 0.], device='cuda:0'), |
1 | # Plot decision boundaries for training and test sets |
7. Replicating non-linear activation functions 复制非线性激活函数
您在自然中遇到的大部分数据都是非线性的(或线性和非线性的组合)。现在我们一直在处理二维图上的点。但想象一下,如果您有想要分类的植物图像,会有很多不同的植物形状。或者您想要总结的维基百科文本,有很多不同的单词组合方式(线性和非线性模式)。
但是非线性激活是什么样的?我们如何复制一些并看看它们的作用如何?
1 | # Create a toy tensor (similar to the data going into our model(s)) |
1 | tensor([-10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., |
1 | # Visualize the toy tensor |
一条直线。
现在让我们看看 ReLU 激活函数如何影响它。
我们不会使用 PyTorch 的 ReLU (torch.nn.ReLU
),而是自己重新创建它。
ReLU 函数将所有负值变为 0,并保持正值不变。
1 | # Create ReLU function by hand |
1 | tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 2., 3., 4., 5., 6., 7., |
看起来我们的 ReLU 函数起作用了,所有负值都是零。
1 | # Plot ReLU activated toy tensor |
太棒了!这看起来和 ReLU 维基百科页面上的 ReLU 函数) 形状一模一样。
我们试试我们一直在使用的 sigmoid函数 怎么样?
sigmoid 函数公式如下:
Or using $x$ as input:
其中 $S$ 代表 sigmoid 函数,$e$ 代表指数(torch.exp()),$i$ 代表张量中的特定元素。
让我们用 PyTorch 构建一个函数来复制 sigmoid 函数。
1 | # Create a custom sigmoid function |
1 | tensor([4.5398e-05, 1.2339e-04, 3.3535e-04, 9.1105e-04, 2.4726e-03, 6.6929e-03, |
这些值看起来很像我们之前看到的预测概率,让我们看看它们的可视化效果。
1 | # Plot sigmoid activated toy tensor |
看起来不错!我们已经从直线变成了曲线。
现在 PyTorch 中存在许多我们尚未尝试过的非线性激活函数。
但这两个是最常见的两个。
问题仍然存在,您可以使用无限数量的线性(直线)和非线性(非直线)线来绘制什么图案?
几乎任何东西都可以,对吗?
当我们结合线性和非线性函数时,这正是我们的模型所做的事情。
我们不是告诉模型要做什么,而是给它工具来找出如何最好地发现数据中的模式。
这些工具是线性和非线性函数。
8. Putting things together by building a multi-class PyTorch model 通过构建多类 PyTorch 模型将所有内容整合在一起
使用多类分类问题将它们放在一起。
二元分类
问题是将某物归类为两个选项之一(例如,将一张照片归类为猫的照片或狗的照片)。而多类分类
问题是从两个以上的选项列表中对某物进行分类(例如,将一张照片归类为猫、狗或鸡)。
8.1 Creating multi-class classification data 创建多类别分类数据
为了开始多类分类问题,让我们创建一些多类数据。
为此,我们可以利用 Scikit-Learn
的 make_blobs() 方法。
此方法将创建我们想要的任意数量的类(使用 centers
参数)。
具体来说,我们可以这样做:
1、使用 make_blobs()
创建一些多类数据。
2、将数据转换为张量(默认 make_blobs()
使用NumPy数组)。
3、使用 train_test_split()
将数据分为训练集和测试集 。
4、使数据可视化。
1 | # Import dependencies |
1 | tensor([[-8.4134, 6.9352], |
准备好了一些多类数据。建立一个模型来分离彩色斑点。
问题:这个数据集需要非线性吗?或者你可以画出一系列直线来分离它吗?
8.2 Building a multi-class classification model in PyTorch 在 PyTorch 中构建多类分类模型
到目前为止,我们已经在 PyTorch 中创建了一些模型。
您或许还开始了解神经网络的灵活性。
如何构建一个类似model_3
但仍然能够处理多类数据的系统呢?
创建一个nn.Module
包含三个超参数的子类:
input_features
X 进入模型的特征数量。output_features
我们想要的输出特征的理想数量(这将等同于NUM_CLASSES或等于多类分类问题中的类数)。hidden_units
我们希望每个隐藏层使用的隐藏神经元的数量。
然后我们将使用上面的超参数创建模型类。
1 | # Create device agnostic code |
1 | 'cuda' |
1 | from torch import nn |
1 | BlobModel( |
8.3 Creating a loss function and optimizer for a multi-class PyTorch model 为多类 PyTorch 模型创建损失函数和优化器
由于我们正在研究多类分类问题,我们将使用该nn.CrossEntropyLoss()
方法作为我们的损失函数。
我们将坚持使用学习率为 0.1 的 SGD 来优化我们的model_4
参数。
1 | # Create loss and optimizer |
8.4 Getting prediction probabilities for a multi-class PyTorch model 获取多类 PyTorch 模型的预测概率
准备好了损失函数和优化器,并且准备好训练我们的模型,但在此之前,让我们对我们的模型进行一次前向传递,看看它是否有效。
1 | # Perform a single forward pass on the data (we'll need to put it to the target device for it to work) |
1 | tensor([[-1.2711, -0.6494, -1.4740, -0.7044], |
为每个样本的每个特征都获得了一个值。检查一下形状以确认。
1 | # How many elements in a single prediction sample? |
1 | (torch.Size([4]), 4) |
模型正在为每个类别预测一个值。
你还记得我们模型的原始输出叫什么吗?
提示:它与“frog splits”押韵(在制作这些材料时没有伤害任何动物)。
如果你猜是 logits,那你就猜对了。
所以现在我们的模型正在输出 logits,但如果我们想弄清楚样本到底是哪个标签,该怎么办?
如何从 logits
-> prediction probabilities
-> prediction labels
,就像我们处理二元分类问题一样?
这就是 softmax 激活函数 发挥作用的地方。
softmax 函数计算每个预测类相对于所有其他可能类成为实际预测类的概率。
1 | # Make prediction logits with model |
1 | tensor([[-1.2549, -0.8112, -1.4795, -0.5696], |
softmax 函数的输出可能看起来仍然是乱码(确实如此,因为我们的模型尚未经过训练,并且使用随机模式进行预测),但每个样本都有非常具体的区别。
将 logits 传递到 softmax 函数后,每个样本现在都加到 1(或非常接近)。
1 | # Sum the first sample output of the softmax activation function |
1 | tensor(1., device='cuda:0', grad_fn=<SumBackward0>) |
这些预测概率本质上说明了模型认为目标 X 样本(输入)映射到每个类的程度。
由于 y_pred_probs
中每个类都有一个值,因此最高值的索引就是模型认为特定数据样本最属于的类。
我们可以使用 torch.argmax()
检查哪个索引具有最高值。
1 | # Which class does the model think is *most* likely at the index 0 sample? |
1 | tensor([0.1872, 0.2918, 0.1495, 0.3715], device='cuda:0', |
您可以看到 torch.argmax()
的输出返回 3,因此对于索引 0 处的样本的特征 (X
),模型预测最可能的类值 (y
) 是 3。
当然,现在这只是随机猜测,所以它有 25% 的正确率(因为有四个类)。但我们可以通过训练模型来提高这些机会。
模型的原始输出称为 logits。
对于多类分类问题,要将 logits 转换为预测概率,请使用 softmax 激活函数 (torch.softmax)。
具有最高预测概率的值的索引是模型认为在给定该样本的输入特征的情况下最有可能的类号(虽然这是一个预测,但并不意味着它是正确的)。
8.5 Creating a training and testing loop for a multi-class PyTorch model 为多类 PyTorch 模型创建训练和测试循环
好了,现在我们已经完成了所有准备步骤,让我们编写一个训练和测试循环来改进和评估我们的模型。
我们之前已经完成了很多这些步骤,所以其中很多都是练习。
唯一的区别是,我们将调整步骤,将模型输出(logits
)转换为预测概率(使用softmax激活函数),然后转换为预测标签(通过取softmax激活函数输出的argmax)。
让我们训练模型epochs=100
,并每10个epochs评估一次。
1 | # Fit the model |
1 | Epoch: 0 | Loss: 1.04324, Acc: 65.50% | Test Loss: 0.57861, Test Acc: 95.50% |
8.6 Making and evaluating predictions with a PyTorch multi-class model 使用 PyTorch 多类模型进行预测并评估预测
看起来我们训练过的模型表现得相当不错。
但为了确保这一点,让我们做一些预测并将它们可视化。
1 | # Make predictions |
1 | tensor([[ 4.3377, 10.3539, -14.8948, -9.7642], |
看起来我们模型的预测仍然是 logit
形式。
但为了评估它们,它们必须与我们的标签 (y_blob_test
) 具有相同的形式,后者是整数形式。
让我们将模型的预测 logit
转换为预测概率(使用 torch.softmax()
),然后转换为预测标签(通过获取每个样本的 argmax()
)。
可以跳过
torch.softmax()
函数,直接在logits
上调用torch.argmax()
,从预测logits
->predicted labels
直接进入。
例如,y_preds = torch.argmax(y_logits, dim=1)
,这节省了一个计算步骤(没有torch.softmax()
),但导致没有可用的预测概率。
1 | # Turn predicted logits in prediction probabilities |
1 | Predictions: tensor([1, 3, 2, 1, 0, 3, 2, 0, 2, 0], device='cuda:0') |
模型预测现在与测试标签的形式相同。
使用 plot_decision_boundary()
将它们可视化,请记住,因为我们的数据在 GPU 上,所以我们必须将其移动到 CPU 以便与 matplotlib 一起使用(plot_decision_boundary()
会自动为我们执行此操作)。
1 | plt.figure(figsize=(12, 6)) |
9. More classification evaluation metrics 更多分类评估指标
到目前为止,我们仅介绍了评估分类模型的几种方法(准确性、损失和可视化预测)。
这些是您会遇到的一些最常见的方法,并且是一个很好的起点。
可能希望使用更多指标来评估分类模型,例如:
指标名称/评估方法 | 定义 | 代码 |
---|---|---|
预测精度Accuracy | 在 100 个预测中,您的模型有多少个预测正确?例如,95% 的准确率意味着 100 个预测中有 95 个正确。 | torchmetrics.Accuracy() or sklearn.metrics.accuracy_score() |
准确率Precision | 真阳性与样本总数的比例。精度越高,假阳性越少(模型预测为 1,但实际应该是 0)。 | torchmetrics.Precision() or sklearn.metrics.precision_score() |
召回 Recall | 真阳性占真阳性和假阴性总数的比例(模型预测为 0,但实际应为 1)。召回率越高,假阴性越少。 | torchmetrics.Recall() or sklearn.metrics.recall_score() |
F1分数 F1-score | 将精度和召回率结合为一个指标。1 表示最好,0 表示最差。 | torchmetrics.F1Score() or sklearn.metrics.f1_score() |
混淆矩阵 Confusion matrix | 以表格方式将预测值与真实值进行比较,如果 100% 正确,矩阵中的所有值将从左上角到右下角(对角线)。 | torchmetrics.ConfusionMatrix or sklearn.metrics.plot_confusion_matrix() |
分类报告 Classification report | 收集一些主要的分类指标,例如精确度、召回率和 f1 分数。 | sklearn.metrics.classification_report() |
Scikit-Learn(一个流行的、世界一流的机器学习库)对上述指标有许多实现,如果你正在寻找一个类似 PyTorch 的版本,请查看 TorchMetrics,尤其是 TorchMetrics 分类部分。
尝试一下 torchmetrics.Accuracy
指标。
1 | try: |
1 | tensor(0.9950, device='cuda:0') |
Exercises
所有练习都集中于练习以上部分中的代码。
您应该能够通过参考每个部分或按照链接的资源来完成它们。
所有练习都应使用设备激动代码来完成。
资源:
- 练习模板笔记本02
- 02 的示例解决方案笔记本(在查看之前先尝试练习)
使用 Scikit-Learn 的函数创建二元分类数据集
make_moons()
。- 为了一致性,数据集应该有 1000 个样本和一个
random_state=42
。 - 将数据转换为 PyTorch 张量。将数据分为训练集和测试集,
train_test_split
其中 80% 用于训练,20% 用于测试。
- 为了一致性,数据集应该有 1000 个样本和一个
通过子类化构建一个模型
nn.Module
,该模型包含非线性激活函数,并且能够拟合您在 1 中创建的数据。- 请随意使用您想要的 PyTorch 层(线性和非线性)的任意组合。
设置二元分类兼容的损失函数和优化器,以便在训练模型时使用。
创建一个训练和测试循环,以使您在 2 中创建的模型适合您在 1 中创建的数据。
- 为了测量模型准确性,您可以创建自己的准确性函数或使用TorchMetrics中的准确性函数。
- 对模型进行足够长时间的训练,以达到 96% 以上的准确率。
- 训练循环应该每 10 个时期输出一次模型训练和测试集损失和准确率的进度。
使用训练好的模型进行预测,并使用
plot_decision_boundary()
此笔记本中创建的函数绘制它们。在纯 PyTorch 中复制 Tanh(双曲正切)激活函数。
- 请随意参考ML 备忘单网站来获取该公式。
使用CS231n 中的螺旋数据创建功能 创建多类数据集(代码见下文)。
- 构建一个能够拟合数据的模型(您可能需要线性和非线性层的组合)。
- 构建一个能够处理多类数据的损失函数和优化器(可选扩展:使用 Adam 优化器而不是 SGD,您可能必须尝试不同的学习率值才能使其发挥作用)。
- 对多类数据进行训练和测试循环,并在其上训练模型以达到 95% 以上的测试准确率(您可以在此处使用任何您喜欢的准确率测量函数)。
- 根据模型预测在螺旋数据集上绘制决策边界,该
plot_decision_boundary()
函数也适用于该数据集。
1 | # Code for creating a spiral dataset from CS231n |
Extra-curriculum 课外活动
- 写下 3 个您认为机器分类可能有用的问题(可以是任何问题,您可以发挥创造力,例如,根据购买金额和购买地点特征将信用卡交易分类为欺诈或非欺诈)。
- 研究基于梯度的优化器(如 SGD 或 Adam)中的“动量”概念,它是什么意思?
- 花 10 分钟阅读Wikipedia 上关于不同激活函数的页面,其中有多少个你能与PyTorch 的激活函数相媲美?
- 研究何时准确度可能不是一个好的衡量标准(提示:阅读Will Koehrsen 的《超越准确度》来获取想法)。
- 观看:要了解我们的神经网络内部发生的情况以及它们如何学习,请观看麻省理工学院的深度学习简介视频。