DiSMEC++
prediction.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 "prediction.h"
7 
8 #include <utility>
9 #include "data/data.h"
10 #include "spdlog/spdlog.h"
11 #include "spdlog/fmt/fmt.h"
12 #include "model/model.h"
13 
14 using namespace dismec;
15 using namespace dismec::prediction;
16 
18  std::shared_ptr<const Model> model) :
19  m_Data(data), m_Model(std::move(model)), m_FeatureReplicator(m_Data->get_features())
20 {
21  if(m_Model->num_labels() != data->num_labels()) {
22  throw std::invalid_argument(
23  fmt::format("Mismatched number of labels between model ({}) and data ({})",
24  m_Model->num_labels(), data->num_labels()));
25  }
26 
27  if(m_Model->num_features() != data->num_features()) {
28  throw std::invalid_argument(
29  fmt::format("Mismatched number of features between model ({}) and data ({})",
30  m_Model->num_features(), data->num_features()));
31  }
32 }
33 
35  m_ThreadLocalFeatures.resize(num_threads);
36 }
37 
39  m_ThreadLocalFeatures.at(thread_id.to_index()) = m_FeatureReplicator.get_local();
40 }
41 
42 namespace {
43  Model::FeatureMatrixIn make_matrix(const DenseFeatures& features, long begin, long end) {
44  return Model::FeatureMatrixIn::DenseRowMajorRef{features.middleRows(begin, end-begin)};
45  }
46  Model::FeatureMatrixIn make_matrix(const SparseFeatures& features, long begin, long end) {
47  return Model::FeatureMatrixIn::SparseRowMajorRef{features.middleRows(begin, end-begin)};
48  }
49 }
50 
51 void PredictionBase::do_prediction(long begin, long end, thread_id_t thread_id, Eigen::Ref<PredictionMatrix> target) {
52  auto& local_features = m_ThreadLocalFeatures.at(thread_id.to_index());
53  visit([&](const auto& features){
54  m_Model->predict_scores(make_matrix(features, begin, end), target);
55  }, *local_features);
56 
57 }
58 
59 FullPredictionTaskGenerator::FullPredictionTaskGenerator(const DatasetBase* data, std::shared_ptr<const Model> model) :
60  PredictionBase(data, std::move(model))
61 {
62  m_Predictions.resize(data->num_examples(), data->num_labels());
63 }
64 
66 {
67  return m_Data->num_examples();
68 }
69 
70 void FullPredictionTaskGenerator::run_tasks(long begin, long end, thread_id_t thread_id)
71 {
72  do_prediction(begin, end, thread_id, m_Predictions.middleRows(begin, end - begin));
73 }
74 
75 void FullPredictionTaskGenerator::prepare(long num_threads, long chunk_size) {
76  make_thread_local_features(num_threads);
77 }
78 
79 
80 TopKPredictionTaskGenerator::TopKPredictionTaskGenerator(const DatasetBase* data, std::shared_ptr<const Model> model, long K) :
81  PredictionBase(data, std::move(model)), m_K(K)
82 {
83  m_TopKValues.resize(data->num_examples(), m_K);
84  m_TopKIndices.resize(data->num_examples(), m_K);
85  m_TopKValues.setConstant(-std::numeric_limits<real_t>::infinity());
86 
87  // generate a transpose of the label matrix
88  std::vector<std::vector<long>> examples_to_labels(data->num_examples());
89  for(label_id_t label{0}; label.to_index() < data->num_labels(); ++label) {
90  for(auto example : dynamic_cast<const MultiLabelData*>(data)->get_label_instances(label)) {
91  examples_to_labels[example].push_back(label.to_index());
92  }
93  }
94 
95  m_GroundTruth = std::move(examples_to_labels);
96  m_ConfusionMatrix.fill(std::int64_t{0});
97 }
98 
100  return m_Data->num_examples();
101 }
102 
103 void TopKPredictionTaskGenerator::prepare(long num_threads, long chunk_size) {
104  m_ThreadLocalPredictionCache.resize(num_threads);
105  for(auto& cache : m_ThreadLocalPredictionCache) {
106  cache.resize(chunk_size, m_Model->num_weights());
107  }
108  m_ThreadLocalTopKIndices.resize(num_threads);
109  for(auto& cache : m_ThreadLocalTopKIndices) {
110  cache.resize(chunk_size, m_K);
111  }
112  m_ThreadLocalTopKValues.resize(num_threads);
113  for(auto& cache : m_ThreadLocalTopKValues) {
114  cache.resize(chunk_size, m_K);
115  }
116  make_thread_local_features(num_threads);
117 
118  m_ThreadLocalConfusionMatrix.resize(num_threads);
119  for(auto& cache : m_ThreadLocalConfusionMatrix) {
120  cache.fill(0);
121  }
122 }
123 
126  for(auto& tl_cm: m_ThreadLocalConfusionMatrix) {
127  for(int i = 0; i < 4; ++i) {
128  m_ConfusionMatrix[i] += tl_cm[i];
129  }
130  }
131 }
132 
133 void TopKPredictionTaskGenerator::run_tasks(long begin, long end, thread_id_t thread_id) {
134  auto& prediction_matrix = m_ThreadLocalPredictionCache.at(thread_id.to_index());
135  auto& topk_vals = m_ThreadLocalTopKValues.at(thread_id.to_index());
136  auto& topk_idx = m_ThreadLocalTopKIndices.at(thread_id.to_index());
137  auto& cm = m_ThreadLocalConfusionMatrix.at(thread_id.to_index());
138 
139  // quick access to the label indices that are currently active
140  long index_offset = m_Model->labels_begin().to_index();
141  long last_index = m_Model->labels_end().to_index();
142 
143  // load from global buffer, in case we do a reduction
144  topk_idx = m_TopKIndices.middleRows(begin, end-begin);
145  topk_vals = m_TopKValues.middleRows(begin, end-begin);
146 
147  // generate raw predictions in prediction_matrix
148  do_prediction(begin, end, thread_id, prediction_matrix.middleRows(0, end-begin));
149 
150  // confusion matrix
151  std::int64_t true_positives = 0;
152  std::int64_t num_gt_positives = 0;
153  for(long sample = begin; sample < end; ++sample) {
154  // iterate over all true values
155  for(auto& gt : m_GroundTruth[sample])
156  {
157  // we have to take into account that we are potentially only looking at a subset of the labels.
158  if(gt < index_offset) continue;
159  if(gt >= last_index) break;
160 
161  // correctly predicted true label
162  if(prediction_matrix.coeff(sample - begin, gt - index_offset) > 0) {
163  ++true_positives;
164  }
165  ++num_gt_positives;
166  }
167  }
168 
169  std::int64_t positive_prediction = 0;
170  for(long t = 0; t < end - begin; ++t) {
171  double threshold = topk_vals.coeff(t, m_TopKValues.cols() - 1);
172 
173  // reduce to top k
174  for(long j = 0; j < prediction_matrix.cols(); ++j)
175  {
176  real_t value = prediction_matrix.coeff(t, j);
177  if(value > 0) ++positive_prediction;
178  if(value < threshold) {
179  continue;
180  }
181 
182  long index = index_offset + j;
183  for(long k = 0; k < m_K; ++k) {
184  // search for the first entry where we are larger. Once we've inserted this value,
185  // move the other values to the right.
186  if(value > topk_vals.coeff(t, k)) {
187  value = std::exchange(topk_vals.coeffRef(t, k), value);
188  index = std::exchange(topk_idx.coeffRef(t, k), index);
189  }
190  }
191 
192  // update the threshold: this is the value in the last column
193  threshold = topk_vals.coeff(t, topk_vals.cols() - 1);
194  }
195  }
196 
197  std::int64_t total = (end - begin) * prediction_matrix.cols();
198  std::int64_t true_neg = total - positive_prediction - num_gt_positives + true_positives;
199 
200  cm[TRUE_POSITIVES] += true_positives;
201  cm[FALSE_NEGATIVES] += num_gt_positives - true_positives;
202  cm[FALSE_POSITIVES] += positive_prediction - true_positives;
203  cm[TRUE_NEGATIVES] += true_neg;
204 
205  // copy to global buffer
206  m_TopKIndices.middleRows(begin, end-begin) = topk_idx;
207  m_TopKValues.middleRows(begin, end-begin) = topk_vals;
208 }
209 
210 void TopKPredictionTaskGenerator::update_model(std::shared_ptr<const Model> model) {
211  m_Model = std::move(model);
212 }
long num_examples() const noexcept
Get the total number of instances, i.e. the number of rows in the feature matrix.
Definition: data.cpp:52
virtual long num_labels() const noexcept=0
long num_features() const noexcept
Get the total number of features, i.e. the number of columns in the feature matrix.
Definition: data.cpp:48
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
void run_tasks(long begin, long end, thread_id_t thread_id) override
Definition: prediction.cpp:70
FullPredictionTaskGenerator(const DatasetBase *data, std::shared_ptr< const Model > model)
Definition: prediction.cpp:59
void prepare(long num_threads, long chunk_size) override
Called to notify the TaskGenerator about the number of threads.
Definition: prediction.cpp:75
Base class for handling predictions.
Definition: prediction.h:34
void do_prediction(long begin, long end, thread_id_t thread_id, Eigen::Ref< PredictionMatrix > target)
Predicts the scores for a subset of the instances given by the half-open interval [begin,...
Definition: prediction.cpp:51
void init_thread(thread_id_t thread_id) final
Called once a thread has spun up, but before it runs its first task.
Definition: prediction.cpp:38
PredictionBase(const DatasetBase *data, std::shared_ptr< const Model > model)
Constructor, checks that data and model are compatible.
Definition: prediction.cpp:17
std::vector< std::shared_ptr< const GenericFeatureMatrix > > m_ThreadLocalFeatures
Definition: prediction.h:67
std::shared_ptr< const Model > m_Model
Model (possibly partial) for which prediction is run.
Definition: prediction.h:41
const DatasetBase * m_Data
Data on which the prediction is run.
Definition: prediction.h:40
parallel::NUMAReplicator< const GenericFeatureMatrix > m_FeatureReplicator
The NUMAReplicator that generates NUMA-local copies for the feature matrices.
Definition: prediction.h:63
void make_thread_local_features(long num_threads)
Definition: prediction.cpp:34
static constexpr const int TRUE_POSITIVES
Definition: prediction.h:101
void finalize() override
Called after all threads have finished their tasks.
Definition: prediction.cpp:124
std::vector< PredictionMatrix > m_ThreadLocalTopKValues
Definition: prediction.h:112
std::vector< PredictionMatrix > m_ThreadLocalPredictionCache
Definition: prediction.h:111
void update_model(std::shared_ptr< const Model > model)
Definition: prediction.cpp:210
std::vector< std::vector< long > > m_GroundTruth
Definition: prediction.h:116
std::vector< IndexMatrix > m_ThreadLocalTopKIndices
Definition: prediction.h:113
static constexpr const int TRUE_NEGATIVES
Definition: prediction.h:103
TopKPredictionTaskGenerator(const DatasetBase *data, std::shared_ptr< const Model > model, long K)
Definition: prediction.cpp:80
static constexpr const int FALSE_POSITIVES
Definition: prediction.h:102
static constexpr const int FALSE_NEGATIVES
Definition: prediction.h:104
void run_tasks(long begin, long end, thread_id_t thread_id) override
Definition: prediction.cpp:133
void prepare(long num_threads, long chunk_size) override
Called to notify the TaskGenerator about the number of threads.
Definition: prediction.cpp:103
std::array< std::int64_t, 4 > m_ConfusionMatrix
Definition: prediction.h:117
std::vector< std::array< std::int64_t, 4 > > m_ThreadLocalConfusionMatrix
Definition: prediction.h:114
Eigen::Ref< SparseRowMajor< T > > SparseRowMajorRef
Eigen::Ref< DenseRowMajor< T > > DenseRowMajorRef
Model::FeatureMatrixIn make_matrix(const SparseFeatures &features, long begin, long end)
Definition: prediction.cpp:46
auto get_features(const DatasetBase &ds)
Definition: py_data.cpp:28
auto visit(F &&f, Variants &&... variants)
Definition: eigen_generic.h:95
Main namespace in which all types, classes, and functions are defined.
Definition: app.h:15
types::DenseRowMajor< real_t > DenseFeatures
Dense Feature Matrix in Row Major format.
Definition: matrix_types.h:58
types::SparseRowMajor< real_t > SparseFeatures
Sparse Feature Matrix in Row Major format.
Definition: matrix_types.h:50
float real_t
The default type for floating point values.
Definition: config.h:17