TREA_2.0_codebase / multihop_tasks_walkthrough.md
malay-36's picture
Upload updated pipeline codebase
7e6c03a verified
|
Raw
History Blame Contribute Delete
16.4 kB
# Multihop Temporal Reasoning Tasks β€” Detailed Walkthrough
## Architecture Overview
All 8 new task generators follow a shared architecture built on `MultihopBaseGenerator`, which provides:
- **Scene building**: Constructing multi-event audio sequences with controlled order, durations, volumes, and silences
- **Dataset generation**: Sample-loop orchestration with balanced question type distribution
- **CSV output**: Standardized MCQ, open-text, and metadata CSV writing
```mermaid
graph TD
A[MultihopBaseGenerator] --> B[ConditionalCountTaskGenerator]
A --> C[ConditionalDurationTaskGenerator]
A --> D[BetweenEventsTaskGenerator]
A --> E[EventDensityTaskGenerator]
A --> F[DurationGapTaskGenerator]
A --> G[TemporalArithmeticTaskGenerator]
A --> H[TemporalLoudnessTaskGenerator]
A --> I[MultiHopTaskGenerator]
J[ESC50Dataset] --> A
K[PreprocessedESC50Dataset] --> C
K --> F
K --> G
K --> I
L[AudioProcessor] --> A
M[QuestionGenerator] --> A
```
### Key Design Decision: Why a Shared Base?
The existing 7 tasks (count, duration, order, etc.) are standalone classes with no shared base. This worked because each task has fundamentally different audio construction logic (e.g., overlap uses `overlay()`, during_contains uses temporal containment, silence_gap uses controlled gaps).
The **multihop tasks are different** β€” they all build the same kind of audio scene (sequential events with gaps and volume variation) but ask different questions over that scene. The complexity is in the **question generation and answer computation**, not the audio construction. So a shared base class makes sense here.
---
## Base Class: `MultihopBaseGenerator`
**File:** [multihop_base.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/multihop_base.py)
### What it provides
| Method | Purpose |
|--------|---------|
| `__init__` | Loads config, initializes dataset/processor/question generator, creates output dirs |
| `build_sequential_scene()` | Builds an audio scene with N events, controlled gaps, optional per-event volume |
| `generate_dataset()` | Orchestrates sample generation loop with balanced question types |
| `_save_mcq_csv()` | Writes MCQ CSV in standard pipeline format |
| `_save_open_text_csv()` | Writes open-text CSV in standard pipeline format |
| `_save_metadata_csv()` | Writes metadata CSV |
### Scene Building Logic
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” gap_0 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” gap_1 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” gap_2 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Event 0 │◄──────►│ Event 1 │◄──────►│ Event 2 │◄──────►│ Event 3 β”‚
β”‚ cat_A β”‚ β”‚ cat_B β”‚ β”‚ cat_A β”‚ β”‚ cat_C β”‚
β”‚ vol: 0dBβ”‚ β”‚ vol:-6dB β”‚ β”‚ vol:+3dB β”‚ β”‚ vol:-2dB β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
1. Sample `n_events` unique categories from ESC-50
2. Load and extend each source clip to fill its time slot via `concatenate_to_target_duration()`
3. Apply per-event volume adjustments (if specified)
4. Generate controlled gap durations using `generate_controlled_gap_durations()` (ensures the longest gap is β‰₯ `gap_multiplier Γ— shortest gap`)
5. Concatenate: `event + silence + event + silence + ...`
6. Return per-event metadata (start_ms, end_ms, duration, volume, category)
---
## Task 1: `ConditionalCountTaskGenerator`
**File:** [task_conditional_count.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_conditional_count.py)
**Family:** Multihop Singular
**Reasoning chain:** Temporal condition β†’ Count
### What it does
Counts sound events **after applying a temporal filter**. Unlike the simple COUNT task which asks "how many unique sounds?", this task asks "how many X sounds occur **after** Y?" β€” requiring the model to first identify when Y occurs, then count X events only in the filtered region.
### Audio Construction
Builds a scene with **4–8 events**, intentionally allowing **repeated categories** (unlike between_events which uses all unique). This is critical because counting repeated occurrences is the core question.
```python
# Example scene: categories = [dog, cat, dog, bird, cat, dog, bird]
# count_after(target=dog, anchor=cat[0]) β†’ 3 (indices 2, 5 after first cat)
# count_before(target=bird, anchor=dog[2]) β†’ 1 (index 3 before third dog)
```
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `count_after` | Pick anchor index β†’ count `target_sound` in `categories[anchor+1:]` |
| `count_before` | Pick anchor index β†’ count `target_sound` in `categories[:anchor]` |
| `count_between` | Pick two anchor indices β†’ count events in `categories[idx1+1:idx2]` |
| `count_during` | Pick anchor β†’ count all other `target_sound` occurrences |
| `count_overlap` | Pick anchor β†’ count adjacent events (neighbors at Β±1 index) |
### MCQ Generation
Uses `QuestionGenerator.generate_count_mcq()` β€” generates 4 integer options including the correct count, with distractors sampled from nearby integers.
---
## Task 2: `ConditionalDurationTaskGenerator`
**File:** [task_conditional_duration.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_conditional_duration.py)
**Family:** Multihop Singular
**Reasoning chain:** Temporal condition β†’ Duration comparison
### What it does
Compares durations **only among events in a temporal subset**. "Which sound **after** Y lasts the longest?" requires: (1) identify Y's position, (2) consider only sounds after Y, (3) compare their durations.
### Audio Construction
Uses **`PreprocessedESC50Dataset`** (not plain ESC50Dataset) to get **effective durations** β€” the actual content duration after silence trimming. This ensures duration comparisons are meaningful (a 5s clip with 3s of trailing silence has effective duration 2s).
```python
# Each event loaded with known effective duration:
fname, fpath, eff_dur = self.dataset.sample_file_from_category_with_duration(cat)
```
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `longest_after` | Filter events after anchor β†’ `max(effective_durations)` |
| `shortest_after` | Filter events after anchor β†’ `min(effective_durations)` |
| `longest_before` | Filter events before anchor β†’ `max(effective_durations)` |
| `shortest_before` | Filter events before anchor β†’ `min(effective_durations)` |
| `repeated_compare` | Find category appearing both before AND after anchor β†’ compare their durations |
### MCQ Generation
Uses `QuestionGenerator.generate_category_mcq()` β€” the answer is a sound category name, with distractors drawn from other categories present in the audio.
---
## Task 3: `BetweenEventsTaskGenerator`
**File:** [task_between_events.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_between_events.py)
**Family:** Multihop Singular
**Reasoning chain:** Define temporal window β†’ Identify/Count events inside
### What it does
Identifies or counts sounds occurring **inside the temporal window between two anchor events**. "Which sound occurs between X and Y?" requires: (1) find X, (2) find Y, (3) identify events strictly between them.
### Audio Construction
Uses **all unique categories** (no repeats) so that "which sound between X and Y?" has an unambiguous answer. Scene has **5–8 events**.
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `identify_between` | Pick two anchors `idx1 < idx2` β†’ randomly select one event from `categories[idx1+1:idx2]` as answer |
| `count_between` | Pick two anchors β†’ `len(categories[idx1+1:idx2])` |
| `yes_no_between` | Pick target β†’ check if target is in the between-window (50/50 true/false balance) |
### Key Constraint
Anchors are chosen with **at least 2 positions apart** (`idx2 β‰₯ idx1 + 2`) to guarantee at least 1 event between them. Samples with fewer than 3 events are rejected.
---
## Task 4: `EventDensityTaskGenerator`
**File:** [task_event_density.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_event_density.py)
**Family:** Multihop Singular
**Reasoning chain:** Segment audio into regions β†’ Compare event counts
### What it does
Compares **event frequency across temporal regions**. "Which half of the audio contains more sound events?" requires counting events in each half and comparing.
### Audio Construction
Builds scenes with **6–10 events** (larger scenes to make density differences meaningful). **Allows repeated categories** so label-specific density questions are possible. Categories are shuffled randomly β€” the natural random distribution creates asymmetry.
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `half_density` | Split at midpoint β†’ compare `len(first_half)` vs `len(second_half)` β†’ answer is "first half" / "second half" / "equal" |
| `before_after_density` | Pick anchor β†’ compare count of events before vs after anchor |
| `label_density` | Pick anchor + target label β†’ compare count of `target_sound` before vs after anchor |
### MCQ Generation
Custom option construction β€” options are textual ("first half", "second half", "equal", "cannot determine") rather than category names.
---
## Task 5: `DurationGapTaskGenerator`
**File:** [task_duration_gap.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_duration_gap.py)
**Family:** Multihop Inter-Task
**Reasoning chain:** Duration reasoning ↔ Silence-gap reasoning
### What it does
**Crosses two task families** β€” compares sound event duration against silence gap duration. "Which is longer: the dog sound or the silence after the dog sound?" requires perceiving both the event's length and the gap's length, then comparing.
### Audio Construction
Uses `PreprocessedESC50Dataset` for effective durations. **Wider gap range** (500–4000ms) compared to other tasks, ensuring gaps are sometimes longer and sometimes shorter than events β€” making the comparison non-trivial.
Each event's metadata tracks both `effective_duration_ms` and `gap_before_ms` / `gap_after_ms`.
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `event_vs_after_gap` | Pick event (not last) β†’ compare `effective_duration_ms` vs `gap_after_ms` |
| `event_vs_before_gap` | Pick event (not first) β†’ compare `effective_duration_ms` vs `gap_before_ms` |
| `gap_vs_event` | Pick a gap between events A,B β†’ pick third event C β†’ compare `gap_duration` vs `C.effective_duration_ms` |
---
## Task 6: `TemporalArithmeticTaskGenerator`
**File:** [task_temporal_arithmetic.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_temporal_arithmetic.py)
**Family:** Multihop Inter-Task
**Reasoning chain:** Duration + Count/Repetition + Silence reasoning
### What it does
Compares **aggregated** durations β€” total active time of one label vs another, or total sound time vs total silence time. Requires mentally summing up all occurrences of a repeated sound.
### Audio Construction
Uses `PreprocessedESC50Dataset`. **Allows repeated categories** (critical for aggregation). Computes two aggregate quantities:
- **`label_totals`**: Total effective duration per category (summed across all occurrences)
- **`total_silence_ms`**: Sum of all gap durations
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `total_label_duration` | Pick two categories β†’ compare `label_totals[sound1]` vs `label_totals[sound2]` |
| `label_vs_silence` | Pick one category β†’ compare `label_totals[target]` vs `total_silence_ms` |
| `combined_duration` | Pick one category β†’ compare `label_totals[target]` vs `max(gap_durations)` (longest single silence) |
---
## Task 7: `TemporalLoudnessTaskGenerator`
**File:** [task_temporal_loudness.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_temporal_loudness.py)
**Family:** Multihop Inter-Task
**Reasoning chain:** Temporal filter β†’ Loudness comparison
### What it does
Combines **temporal filtering** with **loudness comparison**. "Which sound after Y is the loudest?" requires: (1) identify Y's position, (2) consider only sounds after Y, (3) compare their loudness levels.
### Audio Construction
Each event gets a **random volume adjustment** from `volume_range_db` (default: [-12, +6] dB). A minimum volume difference (`min_volume_diff_db`: 3dB) is enforced to ensure the loudest/softest distinction is perceivable.
Actual loudness is measured using **LUFS** (`get_lufs_loudness()`) for perceptually accurate loudness comparison.
### Question Types & Answer Computation
| Type | Logic |
|------|-------|
| `loudest_after` | Filter events after anchor β†’ `max(loudness_lufs)` |
| `softest_after` | Filter events after anchor β†’ `min(loudness_lufs)` |
| `loudest_before` | Filter events before anchor β†’ `max(loudness_lufs)` |
| `softest_before` | Filter events before anchor β†’ `min(loudness_lufs)` |
| `repeated_loudness` | Find category with multiple occurrences β†’ which occurrence is loudest? Also detects trend (louder/softer over time) |
---
## Task 8: `MultiHopTaskGenerator`
**File:** [task_multi_hop.py](file:///home/debarpanb1/TREA_2.0/pipeline/tasks/task_multi_hop.py)
**Family:** Multihop Inter-Task
**Reasoning chain:** Explicit 2–3 step chains across different properties
### What it does
The **most complex task** β€” requires explicit multi-step reasoning across different temporal/acoustic properties. "What sound occurs after the longest sound?" requires: (1) identify the longest sound (duration property), (2) find what comes after it (order property).
### Audio Construction
The **richest scene** β€” combines ALL controllable dimensions:
- **Duration variation** via PreprocessedESC50Dataset effective durations
- **Volume variation** via per-event dB adjustments ([-10, +6] dB range)
- **Silence variation** via controlled gaps (500–3000ms range, 2.5Γ— multiplier)
- **Category repetition** for count-based sub-questions
### Question Types & Reasoning Chains
| Type | Step 1 | Step 2 | Step 3 |
|------|--------|--------|--------|
| `after_longest` | Find longest duration event | Return next event in sequence | β€” |
| `before_longest` | Find longest duration event | Return previous event | β€” |
| `after_shortest` | Find shortest duration event | Return next event | β€” |
| `before_loudest` | Find loudest event (LUFS) | Return previous event | β€” |
| `after_longest_gap` | Find longest silence gap | Return event after that gap | (or count remaining events) |
| `loudest_after_anchor` | Find anchor position | Filter events after anchor | Find loudest among them |
| `longest_before_anchor` | Find anchor position | Filter events before anchor | Find longest among them |
| `count_before_loudest` | Find loudest event | Filter events before it | Count target_sound occurrences |
| `count_after_longest` | Find longest event | Filter events after it | Count target_sound occurrences |
### Answer Computation Example
```python
# after_longest: "What sound occurs after the longest sound?"
pivot_idx = durations_ms.index(max(durations_ms)) # Step 1: find longest
target_idx = pivot_idx + 1 # Step 2: get next event
correct = categories[target_idx] # Answer: that category
```
---
## Summary: What Makes Each Task Unique
| Task | Audio Uniqueness | Question Uniqueness |
|------|-----------------|-------------------|
| **conditional_count** | Repeated categories | Temporal filter β†’ integer count |
| **conditional_duration** | PreprocessedESC50 effective durations | Temporal filter β†’ duration superlative |
| **between_events** | All unique categories, β‰₯3 events | Temporal window identification |
| **event_density** | 6–10 events, asymmetric distribution | Region-based count comparison |
| **duration_gap** | Wide gap range (500–4000ms) | Cross-property: event-dur vs silence-dur |
| **temporal_arithmetic** | Repeated categories + tracked totals | Aggregated duration arithmetic |
| **temporal_loudness** | Per-event volume variation + LUFS measurement | Temporal filter β†’ loudness superlative |
| **multi_hop** | All dimensions varied simultaneously | 2–3 step reasoning chains |