PyTorch for NLP: Text Generation with GPT-2

This blog will teach you how to use GPT-2, a powerful language model, to generate realistic text with PyTorch and HuggingFace transformers.

1. Introduction

Text generation is one of the most exciting and challenging tasks in natural language processing (NLP). It involves creating natural and coherent text from a given input, such as a prompt, a keyword, an image, or a previous text. Text generation can be used for various purposes, such as writing summaries, captions, headlines, stories, poems, lyrics, and more.

However, text generation is not an easy problem to solve. It requires a deep understanding of the language, the context, the style, and the domain of the text. It also requires a large amount of data and computational resources to train a model that can generate high-quality text.

Fortunately, there are some powerful tools and frameworks that can help us with text generation. One of them is PyTorch, an open-source machine learning library that provides a flexible and easy-to-use platform for building and deploying deep learning models. Another one is HuggingFace, a company that develops and maintains a collection of state-of-the-art NLP models and libraries, such as transformers, which are neural networks that can handle various NLP tasks, including text generation.

In this blog, we will learn how to use PyTorch and HuggingFace to generate realistic text with GPT-2, a pre-trained language model that can produce impressive results. We will cover the following topics:

  • What is GPT-2 and why is it important for NLP?
  • How to install and import PyTorch and HuggingFace transformers
  • How to load and use a pre-trained GPT-2 model
  • How to fine-tune GPT-2 on your own text data
  • How to generate text with GPT-2 using different decoding strategies
  • How to evaluate the quality and diversity of the generated text
  • Conclusion and further resources

By the end of this blog, you will have a solid understanding of how to use GPT-2 for text generation and how to apply it to your own projects. You will also have some fun and creative examples of text generated by GPT-2. So, let’s get started!

2. What is GPT-2 and why is it important for NLP?

GPT-2 stands for Generative Pre-trained Transformer 2, and it is one of the most advanced and powerful language models in NLP. It was developed by OpenAI, a research organization dedicated to creating and promoting artificial intelligence that can benefit humanity. GPT-2 was released in February 2019, and it caused a lot of excitement and controversy in the NLP community.

But what exactly is a language model, and how does GPT-2 work? A language model is a system that can learn the patterns and rules of a natural language, such as English, from a large amount of text data. A language model can then use this knowledge to generate new text that follows the same patterns and rules. For example, a language model can complete a sentence given a partial input, or generate a paragraph given a topic.

GPT-2 is a special kind of language model, called a neural language model, because it uses a neural network to learn and generate text. Specifically, GPT-2 uses a deep neural network called a transformer, which is composed of many layers of units that can process and encode the input text, and then decode and generate the output text. GPT-2 is also a pre-trained language model, meaning that it has been trained on a huge amount of text data before being used for a specific task. GPT-2 was trained on over 40 GB of text from the internet, covering a wide range of topics and domains.

So, why is GPT-2 important for NLP? GPT-2 is important for NLP because it can generate high-quality and realistic text that can be used for various applications, such as text summarization, text translation, text generation, text classification, and more. GPT-2 can also handle different types of text, such as news articles, fiction stories, product reviews, tweets, and more. GPT-2 can also adapt to different styles, tones, and contexts, depending on the input text. For example, GPT-2 can generate a formal report given a scientific abstract, or a humorous story given a funny prompt.

GPT-2 is also important for NLP because it can learn from any text data, without requiring any labeled or annotated data. This means that GPT-2 can leverage the vast amount of text data available on the internet, and learn from it in an unsupervised way. This also means that GPT-2 can be fine-tuned on a smaller amount of text data for a specific task or domain, without losing its general knowledge and ability. For example, GPT-2 can be fine-tuned on a corpus of movie scripts to generate better dialogues, or on a collection of poems to generate more creative verses.

In summary, GPT-2 is a powerful and versatile language model that can generate realistic and diverse text for various NLP tasks and domains. GPT-2 is also a pre-trained and adaptable language model that can learn from any text data, and be fine-tuned for specific purposes. GPT-2 is one of the most impressive achievements in NLP, and it opens up new possibilities and challenges for the field.

3. How to install and import PyTorch and HuggingFace transformers

