Kurs
Google hat gerade Gemma 4 vorgestellt und bezeichnet es als die bisher intelligenteste offene Modellfamilie, entwickelt für starkes Reasoning und agentische Workflows. Gemma-Modelle sind für unterschiedliche Umgebungen flexibel konzipiert, mit offiziellem Support und Tools für lokale Entwicklung, Cloud-Bereitstellung und Modellanpassung – ideal für Fine-Tuning-Projekte.
In diesem Tutorial stimmen wir Gemma 4 E4B-it auf einem Datensatz zur Emotionsklassifikation von Hugging Face ab. Wir richten eine 3090-GPU-Umgebung ein, laden und inspizieren den Datensatz, bereiten die Daten für überwachtes Fine-Tuning auf, laden das Basismodell, führen eine Baseline-Evaluierung vor dem Training durch, stimmen das Modell ab und bewerten anschließend die Leistung erneut.
1. Umgebung einrichten
Starte mit einer neuen Runpod-Instanz und stelle sicher, dass dein Konto mindestens 5 $ Guthaben hat. Wähle für dieses Tutorial einen 3090-GPU-Pod und das aktuelle PyTorch-Template.
Öffne vor dem Deployen die Template-Einstellungen und passe sie an. Erhöhe sowohl Container-Datenträger als auch Volume-Datenträger auf 40 GB, damit genug Platz für Modell, Datensatz, Caches und Checkpoints vorhanden ist.
Füge außerdem deinen Hugging-Face-Token als Umgebungsvariable hinzu. Du kannst diesen Token in deinem Hugging-Face-Konto unter Settings > Access Tokens erzeugen.

Wenn diese Einstellungen passen, kannst du den Pod deployen. Das Starten kann ein bis zwei Minuten dauern. Öffne danach die JupyterLab-Oberfläche, um in der Umgebung zu arbeiten.

