PyTorch 神经网络分类
Reference: PyTorch Neural Network Classification
该页面由 Jupyter Notebook 生成,原文件于 Github
# 先导入包
import torch
from torch import nn
import matplotlib.pyplot as plt
torch.__version__'2.5.1+cu124'
分类问题有二分类、多分类、多标签等情况。二分类问题则是或不是;多分类问题具有多个类别区分;多标签问题则一个目标可以被分配多个选项。
分类神经网络的一般架构:
| 项目 | 二分类 | 多分类 | 
|---|---|---|
| 输入层 Shape( in_features) | 与特征数相同 | 与特征数相同 | 
| 隐藏层 | 特定问题特定分析 | 特定问题特定分析 | 
| 每个隐藏层的神经元数量 | 特定问题特定分析,一般从 10 到 512 | 特定问题特定分析,一般从 10 到 512 | 
| 输出层 Shape( out_features) | 1(一个类别) | 每个类 1 个输出 | 
| 隐藏层激活函数 | 通常是 ReLU | 通常是 ReLU | 
| 输出层激活函数 | Sigmoid( torch.sigmoid) | Softmax( torch.softmax) | 
| 损失函数 | 二元交叉熵( torch.nn.BCELoss) | 交叉熵( torch.nn.CrossEntropyLoss) | 
| 优化器 | SGD,Adam | SGD,Adam | 
使用 Scikit-Learn 中的 make_circles() 方法生成两个带有不同颜色圆点的圆。
需要安装 Scikit-Learn:pip install scikit-learn
from sklearn.datasets import make_circles
n_samples = 1000
X, y = make_circles(n_samples,
                    noise=0.03,
                    random_state=42)
plt.scatter(x=X[:, 0],
            y=X[:, 1],
            c=y,
            cmap=plt.cm.RdYlBu)<matplotlib.collections.PathCollection at 0x22c9fc26610>
看看输入 Shape 和 输出 Shape,然后弄清楚输入层 Shape(特征数)和输出层 Shape。
X.shape, y.shape # 输入 Shape 和输出 Shape((1000, 2), (1000,))
X[0].shape, y[0].shape # 输入层 Shape 和输出层 Shape((2,), ())
这说明 X 的一个样本有两个特征(向量),而对应的 y 只有一个特征(标量)。
- 有两个输入对应一个输出。
具体来说:
- 将数据转换为张量。
- 将数据分成训练集和测试集。
X = torch.from_numpy(X).type(torch.float)
y = torch.from_numpy(y).type(torch.float)
X.dtype, y.dtype(torch.float32, torch.float32)
使用 Scikit-Learn 中的函数 train_test_split()。
test_size=0.2(80%训练,20%测试),因为分割是随机发生的,所以使用 random_state=42,使得随机可复现。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,
                                                    y,
                                                    test_size=0.2,
                                                    random_state=42)
len(X_train), len(y_train), len(X_test), len(y_test)(800, 800, 200, 200)
构建模型的步骤:
- 设置与设备相关的代码。
- 通过继承 nn. module来构造一个模型。
- 定义损失函数和优化器。
- 创建一个训练循环。
# 1. 设置设备
device = "cuda" if torch.cuda.is_available() else "cpu"
device'cuda'
模型类的操作:
- 继承 nn.Module。
- 在构造函数中创建 2 层 nn.Linear线性层,能够处理 X 和 y 的形状。
- 定义一个 forward()方法,该方法包含模型的前向传递计算。
- 实例化模型类并将其发送到目标设备。
class CircleModelV0(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=5)
        self.layer_2 = nn.Linear(in_features=5, out_features=1)
    def forward(self, x):
        return self.layer_2(self.layer_1)
