前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >相对分数和绝对分数

相对分数和绝对分数

作者头像
不可言诉的深渊
发布2022-11-11 15:06:21
6830
发布2022-11-11 15:06:21
举报
文章被收录于专栏:Python机器学习算法说书人

CLICK ON THE BLUE WORDS ABOVE TO FOLLOW US

众所周知,scikit-learn 中有一个计算 AUC 值的函数,它就是 roc_auc_score。在二分类中,我们只需要给它两个参数,第一个参数是一个元素取值为 {0, 1} 的一维数组,表示该样本是属于正类还是反类;第二个参数是该样本对应的分数(不仅可以是 prob,而且可以是 logit)。

问题

我们有些时候会需要在多分类的时候考察某一类 C 对应 binary 值的 AUC,其中 binary 值是一个取值为 {0, 1} 的一维数组,记作 binary 数组。如果当前样本属于 C 类,那么 binary 数组对应位置值为 1,否则为 0。

这里在考察 AUC 的时候我们难免会去使用之前提到的函数,这个函数的第一个参数已经出来了,就是上面提到的 binary 数组,可是第二个参数我们应该填写什么就成了一个非常显著的问题,因为我们既可以给它对应的 logit,也可以给它对应的 prob。如果给它 logit 这很好办,直接取对应的 logit 送给它就可以了,但是要是给了它对应的 prob 就需要分类讨论,我是基于选到的 logit 应用 sigmoid 函数后拿到的 prob 还是先对所有类的 logit 应用 softmax 函数再去选择对应的 prob。

因为考虑到 AUC 在计算过程中会把第二个参数(也就是所谓的分数)进行排序,并且不管有没有应用 sigmoid 函数都不会改变原来 logit 的顺序,所以应用 sigmoid 函数和没有应用该函数得出的 AUC 值是一样的,我们就重点看一下 logit 和通过 softmax 得到的 prob 在多分类中计算某一类的 AUC 值的时候会有什么区别。

现象

在这里,我以手写数字为例,考察一下每一类对应的 binary 值的 AUC,我在这里就不拿每一类的 AUC 单独分析,而是首先简单地看一下通过 logit 计算出来的 AUC 值和通过 softmax 得到的 prob 计算的 AUC 值,哪一个会先一步把所有类的 AUC 都超过 0.9。代码如下:

代码语言:javascript
复制
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score, roc_auc_score
from sklearn.model_selection import train_test_split
from torch import nn
import numpy as np
import torch
gpu_available = torch.cuda.is_available()
seed = 0
np.random.seed(seed)
torch.manual_seed(seed)
if gpu_available:
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
X, y = load_digits(return_X_y=True)
n_class = y.max()+1
loss_func = nn.CrossEntropyLoss()
mlp = nn.Sequential(nn.Linear(X.shape[1], 16), nn.ReLU(), nn.Linear(16, n_class))
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=seed)
X_train, X_test = torch.FloatTensor(X_train), torch.FloatTensor(X_test)
y_train, y_test = torch.LongTensor(y_train), torch.LongTensor(y_test)
if gpu_available:
    loss_func, mlp = loss_func.cuda(), mlp.cuda()
    X_train, X_test, y_train, y_test = X_train.cuda(), X_test.cuda(), y_train.cuda(), y_test.cuda()
optimizer = torch.optim.Adam(mlp.parameters(), weight_decay=1e-4)
epochs = 300
dict_aucs = {'logit AUC 0.9': {'epoch': 0, 'accuracy': 0.0}, 'prob AUC 0.9': {'epoch': 0, 'accuracy': 0.0}}
for epoch in range(1, epochs+1):
    mlp.train()
    pred_logit = mlp(X_train)
    loss = loss_func(pred_logit, y_train)
    optimizer.zero_grad(True)
    loss.backward()
    optimizer.step()
    mlp.eval()
    with torch.no_grad():
        pred_logit = mlp(X_test)
    accuracy = accuracy_score(y_test.tolist(), pred_logit.argmax(1).tolist())
    print(f'epoch = {epoch}, accuracy = {accuracy}')
    logit_aucs = [roc_auc_score(np.array(y_test.tolist()) == _, pred_logit[:, _].tolist())for _ in range(n_class)]
    if not dict_aucs['logit AUC 0.9']['epoch'] and min(logit_aucs) > 0.9:
        dict_aucs['logit AUC 0.9']['epoch'], dict_aucs['logit AUC 0.9']['accuracy'] = epoch, accuracy
    pred_prob = pred_logit.softmax(1)
    prob_aucs = [roc_auc_score(np.array(y_test.tolist()) == _, pred_prob[:, _].tolist())for _ in range(n_class)]
    if not dict_aucs['prob AUC 0.9']['epoch'] and min(prob_aucs) > 0.9:
        dict_aucs['prob AUC 0.9']['epoch'], dict_aucs['prob AUC 0.9']['accuracy'] = epoch, accuracy
