shrinath-suresh/blogs-docs-splitted huggingface dataset:
\eta)                  \      &\hspace{5mm} \textbf{if} \:
      \lambda \neq 0                                          \
      &\hspace{10mm} g_t \leftarrow g_t + \lambda \theta_{t-1}
      \      &\hspace{5mm}state_sum_t  \leftarrow  state_sum_{t-1}
      + g^2_t                      \      &\hspace{5mm}\theta_t
      \leftarrow          \theta_{t-1}- \tilde{\gamma}
      \frac{g_t}{\sqrt{state_sum_t}+\epsilon}            \
      &\rule{110mm}{0.4pt}
      \[-1.ex]      &\bf{return} \:  \theta_t
      \[-1.ex]      &\rule{110mm}{0.4pt}
      \[-1.ex] \end{aligned}
For further details regarding the algorithm we refer to Adaptive
   Subgradient Methods for Online Learning and Stochastic
   Optimization.
Parameters:
      * params (iterable) -- iterable of parameters to optimize
        or dicts defining parameter groups
  * **lr** (*float**, **optional*) -- learning rate (default:
    1e-2)

  * **lr_decay** (*float**, **optional*) -- learning rate decay
(default: 0)
  * **weight_decay** (*float**, **optional*) -- weight decay (L2
    penalty) (default: 0)

  * **eps** (*float**, **optional*) -- term added to the
    denominator to improve numerical stability (default: 1e-10)

  * **foreach** (*bool**, **optional*) -- whether foreach
    implementation of optimizer is used. If unspecified by the
    user (so foreach is None), we will try to use foreach over the
    for-loop implementation on CUDA, since it is usually
    significantly more performant. (default: None)

  * **maximize** (*bool**, **optional*) -- maximize the params
    based on the objective, instead of minimizing (default: False)

  * **differentiable** (*bool**, **optional*) -- whether autograd
    should occur through the optimizer step in training.
    Otherwise, the step() function runs in a torch.no_grad()
    context. Setting to True can impair performance, so leave it
False if you don't intend to run autograd through this
        instance (default: False)
add_param_group(param_group)
  Add a param group to the "Optimizer" s *param_groups*.

  This can be useful when fine tuning a pre-trained network as
  frozen layers can be made trainable and added to the "Optimizer"
  as training progresses.

  Parameters:
     **param_group** (*dict*) -- Specifies what Tensors should be
     optimized along with group specific optimization options.

load_state_dict(state_dict)
  Loads the optimizer state.

  Parameters:
     **state_dict** (*dict*) -- optimizer state. Should be an
     object returned from a call to "state_dict()".

register_step_post_hook(hook)
  Register an optimizer step post hook which will be called after
  optimizer step. It should have the following signature:

     hook(optimizer, args, kwargs) -> None

  The "optimizer" argument is the optimizer instance being used.
Parameters:
         hook (Callable) -- The user defined hook to be
         registered.
  Returns:
     a handle that can be used to remove the added hook by calling
     "handle.remove()"

  Return type:
     "torch.utils.hooks.RemoveableHandle"

register_step_pre_hook(hook)
  Register an optimizer step pre hook which will be called before
  optimizer step. It should have the following signature:

     hook(optimizer, args, kwargs) -> None or modified args and kwargs

  The "optimizer" argument is the optimizer instance being used.
  If args and kwargs are modified by the pre-hook, then the
  transformed values are returned as a tuple containing the
  new_args and new_kwargs.

  Parameters:
     **hook** (*Callable*) -- The user defined hook to be
     registered.

  Returns:
     a handle that can be used to remove the added hook by calling
     "handle.remove()"

  Return type:
"handle.remove()"
  Return type:
     "torch.utils.hooks.RemoveableHandle"

state_dict()
  Returns the state of the optimizer as a "dict".

  It contains two entries:

  * state - a dict holding current optimization state. Its content
       differs between optimizer classes.

  * param_groups - a list containing all parameter groups where
    each
       parameter group is a dict

zero_grad(set_to_none=False)
  Sets the gradients of all optimized "torch.Tensor" s to zero.

  Parameters:
     **set_to_none** (*bool*) -- instead of setting to zero, set
     the grads to None. This will in general have lower memory
     footprint, and can modestly improve performance. However, it
     changes certain behaviors. For example: 1. When the user
     tries to access a gradient and perform manual ops on it, a
     None attribute or a Tensor full of 0s will behave
     differently. 2. If the user requests
