{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":46105,"databundleVersionId":5087314},{"sourceType":"datasetVersion","sourceId":5158870,"datasetId":2997548,"databundleVersionId":5230660},{"sourceType":"datasetVersion","sourceId":5194802,"datasetId":3020507,"databundleVersionId":5267015},{"sourceType":"datasetVersion","sourceId":5594542,"datasetId":3218684,"databundleVersionId":5669509}],"dockerImageVersionId":30474,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**It takes around 6~7 hours to train a model with single fold.**\n\nSo, you should run this notebook four time (for each seed=42,43,44,45) to get all 4 seed weights of the model.\n\nOr you can just press 'Save and Run All' to get the result of the fold 0 model.\n\nThere are two issues with this notebook:\n\n- Training time issue: The weird thing is that it takes around 3 hours in Colab TPU (v2-8), which is supposed to be slower than Kaggle's TPU (v3-8). If you know a lot about tf+TPU frameworks, please let me know how to debug this issue.\n\n- Training unstability: I Changed some minor configurations from the final solution. I changed epoch 400 -> 300, clipvalue=1. -> None, label_smoothing=0 -> label_smoothing=0.1 for more stable reproducibility(with slightly lower accuracy). unstability mainly caused by high lambda value of the AWP(0.2) - it will cause nan loss somtimes(around once out of five times). you can switch it to 0.1 or lower the learning rate and still can get fairly high accuracy. Also, if you have any idea related to this issue, please let me know.","metadata":{}},{"cell_type":"code","source":"!pip install -q /kaggle/input/tensorflow-2120/tensorflow-2.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n!pip install -q tensorflow-addons==0.20.0\n!pip install -q git+https://github.com/hoyso48/tf-utils@main","metadata":{"id":"IpEQKDrDqAFP","outputId":"2affcd98-7204-4d46-c030-ce8daefe9ad4","execution":{"iopub.status.busy":"2026-02-21T09:12:55.868612Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport tensorflow_addons as tfa\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport tensorflow.keras.mixed_precision as mixed_precision\n\nfrom tqdm.autonotebook import tqdm\nimport sklearn\n\nfrom tf_utils.schedules import OneCycleLR, ListedLR\nfrom tf_utils.callbacks import Snapshot, SWA\nfrom tf_utils.learners import FGM, AWP\n\nimport os\nimport time\nimport pickle\nimport math\nimport random\nimport sys\nimport cv2\nimport gc\nimport glob\nimport datetime\n\nprint(f'Tensorflow Version: {tf.__version__}')\nprint(f'Python Version: {sys.version}')","metadata":{"id":"wD7tqFC_qAFQ","outputId":"426ff58f-01d7-451e-ea99-7b00405d8781","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    for gpu in gpus:\n        tf.config.experimental.set_memory_growth(gpu, True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_strategy(device=None):\n\n    IS_TPU = False\n\n    try:\n        # Try TPU first (only if explicitly requested)\n        if device == \"TPU\":\n            print(\"Connecting to TPU...\")\n            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)\n            IS_TPU = True\n            print(\"TPU connected.\")\n\n        else:\n            raise ValueError(\"Skip TPU\")\n\n    except:\n        # Fallback to GPU / CPU\n        gpus = tf.config.list_physical_devices('GPU')\n\n        if len(gpus) > 1:\n            print(\"Using Multi-GPU\")\n            strategy = tf.distribute.MirroredStrategy()\n\n        elif len(gpus) == 1:\n            print(\"Using Single GPU\")\n            strategy = tf.distribute.get_strategy()\n\n        else:\n            print(\"Using CPU\")\n            strategy = tf.distribute.get_strategy()\n\n    REPLICAS = strategy.num_replicas_in_sync\n    print(f\"REPLICAS: {REPLICAS}\")\n\n    return strategy, REPLICAS, IS_TPU\ndef get_strategy(device=None):\n\n    IS_TPU = False\n\n    try:\n        # Try TPU first (only if explicitly requested)\n        if device == \"TPU\":\n            print(\"Connecting to TPU...\")\n            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)\n            IS_TPU = True\n            print(\"TPU connected.\")\n\n        else:\n            raise ValueError(\"Skip TPU\")\n\n    except:\n        # Fallback to GPU / CPU\n        gpus = tf.config.list_physical_devices('GPU')\n\n        if len(gpus) > 1:\n            print(\"Using Multi-GPU\")\n            strategy = tf.distribute.MirroredStrategy()\n\n        elif len(gpus) == 1:\n            print(\"Using Single GPU\")\n            strategy = tf.distribute.get_strategy()\n\n        else:\n            print(\"Using CPU\")\n            strategy = tf.distribute.get_strategy()\n\n    REPLICAS = strategy.num_replicas_in_sync\n    print(f\"REPLICAS: {REPLICAS}\")\n\n    return strategy, REPLICAS, IS_TPU\n\n\nSTRATEGY, N_REPLICAS, IS_TPU = get_strategy()","metadata":{"id":"u74o98JxqAFQ","outputId":"af042ef9-76e7-40a2-9938-e5fd4bb6f495","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_FILENAMES = glob.glob('/kaggle/input/islr-5fold/*.tfrecords')\nprint(len(TRAIN_FILENAMES))","metadata":{"id":"QVDc3vkwqAFQ","outputId":"b84108e5-9e50-4a46-eca8-6a60ecdde99c","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train DataFrame\ntrain_df = pd.read_csv('/kaggle/input/asl-signs/train.csv')\ndisplay(train_df.head())\ndisplay(train_df.info())","metadata":{"id":"DtrBI-jwqAFQ","outputId":"3ebb3c2b-8fbe-4552-8a16-d50484823b57","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import re\ndef count_data_items(filenames):\n    n = [int(re.compile(r\"-([0-9]*)\\.\").search(filename.split('/')[-1]).group(1)) for filename in filenames]\n    return np.sum(n)\nprint(count_data_items(TRAIN_FILENAMES), len(train_df))\nassert count_data_items(TRAIN_FILENAMES) == len(train_df)","metadata":{"id":"iAc9FKztqAFQ","outputId":"ade0413c-5a17-4a4f-f5a6-b0048ab76ffa","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ROWS_PER_FRAME = 543\nMAX_LEN = 384\nCROP_LEN = MAX_LEN\nNUM_CLASSES  = 250\nPAD = -100.\nNOSE=[\n    1,2,98,327\n]\nLNOSE = [98]\nRNOSE = [327]\nLIP = [ 0, \n    61, 185, 40, 39, 37, 267, 269, 270, 409,\n    291, 146, 91, 181, 84, 17, 314, 405, 321, 375,\n    78, 191, 80, 81, 82, 13, 312, 311, 310, 415,\n    95, 88, 178, 87, 14, 317, 402, 318, 324, 308,\n]\nLLIP = [84,181,91,146,61,185,40,39,37,87,178,88,95,78,191,80,81,82]\nRLIP = [314,405,321,375,291,409,270,269,267,317,402,318,324,308,415,310,311,312]\n\nPOSE = [500, 502, 504, 501, 503, 505, 512, 513]\nLPOSE = [513,505,503,501]\nRPOSE = [512,504,502,500]\n\nREYE = [\n    33, 7, 163, 144, 145, 153, 154, 155, 133,\n    246, 161, 160, 159, 158, 157, 173,\n]\nLEYE = [\n    263, 249, 390, 373, 374, 380, 381, 382, 362,\n    466, 388, 387, 386, 385, 384, 398,\n]\n\nLHAND = np.arange(468, 489).tolist()\nRHAND = np.arange(522, 543).tolist()\n\nPOINT_LANDMARKS = LIP + LHAND + RHAND + NOSE + REYE + LEYE #+POSE\n\nNUM_NODES = len(POINT_LANDMARKS)\nCHANNELS = 6*NUM_NODES\n\nprint(NUM_NODES)\nprint(CHANNELS)\n\ndef interp1d_(x, target_len, method='random'):\n    length = tf.shape(x)[1]\n    target_len = tf.maximum(1,target_len)\n    if method == 'random':\n        if tf.random.uniform(()) < 0.33:\n            x = tf.image.resize(x, (target_len,tf.shape(x)[1]),'bilinear')\n        else:\n            if tf.random.uniform(()) < 0.5:\n                x = tf.image.resize(x, (target_len,tf.shape(x)[1]),'bicubic')\n            else:\n                x = tf.image.resize(x, (target_len,tf.shape(x)[1]),'nearest')\n    else:\n        x = tf.image.resize(x, (target_len,tf.shape(x)[1]),method)\n    return x\n\ndef tf_nan_mean(x, axis=0, keepdims=False):\n    return tf.reduce_sum(tf.where(tf.math.is_nan(x), tf.zeros_like(x), x), axis=axis, keepdims=keepdims) / tf.reduce_sum(tf.where(tf.math.is_nan(x), tf.zeros_like(x), tf.ones_like(x)), axis=axis, keepdims=keepdims)\n\ndef tf_nan_std(x, center=None, axis=0, keepdims=False):\n    if center is None:\n        center = tf_nan_mean(x, axis=axis,  keepdims=True)\n    d = x - center\n    return tf.math.sqrt(tf_nan_mean(d * d, axis=axis, keepdims=keepdims))\n\nclass Preprocess(tf.keras.layers.Layer):\n    def __init__(self, max_len=MAX_LEN, point_landmarks=POINT_LANDMARKS, **kwargs):\n        super().__init__(**kwargs)\n        self.max_len = max_len\n        self.point_landmarks = point_landmarks\n\n    def call(self, inputs):\n        if tf.rank(inputs) == 3:\n            x = inputs[None,...]\n        else:\n            x = inputs\n        \n        mean = tf_nan_mean(tf.gather(x, [17], axis=2), axis=[1,2], keepdims=True)\n        mean = tf.where(tf.math.is_nan(mean), tf.constant(0.5,x.dtype), mean)\n        x = tf.gather(x, self.point_landmarks, axis=2) #N,T,P,C\n        std = tf_nan_std(x, center=mean, axis=[1,2], keepdims=True)\n        \n        x = (x - mean)/std\n\n        if self.max_len is not None:\n            x = x[:,:self.max_len]\n        length = tf.shape(x)[1]\n        x = x[...,:2]\n\n        dx = tf.cond(tf.shape(x)[1]>1,lambda:tf.pad(x[:,1:] - x[:,:-1], [[0,0],[0,1],[0,0],[0,0]]),lambda:tf.zeros_like(x))\n\n        dx2 = tf.cond(tf.shape(x)[1]>2,lambda:tf.pad(x[:,2:] - x[:,:-2], [[0,0],[0,2],[0,0],[0,0]]),lambda:tf.zeros_like(x))\n\n        x = tf.concat([\n            tf.reshape(x, (-1,length,2*len(self.point_landmarks))),\n            tf.reshape(dx, (-1,length,2*len(self.point_landmarks))),\n            tf.reshape(dx2, (-1,length,2*len(self.point_landmarks))),\n        ], axis = -1)\n        \n        x = tf.where(tf.math.is_nan(x),tf.constant(0.,x.dtype),x)\n        \n        return x","metadata":{"id":"6xyloTyiqAFQ","outputId":"dab46b40-8d61-4eb7-a7a5-4d5e10ab8538","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def decode_tfrec(record_bytes):\n    features = tf.io.parse_single_example(record_bytes, {\n        'coordinates': tf.io.FixedLenFeature([], tf.string),\n        'sign': tf.io.FixedLenFeature([], tf.int64),\n    })\n    out = {}\n    out['coordinates']  = tf.reshape(tf.io.decode_raw(features['coordinates'], tf.float32), (-1,ROWS_PER_FRAME,3))\n    out['sign'] = features['sign']\n    return out\n\ndef filter_nans_tf(x, ref_point=POINT_LANDMARKS):\n    mask = tf.math.logical_not(tf.reduce_all(tf.math.is_nan(tf.gather(x,ref_point,axis=1)), axis=[-2,-1]))\n    x = tf.boolean_mask(x, mask, axis=0)\n    return x\n\ndef preprocess(x, augment=False, max_len=MAX_LEN):\n    coord = x['coordinates']\n    coord = filter_nans_tf(coord)\n    if augment:\n        coord = augment_fn(coord, max_len=max_len)\n    coord = tf.ensure_shape(coord, (None,ROWS_PER_FRAME,3))\n    \n    return tf.cast(Preprocess(max_len=max_len)(coord)[0],tf.float32), tf.one_hot(x['sign'], NUM_CLASSES)\n\ndef flip_lr(x):\n    x,y,z = tf.unstack(x, axis=-1)\n    x = 1-x\n    new_x = tf.stack([x,y,z], -1)\n    new_x = tf.transpose(new_x, [1,0,2])\n    lhand = tf.gather(new_x, LHAND, axis=0)\n    rhand = tf.gather(new_x, RHAND, axis=0)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(LHAND)[...,None], rhand)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(RHAND)[...,None], lhand)\n    llip = tf.gather(new_x, LLIP, axis=0)\n    rlip = tf.gather(new_x, RLIP, axis=0)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(LLIP)[...,None], rlip)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(RLIP)[...,None], llip)\n    lpose = tf.gather(new_x, LPOSE, axis=0)\n    rpose = tf.gather(new_x, RPOSE, axis=0)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(LPOSE)[...,None], rpose)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(RPOSE)[...,None], lpose)\n    leye = tf.gather(new_x, LEYE, axis=0)\n    reye = tf.gather(new_x, REYE, axis=0)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(LEYE)[...,None], reye)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(REYE)[...,None], leye)\n    lnose = tf.gather(new_x, LNOSE, axis=0)\n    rnose = tf.gather(new_x, RNOSE, axis=0)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(LNOSE)[...,None], rnose)\n    new_x = tf.tensor_scatter_nd_update(new_x, tf.constant(RNOSE)[...,None], lnose)\n    new_x = tf.transpose(new_x, [1,0,2])\n    return new_x\n\ndef resample(x, rate=(0.8,1.2)):\n    rate = tf.random.uniform((), rate[0], rate[1])\n    length = tf.shape(x)[0]\n    new_size = tf.cast(rate*tf.cast(length,tf.float32), tf.int32)\n    new_x = interp1d_(x, new_size)\n    return new_x\n\ndef spatial_random_affine(xyz,\n    scale  = (0.8,1.2),\n    shear = (-0.15,0.15),\n    shift  = (-0.1,0.1),\n    degree = (-30,30),\n):\n    center = tf.constant([0.5,0.5])\n    if scale is not None:\n        scale = tf.random.uniform((),*scale)\n        xyz = scale*xyz\n\n    if shear is not None:\n        xy = xyz[...,:2]\n        z = xyz[...,2:]\n        shear_x = shear_y = tf.random.uniform((),*shear)\n        if tf.random.uniform(()) < 0.5:\n            shear_x = 0.\n        else:\n            shear_y = 0.\n        shear_mat = tf.identity([\n            [1.,shear_x],\n            [shear_y,1.]\n        ])\n        xy = xy @ shear_mat\n        center = center + [shear_y, shear_x]\n        xyz = tf.concat([xy,z], axis=-1)\n\n    if degree is not None:\n        xy = xyz[...,:2]\n        z = xyz[...,2:]\n        xy -= center\n        degree = tf.random.uniform((),*degree)\n        radian = degree/180*np.pi\n        c = tf.math.cos(radian)\n        s = tf.math.sin(radian)\n        rotate_mat = tf.identity([\n            [c,s],\n            [-s, c],\n        ])\n        xy = xy @ rotate_mat\n        xy = xy + center\n        xyz = tf.concat([xy,z], axis=-1)\n\n    if shift is not None:\n        shift = tf.random.uniform((),*shift)\n        xyz = xyz + shift\n\n    return xyz\n\ndef temporal_crop(x, length=MAX_LEN):\n    l = tf.shape(x)[0]\n    offset = tf.random.uniform((), 0, tf.clip_by_value(l-length,1,length), dtype=tf.int32)\n    x = x[offset:offset+length]\n    return x\n\ndef temporal_mask(x, size=(0.2,0.4), mask_value=float('nan')):\n    l = tf.shape(x)[0]\n    mask_size = tf.random.uniform((), *size)\n    mask_size = tf.cast(tf.cast(l, tf.float32) * mask_size, tf.int32)\n    mask_offset = tf.random.uniform((), 0, tf.clip_by_value(l-mask_size,1,l), dtype=tf.int32)\n    x = tf.tensor_scatter_nd_update(x,tf.range(mask_offset, mask_offset+mask_size)[...,None],tf.fill([mask_size,543,3],mask_value))\n    return x\n\ndef spatial_mask(x, size=(0.2,0.4), mask_value=float('nan')):\n    mask_offset_y = tf.random.uniform(())\n    mask_offset_x = tf.random.uniform(())\n    mask_size = tf.random.uniform((), *size)\n    mask_x = (mask_offset_x<x[...,0]) & (x[...,0] < mask_offset_x + mask_size)\n    mask_y = (mask_offset_y<x[...,1]) & (x[...,1] < mask_offset_y + mask_size)\n    mask = mask_x & mask_y\n    x = tf.where(mask[...,None], mask_value, x)\n    return x\n\ndef augment_fn(x, always=False, max_len=None):\n    if tf.random.uniform(())<0.8 or always:\n        x = resample(x, (0.5,1.5))\n    if tf.random.uniform(())<0.5 or always:\n        x = flip_lr(x)\n    if max_len is not None:\n        x = temporal_crop(x, max_len)\n    if tf.random.uniform(())<0.75 or always:\n        x = spatial_random_affine(x)\n    if tf.random.uniform(())<0.5 or always:\n        x = temporal_mask(x)\n    if tf.random.uniform(())<0.5 or always:\n        x = spatial_mask(x)\n    return x\n\ndef get_tfrec_dataset(tfrecords, batch_size=64, max_len=64, drop_remainder=False, augment=False, shuffle=False, repeat=False):\n    # Initialize dataset with TFRecords\n    ds = tf.data.TFRecordDataset(tfrecords, num_parallel_reads=tf.data.AUTOTUNE, compression_type='GZIP')\n    ds = ds.map(decode_tfrec, tf.data.AUTOTUNE)\n    ds = ds.map(lambda x: preprocess(x, augment=augment, max_len=max_len), tf.data.AUTOTUNE)\n\n    if repeat: \n        ds = ds.repeat()\n        \n    if shuffle:\n        ds = ds.shuffle(shuffle)\n        options = tf.data.Options()\n        options.experimental_deterministic = (False)\n        ds = ds.with_options(options)\n    \n    if batch_size:\n        ds = ds.padded_batch(batch_size, padding_values=PAD, padded_shapes=([max_len,CHANNELS],[NUM_CLASSES]), drop_remainder=drop_remainder)\n\n    ds = ds.prefetch(tf.data.AUTOTUNE)\n        \n    return ds\n\nds = get_tfrec_dataset(TRAIN_FILENAMES, augment=True, batch_size=1024)\nfor x in ds:\n    temp_train = x\n    break","metadata":{"id":"r17ZnZaGqAFQ","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import HTML\nimport matplotlib.animation as animation\nfrom matplotlib.animation import FuncAnimation\n\ndef filter_nans(frames):\n    return frames[~np.isnan(frames).all(axis=(-2,-1))]\n\nds = tf.data.TFRecordDataset(TRAIN_FILENAMES, num_parallel_reads=tf.data.AUTOTUNE, compression_type='GZIP')\nds = ds.map(decode_tfrec, tf.data.AUTOTUNE)\nprint(ds)\nfor x in ds:\n    temp = x['coordinates'].numpy()\n    if not len(filter_nans(temp[:,LHAND])) == 0:\n        break\n    \nedges = [(0,1),(1,2),(2,3),(3,4),(0,5),(0,17),(5,6),(6,7),(7,8),(5,9),(9,10),(10,11),(11,12),\n         (9,13),(13,14),(14,15),(15,16),(13,17),(17,18),(18,19),(19,20)]\n\nfig, ax = plt.subplots()\n\ndef plot_frame(frame, edges=[], idxs=[]):\n        \n    frame[np.isnan(frame)] = 0\n    x = list(frame[...,0])\n    y = list(frame[...,1])\n    if len(idxs) == 0:\n        idxs = list(range(len(x)))\n    ax.clear()\n    ax.scatter(x, y, color='dodgerblue')\n    for i in range(len(x)):\n        ax.text(x[i], y[i], idxs[i])\n        \n    for edge in edges:\n        ax.plot([x[edge[0]], x[edge[1]]], [y[edge[0]], y[edge[1]]], color='salmon')\n    ax.set_xticks([])\n    ax.set_yticks([])\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n\ndef animate_frames(frames, edges=[], idxs=[]):\n    anim = FuncAnimation(fig, lambda frame: plot_frame(frame, edges, idxs), frames=frames, interval=100)\n    return HTML(anim.to_jshtml())","metadata":{"id":"gbVSTH9xqAFQ","outputId":"c212e69c-230c-4ded-b204-183dd28eebcb","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# Load Dataset and Inspect X & y\n# ===============================\n\n# Create dataset without augmentation for clean inspection\nds = get_tfrec_dataset(\n    TRAIN_FILENAMES,\n    batch_size=32,          # small batch for inspection\n    max_len=MAX_LEN,\n    augment=False,\n    shuffle=False\n)\n\n# Take one batch from dataset\nfor batch in ds.take(1):\n    X, y = batch  # Separate features and labels\n\n# ===============================\n# Print Basic Information\n# ===============================\n\nprint(\"X shape:\", X.shape)  # (batch_size, time_steps, features)\nprint(\"y shape:\", y.shape)  # (batch_size, num_classes)\n\nprint(\"\\nData type of X:\", X.dtype)\nprint(\"Data type of y:\", y.dtype)\n\n# ===============================\n# Inspect One Sample\n# ===============================\n\nsample_X = X[0]\nsample_y = y[0]\n\nprint(\"\\nSingle sample shape:\", sample_X.shape)\nprint(\"Single label shape:\", sample_y.shape)\n\nprint(\"\\nNumber of time steps:\", sample_X.shape[0])\nprint(\"Number of features per frame:\", sample_X.shape[1])\n\nprint(\"\\nClass index of first sample:\", tf.argmax(sample_y).numpy())\n\n# ===============================\n# Check Value Statistics\n# ===============================\n\nprint(\"\\nX min value:\", tf.reduce_min(X).numpy())\nprint(\"X max value:\", tf.reduce_max(X).numpy())\nprint(\"X mean value:\", tf.reduce_mean(X).numpy())","metadata":{"id":"at4Io1dNtSLh","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Train / Validation Split using TFRecord files\n# ============================================\n\nfrom sklearn.model_selection import train_test_split\n\n# Split TFRecord files (file-level split)\ntrain_files, valid_files = train_test_split(\n    TRAIN_FILENAMES,\n    test_size=0.2,       # 20% validation\n    random_state=42,\n    shuffle=True\n)\n\nprint(\"Number of training TFRecords:\", len(train_files))\nprint(\"Number of validation TFRecords:\", len(valid_files))\n\n# ============================================\n# Build Training Dataset\n# ============================================\n\ntrain_ds = get_tfrec_dataset(\n    train_files,\n    batch_size=128,\n    max_len=MAX_LEN,\n    augment=True,        # Enable augmentation for training\n    shuffle=2048,\n    repeat=True\n)\n\n# ============================================\n# Build Validation Dataset\n# ============================================\n\nvalid_ds = get_tfrec_dataset(\n    valid_files,\n    batch_size=128,\n    max_len=MAX_LEN,\n    augment=False,       # No augmentation for validation\n    shuffle=False,\n    repeat=False\n)\n\n# ============================================\n# Inspect One Batch\n# ============================================\n\nfor X_batch, y_batch in train_ds.take(1):\n    print(\"\\nTrain batch shape:\", X_batch.shape)\n    print(\"Train label shape:\", y_batch.shape)\n    break\n\nfor X_val, y_val in valid_ds.take(1):\n    print(\"\\nValidation batch shape:\", X_val.shape)\n    print(\"Validation label shape:\", y_val.shape)\n    break# ============================================\n# Train / Validation Split using TFRecord files\n# ============================================\n\nfrom sklearn.model_selection import train_test_split\n\n# Split TFRecord files (file-level split)\ntrain_files, valid_files = train_test_split(\n    TRAIN_FILENAMES,\n    test_size=0.2,       # 20% validation\n    random_state=42,\n    shuffle=True\n)\n\nprint(\"Number of training TFRecords:\", len(train_files))\nprint(\"Number of validation TFRecords:\", len(valid_files))\n\n# ============================================\n# Build Training Dataset\n# ============================================\n\ntrain_ds = get_tfrec_dataset(\n    train_files,\n    batch_size=128,\n    max_len=MAX_LEN,\n    augment=True,        # Enable augmentation for training\n    shuffle=2048,\n    repeat=True\n)\n\n# ============================================\n# Build Validation Dataset\n# ============================================\n\nvalid_ds = get_tfrec_dataset(\n    valid_files,\n    batch_size=128,\n    max_len=MAX_LEN,\n    augment=False,       # No augmentation for validation\n    shuffle=False,\n    repeat=False\n)\n\n# ============================================\n# Inspect One Batch\n# ============================================\n\nfor X_batch, y_batch in train_ds.take(1):\n    print(\"\\nTrain batch shape:\", X_batch.shape)\n    print(\"Train label shape:\", y_batch.shape)\n    break\n\nfor X_val, y_val in valid_ds.take(1):\n    print(\"\\nValidation batch shape:\", X_val.shape)\n    print(\"Validation label shape:\", y_val.shape)\n    break","metadata":{"id":"pRnjnF6Cj9nY","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Save Correct Label Mapping (100% Safe)\n# ==========================================\n\nimport json\nimport pandas as pd\nimport tensorflow as tf\n\n# Load CSV (already loaded usually)\ntrain_df = pd.read_csv('/kaggle/input/asl-signs/train.csv')\n\n# Use the SAME ordering assumption used in TFRecord creation\nclass_names = sorted(train_df['sign'].unique())\n\n# Build id -> label mapping\nid_to_label = {i: label for i, label in enumerate(class_names)}\n\n# Optional safety check (confirm label range inside TFRecords)\nraw_ds = tf.data.TFRecordDataset(TRAIN_FILENAMES, compression_type='GZIP')\nraw_ds = raw_ds.map(decode_tfrec)\n\nlabel_ids = set()\nfor sample in raw_ds.take(2000):   # check first 2000 samples فقط\n    label_ids.add(int(sample['sign'].numpy()))\n\nassert min(label_ids) == 0\nassert max(label_ids) == len(class_names) - 1\n\n# Save JSON\nwith open(\"labels.json\", \"w\") as f:\n    json.dump(id_to_label, f, indent=4)\n\nprint(\"labels.json saved successfully and verified.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =====================================================\n# Pure Strong BiLSTM Model (No Attention)\n# =====================================================\n\ndef build_model(input_dim=CHANNELS, num_classes=NUM_CLASSES):\n\n    inputs = tf.keras.Input(shape=(None, input_dim))\n\n    # Ignore padding\n    x = tf.keras.layers.Masking(mask_value=PAD)(inputs)\n\n    # -------------------------\n    # BiLSTM Block 1\n    # -------------------------\n    x1 = tf.keras.layers.Bidirectional(\n        tf.keras.layers.LSTM(\n            256,\n            return_sequences=True,\n            dropout=0.3\n        )\n    )(x)\n\n    x1 = tf.keras.layers.LayerNormalization()(x1)\n\n    # -------------------------\n    # BiLSTM Block 2\n    # -------------------------\n    x2 = tf.keras.layers.Bidirectional(\n        tf.keras.layers.LSTM(\n            256,\n            return_sequences=True,\n            dropout=0.3\n        )\n    )(x1)\n\n    x2 = tf.keras.layers.LayerNormalization()(x2)\n\n    # Residual connection\n    x = tf.keras.layers.Add()([x1, x2])\n\n    # -------------------------\n    # Final LSTM (sequence → vector)\n    # -------------------------\n    x = tf.keras.layers.Bidirectional(\n        tf.keras.layers.LSTM(\n            128,\n            return_sequences=False,\n            dropout=0.3\n        )\n    )(x)\n\n    # -------------------------\n    # Classification Head\n    # -------------------------\n    x = tf.keras.layers.Dense(256, activation='relu')(x)\n    x = tf.keras.layers.Dropout(0.4)(x)\n\n    outputs = tf.keras.layers.Dense(num_classes, activation='softmax')(x)\n\n    model = tf.keras.Model(inputs, outputs)\n\n    return model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow_addons as tfa","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with STRATEGY.scope():\n\n    optimizer = tf.keras.optimizers.Adam(\n        learning_rate=3e-4,\n        clipnorm=1.0\n    )\n\n    model = build_model()\n\n    model.compile(\n        optimizer=optimizer,\n        loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),\n        metrics=[\"accuracy\"]\n    )\n\nmodel.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Callbacks (Stable for long training)\n# ==========================================\n\ncallbacks_list = [\n\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=20,\n        restore_best_weights=True,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.5,\n        patience=4,\n        min_lr=1e-6,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ModelCheckpoint(\n        'best_asl_model.keras',\n        monitor='val_loss',\n        save_best_only=True,\n        verbose=1\n    )\n]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 30\nBATCH_SIZE = 64 * N_REPLICAS\n\nTOTAL_TRAIN_SAMPLES = count_data_items(train_files)\nTOTAL_VAL_SAMPLES = count_data_items(valid_files)\n\nsteps_per_epoch = TOTAL_TRAIN_SAMPLES // BATCH_SIZE\nvalidation_steps = TOTAL_VAL_SAMPLES // BATCH_SIZE\n\nprint(\"Steps per epoch:\", steps_per_epoch)\nprint(\"Validation steps:\", validation_steps)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Start Training\n# ==========================================\n\nhistory = model.fit(\n    train_ds,\n    validation_data=valid_ds,\n    epochs=EPOCHS,\n    steps_per_epoch=steps_per_epoch,\n    validation_steps=validation_steps,\n    callbacks=callbacks_list,\n    verbose=1\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nEvaluating on Validation Set...\")\nloss, acc = model.evaluate(valid_ds, steps=validation_steps)\nprint(f\"Validation Accuracy: {acc*100:.2f}%\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Training Summary\n# ==========================================\n\nprint(\"Best Training Accuracy:\", max(history.history['accuracy']))\nprint(\"Best Validation Accuracy:\", max(history.history['val_accuracy']))\n\nbest_epoch = history.history['val_accuracy'].index(max(history.history['val_accuracy'])) + 1\nprint(\"Best Epoch:\", best_epoch)\n\nprint(\"\\nFinal Training Loss:\", history.history['loss'][-1])\nprint(\"Final Validation Loss:\", history.history['val_loss'][-1])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Training Curves\n# ==========================================\n\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(14,5))\n\n# Accuracy\nplt.subplot(1,2,1)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.title('Model Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend()\n\n# Loss\nplt.subplot(1,2,2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title('Model Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Model Evaluation\n# ==========================================\n\nval_loss, val_acc = model.evaluate(valid_ds, steps=validation_steps)\n\nprint(f\"Validation Accuracy: {val_acc*100:.2f}%\")\nprint(f\"Validation Loss: {val_loss:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Get Predictions\n# ==========================================\n\nimport numpy as np\n\ny_true = []\ny_pred = []\n\nfor X_batch, y_batch in valid_ds.take(validation_steps):\n    \n    preds = model.predict(X_batch, verbose=0)\n    \n    y_true.extend(np.argmax(y_batch.numpy(), axis=1))\n    y_pred.extend(np.argmax(preds, axis=1))\n\ny_true = np.array(y_true)\ny_pred = np.array(y_pred)\n\nprint(\"Total evaluated samples:\", len(y_true))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Confusion Matrix\n# ==========================================\n\nfrom sklearn.metrics import confusion_matrix\nimport seaborn as sns\n\ncm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(12,10))\nsns.heatmap(cm, cmap='Blues')\nplt.title(\"Confusion Matrix\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Classification Report\n# ==========================================\n\nfrom sklearn.metrics import classification_report\n\nreport = classification_report(y_true, y_pred)\nprint(report)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Build Class Mapping (Correct Order)\n# ==========================================\n\nunique_signs = sorted(train_df['sign'].unique())\n\nprint(\"Number of classes:\", len(unique_signs))\n\nclass_names = unique_signs\n\n# Create label_map (name → id)\nlabel_map = {label: idx for idx, label in enumerate(class_names)}\n\n# Reverse map (id → name)\nid_to_label = {v: k for k, v in label_map.items()}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"report = classification_report(y_true, y_pred, target_names=class_names)\nprint(report)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report\nimport json\n\nreport_dict = classification_report(\n    y_true,\n    y_pred,\n    target_names=class_names,\n    output_dict=True\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n\nsummary_metrics = {\n    \"overall_accuracy\": float(accuracy_score(y_true, y_pred)),\n    \"macro_precision\": float(precision_score(y_true, y_pred, average=\"macro\")),\n    \"macro_recall\": float(recall_score(y_true, y_pred, average=\"macro\")),\n    \"macro_f1\": float(f1_score(y_true, y_pred, average=\"macro\")),\n    \"total_parameters\": int(model.count_params()),\n    \"num_classes\": int(NUM_CLASSES)\n}\nfull_report = {\n    \"summary\": summary_metrics,\n    \"per_class_metrics\": report_dict\n}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with open(\"asl_model_evaluation_report.json\", \"w\") as f:\n    json.dump(full_report, f, indent=4)\n\nprint(\"Evaluation report saved as asl_model_evaluation_report.json\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Precision / Recall / F1 Visualization\n# ==========================================\n\nfrom sklearn.metrics import precision_score, recall_score, f1_score\n\nprecision = precision_score(y_true, y_pred, average='macro')\nrecall = recall_score(y_true, y_pred, average='macro')\nf1 = f1_score(y_true, y_pred, average='macro')\n\nmetrics = ['Precision', 'Recall', 'F1-score']\nvalues = [precision, recall, f1]\n\nplt.figure(figsize=(6,4))\nplt.bar(metrics, values)\nplt.title(\"Macro Metrics\")\nplt.ylim(0,1)\nplt.show()\n\nprint(\"Precision:\", precision)\nprint(\"Recall:\", recall)\nprint(\"F1-score:\", f1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Most Confused Classes\n# ==========================================\n\nmisclassified = y_true != y_pred\n\nprint(\"Total Misclassified Samples:\", np.sum(misclassified))\nprint(\"Error Rate:\", np.mean(misclassified))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Model Complexity\n# ==========================================\n\nmodel.summary()\n\ntotal_params = model.count_params()\nprint(\"Total Parameters:\", total_params)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# Prediction Distribution\n# ==========================================\n\nplt.figure(figsize=(8,4))\nplt.hist(y_pred, bins=50)\nplt.title(\"Prediction Distribution\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save full model in TensorFlow format\nmodel.save(\"asl_model_saved\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert trained model to TFLite\nconverter = tf.lite.TFLiteConverter.from_keras_model(model)\n\n# Important for LSTM / Attention layers\nconverter.target_spec.supported_ops = [\n    tf.lite.OpsSet.TFLITE_BUILTINS,\n    tf.lite.OpsSet.SELECT_TF_OPS\n]\nconverter._experimental_lower_tensor_list_ops = False\n\n# Convert\ntflite_model = converter.convert()\n\n# Save file\nwith open(\"asl_model.tflite\", \"wb\") as f:\n    f.write(tflite_model)\n\nprint(\"Model saved as asl_model.tflite\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}