print(dict_aucs)

在代码中,我在 dict_aucs 字典的 dict_aucs['logit AUC 0.9']['epoch'] 中存放基于 logit 计算出来的 AUC 在第几个 epoch 全部超过 0.9,在 dict_aucs['prob AUC 0.9']['epoch'] 中存放基于通过 softmax 计算得到的 prob 作为分数来计算的所有 AUC 在第几个 epoch 全部超过 0.9。

运行结果如图所示:

我们可以发现基于 logit 算出来的 AUC 要想全部达到一个理想的值(在这里是 0.9)要比基于通过 softmax 得到的 prob 算出来的 AUC 全部达到一个理想的值要困难很多,毕竟额外经过了 50 个左右的 epoch。当然有些人会认为这里只有一次随机实验,并不能认为这个结论是正确的,那么下面我就给出 seed 分别取 0,1,2,3,4 的结果。

seed

logit AUC 0.9 epoch

prob AUC 0.9 epoch

0

126

78

1

101

60

2

119

58

3

173

46

4

161

82

我们可以发现这和我们所假设的完全不符合,因为我们假设两个 AUC 是不会有太大差距的。

原因

在揭示了这一现象之后,我们需要找一下其中的原因。我们首先想到通过 softmax 得到的 prob 是结合了该样本其余类的 logit,很明显这可以作为它们之间差距太大的一个理由,但是这绝对不可以解释为什么基于 logit 算出来的 AUC 要想全部达到一个理想的值(在这里是 0.9)要比基于通过 softmax 得到的 prob 算出来的 AUC 全部达到一个理想的值要困难很多?

其原因我也是想了很久很久,在这里分享一下我的观点(不一定正确)。

我们首先假设有一个样本,该样本属于类别 0,其计算出来的 logit 如下所示(实际上不可能这么巧,这里旨在举例说明):

类别

logit

0

10

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

1

通过对其应用 softmax 之后很明显排序会正常(类别 0 足够大,其他类别足够小),但是如果直接基于 logit 去算 AUC 的时候就会有问题,类别 0 的 logit 比较大,排在前面没有任何毛病。然而其他类别的 logit 真的很小吗?基于 logit 算其他类别的 AUC 值的时候该样本真的能排到后面去吗?我们希望除了类别 0 的 logit 越大越好,当然也要希望其他类别的 logit 越小越好。在这里其他类别的 logit 都是大于 0 的,我们都知道 logit 大于 0 表示该样本属于这一类的概率大于该样本不属于这一类的概率,该样本很明显是类别 0,所以类别 0 的 logit 没有任何问题,但是其他类别的 logit 存在严重的问题,在这里我通过类别 1 的 logit 为例进行一下说明,类别 1 的 logit>0,这意味着模型认为该样本属于类别 1 的概率要大于不属于类别 1 的概率,很明显这已经不是很不合理了,而这实在是非常不合理!

当然还可以举出一个例子,依旧假设该样本属于类别 0,和上面的例子异曲同工,我就直接放在下表中,大家可以自行做一下分析。

类别

logit

0

-1

1

-10

2

-10

3

-10

4

-10

5

-10

6

-10

7

-10

8

-10

9

-10

究其原因,其本质上是相对和绝对的区别。看第一个示例,我们可以发现其他类别的 logit 相对于类别 0 的 logit 确实很小了,但是如果将其他类别的 logit 放在整个领域中,那么它们的 logit 都会比较大。类似地看第二个示例,我们可以发现类别 0 的 logit 相对于其他类别的 logit 确实很大了,但如果将类别 0 放在整个领域中,那么类别 0 的 logit 比较小。因此,我们把 logit 叫做绝对分数,把通过 softmax 得到的 prob 叫做相对分数。

正是因为有着上述两种情况的存在,所以基于 logit 算出来的 AUC 要想全部达到一个理想的值要比基于通过 softmax 得到的 prob 算出来的 AUC 全部达到一个理想的值要困难很多。

结论

通过以上分析,可以得出以下结论:

  1. 人类对客观世界的认识看作是由相对真理走向绝对真理的过程,模型对数据的认识也是从相对走向绝对的过程!
  2. 无论是对我们而言还是对模型而言,达到相对理想的状态很简单,但达到绝对理想的状态很难!
  3. 人外有人,天外有天,学无止境!
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档