Tasks¶
A Task describes one modeling problem. It holds ordered input Records and one representation of the expected output.
from timenet.types import ClassificationTask, Label, LabelSet
CONDITION = LabelSet(name="condition", labels=(Label(name="healthy"), Label(name="faulty")))
task = ClassificationTask(
inputs=(record,),
targets=("faulty",),
label_set=CONDITION,
)
dataset.add_task(task=task)
The concrete class identifies the task type. ClassificationTask and AnswerTask are different
task types, even when both contain string targets.
Common fields¶
Every Task supports these fields:
| Field | Purpose |
|---|---|
id |
Read-only int64 Task identity. add_task() sets it. |
inputs |
Ordered input Records. |
targets |
Ordered inline values or stored objects. |
prompt |
Optional instruction or question. |
input_modalities |
Optional override for the input kinds. None enables inference. |
split |
Optional partition: train, validation, or test. None when the dataset does not partition its Tasks. |
scope |
Optional input region. |
input_annotations |
Attached Annotation occurrences supplied as context. |
target_annotations |
Attached Annotation occurrences used as the answer. |
rationale |
Optional explanation associated with the answer. |
from_tasks |
Parent Tasks from which this Task derives. |
annotations |
Annotations that describe the Task itself. |
metadata |
JSON-compatible Task details. |
targets can contain strings, numbers, Booleans, Records, Signals, or Spans. None means that
target_annotations supplies the answer. An empty tuple is an explicit empty answer.
A Task cannot set both targets and target_annotations.
Input modalities¶
None by default, in this case the value is inferred.
Inference uses each input Signal's spec.modality, restricted to Signals named in scope, if any.
It also adds TEXT for a non-empty prompt or text input annotations. It ignores targets, target
annotations, rationale, and metadata. It does not load Signal values.
To override inference, pass a non-empty set:
from timenet.types import AnswerTask, InputModality
task = AnswerTask(
inputs=(record,),
prompt="Describe this image.",
input_modalities=frozenset({InputModality.IMAGE, InputModality.TEXT}),
)
The override remains subject to validation. For example, a Task with a prompt must declare TEXT.
The writer stores the resolved set in DuckDB, so SQL filters need no inference or Signal reads.
Read-back Tasks carry that stored set as an explicit declaration.
Splits¶
A dataset that ships a partition marks each Task with its split. Records carry no split of their
own: one Record may serve Tasks of several splits.
from timenet.types import ClassificationTask, Label, LabelSet, Split
ACTIVITY = LabelSet(
name="activity",
labels=(Label(name="walking"), Label(name="sitting")),
)
dataset.add_task(
task=ClassificationTask(
inputs=(record,),
targets=("walking",),
label_set=ACTIVITY,
split=Split.TRAIN,
)
)
TimeFDataset.get_train(), get_validation(), and get_test() return the Tasks of one split, and
get_all() returns every Task. Asking a split of a dataset that assigns none raises, since that is a
mistake rather than an empty selection. iter_tasks(split=...) on a dataset or a TimeFReader
filters the same way; the reader applies the filter in DuckDB, so a Task outside the split is never
built. to_features_and_targets(split=...) builds the (X, y) pair of one split.
To train on train and validation together while keeping test separate, select their union:
training_tasks = dataset.get_splits(Split.TRAIN, Split.VALIDATION)
test_tasks = dataset.get_test()
# Or consume the selection lazily from a TimeFDataset:
for task in dataset.iter_tasks(split=(Split.TRAIN, Split.VALIDATION)):
process(task)
Selection preserves dataset order and each Task's original split. Repeated split names do not
duplicate Tasks, and Tasks with split=None are excluded. get_splits() materializes its result
in one pass and raises if the dataset has no split assignments. The dataset iterator remains lazy
and simply yields no matches in that case. Both selectors require at least one valid split;
pass split=None, omit the argument, or use get_all() to include every Task. The dataset
iterator's split argument accepts an enum, a string, or an iterable of either. Modality filters
can be combined with split selection.
Register Tasks¶
Register all referenced objects before you register the Task:
add_task() validates Record, Signal, Annotation, Span, and parent-Task references. It does not
change the input Records. Use dataset.tasks_for(record) to find the Tasks that take a Record as
input.
Use add_tasks() for an atomic batch. A parent in from_tasks can appear in the same batch.
For a large task corpus, use set_task_stream(). The writer validates each streamed Task before it
writes the Task. A stream does not keep the complete task set in memory. TimeNet therefore cannot
validate from_tasks relationships across streamed Tasks.
Records that a Task references¶
A Task references these Records:
- Its inputs.
- Its candidate Records.
- Its Record targets.
- Each Record that holds a Signal that the Task references, for example a Signal target.
Only the inputs are Records that the Task takes as input. tasks_for(record) returns the Task only
for its inputs, and read_tasks(inputs=) selects the Task only through its inputs.
Scope an input¶
scope limits the region that the model gets. A scope holds the Signal objects that it covers:
from timenet.types import ClassificationTask, TimeInterval
task = ClassificationTask(
inputs=(record,),
scope=TimeInterval.seconds(
5.0,
8.0,
time_series=(vibration,),
),
targets=("faulty",),
label_set=CONDITION,
)
Use TimePoint or TimeInterval for a timeline Signal. Use StepPoint or StepInterval for an
ordinal Signal.
Built-in task types¶
TimeNet defines eight task classes:
| Class | Modeling operation | Typical target items |
|---|---|---|
ClassificationTask |
Select one or more labels. | Strings. |
AnswerTask |
Answer a question or caption the inputs. | Strings. |
ScalarPredictionTask |
Predict typed quantities. | Integers or floats. |
TemporalLocalizationTask |
Locate events or regions. | Points or intervals. |
ForecastingTask |
Predict future values. | Records, Signals, or intervals. |
TSEditingTask |
Transform input time-series data. | Records or Signals. |
TSGenerationTask |
Generate time-series data from a prompt. | Records or Signals. |
TSCorrespondenceTask |
Match inputs to candidates. | Records or Signals. |
The target collection stays uniform across these classes. Each subclass adds only the configuration that its operation needs.
The set is closed. TimeF stores each Task's type as a tag, and the reader maps that tag back to one
of these classes. Each class is therefore final, and Task refuses subclasses defined outside
TimeNet. To reuse a prompt or other settings, write a builder function that returns a built-in
Task:
from timenet.dataset import Record
from timenet.types import AnswerTask
def age_question(record: Record, age: int) -> AnswerTask:
return AnswerTask(
inputs=(record,),
prompt="How old is the subject?",
targets=(str(age),),
)
dataset.add_task(task=age_question(record, 64))
Classification¶
from timenet.types import Label, LabelSet
CONDITION = LabelSet(
name="condition",
labels=(Label(name="healthy"), Label(name="faulty", description="A bearing fault.")),
)
FINDINGS = LabelSet(
name="findings",
labels=(Label(name="AF"), Label(name="PVC"), Label(name="LBBB")),
multi_label=True,
)
ClassificationTask(inputs=(record,), targets=("healthy",), label_set=CONDITION)
ClassificationTask(inputs=(record,), targets=("PVC", "AF"), label_set=FINDINGS)
A LabelSet defines the allowed labels for a classification target. The multi_label flag controls
how many labels a target can contain.
If multi_label=False, the default, a target must contain exactly one label. If multi_label=True,
a target contains all labels that apply. Omitted labels do not apply. An empty target means that no
label applies. The order of labels does not matter.
When the dataset adds or streams a task, it makes sure that the target follows the rules of its
label set. Each label must be in the set. No label can repeat. A target with multi_label=False
must contain exactly one label.
Labels come from the targets stored directly on the task or from the text of its target annotations.
Tasks can share the same label set object. Each name identifies one label set within a dataset.
If two different sets have the same name, add_task(), the task stream, and the writer raise an
error. TimeF stores each set once. The reader gives tasks that use the same set a shared object.
Answer and rationale¶
AnswerTask(
inputs=(record,),
prompt="Does this trace show a bearing fault?",
rationale="The repeated impacts match a bearing fault.",
targets=("Yes",),
)
A missing prompt makes the Task an unprompted caption or description.
Scalar prediction¶
ScalarPredictionTask(
inputs=(record,),
targets=(62.0,),
unit="bpm",
target_name="mean_heart_rate",
)
The class keeps the quantity name and physical unit separate from the numeric target.
Temporal localization¶
from timenet.types import (
LocalizationMode,
TemporalLocalizationTask,
TimePoint,
)
TemporalLocalizationTask(
inputs=(record,),
prompt="Locate all R-peaks in lead II.",
mode=LocalizationMode.SPARSE,
targets=(
TimePoint.seconds(1.20, time_series=(lead_ii,)),
TimePoint.seconds(2.05, time_series=(lead_ii,)),
),
)
SPARSE says that unmarked time is unspecified. EXHAUSTIVE says that the targets describe the
complete region of interest.
Forecasting, editing, and generation¶
These Tasks can target stored Records or Signals directly:
ForecastingTask(
inputs=(history,),
targets=(future,),
)
TSEditingTask(
inputs=(raw_record,),
prompt="Remove the baseline wander.",
targets=(clean_record,),
)
TSGenerationTask(
prompt="Generate 10 seconds of a 150 bpm ECG.",
targets=(synthetic_record,),
)
A generation Task can have no input Records.
Correspondence¶
TSCorrespondenceTask(
inputs=(query,),
candidate_records=(record_a, record_b, record_c),
targets=(record_b,),
)
An empty candidate_records tuple means that the candidate pool is dataset-wide.
Compose Tasks¶
from_tasks records derivation between Tasks:
label = ClassificationTask(
inputs=(record,),
targets=("faulty",),
label_set=CONDITION,
)
answer = AnswerTask(
inputs=(record,),
prompt="Is this machine healthy?",
targets=("No",),
from_tasks=(label,),
)
dataset.add_tasks(tasks=(label, answer))
TimeNet rejects missing parents, self-references, and derivation cycles.