20

I could not find a built-in function in Python to generate a log uniform distribution given a min and max value (the R equivalent is here), something like: loguni[n, exp(min), exp(max), base] that returns n log uniformly distributed in the range exp(min) and exp(max).

The closest I found though was numpy.random.uniform.

DavidG
  • 24,279
  • 14
  • 89
  • 82
jkrish
  • 427
  • 1
  • 5
  • 12
  • Your link is not working. Apart from that: check out [scipy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html) (numpy only provides the common dists needed). And if scipy does not provide it, implement it yourself, maybe based on your R-equivalent or doing something like acceptance-rejection which is easy most of the time (but not necessarily the fastest). – sascha May 15 '17 at 11:01
  • Thanks for pointing this out! Changed link, this should work. – jkrish May 15 '17 at 11:06
  • You could just exponentiate the uniform distribution. – Arya McCarthy May 15 '17 at 11:08
  • @aryamccarthy Using numpy.random.uniform [https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.uniform.html]? Right! Thanks! – jkrish May 15 '17 at 11:11
  • 1
    You really need to look at the Markdown syntax for links. The brackets are interpreted as part of the link. – Arya McCarthy May 15 '17 at 11:12

7 Answers7

26

From http://ecolego.facilia.se/ecolego/show/Log-Uniform%20Distribution:

In a loguniform distribution, the logtransformed random variable is assumed to be uniformly distributed.

Thus

