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
