blob: 17b6327bb79beaa1afac5b222efd6da2697657ba [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Fallback-to-NumPy operator implementation."""
from distutils.version import StrictVersion
import functools
import ast
import numpy as np
from . import operator
from . import numpy as _mx_np # pylint: disable=reimported
from .util import np_array, use_np
from .numpy.utils import _STR_2_DTYPE_
from .ndarray.numpy import _internal as _nd_npi
from .symbol.numpy import _internal as _sym_npi
def register(op_name, imperative=True, symbolic=True):
"""Register operators that fallback to NumPy in modules
``mxnet.ndarray.numpy._internal`` and ``mxnet.symbol.numpy._internal``."""
def _save_op(mod):
if hasattr(mod, op_name):
raise ValueError('Duplicate name {} found in module {}'.format(op_name, str(mod)))
op = functools.partial(mod.Custom, op_type=op_name)
setattr(mod, op_name, op)
def _register_helper(prop_cls):
with np_array():
prop_cls = operator.register(op_name)(prop_cls)
if imperative:
_save_op(_nd_npi)
if symbolic:
_save_op(_sym_npi)
return prop_cls
return _register_helper
@use_np # enforce np shape and array semantics for all the methods in this class
class EmptyLike(operator.CustomOp):
"""Fallback to NumPy empty_like operator."""
def __init__(self, dtype, order, subok, shape):
super(EmptyLike, self).__init__()
self._dtype = dtype
self._order = order
self._subok = subok
self._shape = shape
def forward(self, is_train, req, in_data, out_data, aux):
np_version = np.version.version
if StrictVersion(np_version) >= StrictVersion('1.6.0'):
out = np.empty_like(in_data[0].asnumpy(), dtype=self._dtype, order=self._order,
subok=self._subok)
else:
out = np.empty_like(in_data[0].asnumpy())
self.assign(out_data[0], req[0], _mx_np.array(out, device=in_data[0].device))
def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
raise NotImplementedError('Operator empty_like does not support gradient computation')
@register('empty_like_fallback')
class EmptyLikeProp(operator.CustomOpProp):
"""Fallback empty_like operator properties."""
def __init__(self, dtype, order, subok, shape):
super(EmptyLikeProp, self).__init__(need_top_grad=True)
self._dtype = None if dtype == 'None' else dtype
self._order = order
self._subok = ast.literal_eval(subok)
self._shape = ast.literal_eval(shape)
def list_arguments(self):
return ['prototype']
def infer_shape(self, in_shape):
return (in_shape[0],), (in_shape[0],), ()
def infer_type(self, in_type):
if self._dtype is None:
return (in_type[0],), (in_type[0],), ()
else:
return (in_type[0],), (_STR_2_DTYPE_[self._dtype],), ()
def create_operator(self, ctx, in_shapes, in_dtypes):
return EmptyLike(self._dtype, self._order, self._subok, self._shape)
@use_np # enforce np shape and array semantics for all the methods in this class
class Resize(operator.CustomOp):
"""Fallback to NumPy resize operator."""
def __init__(self, new_shape):
super(Resize, self).__init__()
self._new_shape = new_shape
def forward(self, is_train, req, in_data, out_data, aux):
out = np.resize(in_data[0].asnumpy(), self._new_shape)
self.assign(out_data[0], req[0], _mx_np.array(out, dtype=out.dtype, device=out_data[0].device))
def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
raise NotImplementedError('Operator resize does not support gradient computation')
@register('resize_fallback')
class ResizeProp(operator.CustomOpProp):
"""Fallback resize operator properties."""
def __init__(self, new_shape):
super(ResizeProp, self).__init__(need_top_grad=True)
self._new_shape = ast.literal_eval(new_shape)
def list_arguments(self):
return ['a']
def infer_shape(self, in_shape):
out_shape = (self._new_shape,) if np.isscalar(self._new_shape) else self._new_shape
return (in_shape[0],), (out_shape,), ()
def create_operator(self, ctx, in_shapes, in_dtypes):
return Resize(self._new_shape)
@use_np
class Unravel_index(operator.CustomOp):
"""Fallback to NumPy Unravel_index operator."""
def __init__(self, shape):
super(Unravel_index, self).__init__()
self._shape = shape
def forward(self, is_train, req, in_data, out_data, aux):
out = np.unravel_index(in_data[0].asnumpy(), self._shape)
self.assign(out_data[0], req[0], _mx_np.array(out, dtype=out[0].dtype, device=out_data[0].device))
def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
raise NotImplementedError('Operator Unravel_index does not support gradient computation')
@register('unravel_index_fallback')
class Unravel_indexProp(operator.CustomOpProp):
"""Fallback unravel_index operator properties."""
def __init__(self, shape):
super(Unravel_indexProp, self).__init__(need_top_grad=True)
self._shape = ast.literal_eval(shape)
def list_arguments(self):
return ['indices']
def infer_shape(self, in_shape):
dim_list = (1,) if np.isscalar(self._shape) else (len(self._shape),)
out_shape = dim_list + tuple(in_shape[0])
return (in_shape[0],), (out_shape,), ()
def create_operator(self, ctx, in_shapes, in_dtypes):
return Unravel_index(self._shape)
@use_np
class MultivariateNormal(operator.CustomOp):
"""Fallback to the front-end implementation of random.multivariate_normal."""
def __init__(self, size=None):
super(MultivariateNormal, self).__init__()
self._size = size
def forward(self, is_train, req, in_data, out_data, aux):
loc = in_data[0]
cov = in_data[1]
if cov.dtype == np.float16:
scale = _mx_np.linalg.cholesky(cov.astype(np.float32)).astype(np.float16)
else:
scale = _mx_np.linalg.cholesky(cov)
#set context
noise = _mx_np.random.normal(size=out_data[0].shape, dtype=loc.dtype, device=loc.device)
out = loc + _mx_np.einsum('...jk,...j->...k', scale, noise)
self.assign(out_data[0], req[0], out)
def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
raise NotImplementedError('Operator random.multivariate_normal'
' does not support gradient computation')
@register('mvn_fallback')
class MultivariateNormalProp(operator.CustomOpProp):
"""Fallback np.random.multivariate_normal operator properties."""
def __init__(self, size=None):
super(MultivariateNormalProp, self).__init__(need_top_grad=True)
self._size = ast.literal_eval(
size) if size is not None else None
def list_arguments(self):
return ['mean', 'cov']
def infer_shape(self, in_shape):
loc_shape = in_shape[0]
cov_shape = in_shape[1]
if len(loc_shape) < 1:
raise ValueError("mean must be at least 1 dimensional")
if len(cov_shape) < 2:
raise ValueError("cov must be at least 2 dimensional")
if cov_shape[-1] != cov_shape[-2]:
raise ValueError("the last two dimentions of the parameter cov have to be the same,"
" whereas the shape of cov is {}".format(cov_shape))
if cov_shape[-1] != loc_shape[-1]:
raise ValueError("mean and cov must have same length."
"The shape of mean is {} but the shape of cov is {}"
.format(loc_shape[-1:], cov_shape[-2:]))
# handle shape mismatch here
out_shape = np.broadcast(np.empty(loc_shape), np.empty(cov_shape[:-1])).shape
if self._size is not None:
self._size = (self._size,) if np.isscalar(
self._size) else self._size
out_shape = self._size + out_shape
return in_shape, (out_shape,), ()
def create_operator(self, ctx, in_shapes, in_dtypes):
return MultivariateNormal(self._size)