Upload 5 files
Browse files- FineTuning_GPT2_for_Quotes_Generation.ipynb +0 -0
- HuggingFace_Inference_API.ipynb +0 -0
- README.md +42 -0
- run_generation.py +268 -0
- run_language_modeling.py +283 -0
FineTuning_GPT2_for_Quotes_Generation.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
HuggingFace_Inference_API.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quotes Generator
|
| 2 |
+
|
| 3 |
+
## Project description
|
| 4 |
+
|
| 5 |
+
Fine-tuned GPT2 model on the Quotes-500K dataset. For a given user prompt, it can generate motivational quotes starting with it. The model weights are deployed on Hugging Face Models Hub.
|
| 6 |
+
Check it out at https://huggingface.co/nandinib1999/quote-generator.
|
| 7 |
+
|
| 8 |
+
## Training data
|
| 9 |
+
|
| 10 |
+
This is the distribution of the total dataset into training, validation and test dataset for the fine-tuning task.
|
| 11 |
+
|
| 12 |
+
<table style="width:30%">
|
| 13 |
+
<tr>
|
| 14 |
+
<th>train</th>
|
| 15 |
+
<td>349796</td>
|
| 16 |
+
</tr>
|
| 17 |
+
<tr>
|
| 18 |
+
<th>validation</th>
|
| 19 |
+
<td>99942</td>
|
| 20 |
+
</tr>
|
| 21 |
+
<tr>
|
| 22 |
+
<th>test</th>
|
| 23 |
+
<td>49971</td>
|
| 24 |
+
</tr>
|
| 25 |
+
</table>
|
| 26 |
+
|
| 27 |
+
## Training procedure
|
| 28 |
+
|
| 29 |
+
The model was fine-tuned using the Google Colab GPU for one epoch. The weights of the pre-trained GPT2 model were used as a base.
|
| 30 |
+
|
| 31 |
+
## Eval results
|
| 32 |
+
|
| 33 |
+
<table style="width:30%">
|
| 34 |
+
<tr>
|
| 35 |
+
<th>Epoch</th>
|
| 36 |
+
<th>Perplexity</th>
|
| 37 |
+
</tr>
|
| 38 |
+
<tr>
|
| 39 |
+
<td>1</td>
|
| 40 |
+
<td>15.180</td>
|
| 41 |
+
</tr>
|
| 42 |
+
</table>
|
run_generation.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# coding=utf-8
|
| 3 |
+
# Copyright 2018 Google AI, Google Brain and Carnegie Mellon University Authors and the HuggingFace Inc. team.
|
| 4 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
""" Conditional text generation with the auto-regressive models of the library (GPT/GPT-2/CTRL/Transformer-XL/XLNet)
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import logging
|
| 23 |
+
|
| 24 |
+
import numpy as np
|
| 25 |
+
import torch
|
| 26 |
+
|
| 27 |
+
from transformers import (
|
| 28 |
+
CTRLLMHeadModel,
|
| 29 |
+
CTRLTokenizer,
|
| 30 |
+
GPT2LMHeadModel,
|
| 31 |
+
GPT2Tokenizer,
|
| 32 |
+
OpenAIGPTLMHeadModel,
|
| 33 |
+
OpenAIGPTTokenizer,
|
| 34 |
+
TransfoXLLMHeadModel,
|
| 35 |
+
TransfoXLTokenizer,
|
| 36 |
+
XLMTokenizer,
|
| 37 |
+
XLMWithLMHeadModel,
|
| 38 |
+
XLNetLMHeadModel,
|
| 39 |
+
XLNetTokenizer,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
logging.basicConfig(
|
| 44 |
+
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%m/%d/%Y %H:%M:%S", level=logging.INFO,
|
| 45 |
+
)
|
| 46 |
+
logger = logging.getLogger(__name__)
|
| 47 |
+
|
| 48 |
+
MAX_LENGTH = int(10000) # Hardcoded max length to avoid infinite loop
|
| 49 |
+
|
| 50 |
+
MODEL_CLASSES = {
|
| 51 |
+
"gpt2": (GPT2LMHeadModel, GPT2Tokenizer),
|
| 52 |
+
"ctrl": (CTRLLMHeadModel, CTRLTokenizer),
|
| 53 |
+
"openai-gpt": (OpenAIGPTLMHeadModel, OpenAIGPTTokenizer),
|
| 54 |
+
"xlnet": (XLNetLMHeadModel, XLNetTokenizer),
|
| 55 |
+
"transfo-xl": (TransfoXLLMHeadModel, TransfoXLTokenizer),
|
| 56 |
+
"xlm": (XLMWithLMHeadModel, XLMTokenizer),
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
# Padding text to help Transformer-XL and XLNet with short prompts as proposed by Aman Rusia
|
| 60 |
+
# in https://github.com/rusiaaman/XLNet-gen#methodology
|
| 61 |
+
# and https://medium.com/@amanrusia/xlnet-speaks-comparison-to-gpt-2-ea1a4e9ba39e
|
| 62 |
+
PADDING_TEXT = """In 1991, the remains of Russian Tsar Nicholas II and his family
|
| 63 |
+
(except for Alexei and Maria) are discovered.
|
| 64 |
+
The voice of Nicholas's young son, Tsarevich Alexei Nikolaevich, narrates the
|
| 65 |
+
remainder of the story. 1883 Western Siberia,
|
| 66 |
+
a young Grigori Rasputin is asked by his father and a group of men to perform magic.
|
| 67 |
+
Rasputin has a vision and denounces one of the men as a horse thief. Although his
|
| 68 |
+
father initially slaps him for making such an accusation, Rasputin watches as the
|
| 69 |
+
man is chased outside and beaten. Twenty years later, Rasputin sees a vision of
|
| 70 |
+
the Virgin Mary, prompting him to become a priest. Rasputin quickly becomes famous,
|
| 71 |
+
with people, even a bishop, begging for his blessing. <eod> </s> <eos>"""
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def set_seed(args):
|
| 75 |
+
np.random.seed(args.seed)
|
| 76 |
+
torch.manual_seed(args.seed)
|
| 77 |
+
if args.n_gpu > 0:
|
| 78 |
+
torch.cuda.manual_seed_all(args.seed)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
#
|
| 82 |
+
# Functions to prepare models' input
|
| 83 |
+
#
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def prepare_ctrl_input(args, _, tokenizer, prompt_text):
|
| 87 |
+
if args.temperature > 0.7:
|
| 88 |
+
logger.info("CTRL typically works better with lower temperatures (and lower top_k).")
|
| 89 |
+
|
| 90 |
+
encoded_prompt = tokenizer.encode(prompt_text, add_special_tokens=False)
|
| 91 |
+
if not any(encoded_prompt[0] == x for x in tokenizer.control_codes.values()):
|
| 92 |
+
logger.info("WARNING! You are not starting your generation from a control code so you won't get good results")
|
| 93 |
+
return prompt_text
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def prepare_xlm_input(args, model, tokenizer, prompt_text):
|
| 97 |
+
# kwargs = {"language": None, "mask_token_id": None}
|
| 98 |
+
|
| 99 |
+
# Set the language
|
| 100 |
+
use_lang_emb = hasattr(model.config, "use_lang_emb") and model.config.use_lang_emb
|
| 101 |
+
if hasattr(model.config, "lang2id") and use_lang_emb:
|
| 102 |
+
available_languages = model.config.lang2id.keys()
|
| 103 |
+
if args.xlm_language in available_languages:
|
| 104 |
+
language = args.xlm_language
|
| 105 |
+
else:
|
| 106 |
+
language = None
|
| 107 |
+
while language not in available_languages:
|
| 108 |
+
language = input("Using XLM. Select language in " + str(list(available_languages)) + " >>> ")
|
| 109 |
+
|
| 110 |
+
model.config.lang_id = model.config.lang2id[language]
|
| 111 |
+
# kwargs["language"] = tokenizer.lang2id[language]
|
| 112 |
+
|
| 113 |
+
# TODO fix mask_token_id setup when configurations will be synchronized between models and tokenizers
|
| 114 |
+
# XLM masked-language modeling (MLM) models need masked token
|
| 115 |
+
# is_xlm_mlm = "mlm" in args.model_name_or_path
|
| 116 |
+
# if is_xlm_mlm:
|
| 117 |
+
# kwargs["mask_token_id"] = tokenizer.mask_token_id
|
| 118 |
+
|
| 119 |
+
return prompt_text
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def prepare_xlnet_input(args, _, tokenizer, prompt_text):
|
| 123 |
+
prompt_text = (args.padding_text if args.padding_text else PADDING_TEXT) + prompt_text
|
| 124 |
+
return prompt_text
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def prepare_transfoxl_input(args, _, tokenizer, prompt_text):
|
| 128 |
+
prompt_text = (args.padding_text if args.padding_text else PADDING_TEXT) + prompt_text
|
| 129 |
+
return prompt_text
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
PREPROCESSING_FUNCTIONS = {
|
| 133 |
+
"ctrl": prepare_ctrl_input,
|
| 134 |
+
"xlm": prepare_xlm_input,
|
| 135 |
+
"xlnet": prepare_xlnet_input,
|
| 136 |
+
"transfo-xl": prepare_transfoxl_input,
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def adjust_length_to_model(length, max_sequence_length):
|
| 141 |
+
if length < 0 and max_sequence_length > 0:
|
| 142 |
+
length = max_sequence_length
|
| 143 |
+
elif 0 < max_sequence_length < length:
|
| 144 |
+
length = max_sequence_length # No generation bigger than model size
|
| 145 |
+
elif length < 0:
|
| 146 |
+
length = MAX_LENGTH # avoid infinite loop
|
| 147 |
+
return length
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def main():
|
| 151 |
+
parser = argparse.ArgumentParser()
|
| 152 |
+
parser.add_argument(
|
| 153 |
+
"--model_type",
|
| 154 |
+
default=None,
|
| 155 |
+
type=str,
|
| 156 |
+
required=True,
|
| 157 |
+
help="Model type selected in the list: " + ", ".join(MODEL_CLASSES.keys()),
|
| 158 |
+
)
|
| 159 |
+
parser.add_argument(
|
| 160 |
+
"--model_name_or_path",
|
| 161 |
+
default=None,
|
| 162 |
+
type=str,
|
| 163 |
+
required=True,
|
| 164 |
+
help="Path to pre-trained model or shortcut name selected in the list: " + ", ".join(MODEL_CLASSES.keys()),
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
parser.add_argument("--prompt", type=str, default="")
|
| 168 |
+
parser.add_argument("--length", type=int, default=20)
|
| 169 |
+
parser.add_argument("--stop_token", type=str, default=None, help="Token at which text generation is stopped")
|
| 170 |
+
|
| 171 |
+
parser.add_argument(
|
| 172 |
+
"--temperature",
|
| 173 |
+
type=float,
|
| 174 |
+
default=1.0,
|
| 175 |
+
help="temperature of 1.0 has no effect, lower tend toward greedy sampling",
|
| 176 |
+
)
|
| 177 |
+
parser.add_argument(
|
| 178 |
+
"--repetition_penalty", type=float, default=1.0, help="primarily useful for CTRL model; in that case, use 1.2"
|
| 179 |
+
)
|
| 180 |
+
parser.add_argument("--k", type=int, default=0)
|
| 181 |
+
parser.add_argument("--p", type=float, default=0.9)
|
| 182 |
+
|
| 183 |
+
parser.add_argument("--padding_text", type=str, default="", help="Padding text for Transfo-XL and XLNet.")
|
| 184 |
+
parser.add_argument("--xlm_language", type=str, default="", help="Optional language when used with the XLM model.")
|
| 185 |
+
|
| 186 |
+
parser.add_argument("--seed", type=int, default=42, help="random seed for initialization")
|
| 187 |
+
parser.add_argument("--no_cuda", action="store_true", help="Avoid using CUDA when available")
|
| 188 |
+
parser.add_argument("--num_return_sequences", type=int, default=1, help="The number of samples to generate.")
|
| 189 |
+
args = parser.parse_args()
|
| 190 |
+
|
| 191 |
+
args.device = torch.device("cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")
|
| 192 |
+
args.n_gpu = 0 if args.no_cuda else torch.cuda.device_count()
|
| 193 |
+
|
| 194 |
+
set_seed(args)
|
| 195 |
+
|
| 196 |
+
# Initialize the model and tokenizer
|
| 197 |
+
try:
|
| 198 |
+
args.model_type = args.model_type.lower()
|
| 199 |
+
model_class, tokenizer_class = MODEL_CLASSES[args.model_type]
|
| 200 |
+
except KeyError:
|
| 201 |
+
raise KeyError("the model {} you specified is not supported. You are welcome to add it and open a PR :)")
|
| 202 |
+
|
| 203 |
+
tokenizer = tokenizer_class.from_pretrained(args.model_name_or_path)
|
| 204 |
+
model = model_class.from_pretrained(args.model_name_or_path)
|
| 205 |
+
model.to(args.device)
|
| 206 |
+
|
| 207 |
+
args.length = adjust_length_to_model(args.length, max_sequence_length=model.config.max_position_embeddings)
|
| 208 |
+
logger.info(args)
|
| 209 |
+
|
| 210 |
+
prompt_text = args.prompt if args.prompt else input("Model prompt >>> ")
|
| 211 |
+
|
| 212 |
+
# Different models need different input formatting and/or extra arguments
|
| 213 |
+
requires_preprocessing = args.model_type in PREPROCESSING_FUNCTIONS.keys()
|
| 214 |
+
if requires_preprocessing:
|
| 215 |
+
prepare_input = PREPROCESSING_FUNCTIONS.get(args.model_type)
|
| 216 |
+
preprocessed_prompt_text = prepare_input(args, model, tokenizer, prompt_text)
|
| 217 |
+
encoded_prompt = tokenizer.encode(
|
| 218 |
+
preprocessed_prompt_text, add_special_tokens=False, return_tensors="pt", add_space_before_punct_symbol=True
|
| 219 |
+
)
|
| 220 |
+
else:
|
| 221 |
+
encoded_prompt = tokenizer.encode(prompt_text, add_special_tokens=True, return_tensors="pt")
|
| 222 |
+
encoded_prompt = encoded_prompt.to(args.device)
|
| 223 |
+
|
| 224 |
+
if encoded_prompt.size()[-1] == 0:
|
| 225 |
+
input_ids = None
|
| 226 |
+
else:
|
| 227 |
+
input_ids = encoded_prompt
|
| 228 |
+
|
| 229 |
+
output_sequences = model.generate(
|
| 230 |
+
input_ids=input_ids,
|
| 231 |
+
max_length=args.length + len(encoded_prompt[0]),
|
| 232 |
+
temperature=args.temperature,
|
| 233 |
+
top_k=args.k,
|
| 234 |
+
top_p=args.p,
|
| 235 |
+
repetition_penalty=args.repetition_penalty,
|
| 236 |
+
do_sample=True,
|
| 237 |
+
num_return_sequences=args.num_return_sequences,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
# Remove the batch dimension when returning multiple sequences
|
| 241 |
+
if len(output_sequences.shape) > 2:
|
| 242 |
+
output_sequences.squeeze_()
|
| 243 |
+
|
| 244 |
+
generated_sequences = []
|
| 245 |
+
|
| 246 |
+
for generated_sequence_idx, generated_sequence in enumerate(output_sequences):
|
| 247 |
+
print("=== GENERATED SEQUENCE {} ===".format(generated_sequence_idx + 1))
|
| 248 |
+
generated_sequence = generated_sequence.tolist()
|
| 249 |
+
|
| 250 |
+
# Decode text
|
| 251 |
+
text = tokenizer.decode(generated_sequence, clean_up_tokenization_spaces=True)
|
| 252 |
+
|
| 253 |
+
# Remove all text after the stop token
|
| 254 |
+
text = text[: text.find(args.stop_token) if args.stop_token else None]
|
| 255 |
+
|
| 256 |
+
# Add the prompt at the beginning of the sequence. Remove the excess text that was used for pre-processing
|
| 257 |
+
total_sequence = (
|
| 258 |
+
prompt_text + text[len(tokenizer.decode(encoded_prompt[0], clean_up_tokenization_spaces=True)) :]
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
generated_sequences.append(total_sequence)
|
| 262 |
+
print(total_sequence)
|
| 263 |
+
|
| 264 |
+
return generated_sequences
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
if __name__ == "__main__":
|
| 268 |
+
main()
|
run_language_modeling.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
"""
|
| 17 |
+
Fine-tuning the library models for language modeling on a text file (GPT, GPT-2, BERT, RoBERTa).
|
| 18 |
+
GPT and GPT-2 are fine-tuned using a causal language modeling (CLM) loss while BERT and RoBERTa are fine-tuned
|
| 19 |
+
using a masked language modeling (MLM) loss.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
import logging
|
| 24 |
+
import math
|
| 25 |
+
import os
|
| 26 |
+
from dataclasses import dataclass, field
|
| 27 |
+
from typing import Optional
|
| 28 |
+
|
| 29 |
+
from transformers import (
|
| 30 |
+
CONFIG_MAPPING,
|
| 31 |
+
MODEL_WITH_LM_HEAD_MAPPING,
|
| 32 |
+
AutoConfig,
|
| 33 |
+
AutoModelWithLMHead,
|
| 34 |
+
AutoTokenizer,
|
| 35 |
+
DataCollatorForLanguageModeling,
|
| 36 |
+
HfArgumentParser,
|
| 37 |
+
LineByLineTextDataset,
|
| 38 |
+
PreTrainedTokenizer,
|
| 39 |
+
TextDataset,
|
| 40 |
+
Trainer,
|
| 41 |
+
TrainingArguments,
|
| 42 |
+
set_seed,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
logger = logging.getLogger(__name__)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
MODEL_CONFIG_CLASSES = list(MODEL_WITH_LM_HEAD_MAPPING.keys())
|
| 50 |
+
MODEL_TYPES = tuple(conf.model_type for conf in MODEL_CONFIG_CLASSES)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@dataclass
|
| 54 |
+
class ModelArguments:
|
| 55 |
+
"""
|
| 56 |
+
Arguments pertaining to which model/config/tokenizer we are going to fine-tune, or train from scratch.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
model_name_or_path: Optional[str] = field(
|
| 60 |
+
default=None,
|
| 61 |
+
metadata={
|
| 62 |
+
"help": "The model checkpoint for weights initialization. Leave None if you want to train a model from scratch."
|
| 63 |
+
},
|
| 64 |
+
)
|
| 65 |
+
model_type: Optional[str] = field(
|
| 66 |
+
default=None,
|
| 67 |
+
metadata={"help": "If training from scratch, pass a model type from the list: " + ", ".join(MODEL_TYPES)},
|
| 68 |
+
)
|
| 69 |
+
config_name: Optional[str] = field(
|
| 70 |
+
default=None, metadata={"help": "Pretrained config name or path if not the same as model_name"}
|
| 71 |
+
)
|
| 72 |
+
tokenizer_name: Optional[str] = field(
|
| 73 |
+
default=None, metadata={"help": "Pretrained tokenizer name or path if not the same as model_name"}
|
| 74 |
+
)
|
| 75 |
+
cache_dir: Optional[str] = field(
|
| 76 |
+
default=None, metadata={"help": "Where do you want to store the pretrained models downloaded from s3"}
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
@dataclass
|
| 81 |
+
class DataTrainingArguments:
|
| 82 |
+
"""
|
| 83 |
+
Arguments pertaining to what data we are going to input our model for training and eval.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
train_data_file: Optional[str] = field(
|
| 87 |
+
default=None, metadata={"help": "The input training data file (a text file)."}
|
| 88 |
+
)
|
| 89 |
+
eval_data_file: Optional[str] = field(
|
| 90 |
+
default=None,
|
| 91 |
+
metadata={"help": "An optional input evaluation data file to evaluate the perplexity on (a text file)."},
|
| 92 |
+
)
|
| 93 |
+
line_by_line: bool = field(
|
| 94 |
+
default=False,
|
| 95 |
+
metadata={"help": "Whether distinct lines of text in the dataset are to be handled as distinct sequences."},
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
mlm: bool = field(
|
| 99 |
+
default=False, metadata={"help": "Train with masked-language modeling loss instead of language modeling."}
|
| 100 |
+
)
|
| 101 |
+
mlm_probability: float = field(
|
| 102 |
+
default=0.15, metadata={"help": "Ratio of tokens to mask for masked language modeling loss"}
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
block_size: int = field(
|
| 106 |
+
default=-1,
|
| 107 |
+
metadata={
|
| 108 |
+
"help": "Optional input sequence length after tokenization."
|
| 109 |
+
"The training dataset will be truncated in block of this size for training."
|
| 110 |
+
"Default to the model max input length for single sentence inputs (take into account special tokens)."
|
| 111 |
+
},
|
| 112 |
+
)
|
| 113 |
+
overwrite_cache: bool = field(
|
| 114 |
+
default=False, metadata={"help": "Overwrite the cached training and evaluation sets"}
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def get_dataset(args: DataTrainingArguments, tokenizer: PreTrainedTokenizer, evaluate=False):
|
| 119 |
+
file_path = args.eval_data_file if evaluate else args.train_data_file
|
| 120 |
+
if args.line_by_line:
|
| 121 |
+
return LineByLineTextDataset(tokenizer=tokenizer, file_path=file_path, block_size=args.block_size)
|
| 122 |
+
else:
|
| 123 |
+
return TextDataset(
|
| 124 |
+
tokenizer=tokenizer, file_path=file_path, block_size=args.block_size, overwrite_cache=args.overwrite_cache
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def main():
|
| 129 |
+
# See all possible arguments in src/transformers/training_args.py
|
| 130 |
+
# or by passing the --help flag to this script.
|
| 131 |
+
# We now keep distinct sets of args, for a cleaner separation of concerns.
|
| 132 |
+
|
| 133 |
+
parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments))
|
| 134 |
+
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
|
| 135 |
+
|
| 136 |
+
if data_args.eval_data_file is None and training_args.do_eval:
|
| 137 |
+
raise ValueError(
|
| 138 |
+
"Cannot do evaluation without an evaluation data file. Either supply a file to --eval_data_file "
|
| 139 |
+
"or remove the --do_eval argument."
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
if (
|
| 143 |
+
os.path.exists(training_args.output_dir)
|
| 144 |
+
and os.listdir(training_args.output_dir)
|
| 145 |
+
and training_args.do_train
|
| 146 |
+
and not training_args.overwrite_output_dir
|
| 147 |
+
):
|
| 148 |
+
raise ValueError(
|
| 149 |
+
f"Output directory ({training_args.output_dir}) already exists and is not empty. Use --overwrite_output_dir to overcome."
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# Setup logging
|
| 153 |
+
logging.basicConfig(
|
| 154 |
+
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
| 155 |
+
datefmt="%m/%d/%Y %H:%M:%S",
|
| 156 |
+
level=logging.INFO if training_args.local_rank in [-1, 0] else logging.WARN,
|
| 157 |
+
)
|
| 158 |
+
logger.warning(
|
| 159 |
+
"Process rank: %s, device: %s, n_gpu: %s, distributed training: %s, 16-bits training: %s",
|
| 160 |
+
training_args.local_rank,
|
| 161 |
+
training_args.device,
|
| 162 |
+
training_args.n_gpu,
|
| 163 |
+
bool(training_args.local_rank != -1),
|
| 164 |
+
training_args.fp16,
|
| 165 |
+
)
|
| 166 |
+
logger.info("Training/evaluation parameters %s", training_args)
|
| 167 |
+
|
| 168 |
+
# Set seed
|
| 169 |
+
set_seed(training_args.seed)
|
| 170 |
+
|
| 171 |
+
# Load pretrained model and tokenizer
|
| 172 |
+
#
|
| 173 |
+
# Distributed training:
|
| 174 |
+
# The .from_pretrained methods guarantee that only one local process can concurrently
|
| 175 |
+
# download model & vocab.
|
| 176 |
+
|
| 177 |
+
if model_args.config_name:
|
| 178 |
+
config = AutoConfig.from_pretrained(model_args.config_name, cache_dir=model_args.cache_dir)
|
| 179 |
+
elif model_args.model_name_or_path:
|
| 180 |
+
config = AutoConfig.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir)
|
| 181 |
+
else:
|
| 182 |
+
config = CONFIG_MAPPING[model_args.model_type]()
|
| 183 |
+
logger.warning("You are instantiating a new config instance from scratch.")
|
| 184 |
+
|
| 185 |
+
if model_args.tokenizer_name:
|
| 186 |
+
tokenizer = AutoTokenizer.from_pretrained(model_args.tokenizer_name, cache_dir=model_args.cache_dir)
|
| 187 |
+
elif model_args.model_name_or_path:
|
| 188 |
+
tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path, cache_dir=model_args.cache_dir)
|
| 189 |
+
else:
|
| 190 |
+
raise ValueError(
|
| 191 |
+
"You are instantiating a new tokenizer from scratch. This is not supported, but you can do it from another script, save it,"
|
| 192 |
+
"and load it from here, using --tokenizer_name"
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
if model_args.model_name_or_path:
|
| 196 |
+
model = AutoModelWithLMHead.from_pretrained(
|
| 197 |
+
model_args.model_name_or_path,
|
| 198 |
+
from_tf=bool(".ckpt" in model_args.model_name_or_path),
|
| 199 |
+
config=config,
|
| 200 |
+
cache_dir=model_args.cache_dir,
|
| 201 |
+
)
|
| 202 |
+
else:
|
| 203 |
+
logger.info("Training new model from scratch")
|
| 204 |
+
model = AutoModelWithLMHead.from_config(config)
|
| 205 |
+
|
| 206 |
+
special_tokens_dict = {'bos_token': '<BOS>', 'eos_token': '<EOS>', 'pad_token': '<PAD>'}
|
| 207 |
+
num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
|
| 208 |
+
model.resize_token_embeddings(len(tokenizer))
|
| 209 |
+
|
| 210 |
+
if config.model_type in ["bert", "roberta", "distilbert", "camembert"] and not data_args.mlm:
|
| 211 |
+
raise ValueError(
|
| 212 |
+
"BERT and RoBERTa-like models do not have LM heads but masked LM heads. They must be run using the --mlm "
|
| 213 |
+
"flag (masked language modeling)."
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
if data_args.block_size <= 0:
|
| 217 |
+
data_args.block_size = tokenizer.max_len
|
| 218 |
+
# Our input block size will be the max possible for the model
|
| 219 |
+
else:
|
| 220 |
+
data_args.block_size = min(data_args.block_size, tokenizer.max_len)
|
| 221 |
+
|
| 222 |
+
# Get datasets
|
| 223 |
+
|
| 224 |
+
train_dataset = get_dataset(data_args, tokenizer=tokenizer) if training_args.do_train else None
|
| 225 |
+
eval_dataset = get_dataset(data_args, tokenizer=tokenizer, evaluate=True) if training_args.do_eval else None
|
| 226 |
+
data_collator = DataCollatorForLanguageModeling(
|
| 227 |
+
tokenizer=tokenizer, mlm=data_args.mlm, mlm_probability=data_args.mlm_probability
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
# Initialize our Trainer
|
| 231 |
+
trainer = Trainer(
|
| 232 |
+
model=model,
|
| 233 |
+
args=training_args,
|
| 234 |
+
data_collator=data_collator,
|
| 235 |
+
train_dataset=train_dataset,
|
| 236 |
+
eval_dataset=eval_dataset,
|
| 237 |
+
prediction_loss_only=True,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
# Training
|
| 241 |
+
if training_args.do_train:
|
| 242 |
+
model_path = (
|
| 243 |
+
model_args.model_name_or_path
|
| 244 |
+
if model_args.model_name_or_path is not None and os.path.isdir(model_args.model_name_or_path)
|
| 245 |
+
else None
|
| 246 |
+
)
|
| 247 |
+
trainer.train(model_path=model_path)
|
| 248 |
+
trainer.save_model()
|
| 249 |
+
# For convenience, we also re-save the tokenizer to the same directory,
|
| 250 |
+
# so that you can share your model easily on huggingface.co/models =)
|
| 251 |
+
if trainer.is_world_master():
|
| 252 |
+
tokenizer.save_pretrained(training_args.output_dir)
|
| 253 |
+
|
| 254 |
+
# Evaluation
|
| 255 |
+
results = {}
|
| 256 |
+
if training_args.do_eval:
|
| 257 |
+
logger.info("*** Evaluate ***")
|
| 258 |
+
|
| 259 |
+
eval_output = trainer.evaluate()
|
| 260 |
+
|
| 261 |
+
perplexity = math.exp(eval_output["eval_loss"])
|
| 262 |
+
result = {"perplexity": perplexity}
|
| 263 |
+
|
| 264 |
+
output_eval_file = os.path.join(training_args.output_dir, "eval_results_lm.txt")
|
| 265 |
+
if trainer.is_world_master():
|
| 266 |
+
with open(output_eval_file, "w") as writer:
|
| 267 |
+
logger.info("***** Eval results *****")
|
| 268 |
+
for key in sorted(result.keys()):
|
| 269 |
+
logger.info(" %s = %s", key, str(result[key]))
|
| 270 |
+
writer.write("%s = %s\n" % (key, str(result[key])))
|
| 271 |
+
|
| 272 |
+
results.update(result)
|
| 273 |
+
|
| 274 |
+
return results
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _mp_fn(index):
|
| 278 |
+
# For xla_spawn (TPUs)
|
| 279 |
+
main()
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
if __name__ == "__main__":
|
| 283 |
+
main()
|