diff --git a/tensorflow/tensorboard/components/tf-backend/backend.ts b/tensorflow/tensorboard/components/tf-backend/backend.ts index 478f8b7af561f0a69ea8bb4c649cafb32d1607cb..b8898340aafd830f177df030219ae92eb09e5265 100644 --- a/tensorflow/tensorboard/components/tf-backend/backend.ts +++ b/tensorflow/tensorboard/components/tf-backend/backend.ts @@ -44,13 +44,23 @@ module TF.Backend { export interface Histogram { min: number; max: number; - nItems: number; - sum: number; - sumSquares: number; + nItems?: number; + sum?: number; + sumSquares?: number; bucketRightEdges: number[]; bucketCounts: number[]; } + export interface HistogramBin { + x: number, + dx: number, + y: number + } + export type HistogramSeriesDatum = HistogramSeries & Datum; + export interface HistogramSeries { + bins: HistogramBin[] + } + export type ImageDatum = Datum & Image export interface Image { width: number; @@ -169,11 +179,20 @@ module TF.Backend { /** * Return a promise containing HistogramDatums for given run and tag. */ - public histogram(tag: string, run: string): Promise> { + public histogram(tag: string, run: string): Promise> { let p: Promise[]>; let url = this.router.histograms(tag, run); p = this.requestManager.request(url); - return p.then(map(detupler(createHistogram))); + return p.then(map(detupler(createHistogram))) + .then(function(histos) { + return histos.map(function(histo, i) { + return { + wall_time: histo.wall_time, + step: histo.step, + bins: convertBins(histo) + }; + }); + }); } /** @@ -277,6 +296,43 @@ module TF.Backend { }; }; + /** + * Takes histogram data as stored by tensorboard backend and converts it to + * the standard d3 histogram data format to make it more compatible and easier to + * visualize. When visualizing histograms, having the left edge and width makes + * things quite a bit easier. + * + * @param {histogram} Histogram - A histogram from tensorboard backend. + * @return {HistogramBin[]} - Each bin has an x (left edge), a dx (width), and a y (count). + * + * If given rightedges are inclusive, then these left edges (x) are exclusive. + */ + export function convertBins (histogram: Histogram) { + if (histogram.bucketRightEdges.length !== histogram.bucketCounts.length) { + throw(new Error("Edges and counts are of different lengths.")) + } + + var previousRightEdge = histogram.min; + return histogram.bucketRightEdges.map(function(rightEdge: number, i: number) { + + // Use the previous bin's rightEdge as the new leftEdge + var left = previousRightEdge; + + // We need to clip the rightEdge because right-most edge can be + // infinite-sized + var right = Math.min(histogram.max, rightEdge); + + // Store rightEdgeValue for next iteration + previousRightEdge = rightEdge; + + return { + x: left, + dx: right - left, + y: histogram.bucketCounts[i] + }; + }); + } + // The following interfaces (TupleData, HistogramTuple, CompressedHistogramTuple, // and ImageMetadata) describe how the data is sent over from the backend, and thus // wall_time, step, value diff --git a/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts b/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts index 3c2911fe8313a70cf6278db0a2ff1280ea6c7a78..989db78741329c6d841c04fe8b8fbea3990e3246 100644 --- a/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts +++ b/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts @@ -78,13 +78,7 @@ module TF.Backend { backend.histogram("histo1", "run1").then((histos) => { var histo = histos[0]; assertIsDatum(histo); - assert.isNumber(histo.min); - assert.isNumber(histo.max); - assert.isNumber(histo.sum); - assert.isNumber(histo.sumSquares); - assert.isNumber(histo.nItems); - assert.instanceOf(histo.bucketRightEdges, Array); - assert.instanceOf(histo.bucketRightEdges, Array); + assert.instanceOf(histo.bins, Array); done(); }); }); @@ -156,4 +150,71 @@ module TF.Backend { assert.deepEqual(getTags(empty2), []); }); }); + + describe("Verify that the histogram format conversion works.", function() { + + function assertHistogramEquality(h1, h2) { + h1.forEach(function(b1, i) { + var b2 = h2[i]; + assert.closeTo(b1.x, b2.x, 1e-10); + assert.closeTo(b1.dx, b2.dx, 1e-10); + assert.closeTo(b1.y, b2.y, 1e-10); + }); + } + + it("Throws and error if the inputs are of different lengths", function() { + assert.throws(function() { + convertBins({bucketRightEdges:[0], bucketCounts:[1, 2], min: 1, max: 2}); + }, "Edges and counts are of different lengths.") + }); + + it("Handles data with no bins", function() { + assert.deepEqual(convertBins({bucketRightEdges: [], bucketCounts: [], min: 0, max: 0}), []); + }); + + it("Handles data with one bin", function() { + var counts = [1]; + var rightEdges = [1.21e-12]; + var histogram = [ + { x: 1.1e-12, dx: 1.21e-12 - 1.1e-12, y: 1 } + ]; + var newHistogram = convertBins({bucketRightEdges: rightEdges, bucketCounts: counts, min: 1.1e-12, max: 1.21e-12}); + assertHistogramEquality(newHistogram, histogram); + }); + + it("Handles data with two bins.", function() { + var counts = [1, 2]; + var rightEdges = [1.1e-12, 1.21e-12]; + var histogram = [ + { x: 1.0e-12, dx: 1.1e-12 - 1.0e-12, y: 1 }, + { x: 1.1e-12, dx: 1.21e-12 - 1.1e-12, y: 2 } + ]; + var newHistogram = convertBins({bucketRightEdges: rightEdges, bucketCounts: counts, min: 1.0e-12, max: 1.21e-12}); + assertHistogramEquality(newHistogram, histogram); + }); + + it("Handles a domain that crosses zero, but doesn't include zero as an edge.", function() { + var counts = [1, 2]; + var rightEdges = [-1.0e-12, 1.0e-12]; + var histogram = [ + { x: -1.1e-12, dx: 1.1e-12 - 1.0e-12, y: 1 }, + { x: -1.0e-12, dx: 2.0e-12, y: 2 } + ]; + var newHistogram = convertBins({bucketRightEdges: rightEdges, bucketCounts: counts, min: -1.1e-12, max: 1.0e-12}); + assertHistogramEquality(newHistogram, histogram); + }); + + it("Handles a right-most right edge that extends to very large number.", function() { + var counts = [1, 2, 3]; + var rightEdges = [0, 1.0e-12, 1.0e14]; + var histogram = [ + { x: -1.0e-12, dx: 1.0e-12, y: 1 }, + { x: 0, dx: 1.0e-12, y: 2 }, + { x: 1.0e-12, dx: 1.1e-12 - 1.0e-12, y: 3 } + ]; + var newHistogram = convertBins({bucketRightEdges: rightEdges, bucketCounts: counts, min: -1.0e-12, max: 1.1e-12}); + assertHistogramEquality(newHistogram, histogram); + }); + + }); } diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/rebin.ts b/tensorflow/tensorboard/components/tf-histogram-dashboard/rebin.ts new file mode 100644 index 0000000000000000000000000000000000000000..932734af9a6632715aa77f21e5691f7b7b89cd57 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/rebin.ts @@ -0,0 +1,40 @@ +module TF.Histogram { + + /** + * Re-bins histogram data into uniform-width bins. Assumes a uniform distribution of values in given bins. + * + * @param {HistogramBin[]} bins - The original histogram data, + * @param {number} numberOfBins - The number of uniform-width bins to split the data into. + * @return {HistogramBin[]} - Re-binned histogram data. Does not modify original data, returns a new array. + */ + export function rebinHistogram(bins: TF.Backend.HistogramBin[], numberOfBins: number) { + if (bins.length === 0) return []; + + var oldBinsXExtent = [ + d3.min(bins, function(old:any) { return old.x; }), + d3.max(bins, function(old:any) { return old.x + old.dx; }) + ]; + + var newDx: number = (oldBinsXExtent[1] - oldBinsXExtent[0]) / numberOfBins; + + var newBins: TF.Backend.HistogramBin[] = d3.range(oldBinsXExtent[0], oldBinsXExtent[1], newDx).map(function(newX) { + + // Take the count of each existing bin, multiply it by the proportion of + // overlap with the new bin, then sum and store as the count for new + // bin. If no overlap, will add zero, if 100% overlap, will include full + // count into new bin. + var newY = d3.sum(bins.map(function(old) { + var intersectDx = Math.min(old.x + old.dx, newX + newDx) - Math.max(old.x, newX); + return (intersectDx > 0) ? (intersectDx / old.dx) * old.y : 0; + })); + + return { + x: newX, + dx: newDx, + y: newY + }; + }); + + return newBins; + } +} diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/test/index.html b/tensorflow/tensorboard/components/tf-histogram-dashboard/test/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c645f7251bd15ba9e71c7518fcf01d7c64357897 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/test/index.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/test/rebinTests.ts b/tensorflow/tensorboard/components/tf-histogram-dashboard/test/rebinTests.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfdcdd36529a494886bb52efe0fe9972d9bc8ff4 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/test/rebinTests.ts @@ -0,0 +1,75 @@ +/* Copyright 2015 Google Inc. 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. +==============================================================================*/ + +module TF.Histogram { + let assert = chai.assert; + + describe("Rebin", function() { + + var assertHistogramEquality = function(h1, h2) { + h1.forEach(function(b1, i) { + var b2 = h2[i]; + assert.closeTo(b1.x, b2.x, 1e-10); + assert.closeTo(b1.dx, b2.dx, 1e-10); + assert.closeTo(b1.y, b2.y, 1e-10); + }); + } + + // + // Rebinning + // + + it("Returns an empty array if you don't have any bins", function() { + assert.deepEqual(rebinHistogram([], 10), []); + }); + + it("Collapses two bins into one.", function() { + var histogram = [ + {x: 0, dx: 1, y: 1}, + {x: 1, dx: 1, y: 2} + ]; + var oneBin = [ + {x: 0, dx: 2, y: 3} + ]; + assertHistogramEquality(rebinHistogram(histogram, 1), oneBin); + }); + + it("Splits one bin into two.", function() { + var histogram = [ + {x: 0, dx: 1, y: 3} + ]; + var twoBin = [ + {x: 0, dx: 0.5, y: 1.5}, + {x: 0.5, dx: 0.5, y: 1.5} + ]; + assertHistogramEquality(rebinHistogram(histogram, 2), twoBin); + }); + + it("Regularizes non-uniform bins.", function() { + var histogram = [ + {x: 0, dx: 2, y: 3}, + {x: 2, dx: 3, y: 3}, + {x: 5, dx: 1, y: 1} + ]; + var twoBin = [ + {x: 0, dx: 3, y: 4}, + {x: 3, dx: 3, y: 3} + ]; + assertHistogramEquality(rebinHistogram(histogram, 2), twoBin); + }); + + }); + +} diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-vz-histogram-series.html b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-vz-histogram-series.html new file mode 100644 index 0000000000000000000000000000000000000000..8082ec4f70effb0ffec9b21793898c825ff6ba1d --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-vz-histogram-series.html @@ -0,0 +1,405 @@ + + + + + + + + +