File size: 10,476 Bytes
258adb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import json
import argparse
import os
from pathlib import Path
from tqdm import tqdm
import random


def convert_nq_to_invarrag(input_path: str, output_path: str, max_samples: int = None):
    print(f"Converting NQ dataset from {input_path}")
    
    import csv
    data = []
    
    with open(input_path, 'r', encoding='utf-8') as f:
        reader = csv.reader(f, delimiter='\t')
        for idx, row in enumerate(tqdm(reader)):
            if max_samples and idx >= max_samples:
                break
            
            if len(row) >= 2:
                question = row[0]
                answers = eval(row[1])
                
                data.append({
                    'question': question,
                    'answer': answers,
                    'answers': answers,
                    'positive_ctxs': [],
                    'negative_ctxs': [],
                })
    
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    
    print(f"Converted {len(data)} examples to {output_path}")


def convert_triviaqa_to_invarrag(input_path: str, output_path: str, max_samples: int = None):
    print(f"Converting TriviaQA dataset from {input_path}")
    
    import csv
    data = []
    
    with open(input_path, 'r', encoding='utf-8') as f:
        reader = csv.reader(f, delimiter='\t')
        for idx, row in enumerate(tqdm(reader)):
            if max_samples and idx >= max_samples:
                break
            
            if len(row) >= 2:
                question = row[0]
                answers = eval(row[1])
                
                data.append({
                    'question': question,
                    'answer': answers,
                    'answers': answers,
                    'positive_ctxs': [],
                    'negative_ctxs': [],
                })
    
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    
    print(f"Converted {len(data)} examples to {output_path}")


def build_corpus_from_wikipedia(
    wikipedia_path: str,
    output_path: str,
    max_docs: int = 100000,
    doc_length: int = 500
):
    print(f"Building corpus from {wikipedia_path}")
    
    with open(output_path, 'w', encoding='utf-8') as out_f:
        doc_count = 0
        
        with open(wikipedia_path, 'r', encoding='utf-8') as in_f:
            for line in tqdm(in_f):
                if doc_count >= max_docs:
                    break
                
                doc = json.loads(line)
                text = doc.get('text', '')
                
                words = text.split()
                if len(words) > doc_length:
                    text = ' '.join(words[:doc_length])
                
                if len(text) > 100:
                    out_doc = {
                        'id': f"doc_{doc_count}",
                        'text': text,
                        'title': doc.get('title', ''),
                    }
                    out_f.write(json.dumps(out_doc, ensure_ascii=False) + '\n')
                    doc_count += 1
    
    print(f"Built corpus with {doc_count} documents at {output_path}")


