blob: 92f115c3858392048ec395dc21c95e23f1882845 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# =============================================================================
'''This module includes a set of metric classes for evaluating the model's
performance. The specific metric classes could be converted from C++
implmentation or implemented directly using Python.
Example usage::
from singa import tensor
from singa import metric
x = tensor.Tensor((3, 5))
x.uniform(0, 1) # randomly genearte the prediction activation
x = tensor.SoftMax(x) # normalize the prediction into probabilities
y = tensor.from_numpy(np.array([0, 1, 3], dtype=np.int)) # set the truth
f = metric.Accuracy()
acc = f.evaluate(x, y) # averaged accuracy over all 3 samples in x
'''
from __future__ import division
from __future__ import absolute_import
from builtins import range
from builtins import object
from . import singa_wrap as singa
from . import tensor
import numpy as np
class Metric(object):
'''Base metric class.
Subclasses that wrap the C++ loss classes can use the inherited foward,
and evaluate functions of this base class. Other subclasses need
to override these functions. Users need to feed in the **predictions** and
ground truth to get the metric values.
'''
def __init__(self):
self.swig_metric = None
def forward(self, x, y):
'''Compute the metric for each sample.
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth values, one row per sample
Returns:
a tensor of floats, one per sample
'''
return tensor.from_raw_tensor(
self.swig_metric.Forward(x.data, y.data))
def evaluate(self, x, y):
'''Compute the averaged metric over all samples.
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth values, one row per sample
Returns:
a float value for the averaged metric
'''
return self.swig_metric.Evaluate(x.data, y.data)
class Accuracy(Metric):
'''Compute the top one accuracy for single label prediction tasks.
It calls the C++ functions to do the calculation.
'''
def __init__(self):
self.swig_metric = singa.Accuracy()
class Precision(Metric):
'''Make the top-k labels of max probability as the prediction
Compute the precision against the groundtruth labels
'''
def __init__(self, top_k):
self.top_k = top_k
def forward(self, x, y):
'''Compute the precision for each sample.
Convert tensor to numpy for computation
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth labels, one row per sample
Returns:
a tensor of floats, one per sample
'''
dev = x.device
x.to_host()
y.to_host()
x_np = tensor.to_numpy(x)
y_np = tensor.to_numpy(y)
# Sort in descending order
pred_np = np.argsort(-x_np)[:, 0:self.top_k]
prcs_np = np.zeros(pred_np.shape[0], dtype=np.float32)
for i in range(pred_np.shape[0]):
# groundtruth labels
label_np = np.argwhere(y_np[i])
# num of common labels among prediction and groundtruth
num_intersect = np.intersect1d(pred_np[i], label_np).size
prcs_np[i] = num_intersect / float(self.top_k)
precision = tensor.from_numpy(prcs_np)
x.to_device(dev)
y.to_device(dev)
precision.to_device(dev)
return precision
def evaluate(self, x, y):
'''Compute the averaged precision over all samples.
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth values, one row per sample
Returns:
a float value for the averaged metric
'''
return tensor.average(self.forward(x, y))
class Recall(Metric):
'''Make the top-k labels of max probability as the prediction
Compute the recall against the groundtruth labels
'''
def __init__(self, top_k):
self.top_k = top_k
def forward(self, x, y):
'''Compute the recall for each sample.
Convert tensor to numpy for computation
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth labels, one row per sample
Returns:
a tensor of floats, one per sample
'''
dev = x.device
x.to_host()
y.to_host()
x_np = tensor.to_numpy(x)
y_np = tensor.to_numpy(y)
# Sort in descending order
pred_np = np.argsort(-x_np)[:, 0:self.top_k]
recall_np = np.zeros(pred_np.shape[0], dtype=np.float32)
for i in range(pred_np.shape[0]):
# Return the index of non-zero dimension of i-th sample
label_np = np.argwhere(y_np[i])
# Num of common labels among prediction and groundtruth
num_intersect = np.intersect1d(pred_np[i], label_np).size
recall_np[i] = float(num_intersect) / label_np.size
recall = tensor.from_numpy(recall_np)
x.to_device(dev)
y.to_device(dev)
recall.to_device(dev)
return recall
def evaluate(self, x, y):
'''Compute the averaged precision over all samples.
Args:
x (Tensor): predictions, one row per sample
y (Tensor): ground truth values, one row per sample
Returns:
a float value for the averaged metric
'''
return tensor.average(self.forward(x, y))