提交 55693fb0 authored 作者: Pascal Lamblin's avatar Pascal Lamblin

Merge pull request #2187 from nouiz/Tanjay94-Choose

Add: tensor.choose and typed_list.make_list
......@@ -754,6 +754,8 @@ Creating Tensor
>>> f(x, x, x, x).shape
(2, 2, 4, 4)
.. autofunction:: theano.tensor.basic.choose
Reductions
==========
......
......@@ -5092,3 +5092,79 @@ def swapaxes(y, axis1, axis2):
li = range(0, ndim)
li[axis1], li[axis2] = li[axis2], li[axis1]
return y.dimshuffle(li)
def choose(a, choices, out=None, mode='raise'):
"""
Construct an array from an index array and a set of arrays to choose from.
First of all, if confused or uncertain, definitely look at the Examples - in its full generality, this function is less simple than it might seem from the following code description (below ndi = numpy.lib.index_tricks):
np.choose(a,c) == np.array([c[a[I]][I] for I in ndi.ndindex(a.shape)]).
But this omits some subtleties. Here is a fully general summary:
Given an ``index`` array (a) of integers and a sequence of n arrays (choices), a and each choice array are first broadcast, as necessary, to arrays of a common shape; calling these Ba and Bchoices[i], i = 0,...,n-1 we have that, necessarily, Ba.shape == Bchoices[i].shape for each i. Then, a new array with shape Ba.shape is created as follows:
if mode=raise (the default), then, first of all, each element of a (and thus Ba) must be in the range [0, n-1]; now, suppose that i (in that range) is the value at the (j0, j1, ..., jm) position in Ba - then the value at the same position in the new array is the value in Bchoices[i] at that same position;
if mode=wrap, values in a (and thus Ba) may be any (signed) integer; modular arithmetic is used to map integers outside the range [0, n-1] back into that range; and then the new array is constructed as above;
if mode=clip, values in a (and thus Ba) may be any (signed) integer; negative integers are mapped to 0; values greater than n-1 are mapped to n-1; and then the new array is constructed as above.
:Parameters: *a* - int array
This array must contain integers in [0, n-1], where n is the number of choices, unless mode=wrap or mode=clip, in which cases any integers are permissible.
:Parameters: *choices* - sequence of arrays
Choice arrays. a and all of the choices must be broadcastable to the same shape. If choices is itself an array (not recommended), then its outermost dimension (i.e., the one corresponding to choices.shape[0]) is taken as defining the ``sequence``.
:Parameters: *out* - array, optional
If provided, the result will be inserted into this array. It should be of the appropriate shape and dtype.
:Parameters: *mode* - {``raise`` (default), ``wrap``, ``clip``}, optional
Specifies how indices outside [0, n-1] will be treated:
``raise`` : an exception is raised
``wrap`` : value becomes value mod n
``clip`` : values < 0 are mapped to 0, values > n-1 are mapped to n-1
:Returns: merged_array - array
The merged result.
:Raises:
ValueError - shape mismatch
If a and each choice array are not all broadcastable to the same shape.
"""
# This is done to keep the same function signature then NumPy.
assert out is None
return Choose(mode)(a, choices)
class Choose(Op):
__props__ = ('mode',)
def __init__(self, mode):
assert mode in ("raise", "wrap", "clip")
self.mode = mode
def infer_shape(self, node, shapes):
if isinstance(node.inputs[1], TensorVariable):
return[(shapes[0])]
else:
import theano.typed_list
assert isinstance(node.inputs[1], theano.typed_list.TypedListVariable)
raise ShapeError("Case not implemented")
shape = shapes[0]
for i in range(len(shapes[0])-1):
shape[i] = shapes[1][i]
return [(shape)]
def make_node(self, a, choices):
# Import here as it isn't imported by default and we can't
# import at the top as it would cause circular import.
from theano import typed_list
a = as_tensor_variable(a)
if isinstance(choices, (tuple, list)):
choice = theano.typed_list.make_list(choices)
else:
choice = as_tensor_variable(choices)
return Apply(self, [a, choice], [a.type()])
def perform(self, node, inputs, (z, )):
a = inputs[0]
choice = inputs[1]
# TODO reuse out?
z[0] = numpy.choose(a, choice, mode=self.mode)
......@@ -11,7 +11,7 @@ from itertools import izip
# Import builtin min to be able to use it after importing the tensor version.
import __builtin__
builtin_min = __builtin__.min
from nose.tools import assert_raises
from nose.plugins.skip import SkipTest
from nose.plugins.attrib import attr
import numpy
......@@ -46,7 +46,7 @@ from theano.tensor import (_shared, wvector, bvector, autocast_float_as,
itensor3, Tile, switch, Diagonal, Diag,
nonzero, flatnonzero, nonzero_values,
stacklists, DimShuffle, hessian, ptp, power,
swapaxes
swapaxes, choose, Choose
)
from theano.tests import unittest_tools as utt
......@@ -6984,7 +6984,8 @@ class T_swapaxes(unittest.TestCase):
t_s = fn(a)
assert numpy.allclose(n_s, t_s)
class T_Power():
class T_Power(unittest.TestCase):
def test_numpy_compare(self):
rng = numpy.random.RandomState(utt.fetch_seed())
A = tensor.matrix("A", dtype=theano.config.floatX)
......@@ -6997,29 +6998,103 @@ class T_Power():
assert numpy.allclose(n_p, t_p)
def test_multiple_power(self):
x = tensor.matrix()
x = tensor.vector()
y = [1, 2, 3]
z = power(x, y)
f = function([x], z)
assert allclose(f([1, 2, 3]), [1, 4, 27])
assert numpy.allclose(f([1, 2, 3]), [1, 4, 27])
def test_wrong_shape(self):
x = tensor.matrix()
x = tensor.vector()
y = [1, 2, 3]
z = power(x, y)
f = function([x], z)
self.assertRaise(ValueError, f, [1, 2, 3, 4])
self.assertRaises(ValueError, f, [1, 2, 3, 4])
class T_Choose(utt.InferShapeTester):
op = staticmethod(choose)
op_class = Choose
def test_numpy_compare(self):
rng = numpy.random.RandomState(utt.fetch_seed())
A = tensor.matrix("A", dtype=theano.config.floatX)
Q = power(A, 2)
fn = function([A], [Q])
a = rng.rand(4, 4).astype(theano.config.floatX)
n_p = numpy.power(a, 2)
t_p = fn(a)
assert numpy.allclose(n_s, t_s)
a = tensor.vector(dtype='int64')
b = tensor.matrix(dtype='int64')
A = numpy.asarray(numpy.random.rand(4), dtype='int64')
B = numpy.asarray(numpy.random.rand(4, 4), dtype='int64')
modes = ['raise', 'wrap', 'clip']
for m in modes:
f = function([a, b], choose(a, b, mode=m))
t_c = f(A, B)
n_c = numpy.choose(A, B, mode=m)
assert numpy.allclose(t_c, n_c)
def test_numpy_compare_tuple(self):
a = tensor.tensor3(dtype='int64')
b = tensor.tensor3(dtype='int64')
c = tensor.tensor3(dtype='int64')
A = numpy.asarray(numpy.random.rand(2, 1, 1), dtype='int64')
B = numpy.asarray(numpy.random.rand(1, 6, 1), dtype='int64')
C = numpy.asarray(numpy.random.rand(1, 1, 5), dtype='int64')
f = function([a, b, c], choose(a, (b, c)))
t_c = f(A, B, C)
n_c = numpy.choose(A, (B, C))
assert numpy.allclose(t_c, n_c)
def test_infer_shape(self):
a = tensor.matrix(dtype='int64')
b = tensor.vector(dtype='int64')
c = tensor.matrix(dtype='int64')
d = tensor.vector(dtype='int64')
A = numpy.asarray(numpy.random.rand(5, 4), dtype='int64')
B = numpy.asarray(numpy.random.rand(4), dtype='int64')
C = numpy.asarray(numpy.random.rand(7, 4), dtype='int64')
D = numpy.asarray(numpy.random.rand(4), dtype='int64')
var1 = [a, b, a, b]
var2 = [c, d, b, a]
mat1 = [A, B, A, B]
mat2 = [C, D, B, A]
for v, m, w, n in zip(var1, mat1, var2, mat2):
self._compile_and_check([v, w], # theano.function inputs
[self.op(v, w)], # theano.function outputs
# Always use not square matrix!
# inputs data
[m, n],
# Op that should be removed from the graph.
self.op_class)
# Disabled as it isn't implemented.
def ___test_infer_shape_tuple(self):
a = tensor.tensor3(dtype='int64')
b = tensor.tensor3(dtype='int64')
c = tensor.tensor3(dtype='int64')
A = numpy.asarray([1, 0], dtype='int64').reshape((2, 1, 1))
B = numpy.asarray(numpy.random.rand(1, 4, 1), dtype='int64')
C = numpy.asarray(numpy.random.rand(1, 1, 7), dtype='int64')
f = function([a, b, c], choose(a, (b, c)))
shape = (2, 4, 7)
assert numpy.allclose(f(A, B, C).shape, shape)
self._compile_and_check([a, b, c], # theano.function inputs
[self.op(a, (b, c))], # theano.function outputs
# Always use not square matrix!
# inputs data
[A, B, C],
# Op that should be removed from the graph.
self.op_class)
"""
......
......@@ -583,6 +583,9 @@ class _tensor_py_operators:
"""Fill inputted tensor with the assigned value"""
return theano.tensor.basic.fill(self, value)
def choose(self, a, choices, out=None, mode='raise'):
"""Construct an array from an index array and a set of arrays to choose from."""
return theano.tensor.basic.choose(self, a, choices, out=None, mode='raise')
class TensorVariable(_tensor_py_operators, Variable):
"""Subclass to add the tensor operators to the basic `Variable` class."""
......
......@@ -567,3 +567,38 @@ Returns the size of a list.
:param x: typed list.
"""
class MakeList(Op):
def __eq__(self, other):
return type(self) == type(other)
def __hash__(self):
return hash(type(self))
def make_node(self, a):
assert isinstance(a, (tuple, list))
a2 = []
for elem in a:
if not isinstance(elem, theano.gof.Variable):
elem = as_tensor_variable(elem)
a2.append(elem)
if not all(a2[0].type == elem.type for elem in a2):
raise TypeError(
"MakeList need all input variable to be of the same type.")
tl = theano.typed_list.TypedListType(a2[0].type)()
return Apply(self, a2, [tl])
def perform(self, node, inputs, (out, )):
out[0] = list(inputs)
make_list = MakeList()
"""
Build a Python list from those Theano variable.
:param a: tuple/list of Theano variable
:note: All Theano variable must have the same type.
"""
......@@ -10,7 +10,7 @@ from theano.tensor.type_other import SliceType
from theano.typed_list.type import TypedListType
from theano.typed_list.basic import (GetItem, Insert,
Append, Extend, Remove, Reverse,
Index, Count, Length)
Index, Count, Length, make_list, MakeList)
from theano import sparse
from theano.tests import unittest_tools as utt
# TODO, handle the case where scipy isn't installed.
......@@ -553,3 +553,32 @@ class test_length(unittest.TestCase):
x = rand_ranged_matrix(-1000, 1000, [100, 101])
self.assertTrue(f([x, x]) == 2)
class T_MakeList(unittest.TestCase):
def test_wrong_shape(self):
a = T.vector()
b = T.matrix()
self.assertRaises(TypeError, make_list, (a,b))
def correct_answer(self):
a = T.matrix()
b = T.matrix()
x = T.tensor3()
y = T.tensor3()
A = numpy.random.rand(5)
B = numpy.random.rand(7)
X = numpy.random.rand(5,6)
Y = numpy.random.rand(1,9)
c = make_list((a, b))
z = make_list((x, y))
fc = function([a, b], c)
fz = function([x, y], z)
self.assertTrue(f([A, B]) == [A, B])
self.assertTrue(f([X, Y]) == [X, Y])
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论