MIOU的计算

MIOU,即平均交并比(Mean Intersection over Union),是评价语义分割模型性能的一个重要指标。它计算的是预测结果中每一类结果的交并比的平均值。假设绿色为真实的标签值,粉红色为预测的结果。



所谓交并比,就是这两种数据的相交元素数量/两种数据的并集的元素数量,如下图所示:



下面我们来一下MIOU的计算方法。
  1. IOU的计算
    要计算平均交并比,就要首先计算每一类的交并比。交并比(Intersection over Union)是衡量预测值与真实值相似度的一个指标。第i类IOU的计算公式如下:



    其中,TP代表真正例,即某类别i的像素正确预测为该类别的数量;
    FP代表假正例,即非类别i的像素预测为该类别的数量;
    FN代表假负例,即类别i的像素预测为非该类别的数量;
    获取到这几个值之后就可以计算出交并比IOU了。

  2. 混淆矩阵的计算
    混淆矩阵是一个n*n的矩阵(n是类别数),用于表示实际类别与预测类别之间的关系。混淆矩阵的对角线元素表示正确预测的数量,而其他元素表示错误预测的数量。根据周志华老师的《机器学习》,混淆矩阵如下:

    根据混淆矩阵,可以很方便的计算出TP,FN和FP,进而计算出IOU和MIOU。
    混淆矩阵的计算,有好几种算法,我这里选了三种方法介绍一下。
    第一种是网上很流行的numpy.bincount方法:
    numpy.bincount是一个计算某个数组中,索引出现次数的函数。代码举例会更清楚。
import numpy as np

a = [4,5,4,1,3,5,2] 
np.bincount(a)
# 输出:array([0, 1, 1, 1, 2, 2], dtype=int64)

这里,最大值是5,所以类别是0~6之间的6类,返回6个结果,每个位置代表该位置索引出现的次数。这里索引0出现0次,索引1在a中出现1次,索引2在a中出现1次,索引3在a中出现1次,索引4在a中出现2次,索引5在a中出现2次,所以结果是[0,1,1,1,2,2]。
bincount有一个参数minlength,minlength是为了强行设定类别数,比如数组a的最大值只是到5,但是我认为类别数有8类,那么我就可以设定minlength=8,结果当然是后面几类的索引值都是0了。

np.bincount(a, minlength=8)
# 输出:array([0, 1, 1, 1, 2, 2, 0, 0], dtype=int64)
'''
得到n*n的混淆矩阵
a:真实标签label
b:预测结果pred
n:类别数
'''
def confusion_matrix(a, b, n):
    #k就是为了防止出错的
    k = (a >= 0) & (a < n) 
    return np.bincount(n * a[k].astype(int) + b[k], minlength=n**2).reshape(n, n)

这个函数中,k只是为了防止运算出错,其实如果a,b是标签值和预测的结果的话,k可以不需要。这里首先把结果扩充到n * n大小,最后再reshape到n * n,就是为了生成一个n * n的矩阵。而其中的值用n乘上a的值,再加上b的值,其实仅仅是为了计算方便而已。假设a是0,b也是0,那么a乘上n后还是0,加上b还是0,所以索引0就会存在,如果a和b不同,则会有不同的值出现,也就是混淆矩阵其他位置上的值。假设a是1,n是3,那么n * a还是3,b如果是0,那就是和就是3,b如果是1,那和就是4。
这么说可能不大好理解,我举个实际例子,假设有个a和b,类别是3类,用0,1,2表示。

a = np.array([0,1,0,2,1,0,2,2,1])
b = np.array([0,2,0,2,1,0,1,2,1])

这里有三类,0,1,2,所以n=3,minlength=9,我们可以手动计算一下这9个值,根据函数中的算法: [3 * 0 + 0, 3 * 1 + 2, 3 * 0 + 0, 3 * 2 + 2, 3 * 1 + 1, 3 * 0 + 0, 3 * 2 + 1, 3 * 2 + 2, 3 * 1 + 1] ,
结果就是 [0, 5, 0, 8, 4, 0, 7, 8, 4] ,
再做一个bincount就是 [3, 0, 0, 0, 2, 1, 0, 1, 2] ,转成3 * 3的矩阵,就是 [[3,0,0] [0,2,1] [0,1,2]] 这个就是混淆矩阵。用程序验证一下。

confusion_matrix(a,b,3)
#输出:
array([[3, 0, 0],
       [0, 2, 1],
       [0, 1, 2]], dtype=int64)

和我们自己计算的结果一致。
第二种计算混淆矩阵的方法,就比较直观,统计出标签值和预测值的各种结果的总和数,就是混淆矩阵,直接上代码:

import numpy as np  
  
def confusion_matrix_np(y_true, y_pred):  
    """  
    使用NumPy计算混淆矩阵。  
  
    参数:  
    - y_true: 真实标签的数组。  
    - y_pred: 预测标签的数组。  
  
    返回:  
    - 混淆矩阵的NumPy数组。  
    """  
    # 确保y_true和y_pred是NumPy数组  
    y_true = np.asarray(y_true)  
    y_pred = np.asarray(y_pred)  
  
    # 检查y_true和y_pred的长度是否相同  
    if y_true.shape[0] != y_pred.shape[0]:  
        raise ValueError("y_true和y_pred的长度必须相同")  
  
    labels = np.unique(np.concatenate((y_true, y_pred)))  
  
    # 初始化混淆矩阵  
    n_labels = len(labels)  
    conf_mat = np.zeros((n_labels, n_labels), dtype=np.int64)  
  
    # 填充混淆矩阵  
    for i, l1 in enumerate(labels):  
        for j, l2 in enumerate(labels):  
            # 混淆矩阵每个元素的值就是该位置标签值和预测值符合条件的总数
            # 比如:conf_mat[0,0]就是真实值和预测值都是0的总数,
            # conf_mat[0,1]就是真实标签值是1,但预测值是0的元素总数
            conf_mat[i, j] = np.sum((y_true == l1) & (y_pred == l2))  
  
    return conf_mat
cm = confusion_matrix_np(a, b)  
print(cm)
# 输出
[[3 0 0]
 [0 2 1]
 [0 1 2]]

这里可以看到,结果和我们之前用bincount计算的结果一致。
第三种方法最简单,直接安装一个sklearn包,用sklearn提供的计算混淆矩阵的函数计算得到。

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(a, b)  
print(cm)
# 输出:
[[3 0 0]
 [0 2 1]
 [0 1 2]]

可以看到,结果也是一样的。
有个混淆矩阵,我们可以计算IOU了。
IOU的计算,在混淆矩阵中,就是把该类所在行的值列的值求和,然后减去对角线上的值,因为对角线上的值在求和的时候会被计算两次,所以要减去一次,然后用对角线的值去除以这个和就是IOU的值。下面是计算过程:
第一类的IOU:3 / ((3+0+0)+(3+0+0)-3) = 1
第二类的IOU:2 / ((0+2+1)+(0+2+1)-2) = 0.5
第三类的IOU:2 / ((0+1+2)+(0+1+2)-2) = 0.5
如果用程序的话,我们可以一次性计算出来,其实就是做了一个批处理,本质上和我们的计算过程是一样的:

def iou(cm):
    return np.diag(cm) / (cm.sum(0) + cm.sum(1) - np.diag(cm))
    
iou(cm)
#输出:array([1. , 0.5, 0.5])

有了各个类别的IOU,就可以很方便的求得MIOU了。

miou = np.mean(iou(cm))
miou
# 输出:0.6666666666666666
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容