| @@ -1,3 +1,3 @@ | |||
| [submodule "third_party/securec"] | |||
| path = third_party/securec | |||
| url = https://gitee.com/openeuler/bounds_checking_function.git | |||
| url = https://gitee.com/openeuler/libboundscheck.git | |||
| @@ -68,6 +68,11 @@ training process, such as loss value and accuracy rate of each iteration. | |||
| Two scalar curves can be combined and displayed in one chart. | |||
| ### Parameter distribution | |||
| The GUI of MindInsight displays the distribution change tendency of a tensor such as weight | |||
| or gradient during the entire training process. | |||
| ### Image visualization | |||
| The GUI of MindInsight displays both original images and enhanced images during the entire | |||
| @@ -1,5 +1,28 @@ | |||
| ## MindInsight | |||
| # Release 0.2.0-alpha | |||
| ## Major Features and Improvements | |||
| * Parameter distribution graph (Histogram). | |||
| Now you can use [`HistogramSummary`](https://www.mindspore.cn/api/zh-CN/master/api/python/mindspore/mindspore.ops.operations.html#mindspore.ops.operations.HistogramSummary) and MindInsight to record and visualize distribution info of tensors. See our [tutorial](https://www.mindspore.cn/tutorial/zh-CN/master/advanced_use/visualization_tutorials.html) for details. | |||
| * Lineage support Custom information | |||
| * GPU support | |||
| * Model and dataset tracking linkage support | |||
| ## Bugfixes | |||
| * Reduce cyclomatic complexity of `list_summary_directories` ([!11](https://gitee.com/mindspore/mindinsight/pulls/11)). | |||
| * Fix unsafe functions and duplication files and redundant codes ([!14](https://gitee.com/mindspore/mindinsight/pulls/14)). | |||
| * Fix sha256 checksum missing bug ([!24](https://gitee.com/mindspore/mindinsight/pulls/24)). | |||
| * Fix graph bug when node name is empty ([!34](https://gitee.com/mindspore/mindinsight/pulls/34)). | |||
| * Fix start/stop command exit-code incorrect ([!44](https://gitee.com/mindspore/mindinsight/pulls/44)). | |||
| ## Thanks to our Contributors | |||
| Thanks goes to these wonderful people: | |||
| Ye Huang, Weifeng Huang, Zhenzhong Kou, Pengting Luo, Hongzhang Li, Yongxiong Liang, Gongchang Ou, Hui Pan, Luyu Qiu, Junyan Qin, Kai Wen, Weining Wang, Yifan Xia, Yunshu Zhang, Ting Zhao | |||
| Contributions of any kind are welcome! | |||
| # Release 0.1.0-alpha | |||
| * Training process observation | |||
| @@ -23,13 +23,13 @@ from mindinsight.conf import settings | |||
| # Type of the tensor event from external component | |||
| _Tensor = collections.namedtuple('_Tensor', ['wall_time', 'step', 'value']) | |||
| _Tensor = collections.namedtuple('_Tensor', ['wall_time', 'step', 'value', 'filename']) | |||
| TensorEvent = collections.namedtuple( | |||
| 'TensorEvent', ['wall_time', 'step', 'tag', 'plugin_name', 'value']) | |||
| 'TensorEvent', ['wall_time', 'step', 'tag', 'plugin_name', 'value', 'filename']) | |||
| # config for `EventsData` | |||
| _DEFAULT_STEP_SIZES_PER_TAG = settings.DEFAULT_STEP_SIZES_PER_TAG | |||
| _MAX_DELETED_TAGS_SIZE = settings.MAX_TAG_SIZE_PER_EVENTS_DATA * 100 | |||
| CONFIG = { | |||
| 'max_total_tag_sizes': settings.MAX_TAG_SIZE_PER_EVENTS_DATA, | |||
| 'max_tag_sizes_per_plugin': | |||
| @@ -60,6 +60,7 @@ class EventsData: | |||
| self._max_step_sizes_per_tag = self._config['max_step_sizes_per_tag'] | |||
| self._tags = list() | |||
| self._deleted_tags = set() | |||
| self._reservoir_by_tag = {} | |||
| self._reservoir_mutex_lock = threading.Lock() | |||
| @@ -82,6 +83,8 @@ class EventsData: | |||
| if tag not in set(self._tags): | |||
| deleted_tag = self._check_tag_out_of_spec(plugin_name) | |||
| if deleted_tag is not None: | |||
| if tag in self._deleted_tags: | |||
| return | |||
| self.delete_tensor_event(deleted_tag) | |||
| self._tags.append(tag) | |||
| @@ -99,10 +102,11 @@ class EventsData: | |||
| tensor = _Tensor(wall_time=tensor_event.wall_time, | |||
| step=tensor_event.step, | |||
| value=tensor_event.value) | |||
| value=tensor_event.value, | |||
| filename=tensor_event.filename) | |||
| if self._is_out_of_order_step(tensor_event.step, tensor_event.tag): | |||
| self.purge_reservoir_data(tensor_event.step, self._reservoir_by_tag[tag]) | |||
| self.purge_reservoir_data(tensor_event.filename, tensor_event.step, self._reservoir_by_tag[tag]) | |||
| self._reservoir_by_tag[tag].add_sample(tensor) | |||
| @@ -113,6 +117,8 @@ class EventsData: | |||
| Args: | |||
| tag (str): The tag name. | |||
| """ | |||
| if len(self._deleted_tags) < _MAX_DELETED_TAGS_SIZE: | |||
| self._deleted_tags.add(tag) | |||
| self._tags.remove(tag) | |||
| for plugin_name, lock in self._tags_by_plugin_mutex_lock.items(): | |||
| with lock: | |||
| @@ -176,7 +182,7 @@ class EventsData: | |||
| return False | |||
| @staticmethod | |||
| def purge_reservoir_data(start_step, tensor_reservoir): | |||
| def purge_reservoir_data(filename, start_step, tensor_reservoir): | |||
| """ | |||
| Purge all tensor event that are out-of-order step after the given start step. | |||
| @@ -188,7 +194,8 @@ class EventsData: | |||
| Returns: | |||
| int, the number of items removed. | |||
| """ | |||
| cnt_out_of_order = tensor_reservoir.remove_sample(lambda x: x.step < start_step) | |||
| cnt_out_of_order = tensor_reservoir.remove_sample( | |||
| lambda x: x.step < start_step or (x.step > start_step and x.filename == filename)) | |||
| return cnt_out_of_order | |||
| @@ -72,6 +72,10 @@ class Bucket: | |||
| class HistogramContainer: | |||
| # Max quantity of original buckets. | |||
| MAX_ORIGINAL_BUCKETS_COUNT = 90 | |||
| """ | |||
| Histogram data container. | |||
| @@ -114,6 +118,11 @@ class HistogramContainer: | |||
| """Gets original proto message.""" | |||
| return self._msg | |||
| @property | |||
| def original_buckets_count(self): | |||
| """Gets original buckets quantity.""" | |||
| return len(self._original_buckets) | |||
| def set_visual_range(self, max_val: float, min_val: float, bins: int) -> None: | |||
| """ | |||
| Sets visual range for later re-sampling. | |||
| @@ -223,7 +223,8 @@ class MSDataLoader: | |||
| step=event.step, | |||
| tag=tag, | |||
| plugin_name=PluginNameEnum.SCALAR.value, | |||
| value=value.scalar_value) | |||
| value=value.scalar_value, | |||
| filename=self._latest_summary_filename) | |||
| self._events_data.add_tensor_event(tensor_event) | |||
| if value.HasField('image'): | |||
| @@ -232,18 +233,25 @@ class MSDataLoader: | |||
| step=event.step, | |||
| tag=tag, | |||
| plugin_name=PluginNameEnum.IMAGE.value, | |||
| value=value.image) | |||
| value=value.image, | |||
| filename=self._latest_summary_filename) | |||
| self._events_data.add_tensor_event(tensor_event) | |||
| if value.HasField('histogram'): | |||
| histogram_msg = HistogramContainer(value.histogram) | |||
| tag = '{}/{}'.format(value.tag, PluginNameEnum.HISTOGRAM.value) | |||
| tensor_event = TensorEvent(wall_time=event.wall_time, | |||
| step=event.step, | |||
| tag=tag, | |||
| plugin_name=PluginNameEnum.HISTOGRAM.value, | |||
| value=histogram_msg) | |||
| self._events_data.add_tensor_event(tensor_event) | |||
| # Drop steps if original_buckets_count exceeds HistogramContainer.MAX_ORIGINAL_BUCKETS_COUNT | |||
| # to avoid time-consuming re-sample process. | |||
| if histogram_msg.original_buckets_count > HistogramContainer.MAX_ORIGINAL_BUCKETS_COUNT: | |||
| logger.warning('original_buckets_count exceeds HistogramContainer.MAX_ORIGINAL_BUCKETS_COUNT') | |||
| else: | |||
| tag = '{}/{}'.format(value.tag, PluginNameEnum.HISTOGRAM.value) | |||
| tensor_event = TensorEvent(wall_time=event.wall_time, | |||
| step=event.step, | |||
| tag=tag, | |||
| plugin_name=PluginNameEnum.HISTOGRAM.value, | |||
| value=histogram_msg, | |||
| filename=self._latest_summary_filename) | |||
| self._events_data.add_tensor_event(tensor_event) | |||
| if event.HasField('graph_def'): | |||
| graph_proto = event.graph_def | |||
| @@ -253,7 +261,8 @@ class MSDataLoader: | |||
| step=event.step, | |||
| tag=self._latest_summary_filename, | |||
| plugin_name=PluginNameEnum.GRAPH.value, | |||
| value=graph) | |||
| value=graph, | |||
| filename=self._latest_summary_filename) | |||
| try: | |||
| graph_tags = self._events_data.list_tags_by_plugin(PluginNameEnum.GRAPH.value) | |||
| @@ -436,7 +445,8 @@ class _PbParser: | |||
| step=0, | |||
| tag=filename, | |||
| plugin_name=PluginNameEnum.GRAPH.value, | |||
| value=graph) | |||
| value=graph, | |||
| filename=filename) | |||
| logger.info("Build graph success, file path: %s.", file_path) | |||
| return tensor_event | |||
| @@ -23,6 +23,24 @@ from mindinsight.utils.exceptions import ParamValueError | |||
| from mindinsight.datavisual.utils.utils import calc_histogram_bins | |||
| def binary_search(samples, target): | |||
| """Binary search target in samples.""" | |||
| left = 0 | |||
| right = len(samples) - 1 | |||
| while left <= right: | |||
| mid = (left + right) // 2 | |||
| if target < samples[mid].step: | |||
| right = mid - 1 | |||
| elif target > samples[mid].step: | |||
| left = mid + 1 | |||
| else: | |||
| return mid | |||
| # if right is -1, it is less than the first one. | |||
| # if list is [1, 2, 4], target is 3, right will be 1, so wo will insert by 2. | |||
| return right + 1 | |||
| class Reservoir: | |||
| """ | |||
| A container based on Reservoir Sampling algorithm. | |||
| @@ -68,18 +86,28 @@ class Reservoir: | |||
| """ | |||
| with self._mutex: | |||
| if len(self._samples) < self._samples_max_size or self._samples_max_size == 0: | |||
| self._samples.append(sample) | |||
| self._add_sample(sample) | |||
| else: | |||
| # Use the Reservoir Sampling algorithm to replace the old sample. | |||
| rand_int = self._sample_selector.randint( | |||
| 0, self._sample_counter) | |||
| rand_int = self._sample_selector.randint(0, self._sample_counter) | |||
| if rand_int < self._samples_max_size: | |||
| self._samples.pop(rand_int) | |||
| self._samples.append(sample) | |||
| else: | |||
| self._samples[-1] = sample | |||
| self._samples = self._samples[:-1] | |||
| self._add_sample(sample) | |||
| self._sample_counter += 1 | |||
| def _add_sample(self, sample): | |||
| """Search the index and add sample.""" | |||
| if not self._samples or sample.step > self._samples[-1].step: | |||
| self._samples.append(sample) | |||
| return | |||
| index = binary_search(self._samples, sample.step) | |||
| if index == len(self._samples): | |||
| self._samples.append(sample) | |||
| else: | |||
| self._samples.insert(index, sample) | |||
| def remove_sample(self, filter_fun): | |||
| """ | |||
| Remove the samples from Reservoir that do not meet the filter criteria. | |||
| @@ -288,8 +288,8 @@ class Querier: | |||
| try: | |||
| cmp_result = (value1 > value2) - (value1 < value2) | |||
| except TypeError: | |||
| type1 = str(type(value1)) | |||
| type2 = str(type(value2)) | |||
| type1 = type(value1).__name__ | |||
| type2 = type(value2).__name__ | |||
| cmp_result = (type1 > type2) - (type1 < type2) | |||
| return cmp_result | |||
| @@ -314,19 +314,7 @@ class Querier: | |||
| offset_results = self._handle_limit_and_offset(condition, results) | |||
| customized = dict() | |||
| for offset_result in offset_results: | |||
| for obj_name in ["metric", "user_defined"]: | |||
| obj = getattr(offset_result, obj_name) | |||
| require = bool(obj_name == "metric") | |||
| if obj and isinstance(obj, dict): | |||
| for key, value in obj.items(): | |||
| label = f'{obj_name}/{key}' | |||
| customized[label] = dict() | |||
| customized[label]["label"] = label | |||
| # user defined info is not displayed by default | |||
| customized[label]["required"] = require | |||
| customized[label]["type"] = type(value).__name__ | |||
| customized = self._organize_customized(offset_results) | |||
| lineage_types = condition.get(ConditionParam.LINEAGE_TYPE.value) | |||
| lineage_types = self._get_lineage_types(lineage_types) | |||
| @@ -348,6 +336,41 @@ class Querier: | |||
| return lineage_info | |||
| def _organize_customized(self, offset_results): | |||
| """Organize customized.""" | |||
| customized = dict() | |||
| for offset_result in offset_results: | |||
| for obj_name in ["metric", "user_defined"]: | |||
| self._organize_customized_item(customized, offset_result, obj_name) | |||
| # If types contain numbers and string, it will be "mixed". | |||
| # If types contain "int" and "float", it will be "float". | |||
| for key, value in customized.items(): | |||
| types = value["type"] | |||
| if len(types) == 1: | |||
| customized[key]["type"] = list(types)[0] | |||
| elif types.issubset(["int", "float"]): | |||
| customized[key]["type"] = "float" | |||
| else: | |||
| customized[key]["type"] = "mixed" | |||
| return customized | |||
| def _organize_customized_item(self, customized, offset_result, obj_name): | |||
| """Organize customized item.""" | |||
| obj = getattr(offset_result, obj_name) | |||
| require = bool(obj_name == "metric") | |||
| if obj and isinstance(obj, dict): | |||
| for key, value in obj.items(): | |||
| label = f'{obj_name}/{key}' | |||
| current_type = type(value).__name__ | |||
| if customized.get(label) is None: | |||
| customized[label] = dict() | |||
| customized[label]["label"] = label | |||
| # user defined info is not displayed by default | |||
| customized[label]["required"] = require | |||
| customized[label]["type"] = set() | |||
| customized[label]["type"].add(current_type) | |||
| def _get_lineage_types(self, lineage_type_param): | |||
| """ | |||
| Get lineage types. | |||
| @@ -70,13 +70,13 @@ def _package_current_dataset(operation, message): | |||
| message (Operation): Operation proto message. | |||
| """ | |||
| for key, value in operation.items(): | |||
| if key == "operations": | |||
| if value and key == "operations": | |||
| for operator in value: | |||
| _package_enhancement_operation( | |||
| operator, | |||
| message.operations.add() | |||
| ) | |||
| elif key == "sampler": | |||
| elif value and key == "sampler": | |||
| _package_enhancement_operation( | |||
| value, | |||
| message.sampler | |||
| @@ -93,7 +93,6 @@ def _package_enhancement_operation(operation, message): | |||
| operation (dict): Enhancement operation. | |||
| message (Operation): Enhancement operation proto message. | |||
| """ | |||
| for key, value in operation.items(): | |||
| if isinstance(value, list): | |||
| if all(isinstance(ele, int) for ele in value): | |||
| @@ -21,7 +21,7 @@ limitations under the License. | |||
| <meta charset="utf-8" /> | |||
| <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1.0" /> | |||
| <link rel="icon" href="<%= BASE_URL %>/static/img/favicon.ico" /> | |||
| <link rel="icon" href="<%= BASE_URL %>/static/img/favicon.png" /> | |||
| <title>MindInsight</title> | |||
| <style> | |||
| .errorInfo { | |||
| @@ -96,4 +96,14 @@ export default { | |||
| '.edge.highlighted path {stroke: red;}.edge.highlighted polygon {' + | |||
| 'stroke: red;fill: red;}' + | |||
| '.edge.highlighted marker path {fill: red;}</style>', | |||
| dataMapDownloadStyle: '<style> #graph0 > polygon { fill: transparent; }' + | |||
| '.node, .cluster { cursor: pointer; }' + | |||
| '.selected { polygon, ellipse { stroke: red !important; stroke-width: 2px; } }' + | |||
| '.CreatDataset > polygon, .Operator > ellipse { stroke: #58a4e0; fill: #d1ebff; }' + | |||
| '.cluster > polygon { fill: #c1f5d5; stroke: #56b077; }' + | |||
| '.RepeatDataset > polygon { stroke: #fdca5a; fill: #fff2d4; }' + | |||
| '.ShuffleDataset > polygon { stroke: #f79666; fill: #fed78e; }' + | |||
| '.BatchDataset > polygon { stroke: #fa8e5a; fill: #ffcfb8; }' + | |||
| '.edge { path { stroke: rgb(167, 167, 167); }' + | |||
| 'polygon { fill: rgb(167, 167, 167); stroke: rgb(167, 167, 167); } }</style>', | |||
| }; | |||
| @@ -99,6 +99,7 @@ export default { | |||
| multiSelectedItemNames: {}, // Dictionary for storing the name of the selected tags. | |||
| operateSelectAll: true, // Indicates whether to select all tags. | |||
| perSelectItemMarginBottom: 1, // Outer margin of the bottom of each selection box. | |||
| searching: false, | |||
| }; | |||
| }, | |||
| computed: {}, | |||
| @@ -156,11 +157,13 @@ export default { | |||
| * Tag Filter | |||
| */ | |||
| listFilter() { | |||
| this.searching = true; | |||
| if (this.searchInputTimer) { | |||
| clearTimeout(this.searchInputTimer); | |||
| this.searchInputTimer = null; | |||
| } | |||
| this.searchInputTimer = setTimeout(() => { | |||
| this.searching = false; | |||
| let reg; | |||
| try { | |||
| reg = new RegExp(this.searchInput); | |||
| @@ -234,6 +237,9 @@ export default { | |||
| * @return {Object} Dictionary containing selected tags | |||
| */ | |||
| updateSelectedDic() { | |||
| if (this.searching) { | |||
| return this.multiSelectedItemNames; | |||
| } | |||
| let reg; | |||
| try { | |||
| reg = new RegExp(this.searchInput); | |||
| @@ -45,19 +45,20 @@ | |||
| "learningRate": "学习率", | |||
| "modelSize": "模型大小", | |||
| "dataProcess": "数据处理", | |||
| "noDataFound":"暂无满足筛选条件的数据", | |||
| "noDataTips":"请点击“显示全量数据”按钮查看全量数据", | |||
| "noDataFound": "暂无满足筛选条件的数据", | |||
| "noDataTips": "请点击“显示全量数据”按钮查看全量数据", | |||
| "userDefined": "自定义数据", | |||
| "metric": "度量指标", | |||
| "deviceNum": "device数目" | |||
| "deviceNum": "device数目", | |||
| "mixedItemMessage": "该参数含有多种类型数据,无法筛选展示" | |||
| }, | |||
| "dataTraceback": { | |||
| "details": "详情", | |||
| "key": "KEY", | |||
| "value": "VALUE", | |||
| "dataTraceTips": "该数据涉及合并操作", | |||
| "noDataFound":"暂无满足筛选条件的数据", | |||
| "noDataTips":"请点击“显示全量数据”按钮查看全量数据" | |||
| "noDataFound": "暂无满足筛选条件的数据", | |||
| "noDataTips": "请点击“显示全量数据”按钮查看全量数据" | |||
| }, | |||
| "trainingDashboard": { | |||
| "trainingDashboardTitle": "训练看板", | |||
| @@ -66,7 +67,8 @@ | |||
| "trainingScalar": "训练标量信息", | |||
| "samplingData": "数据抽样", | |||
| "imagesampleSwitch": "切换标签", | |||
| "invalidId": "无效的训练作业" | |||
| "invalidId": "无效的训练作业", | |||
| "summaryDirPath": "summary路径:" | |||
| }, | |||
| "scalar": { | |||
| "titleText": "标量", | |||
| @@ -120,6 +122,7 @@ | |||
| "graph": { | |||
| "titleText": "计算图", | |||
| "downloadPic": "下载", | |||
| "fitScreen": "适应屏幕", | |||
| "nodeInfo": "节点信息", | |||
| "legend": "图例", | |||
| "nameSpace": "命名空间", | |||
| @@ -46,6 +46,12 @@ limitations under the License. | |||
| <div :title="$t('graph.fullScreen')" | |||
| class="full-screen-button" | |||
| @click="fullScreen = !fullScreen"></div> | |||
| <div :title="$t('graph.fitScreen')" | |||
| class="fit-screen" | |||
| @click="fit()"></div> | |||
| <div :title="$t('graph.downloadPic')" | |||
| class="download-button" | |||
| @click="downLoadSVG"></div> | |||
| </div> | |||
| <!-- Right column --> | |||
| <div id="sidebar" | |||
| @@ -138,6 +144,7 @@ limitations under the License. | |||
| <script> | |||
| import RequestService from '../../services/request-service'; | |||
| import CommonProperty from '@/common/common-property.js'; | |||
| import {select, selectAll, zoom} from 'd3'; | |||
| import 'd3-graphviz'; | |||
| const d3 = {select, selectAll, zoom}; | |||
| @@ -340,6 +347,7 @@ export default { | |||
| * Initialization method executed after the graph rendering is complete | |||
| */ | |||
| startApp() { | |||
| this.initZooming(); | |||
| const nodes = d3.selectAll('g.node, g.cluster'); | |||
| nodes.on('click', (target, index, nodesList) => { | |||
| this.selectNodeInfo(target); | |||
| @@ -347,9 +355,109 @@ export default { | |||
| nodes.classed('selected', false); | |||
| d3.select(`g[id="${selectedNode.id}"]`).classed('selected', true); | |||
| }); | |||
| d3.select('svg').on('dblclick.zoom', null); | |||
| }, | |||
| /** | |||
| * Initializing the Zoom Function of a Graph | |||
| */ | |||
| initZooming() { | |||
| const graphDom = document.querySelector('#graph0'); | |||
| const graphBox = graphDom.getBBox(); | |||
| const padding = 4; | |||
| const pointer = { | |||
| start: { | |||
| x: 0, | |||
| y: 0, | |||
| }, | |||
| end: { | |||
| x: 0, | |||
| y: 0, | |||
| }, | |||
| }; | |||
| const zoom = d3 | |||
| .zoom() | |||
| .on('start', (target) => { | |||
| pointer.start.x = event.x; | |||
| pointer.start.y = event.y; | |||
| }) | |||
| .on('zoom', (target) => { | |||
| const transformData = this.getTransformData(graphDom); | |||
| let tempStr = ''; | |||
| let change = {}; | |||
| let scale = transformData.scale[0]; | |||
| const graphRect = graphDom.getBoundingClientRect(); | |||
| const mapping = { | |||
| width: graphBox.width / graphRect.width, | |||
| height: graphBox.height / graphRect.height, | |||
| }; | |||
| if (event.type === 'mousemove') { | |||
| pointer.end.x = event.x; | |||
| pointer.end.y = event.y; | |||
| change = { | |||
| x: (pointer.end.x - pointer.start.x) * mapping.width * scale, | |||
| y: (pointer.end.y - pointer.start.y) * mapping.height * scale, | |||
| }; | |||
| pointer.start.x = pointer.end.x; | |||
| pointer.start.y = pointer.end.y; | |||
| } else if (event.type === 'wheel') { | |||
| const wheelDelta = event.wheelDelta; | |||
| const rate = Math.abs(wheelDelta / 100); | |||
| scale = | |||
| wheelDelta > 0 | |||
| ? transformData.scale[0] * rate | |||
| : transformData.scale[0] / rate; | |||
| scale = Math.max(this.scaleRange[0], scale); | |||
| scale = Math.min(this.scaleRange[1], scale); | |||
| change = { | |||
| x: | |||
| (graphRect.x + padding / mapping.width - event.x) * | |||
| mapping.width * | |||
| (scale - transformData.scale[0]), | |||
| y: | |||
| (graphRect.bottom - padding / mapping.height - event.y) * | |||
| mapping.height * | |||
| (scale - transformData.scale[0]), | |||
| }; | |||
| } | |||
| tempStr = `translate(${transformData.translate[0] + | |||
| change.x},${transformData.translate[1] + | |||
| change.y}) scale(${scale})`; | |||
| graphDom.setAttribute('transform', tempStr); | |||
| }); | |||
| const svg = d3.select('svg'); | |||
| svg.on('.zoom', null); | |||
| svg.call(zoom); | |||
| svg.on('dblclick.zoom', null); | |||
| }, | |||
| /** | |||
| * Obtains the transform data of a node. | |||
| * @param {Object} node Node dom data | |||
| * @return {Object} transform data of a node | |||
| */ | |||
| getTransformData(node) { | |||
| if (!node) { | |||
| return []; | |||
| } | |||
| const transformData = node.getAttribute('transform'); | |||
| const attrObj = {}; | |||
| if (transformData) { | |||
| const lists = transformData.trim().split(' '); | |||
| lists.forEach((item) => { | |||
| const index1 = item.indexOf('('); | |||
| const index2 = item.indexOf(')'); | |||
| const name = item.substring(0, index1); | |||
| const params = item | |||
| .substring(index1 + 1, index2) | |||
| .split(',') | |||
| .map((i) => { | |||
| return parseFloat(i) || 0; | |||
| }); | |||
| attrObj[name] = params; | |||
| }); | |||
| } | |||
| return attrObj; | |||
| }, | |||
| /** | |||
| * Process the selected node information. | |||
| * @param {Object} target Selected Object | |||
| @@ -397,6 +505,36 @@ export default { | |||
| }); | |||
| } | |||
| }, | |||
| /** | |||
| * Adapt to the screen | |||
| */ | |||
| fit() { | |||
| const graphDom = document.getElementById('graph0'); | |||
| const box = graphDom.getBBox(); | |||
| const str = `translate(${-box.x},${-box.y}) scale(1)`; | |||
| graphDom.setAttribute('transform', str); | |||
| }, | |||
| /** | |||
| * Download svg | |||
| */ | |||
| downLoadSVG() { | |||
| const svgXml = document.querySelector('#graph #graph0').innerHTML; | |||
| const bbox = document.getElementById('graph0').getBBox(); | |||
| const viewBoxSize = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`; | |||
| const encodeStr = | |||
| `<svg xmlns="http://www.w3.org/2000/svg" ` + | |||
| `xmlns:xlink="http://www.w3.org/1999/xlink" ` + | |||
| `width="${bbox.width}" height="${bbox.height}" ` + | |||
| `viewBox="${viewBoxSize}">${CommonProperty.dataMapDownloadStyle}<g>${svgXml}</g></svg>`; | |||
| // Write the svg stream encoded by base64 to the image object. | |||
| const src = `data:image/svg+xml;base64, | |||
| ${window.btoa(unescape(encodeURIComponent(encodeStr)))}`; | |||
| const a = document.createElement('a'); | |||
| a.href = src; // Export the information in the canvas as image data. | |||
| a.download = 'dataMap'; // Set the download name. | |||
| a.click(); // Click to trigger download. | |||
| }, | |||
| /** | |||
| * Collapse on the right | |||
| */ | |||
| @@ -515,6 +653,30 @@ export default { | |||
| display: inline-block; | |||
| background-image: url('../../assets/images/full-screen.png'); | |||
| } | |||
| .fit-screen { | |||
| position: absolute; | |||
| width: 16px; | |||
| height: 14px; | |||
| right: 32px; | |||
| top: 10px; | |||
| z-index: 999; | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| background-image: url('../../assets/images/fit.png'); | |||
| } | |||
| .download-button { | |||
| position: absolute; | |||
| width: 16px; | |||
| height: 14px; | |||
| right: 54px; | |||
| top: 10px; | |||
| z-index: 999; | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| background-image: url('../../assets/images/download.png'); | |||
| background-size: 14px 14px; | |||
| background-repeat: no-repeat; | |||
| } | |||
| } | |||
| } | |||
| .cl-data-map.full-screen { | |||
| @@ -509,6 +509,7 @@ export default { | |||
| }); | |||
| // The summaryList value could not be saved in the destroy state. | |||
| this.dataCheckedSummary = []; | |||
| this.tableFilter.summary_dir = {in: summaryList}; | |||
| this.$store.commit('setSummaryDirList', summaryList); | |||
| if (!tempList.length) { | |||
| this.summaryDirList = []; | |||
| @@ -967,6 +968,7 @@ export default { | |||
| sorted_name: data.prop, | |||
| sorted_type: data.order, | |||
| }; | |||
| this.pagination.currentPage = 1; | |||
| params.body = Object.assign({}, tempParam, this.tableFilter); | |||
| this.queryLineagesData(params); | |||
| }, | |||
| @@ -1010,6 +1010,10 @@ export default { | |||
| source = node.name; | |||
| for (let i = 0; i < Math.min(5, keys.length); i++) { | |||
| target = keys[i]; | |||
| isConst = !!( | |||
| this.allGraphData[keys[i]] && | |||
| this.allGraphData[keys[i]].type === 'Const' | |||
| ); | |||
| const nodeStr = isConst | |||
| ? `shape="circle";width="0.14";height="0.14";fixedsize=true;` + | |||
| `label="${target.split('/').pop()}\n\n\n";` | |||
| @@ -138,6 +138,8 @@ export default { | |||
| zrDrawElement: {hoverDots: []}, | |||
| chartTipFlag: false, | |||
| charResizeTimer: null, | |||
| changeAxisTimer: null, | |||
| changeViewTimer: null, | |||
| }; | |||
| }, | |||
| computed: { | |||
| @@ -220,6 +222,14 @@ export default { | |||
| this.$store.commit('setIsReload', false); | |||
| this.isReloading = false; | |||
| } | |||
| if (this.changeAxisTimer) { | |||
| clearTimeout(this.changeAxisTimer); | |||
| this.changeAxisTimer = null; | |||
| } | |||
| if (this.changeViewTimer) { | |||
| clearTimeout(this.changeViewTimer); | |||
| this.changeViewTimer = null; | |||
| } | |||
| }, | |||
| mounted() { | |||
| this.init(); | |||
| @@ -394,19 +404,31 @@ export default { | |||
| * @param {Number} val Current mode | |||
| */ | |||
| timeTypeChange(val) { | |||
| this.curPageArr.forEach((item) => { | |||
| this.updateSampleData(item); | |||
| }); | |||
| if (this.changeAxisTimer) { | |||
| clearTimeout(this.changeAxisTimer); | |||
| this.changeAxisTimer = null; | |||
| } | |||
| this.changeAxisTimer = setTimeout(() => { | |||
| this.curPageArr.forEach((item) => { | |||
| this.updateSampleData(item); | |||
| }); | |||
| }, 500); | |||
| }, | |||
| /** | |||
| * The view display type is changed | |||
| * @param {Number} val Current mode | |||
| */ | |||
| viewTypeChange(val) { | |||
| this.curPageArr.forEach((item) => { | |||
| this.formatDataToChar(item); | |||
| this.updateSampleData(item); | |||
| }); | |||
| if (this.changeViewTimer) { | |||
| clearTimeout(this.changeViewTimer); | |||
| this.changeViewTimer = null; | |||
| } | |||
| this.changeViewTimer = setTimeout(() => { | |||
| this.curPageArr.forEach((item) => { | |||
| this.formatDataToChar(item); | |||
| this.updateSampleData(item); | |||
| }); | |||
| }, 200); | |||
| }, | |||
| /** | |||
| * Update the data list based on the filtered tags | |||
| @@ -743,7 +765,7 @@ export default { | |||
| }</td><td style="text-align:center;">${hoveredItem.step}</td><td>${( | |||
| hoveredItem.relative_time / 1000 | |||
| ).toFixed(3)}${unit}</td><td>${this.dealrelativeTime( | |||
| new Date(hoveredItem.wall_time).toString(), | |||
| new Date(hoveredItem.wall_time * 1000).toString(), | |||
| )}</td>`; | |||
| const dom = document.querySelector('#tipTr'); | |||
| dom.innerHTML = htmlStr; | |||
| @@ -996,7 +1018,9 @@ export default { | |||
| const z = this.getValue(rawData, dataIndex, i++); | |||
| const pt = getCoord([x, y], sampleObject); | |||
| // linear map in z axis | |||
| pt[1] -= ((z - minZ) / (maxZ - minZ)) * yValueMapHeight; | |||
| if (maxZ !== minZ) { | |||
| pt[1] -= ((z - minZ) / (maxZ - minZ)) * yValueMapHeight; | |||
| } | |||
| points.push(pt); | |||
| } | |||
| return points; | |||
| @@ -1216,7 +1240,7 @@ export default { | |||
| if (filter.length) { | |||
| if (this.curAxisName === 2) { | |||
| data = sampleObject.fullScreen | |||
| ? this.dealrelativeTime(new Date(filter[0].wall_time).toString()) | |||
| ? this.dealrelativeTime(new Date(filter[0].wall_time * 1000).toString()) | |||
| : []; | |||
| } else if (this.curAxisName === 1) { | |||
| data = `${(filter[0].relative_time / 3600).toFixed(3)}h`; | |||
| @@ -151,6 +151,7 @@ export default { | |||
| 'batch_size', | |||
| 'device_num', | |||
| ], // All keys whose values are int | |||
| keysOfMixed: [], | |||
| echart: { | |||
| chart: null, | |||
| allData: [], | |||
| @@ -166,10 +167,7 @@ export default { | |||
| }, | |||
| chartFilter: {}, // chart filter condition | |||
| tableFilter: {lineage_type: {in: ['model']}}, // table filter condition | |||
| sortInfo: { | |||
| sorted_name: 'summary_dir', | |||
| sorted_type: null, | |||
| }, | |||
| sortInfo: {}, | |||
| showTable: false, | |||
| noData: false, | |||
| haveCustomizedParams: false, | |||
| @@ -281,6 +279,7 @@ export default { | |||
| selectAll: false, // Whether to select all columns | |||
| indeterminate: false, | |||
| }; | |||
| this.keysOfMixed = []; | |||
| this.queryLineagesData(true); | |||
| }, | |||
| /** | |||
| @@ -324,10 +323,15 @@ export default { | |||
| tempParam.limit = this.pagination.pageSize; | |||
| tempParam.offset = this.pagination.currentPage - 1; | |||
| params.body = Object.assign(params.body, this.chartFilter); | |||
| params.body = Object.assign( | |||
| params.body, | |||
| this.chartFilter, | |||
| tempParam, | |||
| this.tableFilter, | |||
| ); | |||
| } else { | |||
| params.body = Object.assign(params.body, this.tableFilter); | |||
| } | |||
| params.body = Object.assign(params.body, tempParam, this.tableFilter); | |||
| RequestService.queryLineagesData(params) | |||
| .then( | |||
| (res) => { | |||
| @@ -344,6 +348,10 @@ export default { | |||
| this.keysOfIntValue.push(i); | |||
| } else if (customized[i].type === 'str') { | |||
| this.keysOfStringValue.push(i); | |||
| } else if (customized[i].type === 'mixed') { | |||
| // list of type mixed | |||
| this.keysOfMixed.push(i); | |||
| this.keysOfStringValue.push(i); | |||
| } | |||
| if (i.startsWith(this.replaceStr.userDefined)) { | |||
| customized[i].label = customized[i].label.replace( | |||
| @@ -568,7 +576,16 @@ export default { | |||
| }; | |||
| chartAxis.forEach((key) => { | |||
| item.value.push(i[key]); | |||
| if ( | |||
| (i[key] || i[key] == 0) && | |||
| this.keysOfMixed && | |||
| this.keysOfMixed.length && | |||
| this.keysOfMixed.includes(key) | |||
| ) { | |||
| item.value.push(i[key].toString()); | |||
| } else { | |||
| item.value.push(i[key]); | |||
| } | |||
| }); | |||
| data.push(item); | |||
| }); | |||
| @@ -581,7 +598,7 @@ export default { | |||
| const values = {}; | |||
| this.echart.showData.forEach((i) => { | |||
| if (i[key] || i[key] === 0) { | |||
| values[i[key]] = i[key]; | |||
| values[i[key].toString()] = i[key].toString(); | |||
| } | |||
| }); | |||
| obj.type = 'category'; | |||
| @@ -693,6 +710,15 @@ export default { | |||
| // select use api | |||
| this.echart.chart.on('axisareaselected', (params) => { | |||
| const key = params.parallelAxisId; | |||
| if ( | |||
| this.keysOfMixed && | |||
| this.keysOfMixed.length && | |||
| this.keysOfMixed.includes(key) | |||
| ) { | |||
| this.$message.error(this.$t('modelTraceback.mixedItemMessage')); | |||
| this.initChart(); | |||
| return; | |||
| } | |||
| const list = this.$store.state.selectedBarList || []; | |||
| const selectedAxisId = params.parallelAxisId; | |||
| if (list.length) { | |||
| @@ -741,6 +767,12 @@ export default { | |||
| {}, | |||
| this.chartFilter, | |||
| this.tableFilter, | |||
| ); | |||
| const tableParams = {}; | |||
| tableParams.body = Object.assign( | |||
| {}, | |||
| this.chartFilter, | |||
| this.tableFilter, | |||
| this.sortInfo, | |||
| ); | |||
| RequestService.queryLineagesData(filterParams) | |||
| @@ -752,20 +784,47 @@ export default { | |||
| res.data.object && | |||
| res.data.object.length | |||
| ) { | |||
| let customized = {}; | |||
| customized = JSON.parse(JSON.stringify(res.data.customized)); | |||
| const customizedKeys = Object.keys(customized); | |||
| if (customizedKeys.length) { | |||
| this.keysOfStringValue = [ | |||
| 'summary_dir', | |||
| 'network', | |||
| 'optimizer', | |||
| 'loss_function', | |||
| 'train_dataset_path', | |||
| 'test_dataset_path', | |||
| 'dataset_mark', | |||
| ]; | |||
| this.keysOfIntValue = [ | |||
| 'train_dataset_count', | |||
| 'test_dataset_count', | |||
| 'epoch', | |||
| 'batch_size', | |||
| 'device_num', | |||
| ]; | |||
| this.keysOfMixed = []; | |||
| customizedKeys.forEach((i) => { | |||
| if (customized[i].type === 'int') { | |||
| this.keysOfIntValue.push(i); | |||
| } else if (customized[i].type === 'str') { | |||
| this.keysOfStringValue.push(i); | |||
| } else if (customized[i].type === 'mixed') { | |||
| // list of type mixed | |||
| this.keysOfMixed.push(i); | |||
| this.keysOfStringValue.push(i); | |||
| } | |||
| }); | |||
| } | |||
| const list = this.setDataOfModel(res.data.object); | |||
| const summaryDirList = list.map((i) => i.summary_dir); | |||
| this.$store.commit('setSummaryDirList', summaryDirList); | |||
| this.echart.showData = this.echart.brushData = list; | |||
| this.initChart(); | |||
| this.table.data = this.echart.brushData.slice( | |||
| 0, | |||
| this.pagination.pageSize, | |||
| ); | |||
| this.pagination.currentPage = 1; | |||
| this.pagination.total = this.echart.brushData.length; | |||
| this.$refs.table.clearSelection(); | |||
| this.getTableList(tableParams); | |||
| } else { | |||
| this.summaryDirList = []; | |||
| this.$store.commit('setSummaryDirList', []); | |||
| @@ -779,6 +838,26 @@ export default { | |||
| } | |||
| }); | |||
| }, | |||
| /** | |||
| * Get table data | |||
| * @param {Object} tableParams | |||
| */ | |||
| getTableList(tableParams) { | |||
| RequestService.queryLineagesData(tableParams) | |||
| .then( | |||
| (res) => { | |||
| if (res && res.data && res.data.object && res.data.object.length) { | |||
| const list = this.setDataOfModel(res.data.object); | |||
| this.table.data = list.slice(0, this.pagination.pageSize); | |||
| this.pagination.currentPage = 1; | |||
| this.pagination.total = this.echart.brushData.length; | |||
| this.$refs.table.clearSelection(); | |||
| } | |||
| }, | |||
| (error) => {}, | |||
| ) | |||
| .catch(() => {}); | |||
| }, | |||
| /** | |||
| * Resetting the Eechart | |||
| */ | |||
| @@ -790,6 +869,7 @@ export default { | |||
| this.showTable = false; | |||
| this.chartFilter = {}; | |||
| this.tableFilter.summary_dir = undefined; | |||
| this.sortInfo = {}; | |||
| this.pagination.currentPage = 1; | |||
| this.echart.allData = []; | |||
| if (this.echart.chart) { | |||
| @@ -34,10 +34,9 @@ limitations under the License. | |||
| <!--operation area --> | |||
| <div class="cl-eval-operate-content" | |||
| v-show="!compare"> | |||
| <multiselectGroupComponents ref="multiselectGroupComponents" | |||
| :checkListArr="tagOperateList" | |||
| @selectedChange="tagSelectedChanged" | |||
| ></multiselectGroupComponents> | |||
| <multiselectGroupComponents ref="multiselectGroupComponents" | |||
| :checkListArr="tagOperateList" | |||
| @selectedChange="tagSelectedChanged"></multiselectGroupComponents> | |||
| </div> | |||
| <!-- Slider --> | |||
| <div class="cl-eval-slider-operate-content" | |||
| @@ -56,14 +55,12 @@ limitations under the License. | |||
| <el-slider v-model="smoothValue" | |||
| :step="0.01" | |||
| :max="0.99" | |||
| @input="updataInputValue" | |||
| ></el-slider> | |||
| <el-input v-model="smoothValueNumber" | |||
| class="w60" | |||
| @input="smoothValueChange" | |||
| @blur="smoothValueBlur" | |||
| ></el-input> | |||
| @input="updataInputValue"></el-slider> | |||
| <el-input v-model="smoothValueNumber" | |||
| class="w60" | |||
| @input="smoothValueChange" | |||
| @blur="smoothValueBlur"></el-input> | |||
| </div> | |||
| <!-- Content display --> | |||
| <div class="cl-eval-show-data-content" | |||
| @@ -247,6 +244,10 @@ export default { | |||
| clearInterval(this.autoUpdateTimer); | |||
| this.autoUpdateTimer = null; | |||
| } | |||
| if (this.axisBenchChangeTimer) { | |||
| clearTimeout(this.axisBenchChangeTimer); | |||
| this.axisBenchChangeTimer = null; | |||
| } | |||
| }, | |||
| mounted() { | |||
| // Adding a Listener | |||
| @@ -290,7 +291,7 @@ export default { | |||
| const runNmeColor = CommonProperty.commonColorArr[0]; | |||
| data.tags.forEach((tagObj) => { | |||
| if (!this.oriDataDictionaries[tagObj]) { | |||
| this.oriDataDictionaries[tagObj]=true; | |||
| this.oriDataDictionaries[tagObj] = true; | |||
| // Add the tag list | |||
| tempTagList.push({ | |||
| label: tagObj, | |||
| @@ -337,7 +338,7 @@ export default { | |||
| this.initOver = true; | |||
| this.$nextTick(() => { | |||
| this.multiSelectedTagNames=this.$refs.multiselectGroupComponents.updateSelectedDic(); | |||
| this.multiSelectedTagNames = this.$refs.multiselectGroupComponents.updateSelectedDic(); | |||
| // Obtains data on the current page | |||
| this.updateTagInPage(); | |||
| @@ -365,10 +366,10 @@ export default { | |||
| const curPageArr = []; | |||
| for (let i = startIndex; i < endIndex; i++) { | |||
| const sampleItem=this.curFilterSamples[i]; | |||
| const sampleItem = this.curFilterSamples[i]; | |||
| if (sampleItem) { | |||
| sampleItem.updateFlag=true; | |||
| sampleItem.show=true; | |||
| sampleItem.updateFlag = true; | |||
| sampleItem.show = true; | |||
| curPageArr.push(sampleItem); | |||
| } | |||
| } | |||
| @@ -381,110 +382,110 @@ export default { | |||
| * Load the data on the current page | |||
| */ | |||
| updateCurPageSamples() { | |||
| this.curPageArr.forEach((sampleObject)=>{ | |||
| const sampleIndex=sampleObject.sampleIndex; | |||
| this.curPageArr.forEach((sampleObject) => { | |||
| const sampleIndex = sampleObject.sampleIndex; | |||
| if (!sampleObject) { | |||
| return; | |||
| } | |||
| sampleObject.updateFlag = true; | |||
| const params = { | |||
| train_id: this.trainingJobId, | |||
| tag: sampleObject.tagName, | |||
| }; | |||
| RequestService.getScalarsSample(params).then((res)=>{ | |||
| // error | |||
| if (!res || !res.data || !res.data.metadatas) { | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.clear(); | |||
| } | |||
| return; | |||
| } | |||
| let hasInvalidData = false; | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.showLoading(); | |||
| } | |||
| RequestService.getScalarsSample(params) | |||
| .then((res) => { | |||
| // error | |||
| if (!res || !res.data || !res.data.metadatas) { | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.clear(); | |||
| } | |||
| return; | |||
| } | |||
| let hasInvalidData = false; | |||
| const resData = res.data; | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.showLoading(); | |||
| } | |||
| const tempObject = { | |||
| valueData: { | |||
| stepData: [], | |||
| absData: [], | |||
| relativeData: [], | |||
| }, | |||
| logData: { | |||
| stepData: [], | |||
| absData: [], | |||
| relativeData: [], | |||
| }, | |||
| }; | |||
| let relativeTimeBench = 0; | |||
| if (resData.metadatas.length) { | |||
| relativeTimeBench = resData.metadatas[0].wall_time; | |||
| } | |||
| const resData = res.data; | |||
| const tempObject = { | |||
| valueData: { | |||
| stepData: [], | |||
| absData: [], | |||
| relativeData: [], | |||
| }, | |||
| logData: { | |||
| stepData: [], | |||
| absData: [], | |||
| relativeData: [], | |||
| }, | |||
| }; | |||
| let relativeTimeBench = 0; | |||
| if (resData.metadatas.length) { | |||
| relativeTimeBench = resData.metadatas[0].wall_time; | |||
| } | |||
| // Initializing Chart Data | |||
| resData.metadatas.forEach((metaData) => { | |||
| if (metaData.value === null && !hasInvalidData) { | |||
| hasInvalidData = true; | |||
| } | |||
| tempObject.valueData.stepData.push([ | |||
| metaData.step, | |||
| metaData.value, | |||
| ]); | |||
| tempObject.valueData.absData.push([ | |||
| metaData.wall_time, | |||
| metaData.value, | |||
| ]); | |||
| tempObject.valueData.relativeData.push([ | |||
| metaData.wall_time - relativeTimeBench, | |||
| metaData.value, | |||
| ]); | |||
| const logValue = metaData.value >= 0 ? metaData.value : ''; | |||
| tempObject.logData.stepData.push([metaData.step, logValue]); | |||
| tempObject.logData.absData.push([metaData.wall_time, logValue]); | |||
| tempObject.logData.relativeData.push([ | |||
| metaData.wall_time - relativeTimeBench, | |||
| logValue, | |||
| ]); | |||
| }); | |||
| // Initializing Chart Data | |||
| resData.metadatas.forEach((metaData) => { | |||
| if (metaData.value === null && !hasInvalidData) { | |||
| hasInvalidData = true; | |||
| } | |||
| tempObject.valueData.stepData.push([ | |||
| metaData.step, | |||
| metaData.value, | |||
| ]); | |||
| tempObject.valueData.absData.push([ | |||
| metaData.wall_time, | |||
| metaData.value, | |||
| ]); | |||
| tempObject.valueData.relativeData.push([ | |||
| metaData.wall_time - relativeTimeBench, | |||
| metaData.value, | |||
| ]); | |||
| const logValue = metaData.value >= 0 ? metaData.value : ''; | |||
| tempObject.logData.stepData.push([metaData.step, logValue]); | |||
| tempObject.logData.absData.push([metaData.wall_time, logValue]); | |||
| tempObject.logData.relativeData.push([ | |||
| metaData.wall_time - relativeTimeBench, | |||
| logValue, | |||
| ]); | |||
| }); | |||
| sampleObject.charData.oriData[0] = tempObject; | |||
| sampleObject.charData.oriData[0] = tempObject; | |||
| if (hasInvalidData) { | |||
| this.$set(sampleObject, 'invalidData', true); | |||
| } | |||
| sampleObject.charData.charOption = this.formateCharOption( | |||
| sampleIndex, | |||
| ); | |||
| if (hasInvalidData) { | |||
| this.$set(sampleObject, 'invalidData', true); | |||
| } | |||
| sampleObject.charData.charOption = this.formateCharOption( | |||
| sampleIndex, | |||
| ); | |||
| this.$forceUpdate(); | |||
| this.$forceUpdate(); | |||
| this.$nextTick(() => { | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.hideLoading(); | |||
| } | |||
| this.$nextTick(() => { | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.hideLoading(); | |||
| } | |||
| // Draw chart | |||
| if (!this.compare) { | |||
| this.updateOrCreateChar(sampleIndex); | |||
| } else { | |||
| this.abort = true; | |||
| } | |||
| }); | |||
| }).catch((e)=>{ | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.clear(); | |||
| } | |||
| }); | |||
| // Draw chart | |||
| if (!this.compare) { | |||
| this.updateOrCreateChar(sampleIndex); | |||
| } else { | |||
| this.abort = true; | |||
| } | |||
| }); | |||
| }) | |||
| .catch((e) => { | |||
| if (sampleObject.charObj) { | |||
| sampleObject.charObj.clear(); | |||
| } | |||
| }); | |||
| }); | |||
| }, | |||
| /** | |||
| * Formatting Chart Data | |||
| * @param {Number} sampleIndex Chart subscript | |||
| @@ -499,7 +500,7 @@ export default { | |||
| let returnFlag = false; | |||
| const seriesData = []; | |||
| const oriData = sampleObject.charData.oriData; | |||
| const runName=sampleObject.runNames; | |||
| const runName = sampleObject.runNames; | |||
| const curBackName = runName + this.backendString; | |||
| const dataObj = { | |||
| name: runName, | |||
| @@ -645,7 +646,7 @@ export default { | |||
| }, | |||
| grid: { | |||
| left: 80, | |||
| right: 10, | |||
| right: sampleObject.fullScreen ? 80 : 10, | |||
| }, | |||
| animation: true, | |||
| dataZoom: [ | |||
| @@ -886,9 +887,11 @@ export default { | |||
| if (sampleObject.fullScreen) { | |||
| sampleObject.charData.charOption.toolbox.feature.myToolFullScreen.iconStyle.borderColor = | |||
| '#3E98C5'; | |||
| sampleObject.charData.charOption.grid.right = 80; | |||
| } else { | |||
| sampleObject.charData.charOption.toolbox.feature.myToolFullScreen.iconStyle.borderColor = | |||
| '#6D7278'; | |||
| sampleObject.charData.charOption.grid.right = 10; | |||
| } | |||
| sampleObject.updateFlag = true; | |||
| @@ -901,14 +904,13 @@ export default { | |||
| }, 0); | |||
| }, | |||
| /** | |||
| * Update Chart by tag | |||
| * @param {Boolean} noPageDataNumChange No new data is added or deleted | |||
| */ | |||
| updateTagInPage(noPageDataNumChange) { | |||
| const curFilterSamples=[]; | |||
| const curFilterSamples = []; | |||
| // Obtains the chart subscript | |||
| this.originDataArr.forEach((sampleItem) => { | |||
| if (this.multiSelectedTagNames[sampleItem.tagName]) { | |||
| @@ -1012,13 +1014,12 @@ export default { | |||
| if (!selectedItemDict) { | |||
| return; | |||
| } | |||
| this.multiSelectedTagNames=selectedItemDict; | |||
| this.multiSelectedTagNames = selectedItemDict; | |||
| // Reset to the first page | |||
| this.pageIndex=0; | |||
| this.pageIndex = 0; | |||
| this.updateTagInPage(); | |||
| }, | |||
| /** | |||
| *window resize | |||
| */ | |||
| @@ -1050,7 +1051,7 @@ export default { | |||
| this.tagOperateList = []; | |||
| this.pageIndex = 0; | |||
| this.originDataArr = []; | |||
| this.oriDataDictionaries={}; | |||
| this.oriDataDictionaries = {}; | |||
| this.curPageArr = []; | |||
| this.tagPropsList = []; | |||
| this.propsList = []; | |||
| @@ -1090,10 +1091,10 @@ export default { | |||
| if (!oriData) { | |||
| return false; | |||
| } | |||
| const newTagDictionaries={}; // Index of the tag in the new data | |||
| const newTagDictionaries = {}; // Index of the tag in the new data | |||
| let dataRemoveFlag = false; | |||
| oriData.tags.forEach((tagName) => { | |||
| newTagDictionaries[tagName]=true; | |||
| newTagDictionaries[tagName] = true; | |||
| }); | |||
| // Delete the tag that does not exist | |||
| const oldTagListLength = this.tagOperateList.length; | |||
| @@ -1132,7 +1133,7 @@ export default { | |||
| const runColor = CommonProperty.commonColorArr[0]; | |||
| oriData.tags.forEach((tagObj) => { | |||
| if (!this.oriDataDictionaries[tagObj]) { | |||
| this.oriDataDictionaries[tagObj]=true; | |||
| this.oriDataDictionaries[tagObj] = true; | |||
| this.tagOperateList.push({ | |||
| label: tagObj, | |||
| checked: true, | |||
| @@ -1198,7 +1199,7 @@ export default { | |||
| const tagAddFlag = this.checkNewDataAndComplete(data); | |||
| this.$nextTick(() => { | |||
| this.multiSelectedTagNames=this.$refs.multiselectGroupComponents.updateSelectedDic(); | |||
| this.multiSelectedTagNames = this.$refs.multiselectGroupComponents.updateSelectedDic(); | |||
| this.updateTagInPage(!tagRemoveFlag && !tagAddFlag); | |||
| this.resizeCallback(); | |||
| @@ -1209,7 +1210,7 @@ export default { | |||
| // Initial chart data | |||
| data.tags.forEach((tagObj) => { | |||
| // Check whether the tag with the same name exists | |||
| // Check whether the tag with the same name exists | |||
| tempTagList.push({ | |||
| label: tagObj, | |||
| checked: true, | |||
| @@ -1266,7 +1267,7 @@ export default { | |||
| if (this.firstNum === 0) { | |||
| return; | |||
| } | |||
| this.smoothValueNumber=Number(val); | |||
| this.smoothValueNumber = Number(val); | |||
| if (this.smoothSliderValueTimer) { | |||
| clearTimeout(this.smoothSliderValueTimer); | |||
| this.smoothSliderValueTimer = null; | |||
| @@ -1279,26 +1280,26 @@ export default { | |||
| smoothValueChange(val) { | |||
| if (!isNaN(val)) { | |||
| if (Number(val)===0) { | |||
| this.smoothValue=0; | |||
| if (Number(val) === 0) { | |||
| this.smoothValue = 0; | |||
| } | |||
| if (Number(val)<0) { | |||
| this.smoothValue=0; | |||
| this.smoothValueNumber=0; | |||
| if (Number(val) < 0) { | |||
| this.smoothValue = 0; | |||
| this.smoothValueNumber = 0; | |||
| } | |||
| if (Number(val)>0) { | |||
| if (Number(val)>0.99) { | |||
| this.smoothValue=0.99; | |||
| this.smoothValueNumber=0.99; | |||
| if (Number(val) > 0) { | |||
| if (Number(val) > 0.99) { | |||
| this.smoothValue = 0.99; | |||
| this.smoothValueNumber = 0.99; | |||
| } else { | |||
| this.smoothValue=Number(val); | |||
| this.smoothValue = Number(val); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| smoothValueBlur() { | |||
| this.smoothValueNumber=this.smoothValue; | |||
| this.smoothValueNumber = this.smoothValue; | |||
| }, | |||
| /** | |||
| @@ -21,6 +21,12 @@ limitations under the License. | |||
| <div class="cl-dashboard-top-title"> | |||
| {{$t('trainingDashboard.trainingDashboardTitle')}} | |||
| </div> | |||
| <div class="path-message"> | |||
| <span>{{$t('symbols.leftbracket')}}</span> | |||
| <span>{{$t('trainingDashboard.summaryDirPath')}}</span> | |||
| <span>{{summaryPath}}</span> | |||
| <span>{{$t('symbols.rightbracket')}}</span> | |||
| </div> | |||
| </div> | |||
| <div class="cl-dashboard-center"> | |||
| <div class="cl-dashboard-con-up" | |||
| @@ -166,6 +172,7 @@ export default { | |||
| return { | |||
| // training job id | |||
| trainingJobId: '', | |||
| summaryPath: '', | |||
| defColorCount: CommonProperty.commonColorArr.length, // default color | |||
| colorNum: 0, | |||
| charObj: null, | |||
| @@ -284,6 +291,7 @@ export default { | |||
| init() { | |||
| if (this.$route.query && this.$route.query.id) { | |||
| this.trainingJobId = this.$route.query.id; | |||
| this.summaryPath = decodeURIComponent(this.trainingJobId); | |||
| } else { | |||
| this.trainingJobId = ''; | |||
| this.$message.error(this.$t('trainingDashboard.invalidId')); | |||
| @@ -1720,6 +1728,12 @@ export default { | |||
| height: 56px; | |||
| vertical-align: middle; | |||
| background: #ffffff; | |||
| .path-message { | |||
| display: inline-block; | |||
| line-height: 20px; | |||
| padding: 18px 16px; | |||
| font-weight: bold; | |||
| } | |||
| .cl-dashboard-top-title { | |||
| float: left; | |||
| color: #000000; | |||
| @@ -36,9 +36,9 @@ class MockReservoir: | |||
| def __init__(self, size): | |||
| self.size = size | |||
| self._samples = [ | |||
| _Tensor('wall_time1', 1, 'value1'), | |||
| _Tensor('wall_time2', 2, 'value2'), | |||
| _Tensor('wall_time3', 3, 'value3') | |||
| _Tensor('wall_time1', 1, 'value1', 'filename1'), | |||
| _Tensor('wall_time2', 2, 'value2', 'filename2'), | |||
| _Tensor('wall_time3', 3, 'value3', 'filename3') | |||
| ] | |||
| def samples(self): | |||
| @@ -107,7 +107,8 @@ class TestEventsData: | |||
| """Test add_tensor_event success.""" | |||
| ev_data = self.get_ev_data() | |||
| t_event = TensorEvent(wall_time=1, step=4, tag='new_tag', plugin_name='plugin_name1', value='value1') | |||
| t_event = TensorEvent(wall_time=1, step=4, tag='new_tag', | |||
| plugin_name='plugin_name1', value='value1', filename='filename') | |||
| ev_data.add_tensor_event(t_event) | |||
| assert 'tag0' not in ev_data._tags | |||
| @@ -116,4 +117,54 @@ class TestEventsData: | |||
| assert 'tag0' not in ev_data._reservoir_by_tag | |||
| assert 'new_tag' in ev_data._tags_by_plugin['plugin_name1'] | |||
| assert ev_data._reservoir_by_tag['new_tag'].samples()[-1] == _Tensor(t_event.wall_time, t_event.step, | |||
| t_event.value) | |||
| t_event.value, 'filename') | |||
| def test_add_tensor_event_out_of_order(self): | |||
| """Test add_tensor_event success for out_of_order summaries.""" | |||
| wall_time = 1 | |||
| value = '1' | |||
| tag = 'tag' | |||
| plugin_name = 'scalar' | |||
| file1 = 'file1' | |||
| ev_data = EventsData() | |||
| steps = [i for i in range(2, 10)] | |||
| for step in steps: | |||
| t_event = TensorEvent(wall_time=1, step=step, tag=tag, | |||
| plugin_name=plugin_name, value=value, filename=file1) | |||
| ev_data.add_tensor_event(t_event) | |||
| t_event = TensorEvent(wall_time=1, step=1, tag=tag, | |||
| plugin_name=plugin_name, value=value, filename=file1) | |||
| ev_data.add_tensor_event(t_event) | |||
| # Current steps should be: [1, 2, 3, 4, 5, 6, 7, 8, 9] | |||
| assert len(ev_data._reservoir_by_tag[tag].samples()) == len(steps) + 1 | |||
| file2 = 'file2' | |||
| new_steps_1 = [5, 10] | |||
| for step in new_steps_1: | |||
| t_event = TensorEvent(wall_time=1, step=step, tag=tag, | |||
| plugin_name=plugin_name, value=value, filename=file2) | |||
| ev_data.add_tensor_event(t_event) | |||
| assert ev_data._reservoir_by_tag[tag].samples()[-1] == _Tensor(wall_time, step, value, file2) | |||
| # Current steps should be: [1, 2, 3, 4, 5, 10] | |||
| steps = [1, 2, 3, 4, 5, 10] | |||
| samples = ev_data._reservoir_by_tag[tag].samples() | |||
| for step, sample in zip(steps, samples): | |||
| filename = file1 if sample.step < 5 else file2 | |||
| assert sample == _Tensor(wall_time, step, value, filename) | |||
| new_steps_2 = [7, 11, 3] | |||
| for step in new_steps_2: | |||
| t_event = TensorEvent(wall_time=1, step=step, tag=tag, | |||
| plugin_name=plugin_name, value=value, filename=file2) | |||
| ev_data.add_tensor_event(t_event) | |||
| # Current steps should be: [1, 2, 3, 5, 7, 10, 11], file2: [3, 5, 7, 10, 11] | |||
| steps = [1, 2, 3, 5, 7, 10, 11] | |||
| new_steps_2.extend(new_steps_1) | |||
| samples = ev_data._reservoir_by_tag[tag].samples() | |||
| for step, sample in zip(steps, samples): | |||
| filename = file2 if sample.step in new_steps_2 else file1 | |||
| assert sample == _Tensor(wall_time, step, value, filename) | |||
| @@ -27,10 +27,14 @@ class TestHistogramReservoir: | |||
| sample1.value.count = 1 | |||
| sample1.value.max = 102 | |||
| sample1.value.min = 101 | |||
| sample1.step = 2 | |||
| sample1.filename = 'filename' | |||
| sample2 = mock.MagicMock() | |||
| sample2.value.count = 2 | |||
| sample2.value.max = 102 | |||
| sample2.value.min = 101 | |||
| sample2.step = 1 | |||
| sample2.filename = 'filename' | |||
| my_reservoir.add_sample(sample1) | |||
| my_reservoir.add_sample(sample2) | |||
| samples = my_reservoir.samples() | |||
| @@ -216,9 +216,8 @@ class TestImagesProcessor: | |||
| """ | |||
| Test removing sample in reservoir. | |||
| If step list is [1, 3, 5, 7, 9, 2, 3, 4, 15], | |||
| and then [3, 5, 7, 9] will be deleted. | |||
| Results will be [1, 2, 3, 4, 15]. | |||
| If step list is [1, 3, 5, 7, 9, 2, 3, 4, 15] in one summary, | |||
| Results will be [1, 2, 3, 4, 5, 7, 9, 15]. | |||
| """ | |||
| test_tag_name = self._complete_tag_name | |||
| @@ -237,5 +236,4 @@ class TestImagesProcessor: | |||
| except ImageNotExistError: | |||
| not_found_step_list.append(test_step) | |||
| assert current_step_list == [1, 2, 3, 4, 15] | |||
| assert not_found_step_list == [5, 7, 9] | |||
| assert current_step_list == [1, 2, 3, 4, 5, 7, 9, 15] | |||
| @@ -204,6 +204,12 @@ CUSTOMIZED_1 = { | |||
| 'metric/mse': {'label': 'metric/mse', 'required': True, 'type': 'float'} | |||
| } | |||
| CUSTOMIZED_2 = { | |||
| 'metric/accuracy': {'label': 'metric/accuracy', 'required': True, 'type': 'mixed'}, | |||
| 'metric/mae': {'label': 'metric/mae', 'required': True, 'type': 'float'}, | |||
| 'metric/mse': {'label': 'metric/mse', 'required': True, 'type': 'float'} | |||
| } | |||
| METRIC_1 = { | |||
| 'accuracy': 1.0000002, | |||
| 'mae': 2.00000002, | |||
| @@ -463,7 +463,7 @@ class TestQuerier(TestCase): | |||
| def test_filter_summary_lineage_success_4(self): | |||
| """Test the success of filter_summary_lineage.""" | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'customized': event_data.CUSTOMIZED_2, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_0, | |||
| LINEAGE_FILTRATION_1, | |||
| @@ -500,7 +500,7 @@ class TestQuerier(TestCase): | |||
| 'sorted_type': 'ascending' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'customized': event_data.CUSTOMIZED_2, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_0, | |||
| LINEAGE_FILTRATION_5, | |||
| @@ -522,7 +522,7 @@ class TestQuerier(TestCase): | |||
| 'sorted_type': 'descending' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_1, | |||
| 'customized': event_data.CUSTOMIZED_2, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_6, | |||
| LINEAGE_FILTRATION_4, | |||