Skip to content

Commit 407e972

Browse files
authored
Merge pull request #239 from labmlai/smoothing
Smoothing
2 parents 31751da + 78c5882 commit 407e972

File tree

11 files changed

+123
-21
lines changed

11 files changed

+123
-21
lines changed

app/server/labml_app/analyses/experiments/comparison.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def defaults(cls):
2727
base_experiment="",
2828
step_range=[-1, -1],
2929
is_base_distributed=False,
30-
smooth_value=1,
30+
smooth_value=0.5,
31+
smooth_function=preferences.SmoothFunction.Exponential.value
3132
)
3233

3334
def update_preferences(self, data: preferences.PreferencesData) -> None:
@@ -52,6 +53,9 @@ def update_preferences(self, data: preferences.PreferencesData) -> None:
5253
if 'smooth_value' in data:
5354
self.smooth_value = data['smooth_value']
5455

56+
if 'smooth_function' in data:
57+
self.smooth_function = data['smooth_function']
58+
5559
r = run.get(self.base_experiment)
5660
if r is not None and r.world_size > 0: # distributed run
5761
self.is_base_distributed = True
@@ -72,7 +76,8 @@ def get_data(self) -> Dict[str, Any]:
7276
'step_range': self.step_range,
7377
'focus_smoothed': self.focus_smoothed,
7478
'is_base_distributed': self.is_base_distributed,
75-
'smooth_value': self.smooth_value
79+
'smooth_value': self.smooth_value,
80+
'smooth_function': self.smooth_function,
7681
}
7782

7883

app/server/labml_app/analyses/preferences.py

+12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
from enum import Enum
12
from typing import Dict, List, Any, Union
23

34
PreferencesData = Dict[str, Any]
45
SeriesPreferences = List[int]
56

67

8+
class SmoothFunction(Enum):
9+
Exponential = 'exponential'
10+
LeftExponential = 'left_exponential'
11+
12+
713
class Preferences:
814
series_preferences: Union[SeriesPreferences, List['SeriesPreferences']]
915
# series_preferences content:
@@ -15,6 +21,7 @@ class Preferences:
1521
step_range: List[int]
1622
focus_smoothed: bool
1723
smooth_value: float
24+
smooth_function: str
1825

1926
@classmethod
2027
def defaults(cls):
@@ -24,6 +31,7 @@ def defaults(cls):
2431
step_range=[-1, -1],
2532
focus_smoothed=True,
2633
smooth_value=0.5, # 50% smooth
34+
smooth_function=SmoothFunction.Exponential.value
2735
)
2836

2937
def update_preferences(self, data: PreferencesData) -> None:
@@ -42,6 +50,9 @@ def update_preferences(self, data: PreferencesData) -> None:
4250
if 'smooth_value' in data:
4351
self.smooth_value = data['smooth_value']
4452

53+
if 'smooth_function' in data:
54+
self.smooth_function = data['smooth_function']
55+
4556
self.save()
4657

4758
def update_series_preferences(self, data: SeriesPreferences) -> None:
@@ -54,4 +65,5 @@ def get_data(self) -> Dict[str, Any]:
5465
'step_range': self.step_range,
5566
'focus_smoothed': self.focus_smoothed,
5667
'smooth_value': self.smooth_value,
68+
'smooth_function': self.smooth_function,
5769
}

app/ui/src/analyses/experiments/chart_wrapper/card.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import {Indicator} from "../../../models/run"
33
import {
44
AnalysisPreferenceModel, ComparisonPreferenceModel,
55
} from "../../../models/preferences"
6-
import {getChartType} from "../../../components/charts/utils"
6+
import {getChartType, getSmoother} from "../../../components/charts/utils"
77
import {LineChart} from "../../../components/charts/lines/chart"
88
import {SparkLines} from "../../../components/charts/spark_lines/chart"
9-
import {TwoSidedExponentialAverage} from "../../../components/charts/smoothing/two_sided_exponential_average"
109

