Navigating the Maze: Streamlined Project Structure for Data Science and everything around it

Siddharth Sah
19 min readMar 15, 2023


In the world of data science and machine learning, structuring your project is crucial for ensuring smooth collaboration, maintainability, and scalability. This article will guide you through the best practices for organising your data science or machine learning projects, complete with code snippets and bullet points for easy understanding.

Table of Contents:

  1. Overview of Project Structure
  2. Directory Structure
  3. Managing Dependencies
  4. Version Control
  5. Jupyter Notebooks and Code Organization
  6. Testing and Validation
  7. Deployment and Monitoring

Overview of Project Structure:

A well-structured project helps in managing code, data, and resources efficiently, and it facilitates collaboration between team members.

Directory Structure:

The directory structure should be organized in a way that separates different components of the project. Here’s a suggested structure:

|-- data/
| |-- raw/
| |-- processed/
|-- docs/
|-- models/
|-- notebooks/
|-- src/
| |--
| |-- data/
| | |--
| | |--
| |-- features/
| | |--
| | |--
| |-- models/
| | |--
| | |--
| | |--
|-- tests/
|-- .gitignore
|-- requirements.txt
  • data/: Contains raw and processed data files
  • docs/: Documentation for the project
  • models/: Trained machine learning models
  • notebooks/: Jupyter notebooks for exploratory analysis and experimentation
  • src/: Source code for the project, organized into subdirectories for data preparation, feature extraction, and modeling
  • tests/: Unit tests and integration tests
  • Project description, setup instructions, and usage information
  • .gitignore: List of files and directories to be ignored by Git
  • requirements.txt: List of Python packages required for the project

Managing Dependencies:

To manage dependencies effectively, use virtual environments and list your project’s dependencies in a requirements.txt file. This makes it easier for others to set up the project and ensures consistency in the development environment.

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Managing Conda Environments and Reproducing Them

Conda is a popular package and environment manager for Python and R programming languages. It allows you to manage multiple environments and isolate dependencies, making it easier to reproduce your project on different machines. In this guide, we will walk through creating and managing Conda environments and demonstrate how to reproduce them.

Installing Conda

To use Conda, you need to install either Anaconda or Miniconda. Anaconda is a full distribution that includes many scientific libraries, while Miniconda is a lightweight distribution containing only the essential packages. Choose the one that best suits your needs and follow the installation instructions from the official websites:

Creating a new Conda environment

To create a new Conda environment, open a terminal and run the following command:

conda create --name my_env python=3.8

Replace my_env with the desired name for your environment and 3.8 with the Python version you want to use.

Activating and deactivating environments

To activate the environment, use the following command:

conda activate my_env

To deactivate the environment and return to the base environment, run:

conda deactivate

Installing packages

With your environment activated, you can install packages using the conda install command. For example, to install NumPy and Pandas, run:

conda install numpy pandas

Exporting environment dependencies

To reproduce your Conda environment, you need to export its dependencies to a file. This file, often called an “environment file,” contains a list of all the packages and their versions installed in the environment. To create an environment file, run:

conda env export > environment.yml

This command will create a file named environment.yml in your current directory. Make sure to include this file in your project repository to ensure reproducibility.

Reproducing the environment

To reproduce the environment on another machine or for another user, follow these steps:

  1. Install Conda (either Anaconda or Miniconda) on the target machine.
  2. Clone your project repository, which should include the environment.yml file.
  3. Open a terminal, navigate to your project directory, and create a new Conda environment using the environment.yml file:
conda env create -f environment.yml

4. Activate the environment:

conda activate my_env

Now, the environment has been reproduced with all dependencies installed.

Updating the environment

When you update your project dependencies, remember to update the environment.yml file as well. You can do this by exporting the updated environment as follows:bashCopy cod

conda env export > environment.yml

Removing an environment

To remove an environment, run:

conda env remove --name my_env

Replace my_env with the name of the environment you want to remove.