differently. 2. If the user requests
         "zero_grad(set_to_none=True)" followed by a backward pass,
         ".grad"s are guaranteed to be None for params that did not
         receive a gradient. 3. "torch.optim" optimizers have a
         different behavior if the gradient is 0 or None (in one case
         it does the step with a gradient of 0 and in the other it
         skips the step altogether).
torch.Tensor.new_empty
Tensor.new_empty(size, *, dtype=None, device=None, requires_grad=False, layout=torch.strided, pin_memory=False) -> Tensor
Returns a Tensor of size "size" filled with uninitialized data. By
   default, the returned Tensor has the same "torch.dtype" and
   "torch.device" as this tensor.
Parameters:
      size (int...) -- a list, tuple, or "torch.Size" of
      integers defining the shape of the output tensor.
Keyword Arguments:
      * dtype ("torch.dtype", optional) -- the desired type of
        returned tensor. Default: if None, same "torch.dtype" as this
        tensor.
  * **device** ("torch.device", optional) -- the desired device of
    returned tensor. Default: if None, same "torch.device" as this
    tensor.

  * **requires_grad** (*bool**, **optional*) -- If autograd should
    record operations on the returned tensor. Default: "False".

  * **layout** ("torch.layout", optional) -- the desired layout of
returned Tensor. Default: "torch.strided".
  * **pin_memory** (*bool**, **optional*) -- If set, returned
    tensor would be allocated in the pinned memory. Works only for
    CPU tensors. Default: "False".

Example:
  >>> tensor = torch.ones(())
  >>> tensor.new_empty((2, 3))
  tensor([[ 5.8182e-18,  4.5765e-41, -1.0545e+30],
          [ 3.0949e-41,  4.4842e-44,  0.0000e+00]])
InstanceNorm2d
class torch.ao.nn.quantized.InstanceNorm2d(num_features, weight, bias, scale, zero_point, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False, device=None, dtype=None)
This is the quantized version of "InstanceNorm2d".
Additional args:
      * scale - quantization scale of the output, type: double.
  * **zero_point** - quantization zero point of the output, type:
    long.
torch.diag
torch.diag(input, diagonal=0, *, out=None) -> Tensor


If "input" is a vector (1-D tensor), then returns a 2-D square
     tensor with the elements of "input" as the diagonal.


If "input" is a matrix (2-D tensor), then returns a 1-D tensor
     with the diagonal elements of "input".


The argument "diagonal" controls which diagonal to consider:


If "diagonal" = 0, it is the main diagonal.


If "diagonal" > 0, it is above the main diagonal.


If "diagonal" < 0, it is below the main diagonal.


Parameters:
      * input (Tensor) -- the input tensor.
  * **diagonal** (*int**, **optional*) -- the diagonal to consider

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
See also:
 "torch.diagonal()" always returns the diagonal of its input.

 "torch.diagflat()" always constructs a tensor with diagonal
 elements specified by the input.

Examples:
Examples:
Get the square matrix where the input vector is the diagonal:
  >>> a = torch.randn(3)
  >>> a
  tensor([ 0.5950,-0.0872, 2.3298])
  >>> torch.diag(a)
  tensor([[ 0.5950, 0.0000, 0.0000],
          [ 0.0000,-0.0872, 0.0000],
          [ 0.0000, 0.0000, 2.3298]])
  >>> torch.diag(a, 1)
  tensor([[ 0.0000, 0.5950, 0.0000, 0.0000],
          [ 0.0000, 0.0000,-0.0872, 0.0000],
          [ 0.0000, 0.0000, 0.0000, 2.3298],
          [ 0.0000, 0.0000, 0.0000, 0.0000]])

Get the k-th diagonal of a given matrix:
  >>> a = torch.randn(3, 3)
  >>> a
  tensor([[-0.4264, 0.0255,-0.1064],
          [ 0.8795,-0.2429, 0.1374],
          [ 0.1029,-0.6482,-1.6300]])
  >>> torch.diag(a, 0)
  tensor([-0.4264,-0.2429,-1.6300])
  >>> torch.diag(a, 1)
  tensor([ 0.0255, 0.1374])
