Commit 7189d272 authored by Jia's avatar Jia Committed by Commit Bot

[On-device adaptive brightness] Allow training to start immediately.

This cl changes when training should start.
1. Previously, training will only start after user has been inactive for a
certain amount of time (the training delay), and also if there are at least
certain number of training points. A training point is created when a user
makes a brightness adjustment.

2. Now, training can still be delayed, but if the delay is 0, we will start
training immediately after a training point is received. We no longer need
to accumulate data points before training.
- We run the training job and also disk-writing job (for writing trained curves)
in a non-UI thread, hence it's non-blocking.

Bug: 881215
Change-Id: Ifcb0714bd74d278722d1c718f57ca82dd16aed1b
Reviewed-on: https://chromium-review.googlesource.com/c/1350020Reviewed-by: default avatarAndrew Moylan <amoylan@chromium.org>
Commit-Queue: Jia Meng <jiameng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611067}
parent 3b49cce0
......@@ -116,9 +116,6 @@ bool SetInitialCurves(Trainer* trainer,
} // namespace
constexpr base::TimeDelta ModellerImpl::kTrainingDelay;
constexpr size_t ModellerImpl::kMaxTrainingDataPoints;
constexpr size_t ModellerImpl::kMinTrainingDataPoints;
constexpr int ModellerImpl::kAmbientLightHorizonSeconds;
constexpr base::TimeDelta ModellerImpl::kAmbientLightHorizon;
constexpr int ModellerImpl::kNumberAmbientValuesToTrack;
......@@ -200,10 +197,7 @@ void ModellerImpl::OnUserBrightnessChanged(double old_brightness_percent,
ConvertToLog(average_ambient_lux),
tick_clock_->NowTicks()});
if (data_cache_.size() == kMaxTrainingDataPoints) {
model_timer_.Stop();
StartTraining();
}
ScheduleTrainerStart();
}
void ModellerImpl::OnUserBrightnessChangeRequested() {}
......@@ -244,6 +238,14 @@ MonotoneCubicSpline ModellerImpl::GetGlobalCurveForTesting() const {
return global_curve_;
}
size_t ModellerImpl::GetMaxTrainingDataPointsForTesting() const {
return max_training_data_points_;
}
base::TimeDelta ModellerImpl::GetTrainingDelayForTesting() const {
return training_delay_;
}
base::FilePath ModellerImpl::GetCurvePathFromProfile(const Profile* profile) {
DCHECK(profile);
const base::FilePath empty_path;
......@@ -305,6 +307,19 @@ ModellerImpl::ModellerImpl(
als_reader_observer_.Add(als_reader);
brightness_monitor_observer_.Add(brightness_monitor);
user_activity_observer_.Add(user_activity_detector);
const int max_training_data_points = GetFieldTrialParamByFeatureAsInt(
features::kAutoScreenBrightness, "max_training_data_points", -1);
if (max_training_data_points > 0) {
max_training_data_points_ = max_training_data_points;
}
const int training_delay_in_seconds = GetFieldTrialParamByFeatureAsInt(
features::kAutoScreenBrightness, "training_delay_in_seconds",
training_delay_.InSeconds());
if (training_delay_in_seconds >= 0) {
training_delay_ = base::TimeDelta::FromSeconds(training_delay_in_seconds);
}
}
......@@ -398,20 +413,31 @@ void ModellerImpl::OnSetInitialCurves(
is_modeller_enabled_ = true;
OnInitializationComplete();
// We may have received a brightness change as a training example before the
// model is set up. Call |ScheduleTrainerStart| to prepare training.
ScheduleTrainerStart();
}
void ModellerImpl::ScheduleTrainerStart() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_modeller_enabled_.has_value() || !*is_modeller_enabled_)
return;
if (data_cache_.size() >= max_training_data_points_ ||
training_delay_.is_zero()) {
model_timer_.Stop();
StartTraining();
return;
}
// Reset the timer if it's already running.
model_timer_.Start(FROM_HERE, kTrainingDelay, this,
model_timer_.Start(FROM_HERE, training_delay_, this,
&ModellerImpl::StartTraining);
}
void ModellerImpl::StartTraining() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (data_cache_.size() < kMinTrainingDataPoints) {
ScheduleTrainerStart();
if (data_cache_.empty()) {
return;
}
......@@ -434,8 +460,6 @@ void ModellerImpl::OnTrainingFinished(const MonotoneCubicSpline& curve) {
base::BindOnce(&SaveCurveToDisk, curve_path_, curve, is_testing_),
base::BindOnce(&ModellerImpl::OnCurveSavedToDisk,
weak_ptr_factory_.GetWeakPtr()));
ScheduleTrainerStart();
}
} // namespace auto_screen_brightness
......
......@@ -41,19 +41,6 @@ class ModellerImpl : public Modeller,
public BrightnessMonitor::Observer,
public ui::UserActivityObserver {
public:
// Once user remains idle for |kTrainingDelay|, we start training the model.
static constexpr base::TimeDelta kTrainingDelay =
base::TimeDelta::FromSeconds(60);
// If number of recorded training data has reached |kMaxTrainingDataPoints| we
// start training immediately, without waiting for user to become idle for
// |kTrainingDelay|.
static constexpr size_t kMaxTrainingDataPoints = 100;
// Only train when there are at least |kMinTrainingDataPoints| training data
// points.
static constexpr size_t kMinTrainingDataPoints = 10;
// TODO(jiameng): we currently use past 10 seconds of ambient values to
// calculate average. May revise.
static constexpr int kAmbientLightHorizonSeconds = 10;
......@@ -109,8 +96,14 @@ class ModellerImpl : public Modeller,
// training.
size_t NumberTrainingDataPointsForTesting() const;
// Returns |global_curve_| for unit tests.
MonotoneCubicSpline GetGlobalCurveForTesting() const;
// Returns |max_training_data_points_| for unit tests.
size_t GetMaxTrainingDataPointsForTesting() const;
base::TimeDelta GetTrainingDelayForTesting() const;
// Returns the path that will be used to store curves. It also creates
// intermediate directories if they do not exist. Returns an empty path on
// failures.
......@@ -164,7 +157,13 @@ class ModellerImpl : public Modeller,
const base::Optional<MonotoneCubicSpline>& loaded_curve,
bool is_personal_curve_valid);
// Starts |model_timer_| to start training after certain inactivity period.
// Either starts training immediately or delays it for |training_delay_|.
// Training starts immediately if |training_delay_| is 0 or number of training
// points reached |max_training_data_points_|.
// This function is called after a user brightness change signal is received
// (that will be used as an example), and when a user activity is detected.
// It's also called after initial curves are set.
// Nothing will happen if model is not enabled.
void ScheduleTrainerStart();
// Starts model training and runs it in non UI thread. Also clears
......@@ -178,6 +177,17 @@ class ModellerImpl : public Modeller,
// are running on non-UI thread.
const bool is_testing_ = false;
// If number of recorded training data has reached |max_training_data_points_|
// we start training immediately, without waiting for user to become idle for
// |training_delay_|. This can be overridden by experiment flag
// "max_training_data_points".
size_t max_training_data_points_ = 100;
// Once user remains idle for |training_delay_|, we start training the model.
// If this value is 0, we will not need to wait for user to remain inactive.
// This can be overridden by experiment flag "training_delay_in_seconds".
base::TimeDelta training_delay_ = base::TimeDelta::FromSeconds(60);
ScopedObserver<AlsReader, AlsReader::Observer> als_reader_observer_;
ScopedObserver<BrightnessMonitor, BrightnessMonitor::Observer>
......
......@@ -101,7 +101,14 @@ class FakeTrainer : public Trainer {
const std::vector<TrainingDataPoint>& data) override {
DCHECK(is_configured_);
DCHECK(current_curve_);
current_curve_.emplace(CreateTestCurveFromTrainingData(data));
std::vector<TrainingDataPoint> used_data = data;
// We need at least 2 points to create a MonotoneCubicSpline. Hence we
// insert another one if |data| has only 1 point.
if (data.size() == 1) {
used_data.push_back(data[0]);
}
current_curve_.emplace(CreateTestCurveFromTrainingData(used_data));
return *current_curve_;
}
......@@ -408,8 +415,8 @@ TEST_F(ModellerImplTest, OnAmbientLightUpdated) {
}
// User brightness changes are received, training example cache reaches
// |kMaxTrainingDataPoints| to trigger early training. This all happens within a
// small window shorter than |kTrainingDelay|.
// |max_training_data_points_| to trigger early training. This all happens
// within a small window shorter than |training_delay_|.
TEST_F(ModellerImplTest, OnUserBrightnessChanged) {
Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess);
......@@ -418,7 +425,9 @@ TEST_F(ModellerImplTest, OnUserBrightnessChanged) {
base::nullopt /* personal_curve */);
std::vector<TrainingDataPoint> expected_data;
for (size_t i = 0; i < ModellerImpl::kMaxTrainingDataPoints - 1; ++i) {
for (size_t i = 0; i < modeller_->GetMaxTrainingDataPointsForTesting() - 1;
++i) {
EXPECT_EQ(i, modeller_->NumberTrainingDataPointsForTesting());
scoped_task_environment_.FastForwardBy(
base::TimeDelta::FromMilliseconds(1));
......@@ -435,7 +444,7 @@ TEST_F(ModellerImplTest, OnUserBrightnessChanged) {
}
// Training should not have started.
EXPECT_EQ(ModellerImpl::kMaxTrainingDataPoints - 1,
EXPECT_EQ(modeller_->GetMaxTrainingDataPointsForTesting() - 1,
modeller_->NumberTrainingDataPointsForTesting());
// Add one more data point to trigger the training early.
......@@ -471,7 +480,7 @@ TEST_F(ModellerImplTest, MultipleUserActivities) {
fake_als_reader_.ReportAmbientLightUpdate(30);
std::vector<TrainingDataPoint> expected_data;
for (size_t i = 0; i < ModellerImpl::kMinTrainingDataPoints; ++i) {
for (size_t i = 0; i < 10; ++i) {
EXPECT_EQ(i, modeller_->NumberTrainingDataPointsForTesting());
scoped_task_environment_.FastForwardBy(
base::TimeDelta::FromMilliseconds(1));
......@@ -487,33 +496,31 @@ TEST_F(ModellerImplTest, MultipleUserActivities) {
ConvertToLog(modeller_->AverageAmbientForTesting()), now});
}
EXPECT_EQ(ModellerImpl::kMinTrainingDataPoints,
modeller_->NumberTrainingDataPointsForTesting());
EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u);
scoped_task_environment_.FastForwardBy(ModellerImpl::kTrainingDelay -
base::TimeDelta::FromSeconds(10));
scoped_task_environment_.FastForwardBy(
modeller_->GetTrainingDelayForTesting() / 2);
// A user activity is received, timer should be reset.
const ui::MouseEvent mouse_event(ui::ET_MOUSE_EXITED, gfx::Point(0, 0),
gfx::Point(0, 0), base::TimeTicks(), 0, 0);
modeller_->OnUserActivity(&mouse_event);
scoped_task_environment_.FastForwardBy(ModellerImpl::kTrainingDelay -
base::TimeDelta::FromSeconds(2));
EXPECT_EQ(ModellerImpl::kMinTrainingDataPoints,
modeller_->NumberTrainingDataPointsForTesting());
scoped_task_environment_.FastForwardBy(
modeller_->GetTrainingDelayForTesting() / 3);
EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u);
// Another user event is received.
modeller_->OnUserActivity(&mouse_event);
// After |kTrainingDelay| - 2 seconds, no training has started.
scoped_task_environment_.FastForwardBy(ModellerImpl::kTrainingDelay -
base::TimeDelta::FromSeconds(2));
// After |training_delay_|/2, no training has started.
scoped_task_environment_.FastForwardBy(
modeller_->GetTrainingDelayForTesting() / 2);
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(ModellerImpl::kMinTrainingDataPoints,
modeller_->NumberTrainingDataPointsForTesting());
EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u);
// After another 2 seconds, training is scheduled.
scoped_task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
// After another |training_delay_|/2, training is scheduled.
scoped_task_environment_.FastForwardBy(
modeller_->GetTrainingDelayForTesting() / 2);
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(0u, modeller_->NumberTrainingDataPointsForTesting());
......@@ -526,27 +533,6 @@ TEST_F(ModellerImplTest, MultipleUserActivities) {
EXPECT_EQ(expected_curve, *result_curve);
}
// No training is done because number of training data points is less than
// |kMinTrainingDataPoints|.
TEST_F(ModellerImplTest, MinTrainingDataPointsRequired) {
Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess);
test_observer_->CheckStatus(true /* is_model_initialized */,
modeller_->GetGlobalCurveForTesting(),
base::nullopt /* personal_curve */);
fake_als_reader_.ReportAmbientLightUpdate(30);
modeller_->OnUserBrightnessChanged(10, 20);
// No training is done because we have too few training data points.
scoped_task_environment_.FastForwardBy(ModellerImpl::kTrainingDelay +
base::TimeDelta::FromSeconds(10));
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(1u, modeller_->NumberTrainingDataPointsForTesting());
EXPECT_FALSE(test_observer_->trained_curve_received());
}
// Global curve specified by valid experiment parameter.
TEST_F(ModellerImplTest, GlobaCurveFromValidExperimentParam) {
const std::string global_curve_spec("1,10\n2,20\n3,30");
......@@ -585,6 +571,32 @@ TEST_F(ModellerImplTest, GlobaCurveFromInvalidExperimentParam) {
base::nullopt /* personal_curve */);
}
// Training delay is 0, hence we train as soon as we have 1 data point.
TEST_F(ModellerImplTest, ZeroTrainingDelay) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kAutoScreenBrightness, {
{"training_delay_in_seconds", "0"},
});
Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess);
test_observer_->CheckStatus(true /* is_model_initialized */,
modeller_->GetGlobalCurveForTesting(),
base::nullopt /* personal_curve */);
fake_als_reader_.ReportAmbientLightUpdate(30);
const ui::MouseEvent mouse_event(ui::ET_MOUSE_EXITED, gfx::Point(0, 0),
gfx::Point(0, 0), base::TimeTicks(), 0, 0);
modeller_->OnUserActivity(&mouse_event);
modeller_->OnUserBrightnessChanged(10, 20);
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(0u, modeller_->NumberTrainingDataPointsForTesting());
EXPECT_TRUE(test_observer_->trained_curve_received());
}
} // namespace auto_screen_brightness
} // namespace power
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment