Source code for cntk.tensor

# Copyright (c) Microsoft. All rights reserved.

# Licensed under the MIT license. See LICENSE.md file in the project root
# for full license information.
# ==============================================================================
"""
Tensor operations.
"""


import warnings
from scipy import sparse

[docs]class TensorOpsMixin(object): ''' This class defines math overloads so that CNTK nodes can be written in math expressions. ''' # operator overload for (+) where self is the left operand def __add__(self, other): from . import ops return ops.plus(self, other) # operator overload for (+) where self is the right operand def __radd__(self, other): from . import ops return ops.plus(other, self) # operator overload for (-) where self is the left operand def __sub__(self, other): from . import ops return ops.minus(self, other) # operator overload for (-) where self is the right operand def __rsub__(self, other): from . import ops return ops.minus(other, self) # operator overload for (*) where self is the left operand def __mul__(self, other): from . import ops return ops.element_times(self, other) # operator overload for (*) where self is the right operand def __rmul__(self, other): from . import ops return ops.element_times(other, self) # operator overload for (@) where self is the left operand def __matmul__(self, other): # NOTE supported in Python 3.5 from . import ops return ops.times(self, other) # operator overload for (@) where self is the right operand def __rmatmul__(self, other): # NOTE supported in Python 3.5 from . import ops return ops.times(other, self) # operator overload for (/) where self is the left operand def __truediv__(self, other): from . import ops self.__div__ = self.__truediv__ return ops.element_divide(self, other) # operator overload for (/) where self is the right operand def __rtruediv__(self, other): from . import ops self.__rdiv__ = self.__rtruediv__ return ops.element_divide(other, self) # Python2 compatibility __div__ = __truediv__ __rdiv__ = __rtruediv__ def __abs__(self): from . import ops return ops.abs(self) def __neg__(self): from . import ops return ops.negate(self) # TODO __xor__, __rxor__, __pow__, __rpow__, __invert__ # Comparison operators are not exposed yet, because of __eq__ being # required to allow comparison of Variables on C++ so that we can say # 'for var in variables'. # __lt__, __le__, __gt__, __ge__, __and__, __rand__, __or__, __ror__, def __getitem__(self, arg): ''' Slicing of a Variable. E.g. var[2:3] will translate into slice(var, axis=0, begin_index=2, end_index=3) ''' from . import ops if hasattr(self, 'outputs') and len(self.outputs) > 1: try: return self.outputs[arg] except Exception as e: msg = 'Slice for multioutput functions is not supported, ' \ 'the fallback to select to output requires ' \ 'that only one index is provided. arg: {}, self: {}'.format( arg, self) raise KeyError(msg) # int or slice: normalize into a tuple of int or tuple of slice if not isinstance(arg, tuple): arg = (arg,) r = self axis0 = 0 from cntk.default_options import get_global_option, get_default_override, default_override_or keras_mode_flag = get_global_option('align_axis', 0) if keras_mode_flag == 1: if (getattr(self, 'dynamic_axes') is not None and len(self.dynamic_axes) > 0): axis0 = -get_default_override(None, axis_offset=default_override_or(len(self.dynamic_axes))) for axis, s in enumerate(arg): if s is Ellipsis: # ellipsis means index relative to end after this point axis0 = -len(arg) continue if isinstance(s, int): # int: normalize into a slice s = slice(s, s+1) if isinstance(s, slice): begin = s.start or 0 end = s.stop or 0 if begin != 0 or end != 0: r = ops.slice(r, axis=axis + axis0, begin_index=begin, end_index=end, strides=s.step) elif isinstance(s, (tuple, list)): # Select multiple elements from the same dimension. This is # different from NumPy's advanced indexing, since we just go # axis by axis from left to right and don't do any # broadcasting. slice_accum = [] for idx in s: if not isinstance(idx, int): raise IndexError( 'indices have to be of type int and not "%s"' % type(idx)) slice_accum.append(ops.slice(r, axis=axis, begin_index=idx, end_index=idx + 1)) if len(slice_accum) > 1: r = ops.splice(*slice_accum, axis=axis) else: r = slice_accum[0] else: raise IndexError( 'type "%s" is not supported as index' % type(s)) return r
AVAILABLE_TENSOR_OPS = ['abs', 'add', 'div', 'getitem', 'matmul', 'mul', 'radd', 'rdiv', 'rmatmul', 'rmul', 'rsub', 'rtruediv', 'sub', 'truediv', 'neg'] def _add_tensor_ops(klass): for op_name in AVAILABLE_TENSOR_OPS: overload_name = '__%s__' % op_name if getattr(klass, overload_name, None): raise ValueError('class "%s" already has operator overload "%s"' % (klass, overload_name)) setattr(klass, overload_name, TensorOpsMixin.__dict__[overload_name])
[docs]class ArrayMixin(object):
[docs] def asarray(self): ''' Converts the instance's data to a NumPy array. ''' import cntk result = None if isinstance(self, cntk.Constant): ndav = super(cntk.Constant, self).value() is_sparse = ndav.is_sparse() elif isinstance(self, cntk.Parameter): ndav = super(cntk.Parameter, self).value() is_sparse = ndav.is_sparse() elif isinstance(self, (cntk.cntk_py.Constant, cntk.cntk_py.Parameter)): ndav = self.value() is_sparse = ndav.is_sparse() elif isinstance(self, (cntk.cntk_py.NDArrayView, cntk.cntk_py.NDMask)): ndav = self if isinstance(self, cntk.NDArrayView): is_sparse = ndav.is_sparse elif isinstance(self, cntk.cntk_py.NDArrayView): is_sparse = ndav.is_sparse() else: is_sparse = False # Value and MinibatchData have a mask, which means that we need the # corresponding Variable to do the proper conversion. For easy # discoverability, we nevertheless add asarray() to those classes as # well, but issue a warning. elif isinstance(self, cntk.cntk_py.Value) or isinstance(self, cntk.cntk_py.MinibatchData): if isinstance(self, cntk.cntk_py.MinibatchData): value = self.data else: value = self if isinstance(value, cntk.Value): is_sparse = value.is_sparse has_mask = super(cntk.Value, value).mask() is not None ndav = value.data else: is_sparse = value.is_sparse() has_mask = value.mask() is not None ndav = value.data() if has_mask: warnings.warn('asarray() will ignore the mask information. ' 'Please use as_sequences() to do the proper ' 'conversion.') if is_sparse: from cntk.internal.sanitize import _sparse_to_dense_network_cache device = ndav.device if callable(device): device = device() network = _sparse_to_dense_network_cache(ndav.shape[1:], False, device) warnings.warn('converting Value object to CSR format might be slow') dense_data = network.eval(self, device=device) def to_csr(dense_data): if len(dense_data.shape) > 2: warnings.warn('Cannot convert a sparse NDArrayView or Value object ' 'with shape %s of rank > 2 to a scipy.csr matrix.' ' Returning dense data.' % str(dense_data.shape)) return dense_data return sparse.csr_matrix(dense_data) if isinstance(dense_data, list): result = [to_csr(d) for d in dense_data] else: result = to_csr(dense_data) else: result = ndav.to_ndarray() return result
def _add_asarray(klass): member_name = 'asarray' if getattr(klass, member_name, None): raise ValueError('class "%s" has already an attribute "%s"' % (klass, member_name)) setattr(klass, member_name, ArrayMixin.__dict__[member_name])