Extract the distributions dashboard to a plugin

This continues the great plugin migration. The distributions plugin was
similar to the histograms plugin, but it also purported to allow CSV
download like the scalars plugin. However, the existing implementation
of this was flawed, and would always yield a 500 on current prod [1]
(unless there were actually no data). This indicates that no one is
actually using it---probably because there isn't a relevant button on
the frontend, anyway!---so I just removed it.

This also changes most frontend occurrences of "compressedHistograms"
to "distributions" while we're at it.

[1]: Due to the reference `value.rank_in_bps` in the handler
`_serve_compressed_histograms`; this field does not exist and throws an
`AttributeError`.

PiperOrigin-RevId: 157787156
This commit is contained in:
William Chargin 2017-06-01 17:37:28 -07:00 committed by TensorFlower Gardener
parent 23cdf96b85
commit 7d7a403096
13 changed files with 277 additions and 80 deletions

View File

@ -380,6 +380,7 @@ filegroup(
"//tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize:all_files", "//tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize:all_files",
"//tensorflow/tensorboard/plugins:all_files", "//tensorflow/tensorboard/plugins:all_files",
"//tensorflow/tensorboard/plugins/audio:all_files", "//tensorflow/tensorboard/plugins/audio:all_files",
"//tensorflow/tensorboard/plugins/distributions:all_files",
"//tensorflow/tensorboard/plugins/histograms:all_files", "//tensorflow/tensorboard/plugins/histograms:all_files",
"//tensorflow/tensorboard/plugins/images:all_files", "//tensorflow/tensorboard/plugins/images:all_files",
"//tensorflow/tensorboard/plugins/projector:all_files", "//tensorflow/tensorboard/plugins/projector:all_files",

View File

@ -230,6 +230,7 @@ add_python_module("tensorflow/tensorboard/backend")
add_python_module("tensorflow/tensorboard/backend/event_processing") add_python_module("tensorflow/tensorboard/backend/event_processing")
add_python_module("tensorflow/tensorboard/plugins") add_python_module("tensorflow/tensorboard/plugins")
add_python_module("tensorflow/tensorboard/plugins/audio") add_python_module("tensorflow/tensorboard/plugins/audio")
add_python_module("tensorflow/tensorboard/plugins/distributions")
add_python_module("tensorflow/tensorboard/plugins/histograms") add_python_module("tensorflow/tensorboard/plugins/histograms")
add_python_module("tensorflow/tensorboard/plugins/images") add_python_module("tensorflow/tensorboard/plugins/images")
add_python_module("tensorflow/tensorboard/plugins/projector") add_python_module("tensorflow/tensorboard/plugins/projector")

View File

@ -14,6 +14,7 @@ py_binary(
"//tensorflow/tensorboard/backend:application", "//tensorflow/tensorboard/backend:application",
"//tensorflow/tensorboard/backend/event_processing:event_file_inspector", "//tensorflow/tensorboard/backend/event_processing:event_file_inspector",
"//tensorflow/tensorboard/plugins/audio:audio_plugin", "//tensorflow/tensorboard/plugins/audio:audio_plugin",
"//tensorflow/tensorboard/plugins/distributions:distributions_plugin",
"//tensorflow/tensorboard/plugins/histograms:histograms_plugin", "//tensorflow/tensorboard/plugins/histograms:histograms_plugin",
"//tensorflow/tensorboard/plugins/images:images_plugin", "//tensorflow/tensorboard/plugins/images:images_plugin",
"//tensorflow/tensorboard/plugins/projector:projector_plugin", "//tensorflow/tensorboard/plugins/projector:projector_plugin",

View File

@ -22,15 +22,12 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
import csv
import os import os
import re import re
import threading import threading
import time import time
import six import six
from six import StringIO
from six.moves import xrange # pylint: disable=redefined-builtin
from six.moves.urllib import parse as urlparse from six.moves.urllib import parse as urlparse
import tensorflow as tf import tensorflow as tf
from werkzeug import wrappers from werkzeug import wrappers
@ -59,6 +56,7 @@ DEFAULT_SIZE_GUIDANCE = {
# /data/runs entirely. # /data/runs entirely.
_MIGRATED_DATA_KEYS = frozenset(( _MIGRATED_DATA_KEYS = frozenset((
'audio', 'audio',
'distributions',
'histograms', 'histograms',
'images', 'images',
'scalars', 'scalars',
@ -69,7 +67,6 @@ LOGDIR_ROUTE = '/logdir'
RUNS_ROUTE = '/runs' RUNS_ROUTE = '/runs'
PLUGIN_PREFIX = '/plugin' PLUGIN_PREFIX = '/plugin'
PLUGINS_LISTING_ROUTE = '/plugins_listing' PLUGINS_LISTING_ROUTE = '/plugins_listing'
COMPRESSED_HISTOGRAMS_ROUTE = '/' + event_accumulator.COMPRESSED_HISTOGRAMS
GRAPH_ROUTE = '/' + event_accumulator.GRAPH GRAPH_ROUTE = '/' + event_accumulator.GRAPH
RUN_METADATA_ROUTE = '/' + event_accumulator.RUN_METADATA RUN_METADATA_ROUTE = '/' + event_accumulator.RUN_METADATA
TAB_ROUTES = ['', '/events', '/images', '/audio', '/graphs', '/histograms'] TAB_ROUTES = ['', '/events', '/images', '/audio', '/graphs', '/histograms']
@ -80,16 +77,6 @@ TAB_ROUTES = ['', '/events', '/images', '/audio', '/graphs', '/histograms']
_VALID_PLUGIN_RE = re.compile(r'^[A-Za-z0-9_.-]+$') _VALID_PLUGIN_RE = re.compile(r'^[A-Za-z0-9_.-]+$')
class _OutputFormat(object):
"""An enum used to list the valid output formats for API calls.
Not all API calls support all formats (for example, only scalars and
compressed histograms support CSV).
"""
JSON = 'json'
CSV = 'csv'
def standard_tensorboard_wsgi( def standard_tensorboard_wsgi(
logdir, logdir,
purge_orphaned_data, purge_orphaned_data,
@ -159,8 +146,6 @@ class TensorBoardWSGIApp(object):
reload_multiplexer(self._multiplexer, path_to_run) reload_multiplexer(self._multiplexer, path_to_run)
self.data_applications = { self.data_applications = {
DATA_PREFIX + COMPRESSED_HISTOGRAMS_ROUTE:
self._serve_compressed_histograms,
DATA_PREFIX + GRAPH_ROUTE: DATA_PREFIX + GRAPH_ROUTE:
self._serve_graph, self._serve_graph,
DATA_PREFIX + LOGDIR_ROUTE: DATA_PREFIX + LOGDIR_ROUTE:
@ -278,35 +263,6 @@ class TensorBoardWSGIApp(object):
return http_util.Respond( return http_util.Respond(
request, str(run_metadata), 'text/x-protobuf') # pbtxt request, str(run_metadata), 'text/x-protobuf') # pbtxt
@wrappers.Request.application
def _serve_compressed_histograms(self, request):
"""Given a tag and single run, return an array of compressed histograms."""
tag = request.args.get('tag')
run = request.args.get('run')
compressed_histograms = self._multiplexer.CompressedHistograms(run, tag)
if request.args.get('format') == _OutputFormat.CSV:
string_io = StringIO()
writer = csv.writer(string_io)
# Build the headers; we have two columns for timing and two columns for
# each compressed histogram bucket.
headers = ['Wall time', 'Step']
if compressed_histograms:
bucket_count = len(compressed_histograms[0].compressed_histogram_values)
for i in xrange(bucket_count):
headers += ['Edge %d basis points' % i, 'Edge %d value' % i]
writer.writerow(headers)
for compressed_histogram in compressed_histograms:
row = [compressed_histogram.wall_time, compressed_histogram.step]
for value in compressed_histogram.compressed_histogram_values:
row += [value.rank_in_bps, value.value]
writer.writerow(row)
return http_util.Respond(request, string_io.getvalue(), 'text/csv')
else:
return http_util.Respond(
request, compressed_histograms, 'application/json')
@wrappers.Request.application @wrappers.Request.application
def _serve_plugins_listing(self, request): def _serve_plugins_listing(self, request):
"""Serves an object mapping plugin name to whether it is enabled. """Serves an object mapping plugin name to whether it is enabled.

View File

@ -167,7 +167,6 @@ class TensorboardServerTest(tf.test.TestCase):
run_json, run_json,
{ {
'run1': { 'run1': {
'compressedHistograms': ['histogram'],
# if only_use_meta_graph, the graph is from the metagraph # if only_use_meta_graph, the graph is from the metagraph
'graph': True, 'graph': True,
'meta_graph': self._only_use_meta_graph, 'meta_graph': self._only_use_meta_graph,
@ -265,13 +264,8 @@ class TensorboardServerTest(tf.test.TestCase):
"""Generates the test data directory. """Generates the test data directory.
The test data has a single run named run1 which contains: The test data has a single run named run1 which contains:
- a histogram [1]
- a graph definition - a graph definition
[1]: Histograms no longer appear in `/runs`, but compressed
histograms do, and they use the same test data. Thus, histograms are
still here for now.
Returns: Returns:
temp_dir: The directory the test data is generated under. temp_dir: The directory the test data is generated under.
""" """
@ -281,14 +275,6 @@ class TensorboardServerTest(tf.test.TestCase):
os.makedirs(run1_path) os.makedirs(run1_path)
writer = tf.summary.FileWriter(run1_path) writer = tf.summary.FileWriter(run1_path)
histogram_value = tf.HistogramProto(
min=0,
max=2,
num=3,
sum=6,
sum_squares=5,
bucket_limit=[0, 1, 2],
bucket=[1, 1, 1])
# Add a simple graph event. # Add a simple graph event.
graph_def = tf.GraphDef() graph_def = tf.GraphDef()
node1 = graph_def.node.add() node1 = graph_def.node.add()
@ -309,13 +295,6 @@ class TensorboardServerTest(tf.test.TestCase):
device_stats = run_metadata.step_stats.dev_stats.add() device_stats = run_metadata.step_stats.dev_stats.add()
device_stats.device = 'test device' device_stats.device = 'test device'
writer.add_run_metadata(run_metadata, 'test run') writer.add_run_metadata(run_metadata, 'test run')
writer.add_event(
tf.Event(
wall_time=0,
step=0,
summary=tf.Summary(value=[
tf.Summary.Value(tag='histogram', histo=histogram_value),
])))
writer.flush() writer.flush()
writer.close() writer.close()

View File

@ -72,7 +72,7 @@ SUMMARY_TYPES = {
## The tagTypes below are just arbitrary strings chosen to pass the type ## The tagTypes below are just arbitrary strings chosen to pass the type
## information of the tag from the backend to the frontend ## information of the tag from the backend to the frontend
COMPRESSED_HISTOGRAMS = 'compressedHistograms' COMPRESSED_HISTOGRAMS = 'distributions'
HISTOGRAMS = 'histograms' HISTOGRAMS = 'histograms'
IMAGES = 'images' IMAGES = 'images'
AUDIO = 'audio' AUDIO = 'audio'

View File

@ -193,8 +193,9 @@ export class Backend {
* Return a promise showing the Run-to-Tag mapping for compressedHistogram * Return a promise showing the Run-to-Tag mapping for compressedHistogram
* data. * data.
*/ */
public compressedHistogramRuns(): Promise<RunToTag> { public compressedHistogramTags(): Promise<RunToTag> {
return this.runs().then((x) => _.mapValues(x, 'compressedHistograms')); return this.requestManager.request(
this.router.pluginRoute('distributions', '/tags'));
} }
/** /**
@ -343,7 +344,8 @@ export class Backend {
*/ */
private compressedHistogram(tag: string, run: string): private compressedHistogram(tag: string, run: string):
Promise<Array<Datum&CompressedHistogramTuple>> { Promise<Array<Datum&CompressedHistogramTuple>> {
const url = this.router.compressedHistograms(tag, run); const url = (this.router.pluginRunTagRoute(
'distributions', '/distributions')(tag, run));
let p: Promise<TupleData<CompressedHistogramTuple>[]>; let p: Promise<TupleData<CompressedHistogramTuple>[]>;
p = this.requestManager.request(url); p = this.requestManager.request(url);
return p.then(map(detupler((x) => x))); return p.then(map(detupler((x) => x)));

View File

@ -21,7 +21,6 @@ export interface Router {
logdir: () => string; logdir: () => string;
runs: () => string; runs: () => string;
isDemoMode: () => boolean; isDemoMode: () => boolean;
compressedHistograms: RunTagUrlFn;
graph: graph:
(run: string, limit_attr_size?: number, (run: string, limit_attr_size?: number,
large_attrs_key?: string) => string; large_attrs_key?: string) => string;
@ -88,7 +87,6 @@ export function router(dataDir = 'data', demoMode = false): Router {
runs: () => dataDir + '/runs' + (demoMode ? '.json' : ''), runs: () => dataDir + '/runs' + (demoMode ? '.json' : ''),
isDemoMode: () => demoMode, isDemoMode: () => demoMode,
graph: graphUrl, graph: graphUrl,
compressedHistograms: standardRoute('compressedHistograms'),
runMetadata: standardRoute('run_metadata', '.pbtxt'), runMetadata: standardRoute('run_metadata', '.pbtxt'),
healthPills: () => dataDir + '/plugin/debugger/health_pills', healthPills: () => dataDir + '/plugin/debugger/health_pills',
textRuns: () => dataDir + '/plugin/text/runs' + (demoMode ? '.json' : ''), textRuns: () => dataDir + '/plugin/text/runs' + (demoMode ? '.json' : ''),

View File

@ -55,13 +55,11 @@ all of the data available from the TensorBoard server. Here is an example:
{ {
"train_run": { "train_run": {
"compressedHistograms": ["foo_histogram", "bar_histogram"],
"graph": true, "graph": true,
"firstEventTimestamp": 123456.789 "firstEventTimestamp": 123456.789
"run_metadata": ["forward prop", "inference"] "run_metadata": ["forward prop", "inference"]
}, },
"eval": { "eval": {
"compressedHistograms": ["foo_histogram", "bar_histogram"],
"graph": false, "graph": false,
"run_metadata": [] "run_metadata": []
} }
@ -81,6 +79,7 @@ and will not appear in the output from this route:
- `audio` - `audio`
- `images` - `images`
- `scalars` - `scalars`
- `compressedHistograms`, moved to `distributions`
- `histograms` - `histograms`
## `/data/plugin/scalars/tags` ## `/data/plugin/scalars/tags`
@ -160,7 +159,21 @@ Annotated Example: (note - real data is higher precision)
] ]
] ]
## '/data/compressedHistograms?run=foo&tag=bar' ## `/data/plugin/distributions/tags`
Returns a dictionary mapping from `run_name` (quoted string) to arrays of
`tag_name` (quoted string), where each array contains the names of all
distribution tags present in the corresponding run. Here is an example:
{
"train_run": ["foo_histogram", "bar_histogram"],
"eval": ["foo_histogram", "bar_histogram"]
}
Note that runs without any distribution tags are included as keys with
value the empty array.
## `/data/plugin/distributions/distributions?run=foo&tag=bar`
Returns an array of event_accumulator.CompressedHistogramEvents ([wall_time, Returns an array of event_accumulator.CompressedHistogramEvents ([wall_time,
step, CompressedHistogramValues]) for the given run and tag. step, CompressedHistogramValues]) for the given run and tag.

View File

@ -0,0 +1,50 @@
# Description:
# TensorBoard plugin for distributions
package(default_visibility = ["//tensorflow:internal"])
licenses(["notice"]) # Apache 2.0
exports_files(["LICENSE"])
load("//tensorflow:tensorflow.bzl", "py_test")
## Distributions Plugin ##
py_library(
name = "distributions_plugin",
srcs = ["distributions_plugin.py"],
srcs_version = "PY2AND3",
visibility = [
"//tensorflow:internal",
],
deps = [
"//tensorflow/tensorboard/backend:http_util",
"//tensorflow/tensorboard/backend/event_processing:event_accumulator",
"//tensorflow/tensorboard/plugins:base_plugin",
"@org_pocoo_werkzeug//:werkzeug",
"@six_archive//:six",
],
)
py_test(
name = "distributions_plugin_test",
size = "small",
srcs = ["distributions_plugin_test.py"],
main = "distributions_plugin_test.py",
srcs_version = "PY2AND3",
deps = [
":distributions_plugin",
"//tensorflow:tensorflow_py",
"//tensorflow/tensorboard/backend:application",
"//tensorflow/tensorboard/backend/event_processing:event_accumulator",
"//tensorflow/tensorboard/backend/event_processing:event_multiplexer",
"@org_pocoo_werkzeug//:werkzeug",
"@six_archive//:six",
],
)
filegroup(
name = "all_files",
srcs = glob(["**"]),
visibility = ["//tensorflow:__pkg__"],
)

View File

@ -0,0 +1,69 @@
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""The TensorBoard Distributions (a.k.a. compressed histograms) plugin."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from werkzeug import wrappers
from tensorflow.tensorboard.backend import http_util
from tensorflow.tensorboard.backend.event_processing import event_accumulator
from tensorflow.tensorboard.plugins import base_plugin
_PLUGIN_PREFIX_ROUTE = event_accumulator.COMPRESSED_HISTOGRAMS
class DistributionsPlugin(base_plugin.TBPlugin):
"""Distributions Plugin for TensorBoard."""
plugin_name = _PLUGIN_PREFIX_ROUTE
def get_plugin_apps(self, multiplexer, unused_logdir):
self._multiplexer = multiplexer
return {
'/distributions': self.distributions_route,
'/tags': self.tags_route,
}
def is_active(self):
"""This plugin is active iff any run has at least one relevant tag."""
return any(self.index_impl().values())
def index_impl(self):
return {
run_name: run_data[event_accumulator.COMPRESSED_HISTOGRAMS]
for (run_name, run_data) in self._multiplexer.Runs().items()
if event_accumulator.COMPRESSED_HISTOGRAMS in run_data
}
def distributions_impl(self, tag, run):
"""Result of the form `(body, mime_type)`."""
values = self._multiplexer.CompressedHistograms(run, tag)
return (values, 'application/json')
@wrappers.Request.application
def tags_route(self, request):
index = self.index_impl()
return http_util.Respond(request, index, 'application/json')
@wrappers.Request.application
def distributions_route(self, request):
"""Given a tag and single run, return array of compressed histograms."""
tag = request.args.get('tag')
run = request.args.get('run')
(body, mime_type) = self.distributions_impl(tag, run)
return http_util.Respond(request, body, mime_type)

View File

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Integration tests for the Distributions Plugin."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os.path
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf
from tensorflow.tensorboard.backend.event_processing import event_accumulator
from tensorflow.tensorboard.backend.event_processing import event_multiplexer
from tensorflow.tensorboard.plugins.distributions import distributions_plugin
class DistributionsPluginTest(tf.test.TestCase):
_STEPS = 99
_DISTRIBUTION_TAG = 'my-favorite-distribution'
_SCALAR_TAG = 'my-boring-scalars'
_RUN_WITH_DISTRIBUTION = '_RUN_WITH_DISTRIBUTION'
_RUN_WITH_SCALARS = '_RUN_WITH_SCALARS'
def set_up_with_runs(self, run_names):
self.logdir = self.get_temp_dir()
for run_name in run_names:
self.generate_run(run_name)
multiplexer = event_multiplexer.EventMultiplexer(size_guidance={
# don't truncate my test data, please
event_accumulator.COMPRESSED_HISTOGRAMS:
self._STEPS,
})
multiplexer.AddRunsFromDirectory(self.logdir)
multiplexer.Reload()
self.plugin = distributions_plugin.DistributionsPlugin()
self.apps = self.plugin.get_plugin_apps(multiplexer, None)
def generate_run(self, run_name):
if run_name == self._RUN_WITH_DISTRIBUTION:
(use_distributions, use_scalars) = (True, False)
elif run_name == self._RUN_WITH_SCALARS:
(use_distributions, use_scalars) = (False, True)
else:
assert False, 'Invalid run name: %r' % run_name
tf.reset_default_graph()
sess = tf.Session()
placeholder = tf.placeholder(tf.float32, shape=[3])
if use_distributions:
tf.summary.histogram(self._DISTRIBUTION_TAG, placeholder)
if use_scalars:
tf.summary.scalar(self._SCALAR_TAG, tf.reduce_mean(placeholder))
summ = tf.summary.merge_all()
subdir = os.path.join(self.logdir, run_name)
writer = tf.summary.FileWriter(subdir)
writer.add_graph(sess.graph)
for step in xrange(self._STEPS):
feed_dict = {placeholder: [1 + step, 2 + step, 3 + step]}
s = sess.run(summ, feed_dict=feed_dict)
writer.add_summary(s, global_step=step)
writer.close()
def test_index(self):
self.set_up_with_runs([self._RUN_WITH_DISTRIBUTION,
self._RUN_WITH_SCALARS])
self.assertEqual({
self._RUN_WITH_DISTRIBUTION: [self._DISTRIBUTION_TAG],
self._RUN_WITH_SCALARS: [],
}, self.plugin.index_impl())
def _test_distributions_json(self, run_name, should_have_distributions):
self.set_up_with_runs([self._RUN_WITH_DISTRIBUTION,
self._RUN_WITH_SCALARS])
if should_have_distributions:
(data, mime_type) = self.plugin.distributions_impl(
self._DISTRIBUTION_TAG, run_name)
self.assertEqual('application/json', mime_type)
self.assertEqual(len(data), self._STEPS)
for i in xrange(self._STEPS):
self.assertEqual(i, data[i].step)
else:
with self.assertRaises(KeyError):
self.plugin.distributions_impl(
self._DISTRIBUTION_TAG, run_name)
def test_distributions_json_with_scalars(self):
self._test_distributions_json(self._RUN_WITH_DISTRIBUTION, True)
def test_distributions_json_with_histogram(self):
self._test_distributions_json(self._RUN_WITH_SCALARS, False)
def test_active_with_distribution(self):
self.set_up_with_runs([self._RUN_WITH_DISTRIBUTION])
self.assertTrue(self.plugin.is_active())
def test_active_with_scalars(self):
self.set_up_with_runs([self._RUN_WITH_SCALARS])
self.assertFalse(self.plugin.is_active())
def test_active_with_both(self):
self.set_up_with_runs([self._RUN_WITH_DISTRIBUTION,
self._RUN_WITH_SCALARS])
self.assertTrue(self.plugin.is_active())
if __name__ == '__main__':
tf.test.main()

View File

@ -33,6 +33,7 @@ from werkzeug import serving
from tensorflow.tensorboard.backend import application from tensorflow.tensorboard.backend import application
from tensorflow.tensorboard.backend.event_processing import event_file_inspector as efi from tensorflow.tensorboard.backend.event_processing import event_file_inspector as efi
from tensorflow.tensorboard.plugins.audio import audio_plugin from tensorflow.tensorboard.plugins.audio import audio_plugin
from tensorflow.tensorboard.plugins.distributions import distributions_plugin
from tensorflow.tensorboard.plugins.histograms import histograms_plugin from tensorflow.tensorboard.plugins.histograms import histograms_plugin
from tensorflow.tensorboard.plugins.images import images_plugin from tensorflow.tensorboard.plugins.images import images_plugin
from tensorflow.tensorboard.plugins.projector import projector_plugin from tensorflow.tensorboard.plugins.projector import projector_plugin
@ -204,10 +205,11 @@ def main(unused_argv=None):
return 0 return 0
else: else:
plugins = [ plugins = [
audio_plugin.AudioPlugin(),
histograms_plugin.HistogramsPlugin(),
images_plugin.ImagesPlugin(),
scalars_plugin.ScalarsPlugin(), scalars_plugin.ScalarsPlugin(),
images_plugin.ImagesPlugin(),
audio_plugin.AudioPlugin(),
distributions_plugin.DistributionsPlugin(),
histograms_plugin.HistogramsPlugin(),
projector_plugin.ProjectorPlugin(), projector_plugin.ProjectorPlugin(),
text_plugin.TextPlugin(), text_plugin.TextPlugin(),
] ]