Before we can use GPT-2 for text generation, we need to install and import two essential libraries: PyTorch and HuggingFace transformers. PyTorch is the framework that provides the core functionalities for building and deploying deep learning models, and HuggingFace transformers is the library that provides the pre-trained models and tools for NLP tasks.

To install PyTorch, you need to have Python 3.6 or higher and pip installed on your system. You can then use the following command in your terminal to install PyTorch:

pip install torch

To install HuggingFace transformers, you also need to have Python 3.6 or higher and pip installed on your system. You can then use the following command in your terminal to install HuggingFace transformers:

pip install transformers

Alternatively, you can use Google Colab, a free online platform that provides a Jupyter notebook environment with Python and many popular libraries pre-installed, including PyTorch and HuggingFace transformers. You can access Google Colab from this link: https://colab.research.google.com/

Once you have installed PyTorch and HuggingFace transformers, you can import them in your Python script or notebook using the following commands:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

The first command imports the torch module, which contains the main classes and functions for PyTorch. The second command imports two specific classes from the transformers module: AutoModelForCausalLM and AutoTokenizer. These classes will allow us to load and use a pre-trained GPT-2 model and its corresponding tokenizer, which we will explain in the next section.

By installing and importing PyTorch and HuggingFace transformers, you have prepared your environment for using GPT-2 for text generation. In the next section, we will show you how to load and use a pre-trained GPT-2 model.

4. How to load and use a pre-trained GPT-2 model

Now that you have installed and imported PyTorch and HuggingFace transformers, you are ready to load and use a pre-trained GPT-2 model for text generation. A pre-trained model is a model that has been trained on a large amount of data and can be used for a specific task without requiring further training. In this case, we will use a pre-trained GPT-2 model that has been trained on over 40 GB of text from the internet, and can generate realistic and diverse text for various domains and styles.

To load a pre-trained GPT-2 model, you need to use the AutoModelForCausalLM class from the transformers module. This class can automatically load and instantiate any causal language model, which is a type of language model that can generate text based on a given input. GPT-2 is an example of a causal language model, as it can generate text that follows the input text in a causal way.

To use the AutoModelForCausalLM class, you need to specify the name of the pre-trained model that you want to load. In this case, we will use the “gpt2” model, which is the base version of GPT-2 that has 124 million parameters. There are also other versions of GPT-2 that have more parameters and can generate better text, such as “gpt2-medium”, “gpt2-large”, and “gpt2-xl”, but they also require more computational resources and memory to run. You can find the list of all available pre-trained models from HuggingFace here: https://huggingface.co/models

To load the “gpt2” model, you can use the following code:

model = AutoModelForCausalLM.from_pretrained("gpt2")

This code will download the pre-trained model from HuggingFace’s servers and store it in a variable called model. You can then use this model to generate text with the input of your choice.

To generate text with the pre-trained GPT-2 model, you need to use the generate method of the model. This method can take various arguments to control the text generation process, such as the input text, the maximum length of the output text, the temperature, the top-k, the top-p, and more. You can find the full documentation of the generate method here: https://huggingface.co/transformers/main_classes/model.html#transformers.generation_utils.GenerationMixin.generate

For example, if you want to generate text with the input “Hello, world”, you can use the following code:

input_text = "Hello, world"
input_ids = tokenizer.encode(input_text, return_tensors="pt")
output_ids = model.generate(input_ids)
output_text = tokenizer.decode(output_ids[0])
print(output_text)

This code will do the following steps:

  • It will create a variable called input_text and assign it the value “Hello, world”. This is the input text that we want to use for text generation.
  • It will use the tokenizer to encode the input text into a sequence of numerical ids that the model can understand. The tokenizer is a class that can convert text into ids and vice versa, and it is specific to each model. We will explain how to load and use the tokenizer in the next paragraph. The tokenizer.encode method will return a tensor of shape (1, n), where n is the number of tokens in the input text. We will store this tensor in a variable called input_ids.
  • It will use the model to generate text based on the input_ids. The model.generate method will return a tensor of shape (1, m), where m is the number of tokens in the output text. We will store this tensor in a variable called output_ids.
  • It will use the tokenizer to decode the output_ids into a string of text that we can read. The tokenizer.decode method will take the first element of the output_ids tensor (which is a tensor of shape (m,)) and convert it into a string. We will store this string in a variable called output_text.
  • It will print the output_text to the console.

If you run this code, you might get something like this:

