"use strict"; /** * @license * Copyright 2018 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. * ============================================================================= */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var engine_1 = require("../engine"); var tensor_1 = require("../tensor"); var tensor_util_env_1 = require("../tensor_util_env"); var util = require("../util"); var axis_util_1 = require("./axis_util"); var concat_split_1 = require("./concat_split"); var operation_1 = require("./operation"); var rand_1 = require("./rand"); var tensor_ops_1 = require("./tensor_ops"); /** * Broadcast an array to a compatible shape NumPy-style. * * The tensor's shape is compared to the broadcast shape from end to beginning. * Ones are prepended to the tensor's shape until is has the same length as * the broadcast shape. If input.shape[i]==shape[i], the (i+1)-th axis is * already broadcast-compatible. If input.shape[i]==1 and shape[i]==N, then * the input tensor is tiled N times along that axis (using tf.tile). * * @param input The tensor that is to be broadcasted. * @param shape The input is to be broadcast to this shape. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function broadcastTo_(x, shape) { var input = tensor_util_env_1.convertToTensor(x, 'broadcastTo', 'x'); var xShape = input.shape; if (shape.some(function (d) { return !(d > 0) || d % 1 !== 0; })) { throw new Error("broadcastTo(): Invalid broadcast shape [" + shape + "]."); } if (shape.length < input.rank) { throw new Error("broadcastTo(): shape.length=" + shape.length + " < input.rank=" + input.rank + "."); } if (shape.length > input.rank) { var newShape = input.shape.slice(); while (newShape.length < shape.length) { newShape.unshift(1); } input = input.reshape(newShape); } var reps = Array.from(shape); for (var i = shape.length - 1; i >= 0; i--) { if (input.shape[i] === shape[i]) { reps[i] = 1; } else if (input.shape[i] !== 1) { throw new Error("broadcastTo(): [" + xShape + "] cannot be broadcast to [" + shape + "]."); } } var axes = reps.map(function (n, i) { return n > 1 ? i : -1; }).filter(function (i) { return i >= 0; }); if (axes.length === 0) { return input.clone(); } return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.tile(input, reps); }, { input: input }, function (dy) { return ({ input: function () { return dy.sum(axes, /*keepDims=*/ true); } }); }); } /** * Creates a new tensor with the same values and shape as the specified * tensor. * * ```js * const x = tf.tensor([1, 2]); * * x.clone().print(); * ``` * * @param x The tensor to clone. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function clone_(x) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'clone', null); var der = function (dy) { return { $x: function () { return dy.toFloat(); } }; }; return engine_1.ENGINE.runKernelFunc(function () { return engine_1.ENGINE.makeTensorFromDataId($x.dataId, $x.shape, $x.dtype); }, { $x: $x }, der); } /** * Create an identity matrix. * * @param numRows Number of rows. * @param numColumns Number of columns. Defaults to `numRows`. * @param batchShape If provided, will add the batch shape to the beginning * of the shape of the returned `tf.Tensor` by repeating the identity * matrix. * @param dtype Data type. * @returns Identity matrix of the specified size and data type, possibly * with batch repetition if `batchShape` is specified. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function eye_(numRows, numColumns, batchShape, dtype) { if (dtype === void 0) { dtype = 'float32'; } if (numColumns == null) { numColumns = numRows; } var buff = buffer([numRows, numColumns], dtype); var n = numRows <= numColumns ? numRows : numColumns; for (var i = 0; i < n; ++i) { buff.set(1, i, i); } var out = buff.toTensor().as2D(numRows, numColumns); if (batchShape == null) { return out; } else { if (batchShape.length === 1) { return exports.tile(exports.expandDims(out, 0), [batchShape[0], 1, 1]); } else if (batchShape.length === 2) { return exports.tile(exports.expandDims(exports.expandDims(out, 0), 0), [batchShape[0], batchShape[1], 1, 1]); } else if (batchShape.length === 3) { return exports.tile(exports.expandDims(exports.expandDims(exports.expandDims(out, 0), 0), 0), [batchShape[0], batchShape[1], batchShape[2], 1, 1]); } else { throw new Error("eye() currently supports only 1D and 2D " + ( // tslint:disable-next-line:no-any "batchShapes, but received " + batchShape.length + "D.")); } } } /** * Creates a `tf.Tensor` with values sampled from a normal distribution. * * ```js * tf.randomNormal([2, 2]).print(); * ``` * * @param shape An array of integers defining the output tensor shape. * @param mean The mean of the normal distribution. * @param stdDev The standard deviation of the normal distribution. * @param dtype The data type of the output. * @param seed The seed for the random number generator. */ /** @doc {heading: 'Tensors', subheading: 'Random'} */ function randomNormal_(shape, mean, stdDev, dtype, seed) { if (mean === void 0) { mean = 0; } if (stdDev === void 0) { stdDev = 1; } if (dtype != null && dtype === 'bool') { throw new Error("Unsupported data type " + dtype); } var randGauss = new rand_1.MPRandGauss(mean, stdDev, dtype, false /* truncated */, seed); var res = buffer(shape, dtype); for (var i = 0; i < res.values.length; i++) { res.values[i] = randGauss.nextValue(); } return res.toTensor(); } /** * Creates a `tf.Tensor` with values sampled from a truncated normal * distribution. * * ```js * tf.truncatedNormal([2, 2]).print(); * ``` * * The generated values follow a normal distribution with specified mean and * standard deviation, except that values whose magnitude is more than 2 * standard deviations from the mean are dropped and re-picked. * * @param shape An array of integers defining the output tensor shape. * @param mean The mean of the normal distribution. * @param stdDev The standard deviation of the normal distribution. * @param dtype The data type of the output tensor. * @param seed The seed for the random number generator. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function truncatedNormal_(shape, mean, stdDev, dtype, seed) { if (mean === void 0) { mean = 0; } if (stdDev === void 0) { stdDev = 1; } if (dtype != null && dtype === 'bool') { throw new Error("Unsupported data type " + dtype); } var randGauss = new rand_1.MPRandGauss(mean, stdDev, dtype, true /* truncated */, seed); var res = buffer(shape, dtype); for (var i = 0; i < res.values.length; i++) { res.values[i] = randGauss.nextValue(); } return res.toTensor(); } /** * Creates a `tf.Tensor` with values sampled from a gamma distribution. * * ```js * tf.randomGamma([2, 2], 1).print(); * ``` * * @param shape An array of integers defining the output tensor shape. * @param alpha The shape parameter of the gamma distribution. * @param beta The inverse scale parameter of the gamma distribution. Defaults * to 1. * @param dtype The data type of the output. Defaults to float32. * @param seed The seed for the random number generator. */ /** @doc {heading: 'Tensors', subheading: 'Random'} */ function randomGamma_(shape, alpha, beta, dtype, seed) { if (beta === void 0) { beta = 1; } if (dtype === void 0) { dtype = 'float32'; } if (beta == null) { beta = 1; } if (dtype == null) { dtype = 'float32'; } if (dtype !== 'float32' && dtype !== 'int32') { throw new Error("Unsupported data type " + dtype); } var rgamma = new rand_1.RandGamma(alpha, beta, dtype, seed); var res = buffer(shape, dtype); for (var i = 0; i < res.values.length; i++) { res.values[i] = rgamma.nextValue(); } return res.toTensor(); } /** * Creates a `tf.Tensor` with values sampled from a uniform distribution. * * The generated values follow a uniform distribution in the range [minval, * maxval). The lower bound minval is included in the range, while the upper * bound maxval is excluded. * * ```js * tf.randomUniform([2, 2]).print(); * ``` * * @param shape An array of integers defining the output tensor shape. * @param minval The lower bound on the range of random values to generate. * Defaults to 0. * @param maxval The upper bound on the range of random values to generate. * Defaults to 1. * @param dtype The data type of the output tensor. Defaults to 'float32'. */ /** @doc {heading: 'Tensors', subheading: 'Random'} */ function randomUniform_(shape, minval, maxval, dtype, seed) { if (minval === void 0) { minval = 0; } if (maxval === void 0) { maxval = 1; } if (dtype === void 0) { dtype = 'float32'; } var res = buffer(shape, dtype); var random = new rand_1.UniformRandom(minval, maxval, null, seed); for (var i = 0; i < res.values.length; i++) { res.values[i] = random.nextValue(); } return res.toTensor(); } /** * Creates a `tf.Tensor` with values sampled from a random number generator * function defined by the user. * * @param shape An array of integers defining the output tensor shape. * @param randFunction A random number generator function which is called * for each element in the output tensor. * @param dtype The data type of the output tensor. Defaults to 'float32'. */ function rand_(shape, randFunction, dtype) { var size = util.sizeFromShape(shape); var values = null; if (dtype == null || dtype === 'float32') { values = new Float32Array(size); } else if (dtype === 'int32') { values = new Int32Array(size); } else if (dtype === 'bool') { values = new Uint8Array(size); } else { throw new Error("Unknown data type " + dtype); } for (var i = 0; i < size; i++) { values[i] = randFunction(); } return engine_1.ENGINE.makeTensor(values, shape, dtype); } /** * Creates a `tf.Tensor` with values drawn from a multinomial distribution. * * ```js * const probs = tf.tensor([.75, .25]); * tf.multinomial(probs, 3).print(); * ``` * * @param logits 1D array with unnormalized log-probabilities, or * 2D array of shape `[batchSize, numOutcomes]`. See the `normalized` * parameter. * @param numSamples Number of samples to draw for each row slice. * @param seed The seed number. * @param normalized Whether the provided `logits` are normalized true * probabilities (sum to 1). Defaults to false. * @return 1D array of shape `[numSamples]`, or 2D array of shape * `[batchSize, numSamples]`, depending on the rank of the input. */ /** @doc {heading: 'Tensors', subheading: 'Random'} */ function multinomial_(logits, numSamples, seed, normalized) { if (normalized === void 0) { normalized = false; } var $logits = tensor_util_env_1.convertToTensor(logits, 'logits', 'multinomial'); var numOutcomes = $logits.size; var origRank = $logits.rank; if (numOutcomes < 2) { throw new Error("Error in multinomial: you need at least 2 outcomes, but got " + (numOutcomes + ".")); } if (origRank > 2) { throw new Error("Rank of probabilities must be 1 or 2, but is " + origRank); } seed = seed || Math.random(); var logits2D = origRank === 1 ? $logits.as2D(1, -1) : $logits; var res = engine_1.ENGINE.runKernelFunc(function (backend) { return backend.multinomial(logits2D, normalized, numSamples, seed); }, { logits2D: logits2D }); return origRank === 1 ? res.as1D() : res; } /** * Creates a one-hot `tf.Tensor`. The locations represented by `indices` take * value `onValue` (defaults to 1), while all other locations take value * `offValue` (defaults to 0). If `indices` is rank `R`, the output has rank * `R+1` with the last axis of size `depth`. * * ```js * tf.oneHot(tf.tensor1d([0, 1], 'int32'), 3).print(); * ``` * * @param indices `tf.Tensor` of indices with dtype `int32`. * @param depth The depth of the one hot dimension. * @param onValue A number used to fill in the output when the index matches * the location. * @param offValue A number used to fill in the output when the index does * not match the location. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function oneHot_(indices, depth, onValue, offValue) { if (onValue === void 0) { onValue = 1; } if (offValue === void 0) { offValue = 0; } if (depth < 2) { throw new Error("Error in oneHot: depth must be >=2, but it is " + depth); } var $indices = tensor_util_env_1.convertToTensor(indices, 'indices', 'oneHot', 'int32'); var outShape = $indices.shape.concat([depth]); $indices = $indices.flatten(); var grad = function (dy) { return { $indices: function () { return tensor_ops_1.zeros($indices.shape, 'float32'); } }; }; var result = engine_1.ENGINE.runKernelFunc(function (backend) { return backend.oneHot($indices, depth, onValue, offValue); }, { $indices: $indices }, grad); return result.reshape(outShape); } /** * Reshapes a `tf.Tensor` to a given shape. * * Given an input tensor, returns a new tensor with the same values as the * input tensor with shape `shape`. * * If one component of shape is the special value -1, the size of that * dimension is computed so that the total size remains constant. In * particular, a shape of [-1] flattens into 1-D. At most one component of * shape can be -1. * * If shape is 1-D or higher, then the operation returns a tensor with shape * shape filled with the values of tensor. In this case, the number of * elements implied by shape must be the same as the number of elements in * tensor. * * ```js * const x = tf.tensor1d([1, 2, 3, 4]); * x.reshape([2, 2]).print(); * ``` * * @param x The input tensor to be reshaped. * @param shape An array of integers defining the output tensor shape. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function reshape_(x, shape) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'reshape', null); shape = util.inferFromImplicitShape(shape, $x.size); util.assert($x.size === util.sizeFromShape(shape), function () { return 'new shape and old shape must have the same number of elements.'; }); var grad = function (dy) { return { x: function () { return dy.reshape($x.shape); } }; }; var attrs = { shape: shape }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.reshape($x, shape); }, { x: $x }, grad, 'Reshape', attrs); } /** * Removes dimensions of size 1 from the shape of a `tf.Tensor`. * * ```js * const x = tf.tensor([1, 2, 3, 4], [1, 1, 4]); * x.squeeze().print(); * ``` * * @param x The input tensor to be squeezed. * @param axis An optional list of numbers. If specified, only * squeezes the dimensions listed. The dimension index starts at 0. It * is an error to squeeze a dimension that is not 1. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function squeeze_(x, axis) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'squeeze'); return exports.reshape($x, util.squeezeShape($x.shape, axis).newShape); } /** * Casts a `tf.Tensor` to a new dtype. * * ```js * const x = tf.tensor1d([1.5, 2.5, 3]); * tf.cast(x, 'int32').print(); * ``` * @param x The input tensor to be casted. * @param dtype The dtype to cast the input tensor to. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function cast_(x, dtype) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'cast'); // Sanity checks. if (!util.isValidDtype(dtype)) { throw new Error("Failed to cast to unknown dtype " + dtype); } if (dtype === 'string' && $x.dtype !== 'string' || dtype !== 'string' && $x.dtype === 'string') { throw new Error('Only strings can be casted to strings'); } var grad = function (dy) { return { x: function () { return dy.clone(); } }; }; var attrs = { dtype: dtype }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.cast($x, dtype); }, { x: $x }, grad, 'Cast', attrs); } /** * Construct a tensor by repeating it the number of times given by reps. * * This operation creates a new tensor by replicating `input` `reps` * times. The output tensor's i'th dimension has `input.shape[i] * * reps[i]` elements, and the values of `input` are replicated * `reps[i]` times along the i'th dimension. For example, tiling * `[a, b, c, d]` by `[2]` produces `[a, b, c, d, a, b, c, d]`. * * ```js * const a = tf.tensor1d([1, 2]); * * a.tile([2]).print(); // or a.tile([2]) * ``` * * ```js * const a = tf.tensor2d([1, 2, 3, 4], [2, 2]); * * a.tile([1, 2]).print(); // or a.tile([1, 2]) * ``` * @param x The tensor to tile. * @param reps Determines the number of replications per dimension. */ /** @doc {heading: 'Tensors', subheading: 'Slicing and Joining'} */ function tile_(x, reps) { var parseAs = null; var $x = tensor_util_env_1.convertToTensor(x, 'x', 'tile', parseAs); util.assert($x.rank === reps.length, function () { return "Error in transpose: rank of input " + $x.rank + " " + ("must match length of reps " + reps + "."); }); var grad = function (dy, saved) { var $x = saved[0]; var derX = function () { var xGrad = tensor_ops_1.zerosLike($x); // TODO(cais): Maybe reduce memory footprint by avoiding repeated // slicing. if ($x.rank === 1) { for (var i = 0; i < reps[0]; ++i) { xGrad = xGrad.add(dy.slice([i * $x.shape[0]], [$x.shape[0]])); } } else if ($x.rank === 2) { for (var i = 0; i < reps[0]; ++i) { for (var j = 0; j < reps[1]; ++j) { xGrad = xGrad.add(dy.slice([i * $x.shape[0], j * $x.shape[1]], [$x.shape[0], $x.shape[1]])); } } } else if ($x.rank === 3) { for (var i = 0; i < reps[0]; ++i) { for (var j = 0; j < reps[1]; ++j) { for (var k = 0; k < reps[2]; ++k) { xGrad = xGrad.add(dy.slice([i * $x.shape[0], j * $x.shape[1], k * $x.shape[2]], [$x.shape[0], $x.shape[1], $x.shape[2]])); } } } } else if ($x.rank === 4) { for (var i = 0; i < reps[0]; ++i) { for (var j = 0; j < reps[1]; ++j) { for (var k = 0; k < reps[2]; ++k) { for (var l = 0; l < reps[3]; ++l) { xGrad = xGrad.add(dy.slice([ i * $x.shape[0], j * $x.shape[1], k * $x.shape[2], l * $x.shape[3] ], [$x.shape[0], $x.shape[1], $x.shape[2], $x.shape[3]])); } } } } } else { throw new Error("Gradient for tile operation is not implemented for rank-" + ($x.rank + " tensors yet.")); } return xGrad; }; return { x: derX }; }; var inputsToSave = [$x]; var attrs = { reps: reps }; return engine_1.ENGINE.runKernelFunc(function (backend, save) { var res = backend.tile($x, reps); save([$x]); return res; }, { x: $x }, grad, 'Tile', attrs, inputsToSave); } /** * Pads a `tf.Tensor1D` with a given value and paddings. See `pad` for details. */ function pad1d_(x, paddings, constantValue) { if (constantValue === void 0) { constantValue = 0; } util.assert(paddings.length === 2, function () { return 'Invalid number of paddings. Must be length of 2.'; }); return exports.pad(x, [paddings], constantValue); } /** * Pads a `tf.Tensor2D` with a given value and paddings. See `pad` for details. */ function pad2d_(x, paddings, constantValue) { if (constantValue === void 0) { constantValue = 0; } util.assert(paddings.length === 2 && paddings[0].length === 2 && paddings[1].length === 2, function () { return 'Invalid number of paddings. Must be length of 2 each.'; }); return exports.pad(x, paddings, constantValue); } /** * Pads a `tf.Tensor3D` with a given value and paddings. See `pad` for details. */ function pad3d_(x, paddings, constantValue) { if (constantValue === void 0) { constantValue = 0; } util.assert(paddings.length === 3 && paddings[0].length === 2 && paddings[1].length === 2 && paddings[2].length === 2, function () { return 'Invalid number of paddings. Must be length of 2 each.'; }); return exports.pad(x, paddings, constantValue); } /** * Pads a `tf.Tensor4D` with a given value and paddings. See `pad` for details. */ function pad4d_(x, paddings, constantValue) { if (constantValue === void 0) { constantValue = 0; } util.assert(paddings.length === 4 && paddings[0].length === 2 && paddings[1].length === 2 && paddings[2].length === 2 && paddings[3].length === 2, function () { return 'Invalid number of paddings. Must be length of 2 each.'; }); return exports.pad(x, paddings, constantValue); } /** * Pads a `tf.Tensor` with a given value and paddings. * * This operation currently only implements the `CONSTANT` mode. * * Also available are stricter rank-specific methods with the same signature * as this method that assert that `paddings` is of given length. * - `tf.pad1d` * - `tf.pad2d` * - `tf.pad3d` * - `tf.pad4d` * * ```js * const x = tf.tensor1d([1, 2, 3, 4]); * x.pad([[1, 2]]).print(); * ``` * @param x The tensor to pad. * @param paddings An array of length `R` (the rank of the tensor), where * each element is a length-2 tuple of ints `[padBefore, padAfter]`, * specifying how much to pad along each dimension of the tensor. * @param constantValue The pad value to use. Defaults to 0. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function pad_(x, paddings, constantValue) { if (constantValue === void 0) { constantValue = 0; } var $x = tensor_util_env_1.convertToTensor(x, 'x', 'pad'); if ($x.rank === 0) { throw new Error('pad(scalar) is not defined. Pass non-scalar to pad'); } var grad = function (dy) { // Pad introduces values around the original tensor, so the gradient // slices the original shape out of the gradient. var begin = paddings.map(function (p) { return p[0]; }); return { x: function () { return dy.slice(begin, $x.shape); } }; }; var attrs = { paddings: paddings, constantValue: constantValue }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.pad($x, paddings, constantValue); }, { x: $x }, grad, 'PadV2', attrs); } /** * Stacks a list of rank-`R` `tf.Tensor`s into one rank-`(R+1)` `tf.Tensor`. * * ```js * const a = tf.tensor1d([1, 2]); * const b = tf.tensor1d([3, 4]); * const c = tf.tensor1d([5, 6]); * tf.stack([a, b, c]).print(); * ``` * * @param tensors A list of tensor objects with the same shape and dtype. * @param axis The axis to stack along. Defaults to 0 (the first dim). */ /** @doc {heading: 'Tensors', subheading: 'Slicing and Joining'} */ function stack_(tensors, axis) { if (axis === void 0) { axis = 0; } var $tensors = tensor_util_env_1.convertToTensorArray(tensors, 'tensors', 'stack'); util.assert($tensors.length >= 1, function () { return 'Pass at least one tensor to tf.stack'; }); if ($tensors.length === 1) { return $tensors[0].expandDims(axis); } var rank = $tensors[0].rank; var shape = $tensors[0].shape; var dtype = $tensors[0].dtype; util.assert(axis <= rank, function () { return 'Axis must be <= rank of the tensor'; }); $tensors.forEach(function (t) { util.assertShapesMatch(shape, t.shape, 'All tensors passed to stack must have matching shapes'); }); $tensors.forEach(function (t) { util.assert(dtype === t.dtype, function () { return 'All tensors passed to stack must have matching dtypes'; }); }); var expandedTensors = $tensors.map(function (t) { return t.expandDims(axis); }); return concat_split_1.concat(expandedTensors, axis); } /** * This operation reshapes the "batch" dimension 0 into `M + 1` dimensions of * shape `blockShape + [batch]`, interleaves these blocks back into the grid * defined by the spatial dimensions `[1, ..., M]`, to obtain a result with * the same rank as the input. The spatial dimensions of this intermediate * result are then optionally cropped according to `crops` to produce the * output. This is the reverse of `tf.spaceToBatchND`. See below for a precise * description. * * ```js * const x = tf.tensor4d([1, 2, 3, 4], [4, 1, 1, 1]); * const blockShape = [2, 2]; * const crops = [[0, 0], [0, 0]]; * * x.batchToSpaceND(blockShape, crops).print(); * ``` * * @param x A `tf.Tensor`. N-D with `x.shape` = `[batch] + spatialShape + * remainingShape`, where spatialShape has `M` dimensions. * @param blockShape A 1-D array. Must have shape `[M]`, all values must * be >= 1. * @param crops A 2-D array. Must have shape `[M, 2]`, all values must be >= 0. * `crops[i] = [cropStart, cropEnd]` specifies the amount to crop from input * dimension `i + 1`, which corresponds to spatial dimension `i`. It is required * that `cropStart[i] + cropEnd[i] <= blockShape[i] * inputShape[i + 1]` * * This operation is equivalent to the following steps: * * 1. Reshape `x` to `reshaped` of shape: `[blockShape[0], ..., * blockShape[M-1], batch / prod(blockShape), x.shape[1], ..., * x.shape[N-1]]` * * 2. Permute dimensions of `reshaped`to produce `permuted` of shape `[batch / * prod(blockShape),x.shape[1], blockShape[0], ..., x.shape[M], * blockShape[M-1],x.shape[M+1], ..., x.shape[N-1]]` * * 3. Reshape `permuted` to produce `reshapedPermuted` of shape `[batch / * prod(blockShape),x.shape[1] * blockShape[0], ..., x.shape[M] * * blockShape[M-1],x.shape[M+1], ..., x.shape[N-1]]` * * 4. Crop the start and end of dimensions `[1, ..., M]` of `reshapedPermuted` * according to `crops` to produce the output of shape: `[batch / * prod(blockShape),x.shape[1] * blockShape[0] - crops[0,0] - crops[0,1], * ..., x.shape[M] * blockShape[M-1] - crops[M-1,0] - * crops[M-1,1],x.shape[M+1], ..., x.shape[N-1]]` */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function batchToSpaceND_(x, blockShape, crops) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'batchToSpaceND'); var prod = blockShape.reduce(function (a, b) { return a * b; }); util.assert($x.rank >= 1 + blockShape.length, function () { return "input rank is " + $x.rank + " but should be > than blockShape.length " + blockShape.length; }); util.assert(crops.length === blockShape.length, function () { return "crops.length is " + crops.length + " but should be equal to blockShape.length " + blockShape.length; }); util.assert($x.shape[0] % prod === 0, function () { return "input tensor batch is " + $x.shape[0] + " but is not divisible by the product of " + ("the elements of blockShape " + blockShape.join(' * ') + " === " + prod); }); var grad = function (dy) { return { $x: function () { return dy.spaceToBatchND(blockShape, crops); } }; }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.batchToSpaceND($x, blockShape, crops); }, { $x: $x }, grad); } /** * This operation divides "spatial" dimensions `[1, ..., M]` of the input into * a grid of blocks of shape `blockShape`, and interleaves these blocks with * the "batch" dimension (0) such that in the output, the spatial * dimensions `[1, ..., M]` correspond to the position within the grid, * and the batch dimension combines both the position within a spatial block * and the original batch position. Prior to division into blocks, * the spatial dimensions of the input are optionally zero padded * according to `paddings`. See below for a precise description. * * ```js * const x = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); * const blockShape = [2, 2]; * const paddings = [[0, 0], [0, 0]]; * * x.spaceToBatchND(blockShape, paddings).print(); * ``` * * @param x A `tf.Tensor`. N-D with `x.shape` = `[batch] + spatialShape + * remainingShape`, where spatialShape has `M` dimensions. * @param blockShape A 1-D array. Must have shape `[M]`, all values must * be >= 1. * @param paddings A 2-D array. Must have shape `[M, 2]`, all values must be >= * 0. `paddings[i] = [padStart, padEnd]` specifies the amount to zero-pad * from input dimension `i + 1`, which corresponds to spatial dimension `i`. It * is required that * `(inputShape[i + 1] + padStart + padEnd) % blockShape[i] === 0` * * This operation is equivalent to the following steps: * * 1. Zero-pad the start and end of dimensions `[1, ..., M]` of the input * according to `paddings` to produce `padded` of shape paddedShape. * * 2. Reshape `padded` to `reshapedPadded` of shape: * `[batch] + [paddedShape[1] / blockShape[0], blockShape[0], ..., * paddedShape[M] / blockShape[M-1], blockShape[M-1]] + remainingShape` * * 3. Permute dimensions of `reshapedPadded` to produce `permutedReshapedPadded` * of shape: `blockShape + [batch] + [paddedShape[1] / blockShape[0], ..., * paddedShape[M] / blockShape[M-1]] + remainingShape` * * 4. Reshape `permutedReshapedPadded` to flatten `blockShape` into the * batch dimension, producing an output tensor of shape: * `[batch * prod(blockShape)] + [paddedShape[1] / blockShape[0], ..., * paddedShape[M] / blockShape[M-1]] + remainingShape` */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function spaceToBatchND_(x, blockShape, paddings) { var $x = tensor_util_env_1.convertToTensor(x, 'x', 'spaceToBatchND'); util.assert($x.rank >= 1 + blockShape.length, function () { return "input rank " + $x.rank + " should be > than [blockShape] " + blockShape.length; }); util.assert(paddings.length === blockShape.length, function () { return "paddings.shape[0] " + paddings.length + " must be equal to [blockShape] " + blockShape.length; }); util.assert($x.shape.reduce(function (a, b, i) { if (i > 0 && i <= blockShape.length) { return a && ((b + paddings[i - 1][0] + paddings[i - 1][1]) % blockShape[i - 1] === 0); } return a; }, true), function () { return "input spatial dimensions " + $x.shape.slice(1) + " with paddings " + paddings.toString() + " must be divisible by blockShapes " + blockShape.toString(); }); var grad = function (dy) { return { $x: function () { return dy.batchToSpaceND(blockShape, paddings); } }; }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.spaceToBatchND($x, blockShape, paddings); }, { $x: $x }, grad); } /** * Unstacks a `tf.Tensor` of rank-`R` into a list of rank-`(R-1)` `tf.Tensor`s. * * ```js * const a = tf.tensor2d([1, 2, 3, 4], [2, 2]); * * tf.unstack(a).forEach(tensor => tensor.print()); * ``` * * @param x A tensor object. * @param axis The axis to unstack along. Defaults to 0 (the first dim). */ /** @doc {heading: 'Tensors', subheading: 'Slicing and Joining'} */ function unstack_(x, axis) { if (axis === void 0) { axis = 0; } axis = axis || 0; var $x = tensor_util_env_1.convertToTensor(x, 'x', 'unstack'); util.assert(axis >= -$x.shape.length && axis < $x.shape.length, function () { return "Axis = " + axis + " is not in [-" + $x.shape.length + ", " + $x.shape.length + ")"; }); if (axis < 0) { axis += $x.shape.length; } var grad = function (dy) { return { x: function () { return exports.stack(dy, axis); } }; }; var attrs = { axis: axis }; return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.unstack($x, axis); }, { x: $x }, grad, 'Unpack', attrs); } /** * Computes the cumulative sum of a `tf.Tensor` along `axis`. * * ```js * const x = tf.tensor([1, 2, 3, 4]); * x.cumsum().print(); * ``` * ```js * const x = tf.tensor([[1, 2], [3, 4]]); * x.cumsum().print(); * ``` * * @param x The input tensor to be summed. * @param axis The axis along which to sum. Optional. Defaults to 0. * @param exclusive Whether to perform exclusive cumulative sum. Optional. * Defaults to false. If set to true then the sum of each tensor entry * does not include its own value, but only the values previous to it * along the specified axis. * @param reverse Whether to sum in the opposite direction. Optional. * Defaults to false. */ /** @doc {heading: 'Operations', subheading: 'Scan'} */ function cumsum_(x, axis, exclusive, reverse) { if (axis === void 0) { axis = 0; } if (exclusive === void 0) { exclusive = false; } if (reverse === void 0) { reverse = false; } var $x = tensor_util_env_1.convertToTensor(x, 'x', 'cumsum'); axis = axis | 0; var permutation = axis_util_1.getAxesPermutation([axis], $x.rank); var permutedX = $x; if (permutation != null) { permutedX = $x.transpose(permutation); } var permutedAxis = axis_util_1.getInnerMostAxes(1, $x.rank)[0]; var grad = function (dy) { return { permutedX: function () { return dy.cumsum(axis, exclusive, !reverse); } }; }; var value = engine_1.ENGINE.runKernelFunc(function (backend) { return backend.cumsum(permutedX, permutedAxis, exclusive, reverse); }, { permutedX: permutedX }, grad); if (permutation != null) { value = value.transpose(permutation); } return value; } /** * Returns a `tf.Tensor` that has expanded rank, by inserting a dimension * into the tensor's shape. * * ```js * const x = tf.tensor1d([1, 2, 3, 4]); * const axis = 1; * x.expandDims(axis).print(); * ``` * * @param x The input tensor whose dimensions to be expanded. * @param axis The dimension index at which to insert shape of `1`. Defaults * to 0 (the first dimension). */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function expandDims_(x, axis) { if (axis === void 0) { axis = 0; } var parseAs = null; var $x = tensor_util_env_1.convertToTensor(x, 'x', 'expandDims', parseAs); util.assert(axis <= $x.rank, function () { return 'Axis must be <= rank of the tensor'; }); var newShape = $x.shape.slice(); if (axis < 0) { // Negative value is counted from the tail of rank. util.assert(-($x.rank + 1) <= axis, function () { return "Axis must be in the interval [" + -($x.rank + 1) + ", " + $x.rank + "]"; }); axis = $x.rank + axis + 1; } newShape.splice(axis, 0, 1); return exports.reshape($x, newShape); } /** * Rearranges data from depth into blocks of spatial data. More specifically, * this op outputs a copy of the input tensor where values from the `depth` * dimension are moved in spatial blocks to the `height` and `width` dimensions. * The attr `blockSize` indicates the input block size and how the data is * moved. * * - Chunks of data of size `blockSize * blockSize` from depth are rearranged * into non-overlapping blocks of size `blockSize x blockSize` * * - The width the output tensor is `inputWidth * blockSize`, whereas the * height is `inputHeight * blockSize` * * - The Y, X coordinates within each block of the output image are determined * by the high order component of the input channel index * * - The depth of the input tensor must be divisible by `blockSize * * blockSize` * * The `dataFormat` attr specifies the layout of the input and output tensors * with the following options: "NHWC": [ `batch, height, width, channels` ] * "NCHW": [ `batch, channels, height, width` ] * * ```js * const x = tf.tensor4d([1, 2, 3, 4], [1, 1, 1, 4]); * const blockSize = 2; * const dataFormat = "NHWC"; * * tf.depthToSpace(x, blockSize, dataFormat).print(); * ``` * * @param x The input tensor of rank 4 * @param blockSIze An `int` that is `>= 2`. The size of the spatial block * @param dataFormat An optional string from: "NHWC", "NCHW". Defaults to "NHWC" */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function depthToSpace_(x, blockSize, dataFormat) { if (dataFormat === void 0) { dataFormat = 'NHWC'; } var $x = tensor_util_env_1.convertToTensor(x, 'x', 'depthToSpace'); var inputHeight = (dataFormat === 'NHWC') ? $x.shape[1] : $x.shape[2]; var inputWidth = (dataFormat === 'NHWC') ? $x.shape[2] : $x.shape[3]; var inputDepth = (dataFormat === 'NHWC') ? $x.shape[3] : $x.shape[1]; util.assert(inputHeight * blockSize >= 0, function () { return "Negative dimension size caused by overflow when multiplying\n " + inputHeight + " and " + blockSize + " for depthToSpace with input shape\n " + $x.shape; }); util.assert(inputWidth * blockSize >= 0, function () { return "Negative dimension size caused by overflow when multiplying\n " + inputWidth + " and " + blockSize + " for depthToSpace with input shape\n " + $x.shape; }); util.assert((inputDepth % (blockSize * blockSize) === 0), function () { return "Dimension size must be evenly divisible by " + blockSize * blockSize + " but is " + inputDepth + " for depthToSpace with input shape " + $x.shape; }); return engine_1.ENGINE.runKernelFunc(function (backend) { return backend.depthToSpace($x, blockSize, dataFormat); }, { $x: $x }); } /** * Computes the difference between two lists of numbers. * * Given a Tensor `x` and a Tensor `y`, this operation returns a Tensor `out` * that represents all values that are in `x` but not in `y`. The returned * Tensor `out` is sorted in the same order that the numbers appear in `x` * (duplicates are preserved). This operation also returns a Tensor indices that * represents the position of each out element in `x`. In other words: * * `out[i] = x[idx[i]] for i in [0, 1, ..., out.length - 1]` * * ```js * const x = [1, 2, 3, 4, 5, 6]; * const y = [1, 3, 5]; * * const [out, indices] = await tf.setdiff1dAsync(x, y); * out.print(); // [2, 4, 6] * indices.print(); // [1, 3, 5] * ``` * * @param x 1-D Tensor. Values to keep. * @param y 1-D Tensor. Must have the same type as x. Values to exclude in the * output. * @returns Promise of Tensor tuple [out, indices]. * out: Tensor with the same type as x. * indices: A Tensor of type int32. */ /** @doc {heading: 'Tensors', subheading: 'Transformations'} */ function setdiff1dAsync_(x, y) { return __awaiter(this, void 0, void 0, function () { var $x, $y, xVals, yVals, ySet, outputSize, i, buffer, indices, i, p; return __generator(this, function (_a) { switch (_a.label) { case 0: $x = tensor_util_env_1.convertToTensor(x, 'x', 'setdiff1d'); $y = tensor_util_env_1.convertToTensor(y, 'y', 'setdiff1d'); util.assert($x.dtype === $y.dtype, function () { return "x and y should have the same dtype, but got x (" + $x.dtype + ") and y (" + $y.dtype + ")."; }); util.assert($x.rank === 1, function () { return "x should be 1D tensor, but got x (" + $x.shape + ")."; }); util.assert($y.rank === 1, function () { return "y should be 1D tensor, but got y (" + $y.shape + ")."; }); return [4 /*yield*/, $x.data()]; case 1: xVals = _a.sent(); return [4 /*yield*/, $y.data()]; case 2: yVals = _a.sent(); ySet = new Set(yVals); outputSize = 0; for (i = 0; i < xVals.length; i++) { if (!ySet.has(xVals[i])) { outputSize++; } } buffer = new tensor_1.TensorBuffer([outputSize], $x.dtype); indices = new tensor_1.TensorBuffer([outputSize], 'int32'); for (i = 0, p = 0; i < xVals.length; i++) { if (!ySet.has(xVals[i])) { buffer.values[p] = xVals[i]; indices.values[p] = i; p++; } } return [2 /*return*/, [buffer.toTensor(), indices.toTensor()]]; } }); }); } /** * Creates an empty `tf.TensorBuffer` with the specified `shape` and `dtype`. * * The values are stored in CPU as `TypedArray`. Fill the buffer using * `buffer.set()`, or by modifying directly `buffer.values`. * * When done, call `buffer.toTensor()` to get an immutable `tf.Tensor` with * those values. * * ```js * // Create a buffer and set values at particular indices. * const buffer = tf.buffer([2, 2]); * buffer.set(3, 0, 0); * buffer.set(5, 1, 0); * * // Convert the buffer back to a tensor. * buffer.toTensor().print(); * ``` * * @param shape An array of integers defining the output tensor shape. * @param dtype The dtype of the buffer. Defaults to 'float32'. * @param values The values of the buffer as `TypedArray`. Defaults to * zeros. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function buffer(shape, dtype, values) { if (dtype === void 0) { dtype = 'float32'; } dtype = dtype || 'float32'; util.assertNonNegativeIntegerDimensions(shape); return new tensor_1.TensorBuffer(shape, dtype, values); } exports.buffer = buffer; /** * Prints information about the `tf.Tensor` including its data. * * ```js * const verbose = true; * tf.tensor2d([1, 2, 3, 4], [2, 2]).print(verbose); * ``` * @param x The tensor to be printed. * @param verbose Whether to print verbose information about the ` Tensor`, * including dtype and size. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ function print(x, verbose) { if (verbose === void 0) { verbose = false; } console.log(x.toString(verbose)); } exports.print = print; exports.batchToSpaceND = operation_1.op({ batchToSpaceND_: batchToSpaceND_ }); exports.broadcastTo = operation_1.op({ broadcastTo_: broadcastTo_ }); exports.cast = operation_1.op({ cast_: cast_ }); exports.clone = operation_1.op({ clone_: clone_ }); exports.cumsum = operation_1.op({ cumsum_: cumsum_ }); exports.depthToSpace = operation_1.op({ depthToSpace_: depthToSpace_ }); exports.expandDims = operation_1.op({ expandDims_: expandDims_ }); exports.eye = operation_1.op({ eye_: eye_ }); exports.multinomial = operation_1.op({ multinomial_: multinomial_ }); exports.oneHot = operation_1.op({ oneHot_: oneHot_ }); exports.pad = operation_1.op({ pad_: pad_ }); exports.pad1d = operation_1.op({ pad1d_: pad1d_ }); exports.pad2d = operation_1.op({ pad2d_: pad2d_ }); exports.pad3d = operation_1.op({ pad3d_: pad3d_ }); exports.pad4d = operation_1.op({ pad4d_: pad4d_ }); exports.rand = operation_1.op({ rand_: rand_ }); exports.randomNormal = operation_1.op({ randomNormal_: randomNormal_ }); exports.randomGamma = operation_1.op({ randomGamma_: randomGamma_ }); exports.randomUniform = operation_1.op({ randomUniform_: randomUniform_ }); exports.reshape = operation_1.op({ reshape_: reshape_ }); exports.spaceToBatchND = operation_1.op({ spaceToBatchND_: spaceToBatchND_ }); exports.squeeze = operation_1.op({ squeeze_: squeeze_ }); exports.stack = operation_1.op({ stack_: stack_ }); exports.tile = operation_1.op({ tile_: tile_ }); exports.truncatedNormal = operation_1.op({ truncatedNormal_: truncatedNormal_ }); exports.unstack = operation_1.op({ unstack_: unstack_ }); exports.setdiff1dAsync = setdiff1dAsync_; //# sourceMappingURL=array_ops.js.map