logU(a, b) ~ exp(U(log(a), log(b))

Thus, we could create a log-uniform distribution using numpy:

def loguniform(low=0, high=1, size=None):
    return np.exp(np.random.uniform(low, high, size))

If you want to choose a different base, we could define a new function as follows:

def lognuniform(low=0, high=1, size=None, base=np.e):
    return np.power(base, np.random.uniform(low, high, size))

EDIT: @joaoFaria's answer is also correct.

def loguniform(low=0, high=1, size=None):
    return scipy.stats.reciprocal(np.exp(low), np.exp(high)).rvs(size)
Scott Gigante
  • 1,450
  • 1
  • 17
  • 29
  • This is dangerous. It'll give a `RuntimeWarning` that you're dividing by zero when 0.0 is drawn. – Arya McCarthy May 15 '17 at 11:13
  • Thanks! So if I want the min and max of the distribution to be between 1 and 1000, are they my low and high here or their exponentials? – jkrish May 15 '17 at 11:13
  • @aryamccarthy True! However my distribution would have values> 0.0 so I could handle it as an exception. – jkrish May 15 '17 at 11:14
  • 1
    I'd suggest to @Scott that he use different defaults. I executed his code and got `OverflowError: Range exceeds bounds.` repeatedly. – Arya McCarthy May 15 '17 at 11:15
  • Good point! D'oh! All fixed. @jkrish if you want the distribution between 1 and 1000, you'll need to input their exponentials, as in the R function. – Scott Gigante May 15 '17 at 11:17
  • @ScottGigante Thanks! I am afraid for high = np.exp(1000) I run into similar errors: `RuntimeWarning: overflow encountered in exp`; `OverflowError: Range exceeds valid bounds`. – jkrish May 15 '17 at 11:23
  • @jkrish I just noticed this: `np.exp(1000) == inf`. I've rewritten it with the limits now being the actual limits. It's less consistent with other implementations (eg in R) but more robust. – Scott Gigante May 15 '17 at 11:28
  • Scott, please, do you have any idea how to "legally" create scipy-compatible lognuniform random numbers generator which has rvs method using your example? I figure it has to rely on https://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.stats.rv_continuous.html but can't get it working... – Anatoly Alekseev Nov 03 '17 at 19:57
  • 2
    @Anatoly-Alekseev this really should be a separate question. However if you need a hint, there's an excellent example of the code required at the bottom of the page you linked. You would just replace the pdf with that of a log uniform distribution: $$f_{X}(x) = \frac{I_{[e^a, e^b]}(x)}{x(b - a)}$$. Derivation here : https://stats.stackexchange.com/questions/110070/log-uniform-distributions – Scott Gigante Nov 04 '17 at 22:24
  • This distribution is already implemented in SciPy as the reciprocal distribution (see the answer below). – Arkandias Dec 04 '19 at 22:49
18

SciPy v1.4 includes a loguniform random variable: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.loguniform.html

Here's how to use it:

from scipy.stats import loguniform

rvs = loguniform.rvs(1e-2, 1e0, size=1000)

This will create random variables evenly spaced between 0.01 and 1. That best shown by visualizing the log-scaled histogram:

This "log-scaling" works regardless of base; loguniform.rvs(2**-2, 2**0, size=1000) also produces log-uniform random variables. More details are in loguniform's documentation.

Scott
  • 2,568
  • 1
  • 27
  • 39
8

I believe the scipy.stats.reciprocal is the distribution you want.
From the documentation:

The probability density function for reciprocal is:

f(x, a, b) = \frac{1}{x \log(b/a)}

for a <= x <= b and a, b > 0

reciprocal takes a and b as shape parameters.

joaoFaria
  • 123
  • 1
  • 4
  • Quickly, this doesn't look like a loguniform distribution, it's more of an inverse log. Correct me if I'm wrong. – Guillaume Chevalier Apr 22 '19 at 19:01
  • @GuillaumeChevalier agreed, this is not a log uniform dist – Jinglesting Jul 16 '19 at 11:36
  • how is this probability density different from the one in [this link](http://ecolego.facilia.se/ecolego/show/Log-Uniform%20Distribution?highlight=uniform) (mentioned in the answer of @ScottGigante) ? – joaoFaria Jul 16 '19 at 15:20
  • 1
    This is the correct answer: the log of the reciprocal distribution is a uniform distribution! – Arkandias Dec 04 '19 at 22:48
  • This is the correct answer. Verify experimentally: `import numpy as np; import matplotlib.pyplot as plt; import scipy.stats; plt.hist(loguniform(0, np.log(20), 20000), bins=50); plt.hist(scipy.stats.reciprocal(1, 20).rvs(20000), bins=50)` – Scott Gigante Dec 06 '19 at 22:59
0
from random import random
from math import log

def loguniform(lo,hi,seed=random()):
    return lo ** ((((log(hi) / log(lo)) - 1) * seed) + 1)

You can check this using a specific seed value: lognorm(10,1000,0.5) returns 100.0

Wolf Elkan
  • 749
  • 6
  • 2
0

Here is one:

Simply use the .rvs() method provided:

class LogUniform(HyperparameterDistribution):
    """Get a LogUniform distribution.
    For example, this is good for neural networks' learning rates: that vary exponentially."""

    def __init__(self, min_included: float, max_included: float):
        """
        Create a quantized random log uniform distribution.
        A random float between the two values inclusively will be returned.
        :param min_included: minimum integer, should be somehow included.
        :param max_included: maximum integer, should be somehow included.
        """
        self.log2_min_included = math.log2(min_included)
        self.log2_max_included = math.log2(max_included)
        super(LogUniform, self).__init__()

    def rvs(self) -> float:
        """
        Will return a float value in the specified range as specified at creation.
        :return: a float.
        """
        return 2 ** random.uniform(self.log2_min_included, self.log2_max_included)

    def narrow_space_from_best_guess(self, best_guess, kept_space_ratio: float = 0.5) -> HyperparameterDistribution:
        """
        Will narrow, in log space, the distribution towards the new best_guess.
        :param best_guess: the value towards which we want to narrow down the space. Should be between 0.0 and 1.0.
        :param kept_space_ratio: what proportion of the space is kept. Default is to keep half the space (0.5).
        :return: a new HyperparameterDistribution that has been narrowed down.
        """
        log2_best_guess = math.log2(best_guess)
        lost_space_ratio = 1.0 - kept_space_ratio
        new_min_included = self.log2_min_included * kept_space_ratio + log2_best_guess * lost_space_ratio
        new_max_included = self.log2_max_included * kept_space_ratio + log2_best_guess * lost_space_ratio
        if new_max_included <= new_min_included or kept_space_ratio == 0.0:
            return FixedHyperparameter(best_guess).was_narrowed_from(kept_space_ratio, self)
        return LogUniform(2 ** new_min_included, 2 ** new_max_included).was_narrowed_from(kept_space_ratio, self)

The original project also includes a LogNormal distribution if that also interests you.

Source:

License:

  • Apache License 2.0, Copyright 2019 Neuraxio Inc.
Guillaume Chevalier
  • 9,613
  • 8
  • 51
  • 79
0
from neuraxle.hyperparams.distributions import LogUniform

# Create a Log Uniform Distribution that ranges from 0.001 to 0.1: 
learning_rate_distribution = LogUniform(0.001, 0.1)

# Get a Random Value Sample (RVS) from the distribution: 
learning_rate_sample = learning_rate_distribution.rvs()

print(learning_rate_sample)

Example output:

0.004532

This is using Neuraxle.

Guillaume Chevalier
  • 9,613
  • 8
  • 51
  • 79
0

A better approach would be instead of directly generating a sample from a log-uniform, you should create the log-uniform density.

In statistics speak, that is a reciprocal distribution which is already in SciPy: scipy.stats.reciprocal. For example, to build a sample that is 10^{x~U[-1,1]}, you would do:

rv = scipy.stats.reciprocal(a=0.1,b=10)
x = rv.rvs(N)

Alternatively, I wrote and use the following code to take the log-transform of any scipy.stats-like (frozen) random-variable

class LogTransformRV(scipy.stats.rv_continuous):
    def __init__(self,rv,base=10):
        self.rv = rv
        self.base = np.e if base in {'e','E'} else base
        super(LogTransformRV, self).__init__()
        self.a,self.b = self.base ** self.rv.ppf([0,1])

    def _pdf(self,x):
        return self.rv.pdf(self._log(x))/(x*np.log(self.base)) # Chain rule

    def _cdf(self,x):
        return self.rv.cdf(self._log(x)) 

    def _ppf(self,y):
        return self.base ** self.rv.ppf(y)

    def _log(self,x):
        return np.log(x)/np.log(self.base)
Justin Winokur
  • 91
  • 1
  • 1
  • 7