https://github.com/GPflow/GPflow
Raw File
Tip revision: 445112bcb708b6ddce327577cdd9c4a76a185fdf authored by John Bradshaw on 24 October 2017, 10:52:48 UTC
Merge remote-tracking branch 'origin/GPflow-1.0-RC' into john-bradshaw/binary-class-GP
Tip revision: 445112b
gaussian_utils.py
import tensorflow as tf

from . import decors

ERF_CODY_LIMIT1 = 0.6629
ERF_CODY_LIMIT2 = 5.6569
M_LN2PI = 1.83787706640934533908193770913
M_LN2 = 0.69314718055994530941723212146
M_SQRTPI = 1.77245385090551602729816748334
M_SQRT2 = 1.41421356237309504880168872421


def convert_from_natural_to_params(nu, tau):
    """
    elementise
    """
    mu = nu / tau
    sigma_sq = 1. / tau
    return mu, sigma_sq


def convert_from_params_to_natural(mu, sigma_sq):
    """
    elementwise
    """
    tau = 1. / sigma_sq
    nu = mu * tau
    return nu, tau


def log_pdf_normal(z):
    return -0.5*(M_LN2PI+tf.square(z))


def deriv_log_cdf_normal(z, name=None):
    """
    Robust implementations of derivative of the log cdf of a standard normal.
    taken from the C code by Matthias Seeger at
    https://github.com/mseeger/apbsint/blob/master/src/eptools/potentials/SpecfunServices.h

    Also see the GPy project:
    https://github.com/SheffieldML/GPy/blob/devel/GPy/util/univariate_Gaussian.py

    Works elementise.
    """
    name = name or "deriv_log_cdf_normal"

    with tf.name_scope(name):
        orig_shape = tf.shape(z)
        z = tf.reshape(z, [-1])

        def inbetween_erf_cody_limit(z):
            # Phi(z) approx (1 + y R_3(y^2))/2, y = z/sqrt(2)
            return tf.identity(2.0 * tf.exp(log_pdf_normal(z)) / (1.0 + (z / M_SQRT2) * _erf_rational_helper_r3(0.5 * tf.square(z))),
                               name="inbetween_erf_cody_limit_op")

        def lower_than_zero(z):
            # Phi(z) approx N(z) Q(-z)/(-z), z<0
            return tf.identity(-z / _erf_rational_helper(-z), name="lower_than_zero_op")

        def default(z):
            t = tf.exp(log_pdf_normal(z), name="pdf_normal_for_def")
            return tf.identity(t / (1.0 - t * _erf_rational_helper(z) / z), name="default_op")


        # So we want to have an elementwise switch function. We could do this in TF with a map and a
        # case. But here instead decided to dynamically partition out into the three different routes
        # And then stitch back the results at the end.

        cases = tf.zeros_like(z, dtype=tf.int32)  # 0 will mean default route
        cases = tf.where(tf.less(tf.abs(z), ERF_CODY_LIMIT1), tf.ones_like(z, dtype=tf.int32), cases)
        # inbetween_erf_cody_limit will be case 1.
        cases = tf.where(tf.less_equal(z, -ERF_CODY_LIMIT1), 2 * tf.ones_like(z, dtype=tf.int32), cases,
                         name="final_cases")
        #^  lower_than_zero will be case 2, but this will only happen if still default and less than zero.

        data_split = tf.dynamic_partition(z, cases, 3)
        partitions = tf.dynamic_partition(tf.range(0, tf.shape(z)[0]), cases, 3)


        default_res = default(data_split[0])
        inbetween_res = inbetween_erf_cody_limit(data_split[1])
        low_than_zero_res = lower_than_zero(data_split[2])

        results_stitched = tf.dynamic_stitch(partitions, [default_res, inbetween_res, low_than_zero_res])
        results = tf.reshape(results_stitched, orig_shape, name="final_reshaped")
    return results


