通过混淆矩阵计算指标的过程梳理

发布于:2022-12-13 ⋅ 阅读:(856) ⋅ 点赞:(0)

本文提到三点,第一是output和target怎么样形成一个混淆矩阵。第二是tensor中特定行和列的删除问题,第三是怎么通过混淆矩阵计算acc和miou的问题。

在计算miou时,老是会出现零值,导致代码运行不下去,遂记录解决问题的方案。

用来transformers代码跑了自己的六分类的数据集。有个疑问一直还没解决:语义分割时背景一定要算到类别里面去吗,这个类别该怎么处理?我的数据集并没说明这一点,我正是在这一点上出了问题。我设置num_cls为6时,总会报错:有一类超出了。设置为7就可以正常跑了,但是在混淆矩阵可以看到0这一类预测值和真实值会出现全是0的情况,导致混淆矩阵没法计算iou,global_acc,acc等指标,错误的混淆矩阵如下所示(真心求大神指点交流这个疑问)

 

在计算时我发现label中的出现的类别数是0-6,但是我的模型中的output类别只有1-6。反正体现在混淆矩阵上0这一类的预测和真实值是空值。我猜想0这一类应该是背景吧,然我就想了曲线救国的方式,将原始混淆矩阵中的第一行和第一列去掉,这样不会影响代码运行,当然如果我的猜想0类是背景正确的话这样计算就是ok的。(反正0值在计算中不存在意义) 我们假设这样是对的吧不然代码跑不下去,组会没法报告了 /捂脸。

我从说按照逻辑顺序记录三个阶段。第一个阶段output和target变换到合适的形式并传入到混淆矩阵部分;第二个是混淆矩阵中去掉某些行和列(这个问题困扰到了我,记录下来);第三个问题是混淆矩阵怎么计算精度。

 

1、output和target的变换

image, target = temp[0], temp[1]  #读取target和image
image, target = image.to(device), target.to(device)
output = model(image)
confmat.update(target.flatten(), output.argmax(1).flatten())

这个地方需要注意的是最后一句。flatten很好理解,比如target.shape()=[1,32,32],经过flatten之后得到target.shape()=[1024],即32*32=1024。

output的shape=[batchsize,num_cls,32,32],首先经过argmax(1)之后shape为[batchsize,32,32].具体为什么会这样,下面说明。

1)torch.max()

两个关键词:类别和概率值

神经网络的输出是每个类别对应的概率值,要返回类别的话就需要用到将概率值与类别对应。比如经典的vgg最后一层经过softmax之后输出的每一类的概率值,比如说dog类别对应的概率值是0.3,cat类别对应的概率值是0.7.

torch.max()返回tensor数据最大值(概率值)和索引(类别),输出的值有两个参数,第一个参数是最大值(概率最大的一个),第二个参数是最大值的索引(也就是对应的类别),主要用于神经网络输出与label的匹配。

如下代码:

a = torch.tensor([[     0,      0,      0,      0,      0,      0,      0],
                [     0,  19703,   3936,   4455,  24978,      4,    964],
                [     0,    751,   6909,   4376,  17809,     59,   1206],
                [     0,  10722,   9988,   9414,  29337,     56,   1646],
                [     0,   9556,  13371,  16550, 324301,    793,  58097],
                [     0,   4597,   3855,   7148,  24205,   1971,   1075],
                [     0,    375,   2433,   1058, 104904,     37, 302337]])
a = torch.max(a,1)
print(a)

运行结果如下图

 可以看到这段代码返回了两个值,一个是tensor的值,也就是概率,另一个是对应的索引值也就是类别

2)argmax()

torch.argmax()的作用与前面类似,我们只想要神经网络最终的标签(类别),它输出的概率值并不关心,那么就可以直接用torch.argmax()返回tensor数据最大值的索引,运行结果如下

 

2、tensor(混淆矩阵)中去掉特定行和列

因为我得到的混淆矩阵中含有0值,计算指标时会出错,最开始我观察发现这个0值永远是出现在第一行和第一列,然后我就想办法怎么删除tensor中的所有全为0值的行和列,代码如下