1110
interface CardWrapperOptions {
1211
width: number
@@ -37,6 +36,7 @@ export class CardWrapper {
3736
private stepRange: number[]
3837
private focusSmoothed: boolean
3938
private smoothValue: number
39+
private smoothFunction: string
4040

4141
private readonly title?: string
4242

@@ -74,14 +74,15 @@ export class CardWrapper {
7474
this.stepRange = preferenceData.step_range
7575
this.focusSmoothed = preferenceData.focus_smoothed
7676
this.smoothValue = preferenceData.smooth_value
77+
this.smoothFunction = preferenceData.smooth_function
7778

78-
let [smoothedSeries, smoothedBaseSeries] = (new TwoSidedExponentialAverage({
79+
let [smoothedSeries, smoothedBaseSeries] = getSmoother(this.smoothFunction, {
7980
indicators: this.series.concat(this.baseSeries ?? []) ?? [],
8081
smoothValue: this.smoothValue,
8182
min: this.stepRange[0],
8283
max: this.stepRange[1],
8384
currentIndicatorLength: this.series.length
84-
})).smoothAndTrim()
85+
}).smoothAndTrim()
8586

8687
this.series = smoothedSeries
8788
this.baseSeries = smoothedBaseSeries

app/ui/src/analyses/experiments/chart_wrapper/view.ts

+37-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import {DeleteButton, SaveButton, ToggleButton} from "../../../components/button
44
import {LineChart} from "../../../components/charts/lines/chart"
55
import {SparkLines} from "../../../components/charts/spark_lines/chart"
66
import {
7-
getChartType
7+
getChartType, getSmoother
88
} from "../../../components/charts/utils"
99
import {NumericRangeField} from "../../../components/input/numeric_range_field"
1010
import {Loader} from "../../../components/loader"
1111
import {Slider} from "../../../components/input/slider"
1212
import {UserMessages} from "../../../components/user_messages"
1313
import {NetworkError} from "../../../network"
14-
import {TwoSidedExponentialAverage} from "../../../components/charts/smoothing/two_sided_exponential_average"
14+
import {SmoothingType} from "../../../components/charts/smoothing/smoothing_base";
1515

1616
interface ViewWrapperOpt {
1717
dataStore: MetricDataStore
@@ -36,6 +36,7 @@ export interface MetricDataStore {
3636
focusSmoothed: boolean
3737
stepRange: number[]
3838
smoothValue: number
39+
smoothFunction: string
3940

4041
isUnsaved: boolean
4142
}
@@ -100,6 +101,19 @@ namespace ChangeHandlers {
100101
}
101102
}
102103

104+
export class SmoothFunctionHandler extends ChangeHandlerBase {
105+
private readonly value: string
106+
107+
constructor(wrapper: ViewWrapper, value: string) {
108+
super(wrapper)
109+
this.value = value
110+
}
111+
112+
protected handleChange() {
113+
this.wrapper.dataStore.smoothFunction = this.value
114+
}
115+
}
116+
103117
export class ToggleChangeHandler extends ChangeHandlerBase {
104118
private readonly idx: number
105119
private readonly isBase: boolean
@@ -153,6 +167,7 @@ export class ViewWrapper {
153167
private readonly focusButton: ToggleButton
154168
private readonly smoothSlider: Slider
155169
private readonly deleteButton: DeleteButton
170+
private readonly leftSmoothButton: ToggleButton
156171
private sparkLines: SparkLines
157172

158173
public dataStore: MetricDataStore
@@ -226,6 +241,22 @@ export class ViewWrapper {
226241
parent: this.constructor.name,
227242
onButtonClick: this.onDelete
228243
})
244+
this.leftSmoothButton = new ToggleButton({
245+
onButtonClick: () => {
246+
let value: string
247+
if (this.dataStore.smoothFunction === SmoothingType.LEFT_EXPONENTIAL) {
248+
value = SmoothingType.EXPONENTIAL
249+
} else {
250+
value = SmoothingType.LEFT_EXPONENTIAL
251+
}
252+
let changeHandler = new ChangeHandlers.SmoothFunctionHandler(this,
253+
value)
254+
changeHandler.change()
255+
},
256+
text: 'Left',
257+
isToggled: this.dataStore.smoothFunction === SmoothingType.LEFT_EXPONENTIAL,
258+
parent: this.constructor.name
259+
})
229260
}
230261

231262
public clear() {
@@ -316,13 +347,13 @@ export class ViewWrapper {
316347
}
317348

318349
private smoothSeries() {
319-
let [series, baseSeries] = (new TwoSidedExponentialAverage({
350+
let [series, baseSeries] = getSmoother(this.dataStore.smoothFunction, {
320351
indicators: this.dataStore.series.concat(this.dataStore.baseSeries ?? []) ?? [],
321352
smoothValue: this.dataStore.smoothValue,
322353
min: this.dataStore.stepRange[0],
323354
max: this.dataStore.stepRange[1],
324355
currentIndicatorLength: this.dataStore.series.length
325-
})).smoothAndTrim()
356+
}).smoothAndTrim()
326357

327358
this.dataStore.series = series
328359
this.dataStore.baseSeries = baseSeries
@@ -350,6 +381,7 @@ export class ViewWrapper {
350381
this.scaleButton.isToggled = this.dataStore.chartType > 0
351382
this.focusButton.isToggled = this.dataStore.focusSmoothed
352383
this.smoothSlider.value = this.dataStore.smoothValue
384+
this.leftSmoothButton.isToggled = this.dataStore.smoothFunction === SmoothingType.LEFT_EXPONENTIAL
353385

354386
this.optionRowContainer.innerHTML = ''
355387
this.optionRowContainer.classList.add('chart-options')
@@ -366,6 +398,7 @@ export class ViewWrapper {
366398
$('div', '.button-row', $ => {
367399
$('span.key', 'Smoothing:')
368400
this.smoothSlider.render($)
401+
this.leftSmoothButton.render($)
369402
})
370403
})
371404
}

app/ui/src/analyses/experiments/comparison/view.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {MetricDataStore, ViewWrapper} from "../chart_wrapper/view"
2121
import comparisonCache from "./cache"
2222
import {NetworkError} from "../../../network"
2323
import {RunsPickerView} from "../../../views/run_picker_view"
24+
import {SmoothingType} from "../../../components/charts/smoothing/smoothing_base"
2425

2526
class ComparisonView extends ScreenView implements MetricDataStore {
2627
private readonly uuid: string
@@ -37,6 +38,7 @@ class ComparisonView extends ScreenView implements MetricDataStore {
3738
focusSmoothed: boolean
3839
stepRange: number[]
3940
smoothValue: number
41+
smoothFunction: string
4042

4143
isUnsaved: boolean
4244

@@ -98,6 +100,7 @@ class ComparisonView extends ScreenView implements MetricDataStore {
98100
this.basePlotIdx = [...this.preferenceData.base_series_preferences]
99101
}
100102
this.smoothValue = this.preferenceData.smooth_value
103+
this.smoothFunction = this.preferenceData.smooth_function
101104

102105
if (!!this.baseUuid) {
103106
await this.updateBaseRun(force)
@@ -276,6 +279,7 @@ class ComparisonView extends ScreenView implements MetricDataStore {
276279
series_names: this.series.map(s => s.name),
277280
base_series_names: this.baseSeries ? this.baseSeries.map(s => s.name) : [],
278281
smooth_value: this.smoothValue,
282+
smooth_function: this.smoothFunction
279283
}
280284

281285
await this.preferenceCache.setPreference(preferenceData)
@@ -346,8 +350,9 @@ class ComparisonView extends ScreenView implements MetricDataStore {
346350
this.plotIdx = []
347351
this.stepRange = [-1, -1]
348352
this.focusSmoothed = true
349-
this.smoothValue = 50
353+
this.smoothValue = 0.5
350354
this.chartType = 0
355+
this.smoothFunction = SmoothingType.EXPONENTIAL
351356

352357
this.setBaseLoading(true)
353358
this.updateBaseRun(true).then(async () => {

app/ui/src/analyses/experiments/metrics/view.ts

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class MetricsView extends ScreenView implements MetricDataStore {
5656
stepRange: number[]
5757
smoothValue: number
5858
isUnsaved: boolean
59+
smoothFunction: string
5960

6061
constructor(uuid: string, metricUuid?: string) {
6162
super()
@@ -91,6 +92,7 @@ class MetricsView extends ScreenView implements MetricDataStore {
9192
this.focusSmoothed = this.preferenceData.focus_smoothed
9293
this.plotIdx = [...fillPlotPreferences(this.series, this.preferenceData.series_preferences)]
9394
this.smoothValue = this.preferenceData.smooth_value
95+
this.smoothFunction = this.preferenceData.smooth_function
9496
})
9597

9698
this.refresh = new AwesomeRefreshButton(this.onRefresh.bind(this))
@@ -276,6 +278,7 @@ class MetricsView extends ScreenView implements MetricDataStore {
276278
sub_series_preferences: undefined,
277279
series_names: this.series.map(s => s.name),
278280
smooth_value: this.smoothValue,
281+
smooth_function: this.smoothFunction
279282
}
280283

281284
if (this.metricUuid == null) {

app/ui/src/components/charts/smoothing/exponential_moving_average.ts

+31-9
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
1-
import {SeriesSmoothing} from "./smoothing_base";
2-
import {PointValue} from "../../../models/run";
1+
import {SeriesSmoothing} from "./smoothing_base"
2+
import {PointValue} from "../../../models/run"
33

44
export class ExponentialMovingAverage extends SeriesSmoothing {
55
protected smooth(): void {
6-
let smoothingFactor = 1 - this.smoothValue / 100
6+
let smoothingFactor = this.smoothValue
7+
8+
if (smoothingFactor <= 0) {
9+
smoothingFactor = 0
10+
}
11+
if (smoothingFactor >= 1) {
12+
smoothingFactor = 1
13+
}
714

815
for (let i = 0; i < this.indicators.length; i++) {
916
let ind = this.indicators[i]
1017

11-
if (ind.series.length == 0) {
18+
if (ind.series.length < 2) {
1219
continue
1320
}
1421

1522
let result: PointValue[] = []
1623

17-
let lastSmoothed = ind.series[0].value
24+
let f_sum: number[] = []
25+
let f_weights: number[] = []
26+
27+
let last_sum = 0
28+
let last_weight = 0
29+
let last_step = ind.series[0].step
30+
31+
for (let j = 0; j < ind.series.length; j++) {
32+
let smooth_gap = Math.pow(smoothingFactor, Math.abs(ind.series[j].step - last_step))
33+
f_sum.push(last_sum * smooth_gap + ind.series[j].value)
34+
f_weights.push(last_weight * smooth_gap + 1)
35+
36+
last_sum = f_sum[j]
37+
last_weight = f_weights[j]
38+
last_step = ind.series[j].step
39+
}
40+
1841
for (let j = 0; j < ind.series.length; j++) {
19-
let smoothed = lastSmoothed * (1 - smoothingFactor) + ind.series[j].value * smoothingFactor
20-
result.push({step: ind.series[j].step, value: ind.series[j].value, smoothed: smoothed,
21-
lastStep: ind.series[j].lastStep})
22-
lastSmoothed = smoothed
42+
let smoothed = f_sum[j] / f_weights[j]
43+
result.push({step: ind.series[j].step, value: ind.series[j].value,
44+
smoothed: smoothed, lastStep: ind.series[j].lastStep})
2345
}
2446

2547
ind.series = result

app/ui/src/components/charts/smoothing/smoothing_base.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Indicator} from "../../../models/run";
1+
import {Indicator} from "../../../models/run"
22

33
export interface SeriesSmoothingOptions {
44
indicators: Indicator[]
@@ -57,4 +57,9 @@ export abstract class SeriesSmoothing {
5757
ind.highTrimIndex = maxIndex
5858
})
5959
}
60+
}
61+
62+
export enum SmoothingType {
63+
EXPONENTIAL = 'exponential',
64+
LEFT_EXPONENTIAL = 'left_exponential'
6065
}

app/ui/src/components/charts/utils.ts

+14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import d3 from "../../d3"
22

33
import {Indicator, IndicatorModel, PointValue} from "../../models/run"
44
import {OUTLIER_MARGIN} from "./constants"
5+
import {SeriesSmoothing, SeriesSmoothingOptions, SmoothingType} from "./smoothing/smoothing_base"
6+
import {TwoSidedExponentialAverage} from "./smoothing/two_sided_exponential_average"
7+
import {ExponentialMovingAverage} from "./smoothing/exponential_moving_average"
58

69
export function getExtentWithoutOutliers(series: PointValue[], func: (d: PointValue) => number): [number, number] {
710
let values = series.map(func)
@@ -243,4 +246,15 @@ function getListFromBinary(binaryString: string): Float32Array {
243246
console.error("Error parsing binary data", e)
244247
return new Float32Array(0);
245248
}
249+
}
250+
251+
export function getSmoother(smoothingType: string, opt: SeriesSmoothingOptions): SeriesSmoothing {
252+
switch (smoothingType) {
253+
case SmoothingType.EXPONENTIAL:
254+
return new TwoSidedExponentialAverage(opt)
255+
case SmoothingType.LEFT_EXPONENTIAL:
256+
return new ExponentialMovingAverage(opt)
257+
default:
258+
throw new Error(`Unknown smoothing type: ${smoothingType}`)
259+
}
246260
}

app/ui/src/models/preferences.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface AnalysisPreferenceBaseModel {
66
step_range: number[]
77
focus_smoothed: boolean
88
smooth_value: number
9+
smooth_function: string
910
}
1011

1112
export interface AnalysisPreferenceModel extends AnalysisPreferenceBaseModel {

0 commit comments

Comments
 (0)