Der erste Schritt in JupyterLab ist ein neues Python-Notebook zu starten und alle benötigten Pakete zu installieren. Führe dazu in einer Notebook-Zelle folgenden Befehl aus:
%%capture
!pip install -U transformers accelerate datasets trl peft bitsandbytes scikit-learn huggingface_hub
Diese Pakete decken den gesamten Workflow ab: Datensatz laden, Modell vorbereiten, Fine-Tuning und Evaluierung.
Melde dich anschließend mit deinem gespeicherten Token bei der Hugging Face Hub an. Das gibt dir Zugriff auf das gesperrte Modell und vereinfacht das Hochladen von Dateien, das Erstellen von Repos und das spätere Pushen deines feinabgestimmten Modells.
import os
from huggingface_hub import login
hf_token = os.environ.get("HF_TOKEN")
if not hf_token:
raise ValueError("Set HF_TOKEN in the RunPod environment before running this notebook.")
login(token=hf_token)
print("Logged in to Hugging Face.")
2. Emotions-Datensatz laden und vorbereiten
Jetzt, da die Umgebung steht, laden wir den Emotions-Datensatz von Hugging Face und erstellen kleinere Splits für Training und Evaluierung.
Für dieses Tutorial nutzen wir nicht den kompletten Datensatz. Stattdessen begrenzen wir Train-, Validierungs- und Testsplit, damit das Fine-Tuning schneller ist und problemlos auf einer einzelnen GPU läuft.
from datasets import load_dataset, DatasetDict
TRAIN_LIMIT = 4000
VALIDATION_LIMIT = 400
TEST_LIMIT = 400
EVAL_LIMIT = 400
raw_dataset = load_dataset("dair-ai/emotion")
def maybe_limit(split, limit):
split = split.shuffle(seed=42)
if limit is None:
return split
return split.select(range(min(limit, len(split))))
dataset = DatasetDict({
"train": maybe_limit(raw_dataset["train"], TRAIN_LIMIT),
"validation": maybe_limit(raw_dataset["validation"], VALIDATION_LIMIT),
"test": maybe_limit(raw_dataset["test"], TEST_LIMIT),
})
dataset
Der finale Datensatz enthält 4.000 Trainingsbeispiele, 400 Validierungsbeispiele und 400 Testbeispiele.
DatasetDict({
train: Dataset({
features: ['text', 'label'],
num_rows: 4000
})
validation: Dataset({
features: ['text', 'label'],
num_rows: 400
})
test: Dataset({
features: ['text', 'label'],
num_rows: 400
})
})
Als Nächstes sehen wir uns die Labelnamen im Datensatz an. Das sind die Emotionsklassen, die das Modell vorhersagen soll.
label_names = dataset["train"].features["label"].names
label_names
Es zeigt sich, dass die Aufgabe sechs Emotionskategorien umfasst: sadness, joy, love, anger, fear und surprise.
['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']
Wir können uns auch ein Beispiel aus dem Trainingssplit ansehen, um die Struktur zu verstehen.
dataset["train"][0]
Jedes Beispiel enthält einen Text und ein numerisches Label. In diesem Fall entspricht das Label 4 der Klasse fear aus der obigen Liste.
{'text': 'while cycling in the country', 'label': 4}
3. Daten für das Fine-Tuning von Gemma 4 formatieren
Bevor wir das Modell feinabstimmen, müssen wir den Datensatz in das Format umwandeln, das Gemma 4 während des Trainings nutzt.
Anstatt nur Rohtext und Labels zu übergeben, strukturieren wir jedes Beispiel als kurze Chat-Interaktion mit einer Systemnachricht, einer User-Nachricht und der erwarteten Assistant-Antwort.
Der Systemprompt sagt dem Modell genau, welche Aufgabe es ausführen soll. Hier soll es als Assistent für Emotionsklassifikation agieren und nur eines der sechs erlaubten Labels zurückgeben.
SYSTEM_PROMPT = """You are an emotion classification assistant.
Read the user's text and answer with exactly one label.
Only choose from: sadness, joy, love, anger, fear, surprise.
Return only the label and nothing else."""
In diesem Setup enthält die User-Nachricht den zu klassifizierenden Text und die Assistant-Nachricht das korrekte Label. So funktioniert überwachtes Fine-Tuning: Das Modell lernt, für jedes Trainingsbeispiel die richtige Antwort zu generieren.
def to_prompt_completion(example):
text = example["text"]
label = label_names[example["label"]]
return {
"prompt": [
{
"role": "system",
"content": SYSTEM_PROMPT,
},
{
"role": "user",
"content": f"Classify the emotion of this text:\n\n{text}",
},
],
"completion": [
{
"role": "assistant",
"content": label,
}
],
}
sft_dataset = dataset.map(to_prompt_completion, remove_columns=dataset["train"].column_names)
Nach dem Anwenden dieser Funktion werden die ursprünglichen Spalten text und label durch strukturierte Felder prompt und completion ersetzt.
Wir prüfen ein Beispiel, um zu bestätigen, dass das Format stimmt.
sft_dataset["train"][0]
Die Ausgabe zeigt die Trainingsstruktur klar: Das Modell sieht die Anweisung, liest den Eingabetext und lernt, das korrekte Emotionslabel als Antwort zu erzeugen.
{'prompt': [{'content': "You are an emotion classification assistant.\nRead the user's text and answer with exactly one label.\nOnly choose from: sadness, joy, love, anger, fear, surprise.\nReturn only the label and nothing else.",
'role': 'system'},
{'content': 'Classify the emotion of this text:\n\nwhile cycling in the country',
'role': 'user'}],
'completion': [{'content': 'fear', 'role': 'assistant'}]}
4. Gemma E4B-it mit 4-Bit-Quantisierung laden
Jetzt laden wir Gemma 4 E4B-it und bereiten es fürs Fine-Tuning vor. Da das Modell relativ groß ist, nutzen wir 4-Bit-Quantisierung, um Speicher zu sparen und den Betrieb auf einer 3090-GPU zu erleichtern. Als Rechentyp verwenden wir außerdem bfloat16 für effizientes Setup.
Wir importieren die benötigten Bibliotheken und definieren die wichtigsten Modelleinstellungen.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
MODEL_ID = "google/gemma-4-E4B-it"
MODEL_DTYPE = torch.bfloat16
USE_4BIT = True
Als Nächstes aktivieren wir einige CUDA-Optimierungen und laden den Tokenizer.
if torch.cuda.is_available():
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
processor = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
if processor.pad_token is None:
processor.pad_token = processor.eos_token
Nun bereiten wir die Quantisierungseinstellungen und Ladeparameter vor.
bnb_config = None
model_kwargs = {
"device_map": "auto",
}
if USE_4BIT:
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=MODEL_DTYPE,
)
model_kwargs["quantization_config"] = bnb_config
else:
model_kwargs["torch_dtype"] = MODEL_DTYPE
Zum Schluss laden wir das Modell und passen seine Konfiguration an den Tokenizer an.
base_model = AutoModelForCausalLM.from_pretrained(MODEL_ID, **model_kwargs)
base_model.config.use_cache = False
base_model.config.pad_token_id = processor.pad_token_id
base_model.config.bos_token_id = processor.bos_token_id
base_model.config.eos_token_id = processor.eos_token_id
base_model.generation_config.pad_token_id = processor.pad_token_id
base_model.generation_config.bos_token_id = processor.bos_token_id
base_model.generation_config.eos_token_id = processor.eos_token_id
print(f"Base model loaded with 4-bit={USE_4BIT} and dtype={MODEL_DTYPE}.")
So wird das Basismodell auf das verfügbare Gerät geladen, Caching fürs Training deaktiviert und die Sondertoken-IDs sowohl in der Modell- als auch in der Generation-Konfiguration korrekt gesetzt.
Base model loaded with 4-bit=True and dtype=torch.bfloat16.
5. Basismodell evaluieren
Bevor wir feinabstimmen, lohnt sich eine Evaluierung des Basismodells als klare Vergleichsbasis.
Wir definieren dazu einige Hilfsfunktionen, die Vorhersagen erzeugen, gültige Emotionslabels extrahieren und die Evaluierung auf dem Testsplit durchführen.
Zunächst erstellen wir ein einfaches Pattern zur Labelextraktion und Helfer für Vorhersagen.
Diese Funktionen übernehmen den kompletten Vorhersagefluss. Das Modell erhält den Input im Chatformat, generiert eine kurze Antwort und wir extrahieren das vorhergesagte Label. Falls zusätzlich Text zurückkommt, versucht die Hilfsfunktion, das erste gültige Emotionslabel zu finden.
import re
LABEL_PATTERN = re.compile(r"\b(sadness|joy|love|anger|fear|surprise)\b", re.IGNORECASE)
def extract_label(raw_text: str) -> str:
raw_text = raw_text.strip().lower()
match = LABEL_PATTERN.search(raw_text)
if match:
return match.group(1)
first_token = raw_text.split()[0].strip(".,!?:;\"'()[]{}") if raw_text.split() else ""
return first_token
def generate_label(model, processor, user_text, system_prompt, max_new_tokens=4):
messages = [
{
"role": "system",
"content": system_prompt,
},
{
"role": "user",
"content": f"Classify the emotion of this text:\n\n{user_text}",
},
]
device = next(model.parameters()).device
inputs = processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
).to(device)
input_len = inputs["input_ids"].shape[-1]
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
pad_token_id=processor.pad_token_id,
eos_token_id=processor.eos_token_id,
)
raw_pred = processor.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
return extract_label(raw_pred)
def predict_emotion(user_text: str, model=None, proc=None) -> str:
model = model or base_model
proc = proc or processor
return generate_label(model, proc, user_text, SYSTEM_PROMPT)
Jetzt testen wir das Setup an einem einzelnen Beispiel, bevor wir die volle Evaluierung starten.
predict_emotion("I feel so happy and excited today!")
Die Beispielvorhersage wirkt korrekt, also können wir zur Evaluierung auf dem Testsplit übergehen.
'joy'
Der folgende Code evaluiert das Modell auf dem Testsplit und sammelt mehrere nützliche Ausgaben. Er speichert die wahren und vorhergesagten Labels, markiert korrekte Vorhersagen und liefert zusammenfassende Metriken, einen Klassifikationsbericht und ein DataFrame mit allen Vorhersagen.
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, f1_score
import pandas as pd
from tqdm.auto import tqdm
VALID_LABELS = set(label_names)
ALL_EVAL_LABELS = label_names + ["INVALID"]
def evaluate_model(model, processor, split="test", limit=EVAL_LIMIT):
y_true, y_pred, rows = [], [], []
raw_source = dataset[split]
if limit is not None:
raw_source = raw_source.select(range(min(limit, len(raw_source))))
model.eval()
for ex in tqdm(raw_source, desc=f"Evaluating {split}", leave=False):
true_label = label_names[ex["label"]]
raw_pred_label = generate_label(model, processor, ex["text"], SYSTEM_PROMPT)
pred_label = raw_pred_label if raw_pred_label in VALID_LABELS else "INVALID"
y_true.append(true_label)
y_pred.append(pred_label)
rows.append({
"text": ex["text"],
"true_label": true_label,
"pred_label": pred_label,
"raw_pred_label": raw_pred_label,
"correct": true_label == pred_label,
})
metrics = {
"accuracy": accuracy_score(y_true, y_pred),
"macro_f1": f1_score(y_true, y_pred, labels=label_names, average="macro", zero_division=0),
"invalid_predictions": sum(1 for p in y_pred if p == "INVALID"),
"evaluated_examples": len(y_true),
}
report = classification_report(
y_true,
y_pred,
labels=label_names,
output_dict=True,
zero_division=0,
)
df = pd.DataFrame(rows)
return metrics, report, df
def confusion_matrix_df(pred_df):
return pd.DataFrame(
confusion_matrix(pred_df["true_label"], pred_df["pred_label"], labels=ALL_EVAL_LABELS),
index=ALL_EVAL_LABELS,
columns=ALL_EVAL_LABELS,
)
Jetzt führen wir die vollständige Baseline-Evaluierung auf dem Basismodell aus.
pre_metrics, pre_report, pre_preds = evaluate_model(base_model, processor, "test")
pre_metrics
Diese Baseline-Ergebnisse zeigen, dass das ungetunte Modell schon ordentlich abschneidet, aber noch Luft nach oben hat.
Die Genauigkeit liegt bei etwa 58,25 %, der Macro-F1-Score bei rund 0,42, und es gab 33 ungültige Vorhersagen, d. h. außerhalb des erwarteten Labelsets.
{'accuracy': 0.5825,
'macro_f1': 0.42112912841373906,
'invalid_predictions': 33,
'evaluated_examples': 400}
Als Nächstes sehen wir uns den vollständigen Klassifikationsbericht je Emotionskategorie an.
pd.DataFrame(pre_report).transpose()
Er enthält Precision, Recall, F1-Score und Support je Klasse. So siehst du vor dem Fine-Tuning, welche Emotionen gut funktionieren und wo das Modell Schwierigkeiten hat.

