DiSMEC++
evaluate.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021, Aalto University, developed by Erik Schultheis
2 // All rights reserved.
3 //
4 // SPDX-License-Identifier: MIT
5 
6 #include "evaluate.h"
7 #include "metrics.h"
8 #include "utils/conversion.h"
9 #include "spdlog/fmt/fmt.h"
10 
11 using namespace dismec::prediction;
12 
13 EvaluateMetrics::EvaluateMetrics(const LabelList* sparse_labels, const IndexMatrix* sparse_predictions, long num_labels) :
14  m_Labels(sparse_labels), m_Predictions(sparse_predictions), m_NumLabels(num_labels) {
15  if(m_Predictions->rows() != ssize(*m_Labels)) {
16  throw std::invalid_argument("number of predictions does not match number of labels");
17  }
18 
19  m_Collectors.resize(1);
20 }
21 
23 
24 void EvaluateMetrics::process_prediction(const std::vector<label_id_t>& raw_labels, const prediction_t& raw_prediction,
25  std::vector<sTrueLabelInfo>& proc_labels, std::vector<sPredLabelInfo>& proc_pred) {
26  proc_pred.clear();
27  proc_labels.reserve(raw_labels.size());
28  std::transform(begin(raw_labels), end(raw_labels), std::back_inserter(proc_labels),
29  [](label_id_t label) {
30  return sTrueLabelInfo{label, -1};
31  });
32 
33  // figure out which predictions are correct and which are wrong
34  for(long j = 0; j < raw_prediction.size(); ++j) {
35  // figure out if this prediction is in the true labels
36  auto lookup = std::lower_bound(begin(raw_labels), end(raw_labels), label_id_t{raw_prediction.coeff(j)});
37  bool is_correct = false;
38  if(lookup != end(raw_labels)) {
39  // if so, mark it as correct and register its rank
40  is_correct = (*lookup) == label_id_t{raw_prediction.coeff(j)};
41  proc_labels[std::distance(begin(raw_labels), lookup)].Rank = j;
42  }
43  proc_pred.push_back(sPredLabelInfo{label_id_t{raw_prediction.coeff(j)}, is_correct});
44  }
45 }
46 
47 void EvaluateMetrics::run_task(long task_id, thread_id_t thread_id) {
48  auto prediction = m_Predictions->row(task_id);
49  const auto& labels = (*m_Labels)[task_id];
50 
51  auto& predicted_cache = m_ThreadLocalPredictedLabels[thread_id.to_index()];
52  auto& true_cache = m_ThreadLocalTrueLabels[thread_id.to_index()];
53 
54  process_prediction(labels, prediction, true_cache, predicted_cache);
55 
56  for(auto& c : m_Collectors[thread_id.to_index()]) {
57  c->update(predicted_cache, true_cache);
58  }
59 }
60 
61 void EvaluateMetrics::run_tasks(long begin, long end, thread_id_t thread_id) {
62  for(long t = begin; t < end; ++t) {
63  run_task(t, thread_id);
64  }
65 }
66 
68  return m_Predictions->rows();
69 }
70 
71 void EvaluateMetrics::add_precision_at_k(long k, std::string name) {
72  if(k > m_Predictions->cols()) {
73  throw std::invalid_argument("Cannot calculate top-k precision for k > #predictions");
74  }
75 
76  if(name.empty()) {
77  name = fmt::format("InstanceP@{}", k);
78  }
79 
80  auto collector = std::make_unique<InstanceRankedPositives>(m_NumLabels, k);
81  m_Metrics.push_back( std::make_unique<InstanceWiseMetricReporter>(name, collector.get()) );
82  m_Collectors[0].push_back(std::move(collector));
83 }
84 
85 void EvaluateMetrics::add_dcg_at_k(long k, bool normalize, std::string name) {
86  if(k > m_Predictions->cols()) {
87  throw std::invalid_argument("Cannot calculate top-k DCG for k > #predictions");
88  }
89 
90  if(name.empty()) {
91  name = fmt::format("Instance{}DCG@{}", normalize ? "n" : "", k);
92  }
93 
94  std::vector<double> weights(k);
95  for(int i = 0; i < k; ++i) {
96  // definition of dcg assumes 1-based indexing; 2 + i ensures finite values
97  weights[i] = 1.0 / std::log(2 + i);
98  }
99 
100  auto collector = std::make_unique<InstanceRankedPositives>(m_NumLabels, k, normalize, std::move(weights));
101  m_Metrics.push_back( std::make_unique<InstanceWiseMetricReporter>(name, collector.get()) );
102  m_Collectors[0].push_back(std::move(collector));
103 }
104 
105 
106 void EvaluateMetrics::add_abandonment_at_k(long k, std::string name) {
107  if(k > m_Predictions->cols()) {
108  throw std::invalid_argument("Cannot calculate top-k abandonment for k > #predictions");
109  }
110 
111  if(name.empty()) {
112  name = fmt::format("Abd@{}", k);
113  }
114 
115  auto collector = std::make_unique<AbandonmentAtK>(m_NumLabels, k);
116  m_Metrics.push_back( std::make_unique<InstanceWiseMetricReporter>(name, collector.get()) );
117  m_Collectors[0].push_back(std::move(collector));
118 }
119 
121  if(k > m_Predictions->cols()) {
122  throw std::invalid_argument("Cannot calculate top-k abandonment for k > #predictions");
123  }
124 
125  auto collector = std::make_unique<ConfusionMatrixRecorder>(m_NumLabels, k);
126  auto metrics = std::make_unique<MacroMetricReporter>(collector.get());
127  auto* result = metrics.get();
128  m_Metrics.push_back( std::move(metrics) );
129  m_Collectors[0].push_back(std::move(collector));
130  return result;
131 }
132 
133 
134 std::vector<std::pair<std::string, double>> EvaluateMetrics::get_metrics() const {
135  std::vector<std::pair<std::string, double>> results;
136  for(const auto& m : m_Metrics) {
137  auto result = m->get_values();
138  std::copy(begin(result), end(result), std::back_inserter(results));
139  }
140  return results;
141 }
142 
143 void EvaluateMetrics::prepare(long num_threads, long chunk_size) {
144  m_ThreadLocalPredictedLabels.resize(num_threads);
145  m_ThreadLocalTrueLabels.resize(num_threads);
146  m_Collectors.resize(num_threads);
147 }
148 
150  for(int i = 1; i < ssize(m_Collectors); ++i) {
151  for(int j = 0; j < ssize(m_Collectors[0]); ++j) {
152  m_Collectors[0][j]->reduce(*m_Collectors[i][j]);
153  }
154  }
155 }
156 
158  /*for(auto& collector : m_Collectors.front()) {
159  m_Collectors[thread_id.to_index()].push_back(collector->clone());
160  }*/
161  if(thread_id.to_index() == 0) return;
162 
163  std::transform(begin(m_Collectors.front()), end(m_Collectors.front()),
164  std::back_inserter(m_Collectors[thread_id.to_index()]),
165  [](const auto& other){
166  return other->clone();
167  });
168 
169 }
Strong typedef for an int to signify a label id.
Definition: types.h:20
constexpr T to_index() const
! Explicitly convert to an integer.
Definition: opaque_int.h:32
Strong typedef for an int to signify a thread id.
Definition: thread_id.h:20
const IndexMatrix * m_Predictions
Definition: evaluate.h:64
void add_dcg_at_k(long k, bool normalize, std::string name={})
Definition: evaluate.cpp:85
MacroMetricReporter * add_macro_at_k(long k)
Definition: evaluate.cpp:120
std::vector< std::vector< sTrueLabelInfo > > m_ThreadLocalTrueLabels
Definition: evaluate.h:71
void prepare(long num_threads, long chunk_size) override
Called to notify the TaskGenerator about the number of threads.
Definition: evaluate.cpp:143
void add_precision_at_k(long k, std::string name={})
Definition: evaluate.cpp:71
EvaluateMetrics(const LabelList *sparse_labels, const IndexMatrix *sparse_predictions, long num_labels)
Definition: evaluate.cpp:13
void finalize() override
Called after all threads have finished their tasks.
Definition: evaluate.cpp:149
long num_tasks() const override
Definition: evaluate.cpp:67
std::vector< std::vector< std::unique_ptr< MetricCollectionInterface > > > m_Collectors
Definition: evaluate.h:67
Eigen::Ref< const Eigen::Matrix< long, 1, Eigen::Dynamic > > prediction_t
Definition: evaluate.h:58
void run_tasks(long begin, long end, thread_id_t thread_id) override
Definition: evaluate.cpp:61
std::vector< std::unique_ptr< MetricReportInterface > > m_Metrics
Definition: evaluate.h:68
std::vector< std::pair< std::string, double > > get_metrics() const
Definition: evaluate.cpp:134
void add_abandonment_at_k(long k, std::string name={})
Definition: evaluate.cpp:106
void init_thread(thread_id_t thread_id) override
Called once a thread has spun up, but before it runs its first task.
Definition: evaluate.cpp:157
static void process_prediction(const std::vector< label_id_t > &raw_labels, const prediction_t &raw_prediction, std::vector< sTrueLabelInfo > &proc_labels, std::vector< sPredLabelInfo > &proc_pred)
Definition: evaluate.cpp:24
std::vector< std::vector< sPredLabelInfo > > m_ThreadLocalPredictedLabels
Definition: evaluate.h:72
std::vector< std::vector< label_id_t > > LabelList
Definition: evaluate.h:36
void run_task(long task_id, thread_id_t thread_id)
Definition: evaluate.cpp:47
types::DenseRowMajor< long > IndexMatrix
Matrix used for indices in sparse predictions.
Definition: matrix_types.h:81
constexpr auto ssize(const C &c) -> std::common_type_t< std::ptrdiff_t, std::make_signed_t< decltype(c.size())>>
signed size free function. Taken from https://en.cppreference.com/w/cpp/iterator/size
Definition: conversion.h:42