Files
2026-07-13 13:24:13 +08:00

123 lines
5.2 KiB
Python

import os
import json
import argparse
import sys
import numpy as np
sys.path.insert(0, 'src/')
from tqdm import tqdm
from typing import Dict, Any
from logger_config import logger
from data_utils import load_query_answers, load_corpus, save_to_readable_format
parser = argparse.ArgumentParser(description='data preprocessing for NQ & TriviaQA in DPR paper')
parser.add_argument('--out-dir', default='./data/dpr/', type=str, metavar='N',
help='output directory')
parser.add_argument('--task', default='nq', type=str, metavar='N',
help='task name, nq or tq')
parser.add_argument('--train-pred-path', default='amlt/0621_cont_100k_psg16_ft/nq/nq_train.dpr.json',
type=str, metavar='N', help='path to train predictions to construct negatives')
parser.add_argument('--dev-pred-path', default='amlt/0621_cont_100k_psg16_ft/nq/nq_dev.dpr.json',
type=str, metavar='N', help='path to dev predictions to construct negatives')
parser.add_argument('--num-negatives', default=100, type=int, metavar='N',
help='number of negative passages')
parser.add_argument('--depth', default=100, type=int, metavar='N',
help='depth to choose negative passages from')
args = parser.parse_args()
os.makedirs(args.out_dir, exist_ok=True)
logger.info('Args: {}'.format(json.dumps(args.__dict__, ensure_ascii=False, indent=4)))
assert args.task in ['nq', 'tq']
def _load_qid_to_positive(path: str) -> Dict[str, str]:
if args.task != 'nq':
logger.warning('Only NQ has manually labeled positives')
return {}
examples = json.load(open(path, 'r', encoding='utf-8'))
qid_to_pos_id = {}
for ex in examples:
positive_ctxs = ex['positive_ctxs']
if len(positive_ctxs) > 0:
qid_to_pos_id[ex['question']] = str(int(positive_ctxs[0]['passage_id']) - 1)
logger.info('Get {} manually labeled positives from {}'.format(len(qid_to_pos_id), path))
return qid_to_pos_id
def _write_prepared_data_to_disk(out_path: str,
split: str,
queries: Dict[str, Dict[str, Any]],
preds_path: str):
qid_to_pos_id = _load_qid_to_positive(path='{}/biencoder-nq-{}.json'.format(args.out_dir, split))
cnt_filtered = 0
preds = json.load(open(preds_path, 'r', encoding='utf-8'))
with open(out_path, 'w', encoding='utf-8') as writer:
for query_id in tqdm(queries, mininterval=1, desc='prepare {} data'.format(split)):
cur_pred: dict = preds[query_id] if query_id in preds else preds[query_id.strip()]
positive_ids, negative_ids = [], []
manual_positive_id = qid_to_pos_id.get(query_id, None)
if manual_positive_id:
positive_ids.append(manual_positive_id)
for ctx in cur_pred['contexts'][:args.depth]:
doc_id = str(ctx['docid'])
if doc_id == manual_positive_id:
continue
elif ctx['has_answer']:
positive_ids.append(doc_id)
else:
negative_ids.append(doc_id)
if not positive_ids or not negative_ids:
cnt_filtered += 1
continue
np.random.shuffle(negative_ids)
negative_ids = negative_ids[:args.num_negatives]
doc_id_to_score = {str(ctx['docid']): float(ctx['score']) for ctx in cur_pred['contexts']}
doc_id_to_score[manual_positive_id] = 1000.
example = {
'query_id': query_id,
'query': queries[query_id]['query'],
'answers': queries[query_id]['answers'],
'positives': {'doc_id': positive_ids,
'score': [doc_id_to_score.get(doc_id, -1.) for doc_id in positive_ids]
},
'negatives': {'doc_id': negative_ids,
'score': [doc_id_to_score.get(doc_id, -1.) for doc_id in negative_ids]
},
}
writer.write(json.dumps(example, ensure_ascii=False, separators=(',', ':')))
writer.write('\n')
if cnt_filtered > 0:
logger.info('{} questions are filtered out'.format(cnt_filtered))
logger.info('Done write {} data to {}'.format(split, out_path))
if __name__ == '__main__':
for split in ['dev', 'train']:
_write_prepared_data_to_disk(
out_path=os.path.join(args.out_dir, '{}_hard_{}.jsonl'.format(args.task, split)),
split=split,
queries=load_query_answers(path=os.path.join(args.out_dir, '{}_{}_queries.tsv'.format(args.task, split))),
preds_path=(args.train_pred_path if split == 'train' else args.dev_pred_path)
)
corpus = load_corpus(path=os.path.join(args.out_dir, 'passages.jsonl.gz'))
for split in ['dev', 'train']:
save_to_readable_format(in_path=os.path.join(args.out_dir, '{}_hard_{}.jsonl'.format(args.task, split)),
corpus=corpus)
logger.info('Done')