Abschließend betrachten wir die Confusion-Matrix.
confusion_matrix_df(pre_preds)
Die Confusion-Matrix zeigt, wie sich die Vorhersagen über die Klassen verteilen.
Im Notebook erscheint sie als Tabelle. So erkennst du leichter, welche Emotionen verwechselt werden und wo das Basismodell am meisten strauchelt.

6. Gemma 4 mit LoRA feinabstimmen
Mit den Baseline-Ergebnissen können wir Gemma 4 jetzt mit LoRA feinabstimmen.
LoRA ist eine parameter-effiziente Fine-Tuning-Methode. Wir aktualisieren nicht das gesamte Modell, sondern hängen kleine trainierbare Adaptergewichte an. Das macht das Training deutlich leichter und praxistauglich auf einer einzelnen GPU.
Wir beginnen mit der LoRA-Konfiguration.
Diese Einstellungen steuern, wie die LoRA-Adapter am Modell angebracht werden. Wir verwenden Rang 16, Dropout 0,05 und wenden LoRA auf alle Linear-Layer an – ein gängiges Setup für effizientes Fine-Tuning.
from peft import LoraConfig
lora_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
target_modules="all-linear"
)
Als Nächstes definieren wir die Trainingskonfiguration und richten den Trainer ein.
Dieses Setup hält den Speicherverbrauch im Rahmen und lässt dem Modell trotzdem genug Spielraum zum Lernen. Wir trainieren eine Epoche, nutzen Gradientenakkumulation für einen größeren effektiven Batch, und aktivieren Optionen wie Gradient Checkpointing und 8-Bit-Optimierung für mehr Effizienz.
from trl import SFTConfig, SFTTrainer
training_args = SFTConfig(
output_dir="./gemma4-emotion-lora",
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
gradient_accumulation_steps=2,
learning_rate=1e-4,
weight_decay=0.01,
lr_scheduler_type="linear",
warmup_steps=50,
num_train_epochs=1,
logging_steps=50,
eval_strategy="steps",
metric_for_best_model="eval_loss",
greater_is_better=False,
gradient_checkpointing=True,
bf16=True,
fp16=False,
tf32=True,
max_length=256,
packing=False,
completion_only_loss=True,
remove_unused_columns=False,
dataloader_num_workers=2,
optim="paged_adamw_8bit",
report_to="none",
)
Nun stellen wir sicher, dass das Basismodell bereit ist, und initialisieren den Trainer. Dabei werden die LoRA-Adapter angehängt und der SFT-Trainer mit unseren formatierten Trainings- und Validierungssplits vorbereitet.
from peft import PeftModel
if isinstance(base_model, PeftModel):
base_model = base_model.unload()
base_model.config.use_cache = False
trainer = SFTTrainer(
model=base_model,
train_dataset=sft_dataset["train"],
eval_dataset=sft_dataset["validation"],
peft_config=lora_config,
args=training_args,
processing_class=processor,
)
Vor dem Training ist es sinnvoll zu prüfen, ob die LoRA-Parameter korrekt angehängt wurden.
Die folgende Zählung ermittelt die Anzahl trainierbarer Parameter und wirft einen Fehler, falls keine LoRA-Layer vorhanden sind.
Danach startet das Training.
trainable_params = 0
for param in trainer.model.parameters():
if param.requires_grad:
trainable_params += param.numel()
if trainable_params == 0:
raise RuntimeError("No trainable LoRA parameters were attached. Check target_modules before training.")
print(f"Trainable LoRA parameters: {trainable_params:,}")
train_result = trainer.train()
trainer.model.eval()
trainer.model.config.use_cache = True
train_result
In diesem Durchlauf dauerte das Training knapp 9 Minuten. Trainings- und Validierungsverlust sanken fortlaufend – ein gutes Zeichen, dass das Modell aus dem Datensatz lernt.

