Slippi-AI (Phillip II)

repository·main·Indexed 40 days ago

https://github.com/vladfi1/slippi-ai

A Super Smash Bros. Melee AI project that utilizes a two-stage training process: behavioral cloning (imitation learning) from Slippi replays followed by Reinforcement Learning (RL) to refine playstyle. The project includes tools for preprocessing .slp files, training agents via scripts/train.py, and evaluating models using scripts/eval_two.py and scripts/run_evaluator.py.

Tokens
12.1K
Snippets
37
Records
42
Agent score
46%

What's inside Slippi-AI

  1. Create a dataset from Slippi replays

    main

    Before training, you must preprocess Slippi replays into a usable format using slippi_db/parse_local.py.

    This process generates:

    1. A Parsed directory containing the preprocessed games.
    2. A meta.json metadata file.

    Refer to the documentation within slippi_db/parse_local.py for specific parsing arguments.

  2. Evaluate trained agents

    main

    Use the following scripts to evaluate performance:

    • Visual Evaluation: Use scripts/eval_two.py to play against a trained agent or to watch two trained agents play against each other in real-time.
    • Automated Evaluation: Use scripts/run_evaluator.py to perform a full, automated evaluation of two agents competing against each other.
  3. Train using Imitation Learning

    main

    Imitation learning trains the agent to mimic human play from a preprocessed dataset. The entry point is scripts/train.py.

    Training Details:

    • Monitoring: Metrics are logged to Weights & Biases (wandb). Set the WANDB_API_KEY environment variable to use your own account.
    • Stopping Criteria: Monitor eval.policy.loss. Once this metric plateaus, training can be stopped.
    • Output: Agent checkpoints are periodically saved to experiments/<tag>/latest.pkl.
    • Duration: On a high-end GPU (e.g., 3080Ti), this typically takes a few days to a week.

    For a reference implementation of arguments, see scripts/imitation_example.sh.

  4. Train using Reinforcement Learning

    main

    Reinforcement Learning (RL) is used to refine the imitation policy by having agents play against themselves. There are two main entry points:

    1. slippi_ai/rl/run.py: Used for training a single agent in a 'ditto' (playing against a copy of itself).
    2. slippi_ai/rl/train_two.py: Used to train two different agents simultaneously.

    Refer to scripts/rl_example.sh for a sample training script configuration.

  5. Install and Setup Slippi-AI for Local Play

    main

    To run the bot locally, clone the repository and install the dependencies with TensorFlow support.

    Prerequisites:

    • Python 3.12 or 3.13.
    • Windows Users: You must enable long paths for pip to work correctly. Ensure no other Dolphin instances are running, as this prevents the bot from sending inputs.

    Installation:

    git clone <repository-url>
    cd slippi-ai
    pip install -e ".[tf]"
    pip install -e ".[tf]"
  6. Play against the AI locally using eval_two.py

    main

    Use scripts/eval_two.py to play against a trained model. By default, the script assumes the human player uses a Wii-U controller adapter.

    Key Options:

    • --p1.type <type>: Set to human to play as Player 1.
    • --p2.ai.path <path>: Path to the trained model file.
    • --p2.character <character>: Specify the character for the AI (e.g., fox, falco, marth).
    • --dolphin.copy_home_directory: Use your own Dolphin configuration and controller settings instead of the default Wii-U setup.
    • --dolphin.iso <path>: Manually specify the Melee ISO if auto-detection fails.
    • --dolphin.path <path>: Manually specify the Slippi Dolphin executable path if auto-detection fails.

    Example Command:

    python scripts/eval_two.py --p1.type human --p2.ai.path <path/to/trained/model> --p2.character fox
    python scripts/eval_two.py --p1.type human --p2.ai.path <path/to/trained/model> [--dolphin.copy_home_directory]
  7. Convert Slippi ordinals to OpenSkill ratings

    main

    To use player ratings in an openskill model (like BradleyTerryFull), you must convert Slippi's ordinal rating system to a Mu/Sigma representation.

    The conversion uses the following constants:

    • ORDINAL_SCALING = 25.0
    • ORDINAL_OFFSET = 1100.0
    • SLIPPI_SIGMA = 3

    Conversion Formulas:

    • Rating to Slippi (Ordinal): ORDINAL_SCALING * (mu - 3*sigma) + ORDINAL_OFFSET
    • Ordinal to Rating (Mu/Sigma): mu = (ordinal - ORDINAL_OFFSET) / ORDINAL_SCALING + 3 * sigma
    # Example conversion logic
    def rating_to_slippi(rating):
        return 25.0 * (rating.mu - 3*rating.sigma) + 1100.0
    
    def rating_from_slippi(ordinal, sigma=3):
        mu = (ordinal - 1100.0) / 25.0 + 3 * sigma
        return [mu, sigma]
  8. Analyze per-character evaluation losses from Weights & Biases

    main

    This notebook demonstrates how to extract, calculate, and visualize character-specific evaluation losses from W&B runs.

    Key steps include:

    1. Retrieve Runs: Use wandb.Api().runs(path, filters=...) to fetch runs, potentially filtered by a group (e.g., 'imitation').
    2. Identify Characters: Extract the list of allowed characters from the run configuration using the dataset.allowed_characters key.
    3. Extract Metrics: Access character-specific metrics using the following key patterns:
      • Losses: eval_characters.losses.<char>
      • Counts: eval_characters.counts.<char>
    4. Calculate Mean Loss: Since W&B stores cumulative values, calculate the mean loss per character as: eval_characters.losses.<char> / eval_characters.counts.<char>.
    5. Visualize: Use plotnine (ggplot style) to plot eval_loss against step for multiple characters.
    # Example: Calculating mean loss for characters
    _losses_key = lambda char: 'eval_characters.losses.' + char
    _counts_key = lambda char: 'eval_characters.counts.' + char
    
    # Assuming 'df' is a pandas DataFrame from run.history()
    for char in chars:
        df[f'{char}.mean'] = df[_losses_key(char)] / df[_counts_key(char)]
    
    # Plotting with plotnine
    (gg.ggplot(to_plot)
     + gg.aes(x="step", y="eval_loss", group="char", color="char")
     + gg.geom_line())
  9. Load and inspect a game using peppi_py

    main

    Use peppi_py.game() to load a Slippi (.slp) file into a structured object. You can optionally enable rollbacks=True to handle frame data with rollback-aware logic. The resulting object allows access to game metadata, frame data, and timing information like start and end frames.

    import peppi_py
    
    test_file = '../data/test/Game_20211113T124748.slp'
    # Load game with rollback support
    peppi_py.game(test_file, rollbacks=True)
    
    # Standard load
    game = peppi_py.game(test_file)
    
    # Accessing metadata and timing
    metadata = game['metadata']
    start_frame = game['start']
    end_frame = game['end']
  10. Analyze parsed replay data with pandas

    main

    To analyze a dataset of parsed replays, load the pickled data into memory and convert it into a pandas DataFrame. This allows for filtering by validity, training suitability, and player attributes.

    Common analysis tasks include:

    • Filtering valid training data: Use df['valid'] and df['is_training'] to isolate high-quality samples.
    • Checking for winners: Use df['winner'].notnull() to ensure a match outcome is recorded.
    • Grouping by source: Use a mapping function to create a compact_raw column to group replays by their original dataset/folder (e.g., 'Zain', 'Phillip').
    import pandas as pd
    import pickle
    import os
    
    # Load parsed data
    root = '/path/to/replays/'
    parsed_path = os.path.join(root, 'parsed.pkl')
    
    with open(parsed_path, 'rb') as f:
        data_bytes = f.read()
    
    data = pickle.loads(data_bytes)
    df = pd.DataFrame(data)
    
    # Filter for valid training data
    training = df[df['valid'] & (df['is_training'] == True)]
  11. Inspect and filter parsed replay data using pandas

    main

    The project uses a pickled database (parsed.pkl) containing replay information. You can load this into a pandas DataFrame to inspect the validity of replays and identify specific error reasons.

    Common workflow:

    1. Load the pickle file using pickle.load().
    2. Convert the database object into a pd.DataFrame.
    3. Filter for invalid replays using the valid column.
    4. Analyze the reason column to find specific issues (e.g., replays where a specific function was 'called').
    import pickle
    import os
    import pandas as pd
    
    root = '/path/to/replays/'
    with open(os.path.join(root, 'parsed.pkl'), 'rb') as f:
        db = pickle.load(f)
    
    df = pd.DataFrame(db)
    
    # Filter for invalid replays
    invalid = df[df['valid'] == False]
    
    # Count occurrences of error reasons
    print(invalid['reason'].value_counts())
    
    # Filter for specific reasons starting with 'called'
    called = invalid[invalid['reason'].map(lambda s: s.startswith('called'))]
  12. Extract and filter Phillip AI replay datasets

    main

    This notebook demonstrates how to process a collection of Super Smash Bros. Melee replays to create a specialized dataset of matches played against 'Phillip' (the Slippi-AI bot).

    Key steps include:

    1. Loading a pre-parsed pickle file containing replay metadata.
    2. Normalizing player names using slippi_ai.nametags.
    3. Converting character IDs to human-readable strings using melee.Character.
    4. Filtering for valid training replays using check_replay from slippi_db.scripts.make_local_dataset.
    5. Grouping matches by matchup (character vs opponent character) to select a representative sample (e.g., the last 5 matches per matchup).
    6. Compiling and renaming the selected files into a structured ZIP archive for training.
    import pandas as pd
    from slippi_ai import nametags
    from slippi_db import utils
    from slippi_db.scripts.make_local_dataset import check_replay
    
    # Example logic for filtering and preparing a matchup dataset
    # 1. Load data
    df = pd.DataFrame(data)
    
    # 2. Filter for Phillip matches
    phillip_df = df[df['compact_raw'] == 'Phillip']
    
    # 3. Filter for valid training data
    training_phil_df = vs_phil_df[vs_phil_df['not_training_reason'].isnull()].copy()
    
    # 4. Select sample matchups
    matchup_df = training_phil_df.groupby(['char', 'opponent_char']).tail(5).reset_index()