#include "media/learning/impl/random_tree_trainer.h"
#include <math.h>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace media {
namespace learning {
RandomTreeTrainer::Split::Split() = default;
RandomTreeTrainer::Split::Split(int index) : split_index(index) {}
RandomTreeTrainer::Split::Split(Split&& rhs) = default;
RandomTreeTrainer::Split::~Split() = default;
RandomTreeTrainer::Split& RandomTreeTrainer::Split::operator=(Split&& rhs) =
default;
RandomTreeTrainer::Split::BranchInfo::BranchInfo() = default;
RandomTreeTrainer::Split::BranchInfo::BranchInfo(BranchInfo&& rhs) = default;
RandomTreeTrainer::Split::BranchInfo::~BranchInfo() = default;
struct InteriorNode : public Model {
InteriorNode(const LearningTask& task,
int split_index,
FeatureValue split_point)
: split_index_(split_index),
ordering_(task.feature_descriptions[split_index].ordering),
split_point_(split_point) {}
TargetHistogram PredictDistribution(const FeatureVector& features) override {
FeatureValue f;
switch (ordering_) {
case LearningTask::Ordering::kUnordered:
f = FeatureValue(features[split_index_] == split_point_);
break;
case LearningTask::Ordering::kNumeric:
f = FeatureValue(features[split_index_] > split_point_);
break;
}
auto iter = children_.find(f);
if (iter == children_.end())
return TargetHistogram();
return iter->second->PredictDistribution(features);
}
TargetHistogram PredictDistributionWithMissingValues(
const FeatureVector& features) {
TargetHistogram total;
for (auto& child_pair : children_) {
TargetHistogram predicted =
child_pair.second->PredictDistribution(features);
total += predicted;
}
return total;
}
void AddChild(FeatureValue v, std::unique_ptr<Model> child) {
DCHECK(!children_.contains(v));
children_.emplace(v, std::move(child));
}
private:
int split_index_ = -1;
base::flat_map<FeatureValue, std::unique_ptr<Model>> children_;
LearningTask::Ordering ordering_;
FeatureValue split_point_;
};
struct LeafNode : public Model {
LeafNode(const TrainingData& training_data,
const std::vector<size_t> training_idx,
LearningTask::Ordering ordering) {
for (size_t idx : training_idx)
distribution_ += training_data[idx];
distribution_.Normalize();
}
TargetHistogram PredictDistribution(const FeatureVector&) override {
return distribution_;
}
private:
TargetHistogram distribution_;
};
RandomTreeTrainer::RandomTreeTrainer(RandomNumberGenerator* rng)
: HasRandomNumberGenerator(rng) {}
RandomTreeTrainer::~RandomTreeTrainer() {}
void RandomTreeTrainer::Train(const LearningTask& task,
const TrainingData& training_data,
TrainedModelCB model_cb) {
std::vector<size_t> training_idx;
training_idx.reserve(training_data.size());
for (size_t idx = 0; idx < training_data.size(); idx++)
training_idx.push_back(idx);
auto model = Train(task, training_data, training_idx);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(model_cb), std::move(model)));
}
std::unique_ptr<Model> RandomTreeTrainer::Train(
const LearningTask& task,
const TrainingData& training_data,
const std::vector<size_t>& training_idx) {
if (training_data.empty()) {
return std::make_unique<LeafNode>(training_data, std::vector<size_t>(),
LearningTask::Ordering::kUnordered);
}
DCHECK_EQ(task.feature_descriptions.size(), training_data[0].features.size());
FeatureSet unused_set;
for (size_t idx = 0; idx < task.feature_descriptions.size(); idx++)
unused_set.insert(idx);
return Build(task, training_data, training_idx, unused_set);
}
std::unique_ptr<Model> RandomTreeTrainer::Build(
const LearningTask& task,
const TrainingData& training_data,
const std::vector<size_t>& training_idx,
const FeatureSet& unused_set) {
DCHECK_GT(training_idx.size(), 0u);
absl::optional<TargetValue> target_value(
training_data[training_idx[0]].target_value);
std::vector<absl::optional<FeatureValue>> feature_values;
feature_values.resize(training_data[0].features.size());
for (size_t feature_idx : unused_set) {
feature_values[feature_idx] =
training_data[training_idx[0]].features[feature_idx];
}
for (size_t idx : training_idx) {
const LabelledExample& example = training_data[idx];
if (target_value && target_value != example.target_value)
target_value.reset();
for (size_t feature_idx : unused_set) {
auto& value = feature_values[feature_idx];
if (value && *value != example.features[feature_idx])
value.reset();
}
}
if (target_value) {
return std::make_unique<LeafNode>(training_data, training_idx,
task.target_description.ordering);
}
FeatureSet new_unused_set = unused_set;
for (size_t feature_idx : unused_set) {
auto& value = feature_values[feature_idx];
if (value)
new_unused_set.erase(feature_idx);
}
FeatureSet feature_candidates = new_unused_set;
const size_t features_per_split =
std::max(static_cast<int>(sqrt(feature_candidates.size())), 3);
while (feature_candidates.size() > features_per_split) {
size_t which = rng()->Generate(feature_candidates.size());
auto iter = feature_candidates.begin();
for (; which; which--, iter++)
;
feature_candidates.erase(iter);
}
Split best_potential_split;
for (int i : feature_candidates) {
Split potential_split =
ConstructSplit(task, training_data, training_idx, i);
if (potential_split.nats_remaining < best_potential_split.nats_remaining) {
best_potential_split = std::move(potential_split);
}
}
if (best_potential_split.branch_infos.size() < 2) {
return std::make_unique<LeafNode>(training_data, training_idx,
task.target_description.ordering);
}
std::unique_ptr<InteriorNode> node = std::make_unique<InteriorNode>(
task, best_potential_split.split_index, best_potential_split.split_point);
for (auto& branch_iter : best_potential_split.branch_infos) {
node->AddChild(branch_iter.first,
Build(task, training_data, branch_iter.second.training_idx,
new_unused_set));
}
return node;
}
RandomTreeTrainer::Split RandomTreeTrainer::ConstructSplit(
const LearningTask& task,
const TrainingData& training_data,
const std::vector<size_t>& training_idx,
int split_index) {
DCHECK_GT(training_idx.size(), 0u);
Split split(split_index);
bool is_numeric = task.feature_descriptions[split_index].ordering ==
LearningTask::Ordering::kNumeric;
if (is_numeric) {
split.split_point =
FindSplitPoint_Numeric(split.split_index, training_data, training_idx);
} else {
split.split_point =
FindSplitPoint_Nominal(split.split_index, training_data, training_idx);
}
double total_weight = 0.;
for (size_t idx : training_idx) {
const LabelledExample& example = training_data[idx];
total_weight += example.weight;
FeatureValue v_i = example.features[split.split_index];
FeatureValue split_feature;
if (is_numeric)
split_feature = FeatureValue(v_i > split.split_point);
else
split_feature = FeatureValue(v_i == split.split_point);
auto result =
split.branch_infos.emplace(split_feature, Split::BranchInfo());
auto iter = result.first;
Split::BranchInfo& branch_info = iter->second;
branch_info.training_idx.push_back(idx);
branch_info.target_histogram += example;
}
switch (task.target_description.ordering) {
case LearningTask::Ordering::kUnordered:
ComputeSplitScore_Nominal(&split, total_weight);
break;
case LearningTask::Ordering::kNumeric:
ComputeSplitScore_Numeric(&split, total_weight);
break;
}
return split;
}
void RandomTreeTrainer::ComputeSplitScore_Nominal(
Split* split,
double total_incoming_weight) {
split->nats_remaining = 0;
for (auto& info_iter : split->branch_infos) {
Split::BranchInfo& branch_info = info_iter.second;
const double weight_along_branch =
branch_info.target_histogram.total_counts();
const double p_branch = weight_along_branch / total_incoming_weight;
for (auto& iter : branch_info.target_histogram) {
double p = iter.second / total_incoming_weight;
split->nats_remaining -= (p * log(p)) * p_branch;
}
}
}
void RandomTreeTrainer::ComputeSplitScore_Numeric(
Split* split,
double total_incoming_weight) {
split->nats_remaining = 0;
for (auto& info_iter : split->branch_infos) {
Split::BranchInfo& branch_info = info_iter.second;
const double weight_along_branch =
branch_info.target_histogram.total_counts();
const double p_branch = weight_along_branch / total_incoming_weight;
double average = branch_info.target_histogram.Average();
for (auto& iter : branch_info.target_histogram) {
double sq_err = (iter.first.value() - average) *
(iter.first.value() - average) * iter.second;
split->nats_remaining += sq_err * p_branch;
}
}
}
FeatureValue RandomTreeTrainer::FindSplitPoint_Numeric(
size_t split_index,
const TrainingData& training_data,
const std::vector<size_t>& training_idx) {
DCHECK_GT(training_idx.size(), 0u);
FeatureValue v_min = training_data[training_idx[0]].features[split_index];
FeatureValue v_max = training_data[training_idx[0]].features[split_index];
for (size_t idx : training_idx) {
const LabelledExample& example = training_data[idx];
FeatureValue v_i = example.features[split_index];
if (v_i < v_min)
v_min = v_i;
if (v_i > v_max)
v_max = v_i;
}
FeatureValue v_split;
if (v_max == v_min) {
v_split = v_max;
} else {
v_split = FeatureValue(
rng()->GenerateDouble(v_max.value() - v_min.value()) + v_min.value());
}
return v_split;
}
FeatureValue RandomTreeTrainer::FindSplitPoint_Nominal(
size_t split_index,
const TrainingData& training_data,
const std::vector<size_t>& training_idx) {
DCHECK_GT(training_idx.size(), 0u);
std::set<FeatureValue> values;
for (size_t idx : training_idx) {
const LabelledExample& example = training_data[idx];
values.insert(example.features[split_index]);
}
size_t which = rng()->Generate(values.size());
auto it = values.begin();
for (; which > 0; it++, which--)
;
return *it;
}
}
}