├── .circleci └── config.yml ├── .dvc ├── .gitignore └── config ├── .dvcignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── artifacts ├── .gitignore └── models │ ├── .gitignore │ └── .gitkeep ├── assets ├── DagsHubPipeline.jpg ├── SystemDesign.jpg ├── activationMap.jpg ├── cicd.jpg └── sample_images.jpg ├── configs └── config.yaml ├── docker-compose.yaml ├── dvc.lock ├── dvc.yaml ├── model_serving ├── README.md ├── app.yaml ├── requirements.txt ├── setup.py └── src │ ├── __init__.py │ ├── configs │ ├── .gitkeep │ └── config.yaml │ ├── images │ ├── .gitkeep │ ├── input.jpg │ └── out.jpg │ ├── main.py │ ├── params.yaml │ ├── prediction_service │ ├── .gitkeep │ ├── __init__.py │ └── services.py │ ├── production_model │ ├── .gitkeep │ └── model.h5 │ ├── schemas │ ├── .gitkeep │ ├── __init__.py │ └── schema.py │ └── utils │ ├── .gitkeep │ ├── __init__.py │ └── common.py ├── params.yaml ├── raw.py ├── requirements.txt ├── scripts ├── sanity_check_alert.sh ├── serving_setup.sh └── training.sh ├── setup.py ├── src ├── .gitkeep ├── README.md ├── __init__.py ├── alerts │ ├── __init__.py │ ├── mail.py │ └── send_alert.py ├── cloud_sync │ ├── __init__.py │ └── object_storage.py ├── config │ ├── .gitignore │ ├── .gitkeep │ ├── __init__.py │ └── cred.txt ├── ml │ ├── .gitkeep │ ├── __init__.py │ └── model.py ├── stage_01_get_data.py ├── stage_02_prepare_model.py ├── stage_03_train_evaluate.py ├── stage_04_model_blessing.py └── utils │ ├── .gitkeep │ ├── __init__.py │ ├── app_logging.py │ ├── common.py │ ├── download_blessed_model.py │ ├── gcp_bucket.py │ └── mlflow_ops.py └── template.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | docker-publisher: 5 | environment: 6 | IMAGE_NAME_DOCKER_HUB: hassi34/brain-tumor-classifier:latest 7 | IMAGE_NAME_ARTIFACT_REGISTRY: braintumor/brain-tumor:latest 8 | GCLOUD_APP_SERVICE : default 9 | docker: 10 | - image: google/cloud-sdk 11 | resource_class: medium 12 | 13 | jobs: 14 | continuous-integration: 15 | docker: 16 | - image: cimg/base:stable 17 | resource_class: medium 18 | steps: 19 | - checkout 20 | - run: 21 | name: install-dependencies 22 | command: | 23 | sudo apt install software-properties-common 24 | sudo apt update 25 | sudo add-apt-repository ppa:deadsnakes/ppa 26 | sudo apt install python3.10 27 | sudo apt install python3-pip 28 | pip3 install flake8 29 | - run: 30 | name: lint-with-flake8 31 | command: | 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 34 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 35 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 36 | 37 | continuous-training: 38 | docker: 39 | - image: google/cloud-sdk 40 | resource_class: large 41 | steps: 42 | - checkout 43 | - run: 44 | name: Model Training 45 | command: | 46 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> TRAINING ENVIRONMENT SETUP >>>>>>>>>>>>>>>>>>" 47 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Install Miniconda >>>>>>>>>>>>>>>>>>" 48 | apt update 49 | apt install wget 50 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ 51 | && mkdir /root/.conda \ 52 | && bash Miniconda3-latest-Linux-x86_64.sh -b \ 53 | && rm -f Miniconda3-latest-Linux-x86_64.sh 54 | export PATH="/root/miniconda3/bin:${PATH}" 55 | echo "Running $(conda --version)" 56 | conda init bash 57 | . /root/.bashrc 58 | conda update -n base -c defaults conda -y 59 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Create Environment >>>>>>>>>>>>>>>>>>" 60 | conda create -n myenv python=3.10 -y 61 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Activate Environment >>>>>>>>>>>>>>>>>>" 62 | conda activate myenv 63 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Install Requirements >>>>>>>>>>>>>>>>>>" 64 | pip install -r requirements.txt 65 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Authenticate GCP >>>>>>>>>>>>>>>>>>" 66 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 67 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Project Folder Structure >>>>>>>>>>>>>>>>>>" 68 | python template.py 69 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Download data from Source >>>>>>>>>>>>>>>>>>" 70 | python src/stage_01_get_data.py --config=configs/config.yaml 71 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Prepare Model >>>>>>>>>>>>>>>>>>" 72 | python src/stage_02_prepare_model.py --config=configs/config.yaml --params=params.yaml 73 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Model Training and Evaluation >>>>>>>>>>>>>>>>>>" 74 | python src/stage_03_train_evaluate.py --config=configs/config.yaml --params=params.yaml 75 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Model blessing >>>>>>>>>>>>>>>>>>" 76 | python src/stage_04_model_blessing.py --config=configs/config.yaml --params=params.yaml 77 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> TRAINING COMPLETED >>>>>>>>>>>>>>>>>>" 78 | no_output_timeout: 1h 79 | 80 | continuous-building: 81 | executor: docker-publisher 82 | steps: 83 | - checkout 84 | - setup_remote_docker 85 | - run: 86 | name: Install dependencies 87 | command: | 88 | pip install -r requirements.txt 89 | - run: 90 | name: Build Docker image 91 | command: | 92 | chmod +x ./scripts/serving_setup.sh 93 | ./scripts/serving_setup.sh 94 | docker build -t $IMAGE_NAME_DOCKER_HUB . 95 | - run: 96 | name: Archive Docker image 97 | command: docker save -o image.tar $IMAGE_NAME_DOCKER_HUB 98 | - persist_to_workspace: 99 | root: . 100 | paths: 101 | - ./image.tar 102 | 103 | continuous-delivery: 104 | executor: docker-publisher 105 | steps: 106 | - attach_workspace: 107 | at: /tmp/workspace 108 | # - checkout 109 | - setup_remote_docker 110 | # - run: 111 | # name: Install dependencies 112 | # command: | 113 | # pip install cryptography python-dotenv 114 | - run: 115 | name: Load archived Docker image 116 | command: docker load -i /tmp/workspace/image.tar 117 | - run: 118 | name: Publish Docker Image to Docker Hub 119 | command: | 120 | echo "$DOCKERHUB_ACCESS_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin 121 | docker push $IMAGE_NAME_DOCKER_HUB 122 | - run: 123 | name: Docker Login GCP 124 | command: gcloud auth configure-docker us-east1-docker.pkg.dev 125 | - run: 126 | name: Publish Docker Image to GCP Artifact Registry 127 | command: | 128 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 129 | docker tag $IMAGE_NAME_DOCKER_HUB us-east1-docker.pkg.dev/mlops-378116/$IMAGE_NAME_ARTIFACT_REGISTRY 130 | gcloud docker -- push us-east1-docker.pkg.dev/mlops-378116/$IMAGE_NAME_ARTIFACT_REGISTRY 131 | 132 | continuous-deployment: 133 | executor: docker-publisher 134 | steps: 135 | - checkout 136 | - run: 137 | name: Deploy Docker Image to Google Compute Engine 138 | command: | 139 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 140 | cp ./model_serving/app.yaml ./app.yaml 141 | gcloud config set project $CLOUDSDK_CORE_PROJECT 142 | gcloud app deploy --image-url=us-east1-docker.pkg.dev/mlops-378116/$IMAGE_NAME_ARTIFACT_REGISTRY --no-promote 143 | - run: 144 | name: Sanity check alert 145 | command: | 146 | chmod +x ./scripts/sanity_check_alert.sh 147 | ./scripts/sanity_check_alert.sh 148 | 149 | promote-to-production: 150 | executor: docker-publisher 151 | steps: 152 | - run: 153 | name: Set up GCloud 154 | command: | 155 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 156 | gcloud config set project $CLOUDSDK_CORE_PROJECT 157 | - run: 158 | name: Migrate traffic 159 | command: gcloud app services set-traffic ${GCLOUD_APP_SERVICE} --splits=$(gcloud app versions list --sort-by="~last_deployed_time" --limit=1 --format="value(id)")=1 160 | - run: 161 | name: Stop previous version 162 | command: gcloud app versions stop --service=${GCLOUD_APP_SERVICE} $(gcloud app versions list --filter="traffic_split=0" --sort-by="~last_deployed_time" --limit=1 --format="value(id)") 163 | 164 | workflows: 165 | Development: 166 | jobs: 167 | - continuous-integration 168 | 169 | - continuous-training: 170 | requires: 171 | - continuous-integration 172 | filters: 173 | branches: 174 | only: 175 | - dev 176 | 177 | 178 | Staging: 179 | jobs: 180 | - continuous-integration 181 | 182 | - continuous-training: 183 | requires: 184 | - continuous-integration 185 | filters: 186 | branches: 187 | only: 188 | - staging 189 | 190 | - continuous-building: 191 | requires: 192 | - continuous-training 193 | - continuous-delivery: 194 | requires: 195 | - continuous-building 196 | - continuous-deployment: 197 | requires: 198 | - continuous-delivery 199 | - sanity-check: 200 | type: approval 201 | requires: 202 | - continuous-deployment 203 | 204 | Production: 205 | jobs: 206 | - continuous-integration 207 | 208 | - continuous-training: 209 | requires: 210 | - continuous-integration 211 | - continuous-building: 212 | requires: 213 | - continuous-training 214 | - continuous-delivery: 215 | requires: 216 | - continuous-building 217 | - continuous-deployment: 218 | requires: 219 | - continuous-delivery 220 | - sanity-check: 221 | type: approval 222 | requires: 223 | - continuous-deployment 224 | - promote-to-production: 225 | requires: 226 | - continuous-deployment 227 | - sanity-check 228 | 229 | filters: 230 | branches: 231 | only: 232 | - main 233 | 234 | # Scheduled Production Pipeline: 235 | # triggers: 236 | # - schedule: 237 | # cron: "5 12 * * *" 238 | # filters: 239 | # branches: 240 | # only: 241 | # - main 242 | # jobs: 243 | # - continuous-integration 244 | 245 | # - continuous-training: 246 | # requires: 247 | # - continuous-integration 248 | # - continuous-building: 249 | # requires: 250 | # - continuous-training 251 | # - continuous-delivery: 252 | # requires: 253 | # - continuous-building 254 | # - continuous-deployment: 255 | # requires: 256 | # - continuous-delivery 257 | # - sanity-check: 258 | # type: approval 259 | # requires: 260 | # - continuous-deployment 261 | # - promote-to-production: 262 | # requires: 263 | # - continuous-deployment 264 | # - sanity-check 265 | -------------------------------------------------------------------------------- /.dvc/.gitignore: -------------------------------------------------------------------------------- 1 | /config.local 2 | /tmp 3 | /cache 4 | -------------------------------------------------------------------------------- /.dvc/config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/.dvc/config -------------------------------------------------------------------------------- /.dvcignore: -------------------------------------------------------------------------------- 1 | # Add patterns of files dvc should ignore, which could improve 2 | # the performance. Learn more at 3 | # https://dvc.org/doc/user-guide/dvcignore 4 | -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # if using pycharm 132 | .idea 133 | 134 | # if using VScode 135 | .vscode 136 | 137 | # add secret keys or API keys here 138 | configs/secrets.yaml 139 | 140 | # add your env folder here if its there 141 | dataset/ 142 | logs/ 143 | credentials.json 144 | 145 | 146 | 147 | /req.csv 148 | /dataset 149 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | LABEL maintainer="hasanain@aicaliber.com" 4 | 5 | EXPOSE 80 443 8080 6 | 7 | WORKDIR /model_serving 8 | 9 | COPY ./model_serving/ . 10 | 11 | RUN pip install --no-cache-dir --upgrade -r requirements.txt 12 | 13 | CMD ["python", "src/main.py"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hasanain Mehmood 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Brain Tumor Classification

4 | 5 |

6 |

7 | 8 | Typing SVG 9 | 10 |

11 |

12 | 13 | Typing SVG 14 | 15 |

16 | 17 |

18 | 19 | License 20 | 21 | 22 | Build 23 | 24 | 25 | Last Commit 26 | 27 | 28 | Code Size 29 | 30 | 31 | Repo Size 32 | 33 | 34 | License 35 | 36 | 37 | Issue Tracking 38 | 39 | 40 | Open Issues 41 | 42 |

