Skip to content

Interpolate

Interpolate

Bases: JSONSerializable

Interpolate combines a set of Outputs weighted by dissimilarity scores.

The scores depend on the Metric used by the NNSearch. They may be, for example, distances or negative cosine similarities.

Source code in src/anguilla/interpolate.py
class Interpolate(JSONSerializable):
    """
    `Interpolate` combines a set of `Outputs` weighted by dissimilarity scores.

    The scores depend on the `Metric` used by the `NNSearch`. 
    They may be, for example, distances or negative cosine similarities.
    """
    def __init__(self, **kw):
        super().__init__(**kw)

    def __call__(self, targets: List[Output], scores: Scores) -> Output:
        """
        Args:
            targets: [k x <batch dims> x <output dims>]
                first dimension is neighbor dimension
                trailing dimensions are feature dimensions
                remaining dimensions are batch dimensions
            scores: [k]

        Returns:
            output: [<batch dims> x <output dims>]
        """
        raise NotImplementedError

__call__(targets, scores)

Parameters:

Name Type Description Default
targets List[Output]

[k x x ] first dimension is neighbor dimension trailing dimensions are feature dimensions remaining dimensions are batch dimensions

required
scores Scores

[k]

required

Returns:

Name Type Description
output Output

[ x ]

Source code in src/anguilla/interpolate.py
def __call__(self, targets: List[Output], scores: Scores) -> Output:
    """
    Args:
        targets: [k x <batch dims> x <output dims>]
            first dimension is neighbor dimension
            trailing dimensions are feature dimensions
            remaining dimensions are batch dimensions
        scores: [k]

    Returns:
        output: [<batch dims> x <output dims>]
    """
    raise NotImplementedError

Mean

Bases: Interpolate

mean of neighbors (piecewise constant mapping)

Source code in src/anguilla/interpolate.py
class Mean(Interpolate):
    """mean of neighbors (piecewise constant mapping)"""
    def __init__(self):
        super().__init__()

    def __call__(self, targets, scores):
        return sum(targets) / len(targets)

Nearest

Bases: Interpolate

return nearest neighbor (voronoi cell mapping)

Source code in src/anguilla/interpolate.py
class Nearest(Interpolate):
    """return nearest neighbor (voronoi cell mapping)"""
    def __init__(self):
        super().__init__()

    def __call__(self, targets, scores):
        idx = np.argmin(scores, 0)
        if idx.ndim > 1:
            return [targets[i,j] for j,i in enumerate(idx)]
        else:
            return targets[idx]

Ripple

Bases: Interpolate

like Smooth but with high-frequency ripples outside the input domain.

useful for making random mappings in high dimensional spaces / bootstrapping expressive mappings from a few points.

Source code in src/anguilla/interpolate.py
class Ripple(Interpolate):
    """
    like `Smooth` but with high-frequency ripples outside the input domain.

    useful for making random mappings in high dimensional spaces / bootstrapping expressive mappings from a few points.
    """
    def __init__(self):
        super().__init__()

    def __call__(self, 
            targets:List[Output], scores:List[float], 
            ripple:float=1, ripple_depth:float=1, eps:float=1e-9):
        """
        Args:
            targets: size [K x ...output_dims...] list or ndarray
            scores: size [K] list or ndarray
            ripple: frequency of ripples
            ripple_depth: amplitude of ripples
            eps: small value preventing division by zero
        """
        targets, scores = np_coerce(targets, scores)

        scores = scores + eps
        assert np.min(scores) > 0

        # largest scores -> 0 weight
        # zero score -> inf weight
        mx = np.max(scores, 0)
        weights = 1/scores + (-3*mx*mx + 3*mx*scores - scores*scores)/(mx**3)
        weights = weights * 2**(
            ripple_depth * 
            (1+np.cos(np.pi*scores/mx)*np.sin(scores*np.pi*ripple))
            )

        weights = weights + eps
        weights = weights / weights.sum(0)

        # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
        result = np.transpose((
            np.transpose(targets)*np.transpose(weights)).sum(-1))

        return result

__call__(targets, scores, ripple=1, ripple_depth=1, eps=1e-09)

Parameters:

Name Type Description Default
targets List[Output]

size [K x ...output_dims...] list or ndarray

required
scores List[float]

size [K] list or ndarray

required
ripple float

frequency of ripples

1
ripple_depth float

amplitude of ripples

1
eps float

small value preventing division by zero

1e-09
Source code in src/anguilla/interpolate.py
def __call__(self, 
        targets:List[Output], scores:List[float], 
        ripple:float=1, ripple_depth:float=1, eps:float=1e-9):
    """
    Args:
        targets: size [K x ...output_dims...] list or ndarray
        scores: size [K] list or ndarray
        ripple: frequency of ripples
        ripple_depth: amplitude of ripples
        eps: small value preventing division by zero
    """
    targets, scores = np_coerce(targets, scores)

    scores = scores + eps
    assert np.min(scores) > 0

    # largest scores -> 0 weight
    # zero score -> inf weight
    mx = np.max(scores, 0)
    weights = 1/scores + (-3*mx*mx + 3*mx*scores - scores*scores)/(mx**3)
    weights = weights * 2**(
        ripple_depth * 
        (1+np.cos(np.pi*scores/mx)*np.sin(scores*np.pi*ripple))
        )

    weights = weights + eps
    weights = weights / weights.sum(0)

    # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
    result = np.transpose((
        np.transpose(targets)*np.transpose(weights)).sum(-1))

    return result

Smooth

