{"metadata":{"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14469640,"sourceType":"datasetVersion","datasetId":9232899},{"sourceId":291093226,"sourceType":"kernelVersion"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":1501.927804,"end_time":"2026-01-12T03:49:39.364838","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-01-12T03:24:37.437034","version":"2.6.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"325fca45c8b04b7eb8ee1661325a4c41":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"3ca2c98e005d43c7813332476a4cf06a":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"44df883bfb6040c7ab4a25506a96db05":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"success","description":"","description_allow_html":false,"layout":"IPY_MODEL_fd4a57b5701242dfb34827aeb43b4471","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_5a2a0a1c767c4d44842581dfcc27c494","tabbable":null,"tooltip":null,"value":1}},"47d2e2adb037444da572a355c29538d7":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"518aada3671b44128d0e56b7c300c211":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_9461582d12af47c2889fd391ba62f55f","IPY_MODEL_44df883bfb6040c7ab4a25506a96db05","IPY_MODEL_7b6830752c5d4f1f8a2876cad3234f52"],"layout":"IPY_MODEL_5355aad91c25422dbad5896c53dc21c5","tabbable":null,"tooltip":null}},"5355aad91c25422dbad5896c53dc21c5":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"5947eb57ec0944498e57f4cfe4993ceb":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"success","description":"","description_allow_html":false,"layout":"IPY_MODEL_cb3208f500cc4ed88c3d932d0652cac7","max":357,"min":0,"orientation":"horizontal","style":"IPY_MODEL_788a1bc8d66e456696fa54fe0d3b7cfd","tabbable":null,"tooltip":null,"value":357}},"5a2a0a1c767c4d44842581dfcc27c494":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"6085fddee23d498a9592f26b425222cb":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"69e5257acdb24029b129255bb2d2ef0f":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"6c313b70806c4ed29a382bc6cbcb5d38":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"788a1bc8d66e456696fa54fe0d3b7cfd":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"7b6830752c5d4f1f8a2876cad3234f52":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_f65134c376d84de59553f997d55c80f2","placeholder":"​","style":"IPY_MODEL_89d0459247e44dc4b2cf6767725b7d59","tabbable":null,"tooltip":null,"value":" 1/1 [00:00&lt;00:00, 13.88it/s]"}},"7b972289968e4905a156120895f84963":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_c628f293a6f346d792a41be9c516cc7b","placeholder":"​","style":"IPY_MODEL_c22e2d69abd341ea8ef20d6f233de895","tabbable":null,"tooltip":null,"value":"100%"}},"87bc7fe28adc4872b13fd9024a30cc48":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"89d0459247e44dc4b2cf6767725b7d59":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"8e342867462945b8aed5ca414f00c915":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_6085fddee23d498a9592f26b425222cb","placeholder":"​","style":"IPY_MODEL_325fca45c8b04b7eb8ee1661325a4c41","tabbable":null,"tooltip":null,"value":" 86.5M/86.5M [00:02&lt;00:00, 64.7MB/s]"}},"9461582d12af47c2889fd391ba62f55f":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_f0e43b56b0804ec282a78b3ec25ee04b","placeholder":"​","style":"IPY_MODEL_ece6cd8e816444ba8defc9bac8e4e9f3","tabbable":null,"tooltip":null,"value":"100%"}},"949ddb6ed3d94ff9a4b528ab644c9ab1":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_dee007fd62cc4e2092da3a11579556a1","IPY_MODEL_e04ba926c909441a8b2436d1125f0539","IPY_MODEL_8e342867462945b8aed5ca414f00c915"],"layout":"IPY_MODEL_3ca2c98e005d43c7813332476a4cf06a","tabbable":null,"tooltip":null}},"9658f841d8ae4d5db1c8ac58833779d5":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_7b972289968e4905a156120895f84963","IPY_MODEL_5947eb57ec0944498e57f4cfe4993ceb","IPY_MODEL_dffb2a82378b4e13871169c4d4e631d3"],"layout":"IPY_MODEL_dd4351e21f4b4351b8ead20464d18e13","tabbable":null,"tooltip":null}},"c22e2d69abd341ea8ef20d6f233de895":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"c628f293a6f346d792a41be9c516cc7b":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"cb3208f500cc4ed88c3d932d0652cac7":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"d3ab488ee7104eada9495464a17e8d8b":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"dd4351e21f4b4351b8ead20464d18e13":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"dee007fd62cc4e2092da3a11579556a1":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_69e5257acdb24029b129255bb2d2ef0f","placeholder":"​","style":"IPY_MODEL_f82cf6acf7a548b9bf49dd1a64664402","tabbable":null,"tooltip":null,"value":"model.safetensors: 100%"}},"dffb2a82378b4e13871169c4d4e631d3":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_6c313b70806c4ed29a382bc6cbcb5d38","placeholder":"​","style":"IPY_MODEL_47d2e2adb037444da572a355c29538d7","tabbable":null,"tooltip":null,"value":" 357/357 [00:20&lt;00:00, 18.93it/s]"}},"e04ba926c909441a8b2436d1125f0539":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"success","description":"","description_allow_html":false,"layout":"IPY_MODEL_87bc7fe28adc4872b13fd9024a30cc48","max":86523256,"min":0,"orientation":"horizontal","style":"IPY_MODEL_d3ab488ee7104eada9495464a17e8d8b","tabbable":null,"tooltip":null,"value":86523256}},"ece6cd8e816444ba8defc9bac8e4e9f3":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"f0e43b56b0804ec282a78b3ec25ee04b":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f65134c376d84de59553f997d55c80f2":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f82cf6acf7a548b9bf49dd1a64664402":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"fd4a57b5701242dfb34827aeb43b4471":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"# Training mode control\nTRAIN = True\nimport os","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Environment detection\nDEBUG = os.environ.get('KAGGLE_KERNEL_RUN_TYPE', '') == 'Interactive'\nLOCAL = os.environ.get('KAGGLE_KERNEL_RUN_TYPE', '') == ''\n\nif LOCAL:\n    os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Paths\nDATA_ROOT = '../input/' if LOCAL else '/kaggle/input/csiro-biomass/'\nOUTPUT_DIR = 'trained_models/'\nos.makedirs(OUTPUT_DIR, exist_ok=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model\nMODEL_NAME = 'tf_efficientnetv2_s'\nTARGET_IMAGE_SIZE = (512, 512)\n\nprint(f\"✅ Config: {MODEL_NAME} | {TARGET_IMAGE_SIZE} | Output: {OUTPUT_DIR}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport pandas as pd\nfrom PIL import Image\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Data","metadata":{"execution":{"iopub.execute_input":"2025-11-21T05:07:31.762158Z","iopub.status.busy":"2025-11-21T05:07:31.760963Z","iopub.status.idle":"2025-11-21T05:07:31.766349Z","shell.execute_reply":"2025-11-21T05:07:31.765388Z","shell.execute_reply.started":"2025-11-21T05:07:31.762125Z"},"papermill":{"duration":0.009557,"end_time":"2026-01-12T03:24:44.485201","exception":false,"start_time":"2026-01-12T03:24:44.475644","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"### Data Interpretation\n\n#### Dry Matter Concept\nPasture contains significant water content that varies with weather, time, and growth stage. To obtain stable, comparable metrics for nutritional value and quantity, agronomists dry samples to remove all moisture before weighing. This \"dry matter\" or \"dry biomass\" weight forms the basis for all competition metrics.\n\n#### Target Variables Explained\n\n**1. Dry Green Vegetation (excluding clover)**\n- Dry weight of growing, green grass (ryegrass, fescue, etc.) - the main non-legume biomass\n- Primary energy source for livestock (cattle, sheep). Rich in carbohydrates, provides daily activity and growth energy. Quantity determines carrying capacity.\n\n**2. Dry Dead Material**\n- Dry weight of dead, yellowed plant matter from previous seasons or drought/maturity\n- Low-quality feed with poor nutritional value and digestibility. High levels indicate management issues (delayed grazing, overmature grass) or poor pasture health. Excess dead material blocks sunlight and inhibits new growth.\n\n**3. Dry Clover Biomass**\n- Dry weight of all clover (legumes like alfalfa) in the pasture\n- \"Superfood\" for pasture\n  - **High Protein**: Much higher protein than grass - critical for weight gain, milk production, and health\n  - **Natural Fertilizer**: Nitrogen fixation converts atmospheric nitrogen to soil nutrients, boosting overall pasture productivity and reducing chemical fertilizer needs\n\n**4. Green Dry Matter (GDM)**\n- Sum of (Dry Green Vegetation) + (Dry Clover Biomass) - all living, green plant dry weight\n- Core metric for used to determine stocking rates (how many cattle, how many days). Directly reflects pasture carrying capacity.\n\n**5. Total Dry Biomass**\n- Sum of all above-ground biomass: (Green) + (Clover) + (Dead)\n- Total plant matter quantity. Comparing Total vs GDM reveals pasture health:\n  - High total but low GDM ratio = excessive dead material accumulation requiring management\n  - Helps understand seasonal growth cycles and overall productivity","metadata":{"papermill":{"duration":0.009713,"end_time":"2026-01-12T03:24:44.504607","exception":false,"start_time":"2026-01-12T03:24:44.494894","status":"completed"},"tags":[]}},{"cell_type":"code","source":"from sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\n\nfrom PIL import Image\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\n# Import Weighted R² Score Utility\nimport sys\nsys.path.append('/kaggle/input/weighted-r-score')\n\nfrom weighted_r_score import calc_competition_metric, validate_predictions\n\nprint(\"✅ Weighted R² utility\")","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:24:44.524949Z","iopub.status.busy":"2026-01-12T03:24:44.524679Z","iopub.status.idle":"2026-01-12T03:24:52.603735Z","shell.execute_reply":"2026-01-12T03:24:52.602803Z"},"papermill":{"duration":8.090963,"end_time":"2026-01-12T03:24:52.605186","exception":false,"start_time":"2026-01-12T03:24:44.514223","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(f'{DATA_ROOT}/train.csv')\ntrain_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:24:52.653073Z","iopub.status.busy":"2026-01-12T03:24:52.652813Z","iopub.status.idle":"2026-01-12T03:24:52.699114Z","shell.execute_reply":"2026-01-12T03:24:52.698425Z"},"papermill":{"duration":0.058409,"end_time":"2026-01-12T03:24:52.700262","exception":false,"start_time":"2026-01-12T03:24:52.641853","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df[['sample_id_prefix', 'sample_id_suffix']] = train_df.sample_id.str.split('__', expand=True)","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:24:52.721710Z","iopub.status.busy":"2026-01-12T03:24:52.721403Z","iopub.status.idle":"2026-01-12T03:24:52.732794Z","shell.execute_reply":"2026-01-12T03:24:52.731967Z"},"papermill":{"duration":0.023491,"end_time":"2026-01-12T03:24:52.734075","exception":false,"start_time":"2026-01-12T03:24:52.710584","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"(train_df.sample_id_suffix == train_df.target_name).all()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:24:52.755497Z","iopub.status.busy":"2026-01-12T03:24:52.755201Z","iopub.status.idle":"2026-01-12T03:24:52.761067Z","shell.execute_reply":"2026-01-12T03:24:52.760261Z"},"papermill":{"duration":0.018222,"end_time":"2026-01-12T03:24:52.762377","exception":false,"start_time":"2026-01-12T03:24:52.744155","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id_prefix', 'image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nagg_train_df = train_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_train_df.reset_index(inplace=True)\nagg_train_df.columns.name = None\n\nagg_train_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:24:52.785178Z","iopub.status.busy":"2026-01-12T03:24:52.784368Z","iopub.status.idle":"2026-01-12T03:25:13.310650Z","shell.execute_reply":"2026-01-12T03:25:13.309867Z"},"papermill":{"duration":20.538258,"end_time":"2026-01-12T03:25:13.311768","exception":false,"start_time":"2026-01-12T03:24:52.773510","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.isclose(\n    agg_train_df[['Dry_Green_g', 'Dry_Clover_g']].sum(axis=1),\n    agg_train_df['GDM_g'],\n    atol=1e-04\n).mean()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:13.366692Z","iopub.status.busy":"2026-01-12T03:25:13.366402Z","iopub.status.idle":"2026-01-12T03:25:13.373762Z","shell.execute_reply":"2026-01-12T03:25:13.373042Z"},"papermill":{"duration":0.019824,"end_time":"2026-01-12T03:25:13.374868","exception":false,"start_time":"2026-01-12T03:25:13.355044","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.isclose(\n    agg_train_df[['GDM_g', 'Dry_Dead_g']].sum(axis=1),\n    agg_train_df['Dry_Total_g'],\n    atol=1e-04\n).mean()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:13.397004Z","iopub.status.busy":"2026-01-12T03:25:13.396752Z","iopub.status.idle":"2026-01-12T03:25:13.403538Z","shell.execute_reply":"2026-01-12T03:25:13.402998Z"},"papermill":{"duration":0.019215,"end_time":"2026-01-12T03:25:13.404710","exception":false,"start_time":"2026-01-12T03:25:13.385495","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(16, 4))\nplt.subplot(1, 3, 1)\nagg_train_df.Dry_Green_g.plot(kind='hist')\n_ = plt.title('Dry_Green_g')\n\nplt.subplot(1, 3, 2)\nagg_train_df.Dry_Clover_g.plot(kind='hist')\n_ = plt.title('Dry_Clover_g')\n\nplt.subplot(1, 3, 3)\nagg_train_df.Dry_Dead_g.plot(kind='hist')\n_ = plt.title('Dry_Dead_g')","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:13.426654Z","iopub.status.busy":"2026-01-12T03:25:13.426358Z","iopub.status.idle":"2026-01-12T03:25:14.049334Z","shell.execute_reply":"2026-01-12T03:25:14.048505Z"},"papermill":{"duration":0.635399,"end_time":"2026-01-12T03:25:14.050527","exception":false,"start_time":"2026-01-12T03:25:13.415128","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 📊 Target-Stratified Fold Assignment\nStratify by Dry_Total_g quantiles while grouping by date","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedGroupKFold\n\nNFOLD = 5\n\n# Create target bins for stratification\nagg_train_df['target_bin'] = pd.qcut(\n    agg_train_df['Dry_Total_g'], q=5, labels=False, duplicates='drop'\n)\n\nkfold = StratifiedGroupKFold(n_splits=NFOLD, shuffle=True, random_state=42)\nagg_train_df['fold'] = -1\n\nfor i, (trn_idx, val_idx) in enumerate(\n    kfold.split(\n        agg_train_df.index,\n        y=agg_train_df['target_bin'],\n        groups=agg_train_df['Sampling_Date']\n    )\n):\n    agg_train_df.loc[val_idx, 'fold'] = i\n\n# Verification\nprint(\"✅ Fold assignment complete\")\nprint(f\"📊 Fold sizes: {agg_train_df.groupby('fold').size().to_dict()}\")\nprint(f\"📊 Target mean by fold:\\n{agg_train_df.groupby('fold')['Dry_Total_g'].mean().round(1)}\")","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:14.075298Z","iopub.status.busy":"2026-01-12T03:25:14.074773Z","iopub.status.idle":"2026-01-12T03:25:14.107797Z","shell.execute_reply":"2026-01-12T03:25:14.106861Z"},"papermill":{"duration":0.046432,"end_time":"2026-01-12T03:25:14.109052","exception":false,"start_time":"2026-01-12T03:25:14.062620","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DataLoader","metadata":{"papermill":{"duration":0.011113,"end_time":"2026-01-12T03:25:14.131643","exception":false,"start_time":"2026-01-12T03:25:14.120530","status":"completed"},"tags":[]}},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:14.154905Z","iopub.status.busy":"2026-01-12T03:25:14.154629Z","iopub.status.idle":"2026-01-12T03:25:20.780795Z","shell.execute_reply":"2026-01-12T03:25:20.780067Z"},"papermill":{"duration":6.639831,"end_time":"2026-01-12T03:25:20.782282","exception":false,"start_time":"2026-01-12T03:25:14.142451","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clean_image(img):\n    # 1. Crop bottom 10% where artifacts often appear\n    h, w = img.shape[:2]\n    img = img[0:int(h*0.90), :]\n    \n    # 2. Remove orange date stamps using inpainting\n    hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)\n    lower = np.array([5, 150, 150])\n    upper = np.array([25, 255, 255])\n    mask = cv2.inRange(hsv, lower, upper)\n    mask = cv2.dilate(mask, np.ones((3,3), np.uint8), iterations=2)\n    if np.sum(mask) > 0:\n        img = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)\n    return img","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, data_root=None):\n        self.data = data\n        self.transform = transform\n        self.data_root = data_root\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        \n        # Load image on-demand instead of from DataFrame\n        image = Image.open(self.data_root + item.image_path).convert('RGB')\n        image = clean_image(np.array(image))  # Convert to numpy array for processing\n        image = Image.fromarray(image)  # Convert back to PIL Image\n\n        if self.transform:\n            image = self.transform(image)\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        return image, targets\n\n\ndef create_dataloader(data, target_image_size=(512, 512), batch_size=32, shuffle=True, aug=True):       \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomRotation(degrees=90, interpolation=Image.BILINEAR),\n            transforms.ColorJitter(brightness=0.2, contrast=0.15, saturation=0.15, hue=0.08),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n\n    dataset = RegressionDataset(data, transform=transform, data_root=DATA_ROOT)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(\n        dataset, \n        batch_size=batch_size, \n        shuffle=shuffle, \n        num_workers=2,  # CHANGED: 4 → 2\n        pin_memory=True \n    )\n    return dataloader","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:20.806754Z","iopub.status.busy":"2026-01-12T03:25:20.806041Z","iopub.status.idle":"2026-01-12T03:25:20.813716Z","shell.execute_reply":"2026-01-12T03:25:20.812964Z"},"papermill":{"duration":0.020852,"end_time":"2026-01-12T03:25:20.814921","exception":false,"start_time":"2026-01-12T03:25:20.794069","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_tta_transforms(target_image_size=(512, 512)):\n    \"\"\"\n    Returns a list of transform pipelines for TTA.\n    Each pipeline creates a different augmented view at inference time.\n    \"\"\"\n    normalize = [\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ]\n    \n    base = [transforms.Resize(target_image_size)]\n    \n    tta_list = []\n    \n    # View 1: Original (no augmentation)\n    tta_list.append(transforms.Compose(base + normalize))\n    \n    # View 2: Horizontal flip\n    tta_list.append(transforms.Compose(base + [\n        transforms.RandomHorizontalFlip(p=1.0)\n    ] + normalize))\n    \n    # View 3: Vertical flip\n    tta_list.append(transforms.Compose(base + [\n        transforms.RandomVerticalFlip(p=1.0)\n    ] + normalize))\n    \n    # View 4: Both flips\n    tta_list.append(transforms.Compose(base + [\n        transforms.RandomHorizontalFlip(p=1.0),\n        transforms.RandomVerticalFlip(p=1.0)\n    ] + normalize))\n\n    # View 5: 90° rotation\n    tta_list.append(transforms.Compose(base + [\n    transforms.RandomRotation(degrees=(90, 90), interpolation=Image.BILINEAR)\n    ] + normalize))\n    \n    return tta_list\n\n\ndef create_tta_dataloaders(data, target_image_size=(512, 512), batch_size=32, shuffle=True, aug=True):\n    \"\"\"\n    Creates multiple dataloaders for TTA - one for each augmented view.\n    Returns a list of dataloaders.\n    \"\"\"\n    tta_transforms = get_tta_transforms(target_image_size)\n    dataloaders = []\n    \n    for i, transform in enumerate(tta_transforms):\n        dataset = RegressionDataset(data, transform=transform, data_root=DATA_ROOT) \n        loader = DataLoader(\n            dataset, \n            batch_size=batch_size, \n            shuffle=False, \n            num_workers=2,  # CHANGED: 4 → 2\n            pin_memory=True  \n        )    \n        dataloaders.append(loader)\n        print(f'TTA dataloader {i+1}/{len(tta_transforms)}: {len(dataset)} samples')\n    \n    return dataloaders","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:20.838954Z","iopub.status.busy":"2026-01-12T03:25:20.838273Z","iopub.status.idle":"2026-01-12T03:25:20.845286Z","shell.execute_reply":"2026-01-12T03:25:20.844587Z"},"papermill":{"duration":0.020065,"end_time":"2026-01-12T03:25:20.846373","exception":false,"start_time":"2026-01-12T03:25:20.826308","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{"papermill":{"duration":0.010876,"end_time":"2026-01-12T03:25:20.868360","exception":false,"start_time":"2026-01-12T03:25:20.857484","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import timm","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:20.892707Z","iopub.status.busy":"2026-01-12T03:25:20.892014Z","iopub.status.idle":"2026-01-12T03:25:25.501249Z","shell.execute_reply":"2026-01-12T03:25:25.500614Z"},"papermill":{"duration":4.623154,"end_time":"2026-01-12T03:25:25.502647","exception":false,"start_time":"2026-01-12T03:25:20.879493","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = timm.create_model(MODEL_NAME, pretrained=True, num_classes=3)","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:25.555847Z","iopub.status.busy":"2026-01-12T03:25:25.555174Z","iopub.status.idle":"2026-01-12T03:25:29.145107Z","shell.execute_reply":"2026-01-12T03:25:29.144387Z"},"papermill":{"duration":3.60437,"end_time":"2026-01-12T03:25:29.146677","exception":false,"start_time":"2026-01-12T03:25:25.542307","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.pretrained_cfg","metadata":{"collapsed":true,"execution":{"iopub.execute_input":"2026-01-12T03:25:29.171489Z","iopub.status.busy":"2026-01-12T03:25:29.170861Z","iopub.status.idle":"2026-01-12T03:25:29.176013Z","shell.execute_reply":"2026-01-12T03:25:29.175411Z"},"jupyter":{"outputs_hidden":true},"papermill":{"duration":0.018663,"end_time":"2026-01-12T03:25:29.177115","exception":false,"start_time":"2026-01-12T03:25:29.158452","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train","metadata":{"papermill":{"duration":0.014739,"end_time":"2026-01-12T03:25:29.236054","exception":false,"start_time":"2026-01-12T03:25:29.221315","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import AdamW","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.270225Z","iopub.status.busy":"2026-01-12T03:25:29.269971Z","iopub.status.idle":"2026-01-12T03:25:29.274293Z","shell.execute_reply":"2026-01-12T03:25:29.273479Z"},"papermill":{"duration":0.023189,"end_time":"2026-01-12T03:25:29.275599","exception":false,"start_time":"2026-01-12T03:25:29.252410","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_checkpoint_safe(path):\n    \"\"\"Load checkpoint with proper device handling\"\"\"\n    try:\n        return torch.load(path, map_location=\"cpu\", weights_only=False)\n    except TypeError:  # Older PyTorch versions don't have weights_only\n        return torch.load(path, map_location=\"cpu\")","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.306223Z","iopub.status.busy":"2026-01-12T03:25:29.305480Z","iopub.status.idle":"2026-01-12T03:25:29.309496Z","shell.execute_reply":"2026-01-12T03:25:29.308893Z"},"papermill":{"duration":0.019497,"end_time":"2026-01-12T03:25:29.310670","exception":false,"start_time":"2026-01-12T03:25:29.291173","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Weighted R² ========\ndef weighted_r2_score(y_true: np.ndarray, y_pred: np.ndarray):\n    \"\"\"\n    y_true, y_pred: shape (N, 5): Green/Clover/Dead/GDM/Total\n    \"\"\"\n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    r2_scores = []\n    for i in range(5):\n        y_t = y_true[:, i]\n        y_p = y_pred[:, i]\n        ss_res = np.sum((y_t - y_p) ** 2)\n        ss_tot = np.sum((y_t - np.mean(y_t)) ** 2)\n        r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0.0\n        r2_scores.append(r2)\n    r2_scores = np.array(r2_scores)\n    weighted_r2 = np.sum(r2_scores * weights) / np.sum(weights)\n    return weighted_r2, r2_scores","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.335659Z","iopub.status.busy":"2026-01-12T03:25:29.335363Z","iopub.status.idle":"2026-01-12T03:25:29.340486Z","shell.execute_reply":"2026-01-12T03:25:29.339882Z"},"papermill":{"duration":0.018827,"end_time":"2026-01-12T03:25:29.341612","exception":false,"start_time":"2026-01-12T03:25:29.322785","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calc_metric(outputs, targets):\n    '''\n        outputs/targets: shape (N, 3): Green/Clover/Dead\n    '''\n    y_true = np.column_stack((\n        targets,\n        targets[:, :2].sum(axis=1),\n        targets.sum(axis=1),\n    ))\n    \n    y_pred = np.column_stack((\n        outputs,\n        outputs[:, :2].sum(axis=1),\n        outputs.sum(axis=1),\n    ))\n    \n    weighted_r2, r2_scores = weighted_r2_score(y_true, y_pred)\n    return weighted_r2, r2_scores","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.367189Z","iopub.status.busy":"2026-01-12T03:25:29.366909Z","iopub.status.idle":"2026-01-12T03:25:29.371758Z","shell.execute_reply":"2026-01-12T03:25:29.370920Z"},"papermill":{"duration":0.019139,"end_time":"2026-01-12T03:25:29.373102","exception":false,"start_time":"2026-01-12T03:25:29.353963","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(model, dataloader, criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n    scaler = torch.cuda.amp.GradScaler()  # NEW: Mixed precision scaler\n\n    for images, targets in dataloader:\n        images = images.to(device)\n        targets = torch.stack(targets).T.float().to(device)\n\n        optimizer.zero_grad()\n        \n        # NEW: Use autocast for FP16\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n            loss = criterion(outputs, targets)\n        \n        # NEW: Scale loss for FP16\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        total_loss += loss.item()\n\n    return total_loss / len(dataloader)\n\n\ndef validate(model, dataloader, criterion, device):\n    model.eval()\n    total_loss = 0\n    all_outputs = []\n    all_targets = []\n\n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = images.to(device)\n            targets = torch.stack(targets).T.float().to(device)\n\n            # NEW: Use autocast for FP16\n            with torch.cuda.amp.autocast():\n                outputs = model(images)\n                loss = criterion(outputs, targets)\n            \n            total_loss += loss.item()\n\n            all_outputs.append(outputs.detach().cpu())\n            all_targets.append(targets.detach().cpu())\n\n    outputs = torch.cat(all_outputs).numpy()\n    targets = torch.cat(all_targets).numpy()\n        \n    # Calculate competition metric using utility\n    weighted_r2, r2_scores = calc_metric(outputs, targets) \n    \n    return total_loss / len(dataloader), weighted_r2, r2_scores","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.398898Z","iopub.status.busy":"2026-01-12T03:25:29.398005Z","iopub.status.idle":"2026-01-12T03:25:29.405680Z","shell.execute_reply":"2026-01-12T03:25:29.404867Z"},"papermill":{"duration":0.022184,"end_time":"2026-01-12T03:25:29.406934","exception":false,"start_time":"2026-01-12T03:25:29.384750","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_fold(data, fold):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    # Hyperparameters\n    batch_size = 32\n    lr = 5e-4\n    patience = 10\n    num_epochs = 5 if DEBUG else 100\n\n    # model, loss, optimizer\n    model = timm.create_model(MODEL_NAME, pretrained=True, num_classes=3)\n    model.to(device)\n\n    criterion = nn.SmoothL1Loss()\n    optimizer = AdamW(model.parameters(), lr=lr)\n    \n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n        optimizer, mode='max', factor=0.5, patience=patience//2\n    )\n    \n    # data\n    train_loader = create_dataloader(data[data.fold != fold], TARGET_IMAGE_SIZE, batch_size, shuffle=True, aug=True)\n    val_loader = create_dataloader(data[data.fold == fold], TARGET_IMAGE_SIZE, batch_size, shuffle=False, aug=False)\n\n    # Training loop\n    history = []\n    best_score = -float('inf')\n    epochs_without_improvement = 0\n    \n    for epoch in range(num_epochs):\n        train_loss = train_epoch(model, train_loader, criterion, optimizer, device)\n        val_loss, weighted_r2, r2_scores = validate(model, val_loader, criterion, device)\n        \n        scheduler.step(weighted_r2)  # Update learning rate based on validation score\n\n        if epoch % 10 == 0:\n            print(f\"Epoch [{epoch}/{num_epochs}]: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, weighted_r2: Early stopping {weighted_r2:.4f}, lr: {optimizer.param_groups[0]['lr']}\")\n        \n        history.append({\n            'train_loss': train_loss,\n            'val_loss': val_loss,\n            'weighted_r2': weighted_r2,\n            'r2_scores': r2_scores,\n        })\n        \n        # Early stopping\n        if weighted_r2 > best_score:\n            best_score = weighted_r2\n            epochs_without_improvement = 0\n            # Save best model checkpoint\n            torch.save(model.state_dict(), f'{OUTPUT_DIR}/best_model_fold{fold}.pth')\n        \n        else:\n            epochs_without_improvement += 1\n        \n        if epochs_without_improvement >= patience:\n            print(f\"Epoch [{epoch+1}/{num_epochs}]: \"\n                  f\"Train Loss: {train_loss:.4f}, \"\n                  f\"Val Loss: {val_loss:.4f}, \"\n                  f\"weighted_r2: {weighted_r2:.4f}, \"\n                  f\"lr: {optimizer.param_groups[0]['lr']:.2e}\")\n            break\n\n    print(f\"\\nTraining completed. Best weighted_r2: {best_score:.4f}\")\n    \n    return history, best_score","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.432062Z","iopub.status.busy":"2026-01-12T03:25:29.431736Z","iopub.status.idle":"2026-01-12T03:25:29.439956Z","shell.execute_reply":"2026-01-12T03:25:29.439191Z"},"papermill":{"duration":0.022351,"end_time":"2026-01-12T03:25:29.441111","exception":false,"start_time":"2026-01-12T03:25:29.418760","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clear_gpu_memory():\n    \"\"\"Clear GPU memory between folds to prevent OOM errors\"\"\"\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n    import gc\n    gc.collect()\n    print(\"✅ GPU memory cleared\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    all_best_score = []\n\n    for i in range(2 if DEBUG else NFOLD):\n        print(f'\\n{\"=\"*60}')\n        print(f'Training Fold {i}/{NFOLD}')\n        print(f'{\"=\"*60}')\n        history, best_score = train_fold(agg_train_df, fold=i)\n        all_best_score.append(best_score)\n        clear_gpu_memory() \n        history = pd.DataFrame(history)\n\n        history.to_json(\n            f'{OUTPUT_DIR}/history_fold{i}.jsonl',\n            orient='records',\n            lines=True,\n            force_ascii=False,\n        )\n            \n        clear_gpu_memory()\n\n        # plot\n        plt.figure(figsize=(12, 4))\n\n        plt.subplot(1, 2, 1)\n        plt.title('Loss')\n        plt.plot(history.train_loss, label='train')\n        plt.plot(history.val_loss, label='val')\n        plt.legend()\n\n        plt.subplot(1, 2, 2)\n        plt.title('weighted_r2')\n        plt.plot(history.weighted_r2)\n        plt.show()\n        plt.tight_layout()\n        plt.savefig(f'{OUTPUT_DIR}/training_curves_fold{i}.png')\n        plt.close()\n\n    print('Per-fold scores:', [f'{s:.4f}' for s in all_best_score])\n    print('Avg CV:', np.mean(all_best_score))","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:25:29.495530Z","iopub.status.busy":"2026-01-12T03:25:29.495262Z","iopub.status.idle":"2026-01-12T03:49:25.064967Z","shell.execute_reply":"2026-01-12T03:49:25.064048Z"},"papermill":{"duration":1435.583686,"end_time":"2026-01-12T03:49:25.066445","exception":false,"start_time":"2026-01-12T03:25:29.482759","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"clear_gpu_memory()  # Clear memory after each fold","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{"papermill":{"duration":0.017995,"end_time":"2026-01-12T03:49:25.103114","exception":false,"start_time":"2026-01-12T03:49:25.085119","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nfrom pathlib import Path","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.140930Z","iopub.status.busy":"2026-01-12T03:49:25.140600Z","iopub.status.idle":"2026-01-12T03:49:25.144808Z","shell.execute_reply":"2026-01-12T03:49:25.144010Z"},"papermill":{"duration":0.024868,"end_time":"2026-01-12T03:49:25.146025","exception":false,"start_time":"2026-01-12T03:49:25.121157","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_lastest_saved_models():\n    model_root = '/kaggle/input/simple-gungahlin-grass-guessing-output/pytorch/default/'\n    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.184143Z","iopub.status.busy":"2026-01-12T03:49:25.183870Z","iopub.status.idle":"2026-01-12T03:49:25.188074Z","shell.execute_reply":"2026-01-12T03:49:25.187510Z"},"papermill":{"duration":0.024704,"end_time":"2026-01-12T03:49:25.189140","exception":false,"start_time":"2026-01-12T03:49:25.164436","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SAVED_MODELS = './trained_models/' if TRAIN else get_lastest_saved_models()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.227344Z","iopub.status.busy":"2026-01-12T03:49:25.227066Z","iopub.status.idle":"2026-01-12T03:49:25.230907Z","shell.execute_reply":"2026-01-12T03:49:25.230282Z"},"papermill":{"duration":0.024382,"end_time":"2026-01-12T03:49:25.232038","exception":false,"start_time":"2026-01-12T03:49:25.207656","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = images.to(device)\n    \n            outputs = model(images)\n            all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.270022Z","iopub.status.busy":"2026-01-12T03:49:25.269751Z","iopub.status.idle":"2026-01-12T03:49:25.274379Z","shell.execute_reply":"2026-01-12T03:49:25.273807Z"},"papermill":{"duration":0.024914,"end_time":"2026-01-12T03:49:25.275396","exception":false,"start_time":"2026-01-12T03:49:25.250482","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_with_tta(model, tta_dataloaders, device):\n    \"\"\"\n    Run prediction on multiple TTA views and average results.\n    \n    Args:\n        model: trained model\n        tta_dataloaders: list of dataloaders (one per TTA view)\n        device: cuda or cpu\n    \n    Returns:\n        averaged predictions across all TTA views\n    \"\"\"\n    model.to(device)\n    model.eval()\n    \n    all_tta_predictions = []\n    \n    # Predict on each TTA view\n    for i, dataloader in enumerate(tta_dataloaders):\n        view_outputs = []\n        with torch.no_grad():\n            for images, targets in dataloader:\n                images = images.to(device)\n                \n                # NEW: Use autocast for FP16\n                with torch.cuda.amp.autocast():\n                    outputs = model(images)\n                \n                view_outputs.append(outputs.detach().cpu())\n        \n        view_preds = torch.cat(view_outputs).numpy()\n        all_tta_predictions.append(view_preds)\n        print(f'  TTA view {i+1}/{len(tta_dataloaders)}: shape {view_preds.shape}')\n    \n    # Average across all TTA views\n    averaged_preds = np.mean(all_tta_predictions, axis=0)\n    print(f'  Final averaged shape: {averaged_preds.shape}')\n    \n    return averaged_preds","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.313247Z","iopub.status.busy":"2026-01-12T03:49:25.312978Z","iopub.status.idle":"2026-01-12T03:49:25.318392Z","shell.execute_reply":"2026-01-12T03:49:25.317789Z"},"papermill":{"duration":0.02578,"end_time":"2026-01-12T03:49:25.319500","exception":false,"start_time":"2026-01-12T03:49:25.293720","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def kfold_predict(dataloader_or_tta_loaders, use_tta=False):\n    \"\"\"\n    Predict using K-fold ensemble, optionally with TTA.\n    \n    Args:\n        dataloader_or_tta_loaders: single dataloader OR list of TTA dataloaders\n        use_tta: if True, expects list of dataloaders and uses TTA\n    \n    Returns:\n        averaged predictions across all folds (and TTA views if enabled)\n    \"\"\"\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    model_files = list(Path(SAVED_MODELS).glob('*.pth'))\n    print(f'Found {len(model_files)} fold models')\n    \n    for model_file in model_files:\n        print(f'\\nLoading: {model_file.name}')\n        model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)\n        \n        # Load checkpoint safely\n        state = load_checkpoint_safe(model_file)\n        model.load_state_dict(state)\n        model.to(device)\n        model.eval()\n\n        # Choose prediction method\n        if use_tta:\n            print('  Using TTA (4 views):')\n            preds = predict_with_tta(model, dataloader_or_tta_loaders, device)\n        else:\n            print('  Using single view (no TTA)')\n            preds = predict(model, dataloader_or_tta_loaders, device)\n        \n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    print(f'\\n✓ K-fold ensemble complete. Final shape: {avg_preds.shape}')\n    return avg_preds","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.359907Z","iopub.status.busy":"2026-01-12T03:49:25.359175Z","iopub.status.idle":"2026-01-12T03:49:25.365636Z","shell.execute_reply":"2026-01-12T03:49:25.364906Z"},"papermill":{"duration":0.028627,"end_time":"2026-01-12T03:49:25.366764","exception":false,"start_time":"2026-01-12T03:49:25.338137","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(DATA_ROOT + 'test.csv')\n\ntest_df['target'] = 0.0\ntest_df[['sample_id_prefix', 'sample_id_suffix']] = test_df.sample_id.str.split('__', expand=True)","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.405355Z","iopub.status.busy":"2026-01-12T03:49:25.404785Z","iopub.status.idle":"2026-01-12T03:49:25.423974Z","shell.execute_reply":"2026-01-12T03:49:25.423223Z"},"papermill":{"duration":0.039988,"end_time":"2026-01-12T03:49:25.425288","exception":false,"start_time":"2026-01-12T03:49:25.385300","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id_prefix', 'image_path']\nagg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_test_df.reset_index(inplace=True)\nagg_test_df.columns.name = None\n\nagg_test_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.464161Z","iopub.status.busy":"2026-01-12T03:49:25.463461Z","iopub.status.idle":"2026-01-12T03:49:25.554654Z","shell.execute_reply":"2026-01-12T03:49:25.553908Z"},"papermill":{"duration":0.111541,"end_time":"2026-01-12T03:49:25.555798","exception":false,"start_time":"2026-01-12T03:49:25.444257","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TTA Configuration\nUSE_TTA = True  # Set to False to disable TTA\n\nif USE_TTA:\n    print('📊 Creating TTA dataloaders (4 augmented views)...\\\\n')\n    test_data = create_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, batch_size=32)\n    print(f'\\\\n✓ Created {len(test_data)} TTA dataloaders')\nelse:\n    print('📊 Creating single test dataloader (no TTA)...')\n    test_data = create_dataloader(agg_test_df, TARGET_IMAGE_SIZE, 10, shuffle=False, aug=False)\n    print('✓ Created 1 test dataloader')","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.594233Z","iopub.status.busy":"2026-01-12T03:49:25.593954Z","iopub.status.idle":"2026-01-12T03:49:25.599854Z","shell.execute_reply":"2026-01-12T03:49:25.599058Z"},"papermill":{"duration":0.026332,"end_time":"2026-01-12T03:49:25.600949","exception":false,"start_time":"2026-01-12T03:49:25.574617","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = kfold_predict(test_data, use_tta=USE_TTA)","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:25.639612Z","iopub.status.busy":"2026-01-12T03:49:25.638889Z","iopub.status.idle":"2026-01-12T03:49:35.589183Z","shell.execute_reply":"2026-01-12T03:49:35.588251Z"},"papermill":{"duration":9.970736,"end_time":"2026-01-12T03:49:35.590390","exception":false,"start_time":"2026-01-12T03:49:25.619654","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def enforce_biomass_constraints(df):\n    \"\"\"\n    Enforce physical laws:\n    - All values >= 0 (no negative biomass)\n    - GDM = Green + Clover (exact)\n    - Total = GDM + Dead (exact)\n    \"\"\"\n    # No negative biomass\n    df['Dry_Green_g'] = df['Dry_Green_g'].clip(lower=0)\n    df['Dry_Clover_g'] = df['Dry_Clover_g'].clip(lower=0)\n    df['Dry_Dead_g'] = df['Dry_Dead_g'].clip(lower=0)\n    \n    # Force mathematical relationships\n    df['GDM_g'] = df['Dry_Green_g'] + df['Dry_Clover_g']\n    df['Dry_Total_g'] = df['GDM_g'] + df['Dry_Dead_g']\n    \n    return df","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:35.632359Z","iopub.status.busy":"2026-01-12T03:49:35.631383Z","iopub.status.idle":"2026-01-12T03:49:35.636634Z","shell.execute_reply":"2026-01-12T03:49:35.636073Z"},"papermill":{"duration":0.02742,"end_time":"2026-01-12T03:49:35.637769","exception":false,"start_time":"2026-01-12T03:49:35.610349","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# SUBMISSION VALIDATION\ndef validate_submission_format(submission_df, test_df):\n    \"\"\"Validate that submission has correct format and all required rows\"\"\"\n    # Check columns\n    required_columns = ['sample_id', 'target']\n    if not all(col in submission_df.columns for col in required_columns):\n        raise ValueError(f\"Submission must contain columns: {required_columns}\")\n    \n    # Check number of rows (5 targets per image)\n    expected_rows = len(test_df['sample_id_prefix'].unique()) * 5\n    if len(submission_df) != expected_rows:\n        raise ValueError(f\"Submission has {len(submission_df)} rows but expected {expected_rows}\")\n    \n    # Check for negative values\n    negative_count = (submission_df['target'] < 0).sum()\n    if negative_count > 0:\n        print(f\"⚠️ Warning: {negative_count} negative predictions found. Clamping to zero.\")\n        submission_df['target'] = submission_df['target'].clip(lower=0)\n    \n    print(\"✅ Submission format validation passed\")\n    return submission_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df = enforce_biomass_constraints(agg_test_df)","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:35.678686Z","iopub.status.busy":"2026-01-12T03:49:35.678116Z","iopub.status.idle":"2026-01-12T03:49:35.687173Z","shell.execute_reply":"2026-01-12T03:49:35.686357Z"},"papermill":{"duration":0.030672,"end_time":"2026-01-12T03:49:35.688332","exception":false,"start_time":"2026-01-12T03:49:35.657660","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_test_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:35.729512Z","iopub.status.busy":"2026-01-12T03:49:35.728954Z","iopub.status.idle":"2026-01-12T03:49:35.739350Z","shell.execute_reply":"2026-01-12T03:49:35.738598Z"},"papermill":{"duration":0.032442,"end_time":"2026-01-12T03:49:35.740663","exception":false,"start_time":"2026-01-12T03:49:35.708221","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name","metadata":{"execution":{"iopub.execute_input":"2026-01-12T03:49:35.782832Z","iopub.status.busy":"2026-01-12T03:49:35.782231Z","iopub.status.idle":"2026-01-12T03:49:35.805436Z","shell.execute_reply":"2026-01-12T03:49:35.804609Z"},"papermill":{"duration":0.044947,"end_time":"2026-01-12T03:49:35.806862","exception":false,"start_time":"2026-01-12T03:49:35.761915","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id', 'target']\nsub_df[cols].to_csv('submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 📦 Config for Inference Notebook","metadata":{}},{"cell_type":"code","source":"import json\n\nconfig = {\n    'model_name': MODEL_NAME,\n    'img_size': TARGET_IMAGE_SIZE,\n    'num_classes': 3\n}\n\nwith open(f'{OUTPUT_DIR}/model_config.json', 'w') as f:\n    json.dump(config, f, indent=2)\n\nprint(f\"✅ Saved: {OUTPUT_DIR}/model_config.json\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}