Synthetic Data Generation with LLMs: A Practical Guide
Language models can produce training examples on demand, which is tempting when real data is scarce. This guide generates customer messages for five banking intents with a local model, compares them with real ones, and trains the same classifier on synthetic, real and mixed data, tested on 200 real messages.
GloryTecks Team
Training & Placement Faculty
Last updated: 3 October 2026
Labelled data is the slow, expensive part of most machine learning projects. A language model can write a thousand example customer messages in minutes, labelled by construction. That makes synthetic data one of the most tempting uses of generative AI, and one of the easiest to get wrong, because generated data can look convincing and still teach the wrong thing.
The only way to know is to measure. This guide generates synthetic examples, compares them with real ones, and tests what each is worth on real data.
The idea, and its history
Using models to create training data is not new. Self-Instruct showed in 2022 that a model could generate instructions and answers, filter out invalid and near-duplicate ones, and use the rest to improve its own instruction following (Wang et al.: Self-Instruct). Today synthetic data is used for classifiers, evaluation sets, fine-tuning and testing.
There is also a warning in the literature. Training models on model-generated content, generation after generation, causes lasting defects: the rare cases at the edges of the real distribution disappear (Shumailov et al.: The Curse of Recursion). Synthetic data tends to be typical. Real data is full of the untypical.
An experiment
The task is routing customer messages for five intents from the BANKING77 dataset, released under CC BY 4.0 (Hugging Face: PolyAI/banking77): card arrival, lost or stolen card, exchange rate, cancelling a transfer, and the account age limit.
The script asks a small local model for 20 varied messages per intent, compares them with 20 real messages per intent, and then trains the same simple classifier three ways. All three are tested on the same 200 real test messages.
import json
import urllib.request
import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer
INTENTS = {
"card_arrival": "the customer's new card has not arrived yet",
"lost_or_stolen_card": "the customer has lost their card or it was stolen",
"exchange_rate": "the customer asks about the exchange rate applied to a payment or transfer",
"cancel_transfer": "the customer wants to cancel a transfer they made",
"age_limit": "the customer asks how old you must be to open an account",
}
names = list(INTENTS)
train = pd.read_csv("banking77/train.csv").query("category in @names")
test = pd.read_csv("banking77/test.csv").query("category in @names")
real = train.groupby("category").head(20) # 20 real messages per intent, for a fair comparison
SCHEMA = {"type": "object", "required": ["messages"],
"properties": {"messages": {"type": "array", "items": {"type": "string"}}}}
def generate(description, n=20):
prompt = (f"Write {n} different messages a bank customer might type into a support chat when {description}. "
"Vary the wording, length, tone and detail; some short and informal, some with typos. "
"Return JSON with a list called messages.")
body = {"model": "llama3.2", "stream": False, "format": SCHEMA,
"options": {"temperature": 0.9, "seed": 42, "num_predict": 1200},
"messages": [{"role": "user", "content": prompt}]}
req = urllib.request.Request("http://localhost:11434/api/chat", json.dumps(body).encode(),
{"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=900) as r:
return json.loads(json.load(r)["message"]["content"])["messages"][:n]
synthetic = pd.DataFrame([{"text": m, "category": intent}
for intent, description in INTENTS.items() for m in generate(description)])
print(f"generated {len(synthetic)} messages for {len(INTENTS)} intents")
for intent in INTENTS:
print(f" {intent:<20} e.g. {synthetic.query('category == @intent').text.iloc[0][:70]!r}")
def distinct_bigrams(texts):
pairs = [tuple(w[i:i + 2]) for t in texts for w in [t.lower().split()] for i in range(len(w) - 1)]
return len(set(pairs)) / max(len(pairs), 1)
print(f"\n{'':10} {'unique messages':>16} {'distinct word pairs':>20} {'average words':>14}")
for name, df in (("real", real), ("synthetic", synthetic)):
print(f"{name:<10} {df.text.nunique() / len(df):>16.0%} {distinct_bigrams(df.text):>20.0%} "
f"{df.text.str.split().str.len().mean():>14.1f}")
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
encode = lambda texts: model.encode(list(texts), normalize_embeddings=True, batch_size=64)
test_vecs = encode(test.text)
def accuracy(examples):
vecs = encode(examples.text)
names = sorted(INTENTS)
centroids = np.stack([vecs[(examples.category == n).to_numpy()].mean(axis=0) for n in names])
predicted = np.array(names)[(test_vecs @ centroids.T).argmax(axis=1)]
return (predicted == test.category.to_numpy()).mean()
print(f"\nrouting {len(test)} real test messages, trained on:")
print(f" 20 real messages per intent {accuracy(real):.1%}")
print(f" 20 synthetic messages per intent {accuracy(synthetic):.1%}")
print(f" both together {accuracy(pd.concat([real, synthetic])):.1%}")
# How close does each synthetic message come to a real one? Near-copies would add little.
nearest = (encode(synthetic.text) @ encode(train.text).T).max(axis=1)
print(f"\nsynthetic messages with a near-identical real message (similarity above 0.95): {(nearest > 0.95).sum()}")
print(f"median similarity to the closest real message: {np.median(nearest):.2f}")generated 95 messages for 5 intents
card_arrival e.g. 'hey i didnt recieve my new card yet'
lost_or_stolen_card e.g. 'Lost my card'
exchange_rate e.g. 'hey whats the ex rate on this transfrence?'
cancel_transfer e.g. 'Hi, I want to cancel my tranfer'
age_limit e.g. 'hey whats the min age for a bank acount?'
unique messages distinct word pairs average words
real 100% 68% 10.5
synthetic 98% 63% 9.8
routing 200 real test messages, trained on:
20 real messages per intent 99.0%
20 synthetic messages per intent 89.5%
both together 93.5%
synthetic messages with a near-identical real message (similarity above 0.95): 3
median similarity to the closest real message: 0.78What the output shows
- The generated messages looked realistic. They were informal and brief and even had deliberate typos, as asked. The model produced 95 of the 100 messages requested.
- On surface measures they were close to real data. 98% were unique against 100% for real messages, with slightly fewer distinct word pairs and similar length.
- They were worth less as training data. Trained on 20 real messages per intent, the classifier got 99.0% of real test messages right. Trained on 20 synthetic ones, 89.5%.
- Mixing them in made things worse. Real plus synthetic scored 93.5%, below real data alone. The synthetic examples pulled each intent's average towards the generator's idea of the intent and away from how customers actually write.
- A few were near-copies of real messages. Three were almost identical to a message in the training set, and the median closest real message had a similarity of 0.78.
These are small numbers from one generator, but the pattern is a common one and cheap to check on your own task. Read together: synthetic data built a usable classifier from nothing, 89.5% with zero real examples. Where real examples exist, it was no substitute for them here, and adding it hurt.
Making synthetic data useful
- Use it where real data is missing, for new intents, rare cases, or a first version you will replace.
- Ask for variety explicitly: different lengths, tones, errors and phrasings, and generate in several batches with different instructions.
- Filter it. Remove duplicates, off-label examples and near-copies, as Self-Instruct did.
- Always test on real data. The 89.5% above is only known because a real test set existed.
- Check closeness to real records before using synthetic data as a privacy measure. Generated text can reproduce real phrasing.
- Keep it labelled as synthetic in your datasets, so it is never mistaken for real evidence or fed back into future generators unknowingly.
Licences and terms
The generator's terms travel with its output. The Llama 3.2 licence, for example, says that anyone who uses Llama outputs to create, train or improve an AI model that is distributed or made available must put "Llama" at the start of that model's name (Meta: Llama 3.2 Community License). Hosted model providers have their own terms about using outputs to train other models. Read them before building a dataset you plan to use for training.
Where it fits
Synthetic data is especially handy for evaluation, generating test questions and adversarial inputs at scale. See evaluating LLM outputs. For fine-tuning, quality matters more than quantity. See LoRA and QLoRA and fine-tuning vs RAG. For the routing task itself, see building a customer support bot.
Common mistakes
- Testing on synthetic data and believing the score.
- Assuming more is better. Here, adding synthetic examples lowered accuracy.
- One prompt, one style: generated data clusters around the generator's habits.
- Ignoring the generator's licence and terms.
- Treating synthetic as automatically private.
- Training generations of models on each other's output.
What is synthetic data generation with LLMs?
Is synthetic data as good as real data?
How do I check the quality of synthetic data?
Can I train a model on outputs from ChatGPT or Llama?
Fine-tuning, embeddings and evaluation, the places synthetic data is used most, are covered in the Generative AI course.
Sources
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Generative AI?
Join GloryTecks’ expert-led Generative AI training in Hyderabad with real-time projects and 100% placement support.
Speech-to-Text and Text-to-Speech with Modern AI
NextCost Optimization for LLM Applications in Production
Related articles
How Large Language Models (LLMs) Work Under the Hood
A large language model does one thing: given some text, it scores every possible next token. Everything else, from chat to code, is that step repeated. This guide follows the step with real numbers from GPT-2 running on a laptop, then explains training, temperature and why fluent text is not always true.
What Is Generative AI? A Complete Beginner's Guide
What generative AI is, in plain language: how language models produce text one token at a time (shown with a tiny runnable model), how chat models are trained, how image models differ, what the technology is reliable for, and why it confabulates.
ChatGPT Complete Guide: Features, Prompts and Use Cases
ChatGPT went from a research preview to one of the most used software products in the world in under four years. This guide maps its main features and when they arrived, shows how to write prompts that work, sorts good uses from risky ones, and explains the limits every user should check for.