| import os |
| import io |
| import sys |
| import argparse |
| import torch |
| |
|
|
| from src.evaluation.evaluator import convert_to_text, eval_moses_bleu |
| from src.utils import AttrDict |
| from src.utils import bool_flag, initialize_exp, restore_segmentation |
| from src.data.dictionary import Dictionary |
| from src.model.transformer import TransformerModel |
|
|
| from flask import Flask, request, jsonify |
|
|
| app = Flask(__name__) |
|
|
|
|
| def get_parser(): |
| """ |
| Generate a parameters parser. |
| """ |
| |
| parser = argparse.ArgumentParser(description="Translate sentences") |
|
|
| |
| parser.add_argument("--dump_path", type=str, default="./dumped/", help="Experiment dump path") |
| parser.add_argument("--ref_path", type=str, default="", help="Reference file path (if evaluating BLEU)") |
| parser.add_argument("--exp_name", type=str, default="", help="Experiment name") |
| parser.add_argument("--exp_id", type=str, default="", help="Experiment ID") |
| parser.add_argument("--seed", type=str, default=0, help="Random seed (for sampled generations)") |
| parser.add_argument("--amp", type=int, default=0, help="fp16 opt level (0 for fp32)") |
| parser.add_argument("--batch_size", type=int, default=32, help="Number of sentences per batch") |
|
|
| |
| parser.add_argument("--sample_temperature", type=float, default=0.0, |
| help="Beam size, default = None (greedy decoding)") |
| parser.add_argument("--beam_size", type=int, default=1, |
| help="Beam size, default = 1 (greedy decoding)") |
| parser.add_argument("--length_penalty", type=float, default=1, |
| help="Length penalty, values < 1.0 favor shorter sentences, while values > 1.0 favor longer ones.") |
| parser.add_argument("--early_stopping", type=bool_flag, default=False, |
| help="Early stopping, stop as soon as we have `beam_size` hypotheses, although longer ones may have better scores.") |
|
|
| |
| parser.add_argument("--model_path", type=str, default="", help="Model path") |
| parser.add_argument("--output_path", type=str, default="", help="Output path") |
|
|
| |
| |
|
|
| |
| parser.add_argument("--src_lang", type=str, default="", help="Source language") |
| parser.add_argument("--tgt_lang", type=str, default="", help="Target language") |
| |
| args = parser.parse_args(args=['--exp_name', 'translate', |
| '--src_lang', 'mh', |
| '--tgt_lang', 'sh', |
| '--model_path', os.environ.get("RAGQA_XLM_CKPT", ""), |
| '--batch_size', '1', |
| '--beam_size', '5', |
| '--length_penalty', '1.0', |
| '--sample_temperature', '0.0' |
| ]) |
|
|
| return args |
|
|
|
|
| |
| params = get_parser() |
|
|
|
|
| torch.manual_seed(params.seed) |
| assert (params.sample_temperature == 0) or (params.beam_size == 1), 'Cannot sample with beam search.' |
| assert params.amp <= 1, f'params.amp == {params.amp} not yet supported.' |
| |
| _LOADED = False |
| dico = None |
| encoder = None |
| decoder = None |
|
|
|
|
| def load(): |
| """首次调用时加载 XLM 模型(需要 RAGQA_XLM_CKPT 与 CUDA GPU);重复调用是空操作。""" |
| global _LOADED, dico, encoder, decoder |
| if _LOADED: |
| return |
| assert params.model_path, "[ragqa] 请设置环境变量 RAGQA_XLM_CKPT 指向 XLM 分解检查点 .pth" |
| reloaded = torch.load(params.model_path) |
| model_params = AttrDict(reloaded['params']) |
|
|
| |
| for name in ['n_words', 'bos_index', 'eos_index', 'pad_index', 'unk_index', 'mask_index']: |
| setattr(params, name, getattr(model_params, name)) |
|
|
| |
| dico = Dictionary(reloaded['dico_id2word'], reloaded['dico_word2id'], reloaded['dico_counts']) |
| encoder = TransformerModel(model_params, dico, is_encoder=True, with_output=False).cuda().eval() |
| decoder = TransformerModel(model_params, dico, is_encoder=False, with_output=True).cuda().eval() |
| if all([k.startswith('module.') for k in reloaded['encoder'].keys()]): |
| reloaded['encoder'] = {k[len('module.'):]: v for k, v in reloaded['encoder'].items()} |
| encoder.load_state_dict(reloaded['encoder']) |
| if all([k.startswith('module.') for k in reloaded['decoder'].keys()]): |
| reloaded['decoder'] = {k[len('module.'):]: v for k, v in reloaded['decoder'].items()} |
| decoder.load_state_dict(reloaded['decoder']) |
|
|
| if params.amp != 0: |
| import apex |
| encoder, decoder = apex.amp.initialize( |
| [encoder, decoder], |
| opt_level=('O%i' % params.amp) |
| ) |
|
|
| params.src_id = model_params.lang2id[params.src_lang] |
| params.tgt_id = model_params.lang2id[params.tgt_lang] |
| _LOADED = True |
|
|
|
|
| def unsupervised_decom(params, sent): |
| |
| assert isinstance(sent, str) |
| src_sent = [] |
| src_sent.append(sent) |
|
|
| |
|
|
| hypothesis = [[] for _ in range(params.beam_size)] |
| for i in range(0, len(src_sent), params.batch_size): |
|
|
| |
| word_ids = [torch.LongTensor([dico.index(w) for w in s.strip().split()]) |
| for s in src_sent[i:i + params.batch_size]] |
| lengths = torch.LongTensor([len(s) + 2 for s in word_ids]) |
| batch = torch.LongTensor(lengths.max().item(), lengths.size(0)).fill_(params.pad_index) |
| batch[0] = params.eos_index |
| for j, s in enumerate(word_ids): |
| if lengths[j] > 2: |
| batch[1:lengths[j] - 1, j].copy_(s) |
| batch[lengths[j] - 1, j] = params.eos_index |
| langs = batch.clone().fill_(params.src_id) |
|
|
| |
| encoded = encoder('fwd', x=batch.cuda(), lengths=lengths.cuda(), langs=langs.cuda(), causal=False) |
| encoded = encoded.transpose(0, 1) |
| max_len = int(1.5 * lengths.max().item() + 10) |
| if params.beam_size == 1: |
| decoded, dec_lengths = decoder.generate( |
| encoded, lengths.cuda(), params.tgt_id, max_len=max_len, |
| sample_temperature=(None if params.sample_temperature == 0 else params.sample_temperature)) |
| else: |
| decoded, dec_lengths, all_hyp_strs = decoder.generate_beam( |
| encoded, lengths.cuda(), params.tgt_id, beam_size=params.beam_size, |
| length_penalty=params.length_penalty, |
| early_stopping=params.early_stopping, |
| max_len=max_len, |
| output_all_hyps=True |
| ) |
| |
|
|
| |
| for j in range(decoded.size(1)): |
|
|
| |
| sent = decoded[:, j] |
| delimiters = (sent == params.eos_index).nonzero().view(-1) |
| assert len(delimiters) >= 1 and delimiters[0].item() == 0 |
| sent = sent[1:] if len(delimiters) == 1 else sent[1:delimiters[1]] |
|
|
| |
| source = src_sent[i + j].strip() |
| target = " ".join([dico[sent[k].item()] for k in range(len(sent))]) |
| if params.beam_size == 1: |
| hypothesis[0].append(target) |
| else: |
| for hyp_rank in range(params.beam_size): |
| print(all_hyp_strs[j][hyp_rank if hyp_rank < len(all_hyp_strs[j]) else -1]) |
| hypothesis[hyp_rank].append(all_hyp_strs[j][hyp_rank if hyp_rank < len(all_hyp_strs[j]) else -1]) |
|
|
| sys.stderr.write("%i / %i: %s -> %s\n" % (i + j, len(src_sent), source.replace('@@ ', ''), target.replace('@@ ', ''))) |
| |
| |
| |
| return [q.strip() + '?' for q in target.replace('@@ ', '').split('?') if q.strip()] |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
|
|
| |
| |
|
|
| def decompose(query): |
| """进程内查询分解:query(str) -> list[str] 子问题。首次调用会加载模型。""" |
| load() |
| return unsupervised_decom(params, query) |
|
|
|
|
| @app.route('/execute', methods=["GET"]) |
| def api_search(): |
| if request.method == "GET": |
|
|
| queries = request.args.get("query") |
| return {"text" : decompose(queries)} |
| else: |
| return '', 405 |
| |
|
|
| if __name__ == '__main__': |
| load() |
| app.run(host='0.0.0.0', port=50002) |