提交 7dafb259 authored 作者: Olivier Delalleau's avatar Olivier Delalleau

Giving up on trying to mimic numpy's mixed scalar/array behavior

上级 5e27dfd9
...@@ -159,18 +159,24 @@ import theano and print the config variable, as in: ...@@ -159,18 +159,24 @@ import theano and print the config variable, as in:
Default: 'custom' Default: 'custom'
This specifies how data types are implicitly figured out in Theano, e.g. for This specifies how data types are implicitly figured out in Theano, e.g. for
constants or in the result of arithmetic operations. The recommended value is constants or in the results of arithmetic operations. The current default
'numpy+floatX', that mimics numpy's behavior except for floats when value ('custom') corresponds to a set of custom rules originally used in
``config.floatX`` is set to 'float32', for which we use float32 instead of Theano (which can be partially customized, see e.g. the in-code help of
float64 unless the user is explicitly using data typed as float64. When ``tensor.NumpyAutocaster``). However the 'custom' option will be
'numpy' is used, this specific floatX behavior is discarded. The current deprecated in a future release of Theano. The 'numpy' setting attempts to
default value is 'custom' for backward compatibility reason, and corresponds mimic the numpy casting rules. 'numpy+floatX' does the same, except that
to a set of custom rules originally used in Theano (which can be partially it prefers to use float32 numbers instead of float64 when ``config.floatX``
customized, see e.g. the in-code help of ``tensor.NumpyAutocaster``). The is set to 'float32' (this will become the default value in a future
'custom' option will be deprecated in a future release of Theano. release of Theano). Note that both 'numpy' and 'numpy+floatX'
**Until further notice, it is strongly advised to never change this option behave differently from numpy on purpose in the following situations:
within a script, and to always clean your Theano cache whenever you modify its * Depending on the value of ``config.int_division``, the resulting type
value**. of a division of integer types with the ``/`` operator may not match
that of numpy.
* On mixed scalar / array operations, numpy tries to prevent the scalar
from upcasting the array's type unless it is of a fundamentally
different type. However it is not practical to implement in Theano
a behavior similar to the one currently found in numpy, so Theano
does not attempt to do the same.
.. attribute:: int_division .. attribute:: int_division
...@@ -178,14 +184,14 @@ import theano and print the config variable, as in: ...@@ -178,14 +184,14 @@ import theano and print the config variable, as in:
Default: 'int' Default: 'int'
Specifies what to do when one tries to compute `x / y`, where both `x` and Specifies what to do when one tries to compute ``x / y``, where both ``x`` and
`y` are of integer types (possibly unsigned). 'int' means an integer is ``y`` are of integer types (possibly unsigned). 'int' means an integer is
returned (as in Python 2.X), but this behavior is deprecated. 'floatX' returned (as in Python 2.X), but this behavior is deprecated. 'floatX'
returns a number of type given by ``config.floatX`. 'raise' is the safest returns a number of type given by ``config.floatX``. 'raise' is the safest
choice (and will become default in a future release of Theano) and raises choice (and will become default in a future release of Theano) and raises
an error when one tries to do such an operation, enforcing the use of the an error when one tries to do such an operation, enforcing the use of the
integer division operator (``//``) (if a float result is intended, either integer division operator (``//``) (if a float result is intended, either
cast one of the arguments to a float, or use `x.__truediv__(y)`). cast one of the arguments to a float, or use ``x.__truediv__(y)``).
.. attribute:: mode .. attribute:: mode
......
...@@ -4407,7 +4407,7 @@ def _test_autocast_numpy_floatX(): ...@@ -4407,7 +4407,7 @@ def _test_autocast_numpy_floatX():
class test_arithmetic_cast(unittest.TestCase): class test_arithmetic_cast(unittest.TestCase):
""" """
Test output types of typical arithmeric operations (* / + - //). Test output types of basic arithmeric operations (* / + - //).
We only test the behavior for `config.cast_policy` set to either 'numpy' or We only test the behavior for `config.cast_policy` set to either 'numpy' or
'numpy+floatX': the 'custom' behavior is (at least partially) tested in 'numpy+floatX': the 'custom' behavior is (at least partially) tested in
...@@ -4435,11 +4435,13 @@ class test_arithmetic_cast(unittest.TestCase): ...@@ -4435,11 +4435,13 @@ class test_arithmetic_cast(unittest.TestCase):
for a_type in dtypes: for a_type in dtypes:
for b_type in dtypes: for b_type in dtypes:
# Note that we do not test division between # Note that we do not test division between
# integers as this is currently forbidden. # integers if it is forbidden.
if (op is operator.div and # Theano deals with integer division in its own
# special way (depending on `config.int_division`).
is_int_division = (
op is operator.div and
a_type in tensor.discrete_dtypes and a_type in tensor.discrete_dtypes and
b_type in tensor.discrete_dtypes): b_type in tensor.discrete_dtypes)
continue
# We will test all meaningful combinations of # We will test all meaningful combinations of
# scalar and array operations. # scalar and array operations.
for combo in ( for combo in (
...@@ -4449,17 +4451,29 @@ class test_arithmetic_cast(unittest.TestCase): ...@@ -4449,17 +4451,29 @@ class test_arithmetic_cast(unittest.TestCase):
('array', 'scalar'), ('array', 'scalar'),
('i_scalar', 'i_scalar'), ('i_scalar', 'i_scalar'),
): ):
theano_args = map(eval, theano_args = map(eval,
['theano_%s' % c for c in combo]) ['theano_%s' % c for c in combo])
numpy_args = map(eval, numpy_args = map(eval,
['numpy_%s' % c for c in combo]) ['numpy_%s' % c for c in combo])
try:
theano_dtype = op( theano_dtype = op(
theano_args[0](a_type), theano_args[0](a_type),
theano_args[1](b_type)).type.dtype theano_args[1](b_type)).type.dtype
# Should have crashed if it is an integer
# division and `config.int_division` does
# not allow it.
assert not (is_int_division and
config.int_division == 'raise')
except theano.scalar.IntegerDivisionError:
assert (is_int_division and
config.int_division == 'raise')
# This is the expected behavior.
continue
# For numpy we have a problem: # For numpy we have a problem:
# http://projects.scipy.org/numpy/ticket/1827 # http://projects.scipy.org/numpy/ticket/1827
# The current expected behavior is to use # As a result we only consider the highest data
# the highest data type that numpy may return. # type that numpy may return.
numpy_dtypes = [ numpy_dtypes = [
op(numpy_args[0](a_type), op(numpy_args[0](a_type),
numpy_args[1](b_type)).dtype, numpy_args[1](b_type)).dtype,
...@@ -4467,6 +4481,9 @@ class test_arithmetic_cast(unittest.TestCase): ...@@ -4467,6 +4481,9 @@ class test_arithmetic_cast(unittest.TestCase):
numpy_args[0](a_type)).dtype] numpy_args[0](a_type)).dtype]
numpy_dtype = theano.scalar.upcast( numpy_dtype = theano.scalar.upcast(
*map(str, numpy_dtypes)) *map(str, numpy_dtypes))
if numpy_dtype == theano_dtype:
# Same data type found, all is good!
continue
if (cfg == 'numpy+floatX' and if (cfg == 'numpy+floatX' and
config.floatX == 'float32' and config.floatX == 'float32' and
a_type != 'float64' and a_type != 'float64' and
...@@ -4474,8 +4491,40 @@ class test_arithmetic_cast(unittest.TestCase): ...@@ -4474,8 +4491,40 @@ class test_arithmetic_cast(unittest.TestCase):
numpy_dtype == 'float64'): numpy_dtype == 'float64'):
# We should keep float32. # We should keep float32.
assert theano_dtype == 'float32' assert theano_dtype == 'float32'
else: continue
assert theano_dtype == numpy_dtype if 'array' in combo and 'scalar' in combo:
# For mixed scalar / array operations,
# Theano may differ from numpy as it does
# not try to prevent the scalar from
# upcasting the array.
array_type, scalar_type = (
(a_type, b_type)[
list(combo).index(arg)]
for arg in ('array', 'scalar'))
up_type = theano.scalar.upcast(array_type,
scalar_type)
if (
# The two data types are different.
scalar_type != array_type and
# The array type is not enough to hold
# the scalar type as well.
array_type != up_type and
# Theano upcasted the result array.
theano_dtype == up_type and
# But Numpy kept its original type.
# (not an equality because of numpy bug
# mentioned above).
array_type in numpy_dtypes):
# Then we accept this difference in
# behavior.
continue
if (is_int_division and
config.int_division == 'floatX'):
assert theano_dtype == config.floatX
continue
# In any other situation: something wrong is
# going on!
assert False
finally: finally:
config.cast_policy = backup_config config.cast_policy = backup_config
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论