From 76cfb2b92fbbb6aac93478de69d5aed416eb738d Mon Sep 17 00:00:00 2001 From: <> Date: Sun, 15 Dec 2024 16:57:22 +0000 Subject: [PATCH] Deployed f785745 with MkDocs version: 1.6.1 --- .nojekyll | 0 0. Overview/0.0. Course.html | 2617 +++++++ 0. Overview/0.1. Projects.html | 2541 +++++++ 0. Overview/0.2. Datasets.html | 2624 +++++++ 0. Overview/0.3. Platforms.html | 2593 +++++++ 0. Overview/0.4. Mentoring.html | 2556 +++++++ 0. Overview/0.5. Assistants.html | 2562 +++++++ 0. Overview/0.6. Resources.html | 2524 +++++++ 0. Overview/index.html | 2419 ++++++ 1. Initializing/1.0. System.html | 2591 +++++++ 1. Initializing/1.1. Python.html | 2608 +++++++ 1. Initializing/1.2. pyenv.html | 2650 +++++++ 1. Initializing/1.3. Poetry.html | 2794 +++++++ 1. Initializing/1.4. git.html | 2641 +++++++ 1. Initializing/1.5. GitHub.html | 2657 +++++++ 1. Initializing/1.6. VS Code.html | 2910 +++++++ 1. Initializing/index.html | 2416 ++++++ 2. Prototyping/2.0. Notebooks.html | 2642 +++++++ 2. Prototyping/2.1. Imports.html | 2655 +++++++ 2. Prototyping/2.2. Configs.html | 2707 +++++++ 2. Prototyping/2.3. Datasets.html | 2715 +++++++ 2. Prototyping/2.4. Analysis.html | 2632 +++++++ 2. Prototyping/2.5. Modeling.html | 2768 +++++++ 2. Prototyping/2.6. Evaluations.html | 2778 +++++++ 2. Prototyping/index.html | 2416 ++++++ 3. Productionizing/3.0. Package.html | 2697 +++++++ 3. Productionizing/3.1. Modules.html | 2655 +++++++ 3. Productionizing/3.2. Paradigms.html | 2988 ++++++++ 3. Productionizing/3.3. Entrypoints.html | 2787 +++++++ 3. Productionizing/3.4. Configurations.html | 2678 +++++++ 3. Productionizing/3.5. Documentations.html | 2676 +++++++ .../3.6. VS Code Workspace.html | 2626 +++++++ 3. Productionizing/index.html | 2416 ++++++ 4. Validating/4.0. Typing.html | 2766 +++++++ 4. Validating/4.1. Linting.html | 2616 +++++++ 4. Validating/4.2. Testing.html | 2825 +++++++ 4. Validating/4.3. Logging.html | 2693 +++++++ 4. Validating/4.4. Security.html | 2606 +++++++ 4. Validating/4.5. Formatting.html | 2681 +++++++ 4. Validating/4.6. Debugging.html | 2624 +++++++ 4. Validating/index.html | 2416 ++++++ 404.html | 2325 ++++++ 5. Refining/5.0. Design Patterns.html | 2821 +++++++ 5. Refining/5.1. Task Automation.html | 2701 +++++++ 5. Refining/5.2. Pre-Commit Hooks.html | 2714 +++++++ 5. Refining/5.3. CI-CD Workflows.html | 2783 +++++++ 5. Refining/5.4. Software Containers.html | 2635 +++++++ 5. Refining/5.5. AI-ML Experiments.html | 2785 +++++++ 5. Refining/5.6. Model Registries.html | 2916 +++++++ 5. Refining/index.html | 2416 ++++++ 6. Sharing/6.0. Repository.html | 2817 +++++++ 6. Sharing/6.1. License.html | 2690 +++++++ 6. Sharing/6.2. Readme.html | 2660 +++++++ 6. Sharing/6.3. Releases.html | 2853 +++++++ 6. Sharing/6.4. Templates.html | 2848 +++++++ 6. Sharing/6.5. Workstations.html | 2570 +++++++ 6. Sharing/6.6. Contributions.html | 2598 +++++++ 6. Sharing/index.html | 2416 ++++++ 7. Observability/0. Reproducibility.html | 2822 +++++++ 7. Observability/1. Monitoring.html | 2793 +++++++ 7. Observability/2. Alerting.html | 2664 +++++++ 7. Observability/3. Lineage.html | 2760 +++++++ 7. Observability/4. Costs-KPIs.html | 2780 +++++++ 7. Observability/5. Explainability.html | 2706 +++++++ 7. Observability/6. Infrastructure.html | 2615 +++++++ 7. Observability/index.html | 2416 ++++++ CNAME | 1 + assets/banner.png | Bin 0 -> 542739 bytes assets/favicon.ico | Bin 0 -> 15406 bytes assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.83f73b43.min.js | 16 + assets/javascripts/bundle.83f73b43.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/logo.png | Bin 0 -> 600752 bytes assets/stylesheets/main.6f8fc17f.min.css | 1 + assets/stylesheets/main.6f8fc17f.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + img/analysis/scatter_matrix.png | Bin 0 -> 477797 bytes img/analysis/statistics.png | Bin 0 -> 157162 bytes img/datasets/values.png | Bin 0 -> 75776 bytes img/evaluations/errors.png | Bin 0 -> 23671 bytes img/evaluations/feature_importances.png | Bin 0 -> 55430 bytes img/evaluations/learning_curve.png | Bin 0 -> 40484 bytes img/evaluations/params.png | Bin 0 -> 77509 bytes img/evaluations/rank.png | Bin 0 -> 43404 bytes img/evaluations/validation_curve.png | Bin 0 -> 37732 bytes img/executions/traces.png | Bin 0 -> 414583 bytes img/explanations/feature_importances.png | Bin 0 -> 105417 bytes img/explanations/sample_explanations.png | Bin 0 -> 118966 bytes .../run_estimator_distributions.png | Bin 0 -> 38483 bytes img/indicators/run_start_time.png | Bin 0 -> 23977 bytes img/indicators/run_time.png | Bin 0 -> 59458 bytes img/infrastructures/system_metrics.png | Bin 0 -> 298460 bytes img/lineages/datasets.png | Bin 0 -> 78939 bytes img/models/pipeline.png | Bin 0 -> 56897 bytes img/monitoring/charts.png | Bin 0 -> 141574 bytes index.html | 2619 +++++++ search/search_index.json | 1 + sitemap.xml | 263 + sitemap.xml.gz | Bin 0 -> 898 bytes 136 files changed, 183031 insertions(+) create mode 100644 .nojekyll create mode 100644 0. Overview/0.0. Course.html create mode 100644 0. Overview/0.1. Projects.html create mode 100644 0. Overview/0.2. Datasets.html create mode 100644 0. Overview/0.3. Platforms.html create mode 100644 0. Overview/0.4. Mentoring.html create mode 100644 0. Overview/0.5. Assistants.html create mode 100644 0. Overview/0.6. Resources.html create mode 100644 0. Overview/index.html create mode 100644 1. Initializing/1.0. System.html create mode 100644 1. Initializing/1.1. Python.html create mode 100644 1. Initializing/1.2. pyenv.html create mode 100644 1. Initializing/1.3. Poetry.html create mode 100644 1. Initializing/1.4. git.html create mode 100644 1. Initializing/1.5. GitHub.html create mode 100644 1. Initializing/1.6. VS Code.html create mode 100644 1. Initializing/index.html create mode 100644 2. Prototyping/2.0. Notebooks.html create mode 100644 2. Prototyping/2.1. Imports.html create mode 100644 2. Prototyping/2.2. Configs.html create mode 100644 2. Prototyping/2.3. Datasets.html create mode 100644 2. Prototyping/2.4. Analysis.html create mode 100644 2. Prototyping/2.5. Modeling.html create mode 100644 2. Prototyping/2.6. Evaluations.html create mode 100644 2. Prototyping/index.html create mode 100644 3. Productionizing/3.0. Package.html create mode 100644 3. Productionizing/3.1. Modules.html create mode 100644 3. Productionizing/3.2. Paradigms.html create mode 100644 3. Productionizing/3.3. Entrypoints.html create mode 100644 3. Productionizing/3.4. Configurations.html create mode 100644 3. Productionizing/3.5. Documentations.html create mode 100644 3. Productionizing/3.6. VS Code Workspace.html create mode 100644 3. Productionizing/index.html create mode 100644 4. Validating/4.0. Typing.html create mode 100644 4. Validating/4.1. Linting.html create mode 100644 4. Validating/4.2. Testing.html create mode 100644 4. Validating/4.3. Logging.html create mode 100644 4. Validating/4.4. Security.html create mode 100644 4. Validating/4.5. Formatting.html create mode 100644 4. Validating/4.6. Debugging.html create mode 100644 4. Validating/index.html create mode 100644 404.html create mode 100644 5. Refining/5.0. Design Patterns.html create mode 100644 5. Refining/5.1. Task Automation.html create mode 100644 5. Refining/5.2. Pre-Commit Hooks.html create mode 100644 5. Refining/5.3. CI-CD Workflows.html create mode 100644 5. Refining/5.4. Software Containers.html create mode 100644 5. Refining/5.5. AI-ML Experiments.html create mode 100644 5. Refining/5.6. Model Registries.html create mode 100644 5. Refining/index.html create mode 100644 6. Sharing/6.0. Repository.html create mode 100644 6. Sharing/6.1. License.html create mode 100644 6. Sharing/6.2. Readme.html create mode 100644 6. Sharing/6.3. Releases.html create mode 100644 6. Sharing/6.4. Templates.html create mode 100644 6. Sharing/6.5. Workstations.html create mode 100644 6. Sharing/6.6. Contributions.html create mode 100644 6. Sharing/index.html create mode 100644 7. Observability/0. Reproducibility.html create mode 100644 7. Observability/1. Monitoring.html create mode 100644 7. Observability/2. Alerting.html create mode 100644 7. Observability/3. Lineage.html create mode 100644 7. Observability/4. Costs-KPIs.html create mode 100644 7. Observability/5. Explainability.html create mode 100644 7. Observability/6. Infrastructure.html create mode 100644 7. Observability/index.html create mode 100644 CNAME create mode 100644 assets/banner.png create mode 100644 assets/favicon.ico create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.83f73b43.min.js create mode 100644 assets/javascripts/bundle.83f73b43.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/logo.png create mode 100644 assets/stylesheets/main.6f8fc17f.min.css create mode 100644 assets/stylesheets/main.6f8fc17f.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 img/analysis/scatter_matrix.png create mode 100644 img/analysis/statistics.png create mode 100644 img/datasets/values.png create mode 100644 img/evaluations/errors.png create mode 100644 img/evaluations/feature_importances.png create mode 100644 img/evaluations/learning_curve.png create mode 100644 img/evaluations/params.png create mode 100644 img/evaluations/rank.png create mode 100644 img/evaluations/validation_curve.png create mode 100644 img/executions/traces.png create mode 100644 img/explanations/feature_importances.png create mode 100644 img/explanations/sample_explanations.png create mode 100644 img/indicators/run_estimator_distributions.png create mode 100644 img/indicators/run_start_time.png create mode 100644 img/indicators/run_time.png create mode 100644 img/infrastructures/system_metrics.png create mode 100644 img/lineages/datasets.png create mode 100644 img/models/pipeline.png create mode 100644 img/monitoring/charts.png create mode 100644 index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/0. Overview/0.0. Course.html b/0. Overview/0.0. Course.html new file mode 100644 index 0000000..c73fb32 --- /dev/null +++ b/0. Overview/0.0. Course.html @@ -0,0 +1,2617 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0. Course - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.0. Course

+

What will this course teach you?

+

Welcome to the MLOps Coding Course designed to bring your AI/ML programming level from basic notebooks to production-grade codebase. Throughout this journey, you will learn:

+
    +
  • How to build and deploy production ready software artifacts.
  • +
  • Transitioning from prototyping in notebooks to structured Python packages.
  • +
  • Enhancing code reliability and maintainability through linting and testing.
  • +
  • Streamlining repetitive tasks using automation, locally and through CI/CD pipelines.
  • +
  • Adopting best practices to develop versatile and resilient AI/ML codebases.
  • +
+

Why enroll in this course?

+

The intersection of AI and ML with software applications is becoming increasingly complex, requiring management of models, datasets, and code. This course aims to bridge the knowledge gap between software engineers and data scientists, empowering you to efficiently navigate and manage AI/ML projects.

+

A key focus is the shift from using notebooks for production, which often lack rigorous software development practices, to a structured codebase. This transition is crucial for tackling production challenges, encouraging better collaboration, and advancing your MLOps capabilities.

+

Is there a fee for this course?

+

We offer this course at no cost, under the Creative Commons Attribution 4.0 International license. This means you can adapt, share, and even use the content for commercial purposes, provided you attribute the original authors.

+

Additionally, for those seeking a deeper understanding, we provide extra support options, including personal mentoring sessions and access to online assistance.

+

What should you know before starting?

+

To get the most out of this course, you should have:

+
    +
  1. A good understanding of Python including loops, conditionals, functions, and classes.
  2. +
  3. Familiarity with terminal commands for software installation, following README guides, and launching applications.
  4. +
  5. Basic knowledge in data science, including data exploration, feature engineering, model training and tuning, and performance evaluation.
  6. +
+

What skills will you acquire?

+

The course is divided into six in-depth chapters, each focusing on different facets of coding and project management skills:

+
    +
  1. Initializing: Go through the necessary tools and platforms for your development environment.
  2. +
  3. Prototyping: Start with notebooks to dive into data science projects and pinpoint viable solutions.
  4. +
  5. Productionizing: Transform your prototype into a neatly organized Python package, complete with scripts, configurations, and documentation.
  6. +
  7. Validating: Adopt practices like typing, linting, testing, and logging to refine code quality.
  8. +
  9. Refining: Leverage advanced software development techniques and tools to polish your project.
  10. +
  11. Sharing: Foster a productive team environment for effective contributions and communication.
  12. +
  13. Observability: Implement tools and practices for monitoring your data, models, and infrastructure.
  14. +
+

What's beyond the scope of this course?

+

While this course provides a solid basis in managing AI/ML code bases, it does not enter into the specificity of the different MLOps platforms like SageMaker, Vertex AI, Azure ML, or Databricks. Vendor courses already cover these end-to-end platforms in length. Instead, this course focuses on core principles and practices that are universally applicable, whether you're working on-premise, cloud-based, or in a hybrid setting.

+

How much time do you need to complete this course?

+

The time required to complete this course varies based on your prior experience and familiarity with the covered tools and practices. If you're already comfortable with tools like Git or VS Code, you may progress faster. The course philosophy encourages incremental improvement following the "make it done, make it right, make it fast" mantra, encouraging you to begin with a functional project version and steadily refine it for better quality and efficiency.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.1. Projects.html b/0. Overview/0.1. Projects.html new file mode 100644 index 0000000..364c98d --- /dev/null +++ b/0. Overview/0.1. Projects.html @@ -0,0 +1,2541 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.1. Projects - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.1. Projects

+

What is the default learning project?

+

The default project of this course involves a forecasting task using the Bike Sharing Demand dataset. The objective is to predict the number of bike rentals based on variables like date and time, weather conditions, and past rental data. A reference implementation is provided to fallback on if needed.

+

Forecasting is a critical skill with wide-ranging applications in academia and industry, utilizing diverse machine learning techniques. This project introduces challenges such as managing data subsets to prevent data leakage, where future information could wrongly influence past predictions. Through tackling this project, you'll gain hands-on experience in structuring MLOps projects effectively, offering a solid foundation for your learning journey.

+

Is it possible to select a personal project instead?

+

Absolutely! We encourage you to dive into a project that resonates with you personally. This could be a project you're currently working on professionally, or a passion project you're eager to develop further. Opting for your own project allows you to apply improvements directly within a familiar domain, streamlining the learning process by removing the need to acquaint yourself with a new project's nuances.

+

How to find project ideas?

+

Looking for inspiration? There are several online platforms offering data science challenges, complete with datasets and clearly defined objectives:

+
    +
  • Kaggle: A hub for data scientists worldwide, Kaggle provides the tools and community support needed to pursue your data science aspirations.
  • +
  • DrivenData: Hosts competitions where data scientists can address significant societal challenges through innovative predictive modeling.
  • +
  • DataCamp: Offers real-world data science competitions, allowing participants to hone their skills, win accolades, and present their solutions.
  • +
+

Can you work on a Large Language Model (LLM) project?

+

Working on projects centered around Large Language Models (LLM) and Generative AI does hold similarities with predictive ML projects, particularly in the areas of model management and code structuring. However, LLM projects also present distinct challenges. Evaluating LLMs can be more intricate, sometimes necessitating the use of external LLMs for thorough testing. Additionally, the training and fine-tuning of LLMs typically demand specific hardware, like high-memory GPUs, and adhere to different methodologies compared to conventional ML tasks.

+

Therefore, we recommend starting with a predictive ML project to get acquainted with fundamental MLOps practices. These core skills will then be easier to adapt and apply to LLM projects, easing the progression to these more specialized areas.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.2. Datasets.html b/0. Overview/0.2. Datasets.html new file mode 100644 index 0000000..4cb408d --- /dev/null +++ b/0. Overview/0.2. Datasets.html @@ -0,0 +1,2624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.2. Datasets - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.2. Datasets

+

What is a dataset?

+

A dataset is an organized collection of data, serving as the cornerstone for any AI or Machine Learning (ML) initiative. The structure of a dataset might vary, yet its pivotal role in shaping the scope, capabilities, and challenges of a project remains undisputed. Data preparation, involving substantial cleaning and exploration of raw data, often consumes the lion's share of a Machine Learning engineer's efforts, reinforcing to the adage that 80% of the work pertains to data processing, leaving only 20% for modeling. Yet, this preparation phase is crucial, setting the stage for the subsequent modeling efforts.

+

The impact of a dataset's quality and size on the outcomes of a model is profound, frequently surpassing the effects of model adjustments. This impact is embedded into the "Garbage in, garbage out" concept related to data science projects.

+

When is the dataset used?

+

Datasets are integral throughout the AI/ML project lifecycle, playing pivotal roles in various stages:

+
    +
  • Exploration: This phase involves delving into the dataset to find insights, study variable relationships, and discern patterns that could influence future predictions.
  • +
  • Data Processing: At this stage, the focus is on crafting features that encapsulate the predictive essence of the data and on partitioning the dataset effectively to gear up for the modeling phase.
  • +
  • Model Tuning: Here, the objective is to refine the model's hyperparameters through strategies like cross-validation to bolster the model's generalization capability.
  • +
  • Model Evaluation: This final step entails evaluating the model's performance on unseen data and identifying areas for potential improvement.
  • +
+

What are the types of datasets?

+

Datasets are typically classified into three categories: structured, unstructured, and semi-structured, each with distinctive features:

+

Structured Data

+

This category includes data that adhere to a clear schema, simplifying organization and analysis.

+
    +
  • Tabular Data: Characterized by its rows-and-columns format, where each column holds data of a uniform type. This format is prevalent in CSV files and databases.
  • +
  • Time Series Data: Comprises sequential data points collected at consistent time intervals. The sequential order is crucial, as alterations can significantly impact the dataset's interpretation, making it vital for forecasting in sectors like finance and energy.
  • +
  • Geospatial Data: Relates to specific geographical locations or areas, critical for spatial analyses and often managed with Geographic Information Systems (GIS).
  • +
  • Graph Data: Consists of nodes (or vertices) and edges (or connections), representing entities and their interrelations. This data type is crucial for modeling complex networks and systems.
  • +
+

Unstructured Data

+

Unstructured data lacks a predefined format, which poses challenges in its processing and analysis.

+
    +
  • Text: Ranges from short messages to extensive documents, marked by the complexity and diversity of language.
  • +
  • Multimedia: Includes images, audio, and video files, known for their high dimensionality and the complexities involved in extracting meaningful insights.
  • +
+

Semi-Structured Data

+

Positioned between structured and unstructured data, semi-structured data does not follow a strict schema but includes markers or tags to aid in identifying specific elements, making it more manageable. JSON and XML are notable examples.

+

Which dataset should you use?

+

The decision on which dataset to use often boils down to a balance between familiarity and exploration of new data. A simple rule of thumb is to opt for the dataset you are most acquainted with. Regardless of the diversity in data types and applications, the core principles of MLOps are applicable across various domains. Therefore, starting with a well-understood dataset allows you to concentrate on honing your MLOps skills rather than untangling the complexities of an unfamiliar dataset.

+

As mentioned in the previous section, the course uses the Bike Sharing Demand dataset dataset by default. You are free to use any other datasets, either for personal or professional purposes.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.3. Platforms.html b/0. Overview/0.3. Platforms.html new file mode 100644 index 0000000..d7b5e06 --- /dev/null +++ b/0. Overview/0.3. Platforms.html @@ -0,0 +1,2593 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.3. Platforms - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.3. Platforms

+

What is an MLOps platform?

+

An MLOps platform is a comprehensive toolkit designed to facilitate the deployment, management, and operational efficiency of AI and Machine Learning (ML) projects in production environments. Essential components of an MLOps platform typically can encompass:

+
+ CI/CD and automated ML pipeline. +
CI/CD and automated ML pipeline (source).
+
+
    +
  • Storage Systems: These are solutions like Amazon S3 or Google Cloud Storage that provide a secure space for storing datasets, models, and other essential artifacts.
  • +
  • Compute Engines: Services such as Kubernetes or Databricks deliver the necessary computational power for training models and executing predictions.
  • +
  • Orchestrators: Automation tools, including Apache Airflow, Metaflow, or Prefect, that streamline and manage workflows and data pipelines efficiently.
  • +
  • Model Registries: Platforms such as MLflow, Neptune.ai, or Weights and Biases that offer functionalities for tracking, versioning, and managing models.
  • +
+

The complexity and scale of an MLOps platform can greatly differ based on an organization's specific needs and the requirements of individual projects. While smaller teams might lean towards open-source options like MLflow for model lifecycle management and Airflow for orchestration due to their cost-effectiveness and versatility, larger enterprises may prefer comprehensive, fully-managed solutions such as Databricks or AWS SageMaker to accommodate extensive AI/ML deployments.

+

Which MLOps platform is the best?

+

Determining the "best" MLOps platform hinges on the unique requirements, infrastructure, and technical expertise of your organization. The ideal choice is one that seamlessly integrates with your existing technology stack and optimally supports your AI/ML workflows. Consider the following steps to guide your selection:

+
    +
  1. Stakeholder Engagement: Engage with key stakeholders across data science, IT operations, and software architecture to define project requirements and objectives.
  2. +
  3. Goal Alignment: Specify your objectives and the degree of platform sophistication needed to fulfill them within your project timelines.
  4. +
  5. Pilot Testing: Conduct pilot projects to evaluate the platform’s alignment with your business needs and its capacity to satisfy user expectations.
  6. +
+

The decision is often influenced by various factors, such as the organization's familiarity with certain technologies (like Kubernetes), budgetary limitations, and the preference for flexibility versus fully-managed services.

+

Why is this course not tied to a specific MLOps platform?

+

Market offerings frequently highlight the ease and simplicity of their MLOps platforms, sometimes at the expense of acknowledging the complexities of crafting a robust AI/ML codebase grounded in software engineering best practices. Although each platform brings its unique advantages and experiences, the foundational skills in MLOps coding are universally applicable, cutting across the specificities of individual platforms.

+

Is an MLOps platform required for this course?

+

Intentionally designed to be platform-agnostic, this course empowers you to apply its principles within any technological ecosystem you choose. Whether you're working with specific environment management systems, leveraging various libraries, or adapting to the workflow methodologies of tools like GitLab or Azure DevOps, the course material is versatile and can be tailored to align with your organization's preferences.

+

How does this course prepare you for using an MLOps platform?

+

Given the variety of artifacts MLOps platforms support, from Jupyter notebooks to Python packages, adopting Python packages is advocated for its robustness and maintainability. This course provides you with the essential skills to seamlessly integrate such packages within the Python ecosystem. It covers the use of testing tools like pytest and coverage, and package management via repositories like PyPI or Docker Hub. Armed with this knowledge, you can confidently tackle other pivotal aspects of your projects, such as data and model management and orchestration, knowing your codebase is solid and dependable.

+

Platform additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.4. Mentoring.html b/0. Overview/0.4. Mentoring.html new file mode 100644 index 0000000..795b525 --- /dev/null +++ b/0. Overview/0.4. Mentoring.html @@ -0,0 +1,2556 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.4. Mentoring - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.4. Mentoring

+

Can you receive mentoring during this course?

+

Mentoring forms a fundamental aspect of this course, provided directly by its authors, Médéric HURIER and Matthieu Jimenez. These mentors come equipped with extensive experience in the educational field. The mentoring program is flexible, catering to both individual learners and groups. You can tailor the frequency of mentorship sessions to match your preferences, from weekly engagements to several sessions per month.

+

What are the benefits of having a mentor?

+

The inclusion of mentorship dramatically enhances the educational journey, contributing to over half of the learning efficacy as observed. Mentorship deepens your engagement with the course materials, encourages critical examination of the methodologies presented, facilitates a more thorough exploration of the concepts, and tailors the learning path to meet your specific needs and context.

+

Mentors also play a pivotal role in motivation. The discipline and persistence required to navigate through the course, especially its more demanding segments, can be daunting. Having a mentor provides the support and encouragement necessary to navigate these challenges more smoothly.

+

How much does mentoring cost?

+

The pricing for mentorship varies, depending on the size of the participant group and the duration of the sessions. To obtain a personalized quote, it’s best to contact the course creators directly. If you are an individual learner, you can also opt for one-on-one mentoring sessions, which are priced differently.

+

Is company training available?

+

Company-specific training sessions are offered, designed to meet the unique needs of your organization. For a tailored training proposal, please reach out through the course’s main contact channel.

+

Can you offer mentoring services using this course?

+

You are more than welcome to leverage this course as a foundation for offering your own mentoring services. The course is designed with open-source principles in mind, aiming for broad accessibility.

+

However, if you opt to use this course material in your mentoring services, it’s imperative to properly acknowledge Médéric HURIER and Matthieu Jimenez as the original authors. Such attribution not only honors the intellectual property rights of the creators but also preserves the educational integrity of the course.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.5. Assistants.html b/0. Overview/0.5. Assistants.html new file mode 100644 index 0000000..2be4579 --- /dev/null +++ b/0. Overview/0.5. Assistants.html @@ -0,0 +1,2562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.5. Assistants - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

0.5. Assistants

+

What is the course assistant?

+

Included in this course is a premium MLOps Coding Assistant, specifically configured to support your learning journey by answering queries and tailoring the course content to your needs. This assistant functions similarly to ChatGPT but is uniquely customized to engage with the topics and materials of this course.

+

How does this assistant work?

+

The assistant utilizes a Large Language Model (LLM) in a Retrieval-Augmented Generation (RAG) architecture to offer real-time support and customized feedback. Upon receiving a query or request for assistance, it applies sophisticated natural language understanding to interpret your input accurately. It then consults its comprehensive database to fetch relevant information or examples, aiming to deliver the most precise and useful responses.

+

This tool is consistently updated to reflect the latest developments in AI and machine learning, ensuring it remains an effective resource throughout your MLOps educational journey. Whether you need further explanation on a topic, examples of MLOps applications, or feedback on your code, this assistant is prepared to assist.

+

How much does the assistant cost?

+

The assistant is a complimentary feature that costs $5 per month. You can access it by contacting the course creators. This fee grants you access to the assistant for one month, enabling you to leverage its support whenever you require assistance or guidance during the course.

+

How should you use the assistant?

+

The assistant is available for a wide range of course-related inquiries. Consider using it in the following ways:

+
    +
  • Demonstrating Examples: Request detailed examples, such as a pipeline example for PyTorch, to deepen your comprehension of key concepts.
  • +
  • Feature Creation Guidance: Seek advice on developing features for your dataset, utilizing the assistant's insights to refine your project.
  • +
  • Understanding Best Practices: Query about the benefits of implementing unit tests or other MLOps best practices to enhance your learning.
  • +
+

The assistant can also perform code reviews, providing critiques and recommendations to elevate the caliber and effectiveness of your work.

+

Can you fully trust the assistant?

+

While the assistant aims to offer accurate and relevant responses, it's essential to critically evaluate its outputs. It is estimated that about 90% of the information it generates will be accurate, but there is still a margin for potential errors or contextually unsuitable suggestions due to the limitations of generative AI technologies.

+

For those looking for a more dependable source of information, or to complement AI's convenience with expert human insight, the human mentoring service is an excellent alternative. This option extends the assistant's support by providing personalized feedback and expert insights from seasoned professionals in the field.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/0.6. Resources.html b/0. Overview/0.6. Resources.html new file mode 100644 index 0000000..e38d61a --- /dev/null +++ b/0. Overview/0.6. Resources.html @@ -0,0 +1,2524 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.6. Resources - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + +

0.6. Resources

+

Is there additional project resources?

+

This course is supplemented with a variety of resources aimed at enriching your MLOps learning journey. A key resource is the MLOps Python Package, designed to exemplify how to structure an MLOps codebase efficiently. This package incorporates the dataset featured in the course, providing a holistic view of what your end project could resemble.

+

Another important resource is the Cookiecutter MLOps Package which generalizes the concepts provided in the MLOps Python Package and this course, allowing you to quickly create a new MLOps project with the same structure.

+

For those seeking to deepen their knowledge in specific areas, the course creators have also contributed insights through personal blog posts. These articles explore subjects like setting up Visual Studio Code for MLOps activities or employing Pydantic for robust data validation. Such resources offer additional insights and actionable advice to enhance your understanding and skills.

+

Can you suggest a new project resource?

+

The course adheres to an open-source ethos, warmly welcoming contributions that augment the educational value for all participants. Whether you've discovered an essential tool, library, or piece of literature that aligns with the course's aims, or you've developed your resources inspired by your coursework and are keen to share them, your contributions are greatly valued.

+

To propose a new project resource, please forward your suggestions to the course's repository and create an issue on GitHub. Your contributions not only aid your peers in their learning process but also play a crucial role in the continuous improvement and updating of the course materials to mirror the evolving landscape of MLOps best practices and innovations.

+

Additional vendor resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/0. Overview/index.html b/0. Overview/index.html new file mode 100644 index 0000000..f9b1377 --- /dev/null +++ b/0. Overview/index.html @@ -0,0 +1,2419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0. Overview - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

0. Overview

+

Welcome to the introductory chapter of our course, where we delve into the integration of software development practices with the dynamic field of data science. This course is designed to empower you with the knowledge and skills necessary to manage and execute artificial intelligence (AI) and machine learning (ML) projects effectively using advanced Python techniques. Whether you're a beginner looking to get started or an experienced professional aiming to enhance your capabilities, this course has something to offer.

+ + +

In this overview, we'll guide you through the various components that make up this comprehensive learning experience:

+
    +
  • 0.0. Course: Unveils the course's mission to integrate software development disciplines with data science. Our objective is to equip learners with the confidence to embark on AI/ML projects using sophisticated Python methodologies.
  • +
  • 0.1. Projects: Provides insight into the default project included within the course, while encouraging learners to incorporate their projects for a tailored learning experience.
  • +
  • 0.2. Datasets: Offers a comprehensive look at various types of datasets, their importance throughout the AI/ML project lifecycle, and guidance on selecting the most suitable datasets.
  • +
  • 0.3. Platforms: Discusses how to choose an MLOps platform that best fits organizational needs, highlighting the course's agnostic approach to specific platforms.
  • +
  • 0.4. Mentoring: Details the mentoring services provided by the course creators, emphasizing the benefits of personalized advice and support.
  • +
  • 0.5. Assistants: Introduces the specialized online assistant tailored for this course, including tips on leveraging it effectively.
  • +
  • 0.6. Resources: Clarifies the extra resources available to enhance the course content and explains how participants can contribute to the course's open-source materials.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.0. System.html b/1. Initializing/1.0. System.html new file mode 100644 index 0000000..7fff65e --- /dev/null +++ b/1. Initializing/1.0. System.html @@ -0,0 +1,2591 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0. System - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.0. System

+ +

This course is tailored to be accessible on a wide range of operating systems including Linux, Chromebook, macOS, and Windows. Although there are no stringent hardware prerequisites, having a computer with enough CPU and RAM specifications is crucial for processing datasets efficiently. This ensures that you can fully engage with the course activities, regardless of your system preferences or the devices you have at your disposal.

+

Can you use JupyterLab or Google Colab for this course?

+

While JupyterLab is an acceptable environment for this course, it's worth noting that we've optimized the course content for Visual Studio Code (VS Code). VS Code offers a broader set of features that are specifically designed to enhance your learning experience in this course. Although Jupyter notebooks and Google Colab can be suitable for initial stages, like the Prototyping chapter, you may find that later sections of the course require functionalities, such as terminal access and file system navigation, that are more efficiently executed in VS Code.

+

Are additional software installations required?

+

Engaging with this course material necessitates installing several key software packages, including Python, Poetry, git, and VS Code. These tools form the backbone of your development workflow:

+
    +
  • Python is indispensable for all course-related coding activities.
  • +
  • Poetry offers an efficient way to manage Python package dependencies.
  • +
  • Git is crucial for version control and collaboration.
  • +
  • VS Code is recommended for its integrated development environment (IDE) capabilities, although alternatives may be used based on personal preference or specific needs.
  • +
+

Detailed instructions for installing these software packages are provided in their respective course chapters.

+

What are the specific hardware requirements for MLOps projects?

+

MLOps projects vary significantly in their complexity and demands on hardware, from simple tabular data analyses to complex machine learning models like transformers:

+
    +
  • Tabular Data Projects: Projects utilizing libraries like scikit-learn or XGBoost typically don't require specialized hardware, though an optional GPU could enhance performance for certain tasks.
  • +
  • Multimedia Data Projects: For projects involving TensorFlow or PyTorch for processing images or video data, access to at least one GPU is beneficial for faster processing.
  • +
  • Large Dataset Projects: Advanced projects that employ transformers or require extensive parallel processing may need multiple GPUs, possibly distributed across several machines for optimal performance.
  • +
+

It's often best to start with a straightforward setup, such as developing models on a local machine with sample data, before scaling to more complex arrangements like cloud-based resources for deployment and broader testing. Cloud platforms also enable running multiple experiments simultaneously, which can expedite the development process.

+

Is it possible to use cloud-based systems?

+

This course supports both local and cloud-based development environments, including options like GitHub Codespaces and Cloud Workstation. Cloud platforms offer considerable benefits, such as standardized development environments for easier team collaboration and enhanced security measures for your data. Nonetheless, it's crucial to understand any specific setup requirements and to manage resources effectively, especially when navigating the limitations of free tiers or usage quotas on these services.

+

System additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.1. Python.html b/1. Initializing/1.1. Python.html new file mode 100644 index 0000000..ad81855 --- /dev/null +++ b/1. Initializing/1.1. Python.html @@ -0,0 +1,2608 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.1. Python - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.1. Python

+

What is Python?

+

Python is a dynamic, high-level programming language known for its ease of learning and readability, making it a favorite among developers across various disciplines, including web development, automation, data science, and machine learning. It stands out for its simplicity and the vast ecosystem of third-party packages, allowing developers to build applications quickly and efficiently. Given its popularity, reflected in rankings such as the Tiobe Index and IEEE Spectrum Annual Ranking, Python is a staple in the programming world. For beginners and seasoned developers alike, Python offers a balance of readability and power, supported by a rich standard library and an extensive array of packages for diverse application needs.

+
+ Python +
Python(source)
+
+

Why is Python preferred for AI/ML projects?

+

Python's preeminence in AI and machine learning is attributed to its comprehensive selection of libraries and frameworks tailored for these fields, such as Pandas for data handling, Scikit-Learn for machine learning algorithms, and PyTorch and TensorFlow for advanced deep learning projects. Its user-friendly syntax supports rapid prototyping and iterative development, essential in the AI/ML workflow. Additionally, Python's interoperability with high-performance languages like C and C++ enables developers to optimize computational efficiency without sacrificing development speed or ease of use, making it the go-to language for AI/ML endeavors.

+

Is Python a good language for MLOps?

+

Python excels in the MLOps domain, offering a blend of simplicity for algorithm development and the robustness required for operational workflows. The key to maximizing Python's benefits in MLOps lies in adopting best practices for code quality and maintainability. This course covers strategies for effective Python code structuring and validation, ensuring that Python's flexibility and extensive toolkit can be leveraged effectively within MLOps pipelines.

+

Can you use other languages for AI/ML?

+

While Python dominates the AI/ML landscape, other languages like R or Julia also provide capabilities for statistical analysis and machine learning. Each of these languages brings unique strengths, whether in performance, syntax, or domain specificity. Transitioning to or incorporating these languages in AI/ML projects is possible but requires careful consideration of their ecosystems and how they fit into the broader project goals.

+

Which Python version should you use?

+

For new projects, the latest Python version is recommended to take advantage of current features and improvements. However, the choice may be influenced by the compatibility needs of significant libraries or production environment constraints. It's vital to avoid unsupported Python versions to ensure your projects remain secure and efficient. Regularly checking the official Python website for version updates and support status is a good practice.

+

How to install Python for this course?

+

pyenv is recommended for managing Python installations during development, offering the flexibility to switch between Python versions on a per-project basis. This approach is preferable to using system package managers or Anaconda, which may limit version flexibility. In production, using the system's default Python version can help avoid compatibility issues. For containerized deployments, you have the freedom to specify any Python version, aligning your development and production environments closely.

+

Python additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.2. pyenv.html b/1. Initializing/1.2. pyenv.html new file mode 100644 index 0000000..640e998 --- /dev/null +++ b/1. Initializing/1.2. pyenv.html @@ -0,0 +1,2650 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.2. pyenv - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.2. pyenv

+

What is pyenv?

+

Pyenv is a tool specifically designed for managing multiple Python versions on a single computer. It addresses a common challenge developers encounter: needing to work on multiple projects simultaneously, each requiring a different Python version. Pyenv allows for seamless switching between Python versions, ensuring each project runs within its required environment. This capability prevents interference with the system-wide Python installation or with other projects.

+

Why should you use pyenv?

+

Opting for pyenv as your Python version management tool offers several benefits:

+
    +
  • Flexibility: Pyenv enables the effortless management and transition between various Python versions, accommodating the unique requirements of different projects.
  • +
  • Non-root Installation: It supports installing Python versions without necessitating system-wide alterations or administrator privileges. This feature is particularly beneficial for users lacking root access.
  • +
  • System Python Independence: Pyenv operates at the user level, managing Python versions independently of the system's Python. This approach mitigates conflicts with the operating system's Python version, promoting a more stable and predictable development environment.
  • +
+

How to install pyenv on your computer?

+

For pyenv installation, refer to the official pyenv GitHub repository, which provides detailed installation instructions for various operating systems. These guidelines facilitate a smooth setup process on your system, regardless of the operating system you're using.

+

Is there a specific setup for MLOps projects?

+

When setting up pyenv for MLOps projects, the key consideration is to select a Python version that ensures compatibility with the project's dependencies, libraries, and frameworks. Other than choosing the appropriate Python version, MLOps projects do not have unique setup requirements with pyenv.

+

How to install the required version of Python for your project?

+

To install a particular Python version, like Python 3.12, use the command below with pyenv:

+
pyenv install 3.12
+
+

You can select the global version of Python to use on your system with this command:

+
pyenv global 3.12
+
+

How can you select the version of Python for your project?

+

To set a Python version for your project, proceed as follows:

+
    +
  1. Go to your project's root directory.
  2. +
  3. Create a .python-version file, adding the desired Python version (e.g., 3.12) to it:
  4. +
+
3.12
+
+
    +
  1. Once pyenv is configured and active, it will automatically switch to the version specified in the .python-version file upon entering the project directory. You can verify the active Python version with:
  2. +
+
# Confirming the currently active Python version
+$ python --version
+
+

If the expected version switch does not occur, ensure that your shell is properly set up to integrate with pyenv. This typically involves appending pyenv initialization commands to your shell's configuration file. Detailed instructions are available on the pyenv GitHub page.

+

How can you determine the currently active Python version in your shell?

+

To check the active Python version in your shell, use the command:

+
pyenv version
+
+

For information on the Python version and its executable location, independent of pyenv, the following commands are useful:

+
# To see the Python version
+python --version
+
+# To locate the Python executable
+which python
+
+

PyEnv additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.3. Poetry.html b/1. Initializing/1.3. Poetry.html new file mode 100644 index 0000000..b1da475 --- /dev/null +++ b/1. Initializing/1.3. Poetry.html @@ -0,0 +1,2794 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.3 Poetry - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.3 Poetry

+

What is Poetry?

+

Poetry stands out as a contemporary tool for Python package and dependency management, aiming to streamline the process of defining, managing, and packaging project dependencies. It fulfills the need for a unified tool capable of handling project setup, dependency resolution, and package distribution, proving to be an essential asset for Python developers.

+
+ Python Environment +
Python Environment(source)
+
+

What is a package?

+

A Python package is a set of Python modules grouped together that can be installed and used within your projects. Python packages help you manage the functionality of Python by allowing you to add and utilize external libraries and frameworks that are not part of the standard Python library.

+

Poetry simplifies the management of these packages by handling them as dependencies. When using Poetry, developers can easily specify which packages are needed for their projects through a pyproject.toml file. Poetry ensures that all specified dependencies are installed in the correct versions, maintaining a stable and conflict-free environment for development. Here’s an example of specifying dependencies with Poetry:

+
[tool.poetry]
+name = "example-project"
+version = "0.1.0"
+description = "An example project to demonstrate Poetry"
+
+[tool.poetry.dependencies]
+python = "^3.8"
+requests = "^2.25.1"
+
+[tool.poetry.dev-dependencies]
+pytest = "^5.2"
+
+

You will learn more on how to construct and publish Python Package in the Package section of this course.

+

Why do you need a package manager?

+

In the Python ecosystem, the distribution and installation of software through packages is a standard practice. These packages, often available in Wheel or zip formats, encapsulate source code along with vital metadata. Manually handling these packages and their dependencies can quickly become cumbersome, underscoring the need for package managers. Tools like Poetry automate these processes, boosting productivity and guaranteeing consistent environments across development and deployment.

+

By default, Poetry will download and install Python packages from Pypi, a repository of software for the Python programming language. If needed, other Python repositories can be configured to providing extra source of dependencies.

+

Why should you use Poetry in your project?

+

Incorporating Poetry into your project brings several key advantages:

+
    +
  • Improved Environment Management: Poetry streamlines the management of different project environments, promoting consistent development practices.
  • +
  • Simplified Package Building and Distribution: It provides a unified workflow for building, distributing, and installing packages, easing the complexities usually associated with these tasks.
  • +
  • Uniform Project Metadata: Poetry employs a standardized approach to defining project metadata, including dependencies, authors, and versioning, through a pyproject.toml file. This standardization ensures clarity and uniformity.
  • +
+

Compared to traditional approaches that involve pip, venv, and manual dependency management, Poetry offers a more cohesive and friendly experience, merging multiple package and environment management tasks into a single, simplified process.

+

How can you install Poetry on your system?

+

Poetry can be installed through various methods to accommodate different preferences and system setups. The recommended way is via pipx, which installs Poetry in an isolated environment to avoid conflicts with other project dependencies. Confirming the installation is as simple as running poetry --version in the terminal, which will display the installed Poetry version.

+
# Install pipx on your system
+python -m pip install pipx
+
+# install poetry using pipx
+pipx install poetry
+
+

At the time of writing, the latest version of Poetry is 1.8.2.

+

How can you use Poetry for your MLOps project?

+

Integrating Poetry into your MLOps project involves several key steps designed to configure and prepare your development environment:

+
    +
  • Begin by creating a new project directory and navigate into it.
  • +
  • Run poetry init in your terminal. This command starts an interactive guide to help set initial project parameters, such as package name, version, description, author, and dependencies. This step generates a pyproject.toml file, crucial for your project's configuration under Poetry.
  • +
  • Run poetry install to install the project dependencies and source code. This will let you access your project code through poetry shell and its command-line utilities with poetry run.
  • +
+

The pyproject.toml file plays a central role in defining your project’s dependencies and settings, with further configuration details available in the Poetry documentation.

+
# https://python-poetry.org/docs/pyproject/
+
+# PROJECT
+
+[tool.poetry]
+name = "bikes"
+version = "1.0.0"
+description = "Predict the number of bikes available."
+repository = "https://github.com/fmind/mlops-python-package"
+documentation = "https://fmind.github.io/mlops-python-package/"
+readme = "README.md"
+license = "CC BY"
+keywords = ["mlops", "python", "package"]
+packages = [{ include = "bikes", from = "src" }]
+
+[tool.poetry.scripts]
+bikes = 'bikes.scripts:main'
+
+[tool.poetry.dependencies]
+python = "^3.12"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+

At the end of the installation process, a poetry.lock file is generated with all the project dependencies that have been installed. You can remove and regenerate the poetry.lock file if you wish to update the list of dependencies.

+

What is a Python virtual environment?

+

A Python virtual environment is an isolated space where you can manage Python packages for specific projects without affecting the global Python installation. This setup allows different projects to have their own dependencies, regardless of what dependencies every other project has.

+

Poetry enhances the management of virtual environments by automatically creating and managing them for each project. It handles the installation of required packages into these isolated environments, ensuring that dependencies for one project do not interfere with another. This process is seamless and automatic, simplifying development workflows and reducing dependency conflicts.

+

How can you make your Poetry project easier to manage?

+

To enhance your Poetry management experience, consider creating a poetry.toml file in your project's root with specific virtual environment configurations:

+
# https://python-poetry.org/docs/configuration/
+
+[virtualenvs]
+in-project = true
+prefer-active-python = true
+
+

These configurations ensure that Poetry creates virtual environments directly within your project directory and prioritizes the active Python interpreter. This practice is aligned with optimal environment management standards. More details are available in the Poetry documentation on environment management.

+

How can you install dependencies for your project with Poetry?

+

Poetry differentiates between main (production) and development dependencies, offering an organized approach to dependency management. To add dependencies, use the following commands:

+
# For main dependencies
+$ poetry add pandas scikit-learn
+
+# For development dependencies
+$ poetry add -G dev ipykernel
+
+

Executing these commands updates the pyproject.toml file, accurately managing and versioning your project's dependencies.

+

In production, you can decide to install only the main dependencies using this command:

+
poetry install --only main
+
+

What is the difference between main and dev dependencies in Poetry?

+

In Poetry, dependencies are divided into two types: main dependencies and development (dev) dependencies.

+

Main Dependencies: These are essential for your project's production environment—your application can't run without them. For example, libraries like Pandas or XGBoost would be main dependencies for an MLOps project.

+

Development Dependencies: These are used only during development and testing, such as testing frameworks (e.g., pytest) or linters (e.g., ruff). They are not required in production.

+

Here’s a simple example in a pyproject.toml file:

+
[tool.poetry.dependencies]
+flask = "^2.0.1"  # Main dependency
+
+[tool.poetry.dev-dependencies]
+pytest = "^6.2.4"  # Development dependency`
+
+

This setup helps keep production environments lean by excluding unnecessary development tools.

+

Can you use Poetry to download Python dependencies from your own organization's code repository?

+

Poetry supports incorporating custom package repositories, including private or organizational ones. This capability allows for the use of proprietary packages in conjunction with those from the public PyPI. Adding a custom repository and setting up authentication is facilitated by Poetry's configuration commands, offering secure and adaptable dependency management.

+

For a deeper understanding of Poetry's features, including advanced configurations, package specifications, and command instructions, consult the official Poetry documentation. These resources offer detailed insights into leveraging Poetry to its fullest potential in your Python projects.

+

Poetry additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.4. git.html b/1. Initializing/1.4. git.html new file mode 100644 index 0000000..5cd4d52 --- /dev/null +++ b/1. Initializing/1.4. git.html @@ -0,0 +1,2641 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4. Git - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.4. Git

+

What is Git?

+

Git is a distributed version control system that is integral for managing both small and large projects effectively. It excels in tracking source code changes during software development, enabling multiple developers to collaborate on the same project without conflicts. Git is highly regarded for its robust data integrity, versatility, and support for complex, nonlinear development workflows.

+
+ Git from XKCD +
Git (source)
+
+

Why do you need Git?

+

Git serves several critical purposes in software development:

+
    +
  • Version Control: It meticulously tracks and manages changes to your project, offering the ability to revert to previous states, compare changes across timelines, and more.
  • +
  • Collaboration: Git facilitates simultaneous collaboration among multiple contributors on the same project. It supports branching and merging strategies, allowing seamless teamwork without risking data overwrite.
  • +
  • Backup and Restore: With changes stored in a repository, Git acts as a backup mechanism. You can revert your project to a prior state or retrieve lost data as needed.
  • +
  • Branching and Merging: Git enables you to create branches for experimenting or developing new features independently of the main project, which can later be merged back into the mainline without disrupting the ongoing development.
  • +
+

How can you install Git?

+

To install Git, consult the Git Installation Guide, which offers comprehensive instructions for a variety of operating systems. This ensures you can efficiently set up Git on any development environment.

+
# installation on mac with brew
+brew install git
+
+# install on linux with apt
+sudo apt install git
+
+

How should you use Git for your project?

+

For Git beginners, starting with a foundational tutorial, such as the one provided by GitHub's Git Tutorial, is recommended. Here's a simplified guide to using Git in your project:

+
    +
  1. Initialize Git: In your project directory, execute git init to create a new local Git repository.
  2. +
  3. Stage Files: Stage files for your next commit with git add <file>, for instance, git add README.md LICENSE.txt.
  4. +
  5. Check Status: Use git status to view staged changes, unstaged changes, and untracked files.
  6. +
  7. Commit Changes: With git commit -m "Initial Commit", commit your staged changes to the repository, including a descriptive message about the changes.
  8. +
+

Should you commit every file in your project?

+

When using Git, it's important to selectively track files. Consider the following guidelines:

+
    +
  • Exclude Secrets: Sensitive data, such as API keys and passwords, should never be committed to your repository.
  • +
  • Manage Large Files: For files exceeding 100MB (e.g., dataset files), use Git Large File Storage (git-lfs) instead of directly committing them to your Git repository.
  • +
  • Omit Cache Files: Do not track temporary or environment-specific files (e.g., .venv, poetry.lock, log files) that don't contribute to the project's primary function.
  • +
+

To exclude certain files and directories from being tracked, create a .gitignore file in your project's root directory. This file should list patterns to match filenames you wish to exclude, for example:

+
# https://git-scm.com/docs/gitignore
+
+# Build
+/dist/
+/build/
+
+# Cache
+.cache/
+.coverage*
+.mypy_cache/
+.ruff_cache/
+.pytest_cache/
+
+# Editor
+/.idea/
+/.vscode/
+.ipynb_checkpoints/
+
+# Environs
+.env
+/.venv/
+
+# Project
+/docs/*
+/mlruns/*
+/outputs/*
+!**/.gitkeep
+
+# Python
+*.py[cod]
+__pycache__/
+
+

Adhering to these practices ensures your repository remains streamlined, containing only pertinent project files and thus enhancing the clarity and efficiency of your development process.

+

Git additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.5. GitHub.html b/1. Initializing/1.5. GitHub.html new file mode 100644 index 0000000..51907f9 --- /dev/null +++ b/1. Initializing/1.5. GitHub.html @@ -0,0 +1,2657 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.5. GitHub - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.5. GitHub

+

What is GitHub?

+

GitHub is a cloud-based platform designed to enhance software development through the Git version control system. It enables developers to store, manage, and track changes in their code, offering a web-based interface, access controls, and collaboration features such as bug tracking, feature requests, task management, and wikis for projects.

+

Why do you need to use GitHub?

+

GitHub provides numerous benefits for your projects:

+
    +
  • Collaboration: It facilitates easy collaboration on projects across different locations, supporting seamless code reviews and management for all team sizes.
  • +
  • Version Control: GitHub offers robust version control, enabling you to track changes, revert to previous versions, and manage branches effectively.
  • +
  • Open Source Networking: By hosting your project on GitHub, you connect with a vast network of developers, which can lead to contributions to your project or opportunities to contribute to others.
  • +
  • Integration: GitHub integrates with a variety of development tools, enhancing your workflow and productivity.
  • +
+

What are GitHub alternatives?

+

There are several platforms offering similar features to GitHub:

+
    +
  • Bitbucket: Provides free private repositories for small teams and integrates with Atlassian's tools like Jira and Trello.
  • +
  • GitLab: Offers a comprehensive DevOps platform with hosted and self-hosted options, renowned for its CI/CD features.
  • +
  • SourceForge: A preferred platform for open-source projects, offering project management and a directory of open-source software.
  • +
  • Azure DevOps, Cloud Source Repository, AWS CodeCommit: Features a suite of development tools, including Git repositories, project management, automated builds, and release management.
  • +
+

How should you learn how to use GitHub?

+

To effectively learn GitHub, consider:

+
    +
  • GitHub's Guides: Begin with the official guides, covering the basics of using GitHub effectively.
  • +
  • Interactive Learning: Explore interactive courses on platforms like Codecademy and Coursera for hands-on training.
  • +
  • Practice: Engage in creating projects or contributing to open-source projects on GitHub to gain practical experience.
  • +
  • Community Resources: Utilize tutorials, videos, and forums from communities on Stack Overflow and Reddit for additional learning resources.
  • +
+

Which services are proposed by GitHub?

+

GitHub offers a range of services to support development and collaboration:

+
    +
  • GitHub Pages: Enables hosting and publishing websites from a GitHub repository.
  • +
  • GitHub Actions: Automates workflows for CI/CD, allowing code building, testing, and deployment directly within GitHub.
  • +
  • GitHub Projects: Provides project management tools to organize work within GitHub.
  • +
  • GitHub Security: Automatically scans code for vulnerabilities and errors.
  • +
  • GitHub Packages: Allows hosting and sharing software packages either privately within an organization or publicly.
  • +
+

Which services do you need to use at the beginning?

+

For new projects, start with GitHub Repositories to store your project code, manage branches, and track changes. This foundational service supports the core needs of most projects and can be supplemented with other GitHub services as your project develops.

+

How to configure GitHub for your MLOps project?

+
    +
  1. Initialize a GitHub Repository:
      +
    • Go to GitHub, click "New repository," name your repository, add a description, choose its visibility, and initialize it with a README. Optionally, add a .gitignore file and select a license.
    • +
    +
  2. +
  3. Set Up a Local Git Project:
      +
    • In your local project directory, run git init to start a Git repository. Add your files with git add ., then commit them with git commit -m "Initial commit".
    • +
    +
  4. +
  5. Link and Synchronize with GitHub:
      +
    • Connect your local repository to GitHub with git remote add origin [Your-GitHub-Repository-URL], replacing the placeholder with your repository's URL. Push your changes with git push -u origin main, substituting main with your branch name if different.
    • +
    +
  6. +
+

Following these steps, you'll set up a GitHub repository, prepare your local Git project, and synchronize your work, laying a solid foundation for version control, collaboration, and CI/CD processes in your MLOps initiatives.

+

GitHub additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/1.6. VS Code.html b/1. Initializing/1.6. VS Code.html new file mode 100644 index 0000000..634feba --- /dev/null +++ b/1. Initializing/1.6. VS Code.html @@ -0,0 +1,2910 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.6. VS Code - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

1.6. VS Code

+

What is VS Code?

+

Visual Studio Code (VS Code) is a versatile and free open-source code editor developed by Microsoft. Known for its efficiency and adaptability, it supports a multitude of programming languages with built-in support for JavaScript, TypeScript, and Node.js. It's celebrated for its extensibility, allowing users to install extensions for additional languages, frameworks, and tools, making it a highly customizable environment for software development.

+

Why should you use VS Code?

+

VS Code stands out as a top choice for developers due to its:

+
    +
  • Lightweight Performance: Offers robust features like IntelliSense for code completion, code navigation, and real-time syntax highlighting without bogging down your system.
  • +
  • Extensibility: Its extension marketplace provides support for almost all major programming languages and tools.
  • +
  • Integrated Terminal: Allows you to run shell commands, Git operations, and scripts from within the editor.
  • +
  • Debugging Tools: Comes with built-in debugging support for several languages and the capability to add more via extensions.
  • +
  • Source Control Integration: Features integrated Git support and extensions for other version control systems, enhancing code management.
  • +
+

How should you install VS Code?

+

To get started with VS Code:

+
    +
  1. Go to the Visual Studio Code website.
  2. +
  3. Download the installer for your operating system (Windows, macOS, or Linux).
  4. +
  5. Execute the installer and follow the prompts to complete the installation.
  6. +
+

Where can you learn how to use VS Code?

+

There are plenty of resources to help you master VS Code:

+
    +
  • Official Documentation: A thorough guide covering everything from basic to advanced features.
  • +
  • VS Code Tips and Tricks: Enhance your productivity with these best practices and shortcuts.
  • +
  • Microsoft Learn: Offers free, interactive tutorials for using VS Code in various scenarios.
  • +
  • YouTube Channels: Search for VS Code tutorials on YouTube to find numerous guides for all skill levels.
  • +
+

Which VS Code extensions should you install for MLOps?

+

This section lists the extensions you can install from VS Code Marketplace:

+

A Tier: The Must

+ +

B Tier: The Great

+ +

C Tier: The Good

+ +

How can you configure VS Code settings for MLOps?

+

You can find below some settings for configuring VS Code. Each setting is annotated to help you understand what is does. Keep in mind that you can hover your cursor over a setting key in VS Code to access more information about that setting. You can also switch between the textual and visual interface using the “Go to document” icon at the top left of the editor settings.

+
{
+    // enable Code Spell Checker by default
+    "cSpell.enabled": true,
+    // limit Code Spell Checker to markdown files
+    "cSpell.enabledLanguageIds": [
+        "markdown"
+    ],
+    // use en-US language by default for Code Spell Checker
+    "cSpell.language": "en-US",
+    // don't accept completion on enter
+    "editor.acceptSuggestionOnEnter": "off",
+    // don't let the cursor blink (distracting)
+    "editor.cursorBlinking": "solid",
+    // smooth caret for smooth editing :)
+    "editor.cursorSmoothCaretAnimation": "on",
+    // always let 15 lines as a margin when you scroll
+    "editor.cursorSurroundingLines": 15,
+    // use Fira code as the main font (support ligatures)
+    "editor.fontFamily": "'Fira Code', monospace",
+    // enable programming ligature (e.g., replace -> by →)
+    "editor.fontLigatures": true,
+    // default font size, use something comfortable for your eyes!
+    "editor.fontSize": 14,
+    // format the code you copy-paste in your editor
+    "editor.formatOnPaste": true,
+    // show the completion next to your cursor
+    "editor.inlineSuggest.enabled": true,
+    // disable the minimap on the right (distracting)
+    "editor.minimap.enabled": false,
+    // disable highlighting the word under the cursor (distracting)
+    "editor.occurrencesHighlight": false,
+    // don't highlight the current line (distracting)
+    "editor.renderLineHighlight": "none",
+    // smooth scrolling for smooth developments :)
+    "editor.smoothScrolling": true,
+    // required to use IntelliSense suggestions
+    "editor.suggestSelection": "first",
+    // enable tab completion (complete code by pressing tab)
+    "editor.tabCompletion": "on",
+    // if the line is longer than your window, display it on several lines
+    "editor.wordWrap": "on",
+    // don't automatically select files in explorer when you open them
+    "explorer.autoReveal": false,
+    // don't ask for confirmation when you delete a file
+    "explorer.confirmDelete": false,
+    // don't ask for confirmation when you drag and drop a file
+    "explorer.confirmDragAndDrop": false,
+    // save your file before switching to another one
+    "files.autoSave": "onFocusChange",
+    // set Python as the default language for new files
+    "files.defaultLanguage": "python",
+    // always have an empty line at the end of the file
+    "files.insertFinalNewline": true,
+    // use VS Code file explorer instead of the operating system
+    "files.simpleDialog.enable": true,
+    // remove whitespaces at the end of each line
+    "files.trimTrailingWhitespace": true,
+    // automatically fetch repository changes from GitHub
+    "git.autofetch": true,
+    // don't ask for confirmation before synchronizing git repositories
+    "git.confirmSync": false,
+    // commit all unstaged files using VS Code Source Control Tab
+    "git.enableSmartCommit": true,
+    // disable GitLens code lens (distracting)
+    "gitlens.codeLens.enabled": false,
+    // disable GitLens annotations on current line (distracting)
+    "gitlens.currentLine.enabled": false,
+    // trigger hover for the current line
+    "gitlens.hovers.currentLine.over": "line",
+    // create a vertical colored line for indentation
+    "indentRainbow.indicatorStyle": "light",
+    // don't ask when restarting Jupyter kernels
+    "jupyter.askForKernelRestart": false,
+    // allow to step out of user written code
+    "jupyter.debugJustMyCode": false,
+    // create an interactive window per file (see tips and tricks)
+    "jupyter.interactiveWindow.creationMode": "perFile",
+    // send the selected code to the interactive window instead of terminal
+    "jupyter.interactiveWindow.textEditor.executeSelection": true,
+    // enable the auto-reload extension by default for Jupyter notebooks
+    "jupyter.runStartupCommands": [
+        "%load_ext autoreload",
+        "%autoreload 2"
+    ],
+    // disable smart scrolling (lock scrolling when output view is selected)
+    "output.smartScroll.enabled": false,
+    // automatically format Python imports and code on save
+    "[python]": {
+        "editor.codeActionsOnSave": {
+            "source.organizeImports": true,
+        },
+        "editor.defaultFormatter": "charliermarsh.ruff",
+    },
+    // disable redhat telemetry (avoid a popup on first use)
+    "redhat.telemetry.enabled": false,
+    // allow untrusted files in the workspace when opened
+    "security.workspace.trust.untrustedFiles": "open",
+    // don't synchronize the following settings
+    "settingsSync.ignoredSettings": [
+        "projectManager.git.baseFolders"
+    ],
+    // use the same keybindings on Linux, Mac, and Windows
+    // note: you might want to drop this setting depending on your keybindings
+    "settingsSync.keybindingsPerPlatform": false,
+    // don't ask which problem matcher to use for executing VS Code tasks
+    "task.problemMatchers.neverPrompt": true,
+    // disable multiline paste warning by default
+    "terminal.integrated.enableMultiLinePasteWarning": false,
+    // enabled stronger integration between VS Code and the terminal
+    "terminal.integrated.shellIntegration.enabled": true,
+    // don't automatically open the peek view after running unit tests
+    "testing.automaticallyOpenPeekView": "never",
+    // don't follow the test currently running in the Test Explorer View
+    "testing.followRunningTest": false,
+    // open the Text Explorer View on failure
+    "testing.openTesting": "openOnTestFailure",
+    // ask VS Code to change settings required for running IntelliCode
+    "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
+    // replace VS Code title bar by the command certer (menu, selections, widgets, ...)
+    "window.commandCenter": true,
+    // don't enable mnemonics shortcuts (e.g., ALT + ...)
+    // this is related to my Alt-key trick, more on that later
+    "window.enableMenuBarMnemonics": false,
+    // authorize the title bar to be changed for enabling the command center
+    "window.titleBarStyle": "custom",
+    // don't preview file (pending opening state), open them directly instead
+    "workbench.editor.enablePreview": false,
+    // focus on the tab on the left instead of the most recent one
+    "workbench.editor.focusRecentEditorAfterClose": false,
+    // all open tab will be opened in multiple line (instead of scroll bar)
+    "workbench.editor.wrapTabs": true,
+}
+
+

How to configure VS Code for using the Jupyter Extension with Poetry?

+

To configure VS Code for using the Jupyter Extension with Poetry, follow these steps to ensure that your Poetry-managed virtual environment is recognized within VS Code. This allows you to use the Jupyter Extension seamlessly with the Python interpreter provided by Poetry.

+
    +
  1. Install the Jupyter Extension: First, ensure that the Jupyter Extension is installed in VS Code. You can find and install this extension from the VS Code Marketplace.
  2. +
  3. Open Your Project in VS Code: Open your project folder in VS Code. If you've just created a new Poetry project, this will be the directory containing your pyproject.toml file.
  4. +
  5. Select Python Interpreter: To make VS Code use the Python interpreter from your Poetry environment:
      +
    • Open the Command Palette (Ctrl+Shift+P on Windows/Linux, Cmd+Shift+P on macOS).
    • +
    • Type Python: Select Interpreter and select it.
    • +
    • Look for the interpreter that corresponds to your Poetry environment. It will typically be located under the .venv path within your project directory or listed as a virtual environment with your project's name.
    • +
    • Select the appropriate interpreter.
    • +
    +
  6. +
  7. Verify Jupyter Notebook Configuration: Create a new Jupyter notebook in VS Code (*.ipynb file) and verify that the cells execute using the Python interpreter from your Poetry environment. You can check the upper-right corner of the notebook interface to see which interpreter is currently active.
  8. +
  9. Install Necessary Libraries: If you need additional Python libraries that are not yet part of your Poetry project, you can add them by running poetry add <library-name> in your terminal or command prompt. This ensures that all dependencies are managed by Poetry and available in the notebook.
  10. +
+

VS Code additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/1. Initializing/index.html b/1. Initializing/index.html new file mode 100644 index 0000000..72ce471 --- /dev/null +++ b/1. Initializing/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1. Initializing - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

1. Initializing

+

The initialization phase is crucial in setting the stage for efficient and streamlined development, particularly for projects centered around Python and MLOps. This chapter aims to guide you through establishing a robust development setup, ensuring that every necessary tool and environment is correctly configured from the get-go. By following these foundational steps, you'll create a solid base for your project, enabling smooth progress and reducing the likelihood of delays caused by environment-related issues.

+
    +
  • 1.0. System: This section ensures your system is adequately prepared, outlining the essential prerequisites for installing and effectively running the necessary development tools.
  • +
  • 1.1. Python: Here, we introduce how to set up Python—the core programming language for our projects. We'll focus on version management and creating isolated environments for each project to avoid conflicts and dependency issues.
  • +
  • 1.2. pyenv: We explore pyenv, a tool for managing multiple Python versions. It's invaluable for working on various projects that each require different Python environments.
  • +
  • 1.3. Poetry: This part delves into using Poetry for dependency management and project packaging. It simplifies the process of defining, installing, and updating project dependencies with ease.
  • +
  • 1.4. git: Focuses on git, the cornerstone version control system integral to GitHub. You'll learn how to initiate and manage repositories effectively, a critical skill for collaborative development.
  • +
  • 1.5. GitHub: Discusses how to leverage GitHub for project hosting, version control, and collaboration. It's a pivotal component in modern development workflows, facilitating teamwork and project management.
  • +
  • 1.6. VS Code: Highlights the setup of Visual Studio Code (VS Code), showing how to adapt this versatile editor into an integrated development environment (IDE) customized for Python and MLOps projects.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.0. Notebooks.html b/2. Prototyping/2.0. Notebooks.html new file mode 100644 index 0000000..95a73c6 --- /dev/null +++ b/2. Prototyping/2.0. Notebooks.html @@ -0,0 +1,2642 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.0. Notebooks - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.0. Notebooks

+

What is a Python notebook?

+

A Python notebook, often referred to as "notebook," is an interactive computing environment that allows users to combine executable code, rich text, visuals, and other multimedia resources in a single document. This tool is invaluable for data science, machine learning projects, documentation, and educational purposes, among others. Notebooks are structured in a cell-based format, where each cell can contain either code or text. When code cells are executed, the output is displayed directly beneath them, facilitating a seamless integration of code and content.

+

Where can you learn how to use notebooks?

+

Learning how to use notebooks is straightforward, thanks to a plethora of online resources. Beginners can start with the official documentation of popular notebook applications like Jupyter or Google Colab. YouTube channels dedicated to data science and Python programming also frequently cover notebooks, providing valuable tips and tutorials for both beginners and advanced users.

+

Why should you use a notebook for prototyping?

+

Notebooks offer an unparalleled environment for prototyping due to their unique blend of features:

+
    +
  • Interactive Development: Notebooks allow for real-time code execution, offering immediate feedback on code functionality. This interactivity is especially beneficial when testing new ideas or debugging.
  • +
  • Exploratory Analysis: The ability to quickly iterate over different analytical approaches and visualize results makes notebooks an ideal tool for exploratory data analysis.
  • +
  • Productive Environment: The integrated environment of notebooks helps maintain focus by minimizing the need to switch between tools or windows. This consolidation of resources boosts productivity and streamlines the development process.
  • +
+

In addition, the narrative structure of notebooks supports a logical flow of ideas, facilitating the documentation of thought processes and methodologies. This makes it easier to share insights with peers or stakeholders and promote collaboration.

+

As an alternative to notebooks, consider using the Python Interactive Window in Visual Studio Code or other text editors. These environments combine the interactivity and productivity benefits of notebooks with the robustness and feature set of an integrated development environment (IDE), such as source control integration, advanced editing tools, and a wide range of extensions for additional functionality.

+

Can you use your notebook in production instead of creating a Python package?

+

Using notebooks in the early stages of development offers many advantages; however, they are not well-suited for production environments due to several limitations:

+
    +
  • Lack of Integration: Notebooks often do not integrate seamlessly with tools commonly used in the Python software development ecosystem, such as testing frameworks (pytest), linting tools (ruff), and package managers (poetry).
  • +
  • Mixed Content: The intermingling of code, output, and narrative in a single document can complicate version control and maintenance, especially with complex projects.
  • +
  • Non-Sequential Flow: Notebooks do not enforce a linear execution order, which can lead to confusion and errors if cells are run out of sequence.
  • +
  • Lack of Reusability: The format of notebooks does not naturally encourage the development of reusable and modular code, such as functions, classes, or packages.
  • +
+

For these reasons, it is advisable to transition from notebooks to structured Python packages for production. Doing so enables better software development practices, such as unit testing, continuous integration, and deployment, thereby enhancing code quality and maintainability.

+

Do you need to review this chapter even if you know how to use notebooks?

+

Even seasoned users can benefit from reviewing this chapter. It introduces advanced techniques, new features, and tools that you may not know about. Furthermore, the chapter emphasizes structuring notebooks effectively and applying best practices to improve readability, collaboration, and overall efficiency.

+

Why do you need to properly organize your Python notebooks?

+

Organizing your Python notebooks is key for efficiently converting them into Python packages, which is essential for scaling AI/ML projects. A well-structured notebook enhances productivity by simplifying maintenance, understanding, and debugging of the code. Proper organization involves using Markdown headers to divide the notebook into clear, logical sections, which not only facilitates code reuse and adaptation but also improves collaboration by making the notebooks easier to navigate and understand for all team members.

+

Should you save notebook outputs in your git repository?

+

Saving notebook outputs in your Git repository is generally not recommended due to several reasons:

+
    +
  • Version Control: Storing outputs in the repository can bloat the repository size, making it slower to clone and more cumbersome to manage.
  • +
  • Reproducibility: Including outputs can make it harder to reproduce the notebook's results, as the outputs may change over time or across different environments.
  • +
  • Confidentiality: Outputs may contain sensitive information, such as data values or model predictions, that should not be shared publicly.
  • +
  • Collaboration: Sharing outputs can lead to conflicts and confusion when multiple users work on the same notebook, as the outputs may not match the code execution.
  • +
  • Code Focus: The primary focus of version control systems like Git is on tracking changes to code, not data or outputs. Including outputs can distract from the main purpose of the repository.
  • +
+

Instead of saving outputs directly in the repository, consider using tools like Jupyter's nbconvert to export notebooks to different formats (e.g., HTML, PDF) that can be shared on other platforms or included in documentation. You can also use Jupyter's built-in cell tags to hide or exclude specific cells from the exported version, allowing you to control what information is shared while keeping the repository clean and focused on code.

+

Notebook additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.1. Imports.html b/2. Prototyping/2.1. Imports.html new file mode 100644 index 0000000..120a856 --- /dev/null +++ b/2. Prototyping/2.1. Imports.html @@ -0,0 +1,2655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.1. Imports - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.1. Imports

+

What are code imports?

+

In Python, code imports are statements that let you include functionality from other libraries or modules into your current project. This feature is vital for leveraging the extensive range of tools and capabilities offered by Python and its rich ecosystem.

+

As outlined by PEP 8, the Python community recommends organizing imports in a specific order for clarity and maintenance:

+
    +
  1. Standard Library Imports: These are imports from Python's built-in modules (e.g., os, sys, math). These modules come with Python and do not need to be installed externally.
  2. +
  3. Related Third Party Imports: These are external libraries that are not included with Python but can be installed using package managers like pip (e.g., numpy, pandas). They extend Python's functionality significantly.
  4. +
  5. Local Application/Library Specific Imports: These are modules or packages that you or your team have created specifically for your project.
  6. +
+

Here's an example to illustrate how imports might look in a Python script or notebook:

+
import os  # Standard library module
+
+import pandas as pd  # External library module
+
+from my_project import my_module  # Internal project module
+
+

Which packages do you need for your project?

+

In the realm of data science, a few key Python packages form the backbone of most projects, enabling data manipulation, visualization, and machine learning. Essential packages include:

+
    +
  • Pandas: For data manipulation and analysis.
  • +
  • NumPy: For numerical computing and array manipulation.
  • +
  • Matplotlib or Plotly: For creating static, interactive, and animated visualizations.
  • +
  • Scikit-learn: For machine learning, providing simple and efficient tools for data analysis and modeling.
  • +
+

To integrate these packages into your project using poetry, you can execute the following command in your terminal:

+
poetry add pandas numpy matplotlib scikit-learn plotly
+
+

This command tells poetry to download and install these packages, along with their dependencies, into your project environment, ensuring version compatibility and easy package management.

+

How should you organize your imports to facilitate your work?

+

Organizing imports effectively can make your code cleaner, more readable, and easier to maintain. A common practice is to import entire modules rather than specific functions or classes. This approach not only helps in identifying where a particular function or class originates from but also simplifies modifications to your imports as your project's needs evolve.

+

Consider the following examples:

+
# Importing entire modules (recommended)
+import pandas as pd
+from sklearn import ensemble
+model = ensemble.RandomForestClassifier()
+
+# Importing specific functions/classes
+from sklearn.ensemble import RandomForestClassifier
+model = RandomForestClassifier()
+
+

Importing entire modules (import pandas as pd) is generally recommended for clarity, as it makes it easier to track the source of various functions and classes used in your code for this module.

+

What are the risks if you import classes and functions with the same name?

+

Importing classes and functions with the same name from different modules can cause name collision, where the latest import overwrites the earlier ones. This can lead to unexpected behavior and make debugging more challenging. Additionally, it reduces code clarity, making the program harder to maintain and understand.

+

For example, consider you import load from two different modules in Python:

+
from module1 import load
+from module2 import load  # overwrite load imported from module1
+
+

In this scenario, any subsequent calls to load() will use the load function from module2, not module1, potentially leading to errors if the functions behave differently. To avoid such issues, you could use aliases:

+
from module1 import load as load1
+from module2 import load as load2`
+
+

Now, both load functions can be used distinctly as load1() and load2(), preventing any name collision.

+

Are there any side effects when importing modules in Python?

+

Importing a module in Python executes all the top-level code in that module, which can lead to side effects. These effects can be both intentional and unintentional. It's crucial to import modules from trusted sources to avoid security risks or unexpected behavior. Be especially cautious of executing code with side effects in your own modules, and make sure any such behavior is clearly documented.

+

Consider this cautionary example:

+
# A module with a potentially harmful operation
+# lib.py
+import os
+os.system("rm -rf /")  # This command is extremely dangerous!
+
+# main.py
+import lib  # Importing lib.py could lead to data loss
+
+

What should you do if packages cannot be imported from your notebook?

+

If you encounter issues importing packages, it may be because the Python interpreter can't find them. This problem is common when using virtual environments. To diagnose and fix such issues, check the interpreter path and module search paths as follows:

+
import sys
+print("Interpreter path:", sys.executable)
+print("Module search paths:", sys.path)
+
+

Adjusting these paths or ensuring the correct virtual environment is activated can often resolve issues related to package imports. With VS Code, you can select the Python environment associated with your project installation (e.g., .venv).

+

Imports additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.2. Configs.html b/2. Prototyping/2.2. Configs.html new file mode 100644 index 0000000..d94d1fb --- /dev/null +++ b/2. Prototyping/2.2. Configs.html @@ -0,0 +1,2707 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.2. Configs - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.2. Configs

+

What are configs?

+

Configurations, often abbreviated as "configs," serve as a cornerstone in programming. They encapsulate a set of parameters or settings designed to adapt the behavior of your code. By employing configs, you introduce a layer of flexibility and customization, enabling easy adjustments of critical variables without the need to tamper with the core logic of your codebase. This strategy not only enhances code usability but also its adaptability across various scenarios.

+

Here's a practical illustration of configs within a notebook context:

+
# Define paths for caching and training data
+CACHE_PATH = '../.cache/'
+TRAIN_DATA_PATH = '../data/train.csv'
+# Configure random state for reproducibility
+RANDOM_STATE = 0
+# Setup dataset parameters for testing and shuffling
+SHUFFLE = True
+TEST_SIZE = 0.2
+TARGET = "SalePrice"
+# Parameters for pipeline configurations
+CV = 5
+SCORING = "neg_mean_squared_error"
+PARAM_GRID = {
+    "regressor__max_depth": [12, 15, 18, 21],
+    "regressor__n_estimators": [150, 200, 250, 300],
+}
+
+

Why should you create configs?

+

Incorporating configs into your projects is a reflection of best practices in software development. This approach ensures your code remains:

+
    +
  • Flexible: Facilitating effortless adaptations and changes to different datasets or experimental scenarios.
  • +
  • Easy to Maintain: Streamlining the process of making updates or modifications without needing to delve deep into the core logic.
  • +
  • User-Friendly: Providing a straightforward means for users to tweak the notebook's functionality to their specific requirements without extensive coding interventions.
  • +
  • Avoid hard coding and magic numbers: Name and document key variables in your notebook to make them understandable and reviewable by others.
  • +
+

Effectively, configurations act as a universal "remote control" for your code, offering an accessible interface for fine-tuning its behavior.

+

Which configs can you provide out of the box?

+

When it comes to data science projects, several common configurations are frequently utilized, including:

+
    +
  • Data Processing: Parameters like SHUFFLE, TEST_SIZE, and RANDOM_STATE are instrumental in controlling how data is prepared and manipulated.
  • +
  • Model Parameters: Definitions such as N_ESTIMATORS and MAX_DEPTH cater to tuning machine learning model behaviors.
  • +
  • Execution Settings: Variables like BATCH_SIZE and EPOCHS are crucial for defining the operational aspects of iterative processes, with LIMIT setting constraints on dataset sizes.
  • +
+

An example of how you might define some of these settings is as follows:

+
# Configuration for shuffling the dataset to mitigate selection bias
+SHUFFLE = False
+# Setting aside a portion of the data for testing purposes
+TEST_SIZE = 0.2
+# Ensuring reproducibility across experiments through fixed randomness
+RANDOM_STATE = 0
+
+

How should you organize the configs in your notebook?

+

A logical and functional organization of your configurations can significantly enhance the readability and maintainability of your code. Grouping configs based on their purpose or domain of application is advisable:

+
## Paths
+
+Define inputs and outputs paths ...
+
+## Randomness
+
+Configure settings to fix randomness ...
+
+## Dataset
+
+Specifications on how to load and transform datasets ...
+
+## Pipelines
+
+Details on defining and executing model pipelines ...
+
+

Such categorization makes it easier for both users and developers to navigate and modify configurations as needed.

+

What are options?

+

In the context of data science notebooks, options are akin to configurations but are specifically tied to the behavior and presentation of libraries such as pandas, matplotlib, and scikit-learn. These options offer a means to customize various aspects, including display settings and output formats, to suit individual needs or project requirements.

+

Here's an example showcasing the use of options in a notebook:

+
import pandas as pd
+import sklearn
+
+# Configure pandas display settings
+pd.options.display.max_rows = None
+pd.options.display.max_columns = None
+# Adjust sklearn output format
+sklearn.set_config(transform_output="pandas")
+
+

Why do you need to pass options?

+

Library defaults may not always cater to your specific needs or the demands of your project. For instance:

+ +

Adjusting these options helps tailor the working environment to better fit your workflow and analytical needs, ensuring that outputs are both informative and visually accessible.

+

How should you configure library options?

+

To optimize your working environment, consider customizing the settings of key libraries according to your project's needs. Here are some guidelines:

+

For Pandas:

+
import pandas as pd
+
+# Adjust maximum display settings for rows and columns
+pd.options.display.max_rows = None
+pd.options.display.max_columns = None
+# Increase maximum column width to improve readability
+pd.options.display.max_colwidth = None
+
+

For Matplotlib:

+
import matplotlib.pyplot as plt
+
+# Customize default figure size for better visibility
+plt.rcParams['figure.figsize'] = (20, 10)
+
+

For Scikit-learn:

+
import sklearn
+
+# Modify the output format to return pandas dataframes instead of numpy arrays
+sklearn.set_config(transform_output='pandas')
+
+

Configs additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.3. Datasets.html b/2. Prototyping/2.3. Datasets.html new file mode 100644 index 0000000..0ef233f --- /dev/null +++ b/2. Prototyping/2.3. Datasets.html @@ -0,0 +1,2715 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.3. Datasets - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.3. Datasets

+

What are datasets?

+

Datasets are collections of data typically structured in a tabular format, comprising rows and columns where each row represents an observation and each column represents a feature of the observation. They are the foundational elements upon which models are trained, tested, and validated, allowing for the extraction of insights, predictions, and understandings of underlying patterns.

+

Here's an example of how you can load a dataset using pandas in a notebook:

+
import pandas as pd
+# Load the dataset into a pandas DataFrame
+train = pd.read_csv('data/train.csv', index_col='Id')
+# Display the shape of the dataset and its first few rows
+print(train.shape)
+train.head()
+
+

Dataset values

+

Datasets can originate from a wide range of sources including files (CSV, Excel, JSON, Parquet, Avro, ...), databases, and real-time data streams. They are essential for developing and testing machine learning models, conducting statistical analyses, and performing data visualization.

+

What are key datasets properties in pandas?

+

When working with datasets in pandas, several key properties enable you to quickly inspect and understand the structure and content of your data. According to the pandas documentation, the main DataFrame attributes include:

+
    +
  • .shape: Returns a tuple representing the dimensionality of the DataFrame.
  • +
  • .dtypes: Provides the data types of each column.
  • +
  • .columns: Gives an Index object containing column labels.
  • +
  • .index: Returns an Index object containing row labels.
  • +
+

These attributes and methods are invaluable for initial data exploration and integrity checks, facilitating a deeper understanding of the dataset's characteristics.

+

Which file format should you use?

+

Choosing the right file format for your dataset is crucial, as it affects the efficiency of data storage, access, and processing. Consider the following criteria when selecting a file format:

+
    +
  1. Orientation:
      +
    • Row-Oriented formats like CSV and Avro are preferable for transactional operations where row-level access is common.
    • +
    • Column-Oriented formats like Parquet and ORC are optimal for analytical querying, offering advantages in compression and read efficiency.
    • +
    +
  2. +
  3. Structure:
      +
    • Flat formats like CSV and Excel work well with tabular data and are straightforward to use with SQL queries and dataframes.
    • +
    • Hierarchical formats like JSON and XML are suitable for nested data structures, facilitating integration with document databases and APIs.
    • +
    +
  4. +
  5. Mode:
      +
    • Textual formats (e.g., CSV, JSON) are human-readable but require consideration for character encoding issues.
    • +
    • Binary formats (e.g., Parquet, Avro) offer superior speed and efficiency, making them suitable for larger datasets.
    • +
    +
  6. +
  7. Density:
      +
    • Dense formats store every data point explicitly
    • +
    • Sparse formats only store non-zero values, which can be more efficient for datasets with many missing values.
    • +
    +
  8. +
+

For data analytics workloads, we recommend using column-oriented, flat, binary format like the Apache Parquet format.

+

For machine learning modeling, we recommend using row-oriented, binary format based on your framework like TFRecord for TensorFlow or XGBoost DMatrix format.

+

How can you optimize the dataset loading process?

+

Optimizing the dataset loading process involves several strategies:

+
    +
  • Vectorization: Utilize operations that apply to entire arrays or datasets at once, minimizing the use of slow Python loops.
  • +
  • Multi-core Processing: Leverage libraries that can perform computations in parallel across multiple CPU cores.
  • +
  • Lazy Evaluation: Use programming techniques or libraries that delay the computation until necessary, optimizing memory usage and computation time.
  • +
  • Distributed Computing: For handling very large datasets, consider distributed computing frameworks that process data across multiple machines.
  • +
+

For large datasets, pandas might not be sufficient. Consider alternative libraries designed for handling large-scale data efficiently:

+
    +
  • Polars: A Rust-based data processing library that is optimized for performance on a single machine and supports lazy operations.
  • +
  • DuckDB: An in-process SQL OLAP database management system that excels in analytical query performance on a single machine.
  • +
  • Spark: A distributed computing system that provides comprehensive support for big data processing and analytics.
  • +
+

The Ibis project unifies these alternatives under a common interface, allowing seamless transition between different backends based on the scale of your data and computational resources (e.g., using pandas for small datasets on a laptop and Spark for big data on clusters).

+

Why do you need to split your dataset into 'X' and 'y'?

+

In supervised learning, the convention is to split the dataset into features (X) and the target variable (y). This separation is crucial because it delineates the input variables that the model uses to learn from the output variable it aims to predict. Structuring your data this way makes it clear to both the machine learning algorithms and the developers what the inputs and outputs of the models should be.

+

You can separate these using pandas in the following way:

+
# Separate the dataset into features and target variable
+X, y = train.drop('target', axis='columns'), train['target']
+
+

This practice lays the groundwork for model training and evaluation, ensuring that the algorithms have a clear understanding of the data they are working with.

+

Do all datasets contain potential X and y variables?

+

Not all datasets contain distinct X (features) and y (target) variables. These are specific to supervised learning. Other types of datasets and machine learning algorithms include:

+
    +
  • Time Series Forecasting: Predicts future values of the same series without separate y targets.
  • +
  • Unsupervised Learning: Like clustering, where data is grouped without predefined targets, or principal component analysis (PCA) which reduces dimensions without a target variable.
  • +
  • Reinforcement Learning: Involves learning from the consequences of actions in an environment, focusing on maximizing rewards rather than predicting a target.
  • +
  • Anomaly Detection: Identifies unusual patterns or outliers without a specific target variable for training.
  • +
+

Why should you split your dataset further into train/test sets?

+

Splitting your dataset into training and testing sets is essential for accurately evaluating the performance of your machine learning models. This approach allows you to:

+
    +
  • Avoid Overfitting: Ensuring that your model performs well not just on the data it was trained on, but also on new, unseen data.
  • +
  • Detect Underfitting: Helping identify if the model is too simplistic to capture the underlying patterns in the data.
  • +
+

The train_test_split function from scikit-learn is commonly used for this purpose:

+
from sklearn.model_selection import train_test_split
+# Split the dataset into training and testing sets
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+

It's crucial to manage potential issues like data leakage, class imbalances, and the temporal nature of data to ensure the reliability of your model evaluations. For instance, the Bike Sharing Demand dataset might use a scikit-learn TimeSeriesSplit to take into account the forecasting aspects of the project.

+

Do you need to shuffle your dataset prior to splitting it into train/test sets?

+

Whether to shuffle your dataset before splitting it into training and testing sets depends on the nature of your problem. For time-sensitive data, such as time series, shuffling could disrupt the temporal sequence, leading to misleading training data and inaccurate models. In such cases, maintaining the chronological order is critical.

+

For datasets where time or sequence does not impart any context to the data, shuffling helps to ensure that the training and testing sets are representative of the overall dataset, preventing any unintentional bias that might arise from the original ordering of the data. This is especially important in scenarios where the dataset may have been sorted or is not randomly distributed (e.g., sorted by price).

+

Datasets additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.4. Analysis.html b/2. Prototyping/2.4. Analysis.html new file mode 100644 index 0000000..bc0602d --- /dev/null +++ b/2. Prototyping/2.4. Analysis.html @@ -0,0 +1,2632 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.4. Analysis - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.4. Analysis

+

What is Exploratory Data Analysis (EDA)?

+

Exploratory Data Analysis (EDA) is a critical step in the data analysis process which involves investigating and summarizing the main characteristics of a dataset, often with visual methods. The goal of EDA is to obtain a deep understanding of the data’s underlying structures and variables, to detect outliers and anomalies, to uncover patterns, and to test assumptions with the help of statistical summaries and graphical representations.

+

EDA is a flexible, data-driven approach that allows for a more in-depth understanding of the data before making any assumptions. It serves as a foundation for formulating hypotheses, defining a more targeted analysis, and selecting appropriate models and algorithms for machine learning projects.

+

How can you use pandas to analyze your data?

+

Dataframe libraries like Pandas are an essential tool for EDA in Python, offering a wide array of functions to quickly slice, dice, and summarize your data. To begin analyzing your dataset with pandas, you can use the following methods:

+
    +
  • .info(): This method provides a concise summary of a DataFrame, giving you a quick overview of the data types, non-null values, and memory usage. It's a good starting point to understand the structure of your dataset.
  • +
  • .describe(include='all'): Generates descriptive statistics that summarize the central tendency, dispersion, and shape of the dataset's distributions. By setting include='all', you ensure that both numeric and object column types are included in the output, offering a more comprehensive view.
  • +
+

Here’s how you might use these methods in practice:

+
import pandas as pd
+df = pd.read_csv('your_dataset.csv')
+# Get a concise summary of the DataFrame
+df.info()
+# Get descriptive statistics for all columns
+df.describe(include='all')
+
+

Dataset statistics

+

These functions allow you to quickly assess the quality and characteristics of your data, facilitating the identification of areas that may require further investigation or preprocessing.

+

How can you visualize patterns in your dataset?

+

Visualizing patterns in your dataset is pivotal for EDA, as it helps in recognizing underlying structures, trends, and outliers that might not be apparent from the raw data alone. Python offers a wealth of libraries for data visualization, including:

+
    +
  • Plotly Express: A high-level interface for interactive graphing.
  • +
  • Matplotlib: A widely used library for creating static, animated, and interactive visualizations.
  • +
  • Seaborn: A library based on matplotlib that provides a high-level interface for drawing attractive statistical graphics.
  • +
+

For instance, Plotly Express's scatter_matrix can be utilized to explore relationships between multiple variables:

+
import plotly.express as px
+df = pd.read_csv('your_dataset.csv')
+px.scatter_matrix(
+df, dimensions=["feature1", "feature2", "feature3"], color="target_variable",
+height=800, title="Scatter Matrix of Features"
+)
+
+

Analysis scatter matrix

+

This method enables the rapid exploration of pairwise relationships within a dataset, facilitating the identification of patterns, correlations, and potential hypotheses for deeper analysis.

+

Is there a way to automate EDA?

+

There are libraries designed to automate the EDA process, significantly reducing the time and effort required to understand a dataset. One such library is ydata-profiling, which generates comprehensive reports from a pandas DataFrame, providing insights into the distribution of each variable, correlations, missing values, and much more.

+

Example with ydata-profiling:

+
from ydata_profiling import ProfileReport
+df = pd.read_csv('your_dataset.csv')
+profile = ProfileReport(df, title='Pandas Profiling Report', minimal=True)
+profile.to_widgets()
+
+

While automated EDA tools like ydata-profiling can offer a quick and broad overview of the dataset, they are not a complete substitute for manual EDA. Human intuition and expertise are crucial for asking the right questions, interpreting the results, and making informed decisions on how to proceed with the analysis. Therefore, automated EDA should be viewed as a complement to, rather than a replacement for, traditional exploratory data analysis methods.

+

How can you handle missing values in datasets?

+

Handling missing values in datasets is crucial for maintaining data integrity. Here are common methods:

+
    +
  1. Remove Data: Delete rows with missing values, especially if the missing data is minimal.
  2. +
  3. Impute Values: Replace missing values with a statistical substitute like mean, median, or mode, or use predictive modeling.
  4. +
  5. Indicator Variables: Create new columns to indicate data is missing, which can be useful for some models.
  6. +
+

MissingNo is a tool for visualizing missing data in Python. To use it:

+
    +
  1. Install MissingNo: pip install missingno
  2. +
  3. Import and Use: +
    import missingno as msno
    +import pandas as pd
    +
    +data = pd.read_csv('your_data.csv')
    +msno.matrix(data)  # Visual matrix of missing data
    +msno.bar(data)     # Bar chart of non-missing values
    +
  4. +
+

These visualizations help identify patterns and distributions of missing data, aiding in effective preprocessing decisions.

+

Analysis additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.5. Modeling.html b/2. Prototyping/2.5. Modeling.html new file mode 100644 index 0000000..b293839 --- /dev/null +++ b/2. Prototyping/2.5. Modeling.html @@ -0,0 +1,2768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.5. Modeling - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.5. Modeling

+

What are pipelines?

+

Pipelines in machine learning provide a streamlined way to organize sequences of data preprocessing and modeling steps. They encapsulate a series of data transformations followed by the application of a model, facilitating both simplicity and efficiency in the development process. Pipelines can be broadly categorized as follows:

+
    +
  • Model Pipeline: Focuses specifically on sequences related to preparing data for machine learning models and applying these models. For instance, scikit-learn's Pipeline class allows for chaining preprocessors and estimators.
  • +
  • Data Pipeline: Encompasses a wider scope, including steps for data gathering, cleaning, and transformation. Tools such as Prefect and ZenML offer capabilities for building comprehensive data pipelines.
  • +
  • Orchestration Pipeline: Targets the automation of a series of tasks, including data and model pipelines, ensuring they execute in an orderly fashion or under specific conditions. Examples include Apache Airflow for creating directed acyclic graphs (DAGs) and Vertex AI for managing ML workflows.
  • +
+

For the purposes of this discussion, we'll focus on model pipelines, crucial for efficiently prototyping machine learning solutions. The code example are based on scikit-learn pipeline, as this toolkit is simple to understand and its concept can be generalized to other types of pipeline like Dagster, Prefect, or Metaflow.

+

Example of defining a pipeline in a notebook:

+
from sklearn import pipeline, compose, preprocessing, ensemble
+
+categoricals = [...] # List of categorical feature names
+numericals = [...] # List of numerical feature names
+RANDOM = 42 # Fixed random state for reproducibility
+CACHE = './.cache' # Path for caching transformers
+
+# Constructing a pipeline
+draft = pipeline.Pipeline(
+    steps=[
+        ("transformer", compose.ColumnTransformer([
+            ("categoricals", preprocessing.OneHotEncoder(
+                sparse_output=False, handle_unknown="ignore"
+            ), categoricals),
+            ("numericals", "passthrough", numericals),
+        ], remainder="drop")),
+        ("regressor", ensemble.RandomForestRegressor(random_state=RANDOM)),
+    ],
+)
+
+

Model pipeline

+

Why do you need to use a pipeline?

+

Implementing pipelines in your machine learning projects offers several key advantages:

+
    +
  • Prevents Data Leakage during preprocessing: By ensuring data preprocessing steps are applied correctly during model training and validation, pipelines help maintain the integrity of your data.
  • +
  • Simplifies Cross-Validation and Hyperparameter Tuning: Pipelines facilitate the application of transformations to data subsets appropriately during procedures like cross-validation, ensuring accurate and reliable model evaluation.
  • +
  • Ensures Consistency: Pipelines guarantee that the same preprocessing steps are executed in both the model training and inference phases, promoting consistency and reliability in your ML workflow.
  • +
+

Pipelines thus represent an essential tool in the machine learning toolkit, streamlining the model development process and enhancing model performance and evaluation.

+

Why do you need to process inputs by type?

+

Different data types typically require distinct preprocessing steps to prepare them effectively for machine learning models:

+
    +
  • Numerical Features may benefit from scaling or normalization to ensure that they're on a similar scale.
  • +
  • Categorical Features often require encoding (e.g., OneHotEncoding) to transform them into a numerical format that models can understand.
  • +
  • Datetime Features might be broken down into more granular components (e.g., year, month, day) to capture temporal patterns more effectively.
  • +
+

Utilizing scikit-learn's ColumnTransformer, you can specify different preprocessing steps for different columns of your data, ensuring that each type is handled appropriately.

+

Example of selecting features by type from a Pandas DataFrame:

+
import pandas as pd
+
+# Assume X_train is your training data stored in a Pandas DataFrame
+num_features = X_train.select_dtypes(include=['number']).columns.tolist()
+cat_features = X_train.select_dtypes(include=['object', 'category']).columns.tolist()
+
+

What is the benefit of using a memory cache?

+

Employing a memory cache with pipelines, such as the memory attribute in scikit-learn's Pipeline, offers significant performance benefits by caching the results of transformation steps. This approach is particularly advantageous during operations like grid search, where certain preprocessing steps are repeatedly executed across different parameter combinations. Caching can dramatically reduce computation time by avoiding redundant processing.

+

Example of utilizing a memory cache with a pipeline:

+
from sklearn import pipeline, compose, preprocessing, ensemble
+
+# Assuming 'categoricals' and 'numericals' are defined as before
+CACHE = './.cache' # Directory for caching transformers
+
+# Constructing the pipeline with caching enabled
+draft = pipeline.Pipeline(
+    steps=[
+        ("transformer", compose.ColumnTransformer([
+            ("categoricals", preprocessing.OneHotEncoder(
+                sparse_output=False, handle_unknown="ignore"
+            ), categoricals),
+            ("numericals", "passthrough", numericals),
+        ], remainder="drop")),
+        ("regressor", ensemble.RandomForestRegressor(random_state=RANDOM)),
+    ],
+    memory=CACHE,
+)
+
+

Even if you don't plan on using scikit-learn pipeline abstraction, you can implement the same concept in your code base to obtain the same benefits.

+

How can you change the pipeline hyper-parameters?

+

Adjusting hyper-parameters within a scikit-learn pipeline can be achieved using the set_params method or by directly accessing parameters via the double underscore (__) notation. This flexibility allows you to fine-tune your model directly within the pipeline structure.

+

Example of setting pipeline hyper-parameters:

+
from sklearn.pipeline import Pipeline
+from sklearn import preprocessing, ensemble
+
+# Assume 'RANDOM_STATE' and 'PARAM_GRID' are defined
+pipeline = Pipeline([
+    ('encoder', preprocessing.OneHotEncoder()),
+    ('regressor', ensemble.RandomForestRegressor(random_state=RANDOM_STATE))
+])
+
+# Adjusting hyper-parameters using 'set_params'
+pipeline.set_params(regressor__n_estimators=100, regressor__max_depth=10)
+
+

Why do you need to perform a grid search with your pipeline?

+

Conducting a grid search over a pipeline is crucial for identifying the optimal combination of model hyper-parameters. This exhaustive search evaluates various parameter combinations across your dataset, using cross-validation to ensure robust assessment of model performance.

+

Example of performing grid search with a pipeline:

+
from sklearn.model_selection import GridSearchCV
+from sklearn import model_selection
+
+CV = 5
+SCORING = 'neg_mean_squared_error'
+PARAM_GRID = {
+    "regressor__max_depth": [15, 20, 25],
+    "regressor__n_estimators": [150, 200, 250],
+}
+RANDOM_STATE = 0
+
+splitter = model_selection.TimeSeriesSplit(n_splits=CV)
+
+search = GridSearchCV(
+    estimator=draft, cv=splitter, param_grid=PARAM_GRID, scoring=SCORING, verbose=1
+)
+search.fit(inputs_train, targets_train)
+
+

Why do you need to perform cross-validation with your pipeline?

+

Cross-validation is a fundamental technique in the validation process of machine learning models, enabling you to assess how well your model is likely to perform on unseen data. By integrating cross-validation into your pipeline, you can ensure a thorough evaluation of your model's performance, mitigating the risk of overfitting and underfitting.

+

When utilizing GridSearchCV from scikit-learn for hyperparameter tuning, the cv parameter plays a crucial role in defining the cross-validation splitting strategy. This flexibility allows you to tailor the cross-validation process to the specific needs of your dataset and problem domain, ensuring that the model evaluation is both thorough and relevant.

+

Here’s a breakdown of how you can control the cross-validation behavior through the cv parameter:

+
    +
  • +

    None: By default, or when cv is set to None, GridSearchCV employs a 5-fold cross-validation strategy. This means the dataset is divided into 5 parts, with the model being trained on 4 parts and validated on the 1 remaining part in each iteration.

    +
  • +
  • +

    Integer: Specifying an integer for cv changes the number of folds in a K-Fold (or StratifiedKFold for classification tasks) cross-validation. For example, cv=10 would perform a 10-fold cross-validation, offering a more thorough validation at the cost of increased computational time.

    +
  • +
  • +

    CV Splitter: scikit-learn provides several splitter classes (e.g., KFold, StratifiedKFold, TimeSeriesSplit) that can be used to define more complex cross-validation strategies. Passing an instance of one of these splitters to cv allows for customized dataset splitting that can account for factors like class imbalance or temporal dependencies.

    +
  • +
  • +

    Iterable: An iterable yielding train/test splits as arrays of indices directly specifies the data partitions for each fold. This option offers maximum flexibility, allowing for completely custom splits based on external logic or considerations (e.g., predefined groups or stratifications not captured by the standard splitters).

    +
  • +
+

Do you need to retrain your pipeline? Should you use the full dataset?

+

After identifying the best model and hyper-parameters through grid search and cross-validation, it's common practice to retrain your model on the entire dataset. This approach allows you to leverage all available data, maximizing the model's learning and potentially enhancing its performance when making predictions on new, unseen data.

+

Retraining your model on the full dataset takes advantage of the insights gained during the model selection process, ensuring that the final model is as robust and well-tuned as possible.

+

Example of retraining your pipeline on the full dataset:

+
# Assuming 'search' is your GridSearchCV object and 'X', 'y' are your full dataset
+final_model = search.best_estimator_
+final_model.fit(X, y)
+
+

Alternatively, if you've used GridSearchCV with refit=True (which is the default setting), the best estimator is automatically refitted on the whole dataset provided to fit, making it ready for use immediately after grid search:

+
# 'search' has been conducted with refit=True
+final_model = search.best_estimator_
+
+

In this way, the final model embodies the culmination of your exploratory work, tuned hyper-parameters, and the comprehensive learning from the entire dataset, positioning it well for effective deployment in real-world applications.

+

It's important to note, however, that while retraining on the full dataset can improve performance, it also eliminates the possibility of evaluating the model on unseen data unless additional, separate validation data is available. Therefore, the decision to retrain should be made with consideration of how model performance will be assessed and validated post-retraining.

+

Modeling additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/2.6. Evaluations.html b/2. Prototyping/2.6. Evaluations.html new file mode 100644 index 0000000..12ee876 --- /dev/null +++ b/2. Prototyping/2.6. Evaluations.html @@ -0,0 +1,2778 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.6. Evaluations - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

2.6. Evaluations

+

What is an evaluation?

+

Model evaluation is a fundamental step in the machine learning workflow that involves assessing a model's predictions to ensure its reliability and accuracy before deployment. It acts as a quality assurance mechanism, providing insights into the model's performance through various means such as error metrics, graphical representations (like validation curves), and more. This step is crucial for verifying that the model performs as expected and is suitable for real-world applications.

+

Why should you evaluate your pipeline?

+

Machine learning models can sometimes behave in unpredictable ways due to their inherent complexity. By evaluating your training pipeline, you can uncover issues like data leakage, which undermines the model's ability to generalize to unseen data. Rigorous evaluation builds trust and credibility, ensuring that the model's performance is genuinely robust and not just a result of overfitting or other biases.

+

For more insights on data leakage, explore this link: Data Leakage in Machine Learning.

+

How can you generate predictions with your pipeline?

+

To generate predictions using your machine learning pipeline, employ the hold-out dataset (test set). This approach ensures that the predictions are made on data that the model has not seen during training, providing a fair assessment of its generalization capability. Here's how you can do it:

+
# Generate predictions
+predictions = pd.Series(final.predict(inputs_test), index=inputs_test.index)
+print(predictions.shape)
+predictions.head()
+
+

Also, leverage the insights from your hyperparameter tuning process to understand the effectiveness of various configurations:

+
# Analyze hyperparameter tuning results
+results = pd.DataFrame(search.cv_results_)
+results = results.sort_values(by="rank_test_score")
+results.head()
+
+

What do you need to evaluate in your pipeline?

+

Evaluating your training pipeline encompasses several key areas:

+

Ranks

+

When analyzing the outcomes of hyperparameter tuning, focus on:

+
    +
  • Identifying ineffective hyperparameter combinations to eliminate.
  • +
  • Determining if the best hyperparameters are outliers or represent a common trend.
  • +
+

This helps in deciding whether to expand or narrow the search space for optimal parameters.

+

Example:

+
# Visualize rank by test score
+px.line(results, x="rank_test_score", y="mean_test_score", title="Rank by test score")
+
+

Evaluation ranks

+

Params

+

Investigate which hyperparameters lead to better performance by:

+
    +
  • Spotting trends that suggest optimal settings.
  • +
  • Identifying hyperparameters with minimal impact, which could be omitted.
  • +
+

This enables pinpointing the most effective hyperparameters for your specific problem.

+

Example:

+
# Visualize hyperparameter impact
+dimensions = [col for col in results.columns if col.startswith("param_")]
+px.parallel_categories(results, dimensions=dimensions, color="mean_test_score", title="Params by test score")
+
+

Evaluation ranks

+

Predictions

+

Examine the distribution and balance of prediction values, ensuring they align with your training set's characteristics. A similar distribution and balance indicate that your model is generalizing well.

+

Example with a single metric:

+
# Calculate a performance metric
+score = metrics.mean_squared_error(y_test, y_pred)
+score
+
+

Example with a distribution:

+
# Visualize distribution of errors
+px.histogram(errors, x="error", title="Distribution of errors")
+
+

Evaluation errors

+

Feature Importances

+

Understanding which features most significantly influence your model's predictions can streamline the model by removing non-essential features. This analysis is more straightforward in models based on linear and tree structures.

+

Example:

+
# Determine feature importances
+importances = pd.Series(
+    final.named_steps["regressor"].feature_importances_,
+    index=final[:-1].get_feature_names_out(),
+).sort_values(ascending=False)
+print(importances.shape)
+importances.head()
+
+

Evaluation feature importances

+

How can you ensure your pipeline was trained on enough data?

+

Employing a learning curve analysis helps you understand the relationship between the amount of training data and model performance. Continue adding diverse data until the model's performance stabilizes, indicating an optimal data volume has been reached.

+

Example using scikit-learn's learning curve:

+
# Analyze learning curve
+train_size, train_scores, test_scores = model_selection.learning_curve(
+    final, inputs, targets, cv=splitter, scoring=SCORING, random_state=RANDOM,
+)
+learning = pd.DataFrame(
+    {
+        "train_size": train_size,
+        "mean_test_score": test_scores.mean(axis=1),
+        "mean_train_score": train_scores.mean(axis=1),
+    }
+)
+px.line(learning, x="train_size", y=["mean_test_score", "mean_train_score"], title="Learning Curve")
+
+

Evaluation learning curve

+

How can you ensure your pipeline captures the right level of complexity?

+

To balance complexity and performance, use validation curves to see how changes in a model parameter (like depth) affect its performance. Adjust complexity to improve performance without causing overfitting.

+

Example with scikit-learn's validation curve:

+
# Explore validation curves for different parameters
+for param_name, param_range in PARAM_GRID.items():
+    print(f"Validation Curve for: {param_name} -> {param_range}")
+    train_scores, test_scores = model_selection.validation_curve(
+        final, inputs, targets, cv=splitter, scoring=SCORING,
+        param_name=param_name, param_range=param_range,
+    )
+    validation = pd.DataFrame(
+        {
+            "param_value": param_range,
+            "mean_test_score": test_scores.mean(axis=1),
+            "mean_train_score": train_scores.mean(axis=1),
+        }
+    )
+    px.line(
+        validation, x="param_value", y=["mean_test_score", "mean_train_score"], title=f"Validation Curve: {param_name}"
+    )
+
+

Evaluation validation curve

+

Evaluations additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/2. Prototyping/index.html b/2. Prototyping/index.html new file mode 100644 index 0000000..e6c7c24 --- /dev/null +++ b/2. Prototyping/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2. Prototyping - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

2. Prototyping

+

In this chapter, we'll explore the cornerstone of any machine learning (ML) project: Prototyping through Python notebooks. Prototyping is a preliminary phase where data scientists and engineers experiment with various approaches to find the most effective solution. This stage is crucial for understanding the problem at hand, experimenting with different models, and identifying the best strategies before finalizing the project's architecture and moving into production. We'll cover essential tools and practices that enhance the efficiency and effectiveness of this process, focusing on practical aspects that can significantly impact the success of ML projects.

+
    +
  • 2.0. Notebooks: Introduces Jupyter notebooks as an essential tool for prototyping in machine learning, covering their advantages for iterative development and interactive data exploration.
  • +
  • 2.1. Imports: Discusses best practices for organizing import statements in notebooks to ensure clarity and maintainability, including recommendations for grouping and ordering libraries.
  • +
  • 2.2. Configs: Highlights the importance of centralizing configuration settings, such as paths and parameters, for easier experimentation and reproducibility.
  • +
  • 2.3. Datasets: Offers guidelines for loading, exploring, and preprocessing datasets within notebooks, emphasizing methods for efficient data handling and analysis.
  • +
  • 2.4. Analysis: Explores techniques for conducting thorough data analysis in notebooks, including visualizations, statistical tests, and exploratory data analysis (EDA) practices.
  • +
  • 2.5. Modeling: Details strategies for building, refining, and comparing machine learning models directly within notebooks, covering everything from initial prototypes to model selection.
  • +
  • 2.6. Evaluations: Provides insights on effectively evaluating model performance using various metrics and visualizations, underscoring the role of evaluation in the iterative model development process.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.0. Package.html b/3. Productionizing/3.0. Package.html new file mode 100644 index 0000000..d94b2fe --- /dev/null +++ b/3. Productionizing/3.0. Package.html @@ -0,0 +1,2697 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.0. Package - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.0. Package

+

What is a Python package?

+

A Python package is a structured collection of Python modules, which allows for a convenient way to organize and share code. Among the various formats a package can take, the wheel format (.whl) stands out. Wheels are a built package format that can significantly speed up the installation process for Python software, compared to distributing source code and requiring the user to build it themselves.

+

Why do you need to create a Python package?

+

Creating a Python package offers multiple benefits, particularly for developers looking to distribute their code effectively:

+
    +
  • As a Library: Packaging your code as a library enables you to share reusable components across different projects. This is common in the Python ecosystem, with examples like numpy, pandas, and tensorflow being shared as libraries.
  • +
  • As an Application: Packaging also plays a crucial role in deploying applications. It simplifies the distribution and installation process, ensuring your software can be easily executed on various systems, including web or mobile platforms.
  • +
+

Additionally, creating a package can enhance the maintainability of your code, enforce good coding practices by encouraging modular design, and facilitate version control and dependency management.

+

Which tool should you use to create a Python package?

+

The Python ecosystem provides several tools for packaging, each with its unique features and advantages. While the choice can seem overwhelming, as humorously depicted in the xkcd comic on Python environments, Poetry emerges as a standout option. Poetry simplifies dependency management and packaging, offering an intuitive interface for developers.

+

To get started with Poetry for packaging, you can use the following commands:

+
    +
  • Initiate a Poetry package:
  • +
+
poetry init
+
+
    +
  • Start developing the package:
  • +
+
poetry install
+
+
    +
  • Build a package with Poetry:
  • +
+
poetry build --format wheel
+
+

At the end of the build process, a .whl file is generated in the dist folder with the name and version of the project from pyproject.toml.

+

For those seeking alternatives, tools like PDM, Hatch, and Pipenv offer different approaches to package management and development, each with its own set of features designed to cater to various needs within the Python community.

+

Do you recommend Conda for your AI/ML project?

+

Although Conda is a popular choice among data scientists for its ability to manage complex dependencies, it's important to be aware of its limitations. Challenges such as slow performance, a complex dependency resolver, and confusing channel management can hinder productivity. Moreover, Conda's integration with the Python ecosystem, especially with new standards like pyproject.toml, is limited. For managing complex dependencies in AI/ML projects, Docker containers present a robust alternative, offering better isolation and compatibility across environments.

+

How can you install new dependencies with Poetry?

+

Please refer to this section of the course.

+

Which metadata should you provide to your Python package?

+

Including detailed metadata in your pyproject.toml file is crucial for defining your package's identity and dependencies. This file should contain essential information such as the package name, version, authors, and dependencies. Here's an example that outlines the basic structure and content for your package's metadata:

+
# Example metadata for a Python package
+
+[tool.poetry]
+name = "bikes"
+version = "1.0.0"
+description = "Predict the number of bikes available."
+repository = "https://github.com/yourusername/mlops-python-package"
+documentation = "https://yourusername.github.io/mlops-python-package/"
+authors = ["Your Name <youremail@example.com>"]
+readme = "README.md"
+license = "CC BY"
+keywords = ["mlops", "python", "package"]
+packages = [{ include = "bikes", from = "src" }]
+
+[tool.poetry.dependencies]
+python = "^3.10"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+

This information not only aids users in understanding what your package does but also facilitates its discovery and integration into other projects.

+

Where should you add the source code of your Python package?

+

For a clean and efficient project structure, placing your package's source code in a src directory is recommended. This approach, known as the src layout, separates your package's code from other project files, such as tests and documentation, reducing the risk of import clashes and making it easier to package and distribute your code.

+

Here's how you can set up this structure:

+
mkdir -p src/bikes
+touch src/bikes/__init__.py
+
+

The presence of an __init__.py file within a directory indicates to Python that this directory should be treated as a package, making it possible for other parts of your project or external projects to import its modules.

+

Should you publish your Python package? On which platform should you publish it?

+

Deciding whether to publish your Python package depends on your goals. If you aim to share your work with the broader community or need a convenient way to distribute your code across projects or teams, publishing is a great option. The Python Package Index (PyPI) is the primary repository for public Python packages, making it an ideal platform for reaching a wide audience.

+

For private packages or when sharing within a limited group or organization, platforms like AWS CodeArtifact or GCP Artifact Registry offer secure hosting and management of your packages.

+

To publish a package using Poetry, you can use the command:

+
poetry publish
+
+

This will upload your package to PyPI, making it available for installation via pip by the Python community.

+

Package additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.1. Modules.html b/3. Productionizing/3.1. Modules.html new file mode 100644 index 0000000..78abef9 --- /dev/null +++ b/3. Productionizing/3.1. Modules.html @@ -0,0 +1,2655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.1. Modules - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.1. Modules

+

What are Python modules?

+

Python modules are files containing Python code that serve as a fundamental organizational unit in Python programming. These modules encapsulate definitions such as functions, classes, variables, and constants, making it easier to organize, reuse, and share code across different parts of a program.

+

Modules in Python are more than just physical files; they represent a namespace for Python objects. Importing a module allows you to access these objects in your current script or interactive session.

+

To discover the physical location of a module, use its __file__ attribute:

+
import math
+print(math.__file__)
+
+

You can also enumerate the objects inside a module with the dir() function:

+
import math
+print(dir(math))
+
+

Why do you need Python modules?

+

Python modules are essential for managing complexity in your projects. They provide a way to segment your code into distinct namespaces, making your projects more organized, readable, and maintainable. For example, in a machine learning project, you might have separate modules for models (models.py), data processing (datasets.py), and utility functions (utils.py). This separation helps in understanding, testing, and collaborating on large codebases.

+

Modules become indispensable as your project grows beyond a simple script. While a project with less than 100 lines of code might not need separate modules, larger projects benefit greatly from a modular structure.

+

How should you create a Python module?

+

Creating a Python module is as simple as creating a .py file within your project package. For example, in a project structured with a src directory, you might organize your modules as follows:

+
$ touch src/bikes/models.py
+$ touch src/bikes/datasets.py
+
+

This creates two modules, models.py and datasets.py, under the bikes package. Each module can then contain specific functionalities related to your project, such as defining data models or handling dataset loading and preprocessing.

+

How should you import your Python module?

+

Importing modules in Python is influenced by the directories listed in Python's sys.path, akin to path resolution in Unix systems. When importing a module, Python searches through these directories and imports the first match.

+

To see what directories are in your search path, you can use:

+
import sys
+print(sys.path)
+
+

After installing your package locally (e.g., using poetry install), your package's directory will be added to sys.path, allowing you to import its modules without specifying their full path.

+

How should you organize your Python modules?

+

Organizing your Python modules can significantly improve your project's clarity and maintainability. Here are a few strategies for structuring your modules:

+
    +
  1. Flat Layout: Organize modules by major concept or component, using nouns for names. Examples include:
      +
    • models.py
    • +
    • datasets.py
    • +
    • services.py
    • +
    • splitters.py
    • +
    +
  2. +
  3. IO and Domain Separation: Separate modules based on their interaction with the external world (I/O) and internal logic (domain), inspired by IO monad in Haskell and Domain-Driven Design. For instance:
      +
    • IO Layer:
        +
      • io/services.py
      • +
      • io/datasets.py
      • +
      +
    • +
    • Domain Layer:
        +
      • domain/models.py
      • +
      • domain/schemas.py
      • +
      +
    • +
    • High-Level Tasks:
        +
      • training.py
      • +
      • tuning.py
      • +
      • inference.py
      • +
      +
    • +
    +
  4. +
+

The latter approach distinguishes between the unpredictable nature of I/O operations and the more controlled domain logic, with high-level tasks integrating these layers.

+

What are the risks of using Python modules?

+

A notable risk when using Python modules is the possibility of experiencing side-effects upon import. Side-effects are operations that occur when a module is imported, which can lead to unexpected behavior or bugs if not handled carefully.

+
# A module with a potentially harmful operation
+# lib.py
+import os
+os.system("rm -rf /")  # This command is extremely dangerous!
+
+# main.py
+import lib  # Importing lib.py could lead to data loss
+
+

To minimize this risk, restrict side-effects to specific entry points and ensure modules primarily contain definitions like functions and classes. Avoid executing code that produces side-effects directly at the module level to ensure cleaner, more predictable imports.

+

Module additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.2. Paradigms.html b/3. Productionizing/3.2. Paradigms.html new file mode 100644 index 0000000..ab8017c --- /dev/null +++ b/3. Productionizing/3.2. Paradigms.html @@ -0,0 +1,2988 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.2. Paradigms - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.2. Paradigms

+

What is a programming paradigm?

+

A programming paradigm refers to a style or methodology of programming that provides a framework for structuring and solving problems. Paradigms influence how we organize, write, and think about code. Common programming paradigms include procedural, object-oriented, functional, and declarative programming. Each paradigm offers a unique approach to code organization, abstraction, and reuse.

+
    +
  • Procedural programming emphasizes a step-by-step set of instructions, focusing on routines or subroutines to process data.
  • +
  • Object-oriented programming (OOP) encapsulates data and functions that operate on that data within objects, promoting modularity and reuse.
  • +
  • Functional programming treats computation as the evaluation of mathematical functions, avoiding changing state and mutable data.
  • +
  • Declarative programming specifies what the program should accomplish rather than explicitly listing commands or steps to achieve it.
  • +
+

Can you provide code examples for MLOps with each paradigm?

+

Procedural programming

+

Procedural programming is a common method for structuring AI/ML code bases. This approach involves writing the entire program as a sequence of steps in a single script. This paradigm can be straightforward and easy to understand, making it appealing for simple projects. However, this simplicity might not be suitable for more complex real-world applications. Here is an illustrative example:

+
# Simplistic AI/ML Python Script Example
+
+import pandas as pd
+from sklearn.model_selection import train_test_split
+from sklearn.linear_model import LogisticRegression
+from sklearn.metrics import accuracy_score
+
+# Load and preprocess data
+data = pd.read_csv('dataset.csv')
+data.fillna(0, inplace=True)
+
+# Split data
+X, y = data.drop('target', axis=1), data['target']
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+# Train model
+model = LogisticRegression()
+model.fit(X_train, y_train)
+
+# Evaluate model
+predictions = model.predict(X_test)
+accuracy = accuracy_score(y_test, predictions)
+print(f"Model Accuracy: {accuracy}")
+
+

Functional Programming

+

Functional programming in AI/ML projects promotes modular and declarative coding styles by encapsulating code in functions. This method enhances code clarity and maintainability by allowing functions to be reused and tested separately. High-order functions, immutability, and pure functions are key features of this paradigm. An example using functional programming is as follows:

+
from typing import Callable, Tuple
+
+import pandas as pd
+from sklearn.model_selection import train_test_split
+from sklearn.base import BaseEstimator
+from sklearn.linear_model import LogisticRegression
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.metrics import accuracy_score
+
+def load_and_preprocess_data(filepath: str, fill_na_value: float, target_name: str) -> Tuple[pd.DataFrame, pd.Series]:
+    """Load and preprocess data."""
+    data = pd.read_csv(filepath)
+    data = data.fillna(fill_na_value)
+    X = data.drop(target_name, axis=1)
+    y = data[target_name]
+    return X, y
+
+def split_data(X: pd.DataFrame, y: pd.Series, test_size: float, random_state: int) -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series, pd.Series]:
+    """Split the data into a train and testing sets."""
+    return train_test_split(X, y, test_size=test_size, random_state=random_state)
+
+def train_model(X_train: pd.DataFrame, y_train: pd.Series, model_func: Callable[[], BaseEstimator], **kwargs) -> BaseEstimator:
+    """Train the model with inputs and target data."""
+    model = model_func(**kwargs)
+    model.fit(X_train, y_train)
+    return model
+
+def evaluate_model(model: BaseEstimator, X_test: pd.DataFrame, y_test: pd.Series) -> float:
+    """Evaluate the model with a single metric."""
+    predictions = model.predict(X_test)
+    accuracy = accuracy_score(y_test, predictions)
+    return accuracy
+
+def get_model(model_name: str) -> Callable[[], BaseEstimator]:
+    """High-order function to select the model to train."""
+    if model_name == "logistic_regression":
+        return LogisticRegression
+    elif model_name == "random_forest":
+        return RandomForestClassifier
+    else:
+        raise ValueError(f"Model {model_name} is not supported.")
+
+def run_workflow(model_name: str, model_kwargs: dict, filepath: str, fill_na_value: float, target_name: str, test_size: float, random_state: int) -> None:
+    """Orchestrate the training workflow."""
+    X, y = load_and_preprocess_data(filepath, fill_na_value, target_name)
+    X_train, X_test, y_train, y_test = split_data(X, y, test_size, random_state)
+    model_func = get_model(model_name)
+    model = train_model(X_train, y_train, model_func, **model_kwargs)
+    evaluate_model(model, X_test, y_test)
+
+# Example usage
+run_workflow(
+    filepath='dataset.csv',
+    fill_na_value=0.0,
+    target_name='target',
+    test_size=0.2,
+    random_state=42,
+    model_name='random_forest',  # Or 'logistic_regression'
+    model_kwargs={'n_estimators': 30},
+)
+
+

Object-oriented programming

+

Object-Oriented Programming (OOP) uses classes and objects to organize code, making it easier to manage large applications. It fully supports the encapsulation, inheritance, and polymorphism concepts, making it ideal for complex systems. Python's compatibility with OOP is evidenced by many frameworks and libraries adopting it. Below is an example of using OOP principles to manage models:

+
from abc import ABC, abstractmethod
+from typing import Tuple, Type
+
+import pandas as pd
+from sklearn.model_selection import train_test_split
+from sklearn.base import BaseEstimator
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.metrics import accuracy_score
+from tensorflow.keras.models import Sequential
+from tensorflow.keras.layers import Dense
+
+class Model(ABC):
+    """Abstract base class for models."""
+    @abstractmethod
+    def train(self, X_train: pd.DataFrame, y_train: pd.Series) -> None:
+        pass
+
+    @abstractmethod
+    def predict(self, X: pd.DataFrame) -> pd.Series:
+        pass
+
+class RandomForestModel(Model):
+    """Random Forest Classifier model."""
+    def __init__(self, n_estimators: int = 20, max_depth: int = 5) -> None:
+        self.model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth)
+
+    def train(self, X_train: pd.DataFrame, y_train: pd.Series) -> None:
+        self.model.fit(X_train, y_train)
+
+    def predict(self, X: pd.DataFrame) -> pd.Series:
+        return self.model.predict(X)
+
+class KerasBinaryClassifier(Model):
+    """Simple binary classification model using Keras."""
+    def __init__(self, input_dim: int, epochs: int = 100, batch_size: int = 32) -> None:
+        self.epochs = epochs
+        self.batch_size = batch_size
+        self.model = Sequential([
+            Dense(64, activation='relu', input_shape=(input_dim,)),
+            Dense(1, activation='sigmoid')
+        ])
+        self.model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
+
+    def train(self, X_train: pd.DataFrame, y_train: pd.Series) -> None:
+        self.model.fit(X_train, y_train, epochs=self.epochs, batch_size=self.batch_size)
+
+    def predict(self, X: pd.DataFrame) -> pd.Series:
+        predictions = self.model.predict(X)
+        return (predictions > 0.5).flatten()
+
+class ModelFactory:
+    """Factory to create model instances."""
+    @staticmethod
+    def get_model(model_name: str, **kwargs) -> Model:
+        # Assume all model classes are defined in the global scope.
+        model_class = globals()[model_name]
+        return model_class(**kwargs)
+
+class Workflow:
+    """Main workflow class for model training and evaluation."""
+    def run_workflow(self, model_name: str, model_kwargs: dict, filepath: str, fill_na_value: float, target_name: str, test_size: float, random_state: int) -> None:
+        X, y = self.load_and_preprocess_data(filepath, fill_na_value, target_name)
+        X_train, X_test, y_train, y_test = self.split_data(X, y, test_size, random_state)
+        model = ModelFactory.get_model(model_name, **model_kwargs)
+        model.train(X_train, y_train)
+        accuracy = self.evaluate_model(model, X_test, y_test)
+        print(f"Model Accuracy: {accuracy}")
+
+    def load_and_preprocess_data(self, filepath: str, fill_na_value: float, target_name: str) -> Tuple[pd.DataFrame, pd.Series]:
+        """Load and preprocess data."""
+        data = pd.read_csv(filepath)
+        data = data.fillna(fill_na_value)
+        X = data.drop(target_name, axis=1)
+        y = data[target_name]
+        return X, y
+
+    def split_data(self, X: pd.DataFrame, y: pd.Series, test_size: float, random_state: int) -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series, pd.Series]:
+        """Split the data into a train and testing sets."""
+        return train_test_split(X, y, test_size=test_size, random_state=random_state)
+
+    def evaluate_model(self, model: Model, X_test: pd.DataFrame, y_test: pd.Series) -> float:
+        """Evaluate the model with a single metric."""
+        predictions = model.predict(X_test)
+        accuracy = accuracy_score(y_test, predictions)
+        return accuracy
+
+# Example usage
+workflow = Workflow()
+workflow.run_workflow(
+    filepath='dataset.csv',
+    fill_na_value=0.0,
+    target_name='target',
+    test_size=0.2,
+    random_state="42",
+    model_name='RandomForestModel',  # Or 'KerasBinaryClassifier'
+    model_kwargs={'n_estimators': 30},
+)
+
+

Declarative programming

+

Declarative programming is a style where you specify what the program should accomplish without explicitly listing commands or steps to achieve it. This approach can greatly simplify coding in complex systems by separating the logic of what needs to be done from the implementation details. A common use case in machine learning is configuring models and training pipelines using high-level configuration files, which are then processed by machine learning frameworks that handle the underlying operations.

+

For example, using Ludwig, a tool for training machine learning models with declarative configurations, you can specify the architecture and training parameters of a model in YAML format. This allows you to train a model without writing the detailed procedural code typically required. Here's an example of how to train a model to classify images using a Convolutional Neural Network (CNN) defined in a

+
# config.yaml
+input_features:
+- name: image_path
+  type: image
+  encoder:
+      type: stacked_cnn
+      conv_layers:
+        - num_filters: 32
+          filter_size: 3
+          pool_size: 2
+          pool_stride: 2
+        - num_filters: 64
+          filter_size: 3
+          pool_size: 2
+          pool_stride: 2
+          dropout: 0.4
+      fc_layers:
+        - output_size: 128
+          dropout: 0.4
+
+output_features:
+ - name: label
+   type: category
+
+trainer:
+  epochs: 5
+
+

To train the model using this configuration, you would run the following command:

+
ludwig train --dataset mnist_dataset.csv --config config.yaml
+
+

Why do you need to use functions and objects?

+

Functions and objects play a crucial role in structuring code in a readable, maintainable, and reusable manner. They allow you to encapsulate functionality and state, making complex software systems more manageable.

+

Functions enable you to define a block of code that performs a single action, which can be executed whenever the function is called. This promotes code reuse and simplifies debugging and testing by isolating functionality.

+

Objects, fundamental to the object-oriented programming paradigm, bundle data and the methods that operate on that data. This encapsulation promotes modularity, as objects can be developed independently and used in different contexts.

+

How should you write a new function or object?

+

When identifying opportunities to encapsulate code into functions or objects, look for repetitive code patterns, complex logic that needs isolation, or concepts that can be modeled as real-world objects.

+

In the case of repetitive data loading tasks, abstracting the logic into a function simplifies the process:

+
def load_dataset(path: str, index_col: str = "Id") -> pd.DataFrame:
+    """Load a CSV dataset from a local path."""
+    dataset = pd.read_csv(path, index_col=index_col)
+    print(dataset.shape)
+    return dataset
+
+

When it comes to objects, encapsulate data and behavior that logically belong together. For instance, a DataPreprocessor class could encapsulate methods for cleaning, normalizing, and transforming data, keeping these operations neatly packaged and reusable.

+

How should you organize all your functions and objects?

+

Structuring functions and objects into modules helps maintain a clean and navigable codebase. This structure should evolve naturally, starting from a simple layout and growing in complexity as the project expands. For example:

+
    +
  • data.py for data loading, processing functions, and classes.
  • +
  • utils.py for utility functions and classes that support various parts of the project.
  • +
  • models.py for classes representing machine learning models and their logic.
  • +
+

This modular approach aids in separation of concerns, making your code more organized and manageable.

+

Which coding paradigm do you recommend for MLOps code bases?

+

When developing MLOps applications, selecting the appropriate coding paradigm is crucial for scalability and robustness. While Python supports various programming styles, it is particularly well-suited for object-oriented programming (OOP). This paradigm is advantageous for large applications, as it effectively handles polymorphism through both nominal and structural typing. A testament of Python's OOP capabilities can be shown in popular projects such as Pandas, scikit-learn, and Keras.

+

Although Python can utilize functional programming (FP) features, such as functions and higher-order functions, it does not fully embrace the functional paradigm. It lacks critical FP features including ad-hoc or parametric polymorphism, tail-call optimization, and efficient immutable data structures. These shortcomings make Python less ideal for pure functional programming compared to languages specifically designed for FP like Haskell or Clojure.

+

Given these considerations, we recommend favoring the object-oriented paradigm when building MLOps applications in Python. However, Python's flexibility allows for the integration of functional programming elements to enhance code quality and maintainability.

+

What is the hybrid style that mixes object-oriented and functional programming?

+

The hybrid programming style in Python blends the best features of object-oriented and functional programming to create robust and maintainable MLOps applications. This approach helps overcome some of Python's limitations in functional programming while leveraging its strong OOP capabilities. Here are key principles to follow when implementing a hybrid style:

+
    +
  1. Immutable Attributes: Design objects with immutable attributes to prevent changes to the object's state after initialization. This approach minimizes side effects and enhances predictability.
  2. +
  3. Output-Oriented Methods: Methods should primarily return outputs instead of modifying internal state. This practice encourages the separation of concerns and facilitates easier testing and debugging.
  4. +
  5. Idempotent Methods: Ensure methods are idempotent, meaning they consistently return the same result for the same inputs, a core principle in functional programming.
  6. +
  7. Centralized Imperative Statements: Use high-level classes to manage imperative operations (e.g., logging, database updates). This centralization helps delineate where side effects occur within the application, similar to how the IO monad operates in Haskell.
  8. +
  9. Leverage OOP Benefits: Continue to utilize object-oriented features such as subtyping polymorphism and intuitive class-based representations for better program extensibility and readability.
  10. +
+

By integrating these principles, developers can create a flexible architecture that supports extensibility and robustness, which are essential for effective MLOps applications. The hybrid style enables the combination of OOP's clarity and structural benefits with FP's emphasis on immutability and state management, resulting in cleaner, more manageable code.

+

Simplified MLOps application implemented with the Hybrid Style

+

What are the best practices for creating functions and objects?

+

Following best practices ensures that your functions and objects are reliable, maintainable, and easy to understand:

+
    +
  1. Type Hints: Specify input and output types to improve code readability and tooling support.
  2. +
  3. Docstrings: Describe the purpose and usage of functions and objects, including parameters and return values.
  4. +
  5. Single Responsibility: Aim for each function and object to perform a single task or represent a single concept.
  6. +
  7. Descriptive Names: Choose names that clearly convey the purpose and functionality.
  8. +
  9. Default Arguments: Use default arguments to provide flexibility, with caution around mutable defaults.
  10. +
  11. Error Handling: Incorporate error handling and validations to make your code robust.
  12. +
  13. Limit Parameters: Keep the number of parameters to a minimum for simplicity and ease of use.
  14. +
  15. Avoid Global Variables: Use local variables within functions and objects to avoid unintended side effects.
  16. +
  17. Testing: Regularly test your code to catch and fix errors early.
  18. +
  19. Readability: Prioritize clear and straightforward code, making it accessible to others (and your future self).
  20. +
+

Paradigm additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.3. Entrypoints.html b/3. Productionizing/3.3. Entrypoints.html new file mode 100644 index 0000000..bf668ea --- /dev/null +++ b/3. Productionizing/3.3. Entrypoints.html @@ -0,0 +1,2787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.3. Entrypoints - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.3. Entrypoints

+

What are package entrypoints?

+

Package entrypoints are mechanisms in Python packaging that facilitate the exposure of scripts and utilities to end users. Entrypoints streamline the process of integrating and utilizing the functionalities of a package, whether that be through Command-Line Interfaces (CLI) or by other software packages.

+

To elaborate, entrypoints are specified in a package's setup configuration, marking certain functions or classes to be directly accessible. This setup benefits both developers and users by simplifying access to a package's capabilities, improving interoperability among different software components, and enhancing the user experience by providing straightforward commands to execute tasks.

+

Why do you need to set up entrypoints?

+

Entrypoints are essential for making specific functionalities of your package directly accessible from the command-line interface (CLI) or to other software. By setting up entrypoints, you allow users and other programs to find and execute components of your package directly from the CLI, streamlining operations like script execution, service initiation, or utility invocation. Additionally, entrypoints facilitate dynamic discovery and utilization of your package's functionalities by other software and frameworks, such as Apache Airflow, without the need for hard-coded paths or module names. This flexibility is particularly beneficial in complex, interconnected systems where adaptability and ease of use are paramount.

+

How do you create a command-line script?

+

Creating a Python command-line script involves a few steps that turn a Python file into a tool that can be executed directly from the command line. Here’s how to do it:

+

Create a CLI parser

+

A CLI parser is a crucial component that handles command-line arguments, extracting values to configure your application. Python's argparse module is typically adequate for this purpose. The example below outlines how to set up a parser, defining the program's description and establishing various arguments and flags:

+
    +
  • files: This argument accepts one or more configuration files. The nargs="*" parameter allows multiple files to be specified.
  • +
  • --extras: This flag allows additional configuration strings to be passed. Like files, it can accept multiple entries.
  • +
  • --schema: A boolean flag that, when specified, will print the program's schema and exit the program.
  • +
  • --help: An implicit flag provided by argparse that displays a help message detailing the script’s arguments and flags.
  • +
+
import argparse
+
+parser = argparse.ArgumentParser(description="Run an AI/ML job from YAML/JSON configs.")
+parser.add_argument("files", nargs="*", help="Config files for the job (local path only).")
+parser.add_argument("-e", "--extras", nargs="*", default=[], help="Additional config strings for the job.")
+parser.add_argument("-s", "--schema", action="store_true", help="Print settings schema and exit.")
+
+

For alternative libraries, you might consider Typer, Click or Fire, which offer different styles and additional functionalities.

+

Create a main function

+

The main function is the core of your script, which interprets command-line inputs and acts upon them. It is conventional to have it accept a list of arguments and return an integer status code (0 for success, other values indicate errors):

+
def main(argv: list[str] | None = None) -> int:
+    args = parser.parse_args(argv)
+    if args.schema:
+        # Print the schema of the settings
+        print("Schema details here...")
+        return 0
+    # Execute the main application logic here
+    return 0
+
+

Expose the main function

+

To ensure the main function executes when the script is run (and not when imported as a module), use the following condition:

+
if __name__ == "__main__":
+    main()
+
+

You can then run your script from the command line like this:

+
# Running the script without using poetry
+$ python script.py config.yml
+# Running the script using poetry
+$ poetry run python script.py --schema
+
+

How do you create entrypoints with poetry?

+

Creating entrypoints with Poetry involves specifying them in the pyproject.toml file under the [tool.poetry.scripts] section. This section outlines the command-line scripts that your package will make available:

+
[tool.poetry.scripts]
+bikes = 'bikes.scripts:main'
+
+

In this syntax, bikes represents the command users will enter in the CLI to activate your tool. The path bikes.scripts:main directs Poetry to execute the main function found in the scripts module of the bikes package. Upon installation, Poetry generates an executable script for this command, integrating your package's functionality seamlessly into the user's command-line environment, alongside other common utilities:

+
$ poetry run bikes one two three
+
+

This snippet run the bikes entrypoint from the CLI and passes 3 positional arguments: one, two, and three.

+

How can you use an entrypoint in other software?

+

Defining and installing a package with entrypoints enables other software to easily leverage these entrypoints. For example, within Apache Airflow, you can incorporate a task in a Directed Acyclic Graph (DAG) to execute one of your CLI tools as part of an automated workflow. By utilizing Airflow's BashOperator or PythonOperator, your package’s CLI tool can be invoked directly, facilitating seamless integration:

+
from airflow import DAG
+from datetime import datetime, timedelta
+from airflow.providers.databricks.operators.databricks import DatabricksSubmitRunNowOperator
+
+# Define default arguments for your DAG
+default_args = {...}
+
+# Create a DAG instance
+with DAG(
+    'databricks_submit_run_example',
+    default_args=default_args,
+    description='An example DAG to submit a Databricks job',
+    schedule_interval='@daily',
+    catchup=False,
+) as dag:
+    # Define a task to submit a job to Databricks
+    submit_databricks_job = DatabricksSubmitRunNowOperator(
+        task_id='main',
+        json={
+            "python_wheel_task": {
+                "package_name": "bikes",
+                "entry_point": "bikes",
+                "parameters": [ "one", "two", "three" ],
+            },
+        }
+    )
+
+    # Set task dependencies and order (if you have multiple tasks)
+    # In this simple example, there's only one task
+    submit_databricks_job
+
+

In this example, submit_databricks_job is a task that executes the bikes entrypoint.

+

How can you use an entrypoint from the command-line (CLI)?

+

Once your Python package has been packaged with Poetry and a wheel file is generated, you can install and use the package directly from the command-line interface (CLI). Here are the steps to accomplish this:

+
    +
  1. Build your package: Use Poetry to compile your project into a distributable format, such as a wheel file. This is done with the poetry build command, which generates the package files in the dist/ directory.
  2. +
+
poetry build
+
+
    +
  1. Install your package: With the generated wheel file (*.whl), use pip to install your package into your Python environment. The pip install command looks for the wheel file in the dist/ directory, matching the pattern bikes*.whl, which is the package file created by Poetry.
  2. +
+
pip install dist/bikes*.whl
+
+
    +
  1. Run your package from the CLI: After installation, you can invoke the package's entrypoint—defined in your pyproject.toml file—directly from the command line. In this case, the bikes command followed by any necessary arguments. If your entrypoint is designed to accept arguments, they can be passed directly after the command. Ensure the arguments are separated by spaces unless specified otherwise in your documentation or help command.
  2. +
+
bikes one two three
+
+

Which should be the input or output of your entrypoint?

+

Inputs for your entrypoint can vary based on the requirements and functionalities of your package but typically include:

+
    +
  • Configuration files (e.g., JSON, YAML, TOML): These files can define essential settings, parameters, and options required for your tool or package to function. Configuration files are suited for static settings that remain constant across executions, such as environment settings or predefined operational parameters.
  • +
  • Command-line arguments (e.g., --verbose, --account): These arguments provide a dynamic way for users to specify options, flags, and parameters at runtime, offering adaptability for different operational scenarios.
  • +
+

Outputs from your entrypoint should be designed to provide valuable insights and effects, such as:

+
    +
  • Side effects: The primary purpose of your tool or package, which could include data processing, report generation, or initiating other software processes.
  • +
  • Logging: Detailed logs are crucial for debugging, monitoring, and understanding how your tool or package operates within larger systems or workflows.
  • +
+

Careful design of your entrypoints' inputs and outputs ensures your package can be integrated and used efficiently across a wide range of environments and applications, maximizing its utility and effectiveness.

+

Entrypoint additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.4. Configurations.html b/3. Productionizing/3.4. Configurations.html new file mode 100644 index 0000000..8518622 --- /dev/null +++ b/3. Productionizing/3.4. Configurations.html @@ -0,0 +1,2678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.4. Configurations - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.4. Configurations

+

What are configurations?

+

Software configurations consist of parameters or constants for the operation of your program, externalized to allow flexibility and adaptability. Configurations can be provided through various means, such as environment variables, configuration files, or command-line interface (CLI) arguments. For instance, a YAML configuration file might look like this:

+
job:
+  KIND: TrainingJob
+  inputs:
+    KIND: ParquetReader
+    path: data/inputs.parquet
+  targets:
+    KIND: ParquetReader
+    path: data/targets.parquet
+
+

This structure allows for easy adjustment of parameters like file paths or job kinds, facilitating the program's operation across diverse environments and use cases.

+

Why do you need to write configurations?

+

Configurations enhance your code's flexibility, making it adaptable to different environments and scenarios without source code modifications. This separation of code from its execution environment boosts portability and simplifies updates or changes, much like adjusting settings in an application without altering its core functionality.

+

Which file format should you use for configurations?

+

When choosing a format for configuration files, common options include JSON, TOML, and YAML. YAML is frequently preferred for its readability, ease of use, and ability to include comments, which can be particularly helpful for documentation and maintenance. However, it's essential to be aware of YAML's potential for loading malicious content; therefore, always opt for safe loading practices.

+

How should you pass configuration files to your program?

+

Passing configuration files to your program typically utilizes the CLI, offering a straightforward method to integrate configurations with additional command options or flags. For example, executing a command like:

+
$ bikes defaults.yaml training.yaml --verbose
+
+

This example enables the combination of configuration files with verbosity options for more detailed logging. This flexibility is also extendable to configurations stored on cloud services, provided your application supports such paths.

+

Which toolkit should you use to parse and load configurations?

+

For handling configurations in Python, OmegaConf offers a powerful solution with features like YAML loading, deep merging, variable interpolation, and read-only configurations. It's particularly suited for complex settings and hierarchical structures. Additionally, for applications involving cloud storage, cloudpathlib facilitates direct loading from services like AWS, GCP, and Azure.

+
import typing as T
+import omegaconf as oc
+
+Config = oc.ListConfig | oc.DictConfig
+
+def parse_file(path: str) -> Config:
+    """Parse a config file from a path."""
+    return oc.OmegaConf.load(path)
+
+def merge_configs(configs: T.Sequence[Config]) -> Config:
+    """Merge a list of config into a single config."""
+    return oc.OmegaConf.merge(*configs)
+
+args = parser.parse_args(argv)
+files = [configs.parse_file(file) for file in args.files]
+config = configs.merge_configs(files)
+
+

Utilizing Pydantic for configuration validation and default values ensures that your application behaves as expected by catching mismatches or errors in configuration files early in the process, thereby avoiding potential failures after long-running jobs. This is an improvement over Python's dicts as each key are validated and mentioned explicitly in your code base:

+
import pydantic as pdt
+
+class TrainTestSplitter(pdt.BaseModel):
+    """Split a dataframe into a train and test set.
+
+    Parameters:
+        shuffle (bool): shuffle the dataset. Default is False.
+        test_size (int | float): number/ratio for the test set.
+        random_state (int): random state for the splitter object.
+    """
+
+    shuffle: bool = False
+    test_size: int | float
+    random_state: int = 42
+
+

When should you use environment variables instead of configurations files?

+

Environment variables are more suitable for simple configurations or when dealing with sensitive information that shouldn't be stored in files, even though they lack the structure and type-safety of dedicated configuration files. They are universally supported and easily integrated but may become cumbersome for managing complex or numerous settings.

+
$ MLFLOW_TRACKING_URI=./mlruns bikes one two three
+
+

In this example, the MLFLOW_TRACKING_URI is passed as an environment variable to the bikes program, while the command also accepts 3 positional arguments: one, two, and three.

+

What are the best practices for writing and loading configurations?

+

To ensure effective configuration management:

+
    +
  • Always use yaml.safe_load() to prevent the execution of arbitrary code.
  • +
  • Utilize context managers for handling files to ensure proper opening and closing.
  • +
  • Implement robust error handling for I/O operations and parsing.
  • +
  • Validate configurations against a schema to confirm correctness.
  • +
  • Avoid storing sensitive information in plain text; instead, use secure mechanisms.
  • +
  • Provide defaults for optional parameters to enhance usability.
  • +
  • Document configurations with comments for clarity.
  • +
  • Maintain a consistent format across configuration files for better readability.
  • +
  • Consider versioning your configuration format to manage changes effectively in larger projects.
  • +
+

You can use the Configs section of your notebooks to initialize the configuration files for your Python package.

+

Configuration additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.5. Documentations.html b/3. Productionizing/3.5. Documentations.html new file mode 100644 index 0000000..c2cec2e --- /dev/null +++ b/3. Productionizing/3.5. Documentations.html @@ -0,0 +1,2676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.5. Documentations - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.5. Documentations

+

What are software documentations?

+

Software documentation encompasses written text or illustrations that support a software project. It can range from comprehensive API documentation to high-level overviews, guides, and tutorials. Effective documentation plays a crucial role in assisting users and contributors by explaining how to utilize and contribute to a project, ensuring the software is accessible and maintainable.

+

Why do you need to create documentations?

+

Documentation is pivotal for several reasons:

+
    +
  • Usability: It aids users in understanding how to interact with your software, detailing accepted inputs and expected outputs.
  • +
  • Maintainability: Documentation facilitates new contributors in navigating the codebase and contributing efficiently, such as guiding through the process of submitting a pull request.
  • +
  • Longevity: Projects with thorough documentation are more likely to be adopted, maintained, and enhanced over time, thanks to resources like troubleshooting sections.
  • +
  • Quality Assurance: The process of writing documentation can uncover design issues or bugs, much like explaining a problem aloud can lead to solutions (e.g., through a data glossary).
  • +
+

High-quality documentation encourages the use of your software and is valued by your users, while poor documentation can hinder developer productivity and deter users from engaging with your solution.

+

How should you associate documentations to your code base?

+

Documentation within Python code can be incorporated in three key places:

+
    +
  • Module documentation: This should be placed at the top of your Python module to provide an overview.
  • +
+
"""Define trainable machine learning models."""
+
+ +
def parse_file(path: str) -> Config:
+    """Parse a config file from a given path.
+
+    Args:
+        path (str): Path to the local config file.
+
+    Returns:
+        Config: Parsed representation of the config file.
+    """
+    return oc.OmegaConf.load(path)
+
+ +
class ParquetReader(Reader):
+    """Reads a dataframe from a parquet file.
+
+    Attributes:
+        path (str): The local path to the dataset.
+    """
+
+    path: str
+
+

Beyond in-code documentation, complementing it with external documentation (e.g., project organization guides or how to report a bug) is beneficial.

+

Which tool, format, and convention should you use to create documentations?

+

For creating documentation, you have multiple tools, formats, and conventions at your disposal:

+
    +
  • +

    Tools:

    +
      +
    • MkDocs: A fast, simple static site generator designed for project documentation, built with Python.
    • +
    • pdoc: A tool and library for auto-generating API documentation for Python projects, best for API docs.
    • +
    • Sphinx: A robust tool for creating detailed and beautiful documentation, popular within the Python community, albeit with a steeper setup curve.
    • +
    +
  • +
  • +

    Formats:

    +
      +
    • Markdown: A straightforward text format that converts to HTML, ideal for simpler docs.
    • +
    • reStructuredText (reST): Offers more features than Markdown, widely used in Python documentation, especially with Sphinx.
    • +
    +
  • +
  • +

    Convention:

    +
      +
    • Numpy Style: Features a clear, structured format for documenting Python functions, classes, and modules, focusing on readability.
    • +
    • Google Style: Known for its simplicity and ease of use in documenting Python code, emphasizing clarity and brevity.
    • +
    • reStructuredText: Offers a comprehensive set of markup syntax and constructs, ideal for technical documentation that requires detailed structuring and cross-referencing.
    • +
    +
  • +
+

For best practices, choose a tool and format that align with your project's needs and complexity. Adopting a widely recognized convention can facilitate consistency and comprehension across your documentation. Generating a simple API documentation can be as simple as calling a tool like pdoc with an input and output directory:

+
$ poetry run pdoc --docformat=google --output-directory=docs/ src/bikes
+
+

You can also use your IDE or some extensions like autoDocstring to automate the documentation generation process.

+

Is there some frameworks for organizing good documentation?

+

Diataxis is a framework that offers a systematic approach to crafting technical documentation, recognizing four distinct documentation needs: tutorials, how-to guides, technical references, and explanations. It suggests organizing documentation to align with these needs, ensuring users find the information they're looking for efficiently.

+

Diataxis quadrant

+

What are best practices for writing your project documentation?

+
    +
  1. Clarity and Conciseness: Strive for clear, straightforward documentation, avoiding complex language or unnecessary technical jargon.
  2. +
  3. Consistent Style: Maintain a uniform style and format throughout all documentation to enhance readability.
  4. +
  5. Keep It Updated: Regularly revise documentation to reflect the latest changes and additions to your software.
  6. +
  7. Use Examples: Provide practical examples to illustrate how different parts of your software operate.
  8. +
  9. Accessibility: Ensure your documentation is accessible to all users, including those with disabilities.
  10. +
  11. Feedback Loop: Encourage and incorporate feedback on your documentation to continuously improve its quality.
  12. +
  13. Multilingual Support: If possible, offer documentation in multiple languages to cater to a wider audience.
  14. +
  15. Searchable: Implement search functionality to allow users to quickly locate relevant information within your documentation.
  16. +
  17. Versioning: If your software has multiple versions, provide corresponding documentation for each version.
  18. +
+

Documentation additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/3.6. VS Code Workspace.html b/3. Productionizing/3.6. VS Code Workspace.html new file mode 100644 index 0000000..1b22112 --- /dev/null +++ b/3. Productionizing/3.6. VS Code Workspace.html @@ -0,0 +1,2626 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.6. VS Code Workspace - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

3.6. VS Code Workspace

+

What are VS Code Workspaces?

+

VS Code Workspaces are powerful configurations that allow you to manage and work on multiple projects within Visual Studio Code efficiently. A workspace can include one or more folders that are related to a particular project or task. Workspaces in VS Code enable you to save your project's settings, debug configurations, and extensions separately from your global VS Code settings, providing a customized and consistent development environment across your team.

+

Why should you use VS Code Workspace?

+

Using a VS Code Workspace offers several benefits:

+
    +
  • Shared Settings: Workspaces allow you to define common settings across all developers working on the project, ensuring consistency in coding conventions and reducing setup time for new contributors.
  • +
  • Productivity: They enable developers to quickly switch contexts between different projects, each with its own tailored settings and preferences, thus enhancing productivity and focus.
  • +
  • Customization: Workspaces provide the flexibility to customize the development environment according to the specific needs of a project without affecting global settings, making it easier to work on projects with different requirements.
  • +
+

How should you create and open your VS Code Workspace?

+

To create and open a VS Code Workspace:

+
    +
  1. Create a Workspace: Open VS Code and go to File > Save Workspace As... to save your current folder as a workspace. Provide a name for your workspace file, which will have a .code-workspace extension.
  2. +
  3. Add Folders: You can add multiple project folders to your workspace by going to File > Add Folder to Workspace... This is useful for grouping related projects.
  4. +
  5. Open a Workspace: To open an existing workspace, go to File > Open Workspace... and select the .code-workspace file you wish to open.
  6. +
+

Which configurations can you pass to your workspace configuration?

+

VS Code Workspace configurations support the same settings as user settings but apply only within the context of the workspace. These configurations can be specified in the .code-workspace file, allowing you to customize various aspects of your development environment. For example:

+
{
+    "folders": [
+        {
+            "path": "."
+        }
+    ],
+    "settings": {
+        "editor.formatOnSave": true,
+        "python.defaultInterpreterPath": ".venv/bin/python",
+        "python.testing.pytestEnabled": true,
+        "python.testing.pytestArgs": [
+            "tests"
+        ],
+        "[python]": {
+            "editor.codeActionsOnSave": {
+                "source.organizeImports": true
+            },
+            "editor.defaultFormatter": "charliermarsh.ruff",
+        }
+    },
+    "extensions": {
+        "recommendations": [
+            "charliermarsh.ruff",
+            "dchanco.vsc-invoke",
+            "ms-python.mypy-type-checker",
+            "ms-python.python",
+            "ms-python.vscode-pylance",
+            "redhat.vscode-yaml"
+        ]
+    }
+}
+
+

This configuration example sets up a project with:

+
    +
  • Folders: Defines the project folders included in the workspace.
  • +
  • Settings: Customizes editor behavior, such as format on save, default Python interpreter path, and testing configurations specific to Python.
  • +
  • Extensions: Recommends extensions beneficial for the project, promoting consistency across the development team.
  • +
+

By tailoring workspace settings, you ensure that every team member has a consistent development environment, which can include specific code formatting rules, linter settings, and extensions that are automatically recommended upon opening the workspace.

+

How can you configure your VS Code User Settings to become more productive?

+

This article from the MLOps Community: How to configure VS Code for AI, ML and MLOps development in Python 🛠️️ provide a in-depth guide for configuring VS Code environments. It starts by listing extensions that augments your programming environment. Then, the article shares some settings and keybindings that enhances your development experience. Finally, it provides some tips and tricks to boost your coding efficiency with VS Code.

+

VS Code Workspace additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/3. Productionizing/index.html b/3. Productionizing/index.html new file mode 100644 index 0000000..39e800c --- /dev/null +++ b/3. Productionizing/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3. Productionizing - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

3. Productionizing

+

In this chapter, we'll explore key strategies for improving your Python codebase, making it more maintainable, scalable, and efficient. We'll also cover the pivotal transition from working in notebooks to structuring your code as a Python package, a crucial step for enhancing code quality and collaboration. From organizing your project into packages and modules, understanding programming paradigms, and setting up entry points, to managing configurations, documenting your work, and optimizing your development environment with VS Code, you'll learn practical tips to refine your Python projects. Let's dive into refining your Python skills and elevating your projects.

+
    +
  • 3.0. Package: Learn how to organize and structure your Python codebase effectively for better modularity and maintainability.
  • +
  • 3.1. Modules: Dive into the organization of Python files into modules for cleaner, more scalable code.
  • +
  • 3.2. Paradigms: Explore various programming paradigms (OOP, functional programming) and their applications in putting Python in production.
  • +
  • 3.3. Entrypoints: Understand the significance of entry points in managing the execution flow of your Python applications.
  • +
  • 3.4. Configurations: Master the art of externalizing configurations to make your Python projects more flexible and environment-agnostic.
  • +
  • 3.5. Documentations: Emphasize the importance of thorough documentation to enhance code understandability and maintainability.
  • +
  • 3.6. VS Code Workspace: Optimize your development environment using Visual Studio Code for Python programming and better productivity.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.0. Typing.html b/4. Validating/4.0. Typing.html new file mode 100644 index 0000000..01c46e1 --- /dev/null +++ b/4. Validating/4.0. Typing.html @@ -0,0 +1,2766 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.0. Typing - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.0. Typing

+

What is programming typing?

+

Typing in programming involves designating specific data types for variables, functions, and classes within a programming language. This concept is critical for managing how data is stored, processed, and interacted within software applications.

+

Programming languages are categorized into three main types based on how they handle typing:

+
    +
  • Static typing: In statically typed languages, the data type of a variable is known at compile time, which means that type checking is done during the compilation of the program. Examples include Java, C++, and Haskell. This approach allows for early detection of type-related errors, contributing to more robust and error-resistant code.
  • +
  • Dynamic typing: Dynamically typed languages determine the type of a variable at runtime. This flexibility allows for more rapid development but can introduce type-related errors that are harder to detect early in the development process. Examples of dynamically typed languages are Ruby, JavaScript, PHP, and Python.
  • +
  • Gradual typing: Gradual typing offers a blend of static and dynamic typing, allowing developers to choose when to enforce type constraints. This approach provides the flexibility of dynamic typing while still enabling the benefits of static type checking where it's most useful. Languages that support gradual typing include TypeScript, Dart, and Python (from version 3.5 onwards with type annotations).
  • +
+

Additionally, languages can have either a weak or strong type system:

+
    +
  • Weak typing: In languages with weak typing, type coercion is common, allowing for more flexibility in operations between different types but at the risk of unexpected behavior or errors (e.g., 1 + "s" => "1s").
  • +
  • Strong typing: Strongly typed languages enforce stricter rules about interactions between data types, reducing the chances of runtime errors due to unexpected type conversions but requiring more explicit declarations and conversions by the developer (e.g., 1 + "s" => error, str(1) + "s" = "1s").
  • +
+

Why is typing useful in programs?

+

The role of typing in programming, especially in complex or large-scale projects, is invaluable for several reasons:

+
    +
  • Early Bug Detection: Typing helps in identifying potential type-related issues at the early stages of development, preventing bugs that could become costly and complex to resolve later.
  • +
  • Enhanced Code Clarity: Type annotations clarify the expected data types for function inputs and outputs, making the code more readable and understandable.
  • +
  • Improved Development Workflow: Adopting typing encourages a disciplined coding practice, resulting in fewer errors and enhanced code quality.
  • +
  • Facilitates Collaboration: In team settings, clear type annotations ensure that all members understand the data structures and function interfaces, leading to more effective collaboration.
  • +
  • Integration with IDEs: Advanced IDEs utilize type hints to offer superior code completion, error highlighting, and refactoring capabilities.
  • +
+

Although specifying types requires additional effort, this investment significantly improves the codebase's quality.

+

What is the relation between Python and typing?

+

Python is primarily recognized as a strong and dynamically typed language, allowing programmers to write code without specifying types explicitly. This approach is straightforward but may not be scalable for larger projects. Since Python 3.5, the language has supported gradual typing, enabling developers to annotate types. This feature enhances code clarity and aids in error prevention, especially during development.

+

For instance, a simple function without type annotations in Python might look like this:

+
def print_n_times(message, n):
+    for _ in range(n):
+        print(message)
+
+

However, for better clarity and to take advantage of gradual typing, the same function with type annotations would be:

+
def print_n_times(message: str, n: int) -> None:
+    for _ in range(n):
+        print(message)
+
+

Incorporating type annotations is highly recommended for the benefits they bring in terms of code clarity and early error detection, except in some cases where the effort might not justify the value.

+

It's important to note that Python types are checked during development time, meaning they're used to verify the program's logic and flow rather than affecting runtime performance or optimization.

+

To dive deeper into Python typing, exploring resources such as the Mypy cheatsheet and Python's built-in typing module is beneficial.

+

Is it possible to provide types for a dataframe?

+

It's possible to provide types for dataframes using the Pandera library. Pandera offers a flexible and expressive API for validating data in dataframe-like objects, enhancing the readability and robustness of data processing pipelines.

+

Pandera allows for:

+
    +
  1. Defining a schema once and validating different dataframe types, including pandas, dask, modin, and pyspark.pandas.
  2. +
  3. Checking the types and properties of columns in a pandas DataFrame or values in a pandas Series.
  4. +
  5. Performing complex statistical validations, such as hypothesis testing.
  6. +
  7. Integrating seamlessly with data analysis and processing pipelines through function decorators.
  8. +
  9. Using a class-based API for dataframe models, similar to pydantic, and validating dataframes with typing syntax.
  10. +
  11. Synthesizing data from schema objects for property-based testing.
  12. +
  13. Validating dataframes lazily to execute all validation rules before raising an error.
  14. +
  15. Integrating with a rich ecosystem of Python tools like pydantic, fastapi, and mypy.
  16. +
+

Here's an example schema for validating a dataframe in an MLOps codebase:

+
import pandera as pa
+import pandera.typing as papd
+import pandera.typing.common as padt
+
+class InputsSchema(pa.DataFrameModel):
+    """Schema for the project inputs."""
+
+    instant: papd.Index[padt.UInt32] = pa.Field(ge=0, check_name=True)
+    dteday: papd.Series[padt.DateTime] = pa.Field()
+    season: papd.Series[padt.UInt8] = pa.Field(isin=[1, 2, 3, 4])
+    yr: papd.Series[padt.UInt8] = pa.Field(ge=0, le=1)
+    mnth: papd.Series[padt.UInt8] = pa.Field(ge=1, le=12)
+    hr: papd.Series[padt.UInt8] = pa.Field(ge=0, le=23)
+    holiday: papd.Series[padt.Bool] = pa.Field()
+    weekday: papd.Series[padt.UInt8] = pa.Field(ge=0, le=6)
+    workingday: papd.Series[padt.Bool] = pa.Field()
+    weathersit: papd.Series[padt.UInt8] = pa.Field(ge=1, le=4)
+    temp: papd.Series[padt.Float16] = pa.Field(ge=0, le=1)
+    atemp: papd.Series[padt.Float16] = pa.Field(ge=0, le=1)
+    hum: papd.Series[padt.Float16] = pa.Field(ge=0, le=1)
+    windspeed: papd.Series[padt.Float16] = pa.Field(ge=0, le=1)
+    casual: papd.Series[padt.UInt32] = pa.Field(ge=0)
+    registered: papd.Series[padt.UInt32] = pa.Field(ge=0)
+
+

Is it possible to provide better types for classes?

+

Pydantic enhances the native class syntax by validating class attributes and providing a cleaner, more efficient syntax.

+

Features of Pydantic include:

+
    +
  • Validation and serialization powered by type hints, integrating seamlessly with IDEs and static analysis tools.
  • +
  • High performance due to core validation logic written in Rust.
  • +
  • Capability to emit JSON Schema for easy integration with other tools.
  • +
  • Support for both strict and lax modes for data validation.
  • +
  • Validation for many standard library types, including dataclasses and TypedDicts.
  • +
  • Extensive customization options for validators and serializers.
  • +
  • A rich ecosystem of integrations with popular libraries like FastAPI and SQLModel.
  • +
  • Reliability proven by widespread use across various industries and projects.
  • +
+

Example usage in an MLOps codebase:

+
import pydantic as pdt
+
+class GridCVSearcher(pdt.BaseModel):
+    """Grid searcher with cross-fold validation for better model performance metrics."""
+
+    n_jobs: int | None = None
+    refit: bool = True
+    verbose: int = 3
+    error_score: str | float = "raise"
+    return_train_score: bool = False
+
+

How can you check your types with Python?

+

Mypy is the primary tool for type checking in Python, providing command-line and IDE integration options.

+
poetry add -G checkers mypy
+poetry run mypy src/ tests/
+
+

Faster alternatives to mypy include:

+
    +
  • pyright: Static Type Checker for Python. MIT, Microsoft
  • +
  • pyre-check: Performant type-checking for python. MIT, Meta
  • +
  • pytype: A static type analyzer for Python code. Apache-2, Google
  • +
+

Compared to other alternatives, Mypy supports additional plugins as we are doing to see below.

+

How can you configure mypy to improve your validation workflow?

+

To enhance your validation workflow, you can configure mypy in your project's pyproject.toml. Before committing code, it's advisable to run mypy across your codebase to ensure type correctness. You can ignore the .mypy_cache/ folders generated by mypy by adding them to your .gitignore.

+

Example mypy configuration in pyproject.toml:

+
[tool.mypy]
+# improve error messages
+pretty = true
+# enhance strictness level
+strict = true
+# specify the python version
+python_version = "3.12"
+# check untyped definitions
+check_untyped_defs = true
+# all missing imports in code
+ignore_missing_imports = true
+# enable additional mypy plugins
+plugins = ["pandera.mypy", "pydantic.mypy"]
+
+

If you need to ignore mypy for entire file or single line, you can add the following comment:

+
def func(a: int, b: int) -> bool:  # type: ignore[empty-body]
+    pass
+
+

More configuration options are available in the mypy documentation.

+

What are the best practices for providing types in Python?

+
    +
  • Follow the 80-20 rule: Focus on annotating types where it brings the most benefit.
  • +
  • Familiarize yourself with the typing module: for further understanding Python types.
  • +
  • Use implicit typing judiciously, as not all variables require explicit annotations.
  • +
  • Employ typing.Any sparingly when specific types are not necessary or known.
  • +
  • Leverage tools like mypy for continuous type checking during development.
  • +
+

Typing additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.1. Linting.html b/4. Validating/4.1. Linting.html new file mode 100644 index 0000000..33b89e7 --- /dev/null +++ b/4. Validating/4.1. Linting.html @@ -0,0 +1,2616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.1. Linting - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.1. Linting

+

What is software linting?

+

Linting involves utilizing a tool to analyze your code for errors and discrepancies against standard coding conventions. Its primary aim is to identify syntax and stylistic issues, along with other potential programming errors that may have been accidentally included.

+

Why should you use linter tools?

+

Linter are indispensable in Python development due to several key reasons:

+
    +
  • Code Quality: They ensure adherence to best coding practices, crucial for teamwork.
  • +
  • Readability: By enforcing a consistent style, they enhance the readability of the code.
  • +
  • Early Bug Detection: They spot potential bugs early, reducing debugging and testing time.
  • +
  • Learning and Improvement: They are great for developers, particularly novices, to learn and adhere to best practices, thus refining their coding skills.
  • +
+

Which tools should you use to lint your code?

+

Ruff is a fast, modern linting tool that provides instant feedback, essential for efficient development workflows. The main benefit of ruff is its speed that outpaces many other linters, facilitating a quick fix cycle that doesn't hinder workflow. It also enforces a wide range of linting rules for code quality and consistency. There is also a VS Code Extension for ruff you can use to get linting message while you are typing you code.

+
# ruff installation (one shot)
+poetry add -G checkers ruff
+# ruff code base linting
+poetry run ruff src/ tests/
+
+

Remember to exclude the .ruff_cache/ directory in your .gitignore file to prevent the cache folder from being committed.

+

While there are other linting tools like pylint and pyflakes, ruff is highlighted for its speed and comprehensive rule set.

+

How should you configure your linting tools?

+

Configure your linting tools within the pyproject.toml file to tailor their checks to your codebase's specific requirements:

+
[tool.ruff]
+# automatic fix when possible
+fix = true
+# define the default indent width
+indent-width = 4
+# define the default line length
+line-length = 100
+# define the default python version
+target-version = "py312"
+
+[tool.ruff.lint.per-file-ignores]
+# exceptions for docstrings in tests
+"tests/*.py" = ["D100", "D103"]
+
+

If necessary, you can exclude linting rules either in your pyproject.toml file or inline if they are not relevant for your project:

+
# ignore the error code F401 for the line below
+from abc.xyz import function_name  # noqa: F401
+
+

What are the best practices for linting code?

+
    +
  1. Integrate with Development Workflow: Make linting a staple of your development routine. Set up your IDE or code editor for immediate feedback.
  2. +
  3. Customize Rules as Needed: Tailor the linting rules to meet your project's unique needs, although default settings are a solid starting point.
  4. +
  5. Regular Linting Sessions: Promote frequent linting to prevent the accumulation of issues.
  6. +
  7. Code Reviews and Linting: Use linting reports during code reviews to ensure compliance with coding standards.
  8. +
  9. Educate Team Members: Ensure everyone understands the value of linting and knows how to use the tools efficiently.
  10. +
  11. Balance Between Strictness and Flexibility: Enforce rules on error-prone patterns strictly but allow flexibility for personal coding styles, provided they do not compromise code quality.
  12. +
+

Linting additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.2. Testing.html b/4. Validating/4.2. Testing.html new file mode 100644 index 0000000..d74c66f --- /dev/null +++ b/4. Validating/4.2. Testing.html @@ -0,0 +1,2825 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.2. Testing - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.2. Testing

+

What are software tests?

+

Software tests are a set of automated procedures used to ensure that software behaves as intended. They play a critical role in maintaining software reliability, functionality, and in preventing regressions.

+

Tests can be categorized based on their complexity and scope:

+
    +
  • Unit Test: Focuses on individual components or functions, verifying that each part works in isolation. For example, testing a single function that calculates the sum of two numbers.
  • +
  • Regression Test: Ensures that previously developed and tested software still performs correctly after it has been changed or interfaced with other software. This type of test is crucial for identifying unintended side effects of updates.
  • +
  • End-to-End Test: Simulates real user scenarios from start to finish, ensuring the system as a whole operates as expected. It's the most comprehensive form of testing, covering the interaction between various parts of the software and external systems.
  • +
+

Why are tests important in Python projects?

+

Testing is indispensable in Python development for various reasons:

+
    +
  • Quality Assurance: Confirms that the software fulfills its intended requirements and functions correctly.
  • +
  • Regression Prevention: Aids in preventing regressions, where updates unintentionally alter or break existing features.
  • +
  • Refactoring Confidence: Empowers developers to refactor and enhance code with the assurance that they won't unknowingly disrupt existing functionality.
  • +
  • Serve Documentation: Acts as practical documentation that clarifies how the code is meant to operate.
  • +
+

While developers often use print statements for debugging, testing offers more durable assurances. Once a behavior is validated through tests, it can be continuously verified at each code change, unlike print statements which require manual developer intervention. Testing is especially critical in dynamic languages like Python, where compilers cannot validate the program before execution.

+

Which tool should you use to run Python tests?

+

Although Python includes the unittest module, it tends to be verbose. We suggest using pytest, a contemporary framework that simplifies writing small, clear tests, and scales well for complex application and library testing:

+
# content of tests/test_sample.py
+def inc(x):
+    return x + 1
+
+def test_answer():
+    assert inc(3) == 4 # assert the function matches the intended behavior
+
+

To run pytest on your code base for behavior validation:

+
# pytest installation (one-time)
+poetry add pytest
+# pytest execution
+poetry run pytest tests/
+
+

You can enhance pytest with additional plugins:

+
    +
  • pytest-cov: This plugin generates coverage reports, helping identify untested parts of the codebase:
  • +
+
# generate a coverage report
+pytest --cov=src/ tests/
+
+
    +
  • pytest-xdist: Enables parallel test execution, utilizing all available CPU cores to speed up the testing process:
  • +
+
# run pytest using all computer cores
+pytest -n auto tests/
+
+

How should you configure your code base for testing?

+

To prevent committing pytest cache files to git, add .pytest_cache to your .gitignore file.

+

VS Code natively supports pytest. Activate this feature in your *.code-workspace file with the following settings:

+
{
+    "settings": {
+        "python.testing.pytestEnabled": true,
+        "python.testing.pytestArgs": [
+            "tests"
+        ]
+    }
+}
+
+

Adjust pytest configuration globally in your pyproject.toml file:

+
[tool.coverage.run]
+branch = true  # report coverage by branch (if)
+source = ["src"]  # set the default source folder
+omit = ["__main__.py"]  # exclude certain files from coverage report
+
+[tool.pytest.ini_options]
+addopts = "--verbosity=2"  # increase the verbosity level
+pythonpath = ["src"]  # set the default python path
+
+

How should you structure your test structures?

+

Organize your tests in a dedicated tests folder, mirroring the structure of your project. For each module in your project, create a corresponding test file in the tests folder, naming it after the module but prefixed with test_. For instance, tests for a module named models.py should be in a file named test_models.py.

+
src/
+    bikes/
+        models.py
+        metrics.py
+        datasets.py
+tests/
+    test_models.py
+    test_metrics.py
+    test_datasets.py
+
+

Begin each test function with test_ to clearly indicate its purpose. This organization helps in maintaining clarity and ease of navigation within the test suite. You can also separate each test case in 3 steps: Given, When, Then:

+
def test_inputs_schema(inputs_reader: datasets.Reader) -> None:
+    # given
+    schema = schemas.InputsSchema
+    # when
+    data = inputs_reader.read()
+    # then
+    assert schema.check(data) is not None, "Inputs data should be valid!"
+
+

How can you define reusable test components?

+

pytest introduces the concept of fixtures, powerful tools for setting up objects that can be reused across multiple tests. You can define fixtures either in the module where they're used or in a tests/conftest.py file to share them throughout your tests. This is particularly useful in MLOps code bases, where you might not want to retrain models or reload datasets before each test. Here's how you can define and use fixtures:

+
# in conftest.py
+import pytest
+import os
+
+@pytest.fixture(scope="session")
+def tests_path() -> str:
+    """Return the path of the tests folder."""
+    file_path = os.path.abspath(__file__)
+    parent_directory = os.path.dirname(file_path)
+    return parent_directory
+
+@pytest.fixture(scope="function")
+def tmp_outputs_path(tmp_path: str) -> str:
+    """Return a tmp path for the outputs dataset."""
+    return os.path.join(tmp_path, "outputs.parquet")
+
+

These fixtures, especially when utilized in a shared conftest.py, facilitate setting up a common testing environment across the code base, ensuring efficiency and consistency. Fixtures with a session scope will be used across all executions, while function scope will be refreshed before each test.

+

How can you avoid repetition in your test scenarios?

+

To minimize repetition in tests and cover a variety of scenarios, pytest.mark.parametrize allows you to run the same test function with different sets of arguments. This approach is akin to calling the function multiple times with various inputs:

+
import pytest
+
+@pytest.mark.parametrize( "name, interval, greater_is_better",
+    [
+        ("mean_squared_error", [0, float("inf")], False),
+        ("mean_absolute_error", [0, float("inf")], False),
+    ], )
+def test_sklearn_metric( name: str,
+    interval: list,
+    greater_is_better: bool ) -> None:
+    # Example test body
+    assert name in ["mean_squared_error", "mean_absolute_error"]
+    assert isinstance(interval, list)
+    assert isinstance(greater_is_better, bool)
+
+

In this modified example, test_sklearn_metric will execute twice, verifying the function behaves correctly across both scenarios. This demonstrates how pytest.mark.parametrize effectively broadens test coverage with minimal code duplication.

+

How can you validate the output and exceptions of your program?

+

pytest offers several out-of-the-box fixtures for thorough testing, including output capture and exception testing. Here's how you can validate program output and handle exceptions:

+
import json
+import pytest
+
+def test_json_print(capsys) -> None:
+    # Example setup (replace with actual command execution)
+    print(json.dumps({"key": "value"}))  # Simulated program output
+    captured = capsys.readouterr()
+    # Validate
+    assert captured.err == "", "Captured error should be empty!"
+    assert json.loads(captured.out), "Captured output should be a valid JSON!"
+
+def test_main_no_configs() -> None:
+    # given
+    argv = []
+    # when
+    with pytest.raises(RuntimeError) as error:
+        # Replace with actual function call that should raise an error
+        raise RuntimeError("No configs provided.")
+    # then
+    assert "No configs provided." in str(error.value), "Expected RuntimeError was not raised!"
+
+

These examples illustrate capturing program output to verify it's a valid JSON and ensuring the program raises the expected exception under certain conditions, enhancing the robustness of your testing strategy.

+

Is it easy to define tests for MLOps code bases?

+

Testing MLOps code bases poses unique challenges compared to other types of code. You must deal with complex data structures, such as dataframes, and the inherent randomness in machine learning models. Furthermore, ML processes like model tuning can be time-consuming, requiring careful design of your tests to facilitate quicker iterations.

+

Gaining proficiency in writing effective tests for ML code bases comes with experience. As you become more familiar with the nuances of your code and ML workflows, you'll develop strategies for efficient and meaningful testing. Ensuring your ML components are not the weakest link in your software applications is crucial, and a solid testing foundation will bolster confidence in your development process.

+

What are best practices for writing unit tests?

+
    +
  1. Write Readable and Clear Tests: Ensure your tests are straightforward and easy to understand, with descriptive names and necessary comments.
  2. +
  3. Keep Tests Independent: Each test should run independently of others, allowing any order of execution.
  4. +
  5. Use Fixtures for Setup and Teardown: Utilize fixtures for consistent setup and cleanup, reducing redundancy across tests.
  6. +
  7. Regularly Run Your Tests: Integrate testing into your development and CI/CD workflows to catch issues early.
  8. +
  9. Aim for High Test Coverage: Cover as much of your code as possible, especially critical paths, to ensure reliability.
  10. +
  11. Keep Tests Fast: Optimize test speed to maintain efficiency in your development cycle.
  12. +
  13. Review and Update Tests Regularly: As your codebase evolves, ensure your tests remain relevant and reflective of current functionalities.
  14. +
  15. Test for Different Scenarios and Edge Cases: Beyond happy paths, test for potential failure modes and edge conditions to ensure robustness.
  16. +
  17. Strive for at least 80% Code Coverage: This target helps ensure that the majority of your codebase is verified by tests, safeguarding against regressions and encourage a culture of quality.
  18. +
+

Testing additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.3. Logging.html b/4. Validating/4.3. Logging.html new file mode 100644 index 0000000..e6119ae --- /dev/null +++ b/4. Validating/4.3. Logging.html @@ -0,0 +1,2693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3. Logging - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.3. Logging

+

What is software Logging?

+

Logging is a crucial process in software development, involving the recording of events, operations, and errors that occur within a software application. This process is instrumental in understanding how a program operates and in identifying any problems that may arise. Logging can be accomplished using dedicated logging modules provided by programming languages or through simple mechanisms like print statements.

+

Every program has access to two primary channels for communication:

+
    +
  • Standard Output (stdout): This is used for general program output, such as results of operations or status messages. In Python, this can be accessed via sys.stdout.
  • +
  • Standard Error (stderr): This channel is reserved for reporting errors and diagnostic messages. It ensures that error messages are kept separate from the main program output, allowing for easier debugging. In Python, sys.stderr is used for this purpose.
  • +
+

To facilitate logging, developers can choose from various logging levels, allowing them to categorize messages by their severity or importance. These levels include:

+
    +
  • DEBUG: Detailed information, typically of interest only when diagnosing problems.
  • +
  • INFO: Confirmation that things are working as expected.
  • +
  • WARNING: An indication that something unexpected happened, or indicative of some problem in the near future (e.g., ‘disk space low’). The software is still working as expected.
  • +
  • ERROR: Due to a more serious problem, the software has not been able to perform some function.
  • +
  • CRITICAL: A serious error, indicating that the program itself may be unable to continue running.
  • +
+

Why is Logging Important in Python Projects?

+

Logging plays a vital role in both the development and production phases of software projects for several reasons:

+
    +
  • Debugging: It facilitates the identification and resolution of issues in the code.
  • +
  • Monitoring: Logging allows for the ongoing observation of application behavior and performance, crucial for maintaining operational stability.
  • +
  • Audit Trails: Logs serve as a historical record of events, which can be critical for tracing the sequence of actions leading up to a problem.
  • +
  • Documentation: They act as a live form of documentation that details the runtime behavior of an application.
  • +
+

Given the myriad of things that can go wrong during program execution – such as missing files, full disk space, or network connectivity issues – having detailed logs can make the difference between quickly resolving an issue and a prolonged downtime.

+

Which tool should you use to log in Python?

+

Python's standard library includes a robust but somewhat complex logging module. For those seeking simplicity without sacrificing power, the loguru library is an excellent alternative. It streamlines the logging process with an easier syntax and a host of convenient features, such as automatic file rotation and structured logging. Below is how you can install and use loguru:

+
from loguru import logger
+
+# logging example for the inference job
+def run(self)
+   # services
+   logger = self.logger_service.logger()
+   logger.info("With logger: {}", logger)
+   # inputs
+   logger.info("Read inputs: {}", self.inputs)
+   inputs_ = self.inputs.read()  # unchecked!
+   inputs = schemas.InputsSchema.check(inputs_)
+   logger.debug("- Inputs shape: {}", inputs.shape)
+   # model
+   logger.info("With model: {}", self.mlflow_service.registry_name)
+   model_uri = registries.uri_for_model_alias(
+      name=self.mlflow_service.registry_name, alias=self.alias
+   )
+   logger.debug("- Model URI: {}", model_uri)
+   # loader
+   logger.info("Load model: {}", self.loader)
+   model = self.loader.load(uri=model_uri)
+   logger.debug("- Model: {}", model)
+   # outputs
+   logger.info("Predict outputs: {}", len(inputs))
+   outputs = model.predict(inputs=inputs)  # checked
+   logger.debug("- Outputs shape: {}", outputs.shape)
+   # write
+   logger.info("Write outputs: {}", self.outputs)
+   self.outputs.write(outputs)
+   return locals()
+
+

The above example demonstrates the simplicity of using loguru for logging across various severity levels, from debugging information to error reporting, directly to standard error (stderr) by default.

+

Execution traces

+

How can you configure loguru?

+

Configuring loguru in your Python applications is straightforward. You can adjust settings either directly in your code or through a configuration file. Here's an example of how loguru can be configured in a Python project:

+
import pydantic as pdt
+
+class LoggerService(pdt.BaseModel):
+    """Service for logging messages.
+
+    https://loguru.readthedocs.io/en/stable/api/logger.html
+
+    Parameters:
+        sink (str): logging output.
+        level (str): logging level.
+        format (str): logging format.
+        colorize (bool): colorize output.
+        serialize (bool): convert to JSON.
+        backtrace (bool): enable exception trace.
+        diagnose (bool): enable variable display.
+        catch (bool): catch errors during log handling.
+    """
+
+    sink: str = "stderr"
+    level: str = "DEBUG"
+    format: str = (
+        "<green>[{time:YYYY-MM-DD HH:mm:ss.SSS}]</green>"
+        "<level>[{level}]</level>"
+        "<cyan>[{name}:{function}:{line}]</cyan>"
+        " <level>{message}</level>"
+    )
+    colorize: bool = True
+    serialize: bool = False
+    backtrace: bool = True
+    diagnose: bool = False
+    catch: bool = True
+
+    def start(self) -> None:
+        loguru.logger.remove()
+        config = self.model_dump()
+        # use standard sinks or keep the original
+        sinks = {"stderr": sys.stderr, "stdout": sys.stdout}
+        config["sink"] = sinks.get(config["sink"], config["sink"])
+        loguru.logger.add(**config)
+
+    def logger(self) -> loguru.Logger:
+        """Return the main logger.
+
+        Returns:
+            loguru.Logger: the main logger.
+        """
+        return loguru.logger
+
+

This example outlines a configurable logging service, illustrating how loguru can be tailored to meet the specific needs of an application, from adjusting the logging level to formatting messages.

+

What are best practices for logging in Python?

+
    +
  1. Use Appropriate Logging Levels: Choose the right level for each log statement to ensure the log output is relevant and useful.
  2. +
  3. Provide Contextual Information: Enhance log messages with context to make them more informative and easier to understand.
  4. +
  5. Protect Sensitive Information: Avoid logging sensitive data to safeguard privacy and security.
  6. +
  7. Maintain Consistent Formatting: Standardize log message formats to simplify parsing and analysis.
  8. +
  9. Control Log File Size: Implement strategies like log rotation to manage file sizes and prevent excessive disk space usage.
  10. +
  11. Embrace Asynchronous Logging: For performance-sensitive applications, consider asynchronous logging to minimize impact on application throughput.
  12. +
  13. Centralize Logs for Distributed Systems: Aggregate logs from multiple sources to facilitate comprehensive monitoring and analysis.
  14. +
  15. Utilize Structured Logging: Employ structured formats (e.g., JSON) for complex applications to make logs more queryable and machine-readable.
  16. +
  17. Regularly Review Logging Configuration: Ensure your logging setup remains effective and adjust as necessary to capture critical information.
  18. +
  19. Educate Your Team: Promote awareness and understanding of logging best practices among all team members to enhance the quality and consistency of application logs.
  20. +
+

Implementing these practices will help maximize the benefits of logging in your Python projects, providing clear insights into application behavior and aiding in rapid problem resolution.

+

Logging additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.4. Security.html b/4. Validating/4.4. Security.html new file mode 100644 index 0000000..5b6e11f --- /dev/null +++ b/4. Validating/4.4. Security.html @@ -0,0 +1,2606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.4. Security - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.4. Security

+

What is software security?

+

Software security involves the implementation of protective measures and protocols to safeguard software from malicious attacks and other threats. It encompasses a range of practices designed to keep data safe, protect the integrity of software, and ensure that applications operate as intended without unauthorized access or modifications. Effective software security measures are critical in preventing data breaches, protecting user privacy, and maintaining the trustworthiness of software systems.

+

Why is software security important?

+

Software security is crucial for several reasons:

+
    +
  • Protects Sensitive Data: Security measures help protect sensitive information from unauthorized access and theft, including personal user data, financial information, and intellectual property.
  • +
  • Maintains User Trust: By ensuring the confidentiality, integrity, and availability of data, software security maintains and builds trust with users.
  • +
  • Compliance with Regulations: Many industries have regulatory requirements for data protection and privacy, making security a legal obligation.
  • +
  • Prevents Financial Loss: Security breaches can result in significant financial losses due to theft, legal liabilities, and damage to reputation.
  • +
  • Ensures Continuity of Services: Robust security measures help ensure that software applications remain available and operational, preventing disruptions to business operations and services.
  • +
+

What are the main security risks in Python and MLOps?

+
    +
  1. +

    Vulnerable Dependencies: One of the primary security risks in Python projects arises from using libraries and dependencies that contain known vulnerabilities. Regularly updating these dependencies to their latest, secure versions is essential to mitigate this risk.

    +
  2. +
  3. +

    Input Validation: Proper input validation is critical to protect against various forms of attacks, such as SQL injection, cross-site scripting (XSS), and command injection. Ensuring that inputs are validated, sanitized, and securely handled can significantly reduce security risks.

    +
  4. +
  5. +

    Configuration and Secrets Management: Inadequately managed application configurations and secrets, such as API keys and database passwords, can expose sensitive information. Secure storage solutions, like environment variables, secret management services, and encrypted configurations, are vital to safeguard this information.

    +
  6. +
+

Despite these risks, the isolation common in MLOps projects provides some level of protection, especially from direct internet-based threats like denial of service (DoS) attacks or the exploitation of vulnerabilities. This isolation primarily benefits backend operations, though online inference systems accessible via the web still require rigorous security measures.

+

How can security risks be reduced in Python?

+

To mitigate security risks in Python, tools like Bandit offer automated solutions for identifying common security issues within codebases:

+
# Bandit installation
+poetry add -G checkers bandit
+# Running Bandit to lint the codebase
+poetry run bandit src/
+
+

Configuring Bandit through pyproject.toml allows for customization of the linting process, tailoring it to specific project needs:

+
[tool.bandit]
+targets = ["src"]
+
+

For detailed configuration options and best practices, the Bandit documentation is an invaluable resource.

+

How can security risks be reduced with GitHub?

+

Reducing security risks on GitHub involves leveraging its tools for dependency management and vulnerability scanning. GitHub's Dependabot is an automated tool that monitors your repositories for known vulnerabilities in dependencies and provides updates or patches. Configuring Dependabot and regular code audits can significantly enhance your project's security posture.

+

To set up Dependabot in your GitHub repository, you can add a .github/dependabot.yml file with configurations tailored to your project's specific needs, specifying the frequency of checks, the directories to monitor, and the package managers to use.

+

By staying vigilant with updates, practicing secure coding standards, and utilizing available tools, developers can significantly reduce the security risks associated with Python and MLOps projects.

+

Security additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.5. Formatting.html b/4. Validating/4.5. Formatting.html new file mode 100644 index 0000000..1fdc3c4 --- /dev/null +++ b/4. Validating/4.5. Formatting.html @@ -0,0 +1,2681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5. Formatting - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.5. Formatting

+

What is software formatting?

+

Software formatting refers to the process of organizing and structuring code in a way that makes it readable and maintainable. It involves adhering to a set of stylistic rules and conventions that cover aspects such as indentation, line length, variable naming, and the placement of braces and comments. Good formatting improves the clarity of the code, making it easier for developers to understand, navigate, and modify.

+

Why is software formatting important?

+
    +
  1. Enhances Readability: Proper formatting makes code easier to read and understand. Consistent indentation and spacing help delineate code blocks, functions, and logical sections at a glance.
  2. +
  3. Facilitates Collaboration: When working in a team, standardized formatting ensures that everyone can easily work with and understand each other's code, reducing the learning curve for new team members.
  4. +
  5. Improves Maintainability: Well-formatted code is easier to debug, update, and extend. It helps developers identify structural and syntactic errors more quickly, and adhering to common conventions makes the code more predictable.
  6. +
+

Since the inception of programming, there have been several debates, such as tabs vs. spaces or single quote vs. double quote. Having common conventions ensures we can standardize the Python codebase without rehashing these debates.

+

Is there a convention for formatting Python codebases?

+

The default convention for formatting Python codebases is PEP 8. It provides guidelines on various formatting issues such as naming conventions, indentation, line length, and comments. Following PEP 8 helps Python developers maintain a uniform coding style, enhancing code readability and maintainability.

+

Some conventions of PEP 8 might be adjusted to your environment (e.g., screen size). For instance, the default line length is 79, which may be a bit short for modern monitor widths. You can increase this setting to a more comfortable value, such as 100.

+

Which tools should you use to format a codebase in Python?

+

In recent years, black and isort have been the core tools for formatting Python codebases. Black specializes in code formatting, applying a consistent style to your Python code, while isort focuses on sorting and formatting import statements. However, Ruff offers a faster alternative that combines the benefits of both black and isort.

+

You can format your codebase as follows:

+
# ruff installation (one-time)
+poetry add -G checkers ruff
+# format the imports
+poetry run ruff check --select you --fix src/ tests/
+# format the sources
+poetry run ruff format src/ tests/
+
+

How can you automate the formatting of your codebase as you type?

+

Instead of formatting your codebase periodically from the command line, you can configure your code editor, such as VS Code, to automatically format code when you save a file.

+

Below is an example configuration for your [project].code-workspace file to automatically format files using VS Code's Ruff extension:

+
{
+    "settings": {
+        "editor.formatOnSave": true,
+        "[python]": {
+            "editor.codeActionsOnSave": {
+                "source.organizeImports": true
+            },
+            "editor.defaultFormatter": "charliermarsh.ruff",
+        },
+    },
+    "extensions": {
+        "recommendations": [
+            "charliermarsh.ruff",
+        ]
+    }
+}
+
+

Should you change the default formatting proposed by the tools?

+

We recommend keeping the default settings in most cases to avoid debates on how your codebase should be formatted, with few exceptions.

+

In your pyproject.toml, you can configure Ruff formatting, such as the line length and docstring convention, to suit your project:

+
[tool.ruff]
+fix = true
+indent-width = 4
+line-length = 100
+target-version = "py312"
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.ruff.lint.pydocstyle]
+convention = "google"
+
+

Can you disable change or suppress formatting in some code location?

+

Sometimes, you might want to change or disable formatting to improve the readability of your code, such as in unit tests. You can do this in two ways:

+
    +
  • Use soft instructions to force certain formatting, like adding a comma at the end of an enumeration to force one line per item:
  • +
+
items = {
+    "a": 1,
+    "b": 2,
+    "c": 3,
+}
+# With the last comma, this dict will not be formatted as a single line:
+# items = {"a": 1, "b": 2, "c": 3}
+
+
    +
  • Use explicit instructions not to format a block of code with comments:
  • +
+
# fmt: off
+not_formatted=3
+also_not_formatted=4
+# fmt: on
+
+

Formatting additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/4.6. Debugging.html b/4. Validating/4.6. Debugging.html new file mode 100644 index 0000000..f7daba7 --- /dev/null +++ b/4. Validating/4.6. Debugging.html @@ -0,0 +1,2624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.6. Debugging - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

4.6. Debugging

+

What is software debugging?

+

Software debugging is the process of identifying, analyzing, and fixing bugs or errors in a software program. It involves running the program in a controlled environment, where you can inspect variables, step through code, set breakpoints, and understand the program's execution flow to find where things are going wrong.

+

Why is software debugging important?

+

Debugging is crucial for several reasons:

+
    +
  1. Ensures Code Correctness: It helps ensure that the software behaves as expected and meets its requirements.
  2. +
  3. Improves Code Quality: Through debugging, developers can identify and correct underlying issues that might not be immediately apparent, leading to more robust and error-free code.
  4. +
  5. Saves Time and Resources: Debugging can be a more efficient way to diagnose and fix problems compared to manually scrutinizing code or relying on scattered print statements. It allows for a systematic examination of the program’s execution.
  6. +
+

Compared to print statements, debugging offers a quicker and more interactive alternative. You don't have to add print statements, execute your program, and then remove those statements. Instead, you can insert breakpoints and examine the code as much as you want without restarting the program.

+

What is the benefit of debugging over logging?

+

While logging provides valuable insights, especially in a production environment, debugging is a more dynamic tool during development to understand code behavior.

+

The two approaches are complementary:

+
    +
  • Logging offers initial insights about code execution, helping to pinpoint where issues might occur.
  • +
  • Debugging allows for an in-depth investigation of these issues through breakpoints and real-time inspection, without the need for code execution to complete.
  • +
+

Which tool should you use to debug your code?

+

Python includes pdb (Python Debugger), a powerful but command-line-based tool that can be challenging for those not comfortable with CLI environments.

+

For a more user-friendly experience, Integrated Development Environments (IDEs) like Visual Studio Code (VS Code) offer integrated debugging tools. These tools provide a graphical interface for debugging, making it easier to set breakpoints, step through code, and inspect variables.

+

How should you debug your code with VS Code?

+

To start debugging in VS Code, follow these steps:

+
    +
  1. Set a Breakpoint: Click to the left of the line number where you want the execution to pause. A red dot appears, indicating a breakpoint.
  2. +
  3. Launch the Debugger: Open the Run and Debug sidebar (Ctrl+Shift+D on Windows/Linux, Cmd+Shift+D on macOS) and start a debugging session by selecting the appropriate configuration for your project and clicking the green play button.
  4. +
  5. Control Execution: Use the debugging control panel to step over (execute the current line), step into (dive into functions called on the current line), or continue (resume execution until the next breakpoint or the end of the program).
  6. +
  7. Inspect Variables: Hover over variables in the editor to see their current values. The Debug Sidebar also provides a comprehensive view of variables in scope.
  8. +
+

Additionally, you can use the Debug Console to execute commands and modify variables on the fly, offering an interactive environment to test fixes or understand behavior without altering the source code directly.

+

What are some tips for improving your debugging experience?

+

To enhance your debugging experience, consider the following tips:

+
    +
  • Use Conditional Breakpoints: Instead of stopping at every iteration of a loop, you can set conditions for breakpoints to pause execution only when certain conditions are met, saving time.
  • +
  • Leverage Watch Expressions: Add expressions or variables to the watch list to monitor their values in real-time, helping to quickly identify when and how they change unexpectedly.
  • +
  • Understand the Call Stack: The call stack shows you the path execution took to get to the current point. Analyzing it can help identify how different parts of the code interact and where errors propagate from.
  • +
  • Experiment in the Debug Console: Use the debug console to test potential fixes or understand complex code behavior without needing to modify and rerun your program.
  • +
  • Familiarize Yourself with Debugger Features: Spend time learning the features and shortcuts provided by your IDE's debugger. Efficiency in debugging often comes from knowing how to quickly navigate and use available tools.
  • +
+

Mastering the debugger and incorporating these practices can significantly improve your efficiency in diagnosing and fixing issues, making the debugging process less daunting and more productive.

+

Debugging additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/4. Validating/index.html b/4. Validating/index.html new file mode 100644 index 0000000..90a9dfd --- /dev/null +++ b/4. Validating/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.0 Validating - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

4.0 Validating

+

In the fast-paced world of Machine Learning Operations (MLOps), ensuring the quality and integrity of code is crucial for the development of scalable, efficient, and reliable ML systems. This course chapter focuses on essential code validation techniques that bolster the robustness of ML pipelines, from typing to debugging, to elevate code quality and operational efficiency.

+
    +
  • 4.0. Typing: Learn the significance of typing in Python for early error detection and improved code reliability.
  • +
  • 4.1. Linting: Explore how linting tools help maintain a clean codebase by highlighting stylistic errors and potential bugs.
  • +
  • 4.2. Testing: Understand the crucial role of testing in verifying code functionality and ensuring ML models perform as expected.
  • +
  • 4.3. Logging: Discover the importance of logging for monitoring ML systems in production and aiding in troubleshooting.
  • +
  • 4.4. Security: Examine strategies for identifying and mitigating security vulnerabilities in your ML applications.
  • +
  • 4.5. Formatting: Grasp how consistent code formatting enhances readability and collaboration among team members.
  • +
  • 4.6. Debugging: Delve into debugging practices to efficiently identify and solve issues within your ML codebase.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..5d20b39 --- /dev/null +++ b/404.html @@ -0,0 +1,2325 @@ + + + + + + + + + + + + + + + + + + + + + + + MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.0. Design Patterns.html b/5. Refining/5.0. Design Patterns.html new file mode 100644 index 0000000..4a1c0ec --- /dev/null +++ b/5. Refining/5.0. Design Patterns.html @@ -0,0 +1,2821 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.0. Design Patterns - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.0. Design Patterns

+

What is a software design pattern?

+

Software design patterns are proven solutions to common problems encountered during software development. These patterns provide a template for how to solve a problem in a way that has been validated by other developers over time. The concept originated from architecture and was adapted to computer science to help developers design more efficient, maintainable, and reliable code. In essence, design patterns serve as blueprints for solving specific software design issues.

+

Why do you need software design patterns?

+
    +
  • Freedom of choice: In the realm of AI/ML, flexibility and adaptability are paramount. Design patterns enable solutions to remain versatile, allowing for the integration of various options and methodologies without locking into a single approach.
  • +
  • Code Robustness: Python's dynamic nature demands discipline from developers to ensure code robustness. Design patterns provide a structured approach to coding that enhances stability and reliability.
  • +
  • Developer productivity: Employing the right design patterns can significantly boost developer productivity. These patterns facilitate the exploration of diverse solutions, enabling developers to achieve more in less time and enhance the overall value of their projects.
  • +
+

While Python's flexibility is one of its strengths, it can also lead to challenges in maintaining code robustness and reliability. Design patterns help to mitigate these challenges by improving code quality and leveraging proven strategies to refine and enhance the codebase.

+

What are the top design patterns to know?

+

Design patterns are typically categorized into three types:

+

Strategy Pattern (Behavioral)

+

The Strategy pattern is crucial in MLOps for decoupling the objectives (what to do) from the methodologies (how to do it). For example, it allows for the interchange of different algorithms or frameworks (such as TensorFlow, XGBoost, or PyTorch) for model training without altering the underlying code structure. This pattern upholds the Open/Closed Principle, providing the flexibility needed to adapt to changing requirements, such as switching models or data sources based on runtime conditions.

+

Strategy Pattern for MLOps

+

Factory Pattern (Creational)

+

After establishing common interfaces, the Factory pattern plays a vital role in enabling runtime behavior modification of programs. It controls object creation, allowing for dynamic adjustments through external configurations. In MLOps, this translates to the ability to alter AI/ML pipeline settings without code modifications. Python's dynamic features, combined with utilities like Pydantic, facilitate the implementation of the Factory pattern by simplifying user input validation and object instantiation.

+

Factory Pattern for MLOps

+

Adapter Pattern (Structural)

+

The Adapter pattern is indispensable in MLOps due to the diversity of standards and interfaces in the field. It provides a means to integrate various external components, such as training and inference systems across different platforms (e.g., Databricks and Kubernetes), by bridging incompatible interfaces. This ensures seamless integration and the generalization of external components, allowing for smooth communication and operation between disparate systems.

+

Adapter pattern for MLOps

+

How can you define software interfaces with Python?

+

Python supports two primary methods for defining interfaces: Abstract Base Classes (ABC) and Protocols.

+

ABCs utilize Nominal Typing to establish clear class hierarchies and relationships, such as a RandomForestModel being a subtype of a Model. This approach makes the connection between classes explicit:

+
from abc import ABC, abstractmethod
+
+import pandas as pd
+
+class Model(ABC):
+ @abstractmethod
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        pass
+
+ @abstractmethod
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        pass
+
+class RandomForestModel(Model):
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        print("Fitting RandomForestModel...")
+
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        print("Predicting with RandomForestModel...")
+        return pd.DataFrame()
+
+class SVMModel(Model):
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        print("Fitting SVMModel...")
+
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        print("Predicting with SVMModel...")
+        return pd.DataFrame()
+
+

Conversely, Protocols adhere to the Structural Typing principle, embodying Python's duck typing philosophy where a class is considered compatible if it implements certain methods, regardless of its place in the class hierarchy. This means a RandomForestModel is recognized as a Model by merely implementing the expected behaviors.

+
from typing import Protocol, runtime_checkable
+import pandas as pd
+
+@runtime_checkable
+class Model(Protocol):
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        ...
+
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        ...
+
+class RandomForestModel:
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        print("Fitting RandomForestModel...")
+
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        print("Predicting with RandomForestModel...")
+        return pd.DataFrame()
+
+class SVMModel:
+    def fit(self, X: pd.DataFrame, y: pd.DataFrame) -> None:
+        print("Fitting SVMModel...")
+
+    def predict(self, X: pd.DataFrame) -> pd.DataFrame:
+        print("Predicting with SVMModel...")
+        return pd.DataFrame()
+
+

Choosing between ABCs and Protocols depends on your project's needs. ABCs offer a more explicit, structured approach suitable for applications, while Protocols offer flexibility and are more aligned with library development.

+

How can you better validate and instantiate your objects?

+

Pydantic is a valuable tool for defining, validating, and instantiating objects according to specified requirements. It utilizes type annotations to ensure inputs meet predefined criteria, significantly reducing the risk of errors in data-driven operations, such as in MLOps processes.

+

Validating Objects with Pydantic

+

Pydantic utilizes Python's type hints to validate data, ensuring that the objects you create adhere to your specifications from the get-go. This feature is particularly valuable in MLOps, where data integrity is crucial for the success of machine learning models. Here's how you can leverage Pydantic for object validation:

+
from typing import Optional
+from pydantic import BaseModel, Field
+
+class RandomForestClassifierModel(BaseModel):
+    n_estimators: int = Field(default=100, gt=0)
+    max_depth: Optional[int] = Field(default=None, gt=0, allow_none=True)
+    random_state: Optional[int] = Field(default=None, gt=0, allow_none=True)
+
+# Instantiate the model with validated parameters
+model = RandomForestClassifierModel(n_estimators=120, max_depth=5, random_state=42)
+
+

In this example, Pydantic ensures that n_estimators is greater than 0, max_depth is either greater than 0 or None, and similarly for random_state. This kind of validation is essential for maintaining the integrity of your model training processes.

+

Streamlining Object Instantiation with Discriminated Union

+

Pydantic's Discriminated Union feature further simplifies object instantiation, allowing you to dynamically select a class based on a specific attribute (e.g., KIND). This approach can serve as an efficient alternative to the traditional Factory pattern, reducing the need for boilerplate code:

+
from typing import Literal, Union
+from pydantic import BaseModel, Field
+
+class Model(BaseModel):
+    KIND: str
+
+class RandomForestModel(Model):
+    KIND: Literal["RandomForest"]
+    n_estimators: int = 100
+    max_depth: int = 5
+    random_state: int = 42
+
+class SVMModel(Model):
+    KIND: Literal["SVM"]
+    C: float = 1.0
+    kernel: str = "rbf"
+    degree: int = 3
+
+# Define a Union of model configurations
+ModelKind = Union[RandomForestModel, SVMModel]
+
+class Job(BaseModel):
+    model: ModelKind = Field(..., discriminator="KIND")
+
+# Initialize a job from configuration
+config = {
+    "model": {
+        "KIND": "RandomForest",
+        "n_estimators": 100,
+        "max_depth": 5,
+        "random_state": 42,
+    }
+}
+job = Job.model_validate(config)
+
+

This pattern not only makes the instantiation of objects based on dynamic input straightforward but also ensures that each instantiated object is immediately validated against its respective schema, further enhancing the robustness of your application.

+

Incorporating these practices into your MLOps projects can significantly improve the reliability and maintainability of your code, ensuring that your machine learning pipelines are both efficient and error-resistant.

+

Design pattern additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.1. Task Automation.html b/5. Refining/5.1. Task Automation.html new file mode 100644 index 0000000..0d42709 --- /dev/null +++ b/5. Refining/5.1. Task Automation.html @@ -0,0 +1,2701 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.1. Task Automation - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.1. Task Automation

+

What is task automation?

+

Task automation refers to the process of automating repetitive and manual command-line tasks using software tools. This enables tasks to be performed with minimal human intervention, increasing efficiency and accuracy. A common example of task automation in software development is the use of make, a utility that automates the execution of predefined tasks like configure, build, and install within a project repository. By executing a simple command:

+
make configure build install
+
+

developers can streamline the compilation and installation process of software projects, saving time and reducing the likelihood of errors.

+

Why do you need task automation?

+

Task automation is essential for several reasons:

+
    +
  • Don't repeat yourself: Automating tasks helps in avoiding the repetition of similar tasks, ensuring that you spend your time on tasks that require your unique skills and insights.
  • +
  • Share common actions: It enables teams to share a common set of tasks, ensuring consistency and reliability across different environments and among different team members.
  • +
  • Avoid typing mistakes: Automation reduces the chances of errors that can occur when manually typing commands or performing repetitive tasks, leading to more reliable outcomes.
  • +
+

Embracing task automation is a step towards improving efficiency for programmers. The initial effort in setting up automation pays off by saving time and reducing errors, making it a valuable practice in software development.

+

Which tools should you use to automate your tasks?

+

While Make is a ubiquitous and powerful tool for task automation, its syntax can be challenging due to its use of unique symbols (e.g., $*, $%, :=, ...) and strict formatting rules, such as the requirement for tabs instead of spaces. This complexity can make Make intimidating for newcomers.

+

For those seeking a more approachable alternative, PyInvoke offers a simpler, Python-based syntax for defining and running tasks. Here is an example showcasing how to build a Python package (wheel file) using PyInvoke:

+
"""Package tasks for pyinvoke."""
+from invoke.context import Context
+from invoke.tasks import task
+from . import cleans
+
+BUILD_FORMAT = "wheel"
+
+@task(pre=[cleans.dist])
+def build(ctx: Context, format: str = BUILD_FORMAT) -> None:
+    """Build a python package with the given format."""
+    ctx.run(f"poetry build --format={format}")
+
+@task(pre=[build], default=True)
+def all(_: Context) -> None:
+    """Run all package tasks."""
+
+

This example illustrates how tasks can be easily defined and automated using Python, making it accessible for those already familiar with the language. Developers can then execute the task from their terminal:

+
# execute the build task
+inv build
+
+

How can you configure your task automation system?

+

Configuring your task automation system with PyInvoke is straightforward. It can be installed as a Python dependency through:

+
poetry add -G dev invoke
+
+

Then, to configure PyInvoke for your project, create an invoke.yaml file in your repository:

+
run:
+  echo: true
+project:
+  name: bikes
+
+

This configuration file allows you to define general settings under run and project-specific variables under project. Detailed documentation and more configuration options can be found on PyInvoke's website.

+

How should you organize your tasks in your project folder?

+

For an MLOps project, it's advisable to organize tasks into categories and place them within a tasks/ directory at the root of your repository. This directory can include files for different task categories such as cleaning, commits, container management, and more. Here's an example structure:

+ +

In the tasks/__init__.py file, you should import and add all task modules to a collection:

+
"""Task collections for the project."""
+from invoke import Collection
+from . import checks, cleans, commits, containers, dags, docs, formats, installs, mlflow, packages
+
+ns = Collection()
+
+ns.add_collection(checks)
+ns.add_collection(cleans)
+ns.add_collection(commits)
+ns.add_collection(containers)
+ns.add_collection(dags, default=True)
+ns.add_collection(docs)
+ns.add_collection(formats)
+ns.add_collection(installs)
+ns.add_collection(mlflow)
+ns.add_collection(packages)
+
+

Each module, like checks, can define multiple tasks. For example:

+
"""Check tasks for pyinvoke."""
+from invoke.context import Context
+from invoke.tasks import task
+
+@task
+def poetry(ctx: Context) -> None:
+    """Check poetry config files."""
+    ctx.run("poetry check --lock")
+
+@task
+def format(ctx: Context) -> None:
+    """Check the formats with ruff."""
+    ctx.run("poetry run ruff format --check src/ tasks/ tests/")
+
+@task
+def type(ctx: Context) -> None:
+    """Check the types with mypy."""
+    ctx.run("poetry run mypy src/ tasks/ tests/")
+
+@task
+def code(ctx: Context) -> None:
+    """Check the codes with ruff."""
+    ctx.run("poetry run ruff check src/ tasks/ tests/")
+
+@task
+def test(ctx: Context) -> None:
+    """Check the tests with pytest."""
+    ctx.run("poetry run pytest --numprocesses='auto' tests/")
+
+@task
+def security(ctx: Context) -> None:
+    """Check the security with bandit."""
+    ctx.run("poetry run bandit --recursive --configfile=pyproject.toml src/")
+
+@task
+def coverage(ctx: Context) -> None:
+    """Check the coverage with coverage."""
+    ctx.run("poetry run pytest --numprocesses='auto' --cov=src/ --cov-fail-under=80 tests/")
+
+@task(pre=[poetry, format, type, code, security, coverage], default=True)
+def all(_: Context) -> None:
+    """Run all check tasks."""
+
+

These tasks can then be invoked from the command line as needed, providing a structured and efficient way to manage and execute project-related tasks.

+
# run the code checker
+inv checks.code
+# run the code and format checker
+inv checks.code checks.format
+# run all the check tasks in the module
+inv checks
+
+

Task automation additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.2. Pre-Commit Hooks.html b/5. Refining/5.2. Pre-Commit Hooks.html new file mode 100644 index 0000000..c1373f7 --- /dev/null +++ b/5. Refining/5.2. Pre-Commit Hooks.html @@ -0,0 +1,2714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2. Pre-Commit Hooks - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.2. Pre-Commit Hooks

+

What are pre-commit hooks?

+

Pre-commit hooks are automated checks that run before a commit or push is completed in your version control system. They serve as a first line of defense to ensure code quality and compliance with coding standards before the code is shared with the team or integrated into the main codebase. These hooks can run a variety of tasks, from syntax checks and code formatting to more complex operations like static analysis.

+

Why do you need pre-commit hooks?

+
    +
  • Share clean code: Pre-commit hooks help maintain a high code quality by enforcing coding standards and identifying issues early.
  • +
  • Fix common issues: By catching common issues before commit, pre-commit hooks save time and effort in debugging and fixing problems that could have been easily avoided.
  • +
  • Avoid failed CI/CD jobs: Running checks before committing reduces the likelihood of CI/CD jobs failing due to minor issues that could have been caught early.
  • +
+

Compared to CI/CD workflows, pre-commit hooks have the advantage of running locally on your computer, which makes them faster and easier to debug. You should consider balancing your validation jobs between pre-commit hooks and CI/CD systems based on their unique benefits, leveraging pre-commit hooks for quick, local checks and CI/CD pipelines for more comprehensive tests.

+

Which tool should you use to setup pre-commit hooks?

+

The most widely used tool for managing pre-commit hooks is pre-commit. It provides a flexible framework for configuring and managing the hooks you want to run before a commit. The tool supports a wide range of hooks and can be easily integrated into any MLOps project.

+

To install pre-commit as part of your Poetry project, use the following command:

+
poetry add -G commits pre-commit
+
+

Then, create a .pre-commit-config.yaml file in your project directory with a configuration similar to the following, which outlines which hooks to run:

+
# https://pre-commit.com
+# https://pre-commit.com/hooks.html
+
+default_language_version:
+  python: python3.12
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.5.0
+    hooks:
+      - id: check-added-large-files
+      - id: check-case-conflict
+      - id: check-merge-conflict
+      - id: check-toml
+      - id: check-yaml
+      - id: debug-statements
+      - id: end-of-file-fixer
+      - id: mixed-line-ending
+      - id: trailing-whitespace
+
+

To install the pre-commit hooks, use the following commands:

+
poetry run pre-commit install --hook-type pre-push
+poetry run pre-commit install --hook-type commit-msg
+
+

You can now automatically run your hooks before each commit or push, or trigger them manually with:

+
poetry run pre-commit run
+
+

Which hooks should you use for an MLOps project?

+

For an MLOps project utilizing Python, it's beneficial to configure your pre-commit-config.yaml to include hooks that validate both the quality and format of your code. Here’s an example configuration that includes the ruff and ruff-format hooks for code quality and formatting, respectively, along with other useful checks:

+
default_language_version:
+  python: python3.12
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.5.0
+    hooks:
+      - id: check-added-large-files
+      - id: check-case-conflict
+      - id: check-merge-conflict
+      - id: check-toml
+      - id: check-yaml
+      - id: debug-statements
+      - id: end-of-file-fixer
+      - id: mixed-line-ending
+      - id: trailing-whitespace
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.3.3
+    hooks:
+      - id: ruff
+      - id: ruff-format
+
+

Additional hooks are available at the pre-commit website, offering a wide range of checks for different needs.

+

How can you improve your commit messages?

+

Commit messages play a crucial role in software development, offering insights into what changes have been made and why. To enhance the quality of your commit messages and ensure consistency across contributions, Commitizen, a Python tool, can be extremely helpful. It not only formats commit messages but also helps in converting these messages into a comprehensive CHANGELOG. Here's how you can leverage Commitizen to streamline your commit messages:

+

To get started with Commitizen, you can install it using the following command:

+
poetry add -G commits commitizen
+
+

This command adds Commitizen to your project as a development dependency, ensuring that it is available for formatting commit messages.

+

Once installed, Commitizen offers several commands to assist with your commits:

+
# display a guide to help you format your commit messages
+poetry run cz info
+# bump your package version according to semantic versioning
+poetry run cz bump
+# interactively create a new commit message following best practices
+poetry run cz commit
+
+

These commands are designed to guide you through creating structured and informative commit messages, bumping your project version appropriately, and even updating your CHANGELOG automatically.

+

To configure Commitizen to fit your project needs, you can set it up in your pyproject.toml file as shown below:

+
[tool.commitizen]
+name = "cz_conventional_commits" # Uses the conventional commits standard
+tag_format = "v$version" # Customizes the tag format
+version_scheme = "pep440" # Follows the PEP 440 version scheme
+version_provider = "poetry" # Uses poetry for version management
+update_changelog_on_bump = true # Automatically updates the CHANGELOG when the version is bumped
+
+

Integrating Commitizen into your pre-commit workflow ensures that all commits adhere to a consistent format, which is crucial for collaborative projects. You can add it to your .pre-commit-config.yaml like this:

+
 - repo: https://github.com/commitizen-tools/commitizen
+    rev: v3.18.3 # The version of Commitizen you're using
+    hooks:
+      - id: commitizen # Ensures your commit messages follow the conventional format
+      - id: commitizen-branch
+        stages: [push] # Optionally, enforce commit message checks on push
+
+

By incorporating Commitizen into your development workflow, you not only standardize commit messages across your project but also create a more readable and navigable project history. This practice is invaluable for team collaboration and can significantly improve the maintenance and understanding of your project over time.

+

Is there a way to bypass a hook validation?

+

There are occasions when bypassing a pre-commit hook is necessary, such as when you need to make a quick fix or are confident the commit does not introduce any issues. To bypass the pre-commit hooks, you can use the following commands:

+
git commit -m "My message" --no-verify
+git push --no-verify
+
+

What are some best practices to set up hooks?

+

Implementing pre-commit hooks effectively involves considering both the development workflow and the specific needs of your project. Here are some best practices:

+
    +
  • Prioritize fast-executing hooks to maintain an interactive development workflow without significant delays.
  • +
  • Review available hooks to tailor your pre-commit configuration to your project's technology stack and needs.
  • +
  • Collaborate on hook selection by discussing with your team which hooks to enable, ensuring a consensus and uniform coding standards.
  • +
  • Use fixed hook versions to avoid unexpected behavior from updates, aligning them with your project's versions.
  • +
  • Run complex checks locally, such as unit tests, to catch issues before they reach the CI/CD pipeline, balancing the immediacy of pre-commit hooks with the thoroughness of CI/CD checks.
  • +
+

Pre-commit hook additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.3. CI-CD Workflows.html b/5. Refining/5.3. CI-CD Workflows.html new file mode 100644 index 0000000..2cd6b3f --- /dev/null +++ b/5. Refining/5.3. CI-CD Workflows.html @@ -0,0 +1,2783 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.3. CI/CD Workflows - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.3. CI/CD Workflows

+

What is CI/CD?

+

CI/CD stands for Continuous Integration and Continuous Deployment or Continuous Delivery. It's a method used in software development that focuses on automating the process of integrating code changes from multiple contributors into a single software project, as well as automating the delivery or deployment of the software to production environments. Continuous Integration involves automatically testing and building the software every time a team member commits changes to version control. Continuous Deployment or Delivery takes these integrated changes and automatically deploys them to production environments, ensuring a seamless flow from code commit to deployment.

+

What is a CI/CD workflow?

+

A CI/CD workflow refers to the automated sequence of steps that software undergoes from development to deployment. This workflow typically includes stages such as building the application, running automated tests to verify code quality and functionality, and deploying the code to a production or staging environment. The goal of a CI/CD workflow is to enable developers to release new changes to customers quickly and with high confidence by automating the release process.

+

Why do you need CI/CD workflows?

+
    +
  • Guardrail the code base: CI/CD workflows act as a safeguard for the codebase, ensuring that all changes meet quality standards and pass all tests before merging. This protects the main branch from breaking changes, keeping the software in a deployable state.
  • +
  • Automate publication tasks: They streamline the process of getting software from version control into the hands of users by automating the build, test, and deployment tasks. This automation reduces the manual work involved in software releases and speeds up the delivery process.
  • +
  • Report code quality to others: CI/CD systems often integrate with other tools to provide visibility into the health of the codebase. They can generate reports on code quality, test coverage, and other metrics, making it easier to maintain high standards and improve the software over time.
  • +
+

By automating these processes, a CI/CD system reduces the load on development teams, ensures a consistent level of quality, and facilitates faster, more reliable software releases.

+

Which solution should you use for your CI/CD?

+

There are many CI/CD solutions available, each with its own set of features and integrations. GitHub Actions is one of the most popular and accessible solutions, especially for projects hosted on GitHub. It integrates deeply with GitHub repositories, offering a seamless experience for building, testing, and deploying software directly from GitHub.

+

To get started with GitHub Actions for your CI/CD workflows, you'll need to:

+
    +
  1. Create a .github/workflows directory in your repository if it doesn't already exist.
  2. +
  3. Define your workflow in a YAML file within this directory. The file should specify triggers for the workflow, jobs to run, and steps within those jobs.
  4. +
  5. Use predefined actions from the GitHub Marketplace or define your own steps to install dependencies, run tests, build your software, and deploy it.
  6. +
+

Which workflows should you set up for your MLOps project?

+

For MLOps projects, setting up both Verification and Publication workflows is crucial to ensure the quality and seamless deployment of machine learning models and related software.

+

Verification Workflow

+

This workflow runs on pull requests to the main branch, verifying code quality and functionality before merging:

+
name: Check
+on:
+  pull_request:
+    branches:
+      - main
+concurrency:
+  cancel-in-progress: true
+  group: ${{ github.workflow }}-${{ github.ref }}
+jobs:
+  checks:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./.github/actions/setup
+      - run: poetry install --with checks
+      - run: poetry run invoke checks
+
+

Here is a breakdown of each attribute in the workflow:

+
    +
  • name: Sets the name of the workflow as "Check", identifying it in the GitHub Actions UI.
  • +
  • on: Specifies the event that triggers the workflow, in this case, a pull request.
  • +
  • concurrency: Manages how workflow runs are handled concurrently. If cancel-in-progress is set to true, any in-progress runs of the workflow will be canceled when a new run is triggered.
  • +
  • jobs: Defines the jobs to be run as part of the workflow.
      +
    • checks: Identifies a job within the workflow, named "checks".
        +
      • runs-on: Specifies the type of virtual host machine to run the job on.
      • +
      • steps: Lists the steps to be executed as part of the job.
          +
        • - uses: actions/checkout@v4: Utilizes the checkout action to access the repository code within the job.
        • +
        • - uses: ./.github/actions/setup: Applies a custom action located in the repository to set up the environment.
        • +
        • - run: poetry install --with checks: Executes the command to install dependencies specified under the "checks" group with Poetry.
        • +
        • - run: poetry run invoke checks: Runs a command using Poetry to execute predefined checks.
        • +
        +
      • +
      +
    • +
    +
  • +
+

Publication Workflow

+

This workflow is triggered by a release event and is responsible for publishing the software or model:

+
name: Publish
+on:
+  release:
+    types: [published]
+env:
+  DOCKER_IMAGE: ghcr.io/fmind/mlops-python-package
+concurrency:
+  cancel-in-progress: true
+  group: publish-workflow
+jobs:
+  pages:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./.github/actions/setup
+      - run: poetry install --with docs
+      - run: poetry run invoke docs
+      - uses: JamesIves/github-pages-deploy-action@v4
+        with:
+          folder: docs/
+          branch: gh-pages
+  packages:
+    permissions:
+      packages: write
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./.github/actions/setup
+      - run: poetry run invoke packages
+      - uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - uses: docker/setup-buildx-action@v3
+      - uses: docker/build-push-action@v5
+        with:
+          push: true
+          context: .
+          cache-to: type=gha
+          cache-from: type=gha
+          tags: |
+            ${{ env.DOCKER_IMAGE }}:latest
+            ${{ env.DOCKER_IMAGE }}:${{ github.ref_name }}
+
+

This workflow details steps for deploying documentation and building, tagging, and pushing Docker images, ensuring that releases are systematically handled.

+

How can you avoid repeating some steps between CI/CD workflows?

+

To avoid repetition and maintain consistency across workflows, common steps can be abstracted into reusable actions. By creating a setup action under .github/actions, you encapsulate common setup tasks:

+
name: Setup
+description: Setup for project workflows
+runs:
+  using: composite
+  steps:
+    - run: pipx install invoke poetry
+      shell: bash
+    - uses: actions/setup-python@v5
+      with:
+        python-version: 3.12
+        cache: poetry
+
+

This composite action can then be referenced in multiple workflows, ensuring a DRY (Don't Repeat Yourself) approach to CI/CD configuration.

+

You can also find more actions to use in your workflows from GitHub Marketplace: https://github.com/marketplace?type=actions

+

What are some tips and tricks for using CI/CD workflows for MLOps?

+
    +
  • Automate Regular Tasks: Identify and automate manual tasks to increase efficiency and reduce the potential for human error.
  • +
  • Master GitHub Actions Syntax: Understanding the capabilities and triggers available in GitHub Actions allows for more dynamic and responsive workflows.
  • +
  • Use Concurrency Wisely: The concurrency attribute helps to manage workflow runs by canceling or queuing multiple runs, optimizing resource usage.
  • +
  • Leverage GitHub CLI: The GitHub Command Line Interface can streamline workflow executions: gh workflow run ....
  • +
  • Implement Branch Protection: Enforcing branch protection rules ensures that merges can only occur after successful workflow completions, maintaining code quality and stability.
  • +
+

CI-CD Workflow additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.4. Software Containers.html b/5. Refining/5.4. Software Containers.html new file mode 100644 index 0000000..ff1444b --- /dev/null +++ b/5. Refining/5.4. Software Containers.html @@ -0,0 +1,2635 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.4. Software Containers - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.4. Software Containers

+

What are software containers?

+

Software containers are lightweight, stand-alone packages that include everything needed to run a piece of software, including the code, runtime, system tools, system libraries, and settings. Containers are isolated from each other and the host system, yet they share the OS kernel, making them more efficient and faster than traditional virtual machines. Containers ensure consistency across different development and deployment environments, addressing the "it works on my machine" headache.

+

Why do you need software containers?

+
    +
  • Package system dependencies: Containers encapsulate all dependencies required by an application, such as specific versions of libraries and other software, ensuring that it can run in any environment without issues.
  • +
  • Share reusable software artifacts: Containers allow for the creation and sharing of reusable software artifacts. For instance, you can create a container image with a configured environment that can be shared with other developers, ensuring everyone works with the same setup.
  • +
  • Make your package runnable from any system: Containers abstract away the underlying operating system, allowing applications to run uniformly across different environments. This is particularly useful in heterogeneous environments where systems may be running different versions of operating systems or have different configurations.
  • +
+

While Python packages can be challenging to install across different systems, Docker ensures consistency between operating systems and environments. Running a Docker image is often more straightforward and faster than managing Python packages directly on the system.

+

Which tool should you use for creating containers?

+

The most common tool for creating containers is Docker. Docker simplifies the process of building, running, and managing containers. It uses Dockerfile to automate the deployment of applications in lightweight and portable containers.

+

To install Docker, follow these steps:

+
    +
  1. Visit the Docker website and download the Docker Desktop application for your operating system.
  2. +
  3. Follow the installation instructions provided on the website.
  4. +
  5. Once installed, open a terminal or command prompt and verify the installation by running docker --version.
  6. +
+

Docker offers some paid services, such as Docker Hub private repositories and Docker Enterprise Edition, which provide additional features like enhanced security, user management, and automated image builds. It's important to contact your IT administrators to see if these solutions are available in your organization and to explore potential alternatives.

+

Which tool should you use for hosting containers?

+

Containers are software packages that must be hosted on a container registry. You can use either Docker Hub or GitHub Packages to host your containers.

+

To use GitHub Packages, follow these steps:

+
    +
  1. Create a GitHub account and sign in.
  2. +
  3. Navigate to your repository where you want to host your container.
  4. +
  5. In the repository, go to "Packages" and follow the instructions to publish your container image.
  6. +
+

You must then login to the system from the command-line and tag with image by adding ghcr.io/[user] at the beginning, such in: ghcr.io/fmind/mlops-python-package.

+

Which image should you create for your MLOps projects?

+

To start using Docker for your MLOps projects, you can define a simple image in a Dockerfile in your project repository as follows:

+
# https://docs.docker.com/engine/reference/builder/
+
+FROM python:3.12
+COPY dist/*.whl .
+RUN pip install *.whl
+CMD ["bikes", "--help"]
+
+

You can then build and push this image with the following commands:

+
poetry build # build the wheel file
+docker build --tag=bikes:latest .
+docker run --rm bikes:latest
+
+

Which tips and tricks should you use to optimize your container workflow?

+ +

Software container additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.5. AI-ML Experiments.html b/5. Refining/5.5. AI-ML Experiments.html new file mode 100644 index 0000000..6f1b7f0 --- /dev/null +++ b/5. Refining/5.5. AI-ML Experiments.html @@ -0,0 +1,2785 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.5. AI/ML Experiments - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.5. AI/ML Experiments

+

What is an AI/ML experiment?

+

An AI/ML experiment is a structured process where data scientists and machine learning engineers systematically apply various algorithms, tune parameters, and manipulate datasets to develop predictive models. The goal is to find the most effective model that solves a given problem or enhances the performance of existing solutions. These experiments are iterative, involving trials of different configurations to evaluate their impact on model accuracy, efficiency, and other relevant metrics.

+

Why do you need AI/ML experiment?

+
    +
  • Improve reproducibility: Reproducibility is crucial in AI/ML projects to ensure that experiments can be reliably repeated and verified by others. Experiment tracking helps in documenting the setup, code, data, and outcomes, making it easier to replicate results.
  • +
  • Find the best hyperparameters: Hyperparameter tuning is a fundamental step in enhancing model performance. AI/ML experiments allow you to systematically test different hyperparameter configurations to identify the ones that yield the best results.
  • +
  • Assign tags to organize your experiments: Tagging helps in categorizing experiments based on various criteria such as the type of model, dataset used, or the objective of the experiment. This organization aids in navigating and analyzing experiments efficiently.
  • +
  • Track the performance improvement during a run: Continuous monitoring of model performance metrics during experiment runs helps in understanding the impact of changes and guiding further adjustments.
  • +
  • Integrate with several AI/ML frameworks: Experiment tracking tools support integration with popular AI/ML frameworks, streamlining the experimentation process across different environments and tools.
  • +
+

AI/ML experimentation is distinct in MLOps due to the inherent complexity and non-deterministic nature of machine learning tasks. Leveraging experiment tracking tools equips teams with a structured approach to manage this complexity, akin to how scientists document their research findings.

+

Which AI/ML experiment solution should you use?

+

There are various AI/ML experiment tracking solutions available, each offering unique features. Major cloud providers like Google Cloud (Vertex AI), Azure (Azure ML) and AWS (SageMaker) offer integrated MLOps platforms that include experiment tracking capabilities. There are also vendor-specific tools such as Weights & Biases and Neptune AI that specialize in experiment tracking. Among the open-source options, MLflow stands out as a robust and versatile choice for tracking experiments, integrating with a wide range of ML libraries and frameworks.

+

To install MLflow, execute:

+
poetry install mlflow
+
+

To verify the installation and start the MLflow server:

+
poetry run mlflow doctor
+poetry run mlflow server
+
+

For Docker Compose users, the following configuration can launch an MLflow server from a docker-compose.yml file:

+
services:
+  mlflow:
+    image: ghcr.io/mlflow/mlflow:v2.11.0
+    ports:
+      - 5000:5000
+    environment:
+      - MLFLOW_HOST=0.0.0.0
+    command: mlflow server
+
+

Run docker compose up to start the server.

+

Further deployment details for broader access can be found in MLflow's documentation.

+

How should you configure MLflow in your project?

+

Configuring MLflow in your project enables efficient tracking of experiments. Initially, set the tracking and registry URIs to a local folder like ./mlruns, and specify the experiment name. Enabling autologging will automatically record metrics, parameters, and models without manual instrumentation.

+
import mlflow
+
+mlflow.set_tracking_uri("file://./mlruns")
+mlflow.set_registry_uri("file://./mlruns")
+mlflow.set_experiment(experiment_name="Bike Sharing Demand Prediction")
+mlflow.autolog()
+
+

To begin tracking, wrap your code in an MLflow run context, specifying details like the run name and description:

+
with mlflow.start_run(
+    run_name="Demand Forecast Model Training",
+    description="Training with enhanced feature set",
+    log_system_metrics=True,
+) as run:
+    # Your model training code here
+
+

Which information can you track in an AI/ML experiment?

+

MLflow's autologging capability simplifies the tracking of experiments by automatically recording several information. You can complement autologging by manually logging additional information:

+ +

How can you compare AI/ML experiments in your project?

+

Comparing AI/ML experiments is crucial for identifying the most effective models and configurations. MLflow offers two primary methods for comparing experiments: through its web user interface (UI) and programmatically. Here's how you can use both methods:

+

Comparing Experiments via the MLflow Web UI

+
    +
  1. +

    Launch the MLflow Tracking Server: Start the MLflow tracking server if it isn't running already.

    +
  2. +
  3. +

    Navigate to the Experiments Page: Navigate to the experiments page where all your experiments are listed.

    +
  4. +
  5. +

    Select Experiments to Compare: Find the experiments you're interested in comparing and use the checkboxes to select them. You can select multiple experiments for comparison.

    +
  6. +
  7. +

    Use the Compare Button: After selecting the experiments, click on the "Compare" button. This will take you to a comparison view where you can see the runs side-by-side.

    +
  8. +
  9. +

    Analyze the Results: The comparison view will display key metrics, parameters, and other logged information for each run. Use this information to analyze the performance and characteristics of each model or configuration.

    +
  10. +
+

Comparing Experiments Programmatically

+

Comparing experiments programmatically offers more flexibility and can be integrated into your analysis or reporting tools.

+
    +
  1. Search Runs: Use the mlflow.search_runs() function to query the experiments you want to compare. You can filter experiments based on experiment IDs, metrics, parameters, and tags. For example:
  2. +
+
import mlflow
+
+# Assuming you know the experiment IDs or names
+experiment_ids = ["1", "2"]
+runs_df = mlflow.search_runs(experiment_ids)
+
+
    +
  1. +

    Filter and Sort: Once you have the dataframe with runs, you can use pandas operations to filter, sort, and manipulate the data to focus on the specific metrics or parameters you're interested in comparing.

    +
  2. +
  3. +

    Visualize the Comparison: For a more intuitive comparison, consider visualizing the results using libraries such as Matplotlib or Seaborn. For example, plotting the performance metrics of different runs can help in visually assessing which configurations performed better.

    +
  4. +
+
import matplotlib.pyplot as plt
+
+# Example: Comparing validation accuracy of different runs
+plt.figure(figsize=(10, 6))
+for _, row in runs_df.iterrows():
+    plt.plot(row['metrics.validation_accuracy'], label=f"Run {row['run_id'][:7]}")
+
+plt.title("Comparison of Validation Accuracy Across Runs")
+plt.xlabel("Epoch")
+plt.ylabel("Validation Accuracy")
+plt.legend()
+plt.show()
+
+

These methods enable you to conduct thorough comparisons between different experiments, helping guide your decisions on model improvements and selections.

+

What are some tips and tricks for using AI/ML experiments?

+

To maximize the efficacy of AI/ML experiments:

+
    +
  • Align logged information with relevant business metrics to ensure experiments are focused on meaningful outcomes.
  • +
  • Use nested runs to structure experiments hierarchically, facilitating organized exploration of parameter spaces. +
    with mlflow.start_run() as parent_run:
    +    param = [0.01, 0.02, 0.03]
    +
    +    # Create a child run for each parameter setting
    +    for p in param:
    +        with mlflow.start_run(nested=True) as child_run:
    +            mlflow.log_param("p", p)
    +            ...
    +            mlflow.log_metric("val_loss", val_loss)
    +
  • +
  • Employ tagging extensively to enhance the searchability and categorization of experiments.
  • +
  • Track detailed progress by logging steps and timestamps, providing insights into the evolution of model performance. +
    mlflow.log_metric(key="train_loss", value=train_loss, step=epoch, timestamp=now)
    +
  • +
  • Regularly log models to the model registry for versioning and to facilitate deployment processes.
  • +
+

AI-ML Experiment additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/5.6. Model Registries.html b/5. Refining/5.6. Model Registries.html new file mode 100644 index 0000000..b0572d5 --- /dev/null +++ b/5. Refining/5.6. Model Registries.html @@ -0,0 +1,2916 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.6. Model Registries - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

5.6. Model Registries

+

What is a model registry?

+

A model registry is a central storage and management system for machine learning models. It serves as a repository for models at various stages of their lifecycle, from development to deployment. This system supports version control, model tracking, and collaboration across teams, making it a crucial tool for efficient model management.

+

Why do you need a model registry?

+
    +
  • Storing and versioning AI/ML models: Model registries provide a structured way to store and manage multiple versions of models, allowing users to easily roll back to earlier versions if necessary.
  • +
  • Track the origin of AI/ML models: They help track the development history of models, including who trained them, on what data, and the changes made over time.
  • +
  • Make your model easily deployable: With a model registry, models can be moved smoothly from development to production, supporting continuous integration and delivery workflows.
  • +
  • Promote models ready for production: Model registries facilitate the staging and promotion of models, ensuring only verified and tested models make it to production.
  • +
+

Using a model registry acknowledges the unique nature of machine learning models, which are not just code, but include data, configuration, and dependencies.

+

Which model registry solution should you use?

+

There are various solutions available, ranging from cloud-based platforms like Google Vertex AI, AWS SageMaker, Azure ML. Third-party solutions such as Weights & Biases and Neptune AI offer ready-to-use model management features. For those looking for an open-source option, MLflow Model Registry is popular and accessible, supporting a range of ML frameworks.

+

To get started with MLflow, install it via Poetry:

+
poetry add mlflow
+
+

Check its installation and start the server with:

+
poetry run mlflow doctor
+poetry run mlflow server
+
+

What is an MLflow model and a registered model?

+

An MLflow Model is a model logged during an MLflow experiment, using methods such as mlflow.<model_flavor>.log_model(). After logging, these models can be registered in the MLflow Model Registry. A registered model in MLflow has a unique name and contains various versions, each with its own set of aliases, tags, and metadata.

+

How should you integrate MLflow Registry in your project?

+

The integration of MLflow Model Registry happens in 4 steps: Initializing, Saving, registry, and loading.

+

Initializing

+

Configure your MLflow environment before any operations:

+
import mlflow
+mlflow.set_tracking_uri("file://./mlruns")
+mlflow.set_registry_uri("file://./mlruns")
+client.create_registered_model("bikes")
+
+

This will store models and their metadata in the local ./mlruns folder.

+

Saving

+

Log your model using MLflow, either automatically with autologging or manually:

+
import mlflow
+with mlflow.start_run(run_name="training"):
+    model = ...  # your model training logic here
+    mlflow.sklearn.log_model(model, "models") # optional if you use autologging
+
+

Registring

+

Register your model to manage its lifecycle with this command following the saving step:

+
model_version = mlflow.register_model(name="bikes", model_uri=model_info.model_uri)
+
+

Loading

+

Load a model for use in production or further testing:

+
model_uri = "models:/bikes/1"
+model = mlflow.sklearn.load_model(model_uri)
+predictions = model.predict(data)
+
+

How can you sign the model to provide its inputs and outputs?

+

Create a model signature to clearly define what input the model expects and what output it produces:

+
import mlflow
+from mlflow.models.signature import infer_signature
+inputs, outputs = ...  # define your model inputs and outputs here
+signature = infer_signature(inputs, outputs)
+mlflow.sklearn.log_model(model, artifact_path="models", signature=signature, input_example=inputs)
+
+

How should you access the models in the registry?

+

Access models either through MLflow's UI for a visual interface or programmatically for automation and integration into other applications:

+
import mlflow
+client = mlflow.tracking.MlflowClient()
+model_versions = client.search_model_versions("name='bikes'")
+for model_version in model_versions:
+    print(model_version)
+
+

How should you mark your model as ready to production?

+

To effectively manage MLflow models, especially when marking a model as ready for production, using aliases is a highly practical approach. This method allows for a flexible handling of model versions, which can change frequently with new training runs or during model rollbacks.

+

Model aliases in MLflow are mutable, named references that can be assigned to specific versions of a registered model. This allows you to reference a model version using a model URI or through the model registry API without constantly updating the version number. For example, you can create an alias named "champion" that points to version 1 of a model called "MyModel." Subsequently, this model version can be referred to with the URI models:/MyModel@champion.

+

Assigning an Alias

+

You can assign an alias through the MLflow UI or programmatically. Here’s how you can do it programmatically:

+
from mlflow.tracking import MlflowClient
+
+client = MlflowClient(tracking_uri="./mlruns", registry_uri="./mlruns")
+client.set_registered_model_alias(name="bikes", alias="Champion", version=1)
+
+

Retrieving a Model Using an Alias

+

To use the model assigned to an alias in your applications, you can retrieve it as follows:

+
import mlflow
+
+model_uri = "models:/bikes@Champion"
+model = mlflow.pyfunc.load_model(model_uri=model_uri)
+predictions = model.predict(inputs)
+
+

Automating Alias Assignment

+

To automate the assignment of an alias to the latest model version, you can use:

+
versions = client.search_model_versions("name='bikes'", max_results=1, order_by=["version_number DESC"])
+last_version = versions[0].version
+client.set_registered_model_alias(name="bikes", alias="Champion", version=last_version)
+
+

How can you rollback your model if something goes wrong?

+

To rollback a model version in MLflow, you can reassign the alias to a previous stable version either through the UI or programmatically. Remember, after rolling back, you'll need to refresh the model loading in your application either manually or set it up to automatically detect changes.

+

How can you define custom logic associated with your model?

+

MLflow supports a customizable model format called "PyFunc" (Python Function), which allows you to define custom logic that executes in conjunction with your model. Here's how you can utilize PyFunc to integrate custom logic with your model:

+
import mlflow.pyfunc
+
+class CustomModel(mlflow.pyfunc.PythonModel):
+
+    def load_context(self, context):
+        # Load artifacts or dependencies here
+        pass
+
+    def predict(self, context, model_input):
+        # Apply custom logic here
+        return model_input.apply(some_transformation)
+
+# Example of saving the model
+mlflow.pyfunc.save_model(path="path/to/save", python_model=CustomModel())
+
+

This example defines a custom model by subclassing mlflow.pyfunc.PythonModel and implementing the necessary methods to integrate custom behavior during model predictions.

+

Model Registry additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/5. Refining/index.html b/5. Refining/index.html new file mode 100644 index 0000000..ede7444 --- /dev/null +++ b/5. Refining/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5. Refining - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

5. Refining

+

In this chapter, we delve into the critical processes and methodologies that enhance the efficiency, reliability, and scalability of MLOps projects. "Refining" encompasses a set of practices aimed at streamlining the development pipeline, from code formulation to deployment, ensuring that machine learning models are not only accurately developed but also seamlessly integrated into production environments. These practices are essential for maintaining code quality, automating repetitive tasks, and ensuring consistent environments for development and deployment, thereby reducing errors and increasing productivity.

+
    +
  • 5.0. Design Patterns: Explore common architectural blueprints that solve recurring problems in software design and development within the MLOps ecosystem.
  • +
  • 5.1. Task Automation: Learn how to automate mundane and repetitive software development tasks to increase efficiency and reduce the likelihood of human error.
  • +
  • 5.2. Pre-Commit Hooks: Implement pre-commit hooks to automatically check and enforce code quality standards before code is committed, ensuring a clean and maintainable codebase.
  • +
  • 5.3. CI/CD Workflows: Discover how Continuous Integration and Continuous Deployment (CI/CD) workflows can be designed to automate the testing and deployment of machine learning models, ensuring rapid and reliable delivery.
  • +
  • 5.4. Software Containers: Understand the role of software containers in creating consistent environments for developing, testing, and deploying applications, simplifying the complexities of MLOps projects.
  • +
  • 5.5. AI/ML Experiments: Dive into managing and optimizing AI/ML experiments, focusing on tracking, comparison, and reproducibility of results to accelerate innovation.
  • +
  • 5.6. Model Registries: Examine how model registries are used to catalog and manage versions of machine learning models, facilitating model sharing, versioning, and deployment.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.0. Repository.html b/6. Sharing/6.0. Repository.html new file mode 100644 index 0000000..d2375cf --- /dev/null +++ b/6. Sharing/6.0. Repository.html @@ -0,0 +1,2817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.0. Repository - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.0. Repository

+

What is a code repository?

+

A code repository serves as a centralized platform that supports collaboration in software development. It offers tools for version control, managing contribution guidelines, and establishing automation workflows. The key elements defining a code repository include the host platform, the owner (which can be an individual or an organization), and the repository name. For example, a project might be identified by a URL such as https://github.com/fmind/mlops-python-package where additional details on project specifics or documentation could be appended.

+

Popular code repositories include GitHub, GitLab, and Bitbucket, each providing features tailored to different collaboration needs and complexities. GitHub is renowned for its robust support for public repositories, while GitLab and BitBucket are preferred by both private and public entities. Moreover, cloud providers like Google Cloud Platform, Azure, and AWS offer integrated code repository for public and private organizations.

+

Why do you need to configure a code repository?

+
    +
  • Share your code with others: Code repositories facilitate easy sharing of projects, promoting collaborative development and feedback.
  • +
  • Reference your project: They provide a stable environment for hosting your code, ensuring it is always accessible and traceable.
  • +
  • Setup collaboration: Code repositories support multiple developers working on the same project simultaneously without conflict, utilizing features such as branches and pull requests.
  • +
+

Configuring a code repository is essential for both organizational and personal projects, as it secures projects from being confined to a single machine and enhances collaboration, security, and reliability.

+

Which information should you provide in a code repository?

+

On the repository's main page, you should include:

+
    +
  • Name: Use a concise and descriptive name to clearly identify your project.
  • +
  • Description: Provide a detailed description outlining the project's purpose, scope, and functionality.
  • +
  • Tags: Employ tags to help categorize your repository under relevant topics, facilitating easier discovery based on certain technologies or functionalities.
  • +
+

Maintain organizational consistency by following any existing naming conventions which might include team names or technical stacks. For example, forecasting-bikes-ml includes the team name (forecasting), project domain (bikes), and tech stack (machine learning). Such conventions avoid collisions if another teams want to create a bikes project, or if the same team wants to used another technology stack (e.g., spark for data processing).

+ +

Commits, branches, and tags are essential for version management and ensuring changes are recorded and retrievable. Commits are useful for logging new changes, branches for isolating work during development, and tags for organizing project releases.

+

Creating a Commit

+

A git commit represents a snapshot of your project's history at a particular point in time. Here are the steps to create a commit using Git:

+
    +
  1. Modify your files or add new ones within your project directory.
  2. +
  3. Stage the changes you want to include in your commit by running: +
    git add <filename>
    +
    +To add all changes in the directory, you can use: +
    git add .
    +
  4. +
  5. Check the status to see what changes are staged for the next commit: +
    git status
    +
  6. +
  7. Commit the staged changes by running: +
    git commit -m "Your commit message"
    +
  8. +
+

Here, replace "Your commit message" with a brief description of what changes were made in this commit.

+

Creating a Branch

+

A git branch allows you to develop features, fix bugs, or safely experiment with new ideas in a contained area of your repository.

+
    +
  1. Switch to the branch from which you want to base your new branch (commonly the main branch): +
    git checkout main
    +
  2. +
  3. Create and switch to a new branch by running: +
    git checkout -b <branch-name>
    +
    +Replace <branch-name> with a descriptive name for your branch, such as feat/add-login.
  4. +
+

Creating Tags

+

A git tag is used to mark specific points in repository history as important, typically for release versions.

+
    +
  1. Check your commit history to find the commit to which you want to attach a tag: +
    git log
    +
  2. +
  3. Create an annotated tag on your current commit by running: +
    git tag -a <tag-name> -m "Your tag message"
    +
    +Replace <tag-name> with your version or release identifier, such as v1.0.0, and "Your tag message" with a description of what this tag represents.
  4. +
  5. Push the tag to your remote repository: +
    git push origin <tag-name>
    +
  6. +
+

What is the best method to clone a code repository?

+

To clone a repository, you can use either HTTPS or SSH, with SSH being the preferred method due to its security benefits. SSH does not require credential re-authentication for each push or pull operation, unlike HTTPS.

+

To create an SSH key:

+
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
+
+

After generating the SSH key, add it to your repository host account settings. It's crucial to remember never to share your private SSH key as it serves as your secure credential. Always keep it confidential. You can identify the public key, which is safe to share, by its .pub file extension. For instance, if your private key is named id_rsa, your public key will be named id_rsa.pub and this is the key you upload to your repository host for authentication.

+

To clone a repository with SSH (recommended):

+
git clone git@hostname:user/repository.git
+
+

To clone a repository with HTTPS:

+
git clone https://hostname/user/repository.git
+
+

Is it possible to restrict the visibility of a code repository?

+

You can adjust the visibility of your code repository to be either public or private:

+
    +
  • Public repositories are accessible to everyone and are ideal for open-source projects.
  • +
  • Private repositories are only accessible to specific individuals or teams and are suitable for sensitive or proprietary projects.
  • +
+

Choose the visibility setting that best aligns with your project needs and organizational policies.

+

What is the difference between forking and branching a code repository?

+

Forking a repository involves creating a complete copy of the repository under your account, enabling you to work independently without affecting the original repository. Branching creates a separate line within the same repository, allowing for isolated development that can be merged back into the main code base later.

+

Forking is typically used when public collaborators wish to develop their versions of a project, whereas branching is suited for ongoing collaboration within the same project direction, recommended for both public and private organizational use.

+

Is it possible to setup automation at the repository level?

+

Automation can be effectively implemented at the repository level through the use of webhooks, third-party apps, and integrations. These tools can help automate tasks such as testing, deployment, and integration workflows, which enhances productivity and ensures consistency across operations.

+
    +
  • +

    Webhooks: Webhooks can be configured to trigger automated workflows in response to events within the repository, such as pushes, pull requests, or merges. For example, a webhook could trigger an automated build or test in a continuous integration system whenever code is pushed to a repository.

    +
  • +
  • +

    Third-party apps: Many third-party applications are available on platforms like GitHub Apps or GitLab Integrations that can be used to extend the functionality of your repository. For instance, applications like CircleCI, Travis CI, or Jenkins can be integrated for continuous integration and continuous deployment (CI/CD) pipelines. Another popular choice is SonarQube, which can be integrated for automatic code quality reviews.

    +
  • +
  • +

    Integrations: Most repository platforms offer a range of built-in integrations that connect with other tools and services. For example, GitHub can integrate directly with project management tools like Jira or Trello to link commits and pull requests to tasks. Similarly, it can integrate with deployment platforms like Heroku or AWS, enabling automatic deployments when changes are made to specific branches.

    +
  • +
+

How can you protect the code of the main branch?

+

To safeguard the integrity of your main branch, you can implement branch protection rules. These might include requirements for pull request reviews, status checks before merging, and restrictions on who can push to these crucial parts of your repository, ensuring all changes are thoroughly checked.

+
    +
  1. Navigate to Your Repository Settings: Go to your repository on GitHub, click on "Settings," and then select "Branches" from the sidebar.
  2. +
  3. Add Branch Protection Rules: Click on "Add rule" in the "Branch protection rules" section. Enter the name of the branch you want to protect, typically main or master.
  4. +
  5. Configure the Protection Rules:
      +
    • Require pull request reviews before merging: This setting requires one or more reviews before a contributor can merge changes into the protected branch, which helps ensure that code is reviewed and approved by other team members.
    • +
    • Require status checks to pass before merging: Enabling this ensures that all required CI tests pass before the branch can be merged, which is crucial for maintaining code quality and functionality.
    • +
    • Include administrators: You can enforce these rules on repository administrators as well, ensuring that all changes, regardless of who makes them, undergo the same level of scrutiny.
    • +
    • Restrict who can push to matching branches: This setting allows you to specify which users or teams can push to the branch, adding an additional layer of security.
    • +
    +
  6. +
  7. Save Changes: Once configured, save the changes to enforce the rules on the branch.
  8. +
+

Repository additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.1. License.html b/6. Sharing/6.1. License.html new file mode 100644 index 0000000..9bad1e0 --- /dev/null +++ b/6. Sharing/6.1. License.html @@ -0,0 +1,2690 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.1. License - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.1. License

+

What is a software license?

+

A software license is a legal agreement that details the conditions under which a software product can be accessed and used. This agreement is essential for defining the rights and responsibilities of both the developers and the users. It covers aspects like software modification, sharing, and usage in other projects, thus protecting the intellectual property of the creators and the legal rights of the users.

+

Software licenses are typically categorized into two main types: open-source and proprietary. Open-source licenses generally allow the software to be freely used, modified, and shared, while proprietary licenses impose restrictions on these activities.

+

Why do you need a software license?

+

Implementing a software license is crucial for several reasons:

+
    +
  • Legal Clarity: Establishes a legal framework that dictates how the software can be used, preventing disputes.
  • +
  • Copyright Protection: Protects your intellectual property from unauthorized use or distribution.
  • +
  • Control Over Distribution: Dictates the terms of how your software is distributed, whether it is sold, given away, or integrated into other products.
  • +
  • Open Source Integrity: In open-source projects, a clear license preserves the ethos of free use and collaborative development, and governs contributions.
  • +
+

In an organizational setting, it's vital to choose a license that aligns with institutional policies and objectives. Consultation with legal and management teams is recommended to ensure compliance with these policies.

+

Example: MIT vs GPLv3

+

The MIT License and the GNU General Public License v3 (GPLv3) are two prevalent open-source licenses that differ significantly:

+
    +
  • MIT License: Highly permissive, allowing almost unrestricted use, including in proprietary projects, provided the original copyright and license notice are included in copies of the software.
  • +
  • GNU General Public License v3 (GPLv3): This copyleft license requires that any derivatives of the software also be distributed under the same license, ensuring the preservation of usage, modification, and sharing rights.
  • +
+

Case Study: ElasticSearch vs OpenSearch

+

ElasticSearch switched from the very permissive Apache 2.0 license to a dual-license model due to concerns about cloud providers using their software without contributing back. This led Amazon Web Services (AWS) to fork ElasticSearch, creating OpenSearch under the Apache 2.0 license to maintain its openness and community contribution. This case underscores the strategic role of licensing in software development and community engagement.

+

How to choose your software license?

+

Choosing the right software license involves careful consideration of your project’s objectives and the legal landscape:

+
    +
  • For Open Source Projects: Utilize tools like ChooseALicense.com to select a license that fits your preferences regarding how your software is used and shared.
  • +
  • For Proprietary Project: Work closely with your company’s legal team to ensure the license aligns with business strategies and regulatory requirements.
  • +
+

How to define the license for your project?

+

To implement a license in your project:

+
    +
  1. Create a LICENSE.txt file in the root directory.
  2. +
  3. Include the Full License Text: Ensure the complete, unaltered text of the license is included to make it legally binding.
  4. +
+

You can find open-source license text on this website: https://opensource.org/license.

+

Should you choose a different license for AI/ML models?

+

Licensing AI and ML models necessitates addressing unique considerations related to the nature of data usage, model training, and deployment:

+
    +
  • Data Privacy and Usage: It's crucial to ensure that the licensing agreement covers the handling of potentially sensitive or personal information in compliance with applicable privacy laws, such as GDPR.
  • +
  • Model Reproducibility: The license should specify if and how the model can be used to generate derivative models or reproduce results.
  • +
  • Commercial Use: You need to decide whether the model can be used for commercial purposes. Some licenses may restrict commercial use to promote free academic and research utilization.
  • +
  • Attribution Requirements: If required, the license should mandate users to credit the model creators or reference the original research.
  • +
+

Here is a list of some popular licenses used for AI/ML models that address these considerations:

+
    +
  • Apache License 2.0: Permits almost unrestricted use, including commercial use, while still requiring attribution.
  • +
  • GNU General Public License (GPL) v3.0: Ensures that derivative works are distributed under the same license terms, suitable for models where sharing improvements is desired.
  • +
  • MIT License: A permissive license that allows almost any use with minimal restrictions, provided that the license and copyright notice are included.
  • +
  • Creative Commons (CC BY 4.0): Suitable for datasets rather than models, requiring attribution but allowing for commercial and derivative uses.
  • +
+

How should you integrate the license of your software dependencies?

+

Effectively managing the licenses of software dependencies ensures legal compliance and supports community and user trust. Here are streamlined steps for integrating these licenses:

+
    +
  1. Inventory Dependencies: Use tools like SPDX to list and document all third-party components and their licenses.
  2. +
  3. Review License Terms: Analyze what is permitted or restricted under each license.
  4. +
  5. Assess Compatibility: Check for conflicts between dependency licenses and your project’s intended use.
  6. +
  7. Automate License Compliance: Employ tools like WhiteSource or Black Duck to automate license management, integrating them into your CI/CD pipeline.
  8. +
+

These steps help maintain legal integrity while leveraging the benefits of diverse software components.

+

License additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.2. Readme.html b/6. Sharing/6.2. Readme.html new file mode 100644 index 0000000..7df76f9 --- /dev/null +++ b/6. Sharing/6.2. Readme.html @@ -0,0 +1,2660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.2. Readme - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.2. Readme

+

What is a README file?

+

A README.md file acts as the front page of your project repository. Think of it as the first point of contact for anyone encountering your project. It goes beyond just a basic description; it’s your guidebook, introducing the project’s purpose, usage, and how to contribute effectively.

+

Why do you need a README file?

+
    +
  • Advertise your project: A compelling README attracts potential users and contributors by highlighting the value and uniqueness of your project.
  • +
  • Explain the purpose and usage: It clarifies what the project does, who it’s for, and how it can be utilized, thereby increasing user engagement and adoption.
  • +
  • Include some visuals and statistics: Including images, badges, and usage statistics can make the README visually appealing and informative, offering quick insights at a glance.
  • +
+

A well-crafted README is crucial as it forms the first impression of your project. Investing time in developing a clear, informative, and engaging README can significantly impact the project's visibility and usability.

+

Which format should you use for the README file?

+

While READMEs can be written in plain text, Markdown, or RestructuredText. Markdown is the most popular due to its straightforward and intuitive syntax.

+

You can learn more about Markdown format at this link: Markdown Guide.

+

What should you put in your README file?

+

Typically, your README should include the following information:

+
    +
  • Detailed project overview: Provide a comprehensive description of what the project does and the problems it solves.
  • +
  • Key features and functionalities: Highlight the main features and what makes your project stand out.
  • +
  • Visual elements like logos or screenshots: Graphics can help make the README more engaging and easier to understand at a glance.
  • +
  • How to install/set up/use the project: Offer clear, step-by-step instructions to get the project running and how to use it effectively.
  • +
  • Badges: Include badges from platforms like Travis CI, Codecov, and others to show build status, test coverage, etc.
  • +
+

When should you write your README file?

+

Ideally, you should start your README early in the development process and update it as your project evolves.

+

Begin with a basic structure:

+
# Your AI/ML Project
+
+Explore the innovative applications of AI and ML with our project.
+
+

As your project develops, expand the README to include:

+
    +
  • Capabilities: Detail the unique solutions your project offers.
  • +
  • Installation Guide: Provide comprehensive instructions for setting up.
  • +
  • Usage Instructions: Include examples to demonstrate how to use the project effectively.
  • +
+

How can you get more tips on writing a README file?

+

For additional tips on crafting an effective README.md, consider these resources:

+ +

To enhance your README writing, consider using VS Code Extensions like:

+ +

Include headers in your file to facilitate navigation, especially using VS Code extensions like Markdown All in One to automatically generate a Table of Contents.

+

What should you do if my README file becomes too big?

+

If your README grows too extensive, it's advisable to transition to a full-fledged documentation site. This can be achieved using:

+
    +
  • Documentation generators: Tools like Sphinx or mkDocs can help structure and generate detailed documentation.
  • +
  • Documentation hosting: Platforms such as GitHub Pages or ReadTheDocs offer hosting solutions to make your documentation accessible online.
  • +
+

As your project scales, ensuring that the documentation remains manageable and navigable is crucial for maintaining user engagement and developer contributions.

+

README additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.3. Releases.html b/6. Sharing/6.3. Releases.html new file mode 100644 index 0000000..31c5dc8 --- /dev/null +++ b/6. Sharing/6.3. Releases.html @@ -0,0 +1,2853 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.3. Releases - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.3. Releases

+

What is a project release?

+

A project release is a specific version of your project that stabilizes the code and its associated artifacts. It documents a list of changes, bug fixes, and new features aimed at achieving the project's goals. A release serves as a reference point for contributors, enabling them to pinpoint specific software versions that can be utilized in other projects.

+

Why do you need to create project releases?

+
    +
  • Communicate changes to others: Creating a release provides a structured way to inform users and developers about new features, fixes, and improvements. This communication is crucial for managing expectations and helping users to understand how the software has evolved.
  • +
  • Divide the project into milestones: Releases help in setting and achieving milestones throughout the development process. This organization allows teams to track progress, evaluate the effectiveness of the project timeline, and adjust goals as needed.
  • +
  • Ensure each release is stable and consistent: Each release acts as a quality checkpoint, ensuring that the software meets certain standards of stability and consistency. This reduces the risk of regressions and maintains a reliable user experience.
  • +
+

Project releases act as a contract between the contributors and end users. Referencing a specific version, like v8.1.2, guarantees a consistent set of behaviors and interfaces. By maintaining clear and transparent communication about releases, you can build and maintain trust with your user base, especially as the project grows.

+

At which frequency should you release a new project?

+

The frequency of new releases varies depending on the project. Some projects benefit from a rapid release cycle, as short as every two or three weeks, while others may opt for longer intervals, possibly every few months. It's crucial to balance the needs of both contributors and users and to establish a clear naming scheme to prevent compatibility issues and conflicts.

+

Which git workflow should you use for your project?

+

Choosing an effective git workflow is crucial for streamlined team collaboration and efficient project management. Below are three popular workflows, each with its own strengths and suited for different types of projects.

+

GitHub Flow

+

Ideal for projects with frequent releases, GitHub Flow is simple:

+
    +
  • Main branch is always deployable.
  • +
  • Feature branches for each new feature or fix.
  • +
  • Use Pull Requests (PRs) for reviews.
  • +
  • Merge into main after review.
  • +
+

Git Flow

+

More structured, Git Flow is suited for projects with scheduled releases:

+
    +
  • Maintain two main branches: master and develop.
  • +
  • Feature branches merge into develop.
  • +
  • Release branches prepare new production releases.
  • +
  • Hotfix branches allow for quick fixes to production.
  • +
+

Forking Workflow

+

Ideal for open-source projects where multiple external contributors work independently:

+
    +
  • Contributors fork the main repo and work independently.
  • +
  • Changes are submitted via PRs from their own forks.
  • +
  • Central maintainers review and merge PRs into the official repository.
  • +
+

Comparison

+
    +
  • GitHub Flow is best for small teams or projects needing fast, continuous updates.
  • +
  • Git Flow suits large, complex projects with multiple simultaneous development streams.
  • +
  • Forking Workflow is optimal for projects with broad, diverse contributor bases, particularly in open source.
  • +
+

Choose a workflow based on your project's size, complexity, release frequency, and the nature of your team's collaboration.

+

Which versioning schema should you use for a project release?

+

The two most popular versioning schemes are:

+
    +
  • SemVer (Semantic Versioning): SemVer is based on three numerical identifiers: major, minor, and patch (e.g., 1.4.2). Major changes include breaking changes, minor changes add functionality in a backwards-compatible manner, and patches are for backwards-compatible bug fixes.
  • +
  • CalVer (Calendar Versioning): CalVer uses the calendar date to inform the version number, which can be useful for projects where time and the state of the project are closely linked, such as in continuously delivered software.
  • +
+

Python also adopts its own version specification scheme known as PEP440, which provides guidelines for version identifiers based on public releases, pre-releases, post-releases, and development releases.

+

SemVer is recommended for its simplicity and the possibility of communication various level of changes.

+

How can you create a new project release on GitHub?

+

Creating a new release on GitHub involves the following steps:

+
    +
  1. Navigate to your GitHub repository and go to the "Releases" section.
  2. +
  3. Click on "Draft a new release" or use the tag version from your git command line.
  4. +
  5. Fill in the tag version, which should match your versioning schema, add release title and description that includes the highlights of this release such as new features, bug fixes, and any deprecations.
  6. +
  7. Attach binaries or additional relevant files if necessary.
  8. +
  9. Publish the release, which will then be available to users and can be downloaded from the repository.
  10. +
+

How can you prepare and implement a project release with other developers?

+

Preparing and implementing a release with other developers involves collaboration tools provided by platforms like GitHub:

+
    +
  • Issues: Use GitHub Issues to track individual tasks or bugs that need to be addressed before the release.
  • +
  • Labels: Apply labels to issues to categorize them by priority, type (e.g., bug, feature), or status (e.g., in-progress, completed).
  • +
  • Milestones: Group related issues under a milestone corresponding to the release. This helps in tracking progress towards the release and ensures all intended features and fixes are included.
  • +
+

These tools facilitate organization and communication among team members, ensuring that everyone is aligned and aware of the release objectives and timelines.

+

How can you review and include the work of others in the project release?

+

Contributors should work on a feature or fix branch specifically created for their task. Once the task is completed:

+
    +
  1. Feature or fix branch: Developers create branches off the main branch to develop new features or fixes.
  2. +
  3. Pull Request (PR): They submit a Pull Request to merge their branch into the main branch. The PR should include a clear description of the changes and any necessary documentation updates.
  4. +
  5. Code Review: Other contributors review the PR, providing feedback and requesting changes if needed. This ensures that the code meets the project’s standards and integrates well with the existing codebase.
  6. +
+

After approval, the branch is merged into the main branch, and the feature or fix becomes part of the next release. The feature branch can then be archived.

+

Which steps should be performed prior to a new project release?

+

Before releasing, perform these steps:

+
    +
  • Testing: Conduct thorough testing, possibly automated using tools like GitHub Actions, to ensure the software works as intended and is free from major bugs.
  • +
  • Publication: Set up a workflow to automatically handle tasks such as documentation updates and container builds upon release.
  • +
  • Changelog: Update the CHANGELOG.md using tools like commitizen, detailing all changes, fixes, and features in this release.
  • +
+

These steps ensure that the release is polished and ready for public use.

+ +

Effective communication of changes involves several platforms:

+
    +
  • Release page on GitHub: Provides detailed release notes and links to downloadable assets or binaries.
  • +
  • CHANGELOG.md: Maintains a cumulative record of all changes made across releases using the keep a changelog format.
  • +
  • GitHub Pages: Utilizes a version-specific page to provide documentation corresponding to each release.
  • +
+

How long should you support previous releases of your project?

+

The duration for supporting previous releases depends on the project's scope and user base. For instance, projects like Python provide Long Term Support (LTS) versions that are supported for several years. Define the level of support for each release, which might include backporting critical bug fixes or providing security updates.

+

A well-documented support policy reassures users that they can rely on your software for critical applications over time.

+

Release additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.4. Templates.html b/6. Sharing/6.4. Templates.html new file mode 100644 index 0000000..51b043a --- /dev/null +++ b/6. Sharing/6.4. Templates.html @@ -0,0 +1,2848 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.4. Templates - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.4. Templates

+

What is a code template?

+

A code template provides a standardized framework for initiating new projects, particularly beneficial in environments where multiple projects share common elements. This structure usually includes predefined configurations for common tools like linters, unit testing frameworks, and code formatters. By doing so, each new project can be customized with specific details such as the project name, description, and operating environment, while maintaining a consistent approach to software development.

+

For instance, the authors of this course provide the Cookiecutter MLOps Package for free to quickly generate new MLOps Projects based on the principles described in this course. This section explains how to leverage similar code templates and adapt them for your organization.

+

Why do you need to create code templates?

+
    +
  • Align code base and best practices: Creating a code template helps enforce uniformity and adherence to best practices across all projects within an organization
  • +
  • Augment your productivity: Templates streamline the project setup process, significantly reducing the time and effort needed to start new projects
  • +
  • Focus on the main problem: Code templates allow developers to concentrate on solving the business problem rather than getting bogged down by repetitive setup tasks. The template maintainers can focus on enhancing the template itself, ensuring it incorporates the latest and most efficient solutions.
  • +
+

As AI/ML projects become more standardized and akin to an assembly line production in factories, automating their creation ensures faster deployment and a higher standard of initial setup quality.

+

Which tools should you use to create code templates?

+

Cookiecutter

+

Cookiecutter is a widely-used tool in the Python community for creating project templates. It uses a straightforward command-line interface to generate new projects from user-defined templates.

+
cookiecutter [template-directory]
+
+

This command processes the template directory or repository containing a cookiecutter.json file and potentially other template files, prompting the user for input on defined variables.

+

Cruft

+

Cruft complements Cookiecutter by helping manage updates to projects created from a Cookiecutter template. It tracks changes in the template and can apply these changes to existing projects, helping maintain consistency and up-to-date practices across all projects.

+

Initializing a new project with Cruft:

+
cruft create [template-repository-url]
+
+

Updating the project afterwards:

+
cruft update
+
+

How can you pass variables to replace inside a code template?

+

Variables in Cookiecutter templates are managed using Jinja2, a template engine for Python. Jinja2 allows for dynamic content generation using placeholders in template files, which are replaced by actual values at runtime based on user input or default values defined in cookiecutter.json.

+

Example of using Cookiecutter variables in code:

+
# Define a variable in your Python script that uses Cookiecutter variables
+project_name = "{{ cookiecutter.project_name }}"
+
+

The cookiecutter.json file is where all default values for the variables in a template are defined. When a new project is generated, Cookiecutter prompts the user to input values for these variables or accept the defaults as specified in the JSON file.

+

Here is an example of defining variables in the cookiecutter.json file, which includes project metadata and configuration defaults:

+
{
+    "user": "fmind",
+    "name": "MLOps Project",
+    "repository": "{{cookiecutter.name.lower().replace(' ', '-')}}",
+    "package": "{{cookiecutter.repository.replace('-', '_')}}",
+    "license": "MIT",
+    "version": "0.1.0",
+    "description": "TODO",
+    "python_version": "3.12",
+    "mlflow_version": "2.14.3"
+}
+
+

How should you structure a cookiecutter template repository?

+

A cookiecutter template repository typically consists of two primary levels of structure:

+
    +
  1. +

    Template Files and Folders: These are the directories and files that will be generated and are identifiable by their names containing cookiecutter variables (e.g., {{cookiecutter.project_slug}}. All such template files and folders are contained within a single root directory (e.g, {{cookiecutter.repository}}) to facilitate easy generation.

    +
  2. +
  3. +

    Supporting Files: These include additional resources such as documentation, scripts for automating setup tasks, or configuration files necessary for the template itself but not part of the generated project files. These files reside outside the main template folder.

    +
  4. +
+

Refer to the cookiecutter-mlops-package template by this course authors for a practical example of how to structure a template repository.

+

Initializing this template package:

+
cookiecutter gh:fmind/cookiecutter-mlops-package
+
+

For advanced structuring techniques and best practices, refer to the Advanced Usage section of Cookiecutter documentation.

+

What should you include and exclude from the code template?

+

What to Include:

+
    +
  • Automation tasks: Tools like PyInvoke to automate common development tasks.
  • +
  • Linters: Tools such as Ruff to ensure code quality and style consistency.
  • +
  • Unit test configurations: Setup configurations for tools like pytest to facilitate testing.
  • +
  • Project configuration files: Such as pyproject.toml for managing project dependencies and settings.
  • +
+

What to Exclude:

+
    +
  • Specific source code or tests: Avoid including actual code or tests that imply a specific programming style or architecture. This allows developers to apply their preferred coding practices without constraints.
  • +
+

How should you keep the project updated with the code template?

+

To maintain alignment with the original template as it evolves, initiate your project with cruft and periodically run:

+
cruft update
+
+

This will help integrate enhancements and bug fixes from the template into your project, utilizing Git's capabilities to manage any conflicts that arise.

+

How should you illustrate the usage of a code template?

+

Creating one or several demo repositories based on the template serves multiple purposes:

+
    +
  • Demonstrates practical application: Show how the template can be used in a real-world scenario.
  • +
  • Encourages experimentation: Provides a base for others to experiment with different coding styles or architectural approaches.
  • +
  • Facilitates specific feature development: Use the demo to develop features that might not be universally required but are useful for some projects.
  • +
+

How should you change and improve the code template?

+

The most effective approach to develop and refine a code template is to:

+
    +
  1. Generate a new project using the code template.
  2. +
  3. Implement and test your changes in the generated project.
  4. +
  5. Once validated, backport these changes to the template itself.
  6. +
+

This iterative approach helps ensure that the template remains robust and functional across different use cases.

+

How can you test the generation of a code template automatically?

+

Utilizing pytest-cookies, you can automatically test the generation process of your Cookiecutter template:

+
def test_bake_project(cookies):
+    result = cookies.bake(extra_context={"repo_name": "helloworld"})
+    assert result.exit_code == 0
+    assert result.exception is None
+    assert result.project_path.name == "helloworld"
+    assert result.project_path.is_dir()
+
+

Additionally, pytest-shell-utilities can be used to run shell commands post-generation to validate setup tasks:

+
def test_assert_good_exitcode(shell):
+    ret = shell.run("exit", "0")
+    assert ret.returncode == 0
+
+def test_assert_bad_exitcode(shell):
+    ret = shell.run("exit", "1")
+    assert ret.returncode == 1
+
+

How can you perform automated actions after the code generation?

+

Cookiecutter's hook mechanism allows the execution of Python or shell scripts after the project generation. This functionality is crucial for performing setup tasks or cleaning up unnecessary files based on the user's choices.

+

Example of a post-generation hook script:

+
import os
+
+REMOVE_PATHS = [
+    "{% if cookiecutter.packaging != 'pip' %}requirements.txt{% endif %}",
+    "{% if cookiecutter.packaging != 'poetry' %}poetry.lock{% endif %}",
+]
+
+for path in REMOVE_PATHS:
+    path = path.strip()
+    if path and os.path.exists(path):
+        os.unlink(path) if os.path.isfile(path) else os.rmdir(path)
+
+

Templates additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.5. Workstations.html b/6. Sharing/6.5. Workstations.html new file mode 100644 index 0000000..9810404 --- /dev/null +++ b/6. Sharing/6.5. Workstations.html @@ -0,0 +1,2570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.5. Workstations - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.5. Workstations

+

What is a cloud workstation?

+

A cloud workstation is a virtual computing environment hosted on remote servers, providing users with access to powerful computing resources over the internet. These virtual workstations typically include all necessary software and hardware capabilities required for specific tasks, which are maintained by the cloud provider. Users can access these resources from any device capable of connecting to the internet, ensuring flexibility and mobility.

+

Why should you use cloud workstations?

+
    +
  • Improved Security: Cloud workstations benefit from the robust security measures implemented by cloud providers, which include data encryption, application firewalls, intrusion detection systems, and regular security audits.
  • +
  • Start in Minutes: Cloud workstations can be provisioned rapidly, often within minutes. This quick deployment allows users to bypass the lengthy installations and configurations typically required for local setups.
  • +
  • Share Common Setup: Cloud workstations enables teams to share a common development environment, ensuring consistency across all team members' setups. This homogeneity avoids the pitfalls of environment-specific issues and reduces the need for individual support.
  • +
  • Improved Collaboration: Cloud workstations enhance collaboration among team members by allowing multiple users to access and work on the same environment simultaneously. This feature is particularly useful for remote teams, making it easier to share progress, troubleshoot issues, and pair program in real-time.
  • +
  • Provide More Hardware Choice: Cloud providers typically offer a range of hardware configurations that users can choose from, according to their specific needs. This flexibility allows users to scale their resources up or down based on the project's demands without the need for physical hardware upgrades is particularly useful for MLOps.
  • +
+

Compared to local systems, cloud workstations offer greater scalability, reliability, and accessibility. They eliminate the need for upfront hardware investments and reduce the ongoing maintenance and upgrading costs. Moreover, they can provide access to high-performance computing resources that might not be feasible or cost-effective to maintain locally.

+

Which platforms should you use for cloud workstations?

+

Several platforms offer robust cloud workstation services:

+
    +
  • GitHub Codespaces: Ideal for developers, it provides a complete dev environment within GitHub that can run in your browser or integrate with Visual Studio Code.
  • +
  • Cloud Workstation (GCP): Offers customizable compute instances that can be tailored for high-performance environments.
  • +
  • Amazon WorkSpaces (AWS): Provides a broad range of cloud computing options that can be optimized for different workloads, from lightweight tasks to compute-intensive applications.
  • +
+

How can you edit code together using cloud workstations?

+

Collaborative coding in cloud workstations can be enhanced through various extensions and tools. For instance,Visual Studio Code Live Share allows real-time collaboration, letting participants from different locations edit and debug code together.

+

Workstation additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/6.6. Contributions.html b/6. Sharing/6.6. Contributions.html new file mode 100644 index 0000000..5652489 --- /dev/null +++ b/6. Sharing/6.6. Contributions.html @@ -0,0 +1,2598 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.6. Contributions - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

6.6. Contributions

+

What is a code of conduct?

+

A code of conduct is a document that establishes expectations for behavior for community members. It serves as a guideline to help create a safe, respectful, and collaborative environment. This document typically addresses issues such as harassment, discrimination, and conflict resolution. By having a clear code of conduct, projects can encourage an inclusive atmosphere that encourages participation from diverse groups of people.

+

You can add a code of conduct for your project by following this guide from GitHub or adding a CODE_OF_CONDUCT.md file directly at the root of your repository. You can find examples on the Open Source Guides: Establishing a code of conduct.

+

What are contribution guidelines?

+

Contribution guidelines are instructions provided by a project to help contributors understand how they can effectively participate. These guidelines often include standards for coding practices, the submission process for patches or features, and criteria for acceptable behavior when interacting with the project’s community. By following these guidelines, contributors can ensure their efforts align with the project goals and requirements, streamlining the collaboration process.

+

You can define the contributing guidelines for your project by creating a CONTRIBUTING.md file at the top of your repository.

+

How can you build a good software community?

+

Building a good software community involves several key strategies:

+
    +
  1. Open Communication: Encourage open and transparent communication where all members feel heard. Utilize forums, chats, and regular meetings to facilitate discussions.
  2. +
  3. Recognition and Rewards: Recognize and reward contributions, whether they are code submissions, documentation, or community help. This can promote a sense of value and appreciation among members.
  4. +
  5. Inclusivity: Promote an inclusive environment by actively engaging with diverse groups and considering different perspectives in decision-making processes.
  6. +
  7. Learning and Development: Provide opportunities for learning and personal development through workshops, seminars, and mentoring programs. This helps members grow their skills and stay engaged with the community.
  8. +
  9. Leadership and Governance: Establish clear leadership and governance structures that define roles and responsibilities. This helps in managing the community efficiently and ensuring that everyone knows how to contribute effectively.
  10. +
+

How should you invite others to your repository?

+

Inviting others to your repository can be effectively managed through the use of permissions. Here are some steps to consider:

+
    +
  1. Clear Roles and Permissions: Define roles within your repository (e.g., Viewer, Contributor, Maintainer) and assign appropriate permissions that align with these roles.
  2. +
  3. Contribution Guidelines: Provide clear contribution guidelines to help new contributors understand how to get started.
  4. +
  5. Welcoming Message: Send a welcoming message explaining the project’s goals, the importance of each role, and how new contributors can get involved.
  6. +
  7. Use of Tools: Utilize tools like GitHub’s issue tracker and pull requests to facilitate collaboration and communication.
  8. +
+

How can you help people become better contributors?

+

Helping people become better contributors primarily involves mentorship and effective code reviews. Here’s how you can implement these strategies:

+
    +
  1. Code Reviews: Conduct thorough code reviews that not only focus on the correctness of code but also on best practices and design principles. Provide constructive feedback that helps contributors understand what they did well and where they can improve.
  2. +
  3. Documentation: Ensure your project has comprehensive documentation that is easy to understand. This can help contributors get up to speed quickly and reduce misunderstandings.
  4. +
  5. Regular Feedback: Offer regular feedback sessions where contributors can discuss their progress and receive advice on overcoming challenges.
  6. +
  7. Mentoring: Establish a mentoring program where experienced contributors can guide new members. This can significantly enhance the learning curve and encourage more meaningful contributions.
  8. +
+

Contribution additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/6. Sharing/index.html b/6. Sharing/index.html new file mode 100644 index 0000000..d563a93 --- /dev/null +++ b/6. Sharing/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6. Sharing - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

6. Sharing

+

In this chapter, we will explore the essential practices and tools that facilitate the sharing and distribution of machine learning operations (MLOps) projects. Sharing not only enhances collaboration among data scientists and developers but also promotes the reuse and adaptation of existing models and workflows, crucial for the efficient scaling of machine learning solutions. By the end of this chapter, you will understand how to effectively organize, document, and disseminate your MLOps projects to make them accessible and useful to others.

+
    +
  • 6.0. Repository: Learn how to set up and structure a repository for MLOps projects, which serves as the foundation for version control and collaboration.
  • +
  • 6.1. License: Understand the importance of choosing the right license for your project, which governs how others can use, modify, and distribute your work.
  • +
  • 6.2. Readme: Discover the key elements of crafting an effective README file that provides a comprehensive overview and guides users on how to use your project.
  • +
  • 6.3. Releases: Discuss the process of managing project versions through releases, which help in tracking iterations and ensuring stability for end users.
  • +
  • 6.4. Templates: Explore the use of templates to standardize project components, such as data pipelines and model training routines, enhancing consistency and reducing errors.
  • +
  • 6.5. Workstations: Delve into setting up cloud workstations, including configuring environments and tools that are essential for contributors to efficiently work on your project.
  • +
  • 6.6. Contributions: Examine strategies for managing contributions, including guidelines for submitting issues and pull requests, which are vital for collaborative development and project improvement.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/0. Reproducibility.html b/7. Observability/0. Reproducibility.html new file mode 100644 index 0000000..7b5ff13 --- /dev/null +++ b/7. Observability/0. Reproducibility.html @@ -0,0 +1,2822 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.0. Reproducibility - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.0. Reproducibility

+

What is reproducibility in MLOps?

+

Reproducibility in MLOps means being able to reliably recreate the results of an AI/ML experiment or workflow. This capability is crucial for validating findings, debugging models, and ensuring consistent behavior across different environments and over time. Reproducibility helps build trust and transparency in AI/ML projects, allowing for independent verification and accelerating future development by providing a stable foundation to build upon.

+

Why is reproducibility important in MLOps?

+

Reproducibility is a cornerstone in any scientific endeavor, and machine learning is no exception. It ensures that results are not due to chance or specific environmental configurations. This rigor builds trust in the models, making them more reliable for deployment. Additionally, reproducibility is crucial for debugging and fixing issues. If a model's performance degrades unexpectedly, having a reproducible setup allows you to isolate the changes that caused the issue and quickly restore the model's effectiveness.

+

How can you implement reproducibility in your MLOps projects?

+

Implementing reproducibility in MLOps projects necessitates a combination of tools and practices:

+
    +
  • Code Versioning: Utilizing tools like Git to track code changes and revert to specific versions allows you to precisely reproduce the code that generated particular results. This is essential for understanding the evolution of a model and recreating previous experiments.
  • +
  • Environment Management: Ensuring that the environment (e.g., Python version, libraries, dependencies) in which an experiment is conducted is consistent is vital. Employing tools like Docker or Poetry to encapsulate dependencies and manage environments promotes consistency and portability.
  • +
  • Dataset Versioning: Tracking changes to the dataset used for training or evaluation is crucial. This could involve storing multiple versions of the dataset or logging metadata about the dataset's source with MLflow Data.
  • +
  • Randomness Control: Inherently, AI/ML tasks often involve randomness in model initialization, data shuffling, or algorithm execution. To achieve reproducibility, you must control this randomness by fixing random seeds, which ensures that random number generators produce the same sequence of numbers, thereby leading to consistent results.
  • +
  • Experiment Tracking: Employing tools like MLflow to log experiment parameters, metrics, and artifacts allows you to systematically document your experiments. This meticulous logging enables you to review past experiments, compare results, and identify the precise configurations that led to certain outcomes.
  • +
+

How can you fix randomness in AI/ML frameworks?

+

By setting a specific seed, you ensure that the generator always produces the same sequence of "random" numbers, leading to consistent results across different executions of your code, even if those executions occur on different machines or at different times.

+

Here is how you can fix the randomness in your project for several popular machine learning frameworks.

+

Python

+
import random
+
+random.seed(42)
+
+

NumPy

+
import numpy as np
+
+np.random.seed(42)
+
+

Scikit-learn

+
from sklearn.ensemble import RandomForestRegressor
+
+model = RandomForestRegressor(random_state=42)
+
+

PyTorch

+
import torch
+
+torch.manual_seed(42)
+
+

You can also fix the randomness for CUDA operations by using:

+
if torch.cuda.is_available():
+    torch.cuda.manual_seed_all(42)
+
+

For additional reproducibility in multi-GPU environments, consider setting:

+
torch.backends.cudnn.deterministic = True
+torch.backends.cudnn.benchmark = False
+
+

TensorFlow

+
import tensorflow as tf
+
+tf.random.set_seed(42)
+
+

How can you use MLflow Projects to improve the reproducibility of your project?

+

MLflow Projects is a component of MLflow that provides a standard format for packaging data science code in a reusable and reproducible way. An MLflow Project is defined by an MLproject file that specifies the project's dependencies, environment, and entry points. This standardized format makes it easier to share and execute projects across different environments and platforms, promoting both collaboration and consistency in project execution.

+

Defining an MLflow Project

+

To define an MLflow project, you can create an MLproject file in your project's root directory. This file uses YAML syntax to define the project structure. Below is an example of an MLproject file that specifies the project name, environment, and entry point:

+
# https://mlflow.org/docs/latest/projects.html
+
+name: bikes
+python_env: python_env.yaml
+entry_points:
+  main:
+    parameters:
+      conf_file: path
+    command: "PYTHONPATH=src python -m bikes {conf_file}"
+
+

In this example:

+
    +
  • name defines the project name as "bikes".
  • +
  • python_env specifies the path to the python environment file.
  • +
  • entry_points defines entry points, which specify how to run parts of the project.
      +
    • main is an entry point that accepts one parameters: conf_file as a file path.
    • +
    • The command specifies how to execute the entry point, which in this case runs the bikes module with the provided parameters.
    • +
    +
  • +
+

Executing an MLflow Project

+

To run an MLflow Project:

+
mlflow run --experiment-name=bikes --run-name=Training -P conf_file=confs/training.yaml ."
+
+

This command instructs MLflow to run the current directory (.) as a project. The -P flag allows you to pass parameters to the entry points defined in your MLproject file. In this case, it passes confs/training.yaml as the main configuration file.

+

Benefits of Using MLflow Projects

+
    +
  • Simplified Sharing: It's easier to share and distribute projects.
  • +
  • Consistent Execution: Ensures consistent execution across different environments.
  • +
  • Reduced Setup Time: Minimizes the time and effort required to set up and run projects.
  • +
  • Collaboration: Facilitates collaboration among team members.
  • +
+

By leveraging MLflow Projects, you can significantly enhance the reproducibility of your MLOps projects, making it easier to share, execute, and validate your experiments, contributing to the overall robustness and trustworthiness of your ML solutions.

+

Reproducibility additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/1. Monitoring.html b/7. Observability/1. Monitoring.html new file mode 100644 index 0000000..9c63ffd --- /dev/null +++ b/7. Observability/1. Monitoring.html @@ -0,0 +1,2793 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.1. Monitoring - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.1. Monitoring

+

What is AI/ML Monitoring?

+

AI/ML Monitoring is the continuous process of overseeing the performance, behavior, and health of machine learning models in production environments. It's an essential aspect of MLOps that extends beyond the initial training and deployment stages, ensuring that models remain effective and reliable throughout their operational lifecycle. Effective monitoring involves several key tasks, including:

+
    +
  • Tracking Metrics: Capturing and analyzing key performance indicators (KPIs) that reflect the model's accuracy, precision, recall, and other relevant metrics over time.
  • +
  • Setting up Alerts: Establishing trigger mechanisms that alert stakeholders when specific conditions are met, such as a significant drop in model accuracy or the detection of data drift.
  • +
  • Gaining Insights: Providing a means to understand and diagnose issues through visualizations, logs, and other diagnostic tools.
  • +
+

Effective AI/ML monitoring helps prevent model decay, which occurs when model performance deteriorates over time, by quickly identifying issues, enabling timely interventions. It is crucial for both technical teams and business stakeholders to maintain confidence in the accuracy and value of AI/ML solutions.

+

How does AI/ML Monitoring differ from Traditional Software Monitoring?

+

While AI/ML monitoring shares similarities with traditional software monitoring, it also presents unique challenges:

+
    +
  • Non-Deterministic Behavior: Machine learning models can exhibit unpredictable behavior due to their reliance on data patterns. These patterns may change over time, leading to performance degradation or unexpected outputs.
  • +
  • Complex Dependencies: AI/ML applications often depend on multiple external factors, including data sources, feature engineering pipelines, and serving infrastructure. Monitoring must encompass all these dependencies to identify potential sources of issues.
  • +
  • Black Box Nature: The internal workings of some machine learning models can be opaque, making it harder to directly diagnose the reasons for incorrect predictions or behavior changes.
  • +
+

These unique challenges call for specialized tools and strategies, as traditional monitoring systems may not be adequately equipped to address the complexities inherent in AI/ML applications.

+

What are the Benefits of AI/ML Monitoring?

+
    +
  1. Early Problem Detection: By continuously monitoring metrics and setting up alerts, teams can detect problems such as model drift, data quality issues, or biases before they significantly impact business outcomes.
  2. +
  3. Improved Model Performance: Tracking metrics helps identify opportunities for model retraining, hyperparameter tuning, or other optimizations to enhance model performance over time.
  4. +
  5. Increased Reliability: Effective monitoring safeguards against model failures and downtime, ensuring that AI/ML solutions remain stable and dependable, maintaining business continuity.
  6. +
  7. Enhanced User Trust: By ensuring model accuracy and fairness, monitoring practices build trust and confidence in AI/ML solutions among users, promoting their adoption and use.
  8. +
+

AI/ML monitoring plays a crucial role in bridging the gap between model development and production, ensuring that these solutions remain valuable and reliable assets for businesses.

+

Which Metrics Should You Track for AI/ML Monitoring?

+

The metrics tracked for AI/ML monitoring should align with the specific objectives and performance requirements of the model:

+

Common Metrics

+
    +
  • Accuracy, Precision, Recall, F1 Score: These metrics assess the model's overall performance and its ability to correctly classify or predict outcomes.
  • +
  • Mean Absolute Error (MAE), Mean Squared Error (MSE), Root Mean Squared Error (RMSE): These metrics quantify the magnitude of prediction errors, crucial for regression problems.
  • +
  • Area Under the Receiver Operating Characteristic Curve (AUC-ROC): This metric evaluates the model's ability to distinguish between different classes, particularly useful for binary classification problems.
  • +
+

Business Metrics

+

Beyond technical metrics, aligning monitoring with business goals through relevant metrics is crucial:

+
    +
  • Conversion Rate, Customer Churn, Revenue Impact: These metrics measure the direct impact of the model on business outcomes, offering a clear view of its effectiveness.
  • +
+

Data Quality Metrics

+
    +
  • Data Drift, Missing Values, Outliers: Monitoring data quality metrics helps detect changes in input data distribution or format that could impact model performance.
  • +
+

How can you implement AI/ML Monitoring with MLflow?

+

The MLOps Python Package utilizes Mlflow's evaluate API for comprehensive model evaluation, including the validation of results with user-defined thresholds. This capability allows for a standardized approach to model monitoring, ensuring that model quality is consistently assessed and monitored.

+

Here's how you can implement this monitoring functionality:

+
    +
  1. +

    Define Metrics: First, define the metrics you want to track using a class from the bikes.core.metrics module. This class allows you to specify the metric name and whether a higher or lower score indicates better performance.

    +
    from bikes.core import metrics
    +
    +metrics = [
    +    metrics.SklearnMetric(name="mean_squared_error", greater_is_better=False),
    +    metrics.SklearnMetric(name="r2_score", greater_is_better=True),
    +]
    +
    +
  2. +
  3. +

    Set Thresholds (Optional): If you want to establish thresholds for specific metrics, use the Threshold class, again from bikes.core.metrics, to define the absolute threshold value and whether a higher or lower score is desired. These thresholds serve as benchmarks for model performance, potentially triggering alerts if violated.

    +
    thresholds = {
    +    "r2_score": metrics.Threshold(threshold=0.5, greater_is_better=True)
    +}
    +
    +
  4. +
  5. +

    Integrate with the EvaluationsJob: The EvaluationsJob in the bikes.jobs.evaluations module is responsible for loading the registered model, reading the evaluation dataset, and calculating the specified metrics. You can configure this job to use the defined metrics and thresholds.

    +
    from bikes import jobs
    +from bikes.io import datasets
    +
    +evaluations_job = jobs.EvaluationsJob(
    +    inputs=datasets.ParquetReader(path="data/inputs_test.parquet"),
    +    targets=datasets.ParquetReader(path="data/targets_test.parquet"),
    +    metrics=metrics,
    +    thresholds=thresholds,
    +)
    +
    +
  6. +
  7. +

    Execute the Job: Run the EvaluationsJob to compute the metrics and assess them against the thresholds. If any thresholds are violated, MLflow will raise a ModelValidationFailedException, which can be handled appropriately in your workflow.

    +

    with evaluations_job as runner:
    +    runner.run()
    +
    +Monitoring Charts

    +
  8. +
+

How to integrate AI/ML Monitoring to your data infrastructure?

+

You can use the Evidently library to generate interactive reports for model monitoring and analysis. Evidently supports data and target drift detection, model performance monitoring, and the creation of visual reports to understand changes and issues within an ML pipeline. It simplifies the tracking and analysis of changes in model behavior, making the monitoring process more efficient and effective.

+

Here are the steps to integrate Evidently into your Jupyter notebooks for model monitoring:

+
    +
  1. Install the Evidently library:
  2. +
+
pip install evidently
+
+
    +
  1. Import necessary modules:
  2. +
+
import pandas as pd
+from evidently.report import Report
+from evidently.metric_preset import DataDriftPreset
+
+
    +
  1. Load your reference and current data: These datasets represent the data the model was trained on (reference) and the data the model is currently making predictions on (current).
  2. +
+
reference_data = pd.read_csv('reference.csv')
+current_data = pd.read_csv('current.csv')
+
+
    +
  1. Generate an Evidently report:
  2. +
+
report = Report(metrics=[DataDriftPreset()])
+report.run(reference_data=reference_data, current_data=current_data)
+report.show() # or report.save_html('my_report.html')
+
+

This example will generate a report highlighting any data drift between the reference and current datasets, which can be crucial in identifying why a model's performance might be declining.

+

What are the Best Practices for AI/ML Monitoring?

+
    +
  1. Establish Clear Monitoring Goals: Define the objectives of your monitoring efforts, which might include detecting drift, maintaining performance, or ensuring fairness.
  2. +
  3. Choose Relevant Metrics: Select metrics that align with the specific objectives of your model and your business goals.
  4. +
  5. Set up Meaningful Alerts: Design alerts that are actionable and relevant, avoid alert fatigue, and ensure timely responses to critical issues.
  6. +
  7. Integrate Monitoring into CI/CD Pipelines: Incorporate monitoring steps into your continuous integration and continuous deployment (CI/CD) workflows to ensure that changes are thoroughly evaluated and monitored.
  8. +
  9. Visualize Results: Utilize visual dashboards and reports to present monitoring results in an easily understandable format for both technical and business stakeholders.
  10. +
  11. Regularly Review and Update: Periodically review your monitoring setup and adjust metrics, thresholds, and alerts as needed based on experience and changing requirements.
  12. +
+

Monitoring Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/2. Alerting.html b/7. Observability/2. Alerting.html new file mode 100644 index 0000000..9a419fa --- /dev/null +++ b/7. Observability/2. Alerting.html @@ -0,0 +1,2664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.2. Alerting - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.2. Alerting

+

What is AI/ML Alerting?

+

AI/ML Alerting is a crucial aspect of monitoring that involves sending notifications to relevant stakeholders when specific conditions are met, signifying potential issues or deviations in the behavior or performance of a deployed machine learning model. These alerts act as early warning systems, enabling timely intervention and mitigation of problems before they significantly impact business outcomes or user experience.

+

Effective alerting relies on:

+
    +
  • Defining Alert Conditions: Establishing clear criteria that trigger alerts, such as significant drops in model accuracy, exceeding error thresholds, or the detection of data drift.
  • +
  • Identifying Recipients: Specifying individuals or teams responsible for responding to alerts, ensuring that notifications reach the right people.
  • +
  • Selecting Communication Channels: Choosing appropriate methods for delivering alerts, whether it's email, Slack messages, system notifications, or specialized alerting platforms.
  • +
+

Why do you need Alerting for AI/ML?

+

Alerting plays a critical role in maintaining the reliability and value of AI/ML solutions in production environments. Its main benefits include:

+
    +
  1. Rapid Response to Issues: Alerts provide a mechanism for quickly informing stakeholders of problems, reducing the time it takes to diagnose and mitigate them.
  2. +
  3. Proactive Issue Mitigation: By detecting issues early, alerting systems enable proactive interventions, preventing minor problems from escalating into major disruptions or failures.
  4. +
  5. Improved Decision Making: Alerts can provide valuable data and insights that help teams make informed decisions on model retraining, hyperparameter adjustments, or other optimizations.
  6. +
  7. Increased System Uptime: By minimizing downtime and ensuring model accuracy, alerting contributes to the overall stability and reliability of AI/ML applications.
  8. +
+

Effective AI/ML alerting acts as a safety net, safeguarding against model decay and unforeseen circumstances that could impact the performance or behavior of deployed models.

+

Which conditions should trigger an alert?

+

Deciding which conditions should trigger an alert involves considering the specific context of the model and its impact on business goals. Here are some common scenarios:

+
    +
  • Performance Degradation: When a model's accuracy, precision, recall, or other key metrics drop significantly below predefined thresholds.
  • +
  • Data Drift: When the distribution or characteristics of the input data change significantly compared to the data used for training the model, which might signal a need for retraining.
  • +
  • Bias Detection: When the model demonstrates bias towards certain groups or categories, which could lead to unfair or unethical outcomes.
  • +
  • System Errors: When errors or exceptions occur within the model serving infrastructure or dependencies, potentially impacting service availability.
  • +
  • Resource Utilization: When the model's resource consumption, such as CPU, memory, or network bandwidth, exceeds predefined limits, which might signal performance bottlenecks.
  • +
+

Which platforms can send alerts?

+

To implement an effective alerting system, you need to choose communication channels that align with your team’s workflow and preferences. Here are some common options for sending AI/ML alerts:

+
    +
  • Slack and Discord: Suitable for real-time team communication, these messaging platforms allow for instant notifications, discussions, and collaboration among team members.
  • +
  • Datadog: A popular monitoring and observability platform, it provides comprehensive alerting capabilities for various system and application metrics, including those related to AI/ML models.
  • +
  • Statuspal: This platform specializes in status page monitoring and incident communication, making it useful for notifying users about any disruptions or downtime related to AI/ML services.
  • +
  • PagerDuty: A popular incident management platform that can be used for routing AI/ML alerts to the right team members, escalating issues if necessary, and ensuring that incidents are addressed promptly.
  • +
+

How can you implement Alerting (local demo)?

+

The MLOps Python Package includes a simple alerting service based on the plyer library, which provides cross-platform notifications. This service can be used to send alerts to the user's desktop, offering a basic but effective way to notify developers about events during model training, tuning, or deployment.

+

Here's how you can use the alerting service:

+
    +
  1. +

    Configure the AlertsService: Set the enable parameter to True to activate the alerting feature. You can also customize the app_name and timeout parameters as needed.

    +
    from bikes.io import services
    +
    +alerts_service = services.AlertsService(enable=True, app_name="Bikes", timeout=5)
    +
    +
  2. +
  3. +

    Integrate with a Job: Include the alerts_service as a parameter within a job definition. For instance, you can add it to the TrainingJob to receive a notification when model training is complete.

    +
    from bikes import jobs
    +
    +training_job = jobs.TrainingJob(
    +    ...,
    +    alerts_service=alerts_service,
    +)
    +
    +
  4. +
  5. +

    Send Alerts: Use the alerts_service.notify() method within the job's run() method to trigger notifications based on specific conditions.

    +
    # Within the TrainingJob's run() method
    +# ... (training logic)
    +self.alerts_service.notify(title="Training Complete", message=f"Model version: {model_version.version}")
    +
    +
  6. +
  7. +

    Disable for Production: Remember to disable desktop notifications (enable=False) in production settings to avoid disrupting users. Instead, integrate with more appropriate alerting platforms as mentioned earlier.

    +
  8. +
+

What are the Best Practices for AI/ML Alerting?

+
    +
  1. Prioritize Alerts: Categorize alerts by severity to ensure that critical issues receive immediate attention, while less urgent notifications can be handled later.
  2. +
  3. Provide Context: Include relevant information in alert messages, such as the affected model, specific metrics, and potential causes.
  4. +
  5. Avoid Alert Fatigue: Strike a balance between informing stakeholders and overwhelming them with too many notifications.
  6. +
  7. Automate Actions: Where possible, automate responses to alerts, such as triggering model retraining, scaling infrastructure, or rolling back to a previous model version.
  8. +
  9. Regularly Review and Update: Periodically assess the effectiveness of your alerting system and adjust alert conditions, recipients, and channels as needed based on feedback and experience.
  10. +
+

Alerting Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/3. Lineage.html b/7. Observability/3. Lineage.html new file mode 100644 index 0000000..c8cde1b --- /dev/null +++ b/7. Observability/3. Lineage.html @@ -0,0 +1,2760 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.3. Lineage - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.3. Lineage

+

What is Data and Model Lineage?

+

Data and Model Lineage in Machine Learning Operations (MLOps) refers to the comprehensive tracking of data and model origins, transformations, and dependencies throughout the machine learning lifecycle. It provides a historical record of how data flows through various stages, from its initial source to the final model outputs, which is crucial for understanding, debugging, and ensuring the reliability of AI/ML systems.

+

Lineage tracking encompasses:

+
    +
  • Data Origin: Identifying the initial source of data, including databases, APIs, or files.
  • +
  • Data Transformations: Recording all processing steps applied to the data, such as cleaning, feature engineering, and aggregations.
  • +
  • Model Training: Capturing details about the model training process, including hyperparameters, training data used, and resulting model versions.
  • +
  • Model Deployment: Tracking where and how the model is deployed, including serving infrastructure and deployment timestamps.
  • +
+

Why do you need Data and Model Lineage?

+

Lineage information offers multiple benefits for managing and deploying machine learning models effectively:

+
    +
  1. Debugging and Troubleshooting: When an issue arises, lineage information helps trace the origin of the problem by providing a clear picture of the data flow and model dependencies.
  2. +
  3. Data and Model Governance: It enables auditing and compliance by providing a transparent record of data usage, transformations, and model versions.
  4. +
  5. Reproducibility: It enhances reproducibility by documenting the exact steps involved in creating a model, making it easier to recreate results or validate findings.
  6. +
  7. Impact Analysis: Lineage data can be used to assess the impact of changes to data or models on downstream applications or business processes.
  8. +
+

What are the main Data Lineage use cases?

+

Data Lineage provides critical insights into the flow and usage of data within your ML projects, which can be invaluable for various purposes. Here are some common use cases:

+

Data Discovery

+

Lineage information makes it easier to discover and locate relevant datasets within your MLOps system. By tracing the lineage of models or predictions, you can identify the source datasets used, understand their characteristics, and explore their relationships with other datasets. This discovery process simplifies finding the right data for new projects or validating the suitability of existing datasets for specific tasks.

+

Impact Analysis

+

Understanding the impact of changes to data or models on downstream processes is crucial for maintaining system reliability. Lineage information facilitates this analysis by providing a visual representation of data dependencies. For example, if you make changes to a feature engineering step, you can use lineage tracking to determine which models rely on those features, allowing you to assess the potential impact on their performance and take necessary actions, such as retraining or retesting affected models.

+

Data Governance and Compliance

+

Meeting regulatory requirements and maintaining data quality often necessitate a clear understanding of data usage and its transformations. Lineage tracking offers a robust audit trail for data provenance, which can be used for compliance reporting and to ensure adherence to data governance policies. For example, tracking the lineage of personal data used in ML models can help demonstrate compliance with privacy regulations like GDPR, ensuring that data is processed and used according to established guidelines.

+

How to Implement Lineage Tracking with MLflow Datasets?

+

The MLOps Python Package leverages MLflow's Dataset API to log dataset information during the execution of various jobs, such as training or tuning. This provides a streamlined method for integrating lineage tracking directly into the MLflow tracking system, ensuring that dataset details are captured alongside model metrics and parameters.

+

Implementing Lineage Tracking in the MLOps Python Package

+

Here's how lineage is implemented within the MLOps Python Package:

+
    +
  1. +

    Defining Dataset Readers: Within the bikes.io.datasets module, the Reader class serves as an abstract base class for all dataset readers. It defines the lineage() method, which is responsible for generating lineage information.

    +
    import abc
    +
    +class Reader(abc.ABC):
    +    @abc.abstractmethod
    +    def lineage(
    +        self, name: str, data: pd.DataFrame, targets: str | None = None, predictions: str | None = None,
    +    ) -> Lineage:
    +
    +
  2. +
  3. +

    Implementing Lineage in Concrete Readers: Concrete readers, such as the ParquetReader, implement the lineage() method, utilizing the mlflow.data.pandas_dataset.from_pandas function to create an MLflow Dataset object. This object captures the dataset's name, source, schema, and profile information.

    +
    import mlflow.data.pandas_dataset as lineage
    +import pandas as pd
    +
    +class ParquetReader(Reader):
    +    # ... (other methods)
    +
    +    @T.override
    +    def lineage(
    +        self, name: str, data: pd.DataFrame, targets: str | None = None, predictions: str | None = None,
    +    ) -> Lineage:
    +        return lineage.from_pandas(
    +            df=data, name=name, source=self.path, targets=targets, predictions=predictions
    +        )
    +
    +
  4. +
  5. +

    Logging Lineage in Jobs: Jobs, like the TrainingJob or TuningJob, utilize the reader.lineage() method to generate lineage information for input datasets and then log this information to MLflow using mlflow.log_input().

    +
    # Within the TrainingJob's run() method
    +inputs_lineage = self.inputs.lineage(data=inputs, name="inputs")
    +mlflow.log_input(dataset=inputs_lineage, context=self.run_config.name)
    +
    +
  6. +
+

Visualizing Lineage in MLflow

+

The MLflow UI provides a visual representation of lineage information, enabling users to see the flow of data from its source to the models and predictions. This view simplifies understanding how different datasets are used within the project and facilitates debugging by tracing the lineage of specific models or predictions.

+

Lineage Datasets

+

Enhancing Lineage Tracking

+

To further enrich lineage information, consider these practices:

+
    +
  • Log Transformations: During data preprocessing, include logging statements to record specific transformations applied. This can be done with custom tags or attributes within the lineage object.
  • +
  • Utilize Data Versioning: Version control systems for data, like DVC, can be integrated to track different dataset versions used for model training or evaluation.
  • +
  • Capture Feature Engineering Steps: Document the logic behind feature creation or selection to understand how features impact model outcomes.
  • +
  • Track Model Deployment Lineage: Integrate deployment platforms with your lineage tracking system to capture where and how models are deployed.
  • +
+

By embracing these practices, you can significantly improve the transparency and auditability of your AI/ML systems.

+

Lineage Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/4. Costs-KPIs.html b/7. Observability/4. Costs-KPIs.html new file mode 100644 index 0000000..e577a20 --- /dev/null +++ b/7. Observability/4. Costs-KPIs.html @@ -0,0 +1,2780 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.4. Costs and KPIs - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.4. Costs and KPIs

+

What are costs and KPIs in MLOps?

+

Costs in the context of Machine Learning Operations (MLOps) refer to the financial expenditures associated with developing, deploying, and maintaining machine learning models. These costs encompass various factors including:

+
    +
  • Infrastructure: This covers the costs of using cloud computing resources (e.g., virtual machines, storage, databases), on-premise servers, and specialized hardware like GPUs, essential for training and deploying models.
  • +
  • Data: Obtaining, cleaning, labeling, and storing data can incur significant costs. The size, complexity, and source of the data influence these costs.
  • +
  • Personnel: Hiring and managing data scientists, machine learning engineers, data engineers, DevOps professionals, and other personnel involved in the MLOps lifecycle constitutes a significant portion of costs.
  • +
  • Software and Tools: Licensing fees for machine learning libraries, frameworks, data processing tools, monitoring platforms, and other software tools contribute to overall costs.
  • +
  • Operational Expenses: Day-to-day running costs, such as electricity, network bandwidth, security measures, and maintenance for hardware and software, fall under operational expenses.
  • +
+

Key Performance Indicators (KPIs) are metrics used to assess the success and effectiveness of MLOps projects. These KPIs vary based on the project's goals but generally include:

+
    +
  • Model Accuracy: Metrics like precision, recall, F1-score, and AUC measure how well the model predicts outcomes.
  • +
  • Model Performance: This covers metrics related to the model's speed, such as inference latency and throughput, crucial for real-time applications.
  • +
  • Data Quality: Metrics like data completeness, accuracy, and timeliness assess the reliability of the data used to train and evaluate models.
  • +
  • Deployment Efficiency: This measures how smoothly and rapidly models are deployed, including metrics like deployment time and frequency.
  • +
  • Operational Stability: Uptime, error rates, and resource utilization track the stability and reliability of deployed models.
  • +
  • Business Impact: These metrics directly measure the model's impact on the business, such as increased revenue, reduced costs, improved customer satisfaction, or enhanced decision-making.
  • +
+

By monitoring and optimizing both costs and KPIs, organizations can ensure that their MLOps projects are not only technically successful but also financially viable and deliver tangible business value.

+

Why should you track costs and KPIs?

+

Tracking costs and KPIs is essential for several reasons:

+
    +
  • Optimize Resource Allocation: By understanding where costs are incurred, organizations can identify areas for improvement and make informed decisions about resource allocation, maximizing Return on Investment (ROI).
  • +
  • Measure Progress and Success: Tracking KPIs helps teams evaluate the progress and effectiveness of their efforts, ensuring they stay aligned with business objectives.
  • +
  • Identify Bottlenecks: Tracking performance metrics can help pinpoint bottlenecks in the MLOps pipeline, such as slow data processing, inefficient model training, or deployment delays, enabling targeted improvements.
  • +
  • Support Data-Driven Decision-Making: Accurate data on costs and performance metrics empowers teams to make data-driven decisions, driving continuous improvement and innovation.
  • +
+

How can you perform a back-of-the-envelope calculation?

+

Back-of-the-envelope calculations are a quick and simple way to estimate costs and KPIs, providing a preliminary understanding of the financial and performance aspects of an MLOps project. These estimates, while not precise, offer valuable insights for early planning and resource allocation decisions.

+

For example, to estimate the cost of using a cloud computing instance for model training:

+
    +
  1. Identify Resource Requirements: Determine the type of instance needed (e.g., CPU-based, GPU-based), the number of instances, and the duration of training time.
  2. +
  3. Obtain Pricing Information: Consult the cloud provider's pricing information to determine the hourly rate for the chosen instance type.
  4. +
  5. Estimate Total Cost: Multiply the hourly rate by the estimated training time and the number of instances to get an approximate cost.
  6. +
+

Similarly, you can estimate KPIs based on available data and historical information. For instance, if you have past data on model accuracy and deployment frequency, you can use these values to estimate the expected performance and deployment efficiency of a new model.

+

Back-of-the-envelope calculations can be refined as you gain more information about the project's specifics. Remember, these estimates are primarily for initial planning and should be revisited and updated as the project progresses.

+

How can you find good KPI metrics for your MLOps project?

+

Identifying appropriate KPIs for your MLOps project involves aligning them with your business goals:

+
    +
  • Start with Business Objectives: Define clear business objectives that the MLOps project aims to achieve. For instance, if the goal is to improve customer satisfaction through personalized product recommendations, the relevant KPI might be click-through rate or conversion rate.
  • +
  • Consider the MLOps Lifecycle: Identify key stages in the MLOps lifecycle, such as data preparation, model training, deployment, and monitoring, and choose KPIs that reflect the performance and efficiency of these stages.
  • +
  • Balance Between Technical and Business Metrics: Include both technical metrics (e.g., model accuracy, latency) and business metrics (e.g., revenue impact, customer churn reduction) to gain a holistic view of the project's success.
  • +
  • Prioritize Actionable Metrics: Focus on KPIs that are actionable, meaning they can be used to make improvements or drive decision-making.
  • +
  • Regularly Review and Update KPIs: As the project evolves and business priorities change, revisit and update the selected KPIs to ensure they remain relevant and valuable.
  • +
+

Here's an example of aligning KPIs with business goals:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Business GoalMLOps KPIMeasurement
Increase online salesClick-through rate on product recommendationsNumber of clicks on recommendations divided by the number of times recommendations were shown
Reduce customer churnCustomer churn rate after implementing a churn prediction modelNumber of customers who churned divided by the total number of customers
Improve operational efficiencyModel deployment timeTime taken to deploy a new model version to production
+

How can you compute cost and KPI metrics from your MLflow server?

+

Computing technical costs and KPIs can be streamlined using experiment tracking tools like MLflow. The following notebook from the MLOps Python Package demonstrates how to extract valuable insights from an MLflow server, providing a foundation for understanding project performance and costs.

+

Notebook Code Breakdown

+

The provided notebook showcases how to retrieve and analyze data from an MLflow server to compute technical metrics related to experiments, runs, models, and versions:

+

1. Imports: The notebook begins by importing necessary libraries: mlflow for interacting with the MLflow server, pandas for data manipulation, and plotly.express for visualization.

+

2. Options and Configs: This section defines display settings for pandas dataframes and sets configuration parameters like MAX_RESULTS to limit the number of items retrieved and TRACKING_URI and REGISTRY_URI to specify the MLflow server location.

+

3. Clients: It establishes an MlflowClient object to communicate with the MLflow server based on the specified tracking and registry URIs.

+

4. Indicators: This is the core section where the code retrieves data about experiments, runs, models, and versions from the MLflow server. Each retrieval operation is followed by data processing and transformation steps using pandas:

+
    +
  • Experiments: It fetches experiment information, converts timestamps to datetime objects, and displays the first few rows of the dataframe.
  • +
  • Runs: Retrieves run data, including start and end times, tags, and other logged information, then prepares the data for analysis and visualization.
  • +
  • Models: Fetches information about registered models, converts timestamps to datetime objects, and removes the latest_versions column.
  • +
  • Versions: Gets data for model versions, extracts the first alias (if any), converts timestamps to datetime objects, and displays the first few rows.
  • +
+

5. Dashboards: This section demonstrates the use of plotly.express to create interactive visualizations that offer insights into the collected data:

+
    +
  • Experiment Creation Time: Plots a strip chart showing experiment creation times, categorized by lifecycle stage.
  • +
  • Model Creation Timestamp: Creates a strip chart displaying the creation timestamps of registered models.
  • +
  • Version Creation Timestamp: Generates a strip chart illustrating version creation timestamps, grouped by model name.
  • +
  • Run Run Time: Draw a strip chart of the MLflow run time per run name (training, tuning, ...).
  • +
+

Run Time

+
    +
  • Run Start Time: Plots a strip chart showcasing run start times, colored by experiment ID.
  • +
+

Run Start Time

+
    +
  • Run Estimator Class Distribution: Creates a bar chart showing the distribution of estimator classes used in runs.
  • +
+

Run Estimator Distributions

+

Interpreting Technical Cost and KPI Metrics

+

While this notebook focuses on retrieving and visualizing technical information from MLflow, it provides a basis for deriving cost and KPI metrics. Here's how you can interpret and extend the provided data to gain insights into costs and KPIs:

+
    +
  • Compute Usage: By analyzing run start and end times, you can estimate the duration of each run and the total compute time for a project. This information, combined with knowledge of your infrastructure costs, helps in estimating compute costs.
  • +
  • Data Processing Time: Further logging of data processing steps within runs can reveal how much time is spent on data-related tasks, providing insights for optimization.
  • +
  • Model Training Efficiency: Track the number of runs and their durations to understand how efficiently models are trained. This can identify bottlenecks and guide improvements in model development workflows.
  • +
  • Deployment Frequency: Analyze the creation timestamps of model versions to track deployment frequency, a key indicator of agility and responsiveness.
  • +
  • Model Performance Metrics: Retrieve logged metrics like accuracy, precision, recall, and AUC from runs to assess model performance. Compare these metrics across different runs and models to identify the best performing configurations.
  • +
+

Extending the Notebook for Cost Analysis

+

To delve deeper into cost analysis, you can:

+
    +
  1. Incorporate Infrastructure Costs: Introduce variables representing your infrastructure costs, such as the hourly rate for cloud computing instances or the cost of maintaining on-premise hardware.
  2. +
  3. Compute Resource Utilization: Calculate resource utilization by multiplying run durations by the cost of the used resources.
  4. +
  5. Aggregate Costs: Sum up the utilization costs across all runs to estimate the total cost of an experiment or project.
  6. +
  7. Visualize Cost Distributions: Create visualizations to show the breakdown of costs across different stages of the MLOps lifecycle.
  8. +
+

Implementing Business-Specific KPIs

+

Extending the notebook to track business-specific KPIs involves:

+
    +
  1. Define Relevant Metrics: Identify the key business metrics that align with your project's objectives.
  2. +
  3. Log These Metrics in MLflow: Use mlflow.log_metric() to record these metrics during your model training and evaluation runs.
  4. +
  5. Retrieve and Visualize Business KPIs: Retrieve the logged business KPIs from MLflow and create visualizations to analyze trends and identify improvements.
  6. +
+

By incorporating cost and KPI tracking into your MLflow workflows, you gain a comprehensive understanding of your project's performance, enabling informed decision-making and optimized resource allocation for maximum business impact.

+

Cost and KPI Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/5. Explainability.html b/7. Observability/5. Explainability.html new file mode 100644 index 0000000..ce05fc1 --- /dev/null +++ b/7. Observability/5. Explainability.html @@ -0,0 +1,2706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.5. Explainability - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.5. Explainability

+

What is Explainability in AI/ML?

+

Explainability in Artificial Intelligence and Machine Learning (AI/ML) refers to the ability to understand and interpret the reasoning behind a model's predictions or decisions. It's a crucial aspect of responsible AI, particularly as models become more complex and their applications extend to critical domains such as healthcare, finance, and law. Explainability promotes transparency, accountability, and trust in AI systems by making their internal workings accessible to humans, enabling better evaluation of their fairness, bias, and potential risks.

+

Why do you need Explainability in AI/ML?

+
    +
  • Build trust in AI systems: Explainable AI promotes confidence in model predictions by providing insights into the factors influencing decisions.
  • +
  • Identify and mitigate bias: Explainability allows for the detection and correction of biases that may be embedded in data or amplified by models.
  • +
  • Improve model debugging and development: Understanding how a model works can aid in identifying errors or improving its performance.
  • +
  • Meet regulatory requirements: In regulated industries like banking and insurance, explainability is crucial for demonstrating compliance with fair lending or insurance practices.
  • +
+

What is the difference between Local and Global Explainability?

+

Explainability methods can be broadly categorized into two types based on their scope of interpretation:

+
    +
  • Local Explainability: Focuses on understanding individual predictions, providing insights into the specific factors influencing a single output. This approach answers questions like "Why was this loan application rejected?" or "Why did the model predict this patient is at high risk?"
      +
    • Example: SHAP (SHapley Additive exPlanations) is a widely used technique for local explainability. It assigns a contribution score to each feature for a given prediction, quantifying how each feature influences the outcome.
    • +
    +
  • +
  • Global Explainability: Seeks to understand the overall behavior of the model, providing insights into the general relationship between input features and predictions across the entire dataset. This helps answer questions such as "Which features are most important for the model's predictions overall?" or "How does the model generally behave for different customer segments?"
      +
    • Example: Model feature importance scores, often provided by tree-based models or linear models, represent a form of global explainability. These scores indicate the relative influence of each feature on the model's predictions, averaged across the entire dataset.
    • +
    +
  • +
+

Choosing between local and global explainability depends on the specific goals and questions you are trying to answer. In some scenarios, you might require both types of explanations to gain a complete understanding of your model's behavior and its predictions.

+

How can you implement Explainability with SHAP and other AI/ML frameworks?

+

The MLOps Python Package integrates SHAP (SHapley Additive exPlanations) to provide local explainability and Random Forest feature importances for global explainability. It includes a dedicated job, ExplanationsJob, that generates explanations for a registered model using a sample of data inputs.

+

Here's how the package implements explainability:

+
    +
  1. +

    explain_model(): This method, defined within the Model abstract base class in bikes.core.models, is responsible for providing global explainability. For the BaselineSklearnModel, it leverages the feature_importances_ attribute of the underlying RandomForestRegressor to generate a DataFrame outlining feature importances.

    +
    class BaselineSklearnModel(Model):
    +    # ... (other methods)
    +
    +    @T.override
    +    def explain_model(self) -> schemas.FeatureImportances:
    +        model = self.get_internal_model()
    +        regressor = model.named_steps["regressor"]
    +        transformer = model.named_steps["transformer"]
    +        column_names = transformer.get_feature_names_out()
    +        feature_importances = schemas.FeatureImportances(
    +            data={
    +                "feature": column_names,
    +                "importance": regressor.feature_importances_,
    +            }
    +        )
    +        return feature_importances
    +
    +
  2. +
+

Feature importances

+
    +
  1. +

    explain_samples(): This method, also within the Model base class, focuses on local explainability. For BaselineSklearnModel, it utilizes a SHAP TreeExplainer to compute SHAP values for a given set of input samples. These values are then stored in a DataFrame, enabling analysis of feature contributions to individual predictions.

    +
    class BaselineSklearnModel(Model):
    +    # ... (other methods)
    +
    +    @T.override
    +    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
    +        model = self.get_internal_model()
    +        regressor = model.named_steps["regressor"]
    +        transformer = model.named_steps["transformer"]
    +        transformed = transformer.transform(X=inputs)
    +        explainer = shap.TreeExplainer(model=regressor)
    +        shap_values = schemas.SHAPValues(
    +            data=explainer.shap_values(X=transformed),
    +            columns=transformer.get_feature_names_out(),
    +        )
    +        return shap_values
    +
    +
  2. +
+

Sample Explanations

+
    +
  1. +

    ExplanationsJob: This job, defined in bikes.jobs.explanations, orchestrates the process of generating explanations. It loads a registered model from MLflow, reads a sample of data, computes both model-level and sample-level explanations, and then writes these explanations to Parquet files.

    +
    class ExplanationsJob(base.Job):
    +    # ... (other attributes)
    +
    +    def run(self):
    +        # ... (logic for loading model, reading data, and generating explanations)
    +        # - models
    +        logger.info("Explain model: {}", model)
    +        models_explanations = model.explain_model()
    +        logger.debug("- Models explanations shape: {}", models_explanations.shape)
    +        # - samples
    +        logger.info("Explain samples: {}", len(inputs_samples))
    +        samples_explanations = model.explain_samples(inputs=inputs_samples)
    +        logger.debug("- Samples explanations shape: {}", samples_explanations.shape)
    +        # ... (write explanations to files)
    +
    +
  2. +
+

Integrating Explainability into Your Workflow

+

You can incorporate explainability into your MLOps workflow by executing the ExplanationsJob after model training or deployment. The resulting explanation files can then be used for various purposes:

+
    +
  • Model Understanding: Analyze feature importances to understand the key drivers of the model's predictions.
  • +
  • Bias Detection: Identify features that disproportionately influence predictions for specific groups or categories.
  • +
  • Debugging and Improvement: Gain insights into individual predictions that may be erroneous or unexpected, guiding model refinements.
  • +
  • Communication with Stakeholders: Present explanations to business users or regulators to build trust and transparency in model decisions.
  • +
+

Which sectors are impacted by Explainable AI?

+

Explainable AI is especially relevant in sectors where the consequences of model decisions are significant or where regulatory compliance mandates transparency and accountability:

+
    +
  • Banking and Finance: For applications such as loan approvals, credit scoring, or fraud detection, explainability helps ensure fairness and avoid discriminatory practices.
  • +
  • Insurance: Explainable AI is crucial for setting premiums, assessing risk, and handling claims in a way that is transparent and justifiable.
  • +
  • Healthcare: For medical diagnosis, treatment recommendations, or patient risk assessment, explainable AI promotes trust and understanding of model-driven decisions.
  • +
  • Law Enforcement: In scenarios where AI is used for predictive policing or criminal justice applications, explainability is crucial for ethical and legal considerations.
  • +
+

Explainability Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/6. Infrastructure.html b/7. Observability/6. Infrastructure.html new file mode 100644 index 0000000..1c25291 --- /dev/null +++ b/7. Observability/6. Infrastructure.html @@ -0,0 +1,2615 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.6. Infrastructure - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

7.6. Infrastructure

+

What is Infrastructure Monitoring in MLOps?

+

Infrastructure Monitoring in MLOps involves the continuous tracking and analysis of hardware resources and their performance metrics during the execution of machine learning workloads. This encompasses a range of aspects including CPU utilization, memory consumption, network bandwidth usage, disk I/O, and GPU performance (if applicable). The data collected from infrastructure monitoring provides valuable insights into the efficiency and stability of your MLOps pipeline.

+

Why do you need Infrastructure Monitoring for AI/ML?

+

Monitoring the performance and utilization of your hardware infrastructure is crucial for several reasons:

+
    +
  • Resource Optimization: Identify bottlenecks and areas of over-utilization or under-utilization to optimize resource allocation, maximizing efficiency and minimizing costs.
  • +
  • Performance Tuning: Pinpoint performance issues related to hardware limitations or resource contention to enhance model training and inference speeds.
  • +
  • Capacity Planning: Forecast future infrastructure needs based on trends in resource consumption, allowing for proactive scaling to accommodate growing workloads.
  • +
  • Cost Management: Track resource usage to accurately estimate and manage costs associated with running AI/ML workloads on cloud platforms or on-premise infrastructure.
  • +
+

Effective infrastructure monitoring can lead to significant cost savings, improved model training and deployment times, and a more stable and predictable operational environment for your AI/ML solutions.

+

How can you implement Infrastructure Monitoring with MLflow?

+

The MLOps Python Package leverages the MLflow system metrics module to capture and log hardware performance metrics during the execution of jobs. These metrics provide insights into the resource utilization of the system during critical operations such as model training or inference, which is crucial for understanding the resource demands of different models and tasks.

+

Here's how you can integrate infrastructure monitoring into your project using MLflow:

+
    +
  1. +

    Enable System Metrics Logging: Configure the MlflowService within your job to enable system metrics logging. This is done by setting log_system_metrics=True in the RunConfig.

    +
    from bikes.io import services
    +
    +run_config = services.MlflowService.RunConfig(
    +    name="Training", log_system_metrics=True
    +)
    +
    +training_job = jobs.TrainingJob(
    +    ...,
    +    run_config=run_config,
    +)
    +
    +
  2. +
  3. +

    Execute Your Job: Run your job as usual, and MLflow will automatically capture system metrics during execution.

    +
  4. +
  5. +

    View Metrics in the MLflow UI: Access the MLflow UI to see the logged system metrics. These metrics provide information about CPU utilization, memory usage, and other resource-related details, allowing you to assess the hardware demands of your training or inference tasks.

    +
  6. +
+

System Metrics

+

Customizing Infrastructure Monitoring with MLflow

+

MLflow's system metrics module offers customization options, allowing you to:

+
    +
  • Adjust the Collection Interval: Control the frequency at which metrics are sampled.
  • +
  • Select Specific Metrics: Choose which metrics to track, depending on your analysis needs.
  • +
  • Integrate with Other Tools: Export the logged metrics to external monitoring systems for more comprehensive analysis and visualization.
  • +
+

What are Alternative Solutions for Infrastructure Monitoring?

+

Beyond MLflow, specialized monitoring platforms offer comprehensive solutions tailored for production infrastructure setups:

+
    +
  • Datadog: A popular cloud-based monitoring platform that offers a wide range of integrations and visualization tools, including those for AI/ML applications.
  • +
  • Prometheus: An open-source monitoring system that excels at collecting and storing time series data, ideal for tracking resource utilization metrics over time.
  • +
  • Grafana: A visualization and dashboarding tool that can integrate with multiple data sources, including Prometheus, to create informative dashboards for monitoring your MLOps infrastructure.
  • +
+

Infrastructure additional resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/7. Observability/index.html b/7. Observability/index.html new file mode 100644 index 0000000..721d4d6 --- /dev/null +++ b/7. Observability/index.html @@ -0,0 +1,2416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7. Observability - MLOps Coding Course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

7. Observability

+

Observability in Machine Learning Operations (MLOps) is crucial for gaining insights into the performance, behavior, and health of AI/ML models and their supporting infrastructure in production environments. It encompasses practices and tools that enable teams to understand how models are performing, detect issues early on, and make data-driven decisions to optimize and maintain these systems. This chapter delves into several key aspects of observability, equipping you with the knowledge and strategies to build more reliable and effective MLOps pipelines.

+
    +
  • 7.0. Reproducibility: Explore how to make machine learning experiments and pipelines more reproducible using MLflow Projects, enabling others to verify findings, share knowledge, and build upon existing work.
  • +
  • 7.1. Monitoring: Learn the fundamental principles and tools for monitoring AI/ML models, focusing on tracking key metrics, setting up alerts, and understanding changes in model behavior using MLflow Evaluate API and Evidently.
  • +
  • 7.2. Alerting: Understand how to design effective alert systems to promptly notify stakeholders of potential issues with models or infrastructure using tools like Slack, Discord, Datadog, and PagerDuty.
  • +
  • 7.3. Lineage: Delve into data and model lineage, discovering how to track the origin and transformation of data and models throughout the ML lifecycle using MLflow Dataset.
  • +
  • 7.4. Costs and KPIs: Explore techniques for managing costs associated with running AI/ML workloads and for defining and tracking key performance indicators (KPIs) aligned with business goals, using MLflow Tracking for analysis.
  • +
  • 7.5. Explainability: Explore the concept of explainable AI, focusing on techniques like SHAP to understand model predictions and build trust in AI systems.
  • +
  • 7.6. Infrastructure: Discover the importance of infrastructure monitoring, learning how to track resource usage and performance metrics to optimize efficiency and costs through MLflow System Metrics.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..cc7099c --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +mlops-coding-course.fmind.dev diff --git a/assets/banner.png b/assets/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..d632bb14f5932162a2a852ce36baeca5c1def4cc GIT binary patch literal 542739 zcmeFYbyQqUvp0$pBxrDgTX1&`BtUSt;0y#A+-;Bq2@VNvNk|Cp?jg7ZcbyPqaChz= z@|<(tcinHTyUtqo`|q7(Gc$X4S9krYs=KPXCQ@Bh{xK#wCISM&V?_lyO#}oq6$AvN zy+>%k9poa7bl|PWS6km*)7*>3+11I~)&WH0?&A!i0eRb6BOrLsfz{_9chX?r*u0Nj zAu;Qwef+Uerl+jq<*UN%pR*#JU7qAQ=WG&8j~Eq84Go`}#WTe;GGfNIlFATs^NHqFTo+@u-@i>OPWGl-`C9UTJX;E(O7Fd^c z`7tM`LqrBG+%kVU5D*aG+RDnRE6U3L7fS#mnE?qB3Z2rFU8Y(}FIaIN)2+pS+O{Ae zaw*nfdHznFt$N!`@(NXjJn;#Md`Hd7N{_{4N0)&fx}FT)4T6iOv!;6j!F|dT!=K*H z3vpiOR9^cCr3O0uMa+nA8o}0@inJ-FL_f+Nnd503kfuBtNG0sz_wSjOKyqk_s$#!) zgTw~bm_3u0rcq@Hvn6!G8*!LJg9ccbJX2yl6ghmWNWu_k)wpv=0ZkmB9QZxtV4a=H zO#I52-K#=VP+-k=Ewym#!^pb0E}0Y0RH4V$0JXCp3Noe!)}llBdF2s|5qLCTVlfNL z@$+?F-5HXR}yVnA{(l$YZu)?;LGI? z=%4GPm(!<>{HXJBrIQ<9EhC_Zx9dk%P6sS4&M(fl+05PEBIMz@Hs%klj039YZ40QF zzKXJ_r4yLb+{(!U#OV!o26PVrL0rn)+1%0|bZ?J=-o2a)0-2+`w;4}O-7ah$* z6L)(FI(-#&8d)b-5Dh;kKPNZG3vXLbUOGuk8gW-EYf(+Pm;aCezDdx%ad&qX<>K=4 z^5XR3<8*Sh;o=by5#i$I<>KY#09tUk`8c|pdviFt(Zfmnr6C7$vvjp}cDHqMq=C~k zw{Y@sm!P8qp40r-{J_pCD*sL1(d{1;06e(7&7HY;IJvpNV6K1d;pYCr6Cm=>g#M2` z+_V8p<vm4ub$fA@FxaCLZK$I6lm%{A{&gZi_y4B*Z>|4}_lL$nD-{({IVVdG z`1BOzB3hw6-?4ittzfJWO(-v%Q1LAUaw0U>{2V7J}T~UILm-Fw$AMU6-n7dm89VFQGjQSN_f?__0b?eqWD8g4u^;(tZ1VCx2q@AGi$uSIErT>gIh`_;kr z!IWrd9*jcN-109AZswjKtA}|4wEjM_d}Hot0|Hi$f7JEA?%Vz!RKb#;*P73gpAS$4 zkR=B{kBBgb1+SGLhn1y;wS}++A1{x!^}kbhbFy~#GIs^Z*Z?>JSOMz!fE5kPgP@-M zduy*ZAh;9&z&N-CIQ|DP;eP<*`iEgI_?q!w9E)@Pzc3MhXz(wK4AAfIJz((y)-6D_gB{f%7hWUm@HD*uM)#aW%!HXAp%Wb7P zTs^%X7eGLeB#nf~>%Z)?{xLJ)%zetl&wto3PDT#?$~z`&KfI6ips~(fLL8Cy4Jw|) z!(fi~ke%^<1Vh7MxM`chc+MLQwOaKN(N7BHnKk3%5yXO7nSq(Y+-aUFbmDaoApVfX zd-OFBIVj^wr39C$CRXqihS)dZgcR{L5GwsdRwCIq7w3DIW)#mPX&IV*VY{{^n6X=T zMVQFjgs5I4nxuyT%7Db9%o~f*&D}?e$n{Fc7u$|-ML)tDuks*r+A5&9Fg^l!_1-lk z;6J=(hpr<#k=4nAt`J1F5ECm9Ot=-stXI-0#knYY`nr!R%T)zqhfYFi1SymR(eAmd zkPM`ml9jQ|)6s=nFCg|1qo$s%O^LY0KKrJmwxRnvIUa4~NogYWm`?;sU+$vnN68=G9fBi=U!bBq;Mq;)yb6W3@Q{F?QG<_}#%d21s1ky*=c-#mqKlhOa%;ib+ zRhl{y7tgyKI};10dCaiN5qCt*BvNbijY3>lT?i%_vg{V5iE11oi6){ZyjVTNW-RSr5TH*i9HhjrEH`ma(BB`ry;GPbLKQ@& zsBrjcPCxa*tc63^p{tT;#G%Wj$T`c9 zeTIm(l6FUW6}%N%gG!o#&V<`L74SUHJor`le1)}Gw*#ut`>!ZHjwpg=D$l6qU1nby zo3Ry_>Q%r2QE>W}LHi&YDi`CNSH0P3y$r;gQ9QSr952wmHcr0sC;?%$=dF4bP5!D$ zKB0PS?BCTs6+DYsj6M}&fK}XCiEpp;h*41E>33ZuUpS#HkdWSA5#N?VvG^3DtdjVB zNUtnOA9><&KO%jE{gW3#w)DNy+>mChIdUKkNTM8PP?Q}808leuP*nDoW0ED}fq=xZ zwbFy-HMUBUyBH0z}}tlcOS^mO#M zFH4Dr#shO|xw(I(J`HV;$HOm^5)>isiSFV>l7$rSVtpg+{fQgTBGa^^q;VttN?>M) zv^V%Vy zuf%wP+s=*M6Wg7R9E>jwp&{x0HmS?-ik{ZkpDoPgibdgkL1Ro%?odA8_>P(Z1HlnCy{<}8rI-F_tCl?;h2TQw1FIQ**m*~5unjSO<@|TdsrGH}!b-Nk z61=_%Q+<%&Su`-ma!EPi8C)oKw=;KgXK-gZcKZzX4Bqar;^*AR6Ys|&8W#GWNd*zG zcsvo4<0`Blk9uQZM}IZ^{QU?nf?kvSe?+k7VkU>z{D`)U+VO~6eB)8H1Su)n33A8< zhhk>zxi}!H6H%%_r4PdDNS;K=aW0wXicBN~Q6GY|aM{-8@Zl4(a?<$qFtNPb>0!o zBoAS6G2=L3N^MB}&0B1&7SQdfuMw5Q^#V@Es_xmFzxGpzKl}E}3$w7*nb3+eZZq+~ zX(O@{bOGt$$F|3u19ppI29L`z_22Sf9S-)G>ES<4>3IK2NAAG}2bC<~?+GT}U~%r} z4EorIw4~<>;$rxnv@53_`@RGGZ69=h$%#6re9i4!vN&uyVC_1>kM$w3Uk?3kx4k%2 z-fAND6|QJITZ|R(cLlCdsjLVNB(FzP(VeM^?W593rFG4MoL`Cf6L1zl&)!sW@$dey zrgRkrlv3#&Pb(L%FhReBn*P85d$i$-%_*KZu$<8J+`W)G?pRUdfUW~ zs&W=?XZEc8kv*}+4nefB%wm`d%l(6mxUVGY!*R(&-%gV8eS@7XggCg`?p-e1{sb{L zXQV{{&~E032}t>v4YU`d5eFLwLH(t{J6gax@D;xABp4eLz*Q*X1P^&U0_Dn#Abgbv zqjR)fAs69M&HmAh=ZdBwViL}B@b_M;<@bn2!6W5^ovTkB%xF&|HT6yN*Wh4|ay-yb z7)j#Ee%6iWYa7Wun{nDE2U&$)J2gcA)2&n4>YA4Y@`n^p_}o#r?ERdw1h_3`u$;h4E|{asrXNO%P0UK&$H{`@5D z$x6j+MjDwjV_fn|F0be~$dEl}^#Co~Crx@q)^4n}lbMGo4(O|J{?C@PvjH0o#-}sE z&-NpYl|UALycxQTno_FwBCo09!r1F0QrSvc+bRQz=KhC{E8h?NCU9npmh&E@wuAd3 z?Jb?G^Nx3Uevk<2W}M;22!)YVM2e19v_AskRT28Pb}U7@54z>9@^`kqlDerceCJpa z&q!Aou;meN)yw6qroHTI(Yfkuj)Mr;kAtUOhF;sjk~@-FiDKY$oo>T^P>xxw>8y8Y zdHFHjRa!w>L9)T(PuTIdW){GbD`~GID@wBtJ2AsFRg_3r=IX{Ju3`CH8Vxr;P_$9Lu6l9^kBt}e?awcjC-lb zlVur15mT`SbI=+;`)cL=&V-yb@iEraSgAx*UQ9Ougr zsVeIsP4kE8H~n-UYmW>fM1qEldU?os!W>H`GlHpVm2K<$kTX!Y&`cz}Jy;*K;g%+s z4iByt!{+T6p-G6Uc$6dp4#fDj`?M257#BUNTqq{w-U2HxG!EcuXSMv#{&1!)Th6fP zWboN6QemS!tIE&w&7I%%Z1XJcz)o_{MA(WV_vBHYZU5*et<0=9EmV3cUq|2>kF-fS^;~C+V0&n;bX9TD!@b|n>P2=ABt-w73RKVLgTcZ{7 z7zATyPsmgfJ0R7FKd7+(KME-(QgC- z+}TNss`8CeG~KcrXJe#n#Ady_vZb*&0r`Ey!B2W0`##J+?1oeJ0}G@IxZwo_aeGPY zM1Sf`?)8c_ep#Ihea2!++82a}EW0ENz+?o#9iW^_Ldlf1{DHXi<^6$=&|WRl31gi$nAsRx6f#UhlAQTqPk&peh(_+ zgtb{`{>-cB6!G|xYC^$JHrkddf4{*isb3N1v~+$iDt>!hXB-0)y;&(Cm{?W}(JEzTyTRgmqes(ZX=J(Ra#Cr|@wQIkeV2a*hj?^{sz1vbjCV$|Q zwozr>5`y7-->=KVWC9)2A(Pi4@@Jrzram9=F*)B5(S4Fc&O7)TRx zqH4QY=Sq|CtF?Ph!oMq3wo(3!&-*aA>0&bmQtb`t))8{)Jv3b#_bEG`HeJM#4$MIu zyt>Ts6kXoU5OiLCoWf7xIjMFpEpv~oAP2jg{b-XA(UaZ+xdfk&L4ND+wOXK!&e9_u8x+%rw>EjRtJZ1Z}Yc6(jDe>dsde6@TZb$aHI z#dk&QvT$#rq+(0vwCFkFIApn)vCnhyTd^1dyEQR5XOlD?>As92H^sdkizKx3A$ zY;SS>~Vvd7RdVBvfgBDkra&b6<5XMq-x!zR#84mf9lRZUO{2GHh&u4a!XJIp*o!#YjhIoZ~_pk7L3z3DSG&&GV#y0KP4yI(m?Z+m11qc(0;j zVq&K9+2B;!j4(+_+Idw*rDoQWk`~61JaW54?q$62`prxm-{2*=yJ! z<+XZblj(xSXFa&mP2u#LS?V&0$(u20MQV(<>e=$^=f)n42$A{&oEBl!u1{{mE^$+0#cy9P`5Z=C@8e3o zL89-bj-i;T7(fZ3g%dX6?W(lH48@Z7Tn7fRZaFeE1z3e*w zzNFu8KYd$1KYcrr4fzOMHHW8&%&H8jRI{*wlEPD%oQH9zDa5qk>bWr@=~P>Uc>q?% z@l%N-{U;HtWvk!3eRI z5j}(qZR$MsUwAwjeAJMIJ~Qmo(Jg)?L;&^@(nMf^Pway4ht)=PnCZoaHSfEkNZiI% zgm|2mn!8sU8XB4pyD2h#{-nM3oCS&B9CQwSGivtcDQiB$E2ZGts4otEGo((a(Dmo+6*4N=#I&B-UBUM z<jl-Wts3*i5htOI|xH1zv-fd|@T397QRd_o`9mB&|pCKST^}K;6m~Is(RC zvR*5SZZrf;HU|jXwaNDAv&cFH=%bHu^X#`X&^6KDSZrW3Lfy2+NWRf>X> z9rbG(Co2t;sBjYSF|>3wCf8bUb&J=F=lARdcdkj)Y_3Mb-TOqzVwe?}BwkN%jh)&% zU9Ghm9KQ>wE%3|E7?Kzfa8mT!m>yaEefXteeRSjUoWw{vO}dbxw0fdb&;u~Bu@V8M zD6gAj=DtSl)}*)n@cfGvNu$oMyz1%?NotY}T?a=;u6y-RoNWIiMOCTW6JnVpp=E#g zRn_GlMa;3L2Du{CGxi|L&-N`0VOo?PZU5vCYi&b1h$?DR-bx>aS>%87r(k4DasB0z zKL)uV6)e4}?mRA^(yX*2Yirt)QvCT@xpni^9W>eWV4Q9EjmJ%Mv-pzF#?zytqw*=& zlTrEOCBOaCelYB9h;ZrrXYnL4MXKFzFSvs!aITM?v$~V{SG?rn7rHtptAF@c;%t%zXJryo=^Vk161(rVE1K`J4=le5!6vJO#~r&1&XDWH@iJTzemdN4yPr zvSH0*?JN~b#+NxQdGX=i@AENlE(LPv`=hO-3>7eczYT>Tp}mcibF3vP-*>K0(m^$# z`58llswh!|l)})*9BiNELV~+px50(2T5j zO`n5~vfyrJ_17ME%@*5nrXWGLboVp&%KfjP^#du~Rt0Ml+H=UYJ-grjkZKm>r7G3P zq8B>)Q!m#1*Y1pvn>N_ln?SYuqieXWL`&I{zd#Pxti3xXhGIVz{QwB|KCqhL@tYeYY@ab`zn>Wfy=;wlIzpF^@yP9((qDi7WBEi2%*$bTRc29@z zbnj11B|o~wieU#4Q3$ymX5qU1iof%}U*jWP$j^0YE~s1RtSy}Do+in1XCP4%Y2+z; z5&GDuzX3YJRbkdOr<>w6j+$ThMnXeZqYG}$88Gh+NOtGX(auy?=tzUb++n8I0?Lnq zy`Hs06fdb`yCP$K6$w|$<)(U2{K*3K!pIfJypi$LcbNe16A0(VUE6KOJGN<;+lG8z zyOr42a@1u@?J>BLom09Y4p6l74Y3VK0Q zR#)=QYa#)hqNwhRPByp8+oJs)_qoU~;jROE`C6GUC#exblk9sQslX%ZpGhTB^|lTM zqj@i0h>!YFAMx*fsWF|AxA$T$-?~_q_8ixgNkYgitv|yErJi&$T<{dy=%gB!l{+mW z0c(*U*{+~wREtM`;cSAt)rQ+VGD+iVQcr_6vi(YLr(?RE=WO%w0T1kY`Mz}S*E^BY z0ik`LL`3@TqaCNfcB2uAsjY#CJe$gunvH@P&3>1S*ZoOe%gV*Aj&&P)4JFY;QGa-@ zdI?Fr2A`Nfwm%U=*$t~BrcEJPNvfielSO*RH^(FAbKqvr;U?`8E|tY`GMp@*RnB|^ z@B18==Q3khu4G<^2HH+6>*F?eqZfgv-DA4>N^FCc+o4iSU+RO1xQ47do2Uu;hAk$1 z^4(@WlB|Y>vfu1NhrUHlPw?K8#Kq5+MO2Us(^Q%A$S-myjc%xbpf@ij9A~>DY?%p5 z?3ViT0_Q!WEiX>CQf6B%9#<5SSE8MOP8v+t+N;!%SF)(@Yn2>A+gfYl+eSMCDr~gy(Z)A z5%7VCLh$%^<~hr(iHtDbO0R}&s_*ZPo3+B@hRxydnewk760jRFhds7_eQf7oK~J5% zgz@z1l0D})HAmt$?=yj^4~~|Dxve+n21%D^daBSYH9d{SmdaUvrRk4H?MnBK{sF@k zgyf+pO|D0uzBE{DA2v505bpVzn)zzuF}}3QjVN_!TFOWB(*uQzz~i@{Zm5ERAKDc`(nLLzc!HYMT5a>UJ#w$ zkZ`0kcke9(@@3LC{FH5tnQvtM(u*3*%&*7${NS42Ff%O@q&11?QOKN?Wk*xq` zwlY~oyfCPpc%FNdyn}1arM!$6jW2VmRf>mH|*TK4w# z=BnjL!d(#m4YB3biXJLxB(rCB6dm59`0VWA2z>hb^<<+|VHZ01PG~<>tO4Hq!P#<} zLg{~#;=R$u?ENQu{;Z!jWc5*>T&7U=r37DTb7bT=*Gh~H)#4YQQqwb@FLC0*lA+;s zSAo1~R{bBI!Pm=E*fij{`t1suod5AF_8WHsLEk>##Y|_v9Z*8}jU&~LoBqLZHL1tz zsj$S++&~`-=So%+mD#1Kmw3e8&ez$@+Y2s{i!rXMn_0c@v)Z+mMw9ZFCw(N(#NRxz z$m06Bcp8IA9rT+Bxf;J`)@0g}X=~VdO4({^MJKRC2JIXBqp`i}zc>q3)6&rD zDJyLaTFV?HF2ObNKc@$-FV>xRcS{z9u+NzgAOa9^5pTPlZ!==w9|FQQw~FPDrYxjy zyndn3tcd!b_TXwJysEzKcwZAuEg4UUO9n)y$UdMB=AmxW3Ku>$K}SGO7i8$?pYp@^ z+WYg3l=Oh^u_IQhwk;TC;K~Xy)Rbpe_{S$2slc5VCV@^a&;v&MSAZX!n!%ac-%})1 z?rLekoawd-Uq#K@*CBh?So@rdqpo|))B)XEb+0?V=Bun-dXZ>UmVgG5Lw1xMeK*U7 zj^3~v)?_oc`JwVL#cr%ZS{LaMP}QlUv_@hbC+t2~aJmhqi~I0O-EOIQi4N!7oiOh; z9}lY1JXV=|@7Mx|F$kCORyqt}jblJY!&nDk; z?!*(S+N@O{M@~*3UeDjz@9$!N!b5PoTb9i{jG;*LAN}Y@$zM=#)OMPkmbCmwID4bQ zb^R51Gl1d4$G-SyJ?d2j(d#33RkMD5jlRj^hjjAyBg$xMl3{9h3!VcxYqBq^en*n% z-@ZC_T(i*Gz1Hp(w`*Fk{1v<^z z4NDz9LVNPZ^Q!UoCQXk5ndvd$9DO9j4gRq+sA@bFvTkpP@=iC!5iyrLf4#E%DK|n; zLr%4LpntXN({Svk?W149JYW6#-HsSQwp11Yf+=O7D!1=%@9gh${Aq^bZYYJvt&i$9 zZW)&Qdqn+&+6y^Z-i?i<@p}r($C38~lHJxlJv$ze+8CAh3XIh+H=38>OCxsp@@}VXta3=Mj>5Bs^SD3~$v+>Iu*&d76 zDPu2k#tg0ZcG>ih zGq@81zh$>dOw#XpG6sssIlmX%{Z}o3sp);YDYw`qtH(>>pYg4zzpe8KosD1O930!D zyv`q=<{0hw`6M|x*Rl2cR&l8cdMqcI4oJXgfSa8{Kh=%V;St%Dl($}jSsA& zg|SG;b?Gpi&(pbW>O4h|V3y0sv>(U`0m-26^ON&ml~O9*x5i4_K6j%sKV9Je6WEx* zq2pplc*O*qmGEb?+%)SlJPFkZ>-Ks1)W}Q4id{YZO7O!9zJ@v zU!6{!$2W8bX|q>;bl-b!v&!gM7WMgLkP@Mgt=3l76W{hlU{MZY5|i^3;d!<9vI^rh z4hXd4F8P(3^HZRG($A%-+O%H*Czsl$WKPPpuJU`rV)m3tyR0U2ZINudbQ}L2=0=Yq%k^+o}fPHxqa2 zCdTvI*Xsgz2MdGII74;jfg)+PbNHfJhu1}3d>yCPW%t$9v3WYR~F@K+^~3FH%-P@PraP#Nr#N-dTG`rO7{uD z=BXQLz_FXR|CzWhcy8>IkEkk4&0=xFdA>U|DJ)q=k!!Z&b~I=BxN#XdO)-hdWk_Ip z$2~K}G%&4)%FjWHax2c!b4?$!Ce+Hz~aJP|+QO!KVT%sgQIlVO~DUxe;Dj@w#7 ztIAqG+Cz>>97SNxc5LUW@f}J`-q5nPjX^f2KLEf?hI-?xK6#*Z?zLZ$2<$!^rX{h2 znD0`>yf_J&lyi%Vqv>OXLu%}&?JW0S)PMqE@g+)nhutMo^G)b8C;Z*7g^`11ms^tx zJt%4rJ;!}|)M&)$V<63K^^;cnEgBxiAA0Tu;_jPv9J;ahM*5)gP=kR&6{GrHtiILx zB4OtFWi=oPfm)}1)zjWAXqX6iTh~w>ZLc>zmjzq3I#cdVM{#m=C%f}r`A{nmXct<; zyYjYU=-{4QUabGlqV=HeSyR&Cyb>b8?7Vi5?7kZX&GAA0RP8Q#ZN|x1lw2b^OdC%5<1Gdu0t+jv#r8yj_(40eap^*w z#F@Th^Wn=^N=lb|UTsPS1_mjHQW;mtjV_+I6Sl|UO6A$|Ih{8POrBhSc>6cCA}BC5 z+gdC}keGCZzK6a)M!4{vMEjYNk~R#!=LwybGVE8um1^oaR>DFh6UikFB4QjmKUAB# zI6cq(glw2~mfLZ6fUE1yzUg6E z!P$8R4Wc+S;$VWc7a_tB?HXACRgy5OyTV+XwaFO@IA4?(rgS`9+O1nw_4O4kYdJHt zgyA1F-CcCx2DSqmiJ>x}8yBN-fhg_JI4K~4daOSJU2+Qe2>wbT?0!U+=`%Ux2Q~n~ zq-+l7%Us)2Ww-de<*vP0r`JygO4o~vT9xRC*VUvNTZddyIE-E@Dq3Db%FrGAsfsTN%GrosT(M{`|ItC{XDw z^84eA*n^}l zuTo+e0mTMfx0(7HAGX#wJtwo>Kj3KcnQ$PI74$1pGZD5|n9KtGG=Ba3AGYL0zo1Z3~w*r=mX zMTyW7JH^EJ*+!-t&iFSKDB$v2ZjF6?LPEk9aOM6@IqkWD0hykO52$ME{rmUzD9q{BzO#oKwLNmfr2BhkoQg_YT0Z2mZP##WhZ)k-(UaAEa426V zqWaR4^Zqh)87V$HsVA5)mbPtQ+OEIOWx0+EwEp=LtZJmA(}gcj*)dU|1wY4#BH`L| zUC&BMk#}@-gu=`_!)ojsqk(+N?P8o^02f)@aX&<08I68e{N3Q)UEM;~MCZQQOVMzX z)5&fZ-PRM|A`U?yWmfESYmAm79;vl|X(|ahz z!D-kLzoHV16(}2_H#B&0*Gn5MY>9c8ZkR1`-=;VvPm%93P8Tf!5#8!SMX-9jInZG$ zMiK;gn@Mj#1CEiOiFAtnU;$`PAq+_nU%sgO3-*e1@+(#0RF{;@7w&$j9Vh%a;@A9t z?r&(DNhw{fpM(t7e+LT*g^UadQu-K~eutXq|5nGYL$tBuR6|eC9+fK6Aucg_uSAYl? z9#jlWr3hx64MsxA)Z@pC9YJzG2GawdLxmw3_dGC7;_stfRmGpV->!P7Cnj>9=}!1J;>Jb2Z1f#BBwx{ zw$`iX+ZHZ&x1+9r|I4`QL&@h!Dj!GnlAm~EukEI)tFriAj`*qCUc(hqAn)<(Z2wz{ zWH8HDCr3dN70LJ2&Jom7m%Q`y3Qux~FphhiOox+zlf@yxMm9}<>M>vUOqxnbWz2d0 zd6dXyLW$bmr(DD70vvY($EQmo`JW~gSthsh$Q_=q16DyNp;?EKu5W+Yuh$B9VC(O$ zU=62JMik3YT^8b}6I!n!x2GJgX%0)xX_17?M=kpSaRZ!9(LkJ&52V@WYh#-B8l&bN zn_}MZ7tZ~PVK+SxRJ;4l)arY@cyrEmH&|lxbhvixF?`><{LE$)E#*08)vcPoGaXsO z284132Xj~-oH%Lg>UT4RR;#g@Ox+o$47XVaX3OoK-TST~{t>?vW3Ah5q$rA^5IQ>O z#Pyo3X9N?;qq6V)8tZwd$yvUg2iav&%wll$Qm-3}pN0jYahmEiI%?V!|FGNGt(q|kb z1^`Fj9~a_okBFJeJ`uiz#ZW z5ecD2C@SBcdfW2Kr8A?K4`qKt16 zPsB?bH+Wjze+mORZal>rsy=00zQ7rp;Z*NO^0DMUAUD79S?#tFW~xg7*sjV~`X^!0nEN`Sz`U*yoc*~n4Yo~_~j zuE%KCQ#o%O_er<&jCpv42TXUW*kJtYv_N8x(i!5d;>46j&8H9ED!ZyZxFArxKrWXm zi^60h!FHYA&d1YN`&tUR_mV&D?PgnD!IqpvP+_Yxu5Wg?-;9UpGgDN~a_@c?+7~R9 zM_N%V505B^gR#u-R$UwnuxyVPb88 zILuVy@BK_uJE=|m>bivA&8D4j^>!Gt%<7I7vwN)vt@#5B(2R*r=M~2~L(ADII6IBl zT7UVu-4~asWH3GP^7XR1#PJjFE0c>ur&S1KmJp}+lXg1;lBzoM_tL~4P$`n7=@8hFkwfTNlQAAB=vfeZpGWjoa6twgmX*=IG(sz33aR} zJ)Q5reNDIfq^QswHuljkpv6w9PTi>SD`?i&qzO179Nqo+0bJ&A&KoC+Vlm*^9JneV zyx-R7WgN|%zA~4fWHp)dV zy^zae%lbm@nuS6`hEn#j0bYrjcWt_sTNdK3w4JRam*>^=r#Pv>s$Y(vkAY38R^31= z3QGFkwUSKxLmbb+#g5O-cEc=dvE#s@(MMsjm9UA%Jhs;Yb@en_K$yED%;Xy%eHl#1 zh3U=upySCCWAGs;u1=5L^bcmqRYk3y$X*dA=6sCospquVV&ZYj{4$9 z7p;!O@|+#x5}@oQZy=|M^3mH4J{7|(1|4|G+1M_;j)iVaW$4z5$W6-NYsjXpGTs8a zl$pirkKsU(R(_H`>$xsMBBFwBaLhw-QJ&G?qMbY?ZXgV@E%@EI9b2TNq4#=hGL~ew z^!rO9qcd3#cfcwav=cALCxzgKDfomR?kmkMD9LqIE zrZb_q{@ayyD@d=1;SfF}j)#fSRW1t$dc6)-mhy^hgc9Hl_`1|N0PTqMjDC%X&D8QuepkKAHTv&l(tHh za~1ChjAMZltuo=u%Mw=P*c^tXA`LHN_~?vVobZA=s~p?8r+WH*a4C~yV^TE2Fuyl( z!Wl~ZhaqP2Mrc2yiX(bT=4AslngA!d_wH$vK=T@5uTWJdj8lNn#+~+W ziO~+2sh%DZnb;DgaE(jFinKL*I*;+3n}UGT+oRw?Md_|nLYRP=(D|tpig5}NzV_$= z!pN}@ms{P`J{pF}2XZ@*f5;J`Wu9nPp5u`QSiT0te5z$F>t&3?0J~v0+%APzcb_FW zo_qBC`0+gOqLcb)RGwPH$Vkb@N5pMo81!c_O<46>au`U1^tg>k^sID*>L3GsfL~&e zEU>o$qZbDO==E}rWOYl~pz+?geQ&IK0u*B1<{8!Sr}5hoa5Wzwp`xM!DVOlsDx0L7 zoRO2Qad`G6jmHwr^zJA{ktqp0-04EudO75z`TF(X+3x(`A>7jN>d6Y62DgKOvOa}y z;STL6LXt!QGs_2EpUnr(maQfWb=XasQ!^YJBYlql2;hti4XwurEv*dljr;~&X6R}- zwWAvqP@iG8`RmgH}Rwl;Zo z&N-^_)%Lna3m?qS?~D<8wP-|+o>&u@_)y6f;9-rELuVr$z%x2O#X7L9>x72<;a<}|5?;*923Tnm{K#XD5&|fjm6etA`JxwLTn}RJ zz<}jKy~_z~T1^1{^PjoCL&BCOvVyn!+p$V{uuaG7FS{>67V5kgCO`JNjBV96z?n=K zYxeC?NZGY%aDyszq~I_3{(a?7h7-J~$1+wVEW@cajZjrM7|`XEv9U30sAz2Gq@Jk? zUBXH*I@KG>?4+bG1h3r2$!#84SLRgv%o5e}Io>1bvBTrc@0{?> zntAL0n8>3bUv9eEE<$7h99=+~wv>T@O(Jt!50Y{={3ZYjUxsfkk9FAKwk`qvKijj}Rw0fJwCLFYEdvmZRv(Hza0_kMLyuPOT#mzsU7YmB?=O_{CIR>kIz58@kQ2 zc_(HGi7bG#n4t_IA;0~q5lMTGz|L@7bTc3;1uRMWHE^BPq--m57b46&2)sONJkfq^ ztdyo3CIq_mop}~;vR}-`cKq&eP<7hB2V%{vRQc-{xfOF$Y=z3p@*o%PZe;AKxo#h_ z=w<02a}%X+G6ERvIT+X(S|*&6(!qX`8RL}^awB=dl(TOv zu=jRwE8_f7aA4sR&2iy7a>I7fnLPBvR4H8cWEMQ+ME$X#6DHW6CO3CV2lD%8EWNZ( zFa(`sEF1-e$dlSOeUty zjv$QcDv0YUD2%LZDMs6_MXV`MBSHD04;V-=?BWcf7^a)T>5K#4+X*Vq&zP{q1WxBUHxyvIG zCUnF8)Vh|9-ay^^)ZhtOGeFeMcb6HC*jk#nJG=Y7LPMs)a2P2Y`^tENF!ij}}0 z9*m40pOFCzrwfs(X1KDj1?lx0_ zGINJXbN?dkt$e&n+=Ca_Q&3XExW!mdSeV<`_+Fhwj(2O7G4W@T;^zwCQ;$>a4|?$t z@`?=Ng+)cckHPYrKZmGf^y{}(M5};4&J;eDW_pZBtMG-Vm%#ZJV$3WssCb+H=fxr( z9#3tbH@L=Z4qbjItUgsiPc)@TMoPy2N043=(t~H+Q53o7bINVii-(3osi30rm`%4h zx3I9#Yq8#N;P!lZ1V|2OM*(}7=sOs*pBDwNH26Ws<{{s~a|tNTNu}hrw-on}ap%Q& zsaxZl{pF(`XH>^4aP&kWyF6;Z<42L@eDUtm$|?`)qj+@&i>gq6t=(%*i1Ss=oOViS z4K3ttr>)%BxT2C$rw1*bbML?iJv|>*P%bPH9|KCBU%~&hrvn4!5eg;cv?!v%lVz~IS~Cu{YHl~q-J10$U-*tQ9eZm&-1nfVAU zlj^&>>N&efrCumlrsIvzc<7FM5^+pb8Q{c!lSyJ~@`dGaPj-EfI<_Wzj*Ws`O6`pi zrfIpKT51c!QBs8(UTD01x@gZRt|ShC_qK7)cP$cS^v#FHbn-;+4|;kU)KMD7Iz!1E zM&FAH%%N8?4$CI0FLW4<>cBQ`wqybVHN_?^MB7DLjQ9x5A(1FrwhEp^hi}rkUgVKD zhg_62X>zC*Kd4xxy)P?#_io9+2uWJ4<8=W7m6M;Za5#`KpJu(I?%-av0zv(RI(3Yuv60f{8Tf;q zLw;jz7}%&z%djy})2v8ZY7i|c4gp3!zWdWLS=)lWD)w$+ z{s8wJWmLKJQri#)m+f+f!~COiZ-zq!f}T!farVB6IPhTB+I?PkwM$~bo|pK@-i3EJ zGVC(O%CNe!yuj7&vLWUWnXx-4DoTm_bgn}n(8~akGUVbij*@agTPzhphBAcobz3n; zUf8^Ldv6zQkj?G4|A(iqjEXB-noS4}!8Q2c?(XhRAh^4`LxA8qxCI?F1cJK{5Zv9} z-Th7O{qFmB=GUyXSDoF})zx(v19Wjv=iOz-hi+W|VBr`Ac}cqMNn_e%u1L2 zidI$MZB$w`Fi^5Sl;U!rGwu#X&EO9tE7JXyS3ZZD%_9o`!}_FrG+jf>0JfsG(2;GN zYyBzqwrDf!@hy{pQw_-E8DsxEG5goOJMw{7DeUTca!<3p1+Umv0W(yqhAhOjWG^5OU z1=u2O3nw&EIF*5DLG`Y*&%Z62-r_c*`eIeZ;cWwaa)PU(15i zb(!_d$zA8o@brKF<)5aJl`5tyD?>{0BM5y04?1?$!SP40De82NbrwGV(y51Zx>fio zyeHTVIma25s~6e0adkV#dHF1&zc>g7mI8+i;gs%cijDu*sx_bcEblSLx4&(}x^hT# zqF)dF=nQ6jyAxfws(8D07qDMyd8POQg8q;FP1{Fwr!=8EY%gQK)!m(vkaN`nGdvsx0QuJrKqS51|gxm=Z}M|9Ny20wwS( z$ceHb=t&|EDVPTyB}f&vyIl)_b~u0IsBD>J=(l>Z^0La=LC1f>P+M}dn!a;i>WG}i zil-*aXH?WtD=bo6s*`bg9sNZ&Rsu06q~MetZ*cZ~5v9b`P)pis>)jEhG7uC(L#UwPgV$GVCNI{-UJQ89tw0v-e-2K>h*FdT0dj&Oq!)W)CJ|EI zSRqD`Wn?#=wTKOSU=IHW5!N}2tspwdPr%XgNuJ!$a!-t+#lyZ*)GGml=<7ag{<;$k zB$v!&DtG${#p62vSSrP%fQ-rpr^KDH#AJyjH@oN=H(83l$a5ro5iY7+exn%+rnF6n zgZ}dnk`$~rpf{32d-8Y1O2*c;x$m`%0ow6yylcvM!8)7Nl`AzdH%PSXAz-65^91Jb zXWH81oB>pnxYO(FNZf!C1#GNHs7$SzA&^Edxh?msr0To1l+hA7{PK73uCf@&l0OW+8r1;N) zc4fiQq99O_&G1}&I&{aSX%->G=WD4GGD7XyUd$Mo|buTQy$67=; zscikd9={BKKYm9ka(-IGd42WeTvpc!r8KyR-=0phss$HRO{VaTd0cMEQb=Blcn&5> zW5q}9jR6_X{27Tu2H+>+BS`ETJ-=ph3uV+Ax&Tq-~lnGfvC2ZAQwdn6bU8rw!0u0$RBK-M^ z{}6}y$pA3ocVG-a1j(;M-9z3Cb^FD_Ct%W(u0x_1~TmlND7D`FTIXr zvOzoE((#^y*e?KpiAhZ30!Ttsie{K6sRoF!*j1{amWoctCKt5T6vGGc8zc;HymB_{eCqSV znP!$RD)*|wGo|TEWh9VV{9~M0Hu!nPsrMt7<60L)E*ysFcndA=a4B-3F1*Fxuq2>O zp{pK^I$7(MU~R8EpB)gMD2sf=UkZAiso8DIOBgl&cvXXM^jRu=FoG zD_eIm|9VM)I;CGp4nx;a@8steU5c4*Q%NeM!+Va{$KwVHA6A;lrC6pg4Xzk405SAV zY_zI#=yVn_<@iM=12o6*Ux`d-;#5;qhcqH+F*DdSYybGzk|Tyj*p&S({*1X2aI9Fz zuRoTP6(yIPpknwAcQcF*LK8?dDVMU@ag=dvjC$wgj#Jo;*5w%wOn^30voiW~e?v_@gYT&at zMwD6b7$5x*2RW=uq&90vrH)is>L4`k=ubie>_L43DUoXds@ZO~c0rE@ag)}%x_$t= znGkmLYPs8cQEc$m)FZlIEzcE)_NPZ;4j+Z$v|G|H4Ta*AuTOY z3rC%#y6wy>6z5*)pn$z~b#fi=ls8KH2h*CW4>qctwgc#tNoqB4f5O{IS;dl5GN%Ma z^lBC2<-f)%g#U=jPaQT$+I-Y}^WtFUox{xcdz-%IH8Y8alQ3UxKaD2cx)Rhr^Z<5% zIcVzY&q8k5+R}EK>Ak&~Kd$F?8`jNI1U{`6mZCIshK15jx;M7>^pbDX?ZaN-$y(bc zeL<4wH)-oeL#LFCQiA{;Z=SZ{FITY}Ik`o4xdNJceXj_kZ?WUz-t;%n;ra&s;E?A$fM3TwOjEO{le5$#b-{Al;>kC z7K58Cx+;je)aZ}Uo&v^oVw~z7xx(V8U4p&FDbH8-Zki+w^O!^pnsk6_HybbS&gWI>>hlb_r5pg%;<_0;`jp+4&; zYfhR*u1kcX2O^V_aB?p$;mA=yhZp>a(|i+ zJ?FGoWNaC6G*NdOnrPg1g!nniU5~1L-M`~D=!1!h-I*p(AkW~*ycPBk5MZTMUt(_r zHVV6v^y~bSdR|r58G~G9ObQr8T%^V{a{F6vYWB)zPCwaQJv4NeI2+40qflcp+QGSK3S_z5u7*Zb)P>ozdzGIS*(w>y*n|P?vG8DhuI32 zRNw-i&D_??WEZ>nr%`5}gURZTU_27LQ&Qx%a0MJvWP!9WY@hWa$?uxYaG6rjcr86{{VXx&4HUdB~wVf>P|xo^{IA zZyp6@o+q{yoo!pk;}MXvZ^+l}*B*-={Q!L#oauB7uZQse!Oj2w!F`LyrU2y#-CLQM z18GFEa(wDmKQ6I!WI&d7M`GdbTu(Xq7hFe~8qOC6`xUd&bFz)>>asseMc?V;TJMqP zR}OQ$uyLlA4FcQ^ErYIS+=9XkL8b&0p{j>P(2i9VaDCYQW zdzh3`VQ}Zxf@VIz2%7{~RxvnDf#%|M;!YshzX_buauSHH;ZqZQh}(A6?3FOcgu@{f zd0=utkY~3+tN}JMAnRZ(DfO`SMDCz1~$rNd{a!-mWRk4+y`aD2R`W> zznG^U!U>`X>27n3CKrbn@g7+sKYEV;a&JdKCC{W;Z`kLT>A)xD$BA$u@LId?p+_sE zF`D+ftIgf5&3(0dAD*xY*!K(whxizu3yxo-J0FmQAe$}W#LoCGy}DJqaqoLlZ%^-D z5sw*^A5hx;Ng7}OioRV1J|oKG+En=xz@0aRHFlgAZvtTxm3p>9`KWXG2Wr=aq^4wh z2mdwo!x^OMdirfEv@_gRuZi(gRT-?z>a(cn7YU$=(_mVsXMTshGkz2ZM&@fA&kY(J zm&f`K5vZu7Xw&2)4$U1wO$N=3nH;66p6*92f5m)qygh>?o4ZWhf*Pzz+mF7rciT^n zU{9>q&K3;j5vn_O@e`C2V|iQw`*7uqhF??eImz%7RWy^q7=MTjX|^c)l6YBRkMG2z z;WsWb3rsF0DDYbF)EIIUKFCJO%x<+w`k^OjIx!WWA~RZ+akp|T(MEpJv$@bIo|bVY z>ZZn-kNu`2W7P-%FdF;E*ho;j^XykorHP<1ZnR7KuQ7R7BCLHLt44;ghtC)l^J9vm z?f0n|oKl_PDK3v=%)*qSqfJe&%S!N(K2op1mii@Urjv}=vmk@My)4m>-bbtpty{NH zK*}Ww5*N+<_608!uVvw@zW$ZR88$NJ5A256m7le2974I$dH2bF@9wI2N_K<%LHI}F zbT`9Qw=Q~ZGzMxcwAM3b-G*IHn8{1eWkkHAN$>8!a1x>M3|wWh{jvO0zJ5H7Fge$< zjs_&ol`-b3=W|pa!6(@B=#Cz-*BH#)@Lst8baGzoPLh#Sp6mYHfwx+LeK$88|2zVZ zQ)wOGnHemShc38>5lf+7IN=9Tp(|kgMz(J431m##1i_NmN%g>i+g!i`ukW4R!RItu z{yTe%``cx!+2vPjepEZ-f6sGFqnN^`NwaTUN70HOhR8AXJcpT3I`^eg_X%G5-mg5f zeKz%Y$RNbfi5DRG#}v{`w#5m#RR>HXC9B8?c$u4(kF42xy7cCYuecnKu6Q|-%%7}` z?L6?97LsEj!q_E$=AzErkd?KX26DOUo2=;@aS8TTWEWZBSgDtoX1OU4Cn+eNXK9_S#EU4G2PGk-|yDChZ8o)r^a6sa;)-*!v_{C z%+m3U(Z_p$C`G|06%z`AN+~n+7`jqO-+AP!Su~?mZoeHz$`K)^Fcp;-swqau>m&9t zrKp8Y9#PE7$gpcG-VJg8E^|6hpFi$8v;Pbs#xCSr_+2aY< z!Sq>qayJCnJZd16QK^`F#Ps6=ZbL^Gx?8ka=;zxbCSSP$1$1%+t z&J*SNkMOiH+J#M-Nhs)(3~`?&2HuUo;l~SH$pk)u!O*CJH)z67g~CGv!f(!R5Ap90 z1;<{H0XJ{JixE_ggKhaHR?8&5VJOS@&(uJJl^!a;$m> z_nXZAH;WweRI3a=A-SuPx7uBhZ~`-Z6dS(J7`p%09@%#KO zgjI{ELWy7Gpx;>QE)zF0^TEfv)mT8#cZ$Wa<{3=yKAw2hc;_f*{h0Ib_oebIYa#M7 z{I`WO0xJ`J6curm&~cUb`Y=ifwY4+CS#<9qyZ8Gv##+3ZA>L-uIo{MoF%&R`hGp_uYh^Z`HE7D;}m)C@xa(%rH#>>F%Fv96pa6|HfTN7 zfoTp->+W2#fYQ)zV{>H&#dsxz8??CNMDP`$Rng@W^C!y$%(VZr;A z!JnS1V{fm%iyig^>eHAp@o_3lI1vh(Q$yP_R?~UiZ0@PA7}tjkxGVU3I}Duc^72P+ z_WZ*UEaC%G^tBY)2uugq;zzOT!%D0+v%O% z`5qtN?#y4n;_4SS+RDvRZSB@4FMLOTZhZc`^>KUsMP7+STf5~r2X%GG|NVmH{qghBI;2M$;z(An$ zS-CJ{;)nu78-ZC+snP|YTCVICU;3^Db&A5Ic<903Mvow8(UxW2f{&8)D-+YHAmxIp#?$S>rkBwR6T{Xm+lr>#0z&RR z7y|4+p@84sy*2T&zYBnBKh)eaj?AK`yn-SlWw~PF)oDW{P&kPa zP-Jk)5yYTC^gB(r62S_M!%y1GR6@1{AvTd9!NH6|^l`uX{_A+JJwv&T7i6D1yqdOT zej~fHpFZsXu+#T;qYC5ce&^zn66+b;Y(CdGj>|`Pj)SHtOyLlCj3&@INpDXioT6CP ze^e`{6qnfuDpY}s4<7;_(*p6wj=uJ=#}k^Hwe{$pt&V4y)@FNO3VbNC0gB(BY@bYC zXA61+T~p(%UHIc?RD76lFleEUDsEJ~a?O208+QHt*4z2WTxVnN&|=BlxE7dz`s#VU zgZfq?jQaDR7ReL_QEgl(a~j;oz6uHPKeTgKbtpKT_ld29rxN)D8SpgUz=3PsOTD0J za7?)D(|4Xio}1o^kDE{LmU-Q#Kie&_jD9OpAd6#ZXpE2h!Soq2FNs9*NzK?{hz^J| zXIEZjnogyPJls(Vvs+k@p-QlN0|(xL1zv*=10)&|Bg{8A2lvio>Hn5r=?Mnqz0de_ zZLAag@%S>SC=aUq08Uqfk;6{l@%)?a=gkiP(@=XFNtCstcME^FtkX8+biy8?vWy3J zTTsG;)q1EjC|I5rl;iOV72A)&WG}kZ@2>OUK)^@j6j!$#3PQ3*WD=cb@sPWA)t_Bfy=sL+ISInQ!toI-khx)XP-6&mliiQLh%zhZMiB{e+CC? z`h1H0#JId_MX*n|b%v5BJ9SR1_sRJYq3_Iu;Bxm!PJga&m^DMtWyrDW{F83Y_s0~d z0Y2R8iE}35KFO#QHb?(uM!%hbfEaP^RdA%K^y8COXHVh!6OMF_@R*CBXiOhJw_B8! zWlLM4}(I}KT=&aZDFC(RzXrl#gmq3M4SVf*$&8+DdI z{cP8Vgtdpf7OUDgqYRv_G9?oL&OuUjv3Uf0624-)3$KSh(YGH~jw9OW$O-gZW<@_G z+^xP&;YNb?;}RIW0g_f+{k*O%J!=a0> z!Q@x8g>KgMX;z%J-v~RFK@A-7hjpy0So3EaONt!UWY%zgnFv~$2xxpOc=Qs0Z`)q@ z4!yJ2Z43dkVoyVYE`5=gA$`8)Ghr@<9K!Uk^;JT&+~{DK zsnYLY%9cJ=Gc&|kc`I)$e4-K_HpTol^jJg{hKYI@9aXT4#ug>D%nofDUrY=Ktp?^6 zFrq9L({7Qg@4!pUdPeL|%$7QVJMM{-lD8#WHV)zhh zxDi6~Lh9&$tjp_tWp;p`hOeGA@ZlRz=SnVDv-xSAd_y~0ydXx}<( z|6E17bnD31>ltl_A>0u!j4!WwlZ2l)h3NA)RD0XS0M&q{M(Cet?OWVr4^{5vvlZgl zx5u2;vdt$A^31=0RHJ$~DI{aZ^+_#ajjonJf<#N#ISeLG;x|1`3C%mMx*DdWQuQCy zdN3!QC2{to9_I=bm!wVcFcH6hfh^y2Emqj243Q%HCx};xKQl*2J!lf_jTczQ&RxMw zDP=j-#yQS7F@017DusiVPqm)ewp7nmM~TQ?LQxEleO`}ms}x+|qOFALs!$Gs)$2Y7 z&;}zpYo;>N8AVYhZqck>N!htWv2%&)5QPGr@iO|;=|ZRAt<@|V3`9p^+Z1`Ve~>$i z8G{B7!oCnRHl(MY{PDiFC@u*tU68ORutS^zPjy2bMY~Nq+eE7iTt{3ih&!EG;qy1j z_}ocOT{8oI>M#ZWKBw{(OfT&UQ^ah$_CMe44!qeWY`Od}y~{>vo1hkXd8FCkkiEyq zO}w*u-jZAO`r7Na+bIMFl1pdkR!pB3TTRGAIsVm+85D}n+pzgxBT2U%g{_#Hn)+~S z`0id@THyw+d!{#509-hO`t*^o|l-Ji1%eOVcQCERt%NTOE0b5)l3A^Z>`yn&hf z0Nwfi+&K>EG2_k1b1c-?&Ulxr#|-(G zUEO(T@FzEZ>&`&ff0q<|Bzt%yBOGJ~b>&$7oo5~CW4=nv0t@F*j{b7<+f&Gj|Ghq8 z$2M1kvUoZT4VF*~1B?~EdBP8mCDOj!xl%chkw&V%k+AgXyQ`ak@*Lo@Y8)wY(%O=Ku_9Ob$Nu{V@;k z!wjbVJvysXEKoFuMNT10b4Jf!2R^F)$U(3S^eq#78s-(?3T;ru# z#6{P^oSJLwsq)HW;O41FM4N-yf`2KPNOR{U=|)Hu|Jjwr&;`T}xwT62Im6g%@^b5L z7VR%6hpkkaEd;sZsnidON{V7TEDV)BWSMgu%#=knIQC z?gW*^QKM#_cH#OfLbTmvJ6(U?Ztv~BJskBNENrsg@iQh38QWQ+pZq8iQ1q=XCRPbD zRps%$JKN29ULO=Ud}LD=xFgC5+=Lk$8>y~a8C$ALbFV!^7#n){ZC>9?gLdFfkmOJG z(djn9`QLjFsfb`mMHGPe#$V3P-yGi0bKf6x_t|qFk#nC{_Py4h*BUL}t|4i?*RQgV zqw6oD9m^DPrim1O6V2J_D;!~-IYrGrflD{3&WTD-VNEPKuY0N;kD(*EZ4Q8=z)4S!1)bDIZxq}8$ROf3K4y(VxH2%>xH1CC02pIz zTpPKw&!WX+ky25`(3x?I%w($0lh1bD=Sj=i%jTCX{3BDcK+1|h`i&vjjO=WY-&3;( zJ}bQGK6vHy`0(O25FZv4F^w^natQjmw2HB+1axSWD760eO@>$a{F{!#VKFBLHn~V;D1YiCcig z(QrQJT2p5Di#SVJK`KQADYDzNI7h6}NWmj@kp^udySlgw z?uy`gUr!dcjtmp@mSObu$15miA#IBGJc;XCHv=Dxy*T43E~@#s8;o3O`mijAwy=AF z2T@|L3-ObcYT%`sL^r5T+i1`*uW}ggM^rL?JT+~fsa;tNJEvfO{ru?;IP&@7u90~2 zQkrBpgF1J+8CPk-%0paxpo_8tta!I5f$hhLyZ;}l6JGC7k5lv_@J2ci?D#t8_-{4h z4`gpb>!&IkH}}Bf_5NauJH2kB!!9@yGha&&_QZc!iO$r#;e1u1B*p2#{`slOeyu%7 z>DRFC+2Z)t2^)1zs~bu}Q8Vm7uF$8h`8S(PPXRB2_l^Da?%dZm+#cC~x*5qA90f|x z^o0pRM+_#uw^x_#pP7OPXE1kXizB0s%|W`T>qDEp;ftMtBm~^{q;a(m*Ahzp3LH+|RrAjv;#fW+TgeN{_~o2nO^)U#T= zyid~r4J5Cj(o`oQfFgO@uPJNnjNEV}dDiH9^i&&c92=NS{cd;cZNL;Ly035zAVhY= z1$>k|;L6{~cg?x+H7uBH6M39i#$R;CJOx786YPfgw-ZL>SQV6hm%ve{+aM2-?#AmP zH@I5x{*Z*gAj6c7+%rR&%VZn(3Q)crc3O+9uu<+T|Lpo`^Rt7DnL9H#m%gv<#1A(~ zP~(bxD*ARb-zlBUSoZ45o^Qq_&^FiItGtuO6OVQd3d&6U^A z$p~&XPv@81-9uXI)XEvIw=V{)IG;269gvgkuHCmKG3u7!$3olkF&r^a zud)-$b`Hd3OGqqqS$Mb-wVnzUpQ6a6utxQ%a@jXUzWRE)jxV2WWt13f038K-w%7fW z2XgqNN`9L8e={4+X@4RZ%esX1i=6YXCxcKb=Z(w?1wO?8c9E1A`igMu{QScW_R9-3 z=EJs#9?WU%^7ffwe4sR>d;b%b<$H&t_t(upm-kF>p%>+|{j#b~FImYXW8$ZG;@$q% zO4IjKU!I#gf4_5E<{y}tkXujKiGm>F&sP>vmgcGE^lp)xS*EIA7Q|ovC=u;mV$7ed z^o(+oY+d{gZ@#`DI$7?_x~!&CwY#9u2DQ%d>|5~kT3fs#Bl!fGXOu81swEX@vKxzZ z8C@6XdgC*}kRyM7)2+cvvP+)4A)#OVL%#3rf zGBpeJw|xG&BXsS(-D4ym*I8lh3$aN0OJFE;Y&G=3W*BGFntmQYg ztzu8%SP|&B*R_jyo?&66<@3eGC;|!zfS>uRx3eavcq+dIiQ&%zZh58ci&2I-5pPkl z`Gfc`d0M<+FYOP~oP6uakBiUKwfzmCN8JGK$^BAZ`@UzJ{v^-v_bdLm>g489NOCqu z*)G!FRQ@sQPXe(AP-XVyG(??$v-$fj&uUNV=WB<8A8GKCrHX5LkCD@%5zjj!$aX zd<%+V%1kt^^pFtOEo}ObR;eW|J&Bi3HJU@|y4p3w%8^A!6D8TUQ>zmHS{m{t*mNeO4ZLZyH34$Im(Lb7sG5+HqTF&J2AOhV4@q{11QFWktf_!zwT@A zd|LG>EKoiHBAT{>WRcdbNn_3R{1N8Ak!t6JYtmyXaNttu5e43Pr{dt2ff%FUP%s@= zBv(^as~5Xk4-t@vIf-M#P$c~gKMewe!icb2Tp*uScgfJZ0q=co8;T{jxWGV$nhwuq zd8DrDj>c^*JosZPp~5-gEFwoGeIt=&+--3X@I!P9kM-02K%Vg5%w;V{A_Kt%c)K6U zzQa#`tpSR@2qAAXj`*W~l*cXFPtry7PX<+f*;WC+AE3ASWjyhPJ)tb4Ip{I}hS@KS zw$dm$ikGWfd4H1lT36Imgyg0y7%Kco!7)z0L}ZhtH>rB9b9O{O<^_qg(C_z9yyyyF z8;29RLU-ucMZ??$m}gCxRVtFP!}eu~@_|JGO>W69>jXGPshZNdki%g`S|VE%)(1BlA0D?$b=kHyxcXepa}f|Aqx0AEt$0rvpU%Ho}$tt7W}G&|Qy7*9>EG9~OKR z9hbxcmk0(SV#LKkTJC0pr+%kHM&<4!><5Ad#R~YS^(NP-<#kt69^gOS_BCGMJFTb& z3}*Nqyji;#CG$IU9cI_{*!{|=?s-d6mScOyC%C&>+j*4oS*V;JaSYW5$0N8qUW)(xG zX}vF|#`)mL>s0l6yd_pDRY01W%yRl3)RI6r7k|0~+$E9Ul2i+0DKn7E_MxAxqantT zbJ7E8Nl{|@WCFSLUfg((3Khq(Ft(PX^KE`2y~OYMHO8Sm%%V(gfCi8m>i5e;g9SKf z$0f(5vW6Q4JMfz>#ORu#Yv(I+`EeJK=-C>Y3PUN;0cv~ASBzZ;ypYIkI|RsTU0NCe zD4A@uJDQ9Mm)GrHsNr-?POxm#wK3?aqApG=*L<`LLfLM)n1voOkoIj}?QrmEEJ6wg zL$+M25K6mKrGZ=ULX11JeQGoAD)X@OqX8VQM`lpTgS93jq$E_r9|rA+kMGB|VuQ~C zx7t=CTLw?Wqmi*#4F=JRb!k>^nAYu5PgC0u>dIt;7}|qm`klv}QPrDo-g?f1@Jlu| z$EodY@1`VMZ}**P$5pMVgDw(g%M8a1($ghgYMrCOKf4#Xa zY64IqSQk0}Y-OTA8=S?`jS01a5IaW8qdEU$s_Vk;!6t`@ZNk^=!o4A=LVqFY#{oj` zkK@-Twc|CAoNb|tAk+(T;Vbfyv9T+Nxgy#&_4P3K&06SjQ2FW~YW>b!b0ML#IYtnt zh5#h3V+9h?hTon<0oZFebkH~hfKic6nqOGw{it~-j+S8bc>JssFj?b zTOBvvNIE~6BHtEKwW8Q~L+qHzJ%Cf0Td#_n!n8S&yFM3PjJC8Empdaj_ zk^lLkr}nirM^=XraXbxPH7QK?-sX{zlzyZ`>xhf#kI+y$r2WsgkAV6jC|6G74hyGK z2EC3ABIjRmvMr!hrYY^PUF}lFO#(%~R)kf)4*db{tO+Zz)~tP?mjpC9BIuE{{;p7t z?OyZ@G^v2q?Q6w6<#$5w*6QAW-jrMIKd)Yud}u{g>2-^jo3cM-5(*2Y+9{wxjQ9R( zX}1rRMD6rZ17wksON8F zBSrvPJL|+>iCs|)cxUb&K#h5zLq2{p<$3rwO8=sNGd5U}XFHB<-!lDXy!xlVoh7sG z31`eOEaEP}w{o?w*gmUyGwj&=n80-FFP&TRR40JAW|L0`;p-*eE`HIQZL*-eRx3~ct!nk#r)28eSMwJt!Hrqofvv8 ztTzc-J-NsgzIT7yWbX7;%&4^NbdPa-8QosF4QjjydcPwQ*-L!vdB1hXv9x`ear7h2 z{tJ;3y?)EE64?D=bzDMQ<6frSPYK7U-<~iT;XiAQeLnN&_gsI~L|JqC4iUVKkR}TH zy#+@TbSSzMNrN%_Zl?jzAx0d&7OVRqlUM#XE!}Fc5r<8xnS(s1 zK0$kG60SlPhrzom+2$5&YPQL;t=N$L#4+^~j|hH7tGS6gwj~3JCHEDV*s&glrSxzM zB709VS+x0&QzQ2IW@os@Dk=)~BpW%qaXVxuOPKsVMHPMx=fqZXvbh22eW0->Yc`h) zDiE8JZq0_X+Gqza-qUeH8oDfKo~gy>XySYdi9fz8nO!R~wmOu}gsmGajc#HZ6Nz4p zhq*iO>zjK*os5pWRbrb7SKLrn#Z@JwC_$pPg!ei;JAQ6?fsxLjhX-rj?5X(;PPyr! zz+)KW8npIN-A_TaQgiWQ7{Tb zZO4V@TdyR~c#W&>)VkctEX2M7NQBCUJA*f>V}G6boIi7FZ}@9EC@}c|EhGoof~aGY zH83-mjF{TH5ImEAzVUfTKQGWMmkwb;G|E;F2g~8TQN6$mQqMK#^eW*O7Kr1`@zRjQ z2YLR4bhk$(_tfx){_I0gS0;a6Y!L8;cmhH3|8$-QqT3p?@f^XEh3XKg!fuGrSUPL4 zO8Qi(-@4H3O8r|inp`^WPg|RxWDd`)IV9il{l`Ce=i?f}CYMVVH^wEo5!F)f8xE*j z{UZYw?`9Z5L~y!W?gBN$O?ukY=kj&9Ns~y1hT0VJ%Lor#j=lpw+aSTfTNgx)&ab!6 zi*@c_X!#&EwVK?0KcBE#pUi8QR2X*Y`MS%kE-z@wKRn4;@dKtGe&H0tVml=c(!%oZ zK%AT+ZChy2l{=<0@IHu4nmwG>F~2UCO~eU>n6d=hV`hMQcU^JX$l9K7MMcz1I&cq&}} zk*E_i)#xWIK)Bq;+IOG+^+$t(W5;}6A*Eigs4@(}SLoBuN7t3<@nF`FEA>|%O#MsB z!uRHS5->5j8nkJxikodW-VBbaIxIs!80?H-$~g8dn^UIKFS9$U`-;ANQ}bG2*>)`C|z)(|D5t;uB7W61%-w6McVOVOVN& zL|R}^`2cjQwxdsx%pQ^dNYSuWUAVKK$3xk`V8Eq7k-SNh?0h15gl}B&A;8?mMpO;w zV$W8{+2Xy&tIej0RiB}uLrLN2lp=FgYj$tgN7wU+wL0hfQ`(3Zni#*Br@Kh?yl8V* zGkIb(Vg~}l_e+B!w^y271zt~7J6<8d7pJ(L;Q6=Vv~C3eOvqL^3hg`H?-2URbHB;2 z^P=w^#kQ64v-4x9<`Nq<55d0J9s6a$wjsJhD|!$-LKsISUgd(u?Djys>&wICeA@~n zp^wLTOM>S*NM5dWa`Ogwolo|!ySgRi@=S;90A5j%r;)OeeHdK%FjiRRGOhS7?0Zhc zFzCL19}uwbrrhFsz1q6zF!u4ASpq2r&^19s1&b_7v>``7m0_`bpx~F4{CF|L^Wk8* znrsbiCd2SlEsZ>vp=trV8ay<-NEg7lIa=RtEz31ifN0D+&~1FBnV<^>?>psFt9#HY zzu;=gIPdE8^>fboeJk+!tTp=B@#Vv^hrO|6ATu8vl1H|4U^;?Sx<$5kMqvTnk0ol2 z&w{d8Ege|8M8D)CBH@AGLMOlM*=I@yNG2BoMDVJP8i&a0Xe)MD8e+v%)3dmf_otIf zYLkoE8AeL*GehzK5$?{i`7zy^@_%?j7)MMIUDLM&)aWs=_8`imOi^(CM~wYQw5q?2 zb*xr8}O+_wwpwrHIcUcA(j}QLoPrCkrI(SSQX<|GNh{}&n^O* zvuz03*c{f5C^U}%9Kq06Rw`9ZEV`@yGBPgwmzww)FU_zeT5rvm2X7bV{HuYwql(Kg z9?^GB;I*cDJNW}w=y|ZJs;ty0|}&oMFgCInMg2fE6>cpD*Tg1Gb@Zvoc;_gzKlIAQU@m=UzPAQO@`CL{TeDlN`CrJjP06nU3|A%y?g(N zti>o+?;g+VCM>KO!LJp@n^J7EDeJl4hO(ZHsJfnZdrQZadwsf&o4?-A4V`x;Xl0rs zG#R6k*u`~bbngb$#uv?B=~(ffnO46C#^fFgZ*I5m=h#yC6qBJEvr=(mQphBbiUvdD ze#!TEjPl^FTeZVa_G4;mQ1pww67(WW;&lmn2MoiF-| zY_2qp^s)BX7~&9W3sVpAjjIz1G={dk@h|iU*QX-mw^K@)bBa4yVzsk-nMOpM(RG=` znj;X8Nnt4w>#|jU*w-nh>a^@sxp_Dap|%WV8siKPzr6F&hfvQ&FM=C`te*V<8IcZBaEMObkZF zoGMU9ntUIctlVG#GQTNTH=?_2=1;d`2XMwWox?Hg{b-a#C(2^(QhMj*z4u8b3d{( zFx9lA9{~z;vK(;vwh0%u0?9po*2rKwuT{sz7F_;@iB28)Q#TkI0fI3nRZ=p=|Bp2` z3bk||dob-Z!kV%v37mQ~JQs@{-nVZ1MuYL##+AS3TKYXvoc^U;!*wxiM?cN7MT=)? zzUotHeAMKID!+vx{2l7YvyS6TECXV4MxY2sxQ3o3?1NHJ>;;#yaMO+`?$Y2cb$QBa zlbIqP{(-HB63rKJH-^&ACQTq4JF2r`UcXYes&2FS0zlWGnJB_x` z+%5%q0utry(T~hqa)P7?|K|l@Y7N%#Mi;&*<91~{S$wu_EAIN%T|AX=D6&EfKJ9A3 zkJ;w9=E$_cHYcBwDWmz$zg@6ACBqfsF%^NqW-Q}SEn;o;!^R)Sc1XhPdz|cN9mHGJ z#g!jncbtNLC)@E?ley3MOpE`IsdoypHQJI!chxT2wrz8lZQHhO+qP}nwr$(iU#GiI z-21qmz7;XY9LSj?vkpArjvXHALF8Xybr?Knt3-MK znMo=mP`9pka&P7I)J{~VKA^TMD%=;k{uHB?IYI;0)|{tUbBI>yE*!aE9$EpMUINA5qN`03rY_X0}GZi$mWN%*#^t?}c6^ERZj10>XSD+!ZI8X^sq< zsRs3a?Pa??CaljneRaCQ1{0aOV?{<) z@clcp(m~4!$rxmcJEtvodu0Ksq%uw;h&`Z%Z?WM4t4fFF`I_bVvX4;?H+u`^`Hh9C zV``1IyWtGC=d_#q$AlT%+g-fzrMP(Ex%_^2%okpo&vi*#dY{F!Q*=;BJ#zGm{|Tzt zU4no7>Hn9&z_*1;$^NLHAod@6g_DsE#@fkm+U-p$N}YO%|tV zNE&pGdW&iF-CdN*1CUR_Q%UYlN$W*C1V%W>ss-lakTcc7lN(^N@IV;k>)RCMc2J=n z0$QD4ww=FGGqYFZ=htb9W=#uMJSDyA_C-c2cweJ$wy`(TukyoVIRxvUXkt_E7Up&# zo?ZG`;wqo~v#2E8H5KJ#Gec#)+HIv#wy3NCUw|pZilkJnqGKzBDuf{JEHk5MC|GqT zZ;m!61HRYsuGNaX`gK-`>lG6BB>aN6w|5+ep?o+WeQ!#aci@@Ks7n-& z1miEgU3Da494P`ziOYcSjCxdEBOFPbxv^UZwbTbqog6RK75;otD4zbWwVWC=Umpq@ zbBQWCOs$+V!>)Cafbzbvg5_#!y3&`|WtSsr%ht1A_tO;V>+p_OLRc9ve?fkMn#b=t z>?DvIzFb!44$q}Nq`XD1qMAGj@&DFF@a+gu(S-kvGQ7SH&?FF2)DSIF*B$5{gmzAa z%k?_H5M0kx*d1rYt_zMkdn4)9fk==6Nk+c8tdWH!w%)B_%5x3>$rNNFE3AiaWz(&0 zcoP~|mZ^NR5?N~#JBEpV)$p=9-=H?Aq#!On>WF7-Od{P603&6W5r2hfk2cEB(+52V4Ri|4;F zRsmFjv~FihG~P`I=%)&3)VH)+nY+Q*SVrY;ntY#UpcsQoq?sAjF%>2)k0GoIvQ#@i zACCNY%X1&sCs+i>E%XW#1WYsLYmYvgwZ@jqDZRd2Bw3?S-Pq68n2*ywCc$Ac1D9@S zx^m|)0@z+1I7AYv8Rg=r*DZ$XhKSra5R32ZE_sAJiX9UQD1-l)|3(^rKNG^SoVVyS zYp4l5bB=nQWUXuvFl@M!B)f0BE;nC49KClUumLneuqq-P2y!HA47Ch?ab$g*R8}3z z1Z+x|F0r6Izlu-x){52?HZ1ofGpkqVb~Ou@l3{=5Zt4&B@Zs-u2&V<1^bZ$H7*=H& ztt_g*>pUk}SRNjRu25UsnIZ0QaTs{Q-M8tvgVuJtVjdmg>>zDa=X zI!z-rg(Lk>Rs2sI_CkR$PxRZ3{pa`X^*{OCjekX=5W+VhciQg}x@I;qbtWgm)m`7> z*PgormzxbicnSxN+=IGbObYIq;^uMf<%r>Z2ti635`@QT4{IBOPv%4NN9%)>6Dgty zvw>``EI~H*K-%f@#1mt76Q`jPdgWQ`iYPl1gxvuOtcT@Fo9BSd3^HcsiMHlw#=oQ$ zKv!h2PBMjlCI*!J0f`40iG;w0$iQiTt!>NCm|Tl*%X#E9zoZYXl@YI4Q!@4(3EmxBw@hwbtNi4;!PSua~)I zH^d-?fkknL)b`?P1Aif)I5v?XjLPQK%7F2fxN}R=jG5Rzeb5p$F1yh4zArk)u+>pv z$cl6bK*~62u>8x&Cyn>Fw45I@SRPN_3|1IfJ98|kox`zriA-A&a7AC*^0Xp^Gw*zM z{e8H*=k8<}1U$Q3?UMmvXerU=ldZ82S}TmQ~`iFL~$qP7yH3(K-Xl zOx~*g;?Urdnxav5v+dRmF0P`|Ow_$GYiP zkQw6%Y;@Z!##Lt8AA<0gSRvT>7)oGsn;<3 zWWrdQwEtr%LDod2G9>otXtQ>WjDk*yDzF3%$|CbpJy=1LH%APo06Hj@lZ(Cx>B*J%o zHBpGAGWTGc^VH)MN4}$3PMwUNn?BgY)7D5Vi@(s6P8~0Yzs58q+u4!@dO!Em*Vfd3 zJc3riLl0MSS}-syJc+ou=%`LhqZE0=`IosRf_vNtdSmi&3^3k4`0_cxh1oey?h=^} zLnl=}IP8VJQ9KqOUUueg3{0F{4t`tJr@o{J64nX4=Txn;R@-;bcL~^Z@{;%E`@CUY z;xQsjQO0DHHsabeIzRD}{!w2qIJgFqSDb6df&-ESkb!bKhR+g%2tfe`&W(Eje3~Ak zQJRNZPRA(YG&M?Q!nJ|%01JvZRQkQlhikxK(Sd z!K>v3W9EWmiEacP?Tg4dE6SB(0N5l(K?7+MV=q(595*iv(W2Mgficr&g3c2$Z_QUnQtc0!g=g+_aXf=E(GySlRp>qpa#5Od_rL|ID}G2%VsDu_XUZv^i85-P;@v-#~jnsKk~5tDzAy zQ}&%`jv&iWj<#b)oNY(Z8Lz$6DGAp>a$c$Q4(V>(5=&)!7qvz{Cm90unt^%+p74}# z!$WhX$ZdR;ePDrXK+8qe>#C0%s#J$*=uT;9N|TeViKq&x<|vY!;~}1CyUD1f(lK%w zX#J9~1|y`on*AV7U>9}w`S@aVsI$DhfZ?)02W+L1AX#H^*Q53J7O;OgKh!!*p#O5d zn_Z0M59TQ|NM@xUJod?v#L&_?nMV)Jfqw>}mx9vs8Kgi;r$eGOKN16<2=M(4j%Z^A)EjBNx4|Tv(+c% zsM!SGRTL4f2!8HlIToP)=jo5eOPx1ZoQ$Iu8B*Ty-#C?!afyyBAg3EQ zyMZ2N;~Uc=lHFsk2|b-{uQNennMNOnSNurhSeo8j;evrR2J3<$89ASvPE5mkT(cMA ztzbDCH8K$`L)aZ5CxPx41gI)hHMk!<}VfSRMNwU;b)uCwT%a82#T79 zQ62+n-_)MJveL9agITWD7+G4;oI;f08d{Ly9@}G$VQi6&vOg6{ph6IxZH*~loa3ga z5;ipd-$Ea|%N_LpORYfox%P6e9YpLTyiukoIYWYbCnUO0!!XU-_1Tm*t%(*qp3oL8 zmHNZzhKszz{*nq(D|O;}LUo?V&0V`@Myl$)xNG}@L$-wcTgGG|SBaEYlT#f~(Q3mY zSmF&=Ku0W&Q^d&XiMY94Zjxo9M*4}iw`9idKpLqb>csV1>ce!OzI2Z+>met#cV-}x z7QkmnBsU$%o9j0l}(Ozu{}QDmZWKk3E= z!^Vg!fX3KLyQMRexL?zdl)bNro-L2I ze|d@7pt!nU!AeM*fHkh5ZsVQPx!HV;x@PcRp|`OGX(dFlf`XUxVI4vy@s3EkeHdOeWRDAKH`!tn_yq!o)C_vIPLURI7Sm%kQAPnTVv z^4Z?^m*B`Aa{2~Nn>&*30 zv(7la%Y7uph`OXCzWv#tQEiJciV9}NJx}OwzLPn!Om&MCHK!~GPS&8QW6`SX!*fuY zlW$=$2@*dfC9}giFaKH|a{Awo}^f$}V}`M(lU$fA$EPmvhsm zvBq1hod^>K%|SEfN0*--d0j!fU^j!dJJDkX{ z1^T8R#JLG1gE!syMP{kb7?vS~F(?9xPH!6^UY7mIKGO#-U9;}8vbQDA0>3@kvqGa*@&y;GaoPH>_br5~2;Amdkihu)e1uju+FNwAen(%p{@SM9 z_F2%7W;GyPQ4Q?LMTdl>$xQ8C{0T5438mVZSo6Y-V|5!Z(vI$mGunv&=+ne$G1jw; zeJ?8z9VTY31vop)$0}Uy0Xql?p6Xzz)J(zO$;Qul5@T1wDieMohVn0t(_cX2jZ=o9 zQpV=9R5L!fDtPvYo{3D?<;gS{%+xK|>N8%O67R}>b|j_zc-Zv*n6lidi&D|{4m!zp zi$>LU4P%9k{3p(;lq{C^`SmSX=%;BomjpCG`oHt}4LMPv*ne{=>E9&aEf@;7&sMg% z^s+mYT9gam$4mRR_iKway9<M^Vq*e?ro7(}HMOqE$Q|i$X%q~6bX4!B zrlKk`B69s86Ln_3zDJ{RSKNf&jC}g2+q#>u4T}TC^ioi>Q9;oqN)dCbnIy+hlgs7q zJSfdj^;4QIn1RIOxwN|?ZuLz*JYX54#Yw6GP7K1<-@lig7Tn6fu16FG$T4(u@bru6 z8uM(qTIMrirO!xb zN!+W^ES{wGXZ;uqPaMy$a78zrl2>D>atU!eJyvP%^YbKBp(xE-%~2k_&+yl+FV?@# zK~qGGmk&KN4bO0dWe9%CahNc>szD4Gf_kQrHW#Bpd(WbhHKG4V`aL{Z@@Kk7uc;SpDDdNT zyK-hU>_JXW^`OL%JCUEt$<~1k9K907`crmW8QuO6JC?Z@R(w4v|uqh5?oZI?HhqFZFg0815>Y zL(Ny5ZIAO4uJ>W2ug?p#`_#+5OHqyP2=9kqpO%M$Gs3a|o%Q=Xz{Hja{yXX)gcK44 zg*l6HZP6+ZCUYYI>M>?>*YAZx<|+kn>aMvf;Bwk*PZF=(;5A4qj7HH{hu@8RV~+T? z#z_lcz|T^rk*N-@ACKDvL|+DjsfN470A@h@wgwuOrm>hmIfpl>4G%*J9MJJ>`Kes@ zg3C9$1zTd#WG*01@;<==`SidRqoxB%H{4K@br|*;&slccW4`b_J8XLIY*k{Y_BBr4 z%iG45u#Jkg1BuJBQODi*qhN3RZ9Y^RJjkyU+M6A9xAP3qT{U@MgGbRpBgCp0S(YZr z#*MNdT$T~#3dwjwj;r@`?RZ$5R6ilE6KAm$b4*^xu37?sV+erC8}F+H%VY4ve zyx7Wm9RKt7O!KVW9spgjQfsloLepi*D#P;z_3?}yBr6*oW_97cnGG5WUBJXvLtAcruB;m~NvPmpq<<(;*WBiyy=>zuXeU`&(^?o6E&ZOGxtd8P zp-|N_$^LOiLOe^Mi!{vLv5%nBN3obk70T1ijzq`s_6LXH;&uF5z@pMQyt0wPU=J-` zL`{b|ryvxPhG?$=zcKkCVvo}&drpbuU>V)r-3SMc>9eORz?~dWJvErio&MfKR9Me_ zgH5|1FR02hlX&*-3!fZPwtm6#NVz!pgnAB`z35{(!rM(SRm>Nk37J9%d)aIWHuKc4 z54++X@AUl_Qo0A`$}rRJQE8fNa^+v9PE_GK9BE7_<_z2dLrgxp7Yl|6svu=5#lehZ z+|?Pu$_C^alDC8Rpg;hD1vZBS$kRJHgpODGd=9VB>!;Y~0pqJdMCu#HqE(^ktoay< z_&$zRQ-(#nXT4)=GVTVLw;5bf)K$G2N>VWbO58PEJ2#QWkQ^MGYy-aVSt`?<;KlP{ zB-!)u4tC^K5!=pK*-hUCE%~G*=(T6q?xCLlUs^&1Lbzp9I4xDkB?xEtbX#|f3#EdB z(BV4t@+2z41oU7LEQ%l`@7Uo|sBx>mNzwVo#Fl&ikX3<~fw7P{%&S&WFzJyIxucRX zBUL@`$jg5p!1UH9chfl310$VJCy!J)Ytw(pbamEO7|PGh^xW)y{gYU12tLdz)DHun zWW1p}$1?sow*m7AbLL(M^XFd?S{l1+Kj29eo!`RqxWDik+1wU5trzsW`)*oF{I}?B zz%O)xO8$!t_jLp*vWR%d;CgrA1jUfNmJI}NRG-A_xTfx(5;eUI6$)%O-OTW}cl^EL z2+a1eLj`oi$<)+@6ScL|FUzz{3Ugr0-z!4ct(MokTa~8yT6G4oR^wZVX0Z$jxk;p_ z_`~GCP>3(69)2(7BzB+_!~_{%MXhSAC`4xFxgn8P?_XCZEsbMwFx!XqYbZ}P<+2-$ zZOEUps0qN#6X~-LT@LyzxAp?Ky=w<{E=nj;1Yhsn&F)$4zkg43uNtOTpk5KKM1qh2 z13fQf)H~%TJ{0s^s%bfGFw=n4TwPnnRA}FLb!PIh^{b*8QmLR9p*^CsRHPP zEk{-%t2}Gk=pyhKb`HdwCqSc;Pg>&D9?#V549D9Y2MNXNJ196ZF_*umB@01SC0*)@}~)Jc@Tz zqCM#Gu9R9MSW$%;9-OTEuId{ggw|@z`SMv19{x-YAqWpROrgmeh0Q|cY2rUNA|t!W%SWeRUS_b7e63EfK(rJr z{*t>1lPK*cUl%?i$a^x;RTuR|opf~Nh;E~o?8pr_Y}dD2&ynqDnilYFMW#OI;IX{K^;pwIvy zT$YU9Ef`G`M~X2D7o3$?+=1>nRqvA%^oBL<2>-Ln*C|j#5Rp(?i$d-(ZQ7w4QLY4D zLUN00EXxOd6~fj^MjE3dL(guIn%U?Yp$5n+Qz~EDC~Sskq5Vfv5Fy(R98bc$D3mPf z(WaSibI^x%9F8ofo|+C>n$D)UECZri`EZk|ODn5VhqZV3=|Zi?8k$e_ zg~@p$iTi%Q3x(8h)Z@DicCPLZrCi46YWGBQ%U<&l821hLh?G_xX7(p8T<|UD54!Pj z!ku{SC(2z~2hZS@%H{sFAr=6aj8 z880Z$QnWmrp1(U9q zvYBrYV_0kt*r#y7q`z5GU$qn$<^WX4_9EH&L&eRGp-G>vgNT$!Spfwq21JhQA?PM zBQea7X}rOlj6xp9FxKDP_e!*Pi>D|a*OP#WTwIytqA8-8Tm_FhEmfhFqBK{~Y>1yE z&}K!J1$^aOrdO_j#yAnbrg$Jx*ZAlMfTT=5C}lu2a4tnDo8`a6%#b(HzWC>BUS_?p z7bsOVZ|=1cB`{N}LHMOX8`d_yKv62ra)yJ$y+e*X%n{TkYFy7HWmFx_j zZ!ixWci;YEC&?8`$xgRKg^(EiavqB2+xl^WG$v^s(q>MXRC!g*+}c9@pfD`VyvN6g z#T+kerbq3rs1M#JG?y(m)QWcxbOV2rb5K_~kTglq!12EP zIB=M_zZ-QR1LpA+paT3M6Oy#|4nBw)nOzy={LUEH^Kp4Vh(E$W zb{n#3*0NTrSc2?#cT~fubjMCGviOMpGDl?v3&=hFnWyzOtU-(49E7rXH7;IqPcfXj|tE?;{zukD#~EtbJ>BBPZACb9C{um z03+sB#-MYHSs4S`44FTcKe1IT$6aX%R2M|p#~#@B=^3_4|EN#3Uv*V#?nrcd#YHj( zNdOoGw0=lFxZ9MEs3OD&u~^qJR2s=r!=3uZrymrO(_uz4?m;)Kt@Ux3U~_AEU9L4G zb$9cySgrLhU+o; zxbt@ST=U%jr7W;uf=c6YO+@{<;nn+oJ#*-`xhcUl+L~Iqf6w^@HlXc%Hn2K)mbz`x z0htNl^ns7gk{-Ug+Hc|gf=jW|9!aRdK7Q$W{*{FG@lM~4X6gQXb@04;yU}_)Y4P|n z|Bm^v2GP~`j^p*XAE8WoHG%a^y>PbKi>7qBd;BK6DeGQs&}06&!{r9-HSxabaTrQ& z`aBpGl4iW08#GjBJ{NN|aD8T9p|ISl@E3#h9vhC2&LR4Ocs0o8mEB}Q2s)DHIP5}<=_-2@^ za%j{jW|H**bkc>@h6s|I#)Jl|$#jIPHmtH{tbGpEIHO_16N=h!Hq|P;t6RUEt`rMg z@6gF$Xi4P>C~WrO+YHh&#fz!>dea~&azz==;tuG8^3iWeyr5YnSg;t_hTB2N#he}3 z2V#thiz4_NT|!JoMa4%3@6mMFtNlHhA3)YEJ5Gyt6*_ZLw`%8DRKvg^$A6_r#rOf? z;0dAL@uoW`Y^)Ig7PZQ=+7OHwbRSvtDBW)U{onBYVZe#>5^Gem6n_EdLNkQswQ?Wg zhLE6@se?{E1&QANse)o=I`A-G?O~|VfQYgO+DOYa&g3Uad>3~WzB3g5@i6p%>+#B7 zXSDfOnoQEbO=wx-WEy8gk?Z2DAgjwvK$EW!T8QVEivV9?!_^|H8;w|>HkINWJHw%v zRuYYGT8BTtQu(g=>7HGd(0`Z0B^+HW2TZ3x_&V}z020GrBq^|qQgpHEu+3IE_<{=5 zNnw0RWNe(OTbN@zMUv?^Rrmoi>g2fh0aGIGR$RglviwoGwm8_Qj2CU~=M%`@wOT(JUGYcZpZ zGP@->E%)+7NFn&B|SBlFmtzlmxudt zm+f-^v~s;V=mLY~H528u1*{9cJz3YtuZwpP)VYRS;tR&su2eWH1o;bb!eBn+<{Z9WIX(DJiU` zEB@Dp&-aMqbCv#1%eTsJ=L4Vp^?5TSl=4!NV}BA1psdvf>U(~SNwu&{r!== z^R=l)k>Y~APhidYMomectu9Ja8G$h0i7cph1!3uX=*hCf+?x&Bvfv`b+UAEuav_&e z3Rfv>mluML0Uq;hy)e-Ix}q$t_Mnq%bEEMzYaX~4lOF{dO7s$4rD zP0l|IznriH3{*znUUzm|v9yOrX(Juq~HKxop*$k|1GFR6$BltSdv`ne1w5C?>i|1h9@uW`63QdKTk%_^yy$dcHO3w2h*qqiI7e!nmSu<+e%5Xpw z3B@MTXbRe+^@TCUZlkKD!PtnmB<+y>2k&DMtf369V88Lr6omjt&KbNsO~{TW4%8PR z>N&`^b$maeK+@*{ZTp)(HCrSIXV8tteb zW0w=9$MVIAx6Q;CAG@iTffa-W6IsL}S8ApG;SiuQw4Oh7Wltv}tHua?KuAmz$u!G6 znS9_A)WXQ#DJ1^Dh~_46j;$xs&+7~Q0coywBshv$QSvl~1f|3q|2Tv&6FTY{lH~t0(6#dTj-}J1gS&ju`Bb#dSTO@JmB**g=>My`$JjlLFu^{vx}=OQSIjvuAh0W zRnGE_YgB<;VN&94qut;?RgvuDGG3UERlknjJjxg}-yxmKG<{N_^SUo{Y4NV(^`i9s z-lnpXKh@;f4xuX@m%)ceeHw2AENb+D-emX->_<-=V))_=Av4GjJjEH5F3`EpDM)xixsWduRC18=>}QhxV|{6UO&^ z6YwkLO8Z&;q}Zu&{XxRyS7eP+$~ z>wxtK4wsx!fxoe0K|^SLYNE)9^Ux7)TQD>$>})f8gkvyJ`*NhLj(QZe6kmv5St>em zY=I{zBpb^*nPR&djS9;+6a1JQ>lM<4mShxU17ZJb>cpTXXOlT6cA?n=SO1ybI3ej> za13H}=J50+9*L!zT@MoT78sKbkLcY+T_;~g171!#Gm}P0E)kGo%-0_P~dzkFi{e4F9`O zhX-MRtm663WBoNWqUtneJv`tW5E52k@UgyhSsv<%*p-5jg_F6{j`0k#5@)W zQ%3<2rb5p*FPkDNnbSB=ETLu93&N&+@CpN^oci-jU;J|o^z^l``o=&3cA%%CTA}a3 zjtnm&45oy_u%%i8O*kzt>rp9QhY*LpUl`XoGTy-wJ3}Vz^#EsVH%1{TD5MfJJNX`( zP6met7PPy|_VCEr>!;KiTYUEwU$2N1RRNRb9chKul-n?Jr1_mP+Qi z4pJ9fkeA7^5>;i_!Gfu(${Ds_|2)5sZF97zZw z0TxKzz>HyFHaEh2!+N)qF_?-LS+6)~5UMbl>Q?N}-h8HIDQ;Ed@!A#(T$ z;iD%J!!Q(UYt9rj8!Gw9`Eg#p`TlnC&WdiPdilBF?cCk}koj17bm<_9l_)WtC`{9) zqWSPLh^%meHL!%_Q|{UIl93#^@d5sxs_EzTg8Kf#C0&vAci8NpEgqJZmbRauZpnQ5 z2nquB{%G;ZsPTTP_!W-ZIw6wkz73+yZ94wX3N^#;-}UAZTXmO_o>s)T6141L#@D8H zAC*&YaDR|)SE<(1GtZneuwm*U3zsisV3$POU77jLl{mTArG7GZTf1pG*e{%SZGu=v z6C_{0>5SC$AWbNV=_&$Vg7wO_4o|{tncM0~7uWO4W9^aI_28|wQMTFUGqK@?TtJ+7 z-7`)c)_OgHcpwD)xrB zFVT}^`}`Oq51gUH7>qUKF2_o(pr+)Z1c61!BFJpU6RZqJ2ss!&tzNSN9+d$QUgDvY zmo&(5U7Ki%iy1T7^w4mn8dOhnaSTO6!n^^v#%EW30j`v zcgkl5>Ctc282i&#Jjf<>$;#Ezn0lJ|q=DlFG-`M!y}(A~`={93DMX|kSDc@r0J&36 zKFvlOIFWeAXflin@J83ZoTzWG-AyzX) z%ngSjLY$IgUjyv=>3r=JFwV>@M} zllLEGr<||MwHLirJ>yj%#eXQk0LW5EH6I8D2+SnBQ`gSSVI0|sa`wN^IK0J1s|Dd15gkkh)?(4JY0ByjWd0NmL_;RVtQnci^ux@W!)udO6p8LGh zpPTdYQ5;7yDLnCnhUisO0R3@99|%9SXNl2B9E0Sp;6n%d7zS9BM=nRuVVCKNtflnS zNctJF9`+5uFeT-)ymE<@k|7sXBg*Q@xoDtcbO;LhfcS{8a+?61tIi9oQ!rIjB2BHB z=gO-}lR*OY)~)0p8%h%4Qu~v+1zauF7zX}Og3JSvW3{m-(V3u%6K}K62dcs@e`{AR zqfB~v)IkX6#-K&;a^hCPJ?%NJuj?BwKg!Z^2vpr^^%HW8lw(OW&@?8P?89f)F! zjYGUi!V(k#5AzetSmvkAFs;Kdr87_4P3QOX0U8iCj7?+6hzk%53h)5U%Ab%b?XUXaK1zlIEk0M*5p1e56Z^vYacnW`rM>-o(Jx&c zZvorhUsx1=wvSZ0B4rt)ReUfMu}N&SAC3DpPC21A6LknN!#Cxb=)IMm)Rd%ZVQz z1z#{NuPQEcX8nWiHZY2-B`LLL%%PD1ll>|!R-o$$Ze0_zesAU2%;ZgAgbOP2-j{Hz zM>-E{HhtlYpViG~oI04$H9~KNpxg1Hjm2jXE@7FDG8t(S4Mg8%si*jenT^kv|t8{kI6tb&DoH z*iUcW;7U*J?9ilees){TN?}8U!3lz4foxQgc-QX0sbf?OQjUWlXRXLUwapH+<>#QN z@W7)Vo=O55aJ(IM!G3S%$5-PU*YiOBbq)7NrUsK6BL{T$a3H_Cd;1#=X3tDsQE{+8 z2vV)b+Y3S9$kem_pkBj!U<}m@+c&*_YV&cZEHaBDqGrd9_su{YVZ_TpS$ltg(|q62 z`+kwrQtg3(_vbvGug7f%G|RE2#rElLO>9G^<8wpPaX{JsT5 zc>GBHj*>ugIVNzdDdA1gOI@sV3wgQu(pjerSw^MiaRFDTY5im4-ZLWf@NB>RId*fl zs>OKvioWxKF=Ubh@lQjDc}DAAtDXdz@g-y>mZScMo6CLu2s~HsI;Ps_u%Es1?aroD zz1!W^pNlf~VOK`oOznOoACzvqdefNlsC!Ov94s=GM zdz<7o>EY{$Cuv2R$0n>|8ci%hL~m#X`>{neo&}LiWQY@oLtTF_)tB!VH+_I)LPAhb zm5!fd2~ei^Hz-=tMEy@IDTVt;`&`3Gav(yY?G_!x-GgxN9O9kAS$}DXpuIg-4$&;F z8EZz581jHz`hy(_gKSG$d9TtC6_5#aKEwYW2%d(Q{~dll0s$xSarIwVb)F_i{}n&b zmMOaSqa?3#J1pg?@VizZ7lMxkCbx}ENVf5a#~vI-Zs=c@_-f?+9CyG68jcM3g_2nSOY#cchiGwi_xvv&-1I*=nAFUnVDEIyA6bw{I6!&Yf1R)M zE|yxts&@QN!`Z>#y}uZfi1LVK<>PrP0ugDsQ-DjTNO)Nzw92!W>a;p*TIMLIF%!^w zOJ&7;Q~z?oyFkswBdp1jcfw=0wZs&oy3F7AOGj!^t5$V2I}2#$Pqy*mOBT-4Q3JH2 z?o9x>z!d=r)F{;+#$|-0@32P?4sQuF>#MSkGLRMz=IxE!_uq-=h0=n z?=6eo?Ji|{5 zQ6U-$(TA-Y;WL$Nty*cI{WAfYFfAAc^ED#avc4}V-hIXpB=8@$C-dNz9H2pU*GN6> z<`#6Z5oo#+NeAISBP%4lFlk_=Vv5xcBt<-v7D|OczUC9+kW-GCVh~vUiu&8N<}0na zO+iyul3RmWLCas*=6Bjnp($hl9!()se4?k$_x#dsY92W!Yay#>9?L6E%Dfd6TP3Q; zW?SyiCAd`&^7-{gmw%9tGs#z(Ks^woE(WI}qF5FCplK{}fg~XzO`^<)2)*ru&is>1 z+w+b%nOZ}@Bxec`L7X5lsdpiI_~fah(hnyv6@Y^Og2n?2g<@2Uqw>(7*8a1ldvdtr zXF9ouc!J*Qe=!^EI z;4=+*%waHYU^Ig#E}Hv;%;Pg?z`HQ-SdjZks`6*3>o*Iz%--FZc$}605Lqe0E7Wwt z`6#>V`@A_&x_v}EZVDJbR9_R$)7CvwGj*EudzH(Wb^XdVRF7ye&ZM-S$x_)JeN|A0Z3|*yA<+^VS^W{zuvwpKe(zGIMU5QPy zE_Xemz5Lvs*jN%LT}fqpRU{r!8VUN%-w|y0S#1DSzTKyvIW44P5uOJ64;Yo#q`NTVI2J zjKAG<3^q1u>Ysytxb;`w*ROo^zu#15ms@j2yxkb1JSfLTY4wleA5`t%T-Ivu*`2#P zNX{~8HJ!LowOvT1nwCC)8GQLo)xq$H#rDIYVR+>(l$GI1i9ABQ*?GN948$CUypL|74o_$Eah`hyNG}=VO%TK(E zwtJ>2(PUy4&Uo`mhx1mgC^Xq1expSMU^1 za=Qzw!rlHwAScINaquXwH$;I9ci3~e`me*R;jEiNw>!^5w>SLnmFoP+=$T(wHZ6XzeKrHT;PuTTg&OoADy|1sMtG2{n^nu~^30iBLc{ypv0|ok>^~2h z6rDJelWzZB(zdBrUcl5VFG&3RonPI@jz?cOBK6plmco)h-$5WZi;5Rsqvp-`(7kTP zpS76Lr=3I~H;b3Q`NKr{x0dfFZ@OLI;-E?1^hxB+m_n?(k8OW{Ist^aLLqiO`wF)H z(9!tJg_CHPnugF!wCoro77e5L^b!iE=AdQ8h`M{QG)EpsILSzLKtA=L$)A3+bP*TX z5pFbel=5I$nY6d3u)kWcw_2y6AwW-G3NA~M8jB*^8|c}$o1W@Q%&JOMd%GhiWaqaP z*2jEs+3y7+Y~Vy;GfHs#!0ShIg^;ckV$y{rHH2{6M(?@{%jZIb+=!41k?O&nn~FCx z1U?UjTVi-Xa|wJ{XpIdtZCOin$Gc#3*@=8{@Uk1|-XI*-glada+1$$LDH(WDGU>6} zDL1R=G`kq46_Vr1LKn8_dQ2%A%qF%(Hqojz6DEc*bE&Hp?Ap>q!`>E%M!<9&tFfiX zgw$-7LO`c<{&6h4_(Do&&Z0|qv14aBo3~fc*cG;u>$YwX_*`gdLGXF7WSndv(JYf- zI0of=>0J31(e>M*Gh)jNi2(_J2bjL<*ZlAS{vaj}T6BJPwDM%H|N+j)FPc9C$493R5p~(3XqpQpHJb_IG)&Rchq2FO zk7*%Y`_M!xIi#a2H)X`eHi0!My+*5{XZ$g?jAfbe5OMWFlHHi{Fgf|n- zpNpo2Y#dgPpgd_9k#$nD~*~;i{{1-TB;33l;)9Dlz}hUh7j!t5w&IH66K{KhhlFC;RxkfiDTn3a#0zh z&D}}* zo9L6S>r7XDXMqc^O zPpMn~{*dP$|Lw1kF>WMVA9{xR_cr6p&fvIjUCy{O7qS1H%~ZeAKX3M=r*Yo%e@QyO zaMrY>ZF^R&W7Q2mAAFtfe&w(il4TT+=G41?f@PSjyX#NXZ`zK_=V9ELix_kIe9E3& zJ_vk@M|}O#``Zuiy%mfp;m41?MN3@`fB4QtbhR}z=gdnu>AbIU)z9wZy3-~MIw#!S z#l}~kr+m{|I$IhDre`td%uARsYXR5)SfpRryIU9V7@2$!JnRE(%i-De0L3RKKpl!XoQ(d z&f|nHTu4T4A-7+;V9@+NGcyxi|L}Q4V`Jlxi&>v~a}zJUvz1;)q2U)#yu$CEd7bX? zkp)d2c=;_-0zSUA^kfRMQdu~ygqtrqgR1%_URd`57tBAN&fdY-?CFd0#@2GSRPCds ztB16pk0r-X=Gbu~`OP;j;Lt@%LBP6Y|?YOHp}hDFv?z~j&N`Xv&O3#*JE{f&HDxso{OQI1Ji^&?rjwHvI;86@m|DV>OXl&) zrtRGQ-0Nska`~xqm@=x6E0@gU&c~J~gDt~y`Qx=0W0)3qJ+_=p6*c%gF3y~F3}?)k z%#&+24hihU#Z0c zqk$cbPcF8veU0sFUt`L=Q^_Aw!c}+PPtC5a{OanZbho!~!~M(1$SwRxDXYU8e2gyU z$LGyv&-P7;ZK3omzI4lPm@sqx5d%%8o%}`SpMM2SwY&N8xhEu1RA0L9mlTyup{6XE z(wv!J#5v!(mEMjvZo6O(?Txmy(}tDLQnhshKlsygeEG+}=eko$h8#p3?z27lyITpQ z+U?}>AOC?^G{VC_`+omNzhmf?2IZU9^4A}KvmfZW@;b7Ijr!2?n19~a$Qw1D)sNrL z|Ni`j#P-@Vo%v-UU}#b2mOuw{C@UKK|#S0uaBOz6o$<{mS|TGE5GxT zBvV|R1s6JIG>Ka`+4ar*AeZuLE1el-=e*$mJ@FHI#aK@h_wfZ zXvS&ng(plSb>uMS{_q;wEBEl)bw4B0(Z!tYOWMf1Q|cRlf8(C*J-&MlGJls8dc%0)9Sj`G1^*(IsK+Z9x z_C2HoNgZCm&Zk~ZtY$kBOp}%!<*fbrp8yy(dm2UaW>Ed=ThzU`HE~ScJMSN{x;*vD z3kc+9vE^@1vhnV}C${fi@g__D{9oiwo0wGoZRIvtno`u?#>OpO%;-~3L|KZRPcFmg z3lq%GrLDS#ik0v2e@@Hl^AtjXKSp}7hmlK)Dfwav?vVn^7ePs5z^dkcuLyMrsW)3v zMkEbA6%s@FiJGZIdP3Cg6;$rjXlTlyyU&kiNJ4<@omMhvvx0eJm;Us;d79Bg=DSNvC zZ`yF&BQ*BYM!Tzz>Tn%_9v42{jfui=33|0&I<#g?1YS{u(UHmSck9`_wuWeZ8$=9S z{z*xYmhHhJ1tQHu@!aVwzU*Qq&Y444N_Ott#k*T~P}?0wDzKylngXv2krF_8-L^5k zWg&z>H!V_Q2C{rF?JM3Ox^_FFD*{s4OaL{o_SofMSql6bIn&2+>ZKPjXUQpKWoJ=I zJ*$jzwz@iK*Brn^^&5jH($J1{lt>6BIX)rE7E`h;g+$^&yHFXXi-9_v2^{BdDuyjx+7-3uPYRL99z$-|aKm=uU+cIo!zwMJE9O){dkQRt= z46C7s>a{vT)sQ>cpsu=y`f`ER)k&;&C&r%bi0%$&+#nGOEMX=XBbE?=3*k;h_vIj5 zDOk7x4dM17bU&IWfHG1s!tD_21gqD69*q7rG!!Tx9O<^cZd7dr(bhVQeO0(dOd@sc zF}Qu{^hQ#t=nm3Y(?$M>2%{&blb-L#?Q277T__PFSp;rKl*y6wG8rh_rJOn%)eM?t z1HEQD&5?Z+i^+`lOdumQn~77$l9QFq@WK&1`SjCl+OmV@_V8hLdFn7glcRvir|82B z(u4G*GU}9h0PJ1;&Jn$WrfWQR(YF&atqmKuku#-)5%Xt}J*6ZGG)Wm@&$HU94v*dK z&&^`mRTmQN>gJ^@ZcHo~t9fHREjub%_{*D_b={Rbecm-gYUHGjDCDIpZlrZ*Rbrbr zErl81`x^PjO-ceyV%c{Wrz8`))q2{pToz)z>=uGm*l#rH_pU4-)aqLy=~L?&F<}}LPFTR! zH(wca%)@tlpLdo&VFMO>D=8RL$`8+(NnK?bQq%bAPyR^WsPW13`}410(%i+Yef}TZ z{ev$hz>zgiKgj?4^&_0{g$tSe#fy0B*@u(P``h3C#>kN)KlIs8IN=1kx&}*H#Y~e} zU;i925;KUzj(m>qrFZQ)+BIh`V02y%KRBn5C)T{jub+Aq)3P}4#N&pPiMs3Ymj`Wo zZtZ6N`h&|U9+t~-2gKP}v6o}U4CACJ z#T+ww7%Mg;qjeSg8>!gepPQOHY1|=$CdI>Yx&4tBd464g9;>0fi-&Lg8pnaQ+YwL6&pGHk?9RNv^ zR{w0A`lV~^58$tVIHacMfq)6X%q8dH@_2dmAAcM)0DAwm7uZ*^ox-sb7&~>=kaXO` zeYSNkKAQOZmw)to%vh9HAGzlvpRKNHCv{aj6aSv~A7B1R&}8(K;{o{Kjb(%O?R@Vo z_&Q9PyZA#vlka}_yZr8VzoVg{fjjTK^T<~~XP_tDE<9-|gP0+4`KAM7+?fjzn#R@# zpB@Balt23lQ?I;$kqhUr_9H|z)5nhBnG3%~WPk};v@6+v!ZJ*DuY8M=bC*zb;!G-E zdOfkvmz7S=lu}yCDulgyU+nC1U*!WHnLEQPWZ``_El_%j#bNez)bxfq0?@%`-dvxt>=W`c|&oB-&a3ym5K@PHlTp4KD zCc|gvFn;L-a!<%W&y8WZVveVPW+yE%9Z4FU1El*CiNJ#wr9z@%;lVHiM0(R`tP4_6 zrcqPtAsR_V#N2o+3vaj!QNNeYt(!2bcA~o4QPEh!I2{p}ptAim;;seu>T!7pFVSJX z2py@pk-7)v@?&{Yk=`Jd<^oR;G#AR{K?DK_zZX|l2t6l&%5)<#y{Ld2;nL_+CNUMm z5(>+uA#{lc179qR*3dxnt8Zhxvl)7OKv_;w83*k7NYunmBsLX4AX zWanptJ4~mmhZd~|-65#;l|V)nMs*i$&+nxBY2s%Obh{GUmuD=}m`7 z8!FNU(RP&44azu3Kkl&1ENOe6SQe_g3#+e!Xj2m{H5GWrOh(Tig&EACGm=JIi-(%s zos>@1DJYc$GhFDNUZe~oF`W8f9nj~*nRaSGZB&kby=K31VPXnJuNGs!XrSHbp()nL zBsqm5?-+)S$Y$wTr;wMI#f!^V@YY*vDXXl)%*^F7*k*N`@< zh^$HDY2LDP(D~aQc_s-k0cdx!oxaTUr0r%TnixlCA5+T2OP11A*KkN<>-r5_5-fS? zb{`UGk~P8h+}!u}hC%xpH}4>4N(lusrw#&5;tFNmA4B-Wh()>YhgT=bUn|R^Zuc(6 zrDu_rJs30@^0Q$Y?09DtBPL8Er)cz$$Lz1_Uw$9$vv0PpvdlT(#EeDpc$4Qu3opDX zk?%N=P4es?@8EJnEpJ$&rdUaqj>Hw4-q};-lHmW^{CPaMMjUk&%(X z?Af!q>86`F@x&83=bUqBXlO_}HYLT@Y@9iBCbMVHX49tr#POg1{AYIT*ugc|T*F;= z-8IN0{%Cx9;?1WYO!Cn9#Z_kxFhe{tVH&YP+akSoFDisQBG-_b&V&;d*dW2`Wruv; z_+xDY%#!wlMyz#pFyWC42HX zV!eH|?%GYTFfVcJXDQM~562w}(q3IVr0IL}wq4HTZ){>)TU8CpvPc=0pLmBU!}59m zzDJpQ#f4-RkEUsJ6){s#y=Kej$H(NKjZYiUqNJUo6IQ>=_rOG(GDw@CQ*=rpV^1GX z#>7liY6MFQJKu=7%Y;B82b`-C+xs6`A>-SWz(RtsSB@*C5m9M$^`+9@6r!P0uzRG7DI->~9o0~Wj71Ul57^L%7?5;Nh%&)}tE25_wk3K{8UpEn z6cS5%P`Vd%Kf)bAxP9RABlLj1&!wXReq=@vJv)FX2;xmoK?{ZueiuL>V0&qc^hN>R_Jq#yM^xbUCL%PBqWI9><9~?EQHefnJ{s&DPfzl z$C^6X{^oAFdb1cmJ&&AGS$I=I=vp79W`GcwO2-O&&^oq4dkz;825 zl%uxgl9FK5L{{ym`?d8%R&NF)mH@Y;0%18aTFRC!vNRLn_cLPVBo@sSLHZ4Vtz$vFj#q_J0(3w4GSIwfSm;=Z!z_f;u9UK+t?)w5yddMxDJ5%a3a&s0nv7v37%a{m zYbFt2AHGr_Q!kr=5R$T|cOg1lHj_&^K#Wj>Yj;A{%=tZGZ_DxN?_p(G_Q}=m*2yx3nN^9(0!=BPK43x04D>lE{THzHg_ThmcokI?^ky# zl;*<}(q^X#1(*;HLyN@hlSt7`q_ZB?yayTSbUan)@1LA|v>#swEr{mMMY^&uh1UjH zoc3VLV`;X$o+UwZqX%?^JA?|SVe~a4qU|WF4&grLwNIlhJ!T44afg_GptpY6~t`Cz^<0shABwC0Ij(WH3b^ zP)i-goWm+Jg`iKy2)o+oiEW`J(!>P4iDJEkjI1nX&YMPdVHRV?7V*l8S9$y09rT8U z{Wh%r`k3gH9Z>S<*ZDI%q=+eM)|Jt`eK#Ft6-RvRYYky2ZN$9+4w!FfF4xDJKjs`` zdy+P7-8JO?rfs`O8&yR1gmFWHCdwGR;KOiamxTQH4!GjXf6e0_v@gC*nFp6V{8+cI z)F3IXf%AJQ5B`2qvvSEFRg5>3g0AVL=h}XmNq(k>9sLjC3#Br0;&j>?_tRKYHR#Cw zm1S6_K~eFPLHq8%|NhTh`+V?~wMl@<$=Cl9^FyTTGD87|=VTD@deEdKC*5Xp=u$iK zd6pq^C}BswOdjD}nqdvmkC+ywX$^U;m~(GK8P9v%_BPA1KJoV_B}e0E95g<&ph;O- zS<-6qE3UYLd+)uM2OfBUB}m+{uKe^1&sW65~{YCpQ4iTXxb_TSvi+sklSI z1RG`K!r3U(WcP|Uj#zp49Rs|chL%G<$Lp+C^Q440>mR>8$e3H;8Ul>?IOYFw!WHll z>$O3X`uFUWX1UW!$t)d5U;9JxEVWH{r&RpposbqJ#ep)5Ob?ELs3uZ4tveLT@B zl6EG9mK2~g5N^=?2yd#*ByoGe<;T>0D9r(s^JRCLYjE(kqEn zT2ehy0)!@!ZWqc0sxv~(d#yB7_fj|}jqCy!85v$eDPEMah{i0snmTAHuVMdt^|b7$ z!)mOD?hf0_0f*h8KuHZ5@-zDQQck@1e2!VTfQY8^enmN}w`^liPp<=VSSU>*d~USV z0LrV|mME5ikOH^VNijjMuA}MYH5hMfu_eHSlXw>rr4*X7SvCUOQiXt=uq*FM)5f0x;kodd6Cb;N*psIUlT2@Bn zwOY)|7HIB5R~lAa;7i!@O7?YZV{>#rB9MvjreZpPNn*0?JcH#ho^Z#=IWe7a=a!N& zErbm8f)>V7md#|5GO1oFX%X|s2pp5f1a&;75L7%{fod|5Qconu2375(wkP66i&fu6 z`&;jmR*;3qKb(kP2R+W_5sBBJ1Sn5^Fasf6ZEBSUl3^7>ZH{Sk!lB9uTyK*Yk1 z4@dlD>2xMG3S+lP6JbTM!nU`H=GCyYc$-k5EJQ4Zh;^WPTQOqYSl!J?vkz&3CFFq2 zp&HO{Nf17CcLtg}6HE9}xNLTb1EAV~gG3>1P|vATn(jw<1GuFRO9n8bzN3+l001BW zNklPcNQZ@jwHYEWnO#z9qOBUu`JW+=T*{o=qOwSnSuPGjsB7P9&N#}eB$kDKB1 zW`I)cfA@ny=f#KPX(>nYwH3O0pJ;i+RV+ejnf&~vEeAPD5J*Y; z%(u&!Ve-b7^5nL9ka_yd;*osgiwh_np7(FPXG^J1#r59YTERK80 z|M}3XtY7vx(QqHr7JPvjr=3Ij#*>uk%6wI8;usO#v{G^#o zy7X+upLGf=uDz9(9Tf+>7W=)lSJx)_k0b`mmA0PlM^W6($f47aE0?ck>J=9KGUiv@t189S5$IR%qY`DA7(LpejcMwA4B2XT--SpmWaj$YXb+blSdn!a-cq|>QFM`(5gi%vYU)cw!+FFQ2 z?R&;N^p`Y*oRLZlccpFK?h1e_1nyMuq@mni&;`ov0e=vY8bo9UaODQj@d` zAAK0oUa>DVi4+3e?Lw0pQc4U>*vtzBE>q!d@50=%i_Yb%u&Vci5kX-%&l69CA1K|U zK$!M(XdZC6u%a-|DmmK@*%67({D_7vt{xRf~y z7n7Er%I0V-E6fVEyL$-hwtorOzY_;U5`3dLYexwz(eE4~o!TfYaQA_#?4%iHzgY(1Kkk z8FkoJ10}6wQj0RABIb({nwrIAISXBLQ}JR2vAQTiXoLMy4w@`e5L5KMwjC=og;0u* z;HX@>rL=*mIGaK#q}89a@75ILgz(RrLaOGWV@(-qcNCy% zJsX(OJ`_5_ry+zJC2VOq%`y>YA4YF0n0;tC(r^RbSU;F(kC}w#LU*U(@nt&xLJkZz zK&H=%f4_vy3Q}kwbd*#!$mr4#G5~r2BF$j5fz`{Qb1xK_2DCI|v~|(jSch-SG3dEt z&_WqlZ2`*P)7W1bW&9M2VG|^&dEoYTBee(+#b)5SoLfTikKFoU39Q5 zww3*=fpMyY5_btXIa!>v_yn@^awy0vV8zR?vuV>VgvZ6v_#6*Nq#L9k%r5>Na9Erf z9~zc>cz3R8eCQxFL4377b8lti-4BsjI)-UiT}ZGnhlY*Y(Y_4FZq=w)y70(VUrY<;x@89z*;offU{m(1-VD&OOTN<%Ulk>j!Q_lLv%}217 z+AlK}2~)Fc>yXFP?Al7WXYd36;upX8NPp5RuDF8U-ri5eyEiRsNb~jsWA4N;{P$Jo z5spN;{gD@0yS<9m?j9^l@r^T1hzN+%uHirMGaoJ&RMgk zk(ZIm!>_HUs=oOXzmG!4%j5{EeIJd_QRttihiPhRqM)FFyu7?5&}7FB+qYr(@Zm$A z7dM6P?Cd;RBaX%=9v#gMD9gg*58?}^(ARxv^{%7IrXi$e4((8tnqhBmI|S|0Ph%{5 z;J3^;?OZZ)3pwvQKS6hUXxYD)r+$AMFZ}tggoM|pA&kB-dsnSv@2YjI|HZw`y5Z}L zJ>z7K|Mul9zxtM>V|$xg@usJ;@sAJFSzY@txxcDqZ!q&YtbHipzb;X zM)3d22s`i0dl1o9pChi^*V>-6datc~52F^(C8aQr%+fJ*?W-r&(@VqqJ5ag{goYKd zI2xbU6ife7V}(LV3#A}9#ZAds!zf%(MC!zJNHd6v2%3@yrR+NjKnzeYN+g2^ic8sY zU|visM0;m|_WBTec6lhP(jl6LuDrPVx^Z>XV>H&#S+NbPq8xg<(NSQ5B`lOU^rvLt zT_`c&>2+Ib$diKfrXgKEl;*Qn>-v1iG#`3a0J1~qnLgZUZY-Y;Zit#45H=A4JkkwP zv)LaAM9j3;jw>)U8%UOhh29;eZ^sV0SG|F%+>fvfED=E{i-9Z2Z72ACc8qWs7$PkV z&$wcAw};+}a;(PogxNWo1h02eD!C2Yov5{`d|a_$Sdq(Eec@E$*rkmjXJ+ls1cLTJ+N=fhnP>4pO( zW(0|M3uZ#z%8GWNxdoa`0erT^m)V1gb|9mjHkdW&S>nGf6Owd758?7;pkxpwT=A~V zk!Ew)EV=}j4JcZ|1yTcLpiC1YQLI=DY57quKgu11a0nG{0;AJzABTua2`sQ8So^B! z>1an5?8QI41b0>uB9Kn63h>^V4)*PFQ93P$%#k5NSzYL!PLzr`_ADa7ej3MZ%!K$ar?4^}vO5|9E1x8RjeI)7W=TJ0qI4{2VGRvM{X-f`@!|6K@jHBF? zPwz)v1I=%@DXaTme)-zBHZt+zvnZT3jSud9^w8%3E%qG1bC5Bp*BOKMI&+23QbfCZ z+4jhDob=Q0kzG2D5%W&~Wzk+$GsqnNz*xHxPMHVCO@UY6`sD8MtgCNANXf%L`vI#T z|70bw+8XUe$5Am5MwtI^#T;o(=; zbNl5>DIHP3f@4asl%lG>nOhGqhyNtQQ5@&(jYr?f(HI<`8_-18b;gae4Y+D*l1+}+ zueUuQ=FOYOZ+|;j4rbD%NdRo$zWr#8_>4zf^H2<-2Prk}Q*n<*EK0@u>linEHk0R` z!aK{KIHdC`H^0mDMQ1T~+Dz8H^w^;NW2epnpzNL1pUHRk&0ju5EEeU4(?IvUVC{jli{nMuOA65PX zqp+}$JMOrHl#~>H`qQ7XapT~Tkvm|6AOk^@`uDa{dfpkB(HKqJZ2EP~k>_$B1T<0q zV%qbe<~G`^_G0vfNgZB*CnY$@*CcCFvHfm0B@a?tE2;qq73GsvI+o_`{R| zQLN-so?Nk$)zZ>z{cAl2{xCVmrBb@Igy9Pc(1&Z7-Y_OM2r%T&4EQe`n736)7A^`6 zQ-p|xGij~&P+h6BXLpF!=5+L!#BG^~-Uh6?-SqA*!>rnY)zk!1bCSajJD?vbIK)N} zzY7VLV_Gjg2u}#*3xO|$a{Dn|9tip&HHemziZ?$USB4Ma*U^Zex*Lf!G-Jl1NTJ!f z?ZRI7uHs;flW^8-FLX@ltlp0rh}KSewr)r5t3xQmW{)J5-+JE zGin^VsfhOV5N+w`_vX+9BH)1(FUsRVX&Oq!Fq8t11Q~`{55mA^wuqRMtOjA(QbEG9 z`zR#e!mBoMEG3Dpbt}*30z7FiH;yO zYC?GpZOdLK`ubMlQcgnI@;&#@7(IA_A+Tljh~S=ze7vYF7iQxgFh7am$S4@!qjU^?)Q%}Ge zmPMZ~h$@>IlCU+hETp5l)v3Tr_u-pXf^>Q5)ZfRfZg$!x2xXwcM&fNGeSSpTC&UJY zEDWqjJ1*U2o6j3jtY|w#J8jPmB~b%qzLMIB2FiudJZSDTlomos4@$U6uzEBNJRXGC zjqteK&4YZ~ z6TVR0jaa){>20Y;D;$TrXe{3BJPdyd^?OoiZR=v}I6+Z~M#k^}-qbFn)|CvH9ngb` zA^Ti`5)uPj6JAP#ZqdpPtAhr!mT?qQ>?t9ycsQqJE~KEah}`Tv9(d?Elw}|_aVU>= zG(Kw~HPC#ZBDU0`(=UjjlF8bUO)xiX+)7VFGue}h$)9lyb?-0Se9r!eKz9tv_{@_1-yfg!Avj>gg8GX>EuUAmOJ@4g$4$1`Z(ZMWTq-|uJT%9S)WCiAK` zY}ml2O`ACL%rhx1Elt`tWy%y5Em}l%bv19i@y5{_@fnYHXH`Sq$Z<)7Mj<7a{qVP+ zn)`h1fBy`?1wXhW;Z@Q<2G*D`Yr&v%-g@p~Oe4m;rC(0ygvR6eQ~!hf(Z!T+T1#Ek zXS27@9WfTKKfw57=a80@Pg-^!DH+*^40b*7#1lO9&_lfP$}3!b_0>oG{xpx9lJn0P z(%>(nWHey2N)C+j=UzpbCR4t0F2RDqH1@2?#UPISEVgRd8wgFKM#G4bA6GPWEUTSrf+}vGN}n z_uY3NmtA%lOP4O?#TQ>3a+Ok?IWiD5seO9`&tCj(p1t@xcG3kK`~h3aYp95?lJjN$ zYqWa%;;bLXGv;7n875UPzk!gFiI*--?t5H16E8W_uD8#>mb9;}d^Z3&lgAUx&!KsH zC1@eq_q5Yh)yC2IbVl-eW{1U)V=#8vfN!J(kLe_t zO;23I5&~1&GD`=4w&XtvgJogbo*B{udMd(~g9sHsS}tT}p$*GMADfRpVHmDy!*ET_ zL5=o8x`B!`65Y9(j^!)qdv*oU=T;JVY8m0DUZn4-7YRT1JdwwrBl`Gr#2$N&*yGR9 z_r!DbJn<}%$DgC`(P!y-;(5B4zmBTe50;4%%CV@|Y>6Y`7*EHgjD+nef~G@u4k@$e z;5lYG;etZKnHi{H(B7|W{nA_kA2JX?c)eI|9m}nO%lU0HiZG&JM8JsJf6N$|20~d# zWg;*gw{{&B@{%@TIHz54DW!`~hERwdyZ7?e2W3=u_c(qO0(6jm7c$^Oxinj{Dc)}i zNovGGlY{pGyy`%Ldgn&XaeOBDn%%Y0KvDBhQ$X81PrH(B+hDO?#)YCaML-s)16oiF&>F% z$NfuC&{t;UM^7@c`gy#=eWc1kL_tZ^hSo-zEKVrs03@`G+JHi{hkMo>GMAl?p5G0b zB$CeJq;sf529y)4&FaROiv5=ugb*RXhDCyMKSAjjVeuF?7y+>uQG}u~qN8Id7ArBy zS0PVS67gCPw-uvPgxVn#jTMb`737krwq|rkE4rf@>B!(sNipI4cG{P=(S1P&%?q2* zQ#07MBsP=6CX?7i7Ek~+?SWQ&-?0k zBInH8-VU#3RN_GIQ2-{+nm^|8RJMsFtKI@c3z1(bJn+qb1mHcN`aFqL=A?Ru!<@61 z{Z^f^Cx)U0chkGZ-m7Jj2`)S%BDA#r?Mfu?YRTa`nu%>K%%0RqdsBw?rVP#Lot}d>*<{B=) z{BnAGd->o8KRE9DKmPHL^V6UHl!qUFn7{t(zh?XP?aZD%n?L-+KO_u8?zrQQs!E}O z*9OkK_$p>BUP9-TnbD-|`rBB1;T0U(v6(dw{NS{cfdgANFm=IMeDHJs%CkTDF1gkY zR^9voT018)bmG`+LUHow_rAuG%ihWbZ@Pic-m`&+zw@v39@{@?%rNpI|UOMLYFMs*Vujqa)`QWWAe%G~}@$T!`_2bnH?mdWdT{_R0$DFI9 z{e9tUr>>9~*tL&!|MV@EeDIxId-vzr_Wk=A*ts7!n`Zi|WlUJIn5X~xpV<1Xd&azP z(()y=&!1H#T4rCfib)ro&A{#h9C-Al6(f6o_83cl@BN(hz8hm}uGKI4`Z;gAg6yP- z;HZ!ZZYIq+bvIhO?pe+7fy1?%rZD8uKl>tY{M*m4^keU1(z3HT^yFHw(0S%uW?Z(C z<6C!g?;U?UW)`yjdp}{?rOTMK>>RH9#uwQ6!v`5Ta*WKx4(42a8QF=Q{OI;SqHo*o zU%LJ4F>~h3>hIm%-DENuhKI*`=~k5{`C_dPmVE|J%3leKB}@>WR%ue|AEI~Db~+c& z8=hAKJh$mD#_eyub^Y$9CrWY5so~)Zp?c|kDZ`2G{TH4F2Wka z(G5F5Coq0vcKKSWpNtVQSc!_rSVS4DDUv<|=DvL{v)?#}=6Ov>u7s}wtSD5xj;5L~ z5Qj$BpyT3beGy#OktPaAkS{fH{8)~|+cWIhondG&N3!gZE?Ve4L~vva#a$Z+ckhe} z`k3f*jXkO4d%C_Al@(RBGAy<2B`T;Im4;*+)@#Kin^9dY=m{-ITNXFtVKW-r?qORz z!bE`hBbc5Ol(y`mynYMzz=@g#ku^2z9;tmmHj>h;Auk`8VCX; z2%`3@ZhO_tC^}x&fa5^gBRhWztL}IwbFO#;rA(S#dv^2uhK=kQ%GZkE+Csv?rjl4k zQ*8pRakL^;Dr0x-W$0&*QGRAKluA@`FKorjtrApjBhmyd%)9J7-u(V|alwielyaKq z%KLf59Acwa#&;Z)iF<+63dm->E-!+KxhHJYm$fnIf+g5CK>z()Dcrvmwj4tB4`WS; zwGhO|L8M~&r9rufn?t4BFaDefw)(_WV>6)J49ApMqy3VZo~>CgJFa+LDs=NV+xss zCGT47mP1%+MR0*Wq>x9M5HN9&d2PI?#En>M2umY%$4n}5I$%S@n)sYCRXWOIK`A7e zLz11CSY;yG!Dt7YaF9#_$u(iKIc!2>-H_}AMcb?_>4_;MQ6|wWbY`=pyF7;XD~5NE z!a#r}&X`diO1U^rl2E5H!w0Ztq(<7<@%125Z>eVth+s_#d+Y$E{vM>S2XDq=QeD%q zxeoe|cntR|295|*&vfaWSt8f!p_~z*j2f$(ibrBoJBKPnUi^HNA*AAVw2Y`xb_P4> z_xl)ddYP9!i+SfvC)e~=CQWYV```H~kF0(MQi;0ujub%xV~*SS7Z%1LTN-$6T=(Pr zE1RKb-4=pUnVDB!% zLu>%N^9@TG8IA6b*X}yNQ(O1^(%-f7a4(zo9bxhGiQN7Fd|>RoXQ|ACYq#>{dmg5D z@YS&)oYD;+za4t};P1cQxTG}jIwwu6wcL2)jePjSALbLE_ynK&RL!@(TrTsiZ+(kT zeBu*hvP|mn$Rm&N*0;WuFMQz(eD<@Stv%UijSNe0OkS1UMi$7q~lMgX>$#OEeXfog?QcPQL z7H3>^B?~UNg#Z2CE7|tkW8;4BU3cBZ@BGg15Cj2t-+lKhzF%vs_%m(QGUi`@ zRrT*mrpBJ1KK8OmlhVKthn`%+)QgvrKiW&r+838a&wleNy3bua<}o*&;_TaRtp0s$ z{njxz2mr@l*v5}P@JW__{B|ZUJBP{3m&A2O!{KMvI&KUU001BWNkl)yyYcivKcZsf=@p8euo^zYdFinV_QU--fo_|liYgoyAD|L_l| z5GIwIB7k6=Ao`Qx=SN92Wf3J~@;|EAolpG@7ysGEnf0binf0cK)GM6mXZ^qYCxzY< zuW0`~{Nx&*{lZsSa_229{pkCu?QD!Wg)}Ml4RGIwKE;Ypeu&v`zLeQ-jsa8rB!AfKdWc&h6Z%A6)KWL z!viK1+RwqGcz{^0o1zOs1rcEX}u>(J= z2ofPd+M=2rOcugKiG&~G^d6_UZZqX)HX$bph^iEE8IyG&v7OS`Ev?A{#PQq0FG!Hw0UD(f{BR_)l$uLK$VFLb_2ZE}TcCMAZ(;(M(@;CfD6@ z8)q(GPPr+;`r;lQu!q^{`uK5?%F5{pwE9O35UQx(YBWwLBt|U$w&M)`bSs7Xx5KuB zFgS={DrI@Bt>l=PC?h~zNHjsJ4dZ4pxN%`QtVF$IbSB}}G(4Huwr$(C&54~mwr$(C zbH~ZVwrzXj$;A0`&Uv5p{pq#3`)^m*-gVWk&D(nSQV8xO@W=kzcmy7Hj9!L#oke4y zN{0w7G=5kT3Q*D=u`y^I6Xk41v;WkRXM!YxyPlt#OuW;DK|wx4$_Vni_YUsK5Nbpt z-u|y%gNg1KNr5qL-5*M<&-r34NvT@BAeXiTce0rP&Q5@W4(X=3}_ZkvjcM{t; z^D*V;%lHBjNc0db!M@jFkGE2pb`6eU4FyNum9F{?PTC{sW45SKd|HG9_TuDRsiIIM z#7mw8DG{1bQ`QPKnCuzjB!-oTaTgmMixMe!PpvjFA!tXDEsgjxwJ3mY7T}Udk|Qs` z1c$e(j}t3bocvew9doH4)k$Qqq<#=xg}1z%fFXQ}WW(vC5a49?}~MB~3r%i|`K(tp&?Z(3?2;?(!R@RAAc_n9asK5~xp zCXYN-J6OEA|I{X1&amGN64pT|js_=Gn$NB+o^*`K#t*`GK}4X!^9b(`LB)T)q-))c z5?0Mr2a6=y?tVaDe!b{!z4rC?9vF1LT<(_pkLu5Ken6Df@WoTp&Q=R>`rK04e12X6 zzIGHxx-32f{ZV_hj;=O$`xoMO^?&EDbp@AyT@a4aWxTrXV|GwB8jbl9zN(u4a;2VT zrPwD!z4H|hgFdA*ozZ15Ie4+SAKdiIU*NL3@&6?ExIDfEZtm|a-<~zqzx#bWSLc_T z4T#ls{wdyi`L^_t-A?koZvTFM{rUvGetxKLIG}Hv6L7qs70J>vdA{V`9f|GzI@NP~ z$lbb~pH<7h)5xCD_=z^7W@|V^J9saboU55*G zzMp2v74$#?A%Axuqu-6Nz{?D$^KnP?nY9&9C>u5-ncvfpg1O>V&F9DLKf$}G1PWPm zMf`tnJC2Tj-d6;D@6n0nMNFROFh66fXAGud>fDm_Q*_hhDY?8?)OH%*>C$HM`@KJB zdzmsAyNX8V=5J^7qqn?v|Ao&9uC?c|IlQjVT{^9>eh-V4$z_TN59?C5xE#N2T?WHXNW`3GB;&2-HOw)10upy-b$j2LO_Z+57nY@bITjAHAXoMY z1N8G7Vr~auV$@+z_$7Qf{^!oi4`cG@6!R&i4qfs4!IB0oPmme>-W=w3Gu+KcCaLqrRF(ry8ci4DAM0eaB!`AVbm_;lK%)P*Lo7~>!uw%7|XaKD$dL| z^4J95xXp!;9Zk?zENj}eC_(I4$#fbz@T`qF$FY+O`6V+nz-Z#9v2$9%XsEHk?XV6W z;gmf7^GoL>>Gl+(u7SqV&Ei|mnA+glE%0N!_$SP4*WNtDO^5-$5&ZqX{^dk%Jx?Oj z0cTdLq#;fdJr`gQq5_S+u@*r0o%dseMsZ2%`rGnFodXJrK$@oazf+y zlc>!VS}pLXU-qjLWi!R(W~Uplar^W4D+H-ZpUhB{2LnxRpBE2IjHYr#bTQgA$l9Mb zEWbVGcZ*h`9;lT9+XzEwb-9T3EZ=1Lj;VGBX#1}{+njDEa*E44IY&Z8r;tB(iAAki zrBzudw)8R=@<1AIRcV+?Pk35dGEs4_HRCqC)93C>YQ9F)Rziu_a3tK&_Hb9>6ZUKg zOzCE#bGMjPv_$R09X4nQT$k|KrD{s%^Ds}-6G}%Ea6I*a#2@&m7-k_q!}`Qvl9@D2 zxhPgFrT^qqLpy<)pc?^L&1<7G$U3i3uD(6IJ%0clmqzd1;zQ+e2?>tZ=%!O6x|#_u zsAKv)F;k_ds%LiO_4q+jY@i@(MA!N1r&OV*qD}Qo-to-udtO192!ppoF*$uH5X5vv zBCKK0m2j~pFRORs5i+K6Bw%QTiy<=#!}5wha*ImvzZc$jz$J|nox>f7iACLsETE#< zI;(orLVS1Fk;ny5a~g#MU?^T8!wK*GKYg>qsjdk_`+xY%g|r_NOz`72kU~~c@%bgn zh760D7Q21#9deZ$$mJtscLix~R#AdrJZJT^POP+uVX+cUG}VQEq>4g8prnhX#BJH0;odSbqL zDl1HZ9QzNq!l&hp{nb2jsn*|hLE~>J#pCnlGd9-X?Yg}_n-i6wleZD3^T>Ry=X7~J z&qWnh^@L#r3iBF2zy8_1Gm`ArqIt5dRdMer)9BO9Yc0R7b0t|5 zg&R^xShr++a(&Zm-p2Yw;7-^dM>auJ0q@bjdR`|!pVyFxcP;t+f1~_9)4%X`{7HST zX>ZSat(x6I83jgdro8myRf{347W(XwJ|8a;=bPLizaGoo>vul zYEZPG;3&ElfLcQ>+S;XN&1%$jDwYirUbAaM;mE>-5-?fx4fSffh|7Zvv=nZ$tFNhNQ!od*&cCMc3(>MgU?(ET1rlnOPX+6?xwpK4jjRrnzJi#OVyG_>vRpdObWQ zPOYuC2%(WhgPp~5*jS_IAH}y4l|+-`Q4{ul&ntwYFnNL2eN-L56spo1id zWM+9UYWn6ULEwS%l{wACrPIO~EryMOiim1Vic5?)98{g=JpE>W*kv|}HQmW~O1S6f z9I{07_?A5rG}V= zrU{F#P)w|_6&ah1GL{*ox~)b%L`-l*Vo2KZS65?rQ~g*Zp`Ga$XMS0;1=Qu{*-|~h zWrjDfCE&%HA?!yUQB&ubUPsbWl{2-wD-#WZktMKjI1GZh^`t&jT)V$1%XGZxA0o^9 z8P9}`+u#^c>m1^+5mjm;6ORHiGP!b?O77D5(h-2!ALWt^aNLgLuu!pOO12%Uj_r_? z0l-+^2w}5mfJVG**CLOwu{>X4n;k!Bp{-FJy7=K>JlKb1M!!)t76upl%&-7Cye;}@ z$1}2*ND^m1R~&v959m8VR3)Xpn(X0Iqx&sK*B8xG{xda1j?!y^uIt=cXXc1Sw!5(D(Z_GtGLJhIR`BEZRp%*j ztM6%#qe0VcTANeATe$p7nJZ?-B1F1SDwSv zaqD5$V8h`-DPB?E+^g-22Ip|mFChf{lJOK#mtAET?^fjJfTJ^0#imzFISp|@yXxtNnKnMX{aa^O*e~* zHx;E@czuiq2A(5=u1<`5-ehPOU#qc47++Aq?@eQ%`z>LBDg%ZaDUo+M!490tXo1|K z;k`9G1L(x<$1&K`{kGQbNcsjD#>7!ZJVa6#EaRc9)fFWuOO2wF%#dz$`i*8kn6Hj$ zCM}^D;UQ!3;bF%}ycseIIXn!)$;q1e{d_{4MyvRsuV->1iMzLg- zA2!Mr)&dYCNl@&O{Yd`X$`F2Cne(Gheb**qb4XadY3$Amfh?A{S<0CRBIVG*n5V~L z;2*CHHZ8&};d0$(0?E5(O(jJZq0AunuGb1JU6{DY z0n*(8;d*HOtjsQK;tv3ydSRD!=&=b!PM;6Etp)LsX(TLS zscUj1ZgSa_EGC2M_k@I746)H;R}kv;kI-B2u=hi{iJbnDrm}~$C0r|8yZzoCZ^qgB z&Cr&6k|#a*t~w%`tYU4#Da&Ghzfh_t`2Pr$`rjYNvSNNwfPI!gCism9>JQ0PfY1D_ z&Tnju?Vp0rlByH|{tQuL^87uD;=$|M9kaw=DQ3RXnc{{7~1P z7Vmp5h+N9mX~Fw3ieNI3P}Yh-F|8eP=mqffV)u7F-K2_*WBV^tA2)#`=IFVIer}ph zy57fFZNc8x+fpcL4-netgP@a)2TUd5$?i0TTu*iy2E{x6OgZyV=chtL2tvgazf7f2 zO83?;7f^Pr+@O@-Yg_bZ8?9q!2!Co(!kXSIbda13f&8bk(UazIqk+X27(!kHU0kG3 zzErG2R%^e5shl-*xoyHq0jZ2VWF?%WEk_aT;+B}n3d;|S4YGlfgFZ}6gM@{P=an95q`z`gB^HV1EfpF(Y5!#;%gEB$vgS%BuOjZg=u-$XBq?ViW|h zJ|sDihvKe88(;151rQV9B4F|5p~w6cqr()xuLp5xDN<^@i67iZECCQp3V|7rl2>YB zW)W${8weDQsnd>L??MfeU@F5)6mV1!cV+Lz04x}-n8|kRJ?Un4UHJ@%1@ZrO2l_p5 zAUwh&g`s#Jf*x4935 zQ1UU$ybe(kMgEdz026VBN50(As6g(ZTEeAE95%A%sH3Uj_oV_ZgHEBxOZM6YHtvvZ zh2+F0qQq#gVQ#vMj`_ zj3!g5R;#CM-Fd%0iq%0c#Jr{f3|HoepR0xQ|MUBe@trsVF{K%Lx?vTAda7abH~Yy^Ph5?`N_} z!Q52hHuqDlCZ1s0AO@=kxwx@IzY5n$M7yNz6(8||M?UjwiRNf(= z4L(wUmM~*zF-zLy1b!W~j^+eC(b*HBh)45lTt*q_L!`q{KXd>g;?y3p4UC?*$fsNb zBC@OWp%^j)>%>iL@!0C<7II8Cs!Hj6|IttB0b?33fzCI69CkYcpFM$6*hDlmT(qH1 z_|2!rdQj|ClKa;3RcaD$gvO?>fN^%dC?pA*MWuQ7oImS7ADn(9vXNW1s?|z+u5{NNE{snJX$}Lx}^` zpGSsu@U>mBA5qK6?(AKv1moWJCa>F|3%cHHUmKn`bO0G!DnFUIsZGPliQJ`f{@o zv@HNmP3at`&~7XLRmYd|tA1@l5(JJ1zR7Wsb`6h>0Ba}j7zL^po$sJ$D?Zo6$E^ML zNudOMZJ@yyz1N}jt5*)R>Z}=!~Lf6Ry&{q#K{V zG<#`Dx?+VQN@sub$K^?)+bvIWMl%u*gh{_Lc9u`9z}(>8prW$hH#*UZa9*TW*ic|S z#LzenS$xk$G`deU7~1dQ9(prZ*Y0U2kkNYRZv zFj47J%p~h%s*D2J4?DiBiwru_hLg|(wQmP-$zGgwIuTuw z^c`xg@bw+pjnjq-ZnUk$#=#Z46mUD4&}G{W6t(Gc#cYLg+P_VT66ex5_Y;Pq!R!#bG$Mx* z!#33o5+=0;u@tH+Z2xrc6>Tkc-3iKW9|fpJc=dsC$(@T{02=Sw>GHP4$CJr1{pKJQ zNm$Vdlnf*r1(lHXjIpM~tN~d19UyVktX=W@l@XT1JPmvi>GwIzkW?IP|Ps-iQZmPo|f2eN+fJ?R3+H( zHditj^{keso3*bZ$E5@qJc`ucS~W9;)m*v3xLz-JZT?fU*S;ym;S$Cz(h=$qz|JaD zKmtWQE0&;2HXESxztF+4+)EcBT`URwAChpe2oC~g*r1YEp=e-RieU^)GsunSP~n~H za|dhOr%1brL^G&O)VUefZ1gu&YVEka$Y+Jo(!fHngPdr?|9M>PJ+^FFE0qem^u2PEz(4CC*~ZAQ{Xz`Q*TQsJgI<|eh@n3u^|aya#GT3U7zRc< ze*5j;{lE$RBV6p#{dH;ph6CGyrCct4dNjG_ed0DGw#Oc{%q$>!Ls72gXHw^(4G_f) zc$NldVbBvaY2TeWJej5}!^?L=cv6mc8i4>!22Fm)gG?!n07A@;Xeb5%(~v?<*ry9+B@;}tE{uV!x#xDY_+*wzWCRRqE(S78SQ6BW z_R){+o~@9hTB>|~=9{xd>SYCJAn4Rd132j1io;~Tc0hW?lXV5+*U#B_7ZH_wyeQ$Y zhG1w~U0ADDU=EFUp~G%`k<8#SqcwC=S22AYmH@neSJht>1AhG%i=8C z@ynz{X60@}#Q*07pwXsnt@=J*M$>7H6RDK)q$x3?ls$Fw-E8sT z4$uEw9mHE>MQRI;i_o*ub2ms=vSV{^I@bD+tn*z8eqY{6=K3sfrf**dEd(B<^SrOY zufHdjBuxJMlI`|F4%r341*Sxq+f)6{nY~^l1y(D?MtZ7o%Ii(ee7iYfDu~UfF-Dy@ zs6bpY3c6CG|IoBX|kapt(dC&srPzbn7X89KMbCgmzMBfSds%sSQDK z!-@3OJSY+x?fHBJNo!eU&B1pCl>9}2`^?{}jVATe1$JR6l-GnOk`*%{gCjFZBSn<( zGG+c3hI?4Bi=`Y14Vx~ChXN|~ZvVDZz^6B1QI{Pq+6N9Xv;HP*f?KBVzIKh*4X?KA z-&7Y)a;iyaf`@$b)@sRu1KSmRq|rgeDK`8F<1P3eW2gg zIcwIVNwpNIh*FBi$ib1+{>ur+nN&>Y;om#sNvX6{rD%%i2zN?rlOT*Gbjs$ku|I0; z-*p@XFXCZghYhT$08Kcx-nYKa} zR3;jE%N$j@l!!4!l|8E-lIkQDEJ{35b_Z6$>+maBM*KwCQ*io|R|?^1BTk zU?o_fO5#R|libCIYR!=F4F+c>+5A?>d|HFj5Dh4GAX|cKLd=LcTM2pzyD6Hf%lo0w zN?=VGMXa4dg7tV(gZMisFEj#RFfHuRuTFtgV=Xyn?27tyVU~aDlV$Mbe513+UKXoT ztY#PkCgejBD(p?lJe%1LrdS2;m>(hzEaE1?PG+BsWRsQFsHRqVIpSFdcBxvA#=n~D zSj;HWILU{yARpwlBp({zmQJS`=@=^bV-)KOzt*JNBk_alrMytU({`GcCZu|oi{pQ| z`c_+u3fZi_%eem%l0wkWR7)g^dTA{>Vk%MV9`h5|#Z0ehxul0*6L!QZKafy~G!6#; z-NzXXKZ=hbrRic{j5k;`8Lzsf0Yc#eJzA8Q>Dn}%ie}w{G zm)?K5HzELbUul38vIC#bkE`>^1T-di$b4hnsb(V~+f>^9VXaS@r*=Ln-i7cw7?wGJ z=;buXH47DLu==gZEYdNK#v&st#|>){WK!U426F=?ZHyE^z|TQj0q|ecd=eVwGSqm; zdh=wW@pg;-DD24(T^(}8p?El<6hg>`*sW$VWpd+TSU>rcO~sQ{qQIg`)J?%z4id*1 zN>n))Ro2EyKcOLj%2Hj(F^S)aixsU$!YN!O+1Q+Qv3ARcpv`K`?hbC?2PBqip3&A1_ku z2UaOjDR!;a^OUZ>U3R*3?R5!2k&9<0(oF;uf-Qt%U*M(|TdScWxtxA}Le@_TI?*1) zCi|2`6>Rs<4l>Hw{x)t18TuM*>_R3qriU$##%%(HD#Q}060S%IyGrpH$x}J@5ptFN zh@nhMl-N0NXzB_hh{2y`tP?eL*m{q3W@K5v2;NVMYkVykdCpOcaniR|X~l}TbLfMq zMGmz%{=+uk>ced8w-Wk)?33ftR9lp7yA0*$AB&$+A# zQWdJjt)lG+uqkb}q;H5}E~=~%iI-VK2$WaFn_1`;9M6M~3Wm&{E2TT{BLeMCaknk) zy3^+JBAOulLBkE18fC3_@1OXARetNq(qU38gl__6ID_==2bc)8aUyy_` z4sEt-==w7y9G=ZeIsuyj)o!c*Y?fdYu!A5RQ2>I!8b^b{+1e_lLub+ZLc-uXTbt7c zpti*hM|MtW*@vyMKosYk_`;HkX<%3jLtwLTn>|gct{-T@ID7;6TibN?wVB6?A|U}b z`XeqAQHfcdab8knD*Iu-(6a^vuqEcG>jRaZ`Mfx-9wek6_<5yzcoqVU*mq938((Vij0vVtadRt0nk(WBiri)ZRrol|Vxp z%%TdhXHBf_6jDV+W)|O!$6%<3ni8!u48jK!rQ7Xc55D$QNWl2N2XaVrvRoTpb^t2G$*h!s0MMQRlVdKP8qS-mLQ4z zgt+4-NaH3zaIV>!y)%qZ)*WdRu=+Ds>n!J_bM};Fr4FrPnWR-FodmGPksc+z+8r|S zBMjCN*D%%)KMF=4TEgNqpcB^lc44F<`xS9I; z{`rZhKvy207W3**Q-w3ZA=_9dinU%#QB%nTTstVON45`3eV|>8Y0zJkp(i&#?@kB| zt>R*eGe9K-OH$eGjEv2|GqD9AkrXijt4gyBMCey?lf&W`AE=&&C05e!XcR~*Dw>bR z6-V5(QzdX76ysiQYb*Kh9Yt1vIOgR=Kp42?%t_SAf z4RGpjUM0ND%ka%AxPNP7$iwN=eguQ~n5{GN!(|eiyUy%QwdDpT%Ll!&uI%)ps_4H{ zcyiY+_SwM8EJvVj63PExqjM@A{QvXJ1yKP@;zEg$Qn74M>S|p90`@$8pRg-T_fBn zIzH>nC>xH$jCsO1ravND#R@Fm^eo|mQAw{UF)^HA_=@X?5#GhGmZm7!nH8c5V^L7* z>0wTTX>d-xQS8Z?;tuIWdj#N)e;~e7hH`lfI)11WEFAY>4Xdsc*0gr+L;LLoCklkX z+V6Fs9i_3n-cf9C6B-g=O-i2*`!YF;J$BwDOGyn-Xg-o>hf+H;ss-TgljxB{lGvY3 ztW}GJY5EaYWOWBH-*v&yt!5bVV37PrU^rW53-<(HTsx#p>v-$^zKuMI>WV7~TUihy z;1W`}tSEkg@pA}59ky-4LTY$Gl3L9<>Y+|+CiSK>(>X0ykr-Mr72!e2BZh!-s7gz^ zxJ2R4AXcNix;kO1(#u(82s%oS#2*5ZuRS*zMf`wVNgyO*-z^jg&uazKg-Ou>myNiI z`p;ae{<4i*@>t+}2Xd~?w-ZNV>61Et>z<=?w-w9&Tr4d6XHwZZjE|Lw3tt+c2ZwzyUrp6HJu9n-hYTj{-aPu!eaM2kl z;uWxMNP4^zMQU3lW^rY!WSD;+KH<$YIk1g~fa5ThLoFjkA(0!o#D`v z&_xtd35@dP&pYs{Zpz4Yf72CDdIF;4+UGN~!}i!BZS&olsEOSz$zplBukTkXAUr@J zU|fhxEcns-{}pxlh_Q0XtJFA|6#pyvx@eT79h{VBghA^A50TMZkqXp|@6qw6dR&Qr zX(~v_tAp9832CaNOR1zQ{)~YZNVDFrZ#>xq&+Y@Gha<2xztNda!&E_o%yNtl;$4g1 z{vA8t5|4Vl1vjjm!tP4@_wwUG@zWM>%mZ9zxbpKk@lUcA)dkDKdPs8_lND?SQgTZO zg4P{UvIL}bSCMFuMZ^K?<=D5t;I^s^6@=49qymp$X^Bk)d>THFFe3tPqpOa`|Qi$ULRNBDgmXhamNQWgBKrs4(ccx?Ojghyq7`LLnz*Xyav3u%)7CL z=XLuLzvz!gGS5V64vkvl`4r*j2a@c$nZMZf2+aS*~jS$I&#MQTwB@g`ck&!G_(nf zH0^_tW)7iLklGdc&WznlZ!krvj#Y^QFNj7lsXlI@{_^Qf z+7A~fAW%OZy1^UCCrbTFgD9fJgHee%Ti>3|8m%Ywzto}WMrP-X8a#{~l_V=7LLQVj z*EE|uY0sO!n$Vkt!0zj3QXB(Xh7rDuKI{L5^1ebdp)l;Gb}}(%Z!_0$?rw{MS3qRo zmH?!3UM9;eTR=Bp9VR*R@xgJUN9iZd>5R}c#o$KQ6KR;Jj8P!MRT(ZLMG`xJwUrCi z^M!lr#m)uX_HfY&WS8lMzm$+WDKee&ASUqTYc~R5K^*_}i zqlf_M{jH=~H#n1?=#Z_vAq#B}+Iq{x&6`uq=~?0%!g8}Ia#q6w#=EHwW&vj4wSTEl!c=!OnZ! z%~ChA$GIioFOy-iIGFc`6z0ZHgv}L4_%Yi$W zS(r4FOF{zqi|uC`VAI>49QYPKZ$$1u+Skvf=|Gt+Rjfpn%WhQ|E5{7QP$drVjaYXU zm)K###kreXr zu5oS&;vs(NAt0GGWDQcCG8s4aMO2;qjxFRmwYW9#&fbXE7g3ZCh&2|UZalGi@1l7; zTK_nv?07q;>~K46L5p%==9<)<2Z`fFU6R8Jk1*l6e3h2eyv>Z3F0rt6N(k6uDSTHY8qo!eN6LGtb9gEP+(zVz%Hz! z4Q7wxaPGGW?fz?vTp9)zziU{>x>Hw?fpKX#4#+5x^qM#6<953UF-+n^F$C%~HwOg0~ZrH#7Y8 z$|@3usnez6YPz#TYz%)7ysuK$voS8OCpZ><)MWSAj^#oIhXw@@YPbV5hzK&pl9_wY ztR<9Eqy_I;A{zl${GI9Qiodiy$w(B&KnjL;UX%q|95dL;@{KHpbc&$fZWyP z$A?!I+i@P5LIIH^!fm`T z3D$c!#>MLLM&~IBi4%{E_A27g_(=!7YIo!+70ys{=L}JpUa&EHbO%RiFw5lBiF@JM zX$`{S757@m35C!D&x+}|WXv;=4L|c%fi%8QgYZ3}&~h}d@Y;-%T|8VHfZ)x{gD76@hcv$3yn^biJw)WY??YzMUiaXy28E@V1 zFk@tfXG#xNpY+9RH1*L1E&Mu^MP6CWm}sq^chu)3*q4nR7ND9nyJ7+cdsF9!DJPRI zG-^M9V#%sny%J?>p__}r)16|mJxLmg>*CaE7Y-+Qse|Qxbg}+cwLzMrN|!fgz~21% zdy*CzeuhuuBqo=GqVi3Q_v?17)j9aFP)8cZ?x?Se&4z}I8aailKDgSS4Nw=*rXjcj zo`~^Z_n6=Lq`LEUbc_&(KD!D|Q$TuIhEXyxCGR^e&Ac0EqXX^ufi7W0xyI)y<#$cV zIabVuQ;CFiaJk@T>#4uytmGsy{9N}BWr7K1_Y^H_&$>t1bPb;9RpD>S*C*MHQv#=kkWP{Hl z!|;nU5vO_iNdIX+$!y)CS(Ko5rFn;Q)WxkrZ4{Zk(0|oLG`8(K;zeb4Y?R|pW@tFW znw)IJUuZTG`*6wmM$dn!=U*h-BH1?s{Rd4TNs-<4ssl&&fuL&%`)``Szv=bGZ%=;K z!OBVg$jKN*$YqJy^HjHk@sURE4XsiQTzzZE@rU;POJc~JbO{D}WIWz68;R~viupqC zFd`{p;=I%Um@x3z#f)*yl_$&9aE(Y}jel!f`K`AnWTN$C_b-o&)`(lssyz`7pf#a& zT~?Umv8)s;AH)G8-BVoGkJ&{aj(jVCF7C3zPN8SG+%*$%zKmO6II}s+UYiWQb24w@H6{e$V3U z_2}A{YE0eB*ya$19O6x9OQ@nI0oJFqJbNs+$}^!@nlSvo!2VjqliJ6VU`j;JCI<$Em2N85I;T z_y<$y@mib9#o`>iHs@(vRNgw(U3j@i`A2YteTRSp%zNJU75pMXQ4WPYON2mJ(*V9O z2K#{PIy#bD_fiu+1Kvr9`gG%e=x`*aQ0pr#QMv2HGby83uITE)8*5cI=j+=I<5eb^ z_aXhUA4h~exz{n11MUXG40If#YrM`vIEO^=c8nS@xjj-Zkc=Y_cQc!}-qA~c-qg`u z;#!o?U_uW7p|8A<8UE+wMT6yueIwMkRkeHfVLa5zlD2DIY1!B9x$uL7rh!PZWa*tF z6@NBb)K(k2Mk8iz(3rH)i{@DT?1Fq}uf%NxIPR0@ za;b=eN(P6kTS$d#h$Mgtq=IB26(RioFa{LL&MN8>%8S`)h*Kf3G?u|Ay7$dNzmy_0-A|PDP<4 z4D#j!wZi~FH0M1@{y!2DNJygi4}KC!hQDrl<#>S;VqMLQeowiCUk2>Z8j5+WFvmDH zJq=;D2JMM?zkJ0&iB}>WaKJeMU3OY6*e7`Q+jiKXQUC|kWIAj$?}}+erXB;X&sHP5 zGARz1TNzd{i#S!mTb!pY>=33%kVLYAY=mk*bzAq%^AHXvI=RIv2+|5i6O|GpgJQ=3 zAVm~}RnkusLe&MvBEwPH6n*dTE|14#QUkb(n0V%|6WygM;7?kM0_F(W5b?#0b=Vwt9(EYDWKC$t-iHA3$+6ns(U?K=H?gVpFMtom>+qNP&=LSh8*-&fm05iQ3hTTNQ)FH^3(avhSYl<(S{Acf zpQt~Hpi)60nLLd$N9w;^5enWmVDr+Q)0QWD#wNg+cb|;}Ni{ zm%PCr)U$9?j`vY`4|h(OW>f~5C+k*y-?AgskQk+bVeDCr9tatd{cmqXt@(yZ@vF*!y1UYW?0Se@N4{+mHV|i}$#u3EO{ zp=Dsz^M1py^FHJ0dGqh;n|kK^!FjbaB1zB{X8%<`%Pgq=`t?qr_|Azq_q&R=r%i3R z9%4#?&f)0{Y;+fg7;XV~@|zsrVZ*2c4qa>NiQ~x{NUYjoqpvmC~wQT+b|wduOhA3G}$EW%K21 zQIT1b;g0a@S(n+UWI@BPC89VaJPtJtqXBni-_+R_TuK}e(WF)C=LV;6_1gS@UVttL z&%$&v`520h2iwC1BHx4e$P6_nrbnIrJYLC{RNv?N?%~zXRm&Ne*6&`=piKCKY+R2lRG})B8^(wl`XV$pV^I6nN4_G%(5-e4>7~jQof@6A6wPuZV&{(da`oRUF6_Qo43h#O*@Cai5E_WcJnSbQ*lHp%w&iR%zMEdVVYb>+yFBtD9omv< zPkhLcwUbG#HNN$-(#bQ$HotIdsREHNgdzMJBJ9?sT$~6};YK_xTS6pUiR4Cld^O1O zK~WaPizrU~lJ8Vw`GvIkD5Qe~Q?0WLiG4 z5Z+vldGf)`zUsxU4lz`}b-W!0;UrtcOz3EduO85?kR0f^(HDwA<&7l&$T>K51qBW` zdqwVwA9(ZA3#rMxG5oJ6eP(`EN~Cb#@ub{+nQ_O$!#k1`d_|rWc%uUa1?77^EA~0W z-Q5F@5*${R=aEG5z;1mH6vN|k=sERj>U$ou5ZVrRx4}YOuDq#xG2ftmrrm({0+XPyNXcxYrBye?F1L^Za;Jd|wlI3f=6u+o^up ztp5sN4VPT(SNJCS*?riy^rMWDgT;JA!R-~Id)!I(vKG%v3p@Cv_uW_Iyo0KQr8 z#?#q9MD@?cHs%ELXpgn;by!mTe>9zAbYyL?wr677X2<4)6Q^U_wr$&)SQB$%+qNfm zCU!c`m-jp8{NHQ!+G}?`Pt{#l-4(8Uo?j_AGRqTxo&fDhdJq0~LJN|t{cM! zS^J%xe#B+$gS@tI;a3SZwKtODl6*zc+KYNJtD!SyH;G&OijNlwwCCNc~XZ!$zCj}f{4Y>)cp zy|S~xH@xlGcZaK-CoSFJ)zAv^3ON{N8k85S;sA&g9Z!3`Go4SQ6iJh+h~?F4@u3|N z_+gGFJ;#}PfSMF`phpR|pl;+JvHAZ+>lSegC2AHi`kBu!2uj?uK@HrdC4 z-)kIS^ zq-@ApXBmX7K+y41mNMfi5>HxNG=P(ec8Wd$rkFko0fYJrnCmhe{K}8TnSSm0#24|K zQKnWp5^2Dj1Ik5f3<@GHJKRta5o;}U3Wh5lN*J{3^D$#+#RPvdb4=`Qah36X!aZ(K z>{*P^KCQyVg`B%2=%hYy*&Tn>_$2Bg^N*f>FiK6{+P#yQmY_yz!NFaq#96Q=+{|Dp zGA}p!ZYQUXXeWyTr{kd*{@U#>8R(_D%d%3T&ks^3vMj!VVaM#8cS9wjo@ZE!vcmjm zYz|n1{N*K#^G5|_hf25xle3aOs)BJ!8KnFgiFG`>IDT)_mFToO&}JN^uT25dfwRgQ zkVR-uizAzir|HFD|JmY55GF||jOqg>T)unnX>x^kL<@3b?})E6pYH1fUltOpt*^hN z9y^1`JKGP%*4t6ed%@6u z9#?-l;_CTZxA{dOPJUhd>Kk8w$Bo`E+t>-gkA1-s+T;n0ckA}@)H9U{hR>zIT_UN5 zXo!bvsu#V_OaB)}HuSp8;?=C7cDA&SP(2^T@cS*QmV=OvWG=J{6QU$-&J`f(vD&VokZhMk{t_{^T`OeAj@$6RaeXo{e>tA zZ0quBQmHCC$JAamg^5Cg>_zlJ)`fI>w0J3nU?t8XM=PVtUl`{PvO{&y@DJt%_&QsK zc}B|ycB{*d5z{+xuDhoNd56y&_vEin_~#U2W_g4=L0Jk(W2ppjz4-S$I~+Jd9G;UZ zDX(7X?Jdm59hIZp%ub#l(B*?*xTgARINnmfS+c}DV0H|3k23IzE(aZo1M65S%~f7k z{<690m)Gf{Y{=U5qZaycWMQ3_2*Gvj!Qf6rpK^HT*v{^6tnsI8R(J!$U@Xluj4}2* z3cRU{x7cPS*L~06;_ZXgR9qu&=fHsv1od#SD7&yutr(4E53zQ#bRi=}g<$$wH+)*H zW*~JsR*#ZXFB0azZV)*Re8aXktT-0lE;IX|eKpoirP>FSX&J@1$KQscH;=m9)@m$Q zL!uhV2vVF}aQs3cyncyo{I6v__js(F+v1m&BM7NQjaLW!)vagb8=q~ZZTo$%8XT+9 zGYAsFBH2#U23=m#zRl-wLZw1!r*MvkX3y(fqp&>f>|61iF;7|{mamYVz)VRS*TZ$5 z!eIsd1(C<*oJD8z2)P(C4{kZAGyKIo30P9np{F`a72D$uTC~O4x#Y!44j0G7jb(h; z7ie6;6={@7=VE<>hvo;5P}~qW$;NvOtI2e=;pjngf0#wZQ#>Uq5@sx8`ZhgM|8*|Kf|Bj3A?RsEBG8xpb-t(gRt^4DlrnF&~RqXRLw}fHEn;(=syyug`zwMdzJjN>j z4nn?-^mpZ=+U5_}*Y2h*UCkK9egaA0H1(4mti zK_Ig+VQPEtbzs`;f<#Ft$}%=IW67cZs==RjnA@Hbzq~=S;c* zaehJFFcJuH$$?>aoy|;BcJCYI>9SRx%#y$Qp5A5RJ+eLE0n-l#<6%ZkQZA1O(a_t% zGdW^GUP>yj-ykuXaAK3dnupzVhJi5X$R`sGo-5*Z&y%(`t!?qufq2WB6s!Vj;7(*1 zE1oX4niIWE)+gG6llY$}vK8d6McqmoQk6e#lKP7w3rdz0qc;^|qlc2vm$w`+-FKWEOO>nvb`%$AlQNIA2cH3{s{mRhXhElRy)rfy zbyGLe!Q~M06kM~BV{R^%=_Qe@*4H$FiGxLiWF}&${rMBW6pZ<-rA-sxwxz*&y5?g= zOWsA!*FVEpC8%p#qxz(Z#yG)_p#_Ku@YvKBo`|0h?uK@OsSJrmv9?5o6tqwbc-5W9 z;zTc#$s0Z1T$L}-9u1sBpm&xnNARN!Zt@`mqw12sj0nDHG z5{+=e0e=PcR>;Kd>LjwwkzsR~Bzv@?83quM-@}yUt(I}XBo&(&IvWipYgQG&dMY_W zu&ESzTut4>PQD*w&yGnYwah8O{h=?fEmP&@n%G0uAA3wN65 zPta~XQQR?g3SSJZuwYL_Xvrv#ExOr72aw~)uinh^^IaFuG~6|7Ypkq@g<3woLgOSl zFX{@~jTDa}rJ5v5N{h_#R2B7XRnBfVe*$1-@h#_fU6!~d7}WNETxv^is%GaCvGTT{ zA38mS&lu8aIuP`N_k>tsQcBj_Z;gzIWf=f%LZ;((iM?>qf8A7XhK|`7SXJd+r?3h! z3}jmC-14(UG^8i6OM}dfl~~%sM5$ey=+}5$g{$f!W7Cw_47};vc*EOh-I2$M7zMnE z7(5Bn{$ZrO@`nR2KTi!7#0vjczACmUZ9kdnJ#X=L-s^9FlsE17(owbE z)#X%mIbp7B`C-}%`WHR7Fs#%Z@AtT1SJvSCTP;y5zuWNl$?@G5thMoP@qP0mdMW1R z&3*0f@h#d1^}N3zb3dJqnXU`mYzy=*wb%i&W?cCEk7gl1&ORRr?vxK*_P%ttJzqad zx88$3ZcF{Q&ip70GPUiVFxkA0xc;2rMQ@RqNM;-|)W!Bev2O0S93)I}D#Ll|oyd04 zJ`!{))@&bs1E)@Wb5t*^%0)TC()v%`^CyTsb=13$%e=J|?7AF{s(@OwN}0i_-*!kR zj1GsFhD2{GAXXP!>`Z+UhjGzF>#mZQ`q2gP|+(g4wT?vn!`X!s0zK(XsKzVKCp3 z!J)Ib+^@w4j(DD=^Yv4(RrxCM=@~W54Jb&=M4}=aH0mzsuRb5oCtI9?#TW?@d7169+uA z8bKDy>0of6N`^!URpIyO&lx^-=WKvZMm#J)n+QnNdwv9Qe@)xa__yT7ut(H7^_GPDAN7%I6&6NA$%WL6%4Psihw+b4S@^k#uMCt{#T z7Gumj=!z+y?=a;M z!`CLs@gziZE}%8g&Cg~L{W2(W+YZ5pRo?bQCDacY1wN?=RVe`VX8 z*EHwd$+@xCH0F@o)WgCp#7TrXCXdf)Wf#8C8T7_BGLE9L6fU;%^WR#yCYn3!4-=XE zQ{y*0@kKTvkJ9%EKVeEuW5l>bV;3fg?4JXglq7h-*PDLyUW^gZ&HRy$R~W%mQ*Wi_ zj683(FHk635m5=5ZD39xYV!ieT_NRmA%Z2ONETWW3H~vw#G1^wiKKbE1t4uD^EuxW zvyuRzElyzAt;#w+(P9m^|4`JXsPe4pnWNn1icB9zb1KOhM;DHTSyEa^`01;X#AzuL z5JtB1bCocu$27ur+E?_JU|~jI^jH#@jEpYhgk0u_2bcFBbU>q|1NLbonfzsgjT2z+ z|GIwVou&WUq)(nTbxY-cOGT*HO2dduu;m?9y#>a5wK@Io1Sl@A2SK7f=`^spj>GGk zSht|8!^iuoY(H_sHB}*1ErN-XL`E+tMdZ_nugFMr$30G)B{%>hGL> zUeLboHHDlCBN0PDusuj1+=*=}E23g(V^xt>u_p8qpW&ahjASAzz36njGct8Y>ghBM zKEIIDj}`+rq+|@^^{MpKZB)u-16xQ<#f3)6DC;l)tk=xXWy7h5$~I7R2X4E7h)Qcj z?CHS(fv#CXCx&Arwx~)0tEqXeRf&w@0*U&jJI4YQ;O{5sO4#GXC@v|1Y69-IuD&1R z&gQQ#dWQfAgZFA{s8@k2*(RorbL76W*Tf+y?Ie-x*MMeL*hOlLkihk3r|m-I zn_hmg6L0sv5=(5inC1QXtn{Sijo6Zf{6rFo8A2E@dt zq0NLCcYm}NY0Revuts2JR|yUF8P#!@NwIN?DxMlSTK+BIkQuGAD%kV9>O2ZL8U%Bt z4|GJ?Mhs`wuO)K?I91cwt-2aRu?7 z@rC})fY{5B$lB(PAT8x6TNa5AX7)yNeCAw8jQ3 zSP;XMd5}4g$=%=19;iIk$ z2|5v#WNRQEQ=g6q@e~M5*tWZbK3X1&kT>$kdz^kd^!mBm9Y$L&8SXRn{f!OV3DJD1tu?IHiU(SAt@pQLsj}`2oI{CyXfUGBd6a9rwWMm zeE^oaV~k+2q@QZ&Fe=?gJQ^iMQ;zt5@go|6#-hjw1$r#a1~bI}UvdB0eqs1Ht@()h zPjdIuUq8O{^m|bEPibrW@o~%a$(jS3M(TI%e`Wf<%yHi7&TG%_9m3`Bf)hYjqdSA> zZomIvj&ye^J>mzjZS$~$3JBcYiaRc4oV=a-tV=HAeH#BdNn~#C)di=k!>hH$Gh^~{ zjo$7tzNvpHb*FSt-1n!zX@ZvFy&<6+YociX2AkLP~? zh_QfZ*I+80YR21SG)4i7A{p8`_96Xy3#jg-VMV#s9&yQ!7&SV4NQik%>f9_95}59o zC8B=bQ1d5>K?%;$$Y%uO^>&dRQhPN)0oMA3Zp5DR?&oNxshGF|Z@n2vzt72ojXlVT=v>Djj z_kwi_Kw3ef=X9ByC51C=CPGWaf<2Pv#>MUrczlB4koL|!++C1{o4#PO#2AfjWhpmB zh%=@ZkHRH1cbu_?s3C&riL=Tg#07G9fpLz||+7ZIf( zm2v@MNgB7H9UmoxDC`gJo1+;4PE?j)8kU*gMqLlC{))S%&^21rHBV#B z0aVRYN6SdZe@1ml;;HlYiW-0&ug%J{bEpe$3H!;keGP3o5{PyM?zsKLpn)a z+sgE6;5ykT5@y@kO}OXIg1pn~pO)FB6jRZWRrJf_M&8J6`rDQxP3i5#1U|A^vzU^| zE*&ZwGFd05jGxlHw%wV{!JL39d`|#NV75jEFuEcx(j-_cf(#lzJ-wyj#+BFMr?+l) z<;t#^PJiL?j!}d%v^Ag2=0He8L|7vXk!-zoDqc4ShS|+oOzf*tCFcFTV#Pzi#zRZ; zW@FBaPohYT=IH}1zCCV{HFdbF8ElbM5!Mn69cm)P(^JSW+Sl;d?*l#zF^-5xpd+kU zErdn^4H@hr!X6gSwtO$nmbv~u6+r`>Y zWlcBS&n~@VzI@|Kj zefuZYf1!u+2Xgt2*=;Fk{QF2=QYfEW(%ZkRYIL;Zi~5^@RTE*!r4Z_ z7bN{c&#F5Q)^ro-%;FXNWMA3xLGSAF6u-xK?GGjZjW5cgc~m2jl>l)#ERvwa_Sunn z0HHRwc=&BksuLSFq2+(kgf@k&a2u;FzJ)-6#pbvPgC|1gOK>uK9KxkEU~BSuL3RA& zie4+}Qy+z%^=(>l%Q^rO6xn7IBr)u@rV49}=-?FJ@6kbcBa<|dFmk(>!PrRWB%RM} zvJXBW^Y?Zk`^7w*aXF0U?f*T6%;lmq)2Su9;#WoMVKJ7KF^f!!MA)PZzusgy%H<6r z${dV|*^iIu-JmcUP=o0@ogdKIoiYlIECY_>iQ$J?t3?V=%<`|yxqnWnteBOBz=tFn zfMe4kEe{p}wF~#R{u$(xsE9OGt3(G`hlu^c)h-qNnbz96SFhF1Jq<>#WxA+#k_NUp zXfm#3VXoWl=>~0-zL=mqM|N!6iD!-+#vqd*Vwuy@on*kU}KxP-@X#+OnE^3^QVbHcr*yHjK2lhybeyis@Ua)z3F`< zai}bo+qy?@NY5Nkt9|9c5u}{AW1RQSzH-$WXS8KZfoE>-60<)|k2wNpV=Ez#ADoqY zuu`sDB2i%<%bt5AC=25hOIx=A;+M}qhikYm8+0}Pg|bDHX4gJI(~`!4^@CY%9;QUn zXi=CRi^Z?SO`vaPl$@3H(H zu1}1)6TG$0ylB^ZoRQpukyzPamYPL!9{cmQmB4xyQa9(fi}Xd#3W#jy;5(AV|Gq^# zu^UTNpHN;z)M*lvMmNTr(SbmF$q?0~c zBN)i*4hw{CHcszKbhQQjiBDnac z`}@E}N90LVdvJe$1e*O^Xc1)G8mdfaD8oQ4g?H4&ASMvmZvPHfsdJ6R5?DX1I}@NO zmI_vVgpzG$INg`84Ta;z$&5b5%3!*|A@=1&n65zspS$cF1XS~b=ZtaSbaNgk1UX(Y z2@J*MtRyp;;M9?y!y-A6u}MrRW{YCZmTg%XWBOGdw4PJdK`90=GepA+B(p`+tN7Ub z`)QDnDV!5RjS92b8Vnu`L4>=lpmAMC#i-zADfs0@PBN9UcQG^sQZ@P|yj|3w9MUH#+`WzP*%+cS7JeIff!5H75b%#XjUNciuF!$0cS+PHInG2MFsDLzs{ZDbZnWmYTu*9Ic(yl?g0Xn%(rxq0rDw1j0Y#)p0sglgp-vNbL5y`M z&7b6C0x?z__*ZUvOr$JYi6G5ob5;zrsiXvIbG6nZ&NwbK!|&m!4353@%(T}<3nO^R z1Now|zvGnD^NC>bhMNuNxnWYcgH2DmW7);v+xIO0gv8aC3D>n`oxd1fcOt8tVKIGU z;QBun0K+C^`PYy-Qf=!M30kNm4q$~>vpW=qv+@1{=V)t$Xm?+pf9&zBy3b^{h<`@l zlRG@yvLbG{B77LL5{R{k=7v~UmsKoMHll`@x;?S|(eX$(p5z+0y$jktC%%;pM=^%G zjJu3ys*RS=O?Ith)cP+L4|O8MqBkL2OO2uO2{}Pcr_9Ba6xv{;Qki1Z1RU9zZ$5H! z(*N;cb79B~mQDwwsD7Uy|DNa9nz!+q>sWiA;GU=Go}EMqBF`Dy|8Nzz^VPoX_swk< zR@S8Hpb4D!O7Gf4Q@?gh2LbL4X$54OnfG(MbG*K@CZ|%DAIW!5|IFPT_kGvXLK{BW z9hMOBpZ*=Y)wxYnxH$c%iG2>G8FMuoG25+S3)Ya5OrtaN*4sQyA|eR5P*y!eB+a@@ zW29*=?T?^7PWjT}FrfT;$@p>LXk_1e5F){-(iy$5h}VJrD4OSzvn9=g!jla zSa}zUVD!?9FWgBsjHq~2CqDWB+QDKiQ>H8=>aruw^}vZ8gU-^x)R} z%=fgJnzfOoW+3esLe)9j5o1(>%OK7$w(1raLr^1Fl#)qZ6aotj;I^QISPWHgu!c2s zvI=>|2K9}apm7*whJ*Rks{SQRgkB?CQ6YuYHpk+%PJ-V1O zOmOO(7kn}K@hWJ5*s)zuS`fnNyd_fn=NST6feLJCy8Nj^jDaQ&y z9uWs8EH=Nyf0LeNDGF8*iZ(^WF{5mb(=f4B8G|p?iJL-{M$+zF{B2-g2o<(ZLVKjM z6rQuB)}srMYOwN{L&heBkDhUkz;a4_HRuQ}_#ujBRl{jSKnwOsk% zFZwFX6!!sFRq;QQG7s|AJ2seimV7OD5}klDxyf70tW9m+w9a`Ba$(A-20z9)C4X;Z zBZf8xUPmuAHm}t}Gx~uR#JxYN*^>m~RyQf~d)=(9rn-WNkdx@sIfK}S4q=lw@GFq+jt8q=M0Z>358J5{(o@yE9Cv$j zJEy<~i4p(P3(cndFFmzf>5MKFwfnwFzSi)Sd1Q-w0RHok0WdRzvM3T`{6zfm-t!UJ zY`8v^^|WCx5TXzNbeYFLbJH?Y->M+?=eCz=YnWf&k0OWrOOf}eu^%F7V}E-${{~ds zwQA7#RkgtJ<$hoj(3IDfnlI6k_&1@i{d*7^SR94pU#W>g{U+5}Ghl`cB)fEzior{O zgO`15+ZcUYyV3BBGH-j{zyHj$k^-zPtLMc{pUHL}JHP+@18AVq9Am5_OydS`{i|BG z67Q;$Kx@VCpj(wjj*>=#1}6uF8jXe;N%^bH@&;_s%|z6~_>fx|B@DuF^?WMj002bd z*Mg@x*O~g0_e|j~@ItMAu`0f^fi3{c`9Ni6ZjD$mf6u<^ai!<|)tydAPe3s39Rn<+5s~GqOo)h2MHXQYJXTAyJ`$sUlv4{9 zC>G33C&>cD@p&x)ODvE}!X{&cpIBkd1?>co<}>CEYaCNha&D9NKY^&zW(wN!!w$D;kN z$d8ZxI0O*d#Hw<+mo{eJzx$Y9W1UOasZ*;{+jssz8&Ai~|0DUR_S9l2j|IH_nPjb9 zgwaxC1X{@3`=BB+A5qmtmFLEbyRGM+ax{+3rfv)hbus`(zT%oNkj{oSIKO2Gs;xQ zr$aG_+$tD;1>w|hHhj&t?Uc>5dp@kz6sV< znZdstypIEAhfCJ8{5#_$>@OMMWPLcj3E!0?qBwP48kG!!(uuEN7opP6kZxT{VB`aY zy%Pi*_AeEzM({TNhDr{h~h#AjL7=-o+JWX{^XbV;DT=$nLMm)E#7JgRj% zUE(>6)`SxZA;d5Ad$K*RBmc^n;J~%MFVQWni>}wp;SKeQ)*;|hFZVy=Sz91?lVy1Y~ZpFV;CC!gwV=!B0c#LlD;jv7qUTqwo zXXbcy_U~DdB*yvsqRTH^?GwVBku_E%=e=$8;vf#&p_9kO2kHW+M?U!1M^@)Ih}{o1 ziir>H-G8wyR!}KSc`#PV^=B42w=EztoR>r6s%}ekblnnQ_UvdR5?XP1CGd-XuyO*9mJD-gJB9YKMmaS8}sDf?S6oZlo$vWC|~xw*sx0LIY~Nk9DR zB(B>!Xw7FHJjpsitI@S5|$xcV7*(C@ax5?MVfXDGM++JhRdm7DNUh z_QIT!pPt#mi3LN!7M4sFF`yb*M3Od>E@PnLJBtz+dVgPMUo8A{js%7Cy}-uB6OBMv zW|a1OL-mmLC7ArH7*b~Xt4n(LIlKDW_tUC6@hsJ~s`BFb$Eupw#^2RBcNdrqs`J+` zad1CHiqQoVdHBY{u(0^VzuP<5#z*TuK6xjqU*XZs(^c8y#Q$AesuMbM$@MbyxB?J; z{GswZpp$o+QdQ5-Kh|KKfxl$8hgQu5P|7qiEfiv3U!Ew+5eg5vA19A3%bq}`m)5Wo z9!AdGDh%E)Tbv*QPRxG6Y`t!ARGK`qoZKyz7PXo!TZDac4Qst{*!m3&sR*v-846gN z#w}4Nr`gts4!J#2l$vAA1aD|vfFH$^q<+d2*1qYEo8!*PCLI4;ub8!zwmzTMQ2^HN z6gZrJd_B$5dTduEQ}0kDYGlY0uy+JeMOJX46)#b{>2^BU@3I`TnCoaP7lSU(V5Qtj zX0VV3`dvWqsjnw)4qSnp8U>s*TKh5G?zT%z-d;B5d>o8^b-v>WehBas0Z|}yoMkJ- zxxF9Hy#=m#x1W&`DrE%DpQ1l+Bru@i-qJn?xPCmnIr_a|Gl45mV?PCd9$9z3EUxo? zx1CEdmDT^KE48k-Z2=q9fG0%YR?p52d zpjmHV^Z>4qWpY9|Q|^nnd`vEd7*0Pq=XXqUIS3qVV+7Xi79R0e9f_?fIGmlEJ%U+9 zNgARR$d=)fvOsns&enc46ODSQCiTl49({z_`FlxRwVDG>32(kz@uf!m3In2`oobm* z&(v43G(99Vv8h^NFVBM+9(Oh-v;W>Cdl{v2b3W$k?(NEZMo_=pm}>u1*W>!@cnZ>t zTmhDOz7m@^(Qa410ZV>2u?i!Mh7Ssf2y>w@}n z3uy>V*1kT7mIUf7U&VSiF0bTq&+q=;Q?&jSlLOzq&E35xtBQyY|5j^W+cmgs#KNq+ zxQooJl!D14N5?^+WL1Vn*V_LG!24U1n_@PFR;(GkIK>sR( zdMzs&n)ZBD4SIW8Sb=z*IE;3xfj~*y75$~Td7?7i(;fCT_R;SglVbxEFo<}I`83Lg zo0!HK0ck^#T#-)%#RY5!CL+Sxs^oy)hk-0jV(aAziDZZIDx5+NMB;7ue zLeAbDhr!01s%NZH-^t1wTET|ONsSTP>cK)cx)z9i49Z=Z&T)Fo)L<-EeKSLbd^JeR z5{Tjm+1GXvDHjbP4vTNW!D9VRyGl$t=0F zmx+|b5Y^^6P7UM4k9;n9m1{j_Ikv36kF$SOu=801e+e9b@fs)3%EgvMr{C?_Qd0U1 zZ{&d4B~>M7Z`A|me~By$0LmXGg)2YVf-Ib3gfZqAF(c%&CDhnTO20RE-}d zK)DGCO~id!pt571W@s)w_m1(8T5_~UCX=>(&U(BBaSOJL`{#jro&b!EO{{HsF0 z+3mMic{upmqQx4||A_M+aclO9<{5;X^cXu@9bEds*WM~*D z_<1Kdty z8u)nKC#`ILc|`8u-c1DmT}fR4t@<27lp^JP9@;8dDiATUw7LEox})11fiI4*7IZXy z!2}=&eUUgLradqc2GMQf2P-j+!$(3!je5qn+gtsMY4#-T6%dJ6cdske`X9rrp(mOh zl3d?^(5+$4t>Mtwx7xkl2j}@ zIo$#eHcTN>fyLq=K@nUCRaZCMD>=p4I{U|(nav6exUy|UdtB3ZIb$PTs-*AqkQfqK zv=e@Dkf~*@Xz*;VjGdlgYbQgELDE> zKi>MpDsD)j@S?h`sFa8#Et&G5DAKGMHtNt9^?wBX;6=`0&}aeQ4wV!5i3Lo|X#p}6 zVW{Y8A*41&WG;7cK`EO136>3f~bggp^L_GXiEJDb!*9OL$TzKf^ zUSROBg`8L*LiP%?3AQ_><-d#F>ELN~pDTI^Km}I?eoN} ztgOs;Gl>4*pb0%a{WY}zHT2apk^fV;zhu6`*LL-Lc_va_r>_S&IUs5S32?LUx)197 z<@nxW*DX2LLKJ)NtAQnMG5a~k89hFsY5pz_1J3b1o~d7FMOIDr&X}M_MDPsp2+qI* z==|siy4uv%GG{kZ$gcZSYs=^IpJ_a3m?_Tb*xdeL*M39Jv#fVMlmkj)T*vp!?c-72 zzj>Dj`RyDg{{51l@8cDuNH0dOUsY@Q$_*0mlvXeQ!gM;gpWaxktYdw?9gZdzf+$yi zDS1~D^ptR}TyE=O6ippm=?+ZSZ2mO7`ljWSLF{ji?dmTj(gUg9!z$dqEP4tqk<`XAqF)lcCK_Svn*!A%-A-t8bD6UF6nmMp zsIPBC3Ocst51wf4YIjVg66-s1g*coxV`OB%eQCby6N!xUTr)NGzuP>0)@~VhiNA;4 zwCAaHc|xi~6>BWk6zN@<&xI#LIa7}1>OUqO;UuhUSBtuo1~|}Sv<7J|S8312)-iN{ zT7nqYiqQ&~73}Bud_sl;xpoGh?Xn}tED;}d-#1qFGNw?=Py6tw2mWCe{^E*7!-%42 zf=aoAugcRt3)Sj~l~PaX-aJf0ngsoOpFq?iqoHkx6Mw&H?E9JVA?s zy3R0738g-09mLq9j1n+#Hw<}gu`B#5=V4}+F~qGZ=<)sHARR|36sX^~U1lKKI+yyi z*2`$ec>b-h^Azh@?~J_%#(FX=C^_JxGi(qh8p_XZz4KHnrV@pWf_9O)3MUwSpoqyq zpYDK#cc`?VK}Nba^ni?ss+1c~CF0K>Wx*QCk$iQc3b{i3YFt2H$>%f-n{{YMi+lqp z%-V4PGH#mjx2x>M@JqOU>O?-RN2z9dTLR_Cr!sb5^{H5_;Xx+w@@+ZIV<&Q??Q}ec z`|L;hOGa>zPGIB9#KfVp^u0Wz%Jn!UDGLe;-HDU%np( z-xrATsbE|7JdmC82d`|yvQFKyNT2g-U*ZhNjNp``;Ed4^zf_TSJaeZ_tFN1n9f?_{ z7)Pxjv%C%QBQU6Hy!y)(!QYxL_Ye@5yr#+6&Lf_V|8Kp}{S;gNyNq`GX_)Kjv889q zz2{Z;YcTbr?6t$KV@m(yQ2%!G$`6zV)hqb46FlfRnfEr9cfkLNAAlrO&-!^rE^F6$ zW%X@Ot$+LtkLmN8q(IwO4OZd~8{{wZ4Kxeo%@fLXRZ77`o$M$=I z8{1LK$JvbIKv&0hGJ6JZ*Qoxdy!_2mPj}vH&j!4ceuloW(Zs*7GoKS^dlq)Jqv{d+ zy)k=1gaBzi`?oh3!4DAA?a=3*kX0 zAo!Z$Yxg*!58;Q;1#+(^ZYri0Sw&o)u{LBh+HH=B=CAwjkvFmo&acCk>ffs{V!>)h zF-|Uc9LpvpmnKr2@PBqq$4gJ>3D+3p9$`ZpNYoBZJy}2?j(gx2b1X^Lk)xWC^@k%x z5-QP>heB!D)_uHKnw3c06#rS}7sD%L8R)|+TvnK56}3vLjWgU#(^e{(} zgLQeqjosP*vnss}kdjAO_%W#{MOFFFCuP_O(za|9C&f7_36rL!3_cwCyu(rp#_14Qk`Fhh8IViY+ zRaXtKGmI;~JF*(ScQ9!->8@OFeIH5xnjZHXrQ({D`cloGspxC|?g1&{S*WEl8-9q^ zg^+lhh{#Noq`O90Yw*W6OsWZOSbo((c{RfLF{_Ja<>*qm<^BhPmbOXL(G5XfTD1>k zn$1WC;A+AXeI$TuX$?MgED4XDC|!!W>*$8)$&N)hS;%XmrX*MfrFF^}((5>FY0~Wt zH%j+-`yuUtGrL^k4qQFz#f+vM-f^QTR#sMT8nHeYSMb=XUfZ1FbNY3(2~P92IfT10 zi>UP=$C5$jt5i=GfoZxv0@wXW7?v@TJ2uq{Y=TAG(|Zhy7j&}<9AlW+-Ah|#K9{?P z7}*%-`#pk6iJ5vmOZRAy%Sv)?h_J7f;nLX26P2%4**&Y-7in+_BznoIx%;=$Gt8lW z-m881@c1Kl2{%s6b!|AGT8)P`O@@i0i{r-Wo#_--S2ADg_sbEj^GtSuBd7S9P)R0m zTaKp!6Ok)q8N@i>>D{-=tg2K^p3_mW!zRt>Q=y`?Pc6VL)oP1?&D}{I$X0gMa3F;% z*GmpCTmQAcmKBDxS&b2n2Vn?4hh@AD7V{?%E?7N|xe3JRw(UhZ(*21h@p)+*L-#u* zW4yTUwM9Y7Ic(maW)UlL+b`0K63>Kl%4JQz@XtD(-|b4T#rB3(h6|65QN;=4^E@d-6B^Ve)T!_`YXV`Q+)!t_ z#w%Ai!_i2&yEd(4rdAO@I z)SIdL72xYQc<)3d17ev&S->;BA(`x~m2<6*vBf#xBfAi(*^MI-U$b3=H{#vhCGw2# z_vjglvPwlrSZc`8U?=FqD5;w>(^2S$#)5?)BgKhNsQX6j(diZAK3lbql{%ZkGaQy) z$;IY3L&WLpgs)wLxXMn*#j}5Jj@(-K5&&tA(O(>8uK1NZzA9pkLhkctEB~cKDqc9>myIgF9@yb6M zM%g~E$-6so7T;JBY71)x*LnaPMJ(B<8n5u~e8ThsXH?&D{k*Ec| z_zQa;a5h{C>A@mBdIT=p{ZBf#dWBOQ*l>Lo>_k$k&~>sl#uWm$n8enpMbl z`yaqC*ZT=i7I78PwTo6bBKYXcC z>nc2wVZtZqtHEi zYsx+k!vxYdqr?G0zWzzn@E0K;3sNw!KDAC6TXJGPC; zH0?_hyhmeuzW4a!*Nd(i8ELuJaylms)e3A%par8)1)@^Hex zNicqE#{}5x<~LeG8dv;1`>2v*ngNb8hUsnz93Q##FR8Q;8e9oNdo*iL9rna7O`gG-ong%w z0)W8vc?ON!_6dh)Ok|Fc_+^CHAy!Ral=IceiL2>OpEQ!H3z2jVDt6~%do`lP4yR`4 z4=sjVM(z&dbp+G;qgUGiIw_xvHEq3bfJRPQ+auBZ$pY9+B#a>`o>IUWSSO<;4Z(to z!i7()Ly1M}#8Cn79R3L*LRq#lH_S6u2NfL)W$j#9m7Q&Ld+G?qiHw2?4t5-AWHG6p z?*EE0VV`aC5R1+}!(dE7nXH1zfyJg}wEo`Wc-8ZQ2ikNeE}k<)dKJrcrZJpHXqYs$ zVZtwIXwyOD?5NhWiZnayY`oUA3*v8+yeNf^^mmNuFX!>A6YtUzLk~CkzMfN1;igog z=m2|D&PK1EI?Ek+2nPU0r*EZCN|hS-Et5(!GFVmcEP6yKe{Xqv4AyyQ%ZeC5SCfqm z;iU7+Enze)s9bfukjs5rQ-{&A{iykT_AhPQkaL!)|M@I^rCh|Tw(!1TiQ1Z+N0!kp ztYbxJ!ohF9&&CSCSC*Tf;IxLFGDm7iAGDz|I3wgz(3C6V^M(RF@`O@G9Me99@=y>y z1dP25lkN4I#h2gPDqmr2Z_limp=kGJkPv2BeuGaip|a^Mp`|q631aEou$zxNZmlGd zL5#Z|(_R1CLARA{|C_N_vZVGFN(lCPL#y6rKPqRV$&>D4_Ri^*PtY>Y(7$e~^FrNh zEG)ojo3vyDntYG(ybvWUiTFVLu>f(9d$*Imx}YRWqUk9hz5|1tGV(V2zYvK>45<8*A> zwr$&1$F^F zkfSMvMT4S2l46rEK4gEotp_(x3C z)fytr4*%^PHEO(nNTIXEL@Rwh?L;GeU0)?who-<+H?;L4tH>iSpt`xnzQdp4)Zosl z8(6Sob`T<4g5s?jcJAD1&0SKFBu#U`Gve7rxf5uP7!FRLY7aESm4zgbQW~T5Y zEl0V!rK-OuD+f@K&j7_#h$5QcExIB=%cqQS!B}KOO=zHRGV`mo>$rSbndj+NHZeRzs|8pSCeMxZOYxdpoZ(PRf_JQ>%2R(m%82F zX7;MC0lyNOA9~kJ{zno0ksHMj+WX!AM;}%AcVIK={`yf{n9t>GFTo@{2ik)1{Q30{ zcBeP-os^&jN6)7t=eDA)me+L9jtX$Uatw7ibK=4$n1`BJo^&v(YDQsd}afS@US-IYve2L+nWdE!q zbl;~x!UZ{03nVZCqwJwo)eVHjm`x~%I-SXR%J@BQ@y0G-hYb$%#h#3e1dUFNx_8N} z*Lv8;h97q8x^#1CI-}UpP^&>JThKoF{=@>NqsIU%C6O@K@@j?9*SVOAi$G053CuQ| zfS#@r**U4*wb`#+{HdUj5-a&wvv#J(sHo`r2g}j7BW##R8)2FettAY}i*||tI=%Kt zIolmcB=Xv!M($(Uzp6(XjA(Ql(^K<3tS-L|41q576QN^+xY~BD4>%>G5RveIyd`ho zqn7CI4*LLzPwq)2umpel>0Sxy78d@0=F#hBX63Ub=@dOje)J?cS@hayw%hILn|7N& zTnyv%1byvgL+gsKS5n5hVl8e*{nBo5ih@pujk6u+V7Krue5wk)(|0=37$zpE-P#Dq z?mH#|&zHtXX{iQ_jc)90gWRkGZ-m3T=Gvd+%ctJ+PdQFRj0F-Q1rkSs`HuX%6Qt9x zFY;+MHKp0-Xh=WVi=Wu0K3K_uAUGll&6LR=+LcE(FD) z-5?}@!;z8d!$Q_M%zny+k%z9fXr!;ih5TI5sCN~r(YXmpv0WE7!=9m;7%sOz6z$K&!xZ4DiZ zr@fv`9;E}?W1VG0+K;Zl2zG|Y3r-{ zPulTy1SqO0=D57Vj-TwzT28H!y;}W!$Co%3XAQFv-9P~{FDnp4;tt4)82)*X0zlTF z@gkxEWI;tm<>|8SY33eO^JDZis>ROxgp{bp!tFg-V2xM-Cd&8;;qjN z_4&x$`W~jMbDm^AEqhS>fc=>dQo%tkv9f2E$1gKYXRg^1+WZbQd3PiK!71cZYHFR; zrTH;o)ijX=a%qngJjbGIQ4LLw_7^@@fNT?2HkT2uOt?0MC?M$f9eSPw)+u&9`o_#& zG{D>s$=JruA5Be~BudYqZkQ#GGajZr#AfUFjY+Ar(h_9U9tx6zEw%PI)*a$==kJ+N zTsA}o$3@ke*CA%6T`m3rLPMDjmr5W^op%8~e$g=VfGQdQo)@AK_3))_v58o^uV)f| z1yh5XK^wj5I3!9UBAFylSpA|2IG-m^^fIQE?;ptY=>F0_ zEoRkZ-nnLYy_?|(tJTJ7J5n9aj?MoARrdb_)h+Z}d>G$Zo`LVTjYq~gm7n%^`=2O)f%r zi8Cp^HiL~pZ zLE$Nq>a|*oMo7&y0>cRl7JiK{FiKz;Zd|^qf>E+aFNh{SYRb^C*v!vJNM?}CujAYx zcDW6dBf9v08Qi>Wq_+P{$%LFRd2Xfs%hcUhA20~^tkbzqCzn>VYSww3F^oz-7xZ_D zav2Xt|KE9F(5dCCBuMMd7$kB>{w=>|-l$3D67pXtaEJ><7O-yqKPuN~NOW9DwCG;6 zA%gP`PdSzA%D8=N7Q$w$(3T_!srEOVVv z2+L5frn%NWx%5n4nQ{x5SqIdQ(IV9B4%d3WrBq+Kqib5?raUC#tylTB3ElVjS8>Mk zVayGFqbCbUt?gg0Euhq+D+~SQwIQ=ZgaQm)Wg+1cDJ}gKpbEjNX|@VYMl>&>1L%4# z<)a&r0CorsXxjUp^#c}`0LP};%S90_n_h->Q^;!9M=R$vRbUKsEoSZ;I!Dz33k|F` zGPx9koc#~$dq_!Ro~-5jn1n4z1rmuwoP$;51Tmsp1(Xb^GG$@_1lYwD(A=RtuGb?SHbxG{srJaoE6;zBwKK}*7C{*2 z@g-l|&{;ceQczSR40}X@!dNVJ-)>^YD=PVQPcc@nkGJ2fvE0uZ5t*gzwkvKVypvee zjo2L8DXEJJ#a**8r1;H|R5c<^x_bK0b~|}r7r-r=q0R1F7ucAqO3u*#;FZ1IVJWdz zc6J)(TlJs+6ybcZgC%wicXq?HMM5KbR?I8!sp)`Z%LYl`Rv0&;C1kFo~_NhmK zvMiBUKP_(I-W*e|&)^T&xZ@>I`h{FU>bf*yv!%QKX)93NVH{hlk(oj{Ss)>x&R0dm z%AYBa?Qh+EQAT3cnbd~U;gI{-gZZ)E?`Jm|`?Z6>o22by!7?`aNk+!Bi7+7uizsz6 z5=dp9YdE*}fTEZg6r(WNZ$l2o?h1Y!a7O3m1eoN~%x;LK9OeJTi}}D^M2Fs;+59i# zkE7RBTq5cJGT}=t+B88+t;9pMoh3?K&Wm{R5~o{cCB3fTy_V^!ROBdiYGZ=q4Ak95 zbUXT^gqz)8A-@tBqaO$!J}RnmsP(ZBho7LI3hffHy|FB+0*TWDNT^mONlM+y^Jl=N zXhDz&TtJa!3g$6*F@F7L5@G%}W)>zT@;X}BJ6A;Pw4vhUm6c*-!m+B~|7DObZlLey z!qFE#NCLm23Yfm{?f{dVhD^*U5CH2!gx(}we^>+AdP}BroUUdwkP_K3*V`NQh))lQ zOCz6-9e+gF-b5dgR|TEZ4^SOBmq(wiUrPkrAOyA#YwF_kGWY?SUO+1fSbNf z;m0x2h{*hh0l5hul6Kf=jWS+fOpL2?ZIU2H5A(>R2vQ&2(U1%=-XEM*wVid+z4cnCOzevL4? zds@u;IOlFoBsf@fPwODkfP~E)72Gpbe{X%{5dS(D;lXJ*%er{*#GEh@bVzn&F zRJ)%%87yX+K;|T`0$bAS9eeBbNj&jK4X{L3Yxk>}^0z8J(jBd+Wdox78V^~pKzY)XX7@x~TQn-QFX7|BY<(CA{hTSG{-@Qh;Jrq4VWwWE_ zRs-M(z>zAOz>!F8w+GhH`TVuCOaC1NGh^V&$)iieZ4i7`R3jSk& z`_ZGklJEsmOcE-keV+c7pZB!tC!L)jvihE@PPppzzti$Us`S8zkNgz_BM@nY`=<<-QPCLykUYt zKaWa9B+d{XKx8Ht1o+bwCwRbHYpeR#=RNTpDk3eJ5tgPm^Pyc5OwxF!Xw?TuQo4G{m^fps>UEMe>Yk_$1&_kbz0$NCpz4DF=5r|MH81>V_4U z;sRd6NP~t6kMn^70u~5K$r12_9WpLiKHnJ=G#cK9{7Xb`a!giDqHq2hGI|ORC+1b6 zn|J@?R)nOu=?E@KQTG`{M5P(l2}n^@2wxe?rX)hR zGJG@)9u8*tjCZzg5P)0a=#N#I8vk%r`C!>m%J=~(TO&jA{~*)r|LjHZ^;E5KzMXH0 zINwMA$N3l>WZcfmz9BQ)Jhxj}%R@nHuai4?@Idjj4J8v){Pj9djtBo)K^Gx4{!}7L zhz;s)WWbUX%7ybmdLpqHLP0Wg0nVyNBZ0No;D;a^4}Z9TPAdj^!o~<7hH&`KcCRxC zOj&49e=F{DjmD@cbe{8i<}AuJfJeo1xrh-zm`VG2*)eoL>eQP z0+m{WpTDJ&2QCz2jdgrl@148#Cmmb6)0M8e;BNxgER0X^ABBr(x@?^XZ=)3Dcm}jW(oLA%`X)tT?wwJ$Rq@UE zpc&kYoO2b?H7BTY>)dA0bzA3MI9Rphrnc~+73BkZXbGhso3PK#`zMYzM@Mv6&|Dyk zMy7=+RtZ4}p;a}w*=)|iP$pAYvxTi}iq?Lk&da%b(RE(9;_D+^6!+Uth>AC$(FkU; zn&qUjlK4U%!p6)T1v*NnGxL6rp0PVT`3!>j1!r<*$Ee}go$a_f(`5~{nU)vqG{-09 zT09QuJi8_Jj>vjByRx?CmdZHvKRjk=-Nt-sZxDPOqH}h8iut+b&PChvHRCTq%8(QV z)n);OlzZ7w`2)K9TXbQUhYL$|g^Ouu>un-FRQy2DjX=a)IcE`Q&ZZ7q(9TnDyIdky8n04#Ybcc;QG2?Z-WKcH+X!T^W`jETwGorZpu`blah+>gU=trO zA~CA;xu45#q6t}2oH0+cNHTH`w5Pc&3{*-*t}Ddrn`2@%DSdEJBbik&s);lOFEjH@ zmeLyXXfac?Qi8dn= z6-Vp;O==GhgSsmIciAfA6QnJCyhO}8q_cYU_Ks0%U&K2M&Ize8Jgz+vbAgv_Isrl2(ULhDa9@G%5`Ea?I@3fgq}~3TiwY-#I#2z{W*LRrYn_wyBQNvySKESvpUx55j^?jzY!N(DnZMRS5{8JD0 zI%G6tI&87%9=aq89s0s>ky*CVINI=XGs0+mJR&)X*gx%o^<+ke$jX6^u5|Dc%V-`5CmC}i* zULa;?xTx0Cw+HglmoiPgYE{KrNj#H((Wx?%LaGg$ynn7Rg4-P`BP2vE%_DPJzB(_- zr37UnQu+ zG?zov=o^NZEXEYnl&tb#+Y))&dW;wjw>6|YwRx6>geo2Yv)%vHoWd6%kVVtnQaC)mvx`H?97cmL+CT+=b zgPUI824J}UEJcGw1^lND52VP-6g5wsdm@81&3RH;28%4EZM~#Llf+^=>AcHQ6Ip}kt^^l7=D?=)+9k~LUHm*9j*yD-2k_-Z)$WS-tJ^= zP{>ij%aza*Y=Y4sBE<-~1H*y$CB@~zw)lmB85cxJC)tbO%VUrmx=I_CjD!h|L;mEa zfCEVWN-kL%&P>g@os(!nAP6CuaiIOKKk#&6D<2SSP;8R>UoXHsyTSWNrTgDXImgjc zJfnx3Z0b!kx~5aN9B3E;%SI>n%Mv($G@ZzrnCe*c{tEgvz*Id)2H%Fu}V*fD+Y800wpq5w$=5^2W5JR^Y zv5)HX1*@CK#|G4At6;Tl%4+%W5AkuhI}#`88-00T6;kDOOTm)5q`Ia9zv6Z@bUog| zx7*&4i%2p94uJq*Nl0OR;GvK(;kB_nl|FFqj>s$hk-e(Ae-#8bZv!%0Zr`}j$6@K| zk=!2_h&#Mm!^EU2;^B`JP)7?45~8GKhREbAmuZcq8ouZ52}WY{CBc}v6xEzMl&M|e zlmOAK&ov$Hd#>H16){|5Y`lH@uAx8U9Ps0maEDBd<&(wBU6+_lETO~t4cNI2v5=|B zPu`7FpQA!;gI3~&5vr$g&41F0x($}4Xcjy!Fbgw&@`pI za6n_E5?Graoz)FNS}n0(%-RP%D9c1W08L9qC;y2gmddbsUAV-84vZmY{EhlNXviO+LUot+w* zb9#{RYT@{Wyf>t%G~36L2*waP{aFj^Qg+Yd_AW2CYX{k^lC2BO9qMdBC#N zZd>9L;t=BX@4@>^_*M+WpZDQC_q9>zT>9@QE1;W(oyWN3K~Apx=2j&(72r7cMD%!l zBnsJrc>twIod~3qIY55|qAt-y8)}J|9$?UF7cgzqG!RW0A*=4NhaB;Q`E^8LS=$xh z`9XewV=f8*W403mjZ~L6M44^jvO8kZ`@nqReI`d%vp!^2$#F(^Td!fl10H!SSZf4? z!+9>LbQ~p_f~_`%*g|5%vU2b5Yf$oer5}|Gt-WDN6i4XQ1}X_N)AYQUq!n>JBP$y% zu_#!W3OwUY@m74$HJ7oK$;H{2Ja74+79tx}(JHfd+_L-RLk;75dy-LiH4eyqt@`FU z*AGm;Z1peAP}PHg_=#a&Dx0CZKb6wm9e%$+5LaGLJYAeqhS;^oER5k_#|vD1-~E2v17;PE zT%f#VG2y1mH!`3^-vjy?bhe5Y3#Ndrz3Rn0p2XR7vcwaVE}`pxHgi&LwnQu5Dt zb;FbI`Y|za2OlV54kRD6DDY9Yi2Ndosj!xB^6advkST`fUDMP_)Vq{^urk2J*iA$k z1t3fx6c;DzAt#%#xVbpFbNThJ{#i+OhJ%1TuaEoX^ua6PA*ow!*K>EZqXy;=q`H1O zKrx>^*e^S2)NmbSFa`&tNKo6sYa$Z!m|q#3|4?*3q69UXD#BWKzL##e--PS@b6694 z);0f=e8L9!T3v$i_*V^>*MNf!YMawqDkgTY8>ZpFXJQ7&LkBX)%~i1Q0WFuSOM~IE zEUU8#zJ2vg0%fW~aLDCh)YzQR7#zyH6B_4FqF7hRLQAxHE3_?TJSKU8%RgkU5lo_= zicM_V?gtlHj$3SHEg~!_Z)!H3syoIbp>;-@pubYd<%N_vgoi-ADZz>Kxb-`JkN2{i zcspU%<9Uo6;;se>5cp$;NhUBVC)IxOjL4)#(&6gh$=N|m?_>%oFNxqN`a1^C7Wt@VKsfUO=by9P3Wa*?XpkJ!(swt^SVHVxy>CNxR3y9DK>Fodoww}iCTYoOr~QebACZ* z@$W~FW=tH|ZHoFlNlvw7n^OeqhyN}mvAM)hV|s}~ZogmIWPdr3GSA?R=1Dlx_{N|0 z&;Z|Rz><==T)z1VbzJd6@$6gKCLP>t0P;8lN{Fp3d3{9N`l9}_Pry%x`!jX;W)A8y z+VmPmfxm{caUa3MHbG%F8lb>*kma*1k>$2;s9Dl_IAZk5k-OM}r@w79x&C^DCeGPX zD;d%c_m8{loPbrYEs$drPpYdBr(Yfex5#YMmX*6VIIk8IaggIE02u4V3J z?WdCs6Eqy(c4}guLXd+iGl&h9qaHv#Z;;*`QU>CXx(=ho{!v0QrNxL|Do}5I{qZ|wU$Kl}|hbb@ow>nla7*1qbQyol~3R-s}YCj$583VG6e8RyB zbRAyD4i(lk+yoe!AvHh|qj0VO2qCGP)(H->S_>k)xp{7l;E0go`Beh?%k&w7r|BGA z7|ghszjvllEy0P){eGh#iaVf&8>I4iq0=K*rxtaOJZc4WV~|}ztM#NWW+}-`z!Eem zmf~$?)p9^8E2?8890P{(L*uiWPLNt)>W&nFNNGscs^5;`HKBjLV&D`?Fr4Um&QF%V=8I2dQ8bM%;+fVomV<`7qX_L++() z{vM+amCiUh)PNzG=9YCx=L3Vj?E+#(#}nEjk+^EOs-|`&yz77pN>AL@DdIu50&@L!WTqXcjL^%q^D-IM-NT{l93T#uCN)t;CmLs$n zj+5nbsVLjy!Enu_bw>#A&q#haS6>(ST)1g&9bJDyc1iwBWe@A7YHE3zc)9*Q={$$l z3df;b7rxsDp)kovjuk95)Gu2{@UOvD@d*=U=*C>nvt1a`6N905UhoQ zAX_l)W^01ka2FxYVN?((k9#XtIom9>>`Tx*kzl#A46Uz7M=xAgLQ2_Yl&_$0L=-$P zyYFgnR7AYaCz0d@`IzL9u)-iL1~^vnC9g5TPx5ART06nK3`KMx z0AECFvR{!or9X{w2OziD>XnLTvA zHZs0u44A>$11;Pr0QM`t_sUy{u~iCEs%4Lry6Ff^oaKPQEVRDwberHCLo4OirAlI8 zCu9-5IOa_4&Kl7znZW-u^L|!~6e=Db8iwXLKUP^U91q-rz>u-vIVGlyZw5R$QD z!PDPQs-WahXjGm2bq^=og0z;(X&rCMc#E)W$!i%ZFNa={#U!r=A>g zs(U@GWBVRjoTbBCkcmu%2QaYLkfJ@M{TZ<)oY5MGHCg-HNR#jWXDrWNNFE<+%}|(- z0E9mjeq7bD+z_e&MVR}sK%;5b4V5NUCn}vG`Ed*qL`Wz}(ntLtz&!3kz1&_z5P#t8 zZ=R^UD1DFV+%mIFy7b}kDe|?sS=+e-^sF7fBMH?DYJeOiGj=VjhQ5fWqc^$2nW%t> zCKPup{}vS4k=wYUf(ZhQpgS#@aaz#H`G#^IBnc*P=2UH8j-r*YF3xHNY=ih3k|pzZ zpZGdEcb_8!cT>26IK=`TPSFQC9peu}SknTo1tMWUQ9!{J-I}u#DQuGZO^;Y@jn|1; z4DuJ7xFoH<&i{`sZ}gklbYPn4$4~x+W=~``4-g^Zg-P)F5FopKPGb8mGo`sLO#_N-&auMSFPAxjPb|Bv-t&SKqNVeV-Re zBkP|9KZ<0Ef>p?vBM2_|n^Cob(MsU}K+!JT5wyhgaqejiaV4IgAQ6xsC|&_!a_?d- zk=(DQqc(YI0ZAQ+&8Sw{mtHnM*;Tfo@)4&O2A!_Xi}cE_3#trC5D*4c-0SssF%FZk z+Xn_t6Irhg@_<3o-%rW(n?3LF2nNS`8?tx+(1_M9oe}SMMsJY-nj}w%fenm}eHSJH zdLEj846*jL2LsEq8W|ySg%K@!cMy%H?+gbNaER%&BqEJMGYyKu@Z0mBy&*UO~sq(6mvKodf#3U%jr> zepllazBi<}g_=j9q%l^X&YI{!3DfusEE0DQ+v6JpG81(%d}-Ioi?C78>6+&Z=jQ+L z-#b41i#!RQ9l-qB#taSK7q$=&#M?NZZ~hLx-CP(mDY>cRQ)re)7cZ?}Jh`S-#M>5D zCm|<%Oy~k8!&CcHMsP#8eKBeim1*yT;M9Rd5tg>ssE>iuMRmm?f{6x|PCVUw8+oBa zq{7+bSk*)xxS=q#i1aM(CyrIz7f60qY?riUK~@Q@;1scR1ra2zY!zH~T9kt(B3XzF zGdp4wdT+XPs8MOeJVbu$S`+`$FAyS;9vxc@Mwpe(E&QH6B z%*0*bkkcoN_(^8j};oKX%nZDB`GybspLWmrxg zO>psw9L`^w%$ip*woNSFY7eA~E|<&9Ixf77-i$=!h@#-3^dfk`zj-n!vF;~vsPpTc z$I75J0F=Lq!S3KF(9P{iEY1x%F|p2`P==4O;*|4&2g(?1lyz$(#Oty6h}>S=bb;kT zL*vo7o1CeRX*QF}D%zXCxPFNRb6r@$$`24TpP{-X81?PLA<)gDevPB=9){5+E@U`kTW=iv`+I`5-p+ppV(h<}w8_u}!Hh_p7jE z4R_KX?hXc?;X2iy25enkUc+vKf9dIaTTxaDO;=J!nh_^eFbPNg4w$JvTB#kl+6F>_ zFXvfq*PYy%gKlN%Pq3mrj&SV_d?U-{=e?Hrq1)U>>PZkPTAvt=&uDIRLCgH> z3oV9?B#{^dZI6K`{*f7C^Y8{PzufFl24 zuyrZ zjP(Tg({|2=;e6cQhkw)YzHE<^^A7au9zV6?|MiaD+2SblKwdhG2B7aeh;*8$Z>)ZL@(i!V4UzmeA#H&b5Hqu$xXq&q zHn9|<876%M!e-KfGC0Epg;NPICVq>m!7}WUu`&vJEJH_B>IuU?2bHEqJjZM!;@(`U z1+c@{k56YkuSB-p&up?k9VO1)LDIbra-Y96&>keZJW^f7TVhcahrLClNc;8r;Fagr z_Equt-N-3i+>RPUpUJoP%!{=>_0kR!imjz4@MRZfV8t!Jo{-1KQ+AOBSR9!Qx9v ztK3(QS$M?{5YyNb_E!W)^@9pxmXGjY z#_@$^+V#QGk(vIPUvNA`KerEBM$$rC zHr_$b$@OL0L^zVw)_a91a3~Ff{lX9ZO5;JN)aQoQ+2{;*(!~x%^*1kQs=RF3vIjAd zlf?3sRg)snMMwmNTouCR*-7zlPUp8f$L-t@&$j}Jz<^auY@(to=%U!#!%5V%Cs=$x zZBKHV{Ir!H3Vi)`GjN|hNPw#qL_>9dGrEjmUu4N9lNAh+s74fYMiA$&kM9|6>dV2C zWscx+gh@UZ6q}Qbj7;&tjAa1m>wl%EyQTC99!nxu6l)xF8JU1dxuev8%_x``3h728 z2tfaCF;pIgBbgG9Ib$G0wzD9kqn&6oP=iVX73eSYbfqfq{y6tI0n3QNBxPWeTr9!} z7##BtG-i8A)b7pokJc$_DY^C_VQHjHC2qVWand6rO@gH&aDkN94TbZ+CF+r5AlH|C zqpYie_noDut*0diV6Tbt?nqOwh|e)b574GM8RfK$sD8HiWo5$jqX~$%|H6T1uFpk9 zIodEJPNB`qZ_Ir>8NJfRG{as#M8MeC@%*Val(+kD{i$p;lXb^FPeM!#0`Gk;wC(Ht zlH(-Jh#W1_dZQ`)dMsmi||m26W#C5IYT%Z-?<02mX?QnJDwqNJ+|G?zc@y`xy(lJzK*lq z-dt9=+C+Jme&e?!pmQN>B%}K}?nvtvP>wO>7)YD{#xCM?E?FnAw%}ybik|!0+R{OT zpE#hgVA{aY$#}#KR>41Uv=n-d3$gD+HzM{sq4X58%pocaFa?^HB^@ZZbfZoQ!pWP6 z^9Vu|TvmOYPEa1BT<#fg0?19aefm}A)S<&35#qfaz=Boi@Ie9r9ZmETd+l9R+5{W< z1}+e0+x9GKJ1$hEc%Il62ssqbcgAzGDdOQ#E1^wmZVDzEOFpv#ix}pZq7ko^FL9s4 zmI2M#b8Ld;zL1GCg1<_rm+>&E)Wb`D^@|Zl##0N9(dUmG?$b}p6z?1+X$u*NAuGjX zN>JBWD8|_z2s_Xlj)A*7mWcPih5@Vn)+A$La$aFZnnrB+*N*Aka1rxQUI>Z=+@@;Z z+i!p!+ItFi!?r!<*@}*g&xWx)9x(($G)~d4SZG|GH3Qqy8fQghRgyKB8X1^ts;U1q zBH&WLi`{abLE5s-|7gi&A(k(4eL41ia)a{kk2MzQGV0kCEM;T4^B2yJ!eg5rn)@Xg49ly#htkstKATH%XbVRg}Grf2)l- z3iL?L+Tbkzl)U;Zz-fpSF`K=NJPespQd_uvMO?d+K%dayjb-9boP2`S%7ExhhY7>O zY)0$?NlSNIVDjX1ogIJhFugz;D^Il04)=b z)AFH6wC;s`gt{1!lwG&~pR) z;$L$tC>U^`LV$}=OEp{=6m^b*v_S%Ij$ab~Xfg}Ud z)Xwx^*%jl#b%=UkwrhGk@Y@Y_NvWSP$^?5MKdcF8kvRRDOy0*;K;a-&JV5f50a~7U z#1G0lLtcL?<2{|I6PyxkgzRifns!I{@dyUeoS>Z~mW@R0^HJh8dJts`xBwPhXAr(Z zmr6_zr`(``{fJw}*oA8PG@IYz8W%eDR*>VuEyo!X^$?8LpZPu zYMqXxpLp^hiA2)7OTF)m|Ky)NKfBcQyj$LMzv$LUaxx$r_Dzpnk3=NEpG{?(R8!==p>{ z*tTWb`JB11_WsTE5c`f{cuDxfaTs`Oyea21{QlviT)TNou~9N(JfDsphl{IUREy;o zQiyIp0jB0Su5)Wt=gGJGy0^L3W9sf5hkGMLVeT&)M=FO1}3CHOYz(>794y+7a$_DJn*LVkMjZi*~#1N1l=Z|-ME z`q%r(LPPA#CmCaQGjW>PL({@xCkZGnHHA&iI2GzcH_tiZye3Y+Nw&PJh?ipKX=R(D z#SJn2S4H8}3QO#tH)~ESE^Cj|-bUvR2O@`YbN)iCp|Tudmz_~Z6HTy` z(3KfN#PDyi4M8xXsM;DG-944b1kPm^t#O4JJMu5DsavX}YQjo-B^@!4Mi$6R5P#bq z9?#9SSx0Z+Rhx)`^UE!%kEM~mr*TDR*7huII2~ra*Y@Fx~5mK65+qH-w z3{BXo4{DtRja>mg%t(+A%Enu$YO=%R#V@;+B`+LFUNe8VUnL_9*#|8_bq*SC2*ex@ z>dey;)f?d-lJ7P>idKJkffGtU$esK_!Q9HG7zY~td=5<{>$7%D%`Z|WIrGybD_Q-O zgF>iJV6Iggqo)XOCwO&HSS0$1;GU9~iSrZ3U8p?ZTw#XD^9>2h!v|0uPlCEancyDU zppqBtgb`7y0-WhA-w!zL_ZY5p=Phmzom}+D4~-kx`rSJPe;!dk{Umx?Cv^j+q<_iQ zR$K+*ptQEFa_{Rpu%C_)4Q!#3#SDQE25k@~j$y3%uF*ABe=4T1kpdNg!l1PJm1l}a z4Cc!2u6OiIgOU-Ha>96#AO+@U?2g-1rj&u@ffUL2vHsfsMYtls$8Xtl%SstPnz&@5 z#mw)YMS8p%cEat$o&t~$Lb~HWW|Ti^V=C3%yTgGBkfWooG=eiz2%)GdNL!W|Uk^AQ zJ$$Q93jMUOcV0**u5MQ=x*<>w$62WUe_BDW_8>}mRio3nR%ZZ``#MCXUT-LJ{BLDN zHOKwrcTMLF=gw1&>rbHK9bTc&>cYZ6_sfnHX`;d1!SK~abLIW|w(IV+YtI8t&)c+} zEK!7;Cl%ZxX(6^s?Is?63eV@Sm)(ccC7-eIv{n?#?(CNSa%2rCd5RNlDD8Q-^7yGG9giSAdM-($77uPX$4VH-L= zPfv%-*3Ojazg2bu@JMqrf?kiw3gmJJ#UlwT!Hl?@;E~M~J6&0~*0m(mle?u+G`R_f zF^abZJyeju@iWHdH@edgR?rEUq7-f09$d06bIK-aTs>`?OovtAMNx3FgA&ss9JJ^; z;W-0bw22Q0;eI9pNCiWL1L}lITVm!M(<8*9prJ($>Zn#2eq?kt{hyJQ0$62Sa&+@M%{1VS7h zOiB67?IvtaKFG~A7ag1!knmH7N92^O>*4Ig0#H zsQ~4`@-a)IY1;*k$*wD6>aiRa9xeg%N=%X@iOyxRi{gV%zMeMyua6WEX;QbM;$60y zWWLaO1%K+l*5CxR5S{4VdD(~I(?agp{ZL$zwPmp3JVGE`idFd{@$jiI?>_HA;!f~<03l{>e}NhB)(JvC*Ya4@zy?-T+- zL9-n(z(rXJ`~0zS&QRM^y>Xe=dJ#G{ll3n?CkM#{Q_3OCMqyLRdBz0Ivus2Mh-`Eavf}1 z8Dpi$ei*6hf}1x9x~TY4 z917@@awD_kn_pQ)QkHQUYPQ5%_^a_%p- z1ERn09k&tNQOL67Nko(FtW9N0l%ai#B3x_F(BKZPws%@%2_qfQP7_5Z%x88Q#=2cLXJsiwF~ z@!7G$fp+Mc){O>q}3S=6@7g{jcpl=Q6CdG01uhV z3OpzZY-Ne*8zRaK2^((l?{j0o65!%s5dU>Z+~3n`#Pr5f^;BKg?@B_?BnQ4+^IH?q z9*7Y``xk(oghOHAB(dMbg-4TOh9X6-Vfh;vtec%6xw6aGHThg0_U|787l4+%@bf#_ z3ET+kiz^#a8mt?+;fXi46DIQcE!o)}qJsumlo&pt8#8PJZ$|69S3Fejn1;owq&mTzscKv+F4DPL4zZel>>2=(AK* z3c^d`Dn38Xmb;KNu{7hrc^|Jnqg=j7l4BwzI+R4sfs;k(Wh&PF@AXphj5aVzR zlL%{!v_C7h7A?W=Fb@@R0_UZpHZ`T)EggIFGc0?v*LzxLtGu$0AE}+RD3%4q*|J_T z3j6ZQxXyBv_9silYf!M^X`~xD6rfFo=yzidI}3!fFHVO@l$FeS2si7#R!bzEneqp& z+K@D{dcA6_3SRr|uPRAuq$4;MSuEU!Z5dUaS1X=QBCH5Z>Bz3}q*XM_PPT3Ipm3^! zB2zZJ*G|~6Vu&p6WpG6_*-k4uv}{UP?tG4!E?W7XPr8lT&d7@;PRUSDX;dU*1mE(d zQnL8DYdwuF3&~EU^33AWN~<{h7WE80j4R94Ut}AxSSAqOs0+2^mUg8Nfp>p$aD#+= zzWH4ed)aAB!oX3c32bN1_Kbk`v6?!Ai z#bo|F5-f?7kWJS@xuB`1_je74${bRT=r8*Uo|!ZGrfDZitrq^MqKH&R25uKE++NTt zPQhNLNpq?7M}u#dhN6&huxh-()+o@VmOtRkVwyTfvHmV(y4s*M&v~Rd@i#d~gAHbL zNQ#IWjV|Q8H?h+n*o>O0_*x%s5wyUTQr^1XX6&Wlb)v;Mf2&5a{{ z?t)8y1>R^16Hb~Up4sem7%Ck4DEBb-S|ZA;JXX~sCeTpp(%zHhi{a^_C0Bss|Be?g zs=SD%`=f6@g^NbQ!V}$A0E`G5kcb)ywl1SBO;AtMvwipXW+8RWrdr`e)|)7J`|?fQ zuz&7ooGwUn!?kE2W!$++%(kp<&G>pqtlwv`6pI%+eE{X&(Z_n znl+HN0*E-q*^S&Z`|Pl)j|8b;GGMrfAN7(jt%gq2B%D@qx&LP|@?1KY#<9<|k#lQJ z3B{XT9CEScP^(yK>zy`bZE9)hq0Km-*cT4?d4iF%=J- zKtO2X_YI4+hFiR=&->L3ulN0llg(9zlXKA5?BlDKl%>)pQr`kG9=2;RrQ@;nq)y?mq?SK*>U&3+<-NgVT=l2`TZx>$t?meQ5Gi?{FlOJ+=+ODu`<$ln zgO2=&oN5n%ZLyeF9kD})GDSN|m%!t8uO5k~e>aBWqAL<-pGLmpk`VooZI_w~dZu;nqV$-Jki*&teGjNS3c>64wS~0}|ns zcinFL&7}Cfqi>YW5I*<x}L6a z#sCG;PyFxXizkd=yEnjhOu)x`So`jZ(Ed!EUq4`BVq+c!iBddG8IG=&r@={T(5ch)UyZ@xPg2mP*ow zvlrr*7H+wUa}{O7O}H@UQdx%QZ{QcO#I_kW^;_AK^LFRtvqaV@qzg57uIj;CP8=0j zGmUG^s(4xn&?V7Yw4upsDeB3%L|_cL@SFvfbRCrv@BrVPta3O*K9b!#mPT1q3gig9 zZ82Ty#H7g;TvNTHzb=ucIsOJ12ET9DA~#do&7 zDHs1pOTdFGkjoV$33b1PQflA^eYvU|DP)2m_Mh_hmLLkR!D>iIKW^rX6~MZL+qKkW zUn8GHN1Z2?p+>q9NQ{7ZMPH*5YlapM=W8e}^{uiZV8v_fI}an+_P2FIa*tOrrs~?X4bpNkfvE%g`##_}yUvlaE_d}=74>ZS}Bg8sBF|#7z^r<^gO2(fwfT3eIH@we zCZX}j_Z480j~x>%@s58hUlbBGE%KFALl6z^nyKILW~sM4W^L8pd&$M*-jbo<;~R|d zu<)pg{z4ZzRsRW zB&~cUj?}jW#>412PiJf;1SC5hMTa*;nxty~L#+DdK_pd|vEBoNt_kaH;9t8K-hC)R z@(u=A+H%oM!zUQk2=n_ur}>VI_n$#rHR1Kh`{y3JVE_mM{su2Hi<8qbe z!JFW!fuBI$$A!h&c@>pG8iV%0S03oge4KAE@a+NphBxwl3=Q<~&|6CtGus^vZ9$k3 zypfRx7*Zg4w$25;#69n63Wc~5+PMSDxF4IG+FpY4j(U210d2irqh+4~Ru4jz3Wf# zLdL3GqNi4p=T}tjqk*cm=dC3Ew*z1z{=c$%7YyoLC-@UxH)>c{o5Nld$V~5>D1*Zf zpkJ@;Y?7k!*+NO3XD^Z4!BO@@y--2CeB&)i@AP%8PZJ?W6ehr-$ShtS&6iW^q>=v2 z$BMo^z{t2}daCypqBPGJBhB}6k^lBFj+Db!BBKZV=jWmuSi0wg=&|k}Y*+)W$$*op z9r12!u2`>HW8TTR$8MYV{@Jnn`#ni=dAg_wI~DUB zCtf}J)y9L9UMitPij{?-dp1S$og*CPE^8rcH($RgpyFrIrq;WKTm0FT$FeI5 zxgFslTn57_pj!`L$JMa$2h?sl7rwnqu7*QMx1Edio$a(|O#q)#Feq1pQ6t>GXTx1bp>EgpUVbCgwLE4`C$KA+he?1B zti@Q~R1T$&WB~uYEt=6;v)grS@9W0UY(TFG9?44@)54ekg2?a1jMtoKcwKXQ#Ota( z4RVv~b}FuS`pC^MerjyaX!G*9k!gS1?~O6k6ZLjks5@4aQ-BjM|G*`6+&UF1zxo{l z_r4mAQImm++lTWEJFSZqPEo>MKSC`bj)iQ+XoByNULL(4U7>JI`tKxlCuaz;ia48$ z!J8DGiJWlB>m>-g_X2?WnB6KtOi&LhA8(;wOZ~xxvRD$ z%+zd2Ybp9|)!lkkrdb{y7%ZrNW*C7-3IL;@@}g+65d&-}5l#K;9KyG&RpU&X!K)n6 zLd?2fcI-ep&hD119(pijc z!wOED31z=69Hyu#fYLf;M7?|z#SQ@P5i)IMP+DlXpK^d%PTL9lu9YM z7bH7?xa-OuVMqTE~%8 z!gA<0PU)&Bn8oNqA=2Z-^ZvE&B;)BKWxts)N}(he{#+QjZHHZ8cQSwdqs(}7bi$}G z-w=EboqUpAyZk3m3)PixK9ma%rhxPlw$-u1`;FLtnpSfi`Jhf!6SuBh#DDRH&+tTS zNe2#iikT*ox!Qd1$$~c^XQ@q9iXyzD&nj5e4}x&yrcIwZ(-jbkN}?$0`Tbuzx3 zge;b<_#kJO*$MorZS-yQ7Cb`+9{r1J0*q1YY2{=)&t9Z0WqtoQm^b*W0cNqv zM zp0Pn%qlTFM6eA)EXTg0()Nv8xu-g$rwJ2KQu*lhRi7+xIxHEThfi7^DCe4y1v{fnT z7f19}n7g3d%`Qsd_NKCLy`w!F`ngM!O=QKz22Kfj89G*8WTaAmIgB`Al>zYAqFbi% z9GaL!wS4+-YjLGK@j2WpMxU3kl}Qf5BRB=1k{z?{u|^?WL8ovh*3h`TjR%u*PXnuMT=CHh=h!Uo{Y3lua5S5yYYK9y|)TK zvrl{#9B-PJmQ88mTM&XVx@RS>NVG7TzddyTt;odU+?xDdMU)kR4BPWHQ@l?%E~D9M z{EgjJ+&vi>`X8dSJ|~)rKKx!O$n0@+{8=9S8Nt*9*SIc|b-Z6Fy~e5A3KKT;nuLWC zuytHYuIp5ZN;Onky?7P?UuQuJ%Istm_#c5cN$K}?e~L~^;r5%>n+L!h*hl82rzG(8 zj7K?G4i+TXb9u7{c1gW*xBGT$DOCU1@@0~;N2{#pz^DaDNr&JSCW}PpL-tReagiPD=hIESM%d{K6bM8q3bbP<}C_qs)R3338 zr!Xa#(U$z{*oD6?T;h;Cz#_Wj7XI||#nlv_Cp4FD(=PoM!EOLcboaqb=4%D`EsSFOgh4j(P~^=~jf;9sVN)UD|7k z_4_GD@6>p(9|?-E3WuyiikU7n6qT@Qcd}~@^~##aP=-^aS`9k5MbMQa7qpY%J>Wf*`}sjG;G^6CK3$j^tw6!;_KbDO09RduF&g63U| zTo0-W^++N<*X#N?pJ*nZGr6w29ANhKo5Aw{PTQAmp22E{VDK`bxz_6^q{M#%mBneh zzaPb9?(W_uh`%FVZ}p;Pl2`@nW&@M&GI0;@_w@V$s0O1tdVYxeB*KIu>~y!1XgEhr znruXc`y#>ByhhV!-yVVeOvF2y2X}cO{!TZb`JX@WalZFOb)I+@3<5FIYTv(rZ;b33 zF9p>RhTbrY8(PG!g3;eBrEa{tAJ7_t@NvtSFe z>>FNJlOUkzvqWAw;_3?sJNWr0jhlErscf|qBUmN#CPK|ww&Pbl0KYcNW9CK1DnR0k z=w?mD`u}!TVN|gUFVur|5N_t$Rm=*uQzVVuhOZr<^D?%SjU(OUO#F4j(4Sg!Y7A~$ z{pp9LUWqLeDOkU*#{D*O?tZ=r=-;Q5v$U}_M3f{z@pN zi6OtD$A^g;i}J#IeW80laocr9{w@$SW{A-F;Y0r67gPw9C?>xndq=}lxV?OkmH;mw zGy2kl3x|ModA@I9073!0PMx^l^9h+AVb0zzELLgXtvT`g0#IN2aX=A(9S|adPtZ?K zY0-=(1ThN!T>CjhzI4wIO8M@`n&-x{g*>?hHg@50u5i@nz+|YK;Rbncv;L%Ow!k!x zm#`ir3|zP&h>#;q!?-GMWlCsDv+XQI2oDj*PDF(p;!X+MO0RUaUV|jm zoVw8Qkbaq^{PHDPlPhDePB@Q+-WDvy$pD8;p91kjo#7q6wV{aX7E?+N4#-S(?7MF$ zW{B0+r#{v}NzrBtRQ;o^hP+Z?mi4>;Q}Gpil) zY@=~{mg+uMi`^gKv)gOPvNs)ZkXn=pH8^>^&gPT1XE#j^50Ua_#9~US-?hUMUy{|q zj$ddS8NU1xZ_tx91Ns%Q!5fTESh}!#0mh1f`hW2b;tty<^x6ruT33o1ZN*%6Kcq63`hTzHgEm}?Cf3t>l1 z?1vDP2o?w-LMyj>A~quHlGAvp5j=oqZdu3SGyC)lMmZ!!6(-r>vtE}DDNjtl_uylH zueidjP`hm60mrLGB*%^XA-ypwOX*dF}1avewCovd}s5%IrY`k&Riujh4T zjg5_hjitvP$pDs;N>HF*c6rpO9YZtQLZ7$4sVAp*SE?7g~DZfyUfm0RqkLc#N*AY+Hc5W|B8sxh`dY$&?2ML&1R; zjya2C8vc=Y80`;U8=-WFxa%2l%^s>>yjWAz-N*fFLJ-tH0)qrI--+gfk zo72qO=oDj>9z1*$_j7me?1^tvhucKPqWSSt_KAljj|bUJs^kdD{pZeV(}8B3(?2P| zWMuCP6cK7o>0Q0WnXk)%k-~u=K+AFGh=B)qyTEk&F;j3B zO+X6%(B!5f z8c>zwWA3Y$CXn4)*hsf9E`1=xa38+&?QP_-0f05pKA7o|Pg=6K& z!`(4DsTDp`^osM=!q=R^c+!W?&!v!&zUv+l1j5SYNvP;oDkl-kp3L)})FCk?k?|c? z+A#)tuE3G`l!l=7P$VVxTxfWN^8Kx&d6meQy9_?4BjsZE(yTj>QsReq#ayDwx4^{} zQI11e?@^LBd}eH-X%>LEMQATiDW#t+xe9E&*~r9`DLW$|BnLu`8-*1Trd>nKNsK3g zd)Eh|x|Yy%UO7B9=8HCq((kbqpezVr%94gi!Uv=xkU_g6=mv1I4^oZ2@?u#07{ntm zx(J{yu$0y}w6>z+HH#*JJc=d&U=IV;R0EkgB(1|U?_#AG>`clh`jRHNU{)~$+7=uj zZ}`dPz^%n0TBy-sM{vD*f~M36la@!umw}u=yL_-tbX_PJ8#mW$lK4}$XXPXl1=$Sw zpcRTBu!Ct#jG~i{5JsZWd`aRV24W-Dv#9TEV8wd1!sdBk2EVrrcE?{y(ZG^@U_O@u zp#fNWD|Yw=-GMqXyS)dq{plRxTA1XjL7bvAi=v}8Q+U&B92&T(G3;WWx>nNsHS@KQ z?SxmSxQYNYeIOjGttd^fk@#G@k+a=5bwOt8RWp5!kSAD;Y;kd;E_WXF9j2Bh3S5i; zrfr&LL`7_-EYTxXL+<`yBmb2#Lwya=XT;)ShQ#isl~4CuLhr*y<}pNMIxYF5ikVtu+A<>)QLz$t4y<7u+$ zb!@vau8`G+A)fh=SBAfC!8GMFDQ6sk)|QSBWCLZ>!#hnu7Xe1jtnp=PuhNlv5nOLC zG8_3ebM3M|>V+67<>Hi|mivr|nKtVUA-!*%3ln=;_B|Ou?-RcFU05N*qluJ*7QGf5 zyg2?_xc_kKmyN^B**WoSM!#fQHVu) z6}4Fa=(Sm_+XuZW^VDx~OE8N-=mV9(ce4Z&EOETBqu&B41{qZmp_rJ?H;*+nENk}W z)*hI5y}h@eA_Y9@2l}K;3SP_Dfkp(puQJWda;c!amH;501WLP-#otXIyyBcsZR?wpa0LSc=Q{qnK{XDp7LTzi+BG56n8 zaYa}cxKD9IDBR-je~j&&4hVAc>IR`_1aem0FwYP8$3rixlt&g8k_DUjljTzd&g(+n zpyg7;gKjk4!mgR6U~XD$ukon(c>C^?(hy`F%{urWbXI!=4u~AII%8=T zh@UHF?twqyD&|7NSU6-qpb=H5TUfDi+CqQm+t26ilM1VTiU{RfQ zjraM-u{45wsz*S|hp@nfnI@}t%&v;Tx|rV-rRY|a92O9Phj5_nGG?{hl*<9z*`1n@ z^EGkeL4B4>q56{O_{%zPUadUIUvZahLY#q|O;{*1GItnb6&5vYmKs&2m86y%m*dSF=-XzT&k z({m-aA6fiC%1W{K|4>4ScY;p|gZhoS0)C9ekV2qhs@xhpSvRbGrQK7gKt+hpnZx|T zHyk2e2rRc3leZ#eZO-^3hOEyJy}1+~J`%4=9->0dP7`v=AADkLek(4enl2`@l?$%= z_O$mvEL@DljO_fI9gNPKDVtoYX!N}6j9g_1k}K-5322K2L2HX7+=ddHB*LQ|Z#P%q zf){TElb--^7DZ?(A_HPnqOjT9(Jz|ITU@<+G&MJ?XlN+txgZ4#TjAo#+%7~hEiNv~ zt$zMMaenw~KPnhc7EC#5ibc&E@hv?xtV2T)M)+A@k2QUNBHy<}`TK|I7YC47{(XTn zZ%JR~>WF{dJ^6YGjb~A;U}CQqOm|_pzPJS6AU$6wTFV1JwyNy)V)(H2qJMKP>d!U`_->++I7ZMR?_|FzPIbV++Y@ zJ?LplJzzCkH~#VZdV<Y&bv^u(*;##m{o|=H%!R z{Fk}ec944#zPjWDYVDm;d-{z3^vP*c%nh;7?<@1W9vgCku4eT~Cc2GDbrAKt*>Ye= z`W}<_K4_8Qvu`!U_h2LV+zL1%An~zKW*%IP#!i&&l35QXeX(Y=F8ci<%3Yg~@V74s zswx0-PB<;&Lvc*>8413C!f15>mv!7VmJEh);Edwx)0R6&2Q9-i9xAmYnEi-O^az8> zccHw`7Yy^_m$(fAU9vFZq|#1^e0ijZ3ldR%f3l?*skMoqp(<8?vxEa>>pfIUSwv~r zxV`q^>77!ME0B?IR%8kXUHZsUVS4~C+h37tVxGbcGkpk7XH*5aLm;- z+S__^5`OeranJk|6mLK`Y0~K`V#jUYXKA*UhYxqi3nCpW`5@}5ked?hfmS_h()}} zwt%xv?!p`W>o#FIc}948$VU3XwM8QCSsPKC?ZBO8bjdeJ)HG5Lzw3kv`GyY&-uScO za{yS2@X}P-&7s7a+CSWl6$!wrVwVW)-iv?vPu_e-=9$)FT|XImS!b#}qY1(&i{ZAz zDYXhmr4ue>l(?-IK)AGv3Q;A}O#iGH?D=a-X^Abuof2MfONSa6ZU=f5YXa}!E?1jV zrRA&Lga7y-lb8LqI5I5H+1k&9pO6eg?aZ#f} zqt3C+NpmL#zRPbT4cd>WNOScTB_53vbe`{$WhW*EL(XZN#)m15LYkEdT#slFbH6;P8 z>yiknC|DBj_$<4Q2sK5Xl7;VdELQ5vT;R;SYfrs}{O$L+OhS$%{~bc_15)k)BEaE= z_0Ax2-V2>U$DXZUn`6v#zG)k$gZf*}^G@sc;@uLzmwu?AM-!sEy6#|Qm%!MtpGwSL zLJNP`v>*9z_`P!7b8&iJi4;Lgj8i=q{E8o^ULL1n2Qg!dK0(3ORUQvIZfqOvK)-6n zCe{0{j)YuIuG@*;?eVQDrsHU(?STpsA0{GHp@^(6Bj`ytkicNSNv`%Q^5Lh$aU}sY zZVBpxD|q~@*~`e~D#?w*Z1366eU1lyC(t(L)D9|^Nzez_koCX#P=}%X69UGAdW55P z?~g~fTdt?xiL2aSE9L)v>&I988)Ib zpU@TJ`$BZfxr!v5EiT-qBt>Dk_%|Z>0!Db4v43#pPDH%uBW%|tD(_wNWHoTes0rJ* zbZ2SAQkOfZ=HSIVW7gtnYXmOTGkaZ6T~TbaI{j0RR>cg%av6~PKSI|HJ<}<3=n!aa z^f2yjGQLSTc!M-6d`xUBDCY@r^#)Dz0P>y>)?>EYb3(zZfG|ZW+6*JuL=}9Yc^$28 z#$?oV8rYg{)>8}eK8~Jkal;hSe&+rrQd730C8|__BlPm5c|N{^sTLVBQ=g@LrBgFI z!*7d~1?aA80BZel+U#zuyI{Hlg&rD_^a|8NK@(sU3DZQMEvD)Rsqdo|w;Dd)#33$` z!U6AuP!DzgosVP__AY!ALO8u@5i*WA@f3f3y!b8#8f7JKlB?rjR#T(P*~3D2>DQB`yxD@En5i70mW z-`sz4*atJ#M$DyuXzASorqy-L42n~qPY`(vQ(<5d(@l);&1D2y5l_Z#PZ@Po_&CX7 z-h8Ub(aN(3X#~nSgN4jpY6ifM;4-Nh<`D4iEMS~g?VrIV(^ zvE+1(eQn2P`+D!A4^EiXve+dHcrlJGKn`5wx|eJn&W1}rhMwgc(4rsj7wcUa&yJxs zHw&v{+9_-i#b+!Cup9_{=9-}&u;*ff(cmi3Lwl9UF4DF1cvP$0nD4L6?`OzxFQkwO zg%1LzS2*}C60UA6NUB!%Y2FVG`l7F+|0$B__= zJ}-pu=84eG0zd7D0btTGBuo-1TmcKvR&-zfmmd~K9GcAM&j(f+^C&mCc;B{v&3~ul zV)7kI`9ZUk0Y{CY>+ z#Xp79Ce$W4r8uY0${`Zc;!B~bf9yv)g8%j5|6#^1je|HCb{2&s9;ah4`D?0iQ&tKL z_}{;O!JiFTJei`$L|wb18HT--l(JHgQ> zK{v#_?sK=UHz&}*hy@7&^|cw!U!_c1qMH{7)Sj+{_eL>g#`1H#3~i!*0gd-n|`(%=c> zObj~eR___H)^&>>r&!Eyzx87zsX8o{NhqO&*vjSLi)ou+>hjL1@#O76xS*4PMT;-@sxTtQP|RT9A7+ znC9+8++Cm?KH9eNaSiU0N`Gg5Uy&Lypj~r>3z$R5IU79Jo!l*gpGpl8ujXx7=o1S_ zgj2$zoNrnQCqFnQO@+q~4*Kh~G#!SA)0~iH^>_55Wc{~!|H80VON>Qm+tZBK2Vpn1 z1Qw@8HCV6clf6Kn{nb1CVkipf&s+&pBR)K}#1wOfXd)`MUjzBUeep1**uhR1jr5ai z|1P8XJD{7pNnT;+#RTGsB;uKp{19>J%~r3Zz-F*Tk|5B`19SR*{q=vwv*_>g;MU+c zERF~g#X3@Q@mEM@(L@jqxINdzer6c3F!r!4re&_eSgWUT6~in@#SS&_1>dg6gz-ds zLE4SsB3Xr1NR*gGL*+-_5W{-+M9`NzO0Hn7Rz{RDaSBAN)gOljwnZMVvytMP#bsGY z8Q^R>YO@Zc(Kr#pmT2^;va=w|NDEIf%V=@dL_i^5s+HT?84d@wPYK+y=RU8`dK^lj z2A)F-?JHbMU_$p&(9WN+&*V;PMMw@5V9Gp6$k=^Z8VJdk82-qkrT3sJgXbqjLoQJJ z9Ah(MCyJXZ6NbhpeZ|G3hNj*y zSMQ60Xu(`C;HvEarEs1mPVkkcyZc}|nZ)yy4D72HZi#?js-zRVO^5@#?t?^C_Z$M9 zIP92QJMh$Mur?OjLUu^yKlnk>Tiq3Rab(VcuG24cFgy59!Z_Qrw%=DB|w3~~)a-V64V^4=+WKOgGa->y1e_E7!Yr_SvGjdg{(aV>1# z<6*byjsHY5v6<5OWdyj9gk8KgpNmZxChi-(OVgQA1oTSYV8F|>Ha|PerjVXbDl>O@ z$rf4e3L8;8etb7&0}-Ve1yj}D*JTM=>+(#Pyg|kl2*hCm+!$|Gj^TT`7&6_o)PP0p zoc=tTdg8_Pz5`DY6^v^)<$hBE{bE<3|4al*ey@wwy6uUbR~=Cyh&aS9LTm5)zkh-* zyoP@Na|hw{aO^#e`rZ7~V^YQ)LoG|Ec=bGORjaT5)^ik|d-p>x7O zGo3c+g)%&Pvm<%(sqRQ65V~s?(;pO`2}Ko^oJnyrUn>K++7dBA3+8Z_g6_!ztAasq$k_{R|E_mk`4 zDXwJz2CH;A5qbM}u)TtigGEs&X>>9|d&y7&71q!JDEi>&VNznLFa{x>$}bIP_;-br zri23{BqOw{bZ{9wy@HK1n%m`_slGSN_BQN*vA5)(_xEtx4BvmEKe}GJ8T>Zar+!

0U77{PnT-m;{DWsLn1$o*i>0gZa9} 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

cGBs0O;X@{+<6#JSbZe%#i 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