Files
qwen2.5-0.5b-game-commands-stt/create_dataset.py

359 lines
14 KiB
Python
Raw Permalink Normal View History

"""
Generate a synthetic dataset for: Noisy STT text -> Game Command matching.
This creates training data where:
- Input: noisy/garbled speech-to-text transcriptions (as a player would speak into mic)
- Output: JSON with the canonical game command
Based on the approach from arxiv:2502.12923 (synthetic command data + structured JSON output).
"""
import json
import random
import re
from datasets import Dataset, DatasetDict
# =============================================================================
# GAME COMMANDS - Define your game's command vocabulary
# =============================================================================
GAME_COMMANDS = {
"OpenDoor": {
"description": "Open a door",
"canonical_phrases": [
"open the door", "open door", "door open", "open it",
"open that door", "can you open the door", "open the gate",
"unlock and open", "open sesame", "let me through"
]
},
"CloseDoor": {
"description": "Close a door",
"canonical_phrases": [
"close the door", "close door", "shut the door", "shut it",
"close that", "slam the door", "shut that door"
]
},
"Attack": {
"description": "Attack an enemy",
"canonical_phrases": [
"attack", "attack enemy", "hit them", "strike", "fight",
"attack now", "kill it", "go attack", "charge", "swing at them",
"slash", "hit it", "attack that one"
]
},
"Defend": {
"description": "Raise defense/shield",
"canonical_phrases": [
"defend", "block", "raise shield", "shield up", "guard",
"protect me", "defensive stance", "hold block", "shield"
]
},
"Heal": {
"description": "Use healing ability or potion",
"canonical_phrases": [
"heal", "heal me", "use potion", "drink potion", "health potion",
"heal up", "restore health", "use heal", "cast heal", "healing"
]
},
"UseItem": {
"description": "Use an item from inventory",
"canonical_phrases": [
"use item", "use it", "activate item", "use the item",
"consume item", "use that", "equip and use"
]
},
"PickUp": {
"description": "Pick up an item from the ground",
"canonical_phrases": [
"pick up", "pick it up", "grab it", "take it", "collect",
"pick that up", "loot", "grab that", "get it", "take that"
]
},
"DropItem": {
"description": "Drop an item",
"canonical_phrases": [
"drop it", "drop item", "throw away", "discard", "put it down",
"drop that", "leave it", "toss it"
]
},
"Jump": {
"description": "Make character jump",
"canonical_phrases": [
"jump", "jump up", "hop", "leap", "jump over", "jump now",
"double jump", "jump higher"
]
},
"Crouch": {
"description": "Make character crouch/duck",
"canonical_phrases": [
"crouch", "duck", "crouch down", "get down", "hide",
"stay low", "go low", "sneak"
]
},
"Sprint": {
"description": "Start sprinting/running fast",
"canonical_phrases": [
"sprint", "run", "run fast", "go fast", "sprint now",
"faster", "hurry", "rush", "quick run", "speed up"
]
},
"Stop": {
"description": "Stop moving",
"canonical_phrases": [
"stop", "halt", "stay", "don't move", "freeze", "hold",
"wait", "stop moving", "stand still"
]
},
"Reload": {
"description": "Reload weapon",
"canonical_phrases": [
"reload", "reload weapon", "load ammo", "reload gun",
"refill ammo", "reload now", "need ammo"
]
},
"SwitchWeapon": {
"description": "Switch to another weapon",
"canonical_phrases": [
"switch weapon", "change weapon", "next weapon", "swap weapon",
"other weapon", "switch gun", "switch to sword", "equip weapon"
]
},
"CastSpell": {
"description": "Cast a magic spell",
"canonical_phrases": [
"cast spell", "use magic", "fire spell", "cast it",
"magic attack", "spell now", "use ability", "cast fireball"
]
},
"Interact": {
"description": "Interact with an object or NPC",
"canonical_phrases": [
"interact", "talk to them", "use that", "activate",
"press button", "pull lever", "talk", "interact with it"
]
},
"OpenInventory": {
"description": "Open the inventory menu",
"canonical_phrases": [
"open inventory", "inventory", "show inventory", "check inventory",
"open bag", "open backpack", "show items", "my items"
]
},
"OpenMap": {
"description": "Open the map",
"canonical_phrases": [
"open map", "show map", "map", "where am I",
"check map", "look at map", "navigate"
]
},
"SaveGame": {
"description": "Save the game",
"canonical_phrases": [
"save game", "save", "quick save", "save now",
"save progress", "save it"
]
},
"PauseGame": {
"description": "Pause the game",
"canonical_phrases": [
"pause", "pause game", "pause it", "hold on",
"time out", "break", "stop game"
]
},
}
# =============================================================================
# ASR NOISE SIMULATION
# =============================================================================
PHONETIC_NOISE = {
"open": ["opun", "opin", "o pen", "owen", "oben", "oven", "oppn"],
"close": ["cloze", "cloes", "klose", "cloz", "clothe", "clos"],
"door": ["dur", "dor", "doer", "dore", "dour", "tor"],
"attack": ["attak", "a tack", "attac", "atak", "attic", "a tech"],
"enemy": ["enemi", "enamy", "enemy", "emeny", "ememy"],
"defend": ["defnd", "defent", "difend", "de fend", "def end"],
"shield": ["sheeld", "shild", "sheld", "sheild", "chield"],
"heal": ["hel", "heel", "he'll", "heell", "hil", "hale"],
"potion": ["posion", "poshan", "potian", "potion", "poshin"],
"pick": ["pic", "pik", "peek", "prick", "pig"],
"jump": ["jum", "jamp", "jomp", "jup", "gump", "dump"],
"crouch": ["crowch", "krauch", "croush", "krouch", "couch"],
"sprint": ["spint", "sprnt", "sprent", "srint", "print"],
"reload": ["relod", "re load", "ree load", "relode", "reelod"],
"weapon": ["wepon", "weppon", "wapon", "weapn", "weapen"],
"switch": ["swich", "swish", "swithc", "svitch", "stitch"],
"spell": ["spel", "spal", "spall", "spell", "speel"],
"cast": ["kast", "cas", "cass", "karst", "cast"],
"inventory": ["inventery", "inventry", "inventor", "invetory", "inventori"],
"map": ["mop", "nap", "map", "maps", "mab"],
"save": ["sav", "safe", "saev", "sayve", "saf"],
"pause": ["paws", "pouse", "paus", "pawse", "pos"],
"item": ["ithem", "iten", "I tem", "itam", "aitem"],
"grab": ["grap", "grabe", "gab", "garb", "crab"],
"stop": ["stob", "stap", "stopp", "sop", "top"],
"run": ["rn", "runn", "wrun", "ran", "rune"],
"fast": ["fass", "fas", "farst", "fust", "fest"],
"use": ["yuse", "uz", "ewse", "uce", "oose"],
"fire": ["fir", "fyre", "fiar", "fier", "fre"],
"magic": ["majic", "magik", "madgic", "majik", "magc"],
"game": ["gaem", "gam", "gaim", "gme", "dame"],
"talk": ["tok", "tawk", "tolk", "tak", "talc"],
"button": ["buton", "buttn", "baton", "botton", "buttan"],
"lever": ["leaver", "lever", "leva", "levr", "liver"],
"up": ["op", "uhp", "ap", "ub"],
"down": ["don", "doun", "daun", "dawn"],
"the": ["da", "de", "teh", "duh", "tha", ""],
"it": ["et", "ih", "at", "i", "id"],
"that": ["dat", "tat", "thet", "thad"],
"now": ["naw", "nou", "naow", "no"],
}
BG_NOISE_INSERTIONS = ["um", "uh", "hmm", "ah", "like", "you know", "err", "", "", ""]
def apply_formatting_noise(text):
noise_type = random.random()
if noise_type < 0.15:
text = text.lower()
elif noise_type < 0.25:
text = ''.join(c.upper() if random.random() < 0.2 else c.lower() for c in text)
elif noise_type < 0.35:
words = text.split()
if len(words) > 1:
idx = random.randint(0, len(words)-2)
words[idx] = words[idx] + words[idx+1]
del words[idx+1]
text = ' '.join(words)
elif noise_type < 0.4:
text = re.sub(r' ', ' ', text, count=1)
return text
def inject_asr_noise(text, noise_level=0.4):
words = text.lower().split()
result = []
for i, word in enumerate(words):
if random.random() < 0.05 * noise_level:
continue
if word in PHONETIC_NOISE and random.random() < noise_level:
result.append(random.choice(PHONETIC_NOISE[word]))
elif random.random() < noise_level * 0.3 and len(word) > 3:
noise_ops = ['swap', 'delete', 'insert', 'replace']
op = random.choice(noise_ops)
w = list(word)
if op == 'swap' and len(w) > 2:
idx = random.randint(0, len(w)-2)
w[idx], w[idx+1] = w[idx+1], w[idx]
elif op == 'delete' and len(w) > 2:
idx = random.randint(0, len(w)-1)
del w[idx]
elif op == 'insert':
idx = random.randint(0, len(w))
w.insert(idx, random.choice('abcdefghijklmnopqrstuvwxyz'))
elif op == 'replace':
idx = random.randint(0, len(w)-1)
w[idx] = random.choice('abcdefghijklmnopqrstuvwxyz')
result.append(''.join(w))
else:
result.append(word)
if random.random() < 0.08 * noise_level:
filler = random.choice(BG_NOISE_INSERTIONS)
if filler:
result.append(filler)
if random.random() < 0.1 * noise_level and len(result) > 0:
idx = random.randint(0, len(result)-1)
result.insert(idx, result[idx])
text = ' '.join(result)
text = apply_formatting_noise(text)
return text
def generate_variations(phrase, num_variations=5):
variations = []
for _ in range(num_variations):
noise_level = random.uniform(0.2, 0.8)
noisy = inject_asr_noise(phrase, noise_level)
if noisy.strip() and noisy != phrase.lower():
variations.append(noisy)
variations.append(phrase.lower())
variations.append(phrase)
return variations
SYSTEM_PROMPT = """You are a game command interpreter. The user's input is noisy speech-to-text from a microphone in a gaming environment. Your job is to identify the intended game command from the noisy input.
Valid commands: {commands}
Respond with ONLY a JSON object: {{"command": "<CommandName>"}}
If the input doesn't match any command, respond: {{"command": "Unknown"}}"""
def get_system_prompt():
command_list = ", ".join(sorted(GAME_COMMANDS.keys()))
return SYSTEM_PROMPT.format(commands=command_list)
def generate_dataset(num_samples_per_command=100, include_negatives=True):
samples = []
system_msg = get_system_prompt()
for cmd_name, cmd_info in GAME_COMMANDS.items():
for phrase in cmd_info["canonical_phrases"]:
num_vars = max(3, num_samples_per_command // len(cmd_info["canonical_phrases"]))
variations = generate_variations(phrase, num_variations=num_vars)
for noisy_text in variations:
if not noisy_text.strip():
continue
sample = {
"messages": [
{"role": "system", "content": system_msg},
{"role": "user", "content": noisy_text},
{"role": "assistant", "content": json.dumps({"command": cmd_name})}
]
}
samples.append(sample)
if include_negatives:
negative_phrases = [
"what time is it", "hello how are you", "this game is awesome",
"I need to go to the bathroom", "what's the score", "can you hear me",
"testing testing one two three", "hold on let me think", "that was close",
"nice shot dude", "where are we going", "I don't know what to do",
"this is so hard", "let's take a different route", "watch out behind you",
"I think we should go left", "what level are you",
"how much health do you have", "we need more players",
"good game everyone", "random noise blah blah", "shshhshshs",
"mmmhmm yeah", "no no no wait", "okay okay I got it",
]
for phrase in negative_phrases:
for _ in range(8):
noisy = inject_asr_noise(phrase, noise_level=random.uniform(0.1, 0.5))
sample = {
"messages": [
{"role": "system", "content": system_msg},
{"role": "user", "content": noisy},
{"role": "assistant", "content": json.dumps({"command": "Unknown"})}
]
}
samples.append(sample)
random.shuffle(samples)
return samples
if __name__ == "__main__":
random.seed(42)
print("Generating training dataset...")
train_samples = generate_dataset(num_samples_per_command=120)
print(f" Training samples: {len(train_samples)}")
random.seed(123)
print("Generating test dataset...")
test_samples = generate_dataset(num_samples_per_command=20)
print(f" Test samples: {len(test_samples)}")
train_ds = Dataset.from_list(train_samples)
test_ds = Dataset.from_list(test_samples)
dataset_dict = DatasetDict({"train": train_ds, "test": test_ds})
print(f"\nPushing to Hub...")
dataset_dict.push_to_hub("Marco333/game-commands-noisy-stt", private=False)
print("Done!")