model_0 = CircleModelV0().to(device)
model_0CircleModelV0( (layer_1): Linear(in_features=2, out_features=5, bias=True) (layer_2): Linear(in_features=5, out_features=1, bias=True) )
由上面代码可知该模型类的结构为: 2(输入层) -> 5(隐藏层) -> 1(输出层)
也可以使用 nn.Sequential 执行与上面相同的操作。nn.Sequential 按层出现的顺序对输入数据执行前向传递计算。
model_0 = nn.Sequential(
    nn.Linear(in_features=2, out_features=5),
    nn.Linear(in_features=5, out_features=1)
).to(device)
model_0Sequential( (0): Linear(in_features=2, out_features=5, bias=True) (1): Linear(in_features=5, out_features=1, bias=True) )
自定义模型类可以自定义更多细节,而 nn.Sequential() 则更方便。
常见损失函数:
| 损失函数 | 适用类型 | 代码 | 
|---|---|---|
| 交叉熵损失函数 | 多分类 | torch.nn.CrossEntropyLoss | 
| 平均绝对误差 MAE,L1 Loss | 回归问题 | torch.nn.L1Loss | 
| 均方误差 MSE,L2 Loss | 回归问题 | torch.nn.MSELoss | 
常见优化器:
| 优化器 | 适用类型 | 代码 | 
|---|---|---|
| 随机梯度下降(SGD) | 分类问题、回归问题等 | torch.optim.SGD() | 
| Adam | 分类问题、回归问题等 | torch.optim.Adam() | 
此处讨论二分类问题,使用一个二元交叉熵损失函数。
注意:损失函数是衡量模型预测错误程度的函数,损失越高,模型越差。
此外,PyTorch 文档经常将损失函数称为“损失准则(loss criterion)”或“准则(criterion)”,这些都是描述同一事物的不同方式。
二元交叉熵函数有 torch.nn.BCELoss() 和 torch.nn.BCEWithLogitsLoss()。
- torch.nn.BCELoss():创建一个损失函数,用于测量目标(标签)和输入(特征)之间的二进制交叉熵。
- torch.nn.BCEWithLogitsLoss():它内置了一个 sigmoid 层,其他这与上面的相同。
torch.nn.BCEWithLogitsLoss() 的文档指出,它比在 nn.Sigmoid 层之后使用 torch.nn.BCELoss() 在数值上更稳定。
对于优化器,将使用 torch.optim.SGD() 以 0.1 的学习率优化模型参数。
loss_fn = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(params=model_0.parameters(),
                            lr=0.1)评估指标可用于提供关于模型运行情况的另一个视角。如果一个损失函数衡量模型的错误程度,那么也有评估指标衡量他的正确程度。
def accuracy_fn(y_true, y_pred):
    correct = torch.eq(y_true, y_pred).sum().item()
    acc = (correct / len(y_pred)) * 100
    return acc线性层的公式为:
$$ y = x \cdot \text{Weights}^T + bias $$模型的原始输出通常被称为logits。使用激活函数将 logits 转换成与真值标签相比较的数字。
torch.manual_seed(42)
epochs = 100
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)
model_0 = model_0.to(device)
for epoch in range(epochs):
    model_0.train()
    y_logits = model_0(X_train).squeeze()
    y_pred = torch.round(torch.sigmoid(y_logits))
    loss = loss_fn(y_logits, y_train)
    acc = accuracy_fn(y_true=y_train, y_pred=y_pred)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    model_0.eval()
    with torch.inference_mode():
        test_logits = model_0(X_test).squeeze()
        test_pred = torch.round(torch.sigmoid(test_logits))
        test_loss = loss_fn(test_logits, y_test)
        test_acc = accuracy_fn(y_true=y_test, y_pred=test_pred)
    if epoch % 10 == 0:
        print(f"{epoch}/{epochs} | Loss: {loss:.5f}, Accu: {acc:.2f}% | Test Loss: {test_loss:.5f}, Test Accu: {test_acc:.2f}%")
    0/100 | Loss: 0.70365, Accu: 49.88% | Test Loss: 0.71542, Test Accu: 45.50%