Nach Abschluss speichern wir Adapter und Tokenizer lokal.
trainer.model.save_pretrained("./gemma4-emotion-lora")
processor.save_pretrained("./gemma4-emotion-lora")
Zum Schluss pushen wir das Modell zur Hugging Face Hub.
Damit lädst du den feinabgestimmten Adapter und den Tokenizer in die Hub hoch, kannst von überall darauf zugreifen, ihn teilen oder direkt in einem anderen Notebook bzw. einer App laden.
repo_id = "kingabzpro/gemma4-emotion-lora"
# Push adapter + processor to the Hub
trainer.model.push_to_hub(
repo_id,
private=False,
)
processor.push_to_hub(
repo_id,
private=False,
)
Du kannst dir kingabzpro/gemma4-emotion-lora jetzt auf Hugging Face ansehen und selbst ausprobieren. Das Repository enthält die Modelldateien, Nutzungshinweise und die Fine-Tuning-Ergebnisse.

Quelle: kingabzpro/gemma4-emotion-lora · Hugging Face
7. Feinabgestimmtes Modell evaluieren
Nach dem Training bewerten wir das feinabgestimmte Modell auf demselben Testsplit und vergleichen die Ergebnisse mit dem Basismodell. So sehen wir, ob die LoRA-Feinabstimmung die Emotionsklassifikation verbessert hat.
Wir laden das feinabgestimmte Modell aus dem Trainer und führen die Evaluierung aus.
ft_model = trainer.model
ft_model.eval()
ft_model.config.use_cache = True
post_metrics, post_report, post_preds = evaluate_model(ft_model, processor, "test")
post_metrics
Das liefert die zentralen Evaluationsmetriken für das feinabgestimmte Modell.
Die Ergebnisse sind klar besser als die Baseline. Nach dem Fine-Tuning erreicht das Modell 77,25 % Genauigkeit und einen Macro-F1-Score von 0,698. Die Zahl ungültiger Vorhersagen sinkt von 33 auf 20 – das Modell ist also nicht nur genauer, sondern gibt auch konstanter gültige Labels zurück.
{'accuracy': 0.7725,
'macro_f1': 0.697702361480462,
'invalid_predictions': 20,
'evaluated_examples': 400}
Als Nächstes sehen wir den vollständigen Klassifikationsbericht.
Dieser wird direkt im Notebook als pandas DataFrame angezeigt. Er enthält Precision, Recall, F1-Score und Support je Emotionsklasse, sodass du leichter siehst, welche Kategorien sich am stärksten verbessert haben.
pd.DataFrame(post_report).transpose()