Hello, world! I am a text generator powered by GPT-2, a pre-trained language model that can produce realistic and diverse text. I can generate text for various domains and styles, depending on the input text that you give me. What do you want me to write about?

As you can see, the model has generated a text that follows the input text in a causal way, and introduces itself and its capabilities. The text is also coherent and fluent, and it uses punctuation and capitalization correctly. However, the text is also somewhat generic and vague, and it does not provide any specific or interesting information. This is because we have used the base version of GPT-2, which is not very powerful compared to the larger versions. We have also used the default arguments for the generate method, which are not very optimal for text generation. We will show you how to improve the text quality and diversity in the next sections.

Before we move on, we need to explain how to load and use the tokenizer that we have used in the previous code. The tokenizer is a class that can convert text into ids and vice versa, and it is specific to each model. To load the tokenizer that corresponds to the “gpt2” model, you need to use the AutoTokenizer class from the transformers module. This class can automatically load and instantiate any tokenizer, given the name of the pre-trained model. To load the “gpt2” tokenizer, you can use the following code:

tokenizer = AutoTokenizer.from_pretrained("gpt2")

This code will download the pre-trained tokenizer from HuggingFace’s servers and store it in a variable called tokenizer. You can then use this tokenizer to encode and decode text, as we have shown in the previous code.

By loading and using a pre-trained GPT-2 model and its corresponding tokenizer, you have learned how to generate text with PyTorch and HuggingFace transformers. In the next section, we will show you how to fine-tune GPT-2 on your own text data, to improve the text quality and relevance for your specific task or domain.

5. How to fine-tune GPT-2 on your own text data

One of the advantages of using a pre-trained GPT-2 model is that you can fine-tune it on your own text data, to improve the text quality and relevance for your specific task or domain. Fine-tuning is a process of training a pre-trained model on a smaller amount of data, to adapt it to a new task or domain, without losing its general knowledge and ability. For example, you can fine-tune GPT-2 on a corpus of movie scripts to generate better dialogues, or on a collection of poems to generate more creative verses.

To fine-tune GPT-2 on your own text data, you need to follow these steps:

  1. Prepare your text data. You need to have a text file that contains your text data, with one sample per line. For example, if you want to fine-tune GPT-2 on movie scripts, you need to have a text file that contains one movie script per line. You also need to make sure that your text data is clean and consistent, and that it does not contain any unwanted characters or symbols.
  2. Load your text data. You need to use the tokenizer to encode your text data into a sequence of ids that the model can understand. You can use the tokenizer.encode_plus method, which can take a text file as input and return a dictionary of tensors that contain the input ids, the attention masks, and the token type ids. The input ids are the numerical ids of the tokens in the text, the attention masks are binary tensors that indicate which tokens are relevant for the model, and the token type ids are optional tensors that indicate the type of each token (such as sentence A or sentence B). You can find the full documentation of the tokenizer.encode_plus method here: https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.PreTrainedTokenizer.encode_plus
  3. Create a PyTorch dataset. You need to use the torch.utils.data.Dataset class to create a PyTorch dataset that can store and handle your text data. You can subclass this class and override the __init__, __len__, and __getitem__ methods, to define how to initialize, count, and access your text data. You can find the full documentation of the torch.utils.data.Dataset class here: https://pytorch.org/docs/stable/data.html#torch.utils.data.Dataset
  4. Create a PyTorch data loader. You need to use the torch.utils.data.DataLoader class to create a PyTorch data loader that can load and batch your text data. You can pass your PyTorch dataset as an argument to this class, and specify other arguments such as the batch size, the shuffle option, and the number of workers. The batch size is the number of samples that are processed together, the shuffle option is a boolean value that indicates whether to shuffle the data or not, and the number of workers is the number of processes that are used to load the data. You can find the full documentation of the torch.utils.data.DataLoader class here: https://pytorch.org/docs/stable/data.html#torch.utils.data.DataLoader
  5. Define the training parameters. You need to define some parameters that will control the training process, such as the number of epochs, the learning rate, the weight decay, and the gradient clipping. The number of epochs is the number of times that the model will go through the entire data, the learning rate is the factor that determines how much the model will update its weights, the weight decay is the factor that prevents the model from overfitting, and the gradient clipping is the technique that prevents the model from exploding or vanishing gradients. You can use the transformers.TrainingArguments class to define these parameters, and pass them to the transformers.Trainer class, which will handle the training process. You can find the full documentation of the transformers.TrainingArguments class here: https://huggingface.co/transformers/main_classes/trainer.html#transformers.TrainingArguments
  6. Train the model. You need to use the transformers.Trainer class to train the model on your text data. You can pass the model, the data loader, and the training arguments as arguments to this class, and call the train method to start the training process. You can also pass a validation data loader and a validation metric as arguments, to evaluate the model on a validation set during the training process. You can find the full documentation of the transformers.Trainer class here: https://huggingface.co/transformers/main_classes/trainer.html#transformers.Trainer
  7. Save the model. You need to use the save_pretrained method of the model to save the fine-tuned model to a directory of your choice. You can also use the save_pretrained method of the tokenizer to save the tokenizer to the same directory. This will allow you to load and use the fine-tuned model and tokenizer later. You can find the full documentation of the save_pretrained method here: https://huggingface.co/transformers/main_classes/model.html#transformers.PreTrainedModel.save_pretrained

