summaryrefslogtreecommitdiff
path: root/split_dataset.py
blob: a161cc18c52f7c15634c070517b93f3fedc16040 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import sys
import json
import random
import argparse

def split_dataset(
    input_filepath: str, 
    train_filepath: str = "train.jsonl", 
    validation_filepath: str = "validation.jsonl", 
    split_ratio: float = 0.95,
    balance_ratio: float = 1.0
):
    """
    Reads a .jsonl file, balances the positive/negative examples, shuffles,
    and splits it into training and validation files.
    """
    print(f"Loading data from {input_filepath}...")
    try:
        with open(input_filepath, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    except FileNotFoundError:
        print(f"Error: Input file not found at {input_filepath}", file=sys.stderr)
        sys.exit(1)

    # 1. Separate into positive and negative examples
    positive_examples = []
    negative_examples = []
    for line in lines:
        try:
            entry = json.loads(line)
            if entry.get("target", "").strip():
                positive_examples.append(line)
            else:
                negative_examples.append(line)
        except json.JSONDecodeError:
            print(f"Warning: Skipping malformed JSON line: {line.strip()}", file=sys.stderr)

    print(f"Found {len(positive_examples)} positive examples and {len(negative_examples)} negative examples.")

    # 2. Shuffle both lists independently
    random.shuffle(positive_examples)
    random.shuffle(negative_examples)

    # 3. Balance the dataset based on the ratio
    num_positives = len(positive_examples)
    num_negatives_to_keep = int(num_positives * balance_ratio)

    if len(negative_examples) < num_negatives_to_keep:
        print(f"Warning: Not enough negative examples to meet a {balance_ratio}:1 ratio. "
              f"Using all {len(negative_examples)} negative examples.", file=sys.stderr)
        num_negatives_to_keep = len(negative_examples)

    print(f"Balancing dataset with {num_positives} positive and {num_negatives_to_keep} negative examples.")
    
    balanced_lines = positive_examples + negative_examples[:num_negatives_to_keep]
    
    # 4. Final shuffle to mix positive and negative examples
    print("Shuffling final balanced dataset...")
    random.shuffle(balanced_lines)

    # 5. Determine the split point and write files
    split_index = int(len(balanced_lines) * split_ratio)
    train_lines = balanced_lines[:split_index]
    validation_lines = balanced_lines[split_index:]

    print(f"Writing {len(train_lines)} lines to {train_filepath}...")
    with open(train_filepath, 'w', encoding='utf-8') as f:
        f.writelines(train_lines)

    print(f"Writing {len(validation_lines)} lines to {validation_filepath}...")
    with open(validation_filepath, 'w', encoding='utf-8') as f:
        f.writelines(validation_lines)
    
    print("\nSplitting complete.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Balance and split a .jsonl dataset into training and validation sets."
    )
    parser.add_argument("input_file", type=str, help="Path to the input .jsonl file.")
    parser.add_argument(
        "--split", 
        type=float, 
        default=0.95, 
        help="Ratio for the training set split (e.g., 0.95 for a 95/5 split). Default is 0.95."
    )
    parser.add_argument(
        "--ratio",
        type=float,
        default=1.0,
        help="Ratio of negative to positive examples (e.g., 1.0 for 1:1, 2.0 for 2:1). Default is 1.0."
    )
    
    args = parser.parse_args()
    
    if not 0 < args.split < 1:
        print("Error: Split ratio must be between 0 and 1.", file=sys.stderr)
        sys.exit(1)
        
    split_dataset(args.input_file, split_ratio=args.split, balance_ratio=args.ratio)