##删除tensor中全为0的行和列
def del_tensor_0_row(Cs):
    idx = torch.all(Cs[..., :] == 0, axis=1)
    index=[]
    for i in range(idx.shape[0]):
        if not idx[i].item():
            index.append(i)
    index=torch.tensor(index)
    Cs = torch.index_select(Cs, 0, index)
    return Cs


def del_tensor_0_cloumn(Cs):
    idx = torch.where(torch.all(Cs[..., :] == 0, axis=0))[0]
    all = torch.arange(Cs.shape[1])
    for i in range(len(idx)):
        all = all[torch.arange(all.size(0))!=idx[i]-i]
    Cs = torch.index_select(Cs, 1, all)
    return Cs

运行结果如下

但是最重要的是我只有运行到第三个epoch的时候,第一行和第一列才会完全为零,所以上面的方法不行,我得按照索引来删除tensor中指定的行和列。所以有了下面代码删除特定行和列

代码如下

a = torch.tensor([[     0,      0,      123,      0,      0,      0,      0],
                [     0,  19703,   3936,   4455,  24978,      4,    964],
                [     0,    751,   6909,   4376,  17809,     59,   1206],
                [   12,  10722,   9988,   9414,  29337,     56,   1646],
                [     0,   9556,  13371,  16550, 324301,    793,  58097],
                [     0,   4597,   3855,   7148,  24205,   1971,   1075],
                [     0,    375,   2433,   1058, 104904,     37, 302337]])
# print(a)
###删除特定行和列
indices = torch.tensor([1,2,3,4,5,6])
#torch.index_select中的第二个参数0或1分别表示行或列
a = torch.index_select(a, 0, indices)
a = torch.index_select(a, 1, indices)
print(a)

运行结果如下

 torch.index_select(a, 0, indices)中的第一个参数是原始的tensor,第二个参数是0或者1表示按照行或列索引,第三个参数表示需要留下的目标行或者目标列。

3、混淆矩阵怎么计算精度指标

语义分割中常见的指标有global_acc,acc,iou等

首先知道混淆矩阵的基本知识和上述指标计算的公式,可参考该文章语义分割之MIoU原理与实现 - 简书 (jianshu.com)

代码如下

    def update(self, a, b):
        n = self.num_classes
        if self.mat is None:
            # 创建混淆矩阵
            self.mat = torch.zeros((n, n), dtype=torch.int64, device=a.device)
        with torch.no_grad():
            # 寻找GT中为目标的像素索引
            k = (a >= 0) & (a < n)
            # 统计像素真实类别a[k]被预测成类别b[k]的个数(这里的做法很巧妙)
            inds = n * a[k].to(torch.int64) + b[k]
            self.mat += torch.bincount(inds, minlength=n**2).reshape(n, n)
            # print(self.mat)

    def reset(self):
        if self.mat is not None:
            self.mat.zero_()

    def compute(self):
        h = self.mat.float()
        h = h.cpu()
        indices = torch.tensor([1, 2, 3, 4, 5, 6])
        h = torch.index_select(h, 0, indices)
        h = torch.index_select(h, 1, indices)
        # print(h.cpu())
        # 计算全局预测准确率(混淆矩阵的对角线为预测正确的个数)
        acc_global = torch.diag(h).sum() / h.sum()  #torch.diag(h)取张量h对角线上的元素,sum()求和函数
        # print("acc_global is \n",acc_global)
        # 计算每个类别的准确率
        acc = torch.diag(h) / h.sum(1)  #h.sum(1) 求h中每一行的和
        # print("acc is \n",acc)
        # 计算每个类别预测与真实目标的iou
        iu = torch.diag(h) / (h.sum(1) + h.sum(0) - torch.diag(h))
        # print("iou is\n",iu)
        return acc_global, acc, iu

首先将output和target作为参数,输入到update函数中,这个时候输入进来的a和b已经是每个概率对应到类别了(可参考第二节的argmax()函数)。Output作为预测的类别,target作为真实的类别,两组参数形成一个混淆矩阵,计算各项指标。

此文作为踩坑记录在此,防止日后忘记。欢迎交流。

参考文章:

http://t.csdn.cn/7YKZb
http://t.csdn.cn/49iGH
https://www.jianshu.com/p/42939bf83b8a

本文含有隐藏内容,请 开通VIP 后查看