JuliaKreutzerCohere commited on
Commit
afb76d1
·
verified ·
1 Parent(s): 0809b49

Upload script.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. script.py +204 -0
script.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import pandas as pd
5
+ import torch
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM
7
+
8
+ # The repo is the working directory at run time, and there is no network.
9
+ os.environ["HF_HUB_OFFLINE"] = "1"
10
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
11
+ MODEL_ID = "."
12
+ MAX_NEW_TOKENS = 2000
13
+ TEMPERATURE = 0.8
14
+ TOP_P = 0.95
15
+ MAX_ATTEMPTS = 5
16
+
17
+ SYSTEM = (
18
+ "You solve International Linguistics Olympiad problems by reasoning from the "
19
+ "data in CONTEXT you are given to solve the problems in QUERY. \n"
20
+ "There are common TASK TYPES that we specify below, but "
21
+ "you may meet a TASK TYPE you have never seen: read the "
22
+ "instruction and the examples, and answer the QUERY in the same form they use.\n\n"
23
+ "Common TASK TYPES and what to return: \n"
24
+ "`translation`: return the translated form only, in the language the task asks for; \n"
25
+ "`fill_blanks`: return only the missing form for each indicated blank "
26
+ "(beware: this could be many different things: a word, a part of a word or a phonetic transcription---pay close attention to what part of the CONTEXT is missing in QUERY); \n"
27
+ "`match_letters`: return only the option letter (for example A, B, C); \n"
28
+ "`text_to_num`: return the number in digits; \n"
29
+ "`num_to_text`: return the number written out in words, in the language asked; \n"
30
+ "any other type: return exactly what the instruction asks for, nothing else. \n\n"
31
+ "As the first part of your answer, reason step by step about (1) the linguistic "
32
+ "rules that can be deduced from the given examples in CONTEXT, and (2) "
33
+ "how to apply them to the given problems in QUERY, and (3) in what format answers need to be returned (words, numbers, phonetic transcriptions, ...). \n"
34
+ "Then write a draft of the final answer. "
35
+ "Subsequently, compare it with the format requirements again, "
36
+ "and verify it's compliant with the deduced rules, and it is complete, i.e. has an answer for each element in QUERY. "
37
+ "If necessary, correct and refine."
38
+ "Finally, write a line that says exactly `FINAL ANSWERS:` "
39
+ "and, below it, write the answers to the items requested in QUERY (not those in CONTEXT),"
40
+ "one answer per line (separated by \n) in the order the items are asked for in the QUERY -- the "
41
+ "bare answer only, no numbering, no quotes, no extra text, according to the given TASK TYPE."
42
+ )
43
+
44
+ tok = AutoTokenizer.from_pretrained(MODEL_ID)
45
+ model = AutoModelForCausalLM.from_pretrained(
46
+ MODEL_ID, torch_dtype=torch.float16, device_map="auto"
47
+ ).eval()
48
+
49
+ df = pd.read_csv("/tmp/data/test.csv", dtype=str).fillna("")
50
+
51
+ outputs_queries_types = []
52
+ for _, r in df.iterrows():
53
+
54
+ # Create the prompt.
55
+ messages = [
56
+ {"role": "system", "content": SYSTEM},
57
+ {"role": "user", "content":
58
+ f"CONTEXT:{r['context'].strip()}\nTASK TYPE:`{r['task_type']}`\n\nQUERY:{r['query'].strip()}"},
59
+ ]
60
+ ids = tok.apply_chat_template(
61
+ messages, add_generation_prompt=True, return_tensors="pt",
62
+ ).to(model.device)
63
+
64
+ # Generate the answer.
65
+ done = False
66
+ attempts = 0
67
+ while not done and attempts < MAX_ATTEMPTS:
68
+ with torch.no_grad():
69
+ out = model.generate(
70
+ ids,
71
+ max_new_tokens=MAX_NEW_TOKENS,
72
+ do_sample=True,
73
+ temperature=TEMPERATURE,
74
+ top_p=TOP_P,)
75
+ text = tok.decode(out[0][ids.shape[-1]:], skip_special_tokens=True).strip()
76
+ # IF NO FINAL ANSWER keyword is used, try again.
77
+ attempts += 1
78
+ if "final answer" not in text.lower() or text.lower().split('final answer')[1].split('\n')==0:
79
+ print(f'TRYING AGAIN...attempts #{attempts+1}/{MAX_ATTEMPTS}')
80
+ else:
81
+ done = True
82
+
83
+ outputs_queries_types.append((text, r['id'], r['query'], r['task_type']))
84
+ print(f"{len(outputs_queries_types)}/{len(df)} done", flush=True)
85
+
86
+ # Postprocess and store the answers.
87
+ def expected_answer_count(query: str, task_type: str) -> int:
88
+ if task_type == "match_letters":
89
+ numbered = re.findall(r"^\s*\d+\.", query, re.MULTILINE)
90
+ return len(numbered) or 1
91
+
92
+ if "blanks" in query.lower():
93
+ range_match = re.search(r"\((\d+)-(\d+)\)", query)
94
+ if range_match:
95
+ return int(range_match.group(2)) - int(range_match.group(1)) + 1
96
+ return len(re.findall(r"\(\d+\)", query)) or 1
97
+
98
+ numbered = re.findall(r"^\s*\d+[.)]", query, re.MULTILINE)
99
+ return len(numbered) or 1
100
+
101
+
102
+ def split_single_line_answer(text: str, expected: int, task_type: str) -> list[str]:
103
+ text = text.strip()
104
+ if expected <= 1:
105
+ return [text]
106
+
107
+ def try_split(pattern: str) -> list[str] | None:
108
+ parts = [part.strip() for part in re.split(pattern, text) if part.strip()]
109
+ return parts if len(parts) == expected else None
110
+
111
+ if task_type == "match_letters":
112
+ for pattern in (r"\s+", r",\s*", r";\s*"):
113
+ if result := try_split(pattern):
114
+ return result
115
+ letters = re.findall(r"[A-Za-z]", text)
116
+ if len(letters) == expected:
117
+ return [letter.upper() for letter in letters]
118
+ return [text]
119
+
120
+ if task_type in ("text_to_num", "num_to_text"):
121
+ for pattern in (r",\s*", r";\s*", r"\s+"):
122
+ if result := try_split(pattern):
123
+ return result
124
+ return [text]
125
+
126
+ for pattern in (r";\s*", r",\s*"):
127
+ if result := try_split(pattern):
128
+ return result
129
+ return [text]
130
+
131
+
132
+ def postprocess_answer(text, query, task_type):
133
+ """Keep only the lines after the last 'FINAL ANSWERS:' marker, one answer per line,
134
+ stopping at the first empty line. If eval_type is multiple and only one line as answer, split at whitespace."""
135
+ # Updated regex to be more flexible with surrounding characters
136
+ marker_match = list(re.finditer(r"(?im)^[^\w\n]*final answers?[^\w\n]*:?\s*$", text))
137
+ if marker_match:
138
+ text_after_marker = text[marker_match[-1].end():]
139
+ #print('FOUND FINAL ANSWER', text_after_marker)
140
+ else:
141
+ #print("No 'FINAL ANSWERS:' marker found")
142
+ return []
143
+
144
+ answers = []
145
+ answer_lines = text_after_marker.splitlines()
146
+ for i, line in enumerate(answer_lines):
147
+ stripped_line = line.strip('`').strip()
148
+
149
+ # Stop processing if an empty line is encountered (not as first line)
150
+ if stripped_line=='':
151
+ continue
152
+
153
+ # Use a more precise regex to only remove numbering if it's a prefix to other text
154
+ # This ensures that lines which are just numbers (e.g., '1') are not stripped.
155
+ match_numbered_prefix = re.match(r"^\s*\d+[.)]\s+(.*)", stripped_line)
156
+ if match_numbered_prefix:
157
+ cleaned_line = match_numbered_prefix.group(1).strip()
158
+ else:
159
+ cleaned_line = stripped_line
160
+
161
+ # Remove any bold markdown '**'
162
+ cleaned_line = re.sub(r"\*\*", "", cleaned_line).strip()
163
+
164
+ # Specific handling for 'match_letters' task type to strip extra words
165
+ if task_type == 'match_letters':
166
+ parts = [
167
+ part.strip("().[]")
168
+ for part in re.split(r"[\s,;]+", cleaned_line)
169
+ if part.strip()
170
+ ]
171
+ if not (len(parts) > 1 and all(re.fullmatch(r"[A-Za-z]", part) for part in parts)):
172
+ match_letter_word = re.match(
173
+ r"^\s*(?:\(([A-Za-z])\)|\[([A-Za-z])\]|([A-Za-z]))\.?:?\s*(.*)$",
174
+ cleaned_line,
175
+ )
176
+ if match_letter_word:
177
+ letter = (
178
+ match_letter_word.group(1)
179
+ or match_letter_word.group(2)
180
+ or match_letter_word.group(3)
181
+ )
182
+ cleaned_line = letter.upper()
183
+
184
+ # Append the cleaned, non-empty line
185
+ if cleaned_line:
186
+ answers.append(cleaned_line)
187
+
188
+ #print('PARSED ANSWERS', answers)
189
+
190
+ # Compare against QUERY length: sometimes model forgets newlines
191
+ #print('QUERY', query)
192
+ expected = expected_answer_count(query, task_type)
193
+ query_len = len(query.splitlines()) - 2
194
+ #print(query_len)
195
+ if len(answers) == 1 and expected > 1:
196
+ answers = split_single_line_answer(answers[0], expected, task_type)
197
+ return answers
198
+
199
+ rows = []
200
+ for answer, row_id, query, task_type in outputs_queries_types:
201
+ answers = postprocess_answer(answer, query, task_type)
202
+ rows.append({"id": row_id, "pred": json.dumps(answers, ensure_ascii=False)})
203
+ pd.DataFrame(rows).to_csv("submission.csv", index=False)
204
+ print("wrote submission.csv", flush=True)