{"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":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":4533,"sourceType":"modelInstanceVersion","modelInstanceId":3325,"modelId":986},{"sourceId":4534,"sourceType":"modelInstanceVersion","modelInstanceId":3326,"modelId":986},{"sourceId":4535,"sourceType":"modelInstanceVersion","modelInstanceId":3327,"modelId":986},{"sourceId":4537,"sourceType":"modelInstanceVersion","modelInstanceId":3329,"modelId":986}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import kagglehub\n\n# Download latest version\npath1 = kagglehub.model_download(\"metaresearch/dinov2/pyTorch/small\")\npath2 = kagglehub.model_download(\"metaresearch/dinov2/pyTorch/base\")\npath3 = kagglehub.model_download(\"metaresearch/dinov2/pyTorch/large\")\nprint(\"Path to Dino small files:\", path1)\nprint(\"Path to Dino base files:\", path2)\nprint(\"Path to Dino large files:\", path3)\n ","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":true,"_kg_hide-input":true,"scrolled":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:35.241413Z","iopub.execute_input":"2025-12-16T15:30:35.241722Z","iopub.status.idle":"2025-12-16T15:30:36.609335Z","shell.execute_reply.started":"2025-12-16T15:30:35.241682Z","shell.execute_reply":"2025-12-16T15:30:36.607866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kagglehub\n\n# Download latest version\npath4 = kagglehub.model_download(\"metaresearch/dinov2/pyTorch/giant\")\n\nprint(\"Path to model files:\", path4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:36.611726Z","iopub.execute_input":"2025-12-16T15:30:36.612310Z","iopub.status.idle":"2025-12-16T15:30:36.870066Z","shell.execute_reply.started":"2025-12-16T15:30:36.612281Z","shell.execute_reply":"2025-12-16T15:30:36.869018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !pip download -d packages transformers\n!pip install -q -U transformers --no-index -f /kaggle/input/metadino-v3-convnext/pytorch/default/3/packages\n# !pip install -q -U transformers xgboost opencv-python 'numpy<2.0' 'pandas>=2.2'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:36.871111Z","iopub.execute_input":"2025-12-16T15:30:36.871414Z","iopub.status.idle":"2025-12-16T15:30:40.908612Z","shell.execute_reply.started":"2025-12-16T15:30:36.871386Z","shell.execute_reply":"2025-12-16T15:30:40.905515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import huggingface_hub\nhuggingface_hub.login()","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:40.910489Z","iopub.execute_input":"2025-12-16T15:30:40.911019Z","iopub.status.idle":"2025-12-16T15:30:41.546458Z","shell.execute_reply.started":"2025-12-16T15:30:40.910936Z","shell.execute_reply":"2025-12-16T15:30:41.545022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport random\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom tqdm.auto import tqdm\n\nimport torch\nimport warnings\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nwarnings.simplefilter(action='ignore', category=FutureWarning)\nprint(f\"PyTorch: {torch.__version__}\")\nprint(f\"Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}\")","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:41.547538Z","iopub.execute_input":"2025-12-16T15:30:41.547879Z","iopub.status.idle":"2025-12-16T15:30:48.284924Z","shell.execute_reply.started":"2025-12-16T15:30:41.547845Z","shell.execute_reply":"2025-12-16T15:30:48.283853Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Explore Data","metadata":{}},{"cell_type":"code","source":"PATH_DATA = '/kaggle/input/csiro-biomass'\nPATH_TRAIN_CSV = os.path.join(PATH_DATA, 'train.csv')\nPATH_TRAIN_IMG = os.path.join(PATH_DATA, 'train')\nPATH_TEST_IMG = os.path.join(PATH_DATA, 'test')\n\ndf = pd.read_csv(PATH_TRAIN_CSV)\nprint(f\"Dataset size: {df.shape}\")\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:48.286376Z","iopub.execute_input":"2025-12-16T15:30:48.287006Z","iopub.status.idle":"2025-12-16T15:30:48.341828Z","shell.execute_reply.started":"2025-12-16T15:30:48.286970Z","shell.execute_reply":"2025-12-16T15:30:48.340843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_COLS = [c for c in df.columns if c not in ['image_id', 'Image']]\nprint(f\"Target columns: {TARGET_COLS}\")\nprint(f\"Number of targets: {len(TARGET_COLS)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:48.344643Z","iopub.execute_input":"2025-12-16T15:30:48.344975Z","iopub.status.idle":"2025-12-16T15:30:48.351687Z","shell.execute_reply.started":"2025-12-16T15:30:48.344928Z","shell.execute_reply":"2025-12-16T15:30:48.350076Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Plot target distribution","metadata":{}},{"cell_type":"code","source":"# Exclude non-numeric or identifier columns from histogram plotting\ncols_to_plot = [col for col in TARGET_COLS if col not in ['sample_id', 'image_path', 'State', 'target_name']]\n\nfor col in cols_to_plot:\n    plt.figure(figsize=(8, 3)) # Create a new figure for each histogram\n    plt.hist(df[col].dropna(), bins=50, edgecolor='black', alpha=0.7)\n    plt.xlabel(col, fontsize=12)\n    plt.ylabel('Count', fontsize=12)\n    plt.title(f'{col} Distribution', fontsize=14, fontweight='bold')\n    plt.grid(alpha=0.3)\n    plt.xticks(rotation=45, ha=\"right\") # Rotate x-axis labels\n    plt.tight_layout() # Adjust layout to prevent overlap\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:48.352703Z","iopub.execute_input":"2025-12-16T15:30:48.353258Z","iopub.status.idle":"2025-12-16T15:30:50.170014Z","shell.execute_reply.started":"2025-12-16T15:30:48.353222Z","shell.execute_reply":"2025-12-16T15:30:50.169087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols_to_plot = ['State', 'target_name']\nn_rows, n_cols = 1, len(cols_to_plot)\nfig, axes = plt.subplots(n_rows, n_cols, figsize=(4 * n_cols, 4 * n_rows))\n\n# Ensure axes is an array even for a single subplot\naxes = axes.flatten()\n\nfor ax, col in zip(axes, cols_to_plot):\n    counts = df[col].value_counts()\n    ax.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=140)\n    ax.set_title(f'Distribution of {col}', fontsize=14, fontweight='bold')\n    ax.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:50.171104Z","iopub.execute_input":"2025-12-16T15:30:50.171366Z","iopub.status.idle":"2025-12-16T15:30:50.407116Z","shell.execute_reply.started":"2025-12-16T15:30:50.171345Z","shell.execute_reply":"2025-12-16T15:30:50.405981Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\ngrouped_train_data = df.groupby('target_name')\n\nplt.figure(figsize=(12, 8))\nfor target_name, group_data in grouped_train_data:\n    sns.histplot(data=group_data, x='target', kde=True, label=target_name)\n\nplt.title('Distribution of Target for Each Target Name Class')\nplt.xlabel('Target')\nplt.ylabel('Frequency')\nplt.legend(title='Target Name')\nplt.grid(True) # Added grid here\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:50.407943Z","iopub.execute_input":"2025-12-16T15:30:50.408414Z","iopub.status.idle":"2025-12-16T15:30:51.116020Z","shell.execute_reply.started":"2025-12-16T15:30:50.408383Z","shell.execute_reply":"2025-12-16T15:30:51.114943Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Show sample images","metadata":{}},{"cell_type":"code","source":"\n\n# Colonnes à analyser\ncols = ['Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\n\ndf_tmp = df[cols].copy()\n\n# Discrétisation des colonnes continues\ndf_tmp['Sampling_Date'] = pd.to_datetime(df_tmp['Sampling_Date'], errors='coerce').dt.month\nfor c in ['Pre_GSHH_NDVI', 'Height_Ave_cm']:\n    df_tmp[c] = pd.qcut(df_tmp[c], q=4, labels=False, duplicates='drop')\n\n# Encodage des catégorielles\nfor c in ['State', 'Species']:\n    df_tmp[c] = df_tmp[c].astype('category').cat.codes\n\nn = len(cols)\nmat = np.zeros((n, n))\n\nfor i, gi in enumerate(cols):\n    for j, gj in enumerate(cols):\n        if i == j:\n            mat[i, j] = 0\n            continue\n        # distribution globale de gj\n        p_global = df_tmp[gj].value_counts(normalize=True)\n        # pour chaque modalité de gi\n        tvds = []\n        for g in df_tmp[gi].unique():\n            mask = df_tmp[gi] == g\n            p_cond = df_tmp.loc[mask, gj].value_counts(normalize=True)\n            # Total Variation Distance\n            tvd = 0.5 * sum(abs(p_global.reindex(p_cond.index, fill_value=0) - p_cond))\n            tvds.append(tvd)\n        mat[i, j] = np.mean(tvds)\n\n# Plot minimaliste noir/blanc\nplt.figure(figsize=(6, 5))\nplt.imshow(mat, cmap='Greys', interpolation='nearest')\nplt.colorbar(fraction=0.046)\nplt.xticks(range(n), cols, rotation=45, ha='right')\nplt.yticks(range(n), cols)\nplt.title(\"Mean TVD matrix (i = group, j = representation)\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:51.117215Z","iopub.execute_input":"2025-12-16T15:30:51.117578Z","iopub.status.idle":"2025-12-16T15:30:51.537400Z","shell.execute_reply.started":"2025-12-16T15:30:51.117547Z","shell.execute_reply":"2025-12-16T15:30:51.536204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\n\n# -------------------------------------------------\n# Utilities\n# -------------------------------------------------\n\ndef unique_index(mask):\n    \"\"\"\n    Each pack of 5 consecutive True → keep only the first one\n    \"\"\"\n    for i in range(len(mask) - 4):\n        if np.all(mask[i:i+5]):\n            mask[i+1:i+5] = False\n\n\ndef keep_n(mask, n):\n    \"\"\"\n    Keep only the first n True values\n    \"\"\"\n    true_idx = np.where(mask)[0]\n    if len(true_idx) > n:\n        mask[true_idx[n:]] = False\n\n\n# -------------------------------------------------\n# Main function\n# -------------------------------------------------\n\ndef show_specific_images(\n    df,\n    n_max=10,\n    path_img=PATH_DATA,\n    State=True,\n    Species=True,\n    Pre_GSHH_NDVI=True,\n    Height_Ave_cm=True,\n    Sampling_Date=True\n):\n    df_sorted = df.copy()\n    N = len(df_sorted)\n\n    # -------------------------------------------------\n    # Force Sampling_Date to datetime (ROBUST)\n    # -------------------------------------------------\n    df_sorted['Sampling_Date'] = pd.to_datetime(\n        df_sorted['Sampling_Date'],\n        errors='coerce'\n    )\n\n    # -------------------------------------------------\n    # State\n    # -------------------------------------------------\n    mask_State = np.ones(N, dtype=bool)\n    if State is not True:\n        mask_State = (df_sorted['State'] == State).values\n\n    # -------------------------------------------------\n    # Species\n    # -------------------------------------------------\n    mask_Species = np.ones(N, dtype=bool)\n    if Species is not True:\n        mask_Species = np.zeros(N, dtype=bool)\n        for sp in Species:\n            mask_Species |= (df_sorted['Species'] == sp).values\n\n    # -------------------------------------------------\n    # NDVI\n    # -------------------------------------------------\n    mask_Pre_GSHH_NDVI = np.ones(N, dtype=bool)\n    if Pre_GSHH_NDVI is not True:\n        mask_Pre_GSHH_NDVI = (\n            (df_sorted['Pre_GSHH_NDVI'] >= Pre_GSHH_NDVI[0]) &\n            (df_sorted['Pre_GSHH_NDVI'] <= Pre_GSHH_NDVI[1])\n        ).values\n\n    # -------------------------------------------------\n    # Height\n    # -------------------------------------------------\n    mask_Height_Ave_cm = np.ones(N, dtype=bool)\n    if Height_Ave_cm is not True:\n        mask_Height_Ave_cm = (\n            (df_sorted['Height_Ave_cm'] >= Height_Ave_cm[0]) &\n            (df_sorted['Height_Ave_cm'] <= Height_Ave_cm[1])\n        ).values\n\n    # -------------------------------------------------\n    # Sampling Date (MONTH)\n    # -------------------------------------------------\n    mask_Sampling_Date = np.ones(N, dtype=bool)\n    if Sampling_Date is not True:\n        mask_Sampling_Date = np.zeros(N, dtype=bool)\n        for m in Sampling_Date:\n            m = int(m)  # '1' → 1\n            mask_Sampling_Date |= (\n                df_sorted['Sampling_Date'].dt.month == m\n            ).values\n\n    # -------------------------------------------------\n    # Combine masks\n    # -------------------------------------------------\n    indices_to_show = (\n        mask_State &\n        mask_Species &\n        mask_Pre_GSHH_NDVI &\n        mask_Height_Ave_cm &\n        mask_Sampling_Date\n    )\n\n    # Enforce uniqueness\n    unique_index(indices_to_show)\n\n    total = indices_to_show.sum()\n    if total == 0:\n        print(\"No picture to show\")\n        print(\"Available months:\")\n        print(df_sorted['Sampling_Date'].dt.month.value_counts().sort_index())\n        return\n\n    n = min(total, n_max)\n    keep_n(indices_to_show, n)\n\n    print(f\"n_max = {n_max} → showing {n}/{total} images ({100*n/total:.1f}%)\")\n\n    df_to_show = df_sorted.loc[indices_to_show]\n\n    # -------------------------------------------------\n    # Plot\n    # -------------------------------------------------\n    n_cols = 4\n    n_rows = (n + n_cols - 1) // n_cols\n\n    fig, axes = plt.subplots(\n        n_rows,\n        n_cols,\n        figsize=(16, 4 * n_rows)\n    )\n    axes = axes.flatten()\n\n    for i, (_, row) in enumerate(df_to_show.iterrows()):\n        img_path = os.path.join(path_img, row['image_path'])\n        if os.path.exists(img_path):\n            img = Image.open(img_path).convert('RGB')\n            axes[i].imshow(img)\n            axes[i].set_title(\n                f\"ID: {row['sample_id']}\\n\"\n                f\"{row['State']} – {row['Species']}\\n\"\n                f\"NDVI: {row['Pre_GSHH_NDVI']} | \"\n                f\"H: {row['Height_Ave_cm']} cm\\n\"\n                f\"{row['Sampling_Date'].date()}\",\n                fontsize=9\n            )\n        axes[i].axis('off')\n\n    # Remove unused axes\n    for j in range(i + 1, len(axes)):\n        fig.delaxes(axes[j])\n\n    plt.tight_layout()\n    plt.show()\n\n    # -------------------------------------------------\n    # Debug info\n    # -------------------------------------------------\n    print(State, 'State')\n    print(Species, 'Species')\n    print(Pre_GSHH_NDVI, 'Pre_GSHH_NDVI')\n    print(Height_Ave_cm, 'Height_Ave_cm')\n    print(Sampling_Date, '>> months to show')\n\n\n\nimg_path_test=PATH_DATA+\"/train/\"+\"ID1011485656.jpg\"\n\nshow_specific_images(df,n_max=20, path_img=PATH_DATA,State='NSW', Pre_GSHH_NDVI=True)#Species=['Phalaris','Ryegrass_Clover','Clover']\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:30:51.538399Z","iopub.execute_input":"2025-12-16T15:30:51.538655Z","iopub.status.idle":"2025-12-16T15:31:00.926700Z","shell.execute_reply.started":"2025-12-16T15:30:51.538636Z","shell.execute_reply":"2025-12-16T15:31:00.925044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_images(df_sample, n=12, path_img=PATH_DATA):\n    \"\"\"Displays a linear sampling of images sorted by target value.\"\"\"\n\n    # Sort the DataFrame by the 'target' column\n    df_sorted = df_sample.sort_values(by='target').reset_index(drop=True)\n\n    # Perform linear sampling\n    indices_to_show = np.linspace(0, len(df_sorted) - 1, n, dtype=int)\n    df_to_show = df_sorted.iloc[indices_to_show]\n\n    # Determine the number of rows and columns for subplots\n    n_cols = 3  # You can adjust this number\n    n_rows = (n + n_cols - 1) // n_cols\n\n    fig, axes = plt.subplots(n_rows, n_cols, figsize=(16, 4 * n_rows))\n    axes = axes.flatten()\n\n    # Remove unused subplots if any\n    for i in range(n, len(axes)):\n        fig.delaxes(axes[i])\n\n    for i, (idx, row) in enumerate(df_to_show.iterrows()):\n        # Use image_path directly (includes train/ID....jpg)\n        img_path = os.path.join(path_img, row['image_path'])\n\n        if os.path.exists(img_path):\n            img = Image.open(img_path).convert('RGB')\n            axes[i].imshow(img)\n            # Include the target value in the title\n            title = f\"ID: {row['sample_id']}\\nTarget: {row['target']:.2f}\"\n            axes[i].set_title(title, fontsize=10)\n        axes[i].axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\n# Example usage: Show 12 images linearly sampled based on target value\n# show_images(df, n=12)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:31:00.928365Z","iopub.execute_input":"2025-12-16T15:31:00.928848Z","iopub.status.idle":"2025-12-16T15:31:00.942656Z","shell.execute_reply.started":"2025-12-16T15:31:00.928805Z","shell.execute_reply":"2025-12-16T15:31:00.941174Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data augmentation","metadata":{}},{"cell_type":"code","source":"df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:31:00.943770Z","iopub.execute_input":"2025-12-16T15:31:00.944149Z","iopub.status.idle":"2025-12-16T15:31:00.976354Z","shell.execute_reply.started":"2025-12-16T15:31:00.944122Z","shell.execute_reply":"2025-12-16T15:31:00.975098Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature extraction","metadata":{}},{"cell_type":"code","source":"from transformers import pipeline\n\n# Define the feature extraction pipeline\nfeature_extractor = pipeline(\n    # model=\"facebook/dinov3-convnext-tiny-pretrain-lvd1689m\",\n    model=path4,\n    task=\"image-feature-extraction\",\n    device=0 if torch.cuda.is_available() else -1 # Use GPU if available\n)\n\ndef extract_image_features_pipeline(image_path, feature_extractor, path_data):\n    \"\"\"\n    Splits an image into two 1000x1000 parts and extracts features from each using a pipeline.\n    \"\"\"\n    full_image_path = os.path.join(path_data, image_path)\n    img = Image.open(full_image_path)#.convert('RGB')\n\n    # Split the image into 8 500x500 parts\n    width, height = img.size\n\n    img11 = img.crop((0,0 ,width//4 ,height//2))\n    img12 = img.crop((width//4,0 ,width//2 ,height//2 ))\n    img13 = img.crop((0,height//2 ,width//4 ,height ))\n    img14 = img.crop((width//4,height//2 ,width//2 ,height ))\n\n    img21 = img.crop((width//2,0 ,(3*width)//4 ,height//2 ))\n    img22 = img.crop(((3*width)//4,0 ,width ,height//2 ))\n    img23 = img.crop((width//2,height//2 ,(3*width)//4 ,height ))\n    img24 = img.crop(((3*width)//4,height//2 ,width ,height ))\n\n    \n    # The pipeline expects PIL Images or paths, use 'inputs'\n    extracted_features = feature_extractor(inputs=[img11,img12,img13,img14, img21,img22,img23,img24], pool=True)\n\n    # separate in the two images : \n    extracted_features_img1=[np.mean([extracted_features[i][0][j] for i in range(4)]) for j in range(len(extracted_features[i][0]))]\n    extracted_features_img2=[np.mean([extracted_features[i][0][j] for i in range(4,8)]) for j in range(len(extracted_features[i][0]))]\n    # Concatenate features from both parts\n    \n    combined_features = extracted_features_img1+extracted_features_img2\n    return combined_features\n\nprint(\"Image feature extraction function defined using pipeline.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:31:00.977171Z","iopub.execute_input":"2025-12-16T15:31:00.977477Z","iopub.status.idle":"2025-12-16T15:32:07.351000Z","shell.execute_reply.started":"2025-12-16T15:31:00.977453Z","shell.execute_reply":"2025-12-16T15:32:07.350058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_unique=df[df[\"target_name\"]==\"GDM_g\"]\n\n# Extract features for all images\nimage_features_unique = {}\nfor index, row in tqdm(df_unique.iterrows(), total=df_unique.shape[0]):\n    image_path = row['image_path']\n    # Use the pipeline function\n    features = extract_image_features_pipeline(image_path, feature_extractor, PATH_DATA)\n    # Use the sample_id as the key for the features\n    image_features_unique[row['sample_id']] = features\n\n\n\nl_target=[\"Dry_Clover_g\",\"Dry_Dead_g\",\"Dry_Green_g\",\"Dry_Total_g\",\"GDM_g\"]\n\nkeys=image_features_unique.keys()\nimage_features=image_features_unique.copy()\nfor key in keys:\n    values=image_features_unique[key]\n    for target_name in l_target:\n        new_key=key[:-5]+target_name\n        image_features[new_key]=values\n\n\nprint(\"Image feature extraction complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-16T15:32:07.351916Z","iopub.execute_input":"2025-12-16T15:32:07.352271Z","execution_failed":"2025-12-16T18:48:02.088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert the image features dictionary to a DataFrame\nimage_features_df = pd.DataFrame.from_dict(image_features, orient='index')\nimage_features_df.index.name = 'sample_id'\nimage_features_df.columns = [f'img_feature_{i}' for i in range(image_features_df.shape[1])]\n\n# One-hot encode the 'target_name' column\ndf_one_hot = pd.get_dummies(df['target_name'], prefix='target_name').astype(int) # Convert to int\n\n# Merge the image features and one-hot encoded features with the original DataFrame\ndf_combined = df.merge(image_features_df, on='sample_id', how='left')\ndf_combined = df_combined.merge(df_one_hot, left_index=True, right_index=True)\n\n# unique (5 >> 1)\ndf_combined_unique=df_combined[[df_combined[\"sample_id\"][i][-5:]==\"GDM_g\" for i in range(len(df_combined[\"sample_id\"]))]]\nimage_features_df_unique=image_features_df[[image_features_df.index[i][-5:]==\"GDM_g\" for i in range(len(image_features_df.index))]]\n\n\nprint(\"Image features and one-hot encoded target names combined with the original DataFrame.\")\ndisplay(df_combined.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# Flatten all image features into a single Series\nimage_features_cols = [col for col in df_combined.columns if col.startswith(\"img_feature_\")]\nall_features_flat = image_features_df[image_features_cols].values.flatten()\nall_features_series = pd.Series(all_features_flat)\n\n# Plot a single histogram for all features\nplt.figure(figsize=(10, 6))\nsns.histplot(all_features_series, kde=True, bins=100) # Adjust bins as needed\nplt.title('Distribution of All Extracted Image Feature Values')\nplt.xlabel('Feature Value')\nplt.ylabel('Frequency')\nplt.grid(True, alpha=0.3)\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.088Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Pseudo target","metadata":{}},{"cell_type":"markdown","source":"Dans l'ordre, on teste la qualité des prédictions sur le train : \n- saison\n- espece\n- etat\n- cm\n- nvdi\n","metadata":{}},{"cell_type":"markdown","source":"## StratifiedGroupKFold when prediciting Seasons *(groups=states)*","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split,StratifiedGroupKFold,StratifiedKFold\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n# Authors: The scikit-learn developers\n# SPDX-License-Identifier: BSD-3-Clause\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Patch\n\ndef permutation_sort(l):\n    temp=list(np.copy(l))\n    index=[]\n    for i in range(len(temp)):\n        i=temp.index(min(temp))\n        temp[i]=np.inf\n        index.append(i)\n    return index\n\npermutation_sort([1,2,1,3,2,3,2,1])","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# 0. CRÉATION DU DATAFRAME DE CLASSIFICATION\n# ============================================================================\ndf_temp = df_unique.copy()\ndf_temp['date_parsed'] = pd.to_datetime(df_temp['Sampling_Date'], format='%Y/%m/%d', errors='coerce')\ndf_temp['month'] = df_temp['date_parsed'].dt.month\n\ndef get_european_season(month):\n    if month in [12, 1, 2]:\n        return 'Hiver'\n    elif month in [3, 4, 5]:\n        return 'Printemps'\n    elif month in [6, 7, 8]:\n        return 'Été'\n    elif month in [9, 10, 11]:\n        return 'Automne'\n    else:\n        return None\n\ndf_temp['saison'] = df_temp['month'].apply(get_european_season)\nseason_mapping = {'Hiver':0, 'Printemps':1, 'Été':2, 'Automne':3}\ndf_temp['saison_encoded'] = df_temp['saison'].map(season_mapping)\n\ndf_classification = df_combined_unique.copy()\ndf_classification['month'] = df_temp['month']\ndf_classification['saison'] = df_temp['saison']\ndf_classification['saison_encoded'] = df_temp['saison_encoded']\n\nprint(f\"✓ df_classification créé avec {len(df_classification)} échantillons\")\n\nreverse_mapping = {0:'Hiver',1:'Printemps',2:'Été',3:'Automne'}\n\n# Features et labels\nimage_feature_cols = [col for col in df_combined.columns if col.startswith('img_feature_')]\nX = df_classification[image_feature_cols].values\ny = df_classification['saison_encoded'].values\ngroups = df_classification['State'].values\ninput_dim = X.shape[1]\n\n\n\nsfgk=StratifiedGroupKFold(n_splits=3)#,shuffle=True)\ngroups=list(df_combined_unique['Species'])  # modif 1\ndef state_to_number(state):\n    l=['NSW', 'Vic', 'Tas', 'WA']\n    l=pd.unique(df_temp['Species']) # modif 2\n    for i in range(len(l)):\n        if state==l[i]:\n            return i\n\n\ngroups=[state_to_number(state) for state in groups]\nprint(groups)\n\ngroups=[groups[permutation_sort(groups)[i]] for i in range(len(groups))]\ny =[y[permutation_sort(y)[i]] for i in range(len(y))]\nfor i,(train_index,test_index) in enumerate(sfgk.split(X,y,groups)):\n    print(len(train_index),len(test_index))\n\ndef visualize_groups(classes, groups, name):\n    # Visualize dataset groups\n    fig, ax = plt.subplots()\n    ax.scatter(\n        range(len(groups)),\n        [0.5] * len(groups),\n        c=groups,\n        marker=\"_\",\n        lw=50,\n        cmap=cmap_data,\n    )\n    ax.scatter(\n        range(len(groups)),\n        [3.5] * len(groups),\n        c=classes,\n        marker=\"_\",\n        lw=50,\n        cmap=cmap_data,\n    )\n    ax.set(\n        ylim=[-1, 5],\n        yticks=[0.5, 3.5],\n        yticklabels=[\"Data\\ngroup\", \"Data\\nclass\"],\n        xlabel=\"Sample index\",\n    )\n\nrng = np.random.RandomState(1338)\ncmap_data = plt.cm.Paired\ncmap_cv = plt.cm.coolwarm\nn_splits = 4\n\nvisualize_groups(y, groups, \"no groups\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_cv_indices(cv, X, y, group, ax, n_splits, lw=10):\n    \"\"\"Create a sample plot for indices of a cross-validation object.\"\"\"\n    use_groups = \"Group\" in type(cv).__name__\n    groups = group if use_groups else None\n    # Generate the training/testing visualizations for each CV split\n    for ii, (tr, tt) in enumerate(cv.split(X=X, y=y, groups=groups)):\n        # Fill in indices with the training/test groups\n        indices = np.array([np.nan] * len(X))\n        indices[tt] = 1\n        indices[tr] = 0\n\n        # Visualize the results\n        ax.scatter(\n            range(len(indices)),\n            [ii + 0.5] * len(indices),\n            c=indices,\n            marker=\"_\",\n            lw=lw,\n            cmap=cmap_cv,\n            vmin=-0.2,\n            vmax=1.2,\n        )\n\n    # Plot the data classes and groups at the end\n    ax.scatter(\n        range(len(X)), [ii + 1.5] * len(X), c=y, marker=\"_\", lw=lw, cmap=cmap_data\n    )\n\n    ax.scatter(\n        range(len(X)), [ii + 2.5] * len(X), c=group, marker=\"_\", lw=lw, cmap=cmap_data\n    )\n\n    # Formatting\n    yticklabels = list(range(n_splits)) + [\"class\", \"group\"]\n    ax.set(\n        yticks=np.arange(n_splits + 2) + 0.5,\n        yticklabels=yticklabels,\n        xlabel=\"Sample index\",\n        ylabel=\"CV iteration\",\n        ylim=[n_splits + 2.2, -0.2],\n        xlim=[0, len(y)],\n    )\n    ax.set_title(\"{}\".format(type(cv).__name__), fontsize=15)\n    return ax","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR CLASSIFICATION MULTI-CLASSES: 4 SAISONS (AUTO-ADAPTATIF)\n# ============================================================================\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport numpy as np\nimport pandas as pd\n\n\n\n# Création d'une instance de StratifiedGroupKFold avec le nombre de splits souhaité\nn_splits = 3\ncv = StratifiedGroupKFold(n_splits=n_splits)\n\n# On suppose que 'y' et 'groups' existent déjà et ont la bonne longueur\nX_dummy = [0] * len(y)  # X n'est pas utilisé ici mais doit avoir la même longueur que y\n\n# Création de la figure\nfig, ax = plt.subplots(figsize=(12, 4))\n\n# Définition des colormaps pour le plotting\ncmap_cv = plt.cm.coolwarm\ncmap_data = plt.cm.tab10\n\n# Appel de la fonction pour tracer les indices\nplot_cv_indices(cv, X_dummy, y, groups, ax, n_splits)\n\nplt.show()\n\n\nprint(\"=\"*80)\nprint(\"CLASSIFICATION MULTI-CLASSES: 4 SAISONS EUROPÉENNES (StratifiedGroupKFold)\")\nprint(\"=\"*80)\n\n\n\n# ============================================================================\n# 1. DÉFINITION DU MODÈLE MLP\n# ============================================================================\nclass SeasonClassifier(nn.Module):\n    def __init__(self, input_dim, hidden_dims=[256,128,64], num_classes=4, dropout=0.3):\n        super().__init__()\n        layers = []\n        prev_dim = input_dim\n        for h in hidden_dims:\n            layers.append(nn.Linear(prev_dim, h))\n            layers.append(nn.ReLU())\n            layers.append(nn.BatchNorm1d(h))\n            layers.append(nn.Dropout(dropout))\n            prev_dim = h\n        layers.append(nn.Linear(prev_dim, num_classes))\n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\n# ============================================================================\n# 2. STRATIFIEDGROUPKFOLD 3-FOLDS AVEC ÉVOLUTION PAR EPOCH\n# ============================================================================\n\nfold_accuracies = []\nfold_train_acc_hist = []\nfold_test_acc_hist = []\ny = np.array(y, dtype=int)\ngroups = np.array(groups, dtype=int)\n\nfor fold, (train_idx, test_idx) in enumerate(cv.split(X, y, groups=groups), 1):\n    train_idx = np.array(train_idx)\n    test_idx = np.array(test_idx)\n\n    X_train, X_test = X[train_idx], X[test_idx]\n    y_train, y_test = y[train_idx], y[test_idx]  # plus d'erreur\n\n    \n    X_train_tensor = torch.FloatTensor(X_train)\n    y_train_tensor = torch.LongTensor(y_train)\n    X_test_tensor = torch.FloatTensor(X_test)\n    y_test_tensor = torch.LongTensor(y_test)\n    \n    train_loader = DataLoader(TensorDataset(X_train_tensor, y_train_tensor), batch_size=32, shuffle=True)\n    test_loader = DataLoader(TensorDataset(X_test_tensor, y_test_tensor), batch_size=32, shuffle=False)\n    \n    model = SeasonClassifier(input_dim=input_dim).to(device)\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5)\n    \n    train_acc_hist = []\n    test_acc_hist = []\n    best_acc = 0\n    best_state = None\n    \n    for epoch in range(50):\n        model.train()\n        correct_train = 0\n        total_train = 0\n        for batch_X, batch_y in train_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            optimizer.zero_grad()\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            loss.backward()\n            optimizer.step()\n            \n            _, preds = torch.max(outputs, 1)\n            correct_train += (preds == batch_y).sum().item()\n            total_train += batch_y.size(0)\n        \n        train_acc = correct_train / total_train\n        train_acc_hist.append(train_acc)\n        \n        # Evaluation test\n        model.eval()\n        all_preds, all_labels = [], []\n        correct_test = 0\n        total_test = 0\n        with torch.no_grad():\n            for batch_X, batch_y in test_loader:\n                batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n                outputs = model(batch_X)\n                _, preds = torch.max(outputs, 1)\n                \n                all_preds.extend(preds.cpu().numpy())\n                all_labels.extend(batch_y.cpu().numpy())\n                \n                correct_test += (preds == batch_y).sum().item()\n                total_test += batch_y.size(0)\n        test_acc = correct_test / total_test\n        test_acc_hist.append(test_acc)\n        \n        scheduler.step(loss)\n        \n        if test_acc > best_acc:\n            best_acc = test_acc\n            best_state = model.state_dict().copy()\n    \n    model.load_state_dict(best_state)\n    fold_train_acc_hist.append(train_acc_hist)\n    fold_test_acc_hist.append(test_acc_hist)\n    fold_accuracies.append(best_acc)\n    \n    print(f\"\\nFold {fold} - Best Accuracy: {best_acc:.4f}\")\n    print(classification_report(\n    all_labels, all_preds, \n    labels=[0,1,2,3],       # ← forcer toutes les classes\n    target_names=['Hiver','Printemps','Été','Automne'],\n    zero_division=0          # ← évite les divisions par zéro\n))\n\n# ============================================================================\n# 3. PLOT COMBINÉ DE L'ÉVOLUTION DE L'ACCURACY PAR FOLD\n# ============================================================================\nplt.figure(figsize=(10,6))\nimport matplotlib.cm as cm\ncmap = cm.get_cmap('tab10', n_splits)  # 'tab10' contient 10 couleurs\ncolors = [cmap(i) for i in range(n_splits)]\n\nfor i in range(n_splits):\n    # train accuracy\n    plt.plot(fold_train_acc_hist[i], color=colors[i], linestyle='-', label=f'Fold {i+1} Train')\n    # test accuracy\n    plt.plot(fold_test_acc_hist[i], color=colors[i], linestyle='--', alpha=0.7, label=f'Fold {i+1} Test')\n\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Évolution de l\\'Accuracy par Fold')\nplt.grid(alpha=0.3)\nplt.legend()\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR CLASSIFICATION MULTI-CLASSES: 4 SAISONS (AUTO-ADAPTATIF)\n# STRATIFIED K-FOLD SANS GROUP\n# ============================================================================\n\n# Création d'une instance de StratifiedGroupKFold avec le nombre de splits souhaité\nn_splits = 3\ncv = StratifiedKFold(n_splits=n_splits)\n\n# On suppose que 'y' et 'groups' existent déjà et ont la bonne longueur\nX_dummy = [0] * len(y)  # X n'est pas utilisé ici mais doit avoir la même longueur que y\n\n# Création de la figure\nfig, ax = plt.subplots(figsize=(12, 4))\n\n# Définition des colormaps pour le plotting\ncmap_cv = plt.cm.coolwarm\ncmap_data = plt.cm.tab10\n\n# Appel de la fonction pour tracer les indices\nplot_cv_indices(cv, X_dummy, y, groups, ax, n_splits)\n\nplt.show()\n\n\n\nprint(\"=\"*80)\nprint(\"CLASSIFICATION MULTI-CLASSES: 4 SAISONS EUROPÉENNES (StratifiedKFold)\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 0. CRÉATION DU DATAFRAME DE CLASSIFICATION\n# ============================================================================\ndf_temp = df_unique.copy()\ndf_temp['date_parsed'] = pd.to_datetime(df_temp['Sampling_Date'], format='%Y/%m/%d', errors='coerce')\ndf_temp['month'] = df_temp['date_parsed'].dt.month\n\ndef get_european_season(month):\n    if month in [12, 1, 2]:\n        return 'Hiver'\n    elif month in [3, 4, 5]:\n        return 'Printemps'\n    elif month in [6, 7, 8]:\n        return 'Été'\n    elif month in [9, 10, 11]:\n        return 'Automne'\n    else:\n        return None\n\ndf_temp['saison'] = df_temp['month'].apply(get_european_season)\nseason_mapping = {'Hiver':0, 'Printemps':1, 'Été':2, 'Automne':3}\ndf_temp['saison_encoded'] = df_temp['saison'].map(season_mapping)\n\ndf_classification = df_combined_unique.copy()\ndf_classification['month'] = df_temp['month']\ndf_classification['saison'] = df_temp['saison']\ndf_classification['saison_encoded'] = df_temp['saison_encoded']\n\nprint(f\"✓ df_classification créé avec {len(df_classification)} échantillons\")\n\nreverse_mapping = {0:'Hiver',1:'Printemps',2:'Été',3:'Automne'}\n\n# Features et labels\nimage_feature_cols = [col for col in df_combined.columns if col.startswith('img_feature_')]\nX = df_classification[image_feature_cols].values\ny = df_classification['saison_encoded'].values\ninput_dim = X.shape[1]\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\n# ============================================================================\n# 1. DÉFINITION DU MODÈLE MLP\n# ============================================================================\nclass SeasonClassifier(nn.Module):\n    def __init__(self, input_dim, hidden_dims=[256,128,64], num_classes=4, dropout=0.3):\n        super().__init__()\n        layers = []\n        prev_dim = input_dim\n        for h in hidden_dims:\n            layers.append(nn.Linear(prev_dim, h))\n            layers.append(nn.ReLU())\n            layers.append(nn.BatchNorm1d(h))\n            layers.append(nn.Dropout(dropout))\n            prev_dim = h\n        layers.append(nn.Linear(prev_dim, num_classes))\n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\n# ============================================================================\n# 2. STRATIFIEDK-FOLD 3-FOLDS\n# ============================================================================\n\nfold_train_acc_hist = []\nfold_test_acc_hist = []\nfold_accuracies = []\n\nfor fold, (train_idx, test_idx) in enumerate(cv.split(X, y), 1):\n    print(f\"\\n--- Fold {fold}/{n_splits} ---\")\n    \n    X_train, X_test = X[train_idx], X[test_idx]\n    y_train, y_test = y[train_idx], y[test_idx]\n    \n    X_train_tensor = torch.FloatTensor(X_train)\n    y_train_tensor = torch.LongTensor(y_train)\n    X_test_tensor = torch.FloatTensor(X_test)\n    y_test_tensor = torch.LongTensor(y_test)\n    \n    train_loader = DataLoader(TensorDataset(X_train_tensor, y_train_tensor), batch_size=32, shuffle=True)\n    test_loader = DataLoader(TensorDataset(X_test_tensor, y_test_tensor), batch_size=32, shuffle=False)\n    \n    model = SeasonClassifier(input_dim=input_dim).to(device)\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5)\n    \n    train_acc_hist = []\n    test_acc_hist = []\n    best_acc = 0\n    best_state = None\n    \n    for epoch in range(50):\n        model.train()\n        correct_train, total_train = 0, 0\n        for batch_X, batch_y in train_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            optimizer.zero_grad()\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            loss.backward()\n            optimizer.step()\n            _, preds = torch.max(outputs,1)\n            correct_train += (preds==batch_y).sum().item()\n            total_train += batch_y.size(0)\n        train_acc = correct_train / total_train\n        train_acc_hist.append(train_acc)\n        \n        # Eval test\n        model.eval()\n        correct_test, total_test = 0,0\n        all_preds, all_labels = [], []\n        with torch.no_grad():\n            for batch_X, batch_y in test_loader:\n                batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n                outputs = model(batch_X)\n                _, preds = torch.max(outputs,1)\n                correct_test += (preds==batch_y).sum().item()\n                total_test += batch_y.size(0)\n                all_preds.extend(preds.cpu().numpy())\n                all_labels.extend(batch_y.cpu().numpy())\n        test_acc = correct_test / total_test\n        test_acc_hist.append(test_acc)\n        \n        scheduler.step(loss)\n        \n        if test_acc > best_acc:\n            best_acc = test_acc\n            best_state = model.state_dict().copy()\n    \n    model.load_state_dict(best_state)\n    fold_train_acc_hist.append(train_acc_hist)\n    fold_test_acc_hist.append(test_acc_hist)\n    fold_accuracies.append(best_acc)\n    \n    print(f\"\\nFold {fold} - Best Accuracy: {best_acc:.4f}\")\n    print(classification_report(all_labels, all_preds, labels=[0,1,2,3],\n                                target_names=['Hiver','Printemps','Été','Automne'],\n                                zero_division=0))\n\n# ============================================================================\n# 3. PLOT COMBINÉ DE L'ÉVOLUTION DE L'ACCURACY PAR FOLD\n# ============================================================================\nplt.figure(figsize=(10,6))\nimport matplotlib.cm as cm\ncmap = cm.get_cmap('tab10', n_splits)  # 'tab10' contient 10 couleurs\ncolors = [cmap(i) for i in range(n_splits)]\n\n\nfor i in range(n_splits):\n    plt.plot(fold_train_acc_hist[i], color=colors[i], linestyle='-', label=f'Fold {i+1} Train')\n    plt.plot(fold_test_acc_hist[i], color=colors[i], linestyle='--', alpha=0.7, label=f'Fold {i+1} Test')\n\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Évolution de l\\'Accuracy par Fold (StratifiedKFold)')\nplt.grid(alpha=0.3)\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR CLASSIFICATION DES ESPÈCES (15 CLASSES)\n# ============================================================================\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nfrom sklearn.utils.class_weight import compute_class_weight\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nprint(\"=\"*80)\nprint(\"CLASSIFICATION DES ESPÈCES DE PLANTES (15 CLASSES)\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 1. PRÉPARATION DES DONNÉES\n# ============================================================================\n\n# Créer le mapping des espèces\nspecies_list = sorted(df['Species'].unique())\nspecies_mapping = {species: idx for idx, species in enumerate(species_list)}\nreverse_species_mapping = {idx: species for species, idx in species_mapping.items()}\n\n# Encoder les espèces\ndf_species = df_combined_unique.copy()\ndf_species['species'] = df_unique['Species']\ndf_species['species_encoded'] = df_species['species'].map(species_mapping)\n\nprint(f\"Nombre d'espèces: {len(species_mapping)}\")\nprint(f\"Échantillons totaux: {len(df_species)}\")\n\n# Extraire les features DINOv2\nimage_feature_cols = [col for col in df_combined.columns if col.startswith('img_feature_')]\nX = df_species[image_feature_cols].values\ny = df_species['species_encoded'].values\n\ninput_dim = X.shape[1]\nnum_classes = len(species_mapping)\n\nprint(f\"Input dim: {input_dim} | Classes: {num_classes}\")\n\n# Train/Test split stratifié\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42, stratify=y\n)\n\nprint(f\"Train: {X_train.shape[0]} | Test: {X_test.shape[0]}\")\n\n# Calculer les poids de classes pour gérer le déséquilibre\nclass_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)\nclass_weights_tensor = torch.FloatTensor(class_weights)\n\n# Convertir en tensors PyTorch\nX_train_tensor = torch.FloatTensor(X_train)\ny_train_tensor = torch.LongTensor(y_train)\nX_test_tensor = torch.FloatTensor(X_test)\ny_test_tensor = torch.LongTensor(y_test)\n\n# DataLoaders\nbatch_size = 32\ntrain_dataset = TensorDataset(X_train_tensor, y_train_tensor)\ntest_dataset = TensorDataset(X_test_tensor, y_test_tensor)\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n# ============================================================================\n# 2. DÉFINITION DU MODÈLE (PLUS PROFOND POUR 15 CLASSES)\n# ============================================================================\n\nclass SpeciesClassifier(nn.Module):\n    def __init__(self, input_dim, num_classes, hidden_dims=[512, 256, 128, 64], dropout=0.4):\n        super(SpeciesClassifier, self).__init__()\n        \n        layers = []\n        prev_dim = input_dim\n        \n        for hidden_dim in hidden_dims:\n            layers.append(nn.Linear(prev_dim, hidden_dim))\n            layers.append(nn.ReLU())\n            layers.append(nn.BatchNorm1d(hidden_dim))\n            layers.append(nn.Dropout(dropout))\n            prev_dim = hidden_dim\n        \n        layers.append(nn.Linear(prev_dim, num_classes))\n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = SpeciesClassifier(input_dim=input_dim, num_classes=num_classes, \n                          hidden_dims=[512, 256, 128, 64], dropout=0.4)\nmodel = model.to(device)\nclass_weights_tensor = class_weights_tensor.to(device)\n\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Paramètres: {total_params:,} | Device: {device}\")\n\n# ============================================================================\n# 3. ENTRAÎNEMENT\n# ============================================================================\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)  # Poids pour déséquilibre\noptimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=8, factor=0.5)\n\nnum_epochs = 80\ntrain_losses = []\ntest_losses = []\ntest_accuracies = []\nbest_test_acc = 0\nbest_epoch = 0\n\nprint(f\"\\nEntraînement: {num_epochs} époques avec class weighting...\")\n\nfor epoch in range(num_epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        optimizer.zero_grad()\n        outputs = model(batch_X)\n        loss = criterion(outputs, batch_y)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n    \n    # Validation\n    model.eval()\n    test_loss = 0.0\n    all_preds = []\n    all_labels = []\n    with torch.no_grad():\n        for batch_X, batch_y in test_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            test_loss += loss.item()\n            _, preds = torch.max(outputs, 1)\n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(batch_y.cpu().numpy())\n    \n    test_loss /= len(test_loader)\n    test_losses.append(test_loss)\n    test_acc = accuracy_score(all_labels, all_preds)\n    test_accuracies.append(test_acc)\n    scheduler.step(test_loss)\n    \n    if test_acc > best_test_acc:\n        best_test_acc = test_acc\n        best_epoch = epoch\n        best_model_state = model.state_dict().copy()\n    \n    if (epoch + 1) % 20 == 0 or epoch == 0:\n        print(f\"Epoch [{epoch+1:3d}/{num_epochs}] Loss: {train_loss:.4f}/{test_loss:.4f} Acc: {test_acc:.4f}\")\n\nprint(f\"\\n✓ Meilleure accuracy: {best_test_acc:.4f} (epoch {best_epoch + 1})\")\nmodel.load_state_dict(best_model_state)\n\n# ============================================================================\n# 4. ÉVALUATION FINALE\n# ============================================================================\n\nmodel.eval()\nfinal_preds = []\nfinal_probs = []\n\nwith torch.no_grad():\n    for batch_X, _ in test_loader:\n        batch_X = batch_X.to(device)\n        outputs = model(batch_X)\n        probs = torch.softmax(outputs, dim=1)\n        _, preds = torch.max(outputs, 1)\n        final_preds.extend(preds.cpu().numpy())\n        final_probs.extend(probs.cpu().numpy())\n\nfinal_preds = np.array(final_preds)\nfinal_probs = np.array(final_probs)\n\naccuracy = accuracy_score(y_test, final_preds)\nconf_matrix = confusion_matrix(y_test, final_preds)\n\nprint(f\"\\nAccuracy finale: {accuracy:.4f} ({accuracy*100:.2f}%)\")\nprint(f\"\\nClassification Report (top classes):\")\n\n\n# ============================================================================\n# 5. VISUALISATIONS\n# ============================================================================\n\n# Courbes Loss & Accuracy\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\naxes[0].plot(train_losses, label='Train Loss', linewidth=2)\naxes[0].plot(test_losses, label='Test Loss', linewidth=2)\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Loss Evolution - Classification Espèces', fontweight='bold')\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\naxes[1].plot(test_accuracies, label='Test Accuracy', color='green', linewidth=2)\naxes[1].axhline(y=best_test_acc, color='red', linestyle='--', label=f'Best: {best_test_acc:.4f}')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Accuracy')\naxes[1].set_title('Test Accuracy Evolution', fontweight='bold')\naxes[1].legend()\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Matrice de confusion (avec noms abrégés)\nspecies_names_short = [s[:20] for s in species_list]  # Tronquer les noms longs\n\nplt.figure(figsize=(14, 12))\nsns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', \n            xticklabels=species_names_short,\n            yticklabels=species_names_short,\n            cbar_kws={'label': 'Count'})\nplt.ylabel('Vérité terrain', fontsize=12)\nplt.xlabel('Prédiction', fontsize=12)\nplt.title('Matrice de Confusion - Classification des Espèces (15 classes)', \n          fontsize=14, fontweight='bold')\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"CLASSIFICATION DES ESPÈCES TERMINÉE\")\nprint(\"=\"*80)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR CLASSIFICATION DES ÉTATS (4 CLASSES)\n# ============================================================================\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nfrom sklearn.utils.class_weight import compute_class_weight\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nprint(\"=\"*80)\nprint(\"CLASSIFICATION DES ÉTATS AUSTRALIENS (4 CLASSES)\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 1. PRÉPARATION DES DONNÉES\n# ============================================================================\n\n# Créer le mapping des états\nstate_list = sorted(df['State'].unique())\nstate_mapping = {state: idx for idx, state in enumerate(state_list)}\nreverse_state_mapping = {idx: state for state, idx in state_mapping.items()}\n\n# Encoder les états\ndf_states = df_combined_unique.copy()\ndf_states['state'] = df_unique['State']\ndf_states['state_encoded'] = df_states['state'].map(state_mapping)\n\nprint(f\"États: {len(state_mapping)} | Échantillons: {len(df_states)}\")\n\n# Extraire les features DINOv2\nimage_feature_cols = [col for col in df_combined.columns if col.startswith('img_feature_')]\nX = df_states[image_feature_cols].values\ny = df_states['state_encoded'].values\n\ninput_dim = X.shape[1]\nnum_classes = len(state_mapping)\n\n# Train/Test split stratifié\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42, stratify=y\n)\n\nprint(f\"Train: {X_train.shape[0]} | Test: {X_test.shape[0]}\")\n\n# Calculer les poids de classes\nclass_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)\nclass_weights_tensor = torch.FloatTensor(class_weights)\n\n# Convertir en tensors PyTorch\nX_train_tensor = torch.FloatTensor(X_train)\ny_train_tensor = torch.LongTensor(y_train)\nX_test_tensor = torch.FloatTensor(X_test)\ny_test_tensor = torch.LongTensor(y_test)\n\n# DataLoaders\nbatch_size = 32\ntrain_dataset = TensorDataset(X_train_tensor, y_train_tensor)\ntest_dataset = TensorDataset(X_test_tensor, y_test_tensor)\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n# ============================================================================\n# 2. DÉFINITION DU MODÈLE\n# ============================================================================\n\nclass StateClassifier(nn.Module):\n    def __init__(self, input_dim, num_classes, hidden_dims=[256, 128, 64], dropout=0.3):\n        super(StateClassifier, self).__init__()\n        \n        layers = []\n        prev_dim = input_dim\n        \n        for hidden_dim in hidden_dims:\n            layers.append(nn.Linear(prev_dim, hidden_dim))\n            layers.append(nn.ReLU())\n            layers.append(nn.BatchNorm1d(hidden_dim))\n            layers.append(nn.Dropout(dropout))\n            prev_dim = hidden_dim\n        \n        layers.append(nn.Linear(prev_dim, num_classes))\n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = StateClassifier(input_dim=input_dim, num_classes=num_classes, \n                        hidden_dims=[256, 128, 64], dropout=0.3)\nmodel = model.to(device)\nclass_weights_tensor = class_weights_tensor.to(device)\n\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Paramètres: {total_params:,} | Device: {device}\")\n\n# ============================================================================\n# 3. ENTRAÎNEMENT\n# ============================================================================\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\noptimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5)\n\nnum_epochs = 50\ntrain_losses = []\ntest_losses = []\ntest_accuracies = []\nbest_test_acc = 0\nbest_epoch = 0\n\nprint(f\"\\nEntraînement: {num_epochs} époques...\")\n\nfor epoch in range(num_epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        optimizer.zero_grad()\n        outputs = model(batch_X)\n        loss = criterion(outputs, batch_y)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n    \n    # Validation\n    model.eval()\n    test_loss = 0.0\n    all_preds = []\n    all_labels = []\n    with torch.no_grad():\n        for batch_X, batch_y in test_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            test_loss += loss.item()\n            _, preds = torch.max(outputs, 1)\n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(batch_y.cpu().numpy())\n    \n    test_loss /= len(test_loader)\n    test_losses.append(test_loss)\n    test_acc = accuracy_score(all_labels, all_preds)\n    test_accuracies.append(test_acc)\n    scheduler.step(test_loss)\n    \n    if test_acc > best_test_acc:\n        best_test_acc = test_acc\n        best_epoch = epoch\n        best_model_state = model.state_dict().copy()\n    \n    if (epoch + 1) % 10 == 0 or epoch == 0:\n        print(f\"Epoch [{epoch+1:3d}/{num_epochs}] Loss: {train_loss:.4f}/{test_loss:.4f} Acc: {test_acc:.4f}\")\n\nprint(f\"\\n✓ Meilleure accuracy: {best_test_acc:.4f} (epoch {best_epoch + 1})\")\nmodel.load_state_dict(best_model_state)\n\n# ============================================================================\n# 4. ÉVALUATION FINALE\n# ============================================================================\n\nmodel.eval()\nfinal_preds = []\nfinal_probs = []\n\nwith torch.no_grad():\n    for batch_X, _ in test_loader:\n        batch_X = batch_X.to(device)\n        outputs = model(batch_X)\n        probs = torch.softmax(outputs, dim=1)\n        _, preds = torch.max(outputs, 1)\n        final_preds.extend(preds.cpu().numpy())\n        final_probs.extend(probs.cpu().numpy())\n\nfinal_preds = np.array(final_preds)\nfinal_probs = np.array(final_probs)\n\naccuracy = accuracy_score(y_test, final_preds)\nconf_matrix = confusion_matrix(y_test, final_preds)\n\nprint(f\"\\nAccuracy finale: {accuracy:.4f} ({accuracy*100:.2f}%)\")\nprint(f\"\\nClassification Report:\")\nreport = classification_report(y_test, final_preds, \n                               target_names=[reverse_state_mapping[i] for i in range(num_classes)])\nprint(report)\n\n# ============================================================================\n# 5. VISUALISATIONS\n# ============================================================================\n\n# Courbes Loss & Accuracy\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\naxes[0].plot(train_losses, label='Train Loss', linewidth=2)\naxes[0].plot(test_losses, label='Test Loss', linewidth=2)\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Loss Evolution - Classification États', fontweight='bold')\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\naxes[1].plot(test_accuracies, label='Test Accuracy', color='green', linewidth=2)\naxes[1].axhline(y=best_test_acc, color='red', linestyle='--', label=f'Best: {best_test_acc:.4f}')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Accuracy')\naxes[1].set_title('Test Accuracy Evolution', fontweight='bold')\naxes[1].legend()\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Matrice de confusion\nplt.figure(figsize=(8, 7))\nsns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', \n            xticklabels=state_list,\n            yticklabels=state_list,\n            cbar_kws={'label': 'Count'})\nplt.ylabel('Vérité terrain', fontsize=12)\nplt.xlabel('Prédiction', fontsize=12)\nplt.title('Matrice de Confusion - Classification des États (4 classes)', \n          fontsize=14, fontweight='bold')\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"CLASSIFICATION DES ÉTATS TERMINÉE\")\nprint(\"=\"*80)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR RÉGRESSION DE LA HAUTEUR\n# ============================================================================\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\nfrom sklearn.preprocessing import StandardScaler\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nprint(\"=\"*80)\nprint(\"RÉGRESSION DE LA HAUTEUR DE L'HERBE\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 1. PRÉPARATION DES DONNÉES\n# ============================================================================\n\n# Extraire features et target\nimage_feature_cols = [col for col in df_combined_unique.columns if col.startswith('img_feature_')]\nX = df_combined_unique[image_feature_cols].values\ny = df_unique['Height_Ave_cm'].values\n\ninput_dim = X.shape[1]\n\nprint(f\"Input dim: {input_dim} | Échantillons: {len(X)}\")\nprint(f\"Hauteur: min={y.min():.1f} | max={y.max():.1f} | mean={y.mean():.1f} cm\")\n\n# Train/Test split\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42\n)\n\n# Normaliser la target pour l'entraînement\nscaler_y = StandardScaler()\ny_train_scaled = scaler_y.fit_transform(y_train.reshape(-1, 1)).flatten()\ny_test_scaled = scaler_y.transform(y_test.reshape(-1, 1)).flatten()\n\nprint(f\"Train: {X_train.shape[0]} | Test: {X_test.shape[0]}\")\n\n# Convertir en tensors PyTorch\nX_train_tensor = torch.FloatTensor(X_train)\ny_train_tensor = torch.FloatTensor(y_train_scaled).unsqueeze(1)\nX_test_tensor = torch.FloatTensor(X_test)\ny_test_tensor = torch.FloatTensor(y_test_scaled).unsqueeze(1)\n\n# DataLoaders\nbatch_size = 32\ntrain_dataset = TensorDataset(X_train_tensor, y_train_tensor)\ntest_dataset = TensorDataset(X_test_tensor, y_test_tensor)\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n# ============================================================================\n# 2. DÉFINITION DU MODÈLE\n# ============================================================================\n\nclass HeightRegressor(nn.Module):\n    def __init__(self, input_dim, hidden_dims=[256, 128, 64], dropout=0.3):\n        super(HeightRegressor, self).__init__()\n        \n        layers = []\n        prev_dim = input_dim\n        \n        for hidden_dim in hidden_dims:\n            layers.append(nn.Linear(prev_dim, hidden_dim))\n            layers.append(nn.ReLU())\n            layers.append(nn.BatchNorm1d(hidden_dim))\n            layers.append(nn.Dropout(dropout))\n            prev_dim = hidden_dim\n        \n        # Output: 1 neurone pour régression\n        layers.append(nn.Linear(prev_dim, 1))\n        \n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = HeightRegressor(input_dim=input_dim, hidden_dims=[256, 128, 64], dropout=0.3)\nmodel = model.to(device)\n\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Paramètres: {total_params:,} | Device: {device}\")\n\n# ============================================================================\n# 3. ENTRAÎNEMENT\n# ============================================================================\n\ncriterion = nn.SmoothL1Loss()  # Robuste aux outliers\noptimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10, factor=0.5)\n\nnum_epochs = 80\ntrain_losses = []\ntest_losses = []\ntest_rmses = []\nbest_test_rmse = float('inf')\nbest_epoch = 0\n\nprint(f\"\\nEntraînement: {num_epochs} époques...\")\n\nfor epoch in range(num_epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        optimizer.zero_grad()\n        outputs = model(batch_X)\n        loss = criterion(outputs, batch_y)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n    \n    # Validation\n    model.eval()\n    test_loss = 0.0\n    all_preds = []\n    all_labels = []\n    with torch.no_grad():\n        for batch_X, batch_y in test_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            test_loss += loss.item()\n            all_preds.extend(outputs.cpu().numpy())\n            all_labels.extend(batch_y.cpu().numpy())\n    \n    test_loss /= len(test_loader)\n    test_losses.append(test_loss)\n    \n    # Calculer RMSE sur les valeurs dénormalisées\n    all_preds = np.array(all_preds).flatten()\n    all_labels = np.array(all_labels).flatten()\n    all_preds_orig = scaler_y.inverse_transform(all_preds.reshape(-1, 1)).flatten()\n    all_labels_orig = scaler_y.inverse_transform(all_labels.reshape(-1, 1)).flatten()\n    \n    test_rmse = np.sqrt(mean_squared_error(all_labels_orig, all_preds_orig))\n    test_rmses.append(test_rmse)\n    \n    scheduler.step(test_loss)\n    \n    if test_rmse < best_test_rmse:\n        best_test_rmse = test_rmse\n        best_epoch = epoch\n        best_model_state = model.state_dict().copy()\n    \n    if (epoch + 1) % 20 == 0 or epoch == 0:\n        print(f\"Epoch [{epoch+1:3d}/{num_epochs}] Loss: {train_loss:.4f}/{test_loss:.4f} RMSE: {test_rmse:.2f} cm\")\n\nprint(f\"\\n✓ Meilleur RMSE: {best_test_rmse:.2f} cm (epoch {best_epoch + 1})\")\nmodel.load_state_dict(best_model_state)\n\n# ============================================================================\n# 4. ÉVALUATION FINALE\n# ============================================================================\n\nmodel.eval()\nfinal_preds = []\n\nwith torch.no_grad():\n    for batch_X, _ in test_loader:\n        batch_X = batch_X.to(device)\n        outputs = model(batch_X)\n        final_preds.extend(outputs.cpu().numpy())\n\nfinal_preds = np.array(final_preds).flatten()\nfinal_preds_orig = scaler_y.inverse_transform(final_preds.reshape(-1, 1)).flatten()\n\n# Métriques\nrmse = np.sqrt(mean_squared_error(y_test, final_preds_orig))\nmae = mean_absolute_error(y_test, final_preds_orig)\nr2 = r2_score(y_test, final_preds_orig)\n\nprint(f\"\\nMétriques finales:\")\nprint(f\"  RMSE: {rmse:.2f} cm\")\nprint(f\"  MAE: {mae:.2f} cm\")\nprint(f\"  R²: {r2:.4f}\")\n\n# ============================================================================\n# 5. VISUALISATIONS\n# ============================================================================\n\n# Courbes Loss & RMSE\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\naxes[0].plot(train_losses, label='Train Loss', linewidth=2)\naxes[0].plot(test_losses, label='Test Loss', linewidth=2)\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Loss Evolution - Régression Hauteur', fontweight='bold')\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\naxes[1].plot(test_rmses, label='Test RMSE', color='green', linewidth=2)\naxes[1].axhline(y=best_test_rmse, color='red', linestyle='--', label=f'Best: {best_test_rmse:.2f} cm')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('RMSE (cm)')\naxes[1].set_title('RMSE Evolution', fontweight='bold')\naxes[1].legend()\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Scatter plot: Prédictions vs Vérité terrain\nfig, axes = plt.subplots(1, 2, figsize=(14, 6))\n\n# Plot 1: Scatter avec ligne identité\naxes[0].scatter(y_test, final_preds_orig, alpha=0.5, s=20)\naxes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], \n             'r--', linewidth=2, label='Prédiction parfaite')\naxes[0].set_xlabel('Hauteur réelle (cm)')\naxes[0].set_ylabel('Hauteur prédite (cm)')\naxes[0].set_title('Prédictions vs Vérité terrain', fontweight='bold')\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\n# Plot 2: Erreur résiduelle\nresiduals = y_test - final_preds_orig\naxes[1].scatter(final_preds_orig, residuals, alpha=0.5, s=20)\naxes[1].axhline(y=0, color='r', linestyle='--', linewidth=2)\naxes[1].set_xlabel('Hauteur prédite (cm)')\naxes[1].set_ylabel('Erreur résiduelle (cm)')\naxes[1].set_title('Analyse des résidus', fontweight='bold')\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Distribution des erreurs\nplt.figure(figsize=(10, 5))\nplt.hist(residuals, bins=50, edgecolor='black', alpha=0.7)\nplt.axvline(x=0, color='r', linestyle='--', linewidth=2, label='Erreur nulle')\nplt.xlabel('Erreur (cm)')\nplt.ylabel('Fréquence')\nplt.title('Distribution des erreurs de prédiction', fontweight='bold')\nplt.legend()\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"RÉGRESSION DE LA HAUTEUR TERMINÉE\")\nprint(\"=\"*80)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# MODÈLE MLP POUR RÉGRESSION DU NDVI\n# ============================================================================\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\nfrom sklearn.preprocessing import StandardScaler\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nprint(\"=\"*80)\nprint(\"RÉGRESSION DU NDVI\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 1. PRÉPARATION DES DONNÉES\n# ============================================================================\n\nimage_feature_cols = [col for col in df_combined_unique.columns if col.startswith('img_feature_')]\nX = df_combined_unique[image_feature_cols].values\ny = df_unique['Pre_GSHH_NDVI'].values\n\nprint(f\"Input: {X.shape[1]} | Samples: {len(X)} | NDVI range: [{y.min():.2f}, {y.max():.2f}]\")\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\n# Normaliser la target\nscaler_y = StandardScaler()\ny_train_scaled = scaler_y.fit_transform(y_train.reshape(-1, 1)).flatten()\ny_test_scaled = scaler_y.transform(y_test.reshape(-1, 1)).flatten()\n\n# Tensors\nX_train_tensor = torch.FloatTensor(X_train)\ny_train_tensor = torch.FloatTensor(y_train_scaled).unsqueeze(1)\nX_test_tensor = torch.FloatTensor(X_test)\ny_test_tensor = torch.FloatTensor(y_test_scaled).unsqueeze(1)\n\n# DataLoaders\nbatch_size = 32\ntrain_loader = DataLoader(TensorDataset(X_train_tensor, y_train_tensor), batch_size=batch_size, shuffle=True)\ntest_loader = DataLoader(TensorDataset(X_test_tensor, y_test_tensor), batch_size=batch_size, shuffle=False)\n\n# ============================================================================\n# 2. MODÈLE\n# ============================================================================\n\nclass NDVIRegressor(nn.Module):\n    def __init__(self, input_dim, hidden_dims=[256, 128, 64], dropout=0.3):\n        super(NDVIRegressor, self).__init__()\n        layers = []\n        prev_dim = input_dim\n        for hidden_dim in hidden_dims:\n            layers.extend([nn.Linear(prev_dim, hidden_dim), nn.ReLU(), \n                          nn.BatchNorm1d(hidden_dim), nn.Dropout(dropout)])\n            prev_dim = hidden_dim\n        layers.append(nn.Linear(prev_dim, 1))\n        self.network = nn.Sequential(*layers)\n    \n    def forward(self, x):\n        return self.network(x)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = NDVIRegressor(input_dim=X.shape[1], hidden_dims=[256, 128, 64], dropout=0.3).to(device)\n\nprint(f\"Params: {sum(p.numel() for p in model.parameters()):,} | Device: {device}\")\n\n# ============================================================================\n# 3. ENTRAÎNEMENT\n# ============================================================================\n\ncriterion = nn.SmoothL1Loss()\noptimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10, factor=0.5)\n\nnum_epochs = 80\ntrain_losses, test_losses, test_rmses = [], [], []\nbest_test_rmse = float('inf')\n\nfor epoch in range(num_epochs):\n    # Train\n    model.train()\n    train_loss = 0.0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        optimizer.zero_grad()\n        loss = criterion(model(batch_X), batch_y)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n    \n    # Val\n    model.eval()\n    test_loss = 0.0\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for batch_X, batch_y in test_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            outputs = model(batch_X)\n            test_loss += criterion(outputs, batch_y).item()\n            all_preds.extend(outputs.cpu().numpy())\n            all_labels.extend(batch_y.cpu().numpy())\n    \n    test_loss /= len(test_loader)\n    test_losses.append(test_loss)\n    \n    # RMSE dénormalisé\n    all_preds = scaler_y.inverse_transform(np.array(all_preds).reshape(-1, 1)).flatten()\n    all_labels = scaler_y.inverse_transform(np.array(all_labels).reshape(-1, 1)).flatten()\n    test_rmse = np.sqrt(mean_squared_error(all_labels, all_preds))\n    test_rmses.append(test_rmse)\n    \n    scheduler.step(test_loss)\n    \n    if test_rmse < best_test_rmse:\n        best_test_rmse = test_rmse\n        best_epoch = epoch\n        best_model_state = model.state_dict().copy()\n    \n    if (epoch + 1) % 20 == 0 or epoch == 0:\n        print(f\"Epoch [{epoch+1:3d}/{num_epochs}] Loss: {train_loss:.4f}/{test_loss:.4f} RMSE: {test_rmse:.4f}\")\n\nprint(f\"✓ Best RMSE: {best_test_rmse:.4f} (epoch {best_epoch + 1})\")\nmodel.load_state_dict(best_model_state)\n\n# ============================================================================\n# 4. ÉVALUATION\n# ============================================================================\n\nmodel.eval()\nfinal_preds = []\nwith torch.no_grad():\n    for batch_X, _ in test_loader:\n        final_preds.extend(model(batch_X.to(device)).cpu().numpy())\n\nfinal_preds = scaler_y.inverse_transform(np.array(final_preds).reshape(-1, 1)).flatten()\n\nrmse = np.sqrt(mean_squared_error(y_test, final_preds))\nmae = mean_absolute_error(y_test, final_preds)\nr2 = r2_score(y_test, final_preds)\n\nprint(f\"\\nRMSE: {rmse:.4f} | MAE: {mae:.4f} | R²: {r2:.4f}\")\n\n# ============================================================================\n# 5. VISUALISATIONS\n# ============================================================================\n\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\naxes[0].plot(train_losses, label='Train', linewidth=2)\naxes[0].plot(test_losses, label='Test', linewidth=2)\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Loss - Régression NDVI', fontweight='bold')\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\naxes[1].plot(test_rmses, color='green', linewidth=2)\naxes[1].axhline(y=best_test_rmse, color='red', linestyle='--', label=f'Best: {best_test_rmse:.4f}')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('RMSE')\naxes[1].set_title('RMSE Evolution', fontweight='bold')\naxes[1].legend()\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\nfig, axes = plt.subplots(1, 2, figsize=(14, 6))\n\naxes[0].scatter(y_test, final_preds, alpha=0.5, s=20)\naxes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', linewidth=2)\naxes[0].set_xlabel('NDVI réel')\naxes[0].set_ylabel('NDVI prédit')\naxes[0].set_title('Prédictions vs Vérité', fontweight='bold')\naxes[0].grid(alpha=0.3)\n\nresiduals = y_test - final_preds\naxes[1].scatter(final_preds, residuals, alpha=0.5, s=20)\naxes[1].axhline(y=0, color='r', linestyle='--', linewidth=2)\naxes[1].set_xlabel('NDVI prédit')\naxes[1].set_ylabel('Erreur résiduelle')\naxes[1].set_title('Analyse des résidus', fontweight='bold')\naxes[1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\nplt.figure(figsize=(10, 5))\nplt.hist(residuals, bins=50, edgecolor='black', alpha=0.7)\nplt.axvline(x=0, color='r', linestyle='--', linewidth=2)\nplt.xlabel('Erreur')\nplt.ylabel('Fréquence')\nplt.title('Distribution des erreurs', fontweight='bold')\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()\n\nprint(\"=\"*80)\nprint(\"RÉGRESSION NDVI TERMINÉE\")\nprint(\"=\"*80)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train Regressor","metadata":{}},{"cell_type":"code","source":"from sklearn.decomposition import PCA\nfrom sklearn.preprocessing import StandardScaler\n\n# Separate image features and other features\nimage_feature_cols = [col for col in df_combined.columns if col.startswith('img_feature_')]\nother_features_cols = [col for col in df_combined.columns if col.startswith('target_name_')]\n\nX_image_features = df_combined[image_feature_cols]\nX_other_features = df_combined[other_features_cols]\n\n# Standardize the image features before applying PCA\nscaler = StandardScaler()\nX_image_scaled = scaler.fit_transform(X_image_features)\n\n# Apply PCA to reduce dimensionality\n# You can adjust the number of components (n_components)\npca = PCA(n_components=300) # Example: Reduce components by 20x\nX_image_pca = pca.fit_transform(X_image_scaled)\n\n# Convert the PCA reduced features to a DataFrame\nX_image_pca_df = pd.DataFrame(\n    X_image_pca, index=df_combined.index,\n    columns=[f'pca_img_feature_{i}' for i in range(pca.n_components)])\n\n# Combine the PCA reduced image features with the other features\nX_combined_pca = pd.concat([X_other_features, X_image_pca_df], axis=1)\n\nprint(f\"Original number of image features: {X_image_features.shape[1]}\")\nprint(f\"Reduced number of image features after PCA: {X_image_pca_df.shape[1]}\")\nprint(f\"Total number of features for regression after PCA: {X_combined_pca.shape[1]}\")\n\n# Display the first few rows of the combined feature DataFrame after PCA\ndisplay(X_combined_pca.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import xgboost as xgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\nfrom skopt import BayesSearchCV\nfrom skopt.space import Real, Integer, Categorical\nimport numpy as np\n\n# Define features (X) and target (y) using the PCA-reduced image features\nfeatures = X_combined_pca.columns.tolist()\nprint(f\"training features: {features}\")\n\ntarget = 'target'\nX = X_combined_pca\ny = df_combined[target]\n\n# Split data into training and testing sets\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\n# Define search space for Bayesian optimization\nsearch_spaces = {\n    'n_estimators': Integer(100, 1000),\n    'learning_rate': Real(0.001, 0.2, prior='log-uniform'),\n    'max_depth': Integer(3, 10),\n    'colsample_bytree': Real(0.5, 1.0),\n    'reg_alpha': Real(1e-8, 10, prior='log-uniform'),\n    'reg_lambda': Real(1, 10),\n    'subsample': Real(0.5, 1.0),  # ✅ CORRECTION : Real au lieu de liste\n}\n\nxgb_model = xgb.XGBRegressor(objective='reg:squarederror', random_state=42)\n\n# Use BayesSearchCV for Bayesian optimization\nbayes_search = BayesSearchCV(\n    estimator=xgb_model,\n    search_spaces=search_spaces,\n    n_iter=50,\n    scoring='neg_mean_squared_error',\n    cv=3,\n    verbose=2,\n    random_state=42,\n    n_jobs=-1,\n)\n\nbayes_search.fit(X_train, y_train)\n\n# Get the best parameters and the best model\nbest_params = bayes_search.best_params_\nxgb_model = bayes_search.best_estimator_\n\nprint(\"Bayesian optimization complete.\")\nprint(f\"Best parameters found: {best_params}\")\nprint(\"XGBoost model training complete with best parameters.\")","metadata":{"trusted":true,"_kg_hide-output":true,"scrolled":true,"execution":{"execution_failed":"2025-12-16T18:48:02.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Get feature importances from the best model found by GridSearchCV\n# If you didn't use GridSearchCV, use xgb_model.feature_importances_\nfeature_importances = xgb_model.feature_importances_\n\n# Create a pandas Series for easier handling and sorting\nfeature_importances_series = pd.Series(feature_importances, index=X_train.columns)\n\n# Sort features by importance\nsorted_feature_importances = feature_importances_series.sort_values(ascending=False)\n\n# Plot the top N feature importances (e.g., top 20)\nn_top_features = 20\nplt.figure(figsize=(10, 8))\nsns.barplot(x=sorted_feature_importances.head(n_top_features).values,\n            y=sorted_feature_importances.head(n_top_features).index,\n            palette='viridis')\nplt.title(f'Top {n_top_features} Feature Importances (XGBoost)', fontsize=14)\nplt.xlabel('Importance', fontsize=12)\nplt.ylabel('Feature', fontsize=12)\nplt.tight_layout()\nplt.show()\n\n# You can also display the sorted feature importances\nprint(\"Sorted Feature Importances:\")\ndisplay(sorted_feature_importances)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Make predictions on the test data\ny_pred = xgb_model.predict(X_test)\n\nprint(\"Predictions on test data complete.\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import mean_absolute_error, r2_score\n\n# Calculate Root Mean Squared Error (RMSE)\nrmse = np.sqrt(mean_squared_error(y_test, y_pred))\nprint(f\"Root Mean Squared Error (RMSE) on the test set: {rmse}\")\n\n# Calculate Mean Absolute Error (MAE)\nmae = mean_absolute_error(y_test, y_pred)\nprint(f\"Mean Absolute Error (MAE) on the test set: {mae}\")\n\n# Calculate R-squared\nr2 = r2_score(y_test, y_pred)\nprint(f\"R-squared on the test set: {r2}\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test predictions","metadata":{}},{"cell_type":"code","source":"# 1. Load test data\nPATH_TEST_CSV = os.path.join(PATH_DATA, 'test.csv')\ndf_test = pd.read_csv(PATH_TEST_CSV)\nprint(f\"Test Dataset size: {df_test.shape}\")\ndisplay(df_test.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Extract test image features\ntest_image_features = {}\nfor index, row in tqdm(df_test.iterrows(), total=df_test.shape[0]):\n    image_path = row['image_path']\n    # Use the pipeline function\n    features = extract_image_features_pipeline(image_path, feature_extractor, PATH_DATA)\n    # Use the sample_id as the key for the features\n    test_image_features[row['sample_id']] = features\n\nprint(\"Test Image feature extraction complete.\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. Combine test features\ntest_image_features_df = pd.DataFrame.from_dict(test_image_features, orient='index')\ntest_image_features_df.index.name = 'sample_id'\ntest_image_features_df.columns = [f'img_feature_{i}' for i in range(test_image_features_df.shape[1])]\n\n# One-hot encode the 'target_name' column for the test set\ntest_df_one_hot = pd.get_dummies(df_test['target_name'], prefix='target_name').astype(int)\n\n# Merge the test image features and one-hot encoded features with the original test DataFrame\ndf_test_combined = df_test.merge(test_image_features_df, on='sample_id', how='left')\ndf_test_combined = df_test_combined.merge(test_df_one_hot, left_index=True, right_index=True)\n\nprint(\"Test image features and one-hot encoded target names combined.\")\ndisplay(df_test_combined.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. Scale and PCA transform test image features\n# Separate image features and other features for the test set\ntest_image_feature_cols = [col for col in df_test_combined.columns if col.startswith('img_feature_')]\ntest_other_features_cols = [col for col in df_test_combined.columns if col.startswith('target_name_')]\n\nX_test_image_features = df_test_combined[test_image_feature_cols]\nX_test_other_features = df_test_combined[test_other_features_cols]\n\n# Apply the *trained* scaler and PCA to the test image features\nX_test_image_scaled = scaler.transform(X_test_image_features) # Use the fitted scaler\nX_test_image_pca = pca.transform(X_test_image_scaled)       # Use the fitted pca\n\n# Convert the PCA reduced features to a DataFrame\nX_test_image_pca_df = pd.DataFrame(\n    X_test_image_pca, index=df_test_combined.index,\n    columns=[f'pca_img_feature_{i}' for i in range(pca.n_components)])\n\nprint(f\"Scaled and PCA-transformed test image features shape: {X_test_image_pca_df.shape}\")\n\n# Display the first few rows of the PCA reduced test image features\ndisplay(X_test_image_pca_df.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. Prepare test data for inference\n# Combine the PCA reduced image features with the other features (one-hot encoded target_name)\nX_test_final = pd.concat([X_test_other_features, X_test_image_pca_df], axis=1)\n\nprint(f\"Final test feature DataFrame shape: {X_test_final.shape}\")\n\n# Display the first few rows of the final test feature DataFrame\ndisplay(X_test_final.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 6. Make predictions\ntest_predictions = xgb_model.predict(X_test_final)\n\nprint(\"Predictions on test data complete.\")\ndisplay(test_predictions)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prepare submission","metadata":{}},{"cell_type":"code","source":"# 7. Generate submission file\nsubmission_df = pd.DataFrame({'sample_id': df_test['sample_id'], 'target': test_predictions})\n\n# Ensure predictions are non-negative if the target represents a physical quantity\nsubmission_df['target'] = submission_df['target'].clip(lower=0)\n\nsubmission_path = 'submission.csv'\nsubmission_df.to_csv(submission_path, index=False)\n\nprint(f\"Submission file generated at: {submission_path}\")\ndisplay(submission_df.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-16T18:48:02.090Z"}},"outputs":[],"execution_count":null}]}