Skip to content

Commit 1a99525

Browse files
author
local
committed
Fix the two piecewise-linear regression calculation
#66 The current implementation of the regression calculation has these flaws: When processing (x[0], y[0]), L1 must be any line through (x[0], y[0]) which meets L2 at a point (x’, y’) where x[0] < x' < x[1]. L1 has no error. When processing (x[n - 2], y[n - 2]), L2 must be any line through (x[n - 1], y[n - 1]) which meets L1 at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. L2 has no error. The lambda calculation is incorrect. It includes a term called H which is equal to C - I. Looking at the algorithm of Kundu/Ubhaya, this should be just C. lambda should to be used with calculating L1 and (1 - lambda) should to be used with calculating L2. Currently (1 - lambda) is used in calculating L1 and L2. The current calculation has this condition if (t1 != t2) continue; This condition is almost always true even if t1 and t2 are essentiallyEqual.
1 parent f1c7edb commit 1a99525

File tree

1 file changed

+158
-140
lines changed

1 file changed

+158
-140
lines changed

MotionMark/resources/statistics.js

Lines changed: 158 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -181,39 +181,48 @@ Regression = Utilities.createClass(
181181
// All samples are analyzed. startIndex, endIndex are just stored for use by the caller.
182182
function(samples, startIndex, endIndex, options)
183183
{
184-
const desiredFrameLength = options.desiredFrameLength;
185-
var profile;
184+
this.segment1 = {
185+
s: 0,
186+
t: 0,
187+
n: 0,
188+
e: Number.MAX_VALUE
189+
};
186190

187-
if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.slope) {
188-
profile = this._calculateRegression(samples, {
189-
shouldClip: true,
190-
s1: desiredFrameLength,
191-
t1: 0
192-
});
193-
this.profile = Strings.json.profiles.slope;
194-
} else if (options.preferredProfile == Strings.json.profiles.flat) {
195-
profile = this._calculateRegression(samples, {
196-
shouldClip: true,
197-
s1: desiredFrameLength,
191+
this.segment2 = {
192+
s: 0,
193+
t: 0,
194+
n: 0,
195+
e: Number.MAX_VALUE
196+
};
197+
198+
if (options.preferredProfile == Strings.json.profiles.flat) {
199+
this._calculateRegression(samples, {
200+
s1: options.desiredFrameLength,
198201
t1: 0,
199202
t2: 0
200203
});
201204
this.profile = Strings.json.profiles.flat;
205+
} else {
206+
this._calculateRegression(samples, {
207+
s1: options.desiredFrameLength,
208+
t1: 0
209+
});
210+
this.profile = Strings.json.profiles.slope;
202211
}
203212

204213
this.startIndex = Math.min(startIndex, endIndex);
205214
this.endIndex = Math.max(startIndex, endIndex);
206215

207-
this.complexity = profile.complexity;
208-
this.s1 = profile.s1;
209-
this.t1 = profile.t1;
210-
this.s2 = profile.s2;
211-
this.t2 = profile.t2;
212-
this.stdev1 = profile.stdev1;
213-
this.stdev2 = profile.stdev2;
214-
this.n1 = profile.n1;
215-
this.n2 = profile.n2;
216-
this.error = profile.error;
216+
this.complexity = this._complexity();
217+
this.s1 = this.segment1.s;
218+
this.t1 = this.segment1.t;
219+
this.s2 = this.segment2.s;
220+
this.t2 = this.segment2.t;
221+
this.stdev1 = Math.sqrt(this.segment1.e / this.segment1.n);
222+
this.stdev2 = Math.sqrt(this.segment2.n / this.segment2.n);
223+
this.n1 = this.segment1.n;
224+
this.n2 = this.segment2.n;
225+
this.error = this._error();
217226
}, {
218227

219228
valueAt: function(complexity)
@@ -223,6 +232,32 @@ Regression = Utilities.createClass(
223232
return this.s1 + this.t1 * complexity;
224233
},
225234

235+
_complexity: function()
236+
{
237+
// The score is the x coordinate of the intersection of segment1 and segment2.
238+
return (this.segment1.s - this.segment2.s) / (this.segment2.t - this.segment1.t);
239+
},
240+
241+
_error: function() {
242+
return this.segment1.e + this.segment2.e;
243+
},
244+
245+
_setOptimal: function(segment1, segment2, options) {
246+
if (segment1.e + segment2.e > this.segment1.e + this.segment2.e)
247+
return false;
248+
249+
this.segment1.s = options.s1 !== undefined ? options.s1 : segment1.s;
250+
this.segment1.t = options.t1 !== undefined ? options.t1 : segment1.t;
251+
this.segment1.e = segment1.e;
252+
this.segment1.n = segment1.n;
253+
254+
this.segment2.s = options.s2 !== undefined ? options.s2 : segment2.s;
255+
this.segment2.t = options.t2 !== undefined ? options.t2 : segment2.t;
256+
this.segment2.e = segment2.e;
257+
this.segment2.n = segment2.n;
258+
return true;
259+
},
260+
226261
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
227262
//
228263
// Minimize sum of (y - y')^2
@@ -239,153 +274,136 @@ Regression = Utilities.createClass(
239274
const complexityIndex = 0;
240275
const frameLengthIndex = 1;
241276

242-
if (samples.length == 1) {
243-
// Only one sample point; we can't calculate any regression.
244-
var x = samples[0][complexityIndex];
245-
return {
246-
complexity: x,
247-
s1: x,
248-
t1: 0,
249-
s2: x,
250-
t2: 0,
251-
error1: 0,
252-
error2: 0
253-
};
254-
}
255-
256277
// Sort by increasing complexity.
257278
var sortedSamples = samples.slice().sort((a, b) => a[complexityIndex] - b[complexityIndex]);
258-
259-
// x is expected to increase in complexity
260-
var lowComplexity = sortedSamples[0][complexityIndex];
261-
var highComplexity = sortedSamples[samples.length - 1][complexityIndex];
262279

263-
var a1 = 0, b1 = 0, c1 = 0, d1 = 0, h1 = 0, k1 = 0;
264-
var a2 = 0, b2 = 0, c2 = 0, d2 = 0, h2 = 0, k2 = 0;
280+
let a1 = 0, b1 = 0, c1 = 0, d1 = 0, h1 = 0, k1 = 0;
281+
let a2 = 0, b2 = 0, c2 = 0, d2 = 0, h2 = 0, k2 = 0;
282+
283+
for (var j = 0; j < sortedSamples.length; ++j) {
284+
let x = sortedSamples[j][complexityIndex];
285+
let y = sortedSamples[j][frameLengthIndex];
265286

266-
for (var i = 0; i < sortedSamples.length; ++i) {
267-
var x = sortedSamples[i][complexityIndex];
268-
var y = sortedSamples[i][frameLengthIndex];
269287
a2 += 1;
270288
b2 += x;
271289
c2 += x * x;
272290
d2 += y;
273-
h2 += y * x;
291+
h2 += x * y;
274292
k2 += y * y;
275293
}
276294

277-
var s1_best, t1_best, s2_best, t2_best, n1_best, n2_best, error1_best, error2_best, x_best, x_prime;
278-
279-
function setBest(s1, t1, error1, s2, t2, error2, splitIndex, x_prime, x)
280-
{
281-
s1_best = s1;
282-
t1_best = t1;
283-
error1_best = error1;
284-
s2_best = s2;
285-
t2_best = t2;
286-
error2_best = error2;
287-
// Number of samples included in the first segment, inclusive of splitIndex
288-
n1_best = splitIndex + 1;
289-
// Number of samples included in the second segment
290-
n2_best = samples.length - splitIndex - 1;
291-
if (!options.shouldClip || (x_prime >= lowComplexity && x_prime <= highComplexity))
292-
x_best = x_prime;
293-
else {
294-
// Discontinuous piecewise regression
295-
x_best = x;
296-
}
297-
}
295+
for (let j = 0; j < sortedSamples.length - 1; ++j) {
296+
let x = sortedSamples[j][complexityIndex];
297+
let y = sortedSamples[j][frameLengthIndex];
298+
let xx = x * x;
299+
let xy = x * y;
300+
let yy = y * y;
298301

299-
// Iterate from 0 to n - 2, inclusive
300-
for (var i = 0; i < sortedSamples.length - 1; ++i) {
301-
var x = sortedSamples[i][complexityIndex];
302-
var y = sortedSamples[i][frameLengthIndex];
303-
var xx = x * x;
304-
var yx = y * x;
305-
var yy = y * y;
306-
// a1, b1, etc. is sum from 0 to i, inclusive
307302
a1 += 1;
308303
b1 += x;
309304
c1 += xx;
310305
d1 += y;
311-
h1 += yx;
306+
h1 += xy;
312307
k1 += yy;
313-
// a2, b2, etc. is sum from i+1 to sortedSamples.length - 1, inclusive
308+
314309
a2 -= 1;
315310
b2 -= x;
316311
c2 -= xx;
317312
d2 -= y;
318-
h2 -= yx;
313+
h2 -= xy;
319314
k2 -= yy;
320315

321-
var A = c1*d1 - b1*h1;
322-
var B = a1*h1 - b1*d1;
323-
var C = a1*c1 - b1*b1;
324-
var D = c2*d2 - b2*h2;
325-
var E = a2*h2 - b2*d2;
326-
var F = a2*c2 - b2*b2;
327-
var s1 = options.s1 !== undefined ? options.s1 : (A / C);
328-
var t1 = options.t1 !== undefined ? options.t1 : (B / C);
329-
var s2 = options.s2 !== undefined ? options.s2 : (D / F);
330-
var t2 = options.t2 !== undefined ? options.t2 : (E / F);
331-
// Assumes that the two segments meet
332-
var x_prime = (s1 - s2) / (t2 - t1);
333-
334-
var error1 = (k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) || Number.MAX_VALUE;
335-
var error2 = (k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) || Number.MAX_VALUE;
336-
337-
if (i == 0) {
338-
setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x);
339-
continue;
340-
}
316+
let A = (c1 * d1) - (b1 * h1);
317+
let B = (a1 * h1) - (b1 * d1);
318+
let C = (a1 * c1) - (b1 * b1);
319+
let D = (c2 * d2) - (b2 * h2);
320+
let E = (a2 * h2) - (b2 * d2);
321+
let F = (a2 * c2) - (b2 * b2);
322+
323+
let s1 = A / C;
324+
let t1 = B / C;
325+
let s2 = D / F;
326+
let t2 = E / F;
341327

342328
if (C == 0 || F == 0)
343329
continue;
344330

345-
// Projected point is not between this and the next sample
346-
var nextSampleComplexity = sortedSamples[i + 1][complexityIndex];
347-
if (x_prime > nextSampleComplexity || x_prime < x) {
348-
// Calculate lambda, which divides the weight of this sample between the two lines
349-
350-
// These values remove the influence of this sample
351-
var I = c1 - 2*b1*x + a1*xx;
352-
var H = C - I;
353-
var G = A + B*x - C*y;
354-
355-
var J = D + E*x - F*y;
356-
var K = c2 - 2*b2*x + a2*xx;
357-
358-
var lambda = (G*F + G*K - H*J) / (I*J + G*K);
359-
if (lambda > 0 && lambda < 1) {
360-
var lambda1 = 1 - lambda;
361-
s1 = options.s1 !== undefined ? options.s1 : ((A - lambda1*(-h1*x + d1*xx + c1*y - b1*yx)) / (C - lambda1*I));
362-
t1 = options.t1 !== undefined ? options.t1 : ((B - lambda1*(h1 - d1*x - b1*y + a1*yx)) / (C - lambda1*I));
363-
s2 = options.s2 !== undefined ? options.s2 : ((D + lambda1*(-h2*x + d2*xx + c2*y - b2*yx)) / (F + lambda1*K));
364-
t2 = options.t2 !== undefined ? options.t2 : ((E + lambda1*(h2 - d2*x - b2*y + a2*yx)) / (F + lambda1*K));
365-
x_prime = (s1 - s2) / (t2 - t1);
366-
367-
error1 = ((k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) - lambda1 * Math.pow(y - (s1 + t1*x), 2)) || Number.MAX_VALUE;
368-
error2 = ((k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) + lambda1 * Math.pow(y - (s2 + t2*x), 2)) || Number.MAX_VALUE;
369-
} else if (t1 != t2)
370-
continue;
331+
let segment1;
332+
let segment2;
333+
334+
if (j == 0) {
335+
// Let segment1 be any line through (x0, y0) which meets segment2 at
336+
// a point (x’, y’) where x[0] < x' < x[1]. segment1 has no error.
337+
let xMid = (x + sortedSamples[j + 1][complexityIndex]) / 2;
338+
let yMid = s2 + t2 * xMid;
339+
let tMid = (yMid - y) / (xMid - x);
340+
segment1 = {
341+
s: y - tMid * x,
342+
t: tMid,
343+
n: 1,
344+
e: 0
345+
};
346+
} else {
347+
segment1 = {
348+
s: s1,
349+
t: t1,
350+
n: j + 1,
351+
e: k1 + (a1 * s1 * s1) + (c1 * t1 * t1) - (2 * d1 * s1) - (2 * h1 * t1) + (2 * b1 * s1 * t1)
352+
};
371353
}
372354

373-
if (error1 + error2 < error1_best + error2_best)
374-
setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x);
375-
}
355+
if (j == sortedSamples.length - 2) {
356+
// Let segment2 be any line through (x[n - 1], y[n - 1]) which meets segment1
357+
// at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. segment2 has no error.
358+
let xMid = (x + sortedSamples[j + 1][complexityIndex]) / 2;
359+
let yMid = s1 + t1 * xMid;
360+
let tMid = (yMid - sortedSamples[j + 1][frameLengthIndex]) / (xMid - sortedSamples[j + 1][complexityIndex]);
361+
segment2 = {
362+
s: y - tMid * x,
363+
t: tMid,
364+
n: 1,
365+
e: 0
366+
};
367+
} else {
368+
segment2 = {
369+
s: s2,
370+
t: t2,
371+
n: sortedSamples.length - (j + 1),
372+
e: k2 + (a2 * s2 * s2) + (c2 * t2 * t2) - (2 * d2 * s2) - (2 * h2 * t2) + (2 * b2 * s2 * t2)
373+
};
374+
}
376375

377-
return {
378-
complexity: x_best,
379-
s1: s1_best,
380-
t1: t1_best,
381-
stdev1: Math.sqrt(error1_best / n1_best),
382-
s2: s2_best,
383-
t2: t2_best,
384-
stdev2: Math.sqrt(error2_best / n2_best),
385-
error: error1_best + error2_best,
386-
n1: n1_best,
387-
n2: n2_best
388-
};
376+
if (this._setOptimal(segment1, segment2, options))
377+
continue
378+
379+
let G = A + B * x - C * y;
380+
let H = D + E * x - F * y;
381+
382+
let I = c1 - 2 * b1 * x + a1 * xx;
383+
let K = c2 - 2 * b2 * x + a2 * xx;
384+
385+
let lambda = (G * F + G * K - H * C) / (I * H + G * K);
386+
if (!(lambda > 0 && lambda < 1))
387+
continue;
388+
389+
let lambda1 = 1 - lambda;
390+
391+
segment1 = {
392+
s: (A + lambda * (-h1 * x + d1 * xx + c1 * y - b1 * xy)) / (C - lambda * I),
393+
t: (B + lambda * (h1 - d1 * x - b1 * y + a1 * xy)) / (C - lambda * I),
394+
n: j + 1,
395+
e: (k1 + a1 * s1 * s1 + c1 * t1 * t1 - 2 * d1 * s1 - 2 * h1 * t1 + 2 * b1 * s1 * t1) - lambda * Math.pow(y - (s1 + t1 * x), 2)
396+
};
397+
398+
segment2 = {
399+
s: (D + lambda1 * (-h2 * x + d2 * xx + c2 * y - b2 * xy)) / (F + lambda1 * K),
400+
t: (E + lambda1 * (h2 - d2 * x - b2 * y + a2 * xy)) / (F + lambda1 * K),
401+
n: sortedSamples.length - (j + 1),
402+
e: (k2 + a2 * s2 * s2 + c2 * t2 * t2 - 2 * d2 * s2 - 2 * h2 * t2 + 2 * b2 * s2 * t2) + lambda1 * Math.pow(y - (s2 + t2 * x), 2)
403+
};
404+
405+
this._setOptimal(segment1, segment2, options);
406+
}
389407
}
390408
});
391409

0 commit comments

Comments
 (0)