Brush C++ API
A flexible interpretable machine learning framework
Toggle main menu visibility
Loading...
Searching...
No Matches
weight_optimizer.h
Go to the documentation of this file.
1
/* Brush
2
copyright 2020 William La Cava
3
license: GNU/GPL v3
4
5
Code below heavily inspired by heal-research/operon, Copyright 2019-2022 Heal Research
6
*/
7
#ifndef WEIGHT_OPTIMIZER_H
8
#define WEIGHT_OPTIMIZER_H
9
10
#include <ceres/dynamic_autodiff_cost_function.h>
11
#include <ceres/dynamic_numeric_diff_cost_function.h>
12
#include <ceres/loss_function.h>
13
#include <ceres/solver.h>
14
#include <ceres/tiny_solver.h>
15
16
#include "
tiny_cost_function.h
"
17
18
namespace
Brush
19
{
20
21
struct
OptimizerSummary
{
22
double
InitialCost
;
23
double
FinalCost
;
24
int
Iterations
;
25
int
FunctionEvaluations
;
26
int
JacobianEvaluations
;
27
bool
Success
;
28
};
29
30
template
<
typename
PT>
31
struct
ResidualEvaluator
{
32
typedef
float
Scalar
;
33
ResidualEvaluator
(
PT
& program,
Dataset
const
& dataset,
34
const
vector<float>
& class_weights = {})
35
:
program_
(program)
36
,
dataset_
(dataset)
37
,
numParameters_
(program.get_weights().size())
38
,
y_true_
(dataset.y)
39
,
class_weights_
(class_weights)
40
{}
41
42
template
<
typename
T>
43
auto
operator()
(Eigen::DenseBase<T>& parameters, Eigen::DenseBase<T>& residuals)
const
noexcept
->
void
44
{
45
return
(*
this
)(parameters.data(), residuals.data());
46
}
47
48
template
<
typename
T>
49
auto
operator()
(T
const
* parameters, T* residuals)
const
->
bool
50
{
51
const
T ** new_weights = ¶meters;
52
auto
residualMap = Eigen::Map<Array<T, Dynamic, 1>>(
53
residuals,
GetDataset
().get_n_samples());
54
55
if
constexpr
(PT::program_type ==
ProgramType::MulticlassClassifier
)
56
{
57
using
MatrixType = Array<T, Dynamic, Dynamic>;
58
auto
probabilities =
GetProgram
().template predict_with_weights<MatrixType>(
59
GetDataset
(), new_weights);
60
for
(
int
i = 0; i <
GetDataset
().get_n_samples(); ++i)
61
{
62
const
int
label =
static_cast<
int
>
(
GetTarget
()(i));
63
const
T probability = probabilities(i, label);
64
const
float
weight =
class_weights_
.empty() ? 1.0f
65
:
class_weights_
.at(label);
66
// TinySolver minimizes squared residuals, so sqrt(loss)
67
// makes its objective the weighted multinomial log loss.
68
residualMap(i) = sqrt(T(weight) * -log(probability));
69
}
70
}
71
else
72
{
73
using
ArrayType = Array<T, Dynamic, 1>;
74
ArrayType y_pred =
GetProgram
().template predict_with_weights<ArrayType>(
75
GetDataset
(), new_weights);
76
77
if
(
GetDataset
().classification)
78
{
79
// tolerance to avoid numeric errors.
80
81
// Using an eps with 7 significant digits to avoid weird behavior.
82
// From some experiments, I noticed that using very small values (previous
83
// was pow(10,-10) caused the optimizer to evaluate to nans), probably because
84
// we were calculating log(0) due to cpp ignoring more than 7 significant digits
85
// for floats. using -6. As a reference, tensorflow uses -7.
86
// We need to remember that we dont want to have eps too big, otherwise
87
// we are biasing the predictions away from 0 and 1
88
float
eps = 1e-6f;
89
90
auto
y =
GetTarget
();
91
92
// cout << T(1.0) << ", " << T(eps) << endl;
93
94
// clamp values and avoid log(0)
95
y_pred = y_pred.min(T(1.0) - T(eps)).max(T(eps));
96
97
for
(
int
i = 0; i < y_pred.size(); ++i)
98
{
99
const
float
weight =
class_weights_
.empty() ? 1.0f
100
:
class_weights_
.at(
static_cast<
int
>
(y(i)));
101
const
T
log_loss
= -(T(y(i)) * log(y_pred(i))
102
+ (T(1.0f) - T(y(i))) * log(T(1.0f) - y_pred(i)));
103
// See multiclass branch above: this makes the least-squares
104
// objective equal weighted binary log loss.
105
residualMap(i) = sqrt(T(weight) *
log_loss
);
106
}
107
}
108
else
109
{
110
residualMap = y_pred -
GetTarget
();
111
}
112
}
113
114
return
true
;
115
}
116
117
[[nodiscard]]
auto
NumParameters
() const ->
size_t
{
return
numParameters_
; }
118
[[nodiscard]]
auto
NumResiduals
() const ->
size_t
{
return
y_true_
.get().size(); }
119
inline
auto
GetProgram
()
const
{
return
program_
.get();};
120
inline
auto
GetDataset
()
const
{
return
dataset_
.get();};
121
inline
auto
GetTarget
()
const
{
return
y_true_
.get();};
122
123
private
:
124
std::reference_wrapper<PT>
program_
;
125
std::reference_wrapper<Dataset const>
dataset_
;
126
std::reference_wrapper<ArrayXf const>
y_true_
;
127
vector<float>
class_weights_
;
128
size_t
numParameters_
;
// cache the number of parameters in the tree
129
};
130
131
// TODO: see this struct and try to understand how to make non-templated classes
132
struct
WeightOptimizer
133
{
138
template
<
typename
PT>
139
void
update
(
PT
& program,
const
Dataset
& dataset,
140
const
vector<float>
& class_weights = {})
141
{
142
if
(program.get_n_weights() == 0)
143
return
;
144
145
// fmt::print("number of weights: {}\n",program.get_n_weights());
146
auto
init_weights = program.get_weights();
147
148
using
CFType =
Brush::TinyCostFunction<ResidualEvaluator<PT>
> ;
149
ResidualEvaluator<PT>
evaluator(program, dataset, class_weights);
150
CFType cost_function(evaluator);
151
ceres::TinySolver<CFType> solver;
152
solver.options.max_num_iterations = 10;
153
154
typename
decltype
(solver)
::Parameters
parameters = program.get_weights();
155
solver.Solve(cost_function, ¶meters);
156
157
// fmt::print("Summary:\nInitial cost: {}\nFinal Cost: {}\nIterations: {}\n",
158
// solver.summary.initial_cost,
159
// solver.summary.final_cost,
160
// solver.summary.iterations
161
// );
162
// fmt::print("Initial weights: {}\nFinal weights: {}\n",
163
// init_weights,
164
// parameters
165
// );
166
167
// Validate optimizer results before applying
168
bool
costs_valid = !std::isnan(solver.summary.final_cost) &&
169
!std::isinf(solver.summary.final_cost) &&
170
!std::isnan(solver.summary.initial_cost) &&
171
!std::isinf(solver.summary.initial_cost);
172
173
bool
weights_valid = parameters.allFinite();
174
175
if
(costs_valid && weights_valid &&
176
solver.summary.final_cost < solver.summary.initial_cost)
177
{
178
program.set_weights(parameters);
179
}
180
181
}
182
};
183
184
}
// namespace Brush
185
#endif
Dataset
holds variable type data.
Definition
data.h:51
vector
STL class.
Brush::Eval::log_loss
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:19
Brush
< nsga2 selection operator for getting the front
Definition
bandit.cpp:3
Brush::PT
ProgramType PT
Definition
program.h:39
Brush::ProgramType::MulticlassClassifier
@ MulticlassClassifier
Definition
types.h:73
Brush::OptimizerSummary
Definition
weight_optimizer.h:21
Brush::OptimizerSummary::FinalCost
double FinalCost
Definition
weight_optimizer.h:23
Brush::OptimizerSummary::InitialCost
double InitialCost
Definition
weight_optimizer.h:22
Brush::OptimizerSummary::JacobianEvaluations
int JacobianEvaluations
Definition
weight_optimizer.h:26
Brush::OptimizerSummary::Iterations
int Iterations
Definition
weight_optimizer.h:24
Brush::OptimizerSummary::FunctionEvaluations
int FunctionEvaluations
Definition
weight_optimizer.h:25
Brush::OptimizerSummary::Success
bool Success
Definition
weight_optimizer.h:27
Brush::Parameters
Definition
params.h:20
Brush::ResidualEvaluator
Definition
weight_optimizer.h:31
Brush::ResidualEvaluator::NumResiduals
auto NumResiduals() const -> size_t
Definition
weight_optimizer.h:118
Brush::ResidualEvaluator::GetTarget
auto GetTarget() const
Definition
weight_optimizer.h:121
Brush::ResidualEvaluator::dataset_
std::reference_wrapper< Dataset const > dataset_
Definition
weight_optimizer.h:125
Brush::ResidualEvaluator::GetDataset
auto GetDataset() const
Definition
weight_optimizer.h:120
Brush::ResidualEvaluator::operator()
auto operator()(Eigen::DenseBase< T > ¶meters, Eigen::DenseBase< T > &residuals) const noexcept -> void
Definition
weight_optimizer.h:43
Brush::ResidualEvaluator::numParameters_
size_t numParameters_
Definition
weight_optimizer.h:128
Brush::ResidualEvaluator::Scalar
float Scalar
Definition
weight_optimizer.h:32
Brush::ResidualEvaluator::NumParameters
auto NumParameters() const -> size_t
Definition
weight_optimizer.h:117
Brush::ResidualEvaluator::program_
std::reference_wrapper< PT > program_
Definition
weight_optimizer.h:124
Brush::ResidualEvaluator::operator()
auto operator()(T const *parameters, T *residuals) const -> bool
Definition
weight_optimizer.h:49
Brush::ResidualEvaluator::ResidualEvaluator
ResidualEvaluator(PT &program, Dataset const &dataset, const vector< float > &class_weights={})
Definition
weight_optimizer.h:33
Brush::ResidualEvaluator::GetProgram
auto GetProgram() const
Definition
weight_optimizer.h:119
Brush::ResidualEvaluator::class_weights_
vector< float > class_weights_
Definition
weight_optimizer.h:127
Brush::ResidualEvaluator::y_true_
std::reference_wrapper< ArrayXf const > y_true_
Definition
weight_optimizer.h:126
Brush::TinyCostFunction
Definition
tiny_cost_function.h:81
Brush::WeightOptimizer
Definition
weight_optimizer.h:133
Brush::WeightOptimizer::update
void update(PT &program, const Dataset &dataset, const vector< float > &class_weights={})
Update program weights using non-linear least squares.
Definition
weight_optimizer.h:139
tiny_cost_function.h
src
program
optimizer
weight_optimizer.h
Generated by
1.18.0