10/100 | Loss: 0.70059, Accu: 50.25% | Test Loss: 0.71142, Test Accu: 45.00%
20/100 | Loss: 0.69869, Accu: 50.38% | Test Loss: 0.70858, Test Accu: 46.00%
30/100 | Loss: 0.69740, Accu: 50.75% | Test Loss: 0.70641, Test Accu: 46.00%
40/100 | Loss: 0.69648, Accu: 50.75% | Test Loss: 0.70469, Test Accu: 45.50%
50/100 | Loss: 0.69580, Accu: 50.50% | Test Loss: 0.70329, Test Accu: 46.50%
60/100 | Loss: 0.69527, Accu: 50.75% | Test Loss: 0.70214, Test Accu: 46.00%
70/100 | Loss: 0.69487, Accu: 50.88% | Test Loss: 0.70117, Test Accu: 45.50%
80/100 | Loss: 0.69455, Accu: 50.75% | Test Loss: 0.70036, Test Accu: 45.00%
90/100 | Loss: 0.69429, Accu: 50.75% | Test Loss: 0.69966, Test Accu: 45.00%
模型看起来它很好地完成了训练和测试步骤,但结果似乎并没有太大的变化。每次数据分割时,准确率在 50% 左右。这是一个平衡的二进制分类问题,这意味着模型的性能与随机猜测差不多。
从指标来看,模型似乎是随机猜测。绘制一个模型预测的图,它试图预测的数据以及它为某个东西是类0还是类1创建的决策边界。
为此,编写一些代码,一个名为 plot_decision_boundary() 的有用函数,该函数创建一个 NumPy meshgrid,以可视化地绘制我们的模型预测某些类的不同点。
import numpy as np
def plot_decision_boundary(model: torch.nn.Module, X: torch.Tensor, y: torch.Tensor):
    """Plots decision boundaries of model predicting on X in comparison to y.
    Source - https://madewithml.com/courses/foundations/neural-networks/ (with modifications)
    """
    # Put everything to CPU (works better with NumPy + Matplotlib)
    model.to("cpu")
    X, y = X.to("cpu"), y.to("cpu")
    # Setup prediction boundaries and grid
    x_min, x_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
    y_min, y_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 101), np.linspace(y_min, y_max, 101))
    # Make features
    X_to_pred_on = torch.from_numpy(np.column_stack((xx.ravel(), yy.ravel()))).float()
    # Make predictions
    model.eval()
    with torch.inference_mode():
        y_logits = model(X_to_pred_on)
    # Test for multi-class or binary and adjust logits to prediction labels
    if len(torch.unique(y)) > 2:
        y_pred = torch.softmax(y_logits, dim=1).argmax(dim=1)  # mutli-class
    else:
        y_pred = torch.round(torch.sigmoid(y_logits))  # binary
    # Reshape preds and plot
    y_pred = y_pred.reshape(xx.shape).detach().numpy()
    plt.contourf(xx, yy, y_pred, cmap=plt.cm.RdYlBu, alpha=0.7)
    plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.RdYlBu)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_0, X_train, y_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_0, X_test, y_test)由图可知,模型目前正在尝试用直线分割红点和蓝点。由于我们的数据是圆形的,所以画一条直线最多只能把它从中间切开。
在机器学习方面,模型是欠拟合的,这意味着它没有从数据中学习预测模式。
尝试解决模型的拟合不足问题。
- 专注于模型(而不是数据)。
| 技巧 | 作用 | 
|---|---|
| 增加更多隐藏层 | 每一层都可能增加模型的学习能力,每一层都能够学习数据中的某种新模式。更多的层通常被称为使神经网络更深 | 
| 增加更多隐藏神经元 | 与上面类似,每层更多的隐藏单元意味着模型学习能力的潜在增加。更多的隐藏单元通常被称为使你的神经网络更宽 | 
| 增加训练轮数 | 如果模型训练更久,它可能会学到更多 | 
| 改变激活函数 | 有些数据无法仅用直线拟合,使用非线性激活函数可以帮助解决这个问题 | 
| 改变学习率 | 优化器的学习率决定了模型每一步应该改变多少参数,太多了模型会过度校正,太少了模型学习不足 | 
| 改变损失函数 | 不同的问题需要不同的损失函数 | 
| 迁移学习 | 从一个与问题领域相似的问题领域中提取一个预先训练好的模型,并根据问题进行调整 | 
class CircleModelV1(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=10)
        self.layer_2 = nn.Linear(in_features=10, out_features=10)
        self.layer_3 = nn.Linear(in_features=10, out_features=1)
        self.relu = nn.ReLU()
    def forward(self, x):
        return self.layer_3(self.relu(self.layer_2(self.relu(self.layer_1(x)))))
