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

[7.x] [ML] Add feature importance summary charts (#78238) #79159

Merged
merged 1 commit into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 &&
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