{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install mediapipe","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:31:35.909966Z","iopub.execute_input":"2023-09-25T08:31:35.910323Z","iopub.status.idle":"2023-09-25T08:31:51.380342Z","shell.execute_reply.started":"2023-09-25T08:31:35.910293Z","shell.execute_reply":"2023-09-25T08:31:51.379204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport json\nimport mediapipe\nimport matplotlib\nimport matplotlib.pyplot as plt\nfrom matplotlib import animation, rc\nfrom skimage.transform import resize\nfrom mediapipe.framework.formats import landmark_pb2\nimport shutil\nimport warnings\nfrom tqdm.notebook import tqdm\n\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\nimport pyarrow.parquet as pq\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\nwarnings.simplefilter('ignore')\ntf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-09-25T08:31:51.382608Z","iopub.execute_input":"2023-09-25T08:31:51.383081Z","iopub.status.idle":"2023-09-25T08:32:01.813404Z","shell.execute_reply.started":"2023-09-25T08:31:51.383038Z","shell.execute_reply":"2023-09-25T08:32:01.812276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# resolver = tf.distribute.cluster_resolver.TPUClusterResolver()\n# tf.config.experimental_connect_to_cluster(resolver)\n# tf.tpu.experimental.initialize_tpu_system(resolver)\n# strategy = tf.distribute.TPUStrategy(resolver)\nstrategy = tf.distribute.MirroredStrategy()","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:01.815027Z","iopub.execute_input":"2023-09-25T08:32:01.815765Z","iopub.status.idle":"2023-09-25T08:32:06.88198Z","shell.execute_reply.started":"2023-09-25T08:32:01.815724Z","shell.execute_reply":"2023-09-25T08:32:06.880925Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data = pd.read_csv('/kaggle/input/asl-fingerspelling/train.csv')\ntrain_data","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:06.884629Z","iopub.execute_input":"2023-09-25T08:32:06.885094Z","iopub.status.idle":"2023-09-25T08:32:07.043911Z","shell.execute_reply.started":"2023-09-25T08:32:06.885057Z","shell.execute_reply":"2023-09-25T08:32:07.043043Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Fetch sequence_id, file_id, phrase from first row\npth, sequence_id, file_id, phrase = train_data.iloc[0][['path','sequence_id', 'file_id', 'phrase']]\nprint(f\"path: {pth}, sequence_id: {sequence_id}, file_id: {file_id}, phrase: {phrase}\")","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:07.045499Z","iopub.execute_input":"2023-09-25T08:32:07.045893Z","iopub.status.idle":"2023-09-25T08:32:07.057649Z","shell.execute_reply.started":"2023-09-25T08:32:07.045841Z","shell.execute_reply":"2023-09-25T08:32:07.056595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Fetch data from parquet file\nsample_sequence_df = pq.read_table(f\"/kaggle/input/asl-fingerspelling/\"+str(pth), filters=[[('sequence_id', '=', sequence_id)],]).to_pandas()\nprint(\"Full sequence dataset shape is {}\".format(sample_sequence_df.shape))","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:07.059289Z","iopub.execute_input":"2023-09-25T08:32:07.059942Z","iopub.status.idle":"2023-09-25T08:32:11.582905Z","shell.execute_reply.started":"2023-09-25T08:32:07.059908Z","shell.execute_reply":"2023-09-25T08:32:11.581924Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_sequence_df","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:11.584316Z","iopub.execute_input":"2023-09-25T08:32:11.584853Z","iopub.status.idle":"2023-09-25T08:32:11.620968Z","shell.execute_reply.started":"2023-09-25T08:32:11.584819Z","shell.execute_reply":"2023-09-25T08:32:11.619938Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function create animation from images.\n\nmatplotlib.rcParams['animation.embed_limit'] = 2**128\nmatplotlib.rcParams['savefig.pad_inches'] = 0\nrc('animation', html='jshtml')\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    def animate_func(i):\n        im.set_array(images[i])\n        return [im]\n\n    return animation.FuncAnimation(fig, animate_func, frames=len(images), interval=1000/10)","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:11.622627Z","iopub.execute_input":"2023-09-25T08:32:11.622996Z","iopub.status.idle":"2023-09-25T08:32:11.634285Z","shell.execute_reply.started":"2023-09-25T08:32:11.622962Z","shell.execute_reply":"2023-09-25T08:32:11.633229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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\nmp_pose = mediapipe.solutions.pose\nmp_hands = mediapipe.solutions.hands\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        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        right_hand_image = np.zeros((600, 600, 3))\n\n        right_hand_landmarks = landmark_pb2.NormalizedLandmarkList()\n        \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        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        images.append([right_hand_image.astype(np.uint8), left_hand_image.astype(np.uint8)])\n        all_hand_landmarks.append([right_hand_landmarks, left_hand_landmarks])\n    return images, all_hand_landmarks","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:11.63638Z","iopub.execute_input":"2023-09-25T08:32:11.636871Z","iopub.status.idle":"2023-09-25T08:32:11.652077Z","shell.execute_reply.started":"2023-09-25T08:32:11.636823Z","shell.execute_reply":"2023-09-25T08:32:11.650936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get the images created using mediapipe apis\nhand_images, hand_landmarks = get_hands(sample_sequence_df)\n# Fetch and show the data for right hand\ncreate_animation(np.array(hand_images)[:, 0])","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:11.657552Z","iopub.execute_input":"2023-09-25T08:32:11.657979Z","iopub.status.idle":"2023-09-25T08:32:23.852842Z","shell.execute_reply.started":"2023-09-25T08:32:11.657952Z","shell.execute_reply":"2023-09-25T08:32:23.851583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Pose coordinates for hand movement.\nLPOSE = [13, 15, 17, 19, 21]\nRPOSE = [14, 16, 18, 20, 22]\nPOSE = LPOSE + RPOSE","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:23.854021Z","iopub.execute_input":"2023-09-25T08:32:23.854366Z","iopub.status.idle":"2023-09-25T08:32:23.862692Z","shell.execute_reply.started":"2023-09-25T08:32:23.854337Z","shell.execute_reply":"2023-09-25T08:32:23.861884Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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-09-25T08:32:23.86417Z","iopub.execute_input":"2023-09-25T08:32:23.865126Z","iopub.status.idle":"2023-09-25T08:32:23.914958Z","shell.execute_reply.started":"2023-09-25T08:32:23.865093Z","shell.execute_reply":"2023-09-25T08:32:23.913921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"FEATURE_COLUMNS = X + Y + Z","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:23.916726Z","iopub.execute_input":"2023-09-25T08:32:23.917496Z","iopub.status.idle":"2023-09-25T08:32:23.925729Z","shell.execute_reply.started":"2023-09-25T08:32:23.917459Z","shell.execute_reply":"2023-09-25T08:32:23.924793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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 RPOSE]\nLPOSE_IDX = [i for i, col in enumerate(FEATURE_COLUMNS)  if  \"pose\" in col and int(col[-2:]) in LPOSE]","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:32:23.927383Z","iopub.execute_input":"2023-09-25T08:32:23.928187Z","iopub.status.idle":"2023-09-25T08:32:23.940433Z","shell.execute_reply.started":"2023-09-25T08:32:23.928073Z","shell.execute_reply":"2023-09-25T08:32:23.939475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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\")\n# else:\n#     shutil.rmtree(\"preprocessed\")\n#     os.mkdir(\"preprocessed\")\n\n# Loop through each file_id\nfor file_id in tqdm(train_data.file_id.unique()):\n    # File name for the updated data\n    tf_file = f\"preprocessed/{file_id}.tfrecord\"\n    \n    # Check if the TFRecord file already exists\n    if os.path.exists(tf_file):\n        print(f\"TFRecord file {tf_file} already exists.\")\n        continue\n    # Parquet file name\n    pq_file = f\"/kaggle/input/asl-fingerspelling/train_landmarks/{file_id}.parquet\"\n    # Filter train.csv and fetch entries only for the relevant file_id\n    file_df = train_data.loc[train_data[\"file_id\"] == file_id]\n    # Fetch the parquet file\n    parquet_df = pq.read_table(f\"/kaggle/input/asl-fingerspelling/train_landmarks/{str(file_id)}.parquet\", columns=['sequence_id'] + FEATURE_COLUMNS).to_pandas()\n    parquet_numpy = parquet_df.to_numpy()\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(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-09-25T08:32:23.942077Z","iopub.execute_input":"2023-09-25T08:32:23.942747Z","iopub.status.idle":"2023-09-25T08:43:33.966029Z","shell.execute_reply.started":"2023-09-25T08:32:23.942715Z","shell.execute_reply":"2023-09-25T08:43:33.964922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tf_records = train_data.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-09-25T08:43:33.967515Z","iopub.execute_input":"2023-09-25T08:43:33.968222Z","iopub.status.idle":"2023-09-25T08:43:34.034726Z","shell.execute_reply.started":"2023-09-25T08:43:33.968184Z","shell.execute_reply":"2023-09-25T08:43:34.033769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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-09-25T08:43:34.036254Z","iopub.execute_input":"2023-09-25T08:43:34.036606Z","iopub.status.idle":"2023-09-25T08:43:34.048392Z","shell.execute_reply.started":"2023-09-25T08:43:34.036573Z","shell.execute_reply":"2023-09-25T08:43:34.047463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Reference: https://www.kaggle.com/code/irohith/aslfr-transformer/notebook\n\n# Function to resize and add padding.\ndef resize_pad(x):\n    if tf.shape(x)[0] < FRAME_LEN:\n        x = tf.pad(x, ([[0, FRAME_LEN-tf.shape(x)[0]], [0, 0], [0, 0]]))\n    else:\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 less NaN values since it is in frame moving.\ndef pre_process(x):\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    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    rnans = tf.math.count_nonzero(rnan_idx)\n    lnans = tf.math.count_nonzero(lnan_idx)\n    \n    # For dominant hand\n    if rnans > lnans:\n        hand = lhand\n        pose = lpose\n        \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    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    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    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    x = tf.concat([hand, pose], axis=1)\n    x = resize_pad(x)\n    \n    x = tf.where(tf.math.is_nan(x), tf.zeros_like(x), x)\n    x = tf.reshape(x, (FRAME_LEN, len(LHAND_IDX) + len(LPOSE_IDX)))\n    return x","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:34.050034Z","iopub.execute_input":"2023-09-25T08:43:34.050434Z","iopub.status.idle":"2023-09-25T08:43:34.071908Z","shell.execute_reply.started":"2023-09-25T08:43:34.0504Z","shell.execute_reply":"2023-09-25T08:43:34.070888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def decode_fn(record_bytes):\n    schema = {COL: tf.io.VarLenFeature(dtype=tf.float32) for COL in FEATURE_COLUMNS}\n    schema[\"phrase\"] = tf.io.FixedLenFeature([], dtype=tf.string)\n    features = tf.io.parse_single_example(record_bytes, schema)\n    phrase = features[\"phrase\"]\n    landmarks = ([tf.sparse.to_dense(features[COL]) for COL in FEATURE_COLUMNS])\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-09-25T08:43:34.073328Z","iopub.execute_input":"2023-09-25T08:43:34.074044Z","iopub.status.idle":"2023-09-25T08:43:34.082975Z","shell.execute_reply.started":"2023-09-25T08:43:34.073862Z","shell.execute_reply":"2023-09-25T08:43:34.081917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"table = 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\")","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:34.084332Z","iopub.execute_input":"2023-09-25T08:43:34.084985Z","iopub.status.idle":"2023-09-25T08:43:34.15754Z","shell.execute_reply.started":"2023-09-25T08:43:34.084951Z","shell.execute_reply":"2023-09-25T08:43:34.156679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def convert_fn(landmarks, phrase):\n    # Add start and end pointers to phrase.\n    phrase = start_token + phrase + end_token\n    phrase = tf.strings.bytes_split(phrase)\n    phrase = table.lookup(phrase)\n    # Vectorize and add padding.\n    phrase = tf.pad(phrase, paddings=[[0, 64 - tf.shape(phrase)[0]]], mode = 'CONSTANT', constant_values = pad_token_idx)\n    # Apply pre_process function to the landmarks.\n    return pre_process(landmarks), phrase","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:34.158987Z","iopub.execute_input":"2023-09-25T08:43:34.159336Z","iopub.status.idle":"2023-09-25T08:43:34.165502Z","shell.execute_reply.started":"2023-09-25T08:43:34.159303Z","shell.execute_reply":"2023-09-25T08:43:34.164451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"batch_size = 64\ntrain_len = int(0.8 * len(tf_records))\n\nwith strategy.scope():\n    train_ds = tf.data.TFRecordDataset(tf_records[:train_len]).map(decode_fn).map(convert_fn).batch(batch_size).prefetch(buffer_size=tf.data.AUTOTUNE).cache()\n    valid_ds = tf.data.TFRecordDataset(tf_records[train_len:]).map(decode_fn).map(convert_fn).batch(batch_size).prefetch(buffer_size=tf.data.AUTOTUNE).cache()","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:34.167345Z","iopub.execute_input":"2023-09-25T08:43:34.167769Z","iopub.status.idle":"2023-09-25T08:43:36.152671Z","shell.execute_reply.started":"2023-09-25T08:43:34.167737Z","shell.execute_reply":"2023-09-25T08:43:36.151667Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Take one batch from the dataset\nfor inputs, outputs in train_ds.take(1):\n    # Print the shape of the inputs and outputs\n    print(f\"Input shape: {inputs.shape}\")\n    print(f\"Output shape: {outputs.shape}\")","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:36.154254Z","iopub.execute_input":"2023-09-25T08:43:36.154616Z","iopub.status.idle":"2023-09-25T08:43:36.738588Z","shell.execute_reply.started":"2023-09-25T08:43:36.154581Z","shell.execute_reply":"2023-09-25T08:43:36.737548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# image_tensor = tf.cast(inputs[0], tf.float32) \n\n# plt.imshow(image_tensor, cmap='gray')\n# plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:36.740437Z","iopub.execute_input":"2023-09-25T08:43:36.740793Z","iopub.status.idle":"2023-09-25T08:43:36.745291Z","shell.execute_reply.started":"2023-09-25T08:43:36.740757Z","shell.execute_reply":"2023-09-25T08:43:36.74393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# outputs[0].shape","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:36.746684Z","iopub.execute_input":"2023-09-25T08:43:36.747374Z","iopub.status.idle":"2023-09-25T08:43:37.15033Z","shell.execute_reply.started":"2023-09-25T08:43:36.747335Z","shell.execute_reply":"2023-09-25T08:43:37.1491Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class TokenEmbedding(layers.Layer):\n    def __init__(self, num_vocab=1000, maxlen=100, num_hid=64):\n        super().__init__()\n        self.emb = tf.keras.layers.Embedding(num_vocab, num_hid)\n        self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=num_hid)\n\n    def call(self, x):\n        maxlen = tf.shape(x)[-1]\n        x = self.emb(x)\n        positions = tf.range(start=0, limit=maxlen, delta=1)\n        positions = self.pos_emb(positions)\n        return x + positions\n\n\nclass LandmarkEmbedding(layers.Layer):\n    def __init__(self, num_hid=64, maxlen=100):\n        super().__init__()\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        self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=num_hid)\n\n    def call(self, x):\n        x = self.conv1(x)\n        x = self.conv2(x)\n        return self.conv3(x)","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:37.153842Z","iopub.execute_input":"2023-09-25T08:43:37.154195Z","iopub.status.idle":"2023-09-25T08:43:37.166393Z","shell.execute_reply.started":"2023-09-25T08:43:37.154147Z","shell.execute_reply":"2023-09-25T08:43:37.16531Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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        self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)\n        self.ffn = keras.Sequential(\n            [\n                layers.Dense(feed_forward_dim, activation=\"relu\"),\n                layers.Dense(embed_dim),\n            ]\n        )\n        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)\n        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)\n        self.dropout1 = layers.Dropout(rate)\n        self.dropout2 = layers.Dropout(rate)\n\n    def call(self, inputs, training):\n        attn_output = self.att(inputs, inputs)\n        attn_output = self.dropout1(attn_output, training=training)\n        out1 = self.layernorm1(inputs + attn_output)\n        ffn_output = self.ffn(out1)\n        ffn_output = self.dropout2(ffn_output, training=training)\n        return self.layernorm2(out1 + ffn_output)","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:37.167773Z","iopub.execute_input":"2023-09-25T08:43:37.168284Z","iopub.status.idle":"2023-09-25T08:43:37.17999Z","shell.execute_reply.started":"2023-09-25T08:43:37.168249Z","shell.execute_reply":"2023-09-25T08:43:37.178905Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Customized to add `training` variable\n# Reference: https://www.kaggle.com/code/shlomoron/aslfr-a-simple-transformer/notebook\n\nclass TransformerDecoder(layers.Layer):\n    def __init__(self, embed_dim, num_heads, feed_forward_dim, dropout_rate=0.1):\n        super().__init__()\n        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)\n        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)\n        self.layernorm3 = layers.LayerNormalization(epsilon=1e-6)\n        self.self_att = layers.MultiHeadAttention(\n            num_heads=num_heads, key_dim=embed_dim\n        )\n        self.enc_att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)\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        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 flow of information from future tokens to 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        input_shape = tf.shape(target)\n        batch_size = input_shape[0]\n        seq_len = input_shape[1]\n        causal_mask = self.causal_attention_mask(batch_size, seq_len, seq_len, tf.bool)\n        target_att = self.self_att(target, target, attention_mask=causal_mask)\n        target_norm = self.layernorm1(target + self.self_dropout(target_att, training = training))\n        enc_out = self.enc_att(target_norm, enc_out)\n        enc_out_norm = self.layernorm2(self.enc_dropout(enc_out, training = training) + target_norm)\n        ffn_out = self.ffn(enc_out_norm)\n        ffn_out_norm = self.layernorm3(enc_out_norm + self.ffn_dropout(ffn_out, training = training))\n        return ffn_out_norm","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:43:37.186187Z","iopub.execute_input":"2023-09-25T08:43:37.188298Z","iopub.status.idle":"2023-09-25T08:43:37.204042Z","shell.execute_reply.started":"2023-09-25T08:43:37.188271Z","shell.execute_reply":"2023-09-25T08:43:37.202853Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Customized to add edit_dist metric and training variable.\n# Reference:\n# https://www.kaggle.com/code/irohith/aslfr-transformer/notebook\n# https://www.kaggle.com/code/shlomoron/aslfr-a-simple-transformer/notebook\n\nclass Transformer(keras.Model):\n    def __init__(\n        self,\n        num_hid=200,\n        num_head=4,\n        num_feed_forward=400,\n        source_maxlen = 128,\n        target_maxlen=64,\n        num_layers_enc=2,\n        num_layers_dec=1,\n        num_classes=62\n    ):\n        super().__init__()\n        self.loss_metric = keras.metrics.Mean(name=\"loss\")\n        self.acc_metric = keras.metrics.Mean(name=\"edit_dist\")\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        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        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        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        self.classifier = layers.Dense(num_classes)\n\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    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    @property\n    def metrics(self):\n        return [self.loss_metric]\n\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        # Computes the Levenshtein distance between sequences since the evaluation\n        # metric for this contest is the normalized total levenshtein distance.\n        edit_dist = tf.edit_distance(tf.sparse.from_dense(target), \n                                     tf.sparse.from_dense(tf.cast(tf.argmax(preds, axis=1), tf.int32)))\n        edit_dist = tf.reduce_mean(edit_dist)\n        self.acc_metric.update_state(edit_dist)\n        self.loss_metric.update_state(loss)\n        return {\"loss\": self.loss_metric.result(), \"edit_dist\": self.acc_metric.result()}\n\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        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        # Computes the Levenshtein distance between sequences since the evaluation\n        # metric for this contest is the normalized total levenshtein distance.\n        edit_dist = tf.edit_distance(tf.sparse.from_dense(target), \n                                     tf.sparse.from_dense(tf.cast(tf.argmax(preds, axis=1), tf.int32)))\n        edit_dist = tf.reduce_mean(edit_dist)\n        self.acc_metric.update_state(edit_dist)\n        self.loss_metric.update_state(loss)\n        return {\"loss\": self.loss_metric.result(), \"edit_dist\": self.acc_metric.result()}\n\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-09-25T08:47:52.174603Z","iopub.execute_input":"2023-09-25T08:47:52.174996Z","iopub.status.idle":"2023-09-25T08:47:52.191654Z","shell.execute_reply.started":"2023-09-25T08:47:52.174963Z","shell.execute_reply":"2023-09-25T08:47:52.190369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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-09-25T08:47:53.520837Z","iopub.execute_input":"2023-09-25T08:47:53.521238Z","iopub.status.idle":"2023-09-25T08:47:53.5318Z","shell.execute_reply.started":"2023-09-25T08:47:53.521205Z","shell.execute_reply":"2023-09-25T08:47:53.530341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Transformer variables are customized from original keras tutorial to suit this dataset.\n# Reference: https://www.kaggle.com/code/shlomoron/aslfr-a-simple-transformer/notebook\n\n\n\nwith strategy.scope():\n    batch = next(iter(valid_ds))\n\n    # The vocabulary to convert predicted indices into characters\n    idx_to_char = list(char_to_num.keys())\n    display_cb = DisplayOutputs(batch, idx_to_char, target_start_token_idx=char_to_num['<'], target_end_token_idx=char_to_num['>'])  \n    # set the arguments as per vocabulary index for '<' and '>'\n    model = 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    )\n    loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True, label_smoothing=0.1,)\n\n\n    optimizer = keras.optimizers.Adam(0.0001)\n    model.compile(optimizer=optimizer, loss=loss_fn)","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:47:55.514629Z","iopub.execute_input":"2023-09-25T08:47:55.515028Z","iopub.status.idle":"2023-09-25T08:47:56.023938Z","shell.execute_reply.started":"2023-09-25T08:47:55.514997Z","shell.execute_reply":"2023-09-25T08:47:56.022921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = model.fit(train_ds, validation_data=train_ds, epochs=13)","metadata":{"execution":{"iopub.status.busy":"2023-09-25T08:47:57.969019Z","iopub.execute_input":"2023-09-25T08:47:57.970183Z","iopub.status.idle":"2023-09-25T08:47:58.599512Z","shell.execute_reply.started":"2023-09-25T08:47:57.970137Z","shell.execute_reply":"2023-09-25T08:47:58.597842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import tensorflow as tf\n# from tensorflow.keras.applications import ResNet50\n# from tensorflow.keras import layers\n\n# class MyModel(tf.keras.Model):\n#     def __init__(self, num_classes=62):\n#         super().__init__()\n#         self.resnet50 = ResNet50(include_top=False, weights=None, input_shape=(128, 78, 1))\n#         self.flatten = layers.Flatten()\n#         self.classifier = layers.Dense(num_classes)\n\n#     def call(self, inputs):\n#         x = self.resnet50(inputs)\n#         x = self.flatten(x)\n#         return self.classifier(x)\n\n# # Assume `train_ds` is your dataset\n# # Add a dimension to your images to simulate the color channel\n# train_ds_1 = train_ds.map(lambda x, y: (tf.expand_dims(x, axis=-1), y))\n\n# # Instantiate your model\n# model = MyModel(num_classes=62)\n\n# # Compile your model\n# model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])\n\n# # Train your model\n# model.fit(train_ds_1, epochs=10)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}