From 227dd479ff9bf2b20960a52bb040865bd43cb977 Mon Sep 17 00:00:00 2001 From: Reuben Dunn Date: Mon, 18 Jan 2021 21:30:05 -0800 Subject: [PATCH] Reduced Captum Insights package size (#562) Summary: This commit reduces the size of Captum Insights by - Replacing the old graphing library with a more lightweight one - In the standalone app, using compression in the Flask server - In the notebook extension, excluding unused dependencies-of-dependencies ![Graph screenshot](https://user-images.githubusercontent.com/13208038/102558400-31c74080-4082-11eb-93b2-9b5c474fa0ae.png) For the standalone app, it reduces the size significantly: ![size comparison](https://user-images.githubusercontent.com/13208038/102558239-de54f280-4081-11eb-9718-24b9174d408b.png) For the notebook extension, there's a similar size reduction of `index.js` from 1090 KB to 449 KB. Testing: I used `titanic.py` to test this change, making sure that the graphs are working as before and that the other functionality is unnafected. Pull Request resolved: https://github.com/pytorch/captum/pull/562 Reviewed By: edward-io Differential Revision: D25628623 Pulled By: Reubend fbshipit-source-id: ef8a0d9ec8c7e0df6955b69dd7a96656defc37e8 --- .gitignore | 1 + captum/attr/_utils/visualization.py | 2 +- .../insights/attr_vis/frontend/package.json | 8 +- .../frontend/src/components/Feature.tsx | 71 +++++++++------- .../attr_vis/frontend/src/components/Plot.tsx | 6 -- .../src/components/plotly.module.d.ts | 1 - .../attr_vis/frontend/src/utils/dataPoint.ts | 11 +++ .../frontend/widget/webpack.config.js | 3 +- captum/insights/attr_vis/frontend/yarn.lock | 82 +++++++++++-------- captum/insights/attr_vis/server.py | 2 + setup.py | 2 +- 11 files changed, 114 insertions(+), 75 deletions(-) delete mode 100644 captum/insights/attr_vis/frontend/src/components/Plot.tsx delete mode 100644 captum/insights/attr_vis/frontend/src/components/plotly.module.d.ts create mode 100644 captum/insights/attr_vis/frontend/src/utils/dataPoint.ts diff --git a/.gitignore b/.gitignore index 7f19610e48..234f7dd27c 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,4 @@ website/static/js/* !website/static/js/code_block_buttons.js website/static/_sphinx-sources/ node_modules +captum/insights/attr_vis/widget/static diff --git a/captum/attr/_utils/visualization.py b/captum/attr/_utils/visualization.py index 492bac542d..89985a0c76 100644 --- a/captum/attr/_utils/visualization.py +++ b/captum/attr/_utils/visualization.py @@ -514,7 +514,7 @@ def format_word_importances(words, importances): def visualize_text( datarecords: Iterable[VisualizationDataRecord], legend: bool = True -) -> HTML: +) -> "HTML": # In quotes because this type doesn't exist in standalone mode assert HAS_IPYTHON, ( "IPython must be available to visualize text. " "Please run 'pip install ipython'." diff --git a/captum/insights/attr_vis/frontend/package.json b/captum/insights/attr_vis/frontend/package.json index 04cdb7c0c0..97044748df 100644 --- a/captum/insights/attr_vis/frontend/package.json +++ b/captum/insights/attr_vis/frontend/package.json @@ -11,15 +11,14 @@ "@types/node": "^14.0.13", "@types/react": "^16.9.38", "@types/react-dom": "^16.9.8", - "@types/react-plotly.js": "^2.2.4", "@types/react-tag-autocomplete": "^5.12.0", "babel-loader": "^8.0.6", + "chart.js": "^2.9.4", "css-loader": "3.3.0", "js-levenshtein": "^1.1.6", - "plotly.js-basic-dist-min": "^1.58.2", "react": "^16.9.0", + "react-chartjs-2": "^2.11.1", "react-dom": "^16.9.0", - "react-plotly.js": "^2.4.0", "react-scripts": "3.4.1", "react-tag-autocomplete": "^5.11.1", "typescript": "^3.9.5", @@ -45,5 +44,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/chart.js": "^2.9.29" } } diff --git a/captum/insights/attr_vis/frontend/src/components/Feature.tsx b/captum/insights/attr_vis/frontend/src/components/Feature.tsx index 23bd352958..9060cf9331 100644 --- a/captum/insights/attr_vis/frontend/src/components/Feature.tsx +++ b/captum/insights/attr_vis/frontend/src/components/Feature.tsx @@ -1,8 +1,9 @@ import { calcHSLFromScore } from "../utils/color"; +import { DataPoint } from "../utils/dataPoint"; import React from "react"; import styles from "../App.module.css"; import Tooltip from "./Tooltip"; -import Plot from "./Plot"; +import { Bar } from "react-chartjs-2"; import { FeatureOutput } from "../models/visualizationOutput"; interface FeatureProps { @@ -97,35 +98,47 @@ type GeneralFeatureProps = FeatureProps<{ }>; function GeneralFeature(props: GeneralFeatureProps) { - return ( - (v < 0 ? "#d45c43" : "#80aaff") // red if negative, else blue - ), - }, - }, - ]} - config={{ - displayModeBar: false, - }} - layout={{ - height: 300, - margin: { - t: 20, - pad: 0, + const data = { + labels: props.data.base, + datasets: [ + { + barPercentage: 0.5, + data: props.data.modified, + backgroundColor: (dataPoint: DataPoint) => { + if (!dataPoint.dataset || !dataPoint.dataset.data || dataPoint.datasetIndex === undefined) { + return "#d45c43"; // Default to red + } + const yValue = dataPoint.dataset.data[dataPoint.dataIndex as number] || 0; + return yValue < 0 ? "#d45c43" : "#80aaff"; // Red if negative, else blue }, - yaxis: { - fixedrange: true, - showgrid: false, - }, - xaxis: { - fixedrange: false, + }, + ], + }; + + return ( + diff --git a/captum/insights/attr_vis/frontend/src/components/Plot.tsx b/captum/insights/attr_vis/frontend/src/components/Plot.tsx deleted file mode 100644 index 85896e9fca..0000000000 --- a/captum/insights/attr_vis/frontend/src/components/Plot.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import createPlotlyComponent from "react-plotly.js/factory"; -import Plotly from "plotly.js-basic-dist-min"; - -const Plot = createPlotlyComponent(Plotly); - -export default Plot; diff --git a/captum/insights/attr_vis/frontend/src/components/plotly.module.d.ts b/captum/insights/attr_vis/frontend/src/components/plotly.module.d.ts deleted file mode 100644 index c951d84a27..0000000000 --- a/captum/insights/attr_vis/frontend/src/components/plotly.module.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "plotly.js-basic-dist-min"; diff --git a/captum/insights/attr_vis/frontend/src/utils/dataPoint.ts b/captum/insights/attr_vis/frontend/src/utils/dataPoint.ts new file mode 100644 index 0000000000..c4852ec5fe --- /dev/null +++ b/captum/insights/attr_vis/frontend/src/utils/dataPoint.ts @@ -0,0 +1,11 @@ +import * as chartjs from "chart.js"; + +// Because there's no data point type exported by the +// main type declaration for chart.js, we have our own. + +export interface DataPoint { + chart?: object; + dataIndex?: number; + dataset?: chartjs.ChartDataSets; + datasetIndex?: number; +} diff --git a/captum/insights/attr_vis/frontend/widget/webpack.config.js b/captum/insights/attr_vis/frontend/widget/webpack.config.js index b6aa0066f4..f3c2b01fa3 100644 --- a/captum/insights/attr_vis/frontend/widget/webpack.config.js +++ b/captum/insights/attr_vis/frontend/widget/webpack.config.js @@ -57,6 +57,7 @@ module.exports = [ resolve: { modules: ["../node_modules"], }, + externals: ["moment"], // Removes unused dependency-of-dependency }, { // Bundle for the notebook containing the custom widget views and models @@ -82,6 +83,6 @@ module.exports = [ modules: ["../node_modules"], extensions: extensions, }, - externals: ["@jupyter-widgets/base"], + externals: ["@jupyter-widgets/base", "moment"], }, ]; diff --git a/captum/insights/attr_vis/frontend/yarn.lock b/captum/insights/attr_vis/frontend/yarn.lock index d5fe8da620..588f31aa81 100644 --- a/captum/insights/attr_vis/frontend/yarn.lock +++ b/captum/insights/attr_vis/frontend/yarn.lock @@ -1462,16 +1462,18 @@ dependencies: "@babel/types" "^7.3.0" +"@types/chart.js@^2.9.29": + version "2.9.29" + resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.29.tgz#73bf7f02387402943f29946012492f10bde7ed43" + integrity sha512-WOZMitUU3gHDM0oQsCsVivX+oDsIki93szcTmmUPBm39cCvAELBjokjSDVOoA3xiIEbb+jp17z/3S2tIqruwOQ== + dependencies: + moment "^2.10.2" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/d3@^3": - version "3.5.43" - resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.43.tgz#e9b4992817e0b6c5efaa7d6e5bb2cee4d73eab58" - integrity sha512-t9ZmXOcpVxywRw86YtIC54g7M9puRh8hFedRvVfHKf5YyOP6pSxA0TvpXpfseXSCInoW4P7bggTrSDiUOs4g5w== - "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -1544,13 +1546,6 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/plotly.js@*": - version "1.50.13" - resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-1.50.13.tgz#bd56b37f4d849b63659ccb966fc71d7e61415315" - integrity sha512-uxqsHe7eZa+RtWI0kPcoBHKDTdQ0rqWWDIivwpwFOrpTtjNe9Vwj0Mc6wZdyHUiq02vsSqdAXDbl6noRKKzmkg== - dependencies: - "@types/d3" "^3" - "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1568,14 +1563,6 @@ dependencies: "@types/react" "*" -"@types/react-plotly.js@^2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@types/react-plotly.js/-/react-plotly.js-2.2.4.tgz#360fcfcace64d0ef173cf0c7d67fc2441b39f659" - integrity sha512-dsvngno7Ar13XoF2eJXM609Msoc5DPboNp8x4NE4L+rsBEEsu16/0b0Fze8REx3fA4HEUk4rcn3uJsx48m2sew== - dependencies: - "@types/plotly.js" "*" - "@types/react" "*" - "@types/react-tag-autocomplete@^5.12.0": version "5.12.0" resolved "https://registry.yarnpkg.com/@types/react-tag-autocomplete/-/react-tag-autocomplete-5.12.0.tgz#bcebefd07abd20ee7a1d9594cf4f2f0297625868" @@ -2787,6 +2774,29 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== + dependencies: + chartjs-color "^2.1.0" + moment "^2.10.2" + +chartjs-color-string@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" + integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== + dependencies: + color-name "^1.0.0" + +chartjs-color@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0" + integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== + dependencies: + chartjs-color-string "^0.6.0" + color-convert "^1.9.3" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -2945,7 +2955,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -6616,6 +6626,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + loglevel@^1.6.6: version "1.6.7" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" @@ -6924,6 +6939,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" +moment@^2.10.2: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -7716,11 +7736,6 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" -plotly.js-basic-dist-min@^1.58.2: - version "1.58.2" - resolved "https://registry.yarnpkg.com/plotly.js-basic-dist-min/-/plotly.js-basic-dist-min-1.58.2.tgz#7b40228f0e6d46c8936f90a0d876803df68dfe39" - integrity sha512-Yu84SGV2+bgK7ZVzmr/xUh7d6qucZ5kR/lA9emJs7N+KFmZXn7t69NCMn5YWCNeKdFys/ogdHBT9iG42yBFdPA== - pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -8657,6 +8672,14 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" +react-chartjs-2@^2.11.1: + version "2.11.1" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.11.1.tgz#a78d0df05fc8bc8ffcd4c4ab5b89a25dd2ca3278" + integrity sha512-G7cNq/n2Bkh/v4vcI+GKx7Q1xwZexKYhOSj2HmrFXlvNeaURWXun6KlOUpEQwi1cv9Tgs4H3kGywDWMrX2kxfA== + dependencies: + lodash "^4.17.19" + prop-types "^15.7.2" + react-dev-utils@^10.2.1: version "10.2.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19" @@ -8707,13 +8730,6 @@ react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-plotly.js@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/react-plotly.js/-/react-plotly.js-2.4.0.tgz#7a8fd89ffa126daa36a5855890282960e2e4eaf0" - integrity sha512-BCkxMe8yWqu3nP/hw9A1KCIuoL67WV5/k68SL9yhEkF6UG+pAuIev9Q3cMKtNkQJZhsYFpOmlqrpPjIdUFACOQ== - dependencies: - prop-types "^15.7.2" - react-scripts@3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a" diff --git a/captum/insights/attr_vis/server.py b/captum/insights/attr_vis/server.py index 17bfd8e34f..720c938698 100644 --- a/captum/insights/attr_vis/server.py +++ b/captum/insights/attr_vis/server.py @@ -7,6 +7,7 @@ from typing import Optional from flask import Flask, jsonify, render_template, request +from flask_compress import Compress from torch import Tensor from captum.log import log_usage @@ -16,6 +17,7 @@ ) visualizer = None port = None +Compress(app) def namedtuple_to_dict(obj): diff --git a/setup.py b/setup.py index 26c1cb5482..36159ae1ee 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def report(*args): pass -INSIGHTS_REQUIRES = ["flask", "ipython", "ipywidgets", "jupyter"] +INSIGHTS_REQUIRES = ["flask", "ipython", "ipywidgets", "jupyter", "flask-compress"] INSIGHTS_FILE_SUBDIRS = [ "insights/attr_vis/frontend/build",