JuliaKreutzerCohere commited on
Commit
2f17dd7
·
verified ·
1 Parent(s): 326bc8a

Upload script.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. script.py +104 -89
script.py CHANGED
@@ -93,7 +93,7 @@ SYSTEM = (
93
  )
94
 
95
  SYSTEM_POST_EXPLAIN = (
96
- "You are a helpful assistant that explains the reasoning behind the answers to the International Linguistics Olympiad problems.
97
  "You are given the following information:\n"
98
  "- The context of the problem\n"
99
  "- The task type\n"
@@ -101,69 +101,36 @@ SYSTEM_POST_EXPLAIN = (
101
  "- The answer\n"
102
  "- The reasoning\n"
103
  "You need to explain the reasoning behind the answer in a way that is easy to understand and concisely focused on the key insights and rules deduced and applied.\n"
104
- "Do not include any other text, do not includethe answer in the explanation, "
105
- " and do not invent any new information."
106
  )
107
- tok = load_tokenizer(MODEL_ID)
108
- model = AutoModelForCausalLM.from_pretrained(
109
- MODEL_ID, torch_dtype=torch.float16, device_map="auto"
110
- ).eval()
111
 
112
- with open("/tmp/data/test.csv", encoding="utf-8", newline="") as f:
113
- test_rows = list(csv.DictReader(f))
 
 
 
 
 
 
 
 
 
114
 
115
- outputs_queries_types = []
116
- for r in test_rows:
117
 
118
- # Create the prompt.
119
- messages = [
120
- {"role": "system", "content": SYSTEM},
121
- {"role": "user", "content":
122
- f"CONTEXT:{r['context'].strip()}\nTASK TYPE:`{r['task_type']}`\n\nQUERY:{r['query'].strip()}"},
123
- ]
124
- ids = tok.apply_chat_template(
125
- messages, add_generation_prompt=True, return_tensors="pt",
126
- ).to(model.device)
127
 
128
- # Generate the answer.
129
- done = False
130
- attempts = 0
131
- while not done and attempts < MAX_ATTEMPTS:
132
- with torch.no_grad():
133
- out = model.generate(
134
- ids,
135
- max_new_tokens=MAX_NEW_TOKENS,
136
- do_sample=True,
137
- temperature=TEMPERATURE,
138
- top_p=TOP_P,)
139
- text = tok.decode(out[0][ids.shape[-1]:], skip_special_tokens=True).strip()
140
- # IF NO FINAL ANSWER keyword is used, try again.
141
- attempts += 1
142
- if "final answer" not in text.lower() or text.lower().split('final answer')[1].split('\n')==0:
143
- print(f'TRYING AGAIN...attempts #{attempts+1}/{MAX_ATTEMPTS}')
144
- else:
145
- done = True
146
 
147
- # Generate the explanation.
148
- messages_post_explain = [
149
- {"role": "system", "content": SYSTEM_POST_EXPLAIN},
150
- {"role": "user", "content":
151
- f"CONTEXT:{r['context'].strip()}\nTASK TYPE:`{r['task_type']}`\n\nQUERY:{r['query'].strip()}\n\nANSWER:{text.strip()}"},
152
- ]
153
- ids_post_explain = tok.apply_chat_template(
154
- messages_post_explain, add_generation_prompt=True, return_tensors="pt",
155
- ).to(model.device)
156
- with torch.no_grad():
157
- out_post_explain = model.generate(
158
- ids_post_explain,
159
- max_new_tokens=MAX_NEW_TOKENS,
160
- do_sample=False)
161
- text_post_explain = tok.decode(out_post_explain[0][ids_post_explain.shape[-1]:], skip_special_tokens=True).strip()
162
 
163
- outputs_queries_types.append((text, text_post_explain, r['id'], r['query'], r['task_type']))
164
- print(f"{len(outputs_queries_types)}/{len(test_rows)} done", flush=True)
165
 
166
- # Postprocess and store the answers.
167
  def expected_answer_count(query: str, task_type: str) -> int:
168
  if task_type == "match_letters":
169
  numbered = re.findall(r"^\s*\d+\.", query, re.MULTILINE)
@@ -209,46 +176,32 @@ def split_single_line_answer(text: str, expected: int, task_type: str) -> list[s
209
  return [text]
210
 
211
 
212
- def postprocess_answer(text, query, task_type):
213
- """Keep only the lines after the last 'FINAL ANSWERS:' marker, one answer per line,
214
- stopping at the first empty line. If eval_type is multiple and only one line as answer, split at whitespace."""
215
- # Updated regex to be more flexible with surrounding characters
216
- marker_match = list(re.finditer(r"(?im)^[^\w\n]*final answers?[^\w\n]*:?\s*$", text))
217
- if marker_match:
218
- text_after_marker = text[marker_match[-1].end():]
219
- #print('FOUND FINAL ANSWER', text_after_marker)
220
- else:
221
- #print("No 'FINAL ANSWERS:' marker found")
222
- return []
223
-
224
  answers = []
225
- answer_lines = text_after_marker.splitlines()
226
- for i, line in enumerate(answer_lines):
227
- stripped_line = line.strip('`').strip()
228
-
229
- # Stop processing if an empty line is encountered (not as first line)
230
- if stripped_line=='':
231
- continue
232
 
233
- # Use a more precise regex to only remove numbering if it's a prefix to other text
234
- # This ensures that lines which are just numbers (e.g., '1') are not stripped.
235
  match_numbered_prefix = re.match(r"^\s*\d+[.)]\s+(.*)", stripped_line)
236
  if match_numbered_prefix:
237
  cleaned_line = match_numbered_prefix.group(1).strip()
238
  else:
239
  cleaned_line = stripped_line
240
 
241
- # Remove any bold markdown '**'
242
  cleaned_line = re.sub(r"\*\*", "", cleaned_line).strip()
243
 
244
- # Specific handling for 'match_letters' task type to strip extra words
245
- if task_type == 'match_letters':
246
  parts = [
247
  part.strip("().[]")
248
  for part in re.split(r"[\s,;]+", cleaned_line)
249
  if part.strip()
250
  ]
251
- if not (len(parts) > 1 and all(re.fullmatch(r"[A-Za-z]", part) for part in parts)):
 
 
 
252
  match_letter_word = re.match(
253
  r"^\s*(?:\(([A-Za-z])\)|\[([A-Za-z])\]|([A-Za-z]))\.?:?\s*(.*)$",
254
  cleaned_line,
@@ -261,21 +214,83 @@ def postprocess_answer(text, query, task_type):
261
  )
262
  cleaned_line = letter.upper()
263
 
264
- # Append the cleaned, non-empty line
265
  if cleaned_line:
266
  answers.append(cleaned_line)
267
 
268
- #print('PARSED ANSWERS', answers)
269
-
270
- # Compare against QUERY length: sometimes model forgets newlines
271
- #print('QUERY', query)
272
  expected = expected_answer_count(query, task_type)
273
- query_len = len(query.splitlines()) - 2
274
- #print(query_len)
275
  if len(answers) == 1 and expected > 1:
276
  answers = split_single_line_answer(answers[0], expected, task_type)
277
  return answers
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  rows = []
280
  for answer, explanation, row_id, query, task_type in outputs_queries_types:
281
  answers = postprocess_answer(answer, query, task_type)
@@ -284,4 +299,4 @@ with open("submission.csv", "w", encoding="utf-8", newline="") as f:
284
  writer = csv.DictWriter(f, fieldnames=["id", "pred", "explanation"])
285
  writer.writeheader()
286
  writer.writerows(rows)
287
- print("wrote submission.csv", flush=True)
 
93
  )
94
 
95
  SYSTEM_POST_EXPLAIN = (
96
+ "You are a helpful assistant that explains the reasoning behind the answers to the International Linguistics Olympiad problems.\n"
97
  "You are given the following information:\n"
98
  "- The context of the problem\n"
99
  "- The task type\n"
 
101
  "- The answer\n"
102
  "- The reasoning\n"
103
  "You need to explain the reasoning behind the answer in a way that is easy to understand and concisely focused on the key insights and rules deduced and applied.\n"
104
+ "Do not include any other text, do not include the answer in the explanation, "
105
+ "and do not invent any new information."
106
  )
 
 
 
 
107
 
108
+ # Prefer a dedicated header line; also allow same-line answers after the colon.
109
+ # Fall back to the last "final answer(s):" substring if no clean header line exists.
110
+ FINAL_ANSWERS_LINE_RE = re.compile(
111
+ # Header alone on the line (markdown / punctuation allowed), OR
112
+ # header with first answer on the same line after a colon.
113
+ r"(?im)^[^\w\n]*final answers?[^\w\n]*:?[ \t]*(?=\n|$)|"
114
+ r"(?im)^[^\w\n]*final answers?\s*:\s*"
115
+ )
116
+ FINAL_ANSWERS_INLINE_RE = re.compile(
117
+ r"(?is)\bfinal answers?\s*:\s*"
118
+ )
119
 
 
 
120
 
121
+ def extract_raw_final(text: str) -> str:
122
+ """Return text after the last final-answers marker, or '' if none found."""
123
+ line_matches = list(FINAL_ANSWERS_LINE_RE.finditer(text))
124
+ if line_matches:
125
+ return text[line_matches[-1].end() :]
 
 
 
 
126
 
127
+ inline_matches = list(FINAL_ANSWERS_INLINE_RE.finditer(text))
128
+ if inline_matches:
129
+ return text[inline_matches[-1].end() :]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
 
 
133
 
 
134
  def expected_answer_count(query: str, task_type: str) -> int:
135
  if task_type == "match_letters":
136
  numbered = re.findall(r"^\s*\d+\.", query, re.MULTILINE)
 
176
  return [text]
177
 
178
 
179
+ def parse_answer_lines(text_after_marker: str, query: str, task_type: str) -> list[str]:
180
+ """Parse cleaned answer lines from the raw final-answers section."""
 
 
 
 
 
 
 
 
 
 
181
  answers = []
182
+ for line in text_after_marker.splitlines():
183
+ stripped_line = line.strip("`").strip()
184
+ if stripped_line == "":
185
+ continue
 
 
 
186
 
 
 
187
  match_numbered_prefix = re.match(r"^\s*\d+[.)]\s+(.*)", stripped_line)
188
  if match_numbered_prefix:
189
  cleaned_line = match_numbered_prefix.group(1).strip()
190
  else:
191
  cleaned_line = stripped_line
192
 
 
193
  cleaned_line = re.sub(r"\*\*", "", cleaned_line).strip()
194
 
195
+ if task_type == "match_letters":
 
196
  parts = [
197
  part.strip("().[]")
198
  for part in re.split(r"[\s,;]+", cleaned_line)
199
  if part.strip()
200
  ]
201
+ if not (
202
+ len(parts) > 1
203
+ and all(re.fullmatch(r"[A-Za-z]", part) for part in parts)
204
+ ):
205
  match_letter_word = re.match(
206
  r"^\s*(?:\(([A-Za-z])\)|\[([A-Za-z])\]|([A-Za-z]))\.?:?\s*(.*)$",
207
  cleaned_line,
 
214
  )
215
  cleaned_line = letter.upper()
216
 
 
217
  if cleaned_line:
218
  answers.append(cleaned_line)
219
 
 
 
 
 
220
  expected = expected_answer_count(query, task_type)
 
 
221
  if len(answers) == 1 and expected > 1:
222
  answers = split_single_line_answer(answers[0], expected, task_type)
223
  return answers
224
 
225
+
226
+ def postprocess_answer(text, query, task_type):
227
+ """Keep only the content after the last 'FINAL ANSWERS' marker, one answer per line."""
228
+ text_after_marker = extract_raw_final(text)
229
+ if not text_after_marker.strip():
230
+ return []
231
+ return parse_answer_lines(text_after_marker, query, task_type)
232
+
233
+
234
+ tok = load_tokenizer(MODEL_ID)
235
+ model = AutoModelForCausalLM.from_pretrained(
236
+ MODEL_ID, torch_dtype=torch.float16, device_map="auto"
237
+ ).eval()
238
+
239
+ with open("/tmp/data/test.csv", encoding="utf-8", newline="") as f:
240
+ test_rows = list(csv.DictReader(f))
241
+
242
+ outputs_queries_types = []
243
+ for r in test_rows:
244
+
245
+ # Create the prompt.
246
+ messages = [
247
+ {"role": "system", "content": SYSTEM},
248
+ {"role": "user", "content":
249
+ f"CONTEXT:{r['context'].strip()}\nTASK TYPE:`{r['task_type']}`\n\nQUERY:{r['query'].strip()}"},
250
+ ]
251
+ ids = tok.apply_chat_template(
252
+ messages, add_generation_prompt=True, return_tensors="pt",
253
+ ).to(model.device)
254
+
255
+ # Generate the answer.
256
+ done = False
257
+ attempts = 0
258
+ text = ""
259
+ while not done and attempts < MAX_ATTEMPTS:
260
+ with torch.no_grad():
261
+ out = model.generate(
262
+ ids,
263
+ max_new_tokens=MAX_NEW_TOKENS,
264
+ do_sample=True,
265
+ temperature=TEMPERATURE,
266
+ top_p=TOP_P,)
267
+ text = tok.decode(out[0][ids.shape[-1]:], skip_special_tokens=True).strip()
268
+ # Retry until we can extract a non-empty final-answers section.
269
+ attempts += 1
270
+ if extract_raw_final(text).strip():
271
+ done = True
272
+ else:
273
+ print(f'TRYING AGAIN...attempts #{attempts}/{MAX_ATTEMPTS}')
274
+
275
+ # Generate the explanation.
276
+ messages_post_explain = [
277
+ {"role": "system", "content": SYSTEM_POST_EXPLAIN},
278
+ {"role": "user", "content":
279
+ f"CONTEXT:{r['context'].strip()}\nTASK TYPE:`{r['task_type']}`\n\nQUERY:{r['query'].strip()}\n\nANSWER:{text.strip()}"},
280
+ ]
281
+ ids_post_explain = tok.apply_chat_template(
282
+ messages_post_explain, add_generation_prompt=True, return_tensors="pt",
283
+ ).to(model.device)
284
+ with torch.no_grad():
285
+ out_post_explain = model.generate(
286
+ ids_post_explain,
287
+ max_new_tokens=MAX_NEW_TOKENS,
288
+ do_sample=False)
289
+ text_post_explain = tok.decode(out_post_explain[0][ids_post_explain.shape[-1]:], skip_special_tokens=True).strip()
290
+
291
+ outputs_queries_types.append((text, text_post_explain, r['id'], r['query'], r['task_type']))
292
+ print(f"{len(outputs_queries_types)}/{len(test_rows)} done", flush=True)
293
+
294
  rows = []
295
  for answer, explanation, row_id, query, task_type in outputs_queries_types:
296
  answers = postprocess_answer(answer, query, task_type)
 
299
  writer = csv.DictWriter(f, fieldnames=["id", "pred", "explanation"])
300
  writer.writeheader()
301
  writer.writerows(rows)
302
+ print("wrote submission.csv", flush=True)