Skip to content

Commit e763bc3

Browse files
authored
[ML] Linear scaling change detection (#25)
This implements detection of linear scaling events. It also finishes up the unit testing of change detection and fixes some issues these turned up: specifically, 1) the behaviour when a change is detected but the trend model has no components, 2) the handling of time shifts in the trend model and 3) the handling of data types in the trend component change model. Finally, we are now more careful with the weights we apply to samples added to both the standard and change models. This has meant I've been able to revert scaling the changes, since the trend is less influenced by values during the change detection period if we're likely to detect a change.
1 parent 447f31a commit e763bc3

38 files changed

+1081
-342
lines changed

include/maths/CCalendarComponent.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class MATHS_EXPORT CCalendarComponent : private CDecompositionComponent
8686
//! Clear all data.
8787
void clear(void);
8888

89+
//! Linearly scale the component's by \p scale.
90+
void linearScale(core_t::TTime time, double scale);
91+
8992
//! Adds a value \f$(t, f(t))\f$ to this component.
9093
//!
9194
//! \param[in] time The time of the point.

include/maths/CCalendarComponentAdaptiveBucketing.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class MATHS_EXPORT CCalendarComponentAdaptiveBucketing : private CAdaptiveBucket
7171
//! allocated memory.
7272
void clear(void);
7373

74+
//! Linearly scale the bucket values by \p scale.
75+
void linearScale(double scale);
76+
7477
//! Add the function value at \p time.
7578
//!
7679
//! \param[in] time The time of \p value.

include/maths/CModel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class MATHS_EXPORT CModelParams
7474
bool testForChange(core_t::TTime changeInterval) const;
7575

7676
//! Get the minimum time to detect a change point in the model.
77-
core_t::TTime minimumTimeToDetectChange(core_t::TTime timeSinceLastChangePoint) const;
77+
core_t::TTime minimumTimeToDetectChange(void) const;
7878

7979
//! Get the maximum time to test for a change point in the model.
8080
core_t::TTime maximumTimeToTestForChange(void) const;

include/maths/CRegression.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,23 @@ class MATHS_EXPORT CRegression
235235
}
236236
}
237237

