{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":59156,"databundleVersionId":13874099,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nfrom glob import glob\n\n# Initialize lists to store data\nall_tracking_dfs = []\nall_annotation_dfs = []\ntotal_frames = 0\ntotal_sequences = 0\ntracking_cols = set()\nannotation_cols = set()\n\n# Load CSV files\ntrain_csv = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/train.csv') if os.path.exists('/kaggle/input/MABe-mouse-behavior-detection/train.csv') else None\ntest_csv = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/test.csv') if os.path.exists('/kaggle/input/MABe-mouse-behavior-detection/test.csv') else None\nsample_submission = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/sample_submission.csv') if os.path.exists('/kaggle/input/MABe-mouse-behavior-detection/sample_submission.csv') else None\n\n# Load Parquet files\nfor dirpath, _, filenames in os.walk('/kaggle/input/MABe-mouse-behavior-detection'):\n    for filename in filenames:\n        if filename.endswith('.parquet'):\n            file_path = os.path.join(dirpath, filename)\n            try:\n                df = pd.read_parquet(file_path)\n                if 'train_tracking' in dirpath or 'test_tracking' in dirpath:\n                    all_tracking_dfs.append(df)\n                    tracking_cols.update(df.columns)\n                    total_frames += len(df)\n                    total_sequences += 1\n                elif 'train_annotation' in dirpath:\n                    all_annotation_dfs.append(df)\n                    annotation_cols.update(df.columns)\n            except Exception:\n                pass\n\nprint(\"Dataset is loaded.\")\n\n# Print insights\nprint(\"\\n=== Dataset Insights ===\")\nprint(f\"Total sequences: {total_sequences}\")\nprint(f\"Total frames: {total_frames}\")\nprint(f\"Tracking columns: {len(tracking_cols)} (e.g., {list(tracking_cols)[:5]})\")\nprint(f\"Annotation columns: {len(annotation_cols)} (e.g., {list(annotation_cols)[:5]})\")\nif train_csv is not None:\n    print(f\"Train CSV shape: {train_csv.shape}\")\nif test_csv is not None:\n    print(f\"Test CSV shape: {test_csv.shape}\")\nif sample_submission is not None:\n    print(f\"Sample submission shape: {sample_submission.shape}\")\n\n# Behavior distribution (if annotations available)\nbehavior_cols = [col for col in annotation_cols if col not in tracking_cols and col not in ['frame', 'timestamp', 'sequence_id']]\nif all_annotation_dfs and behavior_cols:\n    combined_annotations = pd.concat(all_annotation_dfs, ignore_index=True)\n    for col in behavior_cols[:2]:  # Limit to avoid clutter\n        if col in combined_annotations.columns:\n            print(f\"\\nDistribution of {col}:\\n{combined_annotations[col].value_counts(normalize=True)}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-17T08:47:45.163438Z","iopub.execute_input":"2025-10-17T08:47:45.164094Z","iopub.status.idle":"2025-10-17T08:48:53.890188Z","shell.execute_reply.started":"2025-10-17T08:47:45.164070Z","shell.execute_reply":"2025-10-17T08:48:53.889472Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Additional Insights on the given Dataset ","metadata":{}},{"cell_type":"code","source":"\nimport numpy as np\nimport pandas as pd\nimport os\nfrom glob import glob\nfrom collections import defaultdict\n\n# Set data directory\ndata_dir = '/kaggle/input/MABe-mouse-behavior-detection'\n\n# Initialize minimal storage\ncsv_info = {'train.csv': {}, 'test.csv': {}, 'sample_submission.csv': {}}\ntracking_info = defaultdict(list)\nannotation_info = defaultdict(list)\nunique_bodyparts = set()\nunique_mouse_ids = set()\nseq_lengths = []\nnum_mice_per_seq = []\nbodyparts_per_mouse_list = []\nnan_total = 0\nunique_actions = set()\naction_counts = defaultdict(int)\nevent_lengths = []\nunique_agents = set()\nunique_targets = set()\nlabeled_frames = 0\nsequence_id_map = {}  # Map video_id to file paths\n\n# Load CSVs minimally\nfor csv_file in csv_info:\n    path = os.path.join(data_dir, csv_file)\n    if os.path.exists(path):\n        df = pd.read_csv(path, nrows=5)\n        csv_info[csv_file]['shape'] = (pd.read_csv(path, usecols=[0]).shape[0], df.shape[1])\n        csv_info[csv_file]['columns'] = ', '.join(df.columns)\n    else:\n        csv_info[csv_file] = {'shape': 'N/A', 'columns': 'N/A'}\n\n# Build sequence ID map from train.csv\nif csv_info['train.csv']['shape'] != 'N/A':\n    train_df = pd.read_csv(os.path.join(data_dir, 'train.csv'), usecols=['video_id'])\n    for vid in train_df['video_id'].unique():\n        sequence_id_map[vid] = {'tracking': None, 'annotation': None}\n\n# Process tracking files\ntracking_dirs = ['train_tracking', 'test_tracking']\nfor tdir in tracking_dirs:\n    parquet_files = glob(os.path.join(data_dir, f'{tdir}/**/*.parquet'), recursive=True)\n    for file in parquet_files:\n        df = pd.read_parquet(file, columns=['video_frame', 'mouse_id', 'bodypart', 'x', 'y'])\n        unique_bodyparts.update(df['bodypart'].unique())\n        unique_mouse_ids.update(df['mouse_id'].unique())\n        seq_lengths.append(df['video_frame'].max() - df['video_frame'].min() + 1)\n        num_mice = df['mouse_id'].nunique()\n        num_mice_per_seq.append(num_mice)\n        bodyparts_per_mouse_list.append(df.groupby('mouse_id')['bodypart'].nunique().mean())\n        nan_total += df[['x', 'y']].isna().sum().sum()\n        sample = df[['x', 'y']].dropna().sample(n=min(100, len(df)), random_state=42)\n        tracking_info['x'].extend(sample['x'])\n        tracking_info['y'].extend(sample['y'])\n        # Update sequence_id_map\n        vid = os.path.basename(file).split('.')[0]\n        if vid in sequence_id_map:\n            sequence_id_map[vid]['tracking'] = file\n        del df\n\n# Process annotation files\nannotation_files = glob(os.path.join(data_dir, 'train_annotation/**/*.parquet'), recursive=True)\nfor file in annotation_files:\n    df = pd.read_parquet(file, columns=['action', 'start_frame', 'stop_frame', 'agent_id', 'target_id'])\n    unique_actions.update(df['action'].unique())\n    for act, count in df['action'].value_counts().items():\n        action_counts[act] += count\n    lengths = df['stop_frame'] - df['start_frame'] + 1\n    event_lengths.extend(lengths)\n    labeled_frames += lengths.sum()\n    unique_agents.update(df['agent_id'].unique())\n    unique_targets.update(df['target_id'].unique())\n    vid = os.path.basename(file).split('.')[0]\n    if vid in sequence_id_map:\n        sequence_id_map[vid]['annotation'] = file\n    del df\n\n# Create summary tables\ncsv_summary = {\n    'File': list(csv_info.keys()),\n    'Shape': [csv_info[f]['shape'] for f in csv_info],\n    'Columns': [csv_info[f]['columns'] for f in csv_info]\n}\ncsv_df = pd.DataFrame(csv_summary)\n\nx_stats = pd.Series(tracking_info['x']) if tracking_info['x'] else pd.Series()\ny_stats = pd.Series(tracking_info['y']) if tracking_info['y'] else pd.Series()\ntracking_summary = {\n    'Metric': ['Unique Bodyparts', 'Sequences', 'Seq Lengths (mean/std)', 'Mice per Seq (mode)', 'Bodyparts per Mouse (mean)', \n               'X Coords (min/max/mean)', 'Y Coords (min/max/mean)', 'NaNs', 'Total Frames'],\n    'Value': [f\"{len(unique_bodyparts)}/{', '.join(sorted(unique_bodyparts)[:5])}...\",\n              len(seq_lengths),\n              f\"{np.mean(seq_lengths):.2f}/{np.std(seq_lengths):.2f}\" if seq_lengths else 'N/A',\n              pd.Series(num_mice_per_seq).mode()[0] if num_mice_per_seq else 'N/A',\n              f\"{np.mean(bodyparts_per_mouse_list):.2f}\" if bodyparts_per_mouse_list else 'N/A',\n              f\"{x_stats.min():.2f}/{x_stats.max():.2f}/{x_stats.mean():.2f}\" if not x_stats.empty else 'N/A',\n              f\"{y_stats.min():.2f}/{y_stats.max():.2f}/{y_stats.mean():.2f}\" if not y_stats.empty else 'N/A',\n              nan_total,\n              sum(seq_lengths) if seq_lengths else 0]\n}\ntracking_df = pd.DataFrame(tracking_summary)\n\naction_counts_sorted = sorted(action_counts.items(), key=lambda x: x[1], reverse=True)\nannotation_summary = {\n    'Metric': ['Unique Actions', 'Top 3 Actions', 'Event Lengths (mean/std)', 'Agent/Target IDs', 'Labeled Frames', 'Labeled Proportion'],\n    'Value': [f\"{len(unique_actions)}/{', '.join(sorted(unique_actions)[:5])}...\",\n              '; '.join([f\"{act}: {cnt}\" for act, cnt in action_counts_sorted[:3]]),\n              f\"{np.mean(event_lengths):.2f}/{np.std(event_lengths):.2f}\" if event_lengths else 'N/A',\n              f\"{len(unique_agents)}/{len(unique_targets)}\",\n              labeled_frames,\n              f\"{labeled_frames / sum(seq_lengths):.4f}\" if seq_lengths and sum(seq_lengths) > 0 else '0.0000']\n}\nannotation_df = pd.DataFrame(annotation_summary)\n\n# Sample merge with robust matching\nmerge_summary = {}\nif sequence_id_map:\n    for vid, paths in list(sequence_id_map.items())[:1]:  # Try first valid pair\n        if paths['tracking'] and paths['annotation']:\n            df_ann = pd.read_parquet(paths['annotation'], columns=['action', 'start_frame', 'stop_frame'])\n            df_track = pd.read_parquet(paths['tracking'], columns=['video_frame', 'mouse_id', 'bodypart', 'x', 'y'])\n            df_frames = pd.DataFrame({'video_frame': range(df_track['video_frame'].min(), df_track['video_frame'].max() + 1)})\n            df_frames['action'] = 'other'\n            for _, row in df_ann.iterrows():\n                mask = (df_frames['video_frame'] >= row['start_frame']) & (df_frames['video_frame'] <= row['stop_frame'])\n                df_frames.loc[mask, 'action'] = row['action']\n            df_pivot = df_track.pivot(index='video_frame', columns=['mouse_id', 'bodypart'], values=['x', 'y'])\n            df_pivot.columns = ['_'.join(map(str, col)).strip() for col in df_pivot.columns.values]\n            df_merged = df_frames.merge(df_pivot.reset_index(), on='video_frame', how='left')\n            merge_summary = {\n                'Metric': ['Merged Shape', 'Unique Actions', 'Input Features'],\n                'Value': [f\"{df_merged.shape[0]}/{df_merged.shape[1]}\",\n                          ', '.join(df_merged['action'].unique()),\n                          len([col for col in df_merged.columns if col not in ['action', 'video_frame']])]\n            }\n            del df_ann, df_track, df_frames, df_pivot, df_merged\n            break\nmerge_df = pd.DataFrame(merge_summary) if merge_summary else pd.DataFrame({'Metric': ['No Merge'], 'Value': ['N/A']})\n\n# Display tables\nprint(\"\\n=== CSV Summary ===\")\nprint(csv_df.to_markdown(index=False))\nprint(\"\\n=== Tracking Summary ===\")\nprint(tracking_df.to_markdown(index=False))\nprint(\"\\n=== Annotation Summary ===\")\nprint(annotation_df.to_markdown(index=False))\nprint(\"\\n=== Sample Merge Summary ===\")\nprint(merge_df.to_markdown(index=False))\nprint(\"\\nModel Tips: Normalize coords to [0,1], use GNN/LSTM/Transformer for temporal data, handle class imbalance (e.g., oversample sniff), semi-supervised for 15% labeled data.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T09:32:08.810584Z","iopub.execute_input":"2025-10-17T09:32:08.810880Z","iopub.status.idle":"2025-10-17T09:36:15.789258Z","shell.execute_reply.started":"2025-10-17T09:32:08.810862Z","shell.execute_reply":"2025-10-17T09:36:15.788522Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MODEL IMPLEMENTATION","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nfrom glob import glob\nimport lightgbm as lgb\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\n\n# Function to prepare data for a sequence\ndef prepare_data(track_file, ann_file=None, mice=4, bodyparts=29, sample_rate=1):\n    # Read tracking data with only necessary columns to save memory\n    df_track = pd.read_parquet(track_file, columns=['video_frame', 'mouse_id', 'bodypart', 'x', 'y'])\n    # Sort by video_frame to ensure order\n    df_track = df_track.sort_values('video_frame')\n    # Subsample if sample_rate > 1 (but default to 1 for accuracy on frames)\n    if sample_rate > 1:\n        df_track = df_track.iloc[::sample_rate]\n    # Use pivot_table to handle any potential duplicates safely\n    df_pivot = df_track.pivot_table(index='video_frame', columns=['mouse_id', 'bodypart'], values=['x', 'y'], aggfunc='first')\n    df_pivot.columns = ['_'.join(map(str, col)).strip() for col in df_pivot.columns]\n    df_pivot = df_pivot.fillna(0)  # Handle missing keypoints\n    \n    # Feature engineering: distances and velocities for key bodyparts\n    feature_cols = []\n    key_parts = ['nose', 'tail_base', 'body_center']\n    for i in range(1, mice + 1):\n        for j in range(i + 1, mice + 1):\n            for part in key_parts:\n                col_ix = f'x_{i}_{part}'\n                col_iy = f'y_{i}_{part}'\n                col_jx = f'x_{j}_{part}'\n                col_jy = f'y_{j}_{part}'\n                dist_col = f'dist_{i}_{j}_{part}'\n                if all(col in df_pivot.columns for col in [col_ix, col_iy, col_jx, col_jy]):\n                    dx = df_pivot[col_ix] - df_pivot[col_jx]\n                    dy = df_pivot[col_iy] - df_pivot[col_jy]\n                    df_pivot[dist_col] = np.nan_to_num(np.sqrt(np.maximum(0.0, dx**2 + dy**2)), nan=0.0)\n                else:\n                    df_pivot[dist_col] = 0.0\n                feature_cols.append(dist_col)\n        # Velocity for each mouse (using nose)\n        col_nx = f'x_{i}_nose'\n        col_ny = f'y_{i}_nose'\n        vel_col = f'vel_{i}_nose'\n        if col_nx in df_pivot.columns and col_ny in df_pivot.columns:\n            dx = df_pivot[col_nx].diff()\n            dy = df_pivot[col_ny].diff()\n            df_pivot[vel_col] = np.nan_to_num(np.sqrt(np.maximum(0.0, dx**2 + dy**2)), nan=0.0)\n        else:\n            df_pivot[vel_col] = 0.0\n        feature_cols.append(vel_col)\n    \n    X = df_pivot[feature_cols].copy()\n    \n    if ann_file:\n        df_ann = pd.read_parquet(ann_file)\n        frames = df_pivot.index  # Use subsampled frames if applicable\n        df_labels = pd.DataFrame({'video_frame': frames}).set_index('video_frame')\n        df_labels['action'] = 'other'\n        for _, row in df_ann.iterrows():\n            mask = (df_labels.index >= row['start_frame']) & (df_labels.index <= row['stop_frame'])\n            df_labels.loc[mask, 'action'] = row['action']\n        y = df_labels['action']\n        return X, y\n    return X, None\n\n# Load metadata\ntrain_csv = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/train.csv')\ntest_csv = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/test.csv')\n\n# Label encoder for actions\nle = LabelEncoder()\n\n# Train on a small subset to manage memory\nnum_train_seq = 5\nX_train_list = []\ny_train_list = []\nfor idx in range(num_train_seq):\n    print(f\"Processing training sequence {idx + 1}/{num_train_seq}\")\n    row = train_csv.iloc[idx]\n    vid = row['video_id']\n    track_files = glob(f'/kaggle/input/MABe-mouse-behavior-detection/train_tracking/**/{vid}.parquet', recursive=True)\n    ann_files = glob(f'/kaggle/input/MABe-mouse-behavior-detection/train_annotation/**/{vid}.parquet', recursive=True)\n    track_path = track_files[0] if track_files else None\n    ann_path = ann_files[0] if ann_files else None\n    if track_path and ann_path:\n        X, y = prepare_data(track_path, ann_path)\n        X_train_list.append(X)\n        y_train_list.append(y)\n        del X, y  # Free memory\n\nif X_train_list:\n    X_train = pd.concat(X_train_list, ignore_index=True)\n    y_train = pd.concat(y_train_list, ignore_index=True)\n    y_train_enc = le.fit_transform(y_train)\n    \n    # Compute class weights to handle imbalance\n    class_weights = compute_class_weight('balanced', classes=np.unique(y_train_enc), y=y_train_enc)\n    weights = np.array([class_weights[label] for label in y_train_enc])\n    \n    # LightGBM params for multi-class classification\n    params = {\n        'objective': 'multiclass',\n        'num_class': len(le.classes_),\n        'metric': 'multi_logloss',\n        'learning_rate': 0.1,\n        'num_leaves': 31,\n        'verbose': -1\n    }\n    train_set = lgb.Dataset(X_train, label=y_train_enc, weight=weights)\n    model = lgb.train(params, train_set, num_boost_round=200)\n    del X_train, y_train, train_set  # Free memory\nelse:\n    print(\"No training data found. Using dummy predictions.\")\n    model = None\n\n# Predict on test\nsubmission = []\nrow_id = 0\nfor _, row in test_csv.iterrows():\n    print(f\"Processing test sequence for video_id: {row['video_id']}\")\n    vid = row['video_id']\n    track_files = glob(f'/kaggle/input/MABe-mouse-behavior-detection/test_tracking/**/{vid}.parquet', recursive=True)\n    track_path = track_files[0] if track_files else None\n    if track_path:\n        X_test, _ = prepare_data(track_path)\n        if model is not None:\n            preds = model.predict(X_test)\n            pred_labels = le.inverse_transform(np.argmax(preds, axis=1))\n        else:\n            pred_labels = np.full(len(X_test), 'other')\n        df_pred = pd.DataFrame({'video_frame': X_test.index, 'action': pred_labels})\n        df_pred['shifted'] = df_pred['action'].shift(1)\n        df_pred['group'] = (df_pred['action'] != df_pred['shifted']).cumsum()\n        events = df_pred[df_pred['action'] != 'other'].groupby(['action', 'group']).agg(\n            start_frame=('video_frame', 'min'),\n            stop_frame=('video_frame', 'max')\n        ).reset_index()  # Keep 'action' in columns\n        events['video_id'] = vid\n        events['agent_id'] = 1  # Dummy; improve with clustering\n        events['target_id'] = 2\n        events['row_id'] = range(row_id, row_id + len(events))\n        row_id += len(events)\n        submission.append(events)\n        del X_test, df_pred, events  # Free memory\n\nif submission:\n    submission_df = pd.concat(submission, ignore_index=True)\n    submission_df = submission_df[['row_id', 'video_id', 'agent_id', 'target_id', 'action', 'start_frame', 'stop_frame']]\nelse:\n    submission_df = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/sample_submission.csv')\n\nsubmission_df.to_csv('/kaggle/working/submission.csv', index=False)\nprint(\"Submission created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T10:43:22.782962Z","iopub.execute_input":"2025-10-17T10:43:22.783528Z","iopub.status.idle":"2025-10-17T10:43:30.100942Z","shell.execute_reply.started":"2025-10-17T10:43:22.783501Z","shell.execute_reply":"2025-10-17T10:43:30.100255Z"}},"outputs":[],"execution_count":null}]}