File size: 2,250 Bytes
143710c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
"""PyTorch Dataset for the caption model.

Each item = one (image, caption) pair, NOT one image. Since each image has 5
captions, an image appears up to 5 times across items in a split -- this is
correct and expected (more training signal per image, standard practice).
"""
from __future__ import annotations

from pathlib import Path

import pandas as pd
import torch
from torch.utils.data import Dataset

from src.data.preprocessing import add_special_tokens, clean_caption
from src.data.vocabulary import Vocabulary


class CaptionDataset(Dataset):
    def __init__(
        self,
        csv_path: str | Path,
        features_path: str | Path,
        vocab: Vocabulary,
        max_len: int = 35,
    ):
        self.df = pd.read_csv(csv_path)
        self.features: dict[str, torch.Tensor] = torch.load(features_path, weights_only=True)
        self.vocab = vocab
        self.max_len = max_len

        missing = set(self.df["image"].unique()) - set(self.features.keys())
        if missing:
            raise ValueError(
                f"{len(missing)} images in {csv_path} have no cached feature "
                f"(run scripts/extract_features.py first). Example: {next(iter(missing))}"
            )

    def __len__(self) -> int:
        return len(self.df)

    def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
        row = self.df.iloc[idx]
        image_filename = row["image"]
        raw_caption = row["caption"]

        image_feature = self.features[image_filename]  # (feature_dim,)

        tokens = add_special_tokens(clean_caption(raw_caption))
        ids = self.vocab.numericalize(tokens)

        # truncate (rare) or pad to fixed length
        ids = ids[: self.max_len]
        pad_len = self.max_len - len(ids)
        ids = ids + [self.vocab.pad_idx] * pad_len

        full_seq = torch.tensor(ids, dtype=torch.long)  # (max_len,)
        input_seq = full_seq[:-1]  # everything except last token
        target_seq = full_seq[1:]  # everything except first token (<start>)

        return {
            "image_feature": image_feature,
            "input_seq": input_seq,
            "target_seq": target_seq,
            "image_filename": image_filename,
            "raw_caption": raw_caption,
        }