diff --git a/mindinsight/ui/src/components/search-select.vue b/mindinsight/ui/src/components/search-select.vue
index 485f22bd..dafa2ef3 100644
--- a/mindinsight/ui/src/components/search-select.vue
+++ b/mindinsight/ui/src/components/search-select.vue
@@ -110,7 +110,7 @@ limitations under the License.
* The PublicStore holds the key of focused selector
* When there is more than two component in same page, help selector to keep correct display
*/
-const PublicStore = {activeKey: {key: ''}};
+const PublicStore = { activeKey: { key: '' } };
export default {
props: {
multiple: {
@@ -215,8 +215,11 @@ export default {
* The logic of click event that add to window, which can make response to defocus
*/
clickHandler() {
- this.ifFocus = false;
- this.ifActive = false;
+ if (this.ifFocus) {
+ this.ifFocus = false;
+ this.ifActive = false;
+ this.$emit('selectBlur');
+ }
},
/**
* The logic of click the selector, including everywhere
@@ -310,9 +313,14 @@ export default {
* @param {Number} index
*/
mulDeselectOption(option, index) {
- const indexTemp = this.indexes.indexOf(index);
- this.indexes.splice(indexTemp, 1);
- option.selected = false;
+ return new Promise((resolve) => {
+ const indexTemp = this.indexes.indexOf(index);
+ this.indexes.splice(indexTemp, 1);
+ option.selected = false;
+ this.$nextTick(() => {
+ resolve(true);
+ });
+ });
},
/**
* The logic of select option when multiple is true
@@ -361,7 +369,11 @@ export default {
cancelLabel(event, index) {
event.stopPropagation();
event.preventDefault();
- this.mulDeselectOption(this.options[index], index);
+ this.mulDeselectOption(this.options[index], index).then(() => {
+ if (!this.ifActive) {
+ this.$emit('cancelLabel');
+ }
+ });
},
},
created() {
@@ -411,7 +423,7 @@ export default {
}, this.filterDebounce);
},
// The watcher of source can process asynchronous data input, or make response when original data changed
- 'source': {
+ source: {
handler(newVal) {
this.indexes = [];
this.options = this.processData(newVal);
@@ -427,7 +439,7 @@ export default {
}
},
},
- 'indexes': {
+ indexes: {
handler() {
this.$nextTick(() => {
this.$emit('selectedUpdate', this.calValues());
@@ -558,16 +570,29 @@ export default {
.cl-search-select .select-container .option-container .select-option:hover {
background-color: #f5f7fa;
}
-.cl-search-select .select-container .option-container .select-option .label-container {
+.cl-search-select
+ .select-container
+ .option-container
+ .select-option
+ .label-container {
white-space: nowrap;
max-width: 320px;
text-overflow: ellipsis;
overflow: hidden;
}
-.cl-search-select .select-container .option-container .select-option .icon-container {
+.cl-search-select
+ .select-container
+ .option-container
+ .select-option
+ .icon-container {
width: 14px;
}
-.cl-search-select .select-container .option-container .select-option .icon-container .icon-no-selected {
+.cl-search-select
+ .select-container
+ .option-container
+ .select-option
+ .icon-container
+ .icon-no-selected {
display: none;
}
.cl-search-select .select-container .option-container .is-selected {
@@ -591,7 +616,9 @@ export default {
-webkit-box-shadow: inset 0 0 6px rgba(144, 147, 153, 0.3);
background-color: #e8e8e8;
}
-.cl-search-select .select-container .option-container::-webkit-scrollbar-thumb:hover {
+.cl-search-select
+ .select-container
+ .option-container::-webkit-scrollbar-thumb:hover {
-webkit-box-shadow: inset 0 0 6px rgba(144, 147, 153, 0.3);
background-color: #cacaca;
border-radius: 3px;
diff --git a/mindinsight/ui/src/locales/en-us.json b/mindinsight/ui/src/locales/en-us.json
index 14e16fe2..5ed1a31c 100644
--- a/mindinsight/ui/src/locales/en-us.json
+++ b/mindinsight/ui/src/locales/en-us.json
@@ -23,7 +23,8 @@
"deselectAll": "Clear",
"dataLoading": "Loading data...",
"notice": "Information",
- "caseMode": "Not case sensitive"
+ "caseMode": "Not case sensitive",
+ "all": "All"
},
"symbols": {
"leftbracket": "(",
@@ -799,24 +800,34 @@
"explainSummaryCurrentFolder": "Root path of the explanation log:",
"summaryPath": "Explanation Log Path",
"title": "Saliency Map Visualization",
+ "conterfactualInterpretation": "Counterfactual Explanation",
"explainMethod": "Explanation Methods",
"viewScore": "View Score",
"fetch": "Filter",
"minConfidence": "Probability Threshold",
+ "confidenceRange": "Probability and Range",
"imgSort": "Sort Images By",
"default": "Default",
"byProbability": "Probabilities in descending order",
+ "byUncertainty": "Uncertainties in descending order",
+ "uncertainty": "uncertainty",
"superposeImg": "Overlay on Original Image",
"originalPicture": "Original Image",
"forecastTag": "Prediction Tag",
+ "forecastTagPosibility": "Prediction Tag (Probability)",
"tag": "Tag",
- "tagTip": "Query the image data that contains any filter label",
+ "filterImg": "Filter Image",
+ "tagTip": "Query image data that contains any filter tag",
+ "typeTip": "Query image data that contains any prediction type",
"confidence": "Probability",
- "forecastTagTip": "When the inference image has the correct tag, the following four flags are displayed in the tag row",
+ "forecastTagTip": "When the inference image has the correct tag, the following three flags are displayed in the tag row",
"TP": "TP, indicates true positive. The tag is a positive sample, and the classification is a positive sample;",
"FN": "FN, indicates false negative. The tag is a positive sample, and the classification is a negative sample;",
"FP": "FP, indicates false positive. The tag is a negative sample, and the classification is a positive sample;",
"TN": "TN, Indicates true negative. The tag is a negative sample, and the classification is a negative sample;",
+ "coverfactualInterpretation": "Mask-based Counterfactual Explanation",
+ "viewExplanation": "View Explanation",
+ "maskingProcess": "Layer-by-layer Masking Process",
"mainTipTitle": "Function description:",
"mainTipPartOne": "This function visualizes the basis for model classification. After the to-be-explained model, the image, and the tag are selected, a contribution degree of each pixel in the original image to the selected tag is calculated by using an explanation method, and visualization is performed by using a saliency map similar to a heatmap. A brighter color indicates that the corresponding area contributes more to the selected tag of the model prediction. The darker the color, the smaller the contribution of the area to the selected tag.",
"mainTipPartTwo": "A saliency map helps you understand the features related to the specified tag during deep neural network inference. When the inference basis and expectation of a model are different, you can debug the model by referring to the saliency map so that the model can perform inference based on proper features.",
@@ -824,7 +835,13 @@
"mainTipPartFour": "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9050829",
"noExplainer": "Select Explanation Method",
"minConfidenceTip": "Probability threshold of a prediction tag. A tag is recorded as a prediction tag if its output probability is greater than the threshold.",
- "noData": "Loading data, please refresh the page later"
+ "noData": "Loading data... Refresh the page later",
+ "predictionType": "Prediction Type",
+ "disableSaliencyMapTip": "Failed to view saliency map visualization because no saliency map log is available.",
+ "disableHOCTip": "Failed to view counterfactual explanation because no counterfactual explanation log is available.",
+ "hocMinConfidenceTip": "Display and explain the labels whose prediction probability is greater than the prediction threshold",
+ "imageList": "Picture list",
+ "hocTitleTip": "Gradually shrink the display range to find the minimum display area where the prediction probability is greater than the threshold"
},
"metric": {
"scoreSystem": "Scoring System",
diff --git a/mindinsight/ui/src/locales/zh-cn.json b/mindinsight/ui/src/locales/zh-cn.json
index 9a394b38..8860b26a 100644
--- a/mindinsight/ui/src/locales/zh-cn.json
+++ b/mindinsight/ui/src/locales/zh-cn.json
@@ -23,7 +23,8 @@
"deselectAll": "取消全选",
"dataLoading": "数据加载中",
"notice": "提示",
- "caseMode": "不区分大小写"
+ "caseMode": "不区分大小写",
+ "all": "全部"
},
"symbols": {
"leftbracket": "(",
@@ -795,24 +796,34 @@
"explainSummaryCurrentFolder": "解释日志根路径:",
"summaryPath": "解释日志路径",
"title": "显著图可视化",
+ "conterfactualInterpretation": "反事实解释",
"explainMethod": "解释方法",
"viewScore": "查看评分",
"fetch": "筛选",
"minConfidence": "概率阈值",
+ "confidenceRange": "概率(区间)",
"imgSort": "图片排序",
"default": "默认",
"byProbability": "概率值降序",
+ "byUncertainty": "不确定性值降序",
+ "uncertainty": "不确定性",
"superposeImg": "叠加于原图",
"originalPicture": "原始图片",
"forecastTag": "预测标签",
+ "forecastTagPosibility": "预测标签 (概率)",
"tag": "标签",
+ "filterImg": "图片筛选",
"tagTip": "查询包含任一筛选标签的图片数据",
+ "typeTip": "查询包含任一预测类型的图片数据",
"confidence": "概率",
- "forecastTagTip": "当推理图片带有正确标签时,标签行会显示下列四种旗标",
+ "forecastTagTip": "当推理图片带有正确标签时,标签行会显示下列三种旗标",
"TP": "TP,代表Ture Positive,标签为正样本,分类为正样本;",
"FN": "FN,代表False Negative,标签为正样本,分类为负样本;",
- "FP": "FP,代表Fasle Positive,标签为负样本,分类为正样本;",
+ "FP": "FP,代表False Positive,标签为负样本,分类为正样本;",
"TN": "TN,代表Ture Negative,标签为负样本,分类为负样本;",
+ "coverfactualInterpretation": "遮掩反事实解释",
+ "viewExplanation": "查看解释",
+ "maskingProcess": "逐层遮掩过程",
"mainTipTitle": "功能说明:",
"mainTipPartOne": "本功能对模型分类的依据进行可视化。选定待解释模型、图片和标签后,解释方法计算得到原始图像中每个像素对选定标签的贡献度,以类似热力图的显著图进行可视。显著图颜色越亮,表示对应区域对于模型预测选定标签的贡献越多;颜色越暗,该区域对选定标签的贡献越小。",
"mainTipPartTwo": "显著图可以帮助我们了解深度神经网络推理时,和指定标签有关系的特征。当模型推理依据和期望不同时,可以参考显著图对模型进行调试,让模型依据合理的特征进行推理。",
@@ -820,7 +831,13 @@
"mainTipPartFour": "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9050829",
"noExplainer": "请选择解释方法",
"minConfidenceTip": "预测标签的概率阈值,当一个标签的输出概率大于阈值时就会被记为预测标签。",
- "noData": "数据正在加载,请稍后刷新页面"
+ "noData": "数据正在加载,请稍后刷新页面",
+ "predictionType": "预测类型",
+ "disableSaliencyMapTip": "无显著图可视日志,无法查看",
+ "disableHOCTip": "无反事实解释日志,无法查看",
+ "hocMinConfidenceTip": "对预测概率大于预测阈值的标签进行显示解释",
+ "imageList": "图片列表",
+ "hocTitleTip": "逐步收缩显示范围,以寻找出预测概率大于阈值的最小显示区域"
},
"metric": {
"scoreSystem": "评分体系",
diff --git a/mindinsight/ui/src/main.js b/mindinsight/ui/src/main.js
index ccfbb655..051a3e90 100644
--- a/mindinsight/ui/src/main.js
+++ b/mindinsight/ui/src/main.js
@@ -29,10 +29,23 @@ import {basePath} from '@/services/fetcher';
let language = window.localStorage.getItem('milang');
const languageList = ['zh-cn', 'en-us'];
-if (!language || !languageList.includes(language)) {
- language = languageList[1];
+if (!language) {
+ let tempLang = navigator.language || navigator.userLanguage;
+ tempLang = tempLang.substr(0, 2);
+ if (tempLang === 'zh') {
+ language = languageList[0];
+ } else {
+ language = languageList[1];
+ }
window.localStorage.setItem('milang', language);
+} else {
+ if (!languageList.includes(language)) {
+ // set English if no default language
+ language = languageList[1];
+ window.localStorage.setItem('milang', language);
+ }
}
+store.commit('setLanguage', language);
if (language !== languageList[0]) {
Vue.use(ElementUI, {locale});
diff --git a/mindinsight/ui/src/router.js b/mindinsight/ui/src/router.js
index a162cad9..505a602c 100644
--- a/mindinsight/ui/src/router.js
+++ b/mindinsight/ui/src/router.js
@@ -139,8 +139,12 @@ export default new Router({
},
{
path: '/explain/saliency-map',
+ component: () => import('./views/explain/saliency-map.vue'),
+ },
+ {
+ path: '/explain/conterfactual-interpretation',
component: () =>
- import('./views/explain/saliency-map.vue'),
+ import('./views/explain/conterfactual-interpretation.vue'),
},
{
path: '/explain/xai-metric',
diff --git a/mindinsight/ui/src/services/request-service.js b/mindinsight/ui/src/services/request-service.js
index b5977d67..72a04c02 100644
--- a/mindinsight/ui/src/services/request-service.js
+++ b/mindinsight/ui/src/services/request-service.js
@@ -440,6 +440,13 @@ export default {
params: params,
});
},
+ queryHOCData(params) {
+ return axios({
+ method: 'post',
+ url: 'v1/mindinsight/explainer/hoc',
+ data: params,
+ });
+ },
tensorHitsData(params) {
return axios({
method: 'get',
diff --git a/mindinsight/ui/src/store.js b/mindinsight/ui/src/store.js
index 50fdaebe..9b76afc6 100644
--- a/mindinsight/ui/src/store.js
+++ b/mindinsight/ui/src/store.js
@@ -40,6 +40,8 @@ export default new Vuex.Store({
// Echart column selected by model traceability box
selectedBarList: [],
customizedColumnOptions: [],
+ // Current language
+ language: 'en-us',
},
mutations: {
// set cancelTokenArr
@@ -87,6 +89,9 @@ export default new Vuex.Store({
componentsNum(state) {
state.componentsCount++;
},
+ setLanguage(state, val) {
+ state.language = val;
+ },
},
actions: {},
});
diff --git a/mindinsight/ui/src/views/explain/conterfactual-interpretation.vue b/mindinsight/ui/src/views/explain/conterfactual-interpretation.vue
new file mode 100644
index 00000000..26baef08
--- /dev/null
+++ b/mindinsight/ui/src/views/explain/conterfactual-interpretation.vue
@@ -0,0 +1,917 @@
+
+
+
+
+
+ {{$t('explain.coverfactualInterpretation')}}
+
+
+
+
+
+
+
+
+
+
+ {{$t('explain.imageList')}}
+
+
+
+
{{$t('explain.tag')}}
+
+
+
+
+
+
+
+
{{$t('explain.imgSort')}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
{{$t("public.noData")}}
+
{{$t("public.dataLoading")}}
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{$t('explain.originalPicture')}}
+
+
+
+ {{$t('explain.forecastTagPosibility')}}
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
![]()
+
+
{{$t("public.noData")}}
+
{{$t("public.dataLoading")}}
+
+
+
+
+
+
{{$t('explain.viewExplanation')}}
+
+
+ {{ $t('explain.minConfidence') }}
+
+
+ {{ $t('explain.hocMinConfidenceTip') }}
+
+
+
+
{{ $t('symbols.colon') }}
+
{{ minConfidence }}
+
+
+
+
+
+
![]()
+
+
+
![]()
+
+
{{$t("public.noData")}}
+
{{$t("public.dataLoading")}}
+
+
+
+ {{curImageData.curSampleData.label}}
+ {{$t('symbols.leftbracket')}}
+ {{curImageData.curSampleData.hoc_layers[curImageData.imageIndex].confidence.toFixed(3)}}
+ {{$t('symbols.rightbracket')}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mindinsight/ui/src/views/explain/saliency-map.vue b/mindinsight/ui/src/views/explain/saliency-map.vue
index 54536f8e..de5aa39e 100644
--- a/mindinsight/ui/src/views/explain/saliency-map.vue
+++ b/mindinsight/ui/src/views/explain/saliency-map.vue
@@ -17,27 +17,27 @@ limitations under the License.
- {{$t('explain.title')}}
+ {{ $t('explain.title') }}
@@ -50,87 +50,104 @@ limitations under the License.
- {{$t('explain.viewScore')}}
+ {{
+ $t('explain.viewScore')
+ }}
-
-
- {{ $t('explain.tag') }}
-
-
- {{$t('explain.tagTip')}}
-
-
-
-
-
-
+
+ {{ $t('explain.filterImg') }}
+
+
+
+ {{ $t('explain.tag') }}
+
+
+ {{ $t('explain.tagTip') }}
+
+
+
+
+ @selectEnter="fetch"
+ @selectBlur="fetch"
+ @cancelLabel="fetch">
-
-
- {{ $t('explain.fetch') }}
-
-
-
- {{ $t('explain.minConfidence')}}
-
-
- {{$t('explain.minConfidenceTip')}}
-
-
-
- {{$t('symbols.colon')}}
- {{minConfidence}}
-
-
-
-
- {{$t('explain.imgSort')}}
-
-
-
-
-
-
-
- {{
- $t('explain.superposeImg')
- }}
-
-
+
+
+ {{ $t('explain.predictionType') }}
+
+
+ {{ $t('explain.typeTip') }}
+
+
+
+
+ {{TP}}
+ {{FN}}
+ {{FP}}
+
+
+
+
+ {{ $t('explain.minConfidence') }}
+
+
+ {{ $t('explain.minConfidenceTip') }}
+
+
+
+
{{ $t('symbols.colon') }}
+
{{ minConfidence }}
+
+
+
+
+
+ {{ $t('explain.imgSort') }}
+
+
+
+
+
+
+
+ {{ $t('explain.superposeImg') }}
+
+ v-if="ifNoData">
![]()
+ alt="" />
+ v-if="ifLoading">
{{ $t('public.dataLoading') }}
+ :empty-text="emptyText">
@@ -202,12 +213,49 @@ limitations under the License.
-
+
+
+
+
{{ $t('explain.tag') }}
+
{{ $t('explain.confidenceRange') }}
+
{{ $t('explain.uncertainty') }}
+
+
+
+
+
{{ tag.label }}
+
+
{{ tag.confidence.toFixed(3) }}
+
+ {{
+ `[${Math.floor(tag.confidence_itl95[0] * 100) / 100},`+
+ ` ${Math.ceil(tag.confidence_itl95[1] * 100) / 100}]`
+ }}
+
+
+
+ {{ tag.confidence_sd === 0 ? 0 : tag.confidence_sd.toFixed(6) }}
+
+
+
+
+
-
{{$t('explain.tag')}}
-
{{$t('explain.confidence')}}
+
{{ $t('explain.tag') }}
+
{{ $t('explain.confidence') }}
@@ -215,16 +263,18 @@ limitations under the License.
:key="tag.label"
class="tag-content-item tag-content-item-false"
:class="{
- 'tag-active': index === scope.row.activeLabelIndex,
- 'tag-tp': tag.type === 'tp',
- 'tag-fn': tag.type === 'fn',
- 'tag-fp': tag.type === 'fp'
- }"
+ 'tag-active': index === scope.row.activeLabelIndex,
+ 'tag-tp': tag.prediction_type.toUpperCase() === TP,
+ 'tag-fn': tag.prediction_type.toUpperCase() === FN,
+ 'tag-fp': tag.prediction_type.toUpperCase() === FP,
+ }"
@click="changeActiveLabel(scope.row, index)">
{{tag.label}}
-
{{tag.confidence.toFixed(3)}}
+ :title="tag.label">
+ {{ tag.label }}
+
+
{{ tag.confidence.toFixed(3) }}
@@ -237,13 +287,19 @@ limitations under the License.
class-name="canvas-cell"
:resizable="false">
- {{explainer}}
+ {{ explainer }}
-
@@ -256,9 +312,9 @@ limitations under the License.
![]()
+ alt="" />
- {{$t('explain.noExplainer')}}
+ {{ $t('explain.noExplainer') }}
@@ -301,6 +357,13 @@ import searchSelect from '../../components/search-select';
import requestService from '../../services/request-service.js';
import {basePath} from '@/services/fetcher';
+// The effective prediction types
+const [TP, FN, FP] = ['TP', 'FN', 'FP'];
+// The sorted name of image
+const [CONFIDENCE, UNCERTAINTY] = ['confidence', 'uncertainty'];
+// The sorted type of image
+const DESCENDING = 'descending';
+
export default {
components: {
selectGroup,
@@ -309,6 +372,9 @@ export default {
},
data() {
return {
+ ifNoData: true,
+ ifLoading: true,
+ ifError: false,
trainID: '', // The id of the train
selectedExplainers: [], // The selected explainer methods
allExplainers: [], // The list of all explainer method
@@ -319,21 +385,18 @@ export default {
imgShow: false,
}, // The object of show img dialog
ifSuperpose: false, // If open the superpose function
- ifTableLoading: true, // If the table waiting for the data
- ifError: false, // If request error
minConfidence: 0, // The min confidence
tableData: null, // The table data
selectedTruthLabels: [], // The selected truth labels
truthLabels: [], // The list of all truth labels
- // truthLabelsTemp: [], // The list of all truth labels
- sortedName: 'confidence', // The sorted Name of sort
+ sortedName: CONFIDENCE, // The sorted Name of sort
sortedNames: [
{
label: this.$t('explain.byProbability'),
- value: 'confidence',
+ value: CONFIDENCE,
},
], // The list of all sorted Name
- sortedType: 'descending', // The default sorted type
+ sortedType: DESCENDING, // The default sorted type
pageInfo: {
currentPage: 1,
pageSize: 2,
@@ -341,8 +404,18 @@ export default {
}, // The object of pagination information
tableHeight: 0, // The height of table to fix the table header
queryParameters: null, // The complete parameters of query table information, have pagination information
- labelReady: false, // If the truth labels are ready
pageChangeDelay: 200, // The time interval used to prevent the violent clicks of changing current page
+ emptyText: '', // The empty text of table
+ TP: TP, // Means true positive
+ FN: FN, // Means false negative
+ FP: FP, // Means false positive
+ predictionTypes: [TP, FN, FP], // The effective filter prediction types
+ hasPrediction: true, // If prediction type filter is effective
+ hasMetric: false, // If has metric information
+ requestTime: 0, // The count of request
+ requestDelay: 1500, // The delay of request in ms
+ requestLimit: 3, // The limit of request
+ requestTimer: null, // The interval timer of request
};
},
computed: {
@@ -353,6 +426,7 @@ export default {
labels: this.selectedTruthLabels,
explainer: this.selectedExplainers,
sorted_name: this.sortedName,
+ prediction_types: this.predictionTypes,
};
},
},
@@ -398,7 +472,7 @@ export default {
this.pageInfo.currentPage = 1;
this.queryParameters.offset = this.pageInfo.currentPage - 1;
this.queryParameters.limit = val;
- this.queryPageInfo(this.queryParameters);
+ this.queryPageInfo(this.queryParameters).then();
},
/**
* The logic that is executed when the current page number changed
@@ -408,7 +482,7 @@ export default {
clearTimeout(this.pageChangeTimer);
this.pageChangeTimer = setTimeout(() => {
this.queryParameters.offset = val - 1;
- this.queryPageInfo(this.queryParameters);
+ this.queryPageInfo(this.queryParameters).then();
this.pageChangeTimer = null;
}, this.pageChangeDelay);
},
@@ -420,7 +494,7 @@ export default {
this.queryParameters.sorted_name = val;
this.pageInfo.currentPage = 1;
this.queryParameters.offset = this.pageInfo.currentPage - 1;
- this.queryPageInfo(this.queryParameters);
+ this.queryPageInfo(this.queryParameters).then();
},
/**
* The logic of click the explainer method canvas
@@ -438,46 +512,64 @@ export default {
/**
* Request basic information of train
* @param {Object} params Parameters of the request basic information of train interface
- * @return {Object}
*/
queryTrainInfo(params) {
- return new Promise((resolve, reject) => {
- requestService
- .queryTrainInfo(params)
- .then(
- (res) => {
- if (res && res.data) {
- if (res.data.saliency) {
- this.minConfidence = res.data.saliency.min_confidence
+ requestService
+ .queryTrainInfo(params)
+ .then(
+ (res) => {
+ if (res && res.data) {
+ if (res.data.saliency) {
+ this.minConfidence = res.data.saliency.min_confidence
? res.data.saliency.min_confidence
: '--';
- this.allExplainers = this.arrayToCheckBox(
- res.data.saliency.explainers,
- );
+ this.hasMetric = res.data.saliency.metrics.length
+ ? true
+ : false;
+ this.allExplainers = this.arrayToCheckBox(
+ res.data.saliency.explainers,
+ );
+ }
+ if (res.data.classes) {
+ const truthLabels = [];
+ for (let i = 0; i < res.data.classes.length; i++) {
+ truthLabels.push(res.data.classes[i].label);
}
- if (res.data.classes) {
- const truthLabels = [];
- for (let i = 0; i < res.data.classes.length; i++) {
- truthLabels.push(res.data.classes[i].label);
- }
- this.truthLabels = truthLabels;
+ this.truthLabels = truthLabels;
+ }
+ if (res.data.uncertainty) {
+ this.uncertaintyEnabled = res.data.uncertainty.enabled
+ ? true
+ : false;
+ // The sort by uncertainty only valid when uncertaintyEnabled is true
+ if (this.uncertaintyEnabled) {
+ this.sortedNames.push({
+ label: this.$t('explain.byUncertainty'),
+ value: UNCERTAINTY,
+ });
}
}
- resolve(true);
- },
- (error) => {
- reject(error);
- },
- )
- .catch((error) => {
- reject(error);
- });
- });
+ }
+ this.ifNoData = false;
+ this.ifLoading = false;
+ },
+ () => {
+ this.ifNoData = false;
+ this.ifLoading = false;
+ this.ifError = true;
+ },
+ )
+ .catch(() => {
+ this.ifNoData = false;
+ this.ifLoading = false;
+ this.ifError = true;
+ });
},
/**
* The complete logic of table update when any condition changed
* @param {Object} params The main parameters
* @param {Object} supParams The supplymentary parameters
+ * @return {Promise}
*/
updateTable(params, supParams) {
const paramsTemp = JSON.parse(JSON.stringify(params));
@@ -496,50 +588,64 @@ export default {
}
}
Object.assign(paramsTemp, supParams);
- this.queryPageInfo(paramsTemp);
+ return this.queryPageInfo(paramsTemp);
},
/**
* Request page table information
* @param {Object} params Parameters of the request page information interface
+ * @return {Promise}
*/
queryPageInfo(params) {
params.train_id = decodeURIComponent(params.train_id);
this.queryParameters = params;
- requestService
- .queryPageInfo(params)
- .then(
- (res) => {
- // Make sure the offset of response is equal to offset of request
- if (params.offset === this.queryParameters.offset) {
- if (res && res.data && res.data.samples) {
- if (this.minConfidence === '--') {
- this.tableData = this.processTableData(res.data.samples, false);
- this.pageInfo.total =
- res.data.count !== undefined ? res.data.count : 0;
+ return new Promise((resolve) => {
+ requestService
+ .queryPageInfo(params)
+ .then(
+ (res) => {
+ // Make sure the offset of response is equal to offset of request
+ if (params.offset === this.queryParameters.offset) {
+ if (res && res.data && res.data.samples) {
+ this.tableData = this.processTableData(res.data.samples);
+ this.pageInfo.total = res.data.count ? res.data.count : 0;
+ if (!res.data.count) {
+ // 3: Prediction type has three valid values
+ // When length === 3 || length === 0, means search without type limit
+ const typeLimit =
+ params.prediction_types.length !== 3 &&
+ params.prediction_types.length !== 0;
+ if (params.labels || typeLimit) {
+ // With label or type limit
+ this.emptyText = this.$t('public.noData');
+ } else {
+ // Without limit
+ if (this.requestTime === this.requestLimit) {
+ this.emptyText = this.$t('public.noData');
+ } else {
+ this.emptyText = this.$t('explain.noData');
+ }
+ }
+ resolve(false);
+ } else {
+ resolve(true);
+ }
} else {
- this.tableData = this.processTableData(
- res.data.samples,
- this.minConfidence,
- );
- this.pageInfo.total =
- res.data.count !== undefined ? res.data.count : 0;
+ this.pageInfo.total = 0;
}
- } else {
- this.pageInfo.total = 0;
}
- this.ifError = false;
- this.ifTableLoading = false;
- }
- },
- (error) => {
- this.ifError = true;
- this.ifTableLoading = true;
- },
- )
- .catch((e) => {
- this.ifError = true;
- this.ifTableLoading = true;
- });
+ },
+ () => {
+ this.ifNoData = false;
+ this.ifLoading = false;
+ this.ifError = true;
+ },
+ )
+ .catch(() => {
+ this.ifNoData = false;
+ this.ifLoading = false;
+ this.ifError = true;
+ });
+ });
},
/**
* Process the original table data
@@ -548,43 +654,17 @@ export default {
* If min confidence is lost, replace with type except number, such as 'false', 'null'
* @return {Object} The processed table data
*/
- processTableData(samples, minConfidence) {
+ processTableData(samples) {
for (let i = 0; i < samples.length; i++) {
samples[i].activeLabelIndex = 0;
if (samples[i].inferences) {
for (let j = 0; j < samples[i].inferences.length; j++) {
- if (
- typeof minConfidence === 'number' &&
- typeof samples[i].inferences[j].confidence === 'number'
- ) {
- // Model Inference Result
- const MIR =
- samples[i].inferences[j].confidence * 100 >=
- minConfidence * 100;
- let labelValid;
- // The label if valid, judged by whether it exists in the truth labels
- if (samples[i].labels && samples[i].inferences[j].label) {
- labelValid =
- samples[i].labels.indexOf(samples[i].inferences[j].label) >=
- 0;
- } else {
- labelValid = false;
+ if (!samples[i].inferences[j].prediction_type) {
+ // When prediction type is null, prediction type filter is useless
+ if (this.hasPrediction) {
+ this.hasPrediction = false;
}
- if (MIR) {
- if (labelValid) {
- samples[i].inferences[j].type = 'tp';
- } else {
- samples[i].inferences[j].type = 'fp';
- }
- } else {
- if (labelValid) {
- samples[i].inferences[j].type = 'fn';
- } else {
- samples[i].inferences[j].type = 'tn';
- }
- }
- } else {
- samples[i].inferences[j].type = 'none';
+ samples[i].inferences[j].prediction_type = 'none';
}
// Defined the attr{key: explainer, value: overlay} out the saliencies
// Can provide some convenience for some table operation
@@ -655,6 +735,25 @@ export default {
query: {id: this.trainID},
});
},
+ initPage() {
+ return new Promise((resolve) => {
+ this.updateTable(this.baseQueryParameters, {
+ limit: this.pageInfo.pageSize,
+ offset: this.pageInfo.currentPage - 1,
+ }).then((hasData) => {
+ // If has data now
+ if (hasData) {
+ const params = {
+ train_id: this.trainID,
+ };
+ this.queryTrainInfo(params);
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ });
+ });
+ },
},
created() {
if (!this.$route.query.id) {
@@ -663,24 +762,28 @@ export default {
return;
}
this.trainID = this.$route.query.id;
- const params = {
- train_id: this.trainID,
- };
- this.queryTrainInfo(params)
- .then(
- (res) => {
- this.updateTable(this.baseQueryParameters, {
- limit: this.pageInfo.pageSize,
- offset: this.pageInfo.currentPage - 1,
- });
- },
- (error) => {
- this.ifError = true;
- },
- )
- .catch((e) => {
- this.ifError = true;
- });
+ this.initPage().then((hasData) => {
+ this.requestTime = 1;
+ if (!hasData) {
+ this.requestTimer = setInterval(() => {
+ this.initPage().then((hasDataNow) => {
+ this.requestTime++;
+ if (hasDataNow) {
+ clearInterval(this.requestTimer);
+ } else {
+ if (this.requestTime === this.requestLimit) {
+ // Still has no data
+ this.ifLoading = false;
+ clearInterval(this.requestTimer);
+ }
+ }
+ });
+ }, this.requestDelay);
+ }
+ });
+ },
+ destroyed() {
+ clearInterval(this.requestTimer);
},
mounted() {
// Change the page title
@@ -700,6 +803,12 @@ export default {
font-weight: normal;
}
+.cl-saliency-map .el-checkbox {
+ margin-right: 16px;
+}
+.cl-saliency-map .el-checkbox__label {
+ padding-left: 8px;
+}
.cl-saliency-map .el-icon-info {
color: #6c7280;
}
@@ -714,15 +823,31 @@ export default {
.cl-saliency-map .el-checkbox__label {
color: #333333 !important;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .el-table__body .pic-cell .cell {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .el-table__body
+ .pic-cell
+ .cell {
text-overflow: clip;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .el-table__body .pic-cell .cell img {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .el-table__body
+ .pic-cell
+ .cell
+ img {
height: 250px;
width: 250px;
object-fit: contain;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .el-table__body .canvas-cell img {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .el-table__body
+ .canvas-cell
+ img {
cursor: pointer;
}
.cl-saliency-map .cl-saliency-map-table .table-data .el-table__body .cell {
@@ -734,18 +859,21 @@ export default {
max-width: 650px;
}
-.el-tooltip__popper .tooltip-container .cl-saliency-map-tip {
+.el-tooltip__popper .saliency-tooltip-container .cl-saliency-map-tip {
padding: 10px;
}
-.el-tooltip__popper .tooltip-container .cl-saliency-map-tip .tip-title {
+.el-tooltip__popper
+ .saliency-tooltip-container
+ .cl-saliency-map-tip
+ .tip-title {
font-size: 16px;
font-weight: bold;
}
-.el-tooltip__popper .tooltip-container .cl-saliency-map-tip .tip-part {
+.el-tooltip__popper .saliency-tooltip-container .cl-saliency-map-tip .tip-part {
line-height: 20px;
word-break: normal;
}
-.el-tooltip__popper .tooltip-container .tag-tip .tip-item {
+.el-tooltip__popper .saliency-tooltip-container .tag-tip .tip-item {
margin-bottom: 10px;
font-size: 12px;
color: #575d6c;
@@ -753,13 +881,16 @@ export default {
display: flex;
align-items: center;
}
-.el-tooltip__popper .tooltip-container .tag-tip .tip-item .tip-icon {
+.el-tooltip__popper .saliency-tooltip-container .tag-tip .tip-item .tip-icon {
margin-right: 4px;
}
-.el-tooltip__popper .tooltip-container .tag-tip .tip-item:last-of-type {
+.el-tooltip__popper
+ .saliency-tooltip-container
+ .tag-tip
+ .tip-item:last-of-type {
margin-bottom: 0px;
}
-.el-tooltip__popper .tooltip-container .tag-tip .tip-title {
+.el-tooltip__popper .saliency-tooltip-container .tag-tip .tip-title {
color: #333333;
}
@@ -800,50 +931,46 @@ export default {
text-decoration: underline;
}
.cl-saliency-map .cl-saliency-map-condition {
- padding: 0px 32px 21px 32px;
- height: 58px;
+ padding: 0px 32px 21px;
+ line-height: 37px;
display: flex;
align-items: center;
- justify-content: space-between;
border-bottom: 1px solid #e6ebf5;
+ flex-wrap: wrap;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-left {
- height: 100%;
+.cl-saliency-map .cl-saliency-map-condition .item-gap {
+ flex-grow: 1;
+}
+.cl-saliency-map .cl-saliency-map-condition .margin-left {
+ margin-left: 24px;
+}
+.cl-saliency-map .cl-saliency-map-condition .condition-item {
+ margin-right: 16px;
display: flex;
align-items: center;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-left .condition-item {
- margin-right: 16px;
- height: 100%;
+.cl-saliency-map .cl-saliency-map-condition .condition-item .item-children {
+ margin-right: 8px;
+}
+.cl-saliency-map .cl-saliency-map-condition .condition-item .selector {
+ margin-left: 20px;
display: flex;
align-items: center;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-left .condition-item .condition-button {
+.cl-saliency-map .cl-saliency-map-condition .condition-item .condition-button {
padding: 7px 15px;
border-radius: 2px;
border: 1px solid #00a5a7;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-left .condition-item .el-icon-info {
+.cl-saliency-map .cl-saliency-map-condition .condition-item .el-icon-info {
margin-right: 4px;
margin-left: 2px;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-left .search-select {
+.cl-saliency-map .cl-saliency-map-condition .search-select {
width: 200px;
height: 32px;
}
-.cl-saliency-map .cl-saliency-map-condition .condition-right {
- display: flex;
- align-items: center;
-}
-.cl-saliency-map .cl-saliency-map-condition .condition-right .condition-item {
- margin-right: 24px;
- display: flex;
- align-items: center;
-}
-.cl-saliency-map .cl-saliency-map-condition .condition-right .condition-item .item-children {
- margin-right: 12px;
-}
-.cl-saliency-map .cl-saliency-map-condition .condition-right .condition-item:last-of-type {
+.cl-saliency-map .cl-saliency-map-condition .condition-item:last-of-type {
margin-right: 0px;
}
.cl-saliency-map .cl-saliency-map-table {
@@ -870,33 +997,63 @@ export default {
display: flex;
flex-direction: column;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .center {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .center {
text-align: center;
}
.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag div,
.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag span {
font-size: 12px;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-title-true {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-title-true {
display: grid;
grid-template-columns: 35% 35% 30%;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-title-true .first {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-title-true
+ .first {
padding-left: 12px;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-title-false {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-title-false {
display: grid;
grid-template-columns: 20% 40% 40%;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content {
flex-grow: 1;
overflow-y: scroll;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content::-webkit-scrollbar {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content::-webkit-scrollbar {
width: 0px;
height: 0px;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item {
background-repeat: no-repeat;
background-position: 2px 0px;
box-sizing: border-box;
@@ -907,48 +1064,106 @@ export default {
border-radius: 3px;
margin-bottom: 6px;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item .first {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item
+ .first {
padding-left: 10px;
background-color: rgba(0, 0, 0, 0) !important;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item .more-action {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item
+ .more-action {
cursor: pointer;
text-decoration: underline;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item .content-label {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item
+ .content-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item-true {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item-true {
display: grid;
grid-template-columns: 35% 35% 30%;
align-items: center;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-content-item-false {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-content-item-false {
display: grid;
grid-template-columns: 20% 40% 40%;
align-items: center;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-active {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-active {
background-color: #00a5a7;
color: #ffffff;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content :hover {
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ :hover {
background-color: #00a5a7;
color: #ffffff;
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-tp {
- background-image: url("../../assets/images/explain-tp.svg");
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-tp {
+ background-image: url('../../assets/images/explain-tp.svg');
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-fn {
- background-image: url("../../assets/images/explain-fn.svg");
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-fn {
+ background-image: url('../../assets/images/explain-fn.svg');
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-fp {
- background-image: url("../../assets/images/explain-fp.svg");
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-fp {
+ background-image: url('../../assets/images/explain-fp.svg');
}
-.cl-saliency-map .cl-saliency-map-table .table-data .table-forecast-tag .tag-content .tag-tn {
- background-image: url("../../assets/images/explain-tn.svg");
+.cl-saliency-map
+ .cl-saliency-map-table
+ .table-data
+ .table-forecast-tag
+ .tag-content
+ .tag-tn {
+ background-image: url('../../assets/images/explain-tn.svg');
}
.cl-saliency-map .cl-saliency-map-pagination {
padding: 0 32px;
diff --git a/mindinsight/ui/src/views/explain/summary-list.vue b/mindinsight/ui/src/views/explain/summary-list.vue
index afed041e..af11b847 100644
--- a/mindinsight/ui/src/views/explain/summary-list.vue
+++ b/mindinsight/ui/src/views/explain/summary-list.vue
@@ -68,8 +68,24 @@ limitations under the License.
+
+
+
@@ -124,7 +140,7 @@ export default {
type: 0,
},
tableDom: null,
- operateWidth: localStorage.getItem('milang') === 'en-us' ? 230 : 145,
+ operateWidth: this.$store.state.language === 'en-us' ? 430 : 290,
};
},
computed: {},
@@ -224,7 +240,7 @@ export default {
*/
goToSaliencyMap(row) {
this.contextMenu.show = false;
- const trainId = encodeURIComponent(row.train_id);
+ const trainId = row.train_id;
this.$router.push({
path: '/explain/saliency-map',
@@ -232,6 +248,27 @@ export default {
});
},
+ /**
+ * go to Profiler
+ * @param {Object} row select row
+ */
+ goToConterfactualinterpretation(row) {
+ this.contextMenu.show = false;
+ const profilerDir = row.profiler_dir;
+ const trainId = row.train_id;
+ const path = row.relative_path;
+ const router = '/explain/conterfactual-interpretation';
+
+ this.$router.push({
+ path: router,
+ query: {
+ dir: profilerDir,
+ id: trainId,
+ path: path,
+ },
+ });
+ },
+
rightClick(row, event, type) {
const maxWidth = 175;
this.contextMenu.data = row;
@@ -243,31 +280,24 @@ export default {
this.contextMenu.show = true;
},
- doRightClick(key) {
+ doRightClick() {
const row = this.contextMenu.data;
if (!row) {
return;
}
+ this.contextMenu.show = false;
+ const trainId = row.train_id;
if (this.contextMenu.type) {
- this.contextMenu.show = false;
- const profilerDir = encodeURIComponent(row.profiler_dir);
- const trainId = encodeURIComponent(row.train_id);
- const path = encodeURIComponent(row.relative_path);
const router = '/explain/conterfactual-interpretation';
const routeUrl = this.$router.resolve({
path: router,
query: {
- dir: profilerDir,
id: trainId,
- path: path,
},
});
window.open(routeUrl.href, '_blank');
} else {
- this.contextMenu.show = false;
- const trainId = encodeURIComponent(row.train_id);
-
const routeUrl = this.$router.resolve({
path: '/explain/saliency-map',
query: {id: trainId},