torch.fft.hfft2
torch.fft.hfft2(input, s=None, dim=(- 2, - 1), norm=None, *, out=None) -> Tensor
Computes the 2-dimensional discrete Fourier transform of a
   Hermitian symmetric "input" signal. Equivalent to "hfftn()" but
   only transforms the last two dimensions by default.
"input" is interpreted as a one-sided Hermitian signal in the time
   domain. By the Hermitian property, the Fourier transform will be
   real-valued.
Note:
 Supports torch.half and torch.chalf on CUDA with GPU Architecture
 SM53 or greater. However it only supports powers of 2 signal
 length in every transformed dimensions. With default arguments,
 the size of last dimension should be (2^n + 1) as argument *s*
 defaults to even output size = 2 * (last_dim_size - 1)

Parameters:
      * input (Tensor) -- the input tensor
  * **s** (*Tuple**[**int**]**, **optional*) -- Signal size in the
    transformed dimensions. If given, each dimension "dim[i]" will
either be zero-padded or trimmed to the length "s[i]" before
        computing the Hermitian FFT. If a length "-1" is specified, no
        padding is done in that dimension. Defaults to even output in
        the last dimension: "s[-1] = 2*(input.size(dim[-1]) - 1)".
  * **dim** (*Tuple**[**int**]**, **optional*) -- Dimensions to be
    transformed. The last dimension must be the half-Hermitian
    compressed dimension. Default: last two dimensions.

  * **norm** (*str**, **optional*) --

    Normalization mode. For the forward transform ("hfft2()"),
    these correspond to:

    * ""forward"" - normalize by "1/n"

    * ""backward"" - no normalization

    * ""ortho"" - normalize by "1/sqrt(n)" (making the Hermitian
      FFT orthonormal)

    Where "n = prod(s)" is the logical FFT size. Calling the
    backward transform ("ihfft2()") with the same normalization
    mode will apply an overall normalization of "1/n" between the
two transforms. This is required to make "ihfft2()" the exact
        inverse.
    Default is ""backward"" (no normalization).

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
-[ Example ]-
Starting from a real frequency-space signal, we can generate a
   Hermitian-symmetric time-domain signal: >>> T = torch.rand(10, 9)



t = torch.fft.ihfft2(T)



Without specifying the output length to "hfftn()", the output will
   not round-trip properly because the input is odd-length in the last
   dimension:



torch.fft.hfft2(t).size()
   torch.Size([10, 10])



So, it is recommended to always pass the signal shape "s".



roundtrip = torch.fft.hfft2(t, T.size())
roundtrip.size()
   torch.Size([10, 9])
torch.allclose(roundtrip, T)
   True
torch.topk
torch.topk(input, k, dim=None, largest=True, sorted=True, *, out=None)
Returns the "k" largest elements of the given "input" tensor along
   a given dimension.
If "dim" is not given, the last dimension of the input is chosen.
If "largest" is "False" then the k smallest elements are
   returned.
A namedtuple of (values, indices) is returned with the values
   and indices of the largest k elements of each row of the
   input tensor in the given dimension dim.
The boolean option "sorted" if "True", will make sure that the
   returned k elements are themselves sorted
Parameters:
      * input (Tensor) -- the input tensor.
  * **k** (*int*) -- the k in "top-k"

  * **dim** (*int**, **optional*) -- the dimension to sort along

  * **largest** (*bool**, **optional*) -- controls whether to
    return largest or smallest elements

  * **sorted** (*bool**, **optional*) -- controls whether to
return the elements in sorted order
Keyword Arguments:
      out (tuple, optional) -- the output tuple of (Tensor,
      LongTensor) that can be optionally given to be used as output
      buffers
Example:
  >>> x = torch.arange(1., 6.)
  >>> x
  tensor([ 1.,  2.,  3.,  4.,  5.])
  >>> torch.topk(x, 3)
  torch.return_types.topk(values=tensor([5., 4., 3.]), indices=tensor([4, 3, 2]))
SmoothL1Loss
class torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean', beta=1.0)
Creates a criterion that uses a squared term if the absolute
   element-wise error falls below beta and an L1 term otherwise. It is
   less sensitive to outliers than "torch.nn.MSELoss" and in some
   cases prevents exploding gradients (e.g. see the paper Fast R-CNN
   by Ross Girshick).
