1# Copyright 2020 Huawei Technologies Co., Ltd 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================ 15"""Cauchy Distribution""" 16import numpy as np 17from mindspore.ops import operations as P 18from mindspore.ops import composite as C 19from mindspore._checkparam import Validator 20from mindspore.common import dtype as mstype 21from .distribution import Distribution 22from ._utils.utils import check_greater_zero, check_distribution_name, raise_not_defined 23from ._utils.custom_ops import exp_generic, log_generic, log1p_generic 24 25 26class Cauchy(Distribution): 27 """ 28 Cauchy distribution. 29 30 Args: 31 loc (int, float, list, numpy.ndarray, Tensor): The location of the Cauchy distribution. 32 scale (int, float, list, numpy.ndarray, Tensor): The scale of the Cauchy distribution. 33 seed (int): The seed used in sampling. The global seed is used if it is None. Default: None. 34 dtype (mindspore.dtype): The type of the event samples. Default: mstype.float32. 35 name (str): The name of the distribution. Default: 'Cauchy'. 36 37 Supported Platforms: 38 ``Ascend`` 39 40 Note: 41 `scale` must be greater than zero. 42 `dist_spec_args` are `loc` and `scale`. 43 `dtype` must be a float type because Cauchy distributions are continuous. 44 Cauchy distribution is not supported on GPU backend. 45 46 Examples: 47 >>> import mindspore 48 >>> import mindspore.nn as nn 49 >>> import mindspore.nn.probability.distribution as msd 50 >>> from mindspore import Tensor 51 >>> # To initialize a Cauchy distribution of loc 3.0 and scale 4.0. 52 >>> cauchy1 = msd.Cauchy(3.0, 4.0, dtype=mindspore.float32) 53 >>> # A Cauchy distribution can be initialized without arguments. 54 >>> # In this case, 'loc' and `scale` must be passed in through arguments. 55 >>> cauchy2 = msd.Cauchy(dtype=mindspore.float32) 56 >>> # Here are some tensors used below for testing 57 >>> value = Tensor([1.0, 2.0, 3.0], dtype=mindspore.float32) 58 >>> loc_a = Tensor([2.0], dtype=mindspore.float32) 59 >>> scale_a = Tensor([2.0, 2.0, 2.0], dtype=mindspore.float32) 60 >>> loc_b = Tensor([1.0], dtype=mindspore.float32) 61 >>> scale_b = Tensor([1.0, 1.5, 2.0], dtype=mindspore.float32) 62 >>> # Private interfaces of probability functions corresponding to public interfaces, including 63 >>> # `prob`, `log_prob`, `cdf`, `log_cdf`, `survival_function`, and `log_survival`, 64 >>> # have the same arguments as follows. 65 >>> # Args: 66 >>> # value (Tensor): the value to be evaluated. 67 >>> # loc (Tensor): the location of the distribution. Default: self.loc. 68 >>> # scale (Tensor): the scale of the distribution. Default: self.scale. 69 >>> # Examples of `prob`. 70 >>> # Similar calls can be made to other probability functions 71 >>> # by replacing 'prob' by the name of the function 72 >>> ans = cauchy1.prob(value) 73 >>> print(ans.shape) 74 (3,) 75 >>> # Evaluate with respect to distribution b. 76 >>> ans = cauchy1.prob(value, loc_b, scale_b) 77 >>> print(ans.shape) 78 (3,) 79 >>> # `loc` and `scale` must be passed in during function calls 80 >>> ans = cauchy2.prob(value, loc_a, scale_a) 81 >>> print(ans.shape) 82 (3,) 83 >>> # Functions `mode` and `entropy` have the same arguments. 84 >>> # Args: 85 >>> # loc (Tensor): the location of the distribution. Default: self.loc. 86 >>> # scale (Tensor): the scale of the distribution. Default: self.scale. 87 >>> # Example of `mode`. 88 >>> ans = cauchy1.mode() # return 3.0 89 >>> print(ans.shape) 90 () 91 >>> ans = cauchy1.mode(loc_b, scale_b) # return loc_b 92 >>> print(ans.shape) 93 (3,) 94 >>> # `loc` and `scale` must be passed in during function calls. 95 >>> ans = cauchy2.mode(loc_a, scale_a) 96 >>> print(ans.shape) 97 (3,) 98 >>> # Interfaces of 'kl_loss' and 'cross_entropy' are the same: 99 >>> # Args: 100 >>> # dist (str): the type of the distributions. Only "Cauchy" is supported. 101 >>> # loc_b (Tensor): the loc of distribution b. 102 >>> # scale_b (Tensor): the scale distribution b. 103 >>> # loc (Tensor): the loc of distribution a. Default: self.loc. 104 >>> # scale (Tensor): the scale distribution a. Default: self.scale. 105 >>> # Examples of `kl_loss`. `cross_entropy` is similar. 106 >>> ans = cauchy1.kl_loss('Cauchy', loc_b, scale_b) 107 >>> print(ans.shape) 108 (3,) 109 >>> ans = cauchy1.kl_loss('Cauchy', loc_b, scale_b, loc_a, scale_a) 110 >>> print(ans.shape) 111 (3,) 112 >>> # Additional `loc` and `scale` must be passed in. 113 >>> ans = cauchy2.kl_loss('Cauchy', loc_b, scale_b, loc_a, scale_a) 114 >>> print(ans.shape) 115 (3,) 116 >>> # Examples of `sample`. 117 >>> # Args: 118 >>> # shape (tuple): the shape of the sample. Default: () 119 >>> # loc (Tensor): the location of the distribution. Default: self.loc. 120 >>> # scale (Tensor): the scale of the distribution. Default: self.scale. 121 >>> ans = cauchy1.sample() 122 >>> print(ans.shape) 123 () 124 >>> ans = cauchy1.sample((2,3)) 125 >>> print(ans.shape) 126 (2, 3) 127 >>> ans = cauchy1.sample((2,3), loc_b, scale_b) 128 >>> print(ans.shape) 129 (2, 3, 3) 130 >>> ans = cauchy2.sample((2,3), loc_a, scale_a) 131 >>> print(ans.shape) 132 (2, 3, 3) 133 """ 134 135 def __init__(self, 136 loc=None, 137 scale=None, 138 seed=None, 139 dtype=mstype.float32, 140 name="Cauchy"): 141 """ 142 Constructor of Cauchy. 143 """ 144 param = dict(locals()) 145 param['param_dict'] = {'loc': loc, 'scale': scale} 146 valid_dtype = mstype.float_type 147 Validator.check_type_name("dtype", dtype, valid_dtype, type(self).__name__) 148 super(Cauchy, self).__init__(seed, dtype, name, param) 149 150 self._loc = self._add_parameter(loc, 'loc') 151 self._scale = self._add_parameter(scale, 'scale') 152 if self._scale is not None: 153 check_greater_zero(self._scale, "scale") 154 155 # ops needed for the class 156 self.atan = P.Atan() 157 self.cast = P.Cast() 158 self.const = P.ScalarToArray() 159 self.dtypeop = P.DType() 160 self.exp = exp_generic 161 self.fill = P.Fill() 162 self.less = P.Less() 163 self.log = log_generic 164 self.log1p = log1p_generic 165 self.squeeze = P.Squeeze(0) 166 self.shape = P.Shape() 167 self.sq = P.Square() 168 self.sqrt = P.Sqrt() 169 self.tan = P.Tan() 170 self.uniform = C.uniform 171 172 self.entropy_const = np.log(4 * np.pi) 173 174 175 def extend_repr(self): 176 """Display instance object as string.""" 177 if self.is_scalar_batch: 178 str_info = 'location = {}, scale = {}'.format(self._loc, self._scale) 179 else: 180 str_info = 'batch_shape = {}'.format(self._broadcast_shape) 181 return str_info 182 183 @property 184 def loc(self): 185 """ 186 Return the location of the distribution after casting to dtype. 187 """ 188 return self._loc 189 190 @property 191 def scale(self): 192 """ 193 Return the scale of the distribution after casting to dtype. 194 """ 195 return self._scale 196 197 def _get_dist_type(self): 198 return "Cauchy" 199 200 def _get_dist_args(self, loc=None, scale=None): 201 if scale is not None: 202 self.checktensor(scale, 'scale') 203 else: 204 scale = self.scale 205 if loc is not None: 206 self.checktensor(loc, 'loc') 207 else: 208 loc = self.loc 209 return loc, scale 210 211 def _mode(self, loc=None, scale=None): 212 """ 213 The mode of the distribution. 214 """ 215 loc, scale = self._check_param_type(loc, scale) 216 return loc 217 218 def _mean(self, *args, **kwargs): 219 return raise_not_defined('mean', 'Cauchy', *args, **kwargs) 220 221 def _sd(self, *args, **kwargs): 222 return raise_not_defined('standard deviation', 'Cauchy', *args, **kwargs) 223 224 def _var(self, *args, **kwargs): 225 return raise_not_defined('variance', 'Cauchy', *args, **kwargs) 226 227 def _entropy(self, loc=None, scale=None): 228 r""" 229 Evaluate entropy. 230 231 .. math:: 232 H(X) = \log(4 * \Pi * scale) 233 """ 234 loc, scale = self._check_param_type(loc, scale) 235 return self.log(scale) + self.entropy_const 236 237 def _log_prob(self, value, loc=None, scale=None): 238 r""" 239 Evaluate log probability. 240 241 Args: 242 value (Tensor): The value to be evaluated. 243 loc (Tensor): The location of the distribution. Default: self.loc. 244 scale (Tensor): The scale of the distribution. Default: self.scale. 245 246 .. math:: 247 L(x) = \log(\frac{1}{\pi * scale} * \frac{scale^{2}}{(x - loc)^{2} + scale^{2}}) 248 """ 249 value = self._check_value(value, 'value') 250 value = self.cast(value, self.dtype) 251 loc, scale = self._check_param_type(loc, scale) 252 z = (value - loc) / scale 253 log_unnormalized_prob = (-1) * self.log1p(self.sq(z)) 254 log_normalization = self.log(np.pi * scale) 255 return log_unnormalized_prob - log_normalization 256 257 def _cdf(self, value, loc=None, scale=None): 258 r""" 259 Evaluate the cumulative distribution function on the given value. 260 261 Args: 262 value (Tensor): The value to be evaluated. 263 loc (Tensor): The location of the distribution. Default: self.loc. 264 scale (Tensor): The scale the distribution. Default: self.scale. 265 266 .. math:: 267 cdf(x) = \frac{\arctan{(x - loc) / scale}}{\pi} + 0.5 268 """ 269 value = self._check_value(value, 'value') 270 value = self.cast(value, self.dtype) 271 loc, scale = self._check_param_type(loc, scale) 272 z = (value - loc) / scale 273 return self.atan(z) / np.pi + 0.5 274 275 def _log_cdf(self, value, loc=None, scale=None): 276 r""" 277 Evaluate the log cumulative distribution function on the given value. 278 279 Args: 280 value (Tensor): The value to be evaluated. 281 loc (Tensor): The location of the distribution. Default: self.loc. 282 scale (Tensor): The scale the distribution. Default: self.scale. 283 284 .. math:: 285 log_cdf(x) = \log(\frac{\arctan(\frac{x-loc}{scale})}{\pi} + 0.5) 286 = \log {\arctan(\frac{x-loc}{scale}) + 0.5pi}{pi} 287 = \log1p \frac{2 * arctan(\frac{x-loc}{scale})}{pi} - \log2 288 """ 289 value = self._check_value(value, 'value') 290 value = self.cast(value, self.dtype) 291 loc, scale = self._check_param_type(loc, scale) 292 z = (value - loc) / scale 293 return self.log1p(2. * self.atan(z) / np.pi) - self.log(self.const(2.)) 294 295 def _quantile(self, p, loc=None, scale=None): 296 loc, scale = self._check_param_type(loc, scale) 297 return loc + scale * self.tan(np.pi * (p - 0.5)) 298 299 def _kl_loss(self, dist, loc_b, scale_b, loc_a=None, scale_a=None): 300 r""" 301 Evaluate Cauchy-Cauchy kl divergence, i.e. KL(a||b). 302 303 Args: 304 dist (str): The type of the distributions. Should be "Cauchy" in this case. 305 loc_b (Tensor): The loc of distribution b. 306 scale_b (Tensor): The scale of distribution b. 307 loc (Tensor): The loc of distribution a. Default: self.loc. 308 scale (Tensor): The scale of distribution a. Default: self.scale. 309 310 .. math:: 311 KL(a||b) = \log(\frac{(scale_a + scale_b)^{2} + (loc_a - loc_b)^{2}} 312 {4 * scale_a * scale_b}) 313 """ 314 check_distribution_name(dist, 'Cauchy') 315 loc_a, scale_a = self._check_param_type(loc_a, scale_a) 316 loc_b = self._check_value(loc_b, 'loc_b') 317 loc_b = self.cast(loc_b, self.parameter_type) 318 scale_b = self._check_value(scale_b, 'scale_b') 319 scale_b = self.cast(scale_b, self.parameter_type) 320 sum_square = self.sq(scale_a + scale_b) 321 square_diff = self.sq(loc_a - loc_b) 322 return self.log(sum_square + square_diff) - \ 323 self.log(self.const(4.0)) - self.log(scale_a) - self.log(scale_b) 324 325 def _cross_entropy(self, dist, loc_b, scale_b, loc_a=None, scale_a=None): 326 r""" 327 Evaluate cross entropy between Cauchy distributions. 328 329 Args: 330 dist (str): The type of the distributions. Should be "Cauchy" in this case. 331 loc_b (Tensor): The loc of distribution b. 332 scale_b (Tensor): The scale of distribution b. 333 loc (Tensor): The loc of distribution a. Default: self.loc. 334 scale (Tensor): The scale of distribution a. Default: self.scale. 335 """ 336 check_distribution_name(dist, 'Cauchy') 337 return self._entropy(loc_a, scale_a) + self._kl_loss(dist, loc_b, scale_b, loc_a, scale_a) 338 339 def _sample(self, shape=(), loc=None, scale=None): 340 """ 341 Sampling. 342 343 Args: 344 shape (tuple): The shape of the sample. Default: (). 345 loc (Tensor): The location of the samples. Default: self.loc. 346 scale (Tensor): The scale of the samples. Default: self.scale. 347 348 Returns: 349 Tensor, with the shape being shape + batch_shape. 350 """ 351 shape = self.checktuple(shape, 'shape') 352 loc, scale = self._check_param_type(loc, scale) 353 batch_shape = self.shape(loc + scale) 354 origin_shape = shape + batch_shape 355 if origin_shape == (): 356 sample_shape = (1,) 357 else: 358 sample_shape = origin_shape 359 l_zero = self.const(0.0) 360 h_one = self.const(1.0) 361 sample_uniform = self.uniform(sample_shape, l_zero, h_one, self.seed) 362 sample = self._quantile(sample_uniform, loc, scale) 363 value = self.cast(sample, self.dtype) 364 if origin_shape == (): 365 value = self.squeeze(value) 366 return value 367