From 7e167c051a5791cfe63300264bc1d01f3c35a94c Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 12:25:39 -0400 Subject: [PATCH 01/27] Add tensorflow docs --- src/libraries.scrbl | 1 + src/trove/tensorflow.scrbl | 1016 ++++++++++++++++++++++++++++++++++++ 2 files changed, 1017 insertions(+) create mode 100644 src/trove/tensorflow.scrbl diff --git a/src/libraries.scrbl b/src/libraries.scrbl index 88e5ef3..c03fc51 100644 --- a/src/libraries.scrbl +++ b/src/libraries.scrbl @@ -37,3 +37,4 @@ This section contains information on libraries that come with Pyret. @include-section["trove/plot.js.rkt"] @include-section["trove/statistics.scrbl"] @include-section["trove/math.scrbl"] +@include-section["trove/tensorflow.scrbl"] diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl new file mode 100644 index 0000000..18b361a --- /dev/null +++ b/src/trove/tensorflow.scrbl @@ -0,0 +1,1016 @@ +#lang scribble/manual +@(require "../../scribble-api.rkt" "../abbrevs.rkt") + +@(define Tensor (a-id "Tensor" (xref "tensorflow" "Tensor"))) +@(define Model (a-id "Model" (xref "tensorflow" "Model"))) +@(define Layer (a-id "Layer" (xref "tensorflow" "Layer"))) + +@(define (tensor-method name) + (method-doc "Tensor" #f name #:alt-docstrings "")) + +@(append-gen-docs + `(module "tensorflow" + (path "src/arr/trove/tensorflow.arr") + + (fun-spec (name "tensor")) + (fun-spec + (name "list-to-tensor") + (arity 1) + (args ("values")) + (return ,Tensor) + (contract + (a-arrow ,(L-of N) ,Tensor))) + (fun-spec + (name "make-scalar") + (arity 1) + (args ("value")) + (return ,Tensor) + (contract + (a-arrow ,N ,Tensor))) + (fun-spec + (name "random-normal") + (arity 1) + (args ("shape")) + (return ,Tensor) + (contract + (a-arrow ,(L-of N) ,Tensor))) + (fun-spec + (name "make-variable") + (arity 1) + (args ("initial-value")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + + (data-spec + (name "Tensor") + (type-vars ()) + (variants ("tensor")) + (shared + ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,N))) + (method-spec + (name "shape") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,(L-of N)))) + (method-spec + (name "flatten") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "as-scalar") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "as-1d") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "as-2d") + (arity 3) + (params ()) + (args ("self" "rows" "columns")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N ,N ,Tensor))) + (method-spec + (name "as-3d") + (arity 4) + (params ()) + (args ("self" "rows" "columns" "depth")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N ,N ,N ,Tensor))) + (method-spec + (name "as-4d") + (arity 5) + (params ()) + (args ("self" "rows" "columns" "depth1" "depth2")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N ,N ,N ,N ,Tensor))) + (method-spec + (name "as-type") + (arity 2) + (params ()) + (args ("self" "dataType")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,S ,Tensor))) + (method-spec + (name "data-sync") + (arity 1) + (params ()) + (args ("self" )) + (return ,(L-of RN)) + (contract + (a-arrow ,Tensor ,(L-of RN)))) + (method-spec + (name "to-float") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "to-int") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "to-bool") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "to-variable") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "reshape") + (arity 2) + (params ()) + (args ("self" "newShape")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(L-of N) ,Tensor))) + (method-spec + (name "clone") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "placeholder") + (arity 2) + (params ()) + (args ("self" "width")) + (return ,(L-of S)) + (contract + (a-arrow ,Tensor ,N ,(L-of S)))) + ))) + + (fun-spec + (name "add-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-add-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "subtract-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "multiply-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "divide-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "floor-divide-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-max") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-min") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-modulo") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-expt") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-squared-difference") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + + (fun-spec + (name "tensor-abs") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-acos") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-acosh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-asin") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-asinh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-atan") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-atan2") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "tensor-atanh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-ceil") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "clip-by-value") + (arity 3) + (args ("tensor" "min-value" "max-value")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N, N ,Tensor))) + (fun-spec + (name "tensor-cos") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-cosh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "exponential-linear-units") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "elu") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "gauss-error") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "erf") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-exp") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-exp-min1") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-floor") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "leaky-relu") + (arity 2) + (args ("tensor" "alpha")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N ,Tensor))) + (fun-spec + (name "tensor-log") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-log-plus1") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "log-sigmoid") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-negate") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "parametric-relu") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-reciprocal") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "relu") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-round") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "reciprocal-sqrt") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "scaled-elu") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "sigmoid") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "signed-ones") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-sin") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "softplus") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-sqrt") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-square") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "step") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-tan") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-tanh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + + (fun-spec + (name "all") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "arg-max") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "arg-min") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "log-sum-exp") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "reduce-max") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "mean") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "reduce-min") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "reduce-sum") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) + )) + +@docmodule["tensorflow"]{ + A module that provides a Pyret interface for TensorFlow, a + symbolic math library for machine learning applications. + + @;######################################################################### + @section{The Tensor Datatype} + + @type-spec["Tensor"]{ + + @pyret{Tensor}s are the core datastructure for @pyret{tensorflow} + applications. They are a generalization of vectors and matrices that + allows for higher dimensions. + + For example, a tensor could be a one-dimensional matrix (a vector), a + three-dimensional matrix (a cube), a zero-dimensional matrix (a single + number), or a higher dimensional structure that is more difficult to + visualize. + + @margin-note{ + @pyret{Tensor}s actually store values in a form slightly less precise + than @pyret{Roughnum}s. The reason for this is that TensorFlow.js (the + library that Pyret @pyret{Tensor}s are built on) stores tensor values in + JavaScript @tt{Float32Array}s for performance reasons. (But this + shouldn't substantially affect program results in most cases.) + } + + For performance reasons, @pyret{Tensor}s do not support arbitrary + precision. Retrieving values from a @pyret{Tensor} using + @pyret-method["Tensor" "data-sync"] always returns a + @pyret{List}. + + } + + @;######################################################################### + @section{Tensor Constructors} + + @collection-doc["tensor" #:contract `(a-arrow ("value" ,N) ,Tensor)] + + Creates a new @pyret{Tensor} with the given @pyret{value}s. + + Every @pyret{Tensor} created with this constructor is one-dimensional. Use + @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], + @pyret-method["Tensor" "as-3d"], @pyret-method["Tensor" "as-4d"], or + @pyret-method["Tensor" "reshape"] to change the shape of a @pyret{Tensor} + after instantiating it. + + @examples{ + [tensor: 1, 2, 3] # a size-3 tensor + [tensor: 1.4, 5.2, 0.4, 12.4, 14.3, 6].as-2d(3, 2) # a 3 x 2 tensor + [tensor: 9, 4, 0, -32, 23, 1, 3, 2].as-3d(2, 2, 2) # a 2 x 2 x 2 tensor + } + + @function["list-to-tensor"] + + Creates a new @pyret{Tensor} with the values in the input @pyret{List}. + + Similar to the @pyret-id["tensor"] constructor, all @pyret{Tensor}s created + using @pyret-id["list-to-tensor"] are one-dimensional by default. Use + @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], + @pyret-method["Tensor" "as-3d"], @pyret-method["Tensor" "as-4d"], or + @pyret-method["Tensor" "reshape"] to change the shape of a @pyret{Tensor} + after instantiating it. + + @function["make-scalar"] + + Creates a new @pyret{Tensor} of rank-0 with the given @pyret{value}. + + The same functionality can be achieved with the @pyret-id["tensor"] + constructor and the @pyret-method["Tensor" "as-scalar"] method, but it's + recommended to use @pyret-id["make-scalar"] as it makes the code more + readable. + + @function["random-normal"] + + Creates a new @pyret{Tensor} with the given shape (represented as values in + the input @pyret{List}) where all of the values are sampled from a normal + distribution. + + @function["make-variable"] + + Creates a new, mutable @pyret{Tensor} initialized to the values of the input + @pyret{Tensor}. + + The same functionality can be achieved with the + @pyret-method["Tensor" "to-variable"] method. + + @;######################################################################### + @section{Tensor Methods} + + @tensor-method["size"] + + Returns the size of the @pyret{Tensor} (the number of values stored in the + @pyret{Tensor}). + + @examples{ + check: + make-scalar(4.21).size() is 1 + [tensor: 6.32].size() is 1 + [tensor: 1, 2, 3].size() is 3 + [tensor: 1.4, 5.2, 0.4, 12.4, 14.3, 6].as-2d(3, 2).size() is 6 + end + } + + @tensor-method["shape"] + + Returns a @pyret{List} representing the shape of the @pyret{Tensor}. + Each element in the @pyret{List} corresponds to the size in each + dimension. + + @examples{ + check: + make-scalar(3).shape() is empty + [tensor: 9].shape() is [list: 1] + [tensor: 8, 3, 1].shape() is [list: 3] + [tensor: 0, 0, 0, 0, 0, 0].as-2d(3, 2).shape() is [list: 3, 2] + end + } + + @tensor-method["flatten"] + + Constructs a new, one-dimensional @pyret{Tensor} from the values of the + original @pyret{Tensor}. + + @examples{ + check: + a = [tensor: 1, 2, 3, 4, 5, 6].as-2d(3, 2) + a.shape() is [list: 3, 2] + a.flatten().shape() is [list: 6] + + b = make-scalar(12) + b.shape() is empty + b.flatten().shape() is [list: 1] + end + } + + @tensor-method["as-scalar"] + + Constructs a new, zero-dimensional @pyret{Tensor} from the values of the + original, size-1 @pyret{Tensor}. + + Raises an error if the calling @pyret{Tensor} is not size-1. + + @examples{ + check: + one-dim = [TF.tensor: 1] + one-dim.as-scalar().shape() is empty + one-dim.shape() is [list: 1] # doesn't modify shape of original tensor + + two-dim = [TF.tensor: 1, 2] + two-dim.as-scalar() raises + "Tensor was size-2 but `as-scalar` requires the tensor to be size-1" + end + } + + @tensor-method["as-1d"] + + Constructs a new, rank-1 @pyret{Tensor} from the values of the original + @pyret{Tensor}. + + @tensor-method["as-2d"] + + Constructs a new, rank-2 @pyret{Tensor} with the input dimensions from the + values of the original @pyret{Tensor}. + + @tensor-method["as-3d"] + + Constructs a new, rank-3 @pyret{Tensor} with the input dimensions from the + values of the original @pyret{Tensor}. + + @tensor-method["as-4d"] + + Constructs a new, rank-4 @pyret{Tensor} with the input dimensions from the + values of the original @pyret{Tensor}. + + @tensor-method["as-type"] + + Constructs a new @pyret{Tensor} from the values of the original + @pyret{Tensor} with all of the values cast to the input datatype. + + The possible @pyret{dataType}s are @pyret{"float32"}, @pyret{"int32"}, or + @pyret{"bool"}. Any other @pyret{dataType} will raise an error. + + @tensor-method["data-sync"] + + Returns a @pyret{List} containing the data in the @pyret{Tensor}. + + The "@pyret{-sync}" part of the method name is a remnant of the Tensorflow.js + naming scheme. + + @tensor-method["to-float"] + + Constructs a new @pyret{Tensor} from the values of the original + @pyret{Tensor} with all of the values cast to the @tt{"float32"} datatype. + + @tensor-method["to-int"] + + Constructs a new @pyret{Tensor} from the values of the original + @pyret{Tensor} with all of the values cast to the @tt{"int32"} datatype. + + @tensor-method["to-bool"] + + Constructs a new @pyret{Tensor} from the values of the original + @pyret{Tensor} with all of the values cast to the @tt{"bool"} datatype. + + @tensor-method["to-variable"] + + Constructs a new, mutable @pyret{Tensor} from the values of the original + @pyret{Tensor}. + + @tensor-method["reshape"] + + Constructs a new @pyret{Tensor} with the input dimensions @pyret{newShape} + from the values of the original @pyret{Tensor}. + + @tensor-method["clone"] + + Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. + + @;######################################################################### + @section{Arithmetic Operations} + + @function["add-tensors"] + + Adds two @pyret{Tensor}s element-wise, A + B. + + @function["strict-add-tensors"] + + @function["subtract-tensors"] + + @function["multiply-tensors"] + + @function["divide-tensors"] + + @function["floor-divide-tensors"] + + @function["tensor-max"] + + @function["tensor-min"] + + @function["tensor-modulo"] + + @function["tensor-expt"] + + @function["tensor-squared-difference"] + + @;######################################################################### + @section{Basic Math Operations} + + @function["tensor-abs"] + @function["tensor-acos"] + @function["tensor-acosh"] + @function["tensor-asin"] + @function["tensor-asinh"] + @function["tensor-atan"] + @function["tensor-atan2"] + @function["tensor-atanh"] + @function["tensor-ceil"] + @function["clip-by-value"] + @function["tensor-cos"] + @function["tensor-cosh"] + @function["exponential-linear-units"] + @function["elu"] + @function["erf"] + @function["gauss-error"] + @function["tensor-exp"] + @function["tensor-exp-min1"] + @function["tensor-floor"] + @function["leaky-relu"] + @function["tensor-log"] + @function["tensor-log-plus1"] + @function["log-sigmoid"] + @function["tensor-negate"] + @function["parametric-relu"] + @function["tensor-reciprocal"] + @function["relu"] + @function["tensor-round"] + @function["reciprocal-sqrt"] + @function["scaled-elu"] + @function["sigmoid"] + @function["signed-ones"] + @function["tensor-sin"] + @function["softplus"] + @function["tensor-sqrt"] + @function["tensor-square"] + @function["step"] + @function["tensor-tan"] + @function["tensor-tanh"] + + @;######################################################################### + @section{Reduction Operations} + + @function["all"] + @function["arg-max"] + @function["arg-min"] + @function["log-sum-exp"] + @function["reduce-max"] + @function["mean"] + @function["reduce-min"] + @function["reduce-sum"] + + @;######################################################################### + @section{Models} + + @type-spec["Model"]{ + Models are one of the primary abstractions used in TensorFlow. Models can be + trained, evaluated, and used for prediction. + + Models represent a collection of @pyret{Layers}. + } + + @;######################################################################### + @section{Layers} + + @type-spec["Layer"]{ + Layers are the primary building block for constructing a @pyret{Model}. Each + layer will typically perform some computation to transform its input to its + output. + + Layers will automatically take care of creating and initializing the various + internal variables/weights they need to function. + } + + @;######################################################################### + @section{Optimizers} + + @type-spec["Optimizer"]{ + Layers are the primary building block for constructing a @pyret{Model}. Each + layer will typically perform some computation to transform its input to its + output. + + Layers will automatically take care of creating and initializing the various + internal variables/weights they need to function. + } + + @examples{ + import tensorflow as TF + import chart as C + import image as I + import lists as L + + type Tensor = TF.Tensor + type Optimizer = TF.Optimizer + type ChartWindow = C.ChartWindow + type Image = I.Image + + # Create a tiny helper function: + fun positive-rand() -> Number: + doc: "Generates a positive Number between 0 and 1" + num-random(10000000) / 1000000 + end + + # `train-x` and `train-y` represent random points in a dataset, plotted + # on `scatter-plot`: + train-x = [list: + 3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, + 10.791, 5.313, 7.997, 5.654, 9.27, 3.1] + + train-y = [list: + 1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, + 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3] + + scatter-plot = C.from-list.scatter-plot(train-x, train-y) + + # Create two scalar Tensors `m` and `b` that are variables: + m = TF.make-scalar(positive-rand()).to-variable() + b = TF.make-scalar(positive-rand()).to-variable() + + # Setup a few helper functions before training: + fun predict(x :: Tensor) -> Tensor: + doc: ```Uses the current values of m and b to predict what Y-values will + be generated given a Tensor `x` representing X-values``` + + temp = TF.multiply-tensors(m, x) + TF.add-tensors(temp, b) + end + + fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: + doc: ```Used to calculate a measure of difference between the predicted + Y-values and the actual Y-values``` + + TF.subtract-tensors(prediction, actual-values) + ^ TF.tensor-square(_) + ^ TF.mean(_) + end + + # Train the model by creating an Optimizer. The optimizer will change any + # variable tensors used in the function passed into it in an attempt to + # minimize the returned loss: + fun train(): + doc: "Trains the model" + learning-rate = 0.005 + optimizer = TF.train-sgd(learning-rate) + + optimizer.minimize(lam() block: + prediction = predict(TF.list-to-tensor(train-x).as-1d()) + step-loss = loss(prediction, TF.list-to-tensor(train-y).as-1d()) + step-loss + end, empty) + end + + fun plot() -> ChartWindow: + doc: "Plots the current mx + b function and overlays it on the scatter plot" + shadow m = m.data-sync().first + shadow b = b.data-sync().first + + function-plot = C.from-list.function-plot(lam(x): (m * x) + b end) + C.render-charts([list: scatter-plot, function-plot]) + end + + fun train-steps(steps :: Number) -> Image block: + doc: "Trains the model `steps` times" + for L.each(_ from L.range(0, steps)) block: + train() + print("y = " + num-to-string(m.data-sync().first) + "x + " + num-to-string(b.data-sync().first)) + end + plot().get-image() + end + } +} From 8f119e12942e68e918e376d1476a533fa7b965b1 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 13:09:24 -0400 Subject: [PATCH 02/27] Add arithmetic operations examples and documentation --- src/trove/tensorflow.scrbl | 162 ++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 18b361a..1bdea41 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -206,6 +206,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-subtract-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "multiply-tensors") (arity 2) @@ -213,6 +220,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-multiply-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "divide-tensors") (arity 2) @@ -220,6 +234,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-divide-tensors") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "floor-divide-tensors") (arity 2) @@ -234,6 +255,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-tensor-max") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "tensor-min") (arity 2) @@ -241,6 +269,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-tensor-min") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "tensor-modulo") (arity 2) @@ -248,15 +283,36 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-tensor-modulo") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec (name "tensor-expt") (arity 2) + (args ("base" "exponent")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "strict-tensor-expt") + (arity 2) + (args ("a" "b")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (fun-spec + (name "squared-difference") + (arity 2) (args ("a" "b")) (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor ,Tensor))) (fun-spec - (name "tensor-squared-difference") + (name "strict-squared-difference") (arity 2) (args ("a" "b")) (return ,Tensor) @@ -820,25 +876,125 @@ Adds two @pyret{Tensor}s element-wise, A + B. - @function["strict-add-tensors"] + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-add-tensors"]. @function["subtract-tensors"] + Subtracts two @pyret{Tensor}s element-wise, A – B. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-subtract-tensors"]. + @function["multiply-tensors"] + Multiplies two @pyret{Tensor}s element-wise, A * B. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-multiply-tensors"]. + @function["divide-tensors"] + Divides two @pyret{Tensor}s element-wise, A / B. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-divide-tensors"]. + @function["floor-divide-tensors"] + Divides two @pyret{Tensor}s element-wise, A / B, with the result rounded + with the floor function. + @function["tensor-max"] + Returns a @pyret{Tensor} containing the maximum of @pyret{a} and @pyret{b}, + element-wise. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-tensor-max"]. + @function["tensor-min"] + Returns a @pyret{Tensor} containing the minimum of @pyret{a} and @pyret{b}, + element-wise. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-tensor-min"]. + @function["tensor-modulo"] + Computes the modulo of @pyret{a} and @pyret{b}, element-wise. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-tensor-modulo"]. + @function["tensor-expt"] - @function["tensor-squared-difference"] + Computes the power of @pyret{base} to @pyret{exponent}, element-wise. + + To ensure that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-tensor-expt"]. + + @function["squared-difference"] + + Computes @pyret{(a - b) * (a - b)}, element-wise. + + To assert that @pyret{a} and @pyret{b} are the same shape, use + @pyret-id["strict-squared-difference"]. + + @function["strict-add-tensors"] + + Same as @pyret-id["add-tensors"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-subtract-tensors"] + + Same as @pyret-id["subtract-tensors"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-multiply-tensors"] + + Same as @pyret-id["multiply-tensors"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-divide-tensors"] + + Same as @pyret-id["divide-tensors"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-tensor-max"] + + Same as @pyret-id["tensor-max"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-tensor-min"] + + Same as @pyret-id["tensor-min"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-tensor-expt"] + + Same as @pyret-id["tensor-expt"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-tensor-modulo"] + + Same as @pyret-id["tensor-modulo"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). + + @function["strict-squared-difference"] + + Same as @pyret-id["squared-difference"], but raises an error if @pyret{a} and + @pyret{b} are not the same shape (as determined by + @pyret-method["Tensor" "shape"]). @;######################################################################### @section{Basic Math Operations} From c36a503eca85836b28d4cac3fb916130a3d27f74 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 15:47:06 -0400 Subject: [PATCH 03/27] Add more tensorflow documentation --- src/trove/tensorflow.scrbl | 208 +++++++++++++++++++++++++++++++++++-- 1 file changed, 200 insertions(+), 8 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 1bdea41..8113519 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -115,7 +115,7 @@ (name "as-type") (arity 2) (params ()) - (args ("self" "dataType")) + (args ("self" "data-type")) (return ,Tensor) (contract (a-arrow ,Tensor ,S ,Tensor))) @@ -163,7 +163,7 @@ (name "reshape") (arity 2) (params ()) - (args ("self" "newShape")) + (args ("self" "new-shape")) (return ,Tensor) (contract (a-arrow ,Tensor ,(L-of N) ,Tensor))) @@ -489,11 +489,11 @@ (a-arrow ,Tensor ,Tensor))) (fun-spec (name "parametric-relu") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "alpha")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,N ,Tensor))) (fun-spec (name "tensor-reciprocal") (arity 1) @@ -682,6 +682,13 @@ @pyret-method["Tensor" "data-sync"] always returns a @pyret{List}. + Since @pyret{Tensor}s are immutable, all operations always return new + @pyret{Tensor}s and never modify the input @pyret{Tensor}s. The exception + to this is when a @pyret{Tensor} is transformed into a mutable + @pyret{Tensor} using the @pyret-id["make-variable"] function or the + @pyret-method["Tensor" "to-variable"] method. These "variable tensors" + can be modified by @pyret{Optimizer}s. + } @;######################################################################### @@ -810,27 +817,55 @@ Constructs a new, rank-1 @pyret{Tensor} from the values of the original @pyret{Tensor}. + The same functionality can be achieved with @pyret-method["Tensor" "reshape"], + but it's recommended to use @pyret-method["Tensor" "as-1d"] as it makes the + code more readable. + @tensor-method["as-2d"] Constructs a new, rank-2 @pyret{Tensor} with the input dimensions from the values of the original @pyret{Tensor}. + The number of elements implied by the input dimensions must be the same as the + number of elements in the calling @pyret{Tensor}. Otherwise, the method + raises an error. + + The same functionality can be achieved with @pyret-method["Tensor" "reshape"], + but it's recommended to use @pyret-method["Tensor" "as-2d"] as it makes the + code more readable. + @tensor-method["as-3d"] Constructs a new, rank-3 @pyret{Tensor} with the input dimensions from the values of the original @pyret{Tensor}. + The number of elements implied by the input dimensions must be the same as the + number of elements in the calling @pyret{Tensor}. Otherwise, the method + raises an error. + + The same functionality can be achieved with @pyret-method["Tensor" "reshape"], + but it's recommended to use @pyret-method["Tensor" "as-3d"] as it makes the + code more readable. + @tensor-method["as-4d"] Constructs a new, rank-4 @pyret{Tensor} with the input dimensions from the values of the original @pyret{Tensor}. + The number of elements implied by the input dimensions must be the same as the + number of elements in the calling @pyret{Tensor}. Otherwise, the method + raises an error. + + The same functionality can be achieved with @pyret-method["Tensor" "reshape"], + but it's recommended to use @pyret-method["Tensor" "as-4d"] as it makes the + code more readable. + @tensor-method["as-type"] Constructs a new @pyret{Tensor} from the values of the original @pyret{Tensor} with all of the values cast to the input datatype. - The possible @pyret{dataType}s are @pyret{"float32"}, @pyret{"int32"}, or + The possible @pyret{data-type}s are @pyret{"float32"}, @pyret{"int32"}, or @pyret{"bool"}. Any other @pyret{dataType} will raise an error. @tensor-method["data-sync"] @@ -862,9 +897,18 @@ @tensor-method["reshape"] - Constructs a new @pyret{Tensor} with the input dimensions @pyret{newShape} + Constructs a new @pyret{Tensor} with the input dimensions @pyret{new-shape} from the values of the original @pyret{Tensor}. + The number of elements implied by @pyret{new-shape} must be the same as the + number of elements in the calling @pyret{Tensor}. Otherwise, the method + raises an error. + + When reshaping a @pyret{Tensor} to be 0-, 1-, 2-, 3-, or 4-dimensional, + use @pyret-method["Tensor" "as-scalar"], @pyret-method["Tensor" "as-1d"], + @pyret-method["Tensor" "as-2d"], @pyret-method["Tensor" "as-3d"], or + @pyret-method["Tensor" "as-4d"] for readability. + @tensor-method["clone"] Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. @@ -1000,45 +1044,193 @@ @section{Basic Math Operations} @function["tensor-abs"] + + Computes the absolute value of the @pyret{Tensor}, element-wise. + @function["tensor-acos"] + + Computes the inverse cosine of the @pyret{Tensor}, element-wise. + @function["tensor-acosh"] + + Computes the inverse hyperbolic cosine of the @pyret{Tensor}, element-wise. + @function["tensor-asin"] + + Computes the inverse sine of the @pyret{Tensor}, element-wise. + @function["tensor-asinh"] + + Computes the inverse hyperbolic sine of the @pyret{Tensor}, element-wise. + @function["tensor-atan"] + + Computes the inverse tangent of the @pyret{Tensor}, element-wise. + @function["tensor-atan2"] + + Computes the @link["https://en.wikipedia.org/wiki/Atan2" + "four-quadrant inverse tangent"] of @pyret{a} and @pyret{b}, element-wise. + @function["tensor-atanh"] + + Computes the inverse hyperbolic tangent of the @pyret{Tensor}, element-wise. + @function["tensor-ceil"] + + Computes the ceiling of the @pyret{Tensor}, element-wise. + @function["clip-by-value"] + + Clips the values of the @pyret{Tensor}, element-wise, such that every element + in the resulting @pyret{Tensor} is at least @pyret{min-value} and is at most + @pyret{max-value}. + @function["tensor-cos"] + + Computes the cosine of the @pyret{Tensor}, element-wise. + @function["tensor-cosh"] + + Computes the hyperbolic cosine of the @pyret{Tensor}, element-wise. + @function["exponential-linear-units"] + + Applies the @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#ELUs" + "exponential linear units"] function to the @pyret{Tensor}, element-wise. + @function["elu"] - @function["erf"] + + Alias for @pyret-id["exponential-linear-units"]. + @function["gauss-error"] + + Applies the @link["http://mathworld.wolfram.com/Erf.html" "gauss error function"] + to the @pyret{Tensor}, element-wise. + + @function["erf"] + + Alias for @pyret-id["gauss-error"]. + @function["tensor-exp"] + + Computes the equivalent of @pyret{num-exp(tensor)}, element-wise. + @function["tensor-exp-min1"] + + Computes the equivalent of @pyret{num-exp(tensor - 1)}, element-wise. + @function["tensor-floor"] + + Computes the floor of the @pyret{Tensor}, element-wise. + @function["leaky-relu"] + + Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" + "leaky rectified linear units"] function to the @pyret{Tensor}, element-wise. + + @pyret{alpha} is the scaling factor for negative values. The default in + TensorFlow.js is @pyret{0.2}, but the argument has been exposed here for more + flexibility. + @function["tensor-log"] + + Computes the natural logarithm of the @pyret{Tensor}, element-wise; that is, + it computes the equivalent of @pyret{num-log(tensor)}. + @function["tensor-log-plus1"] + + Computes the natural logarithm of the @pyret{Tensor} plus 1, element-wise; + that is, it computes the equivalent of @pyret{num-log(tensor + 1)}. + @function["log-sigmoid"] + + Applies the @link["https://en.wikibooks.org/wiki/Artificial_Neural_Networks/ + Activation_Functions#Continuous_Log-Sigmoid_Function" "log sigmoid"] function + to the @pyret{Tensor}, element-wise. + @function["tensor-negate"] + + Multiplies each element in the @pyret{Tensor} by @pyret{-1}. + @function["parametric-relu"] + + Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" + "leaky rectified linear units"] function to the @pyret{Tensor}, element-wise, + using parametric alphas. + + @pyret{alpha} is the scaling factor for negative values. + @function["tensor-reciprocal"] + + Computes the reciprocal of the @pyret{Tensor}, element-wise; that is, it + computes the equivalent of @pyret{1 / tensor}. + @function["relu"] + + Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)" + "rectified linear units"] function to the @pyret{Tensor}, element-wise. + @function["tensor-round"] + + Computes the equivalent of @pyret{num-round(tensor)}, element-wise. + @function["reciprocal-sqrt"] + + Computes the recriprocal of the square root of the @pyret{Tensor}, + element-wise. + + The resulting @pyret{Tensor} is roughly equivalent to + @pyret{tensor-reciprocal(tensor-sqrt(tensor))}. + @function["scaled-elu"] + + Applies a scaled, exponential linear units function to the @pyret{Tensor}, + element-wise. + @function["sigmoid"] + + Applies the sigmoid function to the @pyret{Tensor}, element-wise. + @function["signed-ones"] + + Returns an element-wise indication of the sign of each number in the + @pyret{Tensor}; that is, every value in the original tensor is represented + in the resulting tensor as @pyret{~+1} if the value is positive, @pyret{~-1} + if the value was negative, or @pyret{~0} if the value was zero or not a + number. + @function["tensor-sin"] + + Computes the sine of the @pyret{Tensor}, element-wise. + @function["softplus"] + + Applies the softplus function to the @pyret{Tensor}, element-wise. + @function["tensor-sqrt"] + + Computes the square root of the @pyret{Tensor}, element-wise. + @function["tensor-square"] + + Computes the square of the @pyret{Tensor}, element-wise. + @function["step"] + + Returns an element-wise indication of the sign of each number in the + @pyret{Tensor}; that is, every value in the original tensor is represented + in the resulting tensor as @pyret{~+1} if the value is positive; otherwise, + it is represented as @pyret{~0}. + @function["tensor-tan"] + + Computes the tangent of the @pyret{Tensor}, element-wise. + @function["tensor-tanh"] + Computes the hyperbolic tangent of the @pyret{Tensor}, element-wise. + @;######################################################################### @section{Reduction Operations} From 1e0be4baae73dd7ee5f35e775b4d34f5f990ca84 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 20:57:39 -0400 Subject: [PATCH 04/27] Add external links to machine learning function descriptions --- src/trove/tensorflow.scrbl | 170 +++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 7 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 8113519..108c872 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -3,7 +3,9 @@ @(define Tensor (a-id "Tensor" (xref "tensorflow" "Tensor"))) @(define Model (a-id "Model" (xref "tensorflow" "Model"))) +@(define Sequential (a-id "Sequential" (xref "tensorflow" "Sequential"))) @(define Layer (a-id "Layer" (xref "tensorflow" "Layer"))) +@(define Optimizer (a-id "Optimizer" (xref "tensorflow" "Optimizer"))) @(define (tensor-method name) (method-doc "Tensor" #f name #:alt-docstrings "")) @@ -13,6 +15,13 @@ (path "src/arr/trove/tensorflow.arr") (fun-spec (name "tensor")) + (fun-spec + (name "is-tensor") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) (fun-spec (name "list-to-tensor") (arity 1) @@ -649,6 +658,126 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor))) + + (fun-spec + (name "is-model") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) + (fun-spec + (name "make-model") + (arity 1) + (args ("values")) + (return ,Model) + (contract + (a-arrow ,(L-of N) ,Model))) + + (data-spec + (name "Model") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,N))) + ))) + + (fun-spec + (name "is-sequential") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) + (fun-spec + (name "make-sequential") + (arity 1) + (args ("values")) + (return ,Sequential) + (contract + (a-arrow ,(L-of N) ,Sequential))) + + (data-spec + (name "Sequential") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,N))) + ))) + + (fun-spec + (name "is-dense-layer") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) + (fun-spec + (name "make-dense-layer") + (arity 1) + (args ("values")) + (return ,Layer) + (contract + (a-arrow ,(L-of N) ,Layer))) + + (data-spec + (name "Layer") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,N))) + ))) + + (fun-spec + (name "is-optimizer") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) + (fun-spec + (name "train-sgd") + (arity 1) + (args ("values")) + (return ,Optimizer) + (contract + (a-arrow ,(L-of N) ,Optimizer))) + + (data-spec + (name "Optimizer") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,Tensor ,N))) + ))) )) @docmodule["tensorflow"]{ @@ -905,9 +1034,10 @@ raises an error. When reshaping a @pyret{Tensor} to be 0-, 1-, 2-, 3-, or 4-dimensional, - use @pyret-method["Tensor" "as-scalar"], @pyret-method["Tensor" "as-1d"], - @pyret-method["Tensor" "as-2d"], @pyret-method["Tensor" "as-3d"], or - @pyret-method["Tensor" "as-4d"] for readability. + it's recommended to use @pyret-method["Tensor" "as-scalar"], + @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], + @pyret-method["Tensor" "as-3d"], or @pyret-method["Tensor" "as-4d"] as + they make the code more readable. @tensor-method["clone"] @@ -1206,7 +1336,8 @@ @function["softplus"] - Applies the softplus function to the @pyret{Tensor}, element-wise. + Applies the @link["https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/" + "softplus"] function to the @pyret{Tensor}, element-wise. @function["tensor-sqrt"] @@ -1246,35 +1377,60 @@ @;######################################################################### @section{Models} + @pyret{Model}s represent a collection of @pyret{Layers}, and define a series + of inputs and outputs. They are one of the primary abstractions used in + TensorFlow, and can be trained, evaluated, and used for prediction. + + There are two types of models in TensorFlow: @pyret{Sequential}, where + the outputs of one @pyret{Layer} are the inputs to the next @pyret{Layer}, + and @pyret{Model}, which is more generic and supports arbitrary, non-cyclic + graphs of @pyret{Layer}s. + @type-spec["Model"]{ - Models are one of the primary abstractions used in TensorFlow. Models can be - trained, evaluated, and used for prediction. - Models represent a collection of @pyret{Layers}. + A @pyret{Model} is a data structure that consists of @pyret{Layer}s and + defines inputs and outputs. It is more generic than @pyret{Sequential} + models as it supports arbitrary, non-cyclic graphs of @pyret{Layer}s. + + } + + @type-spec["Sequential"]{ + + A @pyret{Sequential} model is a model where the outputs of one + @pyret{Layer} are the inputs to the next @pyret{Layer}. That is, the model + topology is a simple "stack" of layers, with no branching or skipping. + + As a result, the first layer passed to a @pyret{Sequential} model must + have a defined input shape. + } @;######################################################################### @section{Layers} @type-spec["Layer"]{ + Layers are the primary building block for constructing a @pyret{Model}. Each layer will typically perform some computation to transform its input to its output. Layers will automatically take care of creating and initializing the various internal variables/weights they need to function. + } @;######################################################################### @section{Optimizers} @type-spec["Optimizer"]{ + Layers are the primary building block for constructing a @pyret{Model}. Each layer will typically perform some computation to transform its input to its output. Layers will automatically take care of creating and initializing the various internal variables/weights they need to function. + } @examples{ From 2d2e7c849aff2b911df51209a43ba4e43e06fd1d Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 22:09:35 -0400 Subject: [PATCH 05/27] Add more documentation to tensorflow reduction functions --- src/trove/tensorflow.scrbl | 62 ++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 108c872..d5f9e21 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -609,6 +609,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "any") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) (fun-spec (name "arg-max") (arity 1) @@ -638,7 +645,7 @@ (contract (a-arrow ,Tensor ,Tensor))) (fun-spec - (name "mean") + (name "reduce-mean") (arity 1) (args ("tensor")) (return ,Tensor) @@ -1349,10 +1356,10 @@ @function["step"] - Returns an element-wise indication of the sign of each number in the - @pyret{Tensor}; that is, every value in the original tensor is represented - in the resulting tensor as @pyret{~+1} if the value is positive; otherwise, - it is represented as @pyret{~0}. + Applies the unit step function to the @pyret{Tensor}, element-wise; that is, + that is, every value in the original tensor is represented + in the resulting tensor as @pyret{~0} if the value is negative; otherwise, + it is represented as @pyret{~+1}. @function["tensor-tan"] @@ -1366,14 +1373,57 @@ @section{Reduction Operations} @function["all"] + + Reduces the input @pyret{Tensor} across all dimensions by computing the + logical "and" of its elements. + + @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises + an error. + + @function["any"] + + Reduces the input @pyret{Tensor} across all dimensions by computing the + logical "or" of its elements. + + @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises + an error. + @function["arg-max"] + + Returns a new @pyret{Tensor} where each element is the index of the maximum + values along the outermost dimension of @pyret{tensor}. + @function["arg-min"] + + Returns a new @pyret{Tensor} where each element is the index of the minimum + values along the outermost dimension of @pyret{tensor}. + @function["log-sum-exp"] + + Computes the @pyret{log(sum(exp(elements along the outermost dimension))}. + + Reduces the input along the outermost dimension. + @function["reduce-max"] - @function["mean"] + + Returns a @pyret{Tensor} containing the maximum value of all entries in + the input @pyret{tensor}. + @function["reduce-min"] + + Returns a @pyret{Tensor} containing the minimum value of all entries in + the input @pyret{tensor}. + + @function["reduce-mean"] + + Returns a @pyret{Tensor} containing the mean value of all entries in + the input @pyret{tensor}. + @function["reduce-sum"] + Returns a @pyret{Tensor} containing the sum of all entries in the input + @pyret{tensor}. + @;######################################################################### @section{Models} From 2a985a3e16ee24b59cf571458abac7f68e581416 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Wed, 11 Jul 2018 23:38:17 -0400 Subject: [PATCH 06/27] Rewrite some reducer docs for clarity --- src/trove/tensorflow.scrbl | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index d5f9e21..d7d243f 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -69,7 +69,7 @@ (arity 1) (params ()) (args ("self")) - (return ,N) + (return ,(L-of N)) (contract (a-arrow ,Tensor ,(L-of N)))) (method-spec @@ -846,6 +846,11 @@ [tensor: 9, 4, 0, -32, 23, 1, 3, 2].as-3d(2, 2, 2) # a 2 x 2 x 2 tensor } + @function["is-tensor"] + + Returns @pyret{true} if @pyret{val} is a @pyret{Tensor}; otherwise, returns + @pyret{false}. + @function["list-to-tensor"] Creates a new @pyret{Tensor} with the values in the input @pyret{List}. @@ -1406,23 +1411,23 @@ @function["reduce-max"] - Returns a @pyret{Tensor} containing the maximum value of all entries in - the input @pyret{tensor}. + Returns a @pyret{Tensor} containing a single value that is the maximum value + of all entries in the input @pyret{tensor}. @function["reduce-min"] - Returns a @pyret{Tensor} containing the minimum value of all entries in - the input @pyret{tensor}. + Returns a @pyret{Tensor} containing a single value that is the minimum value + of all entries in the input @pyret{tensor}. @function["reduce-mean"] - Returns a @pyret{Tensor} containing the mean value of all entries in - the input @pyret{tensor}. + Returns a @pyret{Tensor} containing a single value that is the mean value + of all entries in the input @pyret{tensor}. @function["reduce-sum"] - Returns a @pyret{Tensor} containing the sum of all entries in the input - @pyret{tensor}. + Returns a @pyret{Tensor} containing a single value that is the sum + of all entries in the input @pyret{tensor}. @;######################################################################### @section{Models} From c28fa3f8879b8f7b821c9235e478f173e55f522f Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 12 Jul 2018 10:21:03 -0400 Subject: [PATCH 07/27] Add more testing examples to tensorflow methods --- src/trove/tensorflow.scrbl | 80 +++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index d7d243f..503a9d6 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -943,12 +943,12 @@ @examples{ check: - one-dim = [TF.tensor: 1] - one-dim.as-scalar().shape() is empty - one-dim.shape() is [list: 1] # doesn't modify shape of original tensor + size-one = [TF.tensor: 1] + size-one.as-scalar().shape() is empty + size-one.shape() is [list: 1] # doesn't modify shape of original tensor - two-dim = [TF.tensor: 1, 2] - two-dim.as-scalar() raises + size-two = [TF.tensor: 1, 2] + size-two.as-scalar() raises "Tensor was size-2 but `as-scalar` requires the tensor to be size-1" end } @@ -962,6 +962,23 @@ but it's recommended to use @pyret-method["Tensor" "as-1d"] as it makes the code more readable. + @examples{ + check: + one-dim = [TF.tensor: 1] + two-dim = [TF.tensor: 4, 3, 2, 1].as-2d(2, 2) + three-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as-3d(3, 1, 3) + + one-dim.shape() is [list: 1] + one-dim.as-1d().shape() is [list: 1] + + two-dim.shape() is [list: 2, 2] + two-dim.as-1d().shape() is [list: 4] + + three-dim.shape() is [list: 3, 1, 3] + three-dim.as-1d().shape() is [list: 9] + end + } + @tensor-method["as-2d"] Constructs a new, rank-2 @pyret{Tensor} with the input dimensions from the @@ -975,6 +992,27 @@ but it's recommended to use @pyret-method["Tensor" "as-2d"] as it makes the code more readable. + @examples{ + check: + one-dim = [TF.tensor: 1] + two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5].as-2d(3, 2) + three-dim = [TF.tensor: 4, 3, 2, 1, 0, -1, -2, -3].as-3d(2, 2, 2) + + one-dim.shape() is [list: 1] + one-dim.as-2d(1, 1).shape() is [list: 1, 1] + + two-dim.shape() is [list: 3, 2] + two-dim.as-2d(2, 3).shape() is [list: 2, 3] + + three-dim.shape() is [list: 2, 2, 2] + three-dim.as-2d(4, 2).shape() is [list: 4, 2] + + one-dim.as-2d(2, 1) raises "Cannot reshape" + two-dim.as-2d(3, 3) raises "Cannot reshape" + three-dim.as-2d(5, 4) raises "Cannot reshape" + end + } + @tensor-method["as-3d"] Constructs a new, rank-3 @pyret{Tensor} with the input dimensions from the @@ -988,6 +1026,22 @@ but it's recommended to use @pyret-method["Tensor" "as-3d"] as it makes the code more readable. + @examples{ + check: + one-dim = [TF.tensor: 1] + two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) + + one-dim.shape() is [list: 1] + one-dim.as-3d(1, 1, 1).shape() is [list: 1, 1, 1] + + two-dim.shape() is [list: 4, 2] + two-dim.as-3d(2, 2, 2).shape() is [list: 2, 2, 2] + + one-dim.as-3d(2, 1, 1) raises "Cannot reshape" + two-dim.as-3d(4, 3, 2) raises "Cannot reshape" + end + } + @tensor-method["as-4d"] Constructs a new, rank-4 @pyret{Tensor} with the input dimensions from the @@ -1001,6 +1055,22 @@ but it's recommended to use @pyret-method["Tensor" "as-4d"] as it makes the code more readable. + @examples{ + check: + one-dim = [TF.tensor: 1] + two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) + + one-dim.shape() is [list: 1] + one-dim.as-4d(1, 1, 1, 1).shape() is [list: 1, 1, 1, 1] + + two-dim.shape() is [list: 4, 2] + two-dim.as-4d(2, 2, 1, 2).shape() is [list: 2, 2, 1, 2] + + one-dim.as-4d(2, 1, 1, 1) raises "Cannot reshape" + two-dim.as-4d(2, 2, 2, 2) raises "Cannot reshape" + end + } + @tensor-method["as-type"] Constructs a new @pyret{Tensor} from the values of the original From 3f0022e335aafea13114654fb35ee60769205cf7 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 12 Jul 2018 15:22:06 -0400 Subject: [PATCH 08/27] Add more docs to tensorflow --- src/trove/tensorflow.scrbl | 254 +++++++++++++++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 13 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 503a9d6..f03716c 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -7,8 +7,19 @@ @(define Layer (a-id "Layer" (xref "tensorflow" "Layer"))) @(define Optimizer (a-id "Optimizer" (xref "tensorflow" "Optimizer"))) +@(define Object (a-id "Object" (xref "" "Object"))) +@(define Nothing (a-id "Nothing" (xref "" "Nothing"))) + @(define (tensor-method name) (method-doc "Tensor" #f name #:alt-docstrings "")) +@(define (model-method name) + (method-doc "Model" #f name #:alt-docstrings "")) +@(define (sequential-method name) + (method-doc "Sequential" #f name #:alt-docstrings "")) +@(define (layer-method name) + (method-doc "Layer" #f name #:alt-docstrings "")) +@(define (optimizer-method name) + (method-doc "Optimizer" #f name #:alt-docstrings "")) @(append-gen-docs `(module "tensorflow" @@ -717,13 +728,53 @@ (variants) (shared ((method-spec - (name "size") - (arity 1) + (name "add") + (arity 2) (params ()) - (args ("self")) - (return ,N) + (args ("self" "layer")) + (return ,Nothing) (contract - (a-arrow ,Tensor ,N))) + (a-arrow ,Sequential ,Layer ,Nothing))) + (method-spec + (name "compile") + (arity 2) + (params ()) + (args ("self" "layer")) + (return ,Nothing) + (contract + (a-arrow ,Sequential ,Object ,Nothing))) + (method-spec + (name "evaluate") + (arity 4) + (params ()) + (args ("self" "x" "y" "config")) + (return ,Tensor) + (contract + (a-arrow ,Sequential ,Tensor ,Tensor ,Object ,Tensor))) + (method-spec + (name "predict") + (arity 3) + (params ()) + (args ("self" "x" "config")) + (return ,Tensor) + (contract + (a-arrow ,Sequential ,Tensor ,Object ,Tensor))) + (method-spec + (name "predict-on-batch") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Sequential ,Tensor ,Tensor))) + (method-spec + (name "fit") + (arity 5) + (params ()) + (args ("self" "x" "y" "config" "epoch-callback")) + (return ,Nothing) + (contract + (a-arrow ,Sequential ,Tensor ,Tensor ,Object (a-arrow ,N ,Object ,Nothing) ,Nothing))) ))) (fun-spec @@ -766,10 +817,55 @@ (fun-spec (name "train-sgd") (arity 1) - (args ("values")) + (args ("learning-rate")) + (return ,Optimizer) + (contract + (a-arrow ,N ,Optimizer))) + (fun-spec + (name "train-momentum") + (arity 2) + (args ("learning-rate" "momentum")) + (return ,Optimizer) + (contract + (a-arrow ,N ,N ,Optimizer))) + (fun-spec + (name "train-adagrad") + (arity 2) + (args ("learning-rate" "initial-accumulator")) + (return ,Optimizer) + (contract + (a-arrow + ,N + ,(O-of (a-id "NumPositive" (xref "numbers" "NumPositive"))) + ,Optimizer))) + (fun-spec + (name "train-adadelta") + (arity 3) + (args ("learning-rate" "rho" "epsilon")) (return ,Optimizer) (contract - (a-arrow ,(L-of N) ,Optimizer))) + (a-arrow ,(O-of N) ,(O-of N) ,(O-of N) ,Optimizer))) + (fun-spec + (name "train-adam") + (arity 4) + (args ("learning-rate" "beta-1" "beta-2" "epsilon")) + (return ,Optimizer) + (contract + (a-arrow ,(O-of N) ,(O-of N) ,(O-of N) ,(O-of N) ,Optimizer))) + (fun-spec + (name "train-adamax") + (arity 5) + (args ("learning-rate" "beta-1" "beta-2" "epsilon" "decay")) + (return ,Optimizer) + (contract + (a-arrow ,(O-of N) ,(O-of N) ,(O-of N) ,(O-of N) ,(O-of N) ,Optimizer))) + (fun-spec + (name "train-rmsprop") + (arity 5) + (args ("learning-rate" "decay" "momentum" "epsilon" "is-centered")) + (return ,Optimizer) + (contract + (a-arrow ,N ,(O-of N) ,(O-of N) ,(O-of N) ,B ,Optimizer))) (data-spec (name "Optimizer") @@ -1519,6 +1615,14 @@ } + @nested[#:style 'inset]{ + + @function["is-model"] + + @function["make-model"] + + } + @type-spec["Sequential"]{ A @pyret{Sequential} model is a model where the outputs of one @@ -1530,6 +1634,21 @@ } + @nested[#:style 'inset]{ + + @function["is-sequential"] + + @function["make-sequential"] + + @sequential-method["add"] + @sequential-method["compile"] + @sequential-method["evaluate"] + @sequential-method["predict"] + @sequential-method["predict-on-batch"] + @sequential-method["fit"] + + } + @;######################################################################### @section{Layers} @@ -1544,20 +1663,129 @@ } + @function["is-dense-layer"] + + @function["make-dense-layer"] + @;######################################################################### - @section{Optimizers} + @section{The Optimizer Datatype} @type-spec["Optimizer"]{ - Layers are the primary building block for constructing a @pyret{Model}. Each - layer will typically perform some computation to transform its input to its - output. + @pyret{Optimizer}s are used to perform training operations and compute + gradients. - Layers will automatically take care of creating and initializing the various - internal variables/weights they need to function. + @pyret{Optimizer}s eagerly compute gradients. This means that when a user + provides a function that is a combination of TensorFlow operations + to an @pyret{Optimizer}, the @pyret{Optimizer} automatically differentiates + that function's output with respect to its inputs. } + @function["is-optimizer"] + + Returns @pyret{true} if @pyret{val} is an @pyret{Optimizer}; otherwise, + returns @pyret{false}. + + @;######################################################################### + @section{Optimizer Constructors} + + There are many different types of @pyret{Optimizer}s that use different + formulas to compute gradients. + + @function["train-sgd"] + + Constructs an @pyret{Optimizer} that uses a stochastic gradient descent + algorithm, where @pyret{learning-rate} is the learning rate to use for the + algorithm. + + @function["train-momentum"] + + Constructs an @pyret{Optimizer} that uses a momentum gradient descent + algorithm, where @pyret{learning-rate} is the learning rate to use for the + algorithm and @pyret{momentum} is the momentum to use for the algorithm. + + See @link["http://proceedings.mlr.press/v28/sutskever13.pdf" + "http://proceedings.mlr.press/v28/sutskever13.pdf"]. + + @function["train-adagrad"] + + Constructs an @pyret{Optimizer} that uses the Adagrad algorithm, where + @pyret{learning-rate} is the learning rate to use for the Adagrad gradient + descent algorithm. + + If not @pyret{none}, @pyret{initial-accumulator} is the positive, starting + value for the accumulators in the Adagrad algorithm. If + @pyret{initial-accumulator} is specified but is not positive, the function + raises an error. + + See @link["http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf" + "http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf"] or + @link["http://ruder.io/optimizing-gradient-descent/index.html#adagrad" + "http://ruder.io/optimizing-gradient-descent/index.html#adagrad"]. + + @function["train-adadelta"] + + Constructs an @pyret{Optimizer} that uses the Adadelta algorithm. + + If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for + the Adamax gradient descent algorithm, @pyret{rho} is the learning rate + decay over each update, and @pyret{epsilon} is a constant used to better + condition the gradient updates. + + See @link["https://arxiv.org/abs/1212.5701" "https://arxiv.org/abs/1212.5701"]. + + @function["train-adam"] + + Constructs an @pyret{Optimizer} that uses the Adam algorithm. + + If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for + the Adamax gradient descent algorithm, @pyret{beta-1} is the exponential + decay rate for the first moment estimates, @pyret{beta-2} is the + exponential decay rate for the second moment estimates, and + @pyret{epsilon} is a small constant for numerical stability. + + See @link["https://arxiv.org/abs/1412.6980" "https://arxiv.org/abs/1412.6980"]. + + @function["train-adamax"] + + Constructs an @pyret{Optimizer} that uses the Adamax algorithm. + + If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for + the Adamax gradient descent algorithm, @pyret{beta-1} is the exponential + decay rate for the first moment estimates, @pyret{beta-2} is the + exponential decay rate for the second moment estimates, @pyret{epsilon} is + a small constant for numerical stability, and @pyret{decay} is the learning + rate decay over each update. + + See @link["https://arxiv.org/abs/1412.6980" "https://arxiv.org/abs/1412.6980"]. + + @function["train-rmsprop"] + + Constructs an @pyret{Optimizer} that uses RMSProp gradient descent, where + @pyret{learning-rate} is the learning rate to use for the RMSProp gradient + descent algorithm. + + If not @pyret{none}, @pyret{decay} represents the discounting factor for the + history/coming gradient, @pyret{momentum} represents the momentum to use for + the RMSProp gradient descent algorithm, and @pyret{epsilon} is a small value + to avoid division-by-zero errors. + + If @pyret{is-centered} is @pyret{true}, gradients are normalized by the + estimated varience of the gradient. + + See @link["http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf" + "these slides from the University of Toronto"] for a primer on RMSProp. + + @bold{Note:} This TensorFlow.js implementation uses plain momentum and is + not the "centered" version of RMSProp. + + @;######################################################################### + @section{Usage Examples} + + The below program demonstrates how to use @pyret{tensorflow} to perform + linear regression operations on a dataset. + @examples{ import tensorflow as TF import chart as C From c48dbd7eb510d40eaedabc0e4d2a332cbd205045 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 12 Jul 2018 16:29:02 -0400 Subject: [PATCH 09/27] Add restructure layer docs --- src/trove/tensorflow.scrbl | 128 +++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 12 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index f03716c..1f8a729 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -687,10 +687,10 @@ (fun-spec (name "make-model") (arity 1) - (args ("values")) + (args ("config")) (return ,Model) (contract - (a-arrow ,(L-of N) ,Model))) + (a-arrow ,Object ,Model))) (data-spec (name "Model") @@ -717,10 +717,10 @@ (fun-spec (name "make-sequential") (arity 1) - (args ("values")) + (args ("config")) (return ,Sequential) (contract - (a-arrow ,(L-of N) ,Sequential))) + (a-arrow ,Object ,Sequential))) (data-spec (name "Sequential") @@ -778,19 +778,110 @@ ))) (fun-spec - (name "is-dense-layer") + (name "is-layer") (arity 1) (args ("val")) (return ,B) (contract (a-arrow ,A ,B))) (fun-spec - (name "make-dense-layer") + (name "activation-layer") (arity 1) - (args ("values")) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "dense-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "dropout-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "embedding-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "flatten-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "repeat-vector-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "reshape-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "conv-1d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "conv-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "conv-2d-transpose-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "cropping-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "depthwise-conv-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "separable-conv-2d-layer") + (arity 1) + (args ("config")) (return ,Layer) (contract - (a-arrow ,(L-of N) ,Layer))) + (a-arrow ,Object ,Layer))) + (fun-spec + (name "up-sampling-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) (data-spec (name "Layer") @@ -1663,9 +1754,22 @@ } - @function["is-dense-layer"] - - @function["make-dense-layer"] + @function["is-layer"] + + @function["activation-layer"] + @function["dense-layer"] + @function["dropout-layer"] + @function["embedding-layer"] + @function["flatten-layer"] + @function["repeat-vector-layer"] + @function["reshape-layer"] + @function["conv-1d-layer"] + @function["conv-2d-layer"] + @function["conv-2d-transpose-layer"] + @function["cropping-2d-layer"] + @function["depthwise-conv-2d-layer"] + @function["separable-conv-2d-layer"] + @function["up-sampling-2d-layer"] @;######################################################################### @section{The Optimizer Datatype} @@ -1800,7 +1904,7 @@ # Create a tiny helper function: fun positive-rand() -> Number: doc: "Generates a positive Number between 0 and 1" - num-random(10000000) / 1000000 + num-random(10000000) / 10000000 end # `train-x` and `train-y` represent random points in a dataset, plotted From 7fa06e011cfa467328e47ec859de4f1ef5d5197d Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Fri, 13 Jul 2018 12:49:15 -0400 Subject: [PATCH 10/27] Add pooling, normalization, and merge layer documentation --- src/trove/tensorflow.scrbl | 141 ++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 1f8a729..1fae7cc 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -882,6 +882,111 @@ (return ,Layer) (contract (a-arrow ,Object ,Layer))) + (fun-spec + (name "add-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "average-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "concatenate-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "maximum-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "minimum-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "multiply-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "batch-normalization-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "average-pooling-1d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "average-pooling-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "global-average-pooling-1d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "global-average-pooling-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "global-max-pooling-1d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "global-max-pooling-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "max-pooling-1d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "max-pooling-2d-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) (data-spec (name "Layer") @@ -1741,7 +1846,7 @@ } @;######################################################################### - @section{Layers} + @section{The Layer Datatype} @type-spec["Layer"]{ @@ -1756,6 +1861,9 @@ @function["is-layer"] + @;######################################################################### + @section{Basic Layers} + @function["activation-layer"] @function["dense-layer"] @function["dropout-layer"] @@ -1763,6 +1871,10 @@ @function["flatten-layer"] @function["repeat-vector-layer"] @function["reshape-layer"] + + @;######################################################################### + @section{Convolutional Layers} + @function["conv-1d-layer"] @function["conv-2d-layer"] @function["conv-2d-transpose-layer"] @@ -1771,6 +1883,33 @@ @function["separable-conv-2d-layer"] @function["up-sampling-2d-layer"] + @;######################################################################### + @section{Convolutional Layers} + + @function["add-layer"] + @function["average-layer"] + @function["concatenate-layer"] + @function["maximum-layer"] + @function["minimum-layer"] + @function["multiply-layer"] + + @;######################################################################### + @section{Normalization Layers} + + @function["batch-normalization-layer"] + + @;######################################################################### + @section{Pooling Layers} + + @function["average-pooling-1d-layer"] + @function["average-pooling-2d-layer"] + @function["global-average-pooling-1d-layer"] + @function["global-average-pooling-2d-layer"] + @function["global-max-pooling-1d-layer"] + @function["global-max-pooling-2d-layer"] + @function["max-pooling-1d-layer"] + @function["max-pooling-2d-layer"] + @;######################################################################### @section{The Optimizer Datatype} From 347b0c0a264d2d1eb7a756d5ea56f32e5004344d Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Fri, 13 Jul 2018 14:23:59 -0400 Subject: [PATCH 11/27] Add stochastic gradient descent example for cubic functions --- src/trove/tensorflow.scrbl | 112 +++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 1fae7cc..4081215 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -49,11 +49,18 @@ (a-arrow ,N ,Tensor))) (fun-spec (name "random-normal") - (arity 1) - (args ("shape")) + (arity 3) + (args ("shape" "mean" "standard-deviation")) (return ,Tensor) (contract - (a-arrow ,(L-of N) ,Tensor))) + (a-arrow ,(L-of N) ,(O-of N) ,(O-of N) ,Tensor))) + (fun-spec + (name "random-uniform") + (arity 3) + (args ("shape" "min-val" "max-val")) + (return ,Tensor) + (contract + (a-arrow ,(L-of N) ,(O-of N) ,(O-of N) ,Tensor))) (fun-spec (name "make-variable") (arity 1) @@ -1169,6 +1176,12 @@ the input @pyret{List}) where all of the values are sampled from a normal distribution. + @function["random-uniform"] + + Creates a new @pyret{Tensor} with the given shape (represented as values in + the input @pyret{List}) where all of the values are sampled from a uniform + distribution. + @function["make-variable"] Creates a new, mutable @pyret{Tensor} initialized to the values of the input @@ -2077,7 +2090,98 @@ TF.subtract-tensors(prediction, actual-values) ^ TF.tensor-square(_) - ^ TF.mean(_) + ^ TF.reduce-mean(_) + end + + # Train the model by creating an Optimizer. The optimizer will change any + # variable tensors used in the function passed into it in an attempt to + # minimize the returned loss: + fun train(): + doc: "Trains the model" + learning-rate = 0.005 + optimizer = TF.train-sgd(learning-rate) + + optimizer.minimize(lam() block: + prediction = predict(TF.list-to-tensor(train-x).as-1d()) + step-loss = loss(prediction, TF.list-to-tensor(train-y).as-1d()) + step-loss + end, empty) + end + + fun plot() -> ChartWindow: + doc: "Plots the current mx + b function and overlays it on the scatter plot" + shadow m = m.data-sync().first + shadow b = b.data-sync().first + + function-plot = C.from-list.function-plot(lam(x): (m * x) + b end) + C.render-charts([list: scatter-plot, function-plot]) + end + + fun train-steps(steps :: Number) -> Image block: + doc: "Trains the model `steps` times" + for L.each(_ from L.range(0, steps)) block: + train() + print("y = " + num-to-string(m.data-sync().first) + "x + " + num-to-string(b.data-sync().first)) + end + plot().get-image() + end + } + + The below program demonstrates how to use @pyret{tensorflow} to fit a cubic + function to synthetic data using the stochastic gradient descent algorithm. + + Program based on @link["https://js.tensorflow.org/tutorials/fit-curve.html" + "https://js.tensorflow.org/tutorials/fit-curve.html"]. + + @examples{ + import tensorflow as TF + import chart as C + import image as I + import lists as L + + type Tensor = TF.Tensor + type Optimizer = TF.Optimizer + type ChartWindow = C.ChartWindow + type Image = I.Image + + # Create a tiny helper function: + fun positive-rand() -> Number: + doc: "Generates a positive Number between 0 and 1" + num-random(10000000) / 10000000 + end + + # `train-x` and `train-y` represent random points in a dataset, plotted + # on `scatter-plot`: + train-x = [list: + 3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, + 10.791, 5.313, 7.997, 5.654, 9.27, 3.1] + + train-y = [list: + 1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, + 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3] + + scatter-plot = C.from-list.scatter-plot(train-x, train-y) + + # Create two scalar Tensors `m` and `b` that are variables: + m = TF.make-scalar(positive-rand()).to-variable() + b = TF.make-scalar(positive-rand()).to-variable() + + # Setup a few helper functions before training: + fun predict(x :: Tensor) -> Tensor: + doc: ```Uses the current values of m and b to predict what Y-values will + be generated given a Tensor `x` representing X-values``` + + temp = TF.multiply-tensors(m, x) + TF.add-tensors(temp, b) + end + + fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: + doc: ```Used to calculate a measure of difference between the predicted + Y-values and the actual Y-values``` + + TF.subtract-tensors(prediction, actual-values) + ^ TF.tensor-square(_) + ^ TF.reduce-mean(_) end # Train the model by creating an Optimizer. The optimizer will change any From 87a085bce45f19acfd5d9d04d388806276a97041 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Mon, 16 Jul 2018 12:35:55 -0400 Subject: [PATCH 12/27] Add chaining arithmetic operations to Tensors and minimize documentation for Optimizers --- src/trove/tensorflow.scrbl | 297 ++++++++++++++++++++++++++++--------- 1 file changed, 231 insertions(+), 66 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 4081215..4602f83 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -203,15 +203,86 @@ (contract (a-arrow ,Tensor ,Tensor))) (method-spec - (name "placeholder") + (name "add") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "subtract") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "multiply") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "divide") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "floor-divide") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "max") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "min") (arity 2) (params ()) - (args ("self" "width")) - (return ,(L-of S)) + (args ("self" "x")) + (return ,Tensor) (contract - (a-arrow ,Tensor ,N ,(L-of S)))) + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "modulo") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "expt") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) + (method-spec + (name "squared-difference") + (arity 2) + (params ()) + (args ("self" "x")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,Tensor))) ))) - (fun-spec (name "add-tensors") (arity 2) @@ -1076,13 +1147,13 @@ (variants) (shared ((method-spec - (name "size") - (arity 1) + (name "minimize") + (arity 3) (params ()) - (args ("self")) - (return ,N) + (args ("self" "f", "variables")) + (return ,Tensor) (contract - (a-arrow ,Tensor ,N))) + (a-arrow ,Optimizer (a-arrow "" ,Tensor) ,(L-of Tensor) ,Tensor))) ))) )) @@ -1173,8 +1244,13 @@ @function["random-normal"] Creates a new @pyret{Tensor} with the given shape (represented as values in - the input @pyret{List}) where all of the values are sampled from a normal - distribution. + the input @pyret{List shape}) where all of the values are sampled + from a normal distribution. + + @pyret{mean} is the mean of the normal distribution and + @pyret{standard-deviation} is the standard deviation of the normal + distribution. If @pyret{none}, the respective parameters are set to the + TensorFlow.js defaults. @function["random-uniform"] @@ -1182,6 +1258,11 @@ the input @pyret{List}) where all of the values are sampled from a uniform distribution. + @pyret{min-val} is the lower bound on the range of random values to generate + and @pyret{max-val} is the upper bound on the range of random values to + generate. If @pyret{none}, the respective parameters are set to the + TensorFlow.js defaults. + @function["make-variable"] Creates a new, mutable @pyret{Tensor} initialized to the values of the input @@ -1430,6 +1511,57 @@ Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. + @tensor-method["add"] + + Adds @pyret{x} to the @pyret{Tensor}. This is equivalent to + @pyret-id["add-tensors"]@pyret{(self, x)}. + + @tensor-method["subtract"] + + Subtracts @pyret{x} from the @pyret{Tensor}. This is equivalent to + @pyret-id["subtract-tensors"]@pyret{(self, x)}. + + @tensor-method["multiply"] + + Multiplies the @pyret{Tensor} by @pyret{x}. This is equivalent to + @pyret-id["multiply-tensors"]@pyret{(self, x)}. + + @tensor-method["divide"] + + Divides the @pyret{Tensor} by @pyret{x}. This is equivalent to + @pyret-id["divide-tensors"]@pyret{(self, x)}. + + @tensor-method["floor-divide"] + + Divides the @pyret{Tensor} by @pyret{x}, with the result rounded + with the floor function. This is equivalent to + @pyret-id["floor-divide-tensors"]@pyret{(self, x)}. + + @tensor-method["max"] + + Returns the maximum of the @pyret{Tensor} and @pyret{x}. This is equivalent to + @pyret-id["tensor-max"]@pyret{(self, x)}. + + @tensor-method["min"] + + Returns the minimum of the @pyret{Tensor} and @pyret{x}. This is equivalent to + @pyret-id["tensor-min"]@pyret{(self, x)}. + + @tensor-method["modulo"] + + Computes the modulo of the @pyret{Tensor} and @pyret{x}. This is equivalent to + @pyret-id["tensor-modulo"]@pyret{(self, x)}. + + @tensor-method["expt"] + + Computes the power of the @pyret{Tensor} to @pyret{exponent}. This is + equivalent to @pyret-id["tensor-expt"]@pyret{(self, x)}. + + @tensor-method["squared-difference"] + + Computes @pyret{(self - x) * (self - x)}, element-wise. This is + equivalent to @pyret-id["squared-difference"]@pyret{(self, x)}. + @;######################################################################### @section{Arithmetic Operations} @@ -2036,6 +2168,21 @@ @bold{Note:} This TensorFlow.js implementation uses plain momentum and is not the "centered" version of RMSProp. + @;######################################################################### + @section{Optimizer Methods} + + @optimizer-method["minimize"] + + Executes @pyret{f} and minimizes the scalar output of @pyret{f} by computing + gradients of @pyret{y} with with respect to the list of trainable, variable + @pyret{Tensor}s provided by @pyret{variables}. + + @pyret{f} must be a thunk that returns a scalar @pyret{Tensor}. + The method then returns the scalar @pyret{Tensor} produced by @pyret{f}. + + If @pyret{variables} is @pyret{empty}, the @pyret{Optimizer} will default + to training all trainable variables that have been instantiated. + @;######################################################################### @section{Usage Examples} @@ -2137,84 +2284,102 @@ import tensorflow as TF import chart as C import image as I - import lists as L type Tensor = TF.Tensor - type Optimizer = TF.Optimizer - type ChartWindow = C.ChartWindow - type Image = I.Image + type DataSeries = C.DataSeries - # Create a tiny helper function: fun positive-rand() -> Number: - doc: "Generates a positive Number between 0 and 1" num-random(10000000) / 10000000 end - # `train-x` and `train-y` represent random points in a dataset, plotted - # on `scatter-plot`: - train-x = [list: - 3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, - 10.791, 5.313, 7.997, 5.654, 9.27, 3.1] + fun generate-data(num-points :: Number, coefficients :: Object, sigma :: Number) -> Object: + a = TF.make-scalar(coefficients.a) + b = TF.make-scalar(coefficients.b) + c = TF.make-scalar(coefficients.c) + d = TF.make-scalar(coefficients.d) - train-y = [list: - 1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, - 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3] + xs = TF.random-uniform([list: num-points], some(-1), some(1)) - scatter-plot = C.from-list.scatter-plot(train-x, train-y) + # The below represents ax^3 + bx^2 + cx + d: + ys = a.multiply(xs.expt(TF.make-scalar(3))) + .add(b.multiply(TF.tensor-square(xs))) + .add(c.multiply(xs)) + .add(d) + .add(TF.random-normal([list: num-points], some(0), some(sigma))) - # Create two scalar Tensors `m` and `b` that are variables: - m = TF.make-scalar(positive-rand()).to-variable() - b = TF.make-scalar(positive-rand()).to-variable() + # Normalize the y values to the range 0 to 1: + y-min = TF.reduce-min(ys) + y-max = TF.reduce-max(ys) + y-range = TF.subtract-tensors(y-max, y-min) + ys-normalized = TF.subtract-tensors(ys, y-min) ^ TF.divide-tensors(_, y-range) - # Setup a few helper functions before training: - fun predict(x :: Tensor) -> Tensor: - doc: ```Uses the current values of m and b to predict what Y-values will - be generated given a Tensor `x` representing X-values``` + {xs: xs, ys: ys-normalized} + end - temp = TF.multiply-tensors(m, x) - TF.add-tensors(temp, b) + fun predict(a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor, x :: Tensor) -> Tensor: + # The below represents ax^3 + bx^2 + cx + d: + a.multiply(x.expt(TF.make-scalar(3))) + .add(b.multiply(TF.tensor-square(x))) + .add(c.multiply(x)) + .add(d) end fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: - doc: ```Used to calculate a measure of difference between the predicted - Y-values and the actual Y-values``` - TF.subtract-tensors(prediction, actual-values) ^ TF.tensor-square(_) ^ TF.reduce-mean(_) end - # Train the model by creating an Optimizer. The optimizer will change any - # variable tensors used in the function passed into it in an attempt to - # minimize the returned loss: - fun train(): - doc: "Trains the model" - learning-rate = 0.005 - optimizer = TF.train-sgd(learning-rate) - - optimizer.minimize(lam() block: - prediction = predict(TF.list-to-tensor(train-x).as-1d()) - step-loss = loss(prediction, TF.list-to-tensor(train-y).as-1d()) - step-loss - end, empty) + fun plot(scatter-plot :: DataSeries, a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor) block: + a-val = a.data-sync().first + b-val = b.data-sync().first + c-val = c.data-sync().first + d-val = d.data-sync().first + + print("Equation:") + print("y = " + + num-to-string(a-val) + "x^3 + " + + num-to-string(b-val) + "x^2 + " + + num-to-string(c-val) + "x + " + + num-to-string(d-val)) + function-plot = C.from-list.function-plot( + lam(x): (a-val * num-expt(x, 3)) + (b-val * num-sqr(x)) + (c-val * x) + d-val end) + chart-image = C.render-charts([list: scatter-plot, function-plot]).get-image() + I.scale(0.6, chart-image) end - fun plot() -> ChartWindow: - doc: "Plots the current mx + b function and overlays it on the scatter plot" - shadow m = m.data-sync().first - shadow b = b.data-sync().first + # Generate synthetic data based on a cubic function + test-data = generate-data(100, {a: -0.8, b: -0.2, c: 0.9, d: 0.5}, 0.04) + train-x = test-data.xs.data-sync() + train-y = test-data.ys.data-sync() - function-plot = C.from-list.function-plot(lam(x): (m * x) + b end) - C.render-charts([list: scatter-plot, function-plot]) - end + # Plot the random points ahead of time for better perfomance: + scatter-plot = C.from-list.scatter-plot(train-x, train-y) - fun train-steps(steps :: Number) -> Image block: - doc: "Trains the model `steps` times" - for L.each(_ from L.range(0, steps)) block: - train() - print("y = " + num-to-string(m.data-sync().first) + "x + " + num-to-string(b.data-sync().first)) - end - plot().get-image() + # Generate a few variables representing coefficients in the equation, + # randomized to some value between 0 and 1 + a = TF.make-scalar(positive-rand()).to-variable() + b = TF.make-scalar(positive-rand()).to-variable() + c = TF.make-scalar(positive-rand()).to-variable() + d = TF.make-scalar(positive-rand()).to-variable() + + # Plot the random cubic function overlayed on the initial points: + plot(scatter-plot, a, b, c, d) + + # Create an optimizer: + LEARNING-RATE = 0.5 + TRAINING-CYCLES = 200 + optimizer = TF.train-sgd(LEARNING-RATE) + + # Train the model + for each(i from range(0, TRAINING-CYCLES)): + optimizer.minimize(lam() block: + prediction = predict(a, b, c, d, test-data.xs) + loss(prediction, test-data.ys) + end, empty) end + + # Plot the resulting cubic function overlayed on the initial points: + plot(scatter-plot, a, b, c, d) } } From 6c3e1d24ca761afefae602bb78f3d35558c3f2c4 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sat, 28 Jul 2018 00:25:10 -0400 Subject: [PATCH 13/27] Add examples to Tensor constructor documentation --- src/trove/tensorflow.scrbl | 902 ++++++++++++++++++++++++++++++------- 1 file changed, 746 insertions(+), 156 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 4602f83..043d78d 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -2,20 +2,27 @@ @(require "../../scribble-api.rkt" "../abbrevs.rkt") @(define Tensor (a-id "Tensor" (xref "tensorflow" "Tensor"))) +@(define TensorBuffer (a-id "TensorBuffer" (xref "tensorflow" "TensorBuffer"))) @(define Model (a-id "Model" (xref "tensorflow" "Model"))) @(define Sequential (a-id "Sequential" (xref "tensorflow" "Sequential"))) +@(define SymbolicTensor (a-id "SymbolicTensor" (xref "tensorflow" "SymbolicTensor"))) @(define Layer (a-id "Layer" (xref "tensorflow" "Layer"))) @(define Optimizer (a-id "Optimizer" (xref "tensorflow" "Optimizer"))) @(define Object (a-id "Object" (xref "" "Object"))) @(define Nothing (a-id "Nothing" (xref "" "Nothing"))) +@(define NumInteger (a-id "NumInteger" (xref "numbers" "NumInteger"))) @(define (tensor-method name) (method-doc "Tensor" #f name #:alt-docstrings "")) +@(define (tensor-buffer-method name) + (method-doc "TensorBuffer" #f name #:alt-docstrings "")) @(define (model-method name) (method-doc "Model" #f name #:alt-docstrings "")) @(define (sequential-method name) (method-doc "Sequential" #f name #:alt-docstrings "")) +@(define (symbolic-tensor-method name) + (method-doc "SymbolicTensor" #f name #:alt-docstrings "")) @(define (layer-method name) (method-doc "Layer" #f name #:alt-docstrings "")) @(define (optimizer-method name) @@ -47,20 +54,55 @@ (return ,Tensor) (contract (a-arrow ,N ,Tensor))) + (fun-spec + (name "fill") + (arity 2) + (args ("shape" "value")) + (return ,Tensor) + (contract + (a-arrow ,(L-of NumInteger) ,N ,Tensor))) + (fun-spec + (name "linspace") + (arity 3) + (args ("start" "stop" "num-values")) + (return ,Tensor) + (contract + (a-arrow ,N ,N, N ,Tensor))) + (fun-spec + (name "ones") + (arity 1) + (args ("shape")) + (return ,Tensor) + (contract + (a-arrow ,(L-of NumInteger) ,Tensor))) + (fun-spec + (name "zeros") + (arity 1) + (args ("shape")) + (return ,Tensor) + (contract + (a-arrow ,(L-of NumInteger) ,Tensor))) + (fun-spec + (name "multinomial") + (arity 4) + (args ("logits" "num-samples" "seed" "is-normalized")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,N ,(O-of N) ,B ,Tensor))) (fun-spec (name "random-normal") (arity 3) (args ("shape" "mean" "standard-deviation")) (return ,Tensor) (contract - (a-arrow ,(L-of N) ,(O-of N) ,(O-of N) ,Tensor))) + (a-arrow ,(L-of NumInteger) ,(O-of N) ,(O-of N) ,Tensor))) (fun-spec (name "random-uniform") (arity 3) (args ("shape" "min-val" "max-val")) (return ,Tensor) (contract - (a-arrow ,(L-of N) ,(O-of N) ,(O-of N) ,Tensor))) + (a-arrow ,(L-of NumInteger) ,(O-of N) ,(O-of N) ,Tensor))) (fun-spec (name "make-variable") (arity 1) @@ -178,6 +220,14 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor))) + (method-spec + (name "to-buffer") + (arity 1) + (params ()) + (args ("self")) + (return ,TensorBuffer) + (contract + (a-arrow ,Tensor ,TensorBuffer))) (method-spec (name "to-variable") (arity 1) @@ -194,6 +244,22 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,(L-of N) ,Tensor))) + (method-spec + (name "expand-dims") + (arity 2) + (params ()) + (args ("self" "axis")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of N) ,Tensor))) + (method-spec + (name "squeeze") + (arity 2) + (params ()) + (args ("self" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) (method-spec (name "clone") (arity 1) @@ -281,8 +347,62 @@ (args ("self" "x")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor ,Tensor))) - ))) + (a-arrow ,Tensor ,Tensor ,Tensor)))))) + + (fun-spec + (name "make-buffer") + (arity 1) + (args ("shape")) + (return ,TensorBuffer) + (contract + (a-arrow ,(L-of NumInteger) ,TensorBuffer))) + (fun-spec + (name "is-tensor-buffer") + (arity 1) + (args ("shape")) + (return ,A) + (contract + (a-arrow ,A ,B))) + + (data-spec + (name "TensorBuffer") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "get-now") + (arity 2) + (params ()) + (args ("self" "indices")) + (return ,N) + (contract + (a-arrow ,TensorBuffer ,(L-of NumInteger) ,N))) + (method-spec + (name "set-now") + (arity 3) + (params ()) + (args ("self" "value" "indices")) + (return ,Nothing) + (contract + (a-arrow ,TensorBuffer ,N ,(L-of NumInteger) ,Nothing))) + (method-spec + (name "get-all-now") + (arity 1) + (params ()) + (args ("self")) + (return ,(L-of RN)) + (contract + (a-arrow ,TensorBuffer ,(L-of RN)))) + (method-spec + (name "to-tensor") + (arity 1) + (params ()) + (args ("self")) + (return ,Tensor) + (contract + (a-arrow ,TensorBuffer ,Tensor)))))) + + (fun-spec (name "add-tensors") (arity 2) @@ -648,6 +768,13 @@ (return ,Tensor) (contract (a-arrow ,Tensor ,Tensor))) + (fun-spec + (name "tensor-sinh") + (arity 1) + (args ("tensor")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor))) (fun-spec (name "softplus") (arity 1) @@ -693,67 +820,133 @@ (fun-spec (name "all") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "any") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "arg-max") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "arg-min") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "log-sum-exp") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "reduce-max") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "reduce-mean") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "reduce-min") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec (name "reduce-sum") - (arity 1) - (args ("tensor")) + (arity 2) + (args ("tensor" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor))) + (a-arrow ,Tensor ,(O-of N) ,Tensor))) + + + + (fun-spec + (name "concatenate") + (arity 2) + (args ("tensors" "axis")) + (return ,Tensor) + (contract + (a-arrow ,(L-of Tensor) ,(O-of N) ,Tensor))) + (fun-spec + (name "gather") + (arity 3) + (args ("tensor" "indices" "axis")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,Tensor ,(O-of N) ,Tensor))) + (fun-spec + (name "reverse") + (arity 2) + (args ("tensor" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "slice") + (arity 3 ) + (args ("tensor" "begin" "size")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(L-of N) ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "split") + (arity 2) + (args ("tensor" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "stack") + (arity 2) + (args ("tensor" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "tile") + (arity 2) + (args ("tensor" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "unstack") + (arity 2) + (args ("tensor" "axes")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (fun-spec + (name "strided-slice") + (arity 4) + (args ("tensor" "begin" "end" "strides")) + (return ,Tensor) + (contract + (a-arrow ,Tensor ,(L-of N) ,(L-of N) ,(L-of N) ,Tensor))) (fun-spec (name "is-model") @@ -817,7 +1010,7 @@ (name "compile") (arity 2) (params ()) - (args ("self" "layer")) + (args ("self" "config")) (return ,Nothing) (contract (a-arrow ,Sequential ,Object ,Nothing))) @@ -855,6 +1048,35 @@ (a-arrow ,Sequential ,Tensor ,Tensor ,Object (a-arrow ,N ,Object ,Nothing) ,Nothing))) ))) + (fun-spec + (name "make-input") + (arity 1) + (args ("shape")) + (return ,SymbolicTensor) + (contract + (a-arrow ,(L-of (O-of N)) ,SymbolicTensor))) + (fun-spec + (name "make-batch-input") + (arity 1) + (args ("batch-shape")) + (return ,SymbolicTensor) + (contract + (a-arrow ,(L-of (O-of N)) ,SymbolicTensor))) + + (data-spec + (name "SymbolicTensor") + (type-vars ()) + (variants) + (shared + ((method-spec + (name "shape") + (arity 1) + (params ()) + (args ("self")) + (return ,(L-of (O-of N))) + (contract + (a-arrow ,SymbolicTensor ,(L-of (O-of N)))))))) + (fun-spec (name "is-layer") (arity 1) @@ -1065,6 +1287,76 @@ (return ,Layer) (contract (a-arrow ,Object ,Layer))) + (fun-spec + (name "gru-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "gru-cell-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "lstm-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "lstm-cell-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "rnn-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "simple-rnn-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "simple-rnn-cell-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "stacked-rnn-cells-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "bidirectional-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) + (fun-spec + (name "time-distributed-layer") + (arity 1) + (args ("config")) + (return ,Layer) + (contract + (a-arrow ,Object ,Layer))) (data-spec (name "Layer") @@ -1161,8 +1453,10 @@ A module that provides a Pyret interface for TensorFlow, a symbolic math library for machine learning applications. + @(table-of-contents) + @;######################################################################### - @section{The Tensor Datatype} + @section{Tensors} @type-spec["Tensor"]{ @@ -1198,7 +1492,7 @@ } @;######################################################################### - @section{Tensor Constructors} + @subsection{Tensor Constructors} @collection-doc["tensor" #:contract `(a-arrow ("value" ,N) ,Tensor)] @@ -1221,6 +1515,15 @@ Returns @pyret{true} if @pyret{val} is a @pyret{Tensor}; otherwise, returns @pyret{false}. + @examples{ + check: + is-tensor([tensor: 1, 2, 3]) is true + is-tensor(true) is false + is-tensor(0) is false + is-tensor([list: 1, 2, 3]) is false + end + } + @function["list-to-tensor"] Creates a new @pyret{Tensor} with the values in the input @pyret{List}. @@ -1232,6 +1535,11 @@ @pyret-method["Tensor" "reshape"] to change the shape of a @pyret{Tensor} after instantiating it. + @examples{ + list-to-tensor([list: 5, 3, 4, 7]) # a size-4 tensor + list-to-tensor([list: 9, 3, 2, 3]).as-2d(2, 2) # a 2 x 2 tensor + } + @function["make-scalar"] Creates a new @pyret{Tensor} of rank-0 with the given @pyret{value}. @@ -1241,6 +1549,96 @@ recommended to use @pyret-id["make-scalar"] as it makes the code more readable. + @examples{ + check: + make-scalar(1).size() is 0 + make-scalar(~12.3).shape() is empty + make-scalar(2.34).data-sync() is [list: 1] + end + } + + @function["fill"] + + Creates a @pyret{Tensor} with the input @pyret{shape} where all of the + entries are @pyret{value}. + + @examples{ + check: + fill([list: 0], 1).data-sync() + is-roughly [list: ] + fill([list: 3], 5).data-sync() + is-roughly [list: ~5, ~5, ~5] + fill([list: 3, 2], -3).data-sync() + is-roughly [list: ~-3, ~-3, ~-3, ~-3, ~-3, ~-3] + end + } + + @function["linspace"] + + Returns a @pyret{Tensor} whose values are an evenly spaced sequence of + numbers over the range @pyret{[start, stop]}. @pyret{num-values} is the + number of entries in the output @pyret{Tensor}. + + @examples{ + check: + linspace(0, 0, 1).data-sync() + is-roughly [list: ~0] + linspace(10, 11, 1).data-sync() + is-roughly [list: ~10] + linspace(5, 1, 5).data-sync() + is-roughly [list: ~5, ~4, ~3, ~2, ~1] + linspace(0, 9, 10).data-sync() + is-roughly [list: ~0, ~1, ~2, ~3, ~4, ~5, ~6, ~7, ~8, ~9] + linspace(0, 4, 9).data-sync() + is-roughly [list: ~0, ~0.5, ~1, ~1.5, ~2, ~2.5, ~3, ~3.5, ~4] + end + } + + @function["ones"] + + Returns a @pyret{Tensor} with the given @pyret{shape} where all of the + entries are ones. + + @examples{ + check: + ones([list: 0]).data-sync() is-roughly [list: ] + ones([list: 4]).data-sync() is-roughly [list: ~1, ~1, ~1, ~1] + two-dim = ones([list: 3, 2]) + two-dim.shape() is [list: 3, 2] + two-dim.data-sync() is-roughly [list: ~1, ~1, ~1, ~1, ~1, ~1] + end + } + + @function["zeros"] + + Returns a @pyret{Tensor} with the given @pyret{shape} where all of the + entries are zeros. + + @examples{ + check: + zeros([list: 0]).data-sync() is-roughly [list: ] + zeros([list: 4]).data-sync() is-roughly [list: ~0, ~0, ~0, ~0] + two-dim = zeros([list: 3, 2]) + two-dim.shape() is [list: 3, 2] + two-dim.data-sync() is-roughly [list: ~0, ~0, ~0, ~0, ~0, ~0] + end + } + + @function["multinomial"] + + Creates a new @pyret{Tensor} where all of the values are sampled from a + multinomial distribution. + + @pyret{logits} should be a @pyret{Tensor} representing a one-dimensional + array containing with unnormalized log-probabilities, or a two-dimensional + array of structure @pyret{[batch-size, num-outcomes]}. + + @pyret{num-samples} is the number of samples to draw for each row slice. + @pyret{seed} represents the random seed to use when generating values; if + @pyret{none}, the seed is randomly generated. @pyret{normalized} designates + whether or not the provided logits are normalized true probabilities (i.e: + they sum to 1). + @function["random-normal"] Creates a new @pyret{Tensor} with the given shape (represented as values in @@ -1271,8 +1669,14 @@ The same functionality can be achieved with the @pyret-method["Tensor" "to-variable"] method. + @examples{ + make-variable([tensor: 9, 3, 4.13, 0, 43]) + make-variable(random-normal([list: 4, 5, 3], some(0), some(1))) + make-variable(make-scalar(1)) + } + @;######################################################################### - @section{Tensor Methods} + @subsection{Tensor Methods} @tensor-method["size"] @@ -1329,11 +1733,11 @@ @examples{ check: - size-one = [TF.tensor: 1] + size-one = [tensor: 1] size-one.as-scalar().shape() is empty size-one.shape() is [list: 1] # doesn't modify shape of original tensor - size-two = [TF.tensor: 1, 2] + size-two = [tensor: 1, 2] size-two.as-scalar() raises "Tensor was size-2 but `as-scalar` requires the tensor to be size-1" end @@ -1350,9 +1754,9 @@ @examples{ check: - one-dim = [TF.tensor: 1] - two-dim = [TF.tensor: 4, 3, 2, 1].as-2d(2, 2) - three-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as-3d(3, 1, 3) + one-dim = [tensor: 1] + two-dim = [tensor: 4, 3, 2, 1].as-2d(2, 2) + three-dim = [tensor: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as-3d(3, 1, 3) one-dim.shape() is [list: 1] one-dim.as-1d().shape() is [list: 1] @@ -1380,9 +1784,9 @@ @examples{ check: - one-dim = [TF.tensor: 1] - two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5].as-2d(3, 2) - three-dim = [TF.tensor: 4, 3, 2, 1, 0, -1, -2, -3].as-3d(2, 2, 2) + one-dim = [tensor: 1] + two-dim = [tensor: 0, 1, 2, 3, 4, 5].as-2d(3, 2) + three-dim = [tensor: 4, 3, 2, 1, 0, -1, -2, -3].as-3d(2, 2, 2) one-dim.shape() is [list: 1] one-dim.as-2d(1, 1).shape() is [list: 1, 1] @@ -1414,8 +1818,8 @@ @examples{ check: - one-dim = [TF.tensor: 1] - two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) + one-dim = [tensor: 1] + two-dim = [tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) one-dim.shape() is [list: 1] one-dim.as-3d(1, 1, 1).shape() is [list: 1, 1, 1] @@ -1443,8 +1847,8 @@ @examples{ check: - one-dim = [TF.tensor: 1] - two-dim = [TF.tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) + one-dim = [tensor: 1] + two-dim = [tensor: 0, 1, 2, 3, 4, 5, 6, 7].as-2d(4, 2) one-dim.shape() is [list: 1] one-dim.as-4d(1, 1, 1, 1).shape() is [list: 1, 1, 1, 1] @@ -1487,6 +1891,11 @@ Constructs a new @pyret{Tensor} from the values of the original @pyret{Tensor} with all of the values cast to the @tt{"bool"} datatype. + @tensor-method["to-buffer"] + + Constructs a new @pyret-id["TensorBuffer"] from the values of the original + @pyret{Tensor}. + @tensor-method["to-variable"] Constructs a new, mutable @pyret{Tensor} from the values of the original @@ -1507,6 +1916,21 @@ @pyret-method["Tensor" "as-3d"], or @pyret-method["Tensor" "as-4d"] as they make the code more readable. + @tensor-method["expand-dims"] + + Returns a @pyret{Tensor} that has expanded rank, by inserting a dimension + into the @pyret{Tensor}'s shape at the given dimension index @pyret{axis}. + If @pyret{axis} is @pyret{none}, the method inserts a dimension at index 0 + by default. + + @tensor-method["squeeze"] + + Returns a @pyret{Tensor} with dimensions of size 1 removed from the shape. + + If @pyret{axis} is not @pyret{none}, the method only squeezes the dimensions + listed as indices in @pyret{axis}. The method will raise an error if one of + the dimensions specified in @pyret{axis} is not of size 1. + @tensor-method["clone"] Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. @@ -1563,44 +1987,92 @@ equivalent to @pyret-id["squared-difference"]@pyret{(self, x)}. @;######################################################################### - @section{Arithmetic Operations} + @section{TensorBuffers} + + @type-spec["TensorBuffer"]{ + + @pyret{TensorBuffer}s are mutable objects that allow users to set values + at specific locations before converting the buffer into an immutable + @pyret-id["Tensor"]. + + } + + @function["is-tensor-buffer"] + + Returns @pyret{true} if @pyret{val} is a @pyret{TensorBuffer}; otherwise, + returns @pyret{false}. + + @;######################################################################### + @subsection{TensorBuffer Constructors} + + @function["make-buffer"] + + Creates an @pyret{TensorBuffer} with the specified @pyret{shape}. The + returned @pyret{TensorBuffer}'s values are initialized to @pyret{~0}. + + @;######################################################################### + @subsection{TensorBuffer Methods} + + @tensor-buffer-method["set-now"] + + Sets the value in the @pyret{TensorBuffer} at the specified @pyret{indicies} + to @pyret{value}. + + @tensor-buffer-method["get-now"] + + Returns the value in the @pyret{TensorBuffer} at the specified + @pyret{indicies}. + + @tensor-buffer-method["get-all-now"] + + Returns all values in the @pyret{TensorBuffer}. + + @tensor-buffer-method["to-tensor"] + + Creates an immutable @pyret-id["Tensor"] from the @pyret{TensorBuffer}. + + @;######################################################################### + @section{Operations} + + @;######################################################################### + @subsection{Arithmetic Operations} @function["add-tensors"] - Adds two @pyret{Tensor}s element-wise, A + B. + Adds two @pyret-id["Tensor"]s element-wise, A + B. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-add-tensors"]. @function["subtract-tensors"] - Subtracts two @pyret{Tensor}s element-wise, A – B. + Subtracts two @pyret-id["Tensor"]s element-wise, A – B. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-subtract-tensors"]. @function["multiply-tensors"] - Multiplies two @pyret{Tensor}s element-wise, A * B. + Multiplies two @pyret-id["Tensor"]s element-wise, A * B. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-multiply-tensors"]. @function["divide-tensors"] - Divides two @pyret{Tensor}s element-wise, A / B. + Divides two @pyret-id["Tensor"]s element-wise, A / B. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-divide-tensors"]. @function["floor-divide-tensors"] - Divides two @pyret{Tensor}s element-wise, A / B, with the result rounded + Divides two @pyret-id["Tensor"]s element-wise, A / B, with the result rounded with the floor function. @function["tensor-max"] - Returns a @pyret{Tensor} containing the maximum of @pyret{a} and @pyret{b}, + Returns a @pyret-id["Tensor"] containing the maximum of @pyret{a} and @pyret{b}, element-wise. To assert that @pyret{a} and @pyret{b} are the same shape, use @@ -1608,7 +2080,7 @@ @function["tensor-min"] - Returns a @pyret{Tensor} containing the minimum of @pyret{a} and @pyret{b}, + Returns a @pyret-id["Tensor"] containing the minimum of @pyret{a} and @pyret{b}, element-wise. To assert that @pyret{a} and @pyret{b} are the same shape, use @@ -1690,31 +2162,31 @@ @pyret-method["Tensor" "shape"]). @;######################################################################### - @section{Basic Math Operations} + @subsection{Basic Math Operations} @function["tensor-abs"] - Computes the absolute value of the @pyret{Tensor}, element-wise. + Computes the absolute value of the @pyret-id["Tensor"], element-wise. @function["tensor-acos"] - Computes the inverse cosine of the @pyret{Tensor}, element-wise. + Computes the inverse cosine of the @pyret-id["Tensor"], element-wise. @function["tensor-acosh"] - Computes the inverse hyperbolic cosine of the @pyret{Tensor}, element-wise. + Computes the inverse hyperbolic cosine of the @pyret-id["Tensor"], element-wise. @function["tensor-asin"] - Computes the inverse sine of the @pyret{Tensor}, element-wise. + Computes the inverse sine of the @pyret-id["Tensor"], element-wise. @function["tensor-asinh"] - Computes the inverse hyperbolic sine of the @pyret{Tensor}, element-wise. + Computes the inverse hyperbolic sine of the @pyret-id["Tensor"], element-wise. @function["tensor-atan"] - Computes the inverse tangent of the @pyret{Tensor}, element-wise. + Computes the inverse tangent of the @pyret-id["Tensor"], element-wise. @function["tensor-atan2"] @@ -1723,30 +2195,30 @@ @function["tensor-atanh"] - Computes the inverse hyperbolic tangent of the @pyret{Tensor}, element-wise. + Computes the inverse hyperbolic tangent of the @pyret-id["Tensor"], element-wise. @function["tensor-ceil"] - Computes the ceiling of the @pyret{Tensor}, element-wise. + Computes the ceiling of the @pyret-id["Tensor"], element-wise. @function["clip-by-value"] - Clips the values of the @pyret{Tensor}, element-wise, such that every element - in the resulting @pyret{Tensor} is at least @pyret{min-value} and is at most + Clips the values of the @pyret-id["Tensor"], element-wise, such that every element + in the resulting @pyret-id["Tensor"] is at least @pyret{min-value} and is at most @pyret{max-value}. @function["tensor-cos"] - Computes the cosine of the @pyret{Tensor}, element-wise. + Computes the cosine of the @pyret-id["Tensor"], element-wise. @function["tensor-cosh"] - Computes the hyperbolic cosine of the @pyret{Tensor}, element-wise. + Computes the hyperbolic cosine of the @pyret-id["Tensor"], element-wise. @function["exponential-linear-units"] Applies the @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#ELUs" - "exponential linear units"] function to the @pyret{Tensor}, element-wise. + "exponential linear units"] function to the @pyret-id["Tensor"], element-wise. @function["elu"] @@ -1755,7 +2227,7 @@ @function["gauss-error"] Applies the @link["http://mathworld.wolfram.com/Erf.html" "gauss error function"] - to the @pyret{Tensor}, element-wise. + to the @pyret-id["Tensor"], element-wise. @function["erf"] @@ -1771,12 +2243,13 @@ @function["tensor-floor"] - Computes the floor of the @pyret{Tensor}, element-wise. + Computes the floor of the @pyret-id["Tensor"], element-wise. @function["leaky-relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" - "leaky rectified linear units"] function to the @pyret{Tensor}, element-wise. + "leaky rectified linear units"] function to the @pyret-id["Tensor"], + element-wise. @pyret{alpha} is the scaling factor for negative values. The default in TensorFlow.js is @pyret{0.2}, but the argument has been exposed here for more @@ -1784,41 +2257,42 @@ @function["tensor-log"] - Computes the natural logarithm of the @pyret{Tensor}, element-wise; that is, - it computes the equivalent of @pyret{num-log(tensor)}. + Computes the natural logarithm of the @pyret-id["Tensor"], element-wise; that + is, it computes the equivalent of @pyret{num-log(tensor)}. @function["tensor-log-plus1"] - Computes the natural logarithm of the @pyret{Tensor} plus 1, element-wise; - that is, it computes the equivalent of @pyret{num-log(tensor + 1)}. + Computes the natural logarithm of the @pyret-id["Tensor"] plus 1, + element-wise; that is, it computes the equivalent of + @pyret{num-log(tensor + 1)}. @function["log-sigmoid"] Applies the @link["https://en.wikibooks.org/wiki/Artificial_Neural_Networks/ Activation_Functions#Continuous_Log-Sigmoid_Function" "log sigmoid"] function - to the @pyret{Tensor}, element-wise. + to the @pyret-id["Tensor"], element-wise. @function["tensor-negate"] - Multiplies each element in the @pyret{Tensor} by @pyret{-1}. + Multiplies each element in the @pyret-id["Tensor"] by @pyret{-1}. @function["parametric-relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" - "leaky rectified linear units"] function to the @pyret{Tensor}, element-wise, - using parametric alphas. + "leaky rectified linear units"] function to the @pyret-id["Tensor"], + element-wise, using parametric alphas. @pyret{alpha} is the scaling factor for negative values. @function["tensor-reciprocal"] - Computes the reciprocal of the @pyret{Tensor}, element-wise; that is, it + Computes the reciprocal of the @pyret-id["Tensor"], element-wise; that is, it computes the equivalent of @pyret{1 / tensor}. @function["relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)" - "rectified linear units"] function to the @pyret{Tensor}, element-wise. + "rectified linear units"] function to the @pyret-id["Tensor"], element-wise. @function["tensor-round"] @@ -1826,67 +2300,71 @@ @function["reciprocal-sqrt"] - Computes the recriprocal of the square root of the @pyret{Tensor}, + Computes the recriprocal of the square root of the @pyret-id["Tensor"], element-wise. - The resulting @pyret{Tensor} is roughly equivalent to + The resulting @pyret-id["Tensor"] is roughly equivalent to @pyret{tensor-reciprocal(tensor-sqrt(tensor))}. @function["scaled-elu"] - Applies a scaled, exponential linear units function to the @pyret{Tensor}, - element-wise. + Applies a scaled, exponential linear units function to the + @pyret-id["Tensor"], element-wise. @function["sigmoid"] - Applies the sigmoid function to the @pyret{Tensor}, element-wise. + Applies the sigmoid function to the @pyret-id["Tensor"], element-wise. @function["signed-ones"] Returns an element-wise indication of the sign of each number in the - @pyret{Tensor}; that is, every value in the original tensor is represented - in the resulting tensor as @pyret{~+1} if the value is positive, @pyret{~-1} - if the value was negative, or @pyret{~0} if the value was zero or not a - number. + @pyret-id["Tensor"]; that is, every value in the original tensor is + represented in the resulting tensor as @pyret{~+1} if the value is positive, + @pyret{~-1} if the value was negative, or @pyret{~0} if the value was zero + or not a number. @function["tensor-sin"] - Computes the sine of the @pyret{Tensor}, element-wise. + Computes the sine of the @pyret-id["Tensor"], element-wise. + + @function["tensor-sinh"] + + Computes the hyperbolic sine of the @pyret-id["Tensor"], element-wise. @function["softplus"] Applies the @link["https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/" - "softplus"] function to the @pyret{Tensor}, element-wise. + "softplus"] function to the @pyret-id["Tensor"], element-wise. @function["tensor-sqrt"] - Computes the square root of the @pyret{Tensor}, element-wise. + Computes the square root of the @pyret-id["Tensor"], element-wise. @function["tensor-square"] - Computes the square of the @pyret{Tensor}, element-wise. + Computes the square of the @pyret-id["Tensor"], element-wise. @function["step"] - Applies the unit step function to the @pyret{Tensor}, element-wise; that is, - that is, every value in the original tensor is represented - in the resulting tensor as @pyret{~0} if the value is negative; otherwise, - it is represented as @pyret{~+1}. + Applies the unit step function to the @pyret-id["Tensor"], element-wise; + that is, every value in the original tensor is represented in the resulting + tensor as @pyret{~0} if the value is negative; otherwise, it is represented + as @pyret{~+1}. @function["tensor-tan"] - Computes the tangent of the @pyret{Tensor}, element-wise. + Computes the tangent of the @pyret-id["Tensor"], element-wise. @function["tensor-tanh"] - Computes the hyperbolic tangent of the @pyret{Tensor}, element-wise. + Computes the hyperbolic tangent of the @pyret-id["Tensor"], element-wise. @;######################################################################### - @section{Reduction Operations} + @subsection{Reduction Operations} @function["all"] - Reduces the input @pyret{Tensor} across all dimensions by computing the + Reduces the input @pyret-id["Tensor"] across all dimensions by computing the logical "and" of its elements. @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises @@ -1894,7 +2372,7 @@ @function["any"] - Reduces the input @pyret{Tensor} across all dimensions by computing the + Reduces the input @pyret-id["Tensor"] across all dimensions by computing the logical "or" of its elements. @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises @@ -1902,112 +2380,206 @@ @function["arg-max"] - Returns a new @pyret{Tensor} where each element is the index of the maximum + Returns a new @pyret-id["Tensor"] where each element is the index of the maximum values along the outermost dimension of @pyret{tensor}. @function["arg-min"] - Returns a new @pyret{Tensor} where each element is the index of the minimum + Returns a new @pyret-id["Tensor"] where each element is the index of the minimum values along the outermost dimension of @pyret{tensor}. @function["log-sum-exp"] - Computes the @pyret{log(sum(exp(elements along the outermost dimension))}. + Computes @pyret{log(sum(exp(elements along the outermost dimension))}. - Reduces the input along the outermost dimension. + Reduces @pyret{tensor} along the outermost dimension. @function["reduce-max"] - Returns a @pyret{Tensor} containing a single value that is the maximum value - of all entries in the input @pyret{tensor}. + Returns a @pyret-id["Tensor"] containing a single value that is the maximum value + of all entries in @pyret{tensor}. @function["reduce-min"] - Returns a @pyret{Tensor} containing a single value that is the minimum value - of all entries in the input @pyret{tensor}. + Returns a @pyret-id["Tensor"] containing a single value that is the minimum value + of all entries in @pyret{tensor}. @function["reduce-mean"] - Returns a @pyret{Tensor} containing a single value that is the mean value - of all entries in the input @pyret{tensor}. + Returns a @pyret-id["Tensor"] containing a single value that is the mean value + of all entries in @pyret{tensor}. @function["reduce-sum"] - Returns a @pyret{Tensor} containing a single value that is the sum - of all entries in the input @pyret{tensor}. + Returns a @pyret-id["Tensor"] containing a single value that is the sum + of all entries in @pyret{tensor}. @;######################################################################### - @section{Models} + @subsection{Slicing and Joining Operations} - @pyret{Model}s represent a collection of @pyret{Layers}, and define a series - of inputs and outputs. They are one of the primary abstractions used in - TensorFlow, and can be trained, evaluated, and used for prediction. + @function["concatenate"] - There are two types of models in TensorFlow: @pyret{Sequential}, where - the outputs of one @pyret{Layer} are the inputs to the next @pyret{Layer}, - and @pyret{Model}, which is more generic and supports arbitrary, non-cyclic - graphs of @pyret{Layer}s. + Concatenates each @pyret-id["Tensor"] in @pyret{tensors} along the given + @pyret{axis}. - @type-spec["Model"]{ + If @pyret{axis} is @pyret{none}, the function defaults to concatenating along + axis 0 (the first dimension). - A @pyret{Model} is a data structure that consists of @pyret{Layer}s and - defines inputs and outputs. It is more generic than @pyret{Sequential} - models as it supports arbitrary, non-cyclic graphs of @pyret{Layer}s. + The @pyret-id["Tensor"]s' ranks and types must match, and their sizes must + match in all dimensions except @pyret{axis}. + @function["gather"] + + Gathers slices from the @pyret-id["Tensor"] at every index in @pyret{indices} + along the given @pyret{axis}. + + If @pyret{axis} is @pyret{none}, the function defaults to gathering along + axis 0 (the first dimension). + + @examples{ + check: + input-1 = [tensor: 1, 2, 3, 4] + indices-1 = [tensor: 1, 3, 3] + + gather(input-1, indices-1).data-sync() is [list: 2, 4, 4] + + input-2 = [tensor: 1, 2, 3, 4].as-2d(2, 2) + indices-2 = [tensor: 1, 1, 0] + + gather(input-2, indices-2).data-sync() is [list: 3, 4, + 3, 4, + 1, 2] + end } - @nested[#:style 'inset]{ + @function["reverse"] + + Reverses the values in @pyret{tensor} along the specified @pyret{axis}. + + If @pyret{axis} is @pyret{none}, the function defaults to reversing along + axis 0 (the first dimension). + + @function["slice"] + + Extracts a slice from @pyret{tensor} starting at the coordinates represented + by @pyret{begin}. The resulting slice is of size @pyret{size}. + + A value of @pyret{-1} in @pyret{size} means that the resulting slice will go + all the way to the end of the dimensions in the respective axis. + + If the length of @pyret{size} is less than the rank of in @pyret{tensor}, the + size of the rest of the axes will be implicitly set to @pyret{-1}. If + @pyret{size} is @pyret{none}, the size of all axes will be set to @pyret{-1}. + + @function["split"] + @function["stack"] + @function["tile"] + @function["unstack"] + @function["strided-slice"] + + Extracts a strided slice of a @pyret-id["Tensor"]. + + Roughly speaking, this operations extracts a slice of size + @pyret{(end - begin) / stride} from @pyret{tensor}. Starting at the location + specified by @pyret{begin}, the slice continues by adding @pyret{stride} to + the index until all dimensions are not less than @pyret{end}. Note that a + stride can be negative, which causes a reverse slice. + + @;######################################################################### + @section{Models} + + @pyret{Model}s represent a collection of @pyret-id["Layer"]s, and define a + series of inputs and outputs. They are one of the primary abstractions used + in TensorFlow, and can be trained, evaluated, and used for prediction. + + There are two types of models in TensorFlow: @pyret-id["Sequential"], where + the outputs of one @pyret-id["Layer"] are the inputs to the next + @pyret-id["Layer"], and @pyret-id["Model"], which is more generic and + supports arbitrary, non-cyclic graphs of @pyret-id["Layer"]s. - @function["is-model"] + @;######################################################################### + @subsection{Generic Models} + + @type-spec["Model"]{ - @function["make-model"] + A @pyret{Model} is a data structure that consists of @pyret-id["Layer"]s and + defines inputs and outputs. It is more generic than @pyret-id["Sequential"] + models as it supports arbitrary, non-cyclic graphs of @pyret-id["Layer"]s. } + @function["is-model"] + + @function["make-model"] + + @;######################################################################### + @subsection{Sequential Models} + @type-spec["Sequential"]{ A @pyret{Sequential} model is a model where the outputs of one - @pyret{Layer} are the inputs to the next @pyret{Layer}. That is, the model - topology is a simple "stack" of layers, with no branching or skipping. + @pyret-id["Layer"] are the inputs to the next @pyret-id["Layer"]. That is, + the model topology is a simple "stack" of layers, with no branching or + skipping. As a result, the first layer passed to a @pyret{Sequential} model must have a defined input shape. } - @nested[#:style 'inset]{ + @function["is-sequential"] + + @function["make-sequential"] + + @sequential-method["add"] + @sequential-method["compile"] + @sequential-method["evaluate"] + @sequential-method["predict"] + @sequential-method["predict-on-batch"] + @sequential-method["fit"] - @function["is-sequential"] + @;######################################################################### + @section{SymbolicTensors} + + @type-spec["SymbolicTensor"]{ - @function["make-sequential"] + @pyret{SymbolicTensor}s are placeholders for @pyret-id["Tensor"]s without + any concrete value. - @sequential-method["add"] - @sequential-method["compile"] - @sequential-method["evaluate"] - @sequential-method["predict"] - @sequential-method["predict-on-batch"] - @sequential-method["fit"] + They are most often encountered when building a graph of @pyret-id["Layer"]s + for a @pyret-id["Model"] that takes in some kind of unknown input. } @;######################################################################### - @section{The Layer Datatype} + @subsection{SymbolicTensor Constructors} + + @function["make-input"] + @function["make-batch-input"] + + @;######################################################################### + @subsection{SymbolicTensor Methods} + + @symbolic-tensor-method["shape"] + + @;######################################################################### + @section{Layers} @type-spec["Layer"]{ - Layers are the primary building block for constructing a @pyret{Model}. Each - layer will typically perform some computation to transform its input to its - output. + @pyret{Layer}s are the primary building block for constructing a + @pyret-id["Model"]. Each @pyret{Layer} will typically perform some + computation to transform its input to its output. - Layers will automatically take care of creating and initializing the various - internal variables/weights they need to function. + @pyret{Layer}s will automatically take care of creating and initializing + the various internal variables/weights they need to function. } @function["is-layer"] @;######################################################################### - @section{Basic Layers} + @subsection{Basic Layers} @function["activation-layer"] @function["dense-layer"] @@ -2018,7 +2590,7 @@ @function["reshape-layer"] @;######################################################################### - @section{Convolutional Layers} + @subsection{Convolutional Layers} @function["conv-1d-layer"] @function["conv-2d-layer"] @@ -2029,7 +2601,7 @@ @function["up-sampling-2d-layer"] @;######################################################################### - @section{Convolutional Layers} + @subsection{Merge Layers} @function["add-layer"] @function["average-layer"] @@ -2039,12 +2611,12 @@ @function["multiply-layer"] @;######################################################################### - @section{Normalization Layers} + @subsection{Normalization Layers} @function["batch-normalization-layer"] @;######################################################################### - @section{Pooling Layers} + @subsection{Pooling Layers} @function["average-pooling-1d-layer"] @function["average-pooling-2d-layer"] @@ -2056,7 +2628,25 @@ @function["max-pooling-2d-layer"] @;######################################################################### - @section{The Optimizer Datatype} + @subsection{Recurrent Layers} + + @function["gru-layer"] + @function["gru-cell-layer"] + @function["lstm-layer"] + @function["lstm-cell-layer"] + @function["rnn-layer"] + @function["simple-rnn-layer"] + @function["simple-rnn-cell-layer"] + @function["stacked-rnn-cells-layer"] + + @;######################################################################### + @subsection{Wrapper Layers} + + @function["bidirectional-layer"] + @function["time-distributed-layer"] + + @;######################################################################### + @section{Optimizers} @type-spec["Optimizer"]{ @@ -2076,7 +2666,7 @@ returns @pyret{false}. @;######################################################################### - @section{Optimizer Constructors} + @subsection{Optimizer Constructors} There are many different types of @pyret{Optimizer}s that use different formulas to compute gradients. @@ -2169,7 +2759,7 @@ not the "centered" version of RMSProp. @;######################################################################### - @section{Optimizer Methods} + @subsection{Optimizer Methods} @optimizer-method["minimize"] From 8426c6368ce5ee7866a1e0f1703e60474c8b9f77 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 9 Aug 2018 14:45:09 -0400 Subject: [PATCH 14/27] Rename data-sync to data-now --- src/trove/tensorflow.scrbl | 208 ++++++++++++++++++++++++++++--------- 1 file changed, 158 insertions(+), 50 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 043d78d..a35ae02 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -12,6 +12,7 @@ @(define Object (a-id "Object" (xref "" "Object"))) @(define Nothing (a-id "Nothing" (xref "" "Nothing"))) @(define NumInteger (a-id "NumInteger" (xref "numbers" "NumInteger"))) +@(define NumPositive (a-id "NumPositive" (xref "numbers" "NumPositive"))) @(define (tensor-method name) (method-doc "Tensor" #f name #:alt-docstrings "")) @@ -88,7 +89,7 @@ (args ("logits" "num-samples" "seed" "is-normalized")) (return ,Tensor) (contract - (a-arrow ,Tensor ,N ,(O-of N) ,B ,Tensor))) + (a-arrow ,Tensor ,NumPositive ,(O-of N) ,B ,Tensor))) (fun-spec (name "random-normal") (arity 3) @@ -163,7 +164,7 @@ (args ("self" "rows" "columns")) (return ,Tensor) (contract - (a-arrow ,Tensor ,N ,N ,Tensor))) + (a-arrow ,Tensor ,NumInteger ,NumInteger ,Tensor))) (method-spec (name "as-3d") (arity 4) @@ -171,7 +172,7 @@ (args ("self" "rows" "columns" "depth")) (return ,Tensor) (contract - (a-arrow ,Tensor ,N ,N ,N ,Tensor))) + (a-arrow ,Tensor ,NumInteger ,NumInteger ,NumInteger ,Tensor))) (method-spec (name "as-4d") (arity 5) @@ -179,7 +180,7 @@ (args ("self" "rows" "columns" "depth1" "depth2")) (return ,Tensor) (contract - (a-arrow ,Tensor ,N ,N ,N ,N ,Tensor))) + (a-arrow ,Tensor ,NumInteger ,NumInteger ,NumInteger ,NumInteger ,Tensor))) (method-spec (name "as-type") (arity 2) @@ -189,7 +190,7 @@ (contract (a-arrow ,Tensor ,S ,Tensor))) (method-spec - (name "data-sync") + (name "data-now") (arity 1) (params ()) (args ("self" )) @@ -243,7 +244,7 @@ (args ("self" "new-shape")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(L-of N) ,Tensor))) + (a-arrow ,Tensor ,(L-of NumInteger) ,Tensor))) (method-spec (name "expand-dims") (arity 2) @@ -251,7 +252,7 @@ (args ("self" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of N) ,Tensor))) + (a-arrow ,Tensor ,(O-of NumInteger) ,Tensor))) (method-spec (name "squeeze") (arity 2) @@ -259,7 +260,7 @@ (args ("self" "axes")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,(O-of (L-of NumInteger)) ,Tensor))) (method-spec (name "clone") (arity 1) @@ -1479,7 +1480,7 @@ For performance reasons, @pyret{Tensor}s do not support arbitrary precision. Retrieving values from a @pyret{Tensor} using - @pyret-method["Tensor" "data-sync"] always returns a + @pyret-method["Tensor" "data-now"] always returns a @pyret{List}. Since @pyret{Tensor}s are immutable, all operations always return new @@ -1536,8 +1537,14 @@ after instantiating it. @examples{ - list-to-tensor([list: 5, 3, 4, 7]) # a size-4 tensor - list-to-tensor([list: 9, 3, 2, 3]).as-2d(2, 2) # a 2 x 2 tensor + check: + is-tensor(list-to-tensor(empty)) is true + is-tensor(list-to-tensor([list: 5, 3, 4, 7])) is true + + list-to-tensor(empty).data-now() is empty + list-to-tensor([list: 9, 3, 2, 3]).data-now() is-roughly [list: 9, 3, 2, 3] + list-to-tensor([list: 3, 2, 1, 0, 4, 9]).as-2d(2, 3).shape() is [list: 2, 3] + end } @function["make-scalar"] @@ -1551,9 +1558,9 @@ @examples{ check: - make-scalar(1).size() is 0 + make-scalar(1).size() is 1 make-scalar(~12.3).shape() is empty - make-scalar(2.34).data-sync() is [list: 1] + make-scalar(2.34).data-now() is-roughly [list: 2.34] end } @@ -1564,12 +1571,12 @@ @examples{ check: - fill([list: 0], 1).data-sync() + fill([list: 0], 1).data-now() is-roughly [list: ] - fill([list: 3], 5).data-sync() - is-roughly [list: ~5, ~5, ~5] - fill([list: 3, 2], -3).data-sync() - is-roughly [list: ~-3, ~-3, ~-3, ~-3, ~-3, ~-3] + fill([list: 3], 5).data-now() + is-roughly [list: 5, 5, 5] + fill([list: 3, 2], -3).data-now() + is-roughly [list: -3, -3, -3, -3, -3, -3] end } @@ -1581,16 +1588,16 @@ @examples{ check: - linspace(0, 0, 1).data-sync() - is-roughly [list: ~0] - linspace(10, 11, 1).data-sync() - is-roughly [list: ~10] - linspace(5, 1, 5).data-sync() - is-roughly [list: ~5, ~4, ~3, ~2, ~1] - linspace(0, 9, 10).data-sync() - is-roughly [list: ~0, ~1, ~2, ~3, ~4, ~5, ~6, ~7, ~8, ~9] - linspace(0, 4, 9).data-sync() - is-roughly [list: ~0, ~0.5, ~1, ~1.5, ~2, ~2.5, ~3, ~3.5, ~4] + linspace(0, 3, 1).data-now() + is-roughly [list: 0] + linspace(10, 11, 1).data-now() + is-roughly [list: 10] + linspace(5, 1, 5).data-now() + is-roughly [list: 5, 4, 3, 2, 1] + linspace(0, 9, 10).data-now() + is-roughly [list: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + linspace(0, 4, 9).data-now() + is-roughly [list: 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4] end } @@ -1601,11 +1608,11 @@ @examples{ check: - ones([list: 0]).data-sync() is-roughly [list: ] - ones([list: 4]).data-sync() is-roughly [list: ~1, ~1, ~1, ~1] + ones([list: 0]).data-now() is-roughly [list: ] + ones([list: 4]).data-now() is-roughly [list: 1, 1, 1, 1] two-dim = ones([list: 3, 2]) two-dim.shape() is [list: 3, 2] - two-dim.data-sync() is-roughly [list: ~1, ~1, ~1, ~1, ~1, ~1] + two-dim.data-now() is-roughly [list: 1, 1, 1, 1, 1, 1] end } @@ -1616,11 +1623,11 @@ @examples{ check: - zeros([list: 0]).data-sync() is-roughly [list: ] - zeros([list: 4]).data-sync() is-roughly [list: ~0, ~0, ~0, ~0] + zeros([list: 0]).data-now() is-roughly [list: ] + zeros([list: 4]).data-now() is-roughly [list: 0, 0, 0, 0] two-dim = zeros([list: 3, 2]) two-dim.shape() is [list: 3, 2] - two-dim.data-sync() is-roughly [list: ~0, ~0, ~0, ~0, ~0, ~0] + two-dim.data-now() is-roughly [list: 0, 0, 0, 0, 0, 0] end } @@ -1639,6 +1646,23 @@ whether or not the provided logits are normalized true probabilities (i.e: they sum to 1). + @examples{ + check: + three-dim = [tensor: 1, 1, 1, 1, 1, 1, 1, 1].as-3d(2, 2, 2) + multinomial(three-dim, 2, none, false) + raises "must be a one-dimensional or two-dimensional Tensor" + + multinomial([tensor: ], 1, none, false) + raises "must have at least two possible outcomes" + multinomial([tensor: 0.8], 7, none, false) + raises "must have at least two possible outcomes" + + multinomial([tensor: 1.0, 0.0], 1, none, true).shape() is [list: 1] + multinomial([tensor: 1.0, 0.0], 3, none, true).shape() is [list: 3] + multinomial([tensor: 0.3, 0.5, 0.7], 10, none, false).shape() is [list: 10] + end + } + @function["random-normal"] Creates a new @pyret{Tensor} with the given shape (represented as values in @@ -1650,6 +1674,15 @@ distribution. If @pyret{none}, the respective parameters are set to the TensorFlow.js defaults. + @examples{ + check: + random-normal(empty, none, none).size() is 1 + random-normal(empty, none, none).shape() is empty + random-normal([list: 4, 3], none, none).shape() is [list: 4, 3] + random-normal([list: 2, 5, 3], none, none).shape() is [list: 2, 5, 3] + end + } + @function["random-uniform"] Creates a new @pyret{Tensor} with the given shape (represented as values in @@ -1661,6 +1694,22 @@ generate. If @pyret{none}, the respective parameters are set to the TensorFlow.js defaults. + @examples{ + check: + random-uniform(empty, none, none).size() is 1 + random-uniform(empty, none, none).shape() is empty + random-uniform([list: 1, 3], none, none).shape() is [list: 1, 3] + random-uniform([list: 5, 4, 8], none, none).shape() is [list: 5, 4, 8] + + lower-bound = 1 + upper-bound = 10 + random-data = random-uniform([list: 20], some(lower-bound), some(upper-bound)) + for each(data-point from random-data.data-now()): + data-point satisfies lam(x): (x >= lower-bound) and (x <= upper-bound) end + end + end + } + @function["make-variable"] Creates a new, mutable @pyret{Tensor} initialized to the values of the input @@ -1670,9 +1719,17 @@ @pyret-method["Tensor" "to-variable"] method. @examples{ - make-variable([tensor: 9, 3, 4.13, 0, 43]) - make-variable(random-normal([list: 4, 5, 3], some(0), some(1))) - make-variable(make-scalar(1)) + check: + make-variable([tensor: ]).data-now() is-roughly empty + make-variable([tensor: 1]).data-now() is-roughly [list: 1] + + # We can perform normal Tensor operations on mutable Tensors: + two-dim = [tensor: 4, 5, 3, 9].as-2d(2, 2) + make-variable(two-dim).size() is 4 + make-variable(two-dim).shape() is [list: 2, 2] + make-variable(two-dim).data-now() is-roughly [list: 4, 5, 3, 9] + make-variable(two-dim).as-3d(4, 1, 1).shape() is [list: 4, 1, 1] + end } @;######################################################################### @@ -1756,7 +1813,7 @@ check: one-dim = [tensor: 1] two-dim = [tensor: 4, 3, 2, 1].as-2d(2, 2) - three-dim = [tensor: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as-3d(3, 1, 3) + three-dim = [tensor: 0, 1, 2, 3, 4, 5, 6, 7, 8].as-3d(3, 1, 3) one-dim.shape() is [list: 1] one-dim.as-1d().shape() is [list: 1] @@ -1869,7 +1926,19 @@ The possible @pyret{data-type}s are @pyret{"float32"}, @pyret{"int32"}, or @pyret{"bool"}. Any other @pyret{dataType} will raise an error. - @tensor-method["data-sync"] + @examples{ + check: + some-tensor = [tensor: 1, 3, 5, 8] + + some-tensor.as-type("float32") does-not-raise + some-tensor.as-type("int32") does-not-raise + some-tensor.as-type("bool") does-not-raise + some-tensor.as-type("invalid") + raises "Attempted to cast tensor to invalid type" + end + } + + @tensor-method["data-now"] Returns a @pyret{List} containing the data in the @pyret{Tensor}. @@ -1881,16 +1950,55 @@ Constructs a new @pyret{Tensor} from the values of the original @pyret{Tensor} with all of the values cast to the @tt{"float32"} datatype. + @examples{ + check: + [tensor: 0].to-float().data-now() is-roughly [list: 0] + [tensor: 1].to-float().data-now() is-roughly [list: 1] + [tensor: 0.42].to-float().data-now() is-roughly [list: 0.42] + [tensor: 0.999999].to-float().data-now() is-roughly [list: 0.999999] + [tensor: 1.52, 4.12, 5.99].to-float().data-now() + is-roughly [list: 1.52, 4.12, 5.99] + [tensor: 4, 0.32, 9.40, 8].to-float().data-now() + is-roughly [list: 4, 0.32, 9.40, 8] + end + } + @tensor-method["to-int"] Constructs a new @pyret{Tensor} from the values of the original @pyret{Tensor} with all of the values cast to the @tt{"int32"} datatype. + @examples{ + check: + [tensor: 0].to-int().data-now() is-roughly [list: 0] + [tensor: 1].to-int().data-now() is-roughly [list: 1] + [tensor: 0.42].to-int().data-now() is-roughly [list: 0] + [tensor: 0.999999].to-int().data-now() is-roughly [list: 0] + [tensor: 1.52, 4.12, 5.99].to-int().data-now() + is-roughly [list: 1, 4, 5] + [tensor: 4, 0.32, 9.40, 8].to-int().data-now() + is-roughly [list: 4, 0, 9, 8] + end + } + @tensor-method["to-bool"] Constructs a new @pyret{Tensor} from the values of the original @pyret{Tensor} with all of the values cast to the @tt{"bool"} datatype. + @examples{ + check: + [tensor: 0].to-bool().data-now() is-roughly [list: 0] + [tensor: 1].to-bool().data-now() is-roughly [list: 1] + [tensor: 0.42].to-bool().data-now() is-roughly [list: 1] + [tensor: 1, 4, 5].to-bool().data-now() is-roughly [list: 1, 1, 1] + [tensor: 4, 7, 0, 9].to-bool().data-now() + is-roughly [list: 1, 1, 0, 1] + [tensor: 0, 2, 3, 0, 0].to-bool().data-now() + is-roughly [list: 0, 1, 1, 0, 0] + end + } + @tensor-method["to-buffer"] Constructs a new @pyret-id["TensorBuffer"] from the values of the original @@ -2441,12 +2549,12 @@ input-1 = [tensor: 1, 2, 3, 4] indices-1 = [tensor: 1, 3, 3] - gather(input-1, indices-1).data-sync() is [list: 2, 4, 4] + gather(input-1, indices-1).data-now() is [list: 2, 4, 4] input-2 = [tensor: 1, 2, 3, 4].as-2d(2, 2) indices-2 = [tensor: 1, 1, 0] - gather(input-2, indices-2).data-sync() is [list: 3, 4, + gather(input-2, indices-2).data-now() is [list: 3, 4, 3, 4, 1, 2] end @@ -2847,8 +2955,8 @@ fun plot() -> ChartWindow: doc: "Plots the current mx + b function and overlays it on the scatter plot" - shadow m = m.data-sync().first - shadow b = b.data-sync().first + shadow m = m.data-now().first + shadow b = b.data-now().first function-plot = C.from-list.function-plot(lam(x): (m * x) + b end) C.render-charts([list: scatter-plot, function-plot]) @@ -2858,7 +2966,7 @@ doc: "Trains the model `steps` times" for L.each(_ from L.range(0, steps)) block: train() - print("y = " + num-to-string(m.data-sync().first) + "x + " + num-to-string(b.data-sync().first)) + print("y = " + num-to-string(m.data-now().first) + "x + " + num-to-string(b.data-now().first)) end plot().get-image() end @@ -2921,10 +3029,10 @@ end fun plot(scatter-plot :: DataSeries, a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor) block: - a-val = a.data-sync().first - b-val = b.data-sync().first - c-val = c.data-sync().first - d-val = d.data-sync().first + a-val = a.data-now().first + b-val = b.data-now().first + c-val = c.data-now().first + d-val = d.data-now().first print("Equation:") print("y = " @@ -2940,8 +3048,8 @@ # Generate synthetic data based on a cubic function test-data = generate-data(100, {a: -0.8, b: -0.2, c: 0.9, d: 0.5}, 0.04) - train-x = test-data.xs.data-sync() - train-y = test-data.ys.data-sync() + train-x = test-data.xs.data-now() + train-y = test-data.ys.data-now() # Plot the random points ahead of time for better perfomance: scatter-plot = C.from-list.scatter-plot(train-x, train-y) From 4355cf24e1c3897df2b333b220a316107eba5adb Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 9 Aug 2018 14:45:31 -0400 Subject: [PATCH 15/27] Remove deprecated note about data-sync --- src/trove/tensorflow.scrbl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index a35ae02..323b88f 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -1942,9 +1942,6 @@ Returns a @pyret{List} containing the data in the @pyret{Tensor}. - The "@pyret{-sync}" part of the method name is a remnant of the Tensorflow.js - naming scheme. - @tensor-method["to-float"] Constructs a new @pyret{Tensor} from the values of the original From a8b14f4dc78fc6e08ebc68085b50eccb74692f13 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Thu, 9 Aug 2018 15:39:36 -0400 Subject: [PATCH 16/27] Add a boatload of testcases --- src/trove/tensorflow.scrbl | 105 ++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 323b88f..779f55b 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -1538,8 +1538,8 @@ @examples{ check: - is-tensor(list-to-tensor(empty)) is true - is-tensor(list-to-tensor([list: 5, 3, 4, 7])) is true + list-to-tensor(empty) satisfies is-tensor + list-to-tensor([list: 5, 3, 4, 7]) satisfies is-tensor list-to-tensor(empty).data-now() is empty list-to-tensor([list: 9, 3, 2, 3]).data-now() is-roughly [list: 9, 3, 2, 3] @@ -1942,6 +1942,15 @@ Returns a @pyret{List} containing the data in the @pyret{Tensor}. + @examples{ + check: + [tensor: ].data-now() is-roughly [list: ] + [tensor: 1].data-now() is-roughly [list: 1] + [tensor: 1.43].data-now() is-roughly [list: 1.43] + [tensor: -3.21, 9.4, 0.32].data-now() is-roughly [list: -3.21, 9.4, 0.32] + end + } + @tensor-method["to-float"] Constructs a new @pyret{Tensor} from the values of the original @@ -1952,9 +1961,6 @@ [tensor: 0].to-float().data-now() is-roughly [list: 0] [tensor: 1].to-float().data-now() is-roughly [list: 1] [tensor: 0.42].to-float().data-now() is-roughly [list: 0.42] - [tensor: 0.999999].to-float().data-now() is-roughly [list: 0.999999] - [tensor: 1.52, 4.12, 5.99].to-float().data-now() - is-roughly [list: 1.52, 4.12, 5.99] [tensor: 4, 0.32, 9.40, 8].to-float().data-now() is-roughly [list: 4, 0.32, 9.40, 8] end @@ -1969,12 +1975,9 @@ check: [tensor: 0].to-int().data-now() is-roughly [list: 0] [tensor: 1].to-int().data-now() is-roughly [list: 1] - [tensor: 0.42].to-int().data-now() is-roughly [list: 0] [tensor: 0.999999].to-int().data-now() is-roughly [list: 0] [tensor: 1.52, 4.12, 5.99].to-int().data-now() is-roughly [list: 1, 4, 5] - [tensor: 4, 0.32, 9.40, 8].to-int().data-now() - is-roughly [list: 4, 0, 9, 8] end } @@ -1987,12 +1990,8 @@ check: [tensor: 0].to-bool().data-now() is-roughly [list: 0] [tensor: 1].to-bool().data-now() is-roughly [list: 1] - [tensor: 0.42].to-bool().data-now() is-roughly [list: 1] + [tensor: 0.42].tox-bool().data-now() is-roughly [list: 1] [tensor: 1, 4, 5].to-bool().data-now() is-roughly [list: 1, 1, 1] - [tensor: 4, 7, 0, 9].to-bool().data-now() - is-roughly [list: 1, 1, 0, 1] - [tensor: 0, 2, 3, 0, 0].to-bool().data-now() - is-roughly [list: 0, 1, 1, 0, 0] end } @@ -2001,10 +2000,35 @@ Constructs a new @pyret-id["TensorBuffer"] from the values of the original @pyret{Tensor}. + @examples{ + check: + empty-buffer = [tensor: ].to-buffer() + empty-buffer satisfies is-tensor-buffer + empty-buffer.get-all-now() is-roughly [list: ] + + some-shape = [list: 2, 2] + some-values = [list: 4, 5, 9, 3] + some-tensor = list-to-tensor(some-values).reshape(some-shape) + some-buffer = some-tensor.to-buffer() + some-buffer satisfies is-tensor-buffer + some-buffer.get-all-now() is-roughly some-values + some-buffer.to-tensor().shape() is some-shape + end + } + @tensor-method["to-variable"] Constructs a new, mutable @pyret{Tensor} from the values of the original - @pyret{Tensor}. + @pyret{Tensor}. Equivalent to applying @pyret-id["make-variable"] on the + calling @pyret{Tensor}. + + @examples{ + check: + [tensor: ].to-variable() does-not-raise + [tensor: 4, 5, 1].to-variable() does-not-raise + [tensor: 0, 5, 1, 9, 8, 4].as-2d(3, 2).to-variable() does-not-raise + end + } @tensor-method["reshape"] @@ -2021,6 +2045,21 @@ @pyret-method["Tensor" "as-3d"], or @pyret-method["Tensor" "as-4d"] as they make the code more readable. + @examples{ + check: + [tensor: ].reshape([list: ]) raises "Cannot reshape" + [tensor: 3, 2].reshape([list: ]) raises "Cannot reshape" + [tensor: 3, 2].reshape([list: 6]) raises "Cannot reshape" + [tensor: 3, 2, 1].reshape([list: 2, 4]) raises "Cannot reshape" + + [tensor: 1].reshape([list: 1]).shape() is [list: 1] + [tensor: 1].reshape([list: 1, 1, 1]).shape() is [list: 1, 1, 1] + [tensor: 1].reshape([list: 1, 1, 1, 1, 1]).shape() is [list: 1, 1, 1, 1, 1] + [tensor: 1, 4].reshape([list: 2, 1]).shape() is [list: 2, 1] + [tensor: 1, 4, 4, 5, 9, 3].reshape([list: 3, 2]).shape() is [list: 3, 2] + end + } + @tensor-method["expand-dims"] Returns a @pyret{Tensor} that has expanded rank, by inserting a dimension @@ -2028,6 +2067,18 @@ If @pyret{axis} is @pyret{none}, the method inserts a dimension at index 0 by default. + @examples{ + check: + one-dim = [tensor: 1, 2, 3, 4] + one-dim.shape() is [list: 4] + one-dim.expand-dims(none).shape() is [list: 1, 4] + one-dim.expand-dims(some(1)).shape() is [list: 4, 1] + + one-dim.expand-dims(some(2)) + raises "input axis must be less than or equal to the rank of the tensor" + end + } + @tensor-method["squeeze"] Returns a @pyret{Tensor} with dimensions of size 1 removed from the shape. @@ -2036,10 +2087,36 @@ listed as indices in @pyret{axis}. The method will raise an error if one of the dimensions specified in @pyret{axis} is not of size 1. + @examples{ + check: + multi-dim = [tensor: 1, 2, 3, 4].reshape([list: 1, 1, 1, 4, 1]) + multi-dim.shape() is [list: 1, 1, 1, 4, 1] + multi-dim.squeeze(none).shape() is [list: 4] + multi-dim.squeeze(some([list: 0])).shape() is [list: 1, 1, 4, 1] + multi-dim.squeeze(some([list: 4])).shape() is [list: 1, 1, 1, 4] + multi-dim.squeeze(some([list: 1, 2])).shape() is [list: 1, 4, 1] + + multi-dim.squeeze(some([list: 7])) + raises "Cannot squeeze axis 7 since the axis does not exist" + multi-dim.squeeze(some([list: 3])) + raises "Cannot squeeze axis 3 since the dimension of that axis is 4, not 1" + end + } + @tensor-method["clone"] Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. + @examples{ + check: + some-tensor = [tensor: 1, 2, 3, 4] + new-tensor = some-tensor.clone() + new-tensor.size() is some-tensor.size() + new-tensor.shape() is some-tensor.shape() + new-tensor.data-now() is-roughly some-tensor.data-now() + end + } + @tensor-method["add"] Adds @pyret{x} to the @pyret{Tensor}. This is equivalent to From 9ec6b1d0680c395a566a267790e8d1d9aa8b2d49 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sat, 11 Aug 2018 01:23:36 -0400 Subject: [PATCH 17/27] Add a whole bunch of examples to tensor operations --- src/trove/tensorflow.scrbl | 902 +++++++++++++++++++++++++++++++++++-- 1 file changed, 863 insertions(+), 39 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 779f55b..3c80e63 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -130,9 +130,9 @@ (arity 1) (params ()) (args ("self")) - (return ,(L-of N)) + (return ,(L-of NumInteger)) (contract - (a-arrow ,Tensor ,(L-of N)))) + (a-arrow ,Tensor ,(L-of NumInteger)))) (method-spec (name "flatten") (arity 1) @@ -371,6 +371,22 @@ (variants) (shared ((method-spec + (name "size") + (arity 1) + (params ()) + (args ("self")) + (return ,N) + (contract + (a-arrow ,TensorBuffer ,N))) + (method-spec + (name "shape") + (arity 1) + (params ()) + (args ("self")) + (return ,(L-of NumInteger)) + (contract + (a-arrow ,TensorBuffer ,(L-of NumInteger)))) + (method-spec (name "get-now") (arity 2) (params ()) @@ -883,8 +899,6 @@ (contract (a-arrow ,Tensor ,(O-of N) ,Tensor))) - - (fun-spec (name "concatenate") (arity 2) @@ -915,25 +929,25 @@ (a-arrow ,Tensor ,(L-of N) ,(O-of (L-of N)) ,Tensor))) (fun-spec (name "split") - (arity 2) - (args ("tensor" "axes")) + (arity 3) + (args ("tensor" "split-sizes" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,(L-of N) ,(O-of N) ,Tensor))) (fun-spec (name "stack") (arity 2) - (args ("tensor" "axes")) + (args ("tensors" "axes")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,(L-of Tensor) ,(O-of (L-of N)) ,Tensor))) (fun-spec (name "tile") (arity 2) - (args ("tensor" "axes")) + (args ("tensor" "repetitions")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,(L-of N) ,Tensor))) (fun-spec (name "unstack") (arity 2) @@ -947,7 +961,7 @@ (args ("tensor" "begin" "end" "strides")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(L-of N) ,(L-of N) ,(L-of N) ,Tensor))) + (a-arrow ,Tensor ,(L-of NumInteger) ,(L-of NumInteger) ,(L-of N) ,Tensor))) (fun-spec (name "is-model") @@ -1751,9 +1765,9 @@ @tensor-method["shape"] - Returns a @pyret{List} representing the shape of the @pyret{Tensor}. - Each element in the @pyret{List} corresponds to the size in each - dimension. + Returns a @pyret{List} representing the shape of the + @pyret{Tensor}. Each element in the @pyret{List} corresponds + to the size in each dimension. @examples{ check: @@ -2184,6 +2198,16 @@ Returns @pyret{true} if @pyret{val} is a @pyret{TensorBuffer}; otherwise, returns @pyret{false}. + @examples{ + check: + is-tensor-buffer(make-buffer([list: 1])) is true + is-tensor-buffer(make-buffer([list: 8, 4, 10])) is true + is-tensor-buffer(43) is false + is-tensor-buffer("not a buffer") is false + is-tensor-buffer({some: "thing"}) is false + end + } + @;######################################################################### @subsection{TensorBuffer Constructors} @@ -2192,27 +2216,161 @@ Creates an @pyret{TensorBuffer} with the specified @pyret{shape}. The returned @pyret{TensorBuffer}'s values are initialized to @pyret{~0}. + @examples{ + check: + make-buffer([list: 1]).size() is 1 + make-buffer([list: 1]).shape() is [list: 1] + make-buffer([list: 9, 5]).size() is 45 + make-buffer([list: 9, 5]).shape() is [list: 9, 5] + + # Check for error handling of rank-0 shapes: + make-buffer(empty) raises "input shape List had zero elements" + + # Check for error handling of less than zero dimension sizes: + make-buffer([list: 0]) raises "Cannot create TensorBuffer" + make-buffer([list: -1]) raises "Cannot create TensorBuffer" + make-buffer([list: 4, 5, 0, 3]) raises "Cannot create TensorBuffer" + make-buffer([list: 2, -5, -1, 4]) raises "Cannot create TensorBuffer" + end + } + @;######################################################################### @subsection{TensorBuffer Methods} + @tensor-buffer-method["size"] + + Returns the size of the @pyret{TensorBuffer} (the number of values stored + in the @pyret{TensorBuffer}). + + @examples{ + check: + make-buffer([list: 1]).size() is 1 + make-buffer([list: 4]).size() is 4 + make-buffer([list: 3, 2]).size() is 6 + make-buffer([list: 4, 4]).size() is 16 + make-buffer([list: 4, 3, 5]).size() is 60 + end + } + + @tensor-buffer-method["shape"] + + Returns a @pyret{List} representing the shape of the + @pyret{TensorBuffer}. Each element in the @pyret{List} + corresponds to the size in each dimension. + + @examples{ + check: + make-buffer([list: 1]).shape() is [list: 1] + make-buffer([list: 4, 3]).shape() is [list: 4, 3] + make-buffer([list: 2, 4, 1]).shape() is [list: 2, 4, 1] + make-buffer([list: 4, 3, 5]).shape() is [list: 4, 3, 5] + end + } + @tensor-buffer-method["set-now"] Sets the value in the @pyret{TensorBuffer} at the specified @pyret{indicies} to @pyret{value}. + @examples{ + check: + test-buffer = make-buffer([list: 7]) + test-buffer.set-now(-45, [list: 0]) + test-buffer.set-now(9, [list: 2]) + test-buffer.set-now(0, [list: 4]) + test-buffer.set-now(-3.42, [list: 6]) + + test-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + test-buffer.to-tensor().shape() is [list: 7] + test-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + + # Check out-of-bounds coordinates: + test-buffer.set-now(10, [list: -1]) + raises "Coordinates must be within the bounds of the TensorBuffer's shape" + test-buffer.set-now(10, [list: 8]) + raises "Coordinates must be within the bounds of the TensorBuffer's shape" + + # Check too little coordinates: + test-buffer.set-now(10, [list:]) + raises "number of supplied coordinates must match the rank" + + # Check too many coordinates: + test-buffer.set-now(10, [list: 9, 5]) + raises "number of supplied coordinates must match the rank" + end + } + @tensor-buffer-method["get-now"] Returns the value in the @pyret{TensorBuffer} at the specified @pyret{indicies}. + @examples{ + check: + test-buffer = make-buffer([list: 7]) + test-buffer.set-now(-45, [list: 0]) + test-buffer.set-now(9, [list: 2]) + test-buffer.set-now(0, [list: 4]) + test-buffer.set-now((4 / 3), [list: 5]) + test-buffer.set-now(-3.42, [list: 6]) + + test-buffer.get-now([list: 0]) is-roughly -45 + test-buffer.get-now([list: 1]) is-roughly 0 + test-buffer.get-now([list: 2]) is-roughly 9 + test-buffer.get-now([list: 3]) is-roughly 0 + test-buffer.get-now([list: 4]) is-roughly 0 + test-buffer.get-now([list: 5]) is-roughly (4 / 3) + test-buffer.get-now([list: 6]) is-roughly -3.42 + end + } + @tensor-buffer-method["get-all-now"] Returns all values in the @pyret{TensorBuffer}. + @examples{ + check: + one-dim-buffer = make-buffer([list: 7]) + one-dim-buffer.set-now(-45, [list: 0]) + one-dim-buffer.set-now(9, [list: 2]) + one-dim-buffer.set-now(0, [list: 4]) + one-dim-buffer.set-now((4 / 3), [list: 5]) + one-dim-buffer.set-now(-3.42, [list: 6]) + one-dim-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, (4 / 3), -3.42] + + two-dim-buffer = make-buffer([list: 2, 2]) + two-dim-buffer.set-now(4, [list: 0, 0]) + two-dim-buffer.set-now(3, [list: 0, 1]) + two-dim-buffer.set-now(2, [list: 1, 0]) + two-dim-buffer.set-now(1, [list: 1, 1]) + two-dim-buffer.get-all-now() is-roughly [list: 4, 3, 2, 1] + end + } + @tensor-buffer-method["to-tensor"] Creates an immutable @pyret-id["Tensor"] from the @pyret{TensorBuffer}. + @examples{ + check: + one-dim-buffer = make-buffer([list: 7]) + one-dim-buffer.set-now(-45, [list: 0]) + one-dim-buffer.set-now(9, [list: 2]) + one-dim-buffer.set-now(0, [list: 4]) + one-dim-buffer.set-now(-3.42, [list: 6]) + one-dim-buffer.to-tensor().shape() is [list: 7] + one-dim-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + + two-dim-buffer = make-buffer([list: 2, 2]) + two-dim-buffer.set-now(4, [list: 0, 0]) + two-dim-buffer.set-now(3, [list: 0, 1]) + two-dim-buffer.set-now(2, [list: 1, 0]) + two-dim-buffer.set-now(1, [list: 1, 1]) + two-dim-buffer.to-tensor().shape() is [list: 2, 2] + two-dim-buffer.to-tensor().data-now() is-roughly [list: 4, 3, 2, 1] + end + } + @;######################################################################### @section{Operations} @@ -2226,6 +2384,19 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-add-tensors"]. + @examples{ + check: + add-tensors([tensor: 1], [tensor: 1]).data-now() + is-roughly [list: 2] + add-tensors([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 2, 4] + add-tensors([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 6, 4] + add-tensors([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["subtract-tensors"] Subtracts two @pyret-id["Tensor"]s element-wise, A – B. @@ -2233,6 +2404,19 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-subtract-tensors"]. + @examples{ + check: + subtract-tensors([tensor: 1], [tensor: 1]).data-now() + is-roughly [list: 0] + subtract-tensors([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 0, 2] + subtract-tensors([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: -4, 2] + subtract-tensors([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["multiply-tensors"] Multiplies two @pyret-id["Tensor"]s element-wise, A * B. @@ -2240,6 +2424,19 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-multiply-tensors"]. + @examples{ + check: + multiply-tensors([tensor: 1], [tensor: 1]).data-now() + is-roughly [list: 1] + multiply-tensors([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 1, 3] + multiply-tensors([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 5, 3] + multiply-tensors([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["divide-tensors"] Divides two @pyret-id["Tensor"]s element-wise, A / B. @@ -2247,27 +2444,95 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-divide-tensors"]. + @examples{ + check: + divide-tensors([tensor: 1], [tensor: 1]).data-now() + is-roughly [list: 1] + divide-tensors([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 1, 3] + divide-tensors([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 0.2, 3] + divide-tensors([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + + divide-tensors([tensor: 1], [tensor: 0]) + raises "The argument Tensor cannot contain 0" + divide-tensors([tensor: 4.23], [tensor: 7.65, 1.43, 0, 2.31]) + raises "The argument Tensor cannot contain 0" + end + } + @function["floor-divide-tensors"] - Divides two @pyret-id["Tensor"]s element-wise, A / B, with the result rounded - with the floor function. + Divides two @pyret-id["Tensor"]s element-wise, A / B, with the result + rounded with the floor function. + + @examples{ + check: + floor-divide-tensors([tensor: 1], [tensor: 1]).data-now() + is-roughly [list: 1] + floor-divide-tensors([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 1, 3] + floor-divide-tensors([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 0, 3] + floor-divide-tensors([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + + floor-divide-tensors([tensor: 1], [tensor: 0]) + raises "The argument Tensor cannot contain 0" + floor-divide-tensors([tensor: 4.23], [tensor: 7.65, 1.43, 0]) + raises "The argument Tensor cannot contain 0" + end + } @function["tensor-max"] - Returns a @pyret-id["Tensor"] containing the maximum of @pyret{a} and @pyret{b}, - element-wise. + Returns a @pyret-id["Tensor"] containing the maximum of @pyret{a} and + @pyret{b}, element-wise. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-tensor-max"]. + @examples{ + check: + tensor-max([tensor: 0], [tensor: 1]).data-now() + is-roughly [list: 1] + tensor-max([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 1, 3] + tensor-max([tensor: 1, 3], [tensor: 200]).data-now() + is-roughly [list: 200, 200] + tensor-max([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 5, 3] + tensor-max([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["tensor-min"] - Returns a @pyret-id["Tensor"] containing the minimum of @pyret{a} and @pyret{b}, - element-wise. + Returns a @pyret-id["Tensor"] containing the minimum of @pyret{a} and + @pyret{b}, element-wise. To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-tensor-min"]. + @examples{ + check: + tensor-min([tensor: 0], [tensor: 1]).data-now() + is-roughly [list: 0] + tensor-min([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 1, 1] + tensor-min([tensor: 1, 3], [tensor: 200]).data-now() + is-roughly [list: 1, 3] + tensor-min([tensor: 1, 3], [tensor: 0]).data-now() + is-roughly [list: 0, 0] + tensor-min([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 1, 1] + tensor-min([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["tensor-modulo"] Computes the modulo of @pyret{a} and @pyret{b}, element-wise. @@ -2275,6 +2540,21 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-tensor-modulo"]. + @examples{ + check: + tensor-modulo([tensor: 0], [tensor: 1]).data-now() + is-roughly [list: 0] + tensor-modulo([tensor: 1, 3], [tensor: 1]).data-now() + is-roughly [list: 0, 0] + tensor-modulo([tensor: 1, 3], [tensor: 200]).data-now() + is-roughly [list: 1, 3] + tensor-modulo([tensor: 1, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 1, 0] + tensor-modulo([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["tensor-expt"] Computes the power of @pyret{base} to @pyret{exponent}, element-wise. @@ -2282,6 +2562,21 @@ To ensure that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-tensor-expt"]. + @examples{ + check: + tensor-expt([tensor: 0], [tensor: 1]).data-now() + is-roughly [list: 0] + tensor-expt([tensor: 3], [tensor: -3]).data-now() + is-roughly [list: 0.03703703] + tensor-expt([tensor: 1, 3], [tensor: 4]).data-now() + is-roughly [list: 1, 81] + tensor-expt([tensor: 3, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 243, 3] + tensor-expt([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["squared-difference"] Computes @pyret{(a - b) * (a - b)}, element-wise. @@ -2289,60 +2584,211 @@ To assert that @pyret{a} and @pyret{b} are the same shape, use @pyret-id["strict-squared-difference"]. + @examples{ + check: + squared-difference([tensor: 0], [tensor: 1]).data-now() + is-roughly [list: 1] + squared-difference([tensor: 3], [tensor: -3]).data-now() + is-roughly [list: 36] + squared-difference([tensor: 1, 3], [tensor: 4]).data-now() + is-roughly [list: 9, 1] + squared-difference([tensor: 3, 3], [tensor: 5, 1]).data-now() + is-roughly [list: 4, 4] + squared-difference([tensor: 1, 3, 4], [tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @function["strict-add-tensors"] Same as @pyret-id["add-tensors"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-add-tensors([tensor: 1], [tensor: 0]) + is-roughly add-tensors([tensor: 1], [tensor: 0]) + strict-add-tensors([tensor: -4, -1], [tensor: -8, -2]) + is-roughly add-tensors([tensor: -4, -1], [tensor: -8, -2]) + + strict-add-tensors([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-add-tensors([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-subtract-tensors"] Same as @pyret-id["subtract-tensors"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-subtract-tensors([tensor: 1], [tensor: 0]) + is-roughly subtract-tensors([tensor: 1], [tensor: 0]) + strict-subtract-tensors([tensor: -4, -1], [tensor: -8, -2]) + is-roughly subtract-tensors([tensor: -4, -1], [tensor: -8, -2]) + + strict-subtract-tensors([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-subtract-tensors([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-multiply-tensors"] Same as @pyret-id["multiply-tensors"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-multiply-tensors([tensor: 1], [tensor: 0]) + is-roughly multiply-tensors([tensor: 1], [tensor: 0]) + strict-multiply-tensors([tensor: -4, -1], [tensor: -8, -2]) + is-roughly multiply-tensors([tensor: -4, -1], [tensor: -8, -2]) + + strict-multiply-tensors([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-multiply-tensors([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-divide-tensors"] Same as @pyret-id["divide-tensors"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-divide-tensors([tensor: 1], [tensor: 0]) + is-roughly divide-tensors([tensor: 1], [tensor: 0]) + strict-divide-tensors([tensor: -4, -1], [tensor: -8, -2]) + is-roughly divide-tensors([tensor: -4, -1], [tensor: -8, -2]) + + strict-divide-tensors([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-divide-tensors([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + + strict-divide-tensors([tensor: 1], [tensor: 0]) + raises "The argument Tensor cannot contain 0" + strict-divide-tensors([tensor: 1, 1], [tensor: 1, 0]) + raises "The argument Tensor cannot contain 0" + end + } + @function["strict-tensor-max"] Same as @pyret-id["tensor-max"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-tensor-max([tensor: 1], [tensor: 0]) + is-roughly tensor-max([tensor: 1], [tensor: 0]) + strict-tensor-max([tensor: -4, -1], [tensor: -8, -2]) + is-roughly tensor-max([tensor: -4, -1], [tensor: -8, -2]) + + strict-tensor-max([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-tensor-max([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-tensor-min"] Same as @pyret-id["tensor-min"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-tensor-min([tensor: 1], [tensor: 0]) + is-roughly tensor-min([tensor: 1], [tensor: 0]) + strict-tensor-min([tensor: -4, -1], [tensor: -8, -2]) + is-roughly tensor-min([tensor: -4, -1], [tensor: -8, -2]) + + strict-tensor-min([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-tensor-min([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-tensor-expt"] Same as @pyret-id["tensor-expt"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-tensor-expt([tensor: 1], [tensor: 0]) + is-roughly tensor-expt([tensor: 1], [tensor: 0]) + strict-tensor-expt([tensor: -4, -1], [tensor: -8, -2]) + is-roughly tensor-expt([tensor: -4, -1], [tensor: -8, -2]) + + strict-tensor-expt([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-tensor-expt([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @function["strict-tensor-modulo"] Same as @pyret-id["tensor-modulo"], but raises an error if @pyret{a} and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-tensor-modulo([tensor: 1], [tensor: 0]) + is-roughly tensor-modulo([tensor: 1], [tensor: 0]) + strict-tensor-modulo([tensor: -4, -1], [tensor: -8, -2]) + is-roughly tensor-modulo([tensor: -4, -1], [tensor: -8, -2]) + + strict-tensor-modulo([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-tensor-modulo([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + + strict-tensor-modulo([tensor: 1], [tensor: 0]) + raises "The argument Tensor cannot contain 0" + strict-tensor-modulo([tensor: 1, 1], [tensor: 1, 0]) + raises "The argument Tensor cannot contain 0" + end + } + @function["strict-squared-difference"] - Same as @pyret-id["squared-difference"], but raises an error if @pyret{a} and - @pyret{b} are not the same shape (as determined by + Same as @pyret-id["squared-difference"], but raises an error if @pyret{a} + and @pyret{b} are not the same shape (as determined by @pyret-method["Tensor" "shape"]). + @examples{ + check: + strict-squared-difference([tensor: 1], [tensor: 0]) + is-roughly squared-difference([tensor: 1], [tensor: 0]) + strict-squared-difference([tensor: -4, -1], [tensor: -8, -2]) + is-roughly squared-difference([tensor: -4, -1], [tensor: -8, -2]) + + strict-squared-difference([tensor: 1], [tensor: 1, 2]) + raises "The first tensor does not have the same shape as the second tensor" + strict-squared-difference([tensor: 8, 0].as-2d(2, 1), [tensor: 3, 1]) + raises "The first tensor does not have the same shape as the second tensor" + end + } + @;######################################################################### @subsection{Basic Math Operations} @@ -2350,26 +2796,108 @@ Computes the absolute value of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-abs([tensor: 0]).data-now() is-roughly [list: 0] + tensor-abs([tensor: 1]).data-now() is-roughly [list: 1] + tensor-abs([tensor: -1]).data-now() is-roughly [list: 1] + tensor-abs([tensor: -1, -2, -3]).data-now() is-roughly [list: 1, 2, 3] + + two-dim-abs = tensor-abs([tensor: -4, 5, -6, -7, -8, 9].as-2d(3, 2)) + two-dim-abs.shape() is [list: 3, 2] + two-dim-abs.data-now() is-roughly [list: 4, 5, 6, 7, 8, 9] + end + } + @function["tensor-acos"] Computes the inverse cosine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-acos([tensor: 1]).data-now() is-roughly [list: 0] + tensor-acos([tensor: 0]).data-now() is-roughly [list: ~1.5707963] + tensor-acos([tensor: -1]).data-now() is-roughly [list: ~3.1415927] + tensor-acos([tensor: 0.5, 0.2, 0.6]).data-now() + is-roughly [list: ~1.0471975, ~1.3694384, ~0.9272952] + + tensor-acos([tensor: 10]) + raises "Values in the input Tensor must be between -1 and 1, inclusive" + tensor-acos([tensor: -1, 0, 16, -2]) + raises "Values in the input Tensor must be between -1 and 1, inclusive" + end + } + @function["tensor-acosh"] - Computes the inverse hyperbolic cosine of the @pyret-id["Tensor"], element-wise. + Computes the inverse hyperbolic cosine of the @pyret-id["Tensor"], + element-wise. + + @examples{ + check: + tensor-acosh([tensor: 1]).data-now() is-roughly [list: 0] + tensor-acosh([tensor: 2]).data-now() is-roughly [list: ~1.3169579] + tensor-acosh([tensor: 1, 5, 10, 200]).data-now() + is-roughly [list: ~0, ~2.2924315, ~2.9932229, ~5.9914584] + + tensor-acosh([tensor: 0]) + raises "Values in the input Tensor must be at least 1" + tensor-acosh([tensor: 4, 1, 10, 32, -2, 82]) + raises "Values in the input Tensor must be at least 1" + end + } @function["tensor-asin"] Computes the inverse sine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + # Check one-dimensional usages: + tensor-asin([tensor: 1]).data-now() is-roughly [list: ~1.5707963] + tensor-asin([tensor: 0]).data-now() is-roughly [list: 0] + tensor-asin([tensor: -0.5]).data-now() is-roughly [list: ~-0.5235987] + tensor-asin([tensor: 0.5, 0.2, 0.6]).data-now() + is-roughly [list: ~0.5235987, ~0.2013579, ~0.6435011] + + # Check bounding values: + tensor-asin([tensor: 9]) + raises "Values in the input Tensor must be between -1 and 1, inclusive" + tensor-asin([tensor: -1, -2, -3]) + raises "Values in the input Tensor must be between -1 and 1, inclusive" + end + } + @function["tensor-asinh"] - Computes the inverse hyperbolic sine of the @pyret-id["Tensor"], element-wise. + Computes the inverse hyperbolic sine of the @pyret-id["Tensor"], + element-wise. + + @examples{ + check: + tensor-asinh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-asinh([tensor: 1]).data-now() is-roughly [list: ~0.8813736] + tensor-asinh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-0.8813736, ~-1.4436353, ~-1.8184462] + tensor-asinh([tensor: 21, 0, 32, 2]).data-now() + is-roughly [list: ~3.7382359, ~0, ~4.1591272, ~1.4436354] + end + } @function["tensor-atan"] Computes the inverse tangent of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-atan([tensor: 0]).data-now() is-roughly [list: 0] + tensor-atan([tensor: 1]).data-now() is-roughly [list: ~0.7853981] + tensor-atan([tensor: -1]).data-now() is-roughly [list: ~-0.7853981] + tensor-atan([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-0.7853981, ~-1.1071487, ~-1.2490458] + end + } + @function["tensor-atan2"] Computes the @link["https://en.wikipedia.org/wiki/Atan2" @@ -2377,26 +2905,101 @@ @function["tensor-atanh"] - Computes the inverse hyperbolic tangent of the @pyret-id["Tensor"], element-wise. + Computes the inverse hyperbolic tangent of the @pyret-id["Tensor"], + element-wise. + + @examples{ + check: + # Check one-dimensional usages: + tensor-atanh([tensor: 0.5]).data-now() is-roughly [list: ~0.5493061] + tensor-atanh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-atanh([tensor: -0.9]).data-now() is-roughly [list: ~-1.4722193] + tensor-atanh([tensor: 0.5, 0.2, 0.6]).data-now() + is-roughly [list: ~0.5493061, ~0.2027325, ~0.6931471] + + # Check bounding values: + tensor-atanh([tensor: 1]) + raises "Values in the input Tensor must be between -1 and 1, exclusive" + tensor-atanh([tensor: -1]) + raises "Values in the input Tensor must be between -1 and 1, exclusive" + tensor-atanh([tensor: 0, 16, -1, 9, 1]) + raises "Values in the input Tensor must be between -1 and 1, exclusive" + end + } @function["tensor-ceil"] Computes the ceiling of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + # Check usages on integer tensors: + tensor-ceil([tensor: 0]).data-now() is-roughly [list: 0] + tensor-ceil([tensor: 1]).data-now() is-roughly [list: 1] + tensor-ceil([tensor: -1, -2, -3]).data-now() is-roughly [list: -1, -2, -3] + + # Check usages on float tensors: + tensor-ceil([tensor: 0.3]).data-now() is-roughly [list: 1] + tensor-ceil([tensor: 0.5]).data-now() is-roughly [list: 1] + tensor-ceil([tensor: 0.8]).data-now() is-roughly [list: 1] + tensor-ceil([tensor: -0.2]).data-now() is-roughly [list: 0] + tensor-ceil([tensor: -0.5]).data-now() is-roughly [list: 0] + tensor-ceil([tensor: -0.9]).data-now() is-roughly [list: 0] + tensor-ceil([tensor: 3.5, 5.2, 1.6]).data-now() is-roughly [list: 4, 6, 2] + end + } + @function["clip-by-value"] - Clips the values of the @pyret-id["Tensor"], element-wise, such that every element - in the resulting @pyret-id["Tensor"] is at least @pyret{min-value} and is at most - @pyret{max-value}. + Clips the values of the @pyret-id["Tensor"], element-wise, such that every + element in the resulting @pyret-id["Tensor"] is at least @pyret{min-value} + and is at most @pyret{max-value}. + + @examples{ + check: + clip-by-value([tensor: 0], 0, 0).data-now() is-roughly [list: 0] + clip-by-value([tensor: 0], -1, 1).data-now() is-roughly [list: 0] + clip-by-value([tensor: 0], 1, 4).data-now() is-roughly [list: 1] + clip-by-value([tensor: 21, 0, 32, 2], 4, 9).data-now() + is-roughly [list: 9, 4, 9, 4] + clip-by-value([tensor: 3, 9, 10, 3.24], 4.5, 9.4).data-now() + is-roughly [list: 4.5, 9, 9.4, 4.5] + + clip-by-value([tensor: 1], 10, 0) + raises "minimum value to clip to must be less than or equal to the maximum" + clip-by-value([tensor: 1], -10, -45) + raises "minimum value to clip to must be less than or equal to the maximum" + end + } @function["tensor-cos"] Computes the cosine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-cos([tensor: 0]).data-now() is-roughly [list: 1] + tensor-cos([tensor: 1]).data-now() is-roughly [list: ~0.5403115] + tensor-cos([tensor: -1]).data-now() is-roughly [list: ~0.5403116] + tensor-cos([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~0.9601798, ~-0.4161523, ~-0.6536576] + end + } + @function["tensor-cosh"] Computes the hyperbolic cosine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-cosh([tensor: 0]).data-now() is-roughly [list: 1] + tensor-cosh([tensor: 1]).data-now() is-roughly [list: ~1.5430805] + tensor-cosh([tensor: -1]).data-now() is-roughly [list: ~1.5430805] + tensor-cosh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~1.5430805, ~3.7621955, ~10.0676612] + end + } + @function["exponential-linear-units"] Applies the @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#ELUs" @@ -2408,8 +3011,8 @@ @function["gauss-error"] - Applies the @link["http://mathworld.wolfram.com/Erf.html" "gauss error function"] - to the @pyret-id["Tensor"], element-wise. + Applies the @link["http://mathworld.wolfram.com/Erf.html" + "gauss error function"] to the @pyret-id["Tensor"], element-wise. @function["erf"] @@ -2427,6 +3030,27 @@ Computes the floor of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + # Check usages on integer tensors: + tensor-floor([tensor: 0]).data-now() is-roughly [list: 0] + tensor-floor([tensor: 1]).data-now() is-roughly [list: 1] + tensor-floor([tensor: -1]).data-now() is-roughly [list: -1] + tensor-floor([tensor: -1, -2, -3]).data-now() is-roughly [list: -1, -2, -3] + + # Check usages on float tensors: + tensor-floor([tensor: 0.3]).data-now() is-roughly [list: 0] + tensor-floor([tensor: 0.5]).data-now() is-roughly [list: 0] + tensor-floor([tensor: 0.8]).data-now() is-roughly [list: 0] + tensor-floor([tensor: 0.999]).data-now() is-roughly [list: 0] + tensor-floor([tensor: 1.1]).data-now() is-roughly [list: 1] + tensor-floor([tensor: -0.2]).data-now() is-roughly [list: -1] + tensor-floor([tensor: -0.5]).data-now() is-roughly [list: -1] + tensor-floor([tensor: -0.9]).data-now() is-roughly [list: -1] + tensor-floor([tensor: 3.5, 5.2, 1.6]).data-now() is-roughly [list: 3, 5, 1] + end + } + @function["leaky-relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" @@ -2458,6 +3082,18 @@ Multiplies each element in the @pyret-id["Tensor"] by @pyret{-1}. + @examples{ + check: + tensor-negate([tensor: 0]).data-now() is-roughly [list: 0] + tensor-negate([tensor: 1]).data-now() is-roughly [list: -1] + tensor-negate([tensor: -1]).data-now() is-roughly [list: 1] + tensor-negate([tensor: -1, 2, 3, -4, 5]).data-now() + is-roughly [list: 1, -2, -3, 4, -5] + tensor-negate([tensor: -1, -2, -3, -4, -5]).data-now() + is-roughly [list: 1, 2, 3, 4, 5] + end + } + @function["parametric-relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLUs" @@ -2471,6 +3107,23 @@ Computes the reciprocal of the @pyret-id["Tensor"], element-wise; that is, it computes the equivalent of @pyret{1 / tensor}. + @examples{ + check: + tensor-reciprocal([tensor: 1]).data-now() is-roughly [list: 1] + tensor-reciprocal([tensor: -1]).data-now() is-roughly [list: -1] + tensor-reciprocal([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-1, ~-0.5, ~-0.3333333] + + # Check for division-by-zero errors: + tensor-reciprocal([tensor: 0]) + raises "The argument Tensor cannot contain 0" + tensor-reciprocal([tensor: 1, 0]) + raises "The argument Tensor cannot contain 0" + tensor-reciprocal([tensor: 7.65, 0, 1.43]) + raises "The argument Tensor cannot contain 0" + end + } + @function["relu"] Applies a @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)" @@ -2480,6 +3133,27 @@ Computes the equivalent of @pyret{num-round(tensor)}, element-wise. + Due to unavoidable rounding errors on @pyret{Roughnum}s, the behavior for + numbers ending in @pyret{.5} is inconsistent. See the examples below. + + @examples{ + check: + tensor-round([tensor: 0]).data-now() is-roughly [list: 0] + tensor-round([tensor: 1]).data-now() is-roughly [list: 1] + tensor-round([tensor: -1]).data-now() is-roughly [list: -1] + tensor-round([tensor: 0.1]).data-now() is-roughly [list: 0] + tensor-round([tensor: 0.3]).data-now() is-roughly [list: 0] + tensor-round([tensor: 0.8]).data-now() is-roughly [list: 1] + tensor-round([tensor: 0.999]).data-now() is-roughly [list: 1] + tensor-round([tensor: -1, -2, -3]).data-now() is-roughly [list: -1, -2, -3] + tensor-round([tensor: 3.5, 5.2, 1.6]).data-now() is-roughly [list: 4, 5, 2] + + # Note inconsistent behavior with rounding on Roughnums: + tensor-round([tensor: 0.5]).data-now() is-roughly [list: 0] # rounds down + tensor-round([tensor: 3.5]).data-now() is-roughly [list: 4] # rounds up + end + } + @function["reciprocal-sqrt"] Computes the recriprocal of the square root of the @pyret-id["Tensor"], @@ -2488,6 +3162,25 @@ The resulting @pyret-id["Tensor"] is roughly equivalent to @pyret{tensor-reciprocal(tensor-sqrt(tensor))}. + @examples{ + check: + reciprocal-sqrt([tensor: 1]).data-now() is-roughly [list: 1] + reciprocal-sqrt([tensor: -1]).data-now() is-roughly [list: 1] + reciprocal-sqrt([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~1, ~0.7071067, ~0.5773502] + reciprocal-sqrt([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~0.4082482, ~0.7071067, ~0.5] + + # Check for division-by-zero errors: + reciprocal-sqrt([tensor: 0]) + raises "The argument Tensor cannot contain 0" + reciprocal-sqrt([tensor: 1, 0]) + raises "The argument Tensor cannot contain 0" + reciprocal-sqrt([tensor: 7.65, 0, 1.43]) + raises "The argument Tensor cannot contain 0" + end + } + @function["scaled-elu"] Applies a scaled, exponential linear units function to the @@ -2505,14 +3198,50 @@ @pyret{~-1} if the value was negative, or @pyret{~0} if the value was zero or not a number. + @examples{ + check: + signed-ones([tensor: 0]).data-now() is-roughly [list: 0] + signed-ones([tensor: 1]).data-now() is-roughly [list: 1] + signed-ones([tensor: 3]).data-now() is-roughly [list: 1] + signed-ones([tensor: -1]).data-now() is-roughly [list: -1] + signed-ones([tensor: -5]).data-now() is-roughly [list: -1] + signed-ones([tensor: 9, -7, 5, -3, -1, 0]).data-now() + is-roughly [list: 1, -1, 1, -1, -1, 0] + end + } + @function["tensor-sin"] Computes the sine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-sin([tensor: 0]).data-now() is-roughly [list: 0] + tensor-sin([tensor: 1]).data-now() is-roughly [list: ~0.8414709] + tensor-sin([tensor: -1]).data-now() is-roughly [list: ~-0.8415220] + tensor-sin([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~-0.2794162, ~0.9092976, ~0.7568427] + tensor-sin([tensor: 21, 0, 32, 2]).data-now() + is-roughly [list: ~0.8366656, ~0, ~0.5514304, ~0.9092976] + end + } + @function["tensor-sinh"] Computes the hyperbolic sine of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-sinh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-sinh([tensor: 1]).data-now() is-roughly [list: ~1.1752011] + tensor-sinh([tensor: -1]).data-now() is-roughly [list: ~-1.1752011] + tensor-sinh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-1.1752011, ~-3.6268603, ~-10.0178737] + tensor-sinh([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~201.7131195, ~3.6268601, ~-27.2899169] + end + } + @function["softplus"] Applies the @link["https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/" @@ -2522,25 +3251,85 @@ Computes the square root of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-sqrt([tensor: 0]).data-now() is-roughly [list: 0] + tensor-sqrt([tensor: 1]).data-now() is-roughly [list: 1] + tensor-sqrt([tensor: 4]).data-now() is-roughly [list: 2] + tensor-sqrt([tensor: 9]).data-now() is-roughly [list: 3] + tensor-sqrt([tensor: 25]).data-now() is-roughly [list: 5] + + tensor-sqrt([tensor: -1]).data-now() + raises "Values in the input Tensor must be at least 0" + tensor-sqrt([tensor: 9, -7, 5, -3, -1, 0, 0.5]).data-now() + raises "Values in the input Tensor must be at least 0" + end + } + @function["tensor-square"] Computes the square of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-square([tensor: 0]).data-now() is-roughly [list: 0] + tensor-square([tensor: 1]).data-now() is-roughly [list: 1] + tensor-square([tensor: 5]).data-now() is-roughly [list: 25] + tensor-square([tensor: -1]).data-now() is-roughly [list: 1] + tensor-square([tensor: -3]).data-now() is-roughly [list: 9] + tensor-square([tensor: 9, -7, 5, -3, -1, 0, 0.5]).data-now() + is-roughly [list: 81, 49, 25, 9, 1, 0, 0.25] + end + } + @function["step"] Applies the unit step function to the @pyret-id["Tensor"], element-wise; that is, every value in the original tensor is represented in the resulting - tensor as @pyret{~0} if the value is negative; otherwise, it is represented - as @pyret{~+1}. + tensor as @pyret{~+1} if the value is positive; otherwise, it is represented + as @pyret{~0}. + + @examples{ + check: + step([tensor: 0]).data-now() is-roughly [list: 0] + step([tensor: 1]).data-now() is-roughly [list: 1] + step([tensor: 5]).data-now() is-roughly [list: 1] + step([tensor: -1]).data-now() is-roughly [list: 0] + step([tensor: -3]).data-now() is-roughly [list: 0] + step([tensor: -1, 4, 0, 0, 15, -43, 0]).data-now() + is-roughly [list: 0, 1, 0, 0, 1, 0, 0] + end + } @function["tensor-tan"] Computes the tangent of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-tan([tensor: 0]).data-now() is-roughly [list: 0] + tensor-tan([tensor: 1]).data-now() is-roughly [list: ~1.5573809] + tensor-tan([tensor: -1]).data-now() is-roughly [list: ~-1.5573809] + tensor-tan([tensor: 21, 0, 32, 2]).data-now() + is-roughly [list: ~-1.5275151, ~0, ~0.6610110, ~-2.1850113] + end + } + @function["tensor-tanh"] Computes the hyperbolic tangent of the @pyret-id["Tensor"], element-wise. + @examples{ + check: + tensor-tanh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-tanh([tensor: 1]).data-now() is-roughly [list: ~0.7615941] + tensor-tanh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-0.7615941, ~-0.9640275, ~-0.9950547] + tensor-tanh([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~0.9999876, ~0.9640275, ~-0.9993293] + end + } + @;######################################################################### @subsection{Reduction Operations} @@ -2623,14 +3412,14 @@ input-1 = [tensor: 1, 2, 3, 4] indices-1 = [tensor: 1, 3, 3] - gather(input-1, indices-1).data-now() is [list: 2, 4, 4] + gather(input-1, indices-1, none).data-now() is [list: 2, 4, 4] input-2 = [tensor: 1, 2, 3, 4].as-2d(2, 2) indices-2 = [tensor: 1, 1, 0] - gather(input-2, indices-2).data-now() is [list: 3, 4, - 3, 4, - 1, 2] + gather(input-2, indices-2, none).data-now() is [list: 3, 4, + 3, 4, + 1, 2] end } @@ -2638,8 +3427,8 @@ Reverses the values in @pyret{tensor} along the specified @pyret{axis}. - If @pyret{axis} is @pyret{none}, the function defaults to reversing along - axis 0 (the first dimension). + If @pyret{axes} is @pyret{none}, the function defaults to reversing along + all axes. @function["slice"] @@ -2654,9 +3443,44 @@ @pyret{size} is @pyret{none}, the size of all axes will be set to @pyret{-1}. @function["split"] + + Splits @pyret{tensor} into sub-@pyret-id["Tensor"]s along the specified + @pyret{axis}. + + @pyret{split-sizes} represents the sizes of each output Tensor along the + axis. The sum of the sizes in @pyret{split-sizes} must be equal to + @pyret{tensor.shape().get-value(axis)}; otherwise, an error will be raised. + + If @pyret{axis} is @pyret{none}, the operation will split along the first + dimension (axis 0) by default. + @function["stack"] + + Stacks a list of rank-@pyret{R} @pyret-id["Tensor"]s into one + rank-@pyret{(R + 1)} @pyret-id["Tensor"] along the specified @pyret{axis}. + + Every @pyret-id["Tensor"] in @pyret{tensors} must have the same shape and + data type; otherwise, the function raises an error. + + If @pyret{axis} is @pyret{none}, the operation will split along the first + dimension (axis 0) by default. + @function["tile"] + + Constructs a new @pyret-id["Tensor"] by repeating @pyret{tensor} the number + of times given by @pyret{repetitions}. Each number in @pyret{repetitions} + represents the number of replications in each dimension; that is, the first + element in the list represents the number of replications along the first + dimension, and so on. + @function["unstack"] + + Unstacks a @pyret-id["Tensor"] of rank-@pyret{R} into a @pyret{List} of + rank-@pyret{(R - 1)} @pyret-id["Tensor"]s along the specified @pyret{axis}. + + If @pyret{axis} is @pyret{none}, the operation will split along the first + dimension (axis 0) by default. + @function["strided-slice"] Extracts a strided slice of a @pyret-id["Tensor"]. From 2916ceb2cd05f7556f47a90d42e8040ce737423b Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sat, 11 Aug 2018 01:27:47 -0400 Subject: [PATCH 18/27] Reorder TensorBuffer functions so Tensor operations are located closer to the data definition --- src/trove/tensorflow.scrbl | 380 ++++++++++++++++++------------------- 1 file changed, 190 insertions(+), 190 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 3c80e63..f397110 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -2183,196 +2183,7 @@ equivalent to @pyret-id["squared-difference"]@pyret{(self, x)}. @;######################################################################### - @section{TensorBuffers} - - @type-spec["TensorBuffer"]{ - - @pyret{TensorBuffer}s are mutable objects that allow users to set values - at specific locations before converting the buffer into an immutable - @pyret-id["Tensor"]. - - } - - @function["is-tensor-buffer"] - - Returns @pyret{true} if @pyret{val} is a @pyret{TensorBuffer}; otherwise, - returns @pyret{false}. - - @examples{ - check: - is-tensor-buffer(make-buffer([list: 1])) is true - is-tensor-buffer(make-buffer([list: 8, 4, 10])) is true - is-tensor-buffer(43) is false - is-tensor-buffer("not a buffer") is false - is-tensor-buffer({some: "thing"}) is false - end - } - - @;######################################################################### - @subsection{TensorBuffer Constructors} - - @function["make-buffer"] - - Creates an @pyret{TensorBuffer} with the specified @pyret{shape}. The - returned @pyret{TensorBuffer}'s values are initialized to @pyret{~0}. - - @examples{ - check: - make-buffer([list: 1]).size() is 1 - make-buffer([list: 1]).shape() is [list: 1] - make-buffer([list: 9, 5]).size() is 45 - make-buffer([list: 9, 5]).shape() is [list: 9, 5] - - # Check for error handling of rank-0 shapes: - make-buffer(empty) raises "input shape List had zero elements" - - # Check for error handling of less than zero dimension sizes: - make-buffer([list: 0]) raises "Cannot create TensorBuffer" - make-buffer([list: -1]) raises "Cannot create TensorBuffer" - make-buffer([list: 4, 5, 0, 3]) raises "Cannot create TensorBuffer" - make-buffer([list: 2, -5, -1, 4]) raises "Cannot create TensorBuffer" - end - } - - @;######################################################################### - @subsection{TensorBuffer Methods} - - @tensor-buffer-method["size"] - - Returns the size of the @pyret{TensorBuffer} (the number of values stored - in the @pyret{TensorBuffer}). - - @examples{ - check: - make-buffer([list: 1]).size() is 1 - make-buffer([list: 4]).size() is 4 - make-buffer([list: 3, 2]).size() is 6 - make-buffer([list: 4, 4]).size() is 16 - make-buffer([list: 4, 3, 5]).size() is 60 - end - } - - @tensor-buffer-method["shape"] - - Returns a @pyret{List} representing the shape of the - @pyret{TensorBuffer}. Each element in the @pyret{List} - corresponds to the size in each dimension. - - @examples{ - check: - make-buffer([list: 1]).shape() is [list: 1] - make-buffer([list: 4, 3]).shape() is [list: 4, 3] - make-buffer([list: 2, 4, 1]).shape() is [list: 2, 4, 1] - make-buffer([list: 4, 3, 5]).shape() is [list: 4, 3, 5] - end - } - - @tensor-buffer-method["set-now"] - - Sets the value in the @pyret{TensorBuffer} at the specified @pyret{indicies} - to @pyret{value}. - - @examples{ - check: - test-buffer = make-buffer([list: 7]) - test-buffer.set-now(-45, [list: 0]) - test-buffer.set-now(9, [list: 2]) - test-buffer.set-now(0, [list: 4]) - test-buffer.set-now(-3.42, [list: 6]) - - test-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] - test-buffer.to-tensor().shape() is [list: 7] - test-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] - - # Check out-of-bounds coordinates: - test-buffer.set-now(10, [list: -1]) - raises "Coordinates must be within the bounds of the TensorBuffer's shape" - test-buffer.set-now(10, [list: 8]) - raises "Coordinates must be within the bounds of the TensorBuffer's shape" - - # Check too little coordinates: - test-buffer.set-now(10, [list:]) - raises "number of supplied coordinates must match the rank" - - # Check too many coordinates: - test-buffer.set-now(10, [list: 9, 5]) - raises "number of supplied coordinates must match the rank" - end - } - - @tensor-buffer-method["get-now"] - - Returns the value in the @pyret{TensorBuffer} at the specified - @pyret{indicies}. - - @examples{ - check: - test-buffer = make-buffer([list: 7]) - test-buffer.set-now(-45, [list: 0]) - test-buffer.set-now(9, [list: 2]) - test-buffer.set-now(0, [list: 4]) - test-buffer.set-now((4 / 3), [list: 5]) - test-buffer.set-now(-3.42, [list: 6]) - - test-buffer.get-now([list: 0]) is-roughly -45 - test-buffer.get-now([list: 1]) is-roughly 0 - test-buffer.get-now([list: 2]) is-roughly 9 - test-buffer.get-now([list: 3]) is-roughly 0 - test-buffer.get-now([list: 4]) is-roughly 0 - test-buffer.get-now([list: 5]) is-roughly (4 / 3) - test-buffer.get-now([list: 6]) is-roughly -3.42 - end - } - - @tensor-buffer-method["get-all-now"] - - Returns all values in the @pyret{TensorBuffer}. - - @examples{ - check: - one-dim-buffer = make-buffer([list: 7]) - one-dim-buffer.set-now(-45, [list: 0]) - one-dim-buffer.set-now(9, [list: 2]) - one-dim-buffer.set-now(0, [list: 4]) - one-dim-buffer.set-now((4 / 3), [list: 5]) - one-dim-buffer.set-now(-3.42, [list: 6]) - one-dim-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, (4 / 3), -3.42] - - two-dim-buffer = make-buffer([list: 2, 2]) - two-dim-buffer.set-now(4, [list: 0, 0]) - two-dim-buffer.set-now(3, [list: 0, 1]) - two-dim-buffer.set-now(2, [list: 1, 0]) - two-dim-buffer.set-now(1, [list: 1, 1]) - two-dim-buffer.get-all-now() is-roughly [list: 4, 3, 2, 1] - end - } - - @tensor-buffer-method["to-tensor"] - - Creates an immutable @pyret-id["Tensor"] from the @pyret{TensorBuffer}. - - @examples{ - check: - one-dim-buffer = make-buffer([list: 7]) - one-dim-buffer.set-now(-45, [list: 0]) - one-dim-buffer.set-now(9, [list: 2]) - one-dim-buffer.set-now(0, [list: 4]) - one-dim-buffer.set-now(-3.42, [list: 6]) - one-dim-buffer.to-tensor().shape() is [list: 7] - one-dim-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] - - two-dim-buffer = make-buffer([list: 2, 2]) - two-dim-buffer.set-now(4, [list: 0, 0]) - two-dim-buffer.set-now(3, [list: 0, 1]) - two-dim-buffer.set-now(2, [list: 1, 0]) - two-dim-buffer.set-now(1, [list: 1, 1]) - two-dim-buffer.to-tensor().shape() is [list: 2, 2] - two-dim-buffer.to-tensor().data-now() is-roughly [list: 4, 3, 2, 1] - end - } - - @;######################################################################### - @section{Operations} + @section{Tensor Operations} @;######################################################################### @subsection{Arithmetic Operations} @@ -3491,6 +3302,195 @@ the index until all dimensions are not less than @pyret{end}. Note that a stride can be negative, which causes a reverse slice. + @;######################################################################### + @section{TensorBuffers} + + @type-spec["TensorBuffer"]{ + + @pyret{TensorBuffer}s are mutable objects that allow users to set values + at specific locations before converting the buffer into an immutable + @pyret-id["Tensor"]. + + } + + @function["is-tensor-buffer"] + + Returns @pyret{true} if @pyret{val} is a @pyret{TensorBuffer}; otherwise, + returns @pyret{false}. + + @examples{ + check: + is-tensor-buffer(make-buffer([list: 1])) is true + is-tensor-buffer(make-buffer([list: 8, 4, 10])) is true + is-tensor-buffer(43) is false + is-tensor-buffer("not a buffer") is false + is-tensor-buffer({some: "thing"}) is false + end + } + + @;######################################################################### + @subsection{TensorBuffer Constructors} + + @function["make-buffer"] + + Creates an @pyret{TensorBuffer} with the specified @pyret{shape}. The + returned @pyret{TensorBuffer}'s values are initialized to @pyret{~0}. + + @examples{ + check: + make-buffer([list: 1]).size() is 1 + make-buffer([list: 1]).shape() is [list: 1] + make-buffer([list: 9, 5]).size() is 45 + make-buffer([list: 9, 5]).shape() is [list: 9, 5] + + # Check for error handling of rank-0 shapes: + make-buffer(empty) raises "input shape List had zero elements" + + # Check for error handling of less than zero dimension sizes: + make-buffer([list: 0]) raises "Cannot create TensorBuffer" + make-buffer([list: -1]) raises "Cannot create TensorBuffer" + make-buffer([list: 4, 5, 0, 3]) raises "Cannot create TensorBuffer" + make-buffer([list: 2, -5, -1, 4]) raises "Cannot create TensorBuffer" + end + } + + @;######################################################################### + @subsection{TensorBuffer Methods} + + @tensor-buffer-method["size"] + + Returns the size of the @pyret{TensorBuffer} (the number of values stored + in the @pyret{TensorBuffer}). + + @examples{ + check: + make-buffer([list: 1]).size() is 1 + make-buffer([list: 4]).size() is 4 + make-buffer([list: 3, 2]).size() is 6 + make-buffer([list: 4, 4]).size() is 16 + make-buffer([list: 4, 3, 5]).size() is 60 + end + } + + @tensor-buffer-method["shape"] + + Returns a @pyret{List} representing the shape of the + @pyret{TensorBuffer}. Each element in the @pyret{List} + corresponds to the size in each dimension. + + @examples{ + check: + make-buffer([list: 1]).shape() is [list: 1] + make-buffer([list: 4, 3]).shape() is [list: 4, 3] + make-buffer([list: 2, 4, 1]).shape() is [list: 2, 4, 1] + make-buffer([list: 4, 3, 5]).shape() is [list: 4, 3, 5] + end + } + + @tensor-buffer-method["set-now"] + + Sets the value in the @pyret{TensorBuffer} at the specified @pyret{indicies} + to @pyret{value}. + + @examples{ + check: + test-buffer = make-buffer([list: 7]) + test-buffer.set-now(-45, [list: 0]) + test-buffer.set-now(9, [list: 2]) + test-buffer.set-now(0, [list: 4]) + test-buffer.set-now(-3.42, [list: 6]) + + test-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + test-buffer.to-tensor().shape() is [list: 7] + test-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + + # Check out-of-bounds coordinates: + test-buffer.set-now(10, [list: -1]) + raises "Coordinates must be within the bounds of the TensorBuffer's shape" + test-buffer.set-now(10, [list: 8]) + raises "Coordinates must be within the bounds of the TensorBuffer's shape" + + # Check too little coordinates: + test-buffer.set-now(10, [list:]) + raises "number of supplied coordinates must match the rank" + + # Check too many coordinates: + test-buffer.set-now(10, [list: 9, 5]) + raises "number of supplied coordinates must match the rank" + end + } + + @tensor-buffer-method["get-now"] + + Returns the value in the @pyret{TensorBuffer} at the specified + @pyret{indicies}. + + @examples{ + check: + test-buffer = make-buffer([list: 7]) + test-buffer.set-now(-45, [list: 0]) + test-buffer.set-now(9, [list: 2]) + test-buffer.set-now(0, [list: 4]) + test-buffer.set-now((4 / 3), [list: 5]) + test-buffer.set-now(-3.42, [list: 6]) + + test-buffer.get-now([list: 0]) is-roughly -45 + test-buffer.get-now([list: 1]) is-roughly 0 + test-buffer.get-now([list: 2]) is-roughly 9 + test-buffer.get-now([list: 3]) is-roughly 0 + test-buffer.get-now([list: 4]) is-roughly 0 + test-buffer.get-now([list: 5]) is-roughly (4 / 3) + test-buffer.get-now([list: 6]) is-roughly -3.42 + end + } + + @tensor-buffer-method["get-all-now"] + + Returns all values in the @pyret{TensorBuffer}. + + @examples{ + check: + one-dim-buffer = make-buffer([list: 7]) + one-dim-buffer.set-now(-45, [list: 0]) + one-dim-buffer.set-now(9, [list: 2]) + one-dim-buffer.set-now(0, [list: 4]) + one-dim-buffer.set-now((4 / 3), [list: 5]) + one-dim-buffer.set-now(-3.42, [list: 6]) + one-dim-buffer.get-all-now() is-roughly [list: -45, 0, 9, 0, 0, (4 / 3), -3.42] + + two-dim-buffer = make-buffer([list: 2, 2]) + two-dim-buffer.set-now(4, [list: 0, 0]) + two-dim-buffer.set-now(3, [list: 0, 1]) + two-dim-buffer.set-now(2, [list: 1, 0]) + two-dim-buffer.set-now(1, [list: 1, 1]) + two-dim-buffer.get-all-now() is-roughly [list: 4, 3, 2, 1] + end + } + + @tensor-buffer-method["to-tensor"] + + Creates an immutable @pyret-id["Tensor"] from the @pyret{TensorBuffer}. + + @examples{ + check: + one-dim-buffer = make-buffer([list: 7]) + one-dim-buffer.set-now(-45, [list: 0]) + one-dim-buffer.set-now(9, [list: 2]) + one-dim-buffer.set-now(0, [list: 4]) + one-dim-buffer.set-now(-3.42, [list: 6]) + one-dim-buffer.to-tensor().shape() is [list: 7] + one-dim-buffer.to-tensor().data-now() is-roughly [list: -45, 0, 9, 0, 0, 0, -3.42] + + two-dim-buffer = make-buffer([list: 2, 2]) + two-dim-buffer.set-now(4, [list: 0, 0]) + two-dim-buffer.set-now(3, [list: 0, 1]) + two-dim-buffer.set-now(2, [list: 1, 0]) + two-dim-buffer.set-now(1, [list: 1, 1]) + two-dim-buffer.to-tensor().shape() is [list: 2, 2] + two-dim-buffer.to-tensor().data-now() is-roughly [list: 4, 3, 2, 1] + end + } + @;######################################################################### @section{Models} From e145b430e5767fbce3b66738d2070cbc2b60d970 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sun, 12 Aug 2018 01:48:22 -0400 Subject: [PATCH 19/27] Add LayerConfig information --- src/trove/tensorflow.scrbl | 516 +++++++++++++++++++++++++++++++++---- 1 file changed, 473 insertions(+), 43 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index f397110..9b1db42 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -7,6 +7,7 @@ @(define Sequential (a-id "Sequential" (xref "tensorflow" "Sequential"))) @(define SymbolicTensor (a-id "SymbolicTensor" (xref "tensorflow" "SymbolicTensor"))) @(define Layer (a-id "Layer" (xref "tensorflow" "Layer"))) +@(define LayerConfig (a-id "LayerConfig" (xref "tensorflow" "LayerConfig"))) @(define Optimizer (a-id "Optimizer" (xref "tensorflow" "Optimizer"))) @(define Object (a-id "Object" (xref "" "Object"))) @@ -1063,6 +1064,13 @@ (a-arrow ,Sequential ,Tensor ,Tensor ,Object (a-arrow ,N ,Object ,Nothing) ,Nothing))) ))) + (fun-spec + (name "is-symbolic-tensor") + (arity 1) + (args ("val")) + (return ,B) + (contract + (a-arrow ,A ,B))) (fun-spec (name "make-input") (arity 1) @@ -1105,273 +1113,273 @@ (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "dense-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "dropout-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "embedding-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "flatten-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "repeat-vector-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "reshape-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "conv-1d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "conv-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "conv-2d-transpose-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "cropping-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "depthwise-conv-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "separable-conv-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "up-sampling-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "add-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "average-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "concatenate-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "maximum-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "minimum-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "multiply-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "batch-normalization-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "average-pooling-1d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "average-pooling-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "global-average-pooling-1d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "global-average-pooling-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "global-max-pooling-1d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "global-max-pooling-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "max-pooling-1d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "max-pooling-2d-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "gru-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "gru-cell-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "lstm-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "lstm-cell-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "rnn-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "simple-rnn-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "simple-rnn-cell-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "stacked-rnn-cells-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "bidirectional-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (fun-spec (name "time-distributed-layer") (arity 1) (args ("config")) (return ,Layer) (contract - (a-arrow ,Object ,Layer))) + (a-arrow ,LayerConfig ,Layer))) (data-spec (name "Layer") @@ -1388,6 +1396,35 @@ (a-arrow ,Tensor ,N))) ))) + (data-spec + (name "LayerConfig") + (type-vars ()) + (variants)) + (data-spec + (name "Activation") + (type-vars ()) + (variants)) + (data-spec + (name "Initializer") + (type-vars ()) + (variants)) + (data-spec + (name "Constraint") + (type-vars ()) + (variants)) + (data-spec + (name "Regularizer") + (type-vars ()) + (variants)) + (data-spec + (name "DataFormat") + (type-vars ()) + (variants)) + (data-spec + (name "PaddingMethod") + (type-vars ()) + (variants)) + (fun-spec (name "is-optimizer") (arity 1) @@ -2188,6 +2225,30 @@ @;######################################################################### @subsection{Arithmetic Operations} + All arithmetic operations are binary operations that accept two + @pyret-id["Tensor"]s as arguments. If the size of any axis in either + @pyret-id["Tensor"] is greater than 1, the corresponding axis in the + other @pyret-id["Tensor"] must be the same size; otherwise, the operation + raises an error. + + @examples{ + # Valid operations: + add-tensors([tensor: 1], [tensor: 1]) + add-tensors([tensor: 1, 2, 3], [tensor: 1]) + add-tensors([tensor: 1, 2, 3, 4].as-2d(2, 2), [tensor: 1]) + add-tensors([tensor: 1, 2], [tensor: 1, 2, 3, 4].as-2d(2, 2)) + add-tensors([tensor: 1, 2].as-2d(2, 1), [tensor: 1, 2].as-2d(1, 2)) + add-tensors([tensor: 1, 2, 3, 4].as-2d(2, 2), [tensor: 1, 2].as-2d(2, 1)) + + # Invalid operations: + add-tensors([tensor: 1, 2, 3], [tensor: 1, 2]) + add-tensors([tensor: 1, 2].as-2d(2, 1), [tensor: 1, 2, 3].as-2d(3, 1)) + } + + In some cases, this behavior isn't intended, so most arithmetic operations + have a "strict" counterpart that raises an error if the two input + @pyret-id["Tensor"]s do not have the same shape. + @function["add-tensors"] Adds two @pyret-id["Tensor"]s element-wise, A + B. @@ -2624,6 +2685,10 @@ Computes the inverse cosine of the @pyret-id["Tensor"], element-wise. + All of the values in the input @pyret-id["Tensor"] must be between + @pyret{-1} and @pyret{1}, inclusive; otherwise, the function raises an + error. + @examples{ check: tensor-acos([tensor: 1]).data-now() is-roughly [list: 0] @@ -2644,6 +2709,9 @@ Computes the inverse hyperbolic cosine of the @pyret-id["Tensor"], element-wise. + All of the values in the input @pyret-id["Tensor"] must be greater than + or equal to @pyret{1}; otherwise, the function raises an error. + @examples{ check: tensor-acosh([tensor: 1]).data-now() is-roughly [list: 0] @@ -2662,6 +2730,10 @@ Computes the inverse sine of the @pyret-id["Tensor"], element-wise. + All of the values in the input @pyret-id["Tensor"] must be between + @pyret{-1} and @pyret{1}, inclusive; otherwise, the function raises an + error. + @examples{ check: # Check one-dimensional usages: @@ -2719,6 +2791,10 @@ Computes the inverse hyperbolic tangent of the @pyret-id["Tensor"], element-wise. + All of the values in the input @pyret-id["Tensor"] must be between + @pyret{-1} and @pyret{1}, exclusive; otherwise, the function raises an + error. + @examples{ check: # Check one-dimensional usages: @@ -2766,6 +2842,9 @@ element in the resulting @pyret-id["Tensor"] is at least @pyret{min-value} and is at most @pyret{max-value}. + @pyret{min-value} must be less than or equal to @pyret{max-value}; otherwise, + the function raises an error. + @examples{ check: clip-by-value([tensor: 0], 0, 0).data-now() is-roughly [list: 0] @@ -2918,6 +2997,9 @@ Computes the reciprocal of the @pyret-id["Tensor"], element-wise; that is, it computes the equivalent of @pyret{1 / tensor}. + In order to avoid division-by-zero errors, the input @pyret-id["Tensor"] + cannot contain @pyret{0}; otherwise, the function raises an error. + @examples{ check: tensor-reciprocal([tensor: 1]).data-now() is-roughly [list: 1] @@ -2944,8 +3026,8 @@ Computes the equivalent of @pyret{num-round(tensor)}, element-wise. - Due to unavoidable rounding errors on @pyret{Roughnum}s, the behavior for - numbers ending in @pyret{.5} is inconsistent. See the examples below. + Due to unavoidable precision errors on @pyret{Roughnum}s, the behavior for + numbers ending in @tt{.5} is inconsistent. See the examples below. @examples{ check: @@ -2973,6 +3055,9 @@ The resulting @pyret-id["Tensor"] is roughly equivalent to @pyret{tensor-reciprocal(tensor-sqrt(tensor))}. + In order to avoid division-by-zero errors, the input @pyret-id["Tensor"] + cannot contain @pyret{0}; otherwise, the function raises an error. + @examples{ check: reciprocal-sqrt([tensor: 1]).data-now() is-roughly [list: 1] @@ -3055,13 +3140,19 @@ @function["softplus"] - Applies the @link["https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/" - "softplus"] function to the @pyret-id["Tensor"], element-wise. + Applies the softplus function to the @pyret-id["Tensor"], element-wise. + + See @link["https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/" + "https://sefiks.com/2017/08/11/softplus-as-a-neural-networks-activation-function/"] + for more information. @function["tensor-sqrt"] Computes the square root of the @pyret-id["Tensor"], element-wise. + All of the values in the input @pyret-id["Tensor"] must be greater than + or equal to @pyret{0}; otherwise, the function raises an error. + @examples{ check: tensor-sqrt([tensor: 0]).data-now() is-roughly [list: 0] @@ -3516,6 +3607,9 @@ @function["is-model"] + Returns @pyret{true} if @pyret{val} is a @pyret{Model}; otherwise, returns + @pyret{false}. + @function["make-model"] @;######################################################################### @@ -3535,6 +3629,9 @@ @function["is-sequential"] + Returns @pyret{true} if @pyret{val} is a @pyret{Sequential}; otherwise, + returns @pyret{false}. + @function["make-sequential"] @sequential-method["add"] @@ -3557,6 +3654,11 @@ } + @function["is-symbolic-tensor"] + + Returns @pyret{true} if @pyret{val} is a @pyret{SymbolicTensor}; otherwise, + returns @pyret{false}. + @;######################################################################### @subsection{SymbolicTensor Constructors} @@ -3584,14 +3686,342 @@ @function["is-layer"] + Returns @pyret{true} if @pyret{val} is a @pyret{Layer}; otherwise, + returns @pyret{false}. + + @;######################################################################### + @subsection{Layer-Specific Datatypes} + + @type-spec["LayerConfig"]{ + + @tt{LayerConfig}s are used to construct @pyret-id["Layer"]s. + + A @tt{LayerConfig} is an @pyret-id["Object" ""] that describes + the properties of a @pyret-id["Layer"]. + + Every @pyret-id["Layer"] can allow for different options in the + @tt{LayerConfig} used to construct them. Those options are specified + underneath each @pyret-id["Layer"] constructor. Additionally, the + following options are permitted in every @tt{LayerConfig}: + + @itemlist[ + @item{ + @tt{input-shape :: List}. Defines the input shape for + the first layer of a model. This argument is only applicable to input + layers (the first layer of a model). Only one of @tt{input-shape} or + @tt{batch-input-shape} should be defined. + } + @item{ + @tt{batch-input-shape :: List}. Defines the batch + input shape for the first layer of a model. This argument is only + applicable to input layers (the first layer of a model). Only one of + @tt{input-shape} or @tt{batch-input-shape} should be defined. + } + @item{ + @tt{batch-size :: }@pyret-id["NumInteger" "numbers"]. If + @tt{input-shape} is specified, @tt{batch-size} is used to construct + the @tt{batch-input-shape} in the form + @pyret{[list: batch-size, ...input-shape]}. + } + @item{ + @tt{trainable :: }@pyret-id["Boolean" ""]. Whether this layer + is trainable. + } + @item{ + @tt{updatable :: }@pyret-id["Boolean" ""]. Whether the + weights of this layer are updatable by a call to + @pyret-method["Sequential" "fit"]. + } + ] + + All options allowed in a given @pyret-id["Layer"]'s @tt{LayerConfig} are + optional unless otherwise stated. + + } + + @type-spec["Activation"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow activation + function. The following strings are options: + + @itemlist[ + @item{@pyret{"elu"}} + @item{@pyret{"hardSigmoid"}} + @item{@pyret{"linear"}} + @item{@pyret{"relu"}} + @item{@pyret{"relu6"}} + @item{@pyret{"selu"}} + @item{@pyret{"sigmoid"}} + @item{@pyret{"softmax"}} + @item{@pyret{"softplus"}} + @item{@pyret{"softsign"}} + @item{@pyret{"tanh"}} + ] + + } + + @type-spec["Initializer"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow + initialization method. The following strings are options: + + @itemlist[ + @item{@pyret{"constant"}} + @item{@pyret{"glorotNormal"}} + @item{@pyret{"glorotUniform"}} + @item{@pyret{"heNormal"}} + @item{@pyret{"identity"}} + @item{@pyret{"leCunNormal"}} + @item{@pyret{"ones"}} + @item{@pyret{"orthogonal"}} + @item{@pyret{"randomNormal"}} + @item{@pyret{"randomUniform"}} + @item{@pyret{"truncatedNormal"}} + @item{@pyret{"varianceScaling"}} + @item{@pyret{"zeros"}} + ] + + } + + @type-spec["Constraint"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow + constraint function. The following strings are options: + + @itemlist[ + @item{@pyret{"maxNorm"}} + @item{@pyret{"minMaxNorm"}} + @item{@pyret{"nonNeg"}} + @item{@pyret{"unitNorm"}} + ] + + } + + @type-spec["Regularizer"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow + regularizer function. The following strings are options: + + @itemlist[ + @item{@pyret{"l1l2"}} + ] + + } + + @type-spec["DataFormat"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow + tensor data format. The following strings are options: + + @itemlist[ + @item{@pyret{"channelsFirst"}} + @item{@pyret{"channelsLast"}} + ] + + } + + @type-spec["PaddingMethod"]{ + + A @pyret-id["String" ""] that specifies a TensorFlow + padding method. The following strings are options: + + @itemlist[ + @item{@pyret{"valid"}} + @item{@pyret{"same"}} + @item{@pyret{"casual"}} + ] + + } + @;######################################################################### @subsection{Basic Layers} @function["activation-layer"] + + Applies an element-wise activation function to an output. + + Other layers, most notably @pyret-id["dense-layer"]s, can also apply + activation functions. This @pyret{Layer} can be used to extract the values + before and after the activation. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{activation :: }@pyret-id["Activation"]. Defines the activation + function to apply in this @tt{Layer}. + } + ] + @function["dense-layer"] + + Creates a dense (fully-connected) @tt{Layer}. + + This @tt{Layer} implements the operation + @pyret{output = activation(dot(input, kernel) + bias)}, where + @pyret{activation} is the element-wise activation function passed as the + @tt{activation} argument, @pyret{kernel} is a weights matrix created by the + @tt{Layer}, and @pyret{bias} is a bias vector created by the layer if the + @tt{use-bias} option is set to @pyret{true}. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{units :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} A positive integer specifying the dimensionality of the + output space. + } + @item{ + @tt{activation :: }@pyret-id["Activation"]. Defines the activation + function to apply in this @tt{Layer}. + } + @item{ + @tt{use-bias :: }@pyret-id["Boolean" ""]. Whether to apply a + bias vector. + } + @item{ + @tt{kernel-initializer :: }@pyret-id["Initializer"]. Initializer for + the dense kernel weights matrix. + } + @item{ + @tt{bias-initializer :: }@pyret-id["Initializer"]. Initializer for the + bias vector. + } + @item{ + @tt{input-dim :: }@pyret-id["NumInteger" "numbers"]. If specified, + defines @tt{input-shape} as @pyret{[list: input-dim]}. + } + @item{ + @tt{kernel-constraint :: }@pyret-id["Constraint"]. Constraint for + the kernel weights matrix. + } + @item{ + @tt{bias-constraint :: }@pyret-id["Constraint"]. Constraint for the + bias vector. + } + @item{ + @tt{kernel-regularizer :: }@pyret-id["Regularizer"]. Regularizer function + applied to the dense kernel weights matrix. + } + @item{ + @tt{bias-regularizer :: }@pyret-id["Regularizer"]. Regularizer function + applied to the bias vector. + } + @item{ + @tt{activity-regularizer :: }@pyret-id["Regularizer"]. Regularizer + function applied to the activation. + } + ] + @function["dropout-layer"] + + Applies dropout to the input. + + Dropout consists of randomly setting a fraction rate of input units to 0 at + each update during training time, which helps prevent overfitting. See + @link["http://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf" + "http://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf"] for more + information. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{rate :: }@pyret-id["Number" "numbers"]. @bold{Required parameter.} + Denotes the fraction of the input units to drop; must be between + @pyret{0} and @pyret{1}. + } + @item{ + @tt{noise-shape :: List}. Integer array representing the + shape of the binary dropout mask that will be multiplied with the input. + + For instance, if your inputs have shape + @pyret{[list: batch-size, timesteps, features]} and you want the dropout + mask to be the same for all timesteps, you can set + @tt{noise_shape} to @pyret{[list: batch-size, 1, features]}. + } + @item{ + @tt{seed :: }@pyret-id["NumInteger" "numbers"]. An integer to use as + random seed. + } + ] + @function["embedding-layer"] + + Maps positive integers (indices) into dense vectors of fixed size. + + The input shape of this layer is a two-dimensional @pyret-id["Tensor"] + with shape @pyret{[list: batch-size, sequence-length]}. + + The output shape of this layer is a three-dimensional @pyret-id["Tensor"] + with shape @pyret{[list: batch-size, sequence-length, output-dim]}. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{input-dim :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} Must also be a @pyret-id["NumPositive" "numbers"]. Denotes + the size of the vocabulary; that is, the maximum integer index + 1. + } + @item{ + @tt{output-dim :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} Must also be a @pyret-id["NumNonNegative" "numbers"]. + Dimension of the dense embedding. + } + @item{ + @tt{embeddings-initializer :: }@pyret-id["Initializer"]. Initializer for + embeddings matrix. + } + @item{ + @tt{embeddings-regularizer :: }@pyret-id["Regularizer"]. Regularizer function + applied to the embeddings matrix. + } + @item{ + @tt{activity-regularizer :: }@pyret-id["Regularizer"]. Regularizer + function applied to the activation. + } + @item{ + @tt{embeddings-constraint :: }@pyret-id["Constraint"]. Constraint + applied to the embeddings matrix. + } + @item{ + @tt{mask-zero :: }@pyret-id["Boolean" ""]. Whether the input + value @pyret{0} is a special "padding" value that should be masked out. + This is useful when using recurrent layers which may take variable + length input. + + If set to @pyret{true}, then all subsequent layers in the model need to + support masking or an exception will be raised. Additionally, if + @tt{mask-zero} is set to @pyret{true}, as a consequence, index 0 cannot + be used in the vocabulary (that is, @tt{input-dim} should equal the size + the of vocabulary + 1). + } + @item{ + @tt{input-length :: List}. Length of input sequences, when it + is constant. + + This argument is required if you are going to connect + @pyret-id["flatten-layer"]s then @pyret-id["dense-layer"]s upstream, + since otherwise the shape of the dense outputs cannot be computed. + } + ] + @function["flatten-layer"] + + Flattens the input. Does not affect the batch size. + + A @pyret-id["flatten-layer"] flattens each batch in its inputs to one + dimension (making the output two dimensional). + + The @pyret{config} passed to this constructor does not have any additional + options other than the default @pyret-id["LayerConfig"] options. + @function["repeat-vector-layer"] @function["reshape-layer"] From f3164710927a6e1424b20f9f744cac348534853c Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sun, 12 Aug 2018 19:44:27 -0400 Subject: [PATCH 20/27] Add a whole bunch of Layer documentation --- src/trove/tensorflow.scrbl | 154 +++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 5 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 9b1db42..11e1a8f 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -3998,9 +3998,9 @@ If set to @pyret{true}, then all subsequent layers in the model need to support masking or an exception will be raised. Additionally, if - @tt{mask-zero} is set to @pyret{true}, as a consequence, index 0 cannot - be used in the vocabulary (that is, @tt{input-dim} should equal the size - the of vocabulary + 1). + @tt{mask-zero} is set to @pyret{true}, as a consequence, index @pyret{0} + cannot be used in the vocabulary (that is, @tt{input-dim} should equal + the size the of vocabulary + 1). } @item{ @tt{input-length :: List}. Length of input sequences, when it @@ -4019,20 +4019,164 @@ A @pyret-id["flatten-layer"] flattens each batch in its inputs to one dimension (making the output two dimensional). - The @pyret{config} passed to this constructor does not have any additional - options other than the default @pyret-id["LayerConfig"] options. + The @pyret{config} passed to this constructor does not support any + additional options other than the default @pyret-id["LayerConfig"] options. @function["repeat-vector-layer"] + + Repeats the input @tt{num-repeats} times in a new dimension. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{num-repeats :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} Must also be a @pyret-id["NumPositive" "numbers"]. + Represents the number of times to repeat the input. + } + ] + @function["reshape-layer"] + Reshapes an input to a certain shape. + + The input shape can be arbitrary, although all dimensions in the input shape + must be fixed. + + The output shape is + @pyret{[list: batch-size, target-shape.get(0), ..., target-shape.get(i)]}. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{target-shape :: List}. The target shape; should not + include the @tt{batch-size}. + } + ] + @;######################################################################### @subsection{Convolutional Layers} @function["conv-1d-layer"] + + A one-dimensional convolution @tt{Layer}. + + This layer creates a convolution kernel that is convolved with the layer + input over a single spatial (or temporal) dimension to produce a + @pyret-id["Tensor"] of outputs. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{filters :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} The dimensionality of the output space; that is, the number + of filters in the convolution. + } + ] + @function["conv-2d-layer"] + + A two-dimensional convolution @tt{Layer}. + + This layer creates a convolution kernel that is convolved with the layer + input to produce a @pyret-id["Tensor"] of outputs. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{filters :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} The dimensionality of the output space; that is, the number + of filters in the convolution. + } + ] + @function["conv-2d-transpose-layer"] + + Transposed convolutional @tt{Layer}. This is sometimes known as a + "deconvolution" layer. + + The need for transposed convolutions generally arises from the desire to + use a transformation going in the opposite direction of a normal + convolution; for example, from something that has the shape of the output + of some convolution to something that has the shape of its input while + maintaining a connectivity pattern that is compatible with said convolution. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{filters :: }@pyret-id["NumInteger" "numbers"]. @bold{Required + parameter.} The dimensionality of the output space; that is, the number + of filters in the convolution. + } + ] + @function["cropping-2d-layer"] + + Crops an two-dimensional input at the top, bottom, left, and right side + (for example, image data). + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{cropping :: {top-crop :: NumInteger, bottom-crop :: NumInteger, + left-crop :: NumInteger, right-crop :: NumInteger}}. @bold{Required + parameter.} An @pyret-id["Object" ""] that specifies the + cropping along each side of the width and the height. + } + @item{ + @tt{data-format :: }@pyret-id["DataFormat"]. Format of the data, which + determines the ordering of the dimensions in the inputs. + } + ] + @function["depthwise-conv-2d-layer"] + + Depthwise separable two-dimensional convolution. + + A depthwise separable convolution consists of performing just the first + step in a depthwise spatial convolution (which acts on each input channel + separately). The @tt{depth-multiplier} argument controls how many output + channels are generated per input channel in the depthwise step. + + In addition to the default @pyret-id["LayerConfig"] options, the + @pyret{config} passed to this constructor can also contain: + + @itemlist[ + @item{ + @tt{kernel-size :: {width :: NumInteger, height :: NumInteger}}. + @bold{Required parameter.} An @pyret-id["Object" ""] that + specifies the width and height of the two-dimensional convolution + window. + } + @item{ + @tt{depth-multiplier :: }@pyret-id["NumInteger" "numbers"]. The number + of depthwise convolution output channels for each input channel. + } + @item{ + @tt{depthwise-initializer :: }@pyret-id["Initializer"]. Initializer for + the depthwise kernel matrix. + } + @item{ + @tt{depthwise-constraint :: }@pyret-id["Constraint"]. Constraint for + the depthwise kernel matrix. + } + @item{ + @tt{depthwise-regularizer :: }@pyret-id["Regularizer"]. Regularizer function + applied to the depthwise kernel matrix. + } + ] + @function["separable-conv-2d-layer"] @function["up-sampling-2d-layer"] From d881f1fbbe0cf3be7508eef6495e57b67a7cd33c Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Sun, 12 Aug 2018 20:19:55 -0400 Subject: [PATCH 21/27] Improve docs example programs --- src/trove/tensorflow.scrbl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 11e1a8f..e0cf344 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -4405,9 +4405,7 @@ doc: ```Used to calculate a measure of difference between the predicted Y-values and the actual Y-values``` - TF.subtract-tensors(prediction, actual-values) - ^ TF.tensor-square(_) - ^ TF.reduce-mean(_) + TF.reduce-mean(TF.tensor-square(TF.subtract-tensors(prediction, actual-values)), none) end # Train the model by creating an Optimizer. The optimizer will change any @@ -4478,10 +4476,10 @@ .add(TF.random-normal([list: num-points], some(0), some(sigma))) # Normalize the y values to the range 0 to 1: - y-min = TF.reduce-min(ys) - y-max = TF.reduce-max(ys) + y-min = TF.reduce-min(ys, none) + y-max = TF.reduce-max(ys, none) y-range = TF.subtract-tensors(y-max, y-min) - ys-normalized = TF.subtract-tensors(ys, y-min) ^ TF.divide-tensors(_, y-range) + ys-normalized = TF.divide-tensors(TF.subtract-tensors(ys, y-min), y-range) {xs: xs, ys: ys-normalized} end @@ -4497,7 +4495,7 @@ fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: TF.subtract-tensors(prediction, actual-values) ^ TF.tensor-square(_) - ^ TF.reduce-mean(_) + ^ TF.reduce-mean(_, none) end fun plot(scatter-plot :: DataSeries, a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor) block: From 0f64144254231c2e7a494205fc451f2940157d7c Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Mon, 13 Aug 2018 00:58:00 -0400 Subject: [PATCH 22/27] Add 'Trigonometry Operations' section to TF docs --- src/trove/tensorflow.scrbl | 217 +++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 107 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index e0cf344..7fdb376 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -2662,24 +2662,7 @@ } @;######################################################################### - @subsection{Basic Math Operations} - - @function["tensor-abs"] - - Computes the absolute value of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-abs([tensor: 0]).data-now() is-roughly [list: 0] - tensor-abs([tensor: 1]).data-now() is-roughly [list: 1] - tensor-abs([tensor: -1]).data-now() is-roughly [list: 1] - tensor-abs([tensor: -1, -2, -3]).data-now() is-roughly [list: 1, 2, 3] - - two-dim-abs = tensor-abs([tensor: -4, 5, -6, -7, -8, 9].as-2d(3, 2)) - two-dim-abs.shape() is [list: 3, 2] - two-dim-abs.data-now() is-roughly [list: 4, 5, 6, 7, 8, 9] - end - } + @subsection{Trigonometry Operations} @function["tensor-acos"] @@ -2814,6 +2797,115 @@ end } + @function["tensor-cos"] + + Computes the cosine of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-cos([tensor: 0]).data-now() is-roughly [list: 1] + tensor-cos([tensor: 1]).data-now() is-roughly [list: ~0.5403115] + tensor-cos([tensor: -1]).data-now() is-roughly [list: ~0.5403116] + tensor-cos([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~0.9601798, ~-0.4161523, ~-0.6536576] + end + } + + @function["tensor-cosh"] + + Computes the hyperbolic cosine of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-cosh([tensor: 0]).data-now() is-roughly [list: 1] + tensor-cosh([tensor: 1]).data-now() is-roughly [list: ~1.5430805] + tensor-cosh([tensor: -1]).data-now() is-roughly [list: ~1.5430805] + tensor-cosh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~1.5430805, ~3.7621955, ~10.0676612] + end + } + + @function["tensor-sin"] + + Computes the sine of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-sin([tensor: 0]).data-now() is-roughly [list: 0] + tensor-sin([tensor: 1]).data-now() is-roughly [list: ~0.8414709] + tensor-sin([tensor: -1]).data-now() is-roughly [list: ~-0.8415220] + tensor-sin([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~-0.2794162, ~0.9092976, ~0.7568427] + tensor-sin([tensor: 21, 0, 32, 2]).data-now() + is-roughly [list: ~0.8366656, ~0, ~0.5514304, ~0.9092976] + end + } + + @function["tensor-sinh"] + + Computes the hyperbolic sine of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-sinh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-sinh([tensor: 1]).data-now() is-roughly [list: ~1.1752011] + tensor-sinh([tensor: -1]).data-now() is-roughly [list: ~-1.1752011] + tensor-sinh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-1.1752011, ~-3.6268603, ~-10.0178737] + tensor-sinh([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~201.7131195, ~3.6268601, ~-27.2899169] + end + } + + @function["tensor-tan"] + + Computes the tangent of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-tan([tensor: 0]).data-now() is-roughly [list: 0] + tensor-tan([tensor: 1]).data-now() is-roughly [list: ~1.5573809] + tensor-tan([tensor: -1]).data-now() is-roughly [list: ~-1.5573809] + tensor-tan([tensor: 21, 0, 32, 2]).data-now() + is-roughly [list: ~-1.5275151, ~0, ~0.6610110, ~-2.1850113] + end + } + + @function["tensor-tanh"] + + Computes the hyperbolic tangent of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-tanh([tensor: 0]).data-now() is-roughly [list: 0] + tensor-tanh([tensor: 1]).data-now() is-roughly [list: ~0.7615941] + tensor-tanh([tensor: -1, -2, -3]).data-now() + is-roughly [list: ~-0.7615941, ~-0.9640275, ~-0.9950547] + tensor-tanh([tensor: 6, 2, -4]).data-now() + is-roughly [list: ~0.9999876, ~0.9640275, ~-0.9993293] + end + } + + @;######################################################################### + @subsection{Math Operations} + + @function["tensor-abs"] + + Computes the absolute value of the @pyret-id["Tensor"], element-wise. + + @examples{ + check: + tensor-abs([tensor: 0]).data-now() is-roughly [list: 0] + tensor-abs([tensor: 1]).data-now() is-roughly [list: 1] + tensor-abs([tensor: -1]).data-now() is-roughly [list: 1] + tensor-abs([tensor: -1, -2, -3]).data-now() is-roughly [list: 1, 2, 3] + + two-dim-abs = tensor-abs([tensor: -4, 5, -6, -7, -8, 9].as-2d(3, 2)) + two-dim-abs.shape() is [list: 3, 2] + two-dim-abs.data-now() is-roughly [list: 4, 5, 6, 7, 8, 9] + end + } + @function["tensor-ceil"] Computes the ceiling of the @pyret-id["Tensor"], element-wise. @@ -2862,34 +2954,6 @@ end } - @function["tensor-cos"] - - Computes the cosine of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-cos([tensor: 0]).data-now() is-roughly [list: 1] - tensor-cos([tensor: 1]).data-now() is-roughly [list: ~0.5403115] - tensor-cos([tensor: -1]).data-now() is-roughly [list: ~0.5403116] - tensor-cos([tensor: 6, 2, -4]).data-now() - is-roughly [list: ~0.9601798, ~-0.4161523, ~-0.6536576] - end - } - - @function["tensor-cosh"] - - Computes the hyperbolic cosine of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-cosh([tensor: 0]).data-now() is-roughly [list: 1] - tensor-cosh([tensor: 1]).data-now() is-roughly [list: ~1.5430805] - tensor-cosh([tensor: -1]).data-now() is-roughly [list: ~1.5430805] - tensor-cosh([tensor: -1, -2, -3]).data-now() - is-roughly [list: ~1.5430805, ~3.7621955, ~10.0676612] - end - } - @function["exponential-linear-units"] Applies the @link["https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#ELUs" @@ -3106,38 +3170,6 @@ end } - @function["tensor-sin"] - - Computes the sine of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-sin([tensor: 0]).data-now() is-roughly [list: 0] - tensor-sin([tensor: 1]).data-now() is-roughly [list: ~0.8414709] - tensor-sin([tensor: -1]).data-now() is-roughly [list: ~-0.8415220] - tensor-sin([tensor: 6, 2, -4]).data-now() - is-roughly [list: ~-0.2794162, ~0.9092976, ~0.7568427] - tensor-sin([tensor: 21, 0, 32, 2]).data-now() - is-roughly [list: ~0.8366656, ~0, ~0.5514304, ~0.9092976] - end - } - - @function["tensor-sinh"] - - Computes the hyperbolic sine of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-sinh([tensor: 0]).data-now() is-roughly [list: 0] - tensor-sinh([tensor: 1]).data-now() is-roughly [list: ~1.1752011] - tensor-sinh([tensor: -1]).data-now() is-roughly [list: ~-1.1752011] - tensor-sinh([tensor: -1, -2, -3]).data-now() - is-roughly [list: ~-1.1752011, ~-3.6268603, ~-10.0178737] - tensor-sinh([tensor: 6, 2, -4]).data-now() - is-roughly [list: ~201.7131195, ~3.6268601, ~-27.2899169] - end - } - @function["softplus"] Applies the softplus function to the @pyret-id["Tensor"], element-wise. @@ -3203,35 +3235,6 @@ end } - @function["tensor-tan"] - - Computes the tangent of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-tan([tensor: 0]).data-now() is-roughly [list: 0] - tensor-tan([tensor: 1]).data-now() is-roughly [list: ~1.5573809] - tensor-tan([tensor: -1]).data-now() is-roughly [list: ~-1.5573809] - tensor-tan([tensor: 21, 0, 32, 2]).data-now() - is-roughly [list: ~-1.5275151, ~0, ~0.6610110, ~-2.1850113] - end - } - - @function["tensor-tanh"] - - Computes the hyperbolic tangent of the @pyret-id["Tensor"], element-wise. - - @examples{ - check: - tensor-tanh([tensor: 0]).data-now() is-roughly [list: 0] - tensor-tanh([tensor: 1]).data-now() is-roughly [list: ~0.7615941] - tensor-tanh([tensor: -1, -2, -3]).data-now() - is-roughly [list: ~-0.7615941, ~-0.9640275, ~-0.9950547] - tensor-tanh([tensor: 6, 2, -4]).data-now() - is-roughly [list: ~0.9999876, ~0.9640275, ~-0.9993293] - end - } - @;######################################################################### @subsection{Reduction Operations} From c895551093484b09cfd33e9f1beb2b49ce08e1a1 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Mon, 13 Aug 2018 16:08:39 -0400 Subject: [PATCH 23/27] Rename reduction functions to fix type errors --- src/trove/tensorflow.scrbl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 7fdb376..1086de4 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -837,14 +837,14 @@ (a-arrow ,Tensor ,Tensor))) (fun-spec - (name "all") + (name "reduce-all") (arity 2) (args ("tensor" "axis")) (return ,Tensor) (contract (a-arrow ,Tensor ,(O-of N) ,Tensor))) (fun-spec - (name "any") + (name "reduce-any") (arity 2) (args ("tensor" "axis")) (return ,Tensor) @@ -3238,22 +3238,6 @@ @;######################################################################### @subsection{Reduction Operations} - @function["all"] - - Reduces the input @pyret-id["Tensor"] across all dimensions by computing the - logical "and" of its elements. - - @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises - an error. - - @function["any"] - - Reduces the input @pyret-id["Tensor"] across all dimensions by computing the - logical "or" of its elements. - - @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises - an error. - @function["arg-max"] Returns a new @pyret-id["Tensor"] where each element is the index of the maximum @@ -3270,6 +3254,22 @@ Reduces @pyret{tensor} along the outermost dimension. + @function["reduce-all"] + + Reduces the input @pyret-id["Tensor"] across all dimensions by computing the + logical "and" of its elements. + + @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises + an error. + + @function["reduce-any"] + + Reduces the input @pyret-id["Tensor"] across all dimensions by computing the + logical "or" of its elements. + + @pyret{tensor} must be of type @pyret{"bool"}; otherwise, the function raises + an error. + @function["reduce-max"] Returns a @pyret-id["Tensor"] containing a single value that is the maximum value From 84681ae2ed970500adcf98a72fc047697f9041d0 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Tue, 14 Aug 2018 17:19:39 -0400 Subject: [PATCH 24/27] Add more examples to TF docs --- src/trove/tensorflow.scrbl | 169 +++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 34 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 1086de4..e374304 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -394,7 +394,7 @@ (args ("self" "indices")) (return ,N) (contract - (a-arrow ,TensorBuffer ,(L-of NumInteger) ,N))) + (a-arrow ,TensorBuffer ,(L-of NumInteger) ,RN))) (method-spec (name "set-now") (arity 3) @@ -906,42 +906,42 @@ (args ("tensors" "axis")) (return ,Tensor) (contract - (a-arrow ,(L-of Tensor) ,(O-of N) ,Tensor))) + (a-arrow ,(L-of Tensor) ,NumInteger ,Tensor))) (fun-spec (name "gather") (arity 3) (args ("tensor" "indices" "axis")) (return ,Tensor) (contract - (a-arrow ,Tensor ,Tensor ,(O-of N) ,Tensor))) + (a-arrow ,Tensor ,Tensor ,NumInteger ,Tensor))) (fun-spec (name "reverse") (arity 2) (args ("tensor" "axes")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,(O-of (L-of NumInteger)) ,Tensor))) (fun-spec (name "slice") (arity 3 ) (args ("tensor" "begin" "size")) (return ,Tensor) (contract - (a-arrow ,Tensor ,(L-of N) ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,(L-of NumInteger) ,(O-of (L-of NumInteger)) ,Tensor))) (fun-spec (name "split") (arity 3) (args ("tensor" "split-sizes" "axis")) - (return ,Tensor) + (return ,(L-of Tensor)) (contract - (a-arrow ,Tensor ,(L-of N) ,(O-of N) ,Tensor))) + (a-arrow ,Tensor ,(L-of NumInteger) ,NumInteger ,(L-of Tensor)))) (fun-spec (name "stack") (arity 2) - (args ("tensors" "axes")) + (args ("tensors" "axis")) (return ,Tensor) (contract - (a-arrow ,(L-of Tensor) ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,(L-of Tensor) ,NumInteger ,Tensor))) (fun-spec (name "tile") (arity 2) @@ -952,10 +952,10 @@ (fun-spec (name "unstack") (arity 2) - (args ("tensor" "axes")) - (return ,Tensor) + (args ("tensor" "axis")) + (return ,(L-of Tensor)) (contract - (a-arrow ,Tensor ,(O-of (L-of N)) ,Tensor))) + (a-arrow ,Tensor ,NumInteger ,(L-of Tensor)))) (fun-spec (name "strided-slice") (arity 4) @@ -991,8 +991,7 @@ (args ("self")) (return ,N) (contract - (a-arrow ,Tensor ,N))) - ))) + (a-arrow ,Tensor ,N)))))) (fun-spec (name "is-sequential") @@ -1061,8 +1060,7 @@ (args ("self" "x" "y" "config" "epoch-callback")) (return ,Nothing) (contract - (a-arrow ,Sequential ,Tensor ,Tensor ,Object (a-arrow ,N ,Object ,Nothing) ,Nothing))) - ))) + (a-arrow ,Sequential ,Tensor ,Tensor ,Object (a-arrow ,N ,Object ,Nothing) ,Nothing)))))) (fun-spec (name "is-symbolic-tensor") @@ -1096,9 +1094,9 @@ (arity 1) (params ()) (args ("self")) - (return ,(L-of (O-of N))) + (return ,(L-of (O-of NumInteger))) (contract - (a-arrow ,SymbolicTensor ,(L-of (O-of N)))))))) + (a-arrow ,SymbolicTensor ,(L-of (O-of NumInteger)))))))) (fun-spec (name "is-layer") @@ -1393,8 +1391,7 @@ (args ("self")) (return ,N) (contract - (a-arrow ,Tensor ,N))) - ))) + (a-arrow ,Tensor ,N)))))) (data-spec (name "LayerConfig") @@ -1497,8 +1494,7 @@ (args ("self" "f", "variables")) (return ,Tensor) (contract - (a-arrow ,Optimizer (a-arrow "" ,Tensor) ,(L-of Tensor) ,Tensor))) - ))) + (a-arrow ,Optimizer (a-arrow "" ,Tensor) ,(L-of Tensor) ,Tensor)))))) )) @docmodule["tensorflow"]{ @@ -1975,7 +1971,7 @@ @pyret{Tensor} with all of the values cast to the input datatype. The possible @pyret{data-type}s are @pyret{"float32"}, @pyret{"int32"}, or - @pyret{"bool"}. Any other @pyret{dataType} will raise an error. + @pyret{"bool"}. Any other @pyret{data-type} will raise an error. @examples{ check: @@ -2134,9 +2130,9 @@ Returns a @pyret{Tensor} with dimensions of size 1 removed from the shape. - If @pyret{axis} is not @pyret{none}, the method only squeezes the dimensions - listed as indices in @pyret{axis}. The method will raise an error if one of - the dimensions specified in @pyret{axis} is not of size 1. + If @pyret{axes} is not @pyret{none}, the method only squeezes the dimensions + listed as indices in @pyret{axes}. The method will raise an error if one of + the dimensions specified in @pyret{axes} is not of size 1. @examples{ check: @@ -3298,20 +3294,31 @@ Concatenates each @pyret-id["Tensor"] in @pyret{tensors} along the given @pyret{axis}. - If @pyret{axis} is @pyret{none}, the function defaults to concatenating along - axis 0 (the first dimension). - The @pyret-id["Tensor"]s' ranks and types must match, and their sizes must match in all dimensions except @pyret{axis}. + @examples{ + check: + concatenate([list: [tensor: 1], [tensor: 2]], 0).data-now() + is-roughly [list: 1, 2] + concatenate([list: [tensor: 1, 2, 3], [tensor: 4, 5, 6]], 0).data-now() + is-roughly [list: 1, 2, 3, 4, 5, 6] + + two-dim-1 = [tensor: 1, 2, 3, 4].as-2d(2, 2) + two-dim-2 = [tensor: 5, 6, 7, 8].as-2d(2, 2) + + concatenate([list: two-dim-1, two-dim-2], 0).data-now() + is-roughly [list: 1, 2, 3, 4, 5, 6, 7, 8] + concatenate([list: two-dim-1, two-dim-2], 1).data-now() + is-roughly [list: 1, 2, 5, 6, 3, 4, 7, 8] + end + } + @function["gather"] Gathers slices from the @pyret-id["Tensor"] at every index in @pyret{indices} along the given @pyret{axis}. - If @pyret{axis} is @pyret{none}, the function defaults to gathering along - axis 0 (the first dimension). - @examples{ check: input-1 = [tensor: 1, 2, 3, 4] @@ -3335,6 +3342,26 @@ If @pyret{axes} is @pyret{none}, the function defaults to reversing along all axes. + @examples{ + check: + reverse([tensor: 0], none).data-now() + is-roughly [list: 0] + reverse([tensor: 1, 2], none).data-now() + is-roughly [list: 2, 1] + reverse([tensor: 1, 2, 3, 4, 5], none).data-now() + is-roughly [list: 5, 4, 3, 2, 1] + + two-dim = [tensor: 1, 2, 3, 4, 5, 6].as-2d(3, 2) + + reverse(two-dim, none).data-now() + is-roughly [list: 6, 5, 4, 3, 2, 1] + reverse(two-dim, some([list: 0])).data-now() + is-roughly [list: 5, 6, 3, 4, 1, 2] + reverse(two-dim, some([list: 1])).data-now() + is-roughly [list: 2, 1, 4, 3, 6, 5] + end + } + @function["slice"] Extracts a slice from @pyret{tensor} starting at the coordinates represented @@ -3347,6 +3374,32 @@ size of the rest of the axes will be implicitly set to @pyret{-1}. If @pyret{size} is @pyret{none}, the size of all axes will be set to @pyret{-1}. + @examples{ + check: + slice([tensor: 1], [list: 0], none).data-now() + is-roughly [list: 1] + slice([tensor: 1, 2, 3, 4, 5], [list: 2], none).data-now() + is-roughly [list: 3, 4, 5] + + two-dim = [tensor: 1, 2, 3, 4, 5, 6].as-2d(3, 2) + slice(two-dim, [list: 2, 1], none).data-now() + is-roughly [list: 6] + slice(two-dim, [list: 1, 0], none).data-now() + is-roughly [list: 3, 4, 5, 6] + + slice(two-dim, [list: 2], none) + raises "number of coordinates to start the slice at must be equal to the rank" + + slice(two-dim, [list: 1, 0], some([list: 2, 1])).data-now() + is-roughly [list: 3, 5] + slice(two-dim, [list: 1, 0], some([list: 1, 2])).data-now() + is-roughly [list: 3, 4] + + slice(two-dim, [list: 1, 0], some([list: 1])) + raises "dimensions for the size of the slice at must be equal to the rank" + end + } + @function["split"] Splits @pyret{tensor} into sub-@pyret-id["Tensor"]s along the specified @@ -3356,8 +3409,21 @@ axis. The sum of the sizes in @pyret{split-sizes} must be equal to @pyret{tensor.shape().get-value(axis)}; otherwise, an error will be raised. - If @pyret{axis} is @pyret{none}, the operation will split along the first - dimension (axis 0) by default. + @examples{ + check: + one-dim = split([tensor: 1, 2, 3, 4], [list: 1, 1, 2], 0) + + one-dim.length() is 3 + one-dim.get(0).data-now() is-roughly [list: 1] + one-dim.get(1).data-now() is-roughly [list: 2] + one-dim.get(2).data-now() is-roughly [list: 3, 4] + + split([tensor: 1, 2, 3, 4], [list: 1], 0) + raises "sum of split sizes must match the size of the dimension" + split([tensor: 1, 2, 3, 4], [list: 1, 1, 1, 1, 1], 0) + raises "sum of split sizes must match the size of the dimension" + end + } @function["stack"] @@ -3370,6 +3436,24 @@ If @pyret{axis} is @pyret{none}, the operation will split along the first dimension (axis 0) by default. + @examples{ + check: + stack([list: [tensor: 1]], 0).data-now() + is-roughly [list: 1] + stack([list: [tensor: 1], [tensor: 2]], 0).data-now() + is-roughly [list: 1, 2] + stack([list: [tensor: 1, 2], [tensor: 3, 4], [tensor: 5, 6]], 0).data-now() + is-roughly [list: 1, 2, 3, 4, 5, 6] + + stack(empty, 0).data-now() + raises "At least one Tensor must be supplied" + stack([list: [tensor: 1]], 1) + raises "Axis must be within the bounds of the Tensor" + stack([list: [tensor: 1], [tensor: 2, 3], [tensor: 4]], 0) + raises "All tensors passed to `stack` must have matching shapes" + end + } + @function["tile"] Constructs a new @pyret-id["Tensor"] by repeating @pyret{tensor} the number @@ -3386,6 +3470,23 @@ If @pyret{axis} is @pyret{none}, the operation will split along the first dimension (axis 0) by default. + @examples{ + check: + unstack([tensor: 1], 0).map({(x): x.data-now()}) + is-roughly [list: [list: 1]] + unstack([tensor: 1, 2], 0).map({(x): x.data-now()}) + is-roughly [list: [list: 1], [list: 2]] + unstack([tensor: 1, 2, 3, 4], 0).map({(x): x.data-now()}) + is-roughly [list: [list: 1], [list: 2], [list: 3], [list: 4]] + + unstack([tensor: 1].as-scalar(), 0) + raises "Tensor to be unstacked must be at least rank-1, but was rank-0" + + unstack([tensor: 1, 2, 3, 4], 1) + raises "axis at which to unstack the Tensor must be within the bounds" + end + } + @function["strided-slice"] Extracts a strided slice of a @pyret-id["Tensor"]. From 7354e3314fd4627c1e6120b2efa7052d553304ef Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Tue, 14 Aug 2018 19:17:19 -0400 Subject: [PATCH 25/27] Fill in remaining documentation --- src/trove/tensorflow.scrbl | 292 +++++++++++++++++++++---------------- 1 file changed, 170 insertions(+), 122 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index e374304..b9337d8 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -1508,7 +1508,7 @@ @type-spec["Tensor"]{ - @pyret{Tensor}s are the core datastructure for @pyret{tensorflow} + @tt{Tensor}s are the core datastructure for @pyret{tensorflow} applications. They are a generalization of vectors and matrices that allows for higher dimensions. @@ -1518,24 +1518,22 @@ visualize. @margin-note{ - @pyret{Tensor}s actually store values in a form slightly less precise - than @pyret{Roughnum}s. The reason for this is that TensorFlow.js (the - library that Pyret @pyret{Tensor}s are built on) stores tensor values in - JavaScript @tt{Float32Array}s for performance reasons. (But this - shouldn't substantially affect program results in most cases.) + This is because TensorFlow.js (the library that the @tt{tensorflow} + library is built on) stores @tt{Tensor} values in JavaScript + @tt{Float32Array}s for performance reasons. } - For performance reasons, @pyret{Tensor}s do not support arbitrary - precision. Retrieving values from a @pyret{Tensor} using + For performance reasons, @tt{Tensor}s do not support arbitrary + precision. Retrieving values from a @tt{Tensor} using @pyret-method["Tensor" "data-now"] always returns a @pyret{List}. - Since @pyret{Tensor}s are immutable, all operations always return new - @pyret{Tensor}s and never modify the input @pyret{Tensor}s. The exception - to this is when a @pyret{Tensor} is transformed into a mutable - @pyret{Tensor} using the @pyret-id["make-variable"] function or the + Since @tt{Tensor}s are immutable, all operations always return new + @tt{Tensor}s and never modify the input @tt{Tensor}s. The exception + to this is when a @tt{Tensor} is transformed into a mutable + @tt{Tensor} using the @pyret-id["make-variable"] function or the @pyret-method["Tensor" "to-variable"] method. These "variable tensors" - can be modified by @pyret{Optimizer}s. + can be modified by @tt{Optimizer}s. } @@ -1544,12 +1542,12 @@ @collection-doc["tensor" #:contract `(a-arrow ("value" ,N) ,Tensor)] - Creates a new @pyret{Tensor} with the given @pyret{value}s. + Creates a new @tt{Tensor} with the given @pyret{value}s. - Every @pyret{Tensor} created with this constructor is one-dimensional. Use + Every @tt{Tensor} created with this constructor is one-dimensional. Use @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], @pyret-method["Tensor" "as-3d"], @pyret-method["Tensor" "as-4d"], or - @pyret-method["Tensor" "reshape"] to change the shape of a @pyret{Tensor} + @pyret-method["Tensor" "reshape"] to change the shape of a @tt{Tensor} after instantiating it. @examples{ @@ -1560,7 +1558,7 @@ @function["is-tensor"] - Returns @pyret{true} if @pyret{val} is a @pyret{Tensor}; otherwise, returns + Returns @pyret{true} if @pyret{val} is a @tt{Tensor}; otherwise, returns @pyret{false}. @examples{ @@ -1574,13 +1572,13 @@ @function["list-to-tensor"] - Creates a new @pyret{Tensor} with the values in the input @pyret{List}. + Creates a new @tt{Tensor} with the values in the input @pyret{List}. - Similar to the @pyret-id["tensor"] constructor, all @pyret{Tensor}s created + Similar to the @pyret-id["tensor"] constructor, all @tt{Tensor}s created using @pyret-id["list-to-tensor"] are one-dimensional by default. Use @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], @pyret-method["Tensor" "as-3d"], @pyret-method["Tensor" "as-4d"], or - @pyret-method["Tensor" "reshape"] to change the shape of a @pyret{Tensor} + @pyret-method["Tensor" "reshape"] to change the shape of a @tt{Tensor} after instantiating it. @examples{ @@ -1596,7 +1594,7 @@ @function["make-scalar"] - Creates a new @pyret{Tensor} of rank-0 with the given @pyret{value}. + Creates a new @tt{Tensor} of rank-0 with the given @pyret{value}. The same functionality can be achieved with the @pyret-id["tensor"] constructor and the @pyret-method["Tensor" "as-scalar"] method, but it's @@ -1613,7 +1611,7 @@ @function["fill"] - Creates a @pyret{Tensor} with the input @pyret{shape} where all of the + Creates a @tt{Tensor} with the input @pyret{shape} where all of the entries are @pyret{value}. @examples{ @@ -1629,9 +1627,9 @@ @function["linspace"] - Returns a @pyret{Tensor} whose values are an evenly spaced sequence of + Returns a @tt{Tensor} whose values are an evenly spaced sequence of numbers over the range @pyret{[start, stop]}. @pyret{num-values} is the - number of entries in the output @pyret{Tensor}. + number of entries in the output @tt{Tensor}. @examples{ check: @@ -1650,7 +1648,7 @@ @function["ones"] - Returns a @pyret{Tensor} with the given @pyret{shape} where all of the + Returns a @tt{Tensor} with the given @pyret{shape} where all of the entries are ones. @examples{ @@ -1665,7 +1663,7 @@ @function["zeros"] - Returns a @pyret{Tensor} with the given @pyret{shape} where all of the + Returns a @tt{Tensor} with the given @pyret{shape} where all of the entries are zeros. @examples{ @@ -1680,10 +1678,10 @@ @function["multinomial"] - Creates a new @pyret{Tensor} where all of the values are sampled from a + Creates a new @tt{Tensor} where all of the values are sampled from a multinomial distribution. - @pyret{logits} should be a @pyret{Tensor} representing a one-dimensional + @pyret{logits} should be a @tt{Tensor} representing a one-dimensional array containing with unnormalized log-probabilities, or a two-dimensional array of structure @pyret{[batch-size, num-outcomes]}. @@ -1712,7 +1710,7 @@ @function["random-normal"] - Creates a new @pyret{Tensor} with the given shape (represented as values in + Creates a new @tt{Tensor} with the given shape (represented as values in the input @pyret{List shape}) where all of the values are sampled from a normal distribution. @@ -1732,7 +1730,7 @@ @function["random-uniform"] - Creates a new @pyret{Tensor} with the given shape (represented as values in + Creates a new @tt{Tensor} with the given shape (represented as values in the input @pyret{List}) where all of the values are sampled from a uniform distribution. @@ -1759,8 +1757,8 @@ @function["make-variable"] - Creates a new, mutable @pyret{Tensor} initialized to the values of the input - @pyret{Tensor}. + Creates a new, mutable @tt{Tensor} initialized to the values of the input + @tt{Tensor}. The same functionality can be achieved with the @pyret-method["Tensor" "to-variable"] method. @@ -1784,8 +1782,8 @@ @tensor-method["size"] - Returns the size of the @pyret{Tensor} (the number of values stored in the - @pyret{Tensor}). + Returns the size of the @tt{Tensor} (the number of values stored in the + @tt{Tensor}). @examples{ check: @@ -1799,7 +1797,7 @@ @tensor-method["shape"] Returns a @pyret{List} representing the shape of the - @pyret{Tensor}. Each element in the @pyret{List} corresponds + @tt{Tensor}. Each element in the @pyret{List} corresponds to the size in each dimension. @examples{ @@ -1813,8 +1811,8 @@ @tensor-method["flatten"] - Constructs a new, one-dimensional @pyret{Tensor} from the values of the - original @pyret{Tensor}. + Constructs a new, one-dimensional @tt{Tensor} from the values of the + original @tt{Tensor}. @examples{ check: @@ -1830,10 +1828,10 @@ @tensor-method["as-scalar"] - Constructs a new, zero-dimensional @pyret{Tensor} from the values of the - original, size-1 @pyret{Tensor}. + Constructs a new, zero-dimensional @tt{Tensor} from the values of the + original, size-1 @tt{Tensor}. - Raises an error if the calling @pyret{Tensor} is not size-1. + Raises an error if the calling @tt{Tensor} is not size-1. @examples{ check: @@ -1849,8 +1847,8 @@ @tensor-method["as-1d"] - Constructs a new, rank-1 @pyret{Tensor} from the values of the original - @pyret{Tensor}. + Constructs a new, rank-1 @tt{Tensor} from the values of the original + @tt{Tensor}. The same functionality can be achieved with @pyret-method["Tensor" "reshape"], but it's recommended to use @pyret-method["Tensor" "as-1d"] as it makes the @@ -1875,11 +1873,11 @@ @tensor-method["as-2d"] - Constructs a new, rank-2 @pyret{Tensor} with the input dimensions from the - values of the original @pyret{Tensor}. + Constructs a new, rank-2 @tt{Tensor} with the input dimensions from the + values of the original @tt{Tensor}. The number of elements implied by the input dimensions must be the same as the - number of elements in the calling @pyret{Tensor}. Otherwise, the method + number of elements in the calling @tt{Tensor}. Otherwise, the method raises an error. The same functionality can be achieved with @pyret-method["Tensor" "reshape"], @@ -1909,11 +1907,11 @@ @tensor-method["as-3d"] - Constructs a new, rank-3 @pyret{Tensor} with the input dimensions from the - values of the original @pyret{Tensor}. + Constructs a new, rank-3 @tt{Tensor} with the input dimensions from the + values of the original @tt{Tensor}. The number of elements implied by the input dimensions must be the same as the - number of elements in the calling @pyret{Tensor}. Otherwise, the method + number of elements in the calling @tt{Tensor}. Otherwise, the method raises an error. The same functionality can be achieved with @pyret-method["Tensor" "reshape"], @@ -1938,11 +1936,11 @@ @tensor-method["as-4d"] - Constructs a new, rank-4 @pyret{Tensor} with the input dimensions from the - values of the original @pyret{Tensor}. + Constructs a new, rank-4 @tt{Tensor} with the input dimensions from the + values of the original @tt{Tensor}. The number of elements implied by the input dimensions must be the same as the - number of elements in the calling @pyret{Tensor}. Otherwise, the method + number of elements in the calling @tt{Tensor}. Otherwise, the method raises an error. The same functionality can be achieved with @pyret-method["Tensor" "reshape"], @@ -1967,8 +1965,8 @@ @tensor-method["as-type"] - Constructs a new @pyret{Tensor} from the values of the original - @pyret{Tensor} with all of the values cast to the input datatype. + Constructs a new @tt{Tensor} from the values of the original + @tt{Tensor} with all of the values cast to the input datatype. The possible @pyret{data-type}s are @pyret{"float32"}, @pyret{"int32"}, or @pyret{"bool"}. Any other @pyret{data-type} will raise an error. @@ -1987,7 +1985,7 @@ @tensor-method["data-now"] - Returns a @pyret{List} containing the data in the @pyret{Tensor}. + Returns a @pyret{List} containing the data in the @tt{Tensor}. @examples{ check: @@ -2000,8 +1998,8 @@ @tensor-method["to-float"] - Constructs a new @pyret{Tensor} from the values of the original - @pyret{Tensor} with all of the values cast to the @tt{"float32"} datatype. + Constructs a new @tt{Tensor} from the values of the original + @tt{Tensor} with all of the values cast to the @tt{"float32"} datatype. @examples{ check: @@ -2015,8 +2013,8 @@ @tensor-method["to-int"] - Constructs a new @pyret{Tensor} from the values of the original - @pyret{Tensor} with all of the values cast to the @tt{"int32"} datatype. + Constructs a new @tt{Tensor} from the values of the original + @tt{Tensor} with all of the values cast to the @tt{"int32"} datatype. @examples{ check: @@ -2030,8 +2028,8 @@ @tensor-method["to-bool"] - Constructs a new @pyret{Tensor} from the values of the original - @pyret{Tensor} with all of the values cast to the @tt{"bool"} datatype. + Constructs a new @tt{Tensor} from the values of the original + @tt{Tensor} with all of the values cast to the @tt{"bool"} datatype. @examples{ check: @@ -2045,7 +2043,7 @@ @tensor-method["to-buffer"] Constructs a new @pyret-id["TensorBuffer"] from the values of the original - @pyret{Tensor}. + @tt{Tensor}. @examples{ check: @@ -2065,9 +2063,9 @@ @tensor-method["to-variable"] - Constructs a new, mutable @pyret{Tensor} from the values of the original - @pyret{Tensor}. Equivalent to applying @pyret-id["make-variable"] on the - calling @pyret{Tensor}. + Constructs a new, mutable @tt{Tensor} from the values of the original + @tt{Tensor}. Equivalent to applying @pyret-id["make-variable"] on the + calling @tt{Tensor}. @examples{ check: @@ -2079,14 +2077,14 @@ @tensor-method["reshape"] - Constructs a new @pyret{Tensor} with the input dimensions @pyret{new-shape} - from the values of the original @pyret{Tensor}. + Constructs a new @tt{Tensor} with the input dimensions @pyret{new-shape} + from the values of the original @tt{Tensor}. The number of elements implied by @pyret{new-shape} must be the same as the - number of elements in the calling @pyret{Tensor}. Otherwise, the method + number of elements in the calling @tt{Tensor}. Otherwise, the method raises an error. - When reshaping a @pyret{Tensor} to be 0-, 1-, 2-, 3-, or 4-dimensional, + When reshaping a @tt{Tensor} to be 0-, 1-, 2-, 3-, or 4-dimensional, it's recommended to use @pyret-method["Tensor" "as-scalar"], @pyret-method["Tensor" "as-1d"], @pyret-method["Tensor" "as-2d"], @pyret-method["Tensor" "as-3d"], or @pyret-method["Tensor" "as-4d"] as @@ -2109,8 +2107,8 @@ @tensor-method["expand-dims"] - Returns a @pyret{Tensor} that has expanded rank, by inserting a dimension - into the @pyret{Tensor}'s shape at the given dimension index @pyret{axis}. + Returns a @tt{Tensor} that has expanded rank, by inserting a dimension + into the @tt{Tensor}'s shape at the given dimension index @pyret{axis}. If @pyret{axis} is @pyret{none}, the method inserts a dimension at index 0 by default. @@ -2128,7 +2126,7 @@ @tensor-method["squeeze"] - Returns a @pyret{Tensor} with dimensions of size 1 removed from the shape. + Returns a @tt{Tensor} with dimensions of size 1 removed from the shape. If @pyret{axes} is not @pyret{none}, the method only squeezes the dimensions listed as indices in @pyret{axes}. The method will raise an error if one of @@ -2152,7 +2150,7 @@ @tensor-method["clone"] - Constructs a new @pyret{Tensor} that is a copy of the original @pyret{Tensor}. + Constructs a new @tt{Tensor} that is a copy of the original @tt{Tensor}. @examples{ check: @@ -2166,48 +2164,48 @@ @tensor-method["add"] - Adds @pyret{x} to the @pyret{Tensor}. This is equivalent to + Adds @pyret{x} to the @tt{Tensor}. This is equivalent to @pyret-id["add-tensors"]@pyret{(self, x)}. @tensor-method["subtract"] - Subtracts @pyret{x} from the @pyret{Tensor}. This is equivalent to + Subtracts @pyret{x} from the @tt{Tensor}. This is equivalent to @pyret-id["subtract-tensors"]@pyret{(self, x)}. @tensor-method["multiply"] - Multiplies the @pyret{Tensor} by @pyret{x}. This is equivalent to + Multiplies the @tt{Tensor} by @pyret{x}. This is equivalent to @pyret-id["multiply-tensors"]@pyret{(self, x)}. @tensor-method["divide"] - Divides the @pyret{Tensor} by @pyret{x}. This is equivalent to + Divides the @tt{Tensor} by @pyret{x}. This is equivalent to @pyret-id["divide-tensors"]@pyret{(self, x)}. @tensor-method["floor-divide"] - Divides the @pyret{Tensor} by @pyret{x}, with the result rounded + Divides the @tt{Tensor} by @pyret{x}, with the result rounded with the floor function. This is equivalent to @pyret-id["floor-divide-tensors"]@pyret{(self, x)}. @tensor-method["max"] - Returns the maximum of the @pyret{Tensor} and @pyret{x}. This is equivalent to + Returns the maximum of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-max"]@pyret{(self, x)}. @tensor-method["min"] - Returns the minimum of the @pyret{Tensor} and @pyret{x}. This is equivalent to + Returns the minimum of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-min"]@pyret{(self, x)}. @tensor-method["modulo"] - Computes the modulo of the @pyret{Tensor} and @pyret{x}. This is equivalent to + Computes the modulo of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-modulo"]@pyret{(self, x)}. @tensor-method["expt"] - Computes the power of the @pyret{Tensor} to @pyret{exponent}. This is + Computes the power of the @tt{Tensor} to @pyret{exponent}. This is equivalent to @pyret-id["tensor-expt"]@pyret{(self, x)}. @tensor-method["squared-difference"] @@ -3502,7 +3500,7 @@ @type-spec["TensorBuffer"]{ - @pyret{TensorBuffer}s are mutable objects that allow users to set values + @tt{TensorBuffer}s are mutable objects that allow users to set values at specific locations before converting the buffer into an immutable @pyret-id["Tensor"]. @@ -3510,7 +3508,7 @@ @function["is-tensor-buffer"] - Returns @pyret{true} if @pyret{val} is a @pyret{TensorBuffer}; otherwise, + Returns @pyret{true} if @pyret{val} is a @tt{TensorBuffer}; otherwise, returns @pyret{false}. @examples{ @@ -3528,8 +3526,8 @@ @function["make-buffer"] - Creates an @pyret{TensorBuffer} with the specified @pyret{shape}. The - returned @pyret{TensorBuffer}'s values are initialized to @pyret{~0}. + Creates an @tt{TensorBuffer} with the specified @pyret{shape}. The + returned @tt{TensorBuffer}'s values are initialized to @pyret{~0}. @examples{ check: @@ -3554,8 +3552,8 @@ @tensor-buffer-method["size"] - Returns the size of the @pyret{TensorBuffer} (the number of values stored - in the @pyret{TensorBuffer}). + Returns the size of the @tt{TensorBuffer} (the number of values stored + in the @tt{TensorBuffer}). @examples{ check: @@ -3570,7 +3568,7 @@ @tensor-buffer-method["shape"] Returns a @pyret{List} representing the shape of the - @pyret{TensorBuffer}. Each element in the @pyret{List} + @tt{TensorBuffer}. Each element in the @pyret{List} corresponds to the size in each dimension. @examples{ @@ -3584,7 +3582,7 @@ @tensor-buffer-method["set-now"] - Sets the value in the @pyret{TensorBuffer} at the specified @pyret{indicies} + Sets the value in the @tt{TensorBuffer} at the specified @pyret{indicies} to @pyret{value}. @examples{ @@ -3617,7 +3615,7 @@ @tensor-buffer-method["get-now"] - Returns the value in the @pyret{TensorBuffer} at the specified + Returns the value in the @tt{TensorBuffer} at the specified @pyret{indicies}. @examples{ @@ -3641,7 +3639,7 @@ @tensor-buffer-method["get-all-now"] - Returns all values in the @pyret{TensorBuffer}. + Returns all values in the @tt{TensorBuffer}. @examples{ check: @@ -3664,7 +3662,7 @@ @tensor-buffer-method["to-tensor"] - Creates an immutable @pyret-id["Tensor"] from the @pyret{TensorBuffer}. + Creates an immutable @pyret-id["Tensor"] from the @tt{TensorBuffer}. @examples{ check: @@ -3689,7 +3687,7 @@ @;######################################################################### @section{Models} - @pyret{Model}s represent a collection of @pyret-id["Layer"]s, and define a + @tt{Model}s represent a collection of @pyret-id["Layer"]s, and define a series of inputs and outputs. They are one of the primary abstractions used in TensorFlow, and can be trained, evaluated, and used for prediction. @@ -3703,7 +3701,7 @@ @type-spec["Model"]{ - A @pyret{Model} is a data structure that consists of @pyret-id["Layer"]s and + A @tt{Model} is a data structure that consists of @pyret-id["Layer"]s and defines inputs and outputs. It is more generic than @pyret-id["Sequential"] models as it supports arbitrary, non-cyclic graphs of @pyret-id["Layer"]s. @@ -3711,46 +3709,80 @@ @function["is-model"] - Returns @pyret{true} if @pyret{val} is a @pyret{Model}; otherwise, returns + Returns @pyret{true} if @pyret{val} is a @tt{Model}; otherwise, returns @pyret{false}. @function["make-model"] + Creates a new generic @tt{Model}. + @;######################################################################### @subsection{Sequential Models} @type-spec["Sequential"]{ - A @pyret{Sequential} model is a model where the outputs of one + A @tt{Sequential} model is a model where the outputs of one @pyret-id["Layer"] are the inputs to the next @pyret-id["Layer"]. That is, the model topology is a simple "stack" of layers, with no branching or skipping. - As a result, the first layer passed to a @pyret{Sequential} model must - have a defined input shape. + As a result, the first @pyret-id["Layer"] passed to a @tt{Sequential} model + must have a defined input shape. This means that the + @pyret-id["LayerConfig"] used to instantiate the first @pyret-id["Layer"] + must have a defined @tt{input-shape} or @tt{batch-input-shape} parameter. } @function["is-sequential"] - Returns @pyret{true} if @pyret{val} is a @pyret{Sequential}; otherwise, + Returns @pyret{true} if @pyret{val} is a @tt{Sequential}; otherwise, returns @pyret{false}. @function["make-sequential"] + Creates a new @tt{Sequential} model. + @sequential-method["add"] + + Adds a @pyret-id["Layer"] on top of the @tt{Sequential}'s stack. + @sequential-method["compile"] + + Configures and prepares the @tt{Sequential} model for training and + evaluation. + + Compiling outfits the @tt{Sequential} with an optimizer, loss, and/or + metrics. Calling @pyret-method["Sequential" "fit"] or + Calling @pyret-method["Sequential" "evaluate"] on an un-compiled model will + raise an error. + @sequential-method["evaluate"] + + Returns the loss value & metrics values for the model in test mode. + + Loss and metrics parameters should be specified in a call to + @pyret-method["Sequential" "compile"] before calling this method. + @sequential-method["predict"] + + Generates output predictions for the input samples. + + Computation is done in batches. + @sequential-method["predict-on-batch"] + + Returns predictions for a single batch of samples. + @sequential-method["fit"] + Trains the model for a fixed number of epochs (iterations on a dataset). + @;######################################################################### @section{SymbolicTensors} @type-spec["SymbolicTensor"]{ - @pyret{SymbolicTensor}s are placeholders for @pyret-id["Tensor"]s without + @tt{SymbolicTensor}s are placeholders for @pyret-id["Tensor"]s without any concrete value. They are most often encountered when building a graph of @pyret-id["Layer"]s @@ -3760,37 +3792,53 @@ @function["is-symbolic-tensor"] - Returns @pyret{true} if @pyret{val} is a @pyret{SymbolicTensor}; otherwise, + Returns @pyret{true} if @pyret{val} is a @tt{SymbolicTensor}; otherwise, returns @pyret{false}. @;######################################################################### @subsection{SymbolicTensor Constructors} @function["make-input"] + + Creates a new @tt{SymbolicTensor} with the input shape, not including the + batch size. + + @pyret{none} values in the input @pyret{List} represent dimensions of + arbitrary length. + @function["make-batch-input"] + Creates a new @tt{SymbolicTensor} with the input shape, where the first + element in the input @pyret{List} is the batch size. + + @pyret{none} values in the input @pyret{List} represent dimensions of + arbitrary length. + @;######################################################################### @subsection{SymbolicTensor Methods} @symbolic-tensor-method["shape"] + Returns the shape of the @tt{SymbolicTensor}. @pyret{none} values in the + output @pyret{List} represent dimensions of arbitrary length. + @;######################################################################### @section{Layers} @type-spec["Layer"]{ - @pyret{Layer}s are the primary building block for constructing a - @pyret-id["Model"]. Each @pyret{Layer} will typically perform some + @tt{Layer}s are the primary building block for constructing a + @pyret-id["Model"]. Each @tt{Layer} will typically perform some computation to transform its input to its output. - @pyret{Layer}s will automatically take care of creating and initializing + @tt{Layer}s will automatically take care of creating and initializing the various internal variables/weights they need to function. } @function["is-layer"] - Returns @pyret{true} if @pyret{val} is a @pyret{Layer}; otherwise, + Returns @pyret{true} if @pyret{val} is a @tt{Layer}; otherwise, returns @pyret{false}. @;######################################################################### @@ -3945,7 +3993,7 @@ Applies an element-wise activation function to an output. Other layers, most notably @pyret-id["dense-layer"]s, can also apply - activation functions. This @pyret{Layer} can be used to extract the values + activation functions. This @tt{Layer} can be used to extract the values before and after the activation. In addition to the default @pyret-id["LayerConfig"] options, the @@ -4334,36 +4382,36 @@ @type-spec["Optimizer"]{ - @pyret{Optimizer}s are used to perform training operations and compute + @tt{Optimizer}s are used to perform training operations and compute gradients. - @pyret{Optimizer}s eagerly compute gradients. This means that when a user + @tt{Optimizer}s eagerly compute gradients. This means that when a user provides a function that is a combination of TensorFlow operations - to an @pyret{Optimizer}, the @pyret{Optimizer} automatically differentiates + to an @tt{Optimizer}, the @tt{Optimizer} automatically differentiates that function's output with respect to its inputs. } @function["is-optimizer"] - Returns @pyret{true} if @pyret{val} is an @pyret{Optimizer}; otherwise, + Returns @pyret{true} if @pyret{val} is an @tt{Optimizer}; otherwise, returns @pyret{false}. @;######################################################################### @subsection{Optimizer Constructors} - There are many different types of @pyret{Optimizer}s that use different + There are many different types of @tt{Optimizer}s that use different formulas to compute gradients. @function["train-sgd"] - Constructs an @pyret{Optimizer} that uses a stochastic gradient descent + Constructs an @tt{Optimizer} that uses a stochastic gradient descent algorithm, where @pyret{learning-rate} is the learning rate to use for the algorithm. @function["train-momentum"] - Constructs an @pyret{Optimizer} that uses a momentum gradient descent + Constructs an @tt{Optimizer} that uses a momentum gradient descent algorithm, where @pyret{learning-rate} is the learning rate to use for the algorithm and @pyret{momentum} is the momentum to use for the algorithm. @@ -4372,7 +4420,7 @@ @function["train-adagrad"] - Constructs an @pyret{Optimizer} that uses the Adagrad algorithm, where + Constructs an @tt{Optimizer} that uses the Adagrad algorithm, where @pyret{learning-rate} is the learning rate to use for the Adagrad gradient descent algorithm. @@ -4388,7 +4436,7 @@ @function["train-adadelta"] - Constructs an @pyret{Optimizer} that uses the Adadelta algorithm. + Constructs an @tt{Optimizer} that uses the Adadelta algorithm. If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for the Adamax gradient descent algorithm, @pyret{rho} is the learning rate @@ -4399,7 +4447,7 @@ @function["train-adam"] - Constructs an @pyret{Optimizer} that uses the Adam algorithm. + Constructs an @tt{Optimizer} that uses the Adam algorithm. If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for the Adamax gradient descent algorithm, @pyret{beta-1} is the exponential @@ -4411,7 +4459,7 @@ @function["train-adamax"] - Constructs an @pyret{Optimizer} that uses the Adamax algorithm. + Constructs an @tt{Optimizer} that uses the Adamax algorithm. If not @pyret{none}, @pyret{learning-rate} is the learning rate to use for the Adamax gradient descent algorithm, @pyret{beta-1} is the exponential @@ -4424,7 +4472,7 @@ @function["train-rmsprop"] - Constructs an @pyret{Optimizer} that uses RMSProp gradient descent, where + Constructs an @tt{Optimizer} that uses RMSProp gradient descent, where @pyret{learning-rate} is the learning rate to use for the RMSProp gradient descent algorithm. @@ -4449,12 +4497,12 @@ Executes @pyret{f} and minimizes the scalar output of @pyret{f} by computing gradients of @pyret{y} with with respect to the list of trainable, variable - @pyret{Tensor}s provided by @pyret{variables}. + @tt{Tensor}s provided by @pyret{variables}. - @pyret{f} must be a thunk that returns a scalar @pyret{Tensor}. - The method then returns the scalar @pyret{Tensor} produced by @pyret{f}. + @pyret{f} must be a thunk that returns a scalar @tt{Tensor}. + The method then returns the scalar @tt{Tensor} produced by @pyret{f}. - If @pyret{variables} is @pyret{empty}, the @pyret{Optimizer} will default + If @pyret{variables} is @pyret{empty}, the @tt{Optimizer} will default to training all trainable variables that have been instantiated. @;######################################################################### From a7d3ad63417e50fba41f3825634bec29649f0ab1 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Tue, 14 Aug 2018 19:19:17 -0400 Subject: [PATCH 26/27] Remove example programs --- src/trove/tensorflow.scrbl | 197 ------------------------------------- 1 file changed, 197 deletions(-) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index b9337d8..87b53a9 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -4505,201 +4505,4 @@ If @pyret{variables} is @pyret{empty}, the @tt{Optimizer} will default to training all trainable variables that have been instantiated. - @;######################################################################### - @section{Usage Examples} - - The below program demonstrates how to use @pyret{tensorflow} to perform - linear regression operations on a dataset. - - @examples{ - import tensorflow as TF - import chart as C - import image as I - import lists as L - - type Tensor = TF.Tensor - type Optimizer = TF.Optimizer - type ChartWindow = C.ChartWindow - type Image = I.Image - - # Create a tiny helper function: - fun positive-rand() -> Number: - doc: "Generates a positive Number between 0 and 1" - num-random(10000000) / 10000000 - end - - # `train-x` and `train-y` represent random points in a dataset, plotted - # on `scatter-plot`: - train-x = [list: - 3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, - 10.791, 5.313, 7.997, 5.654, 9.27, 3.1] - - train-y = [list: - 1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, - 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3] - - scatter-plot = C.from-list.scatter-plot(train-x, train-y) - - # Create two scalar Tensors `m` and `b` that are variables: - m = TF.make-scalar(positive-rand()).to-variable() - b = TF.make-scalar(positive-rand()).to-variable() - - # Setup a few helper functions before training: - fun predict(x :: Tensor) -> Tensor: - doc: ```Uses the current values of m and b to predict what Y-values will - be generated given a Tensor `x` representing X-values``` - - temp = TF.multiply-tensors(m, x) - TF.add-tensors(temp, b) - end - - fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: - doc: ```Used to calculate a measure of difference between the predicted - Y-values and the actual Y-values``` - - TF.reduce-mean(TF.tensor-square(TF.subtract-tensors(prediction, actual-values)), none) - end - - # Train the model by creating an Optimizer. The optimizer will change any - # variable tensors used in the function passed into it in an attempt to - # minimize the returned loss: - fun train(): - doc: "Trains the model" - learning-rate = 0.005 - optimizer = TF.train-sgd(learning-rate) - - optimizer.minimize(lam() block: - prediction = predict(TF.list-to-tensor(train-x).as-1d()) - step-loss = loss(prediction, TF.list-to-tensor(train-y).as-1d()) - step-loss - end, empty) - end - - fun plot() -> ChartWindow: - doc: "Plots the current mx + b function and overlays it on the scatter plot" - shadow m = m.data-now().first - shadow b = b.data-now().first - - function-plot = C.from-list.function-plot(lam(x): (m * x) + b end) - C.render-charts([list: scatter-plot, function-plot]) - end - - fun train-steps(steps :: Number) -> Image block: - doc: "Trains the model `steps` times" - for L.each(_ from L.range(0, steps)) block: - train() - print("y = " + num-to-string(m.data-now().first) + "x + " + num-to-string(b.data-now().first)) - end - plot().get-image() - end - } - - The below program demonstrates how to use @pyret{tensorflow} to fit a cubic - function to synthetic data using the stochastic gradient descent algorithm. - - Program based on @link["https://js.tensorflow.org/tutorials/fit-curve.html" - "https://js.tensorflow.org/tutorials/fit-curve.html"]. - - @examples{ - import tensorflow as TF - import chart as C - import image as I - - type Tensor = TF.Tensor - type DataSeries = C.DataSeries - - fun positive-rand() -> Number: - num-random(10000000) / 10000000 - end - - fun generate-data(num-points :: Number, coefficients :: Object, sigma :: Number) -> Object: - a = TF.make-scalar(coefficients.a) - b = TF.make-scalar(coefficients.b) - c = TF.make-scalar(coefficients.c) - d = TF.make-scalar(coefficients.d) - - xs = TF.random-uniform([list: num-points], some(-1), some(1)) - - # The below represents ax^3 + bx^2 + cx + d: - ys = a.multiply(xs.expt(TF.make-scalar(3))) - .add(b.multiply(TF.tensor-square(xs))) - .add(c.multiply(xs)) - .add(d) - .add(TF.random-normal([list: num-points], some(0), some(sigma))) - - # Normalize the y values to the range 0 to 1: - y-min = TF.reduce-min(ys, none) - y-max = TF.reduce-max(ys, none) - y-range = TF.subtract-tensors(y-max, y-min) - ys-normalized = TF.divide-tensors(TF.subtract-tensors(ys, y-min), y-range) - - {xs: xs, ys: ys-normalized} - end - - fun predict(a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor, x :: Tensor) -> Tensor: - # The below represents ax^3 + bx^2 + cx + d: - a.multiply(x.expt(TF.make-scalar(3))) - .add(b.multiply(TF.tensor-square(x))) - .add(c.multiply(x)) - .add(d) - end - - fun loss(prediction :: Tensor, actual-values :: Tensor) -> Tensor: - TF.subtract-tensors(prediction, actual-values) - ^ TF.tensor-square(_) - ^ TF.reduce-mean(_, none) - end - - fun plot(scatter-plot :: DataSeries, a :: Tensor, b :: Tensor, c :: Tensor, d :: Tensor) block: - a-val = a.data-now().first - b-val = b.data-now().first - c-val = c.data-now().first - d-val = d.data-now().first - - print("Equation:") - print("y = " - + num-to-string(a-val) + "x^3 + " - + num-to-string(b-val) + "x^2 + " - + num-to-string(c-val) + "x + " - + num-to-string(d-val)) - function-plot = C.from-list.function-plot( - lam(x): (a-val * num-expt(x, 3)) + (b-val * num-sqr(x)) + (c-val * x) + d-val end) - chart-image = C.render-charts([list: scatter-plot, function-plot]).get-image() - I.scale(0.6, chart-image) - end - - # Generate synthetic data based on a cubic function - test-data = generate-data(100, {a: -0.8, b: -0.2, c: 0.9, d: 0.5}, 0.04) - train-x = test-data.xs.data-now() - train-y = test-data.ys.data-now() - - # Plot the random points ahead of time for better perfomance: - scatter-plot = C.from-list.scatter-plot(train-x, train-y) - - # Generate a few variables representing coefficients in the equation, - # randomized to some value between 0 and 1 - a = TF.make-scalar(positive-rand()).to-variable() - b = TF.make-scalar(positive-rand()).to-variable() - c = TF.make-scalar(positive-rand()).to-variable() - d = TF.make-scalar(positive-rand()).to-variable() - - # Plot the random cubic function overlayed on the initial points: - plot(scatter-plot, a, b, c, d) - - # Create an optimizer: - LEARNING-RATE = 0.5 - TRAINING-CYCLES = 200 - optimizer = TF.train-sgd(LEARNING-RATE) - - # Train the model - for each(i from range(0, TRAINING-CYCLES)): - optimizer.minimize(lam() block: - prediction = predict(a, b, c, d, test-data.xs) - loss(prediction, test-data.ys) - end, empty) - end - - # Plot the resulting cubic function overlayed on the initial points: - plot(scatter-plot, a, b, c, d) - } } From 54a80124ca9d4e2623b61cbc2f9947b603c7f4e3 Mon Sep 17 00:00:00 2001 From: Zachary Espiritu Date: Tue, 14 Aug 2018 19:34:52 -0400 Subject: [PATCH 27/27] Add examples for Tensor arithmetic methods --- src/trove/tensorflow.scrbl | 153 +++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/src/trove/tensorflow.scrbl b/src/trove/tensorflow.scrbl index 87b53a9..c4a3b8e 100644 --- a/src/trove/tensorflow.scrbl +++ b/src/trove/tensorflow.scrbl @@ -2167,52 +2167,205 @@ Adds @pyret{x} to the @tt{Tensor}. This is equivalent to @pyret-id["add-tensors"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 1].add([tensor: 1]).data-now() + is-roughly [list: 2] + [tensor: 1, 3].add([tensor: 1]).data-now() + is-roughly [list: 2, 4] + [tensor: 1, 3].add([tensor: 5, 1]).data-now() + is-roughly [list: 6, 4] + [tensor: 1, 3, 4].add([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["subtract"] Subtracts @pyret{x} from the @tt{Tensor}. This is equivalent to @pyret-id["subtract-tensors"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 1].subtract([tensor: 1]).data-now() + is-roughly [list: 0] + [tensor: 1, 3].subtract([tensor: 1]).data-now() + is-roughly [list: 0, 2] + [tensor: 1, 3].subtract([tensor: 5, 1]).data-now() + is-roughly [list: -4, 2] + [tensor: 1, 3, 4].subtract([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["multiply"] Multiplies the @tt{Tensor} by @pyret{x}. This is equivalent to @pyret-id["multiply-tensors"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 1].multiply([tensor: 1]).data-now() + is-roughly [list: 1] + [tensor: 1, 3].multiply([tensor: 1]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].multiply([tensor: 5, 1]).data-now() + is-roughly [list: 5, 3] + [tensor: 1, 3, 4].multiply([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["divide"] Divides the @tt{Tensor} by @pyret{x}. This is equivalent to @pyret-id["divide-tensors"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 1].divide([tensor: 1]).data-now() + is-roughly [list: 1] + [tensor: 1, 3].divide([tensor: 1]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].divide([tensor: 5, 1]).data-now() + is-roughly [list: 0.2, 3] + [tensor: 1, 3, 4].divide([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + + [tensor: 1].divide([tensor: 0]) + raises "The argument Tensor cannot contain 0" + [tensor: 4.23].divide([tensor: 7.65, 1.43, 0, 2.31]) + raises "The argument Tensor cannot contain 0" + end + } + @tensor-method["floor-divide"] Divides the @tt{Tensor} by @pyret{x}, with the result rounded with the floor function. This is equivalent to @pyret-id["floor-divide-tensors"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 1].floor-divide([tensor: 1]).data-now() + is-roughly [list: 1] + [tensor: 1, 3].floor-divide([tensor: 1]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].floor-divide([tensor: 5, 1]).data-now() + is-roughly [list: 0, 3] + [tensor: 1, 3, 4].floor-divide([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + + [tensor: 1].floor-divide([tensor: 0]) + raises "The argument Tensor cannot contain 0" + [tensor: 4.23].floor-divide([tensor: 7.65, 1.43, 0]) + raises "The argument Tensor cannot contain 0" + end + } + @tensor-method["max"] Returns the maximum of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-max"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 0].max([tensor: 1]).data-now() + is-roughly [list: 1] + [tensor: 1, 3].max([tensor: 1]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].max([tensor: 200]).data-now() + is-roughly [list: 200, 200] + [tensor: 1, 3].max([tensor: 5, 1]).data-now() + is-roughly [list: 5, 3] + [tensor: 1, 3, 4].max([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["min"] Returns the minimum of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-min"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 0].min([tensor: 1]).data-now() + is-roughly [list: 0] + [tensor: 1, 3].min([tensor: 1]).data-now() + is-roughly [list: 1, 1] + [tensor: 1, 3].min([tensor: 200]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].min([tensor: 0]).data-now() + is-roughly [list: 0, 0] + [tensor: 1, 3, 4].min([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["modulo"] Computes the modulo of the @tt{Tensor} and @pyret{x}. This is equivalent to @pyret-id["tensor-modulo"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 0].modulo([tensor: 1]).data-now() + is-roughly [list: 0] + [tensor: 1, 3].modulo([tensor: 1]).data-now() + is-roughly [list: 0, 0] + [tensor: 1, 3].modulo([tensor: 5, 1]).data-now() + is-roughly [list: 1, 0] + [tensor: 1, 3, 4].modulo([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + + [tensor: 1].modulo([tensor: 0]) + raises "The argument Tensor cannot contain 0" + [tensor: 1].modulo([tensor: 1, 0]) + raises "The argument Tensor cannot contain 0" + end + } + @tensor-method["expt"] Computes the power of the @tt{Tensor} to @pyret{exponent}. This is equivalent to @pyret-id["tensor-expt"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 0].expt([tensor: 1]).data-now() + is-roughly [list: 0] + [tensor: 1, 3].expt([tensor: 1]).data-now() + is-roughly [list: 1, 3] + [tensor: 1, 3].expt([tensor: 4]).data-now() + is-roughly [list: 1, 81] + [tensor: 3, 3].expt([tensor: 5, 1]).data-now() + is-roughly [list: 243, 3] + [tensor: 1, 3, 4].expt([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @tensor-method["squared-difference"] Computes @pyret{(self - x) * (self - x)}, element-wise. This is equivalent to @pyret-id["squared-difference"]@pyret{(self, x)}. + @examples{ + check: + [tensor: 0].squared-difference([tensor: 1]).data-now() + is-roughly [list: 1] + [tensor: 3].squared-difference([tensor: -3]).data-now() + is-roughly [list: 36] + [tensor: 1, 3].squared-difference([tensor: 4]).data-now() + is-roughly [list: 9, 1] + [tensor: 3, 3].squared-difference([tensor: 5, 1]).data-now() + is-roughly [list: 4, 4] + [tensor: 1, 3, 4].squared-difference([tensor: 5, 1]) + raises "Tensors could not be applied as binary operation arguments" + end + } + @;######################################################################### @section{Tensor Operations}