提交 8d3a67b7 authored 作者: Pascal Lamblin's avatar Pascal Lamblin

Merge pull request #2875 from Saizheng/master

TH-2695 Accept symbolic `reps` in `tensor.tile`
"""A `Type` and `Op` classes to work with numpy.ndarrays symbolically.""" """A `Type` and `Op` classes to work with numpy.ndarrays symbolically."""
from six.moves import builtins
import sys import sys
import warnings import warnings
...@@ -4711,32 +4712,67 @@ def tile(x, reps, ndim=None): ...@@ -4711,32 +4712,67 @@ def tile(x, reps, ndim=None):
See the docstring of `numpy.tile` for details. See the docstring of `numpy.tile` for details.
Currently, x.ndim and len(reps) must be equal, and, if specified, 'ndim' 'reps' can be constant integer (e.g. 3), constant vector(e.g. [2 3]),
must be equal to both. symbolic scalar (e.g. tensor.iscalar()), symbolic vector (e.g. tensor.ivector())
or a list of symbolic scalar (e.g. [tensor.iscalar(), tensor.iscalar()]).
TODO: expand this. ndim is the number of the dimensions of the output, if it is provided, ndim
should be equal or larger than x.ndim and len(reps), otherwise, we will use
max(x.ndim, len(reps)) as ndim. If reps is symbolic vector, the ndim has to
be provided.
""" """
try: if ndim is not None and ndim < x.ndim:
iter(reps) raise ValueError("ndim should be equal or larger than x.ndim")
except TypeError:
raise ValueError("reps must be iterable")
if not numpy.all([isinstance(r, integer_types) or
(isinstance(r, TensorVariable) and
r.dtype in ["int8", "int16", "int32", "int64"])
for r in reps]):
raise ValueError("elements of reps must be scalars of integer dtype")
elif len(reps) != x.ndim:
raise ValueError("len(reps) != x.ndim not currently supported")
elif (ndim is not None) and ndim != x.ndim:
raise ValueError("if specified, ndim must be equal to both x.ndim and "
"len(reps)")
if ndim is None: # if reps is tensor.scalar, integer or tensor.vector, we convert it to a list.
ndim = len(reps) if not isinstance(reps, (list, tuple)):
reps_astensor = as_tensor_variable(reps)
ndim_check = reps_astensor.ndim
if reps_astensor.dtype not in theano.tensor.discrete_dtypes:
raise ValueError("elements of reps must be integer dtype")
# tensor.scalar/integer case
if ndim_check == 0:
reps = [reps]
# tensor.vector case
elif ndim_check == 1:
if ndim is None:
raise ValueError("if reps is tensor.vector, you should specify "
"the ndim")
else:
offset = ndim - reps.shape[0]
# assert that reps.shape[0] does not exceed ndim
offset = theano.tensor.opt.assert_(offset, ge(offset, 0))
# if reps.ndim is less than x.ndim, we pad the reps with
# "1" so that reps will have the same ndim as x.
reps_ = [switch(i < offset, 1, reps[i - offset]) for i in range(ndim)]
reps = reps_
# other raise error
else:
raise ValueError("the dimension of reps should not exceed 1")
else:
if ndim is not None and len(reps) > ndim:
raise ValueError("len(reps) should be equal or less than ndim")
if not numpy.all([isinstance(r, integer_types) or
(isinstance(r, TensorVariable) and
r.dtype in theano.tensor.discrete_dtypes) for r in reps]):
raise ValueError("elements of reps must be scalars of integer dtype")
# if reps.ndim is less than x.ndim, we pad the reps with
# "1" so that reps will have the same ndim as x.
reps = list(reps) reps = list(reps)
shape = [x.shape[i] for i in xrange(ndim)] if ndim is None:
ndim = builtins.max(len(reps), x.ndim)
if len(reps) < ndim:
reps = [1] * (ndim - len(reps)) + reps
shape = [1] * (ndim - x.ndim) + [x.shape[i] for i in xrange(x.ndim)]
alloc_shape = reps + shape alloc_shape = reps + shape
y = alloc(x, *alloc_shape) y = alloc(x, *alloc_shape)
shuffle_ind = numpy.arange(ndim * 2).reshape(2, ndim) shuffle_ind = numpy.arange(ndim * 2).reshape(2, ndim)
......
...@@ -5292,6 +5292,104 @@ def test_tile(): ...@@ -5292,6 +5292,104 @@ def test_tile():
assert numpy.all(run_tile(x, x_, (2, 3, 4, 6), use_symbolic_reps) == assert numpy.all(run_tile(x, x_, (2, 3, 4, 6), use_symbolic_reps) ==
numpy.tile(x_, (2, 3, 4, 6))) numpy.tile(x_, (2, 3, 4, 6)))
# Test when reps is integer, tensor.scalar or tensor.vector.
# Test 1,2,3,4-dimensional cases.
# Test input x has the shape [2], [2, 4], [2, 4, 3], [2, 4, 3, 5].
test_shape = [2, 4, 3, 5]
k = 0
for xtype in [vector(), matrix(), tensor3(), tensor4()]:
x = xtype
k = k+1
x_ = rng.randn(*test_shape[0:k]).astype(config.floatX)
# integer:
reps_ = 2
f = function([x], tile(x, reps_))
assert numpy.all( f(x_) == numpy.tile(x_, reps_))
# tensor.scalar:
reps = iscalar()
reps_ = 2
f = function([x, reps], tile(x, reps))
assert numpy.all( f(x_, reps_) == numpy.tile(x_, reps_))
# tensor.vector:
reps = ivector()
reps_ = [2] if k == 1 or k == 2 else [2, 3]
ndim_ = k
f = function([x, reps], tile(x, reps, ndim_))
assert numpy.all( f(x_, reps_) == numpy.tile(x_, reps_))
# list of integers:
reps_ = [2, 3, 4]
f = function([x], tile(x, reps_))
assert numpy.all( f(x_) == numpy.tile(x_, reps_))
# list of integers and tensor.scalars:
d = iscalar()
reps = [2, d, 4]
f = function([x, d], tile(x, reps))
reps_ = [2, 3, 4]
assert numpy.all( f(x_, 3) == numpy.tile(x_, reps_))
# reps is list, len(reps) > x.ndim, 3 cases below:
r = [2, 3, 4, 5, 6]
reps_ = r[:k+1] # len(reps_) = x.ndim+1
# (1) ndim = None.
f = function([x], tile(x, reps_))
assert numpy.all( f(x_) == numpy.tile(x_, reps_))
# (2) ndim = len(reps).
ndim_ = len(reps_)
f = function([x], tile(x, reps_, ndim_))
assert numpy.all( f(x_) == numpy.tile(x_, reps_))
# (3) ndim > len(reps)
ndim_ = len(reps_) + 1
f = function([x], tile(x, reps_, ndim_))
assert numpy.all( f(x_) == numpy.tile(x_, [1] + reps_))
# reps is list, ndim > x.ndim > len(reps):
r = [2, 3, 4, 5]
if k > 1:
ndim_ = k+1
reps_ = r[:k-1]
f = function([x], tile(x, reps_, ndim_))
assert numpy.all( f(x_) == numpy.tile(x_, [1, 1] + reps_))
# error raising test: ndim not specified when reps is vector
reps = ivector()
numpy.testing.assert_raises(ValueError, tile, x, reps)
# error raising test: not a integer
for reps in [2.5, fscalar(), fvector()]:
numpy.testing.assert_raises(ValueError, tile, x, reps)
# error raising test: the dimension of reps exceeds 1
reps = imatrix()
numpy.testing.assert_raises(ValueError, tile, x, reps)
# error raising test: ndim is not None, ndim < x.ndim
# 3 cases below (reps is list/tensor.scalar/tensor.vector):
for reps in [[2,3,4], iscalar(), ivector()]:
if k > 1:
ndim = k-1
numpy.testing.assert_raises(ValueError, tile, x, reps, ndim)
# error raising test: reps is list, len(reps) > ndim
r = [2, 3, 4, 5, 6]
reps = r[:k+1]
ndim = k
numpy.testing.assert_raises(ValueError, tile, x, reps, ndim)
# error raising test:
# reps is tensor.vector and len(reps_value) > ndim,
# reps_value is the real value when excuting the function.
reps = ivector()
r = [2, 3, 4, 5, 6, 7]
reps_ = r[:k+2]
ndim_ = k+1
f = function([x, reps], tile(x, reps, ndim_))
numpy.testing.assert_raises(AssertionError, f, x_, reps_)
def test_tile_grad(): def test_tile_grad():
def grad_tile(x, reps, np_x): def grad_tile(x, reps, np_x):
......
...@@ -3850,25 +3850,6 @@ class T_Tile(unittest.TestCase): ...@@ -3850,25 +3850,6 @@ class T_Tile(unittest.TestCase):
assert isinstance(topo[0].op, compile.DeepCopyOp) assert isinstance(topo[0].op, compile.DeepCopyOp)
f(data) f(data)
# If the repeat parameter is longer then v.ndim, we must
# replace it with a DimShuffle to add the extra parameter.
# But it isn't supported for now, so assert that we raise an
# error.
self.assertRaises(ValueError, T.tile, v, (1,)*(v.ndim+1))
# If the repeat parameter is shorter then m.ndim, it should
# pad tot he left the repeat patter with 1. It is not supported for now.
#f = theano.function([var], T.tile(v, (1,)*(v.ndim+1)))
#topo = f.maker.fgraph.toposort()
#assert len(topo) == 1
#assert isinstance(topo[0].op, DimShuffe)
self.assertRaises(ValueError, T.tile, m, (1,)*(m.ndim-1))
#f = theano.function([var], T.tile(m, (1,)*(m.ndim-1)))
#topo = f.maker.fgraph.toposort()
#assert len(topo) == 1
#assert isinstance(topo[0].op, compile.DeepCopyOp)
def speed_local_pow_specialize_range(): def speed_local_pow_specialize_range():
val = numpy.random.rand(1e7) val = numpy.random.rand(1e7)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论