提交 f1b7e125 authored 作者: James Bergstra's avatar James Bergstra

Added tensor.autocast_int and tensor.autocast_float

These two functions are used by as_tensor_variable to determine how to turn python ints and floats into ndarrays for TensorConstants. This provides an i-hope-not-too-hacky way for config.floatX=='float32' to make it so that python literals like 1.1 don't force an upcast in expressions like (fvector() + 1.1). Another option would have been to leave the downcast of 1.1 in the graph as a symbolic node that would be pre-computed at compile time, but I think that would behave pretty similarly, and further burden the optimizer.
上级 120eb3c7
...@@ -3,6 +3,10 @@ __docformat__ = "restructuredtext en" ...@@ -3,6 +3,10 @@ __docformat__ = "restructuredtext en"
from basic import * from basic import *
if config.floatX == 'float32':
# change the default casting behaviour for python floats to always cast to float32
autocast_float.dtypes = ('float32',)
import opt import opt
import blas import blas
import xlogx import xlogx
......
...@@ -92,7 +92,7 @@ def as_tensor_variable(x, name = None, ndim=None): ...@@ -92,7 +92,7 @@ def as_tensor_variable(x, name = None, ndim=None):
""" """
if hasattr(x, '_as_TensorVariable'): if hasattr(x, '_as_TensorVariable'):
return x._as_TensorVariable() return x._as_TensorVariable() #TODO: pass name and ndim arguments
if isinstance(x, gof.Apply): if isinstance(x, gof.Apply):
#TODO: use Apply's default output mechanism #TODO: use Apply's default output mechanism
...@@ -138,6 +138,44 @@ _as_tensor_variable = as_tensor_variable ...@@ -138,6 +138,44 @@ _as_tensor_variable = as_tensor_variable
as_tensor = as_tensor_variable as_tensor = as_tensor_variable
class NumpyAutocaster(object):
""" This class is used to cast python ints and floats to numpy arrays.
Python ints are always 64bit and floats are always double precision.
This class uses the algorithm in __call__ to use a narrower dtype when no precision would
be lost, and to even lose precision when this is demanded (e.g. to automatically cast all
floats to single-precision).
"""
def __init__(self, dtypes):
self.dtypes = tuple(dtypes)
def __call__(self, x):
for dtype in self.dtypes:
x_ = numpy.asarray(x, dtype=dtype)
if numpy.all(x == x_):
break
# returns either an exact x_==x, or the last casted x_
return x_
autocast_int = NumpyAutocaster(('int8', 'int16', 'int32', 'int64'))
autocast_float = NumpyAutocaster(('float32', 'float64'))
# autocast_float dtypes might be manipulated in tensor.__init__
class autocast_float_as(object):
"""This class makes it possible to temporarily and locally adjust autocasting behaviour.
For example:
>>> with autocast_float_as('float32') as _dummy:
>>> assert (fvector() + 1.1).dtype == 'float32' # temporary downcasting
>>> assert (fvector() + 1.1).dtype == 'float64' # back to default behaviour
This class might be convenient in some code, but it definitely helps to test the
autocasting mechanism.
"""
def __init__(self, *dtypes):
self.dtypes = dtypes
def __enter__(self):
self.old_dtypes = autocast_float.dtypes
autocast_float.dtypes = self.dtypes
def __exit__(self, *args):
autocast_float.dtypes = self.old_dtypes
def constant_or_value(x, rtype, name=None, ndim=None, dtype=None): def constant_or_value(x, rtype, name=None, ndim=None, dtype=None):
"""Return a symbolic `Constant` with value `x` """Return a symbolic `Constant` with value `x`
...@@ -148,21 +186,16 @@ def constant_or_value(x, rtype, name=None, ndim=None, dtype=None): ...@@ -148,21 +186,16 @@ def constant_or_value(x, rtype, name=None, ndim=None, dtype=None):
""" """
if dtype is not None: if dtype is not None:
# in this case, the semantics are that the caller is forcing the dtype
x_ = numpy.asarray(x, dtype=dtype) x_ = numpy.asarray(x, dtype=dtype)
else: else:
# in this case, this function should infer the dtype according to the autocasting
# rules. See autocasting above.
x_ = None x_ = None
if rtype is TensorConstant and isinstance(x, int): if rtype is TensorConstant and isinstance(x, int):
for dtype in ['int8', 'int16', 'int32', 'int64']: x_ = autocast_int(x)
x_ = numpy.asarray(x, dtype=dtype)
if numpy.all(x == x_):
break
x_ = None
elif rtype is TensorConstant and isinstance(x, float): elif rtype is TensorConstant and isinstance(x, float):
for dtype in ['float32', 'float64']: x_ = autocast_float(x)
x_ = numpy.asarray(x, dtype=dtype)
if numpy.all(x == x_):
break
x_ = None
elif isinstance(x, numpy.ndarray): elif isinstance(x, numpy.ndarray):
x_ = x x_ = x
else: else:
......
...@@ -2220,6 +2220,53 @@ def test_default_state(): ...@@ -2220,6 +2220,53 @@ def test_default_state():
assert f(1) == 4.8 assert f(1) == 4.8
assert f(2.2) == 7 assert f(2.2) == 7
def test_autocast():
orig_autocast = autocast_float.dtypes
# test that autocast_float_as sets the autocast dtype correctly
with autocast_float_as('float32') as ac:
assert autocast_float.dtypes == ('float32',)
assert autocast_float.dtypes == orig_autocast
with autocast_float_as('float64') as ac:
assert autocast_float.dtypes == ('float64',)
assert autocast_float.dtypes == orig_autocast
# test that we can set it back to something, and nest it
with autocast_float_as('float32') as ac:
assert autocast_float.dtypes == ('float32',)
with autocast_float_as('float64') as ac:
assert autocast_float.dtypes == ('float64',)
assert autocast_float.dtypes == ('float32',)
assert autocast_float.dtypes == orig_autocast
# test that the autocasting dtype is used correctly in expression-building
with autocast_float_as('float32') as ac:
assert (dvector()+ 1.1).dtype == 'float64'
assert (fvector()+ 1.1).dtype == 'float32'
assert (fvector()+ numpy.asarray(1.1,dtype='float64')).dtype == 'float64'
assert (fvector()+ numpy.asarray(1.1,dtype='float32')).dtype == 'float32'
assert (dvector()+ 1).dtype == 'float64'
assert (fvector()+ 1).dtype == 'float32'
# test that the autocasting dtype is used correctly in expression-building
with autocast_float_as('float64') as ac:
assert (dvector()+ 1.1).dtype == 'float64'
assert (fvector()+ 1.1).dtype == 'float64'
assert (fvector()+ 1.0).dtype == 'float64'
assert (fvector()+ numpy.asarray(1.1,dtype='float64')).dtype == 'float64'
assert (fvector()+ numpy.asarray(1.1,dtype='float32')).dtype == 'float32'
assert (dvector()+ 1).dtype == 'float64'
assert (fvector()+ 1).dtype == 'float32'
# test that the autocasting dtype is used correctly in expression-building
with autocast_float_as('float32', 'float64') as ac:
assert (dvector()+ 1.1).dtype == 'float64'
assert (fvector()+ 1.1).dtype == 'float64'
assert (fvector()+ 1.0).dtype == 'float32'
with autocast_float_as('float64') as ac:
assert (fvector()+ 1.0).dtype == 'float64'
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) >= 2 and sys.argv[1] == 'OPT': if len(sys.argv) >= 2 and sys.argv[1] == 'OPT':
default_mode = compile.Mode(linker = 'c&py', default_mode = compile.Mode(linker = 'c&py',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论