model_1 = CircleModelV1().to(device)
# model_1 = nn.Sequential(
#     nn.Linear(2, 10),
#     nn.ReLU(),
#     nn.Linear(10, 10),
#     nn.ReLU(),
#     nn.Linear(10, 1),
# ).to(device)
model_1CircleModelV1( (layer_1): Linear(in_features=2, out_features=10, bias=True) (layer_2): Linear(in_features=10, out_features=10, bias=True) (layer_3): Linear(in_features=10, out_features=1, bias=True) (relu): ReLU() )
loss_fn = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(params=model_1.parameters(),
                            lr=0.1)重新训练:
torch.manual_seed(42)
epochs = 1500
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)
model_1.to(device)
for epoch in range(epochs):
    model_1.train()
    y_logits = model_1(X_train).squeeze()
    y_pred = torch.round(torch.sigmoid(y_logits))
    
    loss = loss_fn(y_logits, y_train)
    acc = accuracy_fn(y_true=y_train, y_pred=y_pred)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    model_1.eval()
    with torch.inference_mode():
        test_logits = model_1(X_test).squeeze()
        test_pred = torch.round(torch.sigmoid(test_logits))
        test_loss = loss_fn(test_logits, y_test)
        test_acc = accuracy_fn(y_true=y_test, y_pred=test_pred)
    if epoch % 100 == 0:
        print(f"{epoch}/{epochs} | Loss: {loss:.5f}, Accu: {acc:.2f}% | Test Loss: {test_loss:.5f}, Test Accu: {test_acc:.2f}%")
    0/1500 | Loss: 0.69295, Accu: 50.00% | Test Loss: 0.69319, Test Accu: 50.00%
100/1500 | Loss: 0.69115, Accu: 52.88% | Test Loss: 0.69102, Test Accu: 52.50%
200/1500 | Loss: 0.68977, Accu: 53.37% | Test Loss: 0.68940, Test Accu: 55.00%
300/1500 | Loss: 0.68795, Accu: 53.00% | Test Loss: 0.68723, Test Accu: 56.00%
400/1500 | Loss: 0.68517, Accu: 52.75% | Test Loss: 0.68411, Test Accu: 56.50%
500/1500 | Loss: 0.68102, Accu: 52.75% | Test Loss: 0.67941, Test Accu: 56.50%
600/1500 | Loss: 0.67515, Accu: 54.50% | Test Loss: 0.67285, Test Accu: 56.00%
700/1500 | Loss: 0.66659, Accu: 58.38% | Test Loss: 0.66322, Test Accu: 59.00%
800/1500 | Loss: 0.65160, Accu: 64.00% | Test Loss: 0.64757, Test Accu: 67.50%
900/1500 | Loss: 0.62362, Accu: 74.00% | Test Loss: 0.62145, Test Accu: 79.00%
1000/1500 | Loss: 0.56818, Accu: 87.75% | Test Loss: 0.57378, Test Accu: 86.50%
1100/1500 | Loss: 0.48153, Accu: 93.50% | Test Loss: 0.49935, Test Accu: 90.50%
1200/1500 | Loss: 0.37056, Accu: 97.75% | Test Loss: 0.40595, Test Accu: 92.00%
1300/1500 | Loss: 0.25458, Accu: 99.00% | Test Loss: 0.30333, Test Accu: 96.50%
1400/1500 | Loss: 0.17180, Accu: 99.50% | Test Loss: 0.22108, Test Accu: 97.50%
可视化一下:
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_1, X_train, y_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_1, X_test, y_test)现在模型的分类就有了显著的效果。
利用 Scikit-Learn 的 make_blobs() 方法。这个方法将创建任意数量的类(使用 centers 参数)。
- 使用 make_blobs()创建一些多类数据。
- 将数据转换为张量( make_blobs()的默认值是使用 NumPy 数组)。
- 使用 train_test_split()将数据拆分为训练集和测试集。
- 可视化数据。
import torch
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
NUM_CLASSES = 4
NUM_FEATURES = 2
RANDOM_SEED = 42
X_blob, y_blob = make_blobs(n_samples=1000,
                            n_features=NUM_FEATURES,
                            centers=NUM_CLASSES,
                            cluster_std=1.5,
                            random_state=RANDOM_SEED)