For a batch of size N, the unreduced loss can be described as:
  \ell(x, y) = L = \{l_1, ..., l_N\}^T

with
  l_n = \begin{cases} 0.5 (x_n - y_n)^2 / beta, & \text{if } |x_n
  - y_n| < beta \\ |x_n - y_n| - 0.5 * beta, & \text{otherwise }
  \end{cases}

If reduction is not none, then:
  \ell(x, y) = \begin{cases}     \operatorname{mean}(L), &
  \text{if reduction} = \text{`mean';}\\
  \operatorname{sum}(L),  &  \text{if reduction} = \text{`sum'.}
  \end{cases}

Note:
 Smooth L1 loss can be seen as exactly "L1Loss", but with the |x -
y| < beta portion replaced with a quadratic function such that
     its slope is 1 at |x - y| = beta. The quadratic segment smooths
     the L1 loss near |x - y| = 0.
Note:
 Smooth L1 loss is closely related to "HuberLoss", being
 equivalent to huber(x, y) / beta (note that Smooth L1's beta
 hyper-parameter is also known as delta for Huber). This leads to
 the following differences:

 * As beta -> 0, Smooth L1 loss converges to "L1Loss", while
   "HuberLoss" converges to a constant 0 loss. When beta is 0,
   Smooth L1 loss is equivalent to L1 loss.

 * As beta -> +\infty, Smooth L1 loss converges to a constant 0
   loss, while "HuberLoss" converges to "MSELoss".

 * For Smooth L1 loss, as beta varies, the L1 segment of the loss
   has a constant slope of 1. For "HuberLoss", the slope of the L1
   segment is beta.

Parameters:
      * size_average (bool, optional) -- Deprecated (see
"size_average" and "reduce" are in the process of being
        deprecated, and in the meantime, specifying either of those
        two args will override "reduction". Default: "'mean'"
  * **beta** (*float**, **optional*) -- Specifies the threshold at
    which to change between L1 and L2 loss. The value must be non-
    negative. Default: 1.0

Shape:
      * Input: (*), where * means any number of dimensions.
  * Target: (*), same shape as the input.

  * Output: scalar. If "reduction" is "'none'", then (*), same
    shape as the input.
AdaptiveAvgPool2d
class torch.nn.AdaptiveAvgPool2d(output_size)
Applies a 2D adaptive average pooling over an input signal composed
   of several input planes.
The output is of size H x W, for any input size. The number of
   output features is equal to the number of input planes.
Parameters:
      output_size (Union[int, None,
      Tuple[Optional[int], Optional[int]]])
      -- the target output size of the image of the form H x W. Can be
      a tuple (H, W) or a single H for a square image H x H. H and W
      can be either a "int", or "None" which means the size will be
      the same as that of the input.
Shape:
      * Input: (N, C, H_{in}, W_{in}) or (C, H_{in}, W_{in}).
  * Output: (N, C, S_{0}, S_{1}) or (C, S_{0}, S_{1}), where
    S=\text{output\_size}.

-[ Examples ]-



target output size of 5x7
m = nn.AdaptiveAvgPool2d((5, 7))
input = torch.randn(1, 64, 8, 9)
output = m(input)
output = m(input)
target output size of 7x7 (square)
m = nn.AdaptiveAvgPool2d(7)
input = torch.randn(1, 64, 10, 9)
output = m(input)
target output size of 10x7
m = nn.AdaptiveAvgPool2d((None, 7))
input = torch.randn(1, 64, 10, 9)
output = m(input)
torch.quantized_max_pool1d
torch.quantized_max_pool1d(input, kernel_size, stride=[], padding=0, dilation=1, ceil_mode=False) -> Tensor
Applies a 1D max pooling over an input quantized tensor composed of
   several input planes.
Parameters:
      * input (Tensor) -- quantized tensor
  * **kernel_size** (*list of python:int*) -- the size of the
    sliding window

  * **stride** ("list of int", optional) -- the stride of the
    sliding window

  * **padding** ("list of int", optional) -- padding to be added
    on both sides, must be >= 0 and <= kernel_size / 2

  * **dilation** ("list of int", optional) -- The stride between
    elements within a sliding window, must be > 0. Default 1

  * **ceil_mode** (*bool**, **optional*) -- If True, will use ceil
    instead of floor to compute the output shape. Defaults to
    False.

Returns:
      A quantized tensor with max_pool1d applied.
Return type:
      Tensor
Return type:
      Tensor
Example:
  >>> qx = torch.quantize_per_tensor(torch.rand(2, 2), 1.5, 3, torch.quint8)
  >>> torch.quantized_max_pool1d(qx, [2])
  tensor([[0.0000],
          [1.5000]], size=(2, 1), dtype=torch.quint8,
      quantization_scheme=torch.per_tensor_affine, scale=1.5, zero_point=3)
torch.sort
torch.sort(input, dim=- 1, descending=False, stable=False, *, out=None)
Sorts the elements of the "input" tensor along a given dimension in
   ascending order by value.
If "dim" is not given, the last dimension of the input is chosen.
If "descending" is "True" then the elements are sorted in
   descending order by value.
If "stable" is "True" then the sorting routine becomes stable,
   preserving the order of equivalent elements.
A namedtuple of (values, indices) is returned, where the values
   are the sorted values and indices are the indices of the elements
   in the original input tensor.
Parameters:
      * input (Tensor) -- the input tensor.
  * **dim** (*int**, **optional*) -- the dimension to sort along

  * **descending** (*bool**, **optional*) -- controls the sorting
    order (ascending or descending)

  * **stable** (*bool**, **optional*) -- makes the sorting routine
stable, which guarantees that the order of equivalent elements
        is preserved.
Keyword Arguments:
      out (tuple, optional) -- the output tuple of
      (Tensor, LongTensor) that can be optionally given to be used
      as output buffers
Example:
  >>> x = torch.randn(3, 4)
  >>> sorted, indices = torch.sort(x)
  >>> sorted
  tensor([[-0.2162,  0.0608,  0.6719,  2.3332],
          [-0.5793,  0.0061,  0.6058,  0.9497],
          [-0.5071,  0.3343,  0.9553,  1.0960]])
  >>> indices
  tensor([[ 1,  0,  2,  3],
          [ 3,  1,  0,  2],
          [ 0,  3,  1,  2]])

  >>> sorted, indices = torch.sort(x, 0)
  >>> sorted
  tensor([[-0.5071, -0.2162,  0.6719, -0.5793],
          [ 0.0608,  0.0061,  0.9497,  0.3343],
          [ 0.6058,  0.9553,  1.0960,  2.3332]])
  >>> indices
  tensor([[ 2,  0,  0,  1],
          [ 0,  1,  1,  2],
          [ 1,  2,  2,  0]])
  >>> x = torch.tensor([0, 1] * 9)
x = torch.tensor([0, 1] * 9)
      >>> x.sort()
      torch.return_types.sort(
          values=tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
          indices=tensor([ 2, 16,  4,  6, 14,  8,  0, 10, 12,  9, 17, 15, 13, 11,  7,  5,  3,  1]))
      >>> x.sort(stable=True)
      torch.return_types.sort(
          values=tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
          indices=tensor([ 0,  2,  4,  6,  8, 10, 12, 14, 16,  1,  3,  5,  7,  9, 11, 13, 15, 17]))
torch.sparse.addmm
torch.sparse.addmm(mat, mat1, mat2, *, beta=1., alpha=1.) -> Tensor
This function does exact same thing as "torch.addmm()" in the
   forward, except that it supports backward for sparse COO matrix
   "mat1". When "mat1" is a COO tensor it must have sparse_dim = 2.
   When inputs are COO tensors, this function also supports backward
   for both inputs.
Supports both CSR and COO storage formats.
Note:
 This function doesn't support computing derivaties with respect
 to CSR matrices.

Parameters:
      * mat (Tensor) -- a dense matrix to be added
  * **mat1** (*Tensor*) -- a sparse matrix to be multiplied

  * **mat2** (*Tensor*) -- a dense matrix to be multiplied

  * **beta** (*Number**, **optional*) -- multiplier for "mat"
    (\beta)

  * **alpha** (*Number**, **optional*) -- multiplier for mat1 @
    mat2 (\alpha)
torch.is_nonzero
torch.is_nonzero(input)
Returns True if the "input" is a single element tensor which is not
   equal to zero after type conversions. i.e. not equal to
   "torch.tensor([0.])" or "torch.tensor([0])" or
   "torch.tensor([False])". Throws a "RuntimeError" if "torch.numel()
   != 1" (even in case of sparse tensors).
Parameters:
      input (Tensor) -- the input tensor.
Examples:
  >>> torch.is_nonzero(torch.tensor([0.]))
  False
  >>> torch.is_nonzero(torch.tensor([1.5]))
  True
  >>> torch.is_nonzero(torch.tensor([False]))
  False
  >>> torch.is_nonzero(torch.tensor([3]))
  True
  >>> torch.is_nonzero(torch.tensor([1, 3, 5]))
  Traceback (most recent call last):
  ...
  RuntimeError: bool value of Tensor with more than one value is ambiguous
  >>> torch.is_nonzero(torch.tensor([]))
  Traceback (most recent call last):
  ...
  RuntimeError: bool value of Tensor with no values is ambiguous
torch.signbit
torch.signbit(input, *, out=None) -> Tensor
Tests if each element of "input" has its sign bit set or not.
Parameters:
      input (Tensor) -- the input tensor.
Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
Example:
  >>> a = torch.tensor([0.7, -1.2, 0., 2.3])
  >>> torch.signbit(a)
  tensor([ False, True,  False,  False])
  >>> a = torch.tensor([-0.0, 0.0])
  >>> torch.signbit(a)
  tensor([ True,  False])

Note:
 signbit handles signed zeros, so negative zero (-0) returns True.
torch.kaiser_window
torch.kaiser_window(window_length, periodic=True, beta=12.0, *, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
Computes the Kaiser window with window length "window_length" and
   shape parameter "beta".
Let I_0 be the zeroth order modified Bessel function of the first
   kind (see "torch.i0()") and "N = L - 1" if "periodic" is False and
   "L" if "periodic" is True, where "L" is the "window_length". This
   function computes:
  out_i = I_0 \left( \beta \sqrt{1 - \left( {\frac{i - N/2}{N/2}}
  \right) ^2 } \right) / I_0( \beta )

Calling "torch.kaiser_window(L, B, periodic=True)" is equivalent to
   calling "torch.kaiser_window(L + 1, B, periodic=False)[:-1])". The
   "periodic" argument is intended as a helpful shorthand to produce a
   periodic window as input to functions like "torch.stft()".
Note:
 If "window_length" is one, then the returned window is a single
 element tensor containing a one.
element tensor containing a one.
Parameters:
      * window_length (int) -- length of the window.
  * **periodic** (*bool**, **optional*) -- If True, returns a
    periodic window suitable for use in spectral analysis. If
    False, returns a symmetric window suitable for use in filter
    design.

  * **beta** (*float**, **optional*) -- shape parameter for the
    window.

Keyword Arguments:
      * dtype ("torch.dtype", optional) -- the desired data type
        of returned tensor. Default: if "None", uses a global default
        (see "torch.set_default_tensor_type()").
  * **layout** ("torch.layout", optional) -- the desired layout of
    returned window tensor. Only "torch.strided" (dense layout) is
    supported.

  * **device** ("torch.device", optional) -- the desired device of
    returned tensor. Default: if "None", uses the current device
    for the default tensor type (see
torch.prod
torch.prod(input, *, dtype=None) -> Tensor
Returns the product of all elements in the "input" tensor.
Parameters:
      input (Tensor) -- the input tensor.
Keyword Arguments:
      dtype ("torch.dtype", optional) -- the desired data type of
      returned tensor. If specified, the input tensor is casted to
      "dtype" before the operation is performed. This is useful for
      preventing data type overflows. Default: None.
Example:
  >>> a = torch.randn(1, 3)
  >>> a
  tensor([[-0.8020,  0.5428, -1.5854]])
  >>> torch.prod(a)
  tensor(0.6902)

torch.prod(input, dim, keepdim=False, *, dtype=None) -> Tensor
Returns the product of each row of the "input" tensor in the given
   dimension "dim".
If "keepdim" is "True", the output tensor is of the same size as
   "input" except in the dimension "dim" where it is of size 1.
   Otherwise, "dim" is squeezed (see "torch.squeeze()"), resulting in