├── model.pkl ├── screenshots ├── gai-demo.png └── gai-demo-web.png ├── .dockerignore ├── k8s ├── cluster-config.yaml ├── service.yaml └── deployment.yaml ├── Dockerfile ├── salary.csv ├── requirements.txt ├── salary_prediction.py ├── templates └── index.html ├── LICENSE ├── app.py ├── .gitignore └── README.md /model.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PapiHack/gai-demo/HEAD/model.pkl -------------------------------------------------------------------------------- /screenshots/gai-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PapiHack/gai-demo/HEAD/screenshots/gai-demo.png -------------------------------------------------------------------------------- /screenshots/gai-demo-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PapiHack/gai-demo/HEAD/screenshots/gai-demo-web.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | venv 2 | env 3 | __pycache__ 4 | *$py.class 5 | README.md 6 | Dockerfile 7 | k8s 8 | .gitignore 9 | LICENSE -------------------------------------------------------------------------------- /k8s/cluster-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kind.x-k8s.io/v1alpha4 2 | kind: Cluster 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | -------------------------------------------------------------------------------- /k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: galsenai-demo-service 5 | spec: 6 | selector: 7 | model: salary-prediction 8 | type: NodePort 9 | ports: 10 | - port: 5000 11 | targetPort: http 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY requirements.txt ./ 6 | 7 | RUN python -m pip install --upgrade pip && pip install -r requirements.txt 8 | 9 | COPY . . 10 | 11 | EXPOSE 5000 12 | 13 | CMD ["python", "app.py"] -------------------------------------------------------------------------------- /salary.csv: -------------------------------------------------------------------------------- 1 | experience,test_score,interview_score,Salary 2 | ,8,8,50000 3 | ,5,4,22000 4 | three,6,5,30000 5 | five,9,9,55000 6 | six,3,5,13000 7 | ,2,1,9000 8 | ten,8,6,48000 9 | one,1,2,500 10 | fifteen,9,9,60000 11 | thirteen,7.5,7.5,45000 12 | ten,,5,25000 13 | ,1,1,500 14 | one,3,6,25000 15 | four,5,4,36000 16 | six,5,,8000 17 | eleven,9,8,55000 18 | twelve,9,9,58000 19 | ,2,6,12000 20 | three,5,5,20000 21 | five,6,7,35000 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | certifi==2022.12.7 3 | click==8.0.1 4 | colorama==0.4.4 5 | Flask==2.2.5 6 | gunicorn==20.1.0 7 | importlib-metadata==4.6.3 8 | itsdangerous==2.0.1 9 | Jinja2==3.0.1 10 | joblib==1.2.0 11 | MarkupSafe==2.0.1 12 | numpy==1.22.0 13 | pandas==1.3.1 14 | python-dateutil==2.8.2 15 | pytz==2021.1 16 | scikit-learn==0.24.2 17 | scipy==1.10.0 18 | six==1.16.0 19 | sklearn==0.0 20 | threadpoolctl==2.2.0 21 | typing-extensions==3.10.0.0 22 | Werkzeug==2.2.3 23 | wincertstore==0.2 24 | zipp==3.5.0 25 | -------------------------------------------------------------------------------- /k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: galsenai-demo-deployment 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | model: salary-prediction 10 | template: 11 | metadata: 12 | labels: 13 | model: salary-prediction 14 | spec: 15 | containers: 16 | - name: galsenai-salary-prediction 17 | image: papihack/galsenai-salary-prediction 18 | resources: 19 | limits: 20 | memory: "128Mi" 21 | cpu: "500m" 22 | ports: 23 | - containerPort: 5000 24 | name: http 25 | -------------------------------------------------------------------------------- /salary_prediction.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pickle 3 | from sklearn.linear_model import LinearRegression 4 | 5 | data = pd.read_csv('salary.csv') 6 | 7 | data['experience'].fillna(0, inplace=True) 8 | data['test_score'].fillna(data['test_score'].mean(), inplace=True) 9 | data['interview_score'].fillna(data['interview_score'].mean(), inplace=True) 10 | 11 | X = data.iloc[:, :3] 12 | 13 | # Converting the categorical feature to number 14 | def convert_into_int(word): 15 | word_conv = {'one':1, 'two':2, 'three':3, 'four':4, 'five':5, 'six':6, 'seven':7, 16 | 'eight':8, 'nine':9, 'ten':10, 'eleven':11, 'twelve':12, 'thirteen':13, 17 | 'fourteen':14, 'fifteen':15, 'zero':0, 0:0} 18 | return word_conv[word] 19 | 20 | X['experience'] = X['experience'].apply(lambda x: convert_into_int(x)) 21 | 22 | y = data.iloc[:, -1] 23 | 24 | lr = LinearRegression() 25 | lr.fit(X,y) 26 | 27 | # Serving the model 28 | 29 | pickle.dump(lr, open('model.pkl', 'wb')) -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Salary Prediction 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Predict Salary Analysis

