Skip to content

slisemap.escape

Module that contains alternative escape heuristics.

escape_neighbourhood(X, Y, B, Z, local_model, local_loss, distance, kernel, radius=3.5, force_move=False, **_)

Try to escape a local optimum by moving the data items.

Move the data items to the neighbourhoods (embedding and local model) best suited for them. This is done by finding another item (in the optimal neighbourhood) and copying its values for Z and B.

Parameters:

Name Type Description Default
X Tensor

Data matrix.

required
Y Tensor

Target matrix.

required
B Tensor

Local models.

required
Z Tensor

Embedding matrix.

required
local_model Callable[[Tensor, Tensor], Tensor]

Prediction function for the local models.

required
local_loss Callable[[Tensor, Tensor], Tensor]

Loss function for the local models.

required
distance Callable[[Tensor, Tensor], Tensor]

Embedding distance function.

required
kernel Callable[[Tensor], Tensor]

Kernel for embedding distances.

required
radius float

For enforcing the radius of Z. Defaults to 3.5.

3.5
force_move bool

Do not allow the items to pair with themselves. Defaults to True.

False

Returns:

Name Type Description
B Tensor

Escaped B.

Z Tensor

Escaped Z.

Source code in slisemap/escape.py
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
def escape_neighbourhood(
    X: torch.Tensor,
    Y: torch.Tensor,
    B: torch.Tensor,
    Z: torch.Tensor,
    local_model: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    local_loss: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    distance: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    kernel: Callable[[torch.Tensor], torch.Tensor],
    radius: float = 3.5,
    force_move: bool = False,
    **_: Any,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """Try to escape a local optimum by moving the data items.

    Move the data items to the neighbourhoods (embedding and local model) best suited for them.
    This is done by finding another item (in the optimal neighbourhood) and copying its values for Z and B.

    Args:
        X: Data matrix.
        Y: Target matrix.
        B: Local models.
        Z: Embedding matrix.
        local_model: Prediction function for the local models.
        local_loss: Loss function for the local models.
        distance: Embedding distance function.
        kernel: Kernel for embedding distances.
        radius: For enforcing the radius of Z. Defaults to 3.5.
        force_move: Do not allow the items to pair with themselves. Defaults to True.

    Returns:
        B: Escaped `B`.
        Z: Escaped `Z`.
    """
    L = local_loss(local_model(X, B), Y)
    if radius > 0:
        Z2 = Z * (radius / (torch.sqrt(torch.sum(Z**2) / Z.shape[0]) + 1e-8))
        D = distance(Z2, Z2)
    else:
        D = distance(Z, Z)
    W = kernel(D)
    # K = torch.zeros_like(L)
    # for i in range(L.shape[1]):
    #     K[:, i] = torch.sum(W * L[:, i].ravel()[None, :], 1)
    # K = torch.sum(W[:, :, None] * L[None, :, :], 1)
    K = W @ L
    if force_move:
        _assert(
            K.shape[0] == K.shape[1],
            "force_move only works if (X, Y) corresponds to (B, Z)",
            escape_neighbourhood,
        )
        K.fill_diagonal_(np.inf)
    index = torch.argmin(K, 0)
    return B.detach()[index].clone(), Z.detach()[index].clone()

escape_greedy(X, Y, B, Z, local_model, local_loss, distance, kernel, radius=3.5, force_move=False, **_)

Try to escape a local optimum by moving the data items.

Move the data items to a locations with optimal local models. This is done by finding another item (with an optimal local model) and copying its values for Z and B.

Parameters:

Name Type Description Default
X Tensor

Data matrix.

required
Y Tensor

Target matrix.

required
B Tensor

Local models.

required
Z Tensor

Embedding matrix.

required
local_model Callable[[Tensor, Tensor], Tensor]

Prediction function for the local models.

required
local_loss Callable[[Tensor, Tensor], Tensor]

Loss function for the local models.

required
distance Callable[[Tensor, Tensor], Tensor]

Embedding distance function.

required
kernel Callable[[Tensor], Tensor]

Kernel for embedding distances.

required
radius float

For enforcing the radius of Z. Defaults to 3.5.

3.5
force_move bool

Do not allow the items to pair with themselves. Defaults to True.

False

Returns:

Name Type Description
B Tensor

Escaped B.

Z Tensor

Escaped Z.

Source code in slisemap/escape.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def escape_greedy(
    X: torch.Tensor,
    Y: torch.Tensor,
    B: torch.Tensor,
    Z: torch.Tensor,
    local_model: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    local_loss: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    distance: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    kernel: Callable[[torch.Tensor], torch.Tensor],
    radius: float = 3.5,
    force_move: bool = False,
    **_: Any,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """Try to escape a local optimum by moving the data items.

    Move the data items to a locations with optimal local models.
    This is done by finding another item (with an optimal local model) and copying its values for Z and B.

    Args:
        X: Data matrix.
        Y: Target matrix.
        B: Local models.
        Z: Embedding matrix.
        local_model: Prediction function for the local models.
        local_loss: Loss function for the local models.
        distance: Embedding distance function.
        kernel: Kernel for embedding distances.
        radius: For enforcing the radius of Z. Defaults to 3.5.
        force_move: Do not allow the items to pair with themselves. Defaults to True.

    Returns:
        B: Escaped `B`.
        Z: Escaped `Z`.
    """
    L = local_loss(local_model(X, B), Y)
    if force_move:
        _assert(
            L.shape[0] == L.shape[1],
            "force_move only works if (X, Y) corresponds to (B, Z)",
            escape_greedy,
        )
        L.fill_diagonal_(np.inf)
    index = torch.argmin(L, 0)
    return B.detach()[index].clone(), Z.detach()[index].clone()

escape_combined(X, Y, B, Z, local_model, local_loss, distance, kernel, radius=3.5, force_move=False, **_)

Try to escape a local optimum by moving the data items.

Move the data items to the neighbourhoods (embedding and local model) best suited for them. This is done by finding another item (in the optimal neighbourhood) and copying its values for Z and B.

This is a combination of escape_neighbourhood and escape_greedy.

Parameters:

Name Type Description Default
X Tensor

Data matrix.

required
Y Tensor

Target matrix.

required
B Tensor

Local models.

required
Z Tensor

Embedding matrix.

required
local_model Callable[[Tensor, Tensor], Tensor]

Prediction function for the local models.

required
local_loss Callable[[Tensor, Tensor], Tensor]

Loss function for the local models.

required
distance Callable[[Tensor, Tensor], Tensor]

Embedding distance function.

required
kernel Callable[[Tensor], Tensor]

Kernel for embedding distances.

required
radius float

For enforcing the radius of Z. Defaults to 3.5.

3.5
force_move bool

Do not allow the items to pair with themselves. Defaults to True.

False

Returns:

Name Type Description
B Tensor

Escaped B.

Z Tensor

Escaped Z.

Source code in slisemap/escape.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def escape_combined(
    X: torch.Tensor,
    Y: torch.Tensor,
    B: torch.Tensor,
    Z: torch.Tensor,
    local_model: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    local_loss: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    distance: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    kernel: Callable[[torch.Tensor], torch.Tensor],
    radius: float = 3.5,
    force_move: bool = False,
    **_: Any,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """Try to escape a local optimum by moving the data items.

    Move the data items to the neighbourhoods (embedding and local model) best suited for them.
    This is done by finding another item (in the optimal neighbourhood) and copying its values for Z and B.

    This is a combination of escape_neighbourhood and escape_greedy.

    Args:
        X: Data matrix.
        Y: Target matrix.
        B: Local models.
        Z: Embedding matrix.
        local_model: Prediction function for the local models.
        local_loss: Loss function for the local models.
        distance: Embedding distance function.
        kernel: Kernel for embedding distances.
        radius: For enforcing the radius of Z. Defaults to 3.5.
        force_move: Do not allow the items to pair with themselves. Defaults to True.

    Returns:
        B: Escaped `B`.
        Z: Escaped `Z`.
    """
    L = local_loss(local_model(X, B), Y)
    if radius > 0:
        Z2 = Z * (radius / (torch.sqrt(torch.sum(Z**2) / Z.shape[0]) + 1e-8))
        D = distance(Z2, Z2)
    else:
        D = distance(Z, Z)
    W = kernel(D)
    K = W @ L + L
    if force_move:
        _assert(
            K.shape[0] == K.shape[1],
            "force_move only works if (X, Y) corresponds to (B, Z)",
            escape_combined,
        )
        K.fill_diagonal_(np.inf)
    index = torch.argmin(K, 0)
    return B.detach()[index].clone(), Z.detach()[index].clone()

escape_marginal(X, Y, B, Z, local_model, local_loss, distance, kernel, radius=3.5, force_move=False, Xold=None, Yold=None, jit=True, **_)

Try to escape a local optimum by moving the data items.

Move the data items to locations with optimal marginal losses. This is done by finding another item (where the marginal loss is optimal) and copying its values for Z and B.

This might produce better results than escape_neighbourhood, but is really slow.

Parameters:

Name Type Description Default
X Tensor

Data matrix.

required
Y Tensor

Target matrix.

required
B Tensor

Local models.

required
Z Tensor

Embedding matrix.

required
local_model Callable[[Tensor, Tensor], Tensor]

Prediction function for the local models.

required
local_loss Callable[[Tensor, Tensor], Tensor]

Loss function for the local models.

required
distance Callable[[Tensor, Tensor], Tensor]

Embedding distance function.

required
kernel Callable[[Tensor], Tensor]

Kernel for embedding distances.

required
radius float

For enforcing the radius of Z. Defaults to 3.5.

3.5
force_move bool

Do not allow the items to pair with themselves. Defaults to True.

False
jit bool

Just-In-Time compile the loss function. Defaults to True.

True
Xold Optional[Tensor]

Trained X. Defaults to X.

None
Yold Optional[Tensor]

Trained Y. Defaults to Y.

None

Returns:

Name Type Description
B Tensor

Escaped B.

Z Tensor

Escaped Z.

Source code in slisemap/escape.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def escape_marginal(
    X: torch.Tensor,
    Y: torch.Tensor,
    B: torch.Tensor,
    Z: torch.Tensor,
    local_model: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    local_loss: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    distance: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    kernel: Callable[[torch.Tensor], torch.Tensor],
    radius: float = 3.5,
    force_move: bool = False,
    Xold: Optional[torch.Tensor] = None,
    Yold: Optional[torch.Tensor] = None,
    jit: bool = True,
    **_: Any,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """Try to escape a local optimum by moving the data items.

    Move the data items to locations with optimal marginal losses.
    This is done by finding another item (where the marginal loss is optimal) and copying its values for Z and B.

    This might produce better results than `escape_neighbourhood`, but is really slow.

    Args:
        X: Data matrix.
        Y: Target matrix.
        B: Local models.
        Z: Embedding matrix.
        local_model: Prediction function for the local models.
        local_loss: Loss function for the local models.
        distance: Embedding distance function.
        kernel: Kernel for embedding distances.
        radius: For enforcing the radius of Z. Defaults to 3.5.
        force_move: Do not allow the items to pair with themselves. Defaults to True.
        jit: Just-In-Time compile the loss function. Defaults to True.
        Xold: Trained X. Defaults to X.
        Yold: Trained Y. Defaults to Y.

    Returns:
        B: Escaped `B`.
        Z: Escaped `Z`.
    """
    from slisemap.slisemap import make_marginal_loss

    _assert(
        not force_move or X.shape[0] == Z.shape[0],
        "force_move only works if (X, Y) corresponds to (B, Z)",
        escape_marginal,
    )
    _assert(
        Xold is not None or X.shape[0] == Z.shape[0],
        "(Xold Yold) is required if (X, Y) does not correspond to (B, Z)",
        escape_marginal,
    )
    if radius > 0:
        Z2 = Z * (radius / (torch.sqrt(torch.sum(Z**2) / Z.shape[0]) + 1e-8))
    else:
        Z2 = Z
    lf, set_new = make_marginal_loss(
        X=X if Xold is None else Xold,
        Y=Y if Yold is None else Yold,
        B=B,
        Z=Z2,
        Xnew=X[:1],
        Ynew=Y[:1],
        local_model=local_model,
        local_loss=local_loss,
        distance=distance,
        kernel=kernel,
        radius=0.0,
        lasso=0.0,
        ridge=0.0,
        jit=jit and (X.shape[0] > 10 if Xold is None else Xold.shape[0] > 10),
    )
    index = []
    for i in range(X.shape[0]):
        set_new(X[i : i + 1], Y[i : i + 1])
        index.append(i)
        best = np.inf
        for j in range(Z.shape[0]):
            if force_move and i == j:
                continue
            loss = lf(B[j : j + 1], Z2[j : j + 1])
            if loss < best:
                best = loss
                index[-1] = j
    return B.detach()[index].clone(), Z.detach()[index].clone()