File size: 1,872 Bytes
7e9cfd1
 
 
 
 
 
 
 
 
4cbf907
 
7e9cfd1
 
 
 
 
 
 
 
 
4cbf907
7e9cfd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Stacking ensemble

The combiner: a per-class linear head over the ensemble's per-item probabilities, plus the bank of
probabilities it reads. Given those features it reproduces the released label vectors for both tracks.

## Run

```bash
pip install numpy
python predict.py --track t1     # 352 labels, checked against t1_perclass_stack.txt
python predict.py --track t2     # 644 labels, checked against t2_perclass_stack.txt
```

## Files

| File | Contents |
|---|---|
| `t1_stacker.json`, `t2_stacker.json` | head weights (`coef`, `intercept`), the ordered `sources` list, `npy_column_order`, `label_order`, `n_items` |
| `probs/*.npy` | per-source `(N, 3)` probability arrays, 38 sources for T1 and 40 for T2 |
| `predict.py` | loads a head and its features, applies it, writes and checks the labels |
| `t1_perclass_stack.txt`, `t2_perclass_stack.txt` | the released label vectors |

## Method

Each source contributes a 3-way probability per item. The head reads the concatenated `(N, 3J)`
features and applies per-class weights followed by argmax:

```
logits = X @ W.T + b        # W: (3, 3J), b: (3,)
label  = argmax(logits)
```

Sizes: T1 has J=38, 114 features and 345 parameters; T2 has J=40, 120 features and 363 parameters.

Watch the column convention. `label_order` is `["Favor","Against","None"]` while the on-disk `.npy`
column order is `["Against","Favor","None"]`. `predict.py` applies the permutation explicitly, so
reuse that rather than assuming either order.

## Why a per-class head rather than a weighted average

Weighted-mixture families over the same bank (convex, signed linear, and log/product of experts, 39
weights each) cannot reproduce this labeling: each one gives a Farkas certificate with a
strictly negative margin. The per-class head can, because it weights every source-by-class cell
separately instead of scoring whole members.