1. 程式人生 > 其它 >程式碼筆記1 語義分割的評價指標以及混淆矩陣的計算

程式碼筆記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