Yyk040316/long-context-icl
0
1import logging2import os3from typing import List, Tuple4 5import numpy as np6import pandas as pd7from matplotlib import pyplot as plt8from numpy import typing as npt9from torch import distributed as dist10from transformers import PreTrainedTokenizerBase, LlamaTokenizer, LlamaTokenizerFast11from retriv import SparseRetriever12import re13 14import random15 16from constants import TEXT_BETWEEN_SHOTS17 18_logger = logging.getLogger(__name__)19logging.basicConfig(level=logging.INFO, format='%(message)s')20 21 22def get_max_n_shots(train_df: pd.DataFrame, test_df: pd.DataFrame, tokenizer: PreTrainedTokenizerBase,23 prompt_size: int) -> int:24 # this is nice info-- let's log this even if we don't need to use it 25 longest_test_prompt = test_df[N_TOKENS].max()26 _logger.info(f"longest_test_prompt = {longest_test_prompt}")27 28 n_tokens_between_shots = n_tokens_in_prompt(tokenizer, TEXT_BETWEEN_SHOTS)29 shot_lengths = train_df[N_TOKENS] + n_tokens_between_shots30 prompt_length_percentile = shot_lengths.quantile(0.9)31 print(f"Median length of demonstration: {shot_lengths.quantile(0.5)}")32 print(f"Mean length of demonstration: {sum(shot_lengths)/len(shot_lengths)}")33 34 max_possible_shots_length = prompt_size - longest_test_prompt35 return int(np.floor(max_possible_shots_length / prompt_length_percentile))36 37 38 39 40def synchronize_examples_across_dfs(df1: pd.DataFrame, df2: pd.DataFrame, comp_column: str = "text"):41 df1 = df1.loc[df1[comp_column].isin(df2[comp_column])]42 df2 = df2.loc[df2[comp_column].isin(df1[comp_column])]43 return df1, df244 45def filter_extremely_long_samples(df: pd.DataFrame, tokenizer: PreTrainedTokenizerBase) -> pd.DataFrame:46 df[N_TOKENS] = df[PROMPTS].map(lambda x: n_tokens_in_prompt(tokenizer, x))47 mask = df[N_TOKENS] <= df[N_TOKENS].quantile(0.99)48 _logger.info(f"filtered {sum(~mask)} from dataset due to extreme length")49 df = df.loc[mask].copy()50 _logger.info(f"longest remaining prompt according to tokenizer: {df[N_TOKENS].max()}")51 return df52 53 54def n_tokens_in_prompt(tokenizer: PreTrainedTokenizerBase, prompt: str, add_special_tokens=False) -> int:55 return len(tokenizer.encode(prompt, add_special_tokens=add_special_tokens))56 57 58def plot_results_graph(results, dataset_name, n_shots, model='') -> None:59 plt.figure()60 plt.errorbar(n_shots, np.mean(results, axis=1), np.std(results, axis=1), fmt='*')61 plt.xlabel("# shots")62 plt.xticks(n_shots)63 metric = 'Accuracy'64 plt.ylabel(f"{dataset_name} {metric}")65 plt.title(f"{metric} {dataset_name} {model}")66 67 68def load_results(dataset_name: str, output_dir: str, plot=False) -> Tuple[npt.NDArray[float], List[int]]:69 all_results = os.listdir(output_dir)70 results_path = [r for r in all_results if r.startswith(f'{dataset_name}_')]71 if len(results_path) != 1:72 raise ValueError(f"Found {len(results_path)} results!")73 results_path = results_path[0]74 results = np.load(os.path.join(output_dir, results_path))75 n_shots = [int(d) for d in results_path.split('.')[-2].split('_') if d.isdigit()]76 if plot:77 plot_results_graph(results, dataset_name, n_shots)78 return results, n_shots79 80def save_results(dataset: str, n_shots: List[int], results: np.ndarray[int], predictions: List[str], outpath: str,81 model: str = '', plot_results: bool = True,shuffle = False,noisy = False,reinforce = False) -> None:82 if plot_results:83 plot_results_graph(results, dataset, n_shots, model)84 plt.show()85 if not dist.is_initialized() or dist.get_rank() == 0:86 # in case we use multiple GPUs - we only save one file87 np.save(outpath, results)88 with open(outpath.split(".")[0] + "-outputs.pkl", 'wb') as f:89 import pickle90 pickle.dump(predictions, f)91 clean_name = outpath.split(".")[0].split('/')[-1]92 for num, nshots in enumerate(n_shots):93 if num >= len(predictions):94 break95 for i, rep in enumerate(predictions[num]):96 # need to add id and output columns 97 rep['id'] = rep.index98 rep['n_shots'] = nshots99 if shuffle == True:100 rep['shuffle_number'] = i101 with open(os.path.dirname(outpath) + "/" + clean_name.split("n_shots_")[0]+"+n_shots="+str(nshots)+"+shuffle="+str(i)+".csv", 'w',encoding="utf-8") as f:102 rep.to_csv(f)103 104 elif noisy == True:105 rep['noisy_level'] = i106 with open(os.path.dirname(outpath) + "/" + clean_name.split("n_shots_")[0]+"+n_shots="+str(nshots)+"+noisy_level="+str(i)+".csv", 'w',encoding="utf-8") as f:107 rep.to_csv(f)108 elif reinforce == True:109 rep['reinforce_number'] = i110 with open(os.path.dirname(outpath) + "/" + clean_name.split("n_shots_")[0]+"+n_shots="+str(nshots)+"+reinforce_number="+str(i)+".csv", 'w',encoding="utf-8") as f:111 rep.to_csv(f)112 else:113 rep['run_number'] = i114 with open(os.path.dirname(outpath) + "/" + clean_name.split("n_shots_")[0]+"+n_shots="+str(nshots)+"+run="+str(i)+".csv", 'w',encoding="utf-8") as f:115 rep.to_csv(f)116 117def encode_labels(tokenizer: PreTrainedTokenizerBase, labels: List[str]) -> List[List[int]]:118 if isinstance(tokenizer, LlamaTokenizer):119 # sentence piece - adds a space at the beginning of the sentence120 return [tokenizer.encode(f'{label.lstrip()}', add_special_tokens=False) for label in labels]121 122 return [tokenizer.encode(f' {label.lstrip()}', add_special_tokens=False) for label in labels]123 124 125def encode_stop_seq(tokenizer: PreTrainedTokenizerBase, stop_seq: str) -> int:126 stop_seq_token_id = tokenizer.encode(stop_seq, add_special_tokens=False)127 if isinstance(tokenizer, LlamaTokenizer) or isinstance(tokenizer, LlamaTokenizerFast):128 assert len(stop_seq_token_id) == 2129 else:130 assert len(stop_seq_token_id) == 1131 return stop_seq_token_id[-1]132 133 134def extract_again(text):135 pattern = r"[aA]nswer is \(?([A-J])\)?"136 match = re.search(pattern, text)137 if match:138 return match.group(1)139 else:140 #print("1st answer extract failed\n" + text)141 return extract_final(text)142 143 144def extract_answer(text):145 match = re.search(r'.*[aA]nswer:\s*\(?([A-J])\)?', text)146 if match:147 return match.group(1)148 else:149 #print(" 2nd answer extract failed\n")150 return extract_again(text)151 152 153 154def extract_final(text):155 pattern = r"\(?\b([A-J])\b\)?(?!.*\(?\b([A-J])\b\)?)"156 match = re.search(pattern, text, re.DOTALL)157 if match:158 return match.group(0)159 else:160 #print("failed to extract answer\n")161 return None162 163def extract_answer_math(text):164 """165 从字符串中匹配:166 1. "final answer is" (大小写不敏感)167 2. 任意内容(非贪婪)168 3. 直到 ". I hope" (大小写不敏感)169 170 返回从 "final answer is" 后开始,到 ". I hope" 前结束的部分。171 如果找不到匹配,返回 None。172 """173 pattern = re.compile(174 # (?i) 表示忽略大小写175 r'(?i)final\s*answer\s*is\s*(.*?)\.\s*i\s*hope'176 )177 match = pattern.search(text)178 if match:179 # group(1) 即捕获到的答案内容180 # strip() 用来去掉首尾空格181 return match.group(1).strip()182 #return extract_again_math(text)183 return None184 185 186def extract_again_math(text):187 pattern = r"[aA]nswer\s*:\s*(.+?)(?:\.?\s*[Aa]nswer|$)"188 match = re.search(pattern, text)189 if match:190 return match.group(1)191 else:192 #print("1st answer extract failed\n" + text)193 return extract_final_math(text)194 195 196def extract_final_math(text):197 index = text.find('\\boxed{')198 if index == -1:199 return None200 index += len('\\boxed{')201 brace_count = 1202 content = ''203 while index < len(text):204 char = text[index]205 if char == '{':206 brace_count += 1207 elif char == '}':208 brace_count -= 1209 if brace_count == 0:210 break211 content += char212 index += 1213 return content if content != '' else None214 215def extract_answer_gsm8k(text):216 # 匹配 "####" 后的数字部分(允许包含逗号)217 pattern = r"####\s*([\d,]+)"218 match = re.search(pattern, text)219 if match:220 return match.group(1) # 返回匹配的数字部分221 else:222 # 如果没有匹配到,则打印提示并返回 None223 # print("No final math result found\n" + text)224 return extract_again_math(text)225 226 227 228 229 230 231def _fix_fracs(string):232 substrs = string.split("\\frac")233 new_str = substrs[0]234 if len(substrs) > 1:235 substrs = substrs[1:]236 for substr in substrs:237 new_str += "\\frac"238 if substr[0] == "{":239 new_str += substr240 else:241 try:242 assert len(substr) >= 2243 except:244 return string245 a = substr[0]246 b = substr[1]247 if b != "{":248 if len(substr) > 2:249 post_substr = substr[2:]250 new_str += "{" + a + "}{" + b + "}" + post_substr251 else:252 new_str += "{" + a + "}{" + b + "}"253 else:254 if len(substr) > 2:255 post_substr = substr[2:]256 new_str += "{" + a + "}" + b + post_substr257 else:258 new_str += "{" + a + "}" + b259 string = new_str260 return string261 262def _fix_a_slash_b(string):263 if len(string.split("/")) != 2:264 return string265 a = string.split("/")[0]266 b = string.split("/")[1]267 try:268 a = int(a)269 b = int(b)270 assert string == "{}/{}".format(a, b)271 new_string = "\\frac{" + str(a) + "}{" + str(b) + "}"272 return new_string273 except:274 return string275 276def _remove_right_units(string):277 # "\\text{ " only ever occurs (at least in the val set) when describing units278 if "\\text{ "in string:279 splits = string.split("\\text{ ")280 assert len(splits) == 2281 return splits[0]282 if "\\text{" in string:283 splits = string.split("\\text{")284 assert len(splits) == 2285 return splits[0]286 else:287 return string288 289def _fix_sqrt(string):290 if "\\sqrt" not in string:291 return string292 splits = string.split("\\sqrt")293 new_string = splits[0] 294 for split in splits[1:]:295 if split[0] != "{":296 a = split[0]297 new_substr = "\\sqrt{" + a + "}" + split[1:]298 else:299 new_substr = "\\sqrt" + split300 new_string += new_substr301 return new_string302 303def _replace_frac(string):304 # 将 \frac{a}{b} 替换为 a/b305 pattern = r'\\frac\{([^{}]+)\}\{([^{}]+)\}'306 repl = r'\1/\2'307 string = re.sub(pattern, repl, string)308 return string309 310def _strip_string(string):311 # linebreaks 312 string = string.replace("\n", "")313 #print(string)314 315 string = string.replace("\(", "")316 string = string.replace("\)", "")317 318 string = string.replace("\\,", "")319 string = string.replace("\,", "")320 string = string.replace(",", "")321 322 # remove inverse spaces323 string = string.replace("\\!", "")324 #print(string)325 326 # replace \\ with \327 string = string.replace("\\\\", "\\")328 #print(string)329 330 # replace tfrac and dfrac with frac331 string = string.replace("tfrac", "frac")332 string = string.replace("dfrac", "frac")333 #print(string)334 335 # remove \left and \right336 string = string.replace("\\left", "")337 string = string.replace("\\right", "")338 #print(string)339 340 # Remove circ (degrees)341 string = string.replace("^{\\circ}", "")342 string = string.replace("^\\circ", "")343 344 # remove dollar signs345 string = string.replace("\\$", "")346 string = string.replace("\$", "")347 string = string.replace("$", "")348 349 # remove units (on the right)350 string = _remove_right_units(string)351 352 # remove percentage353 string = string.replace("\\%", "")354 string = string.replace("\%", "")355 356 # " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string357 string = string.replace(" .", " 0.")358 string = string.replace("{.", "{0.")359 360 361 # if empty, return empty string362 if len(string) == 0:363 return string364 if string[0] == ".":365 string = "0" + string366 367 # to consider: get rid of e.g. "k = " or "q = " at beginning368 if len(string.split("=")) == 2:369 if len(string.split("=")[0]) <= 2:370 string = string.split("=")[1]371 372 # fix sqrt3 --> sqrt{3}373 string = _fix_sqrt(string)374 375 # remove spaces376 string = string.replace(" ", "")377 378 # \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b}379 string = _fix_fracs(string)380 381 # manually change 0.5 --> \frac{1}{2}382 if string == "0.5":383 string = "\\frac{1}{2}"384 385 # NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y386 string = _fix_a_slash_b(string)387 string = _replace_frac(string)388 389 #如果string是一个数字390 if string.isdigit():391 #如果是3.0这类的整数但是多了一个.0,去掉.0392 if string[-2:] == ".0":393 string = string[:-2]394 395 396 397 return string398 399def is_equiv(str1, str2, verbose=False):400 if str1 is None and str2 is None:401 print("WARNING: Both None")402 return True403 if str1 is None or str2 is None:404 return False405 406 try:407 ss1 = _strip_string(str1)408 ss2 = _strip_string(str2)409 if verbose:410 print(ss1, ss2)411 return ss1 == ss2412 except:413 return str1 == str2414 415 416def retrieve_context(train_df: pd.DatetimeIndex, index: SparseRetriever, curr_example: str, n_examples: int, split_text, shuffle_seed=None):417 retrieved = index.search(418 query=curr_example, # What to search for 419 return_docs=False, # Default value, return the text of the documents420 cutoff=n_examples, # Default value, number of results to return421 )422 inds = [int(d) for d in retrieved]423 424 if len(inds) < n_examples:425 print(f"WARNING: sampling {n_examples - len(inds)} examples randomly to fill window")426 #从train_df中随机抽取n_examples - len(inds)个样本,并且与inds里已经检索得到的不一样427 inds.extend(train_df['id'].sample(n_examples - len(inds)).loc[~train_df['id'].isin(inds)])428 429 dps = list(train_df.loc[train_df['id'].isin(inds)]['prompt'])430 if shuffle_seed:431 import random432 prev_state = random.getstate()433 random.seed(shuffle_seed)434 random.shuffle(dps)435 random.setstate(prev_state)436 437 text = split_text.join(dps)438 return text439 440def create_retriever(train_df):441 sr = SparseRetriever(442 index_name="training-examples",443 model="bm25",444 min_df=1,445 tokenizer="whitespace",446 stemmer="english",447 stopwords="english",448 do_lowercasing=True,449 do_ampersand_normalization=True,450 do_special_chars_normalization=True,451 do_acronyms_normalization=True,452 do_punctuation_removal=True,453 )454 import random455 filename = f"__temp_index_file_{random.randint(1,5888)}_{random.randint(1,5999)}.csv"456 train_df['id'] = train_df.index457 from pathlib import Path458 import os459 if os.path.exists(filename):460 Path.unlink(Path(filename))461 462 train_df.to_csv(filename)463 sr.index_file(path=filename, 464 show_progress=True, 465 callback=lambda doc: { # Callback defaults to None.466 "id": doc["id"],467 "text": doc["prompt"]}, 468 )469 Path.unlink(Path(filename))470 471 return sr472 473def add_noisy(df,task,noisy_level,noisy_idx,residue_df,labels = None):474 if noisy_level == 0:475 return df476 df_idx = df.index.tolist()477 #先从df_inx里去除noisy_idx那部分478 candidate_idx = [idx for idx in df_idx if idx not in noisy_idx]479 480 481 if task == 'summarization' or task == 'multilingual':482 # 复制 solution 列到 shuffle_solution 列483 df['noisy_solution'] = df['solution']484 485 # 随机选取 noisy_level 个样本的索引486 487 noisy_indices = random.sample(candidate_idx, noisy_level - len(noisy_idx))488 489 all_noisy_indices = noisy_idx + noisy_indices490 #从residue_df里随机选取noisy_level个样本的solution,然后把df里all_noisy_indices对应的solution替换成residue_df里对应的solution491 residue_values = residue_df.loc[random.sample(residue_df.index.tolist(),noisy_level), 'solution'].tolist()492 df.loc[all_noisy_indices, 'noisy_solution'] = residue_values493 494 495 496 """497 original_values = df.loc[noisy_indices, 'solution'].tolist()498 #确保value里没有重复的,否则中断程序499 assert len(original_values) == len(set(original_values)), "Noisy values contain duplicates"500 # 确保完全乱序,直到没有元素保持原位501 if len(noisy_indices) == 1:502 #直接把noisy_indices对应的shuffle_solution替换成除去这个indices对应的solution以外的任意一个值503 shuffled_values = random.sample([v for v in df['solution'].tolist() if v != original_values[0]],1)504 assert shuffled_values[0] != original_values[0], "Shuffled value is the same as original value"505 else:506 iter = 0507 while True:508 shuffled_values = random.sample(original_values, len(original_values))509 iter += 1510 if all(o != s for o, s in zip(original_values, shuffled_values)):511 break512 if iter > 100:513 print("WARNING: could not shuffle noisy examples after 100 iterations")514 # 更新 shuffle_solution 列515 """516 #df.loc[noisy_indices, 'solution'] = shuffled_values517 518 519 520 #生成prompt_new列521 df['prompt_new'] = df['problem'] + df['noisy_solution'] + '\n'522 523 return df,all_noisy_indices524 elif task == 'classification':525 df['shuffle_solution'] = df['solution']526 noisy_indices = random.sample(df.index.tolist(), noisy_level)527 original_values = df.loc[noisy_indices, 'solution'].tolist()528 529 values = set(original_values)530 531 if len(values) > 1:532 for i in range(len(original_values)):533 #把值替换成values不同于原值的值534 iter = 0535 while True:536 new_value = random.choice(list(values))537 iter += 1538 if new_value != original_values[i]:539 break540 if iter > len(labels):541 print("WARNING: could not shuffle noisy examples after all labels were tried")542 assert False543 else:544 #如果只有一个值,就从labels里随机选一个不同于原值的值545 for i in range(len(original_values)):546 iter = 0547 while True:548 new_value = random.choice(labels)549 iter += 1550 if new_value != original_values[i]:551 break552 if iter > len(labels):553 print("WARNING: could not shuffle noisy examples after all labels were tried")554 assert False555 556 df.loc[noisy_indices, 'shuffle_solution'] = shuffled_values557 df['prompt_new'] = df['problem'] + df['shuffle_solution'] + '\n'558 return df,noisy_indices559 elif task == 'qa':560 # 复制 solution 列到 shuffle_solution 列561 df['shuffle_solution'] = df['solution']562 df['shuffle_answer'] = df['answer']563 564 # 随机选取 noisy_level 个样本的索引565 noisy_indices = random.sample(df.index.tolist(), noisy_level)566 567 original_values = df.loc[noisy_indices, 'solution'].tolist()568 original_answers = df.loc[noisy_indices, 'answer'].tolist()569 570 #确保value里没有重复的,否则中断程序571 assert len(original_values) == len(set(original_values)), "Noisy values contain duplicates"572 # 确保完全乱序,直到没有元素保持原位573 iter = 0574 while True:575 shuffled_indices = random.sample(range(len(original_values)), len(original_values))576 shuffled_values = [original_values[i] for i in shuffled_indices]577 shuffled_answers = [original_answers[i] for i in shuffled_indices]578 579 iter += 1580 # 检查是否满足完全乱序581 if all(original_values[i] != shuffled_values[i] for i in range(len(original_values))):582 break583 if iter > 100:584 print("WARNING: could not shuffle noisy examples after 100 iterations")585 # 更新 shuffle_solution 列586 df.loc[noisy_indices, 'shuffle_solution'] = shuffled_values587 df.loc[noisy_indices, 'shuffle_answer'] = shuffled_answers588 589 #生成prompt_new列590 df['prompt_new'] = df['problem'] + 'Solution:\n' + df['shuffle_solution'] + '\n' + "Answer: " + "(" + df['shuffle_answer'].apply(lambda x: x["answer"].rstrip()) + ")" + '\n'591 592 return df,noisy_indices593 594 595 596 