合成数据,顾名思义,是通过人工方式生成的、而非从现实场景中采集的数据。这类数据在形态上与真实数据高度相似,同时能有效规避数据隐私问题,还能大幅降低数据采集的成本。借助合成数据,开发者可便捷地开展软件和模型测试,还能模拟产品上线后的运行表现,完成各类实验验证。目前已有 Faker、SDV、SynthCity 等专门用于生成合成数据的库,大语言模型(LLM)也被广泛应用于该场景。但本文将摒弃对这些外部库和 AI 工具的依赖,教你通过编写原生 Python 脚本实现合成数据生成。这种方式能让你更深入地理解数据集的构建逻辑,以及数据偏差和错误的产生原因。我们将从简单的基础脚本入手,掌握核心实现思路后,再过渡到专业库的使用会更加得心应手。合成数据生成的入门操作,可从构建表格型数据开始。例如,若需要一套虚拟的客户数据集用于内部演示,即可通过以下脚本生成 CSV 格式的合成数据:import csv
import random
from datetime import datetime, timedelta
# 设置随机种子,保证结果可复现
random.seed(42)
# 定义可选的国家和会员套餐类型
countries = ["加拿大", "英国", "阿联酋", "德国", "美国"]
plans = ["免费版", "基础版", "专业版", "企业版"]
# 生成随机的注册日期
defrandom_signup_date():
start = datetime(2024, 1, 1)
end = datetime(2026, 1, 1)
delta_days = (end - start).days
return (start + timedelta(days=random.randint(0, delta_days))).date().isoformat()
rows = []
# 生成1000条客户数据
for i in range(1, 1001):
age = random.randint(18, 70) # 随机生成18-70岁的年龄
country = random.choice(countries) # 随机选择国家
plan = random.choice(plans) # 随机选择套餐
monthly_spend = round(random.uniform(0, 500), 2) # 随机生成月消费额,保留2位小数
rows.append({
"客户编号": f"CUST{i:05d}",
"年龄": age,
"国家": country,
"会员套餐": plan,
"月消费额(元)": monthly_spend,
"注册日期": random_signup_date()
})
# 将数据写入CSV文件
with open("customers.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader() # 写入表头
writer.writerows(rows) # 写入数据行
print("客户数据已保存至customers.csv")
customers.csv 文件包含 1000 条客户数据,前 10 条数据如下:该脚本的实现逻辑十分简洁:定义数据字段、指定各字段的取值范围,随后循环生成数据行并写入文件。其中, `random` 模块可实现整数、浮点数的随机生成,以及随机选择、抽样等操作; `csv` 模块则专门用于读写行式的表格数据。但这种方法存在一个核心缺陷:所有字段的取值均为完全随机,导致生成的数据显得生硬、不符合现实规律。比如企业版用户的月消费可能仅为 2 元,而免费版用户的月消费却高达 400 元;不同年龄段的用户行为特征完全一致,因为数据背后没有建立任何逻辑关联。在实际业务中,数据往往存在明显的内在规律。因此,我们可以为数据生成添加字段间的关联规则,让合成数据更贴合现实,具体可做如下优化:import csv
import random
random.seed(42)
plans = ["免费版", "基础版", "专业版", "企业版"]
# 按权重随机选择套餐(模拟真实的用户占比)
defchoose_plan():
roll = random.random()
if roll < 0.45: # 45%的用户选择免费版
return"免费版"
if roll < 0.75: # 30%的用户选择基础版
return"基础版"
if roll < 0.93: # 18%的用户选择专业版
return"专业版"
return"企业版"# 7%的用户选择企业版
# 根据年龄和套餐生成合理的消费额
defgenerate_spend(age, plan):
if plan == "免费版":
base = random.uniform(0, 10)
elif plan == "基础版":
base = random.uniform(10, 60)
elif plan == "专业版":
base = random.uniform(50, 180)
else:
base = random.uniform(150, 500)
# 40岁及以上用户,消费额上浮15%
if age >= 40:
base *= 1.15
return round(base, 2)
rows = []
for i in range(1, 1001):
age = random.randint(18, 70)
plan = choose_plan()
spend = generate_spend(age, plan)
rows.append({
"客户编号": f"CUST{i:05d}",
"年龄": age,
"会员套餐": plan,
"月消费额(元)": spend
})
with open("controlled_customers.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print("带逻辑控制的客户数据已保存至controlled_customers.csv")
controlled_customers.csv 文件前 10 条数据如下:优化后的数据集具备了符合现实的业务规律,不再是无意义的随机数据,而是对用户行为的合理模拟。实现这类数据控制的核心技巧包括:基于流程模拟的方式,是生成高逼真度合成数据的最优方案之一。这种方式并非直接为各字段赋值,而是通过模拟真实的业务流程,让数据成为流程运行的自然产物。例如,模拟小型仓库的订单处理流程:订单下达→库存减少→库存低于阈值触发补货。import csv
import random
from datetime import datetime, timedelta
random.seed(42)
# 初始化仓库库存
inventory = {
"商品A": 120,
"商品B": 80,
"商品C": 50
}
rows = []
current_time = datetime(2026, 1, 1) # 模拟起始时间
# 模拟30天的仓库操作
for day in range(30):
for product in inventory:
# 生成当日该商品的订单数量
daily_orders = random.randint(0, 12)
# 处理每个订单
for _ in range(daily_orders):
qty = random.randint(1, 5) # 每个订单的购买数量
before = inventory[product] # 下单前库存
# 判断库存是否充足,完成发货或生成待发货订单
if inventory[product] >= qty:
inventory[product] -= qty
status = "已发货"
else:
status = "待发货"
rows.append({
"操作时间": current_time.isoformat(),
"商品名称": product,
"数量": qty,
"操作前库存": before,
"操作后库存": inventory[product],
"操作状态": status
})
# 库存低于20时触发补货
if inventory[product] < 20:
restock = random.randint(30, 80) # 补货数量
inventory[product] += restock
rows.append({
"操作时间": current_time.isoformat(),
"商品名称": product,
"数量": restock,
"操作前库存": inventory[product] - restock,
"操作后库存": inventory[product],
"操作状态": "补货"
})
# 时间推进一天
current_time += timedelta(days=1)
# 将模拟数据写入CSV文件
with open("warehouse_sim.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print("仓库流程模拟数据已保存至warehouse_sim.csv")
warehouse_sim.csv 文件包含 557 条仓库操作数据,前 10 条如下:这种生成方式的优势在于,数据是系统行为的产物,其字段间的关联关系远比直接随机生成更贴合现实。除仓库管理外,还可将该思路应用于以下业务流程的模拟:合成数据并非仅局限于静态表格,实际业务中很多系统都会产生时序数据,例如 APP 访问量、传感器采集数据、每小时订单量、服务器响应时间等。以下脚本实现了带有工作日/周末规律的网站小时级访问量时序数据生成:import csv
import random
from datetime import datetime, timedelta
random.seed(42)
# 时序数据起始时间:2026年1月1日0点
start = datetime(2026, 1, 1, 0, 0, 0)
# 生成30天的小时级数据,共720个时间点
hours = 24 * 30
rows = []
for i inrange(hours):
ts = start + timedelta(hours=i) # 生成当前时间戳
weekday = ts.weekday() # 判断是否为工作日(0-4为工作日,5-6为周末)
# 基础访问量:工作日120,周末80
base = 120
if weekday >= 5:
base = 80
# 按时段调整基础访问量
hour = ts.hour
if8 <= hour <= 11: # 早高峰8-11点,访问量+60
base += 60
elif 18 <= hour <= 21: # 晚高峰18-21点,访问量+40
base += 40
elif 0 <= hour <= 5: # 凌晨0-5点,访问量-30
base -= 30
# 基于高斯分布生成最终访问量,保证非负
visits = max(0, int(random.gauss(base, 15)))
rows.append({
"时间戳": ts.isoformat(),
"访问量": visits
})
# 写入CSV文件
with open("traffic_timeseries.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["时间戳", "访问量"])
writer.writeheader()
writer.writerows(rows)
print("网站访问量时序数据已保存至traffic_timeseries.csv")
traffic_timeseries.csv 文件包含 720 条时序数据,前 10 条如下:该实现思路的优势在于,在保证脚本易理解、易调试的前提下,融入了时序数据的核心特征:趋势性、随机性和周期性。事件日志是另一类实用的合成数据形式,适用于产品数据分析和工作流测试。与“一行数据对应一个用户”的静态表格不同,事件日志采用“一行数据对应一个行为”的形式,记录用户的全流程操作。import csv
import random
from datetime import datetime, timedelta
random.seed(42)
# 定义用户可能的行为事件
events = ["注册", "登录", "浏览页面", "加入购物车", "下单购买", "登出"]
rows = []
start = datetime(2026, 1, 1) # 日志起始时间
# 生成200个用户的行为日志
for user_id inrange(1, 201):
# 每个用户生成5-30个行为事件
event_count = random.randint(5, 30)
# 随机生成用户的首次行为时间(起始时间后0-10天)
current_time = start + timedelta(days=random.randint(0, 10))
for _ inrange(event_count):
event= random.choice(events)
# 下单购买事件:60%概率生成消费金额,其余为0
ifevent == "下单购买" and random.random() < 0.6:
value = round(random.uniform(10, 300), 2)
else:
value = 0.0
rows.append({
"用户编号": f"USER{user_id:04d}",
"事件时间": current_time.isoformat(),
"事件名称": event,
"事件金额(元)": value
})
# 行为事件的时间间隔:1-180分钟
current_time += timedelta(minutes=random.randint(1, 180))
# 写入CSV文件
with open("event_log.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print("用户行为事件日志已保存至event_log.csv")
event_log.csv 文件包含 3338 条行为日志,前 10 条如下:优化事件日志逼真度的一个实用技巧:让事件之间存在先后依赖关系。例如,“下单购买”事件通常应跟随“登录”或“浏览页面”事件,而非独立出现。合成数据在自然语言处理(NLP)领域也具有重要价值,并非所有场景都需要依赖大语言模型,通过 模板+可控变体 的方式,即可快速构建有效的文本数据集。例如,生成客服工单的训练数据:import json
import random
random.seed(42)
# 定义工单问题类型与基础话术
issues = [
("账单问题", "我的会员订阅被重复扣费了"),
("登录问题", "我无法登录自己的账号"),
("物流问题", "我的订单至今还未送达"),
("退款问题", "我想申请退款"),
]
# 定义工单的语气前缀
tones = ["麻烦帮忙处理", "此事十分紧急", "能否帮忙核查一下", "我需要技术支持"]
records = []
# 生成100条客服工单数据
for _ in range(100):
label, message = random.choice(issues)
tone = random.choice(tones)
# 拼接生成完整的工单文本
text = f"{tone}。{message}。"
records.append({
"工单文本": text,
"问题标签": label
})
# 将数据写入JSONL文件(NLP任务常用格式)
with open("support_tickets.jsonl", "w", encoding="utf-8") as f:
for item in records:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
print("客服工单文本数据已保存至support_tickets.jsonl")
support_tickets.jsonl 文件为每行一个 JSON 对象的格式,前 14 条如下:- `{"工单文本": "麻烦帮忙处理。我的会员订阅被重复扣费了。", "问题标签": "账单问题"}`
- `{"工单文本": "此事十分紧急。我的订单至今还未送达。", "问题标签": "物流问题"}`
- `{"工单文本": "此事十分紧急。我无法登录自己的账号。", "问题标签": "登录问题"}`
- `{"工单文本": "麻烦帮忙处理。我的会员订阅被重复扣费了。", "问题标签": "账单问题"}`
- `{"工单文本": "麻烦帮忙处理。我想申请退款。", "问题标签": "退款问题"}`
- `{"工单文本": "麻烦帮忙处理。我的会员订阅被重复扣费了。", "问题标签": "账单问题"}`
- `{"工单文本": "此事十分紧急。我无法登录自己的账号。", "问题标签": "登录问题"}`
- `{"工单文本": "此事十分紧急。我的会员订阅被重复扣费了。", "问题标签": "账单问题"}`
- `{"工单文本": "此事十分紧急。我想申请退款。", "问题标签": "退款问题"}`
- `{"工单文本": "能否帮忙核查一下。我想申请退款。", "问题标签": "退款问题"}`
- `{"工单文本": "此事十分紧急。我的会员订阅被重复扣费了。", "问题标签": "账单问题"}`
- `{"工单文本": "能否帮忙核查一下。我想申请退款。", "问题标签": "退款问题"}`
- `{"工单文本": "此事十分紧急。我的订单至今还未送达。", "问题标签": "物流问题"}`
- `{"工单文本": "能否帮忙核查一下。我无法登录自己的账号。", "问题标签": "登录问题"}`
这种模板化的文本生成方式适用于以下 NLP 场景:合成数据生成脚本是高效的开发工具,但如果使用不当,反而会生成无效数据。请务必规避以下常见错误:- 生成违背业务逻辑的数值(如负的库存、超范围的年龄)
- 生成的数据过于“干净”,缺少真实场景中的边缘案例和异常值
- 过度复用单一生成模式,导致数据集缺乏随机性、易被预测
隐私保护 是合成数据生成的核心考量因素:尽管合成数据能降低真实数据的暴露风险,但并非绝对安全。如果生成器的设计与原始敏感数据过度关联,仍可能发生数据泄露。因此,采用 差分隐私 等隐私保护技术来生成合成数据,是当前的重要发展方向。