@decors.name_scope("erf_rational_helper")
def _erf_rational_helper(x):
    assertion = tf.assert_positive(x)

    def above_erf_limit(x):
        """
         x/sqrt(2) >= 4
         Q(x)   = 1 + sqrt(pi) y R_1(y),
         R_1(y) = poly(p_j,y) / poly(q_j,y),  where  y = 2/x^2
         Ordering of arrays: 4,3,2,1,0,5 (only for numerator p_j; q_5=1)
         ATTENTION: The p_j are negative of the entries here
        """
        P1_ARRAY = [
            3.05326634961232344e-1, 3.60344899949804439e-1,
            1.25781726111229246e-1, 1.60837851487422766e-2,
            6.58749161529837803e-4, 1.63153871373020978e-2]
        Q1_ARRAY = [
            2.56852019228982242e+0, 1.87295284992346047e+0,
            5.27905102951428412e-1, 6.05183413124413191e-2,
            2.33520497626869185e-3]

        y = 2.0 * tf.reciprocal(tf.square(x))
        res = y * P1_ARRAY[5]
        den = y

        for p1_val, q1_val in zip(P1_ARRAY[:4], Q1_ARRAY[:4]):
            res = (res + p1_val) * y
            den = (den + q1_val) * y

        # Minus, because p(j) values have to be negated
        return tf.identity(1.0 - M_SQRTPI * y * (res + P1_ARRAY[4]) / (den + Q1_ARRAY[4]), name="above_erf_limit_route")

    def else_func(x):
        """
         x/sqrt(2) < 4, x/sqrt(2) >= 0.469
         Q(x)   = sqrt(pi) y R_2(y),
         R_2(y) = poly(p_j,y) / poly(q_j,y),   y = x/sqrt(2)
         Ordering of arrays: 7,6,5,4,3,2,1,0,8 (only p_8; q_8=1)
        """
        P2_ARRAY = [
            5.64188496988670089e-1, 8.88314979438837594e+0,
            6.61191906371416295e+1, 2.98635138197400131e+2,
            8.81952221241769090e+2, 1.71204761263407058e+3,
            2.05107837782607147e+3, 1.23033935479799725e+3,
            2.15311535474403846e-8]
        Q2_ARRAY = [
            1.57449261107098347e+1, 1.17693950891312499e+2,
            5.37181101862009858e+2, 1.62138957456669019e+3,
            3.29079923573345963e+3, 4.36261909014324716e+3,
            3.43936767414372164e+3, 1.23033935480374942e+3]


        y = x / M_SQRT2
        res = y * P2_ARRAY[8]
        den = y

        for p2_val, q2_val in zip(P2_ARRAY[:7], Q2_ARRAY[:7]):
            res = (res + p2_val) * y
            den = (den + q2_val) * y

        return tf.identity(M_SQRTPI * y * (res + P2_ARRAY[7]) / (den + Q2_ARRAY[7]), name="else_route_end")

    # with tf.control_dependencies([assertion]):
    #     result = tf.cond(tf.greater_equal(x, ERF_CODY_LIMIT2),
    #             true_fn=above_erf_limit,
    #             false_fn=else_func)
    with tf.control_dependencies([assertion]):
        cases = tf.where(tf.greater_equal(x, ERF_CODY_LIMIT2), tf.zeros_like(x, dtype=tf.int32), tf.ones_like(x, dtype=tf.int32))
        data_split = tf.dynamic_partition(x, cases, 2)
        partitions = tf.dynamic_partition(tf.range(0, tf.shape(x)[0]), cases, 2)

        abover_erf_lim_res = above_erf_limit(data_split[0])
        else_res = else_func(data_split[1])

        result = tf.dynamic_stitch(partitions, [abover_erf_lim_res, else_res], name="erf_rational_helper_final")
    return result


@decors.name_scope("erf_rational_helper_r3")
def _erf_rational_helper_r3(y):
    assertion = tf.assert_non_negative(y)

    P3_ARRAY = [
        3.16112374387056560e+0, 1.13864154151050156e+2,
        3.77485237685302021e+2, 3.20937758913846947e+3,
        1.85777706184603153e-1]
    Q3_ARRAY = [
        2.36012909523441209e+1, 2.44024637934444173e+2,
        1.28261652607737228e+3, 2.84423683343917062e+3]

    nom = y * P3_ARRAY[4]
    den = y

    for p_val, q_val in zip(P3_ARRAY[:3], Q3_ARRAY[:3]):
        nom = (nom + p_val) * y
        den = (den + q_val) * y

    with tf.control_dependencies([assertion]):
        result = (nom + P3_ARRAY[3]) / (den + Q3_ARRAY[3])
    return result
back to top