Skip to content

Commit cf56c2c

Browse files
authored
#1019 feat: Display warnings when changed training subsets or not valid subsets sizes (#1038)
1 parent c67be49 commit cf56c2c

File tree

9 files changed

+247
-48
lines changed

9 files changed

+247
-48
lines changed

web_ui/src/pages/project-details/components/project-models/train-model-dialog/advanced-settings/advanced-settings.component.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const ContentWrapper: FC<{ children: ReactNode }> = ({ children }) => {
2626
};
2727

2828
interface AdvancedSettingsProps {
29+
hasSupportedModels: boolean;
2930
algorithms: SupportedAlgorithm[];
3031
selectedModelTemplateId: string | null;
3132
onChangeSelectedTemplateId: (modelTemplateId: string | null) => void;
@@ -46,6 +47,7 @@ interface TabProps {
4647
}
4748

4849
export const AdvancedSettings: FC<AdvancedSettingsProps> = ({
50+
hasSupportedModels,
4951
algorithms,
5052
selectedModelTemplateId,
5153
onChangeSelectedTemplateId,
@@ -73,6 +75,7 @@ export const AdvancedSettings: FC<AdvancedSettingsProps> = ({
7375
name: 'Data management',
7476
children: (
7577
<DataManagement
78+
hasSupportedModels={hasSupportedModels}
7679
trainingConfiguration={trainingConfiguration}
7780
onUpdateTrainingConfiguration={onUpdateTrainingConfiguration}
7881
/>

web_ui/src/pages/project-details/components/project-models/train-model-dialog/advanced-settings/data-management/data-management.component.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Tiling } from './tiling/tiling.component';
1313
import { TrainingSubsets } from './training-subsets/training-subsets.component';
1414

1515
interface DataManagementProps {
16+
hasSupportedModels: boolean;
1617
trainingConfiguration: TrainingConfiguration;
1718
onUpdateTrainingConfiguration: (
1819
updateFunction: (config: TrainingConfiguration | undefined) => TrainingConfiguration | undefined
@@ -27,7 +28,11 @@ const getAugmentationParameters = (configuration: TrainingConfiguration) => {
2728
return augmentation;
2829
};
2930

30-
export const DataManagement: FC<DataManagementProps> = ({ trainingConfiguration, onUpdateTrainingConfiguration }) => {
31+
export const DataManagement: FC<DataManagementProps> = ({
32+
hasSupportedModels,
33+
trainingConfiguration,
34+
onUpdateTrainingConfiguration,
35+
}) => {
3136
const augmentationParameters = getAugmentationParameters(trainingConfiguration);
3237
const subsetSplitParameters = trainingConfiguration.datasetPreparation.subsetSplit;
3338
const filteringParameters = trainingConfiguration.datasetPreparation.filtering;
@@ -38,6 +43,7 @@ export const DataManagement: FC<DataManagementProps> = ({ trainingConfiguration,
3843
{/* Not supported in v1 of training flow revamp <BalanceLabelsDistribution /> */}
3944
{!isEmpty(subsetSplitParameters) && (
4045
<TrainingSubsets
46+
hasSupportedModels={hasSupportedModels}
4147
subsetsParameters={trainingConfiguration.datasetPreparation.subsetSplit}
4248
onUpdateTrainingConfiguration={onUpdateTrainingConfiguration}
4349
/>

web_ui/src/pages/project-details/components/project-models/train-model-dialog/advanced-settings/data-management/training-subsets/training-subsets.component.tsx

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Copyright (C) 2022-2025 Intel Corporation
22
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE
33

4-
import { FC, useState } from 'react';
4+
import { FC, useRef, useState } from 'react';
55

6-
import { Flex, Grid, minmax, Text, View } from '@geti/ui';
6+
import { Content, Flex, Grid, Heading, InlineAlert, minmax, Text, View } from '@geti/ui';
7+
import { isEqual } from 'lodash-es';
78

89
import {
910
ConfigurationParameter,
@@ -13,7 +14,7 @@ import {
1314
import { Accordion } from '../../ui/accordion/accordion.component';
1415
import { ResetButton } from '../../ui/reset-button.component';
1516
import { SubsetsDistributionSlider } from './subsets-distribution-slider/subsets-distribution-slider.component';
16-
import { getSubsetsSizes } from './utils';
17+
import { areSubsetsSizesValid, getSubsetsSizes, MAX_RATIO_VALUE } from './utils';
1718

1819
import styles from './training-subsets.module.scss';
1920

@@ -85,17 +86,7 @@ const SubsetsDistribution: FC<SubsetsDistributionProps> = ({
8586
}) => {
8687
const handleSubsetDistributionChange = (values: number[] | number): void => {
8788
if (Array.isArray(values)) {
88-
const [startRange, endRange] = values;
89-
90-
const newSubsetSizes = getSubsetsSizes(subsetParameters, endRange - startRange, MAX_RATIO_VALUE - endRange);
91-
92-
if (
93-
[
94-
newSubsetSizes.trainingSubsetSize,
95-
newSubsetSizes.validationSubsetSize,
96-
newSubsetSizes.testSubsetSize,
97-
].some((size) => size === 0)
98-
) {
89+
if (!areSubsetsSizesValid(subsetParameters, values)) {
9990
return;
10091
}
10192

@@ -105,17 +96,7 @@ const SubsetsDistribution: FC<SubsetsDistributionProps> = ({
10596

10697
const handleSubsetDistributionChangeEnd = (values: number[] | number): void => {
10798
if (Array.isArray(values)) {
108-
const [startRange, endRange] = values;
109-
110-
const newSubsetSizes = getSubsetsSizes(subsetParameters, endRange - startRange, MAX_RATIO_VALUE - endRange);
111-
112-
if (
113-
[
114-
newSubsetSizes.trainingSubsetSize,
115-
newSubsetSizes.validationSubsetSize,
116-
newSubsetSizes.testSubsetSize,
117-
].some((size) => size === 0)
118-
) {
99+
if (!areSubsetsSizesValid(subsetParameters, values)) {
119100
return;
120101
}
121102

@@ -156,11 +137,10 @@ const SubsetsDistribution: FC<SubsetsDistributionProps> = ({
156137
);
157138
};
158139

159-
const MAX_RATIO_VALUE = 100;
160-
161140
type SubsetsParameters = TrainingConfiguration['datasetPreparation']['subsetSplit'];
162141

163142
interface TrainingSubsetsProps {
143+
hasSupportedModels: boolean;
164144
subsetsParameters: SubsetsParameters;
165145
onUpdateTrainingConfiguration: (
166146
updateFunction: (config: TrainingConfiguration | undefined) => TrainingConfiguration | undefined
@@ -185,9 +165,44 @@ const getSubsets = (subsetsParameters: SubsetsParameters) => {
185165
};
186166
};
187167

188-
export const TrainingSubsets: FC<TrainingSubsetsProps> = ({ subsetsParameters, onUpdateTrainingConfiguration }) => {
168+
const TrainingSubsetsUnavailable = () => {
169+
return (
170+
<InlineAlert variant={'notice'}>
171+
<Heading>Training subsets configuration unavailable</Heading>
172+
<Content>
173+
The training, validation, and testing subsets are currently unavailable because the project does not
174+
contain enough media items to support a proper split.
175+
<br />
176+
To enable subset configuration, please add more media items so that each subset contains at least one
177+
item.
178+
</Content>
179+
</InlineAlert>
180+
);
181+
};
182+
183+
const TrainingSubsetsChangedDistributionWarning = () => {
184+
return (
185+
<InlineAlert variant={'notice'}>
186+
<Heading>Additional configuration change required to apply new training subsets distribution</Heading>
187+
<Content>
188+
To apply the updated distribution of training, validation, and testing subsets, please go to{' '}
189+
{'"Training"'} tab, choose {'"Pre-trained weights"'}, and enable {'"Reshuffle subsets"'}.
190+
<br />
191+
This will reset your data splits and begin a new training process, replacing the current model.
192+
</Content>
193+
</InlineAlert>
194+
);
195+
};
196+
197+
export const TrainingSubsets: FC<TrainingSubsetsProps> = ({
198+
hasSupportedModels,
199+
subsetsParameters,
200+
onUpdateTrainingConfiguration,
201+
}) => {
189202
const { trainingSubset, validationSubset } = getSubsets(subsetsParameters);
190203

204+
const prevSubsetParameters = useRef(subsetsParameters);
205+
191206
const [subsetsDistribution, setSubsetsDistribution] = useState<number[]>([
192207
trainingSubset.value,
193208
trainingSubset.value + validationSubset.value,
@@ -259,6 +274,10 @@ export const TrainingSubsets: FC<TrainingSubsetsProps> = ({ subsetsParameters, o
259274
testSubsetRatio
260275
);
261276

277+
const subsetsSizesValid = areSubsetsSizesValid(subsetsParameters, subsetsDistribution);
278+
const isChangedDistributionWarningVisible =
279+
hasSupportedModels && !isEqual(prevSubsetParameters.current, subsetsParameters);
280+
262281
return (
263282
<Accordion>
264283
<Accordion.Title>
@@ -269,21 +288,30 @@ export const TrainingSubsets: FC<TrainingSubsetsProps> = ({ subsetsParameters, o
269288
</Accordion.Title>
270289
<Accordion.Content>
271290
<Accordion.Description>
272-
Specify the distribution of annotated samples over the training, validation and test subsets. Note:
273-
items that have already been used for training will stay in the same subset even if these parameters
274-
are changed.
291+
Specify the distribution of annotated samples over the training, validation and test subsets. <br />
292+
Note: items that have already been used for training will stay in the same subset even if these
293+
parameters are changed.
294+
<br />
295+
Each subset must have at least one media item.
275296
</Accordion.Description>
276297
<Accordion.Divider marginY={'size-250'} />
277-
<SubsetsDistribution
278-
subsetParameters={subsetsParameters}
279-
subsetsDistribution={subsetsDistribution}
280-
onSubsetsDistributionChange={setSubsetsDistribution}
281-
testSubsetSize={testSubsetSize}
282-
trainingSubsetSize={trainingSubsetSize}
283-
validationSubsetSize={validationSubsetSize}
284-
onSubsetsDistributionChangeEnd={handleUpdateSubsetsConfiguration}
285-
onSubsetsDistributionReset={handleSubsetsConfigurationReset}
286-
/>
298+
<View UNSAFE_className={subsetsSizesValid ? undefined : styles.disabled}>
299+
<SubsetsDistribution
300+
subsetParameters={subsetsParameters}
301+
subsetsDistribution={subsetsDistribution}
302+
onSubsetsDistributionChange={setSubsetsDistribution}
303+
testSubsetSize={testSubsetSize}
304+
trainingSubsetSize={trainingSubsetSize}
305+
validationSubsetSize={validationSubsetSize}
306+
onSubsetsDistributionChangeEnd={handleUpdateSubsetsConfiguration}
307+
onSubsetsDistributionReset={handleSubsetsConfigurationReset}
308+
/>
309+
</View>
310+
311+
<Flex direction={'column'} gap={'size-200'} marginTop={'size-200'}>
312+
{!subsetsSizesValid && <TrainingSubsetsUnavailable />}
313+
{isChangedDistributionWarningVisible && <TrainingSubsetsChangedDistributionWarning />}
314+
</Flex>
287315
</Accordion.Content>
288316
</Accordion>
289317
);

web_ui/src/pages/project-details/components/project-models/train-model-dialog/advanced-settings/data-management/training-subsets/training-subsets.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@
1111
.totalStats {
1212
font-weight: var(--spectrum-global-font-weight-bold);
1313
}
14+
15+
.disabled {
16+
pointer-events: none;
17+
opacity: 0.5;
18+
}

web_ui/src/pages/project-details/components/project-models/train-model-dialog/advanced-settings/data-management/training-subsets/training-subsets.test.tsx

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import { useState } from 'react';
55

6-
import { fireEvent, screen } from '@testing-library/react';
6+
import { fireEvent, screen, within } from '@testing-library/react';
77

88
import { TrainingConfiguration } from '../../../../../../../../core/configurable-parameters/services/configuration.interface';
99
import {
@@ -109,11 +109,17 @@ describe('TrainingSubsets', () => {
109109
const [trainingSubset, validationSubset, testSubset] = subsetsParameters;
110110
const datasetSize = Number(subsetsParameters.at(-1)?.value);
111111

112-
const App = (props: { subsetParameters: SubsetsParameters }) => {
112+
const App = ({
113+
subsetParameters,
114+
hasSupportedModels = false,
115+
}: {
116+
subsetParameters: SubsetsParameters;
117+
hasSupportedModels?: boolean;
118+
}) => {
113119
const [trainingConfiguration, setTrainingConfiguration] = useState<TrainingConfiguration | undefined>(() =>
114120
getMockedTrainingConfiguration({
115121
datasetPreparation: {
116-
subsetSplit: [...props.subsetParameters],
122+
subsetSplit: subsetParameters,
117123
filtering: {},
118124
augmentation: {},
119125
},
@@ -128,14 +134,15 @@ describe('TrainingSubsets', () => {
128134

129135
return (
130136
<TrainingSubsets
131-
subsetsParameters={trainingConfiguration?.datasetPreparation.subsetSplit ?? props.subsetParameters}
137+
hasSupportedModels={hasSupportedModels}
138+
subsetsParameters={trainingConfiguration?.datasetPreparation.subsetSplit ?? subsetParameters}
132139
onUpdateTrainingConfiguration={handleUpdateTrainingConfiguration}
133140
/>
134141
);
135142
};
136143

137144
it('displays subsets distribution properly', () => {
138-
render(<App subsetParameters={subsetsParameters} />);
145+
render(<App subsetParameters={subsetsParameters} hasSupportedModels />);
139146

140147
expectTrainingSubsetsDistribution({
141148
validationSubset: Number(validationSubset.value),
@@ -215,4 +222,99 @@ describe('TrainingSubsets', () => {
215222
testSize: updatedTestSubsetSize,
216223
});
217224
});
225+
226+
it('shows warning that training subsets is unavailable when there are not enough media items', () => {
227+
const mockedSubsetsParameters = [
228+
getMockedConfigurationParameter({
229+
key: 'training',
230+
type: 'int',
231+
name: 'Training percentage',
232+
value: 70,
233+
description: 'Percentage of data to use for training',
234+
defaultValue: 70,
235+
maxValue: 100,
236+
minValue: 1,
237+
}),
238+
getMockedConfigurationParameter({
239+
key: 'validation',
240+
type: 'int',
241+
name: 'Validation percentage',
242+
value: 20,
243+
description: 'Percentage of data to use for validation',
244+
defaultValue: 20,
245+
maxValue: 100,
246+
minValue: 1,
247+
}),
248+
getMockedConfigurationParameter({
249+
key: 'test',
250+
type: 'int',
251+
name: 'Test percentage',
252+
value: 10,
253+
description: 'Percentage of data to use for testing',
254+
defaultValue: 10,
255+
maxValue: 100,
256+
minValue: 1,
257+
}),
258+
getMockedConfigurationParameter({
259+
key: 'auto_selection',
260+
type: 'bool',
261+
name: 'Auto selection',
262+
value: true,
263+
description: 'Whether to automatically select data for each subset',
264+
defaultValue: true,
265+
}),
266+
getMockedConfigurationParameter({
267+
key: 'remixing',
268+
type: 'bool',
269+
name: 'Remixing',
270+
value: false,
271+
description: 'Whether to remix data between subsets',
272+
defaultValue: false,
273+
}),
274+
getMockedConfigurationParameter({
275+
key: 'dataset_size',
276+
type: 'int',
277+
name: 'Dataset size',
278+
value: 6,
279+
description: 'Total size of the dataset (read-only parameter, not configurable by users)',
280+
defaultValue: 6,
281+
maxValue: null,
282+
minValue: 0,
283+
}),
284+
];
285+
const [_, mockedValidationSubset, mockedTestSubset] = mockedSubsetsParameters;
286+
const mockedDatasetSize = Number(mockedSubsetsParameters.at(-1)?.value);
287+
288+
render(<App subsetParameters={mockedSubsetsParameters} />);
289+
290+
const alert = screen.getByRole('alert');
291+
292+
expect(alert).toBeInTheDocument();
293+
expect(within(alert).getByRole('heading')).toHaveTextContent('Training subsets configuration unavailable');
294+
295+
const validationSize = Math.floor(mockedDatasetSize * (Number(mockedValidationSubset.value) / 100));
296+
const testSize = Math.floor(mockedDatasetSize * (Number(mockedTestSubset.value) / 100));
297+
const trainingSize = mockedDatasetSize - validationSize - testSize;
298+
299+
expectSubsetSizes({
300+
trainingSize,
301+
validationSize,
302+
testSize,
303+
});
304+
});
305+
306+
it('shows warning when updated subset distribution requires enabling reshuffle and training from scratch', () => {
307+
render(<App subsetParameters={subsetsParameters} hasSupportedModels />);
308+
309+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
310+
311+
fireEvent.keyDown(screen.getByLabelText('End range'), { key: 'Left' });
312+
313+
const alert = screen.getByRole('alert');
314+
315+
expect(alert).toBeInTheDocument();
316+
expect(within(alert).getByRole('heading')).toHaveTextContent(
317+
'Additional configuration change required to apply new training subsets distribution'
318+
);
319+
});
218320
});

0 commit comments

Comments
 (0)