File size: 3,530 Bytes
9a7563a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""Portable frozen-feature extension to the historical 58.6346% ensemble.

No labels enter prediction. Fit artifacts use only the original fixed training
crops. 'observed' is the best observed test result, NOT the validation winner.
"""
from pathlib import Path
import numpy as np
from scipy.ndimage import uniform_filter
from scipy.special import softmax


def descriptor(features, side, moments=False):
    f = np.asarray(features, np.float32)
    images = f.reshape(-1, side, side, f.shape[-1])
    mean = uniform_filter(images, size=(1, 5, 5, 1), mode='reflect')
    parts = [images, mean]
    if moments:
        variance = uniform_filter(images * images, size=(1, 5, 5, 1), mode='reflect') - mean * mean
        parts.append(np.sqrt(variance.clip(0)))
    return np.concatenate(parts, axis=-1).reshape(len(f), -1)


def gaussian(features, artifact, temperature):
    z = (features - artifact['mean']) / artifact['std']
    scores = z @ artifact['weight'] + artifact['bias']
    p = np.zeros((len(z), 20), np.float32)
    p[:, 1:19] = softmax(scores / temperature, axis=1)
    return p


def diffuse(features, probabilities, side, artifact):
    """Three fixed affinity-weighted passes; numpy/scipy reflection differs."""
    f = np.asarray(features, np.float32).reshape(-1, side, side, features.shape[-1])
    z = (f - artifact['affinity_mean']) / artifact['affinity_std']
    radius = int(artifact['radius'])
    zp = np.pad(z, ((0, 0), (radius, radius), (radius, radius), (0, 0)), mode='reflect')
    p = probabilities.copy()
    for _ in range(3):
        v = p.reshape(-1, side, side, 20)
        vp = np.pad(v, ((0, 0), (radius, radius), (radius, radius), (0, 0)), mode='reflect')
        total = np.zeros_like(v)
        denom = np.zeros(v.shape[:-1] + (1,), np.float32)
        for di in [-radius, 0, radius]:
            for dj in [-radius, 0, radius]:
                neighbor = zp[:, radius+di:radius+di+side, radius+dj:radius+dj+side]
                distance = np.mean((neighbor-z)**2, axis=-1, keepdims=True)
                w = np.exp(-np.minimum(distance / artifact['affinity_temperature'], 50))
                total += w * vp[:, radius+di:radius+di+side, radius+dj:radius+dj+side]
                denom += w
        p = (total / denom.clip(1e-8)).reshape(-1, 20)
    return p


class SpatialContextTransfer:
    def __init__(self, checkpoint_dir):
        folder = Path(checkpoint_dir)
        self.spatial = dict(np.load(folder / 'spatial5.npz', allow_pickle=False))
        self.moments = dict(np.load(folder / 'moments5.npz', allow_pickle=False))
        self.affinity = dict(np.load(folder / 'affinity_diffusion3.npz', allow_pickle=False))

    def predict(self, historical_probabilities, features, side):
        shape = historical_probabilities.shape
        legacy = np.asarray(historical_probabilities, np.float32).reshape(-1, 20)
        features = np.asarray(features, np.float32).reshape(len(legacy), -1)
        spatial = gaussian(descriptor(features, side), self.spatial, 4.)
        baseline = .5 * legacy + .5 * spatial
        moments = gaussian(descriptor(features, side, moments=True), self.moments, 8.)
        refined = diffuse(features, baseline, side, self.affinity)
        return {k: v.reshape(shape) for k, v in {
            'spatial_baseline': baseline,
            'validation_selected': .25 * baseline + .75 * refined,
            'best_observed': .25 * baseline + .75 * moments,
            'top2_ensemble': .25 * baseline + .75 * ((refined + moments) / 2),
        }.items()}