Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Add feature importance summary charts #78238

Merged
merged 25 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f2b59b7
[ML] Add total summary charts for binary, multiclass (mock), and regr…
qn895 Sep 22, 2020
f14cae9
[ML] Remove mock data
qn895 Sep 22, 2020
c8dd714
[ML] Add formatting to 3 sig fig
qn895 Sep 23, 2020
255c5c3
[ML] Update LoadingPanel for when totalFeatureImportance
qn895 Sep 23, 2020
6820ab7
[ML] Add legend
qn895 Sep 23, 2020
2226e92
Merge remote-tracking branch 'upstream/master' into ml-feature-import…
qn895 Sep 23, 2020
8f18b98
[ML] Update comment for consistency
qn895 Sep 23, 2020
dc2538b
[ML] Only fetch inference model if regression or classification. Add …
qn895 Sep 23, 2020
bf2a837
Merge branch 'master' into ml-feature-importance-summary
elasticmachine Sep 23, 2020
f263a75
Merge remote-tracking branch 'upstream/master' into ml-feature-import…
qn895 Sep 24, 2020
c419c42
[ML] Adjust height, add i18n, only show legend of multiclass
qn895 Sep 24, 2020
fd279e4
[ML] Fix text
qn895 Sep 28, 2020
5ca3178
Merge remote-tracking branch 'upstream/master' into ml-feature-import…
qn895 Sep 28, 2020
6a3c0d8
[ML] Fix text & spacing
qn895 Sep 28, 2020
89f1ee3
[ML] Fix tooltip icon tip to match inline
qn895 Sep 28, 2020
25f8f83
Merge remote-tracking branch 'upstream/master' into ml-feature-import…
qn895 Sep 28, 2020
58fe66b
[ML] Update tooltipContent
qn895 Sep 28, 2020
e18bc41
[ML] Move to feature_importance type file, use util functions, remove cn
qn895 Sep 29, 2020
da48b1a
[ML] Sort order of class form smallest bottom to biggest top
qn895 Sep 29, 2020
3c4251f
[ML] Sort order of binary class form smallest bottom to biggest top
qn895 Sep 29, 2020
b73325b
[ML] Remove commented code
qn895 Sep 29, 2020
ab8a750
[ML] Rename total feature importance & hide numeric values for classi…
qn895 Sep 29, 2020
4c0abf5
Merge remote-tracking branch 'upstream/master' into ml-feature-import…
qn895 Sep 30, 2020
17cfd1d
[ML] Update legend for multi class total feature importance to look n…
qn895 Sep 30, 2020
701cbc2
Merge branch 'master' into ml-feature-importance-summary
elasticmachine Oct 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions x-pack/plugins/ml/common/types/feature_importance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,42 @@ export interface TopClass {
}

export type TopClasses = TopClass[];

export interface ClassFeatureImportanceSummary {
class_name: string;
importance: {
max: number;
min: number;
mean_magnitude: number;
};
}
export interface ClassificationTotalFeatureImportance {
feature_name: string;
classes: ClassFeatureImportanceSummary[];
}

export interface RegressionFeatureImportanceSummary {
max: number;
min: number;
mean_magnitude: number;
}

export interface RegressionTotalFeatureImportance {
feature_name: string;
importance: RegressionFeatureImportanceSummary;
}
export type TotalFeatureImportance =
| ClassificationTotalFeatureImportance
| RegressionTotalFeatureImportance;

export function isClassificationTotalFeatureImportance(
summary: ClassificationTotalFeatureImportance | RegressionTotalFeatureImportance
): summary is ClassificationTotalFeatureImportance {
return (summary as ClassificationTotalFeatureImportance).classes !== undefined;
}

export function isRegressionTotalFeatureImportance(
summary: ClassificationTotalFeatureImportance | RegressionTotalFeatureImportance
): summary is RegressionTotalFeatureImportance {
return (summary as RegressionTotalFeatureImportance).importance !== undefined;
}
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/common/types/inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { DataFrameAnalyticsConfig } from './data_frame_analytics';
import { TotalFeatureImportance } from './feature_importance';

