Wir führen jetzt Auswahl Mischen Ensemble indem die Reihenfolge der Antwortmöglichkeiten für jede Testfrage neu gemischt wird, wodurch mehrere Varianten derselben Frage entstehen. Der LLM wird dann mit diesen Varianten zusammen mit den entsprechenden Beispielen aus wenigen Versuchen aufgefordert, Denkschritte und eine Antwort für jede Variante zu generieren. Schließlich führen wir eine Mehrheitsabstimmung über die Vorhersagen aller Varianten durch und wählen die endgültige Vorhersage aus.
Den Code für diese Implementierung finden Sie hier Hyperlink zum GitHub-Repository.
Zur Implementierung und Evaluierung von Medprompt verwenden wir den MedQA (6)-Datensatz. Zunächst definieren wir Hilfsfunktionen zum Parsen der JSONL-Dateien.
def write_jsonl_file(file_path, dict_list):
"""
Write an inventory of dictionaries to a JSON Strains file.Args:
- file_path (str): The trail to the file the place the information will likely be written.
- dict_list (listing): A listing of dictionaries to jot down to the file.
"""
with open(file_path, 'w') as file:
for dictionary in dict_list:
json_line = json.dumps(dictionary)
file.write(json_line + 'n')
def read_jsonl_file(file_path):
"""
Parses a JSONL (JSON Strains) file and returns an inventory of dictionaries.
Args:
file_path (str): The trail to the JSONL file to be learn.
Returns:
listing of dict: A listing the place every component is a dictionary representing
a JSON object from the file.
"""
jsonl_lines = ()
with open(file_path, 'r', encoding="utf-8") as file:
for line in file:
json_object = json.masses(line)
jsonl_lines.append(json_object)
return jsonl_lines
Implementierung selbstgenerierter CoT
Für unsere Implementierung verwenden wir das Trainingsset von MedQA. Wir implementieren eine Zero-Shot-CoT-Eingabeaufforderung und verarbeiten alle Trainingsfragen. Wir verwenden GPT-4o in unserer Implementierung. Für jede Frage generieren wir den CoT und die entsprechende Antwort. Wir definieren eine Eingabeaufforderung, die auf der im Medprompt-Dokument bereitgestellten Vorlage basiert.
system_prompt = """You're an professional medical skilled. You're supplied with a medical query with a number of reply selections.
Your aim is to suppose by means of the query rigorously and clarify your reasoning step-by-step earlier than choosing the ultimate reply.
Reply solely with the reasoning steps and reply as specified beneath.
Under is the format for every query and reply:Enter:
## Query: {{query}}
{{answer_choices}}
Output:
## Reply
(mannequin generated chain of thought rationalization)
Due to this fact, the reply is (closing mannequin reply (e.g. A,B,C,D))"""
def build_few_shot_prompt(system_prompt, query, examples, include_cot=True):
"""
Builds the zero-shot immediate.Args:
system_prompt (str): Job Instruction for the LLM
content material (dict): The content material for which to create a question, formatted as
required by `create_query`.
Returns:
listing of dict: A listing of messages, together with a system message defining
the duty and a consumer message with the enter query.
"""
messages = ({"position": "system", "content material": system_prompt})
for elem in examples:
messages.append({"position": "consumer", "content material": create_query(elem)})
if include_cot:
messages.append({"position": "assistant", "content material": format_answer(elem("cot"), elem("answer_idx"))})
else:
answer_string = f"""## AnswernTherefore, the reply is {elem("answer_idx")}"""
messages.append({"position": "assistant", "content material": answer_string})
messages.append({"position": "consumer", "content material": create_query(query)})
return messages
def get_response(messages, model_name, temperature = 0.0, max_tokens = 10):
"""
Obtains the responses/solutions of the mannequin by means of the chat-completions API.
Args:
messages (listing of dict): The constructed messages supplied to the API.
model_name (str): Title of the mannequin to entry by means of the API
temperature (float): A price between 0 and 1 that controls the randomness of the output.
A temperature worth of 0 ideally makes the mannequin decide the most certainly token, making the outputs deterministic.
max_tokens (int): Most variety of tokens that the mannequin ought to generate
Returns:
str: The response message content material from the mannequin.
"""
response = shopper.chat.completions.create(
mannequin=model_name,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
return response.selections(0).message.content material
Wir definieren auch Hilfsfunktionen zum Parsen der Begründung und der endgültigen Antwortoption aus der LLM-Antwort.
def matches_ans_option(s):
"""
Checks if the string begins with the precise sample 'Due to this fact, the reply is (A-Z)'.Args:
s (str): The string to be checked.
Returns:
bool: True if the string matches the sample, False in any other case.
"""
return bool(re.match(r'^Due to this fact, the reply is (A-Z)', s))
def extract_ans_option(s):
"""
Extracts the reply possibility (a single capital letter) from the beginning of the string.
Args:
s (str): The string containing the reply sample.
Returns:
str or None: The captured reply possibility if the sample is discovered, in any other case None.
"""
match = re.search(r'^Due to this fact, the reply is ((A-Z))', s)
if match:
return match.group(1) # Returns the captured alphabet
return None
def matches_answer_start(s):
"""
Checks if the string begins with the markdown header '## Reply'.
Args:
s (str): The string to be checked.
Returns:
bool: True if the string begins with '## Reply', False in any other case.
"""
return s.startswith("## Reply")
def validate_response(s):
"""
Validates a multi-line string response that it begins with '## Reply' and ends with the reply sample.
Args:
s (str): The multi-line string response to be validated.
Returns:
bool: True if the response is legitimate, False in any other case.
"""
file_content = s.break up("n")
return matches_ans_option(file_content(-1)) and matches_answer_start(s)
def parse_answer(response):
"""
Parses a response that begins with '## Reply', extracting the reasoning and the reply selection.
Args:
response (str): The multi-line string response containing the reply and reasoning.
Returns:
tuple: A tuple containing the extracted CoT reasoning and the reply selection.
"""
split_response = response.break up("n")
assert split_response(0) == "## Reply"
cot_reasoning = "n".be a part of(split_response(1:-1)).strip()
ans_choice = extract_ans_option(split_response(-1))
return cot_reasoning, ans_choice
Wir verarbeiten nun die Fragen im Trainingsset von MedQA. Wir erhalten CoT-Antworten und Antworten für alle Fragen und speichern sie in einem Ordner.
train_data = read_jsonl_file("knowledge/phrases_no_exclude_train.jsonl")cot_responses = ()
# os.mkdir("cot_responses")
existing_files = os.listdir("cot_responses/")
for idx, merchandise in enumerate(tqdm(train_data)):
if str(idx) + ".txt" in existing_files:
proceed
immediate = build_zero_shot_prompt(system_prompt, merchandise)
attempt:
response = get_response(immediate, model_name="gpt-4o", max_tokens=500)
cot_responses.append(response)
with open(os.path.be a part of("cot_responses", str(idx) + ".txt"), "w", encoding="utf-8") as f:
f.write(response)
besides Exception as e :
print(str(e))
cot_responses.append("")
Wir durchlaufen nun alle generierten Antworten, um zu prüfen, ob sie gültig sind und dem in der Eingabeaufforderung definierten Vorhersageformat entsprechen. Wir verwerfen Antworten, die nicht dem erforderlichen Format entsprechen. Danach überprüfen wir die vorhergesagten Antworten anhand der Grundwahrheit für jede Frage und behalten nur Fragen bei, deren vorhergesagte Antworten mit der Grundwahrheit übereinstimmen.
questions_dict = ()
ctr = 0
for idx, query in enumerate(tqdm(train_data)):
file = open(os.path.be a part of("cot_responses/", str(idx) + ".txt"), encoding="utf-8").learn()
if not validate_response(file):
proceedcot, pred_ans = parse_answer(file)
dict_elem = {}
dict_elem("idx") = idx
dict_elem("query") = query("query")
dict_elem("reply") = query("reply")
dict_elem("choices") = query("choices")
dict_elem("cot") = cot
dict_elem("pred_ans") = pred_ans
questions_dict.append(dict_elem)
filtered_questions_dict = ()
for merchandise in tqdm(questions_dict):
pred_ans = merchandise("choices")(merchandise("pred_ans"))
if pred_ans == merchandise("reply"):
filtered_questions_dict.append(merchandise)
Implementierung des KNN-Modells
Nachdem wir den Trainingssatz verarbeitet und die CoT-Antwort für alle diese Fragen erhalten haben, betten wir nun alle Fragen ein mit dem Texteinbettung-ada-002 von OpenAI.
def get_embedding(textual content, mannequin="text-embedding-ada-002"):
return shopper.embeddings.create(enter = (textual content), mannequin=mannequin).knowledge(0).embeddingfor merchandise in tqdm(filtered_questions_dict):
merchandise("embedding") = get_embedding(merchandise("query"))
inv_options_map = {v:okay for okay,v in merchandise("choices").gadgets()}
merchandise("answer_idx") = inv_options_map(merchandise("reply"))
Wir trainieren nun ein KNN-Modell mit diesen Frageeinbettungen. Dies fungiert zur Inferenzzeit als Retriever, da es uns hilft, ähnliche Datenpunkte aus dem Trainingsset abzurufen, die der Frage aus dem Testset am ähnlichsten sind.
import numpy as np
from sklearn.neighbors import NearestNeighborsembeddings = np.array((d("embedding") for d in filtered_questions_dict))
indices = listing(vary(len(filtered_questions_dict)))
knn = NearestNeighbors(n_neighbors=5, algorithm='auto', metric='cosine').match(embeddings)
Implementierung der dynamischen Few-Shot- und Alternative-Shuffling-Ensemble-Logik
Wir können jetzt Inferenzen ausführen. Für unsere Auswertung nehmen wir eine Teilstichprobe von 500 Fragen aus dem MedQA-Testset. Für jede Frage rufen wir mithilfe des KNN-Moduls die 5 ähnlichsten Fragen aus dem Trainingsset ab, zusammen mit ihren jeweiligen CoT-Argumentationsschritten und vorhergesagten Antworten. Anhand dieser Beispiele konstruieren wir eine Eingabeaufforderung mit wenigen Versuchen.
Für jede Frage mischen wir außerdem die Reihenfolge der Optionen fünfmal, um verschiedene Varianten zu erstellen. Anschließend verwenden wir die erstellte Eingabeaufforderung mit wenigen Versuchen, um die vorhergesagte Antwort für jede der Varianten mit gemischten Optionen zu erhalten.
def shuffle_option_labels(answer_options):
"""
Shuffles the choices of the query.Parameters:
answer_options (dict): A dictionary with the choices.
Returns:
dict: A brand new dictionary with the shuffled choices.
"""
choices = listing(answer_options.values())
random.shuffle(choices)
labels = (chr(i) for i in vary(ord('A'), ord('A') + len(choices)))
shuffled_options_dict = {label: possibility for label, possibility in zip(labels, choices)}
return shuffled_options_dict
test_samples = read_jsonl_file("final_processed_test_set_responses_medprompt.jsonl")for query in tqdm(test_samples, color ="inexperienced"):
question_variants = ()
prompt_variants = ()
cot_responses = ()
question_embedding = get_embedding(query("query"))
distances, top_k_indices = knn.kneighbors((question_embedding), n_neighbors=5)
top_k_dicts = (filtered_questions_dict(i) for i in top_k_indices(0))
query("outputs") = ()
for idx in vary(5):
question_copy = query.copy()
shuffled_options = shuffle_option_labels(query("choices"))
inv_map = {v:okay for okay,v in shuffled_options.gadgets()}
question_copy("choices") = shuffled_options
question_copy("answer_idx") = inv_map(question_copy("reply"))
question_variants.append(question_copy)
immediate = build_few_shot_prompt(system_prompt, question_copy, top_k_dicts)
prompt_variants.append(immediate)
for immediate in tqdm(prompt_variants):
response = get_response(immediate, model_name="gpt-4o", max_tokens=500)
cot_responses.append(response)
for question_sample, reply in zip(question_variants, cot_responses):
if validate_response(reply):
cot, pred_ans = parse_answer(reply)
else:
cot = ""
pred_ans = ""
query("outputs").append({"query": question_sample("query"), "choices": question_sample("choices"), "cot": cot, "pred_ans": question_sample("choices").get(pred_ans, "")})
Wir bewerten nun die Ergebnisse von Medprompt anhand des Testsatzes. Für jede Frage haben wir fünf Vorhersagen, die durch die Ensemble-Logik generiert wurden. Wir nehmen den Modus oder die am häufigsten auftretende Vorhersage für jede Frage als endgültige Vorhersage und bewerten die Leistung. Hier sind zwei Randfälle möglich:
- Es werden jeweils zwei unterschiedliche Antwortmöglichkeiten vorhergesagt, wobei es keinen klaren Sieger gibt.
- Bei der generierten Antwort liegt ein Fehler vor. Dies bedeutet, dass uns keine vorhergesagte Antwortoption zur Verfügung steht.
In beiden Randfällen gehen wir davon aus, dass die Frage vom LLM falsch beantwortet wurde.
def find_mode_string_list(string_list):
"""
Finds essentially the most regularly occurring strings.Parameters:
string_list (listing of str): A listing of strings.
Returns:
listing of str or None: A listing containing essentially the most frequent string(s) from the enter listing.
Returns None if the enter listing is empty.
"""
if not string_list:
return None
string_counts = Counter(string_list)
max_freq = max(string_counts.values())
mode_strings = (string for string, depend in string_counts.gadgets() if depend == max_freq)
return mode_strings
ctr = 0
for merchandise in test_samples:
pred_ans = (x("pred_ans") for x in merchandise("outputs"))
freq_ans = find_mode_string_list(pred_ans)
if len(freq_ans) > 1:
final_prediction = ""
else:
final_prediction = freq_ans(0)
if final_prediction == merchandise("reply"):
ctr +=1
print(ctr / len(test_samples))
Wir bewerten die Leistung von Medprompt mit GPT-4o hinsichtlich der Genauigkeit im MedQA-Check-Subset. Darüber hinaus vergleichen wir die Leistung von Zero-Shot-Prompting, Random Few-Shot-Prompting und Random Few-Shot mit CoT-Prompting.
Wir beobachten, dass die Eingabeaufforderungen Medprompt und Random Few-Shot CoT die Baselines Zero und Few-Shot übertreffen. Überraschenderweise stellen wir jedoch fest, dass Random Few-Shot CoT unsere Medprompt-Leistung übertrifft. Dies könnte mehrere Gründe haben:
- Im ursprünglichen Medprompt-Artikel wurde die Leistung von GPT-4 getestet. Wir stellen fest, dass GPT-4o GPT-4T und GPT-4 bei verschiedenen Textual content-Benchmarks deutlich übertrifft (https://openai.com/index/hello-gpt-4o/), was darauf hindeutet, dass Medprompt auf ein stärkeres Modell wie GPT-4o einen geringeren Effekt haben könnte.
- Wir beschränken unsere Auswertung auf 500 Fragen, die aus MedQA ausgewählt wurden. Das Medprompt-Papier wertet andere medizinische MCQA-Datensätze und die Vollversion von MedQA aus. Die Auswertung von GPT-4o anhand der vollständigen Versionen der Datensätze könnte ein besseres Bild der Gesamtleistung vermitteln.
Medprompt ist ein interessantes Framework zum Erstellen anspruchsvoller Prompting-Pipelines, insbesondere zum Anpassen eines allgemeinen LLM an eine bestimmte Domäne, ohne dass eine Feinabstimmung erforderlich ist. Es hebt auch die Überlegungen hervor, die bei der Entscheidung zwischen Prompting und Feinabstimmung für verschiedene Anwendungsfälle anfallen. Es ist wichtig zu untersuchen, wie weit Prompting getrieben werden kann, um die LLM-Leistung zu verbessern, da es eine ressourcen- und kosteneffiziente Different zur Feinabstimmung bietet.
(1) Nori, H., Lee, YT, Zhang, S., Carignan, D., Edgar, R., Fusi, N., … & Horvitz, E. (2023). Können generalistische Grundlagenmodelle Spezialtuning übertreffen? Fallstudie in der Medizin. arXiv-Vorabdruck arXiv:2311.16452. (https://arxiv.org/abs/2311.16452)
(2) Wei, J., Wang, X., Schuurmans, D., Bosma, M., Xia, F., Chi, E., … & Zhou, D. (2022). Gedankenketten-Aufforderungen führen zum Denken in großen Sprachmodellen. Fortschritte bei neuronalen Informationsverarbeitungssystemen, 3524824–24837. (https://openreview.web/pdf?id=_VjQlMeSB_J)
(3) Gekhman, Z., Yona, G., Aharoni, R., Eyal, M., Feder, A., Reichart, R., & Herzig, J. (2024). Fördert die Feinabstimmung von LLMs auf neues Wissen Halluzinationen? arXiv-Vorabdruck arXiv:2405.05904. (https://arxiv.org/abs/2405.05904)
(4) Singhal, Okay., Azizi, S., Tu, T., Mahdavi, SS, Wei, J., Chung, HW, … & Natarajan, V. (2023). Große Sprachmodelle kodieren klinisches Wissen. Natur, 620(7972), 172–180. (https://www.nature.com/articles/s41586-023-06291-2)
(5) Singhal, Okay., Tu, T., Gottweis, J., Sayres, R., Wulczyn, E., Hou, L., … & Natarajan, V. (2023). Auf dem Weg zur Beantwortung medizinischer Fragen auf Expertenniveau mit großen Sprachmodellen. arXiv-Vorabdruck arXiv:2305.09617. (https://arxiv.org/abs/2305.09617)
(6) Jin, D., Pan, E., Oufattole, N., Weng, WH, Fang, H., & Szolovits, P. (2021). Welche Krankheit hat dieser Affected person? Ein umfangreicher Open-Area-Frage-Antwort-Datensatz aus medizinischen Untersuchungen. Angewandte Wissenschaften, 11(14), 6421. (https://arxiv.org/abs/2009.13081) (Der ursprüngliche Datensatz wird unter einer MIT-Lizenz veröffentlicht)