UNPKG

@tensorflow/tfjs-node

Version:

This repository provides native TensorFlow execution in backend JavaScript applications under the Node.js runtime, accelerated by the TensorFlow C binary under the hood. It provides the same API as [TensorFlow.js](https://js.tensorflow.org/api/latest/).

1,398 lines (1,251 loc) 72.1 kB
/** * @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. * ============================================================================= */ import * as tfc from '@tensorflow/tfjs-core'; import {backend_util, BackendTimingInfo, DataId, DataType, fill, KernelBackend, ones, Rank, rsqrt, Scalar, scalar, ShapeMap, Tensor, Tensor1D, tensor1d, Tensor2D, tensor2d, Tensor3D, Tensor4D, Tensor5D, TensorInfo, tidy, util} from '@tensorflow/tfjs-core'; // tslint:disable-next-line: no-imports-from-dist import {EPSILON_FLOAT32} from '@tensorflow/tfjs-core/dist/backends/backend'; // tslint:disable-next-line: no-imports-from-dist import {FusedBatchMatMulConfig, FusedConv2DConfig} from '@tensorflow/tfjs-core/dist/ops/fused_util'; import {isArray, isNullOrUndefined} from 'util'; import {Int64Scalar} from './int64_tensors'; import {TensorMetadata, TFEOpAttr, TFJSBinding} from './tfjs_binding'; type TensorData = { shape: number[], dtype: number, values: backend_util.BackendValues, id: number }; export class NodeJSKernelBackend extends KernelBackend { binding: TFJSBinding; isGPUPackage: boolean; isUsingGpuDevice: boolean; private tensorMap: tfc.DataStorage<TensorData>; constructor(binding: TFJSBinding, packageName: string) { super(); this.binding = binding; this.isGPUPackage = packageName === '@tensorflow/tfjs-node-gpu'; this.isUsingGpuDevice = this.binding.isUsingGpuDevice(); this.tensorMap = new tfc.DataStorage<TensorData>(this, tfc.engine()); } private getDTypeInteger(dtype: DataType): number { switch (dtype) { case 'float32': return this.binding.TF_FLOAT; case 'int32': return this.binding.TF_INT32; case 'bool': return this.binding.TF_BOOL; case 'complex64': return this.binding.TF_COMPLEX64; case 'string': return this.binding.TF_STRING; default: throw new Error(`Unsupported DType: ${dtype}`); } } private typeAttributeFromTensor(value: Tensor): number { return this.getDTypeInteger(value.dtype); } // Creates a new Tensor and maps the dataId to the passed in ID. private createOutputTensor(metadata: TensorMetadata): Tensor { const newId = {}; this.tensorMap.set(newId, { shape: metadata.shape, dtype: metadata.dtype, id: metadata.id, values: null }); let dtype: DataType; switch (metadata.dtype) { case this.binding.TF_FLOAT: dtype = 'float32'; break; case this.binding.TF_INT32: dtype = 'int32'; break; case this.binding.TF_BOOL: dtype = 'bool'; break; case this.binding.TF_COMPLEX64: dtype = 'complex64'; break; case this.binding.TF_STRING: dtype = 'string'; break; case this.binding.TF_RESOURCE: // NOTE(cais): We currently represent resource-type Tensors // as string of ubytes. dtype = 'string'; break; case this.binding.TF_UINT8: // TensorFlow uses UINT8 as dtype for image tensor. UINT8 is not // supported in TFJS yet, cast it to int32. dtype = 'int32'; break; default: throw new Error(`Unknown dtype enum ${metadata.dtype}`); } return tfc.engine().makeTensorFromDataId(newId, metadata.shape, dtype); } // Prepares Tensor instances for Op execution. private getInputTensorIds(tensors: Array<TensorInfo|Int64Scalar>): number[] { const ids: number[] = []; for (let i = 0; i < tensors.length; i++) { if (tensors[i] instanceof Int64Scalar) { // Then `tensors[i]` is a Int64Scalar, which we currently represent // using an `Int32Array`. const value = (tensors[i] as Int64Scalar).valueArray; const id = this.binding.createTensor([], this.binding.TF_INT64, value); ids.push(id); } else { const info = this.tensorMap.get((tensors[i] as TensorInfo).dataId); // TODO - what about ID in this case? Handle in write()?? if (info.values != null) { // Values were delayed to write into the TensorHandle. Do that before // Op execution and clear stored values. info.id = this.binding.createTensor(info.shape, info.dtype, info.values); info.values = null; } ids.push(info.id); } } return ids; } private createReductionOpAttrs(tensor: Tensor): TFEOpAttr[] { return [ {name: 'keep_dims', type: this.binding.TF_ATTR_BOOL, value: false}, createTypeOpAttr('T', tensor.dtype), createTypeOpAttr('Tidx', 'int32') ]; } private executeSingleInput(name: string, input: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', input.dtype)]; return this.executeSingleOutput(name, opAttrs, [input]); } floatPrecision(): 16|32 { return 32; } epsilon(): number { return EPSILON_FLOAT32; } /** * Executes a TensorFlow Eager Op that provides one output Tensor. * @param name The name of the Op to execute. * @param opAttrs The list of Op attributes required to execute. * @param inputs The list of input Tensors for the Op. * @return A resulting Tensor from Op execution. */ executeSingleOutput(name: string, opAttrs: TFEOpAttr[], inputs: TensorInfo[]): Tensor { const outputMetadata = this.binding.executeOp( name, opAttrs, this.getInputTensorIds(inputs), 1); return this.createOutputTensor(outputMetadata[0]); } /** * Executes a TensorFlow Eager Op that provides multiple output Tensors. * @param name The name of the Op to execute. * @param opAttrs The list of Op attributes required to execute. * @param inputs The list of input Tensors for the Op. * @param numOutputs The number of output Tensors for Op execution. * @return A resulting Tensor array from Op execution. */ executeMultipleOutputs( name: string, opAttrs: TFEOpAttr[], inputs: Tensor[], numOutputs: number): Tensor[] { const outputMetadata = this.binding.executeOp( name, opAttrs, this.getInputTensorIds(inputs), numOutputs); return outputMetadata.map(m => this.createOutputTensor(m)); } numDataIds(): number { return this.tensorMap.numDataIds(); } dispose(): void {} async read(dataId: DataId): Promise<backend_util.BackendValues> { return this.readSync(dataId); } readSync(dataId: DataId): backend_util.BackendValues { if (!this.tensorMap.has(dataId)) { throw new Error(`Tensor ${dataId} was not registered!`); } const info = this.tensorMap.get(dataId); if (info.values != null) { return info.values; } else { return this.binding.tensorDataSync(info.id); } } disposeData(dataId: DataId): void { // No-op if already disposed. if (!this.tensorMap.has(dataId)) { return; } const id = this.tensorMap.get(dataId).id; if (id != null && id >= 0) { this.binding.deleteTensor(id); } this.tensorMap.delete(dataId); } move( dataId: DataId, values: backend_util.BackendValues, shape: number[], dtype: DataType): void { this.tensorMap.set( dataId, {shape, dtype: getTFDType(dtype), values, id: -1}); } write(values: backend_util.BackendValues, shape: number[], dtype: DataType): DataId { const dataId = {}; this.move(dataId, values, shape, dtype); return dataId; } fill<R extends Rank>( shape: ShapeMap[R], value: number|string, dtype?: DataType): Tensor<R> { // TODO(cais, nkreeger): Investigate whether this can be made into // a dtype helper method. The underlying op kernel doesn't accept undefined // or null dtype. if (dtype == null) { if (typeof value === 'number') { dtype = 'float32'; } else { dtype = 'string'; } } const shapeTensor = tensor1d(shape, 'int32'); const valueTensor = scalar(value, dtype); const opAttrs = [ { name: 'T', type: this.binding.TF_ATTR_TYPE, value: this.getDTypeInteger(dtype) }, { name: 'index_type', type: this.binding.TF_ATTR_TYPE, value: this.binding.TF_INT32 } ]; return this.executeSingleOutput( 'Fill', opAttrs, [shapeTensor, valueTensor]) as Tensor<R>; } onesLike<R extends Rank>(x: Tensor<R>): Tensor<R> { const opAttrs = [{ name: 'T', type: this.binding.TF_ATTR_TYPE, value: this.getDTypeInteger(x.dtype) }]; return this.executeSingleOutput('OnesLike', opAttrs, [x]) as Tensor<R>; } zerosLike<R extends Rank>(x: Tensor<R>): Tensor<R> { const opAttrs = [{ name: 'T', type: this.binding.TF_ATTR_TYPE, value: this.getDTypeInteger(x.dtype) }]; return this.executeSingleOutput('ZerosLike', opAttrs, [x]) as Tensor<R>; } stridedSlice<T extends Tensor>( x: T, begin: number[], end: number[], strides: number[]): T { const beginTensor = tensor1d(begin, 'int32'); for (let axis = 0; axis < end.length; axis++) { // Unlike Numpy, when the strides are negative, TF C uses -n-1 instead of // -1 as the "end" in order to include the first element. if (strides[axis] < 0 && end[axis] === -1) { end[axis] -= x.shape[axis]; } } const endTensor = tensor1d(end, 'int32'); const stridesTensor = tensor1d(strides, 'int32'); // All of the masks have already been accounted for in the high level op, // so the backend does NOT need to deal with masks. const opAttrs = [ createTypeOpAttr('T', x.dtype), createTypeOpAttr('Index', 'int32'), {name: 'begin_mask', type: this.binding.TF_ATTR_INT, value: 0}, {name: 'end_mask', type: this.binding.TF_ATTR_INT, value: 0}, {name: 'ellipsis_mask', type: this.binding.TF_ATTR_INT, value: 0}, {name: 'new_axis_mask', type: this.binding.TF_ATTR_INT, value: 0}, {name: 'shrink_axis_mask', type: this.binding.TF_ATTR_INT, value: 0} ]; return this.executeSingleOutput( 'StridedSlice', opAttrs, [x, beginTensor, endTensor, stridesTensor]) as T; } unstack(x: Tensor, axis: number): Tensor[] { if (axis >= x.shape.length) { throw new Error( `Invalid axis supplied: ${axis} shape length: ${x.shape.length}`); } const num = x.shape[axis]; const opAttrs = [ {name: 'num', type: this.binding.TF_ATTR_INT, value: num}, createTypeOpAttr('T', x.dtype), {name: 'axis', type: this.binding.TF_ATTR_INT, value: axis} ]; return this.executeMultipleOutputs('Unpack', opAttrs, [x], num); } batchMatMul( a: Tensor<Rank.R3>, b: Tensor<Rank.R3>, transposeA: boolean, transposeB: boolean): Tensor<Rank.R3> { const opAttrs = [ createTypeOpAttr('T', a.dtype), {name: 'adj_x', type: this.binding.TF_ATTR_BOOL, value: transposeA}, {name: 'adj_y', type: this.binding.TF_ATTR_BOOL, value: transposeB} ]; return this.executeSingleOutput('BatchMatMul', opAttrs, [a, b]) as Tensor<Rank.R3>; } private applyActivation<T extends Tensor>( input: T, activation: string, preluActivationWeights?: Tensor): T { let result = input; if (activation != null) { if (activation === 'linear') { // No-op } else if (activation === 'relu') { result = this.relu(result); } else if (activation === 'prelu') { result = this.prelu(result, preluActivationWeights) as T; } else if (activation === 'elu') { result = this.elu(result); } else if (activation === 'relu6') { result = this.relu6(result); } else { throw new Error(`Activation: ${ activation} has not been implemented for the Node.js backend`); } } return result; } fusedConv2d( {input, filter, convInfo, bias, activation, preluActivationWeights}: FusedConv2DConfig): Tensor4D { let result = this.conv2d(input, filter, convInfo); if (bias != null) { result = this.add(result, bias) as Tensor4D; } result = this.applyActivation(result, activation, preluActivationWeights); return result; } fusedBatchMatMul( {a, b, transposeA, transposeB, bias, activation, preluActivationWeights}: FusedBatchMatMulConfig): Tensor3D { // Core TensorFlow does not have a fused BatchMatMul op. Combine calls to // achieve the same results: let result = this.batchMatMul(a, b, transposeA, transposeB); if (bias != null) { result = this.add(result, bias) as Tensor3D; } result = this.applyActivation(result, activation, preluActivationWeights); return result; } slice<T extends Tensor>(x: T, begin: number[], size: number[]): T { const opAttrs = [createTypeOpAttr('T', x.dtype), createTypeOpAttr('Index', 'int32')]; // Bind tensor values const beginTensor = tensor1d(begin, 'int32'); const sizeTensor = tensor1d(size, 'int32'); return this.executeSingleOutput( 'Slice', opAttrs, [x, beginTensor, sizeTensor]) as T; } reverse<T extends Tensor>(a: T, axis: number[]): T { const opAttrs = [createTypeOpAttr('Tidx', 'int32'), createTypeOpAttr('T', a.dtype)]; const axisTensor = tensor1d(axis, 'int32'); return this.executeSingleOutput('ReverseV2', opAttrs, [a, axisTensor]) as T; } concat(tensors: Tensor[], axis: number): Tensor { const opAttrs = [ {name: 'N', type: this.binding.TF_ATTR_INT, value: tensors.length}, { name: 'Tidx', type: this.binding.TF_ATTR_TYPE, value: this.binding.TF_INT32 }, createTensorsTypeOpAttr('T', tensors) ]; const inputs = Array.from(tensors); inputs.push(scalar(axis, 'int32')); return this.executeSingleOutput('ConcatV2', opAttrs, inputs); } neg<T extends Tensor>(a: T): T { return this.executeSingleInput('Neg', a) as T; } diag(x: Tensor): Tensor { return this.executeSingleInput('Diag', x); } add(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Add', opAttrs, [a, b]); } select(condition: Tensor, a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Select', opAttrs, [condition, a, b]); } addN<T extends Tensor>(tensors: T[]): T { const opAttrs = [ createTypeOpAttr('T', tensors[0].dtype), {name: 'N', type: this.binding.TF_ATTR_INT, value: tensors.length} ]; return this.executeSingleOutput('AddN', opAttrs, tensors) as T; } subtract(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Sub', opAttrs, [a, b]); } multiply(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Mul', opAttrs, [a, b]); } realDivide(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('RealDiv', opAttrs, [a, b]); } floorDiv(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('FloorDiv', opAttrs, [a, b]); } divide(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Div', opAttrs, [a, b]); } divNoNan(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('DivNoNan', opAttrs, [a, b]); } unsortedSegmentSum<T extends Tensor>( x: T, segmentIds: Tensor1D, numSegments: number): Tensor { const opAttrs = [ createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tindices', 'int32'), createTypeOpAttr('Tnumsegments', 'int32') ]; return this.executeSingleOutput( 'UnsortedSegmentSum', opAttrs, [x, segmentIds, scalar(numSegments, 'int32')]); } sum(x: Tensor, axes: number[]): Tensor { const axisTensor = tensor1d(axes, 'int32'); return this.executeSingleOutput( 'Sum', this.createReductionOpAttrs(x), [x, axisTensor]); } prod(x: Tensor, axes: number[]): Tensor { const axesTensor = tensor1d(axes, 'int32'); const opAttrs = [ {name: 'keep_dims', type: this.binding.TF_ATTR_BOOL, value: false}, createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tidx', 'int32') ]; return this.executeSingleOutput('Prod', opAttrs, [x, axesTensor]); } argMin(x: Tensor, axis: number): Tensor { const xInput = x.dtype === 'bool' ? x.toInt() : x; const axisScalar = scalar(axis, 'int32'); const opAttrs = [ createTypeOpAttr('T', xInput.dtype), createTypeOpAttr('Tidx', 'int32'), createTypeOpAttr('output_type', 'int32') ]; return this.executeSingleOutput('ArgMin', opAttrs, [xInput, axisScalar]); } argMax(x: Tensor, axis: number): Tensor { const xInput = x.dtype === 'bool' ? x.toInt() : x; const axisScalar = scalar(axis, 'int32'); const opAttrs = [ createTypeOpAttr('T', xInput.dtype), createTypeOpAttr('Tidx', 'int32'), createTypeOpAttr('output_type', 'int32') ]; return this.executeSingleOutput('ArgMax', opAttrs, [xInput, axisScalar]); } equal(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Equal', opAttrs, [a, b]); } notEqual(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('NotEqual', opAttrs, [a, b]); } less(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Less', opAttrs, [a, b]); } lessEqual(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('LessEqual', opAttrs, [a, b]); } greater(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Greater', opAttrs, [a, b]); } greaterEqual(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('GreaterEqual', opAttrs, [a, b]); } logicalNot<T extends Tensor>(a: T): T { return this.executeSingleOutput('LogicalNot', [], [a]) as T; } logicalAnd(a: Tensor, b: Tensor): Tensor { return this.executeSingleOutput('LogicalAnd', [], [a, b]); } logicalOr(a: Tensor, b: Tensor): Tensor { return this.executeSingleOutput('LogicalOr', [], [a, b]); } where(condition: Tensor): Tensor2D { return this.executeSingleOutput('Where', [], [condition]) as Tensor2D; } topKValues<T extends Tensor>(x: T, k: number): Tensor1D { throw new Error('Method not implemented.'); } topKIndices(x: Tensor, k: number): Tensor1D { throw new Error('Method not implemented.'); } topk<T extends Tensor>(x: T, k?: number, sorted?: boolean): [T, T] { const kCount = isNullOrUndefined(k) ? 1 : k; const isSorted = isNullOrUndefined(sorted) ? true : sorted; const opAttrs = [ {name: 'sorted', type: this.binding.TF_ATTR_BOOL, value: isSorted}, createTypeOpAttr('T', x.dtype), ]; const kTensor = scalar(kCount, 'int32'); // 'TopKV2' has two-hard coded output attributes: return this.executeMultipleOutputs( 'TopKV2', opAttrs, [x, kTensor], 2) as [T, T]; } min(x: Tensor, axes: number[]): Tensor { const axesTensor = tensor1d(axes, 'int32'); return this.executeSingleOutput( 'Min', this.createReductionOpAttrs(x), [x, axesTensor]); } minimum(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Minimum', opAttrs, [a, b]); } max(x: Tensor, axes: number[]): Tensor { const axesTensor = tensor1d(axes, 'int32'); return this.executeSingleOutput( 'Max', this.createReductionOpAttrs(x), [x, axesTensor]); } maximum(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', backend_util.upcastType(a.dtype, b.dtype))]; return this.executeSingleOutput('Maximum', opAttrs, [a, b]); } all(x: Tensor, axes: number[]): Tensor { const opAttrs = [ {name: 'keep_dims', type: this.binding.TF_ATTR_BOOL, value: false}, createTypeOpAttr('Tidx', 'int32') ]; const axesTensor = tensor1d(axes, 'int32'); return this.executeSingleOutput('All', opAttrs, [x, axesTensor]); } any(x: Tensor, axes: number[]): Tensor { const opAttrs = [ {name: 'keep_dims', type: this.binding.TF_ATTR_BOOL, value: false}, createTypeOpAttr('Tidx', 'int32') ]; const axesTensor = tensor1d(axes, 'int32'); return this.executeSingleOutput('Any', opAttrs, [x, axesTensor]); } ceil<T extends Tensor>(x: T): T { return this.executeSingleInput('Ceil', x) as T; } floor<T extends Tensor>(x: T): T { return this.executeSingleInput('Floor', x) as T; } pow<T extends Tensor>(a: T, b: Tensor): T { const dtype = backend_util.upcastType(a.dtype, b.dtype); const opAttrs = [createTypeOpAttr('T', dtype)]; return this.executeSingleOutput( 'Pow', opAttrs, [a.cast(dtype), b.cast(dtype)]) as T; } exp<T extends Tensor>(x: T): T { const xTensor = x.dtype === 'int32' ? x.toFloat() : x; return this.executeSingleInput('Exp', xTensor) as T; } log<T extends Tensor>(x: T): T { return this.executeSingleInput('Log', x) as T; } log1p<T extends Tensor>(x: T): T { return this.executeSingleInput('Log1p', x) as T; } sqrt<T extends Tensor>(x: T): T { return this.executeSingleInput('Sqrt', x) as T; } square<T extends Tensor>(x: T): T { return this.executeSingleInput('Square', x) as T; } relu<T extends Tensor>(x: T): T { return this.executeSingleInput('Relu', x) as T; } relu6<T extends Tensor>(x: T): T { return this.executeSingleInput('Relu6', x) as T; } prelu<T extends Tensor>(x: T, a: T): T { const pos = this.relu(x); const neg = a.mul(x.sub(this.abs(x))).mul(0.5); return pos.add(neg); } elu<T extends Tensor>(x: T): T { return this.executeSingleInput('Elu', x) as T; } eluDer<T extends Tensor>(dy: T, y: T): T { const opAttrs = [createTypeOpAttr('T', y.dtype)]; return this.executeSingleOutput('EluGrad', opAttrs, [dy, y]) as T; } selu<T extends Tensor>(x: T): T { return this.executeSingleInput('Selu', x) as T; } int<T extends Tensor>(x: T): T { throw new Error('Method not implemented.'); } clip<T extends Tensor>(x: T, min: number, max: number): T { const xMin = this.minimum(x, scalar(max)); return this.maximum(xMin, scalar(min)) as T; } abs<T extends Tensor>(x: T): T { return this.executeSingleInput('Abs', x) as T; } complexAbs<T extends Tensor>(x: T): T { const opAttrs = [createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tout', 'float32')]; return this.executeSingleOutput('ComplexAbs', opAttrs, [x]) as T; } sigmoid<T extends Tensor>(x: T): T { return this.executeSingleInput('Sigmoid', x) as T; } sin<T extends Tensor>(x: T): T { return this.executeSingleInput('Sin', x) as T; } cos<T extends Tensor>(x: T): T { return this.executeSingleInput('Cos', x) as T; } tan<T extends Tensor>(x: T): T { return this.executeSingleInput('Tan', x) as T; } asin<T extends Tensor>(x: T): T { return this.executeSingleInput('Asin', x) as T; } acos<T extends Tensor>(x: T): T { return this.executeSingleInput('Acos', x) as T; } atan<T extends Tensor>(x: T): T { return this.executeSingleInput('Atan', x) as T; } sinh<T extends Tensor>(x: T): T { return this.executeSingleInput('Sinh', x) as T; } cosh<T extends Tensor>(x: T): T { return this.executeSingleInput('Cosh', x) as T; } tanh<T extends Tensor>(x: T): T { return this.executeSingleInput('Tanh', x) as T; } mod(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', a.dtype)]; return this.executeSingleOutput('FloorMod', opAttrs, [a, b]); } round<T extends Tensor>(x: T): T { return this.executeSingleInput('Round', x) as T; } sign<T extends Tensor>(x: T): T { return this.executeSingleInput('Sign', x) as T; } isNaN<T extends Tensor>(x: T): T { return this.executeSingleInput('IsNan', x) as T; } isInf<T extends Tensor>(x: T): T { return this.executeSingleInput('IsInf', x) as T; } isFinite<T extends Tensor>(x: T): T { return this.executeSingleInput('IsFinite', x) as T; } rsqrt<T extends Tensor>(x: T): T { return this.executeSingleInput('Rsqrt', x) as T; } reciprocal<T extends Tensor>(x: T): T { return this.executeSingleInput('Reciprocal', x) as T; } asinh<T extends Tensor>(x: T): T { return this.executeSingleInput('Asinh', x) as T; } acosh<T extends Tensor>(x: T): T { return this.executeSingleInput('Acosh', x) as T; } atanh<T extends Tensor>(x: T): T { return this.executeSingleInput('Atanh', x) as T; } erf<T extends Tensor>(x: T): T { return this.executeSingleInput('Erf', x) as T; } squaredDifference(a: Tensor, b: Tensor): Tensor { const opAttrs = [createTypeOpAttr('T', a.dtype)]; return this.executeSingleOutput('SquaredDifference', opAttrs, [a, b]); } expm1<T extends Tensor>(x: T): T { return this.executeSingleInput('Expm1', x) as T; } softplus<T extends Tensor>(x: T): T { return this.executeSingleInput('Softplus', x) as T; } atan2<T extends Tensor>(a: T, b: T): T { const opAttrs = [createTypeOpAttr('T', a.dtype)]; return this.executeSingleOutput('Atan2', opAttrs, [a, b]) as T; } step<T extends Tensor>(x: T, alpha: number): T { const dtype = x.dtype; const nans = this.isNaN(x); const stepNoNans = this.select( this.greater(x, scalar(0, dtype)), ones(x.shape), fill(x.shape, alpha, dtype)); return this.select(nans, x, stepNoNans) as T; } conv2d(x: Tensor4D, filter: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'use_cudnn_on_gpu', type: this.binding.TF_ATTR_BOOL, value: true}, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations}, ]; return this.executeSingleOutput('Conv2D', opAttrs, [x, filter]) as Tensor4D; } conv2dDerInput( dy: Tensor4D, filter: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', 'float32'), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'use_cudnn_on_gpu', type: this.binding.TF_ATTR_BOOL, value: true}, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; const inputSizes = tensor1d(convInfo.inShape, 'int32'); return this.executeSingleOutput( 'Conv2DBackpropInput', opAttrs, [inputSizes, filter, dy]) as Tensor4D; } conv2dDerFilter(x: Tensor4D, dy: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', 'float32'), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'use_cudnn_on_gpu', type: this.binding.TF_ATTR_BOOL, value: true}, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; const filterSizes = tensor1d(convInfo.filterShape, 'int32'); return this.executeSingleOutput( 'Conv2DBackpropFilter', opAttrs, [x, filterSizes, dy]) as Tensor4D; } depthwiseConv2DDerInput( dy: Tensor4D, filter: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', 'float32'), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; const inputSizes = tensor1d(convInfo.inShape, 'int32'); return this.executeSingleOutput( 'DepthwiseConv2dNativeBackpropInput', opAttrs, [inputSizes, filter, dy]) as Tensor4D; } depthwiseConv2DDerFilter( x: Tensor4D, dY: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', 'float32'), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; const filterSizes = tensor1d(convInfo.filterShape, 'int32'); return this.executeSingleOutput( 'DepthwiseConv2dNativeBackpropFilter', opAttrs, [x, filterSizes, dY]) as Tensor4D; } fusedDepthwiseConv2D( {input, filter, convInfo, bias, activation, preluActivationWeights}: FusedConv2DConfig): Tensor4D { let result = this.depthwiseConv2D(input, filter, convInfo); if (bias != null) { result = this.add(result, bias) as Tensor4D; } result = this.applyActivation(result, activation, preluActivationWeights); return result; } depthwiseConv2D( input: Tensor4D, filter: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const dilations = [1, convInfo.dilationHeight, convInfo.dilationWidth, 1]; const opAttrs = [ createTypeOpAttr('T', input.dtype), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; return this.executeSingleOutput( 'DepthwiseConv2dNative', opAttrs, [input, filter]) as Tensor4D; } conv3d( x: Tensor<Rank.R5>, filter: Tensor<Rank.R5>, convInfo: backend_util.Conv3DInfo): Tensor<Rank.R5> { const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; if (!this.isGPUPackage && convInfo.dilationDepth > 1) { throw new Error('CPU Dilation depth must be 1'); } const dilations = [ 1, convInfo.dilationDepth, convInfo.dilationHeight, convInfo.dilationWidth, 1 ]; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; return this.executeSingleOutput('Conv3D', opAttrs, [x, filter]) as Tensor5D; } conv3dDerInput( dy: Tensor<Rank.R5>, filter: Tensor<Rank.R5>, convInfo: backend_util.Conv3DInfo): Tensor<Rank.R5> { const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; if (!this.isGPUPackage && convInfo.dilationDepth > 1) { throw new Error('CPU Dilation depth must be 1'); } const dilations = [ 1, convInfo.dilationDepth, convInfo.dilationHeight, convInfo.dilationWidth, 1 ]; const opAttrs = [ createTypeOpAttr('T', dy.dtype), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations}, createTypeOpAttr('Tshape', 'int32') ]; const inputSizes = tensor1d(convInfo.inShape, 'int32'); return this.executeSingleOutput( 'Conv3DBackpropInputV2', opAttrs, [inputSizes, filter, dy]) as Tensor5D; } conv3dDerFilter( x: Tensor<Rank.R5>, dY: Tensor<Rank.R5>, convInfo: backend_util.Conv3DInfo): Tensor<Rank.R5> { const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; if (!this.isGPUPackage && convInfo.dilationDepth > 1) { throw new Error('CPU Dilation depth must be 1'); } const dilations = [ 1, convInfo.dilationDepth, convInfo.dilationHeight, convInfo.dilationWidth, 1 ]; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, {name: 'dilations', type: this.binding.TF_ATTR_INT, value: dilations} ]; const filterSizes = tensor1d(convInfo.filterShape, 'int32'); return this.executeSingleOutput( 'Conv3DBackpropFilterV2', opAttrs, [x, filterSizes, dY]) as Tensor5D; } maxPool(x: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const ksize = [1, convInfo.filterHeight, convInfo.filterWidth, 1]; const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat } ]; return this.executeSingleOutput('MaxPool', opAttrs, [x]) as Tensor4D; } maxPoolBackprop( dy: Tensor4D, x: Tensor4D, y: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding type was ${convInfo.padInfo.type}`); } const ksize = [1, convInfo.filterHeight, convInfo.filterWidth, 1]; const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; return this.executeSingleOutput('MaxPoolGrad', opAttrs, [x, y, dy]) as Tensor4D; } avgPool(x: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const ksize = [1, convInfo.filterHeight, convInfo.filterWidth, 1]; const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; return this.executeSingleOutput('AvgPool', opAttrs, [x]) as Tensor4D; } avgPoolBackprop(dy: Tensor4D, x: Tensor4D, convInfo: backend_util.Conv2DInfo): Tensor4D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding type was ${convInfo.padInfo.type}`); } const ksize = [1, convInfo.filterHeight, convInfo.filterWidth, 1]; const strides = [1, convInfo.strideHeight, convInfo.strideWidth, 1]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NHWC' : 'NCHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; const origInputShape = tensor1d(x.shape, 'int32'); return this.executeSingleOutput( 'AvgPoolGrad', opAttrs, [origInputShape, dy]) as Tensor4D; } avgPool3d(x: Tensor5D, convInfo: backend_util.Conv3DInfo): Tensor5D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const ksize = [ 1, convInfo.filterDepth, convInfo.filterHeight, convInfo.filterWidth, 1 ]; const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; return this.executeSingleOutput('AvgPool3D', opAttrs, [x]) as Tensor5D; } avgPool3dBackprop( dy: Tensor5D, x: Tensor5D, convInfo: backend_util.Conv3DInfo): Tensor5D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding type was ${convInfo.padInfo.type}`); } const ksize = [ 1, convInfo.filterDepth, convInfo.filterHeight, convInfo.filterWidth, 1 ]; const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; const origInputShape = tensor1d(x.shape, 'int32'); return this.executeSingleOutput( 'AvgPool3DGrad', opAttrs, [origInputShape, dy]) as Tensor5D; } maxPool3d(x: Tensor5D, convInfo: backend_util.Conv3DInfo): Tensor5D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding was ${convInfo.padInfo.type}`); } const ksize = [ 1, convInfo.filterDepth, convInfo.filterHeight, convInfo.filterWidth, 1 ]; const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat } ]; return this.executeSingleOutput('MaxPool3D', opAttrs, [x]) as Tensor5D; } maxPool3dBackprop( dy: Tensor5D, x: Tensor5D, y: Tensor5D, convInfo: backend_util.Conv3DInfo): Tensor5D { if (convInfo.padInfo.type !== 'VALID' && convInfo.padInfo.type !== 'SAME') { throw new Error( `TF Backend supports only 'valid' and 'same' padding ` + `while padding type was ${convInfo.padInfo.type}`); } const ksize = [ 1, convInfo.filterDepth, convInfo.filterHeight, convInfo.filterWidth, 1 ]; const strides = [ 1, convInfo.strideDepth, convInfo.strideHeight, convInfo.strideWidth, 1 ]; const padding = convInfo.padInfo.type; const dataFormat = convInfo.dataFormat === 'channelsLast' ? 'NDHWC' : 'NCDHW'; const opAttrs = [ createTypeOpAttr('T', x.dtype), {name: 'ksize', type: this.binding.TF_ATTR_INT, value: ksize}, {name: 'strides', type: this.binding.TF_ATTR_INT, value: strides}, {name: 'padding', type: this.binding.TF_ATTR_STRING, value: padding}, { name: 'data_format', type: this.binding.TF_ATTR_STRING, value: dataFormat }, ]; return this.executeSingleOutput('MaxPool3DGrad', opAttrs, [x, y, dy]) as Tensor5D; } reshape<T extends Tensor, R extends Rank>(x: T, shape: ShapeMap[R]): Tensor<R> { const shapeTensor = tensor1d(shape, 'int32'); const opAttrs = [ createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tshape', shapeTensor.dtype) ]; return this.executeSingleOutput('Reshape', opAttrs, [x, shapeTensor]) as Tensor<R>; } cast<T extends Tensor>(x: T, dtype: DataType): T { const opAttrs = [ createTypeOpAttr('SrcT', x.dtype), createTypeOpAttr('DstT', dtype), {name: 'Truncate', type: this.binding.TF_ATTR_BOOL, value: false} ]; return this.executeSingleOutput('Cast', opAttrs, [x]) as T; } tile<T extends Tensor>(x: T, reps: number[]): T { const opAttrs = [ createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tmultiples', 'int32') ]; const multiples = tensor1d(reps, 'int32'); return this.executeSingleOutput('Tile', opAttrs, [x, multiples]) as T; } pad<T extends Tensor>( x: T, paddings: Array<[number, number]>, constantValue: number): T { // Bind tensor values const paddingsTensor = tensor2d(paddings, [paddings.length, 2], 'int32'); const constantTensor = scalar(constantValue, x.dtype); const opAttrs = [ createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tpaddings', paddingsTensor.dtype) ]; return this.executeSingleOutput( 'PadV2', opAttrs, [x, paddingsTensor, constantTensor]) as T; } transpose<T extends Tensor>(x: T, perm: number[]): T { const permTensor = tensor1d(perm, 'int32'); const opAttrs = [createTypeOpAttr('T', x.dtype), createTypeOpAttr('Tperm', 'int32')]; return this.executeSingleOutput('Transpose', opAttrs, [x, permTensor]) as T; } gather<T extends Tensor>(x: T, indices: Tensor1D, axis: number): T { const axisTensor = scalar(axis, 'int32'); const opAttrs = [ createTypeOpAttr('Tparams', x.dtype), createTypeOpAttr('Tindices', indices.dtype), createTypeOpAttr('Taxis', 'int32') ]; return this.executeSingleOutput( 'GatherV2', opAttrs, [x, indices, axisTensor]) as T; } gatherND(x: Tensor, indices: Tensor): Tensor { const opAttrs = [ createTypeOpAttr('Tparams', x.dtype), createTypeOpAttr('Tindices', 'int32') ]; return this.executeSingleOutput('GatherNd', opAttrs, [x, indices]); } scatterND<R extends Rank>( indices: Tensor, updates: Tensor, shape: ShapeMap[R]): Tensor<R> { const opAttrs = [ createTypeOpAttr('T', updates.dtype), createTypeOpAttr('Tindices', 'int32') ]; const shapeTensor = tensor1d(shape, 'int32