Auch die Confusion-Matrix wird als Tabelle angezeigt. So erkennst du, wo das feinabgestimmte Modell noch Fehler macht und welche Emotionen am häufigsten verwechselt werden.
confusion_matrix_df(post_preds)

Für einen klaren Vergleich stellen wir die Metriken vor und nach dem Fine-Tuning nebeneinander.
comparison_df = pd.DataFrame([
{"stage": "pre_finetuning", **pre_metrics},
{"stage": "post_finetuning", **post_metrics},
])
comparison_df
Das liefert eine schnelle Übersicht, wie stark sich das Modell nach dem Training verbessert hat.

Hinweis: Wenn beim Ausführen des Codes Probleme auftreten, findest du hier das vollständige Jupyter-Notebook: fine-tune-gemma-4-on-emotions_final.ipynb
Abschließende Gedanken
Das Fine-Tuning von Gemma 4 ist sehr sensibel gegenüber dem Setup – insbesondere Prompt-Struktur und Trainingsargumente. Ist das Prompt-Format falsch oder nutzt du das Template nicht konsequent, kann das Modell trainieren, ohne die Aufgabe wirklich zu lernen. Gleiches gilt für die Trainingseinstellungen. Das sind meist die Hauptgründe, warum der Loss nicht sinkt – oder sinkt, aber die Evaluierung kaum besser wird.
Ein weiterer wichtiger Punkt ist max_length. Wenn du es zu stark reduzierst, vor allem unter etwa 125, lernt das Modell das Muster oft gar nicht. Ich bin auf mehrere Probleme gestoßen, die sich nach und nach lösen ließen – fast immer ging es um Prompt-Formatierung und Trainingskonfiguration.
Für noch bessere Ergebnisse wäre ein guter nächster Schritt, auf dem vollen Datensatz zu feinabstimmen und mindestens 3 Epochen statt nur einer zu trainieren. So bekommt das Modell mehr Beispiele und mehr Zeit zur Anpassung – das sollte Genauigkeit und F1-Score weiter steigern.
Als zertifizierter Data Scientist ist es meine Leidenschaft, modernste Technologien zu nutzen, um innovative Machine Learning-Anwendungen zu entwickeln. Mit meinem fundierten Hintergrund in den Bereichen Spracherkennung, Datenanalyse und Reporting, MLOps, KI und NLP habe ich meine Fähigkeiten bei der Entwicklung intelligenter Systeme verfeinert, die wirklich etwas bewirken können. Neben meinem technischen Fachwissen bin ich auch ein geschickter Kommunikator mit dem Talent, komplexe Konzepte in eine klare und prägnante Sprache zu fassen. Das hat dazu geführt, dass ich ein gefragter Blogger zum Thema Datenwissenschaft geworden bin und meine Erkenntnisse und Erfahrungen mit einer wachsenden Gemeinschaft von Datenexperten teile. Zurzeit konzentriere ich mich auf die Erstellung und Bearbeitung von Inhalten und arbeite mit großen Sprachmodellen, um aussagekräftige und ansprechende Inhalte zu entwickeln, die sowohl Unternehmen als auch Privatpersonen helfen, das Beste aus ihren Daten zu machen.


