chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
"""
|
||||
Data pre-processing: build vocabularies and binarize training data.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import typing as tp
|
||||
from argparse import Namespace
|
||||
from itertools import zip_longest
|
||||
|
||||
from fairseq import options, tasks, utils
|
||||
from fairseq.binarizer import (
|
||||
AlignmentDatasetBinarizer,
|
||||
FileBinarizer,
|
||||
VocabularyDatasetBinarizer,
|
||||
)
|
||||
from fairseq.data import Dictionary
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
level=os.environ.get("LOGLEVEL", "INFO").upper(),
|
||||
stream=sys.stdout,
|
||||
)
|
||||
logger = logging.getLogger("fairseq_cli.preprocess")
|
||||
|
||||
#####################################################################
|
||||
# file name tools
|
||||
#####################################################################
|
||||
|
||||
|
||||
def _train_path(lang, trainpref):
|
||||
return "{}{}".format(trainpref, ("." + lang) if lang else "")
|
||||
|
||||
|
||||
def _file_name(prefix, lang):
|
||||
fname = prefix
|
||||
if lang is not None:
|
||||
fname += ".{lang}".format(lang=lang)
|
||||
return fname
|
||||
|
||||
|
||||
def _dest_path(prefix, lang, destdir):
|
||||
return os.path.join(destdir, _file_name(prefix, lang))
|
||||
|
||||
|
||||
def _dict_path(lang, destdir):
|
||||
return _dest_path("dict", lang, destdir) + ".txt"
|
||||
|
||||
|
||||
def dataset_dest_prefix(args, output_prefix, lang):
|
||||
base = os.path.join(args.destdir, output_prefix)
|
||||
if lang is not None:
|
||||
lang_part = f".{args.source_lang}-{args.target_lang}.{lang}"
|
||||
elif args.only_source:
|
||||
lang_part = ""
|
||||
else:
|
||||
lang_part = f".{args.source_lang}-{args.target_lang}"
|
||||
|
||||
return "{}{}".format(base, lang_part)
|
||||
|
||||
|
||||
def dataset_dest_file(args, output_prefix, lang, extension):
|
||||
return "{}.{}".format(dataset_dest_prefix(args, output_prefix, lang), extension)
|
||||
|
||||
|
||||
#####################################################################
|
||||
# dictionary tools
|
||||
#####################################################################
|
||||
|
||||
|
||||
def _build_dictionary(
|
||||
filenames,
|
||||
task,
|
||||
args,
|
||||
src=False,
|
||||
tgt=False,
|
||||
):
|
||||
assert src ^ tgt
|
||||
return task.build_dictionary(
|
||||
filenames,
|
||||
workers=args.workers,
|
||||
threshold=args.thresholdsrc if src else args.thresholdtgt,
|
||||
nwords=args.nwordssrc if src else args.nwordstgt,
|
||||
padding_factor=args.padding_factor,
|
||||
)
|
||||
|
||||
|
||||
#####################################################################
|
||||
# bin file creation logic
|
||||
#####################################################################
|
||||
|
||||
|
||||
def _make_binary_dataset(
|
||||
vocab: Dictionary,
|
||||
input_prefix: str,
|
||||
output_prefix: str,
|
||||
lang: tp.Optional[str],
|
||||
num_workers: int,
|
||||
args: Namespace,
|
||||
):
|
||||
logger.info("[{}] Dictionary: {} types".format(lang, len(vocab)))
|
||||
|
||||
binarizer = VocabularyDatasetBinarizer(
|
||||
vocab,
|
||||
append_eos=True,
|
||||
)
|
||||
|
||||
input_file = "{}{}".format(input_prefix, ("." + lang) if lang is not None else "")
|
||||
full_output_prefix = dataset_dest_prefix(args, output_prefix, lang)
|
||||
|
||||
final_summary = FileBinarizer.multiprocess_dataset(
|
||||
input_file,
|
||||
args.dataset_impl,
|
||||
binarizer,
|
||||
full_output_prefix,
|
||||
vocab_size=len(vocab),
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
logger.info(f"[{lang}] {input_file}: {final_summary} (by {vocab.unk_word})")
|
||||
|
||||
|
||||
def _make_binary_alignment_dataset(
|
||||
input_prefix: str, output_prefix: str, num_workers: int, args: Namespace
|
||||
):
|
||||
|
||||
binarizer = AlignmentDatasetBinarizer(utils.parse_alignment)
|
||||
|
||||
input_file = input_prefix
|
||||
full_output_prefix = dataset_dest_prefix(args, output_prefix, lang=None)
|
||||
|
||||
final_summary = FileBinarizer.multiprocess_dataset(
|
||||
input_file,
|
||||
args.dataset_impl,
|
||||
binarizer,
|
||||
full_output_prefix,
|
||||
vocab_size=None,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[alignments] {}: parsed {} alignments".format(
|
||||
input_file, final_summary.num_seq
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
#####################################################################
|
||||
# routing logic
|
||||
#####################################################################
|
||||
|
||||
|
||||
def _make_dataset(
|
||||
vocab: Dictionary,
|
||||
input_prefix: str,
|
||||
output_prefix: str,
|
||||
lang: tp.Optional[str],
|
||||
args: Namespace,
|
||||
num_workers: int,
|
||||
):
|
||||
if args.dataset_impl == "raw":
|
||||
# Copy original text file to destination folder
|
||||
output_text_file = _dest_path(
|
||||
output_prefix + ".{}-{}".format(args.source_lang, args.target_lang),
|
||||
lang,
|
||||
args.destdir,
|
||||
)
|
||||
shutil.copyfile(_file_name(input_prefix, lang), output_text_file)
|
||||
else:
|
||||
_make_binary_dataset(
|
||||
vocab, input_prefix, output_prefix, lang, num_workers, args
|
||||
)
|
||||
|
||||
|
||||
def _make_all(lang, vocab, args):
|
||||
if args.trainpref:
|
||||
_make_dataset(
|
||||
vocab, args.trainpref, "train", lang, args=args, num_workers=args.workers
|
||||
)
|
||||
if args.validpref:
|
||||
for k, validpref in enumerate(args.validpref.split(",")):
|
||||
outprefix = "valid{}".format(k) if k > 0 else "valid"
|
||||
_make_dataset(
|
||||
vocab, validpref, outprefix, lang, args=args, num_workers=args.workers
|
||||
)
|
||||
if args.testpref:
|
||||
for k, testpref in enumerate(args.testpref.split(",")):
|
||||
outprefix = "test{}".format(k) if k > 0 else "test"
|
||||
_make_dataset(
|
||||
vocab, testpref, outprefix, lang, args=args, num_workers=args.workers
|
||||
)
|
||||
|
||||
|
||||
def _make_all_alignments(args):
|
||||
if args.trainpref and os.path.exists(args.trainpref + "." + args.align_suffix):
|
||||
_make_binary_alignment_dataset(
|
||||
args.trainpref + "." + args.align_suffix,
|
||||
"train.align",
|
||||
num_workers=args.workers,
|
||||
args=args,
|
||||
)
|
||||
if args.validpref and os.path.exists(args.validpref + "." + args.align_suffix):
|
||||
_make_binary_alignment_dataset(
|
||||
args.validpref + "." + args.align_suffix,
|
||||
"valid.align",
|
||||
num_workers=args.workers,
|
||||
args=args,
|
||||
)
|
||||
if args.testpref and os.path.exists(args.testpref + "." + args.align_suffix):
|
||||
_make_binary_alignment_dataset(
|
||||
args.testpref + "." + args.align_suffix,
|
||||
"test.align",
|
||||
num_workers=args.workers,
|
||||
args=args,
|
||||
)
|
||||
|
||||
|
||||
#####################################################################
|
||||
# align
|
||||
#####################################################################
|
||||
|
||||
|
||||
def _align_files(args, src_dict, tgt_dict):
|
||||
assert args.trainpref, "--trainpref must be set if --alignfile is specified"
|
||||
src_file_name = _train_path(args.source_lang, args.trainpref)
|
||||
tgt_file_name = _train_path(args.target_lang, args.trainpref)
|
||||
freq_map = {}
|
||||
with open(args.alignfile, "r", encoding="utf-8") as align_file:
|
||||
with open(src_file_name, "r", encoding="utf-8") as src_file:
|
||||
with open(tgt_file_name, "r", encoding="utf-8") as tgt_file:
|
||||
for a, s, t in zip_longest(align_file, src_file, tgt_file):
|
||||
si = src_dict.encode_line(s, add_if_not_exist=False)
|
||||
ti = tgt_dict.encode_line(t, add_if_not_exist=False)
|
||||
ai = list(map(lambda x: tuple(x.split("-")), a.split()))
|
||||
for sai, tai in ai:
|
||||
srcidx = si[int(sai)]
|
||||
tgtidx = ti[int(tai)]
|
||||
if srcidx != src_dict.unk() and tgtidx != tgt_dict.unk():
|
||||
assert srcidx != src_dict.pad()
|
||||
assert srcidx != src_dict.eos()
|
||||
assert tgtidx != tgt_dict.pad()
|
||||
assert tgtidx != tgt_dict.eos()
|
||||
if srcidx not in freq_map:
|
||||
freq_map[srcidx] = {}
|
||||
if tgtidx not in freq_map[srcidx]:
|
||||
freq_map[srcidx][tgtidx] = 1
|
||||
else:
|
||||
freq_map[srcidx][tgtidx] += 1
|
||||
align_dict = {}
|
||||
for srcidx in freq_map.keys():
|
||||
align_dict[srcidx] = max(freq_map[srcidx], key=freq_map[srcidx].get)
|
||||
with open(
|
||||
os.path.join(
|
||||
args.destdir,
|
||||
"alignment.{}-{}.txt".format(args.source_lang, args.target_lang),
|
||||
),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
for k, v in align_dict.items():
|
||||
print("{} {}".format(src_dict[k], tgt_dict[v]), file=f)
|
||||
|
||||
|
||||
#####################################################################
|
||||
# MAIN
|
||||
#####################################################################
|
||||
|
||||
|
||||
def main(args):
|
||||
# setup some basic things
|
||||
utils.import_user_module(args)
|
||||
|
||||
os.makedirs(args.destdir, exist_ok=True)
|
||||
|
||||
logger.addHandler(
|
||||
logging.FileHandler(
|
||||
filename=os.path.join(args.destdir, "preprocess.log"),
|
||||
)
|
||||
)
|
||||
logger.info(args)
|
||||
|
||||
assert (
|
||||
args.dataset_impl != "huffman"
|
||||
), "preprocessing.py doesn't support Huffman yet, use HuffmanCodeBuilder directly."
|
||||
|
||||
# build dictionaries
|
||||
|
||||
target = not args.only_source
|
||||
|
||||
if not args.srcdict and os.path.exists(_dict_path(args.source_lang, args.destdir)):
|
||||
raise FileExistsError(_dict_path(args.source_lang, args.destdir))
|
||||
|
||||
if (
|
||||
target
|
||||
and not args.tgtdict
|
||||
and os.path.exists(_dict_path(args.target_lang, args.destdir))
|
||||
):
|
||||
raise FileExistsError(_dict_path(args.target_lang, args.destdir))
|
||||
|
||||
task = tasks.get_task(args.task)
|
||||
|
||||
if args.joined_dictionary:
|
||||
assert (
|
||||
not args.srcdict or not args.tgtdict
|
||||
), "cannot use both --srcdict and --tgtdict with --joined-dictionary"
|
||||
|
||||
if args.srcdict:
|
||||
src_dict = task.load_dictionary(args.srcdict)
|
||||
elif args.tgtdict:
|
||||
src_dict = task.load_dictionary(args.tgtdict)
|
||||
else:
|
||||
assert (
|
||||
args.trainpref
|
||||
), "--trainpref must be set if --srcdict is not specified"
|
||||
src_dict = _build_dictionary(
|
||||
{
|
||||
_train_path(lang, args.trainpref)
|
||||
for lang in [args.source_lang, args.target_lang]
|
||||
},
|
||||
task=task,
|
||||
args=args,
|
||||
src=True,
|
||||
)
|
||||
tgt_dict = src_dict
|
||||
else:
|
||||
if args.srcdict:
|
||||
src_dict = task.load_dictionary(args.srcdict)
|
||||
else:
|
||||
assert (
|
||||
args.trainpref
|
||||
), "--trainpref must be set if --srcdict is not specified"
|
||||
src_dict = _build_dictionary(
|
||||
[_train_path(args.source_lang, args.trainpref)],
|
||||
task=task,
|
||||
args=args,
|
||||
src=True,
|
||||
)
|
||||
|
||||
if target:
|
||||
if args.tgtdict:
|
||||
tgt_dict = task.load_dictionary(args.tgtdict)
|
||||
else:
|
||||
assert (
|
||||
args.trainpref
|
||||
), "--trainpref must be set if --tgtdict is not specified"
|
||||
tgt_dict = _build_dictionary(
|
||||
[_train_path(args.target_lang, args.trainpref)],
|
||||
task=task,
|
||||
args=args,
|
||||
tgt=True,
|
||||
)
|
||||
else:
|
||||
tgt_dict = None
|
||||
|
||||
# save dictionaries
|
||||
|
||||
src_dict.save(_dict_path(args.source_lang, args.destdir))
|
||||
if target and tgt_dict is not None:
|
||||
tgt_dict.save(_dict_path(args.target_lang, args.destdir))
|
||||
|
||||
if args.dict_only:
|
||||
return
|
||||
|
||||
_make_all(args.source_lang, src_dict, args)
|
||||
if target:
|
||||
_make_all(args.target_lang, tgt_dict, args)
|
||||
|
||||
# align the datasets if needed
|
||||
if args.align_suffix:
|
||||
_make_all_alignments(args)
|
||||
|
||||
logger.info("Wrote preprocessed data to {}".format(args.destdir))
|
||||
|
||||
if args.alignfile:
|
||||
_align_files(args, src_dict=src_dict, tgt_dict=tgt_dict)
|
||||
|
||||
|
||||
def cli_main():
|
||||
parser = options.get_preprocessing_parser()
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_main()
|
||||
Reference in New Issue
Block a user