X_blob = torch.from_numpy(X_blob).type(torch.float)
y_blob = torch.from_numpy(y_blob).type(torch.LongTensor)
X_blob_train, X_blob_test, y_blob_train, y_blob_test = train_test_split(X_blob,
                                                                        y_blob,
                                                                        test_size=0.2,
                                                                        random_state=RANDOM_SEED)
plt.figure(figsize=(10, 6))
plt.scatter(X_blob[:, 0], X_blob[:, 1], c=y_blob, cmap=plt.cm.RdYlBu)
                                                                        <matplotlib.collections.PathCollection at 0x22cb30b01d0>
创建一个 nn.Module 的子类,接受三个超参数:
- input_features:输入特征的数量。
- output_features:输出特征数(等效于- NUM_CLASSES或多类分类问题中的类数)。
- hidden_units:每个隐藏层使用的隐藏神经元的数量。
device = "cuda" if torch.cuda.is_available() else "cpu"
from torch import nn
class BlobModel(nn.Module):
    def __init__(self, input_features, output_features, hidden_units=8):
        super().__init__()
        self.model = nn.Sequential(
            nn.Linear(in_features=input_features, out_features=hidden_units),
            nn.ReLU(),
            nn.Linear(in_features=hidden_units, out_features=hidden_units),
            nn.ReLU(),
            nn.Linear(in_features=hidden_units, out_features=output_features)
        )
    def forward(self, x):
        return self.model(x)
model_2 = BlobModel(input_features=NUM_FEATURES,
                    output_features=NUM_CLASSES,
                    hidden_units=8).to(device)
model_2BlobModel(
  (model): Sequential(
    (0): Linear(in_features=2, out_features=8, bias=True)
    (1): ReLU()
    (2): Linear(in_features=8, out_features=8, bias=True)
    (3): ReLU()
    (4): Linear(in_features=8, out_features=4, bias=True)
  )
)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model_2.parameters(),
                           lr=0.1)试着看看模型前向输出:
y_logits = model_2(X_blob_train.to(device))[:5]
y_logitstensor([[-0.7586, -0.6810, -1.5180, -1.1178],
        [-0.2398, -1.2335, -0.9858, -0.2899],
        [ 0.2528, -0.2379,  0.1882, -0.0066],
        [ 0.2391, -0.2472,  0.1494,  0.0213],
        [-0.1214, -0.9804, -0.6918, -0.1923]], device='cuda:0',
       grad_fn=<SliceBackward0>)
再看看经过激活函数 Softmax 之后的结果:
y_pred_probs = torch.softmax(y_logits, dim=1)
y_pred_probstensor([[0.3080, 0.3328, 0.1441, 0.2150],
        [0.3577, 0.1324, 0.1696, 0.3402],
        [0.3011, 0.1843, 0.2823, 0.2323],
        [0.3000, 0.1845, 0.2743, 0.2413],
        [0.3424, 0.1450, 0.1936, 0.3190]], device='cuda:0',
       grad_fn=<SoftmaxBackward0>)
