-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathself_elicit.py
More file actions
380 lines (311 loc) · 11.5 KB
/
Copy pathself_elicit.py
File metadata and controls
380 lines (311 loc) · 11.5 KB
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
import numpy as np
import torch
from utils import get_sentence_token_spans
def get_answer_base(context, question, agents_dict, args):
"""
Generate a basic answer to the question using a QA agent.
Parameters
----------
context : str
The context passage to use for answering the question.
question : str
The question to answer.
agents_dict : dict
Dictionary containing initialized QA agents.
args : argparse.Namespace
Parsed arguments containing configurations.
Returns
-------
str
The answer generated by the QA agent.
"""
return agents_dict["qa"].get_answer(context, question)
def get_answer_cot(context, question, agents_dict, args):
"""
Generate an answer using a chain-of-thought reasoning agent.
Parameters
----------
context : str
The context passage to use for answering the question.
question : str
The question to answer.
agents_dict : dict
Dictionary containing initialized QA agents.
args : argparse.Namespace
Parsed arguments containing configurations.
Returns
-------
str
The answer generated by the chain-of-thought agent.
"""
return agents_dict["cot"].get_answer(context, question)
def get_answer_fullelicit(context, question, agents_dict, args):
"""
Generate an answer using a QA agent after marking the entire context as important.
Parameters
----------
context : str
The context passage to use for answering the question.
question : str
The question to answer.
agents_dict : dict
Dictionary containing initialized QA agents.
args : argparse.Namespace
Parsed arguments containing configurations, including markers for evidence.
Returns
-------
str
The answer generated by the QA agent.
"""
# Mark the entire context as important by adding evidence markers.
context = f"{args.marker_impstart} {context} {args.marker_impend}"
# Use the "qa" agent to answer the question with the marked context.
return agents_dict["qa"].get_answer(context, question)
def get_answer_promptelicit(
context, question, agents_dict, args, return_evidence=False
):
"""
Generate an answer by eliciting evidence from a prompting-based agent.
Parameters
----------
context : str
The context passage to use for answering the question.
question : str
The question to answer.
agents_dict : dict
Dictionary containing initialized agents, including prompt elicitation and QA agents.
args : argparse.Namespace
Parsed arguments containing configurations, such as markers for evidence and max generation tokens.
return_evidence : bool, optional (default=False)
If True, return the selected evidence sentences along with the answer.
Returns
-------
str or tuple
The answer generated by the QA agent. If `return_evidence` is True, also returns the selected evidence sentences.
"""
# Internal function to extract evidence sentences using the "pe" agent.
def prompt_elicit(
agent_elicit,
context,
question,
marker_impstart,
marker_impend,
max_gen_tokens,
):
"""
Perform prompt-based evidence elicitation.
Parameters
----------
agent_elicit : object
The agent used for prompt-based evidence elicitation.
context : str
The context passage to process.
question : str
The question to answer.
marker_impstart : str
Marker indicating the start of important evidence.
marker_impend : str
Marker indicating the end of important evidence.
max_gen_tokens : int
Maximum number of tokens to generate for evidence extraction.
Returns
-------
elicited_context : str
Context with evidence sentences marked.
evidence_sents : list of str
List of evidence sentences extracted from the context.
"""
# Use the "pe" agent to generate evidence sentences from the context.
model_ans_raw = agent_elicit.get_answer(
context, question, max_ans_tokens=max_gen_tokens
)
elicited_context = f"{context}"
evidence_sents = []
# Parse and identify evidence sentences in the context.
for sent in [
sent.lstrip("- ").lstrip('"').rstrip('"')
for sent in model_ans_raw.split("\n")
]:
if context.find(sent) > -1:
# Locate evidence sentence positions in the context.
sent_start = context.find(sent)
sent_end = sent_start + len(sent)
# Insert evidence markers around the identified sentence.
elicited_context = (
elicited_context[:sent_start]
+ f"{marker_impstart} {sent} {marker_impend}"
+ elicited_context[sent_end:]
)
evidence_sents.append(sent)
return elicited_context, evidence_sents
# Perform evidence elicitation and highlight key sentences.
elicited_context, evidence_sents = prompt_elicit(
agents_dict["pe"],
context,
question,
args.marker_impstart,
args.marker_impend,
args.max_ans_tokens,
)
# Use the "se" agent to generate the final answer based on the highlighted context.
model_ans = agents_dict["se"].get_answer(elicited_context, question)
# Return the answer and optionally the evidence sentences.
if return_evidence:
return model_ans, evidence_sents
else:
return model_ans
def get_answer_selfelicit(
context, question, agents_dict, device, args, return_evidence=False
):
"""
Generate an answer by self-elicit evidence using model attention patterns.
Parameters
----------
context : str
The context passage to use for answering the question.
question : str
The question to answer.
agents_dict : dict
Dictionary containing initialized agents, including self-elicit and QA agents.
device : torch.device
Device on which the model computations are performed.
args : argparse.Namespace
Parsed arguments containing configurations, such as markers, layer spans, and thresholds.
return_evidence : bool, optional (default=False)
If True, return the selected evidence sentences along with the answer.
Returns
-------
str or tuple
The answer generated by the QA agent. If `return_evidence` is True, also returns the selected evidence sentences.
"""
# Nested function for self-elicit logic
def self_elicit(
output_att,
sents,
sent_spans,
context_span,
marker_impstart,
marker_impend,
layer_span,
threshold,
verbose=False,
):
"""
Perform evidence selection using attention scores.
Parameters
----------
output_att : list of torch.Tensor
Attention outputs from the model.
sents : list of str
List of sentences in the context.
sent_spans : list of tuple
Token spans for each sentence.
context_span : tuple
Token span for the entire context.
marker_impstart : str
Marker indicating the start of important evidence.
marker_impend : str
Marker indicating the end of important evidence.
layer_span : tuple of int
Range of layers to consider for evidence selection.
threshold : float
Threshold for selecting evidence sentences.
verbose : bool, optional
If True, print debugging information about the process.
Returns
-------
elicited_context : str
Context with evidence sentences marked.
evidence_sents : list of str
List of evidence sentences.
evidence_spans : list of tuple
Token spans for the evidence sentences.
"""
# Compute attention scores for the specified range of layers.
att_layer_scores = np.array(
[
output_att[l][0, :, -1, context_span[0] : context_span[1]]
.detach()
.cpu()
.float()
.numpy()
.mean(axis=0)
for l in range(layer_span[0], layer_span[1])
]
)
# Normalize the attention scores across layers.
att_layer_scores /= att_layer_scores.sum(axis=1, keepdims=True)
# Aggregate token-level scores into sentence-level scores.
att_token_scores = att_layer_scores.mean(axis=0)
sent_scores = np.array(
[
att_token_scores[sent_span[0] : sent_span[1]].mean()
for sent_span in sent_spans
]
)
# Select sentences with scores exceeding the threshold.
target_sent_index = (sent_scores >= sent_scores.max() * threshold).nonzero()[0]
if verbose:
print(f"Sentences scores: {sent_scores.round(2)}")
print(f"Target sentence index: {target_sent_index}")
elicited_context = ""
sent_end = "\n"
evidence_sents = []
for i, sent in enumerate(sents):
if i in target_sent_index and len(sent.replace(" ", "")) > 5:
# Add evidence markers for selected sentences.
elicited_context += (
f"{marker_impstart} {sent} {marker_impend} {sent_end}"
)
evidence_sents.append(sent)
else:
elicited_context += f"{sent} {sent_end}"
# Collect token spans for selected evidence sentences.
evidence_spans = [sent_spans[i] for i in target_sent_index]
return elicited_context, evidence_sents, evidence_spans
# Prepare input tokens and compute attention scores.
input_ids = (
agents_dict["qa"]
.get_chat_template_input_ids(context, question, return_tensors="pt")
.to(device)
)
context_span = agents_dict["qa"].get_context_token_span(context, question)
context_ids = input_ids[:, context_span[0] : context_span[1]]
# Tokenize the context and identify sentence spans.
sent_spans, sents = get_sentence_token_spans(
context_ids, agents_dict["qa"].tokenizer
)
# Run the model and retrieve attention outputs.
outputs = agents_dict["qa"].model(
input_ids,
output_attentions=True,
attention_mask=torch.ones_like(input_ids),
)
output_att = outputs.attentions
n_layers = len(output_att)
# Define the layer range for evidence selection.
layer_span = (
int(args.layer_span[0] * n_layers),
int(args.layer_span[1] * n_layers),
)
# Perform evidence elicitation using the computed attention patterns.
elicited_context, evidence_sents, evidence_spans = self_elicit(
output_att,
sents,
sent_spans,
context_span,
args.marker_impstart,
args.marker_impend,
layer_span=layer_span,
threshold=args.alpha,
)
# Free GPU memory after computation.
del outputs
torch.cuda.empty_cache()
# Use the "se" agent to generate the final answer based on the elicited context.
model_ans = agents_dict["se"].get_answer(elicited_context, question)
# Return the answer and optionally the evidence sentences.
if return_evidence:
return model_ans, evidence_sents
else:
return model_ans