samplers.py 7.34 KB
Newer Older
1
from functools import partial
2
3
4
5

import datasets


haileyschoelkopf's avatar
haileyschoelkopf committed
6
class ContextSampler:
Ethan Smith's avatar
Ethan Smith committed
7
    def __init__(self, docs, task, fewshot_indices=None, rnd=None) -> None:
8
        self.rnd = rnd
9
10
11
12
        if not self.rnd:
            raise ValueError(
                "A `random.Random` generator argument must be provided to `rnd` of FewShotSampler!"
            )
13
14
15
16

        self.task = task
        self.config = task._config

Lintang Sutawika's avatar
Lintang Sutawika committed
17
18
        self.target_delimiter = self.config.target_delimiter
        self.fewshot_delimiter = self.config.fewshot_delimiter
19

lintangsutawika's avatar
lintangsutawika committed
20
21
22
23
        if (
            self.config.fewshot_config is not None
            and self.config.fewshot_config.get("doc_to_text", None) is not None
        ):
24
25
            self.doc_to_text = partial(
                self.task.doc_to_text,
lintangsutawika's avatar
lintangsutawika committed
26
27
                doc_to_text=self.config.fewshot_config.get("doc_to_text", None),
            )
28
29
30
        else:
            self.doc_to_text = self.task.doc_to_text

lintangsutawika's avatar
lintangsutawika committed
31
32
33
34
        if (
            self.config.fewshot_config is not None
            and self.config.fewshot_config.get("doc_to_target", None) is not None
        ):
35
36
            self.doc_to_target = partial(
                self.task.doc_to_target,
lintangsutawika's avatar
lintangsutawika committed
37
38
                doc_to_target=self.config.fewshot_config.get("doc_to_target", None),
            )
39
40
41
        else:
            self.doc_to_target = self.task.doc_to_target

lintangsutawika's avatar
lintangsutawika committed
42
43
44
45
        if (
            self.config.fewshot_config is not None
            and self.config.fewshot_config.get("doc_to_choice", None) is not None
        ):
46
47
            self.doc_to_choice = partial(
                self.task.doc_to_choice,
lintangsutawika's avatar
lintangsutawika committed
48
49
                doc_to_choice=self.config.fewshot_config.get("doc_to_choice", None),
            )
50
51
        else:
            self.doc_to_choice = self.task.doc_to_choice
52

lintangsutawika's avatar
lintangsutawika committed
53
54
        self.docs = docs  # HF dataset split, provided by task._fewshot_docs()
        if fewshot_indices:  # subset few-shot docs from
55
56
57
58
            if not isinstance(self.docs, datasets.Dataset):
                raise ValueError(
                    "Got `fewshot_indices` but fewshot_docs are not a HF dataset. Don't use both `fewshot_indices` and a user-defined few-shot sample list simultaneously"
                )
59
60
61
            self.docs = self.docs.select(fewshot_indices)

    def get_context(self, doc, num_fewshot):
lintangsutawika's avatar
lintangsutawika committed
62
63
64
65
66
67
        # draw an extra fewshot sample if using same split as evaluating on
        n_samples = (
            num_fewshot + 1
            if self.config.fewshot_split == self.config.test_split
            else num_fewshot
        )
68

69
        # draw `n_samples` docs from fewshot_docs
70
71
72
        fewshotex = self.sample(n_samples)

        # get rid of the doc that's the one we're evaluating, if it's in the fewshot
73
        # TODO: should we just stop people from using fewshot from same split as evaluating?
74
        selected_docs = [x for x in fewshotex if x != doc][:num_fewshot]
lintangsutawika's avatar
lintangsutawika committed
75

KonradSzafer's avatar
KonradSzafer committed
76
77
78
79
80
81
82
83
        labeled_examples = ""
        for doc in selected_docs:
            doc_content = self.doc_to_text(doc)
            doc_target = self.doc_to_target(doc)
            labeled_examples += (
                doc_content
                if self.config.doc_to_choice is None or isinstance(doc_content, str)
                else self.doc_to_choice(doc)[doc_content]
84
            )
KonradSzafer's avatar
KonradSzafer committed
85
            labeled_examples += self.target_delimiter
lintangsutawika's avatar
merged  
lintangsutawika committed
86
<<<<<<< HEAD
KonradSzafer's avatar
KonradSzafer committed
87
88
89
            labeled_examples += (
                str(doc_target[0])
                if isinstance(doc_target, list)
90
                else str(doc_target)
KonradSzafer's avatar
KonradSzafer committed
91
92
93
94
                if self.config.doc_to_choice is None or isinstance(doc_target, str)
                else str(self.doc_to_choice(doc)[doc_target])
            )
            labeled_examples += self.fewshot_delimiter