16 | 17 |
18 | 19 | 20 | 21 | 22 |
26 |
27 | {{ prediction_text }} 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, PapiHack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from flask import Flask, request, render_template, jsonify 3 | import pickle 4 | 5 | app = Flask(__name__) 6 | model = pickle.load(open('model.pkl', 'rb')) 7 | 8 | @app.route('/') 9 | def Home(): 10 | return render_template('index.html') 11 | 12 | @app.route('/predict', methods=['POST']) 13 | def predict(): 14 | 15 | int_feature = [int(x) for x in request.form.values()] 16 | final_feature = [np.array(int_feature)] 17 | prediction = model.predict(final_feature) 18 | 19 | output = round(prediction[0], 2) 20 | 21 | return render_template('index.html', prediction_text='Enploye salary sould be $ {}'.format(output)) 22 | 23 | @app.route('/api/predict', methods=['POST']) 24 | def api_predict(): 25 | 26 | data = request.get_json(force=True) 27 | 28 | prediction = model.predict([np.array([int(data['experience']), int(data['test_score']), int(data['interview_score'])])]) 29 | 30 | output = round(prediction[0], 2) 31 | 32 | return jsonify({'salary': output, 'currency': '$'}) 33 | 34 | 35 | if __name__== '__main__': 36 | app.run(host="0.0.0.0", debug=True) 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 GalsenAI Demo Project 🚀 2 | 3 | [![python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) 4 | [![docker](https://img.shields.io/badge/docker-3776AB?style=for-the-badge&logo=docker&logoColor=white)](https://img.shields.io/badge/docker-3776AB?style=for-the-badge&logo=docker&logoColor=white) 5 | [![kubernetes](https://img.shields.io/badge/kubernetes-3776AB?style=for-the-badge&logo=kubernetes&logoColor=white)](https://img.shields.io/badge/kubernetes-3776AB?style=for-the-badge&logo=kubernetes&logoColor=white) 6 | [![MIT licensed](https://img.shields.io/badge/license-mit-blue?style=for-the-badge&logo=appveyor)](./LICENSE) 7 | ![Issues](https://img.shields.io/github/issues/PapiHack/gai-demo?style=for-the-badge&logo=appveyor) 8 | ![PR](https://img.shields.io/github/issues-pr/PapiHack/gai-demo?style=for-the-badge&logo=appveyor) 9 | [![Open Source Love png1](https://badges.frapsoft.com/os/v1/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) 10 | 11 | Demo project of my talk for GalsenAI Dakar community on `How to Deploy and Scale AI (ML or DL) Models with Kubernetes`. 12 | 13 | You can find the slides of my talk at . 14 | 15 | ## Description 16 | 17 | This repo contains a `REST API` built with `Flask` that expose a machine learning model that can predict salary based on `years of experience`, `test score` and `interview score`. 18 | 19 | ## Installation 20 | 21 | You have two ways in order to setup and run this project. 22 | 23 | ### Manual Setup 24 | 25 | For manual installation, you need to have `Python` (version 3 is preferable) on your system. Then you can clone this repo and follow the steps below : 26 | 27 | - Create a virtual environment with the command : 28 | 29 | python3 -m venv venv 30 | 31 | - Activate the virtual environment with the command : 32 | 33 | . venv/bin/activate or source venv/bin/activate 34 | 35 | - Install the necessary dependencies with the command : 36 | 37 | pip install -r requirements.txt 38 | 39 | - Start the server with the command : 40 | 41 | python app.py 42 | 43 | - Go to your browser at the following address : 44 | 45 | http://localhost:5000 46 | 47 | - Or make a `POST` request at with the following payload (change values if you want) : 48 | 49 | { 50 | "experience": 1.8, 51 | "test_score": 12, 52 | "interview_score": 10 53 | } 54 | 55 | ### Docker Setup 56 | 57 | If you have `Docker` on your system, you have two options after placing yourself in the directory of this project : 58 | 59 | - First option : 60 | 61 | - Build your image with the name you want like this : 62 | 63 | docker build . -t salary-prediction-service 64 | 65 | - Run it with the following command : 66 | 67 | docker run -d --name salary-prediction-service -p 5000:5000 salary-prediction-service 68 | 69 | - Second option : 70 | 71 | - Run the image that i already deploy on `docker hub` with the following command : 72 | 73 | docker run -d --name salary-prediction-model -p 5000:5000 papihack/galsenai-salary-prediction 74 | 75 | 76 | ### Kubernetes 77 | 78 | For the deployment of this project on `K8S`, i use a tool named [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) in order to create a local kubernetes cluster. You can also use other alternatives like `minikube` if you want it too. 79 | 80 | The manifest files are located in the `k8s` folder. 81 | 82 | - First and foremost, you need to have or create a kubernetes cluster. After you installed `kind` on your system, you can create one with the following command : 83 | 84 | kind create cluster --config k8s/cluster-config.yaml --name my-cluster 85 | 86 | The file `k8s/cluster-config.yaml` will allow you to have a cluster with `1 Master Node (Control Plane)` and `2 Worker Nodes`. 87 | 88 | - Then you can apply the `deployment` and `service` manifest files with the command : 89 | 90 | kubectl apply -f k8s/service.yaml -f k8s/deployment.yaml 91 | 92 | - Finally, since it is a `NodePort` type service, you can try to find the `IP` of one your nodes and the `generated PORT` of the service in order to try to access to the app. 93 | 94 | You can get those infos by running the following commands : 95 | 96 | kubectl get svc -o wide 97 | 98 | kubectl get no -o wide 99 | 100 | The url should look like at something like : `http://node-ip:svc-port` for the web page and `http://node-ip:svc-port/api/predict` for the `API`. 101 | 102 | ## Screenshots 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
API InteractionWeb interface
114 | 115 | ## Contributing 116 | 117 | Feel free to make a PR or report an issue 😃 118 | 119 | Oh, one more thing, please do not forget to put a description when you make your PR 🙂 120 | 121 | ## Author 122 | 123 | - [M.B.C.M](https://itdev.sn) 124 | [![My Twitter Link](https://img.shields.io/twitter/follow/the_it_dev?style=social)](https://twitter.com/the_it_dev) --------------------------------------------------------------------------------