def add_negative_samples(
    data_path: str,
    corpus_path: str,
    output_path: str,
    num_negatives: int = 5
):
    print(f"Adding negative samples to {data_path}")
    
    with open(data_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    corpus = []
    with open(corpus_path, 'r', encoding='utf-8') as f:
        for line in f:
            doc = json.loads(line)
            corpus.append(doc)
    
    print(f"Loaded {len(corpus)} documents from corpus")
    
    for item in tqdm(data):
        negative_docs = random.sample(corpus, min(num_negatives, len(corpus)))
        item['negative_ctxs'] = [
            {'text': doc['text'], 'title': doc.get('title', '')}
            for doc in negative_docs
        ]
    
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    
    print(f"Saved augmented data to {output_path}")


def split_dataset(
    input_path: str,
    output_dir: str,
    train_ratio: float = 0.8,
    val_ratio: float = 0.1,
    test_ratio: float = 0.1,
    seed: int = 42
):
    print(f"Splitting dataset {input_path}")
    
    with open(input_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    random.seed(seed)
    random.shuffle(data)
    
    total = len(data)
    train_end = int(total * train_ratio)
    val_end = train_end + int(total * val_ratio)
    
    train_data = data[:train_end]
    val_data = data[train_end:val_end]
    test_data = data[val_end:]
    
    os.makedirs(output_dir, exist_ok=True)
    
    with open(os.path.join(output_dir, 'train.json'), 'w') as f:
        json.dump(train_data, f, indent=2, ensure_ascii=False)
    
    with open(os.path.join(output_dir, 'val.json'), 'w') as f:
        json.dump(val_data, f, indent=2, ensure_ascii=False)
    
    with open(os.path.join(output_dir, 'test.json'), 'w') as f:
        json.dump(test_data, f, indent=2, ensure_ascii=False)
    
    print(f"Split into: train={len(train_data)}, val={len(val_data)}, test={len(test_data)}")


def create_sample_data(output_dir: str, num_samples: int = 100):
    print(f"Creating sample data with {num_samples} examples")
    
    os.makedirs(output_dir, exist_ok=True)
    
    sample_qa = [
        ("What is the capital of France?", ["Paris"]),
        ("Who invented the telephone?", ["Alexander Graham Bell", "Bell"]),
        ("What is the largest planet?", ["Jupiter"]),
        ("Who wrote Romeo and Juliet?", ["William Shakespeare", "Shakespeare"]),
        ("What is the speed of light?", ["299,792,458 m/s", "approximately 300,000 km/s"]),
    ]
    
    sample_docs = [
        "Paris is the capital and most populous city of France.",
        "Alexander Graham Bell was a Scottish-born inventor who is credited with inventing the first practical telephone.",
        "Jupiter is the largest planet in our Solar System.",
        "William Shakespeare wrote Romeo and Juliet in the early years of his career.",
        "The speed of light in vacuum is exactly 299,792,458 metres per second.",
        "London is the capital of England and the United Kingdom.",
        "Thomas Edison was an American inventor who developed many devices.",
    ]
    
    train_data = []
    for i in range(num_samples):
        qa = sample_qa[i % len(sample_qa)]
        train_data.append({
            'question': qa[0],
            'answer': qa[1],
            'answers': qa[1],
            'positive_ctxs': [
                {'text': sample_docs[i % len(sample_docs)]}
            ],
            'negative_ctxs': [
                {'text': sample_docs[(i + j) % len(sample_docs)]}
                for j in range(1, 3)
            ]
        })
    
    with open(os.path.join(output_dir, 'train.json'), 'w') as f:
        json.dump(train_data, f, indent=2, ensure_ascii=False)
    
    val_data = train_data[:max(10, num_samples // 10)]
    with open(os.path.join(output_dir, 'val.json'), 'w') as f:
        json.dump(val_data, f, indent=2, ensure_ascii=False)
    
    with open(os.path.join(output_dir, 'corpus.jsonl'), 'w') as f:
        for i, doc in enumerate(sample_docs):
            f.write(json.dumps({
                'id': f'doc_{i}',
                'text': doc
            }, ensure_ascii=False) + '\n')
    
    print(f"Created sample data at {output_dir}")


def main():
    parser = argparse.ArgumentParser(description="Prepare Invar-RAG training data")
    
    subparsers = parser.add_subparsers(dest='command', help='Command to run')
    
    # Convert NQ
    nq_parser = subparsers.add_parser('convert_nq', help='Convert NQ dataset')
    nq_parser.add_argument('--input', type=str, required=True)
    nq_parser.add_argument('--output', type=str, required=True)
    nq_parser.add_argument('--max_samples', type=int, default=None)
    
    # Convert TriviaQA
    tqa_parser = subparsers.add_parser('convert_triviaqa', help='Convert TriviaQA dataset')
    tqa_parser.add_argument('--input', type=str, required=True)
    tqa_parser.add_argument('--output', type=str, required=True)
    tqa_parser.add_argument('--max_samples', type=int, default=None)
    
    # Build corpus
    corpus_parser = subparsers.add_parser('build_corpus', help='Build document corpus')
    corpus_parser.add_argument('--input', type=str, required=True)
    corpus_parser.add_argument('--output', type=str, required=True)
    corpus_parser.add_argument('--max_docs', type=int, default=100000)
    
    # Add negatives
    neg_parser = subparsers.add_parser('add_negatives', help='Add negative samples')
    neg_parser.add_argument('--data', type=str, required=True)
    neg_parser.add_argument('--corpus', type=str, required=True)
    neg_parser.add_argument('--output', type=str, required=True)
    neg_parser.add_argument('--num_negatives', type=int, default=5)
    
    # Split dataset
    split_parser = subparsers.add_parser('split', help='Split dataset')
    split_parser.add_argument('--input', type=str, required=True)
    split_parser.add_argument('--output_dir', type=str, required=True)
    split_parser.add_argument('--train_ratio', type=float, default=0.8)
    split_parser.add_argument('--val_ratio', type=float, default=0.1)
    
    # Create sample
    sample_parser = subparsers.add_parser('create_sample', help='Create sample data')
    sample_parser.add_argument('--output_dir', type=str, required=True)
    sample_parser.add_argument('--num_samples', type=int, default=100)
    
    args = parser.parse_args()
    
    if args.command == 'convert_nq':
        convert_nq_to_invarrag(args.input, args.output, args.max_samples)
    elif args.command == 'convert_triviaqa':
        convert_triviaqa_to_invarrag(args.input, args.output, args.max_samples)
    elif args.command == 'build_corpus':
        build_corpus_from_wikipedia(args.input, args.output, args.max_docs)
    elif args.command == 'add_negatives':
        add_negative_samples(args.data, args.corpus, args.output, args.num_negatives)
    elif args.command == 'split':
        split_dataset(args.input, args.output_dir, args.train_ratio, args.val_ratio)
    elif args.command == 'create_sample':
        create_sample_data(args.output_dir, args.num_samples)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()