{"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":"none","dataSources":[{"sourceId":59156,"databundleVersionId":13874099,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Import necessary libraries\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\nfrom tqdm.auto import tqdm\nfrom matplotlib.animation import FuncAnimation\nfrom IPython.display import HTML","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:42.587397Z","iopub.execute_input":"2025-11-25T15:13:42.587852Z","iopub.status.idle":"2025-11-25T15:13:43.093185Z","shell.execute_reply.started":"2025-11-25T15:13:42.587816Z","shell.execute_reply":"2025-11-25T15:13:43.091932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set some display options for pandas for better readability\npd.set_option('display.max_columns', 100)\nsns.set_style('whitegrid')\n\n# Define the path to your data\nDATA_PATH = '/kaggle/input/MABe-mouse-behavior-detection/'\n\n# Load the metadata files\nprint(\"Loading train.csv (metadata)...\")\ndf_train_meta = pd.read_csv(DATA_PATH + 'train.csv')\n\nprint(\"Loading test.csv (metadata)...\")\ndf_test_meta = pd.read_csv(DATA_PATH + 'test.csv')\n\nprint(\"\\n--- Train Metadata ---\")\nprint(f\"Shape: {df_train_meta.shape}\")\ndisplay(df_train_meta.head())\n\nprint(\"\\n--- Test Metadata ---\")\nprint(f\"Shape: {df_test_meta.shape}\")\ndisplay(df_test_meta.head())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.095256Z","iopub.execute_input":"2025-11-25T15:13:43.095757Z","iopub.status.idle":"2025-11-25T15:13:43.240788Z","shell.execute_reply.started":"2025-11-25T15:13:43.095722Z","shell.execute_reply":"2025-11-25T15:13:43.239701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select the first video from the metadata as our sample\nsample_video_meta = df_train_meta.iloc[0]\nsample_lab_id = sample_video_meta['lab_id']\nsample_video_id = sample_video_meta['video_id']\n\nprint(f\"Loading sample video...\\n  Lab ID: {sample_lab_id}\\n  Video ID: {sample_video_id}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.241757Z","iopub.execute_input":"2025-11-25T15:13:43.242013Z","iopub.status.idle":"2025-11-25T15:13:43.249247Z","shell.execute_reply.started":"2025-11-25T15:13:43.241991Z","shell.execute_reply":"2025-11-25T15:13:43.248136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Construct the file paths using the lab and video IDs\ntracking_path = os.path.join(DATA_PATH, 'train_tracking', sample_lab_id, f'{sample_video_id}.parquet')\nannotation_path = os.path.join(DATA_PATH, 'train_annotation', sample_lab_id, f'{sample_video_id}.parquet')\n\n# Load the actual data from the parquet files\ndf_tracking_sample = pd.read_parquet(tracking_path)\ndf_annot_sample = pd.read_parquet(annotation_path)\n\nprint(\"\\n--- Sample Tracking Data ---\")\nprint(f\"Shape: {df_tracking_sample.shape}\")\nprint(\"Info:\")\ndf_tracking_sample.info()\nprint(\"\\nFirst 5 rows:\")\ndisplay(df_tracking_sample.head())\n\nprint(\"\\n\\n--- Sample Annotation Data ---\")\nprint(f\"Shape: {df_annot_sample.shape}\")\nprint(\"Info:\")\ndf_annot_sample.info()\nprint(\"\\nFirst 5 rows:\")\ndisplay(df_annot_sample.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.251323Z","iopub.execute_input":"2025-11-25T15:13:43.251753Z","iopub.status.idle":"2025-11-25T15:13:43.460973Z","shell.execute_reply.started":"2025-11-25T15:13:43.251720Z","shell.execute_reply":"2025-11-25T15:13:43.460076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. See what bodyparts are available\nunique_bodyparts = df_tracking_sample['bodypart'].unique()\nprint(f\"Unique bodyparts tracked: {unique_bodyparts}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.461857Z","iopub.execute_input":"2025-11-25T15:13:43.462182Z","iopub.status.idle":"2025-11-25T15:13:43.552380Z","shell.execute_reply.started":"2025-11-25T15:13:43.462152Z","shell.execute_reply":"2025-11-25T15:13:43.551181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Pivot the table to get a \"wide\" format\n# We want one row per video_frame, and columns for each mouse's bodypart's x and y coordinates.\n\nprint(\"Pivoting data from long to wide format...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.553282Z","iopub.execute_input":"2025-11-25T15:13:43.553618Z","iopub.status.idle":"2025-11-25T15:13:43.558853Z","shell.execute_reply.started":"2025-11-25T15:13:43.553594Z","shell.execute_reply":"2025-11-25T15:13:43.557937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a pivot for the 'x' coordinates\npivot_x = df_tracking_sample.pivot(\n    index='video_frame', \n    columns=['mouse_id', 'bodypart'], \n    values='x'\n)\n# Rename columns for clarity, e.g., (1, 'nose') -> 'mouse1_nose_x'\npivot_x.columns = [f\"mouse{m}_{bp}_x\" for m, bp in pivot_x.columns]\n\n\n# Create a pivot for the 'y' coordinates\npivot_y = df_tracking_sample.pivot(\n    index='video_frame', \n    columns=['mouse_id', 'bodypart'], \n    values='y'\n)\n# Rename columns for clarity\npivot_y.columns = [f\"mouse{m}_{bp}_y\" for m, bp in pivot_y.columns]\n\n\n# 3. Merge the x and y pivots into a single DataFrame\ndf_wide_sample = pd.concat([pivot_x, pivot_y], axis=1)\n\n# Sort columns alphabetically for consistent order\ndf_wide_sample = df_wide_sample.sort_index(axis=1)\n\n\nprint(\"Pivoting complete.\\n\")\nprint(\"--- Reshaped Wide DataFrame ---\")\nprint(f\"Shape: {df_wide_sample.shape}\")\ndisplay(df_wide_sample.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:43.559976Z","iopub.execute_input":"2025-11-25T15:13:43.560271Z","iopub.status.idle":"2025-11-25T15:13:44.335846Z","shell.execute_reply.started":"2025-11-25T15:13:43.560248Z","shell.execute_reply":"2025-11-25T15:13:44.334555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- BƯỚC 4: GÁN NHÃN HÀNH VI (MERGE LABELS) ---\n# Tạo một cột 'label' mới, mặc định là 'other' (không có hành vi gì đặc biệt)\ndf_wide_sample['label'] = 'other'\n\n# Duyệt qua từng dòng trong bảng ghi chú (annotation) để gán nhãn\n# Logic: Nếu hành vi diễn ra từ frame 10 đến frame 20, ta gán nhãn đó cho các dòng 10-20\nfor _, row in df_annot_sample.iterrows():\n    start = row['start_frame']\n    stop = row['stop_frame']\n    action = row['action']\n    \n    # Chỉ gán nhãn nếu frame nằm trong phạm vi dữ liệu của chúng ta\n    # (Dùng .loc để gán giá trị cho các dòng từ start đến stop)\n    if stop <= df_wide_sample.index.max():\n        df_wide_sample.loc[start:stop, 'label'] = action\n\nprint(\"Đã gán nhãn xong!\")\nprint(\"Phân phối các hành vi trong dữ liệu mẫu:\")\nprint(df_wide_sample['label'].value_counts())\n\n# --- BƯỚC 5: XỬ LÝ DỮ LIỆU THIẾU (MISSING VALUES) ---\n# Như bạn thấy trong output cũ, có rất nhiều giá trị NaN (do camera bị che khuất)\n# Model Machine Learning không hiểu NaN, nên ta phải điền số vào.\n# Cách đơn giản nhất (MVP): Điền số 0\ndf_wide_sample = df_wide_sample.fillna(0)\n\nprint(\"\\nĐã xử lý NaN. Dữ liệu sẵn sàng để train!\")\ndisplay(df_wide_sample.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:44.336874Z","iopub.execute_input":"2025-11-25T15:13:44.337133Z","iopub.status.idle":"2025-11-25T15:13:44.540463Z","shell.execute_reply.started":"2025-11-25T15:13:44.337113Z","shell.execute_reply":"2025-11-25T15:13:44.539362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import classification_report, accuracy_score\n\n# --- 1. CHUẨN BỊ DỮ LIỆU ---\n# X: Dữ liệu đầu vào (Bỏ cột label đi)\nX = df_wide_sample.drop(columns=['label'])\n# y: Nhãn cần dự đoán\ny = df_wide_sample['label']\n\n# --- 2. MÃ HÓA NHÃN (ENCODING) ---\n# Biến đổi chữ thành số (Ví dụ: 'attack' -> 0, 'investigation' -> 1...)\nle = LabelEncoder()\ny_encoded = le.fit_transform(y)\n\nprint(\"Các hành vi tìm thấy:\", le.classes_)\nprint(f\"Dữ liệu X shape: {X.shape}, y shape: {y_encoded.shape}\")\n\n# --- BƯỚC: FEATURE ENGINEERING (TẠO ĐẶC TRƯNG MỚI) ---\n# Tính khoảng cách giữa Mouse 1 và Mouse 2 (Dựa trên Body Center)\n# Công thức Pythagoras: căn bậc hai của ((x1-x2)^2 + (y1-y2)^2)\n\ndx = df_wide_sample['mouse1_body_center_x'] - df_wide_sample['mouse2_body_center_x']\ndy = df_wide_sample['mouse1_body_center_y'] - df_wide_sample['mouse2_body_center_y']\n\n# Tạo cột mới 'distance_m1_m2'\ndf_wide_sample['distance_m1_m2'] = np.sqrt(dx**2 + dy**2)\n\nprint(\"Đã tạo thêm đặc trưng: distance_m1_m2\")\n\n# --- QUAN TRỌNG: CẬP NHẬT LẠI X ---\n# Bây giờ X sẽ bao gồm cả cột khoảng cách mới này\nX = df_wide_sample.drop(columns=['label'])\n\n# ... (Sau đó bạn chạy lại đoạn code chia tập train/test và huấn luyện như cũ)\n\n# --- 3. CHIA TẬP TRAIN / TEST ---\n# Dành 80% để học, 20% để kiểm tra\n# stratify=y_encoded giúp đảm bảo tập test vẫn có đủ các loại hành vi (kể cả hành vi hiếm)\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y_encoded, test_size=0.2, random_state=42, stratify=y_encoded\n)\n\n# --- 4. HUẤN LUYỆN MODEL ---\nprint(\"\\nĐang huấn luyện mô hình Random Forest (vui lòng chờ)...\")\n# Dùng Random Forest vì nó mạnh và không cần chuẩn hóa dữ liệu phức tạp\nmodel = RandomForestClassifier(n_estimators=50, random_state=42)\nmodel.fit(X_train, y_train)\n\n# --- 5. ĐÁNH GIÁ KẾT QUẢ ---\nprint(\"Đã học xong! Đang dự đoán trên tập Test...\")\ny_pred = model.predict(X_test)\n\n# In kết quả\nacc = accuracy_score(y_test, y_pred)\nprint(f\"\\n>>> ĐỘ CHÍNH XÁC (Accuracy): {acc:.2%}\")\nprint(\"\\nChi tiết từng hành vi:\")\nprint(classification_report(y_test, y_pred, target_names=le.classes_))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:44.541961Z","iopub.execute_input":"2025-11-25T15:13:44.542403Z","iopub.status.idle":"2025-11-25T15:13:55.096415Z","shell.execute_reply.started":"2025-11-25T15:13:44.542358Z","shell.execute_reply":"2025-11-25T15:13:55.094971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Lấy thông tin độ quan trọng từ model đã train\nimportances = model.feature_importances_\nfeature_names = X.columns\n\n# Tạo bảng dữ liệu để vẽ\nfeature_imp_df = pd.DataFrame({\n    'Feature': feature_names,\n    'Importance': importances\n}).sort_values(by='Importance', ascending=False)\n\n# Vẽ biểu đồ Top 10\nplt.figure(figsize=(10, 6))\nsns.barplot(x='Importance', y='Feature', data=feature_imp_df.head(15), palette='viridis')\nplt.title('TOP 15 ĐẶC TRƯNG QUAN TRỌNG NHẤT')\nplt.xlabel('Điểm quan trọng (Càng cao càng tốt)')\nplt.ylabel('Tên đặc trưng')\nplt.show()\n\n# In ra giá trị cụ thể của 'distance_m1_m2'\ndist_rank = feature_imp_df[feature_imp_df['Feature'] == 'distance_m1_m2']\nprint(\"Thứ hạng của Distance:\")\nprint(dist_rank)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:13:55.099953Z","iopub.execute_input":"2025-11-25T15:13:55.100270Z","iopub.status.idle":"2025-11-25T15:13:55.695368Z","shell.execute_reply.started":"2025-11-25T15:13:55.100247Z","shell.execute_reply":"2025-11-25T15:13:55.694228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ... (Phần hàm get_video_data giữ nguyên như cũ) ...\n\n# --- CHẠY THỬ NGHIỆM CHIẾN THUẬT \"TRAIN 1 VIDEO - TEST 1 VIDEO\" ---\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.metrics import accuracy_score\nimport pandas as pd\nimport numpy as np\n\nprint(\"1. Đang load Video TRAIN (Index 0)...\")\ndf_train = get_video_data(0)\n\nprint(\"2. Đang load Video TEST (Index 1)...\")\n# Thử lấy video index khác nếu video 1 bị lỗi, ví dụ index 2\ndf_test = get_video_data(1) \n\nif df_train is not None and df_test is not None:\n    # Chuẩn bị dữ liệu\n    X_train = df_train.drop(columns=['label'])\n    y_train_raw = df_train['label']\n    \n    X_test = df_test.drop(columns=['label'])\n    y_test_raw = df_test['label']\n    \n    # --- QUAN TRỌNG: BƯỚC ĐỒNG BỘ HÓA CỘT (FIX LỖI VALUE ERROR) ---\n    # Ép X_test phải có cấu trúc cột Y HỆT X_train\n    # - Cột nào thiếu (ví dụ headpiece): Tự động thêm vào và điền 0\n    # - Cột nào thừa (ví dụ neck): Tự động vứt bỏ\n    print(\"-> Đang đồng bộ hóa các cột dữ liệu...\")\n    feature_columns = X_train.columns\n    X_test = X_test.reindex(columns=feature_columns, fill_value=0)\n    \n    print(f\"Kích thước sau đồng bộ -> Train: {X_train.shape}, Test: {X_test.shape}\")\n    \n    # Mã hóa nhãn (Label Encoding)\n    le = LabelEncoder()\n    # Gộp nhãn cả 2 tập để học hết các tên hành vi\n    all_labels = list(y_train_raw.unique()) + list(y_test_raw.unique())\n    le.fit(all_labels)\n    \n    y_train = le.transform(y_train_raw)\n    y_test = le.transform(y_test_raw)\n    \n    # Huấn luyện\n    print(\"\\n3. Đang huấn luyện Random Forest...\")\n    model = RandomForestClassifier(n_estimators=50, random_state=42)\n    model.fit(X_train, y_train)\n    \n    # Dự đoán\n    print(\"4. Đang dự đoán trên Video Test...\")\n    y_pred = model.predict(X_test)\n    \n    # Kết quả\n    acc = accuracy_score(y_test, y_pred)\n    print(f\"\\n>>> ĐỘ CHÍNH XÁC THỰC TẾ (Cross-Video): {acc:.2%}\")\n    \n    # Xem đặc trưng quan trọng\n    importances = pd.DataFrame({\n        'Feature': X_train.columns,\n        'Importance': model.feature_importances_\n    }).sort_values(by='Importance', ascending=False)\n    \n    print(\"\\nTop 5 đặc trưng quan trọng nhất:\")\n    print(importances.head(5))\n    \n    print(\"\\nThứ hạng của Distance (Khoảng cách):\")\n    print(importances[importances['Feature'] == 'distance_m1_m2'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:18:40.058965Z","iopub.execute_input":"2025-11-25T15:18:40.059270Z","iopub.status.idle":"2025-11-25T15:18:56.469884Z","shell.execute_reply.started":"2025-11-25T15:18:40.059250Z","shell.execute_reply":"2025-11-25T15:18:56.468830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- BƯỚC 8: FEATURE ENGINEERING & SELECTION (CAI NGHIỆN TỌA ĐỘ) ---\n\ndef calculate_features(df):\n    # 1. Tính Khoảng cách (Đã làm)\n    try:\n        dx = df['mouse1_body_center_x'] - df['mouse2_body_center_x']\n        dy = df['mouse1_body_center_y'] - df['mouse2_body_center_y']\n        df['distance'] = np.sqrt(dx**2 + dy**2)\n    except KeyError:\n        df['distance'] = 0\n        \n    # 2. Tính Vận tốc (Velocity) của Mouse 1\n    # Vận tốc = Khoảng cách di chuyển so với frame trước đó\n    # Dùng .diff() để trừ dòng hiện tại cho dòng trước\n    vx = df['mouse1_body_center_x'].diff().fillna(0)\n    vy = df['mouse1_body_center_y'].diff().fillna(0)\n    df['velocity_m1'] = np.sqrt(vx**2 + vy**2)\n    \n    # 3. Tính Vận tốc của Mouse 2 (Nếu có)\n    try:\n        vx2 = df['mouse2_body_center_x'].diff().fillna(0)\n        vy2 = df['mouse2_body_center_y'].diff().fillna(0)\n        df['velocity_m2'] = np.sqrt(vx2**2 + vy2**2)\n    except KeyError:\n        df['velocity_m2'] = 0\n        \n    return df\n\nprint(\"Đang tạo đặc trưng mới (Vận tốc, Khoảng cách)...\")\ndf_train_eng = calculate_features(df_train.copy())\ndf_test_eng = calculate_features(df_test.copy())\n\n# --- QUAN TRỌNG NHẤT: CHỌN LỌC ĐẶC TRƯNG ---\n# Chúng ta chỉ lấy đúng 3 cột này để train. Vứt hết x, y đi!\nselected_features = ['distance', 'velocity_m1', 'velocity_m2']\n\nprint(f\"Chỉ train trên các đặc trưng: {selected_features}\")\n\nX_train_new = df_train_eng[selected_features]\ny_train_new = y_train # Dùng lại nhãn đã encode ở bước trước\n\nX_test_new = df_test_eng[selected_features]\ny_test_new = y_test   # Dùng lại nhãn đã encode ở bước trước\n\n# --- TRAIN LẠI MODEL ---\nprint(\"\\nĐang train model trên dữ liệu tinh gọn...\")\nmodel_new = RandomForestClassifier(n_estimators=50, random_state=42)\nmodel_new.fit(X_train_new, y_train_new)\n\n# Dự đoán\nprint(\"Đang dự đoán...\")\ny_pred_new = model_new.predict(X_test_new)\n\n# Kết quả\nacc_new = accuracy_score(y_test_new, y_pred_new)\nprint(f\"\\n>>> ĐỘ CHÍNH XÁC MỚI: {acc_new:.2%}\")\n\n# Xem cái gì quan trọng nhất bây giờ\nimportances = pd.DataFrame({\n    'Feature': selected_features,\n    'Importance': model_new.feature_importances_\n}).sort_values(by='Importance', ascending=False)\nprint(\"\\nBảng xếp hạng độ quan trọng:\")\nprint(importances)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-25T15:20:56.829464Z","iopub.execute_input":"2025-11-25T15:20:56.829837Z","iopub.status.idle":"2025-11-25T15:21:00.308929Z","shell.execute_reply.started":"2025-11-25T15:20:56.829813Z","shell.execute_reply":"2025-11-25T15:21:00.307916Z"}},"outputs":[],"execution_count":null}]}