Ein eigener kostenfreier Übersetzer für große Textmengen.
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.
Wir können also nicht einfach Fragen stellen und das Sprachmodell antwortet hierauf. Hierfür müssen wir noch ein Finetuning durchführen. Generell kann man viele unterschiedliche Aufgaben eintrainieren. Hierzu zählen:
- Klassifizieren: Einteilen in gute/schlechte Bewertungen, Fragen mit vorgefertigten Antworten, ...
- Extrahieren: Kundendaten, Daten
- Zusammenfassen: Zusammenfassen langer Texte, Erzeugen von Abstracts
- Beantworten: Beantworten von Fragen mit oder ohne vorgegebene Informationen
- Suchen: Suchen von Informationen anhand von sprachlichen Ähnlichkeiten
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.Dolly Datensatz 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 ShareGPTShareGPT oder der Evol-Datensatz von WizardLM.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.Leaderboard 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.QLoRA-Paper
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 entsprechenZeichen pro Token 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 DashboardOPUS-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.Tokenizer 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_txtfrom 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.