#include "media/learning/impl/learning_task_controller_impl.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "media/learning/impl/distribution_reporter.h"
#include "media/learning/impl/extra_trees_trainer.h"
#include "media/learning/impl/lookup_table_trainer.h"
namespace media {
namespace learning {
LearningTaskControllerImpl::LearningTaskControllerImpl(
const LearningTask& task,
std::unique_ptr<DistributionReporter> reporter,
SequenceBoundFeatureProvider feature_provider)
: task_(task),
training_data_(std::make_unique<TrainingData>()),
reporter_(std::move(reporter)),
helper_(std::make_unique<LearningTaskControllerHelper>(
task,
base::BindRepeating(&LearningTaskControllerImpl::AddFinishedExample,
AsWeakPtr()),
std::move(feature_provider))),
expected_feature_count_(task_.feature_descriptions.size()) {
if (task_.feature_subset_size)
DoFeatureSubsetSelection();
switch (task_.model) {
case LearningTask::Model::kExtraTrees:
trainer_ = std::make_unique<ExtraTreesTrainer>();
break;
case LearningTask::Model::kLookupTable:
trainer_ = std::make_unique<LookupTableTrainer>();
break;
}
}
LearningTaskControllerImpl::~LearningTaskControllerImpl() = default;
void LearningTaskControllerImpl::BeginObservation(
base::UnguessableToken id,
const FeatureVector& features,
const absl::optional<TargetValue>& default_target,
const absl::optional<ukm::SourceId>& source_id) {
if (!trainer_)
return;
DCHECK(!default_target);
helper_->BeginObservation(id, features, source_id);
}
void LearningTaskControllerImpl::CompleteObservation(
base::UnguessableToken id,
const ObservationCompletion& completion) {
if (!trainer_)
return;
helper_->CompleteObservation(id, completion);
}
void LearningTaskControllerImpl::CancelObservation(base::UnguessableToken id) {
if (!trainer_)
return;
helper_->CancelObservation(id);
}
void LearningTaskControllerImpl::UpdateDefaultTarget(
base::UnguessableToken id,
const absl::optional<TargetValue>& default_target) {
NOTREACHED();
}
const LearningTask& LearningTaskControllerImpl::GetLearningTask() {
return task_;
}
void LearningTaskControllerImpl::PredictDistribution(
const FeatureVector& features,
PredictionCB callback) {
if (model_)
std::move(callback).Run(model_->PredictDistribution(features));
else
std::move(callback).Run(absl::nullopt);
}
void LearningTaskControllerImpl::AddFinishedExample(LabelledExample example,
ukm::SourceId source_id) {
if (!trainer_ || example.features.size() != expected_feature_count_)
return;
FeatureVector new_features;
if (task_.feature_subset_size) {
for (auto& iter : feature_indices_)
new_features.push_back(example.features[iter]);
example.features = std::move(new_features);
}
DCHECK_EQ(example.features.size(), task_.feature_descriptions.size());
if (training_data_->size() >= task_.max_data_set_size) {
(*training_data_)[rng()->Generate(training_data_->size())] = example;
} else {
training_data_->push_back(example);
}
num_untrained_examples_++;
if (model_ && reporter_) {
TargetHistogram predicted = model_->PredictDistribution(example.features);
DistributionReporter::PredictionInfo info;
info.observed = example.target_value;
info.source_id = source_id;
info.total_training_weight = last_training_weight_;
info.total_training_examples = last_training_size_;
reporter_->GetPredictionCallback(info).Run(predicted);
}
if (training_is_in_progress_)
return;
double frac = ((double)num_untrained_examples_) / training_data_->size();
if (frac < task_.min_new_data_fraction)
return;
num_untrained_examples_ = 0;
last_training_weight_ = training_data_->total_weight();
last_training_size_ = training_data_->size();
TrainedModelCB model_cb =
base::BindOnce(&LearningTaskControllerImpl::OnModelTrained, AsWeakPtr(),
training_data_->total_weight(), training_data_->size());
training_is_in_progress_ = true;
trainer_->Train(task_, *training_data_, std::move(model_cb));
}
void LearningTaskControllerImpl::OnModelTrained(double training_weight,
int training_size,
std::unique_ptr<Model> model) {
DCHECK(training_is_in_progress_);
training_is_in_progress_ = false;
model_ = std::move(model);
last_training_weight_ = training_weight;
last_training_size_ = training_size;
}
void LearningTaskControllerImpl::SetTrainerForTesting(
std::unique_ptr<TrainingAlgorithm> trainer) {
trainer_ = std::move(trainer);
}
void LearningTaskControllerImpl::DoFeatureSubsetSelection() {
std::vector<size_t> features;
for (size_t i = 0; i < task_.feature_descriptions.size(); i++)
features.push_back(i);
for (int i = 0; i < *task_.feature_subset_size; i++) {
int r = rng()->Generate(features.size() - i) + i;
std::swap(features[i], features[r]);
}
for (int i = 0; i < *task_.feature_subset_size; i++)
feature_indices_.insert(features[i]);
std::vector<LearningTask::ValueDescription> adjusted_descriptions;
for (auto& iter : feature_indices_)
adjusted_descriptions.push_back(task_.feature_descriptions[iter]);
task_.feature_descriptions = adjusted_descriptions;
if (reporter_)
reporter_->SetFeatureSubset(feature_indices_);
}
}
}