From a089e8b06418a9a20d4e66c4d5261e82b2b617a4 Mon Sep 17 00:00:00 2001 From: locch Date: Mon, 30 Dec 2024 16:13:16 +0700 Subject: [PATCH] update --- .gitignore | 5 + docs/GEN_AL_LLM_CHEATSHEET.pdf | Bin 0 -> 275470 bytes .../Data_Preparations_for_BERT.ipynb | 1304 ++++++ .../Encoder_Models_with_Baby_BERT.ipynb | 3889 +++++++++++++++++ .../M3-L3-Transformers_for_Translation.ipynb | 2771 ++++++++++++ .../Multi30K_de_en_dataloader.py.1 | 102 + notebooks/LLM_Specialization/input_de.pdf | Bin 0 -> 27628 bytes notebooks/LLM_Specialization/output_en.pdf | Bin 0 -> 2164 bytes .../train_bert_data_new.csv | 1 + 9 files changed, 8072 insertions(+) create mode 100644 docs/GEN_AL_LLM_CHEATSHEET.pdf create mode 100644 notebooks/LLM_Specialization/Data_Preparations_for_BERT.ipynb create mode 100644 notebooks/LLM_Specialization/Encoder_Models_with_Baby_BERT.ipynb create mode 100644 notebooks/LLM_Specialization/M3-L3-Transformers_for_Translation.ipynb create mode 100644 notebooks/LLM_Specialization/Multi30K_de_en_dataloader.py.1 create mode 100644 notebooks/LLM_Specialization/input_de.pdf create mode 100644 notebooks/LLM_Specialization/output_en.pdf create mode 100644 notebooks/LLM_Specialization/train_bert_data_new.csv diff --git a/.gitignore b/.gitignore index cfb83d7..899583a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__ *my_cache *test_trainer *test-trainer +bert_dataset src tmp @@ -21,3 +22,7 @@ llama2.c *.pd *.bin *.h5 + +*.tar +*.zip +*.rar diff --git a/docs/GEN_AL_LLM_CHEATSHEET.pdf b/docs/GEN_AL_LLM_CHEATSHEET.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f78c801e505c9396c885654329ec46b78b97b77 GIT binary patch literal 275470 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
AD7` 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

f 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 B?Un!l literal 0 HcmV?d00001 diff --git a/notebooks/LLM_Specialization/Data_Preparations_for_BERT.ipynb b/notebooks/LLM_Specialization/Data_Preparations_for_BERT.ipynb new file mode 100644 index 0000000..34eab80 --- /dev/null +++ b/notebooks/LLM_Specialization/Data_Preparations_for_BERT.ipynb @@ -0,0 +1,1304 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "23d979f2-56f3-4e6e-8cce-193df90a7c28", + "metadata": {}, + "source": [ + "

\n", + " \n", + " \"Skills\n", + " \n", + "

\n" + ] + }, + { + "cell_type": "markdown", + "id": "6b83d930-50a6-4c57-9f0d-11705e3fd026", + "metadata": {}, + "source": [ + "# **Data Loading and Text Processing for BERT**\n" + ] + }, + { + "cell_type": "markdown", + "id": "00a8a79d-0d93-4680-89f1-855fcadd733d", + "metadata": {}, + "source": [ + "Estimated time needed: **45** minutes\n", + "(When using the pre-trained model provided)\n" + ] + }, + { + "cell_type": "markdown", + "id": "d7a36d2a-3750-4d95-a568-32b30520d710", + "metadata": {}, + "source": [ + "In this hands-on lab, you will learn essential techniques and steps to prepare your data for training BERT models effectively. BERT (Bidirectional Encoder Representations from Transformers) has revolutionized natural language processing tasks by capturing contextual information from both left and right contexts. To harness the power of BERT, you will cover various topics, including random sample selection, tokenization, vocabulary building, text masking, and data preparation for masked language model (MLM) and next sentence prediction (NSP) tasks. By the end of this project, you will have the skills to preprocess your data and create training-ready inputs for BERT models. Let's dive in and get started!\n" + ] + }, + { + "cell_type": "markdown", + "id": "c8372b71-612d-4366-84c4-151b213db093", + "metadata": {}, + "source": [ + "## __Table of contents__\n", + "\n", + "
    \n", + "
  1. Objectives
  2. \n", + "
  3. \n", + " Setup\n", + "
      \n", + "
    1. Installing required libraries
    2. \n", + "
    3. Importing required libraries
    4. \n", + "
    \n", + "
  4. \n", + "
  5. \n", + " Tokenization and vocabulary building\n", + "
      \n", + "
    1. Tokenization
    2. \n", + "
    3. Vocabulary building
    4. \n", + "
    \n", + "
  6. \n", + "
  7. \n", + " Text masking and data preparation for BERT\n", + "
      \n", + "
    1. Text masking
    2. \n", + "
    3. Data preparation for MLM
    4. \n", + "
    5. Data preparation for NSP
    6. \n", + "
    7. Finalizing BERT inputs
    8. \n", + "
    \n", + "
  8. \n", + "
  9. \n", + " Preparing the dataset\n", + "
  10. \n", + "
  11. \n", + " Exercises\n", + "
      \n", + "
    1. Exercise 1 - Initializing the BERTTokenizer
    2. \n", + "
    3. Exercise 2 - Tokenizing the dataset
    4. \n", + "
    5. Exercise 3 - Building the vocabulary with special tokens
    6. \n", + "
    \n", + "
  12. \n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "e3433f0f-6281-46a5-b770-38edf3b2236d", + "metadata": {}, + "source": [ + "## Objectives\n", + "\n", + "After completing this lab, you will be able to:\n", + "\n", + "- Understand the process of random sample selection and tokenization\n", + "- Apply tokenization techniques and build a vocabulary for text processing\n", + "- Implement text masking and prepare data specifically for BERT models\n", + "- Gain proficiency in text masking to create masked language model (MLM) training data\n", + "- Prepare data for next sentence prediction (NSP) training\n" + ] + }, + { + "cell_type": "markdown", + "id": "4cf08179-a25e-4107-927b-a6ec663f7e71", + "metadata": {}, + "source": [ + "----\n" + ] + }, + { + "cell_type": "markdown", + "id": "5ed9adc0-ff9a-4a00-b02c-79f2c2cce146", + "metadata": {}, + "source": [ + "## Setup\n" + ] + }, + { + "cell_type": "markdown", + "id": "8b0ce05f-9e30-4513-9142-f5105a03e99c", + "metadata": {}, + "source": [ + "### Installing required libraries\n", + "\n", + "The following required libraries are __not__ pre-installed in the Skills Network Labs environment. __You will need to run the following cell__ to install them:\n", + "\n", + "```bash\n", + "! pip install 'portalocker>=2.0.0'\n", + "! pip install 'torchtext==0.16.0'\n", + "! pip install transformers==4.39.1\n", + "! pip install pandas\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "ae63e3eb-af2e-4e1b-b796-45dbbb6c88e5", + "metadata": {}, + "source": [ + "### Importing required libraries\n", + "\n", + "\n", + "In this section, you will import the necessary libraries and modules to prepare your dataset for training with PyTorch. The focus is on text processing and creating data loaders that will be used for training your models.\n", + "\n", + "- **DataLoader**: A PyTorch utility that allows you to load data in batches, making it easier to manage large datasets during training.\n", + "- **build_vocab_from_iterator**: A function from `torchtext.vocab` that creates a vocabulary object from an iterator. The vocabulary is crucial for text processing, as it maps tokens (words) to integers.\n", + "- **Vocab**: Represents the vocabulary, a mapping of tokens to indices. This is used to convert text data into a numerical form that the model can understand.\n", + "- **Tensor, torch, nn, Transformer**: Core PyTorch modules and classes for tensors (the fundamental data structure in PyTorch), neural network layers, and the Transformer model architecture.\n", + "- **get_tokenizer**: A function from `torchtext.data.utils` that returns a tokenizer to convert text strings into token lists.\n", + "- **pad_sequence**: A utility from `torch.nn.utils.rnn` that pads sequences to the same length, a common requirement for batch processing in models that deal with variable-length sequences.\n", + "\n", + "This setup is essential for processing text data, converting it into numerical format, and preparing batches of data for training neural networks, particularly for tasks like sequence modeling and classification.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7ef1f039-d1e7-4825-88d4-869f1021de91", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "Tesla P40\n", + "Import Successfully!\n" + ] + } + ], + "source": [ + "import os\n", + "import torch\n", + "\n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"\n", + "print(torch.cuda.is_available())\n", + "print(torch.cuda.get_device_name())\n", + "\n", + "import torch.nn as nn\n", + "from torch.utils.data import DataLoader\n", + "from torchtext.vocab import build_vocab_from_iterator\n", + "from torchtext.vocab import Vocab\n", + "from torch import Tensor\n", + "from torch.nn import Transformer\n", + "from torchtext.data.utils import get_tokenizer\n", + "from torch.nn.utils.rnn import pad_sequence\n", + "from itertools import chain\n", + "from itertools import islice\n", + "from torchtext.datasets import IMDB\n", + "from copy import deepcopy\n", + "import random\n", + "import csv\n", + "import json\n", + "from tqdm import tqdm\n", + "import pandas as pd\n", + "\n", + "# You can also use this section to suppress warnings generated by your code:\n", + "def warn(*args, **kwargs):\n", + " pass\n", + "import warnings\n", + "warnings.warn = warn\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "print(\"Import Successfully!\")" + ] + }, + { + "cell_type": "markdown", + "id": "d5fb2cac-a4e0-4fd8-8b55-42b860c39d23", + "metadata": {}, + "source": [ + "## Tokenization and vocabulary building\n", + "\n", + "In this section, you will define functions and set up necessary components for text processing, crucial for preparing your dataset for model training." + ] + }, + { + "cell_type": "markdown", + "id": "5b652da0-ebda-40d2-b116-d5d8a3e63147", + "metadata": {}, + "source": [ + "### Tokenization\n", + "- The `tokenizer` is initialized to tokenize text using basic English tokenization rules, converting text samples into lists of tokens.\n", + "- `yield_tokens` is a generator function that iterates through the data, yielding tokenized versions of the text samples. This function facilitates vocabulary building by providing a stream of tokens.\n", + "- `word_dict` defines special tokens used in text processing, such as padding `[PAD]`, class `[CLS]`, separator `[SEP]`, mask `[MASK]`, and unknown `[UNK]` tokens, with their corresponding indices.\n", + "- Special symbols and their indices are explicitly defined for clarity and used throughout data preparation.\n", + "- `text_to_index` and `index_to_en` functions are utility converters. The former converts text into a list of numerical indices based on the vocabulary, and the latter reverses this process, translating a sequence of indices back into readable English text.\n", + "\n", + "- **`CLS` (Classification Token)**: This token serves as the Start of Sentence (SOS) marker. It represents the overall meaning of the entire sentence. Commonly used in tasks that require understanding the entire input, like classification.\n", + "\n", + "- **`SEP` (Separator Token)**: Used as the End of Sentence (EOS) marker. It also acts as a delimiter in scenarios where a model needs to understand and differentiate between multiple sentences, like in question-answering or sentence-pair tasks.\n", + "\n", + "- **`PAD` (Padding Token)**: This token is added to sequences to ensure all inputs are of equal length. During training, it's important to note that the `[PAD]` token, typically with an ID of 0, does not contribute to the gradient calculations.\n", + "\n", + "- **`MASK` (Masked Token)**: Utilized for word replacement in tasks like masked language modeling. It allows models to predict the identity of masked-out words, facilitating learning of bidirectional representations.\n", + "\n", + "- **`UNK` (Unknown Token)**: Acts as a placeholder for words that are not found in the tokenizer's vocabulary. This token replaces any unknown or out-of-vocabulary item in the input data.\n", + "\n", + "These components are foundational for preprocessing text data, ensuring it is in the correct format for model training, including tokenization, numerical conversion, and handling special tokens necessary for models like BERT.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6c04aa33-cbf0-487d-92bd-9a1bdd9cbac0", + "metadata": {}, + "outputs": [], + "source": [ + "tokenizer = get_tokenizer(\"basic_english\")\n", + "\n", + "def yield_tokens(data_iter):\n", + " for label, data_sample in data_iter:\n", + " yield tokenizer(data_sample)\n", + "\n", + "# Define special symbols and indices\n", + "PAD_IDX,CLS_IDX, SEP_IDX, MASK_IDX,UNK_IDX= 0, 1, 2, 3, 4\n", + "\n", + "# Make sure the tokens are in order of their indices to properly insert them in vocab\n", + "special_symbols = ['[PAD]','[CLS]', '[SEP]','[MASK]','[UNK]']" + ] + }, + { + "cell_type": "markdown", + "id": "62362568-c0f2-4d6c-b699-ff42722c78c5", + "metadata": {}, + "source": [ + "### Vocabulary building\n", + "\n", + "This section focuses on building the vocabulary from the IMDB dataset.\n", + "- You can utilize the `IMDB` dataset from `torchtext.datasets`, splitting it into training and testing sets.\n", + "- The vocabulary is built using the `build_vocab_from_iterator` function, incorporating special symbols (`[PAD]`, `[CLS]`, `[SEP]`, `[MASK]`, `[UNK]`) at the beginning.\n", + "- The `UNK_IDX` is set as the default index for unknown words, and the total vocabulary size is printed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "dc797061-e701-4d75-8699-4ebb90f9d7f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['I', 'would', 'put', 'this', 'at', 'the', 'top', 'of', 'my', 'list', 'of', 'films', 'in', 'the', 'category', 'of', 'unwatchable', 'trash!', 'There', 'are']\n" + ] + } + ], + "source": [ + "from datasets import load_dataset\n", + "from itertools import islice, chain\n", + "\n", + "# Load the IMDB dataset from Hugging Face\n", + "imdb_dataset = load_dataset(\"imdb\")\n", + "\n", + "# Create data splits\n", + "train_iter = iter(imdb_dataset['train'])\n", + "test_iter = iter(imdb_dataset['test'])\n", + "all_data_iter = iter(chain(train_iter, test_iter))\n", + "\n", + "# Define a tokenizer function to yield tokens\n", + "def yield_tokens(data_iter):\n", + " for item in data_iter:\n", + " # Tokenize the text field (replace this with your tokenizer logic)\n", + " yield item['text'].split()\n", + "\n", + "# Fetch the fifth item's tokens\n", + "fifth_item_tokens = next(islice(yield_tokens(all_data_iter), 5, None))\n", + "print(fifth_item_tokens[:20])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a8acf505-3c99-4275-93b6-d7515e82e09a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "438700\n" + ] + } + ], + "source": [ + "#create vocab : vocab is only built using train data\n", + "vocab=build_vocab_from_iterator(yield_tokens(all_data_iter),specials=special_symbols,special_first=True)\n", + "\n", + "vocab.set_default_index(UNK_IDX)\n", + "VOCAB_SIZE=len(vocab)\n", + "print(VOCAB_SIZE)" + ] + }, + { + "cell_type": "markdown", + "id": "e4a91917-7d9a-41b3-8506-af0ede5edaca", + "metadata": {}, + "source": [ + "\n", + "Now, create functions that transform token indices to token texts and vice versa. You will use them later on.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d8f6d3e4-31f6-491c-99db-c11007da08a8", + "metadata": {}, + "outputs": [], + "source": [ + "text_to_index=lambda text: [vocab(token) for token in tokenizer(text)]\n", + "index_to_en = lambda seq_en: \" \".join([vocab.get_itos()[index] for index in seq_en])" + ] + }, + { + "cell_type": "markdown", + "id": "d10b19bb-85ce-4ecc-acaa-35eb0ca039b7", + "metadata": {}, + "source": [ + "Let's check the mappings:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8cfc59cc-3de9-4afd-8762-f14de623852f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a />
2:\n", + " bert_input.append(current_bert_input)\n", + " bert_label.append(current_bert_label)\n", + " # If including raw tokens, add the current list of raw tokens to the raw tokens list\n", + " if include_raw_tokens:\n", + " raw_tokens_list.append(current_raw_tokens)\n", + "\n", + " # Reset the lists for the next sentence\n", + " current_bert_input = []\n", + " current_bert_label = []\n", + " current_raw_tokens = []\n", + " else:\n", + " # If the current sentence is too short, discard it and reset lists\n", + " current_bert_input = []\n", + " current_bert_label = []\n", + " current_raw_tokens = []\n", + "\n", + " # Add any remaining tokens as a sentence if there are any\n", + " if current_bert_input:\n", + " bert_input.append(current_bert_input)\n", + " bert_label.append(current_bert_label)\n", + " if include_raw_tokens:\n", + " raw_tokens_list.append(current_raw_tokens)\n", + "\n", + " # Return the prepared lists for BERT's MLM training\n", + " return (bert_input, bert_label, raw_tokens_list) if include_raw_tokens else (bert_input, bert_label)" + ] + }, + { + "cell_type": "markdown", + "id": "3185b573-2b4e-4581-8fd0-2c217a8d4fe1", + "metadata": {}, + "source": [ + "Now, let's check how MLM preparations transform the raw input into the input ready for training:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9b40af5d-7932-4a5a-b21f-3870f9785bf7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Without raw tokens: \t \n", + " \t original_input is: \t The sun sets behind the distant mountains. \n", + " \t bert_input is: \t [['[MASK]', 'sun', 'sets', 'behind', 'the', '[MASK]', 'mountains', '.']] \n", + " \t bert_label is: \t [['the', '[PAD]', '[PAD]', '[PAD]', '[PAD]', 'Pearlman', '[PAD]', '[PAD]']]\n", + "--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n", + "With raw tokens: \t \n", + " \t original_input is: \t The sun sets behind the distant mountains. \n", + " \t bert_input is: \t [['[MASK]', 'sun', 'sets', 'behind', 'the', '[MASK]', 'mountains', '.']] \n", + " \t bert_label is: \t [['the', '[PAD]', '[PAD]', '[PAD]', '[PAD]', 'Pearlman', '[PAD]', '[PAD]']] \n", + " \t raw_tokens_list is: \t [['the', 'sun', 'sets', 'behind', 'the', 'distant', 'mountains', '.']]\n" + ] + } + ], + "source": [ + "torch.manual_seed(100)\n", + "original_input=\"The sun sets behind the distant mountains.\"\n", + "tokens=tokenizer(original_input)\n", + "bert_input, bert_label= prepare_for_mlm(tokens, include_raw_tokens=False)\n", + "print(\"Without raw tokens: \\t \",\"\\n \\t original_input is: \\t \", original_input,\"\\n \\t bert_input is: \\t \", bert_input,\"\\n \\t bert_label is: \\t \", bert_label)\n", + "print(\"-\"*200)\n", + "torch.manual_seed(100)\n", + "bert_input, bert_label, raw_tokens_list= prepare_for_mlm(tokens, include_raw_tokens=True)\n", + "print(\"With raw tokens: \\t \",\"\\n \\t original_input is: \\t \", original_input,\"\\n \\t bert_input is: \\t \", bert_input,\"\\n \\t bert_label is: \\t \", bert_label,\"\\n \\t raw_tokens_list is: \\t \", raw_tokens_list)" + ] + }, + { + "cell_type": "markdown", + "id": "110aef09-318f-4402-a2eb-83daf6c6a0fb", + "metadata": {}, + "source": [ + "Therefore, each token in a sentence is labeled, depending on the masking operation that is applied on that token. In this example, the first \"the\" is **masked**, therefore, bert_input is [MASK] and its bert_label is 'The'. Tokens 'sun', 'sets', 'behind' and the last 'the' are not changed, so their corresponding labels are [PAD]. \"distant\" is **masked and replaced with a random token**, therefore, bert_input is [MASK] and its bert_label is 'human-scaled'. Finally, 'mountains' and '.' are **unchanged** so their corresponding labels are [PAD].\n" + ] + }, + { + "cell_type": "markdown", + "id": "5b4e84a7-4f98-45d5-933e-66e4fa81a531", + "metadata": {}, + "source": [ + "### Data preparation for NSP\n", + "\n", + "`process_for_nsp` prepares data for the NSP task by creating pairs of sentences. It labels these pairs to indicate whether the second sentence is the subsequent sentence in the original text, facilitating the model's learning of sentence relationships.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8d0af2f5-5120-4dd5-b5a3-47a6c18e8903", + "metadata": {}, + "outputs": [], + "source": [ + "def process_for_nsp(input_sentences, input_masked_labels):\n", + " \"\"\"\n", + " Prepares data for Next Sentence Prediction (NSP) task in BERT training.\n", + "\n", + " Args:\n", + " input_sentences (list): List of tokenized sentences.\n", + " input_masked_labels (list): Corresponding list of masked labels for the sentences.\n", + "\n", + " Returns:\n", + " bert_input (list): List of sentence pairs for BERT input.\n", + " bert_label (list): List of masked labels for the sentence pairs.\n", + " is_next (list): Binary label list where 1 indicates 'next sentence' and 0 indicates 'not next sentence'.\n", + " \"\"\"\n", + " if len(input_sentences) < 2:\n", + " raise ValueError(\"must have two same number of items.\")\n", + "\n", + "\n", + " # Verify that both input lists are of the same length and have a sufficient number of sentences\n", + " if len(input_sentences) != len(input_masked_labels):\n", + " raise ValueError(\"Both lists must have the same number of items.\")\n", + "\n", + " bert_input = []\n", + " bert_label = []\n", + " is_next = []\n", + "\n", + " available_indices = list(range(len(input_sentences)))\n", + "\n", + " while len(available_indices) >= 2:\n", + " if random.random() < 0.5:\n", + " # Choose two consecutive sentences to simulate the 'next sentence' scenario\n", + " index = random.choice(available_indices[:-1]) # Exclude the last index\n", + " # append list and add '[CLS]' and '[SEP]' tokens\n", + " bert_input.append([['[CLS]']+input_sentences[index]+ ['[SEP]'],input_sentences[index + 1]+ ['[SEP]']])\n", + " bert_label.append([['[PAD]']+input_masked_labels[index]+['[PAD]'], input_masked_labels[index + 1]+ ['[PAD]']])\n", + " is_next.append(1) # Label 1 indicates these sentences are consecutive\n", + "\n", + " # Remove the used indices\n", + " available_indices.remove(index)\n", + " if index + 1 in available_indices:\n", + " available_indices.remove(index + 1)\n", + " else:\n", + " # Choose two random distinct sentences to simulate the 'not next sentence' scenario\n", + " indices = random.sample(available_indices, 2)\n", + " bert_input.append([['[CLS]']+input_sentences[indices[0]]+['[SEP]'],input_sentences[indices[1]]+ ['[SEP]']])\n", + " bert_label.append([['[PAD]']+input_masked_labels[indices[0]]+['[PAD]'], input_masked_labels[indices[1]]+['[PAD]']])\n", + " is_next.append(0) # Label 0 indicates these sentences are not consecutive\n", + "\n", + " # Remove the used indices\n", + " available_indices.remove(indices[0])\n", + " available_indices.remove(indices[1])\n", + "\n", + "\n", + "\n", + " return bert_input, bert_label, is_next" + ] + }, + { + "cell_type": "markdown", + "id": "1e03390e-99df-48c1-a6c8-965e827477fa", + "metadata": {}, + "source": [ + "Let's look into some sample input sentences and create NSP pairs:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b2f4891a-2ac0-429b-8a99-2ad2fd093961", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BERT Input:\n", + "[['[CLS]', 'she', 'enjoys', 'reading', 'books', '[SEP]'], ['he', 'likes', 'playing', 'guitar', '[SEP]']]\n", + "BERT Label:\n", + "[['[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]'], ['he', '[PAD]', '[PAD]', '[PAD]', '[PAD]']]\n", + "Is Next: [1]\n", + "--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n", + "BERT Input:\n", + "[['[CLS]', 'he', 'likes', 'playing', 'guitar', '[SEP]'], ['i', 'love', 'apples', '[SEP]']]\n", + "BERT Label:\n", + "[['[PAD]', 'he', '[PAD]', '[PAD]', '[PAD]', '[PAD]'], ['[PAD]', '[PAD]', '[PAD]', '[PAD]']]\n", + "Is Next: [0]\n" + ] + } + ], + "source": [ + "#flatten the tensor\n", + "flatten = lambda l: [item for sublist in l for item in sublist]\n", + "# Sample input sentences\n", + "input_sentences = [[\"i\", \"love\", \"apples\"], [\"she\", \"enjoys\", \"reading\", \"books\"], [\"he\", \"likes\", \"playing\", \"guitar\"]]\n", + "# Create masked labels for the sentences\n", + "input_masked_labels=[]\n", + "for sentence in input_sentences:\n", + " _, current_masked_label= prepare_for_mlm(sentence, include_raw_tokens=False)\n", + " input_masked_labels.append(flatten(current_masked_label))\n", + "# Create NSP pairs and labels\n", + "random.seed(100)\n", + "bert_input, bert_label, is_next = process_for_nsp(input_sentences, input_masked_labels)\n", + "\n", + "# Print the output\n", + "print(\"BERT Input:\")\n", + "for pair in bert_input:\n", + " print(pair)\n", + "print(\"BERT Label:\")\n", + "for pair in bert_label:\n", + " print(pair)\n", + "print(\"Is Next: \", is_next)\n", + "print(\"-\"*200)\n", + "random.seed(1000)\n", + "bert_input, bert_label, is_next = process_for_nsp(input_sentences, input_masked_labels)\n", + "\n", + "# Print the output\n", + "print(\"BERT Input:\")\n", + "for pair in bert_input:\n", + " print(pair)\n", + "print(\"BERT Label:\")\n", + "for pair in bert_label:\n", + " print(pair)\n", + "print(\"Is Next: \", is_next)\n" + ] + }, + { + "cell_type": "markdown", + "id": "4b427fb7-1189-4eae-a6f0-ea9f24e90f60", + "metadata": {}, + "source": [ + "These two examples demonstrate how sentence pairs are randomly created from the sentence bank and labeled for NSP task. Special symbols [CLS] and [SEP] are first added to the input sentences. BERT label is created using the `prepare_for_mlm` function. In the first example, the second sentence follows the first sentence. Therefore, `Is Next` is 1. In the second example, the second sentence does not follow the first sentence. So, `Is Next` is 0. \n" + ] + }, + { + "cell_type": "markdown", + "id": "3803f4a9-e4f8-4949-8a1e-2fe2b42a93df", + "metadata": {}, + "source": [ + "### Finalizing BERT inputs\n", + "\n", + "`prepare_bert_final_inputs` consolidates the prepared data for MLM and NSP into a format suitable for BERT training, including converting tokens to indices, padding sequences for uniform length, and generating segment labels to distinguish between pairs of sentences. This function is the final step in preparing data for BERT, ensuring it is in the correct format for effective model training.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b19183e5-8e17-4fb5-98db-f3ae3e06e043", + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_bert_final_inputs(bert_inputs, bert_labels, is_nexts,to_tenor=True):\n", + " \"\"\"\n", + " Prepare the final input lists for BERT training.\n", + " \"\"\"\n", + " def zero_pad_list_pair(pair_, pad='[PAD]'):\n", + " pair=deepcopy(pair_)\n", + " max_len = max(len(pair[0]), len(pair[1]))\n", + " #append [PAD] to each sentence in the pair till the maximum length reaches\n", + " pair[0].extend([pad] * (max_len - len(pair[0])))\n", + " pair[1].extend([pad] * (max_len - len(pair[1])))\n", + " return pair[0], pair[1]\n", + "\n", + " #flatten the tensor\n", + " flatten = lambda l: [item for sublist in l for item in sublist]\n", + " #transform tokens to vocab indices\n", + " tokens_to_index=lambda tokens: [vocab[token] for token in tokens]\n", + "\n", + " bert_inputs_final, bert_labels_final, segment_labels_final, is_nexts_final = [], [], [], []\n", + "\n", + " for bert_input, bert_label,is_next in zip(bert_inputs, bert_labels,is_nexts):\n", + " # Create segment labels for each pair of sentences\n", + " segment_label = [[1] * len(bert_input[0]), [2] * len(bert_input[1])]\n", + "\n", + " # Zero-pad the bert_input and bert_label and segment_label\n", + " bert_input_padded = zero_pad_list_pair(bert_input)\n", + " bert_label_padded = zero_pad_list_pair(bert_label)\n", + " segment_label_padded = zero_pad_list_pair(segment_label,pad=0)\n", + "\n", + " #convert to tensors\n", + " if to_tenor:\n", + "\n", + " # Flatten the padded inputs and labels, transform tokens to their corresponding vocab indices, and convert them to tensors\n", + " bert_inputs_final.append(torch.tensor(tokens_to_index(flatten(bert_input_padded)),dtype=torch.int64))\n", + " #bert_labels_final.append(torch.tensor(tokens_to_index(flatten(bert_label_padded)),dtype=torch.int64))\n", + " bert_labels_final.append(torch.tensor(tokens_to_index(flatten(bert_label_padded)),dtype=torch.int64))\n", + " segment_labels_final.append(torch.tensor(flatten(segment_label_padded),dtype=torch.int64))\n", + " is_nexts_final.append(is_next)\n", + "\n", + " else:\n", + " # Flatten the padded inputs and labels\n", + " bert_inputs_final.append(flatten(bert_input_padded))\n", + " bert_labels_final.append(flatten(bert_label_padded))\n", + " segment_labels_final.append(flatten(segment_label_padded))\n", + " is_nexts_final.append(is_next)\n", + "\n", + " return bert_inputs_final, bert_labels_final, segment_labels_final, is_nexts_final\n" + ] + }, + { + "cell_type": "markdown", + "id": "dc9879e4-84be-4d2b-a3ba-1b30741cde37", + "metadata": {}, + "source": [ + "You can check the results using the `bert_input`, `bert_label` and `is_next` from previous example:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "47740bdf-2159-428f-9bdd-1394c07b595e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input:\t\t [[['[CLS]', 'he', 'likes', 'playing', 'guitar', '[SEP]'], ['i', 'love', 'apples', '[SEP]']]] \n", + "inputs_final:\t [tensor([ 1, 34, 1129, 379, 6493, 2, 145, 131, 32997, 2, 0, 0])] \n", + "bert labels final:\t [tensor([ 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])] \n", + "segment labels final:\t [tensor([1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0])] \n", + "is nexts final:\t [0]\n" + ] + } + ], + "source": [ + "bert_inputs_final, bert_labels_final, segment_labels_final, is_nexts_final=prepare_bert_final_inputs(bert_input, bert_label, is_next,to_tenor=True)\n", + "torch.set_printoptions(linewidth=10000)# this assures that whole output is printed in one line\n", + "print(\"input:\\t\\t\",bert_input,\"\\ninputs_final:\\t\",bert_inputs_final,\"\\nbert labels final:\\t\",bert_labels_final,\"\\nsegment labels final:\\t\",segment_labels_final,\"\\nis nexts final:\\t\",is_nexts_final)" + ] + }, + { + "cell_type": "markdown", + "id": "6d831591-d3b0-4366-bd4c-7c11b96e82dc", + "metadata": {}, + "source": [ + "Sentences are zero-padded and each token is mapped to its vocab index(`[CLS]`>>1, `he`>>33, ..., `[SEP]`>>2,`[PAD]`>>0])\n" + ] + }, + { + "cell_type": "markdown", + "id": "2c5524ee-26cf-45f1-ba49-d7b34e1779b5", + "metadata": {}, + "source": [ + "Mask labels are also padded and mapped to vocab indices. In this case, all tokens are **unchanged** except the token, `he` which is masked:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0f6ab1b0-a488-42fd-ba7d-5fdecba5bcf7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input:\t\t [[['[CLS]', 'he', 'likes', 'playing', 'guitar', '[SEP]'], ['i', 'love', 'apples', '[SEP]']]] \n", + "mask_label:\t [[['[PAD]', 'he', '[PAD]', '[PAD]', '[PAD]', '[PAD]'], ['[PAD]', '[PAD]', '[PAD]', '[PAD]']]] \n", + "labels_final: \t [tensor([ 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])]\n" + ] + } + ], + "source": [ + "print(\"input:\\t\\t\",bert_input,\"\\nmask_label:\\t\",bert_label, \"\\nlabels_final: \\t\",bert_labels_final)" + ] + }, + { + "cell_type": "markdown", + "id": "dfb4caff-8c85-4751-be3d-678373861916", + "metadata": {}, + "source": [ + "Finally, segment labels are created, where tokens of the first sentence are labeled with 1, tokens of the second sentence are labeled with 2 and zero-paddings are labeled with 0. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1938e34d-3743-4785-86a9-fd4ffdbcd16f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "inputs_final:\t [tensor([ 1, 34, 1129, 379, 6493, 2, 145, 131, 32997, 2, 0, 0])] \n", + "segment_labels:\t [tensor([1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0])]\n" + ] + } + ], + "source": [ + "print(\"\\ninputs_final:\\t\",bert_inputs_final,\"\\nsegment_labels:\\t\",segment_labels_final)" + ] + }, + { + "cell_type": "markdown", + "id": "a45ff8f5-0142-46ee-ac83-49721c43ea7a", + "metadata": {}, + "source": [ + "## Preparing the dataset\n", + "\n", + "- A CSV file is created to store the data set prepared for BERT training and testing. Each row contains the original text, BERT inputs, labels, segment labels, and the NSP task label.\n", + "- The data from the IMDB data set is tokenized, processed for MLM, and then for NSP. The results are formatted and written to the CSV file, providing a comprehensive data set for BERT model training.\n", + "\n", + "This process is critical for ensuring the data is in the right format for effective training of BERT on the IMDB data set, focusing on understanding text context and relationships between sentences.\n", + "\n", + ">This training process might take about 2 to 3 hours.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "819b8a3e-e5d4-4ed9-9164-68ae9cee5196", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing samples: 0it [00:00, ?it/s]\n" + ] + } + ], + "source": [ + "csv_file_path ='train_bert_data_new.csv'\n", + "with open(csv_file_path, mode='w', newline='', encoding='utf-8') as file:\n", + " csv_writer = csv.writer(file)\n", + " csv_writer.writerow(['Original Text', 'BERT Input', 'BERT Label', 'Segment Label', 'Is Next'])\n", + "\n", + " # Wrap train_iter with tqdm for a progress bar\n", + " for n, (_, sample) in enumerate(tqdm(train_iter, desc=\"Processing samples\")):\n", + " # Tokenize the sample input\n", + " tokens = tokenizer(sample)\n", + " # Create MLM inputs and labels\n", + " bert_input, bert_label = prepare_for_mlm(tokens, include_raw_tokens=False)\n", + " if len(bert_input) < 2:\n", + " continue\n", + " # Create NSP pairs, token labels, and is_next label\n", + " bert_inputs, bert_labels, is_nexts = process_for_nsp(bert_input, bert_label)\n", + " # add zero-paddings, map tokens to vocab indices and create segment labels\n", + " bert_inputs, bert_labels, segment_labels, is_nexts = prepare_bert_final_inputs(bert_inputs, bert_labels, is_nexts)\n", + " # convert tensors to lists, convert lists to JSON-formatted strings\n", + " for bert_input, bert_label, segment_label, is_next in zip(bert_inputs, bert_labels, segment_labels, is_nexts):\n", + " bert_input_str = json.dumps(bert_input.tolist())\n", + " bert_label_str = json.dumps(bert_label.tolist())\n", + " segment_label_str = ','.join(map(str, segment_label.tolist()))\n", + " # Write the data to a CSV file row-by-row\n", + " csv_writer.writerow([sample, bert_input_str, bert_label_str, segment_label_str, is_next])" + ] + }, + { + "cell_type": "markdown", + "id": "5df4e808-0334-4637-a487-bd38ed4d56ef", + "metadata": {}, + "source": [ + "# Exercises\n", + "\n", + "Learn to utilize Hugging Face's pre-trained BertTokenizer for text tokenization, including handling special tokens and preparing the IMDB dataset for NLP model training, without manually building a vocabulary.\n" + ] + }, + { + "cell_type": "markdown", + "id": "b2addc28-bc9d-42fa-a995-8ef538ec37ed", + "metadata": {}, + "source": [ + "### Exercise 1 - Initializing the BERTTokenizer\n", + "1. **Import `BertTokenizer`**:\n", + " Begin by importing the `BertTokenizer` class from the `transformers` library. This library provides access to a wide range of NLP models and their corresponding tokenizers.\n", + "\n", + "2. **Load pretrained tokenizer**:\n", + " Utilize the `from_pretrained` method to load the `bert-base-uncased` tokenizer. This tokenizer is pre-configured with a vocabulary that suits the BERT model trained on uncapitalized English text. It's ideal for understanding the basics of BERT tokenization.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a8cfab06-2c2c-4289-9d5e-978775d70857", + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import BertTokenizer\n", + "\n", + "# Load a pre-trained BERT tokenizer\n", + "tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')" + ] + }, + { + "cell_type": "markdown", + "id": "cc75f8fd-a5c8-42ed-a7f2-faff54ece783", + "metadata": {}, + "source": [ + "
\n", + " Click here for Solution\n", + "\n", + "```python\n", + "from transformers import BertTokenizer\n", + "\n", + "# Load a pre-trained BERT tokenizer\n", + "tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')\n", + "```\n", + "\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "7ef6e34d-f067-48b1-8a2b-32a555b5d76c", + "metadata": {}, + "source": [ + "### Exercise 2 - Tokenizing the dataset\n", + "1. **Define the `yield_tokens` Function**:\n", + " Implement a function named `yield_tokens` that accepts an iterator over the dataset. This function is responsible for processing and tokenizing the text data.\n", + "\n", + "2. **Tokenize Text Samples**:\n", + " Within the `yield_tokens` function, iterate through the dataset. For each text sample, use the `BertTokenizer` to tokenize the text into sequences of token IDs. Make sure to handle longer texts by setting the `truncation` parameter to `True` and specifying a `max_length` to ensure all tokenized outputs are of a manageable size.\n", + "\n", + "3. **Yield Tokenized Texts**:\n", + " After tokenizing each text sample, yield the list of token IDs. These lists will be used in subsequent steps to build data structures suitable for training NLP models.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "94c9268c-1c19-432d-a77b-633f27a62a9b", + "metadata": {}, + "outputs": [], + "source": [ + "def yield_tokens(data_iter):\n", + " for _, data_sample in data_iter:\n", + " # Use the BERT tokenizer to tokenize the text\n", + " # This returns a dictionary with 'input_ids' among other things\n", + " tokens = tokenizer(data_sample, return_tensors='pt', truncation=True, max_length=512)['input_ids'][0]\n", + " yield tokens.tolist()" + ] + }, + { + "cell_type": "markdown", + "id": "0779f62c-aaa7-4760-90e2-e020abf1c2a2", + "metadata": {}, + "source": [ + "
\n", + " Click here for Solution\n", + "\n", + "```python\n", + "def yield_tokens(data_iter):\n", + " for _, data_sample in data_iter:\n", + " # Use the BERT tokenizer to tokenize the text\n", + " # This returns a dictionary with 'input_ids' among other things\n", + " tokens = tokenizer(data_sample, return_tensors='pt', truncation=True, max_length=512)['input_ids'][0]\n", + " yield tokens.tolist()\n", + "```\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "ab0f89a5-bfdc-440e-9c9d-5195b2abb755", + "metadata": {}, + "source": [ + "### Exercise 3 - Building the vocabulary with special tokens\n", + "1. **Define Special Tokens and Indices**: Start by defining indices for special tokens such as `[PAD]`, `[CLS]`, `[SEP]`, `[MASK]`, and `[UNK]`. Create a list named `special_symbols` that includes these tokens, ensuring they are in the correct order according to their indices.\n", + "\n", + "2. **Load Dataset**: Ensure you have the IMDB dataset's training split loaded. This data will be used to build the vocabulary.\n", + "\n", + "3. **Build Vocabulary**: Utilize the `build_vocab_from_iterator` function, passing the `yield_tokens` generator function as an argument. This function iterates over the tokenized dataset and builds a vocabulary. Make sure to include the special tokens by specifying them in the `specials` argument of the `build_vocab_from_iterator` function.\n", + "(Since you are using a pre-trained tokenizer, you don't need to build the vocab from scratch. Instead, you can directly use the tokenizer's vocab.) \n", + "\n", + "4. **Set Default Index for Unknown Tokens**: After building the vocabulary, use the `set_default_index` method to specify the index for unknown tokens (`UNK_IDX`). This ensures that any tokens not found in the vocabulary are handled correctly.\n", + "(Since you are using a pre-trained tokenizer, you don't need to build the vocab from scratch. Instead, you can directly use the tokenizer's vocab.)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "9f25a317-bf7f-4391-9a90-cdd66dbd0a99", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "349ac7a960014f9686015d9191ca8825", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Map: 0%| | 0/25000 [00:00\n", + " \n", + " \"Skills\n", + " \n", + "

\n", + "\n", + "# **Pretraining BERT Models**\n", + "\n", + "Estimated time needed: **60** minutes\n", + "\n", + "In this hands-on lab, you will learn how to build a BERT model from scratch using PyTorch.\n", + "\n", + "## __Table of contents__\n", + "\n", + "
    \n", + "
  1. Objectives
  2. \n", + "
  3. \n", + " Setup\n", + "
      \n", + "
    1. Installing required libraries
    2. \n", + "
    3. Importing required libraries
    4. \n", + "
    \n", + "
  4. \n", + "
  5. \n", + " Background\n", + "
      \n", + "
    1. Introduction to pretraining
    2. \n", + "
    3. Pretraining objectives
    4. \n", + "
    5. Pretraining a BERT model
    6. \n", + "
    \n", + "
  6. \n", + "
  7. Loading data
  8. \n", + "
  9. Model creation
  10. \n", + "
  11. Evaluation
  12. \n", + "
  13. Training
  14. \n", + "
  15. Inference
  16. \n", + "
  17. \n", + " Exercises\n", + "
      \n", + "
    1. Exercise 1: Next Sentence Prediction (NSP) with BERT
    2. \n", + "
    3. Exercise 2: Masked Language Modeling (MLM) with BERT
    4. \n", + "
    \n", + "
  18. \n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "2c6d4d1b-84ed-49f6-97e3-644653b60e0a", + "metadata": {}, + "source": [ + "## Objectives\n", + "\n", + "In this interactive guide, you will delve into the core components of encoder models, with a spotlight on 'Baby BERT', a streamlined variant of the BERT model. This notebook aims to:\n", + "\n", + "- **Demystify tokenization**: Introduce the critical first step in processing text for Natural Language Processing (NLP) — tokenization. We'll learn how to convert raw text into a format that's amenable to machine processing.\n", + "- **Decode encoder models**: Shed light on the structure and function of encoder models, particularly how they capture and process the complexities of language.\n", + "- **Pretraining dynamics**: Walk through the pretraining phase of 'Baby BERT' on a bespoke dataset, showcasing how language models are equipped to understand context and meaning.\n", + "- **Task-specific proficiency**: Assess 'Baby BERT's capabilities in handling sentence sequencing and word prediction challenges through Next Sentence Prediction (NSP) and Masked Language Modeling (MLM).\n", + "- **Performance analysis**: Evaluate how well our model performs these tasks, providing insights into its linguistic comprehension and ability to predict accurately.\n" + ] + }, + { + "cell_type": "markdown", + "id": "94891731-5598-42dc-b025-ee85df063327", + "metadata": {}, + "source": [ + "## Setup\n" + ] + }, + { + "cell_type": "markdown", + "id": "95a488e5-fa89-404a-8d74-ba48fa4a06e6", + "metadata": {}, + "source": [ + "### Installing required libraries\n", + "The following required libraries are __not__ pre-installed in the Skills Network Labs environment. __You will need to run the following cell__ to install them:\n", + "\n", + "```bash\n", + "!pip install 'pandas==2.2.1'\n", + "!pip install 'portalocker>=2.0.0'\n", + "!pip install 'torchtext==0.16.0'\n", + "!pip install 'pandas==2.2.1'\n", + "!pip install transformers\n", + "!pip install matplotlib\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ef02ffc3-ada5-41f7-be39-1aa81c3966f2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "Tesla P40\n", + "Import Successfully!\n" + ] + } + ], + "source": [ + "import os\n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"\n", + "\n", + "import torch\n", + "print(torch.cuda.is_available())\n", + "print(torch.cuda.get_device_name())\n", + "\n", + "from torch.utils.data import DataLoader\n", + "from torch import Tensor\n", + "from torch.nn.utils.rnn import pad_sequence\n", + "from torch.utils.data import Dataset, DataLoader\n", + "import torch.nn as nn\n", + "from transformers import BertTokenizer\n", + "from torch.optim import Adam\n", + "import pandas as pd\n", + "import json\n", + "import math\n", + "from tqdm import tqdm\n", + "import matplotlib.pyplot as plt\n", + "from transformers import get_linear_schedule_with_warmup\n", + "\n", + "# You can also use this section to suppress warnings generated by your code:\n", + "def warn(*args, **kwargs):\n", + " pass\n", + "import warnings\n", + "warnings.warn = warn\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "print(\"Import Successfully!\")" + ] + }, + { + "cell_type": "markdown", + "id": "72b8eb55-63f9-4c68-8748-50c12fcfc441", + "metadata": {}, + "source": [ + "## Background\n", + "### Introduction to pretraining\n", + "\n", + "Pretraining involves training a model on a large corpus of unlabeled text to capture general language patterns and semantic relationships. Pretrained models can then be fine-tuned on specific downstream NLP tasks, such as sentiment analysis, question answering, or machine translation.\n", + "\n", + "The motivation behind pretraining transformers is to address the limitations of traditional approaches that require significant amounts of labeled data for each specific task. Pretraining leverages the abundance of unlabeled text data available on the internet and facilitates transfer learning, where knowledge learned from one task can be transferred to aid in solving other related tasks.\n", + "\n", + "Pretraining objectives play a crucial role in training transformers. For example, masked language modeling (MLM) involves randomly masking some words in a sentence and training the model to predict the masked words based on the surrounding context. This objective helps the model learn contextual understanding and fill in missing information. Another objective called next sentence prediction (NSP) involves predicting whether two sentences are consecutive or randomly chosen from the corpus, enabling the model to learn sentence-level relationships.\n", + "\n", + "In the next sections of this lab, you will delve deeper into pretraining objectives. By the end of this lab, you will have a solid understanding of pretraining tasks for BERT models.\n" + ] + }, + { + "cell_type": "markdown", + "id": "f944d78d-1717-4356-9d20-870da5413893", + "metadata": {}, + "source": [ + "### Pretraining objectives\n", + "\n", + "Pretraining objectives are crucial components of the pretraining process for transformers. These objectives define the tasks that the model is trained on during the pretraining phase, allowing it to learn meaningful contextual representations of language. Two commonly used pretraining objectives are masked language modeling (MLM) and next sentence prediction (NSP).\n", + "\n", + "1. Masked Language Modeling (MLM):\n", + " Masked language modeling involves randomly masking some words in a sentence and training the model to predict the masked words based on the context provided by the surrounding words(i.e., words that appear either before or after the masked word). The objective is to enable the model to learn contextual understanding and fill in missing information.\n", + "\n", + " Here's how MLM works:\n", + " - Given an input sentence, a certain percentage of the words are randomly chosen and replaced with a special [MASK] token.\n", + " - The model's task is to predict the original words that were masked, given the context of the surrounding words.\n", + " - During training, the model learns to understand the relationship between the masked words and the rest of the sentence, effectively capturing the contextual information.\n", + "\n", + "2. Next Sentence Prediction (NSP):\n", + " Next sentence prediction involves training the model to predict whether two sentences are consecutive in the original text or randomly chosen from the corpus. This objective helps the model learn sentence-level relationships and understand the coherence between sentences.\n", + "\n", + " Here's how NSP works:\n", + " - Given a pair of sentences, the model is trained to predict whether the second sentence follows the first sentence in the original text or if it is randomly selected from the corpus.\n", + " - The model learns to capture the relationships between sentences and understand the flow of information in the text.\n", + "\n", + " NSP is particularly useful for tasks that involve understanding the relationship between multiple sentences, such as question answering or document classification. By training the model to predict the coherence of sentence pairs, it learns to capture the semantic connections between them.\n", + "\n", + "It's important to note that different pretrained models may use variations or combinations of these objectives, depending on the specific architecture and training setup.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9786f979-5173-4ea8-bc75-01f23a2b6615", + "metadata": {}, + "source": [ + "### Pretraining a BERT model\n", + "Pretraining a BERT(Bidirectional Encoder Representations from Transformers) model is a complex and time-consuming process that requires a large corpus of unlabeled text data and significant computational resources. However, you provide with a simplified exercise to demonstrate the steps involved in pretraining a BERT model using the Masked Language Modeling (MLM) as well as the Next Sentence Prediction (NSP) objectives.\n", + "\n", + "You will be instructed to:\n", + "- Create train and test dataloaders from dataset\n", + "- Pretrain BERT using an MLM task\n", + "- Pretrain BERT using an NSP task\n", + "- Evaluate the trained model\n" + ] + }, + { + "cell_type": "markdown", + "id": "e1305163-9bf4-4ac8-bd0d-c3a517108166", + "metadata": {}, + "source": [ + "## Loading data\n", + "Let's load the CSV files created in the data preparation lab.\n" + ] + }, + { + "cell_type": "markdown", + "id": "fbc270c4-49fa-416a-96e4-a2f559e9071e", + "metadata": {}, + "source": [ + "```bash\n", + "%%bash\n", + "# Remove any earlier instance of the dataset loaded\n", + "if [ -d \"bert_dataset\" ]; then\n", + " rm -rf bert_dataset\n", + "fi\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "f1b2da2a-524e-419f-bac0-6cee027a8f1a", + "metadata": {}, + "source": [ + "```\n", + "!wget -O BERT_dataset.zip https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/bZaoQD52DcMpE7-kxwAG8A.zip\n", + "!unzip BERT_dataset.zip\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "29a369e5-b94f-4147-bf7f-55e933690f85", + "metadata": {}, + "source": [ + "Now, you can create a torch Dataset using the CSV file you just created:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4e84daae-e3d0-4366-a540-cb504624c5ed", + "metadata": {}, + "outputs": [], + "source": [ + "class BERTCSVDataset(Dataset):\n", + " def __init__(self, filename):\n", + " self.data = pd.read_csv(filename)\n", + "\n", + " def __len__(self):\n", + " return len(self.data)\n", + "\n", + " def __getitem__(self, idx):\n", + " row = self.data.iloc[idx]\n", + " try:\n", + " \n", + " bert_input = torch.tensor(json.loads(row['BERT Input']), dtype=torch.long)\n", + " bert_label = torch.tensor(json.loads(row['BERT Label']), dtype=torch.long)\n", + " segment_label = torch.tensor([int(x) for x in row['Segment Label'].split(',')], dtype=torch.long)\n", + " is_next = torch.tensor(row['Is Next'], dtype=torch.long)\n", + " original_text = row['Original Text'] # If you want to use it\n", + " except json.JSONDecodeError as e:\n", + " print(f\"Error decoding JSON for row {idx}: {e}\")\n", + " print(\"BERT Input:\", row['BERT Input'])\n", + " print(\"BERT Label:\", row['BERT Label'])\n", + " # Handle the error, e.g., by skipping this row or using default values\n", + " return None # or some default values\n", + " \n", + " return bert_input, bert_label, segment_label, is_next # Include original_text if needed" + ] + }, + { + "cell_type": "markdown", + "id": "188967ab-2a65-4884-bf52-3bc6be2ff673", + "metadata": {}, + "source": [ + "Next, create a collate function that applies transformations on batches of data iterator:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "59311edd-90de-4202-b40c-92cfbfcb64ca", + "metadata": {}, + "outputs": [], + "source": [ + "PAD_IDX = 0\n", + "\n", + "def collate_batch(batch):\n", + " bert_inputs_batch, bert_labels_batch, segment_labels_batch, is_nexts_batch = [], [], [], []\n", + "\n", + " for bert_input, bert_label, segment_label, is_next in batch:\n", + " # Convert each sequence to a tensor and append to the respective list\n", + " bert_inputs_batch.append(torch.tensor(bert_input, dtype=torch.long))\n", + " bert_labels_batch.append(torch.tensor(bert_label, dtype=torch.long))\n", + " segment_labels_batch.append(torch.tensor(segment_label, dtype=torch.long))\n", + " is_nexts_batch.append(is_next)\n", + "\n", + " # Pad the sequences in the batch\n", + " bert_inputs_final = pad_sequence(bert_inputs_batch, padding_value=PAD_IDX, batch_first=False)\n", + " bert_labels_final = pad_sequence(bert_labels_batch, padding_value=PAD_IDX, batch_first=False)\n", + " segment_labels_final = pad_sequence(segment_labels_batch, padding_value=PAD_IDX, batch_first=False)\n", + " is_nexts_batch = torch.tensor(is_nexts_batch, dtype=torch.long)\n", + "\n", + " return bert_inputs_final, bert_labels_final, segment_labels_final, is_nexts_batch" + ] + }, + { + "cell_type": "markdown", + "id": "fad5838c-9fb9-4240-bab2-4d9c291eea61", + "metadata": {}, + "source": [ + "Using an arbitrary batch size, you can create train and test dataloaders:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a0c3be02-d90e-4a41-b3d4-441cf3017925", + "metadata": {}, + "outputs": [], + "source": [ + "BATCH_SIZE = 2\n", + "\n", + "train_dataset_path = './bert_dataset/bert_train_data.csv'\n", + "test_dataset_path = './bert_dataset/bert_test_data.csv'\n", + "\n", + "train_dataset = BERTCSVDataset(train_dataset_path)\n", + "test_dataset = BERTCSVDataset(test_dataset_path)\n", + "\n", + "train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)\n", + "test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=collate_batch)" + ] + }, + { + "cell_type": "markdown", + "id": "b44a9111-abe0-4349-8fb3-c2a946948bb8", + "metadata": {}, + "source": [ + "## Model creation\n", + "\n", + "In BERT, positional embedding, token embedding, and segment embedding are three types of embeddings used to represent the input tokens in the model.\n", + "\n", + "1. Token Embedding: Token embedding is the initial representation of each token in a BERT model. It maps each token to a dense vector representation of a fixed size, typically referred to as the embedding size. The token embedding layer in BERT learns the contextual representations of the input tokens. These embeddings capture the semantic meaning of the tokens and their relationships with other tokens in the context.\n", + "\n", + "2. Positional Embedding: BERT is a transformer-based model that processes the input tokens in parallel. However, since transformers don't inherently capture the order of tokens, positional embedding is used to inject positional information into the model. It adds a vector representation to each token that encodes its position in the input sequence. The positional embedding allows BERT to understand the sequential order of the tokens and capture their relative positions.\n", + "\n", + "\n", + "3. Segment Embedding: BERT can handle sentence pairs or sequences that have distinct segments or parts. To differentiate between different segments, such as sentences or document sections, segment embedding is used. It assigns a unique vector representation to each segment or part of the input. The segment embeddings help BERT understand the relationships between different segments and capture the context within and between them.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41df8ee2-d52b-400b-8658-d1935ed3761e", + "metadata": {}, + "outputs": [], + "source": [ + "EMBEDDING_DIM = 10\n", + "\n", + "class TokenEmbedding(nn.Module):\n", + " def __init__(self, vocab_size, emb_size):\n", + " super(TokenEmbedding, self).__init__()\n", + " self.embedding = nn.Embedding(vocab_size, emb_size)\n", + " self.emb_size = emb_size\n", + "\n", + " def forward(self, tokens: Tensor):\n", + " return self.embedding(tokens.long()) * math.sqrt(self.emb_size)\n", + "\n", + "# Define the PositionalEncoding class as a PyTorch module for adding positional information to token embeddings\n", + "class PositionalEncoding(nn.Module):\n", + " def __init__(self, emb_size: int, dropout: float, maxlen: int = 5000):\n", + " super(PositionalEncoding, self).__init__()\n", + " # Create a positional encoding matrix as per the Transformer paper's formula\n", + " den = torch.exp(- torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)\n", + " pos = torch.arange(0, maxlen).reshape(maxlen, 1)\n", + " pos_embedding = torch.zeros((maxlen, emb_size))\n", + " pos_embedding[:, 0::2] = torch.sin(pos * den)\n", + " pos_embedding[:, 1::2] = torch.cos(pos * den)\n", + " pos_embedding = pos_embedding.unsqueeze(-2)\n", + "\n", + " self.dropout = nn.Dropout(dropout)\n", + " self.register_buffer('pos_embedding', pos_embedding)\n", + "\n", + " def forward(self, token_embedding: torch.Tensor):\n", + " # Apply the positional encodings to the input token embeddings\n", + "\n", + " return self.dropout(token_embedding + self.pos_embedding[:token_embedding.size(0), :])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4db21d26-c605-413e-9167-e1a9e42fe67b", + "metadata": {}, + "outputs": [], + "source": [ + "class BERTEmbedding (nn.Module):\n", + "\n", + " def __init__(self, vocab_size, emb_size ,dropout=0.1,train=True):\n", + "\n", + " super().__init__()\n", + "\n", + " self.token_embedding = TokenEmbedding( vocab_size,emb_size )\n", + " self.positional_encoding = PositionalEncoding(emb_size,dropout)\n", + " self.segment_embedding = nn.Embedding(3, emb_size)\n", + " self.dropout = torch.nn.Dropout(p=dropout)\n", + "\n", + " def forward(self, bert_inputs, segment_labels=False):\n", + " my_embeddings=self.token_embedding(bert_inputs)\n", + " if self.train:\n", + " x = self.dropout(my_embeddings + self.positional_encoding(my_embeddings) + self.segment_embedding(segment_labels))\n", + " else:\n", + " x = my_embeddings + self.positional_encoding(my_embeddings)\n", + "\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "id": "414ef6fd-a7a1-41ea-9c23-80b8bbb47802", + "metadata": {}, + "source": [ + "Now, define a complete BERT model with the following key components:\n", + "\n", + "1. Initialization: The `BERT` class is defined as a subclass of `torch.nn.Module`. It initializes the BERT model with parameters such as vocabulary size, model dimension, number of layers, number of attention heads, and dropout rate.\n", + "\n", + "2. Embedding Layer: The BERT model includes an embedding layer that combines token embeddings and segment embeddings using the `BERTEmbedding` class.\n", + "\n", + "3. Transformer Encoder: Transformer Encoder layers are used to encode the input embeddings. The number of layers, attention heads, dropout rate, and model dimension are specified based on the defined parameters.\n", + "\n", + "4. Next Sentence Prediction: The model has a linear layer for Next Sentence Prediction. It takes the output from the Transformer encoder and predicts the relationship between two consecutive sentences, classifying them into two classes.\n", + "\n", + "5. Masked Language Modeling: The model also includes a linear layer for Masked Language Modeling. It predicts the masked tokens in the input sequence by taking the output from the Transformer encoder and making predictions across the vocabulary.\n", + "\n", + "6. Forward Pass: The `forward` method defines the forward pass of the BERT model. It takes input tokens (`bert_inputs`) and segment labels (`segment_labels`) and returns predictions for Next Sentence Prediction and Masked Language Modeling tasks.\n" + ] + }, + { + "cell_type": "markdown", + "id": "f38b1257-bf6d-4f5a-afcd-9e7ebf090a65", + "metadata": {}, + "source": [ + "Using an example input, let's break down the embedding into its three essential components: token embedding, positional encoding and segment embedding to comprehend the process:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d290b238-b89f-408b-9863-006636d1a800", + "metadata": {}, + "outputs": [], + "source": [ + "VOCAB_SIZE=147161\n", + "batch = 2\n", + "count = 0\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "# load sample batches from dataloader\n", + "for batch in train_dataloader:\n", + " bert_inputs, bert_labels, segment_labels, is_nexts = [b.to(device) for b in batch]\n", + " count += 1\n", + " if count == 5:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "aaf22a31-43e6-415c-ab6b-44fa836c30bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([110, 2])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bert_inputs.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7587298e-490c-441d-bb49-3b775ef34f5e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([ 1, 5, 9, 1661, 31, 4873, 886, 213, 3, 3,\n", + " 9, 3027, 10, 18597, 1705, 21, 5, 117, 3430, 15,\n", + " 501, 3, 5, 213, 7593, 367, 79, 196, 135, 3,\n", + " 1806, 12, 19, 370, 5, 478, 1661, 72, 22, 67,\n", + " 3, 13, 247, 3, 256, 56, 10, 5, 182, 10,\n", + " 105, 6, 2, 0, 0, 5, 3, 1087, 41, 9,\n", + " 3, 1051, 200, 14, 12, 19, 3, 360, 25, 14,\n", + " 12, 19, 47, 697, 7, 3722, 3, 741, 48, 59,\n", + " 444, 10, 6789, 1078, 23, 59, 386, 21407, 209, 148,\n", + " 18, 313, 9, 1474, 2016, 46, 3650, 3699, 12, 19,\n", + " 2530, 3, 1111, 410, 3, 12, 19, 208, 3, 2],\n", + " device='cuda:0')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#pick a sample input\n", + "bert_inputs[:,0]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8484ed5d-9b8c-4d11-b296-4cd027f152e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([110, 2])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "segment_labels.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "bbfb0559-b577-474d-8863-949e583929ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n", + " 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n", + " 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], device='cuda:0')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "segment_labels[:,0]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "90bce2cb-1e9a-4d74-a9de-fcbdbd52012c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimensions of token embeddings: torch.Size([110, 2, 10])\n", + "Token Embeddings for the 0th token of the first sample: tensor([ 3.4228, -0.5984, -0.7503, -2.5442, -2.5249, -1.1481, 1.3657, -3.6248,\n", + " -2.5000, 1.6133], device='cuda:0', grad_fn=)\n", + "Token Embeddings for the 1th token of the first sample: tensor([ 0.4806, 1.1055, -0.1212, 1.5770, -3.0704, -6.2625, -0.4907, 0.6401,\n", + " -5.1950, -0.5027], device='cuda:0', grad_fn=)\n", + "Token Embeddings for the 2th token of the first sample: tensor([ 0.0685, -4.3464, -2.3566, -5.7976, 3.7473, -7.0428, 1.4679, 3.3351,\n", + " 0.6718, 4.3551], device='cuda:0', grad_fn=)\n" + ] + } + ], + "source": [ + "# Instantiate the TokenEmbedding \n", + "token_embedding = TokenEmbedding(VOCAB_SIZE, emb_size=EMBEDDING_DIM ).to(device)\n", + "\n", + "# Get the token embeddings for a sample input\n", + "t_embeddings = token_embedding(bert_inputs)\n", + "\n", + "#Each token is transformed into a tensor of size emb_size\n", + "print(f\"Dimensions of token embeddings: {t_embeddings.size()}\") # Expected: (sequence_length, batch_size, EMBEDDING_DIM)\n", + "\n", + "#Check the embedded vectors for first 3 tokens of the first sample in the batch\n", + "# you get embeddings[i,0,:] where i refers to the i'th token of the first sample in the batch (b=0)\n", + "for i in range(3):\n", + " print(f\"Token Embeddings for the {i}th token of the first sample: {t_embeddings[i,0,:]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "56753bd4-7131-49c3-919a-a26c1d25477d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimensions of positionally encoded tokens: torch.Size([110, 2, 10])\n", + "Positional Embeddings for the 0th token of the first sample: tensor([ 3.4228, 0.4016, -0.7503, -1.5442, -2.5249, -0.1481, 1.3657, -2.6248,\n", + " -2.5000, 2.6133], device='cuda:0', grad_fn=)\n", + "Positional Embeddings for the 1th token of the first sample: tensor([ 1.3221, 1.6458, 0.0367, 2.5645, -3.0453, -5.2628, -0.4867, 1.6401,\n", + " -5.1944, 0.4973], device='cuda:0', grad_fn=)\n", + "Positional Embeddings for the 2th token of the first sample: tensor([ 0.9778, -4.7626, -2.0449, -4.8474, 3.7975, -6.0440, 1.4759, 4.3351,\n", + " 0.6731, 5.3551], device='cuda:0', grad_fn=)\n" + ] + } + ], + "source": [ + "positional_encoding = PositionalEncoding(emb_size=EMBEDDING_DIM,dropout=0).to(device)\n", + "\n", + "# Apply positional encoding to token embeddings\n", + "p_embedding = positional_encoding(t_embeddings)\n", + "\n", + "print(f\"Dimensions of positionally encoded tokens: {p_embedding.size()}\")# Expected: (sequence_length, batch_size, EMBEDDING_DIM)\n", + "\n", + "#Check the positional encoded vectors for first 3 tokens of the first sample in the batch\n", + "# you get encoded_tokens[i,0,:] where i refers to the i'th token of the first sample(b=0) in the batch \n", + "for i in range(3):\n", + " print(f\"Positional Embeddings for the {i}th token of the first sample: {p_embedding[i,0,:]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ea62c5b8-3d17-4e19-93ec-2d3ecea26e9f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimensions of segment embedding: torch.Size([110, 2, 10])\n", + "Segment Embeddings for the 0th token of the first sample: tensor([-0.2985, -1.9391, -0.1450, 0.6077, 0.0894, 0.9478, -0.3433, 0.0194,\n", + " -0.7506, -0.9724], device='cuda:0', grad_fn=)\n", + "Segment Embeddings for the 1th token of the first sample: tensor([-0.2985, -1.9391, -0.1450, 0.6077, 0.0894, 0.9478, -0.3433, 0.0194,\n", + " -0.7506, -0.9724], device='cuda:0', grad_fn=)\n", + "Segment Embeddings for the 2th token of the first sample: tensor([-0.2985, -1.9391, -0.1450, 0.6077, 0.0894, 0.9478, -0.3433, 0.0194,\n", + " -0.7506, -0.9724], device='cuda:0', grad_fn=)\n" + ] + } + ], + "source": [ + "segment_embedding = nn.Embedding(3, EMBEDDING_DIM).to(device)\n", + "\n", + "s_embedding = segment_embedding(segment_labels)\n", + "\n", + "print(f\"Dimensions of segment embedding: {s_embedding.size()}\")# Expected: (sequence_length, batch_size, EMBEDDING_DIM)\n", + "\n", + "#Check the Segment Embedding vectors for first 3 tokens of the first sample in the batch\n", + "# you get segment_embedded[i,0,:] where i refers to the i'th token of the first sample(b=0) in the batch \n", + "for i in range(3):\n", + " print(f\"Segment Embeddings for the {i}th token of the first sample: {s_embedding[i,0,:]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "bd46ea48-4d36-43eb-bece-d6660f5967d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimensions of token + position + segment encoded tokens: torch.Size([110, 2, 10])\n", + "BERT_Embedding for 0th token: tensor([ 6.5470, -2.1359, -1.6455, -3.4806, -4.9604, -0.3483, 2.3881, -6.2302,\n", + " -5.7506, 3.2542], device='cuda:0', grad_fn=)\n", + "BERT_Embedding for 1th token: tensor([ 1.5041, 0.8123, -0.2295, 4.7492, -6.0262, -10.5774, -1.3206,\n", + " 2.2997, -11.1400, -0.9778], device='cuda:0',\n", + " grad_fn=)\n", + "BERT_Embedding for 2th token: tensor([ 0.7477, -11.0481, -4.5464, -10.0374, 7.6342, -12.1390, 2.6004,\n", + " 7.6895, 0.5943, 8.7377], device='cuda:0',\n", + " grad_fn=)\n" + ] + } + ], + "source": [ + "#Create the combined embedding vectors\n", + "bert_embeddings = t_embeddings + p_embedding + s_embedding\n", + "print(f\"Dimensions of token + position + segment encoded tokens: {bert_embeddings.size()}\")\n", + "#Check the BERT Embedding vectors for first 3 tokens of the first sample in the batch\n", + "# you get bert_embeddings[i,0,:] where i refers to the i'th token of the first sample(b=0) in the batch \n", + "for i in range(3):\n", + " print(f\"BERT_Embedding for {i}th token: {bert_embeddings[i,0,:]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "61ee3df0-7b2e-43cb-a489-300b7623f69e", + "metadata": {}, + "outputs": [], + "source": [ + "class BERT(torch.nn.Module):\n", + " \n", + " def __init__(self, vocab_size, d_model=768, n_layers=12, heads=12, dropout=0.1):\n", + " \"\"\"\n", + " vocab_size: The size of the vocabulary.\n", + " d_model: The size of the embeddings (hidden size).\n", + " n_layers: The number of Transformer layers.\n", + " heads: The number of attention heads in each Transformer layer.\n", + " dropout: The dropout rate applied to embeddings and Transformer layers.\n", + " \"\"\"\n", + " super().__init__()\n", + " self.d_model = d_model\n", + " self.n_layers = n_layers\n", + " self.heads = heads\n", + "\n", + " # Embedding layer that combines token embeddings and segment embeddings\n", + " self.bert_embedding = BERTEmbedding(vocab_size, d_model, dropout)\n", + "\n", + " # Transformer Encoder layers\n", + " self.encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=heads, dropout=dropout,batch_first=False)\n", + " self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=n_layers)\n", + "\n", + " # Linear layer for Next Sentence Prediction\n", + " self.nextsentenceprediction = nn.Linear(d_model, 2)\n", + "\n", + " # Linear layer for Masked Language Modeling\n", + " self.masked_language = nn.Linear(d_model, vocab_size)\n", + "\n", + " def forward(self, bert_inputs, segment_labels):\n", + " \"\"\"\n", + " bert_inputs: Input tokens.\n", + " segment_labels: Segment IDs for distinguishing different segments in the input.\n", + " mask: Attention mask to prevent attention to padding tokens.\n", + "\n", + " return: Predictions for next sentence task and masked language modeling task.\n", + " \"\"\"\n", + "\n", + " padding_mask = (bert_inputs == PAD_IDX).transpose(0, 1)\n", + " # Generate embeddings from input tokens and segment labels\n", + " my_bert_embedding = self.bert_embedding(bert_inputs, segment_labels)\n", + "\n", + " # Pass embeddings through the Transformer encoder\n", + " transformer_encoder_output = self.transformer_encoder(my_bert_embedding,src_key_padding_mask=padding_mask)\n", + "\n", + "\n", + " next_sentence_prediction = self.nextsentenceprediction(transformer_encoder_output[ 0,:])\n", + " \n", + "\n", + " # Masked Language Modeling: Predict all tokens in the sequence\n", + " masked_language = self.masked_language(transformer_encoder_output)\n", + "\n", + " return next_sentence_prediction, masked_language" + ] + }, + { + "cell_type": "markdown", + "id": "0441c2a3-63f1-422c-99c5-3b620d7bb876", + "metadata": {}, + "source": [ + "Let's create an instance of the model:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "89a1c044-8dec-4274-b7f3-f0c1bcd7a699", + "metadata": {}, + "outputs": [], + "source": [ + "EMBEDDING_DIM = 10\n", + "\n", + "# Define parameters\n", + "vocab_size = 147161 # Replace VOCAB_SIZE with your vocabulary size\n", + "d_model = EMBEDDING_DIM # Replace EMBEDDING_DIM with your embedding dimension\n", + "n_layers = 2 # Number of Transformer layers\n", + "initial_heads = 12 # Initial number of attention heads\n", + "initial_heads = 2\n", + "# Ensure the number of heads is a factor of the embedding dimension\n", + "heads = initial_heads - d_model % initial_heads\n", + "\n", + "dropout = 0.1 # Dropout rate\n", + "\n", + "# Create an instance of the BERT model\n", + "model = BERT(vocab_size, d_model, n_layers, heads, dropout)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d0f87dc9-0a00-4347-ac5b-4eae1285e8c3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([2, 110])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "padding_mask = (bert_inputs == PAD_IDX).transpose(0, 1)\n", + "padding_mask.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "764aeb78-0322-4a66-8c17-50abb8beac76", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([110, 2, 10])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=heads, dropout=dropout,batch_first=False)\n", + "transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=n_layers).to(device)\n", + "# Pass embeddings through the Transformer encoder\n", + "transformer_encoder_output = transformer_encoder(bert_embeddings,src_key_padding_mask=padding_mask)\n", + "transformer_encoder_output.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "20d64951-5bf2-489e-8f65-2eb0e19e616a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NSP Output Shape: torch.Size([2, 2])\n" + ] + } + ], + "source": [ + "nextsentenceprediction = nn.Linear(d_model, 2).to(device)\n", + "\n", + "nsp = nextsentenceprediction(transformer_encoder_output[ 0,:])\n", + "\n", + "#logits for NSP task\n", + "print(f\"NSP Output Shape: {nsp.shape}\") # Expected shape: (batch_size, 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ec5ea936-407a-4223-a21b-1a479012f203", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MLM Output Shape: torch.Size([110, 2, 147161])\n" + ] + } + ], + "source": [ + "masked_language = nn.Linear(d_model, vocab_size).to(device)\n", + "\n", + "# Masked Language Modeling: Predict all tokens in the sequence\n", + "mlm = masked_language(transformer_encoder_output)\n", + "\n", + "#logits for MLM task\n", + "print(f\"MLM Output Shape: {mlm.shape}\") # Expected shape: (seq_length, batch_size, vocab_size)" + ] + }, + { + "cell_type": "markdown", + "id": "e3539135-2671-4303-a43c-0b5fb514e81a", + "metadata": {}, + "source": [ + "## Evaluation\n", + "\n", + "After creating the BERT model, the next step is training and evaluating its performance. To facilitate this, an `evaluate` function is defined with the following steps:\n", + "\n", + "1. Loss Function: The CrossEntropyLoss function is defined to calculate the loss between predicted and actual values.\n", + "\n", + "2. Function Arguments: The function takes arguments including the dataloader, model, loss function, and device.\n", + "\n", + "3. Evaluation Mode: The BERT model is put into evaluation mode using `model.eval()`, disabling dropout and training-specific behaviors. Variables are initialized to track the total loss, total next sentence loss, total mask loss, and total number of batches.\n", + "\n", + "4. Evaluation Loop: The function iterates through the batches in the provided dataloader.\n", + "\n", + "5. Forward Pass: A forward pass is performed with the BERT model to obtain predictions for the next sentence and masked language tasks.\n", + "\n", + "6. Loss Calculation: The losses for the next sentence and masked language tasks are calculated, and then summed up to obtain the total loss for the batch.\n", + "\n", + "7. Average Loss Calculation: The average loss, average next sentence loss, and average mask loss are calculated by dividing the total losses by the total number of batches.\n", + "\n", + "\n", + "The `evaluate` function is used not only for evaluating the BERT model's performance but also during the training phase to assess the model's progress.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "cfe77f70-4221-4c01-b814-cd6a6e2bb8ac", + "metadata": {}, + "outputs": [], + "source": [ + "PAD_IDX=0\n", + "loss_fn_mlm = nn.CrossEntropyLoss(ignore_index=PAD_IDX)# The loss function must ignore PAD tokens and only calculates loss for the masked tokens\n", + "loss_fn_nsp = nn.CrossEntropyLoss()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "2404da57-581c-454b-9473-9afa36d07ad1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "device(type='cuda')" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "model.to(device)\n", + "device" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9adf5d08-06ea-4972-856d-8e3f3bec24f2", + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate(dataloader=test_dataloader, model=model, loss_fn_mlm=loss_fn_mlm, loss_fn_nsp=loss_fn_nsp, device=device):\n", + " model.eval() # Turn off dropout and other training-specific behaviors\n", + "\n", + " total_loss = 0\n", + " total_next_sentence_loss = 0\n", + " total_mask_loss = 0\n", + " total_batches = 0\n", + " with torch.no_grad(): # Turn off gradients for validation, saves memory and computations\n", + " for batch in dataloader:\n", + " bert_inputs, bert_labels, segment_labels, is_nexts = [b.to(device) for b in batch]\n", + "\n", + " # Forward pass\n", + " next_sentence_prediction, masked_language = model(bert_inputs, segment_labels)\n", + "\n", + " # Calculate loss for next sentence prediction\n", + " # Ensure is_nexts is of the correct shape for CrossEntropyLoss\n", + " next_loss = loss_fn_nsp(next_sentence_prediction, is_nexts.view(-1))\n", + "\n", + " # Calculate loss for predicting masked tokens\n", + " # Flatten both masked_language predictions and bert_labels to match CrossEntropyLoss input requirements\n", + " mask_loss = loss_fn_mlm(masked_language.view(-1, masked_language.size(-1)), bert_labels.view(-1))\n", + "\n", + " # Sum up the two losses\n", + " loss = next_loss + mask_loss\n", + " if torch.isnan(loss):\n", + " continue\n", + " else:\n", + " total_loss += loss.item()\n", + " total_next_sentence_loss += next_loss.item()\n", + " total_mask_loss += mask_loss.item()\n", + " total_batches += 1\n", + "\n", + " avg_loss = total_loss / (total_batches + 1)\n", + " avg_next_sentence_loss = total_next_sentence_loss / (total_batches + 1)\n", + " avg_mask_loss = total_mask_loss / (total_batches + 1)\n", + "\n", + " print(f\"Average Loss: {avg_loss:.4f}, Average Next Sentence Loss: {avg_next_sentence_loss:.4f}, Average Mask Loss: {avg_mask_loss:.4f}\")\n", + " return avg_loss" + ] + }, + { + "cell_type": "markdown", + "id": "832ec08d-1d2e-46f3-af51-715d2d08e57a", + "metadata": {}, + "source": [ + "## Training\n", + "The training process for the BERT model involves the following steps:\n", + "\n", + "1. Optimizer Definition: Before training starts, an optimizer is defined for training the BERT model. In this case, the Adam optimizer is used.\n", + "\n", + "2. Training Loop: Within each epoch, the training data is iterated through in batches.\n", + "\n", + "3. Forward Pass: For each batch, a forward pass is performed, where the BERT model predicts the next sentence and masked language tasks.\n", + "\n", + "4. Loss Calculation and Parameter Update: The loss is calculated based on the predicted and actual values. The model's parameters are then updated through backpropagation and gradient clipping.\n", + "\n", + "5. Epoch Evaluation: After each epoch, the average training loss is printed. The model's performance on the test set is evaluated. Additionally, the model is saved after each epoch.\n", + "\n", + "These steps are repeated for multiple epochs to train the BERT model and monitor its progress over time.\n", + "\n", + "**NOTE: The current DataLoader is quite huge, and it will take several hours for the model to train with such a huge dataset. Hence, below is the randomly sampled dataset (which is relatively small which still takes 1-2 hours to run) from IMDB to make the process faster. If you want to train the model on the entire dataset, skip the next cell and directly run the training cell.**\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7812efd5-3a33-4897-b5d8-dbbbbc7bb69b", + "metadata": {}, + "outputs": [], + "source": [ + "BATCH_SIZE = 3\n", + "\n", + "train_dataset_path = './bert_dataset/bert_train_data_sampled.csv'\n", + "test_dataset_path = './bert_dataset/bert_test_data_sampled.csv'\n", + "\n", + "train_dataset = BERTCSVDataset(train_dataset_path)\n", + "test_dataset = BERTCSVDataset(test_dataset_path)\n", + "\n", + "train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)\n", + "test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=collate_batch)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "66f16614-1880-4d82-a2ed-4c46740d2fe5", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Training Epochs: 0%| | 0/5 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plotting the loss values\n", + "plt.plot(range(1, num_epochs + 1), train_losses, label='Training Loss')\n", + "plt.plot(range(1, num_epochs + 1), eval_losses, label='Evaluation Loss')\n", + "plt.xlabel('Epoch')\n", + "plt.ylabel('Loss')\n", + "plt.title('Training and Evaluation Loss')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "719e3efb-9a72-40d8-98f4-e61180d59488", + "metadata": {}, + "source": [ + "## Inference\n", + "Here you can load a model from a pt file that provide for your convenience. So, If the model training on CPU takes too long, you can load this file and continue the lab.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b57f9f4-ce66-452a-88d5-0e72a653a423", + "metadata": {}, + "outputs": [], + "source": [ + "'''model = BERT(vocab_size, d_model, n_layers, heads, dropout) # Ensure these parameters match the original model's\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/H04Cs7O75aOfmJ4YP2HdPw.pt'\n", + "model.load_state_dict(torch.load('H04Cs7O75aOfmJ4YP2HdPw.pt',map_location=torch.device('cpu')))\n", + "model.to(device)'''" + ] + }, + { + "cell_type": "markdown", + "id": "992d0f2c-401f-475c-be58-c942b68def75", + "metadata": {}, + "source": [ + "To evaluate the performance of a pretrained BERT model in predicting whether a second sentence follows the first, a function called `predict_nsp` is defined. The function operates as follows:\n", + "\n", + "1. Tokenization: The input sentences are tokenized using the `tokenizer.encode_plus` method, which returns a dictionary of tokenized inputs. These tokenized inputs are then converted into tensors and moved to the appropriate device for processing.\n", + "\n", + "2. Prediction: The BERT model is utilized to make predictions by passing the token and segment tensors as input.\n", + "\n", + "3. Logits Manipulation: The first element of the logits tensor is selected and unsqueezed to add an extra dimension, resulting in a shape of `[1, 2]`.\n", + "\n", + "4. Probability and Prediction: The logits are passed through a softmax function to obtain probabilities, and the prediction is obtained by taking the argmax.\n", + "\n", + "5. Result Interpretation: The prediction is interpreted and returned as a string, indicating whether the second sentence follows the first or not.\n", + "\n", + "6. Example Usage: An example usage of the `predict_nsp` function is provided, demonstrating how two example sentences can be passed to the function along with a BERT model and tokenizer. The result is printed, indicating whether the second sentence follows the first based on the model's prediction.\n", + "\n", + "By utilizing the `predict_nsp` function, the performance of the pretrained BERT model in determining the relationship between two sentences can be assessed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "592ba77d-0eb5-4299-a2f9-5ddc0ebbb2fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Second sentence does not follow the first\n" + ] + } + ], + "source": [ + "# Initialize the tokenizer with the BERT model's vocabulary\n", + "tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')\n", + "model.eval()\n", + "\n", + "def predict_nsp(sentence1, sentence2, model, tokenizer):\n", + " # Tokenize sentences with special tokens\n", + " tokens = tokenizer.encode_plus(sentence1, sentence2, return_tensors=\"pt\")\n", + " tokens_tensor = tokens[\"input_ids\"].to(device)\n", + " segment_tensor = tokens[\"token_type_ids\"].to(device)\n", + "\n", + " # Predict\n", + " with torch.no_grad():\n", + " # Assuming the model returns NSP predictions first\n", + " nsp_prediction, _ = model(tokens_tensor, segment_tensor)\n", + " # Select the first element (first sequence) of the logits tensor\n", + " first_logits = nsp_prediction[0].unsqueeze(0) # Adds an extra dimension, making it [1, 2]\n", + " logits = torch.softmax(first_logits, dim=1)\n", + " prediction = torch.argmax(logits, dim=1).item()\n", + "\n", + " # Interpret the prediction\n", + " return \"Second sentence follows the first\" if prediction == 1 else \"Second sentence does not follow the first\"\n", + "\n", + "# Example usage\n", + "sentence1 = \"The cat sat on the mat.\"\n", + "sentence2 = \"It was a sunny day\"\n", + "\n", + "print(predict_nsp(sentence1, sentence2, model, tokenizer))" + ] + }, + { + "cell_type": "markdown", + "id": "c9ccc59d-ad0a-4147-a322-04c84b76893b", + "metadata": {}, + "source": [ + "A function is defined to perform Masked Language Modeling (MLM) using a pretrained BERT model. The function operates as follows:\n", + "\n", + "1. Tokenization: The input sentence is tokenized using the tokenizer and converted into token IDs, including the special tokens. The tokenized sentence is stored in the `tokens_tensor` variable.\n", + "\n", + "2. Segment Labels: Dummy segment labels filled with zeros are created and stored as `segment_labels`.\n", + "\n", + "3. Prediction: The BERT model is used to make predictions by passing the token tensor and segment labels as input. The MLM logits are extracted as `predictions`.\n", + "\n", + "4. Mask Token Index: The position of the [MASK] token is identified using the `nonzero` method and stored in the `mask_token_index` variable. Note that all tokens except the mask token are zero-padded.\n", + "\n", + "5. Predicted Index: The predicted index for the [MASK] token is obtained by taking the argmax of the MLM logits at the corresponding position.\n", + "\n", + "6. Token Conversion: The predicted index is converted back to a token using the `convert_ids_to_tokens` method of the tokenizer.\n", + "\n", + "7. Replaced Sentence: The original sentence is replaced with the predicted token at the position of the [MASK] token, resulting in the predicted sentence.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "aac7f946-51b2-475e-ad4e-63c9a986abd0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The cat sat on the [unused6].\n" + ] + } + ], + "source": [ + "def predict_mlm(sentence, model, tokenizer):\n", + " # Tokenize the input sentence and convert to token IDs, including special tokens\n", + " inputs = tokenizer(sentence, return_tensors=\"pt\")\n", + " tokens_tensor = inputs.input_ids.to(device)\n", + "\n", + " # Create dummy segment labels filled with zeros, assuming it's needed by your model\n", + " segment_labels = torch.zeros_like(tokens_tensor)\n", + "\n", + " with torch.no_grad():\n", + " # Forward pass through the model, now correctly handling the output tuple\n", + " output_tuple = model(tokens_tensor, segment_labels)\n", + "\n", + " # Assuming the second element of the tuple contains the MLM logits\n", + " predictions = output_tuple[1] # Adjusted based on your model's output\n", + "\n", + " # Identify the position of the [MASK] token\n", + " mask_token_index = (tokens_tensor == tokenizer.mask_token_id).nonzero(as_tuple=True)[1]\n", + "\n", + " # Get the predicted index for the [MASK] token from the MLM logits\n", + " predicted_index = torch.argmax(predictions[0, mask_token_index.item(), :], dim=-1)\n", + " predicted_token = tokenizer.convert_ids_to_tokens([predicted_index.item()])[0]\n", + "\n", + " # Replace [MASK] in the original sentence with the predicted token\n", + " predicted_sentence = sentence.replace(tokenizer.mask_token, predicted_token, 1)\n", + "\n", + " return predicted_sentence\n", + "\n", + "\n", + "# Example usage\n", + "sentence = \"The cat sat on the [MASK].\"\n", + "print(predict_mlm(sentence, model, tokenizer))" + ] + }, + { + "cell_type": "markdown", + "id": "e3d71ae4-abd1-4f1c-a744-6e6f4eb2aecd", + "metadata": {}, + "source": [ + "Congratualtions! You've just learnt how to create a tiny BERT model and trained it for a few epochs. for the model to generate accurate result, you will need to train it on huge datasets for more epochs and maybe increase the model size.\n" + ] + }, + { + "cell_type": "markdown", + "id": "a2484570-e876-463e-91d8-53f56c51e69c", + "metadata": {}, + "source": [ + "# Exercises\n", + "Now you have preatined our own Baby BERT and evaluated it now to see the difference lets use a pretrained BERT model to predict for few examples as above. \n", + "\n", + "**Note: Since the actual BERT is trained with a lot more data and epochs, its performance should be much better than the model you just built.**\n", + "\n", + "## Exercise 1: Next Sentence Prediction (NSP) with BERT\n", + "\n", + "1. **Load the BERT pretrained model**: Import `BertForPreTraining` and `BertTokenizer` from `transformers`, and load the 'bert-base-uncased' pretrained model and tokenizer.\n", + "2. **Prepare text input**: Encode a pair of sentences using the loaded tokenizer.\n", + "3. **Perform NSP**: Pass the encoded input through the model and interpret the `seq_relationship_logits` to determine if the model predicts the sentences as consecutive.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "7b9ca59e-7f69-4b56-b230-f91b709722a7", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "75b9f0f738ff413b935523e4620f5f06", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "model.safetensors: 0%| | 0.00/440M [00:00\n", + " \n", + " \"Skills\n", + " \n", + "

\n" + ] + }, + { + "cell_type": "markdown", + "id": "131145c7-3820-49bd-8277-bbf7855e18a3", + "metadata": {}, + "source": [ + "# A Transformer Model for Language Translation\n" + ] + }, + { + "cell_type": "markdown", + "id": "c53af99e-7c3d-4125-9481-04c62a618c45", + "metadata": {}, + "source": [ + "## __Table of Contents__\n", + "\n", + "
    \n", + "
  1. Objectives
  2. \n", + "
  3. Background
  4. \n", + "
  5. Setup
  6. \n", + "
  7. DataLoader
  8. \n", + "
  9. Transformer architecture for language translation
  10. \n", + "
  11. Training the model
  12. \n", + "
  13. Translation and evaluation
  14. \n", + "
  15. Exercise: Translating a document
  16. \n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "44defff7-ce8e-4cc8-83a1-c9e38fbc1dd7", + "metadata": {}, + "source": [ + "# Objectives\n", + "After completing this lab, you will be able to:\n", + "\n", + "- Describe the transformer architecture\n", + "- Build a translation model from scratch using PyTorch:\n", + " - Preprocess textual data\n", + " - Design the transformer architecture\n", + " - Train the model using parallel computing\n", + " - Evaluate the model performance\n", + " - Generate translation\n", + "- Translate a PDF document from German to English\n", + "\n", + "# Background\n", + "In today’s interconnected world, the ability to break language barriers is more valuable than ever. Whether it's for expanding business reach, accessing information in different languages, or connecting with people across the globe, language translation plays a crucial role. This is where transformer model for language translation lab steps in, letting you delve into the world of neural machine translation – a field at the forefront of overcoming language barriers. You'll explore how to implement a transformer model to translate text from one language to another.\n", + "\n", + "## Why transformers?\n", + "In the field of natural language processing (NLP), you often deal with sequential data, like sentences. Before transformers, the most common models used were Recurrent Neural Networks (RNNs) and Long Short-Term Memory (LSTMs) networks. These models process data sequentially, meaning they read a sentence word by word, one after the other. This makes them slow and less efficient for long sequences. Also, RNNs and LSTMs can struggle to keep track of information from earlier in the sequence, which is vital in understanding context.\n", + "\n", + "Transformers introduced a new way of processing sequences. Instead of reading word by word in order, they can look at the entire sequence at once. This approach makes them faster and more efficient. They can also better understand the context of each word in a sentence, no matter how long it is.\n", + "\n", + "![translation](https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0201EN-Coursera/Translation.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "ae224e1d-dd62-4121-8123-0adb2e5741ee", + "metadata": {}, + "source": [ + "# Setup\n", + "## Installing required libraries\n", + "Before you start, let's make sure you have all the necessary libraries installed. You can run the following commands to install them:\n", + "\n", + "```bash\n", + "!pip install -U torchdata==0.5.1\n", + "!pip install -U spacy==3.7.2\n", + "!pip install -Uqq portalocker==2.7.0\n", + "!pip install -qq torchtext==0.14.1\n", + "!pip install -Uq nltk==3.8.1\n", + "\n", + "!python -m spacy download de\n", + "!python -m spacy download en\n", + "\n", + "!pip install pdfplumber==0.9.0\n", + "!pip install fpdf==1.7.2\n", + "\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0205EN-SkillsNetwork/Multi30K_de_en_dataloader.py'\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0201EN-Coursera/transformer.pt'\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0201EN-Coursera/input_de.pdf'\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "2bd55d1b-4042-4a07-8497-77fa906e07be", + "metadata": {}, + "source": [ + "## Importing required libraries\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "32e46f09-2f8d-454b-a88d-f6638f26b590", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "Tesla P40\n", + "Import Successfully!\n" + ] + } + ], + "source": [ + "import os\n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"\n", + "\n", + "import torch\n", + "print(torch.cuda.is_available())\n", + "print(torch.cuda.get_device_name())\n", + "\n", + "from torchtext.datasets import multi30k, Multi30k\n", + "from typing import Iterable, List\n", + "import matplotlib.pyplot as plt\n", + "from nltk.translate.bleu_score import sentence_bleu\n", + "from torch import Tensor\n", + "import torch.nn as nn\n", + "from torch.nn import Transformer\n", + "import math\n", + "from tqdm import tqdm\n", + "\n", + "# You can also use this section to suppress warnings generated by your code:\n", + "def warn(*args, **kwargs):\n", + " pass\n", + "import warnings\n", + "warnings.warn = warn\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "print(\"Import Successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7be81fec-9c24-4692-ae53-6236cc6a398d", + "metadata": {}, + "outputs": [], + "source": [ + "# Define special symbols and indices\n", + "UNK_IDX, PAD_IDX, BOS_IDX, EOS_IDX = 0, 1, 2, 3\n", + "# Make sure the tokens are in order of their indices to properly insert them in vocab\n", + "special_symbols = ['', '', '', '']" + ] + }, + { + "cell_type": "markdown", + "id": "af4a73ec-b132-4e90-b294-b9e2b4e4c40b", + "metadata": {}, + "source": [ + "## DataLoader\n", + "\n", + "In the English-German Multi30K dataset, you first load the data and break down sentences into words or smaller pieces, called tokens. From these tokens, you create a unique list or vocabulary. Each token is then turned into a specific number using this vocabulary. Because sentences can be of different lengths, add padding to make them all the same size in a batch. All this processed data is then organized into a PyTorch DataLoader, making it easy to use for training neural networks. A function has been provided for you to handle all these.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "420340d3-9b85-430a-a1ef-775b4f5d6b27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting en-core-web-sm==3.7.1\n", + " Downloading https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl (12.8 MB)\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.8/12.8 MB\u001b[0m \u001b[31m35.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m[36m0:00:01\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n", + "Requirement already satisfied: spacy<3.8.0,>=3.7.2 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from en-core-web-sm==3.7.1) (3.7.2)\n", + "Requirement already satisfied: spacy-legacy<3.1.0,>=3.0.11 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.0.12)\n", + "Requirement already satisfied: spacy-loggers<2.0.0,>=1.0.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (1.0.4)\n", + "Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (1.0.7)\n", + "Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.0.6)\n", + "Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.0.6)\n", + "Requirement already satisfied: thinc<8.3.0,>=8.1.8 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (8.2.2)\n", + "Requirement already satisfied: wasabi<1.2.0,>=0.9.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.9.1)\n", + "Requirement already satisfied: srsly<3.0.0,>=2.4.3 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.4.8)\n", + "Requirement already satisfied: catalogue<2.1.0,>=2.0.6 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.0.10)\n", + "Requirement already satisfied: weasel<0.4.0,>=0.1.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.3.4)\n", + "Requirement already satisfied: typer<0.10.0,>=0.3.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.9.4)\n", + "Requirement already satisfied: smart-open<7.0.0,>=5.2.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (5.2.1)\n", + "Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (4.66.5)\n", + "Requirement already satisfied: requests<3.0.0,>=2.13.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.32.3)\n", + "Requirement already satisfied: pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.8.2)\n", + "Requirement already satisfied: jinja2 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.1.4)\n", + "Requirement already satisfied: setuptools in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (75.1.0)\n", + "Requirement already satisfied: packaging>=20.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (24.1)\n", + "Requirement already satisfied: langcodes<4.0.0,>=3.2.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.3.0)\n", + "Requirement already satisfied: numpy>=1.15.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (1.24.3)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.6.0)\n", + "Requirement already satisfied: pydantic-core==2.20.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.20.1)\n", + "Requirement already satisfied: typing-extensions>=4.6.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (4.11.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.2.3)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2024.12.14)\n", + "Requirement already satisfied: blis<0.8.0,>=0.7.8 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from thinc<8.3.0,>=8.1.8->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.7.9)\n", + "Requirement already satisfied: confection<1.0.0,>=0.0.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from thinc<8.3.0,>=8.1.8->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.1.4)\n", + "Requirement already satisfied: click<9.0.0,>=7.1.1 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from typer<0.10.0,>=0.3.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (8.1.7)\n", + "Requirement already satisfied: cloudpathlib<0.17.0,>=0.7.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from weasel<0.4.0,>=0.1.0->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (0.16.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /home/loc/miniconda3/envs/py38/lib/python3.8/site-packages (from jinja2->spacy<3.8.0,>=3.7.2->en-core-web-sm==3.7.1) (2.1.3)\n", + "Installing collected packages: en-core-web-sm\n", + "Successfully installed en-core-web-sm-3.7.1\n", + "\u001b[38;5;2m✔ Download and installation successful\u001b[0m\n", + "You can now load the package via spacy.load('en_core_web_sm')\n" + ] + } + ], + "source": [ + "!python -m spacy download en_core_web_sm" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3a99706f-8b2c-4313-8466-4d0351a07fab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import spacy\n", + "spacy.load('en_core_web_sm')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b68a59f5-a2bc-4a9f-8d1e-ef5a35a40a23", + "metadata": {}, + "outputs": [], + "source": [ + "%run Multi30K_de_en_dataloader.py" + ] + }, + { + "cell_type": "markdown", + "id": "4006918b-10d9-4caf-ba1b-6187290a7631", + "metadata": {}, + "source": [ + "You've set up data loaders for training and testing. Given the exploratory work, use a batch size of one\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4d5d2c4b-dc2d-46a3-89a3-592361c7c191", + "metadata": {}, + "outputs": [], + "source": [ + "train_dataloader, _ = get_translation_dataloaders(batch_size = 1)" + ] + }, + { + "cell_type": "markdown", + "id": "8599a33e-3685-4091-a647-1f5e611c1164", + "metadata": {}, + "source": [ + "Initialize an iterator for the validation data loader:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "99b55874-971f-4fa7-8c38-ff329bae5ea9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_itr=iter(train_dataloader)\n", + "data_itr" + ] + }, + { + "cell_type": "markdown", + "id": "b5291e61-00b6-4509-aedb-54e05d0f2766", + "metadata": {}, + "source": [ + "To obtain diverse examples, you can cycle through multiple samples since the dataset is sorted by length.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2894e5af-b134-42f8-962a-9e63b3553710", + "metadata": {}, + "outputs": [], + "source": [ + "for n in range(1000):\n", + " german, english= next(data_itr)" + ] + }, + { + "cell_type": "markdown", + "id": "769e7651-88c4-4f4c-96d5-ca616df1ffcf", + "metadata": {}, + "source": [ + "The dataset is structured as sequence-batch-feature, rather than the typical batch-feature-sequence. For compatibility with your utility functions, you can transpose the dataset.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "2a9eb727-0d58-466a-9369-0a38985bf449", + "metadata": {}, + "outputs": [], + "source": [ + "german=german.T\n", + "english=english.T" + ] + }, + { + "cell_type": "markdown", + "id": "b49bfbe7-baed-46d7-9614-ac4199586c63", + "metadata": {}, + "source": [ + "You can print out the text by converting the indexes to words using ```index_to_german``` and ```index_to_english```\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "56c19b7c-c2bd-491b-bde3-887ce7e6b7af", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sample 0\n", + "german input\n", + " Ein Feuerwehrangehöriger arbeitet bei einem Brand . \n", + "english target\n", + " A firefighter is working at a fire . \n", + "_________\n", + "\n", + "sample 1\n", + "german input\n", + " Ein Mann spielt auf einem Flügel . \n", + "english target\n", + " A man playing a black grand piano . \n", + "_________\n", + "\n", + "sample 2\n", + "german input\n", + " Ein brauner Hund spielt im Schnee . \n", + "english target\n", + " A brown dog plays in the snow . \n", + "_________\n", + "\n", + "sample 3\n", + "german input\n", + " Mehrere Hunde in einem winterlichen Ambiente . \n", + "english target\n", + " Several dogs grouped together in a winter setting . \n", + "_________\n", + "\n", + "sample 4\n", + "german input\n", + " Ein Mann klettert einen Felsen hoch . \n", + "english target\n", + " A man climbs up a rock . \n", + "_________\n", + "\n", + "sample 5\n", + "german input\n", + " Zwei Teams kämpfen um den Sieg . \n", + "english target\n", + " Two teams battle it out for the win ! \n", + "_________\n", + "\n", + "sample 6\n", + "german input\n", + " Kinder spielen in einem aufblasbaren Spielplatz . \n", + "english target\n", + " Kids play in a blow up playground . \n", + "_________\n", + "\n", + "sample 7\n", + "german input\n", + " Ein kleiner Junge malt einen Teller . \n", + "english target\n", + " A little boy is paining a plate . \n", + "_________\n", + "\n", + "sample 8\n", + "german input\n", + " Die beiden Frauen blicken auf etwas . \n", + "english target\n", + " The two woman are looking at something . \n", + "_________\n", + "\n", + "sample 9\n", + "german input\n", + " Fünf Kricketspieler liegen auf dem Gras . \n", + "english target\n", + " Five cricket players are lounging on the grass . \n", + "_________\n", + "\n" + ] + } + ], + "source": [ + "for n in range(10):\n", + " german, english= next(data_itr)\n", + "\n", + " print(\"sample {}\".format(n))\n", + " print(\"german input\")\n", + " print(index_to_german(german))\n", + " print(\"english target\")\n", + " print(index_to_eng(english))\n", + " print(\"_________\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "21da877b-b56d-4e0d-841a-578940d5b2d1", + "metadata": {}, + "source": [ + "Let's define your device (CPU or GPU) for training. You'll check if a GPU is available and use it; otherwise, you'll use the CPU.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c0833803-e32a-439b-b89f-afa30a6d7af8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "device(type='cuda')" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", + "DEVICE" + ] + }, + { + "cell_type": "markdown", + "id": "d813f0fc-82d6-4a2f-a7a3-4bdb577b0b2d", + "metadata": {}, + "source": [ + "Now that you've covered data preparation, let's move on to understanding the key components of the transformer model.\n" + ] + }, + { + "cell_type": "markdown", + "id": "14da7c9b-4e02-4c1b-aaa8-bb5cc1d1f7fe", + "metadata": {}, + "source": [ + "## Important concepts" + ] + }, + { + "cell_type": "markdown", + "id": "51f8d51f-4a57-4585-beb3-cf3f6ca33a39", + "metadata": {}, + "source": [ + "\n", + "### Masking\n", + "\n", + "During training, the entire sequence is visible to the model and used as input to learn patterns. In contrast, for prediction, the future sequence is not available. To do this, employ masking to simulate this lack of future data, ensuring the model learns to predict without seeing the actual next tokens. It is crucial for ensuring certain positions are not attended to. The function ```generate_square_subsequent_mask``` produces an upper triangular matrix, which ensures that during decoding, a token can't attend to future tokens.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1cdd1612-f393-4bde-bd3d-8b86eabc5090", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_square_subsequent_mask(sz,device=DEVICE):\n", + " mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)\n", + " mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))\n", + " return mask" + ] + }, + { + "cell_type": "markdown", + "id": "d5eaf3ce-dceb-45f5-93cb-2a47eb3ba543", + "metadata": {}, + "source": [ + "The ```create_mask``` function, on the other hand, generates both source and target masks, as well as padding masks based on the provided source and target sequences. The padding masks ensure that the model doesn't attend to pad tokens, providing a streamlined attention.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "6af25caa-6392-4432-a03e-a61dba310d60", + "metadata": {}, + "outputs": [], + "source": [ + "def create_mask(src, tgt,device=DEVICE):\n", + " src_seq_len = src.shape[0]\n", + " tgt_seq_len = tgt.shape[0]\n", + "\n", + " tgt_mask = generate_square_subsequent_mask(tgt_seq_len)\n", + " src_mask = torch.zeros((src_seq_len, src_seq_len),device=DEVICE).type(torch.bool)\n", + "\n", + " src_padding_mask = (src == PAD_IDX).transpose(0, 1)\n", + " tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)\n", + " return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask" + ] + }, + { + "cell_type": "markdown", + "id": "39d858d5-f8fc-4e4a-84a8-3503f6efe11f", + "metadata": {}, + "source": [ + "### Positional encoding\n", + "The transformer model doesn't have built-in knowledge of the order of tokens in the sequence. To give the model this information, positional encodings are added to the tokens embeddings. These encodings have a fixed pattern based on their position in the sequence.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "4a9124d3-d976-4486-a748-ead3ab6c9a4e", + "metadata": {}, + "outputs": [], + "source": [ + "# Add positional information to the input tokens\n", + "class PositionalEncoding(nn.Module):\n", + " def __init__(self,\n", + " emb_size: int,\n", + " dropout: float,\n", + " maxlen: int = 5000):\n", + " super(PositionalEncoding, self).__init__()\n", + " den = torch.exp(- torch.arange(0, emb_size, 2)* math.log(10000) / emb_size)\n", + " pos = torch.arange(0, maxlen).reshape(maxlen, 1)\n", + " pos_embedding = torch.zeros((maxlen, emb_size))\n", + " pos_embedding[:, 0::2] = torch.sin(pos * den)\n", + " pos_embedding[:, 1::2] = torch.cos(pos * den)\n", + " pos_embedding = pos_embedding.unsqueeze(-2)\n", + "\n", + " self.dropout = nn.Dropout(dropout)\n", + " self.register_buffer('pos_embedding', pos_embedding)\n", + "\n", + " def forward(self, token_embedding: Tensor):\n", + " return self.dropout(token_embedding + self.pos_embedding[:token_embedding.size(0), :])" + ] + }, + { + "cell_type": "markdown", + "id": "e0ab23d6-e463-47f0-945f-9307bb5be0d8", + "metadata": {}, + "source": [ + "### Token embedding\n", + "Token embedding, also known as word embedding or word representation, is a way to convert words or tokens from a text corpus into numerical vectors in a continuous vector space. Each unique word or token in the corpus is assigned a fixed-length vector where the numerical values represent various linguistic properties of the word, such as its meaning, context, or relationships with other words.\n", + "\n", + "The `TokenEmbedding` class below converts numerical tokens into embeddings:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "18c693d5-eaea-490b-8829-4a72e288ed5b", + "metadata": {}, + "outputs": [], + "source": [ + "class TokenEmbedding(nn.Module):\n", + " def __init__(self, vocab_size: int, emb_size):\n", + " super(TokenEmbedding, self).__init__()\n", + " self.embedding = nn.Embedding(vocab_size, emb_size)\n", + " self.emb_size = emb_size\n", + "\n", + " def forward(self, tokens: Tensor):\n", + " return self.embedding(tokens.long()) * math.sqrt(self.emb_size)" + ] + }, + { + "cell_type": "markdown", + "id": "9472f9db-52ea-49d3-ba89-4f17596b17b3", + "metadata": {}, + "source": [ + "## Transformer architecture for language translation\n", + "Language translation using a transformer model is a sophisticated process that relies on an encoder-decoder architecture. In this explanation, you will break down the essential components and training procedures for a clear understanding.\n", + "\n", + "### Tokenization and positional encoding\n", + "To begin, source language text (the input sequence) is tokenized, which means it's divided into individual words or subwords. These tokens are then converted into numerical representations. To preserve word order information, positional encodings are added to these numerical tokens.\n", + "\n", + "### Encoder processing\n", + "The next step involves passing these numerical tokens through the encoder. The encoder is composed of multiple layers, each containing self-attention mechanisms and feed-forward neural networks. This architecture allows the transformer model to process the entire input sequence at once, in contrast to traditional RNN-based models like LSTMs or GRUs, which process input sequentially.\n", + "\n", + "### Decoding with teacher forcing\n", + "During training, the target language text (the correct output sequence) is also tokenized and converted into numerical tokens. \"Teacher forcing\" is a training technique where the decoder is provided with the target tokens as input. The decoder uses both the encoder's output and the previously generated tokens (starting with a special start-of-sequence token) to predict the next token in the sequence.\n", + "\n", + "### Output generation and loss calculation\n", + "The decoder generates the translated sequence token by token. At each step, the decoder predicts the next token in the target sequence. The predicted sequence from the decoder is then compared to the actual target sequence using a loss function, typically cross-entropy loss for translation tasks. This loss function quantifies how well the model's predictions match the true target sequence.\n", + "\n", + "## Seq2SeqTransformer\n", + "Now, let's delve into the Seq2SeqTransformer class, which represents the core of the transformer model for language translation.\n", + "\n", + "To train this model effectively, the following aspects will be covered:\n", + "\n", + "- **Data loading:** Loading and preparing the training data, which includes source language text and corresponding target language text.\n", + "\n", + "- **Model initialization:** Initializing the transformer model, including setting up the encoder, decoder, positional encodings, and other necessary components.\n", + "\n", + "- **Optimizer setup:** Choosing an appropriate optimizer, such as Adam, and defining learning rate schedules to update model parameters during training.\n", + "\n", + "- **Training loop:** Iterating through the training data for multiple epochs, using teacher forcing to guide the model's learning process.\n", + "\n", + "- **Loss monitoring:** Recording and potentially plotting training losses for each epoch. These losses indicate how well the model is learning to perform language translation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "6044d199-6a49-42a9-8221-ea4f236702ea", + "metadata": {}, + "outputs": [], + "source": [ + "class Seq2SeqTransformer(nn.Module):\n", + " def __init__(self,\n", + " num_encoder_layers: int,\n", + " num_decoder_layers: int,\n", + " emb_size: int,\n", + " nhead: int,\n", + " src_vocab_size: int,\n", + " tgt_vocab_size: int,\n", + " dim_feedforward: int = 512,\n", + " dropout: float = 0.1):\n", + " super(Seq2SeqTransformer, self).__init__()\n", + "\n", + " self.src_tok_emb = TokenEmbedding(src_vocab_size, emb_size)\n", + " self.tgt_tok_emb = TokenEmbedding(tgt_vocab_size, emb_size)\n", + " self.positional_encoding = PositionalEncoding(\n", + " emb_size, dropout=dropout)\n", + " self.transformer = Transformer(d_model=emb_size,\n", + " nhead=nhead,\n", + " num_encoder_layers=num_encoder_layers,\n", + " num_decoder_layers=num_decoder_layers,\n", + " dim_feedforward=dim_feedforward,\n", + " dropout=dropout)\n", + " self.generator = nn.Linear(emb_size, tgt_vocab_size)\n", + "\n", + " def forward(self,\n", + " src: Tensor,\n", + " trg: Tensor,\n", + " src_mask: Tensor,\n", + " tgt_mask: Tensor,\n", + " src_padding_mask: Tensor,\n", + " tgt_padding_mask: Tensor,\n", + " memory_key_padding_mask: Tensor):\n", + " src_emb = self.positional_encoding(self.src_tok_emb(src))\n", + " tgt_emb = self.positional_encoding(self.tgt_tok_emb(trg))\n", + " outs = self.transformer(src_emb, tgt_emb, src_mask, tgt_mask, None,\n", + " src_padding_mask, tgt_padding_mask, memory_key_padding_mask)\n", + " outs =outs.to(DEVICE)\n", + " return self.generator(outs)\n", + "\n", + " def encode(self, src: Tensor, src_mask: Tensor):\n", + " return self.transformer.encoder(self.positional_encoding(\n", + " self.src_tok_emb(src)), src_mask)\n", + "\n", + " def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):\n", + " return self.transformer.decoder(self.positional_encoding(\n", + " self.tgt_tok_emb(tgt)), memory,\n", + " tgt_mask)" + ] + }, + { + "cell_type": "markdown", + "id": "0bab5378-dcaa-471a-a129-30820b7fb830", + "metadata": {}, + "source": [ + "## Inference\n", + "\n", + "\n", + "The diagram below illustrates the sequence prediction or inference process. You can begin by feeding the indices of your desired translation sequence into the encoder, represented by the lower-left orange section. The resulting embeddings from the encoder are then channeled into the decoder, highlighted in green. Alongside, a start token is introduced at the beginning of the decoder input, as depicted at the base of the green segment.\n", + "\"transformer\"\n", + "The decoder's output is then mapped onto a vocabulary-sized vector using a linear layer. Following this, a softmax function converts these vector scores into probabilities. The highest probability, as determined by the argmax function, provides the index of your predicted word within the translated sequence. This predicted index is fed back into the decoder in conjunction with the initial sequence, setting the stage to determine the subsequent word in the translation. This autoregressive process is demonstrated by the arrow pointing to form the top of the decoder, in green, to the bottom.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "d58f09b1-4e47-4248-a62e-a1f08061d16e", + "metadata": {}, + "outputs": [], + "source": [ + "torch.manual_seed(0)\n", + "\n", + "SRC_LANGUAGE = 'de'\n", + "TGT_LANGUAGE = 'en'\n", + "SRC_VOCAB_SIZE = len(vocab_transform[SRC_LANGUAGE])\n", + "TGT_VOCAB_SIZE = len(vocab_transform[TGT_LANGUAGE])\n", + "EMB_SIZE = 512\n", + "NHEAD = 8\n", + "FFN_HID_DIM = 512\n", + "BATCH_SIZE = 128\n", + "NUM_ENCODER_LAYERS = 3\n", + "NUM_DECODER_LAYERS = 3\n", + "\n", + "transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS, EMB_SIZE,\n", + " NHEAD, SRC_VOCAB_SIZE, TGT_VOCAB_SIZE, FFN_HID_DIM)\n", + "\n", + "for p in transformer.parameters():\n", + " if p.dim() > 1:\n", + " nn.init.xavier_uniform_(p)\n", + "\n", + "transformer = transformer.to(DEVICE)" + ] + }, + { + "cell_type": "markdown", + "id": "55b69acf-0f87-4aeb-bcaf-955c72372b12", + "metadata": {}, + "source": [ + "Let's will start off with a trained model.For this, load the weights of the transformer model from the file 'transformer.pt'.\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "4894108d-7da7-4839-9dbf-9380c5939be1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "transformer.load_state_dict(torch.load('transformer.pt', map_location=DEVICE, ))" + ] + }, + { + "cell_type": "markdown", + "id": "86973173-c46f-4f50-aa32-5187bab8189b", + "metadata": {}, + "source": [ + "Since your dataset is organized by sequence length, let's iterate through it to obtain a longer sequence\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "cd1f068a-5377-4759-9049-87f1c1cea16c", + "metadata": {}, + "outputs": [], + "source": [ + "for n in range(100):\n", + " src ,tgt= next(data_itr)" + ] + }, + { + "cell_type": "markdown", + "id": "1cf92b10-1fad-4f6c-bd11-2f23260dbc05", + "metadata": {}, + "source": [ + "Display the source sequence in German that you aim to translate, alongside the target sequence in English that you want your model to produce\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "a0b404d5-0886-4b54-9377-4f7cb98858d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "engish target A worker taking a reading on a subway train . \n", + "german input Ein Arbeiter liest in einem U-Bahn-Zug . \n" + ] + } + ], + "source": [ + "print(\"engish target\",index_to_eng(tgt))\n", + "print(\"german input\",index_to_german(src))" + ] + }, + { + "cell_type": "markdown", + "id": "e5f70b95-5c09-4509-9c9a-b867d3060406", + "metadata": {}, + "source": [ + "You will find the number of tokens in the German sample:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "efb24258-4304-48a4-bc86-72fc02844a86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_tokens = src.shape[0]\n", + "num_tokens" + ] + }, + { + "cell_type": "markdown", + "id": "22e7a55d-7060-4662-9899-fafe2bebcfc2", + "metadata": {}, + "source": [ + "You can construct a mask to delineate which inputs are factored into the attention computation. Given that this pertains to a translation task, all tokens in the source sequence are accessible, thus setting the mask values to `false`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ff71568f-0868-4d55-a3c7-0de121d0e4ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False],\n", + " [False, False, False, False, False, False, False, False, False]],\n", + " device='cuda:0')" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool).to(DEVICE )\n", + "src_mask[0:10]" + ] + }, + { + "cell_type": "markdown", + "id": "7726d0fc-2182-49bf-bfd1-9072c40e61e9", + "metadata": {}, + "source": [ + "\n", + "\n", + "Extract the first sample from the batch of sequences slated for translation. While currently redundant, this procedure will become relevant later as you handle larger batches.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "82534cc1-6a9f-4891-878c-536d94826571", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch.Size([9, 1])\n", + "torch.Size([9, 1])\n" + ] + } + ], + "source": [ + "src_=src[:,0].unsqueeze(1)\n", + "print(src_.shape)\n", + "print(src.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "247b5f12-5ef7-4a6b-8d05-0e5ddfc2985a", + "metadata": {}, + "source": [ + "Feed the tokens of the sequences designated for translation into the transformer, accompanied by the mask. The resultant values, stored in the 'memory', are the embeddings derived from the output of the transformer encoder as shown in the following image: \n", + "\n", + "\"trasfoemr\"\n", + "\n", + "The memory, which is the encoder's output, encapsulates the original sequence to be translated and serves as the input for the decoder.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "e7507687-ebfa-42d6-9f2e-3890c83fe892", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([9, 1, 512])" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memory = transformer.encode(src_, src_mask)\n", + "memory.shape" + ] + }, + { + "cell_type": "markdown", + "id": "d6cf4c46-60b3-4dd3-88fb-5044a92b87ec", + "metadata": {}, + "source": [ + " To indicate the beginning of an output sequence generation, initiate it with the start symbol:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "fa774558-bb13-47ad-862a-bf3c852488f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[2]], device='cuda:0')" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ys = torch.ones(1, 1).fill_(BOS_IDX).type(torch.long).to(DEVICE)\n", + "ys" + ] + }, + { + "cell_type": "markdown", + "id": "59224b81-a809-451e-aa67-a5e2678894cd", + "metadata": {}, + "source": [ + "Due to some naming conventions, the term \"target\" is used to denote the prediction. In this context, the \"target\" refers to the words following the current prediction. These can be combined with the source to make further predictions. Consequently, you construct a target mask set to 'false' indicating that no values should be ignored:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d7a9cca5-ae05-4d05-b26e-c875150e5a3a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[False]], device='cuda:0')" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tgt_mask = (generate_square_subsequent_mask(ys.size(0)).type(torch.bool)).to(DEVICE)\n", + "tgt_mask" + ] + }, + { + "cell_type": "markdown", + "id": "cac21ae6-e820-4abb-a063-8805c1a583bc", + "metadata": {}, + "source": [ + "You feed the encoder's output (referred to as 'memory') and the previous prediction from the transformer, which at this point is solely the start token, into the decoder:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "b687e518-4314-464e-b5e5-025a0261a46a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 1, 512])" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out = transformer.decode(ys, memory, tgt_mask)\n", + "out.shape" + ] + }, + { + "cell_type": "markdown", + "id": "ca253817-6c85-4f38-921e-3a1124d165d9", + "metadata": {}, + "source": [ + "The decoder's output is an enhanced word embedding representing the anticipated translation. At this point, the batch dimension is omitted.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "c448c677-aae3-4df6-b711-eeb57162d78e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 1, 512])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out = out.transpose(0, 1)\n", + "out.shape" + ] + }, + { + "cell_type": "markdown", + "id": "00b2e730-478f-4d5d-91b3-506eeafa7ed1", + "metadata": {}, + "source": [ + "Once the decoder produces its output, it's passed through output layer logit value over the vocabulary of 10837 words. Later on you will only need the last token so you can input```out[:, -1]```\n" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "5d2a2c61-e4e9-4373-9098-417658a63ad7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 10837])" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "logit = transformer.generator(out[:, -1])\n", + "logit.shape" + ] + }, + { + "cell_type": "markdown", + "id": "c9470d51-4e81-4bd1-9c0d-c5247554feb3", + "metadata": {}, + "source": [ + "The process is succinctly illustrated in the image below:\n", + "\n", + "\"trasfoemr\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "32e3ed9e-b69c-47db-b2a8-321cfbce0476", + "metadata": {}, + "source": [ + "\n", + "The predicted word is determined by identifying the highest logit value, which signifies the model's most probable translation for the input at a specific position; this position corresponds to the index of the next token.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b1c3b10c-4770-46e9-904c-bdfbb1addf01", + "metadata": {}, + "outputs": [], + "source": [ + " _, next_word_index = torch.max(logit, dim=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "7ec94cb8-c671-4745-86b1-5151b80d3408", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "engish output: A\n" + ] + } + ], + "source": [ + "print(\"engish output:\",index_to_eng(next_word_index))" + ] + }, + { + "cell_type": "markdown", + "id": "fe8296d2-9d5c-4283-beb0-0478582149a5", + "metadata": {}, + "source": [ + "You only need the integer for the index:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "0796de56-74c2-438c-8323-df75ad196745", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next_word_index=next_word_index.item()\n", + "next_word_index" + ] + }, + { + "cell_type": "markdown", + "id": "93052db8-4df7-4577-8083-c68b0b9d73c6", + "metadata": {}, + "source": [ + "Now, append the newly predicted word to the prior predictions, allowing the model to consider the entire sequence of generated words when making its next prediction.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "96018e5b-dd25-4c67-aefb-db20d39c5769", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[2],\n", + " [6]], device='cuda:0')" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ys = torch.cat([ys,torch.ones(1, 1).type_as(src.data).fill_(next_word_index)], dim=0)\n", + "ys" + ] + }, + { + "cell_type": "markdown", + "id": "16133b1e-a3d6-408f-9991-713b05af681d", + "metadata": {}, + "source": [ + "To predict the subsequent word in the translation, update target mask and use the transformer decoder to derive the word probabilities. The word with the maximum probability is then selected as the prediction. Note that the encoder output contains all the information you need.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "7b9c4f0b-d3c1-46f2-bd03-464547e12e02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[False, True],\n", + " [False, False]], device='cuda:0')" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Update the target mask for the current sequence length.\n", + "tgt_mask = (generate_square_subsequent_mask(ys.size(0)).type(torch.bool)).to(DEVICE)\n", + "tgt_mask" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "edb4d0f8-1645-4232-ada0-ff06cae8c8d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 2, 512])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Decode the current sequence using the transformer and retrieve the output.\n", + "out = transformer.decode(ys, memory, tgt_mask)\n", + "out = out.transpose(0, 1)\n", + "out.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0073a286-cfae-470b-ad7d-1f57f5207599", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 512])" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out[:, -1].shape" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "f67bb4f6-14fe-4a20-b04d-62f6cef584eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "English output: worker\n" + ] + } + ], + "source": [ + "# Get the word probabilities for the last predicted word.\n", + "prob = transformer.generator(out[:, -1])\n", + "# Find the word index with the highest probability.\n", + "_, next_word_index = torch.max(prob, dim=1)\n", + "# Print the predicted English word.\n", + "print(\"English output:\", index_to_eng(next_word_index))\n", + "# Convert the tensor value to a Python scalar.\n", + "next_word_index = next_word_index.item()" + ] + }, + { + "cell_type": "markdown", + "id": "fc18930d-3fcd-4fe2-934a-aae99a02ed9a", + "metadata": {}, + "source": [ + "Now, update the prediction.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "5d00da69-b08c-48c9-b476-0e7f6eef4f70", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "engish output A worker\n" + ] + } + ], + "source": [ + "ys = torch.cat([ys,torch.ones(1, 1).type_as(src.data).fill_(next_word_index)], dim=0)\n", + "print(\"engish output\",index_to_eng(ys))" + ] + }, + { + "cell_type": "markdown", + "id": "df4585e2-7227-41d4-872f-fadc3a8ceafa", + "metadata": {}, + "source": [ + "The process can be summarized as follows: \n", + "Starting with the initial output of the encoder and the token, the decoder's output is looped back into the decoder until the translated sequence is fully decoded. This cycle continues until the length of the new translated sequence matches that of the original sequence. As shown in the following image, the function ```greedy_decode``` is also included.\n", + " \n", + "\"trasfoemr\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "af2c4ace-3128-44bc-a1f3-06b7f2da168a", + "metadata": {}, + "outputs": [], + "source": [ + "def greedy_decode(model, src, src_mask, max_len, start_symbol):\n", + " src = src.to(DEVICE)\n", + " src_mask = src_mask.to(DEVICE)\n", + "\n", + " memory = model.encode(src, src_mask)\n", + " ys = torch.ones(1, 1).fill_(start_symbol).type(torch.long).to(DEVICE)\n", + " for i in range(max_len-1):\n", + " memory = memory.to(DEVICE)\n", + " tgt_mask = (generate_square_subsequent_mask(ys.size(0))\n", + " .type(torch.bool)).to(DEVICE)\n", + " out = model.decode(ys, memory, tgt_mask)\n", + " out = out.transpose(0, 1)\n", + " prob = model.generator(out[:, -1])\n", + " _, next_word = torch.max(prob, dim=1)\n", + " next_word = next_word.item()\n", + "\n", + " ys = torch.cat([ys,\n", + " torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=0)\n", + " if next_word == EOS_IDX:\n", + " break\n", + " return ys" + ] + }, + { + "cell_type": "markdown", + "id": "764c5a9d-d606-4d0f-8b78-a6d76e4ed30b", + "metadata": {}, + "source": [ + "Retrieve the indices for the German language and generate the corresponding mask:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "9b1e32dc-5da5-45f5-82c9-abc8cb43a2cc", + "metadata": {}, + "outputs": [], + "source": [ + "src\n", + "src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool).to(DEVICE )" + ] + }, + { + "cell_type": "markdown", + "id": "e39b6b66-0aac-461d-8458-e92227ddee5e", + "metadata": {}, + "source": [ + "Set a reasonable value for the max length of target sequence:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "ee27cfb2-7cca-476c-8177-6d88520c92b4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "14" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max_len=src.shape[0]+5\n", + "max_len" + ] + }, + { + "cell_type": "markdown", + "id": "0bc01095-d562-4710-8b2c-63aee971d2c8", + "metadata": {}, + "source": [ + "Apply the function ```greedy_decode``` to data:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "fe75d719-fe24-4203-8f42-bb209bfbdb3a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "engish A worker is reading a subway . \n" + ] + } + ], + "source": [ + "ys=greedy_decode(transformer, src, src_mask, max_len, start_symbol=BOS_IDX)\n", + "print(\"engish \",index_to_eng(ys))" + ] + }, + { + "cell_type": "markdown", + "id": "298f4c0a-6a4b-4153-a5d8-a2455e89780c", + "metadata": {}, + "source": [ + "Notice that it works, but it's not exactly the same. However, it's still pretty good.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ed3844f2-4051-4034-b97a-4226f632200b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "engish A worker taking a reading on a subway train . \n" + ] + } + ], + "source": [ + "print(\"engish \",index_to_eng(tgt))" + ] + }, + { + "cell_type": "markdown", + "id": "8b6c5f0d-2647-4119-bcfa-89ff5e33cc0d", + "metadata": {}, + "source": [ + "### Decoding the differences: Training vs. inference in neural machine translation\n", + "\n", + "During the inference phase, when the model is deployed for actual translation tasks, the decoder generates the sequence without access to the expected target sequence. Instead, it bases its predictions on the encoder's output and the tokens it has produced in sequence so far. The process is autoregressive, with the decoder continually predicting the next token until it outputs an end-of-sequence token, indicating the translation is complete.\n", + "\n", + "The key difference between the training and inference stages lies in the inputs to the decoder. During training, the decoder benefits from exposure to the ground truth--receiving the exact target sequence tokens incrementally through a technique known as \"teacher forcing.\" This approach is in stark contrast to some other neural network architectures that rely on the network's previous predictions as inputs during training. Once training concludes, the datasets used resemble those employed in more conventional neural network models, providing a familiar foundation for comparison and evaluation.\n", + "\n", + "First, import `CrossEntropyLoss` loss and create a Cross Entropy Loss object The loss will not be calculated when the token with index `PAD_IDX` an input.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "48abbce7-ae66-4345-a107-e1ebd4c9f6ba", + "metadata": {}, + "outputs": [], + "source": [ + "from torch.nn import CrossEntropyLoss\n", + "\n", + "loss_fn = CrossEntropyLoss(ignore_index=PAD_IDX)" + ] + }, + { + "cell_type": "markdown", + "id": "069926d6-2e6e-41bd-8b37-5551af40dc59", + "metadata": {}, + "source": [ + "Drop the last sample of the target\n" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "6becf1d5-9068-4a0b-acae-07f9528fdb45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " A worker taking a reading on a subway train .\n", + " A worker taking a reading on a subway train . \n" + ] + } + ], + "source": [ + "tgt_input = tgt[:-1, :]\n", + "print(index_to_eng(tgt_input))\n", + "print(index_to_eng(tgt))" + ] + }, + { + "cell_type": "markdown", + "id": "6ff29f58-35d2-43a3-b690-7585a8529a5e", + "metadata": {}, + "source": [ + "Create the required masks\n" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "c67877bd-6c8d-4cbe-a407-2457695b6517", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of src_mask: torch.Size([9, 9])\n", + "Shape of tgt_mask: torch.Size([11, 11])\n", + "Shape of src_padding_mask: torch.Size([1, 9])\n", + "Shape of tgt_padding_mask: torch.Size([1, 11])\n" + ] + } + ], + "source": [ + "src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)\n", + "print(f\"Shape of src_mask: {src_mask.shape}\")\n", + "print(f\"Shape of tgt_mask: {tgt_mask.shape}\")\n", + "print(f\"Shape of src_padding_mask: {src_padding_mask.shape}\")\n", + "print(f\"Shape of tgt_padding_mask: {tgt_padding_mask.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "41f08336-f9be-4051-9074-7c7d039f5e3e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[False, False, False, False, False, False, False, False, False]],\n", + " device='cuda:0')" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "src_padding_mask" + ] + }, + { + "cell_type": "markdown", + "id": "0daf52a6-297f-4b81-84de-c87c382d01d8", + "metadata": {}, + "source": [ + "In the target mask, each subsequent column incrementally reveals more tokens by introducing negative infinity values, thereby unblocking them. You can display the target mask to visualize the progression or specifically identify which tokens are being masked at each step.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "61808b6a-9f0d-4af6-aa99-04afdbbc5460", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor([[0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], device='cuda:0')\n" + ] + }, + { + "data": { + "text/plain": [ + "['',\n", + " ' A',\n", + " ' A worker',\n", + " ' A worker taking',\n", + " ' A worker taking a',\n", + " ' A worker taking a reading',\n", + " ' A worker taking a reading on',\n", + " ' A worker taking a reading on a',\n", + " ' A worker taking a reading on a subway',\n", + " ' A worker taking a reading on a subway train',\n", + " ' A worker taking a reading on a subway train .']" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(tgt_mask)\n", + "[index_to_eng( tgt_input[t==0]) for t in tgt_mask] #index_to_eng(tgt_input))" + ] + }, + { + "cell_type": "markdown", + "id": "fb374a1e-0f55-424f-9c09-4ceb4774c8e3", + "metadata": {}, + "source": [ + "When you call `model(src, tgt_input, src_mask, tgt_mask, src_padding_mask, tgt_padding_mask, memory_key_padding_mask)`, the forward method of the `Seq2SeqTransformer` class. This process generates logits for the target sequence, which can then be translated into actual tokens by taking the highest probability prediction at each step in the sequence.\n" + ] + }, + { + "cell_type": "markdown", + "id": "20007bae-252b-47df-9c30-3a6f471b41d4", + "metadata": {}, + "source": [ + "## Loss\n" + ] + }, + { + "cell_type": "markdown", + "id": "ad929a00-bde6-40c3-b241-4b021a5fd62b", + "metadata": {}, + "source": [ + "Let's delve into how you can calculate the loss, you have your ```src``` and ```tgt_input```\n" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "893115f7-9ec3-44ed-82e8-f18e44816c71", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "output shape torch.Size([11, 1, 10837])\n", + "target shape torch.Size([11, 1])\n", + "source shape torch.Size([9, 1])\n" + ] + } + ], + "source": [ + "logits = transformer(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)\n", + "\n", + "print(\"output shape\",logits.shape)\n", + "print(\"target shape\",tgt_input.shape)\n", + "print(\"source shape \",src.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "1b858ea2-84b0-48e9-bc79-aa01400b704f", + "metadata": {}, + "source": [ + "\n", + "During the training phase, an intriguing and sophisticated aspect of the process is the dual functionality of the target. It simultaneously acts as the input for the transformer's decoder and as the standard against which the prediction's accuracy is measured. For clarity in the discussions, you'll refer to the target used as the input for the decoder as the \"Input to the Decoder.\" On the other hand, the \"Ground Truth for Prediction,\" which is a the target sequence shifted to the right, as it's an auto regressive model. This will be simply known as the \"Target out\" moving forward.\n" + ] + }, + { + "cell_type": "markdown", + "id": "20f585bc-e0f3-40ce-9ef3-653995b7279d", + "metadata": {}, + "source": [ + "Ground Truth for Prediction is simply shifted right and is called ```tgt_out``` , you can print out tokens:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "aa77e029-c6b6-425a-bfdb-8a29b2732b44", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch.Size([11, 1])\n" + ] + }, + { + "data": { + "text/plain": [ + "['A',\n", + " 'worker',\n", + " 'taking',\n", + " 'a',\n", + " 'reading',\n", + " 'on',\n", + " 'a',\n", + " 'subway',\n", + " 'train',\n", + " '.',\n", + " '']" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tgt_out = tgt[1:, :]\n", + "print(tgt_out.shape)\n", + "[index_to_eng(t) for t in tgt_out]" + ] + }, + { + "cell_type": "markdown", + "id": "95c25eb2-be10-4d98-b124-3242e20fb8d3", + "metadata": {}, + "source": [ + "\n", + "The token indices represent the classes you aim to predict. By flattening the tensor, each index becomes a distinct sample, serving as the target for the cross-entropy loss. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "7bcac2f6-dec2-40da-8c45-3689b34714a0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch.Size([11])\n" + ] + }, + { + "data": { + "text/plain": [ + "tensor([ 6, 348, 168, 4, 217, 9, 4, 369, 240, 5, 3],\n", + " device='cuda:0')" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tgt_out_flattened = tgt_out.reshape(-1)\n", + "print(tgt_out_flattened.shape)\n", + "tgt_out_flattened" + ] + }, + { + "cell_type": "markdown", + "id": "abc59967-1a29-4e5f-bca7-9a0b77e9d426", + "metadata": {}, + "source": [ + "In this autoregressive model, showcase the input target tokens after the application of the mask. Beside them, you can display the target output, illustrating how the model adeptly predicts past values based on present ones. This clear visualization highlights the model's capability to use current information to infer what has preceded, a key feature of its autoregressive nature.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "a2f7af8e-08f6-4632-8a0a-4719c53a7236", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['input: target: A',\n", + " 'input: A target: worker',\n", + " 'input: A worker target: taking',\n", + " 'input: A worker taking target: a',\n", + " 'input: A worker taking a target: reading',\n", + " 'input: A worker taking a reading target: on',\n", + " 'input: A worker taking a reading on target: a',\n", + " 'input: A worker taking a reading on a target: subway',\n", + " 'input: A worker taking a reading on a subway target: train',\n", + " 'input: A worker taking a reading on a subway train target: .',\n", + " 'input: A worker taking a reading on a subway train . target: ']" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "[\"input: {} target: {}\".format(index_to_eng( tgt_input[m==0]),index_to_eng( t)) for m,t in zip(tgt_mask,tgt_out)] " + ] + }, + { + "cell_type": "markdown", + "id": "14ac15a7-233d-4418-b31f-b572b22de838", + "metadata": {}, + "source": [ + "Now, calculate the loss as the output from the transformer's decoder is provided as input to the cross-entropy loss function along with the target sequence values. Given that the transformer's output has the dimensions sequence length, batch size, and features (vocab_size), it's necessary to reshape this output to align with the standard input format required by the cross-entropy loss function. This step ensures that the loss is calculated correctly, comparing the predicted sequence against the ground truth at each time step across the batch using the reshape method\n" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "e2510f5a-6aaa-4cc9-9b17-77d254adf536", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor(1.5756, device='cuda:0', grad_fn=)\n" + ] + } + ], + "source": [ + "loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))\n", + "print(loss)" + ] + }, + { + "cell_type": "markdown", + "id": "641380aa-7f40-4644-b83b-46639deb0c8d", + "metadata": {}, + "source": [ + "### Under the hood of loss calculation (Optional)\n", + "That's it for loss calculation, but if you are curious how the loss is calculated here is what happens under the hood of calculating the Cross Entropy loss. First, check the shape of tensors before and after the reshaping:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "ae957c55-bf8f-4990-a6a5-93638f4bc4fe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "logit's shape is: torch.Size([11, 1, 10837])\n", + "logit_flat's shape is: torch.Size([11, 10837])\n", + "tgt_out's shape is: torch.Size([11, 1])\n", + "tgt_out_flat's shape is: torch.Size([11])\n" + ] + } + ], + "source": [ + "# logits.reshape(-1, logits.shape[-1]) reshapes the logits tensor to a 2D tensor with a shape of [sequence_length * batch_size, vocab_size]. This reshaping is done to align both the predicted logits and target outputs for the loss calculation.\n", + "print(\"logit's shape is:\",logits.shape)\n", + "logits_flattened = logits.reshape(-1, logits.shape[-1])\n", + "print(\"logit_flat's shape is:\",logits_flattened.shape)\n", + "\n", + "\n", + "# tgt_out.reshape(-1) reshapes the tgt_out tensor to a 1D tensor by flattening it along the sequence and batch dimensions. This is done to align it with the reshaped logits.\n", + "print(\"tgt_out's shape is:\",tgt_out.shape)\n", + "tgt_out_flattened = tgt_out.reshape(-1)\n", + "print(\"tgt_out_flat's shape is:\",tgt_out_flattened.shape)\n" + ] + }, + { + "cell_type": "markdown", + "id": "f8aaa120-4d5e-4def-9901-5764e6130747", + "metadata": {}, + "source": [ + "Inside the loss function, logits will transform into probabilities between [0,1] that sum up to 1:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "d1a88ba9-021c-412d-a8dd-ad6277ef0d01", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(1.0000, device='cuda:0', grad_fn=)" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Applying the Cross-Entropy Loss Function\n", + "probs = torch.nn.functional.softmax(logits_flattened, dim=1)\n", + "probs[1].sum()" + ] + }, + { + "cell_type": "markdown", + "id": "f8ff8d69-43af-4144-9b29-28606b3ffb65", + "metadata": {}, + "source": [ + "let's check the probabilities for some random tokens:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "41d82aa4-0c27-44b2-8aef-1901465f1c11", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Predicted token id: 6 predicted probaility: 0.8037638664245605\n", + "Actual token id: 6 predicted probaility: 0.8037638664245605 \n", + "\n", + "Predicted token id: 348 predicted probaility: 0.8495506048202515\n", + "Actual token id: 348 predicted probaility: 0.8495506048202515 \n", + "\n", + "Predicted token id: 10 predicted probaility: 0.5691133737564087\n", + "Actual token id: 168 predicted probaility: 1.8116032151738182e-05 \n", + "\n", + "Predicted token id: 4 predicted probaility: 0.9238837957382202\n", + "Actual token id: 4 predicted probaility: 0.9238837957382202 \n", + "\n", + "Predicted token id: 369 predicted probaility: 0.27735066413879395\n", + "Actual token id: 217 predicted probaility: 0.10477317124605179 \n", + "\n" + ] + } + ], + "source": [ + "for i in range (5):\n", + " # using argmax, you can retrieve the index of the token that is predicted with the highest probaility\n", + " print(\"Predicted token id:\",probs[i].argmax().item(), \"predicted probaility:\",probs[i].max().item())\n", + " # you can also check the actual token from the tgt_out_flat\n", + " print(\"Actual token id:\",tgt_out_flattened[i].item(), \"predicted probaility:\", probs[i,tgt_out_flattened[i]].item(),\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "e818c4c4-a0c2-4ad8-a40f-6a0bdffa41fa", + "metadata": {}, + "source": [ + "It can be seen that for many tokens the model is doing a good job in predicting the token, while for some of the tokens(for example the third block in the output above) the model is not assigning a high probability to the actual token to be predicted. The difference between the predicted probability for such tokens is the reason why the loss would not sum up to 0.\n", + "\n", + "Now, you can proceed with calculating the difference between the actual token's probability (1) and the predicted probabilities for each token:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "d641284d-d3d3-4434-a1ee-7042732701ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loss: -0.6220170259475708\n" + ] + } + ], + "source": [ + "neg_log_likelihood = torch.nn.functional.nll_loss(probs, tgt_out_flattened)\n", + "# Step 3: Obtaining the Loss Value\n", + "loss = neg_log_likelihood\n", + "\n", + "# Print the total loss value\n", + "print(\"Loss:\", loss.item())" + ] + }, + { + "cell_type": "markdown", + "id": "6e80660f-ba1d-49c9-a816-de7b60ec7a36", + "metadata": {}, + "source": [ + "## Evaluate \n", + "By following the aforementioned procedures, you can develop a function that is capable of making predictions and subsequently computing the corresponding loss on the validation data, you will use this function later on.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "284f7939-94db-4466-9b33-86cdf8a58b65", + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate(model):\n", + " model.eval()\n", + " losses = 0\n", + "\n", + "\n", + "\n", + " for src, tgt in val_dataloader:\n", + " src = src.to(DEVICE)\n", + " tgt = tgt.to(DEVICE)\n", + "\n", + " tgt_input = tgt[:-1, :]\n", + "\n", + " src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)\n", + " logits = model(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)\n", + "\n", + " tgt_out = tgt[1:, :]\n", + " loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))\n", + " losses += loss.item()\n", + "\n", + " return losses / len(list(val_dataloader))" + ] + }, + { + "cell_type": "markdown", + "id": "7695b930-a49d-4cc6-8701-d285d21f633d", + "metadata": {}, + "source": [ + "# Training the model\n", + "Incorporating the previously outlined steps, proceed to train the model. Apart from these specific procedures, the overall training process conforms to the conventional methods employed in neural network training. Now, write a function to train the model\n" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "b162cc2e-46bc-4294-8c15-aa99e7feef8f", + "metadata": {}, + "outputs": [], + "source": [ + "def train_epoch(model, optimizer, train_dataloader):\n", + " model.train()\n", + " losses = 0\n", + "\n", + " # Wrap train_dataloader with tqdm for progress logging\n", + " train_iterator = tqdm(train_dataloader, desc=\"Training\", leave=False)\n", + "\n", + " for src, tgt in train_iterator:\n", + " src = src.to(DEVICE)\n", + " tgt = tgt.to(DEVICE)\n", + "\n", + " tgt_input = tgt[:-1, :]\n", + "\n", + " src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)\n", + " src_mask = src_mask.to(DEVICE)\n", + " tgt_mask = tgt_mask.to(DEVICE)\n", + " src_padding_mask = src_padding_mask.to(DEVICE)\n", + " tgt_padding_mask = tgt_padding_mask.to(DEVICE)\n", + "\n", + " logits = model(src, tgt_input, src_mask, tgt_mask, src_padding_mask, tgt_padding_mask, src_padding_mask)\n", + " logits = logits.to(DEVICE)\n", + "\n", + " optimizer.zero_grad()\n", + "\n", + " tgt_out = tgt[1:, :]\n", + " loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))\n", + " loss.backward()\n", + "\n", + " optimizer.step()\n", + " losses += loss.item()\n", + "\n", + " # Update tqdm progress bar with the current loss\n", + " train_iterator.set_postfix(loss=loss.item())\n", + "\n", + " return losses / len(list(train_dataloader))" + ] + }, + { + "cell_type": "markdown", + "id": "0b5e5572-d2e2-4cf5-8da3-89ad46b741ab", + "metadata": {}, + "source": [ + "The configuration for the translation model includes a source and target vocabulary size determined by the dataset languages, an embedding size of 512, 8 attention heads, a hidden dimension for the feed-forward network of 512, and a batch size of 128. The model is structured with three layers each in both the encoder and the decoder.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "ccc88374-9924-4230-91e0-d2cdab65f0d0", + "metadata": {}, + "outputs": [], + "source": [ + "torch.manual_seed(0)\n", + "\n", + "SRC_VOCAB_SIZE = len(vocab_transform[SRC_LANGUAGE])\n", + "TGT_VOCAB_SIZE = len(vocab_transform[TGT_LANGUAGE])\n", + "EMB_SIZE = 512\n", + "NHEAD = 8\n", + "FFN_HID_DIM = 512\n", + "BATCH_SIZE = 128\n", + "NUM_ENCODER_LAYERS = 3\n", + "NUM_DECODER_LAYERS = 3" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "31dfaf01-c07e-45b6-8dba-4be829c878e5", + "metadata": {}, + "outputs": [], + "source": [ + "train_dataloader, val_dataloader = get_translation_dataloaders(batch_size = BATCH_SIZE)" + ] + }, + { + "cell_type": "markdown", + "id": "7bcb4619-d411-45b9-b37d-62872599b755", + "metadata": {}, + "source": [ + "Create a transformer model.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "e35c50cb-4f8f-48a6-9cf6-b0b35614d24a", + "metadata": {}, + "outputs": [], + "source": [ + "transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS, EMB_SIZE,\n", + " NHEAD, SRC_VOCAB_SIZE, TGT_VOCAB_SIZE, FFN_HID_DIM)\n", + "transformer = transformer.to(DEVICE)" + ] + }, + { + "cell_type": "markdown", + "id": "de1e8c38-eb94-410a-ad03-41b764da8297", + "metadata": {}, + "source": [ + "Initialize the weights of the transformer model.\n" + ] + }, + { + "cell_type": "markdown", + "id": "52fee72d-48ae-4a1c-86d3-85bdb2d890e6", + "metadata": {}, + "source": [ + "Insulate the Adam optimizer.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "dce129ea-57de-4957-adb5-0b5990cc5ee7", + "metadata": {}, + "outputs": [], + "source": [ + "optimizer = torch.optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)" + ] + }, + { + "cell_type": "markdown", + "id": "ceaa8377-f549-4aaf-bbe3-a58beb79f6cc", + "metadata": {}, + "source": [ + "Initialize the train loss and validation loss list.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "08a475f5-7a1c-4d18-8a84-0af88a4ed117", + "metadata": {}, + "outputs": [], + "source": [ + "TrainLoss=[]\n", + "ValLoss=[]" + ] + }, + { + "cell_type": "markdown", + "id": "88d54dd4-794f-4816-b92e-5921b2eb7a2f", + "metadata": {}, + "source": [ + "Train the model for 10 epochs using the above functions.\n", + "\n", + "Please be aware that training the model using CPUs can be a time-consuming process. If you don't have access to GPUs, you can jump to \"loading the saved model\" and proceed with loading the pretrained model using the provided code. You have been provided with the saved model that has been trained for 40 epochs. \n", + "> The Skills Network Lab environment uses CPU. The training time for each epoch can anywhere between 40 minutes to an hour.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "c1379b1c-0f24-4068-919f-d5e910b2f0d2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 1, Train loss: 3.880, Val loss: 3.834, Epoch time = 25.898s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 2, Train loss: 3.484, Val loss: 3.596, Epoch time = 25.854s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 3, Train loss: 3.229, Val loss: 3.406, Epoch time = 26.871s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 4, Train loss: 3.036, Val loss: 3.248, Epoch time = 26.921s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 5, Train loss: 2.877, Val loss: 3.140, Epoch time = 27.636s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 6, Train loss: 2.740, Val loss: 3.034, Epoch time = 24.879s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 7, Train loss: 2.622, Val loss: 2.934, Epoch time = 26.314s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 8, Train loss: 2.515, Val loss: 2.862, Epoch time = 27.559s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 9, Train loss: 2.418, Val loss: 2.813, Epoch time = 28.556s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 10, Train loss: 2.333, Val loss: 2.754, Epoch time = 26.587s\n" + ] + } + ], + "source": [ + "from timeit import default_timer as timer\n", + "NUM_EPOCHS = 10\n", + "\n", + "for epoch in range(1, NUM_EPOCHS+1):\n", + " start_time = timer()\n", + " train_loss = train_epoch(transformer, optimizer, train_dataloader)\n", + " TrainLoss.append(train_loss)\n", + " end_time = timer()\n", + " val_loss = evaluate(transformer)\n", + " ValLoss.append(val_loss)\n", + " print((f\"Epoch: {epoch}, Train loss: {train_loss:.3f}, Val loss: {val_loss:.3f}, \"f\"Epoch time = {(end_time - start_time):.3f}s\"))\n", + "torch.save(transformer.state_dict(), 'transformer_de_to_en_model.pt')" + ] + }, + { + "cell_type": "markdown", + "id": "9ef8ec82-331d-463e-ba8b-188525180bbe", + "metadata": {}, + "source": [ + "Plot the loss for the training and validation data." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "07302c22-9ed2-4fce-8f8b-795654784fea", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA04AAAHWCAYAAABACtmGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/xnp5ZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB1/ElEQVR4nO3dd3hU1drG4WdSSCEFCJACobdQQlcBKQpIURQLTRTwcPQoICKCXUTwiDQbHhAsoCKgIKAiRVBAmoLSQhWkQwBpCaEkkMz3x/pShoRJAkn2JPnd17WvzOzZs/c7OGoe1trvstntdrsAAAAAANflZnUBAAAAAODqCE4AAAAAkAmCEwAAAABkguAEAAAAAJkgOAEAAABAJghOAAAAAJAJghMAAAAAZILgBAAAAACZIDgBAAAAQCYITgDgwvr06aMKFSrc0HuHDx8um82WswW5mAMHDshms2natGl5fm2bzabhw4enPJ82bZpsNpsOHDiQ6XsrVKigPn365Gg9N/NduRlW/jMAgLxEcAKAG2Cz2bK0rVixwupSC72BAwfKZrNp79691z3mlVdekc1m09atW/Owsuw7duyYhg8frs2bN1tdCgAUOh5WFwAA+dGXX37p8PyLL77Q0qVL0+2PiIi4qet8/PHHSkpKuqH3vvrqq3rxxRdv6voFQc+ePTVhwgTNmDFDw4YNy/CYmTNnqk6dOoqMjLzh6zz66KPq3r27vLy8bvgcmTl27JjeeOMNVahQQfXq1XN47Wa+KwCAzBGcAOAGPPLIIw7Pf/vtNy1dujTd/mtdvHhRvr6+Wb6Op6fnDdUnSR4eHvLw4D/zt956q6pUqaKZM2dmGJzWrVun/fv36+23376p67i7u8vd3f2mznEzbua7AgDIHFP1ACCXtGrVSrVr19aff/6pFi1ayNfXVy+//LIk6bvvvtPdd9+tsLAweXl5qXLlyho5cqQSExMdznHtfSvJ95OMGzdOU6ZMUeXKleXl5aXGjRtrw4YNDu/N6B4nm82mAQMGaP78+apdu7a8vLxUq1YtLV68OF39K1asUKNGjeTt7a3KlStr8uTJWb5vatWqVerSpYvKlSsnLy8vhYeH69lnn9WlS5fSfT4/Pz8dPXpUnTt3lp+fn0qVKqUhQ4ak+7M4d+6c+vTpo8DAQBUrVky9e/fWuXPnMq1FMqNOu3bt0saNG9O9NmPGDNlsNvXo0UMJCQkaNmyYGjZsqMDAQBUtWlTNmzfX8uXLM71GRvc42e12vfnmmypbtqx8fX11xx13aPv27enee+bMGQ0ZMkR16tSRn5+fAgIC1KFDB23ZsiXlmBUrVqhx48aSpMceeyxlOmjyvUUZ3eN04cIFPffccwoPD5eXl5eqV6+ucePGyW63OxyXne9FVv3yyy9q3ry5ihYtqmLFium+++7Tzp07HY45f/68Bg0apAoVKsjLy0ulS5dW27ZtHf457dmzRw8++KBCQkLk7e2tsmXLqnv37oqJibnh2gDgRvBXkQCQi06fPq0OHTqoe/fueuSRRxQcHCzJ/JLt5+enwYMHy8/PT7/88ouGDRum2NhYjR07NtPzzpgxQ+fPn9d//vMf2Ww2jRkzRg888ID27duX6cjD6tWrNXfuXPXr10/+/v764IMP9OCDD+rQoUMKCgqSJG3atEnt27dXaGio3njjDSUmJmrEiBEqVapUlj737NmzdfHiRT311FMKCgrS+vXrNWHCBB05ckSzZ892ODYxMVHt2rXTrbfeqnHjxmnZsmUaP368KleurKeeekqSCSD33XefVq9erSeffFIRERGaN2+eevfunaV6evbsqTfeeEMzZsxQgwYNHK79zTffqHnz5ipXrpxOnTqlTz75RD169NDjjz+u8+fP69NPP1W7du20fv36dNPjMjNs2DC9+eab6tixozp27KiNGzfqrrvuUkJCgsNx+/bt0/z589WlSxdVrFhRJ06c0OTJk9WyZUvt2LFDYWFhioiI0IgRIzRs2DA98cQTat68uSSpadOmGV7bbrfr3nvv1fLly9W3b1/Vq1dPS5Ys0dChQ3X06FG9++67Dsdn5XuRVcuWLVOHDh1UqVIlDR8+XJcuXdKECRPUrFkzbdy4MSXgPfnkk5ozZ44GDBigmjVr6vTp01q9erV27typBg0aKCEhQe3atVN8fLyefvpphYSE6OjRo1qwYIHOnTunwMDAbNUFADfFDgC4af3797df+5/Uli1b2iXZP/roo3THX7x4Md2+//znP3ZfX1/75cuXU/b17t3bXr58+ZTn+/fvt0uyBwUF2c+cOZOy/7vvvrNLsv/www8p+15//fV0NUmyFylSxL53796UfVu2bLFLsk+YMCFlX6dOney+vr72o0ePpuzbs2eP3cPDI905M5LR5xs1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9+/XRx99pJo1ayouLi5b10pej2rUqFG655571LFjR23atEmLFi1yGEVKvu6IESP02GOPqWnTpoqKitJXX33lMFIlSZUrV1axYsX00Ucfyd/fX0WLFtWtt96qihUrprt+p06ddMcdd+iVV17RgQMHVLduXf3000/67rvvNGjQIIdGEDlt7Nix6tChg5o0aaK+ffumtCMPDAzU8OHDJZmmKGXLltVDDz2kunXrys/PT8uWLdOGDRs0fvx4SWYtqAEDBqhLly6qVq2arl69qi+//FLu7u568MEHc61+AMgII04AkMeCgoK0YMEChYaG6tVXX9W4cePUtm1bjRkzxurSUjRs2FCLFi1S8eLF9dprr+nTTz/ViBEj1Lp1a4cRmox4enrqhx9+UL169TRq1Ci98cYbqlq1qr744osbrsfNzU3ff/+9evbsqenTp+uVV15RmTJl9Pnnn2f7XMlhKTQ0VHfeeafDa3369NFbb72lLVu2aODAgVqyZImmT5+uRo0a3VDdb775pt544w1t2rRJQ4cO1d9//62ffvrJYaqaJL388st67rnntGTJEj3zzDPauHGjfvzxR4WHhzsc5+npqc8//1zu7u568skn1aNHD61cuTLDayf/mQ0aNEgLFizQoEGDtGPHDo0dO1bvvPPODX2erGrTpo0WL16soKAgDRs2TOPGjdNtt92mNWvWpIQ8X19f9evXT5s3b9brr7+uZ599Vrt379bEiRM1ePBgSVLdunXVrl07/fDDDxo8eLCGDx8uPz8/LVq0SLfddluufgYAuJbN7kp/xQkAcGmdO3fW9u3btWfPHqtLAQAgTzHiBADI0KVLlxye79mzRwsXLlSrVq2sKQgAAAsx4gQAyFBoaKj69OmjSpUq6eDBg5o0aZLi4+O1adOmdGsTAQBQ0NEcAgCQofbt22vmzJk6fvy4vLy81KRJE7311luEJgBAocSIEwAAAABkgnucAAAAACATBCcAAAAAyEShu8cpKSlJx44dk7+/v2w2m9XlAAAAALCI3W7X+fPnFRYWJjc352NKhS44HTt2LN2CggAAAAAKr8OHD6ts2bJOjyl0wcnf31+S+cMJCAiwuBoAAAAAVomNjVV4eHhKRnCm0AWn5Ol5AQEBBCcAAAAAWbqFh+YQAAAAAJAJghMAAAAAZILgBAAAAACZKHT3OAEAAMC12e12Xb16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AC7tjjvucAhGklSiRImUx02aNHF4rUmTJtq8ebMkaefOnapbt25KaJKkZs2aKSkpSbt375bNZtOxY8fUunVrpzVERkamPC5atKgCAgJ08uRJSdJTTz2lBx98UBs3btRdd92lzp07q2nTpjf0WQEArovgBABwaUWLFk03dS6n+Pj4ZOk4T09Ph+c2m01JSUmSpA4dOujgwYNauHChli5dqtatW6t///4aN25cjtcLALAO9zgBAPK13377Ld3ziIgISVJERIS2bNmiCxcupLy+Zs0aubm5qXr16vL391eFChX0888/31QNpUqVUu/evTV9+nS99957mjJlyk2dDwDgehhxAgC4tPj4eB0/ftxhn4eHR0oDhtmzZ6tRo0a6/fbb9dVXX2n9+vX69NNPJUk9e/bU66+/rt69e2v48OH6559/9PTTT+vRRx9VcHCwJGn48OF68sknVbp0aXXo0EHnz5/XmjVr9PTTT2epvmHDhqlhw4aqVauW4uPjtWDBgpTgBgAoOAhOAACXtnjxYoWGhjrsq169unbt2iXJdLybNWuW+vXrp9DQUM2cOVM1a9aUJPn6+mrJkiV65pln1LhxY/n6+urBBx/UO++8k3Ku3r176/Lly3r33Xc1ZMgQlSxZUg899FCW6ytSpIheeuklHThwQD4+PmrevLlmzZqVA58cAOBK6KoHAMi3bDab5s2bp86dO1tdCgCggOMeJwAAAADIBMEJAAAAADLBPU4AgHyL2eYAgLzCiBMAAAAAZILgBAAAAACZIDgBAAAAQCYITgAAAACQCYITAAAAAGSC4AQAAAAAmSA4AQAAAEAmCE4AAAAAkIn/A7nXQJhapSDEAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "epochs = range(1, len(TrainLoss) + 1)\n", + "\n", + "plt.figure(figsize=(10, 5))\n", + "plt.plot(epochs, TrainLoss, 'r', label='Training loss')\n", + "plt.plot(epochs,ValLoss, 'b', label='Validation loss')\n", + "plt.title('Training and Validation loss')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('Loss')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "e1d44869-6dcb-42d1-92ef-eff2fb8445f1", + "metadata": {}, + "source": [ + "## Loading the saved model\n", + "If you want to skip training and load the pretrained model that is provided, go ahead and uncomment the following cell:\n", + "\n", + "```bash\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0201EN-Coursera/transformer_de_to_en_model.pt'\n", + "transformer.load_state_dict(torch.load('transformer_de_to_en_model.pt',map_location=torch.device('cpu')))\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "b843fbee-e631-4385-a7eb-0e9ffd04fae5", + "metadata": {}, + "source": [ + "## Translation and evaluation \n", + "Using the greedy_decode function that you defined earlier, you can create a translator function that generates English translation of an input German text.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "1a919202-6504-457f-8134-91076072f462", + "metadata": {}, + "outputs": [], + "source": [ + "# translate input sentence into target language\n", + "def translate(model: torch.nn.Module, src_sentence: str):\n", + " model.eval()\n", + " src = text_transform[SRC_LANGUAGE](src_sentence).view(-1, 1)\n", + " num_tokens = src.shape[0]\n", + " src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool)\n", + " tgt_tokens = greedy_decode(\n", + " model, src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()\n", + " return \" \".join(vocab_transform[TGT_LANGUAGE].lookup_tokens(list(tgt_tokens.cpu().numpy()))).replace(\"\", \"\").replace(\"\", \"\")" + ] + }, + { + "cell_type": "markdown", + "id": "70875325-2d3f-425f-84e0-2b3d175d958b", + "metadata": {}, + "source": [ + "Now, let's look into some sample translations:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "1f8198b7-143d-4432-bac3-7d3fb42dd8cf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "German Sentence: Männer stehen neben irgendeiner hydraulischen Maschine . \n", + "English Translation: Men are standing next to some sort of hydraulic machine . \n", + "Model Translation: Men stand near a lake stand near some sort of some sort of some sort of some sort of\n", + "_________\n", + "\n", + "German Sentence: Zwei Arbeiter reinigen nachts ein Bauwerk . \n", + "English Translation: Two workers are cleaning a structure at night . \n", + "Model Translation: Two workers are cleaning a long beach while a worker is a younger night in a long long long\n", + "_________\n", + "\n", + "German Sentence: Sieben Bauarbeiter arbeiten an einem Gebäude . \n", + "English Translation: Seven construction workers working on a building . \n", + "Model Translation: Construction workers work on a building working on a building working on a building . \n", + "_________\n", + "\n", + "German Sentence: Die Kinder spielen nachts mit Wunderkerzen . \n", + "English Translation: The children play with sparklers at night . \n", + "Model Translation: The children are playing with a green and white and white striped shirt is playing with a few people\n", + "_________\n", + "\n", + "German Sentence: Ein älteres Paar geht zusammen spazieren . \n", + "English Translation: An older couple taking a walk together . \n", + "Model Translation: A couple walks together in a pink day walks together together together together in a park . \n", + "_________\n", + "\n" + ] + } + ], + "source": [ + "for n in range(5):\n", + " german, english= next(data_itr)\n", + "\n", + " print(\"German Sentence:\",index_to_german(german).replace(\"\", \"\").replace(\"\", \"\"))\n", + " print(\"English Translation:\",index_to_eng(english).replace(\"\", \"\").replace(\"\", \"\"))\n", + " print(\"Model Translation:\",translate(transformer,index_to_german(german)))\n", + " print(\"_________\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "277537fb-7697-4b97-af23-197c804bfb32", + "metadata": {}, + "source": [ + "### Evaluation with BLEU score\n", + "To evaluate the generated translations, a function calculate_bleu_score is introduced. It computes the BLEU score, a common metric for machine translation quality, by comparing the generated translation to reference translations. The BLEU score provides a quantitative measure of translation accuracy.\n", + "\n", + "The code also includes an example of calculating the BLEU score for a generated translation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "d06e06e9-3d89-471a-8ee2-1ee67759d9be", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_bleu_score(generated_translation, reference_translations):\n", + " # convert the generated translations and reference translations into the expected format for sentence_bleu\n", + " references = [reference.split() for reference in reference_translations]\n", + " hypothesis = generated_translation.split()\n", + "\n", + " # calculate the BLEU score\n", + " bleu_score = sentence_bleu(references, hypothesis)\n", + "\n", + " return bleu_score" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "2bc88863-3297-40dd-8f09-bcb586eff830", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BLEU Score: 0.2796842457966537 for A brown dog playing in the snow with brown dog playing in the\n" + ] + } + ], + "source": [ + "generated_translation = translate(transformer,\"Ein brauner Hund spielt im Schnee .\")\n", + "\n", + "reference_translations = [\n", + " \"A brown dog is playing in the snow .\",\n", + " \"A brown dog plays in the snow .\",\n", + " \"A brown dog is frolicking in the snow .\",\n", + " \"In the snow, a brown dog is playing .\"\n", + "\n", + "]\n", + "\n", + "bleu_score = calculate_bleu_score(generated_translation, reference_translations)\n", + "print(\"BLEU Score:\", bleu_score, \"for\",generated_translation)" + ] + }, + { + "cell_type": "markdown", + "id": "c4ce32b1-e543-4f04-9392-8ec93dba2d9f", + "metadata": {}, + "source": [ + "## Exercise: Translating a document\n", + "In this exercise, you will implement a feature that translates a PDF in German to English. To achieve this, you will leverage the same sequence-to-sequence transformer model discussed previously and make necessary modifications.\n", + "\n", + "1. **Define the translation function**:\n", + " Create a function named `translate_pdf` that takes the following parameters:\n", + " - `input_file`: The path to the input PDF file to be translated.\n", + " - `translator_model`: A model or function that will handle the translation of text.\n", + " - `output_file`: The path where the translated PDF will be saved.\n", + "\n", + "2. **Read and translate the PDF**:\n", + " Use `pdfplumber` to open and read the text from each page of the input PDF. Translate the extracted text using the `translator_model`.\n", + "\n", + "3. **Format and write the translated text to a new PDF**:\n", + " - Use `textwrap` to wrap the translated text so that it fits within the A4 page width.\n", + " - Create a new PDF with `FPDF` and add the wrapped translated text to it.\n", + " - Save the new PDF with the translated text to `output_file`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "cc7e078b-ed07-4d45-9dd7-322e0f236f52", + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install -qq pdfplumber\n", + "# !pip install -qq fpdf" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "5830d5eb-8fa5-4cb4-9160-d1c6af951cf2", + "metadata": {}, + "outputs": [], + "source": [ + "import pdfplumber\n", + "import textwrap\n", + "from fpdf import FPDF\n", + "\n", + "def translate_pdf(input_file, translator_model,output_file):\n", + " translated_text = \"\"\n", + "\n", + " # Read the input PDF file\n", + " with pdfplumber.open(input_file) as pdf:\n", + "\n", + "\n", + " # Extract text from each page of the PDF\n", + " for page in pdf.pages:\n", + " text_content = page.extract_text()\n", + " num_pages = len(pdf.pages)\n", + " a4_width_mm = 210\n", + " pt_to_mm = 0.35\n", + " fontsize_pt = 10\n", + " fontsize_mm = fontsize_pt * pt_to_mm\n", + " margin_bottom_mm = 10\n", + " character_width_mm = 7 * pt_to_mm\n", + " width_text = a4_width_mm / character_width_mm\n", + "\n", + " pdf = FPDF(orientation='P', unit='mm', format='A4')\n", + " pdf.set_auto_page_break(True, margin=margin_bottom_mm)\n", + " pdf.add_page()\n", + " pdf.set_font(family='Courier', size=fontsize_pt)\n", + " # Split the text into sentences\n", + " sentences = text_content.split(\".\")\n", + "\n", + " # Translate each sentence using the custom translator model\n", + " for sentence in sentences:\n", + " translated_sentence = translate(translator_model,sentence)\n", + " lines = textwrap.wrap(translated_sentence, width_text)\n", + "\n", + " if len(lines) == 0:\n", + " pdf.ln()\n", + "\n", + " for wrap in lines:\n", + " pdf.cell(0, fontsize_mm, wrap, ln=1)\n", + "\n", + " pdf.output(output_file, 'F')" + ] + }, + { + "cell_type": "markdown", + "id": "761ae282-e672-4458-bc5a-a6885bfcf612", + "metadata": {}, + "source": [ + "Here is a German document for you to convert\n", + "```bash\n", + "!wget 'https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0201EN-Coursera/input_de.pdf'\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "190fc1d4-d573-44fe-a6a5-4abc34295c7f", + "metadata": {}, + "source": [ + "Now call the translate_pdf for the German file as an input to the function and check the output file for the translated file.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "40b8cfbb-4a3a-4ef4-a1a8-df293384bcef", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Translated PDF file is saved as: output_en.pdf\n" + ] + } + ], + "source": [ + "input_file_path = \"input_de.pdf\"\n", + "output_file = 'output_en.pdf'\n", + "translate_pdf(input_file_path, transformer,output_file)\n", + "print(\"Translated PDF file is saved as:\", output_file)" + ] + }, + { + "cell_type": "markdown", + "id": "adc20ee7-a829-4fd6-8df7-b54fa0c05fc0", + "metadata": {}, + "source": [ + "
\n", + " Click here for Solution\n", + "\n", + "```python\n", + "input_file_path = \"input_de.pdf\"\n", + "output_file = 'output_en.pdf'\n", + "translate_pdf(input_file_path, transformer,output_file)\n", + "print(\"Translated PDF file is saved as:\", output_file)\n", + "```\n", + "\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "5ba3a03a-2273-48f4-8bab-62ce8aa36cc2", + "metadata": {}, + "source": [ + "# Congratulations! You have completed the lab\n", + "\n", + "## Authors\n", + "\n", + "[Joseph Santarcangelo](https://author.skills.network/instructors/joseph_santarcangelo) has a Ph.D. in Electrical Engineering, his research focused on using machine learning, signal processing, and computer vision to determine how videos impact human cognition. Joseph has been working for IBM since he completed his PhD.\n", + "\n", + "[Fateme Akbari](https://www.linkedin.com/in/fatemeakbari/) is a Ph.D. candidate in Information Systems at McMaster University with demonstrated research experience in Machine Learning and NLP.\n", + "\n", + "## References\n", + "[Attention is all you need](https://papers.nips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf) paper.\n", + "\n", + "[Language Translation with nn.Transformer and torchtext](https://pytorch.org/tutorials/beginner/translation_transformer.html) PyTorch tutorial\n", + "\n", + "© Copyright IBM Corporation. All rights reserved.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca7940ef-72cd-4fca-ac16-62790c98c0c0", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/LLM_Specialization/Multi30K_de_en_dataloader.py.1 b/notebooks/LLM_Specialization/Multi30K_de_en_dataloader.py.1 new file mode 100644 index 0000000..f4f229f --- /dev/null +++ b/notebooks/LLM_Specialization/Multi30K_de_en_dataloader.py.1 @@ -0,0 +1,102 @@ +import torch +from torch.nn.utils.rnn import pad_sequence +from torch.utils.data import DataLoader +from torchtext.datasets import Multi30k, multi30k +from torchtext.data.utils import get_tokenizer +from torchtext.vocab import build_vocab_from_iterator +from typing import Iterable, List + +# We need to modify the URLs for the dataset since the links to the original dataset are broken +multi30k.URL["train"] = "https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0205EN-SkillsNetwork/training.tar.gz" +multi30k.URL["valid"] = "https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMSkillsNetwork-AI0205EN-SkillsNetwork/validation.tar.gz" + +SRC_LANGUAGE = 'de' +TGT_LANGUAGE = 'en' + +# Making a placeholder dict to store both tokenizers +token_transform = {} +token_transform[SRC_LANGUAGE] = get_tokenizer('spacy', language='de_core_news_sm') +token_transform[TGT_LANGUAGE] = get_tokenizer('spacy', language='en_core_web_sm') + +# Define special symbols and indices +UNK_IDX, PAD_IDX, BOS_IDX, EOS_IDX = 0, 1, 2, 3 +special_symbols = ['', '', '', ''] + +# Place holder dict for 'en' and 'de' vocab transforms +vocab_transform = {} + +def yield_tokens(data_iter: Iterable, language: str) -> List[str]: + language_index = {SRC_LANGUAGE: 0, TGT_LANGUAGE: 1} + for data_sample in data_iter: + yield token_transform[language](data_sample[language_index[language]]) + +for ln in [SRC_LANGUAGE, TGT_LANGUAGE]: + train_iterator = Multi30k(split='train', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE)) + sorted_dataset = sorted(train_iterator, key=lambda x: len(x[0].split())) + vocab_transform[ln] = build_vocab_from_iterator(yield_tokens(sorted_dataset, ln), + min_freq=1, + specials=special_symbols, + special_first=True) + +for ln in [SRC_LANGUAGE, TGT_LANGUAGE]: + vocab_transform[ln].set_default_index(UNK_IDX) + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + +def tensor_transform_s(token_ids: List[int]): + return torch.cat((torch.tensor([BOS_IDX]), + torch.flip(torch.tensor(token_ids), dims=(0,)), + torch.tensor([EOS_IDX]))) + +def tensor_transform_t(token_ids: List[int]): + return torch.cat((torch.tensor([BOS_IDX]), + torch.tensor(token_ids), + torch.tensor([EOS_IDX]))) + +def sequential_transforms(*transforms): + def func(txt_input): + for transform in transforms: + txt_input = transform(txt_input) + return txt_input + return func + +text_transform = {} +def collate_fn(batch): + src_batch, tgt_batch = [], [] + for src_sample, tgt_sample in batch: + src_sequences = text_transform[SRC_LANGUAGE](src_sample.rstrip("\n")) + src_sequences = torch.tensor(src_sequences, dtype=torch.int64) + tgt_sequences = text_transform[TGT_LANGUAGE](tgt_sample.rstrip("\n")) + tgt_sequences = torch.tensor(tgt_sequences, dtype=torch.int64) + src_batch.append(src_sequences) + tgt_batch.append(tgt_sequences) + + src_batch = pad_sequence(src_batch, padding_value=PAD_IDX, batch_first=True) + tgt_batch = pad_sequence(tgt_batch, padding_value=PAD_IDX, batch_first=True) + src_batch = src_batch.t() + tgt_batch = tgt_batch.t() + return src_batch.to(device), tgt_batch.to(device) + +def get_translation_dataloaders(batch_size=4,flip=False): + train_iterator = Multi30k(split='train', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE)) + sorted_train_iterator = sorted(train_iterator, key=lambda x: len(x[0].split())) + # Update text_transform based on the flip parameter + if flip: + text_transform[SRC_LANGUAGE] = sequential_transforms(token_transform[SRC_LANGUAGE], vocab_transform[SRC_LANGUAGE], tensor_transform_s) + else: + text_transform[SRC_LANGUAGE] = sequential_transforms(token_transform[SRC_LANGUAGE], vocab_transform[SRC_LANGUAGE], tensor_transform_t) + text_transform[TGT_LANGUAGE] = sequential_transforms(token_transform[TGT_LANGUAGE], vocab_transform[TGT_LANGUAGE], tensor_transform_t) + + train_dataloader = DataLoader(sorted_train_iterator, batch_size=batch_size, collate_fn=collate_fn, drop_last=True) + + valid_iterator = Multi30k(split='valid', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE)) + sorted_valid_dataloader = sorted(valid_iterator, key=lambda x: len(x[0].split())) + valid_dataloader = DataLoader(sorted_valid_dataloader, batch_size=batch_size, collate_fn=collate_fn, drop_last=True) + + return train_dataloader, valid_dataloader + +def index_to_eng(seq_en): + return " ".join([vocab_transform['en'].get_itos()[index.item()] for index in seq_en]) + +def index_to_german(seq_de): + return " ".join([vocab_transform['de'].get_itos()[index.item()] for index in seq_de]) diff --git a/notebooks/LLM_Specialization/input_de.pdf b/notebooks/LLM_Specialization/input_de.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a938cb363432a321b430f1f62f568bd663958102 GIT binary patch literal 27628 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 literal 0 HcmV?d00001 diff --git a/notebooks/LLM_Specialization/train_bert_data_new.csv b/notebooks/LLM_Specialization/train_bert_data_new.csv new file mode 100644 index 0000000..6d246e2 --- /dev/null +++ b/notebooks/LLM_Specialization/train_bert_data_new.csv @@ -0,0 +1 @@ +Original Text,BERT Input,BERT Label,Segment Label,Is Next