By following these steps, you can effectively manage and reproduce your Conda environments, ensuring that your data science and machine learning projects run smoothly across different machines and environments.

Version Control:

Version control is essential for tracking changes, collaborating with others, and maintaining the project’s history. Git is a popular choice for version control.

git init
git add .
git commit -m "Initial commit"
git remote add origin <your-repository-url>
git push -u origin master

In data science and machine learning projects, the dataset is as important as the code. Changes in data can lead to significant variations in model performance and accuracy. Therefore, it is crucial to maintain proper version control for your datasets, just as you would for your codebase.

Version control for datasets can be achieved using various tools and techniques. Here, we will discuss some popular tools and best practices for dataset version control:

Data Version Control (DVC):

DVC is an open-source tool specifically designed for dataset version control. It works alongside traditional version control systems like Git and enables efficient management of large datasets. DVC tracks changes in data files and directories, storing them in a separate remote storage such as Amazon S3, Google Cloud Storage, or local file storage. Here are the key benefits of DVC:

  • Storage-efficient: DVC uses data deduplication and delta encoding techniques to store only the changes between different dataset versions.
  • Easy integration: DVC works well with existing Git repositories, making it easy to incorporate dataset versioning into your current workflow.
  • Reproducibility: DVC keeps track of the relationship between code and data, allowing you to recreate any previous version of your dataset and the corresponding model.

In this example, we will walk through a simple DVC workflow for beginners. We will create a DVC project, add data to it, and demonstrate basic DVC commands.

  1. Install DVC

First, you need to install DVC. You can do this using pip:bashCopy coe

pip install dvc

2. Initialize a DVC project

To create a new DVC project, navigate to your project directory and run the following command:

dvc init

This will initialize a DVC project and create a .dvc folder in your project directory.

3. Configure remote storage

Next, configure your remote storage. In this example, we will use a local directory as remote storage. Replace /path/to/remote/storage with the path to your desired remote storage location:

dvc remote add -d myremote /path/to/remote/storage

4. Add data to the DVC project

Now, let’s add a dataset to the DVC project. Assume you have a CSV file called data.csv in your project directory. To start tracking the dataset with DVC, run the following command:

dvc add data.csv

This command will create a data.csv.dvc file in your project directory, which contains metadata about the dataset. Make sure to add this file to your Git repository to track dataset changes:

git add data.csv.dvc

5. Push data to remote storage

To store the dataset in your remote storage, run:

dvc push

6. Retrieve a specific version of the dataset

If you want to retrieve a specific version of the dataset from remote storage, first checkout the desired Git commit, and then run

dvc pull

This will download the dataset associated with that Git commit from the remote storage.

Jupyter Notebooks and Code Organization:

Jupyter notebooks are great for exploratory analysis, visualization, and prototyping. However, they can become unwieldy for large projects. It’s a good idea to refactor reusable code from notebooks into Python scripts within the src/ directory.

Jupyter Notebooks are an essential tool for data science and machine learning projects. They provide an interactive environment to write, run, and document your code. However, as projects grow in complexity, it is crucial to organize your notebooks and code effectively. In this section, we will discuss some best practices for organizing Jupyter Notebooks and code.

1. Separate code into logical sections

Use Jupyter Notebooks to break your code into logical sections or “cells.” Each cell should ideally contain a single task or step, making it easier to understand the flow of your project. To create a new cell, click the “+” button in the toolbar or press Shift + Enter after running a cell.

2. Use Markdown cells for documentation

Jupyter Notebooks support Markdown, a lightweight markup language that allows you to format text easily. Use Markdown cells to provide explanations, instructions, and context for your code. To create a Markdown cell, select the cell type as “Markdown” from the dropdown menu in the toolbar, or press M when the cell is selected.

Here’s an example of a Markdown cell:

# This is a header

This is a paragraph with *italic text* and **bold text**.

- This is a bullet point list
- Another item