经过 Softmax 函数之后,先前的数字变成预测到某类的概率。这些预测概率本质上是说模型认为目标样本(输入)映射到每个类的程度。
由于y_pred_probs中的每个类都有一个值,因此最高值的索引是模型认为特定数据样本最属于的类。
可以使用 torch.argmax() 检查哪个索引具有最高值。
torch.argmax(y_pred_probs[0])tensor(1, device='cuda:0')
torch.manual_seed(42)
epochs = 100
X_blob_train, y_blob_train = X_blob_train.to(device), y_blob_train.to(device)
X_blob_test, y_blob_test = X_blob_test.to(device), y_blob_test.to(device)
model_2.to(device)
for epoch in range(epochs):
    model_2.train()
    y_logits = model_2(X_blob_train)
    y_pred = torch.softmax(y_logits, dim=1).argmax(dim=1)
    loss = loss_fn(y_logits, y_blob_train)
    acc = accuracy_fn(y_true=y_blob_train, y_pred=y_pred)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    model_2.eval()
    with torch.inference_mode():
        test_logits = model_2(X_blob_test)
        test_pred = torch.softmax(test_logits, dim=1).argmax(dim=1)
        test_loss = loss_fn(test_logits, y_blob_test)
        tess_acc = accuracy_fn(y_true=y_blob_test, y_pred=test_pred)
    if epoch % 10 == 0:
        print(f"{epoch}/{epochs} | Loss: {loss:.5f}, Acc: {acc:.2f}% | Test Loss: {test_loss:.5f}, Test Acc: {test_acc:.2f}%")0/100 | Loss: 1.15883, Acc: 40.38% | Test Loss: 1.07554, Test Acc: 99.00%
10/100 | Loss: 0.64476, Acc: 96.75% | Test Loss: 0.66069, Test Acc: 99.00%
20/100 | Loss: 0.42535, Acc: 98.50% | Test Loss: 0.43074, Test Acc: 99.00%
30/100 | Loss: 0.25294, Acc: 99.12% | Test Loss: 0.24508, Test Acc: 99.00%
40/100 | Loss: 0.11232, Acc: 99.25% | Test Loss: 0.10229, Test Acc: 99.00%
50/100 | Loss: 0.06627, Acc: 99.25% | Test Loss: 0.05848, Test Acc: 99.00%
60/100 | Loss: 0.05068, Acc: 99.25% | Test Loss: 0.04293, Test Acc: 99.00%
70/100 | Loss: 0.04300, Acc: 99.25% | Test Loss: 0.03491, Test Acc: 99.00%
80/100 | Loss: 0.03836, Acc: 99.25% | Test Loss: 0.02988, Test Acc: 99.00%
90/100 | Loss: 0.03525, Acc: 99.25% | Test Loss: 0.02663, Test Acc: 99.00%
使用准确率评估:
model_2.eval()
with torch.inference_mode():
    y_logits = model_2(X_blob_test)
y_preds = torch.softmax(y_logits, dim=1).argmax(dim=1)
print(f"Test accuracy: {accuracy_fn(y_true=y_blob_test, y_pred=y_preds)}%")Test accuracy: 99.5%
可视化评估:
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_2, X_blob_train, y_blob_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_2, X_blob_test, y_blob_test)| 评估指标 | 定义 | 代码 | 
|---|---|---|
| 正确率 | 模型预测正确的占比 | torchmetrics.Accuracy()或sklearn.metrics.accuracy_score() | 
| 精确率 | $\text{Precision}=\frac {TP}{TP+FP}$ | torchmetrics.Precision()或sklearn.metrics.precision_score() | 
| 召回率 | $\text{Recall}=\frac {TP}{TP+FN}$ | torchmetrics.Recall()或sklearn.metrics.recall_score() | 
| F1-Score | 将查准率和查全率合并为一个指标。1 是最好的,0 是最坏的 | torchmetrics.F1Score()或sklearn.metrics.f1_score() | 
| 混淆矩阵 | 以表格方式将预测值与真实值进行比较,如果100%正确,矩阵中的所有值将从左上角到右下角(对角线) | torchmetrics.ConfusionMatrix或sklearn.metrics.plot_confusion_matrix() | 
| 分类报告 | 一些主要分类指标的集合,如精度,召回率和f1分数 | sklearn.metrics.classification_report() |