By following these steps, you have learned how to fine-tune GPT-2 on your own text data, to improve the text quality and relevance for your specific task or domain. In the next section, we will show you how to generate text with GPT-2 using different decoding strategies, to control the text diversity and creativity.

6. How to generate text with GPT-2 using different decoding strategies

Generating text with GPT-2 is not a deterministic process, meaning that the same input can produce different outputs each time. This is because the model uses a probabilistic approach to select the next token to generate, based on the previous tokens and the input text. However, not all tokens have the same probability of being selected, and some tokens are more likely than others to follow the previous tokens and the input text. Therefore, the way that the model selects the next token can affect the quality and diversity of the generated text.

To control the way that the model selects the next token, we can use different decoding strategies, which are algorithms that define how to choose the next token from a set of possible candidates. There are many decoding strategies that can be used for text generation, but we will focus on three of the most common and effective ones: greedy decoding, beam search, and top-k sampling.

Greedy decoding is the simplest and fastest decoding strategy, but also the least diverse and creative. It works by selecting the token that has the highest probability of being the next token, according to the model. For example, if the model predicts that the next token has a 90% chance of being “the”, a 5% chance of being “a”, and a 5% chance of being “an”, greedy decoding will always choose “the” as the next token. Greedy decoding can produce coherent and fluent text, but it can also produce repetitive and boring text, as it always chooses the most likely option.

Beam search is a more advanced and slower decoding strategy, but also more diverse and accurate. It works by keeping track of a fixed number of possible sequences of tokens, called beams, and expanding each beam with the next token that has the highest probability, according to the model. For example, if the model predicts that the next token has a 90% chance of being “the”, a 5% chance of being “a”, and a 5% chance of being “an”, and we use a beam size of 2, beam search will keep two beams: one with “the” as the next token, and one with “a” as the next token. Then, it will expand each beam with the next token that has the highest probability, according to the model, and keep the two beams that have the highest overall probability. Beam search can produce more diverse and accurate text, but it can also produce inconsistent and unnatural text, as it can switch between different beams.

Top-k sampling is a more random and faster decoding strategy, but also more creative and unpredictable. It works by sampling the next token from a subset of the possible candidates, called the top-k, which are the k tokens that have the highest probability of being the next token, according to the model. For example, if the model predicts that the next token has a 90% chance of being “the”, a 5% chance of being “a”, and a 5% chance of being “an”, and we use a top-k value of 2, top-k sampling will sample the next token from the two tokens that have the highest probability: “the” and “a”. Top-k sampling can produce more creative and unpredictable text, but it can also produce incoherent and irrelevant text, as it can choose any token from the top-k.

To use different decoding strategies with the pre-trained GPT-2 model, you need to pass different arguments to the generate method of the model. For example, to use greedy decoding, you can use the following code:

output_ids = model.generate(input_ids, do_sample=False)

This code will set the do_sample argument to False, which means that the model will use greedy decoding to select the next token. To use beam search, you can use the following code:

output_ids = model.generate(input_ids, do_sample=False, num_beams=5)

This code will set the do_sample argument to False, and the num_beams argument to 5, which means that the model will use beam search with a beam size of 5 to select the next token. To use top-k sampling, you can use the following code:

output_ids = model.generate(input_ids, do_sample=True, top_k=10)

This code will set the do_sample argument to True, and the top_k argument to 10, which means that the model will use top-k sampling with a top-k value of 10 to select the next token.

By using different decoding strategies with GPT-2, you have learned how to control the text diversity and creativity for text generation. In the next section, we will show you how to evaluate the quality and diversity of the generated text, using different metrics and methods.

7. How to evaluate the quality and diversity of the generated text

Now that you have learned how to generate text with GPT-2 using different decoding strategies, you might be wondering how to evaluate the quality and diversity of the generated text. Evaluating text generation is not a trivial task, as there is no single objective metric that can capture all the aspects of good text, such as fluency, coherence, relevance, informativeness, creativity, and originality. Moreover, different applications and domains might have different expectations and preferences for the generated text.

However, there are some common methods and metrics that can help you assess the quality and diversity of the generated text, both quantitatively and qualitatively. In this section, we will introduce some of these methods and metrics, and show you how to use them with PyTorch and HuggingFace.

One of the most widely used methods for evaluating text generation is human evaluation, which involves asking human judges to rate the generated text according to some criteria, such as readability, accuracy, relevance, and diversity. Human evaluation is considered the most reliable and valid method, as it reflects the actual perception and preference of the target audience. However, human evaluation is also costly, time-consuming, and subjective, as it requires recruiting and instructing human judges, and aggregating and analyzing their ratings.

Another method for evaluating text generation is automatic evaluation, which involves using some mathematical formulas or algorithms to compute some scores or statistics for the generated text, based on some reference text or some linguistic features. Automatic evaluation is convenient, fast, and objective, as it can be easily implemented and applied to a large amount of text. However, automatic evaluation is also limited, imperfect, and biased, as it cannot capture all the nuances and subtleties of natural language, and it might not correlate well with human judgment.

Some of the most common metrics for automatic evaluation are:

  • Perplexity: Perplexity measures how well a language model can predict the next word in a sequence of text, based on the probability distribution of the words. A lower perplexity means a higher probability, and a higher perplexity means a lower probability. Perplexity can be used to measure the fluency and coherence of the generated text, as well as the generalization and adaptation of the language model. However, perplexity does not account for the relevance, informativeness, or diversity of the generated text, and it might be influenced by the length and domain of the text.
  • BLEU: BLEU stands for Bilingual Evaluation Understudy, and it is a metric that compares the generated text with one or more reference texts, based on the number of matching n-grams (sequences of n words) between them. A higher BLEU score means a higher similarity, and a lower BLEU score means a lower similarity. BLEU can be used to measure the accuracy and relevance of the generated text, especially for text translation and text summarization tasks. However, BLEU does not account for the fluency, coherence, or diversity of the generated text, and it might penalize creative or original text that deviates from the reference text.
  • ROUGE: ROUGE stands for Recall-Oriented Understudy for Gisting Evaluation, and it is a metric that compares the generated text with one or more reference texts, based on the number of overlapping n-grams, word sequences, or word pairs between them. A higher ROUGE score means a higher overlap, and a lower ROUGE score means a lower overlap. ROUGE can be used to measure the informativeness and relevance of the generated text, especially for text summarization and text generation tasks. However, ROUGE does not account for the fluency, coherence, or diversity of the generated text, and it might favor longer or redundant text that covers more of the reference text.
  • METEOR: METEOR stands for Metric for Evaluation of Translation with Explicit ORdering, and it is a metric that compares the generated text with one or more reference texts, based on the number of matching words or phrases between them, with some adjustments for word order and word forms. A higher METEOR score means a higher match, and a lower METEOR score means a lower match. METEOR can be used to measure the accuracy and relevance of the generated text, especially for text translation and text generation tasks. However, METEOR does not account for the fluency, coherence, or diversity of the generated text, and it might be sensitive to the choice and quality of the reference text.
  • Self-BLEU: Self-BLEU is a metric that compares the generated text with other texts generated by the same model, based on the BLEU score. A lower self-BLEU score means a higher diversity, and a higher self-BLEU score means a lower diversity. Self-BLEU can be used to measure the diversity and originality of the generated text, especially for text generation and text augmentation tasks. However, self-BLEU does not account for the quality, fluency, coherence, relevance, or informativeness of the generated text, and it might be affected by the number and length of the generated texts.

