def make_node(self, a, val, offset): a = aet.as_tensor_variable(a) val = aet.as_tensor_variable(val) offset = aet.as_tensor_variable(offset) if a.ndim != 2: raise TypeError( "%s: first parameter must have exactly" " two dimensions" % self.__class__.__name__ ) elif val.ndim != 0: raise TypeError( f"{self.__class__.__name__}: second parameter must be a scalar" ) elif offset.ndim != 0: raise TypeError( f"{self.__class__.__name__}: third parameter must be a scalar" ) val = aet.cast(val, dtype=upcast(a.dtype, val.dtype)) if val.dtype != a.dtype: raise TypeError( "%s: type of second parameter must be the same" " as the first's" % self.__class__.__name__ ) elif offset.dtype not in integer_dtypes: raise TypeError( f"{self.__class__.__name__}: type of third parameter must be as integer" " use aesara.tensor.cast( input, 'int32/int64')" ) return Apply(self, [a, val, offset], [a.type()])
def make_node(self, x, w, v, gw, gv): x, w, v, gw, gv = map(as_tensor_variable, (x, w, v, gw, gv)) assert x.ndim == 2 assert w.ndim == 1 assert v.ndim == 2 assert gw.ndim == 1 assert gv.ndim == 2 out_dtype = aes.upcast(x.dtype, w.dtype, v.dtype, gw.dtype, gv.dtype) out = matrix(dtype=out_dtype) return Apply(self, [x, w, v, gw, gv], [out])
def make_node(self, a, val): a = basic.as_tensor_variable(a) val = basic.as_tensor_variable(val) if a.ndim < 2: raise TypeError("%s: first parameter must have at least" " two dimensions" % self.__class__.__name__) elif val.ndim != 0: raise TypeError("%s: second parameter must be a scalar" % self.__class__.__name__) val = basic.cast(val, dtype=upcast(a.dtype, val.dtype)) if val.dtype != a.dtype: raise TypeError("%s: type of second parameter must be the same as" " the first's" % self.__class__.__name__) return Apply(self, [a, val], [a.type()])
def filter(self, data, strict=False, allow_downcast=None): """ Convert `data` to something which can be associated to a `TensorVariable`. This function is not meant to be called in user code. It is for `Linker` instances to use when running a compiled graph. """ # Explicit error message when one accidentally uses a Variable as # input (typical mistake, especially with shared variables). if isinstance(data, Variable): raise TypeError( "Expected an array-like object, but found a Variable: " "maybe you are trying to call a function on a (possibly " "shared) variable instead of a numeric array?") if isinstance(data, np.memmap) and (data.dtype == self.numpy_dtype): # numpy.memmap is a "safe" subclass of ndarray, # so we can use it wherever we expect a base ndarray. # however, casting it would defeat the purpose of not # loading the whole data into memory pass elif isinstance(data, np.ndarray) and (data.dtype == self.numpy_dtype): if data.dtype.num != self.numpy_dtype.num: data = _asarray(data, dtype=self.dtype) # -- now fall through to ndim check elif strict: # If any of the two conditions above was not met, # we raise a meaningful TypeError. if not isinstance(data, np.ndarray): raise TypeError( f"{self} expected a ndarray object (got {type(data)}).") if data.dtype != self.numpy_dtype: raise TypeError( f"{self} expected an ndarray with dtype={self.numpy_dtype} (got {data.dtype})." ) else: if allow_downcast: # Convert to self.dtype, regardless of the type of data data = _asarray(data, dtype=self.dtype) # TODO: consider to pad shape with ones to make it consistent # with self.broadcastable... like vector->row type thing else: if isinstance(data, np.ndarray): # Check if self.dtype can accurately represent data # (do not try to convert the data) up_dtype = aes.upcast(self.dtype, data.dtype) if up_dtype == self.dtype: # Bug in the following line when data is a # scalar array, see # http://projects.scipy.org/numpy/ticket/1611 # data = data.astype(self.dtype) data = _asarray(data, dtype=self.dtype) if up_dtype != self.dtype: err_msg = ( f"{self} cannot store a value of dtype {data.dtype} without " "risking loss of precision. If you do not mind " "this loss, you can: " f"1) explicitly cast your data to {self.dtype}, or " '2) set "allow_input_downcast=True" when calling ' f'"function". Value: "{repr(data)}"') raise TypeError(err_msg) elif (allow_downcast is None and isinstance(data, (float, np.floating)) and self.dtype == config.floatX): # Special case where we allow downcasting of Python float # literals to floatX, even when floatX=='float32' data = _asarray(data, self.dtype) else: # data has to be converted. # Check that this conversion is lossless converted_data = _asarray(data, self.dtype) # We use the `values_eq` static function from TensorType # to handle NaN values. if TensorType.values_eq(np.asarray(data), converted_data, force_same_dtype=False): data = converted_data else: # Do not print a too long description of data # (ndarray truncates it, but it's not sure for data) str_data = str(data) if len(str_data) > 80: str_data = str_data[:75] + "(...)" err_msg = ( f"{self} cannot store accurately value {data}, " f"it would be represented as {converted_data}. " "If you do not mind this precision loss, you can: " "1) explicitly convert your data to a numpy array " f"of dtype {self.dtype}, or " '2) set "allow_input_downcast=True" when calling ' '"function".') raise TypeError(err_msg) if self.ndim != data.ndim: raise TypeError( f"Wrong number of dimensions: expected {self.ndim}," f" got {data.ndim} with shape {data.shape}.") if not data.flags.aligned: try: msg = "object buffer" + str(data.data) except AttributeError: msg = "" raise TypeError( "The numpy.ndarray object is not aligned." " Aesara C code does not support that.", msg, "object shape", data.shape, "object strides", data.strides, "object dtype", data.dtype, ) i = 0 for b in self.broadcastable: if b and data.shape[i] != 1: raise TypeError( "Non-unit value on shape on a broadcastable" " dimension.", data.shape, self.broadcastable, ) i += 1 if self.filter_checks_isfinite and not np.all(np.isfinite(data)): raise ValueError("non-finite elements not allowed") return data
def uniform(self, size, low=0.0, high=1.0, ndim=None, dtype=None, nstreams=None, **kwargs): # TODO : need description for parameter 'size', 'ndim', 'nstreams' """ Sample a tensor of given size whose element from a uniform distribution between low and high. If the size argument is ambiguous on the number of dimensions, ndim may be a plain integer to supplement the missing information. Parameters ---------- low Lower bound of the interval on which values are sampled. If the ``dtype`` arg is provided, ``low`` will be cast into dtype. This bound is excluded. high Higher bound of the interval on which values are sampled. If the ``dtype`` arg is provided, ``high`` will be cast into dtype. This bound is excluded. size Can be a list of integer or Aesara variable (ex: the shape of other Aesara Variable). dtype The output data type. If dtype is not specified, it will be inferred from the dtype of low and high, but will be at least as precise as floatX. """ low = as_tensor_variable(low) high = as_tensor_variable(high) if dtype is None: dtype = aes.upcast(config.floatX, low.dtype, high.dtype) low = cast(low, dtype=dtype) high = cast(high, dtype=dtype) low = undefined_grad(low) high = undefined_grad(high) if isinstance(size, tuple): msg = "size must be a tuple of int or an Aesara variable" assert all( isinstance(i, (np.integer, int, Variable)) for i in size), msg if any(isinstance(i, (np.integer, int)) and i <= 0 for i in size): raise ValueError( "The specified size contains a dimension with value <= 0", size) else: if not (isinstance(size, Variable) and size.ndim == 1): raise TypeError("size must be a tuple of int or an Aesara " "Variable with 1 dimension, got " + str(size) + " of type " + str(type(size))) orig_nstreams = nstreams if nstreams is None: nstreams = self.n_streams(size) rstates = self.get_substream_rstates(nstreams, dtype) d = {} if "target" in kwargs: d = dict(target=kwargs.pop("target")) if len(kwargs) > 0: raise TypeError( f"uniform() got unexpected keyword arguments {kwargs.keys()}") node_rstate = shared(rstates, **d) u = self.pretty_return( node_rstate, *mrg_uniform.new(node_rstate, ndim, dtype, size), size=size, nstreams=orig_nstreams, ) # Add a reference to distinguish from other shared variables node_rstate.tag.is_rng = True r = u * (high - low) + low if u.type.broadcastable != r.type.broadcastable: raise NotImplementedError( "Increase the size to match the broadcasting pattern of " "`low` and `high` arguments") assert r.dtype == dtype return r
def normal( self, size, avg=0.0, std=1.0, ndim=None, dtype=None, nstreams=None, truncate=False, **kwargs, ): """ Sample a tensor of values from a normal distribution. Parameters ---------- size : int_vector_like Array dimensions for the output tensor. avg : float_like, optional The mean value for the truncated normal to sample from (defaults to 0.0). std : float_like, optional The standard deviation for the truncated normal to sample from (defaults to 1.0). truncate : bool, optional Truncates the normal distribution at 2 standard deviations if True (defaults to False). When this flag is set, the standard deviation of the result will be less than the one specified. ndim : int, optional The number of dimensions for the output tensor (defaults to None). This argument is necessary if the size argument is ambiguous on the number of dimensions. dtype : str, optional The data-type for the output tensor. If not specified, the dtype is inferred from avg and std, but it is at least as precise as floatX. kwargs Other keyword arguments for random number generation (see uniform). Returns ------- samples : TensorVariable A Aesara tensor of samples randomly drawn from a normal distribution. """ size = _check_size(size) avg = undefined_grad(as_tensor_variable(avg)) std = undefined_grad(as_tensor_variable(std)) if dtype is None: dtype = aes.upcast(config.floatX, avg.dtype, std.dtype) avg = at.cast(avg, dtype=dtype) std = at.cast(std, dtype=dtype) # generate even number of uniform samples # Do manual constant folding to lower optiimizer work. if isinstance(size, Constant): n_odd_samples = size.prod(dtype="int64") else: n_odd_samples = prod(size, dtype="int64") n_even_samples = n_odd_samples + n_odd_samples % 2 uniform = self.uniform( (n_even_samples, ), low=0.0, high=1.0, ndim=1, dtype=dtype, nstreams=nstreams, **kwargs, ) # box-muller transform u1 = uniform[:n_even_samples // 2] u2 = uniform[n_even_samples // 2:] r = sqrt(-2.0 * log(u1)) theta = np.array(2.0 * np.pi, dtype=dtype) * u2 cos_theta, sin_theta = cos(theta), sin(theta) z0 = r * cos_theta z1 = r * sin_theta if truncate: # use valid samples to_fix0 = (z0 < -2.0) | (z0 > 2.0) to_fix1 = (z1 < -2.0) | (z1 > 2.0) z0_valid = z0[at.nonzero(~to_fix0)] z1_valid = z1[at.nonzero(~to_fix1)] # re-sample invalid samples to_fix0 = at.nonzero(to_fix0)[0] to_fix1 = at.nonzero(to_fix1)[0] n_fix_samples = to_fix0.size + to_fix1.size lower = at.constant(1.0 / np.e**2, dtype=dtype) u_fix = self.uniform( (n_fix_samples, ), low=lower, high=1.0, ndim=1, dtype=dtype, nstreams=nstreams, **kwargs, ) r_fix = sqrt(-2.0 * log(u_fix)) z0_fixed = r_fix[:to_fix0.size] * cos_theta[to_fix0] z1_fixed = r_fix[to_fix0.size:] * sin_theta[to_fix1] # pack everything together to a useful result norm_samples = at.join(0, z0_valid, z0_fixed, z1_valid, z1_fixed) else: norm_samples = at.join(0, z0, z1) if isinstance(n_odd_samples, Variable): samples = norm_samples[:n_odd_samples] elif n_odd_samples % 2 == 1: samples = norm_samples[:-1] else: samples = norm_samples samples = reshape(samples, newshape=size, ndim=ndim) samples *= std samples += avg return samples
def make_node(self, a, b): a = as_tensor_variable(a) b = as_tensor_variable(b) out_dtype = aes.upcast(a.dtype, b.dtype) x = matrix(dtype=out_dtype) return Apply(self, [a, b], [x])
def __init__(self, comp_dists, states, *args, **kwargs): """Initialize a `SwitchingProcess` instance. Each `Distribution` object in `comp_dists` must have a `Distribution.random_subset` method that takes a list of indices and returns a sample for only that subset. Unfortunately, since PyMC3 doesn't provide such a method, you'll have to implement it yourself and monkey patch a `Distribution` class. Parameters ---------- comp_dists : list of Distribution A list containing `Distribution` objects for each mixture component. These are essentially the emissions distributions. states : DiscreteMarkovChain The hidden state sequence. It should have a number of states equal to the size of `comp_dists`. """ self.states = at.as_tensor_variable(pm.intX(states)) if len(comp_dists) > 31: warnings.warn( "There are too many mixture distributions to properly" " determine their combined shape.") self.comp_dists = comp_dists states_tv = get_test_value(self.states) bcast_comps = np.broadcast( states_tv, *[get_and_check_comp_value(x) for x in comp_dists[:31]]) shape = bcast_comps.shape defaults = kwargs.pop("defaults", []) out_dtype = upcast(*[x.type.dtype for x in comp_dists]) dtype = kwargs.pop("dtype", out_dtype) if not all_discrete(comp_dists): try: bcast_means = tt_broadcast_arrays( *([self.states] + [d.mean.astype(dtype) for d in self.comp_dists])) self.mean = at.choose(self.states, bcast_means[1:]) if "mean" not in defaults: defaults.append("mean") except (AttributeError, ValueError, IndexError): # pragma: no cover pass try: bcast_modes = tt_broadcast_arrays( *([self.states] + [d.mode.astype(dtype) for d in self.comp_dists])) self.mode = at.choose(self.states, bcast_modes[1:]) if "mode" not in defaults: defaults.append("mode") except (AttributeError, ValueError, IndexError): # pragma: no cover pass super().__init__(shape=shape, dtype=dtype, defaults=defaults, **kwargs)
def filter_inplace(self, data, old_data, strict=False, allow_downcast=None): if isinstance(data, gpuarray.GpuArray) and data.typecode == self.typecode: # This is just to make this condition not enter the # following branches pass elif strict: if not isinstance(data, gpuarray.GpuArray): raise TypeError("%s expected a GpuArray object." % self, data, type(data)) if self.typecode != data.typecode: raise TypeError("%s expected typecode %d (dtype %s), " "got %d (dtype %s)." % (self, self.typecode, self.dtype, data.typecode, str(data.dtype))) if self.context != data.context: raise TypeError("data context does not match type context") # fallthrough to ndim check elif allow_downcast or (allow_downcast is None and type(data) == float and self.dtype == config.floatX): if not isinstance(data, gpuarray.GpuArray): data = np.array(data, dtype=self.dtype, copy=False, ndmin=len(self.broadcastable)) else: data = gpuarray.array( data, dtype=self.typecode, copy=False, ndmin=len(self.broadcastable), context=self.context, ) else: if not hasattr(data, "dtype"): converted_data = aesara._asarray(data, self.dtype) # We use the `values_eq` static function from TensorType # to handle NaN values. if TensorType.values_eq(np.asarray(data), converted_data, force_same_dtype=False): data = converted_data up_dtype = scalar.upcast(self.dtype, data.dtype) if up_dtype == self.dtype: if not isinstance(data, gpuarray.GpuArray): data = np.array(data, dtype=self.dtype, copy=False) else: data = gpuarray.array(data, dtype=self.dtype, copy=False) else: raise TypeError("%s cannot store a value of dtype %s " "without risking loss of precision." % (self, data.dtype)) if self.ndim != data.ndim: raise TypeError( "Wrong number of dimensions: expected %s, " "got %s with shape %s." % (self.ndim, data.ndim, data.shape), data, ) shp = data.shape for i, b in enumerate(self.broadcastable): if b and shp[i] != 1: raise TypeError( "Non-unit value on shape on a broadcastable" " dimension.", shp, self.broadcastable, ) if not isinstance(data, gpuarray.GpuArray): if (old_data is not None and old_data.shape == data.shape and ( # write() only work if the destitation is contiguous. old_data.flags["C_CONTIGUOUS"] or old_data.flags["F_CONTIGUOUS"])): old_data.write(data) data = old_data else: data = pygpu.array(data, context=self.context) return data