├── app ├── __init__.py ├── california_housing_model.joblib ├── __pycache__ │ ├── server.cpython-311.pyc │ └── __init__.cpython-311.pyc ├── model.py └── server.py ├── .gitignore ├── requirements.txt ├── runserver.py ├── .github └── workflows │ ├── main.yml │ └── GHCR.yml ├── Dockerfile ├── static ├── predict.js └── style.css ├── templates └── index.html └── README.md /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | numpy 3 | pandas 4 | scikit-learn 5 | flask 6 | joblib 7 | -------------------------------------------------------------------------------- /app/california_housing_model.joblib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzioDEVio/MLOps/HEAD/app/california_housing_model.joblib -------------------------------------------------------------------------------- /app/__pycache__/server.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzioDEVio/MLOps/HEAD/app/__pycache__/server.cpython-311.pyc -------------------------------------------------------------------------------- /app/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzioDEVio/MLOps/HEAD/app/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /runserver.py: -------------------------------------------------------------------------------- 1 | 2 | from waitress import serve 3 | from app.server import app 4 | 5 | if __name__ == '__main__': 6 | serve(app, host='0.0.0.0', port=5000) 7 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | 2 | name: MLOps with Python application.. 3 | 4 | on: 5 | #push: 6 | branches: [ main ] 7 | #pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 3.8 18 | uses: actions/setup-python@v1 19 | with: 20 | python-version: 3.8 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install -r requirements.txt 25 | - name: Run tests 26 | run: | 27 | python -m unittest discover 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.11-slim 3 | 4 | # Set the working directory to /app 5 | WORKDIR /app 6 | 7 | # Copy the current directory contents into the container at /app 8 | COPY . . 9 | 10 | # Install any needed packages specified in requirements.txt 11 | RUN pip install --no-cache-dir -r requirements.txt gunicorn 12 | 13 | # Make port 5000 available to the world outside this container 14 | EXPOSE 5000 15 | 16 | # Define environment variable 17 | ENV NAME World 18 | 19 | # Run app.py when the container launches 20 | CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app.server:app"] 21 | 22 | # Create a non-root user and switch to it 23 | RUN adduser --disabled-password --gecos "" appuser 24 | USER appuser 25 | 26 | # Health check 27 | HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ 28 | CMD curl -f http://localhost:5000/ || exit 1 29 | -------------------------------------------------------------------------------- /app/model.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | from sklearn.datasets import fetch_california_housing 4 | from sklearn.model_selection import train_test_split 5 | from sklearn.linear_model import LinearRegression 6 | from sklearn.metrics import mean_squared_error 7 | from joblib import dump 8 | 9 | def train_model(): 10 | # Load data 11 | data = fetch_california_housing() 12 | df = pd.DataFrame(data.data, columns=data.feature_names) 13 | df['TARGET'] = data.target 14 | 15 | # Split data 16 | X = df.drop('TARGET', axis=1) 17 | y = df['TARGET'] 18 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 19 | 20 | # Train model 21 | model = LinearRegression() 22 | model.fit(X_train, y_train) 23 | 24 | # Save the model 25 | dump(model, 'app/california_housing_model.joblib') 26 | 27 | # Predict & Evaluate 28 | predictions = model.predict(X_test) 29 | mse = mean_squared_error(y_test, predictions) 30 | print(f"Model MSE: {mse}") 31 | 32 | if __name__ == "__main__": 33 | train_model() 34 | -------------------------------------------------------------------------------- /static/predict.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEventListener("DOMContentLoaded", function() { 3 | var form = document.getElementById("predictionForm"); 4 | form.addEventListener("submit", function(event) { 5 | event.preventDefault(); // Prevent the default form submission 6 | var formData = new FormData(form); 7 | var requestData = {}; 8 | formData.forEach(function(value, key) { 9 | requestData[key] = value; 10 | }); 11 | 12 | fetch('/predict', { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | body: JSON.stringify(requestData), 18 | }) 19 | .then(response => response.json()) 20 | .then(data => { 21 | document.getElementById('predictionResult').textContent = 'Estimated Price: $' + data.prediction[0].toFixed(2); 22 | }) 23 | .catch((error) => { 24 | console.error('Error:', error); 25 | document.getElementById('predictionResult').textContent = 'Error predicting price.'; 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /app/server.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import Flask, request, jsonify, render_template 3 | import numpy as np 4 | import pandas as pd # Ensure pandas is imported 5 | from joblib import load 6 | import os 7 | 8 | # Define the root directory path 9 | ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 10 | 11 | # Initialize the Flask application 12 | app = Flask(__name__, root_path=ROOT_PATH) 13 | model = load(os.path.join(ROOT_PATH, 'app', 'california_housing_model.joblib')) 14 | 15 | @app.route('/') 16 | def index(): 17 | # Print the path to the templates directory for debugging 18 | print(f"Templates directory: {app.template_folder}") 19 | return render_template('index.html') 20 | 21 | @app.route('/predict', methods=['POST']) 22 | def predict(): 23 | try: 24 | data = request.get_json() # Get data as JSON 25 | features = [float(data[key]) for key in sorted(data)] # Convert and sort the data 26 | feature_names = ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude'] # Ensure these match your training data 27 | df = pd.DataFrame([features], columns=feature_names) 28 | prediction = model.predict(df) 29 | return jsonify({'prediction': prediction.tolist()}) 30 | except Exception as e: 31 | print("Error:", str(e)) # Printing the error might give more insights in debugging 32 | return jsonify({'error': str(e)}), 400 33 | 34 | if __name__ == '__main__': 35 | app.run(debug=True) 36 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | 2 | body, html { 3 | height: 100%; 4 | margin: 0; 5 | font-family: Arial, sans-serif; 6 | background-color: #eaeaea; 7 | } 8 | 9 | .wrapper { 10 | min-height: 100%; 11 | position: relative; 12 | padding-bottom: 50px; /* Footer height */ 13 | } 14 | 15 | header { 16 | background-color: #004b97; 17 | color: #fff; 18 | padding: 10px 0; 19 | text-align: center; 20 | } 21 | 22 | header h1 { 23 | margin: 0; 24 | } 25 | 26 | .form-container { 27 | width: 60%; 28 | margin: 30px auto; 29 | background: #fff; 30 | padding: 20px; 31 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 32 | border-radius: 8px; 33 | } 34 | 35 | .input-group { 36 | margin-bottom: 10px; 37 | } 38 | 39 | .input-group label { 40 | display: block; 41 | margin-bottom: 5px; 42 | } 43 | 44 | .input-group input { 45 | width: 100%; 46 | padding: 8px; 47 | margin-bottom: 10px; 48 | border: 1px solid #ccc; 49 | border-radius: 4px; 50 | } 51 | 52 | .submit-btn button { 53 | width: 100%; 54 | padding: 10px; 55 | border: none; 56 | background-color: #5cb85c; 57 | color: white; 58 | font-size: 16px; 59 | cursor: pointer; 60 | border-radius: 4px; 61 | transition: background-color 0.3s; 62 | } 63 | 64 | .submit-btn button:hover { 65 | background-color: #4cae4c; 66 | } 67 | 68 | footer { 69 | position: absolute; 70 | width: 100%; 71 | height: 50px; /* Footer height */ 72 | background-color: #333; 73 | color: #fff; 74 | text-align: center; 75 | bottom: 0; 76 | left: 0; 77 | } 78 | 79 | footer p { 80 | margin: 0; 81 | padding: 15px; 82 | } 83 | 84 | /* Add this to your existing style.css content */ 85 | .prediction-result { 86 | margin-top: 20px; 87 | padding: 10px; 88 | background-color: #f8f9fa; 89 | border: 1px solid #ddd; 90 | text-align: center; 91 | font-size: 1.2em; 92 | color: #333; 93 | } 94 | -------------------------------------------------------------------------------- /.github/workflows/GHCR.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker image to Github Container Registry 2 | 3 | on: 4 | #push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ghcr-build-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build-and-push: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | image_tag: ${{ steps.set_output.outputs.image_tag }} # This will set the output for the job 17 | steps: 18 | - name: Check out the code 19 | uses: actions/checkout@v2 20 | 21 | - name: Log in to GitHub Container Registry 22 | uses: docker/login-action@v1 23 | with: 24 | registry: ghcr.io 25 | username: ${{ secrets.DOCKER_USERNAME }} 26 | password: ${{ secrets.DOCKER_PASSWORD }} 27 | 28 | - name: Set lowercase repository owner and set image tag 29 | id: set_output 30 | run: | 31 | REPO_OWNER=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}') 32 | IMAGE_TAG="v1.2.1" 33 | echo "REPO_OWNER=$REPO_OWNER" >> $GITHUB_ENV 34 | echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV 35 | echo "::set-output name=image_tag::$IMAGE_TAG" # This sets the output for image tag 36 | 37 | - name: Build Docker image with specific tag 38 | run: | 39 | echo "Building Image: ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ env.IMAGE_TAG }}" 40 | docker build . -t ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ env.IMAGE_TAG }} 41 | 42 | - name: Push Docker image to GitHub Container Registry 43 | run: | 44 | echo "Pushing Image: ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ env.IMAGE_TAG }}" 45 | docker push ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ env.IMAGE_TAG }} 46 | 47 | - name: Tag and push Docker image with commit SHA 48 | run: | 49 | echo "Tagging and Pushing SHA: ${{ github.sha }}" 50 | docker tag ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ env.IMAGE_TAG }} ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ github.sha }} 51 | docker push ghcr.io/${{ env.REPO_OWNER }}/ghcr-realestateapp:${{ github.sha }} 52 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Real Estate Price Predictor 7 | 8 | 9 | 10 | 11 |
12 |
13 |

