提交 150add2c authored 作者: Brandon T. Willard's avatar Brandon T. Willard 提交者: Brandon T. Willard

Update developer documentation on Type to include new partial shape information

上级 e6f9689e
......@@ -203,36 +203,39 @@ structures, code going like ``def f(x): ...`` would produce an :class:`Op` for
:class:`Type`
-------------
A :class:`Type` in Aesara represents a set of constraints on potential
data objects. These constraints allow Aesara to tailor C code to handle
them and to statically optimize the computation graph. For instance,
the :ref:`irow <libdoc_tensor_creation>` type in the :mod:`aesara.tensor` package
gives the following constraints on the data the :class:`Variable`\s of type ``irow``
may contain:
#. Must be an instance of :class:`numpy.ndarray`: ``isinstance(x, numpy.ndarray)``
#. Must be an array of 32-bit integers: ``str(x.dtype) == 'int32'``
#. Must have a shape of 1xN: ``len(x.shape) == 2 and x.shape[0] == 1``
Knowing these restrictions, Aesara may generate C code for addition, etc.
that declares the right data types and that contains the right number
of loops over the dimensions.
Note that an Aesara :class:`Type` is not equivalent to a Python type or
class. Indeed, in Aesara, :ref:`irow <libdoc_tensor_creation>` and :ref:`dmatrix
<libdoc_tensor_creation>` both use :class:`numpy.ndarray` as the underlying type
for doing computations and storing data, yet they are different Aesara
:class:`Type`\s. Indeed, the constraints set by `dmatrix` are:
#. Must be an instance of :class:`numpy.ndarray`: ``isinstance(x, numpy.ndarray)``
#. Must be an array of 64-bit floating point numbers: ``str(x.dtype) == 'float64'``
#. Must have a shape of ``MxN``, no restriction on ``M`` or ``N``: ``len(x.shape) == 2``
These restrictions are different from those of ``irow`` which are listed above.
There are cases in which a :class:`Type` can fully correspond to a Python type,
such as the `double`\ :class:`Type`, which corresponds to
Python's ``float``.
A :class:`Type` in Aesara provides static information (or constraints) about
data objects in a graph. The information provided by :class:`Type`\s allows
Aesara to perform optimizations and produce more efficient compiled code.
Every symbolic :class:`Variable` in an Aesara graph has an associated
:class:`Type` instance, and :class:`Type`\s also serve as a means of
constructing :class:`Variable` instances. In other words, :class:`Type`\s and
:class:`Variable`\s go hand-in-hand.
For example, :ref:`aesara.tensor.irow <libdoc_tensor_creation>` is an instance of a
:class:`Type` and it can be used to construct variables as follows:
>>> from aesara.tensor import irow
>>> irow()
<TensorType(int32, (1, None))>
As the string print-out shows, `irow` specifies the following information about
the :class:`Variable`\s it constructs:
#. They represent tensors that are backed by :class:`numpy.ndarray`\s.
This comes from the fact that `irow` is an instance of :class:`TensorType`,
which is the base :class:`Type` for symbolic :class:`numpy.ndarray`\s.
#. They represent arrays of 32-bit integers (i.e. from the ``int32``).
#. They represent arrays with shapes of :math:`1 \times N`, or, in code, ``(1,
None)``, where ``None`` represents any shape value.
Note that Aesara :class:`Type`\s are not necessarily equivalent to Python types or
classes. Aesara's :class:`TensorType`'s, like `irow`, use :class:`numpy.ndarray`
as the underlying Python type for performing computations and storing data, but
:class:`numpy.ndarray`\s model a much wider class of arrays than most :class:`TensorType`\s.
In other words, Aesara :class:`Type`'s try to be more specific.
For more information see :ref:`aesara_type`.
.. index::
single: Variable
......
差异被折叠。
......@@ -4,25 +4,21 @@
How Shape Information is Handled by Aesara
==========================================
It is not possible to strictly enforce the shape of an Aesara variable when
building a graph since the particular value provided at run-time for a parameter of a
Aesara function may condition the shape of the Aesara variables in its graph.
Currently, information regarding shape is used in two ways in Aesara:
- To generate faster C code for the 2d convolution on the CPU and the GPU,
when the exact output shape is known in advance.
Currently, information regarding shape is used in the following ways by Aesara:
- To remove computations in the graph when we only want to know the
shape, but not the actual value of a variable. This is done with the
`Op.infer_shape` method.
:meth:`Op.infer_shape` method.
- To generate faster compiled code (e.g. for a 2D convolution).
Example:
Example:
>>> import aesara
>>> x = aesara.tensor.matrix('x')
>>> f = aesara.function([x], (x ** 2).shape)
>>> aesara.printing.debugprint(f) # doctest: +NORMALIZE_WHITESPACE
>>> aesara.dprint(f)
MakeVector{dtype='int64'} [id A] '' 2
|Shape_i{0} [id B] '' 1
| |x [id C]
......@@ -30,15 +26,46 @@ MakeVector{dtype='int64'} [id A] '' 2
|x [id C]
The output of this compiled function does not contain any multiplication
or power. Aesara has removed them to compute directly the shape of the
output.
The output of this compiled function does not contain any multiplication or
power computations; Aesara has removed them to compute the shape of the output
directly.
Aesara propagates information about shapes within a graph using specialized
:class:`Op`\s and static :class:`Type` information (see :ref:`aesara_type`).
Specifying Exact Shape
======================
Currently, specifying a shape is not as easy and flexible as we wish and we plan some
upgrade. Here is the current state of what can be done:
- You can pass the shape info directly to the ``ConvOp`` created
when calling ``conv2d``. You simply set the parameters ``image_shape``
and ``filter_shape`` inside the call. They must be tuples of 4
elements. For example:
.. code-block:: python
aesara.tensor.nnet.conv2d(..., image_shape=(7, 3, 5, 5), filter_shape=(2, 3, 4, 4))
- You can use the ``SpecifyShape`` op to add shape information anywhere in the
graph. This allows to perform some optimizations. In the following example,
this makes it possible to precompute the Aesara function to a constant.
>>> import aesara
>>> x = aesara.tensor.matrix()
>>> x_specify_shape = aesara.tensor.specify_shape(x, (2, 2))
>>> f = aesara.function([x], (x_specify_shape ** 2).shape)
>>> aesara.printing.debugprint(f) # doctest: +NORMALIZE_WHITESPACE
DeepCopyOp [id A] '' 0
|TensorConstant{(2,) of 2} [id B]
Shape Inference Problem
=======================
Problems with Shape inference
=============================
Aesara propagates information about shape in the graph. Sometimes this
can lead to errors. Consider this example:
Sometimes this can lead to errors. Consider this example:
>>> import numpy
>>> import aesara
......@@ -75,7 +102,7 @@ Traceback (most recent call last):
ValueError: ...
As you can see, when asking only for the shape of some computation (``join`` in the
example), an inferred shape is computed directly, without executing
example above), an inferred shape is computed directly, without executing
the computation itself (there is no ``join`` in the first output or debugprint).
This makes the computation of the shape faster, but it can also hide errors. In
......@@ -93,40 +120,3 @@ optimization, using the Aesara flag
same effect by running in the modes ``FAST_COMPILE`` (it will not apply this
optimization, nor most other optimizations) or ``DebugMode`` (it will test
before and after all optimizations (much slower)).
Specifying Exact Shape
======================
Currently, specifying a shape is not as easy and flexible as we wish and we plan some
upgrade. Here is the current state of what can be done:
- You can pass the shape info directly to the ``ConvOp`` created
when calling ``conv2d``. You simply set the parameters ``image_shape``
and ``filter_shape`` inside the call. They must be tuples of 4
elements. For example:
.. code-block:: python
aesara.tensor.nnet.conv2d(..., image_shape=(7, 3, 5, 5), filter_shape=(2, 3, 4, 4))
- You can use the ``SpecifyShape`` op to add shape information anywhere in the
graph. This allows to perform some optimizations. In the following example,
this makes it possible to precompute the Aesara function to a constant.
>>> import aesara
>>> x = aesara.tensor.matrix()
>>> x_specify_shape = aesara.tensor.specify_shape(x, (2, 2))
>>> f = aesara.function([x], (x_specify_shape ** 2).shape)
>>> aesara.printing.debugprint(f) # doctest: +NORMALIZE_WHITESPACE
DeepCopyOp [id A] '' 0
|TensorConstant{(2,) of 2} [id B]
Future Plans
============
The parameter "constant shape" will be added to ``aesara.shared()``. This is probably
the most frequent occurrence with ``shared`` variables. It will make the code
simpler and will make it possible to check that the shape does not change when
updating the ``shared`` variable.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论