3. Modularise your code

As your project grows, consider modularising your code by moving reusable functions and classes into separate Python files. This helps keep your notebook clean and focused on high-level tasks.

For example, let’s say you have a utility function process_data() in your notebook:

def process_data(data):
# Do something with the data
return processed_data

Move this function to a separate Python file, e.g.,, and then import it into your notebook:

from utils import process_data
# Use the function in your notebook
processed_data = process_data(raw_data)

Useful things you can do in Jupyter Notebook, along with code examples:

1. Interactive plotting with Matplotlib

Jupyter Notebook allows you to create and display interactive plots directly in the notebook. By using the %matplotlib inline magic command, you can render your plots in the notebook itself.

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.plot(x, y)
plt.title('Sine Wave')

2. Displaying images and videos

Jupyter Notebook can display images and videos from local files or URLs. Use the IPython display module to load and display multimedia content.

from IPython.display import Image, display
# Display an image from a URL
url = ''
# Display a local image
local_image_path = 'path/to/local/image.jpg'

3. Running shell commands

You can run shell commands directly within a Jupyter Notebook using the ! prefix. This is useful for installing packages, checking file contents, or running scripts.

# List the contents of the current directory
# Install a package using pip
!pip install numpy

4. Magic commands

Magic commands are special commands that provide additional functionality in Jupyter Notebook. They are prefixed with % for line magics and %% for cell magics.

# Measure the execution time of a single line of code
%timeit sum(range(1000))
# Measure the execution time of an entire cell
total = 0
for i in range(1000):
total += i

5. Interactive widgets

Jupyter Notebook supports interactive widgets that allow you to create interactive user interfaces directly in the notebook. Use the ipywidgets library to create and display widgets.

import ipywidgets as widgets
# Create a slider widget
slider = widgets.IntSlider(min=0, max=10, step=1, value=5)
# Get the current value of the slider

I have started using Jupyter Lab more than only the notebook. It comes with some great tools.

Here are some of the key features and usage of JupyterLab:

  1. Flexible workspace: JupyterLab allows you to arrange multiple notebooks, text editors, terminals, and other components using a tabbed and paneled interface. You can customize your layout by resizing, splitting, and arranging these components to suit your workflow.
  2. File browser: JupyterLab comes with a built-in file browser that enables you to navigate, create, and manage files and directories in your project. You can also drag and drop files between the file browser and your workspace.
  3. Code editor: JupyterLab includes a powerful code editor with syntax highlighting, code completion, and automatic indentation. The code editor supports various programming languages, including Python, R, Julia, and more.
  4. Integrated terminal: You can open and use multiple terminals directly within JupyterLab, allowing you to run shell commands, start external processes, or interact with version control systems like Git.
  5. Extensions: JupyterLab has a growing ecosystem of extensions that add new functionality and customize the user interface. Some popular extensions include JupyterLab Git (for Git integration), JupyterLab LSP (for Language Server Protocol support), and JupyterLab DrawIO (for creating diagrams and flowcharts).
  6. Rich output and interactivity: Just like the classic Jupyter Notebook, JupyterLab supports rich output and interactive widgets for data visualization and exploration. You can display images, videos, plots, and interactive elements directly within your notebook cells.

To get started with JupyterLab, you can install it using pip or conda:

# Install using pip
pip install jupyterlab
# Install using conda
conda install -c conda-forge jupyterlab

Once installed, you can launch JupyterLab by running the following command in your terminal:

jupyter lab

This will open JupyterLab in your default web browser, allowing you to create, open, and edit notebooks, as well as use other features of JupyterLab.

Testing and Validation:

To ensure the reliability and robustness of your project, write unit tests and integration tests. Store tests in the tests/ directory and use a testing framework like pytest to run them.

pip install pytest
pytest tests/

Unit testing: Unit tests check the correctness of individual functions and classes in isolation. Python has a built-in library called unittest for writing and running unit tests. There are also third-party libraries like pytest that offer more advanced features and a simpler syntax.