43 | 44 | Following are the main contents to follow, you can jump to any section: 45 | 46 | > - [Introduction](#project-intro)
47 | > - [Project Notebooks](https://github.com/Hassi34/brain-tumor-classification/tree/notebooks) 48 | > - [Train Model Locally](#train-locally)
49 | > - [Dataset details](#dataset-details)
50 | > - [Results (Performance Metrics)](#results-)
51 | > - [Tech Stack](#tech-stack)
52 | > - [Infrastructure](#infra-)
53 | > - [DagsHub Data Pipeline](https://dagshub.com/hassi34/brain-tumor-classification) 54 | > - [Run Locally](#run-local)
55 | > - [Environment Setup](#env-setup)
56 | > - [Environment Variables](#env-vars)
57 | > - [Run Pipeline](#run-pipeline)
58 | > - [REST API with Docker](rest-api)
59 | > - [Pull Image from Docker Hub](#docker-container)
60 | > - [Docker Container](#docker-container)
61 | > - [Conclusion](#conclusion-)
62 | 63 | ### Introduction 64 | 65 | Brain tumor detection is a critical task in the field of medical imaging, as it plays a crucial role in diagnosing and treating brain tumors, which can be life-threatening. With the advancement of machine learning and artificial intelligence (AI), vision AI has emerged as a promising approach for accurate and efficient brain tumor detection from medical images. In this project, we aim to develop a vision AI system for brain tumor detection using a level 2 MLOps (Machine Learning Operations) architecture, which includes a robust dvc (Data Version Control) pipeline and a Docker image for seamless production deployment. 66 | 67 | MLOps, also known as DevOps for machine learning, is an iterative and collaborative approach that integrates machine learning models into the software development process, ensuring the reliability, scalability, and maintainability of ML models in production. Level 2 MLOps architecture focuses on advanced versioning and reproducibility, ensuring that the ML pipeline is well-documented and can be easily replicated in different environments. 68 | 69 | The ultimate goal of our vision AI project is to develop a robust and scalable brain tumor detection system that can be easily deployed in production environments. By leveraging the level 2 MLOps architecture. It will help to minimize the healthcare operational cost and increase the effectiveness of the services by assisting the healthcare provider in accurate decision-making. 70 | 71 | ## Class Activation Map 72 | ![image](./assets/activationMap.jpg) 73 | ## System Design 74 | ![image](./assets/SystemDesign.jpg) 75 | 76 | ## CICD on Circleci 77 | ![image](./assets/cicd.jpg) 78 | ## DagsHub Data Pipeline 79 | ![image](./assets/DagsHubPipeline.jpg)
80 | Complete Project Data Pipeline is available at [DagsHub Data Pipeline](https://dagshub.com/hassi34/brain-tumor-classification) 81 | 82 | 83 | ## Train Model Locally 84 |
 85 | Dataset              : Brain Tumor MRI Dataset  
 86 | Jupyter Notebooks    : Model Traninig Notebooks                  
 87 | 
88 | The sample images of Glioma, Meningioma, Pituitary and Normal patients are shown in figure below: 89 | ![Image of Brain MRI](./assets/sample_images.jpg) 90 | 91 | #### Dataset Details 92 |
 93 | Dataset Name            : Brain Tumor MRI Dataset (Glioma vs Meningioma vs Pituitary vs Normal)
 94 | Number of Class         : 4
 95 | Number/Size of Images   : Total      : 7023 (151 MB)
 96 |                           Training   : 5712 
 97 |                           Testing    : 1311 
 98 |                          
 99 | 
100 | #### Results (Performance Metrics) 101 | We have achieved following results with DenseNet121 model for detection of Glioma, Meningioma, Pituitary and Normal patients from Brain MRI images. 102 | 103 |
104 |  Performance Metrics 
105 | Test Accuracy                                    : 98.9%
106 | Precision                                        : 99.00%
107 | Sensitivity (Glioma)                             : 100% 
108 | Sensitivity (Meningioma)                         : 99% 
109 | Sensitivity (Pituitary)                          : 100% 
110 | Sensitivity (Normal)                             : 99% 
111 | F1-score                                         : 99.00%
112 | AUC                                              : 1.0
113 | 
114 | 115 | 116 | ## Tech Stack Used 117 |
118 | 1. Python 
119 | 2. Data Version Control (DVC) 
120 | 3. Docker
121 | 4. Machine learning algorithms
122 | 5. MLFlow
123 | 6. Cloud Computing
124 | 7. SMTP Server
125 | 
126 | ## Infrastructure 127 |
128 | 1. DockerHub
129 | 2. Google Cloud Storage (GCS)
130 | 3. Google Artifact Registry
131 | 4. GitHub
132 | 5. DaghsHub
133 | 6. CircleCI
134 | 7. Google App Engine
135 | 
136 | ## Run Locally 137 | 138 | * Ensure you have [Python 3.7+](https://www.python.org/downloads/) installed. 139 | 140 | * Create a new Python Conda environment: 141 | 142 | ```bash 143 | conda create -n venv python=3.10 144 | conda activate venv 145 | ``` 146 | OR 147 | * Create a new Python virtual environment with pip: 148 | ```bash 149 | virtualenv venv 150 | source venv/Scripts/activate 151 | ``` 152 | Install dependencies 153 | 154 | ```bash 155 | pip install -r requirements.txt 156 | ``` 157 | 158 | Clone the project 159 | 160 | ```bash 161 | git clone https://github.com/Hassi34/brain-tumor-classification.git 162 | ``` 163 | 164 | Go to the project directory 165 | 166 | ```bash 167 | cd brain-tumor-classification 168 | ``` 169 | 170 | #### Export the environment variable 171 | ```bash 172 | # MLFlow 173 | MLFLOW_TRACKING_URI="" 174 | MLFLOW_TRACKING_USERNAME="" 175 | MLFLOW_TRACKING_PASSWORD="" 176 | 177 | #DockerHub 178 | DOCKERHUB_ACCESS_TOKEN="" 179 | DOCKERHUB_USERNAME="" 180 | 181 | #GCP 182 | JSON_DCRYPT_KEY="" 183 | GCLOUD_SERVICE_KEY="" 184 | CLOUDSDK_CORE_PROJECT="" 185 | GOOGLE_COMPUTE_REGION="" 186 | GOOGLE_COMPUTE_ZONE="" 187 | 188 | #Alerts 189 | EMAIL_PASS="" 190 | SERVER_EMAIL="" 191 | EMAIL_RECIPIENTS="" 192 | 193 | ``` 194 | 195 | Run Pipeline 196 | 197 | ```bash 198 | dvc repro 199 | ``` 200 | ## REST API with Docker 201 | To run the following commands, ensure you have the docker installed on your system. 202 | 203 | ### Pull Image from Docker Hub 204 | In case you have not already pulled the image from the Docker Hub, you can use the following command: 205 | ```bash 206 | docker pull hassi34/brain-tumor-classification 207 | ``` 208 | 209 | ### Docker Container 210 | Now once you have the docker image from the Docker Hub, you can now run the following commands to test and deploy the container to the web 211 | 212 | * Run a Docker Container
213 | Check all the available images: 214 | ```bash 215 | docker images 216 | ``` 217 | Use the following command to run a docker container on your system: 218 | ```bash 219 | docker run --name -p 80:8080 -d 220 | ``` 221 | Check if the container is running: 222 | ```bash 223 | docker ps 224 | ``` 225 | If the container is running, then the API services will be available on all the network interfaces
226 | To access the API service, type **``localhost``** in the browser. 227 | ## Conclusion 228 | This project is production ready for similar use cases and will provide the automated and orchestrated pipeline. 229 | #### **👉🏻Thank you for visiting 🙏 Your feedback would be highly appreciated 💯😊**
230 | #### **👉🏻If you find this project useful then don't forget to star the repo ✨⭐🤖**
231 | #### 🌏[My Portfolio Website][website]

232 | #### **📃 License** 233 | [MIT][license] © [Hasanain][website] 234 | 235 | [license]: hhttps://github.com/Hassi34/brain-tumor-classification/blob/main/LICENSE 236 | [website]: https://hasanain.aicaliber.com 237 | 238 | Let's connect on **[``LinkedIn``](https://www.linkedin.com/in/hasanain-mehmood)**
-------------------------------------------------------------------------------- /artifacts/.gitignore: -------------------------------------------------------------------------------- 1 | /checkpoints 2 | /model_evaluation 3 | -------------------------------------------------------------------------------- /artifacts/models/.gitignore: -------------------------------------------------------------------------------- 1 | /base_model.h5 2 | /untrained_full_model.h5 3 | /trained_model.h5 4 | /blessed_model.h5 5 | -------------------------------------------------------------------------------- /artifacts/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/artifacts/models/.gitkeep -------------------------------------------------------------------------------- /assets/DagsHubPipeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/assets/DagsHubPipeline.jpg -------------------------------------------------------------------------------- /assets/SystemDesign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/assets/SystemDesign.jpg -------------------------------------------------------------------------------- /assets/activationMap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/assets/activationMap.jpg -------------------------------------------------------------------------------- /assets/cicd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/assets/cicd.jpg -------------------------------------------------------------------------------- /assets/sample_images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/assets/sample_images.jpg -------------------------------------------------------------------------------- /configs/config.yaml: -------------------------------------------------------------------------------- 1 | artifacts: 2 | ARTIFACTS_DIR: artifacts 3 | BASE_MODEL_FILE_PATH: artifacts/models/base_model.h5 4 | UNTRAINED_MODEL_FILE_PATH: artifacts/models/untrained_full_model.h5 5 | TRAINED_MODEL_FILE_PATH: artifacts/models/trained_model.h5 6 | BLESSED_MODEL_FILE_PATH: artifacts/models/blessed_model.h5 7 | CHECKPOINTS_DIR: artifacts/checkpoints 8 | CHECKPOINT_FILE_PATH: artifacts/checkpoints/.mdl_wts.hdf5 9 | MODEL_EVAL_DIR: artifacts/model_evaluation 10 | MODEL_EVALUATION_PLOT : artifacts/model_evaluation/eval_metrics.jpg 11 | CONFUSION_MATRIX_PLOT_FILE_PATH: artifacts/model_evaluation/confusion_metrics.jpg 12 | 13 | local_data: 14 | DATA_DIR : dataset 15 | 16 | logs: 17 | LOGS_DIR : logs 18 | RUNNING_LOGS_FILE_PATH: logs/running_logs.log 19 | TENSORBOARD_ROOT_LOGS_DIR: logs/tensorboard_logs_dir 20 | 21 | gcs_config: 22 | BUCKET_NAME: mlops-378116 23 | DATA_DIR : mlops-378116/brain-tumor-classification 24 | ARTIFACTS : mlops-378116/brain-tumor-classification/artifacts 25 | 26 | BASE_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/base_model.h5 27 | UNTRAINED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/untrained_full_model.h5 28 | TRAINED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/trained_model.h5 29 | BLESSED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/blessed_model.h5 30 | 31 | CHECKPOINTS_DIR: mlops-378116/brain-tumor-classification/artifacts/checkpoints 32 | MODEL_EVAL_DIR: mlops-378116/brain-tumor-classification/artifacts/model_evaluation 33 | 34 | LOGS_DIR : mlops-378116/brain-tumor-classification/logs 35 | TENSORBOARD_ROOT_LOGS_DIR : mlops-378116/brain-tumor-classification/logs/tensorboard_logs_dir 36 | LOGS_FILE_PATH : brain-tumor-classification/logs/running_logs.log 37 | 38 | mlflow: 39 | EXPERIMENT_NAME: BrainTumorExp 40 | RUN_ID_PREFIX: online #change to online for deploying the code as pipeline 41 | MODEL_NAME : brain-tumor-model 42 | LOGGED_MODEL_DIR: logged_model 43 | 44 | model_serving: 45 | PRODUCTION_MODEL_PATH: src/production_model/model.h5 46 | INPUT_IMG_FILE_PATH: src/images/input.jpg 47 | OUTPUT_IMG_FILE_PATH: src/images/out.jpg 48 | CLASS_INDICES : {'glioma': 0, 'meningioma': 1, 'notumor': 2, 'pituitary': 3} 49 | APP_HOST : 0.0.0.0 50 | APP_PORT : 8080 51 | API_TITLE: Brain Tumor Classification 52 | API_DESCRIPTION: The REST API service accepts base64 encoded image and returns the predicted class, confidence scores and a base64 encoded image with the class activation map. 53 | API_VERSION: 0.0.1 54 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | container_name: brain-tumor-container 5 | command: python src/main.py 6 | ports: 7 | - 8080:8080 8 | volumes: 9 | - .:/brain_tumor_model_serving -------------------------------------------------------------------------------- /dvc.lock: -------------------------------------------------------------------------------- 1 | schema: '2.0' 2 | stages: 3 | LoadData: 4 | cmd: python src/stage_01_get_data.py --config=configs/config.yaml 5 | deps: 6 | - path: configs/config.yaml 7 | md5: 854bc50c2afc78589b13eb6ea8d24af2 8 | size: 2332 9 | - path: src/cloud_sync/object_storage.py 10 | md5: 276090407df5f37f317c71e090c1c68f 11 | size: 3452 12 | - path: src/stage_01_get_data.py 13 | md5: 2f1f7e9661117522fda13ab2d4706808 14 | size: 1054 15 | - path: src/utils/common.py 16 | md5: 30afeb62522e666fc501d207dc0996e2 17 | size: 503 18 | outs: 19 | - path: dataset/ 20 | md5: ee3ea11752cd6317fafe75dcb908bae3.dir 21 | size: 98844072 22 | nfiles: 1824 23 | PrepareModel: 24 | cmd: python src/stage_02_prepare_model.py --config=configs/config.yaml --params=params.yaml 25 | deps: 26 | - path: configs/config.yaml 27 | md5: 854bc50c2afc78589b13eb6ea8d24af2 28 | size: 2332 29 | - path: params.yaml 30 | md5: 732567af23dd45e1e4face8baf307cc6 31 | size: 535 32 | - path: src/cloud_sync/object_storage.py 33 | md5: 276090407df5f37f317c71e090c1c68f 34 | size: 3452 35 | - path: src/ml/model.py 36 | md5: e15c9a9261902ca431e3433e19e36f53 37 | size: 5034 38 | - path: src/stage_02_prepare_model.py 39 | md5: 0d0898a1227b7852eea78c539c52e69e 40 | size: 2322 41 | - path: src/utils/common.py 42 | md5: 30afeb62522e666fc501d207dc0996e2 43 | size: 503 44 | params: 45 | params.yaml: 46 | CLASSES: 3 47 | FREEZE_ALL: true 48 | FREEZE_TILL: -5 49 | IMAGE_SIZE: 50 | - 224 51 | - 224 52 | - 3 53 | LEARNING_RATE: 0.001 54 | LOSS_FUNCTION: categorical_crossentropy 55 | METRICS: accuracy 56 | outs: 57 | - path: artifacts/models/base_model.h5 58 | md5: aae3f4c5cd8a557e8d2d5a98ee37322e 59 | size: 75214488 60 | - path: artifacts/models/untrained_full_model.h5 61 | md5: bf9239511ef077b8bb12b0995f4a41b1 62 | size: 79505320 63 | TraningAndEvaluation: 64 | cmd: python src/stage_03_train_evaluate.py --config=configs/config.yaml --params=params.yaml 65 | deps: 66 | - path: artifacts/models/untrained_full_model.h5 67 | md5: 8ec4833fdfc1da2d81109992e1246605 68 | size: 79505320 69 | - path: configs/config.yaml 70 | md5: 854bc50c2afc78589b13eb6ea8d24af2 71 | size: 2332 72 | - path: dataset/ 73 | md5: ee3ea11752cd6317fafe75dcb908bae3.dir 74 | size: 98844072 75 | nfiles: 1824 76 | - path: params.yaml 77 | md5: 732567af23dd45e1e4face8baf307cc6 78 | size: 535 79 | - path: src/cloud_sync/object_storage.py 80 | md5: 276090407df5f37f317c71e090c1c68f 81 | size: 3452 82 | - path: src/ml/model.py 83 | md5: e15c9a9261902ca431e3433e19e36f53 84 | size: 5034 85 | - path: src/stage_03_train_evaluate.py 86 | md5: 07d0fb7d9e8fa08efc0299f1941c70e9 87 | size: 6911 88 | - path: src/utils/common.py 89 | md5: 30afeb62522e666fc501d207dc0996e2 90 | size: 503 91 | params: 92 | params.yaml: 93 | BATCH_SIZE: 16 94 | DATA_AUGMENTATION: true 95 | EARLY_STOPPING_PATIENCE: 10 96 | EPOCHS: 2 97 | LEARNING_RATE_PATIENCE: 4 98 | TRAIN_FORM_CHECKPOINT: false 99 | VALIDATION_SPLIT_SIZE: 0.2 100 | outs: 101 | - path: artifacts/checkpoints/ 102 | md5: 140c203bf6825cbb5e772f49c4e045fa.dir 103 | size: 88027400 104 | nfiles: 1 105 | - path: artifacts/model_evaluation/ 106 | md5: 29e825cc649f6928c176bbe196f88003.dir 107 | size: 47557 108 | nfiles: 2 109 | - path: artifacts/models/trained_model.h5 110 | md5: fdc1bdc523206a61397dd987e55be007 111 | size: 88027400 112 | - path: logs/tensorboard_logs_dir/ 113 | md5: 7aae23d26a6d177fea25efe78ae70b63.dir 114 | size: 1736379 115 | nfiles: 2 116 | ModelBlessing: 117 | cmd: python src/stage_04_model_blessing.py --config=configs/config.yaml --params=params.yaml 118 | deps: 119 | - path: artifacts/models/trained_model.h5 120 | md5: de7c41b0fd735f1d74efb62ab5a8bc65 121 | size: 88027400 122 | - path: configs/config.yaml 123 | md5: 3bf9212ebd8810093f7fbf31a21dedb4 124 | size: 1861 125 | - path: params.yaml 126 | md5: 604a1a6b9492affd385965e02fe96602 127 | size: 536 128 | - path: src/cloud_sync/object_storage.py 129 | md5: 276090407df5f37f317c71e090c1c68f 130 | size: 3452 131 | - path: src/ml/model.py 132 | md5: e15c9a9261902ca431e3433e19e36f53 133 | size: 5034 134 | - path: src/stage_04_model_blessing.py 135 | md5: 8a0465c88d5cfde10f8b8e65f21239c2 136 | size: 4287 137 | - path: src/utils/common.py 138 | md5: 49d2036517bacc83d42e76338a4528cc 139 | size: 537 140 | params: 141 | params.yaml: 142 | BLESSING_THRESHOLD_COMPARISION: false 143 | MODEL_BLESSING_THRESHOLD: 1.5 144 | outs: 145 | - path: artifacts/models/blessed_model.h5 146 | md5: c8cb4ac086d0e0aad0d5d03651671e9b 147 | size: 88027400 148 | -------------------------------------------------------------------------------- /dvc.yaml: -------------------------------------------------------------------------------- 1 | # add stages here 2 | stages: 3 | LoadData: 4 | cmd: python src/stage_01_get_data.py --config=configs/config.yaml 5 | deps: 6 | - src/stage_01_get_data.py 7 | - src/utils/common.py 8 | - configs/config.yaml 9 | - src/cloud_sync/object_storage.py 10 | outs: 11 | - dataset/ 12 | 13 | PrepareModel: 14 | cmd: python src/stage_02_prepare_model.py --config=configs/config.yaml --params=params.yaml 15 | deps: 16 | - src/stage_02_prepare_model.py 17 | - src/cloud_sync/object_storage.py 18 | - src/utils/common.py 19 | - src/ml/model.py 20 | - configs/config.yaml 21 | - params.yaml 22 | params: 23 | - IMAGE_SIZE 24 | - CLASSES 25 | - LEARNING_RATE 26 | - FREEZE_ALL 27 | - FREEZE_TILL 28 | - LOSS_FUNCTION 29 | - METRICS 30 | outs: 31 | - artifacts/models/base_model.h5 32 | - artifacts/models/untrained_full_model.h5 33 | 34 | TraningAndEvaluation: 35 | cmd: python src/stage_03_train_evaluate.py --config=configs/config.yaml --params=params.yaml 36 | deps: 37 | - src/stage_03_train_evaluate.py 38 | - src/cloud_sync/object_storage.py 39 | - dataset/ 40 | - artifacts/models/untrained_full_model.h5 41 | - src/utils/common.py 42 | - src/ml/model.py 43 | - configs/config.yaml 44 | - params.yaml 45 | params: 46 | - VALIDATION_SPLIT_SIZE 47 | - BATCH_SIZE 48 | - DATA_AUGMENTATION 49 | - EARLY_STOPPING_PATIENCE 50 | - LEARNING_RATE_PATIENCE 51 | - EPOCHS 52 | - TRAIN_FORM_CHECKPOINT 53 | outs: 54 | - artifacts/checkpoints/ 55 | - artifacts/model_evaluation/ 56 | - artifacts/models/trained_model.h5 57 | - logs/tensorboard_logs_dir/ 58 | 59 | ModelBlessing: 60 | cmd: python src/stage_04_model_blessing.py --config=configs/config.yaml --params=params.yaml 61 | deps: 62 | - src/stage_04_model_blessing.py 63 | - artifacts/models/trained_model.h5 64 | - src/cloud_sync/object_storage.py 65 | - src/utils/common.py 66 | - src/ml/model.py 67 | - configs/config.yaml 68 | - params.yaml 69 | params: 70 | - BLESSING_THRESHOLD_COMPARISION 71 | - MODEL_BLESSING_THRESHOLD 72 | outs: 73 | - artifacts/models/blessed_model.h5 -------------------------------------------------------------------------------- /model_serving/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/README.md -------------------------------------------------------------------------------- /model_serving/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | 4 | resources: 5 | cpu: 2 6 | memory_gb: 4 7 | disk_size_gb: 10 8 | volumes: 9 | - name: ramdisk1 10 | volume_type: tmpfs 11 | size_gb: 0.5 12 | 13 | liveness_check: 14 | check_interval_sec: 30 15 | timeout_sec: 4 16 | failure_threshold: 2 17 | success_threshold: 2 18 | 19 | readiness_check: 20 | check_interval_sec: 5 21 | timeout_sec: 4 22 | failure_threshold: 2 23 | success_threshold: 2 24 | app_start_timeout_sec: 300 25 | 26 | automatic_scaling: 27 | min_num_instances: 1 28 | max_num_instances: 3 29 | cool_down_period_sec: 180 30 | cpu_utilization: 31 | target_utilization: 0.6 32 | target_concurrent_requests: 100 33 | 34 | env_variables: 35 | MY_VAR: "my value" -------------------------------------------------------------------------------- /model_serving/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.95.0 2 | uvicorn 3 | aipilot 4 | opencv-python-headless==4.7.0.72 5 | imutils 6 | PyYAML 7 | -e . -------------------------------------------------------------------------------- /model_serving/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from typing import List 3 | 4 | with open("README.md", "r", encoding="utf-8") as f: 5 | long_description = f.read() 6 | 7 | # Declaring variables for setup functions 8 | PROJECT_NAME = "src" 9 | VERSION = "0.0.1" 10 | AUTHOR = "Hasanain" 11 | USER_NAME = "hassi34" 12 | AUTHOR_EMAIL = "hasanain@aicaliber.com" 13 | REPO_NAME = "brain-tumor-classification" 14 | DESRCIPTION = "This project contains the production ready Machine Learning(Deep Learning) solution for detecting and classifying the brain tumor in medical images" 15 | REQUIREMENT_FILE_NAME = "requirements.txt" 16 | LICENSE = "MIT" 17 | PYTHON_REQUIRES = ">=3.7" 18 | 19 | HYPHEN_E_DOT = "-e ." 20 | 21 | 22 | def get_requirements_list() -> List[str]: 23 | """ 24 | Description: This function is going to return list of requirement 25 | mention in requirements.txt file 26 | return This function is going to return a list which contain name 27 | of libraries mentioned in requirements.txt file 28 | """ 29 | with open(REQUIREMENT_FILE_NAME) as requirement_file: 30 | requirement_list = requirement_file.readlines() 31 | requirement_list = [requirement_name.replace("\n", "") for requirement_name in requirement_list] 32 | if HYPHEN_E_DOT in requirement_list: 33 | requirement_list.remove(HYPHEN_E_DOT) 34 | return requirement_list 35 | 36 | 37 | setup( 38 | name=PROJECT_NAME, 39 | version=VERSION, 40 | author=AUTHOR, 41 | description=DESRCIPTION, 42 | long_description=long_description, 43 | url=f"https://github.com/{USER_NAME}/{REPO_NAME}", 44 | packages=find_packages(), 45 | license=LICENSE, 46 | python_requires=PYTHON_REQUIRES, 47 | install_requires=get_requirements_list() 48 | ) -------------------------------------------------------------------------------- /model_serving/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/__init__.py -------------------------------------------------------------------------------- /model_serving/src/configs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/configs/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/configs/config.yaml: -------------------------------------------------------------------------------- 1 | artifacts: 2 | ARTIFACTS_DIR: artifacts 3 | BASE_MODEL_FILE_PATH: artifacts/models/base_model.h5 4 | UNTRAINED_MODEL_FILE_PATH: artifacts/models/untrained_full_model.h5 5 | TRAINED_MODEL_FILE_PATH: artifacts/models/trained_model.h5 6 | BLESSED_MODEL_FILE_PATH: artifacts/models/blessed_model.h5 7 | CHECKPOINTS_DIR: artifacts/checkpoints 8 | CHECKPOINT_FILE_PATH: artifacts/checkpoints/.mdl_wts.hdf5 9 | MODEL_EVAL_DIR: artifacts/model_evaluation 10 | MODEL_EVALUATION_PLOT : artifacts/model_evaluation/eval_metrics.jpg 11 | CONFUSION_MATRIX_PLOT_FILE_PATH: artifacts/model_evaluation/confusion_metrics.jpg 12 | 13 | local_data: 14 | DATA_DIR : dataset 15 | 16 | logs: 17 | LOGS_DIR : logs 18 | RUNNING_LOGS_FILE_PATH: logs/running_logs.log 19 | TENSORBOARD_ROOT_LOGS_DIR: logs/tensorboard_logs_dir 20 | 21 | gcs_config: 22 | BUCKET_NAME: mlops-378116 23 | DATA_DIR : mlops-378116/brain-tumor-classification 24 | ARTIFACTS : mlops-378116/brain-tumor-classification/artifacts 25 | 26 | BASE_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/base_model.h5 27 | UNTRAINED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/untrained_full_model.h5 28 | TRAINED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/trained_model.h5 29 | BLESSED_MODEL_FILE_PATH: brain-tumor-classification/artifacts/models/blessed_model.h5 30 | 31 | CHECKPOINTS_DIR: mlops-378116/brain-tumor-classification/artifacts/checkpoints 32 | MODEL_EVAL_DIR: mlops-378116/brain-tumor-classification/artifacts/model_evaluation 33 | 34 | LOGS_DIR : mlops-378116/brain-tumor-classification/logs 35 | TENSORBOARD_ROOT_LOGS_DIR : mlops-378116/brain-tumor-classification/logs/tensorboard_logs_dir 36 | LOGS_FILE_PATH : brain-tumor-classification/logs/running_logs.log 37 | 38 | mlflow: 39 | EXPERIMENT_NAME: BrainTumorExp 40 | RUN_ID_PREFIX: online #change to online for deploying the code as pipeline 41 | MODEL_NAME : brain-tumor-model 42 | LOGGED_MODEL_DIR: logged_model 43 | 44 | model_serving: 45 | PRODUCTION_MODEL_PATH: src/production_model/model.h5 46 | INPUT_IMG_FILE_PATH: src/images/input.jpg 47 | OUTPUT_IMG_FILE_PATH: src/images/out.jpg 48 | CLASS_INDICES : {'glioma': 0, 'meningioma': 1, 'notumor': 2, 'pituitary': 3} 49 | APP_HOST : 0.0.0.0 50 | APP_PORT : 8080 51 | API_TITLE: Brain Tumor Classification 52 | API_DESCRIPTION: The REST API service accepts base64 encoded image and returns the predicted class, confidence scores and a base64 encoded image with the class activation map. 53 | API_VERSION: 0.0.1 54 | -------------------------------------------------------------------------------- /model_serving/src/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/images/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/images/input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/images/input.jpg -------------------------------------------------------------------------------- /model_serving/src/images/out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/images/out.jpg -------------------------------------------------------------------------------- /model_serving/src/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi.middleware.cors import CORSMiddleware 3 | from starlette.responses import RedirectResponse 4 | from uvicorn import run as app_run 5 | from utils.common import read_yaml 6 | import schemas.schema as SCHEMA 7 | from fastapi import status 8 | from prediction_service.services import predict 9 | 10 | config = read_yaml('src/configs/config.yaml') 11 | 12 | PRODUCTION_MODEL_PATH = config['model_serving']['PRODUCTION_MODEL_PATH'] 13 | APP_HOST = config['model_serving']['APP_HOST'] 14 | APP_PORT = config['model_serving']['APP_PORT'] 15 | 16 | INPUT_IMG_FILE_PATH = config['model_serving']['INPUT_IMG_FILE_PATH'] 17 | OUTPUT_IMG_FILE_PATH = config['model_serving']['OUTPUT_IMG_FILE_PATH'] 18 | CLASS_INDICES = config['model_serving']['CLASS_INDICES'] 19 | 20 | API_TITLE = config['model_serving']['API_TITLE'] 21 | API_DESCRIPTION = config['model_serving']['API_DESCRIPTION'] 22 | API_VERSION = config['model_serving']['API_VERSION'] 23 | 24 | app = FastAPI( 25 | title=API_TITLE, 26 | description=API_DESCRIPTION, 27 | version=API_VERSION 28 | ) 29 | 30 | origins = ["*"] 31 | 32 | app.add_middleware( 33 | CORSMiddleware, 34 | allow_origins=origins, 35 | allow_credentials=True, 36 | allow_methods=["*"], 37 | allow_headers=["*"], 38 | ) 39 | 40 | 41 | @app.get("/", tags=["Authentication"]) 42 | async def index(): 43 | return RedirectResponse(url="/docs") 44 | 45 | 46 | @app.post('/predict', tags=["Prediction"], response_model=SCHEMA.ShowResults, 47 | status_code=status.HTTP_200_OK) 48 | async def predict_route(inputParam: SCHEMA.Prediction): 49 | base64_enc_img = inputParam.base64_enc_img 50 | prediction_proba, predicted_cls, base64_enc_class_activation_map = predict( 51 | base64_enc_img, 52 | PRODUCTION_MODEL_PATH, 53 | INPUT_IMG_FILE_PATH, 54 | OUTPUT_IMG_FILE_PATH 55 | ) 56 | 57 | return {"predicted_class": predicted_cls, 58 | "class_indices": CLASS_INDICES, 59 | "prediction_probabilities": prediction_proba, 60 | "base64_enc_class_activation_map": base64_enc_class_activation_map 61 | } 62 | 63 | if __name__ == "__main__": 64 | app_run(app=app, host=APP_HOST, port=APP_PORT) -------------------------------------------------------------------------------- /model_serving/src/params.yaml: -------------------------------------------------------------------------------- 1 | # This contains params to be used by the stages to train or predict 2 | IMAGE_SIZE : [224, 224, 3] # Image size used in VGG16 3 | CLASSES : 4 4 | FREEZE_ALL : True 5 | FREEZE_TILL: -5 6 | DATA_AUGMENTATION : True 7 | VALIDATION_SPLIT_SIZE: 0.20 8 | LOSS_FUNCTION : categorical_crossentropy 9 | METRICS : accuracy 10 | BATCH_SIZE : 16 # Default batch size is 32 11 | EPOCHS: 2 12 | LEARNING_RATE : 0.001 13 | EARLY_STOPPING_PATIENCE : 10 14 | LEARNING_RATE_PATIENCE : 3 15 | TRAIN_FORM_CHECKPOINT : False 16 | 17 | BLESSING_THRESHOLD_COMPARISION : False 18 | MODEL_BLESSING_THRESHOLD : 0.001 19 | 20 | 21 | -------------------------------------------------------------------------------- /model_serving/src/prediction_service/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/prediction_service/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/prediction_service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/prediction_service/__init__.py -------------------------------------------------------------------------------- /model_serving/src/prediction_service/services.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from fastapi import HTTPException, status 3 | from src.utils.common import decode_base64_image, encode_image_into_base64 4 | from tensorflow.keras.models import load_model 5 | import imutils 6 | from aipilot.tf.cv import GradCam 7 | from PIL import Image 8 | from typing import Union 9 | import cv2 10 | 11 | def prep_img(input_img_path, IMG_SIZE=224): 12 | if input_img_path.endswith(('.jpg','.png', '.jpeg', '.JPG', '.PNG', '.JPEG')): 13 | """ 14 | Finds the extreme points on the image and crops the rectangular out of them, removes the noise and add the pseudo colors 15 | """ 16 | img = cv2.imread(input_img_path) 17 | gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 18 | gray = cv2.GaussianBlur(gray, (3, 3), 0) 19 | # threshold the image, then perform a series of erosions + 20 | # dilations to remove any small regions of noise 21 | thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1] 22 | thresh = cv2.erode(thresh, None, iterations=2) 23 | thresh = cv2.dilate(thresh, None, iterations=2) 24 | # find contours in thresholded image, then grab the largest one 25 | cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 26 | cnts = imutils.grab_contours(cnts) 27 | c = max(cnts, key=cv2.contourArea) 28 | # find the extreme points 29 | extLeft = tuple(c[c[:, :, 0].argmin()][0]) 30 | extRight = tuple(c[c[:, :, 0].argmax()][0]) 31 | extTop = tuple(c[c[:, :, 1].argmin()][0]) 32 | extBot = tuple(c[c[:, :, 1].argmax()][0]) 33 | ADD_PIXELS = 0 34 | new_img = img[extTop[1]-ADD_PIXELS:extBot[1]+ADD_PIXELS, extLeft[0]-ADD_PIXELS:extRight[0]+ADD_PIXELS].copy() 35 | new_img = cv2.cvtColor(new_img, cv2.COLOR_RGB2GRAY) 36 | new_img = cv2.bilateralFilter(new_img, 2, 50, 50) # remove images noise. 37 | new_img = cv2.applyColorMap(new_img, cv2.COLORMAP_BONE) # produce a pseudocolored image. 38 | new_img = cv2.resize(new_img,(IMG_SIZE,IMG_SIZE)) 39 | cv2.imwrite(input_img_path, new_img) 40 | return new_img 41 | else: 42 | raise Exception("Invalid File Extension. Valid Extensions : ['.jpg','.png', '.jpeg', '.JPG', '.PNG', '.JPEG']") 43 | 44 | def predict(base64_enc_img : str, model_path: str , 45 | input_img_path: str, output_img_path: str): 46 | decode_base64_image(base64_enc_img, input_img_path) 47 | img_arr = prep_img(input_img_path=input_img_path) 48 | #img_arr = np.asarray(Image.open(input_img_path)) 49 | img_arr = np.expand_dims(img_arr, axis=0)/255.0 50 | model = load_model(model_path) 51 | prediction_proba = np.round(model.predict(img_arr), decimals = 6) 52 | predicted_cls = np.argmax(prediction_proba, axis=1)[0] 53 | 54 | gradcam = GradCam(model, "conv5_block16_concat", in_img_path= input_img_path, 55 | out_img_path=output_img_path, normalize_img=True) 56 | gradcam.get_gradcam() 57 | 58 | base64_enc_class_activation_map = encode_image_into_base64(output_img_path) 59 | 60 | prediction_proba = list(prediction_proba[0]) 61 | prediction_proba = [round(float(i), 4) for i in prediction_proba] 62 | print(prediction_proba) 63 | if isinstance(prediction_proba, Union [list, np.ndarray]) and len(prediction_proba) == 4\ 64 | and isinstance(prediction_proba[0], Union[float, np.float32])\ 65 | and isinstance(prediction_proba[1], Union[float, np.float32])\ 66 | and isinstance(prediction_proba[2], Union[float, np.float32])\ 67 | and isinstance(prediction_proba[3], Union[float, np.float32])\ 68 | and isinstance(base64_enc_class_activation_map, str): 69 | return prediction_proba, predicted_cls, base64_enc_class_activation_map 70 | else: 71 | message = "Unexpected prediction values" 72 | raise HTTPException(status_code=status.HTTP_417_EXPECTATION_FAILED, 73 | detail=message) -------------------------------------------------------------------------------- /model_serving/src/production_model/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/production_model/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/production_model/model.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/production_model/model.h5 -------------------------------------------------------------------------------- /model_serving/src/schemas/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/schemas/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/schemas/__init__.py -------------------------------------------------------------------------------- /model_serving/src/schemas/schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class Prediction(BaseModel): 4 | base64_enc_img : str 5 | 6 | class ShowResults(BaseModel): 7 | predicted_class : int = None 8 | class_indices: dict = None 9 | prediction_probabilities : list = None 10 | base64_enc_class_activation_map: str = None -------------------------------------------------------------------------------- /model_serving/src/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/utils/.gitkeep -------------------------------------------------------------------------------- /model_serving/src/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/model_serving/src/utils/__init__.py -------------------------------------------------------------------------------- /model_serving/src/utils/common.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import base64 3 | 4 | def read_yaml(path_to_yaml: str) -> dict: 5 | with open(path_to_yaml) as yaml_file: 6 | content = yaml.safe_load(yaml_file) 7 | return content 8 | 9 | def decode_base64_image(img_string, file_name): 10 | img_data = base64.b64decode(img_string) 11 | with open("./"+file_name, 'wb') as f: 12 | f.write(img_data) 13 | f.close() 14 | 15 | def encode_image_into_base64(img_path): 16 | with open(img_path, "rb") as f: 17 | return base64.b64encode(f.read()).decode("utf-8") -------------------------------------------------------------------------------- /params.yaml: -------------------------------------------------------------------------------- 1 | # This contains params to be used by the stages to train or predict 2 | IMAGE_SIZE : [224, 224, 3] # Image size used in VGG16 3 | CLASSES : 4 4 | FREEZE_ALL : True 5 | FREEZE_TILL: -5 6 | DATA_AUGMENTATION : True 7 | VALIDATION_SPLIT_SIZE: 0.20 8 | LOSS_FUNCTION : categorical_crossentropy 9 | METRICS : accuracy 10 | BATCH_SIZE : 16 # Default batch size is 32 11 | EPOCHS: 2 12 | LEARNING_RATE : 0.001 13 | EARLY_STOPPING_PATIENCE : 10 14 | LEARNING_RATE_PATIENCE : 3 15 | TRAIN_FORM_CHECKPOINT : False 16 | 17 | BLESSING_THRESHOLD_COMPARISION : False 18 | MODEL_BLESSING_THRESHOLD : 0.001 19 | 20 | 21 | -------------------------------------------------------------------------------- /raw.py: -------------------------------------------------------------------------------- 1 | from src.cloud_sync import CloudSync 2 | 3 | sync = CloudSync() 4 | #sync.sync_gcs_to_local_data_dir() 5 | 6 | #sync.sync_checkpoints_dir_to_gcs() 7 | 8 | #sync.sync_gcs_to_checkpoints_dir() 9 | #sync.sync_tensorboard_logs_dir_to_gcs() 10 | #sync.sync_gcs_to_tensorboard_logs_dir() 11 | #sync.sync_model_eval_dir_to_gcs() 12 | #sync.upload_trained_model() 13 | #sync.download_trained_model() 14 | #sync.upload_untrained_full_model() 15 | #sync.download_untrained_model() 16 | #sync.sync_local_data_dir_to_gcs() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aipilot 2 | tqdm>=4.64.1 3 | dvc>=2.50.0 4 | dvc-gs 5 | python-dotenv 6 | google-cloud-storage 7 | cryptography>=39.0.1 8 | mlflow>=2.2.2 9 | -e . -------------------------------------------------------------------------------- /scripts/sanity_check_alert.sh: -------------------------------------------------------------------------------- 1 | pip install -r requirements.txt 2 | python3 src/alerts/send_alert.py -------------------------------------------------------------------------------- /scripts/serving_setup.sh: -------------------------------------------------------------------------------- 1 | echo [$(date)]: "START SERVING SETUP" 2 | echo [$(date)]: "Create project folder structure" 3 | python3 template.py 4 | echo [$(date)]: "Copy blessed model to serving" 5 | python3 src/utils/download_blessed_model.py 6 | cp artifacts/models/blessed_model.h5 model_serving/src/production_model/model.h5 7 | echo [$(date)]: "Copy configuration to serving" 8 | cp configs/config.yaml model_serving/src/configs/config.yaml 9 | echo [$(date)]: "Copy params to serving" 10 | cp params.yaml model_serving/src/params.yaml 11 | echo [$(date)]: "copy common utils to serving" 12 | cp src/utils/common.py model_serving/src/utils/common.py 13 | echo [$(date)]: "Copy setup.py" 14 | cp setup.py model_serving/setup.py 15 | echo [$(date)]: "SERVING SETUP COMPLETED" -------------------------------------------------------------------------------- /scripts/training.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash -eo pipefail 3 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> TRAINING ENVIRONMENT SETUP >>>>>>>>>>>>>>>>>>" 4 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Install Miniconda >>>>>>>>>>>>>>>>>>" 5 | apt update 6 | apt install wget 7 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ 8 | && mkdir /root/.conda \ 9 | && bash Miniconda3-latest-Linux-x86_64.sh -b \ 10 | && rm -f Miniconda3-latest-Linux-x86_64.sh 11 | export PATH="/root/miniconda3/bin:${PATH}" 12 | echo "Running $(conda --version)" 13 | conda init bash 14 | . /root/.bashrc 15 | conda update -n base -c defaults conda -y 16 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Create Environment >>>>>>>>>>>>>>>>>>" 17 | conda create -n myenv python=3.10 -y 18 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Activate Environment >>>>>>>>>>>>>>>>>>" 19 | conda activate myenv 20 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Install Requirements >>>>>>>>>>>>>>>>>>" 21 | pip install -r requirements.txt 22 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Authenticate GCP >>>>>>>>>>>>>>>>>>" 23 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 24 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Project Folder Structure >>>>>>>>>>>>>>>>>>" 25 | python template.py 26 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Download data from Source >>>>>>>>>>>>>>>>>>" 27 | python src/stage_01_get_data.py --config=configs/config.yaml 28 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Prepare Model >>>>>>>>>>>>>>>>>>" 29 | python src/stage_02_prepare_model.py --config=configs/config.yaml --params=params.yaml 30 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Model Training and Evaluation >>>>>>>>>>>>>>>>>>" 31 | python src/stage_03_train_evaluate.py --config=configs/config.yaml --params=params.yaml 32 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> Model blessing >>>>>>>>>>>>>>>>>>" 33 | python src/stage_04_model_blessing.py --config=configs/config.yaml --params=params.yaml 34 | echo [$(date)]: ">>>>>>>>>>>>>>>>>> TRAINING COMPLETED >>>>>>>>>>>>>>>>>>" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from typing import List 3 | 4 | with open("README.md", "r", encoding="utf-8") as f: 5 | long_description = f.read() 6 | 7 | # Declaring variables for setup functions 8 | PROJECT_NAME = "src" 9 | VERSION = "0.0.1" 10 | AUTHOR = "Hasanain" 11 | USER_NAME = "hassi34" 12 | AUTHOR_EMAIL = "hasanain@aicaliber.com" 13 | REPO_NAME = "brain-tumor-classification" 14 | DESRCIPTION = "This project contains the production ready Machine Learning(Deep Learning) solution for detecting and classifying the brain tumor in medical images" 15 | REQUIREMENT_FILE_NAME = "requirements.txt" 16 | LICENSE = "MIT" 17 | PYTHON_REQUIRES = ">=3.7" 18 | 19 | HYPHEN_E_DOT = "-e ." 20 | 21 | 22 | def get_requirements_list() -> List[str]: 23 | """ 24 | Description: This function is going to return list of requirement 25 | mention in requirements.txt file 26 | return This function is going to return a list which contain name 27 | of libraries mentioned in requirements.txt file 28 | """ 29 | with open(REQUIREMENT_FILE_NAME) as requirement_file: 30 | requirement_list = requirement_file.readlines() 31 | requirement_list = [requirement_name.replace("\n", "") for requirement_name in requirement_list] 32 | if HYPHEN_E_DOT in requirement_list: 33 | requirement_list.remove(HYPHEN_E_DOT) 34 | return requirement_list 35 | 36 | 37 | setup( 38 | name=PROJECT_NAME, 39 | version=VERSION, 40 | author=AUTHOR, 41 | description=DESRCIPTION, 42 | long_description=long_description, 43 | url=f"https://github.com/{USER_NAME}/{REPO_NAME}", 44 | packages=find_packages(), 45 | license=LICENSE, 46 | python_requires=PYTHON_REQUIRES, 47 | install_requires=get_requirements_list() 48 | ) -------------------------------------------------------------------------------- /src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/src/.gitkeep -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | #note1 -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/alerts/__init__.py: -------------------------------------------------------------------------------- 1 | from .mail import Email -------------------------------------------------------------------------------- /src/alerts/mail.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os, datetime, pytz, smtplib 3 | from email.message import EmailMessage 4 | 5 | load_dotenv() 6 | EMAIL_PASS = os.environ['EMAIL_PASS'] 7 | SENDER = os.environ['SERVER_EMAIL'] 8 | RECIPIENTS = os.environ['EMAIL_RECIPIENTS'] 9 | 10 | class Email: 11 | def __init__(self): 12 | self.recipients = RECIPIENTS 13 | self.password = EMAIL_PASS 14 | self.sender = SENDER 15 | self.datetime = datetime.datetime.now(pytz.timezone("Asia/Jakarta")).strftime("%m/%d/%Y, %H:%M:%S") 16 | 17 | def send_sanity_check_alert(self): 18 | try: 19 | msg = EmailMessage() 20 | msg["Subject"] = "Model Training and Deployment finished" 21 | msg["From"] = self.sender 22 | msg["To"] = self.recipients 23 | 24 | body = """ 25 | 26 | Hi Hasanain, 27 | 28 | A new version of Brain Tumor Image Classifier has been deployed to GCP App Engine, awaiting sanity check... 29 | 30 | Note: Do not reply to this email as this is a system-generated alert. 31 | 32 | Regards, 33 | MLOps 34 | """ 35 | msg.set_content(body) 36 | 37 | with smtplib.SMTP("smtp.gmail.com", 587) as smtp: 38 | smtp.starttls() 39 | smtp.login(self.sender, self.password) 40 | print("log in successfull!! \nsending email") 41 | smtp.send_message(msg) 42 | print(f"email Sent to {self.recipients}") 43 | 44 | except Exception as e: 45 | raise e -------------------------------------------------------------------------------- /src/alerts/send_alert.py: -------------------------------------------------------------------------------- 1 | from src.alerts import Email 2 | 3 | alert = Email() 4 | alert.send_sanity_check_alert() -------------------------------------------------------------------------------- /src/cloud_sync/__init__.py: -------------------------------------------------------------------------------- 1 | from src.config import * 2 | from .object_storage import CloudSync -------------------------------------------------------------------------------- /src/cloud_sync/object_storage.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.utils.common import read_yaml 3 | from src.utils.gcp_bucket import BucketGCP 4 | 5 | config = read_yaml("configs/config.yaml") 6 | params = read_yaml("params.yaml") 7 | 8 | LOCAL_DATA_DIR = config['local_data']['DATA_DIR'] 9 | BUCKET_NAME = config['gcs_config']['BUCKET_NAME'] 10 | GCS_DATA_DIR = config['gcs_config']['DATA_DIR'] 11 | 12 | GS_ARTIFACTS = config['gcs_config']['ARTIFACTS'] 13 | GS_UNTRAINED_MODEL = config['gcs_config']['UNTRAINED_MODEL_FILE_PATH'] 14 | GS_TRAINED_MODEL = config['gcs_config']['TRAINED_MODEL_FILE_PATH'] 15 | GS_BLESSED_MODEL = config['gcs_config']['BLESSED_MODEL_FILE_PATH'] 16 | GS_LOGS_DIR = config['gcs_config']['LOGS_DIR'] 17 | GS_LOGS_FILE_PATH = config['gcs_config']['LOGS_FILE_PATH'] 18 | GS_TENSORBOARD_ROOT_LOGS_DIR = config['gcs_config']['TENSORBOARD_ROOT_LOGS_DIR'] 19 | GS_MODEL_EVAL_DIR = config['gcs_config']['MODEL_EVAL_DIR'] 20 | GS_CHECKPOINTS_DIR = config['gcs_config']['CHECKPOINTS_DIR'] 21 | 22 | UNTRAINED_MODEL = config['artifacts']['UNTRAINED_MODEL_FILE_PATH'] 23 | TRAINED_MODEL_FILE_PATH = config['artifacts']['TRAINED_MODEL_FILE_PATH'] 24 | BLESSED_MODEL_FILE_PATH = config['artifacts']['BLESSED_MODEL_FILE_PATH'] 25 | LOGS_DIR = config['logs']['LOGS_DIR'] 26 | LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 27 | TENSORBOARD_ROOT_LOGS_DIR = config['logs']['TENSORBOARD_ROOT_LOGS_DIR'] 28 | MODEL_EVAL_DIR = config['artifacts']['MODEL_EVAL_DIR'] 29 | CHECKPOINTS_DIR = config['artifacts']['CHECKPOINTS_DIR'] 30 | ARTIFACTS = config['artifacts']['ARTIFACTS_DIR'] 31 | 32 | class CloudSync: 33 | def __init__(self): 34 | self.gcs = BucketGCP(bucket_name = BUCKET_NAME) 35 | 36 | def sync_local_data_dir_to_gcs(self): 37 | cmd = f"gsutil -m cp -R {LOCAL_DATA_DIR} gs://{GCS_DATA_DIR}" 38 | os.system(cmd) 39 | 40 | def sync_gcs_to_local_data_dir(self): 41 | cmd = f"gsutil -m cp -R gs://{GCS_DATA_DIR}/dataset ./" 42 | os.system(cmd) 43 | 44 | def sync_tensorboard_logs_dir_to_gcs(self): 45 | cmd = f"gsutil -m cp -R {TENSORBOARD_ROOT_LOGS_DIR} gs://{GS_LOGS_DIR}" 46 | os.system(cmd) 47 | 48 | def sync_gcs_to_tensorboard_logs_dir(self): 49 | cmd = f"gsutil -m cp -R gs://{GS_TENSORBOARD_ROOT_LOGS_DIR} {LOGS_DIR}" 50 | os.system(cmd) 51 | 52 | def sync_model_eval_dir_to_gcs(self): 53 | cmd = f"gsutil -m cp -R {MODEL_EVAL_DIR} gs://{GS_ARTIFACTS}" 54 | os.system(cmd) 55 | 56 | def sync_checkpoints_dir_to_gcs(self): 57 | cmd = f"gsutil -m cp -R {CHECKPOINTS_DIR} gs://{GS_ARTIFACTS}" 58 | os.system(cmd) 59 | 60 | def sync_gcs_to_checkpoints_dir(self): 61 | cmd = f"gsutil -m cp -R gs://{GS_CHECKPOINTS_DIR} {ARTIFACTS}" 62 | os.system(cmd) 63 | 64 | def upload_untrained_full_model(self): 65 | self.gcs.upload_file(UNTRAINED_MODEL, GS_UNTRAINED_MODEL) 66 | 67 | def upload_trained_model(self): 68 | self.gcs.upload_file(TRAINED_MODEL_FILE_PATH, GS_TRAINED_MODEL) 69 | 70 | def upload_blessed_model(self): 71 | self.gcs.upload_file(BLESSED_MODEL_FILE_PATH, GS_BLESSED_MODEL) 72 | 73 | def upload_logs(self): 74 | self.gcs.upload_file(LOGS_FILE_PATH, GS_LOGS_FILE_PATH) 75 | 76 | 77 | def download_untrained_model(self): 78 | self.gcs.download_file(GS_UNTRAINED_MODEL, UNTRAINED_MODEL) 79 | 80 | def download_trained_model(self): 81 | self.gcs.download_file(GS_TRAINED_MODEL, TRAINED_MODEL_FILE_PATH) 82 | 83 | def download_blessed_model(self): 84 | self.gcs.download_file(GS_BLESSED_MODEL, BLESSED_MODEL_FILE_PATH) 85 | 86 | def download_logs(self): 87 | self.gcs.download_file(GS_LOGS_FILE_PATH, LOGS_FILE_PATH) 88 | -------------------------------------------------------------------------------- /src/config/.gitignore: -------------------------------------------------------------------------------- 1 | credentials.json -------------------------------------------------------------------------------- /src/config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/src/config/.gitkeep -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | from cryptography.fernet import Fernet 4 | 5 | load_dotenv() 6 | key=os.environ['JSON_DCRYPT_KEY'] 7 | CREDENTIALS_PATH = os.path.join("src", "config", "credentials.json") 8 | CRED_TXT = os.path.join("src", "config", "cred.txt") 9 | 10 | fernet = Fernet(key) 11 | 12 | with open(CRED_TXT, 'rb') as f: 13 | encrypted = f.read() 14 | decrypted = fernet.decrypt(encrypted) 15 | 16 | with open(CREDENTIALS_PATH, 'wb') as json_file: 17 | json_file.write(decrypted) 18 | 19 | try: 20 | os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = CREDENTIALS_PATH 21 | except Exception as e: 22 | raise e -------------------------------------------------------------------------------- /src/config/cred.txt: -------------------------------------------------------------------------------- 1 | gAAAAABj8tGF1tguDj5ju1EF9ZfK2zh7PzRSVdzTc0-SfauFVuWPXw84xMZ_axmoS58V5Hxprj6-YFxcpoKquyX33HY04BYvUb_LG5EkaY_bgCZcNJ-ERLZsg6F1ouOkSQ0GM06CD2alclHCLOB6S_u_ss3WtM3yy0uLA8kgLkKmerB3uaKiNn2w8J-OEZbRQ2dcAAJqPoQY_T2v4wnOjQaqYQ_XMV7ohg4c7bwMNM8IXozn9zhcVdxRMhi1BX37i2RseWxYYiL_ZqFAXDL0zWLZlP6TSNWfTAbStrmoSxte2VPMn_764DV9-ATTW1F84Ddv7sFFTe6Q-hc4zEEnljOQcdFhfK5p2hFHiSqA6IhHJGy-lqWyFJG-LCMzKdUKTeyNKmNvEAxh96B6jz4qVScWws8Z3fzZbeRTQhxOQBv21qPnKZKIDY7RD96S9x9zKwTxMquGvRt_CNMt945mcfUC6xC-qxft3Pry-RRwwd2PhgPC9sQyYQXMrcN4h0WEDaFJuYWtgMzRVoUbgWYU9BQABPrqSJD7rX_NNufeM46ywpUVenY2lv6Vcf1-RCtdcWiaZv8uPfzx-BVBwuo1SsSnGDZfbeOR_k44jjlFizXdBdUy4rPpSxc8kR9_f1L7ffbuINk3xkHnQymSI6b6xW9D6JOFOOSF_mGDgwwWTn2t863SgvrhmB6rvMiHGAm1G0Ocy7rqlXQy5rC4OIeRKJN9OuPqeUV3xvMndAaPm9oyBWSwduJibNvxqLKTBMEdM-rMMpafob1qR6xyeohU3_WcopHmwoyxWsRgT0C_d4SxziJvCXR4UBRUB_nsHdBrx1S1DHNQPWBYI-mXUEYVbFaq-aCKDAxINQETzuLmV-y_bngfKnxKJW12mZIiVEc-kNO9g-1LnlCI1jkV5wOu_Mp6kbBKVCBFevQUj7XxXN8syuT5JsexGgqBy81nX3szcUwe1PXkXhfpcn2s6iKrA9vxZpmeoeruCovmH8qmQAfVfgNzasqBwHwxsQZ3cp6Gaguim8jZkuk5T-6IynQ65yPyJzCIwC73c5wJEv3ROYwxE2kvZIZxUSyKfvOYDYQPzxJ-OCoFeKfX3UvwoVnAkVvEmvV48NOnkvgwV8IBjmw9Da3DkgaZjwqR7aJhiuPeq_Ph_-dfXbGsTpMX2E7-EieH6oxKZTKpAreVEGN0ZUZJbg7shKXDUwwgcDqEelrESTjid_Md2H9J5hlP7sm5YT8v4LCdYjxpmfF5VVI51YssTzUjh3Jpwte1XNGZfmCDnQp1jaS1gNGVnNPR8D3PKhxjf69WErp7AQvtEPDbG9_CID8RIjDA2eJtlzZc5hBU5Uoisjvzwv8xqbmw3j56lv-_T6mRUrnJQ_hfCS8VCgGQ8ZAJ2gmMp_IwSyCz6h4lQELmimMO45Gesu28OXiqxTPpEC04oBx4O1r9ZWr1ch7vTtU5SVmsfg4yGGWZkCLS43THJFZhUheHIr2BQBWqVLZO8g-EerjWG8xKH6V8c3NQSn_klHflk9p6hp9Jd7xDoVpgYVmk1m77_07IR9HFmGQ-Xy0nrBWicXA7qGP3pEG0-22Mv09gBmHLjZzBDXfGzoVD8nFRwpkd4uvN5DmnLHLAEQiFdU8rjkCQjPIB9V1E58IAH_ZuMnnsjTtCo5MC4w7Gzkq8tebzsPg-KNXecnVChRmyKk50WZRdeV6Y4q8t5x_DUDj5WeWH4UX-JDNJpOL07qUgeDW4GbX2vxCUa32rwiDFu-d_IFPReqIfWOlwCdBAtxfci7KGM5zCJ7ha31KbWK2mcScDOgQkoMQnpXxIWbHCqB8Si3790q0hSz80lAAlBReArkGxJ-R-FRQi_7-LeqNI3n2dCEbUDGoHSLGEuUcqmYb5ud5BenJ-xdCU8bjVzpUdFVRKrzYHlHhMMvIyC6tm8NZI4klPuRE2Xw9Kp0FJf93P4t5P9VBqJDBMUbDR-nQOPZFLWhjNwF0lJwriJrZcIUE9vEQf0o-megbmIsybcyZyrMA4DzZeKRf3lcXa5cWuqeXgOpIgmAIURK37GY64Vvx-6wjgBuxt2ESo-a62v0gEjbYfDsoLIiyHCWEly6YiAzBsqFb_-HnKiFfrLv4MLSIB_34c0UlBoM1yaGOIsenrlbrYPqFXuRnJkqZFSdvjZ_ewc-PlfmtKMvj9V96HSyFfkZ56_aCc7DJYreR6XfD1slmFeaSJTnJJCM9CVAnomV_pueLJEmV00wPugv0h88M2fmDxlNG1Yf5IfDu2Oo4WOE5hY9c_5h-qfm5BXA3Vo_t5XjRb9qcIkLxJ1xdY_ZlLfBc9y3YDxuwKMLmvik_EFvFhtyIDFkDy3OSjgsvMZikDo7iVu_y0ODjLKYoXt8EKPxMuliFuTL-GvH6sxUJXjMJ8US-8u8wInOng2oXgwKyGVr1Mf_RL1Hl8a0RA0LAbuHr1QifKvOdFdS_eZqdAdRJCs4ayaWlNDVHP32zafTWf1LwGmQw6RUXOvZ3KM71xyIDcMU7QPzmpPAjyJ4gLNpRDWiP6X9wFC4Hm5DaYjgWWpSRtK_hp8JnjNSYf-ERmeZoQ7ugo-rt8d1E6cYzPAyvSz5YeBFvfNVHUy8hUzpgIhrtvviBw-t1KRCopqSP7HZ9O9K2qyc8DnsQnfpHaX-26k0uv4ccw330Vqq7hUDRyiN_ca8HO-lPw4lOow5OFkhH5u2Hv5lTRr3UEP6AzyhMENeQFrI6E5ZYkV3rcmRlIbKD6y66g7JmFq-5EOoefrRlHW7rtA7bHL-py0d7y5VNOPonF-A6cd2sdFbXUbx8eltYmVllTlxNTehelpDGxhcGm8kM-ok-h42WKJIbk-fb-jDzpn1kitLBYUqIE2VAs7V9fELNV0u-r1usA2E9pbGPG6RPi409R3Ju6wEPwI6mzQwDMh2HDeqxkwVr7aTidsA1kM6ViOAgfgLNsqpxljiSCS4tHE7hyAIlDsR5clwHPhicBe7_GiAoqOKrZ4GgZEc2Xuwthr_qIhm6BRU0Q3HA1QXQaZSZYo8U-YNku31nHGOBRnkBzQ9IcefL22agt2-m6-pboFL11i1ULkVWClzu4WMK4-jCC6ryq6vJC7t09GJg21g9Pou2ifl7m4sAB_Q6nEekyFZMv9roSBa7YZ7Eop-QRSBWT0seuPFy58AvL9HqFn9iwbjkONPZLrntVcRqB7Pb_-55HTHTOXNpc -------------------------------------------------------------------------------- /src/ml/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/src/ml/.gitkeep -------------------------------------------------------------------------------- /src/ml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/src/ml/__init__.py -------------------------------------------------------------------------------- /src/ml/model.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import io 3 | from typing import Union 4 | from tensorflow.keras.layers import (GlobalAveragePooling2D, Dense, 5 | Activation, Dropout,BatchNormalization) 6 | import keras 7 | from logging import RootLogger 8 | 9 | # config = read_yaml("configs/config.yaml") 10 | # LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 11 | # logger = get_logger(LOGS_FILE_PATH) 12 | 13 | def _get_model_summary(model): 14 | with io.StringIO() as stream: 15 | model.summary( 16 | print_fn = lambda x: stream.write(f"{x}\n") 17 | ) 18 | summary_str = stream.getvalue() 19 | return summary_str 20 | 21 | def get_DenseNet121_model(input_shape : list[int], model_path: str, logger: RootLogger) -> tf.keras.models.Model: 22 | model = tf.keras.applications.densenet.DenseNet121(input_shape = input_shape, 23 | weights = "imagenet", 24 | include_top = False) 25 | 26 | logger.info(f"base model summary: {_get_model_summary(model)}") 27 | model.save(model_path) 28 | logger.info(f"DenseNet121 base model saved at {model_path}") 29 | return model 30 | 31 | def prepare_full_model(base_model: tf.keras.models.Model, 32 | logger: RootLogger, learning_rate: float=0.001, 33 | freeze_all: bool=True, freeze_till: int= None, 34 | activation: str='softmax',CLASSES: int=3, 35 | loss_function: str='categorical_crossentropy', 36 | mertrics: str='accuracy') -> tf.keras.models.Model: 37 | """Prepares the complete transfer learning model architecture 38 | 39 | Args: 40 | base_model (tf.keras.models.Model): SOTA model being used for transfer learning. 41 | learning_rate (float, optional): Learning rate for the model training. Defaults to 0.001. 42 | freeze_all (bool, optional): Freezes all the layers to make them untrainable. Defaults to True. 43 | freeze_till (int, optional): This value defines the extent of layers that should be trained. Defaults to None. 44 | activation (str, optional): Activation function at final layer. Defaults to 'softmax'. 45 | CLASSES (int, optional): Number of target classes. Defaults to 3. 46 | loss_function (str, optional): Function for calculating the loss. Defaults to 'categorical_crossentropy'. 47 | mertrics (str, optional): Metrics to be used for model training and evaluation. Defaults to 'accuracy'. 48 | 49 | Returns: 50 | tf.keras.models.Model: Full model architecture ready to be trained. 51 | """ 52 | 53 | if freeze_all: 54 | for layer in base_model.layers: 55 | layer.trainable = False 56 | elif (freeze_till is not None) and (freeze_till > 0): 57 | for layer in base_model.layers[:freeze_till]: 58 | layer.trainable = False 59 | 60 | ## add our layers to the base model 61 | x = base_model.output 62 | x = GlobalAveragePooling2D()(x) 63 | x = Dense(units=512)(x) 64 | x = BatchNormalization()(x) 65 | x = Activation('relu')(x) 66 | x = Dropout(rate=0.3)(x) 67 | x = Dense(units=128)(x) 68 | x = BatchNormalization()(x) 69 | x = Activation('relu')(x) 70 | x = Dropout(rate=0.2)(x) 71 | x = Dense(units=64)(x) 72 | x = BatchNormalization()(x) 73 | x = Activation('relu')(x) 74 | x = Dense(units=32, activation='relu')(x) 75 | predictions = Dense(CLASSES, activation=activation)(x) 76 | full_model = tf.keras.models.Model(base_model.input, outputs = predictions) 77 | 78 | full_model.compile( 79 | optimizer = tf.keras.optimizers.Adam(learning_rate = learning_rate), 80 | loss = loss_function, 81 | metrics = [mertrics] 82 | ) 83 | 84 | logger.info("Custom model is compiled and ready to be trained") 85 | 86 | logger.info(f"full model summary: {_get_model_summary(full_model)}") 87 | 88 | return full_model 89 | 90 | def load_model(model_path: str, logger: RootLogger) -> tf.keras.models.Model: 91 | model = tf.keras.models.load_model(model_path) 92 | logger.info(f"Model is loaded from {model_path}") 93 | #logger.info(f"untrained full model summary: \n{_get_model_summary(model)}") 94 | return model 95 | 96 | def is_blessed(valid_generator : keras.preprocessing.image.DirectoryIterator, 97 | blessing_threshold: Union[int, float], 98 | trained_model_path: str, 99 | production_model_path:str, 100 | logger: RootLogger) -> bool: 101 | trained_model = load_model(trained_model_path, logger= logger) 102 | production_model = load_model(production_model_path, logger= logger) 103 | 104 | trained_predictions = trained_model.evaluate(valid_generator) 105 | production_prediction = production_model.evaluate(valid_generator) 106 | train_accuracy = round(trained_predictions[1], 3) 107 | production_accuracy = round(production_prediction[1], 3) 108 | logger.info(f"Accuracy with trained model: {train_accuracy}, Accuracy with production model: {production_accuracy}") 109 | if train_accuracy - production_accuracy >= blessing_threshold: 110 | return True 111 | else: 112 | return False -------------------------------------------------------------------------------- /src/stage_01_get_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from src.utils.common import read_yaml 3 | from src.cloud_sync import CloudSync 4 | from src.utils.app_logging import get_logger 5 | 6 | STAGE = "Load Data" 7 | 8 | def get_data(): 9 | 10 | logger.info("Loading data from the source...") 11 | cloud_sync.sync_gcs_to_local_data_dir() 12 | logger.info("Data has been saved locally") 13 | 14 | if __name__ == '__main__': 15 | args = argparse.ArgumentParser() 16 | args.add_argument("--config", "-c", default="configs/config.yaml") 17 | parsed_args = args.parse_args() 18 | cloud_sync = CloudSync() 19 | config = read_yaml(parsed_args.config) 20 | LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 21 | logger = get_logger(LOGS_FILE_PATH) 22 | try: 23 | logger.info("\n********************") 24 | logger.info(f'>>>>> stage "{STAGE}" started <<<<<') 25 | get_data() 26 | #cloud_sync.upload_logs() 27 | logger.info(f'>>>>> stage "{STAGE}" completed!<<<<<\n') 28 | except Exception as e: 29 | cloud_sync.upload_logs() 30 | logger.exception(e) 31 | raise e -------------------------------------------------------------------------------- /src/stage_02_prepare_model.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from src.utils.common import read_yaml 3 | from src.ml.model import get_DenseNet121_model , prepare_full_model 4 | from src.cloud_sync import CloudSync 5 | from utils.app_logging import get_logger 6 | 7 | STAGE = "Prepare Model" 8 | 9 | def prepare_model(config_path, params_path): 10 | config = read_yaml(config_path) 11 | params = read_yaml(params_path) 12 | 13 | base_model_path = config['artifacts']['BASE_MODEL_FILE_PATH'] 14 | untrained_model_path = config['artifacts']['UNTRAINED_MODEL_FILE_PATH'] 15 | inpute_shape = params['IMAGE_SIZE'] 16 | classes = params['CLASSES'] 17 | learning_rate = params['LEARNING_RATE'] 18 | freeze_all = params['FREEZE_ALL'] 19 | freeze_till = params['FREEZE_TILL'] 20 | loss_function = params['LOSS_FUNCTION'] 21 | metrics = params['METRICS'] 22 | 23 | base_model = get_DenseNet121_model(input_shape = inpute_shape, 24 | model_path = base_model_path, 25 | logger= logger) 26 | logger.info("Downloaded the base model") 27 | logger.info("Preparing the complete model...") 28 | full_model = prepare_full_model( 29 | base_model = base_model, 30 | logger= logger, 31 | learning_rate= learning_rate, 32 | freeze_all= freeze_all, 33 | freeze_till= freeze_till, 34 | CLASSES = classes, 35 | loss_function= loss_function, 36 | mertrics= metrics) 37 | 38 | full_model.save(untrained_model_path) 39 | logger.info(f'Full untrained model is saved at {untrained_model_path}') 40 | 41 | if __name__ == '__main__': 42 | args = argparse.ArgumentParser() 43 | args.add_argument("--config", "-c", default="configs/config.yaml") 44 | args.add_argument("--params", "-p", default="params.yaml") 45 | parsed_args = args.parse_args() 46 | cloud_sync = CloudSync() 47 | #cloud_sync.download_logs() 48 | config = read_yaml(parsed_args.config) 49 | LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 50 | logger = get_logger(LOGS_FILE_PATH) 51 | 52 | try: 53 | logger.info("\n********************") 54 | logger.info(f">>>>> stage {STAGE} started <<<<<") 55 | prepare_model(config_path=parsed_args.config, params_path=parsed_args.params) 56 | cloud_sync.upload_untrained_full_model() 57 | logger.info(f">>>>> stage {STAGE} completed!<<<<<\n") 58 | except Exception as e: 59 | cloud_sync.upload_logs() 60 | logger.exception(e) 61 | raise e -------------------------------------------------------------------------------- /src/stage_03_train_evaluate.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from src.utils.common import read_yaml 3 | import tensorflow as tf 4 | from aipilot.tf.cv import DataPrep 5 | import keras 6 | from aipilot.tf import Devices, callbacks, Evaluator 7 | from ml.model import load_model 8 | import pandas as pd 9 | from utils.app_logging import get_logger 10 | from src.cloud_sync import CloudSync 11 | import matplotlib.pyplot as plt 12 | from datetime import datetime 13 | from pathlib import Path 14 | import mlflow 15 | from utils import MLFlowManager 16 | 17 | STAGE = "Training & Evaluation" 18 | 19 | def train_evaluate(config_path, params_path): 20 | 21 | config = read_yaml(config_path) 22 | params = read_yaml(params_path) 23 | 24 | local_data_dir = config['local_data']['DATA_DIR'] 25 | tensorboard_log_dir = config['logs']['TENSORBOARD_ROOT_LOGS_DIR'] 26 | checkpoint_file_path = config['artifacts']['CHECKPOINT_FILE_PATH'] 27 | untrained_model_path = config['artifacts']['UNTRAINED_MODEL_FILE_PATH'] 28 | trained_model_path = config['artifacts']['TRAINED_MODEL_FILE_PATH'] 29 | eval_metrics_plot_file_path = config['artifacts']['MODEL_EVALUATION_PLOT'] 30 | confusion_metrix_plot_file_path = config['artifacts']['CONFUSION_MATRIX_PLOT_FILE_PATH'] 31 | 32 | Path(checkpoint_file_path).parent.absolute().mkdir(parents=True, exist_ok=True) 33 | Path(eval_metrics_plot_file_path).parent.absolute().mkdir(parents=True, exist_ok=True) 34 | 35 | val_split = params['VALIDATION_SPLIT_SIZE'] 36 | batch_size = params['BATCH_SIZE'] 37 | data_augmentation = params['DATA_AUGMENTATION'] 38 | early_stopping_patience = params['EARLY_STOPPING_PATIENCE'] 39 | learning_rate_patience = params['LEARNING_RATE_PATIENCE'] 40 | epochs = params['EPOCHS'] 41 | train_from_checkpoint = params['TRAIN_FORM_CHECKPOINT'] 42 | 43 | devices = Devices() 44 | devices.gpu_device 45 | 46 | mlflow_service = MLFlowManager() 47 | experiment_name = config['mlflow']['EXPERIMENT_NAME'] 48 | mlflow_model_name = config['mlflow']['MODEL_NAME'] 49 | experiment_id = mlflow_service.get_or_create_an_experiment(experiment_name) 50 | 51 | timestamp = datetime.now().strftime("%Y%m%d%H%M%S") 52 | run_name = config['mlflow']['RUN_ID_PREFIX'] + "-" + timestamp 53 | 54 | mlflow.tensorflow.autolog(silent=False, log_models = True, 55 | registered_model_name=mlflow_model_name) 56 | data_ops = DataPrep(local_data_dir) 57 | 58 | custom_data_augmentation = tf.keras.preprocessing.image.ImageDataGenerator(rotation_range=15, 59 | horizontal_flip=True, 60 | rescale=1./255, 61 | validation_split= 0.2) 62 | train_generator, valid_generator = data_ops.data_generators( val_split=val_split, 63 | batch_size= batch_size, 64 | data_augmentation= data_augmentation, 65 | augmentation_strategy= custom_data_augmentation) 66 | steps_per_epoch = train_generator.samples // train_generator.batch_size 67 | validation_steps = valid_generator.samples // valid_generator.batch_size 68 | logger.info("Successfully created the data generators") 69 | (early_stopping_cb, checkpointing_cb, 70 | tensorboard_cb, reduce_on_plateau_cb) = callbacks( 71 | model_ckpt_file_path = checkpoint_file_path, 72 | tensorboard_logs_dir = tensorboard_log_dir, 73 | es_patience = early_stopping_patience, 74 | lr_patience = learning_rate_patience 75 | ) 76 | logger.info("Callbacks initialized...") 77 | if train_from_checkpoint: 78 | model = keras.models.load_model(checkpoint_file_path) 79 | logger.info("Started model training from the checkpoint") 80 | else: 81 | model = load_model(untrained_model_path, logger=logger) 82 | logger.info("Started model training from scratch") 83 | history = [] 84 | with mlflow.start_run(experiment_id=experiment_id, run_name=run_name): 85 | h = model.fit( 86 | train_generator, 87 | validation_data = valid_generator, 88 | epochs=epochs, 89 | steps_per_epoch=steps_per_epoch, 90 | validation_steps=validation_steps, 91 | callbacks = [tensorboard_cb , early_stopping_cb, checkpointing_cb, reduce_on_plateau_cb] 92 | ) 93 | logger.info("Model training completed successfully") 94 | history.append(h) 95 | 96 | history_df = pd.DataFrame() 97 | for h in history: 98 | history_df = pd.concat([history_df, pd.DataFrame.from_records(h.history)]) 99 | fig = history_df.reset_index(drop=True).plot(figsize= (8,5)).get_figure() 100 | fig.savefig(eval_metrics_plot_file_path) 101 | plt.clf() 102 | logger.info(f"Evaluation Metrics plot has been saved to : {eval_metrics_plot_file_path}") 103 | model.save(trained_model_path) 104 | logger.info(f"Trained Model saved to : {trained_model_path}") 105 | 106 | eval= Evaluator(model, train_generator, valid_generator) 107 | matrix_plot = eval.confusion_matrix() 108 | matrix_plot.savefig(confusion_metrix_plot_file_path) 109 | logger.info(f"Confusion Matrix Plot saved to : {confusion_metrix_plot_file_path}") 110 | 111 | if __name__ == '__main__': 112 | args = argparse.ArgumentParser() 113 | args.add_argument("--config", "-c", default="configs/config.yaml") 114 | args.add_argument("--params", "-p", default="params.yaml") 115 | parsed_args = args.parse_args() 116 | cloud_sync = CloudSync() 117 | #cloud_sync.download_logs() 118 | cloud_sync.download_untrained_model() 119 | config = read_yaml(parsed_args.config) 120 | LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 121 | logger = get_logger(LOGS_FILE_PATH) 122 | 123 | try: 124 | logger.info("\n********************") 125 | logger.info(f'>>>>> stage "{STAGE}" started <<<<<') 126 | train_evaluate(config_path=parsed_args.config, params_path=parsed_args.params) 127 | cloud_sync.upload_trained_model() 128 | cloud_sync.sync_checkpoints_dir_to_gcs() 129 | cloud_sync.sync_model_eval_dir_to_gcs() 130 | cloud_sync.sync_tensorboard_logs_dir_to_gcs() 131 | logger.info(f'>>>>> stage "{STAGE}" completed!<<<<<\n') 132 | #cloud_sync.upload_logs() 133 | except Exception as e: 134 | logger.exception(e) 135 | cloud_sync.upload_logs() 136 | raise e -------------------------------------------------------------------------------- /src/stage_04_model_blessing.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from src.utils.common import read_yaml 3 | import tensorflow as tf 4 | from aipilot.tf.cv import DataPrep 5 | from aipilot.tf import Devices 6 | from ml.model import load_model 7 | from src.utils.app_logging import get_logger 8 | from src.cloud_sync import CloudSync 9 | from src.ml.model import is_blessed 10 | from utils.mlflow_ops import MLFlowManager 11 | import sys 12 | 13 | STAGE = "Model Blessing" 14 | 15 | 16 | def model_blessing(config_path, params_path): 17 | # read config files 18 | config = read_yaml(config_path) 19 | params = read_yaml(params_path) 20 | 21 | local_data_dir = config['local_data']['DATA_DIR'] 22 | blessing_threshold_comparision = params['BLESSING_THRESHOLD_COMPARISION'] 23 | model_blessing_threshold = params['MODEL_BLESSING_THRESHOLD'] 24 | trained_model_path = config['artifacts']['TRAINED_MODEL_FILE_PATH'] 25 | blessed_model_path = config['artifacts']['BLESSED_MODEL_FILE_PATH'] 26 | 27 | val_split = params['VALIDATION_SPLIT_SIZE'] 28 | batch_size = params['BATCH_SIZE'] 29 | data_augmentation = params['DATA_AUGMENTATION'] 30 | 31 | devices = Devices() 32 | devices.gpu_device 33 | data_ops = DataPrep(local_data_dir) 34 | 35 | mlflow_service = MLFlowManager() 36 | mlflow_model_name = config['mlflow']['MODEL_NAME'] 37 | 38 | custom_data_augmentation = tf.keras.preprocessing.image.ImageDataGenerator(rotation_range=15, 39 | horizontal_flip=True, 40 | rescale=1./255, 41 | validation_split=0.2) 42 | train_generator, valid_generator = data_ops.data_generators(val_split=val_split, 43 | batch_size=batch_size, 44 | data_augmentation=data_augmentation, 45 | augmentation_strategy=custom_data_augmentation) 46 | 47 | logger.info("Successfully created the data generators") 48 | 49 | proceed_blessing = is_blessed(valid_generator, 50 | model_blessing_threshold, 51 | trained_model_path, 52 | blessed_model_path, 53 | logger=logger) 54 | if blessing_threshold_comparision: 55 | if not proceed_blessing: 56 | logger.info( 57 | "Current model is not better than the production model, terminating the pipeline...") 58 | cloud_sync.upload_logs() 59 | sys.exit(1) 60 | 61 | logger.info( 62 | "Current model is better than the production model, proceeding with model blessing...") 63 | 64 | logger.info("Performing pre-blessing model validations...") 65 | model = load_model(trained_model_path, logger=logger) 66 | predictions = model.evaluate(valid_generator) 67 | 68 | if isinstance(predictions, list) and len(predictions) == 2 and\ 69 | isinstance(predictions[0], float) and isinstance(predictions[1], float): 70 | logger.info("Model has been validated successfully") 71 | model.save(blessed_model_path) 72 | logger.info( 73 | f"Model has been blessed and saved at : {blessed_model_path}") 74 | 75 | latest_model_version = mlflow_service.latest_model_version(model_name=mlflow_model_name) 76 | mlflow_service.transition_model_version_stage( 77 | model_name=mlflow_model_name, model_version=latest_model_version, stage="Production") 78 | logger.info( 79 | f"Model latest version {latest_model_version} has been transitioned to MLFlow Production") 80 | cloud_sync.upload_blessed_model() 81 | 82 | else: 83 | logger.info( 84 | "Skipped model blessing as the threshold comparison has been set to 'False'") 85 | cloud_sync.upload_logs() 86 | sys.exit(0) 87 | 88 | 89 | if __name__ == '__main__': 90 | args = argparse.ArgumentParser() 91 | args.add_argument("--config", "-c", default="configs/config.yaml") 92 | args.add_argument("--params", "-p", default="params.yaml") 93 | parsed_args = args.parse_args() 94 | cloud_sync = CloudSync() 95 | cloud_sync.download_trained_model() 96 | cloud_sync.download_blessed_model() 97 | config = read_yaml(parsed_args.config) 98 | LOGS_FILE_PATH = config['logs']['RUNNING_LOGS_FILE_PATH'] 99 | logger = get_logger(LOGS_FILE_PATH) 100 | 101 | try: 102 | logger.info("\n********************") 103 | logger.info(f'>>>>> stage "{STAGE}" started <<<<<') 104 | model_blessing(config_path=parsed_args.config, 105 | params_path=parsed_args.params) 106 | logger.info(f'>>>>> stage "{STAGE}" completed!<<<<<\n') 107 | cloud_sync.upload_logs() 108 | except Exception as e: 109 | logger.exception(e) 110 | cloud_sync.upload_logs() 111 | raise e 112 | -------------------------------------------------------------------------------- /src/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hassi34/brain-tumor-classification/ddab3834e85a82a38b2bfd6b0fb1bd7096f1da73/src/utils/.gitkeep -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .mlflow_ops import MLFlowManager 2 | -------------------------------------------------------------------------------- /src/utils/app_logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | 4 | def get_logger(logsFilePath: str, maxBytes: int=10240000000000, backupCount: int=0) -> object: 5 | logger = logging.getLogger() 6 | fh = logging.handlers.RotatingFileHandler(logsFilePath, maxBytes=maxBytes, backupCount=backupCount) 7 | fh.setLevel(logging.DEBUG)#no matter what level I set here 8 | formatter = logging.Formatter("[%(asctime)s - %(levelname)s - %(name)s - %(module)s - %(lineno)s] : %(message)s") 9 | fh.setFormatter(formatter) 10 | logger.addHandler(fh) 11 | logger.setLevel(logging.INFO) 12 | return logger 13 | -------------------------------------------------------------------------------- /src/utils/common.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import base64 3 | 4 | def read_yaml(path_to_yaml: str) -> dict: 5 | with open(path_to_yaml) as yaml_file: 6 | content = yaml.safe_load(yaml_file) 7 | return content 8 | 9 | def decode_base64_image(img_string, file_name): 10 | img_data = base64.b64decode(img_string) 11 | with open("./"+file_name, 'wb') as f: 12 | f.write(img_data) 13 | f.close() 14 | 15 | def encode_image_into_base64(img_path): 16 | with open(img_path, "rb") as f: 17 | return base64.b64encode(f.read()).decode("utf-8") -------------------------------------------------------------------------------- /src/utils/download_blessed_model.py: -------------------------------------------------------------------------------- 1 | 2 | from src.cloud_sync import CloudSync 3 | 4 | 5 | def download_production_model(): 6 | 7 | cloud_sync = CloudSync() 8 | cloud_sync.download_blessed_model() 9 | 10 | if __name__ == '__main__': 11 | 12 | download_production_model() 13 | -------------------------------------------------------------------------------- /src/utils/gcp_bucket.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author : Hasanain Mehmood 3 | Contact : hasanain@aicailber.com 4 | """ 5 | 6 | from google.cloud import storage 7 | import json 8 | import pandas as pd 9 | 10 | class BucketGCP: 11 | def __init__(self, bucket_name = None): 12 | self.client = storage.Client() 13 | self.project_id = self.client.project 14 | if bucket_name is None: 15 | try: 16 | self.bucket_name = self.project_id 17 | except Exception as e: 18 | print( 19 | "Could not make the client session with default bucket name as the project name. Please provide the bucket name manually") 20 | raise e 21 | else: 22 | self.bucket_name = bucket_name 23 | 24 | def is_exists_bucket(self, bucket_name: str=None): 25 | if bucket_name is None: 26 | bucket_name = self.bucket_name 27 | return True if self.client.bucket(bucket_name).exists() else False 28 | 29 | def is_exists_blob(self, filename, bucket_name: str=None): 30 | if bucket_name is None: 31 | bucket_name = self.bucket_name 32 | if self.is_exists_bucket(): 33 | bucket = self.client.get_bucket(bucket_name) 34 | blob = bucket.blob(filename) 35 | return blob.exists() 36 | else: 37 | print(f'Specified bucket "{self.bucket_name}" does not exist, please create a bucket first') 38 | 39 | def create_bucket(self, storage_class: str="COLDLINE", location: str="US", bucket_name: str=None): 40 | """Creates a new bucket with the specified storage class and location 41 | 42 | Args: 43 | storage_class (str, optional): Storage class. Defaults to "COLDLINE".Available Selections = ["STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE"] 44 | location (str, optional): Storage location. Defaults to "US".Available Selections = ["ASIA", "EU", "US"] 45 | """ 46 | if bucket_name is None: 47 | bucket_name = self.bucket_name 48 | 49 | if not self.is_exists_bucket(bucket_name): 50 | bucket = self.client.bucket(bucket_name) 51 | bucket.storage_class = storage_class 52 | new_bucket = self.client.create_bucket(bucket, location=location) 53 | print(f'Created bucket "{new_bucket.name}" in "{new_bucket.location}" with storage class "{new_bucket.storage_class}"') 54 | else: 55 | print( 56 | f'Bucket "{bucket_name}" already exists in project "{self.project_id}"') 57 | print('Skipping bucket creation...') 58 | 59 | def enable_versioning(self, bucket_name: str=None): 60 | if bucket_name is None: 61 | bucket_name = self.bucket_name 62 | 63 | if self.is_exists_bucket(bucket_name): 64 | bucket = self.client.get_bucket(bucket_name) 65 | bucket.versioning_enabled = True 66 | bucket.patch() 67 | print(f'Object versioning for Bucket "{bucket.name}" has been enabled') 68 | else: 69 | print(f'Specified bucket "{bucket_name}" does not exist, please create a bucket first') 70 | 71 | def disable_versioning(self, bucket_name: str=None): 72 | print(bucket_name) 73 | if bucket_name is None: 74 | bucket_name = self.bucket_name 75 | 76 | if self.is_exists_bucket(bucket_name): 77 | bucket = self.client.get_bucket(bucket_name) 78 | bucket.versioning_enabled = False 79 | bucket.patch() 80 | print(f'Object versioning for Bucket "{bucket.name}" has been disabled') 81 | else: 82 | print(f'Specified bucket "{bucket_name}" does not exist, please create a bucket first') 83 | 84 | def list_blobs(self, bucket_name: str=None)-> list: 85 | if bucket_name is None: 86 | bucket_name = self.bucket_name 87 | 88 | if self.is_exists_bucket(bucket_name): 89 | try: 90 | blobs = self.client.list_blobs(bucket_name) 91 | return [blob.name for blob in blobs] 92 | except Exception: 93 | print(f'There are not blobs available in "{bucket_name}"') 94 | else: 95 | print(f'Specified bucket "{bucket_name}" does not exist, please create a bucket first') 96 | 97 | def delete_blob(self, blob_name: str, alert: bool=True, bucket_name: str=None): 98 | if bucket_name is None: 99 | bucket_name = self.bucket_name 100 | if alert: 101 | usr_rsp = input(f'>>> "{blob_name}" will be permanently deleted from {bucket_name}. Type "Yes" if you want to delete else "No" :') 102 | if usr_rsp.title() == "Yes": 103 | bucket = self.client.bucket(bucket_name) 104 | blob = bucket.blob(blob_name) 105 | if blob.exists(): 106 | blob.delete() 107 | print(f'Deleted blob "{blob_name}".') 108 | else: 109 | print(f'Blob "{blob_name}" does not exists') 110 | 111 | elif usr_rsp.title() == "Yes": 112 | print('Did not Delete the blob.') 113 | else: 114 | print("!!!Invalid Response!!!") 115 | 116 | else: 117 | bucket = self.client.bucket(bucket_name) 118 | blob = bucket.blob(blob_name) 119 | if blob.exists(): 120 | blob.delete() 121 | print(f'Deleted blob "{blob_name}".') 122 | else: 123 | print(f'Blob "{blob_name}" does not exists') 124 | 125 | def get_csv(self, filename:str, bucket_name: str=None)-> object: 126 | if bucket_name is None: 127 | bucket_name = self.bucket_name 128 | return pd.read_csv('gs://' + bucket_name + '/' + filename, encoding='UTF-8') 129 | 130 | def get_excel(self, filename:str, bucket_name: str=None)-> object: 131 | if bucket_name is None: 132 | bucket_name = self.bucket_name 133 | return pd.read_csv('gs://' + bucket_name + '/' + filename, encoding='UTF-8') 134 | 135 | def get_file_text(self, filename:str, bucket_name: str=None)-> str: 136 | if bucket_name is None: 137 | bucket_name = self.bucket_name 138 | bucket = self.client.bucket(bucket_name) 139 | blob = bucket.blob(filename) 140 | if blob.exists(): 141 | data = blob.download_as_string() 142 | return data 143 | else: 144 | print(f'"{filename}" does not exist') 145 | 146 | def get_json(self, filename:str, bucket_name: str=None)-> str: 147 | if bucket_name is None: 148 | bucket_name = self.bucket_name 149 | bucket = self.client.bucket(bucket_name) 150 | blob = bucket.blob(filename) 151 | if blob.exists(): 152 | data = json.loads(blob.download_as_string()) 153 | return data 154 | else: 155 | print(f'"{filename}" does not exist') 156 | 157 | def upload_file(self, source_file_name:str, destination_blob_name:str, bucket_name: str=None): 158 | if bucket_name is None: 159 | bucket_name = self.bucket_name 160 | try: 161 | bucket = self.client.get_bucket(bucket_name) 162 | blob = bucket.blob(destination_blob_name) 163 | blob.upload_from_filename(source_file_name) 164 | print(f'File "{source_file_name}" uploaded to "{destination_blob_name}".') 165 | except Exception as e: 166 | print(f'Could not upload "{source_file_name}" to "{destination_blob_name}".') 167 | raise e 168 | 169 | def download_file(self, source_blob_name:str, destination_file_name:str, bucket_name: str=None): 170 | if bucket_name is None: 171 | bucket_name = self.bucket_name 172 | try: 173 | bucket = self.client.get_bucket(bucket_name) 174 | blob = bucket.blob(source_blob_name) 175 | blob.download_to_filename(destination_file_name) 176 | print(f'File "{source_blob_name}" downloaded to "{destination_file_name}".') 177 | except Exception as e: 178 | print(f'Could not download "{source_blob_name}" to "{destination_file_name}".') 179 | raise e 180 | 181 | def empty_out_bucket(self, bucket_name: str=None): 182 | if bucket_name is None: 183 | bucket_name = self.bucket_name 184 | if self.is_exists_bucket(bucket_name): 185 | blobs = self.client.list_blobs(bucket_name) 186 | for blob in blobs: 187 | print(f'Deleting file {blob.name}...') 188 | blob.delete() 189 | else: 190 | print(f'Bucket "{bucket_name}" Does Not exist') 191 | 192 | def dlt_bucket(self, bucket_name: str=None, alert: bool=True): 193 | if bucket_name is None: 194 | bucket_name = self.bucket_name 195 | 196 | if alert: 197 | usr_rsp = input(f'>>> Storage bucket "{bucket_name}" and all of its data will be permanently deleted. Type "Yes" if you want to delete else "No" :') 198 | if usr_rsp.title() == "Yes": 199 | self.empty_out_bucket(bucket_name) 200 | bucket = self.client.get_bucket(bucket_name) 201 | bucket.delete() 202 | print(f'Deleted "{bucket_name}".') 203 | elif usr_rsp.title() == "Yes": 204 | print('Did not delete the bucket') 205 | else: 206 | print("!!!Invalid Response!!!") 207 | else: 208 | self.empty_out_bucket(bucket_name) 209 | bucket = self.client.get_bucket(bucket_name) 210 | bucket.delete() 211 | print(f'Deleted "{bucket_name}".') 212 | 213 | def list_buckets(self): 214 | return [bucket for bucket in self.client.list_buckets()] 215 | -------------------------------------------------------------------------------- /src/utils/mlflow_ops.py: -------------------------------------------------------------------------------- 1 | import mlflow 2 | from dotenv import load_dotenv 3 | from mlflow.sklearn import load_model 4 | import os 5 | load_dotenv() 6 | 7 | MLFLOW_TRACKING_URI= os.environ["MLFLOW_TRACKING_URI"] 8 | os.environ["MLFLOW_TRACKING_USERNAME"] 9 | os.environ["MLFLOW_TRACKING_PASSWORD"] 10 | mlflow.set_tracking_uri(MLFLOW_TRACKING_URI) 11 | 12 | class MLFlowManager: 13 | def __init__(self): 14 | if mlflow.tracking.is_tracking_uri_set(): 15 | self.client = mlflow.MlflowClient() 16 | else: 17 | raise Exception("Tracking URI not set") 18 | 19 | def get_or_create_an_experiment(self, experiment_name): 20 | exp = mlflow.get_experiment_by_name(experiment_name) 21 | if exp is None: 22 | exp_id = mlflow.create_experiment(experiment_name) 23 | return exp_id 24 | return exp.experiment_id 25 | 26 | def latest_model_version(self, model_name) -> int: 27 | return self.client.get_latest_versions(model_name)[0].version 28 | 29 | @property 30 | def get_latest_version_model_uri(self, model_name) -> str: 31 | model_uri = f"models:/{model_name}/{self.latest_model_version(model_name)}" 32 | return model_uri 33 | 34 | def load_latest_model_version(self, model_name): 35 | return load_model(self.get_latest_version_model_uri(model_name)) 36 | 37 | def get_best_run_id_and_model_uri(self, experiment_id: str, metric_name: str="metrics.mae", ascending = True ): 38 | runs = mlflow.search_runs(f"{experiment_id}") 39 | runs = runs.dropna(subset=['tags.mlflow.log-model.history']) 40 | runs.sort_values(by=[metric_name], ascending = ascending, inplace = True) 41 | runs.to_csv('mlflow.csv', index=False) 42 | runs.reset_index(inplace= True, drop = True) 43 | best_run_id = runs['run_id'][0] 44 | 45 | best_run = runs[runs["run_id"]==best_run_id] 46 | artifact_uri = best_run['artifact_uri'][0] 47 | 48 | logged_model_dir = best_run['tags.mlflow.log-model.history'][0].split(',')[1:2] 49 | logged_model_dir = logged_model_dir[0].strip().split(':')[1].replace('"', '').strip() 50 | 51 | model_uri = str(artifact_uri)+"/"+str(logged_model_dir) 52 | 53 | return best_run_id, model_uri 54 | 55 | def print_registered_model(self, model_name): 56 | for model in self.client.search_registered_models(filter_string = f"name LIKE {model_name}"): 57 | for model_version in model.latest_versions: 58 | print(f"name : {model_version.name} run_id : {model_version.run_id} version : {model_version.version} stage : {model_version.current_stage}") 59 | 60 | def rename_a_registered_model(self ,current_name, new_name): 61 | self.client.rename_registered_model( 62 | name = current_name, 63 | new_name = new_name, 64 | ) 65 | 66 | def transition_model_version_stage(self, model_name, model_version, stage): 67 | self.client.transition_model_version_stage( 68 | name = model_name, 69 | version = model_version, 70 | stage = stage 71 | ) 72 | -------------------------------------------------------------------------------- /template.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.config import * 3 | 4 | def create_structure(): 5 | 6 | dirs = [ 7 | os.path.join("dataset"), 8 | os.path.join("artifacts", "models"), 9 | os.path.join("artifacts", "model_evaluation"), 10 | os.path.join("artifacts", "checkpoints"), 11 | os.path.join("logs", "tensorboard_logs_dir"), 12 | os.path.join("model_serving", "src", "utils"), 13 | os.path.join("model_serving", "src", "images"), 14 | os.path.join("model_serving", "src", "prediction_service"), 15 | os.path.join("model_serving", "src", "schemas"), 16 | os.path.join("model_serving", "src", "configs"), 17 | os.path.join("model_serving","src", "production_model"), 18 | os.path.join("src", "config"), 19 | os.path.join("src", "ml"), 20 | os.path.join("src", "utils") 21 | ] 22 | 23 | for dir_ in dirs: 24 | os.makedirs(dir_, exist_ok= True) 25 | with open(os.path.join(dir_, ".gitkeep"), "w") as f: 26 | pass 27 | 28 | files = [ 29 | "dvc.yaml", 30 | "params.yaml", 31 | ".gitignore", 32 | os.path.join("src", "__init__.py"), 33 | os.path.join("src", "README.md"), 34 | os.path.join("src", "config", "__init__.py"), 35 | os.path.join("src", "ml", "__init__.py"), 36 | os.path.join("logs", "running_logs.log"), 37 | os.path.join("model_serving","src", "__init__.py"), 38 | os.path.join("model_serving","src", "utils", "__init__.py"), 39 | os.path.join("model_serving","src", "schemas", "__init__.py"), 40 | os.path.join("model_serving","src", "prediction_service", "__init__.py"), 41 | os.path.join("model_serving","src", "prediction_service", "services.py"), 42 | os.path.join("model_serving","src", "main.py"), 43 | os.path.join("model_serving", "requirements.txt"), 44 | os.path.join("model_serving", "README.md"), 45 | os.path.join("model_serving", "setup.py") 46 | ] 47 | 48 | for file in files: 49 | if not os.path.exists(file): 50 | with open(file, "w") as f: 51 | pass 52 | 53 | if __name__ == "__main__": 54 | create_structure() --------------------------------------------------------------------------------