aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tensorboard/components/tf-categorizer
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/tensorboard/components/tf-categorizer')
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/categorizer.ts133
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/demo/index.html97
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/index.html18
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts139
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html103
5 files changed, 490 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts b/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts
new file mode 100644
index 0000000000..e05078279e
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts
@@ -0,0 +1,133 @@
+/// <reference path="../../typings/tsd.d.ts" />
+
+module Categorizer {
+ /**
+ * This module contains methods that allow sorting tags into "categories".
+ * A category contains a name and a list of tags.
+ * The sorting strategy is defined by a "CustomCategorization", which contains
+ * "categoryDefinitions" which are regex rules used to construct a category.
+ * E.g. the regex rule "xent" will create a category called "xent" that
+ * contains values whose tags match the regex.
+ *
+ * After custom categories are evaluated, the tags are sorted by a hardcoded
+ * fallback categorizer, which may, for example, group tags into categories
+ * based on their top namespace.
+ */
+
+ export interface Category {
+ // Categories that data is sorted into
+ name: string;
+ tags: string[];
+ }
+
+ export interface CustomCategorization {
+ // Defines a categorization strategy
+ categoryDefinitions: string[];
+ fallbackCategorizer: string;
+ /* {"TopLevelNamespaceCategorizer",
+ "LegacyUnderscoreCategorizer"} */
+ }
+
+ export interface Categorizer {
+ // Function that generates categories
+ (tags: string[]): Category[];
+ }
+
+ /* Canonical TensorFlow ops are namespaced using forward slashes.
+ * This fallback categorizer categorizes by the top-level namespace.
+ */
+ export var topLevelNamespaceCategorizer: Categorizer = splitCategorizer(/\//);
+
+ // Try to produce good categorizations on legacy graphs, which often
+ // are namespaced like l1_foo/bar or l2_baz/bam.
+ // If there is no leading underscore before the first forward slash,
+ // then it behaves the same as topLevelNamespaceCategorizer
+ export var legacyUnderscoreCategorizer: Categorizer = splitCategorizer(/[\/_]/);
+
+ export function fallbackCategorizer(s: string): Categorizer {
+ switch (s) {
+ case "TopLevelNamespaceCategorizer":
+ return topLevelNamespaceCategorizer;
+ case "LegacyUnderscoreCategorizer":
+ return legacyUnderscoreCategorizer;
+ default:
+ throw new Error("Unrecognized categorization strategy: " + s);
+ }
+ }
+
+ /* An "extractor" is a function that takes a tag name, and "extracts" a category name.
+ * This function takes an extractor, and produces a categorizer.
+ * Currently, it is just used for the fallbackCategorizer, but we may want to
+ * refactor the general categorization logic to use the concept of extractors.
+ */
+ function extractorToCategorizer(extractor: (s: string) => string): Categorizer {
+ return (tags: string[]): Category[] => {
+ if (tags.length === 0) {
+ return [];
+ }
+ var sortedTags = tags.slice().sort();
+ var categories: Category[] = [];
+ var currentCategory = {
+ name: extractor(sortedTags[0]),
+ tags: [],
+ };
+ sortedTags.forEach((t: string) => {
+ var topLevel = extractor(t);
+ if (currentCategory.name !== topLevel) {
+ categories.push(currentCategory);
+ currentCategory = {
+ name: topLevel,
+ tags: [],
+ };
+ }
+ currentCategory.tags.push(t);
+ });
+ categories.push(currentCategory);
+ return categories;
+ };
+ }
+
+ function splitCategorizer(r: RegExp): Categorizer {
+ var extractor = (t: string) => {
+ return t.split(r)[0];
+ };
+ return extractorToCategorizer(extractor);
+ }
+
+ export interface CategoryDefinition {
+ name: string;
+ matches: (t: string) => boolean;
+ }
+
+ export function defineCategory(ruledef: string): CategoryDefinition {
+ var r = new RegExp(ruledef);
+ var f = function(tag: string): boolean {
+ return r.test(tag);
+ };
+ return { name: ruledef, matches: f };
+ }
+
+ export function _categorizer(rules: CategoryDefinition[], fallback: Categorizer) {
+ return function(tags: string[]): Category[] {
+ var remaining: d3.Set = d3.set(tags);
+ var userSpecified = rules.map((def: CategoryDefinition) => {
+ var tags: string[] = [];
+ remaining.forEach((t: string) => {
+ if (def.matches(t)) {
+ tags.push(t);
+ }
+ });
+ var cat = { name: def.name, tags: tags.sort() };
+ return cat;
+ });
+ var defaultCategories = fallback(remaining.values());
+ return userSpecified.concat(defaultCategories);
+ };
+ }
+
+ export function categorizer(s: CustomCategorization): Categorizer {
+ var rules = s.categoryDefinitions.map(defineCategory);
+ var fallback = fallbackCategorizer(s.fallbackCategorizer);
+ return _categorizer(rules, fallback);
+ };
+}
diff --git a/tensorflow/tensorboard/components/tf-categorizer/demo/index.html b/tensorflow/tensorboard/components/tf-categorizer/demo/index.html
new file mode 100644
index 0000000000..ea3f162aa5
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/demo/index.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <script src="../../../bower_components/d3/d3.js"></script>
+ <link rel="import" href="../tf-categorizer.html">
+ <link rel="import" href="../../../bower_components/iron-flex-layout/classes/iron-flex-layout.html">
+
+ </head>
+ <body>
+ <style>
+ </style>
+ <dom-module id="x-demo">
+ <style>
+ .container {
+ width: 255px;
+ padding: 10px;
+ border: 1px solid var(--paper-indigo-900);
+ border-radius: 5px;
+ position: fixed;
+ }
+ :host {
+ margin: 0px;
+ }
+
+ .categories {
+ font-family: "RobotoDraft",Helvetica;
+ margin-left: 300px;
+ width: 500px;
+ border: 1px solid var(--paper-indigo-500);
+ border-radius: 5px;
+ }
+
+ .category {
+ background-color: var(--paper-indigo-50);
+ margin: 20px;
+ padding: 20px;
+ border-radius: 5px;
+ }
+
+ .cat-name {
+ font-size: 20px;
+ }
+
+ .tag {
+ border-radius: 5px;
+ padding: 5px;
+ margin: 5px;
+ background-color: var(--paper-indigo-900);
+ color: white;
+ }
+ </style>
+ <template>
+ <div class="container">
+ <tf-categorizer categories="{{categories}}" tags="[[tags]]" id="demo"></tf-categorizer>
+ </div>
+ <div class="categories">
+ <template is="dom-repeat" items="[[categories]]">
+ <div class="category">
+ <p class="cat-name">Category: <span>[[item.name]]</span></p>
+ <div class="tags-container layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]">
+ <span class="tag layout vertical center-center">[[item]]</span>
+ </template>
+ </div>
+ </template>
+ </div>
+ </template>
+ <script>
+
+ function tagsGenerator() {
+ var tags = ["special1", "special2", "special3", "special4", "special5"];
+ ["l1", "l2", "l3", "l4", "l5"].forEach(function(l) {
+ ["foo", "bar", "baz", "boink", "zod", "specialx"].forEach(function(x) {
+ tags.push(l + "/" + x);
+ });
+ });
+ return tags;
+ }
+
+ Polymer({
+ is: "x-demo",
+ properties: {
+ tags: { type: Array, value: tagsGenerator },
+ },
+ });
+ </script>
+ </dom-module>
+
+ <x-demo id="demo"></x-demo>
+ </body>
+ <script>
+ HTMLImports.whenReady(function() {
+ window.demo = document.getElementById("demo");
+ })
+ </script>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-categorizer/index.html b/tensorflow/tensorboard/components/tf-categorizer/index.html
new file mode 100644
index 0000000000..f08a125f7c
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+
+ <title>tf-categorizer</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../../bower_components/iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+ <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts b/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts
new file mode 100644
index 0000000000..be09c56c41
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts
@@ -0,0 +1,139 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+/// <reference path="../categorizer.ts" />
+var assert = chai.assert;
+
+module Categorizer {
+ describe("categorizer", () => {
+ describe("topLevelNamespaceCategorizer", () => {
+ it("returns empty array on empty tags", () => {
+ assert.lengthOf(topLevelNamespaceCategorizer([]), 0);
+ });
+
+ it("handles a simple case", () => {
+ var simple = ["foo1/bar", "foo1/zod", "foo2/bar", "foo2/zod",
+ "gosh/lod/mar", "gosh/lod/ned"];
+ var expected = [
+ { name: "foo1", tags: ["foo1/bar", "foo1/zod"] },
+ { name: "foo2", tags: ["foo2/bar", "foo2/zod"] },
+ { name: "gosh", tags: ["gosh/lod/mar", "gosh/lod/ned"] },
+ ];
+ assert.deepEqual(topLevelNamespaceCategorizer(simple), expected);
+ });
+
+ it("orders the categories", () => {
+ var test = ["e", "f", "g", "a", "b", "c"];
+ var expected = [
+ { name: "a", tags: ["a"] },
+ { name: "b", tags: ["b"] },
+ { name: "c", tags: ["c"] },
+ { name: "e", tags: ["e"] },
+ { name: "f", tags: ["f"] },
+ { name: "g", tags: ["g"] },
+ ];
+ assert.deepEqual(topLevelNamespaceCategorizer(test), expected);
+ });
+
+ it("handles cases where category names overlap node names", () => {
+ var test = ["a", "a/a", "a/b", "a/c", "b", "b/a"];
+ var actual = topLevelNamespaceCategorizer(test);
+ var expected = [
+ { name: "a", tags: ["a", "a/a", "a/b", "a/c"] },
+ { name: "b", tags: ["b", "b/a"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("handles singleton case", () => {
+ assert.deepEqual(topLevelNamespaceCategorizer(["a"]), [{ name: "a", tags: ["a"] }]);
+ });
+ });
+
+ describe("legacyUnderscoreCategorizer", () => {
+ it("splits by shorter of first _ or /", () => {
+ var tags = ["l0_bar/foo", "l0_bar/baz", "l0_foo/wob", "l1_zoink/bla",
+ "l1_wibble/woz", "l1/foo_woink", "l2/wozzle_wizzle"];
+ var actual = legacyUnderscoreCategorizer(tags);
+ var expected = [
+ { name: "l0", tags: ["l0_bar/baz", "l0_bar/foo", "l0_foo/wob"] },
+ { name: "l1", tags: ["l1/foo_woink", "l1_wibble/woz", "l1_zoink/bla"] },
+ { name: "l2", tags: ["l2/wozzle_wizzle"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+ });
+
+ describe("customCategorizer", () => {
+ function noFallbackCategorizer(tags: string[]): Category[] {
+ return [];
+ }
+
+ function testCategorizer(defs: string[],
+ fallback: Categorizer, tags: string[]): Category[] {
+ var catDefs = defs.map(defineCategory);
+ return _categorizer(catDefs, fallback)(tags);
+ }
+
+ it("categorizes by regular expression", () => {
+ var defs = ["foo..", "bar.."];
+ var tags = ["fooab", "fooxa", "barts", "barms"];
+ var actual = testCategorizer(defs, noFallbackCategorizer, tags);
+ var expected = [
+ { name: "foo..", tags: ["fooab", "fooxa"] },
+ { name: "bar..", tags: ["barms", "barts"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("matches non-exclusively", () => {
+ var tags = ["abc", "bar", "zod"];
+ var actual = testCategorizer(["...", "bar"], noFallbackCategorizer, tags);
+ var expected = [
+ { name: "...", tags: ["abc", "bar", "zod"] },
+ { name: "bar", tags: ["bar"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("creates categories for unmatched rules", () => {
+ var actual = testCategorizer(["a", "b", "c"], noFallbackCategorizer, []);
+ var expected = [
+ { name: "a", tags: [] },
+ { name: "b", tags: [] },
+ { name: "c", tags: [] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("category regexs work with special characters", () => {
+ var defs = ["^\\w+$", "^\\d+$", "^\\/..$"];
+ var tags = ["foo", "3243", "/xa"];
+ var actual = testCategorizer(defs, noFallbackCategorizer, tags);
+ var expected = [
+ { name: "^\\w+$", tags: ["3243", "foo"] },
+ { name: "^\\d+$", tags: ["3243"] },
+ { name: "^\\/..$", tags: ["/xa"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("category tags are sorted", () => {
+ var tags = ["a", "z", "c", "d", "e", "x", "f", "y", "g"];
+ var sorted = tags.slice().sort();
+ var expected = [{ name: ".*", tags: sorted}];
+ var actual = testCategorizer([".*"], noFallbackCategorizer, tags);
+ assert.deepEqual(actual, expected);
+ });
+
+ it("if nonexclusive: all tags passed to fallback", () => {
+ var passedToDefault = null;
+ function defaultCategorizer(tags: string[]): Category[] {
+ passedToDefault = tags;
+ return [];
+ }
+ var tags = ["foo", "bar", "foo123"];
+ testCategorizer(["foo"], defaultCategorizer, tags);
+ assert.deepEqual(passedToDefault, tags);
+ });
+ });
+ });
+}
diff --git a/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html b/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html
new file mode 100644
index 0000000000..3672db38a2
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html
@@ -0,0 +1,103 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-toggle-button/paper-toggle-button.html">
+
+<link rel="import" href="../tf-regex-group/tf-regex-group.html">
+<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
+
+<!--
+`tf-categorizer` turns an array of tags into an array of categories
+
+The transformation from tags to categories is controlled by the user, through
+interacting with the categorizer widget.
+
+(See type signatures in categorizer.ts)
+
+Example:
+ <tf-categorizer tags="[[tags]]" categories="{{categories}}"></tf-categorizer>
+
+Public Properties:
+`tags` - Array of strings that are the tags to categorize. Should be one-way bound downward.
+`categories` - Array of Categorizer.Category objects that are generated by the Categorizer.
+ Are readOnly and notify: True. Expected to be one-way bound upward.
+
+The categorizer provides inputs for adding regular expression rules and toggling whether
+categories are exclusive.
+-->
+<dom-module id="tf-categorizer">
+ <template>
+ <div class="inputs">
+ <tf-regex-group id="regex-group" regexes="{{regexes}}"></tf-regex-group>
+ </div>
+ <div id="underscore-categorization">
+ <span>Split On Underscores:</span>
+ <paper-toggle-button checked="{{splitOnUnderscore}}"></paper-toggle-button>
+ </div>
+ <style>
+ :host {
+ display: block;
+ padding-bottom: 5px;
+ padding-top: 5px;
+ }
+
+ .inputs {
+ padding-left: 5px;
+ }
+
+ paper-toggle-button {
+ --paper-toggle-button-checked-button-color: var(--tb-orange-strong);
+ --paper-toggle-button-checked-bar-color: var(--tb-orange-weak);
+ }
+ #underscore-categorization {
+ padding-left: 94px;
+ color: var(--paper-grey-700);
+ font-size: 14px;
+ }
+ </style>
+ </template>
+ <script src="categorizer.js"></script>
+ <script>
+ Polymer({
+ is: "tf-categorizer",
+ properties: {
+ regexes: {type: Array},
+ tags: {type: Array},
+ categoriesAreExclusive: {type: Boolean, value: true},
+ fallbackCategorizer: {
+ type: String,
+ computed: "chooseFallbackCategorizer(splitOnUnderscore)"
+ },
+ splitOnUnderscore: {
+ type: Boolean,
+ value: false,
+ },
+ categorizer: {
+ type: Object,
+ computed: "computeCategorization(regexes.*, categoriesAreExclusive, fallbackCategorizer)",
+ },
+ categories: {type: Array, value: function() {return [];}, notify: true, readOnly: true},
+ },
+ observers: ['recategorize(tags.*, categorizer)'],
+ computeCategorization: function(regexes, categoriesAreExclusive, fallbackCategorizer) {
+ var categorizationStrategy = {
+ categoryDefinitions: regexes.base,
+ categoriesAreExclusive: categoriesAreExclusive,
+ fallbackCategorizer: fallbackCategorizer,
+ };
+ return Categorizer.categorizer(categorizationStrategy);
+ },
+ recategorize: function() {
+ this.debounce("tf-categorizer-recategorize", function (){
+ var categories = this.categorizer(this.tags);
+ this._setCategories(categories);
+ })
+ },
+ chooseFallbackCategorizer: function(splitOnUnderscore) {
+ if (splitOnUnderscore) {
+ return "LegacyUnderscoreCategorizer";
+ } else {
+ return "TopLevelNamespaceCategorizer";
+ }
+ },
+ });
+ </script>
+</dom-module>