Winnower
The Winnower is the final stage of the pipeline. It prepares held-out timelines
for evaluation: it splits each timeline at a configurable cut-point into a
past portion (the context a model is given) and a future portion (what
the model must predict), and it attaches outcome labels. Its behavior is driven
by a winnowing config (winnowing.yaml).
The name is apt — the winnower also filters out subjects that cannot be
fairly evaluated, for example timelines that end before the outcome horizon is
reached.
What it produces
Running the winnower (via save_all) writes
one file per configured split to the processed-data directory:
{split}_for_inference.parquet — the winnowed timelines with past/future
splits and outcome flags (defaults to held_out).
How it works
prepare_winnowed_frame chains
these steps:
load_frame — load the tokenized
timelines for the requested split and compute elapsed times.
run_thresholding — compute
last_valid, the cut-point between past and future. This is derived either
from an elapsed-time horizon or from the first occurrence of a token of
interest. Timelines that never reach the cut-point are dropped.
add_outcome_flags — for each
configured outcome token, add *_past and *_future boolean labels (for
example DSCG//expired_past and DSCG//expired_future), so you can tell
whether an outcome truly falls within the prediction window.
!!! note "Depends on tokenizer artifacts" The winnower reads the tokenizer.yaml
written by the Tokenizer to resolve outcome-token patterns
against the learned vocabulary, so tokenization must run first.
prepares held-out data for evaluation,
adding flags to disqualify certain subjects from evaluation
Winnower
Bases: Configurable
filters held-out timelines for evaluation;
assigns flags to disqualify certain subjects from evaluation,
e.g. those whose timelines ends prior to the outcome horizon
Source code in src/cocoa/winnower.py
| class Winnower(Configurable):
"""
filters held-out timelines for evaluation;
assigns flags to disqualify certain subjects from evaluation,
e.g. those whose timelines ends prior to the outcome horizon
"""
default_file = "winnowing.yaml"
def __init__(
self,
winnowing_cfg: pathlib.Path | str = None,
processed_data_home: pathlib.Path | str = None,
is_training: bool = True,
**kwargs,
):
super().__init__(winnowing_cfg, **kwargs)
self.processed_data_home = (
pathlib.Path(processed_data_home).expanduser().resolve()
)
self.tkzr_cfg = OmegaConf.load(self.processed_data_home / "tokenizer.yaml")
self.grokked_outcome_tokens = [
x
for x in self.tkzr_cfg.lookup.keys()
if any(fnmatch.fnmatch(x, p) for p in self.cfg.outcome_tokens)
]
self.rng = np.random.default_rng(seed=42)
self.logger.info("Winnower initialized...")
self.logger.info(f"{self.processed_data_home=}")
self.logger.info(
f"Processed expressions to generate {self.grokked_outcome_tokens=}"
)
def load_frame(self, split="held_out") -> pl.LazyFrame:
"""
loads held_out timelines, and performs some preliminary calculations;
these are lazily evaluated, so only completed if used
"""
return (
pl.scan_parquet(self.processed_data_home / "tokens_times.parquet")
.join(
pl.scan_parquet(self.processed_data_home / "subject_splits.parquet"),
on="subject_id",
validate="1:1",
)
.filter(pl.col("split") == split)
.drop("split")
.with_columns(
s_elapsed=pl.col("times").list.eval(
(pl.element() - pl.element().first()).dt.total_seconds()
)
)
.with_columns(s_total_duration=pl.col("s_elapsed").list.last())
)
def run_thresholding(self, df: pl.LazyFrame) -> pl.LazyFrame:
"""
evaluates configurable criteria for establishing a cut-point "last_valid";
drops timelines that do not reach that point
"""
if "horizon_s" in self.cfg or "duration_s" in self.cfg.get("threshold", {}):
# run duration-based thresholding
horizon_s = self.cfg.get("horizon_s", self.cfg.threshold.duration_s)
return df.filter(pl.col("s_total_duration") > horizon_s).with_columns(
last_valid=pl.col("s_elapsed")
.list.eval(pl.element() < horizon_s)
.list.sum()
)
elif "first_occurrence" in self.cfg.get("threshold", {}):
# run first-occurrence-based thresholding
toi = self.tkzr_cfg.lookup[self.cfg.threshold.first_occurrence]
return df.filter(pl.col("tokens").list.contains(toi)).with_columns(
last_valid=pl.col("tokens")
.list.eval(pl.element() == toi)
.list.arg_max()
+ pl.lit(1)
# place the triggering token into the past; it is known
)
else:
raise NotImplementedError("Please check the thresholding configuration.")
def add_outcome_flags(self, df: pl.LazyFrame) -> pl.LazyFrame:
"""
adds boolean flags for each outcome token and tense,
e.g. DSCG//expired_past, DSCG//expired_future
"""
df = df.with_columns(
tokens_past=pl.col("tokens").list.head("last_valid"),
s_elapsed_past=pl.col("s_elapsed").list.head("last_valid"),
tokens_future=pl.col("tokens").list.tail(
pl.col("tokens").list.len() - pl.col("last_valid")
),
) # split into past and future
if "horizon_after_threshold_s" in self.cfg:
df = (
df.with_columns(
s_elapsed_thresh=pl.col("times")
.list.tail(
pl.col("tokens_future").list.len() + 1
) # include threshold time
.list.eval((pl.element() - pl.element().first()).dt.total_seconds())
)
.with_columns(
valid_future_count=pl.col("s_elapsed_thresh")
.list.eval(pl.element() <= self.cfg.horizon_after_threshold_s)
.list.sum()
- pl.lit(1) # threshold token was counted, drop it
)
.with_columns(
tokens_future=pl.col("tokens_future").list.head(
"valid_future_count"
)
)
)
return df.with_columns(
**{
f"{t}_{tense}": pl.col(f"tokens_{tense}").list.contains(
self.tkzr_cfg.lookup[t]
)
for t in self.grokked_outcome_tokens
for tense in ("past", "future")
}
)
def prepare_winnowed_frame(self, split="held_out") -> pl.LazyFrame:
"""loads held-out data, splits at time threshold, and prepares labels"""
return (
self.load_frame(split=split)
.pipe(self.run_thresholding)
.pipe(self.add_outcome_flags)
)
def save_all(self, verbose: bool = False):
"""grabs winnowed frame, prints summary stats if requested, and saves it"""
for split in self.cfg.get("splits", ["held_out"]):
df = self.prepare_winnowed_frame(split=split)
df.sink_parquet(
self.processed_data_home / f"{split}_for_inference.parquet",
engine="streaming",
)
if verbose:
self.logger.info(f"Prepared split {split} for inference:")
self.logger.summarize_thresholded(df, self.grokked_outcome_tokens)
|
add_outcome_flags(df)
adds boolean flags for each outcome token and tense,
e.g. DSCG//expired_past, DSCG//expired_future
Source code in src/cocoa/winnower.py
| def add_outcome_flags(self, df: pl.LazyFrame) -> pl.LazyFrame:
"""
adds boolean flags for each outcome token and tense,
e.g. DSCG//expired_past, DSCG//expired_future
"""
df = df.with_columns(
tokens_past=pl.col("tokens").list.head("last_valid"),
s_elapsed_past=pl.col("s_elapsed").list.head("last_valid"),
tokens_future=pl.col("tokens").list.tail(
pl.col("tokens").list.len() - pl.col("last_valid")
),
) # split into past and future
if "horizon_after_threshold_s" in self.cfg:
df = (
df.with_columns(
s_elapsed_thresh=pl.col("times")
.list.tail(
pl.col("tokens_future").list.len() + 1
) # include threshold time
.list.eval((pl.element() - pl.element().first()).dt.total_seconds())
)
.with_columns(
valid_future_count=pl.col("s_elapsed_thresh")
.list.eval(pl.element() <= self.cfg.horizon_after_threshold_s)
.list.sum()
- pl.lit(1) # threshold token was counted, drop it
)
.with_columns(
tokens_future=pl.col("tokens_future").list.head(
"valid_future_count"
)
)
)
return df.with_columns(
**{
f"{t}_{tense}": pl.col(f"tokens_{tense}").list.contains(
self.tkzr_cfg.lookup[t]
)
for t in self.grokked_outcome_tokens
for tense in ("past", "future")
}
)
|
load_frame(split='held_out')
loads held_out timelines, and performs some preliminary calculations;
these are lazily evaluated, so only completed if used
Source code in src/cocoa/winnower.py
| def load_frame(self, split="held_out") -> pl.LazyFrame:
"""
loads held_out timelines, and performs some preliminary calculations;
these are lazily evaluated, so only completed if used
"""
return (
pl.scan_parquet(self.processed_data_home / "tokens_times.parquet")
.join(
pl.scan_parquet(self.processed_data_home / "subject_splits.parquet"),
on="subject_id",
validate="1:1",
)
.filter(pl.col("split") == split)
.drop("split")
.with_columns(
s_elapsed=pl.col("times").list.eval(
(pl.element() - pl.element().first()).dt.total_seconds()
)
)
.with_columns(s_total_duration=pl.col("s_elapsed").list.last())
)
|
prepare_winnowed_frame(split='held_out')
loads held-out data, splits at time threshold, and prepares labels
Source code in src/cocoa/winnower.py
| def prepare_winnowed_frame(self, split="held_out") -> pl.LazyFrame:
"""loads held-out data, splits at time threshold, and prepares labels"""
return (
self.load_frame(split=split)
.pipe(self.run_thresholding)
.pipe(self.add_outcome_flags)
)
|
run_thresholding(df)
evaluates configurable criteria for establishing a cut-point "last_valid";
drops timelines that do not reach that point
Source code in src/cocoa/winnower.py
| def run_thresholding(self, df: pl.LazyFrame) -> pl.LazyFrame:
"""
evaluates configurable criteria for establishing a cut-point "last_valid";
drops timelines that do not reach that point
"""
if "horizon_s" in self.cfg or "duration_s" in self.cfg.get("threshold", {}):
# run duration-based thresholding
horizon_s = self.cfg.get("horizon_s", self.cfg.threshold.duration_s)
return df.filter(pl.col("s_total_duration") > horizon_s).with_columns(
last_valid=pl.col("s_elapsed")
.list.eval(pl.element() < horizon_s)
.list.sum()
)
elif "first_occurrence" in self.cfg.get("threshold", {}):
# run first-occurrence-based thresholding
toi = self.tkzr_cfg.lookup[self.cfg.threshold.first_occurrence]
return df.filter(pl.col("tokens").list.contains(toi)).with_columns(
last_valid=pl.col("tokens")
.list.eval(pl.element() == toi)
.list.arg_max()
+ pl.lit(1)
# place the triggering token into the past; it is known
)
else:
raise NotImplementedError("Please check the thresholding configuration.")
|
save_all(verbose=False)
grabs winnowed frame, prints summary stats if requested, and saves it
Source code in src/cocoa/winnower.py
| def save_all(self, verbose: bool = False):
"""grabs winnowed frame, prints summary stats if requested, and saves it"""
for split in self.cfg.get("splits", ["held_out"]):
df = self.prepare_winnowed_frame(split=split)
df.sink_parquet(
self.processed_data_home / f"{split}_for_inference.parquet",
engine="streaming",
)
if verbose:
self.logger.info(f"Prepared split {split} for inference:")
self.logger.summarize_thresholded(df, self.grokked_outcome_tokens)
|