Bases: Interpolate

Interpolate which is non-discontinuous (for k > 2)

tries to prevent discontinuities while preserving the input-output mapping exactly where close to data points.

works well with larger k. out-of-domain input areas tend to be averages of many outputs.

Source code in src/anguilla/interpolate.py
class Smooth(Interpolate):
    """
    Interpolate which is non-discontinuous (for `k` > 2)

    tries to prevent discontinuities while preserving the input-output mapping
    exactly where close to data points.

    works well with larger `k`.
    out-of-domain input areas tend to be averages of many outputs.
    """
    def __init__(self):
        super().__init__()

    def __call__(self, targets:List[Output], scores:List[float], eps:float=1e-9):
        """
        Args:
            targets: size [K x ...batch dims... x ...output_dims...] 
            scores: size [K x ...batch dims...] 
            eps: small value preventing division by zero
        """
        targets, scores = np_coerce(targets, scores)

        scores = scores + eps
        assert np.min(scores) > 0

        # largest scores -> 0 weight
        # zero score -> inf weight
        # zero first/second derivative at largest score
        mx = np.max(scores, 0)
        weights = 1/scores + (-3*mx*mx + 3*mx*scores - scores*scores)/(mx**3)

        weights = weights + eps
        weights = weights / weights.sum(0)

        # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
        result = np.transpose((
            np.transpose(targets)*np.transpose(weights)).sum(-1))

        return result

__call__(targets, scores, eps=1e-09)

Parameters:

Name Type Description Default
targets List[Output]

size [K x ...batch dims... x ...output_dims...]

required
scores List[float]

size [K x ...batch dims...]

required
eps float

small value preventing division by zero

1e-09
Source code in src/anguilla/interpolate.py
def __call__(self, targets:List[Output], scores:List[float], eps:float=1e-9):
    """
    Args:
        targets: size [K x ...batch dims... x ...output_dims...] 
        scores: size [K x ...batch dims...] 
        eps: small value preventing division by zero
    """
    targets, scores = np_coerce(targets, scores)

    scores = scores + eps
    assert np.min(scores) > 0

    # largest scores -> 0 weight
    # zero score -> inf weight
    # zero first/second derivative at largest score
    mx = np.max(scores, 0)
    weights = 1/scores + (-3*mx*mx + 3*mx*scores - scores*scores)/(mx**3)

    weights = weights + eps
    weights = weights / weights.sum(0)

    # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
    result = np.transpose((
        np.transpose(targets)*np.transpose(weights)).sum(-1))

    return result

Softmax

Bases: Interpolate

Like Mean, but weighted toward the nearer neighbors.

when k is small, has discontinuities when temp is large, acts more like Mean. -> tends to get 'washed out' for larger k / larger temp

when temp is small, acts more like Nearest (voronoi cells).

Source code in src/anguilla/interpolate.py
class Softmax(Interpolate):
    """
    Like `Mean`, but weighted toward the nearer neighbors.

    when `k` is small, has discontinuities
    when temp is large, acts more like `Mean`.
    -> tends to get 'washed out' for larger `k` / larger temp

    when temp is small, acts more like `Nearest` (voronoi cells).
    """
    def __init__(self):
        super().__init__()

    def __call__(self, targets:List[Output], scores:List[float], temp:float=0.5):
        """
        Args:
            targets: size [K x ...batch dims... x ...output_dims...] 
            scores: size [K x ...batch dims...] 
            temp: temperature of softmax
        """
        targets, scores = np_coerce(targets, scores)
        # print(targets.shape, scores.shape)

        if temp==0:
            result = Nearest()(targets, scores)
        else:
            centered = scores - np.mean(scores, 0) # for numerical precision
            logits = np.maximum(-centered/temp, -20)
            # print(f'{logits=}')
            if np.max(np.abs(logits)) > 80:
                # NOTE: not batched properly
                result = Nearest()(targets, scores)
            else:
                weights = np.exp(logits)
                # print(f'{weights=}')
                weights /= weights.sum(0)
                # print(f'{weights=}')
                # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
                result = np.transpose((
                    np.transpose(targets)*np.transpose(weights)).sum(-1))
        # print(f'{result=}')
        return result

__call__(targets, scores, temp=0.5)

Parameters:

Name Type Description Default
targets List[Output]

size [K x ...batch dims... x ...output_dims...]

required
scores List[float]

size [K x ...batch dims...]

required
temp float

temperature of softmax

0.5
Source code in src/anguilla/interpolate.py
def __call__(self, targets:List[Output], scores:List[float], temp:float=0.5):
    """
    Args:
        targets: size [K x ...batch dims... x ...output_dims...] 
        scores: size [K x ...batch dims...] 
        temp: temperature of softmax
    """
    targets, scores = np_coerce(targets, scores)
    # print(targets.shape, scores.shape)

    if temp==0:
        result = Nearest()(targets, scores)
    else:
        centered = scores - np.mean(scores, 0) # for numerical precision
        logits = np.maximum(-centered/temp, -20)
        # print(f'{logits=}')
        if np.max(np.abs(logits)) > 80:
            # NOTE: not batched properly
            result = Nearest()(targets, scores)
        else:
            weights = np.exp(logits)
            # print(f'{weights=}')
            weights /= weights.sum(0)
            # print(f'{weights=}')
            # result = (np.moveaxis(targets,0,-1)*weights).sum(-1)
            result = np.transpose((
                np.transpose(targets)*np.transpose(weights)).sum(-1))
    # print(f'{result=}')
    return result