Introduce code from Rust monorepo. This is a mess

This commit is contained in:
Brandon Thomas
2022-02-28 05:43:54 -05:00
parent 921fb49f9a
commit ba326bfcfd
1058 changed files with 192192 additions and 0 deletions
@@ -0,0 +1,242 @@
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import logging
import os
import random
import re
import string
import subprocess
from nemo.utils import logging
URL = {'tatoeba': 'https://downloads.tatoeba.org/exports/sentences.csv'}
def __maybe_download_file(destination: str, source: str):
"""
Downloads source to destination if not exists.
If exists, skips download
Args:
destination: local filepath
source: url of resource
"""
source = URL[source]
if not os.path.exists(destination):
logging.info(f'Downloading {source} to {destination}')
subprocess.run(['wget', '-O', destination, source])
else:
logging.info(f'{destination} found. Skipping download')
def __process_english_sentences(
in_file: str, out_file: str, percent_to_cut: float = 0, num_to_combine: int = 1, num_samples: int = -1
):
"""
Extract English sentences from the Tatoeba dataset.
Expected in_file format
that
contrain letters and punctuation marks (,.?).
Chop and combine sentences.
Args:
in_file: local filepath to the tatoeba dataset.
Format: id [TAB] region_name [TAB] sentence,
for example: "1276\teng\tLet's try something.\n"
out_file: local filepath to the clean dataset
percent_to_cut: Percent of sentences to cut in the middle
to get examples of incomplete sentences.
This could be useful since ASR output not always
represents a complete sentence
num_to_combine: Number of sentences to combine into
a single example
num_samples: Number of samples in the final dataset
"""
if not os.path.exists(in_file):
raise FileNotFoundError(f'{in_file} not found.')
in_file = open(in_file, 'r')
out_file = open(out_file, 'w')
lines_to_combine = []
samples_count = 0
for line in in_file:
line = line.split('\t')
# use only English sentences
if line[1] == 'eng':
line = line[2].strip()
if re.match("^[A-Z][A-Za-z.,'?\s]+$", line): # nopep8
# chop some sentences in the middle
if percent_to_cut > 0:
line = line.split()
if random.random() < percent_to_cut:
line = line[: len(line) // 2]
line = ' '.join(line)
# combine multiple sentences into a single example
# to make it harder for the model to learn eos punctuation
if len(lines_to_combine) >= num_to_combine:
if samples_count == num_samples:
return
out_file.write(' '.join(lines_to_combine) + '\n')
lines_to_combine = []
samples_count += 1
lines_to_combine.append(line)
if len(lines_to_combine) > 0 and (samples_count < num_samples or num_samples < 0):
out_file.write(' '.join(lines_to_combine) + '\n')
def __split_into_train_dev(in_file: str, train_file: str, dev_file: str, percent_dev: float):
"""
Create train and dev split of the dataset.
Args:
in_file: local filepath to the dataset
train_file: local filepath to the train dataset
dev_file: local filepath to the dev dataset
percent_dev: Percent of the sentences in the dev set
"""
if not os.path.exists(in_file):
raise FileNotFoundError(f'{in_file} not found.')
lines = open(in_file, 'r').readlines()
train_file = open(train_file, 'w')
dev_file = open(dev_file, 'w')
dev_size = int(len(lines) * percent_dev)
train_file.write(' '.join(lines[:-dev_size]))
dev_file.write(' '.join(lines[-dev_size:]))
def remove_punctuation(word: str):
"""
Removes all punctuation marks from a word except for '
that is often a part of word: don't, it's, and so on
"""
all_punct_marks = string.punctuation.replace("'", '')
return re.sub('[' + all_punct_marks + ']', '', word)
def create_text_and_labels(output_dir: str, file_path: str, punct_marks: str = ',.?'):
"""
Create datasets for training and evaluation.
Args:
output_dir: path to the output data directory
file_path: path to file name
punct_marks: supported punctuation marks
The data will be split into 2 files: text.txt and labels.txt. \
Each line of the text.txt file contains text sequences, where words\
are separated with spaces. The labels.txt file contains \
corresponding labels for each word in text.txt, the labels are \
separated with spaces. Each line of the files should follow the \
format: \
[WORD] [SPACE] [WORD] [SPACE] [WORD] (for text.txt) and \
[LABEL] [SPACE] [LABEL] [SPACE] [LABEL] (for labels.txt).'
"""
if not os.path.exists(file_path):
raise ValueError(f'{file_path} not found')
os.makedirs(output_dir, exist_ok=True)
base_name = os.path.basename(file_path)
labels_file = os.path.join(output_dir, 'labels_' + base_name)
text_file = os.path.join(output_dir, 'text_' + base_name)
with open(file_path, 'r') as f:
with open(text_file, 'w') as text_f:
with open(labels_file, 'w') as labels_f:
for line in f:
line = line.split()
text = ''
labels = ''
for word in line:
label = word[-1] if word[-1] in punct_marks else 'O'
word = remove_punctuation(word)
if len(word) > 0:
if word[0].isupper():
label += 'U'
else:
label += 'O'
word = word.lower()
text += word + ' '
labels += label + ' '
text_f.write(text.strip() + '\n')
labels_f.write(labels.strip() + '\n')
print(f'{text_file} and {labels_file} created from {file_path}.')
def __delete_file(file_to_del: str):
"""
Deletes the file
Args:
file_to_del: local filepath to the file to delete
"""
if os.path.exists(file_to_del):
os.remove(file_to_del)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Prepare tatoeba dataset')
parser.add_argument("--data_dir", required=True, type=str)
parser.add_argument("--dataset", default='tatoeba', type=str)
parser.add_argument("--num_samples", default=-1, type=int, help='-1 to use the whole dataset')
parser.add_argument("--percent_to_cut", default=0, type=float, help='Percent of sentences to cut in the middle')
parser.add_argument(
"--num_lines_to_combine", default=1, type=int, help='Number of lines to combine into single example'
)
parser.add_argument("--percent_dev", default=0.2, type=float, help='Size of the dev set, float')
parser.add_argument("--clean_dir", action='store_true')
args = parser.parse_args()
if not os.path.exists(args.data_dir):
os.makedirs(args.data_dir)
if args.dataset != 'tatoeba':
raise ValueError("Unsupported dataset.")
logging.info(f'Downloading tatoeba dataset')
tatoeba_dataset = os.path.join(args.data_dir, 'sentences.csv')
__maybe_download_file(tatoeba_dataset, args.dataset)
logging.info(f'Processing English sentences...')
clean_eng_sentences = os.path.join(args.data_dir, 'clean_eng_sentences.txt')
__process_english_sentences(
tatoeba_dataset, clean_eng_sentences, args.percent_to_cut, args.num_lines_to_combine, args.num_samples
)
train_file = os.path.join(args.data_dir, 'train.txt')
dev_file = os.path.join(args.data_dir, 'dev.txt')
logging.info(
f'Splitting the {args.dataset} dataset into train and dev sets' + ' and creating labels and text files'
)
__split_into_train_dev(clean_eng_sentences, train_file, dev_file, args.percent_dev)
logging.info(f'Creating text and label files for training')
create_text_and_labels(args.data_dir, os.path.join(args.data_dir, 'train.txt'))
create_text_and_labels(args.data_dir, os.path.join(args.data_dir, 'dev.txt'))
if args.clean_dir:
logging.info(f'Cleaning up {args.data_dir}')
__delete_file(clean_eng_sentences)
__delete_file(tatoeba_dataset)
__delete_file(train_file)
__delete_file(dev_file)
logging.info(f'Processing of the {args.dataset} is complete')
@@ -0,0 +1,124 @@
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import os
from nemo.utils import logging
def __convert_data(in_file: str, out_text_f: str, out_labels_f: str, max_length: int):
"""
Convert data from the IOB format to NeMo accepted format described below.
in_file should be in the IOB format, see example here:
https://www.clips.uantwerpen.be/conll2003/ner/.
Args:
in_file: input file name
out_text_f: output file with text
out_labels_f: output file with labels
max_length: use -1 to leave the examples' length as is, otherwise long examples will be split into multiple
examples
After the conversion, the dataset is split into 2 files: text.txt
and labels.txt.
Each line of the text.txt file contains text sequences, where words
are separated with spaces. The labels.txt file contains corresponding
labels for each word in text.txt, the labels are separated with spaces.
Each line of the files should follow the format:
[WORD] [SPACE] [WORD] [SPACE] [WORD] (for text.txt) and
[LABEL] [SPACE] [LABEL] [SPACE] [LABEL] (for labels.txt).
"""
in_file = open(in_file, 'r')
if max_length == -1:
with open(out_text_f, 'w') as out_text, open(out_labels_f, 'w') as out_labels:
for line in in_file:
if line == '\n':
out_text.write(line)
out_labels.write(line)
else:
line = line.split()
out_text.write(line[0] + ' ')
out_labels.write(line[-1] + ' ')
else:
words = []
labels = []
with open(out_text_f, 'w') as out_text, open(out_labels_f, 'w') as out_labels:
lines = in_file.readlines()
for line_id, line in enumerate(lines):
logging.info(f"{line_id} {len(lines)}")
contends = line.strip()
if len(contends) == 0:
assert len(words) == len(labels)
if len(words) > max_length:
# split if the sentence is longer than max_length
while len(words) > max_length:
tmplabel = labels[:max_length]
for iidx in range(len(tmplabel)):
if tmplabel.pop() == 'O':
break
l = ' '.join([label for label in labels[: len(tmplabel) + 1] if len(label) > 0])
w = ' '.join([word for word in words[: len(tmplabel) + 1] if len(word) > 0])
out_text.write(w + "\n")
out_labels.write(l + "\n")
words = words[len(tmplabel) + 1 :]
labels = labels[len(tmplabel) + 1 :]
if len(words) == 0:
continue
l = ' '.join([label for label in labels if len(label) > 0])
w = ' '.join([word for word in words if len(word) > 0])
out_text.write(w + "\n")
out_labels.write(l + "\n")
words = []
labels = []
continue
word = line.strip().split()[0]
label = line.strip().split()[-1]
words.append(word)
labels.append(label)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Convert data from IOB format to the format compatible with \
nlp/examples/token_classification/scripts/token_classification_train.py and \
token_classification_evaluate.py'
)
parser.add_argument("--data_file", required=True, type=str, help='path to a file in IOB format')
parser.add_argument(
"--max_length",
default=-1,
type=int,
help='use -1 to leave the examples\'s length as is, '
'otherwise long examples will be split into multiple examples',
)
args = parser.parse_args()
data_dir, basename = os.path.split(args.data_file)
prefix = os.path.splitext(basename)[0]
if not os.path.exists(args.data_file):
raise FileNotFoundError(f"{args.data_file} not found")
logging.info(f'Processing {args.data_file}')
out_text = os.path.join(data_dir, 'text_' + prefix + '.txt')
out_labels = os.path.join(data_dir, 'labels_' + prefix + '.txt')
__convert_data(args.data_file, out_text, out_labels, args.max_length)
logging.info(f'Processing of the {args.data_file} is complete')
@@ -0,0 +1,99 @@
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The script converts raw text to the NeMo format for punctuation and capitalization task.
Raw Data Format
---------------
The Punctuation and Capitalization model can work with any text dataset, although it is recommended to balance the data, especially for the punctuation task.
Before pre-processing the data to the format expected by the model, the data should be split into train.txt and dev.txt (and optionally test.txt).
Each line in the **train.txt/dev.txt/test.txt** should represent one or more full and/or truncated sentences.
Example of the train.txt/dev.txt file:
When is the next flight to New York?
The next flight is ...
....
The `source_data_dir` structure should look like this:
.
|--sourced_data_dir
|-- dev.txt
|-- train.txt
NeMo Data Format for training the model
---------------------------------------
The punctuation and capitalization model expects the data in the following format:
The training and evaluation data is divided into 2 files: text.txt and labels.txt. \
Each line of the **text.txt** file contains text sequences, where words are separated with spaces, i.e.
[WORD] [SPACE] [WORD] [SPACE] [WORD], for example:
when is the next flight to new york
the next flight is ...
...
The **labels.txt** file contains corresponding labels for each word in text.txt, the labels are separated with spaces. \
Each label in labels.txt file consists of 2 symbols:
* the first symbol of the label indicates what punctuation mark should follow the word (where O means no punctuation needed);
* the second symbol determines if a word needs to be capitalized or not (where U indicates that the word should be upper cased, and O - no capitalization needed.)
By default the following punctuation marks are considered: commas, periods, and question marks; the rest punctuation marks were removed from the data.
This can be changed by introducing new labels in the labels.txt files
Each line of the labels.txt should follow the format: [LABEL] [SPACE] [LABEL] [SPACE] [LABEL] (for labels.txt). \
For example, labels for the above text.txt file should be:
OU OO OO OO OO OO OU ?U
OU OO OO OO ...
...
The complete list of all possible labels for this task used in this tutorial is: OO, ,O, .O, ?O, OU, ,U, .U, ?U.
Converting Raw data to NeMo format
----------------------------------
To pre-process the raw text data, stored under :code:`sourced_data_dir` (see the :ref:`raw_data_format_punct`
section), run the following command:
python examples/nlp/token_classification/data/prepare_data_for_punctuation_capitalization.py \
-s <PATH_TO_THE_SOURCE_FILE>
-o <PATH_TO_THE_OUTPUT_DIRECTORY>
"""
import argparse
import os
from get_tatoeba_data import create_text_and_labels
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Prepare data for punctuation and capitalization tasks')
parser.add_argument("-s", "--source_file", required=True, type=str, help="Path to the source file")
parser.add_argument("-o", "--output_dir", required=True, type=str, help="Path to the output directory")
args = parser.parse_args()
if not os.path.exists(args.source_file):
raise ValueError(f'{args.source_file} was not found')
os.makedirs(args.output_dir, exist_ok=True)
create_text_and_labels(args.output_dir, args.source_file)
print(f'Processing of the {args.source_file} is complete')