238+
//! Linearly scale the regression model.
239+
//!
240+
//! i.e. apply a transform such that each regression parameter maps
241+
//! to \p scale times its current value.
242+
//!
243+
//! \param[in] scale The scale to apply to the regression parameters.
244+
void linearScale(double scale)
245+
{
246+
if (CBasicStatistics::count(m_S) > 0.0)
247+
{
248+
for (std::size_t i = 0u; i < N; ++i)
249+
{
250+
CBasicStatistics::moment<0>(m_S)(i+2*N-1) *= scale;
251+
}
252+
}
253+
}
254+
238255
//! Multiply the statistics' count by \p scale.
239256
CLeastSquaresOnline scaled(double scale) const
240257
{
@@ -272,14 +289,13 @@ class MATHS_EXPORT CRegression
272289
if (this->parameters(params, maxCondition))
273290
{
274291
std::ptrdiff_t n = static_cast<std::ptrdiff_t>(params.size());
275-
double xi = x;
276292
for (std::ptrdiff_t i = n - 1; i >= 0; --i)
277293
{
278294
result[i] = params[i];
279295
for (std::ptrdiff_t j = i + 1; j < n; ++j)
280296
{
281297
params[j] *= static_cast<double>(i + 1)
282-
/ static_cast<double>(j - i) * xi;
298+
/ static_cast<double>(j - i) * x;
283299
result[i] += params[j];
284300
}
285301
}

include/maths/CSeasonalComponent.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ class MATHS_EXPORT CSeasonalComponent : private CDecompositionComponent
104104
//! Shift the component's slope by \p shift.
105105
void shiftSlope(double shift);
106106

107+
//! Linearly scale the component's by \p scale.
108+
void linearScale(core_t::TTime time, double scale);
109+
107110
//! Adds a value \f$(t, f(t))\f$ to this component.
108111
//!
109112
//! \param[in] time The time of the point.

include/maths/CSeasonalComponentAdaptiveBucketing.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ class MATHS_EXPORT CSeasonalComponentAdaptiveBucketing : private CAdaptiveBucket
9797
//! Shift the regressions' gradients by \p shift.
9898
void shiftSlope(double shift);
9999

100+
//! Linearly scale the regressions by \p scale.
101+
void linearScale(double scale);
102+
100103
//! Add the function value at \p time.
101104
//!
102105
//! \param[in] time The time of \p value.

include/maths/CTimeSeriesChangeDetector.h

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ struct MATHS_EXPORT SChangeDescription
5050
enum EDescription
5151
{
5252
E_LevelShift,
53+
E_LinearScale,
5354
E_TimeShift
5455
};
5556

@@ -88,7 +89,7 @@ class MATHS_EXPORT CUnivariateTimeSeriesChangeDetector
8889
public:
8990
CUnivariateTimeSeriesChangeDetector(const TDecompositionPtr &trendModel,
9091
const TPriorPtr &residualModel,
91-
core_t::TTime minimumTimeToDetect = 6 * core::constants::HOUR,
92+
core_t::TTime minimumTimeToDetect = 12 * core::constants::HOUR,
9293
core_t::TTime maximumTimeToDetect = core::constants::DAY,
9394
double minimumDeltaBicToDetect = 14.0);
9495

@@ -103,6 +104,13 @@ class MATHS_EXPORT CUnivariateTimeSeriesChangeDetector
103104
//! if there has been.
104105
TOptionalChangeDescription change();
105106

107+
//! The function used to decide whether to accept a change.
108+
//! A change is accepted at a value of 1.0 for this function.
109+
//!
110+
//! \param[out] change Filled in with the index of the change
111+
//! the most likely change.
112+
double decisionFunction(std::size_t &change) const;
113+
106114
//! Add \p samples to the change detector.
107115
void addSamples(const TWeightStyleVec &weightStyles,
108116
const TTimeDoublePr1Vec &samples,
@@ -123,7 +131,7 @@ class MATHS_EXPORT CUnivariateTimeSeriesChangeDetector
123131
private:
124132
using TChangeModel = time_series_change_detector_detail::CUnivariateChangeModel;
125133
using TChangeModelPtr = boost::shared_ptr<TChangeModel>;
126-
using TChangeModelPtr4Vec = core::CSmallVector<TChangeModelPtr, 4>;
134+
using TChangeModelPtr5Vec = core::CSmallVector<TChangeModelPtr, 5>;
127135
using TMinMaxAccumulator = CBasicStatistics::CMinMax<core_t::TTime>;
128136

129137
private:
@@ -147,7 +155,7 @@ class MATHS_EXPORT CUnivariateTimeSeriesChangeDetector
147155
double m_CurrentEvidenceOfChange;
148156

149157
//! The change models.
150-
TChangeModelPtr4Vec m_ChangeModels;
158+
TChangeModelPtr5Vec m_ChangeModels;
151159
};
152160

153161
namespace time_series_change_detector_detail
@@ -158,6 +166,7 @@ namespace time_series_change_detector_detail
158166
class MATHS_EXPORT CUnivariateChangeModel : private core::CNonCopyable
159167
{
160168
public:
169+
using TDouble1Vec = core::CSmallVector<double, 1>;
161170
using TDouble4Vec = core::CSmallVector<double, 4>;
162171
using TDouble4Vec1Vec = core::CSmallVector<TDouble4Vec, 1>;
163172
using TTimeDoublePr = std::pair<core_t::TTime, double>;
@@ -189,10 +198,10 @@ class MATHS_EXPORT CUnivariateChangeModel : private core::CNonCopyable
189198
virtual TOptionalChangeDescription change() const = 0;
190199

191200
//! Update the change model with \p samples.
192-
virtual void addSamples(std::size_t count,
193-
const TWeightStyleVec &weightStyles,
201+
virtual void addSamples(const std::size_t count,
202+
TWeightStyleVec weightStyles,
194203
const TTimeDoublePr1Vec &samples,
195-
const TDouble4Vec1Vec &weights) = 0;
204+
TDouble4Vec1Vec weights) = 0;
196205

197206
//! Debug the memory used by this object.
198207
void debugMemoryUsage(core::CMemoryUsage::TMemoryUsagePtr mem) const;
@@ -206,29 +215,28 @@ class MATHS_EXPORT CUnivariateChangeModel : private core::CNonCopyable
206215
//! Get a checksum for this object.
207216
virtual uint64_t checksum(uint64_t seed) const = 0;
208217

209-
protected:
210-
//! The sample count to initialize a change model.
211-
static const std::size_t COUNT_TO_INITIALIZE{5u};
212-
213218
protected:
214219
//! Restore the residual model reading state from \p traverser.
215220
bool restoreResidualModel(const SDistributionRestoreParams &params,
216221
core::CStateRestoreTraverser &traverser);
217222

218223
//! Get the log-likelihood.
219224
double logLikelihood() const;
220-
//! Update the data log-likelihood with \p logLikelihood.
221-
void addLogLikelihood(double logLikelihood);
222225

223226
//! Get the expected log-likelihood.
224227
double expectedLogLikelihood() const;
225-
//! Update the expected data log-likelihood with \p logLikelihood.
226-
void addExpectedLogLikelihood(double logLikelihood);
228+
229+
//! Update the log-likelihood with \p samples.
230+
void updateLogLikelihood(const TWeightStyleVec &weightStyles,
231+
const TDouble1Vec &samples,
232+
const TDouble4Vec1Vec &weights);
233+
234+
//! Update the expected log-likelihoods.
235+
void updateExpectedLogLikelihood(const TWeightStyleVec &weightStyles,
236+
const TDouble4Vec1Vec &weights);
227237

228238
//! Get the time series trend model.
229239
const CTimeSeriesDecompositionInterface &trendModel() const;
230-
//! Get the time series trend model.
231-
CTimeSeriesDecompositionInterface &trendModel();
232240

233241
//! Get the time series residual model.
234242
const CPrior &residualModel() const;
@@ -275,10 +283,10 @@ class MATHS_EXPORT CUnivariateNoChangeModel final : public CUnivariateChangeMode
275283
virtual TOptionalChangeDescription change() const;
276284

277285
//! Get the log likelihood of \p samples.
278-
virtual void addSamples(std::size_t count,
279-
const TWeightStyleVec &weightStyles,
286+
virtual void addSamples(const std::size_t count,
287+
TWeightStyleVec weightStyles,
280288
const TTimeDoublePr1Vec &samples,
281-
const TDouble4Vec1Vec &weights);
289+
TDouble4Vec1Vec weights);
282290

283291
//! Get the static size of this object.
284292
virtual std::size_t staticSize() const;
@@ -312,10 +320,10 @@ class MATHS_EXPORT CUnivariateLevelShiftModel final : public CUnivariateChangeMo
312320
virtual TOptionalChangeDescription change() const;
313321

314322
//! Update with \p samples.
315-
virtual void addSamples(std::size_t count,
316-
const TWeightStyleVec &weightStyles,
323+
virtual void addSamples(const std::size_t count,
324+
TWeightStyleVec weightStyles,
317325
const TTimeDoublePr1Vec &samples,
318-
const TDouble4Vec1Vec &weights);
326+
TDouble4Vec1Vec weights);
319327

320328
//! Get the static size of this object.
321329
virtual std::size_t staticSize() const;
@@ -324,7 +332,6 @@ class MATHS_EXPORT CUnivariateLevelShiftModel final : public CUnivariateChangeMo
324332
virtual uint64_t checksum(uint64_t seed) const;
325333

326334
private:
327-
using TDoubleVec = std::vector<double>;
328335
using TMeanAccumulator = CBasicStatistics::SSampleMean<double>::TAccumulator;
329336

330337
private:
@@ -338,6 +345,56 @@ class MATHS_EXPORT CUnivariateLevelShiftModel final : public CUnivariateChangeMo
338345
double m_SampleCount;
339346
};
340347

348+
//! \brief Captures the likelihood of the data given an arbitrary
349+
//! linear scaling.
350+
class MATHS_EXPORT CUnivariateLinearScaleModel final : public CUnivariateChangeModel
351+
{
352+
public:
353+
CUnivariateLinearScaleModel(const TDecompositionPtr &trendModel,
354+
const TPriorPtr &residualModel);
355+
356+
//! Initialize by reading state from \p traverser.
357+
virtual bool acceptRestoreTraverser(const SModelRestoreParams &params,
358+
core::CStateRestoreTraverser &traverser);
359+
360+
//! Persist state by passing information to \p inserter.
361+
virtual void acceptPersistInserter(core::CStatePersistInserter &inserter) const;
362+
363+
//! The BIC of applying the level shift.
364+
virtual double bic() const;
365+
366+
//! The expected BIC of applying the change.
367+
virtual double expectedBic() const;
368+
369+
//! Get a description of the level shift.
370+
virtual TOptionalChangeDescription change() const;
371+
372+
//! Update with \p samples.
373+
virtual void addSamples(const std::size_t count,
374+
TWeightStyleVec weightStyles,
375+
const TTimeDoublePr1Vec &samples,
376+
TDouble4Vec1Vec weights);
377+
378+
//! Get the static size of this object.
379+
virtual std::size_t staticSize() const;
380+
381+
//! Get a checksum for this object.
382+
virtual uint64_t checksum(uint64_t seed) const;
383+
384+
private:
385+
using TMeanAccumulator = CBasicStatistics::SSampleMean<double>::TAccumulator;
386+
387+
private:
388+
//! The optimal shift.
389+
TMeanAccumulator m_Scale;
390+
391+
//! The mode of the initial residual distribution model.
392+
double m_ResidualModelMode;
393+
394+
//! The number of samples added so far.
395+
double m_SampleCount;
396+
};
397+
341398
//! \brief Captures the likelihood of the data given a specified
342399
//! time shift.
343400
class MATHS_EXPORT CUnivariateTimeShiftModel final : public CUnivariateChangeModel
@@ -364,10 +421,10 @@ class MATHS_EXPORT CUnivariateTimeShiftModel final : public CUnivariateChangeMod
364421
virtual TOptionalChangeDescription change() const;
365422

366423
//! Update with \p samples.
367-
virtual void addSamples(std::size_t count,
368-
const TWeightStyleVec &weightStyles,
424+
virtual void addSamples(const std::size_t count,
425+
TWeightStyleVec weightStyles,
369426
const TTimeDoublePr1Vec &samples,
370-
const TDouble4Vec1Vec &weights);
427+
TDouble4Vec1Vec weights);
371428

372429
//! Get the static size of this object.
373430
virtual std::size_t staticSize() const;

include/maths/CTimeSeriesDecomposition.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,12 @@ class MATHS_EXPORT CTimeSeriesDecomposition : public CTimeSeriesDecompositionInt
120120
//! \param[in] value The value immediately before the change
121121
//! point.
122122
//! \param[in] change A description of the change to apply.
123-
virtual void applyChange(core_t::TTime time, double value,
123+
//! \return True if a new component was detected.
124+
virtual bool applyChange(core_t::TTime time, double value,
124125
const SChangeDescription &change);
125126

126127
//! Propagate the decomposition forwards to \p time.
127-
void propagateForwardsTo(core_t::TTime time);
128+
virtual void propagateForwardsTo(core_t::TTime time);
128129

129130
//! Get the mean value of the time series in the vicinity of \p time.
130131
virtual double meanValue(core_t::TTime time) const;
@@ -192,10 +193,14 @@ class MATHS_EXPORT CTimeSeriesDecomposition : public CTimeSeriesDecompositionInt
192193
//! Get the static size of this object.
193194
virtual std::size_t staticSize(void) const;
194195

196+
//! Get the time shift which is being applied.
197+
virtual core_t::TTime timeShift(void) const;
198+
195199
//! Get the seasonal components.
196200
virtual const maths_t::TSeasonalComponentVec &seasonalComponents(void) const;
197201

198-
//! This is the latest time of any point added to this object or the time skipped to.
202+
//! This is the latest time of any point added to this object or
203+
//! the time skipped to.
199204
virtual core_t::TTime lastValueTime(void) const;
200205

201206
private:

include/maths/CTimeSeriesDecompositionDetail.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,15 @@ class MATHS_EXPORT CTimeSeriesDecompositionDetail
389389
//! Create a new calendar component.
390390
virtual void handle(const SDetectedCalendar &message);
391391

392-
//! Apply \p change at \p time.
392+
//! Start using the trend for prediction.
393+
void useTrendForPrediction(void);
394+
395+
//! Apply \p shift to the level at \p time and \p value.
393396
void shiftLevel(core_t::TTime time, double value, double shift);
394397

398+
//! Apply a linear scale of \p scale.
399+
void linearScale(core_t::TTime time, double scale);
400+
395401
//! Maybe re-interpolate the components.
396402
void interpolate(const SMessage &message);
397403

@@ -549,6 +555,9 @@ class MATHS_EXPORT CTimeSeriesDecompositionDetail
549555
//! Shift the components' time origin to \p time.
550556
void shiftOrigin(core_t::TTime time);
551557

558+
//! Linearly scale the components' by \p scale.
559+
void linearScale(core_t::TTime time, double scale);
560+
552561
//! Get a checksum for this object.
553562
uint64_t checksum(uint64_t seed = 0) const;
554563

@@ -608,6 +617,9 @@ class MATHS_EXPORT CTimeSeriesDecompositionDetail
608617
//! Remove low value components.
609618
bool prune(core_t::TTime time, core_t::TTime bucketLength);
610619

620+
//! Linearly scale the components' by \p scale.
621+
void linearScale(core_t::TTime time, double scale);
622+
611623
//! Get a checksum for this object.
612624
uint64_t checksum(uint64_t seed = 0) const;
613625

include/maths/CTimeSeriesDecompositionInterface.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ class MATHS_EXPORT CTimeSeriesDecompositionInterface
9898
//! \param[in] value The value immediately before the change
9999
//! point.
100100
//! \param[in] change A description of the change to apply.
101-
virtual void applyChange(core_t::TTime time, double value,
101+
//! \return True if a new component was detected.
102+
virtual bool applyChange(core_t::TTime time, double value,
102103
const SChangeDescription &change) = 0;
103104

104105
//! Propagate the decomposition forwards to \p time.
@@ -171,10 +172,14 @@ class MATHS_EXPORT CTimeSeriesDecompositionInterface
171172
//! Get the static size of this object.
172173
virtual std::size_t staticSize(void) const = 0;
173174

175+
//! Get the time shift which is being applied.
176+
virtual core_t::TTime timeShift(void) const = 0;
177+
174178
//! Get the seasonal components.
175179
virtual const maths_t::TSeasonalComponentVec &seasonalComponents(void) const = 0;
176180

177-
//! This is the latest time of any point added to this object or the time skipped to.
181+
//! This is the latest time of any point added to this object or
182+
//! the time skipped to.
178183
virtual core_t::TTime lastValueTime(void) const = 0;
179184
};
180185

0 commit comments

Comments
 (0)