Bisher haben wir die Sprachmodelle lediglich mit einer großen Menge an Daten trainiert. Damit führt das Sprachmodell lediglich die Eingabe fort und man muss speziell mit diesem interagieren.
Zum finetunen eines deutschen Netzes wird ein entsprechender Datensatz benötigt. Einerseits können
solche Datensätze manuell erzeugt werden. Ein Beispiel ist der Datensatz von Databricks der von
mehr als 5000 Beschäftigten über einen internen Wettbewerb erzeugt wurde.
Andererseits kann der
Datensatz auch über die Interaktion mit anderen LLMs von Anthropic oder OpenAI erzeugt werden. Hierbei
gibt es ebenfalls viele verschiedene Herangehensweisen, wie das allgemeine Sammeln von Interaktionen mit
ChatGPT über ShareGPT oder der Evol-Datensatz von
WizardLM .
Im Folgenden soll jedoch kein neuer Datensatz erzeugt werden, da dies bei manueller Erstellung sehr
aufwendig sein kann und bei automatisierter Erstellung ein API Zugang inkl. Bezahlung notwendig ist.
Eine
Alternative besteht darin, einen bestehenden Datensatz zu übersetzen. Hierfür bieten sich einige
Datensätze an, die für das Finetunen von englischen Sprachmodellen verwendet werden. Diese lassen sich
in der Regel
über die entsprechenden Github Repositories oder über Huggingface herunterladen.
Eine Übersicht kann erlangt werden, indem man zum Beispiel die Benchmarks unterschiedlicher Netze
anschaut und dann die bei deren Training verwendeten Datensätze für das eigene Training ebenfalls nutzt.
Generell muss man hier die unterschiedlichen Netzgrößen ebenfalls unterscheiden. Auf Consumer-GPUs mit
24 GB VRAM können mit einigen Tricks bis zu 33B große Netze trainiert werden.
Darüber hinaus gibt es meist von diesen Datensätzen noch "unfiltered" Datensätze, in denen Limitierungen
in den LLM-Ausgaben herausgefiltert werden. Dies hat zwar den Vorteil, dass das später trainierte Modell
teilweise bessere Rückmeldungen liefert und weniger nach dem Kontext "Ich bin ein KI-Modell
und
habe keine Meinung hierzu" antwortet, führt jedoch auch dazu, dass keinerlei Sicherheitsmechanismen mehr
vorhanden sind.
Für die Übersetzung bieten sich ebenfalls unterschiedliche Wege an:
Die Preise für die Übersetzung betragen ca. 20€ pro 1 Mio. Zeichen. Der Alpaca evol Instruct Datensatz zum Beispiel besitzt über 130 Mio. Zeichen, was einem Preis von 2605€ entsprechen würde.
import pandas as pd
print("Preisberechnung für Deepl")
dataset = pd.read_json("evol_instruct_70k/alpaca_evol_instruct_70k.json")
total = dataset["instruction"].str.len().sum()+dataset["output"].str.len().sum()
print(f"Total characters: {total}, Total price: {total/1000000*20}")
Preisberechnung für DeepL Total characters: 130296989, Total price: 2605.9397799999997
Nehmen wir an, dass im Englischen ca. 4 Zeichen einem Token entsprechen folgen hieraus 977 Dollar bei 0.03 Dollar pro 1K Token mit dem GPT4 Modell und 8K Kontext. Da in anderen Sprachen weniger Zeichen pro Token folgen, liegt der Preis wahrscheinlich sogar noch über dem berechneten Preis. Lediglich das Chat-GPT Modell mit 0.002 Dollar pro 1k Token scheint noch sinnvoll zu sein. Hierbei würden sich Kosten von ca. 100€ ergeben.
import pandas as pd
print("Preisberechnung für GPT4")
dataset = pd.read_json("alpaca_evol_instruct_70k.json")
total = dataset["instruction"].str.len().sum()+dataset["output"].str.len().sum()
print(f"Total characters: {total}, Total price: {total/4/1000*0.03}")
print("Preisberechnung für Chat-GPT")
print(f"Total characters: {total}, Total price: {total/4/1000*0.002}")
Preisberechnung für GPT4 Total characters: 130296989, Total price: 977.2274175 Preisberechnung für Chat-GPT Total characters: 130296989, Total price: 65.1484945
Da die beiden Alternativen über API Calls relativ teuer sein können, bietet es sich an, spezielle
Übersetzungsmodelle zu verwenden. Eine Übersicht
bietet das OPUS-MT Dashboard , welches unterschiedliche Übersetzungsmodelle
vergleicht.
Hierbei stechen zwei unterschiedliche Modell raus: facebook/wmt19-en-de und opus-mt-align-en-de
Wir verwenden für die Übersetzung die Python-Bibliothek Transformers, mit der sowohl das Sprachmodell
als auch der Tokenizer ausgeführt werden. Wie der Tokenizer funktioniert, wurde bereits im letzten
Beitrag
beschrieben.
Als Datensatz verwenden wir den Alpaca Evol Instruct Datensatz von WizardLM.
Zuerst laden wir das Übersetzungsmodell und den Tokenizer mit der Transformers Bibliothek. Wir haben uns
hier für das opus-mt-en-de Modell entschieden, da das Facebook-Modell eine Kombination aus 4
verschiedenen Modellen verwendet, die gemeinsam eine Vorhersage machen. Diese Einbindung mehrerer
Modelle als
Ensemble ist jedoch noch nicht in der Transformers Bibliothek implementiert.
# Laden des Sprachmodells
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
model_name = "Helsinki-NLP/opus-mt-en-de"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name).to(device="cuda")
Als nächstes Laden wir den Datensatz von Huggingface:
# Downloaden des Datensatzes:
!git clone https://huggingface.co/datasets/WizardLM/evol_instruct_70k
# Einlesen des Datensatzes
import pandas as pd
dataset = pd.read_json(r"evol_instruct_70k/alpaca_evol_instruct_70k.json")
dataset
instruction | output | |
---|---|---|
0 | Can you provide a list of healthy habits to ma... | Here's an HTML page with bullet points for hea... |
1 | How can we use Python to calculate the GCD (gr... | Yes, that's correct! The function you've provi... |
2 | Generate a list of ten essential items a perso... | Sure, here's a list of ten essential items a p... |
3 | How can we implement a privacy-enhancing techn... | Homomorphic encryption is a powerful technique... |
4 | Can you provide a list of the world's most fam... | Here is the JSON format for the world's most f... |
... | ... | ... |
69995 | How can I use a pencil in unconventional ways ... | Here are 5 specific examples of how you can us... |
69996 | Can you solve this space challenge? Your task ... | Yes, I am up for the challenge! Let's get star... |
69997 | I have a list of novels and their correspondin... | Yes, I can help you with that. Here's the SQL ... |
69998 | Determine the area of a regular pentagon with ... | Using the formula for the area of a regular pe... |
69999 | What is the C++ code to calculate the surface ... | Here is the C++ code to calculate the surface ... |
70000 rows × 2 columns
Als erstes extrahieren wir den Code in den Instruktionen und Ausgaben. Das Sprachmodell würde hier viele Fehler einbauen und zum Beispiel for oder if-Schleifen übersetzen. Wir ersetzen den Code lediglich durch "<code_snip>".
import re
import numpy as np
dataset["instruction_code"] = np.nan
dataset["instruction_cleaned"] = np.nan
for i, row in dataset.iterrows():
code = re.findall(r"```([\s\S]*?)```", row["instruction"])
if code == []:
code = [np.nan]
dataset.at[i, "instruction_code"] = str(code)
dataset.at[i, "instruction_cleaned"] = re.sub(r"```([\s\S]*?)```", '<code_snip>', row["instruction"], flags=re.DOTALL)
code_out = re.findall(r"```([\s\S]*?)```", row["output"])
if code_out == []:
code_out = [np.nan]
dataset.at[i, "output_code"] = str(code_out)
dataset.at[i, "output_cleaned"] = re.sub(r"```([\s\S]*?)```", '<code_snip>', row["output"], flags=re.DOTALL)
Da das Sprachmodell keine Zeilenumbrüche nach "\n" übersetzt, müssen wir die Texte entweder an diese Stellen trennen oder wir ersetzen diese durch den HTML-Tag <br>
dataset["instruction"] = dataset["instruction"].str.replace("\n", "<br>")
dataset["output"] = dataset["output"].str.replace("\n", "<br>")
Die Übersetzungsmodelle arbeiten ebenfalls mit Token und haben eine eingeschränkte Tokenlänge von ca. 512 bis 1024 Token. Die Texte werden somit auf eine bestimmte maximale Länge aufgeteilt und möglichst am Ende des Satzes oder Abschnittes getrennt.
def split_long_string(text, max_length):
result = []
while len(text) > max_length:
if r"\n" in text[:max_length]:
split_index = text[:max_length].rindex(r"\n") + 2
elif "." in text[:max_length]:
split_index = text[:max_length].rindex(".") + 1
else:
split_index = max_length
result.append(text[:split_index].strip())
text = text[split_index:]
if text:
result.append(text.strip())
return result, len(result)
def split_wrapper(long_str_list, max_len=500):
short_str_list = []
split_lens = []
for single_str in long_str_list:
short_string, split_len = split_long_string(single_str, max_len)
short_str_list.extend(short_string)
split_lens.append(split_len)
return short_str_list, split_lens
source_instructions ,split_len_instructions = split_wrapper(source_instructions)
source_outputs ,split_len_outputs = split_wrapper(source_outputs)
Nun können wir die eigentlichen Texte übersetzen. Hierfür definieren wir zuerst eine Funktion und iterieren diese in Batches über einen Datensatz:
def translate(model, tokenizer, data):
tokenized_txt = tokenizer(data, return_tensors="pt", padding=True).to("cuda")
uebersetzt_txt = model.generate(tokenized_txt["input_ids"],
attention_mask=tokenized_txt["attention_mask"],
max_length=512,
)
uebersetzt_txt = tokenizer.batch_decode(uebersetzt_txt, skip_special_tokens=True)
return uebersetzt_txt, tokenized_txt
from tqdm.notebook import tqdm
BATCH_SIZE = 16
# Instructions übersetzen:
translated_instructions = []
for num in tqdm(range(0, len(source_instructions), BATCH_SIZE)):
batch = source_instructions.iloc[num:num+BATCH_SIZE].to_list()
uebersetzt_txt, tokenized_txt = translate(model, tokenizer, batch)
translated_instructions.extend(uebersetzt_txt)
# Output übersetzen:
translated_outputs = []
for num in tqdm(range(0, len(source_outputs), BATCH_SIZE)):
batch = source_outputs.iloc[num:num+BATCH_SIZE].to_list()
uebersetzt_txt, tokenized_txt = translate(model, tokenizer, batch)
translated_outputs.extend(uebersetzt_txt)
In diesem Schritt kombinieren wir die einzelnen Abschnitte der Texte wieder und stellen die Zeilenumbrüche wieder in der ursprünglichen Form her. Anschließend können wir die Code-Abschnitte wieder in der ursprünglichen Form einsetzen.
# Hier die Texte wieder joinen:
def join_data(translated_data, split_lengths):
joined_data = []
curr_len = 0
for conv_len in split_lengths:
joined_data.append("".join(translated_data[curr_len:curr_len+conv_len]))
curr_len+=conv_len
return joined_data
translated_instructions = join_data(translated_instructions, split_len_instructions)
translated_outputs = join_data(translated_outputs, split_len_outputs)
# Textumbrüche widerherstellen
translated_instructions = list(map(lambda t: t.replace(" < br > ", "\n").replace("<br>", "\n"), translated_instructions))
translated_outputs = list(map(lambda t: t.replace(" < br > ", "\n").replace("<br>", "\n"), translated_outputs))
# Code wiederherstellen
TRANSLATED_TOKEN = r"<code_snip>"
def restore_code(translated_data, dataset, TRANSLATED_TOKEN, type="instruction"):
for n, data in enumerate(translated_data):
try:
code_snippets = eval(dataset[f"{type}_code"][n])
old_len = len(code_snippets)
translated_len = data.count(TRANSLATED_TOKEN)
cleaned_data = dataset[f"{type}_cleaned"][n]
if translated_len != old_len:
print(f"Len code snippets: {old_len}, translated Code: {translated_len} - {code_snippets} ||| {data} ||| {cleaned_data}") # Check dass kein code snippet verschwunden ist
for code_snippet in code_snippets:
translated_data[n] = translated_data[n].replace(TRANSLATED_TOKEN, code_snip,1)
except NameError: # No Code-Snippet present
pass
return translated_data
translated_instructions = restore_code(translated_instructions, dataset, TRANSLATED_TOKEN, "instruction")
translated_outputs = restore_code(translated_outputs, dataset, TRANSLATED_TOKEN, "output")
Während der Übersetzung bzw. Umwandlung des Codes kommt es teilweise zu Problemen. Das Ersatzwort,
"<code_snip>", wird
nicht einheitlich übersetzt. Somit können nicht alle Code-Bestandteile korrekt zurückgewandelt werden.
Dies betrifft jedoch lediglich jeweils 62 der 70 000 Anweisungen und Rückmeldungen.
Wir konnten damit zeigen, dass es möglich ist einen großen Datensatz lokal mit relativ
geringem Aufwand zu übersetzen. Der Datensatz ist auf Huggingface
verfügbar.
In dem nächsten Beitrag zeigen wir, wie ein solcher Datensatz verwendet werden kann, um ein
Sprachmodell anzupassen.