{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":242954653,"sourceType":"kernelVersion"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#score 0.63 - 0.65, good luck for you. Upload if you like it \nimport os\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom tensorflow.keras.models import Sequential, load_model\nfrom tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\nfrom tensorflow.keras.layers import GRU, Bidirectional, GlobalAveragePooling1D\nfrom tensorflow.keras.optimizers import AdamW\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping\nimport tensorflow as tf\nimport polars as pl\nimport kaggle_evaluation.cmi_inference_server\nprint(\"Imports loaded\")\n\n# Load the dataset\nprint(\"Loading dataset...\")\ndf = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\nprint(f\"Loaded {len(df)} rows.\")\n\nlabel_encoder = LabelEncoder()\ndf['gesture'] = label_encoder.fit_transform(df['gesture'].astype(str))\n\n# Save class names for inference\nnp.save('gesture_classes.npy', label_encoder.classes_)\n\n# Print class label mapping\nprint(\"Gesture label mapping:\")\nfor idx, label in enumerate(label_encoder.classes_):\n    print(f\"  {idx}: {label}\")\n\nprint(\"Checking for IMU-only sequences...\")\n\ndef check_for_imu_only_seqs():\n    # Identify thermopile and TOF columns\n    thermal_tof_cols = [col for col in df.columns if col.startswith('thm_') or col.startswith('tof_')]\n    \n    # Group by sequence and check if all thm_/tof_ values are null\n    imu_only_flags = df[thermal_tof_cols].isna().groupby(df['sequence_id']).all().all(axis=1)\n    \n    # Report statistics\n    total_sequences = df['sequence_id'].nunique()\n    imu_only_count = imu_only_flags.sum()\n    imu_only_pct = (imu_only_count / total_sequences) * 100\n    \n    print(f\"Total sequences: {total_sequences}\")\n    print(f\"IMU-only sequences (all thm_/tof_ null): {imu_only_count} ({imu_only_pct:.1f}%)\")\n\ncheck_for_imu_only_seqs()\n\nexcluded_cols = {\n    'gesture', 'sequence_type', 'behavior', 'orientation',  # train-only\n    'row_id', 'subject', 'phase',  # metadata\n    'sequence_id', 'sequence_counter'  # identifiers\n}\n\n# Setting this true makes model ignore thermal and tof data\ndrop_thermal_and_tof = True\n\nif drop_thermal_and_tof:\n    thermal_tof_cols = [col for col in df.columns if col.startswith('thm_') or col.startswith('tof_')]\n    excluded_cols.update(thermal_tof_cols)\n    print(f\"Ignoring {len(thermal_tof_cols)} thermopile / time-of-flight columns.\")\n\n# Select numeric feature columns\nfeature_cols = [col for col in df.columns if col not in excluded_cols]\nprint(f\"Using {len(feature_cols)} numeric feature columns for training:\")\nprint(feature_cols)\n\n# Check for NaNs in selected feature columns\nnan_counts = df[feature_cols].isna().sum()\ntotal_nans = nan_counts.sum()\nprint(f\"\\nTotal missing values in feature columns: {total_nans}\")\nif total_nans > 0:\n    print(\"Columns with missing values:\")\n    print(nan_counts[nan_counts > 0])\nelse:\n    print(\"No missing values found in feature columns.\")\n\ndef preprocess_sequence(df_sequence: pd.DataFrame, feature_cols: list) -> np.ndarray:\n    data = df_sequence[feature_cols].copy()\n    data = data.ffill().bfill().fillna(0)\n    scaled = StandardScaler().fit_transform(data)\n    return scaled\n\n# Build sequences\nsequence_ids = df['sequence_id'].unique()\nsequences = df.groupby('sequence_id')\n\nX = []\nseq_lengths = []\n\nprint(\"Building sequences...\")\nfor i, (seq_id, seq) in enumerate(sequences):\n    if i % 500 == 0:\n        print(f\"Processing sequence {i}...\")\n    processed = preprocess_sequence(seq, feature_cols)\n    X.append(processed)\n    seq_lengths.append(processed.shape[0])\n\nmax_len_perentile = 90\n\n# Report sequence length stats\nminlen = min(seq_lengths)\navglen = int(np.mean(seq_lengths))\npad_len_to_use = int(np.percentile(seq_lengths, max_len_perentile))  \nprint(f\"Sequence length stats - Min: {minlen}, Avg: {avglen}, {max_len_perentile}th percentile: {pad_len_to_use}\")\nprint(f\"Padding / truncating all sequences to fixed length {pad_len_to_use}...\")\n\nnp.save(\"sequence_maxlen.npy\", pad_len_to_use)  # Save for inference\n\n# Pad/truncate to fixed length\nX = pad_sequences(X, maxlen=pad_len_to_use, dtype='float32', padding='post', truncating='post')\n\n# Use groupby to get the first gesture per sequence (already integer-encoded)\ny = df.groupby('sequence_id')['gesture'].first().values\n\nprint(\"Integer labels:\", y[:4])\n\n# Convert to one-hot vectors\nnum_classes = len(np.unique(y))\ny = to_categorical(y, num_classes=num_classes)\n\nprint(\"After one-hot encoding:\", y[:4])\n\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\n\n# Build CNN-GRU model\nprint(\"Building CNN-GRU model...\")\nmodel = Sequential([\n    # Multi-scale CNN blocks for feature extraction\n    Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])),\n    BatchNormalization(),\n    Conv1D(64, kernel_size=3, activation='relu'),\n    MaxPooling1D(2),\n    Dropout(0.3),\n    \n    Conv1D(128, kernel_size=5, activation='relu'),\n    BatchNormalization(),\n    Conv1D(128, kernel_size=5, activation='relu'),\n    MaxPooling1D(2),\n    Dropout(0.3),\n    \n    Conv1D(256, kernel_size=7, activation='relu'),\n    BatchNormalization(),\n    Conv1D(256, kernel_size=7, activation='relu'),\n    MaxPooling1D(2),\n    Dropout(0.4),\n    \n    # Bidirectional GRU for temporal dependencies (more efficient than LSTM)\n    Bidirectional(GRU(128, return_sequences=True, dropout=0.3, recurrent_dropout=0.3)),\n    Bidirectional(GRU(64, return_sequences=False, dropout=0.3, recurrent_dropout=0.3)),\n    \n    # Dense layers for classification\n    Dense(512, activation='relu'),\n    BatchNormalization(),\n    Dropout(0.5),\n    Dense(256, activation='relu'),\n    BatchNormalization(),\n    Dropout(0.4),\n    Dense(128, activation='relu'),\n    Dropout(0.3),\n    \n    Dense(num_classes, activation='softmax')\n])\n\n# Compile model with categorical crossentropy loss (for one-hot labels)\nmodel.compile(\n    optimizer=AdamW(learning_rate=0.001, weight_decay=1e-4), \n    loss='categorical_crossentropy', \n    metrics=['accuracy']\n)\nmodel.summary()\n\n# Define callbacks for better training\ncallbacks = [\n    ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-7, verbose=1),\n    EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True, verbose=1)\n]\n\n# Train model using explicitly split validation set (80/20 held out)\nprint(\"Training CNN-GRU model...\")\nhistory = model.fit(\n    X_train, y_train, \n    epochs=60, \n    batch_size=128, \n    validation_data=(X_val, y_val),\n    callbacks=callbacks,\n    verbose=1\n)\nmodel.save(\"gesture_cnn_gru_model.h5\")\nprint(\"Training complete.\")\n\nfrom cmi_2025_metric_copy_for_import import CompetitionMetric\n\n# Get predicted labels for the validation set\nprint(\"Predicting on validation set...\")\ny_val_pred_probs = model.predict(X_val, verbose=0)\ny_val_pred = np.argmax(y_val_pred_probs, axis=1)\ny_val_true = np.argmax(y_val, axis=1)\n\n# Map integer labels back to gesture strings\ngesture_classes = np.load(\"gesture_classes.npy\", allow_pickle=True)\nval_pred_labels = pd.Series(y_val_pred).map(lambda i: gesture_classes[i])\nval_true_labels = pd.Series(y_val_true).map(lambda i: gesture_classes[i])\n\n# Build DataFrames for the metric\nval_submission = pd.DataFrame({'gesture': val_pred_labels})\nval_solution = pd.DataFrame({'gesture': val_true_labels})\n\n# Run competition metric\nmetric = CompetitionMetric()\nscore = metric.calculate_hierarchical_f1(val_solution, val_submission)\nprint(f\"Estimated leaderboard (val) score: {score:.4f}\")\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_seq = sequence.to_pandas()\n    processed = preprocess_sequence(df_seq, feature_cols)\n    maxlen = int(np.load(\"sequence_maxlen.npy\"))  # ensure consistent shape\n    padded = pad_sequences([processed], maxlen=maxlen, dtype='float32', padding='post', truncating='post')\n    model = load_model(\"gesture_cnn_gru_model.h5\")\n    prediction = model.predict(padded, verbose=0)\n    predicted_index = np.argmax(prediction, axis=1)[0]\n    gesture_classes = np.load(\"gesture_classes.npy\", allow_pickle=True)\n    return gesture_classes[predicted_index]\n\n# Launch inference server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )\n\n# Manual test (only runs outside Kaggle gateway)\nif not os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(\"\\nRunning manual test...\")\n    test_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\n    sample_seq_id = test_df['sequence_id'].unique()[0]\n    test_seq = test_df[test_df['sequence_id'] == sample_seq_id]\n    prediction = predict(pl.DataFrame(test_seq), None)\n    print(f\"Manual prediction result for sequence_id {sample_seq_id}: {prediction}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T09:17:14.681039Z","iopub.execute_input":"2025-06-01T09:17:14.681315Z","iopub.status.idle":"2025-06-01T09:19:13.647367Z","shell.execute_reply.started":"2025-06-01T09:17:14.681292Z","shell.execute_reply":"2025-06-01T09:19:13.646486Z"}},"outputs":[],"execution_count":null}]}