├── .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 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 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 | 
73 | ## System Design
74 | 
75 |
76 | ## CICD on Circleci
77 | 
78 | ## DagsHub Data Pipeline
79 | 
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 | 
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()
--------------------------------------------------------------------------------