Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
metrics.cpp
Go to the documentation of this file.
1#include "metrics.h"
2
3namespace Brush {
4namespace Eval {
5
6/* Scoring functions */
7
9float mse(const VectorXf& y, const VectorXf& yhat, VectorXf& loss,
10 const vector<float>& class_weights)
11{
12 loss = (yhat - y).array().pow(2);
13 return loss.mean();
14}
15
16
17VectorXf log_loss(const VectorXf& y, const VectorXf& predict_proba,
18 const vector<float>& class_weights)
19{
20 float eps = pow(10,-10);
21
22 VectorXf loss;
23
24 loss.resize(y.rows());
25 for (unsigned i = 0; i < y.rows(); ++i)
26 {
27 if (predict_proba(i) < eps || 1 - predict_proba(i) < eps)
28 // clip probabilities since log loss is undefined for predict_proba=0 or predict_proba=1
29 loss(i) = -(y(i)*log(eps) + (1-y(i))*log(1-eps));
30 else
31 loss(i) = -(y(i)*log(predict_proba(i)) + (1-y(i))*log(1-predict_proba(i)));
32
33 if (loss(i)<0)
34 std::runtime_error("loss(i)= " + to_string(loss(i))
35 + ". y = " + to_string(y(i)) + ", predict_proba(i) = "
36 + to_string(predict_proba(i)));
37 }
38
39 return loss;
40}
41
43float mean_log_loss(const VectorXf& y,
44 const VectorXf& predict_proba, VectorXf& loss,
45 const vector<float>& class_weights)
46{
47 loss = log_loss(y,predict_proba,class_weights);
48
49 if (!class_weights.empty())
50 {
51 float sum_weights = 0;
52
53 // we keep loss without weights, as this may affect lexicase
54 VectorXf weighted_loss;
55 weighted_loss.resize(y.rows());
56 for (unsigned i = 0; i < y.rows(); ++i)
57 {
58 weighted_loss(i) = loss(i) * class_weights.at(y(i));
59 sum_weights += class_weights.at(y(i));
60 }
61
62 // equivalent of sklearn's log_loss with weights. It uses np.average,
63 // which returns avg = sum(a * weights) / sum(weights)
64 return weighted_loss.sum() / sum_weights; // normalize weight contributions
65 }
66
67 return loss.mean();
68}
69
70// accuracy
71float zero_one_loss(const VectorXf& y,
72 const VectorXf& predict_proba, VectorXf& loss,
73 const vector<float>& class_weights )
74{
75 VectorXi yhat = (predict_proba.array() > 0.5).cast<int>();
76
77 // we are actually finding wrong predictions here
78 loss = (yhat.array() != y.cast<int>().array()).cast<float>();
79
80 // Apply class weights if provided
81 if (!class_weights.empty()) {
82 for (int i = 0; i < y.rows(); ++i) {
83 loss(i) *= class_weights.at(y(i));
84 }
85 }
86
87 // since loss is wrong predictions, we need to invert it
88 return 1.0 - loss.mean();
89}
90
91// balanced accuracy
92float bal_zero_one_loss(const VectorXf& y,
93 const VectorXf& predict_proba, VectorXf& loss,
94 const vector<float>& class_weights )
95{
96 VectorXi yhat = (predict_proba.array() > 0.5).cast<int>();
97
98 loss = (yhat.array() != y.cast<int>().array()).cast<float>();
99
100 float TP = 0;
101 float FP = 0;
102 float TN = 0;
103 float FN = 0;
104
105 int num_instances = y.rows();
106 for (int i = 0; i < num_instances; ++i) {
107 // float weight = class_weights.empty() ? 1.0f : class_weights.at(y(i));
108 float weight = 1.0f; // it is already balanced; ignoring class weights
109
110 if (yhat(i) == 1.0 && y(i) == 1.0) TP += weight;
111 else if (yhat(i) == 1.0 && y(i) == 0.0) FP += weight;
112 else if (yhat(i) == 0.0 && y(i) == 0.0) TN += weight;
113 else FN += weight;
114 }
115
116 float eps = pow(10,-10);
117
118 float TPR = (TP + eps) / (TP + FN + eps);
119 float TNR = (TN + eps) / (TN + FP + eps);
120
121 return (TPR + TNR) / 2.0;
122}
123
124float average_precision_score(const VectorXf& y, const VectorXf& predict_proba,
125 VectorXf& loss,
126 const vector<float>& class_weights) {
127
128 // TODO: revisit this
129
130 float eps = pow(10,-10);
131
132 // Assuming y contains binary labels (0 or 1)
133 int num_instances = y.rows();
134
135 // get argsort of predict proba (descending)
136 vector<int> argsort(num_instances);
137
138 iota(argsort.begin(), argsort.end(), 0);
139 sort(argsort.begin(), argsort.end(), [&](int i, int j) {
140 return predict_proba(i) > predict_proba(j);
141 });
142
143 float ysum = 0;
144 if (!class_weights.empty())
145 for (int i = 0; i < y.size(); i++) {
146 ysum += y(i) * class_weights.at(y(i));
147 }
148 else
149 ysum = y.sum();
150
151 // Calculate the precision and recall values
152 VectorXf precision(num_instances);
153 VectorXf recall(num_instances);
154
155 float true_positives = 0;
156 float false_positives = 0;
157 float positives = ysum;
158
159 // we need to iterate over the sorted indices
160 // and calculate precision and recall at each step
161 for (int i = 0; i < num_instances; ++i) {
162 int index = argsort[i];
163
164 float weight = class_weights.empty() ? 1.0f : class_weights.at(y(index));
165
166 if (y(index) > 0.5) {
167 true_positives += weight;
168 }
169 else {
170 false_positives += weight;
171 }
172
173 int relevant = true_positives+false_positives;
174
175 precision(i) = relevant==0.0 ? 0.0 : true_positives/relevant;
176 recall(i) = ysum==0.0 ? 1.0 : true_positives/ysum;
177 }
178
179 // Calculate the average precision score
180 float average_precision = 0.0;
181 loss.resize(num_instances);
182
183 for (int i = 0; i < num_instances; ++i) {
184 if (i > 0)
185 average_precision += (recall(i) - recall(i-1)) * precision(i);
186
187 int index = argsort[i];
188 float p = predict_proba(index);
189
190 // The loss vector is used in lexicase selection. we need to set something useful here
191 // that does make sense on individual level. Using log loss here.
192 if (p < eps || 1 - p < eps)
193 loss(index) = -(y(index)*log(eps) + (1-y(index))*log(1-eps));
194 else
195 loss(index) = -(y(index)*log(p) + (1-y(index))*log(1-p));
196 }
197
198 return average_precision;
199}
200
201// multinomial log loss
202VectorXf multi_log_loss(const VectorXf& y, const ArrayXXf& predict_proba,
203 const vector<float>& class_weights)
204{
205 // TODO: fix softmax and multiclassification, then implement this
206 VectorXf loss = VectorXf::Zero(y.rows());
207
208 // TODO: needs to be the index of unique elements
209 // get class labels
210 // vector<float> uc = unique( ArrayXi(y.cast<int>()) );
211
212 // float eps = pow(10,-10);
213 // float sum_weights = 0;
214 // for (unsigned i = 0; i < y.rows(); ++i)
215 // {
216 // for (const auto& c : uc)
217 // {
218 // // for specific class
219 // ArrayXf yhat = predict_proba.col(int(c));
220 // /* std::cout << "class " << c << "\n"; */
221
222 // /* float yi = y(i) == c ? 1.0 : 0.0 ; */
223 // /* std::cout << "yi: " << yi << ", yhat(" << i << "): " << yhat(i) ; */
224 // if (y(i) == c)
225 // {
226 // if (yhat(i) < eps || 1 - yhat(i) < eps)
227 // {
228 // // clip probabilities since log loss is undefined for yhat=0 or yhat=1
229 // loss(i) += -log(eps);
230 // }
231 // else
232 // {
233 // loss(i) += -log(yhat(i));
234 // }
235 // /* std::cout << ", loss(" << i << ") = " << loss(i); */
236 // }
237 // /* std::cout << "\n"; */
238 // }
239 // if (!class_weights.empty()){
240 // /* std::cout << "weights.at(y(" << i << ")): " << class_weights.at(y(i)) << "\n"; */
241 // loss(i) = loss(i)*class_weights.at(y(i));
242 // sum_weights += class_weights.at(y(i));
243 // }
244 // }
245 // if (sum_weights > 0)
246 // loss = loss.array() / sum_weights * y.size();
247
248 /* cout << "loss.mean(): " << loss.mean() << "\n"; */
249 /* cout << "loss.sum(): " << loss.sum() << "\n"; */
250 return loss;
251}
252
253float mean_multi_log_loss(const VectorXf& y,
254 const ArrayXXf& predict_proba, VectorXf& loss,
255 const vector<float>& class_weights)
256{
257 loss = multi_log_loss(y, predict_proba, class_weights);
258
259 /* std::cout << "loss: " << loss.transpose() << "\n"; */
260 /* std::cout << "mean loss: " << loss.mean() << "\n"; */
261 return loss.mean();
262}
263
264float multi_zero_one_loss(const VectorXf& y,
265 const ArrayXXf& predict_proba, VectorXf& loss,
266 const vector<float>& class_weights )
267{
268 // TODO: implement this
269 // vector<float> uc = unique(y);
270 // vector<int> c;
271 // for (const auto& i : uc)
272 // c.push_back(int(i));
273
274 // // sensitivity (TP) and specificity (TN)
275 // vector<float> TP(c.size(),0.0), TN(c.size(), 0.0), P(c.size(),0.0), N(c.size(),0.0);
276 // ArrayXf class_accuracies(c.size());
277
278 // // get class counts
279
280 // for (unsigned i=0; i< c.size(); ++i)
281 // {
282 // P.at(i) = (y.array().cast<int>() == c.at(i)).count(); // total positives for this class
283 // N.at(i) = (y.array().cast<int>() != c.at(i)).count(); // total negatives for this class
284 // }
285
286
287 // for (unsigned i = 0; i < y.rows(); ++i)
288 // {
289 // if (yhat(i) == y(i)) // true positive
290 // ++TP.at(y(i) == -1 ? 0 : y(i)); // if-then ? accounts for -1 class encoding
291
292 // for (unsigned j = 0; j < c.size(); ++j)
293 // if ( y(i) !=c.at(j) && yhat(i) != c.at(j) ) // true negative
294 // ++TN.at(j);
295
296 // }
297
298 // // class-wise accuracy = 1/2 ( true positive rate + true negative rate)
299 // for (unsigned i=0; i< c.size(); ++i){
300 // class_accuracies(i) = (TP.at(i)/P.at(i) + TN.at(i)/N.at(i))/2;
301 // //std::cout << "TP(" << i << "): " << TP.at(i) << ", P[" << i << "]: " << P.at(i) << "\n";
302 // //std::cout << "TN(" << i << "): " << TN.at(i) << ", N[" << i << "]: " << N.at(i) << "\n";
303 // //std::cout << "class accuracy(" << i << "): " << class_accuracies(i) << "\n";
304 // }
305
306 // // set loss vectors if third argument supplied
307 // loss = (yhat.cast<int>().array() != y.cast<int>().array()).cast<float>();
308
309 // return 1.0 - class_accuracies.mean();
310
311 return 0.0;
312}
313
314} // metrics
315} // Brush
float multi_zero_one_loss(const VectorXf &y, const ArrayXXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
Accuracy for multi-classification.
Definition metrics.cpp:264
float zero_one_loss(const VectorXf &y, const VectorXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
Accuracy for binary classification.
Definition metrics.cpp:71
float mean_log_loss(const VectorXf &y, const VectorXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
log loss
Definition metrics.cpp:43
float mean_multi_log_loss(const VectorXf &y, const ArrayXXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
Calculates the mean multinomial log loss between the predicted probabilities and the true labels.
Definition metrics.cpp:253
float average_precision_score(const VectorXf &y, const VectorXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
Calculates the average precision score between the predicted probabilities and the true labels.
Definition metrics.cpp:124
float mse(const VectorXf &y, const VectorXf &yhat, VectorXf &loss, const vector< float > &class_weights)
mean squared error
Definition metrics.cpp:9
VectorXf multi_log_loss(const VectorXf &y, const ArrayXXf &predict_proba, const vector< float > &class_weights)
Calculates the multinomial log loss between the predicted probabilities and the true labels.
Definition metrics.cpp:202
VectorXf log_loss(const VectorXf &y, const VectorXf &predict_proba, const vector< float > &class_weights)
Calculates the log loss between the predicted probabilities and the true labels.
Definition metrics.cpp:17
float bal_zero_one_loss(const VectorXf &y, const VectorXf &predict_proba, VectorXf &loss, const vector< float > &class_weights)
Balanced accuracy for binary classification.
Definition metrics.cpp:92
vector< size_t > argsort(const vector< T > &v, bool ascending=true)
return indices that sort a vector
Definition utils.h:247
string to_string(const T &value)
template function to convert objects to string for logging
Definition utils.h:369
< nsga2 selection operator for getting the front
Definition bandit.cpp:4