Files
mrdbourke--pytorch-deep-lea…/video_notebooks/09_pytorch_model_deployment_video.ipynb
wehub-resource-sync 37c25cd088
make_docs / deploy (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:27:58 +08:00

7410 lines
430 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "09_pytorch_model_deployment_video.ipynb",
"provenance": [],
"collapsed_sections": [],
"authorship_tag": "ABX9TyNt2DZ7BeVYNvz3f66FFjqh",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU",
"gpuClass": "standard",
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"ba35f3c656cb4abf9670e65877e71ff2": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_f19b3440b18b4784a7ed5c9105f7aa2a",
"IPY_MODEL_a52d79024ad440f5bb49f6bebec1dbd1",
"IPY_MODEL_f9ca832be7ac4fc1b4bb02ca7669934d"
],
"layout": "IPY_MODEL_36dfc8d462314c7e89c7f2964de90424"
}
},
"f19b3440b18b4784a7ed5c9105f7aa2a": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_615ca5438a434148a8776c369791a0e1",
"placeholder": "",
"style": "IPY_MODEL_373a60cc71814cbd9bb8cd835e2f6c83",
"value": "100%"
}
},
"a52d79024ad440f5bb49f6bebec1dbd1": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_72f0224ee91e482fa88d9980b983d09f",
"max": 10,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_6ddc399f08e2468f9a775633ce809c8e",
"value": 10
}
},
"f9ca832be7ac4fc1b4bb02ca7669934d": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_e25bb7d9648c4448b70e487d01f5549b",
"placeholder": "",
"style": "IPY_MODEL_8b465175db4546598563e146b02477dd",
"value": " 10/10 [00:58<00:00, 5.82s/it]"
}
},
"36dfc8d462314c7e89c7f2964de90424": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"615ca5438a434148a8776c369791a0e1": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"373a60cc71814cbd9bb8cd835e2f6c83": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"72f0224ee91e482fa88d9980b983d09f": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"6ddc399f08e2468f9a775633ce809c8e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"e25bb7d9648c4448b70e487d01f5549b": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"8b465175db4546598563e146b02477dd": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"cb5027e48c7849d2a66300789b5b6db6": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_f5a0592da3a94970b8bc47b10282054b",
"IPY_MODEL_9d2e2f76d33d4d7cb85fab5848a8b495",
"IPY_MODEL_d212279b331648d2bce193c9cae0e25b"
],
"layout": "IPY_MODEL_c5060376c93048f79f70ee65cd08f3df"
}
},
"f5a0592da3a94970b8bc47b10282054b": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_fabf0282728d4dce9bb0949219d0a329",
"placeholder": "",
"style": "IPY_MODEL_511211588f62455a961d966390cff822",
"value": "100%"
}
},
"9d2e2f76d33d4d7cb85fab5848a8b495": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_e416f7063a2c413fb808910463499be4",
"max": 10,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_ede6ba36dbaa4a1687dad9052ee1e908",
"value": 10
}
},
"d212279b331648d2bce193c9cae0e25b": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_739d5b7498274710b8a336584cc4ef65",
"placeholder": "",
"style": "IPY_MODEL_2b5c1a40a25240a586179f54d14579df",
"value": " 10/10 [00:54<00:00, 5.47s/it]"
}
},
"c5060376c93048f79f70ee65cd08f3df": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"fabf0282728d4dce9bb0949219d0a329": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"511211588f62455a961d966390cff822": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"e416f7063a2c413fb808910463499be4": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"ede6ba36dbaa4a1687dad9052ee1e908": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"739d5b7498274710b8a336584cc4ef65": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"2b5c1a40a25240a586179f54d14579df": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"0d0b2fbe5b904839b575c60f007c9977": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_a5b08b956f794e99bf76fc5cc9679a2d",
"IPY_MODEL_144570d550b64d11958b2e497696b7cd",
"IPY_MODEL_a8b2cee8020a448a8b568a471148d981"
],
"layout": "IPY_MODEL_96017667844f4a67a6e670c66c2141ab"
}
},
"a5b08b956f794e99bf76fc5cc9679a2d": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_9a906e7dd2714589bd586e08565ff95b",
"placeholder": "",
"style": "IPY_MODEL_8df61ce127634d848c0054b2e0b72974",
"value": "100%"
}
},
"144570d550b64d11958b2e497696b7cd": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_35ba32b372f14557bdec85747f349318",
"max": 150,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_e4bd9761438f4734b674ebcc5e71fa08",
"value": 150
}
},
"a8b2cee8020a448a8b568a471148d981": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_d134831ffa9e446b85d36ec9ea0b2105",
"placeholder": "",
"style": "IPY_MODEL_cf83b2528f8146b0a1b5b34e4f2f53f3",
"value": " 150/150 [00:18<00:00, 8.26it/s]"
}
},
"96017667844f4a67a6e670c66c2141ab": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"9a906e7dd2714589bd586e08565ff95b": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"8df61ce127634d848c0054b2e0b72974": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"35ba32b372f14557bdec85747f349318": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"e4bd9761438f4734b674ebcc5e71fa08": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"d134831ffa9e446b85d36ec9ea0b2105": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"cf83b2528f8146b0a1b5b34e4f2f53f3": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"dae44612c46644c8b496a7f9bfa6c3c2": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_48486a66fd844a9290ec49866c134b3c",
"IPY_MODEL_3983c92304d04553a213755aac582367",
"IPY_MODEL_4b95cf3fb5f34ef98a83ef1fe14d9db6"
],
"layout": "IPY_MODEL_65db50a782564d31b1d0f81224adfd56"
}
},
"48486a66fd844a9290ec49866c134b3c": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_aa5ad051ad4b4d1299e417bbfd15581b",
"placeholder": "",
"style": "IPY_MODEL_e5023f3772b14e1ca6f31a683cc9849e",
"value": "100%"
}
},
"3983c92304d04553a213755aac582367": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_55b52e781a074d27b054eefe594e06c8",
"max": 150,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_aa902acee91d41219628f85319b4902e",
"value": 150
}
},
"4b95cf3fb5f34ef98a83ef1fe14d9db6": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_95bfc6e17e634d2c831d79aef7ec545d",
"placeholder": "",
"style": "IPY_MODEL_b3ba57b373e140988c34219c07bed764",
"value": " 150/150 [01:23<00:00, 1.63it/s]"
}
},
"65db50a782564d31b1d0f81224adfd56": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"aa5ad051ad4b4d1299e417bbfd15581b": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"e5023f3772b14e1ca6f31a683cc9849e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"55b52e781a074d27b054eefe594e06c8": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"aa902acee91d41219628f85319b4902e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"95bfc6e17e634d2c831d79aef7ec545d": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"b3ba57b373e140988c34219c07bed764": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"426981b3072b4c7fa208a8399767228b": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_951d9f290e9e4a868dc6dfa8c3fc4231",
"IPY_MODEL_0944a26e41904457bef7974d2ef6ebce",
"IPY_MODEL_823b3960411d480388e45d8f77b253fe"
],
"layout": "IPY_MODEL_3b9dbcf7693e4eba99519aac54d9f341"
}
},
"951d9f290e9e4a868dc6dfa8c3fc4231": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_1c9dd9a9f4d14a13b8f9682464025075",
"placeholder": "",
"style": "IPY_MODEL_f783356d1f9b45c08b0a68116dfe69c4",
"value": "100%"
}
},
"0944a26e41904457bef7974d2ef6ebce": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_a060c3ffbfcb4f2980e80ca28be510d6",
"max": 4996278331,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_0dc3801c92fc43dd85630375830052a6",
"value": 4996278331
}
},
"823b3960411d480388e45d8f77b253fe": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_a710d9b0975549a98a6d15b9e866ca9b",
"placeholder": "",
"style": "IPY_MODEL_69f6eeb1718a44de90f273dcfb5af75d",
"value": " 4996278331/4996278331 [05:45<00:00, 15902445.83it/s]"
}
},
"3b9dbcf7693e4eba99519aac54d9f341": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"1c9dd9a9f4d14a13b8f9682464025075": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"f783356d1f9b45c08b0a68116dfe69c4": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"a060c3ffbfcb4f2980e80ca28be510d6": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"0dc3801c92fc43dd85630375830052a6": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"a710d9b0975549a98a6d15b9e866ca9b": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"69f6eeb1718a44de90f273dcfb5af75d": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"5a41e92879364ffdbe4a4b84b2a815b4": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_f7267b534abd43ab84c70c74b644a06b",
"IPY_MODEL_f11aeabb239a4e5aa8c1847d7437b60f",
"IPY_MODEL_572281cc888f48c7afd2461426b0321e"
],
"layout": "IPY_MODEL_245ab0553fb445ec8d7961a218c97a39"
}
},
"f7267b534abd43ab84c70c74b644a06b": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_2b5471c1519b461aa31736b9d24d43d2",
"placeholder": "",
"style": "IPY_MODEL_c98453092a49405780dec8a65b79e6fb",
"value": "100%"
}
},
"f11aeabb239a4e5aa8c1847d7437b60f": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_bb8a6f3b1d7443b28be1571e43755493",
"max": 5,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_cc9109d69c8544e18a9dac3fa8f4da22",
"value": 5
}
},
"572281cc888f48c7afd2461426b0321e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"model_module_version": "1.5.0",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_0aabb802874547beaa385aa0ee024fbd",
"placeholder": "",
"style": "IPY_MODEL_bf88ce31ca9041e9910921e1c6fe84cf",
"value": " 5/5 [18:10<00:00, 210.93s/it]"
}
},
"245ab0553fb445ec8d7961a218c97a39": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"2b5471c1519b461aa31736b9d24d43d2": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"c98453092a49405780dec8a65b79e6fb": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"bb8a6f3b1d7443b28be1571e43755493": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"cc9109d69c8544e18a9dac3fa8f4da22": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": ""
}
},
"0aabb802874547beaa385aa0ee024fbd": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"model_module_version": "1.2.0",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": 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,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"bf88ce31ca9041e9910921e1c6fe84cf": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"model_module_version": "1.5.0",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
}
}
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/mrdbourke/pytorch-deep-learning/blob/main/videos/09_pytorch_model_deployment_video.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"# 09. PyTorch Model Deployment\n",
"\n",
"What is model deployment?\n",
"\n",
"Machine learning model deployment is the act of making your machine learning model(s) available to someone or something else.\n",
"\n",
"## Resources: \n",
"\n",
"* Book version of notebook: https://www.learnpytorch.io/09_pytorch_model_deployment/ \n",
"* Slides: https://github.com/mrdbourke/pytorch-deep-learning/blob/main/slides/09_pytorch_model_deployment.pdf "
],
"metadata": {
"id": "fztGSYtzaZw7"
}
},
{
"cell_type": "markdown",
"source": [
"## 0. Get setup"
],
"metadata": {
"id": "b6EmBsFjausC"
}
},
{
"cell_type": "code",
"source": [
"# For this notebook to run with updated APIs, we need torch 1.12+ and torchvision 0.13+\n",
"try:\n",
" import torch\n",
" import torchvision\n",
" assert int(torch.__version__.split(\".\")[1]) >= 12, \"torch version should be 1.12+\"\n",
" assert int(torchvision.__version__.split(\".\")[1]) >= 13, \"torchvision version should be 0.13+\"\n",
" print(f\"torch version: {torch.__version__}\")\n",
" print(f\"torchvision version: {torchvision.__version__}\")\n",
"except:\n",
" print(f\"[INFO] torch/torchvision versions not as required, installing nightly versions.\")\n",
" !pip3 install -U torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113\n",
" import torch\n",
" import torchvision\n",
" print(f\"torch version: {torch.__version__}\")\n",
" print(f\"torchvision version: {torchvision.__version__}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "9YpSiECUbEe0",
"outputId": "f55c04b5-046a-4d09-a463-69400c39a035"
},
"execution_count": 73,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"torch version: 1.12.1+cu113\n",
"torchvision version: 0.13.1+cu113\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Continue with regular imports\n",
"import matplotlib.pyplot as plt\n",
"import torch\n",
"import torchvision\n",
"\n",
"from torch import nn\n",
"from torchvision import transforms\n",
"\n",
"# Try to get torchinfo, install it if it doesn't work\n",
"try:\n",
" from torchinfo import summary\n",
"except:\n",
" print(\"[INFO] Couldn't find torchinfo... installing it.\")\n",
" !pip install -q torchinfo\n",
" from torchinfo import summary\n",
"\n",
"# Try to import the going_modular directory, download it from GitHub if it doesn't work\n",
"try:\n",
" from going_modular.going_modular import data_setup, engine\n",
" from helper_functions import download_data, set_seeds, plot_loss_curves\n",
"except:\n",
" # Get the going_modular scripts\n",
" print(\"[INFO] Couldn't find going_modular or helper_functions scripts... downloading them from GitHub.\")\n",
" !git clone https://github.com/mrdbourke/pytorch-deep-learning\n",
" !mv pytorch-deep-learning/going_modular .\n",
" !mv pytorch-deep-learning/helper_functions.py . # get the helper_functions.py script\n",
" !rm -rf pytorch-deep-learning\n",
" from going_modular.going_modular import data_setup, engine\n",
" from helper_functions import download_data, set_seeds, plot_loss_curves"
],
"metadata": {
"id": "ivpKKpO6bOsJ"
},
"execution_count": 74,
"outputs": []
},
{
"cell_type": "code",
"source": [
"!ls going_modular/going_modular"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "RffrxihTbg7q",
"outputId": "7907382f-b4c9-451b-df64-f7fd9df4c8b8"
},
"execution_count": 75,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"data_setup.py model_builder.py __pycache__ train.py\n",
"engine.py predictions.py\t README.md utils.py\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
"device "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 36
},
"id": "YYDOXbsVbp0r",
"outputId": "5b06ef73-c499-409f-9eb6-6649804c5b15"
},
"execution_count": 76,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"'cuda'"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "string"
}
},
"metadata": {},
"execution_count": 76
}
]
},
{
"cell_type": "markdown",
"source": [
"## 1. Getting Data\n",
"\n",
"The dataset we're going to use for deploying a FoodVision Mini model is...\n",
"\n",
"Pizza, steak, sushi 20% dataset (pizza, steak, sushi classes from Food101, random 20% of samples)\n",
"\n",
"We can get data with code from: https://www.learnpytorch.io/09_pytorch_model_deployment/#1-getting-data"
],
"metadata": {
"id": "Hr_pgSE2a2Bu"
}
},
{
"cell_type": "code",
"source": [
"# Download pizza, steak, sushi images from GitHub\n",
"data_20_percent_path = download_data(source=\"https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi_20_percent.zip\",\n",
" destination=\"pizza_steak_sushi_20_percent\")\n",
"\n",
"data_20_percent_path"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "egGp5nNTbD7M",
"outputId": "f5c80b42-ca15-47ff-a323-72fe44c39011"
},
"execution_count": 77,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] data/pizza_steak_sushi_20_percent directory exists, skipping download.\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"PosixPath('data/pizza_steak_sushi_20_percent')"
]
},
"metadata": {},
"execution_count": 77
}
]
},
{
"cell_type": "code",
"source": [
"# Setup training and test paths\n",
"train_dir = data_20_percent_path / \"train\"\n",
"test_dir = data_20_percent_path / \"test\"\n",
"\n",
"train_dir, test_dir"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Tjaa2VP3cpaO",
"outputId": "399f28be-91f4-43b8-9a3c-b1992933c686"
},
"execution_count": 78,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(PosixPath('data/pizza_steak_sushi_20_percent/train'),\n",
" PosixPath('data/pizza_steak_sushi_20_percent/test'))"
]
},
"metadata": {},
"execution_count": 78
}
]
},
{
"cell_type": "markdown",
"source": [
"## 2. FoodVision Mini model deployment experiment outline\n",
"\n",
"### 3 questions\n",
"1. What is my most ideal machine learning model deployment scenario?\n",
"2. Where is my model going to go?\n",
"3. How is my model going to function?\n",
"\n",
"**FoodVision Mini ideal use case:** A model that performs well and fast.\n",
"\n",
"1. Performs well: 95%+ accuracy\n",
"2. Fast: as close to real-time (or faster) as possible (30FPS+ or 30ms latency) \n",
" * Latency = time for prediction to take place \n",
"\n",
"To try and achieve these goals, we're going to build two model experiments:\n",
"\n",
"1. EffNetB2 feature extractor (just like in 07. PyTorch Experiment Tracking)\n",
"2. ViT feature extractor (just like in 08. PyTorch Paper Replicating)\n",
"\n",
"\n"
],
"metadata": {
"id": "3Da8tLPjczI0"
}
},
{
"cell_type": "markdown",
"source": [
"## 3. Creating an EffNetB2 feature extractor\n",
"\n",
"Feautre extractor = a term for a transfer learning model that has its base layers frozen and output layers (or head layers) customized to a certain problem.\n",
"\n",
"EffNetB2 pretrained model in PyTorch - https://pytorch.org/vision/stable/models/generated/torchvision.models.efficientnet_b2.html#torchvision.models.EfficientNet_B2_Weights "
],
"metadata": {
"id": "mmG_WB-Fd71H"
}
},
{
"cell_type": "code",
"source": [
"import torchvision\n",
"\n",
"# 1. Setup pretrained EffNetB2 weights\n",
"effnetb2_weights = torchvision.models.EfficientNet_B2_Weights.DEFAULT # \"DEFAULT\" is equivalent to saying \"best available\"\n",
"\n",
"# 2. Get EffNetB2 transforms\n",
"effnetb2_transforms = effnetb2_weights.transforms()\n",
"\n",
"# 3. Setup pretrained model instance\n",
"effnetb2 = torchvision.models.efficientnet_b2(weights=effnetb2_weights) # could also use weights=\"DEFAULT\"\n",
"\n",
"# 4. Freeze the base layers in the model (this will stop all layers from training)\n",
"for param in effnetb2.parameters():\n",
" param.requires_grad = False"
],
"metadata": {
"id": "HeROOhN-fXwf"
},
"execution_count": 79,
"outputs": []
},
{
"cell_type": "code",
"source": [
"from torchinfo import summary\n",
"\n",
"# # Print EffNetB2 model summary (uncomment for full output) \n",
"# summary(effnetb2, \n",
"# input_size=(1, 3, 224, 224),\n",
"# col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n",
"# col_width=20,\n",
"# row_settings=[\"var_names\"])"
],
"metadata": {
"id": "eoLgDG7ff1VL"
},
"execution_count": 80,
"outputs": []
},
{
"cell_type": "code",
"source": [
"effnetb2.classifier"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "SV7rA-QSgvPd",
"outputId": "f8626cd1-6674-4652-95dc-35f8ba680fb2"
},
"execution_count": 81,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Sequential(\n",
" (0): Dropout(p=0.3, inplace=True)\n",
" (1): Linear(in_features=1408, out_features=1000, bias=True)\n",
")"
]
},
"metadata": {},
"execution_count": 81
}
]
},
{
"cell_type": "code",
"source": [
"# Set seeds for reproducibility\n",
"set_seeds()\n",
"effnetb2.classifier = nn.Sequential(\n",
" nn.Dropout(p=0.3, inplace=True),\n",
" nn.Linear(in_features=1408, out_features=3, bias=True))"
],
"metadata": {
"id": "0Twn8RLehBZS"
},
"execution_count": 82,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# from torchinfo import summary\n",
"\n",
"# # Print EffNetB2 model summary (uncomment for full output) \n",
"# summary(effnetb2, \n",
"# input_size=(1, 3, 224, 224),\n",
"# col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n",
"# col_width=20,\n",
"# row_settings=[\"var_names\"])"
],
"metadata": {
"id": "4gDqFBFkhQb0"
},
"execution_count": 83,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### 3.1 Creating a function to make an EffNetB2 feature extractor"
],
"metadata": {
"id": "uEl8EQeYhS2H"
}
},
{
"cell_type": "code",
"source": [
"def create_effnetb2_model(num_classes:int=3, # default output classes = 3 (pizza, steak, sushi)\n",
" seed:int=42):\n",
" # 1, 2, 3 Create EffNetB2 pretrained weights, transforms and model\n",
" weights = torchvision.models.EfficientNet_B2_Weights.DEFAULT\n",
" transforms = weights.transforms()\n",
" model = torchvision.models.efficientnet_b2(weights=weights)\n",
"\n",
" # 4. Freeze all layers in the base model\n",
" for param in model.parameters():\n",
" param.requires_grad = False\n",
"\n",
" # 5. Change classifier head with random seed for reproducibility\n",
" torch.manual_seed(seed)\n",
" model.classifier = nn.Sequential(\n",
" nn.Dropout(p=0.3, inplace=True),\n",
" nn.Linear(in_features=1408, out_features=num_classes)\n",
" )\n",
"\n",
" return model, transforms"
],
"metadata": {
"id": "xRUzPCQZh_4A"
},
"execution_count": 84,
"outputs": []
},
{
"cell_type": "code",
"source": [
"effnetb2, effnetb2_transforms = create_effnetb2_model(num_classes=3,\n",
" seed=42)"
],
"metadata": {
"id": "1XuwSyTFi2GG"
},
"execution_count": 85,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# from torchinfo import summary\n",
"\n",
"# # Print EffNetB2 model summary (uncomment for full output) \n",
"# summary(effnetb2, \n",
"# input_size=(1, 3, 288, 288),\n",
"# col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n",
"# col_width=20,\n",
"# row_settings=[\"var_names\"])"
],
"metadata": {
"id": "ZmNK6oYmi_Vt"
},
"execution_count": 86,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### 3.2 Creating DataLoaders for EffNetB2"
],
"metadata": {
"id": "DHLMR5LsjAvW"
}
},
{
"cell_type": "code",
"source": [
"# Setup DataLoaders\n",
"from going_modular.going_modular import data_setup\n",
"\n",
"train_dataloader_effnetb2, test_dataloader_effnetb2, class_names = data_setup.create_dataloaders(train_dir=train_dir,\n",
" test_dir=test_dir,\n",
" transform=effnetb2_transforms,\n",
" batch_size=32)"
],
"metadata": {
"id": "5lUtcYVKjuY-"
},
"execution_count": 87,
"outputs": []
},
{
"cell_type": "code",
"source": [
"len(train_dataloader_effnetb2), len(test_dataloader_effnetb2), class_names"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "dKpZ2LdEj--i",
"outputId": "3de9a2b6-82b3-4e59-a86d-373a071f1eab"
},
"execution_count": 88,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(15, 5, ['pizza', 'steak', 'sushi'])"
]
},
"metadata": {},
"execution_count": 88
}
]
},
{
"cell_type": "markdown",
"source": [
"### 3.3 Training EffNetB2 feature extractor "
],
"metadata": {
"id": "ZaFplq8ikEPp"
}
},
{
"cell_type": "code",
"source": [
"from going_modular.going_modular import engine\n",
"\n",
"# Loss function\n",
"loss_fn = torch.nn.CrossEntropyLoss()\n",
"\n",
"# Optimizer\n",
"optimizer = torch.optim.Adam(params=effnetb2.parameters(),\n",
" lr=1e-3)\n",
"\n",
"# Training function (engine.py)\n",
"set_seeds()\n",
"effnetb2_results = engine.train(model=effnetb2,\n",
" train_dataloader=train_dataloader_effnetb2,\n",
" test_dataloader=test_dataloader_effnetb2,\n",
" epochs=10,\n",
" optimizer=optimizer,\n",
" loss_fn=loss_fn, \n",
" device=device)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 227,
"referenced_widgets": [
"ba35f3c656cb4abf9670e65877e71ff2",
"f19b3440b18b4784a7ed5c9105f7aa2a",
"a52d79024ad440f5bb49f6bebec1dbd1",
"f9ca832be7ac4fc1b4bb02ca7669934d",
"36dfc8d462314c7e89c7f2964de90424",
"615ca5438a434148a8776c369791a0e1",
"373a60cc71814cbd9bb8cd835e2f6c83",
"72f0224ee91e482fa88d9980b983d09f",
"6ddc399f08e2468f9a775633ce809c8e",
"e25bb7d9648c4448b70e487d01f5549b",
"8b465175db4546598563e146b02477dd"
]
},
"id": "NCcK0I8SkP30",
"outputId": "1e46dec4-c18e-49b3-b7f6-2dc59d644143"
},
"execution_count": 89,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/10 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "ba35f3c656cb4abf9670e65877e71ff2"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch: 1 | train_loss: 0.9856 | train_acc: 0.5604 | test_loss: 0.7408 | test_acc: 0.9347\n",
"Epoch: 2 | train_loss: 0.7175 | train_acc: 0.8438 | test_loss: 0.5869 | test_acc: 0.9409\n",
"Epoch: 3 | train_loss: 0.5876 | train_acc: 0.8917 | test_loss: 0.4909 | test_acc: 0.9500\n",
"Epoch: 4 | train_loss: 0.4474 | train_acc: 0.9062 | test_loss: 0.4355 | test_acc: 0.9409\n",
"Epoch: 5 | train_loss: 0.4290 | train_acc: 0.9104 | test_loss: 0.3915 | test_acc: 0.9443\n",
"Epoch: 6 | train_loss: 0.4381 | train_acc: 0.8896 | test_loss: 0.3512 | test_acc: 0.9688\n",
"Epoch: 7 | train_loss: 0.4245 | train_acc: 0.8771 | test_loss: 0.3268 | test_acc: 0.9563\n",
"Epoch: 8 | train_loss: 0.3897 | train_acc: 0.8958 | test_loss: 0.3457 | test_acc: 0.9381\n",
"Epoch: 9 | train_loss: 0.3749 | train_acc: 0.8812 | test_loss: 0.3129 | test_acc: 0.9131\n",
"Epoch: 10 | train_loss: 0.3757 | train_acc: 0.8604 | test_loss: 0.2813 | test_acc: 0.9688\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 3.4 Inspecting EffNetB2 loss curves"
],
"metadata": {
"id": "xs6U1XTLlrbo"
}
},
{
"cell_type": "code",
"source": [
"from helper_functions import plot_loss_curves\n",
"\n",
"plot_loss_curves(effnetb2_results)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 458
},
"id": "H5JEnNIImAgg",
"outputId": "2d624191-5386-4770-b6ee-b92ae814f976"
},
"execution_count": 90,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x504 with 2 Axes>"
],
"image/png": "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IiL+p1bb81trp1lrk621yfHx8bV5ahERr3Xw4EFefPHFar9v1KhRHDx4sNrvGz9+PB9++GG13yciIiK1pyaC2i6gRaXnzT2viYhIFZwuqJWWlp7xfV999RUNGza8UGWJiIiIi2qiPf/nwH3GmPeAfsAha+2eGjiuiEite/zf61i/+3CNHrNz0wb84aoup90+ZcoUNm/eTI8ePQgODiYsLIzo6GjS0tLYuHEj1157LTt37qSwsJBf/OIXTJ48GYBWrVqRmppKfn4+I0eOZODAgSxatIhmzZrx2WefUa9evbPW9u233/Lggw9SWlpKnz59eOmllwgNDWXKlCl8/vnnBAUFccUVV/D000/zwQcf8PjjjxMYGEhUVBQLFiyosZ+RiIiInOisQc0Y8y4wFIgzxmQCfwCCAay1LwNfAaOADKAAmHChihUR8UdPPfUUa9euZeXKlcybN4+f/exnrF279thaZNOnTycmJoajR4/Sp08fbrjhBmJjY084xqZNm3j33Xd55ZVXuPnmm/noo48YO3bsGc9bWFjI+PHj+fbbb+nQoQO33347L730EuPGjeOTTz4hLS0NY8yx6ZVTp05l9uzZNGvW7JymXIqIiEjVnTWoWWtvPct2C9xbYxWJiLjoTCNftaVv374nLBj93HPP8cknnwCwc+dONm3a9JOg1rp1a3r06AFA79692bZt21nPk56eTuvWrenQoQMAd9xxBy+88AL33XcfYWFhTJw4kdGjRzN69GgABgwYwPjx47n55pu5/vrra+JbFRERkdOo1WYiNamkrJxl2w+4XYaISI2LiIg49vW8efP45ptv+OGHH1i1ahU9e/Y85YLSoaGhx74ODAw86/1tZxIUFMSSJUu48cYb+eKLLxgxwllK8+WXX+aJJ55g586d9O7dm/3795/zOURELpjiAjiif5/kAstcBiU/vR7XJJ8Nam8t3s4NLy1iZ26B26WIiJyXyMhI8vLyTrnt0KFDREdHEx4eTlpaGosXL66x83bs2JFt27aRkZEBwMyZMxkyZAj5+fkcOnSIUaNG8be//Y1Vq1YBsHnzZvr168fUqVOJj49n586dZzq8iEjtKi2CxS/Bs13hL23g5YHw9aOwZb6zTaSm7FsHb1wFsx++oKepiWYirhjSwWnvn5Kexe39W7lbjIjIeYiNjWXAgAF07dqVevXq0ahRo2Pb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\n"
},
"metadata": {
"needs_background": "light"
}
}
]
},
{
"cell_type": "markdown",
"source": [
"See here for what an ideal loss curve should look like: https://www.learnpytorch.io/04_pytorch_custom_datasets/#8-what-should-an-ideal-loss-curve-look-like "
],
"metadata": {
"id": "MOxZSC94meBP"
}
},
{
"cell_type": "markdown",
"source": [
"### 3.5 Saving EffNetB2 feature extractor"
],
"metadata": {
"id": "z2NoT7L_mV_X"
}
},
{
"cell_type": "code",
"source": [
"from going_modular.going_modular import utils\n",
"\n",
"# Save the model\n",
"utils.save_model(model=effnetb2,\n",
" target_dir=\"models\",\n",
" model_name=\"09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "JTME7_YSo5CY",
"outputId": "89b33d6d-d416-4545-c3c4-cb3faa27de4b"
},
"execution_count": 91,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Saving model to: models/09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 3.6 Inspecting the size of our EffNetB2 feature extractor\n",
"\n",
"Why would it be important to consider the size of a saved model?\n",
"\n",
"If we're deploying our model to be used on a mobile app/website, there may be limited compute resources.\n",
"\n",
"So if our model file is too large, we may not be able to store/run it on our target device."
],
"metadata": {
"id": "huZdf9QFpJ7R"
}
},
{
"cell_type": "code",
"source": [
"from pathlib import Path\n",
"\n",
"# Get the model size in bytes and convert to megabytes\n",
"pretrained_effnetb2_model_size = Path(\"models/09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\").stat().st_size / (1024 * 1024)\n",
"print(f\"Pretrained EffNetB2 feature extractor model size: {round(pretrained_effnetb2_model_size, 2)} MB\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "jPo2jgrBpZFw",
"outputId": "2065f260-324f-4c28-d0e9-264c1927ccd8"
},
"execution_count": 92,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Pretrained EffNetB2 feature extractor model size: 29.82 MB\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 3.7 Collecting EffNetB2 feature extractor stats"
],
"metadata": {
"id": "4p7eY0Z-qg3b"
}
},
{
"cell_type": "code",
"source": [
"# Count number of parameters in EffNetB2\n",
"effnetb2_total_params = sum(torch.numel(param) for param in effnetb2.parameters())\n",
"effnetb2_total_params"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "fDfHVCMWrUIf",
"outputId": "cc6d7fa8-a5e6-4ed0-acbd-0e37a6ebace4"
},
"execution_count": 93,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"7705221"
]
},
"metadata": {},
"execution_count": 93
}
]
},
{
"cell_type": "code",
"source": [
"# Create a dictionary with EffNetB2 statistics\n",
"effnetb2_stats = {\"test_loss\": effnetb2_results[\"test_loss\"][-1],\n",
" \"test_acc\": effnetb2_results[\"test_acc\"][-1],\n",
" \"number_of_parameters\": effnetb2_total_params,\n",
" \"model_size (MB)\": pretrained_effnetb2_model_size}\n",
"\n",
"effnetb2_stats"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "sMQ_axUzrup3",
"outputId": "b5e45dc1-dcae-48e6-c4d1-c5c02e0ce001"
},
"execution_count": 94,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"{'test_loss': 0.28128677904605864,\n",
" 'test_acc': 0.96875,\n",
" 'number_of_parameters': 7705221,\n",
" 'model_size (MB)': 29.824288368225098}"
]
},
"metadata": {},
"execution_count": 94
}
]
},
{
"cell_type": "markdown",
"source": [
"## 4. Creating a ViT feature extractor\n",
"\n",
"We're up to our second modelling experiment, repeating the steps for EffNetB2 but this time with a ViT feature extractor, see here for ideas: https://www.learnpytorch.io/08_pytorch_paper_replicating/#10-using-a-pretrained-vit-from-torchvisionmodels-on-the-same-dataset"
],
"metadata": {
"id": "0lckiZT6sPQm"
}
},
{
"cell_type": "code",
"source": [
"# Check out the ViT heads layer\n",
"vit = torchvision.models.vit_b_16()\n",
"vit.heads"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Ww-6Q_tisrFH",
"outputId": "79aebfa9-fe9b-4db5-b4aa-98478418b7e7"
},
"execution_count": 95,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Sequential(\n",
" (head): Linear(in_features=768, out_features=1000, bias=True)\n",
")"
]
},
"metadata": {},
"execution_count": 95
}
]
},
{
"cell_type": "code",
"source": [
"def create_vit_model(num_classes:int=3,\n",
" seed:int=42):\n",
" # Create ViT_B_16 pretrained weights, transforms and model\n",
" weights = torchvision.models.ViT_B_16_Weights.DEFAULT\n",
" transforms = weights.transforms()\n",
" model = torchvision.models.vit_b_16(weights=weights)\n",
"\n",
" # Freeze all of the base layers\n",
" for param in model.parameters():\n",
" param.requires_grad = False\n",
"\n",
" # Change classifier head to suit our needs\n",
" torch.manual_seed(seed)\n",
" model.heads = nn.Sequential(nn.Linear(in_features=768, \n",
" out_features=num_classes))\n",
" \n",
" return model, transforms"
],
"metadata": {
"id": "HB2FxBD5Pn6d"
},
"execution_count": 96,
"outputs": []
},
{
"cell_type": "code",
"source": [
"vit, vit_transforms = create_vit_model()\n",
"vit_transforms"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "0UkRL23GPpwN",
"outputId": "f8ae1dc3-e7d8-4da1-d67b-ee6385f1b4d8"
},
"execution_count": 97,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"ImageClassification(\n",
" crop_size=[224]\n",
" resize_size=[256]\n",
" mean=[0.485, 0.456, 0.406]\n",
" std=[0.229, 0.224, 0.225]\n",
" interpolation=InterpolationMode.BILINEAR\n",
")"
]
},
"metadata": {},
"execution_count": 97
}
]
},
{
"cell_type": "code",
"source": [
"# from torchinfo import summary\n",
"\n",
"# # Print ViT model summary (uncomment for full output) \n",
"# summary(vit, \n",
"# input_size=(1, 3, 224, 224),\n",
"# col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n",
"# col_width=20,\n",
"# row_settings=[\"var_names\"])"
],
"metadata": {
"id": "zi7PJ9ARQjRB"
},
"execution_count": 98,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### 4.1 Create DataLoaders for ViT feature extractor"
],
"metadata": {
"id": "HCC68E6RQsO3"
}
},
{
"cell_type": "code",
"source": [
"# Setup ViT DataLoaders \n",
"from going_modular.going_modular import data_setup\n",
"train_dataloader_vit, test_dataloader_vit, class_names = data_setup.create_dataloaders(train_dir=train_dir,\n",
" test_dir=test_dir,\n",
" transform=vit_transforms,\n",
" batch_size=32)\n",
"len(train_dataloader_vit), len(test_dataloader_vit), class_names"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "s6O5f5EjQ6Py",
"outputId": "cd51337d-cf7f-4bc3-f826-4d582dac7d8a"
},
"execution_count": 99,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(15, 5, ['pizza', 'steak', 'sushi'])"
]
},
"metadata": {},
"execution_count": 99
}
]
},
{
"cell_type": "markdown",
"source": [
"### 4.2 Training ViT Feature Extractor\n",
"\n",
"We're up to model experiment number two: a ViT feature extractor."
],
"metadata": {
"id": "VGZilSlTRj9j"
}
},
{
"cell_type": "code",
"source": [
"from going_modular.going_modular import engine\n",
"\n",
"# Setup optimizer \n",
"optimizer = torch.optim.Adam(params=vit.parameters(),\n",
" lr=1e-3)\n",
"\n",
"# Setup loss function\n",
"loss_fn = torch.nn.CrossEntropyLoss()\n",
"\n",
"# Train ViT feature extractor with seeds set for reproducibility\n",
"set_seeds()\n",
"vit_results = engine.train(model=vit,\n",
" train_dataloader=train_dataloader_vit,\n",
" test_dataloader=test_dataloader_vit,\n",
" epochs=10,\n",
" optimizer=optimizer,\n",
" loss_fn=loss_fn,\n",
" device=device)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 227,
"referenced_widgets": [
"cb5027e48c7849d2a66300789b5b6db6",
"f5a0592da3a94970b8bc47b10282054b",
"9d2e2f76d33d4d7cb85fab5848a8b495",
"d212279b331648d2bce193c9cae0e25b",
"c5060376c93048f79f70ee65cd08f3df",
"fabf0282728d4dce9bb0949219d0a329",
"511211588f62455a961d966390cff822",
"e416f7063a2c413fb808910463499be4",
"ede6ba36dbaa4a1687dad9052ee1e908",
"739d5b7498274710b8a336584cc4ef65",
"2b5c1a40a25240a586179f54d14579df"
]
},
"id": "UsO1FtiTRtzg",
"outputId": "9f5b5539-a646-445b-ede8-b2f054f81988"
},
"execution_count": 100,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/10 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "cb5027e48c7849d2a66300789b5b6db6"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch: 1 | train_loss: 0.7023 | train_acc: 0.7500 | test_loss: 0.2714 | test_acc: 0.9290\n",
"Epoch: 2 | train_loss: 0.2531 | train_acc: 0.9104 | test_loss: 0.1669 | test_acc: 0.9602\n",
"Epoch: 3 | train_loss: 0.1766 | train_acc: 0.9542 | test_loss: 0.1270 | test_acc: 0.9693\n",
"Epoch: 4 | train_loss: 0.1277 | train_acc: 0.9625 | test_loss: 0.1072 | test_acc: 0.9722\n",
"Epoch: 5 | train_loss: 0.1163 | train_acc: 0.9646 | test_loss: 0.0950 | test_acc: 0.9784\n",
"Epoch: 6 | train_loss: 0.1270 | train_acc: 0.9375 | test_loss: 0.0830 | test_acc: 0.9722\n",
"Epoch: 7 | train_loss: 0.0899 | train_acc: 0.9771 | test_loss: 0.0844 | test_acc: 0.9784\n",
"Epoch: 8 | train_loss: 0.0928 | train_acc: 0.9812 | test_loss: 0.0759 | test_acc: 0.9722\n",
"Epoch: 9 | train_loss: 0.0933 | train_acc: 0.9792 | test_loss: 0.0729 | test_acc: 0.9784\n",
"Epoch: 10 | train_loss: 0.0662 | train_acc: 0.9833 | test_loss: 0.0642 | test_acc: 0.9847\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 4.3 Plot loss curves of ViT feature extractor"
],
"metadata": {
"id": "EuPMeMMlS3NE"
}
},
{
"cell_type": "code",
"source": [
"from helper_functions import plot_loss_curves\n",
"\n",
"plot_loss_curves(vit_results)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 458
},
"id": "LPpkHiuaS9lj",
"outputId": "abce7102-e6b4-430a-f3d5-a558f5d90bed"
},
"execution_count": 101,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x504 with 2 Axes>"
],
"image/png": "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YOarI8ybN4958+YxcqS/j5LX6yU/P5/jjjuOW2+9ldtuu42zzjqL4447rtnv+dprrzFz5kxqa2vZvHkzK1aswBhDr169GD16NADdunUD4MMPP+Taa69tWMLYs+fBuy5PmjSJ6OhoAGpqarjxxhtZunQpbreb1atXN7zvFVdc0bBEs/59r776ap599lkefPBBXn31VRYuXNjsP1dLKKiJiIgcgLW21hhzI/A+/vb8z1hrlxtj7gXyrLXvBE69GHjFWtt4Nmww8JQxpg5w4b9GbX9NSEREQpK1ljvuuINf/OIXP3puyZIlzJkzhzvvvJPx48dz9913H/T91qxZwwMPPMCiRYvo0aMHl19+OZWVlQd93b7CwsKoq/PvS7rv62Nj93TFfeihh0hNTeWbb76hrq6OqKgDN9A677zzmD59OieffDKjRo0iMTGxxbU1h9pBiYiIHIS1do61doC1Nsta+8fAsbsbhTSstfdYa2/f53VfWmuHWWuHB34+3dG1i4i0h/j4eEpL/R2ETzvtNJ555hm8Xi8AGzduxOPxsGnTJmJiYpgyZQq/+c1vGq7javzapuzevZvY2FgSEhLYsmUL7733HgADBw5k8+bNLFq0CIDS0lJqa2uZMGECTz31FLW1tQANSx/79evH4sWLAXjzzTf3+3m7du2iV69euFwuZs2ahc/nv8x4woQJPPvss5SXl+/1vlFRUZx22mlcd9117bbsERTURERERESkhRITExk3bhxDhw7lgw8+4NJLL+Xoo49m2LBhnH/++ZSWlvLdd98xZswYRowYwfTp07nzzjsBmDp1KhMnTuSkk05q8r2HDx/OyJEjGTRoEJdeeinjxo0DICIigldffZWbbrqJ4cOHM2HCBCorK7n66qvJyMjgiCOOYPjw4bz88ssA/OEPf2DatGnk5ubidu+/Mdb111/P888/z/Dhw1m1alXDbNvEiROZNGkSubm5jBgxggceeKDhNT/96U9xuVyceuqpbfJ9NsXsvUKj4+Tm5tq8vDxHPltERDqWMWaxtTbX6TpChcZIETmYlStXMniwGj455YEHHmDXrl3cd999zX5NU//MDjQ+6ho1ERERERGRZjr33HMpLCzk448/PvjJh0BBTUREREREHDF27Fiqqqr2OjZr1iyGDRvmUEUHN3v27A75HAU1ERERERFxxFdffeV0CUFLzURE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\n"
},
"metadata": {
"needs_background": "light"
}
}
]
},
{
"cell_type": "markdown",
"source": [
"For more on what an ideal loss curves should look like see here: https://www.learnpytorch.io/04_pytorch_custom_datasets/#8-what-should-an-ideal-loss-curve-look-like"
],
"metadata": {
"id": "9zqiyaBWTBqc"
}
},
{
"cell_type": "markdown",
"source": [
"### 4.4 Saving ViT feature extractor"
],
"metadata": {
"id": "1C8p7jY5TQ1D"
}
},
{
"cell_type": "code",
"source": [
"# Save model\n",
"from going_modular.going_modular import utils\n",
"\n",
"utils.save_model(model=vit,\n",
" target_dir=\"models\",\n",
" model_name=\"09_pretrained_vit_feature_extractor_pizza_steak_sushi_20_percent.pth\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "T5QAFc1bTopE",
"outputId": "b3d2edab-fd75-42fb-a647-f09c9f4fcaf3"
},
"execution_count": 102,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Saving model to: models/09_pretrained_vit_feature_extractor_pizza_steak_sushi_20_percent.pth\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 4.5 Checking the size of ViT feature extractor"
],
"metadata": {
"id": "mu0mdgcTT2yi"
}
},
{
"cell_type": "code",
"source": [
"from pathlib import Path\n",
"\n",
"# Get the model size in bytes then convert to megabytes\n",
"pretrained_vit_model_size = Path(\"models/09_pretrained_vit_feature_extractor_pizza_steak_sushi_20_percent.pth\").stat().st_size / (1024*1024)\n",
"print(f\"Pretrained ViT feature extractor model size: {pretrained_vit_model_size} MB\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "cjmHwt-iT9-5",
"outputId": "5d1dd2df-3322-43a2-fb1a-69504d4e1f8f"
},
"execution_count": 103,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Pretrained ViT feature extractor model size: 327.36128330230713 MB\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 4.6 Collecting ViT feature extractor stats"
],
"metadata": {
"id": "AbHS2zyXUL87"
}
},
{
"cell_type": "code",
"source": [
"# Count number of parameters in ViT \n",
"vit_total_params = sum(torch.numel(param) for param in vit.parameters())\n",
"vit_total_params"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "k7S3eAIXUV6U",
"outputId": "593fef39-b6a9-429b-898f-0b097713541d"
},
"execution_count": 104,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"85800963"
]
},
"metadata": {},
"execution_count": 104
}
]
},
{
"cell_type": "code",
"source": [
"# Create ViT statistics dictionary\n",
"vit_stats = {\"test_loss\": vit_results[\"test_loss\"][-1],\n",
" \"test_acc\": vit_results[\"test_acc\"][-1],\n",
" \"number_of_parameters\": vit_total_params,\n",
" \"model_size (MB)\": pretrained_vit_model_size}"
],
"metadata": {
"id": "AT-D4Q6zVN8X"
},
"execution_count": 105,
"outputs": []
},
{
"cell_type": "code",
"source": [
"vit_stats"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Ec3mJ3s0V1Yi",
"outputId": "4212a029-960d-4d53-97dd-7f2f0eae6e3c"
},
"execution_count": 106,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"{'test_loss': 0.06418212698772549,\n",
" 'test_acc': 0.984659090909091,\n",
" 'number_of_parameters': 85800963,\n",
" 'model_size (MB)': 327.36128330230713}"
]
},
"metadata": {},
"execution_count": 106
}
]
},
{
"cell_type": "markdown",
"source": [
"## 5. Making predictions with our trained models and timing them\n",
"\n",
"Our goal:\n",
"1. Performs well (95%+ test accuracy)\n",
"2. Fast (30+FPS)\n",
"\n",
"To test criteria two:\n",
"1. Loop through test images\n",
"2. Time how long each model takes to make a prediction on the image \n",
"\n",
"Let's work towards making a function called `pred_and_store()` to do so. \n",
"\n",
"First we'll need a list of test image paths. "
],
"metadata": {
"id": "0Ml5ZoB2V32u"
}
},
{
"cell_type": "code",
"source": [
"from pathlib import Path\n",
"\n",
"# Get all test data paths\n",
"test_data_paths = list(Path(test_dir).glob(\"*/*.jpg\"))\n",
"test_data_paths[:5]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "r_MNGQPsWGDe",
"outputId": "410de3e2-f8cf-4388-d189-8532ad5a88cd"
},
"execution_count": 107,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/1203702.jpg'),\n",
" PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/46797.jpg'),\n",
" PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/389730.jpg'),\n",
" PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/3227791.jpg'),\n",
" PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/1844723.jpg')]"
]
},
"metadata": {},
"execution_count": 107
}
]
},
{
"cell_type": "markdown",
"source": [
"### 5.1 Creating a function to make across the test dataset\n",
"\n",
"Steps to create `pred_and_store()`:\n",
"\n",
"1. Create a function that takes a list of paths and a trained PyTorch and a series of transforms a list of target class names and a target device.\n",
"2. Create an empty list (can return a full list of all predictions later).\n",
"3. Loop through the target input paths (the rest of the steps will take place inside the loop).\n",
"4. Create an empty dictionary for each sample (prediction statistics will go in here).\n",
"5. Get the sample path and ground truth class from the filepath.\n",
"6. Start the prediction timer. \n",
"7. Open the image using `PIL.Image.open(path)`.\n",
"8. Transform the image to be usable with a given model.\n",
"9. Prepare the model model for inference by sending to the target device and turning on `eval()` mode.\n",
"10. Turn on `torch.inference_mode()` and pass the target transformed image to the model and perform forward pass + calculate pred prob + pred class.\n",
"11. Add the pred prob + pred class to empty dictionary from step 4. \n",
"12. End the prediction timer started in step 6 and add the time to the prediction dictionary.\n",
"13. See if the predicted class matches the ground truth class.\n",
"14. Append the updated prediction dictionary to the empty list of predictions we created in step 2.\n",
"15. Return the list of prediction dictionaries."
],
"metadata": {
"id": "t0JHPvLWkudO"
}
},
{
"cell_type": "code",
"source": [
"import pathlib\n",
"import torch\n",
"\n",
"from PIL import Image\n",
"from timeit import default_timer as timer # https://docs.python.org/3/library/timeit.html#timeit.default_timer \n",
"from tqdm.auto import tqdm\n",
"from typing import List, Dict\n",
"\n",
"# 1. Create a function that takes a list of paths and a trained PyTorch and a series of transforms a list of target class names and a target device.\n",
"def pred_and_store(paths: List[pathlib.Path],\n",
" model: torch.nn.Module,\n",
" transform: torchvision.transforms,\n",
" class_names: List[str],\n",
" device: str = \"cuda\" if torch.cuda.is_available() else \"cpu\") -> List[Dict]:\n",
" \n",
" # 2. Create an empty list (can return a full list of all predictions later).\n",
" pred_list = []\n",
"\n",
" # 3. Loop through the target input paths (the rest of the steps will take place inside the loop).\n",
" for path in tqdm(paths):\n",
"\n",
" # 4. Create an empty dictionary for each sample (prediction statistics will go in here). \n",
" pred_dict = {}\n",
"\n",
" # 5. Get the sample path and ground truth class from the filepath.\n",
" pred_dict[\"image_path\"] = path\n",
" class_name = path.parent.stem\n",
" pred_dict[\"class_name\"] = class_name\n",
"\n",
" # 6. Start the prediction timer.\n",
" start_time = timer()\n",
"\n",
" # 7. Open the image using Image.open(path)\n",
" img = Image.open(path)\n",
"\n",
" # 8. Transform the image to be usable with a given model (also add a batch dimension and send to target device)\n",
" transformed_image = transform(img).unsqueeze(0).to(device)\n",
"\n",
" # 9. Prepare the model model for inference by sending to the target device and turning on eval() mode.\n",
" model = model.to(device)\n",
" model.eval()\n",
"\n",
" # 10. Turn on `torch.inference_mode()` and pass the target transformed image to the model and perform forward pass + calculate pred prob + pred class.\n",
" with torch.inference_mode():\n",
" pred_logit = model(transformed_image)\n",
" pred_prob = torch.softmax(pred_logit, dim=1) # turn logits into predicition probabilities\n",
" pred_label = torch.argmax(pred_prob, dim=1) # turn prediction probability into prediction label\n",
" pred_class = class_names[pred_label.cpu()] # hardcode prediction class to be on CPU (Python variables live on CPU)\n",
" \n",
" # 11. Add the pred prob + pred class to empty dictionary from step 4. \n",
" pred_dict[\"pred_prob\"] = round(pred_prob.unsqueeze(0).max().cpu().item(), 4)\n",
" pred_dict[\"pred_class\"] = pred_class\n",
"\n",
" # 12. End the prediction timer started in step 6 and add the time to the prediction dictionary.\n",
" end_time = timer()\n",
" pred_dict[\"time_for_pred\"] = round(end_time-start_time, 4)\n",
"\n",
" # 13. See if the predicted class matches the ground truth class. \n",
" pred_dict[\"correct\"] = class_name == pred_class\n",
"\n",
" # 14. Append the updated prediction dictionary to the empty list of predictions we created in step 2. \n",
" pred_list.append(pred_dict) \n",
"\n",
" # 15. Return the list of prediction dictionaries.\n",
" return pred_list"
],
"metadata": {
"id": "06yutpY0mlCm"
},
"execution_count": 108,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### 5.2 Making and timing predictions with EffNetB2\n",
"\n",
"Let's test our `pred_and_store()` function.\n",
"\n",
"Two things to note:\n",
"1. Device - we're going to hardcode our predictions to happen on CPU (because you won't always be sure of having a GPU when you deploy your model).\n",
"2. Transforms - we want to make sure each of the models are predicting on images that have been prepared with the appropriate transforms (e.g. EffNetB2 with `effnetb2_transforms`) \n"
],
"metadata": {
"id": "NVUy5XTRms5h"
}
},
{
"cell_type": "code",
"source": [
"# Make predictions test dataset with EffNetB2\n",
"effnetb2_test_pred_dicts = pred_and_store(paths=test_data_paths,\n",
" model=effnetb2,\n",
" transform=effnetb2_transforms,\n",
" class_names=class_names,\n",
" device=\"cpu\") # hardcode predictions to happen on CPU"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 49,
"referenced_widgets": [
"0d0b2fbe5b904839b575c60f007c9977",
"a5b08b956f794e99bf76fc5cc9679a2d",
"144570d550b64d11958b2e497696b7cd",
"a8b2cee8020a448a8b568a471148d981",
"96017667844f4a67a6e670c66c2141ab",
"9a906e7dd2714589bd586e08565ff95b",
"8df61ce127634d848c0054b2e0b72974",
"35ba32b372f14557bdec85747f349318",
"e4bd9761438f4734b674ebcc5e71fa08",
"d134831ffa9e446b85d36ec9ea0b2105",
"cf83b2528f8146b0a1b5b34e4f2f53f3"
]
},
"id": "sduFwTaomzTH",
"outputId": "b4b43263-f1ea-4523-ec0c-0698f5d18246"
},
"execution_count": 109,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/150 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "0d0b2fbe5b904839b575c60f007c9977"
}
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"effnetb2_test_pred_dicts[:2]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "YMgD-dmdsHXQ",
"outputId": "d08a607a-c980-4931-9758-6576eb4b587f"
},
"execution_count": 110,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[{'image_path': PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/1203702.jpg'),\n",
" 'class_name': 'sushi',\n",
" 'pred_prob': 0.8812,\n",
" 'pred_class': 'sushi',\n",
" 'time_for_pred': 0.1742,\n",
" 'correct': True},\n",
" {'image_path': PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/46797.jpg'),\n",
" 'class_name': 'sushi',\n",
" 'pred_prob': 0.9184,\n",
" 'pred_class': 'sushi',\n",
" 'time_for_pred': 0.1266,\n",
" 'correct': True}]"
]
},
"metadata": {},
"execution_count": 110
}
]
},
{
"cell_type": "code",
"source": [
"# Turn the test_pred_dicts into a DataFrame\n",
"import pandas as pd\n",
"effnetb2_test_pred_df = pd.DataFrame(effnetb2_test_pred_dicts)\n",
"effnetb2_test_pred_df.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"id": "6KoSTMaNsN8_",
"outputId": "8ffa9058-5cc7-4aaa-b3c4-386d4ec64150"
},
"execution_count": 111,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" image_path class_name pred_prob \\\n",
"0 data/pizza_steak_sushi_20_percent/test/sushi/1... sushi 0.8812 \n",
"1 data/pizza_steak_sushi_20_percent/test/sushi/4... sushi 0.9184 \n",
"2 data/pizza_steak_sushi_20_percent/test/sushi/3... sushi 0.6810 \n",
"3 data/pizza_steak_sushi_20_percent/test/sushi/3... sushi 0.8659 \n",
"4 data/pizza_steak_sushi_20_percent/test/sushi/1... sushi 0.8830 \n",
"\n",
" pred_class time_for_pred correct \n",
"0 sushi 0.1742 True \n",
"1 sushi 0.1266 True \n",
"2 sushi 0.1276 True \n",
"3 sushi 0.1258 True \n",
"4 sushi 0.1247 True "
],
"text/html": [
"\n",
" <div id=\"df-2fb8c555-7130-4f1b-a83b-feb81bc531a9\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>image_path</th>\n",
" <th>class_name</th>\n",
" <th>pred_prob</th>\n",
" <th>pred_class</th>\n",
" <th>time_for_pred</th>\n",
" <th>correct</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/1...</td>\n",
" <td>sushi</td>\n",
" <td>0.8812</td>\n",
" <td>sushi</td>\n",
" <td>0.1742</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/4...</td>\n",
" <td>sushi</td>\n",
" <td>0.9184</td>\n",
" <td>sushi</td>\n",
" <td>0.1266</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/3...</td>\n",
" <td>sushi</td>\n",
" <td>0.6810</td>\n",
" <td>sushi</td>\n",
" <td>0.1276</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/3...</td>\n",
" <td>sushi</td>\n",
" <td>0.8659</td>\n",
" <td>sushi</td>\n",
" <td>0.1258</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/1...</td>\n",
" <td>sushi</td>\n",
" <td>0.8830</td>\n",
" <td>sushi</td>\n",
" <td>0.1247</td>\n",
" <td>True</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-2fb8c555-7130-4f1b-a83b-feb81bc531a9')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-2fb8c555-7130-4f1b-a83b-feb81bc531a9 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-2fb8c555-7130-4f1b-a83b-feb81bc531a9');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 111
}
]
},
{
"cell_type": "code",
"source": [
"# Check number of correct predictions\n",
"effnetb2_test_pred_df.correct.value_counts()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "djw2PSQ7sjB8",
"outputId": "fc742e7d-102a-45a9-97aa-279584187cf2"
},
"execution_count": 112,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"True 145\n",
"False 5\n",
"Name: correct, dtype: int64"
]
},
"metadata": {},
"execution_count": 112
}
]
},
{
"cell_type": "code",
"source": [
"# Find the average time per prediction \n",
"effnetb2_average_time_per_pred = round(effnetb2_test_pred_df.time_for_pred.mean(), 4)\n",
"print(f\"EffNetB2 average time per prediction: {effnetb2_average_time_per_pred}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NxxgCNKFs2e2",
"outputId": "c3ba39ec-28a2-450e-8c32-9aa943033151"
},
"execution_count": 113,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"EffNetB2 average time per prediction: 0.1244\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"> **Note:** Prediction times will vary (much like training times) depending on the hardware you're using... so generally the faster your compute (e.g. CPU or GPU), the faster the predictions will happen."
],
"metadata": {
"id": "vHT8FLadtOda"
}
},
{
"cell_type": "code",
"source": [
"# Add time per pred to EffNetB2 stats dictionary\n",
"effnetb2_stats[\"time_per_pred_cpu\"] = effnetb2_average_time_per_pred \n",
"effnetb2_stats"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "I3drhFrfwAkF",
"outputId": "bb40c859-6a91-4838-a6c0-a5ab1bc55a01"
},
"execution_count": 114,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"{'test_loss': 0.28128677904605864,\n",
" 'test_acc': 0.96875,\n",
" 'number_of_parameters': 7705221,\n",
" 'model_size (MB)': 29.824288368225098,\n",
" 'time_per_pred_cpu': 0.1244}"
]
},
"metadata": {},
"execution_count": 114
}
]
},
{
"cell_type": "markdown",
"source": [
"### 5.3 Making and timing predictions with ViT "
],
"metadata": {
"id": "S7O84_p3tKPa"
}
},
{
"cell_type": "code",
"source": [
"# Make list of prediction dictionaries with ViT feature extractor model on test images \n",
"vit_test_pred_dicts = pred_and_store(paths=test_data_paths,\n",
" model=vit,\n",
" transform=vit_transforms,\n",
" class_names=class_names,\n",
" device=\"cpu\") # hardcode device to CPU because not sure if GPU available when we deploy "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 49,
"referenced_widgets": [
"dae44612c46644c8b496a7f9bfa6c3c2",
"48486a66fd844a9290ec49866c134b3c",
"3983c92304d04553a213755aac582367",
"4b95cf3fb5f34ef98a83ef1fe14d9db6",
"65db50a782564d31b1d0f81224adfd56",
"aa5ad051ad4b4d1299e417bbfd15581b",
"e5023f3772b14e1ca6f31a683cc9849e",
"55b52e781a074d27b054eefe594e06c8",
"aa902acee91d41219628f85319b4902e",
"95bfc6e17e634d2c831d79aef7ec545d",
"b3ba57b373e140988c34219c07bed764"
]
},
"id": "1eWeigxfumoO",
"outputId": "46a25224-ebb4-4fdf-881d-facaf2754e52"
},
"execution_count": 115,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/150 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "dae44612c46644c8b496a7f9bfa6c3c2"
}
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"# Check the first couple of ViT predictions \n",
"vit_test_pred_dicts[:2]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "kTjpX207u6hQ",
"outputId": "260e0702-14f0-4749-800e-c2010a811bab"
},
"execution_count": 116,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[{'image_path': PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/1203702.jpg'),\n",
" 'class_name': 'sushi',\n",
" 'pred_prob': 0.9589,\n",
" 'pred_class': 'sushi',\n",
" 'time_for_pred': 0.7028,\n",
" 'correct': True},\n",
" {'image_path': PosixPath('data/pizza_steak_sushi_20_percent/test/sushi/46797.jpg'),\n",
" 'class_name': 'sushi',\n",
" 'pred_prob': 0.9927,\n",
" 'pred_class': 'sushi',\n",
" 'time_for_pred': 0.5721,\n",
" 'correct': True}]"
]
},
"metadata": {},
"execution_count": 116
}
]
},
{
"cell_type": "code",
"source": [
"# Turn vit_test_pred_dicts\n",
"import pandas as pd\n",
"vit_test_pred_df = pd.DataFrame(vit_test_pred_dicts)\n",
"vit_test_pred_df.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"id": "Ivd1lYSHvHvz",
"outputId": "b83690e8-5c22-4d06-9e2f-a070370bfef8"
},
"execution_count": 117,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" image_path class_name pred_prob \\\n",
"0 data/pizza_steak_sushi_20_percent/test/sushi/1... sushi 0.9589 \n",
"1 data/pizza_steak_sushi_20_percent/test/sushi/4... sushi 0.9927 \n",
"2 data/pizza_steak_sushi_20_percent/test/sushi/3... sushi 0.9908 \n",
"3 data/pizza_steak_sushi_20_percent/test/sushi/3... sushi 0.4956 \n",
"4 data/pizza_steak_sushi_20_percent/test/sushi/1... sushi 0.9870 \n",
"\n",
" pred_class time_for_pred correct \n",
"0 sushi 0.7028 True \n",
"1 sushi 0.5721 True \n",
"2 sushi 0.5463 True \n",
"3 pizza 0.5606 False \n",
"4 sushi 0.5535 True "
],
"text/html": [
"\n",
" <div id=\"df-a734f163-cb4a-46b2-9e73-fbc646487af7\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>image_path</th>\n",
" <th>class_name</th>\n",
" <th>pred_prob</th>\n",
" <th>pred_class</th>\n",
" <th>time_for_pred</th>\n",
" <th>correct</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/1...</td>\n",
" <td>sushi</td>\n",
" <td>0.9589</td>\n",
" <td>sushi</td>\n",
" <td>0.7028</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/4...</td>\n",
" <td>sushi</td>\n",
" <td>0.9927</td>\n",
" <td>sushi</td>\n",
" <td>0.5721</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/3...</td>\n",
" <td>sushi</td>\n",
" <td>0.9908</td>\n",
" <td>sushi</td>\n",
" <td>0.5463</td>\n",
" <td>True</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/3...</td>\n",
" <td>sushi</td>\n",
" <td>0.4956</td>\n",
" <td>pizza</td>\n",
" <td>0.5606</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>data/pizza_steak_sushi_20_percent/test/sushi/1...</td>\n",
" <td>sushi</td>\n",
" <td>0.9870</td>\n",
" <td>sushi</td>\n",
" <td>0.5535</td>\n",
" <td>True</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-a734f163-cb4a-46b2-9e73-fbc646487af7')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-a734f163-cb4a-46b2-9e73-fbc646487af7 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-a734f163-cb4a-46b2-9e73-fbc646487af7');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 117
}
]
},
{
"cell_type": "code",
"source": [
"# See how many correct\n",
"vit_test_pred_df.correct.value_counts()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "SbOWvg2NvQLc",
"outputId": "dc26274f-3895-49d9-8b80-df1ee5f4728c"
},
"execution_count": 118,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"True 148\n",
"False 2\n",
"Name: correct, dtype: int64"
]
},
"metadata": {},
"execution_count": 118
}
]
},
{
"cell_type": "code",
"source": [
"# Calculate average time per prediction for ViT model\n",
"vit_average_time_per_pred = round(vit_test_pred_df.time_for_pred.mean(), 4)\n",
"print(f\"ViT average time per prediction: {vit_average_time_per_pred}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "xSa1HzC2vcRl",
"outputId": "41f6f50e-b5f1-491d-8ad9-1c7e830df4da"
},
"execution_count": 119,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"ViT average time per prediction: 0.5554\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Add average time per prediction to ViT stats\n",
"vit_stats[\"time_per_pred_cpu\"] = vit_average_time_per_pred\n",
"vit_stats"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "jgfzAKT1vxFe",
"outputId": "9c815d86-32c4-443a-dd84-279df269dbd7"
},
"execution_count": 120,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"{'test_loss': 0.06418212698772549,\n",
" 'test_acc': 0.984659090909091,\n",
" 'number_of_parameters': 85800963,\n",
" 'model_size (MB)': 327.36128330230713,\n",
" 'time_per_pred_cpu': 0.5554}"
]
},
"metadata": {},
"execution_count": 120
}
]
},
{
"cell_type": "markdown",
"source": [
"## 6. Comparing model results, prediction times and size"
],
"metadata": {
"id": "Kofmdazwv-JT"
}
},
{
"cell_type": "code",
"source": [
"# Turn stat dictionaries into DataFrame\n",
"df = pd.DataFrame([effnetb2_stats, vit_stats])\n",
"\n",
"# Add column for model names\n",
"df[\"model\"] = [\"EffNetB2\", \"ViT\"] \n",
"\n",
"# Convert accuracy to percentages\n",
"df[\"test_acc\"] = round(df[\"test_acc\"] * 100, 2) \n",
"\n",
"df"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 112
},
"id": "TOtprDnSwvD8",
"outputId": "3e1f9658-633f-463c-ff05-cfd8f27f8e33"
},
"execution_count": 121,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" test_loss test_acc number_of_parameters model_size (MB) \\\n",
"0 0.281287 96.88 7705221 29.824288 \n",
"1 0.064182 98.47 85800963 327.361283 \n",
"\n",
" time_per_pred_cpu model \n",
"0 0.1244 EffNetB2 \n",
"1 0.5554 ViT "
],
"text/html": [
"\n",
" <div id=\"df-619550e6-2cdd-4d06-9048-318a1c7ebd73\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>test_loss</th>\n",
" <th>test_acc</th>\n",
" <th>number_of_parameters</th>\n",
" <th>model_size (MB)</th>\n",
" <th>time_per_pred_cpu</th>\n",
" <th>model</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.281287</td>\n",
" <td>96.88</td>\n",
" <td>7705221</td>\n",
" <td>29.824288</td>\n",
" <td>0.1244</td>\n",
" <td>EffNetB2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0.064182</td>\n",
" <td>98.47</td>\n",
" <td>85800963</td>\n",
" <td>327.361283</td>\n",
" <td>0.5554</td>\n",
" <td>ViT</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-619550e6-2cdd-4d06-9048-318a1c7ebd73')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-619550e6-2cdd-4d06-9048-318a1c7ebd73 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-619550e6-2cdd-4d06-9048-318a1c7ebd73');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 121
}
]
},
{
"cell_type": "markdown",
"source": [
"Which model is better?\n",
"* `test_loss` (lower is better) - ViT\n",
"* `test_acc` (higher is better) - ViT\n",
"* `number_of_parameters` (generally lower is better*) - EffNetB2 (if a model has more parameters, it generally takes longer to compute)\n",
" * *sometimes models with higher parameters can still perform fast\n",
"* `model_size (MB)` - EffNetB2 (for our use case of deploying to a mobile device, generally lower is better)\n",
"* `time_per_pred_cpu` (lower is better, will be highly dependent on the hardware you're running on) - EffNetB2 \n",
"\n",
"Both models fail to achieve our goal of 30+FPS... however we could always just try and use EffNetB2 and see how it goes."
],
"metadata": {
"id": "IJXzZlkQxQyx"
}
},
{
"cell_type": "code",
"source": [
"# Compare ViT to EffNetB2 across different characteristics\n",
"pd.DataFrame(data=(df.set_index(\"model\").loc[\"ViT\"] / df.set_index(\"model\").loc[\"EffNetB2\"]),\n",
" columns=[\"ViT to EffNetB2 ratios\"]).T"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 81
},
"id": "OJzwJqjLw841",
"outputId": "dd1600ef-026a-466c-9d12-e9ba9a13c83e"
},
"execution_count": 122,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" test_loss test_acc number_of_parameters \\\n",
"ViT to EffNetB2 ratios 0.228173 1.016412 11.135432 \n",
"\n",
" model_size (MB) time_per_pred_cpu \n",
"ViT to EffNetB2 ratios 10.976332 4.46463 "
],
"text/html": [
"\n",
" <div id=\"df-7469302b-7942-4f6a-9051-434e805d9409\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>test_loss</th>\n",
" <th>test_acc</th>\n",
" <th>number_of_parameters</th>\n",
" <th>model_size (MB)</th>\n",
" <th>time_per_pred_cpu</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>ViT to EffNetB2 ratios</th>\n",
" <td>0.228173</td>\n",
" <td>1.016412</td>\n",
" <td>11.135432</td>\n",
" <td>10.976332</td>\n",
" <td>4.46463</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-7469302b-7942-4f6a-9051-434e805d9409')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-7469302b-7942-4f6a-9051-434e805d9409 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-7469302b-7942-4f6a-9051-434e805d9409');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 122
}
]
},
{
"cell_type": "markdown",
"source": [
"### 6.1 Visualizing the speed vs. performance tradeoff\n",
"\n",
"So we've compared our EffNetB2 and ViT feature extractor models, now let's visualize the comparison with a speed vs. performance plot.\n",
"\n",
"We can do so with matplotlib: \n",
"1. Create a scatter plot from the comparison DataFrame to compare EffNetB2 and ViT across test accuracy and prediction time.\n",
"2. Add titles and labels to make our plot look nice.\n",
"3. Annotate the samples on the scatter plot so we know what's going on. \n",
"4. Create a legend based on the model sizes (`model_size (MB)`)."
],
"metadata": {
"id": "6KiF2pRiyxVc"
}
},
{
"cell_type": "code",
"source": [
"df"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 112
},
"id": "t-MOghSy0hLD",
"outputId": "85938e16-1b81-48dc-c7d3-71698972982b"
},
"execution_count": 123,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" test_loss test_acc number_of_parameters model_size (MB) \\\n",
"0 0.281287 96.88 7705221 29.824288 \n",
"1 0.064182 98.47 85800963 327.361283 \n",
"\n",
" time_per_pred_cpu model \n",
"0 0.1244 EffNetB2 \n",
"1 0.5554 ViT "
],
"text/html": [
"\n",
" <div id=\"df-d203bdcc-c857-47bb-8f33-feafb1e67e9d\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>test_loss</th>\n",
" <th>test_acc</th>\n",
" <th>number_of_parameters</th>\n",
" <th>model_size (MB)</th>\n",
" <th>time_per_pred_cpu</th>\n",
" <th>model</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.281287</td>\n",
" <td>96.88</td>\n",
" <td>7705221</td>\n",
" <td>29.824288</td>\n",
" <td>0.1244</td>\n",
" <td>EffNetB2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0.064182</td>\n",
" <td>98.47</td>\n",
" <td>85800963</td>\n",
" <td>327.361283</td>\n",
" <td>0.5554</td>\n",
" <td>ViT</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-d203bdcc-c857-47bb-8f33-feafb1e67e9d')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-d203bdcc-c857-47bb-8f33-feafb1e67e9d button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-d203bdcc-c857-47bb-8f33-feafb1e67e9d');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 123
}
]
},
{
"cell_type": "code",
"source": [
"import matplotlib\n",
"matplotlib.__version__"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 36
},
"id": "nLzxvWaA14wA",
"outputId": "255156b4-5b5e-4cb7-ff8d-10bd52d22a14"
},
"execution_count": 124,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"'3.2.2'"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "string"
}
},
"metadata": {},
"execution_count": 124
}
]
},
{
"cell_type": "code",
"source": [
"# 1. Create a plot from model comparison DataFrame\n",
"import matplotlib.pyplot as plt\n",
"fig, ax = plt.subplots(figsize=(12, 8))\n",
"scatter = ax.scatter(data=df,\n",
" x=\"time_per_pred_cpu\",\n",
" y=\"test_acc\",\n",
" c=[\"blue\", \"orange\"],\n",
" s=\"model_size (MB)\")\n",
"\n",
"# 2. Add titles and labels to make our plot look good\n",
"ax.set_title(\"FoodVision Mini Inference Speed vs Performance\", fontsize=18)\n",
"ax.set_xlabel(\"Prediction time per image (seconds)\", fontsize=14)\n",
"ax.set_ylabel(\"Test accuracy (%)\", fontsize=14)\n",
"ax.tick_params(axis=\"both\", labelsize=12)\n",
"ax.grid(True)\n",
"\n",
"# 3. Annotate the samples on the scatter plot so we know what's going on. \n",
"for index, row in df.iterrows(): \n",
" ax.annotate(s=row[\"model\"], # note: in some versions of Matplotlib, this may need to be \"text\" rather than \"s\"\n",
" xy=(row[\"time_per_pred_cpu\"]+0.0006, row[\"test_acc\"]+0.03),\n",
" size=12)\n",
"\n",
"# 4. Create a legend based on the model sizes (model_size (MB)).\n",
"handles, labels = scatter.legend_elements(prop=\"sizes\", alpha=0.5)\n",
"model_size_legend = ax.legend(handles,\n",
" labels,\n",
" loc=\"lower right\",\n",
" title=\"Model size (MB)\",\n",
" fontsize=12)\n",
"\n",
"# Save the figure\n",
"plt.savefig(\"09-foodvision-mini-inference-speed-vs-performance.png\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 524
},
"id": "tnjz2F77z3In",
"outputId": "2354829c-7d08-4120-f2c7-00006189bd4b"
},
"execution_count": 125,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 864x576 with 1 Axes>"
],
"image/png": "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5ZdffrjD6FijRo1i/vz5/drG5FuSJKmDRcRwh9CxBvLYmnxLkiRJNTH5liRJWsZEBAcffPCi+y+99BLrrLMO73nPe/rVz9ixY3nssccWu023o48+mksvvbRfMbRy3nnncdxxxwFwzDHHEBHcddddi9Z/73vfIyKYOXPmohi33nprxo0bx9Zbb835558PwIsvvshOO+3ESy+9tNgxdTP5liRJWsasssoq3HzzzTz//PMA/OY3v2GDDTYY5qjguOOOY7fddlvsfr797W9zxBFHLLq/9dZbc9ZZZy26/7Of/Ywtt9zyFdtcfvnlzJo1ixkzZvCZz3wGgBVWWIFdd92Vs88+e7Fj6mbyLUmStAzaZ599uPDCCwE488wz+fCHP7xo3eOPP85+++3HNttsw9vf/nZmz54NwJw5c9hjjz3Ycsst+fjHP05mLtpm2rRpvO1tb2PcuHF84hOfYMGCBT3ue8GCBRx66KFstdVWbL311pxwwgkAHHroocyYMYOZM2cybty4RSPR3bXVd999N3vttRfbb789O+64I7fffvur+v7zn//MiiuuyNprr71o2X777bdoNPvuu+9m9dVXf8X6Rk8//TRrrrnmK7adPn167w9mP5h8S5IkLYM+9KEPcdZZZ/HCCy8we/Zs/u7v/m7Ruq9+9atst912zJ49m+OPP56JEycCcOyxx7LDDjtwyy23sP/++3PfffcBcNttt3H22WdzzTXXMGvWLEaMGNFrwjpr1iwefPBBbr75Zm666SYOO+ywV6wfP348s2bNYtasWey1114ceeSRAEyePJkTTzyR66+/nu985zuvGN3uds011/CWt7zlFctWW201Xv/613PzzTdz1llnceCBB75qu1122YWtttqKnXfema9//euLlm+11VZcd911fT2cbav1S3YiYnPgh8D2wKPAlMz8RUQcBPxPQ9PlgFHA+My8vkU/awGnAHsAjwFfzMwzhjp+SZKkTrHNNttw7733cuaZZ7LPPvu8Yt3VV1/NueeeC8C73/1u5syZw9NPP82VV17Jz3/+cwD23XffRSPEv/3tb7n++ut561vfCsDzzz/Puuuu2+O+N954Y+655x4+/elPs++++7LHHnu0bHf22Wdzww03cMkllzB37lx+97vf8cEPfnDR+nnz5r1qm4ceeoh11lnnVcu7/9m4+OKL+e1vf8upp576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\n"
},
"metadata": {
"needs_background": "light"
}
}
]
},
{
"cell_type": "markdown",
"source": [
"## 7. Bringing FoodVision Mini to life by creating a Gradio demo\n",
"\n",
"We've chosen to deploy EffNetB2 as it fulfils our criteria the best.\n",
"\n",
"What is Gradio? \n",
"\n",
"> Gradio is the fastest way to demo your machine learning model with a friendly web interface so that anyone can use it, anywhere! https://gradio.app/ \n",
"\n",
"For FoodVision Mini, we're going to be working towards building something like this: https://huggingface.co/spaces/mrdbourke/foodvision_mini \n",
"\n"
],
"metadata": {
"id": "j_dqjX900zdC"
}
},
{
"cell_type": "code",
"source": [
"# Import/install Gradio \n",
"try:\n",
" import gradio as gr\n",
"except: \n",
" !pip -q install gradio\n",
" import gradio as gr\n",
" \n",
"print(f\"Gradio version: {gr.__version__}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "AGMmZINs1GbD",
"outputId": "e49e9969-87ff-4297-9625-eff8a02180c7"
},
"execution_count": 126,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Gradio version: 3.1.7\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 7.1 Gradio overview\n",
"\n",
"Gradio helps you create machine learning demos.\n",
"\n",
"Why create a demo? \n",
"\n",
"So other people can try our models and we can test them in the real-world.\n",
"\n",
"Deployment is as important as training.\n",
"\n",
"The overall premise of Gradio is to map inputs -> function/model -> outputs."
],
"metadata": {
"id": "s5WOjvlY1FGY"
}
},
{
"cell_type": "markdown",
"source": [
"### 7.2 Creating a function to map our inputs and outputs"
],
"metadata": {
"id": "1KnalAXU28mL"
}
},
{
"cell_type": "code",
"source": [
"# Put our model on the CPU\n",
"effnetb2 = effnetb2.to(\"cpu\")\n",
"\n",
"# Check the device \n",
"next(iter(effnetb2.parameters())).device"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "0yYjOWA43UYX",
"outputId": "97c53c77-ab49-490f-a5c7-854faa453453"
},
"execution_count": 127,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"device(type='cpu')"
]
},
"metadata": {},
"execution_count": 127
}
]
},
{
"cell_type": "markdown",
"source": [
"Let's create a function called `predict()` to go from:\n",
"\n",
"```\n",
"images of food -> ML model (EffNetB2) -> outputs (food class label, prediction time)\n",
"```"
],
"metadata": {
"id": "JFRRK6AX3q_x"
}
},
{
"cell_type": "code",
"source": [
"from typing import Tuple, Dict\n",
"\n",
"def predict(img) -> Tuple[Dict, float]:\n",
" # Start a timer\n",
" start_time = timer()\n",
"\n",
" # Transform the input image for use with EffNetB2\n",
" img = effnetb2_transforms(img).unsqueeze(0) # unsqueeze = add batch dimension on 0th index\n",
"\n",
" # Put model into eval mode, make prediction\n",
" effnetb2.eval()\n",
" with torch.inference_mode():\n",
" # Pass transformed image through the model and turn the prediction logits into probaiblities\n",
" pred_probs = torch.softmax(effnetb2(img), dim=1)\n",
"\n",
" # Create a prediction label and prediction probability dictionary\n",
" pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}\n",
"\n",
" # Calculate pred time\n",
" end_time = timer()\n",
" pred_time = round(end_time - start_time, 4)\n",
"\n",
" # Return pred dict and pred time\n",
" return pred_labels_and_probs, pred_time\n"
],
"metadata": {
"id": "XP1g2Qmh3o9r"
},
"execution_count": 128,
"outputs": []
},
{
"cell_type": "code",
"source": [
"import random\n",
"from PIL import Image \n",
"\n",
"# Get a list of all test image filepaths\n",
"test_data_paths = list(Path(test_dir).glob(\"*/*.jpg\"))\n",
"print(f\"Example test data path: {test_data_paths[0]}\")\n",
"\n",
"# Randomly select a test image path\n",
"random_image_path = random.sample(test_data_paths, k=1)[0]\n",
"random_image_path\n",
"\n",
"# Open the target image\n",
"image = Image.open(random_image_path)\n",
"print(f\"[INFO] Predicting on image at path: {random_image_path}\\n\")\n",
"\n",
"# Predict on the target image and print out the outputs\n",
"pred_dict, pred_time = predict(img=image)\n",
"print(pred_dict)\n",
"print(pred_time)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2Eu6e03t5vr_",
"outputId": "d9de25fe-708c-4fff-c2ae-819ffac0a4fe"
},
"execution_count": 129,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Example test data path: data/pizza_steak_sushi_20_percent/test/sushi/1203702.jpg\n",
"[INFO] Predicting on image at path: data/pizza_steak_sushi_20_percent/test/pizza/129666.jpg\n",
"\n",
"{'pizza': 0.6918998956680298, 'steak': 0.15962719917297363, 'sushi': 0.14847293496131897}\n",
"0.1745\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 7.3 Creating a list of example images\n",
"\n",
"The examples for Gradio can be created with the `examples` parameter, see here: https://gradio.app/docs/#building-demos "
],
"metadata": {
"id": "WQbfWo2W5wWn"
}
},
{
"cell_type": "code",
"source": [
"# Create list of example inputs to our Gradio demo\n",
"example_list = [[str(filepath)] for filepath in random.sample(test_data_paths, k=3)]\n",
"example_list"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4HyaNFkd7_Jc",
"outputId": "5d517fb5-79ef-42c2-b8a6-a31898c48228"
},
"execution_count": 130,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[['data/pizza_steak_sushi_20_percent/test/sushi/1346344.jpg'],\n",
" ['data/pizza_steak_sushi_20_percent/test/pizza/998005.jpg'],\n",
" ['data/pizza_steak_sushi_20_percent/test/sushi/3401466.jpg']]"
]
},
"metadata": {},
"execution_count": 130
}
]
},
{
"cell_type": "markdown",
"source": [
"### 7.4 Building a Gradio Interface\n",
"\n",
"Let's use `gr.Interface()` to go from:\n",
"\n",
"```\n",
"input: image -> transform -> predict with EffNetB2 -> output: pred, prob prob, time\n",
"```\n",
"\n"
],
"metadata": {
"id": "phwDZEfW8mI2"
}
},
{
"cell_type": "code",
"source": [
"import gradio as gr\n",
"\n",
"# Create title, description and article\n",
"title = \"FoodVision Mini 🍕🥩🍣\"\n",
"description = \"An [EfficientNetB2 feature extractor](https://pytorch.org/vision/stable/models/generated/torchvision.models.efficientnet_b2.html#torchvision.models.efficientnet_b2) computer vision model to classify images as pizza, steak or sushi.\"\n",
"article = \"Created at [09. PyTorch Model Deployment](https://www.learnpytorch.io/09_pytorch_model_deployment/#74-building-a-gradio-interface).\"\n",
"\n",
"# Create the Gradio demo\n",
"demo = gr.Interface(fn=predict, # maps inputs to outputs\n",
" inputs=gr.Image(type=\"pil\"),\n",
" outputs=[gr.Label(num_top_classes=3, label=\"Predictions\"),\n",
" gr.Number(label=\"Prediction time (s)\")],\n",
" examples=example_list,\n",
" title=title,\n",
" description=description,\n",
" article=article)\n",
"\n",
"# Launch the demo!\n",
"demo.launch(debug=False, # print errors locally?\n",
" share=True) # generate a publically shareable URL "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 645
},
"id": "cndSbW519ATs",
"outputId": "24cfd39b-721a-4dec-f5c3-a502ebb90d92"
},
"execution_count": 131,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Colab notebook detected. To show errors in colab notebook, set `debug=True` in `launch()`\n",
"Running on public URL: https://27876.gradio.app\n",
"\n",
"This share link expires in 72 hours. For free permanent hosting, check out Spaces: https://huggingface.co/spaces\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.HTML object>"
],
"text/html": [
"<div><iframe src=\"https://27876.gradio.app\" width=\"900\" height=\"500\" allow=\"autoplay; camera; microphone;\" frameborder=\"0\" allowfullscreen></iframe></div>"
]
},
"metadata": {}
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(<gradio.routes.App at 0x7f0a593100d0>,\n",
" 'http://127.0.0.1:7861/',\n",
" 'https://27876.gradio.app')"
]
},
"metadata": {},
"execution_count": 131
}
]
},
{
"cell_type": "markdown",
"source": [
"## 8. Turning our FoodVision Mini Gradio Demo into a deployable app\n",
"\n",
"Our Gradio demos from Google Colab are fantastic but they expire within 72 hours.\n",
"\n",
"To fix this, we're going to prepare our app files so we can host them on Hugging Face Spaces: https://huggingface.co/docs/hub/spaces"
],
"metadata": {
"id": "gVpShnWV_KVT"
}
},
{
"cell_type": "markdown",
"source": [
"### 8.1 What is Hugging Face Spaces? \n",
"\n",
"> Hugging Face Spaces offer a simple way to host ML demo apps directly on your profile or your organizations profile. This allows you to create your ML portfolio, showcase your projects at conferences or to stakeholders, and work collaboratively with other people in the ML ecosystem.\n",
"\n",
"If GitHub is a place to show your coding ability, Hugging Face Spaces is a place to show your machine learning ability (through sharing ML demos that you've built).\n",
"\n"
],
"metadata": {
"id": "cByikjGwlnSr"
}
},
{
"cell_type": "markdown",
"source": [
"### 8.2 Deployed Gradio app structure\n",
"\n",
"Let's start to put all of our app files into a single directory:\n",
"\n",
"```\n",
"Colab -> folder with all Gradio files -> upload app files to Hugging Face Spaces -> deploy\n",
"```\n",
"\n",
"By the end our file structure will look like this: \n",
"\n",
"```\n",
"demos/\n",
"└── foodvision_mini/\n",
" ├── 09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\n",
" ├── app.py\n",
" ├── examples/\n",
" │ ├── example_1.jpg\n",
" │ ├── example_2.jpg\n",
" │ └── example_3.jpg\n",
" ├── model.py\n",
" └── requirements.txt\n",
"```\n",
"\n",
"Why use this structure?\n",
"\n",
"Because it's one of the simplest we could start with.\n",
"\n",
"You can see this in action:\n",
"* Deployed app - https://huggingface.co/spaces/mrdbourke/foodvision_mini\n",
"* See the example file structure - https://huggingface.co/spaces/mrdbourke/foodvision_mini/tree/main "
],
"metadata": {
"id": "N7Jatztnlp7t"
}
},
{
"cell_type": "markdown",
"source": [
"### 8.3 Creating a `demos` folder to store our FoodVision app files"
],
"metadata": {
"id": "zdJRIkLymrao"
}
},
{
"cell_type": "code",
"source": [
"import shutil\n",
"from pathlib import Path\n",
"\n",
"# Create FoodVision mini demo path\n",
"foodvision_mini_demo_path = Path(\"demos/foodvision_mini/\")\n",
"\n",
"# Remove files that might exist and create a new directory\n",
"if foodvision_mini_demo_path.exists():\n",
" shutil.rmtree(foodvision_mini_demo_path)\n",
" foodvision_mini_demo_path.mkdir(parents=True,\n",
" exist_ok=True)\n",
"else:\n",
" foodvision_mini_demo_path.mkdir(parents=True,\n",
" exist_ok=True)\n",
"\n",
"!ls demos/foodvision_mini/"
],
"metadata": {
"id": "EwtkCXuSo6Ac"
},
"execution_count": 132,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### 8.4 Creating a folder of example images to use with our FoodVision Mini demo\n",
"\n",
"What we want:\n",
"* 3 images in an `examples/` directory \n",
"* Images should be from the test set"
],
"metadata": {
"id": "geu6Ge7PpiJ3"
}
},
{
"cell_type": "code",
"source": [
"import shutil\n",
"from pathlib import Path\n",
"\n",
"# Create an examples directory\n",
"foodvision_mini_examples_path = foodvision_mini_demo_path / \"examples\" \n",
"foodvision_mini_examples_path.mkdir(parents=True, exist_ok=True)\n",
"\n",
"# Collect three random test dataset image paths\n",
"foodvision_mini_examples = [Path('data/pizza_steak_sushi_20_percent/test/sushi/592799.jpg'),\n",
" Path('data/pizza_steak_sushi_20_percent/test/steak/3622237.jpg'),\n",
" Path('data/pizza_steak_sushi_20_percent/test/pizza/2582289.jpg')]\n",
" \n",
"# Copy the three images to the examples directory \n",
"for example in foodvision_mini_examples:\n",
" destination = foodvision_mini_examples_path / example.name\n",
" print(f\"[INFO] Copying {example} to {destination}\")\n",
" shutil.copy2(src=example,\n",
" dst=destination)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "kcq5yGKdqGwx",
"outputId": "1bbe5ab1-91a2-4338-d867-7fa34f28ce68"
},
"execution_count": 133,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Copying data/pizza_steak_sushi_20_percent/test/sushi/592799.jpg to demos/foodvision_mini/examples/592799.jpg\n",
"[INFO] Copying data/pizza_steak_sushi_20_percent/test/steak/3622237.jpg to demos/foodvision_mini/examples/3622237.jpg\n",
"[INFO] Copying data/pizza_steak_sushi_20_percent/test/pizza/2582289.jpg to demos/foodvision_mini/examples/2582289.jpg\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Let's now verify that we can get a list of lists from our `examples/` directory."
],
"metadata": {
"id": "ew_bFP2Krh87"
}
},
{
"cell_type": "code",
"source": [
"import os\n",
"\n",
"# Get example filepaths in a list of lists\n",
"example_list = [[\"examples/\" + example] for example in os.listdir(foodvision_mini_examples_path)]\n",
"example_list"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "0VxEbm-bqaCa",
"outputId": "5dff0234-802e-47ec-9eb7-0fb85ed6d02d"
},
"execution_count": 134,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"[['examples/592799.jpg'], ['examples/3622237.jpg'], ['examples/2582289.jpg']]"
]
},
"metadata": {},
"execution_count": 134
}
]
},
{
"cell_type": "markdown",
"source": [
"### 8.5 Moving our trained EffNetB2 model to our FoodVision Mini demo directory "
],
"metadata": {
"id": "y3Ujy4l9rmFS"
}
},
{
"cell_type": "code",
"source": [
"import shutil\n",
"\n",
"# Create a source path for our target model \n",
"effnetb2_foodvision_mini_model_path = \"models/09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\"\n",
"\n",
"# Create a destination path for our target model\n",
"effnetb2_foodvision_mini_model_destination = foodvision_mini_demo_path / effnetb2_foodvision_mini_model_path.split(\"/\")[1]\n",
"\n",
"# Try to move the model file\n",
"try:\n",
" print(f\"[INFO] Attempting to move {effnetb2_foodvision_mini_model_path} to {effnetb2_foodvision_mini_model_destination}\")\n",
"\n",
" # Move the movel\n",
" shutil.move(src=effnetb2_foodvision_mini_model_path,\n",
" dst=effnetb2_foodvision_mini_model_destination)\n",
" \n",
" print(f\"[INFO] Model move complete.\")\n",
"# If the model has already been moved, check if it exists\n",
"except:\n",
" print(f\"[INFO] No model found at {effnetb2_foodvision_mini_model_path}, perhaps its already been moved?\")\n",
" print(f\"[INFO] Model exists at {effnetb2_foodvision_mini_model_destination}: {effnetb2_foodvision_mini_model_destination.exists()}\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Ot32u9VAsZea",
"outputId": "f724d1f0-259c-425d-fce2-cdbbf212a75a"
},
"execution_count": 135,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Attempting to move models/09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth to demos/foodvision_mini/09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\n",
"[INFO] Model move complete.\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 8.6 Turning off EffNetB2 model into a Python script (`model.py`)\n",
"\n",
"We have a saved `.pth` model `state_dict` and want to load it into a model instance.\n",
"\n",
"Let's move our `create_effnetb2_model()` function to a script so we can reuse it."
],
"metadata": {
"id": "Cm-Qg7QFtO32"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_mini/model.py\n",
"import torch\n",
"import torchvision\n",
"\n",
"from torch import nn\n",
"\n",
"def create_effnetb2_model(num_classes:int=3, # default output classes = 3 (pizza, steak, sushi)\n",
" seed:int=42):\n",
" # 1, 2, 3 Create EffNetB2 pretrained weights, transforms and model\n",
" weights = torchvision.models.EfficientNet_B2_Weights.DEFAULT\n",
" transforms = weights.transforms()\n",
" model = torchvision.models.efficientnet_b2(weights=weights)\n",
"\n",
" # 4. Freeze all layers in the base model\n",
" for param in model.parameters():\n",
" param.requires_grad = False\n",
"\n",
" # 5. Change classifier head with random seed for reproducibility\n",
" torch.manual_seed(seed)\n",
" model.classifier = nn.Sequential(\n",
" nn.Dropout(p=0.3, inplace=True),\n",
" nn.Linear(in_features=1408, out_features=num_classes)\n",
" )\n",
"\n",
" return model, transforms"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "C-kPD8bjukR7",
"outputId": "8c301c16-2977-4ffd-bd3b-5955c3521ab8"
},
"execution_count": 136,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_mini/model.py\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"class_names"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "TiukwXZzvvuZ",
"outputId": "a3d0e3ae-e41a-4f16-bcab-ce4de691b19d"
},
"execution_count": 137,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['pizza', 'steak', 'sushi']"
]
},
"metadata": {},
"execution_count": 137
}
]
},
{
"cell_type": "markdown",
"source": [
"### 8.7 Turning our FoodVision Mini Gradio app into a Python script (`app.py`)\n",
"\n",
"The `app.py` file will have four major parts:\n",
"1. Imports and class names setup\n",
"2. Model and transforms preparation\n",
"3. Predict function (`predict()`) \n",
"4. Gradio app - our Gradio interface + launch command"
],
"metadata": {
"id": "y1xii5QDu_vq"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_mini/app.py\n",
"### 1. Imports and class names setup ###\n",
"import gradio as gr\n",
"import os \n",
"import torch\n",
"\n",
"from model import create_effnetb2_model\n",
"from timeit import default_timer as timer\n",
"from typing import Tuple, Dict\n",
"\n",
"# Setup class names\n",
"class_names = ['pizza', 'steak', 'sushi']\n",
"\n",
"### 2. Model and transforms perparation ###\n",
"effnetb2, effnetb2_transforms = create_effnetb2_model(\n",
" num_classes=3)\n",
"\n",
"# Load save weights\n",
"effnetb2.load_state_dict(\n",
" torch.load(\n",
" f=\"09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth\",\n",
" map_location=torch.device(\"cpu\") # load the model to the CPU\n",
" )\n",
")\n",
"\n",
"### 3. Predict function ### \n",
"\n",
"def predict(img) -> Tuple[Dict, float]:\n",
" # Start a timer\n",
" start_time = timer()\n",
"\n",
" # Transform the input image for use with EffNetB2\n",
" img = effnetb2_transforms(img).unsqueeze(0) # unsqueeze = add batch dimension on 0th index\n",
"\n",
" # Put model into eval mode, make prediction\n",
" effnetb2.eval()\n",
" with torch.inference_mode():\n",
" # Pass transformed image through the model and turn the prediction logits into probaiblities\n",
" pred_probs = torch.softmax(effnetb2(img), dim=1)\n",
"\n",
" # Create a prediction label and prediction probability dictionary\n",
" pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}\n",
"\n",
" # Calculate pred time\n",
" end_time = timer()\n",
" pred_time = round(end_time - start_time, 4)\n",
"\n",
" # Return pred dict and pred time\n",
" return pred_labels_and_probs, pred_time\n",
"\n",
"### 4. Gradio app ### \n",
"\n",
"# Create title, description and article\n",
"title = \"FoodVision Mini 🍕🥩🍣\"\n",
"description = \"An [EfficientNetB2 feature extractor](https://pytorch.org/vision/stable/models/generated/torchvision.models.efficientnet_b2.html#torchvision.models.efficientnet_b2) computer vision model to classify images as pizza, steak or sushi.\"\n",
"article = \"Created at [09. PyTorch Model Deployment](https://www.learnpytorch.io/09_pytorch_model_deployment/#74-building-a-gradio-interface).\"\n",
"\n",
"# Create example list\n",
"example_list = [[\"examples/\" + example] for example in os.listdir(\"examples\")]\n",
"\n",
"# Create the Gradio demo\n",
"demo = gr.Interface(fn=predict, # maps inputs to outputs\n",
" inputs=gr.Image(type=\"pil\"),\n",
" outputs=[gr.Label(num_top_classes=3, label=\"Predictions\"),\n",
" gr.Number(label=\"Prediction time (s)\")],\n",
" examples=example_list,\n",
" title=title,\n",
" description=description,\n",
" article=article)\n",
"\n",
"# Launch the demo!\n",
"demo.launch() "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "5x8ZsZ-uvoyZ",
"outputId": "9bc75cb9-deca-4baf-e177-2cc0164813bd"
},
"execution_count": 138,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_mini/app.py\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 8.8 Creating a requirements file for FoodVision Mini (`requirements.txt`)\n",
"\n",
"The requirements file will tell our Hugging Face Space what software dependencies our app requires.\n",
"\n",
"The three main ones are:\n",
"* `torch`\n",
"* `torchvision`\n",
"* `gradio` "
],
"metadata": {
"id": "1if1WABux1R6"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_mini/requirements.txt\n",
"torch==1.12.0\n",
"torchvision==0.13.0\n",
"gradio==3.1.4"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "_1yzFu1hydaK",
"outputId": "f3bd9123-22ea-49d5-ab6a-285e178e34b5"
},
"execution_count": 139,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_mini/requirements.txt\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"## 9. Deploying our FoodVision Mini app HuggingFace Spaces\n",
"\n",
"There are two main options for uploading to a Hugging Face Space (also called a Hugging Face Repository, similar to a git repository):\n",
"\n",
"* Uploading via the Hugging Face Web interface (easiest).\n",
"* Uploading via the command line or terminal.\n",
" * Bonus: You can also use the huggingface_hub library to interact with Hugging Face, this would be a good extension to the above two options."
],
"metadata": {
"id": "jilb9gjszIA2"
}
},
{
"cell_type": "markdown",
"source": [
"### 9.1 Downloading our FoodVision Mini app files\n",
"\n",
"We want to download our `foodvision_mini` demo app so we can upload it to Hugging Face Spaces."
],
"metadata": {
"id": "LNadlbJs0sIv"
}
},
{
"cell_type": "code",
"source": [
"!ls demos/foodvision_mini/examples"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "HKjcf3120OvF",
"outputId": "f7de47a8-ce38-46dd-eee8-aa15a37b6e97"
},
"execution_count": 140,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"2582289.jpg 3622237.jpg 592799.jpg\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Change into the foodvision_mini directory and then zip it from the inside\n",
"!cd demos/foodvision_mini && zip -r ../foodvision_mini.zip * -x \"*.pyc\" \"*.ipynb\" \"*__pycache__*\" \"*ipynb_checkpoints*\""
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "bCNPI3so06_E",
"outputId": "b483036a-7466-497b-bea8-41d2c1529fb7"
},
"execution_count": 141,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"updating: 09_pretrained_effnetb2_feature_extractor_pizza_steak_sushi_20_percent.pth (deflated 8%)\n",
"updating: app.py (deflated 54%)\n",
"updating: examples/ (stored 0%)\n",
"updating: examples/592799.jpg (deflated 1%)\n",
"updating: examples/3622237.jpg (deflated 0%)\n",
"updating: examples/2582289.jpg (deflated 17%)\n",
"updating: model.py (deflated 46%)\n",
"updating: requirements.txt (deflated 4%)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Download\n",
"try:\n",
" from google.colab import files\n",
" files.download(\"demos/foodvision_mini.zip\")\n",
"except:\n",
" print(f\"Not running in Google Colab, can't use google.colab.files.download(), please download foodvision_mini.zip manually.\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 17
},
"id": "sZthL_n01dBj",
"outputId": "ccb02622-9844-406a-de18-d71a17550730"
},
"execution_count": 142,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.Javascript object>"
],
"application/javascript": [
"\n",
" async function download(id, filename, size) {\n",
" if (!google.colab.kernel.accessAllowed) {\n",
" return;\n",
" }\n",
" const div = document.createElement('div');\n",
" const label = document.createElement('label');\n",
" label.textContent = `Downloading \"${filename}\": `;\n",
" div.appendChild(label);\n",
" const progress = document.createElement('progress');\n",
" progress.max = size;\n",
" div.appendChild(progress);\n",
" document.body.appendChild(div);\n",
"\n",
" const buffers = [];\n",
" let downloaded = 0;\n",
"\n",
" const channel = await google.colab.kernel.comms.open(id);\n",
" // Send a message to notify the kernel that we're ready.\n",
" channel.send({})\n",
"\n",
" for await (const message of channel.messages) {\n",
" // Send a message to notify the kernel that we're ready.\n",
" channel.send({})\n",
" if (message.buffers) {\n",
" for (const buffer of message.buffers) {\n",
" buffers.push(buffer);\n",
" downloaded += buffer.byteLength;\n",
" progress.value = downloaded;\n",
" }\n",
" }\n",
" }\n",
" const blob = new Blob(buffers, {type: 'application/binary'});\n",
" const a = document.createElement('a');\n",
" a.href = window.URL.createObjectURL(blob);\n",
" a.download = filename;\n",
" div.appendChild(a);\n",
" a.click();\n",
" div.remove();\n",
" }\n",
" "
]
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.Javascript object>"
],
"application/javascript": [
"download(\"download_4115c36f-3db2-4eed-a1d3-985b969bee91\", \"foodvision_mini.zip\", 28972299)"
]
},
"metadata": {}
}
]
},
{
"cell_type": "markdown",
"source": [
"### 9.2 Running our Gradio demo app locally\n",
"\n",
"Running the app locally - https://www.learnpytorch.io/09_pytorch_model_deployment/#92-running-our-foodvision-mini-demo-locally \n",
"\n"
],
"metadata": {
"id": "96nmMyNm1d23"
}
},
{
"cell_type": "markdown",
"source": [
"### 9.3 Uploading our FoodVision Mini Gradio demo to Hugging Face Spaces\n",
"\n",
"See the steps here - https://www.learnpytorch.io/09_pytorch_model_deployment/#93-uploading-to-hugging-face \n",
"\n",
"See the live app deployed here - https://huggingface.co/spaces/mrdbourke/foodvision_mini_video "
],
"metadata": {
"id": "p1ALcikm45es"
}
},
{
"cell_type": "markdown",
"source": [
"We can also share our app by embedding it: https://gradio.app/sharing_your_app/#embedding-hosted-spaces"
],
"metadata": {
"id": "Jv7eB8Sm-B81"
}
},
{
"cell_type": "code",
"source": [
"# IPython is a library to help make Python interactive\n",
"from IPython.display import IFrame\n",
"\n",
"# Embed FoodVision Mini Gradio demo\n",
"IFrame(src=\"https://hf.space/embed/mrdbourke/foodvision_mini_video/+\", width=900, height=750)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 771
},
"id": "oq1ozup75H1T",
"outputId": "69c9bc16-0cf1-45ea-9ec4-6d24d823e2e5"
},
"execution_count": 143,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"<IPython.lib.display.IFrame at 0x7f0a592721d0>"
],
"text/html": [
"\n",
" <iframe\n",
" width=\"900\"\n",
" height=\"750\"\n",
" src=\"https://hf.space/embed/mrdbourke/foodvision_mini_video/+\"\n",
" frameborder=\"0\"\n",
" allowfullscreen\n",
" ></iframe>\n",
" "
]
},
"metadata": {},
"execution_count": 143
}
]
},
{
"cell_type": "markdown",
"source": [
"## 10. Creating FoodVision Big!!!\n",
"\n",
"FoodVision Mini works well with 3 classes (pizza, steak, sushi). \n",
"\n",
"So all of experimenting is paying off...\n",
"\n",
"Let's step things up a notch and make FoodVision BIG!!! using all of the Food101 classes. "
],
"metadata": {
"id": "m41X7TCd6_cT"
}
},
{
"cell_type": "markdown",
"source": [
"### 10.1 Creating a model for FoodVision Big + transforms"
],
"metadata": {
"id": "dWAgWBXr812g"
}
},
{
"cell_type": "code",
"source": [
"# Create Food101 model and transforms\n",
"effnetb2_food101, effnetb2_transforms = create_effnetb2_model(num_classes=101)"
],
"metadata": {
"id": "zNvhScO59IR7"
},
"execution_count": 145,
"outputs": []
},
{
"cell_type": "code",
"source": [
"from torchinfo import summary\n",
"\n",
"# Print EffNetB2 model summary (uncomment for full output) \n",
"summary(effnetb2_food101, \n",
" input_size=(1, 3, 224, 224),\n",
" col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n",
" col_width=20,\n",
" row_settings=[\"var_names\"])"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "w_vqDRO69seT",
"outputId": "f0a1d543-65ca-442c-a920-2f74b09dfcfc"
},
"execution_count": 146,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"============================================================================================================================================\n",
"Layer (type (var_name)) Input Shape Output Shape Param # Trainable\n",
"============================================================================================================================================\n",
"EfficientNet (EfficientNet) [1, 3, 224, 224] [1, 101] -- Partial\n",
"├─Sequential (features) [1, 3, 224, 224] [1, 1408, 7, 7] -- False\n",
"│ └─Conv2dNormActivation (0) [1, 3, 224, 224] [1, 32, 112, 112] -- False\n",
"│ │ └─Conv2d (0) [1, 3, 224, 224] [1, 32, 112, 112] (864) False\n",
"│ │ └─BatchNorm2d (1) [1, 32, 112, 112] [1, 32, 112, 112] (64) False\n",
"│ │ └─SiLU (2) [1, 32, 112, 112] [1, 32, 112, 112] -- --\n",
"│ └─Sequential (1) [1, 32, 112, 112] [1, 16, 112, 112] -- False\n",
"│ │ └─MBConv (0) [1, 32, 112, 112] [1, 16, 112, 112] (1,448) False\n",
"│ │ └─MBConv (1) [1, 16, 112, 112] [1, 16, 112, 112] (612) False\n",
"│ └─Sequential (2) [1, 16, 112, 112] [1, 24, 56, 56] -- False\n",
"│ │ └─MBConv (0) [1, 16, 112, 112] [1, 24, 56, 56] (6,004) False\n",
"│ │ └─MBConv (1) [1, 24, 56, 56] [1, 24, 56, 56] (10,710) False\n",
"│ │ └─MBConv (2) [1, 24, 56, 56] [1, 24, 56, 56] (10,710) False\n",
"│ └─Sequential (3) [1, 24, 56, 56] [1, 48, 28, 28] -- False\n",
"│ │ └─MBConv (0) [1, 24, 56, 56] [1, 48, 28, 28] (16,518) False\n",
"│ │ └─MBConv (1) [1, 48, 28, 28] [1, 48, 28, 28] (43,308) False\n",
"│ │ └─MBConv (2) [1, 48, 28, 28] [1, 48, 28, 28] (43,308) False\n",
"│ └─Sequential (4) [1, 48, 28, 28] [1, 88, 14, 14] -- False\n",
"│ │ └─MBConv (0) [1, 48, 28, 28] [1, 88, 14, 14] (50,300) False\n",
"│ │ └─MBConv (1) [1, 88, 14, 14] [1, 88, 14, 14] (123,750) False\n",
"│ │ └─MBConv (2) [1, 88, 14, 14] [1, 88, 14, 14] (123,750) False\n",
"│ │ └─MBConv (3) [1, 88, 14, 14] [1, 88, 14, 14] (123,750) False\n",
"│ └─Sequential (5) [1, 88, 14, 14] [1, 120, 14, 14] -- False\n",
"│ │ └─MBConv (0) [1, 88, 14, 14] [1, 120, 14, 14] (149,158) False\n",
"│ │ └─MBConv (1) [1, 120, 14, 14] [1, 120, 14, 14] (237,870) False\n",
"│ │ └─MBConv (2) [1, 120, 14, 14] [1, 120, 14, 14] (237,870) False\n",
"│ │ └─MBConv (3) [1, 120, 14, 14] [1, 120, 14, 14] (237,870) False\n",
"│ └─Sequential (6) [1, 120, 14, 14] [1, 208, 7, 7] -- False\n",
"│ │ └─MBConv (0) [1, 120, 14, 14] [1, 208, 7, 7] (301,406) False\n",
"│ │ └─MBConv (1) [1, 208, 7, 7] [1, 208, 7, 7] (686,868) False\n",
"│ │ └─MBConv (2) [1, 208, 7, 7] [1, 208, 7, 7] (686,868) False\n",
"│ │ └─MBConv (3) [1, 208, 7, 7] [1, 208, 7, 7] (686,868) False\n",
"│ │ └─MBConv (4) [1, 208, 7, 7] [1, 208, 7, 7] (686,868) False\n",
"│ └─Sequential (7) [1, 208, 7, 7] [1, 352, 7, 7] -- False\n",
"│ │ └─MBConv (0) [1, 208, 7, 7] [1, 352, 7, 7] (846,900) False\n",
"│ │ └─MBConv (1) [1, 352, 7, 7] [1, 352, 7, 7] (1,888,920) False\n",
"│ └─Conv2dNormActivation (8) [1, 352, 7, 7] [1, 1408, 7, 7] -- False\n",
"│ │ └─Conv2d (0) [1, 352, 7, 7] [1, 1408, 7, 7] (495,616) False\n",
"│ │ └─BatchNorm2d (1) [1, 1408, 7, 7] [1, 1408, 7, 7] (2,816) False\n",
"│ │ └─SiLU (2) [1, 1408, 7, 7] [1, 1408, 7, 7] -- --\n",
"├─AdaptiveAvgPool2d (avgpool) [1, 1408, 7, 7] [1, 1408, 1, 1] -- --\n",
"├─Sequential (classifier) [1, 1408] [1, 101] -- True\n",
"│ └─Dropout (0) [1, 1408] [1, 1408] -- --\n",
"│ └─Linear (1) [1, 1408] [1, 101] 142,309 True\n",
"============================================================================================================================================\n",
"Total params: 7,843,303\n",
"Trainable params: 142,309\n",
"Non-trainable params: 7,700,994\n",
"Total mult-adds (M): 657.78\n",
"============================================================================================================================================\n",
"Input size (MB): 0.60\n",
"Forward/backward pass size (MB): 156.80\n",
"Params size (MB): 31.37\n",
"Estimated Total Size (MB): 188.77\n",
"============================================================================================================================================"
]
},
"metadata": {},
"execution_count": 146
}
]
},
{
"cell_type": "markdown",
"source": [
"Since we're working with a larger dataset, we may want to introduce some data augmentation techniques: \n",
"* This is because with larger datasets and larger models, overfitting becomes more of a problem. \n",
"* Because we're working with a large number of classes, let's use TrivialAugment as our data augmentation technique.\n",
"\n",
"For a list of state-of-the-art computer vision recipes: https://pytorch.org/blog/how-to-train-state-of-the-art-models-using-torchvision-latest-primitives/ "
],
"metadata": {
"id": "VPWD94rM-wfe"
}
},
{
"cell_type": "code",
"source": [
"# Create training data transforms\n",
"food101_train_transforms = torchvision.transforms.Compose([\n",
" torchvision.transforms.TrivialAugmentWide(),\n",
" effnetb2_transforms]) \n",
"\n",
"food101_train_transforms "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ZE7_bIz599tF",
"outputId": "342675c7-bcc2-4a57-f7f7-6ec6549184ea"
},
"execution_count": 156,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Compose(\n",
" TrivialAugmentWide(num_magnitude_bins=31, interpolation=InterpolationMode.NEAREST, fill=None)\n",
" ImageClassification(\n",
" crop_size=[288]\n",
" resize_size=[288]\n",
" mean=[0.485, 0.456, 0.406]\n",
" std=[0.229, 0.224, 0.225]\n",
" interpolation=InterpolationMode.BICUBIC\n",
")\n",
")"
]
},
"metadata": {},
"execution_count": 156
}
]
},
{
"cell_type": "code",
"source": [
"# Testing data transform\n",
"effnetb2_transforms"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "fNsAgHj6-EOC",
"outputId": "24b775e8-5861-4d2b-e291-6bf31e9b9096"
},
"execution_count": 153,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"ImageClassification(\n",
" crop_size=[288]\n",
" resize_size=[288]\n",
" mean=[0.485, 0.456, 0.406]\n",
" std=[0.229, 0.224, 0.225]\n",
" interpolation=InterpolationMode.BICUBIC\n",
")"
]
},
"metadata": {},
"execution_count": 153
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.2 Getting data for FoodVision Big\n",
"\n",
"Get Food101 dataset - https://pytorch.org/vision/main/generated/torchvision.datasets.Food101.html "
],
"metadata": {
"id": "_g5Ctywd_VpV"
}
},
{
"cell_type": "code",
"source": [
"from torchvision import datasets\n",
"\n",
"# Setup data directory\n",
"from pathlib import Path\n",
"data_dir = Path(\"data\")\n",
"\n",
"# Get the training data (~750 images x 101 classes)\n",
"train_data = datasets.Food101(root=data_dir,\n",
" split=\"train\",\n",
" transform=food101_train_transforms, # apply data augmentation to training data\n",
" download=True)\n",
"\n",
"# Get the testing data (~250 images x 101 classes)\n",
"test_data = datasets.Food101(root=data_dir,\n",
" split=\"test\",\n",
" transform=effnetb2_transforms, # don't perform data augmentation on the test data\n",
" download=True)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 85,
"referenced_widgets": [
"426981b3072b4c7fa208a8399767228b",
"951d9f290e9e4a868dc6dfa8c3fc4231",
"0944a26e41904457bef7974d2ef6ebce",
"823b3960411d480388e45d8f77b253fe",
"3b9dbcf7693e4eba99519aac54d9f341",
"1c9dd9a9f4d14a13b8f9682464025075",
"f783356d1f9b45c08b0a68116dfe69c4",
"a060c3ffbfcb4f2980e80ca28be510d6",
"0dc3801c92fc43dd85630375830052a6",
"a710d9b0975549a98a6d15b9e866ca9b",
"69f6eeb1718a44de90f273dcfb5af75d"
]
},
"id": "T0zcPt91_p1p",
"outputId": "8264d46e-c1ec-4864-cb7c-3b15df618ab6"
},
"execution_count": 157,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Downloading https://data.vision.ee.ethz.ch/cvl/food-101.tar.gz to data/food-101.tar.gz\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/4996278331 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "426981b3072b4c7fa208a8399767228b"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Extracting data/food-101.tar.gz to data\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"750 * 101, 250 * 101"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NxsMRAGBAMGH",
"outputId": "f5aa979c-c4c6-4ffb-a3da-1ee0fc35a7a6"
},
"execution_count": 155,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(75750, 25250)"
]
},
"metadata": {},
"execution_count": 155
}
]
},
{
"cell_type": "code",
"source": [
"# Get Food101 class names \n",
"food101_class_names = train_data.classes\n",
"\n",
"# View the first 10\n",
"food101_class_names[:10]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "rdUvebbJAM6T",
"outputId": "746e92e8-ea20-49c5-e82f-6ee939128bd9"
},
"execution_count": 158,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['apple_pie',\n",
" 'baby_back_ribs',\n",
" 'baklava',\n",
" 'beef_carpaccio',\n",
" 'beef_tartare',\n",
" 'beet_salad',\n",
" 'beignets',\n",
" 'bibimbap',\n",
" 'bread_pudding',\n",
" 'breakfast_burrito']"
]
},
"metadata": {},
"execution_count": 158
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.3 Creating a subset of the Food101 dataset for faster experimenting\n",
"\n",
"Why create a subset?\n",
"\n",
"We want our first few experiments to run as quick as possible.\n",
"\n",
"We know FoodVision Mini works pretty well but this the is first time we've upgraded to 101 classes.\n",
"\n",
"To do so, let's make a subset of 20% of the data from the Food101 dataset (training and test).\n",
"\n",
"Our short-term goal: to beat the original Food101 paper result of 56.40% accuracy on the test dataset (see the paper: https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/static/bossard_eccv14_food-101.pdf)\n",
"\n",
"We want to beat this result using modern deep learning techniques and only 20% of the data. "
],
"metadata": {
"id": "6WiVb1a3C10F"
}
},
{
"cell_type": "code",
"source": [
"len(train_data) * 0.2, len(test_data) * 0.2"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "yXmjLXeuFIzS",
"outputId": "a597987c-f9e7-49fb-d9bf-87e44b7e012a"
},
"execution_count": 161,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(15150.0, 5050.0)"
]
},
"metadata": {},
"execution_count": 161
}
]
},
{
"cell_type": "code",
"source": [
"from torch.utils.data import random_split # https://pytorch.org/docs/stable/data.html#torch.utils.data.random_split \n",
"\n",
"def split_dataset(dataset:torchvision.datasets,\n",
" split_size:float=0.2,\n",
" seed:int=42):\n",
" # Create split lengths based on original dataset length\n",
" length_1 = int(len(dataset) * split_size) # defaults to 20% data split \n",
" length_2 = len(dataset) - length_1 # remaining length\n",
"\n",
" # Print out info\n",
" print(f\"[INFO] Splitting dataset of length {len(dataset)} into splits of size: {length_1} and {length_2}\")\n",
" \n",
" # Create splits with given random seed\n",
" random_split_1, random_split_2 = torch.utils.data.random_split(dataset,\n",
" lengths=[length_1, length_2],\n",
" generator=torch.manual_seed(seed))\n",
" \n",
" return random_split_1, random_split_2"
],
"metadata": {
"id": "DZbT-mGSEEGG"
},
"execution_count": 162,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Create training 20% split Food101 \n",
"train_data_food101_20_percent, _ = split_dataset(dataset=train_data,\n",
" split_size=0.2)\n",
"\n",
"# Create testing 20% split Food101\n",
"test_data_food101_20_percent, _ = split_dataset(dataset=test_data,\n",
" split_size=0.2)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Rnr-VGlNF03o",
"outputId": "f27e4529-a22f-4b56-ea2d-1a09dc93f5fe"
},
"execution_count": 163,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Splitting dataset of length 75750 into splits of size: 15150 and 60600\n",
"[INFO] Splitting dataset of length 25250 into splits of size: 5050 and 20200\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"len(train_data_food101_20_percent), len(test_data_food101_20_percent)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "MmIIuYj2GN2i",
"outputId": "71adf2ef-f603-4ae9-ba1b-7908a7aa861a"
},
"execution_count": 164,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(15150, 5050)"
]
},
"metadata": {},
"execution_count": 164
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.4 Turning our Food101 datasets into `DataLoader`s"
],
"metadata": {
"id": "Jl7899MqGSFl"
}
},
{
"cell_type": "code",
"source": [
"import os\n",
"\n",
"os.cpu_count()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "shvR7ZPlHa-c",
"outputId": "c889671a-b121-41e5-ae24-3b9fe8992c98"
},
"execution_count": 165,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"2"
]
},
"metadata": {},
"execution_count": 165
}
]
},
{
"cell_type": "code",
"source": [
"import os\n",
"import torch\n",
"\n",
"NUM_WORKERS = 2 # this value is very experimental and the best value will differ depeneding on the hardware you're using, search \"pytorch num workers setting for more\"\n",
"BATCH_SIZE = 32\n",
"\n",
"# Create Food101 20% training DataLoader\n",
"train_dataloader_food101_20_percent = torch.utils.data.DataLoader(dataset=train_data_food101_20_percent,\n",
" batch_size=BATCH_SIZE,\n",
" shuffle=True,\n",
" num_workers=NUM_WORKERS)\n",
"\n",
"# Create Food101 20% testing DataLoader\n",
"test_dataloader_food101_20_percent = torch.utils.data.DataLoader(dataset=test_data_food101_20_percent,\n",
" batch_size=BATCH_SIZE,\n",
" shuffle=False,\n",
" num_workers=NUM_WORKERS)"
],
"metadata": {
"id": "0I5ReLXgGbdb"
},
"execution_count": 166,
"outputs": []
},
{
"cell_type": "code",
"source": [
"len(train_dataloader_food101_20_percent), len(test_dataloader_food101_20_percent)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "sJcjlCKiIQp6",
"outputId": "d0786505-96c5-491f-ac4e-4f35e86cf680"
},
"execution_count": 167,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"(474, 158)"
]
},
"metadata": {},
"execution_count": 167
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.5 Training FoodVision Big!!!!\n",
"\n",
"Things for training:\n",
"* 5 epochs\n",
"* Optimizer: `torch.optim.Adam(lr=1e-3)`\n",
"* Loss function: `torch.nn.CrossEntropyLoss(label_smoothing=0.1)`\n",
"\n",
"Why use label smoothing? \n",
"\n",
"Label smoothing helps to prevent overfitting (it's a regularization technique).\n",
"\n",
"Without label smoothing and 5 classes: \n",
"\n",
"```\n",
"[0.00, 0.00, 0.99, 0.01, 0.00]\n",
"```\n",
"\n",
"With label smoothing and 5 classes:\n",
"\n",
"```\n",
"[0.01, 0.01, 0.96, 0.01, 0.01]\n",
"```\n",
"\n",
"> **Note:** Depending on your hardware, running the following cell may take 15-20 minutes (takes about 17 minutes on a NVIDIA Tesla P100 GPU)."
],
"metadata": {
"id": "L6_bOhjTIT5K"
}
},
{
"cell_type": "code",
"source": [
"from going_modular.going_modular import engine\n",
"\n",
"# Setup optimizer\n",
"optimizer = torch.optim.Adam(params=effnetb2_food101.parameters(),\n",
" lr=1e-3)\n",
"\n",
"# Setup loss\n",
"loss_fn = torch.nn.CrossEntropyLoss(label_smoothing=0.1)\n",
"\n",
"# Want to beat the original Food101 paper's result of 56.4% accuracy on the test dataset with 20% of the data\n",
"set_seeds()\n",
"effnetb2_food101_results = engine.train(model=effnetb2_food101,\n",
" train_dataloader=train_dataloader_food101_20_percent,\n",
" test_dataloader=test_dataloader_food101_20_percent,\n",
" optimizer=optimizer,\n",
" loss_fn=loss_fn,\n",
" epochs=5,\n",
" device=device)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 138,
"referenced_widgets": [
"5a41e92879364ffdbe4a4b84b2a815b4",
"f7267b534abd43ab84c70c74b644a06b",
"f11aeabb239a4e5aa8c1847d7437b60f",
"572281cc888f48c7afd2461426b0321e",
"245ab0553fb445ec8d7961a218c97a39",
"2b5471c1519b461aa31736b9d24d43d2",
"c98453092a49405780dec8a65b79e6fb",
"bb8a6f3b1d7443b28be1571e43755493",
"cc9109d69c8544e18a9dac3fa8f4da22",
"0aabb802874547beaa385aa0ee024fbd",
"bf88ce31ca9041e9910921e1c6fe84cf"
]
},
"id": "c7a3wwHZIbii",
"outputId": "7d9cbc37-3eb6-4204-cd1a-7f15dfebb007"
},
"execution_count": 169,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
" 0%| | 0/5 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "5a41e92879364ffdbe4a4b84b2a815b4"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch: 1 | train_loss: 3.6411 | train_acc: 0.2814 | test_loss: 2.7810 | test_acc: 0.4947\n",
"Epoch: 2 | train_loss: 2.8608 | train_acc: 0.4421 | test_loss: 2.4720 | test_acc: 0.5355\n",
"Epoch: 3 | train_loss: 2.6546 | train_acc: 0.4862 | test_loss: 2.3634 | test_acc: 0.5612\n",
"Epoch: 4 | train_loss: 2.5434 | train_acc: 0.5125 | test_loss: 2.3020 | test_acc: 0.5765\n",
"Epoch: 5 | train_loss: 2.4951 | train_acc: 0.5236 | test_loss: 2.2794 | test_acc: 0.5796\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"We've just done something in ~18 minutes that wasn't possible 10 years ago..."
],
"metadata": {
"id": "m58oNCziUI55"
}
},
{
"cell_type": "markdown",
"source": [
"### 10.6 Inspecting loss curves of FoodVision Big model"
],
"metadata": {
"id": "vvLG-Ng8NHaP"
}
},
{
"cell_type": "code",
"source": [
"from helper_functions import plot_loss_curves\n",
"\n",
"plot_loss_curves(effnetb2_food101_results)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 458
},
"id": "rjR0ojWvNZUf",
"outputId": "4a35982b-8f0f-4612-8562-9a9678988906"
},
"execution_count": 171,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x504 with 2 Axes>"
],
"image/png": "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\n"
},
"metadata": {
"needs_background": "light"
}
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.7 Save and load FoodVision Big model"
],
"metadata": {
"id": "xl7SV-rvNfkO"
}
},
{
"cell_type": "code",
"source": [
"from going_modular.going_modular import utils\n",
"\n",
"# Create a model path\n",
"effnetb2_food101_model_path = \"09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth\"\n",
"\n",
"# Save FoodVision Big model\n",
"utils.save_model(model=effnetb2_food101,\n",
" target_dir=\"models/\",\n",
" model_name=effnetb2_food101_model_path)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "wMsSEOoMNicH",
"outputId": "7696b526-589b-4ae9-99b1-5dc99a1a3807"
},
"execution_count": 172,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Saving model to: models/09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Create Food101 compatible EffNetB2 instance\n",
"loaded_effnetb2_food101, effnetb2_transforms = create_effnetb2_model(num_classes=101)\n",
"\n",
"# Load the saved model's state_dict()\n",
"loaded_effnetb2_food101.load_state_dict(torch.load(\"models/09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "cfw3uDURN2b1",
"outputId": "50a0b47e-43a3-468a-9e3d-0ec34f6e680f"
},
"execution_count": 173,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"<All keys matched successfully>"
]
},
"metadata": {},
"execution_count": 173
}
]
},
{
"cell_type": "markdown",
"source": [
"### 10.8 Checking FoodVision Big model size"
],
"metadata": {
"id": "YzliGRsUOPVD"
}
},
{
"cell_type": "code",
"source": [
"from pathlib import Path\n",
"\n",
"# Get the model size in bytes then convert to megabytes\n",
"pretrained_effnetb2_food101_model_size = Path(\"models\", effnetb2_food101_model_path).stat().st_size // (1024*1024) # division converts bytes to megabytes (roughly) \n",
"print(f\"Pretrained EffNetB2 feature extractor Food101 model size: {pretrained_effnetb2_food101_model_size} MB\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "cyIZ_3DSOT6R",
"outputId": "99988ce0-d6ab-4c21-8b25-82b004ff8ac4"
},
"execution_count": 174,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Pretrained EffNetB2 feature extractor Food101 model size: 30 MB\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"## 11. Turning our FoodVision Big model into a deployable app \n",
"\n",
"Why deploy a model?\n",
"\n",
"Deploying a model allows you to see how your model goes in the real-world (the ultimate test set).\n",
"\n",
"Let's create an outline for our FoodVision Big app: \n",
"\n",
"```\n",
"demos/\n",
" foodvision_big/\n",
" 09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth\n",
" app.py\n",
" class_names.txt\n",
" examples/\n",
" example_1.jpg\n",
" model.py\n",
" requirements.txt\n",
"```"
],
"metadata": {
"id": "b2gXaj5DU7H5"
}
},
{
"cell_type": "code",
"source": [
"from pathlib import Path\n",
"\n",
"# Create FoodVision Big demo path\n",
"foodvision_big_demo_path = Path(\"demos/foodvision_big/\")\n",
"\n",
"# Make FoodVision Big demo directory\n",
"foodvision_big_demo_path.mkdir(parents=True,\n",
" exist_ok=True)\n",
"\n",
"# Make FoodVision Big demo examples directory\n",
"(foodvision_big_demo_path / \"examples\").mkdir(parents=True, exist_ok=True)"
],
"metadata": {
"id": "zlIU3bK9WTTe"
},
"execution_count": 177,
"outputs": []
},
{
"cell_type": "code",
"source": [
"!ls demos/foodvision_big/"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "itszfiShXDGV",
"outputId": "259ed63d-ed6c-4052-e87f-e83a91917812"
},
"execution_count": 178,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"examples\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.1 Downloading an example image and moving it to the `examples` directory"
],
"metadata": {
"id": "EVYwRk_vXFIw"
}
},
{
"cell_type": "code",
"source": [
"# Download and move example image\n",
"!wget https://github.com/mrdbourke/pytorch-deep-learning/raw/main/images/04-pizza-dad.jpeg \n",
"!mv 04-pizza-dad.jpeg demos/foodvision_big/examples/04-pizza-dad.jpeg"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "TwuV22JaXvZX",
"outputId": "072534e6-2666-4696-c95f-bbacda84492a"
},
"execution_count": 180,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"--2022-08-30 01:31:42-- https://github.com/mrdbourke/pytorch-deep-learning/raw/main/images/04-pizza-dad.jpeg\n",
"Resolving github.com (github.com)... 192.30.255.112\n",
"Connecting to github.com (github.com)|192.30.255.112|:443... connected.\n",
"HTTP request sent, awaiting response... 302 Found\n",
"Location: https://raw.githubusercontent.com/mrdbourke/pytorch-deep-learning/main/images/04-pizza-dad.jpeg [following]\n",
"--2022-08-30 01:31:43-- https://raw.githubusercontent.com/mrdbourke/pytorch-deep-learning/main/images/04-pizza-dad.jpeg\n",
"Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.111.133, 185.199.110.133, 185.199.109.133, ...\n",
"Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 2874848 (2.7M) [image/jpeg]\n",
"Saving to: 04-pizza-dad.jpeg\n",
"\n",
"04-pizza-dad.jpeg 100%[===================>] 2.74M --.-KB/s in 0.04s \n",
"\n",
"2022-08-30 01:31:43 (64.7 MB/s) - 04-pizza-dad.jpeg saved [2874848/2874848]\n",
"\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"!mv models/09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth demos/foodvision_big/"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3223kIODX09M",
"outputId": "5ab451d1-2eed-474c-b090-2c8525fda9f2"
},
"execution_count": 182,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"mv: cannot stat 'models/09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth': No such file or directory\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.2 Saving Food101 class names to file (`class_names.txt`)\n",
"\n",
"Let's save all of the Food101 class names to a .txt file so we can import them and use them in our app."
],
"metadata": {
"id": "R8svkoEWYNxO"
}
},
{
"cell_type": "code",
"source": [
"# Check out the first 10 Food101 class names\n",
"food101_class_names[:10]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "KiIzS9SuYX4b",
"outputId": "73e8aa2d-fdde-4d8d-a0c7-5d1b2166ed11"
},
"execution_count": 184,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['apple_pie',\n",
" 'baby_back_ribs',\n",
" 'baklava',\n",
" 'beef_carpaccio',\n",
" 'beef_tartare',\n",
" 'beet_salad',\n",
" 'beignets',\n",
" 'bibimbap',\n",
" 'bread_pudding',\n",
" 'breakfast_burrito']"
]
},
"metadata": {},
"execution_count": 184
}
]
},
{
"cell_type": "code",
"source": [
"# Create path to Food101 class names\n",
"foodvision_big_class_names_path = foodvision_big_demo_path / \"class_names.txt\"\n",
"foodvision_big_class_names_path"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "AfsNWfGyZCv4",
"outputId": "0de3b408-6d5a-416f-8857-fa89013c3100"
},
"execution_count": 185,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"PosixPath('demos/foodvision_big/class_names.txt')"
]
},
"metadata": {},
"execution_count": 185
}
]
},
{
"cell_type": "code",
"source": [
"# Write Food101 class names to text file\n",
"with open(foodvision_big_class_names_path, \"w\") as f:\n",
" print(f\"[INFO] Saving Food101 class names to {foodvision_big_class_names_path}\")\n",
" f.write(\"\\n\".join(food101_class_names)) # new line per class name"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Q-QAU7HyZOr1",
"outputId": "792ff87c-d115-47b9-a773-07f998ef8820"
},
"execution_count": 186,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[INFO] Saving Food101 class names to demos/foodvision_big/class_names.txt\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Open Food101 class names file and read each line into a list\n",
"with open(foodvision_big_class_names_path, \"r\") as f:\n",
" food101_class_names_loaded = [food.strip() for food in f.readlines()]\n",
"\n",
"food101_class_names_loaded[:5]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "BFDijaFJZi_l",
"outputId": "b168ced1-cba0-48cf-e25b-25f77bffa442"
},
"execution_count": 189,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['apple_pie', 'baby_back_ribs', 'baklava', 'beef_carpaccio', 'beef_tartare']"
]
},
"metadata": {},
"execution_count": 189
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.3 Turning our FoodVision Big model into a Python script (`model.py`)"
],
"metadata": {
"id": "ZCXNzl6taDu5"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_big/model.py\n",
"import torch\n",
"import torchvision\n",
"\n",
"from torch import nn\n",
"\n",
"def create_effnetb2_model(num_classes:int=3, # default output classes = 3 (pizza, steak, sushi)\n",
" seed:int=42):\n",
" # 1, 2, 3 Create EffNetB2 pretrained weights, transforms and model\n",
" weights = torchvision.models.EfficientNet_B2_Weights.DEFAULT\n",
" transforms = weights.transforms()\n",
" model = torchvision.models.efficientnet_b2(weights=weights)\n",
"\n",
" # 4. Freeze all layers in the base model\n",
" for param in model.parameters():\n",
" param.requires_grad = False\n",
"\n",
" # 5. Change classifier head with random seed for reproducibility\n",
" torch.manual_seed(seed)\n",
" model.classifier = nn.Sequential(\n",
" nn.Dropout(p=0.3, inplace=True),\n",
" nn.Linear(in_features=1408, out_features=num_classes)\n",
" )\n",
"\n",
" return model, transforms"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "yPBlo8Poan44",
"outputId": "09654c8f-fbba-460f-c2e6-393757be6eff"
},
"execution_count": 190,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_big/model.py\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.4 Turning our FoodVision Big Gradio app into a Python script (`app.py`)\n",
"\n",
"The `app.py` file will have four major parts:\n",
"1. Imports and class names setup - for class names, we'll need to import from `class_names.txt` rather than with a Python list \n",
"2. Model and transforms preparation - we'll need to make sure our model is suitable for FoodVision Big\n",
"3. Predict function (`predict()`) - this can stay the same as the original `predict()`\n",
"4. Gradio app - our Gradio interface + launch command - this will change slightly from FoodVision Mini to reflect the FoodVision Big updates "
],
"metadata": {
"id": "m9FsAU6paxUW"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_big/app.py\n",
"### 1. Imports and class names setup ###\n",
"import gradio as gr\n",
"import os\n",
"import torch\n",
"\n",
"from model import create_effnetb2_model\n",
"from timeit import default_timer as timer\n",
"from typing import Tuple, Dict\n",
"\n",
"# Setup class names\n",
"with open(\"class_names.txt\", \"r\") as f:\n",
" class_names = [food_name.strip() for food_name in f.readlines()]\n",
"\n",
"### 2. Model and transforms preparation ### \n",
"# Create model and transforms\n",
"effnetb2, effnetb2_transforms = create_effnetb2_model(num_classes=101)\n",
"\n",
"# Load saved weights\n",
"effnetb2.load_state_dict(\n",
" torch.load(f=\"09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth\",\n",
" map_location=torch.device(\"cpu\")) # load to CPU\n",
")\n",
"\n",
"### 3. Predict function ###\n",
"\n",
"def predict(img) -> Tuple[Dict, float]:\n",
" # Start a timer\n",
" start_time = timer()\n",
"\n",
" # Transform the input image for use with EffNetB2\n",
" img = effnetb2_transforms(img).unsqueeze(0) # unsqueeze = add batch dimension on 0th index\n",
"\n",
" # Put model into eval mode, make prediction\n",
" effnetb2.eval()\n",
" with torch.inference_mode():\n",
" # Pass transformed image through the model and turn the prediction logits into probaiblities\n",
" pred_probs = torch.softmax(effnetb2(img), dim=1)\n",
"\n",
" # Create a prediction label and prediction probability dictionary\n",
" pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}\n",
"\n",
" # Calculate pred time\n",
" end_time = timer()\n",
" pred_time = round(end_time - start_time, 4)\n",
"\n",
" # Return pred dict and pred time\n",
" return pred_labels_and_probs, pred_time\n",
"\n",
"### 4. Gradio app ###\n",
"\n",
"# Create title, description and article\n",
"title = \"FoodVision BIG 🍔👁💪\"\n",
"description = \"An [EfficientNetB2 feature extractor](https://pytorch.org/vision/stable/models/generated/torchvision.models.efficientnet_b2.html#torchvision.models.efficientnet_b2) computer vision model to classify images [101 classes of food from the Food101 dataset](https://github.com/mrdbourke/pytorch-deep-learning/blob/main/extras/food101_class_names.txt).\"\n",
"article = \"Created at [09. PyTorch Model Deployment](https://www.learnpytorch.io/09_pytorch_model_deployment/#11-turning-our-foodvision-big-model-into-a-deployable-app).\"\n",
"\n",
"# Create example list\n",
"example_list = [[\"examples/\" + example] for example in os.listdir(\"examples\")]\n",
"\n",
"# Create the Gradio demo\n",
"demo = gr.Interface(fn=predict, # maps inputs to outputs\n",
" inputs=gr.Image(type=\"pil\"),\n",
" outputs=[gr.Label(num_top_classes=5, label=\"Predictions\"),\n",
" gr.Number(label=\"Prediction time (s)\")],\n",
" examples=example_list,\n",
" title=title,\n",
" description=description,\n",
" article=article)\n",
"\n",
"# Launch the demo!\n",
"demo.launch() "
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "pV5AMfeSa8Kh",
"outputId": "4038910f-d8b0-428b-a573-1f145a622303"
},
"execution_count": 191,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_big/app.py\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.5 Creating a requirements file for FoodVision Big (`requirements.txt`) "
],
"metadata": {
"id": "-X7rUdifeUT2"
}
},
{
"cell_type": "code",
"source": [
"%%writefile demos/foodvision_big/requirements.txt\n",
"torch==1.12.0\n",
"torchvision==0.13.0\n",
"gradio==3.1.4"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "0CVhzrUZdlA_",
"outputId": "9812c007-69fc-4d90-a410-6c063cdcaed4"
},
"execution_count": 192,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Writing demos/foodvision_big/requirements.txt\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.6 Downloading our FoodVision Big app files"
],
"metadata": {
"id": "hvaDFJZwePry"
}
},
{
"cell_type": "code",
"source": [
"# Change into the foodvision_big directory and then zip it from the inside\n",
"!cd demos/foodvision_big && zip -r ../foodvision_big.zip * -x \"*.pyc\" \"*.ipynb\" \"*__pycache__*\" \"*ipynb_checkpoints*\""
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ejIkVTl5ebVB",
"outputId": "188d7a9b-0916-4887-e3db-815ada252a89"
},
"execution_count": 193,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
" adding: 09_pretrained_effnetb2_feature_extractor_food101_20_percent.pth (deflated 8%)\n",
" adding: app.py (deflated 54%)\n",
" adding: class_names.txt (deflated 48%)\n",
" adding: examples/ (stored 0%)\n",
" adding: examples/04-pizza-dad.jpeg (deflated 0%)\n",
" adding: model.py (deflated 46%)\n",
" adding: requirements.txt (deflated 4%)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Download\n",
"try:\n",
" from google.colab import files\n",
" files.download(\"demos/foodvision_big.zip\")\n",
"except:\n",
" print(f\"Not running in Google Colab, can't use google.colab.files.download(), please download foodvision_big.zip manually.\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 17
},
"id": "7ffyQOhJemOL",
"outputId": "4ea3bb6a-b577-48d0-a96a-2c2ba1655d6d"
},
"execution_count": 194,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.Javascript object>"
],
"application/javascript": [
"\n",
" async function download(id, filename, size) {\n",
" if (!google.colab.kernel.accessAllowed) {\n",
" return;\n",
" }\n",
" const div = document.createElement('div');\n",
" const label = document.createElement('label');\n",
" label.textContent = `Downloading \"${filename}\": `;\n",
" div.appendChild(label);\n",
" const progress = document.createElement('progress');\n",
" progress.max = size;\n",
" div.appendChild(progress);\n",
" document.body.appendChild(div);\n",
"\n",
" const buffers = [];\n",
" let downloaded = 0;\n",
"\n",
" const channel = await google.colab.kernel.comms.open(id);\n",
" // Send a message to notify the kernel that we're ready.\n",
" channel.send({})\n",
"\n",
" for await (const message of channel.messages) {\n",
" // Send a message to notify the kernel that we're ready.\n",
" channel.send({})\n",
" if (message.buffers) {\n",
" for (const buffer of message.buffers) {\n",
" buffers.push(buffer);\n",
" downloaded += buffer.byteLength;\n",
" progress.value = downloaded;\n",
" }\n",
" }\n",
" }\n",
" const blob = new Blob(buffers, {type: 'application/binary'});\n",
" const a = document.createElement('a');\n",
" a.href = window.URL.createObjectURL(blob);\n",
" a.download = filename;\n",
" div.appendChild(a);\n",
" a.click();\n",
" div.remove();\n",
" }\n",
" "
]
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.Javascript object>"
],
"application/javascript": [
"download(\"download_948085f5-4cd1-4408-ba53-6bbd224ce6ff\", \"foodvision_big.zip\", 32183738)"
]
},
"metadata": {}
}
]
},
{
"cell_type": "markdown",
"source": [
"### 11.7 Deploying our FoodVision Big model app to Hugging Faces Spaces\n",
"\n",
"Let's bring FoodVision Big to life by deploying it to the world!!!\n",
"\n",
"See steps here: https://www.learnpytorch.io/09_pytorch_model_deployment/#117-deploying-our-foodvision-big-app-to-huggingface-spaces \n",
"\n",
"See our deployed app here: https://huggingface.co/spaces/mrdbourke/foodvision_big_video "
],
"metadata": {
"id": "KkOrng7Sewfk"
}
},
{
"cell_type": "markdown",
"source": [
"### Main takeaways, exercises and extra-curriculum\n",
"\n",
"* Main takeaways: https://www.learnpytorch.io/09_pytorch_model_deployment/#main-takeaways \n",
"* Exercises: https://www.learnpytorch.io/09_pytorch_model_deployment/#exercises\n",
"* Extra-curriculum: https://www.learnpytorch.io/09_pytorch_model_deployment/#extra-curriculum "
],
"metadata": {
"id": "wk_u0VmsfyuE"
}
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "a4AK4P2Glajh"
},
"execution_count": null,
"outputs": []
}
]
}