-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable WIT usage as a jupyter extension #1662
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @jameswex, thanks a lot for opening the PR.
Just curious, since setup.py runs/generates the static file, you don't need to check those files in, right?
It is just required at the pip package generation time for the data_files?
I think you are right that static/extension.js, static/index.js and static/index.js.map don't need to be checked in as they are created by the pip setup. This is my first pip package so I'm not very familiar with it, so its possible there is some trimming down that can be done. I believe the wit_jupyter.html needs to stay as part of the submission, unless the setup.py can be altered to build it from the appopriate bazel run and copy it to the right place for packaging. Is that also doable in the setup.py? |
James, would it be possible to run the |
Another option re: the generated code might be checking in a script that first generates those intermediate files in a temporary location, and then invokes setup.py to build the pip package using those files. This is basically what we do for the main pip package in tensorboard/tensorboard/pip_package/BUILD Lines 8 to 30 in 0da00e7
tensorboard/tensorboard/pip_package/build_pip_package.sh Lines 75 to 116 in 0da00e7
|
I added a new command to setup.py that builds wit_jupyter and moves it to the static dir, so the static dir has been removed from the repo. |
@stephanwlee @tolga-b This PR now only contains the necessary code to use WIT as a jupyter extension (including building the pip package for the extension). The colab extension code will be reviewed as a separate PR after this is submitted. Can be tested locally if desired by building the pip package whl files as per the new bazel rule in pip_package directory then using 'pip install [path to built whl file] and running the jupyter nbextension commands as per the updated plugin documentation, then running through the WIT_from_scratch.ipynb notebook which has been updated to show this usage. |
|
||
## How do I use it in a Jupyter notebook? | ||
First, install and enable WIT for Jupyter through the following commands: | ||
> pip install witwidget |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to read better like this
\```sh
pip install witwidget
jupyter nbextension install --py --symlink --sys-prefix witwidget
jupyter nbextension enable --py --sys-prefix witwidget
\```
(omit the backslash before backticks in code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by markdown tip: @stephanwlee, you can write
Seems to read better like this
``````markdown
```sh
pip install witwidget
jupyter nbextension install --py --symlink --sys-prefix witwidget
jupyter nbextension enable --py --sys-prefix witwidget
```
``````
which will render like this:
Seems to read better like this
```sh pip install witwidget jupyter nbextension install --py --symlink --sys-prefix witwidget jupyter nbextension enable --py --sys-prefix witwidget ```
In general, you can start code fences or inline code spans with any
number of backticks, and close them with the matching number of
backticks.
@@ -0,0 +1,13 @@ | |||
# Copyright 2018 The TensorFlow Authors. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
omit this file for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
// python backend. | ||
var WITView = widgets.DOMWidgetView.extend({ | ||
render: function() { | ||
loadVulcanizedTemplate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a synchronous operation? I forgot whether adding a script tag to the head is synchronous. Anyways, depending on the answer, it may impact L42
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm seeing this code function correctly as-is when i test locally. should i be instead deferring to some point in the future to be more defensive? The setTimeouts (now requestAnimationFrames) in my code may be accomplishing this for me already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried this. Not sure of <link>
differs too much from <script>
but it sounds like it is worth doing some investigation to invoke L26: http://jsfiddle.net/b726ykhc/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bringing this up. Fixed this all with change to wait for onload of the import before creating the WIT polymer element. Now there are no more setTimeouts OR requestAnimationFrame calls. Much simpler logic.
|
||
// Add listeners for changes from WIT Polymer element. Passes changes | ||
// along to python. | ||
this.view_.addEventListener('infer-examples', e => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would you be factor out view creation to its own method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
this.mutantFeature = e.detail.feature_name; | ||
this.touch(); | ||
}); | ||
setTimeout(()=> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer to use requestAnimationFrame
unless it has to happen in nextTick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
// Callback functions for when changes made on python side. | ||
examples_changed: function() { | ||
const examples = this.model.get('examples'); | ||
if (!this.view_.updateExampleContents_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should not read the private property of the view. Can this be refactored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
made this method public as it is needed to be called here.
i added this because i noticed that in practice when the element is created, it doesn't initially have its methods defined (must have to do with how polymer elements are created?)
examples_changed: function() { | ||
const examples = this.model.get('examples'); | ||
if (!this.view_.updateExampleContents_) { | ||
setTimeout(() => this.examples_changed(), 100); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If above if statement is truthy, we can get stuck in infinite loop. Also, since examples_changed can get triggered multiple times, consider clearing the timeout before doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switched to requestAnimationFrame. i added this because i noticed that in practice when the element is created, it doesn't initially have its methods defined (must have to do with how polymer elements are created?), so we need to wait until the method exists before calling it.
}, | ||
eligible_features_changed: function() { | ||
const features = this.model.get('eligible_features'); | ||
this.view_.partialDepPlotEligibleFeatures = features |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing trailing semi-colon (for consistency)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
}, | ||
config_changed: function() { | ||
const config = this.model.get('config'); | ||
if ('inference_address' in config) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious: is config guaranteed to be non-null or non-undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added protection against null
}, | ||
sprite_changed: function() { | ||
if (!this.view_.updateSprite_) { | ||
setTimeout(() => this.sprite_changed(), 100); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider clearing timeout before setting a new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switched to requestAnimationFrame
limitations under the License. | ||
==============================================================================*/ | ||
|
||
__webpack_public_path__ = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this currently being exported as a global variable? If so, can we be a little bit more explicit with (global || window).__webpack_public_path__
? Or, pull it out to own module and exporting the value that wit.js
requires would be more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used window.
output: { | ||
filename: 'index.js', | ||
path: path.resolve(__dirname, 'static'), | ||
libraryTarget: 'amd' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any thoughts on setting the public path here and remove https://github.com/tensorflow/tensorboard/pull/1662/files#diff-56459f8bc62645373c45f3cb38734e04R16?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't matter to me where it is set, we don't expect it to be updated in future PRs.
"""WIT widget for Jupyter.""" | ||
_view_name = Unicode('WITView').tag(sync=True) | ||
_view_module = Unicode('wit-widget').tag(sync=True) | ||
_view_module_version = Unicode('^0.1.5').tag(sync=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the version number is encoded in three different places (version.py for pip package gen, package.json, and here). Can we do anything to make it more maintainable? If not, we might need to update the RELEASE.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking back at TFMA's use, only the version.py is important for the package. The package.json doesn't need a version as it isn't published and the view module versions can be kept static at 0.1.0 - they do not need to match the pip version and don't need to ever be updated as this is the only wit widget to be pip installed for a jupyter kernel. made appropriate changes.
|
||
def __init__(self, config_builder, height=1000): | ||
super(WitWidget, self).__init__(layout=Layout(height='%ipx' % height)) | ||
tf.logging.set_verbosity(tf.logging.WARN) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please note that this is going away in tf 2.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this plugin and widget version of it will need tf 2.0 updates, but i imagine outside of this PR
this.view_.hasSprite = true; | ||
this.view_.localAtlasUrl = spriteUrl; | ||
this.view_.updateSprite(); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation is off
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you James, just some high-level comments for now:
- Run Inference button in jupyter notebook always stays on.
- I get the following deprecation warning under the widget in jupyter:
WARNING:tensorflow: .../python2.7/site-packages/tensorflow_estimator/python/estimator/util.py:104: make_initializable_iterator (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version. Instructions for updating: Use
for ... in dataset:to iterate over a dataset. If using
tf.estimator, return the
Datasetobject directly from your input function. As a last resort, you can use
tf.compat.v1.data.make_initializable_iterator(dataset).
fi | ||
|
||
set -x | ||
command -v virtualenv >/dev/null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line fails silently when virtualenv is not installed (does not warn that it is not installed, just says build completed successfully).
In jupyter mode PDP and Global PDP panels do not seem to always open the top plot as it was in tensorboard mode. For example in WIT_From_Scratch notebook, if you open PDP panel then close and open it again, all PDPs are closed and one needs to click on "Age" again for the plot to be computed. |
Thanks Tolga. Fixed the warning with a change in the example ipython notebook and fixed the inference button issue with a fix in the WitConfigBuilder object handling when a label vocab isn't set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pip package scripting LGTM.
"//tensorboard/plugins/interactive_inference/tf_interactive_inference_dashboard:wit_jupyter", # For HTML payload | ||
"//tensorboard/plugins/interactive_inference/witwidget/notebook/jupyter/js:js_files", | ||
"//tensorboard/plugins/interactive_inference/utils:inference_utils", # Utility method that visualization code uses. | ||
"//tensorboard/plugins/interactive_inference/witwidget", # Version module (read by setup.py) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale comment? The line below is the version module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
REQUIRED_PACKAGES = [ | ||
'ipywidgets>=7.0.0', | ||
'jupyter>=1.0,<2', | ||
'tensorflow>=1.12.0', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW - TensorBoard doesn't express a direct dep on TF because if this dep won't be satisfied by e.g. the tensorflow-gpu
or tf-nightly
packages, as pip isn't smart enough to realize that they're all providing the tensorflow
package. If the user already has one of those packages installed, then installing witwidget
will cause tensorflow
to be installed and clobber the existing package, which can result in a bit of a mess.
I guess tensorflow-serving-api already depends on TF directly, though (it partially gets around this by shipping a -gpu
version that depends on tensorflow-gpu
instead, apparently), so you're kind of stuck. Just something to be aware of at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm not ideal. thanks for explaining this.
'Programming Language :: Python :: 3', | ||
'Programming Language :: Python :: 3.3', | ||
'Programming Language :: Python :: 3.4', | ||
'Programming Language :: Python :: 3.5', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also include 3.6? Not that it makes much functional difference AFAIK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
This PR enables the What-If Tool to work as a Jupyter extension, while not affecting its use in TensorBoard as well, which many customers have asked for.
The code for the jupyter extension sits in the witwidget directory under the top-level plugin directory. The jupyter extension can be installed as its own pip package named witwidget and is built using a bazel command and shell script just like the main tensorboard pip package. There are added instructions for this mode in the WIT top-level readme and the WIT_from_scratch tutorial jupyter notebook.
wit.py and wit.js contain the special code for using WIT in a jupyter extension, using the ipywidget framework with traitlets. Most of the rest of the new files are necessary boilerplate from https://github.com/jupyter-widgets/widget-cookiecutter
Screenshot showing invocation and display of WIT inside jupyter notebook.
Ran tensorboard with WIT on existing models to ensure no functionality loss in TensorBoard.
Built pip package and installed it, ran WIT inside of notebook as per the updated tutorial notebook and tested all of its features.
Colab implementation of extension will be in separate PR. Colab doesn't allow for ipywidget use so the implementation must be different unfortunately. This follows the same patterns as the TensorFlow Model Analysis jupyter/colab extensions.