export interface IngestStats {
count: number;
Expand Down Expand Up @@ -54,6 +55,7 @@ export interface ModelConfigResponse {
| {
analytics_config: DataFrameAnalyticsConfig;
input: any;
total_feature_importance?: TotalFeatureImportance[];
}
| Record<string, any>;
model_id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ interface DecisionPathChartProps {
baseline?: number;
minDomain: number | undefined;
maxDomain: number | undefined;
showValues?: boolean;
}

const DECISION_PATH_MARGIN = 125;
Expand All @@ -87,6 +88,7 @@ export const DecisionPathChart = ({
minDomain,
maxDomain,
baseline,
showValues,
}: DecisionPathChartProps) => {
// adjust the height so it's compact for items with more features
const baselineData: LineAnnotationDatum[] = useMemo(
Expand All @@ -105,9 +107,12 @@ export const DecisionPathChart = ({
],
[baseline]
);
// guarantee up to num_precision significant digits
// without having it in scientific notation
const tickFormatter = useCallback((d) => Number(d.toPrecision(NUM_PRECISION)).toString(), []);
// if regression, guarantee up to num_precision significant digits without having it in scientific notation
// if classification, hide the numeric values since we only want to show the path
const tickFormatter = useCallback(
(d) => (showValues === false ? '' : Number(d.toPrecision(NUM_PRECISION)).toString()),
[]
);

return (
<Chart
Expand All @@ -127,6 +132,7 @@ export const DecisionPathChart = ({
<Axis
id={'xpack.ml.dataframe.analytics.explorationResults.decisionPathXAxis'}
tickFormat={tickFormatter}
ticks={showValues === false ? 0 : undefined}
title={i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.decisionPathXAxisTitle',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const ClassificationDecisionPath: FC<ClassificationDecisionPathProps> = (
predictionFieldName={predictionFieldName}
minDomain={domain.minDomain}
maxDomain={domain.maxDomain}
showValues={false}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@ import { DataFrameAnalyticsConfig } from '../common';

import { isGetDataFrameAnalyticsStatsResponseOk } from '../pages/analytics_management/services/analytics_service/get_analytics';
import { DATA_FRAME_TASK_STATE } from '../pages/analytics_management/components/analytics_list/common';
import { useInferenceApiService } from '../../services/ml_api_service/inference';
import { TotalFeatureImportance } from '../../../../common/types/feature_importance';
import { getToastNotificationService } from '../../services/toast_notification_service';
import {
isClassificationAnalysis,
isRegressionAnalysis,
} from '../../../../common/util/analytics_utils';

export const useResultsViewConfig = (jobId: string) => {
const mlContext = useMlContext();
const inferenceApiService = useInferenceApiService();

const [indexPattern, setIndexPattern] = useState<IndexPattern | undefined>(undefined);
const [isInitialized, setIsInitialized] = useState<boolean>(false);
const [needsDestIndexPattern, setNeedsDestIndexPattern] = useState<boolean>(false);
Expand All @@ -33,13 +42,18 @@ export const useResultsViewConfig = (jobId: string) => {
const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState<undefined | string>(undefined);
const [jobStatus, setJobStatus] = useState<DATA_FRAME_TASK_STATE | undefined>(undefined);

const [totalFeatureImportance, setTotalFeatureImportance] = useState<
TotalFeatureImportance[] | undefined
>(undefined);

// get analytics configuration, index pattern and field caps
useEffect(() => {
(async function () {
setIsLoadingJobConfig(false);

try {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);

const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
? analyticsStats.data_frame_analytics[0]
Expand All @@ -54,6 +68,28 @@ export const useResultsViewConfig = (jobId: string) => {
analyticsConfigs.data_frame_analytics.length > 0
) {
const jobConfigUpdate = analyticsConfigs.data_frame_analytics[0];
// don't fetch the total feature importance if it's outlier_detection
if (
isClassificationAnalysis(jobConfigUpdate.analysis) ||
isRegressionAnalysis(jobConfigUpdate.analysis)
) {
try {
const inferenceModels = await inferenceApiService.getInferenceModel(`${jobId}*`, {
include: 'total_feature_importance',
});
const inferenceModel = inferenceModels.find(
(model) => model.metadata?.analytics_config?.id === jobId
);
if (
Array.isArray(inferenceModel?.metadata?.total_feature_importance) === true &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - I don't think you really need the === true after the isArray check as the isArray method is pretty clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reasons TS needs the explicit check here to make the second part of the if statement valid. Not sure why but I will leave it for now if that's okay.

inferenceModel?.metadata?.total_feature_importance.length > 0
) {
setTotalFeatureImportance(inferenceModel?.metadata?.total_feature_importance);
}
} catch (e) {
getToastNotificationService().displayErrorToast(e);
}
}

try {
const destIndex = Array.isArray(jobConfigUpdate.dest.index)
Expand Down Expand Up @@ -103,5 +139,6 @@ export const useResultsViewConfig = (jobId: string) => {
jobConfigErrorMessage,
jobStatus,
needsDestIndexPattern,
totalFeatureImportance,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';

import { ExplorationPageWrapper } from '../exploration_page_wrapper';
import { EvaluatePanel } from './evaluate_panel';

import { FeatureImportanceSummaryPanel } from '../total_feature_importance_summary/feature_importance_summary';
interface Props {
jobId: string;
defaultIsTraining?: boolean;
Expand All @@ -27,6 +27,7 @@ export const ClassificationExploration: FC<Props> = ({ jobId, defaultIsTraining
}
)}
EvaluatePanel={EvaluatePanel}
FeatureImportanceSummaryPanel={FeatureImportanceSummaryPanel}
defaultIsTraining={defaultIsTraining}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/
import { ExplorationResultsTable } from '../exploration_results_table';
import { JobConfigErrorCallout } from '../job_config_error_callout';
import { LoadingPanel } from '../loading_panel';
import { FeatureImportanceSummaryPanelProps } from '../total_feature_importance_summary/feature_importance_summary';

export interface EvaluatePanelProps {
jobConfig: DataFrameAnalyticsConfig;
Expand All @@ -27,13 +28,15 @@ interface Props {
jobId: string;
title: string;
EvaluatePanel: FC<EvaluatePanelProps>;
FeatureImportanceSummaryPanel: FC<FeatureImportanceSummaryPanelProps>;
defaultIsTraining?: boolean;
}

export const ExplorationPageWrapper: FC<Props> = ({
jobId,
title,
EvaluatePanel,
FeatureImportanceSummaryPanel,
defaultIsTraining,
}) => {
const {
Expand All @@ -45,6 +48,7 @@ export const ExplorationPageWrapper: FC<Props> = ({
jobConfigErrorMessage,
jobStatus,
needsDestIndexPattern,
totalFeatureImportance,
} = useResultsViewConfig(jobId);
const [searchQuery, setSearchQuery] = useState<ResultsSearchQuery>(defaultSearchQuery);

Expand All @@ -63,6 +67,14 @@ export const ExplorationPageWrapper: FC<Props> = ({
{isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && (
<EvaluatePanel jobConfig={jobConfig} jobStatus={jobStatus} searchQuery={searchQuery} />
)}
{isLoadingJobConfig === true && totalFeatureImportance === undefined && <LoadingPanel />}
{isLoadingJobConfig === false && totalFeatureImportance !== undefined && (
<>
<EuiSpacer />
<FeatureImportanceSummaryPanel totalFeatureImportance={totalFeatureImportance} />
</>
)}

<EuiSpacer />
{isLoadingJobConfig === true && jobConfig === undefined && <LoadingPanel />}
{isLoadingJobConfig === false &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { ExplorationPageWrapper } from '../exploration_page_wrapper';

import { EvaluatePanel } from './evaluate_panel';
import { FeatureImportanceSummaryPanel } from '../total_feature_importance_summary/feature_importance_summary';

interface Props {
jobId: string;
Expand All @@ -25,6 +26,7 @@ export const RegressionExploration: FC<Props> = ({ jobId, defaultIsTraining }) =
values: { jobId },
})}
EvaluatePanel={EvaluatePanel}
FeatureImportanceSummaryPanel={FeatureImportanceSummaryPanel}
defaultIsTraining={defaultIsTraining}
/>
);
Loading