To demonstrate unit testing, let’s consider a simple function that adds two numbers:

def add(a, b):
return a + b

We can write a unit test for this function using unittest:

import unittest
class TestAddition(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(0, 0), 0)
if __name__ == '__main__':

Or using pytest:

def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0

Model validation: To assess the performance of machine learning models, you can use validation techniques like k-fold cross-validation, train-test split, or holdout validation. Scikit-learn provides useful tools for model validation:

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
# Load the Iris dataset
data = load_iris()
X, y =,
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# Train a logistic regression model
model = LogisticRegression(), y_train)
# Test the model on the test set
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")
# Perform k-fold cross-validation
cv_scores = cross_val_score(model, X, y, cv=5)
print(f"Cross-validation scores: {cv_scores}")
print(f"Mean CV accuracy: {cv_scores.mean():.2f}")

Testing Jupyter Notebooks: If you’re using Jupyter Notebooks for your project, you can use nbval to run tests within the notebook. nbval is a plugin for pytest that allows you to validate the output of your notebook cells. To use nbval, install it using pip:

pip install nbval

Then, add assertions in your notebook cells and run the tests with the following command:

pytest --nbval your_notebook.ipynb

Hyperparameter optimization and model export are essential steps in a machine learning pipeline, enabling you to fine-tune your models and deploy them for predictions. This article will provide an in-depth guide to hyperparameter optimization techniques, model exporting, and using these optimized models for making predictions.

Hyperparameter Optimization

Hyperparameter optimization is the process of searching for the optimal combination of hyperparameters to achieve the best model performance. Here, we’ll discuss three popular hyperparameter optimization techniques:

  1. Grid Search
  2. Random Search
  3. Bayesian Optimization

Grid Search

Grid search is a brute-force approach to hyperparameter optimization. It involves defining a set of hyperparameter values and then evaluating all possible combinations.

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
param_grid = {
'n_estimators': [10, 50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10]
model = RandomForestClassifier()
grid_search = GridSearchCV(estimator=model, param_grid=param_grid, cv=5), y_train)
print("Best parameters found: ", grid_search.best_params_)

Random Search

Random search, unlike grid search, samples hyperparameter values randomly from a specified distribution. This method is faster and can sometimes achieve better results than grid search.

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_dist = {
'n_estimators': randint(10, 200),
'max_depth': [None] + list(randint(1, 30).rvs(size=3)),
'min_samples_split': randint(2, 11)
random_search = RandomizedSearchCV(estimator=model, param_distributions=param_dist, cv=5, n_iter=50), y_train)
print("Best parameters found: ", random_search.best_params_)

Bayesian Optimization

Bayesian optimization is a more advanced technique that leverages probability distributions to search for optimal hyperparameter values.

from skopt import BayesSearchCV
from import Integer, Categorical
param_space = {
'n_estimators': Integer(10, 200),
'max_depth': Categorical([None] + list(range(1, 31))),
'min_samples_split': Integer(2, 10)
bayes_search = BayesSearchCV(estimator=model, search_spaces=param_space, cv=5, n_iter=50), y_train)
print("Best parameters found: ", bayes_search.best_params_)

HyperOpt is another popular library for hyperparameter optimization using a Sequential Model-based Global Optimization (SMBO) algorithm. The following example demonstrates how to use HyperOpt for optimizing the YOLOv5 model’s hyperparameters.

First, you’ll need to install the hyperopt library:

pip install hyperopt

Now, let’s create a custom evaluation function that will train a YOLOv5 model and return its loss.

from hyperopt import fmin, tpe, hp
import subprocess
import re
def train_yolov5(params):
# Set your YOLOv5 training command
command = f"python --img 640 --batch 16 --epochs 100 --data dataset.yaml --weights --cache --nosave --hyp {params['hyp']}"
# Execute the training command
output = subprocess.check_output(command, shell=True).decode("utf-8")

