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,119 @@
# Copyright (c) 2020, 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.
# Config file for text classification with pre-trained BERT models
trainer:
gpus: 1 # number of GPUs (0 for CPU), or list of the GPUs to use e.g. [0, 1]
num_nodes: 1
max_epochs: 100
max_steps: null # precedence over max_epochs
accumulate_grad_batches: 1 # accumulates grads every k batches
gradient_clip_val: 0.0
amp_level: O0 # O1/O2 for mixed precision
precision: 32 # Should be set to 16 for O1 and O2 to enable the AMP.
accelerator: ddp
log_every_n_steps: 1 # Interval of logging.
val_check_interval: 1.0 # Set to 0.25 to check 4 times per epoch, or an int for number of iterations
resume_from_checkpoint: null # The path to a checkpoint file to continue the training, restores the whole state including the epoch, step, LR schedulers, apex, etc.
num_sanity_val_steps: 0 # number of steps to perform validation steps for sanity check the validation process before starting the training, setting to 0 disables it
checkpoint_callback: False # Provided by exp_manager
logger: False # Provided by exp_manager
model:
nemo_path: text_classification_model.nemo # filename to save the model and associated artifacts to .nemo file
tokenizer:
tokenizer_name: ${model.language_model.pretrained_model_name} # or sentencepiece
vocab_file: null # path to vocab file
tokenizer_model: null # only used if tokenizer is sentencepiece
special_tokens: null
language_model:
pretrained_model_name: bert-base-uncased
lm_checkpoint: null
config_file: null # json file, precedence over config
config: null
classifier_head:
num_output_layers: 2
fc_dropout: 0.1
class_labels:
class_labels_file : null # optional to specify a file containing the list of the labels
dataset:
num_classes: ??? # The number of classes. 0 < Label <num_classes.
do_lower_case: false # true for uncased models, false for cased models, will be set automatically if pre-trained tokenizer model is used
max_seq_length: 256 # the maximum length BERT supports is 512
class_balancing: null # null or 'weighted_loss'. 'weighted_loss' enables the weighted class balancing of the loss, may be used for handling unbalanced classes
use_cache: false # uses a cache to store the processed dataset, you may use it for large datasets for speed up
train_ds:
file_path: null
batch_size: 64
shuffle: true
num_samples: -1 # number of samples to be considered, -1 means all the dataset
num_workers: 3
drop_last: false
pin_memory: false
validation_ds:
file_path: null
batch_size: 64
shuffle: false
num_samples: -1 # number of samples to be considered, -1 means all the dataset
num_workers: 3
drop_last: false
pin_memory: false
test_ds:
file_path: null
batch_size: 64
shuffle: false
num_samples: -1 # number of samples to be considered, -1 means all the dataset
num_workers: 3
drop_last: false
pin_memory: false
optim:
name: adam
lr: 2e-5
# optimizer arguments
betas: [0.9, 0.999]
weight_decay: 0.01
# scheduler setup
sched:
name: WarmupAnnealing
# Scheduler params
warmup_steps: null
warmup_ratio: 0.1
last_epoch: -1
# pytorch lightning args
monitor: val_loss
reduce_on_plateau: false
# List of some sample queries for inference after training is done
infer_samples: [
'by the end of no such thing the audience , like beatrice , has a watchful affection for the monster .',
'director rob marshall went out gunning to make a great one .',
'uneasy mishmash of styles and genres .',
]
exp_manager:
exp_dir: null # exp_dir for your experiment, if None, defaults to "./nemo_experiments"
name: "TextClassification" # The name of your model
create_tensorboard_logger: True # Whether you want exp_manger to create a tb logger
create_checkpoint_callback: True # Whether you want exp_manager to create a modelcheckpoint callback
@@ -0,0 +1,243 @@
# Copyright (c) 2020, 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.
"""
This script can be used to process and import IMDB, ChemProt, SST-2, and THUCnews datasets into NeMo's format.
You may run it as the following:
python import_datasets.py \
--dataset_name DATASET_NAME \
--target_data_dir TARGET_PATH \
--source_data_dir SOURCE_PATH
The dataset should be specified by "DATASET_NAME" which can be from ["sst-2", "chemprot", "imdb", "thucnews"].
It reads the data from "SOURCE_PATH" folder, processes and converts the data into NeMo's format.
Then writes the results into "TARGET_PATH" folder.
"""
import argparse
import csv
import glob
import os
from os.path import exists
import tqdm
from nemo.utils import logging
def process_imdb(infold, outfold, uncased, modes=['train', 'test']):
if not os.path.exists(infold):
link = 'https://ai.stanford.edu/~amaas/data/sentiment/'
raise ValueError(
f'Data not found at {infold}. '
f'Please download IMDB reviews dataset from {link} and '
f'extract it into the folder specified by source_data_dir argument.'
)
logging.info(f'Processing IMDB dataset and store at {outfold}')
os.makedirs(outfold, exist_ok=True)
outfiles = {}
for mode in modes:
outfiles[mode] = open(os.path.join(outfold, mode + '.tsv'), 'w')
for sent in ['neg', 'pos']:
if sent == 'neg':
label = 0
else:
label = 1
files = glob.glob(f'{infold}/{mode}/{sent}/*.txt')
for file in files:
with open(file, 'r') as f:
review = f.read().strip()
if uncased:
review = review.lower()
review = review.replace("<br />", "")
outfiles[mode].write(f'{review}\t{label}\n')
for mode in modes:
outfiles[mode].close()
class_labels_file = open(os.path.join(outfold, 'label_ids.tsv'), 'w')
class_labels_file.write('negative\npositive\n')
class_labels_file.close()
def process_sst2(infold, outfold, uncased, splits=['train', 'dev']):
"""Process sst2 dataset."""
# "test" split doesn't have labels, so it is skipped
if not os.path.exists(infold):
link = 'https://dl.fbaipublicfiles.com/glue/data/SST-2.zip'
raise ValueError(
f'Data not found at {infold}. Please download SST-2 dataset from `{link}` and '
f'extract it into the folder specified by `source_data_dir` argument.'
)
logging.info(f'Processing SST-2 dataset')
os.makedirs(outfold, exist_ok=True)
def _read_tsv(input_file, quotechar=None):
"""Read a tab separated value file."""
with open(input_file, "r") as f:
reader = csv.reader(f, delimiter="\t", quotechar=quotechar)
lines = []
for line in reader:
lines.append(line)
return lines
for split in splits:
# Load input file.
input_file = os.path.join(infold, split + '.tsv')
lines = _read_tsv(input_file)
# Create output.
outfile = open(os.path.join(outfold, split + '.tsv'), 'w')
# Copy lines, skip the header (line 0).
for line in lines[1:]:
text = line[0]
label = line[1]
# Lowercase when required.
if uncased:
text = text.lower()
# Write output.
outfile.write(f'{text}\t{label}\n')
# Close file.
outfile.close()
class_labels_file = open(os.path.join(outfold, 'label_ids.tsv'), 'w')
class_labels_file.write('negative\npositive\n')
class_labels_file.close()
logging.info(f'Result stored at {outfold}')
def process_chemprot(source_dir, target_dir, uncased, modes=['train', 'test', 'dev']):
if not os.path.exists(source_dir):
link = 'https://github.com/arwhirang/recursive_chemprot/tree/master/Demo/tree_LSTM/data'
raise ValueError(f'Data not found at {source_dir}. ' f'Please download ChemProt from {link}.')
logging.info(f'Processing Chemprot dataset and store at {target_dir}')
os.makedirs(target_dir, exist_ok=True)
naming_map = {'train': 'trainingPosit_chem', 'test': 'testPosit_chem', 'dev': 'developPosit_chem'}
def _read_tsv(input_file, quotechar=None):
"""Reads a tab separated value file."""
with open(input_file, "r") as f:
reader = csv.reader(f, delimiter="\t", quotechar=quotechar)
lines = []
for line in reader:
lines.append(line)
return lines
outfiles = {}
label_mapping = {}
out_label_mapping = open(os.path.join(target_dir, 'label_mapping.tsv'), 'w')
for mode in modes:
outfiles[mode] = open(os.path.join(target_dir, mode + '.tsv'), 'w')
input_file = os.path.join(source_dir, naming_map[mode])
lines = _read_tsv(input_file)
for line in lines:
text = line[1]
label = line[2]
if label == "True":
label = line[3]
if uncased:
text = text.lower()
if label not in label_mapping:
out_label_mapping.write(f'{label}\t{len(label_mapping)}\n')
label_mapping[label] = len(label_mapping)
label = label_mapping[label]
outfiles[mode].write(f'{text}\t{label}\n')
for mode in modes:
outfiles[mode].close()
out_label_mapping.close()
def process_thucnews(infold, outfold):
modes = ['train', 'test']
train_size = 0.8
if not os.path.exists(infold):
link = 'thuctc.thunlp.org/'
raise ValueError(f'Data not found at {infold}. ' f'Please download THUCNews from {link}.')
logging.info(f'Processing THUCNews dataset and store at {outfold}')
os.makedirs(outfold, exist_ok=True)
outfiles = {}
for mode in modes:
outfiles[mode] = open(os.path.join(outfold, mode + '.tsv'), 'a+', encoding='utf-8')
categories = ['体育', '娱乐', '家居', '彩票', '房产', '教育', '时尚', '时政', '星座', '游戏', '社会', '科技', '股票', '财经']
for category in categories:
label = categories.index(category)
category_files = glob.glob(f'{infold}/{category}/*.txt')
test_num = int(len(category_files) * (1 - train_size))
test_files = category_files[:test_num]
train_files = category_files[test_num:]
for mode in modes:
logging.info(f'Processing {mode} data of the category {category}')
if mode == 'test':
files = test_files
else:
files = train_files
if len(files) == 0:
logging.info(f'Skipping category {category} for {mode} mode')
continue
for file in tqdm.tqdm(files):
with open(file, 'r', encoding='utf-8') as f:
news = f.read().strip().replace('\r', '')
news = news.replace('\n', '').replace('\t', ' ')
outfiles[mode].write(f'{news}\t{label}\n')
for mode in modes:
outfiles[mode].close()
if __name__ == "__main__":
# Parse the command-line arguments.
parser = argparse.ArgumentParser(description="Process and convert datasets into NeMo\'s format.")
parser.add_argument("--dataset_name", required=True, type=str, choices=['imdb', 'thucnews', 'chemprot'])
parser.add_argument(
"--source_data_dir", required=True, type=str, help='The path to the folder containing the dataset files.'
)
parser.add_argument("--target_data_dir", required=True, type=str)
parser.add_argument("--do_lower_case", action='store_true')
args = parser.parse_args()
dataset_name = args.dataset_name
do_lower_case = args.do_lower_case
source_dir = args.source_data_dir
target_dir = args.target_data_dir
if not exists(source_dir):
raise FileNotFoundError(f"{source_dir} does not exist.")
if dataset_name == 'imdb':
process_imdb(source_dir, target_dir, do_lower_case)
elif dataset_name == 'thucnews':
process_thucnews(source_dir, target_dir)
elif dataset_name == "chemprot":
process_chemprot(source_dir, target_dir, do_lower_case)
elif dataset_name == "sst-2":
process_sst2(source_dir, target_dir, do_lower_case)
else:
raise ValueError(
f'Dataset {dataset_name} is not supported.'
+ "Please make sure that you build the preprocessing process for it. "
+ "NeMo's format assumes that a data file has a header and each line of the file follows "
+ "the format: text [TAB] label. Label is assumed to be an integer."
)
@@ -0,0 +1,41 @@
# 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.
"""
This script runs model parallel text classification evaluation.
"""
import pytorch_lightning as pl
from omegaconf import DictConfig, OmegaConf
from nemo.collections.nlp.models.text_classification import TextClassificationModel
from nemo.collections.nlp.parts.nlp_overrides import NLPDDPPlugin
from nemo.core.config import hydra_runner
from nemo.utils import logging
from nemo.utils.exp_manager import exp_manager
@hydra_runner(config_path="conf", config_name="text_classification_config")
def main(cfg: DictConfig) -> None:
logging.info(f'\nConfig Params:\n{OmegaConf.to_yaml(cfg)}')
trainer = pl.Trainer(plugins=[NLPDDPPlugin()], **cfg.trainer)
exp_manager(trainer, cfg.get("exp_manager", None))
# TODO: can we drop strict=False
model = TextClassificationModel.restore_from(cfg.model.nemo_path, trainer=trainer, strict=False)
model.setup_test_data(test_data_config=cfg.model.test_ds)
trainer.test(model=model, ckpt_path=None)
if __name__ == '__main__':
main()
@@ -0,0 +1,154 @@
# Copyright (c) 2020, 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.
"""
This script contains an example on how to train, evaluate and perform inference with the TextClassificationModel.
TextClassificationModel in NeMo supports text classification problems such as sentiment analysis or
domain/intent detection for dialogue systems, as long as the data follows the format specified below.
***Data format***
TextClassificationModel requires the data to be stored in TAB separated files (.tsv) with two columns of sentence and
label. Each line of the data file contains text sequences, where words are separated with spaces and label separated
with [TAB], i.e.:
[WORD][SPACE][WORD][SPACE][WORD][TAB][LABEL]
For example:
hide new secretions from the parental units[TAB]0
that loves its characters and communicates something rather beautiful about human nature[TAB]1
...
If your dataset is stored in another format, you need to convert it to this format to use the TextClassificationModel.
***Setting the configs***
The model and the PT trainer are defined in a config file which declares multiple important sections.
The most important ones are:
model: All arguments that are related to the Model - language model, tokenizer, head classifier, optimizer,
schedulers, and datasets/data loaders.
trainer: Any argument to be passed to PyTorch Lightning including number of epochs, number of GPUs,
precision level, etc.
This script uses the `/examples/nlp/text_classification/conf/text_classification_config.yaml` default config file
by default. You may update the config file from the file directly or by using the command line arguments.
Other option is to set another config file via command line arguments by `--config-name=CONFIG_FILE_PATH'.
You first need to set the num_classes in the config file which specifies the number of classes in the dataset.
Notice that some config lines, including `model.dataset.classes_num`, have `???` as their value, this means that values
for these fields are required to be specified by the user. We need to specify and set the `model.train_ds.file_name`,
`model.validation_ds.file_name`, and `model.test_ds.file_name` in the config file to the paths of the train, validation,
and test files if they exist. We may do it by updating the config file or by setting them from the command line.
***How to run the script?***
For example the following would train a model for 50 epochs in 2 GPUs on a classification task with 2 classes:
# python text_classification_with_bert.py
model.dataset.num_classes=2
model.train_ds=PATH_TO_TRAIN_FILE
model.validation_ds=PATH_TO_VAL_FILE
trainer.max_epochs=50
trainer.gpus=2
This script would also reload the last checkpoint after the training is done and does evaluation on the dev set,
then performs inference on some sample queries.
By default, this script uses examples/nlp/text_classification/conf/text_classifciation_config.py config file, and
you may update all the params in the config file from the command line. You may also use another config file like this:
# python text_classification_with_bert.py --config-name==PATH_TO_CONFIG_FILE
model.dataset.num_classes=2
model.train_ds=PATH_TO_TRAIN_FILE
model.validation_ds=PATH_TO_VAL_FILE
trainer.max_epochs=50
trainer.gpus=2
***Load a saved model***
This script would save the model after training into '.nemo' checkpoint file specified by nemo_path of the model config.
You may restore the saved model like this:
model = TextClassificationModel.restore_from(restore_path=NEMO_FILE_PATH)
***Evaluation a saved model on another dataset***
# If you wanted to evaluate the saved model on another dataset, you may restore the model and create a new data loader:
eval_model = TextClassificationModel.restore_from(restore_path=checkpoint_path)
# Then, you may create a dataloader config for evaluation:
eval_config = OmegaConf.create(
{'file_path': cfg.model.test_ds.file_path, 'batch_size': 64, 'shuffle': False, 'num_workers': 3}
)
eval_model.setup_test_data(test_data_config=eval_config)
# You need to create a new trainer:
eval_trainer = pl.Trainer(gpus=1)
eval_model.set_trainer(eval_trainer)
eval_trainer.test(model=eval_model, verbose=False)
"""
import pytorch_lightning as pl
from omegaconf import DictConfig, OmegaConf
from nemo.collections.nlp.models.text_classification import TextClassificationModel
from nemo.collections.nlp.parts.nlp_overrides import NLPDDPPlugin
from nemo.core.config import hydra_runner
from nemo.utils import logging
from nemo.utils.exp_manager import exp_manager
@hydra_runner(config_path="conf", config_name="text_classification_config")
def main(cfg: DictConfig) -> None:
logging.info(f'\nConfig Params:\n{OmegaConf.to_yaml(cfg)}')
trainer = pl.Trainer(plugins=[NLPDDPPlugin()], **cfg.trainer)
exp_manager(trainer, cfg.get("exp_manager", None))
if not cfg.model.train_ds.file_path:
raise ValueError("'train_ds.file_path' need to be set for the training!")
model = TextClassificationModel(cfg.model, trainer=trainer)
logging.info("===========================================================================================")
logging.info('Starting training...')
trainer.fit(model)
logging.info('Training finished!')
logging.info("===========================================================================================")
if cfg.model.nemo_path:
# '.nemo' file contains the last checkpoint and the params to initialize the model
model.save_to(cfg.model.nemo_path)
logging.info(f'Model is saved into `.nemo` file: {cfg.model.nemo_path}')
# We evaluate the trained model on the test set if test_ds is set in the config file
if cfg.model.test_ds.file_path:
logging.info("===========================================================================================")
logging.info("Starting the testing of the trained model on test set...")
trainer.test(model=model, ckpt_path=None, verbose=False)
logging.info("Testing finished!")
logging.info("===========================================================================================")
# perform inference on a list of queries.
if "infer_samples" in cfg.model and cfg.model.infer_samples:
logging.info("===========================================================================================")
logging.info("Starting the inference on some sample queries...")
# max_seq_length=512 is the maximum length BERT supports.
results = model.classifytext(queries=cfg.model.infer_samples, batch_size=16, max_seq_length=512)
logging.info('The prediction results of some sample queries with the trained model:')
for query, result in zip(cfg.model.infer_samples, results):
logging.info(f'Query : {query}')
logging.info(f'Predicted label: {result}')
logging.info("Inference finished!")
logging.info("===========================================================================================")
if __name__ == '__main__':
main()