代码笔记1 语义分割的评价指标以及混淆矩阵的计算
1 评价指标
语义分割的评价指标大致就几个:可见[1][2]
Pixel Accuracy (PA)
分类正确的像素点数和所有的像素点数的比例
Mean Pixel Accuracy (MPA)
计算每一类分类正确的像素点数和该类的所有像素点数的比例然后求平均
Intersection over Union(IoU or IU)
也就是所谓的交并比
Mean Intersection over Union (MIoU)
计算每一类的IoU然后求平均。
Frequency Weighted Intersection over Union (FWIoU)
可以理解为根据每一类出现的频率对各个类的IoU进行加权求和
这些指标的公式和理论之后有机会写在理论学习里面,这不是重点。
2 混淆矩阵
为了快速方便的计算这些指标,引入了混淆矩阵:可见[3][4]
混淆矩阵(Confusion Matrix):
混淆矩阵的每一列代表了预测类别,每一列的总数表示预测为该类别的数据的数目;每一行代表了数据的真实归属类别,每一行的数据总数表示该类别的数据实例的数目。每一列中的数值表示真实数据被预测为该类的数目:第一行第一列中的43表示有43个实际归属第一类的实例被预测为第一类,同理,第一行第二列的2表示有2个实际归属为第一类的实例被错误预测为第二类
简而言之就是量化误差,并且可以通过混淆矩阵计算各种评价指标(PA\IoU\etc.)[5]
3 代码
这个求混淆矩阵的方法应该是来源于FCN源码的score.py
`
class SegmentationMetric(object):
def __init__(self, numClass):
self.numClass = numClass
self.confusionMatrix = np.zeros((self.numClass,) * 2) # 混淆矩阵(空)
def pixelAccuracy(self):
# return all class overall pixel accuracy 正确的像素占总像素的比例
# PA = acc = (TP + TN) / (TP + TN + FP + TN)
acc = np.diag(self.confusionMatrix).sum() / self.confusionMatrix.sum()
return acc
def classPixelAccuracy(self):
# return each category pixel accuracy(A more accurate way to call it precision)
# acc = (TP) / TP + FP
classAcc = np.diag(self.confusionMatrix) / self.confusionMatrix.sum(axis=1)
return classAcc # 返回的是一个列表值,如:[0.90, 0.80, 0.96],表示类别1 2 3各类别的预测准确率
def meanPixelAccuracy(self):
"""
Mean Pixel Accuracy(MPA,均像素精度):是PA的一种简单提升,计算每个类内被正确分类像素数的比例,之后求所有类的平均。
:return:
"""
classAcc = self.classPixelAccuracy()
meanAcc = np.nanmean(classAcc) # np.nanmean 求平均值,nan表示遇到Nan类型,其值取为0
return meanAcc # 返回单个值,如:np.nanmean([0.90, 0.80, 0.96, nan, nan]) = (0.90 + 0.80 + 0.96) / 3 = 0.89
def IntersectionOverUnion(self):
# Intersection = TP Union = TP + FP + FN
# IoU = TP / (TP + FP + FN)
intersection = np.diag(self.confusionMatrix) # 取对角元素的值,返回列表
union = np.sum(self.confusionMatrix, axis=1) + np.sum(self.confusionMatrix, axis=0) - np.diag(
self.confusionMatrix) # axis = 1表示混淆矩阵行的值,返回列表; axis = 0表示取混淆矩阵列的值,返回列表
IoU = intersection / union # 返回列表,其值为各个类别的IoU
return IoU
def meanIntersectionOverUnion(self):
mIoU = np.nanmean(self.IntersectionOverUnion()) # 求各类别IoU的平均
return mIoU
def getConfusionMatrix(self, imgPredict, imgLabel): #
"""
同FCN中score.py的fast_hist()函数,计算混淆矩阵
:param imgPredict:
:param imgLabel:
:return: 混淆矩阵
"""
# remove classes from unlabeled pixels in gt image and predict
mask = (imgLabel >= 0) & (imgLabel < self.numClass)
label = self.numClass * imgLabel[mask] + imgPredict[mask]
count = np.bincount(label, minlength=self.numClass * self.numClass)
confusionMatrix = count.reshape(self.numClass, self.numClass)
# print(confusionMatrix)
return confusionMatrix
def Frequency_Weighted_Intersection_over_Union(self):
"""
FWIoU,频权交并比:为MIoU的一种提升,这种方法根据每个类出现的频率为其设置权重。
FWIOU = [(TP+FN)/(TP+FP+TN+FN)] *[TP / (TP + FP + FN)]
"""
freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix)
iu = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))
FWIoU = (freq[freq > 0] * iu[freq > 0]).sum()
return FWIoU
def addBatch(self, imgPredict, imgLabel):
assert imgPredict.shape == imgLabel.shape
self.confusionMatrix += self.getConfusionMatrix(imgPredict, imgLabel) # 得到混淆矩阵
return self.confusionMatrix
def reset(self):
self.confusionMatrix = np.zeros((self.numClass, self.numClass))`
因为我用的并不是VOC数据集,我用的是SunRGBD数据集。我也算是自己踩了一回坑,也怪我不细致,至于什么问题,等我忙完了这个会总结在下一篇的。总而言之就是,Sun的label是有0一类,这一类是不放入损失函数或者评价指标计算中的,同时在训练网络中应该不把这个当作一类。
说多了,我其实是想说这个求混淆矩阵方法的巧妙性。
def getConfusionMatrix(self, imgPredict, imgLabel): #
"""
同FCN中score.py的fast_hist()函数,计算混淆矩阵
:param imgPredict:
:param imgLabel:
:return: 混淆矩阵
"""
# remove classes from unlabeled pixels in gt image and predict
mask = (imgLabel >= 0) & (imgLabel < self.numClass)
label = self.numClass * imgLabel[mask] + imgPredict[mask]
count = np.bincount(label, minlength=self.numClass * self.numClass)
confusionMatrix = count.reshape(self.numClass, self.numClass)
# print(confusionMatrix)
return confusionMatrix
mask后的结果是一个bool tensor,就是一组由bool变量组成的和判定的tensor的shape一样的,其中满足条件的为True,不满足的为False。
Tensor[Mask]操作的结果是一组一维tensor,其中是按照先行后列的顺序取出了为Mask为True的Tensor值。
此时另label = self.numClass * imgLabel[mask] + imgPredict[mask]
其实就是产生了一个值event,令 event(i,j)代表着label为类别i,predit为类别j事件的发生,并且在总共numClass * numClass这么多事件中,这个值不重复,这个公式是怎么做到的呢。
event(i,j) = numClass*i+j
其实我们可以看出来,比如当i=0时,0到numClass-1分别代表着label为0而预测为numClass类。而i=1,numClass到2 * numClass-1代表着label为1而预测为numClass类。以此类推,我们可以得到event的值的种类有(numClass)[从0开始到numClass-1,共numClass类] * (numClass),其中event的最大值为numClass * (numClass-1)+(numClass-1),大家算一下就可以发现,event的最大值就等于event的种类,这就意味着每一个event的值都代表着一个event事件。
然后通过
count = np.bincount(label, minlength=self.numClass * self.numClass)
使用bincount按照顺序统计每个值出现的次数后使用
confusionMatrix = count.reshape(self.numClass, self.numClass)
reshape成为混淆矩阵。
References
[1]https://zhuanlan.zhihu.com/p/61880018
[2]https://blog.csdn.net/lingzhou33/article/details/87901365
[3]https://baike.baidu.com/item/混淆矩阵/10087822?fr=aladdin
[4]https://zhuanlan.zhihu.com/p/46204175
[5]https://blog.csdn.net/weixin_38353277/article/details/121029978?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_aa&utm_relevant_index=1