lintangsutawika's avatar
merged  
lintangsutawika committed
95
=======
lintangsutawika's avatar
lintangsutawika committed
96
            if doc_target != "":
97
98
99
100
101
102
103
104
                labeled_examples += (
                    str(doc_target[0])
                    if isinstance(doc_target, list)
                    else doc_target
                    if self.config.doc_to_choice is None or isinstance(doc_target, str)
                    else str(self.doc_to_choice(doc)[doc_target])
                )
                labeled_examples += self.fewshot_delimiter
lintangsutawika's avatar
merged  
lintangsutawika committed
105
>>>>>>> mmlu-pro-changes
106
107
108

        return labeled_examples

KonradSzafer's avatar
KonradSzafer committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    def get_chat_context(
        self,
        doc,
        num_fewshot,
        fewshot_as_multiturn: bool = False,
    ):
        chat_history = []
        # draw an extra fewshot sample if using same split as evaluating on
        n_samples = (
            num_fewshot + 1
            if self.config.fewshot_split == self.config.test_split
            else num_fewshot
        )
        # draw `n_samples` docs from fewshot_docs
        fewshotex = self.sample(n_samples)

        # get rid of the doc that's the one we're evaluating, if it's in the fewshot
        # TODO: should we just stop people from using fewshot from same split as evaluating?
        selected_docs = [x for x in fewshotex if x != doc][:num_fewshot]

        if fewshot_as_multiturn:
            for doc in selected_docs:
                doc_content = self.doc_to_text(doc)
                doc_target = self.doc_to_target(doc)
                chat_history.append(
                    {
                        "role": "user",
                        "content": doc_content
                        if self.config.doc_to_choice is None
                        or isinstance(doc_content, str)
                        else self.doc_to_choice(doc)[doc_content],
                    }
                )
                chat_history.append(
                    {
                        "role": "assistant",
                        "content": str(doc_target[0])
                        if isinstance(doc_target, list)
                        else doc_target
                        if self.config.doc_to_choice is None
                        or isinstance(doc_target, str)
                        else str(self.doc_to_choice(doc)[doc_target]),
                    }
                )
        else:
            # get fewshot context as one user turn
            chat_history.append(
                {"role": "user", "content": self.get_context(doc, num_fewshot)}
            )

        return chat_history

161
162
163
164
165
166
167
168
    def sample(self, n):
        """
        Draw `n` samples from our fewshot docs. This method should be overridden by subclasses.
        """

        return self.rnd.sample(self.docs, n)


haileyschoelkopf's avatar
haileyschoelkopf committed
169
170
171
172
173
174
class FirstNSampler(ContextSampler):
    def sample(self, n) -> None:
        """
        Draw the first `n` samples in order from the specified split.
        Used for tasks with "canonical" ordered fewshot examples, such as MMLU and CMMLU.
        """
175
176
        assert (
            n <= len(self.docs)
haileyschoelkopf's avatar
haileyschoelkopf committed
177
178
179
180
181
        ), f"Error: number of fewshot samples requested exceeds the {len(self.docs)} that are available."
        return self.docs[:n]


class BalancedSampler(ContextSampler):
Ethan Smith's avatar
Ethan Smith committed
182
    def sample(self, n) -> None:
183
        """
lintangsutawika's avatar
lintangsutawika committed
184
        TODO: this should return approximately class-balanced samples from our fewshot examples.
185
        TODO: what order should they be in? maybe random?
186
187
188
189
190
        """

        pass


haileyschoelkopf's avatar
haileyschoelkopf committed
191
class ManualSampler(ContextSampler):
Ethan Smith's avatar
Ethan Smith committed
192
    def sample(self, n) -> None:
lintangsutawika's avatar
lintangsutawika committed
193
194
        """ """
        pass
195
196


haileyschoelkopf's avatar
haileyschoelkopf committed
197
198
199
200
201
202
203
204
205
206
207
208
209
SAMPLER_REGISTRY = {
    "default": ContextSampler,
    "first_n": FirstNSampler,
}


def get_sampler(name):
    try:
        return SAMPLER_REGISTRY[name]
    except KeyError:
        raise ValueError(
            f"Attempted to use contextsampler '{name}', but no sampling strategy for this name found! Supported model names: {', '.join(SAMPLER_REGISTRY.keys())}"
        )