From 4db4b07a393b24ef17ce2c9c7f3cf9c24d3546f0 Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Fri, 20 Mar 2020 11:48:05 -0400 Subject: [PATCH 1/5] random_gamma, random_normal, truncated_normal --- tfjs-core/src/ops/array_ops.ts | 103 +------ tfjs-core/src/ops/array_ops_test.ts | 339 +-------------------- tfjs-core/src/ops/ops.ts | 3 + tfjs-core/src/ops/random_gamma.ts | 59 ++++ tfjs-core/src/ops/random_gamma_test.ts | 125 ++++++++ tfjs-core/src/ops/random_normal.ts | 54 ++++ tfjs-core/src/ops/random_normal_test.ts | 124 ++++++++ tfjs-core/src/ops/truncated_normal.ts | 59 ++++ tfjs-core/src/ops/truncated_normal_test.ts | 127 ++++++++ 9 files changed, 555 insertions(+), 438 deletions(-) create mode 100644 tfjs-core/src/ops/random_gamma.ts create mode 100644 tfjs-core/src/ops/random_gamma_test.ts create mode 100644 tfjs-core/src/ops/random_normal.ts create mode 100644 tfjs-core/src/ops/random_normal_test.ts create mode 100644 tfjs-core/src/ops/truncated_normal.ts create mode 100644 tfjs-core/src/ops/truncated_normal_test.ts diff --git a/tfjs-core/src/ops/array_ops.ts b/tfjs-core/src/ops/array_ops.ts index b19bb6687a3..7eea0a2ce47 100644 --- a/tfjs-core/src/ops/array_ops.ts +++ b/tfjs-core/src/ops/array_ops.ts @@ -23,7 +23,7 @@ import * as util from '../util'; import {getAxesPermutation, getInnerMostAxes} from './axis_util'; import {concat} from './concat_split'; import {op} from './operation'; -import {MPRandGauss, RandGamma, UniformRandom} from './rand'; +import {UniformRandom} from './rand'; import {zeros, zerosLike} from './tensor_ops'; /** @@ -101,104 +101,6 @@ function eye_( } } -/** - * 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: ShapeMap[R], mean = 0, stdDev = 1, dtype?: 'float32'|'int32', - seed?: number): Tensor { - if (dtype != null && (dtype as DataType) === 'bool') { - throw new Error(`Unsupported data type ${dtype}`); - } - const randGauss = - new MPRandGauss(mean, stdDev, dtype, false /* truncated */, seed); - const res = buffer(shape, dtype); - for (let 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: ShapeMap[R], mean = 0, stdDev = 1, dtype?: 'float32'|'int32', - seed?: number): Tensor { - if (dtype != null && (dtype as DataType) === 'bool') { - throw new Error(`Unsupported data type ${dtype}`); - } - const randGauss = - new MPRandGauss(mean, stdDev, dtype, true /* truncated */, seed); - const res = buffer(shape, dtype); - for (let 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: ShapeMap[R], alpha: number, beta = 1, - dtype: 'float32'|'int32' = 'float32', seed?: number): Tensor { - if (beta == null) { - beta = 1; - } - if (dtype == null) { - dtype = 'float32'; - } - if (dtype !== 'float32' && dtype !== 'int32') { - throw new Error(`Unsupported data type ${dtype}`); - } - const rgamma = new RandGamma(alpha, beta, dtype, seed); - const res = buffer(shape, dtype); - for (let 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. * @@ -1144,14 +1046,11 @@ export const pad2d = op({pad2d_}); export const pad3d = op({pad3d_}); export const pad4d = op({pad4d_}); export const rand = op({rand_}); -export const randomNormal = op({randomNormal_}); -export const randomGamma = op({randomGamma_}); export const randomUniform = op({randomUniform_}); export const reshape = op({reshape_}); export const spaceToBatchND = op({spaceToBatchND_}); export const squeeze = op({squeeze_}); export const stack = op({stack_}); export const tile = op({tile_}); -export const truncatedNormal = op({truncatedNormal_}); export const unstack = op({unstack_}); export const setdiff1dAsync = setdiff1dAsync_; diff --git a/tfjs-core/src/ops/array_ops_test.ts b/tfjs-core/src/ops/array_ops_test.ts index fe990494af2..7eac47ee321 100644 --- a/tfjs-core/src/ops/array_ops_test.ts +++ b/tfjs-core/src/ops/array_ops_test.ts @@ -18,11 +18,8 @@ import * as tf from '../index'; import {ALL_ENVS, BROWSER_ENVS, describeWithFlags, NODE_ENVS} from '../jasmine_util'; import {expectArraysClose, expectArraysEqual, expectPromiseToFail, expectValuesInRange} from '../test_util'; -import {TypedArray} from '../types'; import * as util from '../util'; -import {expectArrayInMeanStdRange, jarqueBeraNormalityTest} from './rand_util'; - describeWithFlags('zeros', ALL_ENVS, () => { it('1D default dtype', async () => { const a: tf.Tensor1D = tf.zeros([3]); @@ -950,337 +947,6 @@ describeWithFlags('eye', ALL_ENVS, () => { }); }); -describeWithFlags('randomNormal', ALL_ENVS, () => { - const SEED = 2002; - const EPSILON = 0.05; - - it('should return a float32 1D of random normal values', async () => { - const SAMPLES = 10000; - - // Ensure defaults to float32. - let result = tf.randomNormal([SAMPLES], 0, 0.5, null, SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual([SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); - - result = tf.randomNormal([SAMPLES], 0, 1.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual([SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); - }); - - it('should return a int32 1D of random normal values', async () => { - const SAMPLES = 10000; - const result = tf.randomNormal([SAMPLES], 0, 2, 'int32', SEED); - expect(result.dtype).toBe('int32'); - expect(result.shape).toEqual([SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); - }); - - it('should return a float32 2D of random normal values', async () => { - const SAMPLES = 100; - - // Ensure defaults to float32. - let result = tf.randomNormal([SAMPLES, SAMPLES], 0, 2.5, null, SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual([SAMPLES, SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2.5, EPSILON); - - result = tf.randomNormal([SAMPLES, SAMPLES], 0, 3.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual([SAMPLES, SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); - }); - - it('should return a int32 2D of random normal values', async () => { - const SAMPLES = 100; - const result = tf.randomNormal([SAMPLES, SAMPLES], 0, 2, 'int32', SEED); - expect(result.dtype).toBe('int32'); - expect(result.shape).toEqual([SAMPLES, SAMPLES]); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); - }); - - it('should return a float32 3D of random normal values', async () => { - const SAMPLES_SHAPE = [20, 20, 20]; - - // Ensure defaults to float32. - let result = tf.randomNormal(SAMPLES_SHAPE, 0, 0.5, null, SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); - - result = tf.randomNormal(SAMPLES_SHAPE, 0, 1.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); - }); - - it('should return a int32 3D of random normal values', async () => { - const SAMPLES_SHAPE = [20, 20, 20]; - const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); - expect(result.dtype).toBe('int32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); - }); - - it('should return a float32 4D of random normal values', async () => { - const SAMPLES_SHAPE = [10, 10, 10, 10]; - - // Ensure defaults to float32. - let result = tf.randomNormal(SAMPLES_SHAPE, 0, 0.5, null, SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); - - result = tf.randomNormal(SAMPLES_SHAPE, 0, 1.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); - }); - - it('should return a int32 4D of random normal values', async () => { - const SAMPLES_SHAPE = [10, 10, 10, 10]; - - const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); - expect(result.dtype).toBe('int32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); - }); - - it('should return a int32 5D of random normal values', async () => { - const SAMPLES_SHAPE = [10, 10, 10, 10, 10]; - - const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); - expect(result.dtype).toBe('int32'); - expect(result.shape).toEqual(SAMPLES_SHAPE); - jarqueBeraNormalityTest(await result.data()); - expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); - }); -}); - -describeWithFlags('truncatedNormal', ALL_ENVS, () => { - // Expect slightly higher variances for truncated values. - const EPSILON = 0.60; - const SEED = 2002; - - function assertTruncatedValues( - values: TypedArray, mean: number, stdv: number) { - const bounds = mean + stdv * 2; - for (let i = 0; i < values.length; i++) { - expect(Math.abs(values[i])).toBeLessThanOrEqual(bounds); - } - } - - it('should return a random 1D float32 array', async () => { - const shape: [number] = [1000]; - - // Ensure defaults to float32 w/o type: - let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 3.5); - expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); - - result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 4.5); - expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); - }); - - it('should return a randon 1D int32 array', async () => { - const shape: [number] = [1000]; - const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); - expect(result.dtype).toBe('int32'); - assertTruncatedValues(await result.data(), 0, 5); - expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); - }); - - it('should return a 2D float32 array', async () => { - const shape: [number, number] = [50, 50]; - - // Ensure defaults to float32 w/o type: - let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 3.5); - expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); - - result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 4.5); - expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); - }); - - it('should return a 2D int32 array', async () => { - const shape: [number, number] = [50, 50]; - const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); - expect(result.dtype).toBe('int32'); - assertTruncatedValues(await result.data(), 0, 5); - expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); - }); - - it('should return a 3D float32 array', async () => { - const shape: [number, number, number] = [10, 10, 10]; - - // Ensure defaults to float32 w/o type: - let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 3.5); - expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); - - result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 4.5); - expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); - }); - - it('should return a 3D int32 array', async () => { - const shape: [number, number, number] = [10, 10, 10]; - const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); - expect(result.dtype).toBe('int32'); - assertTruncatedValues(await result.data(), 0, 5); - expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); - }); - - it('should return a 4D float32 array', async () => { - const shape: [number, number, number, number] = [5, 5, 5, 5]; - - // Ensure defaults to float32 w/o type: - let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 3.5); - expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); - - result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); - expect(result.dtype).toBe('float32'); - assertTruncatedValues(await result.data(), 0, 4.5); - expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); - }); - - it('should return a 4D int32 array', async () => { - const shape: [number, number, number, number] = [5, 5, 5, 5]; - const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); - expect(result.dtype).toBe('int32'); - assertTruncatedValues(await result.data(), 0, 5); - expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); - }); -}); - -const GAMMA_MIN = 0; -const GAMMA_MAX = 40; - -describeWithFlags('randomGamma', ALL_ENVS, () => { - it('should return a random 1D float32 array', async () => { - const shape: [number] = [10]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomGamma(shape, 2, 2); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - - result = tf.randomGamma(shape, 2, 2, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 1D int32 array', async () => { - const shape: [number] = [10]; - const result = tf.randomGamma(shape, 2, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 2D float32 array', async () => { - const shape: [number, number] = [3, 4]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomGamma(shape, 2, 2); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - - result = tf.randomGamma(shape, 2, 2, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 2D int32 array', async () => { - const shape: [number, number] = [3, 4]; - const result = tf.randomGamma(shape, 2, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 3D float32 array', async () => { - const shape: [number, number, number] = [3, 4, 5]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomGamma(shape, 2, 2); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - - result = tf.randomGamma(shape, 2, 2, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 3D int32 array', async () => { - const shape: [number, number, number] = [3, 4, 5]; - const result = tf.randomGamma(shape, 2, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 4D float32 array', async () => { - const shape: [number, number, number, number] = [3, 4, 5, 6]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomGamma(shape, 2, 2); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - - result = tf.randomGamma(shape, 2, 2, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 4D int32 array', async () => { - const shape: [number, number, number, number] = [3, 4, 5, 6]; - const result = tf.randomGamma(shape, 2, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 5D float32 array', async () => { - const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomGamma(shape, 2, 2); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - - result = tf.randomGamma(shape, 2, 2, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); - - it('should return a random 5D int32 array', async () => { - const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; - const result = tf.randomGamma(shape, 2, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); - }); -}); - describeWithFlags('randomUniform', ALL_ENVS, () => { it('should return a random 1D float32 array', async () => { const shape: [number] = [10]; @@ -1612,7 +1278,8 @@ describeWithFlags('fromPixels', BROWSER_ENVS, () => { video.autoplay = true; const source = document.createElement('source'); // tslint:disable:max-line-length - source.src = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkwMSA3ZDBmZjIyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTMgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTI4LjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAAAwZYiEAD//8m+P5OXfBeLGOfKE3xkODvFZuBflHv/+VwJIta6cbpIo4ABLoKBaYTkTAAAC7m1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIYdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAACgAAAAWgAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABkG1kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAATttaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAD7c3RibAAAAJdzdHNkAAAAAAAAAAEAAACHYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAACgAFoASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADFhdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw'; + source.src = + 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkwMSA3ZDBmZjIyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTMgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTI4LjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAAAwZYiEAD//8m+P5OXfBeLGOfKE3xkODvFZuBflHv/+VwJIta6cbpIo4ABLoKBaYTkTAAAC7m1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIYdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAACgAAAAWgAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABkG1kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAATttaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAD7c3RibAAAAJdzdHNkAAAAAAAAAAEAAACHYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAACgAFoASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADFhdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw'; source.type = 'video/mp4'; video.appendChild(source); document.body.appendChild(video); @@ -1627,7 +1294,7 @@ describeWithFlags('fromPixels', BROWSER_ENVS, () => { const res = tf.browser.fromPixels(video); expect(res.shape).toEqual([90, 160, 3]); const data = await res.data(); - expect(data.length).toEqual(90 * 160 *3); + expect(data.length).toEqual(90 * 160 * 3); document.body.removeChild(video); }); diff --git a/tfjs-core/src/ops/ops.ts b/tfjs-core/src/ops/ops.ts index 720d02bc6a0..87493d8b089 100644 --- a/tfjs-core/src/ops/ops.ts +++ b/tfjs-core/src/ops/ops.ts @@ -17,8 +17,11 @@ // Modularized ops. export {broadcastTo} from './broadcast_to'; +export {randomGamma} from './random_gamma'; +export {randomNormal} from './random_normal'; export {square} from './square'; export {squaredDifference} from './squared_difference'; +export {truncatedNormal} from './truncated_normal'; export * from './batchnorm'; export * from './boolean_mask'; diff --git a/tfjs-core/src/ops/random_gamma.ts b/tfjs-core/src/ops/random_gamma.ts new file mode 100644 index 00000000000..3c740bc1794 --- /dev/null +++ b/tfjs-core/src/ops/random_gamma.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2020 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 {Tensor} from '../tensor'; +import {Rank, ShapeMap} from '../types'; + +import {buffer} from './array_ops'; +import {op} from './operation'; +import {RandGamma} from './rand'; + +/** + * 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: ShapeMap[R], alpha: number, beta = 1, + dtype: 'float32'|'int32' = 'float32', seed?: number): Tensor { + if (beta == null) { + beta = 1; + } + if (dtype == null) { + dtype = 'float32'; + } + if (dtype !== 'float32' && dtype !== 'int32') { + throw new Error(`Unsupported data type ${dtype}`); + } + const rgamma = new RandGamma(alpha, beta, dtype, seed); + const res = buffer(shape, dtype); + for (let i = 0; i < res.values.length; i++) { + res.values[i] = rgamma.nextValue(); + } + return res.toTensor(); +} +export const randomGamma = op({randomGamma_}); diff --git a/tfjs-core/src/ops/random_gamma_test.ts b/tfjs-core/src/ops/random_gamma_test.ts new file mode 100644 index 00000000000..3604063d423 --- /dev/null +++ b/tfjs-core/src/ops/random_gamma_test.ts @@ -0,0 +1,125 @@ +/** + * @license + * Copyright 2020 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 tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectValuesInRange} from '../test_util'; + +const GAMMA_MIN = 0; +const GAMMA_MAX = 40; + +describeWithFlags('randomGamma', ALL_ENVS, () => { + it('should return a random 1D float32 array', async () => { + const shape: [number] = [10]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomGamma(shape, 2, 2); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + + result = tf.randomGamma(shape, 2, 2, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 1D int32 array', async () => { + const shape: [number] = [10]; + const result = tf.randomGamma(shape, 2, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 2D float32 array', async () => { + const shape: [number, number] = [3, 4]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomGamma(shape, 2, 2); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + + result = tf.randomGamma(shape, 2, 2, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 2D int32 array', async () => { + const shape: [number, number] = [3, 4]; + const result = tf.randomGamma(shape, 2, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 3D float32 array', async () => { + const shape: [number, number, number] = [3, 4, 5]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomGamma(shape, 2, 2); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + + result = tf.randomGamma(shape, 2, 2, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 3D int32 array', async () => { + const shape: [number, number, number] = [3, 4, 5]; + const result = tf.randomGamma(shape, 2, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 4D float32 array', async () => { + const shape: [number, number, number, number] = [3, 4, 5, 6]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomGamma(shape, 2, 2); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + + result = tf.randomGamma(shape, 2, 2, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 4D int32 array', async () => { + const shape: [number, number, number, number] = [3, 4, 5, 6]; + const result = tf.randomGamma(shape, 2, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 5D float32 array', async () => { + const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomGamma(shape, 2, 2); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + + result = tf.randomGamma(shape, 2, 2, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); + + it('should return a random 5D int32 array', async () => { + const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; + const result = tf.randomGamma(shape, 2, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), GAMMA_MIN, GAMMA_MAX); + }); +}); diff --git a/tfjs-core/src/ops/random_normal.ts b/tfjs-core/src/ops/random_normal.ts new file mode 100644 index 00000000000..a60120b4cd1 --- /dev/null +++ b/tfjs-core/src/ops/random_normal.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2020 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 {Tensor} from '../tensor'; +import {DataType, Rank, ShapeMap} from '../types'; + +import {buffer} from './array_ops'; +import {op} from './operation'; +import {MPRandGauss} from './rand'; + +/** + * 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: ShapeMap[R], mean = 0, stdDev = 1, dtype?: 'float32'|'int32', + seed?: number): Tensor { + if (dtype != null && (dtype as DataType) === 'bool') { + throw new Error(`Unsupported data type ${dtype}`); + } + const randGauss = + new MPRandGauss(mean, stdDev, dtype, false /* truncated */, seed); + const res = buffer(shape, dtype); + for (let i = 0; i < res.values.length; i++) { + res.values[i] = randGauss.nextValue(); + } + return res.toTensor(); +} + +export const randomNormal = op({randomNormal_}); diff --git a/tfjs-core/src/ops/random_normal_test.ts b/tfjs-core/src/ops/random_normal_test.ts new file mode 100644 index 00000000000..aa86ef5b9d8 --- /dev/null +++ b/tfjs-core/src/ops/random_normal_test.ts @@ -0,0 +1,124 @@ + +import * as tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectArrayInMeanStdRange, jarqueBeraNormalityTest} from './rand_util'; + +describeWithFlags('randomNormal', ALL_ENVS, () => { + const SEED = 2002; + const EPSILON = 0.05; + + it('should return a float32 1D of random normal values', async () => { + const SAMPLES = 10000; + + // Ensure defaults to float32. + let result = tf.randomNormal([SAMPLES], 0, 0.5, null, SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual([SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); + + result = tf.randomNormal([SAMPLES], 0, 1.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual([SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); + }); + + it('should return a int32 1D of random normal values', async () => { + const SAMPLES = 10000; + const result = tf.randomNormal([SAMPLES], 0, 2, 'int32', SEED); + expect(result.dtype).toBe('int32'); + expect(result.shape).toEqual([SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); + }); + + it('should return a float32 2D of random normal values', async () => { + const SAMPLES = 100; + + // Ensure defaults to float32. + let result = tf.randomNormal([SAMPLES, SAMPLES], 0, 2.5, null, SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual([SAMPLES, SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2.5, EPSILON); + + result = tf.randomNormal([SAMPLES, SAMPLES], 0, 3.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual([SAMPLES, SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); + }); + + it('should return a int32 2D of random normal values', async () => { + const SAMPLES = 100; + const result = tf.randomNormal([SAMPLES, SAMPLES], 0, 2, 'int32', SEED); + expect(result.dtype).toBe('int32'); + expect(result.shape).toEqual([SAMPLES, SAMPLES]); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); + }); + + it('should return a float32 3D of random normal values', async () => { + const SAMPLES_SHAPE = [20, 20, 20]; + + // Ensure defaults to float32. + let result = tf.randomNormal(SAMPLES_SHAPE, 0, 0.5, null, SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); + + result = tf.randomNormal(SAMPLES_SHAPE, 0, 1.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); + }); + + it('should return a int32 3D of random normal values', async () => { + const SAMPLES_SHAPE = [20, 20, 20]; + const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); + expect(result.dtype).toBe('int32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); + }); + + it('should return a float32 4D of random normal values', async () => { + const SAMPLES_SHAPE = [10, 10, 10, 10]; + + // Ensure defaults to float32. + let result = tf.randomNormal(SAMPLES_SHAPE, 0, 0.5, null, SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 0.5, EPSILON); + + result = tf.randomNormal(SAMPLES_SHAPE, 0, 1.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 1.5, EPSILON); + }); + + it('should return a int32 4D of random normal values', async () => { + const SAMPLES_SHAPE = [10, 10, 10, 10]; + + const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); + expect(result.dtype).toBe('int32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); + }); + + it('should return a int32 5D of random normal values', async () => { + const SAMPLES_SHAPE = [10, 10, 10, 10, 10]; + + const result = tf.randomNormal(SAMPLES_SHAPE, 0, 2, 'int32', SEED); + expect(result.dtype).toBe('int32'); + expect(result.shape).toEqual(SAMPLES_SHAPE); + jarqueBeraNormalityTest(await result.data()); + expectArrayInMeanStdRange(await result.data(), 0, 2, EPSILON); + }); +}); diff --git a/tfjs-core/src/ops/truncated_normal.ts b/tfjs-core/src/ops/truncated_normal.ts new file mode 100644 index 00000000000..022c2df3101 --- /dev/null +++ b/tfjs-core/src/ops/truncated_normal.ts @@ -0,0 +1,59 @@ +/** + * @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 {Tensor} from '../tensor'; +import {DataType, Rank, ShapeMap} from '../types'; + +import {buffer} from './array_ops'; +import {op} from './operation'; +import {MPRandGauss} from './rand'; + +/** + * 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: ShapeMap[R], mean = 0, stdDev = 1, dtype?: 'float32'|'int32', + seed?: number): Tensor { + if (dtype != null && (dtype as DataType) === 'bool') { + throw new Error(`Unsupported data type $ { dtype }`); + } + const randGauss = + new MPRandGauss(mean, stdDev, dtype, true /* truncated */, seed); + const res = buffer(shape, dtype); + for (let i = 0; i < res.values.length; i++) { + res.values[i] = randGauss.nextValue(); + } + return res.toTensor(); +} + +export const truncatedNormal = op({truncatedNormal_}); diff --git a/tfjs-core/src/ops/truncated_normal_test.ts b/tfjs-core/src/ops/truncated_normal_test.ts new file mode 100644 index 00000000000..7aac1d04bdc --- /dev/null +++ b/tfjs-core/src/ops/truncated_normal_test.ts @@ -0,0 +1,127 @@ +/** + * @license + * Copyright 2020 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 tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {TypedArray} from '../types'; +import {expectArrayInMeanStdRange} from './rand_util'; + +describeWithFlags('truncatedNormal', ALL_ENVS, () => { + // Expect slightly higher variances for truncated values. + const EPSILON = 0.60; + const SEED = 2002; + + function assertTruncatedValues( + values: TypedArray, mean: number, stdv: number) { + const bounds = mean + stdv * 2; + for (let i = 0; i < values.length; i++) { + expect(Math.abs(values[i])).toBeLessThanOrEqual(bounds); + } + } + + it('should return a random 1D float32 array', async () => { + const shape: [number] = [1000]; + + // Ensure defaults to float32 w/o type: + let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 3.5); + expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); + + result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 4.5); + expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); + }); + + it('should return a randon 1D int32 array', async () => { + const shape: [number] = [1000]; + const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); + expect(result.dtype).toBe('int32'); + assertTruncatedValues(await result.data(), 0, 5); + expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); + }); + + it('should return a 2D float32 array', async () => { + const shape: [number, number] = [50, 50]; + + // Ensure defaults to float32 w/o type: + let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 3.5); + expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); + + result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 4.5); + expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); + }); + + it('should return a 2D int32 array', async () => { + const shape: [number, number] = [50, 50]; + const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); + expect(result.dtype).toBe('int32'); + assertTruncatedValues(await result.data(), 0, 5); + expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); + }); + + it('should return a 3D float32 array', async () => { + const shape: [number, number, number] = [10, 10, 10]; + + // Ensure defaults to float32 w/o type: + let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 3.5); + expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); + + result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 4.5); + expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); + }); + + it('should return a 3D int32 array', async () => { + const shape: [number, number, number] = [10, 10, 10]; + const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); + expect(result.dtype).toBe('int32'); + assertTruncatedValues(await result.data(), 0, 5); + expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); + }); + + it('should return a 4D float32 array', async () => { + const shape: [number, number, number, number] = [5, 5, 5, 5]; + + // Ensure defaults to float32 w/o type: + let result = tf.truncatedNormal(shape, 0, 3.5, null, SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 3.5); + expectArrayInMeanStdRange(await result.data(), 0, 3.5, EPSILON); + + result = tf.truncatedNormal(shape, 0, 4.5, 'float32', SEED); + expect(result.dtype).toBe('float32'); + assertTruncatedValues(await result.data(), 0, 4.5); + expectArrayInMeanStdRange(await result.data(), 0, 4.5, EPSILON); + }); + + it('should return a 4D int32 array', async () => { + const shape: [number, number, number, number] = [5, 5, 5, 5]; + const result = tf.truncatedNormal(shape, 0, 5, 'int32', SEED); + expect(result.dtype).toBe('int32'); + assertTruncatedValues(await result.data(), 0, 5); + expectArrayInMeanStdRange(await result.data(), 0, 5, EPSILON); + }); +}); From 61c40d127efbed4765859239385ae3c2b31bd165 Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Fri, 20 Mar 2020 12:12:51 -0400 Subject: [PATCH 2/5] rand and randomUniform --- tfjs-core/src/ops/array_ops.ts | 69 +------ tfjs-core/src/ops/array_ops_test.ts | 250 +---------------------- tfjs-core/src/ops/dropout.ts | 2 +- tfjs-core/src/ops/ops.ts | 2 + tfjs-core/src/ops/rand.ts | 229 +++------------------ tfjs-core/src/ops/rand_test.ts | 116 ++++++++++- tfjs-core/src/ops/rand_util.ts | 216 ++++++++++++++++++++ tfjs-core/src/ops/random_gamma.ts | 2 +- tfjs-core/src/ops/random_normal.ts | 2 +- tfjs-core/src/ops/random_uniform.ts | 54 +++++ tfjs-core/src/ops/random_uniform_test.ts | 157 ++++++++++++++ tfjs-core/src/ops/truncated_normal.ts | 2 +- tfjs-core/src/tests.ts | 4 + 13 files changed, 589 insertions(+), 516 deletions(-) create mode 100644 tfjs-core/src/ops/random_uniform.ts create mode 100644 tfjs-core/src/ops/random_uniform_test.ts diff --git a/tfjs-core/src/ops/array_ops.ts b/tfjs-core/src/ops/array_ops.ts index 7eea0a2ce47..a15d89c8cae 100644 --- a/tfjs-core/src/ops/array_ops.ts +++ b/tfjs-core/src/ops/array_ops.ts @@ -23,7 +23,6 @@ import * as util from '../util'; import {getAxesPermutation, getInnerMostAxes} from './axis_util'; import {concat} from './concat_split'; import {op} from './operation'; -import {UniformRandom} from './rand'; import {zeros, zerosLike} from './tensor_ops'; /** @@ -101,67 +100,6 @@ function eye_( } } -/** - * 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: ShapeMap[R], minval = 0, maxval = 1, dtype: DataType = 'float32', - seed?: number|string): Tensor { - const res = buffer(shape, dtype); - const random = new UniformRandom(minval, maxval, null, seed); - for (let 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: ShapeMap[R], randFunction: () => number, - dtype?: DataType): Tensor { - const size = util.sizeFromShape(shape); - - let 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 (let i = 0; i < size; i++) { - values[i] = randFunction(); - } - return ENGINE.makeTensor(values, shape, dtype) as Tensor; -} - /** * Creates a `tf.Tensor` with values drawn from a multinomial distribution. * @@ -1002,7 +940,7 @@ async function setdiff1dAsync_( * zeros. */ /** @doc {heading: 'Tensors', subheading: 'Creation'} */ -function buffer( +export function buffer( shape: ShapeMap[R], dtype: D = 'float32' as D, values?: DataTypeMap[D]): TensorBuffer { dtype = dtype || 'float32' as D; @@ -1027,8 +965,7 @@ function print(x: T, verbose = false): void { } export { - buffer, // Not wrapped in op() since no tensors. - print // Not wrapped in op() since no need to increase stack trace. + print // Not wrapped in op() since no need to increase stack trace. }; export const batchToSpaceND = op({batchToSpaceND_}); @@ -1045,8 +982,6 @@ export const pad1d = op({pad1d_}); export const pad2d = op({pad2d_}); export const pad3d = op({pad3d_}); export const pad4d = op({pad4d_}); -export const rand = op({rand_}); -export const randomUniform = op({randomUniform_}); export const reshape = op({reshape_}); export const spaceToBatchND = op({spaceToBatchND_}); export const squeeze = op({squeeze_}); diff --git a/tfjs-core/src/ops/array_ops_test.ts b/tfjs-core/src/ops/array_ops_test.ts index 7eac47ee321..a176cc9c7d8 100644 --- a/tfjs-core/src/ops/array_ops_test.ts +++ b/tfjs-core/src/ops/array_ops_test.ts @@ -17,8 +17,7 @@ import * as tf from '../index'; import {ALL_ENVS, BROWSER_ENVS, describeWithFlags, NODE_ENVS} from '../jasmine_util'; -import {expectArraysClose, expectArraysEqual, expectPromiseToFail, expectValuesInRange} from '../test_util'; -import * as util from '../util'; +import {expectArraysClose, expectArraysEqual, expectPromiseToFail} from '../test_util'; describeWithFlags('zeros', ALL_ENVS, () => { it('1D default dtype', async () => { @@ -764,116 +763,6 @@ describeWithFlags('onesLike', ALL_ENVS, () => { }); }); -describeWithFlags('rand', ALL_ENVS, () => { - it('should return a random 1D float32 array', async () => { - const shape: [number] = [10]; - - // Enusre defaults to float32 w/o type: - let result = tf.rand(shape, () => util.randUniform(0, 2)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2); - - result = tf.rand(shape, () => util.randUniform(0, 1.5)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 1D int32 array', async () => { - const shape: [number] = [10]; - const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 1D bool array', async () => { - const shape: [number] = [10]; - const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 2D float32 array', async () => { - const shape = [3, 4]; - - // Enusre defaults to float32 w/o type: - let result = tf.rand(shape, () => util.randUniform(0, 2.5)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.rand(shape, () => util.randUniform(0, 1.5), 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 2D int32 array', async () => { - const shape = [3, 4]; - const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 2D bool array', async () => { - const shape = [3, 4]; - const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 3D float32 array', async () => { - const shape = [3, 4, 5]; - - // Enusre defaults to float32 w/o type: - let result = tf.rand(shape, () => util.randUniform(0, 2.5)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.rand(shape, () => util.randUniform(0, 1.5), 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 3D int32 array', async () => { - const shape = [3, 4, 5]; - const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 3D bool array', async () => { - const shape = [3, 4, 5]; - const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 4D float32 array', async () => { - const shape = [3, 4, 5, 6]; - - // Enusre defaults to float32 w/o type: - let result = tf.rand(shape, () => util.randUniform(0, 2.5)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.rand(shape, () => util.randUniform(0, 1.5)); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 4D int32 array', async () => { - const shape = [3, 4, 5, 6]; - const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 4D bool array', async () => { - const shape = [3, 4, 5, 6]; - const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); -}); - describeWithFlags('eye', ALL_ENVS, () => { it('1x1', async () => { const r = tf.eye(1); @@ -947,143 +836,6 @@ describeWithFlags('eye', ALL_ENVS, () => { }); }); -describeWithFlags('randomUniform', ALL_ENVS, () => { - it('should return a random 1D float32 array', async () => { - const shape: [number] = [10]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomUniform(shape, 0, 2.5); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.randomUniform(shape, 0, 1.5, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 1D int32 array', async () => { - const shape: [number] = [10]; - const result = tf.randomUniform(shape, 0, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 1D bool array', async () => { - const shape: [number] = [10]; - const result = tf.randomUniform(shape, 0, 1, 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 2D float32 array', async () => { - const shape: [number, number] = [3, 4]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomUniform(shape, 0, 2.5); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.randomUniform(shape, 0, 1.5, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 2D int32 array', async () => { - const shape: [number, number] = [3, 4]; - const result = tf.randomUniform(shape, 0, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 2D bool array', async () => { - const shape: [number, number] = [3, 4]; - const result = tf.randomUniform(shape, 0, 1, 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 3D float32 array', async () => { - const shape: [number, number, number] = [3, 4, 5]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomUniform(shape, 0, 2.5); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.randomUniform(shape, 0, 1.5, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 3D int32 array', async () => { - const shape: [number, number, number] = [3, 4, 5]; - const result = tf.randomUniform(shape, 0, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 3D bool array', async () => { - const shape: [number, number, number] = [3, 4, 5]; - const result = tf.randomUniform(shape, 0, 1, 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 4D float32 array', async () => { - const shape: [number, number, number, number] = [3, 4, 5, 6]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomUniform(shape, 0, 2.5); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.randomUniform(shape, 0, 1.5, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 4D int32 array', async () => { - const shape: [number, number, number, number] = [3, 4, 5, 6]; - const result = tf.randomUniform(shape, 0, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 4D bool array', async () => { - const shape: [number, number, number, number] = [3, 4, 5, 6]; - const result = tf.randomUniform(shape, 0, 1, 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); - - it('should return a random 5D float32 array', async () => { - const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; - - // Ensure defaults to float32 w/o type: - let result = tf.randomUniform(shape, 0, 2.5); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 2.5); - - result = tf.randomUniform(shape, 0, 1.5, 'float32'); - expect(result.dtype).toBe('float32'); - expectValuesInRange(await result.data(), 0, 1.5); - }); - - it('should return a random 5D int32 array', async () => { - const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; - const result = tf.randomUniform(shape, 0, 2, 'int32'); - expect(result.dtype).toBe('int32'); - expectValuesInRange(await result.data(), 0, 2); - }); - - it('should return a random 5D bool array', async () => { - const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; - const result = tf.randomUniform(shape, 0, 1, 'bool'); - expect(result.dtype).toBe('bool'); - expectValuesInRange(await result.data(), 0, 1); - }); -}); - class MockContext { getImageData(x: number, y: number, width: number, height: number) { const data = new Uint8ClampedArray(width * height * 4); diff --git a/tfjs-core/src/ops/dropout.ts b/tfjs-core/src/ops/dropout.ts index a18942b59cf..0984ffc97ce 100644 --- a/tfjs-core/src/ops/dropout.ts +++ b/tfjs-core/src/ops/dropout.ts @@ -20,9 +20,9 @@ import {convertToTensor} from '../tensor_util_env'; import {TensorLike} from '../types'; import * as util from '../util'; -import {randomUniform} from './array_ops'; import {getNoiseShape} from './dropout_util'; import {op} from './operation'; +import {randomUniform} from './random_uniform'; /** * Computes dropout. diff --git a/tfjs-core/src/ops/ops.ts b/tfjs-core/src/ops/ops.ts index 87493d8b089..ca5f559813b 100644 --- a/tfjs-core/src/ops/ops.ts +++ b/tfjs-core/src/ops/ops.ts @@ -17,8 +17,10 @@ // Modularized ops. export {broadcastTo} from './broadcast_to'; +export {rand} from './rand'; export {randomGamma} from './random_gamma'; export {randomNormal} from './random_normal'; +export {randomUniform} from './random_uniform'; export {square} from './square'; export {squaredDifference} from './squared_difference'; export {truncatedNormal} from './truncated_normal'; diff --git a/tfjs-core/src/ops/rand.ts b/tfjs-core/src/ops/rand.ts index 9545cddc879..e368f64e836 100644 --- a/tfjs-core/src/ops/rand.ts +++ b/tfjs-core/src/ops/rand.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google Inc. All Rights Reserved. + * Copyright 2020 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 @@ -15,200 +15,39 @@ * ============================================================================= */ -import * as seedrandom from 'seedrandom'; +import {ENGINE} from '../engine'; +import {Tensor} from '../tensor'; +import {DataType, Rank, ShapeMap} from '../types'; +import {sizeFromShape} from '../util'; -export interface RandomBase { - nextValue(): number; -} - -export interface RandomGamma { - nextValue(): number; -} - -export interface RandNormalDataTypes { - float32: Float32Array; - int32: Int32Array; -} - -export interface RandGammaDataTypes { - float32: Float32Array; - int32: Int32Array; -} - -// https://en.wikipedia.org/wiki/Marsaglia_polar_method -export class MPRandGauss implements RandomBase { - private mean: number; - private stdDev: number; - private nextVal: number; - private dtype?: keyof RandNormalDataTypes; - private truncated?: boolean; - private upper?: number; - private lower?: number; - private random: seedrandom.prng; - - constructor( - mean: number, stdDeviation: number, dtype?: keyof RandNormalDataTypes, - truncated?: boolean, seed?: number) { - this.mean = mean; - this.stdDev = stdDeviation; - this.dtype = dtype; - this.nextVal = NaN; - this.truncated = truncated; - if (this.truncated) { - this.upper = this.mean + this.stdDev * 2; - this.lower = this.mean - this.stdDev * 2; - } - const seedValue = seed ? seed : Math.random(); - this.random = seedrandom.alea(seedValue.toString()); - } - - /** Returns next sample from a Gaussian distribution. */ - public nextValue(): number { - if (!isNaN(this.nextVal)) { - const value = this.nextVal; - this.nextVal = NaN; - return value; - } - - let resultX: number, resultY: number; - let isValid = false; - while (!isValid) { - let v1: number, v2: number, s: number; - do { - v1 = 2 * this.random() - 1; - v2 = 2 * this.random() - 1; - s = v1 * v1 + v2 * v2; - } while (s >= 1 || s === 0); - - const mul = Math.sqrt(-2.0 * Math.log(s) / s); - resultX = this.mean + this.stdDev * v1 * mul; - resultY = this.mean + this.stdDev * v2 * mul; - - if (!this.truncated || this.isValidTruncated(resultX)) { - isValid = true; - } - } - - if (!this.truncated || this.isValidTruncated(resultY)) { - this.nextVal = this.convertValue(resultY); - } - return this.convertValue(resultX); - } - - /** Handles proper rounding for non-floating-point numbers. */ - private convertValue(value: number): number { - if (this.dtype == null || this.dtype === 'float32') { - return value; - } - return Math.round(value); - } - - /** Returns true if less than 2-standard-deviations from the mean. */ - private isValidTruncated(value: number): boolean { - return value <= this.upper && value >= this.lower; - } -} +import {op} from './operation'; -// Marsaglia, George, and Wai Wan Tsang. 2000. "A Simple Method for Generating -// Gamma Variables." -export class RandGamma implements RandomGamma { - private alpha: number; - private beta: number; - private d: number; - private c: number; - private dtype?: keyof RandGammaDataTypes; - private randu: seedrandom.prng; - private randn: MPRandGauss; - - constructor( - alpha: number, beta: number, dtype: keyof RandGammaDataTypes, - seed?: number) { - this.alpha = alpha; - this.beta = 1 / beta; // convert rate to scale parameter - this.dtype = dtype; - - const seedValue = seed ? seed : Math.random(); - this.randu = seedrandom.alea(seedValue.toString()); - this.randn = new MPRandGauss(0, 1, dtype, false, this.randu()); - - if (alpha < 1) { - this.d = alpha + (2 / 3); - } else { - this.d = alpha - (1 / 3); - } - this.c = 1 / Math.sqrt(9 * this.d); - } - - /** Returns next sample from a gamma distribution. */ - public nextValue(): number { - let x2: number, v0: number, v1: number, x: number, u: number, v: number; - while (true) { - do { - x = this.randn.nextValue(); - v = 1 + (this.c * x); - } while (v <= 0); - v *= v * v; - x2 = x * x; - v0 = 1 - (0.331 * x2 * x2); - v1 = (0.5 * x2) + (this.d * (1 - v + Math.log(v))); - u = this.randu(); - if (u < v0 || Math.log(u) < v1) { - break; - } - } - v = (1 / this.beta) * this.d * v; - if (this.alpha < 1) { - v *= Math.pow(this.randu(), 1 / this.alpha); - } - return this.convertValue(v); - } - /** Handles proper rounding for non-floating-point numbers. */ - private convertValue(value: number): number { - if (this.dtype === 'float32') { - return value; - } - return Math.round(value); - } -} - -export class UniformRandom implements RandomBase { - private min: number; - private range: number; - private random: seedrandom.prng; - private dtype?: keyof RandNormalDataTypes; - - constructor( - min = 0, max = 1, dtype?: keyof RandNormalDataTypes, - seed?: string|number) { - this.min = min; - this.range = max - min; - this.dtype = dtype; - if (seed == null) { - seed = Math.random(); - } - if (typeof seed === 'number') { - seed = seed.toString(); - } - - if (!this.canReturnFloat() && this.range <= 1) { - throw new Error( - `The difference between ${min} - ${max} <= 1 and dtype is not float`); - } - this.random = seedrandom.alea(seed); - } - - /** Handles proper rounding for non floating point numbers. */ - private canReturnFloat = () => - (this.dtype == null || this.dtype === 'float32'); - - private convertValue(value: number): number { - if (this.canReturnFloat()) { - return value; - } - return Math.round(value); - } - - nextValue() { - return this.convertValue(this.min + this.range * this.random()); - } +/** + * 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: ShapeMap[R], randFunction: () => number, + dtype?: DataType): Tensor { + const size = sizeFromShape(shape); + let 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 (let i = 0; i < size; i++) { + values[i] = randFunction(); + } + return ENGINE.makeTensor(values, shape, dtype) as Tensor; } +export const rand = op({rand_}); diff --git a/tfjs-core/src/ops/rand_test.ts b/tfjs-core/src/ops/rand_test.ts index 24ccfc93a27..099fb135254 100644 --- a/tfjs-core/src/ops/rand_test.ts +++ b/tfjs-core/src/ops/rand_test.ts @@ -15,10 +15,124 @@ * ============================================================================= */ +import {util} from '..'; +import * as tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; import {expectValuesInRange} from '../test_util'; -import {MPRandGauss, RandGamma, UniformRandom} from './rand'; + +import {MPRandGauss, RandGamma, UniformRandom} from './rand_util'; import {expectArrayInMeanStdRange, jarqueBeraNormalityTest} from './rand_util'; +describeWithFlags('rand', ALL_ENVS, () => { + it('should return a random 1D float32 array', async () => { + const shape: [number] = [10]; + + // Enusre defaults to float32 w/o type: + let result = tf.rand(shape, () => util.randUniform(0, 2)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2); + + result = tf.rand(shape, () => util.randUniform(0, 1.5)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 1D int32 array', async () => { + const shape: [number] = [10]; + const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 1D bool array', async () => { + const shape: [number] = [10]; + const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 2D float32 array', async () => { + const shape = [3, 4]; + + // Enusre defaults to float32 w/o type: + let result = tf.rand(shape, () => util.randUniform(0, 2.5)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.rand(shape, () => util.randUniform(0, 1.5), 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 2D int32 array', async () => { + const shape = [3, 4]; + const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 2D bool array', async () => { + const shape = [3, 4]; + const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 3D float32 array', async () => { + const shape = [3, 4, 5]; + + // Enusre defaults to float32 w/o type: + let result = tf.rand(shape, () => util.randUniform(0, 2.5)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.rand(shape, () => util.randUniform(0, 1.5), 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 3D int32 array', async () => { + const shape = [3, 4, 5]; + const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 3D bool array', async () => { + const shape = [3, 4, 5]; + const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 4D float32 array', async () => { + const shape = [3, 4, 5, 6]; + + // Enusre defaults to float32 w/o type: + let result = tf.rand(shape, () => util.randUniform(0, 2.5)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.rand(shape, () => util.randUniform(0, 1.5)); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 4D int32 array', async () => { + const shape = [3, 4, 5, 6]; + const result = tf.rand(shape, () => util.randUniform(0, 2), 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 4D bool array', async () => { + const shape = [3, 4, 5, 6]; + const result = tf.rand(shape, () => util.randUniform(0, 1), 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); +}); + function isFloat(n: number): boolean { return Number(n) === n && n % 1 !== 0; } diff --git a/tfjs-core/src/ops/rand_util.ts b/tfjs-core/src/ops/rand_util.ts index 25f99ec8125..889b3bfc153 100644 --- a/tfjs-core/src/ops/rand_util.ts +++ b/tfjs-core/src/ops/rand_util.ts @@ -15,9 +15,207 @@ * ============================================================================= */ +import * as seedrandom from 'seedrandom'; + import {expectNumbersClose, testEpsilon} from '../test_util'; import {TypedArray} from '../types'; +export interface RandomBase { + nextValue(): number; +} + +export interface RandomGamma { + nextValue(): number; +} + +export interface RandNormalDataTypes { + float32: Float32Array; + int32: Int32Array; +} + +export interface RandGammaDataTypes { + float32: Float32Array; + int32: Int32Array; +} + +// https://en.wikipedia.org/wiki/Marsaglia_polar_method +export class MPRandGauss implements RandomBase { + private mean: number; + private stdDev: number; + private nextVal: number; + private dtype?: keyof RandNormalDataTypes; + private truncated?: boolean; + private upper?: number; + private lower?: number; + private random: seedrandom.prng; + + constructor( + mean: number, stdDeviation: number, dtype?: keyof RandNormalDataTypes, + truncated?: boolean, seed?: number) { + this.mean = mean; + this.stdDev = stdDeviation; + this.dtype = dtype; + this.nextVal = NaN; + this.truncated = truncated; + if (this.truncated) { + this.upper = this.mean + this.stdDev * 2; + this.lower = this.mean - this.stdDev * 2; + } + const seedValue = seed ? seed : Math.random(); + this.random = seedrandom.alea(seedValue.toString()); + } + + /** Returns next sample from a Gaussian distribution. */ + public nextValue(): number { + if (!isNaN(this.nextVal)) { + const value = this.nextVal; + this.nextVal = NaN; + return value; + } + + let resultX: number, resultY: number; + let isValid = false; + while (!isValid) { + let v1: number, v2: number, s: number; + do { + v1 = 2 * this.random() - 1; + v2 = 2 * this.random() - 1; + s = v1 * v1 + v2 * v2; + } while (s >= 1 || s === 0); + + const mul = Math.sqrt(-2.0 * Math.log(s) / s); + resultX = this.mean + this.stdDev * v1 * mul; + resultY = this.mean + this.stdDev * v2 * mul; + + if (!this.truncated || this.isValidTruncated(resultX)) { + isValid = true; + } + } + + if (!this.truncated || this.isValidTruncated(resultY)) { + this.nextVal = this.convertValue(resultY); + } + return this.convertValue(resultX); + } + + /** Handles proper rounding for non-floating-point numbers. */ + private convertValue(value: number): number { + if (this.dtype == null || this.dtype === 'float32') { + return value; + } + return Math.round(value); + } + + /** Returns true if less than 2-standard-deviations from the mean. */ + private isValidTruncated(value: number): boolean { + return value <= this.upper && value >= this.lower; + } +} + +// Marsaglia, George, and Wai Wan Tsang. 2000. "A Simple Method for Generating +// Gamma Variables." +export class RandGamma implements RandomGamma { + private alpha: number; + private beta: number; + private d: number; + private c: number; + private dtype?: keyof RandGammaDataTypes; + private randu: seedrandom.prng; + private randn: MPRandGauss; + + constructor( + alpha: number, beta: number, dtype: keyof RandGammaDataTypes, + seed?: number) { + this.alpha = alpha; + this.beta = 1 / beta; // convert rate to scale parameter + this.dtype = dtype; + + const seedValue = seed ? seed : Math.random(); + this.randu = seedrandom.alea(seedValue.toString()); + this.randn = new MPRandGauss(0, 1, dtype, false, this.randu()); + + if (alpha < 1) { + this.d = alpha + (2 / 3); + } else { + this.d = alpha - (1 / 3); + } + this.c = 1 / Math.sqrt(9 * this.d); + } + + /** Returns next sample from a gamma distribution. */ + public nextValue(): number { + let x2: number, v0: number, v1: number, x: number, u: number, v: number; + while (true) { + do { + x = this.randn.nextValue(); + v = 1 + (this.c * x); + } while (v <= 0); + v *= v * v; + x2 = x * x; + v0 = 1 - (0.331 * x2 * x2); + v1 = (0.5 * x2) + (this.d * (1 - v + Math.log(v))); + u = this.randu(); + if (u < v0 || Math.log(u) < v1) { + break; + } + } + v = (1 / this.beta) * this.d * v; + if (this.alpha < 1) { + v *= Math.pow(this.randu(), 1 / this.alpha); + } + return this.convertValue(v); + } + /** Handles proper rounding for non-floating-point numbers. */ + private convertValue(value: number): number { + if (this.dtype === 'float32') { + return value; + } + return Math.round(value); + } +} + +export class UniformRandom implements RandomBase { + private min: number; + private range: number; + private random: seedrandom.prng; + private dtype?: keyof RandNormalDataTypes; + + constructor( + min = 0, max = 1, dtype?: keyof RandNormalDataTypes, + seed?: string|number) { + this.min = min; + this.range = max - min; + this.dtype = dtype; + if (seed == null) { + seed = Math.random(); + } + if (typeof seed === 'number') { + seed = seed.toString(); + } + + if (!this.canReturnFloat() && this.range <= 1) { + throw new Error( + `The difference between ${min} - ${max} <= 1 and dtype is not float`); + } + this.random = seedrandom.alea(seed); + } + + /** Handles proper rounding for non floating point numbers. */ + private canReturnFloat = () => + (this.dtype == null || this.dtype === 'float32'); + + private convertValue(value: number): number { + if (this.canReturnFloat()) { + return value; + } + return Math.round(value); + } + + nextValue() { + return this.convertValue(this.min + this.range * this.random()); + } +} + export function jarqueBeraNormalityTest(values: TypedArray|number[]) { // https://en.wikipedia.org/wiki/Jarque%E2%80%93Bera_test const n = values.length; @@ -88,3 +286,21 @@ function skewness(values: TypedArray|number[]) { } return (1 / n) * sum3 / Math.pow((1 / (n - 1)) * sum2, 3 / 2); } + +export interface RandomBase { + nextValue(): number; +} + +export interface RandomGamma { + nextValue(): number; +} + +export interface RandNormalDataTypes { + float32: Float32Array; + int32: Int32Array; +} + +export interface RandGammaDataTypes { + float32: Float32Array; + int32: Int32Array; +} diff --git a/tfjs-core/src/ops/random_gamma.ts b/tfjs-core/src/ops/random_gamma.ts index 3c740bc1794..5136cba43eb 100644 --- a/tfjs-core/src/ops/random_gamma.ts +++ b/tfjs-core/src/ops/random_gamma.ts @@ -20,7 +20,7 @@ import {Rank, ShapeMap} from '../types'; import {buffer} from './array_ops'; import {op} from './operation'; -import {RandGamma} from './rand'; +import {RandGamma} from './rand_util'; /** * Creates a `tf.Tensor` with values sampled from a gamma distribution. diff --git a/tfjs-core/src/ops/random_normal.ts b/tfjs-core/src/ops/random_normal.ts index a60120b4cd1..c9b337a29b9 100644 --- a/tfjs-core/src/ops/random_normal.ts +++ b/tfjs-core/src/ops/random_normal.ts @@ -20,7 +20,7 @@ import {DataType, Rank, ShapeMap} from '../types'; import {buffer} from './array_ops'; import {op} from './operation'; -import {MPRandGauss} from './rand'; +import {MPRandGauss} from './rand_util'; /** * Creates a `tf.Tensor` with values sampled from a normal distribution. diff --git a/tfjs-core/src/ops/random_uniform.ts b/tfjs-core/src/ops/random_uniform.ts new file mode 100644 index 00000000000..c9f40341401 --- /dev/null +++ b/tfjs-core/src/ops/random_uniform.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2020 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 {Tensor} from '../tensor'; +import {DataType, Rank, ShapeMap} from '../types'; + +import {buffer} from './array_ops'; +import {op} from './operation'; +import {UniformRandom} from './rand_util'; + +/** + * 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: ShapeMap[R], minval = 0, maxval = 1, dtype: DataType = 'float32', + seed?: number|string): Tensor { + const res = buffer(shape, dtype); + const random = new UniformRandom(minval, maxval, null, seed); + for (let i = 0; i < res.values.length; i++) { + res.values[i] = random.nextValue(); + } + return res.toTensor(); +} +export const randomUniform = op({randomUniform_}); diff --git a/tfjs-core/src/ops/random_uniform_test.ts b/tfjs-core/src/ops/random_uniform_test.ts new file mode 100644 index 00000000000..cbc4ace52d7 --- /dev/null +++ b/tfjs-core/src/ops/random_uniform_test.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2020 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 tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectValuesInRange} from '../test_util'; + +describeWithFlags('randomUniform', ALL_ENVS, () => { + it('should return a random 1D float32 array', async () => { + const shape: [number] = [10]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomUniform(shape, 0, 2.5); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.randomUniform(shape, 0, 1.5, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 1D int32 array', async () => { + const shape: [number] = [10]; + const result = tf.randomUniform(shape, 0, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 1D bool array', async () => { + const shape: [number] = [10]; + const result = tf.randomUniform(shape, 0, 1, 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 2D float32 array', async () => { + const shape: [number, number] = [3, 4]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomUniform(shape, 0, 2.5); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.randomUniform(shape, 0, 1.5, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 2D int32 array', async () => { + const shape: [number, number] = [3, 4]; + const result = tf.randomUniform(shape, 0, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 2D bool array', async () => { + const shape: [number, number] = [3, 4]; + const result = tf.randomUniform(shape, 0, 1, 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 3D float32 array', async () => { + const shape: [number, number, number] = [3, 4, 5]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomUniform(shape, 0, 2.5); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.randomUniform(shape, 0, 1.5, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 3D int32 array', async () => { + const shape: [number, number, number] = [3, 4, 5]; + const result = tf.randomUniform(shape, 0, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 3D bool array', async () => { + const shape: [number, number, number] = [3, 4, 5]; + const result = tf.randomUniform(shape, 0, 1, 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 4D float32 array', async () => { + const shape: [number, number, number, number] = [3, 4, 5, 6]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomUniform(shape, 0, 2.5); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.randomUniform(shape, 0, 1.5, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 4D int32 array', async () => { + const shape: [number, number, number, number] = [3, 4, 5, 6]; + const result = tf.randomUniform(shape, 0, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 4D bool array', async () => { + const shape: [number, number, number, number] = [3, 4, 5, 6]; + const result = tf.randomUniform(shape, 0, 1, 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); + + it('should return a random 5D float32 array', async () => { + const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; + + // Ensure defaults to float32 w/o type: + let result = tf.randomUniform(shape, 0, 2.5); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 2.5); + + result = tf.randomUniform(shape, 0, 1.5, 'float32'); + expect(result.dtype).toBe('float32'); + expectValuesInRange(await result.data(), 0, 1.5); + }); + + it('should return a random 5D int32 array', async () => { + const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; + const result = tf.randomUniform(shape, 0, 2, 'int32'); + expect(result.dtype).toBe('int32'); + expectValuesInRange(await result.data(), 0, 2); + }); + + it('should return a random 5D bool array', async () => { + const shape: [number, number, number, number, number] = [2, 3, 4, 5, 6]; + const result = tf.randomUniform(shape, 0, 1, 'bool'); + expect(result.dtype).toBe('bool'); + expectValuesInRange(await result.data(), 0, 1); + }); +}); diff --git a/tfjs-core/src/ops/truncated_normal.ts b/tfjs-core/src/ops/truncated_normal.ts index 022c2df3101..2fbb38cdbbb 100644 --- a/tfjs-core/src/ops/truncated_normal.ts +++ b/tfjs-core/src/ops/truncated_normal.ts @@ -20,7 +20,7 @@ import {DataType, Rank, ShapeMap} from '../types'; import {buffer} from './array_ops'; import {op} from './operation'; -import {MPRandGauss} from './rand'; +import {MPRandGauss} from './rand_util'; /** * Creates a `tf.Tensor` with values sampled from a truncated normal diff --git a/tfjs-core/src/tests.ts b/tfjs-core/src/tests.ts index dddd4a3eaa1..e06e2239832 100644 --- a/tfjs-core/src/tests.ts +++ b/tfjs-core/src/tests.ts @@ -78,6 +78,9 @@ import './ops/operation_test'; import './ops/pad_test'; import './ops/pool_test'; import './ops/rand_test'; +import './ops/random_gamma_test'; +import './ops/random_normal_test'; +import './ops/random_uniform_test'; import './ops/reduction_ops_test'; import './ops/resize_bilinear_test'; import './ops/resize_nearest_neighbor_test'; @@ -93,6 +96,7 @@ import './ops/spectral_ops_test'; import './ops/strided_slice_test'; import './ops/topk_test'; import './ops/transpose_test'; +import './ops/truncated_normal_test'; import './ops/unary_ops_test'; import './optimizers/adadelta_optimizer_test'; import './optimizers/adagrad_optimizer_test'; From e0d0a090708c5a3d5d124cf258e1c17e03ac46cd Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Fri, 20 Mar 2020 12:18:14 -0400 Subject: [PATCH 3/5] multinomial --- tfjs-core/src/ops/array_ops.ts | 43 ------------------ tfjs-core/src/ops/multinomial.ts | 63 +++++++++++++++++++++++++++ tfjs-core/src/ops/ops.ts | 1 + tfjs-core/src/ops/truncated_normal.ts | 2 +- 4 files changed, 65 insertions(+), 44 deletions(-) create mode 100644 tfjs-core/src/ops/multinomial.ts diff --git a/tfjs-core/src/ops/array_ops.ts b/tfjs-core/src/ops/array_ops.ts index a15d89c8cae..afdbf96096f 100644 --- a/tfjs-core/src/ops/array_ops.ts +++ b/tfjs-core/src/ops/array_ops.ts @@ -100,48 +100,6 @@ function eye_( } } -/** - * 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: Tensor1D|Tensor2D|TensorLike, numSamples: number, seed?: number, - normalized = false): Tensor1D|Tensor2D { - const $logits = convertToTensor(logits, 'logits', 'multinomial'); - const numOutcomes = $logits.size; - const 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(); - const logits2D = origRank === 1 ? $logits.as2D(1, -1) : $logits as Tensor2D; - const res = ENGINE.runKernelFunc( - backend => backend.multinomial(logits2D, normalized, numSamples, seed), - {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 @@ -975,7 +933,6 @@ export const cumsum = op({cumsum_}); export const depthToSpace = op({depthToSpace_}); export const expandDims = op({expandDims_}); export const eye = op({eye_}); -export const multinomial = op({multinomial_}); export const oneHot = op({oneHot_}); export const pad = op({pad_}); export const pad1d = op({pad1d_}); diff --git a/tfjs-core/src/ops/multinomial.ts b/tfjs-core/src/ops/multinomial.ts new file mode 100644 index 00000000000..73ba79582eb --- /dev/null +++ b/tfjs-core/src/ops/multinomial.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright 2020 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 {ENGINE} from '../engine'; +import {Tensor1D, Tensor2D} from '../tensor'; +import {convertToTensor} from '../tensor_util_env'; +import {TensorLike} from '../types'; +import {op} from './operation'; +/** + * 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: Tensor1D|Tensor2D|TensorLike, numSamples: number, seed?: number, + normalized = false): Tensor1D|Tensor2D { + const $logits = convertToTensor(logits, 'logits', 'multinomial'); + const numOutcomes = $logits.size; + const 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(); + const logits2D = origRank === 1 ? $logits.as2D(1, -1) : $logits as Tensor2D; + const res = ENGINE.runKernelFunc( + backend => backend.multinomial(logits2D, normalized, numSamples, seed), + {logits2D}); + return origRank === 1 ? res.as1D() : res; +} +export const multinomial = op({multinomial_}); diff --git a/tfjs-core/src/ops/ops.ts b/tfjs-core/src/ops/ops.ts index ca5f559813b..daa8f8ba8da 100644 --- a/tfjs-core/src/ops/ops.ts +++ b/tfjs-core/src/ops/ops.ts @@ -17,6 +17,7 @@ // Modularized ops. export {broadcastTo} from './broadcast_to'; +export {multinomial} from './multinomial'; export {rand} from './rand'; export {randomGamma} from './random_gamma'; export {randomNormal} from './random_normal'; diff --git a/tfjs-core/src/ops/truncated_normal.ts b/tfjs-core/src/ops/truncated_normal.ts index 2fbb38cdbbb..eb8bfcd5d2b 100644 --- a/tfjs-core/src/ops/truncated_normal.ts +++ b/tfjs-core/src/ops/truncated_normal.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google Inc. All Rights Reserved. + * Copyright 2020 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 From f23bae33f908c83ddbb5cb8b22994382fd879db6 Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Fri, 20 Mar 2020 12:20:17 -0400 Subject: [PATCH 4/5] save --- tfjs-core/src/ops/random_normal_test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tfjs-core/src/ops/random_normal_test.ts b/tfjs-core/src/ops/random_normal_test.ts index aa86ef5b9d8..c5b6425664f 100644 --- a/tfjs-core/src/ops/random_normal_test.ts +++ b/tfjs-core/src/ops/random_normal_test.ts @@ -1,4 +1,21 @@ +/** + * @license + * Copyright 2020 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 tf from '../index'; import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; import {expectArrayInMeanStdRange, jarqueBeraNormalityTest} from './rand_util'; From 4c636d6af4f1adcabcb502454678745845a47a3d Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Mon, 23 Mar 2020 09:57:34 -0400 Subject: [PATCH 5/5] formatting --- tfjs-core/src/ops/multinomial.ts | 2 ++ tfjs-core/src/ops/rand.ts | 1 + tfjs-core/src/ops/random_gamma.ts | 1 + tfjs-core/src/ops/random_uniform.ts | 1 + 4 files changed, 5 insertions(+) diff --git a/tfjs-core/src/ops/multinomial.ts b/tfjs-core/src/ops/multinomial.ts index 73ba79582eb..ee236591d8d 100644 --- a/tfjs-core/src/ops/multinomial.ts +++ b/tfjs-core/src/ops/multinomial.ts @@ -20,6 +20,7 @@ import {Tensor1D, Tensor2D} from '../tensor'; import {convertToTensor} from '../tensor_util_env'; import {TensorLike} from '../types'; import {op} from './operation'; + /** * Creates a `tf.Tensor` with values drawn from a multinomial distribution. * @@ -60,4 +61,5 @@ function multinomial_( {logits2D}); return origRank === 1 ? res.as1D() : res; } + export const multinomial = op({multinomial_}); diff --git a/tfjs-core/src/ops/rand.ts b/tfjs-core/src/ops/rand.ts index e368f64e836..7affe079146 100644 --- a/tfjs-core/src/ops/rand.ts +++ b/tfjs-core/src/ops/rand.ts @@ -50,4 +50,5 @@ function rand_( } return ENGINE.makeTensor(values, shape, dtype) as Tensor; } + export const rand = op({rand_}); diff --git a/tfjs-core/src/ops/random_gamma.ts b/tfjs-core/src/ops/random_gamma.ts index 5136cba43eb..665b17f0d21 100644 --- a/tfjs-core/src/ops/random_gamma.ts +++ b/tfjs-core/src/ops/random_gamma.ts @@ -56,4 +56,5 @@ function randomGamma_( } return res.toTensor(); } + export const randomGamma = op({randomGamma_}); diff --git a/tfjs-core/src/ops/random_uniform.ts b/tfjs-core/src/ops/random_uniform.ts index c9f40341401..c269658dcc5 100644 --- a/tfjs-core/src/ops/random_uniform.ts +++ b/tfjs-core/src/ops/random_uniform.ts @@ -51,4 +51,5 @@ function randomUniform_( } return res.toTensor(); } + export const randomUniform = op({randomUniform_});