# Extract loss from the training output
loss = float("best_fitness=([\d.]+)", output).group(1))

return loss
space = {
'hyp': {
'lr0': hp.loguniform('lr0', -5, -2),
'lrf': hp.loguniform('lrf', -5, -2),
'momentum': hp.uniform('momentum', 0.6, 0.98),
'weight_decay': hp.loguniform('weight_decay', -9, -4),
'warmup_epochs': hp.quniform('warmup_epochs', 0, 5, 1),
'warmup_momentum': hp.uniform('warmup_momentum', 0.6, 0.98),
'box': hp.uniform('box', 0.02, 0.2),
'cls': hp.uniform('cls', 0.2, 2),
'cls_pw': hp.uniform('cls_pw', 0.5, 2),
'obj': hp.uniform('obj', 0.2, 2),
'obj_pw': hp.uniform('obj_pw', 0.5, 2),
'iou_t': hp.uniform('iou_t', 0.1, 0.7),
'anchor_t': hp.uniform('anchor_t', 2, 5),
'fl_gamma': hp.uniform('fl_gamma', 0, 2),
'hsv_h': hp.uniform('hsv_h', 0, 0.1),
'hsv_s': hp.uniform('hsv_s', 0, 0.9),
'hsv_v': hp.uniform('hsv_v', 0, 0.9),
'degrees': hp.uniform('degrees', 0, 45),
'translate': hp.uniform('translate', 0, 0.9),
'scale': hp.uniform('scale', 0, 0.9),
'shear': hp.uniform('shear', 0, 10),
'perspective': hp.uniform('perspective', 0, 0.001),
'flipud': hp.uniform('flipud', 0, 1),
'fliplr': hp.uniform('fliplr', 0, 1),
'mosaic': hp.uniform('mosaic', 0, 1),
'mixup': hp.uniform('mixup', 0, 1)

Finally, let’s run the optimization using the fmin function from HyperOpt. This function will minimize the loss returned by the train_yolov5 function by searching for the best hyperparameters in the defined search space.

# Set the number of evaluations
max_evals = 50
# Run the optimization
best = fmin(
print("Best hyperparameters found: ", best)

After the optimization is complete, you’ll get the best hyperparameters for your YOLOv5 model. You can then use these hyperparameters to train your final YOLOv5 model and achieve improved performance.

Exporting Models for Prediction

After optimizing your model, it’s time to export it for predictions. We’ll use the popular library joblib to save and load models.

import joblib
# Save the model
joblib.dump(grid_search.best_estimator_, "optimized_model.pkl")
# Load the model
loaded_model = joblib.load("optimized_model.pkl")

Making Predictions with the Exported Model

Once you’ve loaded the optimized model, you can use it to make predictions on new, unseen data.

# Make predictions
predictions = loaded_model.predict(X_test)
# Evaluate the performance
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: ", accuracy)

Exporting a trained model and using it for prediction is an essential step in any machine learning workflow. In this guide, we’ll walk through the process of exporting a PyTorch model and using it for making predictions.

  1. Train a simple PyTorch model

First, let’s create a simple neural network and train it on some dummy data. We’ll use the Iris dataset for this example.

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Load the Iris dataset
iris = load_iris()
X =
y =
# 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)
# Scale the data
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# Convert numpy arrays to PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.long)
y_test = torch.tensor(y_test, dtype=torch.long)
# Define the neural network
class IrisNet(nn.Module):
def __init__(self):
super(IrisNet, self).__init__()
self.fc1 = nn.Linear(4, 16)
self.fc2 = nn.Linear(16, 8)
self.fc3 = nn.Linear(8, 3)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
# Initialize the model, loss function, and optimizer
model = IrisNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Train the model
num_epochs = 100
for epoch in range(num_epochs):
outputs = model(X_train)
loss = criterion(outputs, y_train)
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item()}')

2. Save the trained model

After training the model, we can save it to a file for later use., 'iris_net.pth')

3. Load the saved model

To use the saved model for predictions, we’ll first need to load the model state into a new instance of the neural network.

loaded_model = IrisNet()
loaded_model.eval() # Set the model to evaluation mode

4. Make predictions

Now we can use the loaded model to make predictions on new, unseen data.

with torch.no_grad():
test_outputs = loaded_model(X_test)
_, predicted = torch.max(test_outputs, 1)
accuracy = (predicted == y_test).sum().item() / len(y_test)
print(f'Accuracy: {accuracy * 100}%')

This example demonstrates the entire process of training a simple PyTorch model, saving it to a file, loading the saved model, and making predictions using the loaded model. You can adapt this process to your specific use case and model architecture.

Deployment and Monitoring:

Once your project is complete, you may need to deploy it in a production environment. This involves packaging your code, setting up the necessary infrastructure, and configuring the deployment process. Here are some best practices to consider during deployment:

  • Use continuous integration and continuous deployment (CI/CD) tools such as Jenkins, CircleCI, or GitHub Actions to automate the deployment process.
  • Containerise your application using Docker to ensure consistent and reproducible environments across development and production stages.
  • Use cloud platforms such as AWS, Google Cloud Platform, or Microsoft Azure for scalable and flexible infrastructure.
  • Monitor your deployed models with tools like Prometheus, Grafana, or custom-built monitoring solutions to track performance, usage, and errors.

Deployment and monitoring are important aspects of machine learning and data science projects, as they enable you to put your models into production and ensure their performance over time. In this section, we’ll discuss some popular tools for deploying and monitoring your models, including Streamlit for building web applications and cloud platforms for scalable deployments.

Streamlit: Streamlit is an open-source Python library that makes it easy to create custom web applications for machine learning and data science projects. With just a few lines of code, you can build interactive dashboards and visualize your data. To get started, install Streamlit using pip:

pip install streamlit

Next, create a Python script (e.g., with the following code to build a simple Streamlit application:

import streamlit as st
st.title("My First Streamlit App")
user_input = st.text_input("Enter your name:")
st.write(f"Hello, {user_input}!")

Run the Streamlit app by executing the following command:

streamlit run

This will launch your web application in the browser.


Docker is a containerization platform that allows developers to package applications along with their dependencies into a standardized unit called a container. This ensures that the application will run consistently across different environments. In this guide, we’ll demonstrate how to create a simple Python web app using Streamlit and package it using Docker.

Step 1: Create a Streamlit web app

First, let’s create a simple Streamlit web app. Create a new Python file named and add the following code:

import streamlit as st
st.title("Hello, Streamlit!")
st.write("Welcome to our simple Python web app built using Streamlit!")
st.write("Enter your name below and press 'Submit' to see a personalized message:")
name = st.text_input("Your Name")
if st.button("Submit"):
st.write(f"Hello, {name}! Nice to meet you!")

Step 2: Install Streamlit

If you haven’t already, install Streamlit using pip:

pip install streamlit

Step 3: Run the Streamlit web app

To run the web app, use the following command:

streamlit run

Your app should now be running on http://localhost:8501.

Step 4: Create a Dockerfile

To containerize the web app using Docker, create a new file named Dockerfile in the same directory as your file and add the following content:

# Use an official Python runtime as a parent image
FROM python:3.9-slim
# Set the working directory
# Copy requirements.txt into the container
COPY requirements.txt .
# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host -r requirements.txt
# Make port 8501 available to the world outside this container
# Copy the rest of the application files into the container
COPY . .
# Define environment variable
# Run when the container launches
CMD ["streamlit", "run", ""]

Step 5: Create a requirements.txt file

Create a new file named requirements.txt in the same directory as your file and add the following content:


Step 6: Build the Docker image

Navigate to the directory containing your Dockerfile and run the following command to build the Docker image:

docker build -t streamlit-webapp .

Step 7: Run the Docker container

After building the image, run the Docker container using the following command:

docker run -p 8501:8501 streamlit-webapp

Your Streamlit web app should now be running inside a Docker container and accessible at http://localhost:8501.

With these steps, you’ve successfully created a simple Python web app using Streamlit and containerized it using Docker. This setup allows you to easily deploy and scale your application in different environments.

Deployment on the cloud:

Cloud platforms like AWS, Google Cloud, and Microsoft Azure provide various services to deploy and manage your machine learning models. These platforms offer scalable, cost-effective solutions for production deployments.

For example, to deploy a machine learning model on Google Cloud using AI Platform, follow these steps:

  1. Train and save your model in a format supported by AI Platform, such as TensorFlow SavedModel or Scikit-learn model:
import joblib
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
# Train a logistic regression model
data = load_iris()
X, y =,
model = LogisticRegression(), y)
# Save the model
joblib.dump(model, 'model.joblib')

2. Create a Google Cloud Storage bucket and upload your model:

gsutil mb gs://your-bucket-name
gsutil cp model.joblib gs://your-bucket-name/model.joblib

3. Create an AI Platform model and version:

gcloud ai-platform models create your_model_name
gcloud ai-platform versions create your_version_name \
--model=your_model_name \
--origin=gs://your-bucket-name/ \
--runtime-version=2.6 \
--python-version=3.7 \

4. Send prediction requests to your deployed model:

from googleapiclient import discovery
import numpy as np
# Create the AI Platform API client
api ='ml', 'v1')
# Prepare a sample for prediction
sample = np.array([5.1, 3.5, 1.4, 0.2]).reshape(1, -1).tolist()
# Send the prediction request
response = api.projects().predict(
body={'instances': sample}
# Print the prediction results

Monitoring: Once your models are deployed, it’s important to monitor their performance and usage. Cloud platforms provide monitoring and logging services to help you track your model’s performance, resource usage, and errors. For example, Google Cloud’s AI Platform integrates with Cloud Monitoring and Cloud Logging for real-time monitoring and logging of your deployed models.


In this comprehensive guide, we’ve ventured through the winding path of structuring a data science or machine learning project. From organizing directories and managing dependencies, to deploying and monitoring your models, we’ve covered it all — with a sprinkle of code snippets and a dash of humor.

Remember, a well-structured project is the secret ingredient to success in the kitchen of data science and machine learning. It keeps your code fresh, your collaborators happy, and your models performing at their best. So, go forth, dear reader, and conquer the world of machine learning, one well-organized project at a time.

And if you ever find yourself lost in the maze of code, data, and models, remember that a pinch of humour and a healthy dose of best practices will guide you back to the light. Happy coding!


  1. Jupyter Project. (n.d.). Jupyter Notebook.
  2. Jupyter Project. (n.d.). JupyterLab.
  3. DVC (Data Version Control). (n.d.).
  4. Conda. (n.d.). Conda: Package, dependency, and environment management for any language.
  5. Streamlit. (n.d.). Streamlit — The fastest way to build custom ML tools.
  6. Hyperopt. (n.d.). Distributed Asynchronous Hyperparameter Optimization in Python.
  7. Ultralytics. (n.d.). YOLOv5.
  8. PyTorch. (n.d.). PyTorch: An open-source machine learning library.
  9. pytest. (n.d.). pytest: Simple, rapid testing framework for Python.
  10. Docker. (n.d.). Docker: Empowering App Development for Developers.

If you found this article helpful and insightful, don’t forget to show your support by giving it a clap, or maybe even a standing ovation! Feel free to follow me for more articles that’ll keep you up-to-date on the latest trends and best practices in data science, machine learning, and beyond. Let’s keep learning and growing together!



Siddharth Sah

Sr. Research Engineer. Follow me for articles on AI, Machine Learning, and more.