Home Price Estimator

14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 | 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MLOps with Python application](https://github.com/EzioDEVio/MLOps/actions/workflows/main.yml/badge.svg)](https://github.com/EzioDEVio/MLOps/actions/workflows/main.yml) [![Build and Push Docker image to Github Container Registry](https://github.com/EzioDEVio/MLOps/actions/workflows/GHCR.yml/badge.svg)](https://github.com/EzioDEVio/MLOps/actions/workflows/GHCR.yml) 2 | ![Stars](https://img.shields.io/github/stars/EzioDEVio/MLOps?style=social) 3 | ![MIT License](https://img.shields.io/github/license/EzioDEVio/MLOps) 4 | ![Repo Size](https://img.shields.io/github/repo-size/EzioDEVio/MLOps) 5 | ![Last Commit](https://img.shields.io/github/last-commit/EzioDEVio/MLOps?color=blue) 6 | 7 | # MLOps Application for Real Estate Price Prediction 8 | 9 | This project demonstrates the application of Machine Learning Operations (MLOps) principles to a real estate price prediction model. It includes setting up a Flask application to serve predictions from a trained model and deploying this application using Docker and integrating it into a CI/CD workflow. 10 | 11 | ## Project Structure 12 | 13 | - `app/`: Contains the Flask application files. 14 | - `server.py`: The Flask server file with API endpoints. 15 | - `models/`: Contains the trained model file. 16 | - `california_housing_model.joblib`: Pre-trained scikit-learn model. 17 | - `templates/`: HTML files for the application frontend. 18 | - `static/`: CSS and JS files for the frontend. 19 | - `Dockerfile`: Contains all the commands to assemble the app Docker image. 20 | - `requirements.txt`: List of packages required for the application. 21 | 22 | ## Setup and Installation 23 | 24 | ### Prerequisites 25 | 26 | - Python 3.8+ 27 | - pip 28 | - virtualenv (optional) 29 | 30 | ### Local Setup 31 | 32 | 1. **Clone the Repository:** 33 | 34 | ```bash 35 | git clone https://github.com/EzioDEVio/MLOps.git 36 | cd MLOps 37 | ``` 38 | 39 | 2. **Create and Activate a Virtual Environment (optional):** 40 | 41 | Windows: 42 | ```bash 43 | python -m venv venv 44 | venv\Scripts\activate 45 | ``` 46 | 47 | macOS/Linux: 48 | ```bash 49 | python3 -m venv venv 50 | source venv/bin/activate 51 | ``` 52 | 53 | 3. **Install Dependencies:** 54 | 55 | ```bash 56 | pip install -r requirements.txt 57 | ``` 58 | 59 | 4. **Run the Application:** 60 | 61 | ```bash 62 | python app/server.py 63 | ``` 64 | 65 | Visit `http://127.0.0.1:5000` in your web browser to view the app. 66 | 67 | ## Docker Deployment 68 | 69 | 1. **Build the Docker Image:** 70 | 71 | ```bash 72 | docker build -t mlops-app . 73 | ``` 74 | 75 | 2. **Run the Docker Container:** 76 | 77 | ```bash 78 | docker run -p 5000:5000 mlops-app 79 | ``` 80 | 81 | The application should now be accessible at `http://localhost:5000`. 82 | 83 | ## CI/CD Integration 84 | 85 | This project uses GitHub Actions for Continuous Integration and Continuous Deployment (CI/CD). 86 | 87 | ### Workflow 88 | 89 | 1. **Continuous Integration:** 90 | 91 | - Build the Docker image. 92 | - Run tests (add your tests in the workflow). 93 | 94 | 2. **Continuous Deployment:** 95 | 96 | - Push the Docker image to a registry (e.g., Docker Hub). 97 | - Deploy the image to a cloud service (e.g., AWS, Azure). 98 | 99 | ### Setup GitHub Actions 100 | 101 | 1. **Create a `.github/workflows` directory in your repository.** 102 | 103 | 2. **Add a workflow file (e.g., `ci-cd.yml`):** 104 | 105 | ```yaml 106 | name: CI/CD Pipeline 107 | 108 | on: 109 | push: 110 | branches: [ main ] 111 | pull_request: 112 | branches: [ main ] 113 | 114 | jobs: 115 | build: 116 | runs-on: ubuntu-latest 117 | steps: 118 | - uses: actions/checkout@v2 119 | - name: Set up Python 120 | uses: actions/setup-python@v2 121 | with: 122 | python-version: '3.8' 123 | - name: Install dependencies 124 | run: | 125 | python -m pip install --upgrade pip 126 | pip install -r requirements.txt 127 | - name: Build Docker image 128 | run: docker build -t mlops-app . 129 | # Add additional steps for testing and deployment 130 | ``` 131 | 132 | ## Contributing 133 | 134 | Contributions to this project are welcome! Please fork the repository and submit a pull request with your proposed changes. 135 | 136 | ## License 137 | 138 | MIT. 139 | 140 | 141 | . 142 | --------------------------------------------------------------------------------