In this example we will design a "measure" layer, that outputs the accuracy and a confusion matrix for a binary problem during training and the accuracy, false positive rate and false negative rate during test/validation. Although Caffe already has a Accuracy layer, sometimes you want something more, like a F-measure.
This is my measureLayer.py with my class definition:
#Remark: This class is designed for a binary problem, where the first class would be the 'negative' # and the second class would be 'positive' import caffe TRAIN = 0 TEST = 1 class Measure_Layer(caffe.Layer): #Setup method def setup(self, bottom, top): #We want two bottom blobs, the labels and the predictions if len(bottom) != 2: raise Exception("Wrong number of bottom blobs (prediction and label)") #And some top blobs, depending on the phase if self.phase = TEST and len(top) != 3: raise Exception("Wrong number of top blobs (acc, FPR, FNR)") if self.phase = TRAIN and len(top) != 5: raise Exception("Wrong number of top blobs (acc, tp, tn, fp and fn)") #Initialize some attributes self.TPs = 0.0 self.TNs = 0.0 self.FPs = 0.0 self.FNs = 0.0 self.totalImgs = 0 #Forward method def forward(self, bottom, top): #The order of these depends on the prototxt definition predictions = bottom[0].data labels = bottom[1].data self.totalImgs += len(labels) for i in range(len(labels)): #len(labels) is equal to the batch size pred = predictions[i] #pred is a tuple with the normalized probability #of a sample i.r.t. two classes lab = labels[i] if pred[0] > pred[1]: if lab == 1.0: self.FNs += 1.0 else: self.TNs += 1.0 else: if lab == 1.0: self.TPs += 1.0 else: self.FPs += 1.0 acc = (self.TPs + self.TNs) / self.totalImgs try: #just assuring we don't divide by 0 fpr = self.FPs / (self.FPs + self.TNs) except: fpr = -1.0 try: #just assuring we don't divide by 0 fnr = self.FNs / (self.FNs + self.TPs) except: fnr = -1.0 #output data to top blob top[0].data = acc if self.phase == TRAIN: top[1].data = self.TPs top[2].data = self.TNs top[3].data = self.FPs top[4].data = self.FNs elif self.phase == TEST: top[1].data = fpr top[2].data = fnr def reshape(self, bottom, top): """ We don't need to reshape or instantiate anything that is input-size sensitive """ pass def backward(self, bottom, top): """ This layer does not back propagate """ pass
And this is an example of a prototxt with it:
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "TPs"
top: "TNs"
top: "FPs"
top: "FNs"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TRAIN
}
}
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "FPR"
top: "FNR"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TEST
}
}