To use these metrics with PyTorch and HuggingFace, you can use the datasets library, which provides a collection of NLP metrics and evaluation tools. You can install the library with the following command:

pip install datasets

Then, you can import the library and load the metrics you want to use, such as:

from datasets import load_metric
perplexity = load_metric("perplexity")
bleu = load_metric("bleu")
rouge = load_metric("rouge")
meteor = load_metric("meteor")
self_bleu = load_metric("self_bleu")

Next, you can compute the metrics for the generated text and the reference text, such as:

# Assume that you have a list of generated texts and a list of reference texts
generated_texts = [...]
reference_texts = [...]

# Compute perplexity for the generated texts
# Assume that you have a pre-trained language model and a tokenizer
model = ...
tokenizer = ...
perplexity_scores = []
for text in generated_texts:
  # Encode the text and get the logits
  input_ids = tokenizer.encode(text, return_tensors="pt")
  logits = model(input_ids).logits
  # Compute the perplexity score
  perplexity_score = perplexity.compute(predictions=logits, references=input_ids)
  perplexity_scores.append(perplexity_score)

# Compute BLEU for the generated texts and the reference texts
bleu_score = bleu.compute(predictions=generated_texts, references=reference_texts)

# Compute ROUGE for the generated texts and the reference texts
rouge_score = rouge.compute(predictions=generated_texts, references=reference_texts)

# Compute METEOR for the generated texts and the reference texts
meteor_score = meteor.compute(predictions=generated_texts, references=reference_texts)

# Compute self-BLEU for the generated texts
self_bleu_score = self_bleu.compute(predictions=generated_texts)

Finally, you can print or plot the metrics to see the results, such as:

# Print the average perplexity score
print(f"Average perplexity: {sum(perplexity_scores) / len(perplexity_scores)}")

# Print the BLEU score
print(f"BLEU: {bleu_score}")

# Print the ROUGE score
print(f"ROUGE: {rouge_score}")

# Print the METEOR score
print(f"METEOR: {meteor_score}")

# Print the self-BLEU score
print(f"Self-BLEU: {self_bleu_score}")

# Plot the perplexity scores for each generated text
import matplotlib.pyplot as plt
plt.bar(range(len(generated_texts)), perplexity_scores)
plt.xlabel("Generated text")
plt.ylabel("Perplexity")
plt.show()

These are some of the methods and metrics that you can use to evaluate the quality and diversity of the generated text with PyTorch and HuggingFace. However, you should keep in mind that these methods and metrics are not perfect, and they might not reflect the true value and impact of the generated text. Therefore, you should always use them with caution and critical thinking, and complement them with other methods and metrics that are more suitable for your specific task and domain.

8. Conclusion and further resources

In this blog, you have learned how to use PyTorch and HuggingFace to generate realistic text with GPT-2, a pre-trained language model that can produce impressive results. You have covered the following topics:

  • What is GPT-2 and why is it important for NLP?
  • How to install and import PyTorch and HuggingFace transformers
  • How to load and use a pre-trained GPT-2 model
  • How to fine-tune GPT-2 on your own text data
  • How to generate text with GPT-2 using different decoding strategies
  • How to evaluate the quality and diversity of the generated text

By following this blog, you have gained a solid understanding of how to use GPT-2 for text generation and how to apply it to your own projects. You have also seen some fun and creative examples of text generated by GPT-2. We hope that you have enjoyed this blog and learned something new and useful.

If you want to learn more about PyTorch, HuggingFace, GPT-2, and text generation, here are some further resources that you can check out:

  • PyTorch: The official website of PyTorch, where you can find tutorials, documentation, and community forums.
  • HuggingFace: The official website of HuggingFace, where you can find models, datasets, metrics, and libraries for NLP.
  • Better Language Models and Their Implications: The original blog post by OpenAI that introduced GPT-2 and its capabilities and challenges.
  • Language Models are Few-Shot Learners: The research paper by OpenAI that presented GPT-3, the successor of GPT-2, and its performance on various NLP tasks.
  • Text Generation: A website that collects research papers and code for text generation and related tasks.

Thank you for reading this blog, and happy text generation!

Leave a Reply

Your email address will not be published. Required fields are marked *