Professional Python project: deploying and serving machine learning models.
A machine learning model learns patterns from data. But, once trained, the model might just sit on a single computer.
Serving a model means wrapping it in a small web service so anyone can send it a data request over the internet and get a prediction back.
The example project trains a model that identifies penguin species from physical measurements, then deploys it so you can ask it what species a set of measurements belongs to.
My custom project serves two models for medical insurance charges from a single request. Sending age, BMI, number of children, and smoking status returns a cost tier, the probability of each of the four tiers, and a predicted dollar amount. The tier says which bracket a person falls in and how strongly the forest agreed on it; the dollar amount says how much, with nothing attached about agreement. Returning both shows what each one cannot tell you.
This project focuses on learning to deploy a trained model so others can use it.
We learn to:
- save and load a trained model
- wrap a model in a simple API or script
- validate inputs and handle errors gracefully
- think about drift, versioning, and monitoring
My custom project applies this to medical insurance charges. Two models are trained on the same 1,338 rows and served from one endpoint: a random forest that predicts a cost tier and a linear regression that predicts a dollar amount. Both are saved as pipelines, so the engineered features are built inside the artifact rather than by the server.
The classifier reaches 0.8582 accuracy on 268 held-out rows. The regressor reaches R-squared 0.8609 with RMSE 4,607.9 dollars using six terms, close to the forty-four term polynomial I built in Module 4. Both models miss the same group: non-smokers whose charges are far higher than age, BMI, children, and smoking can explain.
See docs/index.md for the full write-up.
This project needs additional dependencies
"fastapi[standard]", # for serving - a web framework for building APIs
"uvicorn", # for serving - ASGI server for FastAPI
"joblib", # for model serialization (saving and loading models)A .joblib file is a serialized Python object that holds
the trained model frozen to disk.
The package joblib converts the in-memory RandomForestClassifier
(with all its learned decision trees and their weights)
into bytes and writes them to a file.
Loading it back gives us the same trained model without having to retrain.
This is how serving a trained model works: train once, save once, load once at startup, then predict on every incoming request.
Keep the example notebook as it is. Either copy it or use it to build a new notebook that ends in _yourname. See docs/your-files.md for more.
Links:
- ml_06_serve_model.ipynb - the example
- ml_06_serve_model_gracecode42_modification.ipynb - Phase 4 modification
- ml_06_serve_model_gracecode42_project.ipynb - Phase 5 custom project, fully executed
You'll work with these areas:
- data/raw - raw data for exploration (only if you add a dataset)
- docs/ - project narrative and documentation
- src/mlstudio/ - the app is an example; run only (no need to modify)
- notebooks/ - interactive analysis
- pyproject.toml - update authorship & links
- zensical.toml - update authorship & links
Show command reference
After you get a copy of this repo in your own GitHub account,
open a machine terminal in your Repos folder:
# Replace username with YOUR GitHub username.
git clone https://github.com/gracecode42/ml-06-serving
cd ml-06-serving
code .These are listed for convenience. For best results, follow the detailed instructions in pro-analytics-02 guide.
uv self update
uv python pin 3.14
uv lock --upgrade
uv sync --extra dev --extra docs --upgrade
uvx pre-commit install
uvx pre-commit autoupdate
git add -A
uvx pre-commit run --all-files
# repeat if changes were made
uvx pre-commit run --all-files
# run the example module to verify the environment (.venv/)
uv run python -m mlstudio.app_case
# TASK 1: train the example model and save it to artifacts/model.joblib.
uv run python -m mlstudio.model_builder_case
# CUSTOM: train both custom models and save them to artifacts/
# Writes model_gracecode42_classifier.joblib and model_gracecode42_regressor.joblib
uv run python -m mlstudio.model_builder_gracecode42_project
# run common chores
uv run ruff format .
uv run ruff check . --fix
uv run python -m pyright
uv run python -m pytest
uv run python -m zensical build
# save progress
git add -A
git commit -m "update"
git push -u origin mainOpen a second terminal. Right-click to rename this terminal "server".
Run:
# Task 2. Start the example server
uv run fastapi dev src/mlstudio/serve_case.py
# CUSTOM: start the insurance server instead
# Returns a cost tier with probabilities and a dollar estimate from one request
uv run fastapi dev src/mlstudio/serve_gracecode42_project.pyKeep this terminal open. You should see the following which means it is ready to receive requests:
FastAPI Starting development server π
Searching for package file structure from directories with __init__.py files
| INFO | M06 | === RUN START ===
| INFO | M06 | project=M06
| INFO | M06 | repo_dir=ml-06-serving
| INFO | M06 | python=3.14.0
| INFO | M06 | os=Windows 11
| INFO | M06 | shell=powershell
| INFO | M06 | cwd=.
| INFO | M06 | github_actions=False
| INFO | M06 | Loading model from: artifacts\model.joblib
| INFO | M06 | Model loaded successfully
Importing from C:\Repos\ml\ml-06-serving\src
module π mlstudio
βββ π __init__.py
βββ π serve_case.py
code Importing the FastAPI app object from the module with the following code:
from mlstudio.serve_case import app
app Using import string: mlstudio.serve_case:app
server Server started at http://127.0.0.1:8000
server Documentation at http://127.0.0.1:8000/docs
tip Running in development mode, for production use: fastapi run
Logs:
INFO Will watch for changes in these directories: ['C:\\Repos\\ml\\ml-06-serving']
INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO Started reloader process [31516] using WatchFiles
| INFO | M06 | === RUN START ===
| INFO | M06 | project=M06
| INFO | M06 | repo_dir=ml-06-serving
| INFO | M06 | python=3.14.0
| INFO | M06 | os=Windows 11
| INFO | M06 | shell=powershell
| INFO | M06 | cwd=.
| INFO | M06 | github_actions=False
| INFO | M06 | Loading model from: artifacts\model.joblib
| INFO | M06 | Model loaded successfully
INFO Started server process [10012]
INFO Waiting for application startup.
INFO Application startup complete.
Open a third terminal. Right-click and rename it "client".
Use this terminal to send a request to the server.
We are making a request to the "/predict" endpoint.
Provide information about a penguin and ask for the predicted species.
Line continuation characters for long commands are different by operating system.
- PowerShell uses a backtick.
- Bash and zsh use a back slash
The curl command means "check url".
- X defines the type of request
- H provides the requested response format (json data)
- d provides a json object (a penguin where we want to get the species)
# Task 3. Send a request to the server
curl -X POST http://127.0.0.1:8000/predict `
-H "Content-Type: application/json" `
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'
# CUSTOM: send a request to the insurance server
# Returns the cost tier, the probability of each tier, and a dollar estimate
curl -X POST http://127.0.0.1:8000/predict `
-H "Content-Type: application/json" `
-d '{"age": 45, "bmi": 33.0, "children": 2, "smoker": "yes"}'# Task 3. Send a request to the server
curl -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" \
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'Should return the predicted result as JSON data:
{ "prediction": "Adelie" }Try sending some slightly different data - does it change the prediction? Study the data. Try to create a request that will answer with each of three different species (Adelie, Chinstrap, Gentoo)
Render hosts your ML model for free. It is easy to set up, but they require a credit card (even for the free options). The machines sleep so it can take a minute to wake up and answer. See the docs/ for more.
Customize the request to see what species is predicted:
# PowerShell
curl -X POST https://ml-penguin-predictor.onrender.com/predict `
-H "Content-Type: application/json" `
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'
# macOS / Linux
curl -X POST https://ml-penguin-predictor.onrender.com/predict \
-H "Content-Type: application/json" \
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'HuggingFace also hosts your ML model for free. It is a bit harder to set up (they use their own repo and we upload files via the browser). No credit card is required. See the docs/ for more.
Customize the request to see what species is predicted:
# PowerShell
curl -X POST https://denisecase-ml-penguin-predictor.hf.space/predict `
-H "Content-Type: application/json" `
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'
# macOS / Linux
curl -X POST https://denisecase-ml-penguin-predictor.hf.space/predict \
-H "Content-Type: application/json" \
-d '{"bill_length_mm": 39.1, "bill_depth_mm": 18.7, "flipper_length_mm": 181, "body_mass_g": 3750}'| Model | Metric | Test result |
|---|---|---|
| Cost tier classifier | Accuracy | 0.8582 |
| Cost tier classifier | Weighted F1 | 0.8556 |
| Cost tier classifier | Recall, very high tier | 0.7164 |
| Charges regressor | R-squared | 0.8609 |
| Charges regressor | RMSE | $4,607.90 |
Plain linear regression split its errors into two flat bands: smokers under BMI 30 at a median of negative 9,716 dollars, smokers at BMI 30 or above at positive 7,587. One smoker coefficient had to cover both groups. Flat bands mean the premium jumps at a threshold rather than rising with BMI, so I added a step term rather than raising the polynomial degree. RMSE fell from 6,198 to 4,608 dollars.
The bands are gone in the second plot. What remains is a group of non-smokers predicted in the low thousands whose charges run ten to twenty thousand dollars higher, with almost nothing below the line. The classifier misses the same people, placing eight top-tier cases in the lowest tier.
Additional project instructions, terms, and notes:

