{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":52950,"databundleVersionId":5973250,"sourceType":"competition"},{"sourceId":7111805,"sourceType":"datasetVersion","datasetId":4100768}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <span style=\"color:darkred; font-weight:bold; font-size:48px;\">Translating Silence: <i>Transformer</i>-Powered American Sign Language Fingerspelling Recognition</span>\n\n\n","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://www.mudpuppy.com/cdn/shop/products/american-sign-language-alphabet-500-piece-family-puzzle-9780735374775-190555_720x.jpg?v=1666027882\" alt=\"ASL Image\">\n</div>","metadata":{}},{"cell_type":"markdown","source":"# **<span style=\"color:darkblue\">1.  Introduction**\n\nEffective communication is the cornerstone of human interaction. In an effort to enhance accessibility and inclusivity, this project focuses on the development of a robust system for detecting and translating **<span style=\"color:darkred\"> American Sign Language (ASL) fingerspelling into text**. The dataset used in this project consists of more than **three million** fingerspelled characters, generously contributed by over 100 Deaf signers. Captured through smartphone selfie cameras, the dataset offers a diverse range of backgrounds and lighting conditions, adding real-world complexity to the academic exploration.\n","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">1.1. Dataset Overview**\n\nThe dataset is a valuable resource for academic research and experimentation:\n\n- **Metadata File [train/supplemental_metadata.csv]:**\n  - **path:** The path to the landmark file.\n  - **file_id:** A unique identifier for the data file.\n  - **participant_id:** A unique identifier for the data contributor.\n  - **sequence_id:** A unique identifier for the landmark sequence.\n  - **phrase:** Labels for the landmark sequence, randomly generated for privacy and ethical considerations.\n\n\n- **Landmark Data [train/supplemental_landmarks/]:**\n  - Extracted from raw videos using the MediaPipe holistic model.\n  - Reshaped into a wide format for efficiency.\n  - Contains spatial coordinates (x, y, z) for 543 landmarks, including face, left hand, pose, and right hand.\n  - Approximately 1,000 sequences with sequence ID as the dataframe index.","metadata":{}},{"cell_type":"markdown","source":"Landmark data is a crucial aspect of this project, providing detailed spatial information for various key points, such as face, left hand, pose, and right hand. The figure below illustrates the concept of landmark data:\n\n<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://developers.google.com/static/mediapipe/images/solutions/examples/hand_landmark.png\" alt=\"LANDMARK MEDIAPIPE\">\n</div>\n\nIn the landmark data, spatial coordinates (x, y, z) are provided for each of the 543 landmarks. It plays a pivotal role in understanding the intricate movements and positions associated with ASL fingerspelling.","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">1.2. Project Objective**\n\n* **Objective** : Create a transformer-based ASL Language to text application \n\n*Note: The dataset has been anonymized, and privacy measures have been implemented to ensure ethical use within an academic framework.*","metadata":{}},{"cell_type":"markdown","source":"# **<span style=\"color:darkblue\">2.  Setup & Dependencies**\n\nBefore diving into the implementation, let's ensure the notebook environment is properly set up. ","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">2.1. Mediapipe**\n\n[**Mediapipe**](https://developers.google.com/mediapipe) is an open-source library developed by Google that offers a comprehensive set of pre-built solutions for various computer vision tasks. It simplifies the implementation of complex pipelines for tasks such as hand tracking, pose estimation, face detection, and more.","metadata":{}},{"cell_type":"code","source":"!pip install mediapipe","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:13.332469Z","iopub.execute_input":"2023-12-21T21:47:13.33315Z","iopub.status.idle":"2023-12-21T21:47:29.834982Z","shell.execute_reply.started":"2023-12-21T21:47:13.333117Z","shell.execute_reply":"2023-12-21T21:47:29.833699Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">2.2. Libraries** ","metadata":{}},{"cell_type":"code","source":"import os\nimport shutil\nimport numpy as np\nimport pandas as pd\nimport pyarrow.parquet as pq\nimport tensorflow as tf\nimport json\nimport mediapipe\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport random\n\nfrom skimage.transform import resize\nfrom mediapipe.framework.formats import landmark_pb2\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tqdm.notebook import tqdm\nfrom matplotlib import animation, rc","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:29.837609Z","iopub.execute_input":"2023-12-21T21:47:29.838402Z","iopub.status.idle":"2023-12-21T21:47:43.844879Z","shell.execute_reply.started":"2023-12-21T21:47:29.838362Z","shell.execute_reply":"2023-12-21T21:47:43.843843Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">2.3. Versions**","metadata":{}},{"cell_type":"code","source":"print(\"TensorFlow v\" + tf.__version__)\nprint(\"Mediapipe v\" + mediapipe.__version__)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:43.846222Z","iopub.execute_input":"2023-12-21T21:47:43.846793Z","iopub.status.idle":"2023-12-21T21:47:43.852562Z","shell.execute_reply.started":"2023-12-21T21:47:43.846763Z","shell.execute_reply":"2023-12-21T21:47:43.851458Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **<span style=\"color:darkblue\">3. Data Loading & Exploration**","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">3.1. Loading the data**","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/asl-fingerspelling/train.csv')\nprint(\"Shape of Train Set is :\" , df.shape)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:43.854134Z","iopub.execute_input":"2023-12-21T21:47:43.854727Z","iopub.status.idle":"2023-12-21T21:47:44.048364Z","shell.execute_reply.started":"2023-12-21T21:47:43.854692Z","shell.execute_reply":"2023-12-21T21:47:44.047311Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:44.051217Z","iopub.execute_input":"2023-12-21T21:47:44.051539Z","iopub.status.idle":"2023-12-21T21:47:44.073788Z","shell.execute_reply.started":"2023-12-21T21:47:44.051511Z","shell.execute_reply":"2023-12-21T21:47:44.072929Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Here's a breakdown of the example data:\n\n* `path`: The path to the Parquet file containing landmarks data for a specific sequence.\n\n* `file_id`: A unique identifier for the data file.\n\n* `sequence_id`: A unique identifier for the landmark sequence.\n\n* `participant_id`: A unique identifier for the data contributor.\n\n* `phrase`: The labels for the landmark sequence, which may represent phrases or words associated with the sign language gestures.\n\n\nA **Parquet** file is a columnar storage file format optimized for use with big data processing frameworks.","metadata":{}},{"cell_type":"markdown","source":"Each entry in **train.csv** contains a `phrase`, its `sequence_id`, `path` and `file_id`. The `file_id` indicates the file that holds the **_landmarks_** data for that particular `phrase` and `sequence_id` is the unique index of the landmark sequence within the landmarks data file.\n\nThe following diagram shows an example of how files are connected: \n\n![ASL Data](https://storage.googleapis.com/kagglesdsdata/datasets/4100768/7111805/asl_data.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=databundle-worker-v2%40kaggle-161607.iam.gserviceaccount.com%2F20231220%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20231220T163433Z&X-Goog-Expires=345600&X-Goog-SignedHeaders=host&X-Goog-Signature=38921595ecd416daedb2d58a94ef3e752de193ea3564e5d893b7090fe9f41b755b6b71a5baf2cf921f4ab6e2d91f3027674f1733514c1e8b066d79de0b24c45858f2fd60f70bac57db2fe63f68d843bfc12291bbfd4d3513b068ec94acb91163b158ea93719c02f5a14c1476292b745af5e2f9df2df214ff0ec67d5eca251c414dc8c7377476684d21d3d45b6cd5ed4d2416cd1e3b124f1395081b9ee11f718aebb655c782f38aeff915af44458eca71fb3151add8273436999aa86462bfd9e4d0c4429272eacb28466e58736602bc7d8f7cfb8c7ae9ed18371814d1c70bfb4ba75ff3c53cbc502ea49bb611438eefe030783670e76b05f6f3f667722c0228c3)\n","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">3.2. Exploring a sample of the Dataset**\n\nAs an example, let us examine the landmarks file for the first row of`train.csv` :","metadata":{}},{"cell_type":"code","source":"sequence_id, file_id, phrase = df.iloc[177][['sequence_id', 'file_id', 'phrase']]\nprint(f\"sequence_id: {sequence_id}, file_id: {file_id}, phrase: {phrase}\")","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:44.074993Z","iopub.execute_input":"2023-12-21T21:47:44.075311Z","iopub.status.idle":"2023-12-21T21:47:44.089483Z","shell.execute_reply.started":"2023-12-21T21:47:44.075284Z","shell.execute_reply":"2023-12-21T21:47:44.088512Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now, let us open the parquet file and fetch the data for the particular `sequence_id` :","metadata":{}},{"cell_type":"code","source":"sample = pq.read_table(f\"/kaggle/input/asl-fingerspelling/train_landmarks/{str(file_id)}.parquet\",\n                       filters=[[('sequence_id', '=', sequence_id)],]).to_pandas()\n\nprint(\"Full sequence dataset shape is :\", sample.shape)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:44.090675Z","iopub.execute_input":"2023-12-21T21:47:44.091057Z","iopub.status.idle":"2023-12-21T21:47:47.565632Z","shell.execute_reply.started":"2023-12-21T21:47:44.091031Z","shell.execute_reply":"2023-12-21T21:47:47.564634Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This particular phrase(***3 creekhouse***) contains 123 entries (or frames) and 1630 columns including 1628 landmark coordinates. Let us print the first 5 entries.","metadata":{}},{"cell_type":"code","source":"sample.head()","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:47.566938Z","iopub.execute_input":"2023-12-21T21:47:47.56722Z","iopub.status.idle":"2023-12-21T21:47:47.595119Z","shell.execute_reply.started":"2023-12-21T21:47:47.567196Z","shell.execute_reply":"2023-12-21T21:47:47.594171Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from ipywidgets import interact\n\n@interact\ndef show_columns(column_start=(0, len(sample.columns), 100), column_end=(100, len(sample.columns), 100)):\n    print(sample.columns[column_start:column_end])\n    \n\n# Frame 1 \n# Face 0-467 (468) x3\n# Left Hand 0-20 (21) x3\n# Pose 0-32 (33) x3\n# Right Hand 0-20 (21) x3","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:47.596521Z","iopub.execute_input":"2023-12-21T21:47:47.59729Z","iopub.status.idle":"2023-12-21T21:47:47.630651Z","shell.execute_reply.started":"2023-12-21T21:47:47.597252Z","shell.execute_reply":"2023-12-21T21:47:47.629677Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">3.3. Data Visualisation using Mediapipe**","metadata":{}},{"cell_type":"markdown","source":"Let us visualize the hand landmarks data for the phrase ***3 creek house*** using the [Hand landmarker APIs](https://developers.google.com/mediapipe/solutions/vision/hand_landmarker) of the [Mediapipe Library](https://developers.google.com/mediapipe).\n\nHand landmarks represent the key points on a human hand.","metadata":{}},{"cell_type":"code","source":"# Function create animation from images.\n\nmatplotlib.rcParams['animation.embed_limit'] = 2**128 #Enables larger embedded animation \nmatplotlib.rcParams['savefig.pad_inches'] = 0 #No extra padding added\nrc('animation', html='jshtml') #Animation through jupyter notebook \n\ndef create_animation(images):\n    fig = plt.figure(figsize=(6, 9))\n    ax = plt.Axes(fig, [0., 0., 1., 1.])\n    ax.set_axis_off()\n    fig.add_axes(ax)\n    im=ax.imshow(images[0], cmap=\"gray\")\n    plt.close(fig)\n    \n    #This function updates the image displayed on the axes for each animation frame. It is used by animation.FuncAnimation to create the animation.\n    def animate_func(i):\n        im.set_array(images[i])\n        return [im]\n    \n    #The FuncAnimation class is used to create the animation. It takes the figure (fig), the animation function (animate_func), the number of frames (len(images)), and the interval between frames (1000/10 milliseconds, or 10 frames per second).\n    return animation.FuncAnimation(fig, animate_func, frames=len(images), interval=1000/10)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:47.63211Z","iopub.execute_input":"2023-12-21T21:47:47.632679Z","iopub.status.idle":"2023-12-21T21:47:47.643536Z","shell.execute_reply.started":"2023-12-21T21:47:47.632644Z","shell.execute_reply":"2023-12-21T21:47:47.642605Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This image illustrates the 21 keypoints on the hand : \n\n<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://developers.google.com/static/mediapipe/images/solutions/hand-landmarks.png\" alt=\"LANDMARK\">\n</div>","metadata":{}},{"cell_type":"code","source":"# Extract the landmark data and convert it to an image using medipipe library.\n# This function extracts the data for both hands.\n\n#Are used for hand landmark detection\nmp_pose = mediapipe.solutions.pose\nmp_hands = mediapipe.solutions.hands\n\n#Are used for visualizing landmarks on images\nmp_drawing = mediapipe.solutions.drawing_utils \nmp_drawing_styles = mediapipe.solutions.drawing_styles\n\ndef get_hands(seq_df):\n    images = []\n    all_hand_landmarks = []\n    for seq_idx in range(len(seq_df)):\n        \n        # Extract x, y, z coordinates for the right hand\n        x_hand = seq_df.iloc[seq_idx].filter(regex=\"x_right_hand.*\").values\n        y_hand = seq_df.iloc[seq_idx].filter(regex=\"y_right_hand.*\").values\n        z_hand = seq_df.iloc[seq_idx].filter(regex=\"z_right_hand.*\").values\n\n        # Create an empty image for the right hand\n        right_hand_image = np.zeros((600, 600, 3))\n\n        # Create NormalizedLandmarkList for the right hand\n        right_hand_landmarks = landmark_pb2.NormalizedLandmarkList()\n        \n        # Populate right_hand_landmarks with x, y, z coordinates\n        for x, y, z in zip(x_hand, y_hand, z_hand):\n            right_hand_landmarks.landmark.add(x=x, y=y, z=z)\n\n        # Draw hand landmarks on the right_hand_image\n        mp_drawing.draw_landmarks(\n                right_hand_image,\n                right_hand_landmarks,\n                mp_hands.HAND_CONNECTIONS,\n                landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())\n        \n        x_hand = seq_df.iloc[seq_idx].filter(regex=\"x_left_hand.*\").values\n        y_hand = seq_df.iloc[seq_idx].filter(regex=\"y_left_hand.*\").values\n        z_hand = seq_df.iloc[seq_idx].filter(regex=\"z_left_hand.*\").values\n        \n        left_hand_image = np.zeros((600, 600, 3))\n        \n        left_hand_landmarks = landmark_pb2.NormalizedLandmarkList()\n        for x, y, z in zip(x_hand, y_hand, z_hand):\n            left_hand_landmarks.landmark.add(x=x, y=y, z=z)\n\n        mp_drawing.draw_landmarks(\n                left_hand_image,\n                left_hand_landmarks,\n                mp_hands.HAND_CONNECTIONS,\n                landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())\n        \n        # Append right and left hand images to the images list\n        images.append([right_hand_image.astype(np.uint8), left_hand_image.astype(np.uint8)])\n        \n         # Append right and left hand landmarks to the all_hand_landmarks list\n        all_hand_landmarks.append([right_hand_landmarks, left_hand_landmarks])\n        \n    return images, all_hand_landmarks","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:47.645256Z","iopub.execute_input":"2023-12-21T21:47:47.645614Z","iopub.status.idle":"2023-12-21T21:47:47.661429Z","shell.execute_reply.started":"2023-12-21T21:47:47.645581Z","shell.execute_reply":"2023-12-21T21:47:47.660403Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get the images created using mediapipe apis\nhand_images, hand_landmarks = get_hands(sample)\n\n# Fetch and show the data for right hand\ncreate_animation(np.array(hand_images)[:, 0])","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:47:47.662895Z","iopub.execute_input":"2023-12-21T21:47:47.663246Z","iopub.status.idle":"2023-12-21T21:48:31.81901Z","shell.execute_reply.started":"2023-12-21T21:47:47.663214Z","shell.execute_reply":"2023-12-21T21:48:31.817608Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **<span style=\"color:darkblue\">4. Data Preprocessing**\n\nFor convenience and efficiency, we will rearrange the data so that each parquet file contains the landmark data along with the phrase it represents. This way we don't have to switch between train.csv and its parquet file. \n\nWe will save the new data in the [TFRecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) format. `TFRecord` format is a simple format for storing a sequence of binary records. Storing and loading the data using `TFRecord` is much more efficient and faster.\n","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">4.1. Pose Landmark Coordinates**\n\nThis image illustrates the pose coordinates related to the whole body movement.\n\n<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://developers.google.com/static/mediapipe/images/solutions/pose_landmarks_index.png\" alt=\"POSE\">\n</div>\n\nCoordinates from **13** to **22** represents the hand movement as follows :\n* 13 - left elbow\n* 14 - right elbow\n* 15 - left wrist\n* 16 - right wrist\n* 17 - left pinky\n* 18 - right pinky\n* 19 - left index\n* 20 - right index\n* 21 - left thumb\n* 22 - right thumb","metadata":{}},{"cell_type":"code","source":"# Pose coordinates for hand movement.\nLP = [13, 15, 17, 19, 21]\nRP = [14, 16, 18, 20, 22]\nPOSE = LP + RP","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:48:31.820765Z","iopub.execute_input":"2023-12-21T21:48:31.821294Z","iopub.status.idle":"2023-12-21T21:48:31.82655Z","shell.execute_reply.started":"2023-12-21T21:48:31.821246Z","shell.execute_reply":"2023-12-21T21:48:31.825534Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Create x,y,z label names from coordinates : ","metadata":{}},{"cell_type":"code","source":"X = [f'x_right_hand_{i}' for i in range(21)] + [f'x_left_hand_{i}' for i in range(21)] + [f'x_pose_{i}' for i in POSE]\nY = [f'y_right_hand_{i}' for i in range(21)] + [f'y_left_hand_{i}' for i in range(21)] + [f'y_pose_{i}' for i in POSE]\nZ = [f'z_right_hand_{i}' for i in range(21)] + [f'z_left_hand_{i}' for i in range(21)] + [f'z_pose_{i}' for i in POSE]","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:48:31.831242Z","iopub.execute_input":"2023-12-21T21:48:31.831554Z","iopub.status.idle":"2023-12-21T21:48:31.841915Z","shell.execute_reply.started":"2023-12-21T21:48:31.831528Z","shell.execute_reply":"2023-12-21T21:48:31.840862Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Create feature columns from the extracted coordinates :","metadata":{}},{"cell_type":"code","source":"FEATURE_COLUMNS = X + Y + Z\nlen(FEATURE_COLUMNS)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:48:31.843072Z","iopub.execute_input":"2023-12-21T21:48:31.843385Z","iopub.status.idle":"2023-12-21T21:48:31.859328Z","shell.execute_reply.started":"2023-12-21T21:48:31.843357Z","shell.execute_reply":"2023-12-21T21:48:31.858225Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Store ids of each coordinate labels to lists : ","metadata":{}},{"cell_type":"code","source":"X_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if \"x_\" in col]\nY_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if \"y_\" in col]\nZ_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if \"z_\" in col]\n\nRHAND_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if \"right\" in col]\nLHAND_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if  \"left\" in col]\nRPOSE_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if  \"pose\" in col and int(col[-2:]) in RP]\nLPOSE_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if  \"pose\" in col and int(col[-2:]) in LP]\n\nlen(LPOSE_IDX)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:48:31.860464Z","iopub.execute_input":"2023-12-21T21:48:31.860755Z","iopub.status.idle":"2023-12-21T21:48:31.873656Z","shell.execute_reply.started":"2023-12-21T21:48:31.860731Z","shell.execute_reply":"2023-12-21T21:48:31.872651Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In summary, this code snippet provides an example of how to extract information from the dataset, construct a file path based on that information, read a sample sequence dataset from a Parquet file, and display information about the sample dataset. This can be useful for exploring the structure and content of the data during the preprocessing stage.","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">4.2. TFRecord Preprocessing**","metadata":{}},{"cell_type":"markdown","source":"Using the extracted landmarks and phrases let us create new dataset files and write them as TFRecords.\n\n**<span style=\"color:darkblue\">Motivation for Using TFRecords:**\n    \n* **Efficiency**: TFRecords are a binary format that can be read efficiently by TensorFlow. This format reduces I/O overhead and allows for faster data loading during training.\n    \n* **Serialization**: TFRecords enable easy serialization of complex data structures, making it convenient to store and retrieve data, especially when dealing with multiple data types.","metadata":{}},{"cell_type":"code","source":"# Set length of frames to 128\nFRAME_LEN = 128\n\n# Create directory to store the new data\nif not os.path.isdir(\"preprocessed\"):\n    os.mkdir(\"preprocessed\")\nelse:\n    shutil.rmtree(\"preprocessed\")\n    os.mkdir(\"preprocessed\")\n\n# Loop through each file_id\nfor file_id in tqdm(df.file_id.unique()):\n    # Parquet file name\n    pq_file = f\"/kaggle/input/asl-fingerspelling/train_landmarks/{file_id}.parquet\"\n    \n    # Filter train.csv and fetch entries only for the relevant file_id\n    file_df = df.loc[df[\"file_id\"] == file_id]\n    \n    # Fetch the parquet file\n    parquet_df = pq.read_table(f\"/kaggle/input/asl-fingerspelling/train_landmarks/{str(file_id)}.parquet\",\n                              columns=['sequence_id'] + FEATURE_COLUMNS).to_pandas()\n    \n    # File name for the updated data\n    tf_file = f\"preprocessed/{file_id}.tfrecord\"\n    parquet_numpy = parquet_df.to_numpy()\n    \n    # Initialize the pointer to write the output of \n    # each `for loop` below as a sequence into the file.\n    with tf.io.TFRecordWriter(tf_file) as file_writer:\n        # Loop through each sequence in file.\n        for seq_id, phrase in zip(file_df.sequence_id, file_df.phrase):\n            # Fetch sequence data\n            frames = parquet_numpy[parquet_df.index == seq_id]\n            \n            # Calculate the number of NaN values in each hand landmark\n            r_nonan = np.sum(np.sum(np.isnan(frames[:, RHAND_IDX]), axis = 1) == 0)\n            l_nonan = np.sum(np.sum(np.isnan(frames[:, LHAND_IDX]), axis = 1) == 0)\n            no_nan = max(r_nonan, l_nonan)\n            \n            if 2*len(phrase)<no_nan:\n                features = {FEATURE_COLUMNS[i]: tf.train.Feature(\n                    float_list=tf.train.FloatList(value=frames[:, i])) for i in range(len(FEATURE_COLUMNS))}\n                features[\"phrase\"] = tf.train.Feature(bytes_list=tf.train.BytesList(value=[bytes(phrase, 'utf-8')]))\n                record_bytes = tf.train.Example(features=tf.train.Features(feature=features)).SerializeToString()\n                file_writer.write(record_bytes)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:48:31.875549Z","iopub.execute_input":"2023-12-21T21:48:31.87606Z","iopub.status.idle":"2023-12-21T21:59:44.496965Z","shell.execute_reply.started":"2023-12-21T21:48:31.876021Z","shell.execute_reply":"2023-12-21T21:59:44.49589Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* This code essentially preprocesses the landmark data and creates TFRecord files for each file_id, ensuring that the sequences are long enough for the specified frame length. The 'phrase' label is also included in the TFRecord features.\n* TFRecord is a binary format used by TensorFlow to efficiently store and read large amounts of data. It is a simple and flexible format that is commonly used for storing training datasets. TFRecord files are especially useful when working with large datasets that may not fit into memory, as they can be efficiently read by TensorFlow during training.\n","metadata":{}},{"cell_type":"markdown","source":"**Overall Explanation:**\n\nThis code processes each sequence, checks if it meets a certain condition based on the length of the phrase and the number of non-NaN values, and then creates a TFRecord file containing the hand landmarks and the associated phrase. This TFRecord file can be efficiently used during model training with TensorFlow. The motivation for using TFRecords lies in their efficiency and compatibility with TensorFlow workflows.","metadata":{}},{"cell_type":"markdown","source":"**A. Get the saved TFRecord files into a list**","metadata":{}},{"cell_type":"code","source":"tf_records = df.file_id.map(lambda x: f'/kaggle/working/preprocessed/{x}.tfrecord').unique()\nprint(f\"List of {len(tf_records)} TFRecord files.\")","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:44.498726Z","iopub.execute_input":"2023-12-21T21:59:44.499167Z","iopub.status.idle":"2023-12-21T21:59:44.560581Z","shell.execute_reply.started":"2023-12-21T21:59:44.499126Z","shell.execute_reply":"2023-12-21T21:59:44.559471Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In summary, this code generates a list of unique TFRecord file paths based on the `file_id` column in the DataFrame. The resulting list, tf_records, contains the paths to the TFRecord files that can be used in further processing or analysis.","metadata":{}},{"cell_type":"markdown","source":"**B. Load character_to_prediction json file**","metadata":{}},{"cell_type":"markdown","source":"This json file contains a character and its value. We will add three new characters, \"<\" and \">\" to mark the start and end of each phrase, and \"P\" for padding.","metadata":{}},{"cell_type":"code","source":"with open (\"/kaggle/input/asl-fingerspelling/character_to_prediction_index.json\", \"r\") as f:\n    char_to_num = json.load(f)\n\n# Add pad_token, start pointer and end pointer to the dict\npad_token = 'P'\nstart_token = '<'\nend_token = '>'\npad_token_idx = 59\nstart_token_idx = 60\nend_token_idx = 61\n\nchar_to_num[pad_token] = pad_token_idx\nchar_to_num[start_token] = start_token_idx\nchar_to_num[end_token] = end_token_idx\nnum_to_char = {j:i for i,j in char_to_num.items()}","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:44.561678Z","iopub.execute_input":"2023-12-21T21:59:44.561987Z","iopub.status.idle":"2023-12-21T21:59:44.573948Z","shell.execute_reply.started":"2023-12-21T21:59:44.561961Z","shell.execute_reply":"2023-12-21T21:59:44.573073Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"=> **Purpose**: Reads a JSON file containing a character-to-index mapping and adds special tokens for padding, start, and end of phrases to the character mapping.","metadata":{}},{"cell_type":"code","source":"# Function to resize and add padding.\ndef resize_pad(x):\n    # Check if the length of x is less than FRAME_LEN\n    if tf.shape(x)[0] < FRAME_LEN:\n        # Pad the tensor with zeros to make its length FRAME_LEN\n        x = tf.pad(x, ([[0, FRAME_LEN-tf.shape(x)[0]], [0, 0], [0, 0]]))\n    else:\n        # Resize the tensor to have length FRAME_LEN\n        x = tf.image.resize(x, (FRAME_LEN, tf.shape(x)[1]))\n    return x\n\n# Detect the dominant hand from the number of NaN values.\n# Dominant hand will have fewer NaN values since it is in frame moving.\ndef pre_process(x):\n    # Extract hand and pose coordinates for both right and left hands\n    rhand = tf.gather(x, RHAND_IDX, axis=1)\n    lhand = tf.gather(x, LHAND_IDX, axis=1)\n    rpose = tf.gather(x, RPOSE_IDX, axis=1)\n    lpose = tf.gather(x, LPOSE_IDX, axis=1)\n    \n    # Identify NaN values in each hand\n    rnan_idx = tf.reduce_any(tf.math.is_nan(rhand), axis=1)\n    lnan_idx = tf.reduce_any(tf.math.is_nan(lhand), axis=1)\n    \n    # Count the number of NaN values\n    rnans = tf.math.count_nonzero(rnan_idx)\n    lnans = tf.math.count_nonzero(lnan_idx)\n    \n    # Determine the dominant hand based on NaN values\n    if rnans > lnans:\n        hand = lhand\n        pose = lpose\n        \n        # Flip hand coordinates for left hand\n        hand_x = hand[:, 0*(len(LHAND_IDX)//3) : 1*(len(LHAND_IDX)//3)]\n        hand_y = hand[:, 1*(len(LHAND_IDX)//3) : 2*(len(LHAND_IDX)//3)]\n        hand_z = hand[:, 2*(len(LHAND_IDX)//3) : 3*(len(LHAND_IDX)//3)]\n        hand = tf.concat([1-hand_x, hand_y, hand_z], axis=1)\n        \n        pose_x = pose[:, 0*(len(LPOSE_IDX)//3) : 1*(len(LPOSE_IDX)//3)]\n        pose_y = pose[:, 1*(len(LPOSE_IDX)//3) : 2*(len(LPOSE_IDX)//3)]\n        pose_z = pose[:, 2*(len(LPOSE_IDX)//3) : 3*(len(LPOSE_IDX)//3)]\n        pose = tf.concat([1-pose_x, pose_y, pose_z], axis=1)\n    else:\n        hand = rhand\n        pose = rpose\n    \n   # Extract coordinates for the dominant hand\n    hand_x = hand[:, 0*(len(LHAND_IDX)//3) : 1*(len(LHAND_IDX)//3)]\n    hand_y = hand[:, 1*(len(LHAND_IDX)//3) : 2*(len(LHAND_IDX)//3)]\n    hand_z = hand[:, 2*(len(LHAND_IDX)//3) : 3*(len(LHAND_IDX)//3)]\n    hand = tf.concat([hand_x[..., tf.newaxis], hand_y[..., tf.newaxis], hand_z[..., tf.newaxis]], axis=-1)\n    \n    # Standardize the hand coordinates\n    mean = tf.math.reduce_mean(hand, axis=1)[:, tf.newaxis, :]\n    std = tf.math.reduce_std(hand, axis=1)[:, tf.newaxis, :]\n    hand = (hand - mean) / std\n\n    # Extract coordinates for the dominant hand's pose\n    pose_x = pose[:, 0*(len(LPOSE_IDX)//3) : 1*(len(LPOSE_IDX)//3)]\n    pose_y = pose[:, 1*(len(LPOSE_IDX)//3) : 2*(len(LPOSE_IDX)//3)]\n    pose_z = pose[:, 2*(len(LPOSE_IDX)//3) : 3*(len(LPOSE_IDX)//3)]\n    pose = tf.concat([pose_x[..., tf.newaxis], pose_y[..., tf.newaxis], pose_z[..., tf.newaxis]], axis=-1)\n    \n    # Concatenate standardized hand and pose coordinates\n    x = tf.concat([hand, pose], axis=1)\n    \n    # Resize and pad the tensor\n    x = resize_pad(x)\n    \n    # Replace NaN values with zeros\n    x = tf.where(tf.math.is_nan(x), tf.zeros_like(x), x)\n    \n    # Reshape the tensor to final dimensions\n    x = tf.reshape(x, (FRAME_LEN, len(LHAND_IDX) + len(LPOSE_IDX)))\n    \n    return x ","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:44.575256Z","iopub.execute_input":"2023-12-21T21:59:44.575632Z","iopub.status.idle":"2023-12-21T21:59:44.599457Z","shell.execute_reply.started":"2023-12-21T21:59:44.575599Z","shell.execute_reply":"2023-12-21T21:59:44.598587Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**<span style=\"color:darkblue\">Why It's Done in the Context of the Project ?**\n\n* **Normalization**: Normalizing coordinates helps in achieving consistent scales, making it easier for the model to learn patterns effectively.\n* **Handling Missing Values**: NaN values are handled appropriately to avoid introducing noise into the model.\n* **Resizing and Padding**: Ensures that all input sequences have a consistent length, which is crucial for model training.\n* **Special Tokens**: The introduction of special tokens enhances the model's understanding of the beginning, end, and padding of phrases during training.\n\nIn summary, this preprocessing code ensures that the input data is in a suitable format for training the ASL fingerspelling model. It addresses issues such as inconsistent scales, missing values, and sequence length variations, contributing to the overall robustness and effectiveness of the machine learning model.\n    \nChara","metadata":{}},{"cell_type":"markdown","source":"**C. Create function to parse data from TFRecord format**","metadata":{}},{"cell_type":"code","source":"def decode_fn(record_bytes):\n    # Define the schema for decoding TFRecord data\n    schema = {COL: tf.io.VarLenFeature(dtype=tf.float32) for COL in FEATURE_COLUMNS}\n    schema[\"phrase\"] = tf.io.FixedLenFeature([], dtype=tf.string)\n    \n    # Parse a single example from the TFRecord\n    features = tf.io.parse_single_example(record_bytes, schema)\n    \n    # Extract the phrase information\n    phrase = features[\"phrase\"]\n    \n    # Extract landmarks data for each frame\n    landmarks = ([tf.sparse.to_dense(features[COL]) for COL in FEATURE_COLUMNS])\n    \n    # Transpose to maintain the original shape of landmarks data\n    landmarks = tf.transpose(landmarks)\n    \n    return landmarks, phrase","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:44.600679Z","iopub.execute_input":"2023-12-21T21:59:44.601028Z","iopub.status.idle":"2023-12-21T21:59:44.612709Z","shell.execute_reply.started":"2023-12-21T21:59:44.601003Z","shell.execute_reply":"2023-12-21T21:59:44.611757Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* The purpose of this function is to decode the binary data stored in TFRecord format into tensors that can be used for training the model. It ensures that the data is correctly parsed, and the landmarks and phrases are extracted in a format suitable for further processing in the deep learning model. The decoding function is a crucial step in the data pipeline, allowing the model to consume and understand the input data during training.","metadata":{}},{"cell_type":"markdown","source":"**D. Create function to convert the data**\n\nThis function transposes and applies masks to the landmark coordinates. It also vectorizes the phrase corresponding to the landmarks using `character_to_prediction_index.json`.","metadata":{}},{"cell_type":"code","source":"# Create a StaticHashTable for mapping characters to their corresponding indices\ntable = tf.lookup.StaticHashTable(\n    initializer=tf.lookup.KeyValueTensorInitializer(\n        keys=list(char_to_num.keys()),\n        values=list(char_to_num.values()),\n    ),\n    default_value=tf.constant(-1),\n    name=\"class_weight\"\n)\n\n# Define the convert function\ndef convert_fn(landmarks, phrase):\n    # Add start and end pointers to the phrase\n    phrase = start_token + phrase + end_token\n    # Split the phrase into individual characters and map to their indices using the StaticHashTable\n    phrase = tf.strings.bytes_split(phrase)\n    phrase = table.lookup(phrase)\n    \n    # Vectorize the phrase and add padding to match the desired sequence length\n    phrase = tf.pad(phrase, paddings=[[0, 64 - tf.shape(phrase)[0]]], mode='CONSTANT', constant_values=pad_token_idx)\n    \n    # Apply the pre_process function to the landmarks\n    return pre_process(landmarks), phrase","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:44.614048Z","iopub.execute_input":"2023-12-21T21:59:44.614696Z","iopub.status.idle":"2023-12-21T21:59:47.575929Z","shell.execute_reply.started":"2023-12-21T21:59:44.61466Z","shell.execute_reply":"2023-12-21T21:59:47.575006Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* In summary, this conversion function prepares the data for training by adding start and end tokens to the phrases, mapping characters to numeric indices, vectorizing, and padding the phrases. It also applies the pre_process function to the landmarks data. This function is often used as part of the data input pipeline during the training process.","metadata":{}},{"cell_type":"markdown","source":"**<span style=\"color:darkblue\">TFRecords Preprocessing Summary:**\n    \n* **TFRecord Creation**: TFRecord files are created for each sequence, storing landmarks and phrases together. This is done to improve data loading efficiency.\n\n* **Data Decoding**: TFRecord data is decoded using the decode_fn function, extracting landmarks and phrases in tensor format.\n\n* **Preprocessing of Landmarks**: Landmarks are pre-processed to handle missing values, normalize data, and ensure consistent shape.\n\n* **Conversion of Data**: The `convert_fn` function is applied to convert landmarks and phrases. The phrase is vectorized using character-to-index mapping, and padding is added.\n\nThese preprocessing steps are essential for preparing the data for training the model. They handle data cleaning, normalization, and formatting, ensuring that the input to the model is in a suitable format for effective learning","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">4.3. Train and Validation Split**","metadata":{}},{"cell_type":"code","source":"batch_size = 64\ntrain_len = int(0.8 * len(tf_records))\n\n# Training dataset\ntrain_ds = (\n    tf.data.TFRecordDataset(tf_records[:train_len])  # Selecting training TFRecord files\n    .map(decode_fn)  # Decoding TFRecord bytes into tensors (landmarks, phrase)\n    .map(convert_fn)  # Applying preprocessing to get input and label tensors\n    .batch(batch_size)  # Grouping samples into batches\n    .prefetch(buffer_size=tf.data.AUTOTUNE)  # Prefetching data for efficiency\n    .cache()  # Caching the dataset for faster access during training\n)\n\n# Validation dataset\nvalid_ds = (\n    tf.data.TFRecordDataset(tf_records[train_len:])  # Selecting validation TFRecord files\n    .map(decode_fn)  # Decoding TFRecord bytes into tensors (landmarks, phrase)\n    .map(convert_fn)  # Applying preprocessing to get input and label tensors\n    .batch(batch_size)  # Grouping samples into batches\n    .prefetch(buffer_size=tf.data.AUTOTUNE)  # Prefetching data for efficiency\n    .cache()  # Caching the dataset for faster access during validation\n)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:47.57734Z","iopub.execute_input":"2023-12-21T21:59:47.577738Z","iopub.status.idle":"2023-12-21T21:59:49.38887Z","shell.execute_reply.started":"2023-12-21T21:59:47.577703Z","shell.execute_reply":"2023-12-21T21:59:49.387994Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for landmarks, phrase in train_ds.take(1):  # Taking one batch for example\n    # Example of input tensor\n    print(\"Training Input Tensor (Landmarks):\")\n    print(landmarks.numpy())\n\n    # Example of label tensor\n    print(\"\\nTraining Label Tensor (Phrase):\")\n    print(phrase.numpy())","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:49.390362Z","iopub.execute_input":"2023-12-21T21:59:49.390744Z","iopub.status.idle":"2023-12-21T21:59:49.859252Z","shell.execute_reply.started":"2023-12-21T21:59:49.390705Z","shell.execute_reply":"2023-12-21T21:59:49.858303Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for landmarks, phrase in train_ds.take(1):  # Taking one batch for example\n    # Print the shape of the input tensor\n    print(\"Training Input Tensor (Landmarks) Shape:\", landmarks.shape)\n\n    # Print the shape of the label tensor\n    print(\"Training Label Tensor (Phrase) Shape:\", phrase.shape)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:49.860585Z","iopub.execute_input":"2023-12-21T21:59:49.860901Z","iopub.status.idle":"2023-12-21T21:59:50.214047Z","shell.execute_reply.started":"2023-12-21T21:59:49.860876Z","shell.execute_reply":"2023-12-21T21:59:50.213078Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The code snippet is using the take(1) method to extract one batch from the training dataset (train_ds). Then, it prints the shape of the input tensor (landmarks) and the label tensor (phrase) for that batch.\n\n* **Training Input Tensor (Landmarks) Shape: `(64, 128, 78)`**\nThis indicates that the input tensor has a shape of `(batch_size, sequence_length, feature_dimension)`. In this case, we have a batch size of 64, each sequence (or sample) has a length of 128 frames, and there are 78 features (landmark coordinates) per frame.\n\n* **Training Label Tensor (Phrase) Shape: `(64, 64)`**\nThis indicates that the label tensor has a shape of `(batch_size, sequence_length)`. In this case, we have a batch size of 64, and each sequence (or label) has a length of 64. The labels represent the phrases or gestures associated with the corresponding landmarks.\n\n\nSo, in summary, each batch consists of 64 samples. Each sample includes a sequence of 128 frames, where each frame has 78 landmark coordinates. The associated labels are sequences of length 64, representing the phrases or gestures corresponding to the landmark sequences. There is a total of **632** batches. \n\nThese datasets (train_ds and valid_ds) are now ready to be used for training and validation in a machine learning model. The batching, prefetching, and caching steps are common practices to optimize the efficiency of the input pipeline during training.","metadata":{}},{"cell_type":"markdown","source":"# **<span style=\"color:darkblue\">5.  Model Creation - <i>Transformer</i>**\n    \nWe will use a **[Transformer](https://en.wikipedia.org/wiki/Transformer_(machine_learning_model))** to train a model for this dataset. **Transformers** are designed to process sequential input data. The model we are going to design is similar to the one used in the [Automatic Speech Recognition with Transformer](https://keras.io/examples/audio/transformer_asr/) tutorial for **Keras**. We will finetune only a small part of the model since we can treat the ASL Fingerspelling recognition problem similar to speech recognition. In both cases, we have to predict a sentence from a sequence of data.\n\n<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"data:image/gif;base64,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\" alt=\"TRAN\">\n</div>","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">5.1. Embedding & Positioning**","metadata":{}},{"cell_type":"markdown","source":"### **<span style=\"color:darkblue\">5.1.1. Token Embedding**","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://i.ibb.co/6WCXKYn/Token-Embedding.png\" alt=\"tokemb\">\n</div>","metadata":{}},{"cell_type":"code","source":"class TokenEmbedding(layers.Layer):\n    def __init__(self, num_vocab=1000, maxlen=100, num_hid=64):\n        super().__init__()\n        # Token Embedding Layer\n        self.emb = tf.keras.layers.Embedding(num_vocab, num_hid)\n        # Positional Embedding Layer\n        self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=num_hid)\n\n    def call(self, x):\n        # Get the maximum sequence length dynamically\n        maxlen = tf.shape(x)[-1]\n        # Token Embedding: Convert token indices to dense vectors\n        x = self.emb(x)\n        # Generate positional embeddings for each position in the sequence\n        positions = tf.range(start=0, limit=maxlen, delta=1)\n        positions = self.pos_emb(positions)\n        return x + positions","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:50.21556Z","iopub.execute_input":"2023-12-21T21:59:50.216353Z","iopub.status.idle":"2023-12-21T21:59:50.224136Z","shell.execute_reply.started":"2023-12-21T21:59:50.216321Z","shell.execute_reply":"2023-12-21T21:59:50.223133Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The `TokenEmbedding` block consists of two main components: the Token Embedding Layer and the Positional Embedding Layer. It is a fundamental part of transformer architectures, addressing the challenge of processing sequential data.","metadata":{}},{"cell_type":"markdown","source":"### **<span style=\"color:darkblue\">5.1.2. Landmark Embedding**","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://i.ibb.co/2yTrMph/Landmark-Embedding.png\" alt=\"landemb\">\n</div>","metadata":{}},{"cell_type":"code","source":"class LandmarkEmbedding(layers.Layer):\n    def __init__(self, num_hid=64, maxlen=100):\n        super().__init__()\n        # Convolutional layers with activation function\n        self.conv1 = tf.keras.layers.Conv1D(\n            num_hid, 11, strides=2, padding=\"same\", activation=\"relu\"\n        )\n        self.conv2 = tf.keras.layers.Conv1D(\n            num_hid, 11, strides=2, padding=\"same\", activation=\"relu\"\n        )\n        self.conv3 = tf.keras.layers.Conv1D(\n            num_hid, 11, strides=2, padding=\"same\", activation=\"relu\"\n        )\n        # Positional Embedding Layer\n        self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=num_hid)\n\n    def call(self, x):\n        # Apply convolutional layers\n        x = self.conv1(x)\n        x = self.conv2(x)\n        return self.conv3(x)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:50.225265Z","iopub.execute_input":"2023-12-21T21:59:50.225529Z","iopub.status.idle":"2023-12-21T21:59:50.235928Z","shell.execute_reply.started":"2023-12-21T21:59:50.225505Z","shell.execute_reply":"2023-12-21T21:59:50.235093Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* This block applies a series of 1D convolutions to the input landmark coordinates, creating a processed representation that captures relevant features and relationships in the data.\n* In the context of sequential data, like time series or landmark coordinates, Conv1D layers are suitable because they consider the sequential nature of the data. Conv1D filters move along the sequence, capturing local patterns and dependencies. This is crucial for tasks like gesture recognition or any application where the order of data points matters.","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">5.2. Encoder & Decoder**","metadata":{}},{"cell_type":"markdown","source":"### **<span style=\"color:darkblue\">5.2.1. <i>Transformer</i> Encoder**","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://i.ibb.co/QHNvGh6/Encoder.png\" alt=\"encod\">\n</div>","metadata":{}},{"cell_type":"code","source":"class TransformerEncoder(layers.Layer):\n    def __init__(self, embed_dim, num_heads, feed_forward_dim, rate=0.1):\n        super().__init__()\n        \n        # Multi-Head Self-Attention Layer\n        self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)\n        \n        # Feedforward Neural Network\n        self.ffn = keras.Sequential(\n            [\n                layers.Dense(feed_forward_dim, activation=\"relu\"),\n                layers.Dense(embed_dim),\n            ]\n        )\n        \n        # Layer Normalization for the Self-Attention Output\n        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)\n        \n        # Layer Normalization for the Feedforward Output\n        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)\n        \n        # Dropout Layers for Self-Attention and Feedforward\n        self.dropout1 = layers.Dropout(rate)\n        self.dropout2 = layers.Dropout(rate)\n\n    def call(self, inputs, training):\n        # Multi-Head Self-Attention\n        attn_output = self.att(inputs, inputs)\n        \n        # Dropout for Self-Attention\n        attn_output = self.dropout1(attn_output, training=training)\n        \n        # Residual Connection and Layer Normalization for Self-Attention\n        out1 = self.layernorm1(inputs + attn_output)\n        \n        # Feedforward Processing\n        ffn_output = self.ffn(out1)\n        \n        # Dropout for Feedforward\n        ffn_output = self.dropout2(ffn_output, training=training)\n        \n        # Residual Connection and Layer Normalization for Feedforward\n        return self.layernorm2(out1 + ffn_output)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:50.237417Z","iopub.execute_input":"2023-12-21T21:59:50.237724Z","iopub.status.idle":"2023-12-21T21:59:50.252427Z","shell.execute_reply.started":"2023-12-21T21:59:50.237698Z","shell.execute_reply":"2023-12-21T21:59:50.251578Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### **Explanation**:\n\n* The TransformerEncoder block is a fundamental building block of the Transformer model, commonly used in natural language processing tasks but applicable to sequential data in general.\n\n* It consists of two main sub-blocks: multi-head self-attention (self.att) and a feedforward neural network (self.ffn).\n\n* **Multi-Head Self-Attention**:\n   self.att is a multi-head self-attention layer. It allows the model to weigh different parts of the input sequence differently, capturing dependencies      between different elements in the sequence.\n* **Feedforward Neural Network**:\n   self.ffn is a feedforward neural network. It processes the information from the attention layer and transforms it to a higher-level representation.\n* **Layer Normalization and Dropout**:\n   After each of these sub-blocks, layer normalization (self.layernorm1, self.layernorm2) is applied to normalize the outputs.\n   Dropout (self.dropout1, self.dropout2) is used during training to prevent overfitting by randomly setting a fraction of input units to zero.\n* **Call Method**:\n   In the call method, the input sequence (inputs) is passed through the self-attention layer, normalized, and then passed through the feedforward            network.\n   Residual connections (inputs + output) are used, and layer normalization is applied again.","metadata":{}},{"cell_type":"markdown","source":"### **<span style=\"color:darkblue\">5.2.2. <i>Transformer</i> Decoder**","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://i.ibb.co/rbrk7mV/Decoder.png\" alt=\"decod\">\n</div>\n\n\n**Difference between Full-Attention and Causal Attention Mechanisms:**\n<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://www.researchgate.net/publication/354542656/figure/fig3/AS:1067437764145154@1631508403847/Attention-Mechanism-Comparison-Causal-models-are-often-supervised-to-predict-the.ppm\" alt=\"Causal\">\n</div>","metadata":{}},{"cell_type":"code","source":"class TransformerDecoder(layers.Layer):\n    def __init__(self, embed_dim, num_heads, feed_forward_dim, dropout_rate=0.1):\n        super().__init__()\n\n        # Layer normalization for self-attention\n        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)\n        # Layer normalization for encoder attention\n        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)\n        # Layer normalization for feed-forward network\n        self.layernorm3 = layers.LayerNormalization(epsilon=1e-6)\n\n        # Self-Attention Layer for Decoding\n        self.self_att = layers.MultiHeadAttention(\n            num_heads=num_heads, key_dim=embed_dim\n        )\n        # Encoder Attention Layer for Decoding\n        self.enc_att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)\n\n        # Dropout layers for self-attention, encoder attention, and feed-forward network\n        self.self_dropout = layers.Dropout(0.5)\n        self.enc_dropout = layers.Dropout(0.1)\n        self.ffn_dropout = layers.Dropout(0.1)\n\n        # Feed-Forward Network\n        self.ffn = keras.Sequential(\n            [\n                layers.Dense(feed_forward_dim, activation=\"relu\"),\n                layers.Dense(embed_dim),\n            ]\n        )\n\n    def causal_attention_mask(self, batch_size, n_dest, n_src, dtype):\n        \"\"\"Masks the upper half of the dot product matrix in self attention.\n\n        This prevents the flow of information from future tokens to the current token.\n        1's in the lower triangle, counting from the lower right corner.\n        \"\"\"\n        i = tf.range(n_dest)[:, None]\n        j = tf.range(n_src)\n        m = i >= j - n_src + n_dest\n        mask = tf.cast(m, dtype)\n        mask = tf.reshape(mask, [1, n_dest, n_src])\n        mult = tf.concat(\n            [batch_size[..., tf.newaxis], tf.constant([1, 1], dtype=tf.int32)], 0\n        )\n        return tf.tile(mask, mult)\n\n    def call(self, enc_out, target, training):\n        # Get the shape of the target\n        input_shape = tf.shape(target)\n        batch_size = input_shape[0]\n        seq_len = input_shape[1]\n\n        # Generate causal attention mask\n        causal_mask = self.causal_attention_mask(batch_size, seq_len, seq_len, tf.bool)\n\n        # Self-attention on the target with causal mask\n        target_att = self.self_att(target, target, attention_mask=causal_mask)\n\n        # Apply layer normalization and dropout to the self-attention output\n        target_norm = self.layernorm1(target + self.self_dropout(target_att, training=training))\n\n        # Encoder attention using the normalized target and encoder output\n        enc_out = self.enc_att(target_norm, enc_out)\n\n        # Apply layer normalization and dropout to the encoder attention output and add to the target\n        enc_out_norm = self.layernorm2(self.enc_dropout(enc_out, training=training) + target_norm)\n\n        # Feed-forward network on the encoder attention output\n        ffn_out = self.ffn(enc_out_norm)\n\n        # Apply layer normalization and dropout to the feed-forward network output and add to the encoder attention output\n        ffn_out_norm = self.layernorm3(enc_out_norm + self.ffn_dropout(ffn_out, training=training))\n\n        return ffn_out_norm","metadata":{"execution":{"iopub.status.busy":"2023-12-21T21:59:50.253589Z","iopub.execute_input":"2023-12-21T21:59:50.253929Z","iopub.status.idle":"2023-12-21T21:59:50.270648Z","shell.execute_reply.started":"2023-12-21T21:59:50.253905Z","shell.execute_reply":"2023-12-21T21:59:50.269748Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### **Explanation**:\n\n* **Multi-Head Self-Attention (mha1)**:\n\nmha1 performs self-attention on the input sequence with a causal mask (look_ahead_mask). It allows the model to attend to previous positions while preventing attention to future positions.\n\n* **Layer Normalization and Residual Connection (layernorm1)**:\n\nlayernorm1 normalizes the output of the self-attention layer and adds it to the input, creating a residual connection.\n* **Multi-Head Attention with Encoder Output (mha2)**:\n\nmha2 performs attention on the decoder's output (out1) using the encoder's output. It helps the decoder focus on relevant information from the encoder.\n* **Layer Normalization and Residual Connection (layernorm2)**:\n\nlayernorm2 normalizes the output of the second attention layer and adds it to the output of the first attention layer, creating another residual connection.\n* **Feedforward Network (ffn)**:\n\nffn is a feedforward neural network applied to the output of the second attention layer.\n* **Layer Normalization and Residual Connection (layernorm3)**:\n\nlayernorm3 normalizes the output of the feedforward network and adds it to the output of the second attention layer, creating a final residual connection.\n* **Dropout**:\n\nDropout layers are applied after each attention and feedforward sub-layer to prevent overfitting during training.","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">5.3. <i>Transformer</i> Building**","metadata":{}},{"cell_type":"markdown","source":"<!-- Centering the image -->\n<div style=\"text-align:center\">\n  <!-- Inserting the image with a URL or file path -->\n  <img src=\"https://i.ibb.co/0nMb93R/TRANSFORMER.png\" alt=\"decod\">\n</div>","metadata":{}},{"cell_type":"code","source":"class Transformer(keras.Model):\n    #Block 1: Define the Transformer Model\n    def __init__(\n        self,\n        num_hid=64,\n        num_head=2,\n        num_feed_forward=128,\n        source_maxlen=100,\n        target_maxlen=100,\n        num_layers_enc=4,\n        num_layers_dec=1,\n        num_classes=60,\n    ):\n        super().__init__()\n        \n    #Block 2: Model Initialization\n        # Metrics for tracking loss and edit distance\n        self.loss_metric = keras.metrics.Mean(name=\"loss\")\n        self.acc_metric = keras.metrics.Mean(name=\"edit_dist\")\n        \n        # Additional metrics for precision, recall, and F1-score\n        #self.precision_metric = keras.metrics.Precision(name=\"precision\")\n        #self.recall_metric = keras.metrics.Recall(name=\"recall\")\n        # Initialize F1Score metric\n        #self.f1_metric = keras.metrics.F1Score(name=\"f1_score\", average=\"weighted\")\n\n        \n        # Model parameters\n        self.num_layers_enc = num_layers_enc\n        self.num_layers_dec = num_layers_dec\n        self.target_maxlen = target_maxlen\n        self.num_classes = num_classes\n        \n    #Block 3: Input Embedding Layers\n        self.enc_input = LandmarkEmbedding(num_hid=num_hid, maxlen=source_maxlen)\n        self.dec_input = TokenEmbedding(\n            num_vocab=num_classes, maxlen=target_maxlen, num_hid=num_hid\n        )\n\n    #Block 4: Encoder\n        # Encoder: Stack of TransformerEncoder layers\n        self.encoder = keras.Sequential(\n            [self.enc_input]\n            + [\n                TransformerEncoder(num_hid, num_head, num_feed_forward)\n                for _ in range(num_layers_enc)\n            ]\n        )\n    \n    #Block 5: Decoder Layers\n        # Decoder layers\n        for i in range(num_layers_dec):\n            setattr(\n                self,\n                f\"dec_layer_{i}\",\n                TransformerDecoder(num_hid, num_head, num_feed_forward),\n            )\n    \n    #Block 6: Classifier\n\n        # Classifier for final predictions\n        self.classifier = layers.Dense(num_classes)\n\n    #Block 7: Decode Method\n    def decode(self, enc_out, target, training):\n        y = self.dec_input(target)\n        for i in range(self.num_layers_dec):\n            y = getattr(self, f\"dec_layer_{i}\")(enc_out, y, training)\n        return y\n\n    #Block 8: Call Method\n    def call(self, inputs, training):\n        source = inputs[0]\n        target = inputs[1]\n        x = self.encoder(source, training)\n        y = self.decode(x, target, training)\n        return self.classifier(y)\n\n    #Block 9: Property for Returning Model Metrics\n    @property\n    def metrics(self):\n        return [self.loss_metric,\n                self.acc_metric]\n\n    #Block 10: Training Step\n    def train_step(self, batch):\n        \"\"\"Processes one batch inside model.fit().\"\"\"\n        source = batch[0]\n        target = batch[1]\n\n        input_shape = tf.shape(target)\n        batch_size = input_shape[0]\n\n        dec_input = target[:, :-1]\n        dec_target = target[:, 1:]\n        with tf.GradientTape() as tape:\n            preds = self([source, dec_input])\n            one_hot = tf.one_hot(dec_target, depth=self.num_classes)\n            mask = tf.math.logical_not(tf.math.equal(dec_target, pad_token_idx))\n            loss = self.compiled_loss(one_hot, preds, sample_weight=mask)\n        trainable_vars = self.trainable_variables\n        gradients = tape.gradient(loss, trainable_vars)\n        self.optimizer.apply_gradients(zip(gradients, trainable_vars))\n\n\n        # Computes the Levenshtein distance between sequences\n        edit_dist = tf.edit_distance(\n            tf.sparse.from_dense(target),\n            tf.sparse.from_dense(tf.cast(tf.argmax(preds, axis=1), tf.int32)),\n        )\n        edit_dist = tf.reduce_mean(edit_dist)\n        self.acc_metric.update_state(edit_dist)\n        self.loss_metric.update_state(loss)\n        #self.precision_metric.update_state(one_hot, preds, sample_weight=mask)\n\n        return {\n            \"loss\": self.loss_metric.result(),\n            \"edit_dist\": self.acc_metric.result()}\n\n    #Block 11: Testing Step\n    def test_step(self, batch):        \n        source = batch[0]\n        target = batch[1]\n\n        input_shape = tf.shape(target)\n        batch_size = input_shape[0]\n\n        dec_input = target[:, :-1]\n        dec_target = target[:, 1:]\n        preds = self([source, dec_input])\n\n        one_hot = tf.one_hot(dec_target, depth=self.num_classes)\n        mask = tf.math.logical_not(tf.math.equal(dec_target, pad_token_idx))\n        loss = self.compiled_loss(one_hot, preds, sample_weight=mask)\n\n\n        # Computes the Levenshtein distance between sequences\n        edit_dist = tf.edit_distance(\n            tf.sparse.from_dense(target),\n            tf.sparse.from_dense(tf.cast(tf.argmax(preds, axis=1), tf.int32)),\n        )\n        edit_dist = tf.reduce_mean(edit_dist)\n        self.acc_metric.update_state(edit_dist)\n        self.loss_metric.update_state(loss)\n        #self.precision_metric.update_state(one_hot, preds, sample_weight=mask)\n\n        return {\n            \"loss\": self.loss_metric.result(),\n            \"edit_dist\": self.acc_metric.result()}\n\n    #Block 12: Inference method\n    def generate(self, source, target_start_token_idx):\n        \"\"\"Performs inference over one batch of inputs using greedy decoding.\"\"\"\n        bs = tf.shape(source)[0]\n        enc = self.encoder(source, training = False)\n        dec_input = tf.ones((bs, 1), dtype=tf.int32) * target_start_token_idx\n        dec_logits = []\n        for i in range(self.target_maxlen - 1):\n            dec_out = self.decode(enc, dec_input, training = False)\n            logits = self.classifier(dec_out)\n            logits = tf.argmax(logits, axis=-1, output_type=tf.int32)\n            last_logit = logits[:, -1][..., tf.newaxis]\n            dec_logits.append(last_logit)\n            dec_input = tf.concat([dec_input, last_logit], axis=-1)\n        return dec_input","metadata":{"execution":{"iopub.status.busy":"2023-12-21T22:37:00.163125Z","iopub.execute_input":"2023-12-21T22:37:00.163655Z","iopub.status.idle":"2023-12-21T22:37:00.191495Z","shell.execute_reply.started":"2023-12-21T22:37:00.163622Z","shell.execute_reply":"2023-12-21T22:37:00.190399Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### **Block 1: Define the Transformer Model**:\nHere, the `Transformer` class is defined, inheriting from keras.Model. It includes various parameters for configuring the model, such as the number of hidden units, heads, and feed-forward dimensions, as well as maximum lengths and the number of layers for the encoder and decoder:\n* `num_hid`: Dimensionality of the hidden state in the model.\n* `num_head`: Number of attention heads in multi-head attention layers.\n* `num_feed_forward`: Dimensionality of the feed-forward layer in the transformer blocks.\n* `source_maxlen`: Maximum length of the source sequence.\n* `target_maxlen`: Maximum length of the target sequence.\n* `num_layers_enc`: Number of encoder layers in the model.\n* `num_layers_dec`: Number of decoder layers in the model.\n* `num_classes`: Number of output classes or tokens in the vocabulary.\n\n#### **Block 2: Model Initialization**:\nIn this part, metrics for tracking loss and edit distance are initialized. Model parameters are also set based on the provided arguments during model creation. `edit_dist` refers to the edit distance between two sequences. The edit distance, also known as Levenshtein distance, measures the minimum number of single-character edits (insertions, deletions, or substitutions) required to change one sequence into the other.\n\n#### **Block 3: Input Embedding Layers**:\nHere, the input embedding layers (``enc_input`` and ``dec_input``) are created using custom classes (``LandmarkEmbedding`` and ``TokenEmbedding``).\n\n#### **Block 4: Encoder**\nThe encoder is defined as a sequential stack of layers, starting with the ``enc_input`` layer followed by multiple instances of the ``TransformerEncoder`` layer.\n\n#### **Block 5: Decoder Layers**\nFor the decoder, a loop creates instances of the ``TransformerDecoder`` layer and sets them as attributes (``dec_layer_i``) of the model. A loop is used to dynamically create and add multiple instances of the TransformerDecoder layer to the model. This is motivated by the desire to create a flexible and customizable architecture that can adapt to different numbers of decoder layers specified by the user.\n\n#### **Block 6: Classifier**\nA dense layer is defined as the classifier for making final predictions. This layer maps the model's output to the number of classes.\n\n#### **Block 7: Decode Method**\nThe decode method takes encoder output (``enc_out``), target sequences (``target``), and a training flag. It processes the target sequences through the decoder layers (``dec_layer_i``).\n\n#### **Block 8: Call Method**\nThe ``call`` method takes input sequences (``source`` and ``target``) and a training flag. It encodes the source sequences, decodes the target sequences, and predicts the final output using the classifier.\n\n#### **Block 9: Property for Returning Model Metrics**\nThis property defines the metrics to be tracked during training and evaluation. In addition to ``loss_metric`` and ``acc_metric`` (edit distance), it includes precision, recall, and F1-score metrics.\n\n#### **Block 10: Training Step**\nIn this block, the ``train_step`` method processes one batch during model training. It calculates the loss, updates model weights, and computes various metrics including precision, recall, F1-score, and Levenshtein distance. The precision, recall, and F1-score metrics are updated using the ground truth (``one_hot``) and predicted values (``preds``). Levenshtein distance is computed using the ``edit_distance`` function.\n\n#### **Block 11: Testing Step**\nSimilar to the training step, the ``test_step`` method processes one batch during model evaluation. It calculates the loss, updates various metrics, and computes precision, recall, F1-score, and Levenshtein distance. The precision, recall, and F1-score metrics are updated using the ground truth (``one_hot``) and predicted values (``preds``). Levenshtein distance is computed using the ``edit_distance`` function.\n\n#### **Block 12: Inference Method**\nIn this block, the ``generate`` method performs inference over one batch of inputs using greedy decoding. It generates predictions for the target sequence. The model is provided with a source sequence, and the target sequence is generated step by step by predicting the next token at each iteration of the loop. The generated sequence (``dec_input``) is then returned. Greedy decoding is a simple strategy used in sequence generation tasks, such as machine translation or text generation, where the model generates one token at a time. In the context of this code, the ``generate`` method is using greedy decoding to generate predictions for the target sequence.","metadata":{}},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">5.4. Model Training & Testing**","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2023-12-21T22:34:55.193213Z","iopub.execute_input":"2023-12-21T22:34:55.193656Z","iopub.status.idle":"2023-12-21T22:34:55.234357Z","shell.execute_reply.started":"2023-12-21T22:34:55.193628Z","shell.execute_reply":"2023-12-21T22:34:55.23341Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DisplayOutputs(keras.callbacks.Callback):\n    def __init__(\n        self, batch, idx_to_token, target_start_token_idx=60, target_end_token_idx=61\n    ):\n        \"\"\"Displays a batch of outputs after every 4 epoch\n\n        Args:\n            batch: A test batch\n            idx_to_token: A List containing the vocabulary tokens corresponding to their indices\n            target_start_token_idx: A start token index in the target vocabulary\n            target_end_token_idx: An end token index in the target vocabulary\n        \"\"\"\n        self.batch = batch\n        self.target_start_token_idx = target_start_token_idx\n        self.target_end_token_idx = target_end_token_idx\n        self.idx_to_char = idx_to_token\n\n    def on_epoch_end(self, epoch, logs=None):\n        if epoch % 4 != 0:\n            return\n        source = self.batch[0]\n        target = self.batch[1].numpy()\n        bs = tf.shape(source)[0]\n        preds = self.model.generate(source, self.target_start_token_idx)\n        preds = preds.numpy()\n        for i in range(bs):\n            target_text = \"\".join([self.idx_to_char[_] for _ in target[i, :]])\n            prediction = \"\"\n            for idx in preds[i, :]:\n                prediction += self.idx_to_char[idx]\n                if idx == self.target_end_token_idx:\n                    break\n            print(f\"target:     {target_text.replace('-','')}\")\n            print(f\"prediction: {prediction}\\n\")","metadata":{"execution":{"iopub.status.busy":"2023-12-21T23:37:04.527924Z","iopub.execute_input":"2023-12-21T23:37:04.528355Z","iopub.status.idle":"2023-12-21T23:37:04.53989Z","shell.execute_reply.started":"2023-12-21T23:37:04.528325Z","shell.execute_reply":"2023-12-21T23:37:04.538874Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch = next(iter(valid_ds))\n\n# The vocabulary to convert predicted indices into characters\nidx_to_char = list(char_to_num.keys())\ndisplay_cb = DisplayOutputs(\n    batch, idx_to_char, target_start_token_idx=char_to_num['<'], target_end_token_idx=char_to_num['>']\n)\n\nmodel = Transformer(\n    num_hid=200,\n    num_head=4,\n    num_feed_forward=400,\n    source_maxlen = FRAME_LEN,\n    target_maxlen=64,\n    num_layers_enc=2,\n    num_layers_dec=1,\n    num_classes=62\n)\nloss_fn = tf.keras.losses.CategoricalCrossentropy(\n    from_logits=True, label_smoothing=0.1,\n)\n\n\noptimizer = keras.optimizers.Adam(0.0001)\nmodel.compile(optimizer=optimizer, loss=loss_fn)\n\n\n\nhistory = model.fit(train_ds, validation_data=valid_ds, callbacks=[display_cb], epochs=20)","metadata":{"execution":{"iopub.status.busy":"2023-12-21T23:37:43.50286Z","iopub.execute_input":"2023-12-21T23:37:43.503819Z","iopub.status.idle":"2023-12-21T23:45:19.489483Z","shell.execute_reply.started":"2023-12-21T23:37:43.503757Z","shell.execute_reply":"2023-12-21T23:45:19.488351Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **<span style=\"color:darkred\">5.5. Model Evaluation**","metadata":{}},{"cell_type":"code","source":"plt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.plot(history.history['val_edit_dist'])\nplt.legend(['training loss', 'val_loss', 'val_edit_dist'])","metadata":{"execution":{"iopub.status.busy":"2023-12-21T23:52:36.032924Z","iopub.execute_input":"2023-12-21T23:52:36.033382Z","iopub.status.idle":"2023-12-21T23:52:36.334661Z","shell.execute_reply.started":"2023-12-21T23:52:36.033346Z","shell.execute_reply":"2023-12-21T23:52:36.333623Z"},"trusted":true},"outputs":[],"execution_count":null}]}