From a4cb62351d9b0e5a53f94dd2d8d20f530ce2687c Mon Sep 17 00:00:00 2001 From: Narra_Venkata_Raghu_Charan Date: Fri, 17 May 2024 10:55:45 +0530 Subject: [PATCH] Add files via upload --- Chicken_Disease_Classification/Dockerfile | 9 + Chicken_Disease_Classification/LICENSE | 21 + Chicken_Disease_Classification/README.md | 203 ++++++++++ Chicken_Disease_Classification/app.py | 49 +++ .../config/config.yaml | 30 ++ Chicken_Disease_Classification/dvc.lock | 100 +++++ Chicken_Disease_Classification/dvc.yaml | 58 +++ Chicken_Disease_Classification/inputImage.jpg | Bin 0 -> 47607 bytes Chicken_Disease_Classification/main.py | 65 +++ Chicken_Disease_Classification/params.yaml | 8 + .../requirements.txt | 18 + .../research/01_data_ingestion.ipynb | 257 ++++++++++++ .../research/02_prepare_base_model.ipynb | 321 +++++++++++++++ .../research/03_prepare_callbacks.ipynb | 254 ++++++++++++ .../research/04_training.ipynb | 375 ++++++++++++++++++ .../05_model_evaluation_with_mlflow.ipynb | 324 +++++++++++++++ .../research/mlflow_exp/argv_demo.py | 11 + .../research/mlflow_exp/example.py | 96 +++++ .../research/trials.ipynb | 253 ++++++++++++ Chicken_Disease_Classification/scores.json | 4 + Chicken_Disease_Classification/setup.py | 29 ++ .../src/cnnClassifier/__init__.py | 23 ++ .../src/cnnClassifier/components/__init__.py | 0 .../components/data_ingestion.py | 38 ++ .../cnnClassifier/components/evaluation.py | 78 ++++ .../components/prepare_base_model.py | 73 ++++ .../components/prepare_callbacks.py | 37 ++ .../src/cnnClassifier/components/training.py | 81 ++++ .../src/cnnClassifier/config/__init__.py | 0 .../src/cnnClassifier/config/configuration.py | 117 ++++++ .../src/cnnClassifier/constants/__init__.py | 4 + .../src/cnnClassifier/entity/__init__.py | 0 .../src/cnnClassifier/entity/config_entity.py | 54 +++ .../src/cnnClassifier/pipeline/__init__.py | 0 .../src/cnnClassifier/pipeline/predict.py | 30 ++ .../pipeline/stage_01_data_ingestion.py | 31 ++ .../pipeline/stage_02_prepare_base_model.py | 34 ++ .../pipeline/stage_03_training.py | 43 ++ .../pipeline/stage_04_evaluation.py | 35 ++ .../src/cnnClassifier/utils/__init__.py | 0 .../src/cnnClassifier/utils/common.py | 138 +++++++ Chicken_Disease_Classification/template.py | 48 +++ .../templates/index.html | 234 +++++++++++ 43 files changed, 3583 insertions(+) create mode 100644 Chicken_Disease_Classification/Dockerfile create mode 100644 Chicken_Disease_Classification/LICENSE create mode 100644 Chicken_Disease_Classification/README.md create mode 100644 Chicken_Disease_Classification/app.py create mode 100644 Chicken_Disease_Classification/config/config.yaml create mode 100644 Chicken_Disease_Classification/dvc.lock create mode 100644 Chicken_Disease_Classification/dvc.yaml create mode 100644 Chicken_Disease_Classification/inputImage.jpg create mode 100644 Chicken_Disease_Classification/main.py create mode 100644 Chicken_Disease_Classification/params.yaml create mode 100644 Chicken_Disease_Classification/requirements.txt create mode 100644 Chicken_Disease_Classification/research/01_data_ingestion.ipynb create mode 100644 Chicken_Disease_Classification/research/02_prepare_base_model.ipynb create mode 100644 Chicken_Disease_Classification/research/03_prepare_callbacks.ipynb create mode 100644 Chicken_Disease_Classification/research/04_training.ipynb create mode 100644 Chicken_Disease_Classification/research/05_model_evaluation_with_mlflow.ipynb create mode 100644 Chicken_Disease_Classification/research/mlflow_exp/argv_demo.py create mode 100644 Chicken_Disease_Classification/research/mlflow_exp/example.py create mode 100644 Chicken_Disease_Classification/research/trials.ipynb create mode 100644 Chicken_Disease_Classification/scores.json create mode 100644 Chicken_Disease_Classification/setup.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/data_ingestion.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/evaluation.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/prepare_base_model.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/prepare_callbacks.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/components/training.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/config/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/config/configuration.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/constants/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/entity/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/entity/config_entity.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/predict.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_01_data_ingestion.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_02_prepare_base_model.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_03_training.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_04_evaluation.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/utils/__init__.py create mode 100644 Chicken_Disease_Classification/src/cnnClassifier/utils/common.py create mode 100644 Chicken_Disease_Classification/template.py create mode 100644 Chicken_Disease_Classification/templates/index.html diff --git a/Chicken_Disease_Classification/Dockerfile b/Chicken_Disease_Classification/Dockerfile new file mode 100644 index 0000000000..e46419162b --- /dev/null +++ b/Chicken_Disease_Classification/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.8-slim-buster + +RUN apt update -y && apt install awscli -y +WORKDIR /app + +COPY . /app +RUN pip install -r requirements.txt + +CMD ["python3", "app.py"] \ No newline at end of file diff --git a/Chicken_Disease_Classification/LICENSE b/Chicken_Disease_Classification/LICENSE new file mode 100644 index 0000000000..93bc26f8cc --- /dev/null +++ b/Chicken_Disease_Classification/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 BAPPY AHMED + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Chicken_Disease_Classification/README.md b/Chicken_Disease_Classification/README.md new file mode 100644 index 0000000000..c4eaa013f2 --- /dev/null +++ b/Chicken_Disease_Classification/README.md @@ -0,0 +1,203 @@ +# Chicken-Disease-Classification-Project + + +## Workflows + +1. Update config.yaml +2. Update secrets.yaml [Optional] +3. Update params.yaml +4. Update the entity +5. Update the configuration manager in src config +6. Update the components +7. Update the pipeline +8. Update the main.py +9. Update the dvc.yaml +While Building the project, we have to follow these workflows, make sure we are updating everthing at every step. + +# How to run? +### STEPS: + +Clone the repository + +```bash +https://github.com/entbappy/Chicken-Disease-Classification--Project +``` +### STEP 01- Create a conda environment after opening the repository + +```bash +conda create -n cnncls python=3.8 -y +``` + +```bash +conda activate cnncls +``` + + +### STEP 02- install the requirements +```bash +pip install -r requirements.txt +``` + + +```bash +# Finally run the following command +python app.py +``` + +Now, +```bash +open up you local host and port +``` + + +### DVC cmd + +1. dvc init +2. dvc repro +3. dvc dag + + +## MLflow + +[Documentation](https://mlflow.org/docs/latest/index.html) + + +##### cmd +- mlflow ui + +### dagshub +[dagshub](https://dagshub.com/) + +MLFLOW_TRACKING_URI=https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow +MLFLOW_TRACKING_USERNAME=entbappy +MLFLOW_TRACKING_PASSWORD=6824692c47a369aa6f9eac5b10041d5c8edbcef0 +python script.py + +Run this to export as env variables: + +```bash + +export MLFLOW_TRACKING_URI=https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow + +export MLFLOW_TRACKING_USERNAME=entbappy + +export MLFLOW_TRACKING_PASSWORD=6824692c47a369aa6f9eac5b10041d5c8edbcef0 + +``` + + +# AWS-CICD-Deployment-with-Github-Actions + +## 1. Login to AWS console. + +## 2. Create IAM user for deployment + + #with specific access + + 1. EC2 access : It is virtual machine + + 2. ECR: Elastic Container registry to save your docker image in aws + + + #Description: About the deployment + + 1. Build docker image of the source code + + 2. Push your docker image to ECR + + 3. Launch Your EC2 + + 4. Pull Your image from ECR in EC2 + + 5. Lauch your docker image in EC2 + + #Policy: + + 1. AmazonEC2ContainerRegistryFullAccess + + 2. AmazonEC2FullAccess + + +## 3. Create ECR repo to store/save docker image + - Save the URI: 566373416292.dkr.ecr.us-east-1.amazonaws.com/chicken + + +## 4. Create EC2 machine (Ubuntu) + +## 5. Open EC2 and Install docker in EC2 Machine: + + + #optinal + + sudo apt-get update -y + + sudo apt-get upgrade + + #required + + curl -fsSL https://get.docker.com -o get-docker.sh + + sudo sh get-docker.sh + + sudo usermod -aG docker ubuntu + + newgrp docker + +# 6. Configure EC2 as self-hosted runner: + setting>actions>runner>new self hosted runner> choose os> then run command one by one + + +# 7. Setup github secrets: + + AWS_ACCESS_KEY_ID= + + AWS_SECRET_ACCESS_KEY= + + AWS_REGION = us-east-1 + + AWS_ECR_LOGIN_URI = demo>> 566373416292.dkr.ecr.ap-south-1.amazonaws.com + + ECR_REPOSITORY_NAME = simple-app + + + + +# AZURE-CICD-Deployment-with-Github-Actions + +## Save pass: + +s3cEZKH5yytiVnJ3h+eI3qhhzf9q1vNwEi6+q+WGdd+ACRCZ7JD6 + + +## Run from terminal: + +docker build -t chickenapp.azurecr.io/chicken:latest . + +docker login chickenapp.azurecr.io + +docker push chickenapp.azurecr.io/chicken:latest + + +## Deployment Steps: + +1. Build the Docker image of the Source Code +2. Push the Docker image to Container Registry +3. Launch the Web App Server in Azure +4. Pull the Docker image from the container registry to Web App server and run + + + +## About MLflow & DVC + +MLflow + + - Its Production Grade + - Trace all of your expriements + - Logging & taging your model + + +DVC + + - Its very lite weight for POC only + - lite weight expriements tracker + - It can perform Orchestration (Creating Pipelines) diff --git a/Chicken_Disease_Classification/app.py b/Chicken_Disease_Classification/app.py new file mode 100644 index 0000000000..695c23b65d --- /dev/null +++ b/Chicken_Disease_Classification/app.py @@ -0,0 +1,49 @@ +from flask import Flask, request, jsonify, render_template +import os +from flask_cors import CORS, cross_origin +from cnnClassifier.utils.common import decodeImage +from cnnClassifier.pipeline.predict import PredictionPipeline + + +os.putenv('LANG', 'en_US.UTF-8') +os.putenv('LC_ALL', 'en_US.UTF-8') + +app = Flask(__name__) +CORS(app) + + +class ClientApp: + def __init__(self): + self.filename = "inputImage.jpg" + self.classifier = PredictionPipeline(self.filename) + + +@app.route("/", methods=['GET']) +@cross_origin() +def home(): + return render_template('index.html') + + +@app.route("/train", methods=['GET','POST']) +@cross_origin() +def trainRoute(): + os.system("python main.py") + return "Training done successfully!" + + + +@app.route("/predict", methods=['POST']) +@cross_origin() +def predictRoute(): + image = request.json['image'] + decodeImage(image, clApp.filename) + result = clApp.classifier.predict() + return jsonify(result) + + +if __name__ == "__main__": + clApp = ClientApp() + # app.run(host='0.0.0.0', port=8080) #local host + # app.run(host='0.0.0.0', port=8080) #for AWS + app.run(host='0.0.0.0', port=80) #for AZURE + diff --git a/Chicken_Disease_Classification/config/config.yaml b/Chicken_Disease_Classification/config/config.yaml new file mode 100644 index 0000000000..0171fed733 --- /dev/null +++ b/Chicken_Disease_Classification/config/config.yaml @@ -0,0 +1,30 @@ +artifacts_root: artifacts + + +data_ingestion: + root_dir: artifacts/data_ingestion + source_URL: https://github.com/entbappy/Branching-tutorial/raw/master/Chicken-fecal-images.zip + local_data_file: artifacts/data_ingestion/data.zip + unzip_dir: artifacts/data_ingestion + + + +prepare_base_model: + root_dir: artifacts/prepare_base_model + base_model_path: artifacts/prepare_base_model/base_model.h5 + updated_base_model_path: artifacts/prepare_base_model/base_model_updated.h5 + + + + +prepare_callbacks: + root_dir: artifacts/prepare_callbacks + tensorboard_root_log_dir: artifacts/prepare_callbacks/tensorboard_log_dir + checkpoint_model_filepath: artifacts/prepare_callbacks/checkpoint_dir/model.h5 + + + + +training: + root_dir: artifacts/training + trained_model_path: artifacts/training/model.h5 diff --git a/Chicken_Disease_Classification/dvc.lock b/Chicken_Disease_Classification/dvc.lock new file mode 100644 index 0000000000..9b9d183155 --- /dev/null +++ b/Chicken_Disease_Classification/dvc.lock @@ -0,0 +1,100 @@ +schema: '2.0' +stages: + data_ingestion: + cmd: python src/cnnClassifier/pipeline/stage_01_data_ingestion.py + deps: + - path: config/config.yaml + md5: 3f6dc733e0735665409e293214355568 + size: 818 + - path: src/cnnClassifier/pipeline/stage_01_data_ingestion.py + md5: b0ff51aae578695575ba98f83c03d86e + size: 914 + outs: + - path: artifacts/data_ingestion/Chicken-fecal-images + md5: 9e1f8dd2eae3c29e9d635df89d438ae4.dir + size: 12207807 + nfiles: 390 + prepare_base_model: + cmd: python src/cnnClassifier/pipeline/stage_02_prepare_base_model.py + deps: + - path: config/config.yaml + md5: 3f6dc733e0735665409e293214355568 + size: 818 + - path: src/cnnClassifier/pipeline/stage_02_prepare_base_model.py + md5: 5bbb3b3d7f36b28620ae359473f09153 + size: 1003 + params: + params.yaml: + CLASSES: 2 + IMAGE_SIZE: + - 224 + - 224 + - 3 + INCLUDE_TOP: false + LEARNING_RATE: 0.01 + WEIGHTS: imagenet + outs: + - path: artifacts/prepare_base_model + md5: 0774dbc0f4a82fe0f16c2b4f8b2ad70c.dir + size: 118054560 + nfiles: 2 + training: + cmd: python src/cnnClassifier/pipeline/stage_03_training.py + deps: + - path: artifacts/data_ingestion/Chicken-fecal-images + md5: 9e1f8dd2eae3c29e9d635df89d438ae4.dir + size: 12207807 + nfiles: 390 + - path: artifacts/prepare_base_model + md5: 0774dbc0f4a82fe0f16c2b4f8b2ad70c.dir + size: 118054560 + nfiles: 2 + - path: config/config.yaml + md5: 3f6dc733e0735665409e293214355568 + size: 818 + - path: src/cnnClassifier/components/prepare_callbacks.py + md5: dfc1909f7e54d14bb10ff488888367f4 + size: 1008 + - path: src/cnnClassifier/pipeline/stage_03_training.py + md5: 040b50b39a7a78cd8f367c558ebf6dd1 + size: 1268 + params: + params.yaml: + AUGMENTATION: true + BATCH_SIZE: 16 + EPOCHS: 1 + IMAGE_SIZE: + - 224 + - 224 + - 3 + outs: + - path: artifacts/training/model.h5 + md5: 36c31aaf79f22816bd5e8e5df9d87d03 + size: 59337520 + evaluation: + cmd: python src/cnnClassifier/pipeline/stage_04_evaluation.py + deps: + - path: artifacts/data_ingestion/Chicken-fecal-images + md5: 9e1f8dd2eae3c29e9d635df89d438ae4.dir + size: 12207807 + nfiles: 390 + - path: artifacts/training/model.h5 + md5: 36c31aaf79f22816bd5e8e5df9d87d03 + size: 59337520 + - path: config/config.yaml + md5: 3f6dc733e0735665409e293214355568 + size: 818 + - path: src/cnnClassifier/pipeline/stage_04_evaluation.py + md5: f5c5da091d33ae0dc6b6c753ff6a6f82 + size: 923 + params: + params.yaml: + BATCH_SIZE: 16 + IMAGE_SIZE: + - 224 + - 224 + - 3 + outs: + - path: scores.json + md5: 85d9b2cdb3d44e4a7fbe93252e3f060a + size: 73 diff --git a/Chicken_Disease_Classification/dvc.yaml b/Chicken_Disease_Classification/dvc.yaml new file mode 100644 index 0000000000..0a7bc686f4 --- /dev/null +++ b/Chicken_Disease_Classification/dvc.yaml @@ -0,0 +1,58 @@ +stages: + data_ingestion: + cmd: python src/cnnClassifier/pipeline/stage_01_data_ingestion.py + deps: + - src/cnnClassifier/pipeline/stage_01_data_ingestion.py + - config/config.yaml + outs: + - artifacts/data_ingestion/Chicken-fecal-images + + + prepare_base_model: + cmd: python src/cnnClassifier/pipeline/stage_02_prepare_base_model.py + deps: + - src/cnnClassifier/pipeline/stage_02_prepare_base_model.py + - config/config.yaml + params: + - IMAGE_SIZE + - INCLUDE_TOP + - CLASSES + - WEIGHTS + - LEARNING_RATE + outs: + - artifacts/prepare_base_model + + + + training: + cmd: python src/cnnClassifier/pipeline/stage_03_training.py + deps: + - src/cnnClassifier/pipeline/stage_03_training.py + - src/cnnClassifier/components/prepare_callbacks.py + - config/config.yaml + - artifacts/data_ingestion/Chicken-fecal-images + - artifacts/prepare_base_model + params: + - IMAGE_SIZE + - EPOCHS + - BATCH_SIZE + - AUGMENTATION + outs: + - artifacts/training/model.h5 + + + + + evaluation: + cmd: python src/cnnClassifier/pipeline/stage_04_evaluation.py + deps: + - src/cnnClassifier/pipeline/stage_04_evaluation.py + - config/config.yaml + - artifacts/data_ingestion/Chicken-fecal-images + - artifacts/training/model.h5 + params: + - IMAGE_SIZE + - BATCH_SIZE + metrics: + - scores.json: + cache: false diff --git a/Chicken_Disease_Classification/inputImage.jpg b/Chicken_Disease_Classification/inputImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d60489d8a1b297edce6b47246f5cf695686216b5 GIT binary patch literal 47607 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�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 Uqw}ZU!yKVy%>%b{Q=UKn*;&nsvj6}9 literal 0 HcmV?d00001 diff --git a/Chicken_Disease_Classification/main.py b/Chicken_Disease_Classification/main.py new file mode 100644 index 0000000000..f460fb691d --- /dev/null +++ b/Chicken_Disease_Classification/main.py @@ -0,0 +1,65 @@ +from cnnClassifier import logger +from cnnClassifier.pipeline.stage_01_data_ingestion import DataIngestionTrainingPipeline +from cnnClassifier.pipeline.stage_02_prepare_base_model import PrepareBaseModelTrainingPipeline +from cnnClassifier.pipeline.stage_03_training import ModelTrainingPipeline +from cnnClassifier.pipeline.stage_04_evaluation import EvaluationPipeline + + +STAGE_NAME = "Data Ingestion stage" +try: + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + data_ingestion = DataIngestionTrainingPipeline() + data_ingestion.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") +except Exception as e: + logger.exception(e) + raise e + + + + +STAGE_NAME = "Prepare base model" +try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + prepare_base_model = PrepareBaseModelTrainingPipeline() + prepare_base_model.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") +except Exception as e: + logger.exception(e) + raise e + + + + +STAGE_NAME = "Training" +try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + model_trainer = ModelTrainingPipeline() + model_trainer.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") +except Exception as e: + logger.exception(e) + raise e + + + + + + +STAGE_NAME = "Evaluation stage" +try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + model_evalution = EvaluationPipeline() + model_evalution.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") + +except Exception as e: + logger.exception(e) + raise e + + + + diff --git a/Chicken_Disease_Classification/params.yaml b/Chicken_Disease_Classification/params.yaml new file mode 100644 index 0000000000..c69ece0aa6 --- /dev/null +++ b/Chicken_Disease_Classification/params.yaml @@ -0,0 +1,8 @@ +AUGMENTATION: True +IMAGE_SIZE: [224, 224, 3] # as per VGG 16 model +BATCH_SIZE: 16 +INCLUDE_TOP: False +EPOCHS: 1 +CLASSES: 2 +WEIGHTS: imagenet +LEARNING_RATE: 0.01 diff --git a/Chicken_Disease_Classification/requirements.txt b/Chicken_Disease_Classification/requirements.txt new file mode 100644 index 0000000000..c3897101ec --- /dev/null +++ b/Chicken_Disease_Classification/requirements.txt @@ -0,0 +1,18 @@ +tensorflow +pandas +dvc +mlflow==2.2.2 +notebook +numpy +matplotlib +seaborn +python-box==6.0.2 +pyYAML +tqdm +ensure==1.0.2 +joblib +types-PyYAML +scipy +Flask +Flask-Cors +-e . \ No newline at end of file diff --git a/Chicken_Disease_Classification/research/01_data_ingestion.ipynb b/Chicken_Disease_Classification/research/01_data_ingestion.ipynb new file mode 100644 index 0000000000..c1d01080b9 --- /dev/null +++ b/Chicken_Disease_Classification/research/01_data_ingestion.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project\\\\research'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from pathlib import Path\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class DataIngestionConfig:\n", + " root_dir: Path\n", + " source_URL: str\n", + " local_data_file: Path\n", + " unzip_dir: Path" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from cnnClassifier.constants import *\n", + "from cnnClassifier.utils.common import read_yaml, create_directories" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "class ConfigurationManager:\n", + " def __init__(\n", + " self,\n", + " config_filepath = CONFIG_FILE_PATH,\n", + " params_filepath = PARAMS_FILE_PATH):\n", + "\n", + " self.config = read_yaml(config_filepath)\n", + " self.params = read_yaml(params_filepath)\n", + "\n", + " create_directories([self.config.artifacts_root])\n", + "\n", + "\n", + " \n", + " def get_data_ingestion_config(self) -> DataIngestionConfig:\n", + " config = self.config.data_ingestion\n", + "\n", + " create_directories([config.root_dir])\n", + "\n", + " data_ingestion_config = DataIngestionConfig(\n", + " root_dir=config.root_dir,\n", + " source_URL=config.source_URL,\n", + " local_data_file=config.local_data_file,\n", + " unzip_dir=config.unzip_dir \n", + " )\n", + "\n", + " return data_ingestion_config\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import urllib.request as request\n", + "import zipfile\n", + "from cnnClassifier import logger\n", + "from cnnClassifier.utils.common import get_size" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "class DataIngestion:\n", + " def __init__(self, config: DataIngestionConfig):\n", + " self.config = config\n", + "\n", + "\n", + " \n", + " def download_file(self):\n", + " if not os.path.exists(self.config.local_data_file):\n", + " filename, headers = request.urlretrieve(\n", + " url = self.config.source_URL,\n", + " filename = self.config.local_data_file\n", + " )\n", + " logger.info(f\"{filename} download! with following info: \\n{headers}\")\n", + " else:\n", + " logger.info(f\"File already exists of size: {get_size(Path(self.config.local_data_file))}\") \n", + "\n", + "\n", + " \n", + " def extract_zip_file(self):\n", + " \"\"\"\n", + " zip_file_path: str\n", + " Extracts the zip file into the data directory\n", + " Function returns None\n", + " \"\"\"\n", + " unzip_path = self.config.unzip_dir\n", + " os.makedirs(unzip_path, exist_ok=True)\n", + " with zipfile.ZipFile(self.config.local_data_file, 'r') as zip_ref:\n", + " zip_ref.extractall(unzip_path)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-05-31 10:49:27,686: INFO: common: yaml file: config\\config.yaml loaded successfully]\n", + "[2023-05-31 10:49:27,688: INFO: common: yaml file: params.yaml loaded successfully]\n", + "[2023-05-31 10:49:27,689: INFO: common: created directory at: artifacts]\n", + "[2023-05-31 10:49:27,691: INFO: common: created directory at: artifacts/data_ingestion]\n", + "[2023-05-31 10:49:48,873: INFO: 1170291011: artifacts/data_ingestion/data.zip download! with following info: \n", + "Connection: close\n", + "Content-Length: 11616915\n", + "Cache-Control: max-age=300\n", + "Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\n", + "Content-Type: application/zip\n", + "ETag: \"adf745abc03891fe493c3be264ec012691fe3fa21d861f35a27edbe6d86a76b1\"\n", + "Strict-Transport-Security: max-age=31536000\n", + "X-Content-Type-Options: nosniff\n", + "X-Frame-Options: deny\n", + "X-XSS-Protection: 1; mode=block\n", + "X-GitHub-Request-Id: DD26:594D:46A0F2:51208A:6476D1D8\n", + "Accept-Ranges: bytes\n", + "Date: Wed, 31 May 2023 04:49:29 GMT\n", + "Via: 1.1 varnish\n", + "X-Served-By: cache-hkg17929-HKG\n", + "X-Cache: MISS\n", + "X-Cache-Hits: 0\n", + "X-Timer: S1685508569.655993,VS0,VE951\n", + "Vary: Authorization,Accept-Encoding,Origin\n", + "Access-Control-Allow-Origin: *\n", + "X-Fastly-Request-ID: ef2c1333b2cc4998b0020b1c7edf46d7a603aa90\n", + "Expires: Wed, 31 May 2023 04:54:29 GMT\n", + "Source-Age: 1\n", + "\n", + "]\n" + ] + } + ], + "source": [ + "try:\n", + " config = ConfigurationManager()\n", + " data_ingestion_config = config.get_data_ingestion_config()\n", + " data_ingestion = DataIngestion(config=data_ingestion_config)\n", + " data_ingestion.download_file()\n", + " data_ingestion.extract_zip_file()\n", + "except Exception as e:\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/research/02_prepare_base_model.ipynb b/Chicken_Disease_Classification/research/02_prepare_base_model.ipynb new file mode 100644 index 0000000000..8d71752aa2 --- /dev/null +++ b/Chicken_Disease_Classification/research/02_prepare_base_model.ipynb @@ -0,0 +1,321 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project\\\\research'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from pathlib import Path\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class PrepareBaseModelConfig:\n", + " root_dir: Path\n", + " base_model_path: Path\n", + " updated_base_model_path: Path\n", + " params_image_size: list\n", + " params_learning_rate: float\n", + " params_include_top: bool\n", + " params_weights: str\n", + " params_classes: int" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from cnnClassifier.constants import *\n", + "from cnnClassifier.utils.common import read_yaml, create_directories" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "class ConfigurationManager:\n", + " def __init__(\n", + " self, \n", + " config_filepath = CONFIG_FILE_PATH,\n", + " params_filepath = PARAMS_FILE_PATH):\n", + " self.config = read_yaml(config_filepath)\n", + " self.params = read_yaml(params_filepath)\n", + " create_directories([self.config.artifacts_root])\n", + "\n", + "\n", + " def get_prepare_base_model_config(self) -> PrepareBaseModelConfig:\n", + " config = self.config.prepare_base_model\n", + " \n", + " create_directories([config.root_dir])\n", + "\n", + " prepare_base_model_config = PrepareBaseModelConfig(\n", + " root_dir=Path(config.root_dir),\n", + " base_model_path=Path(config.base_model_path),\n", + " updated_base_model_path=Path(config.updated_base_model_path),\n", + " params_image_size=self.params.IMAGE_SIZE,\n", + " params_learning_rate=self.params.LEARNING_RATE,\n", + " params_include_top=self.params.INCLUDE_TOP,\n", + " params_weights=self.params.WEIGHTS,\n", + " params_classes=self.params.CLASSES\n", + " )\n", + "\n", + " return prepare_base_model_config\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import urllib.request as request\n", + "from zipfile import ZipFile\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "class PrepareBaseModel:\n", + " def __init__(self, config: PrepareBaseModelConfig):\n", + " self.config = config\n", + "\n", + "\n", + " \n", + " def get_base_model(self):\n", + " self.model = tf.keras.applications.vgg16.VGG16(\n", + " input_shape=self.config.params_image_size,\n", + " weights=self.config.params_weights,\n", + " include_top=self.config.params_include_top\n", + " )\n", + "\n", + " self.save_model(path=self.config.base_model_path, model=self.model)\n", + "\n", + "\n", + " \n", + " @staticmethod\n", + " def _prepare_full_model(model, classes, freeze_all, freeze_till, learning_rate):\n", + " if freeze_all:\n", + " for layer in model.layers:\n", + " model.trainable = False\n", + " elif (freeze_till is not None) and (freeze_till > 0):\n", + " for layer in model.layers[:-freeze_till]:\n", + " model.trainable = False\n", + "\n", + " flatten_in = tf.keras.layers.Flatten()(model.output)\n", + " prediction = tf.keras.layers.Dense(\n", + " units=classes,\n", + " activation=\"softmax\"\n", + " )(flatten_in)\n", + "\n", + " full_model = tf.keras.models.Model(\n", + " inputs=model.input,\n", + " outputs=prediction\n", + " )\n", + "\n", + " full_model.compile(\n", + " optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate),\n", + " loss=tf.keras.losses.CategoricalCrossentropy(),\n", + " metrics=[\"accuracy\"]\n", + " )\n", + "\n", + " full_model.summary()\n", + " return full_model\n", + " \n", + "\n", + " def update_base_model(self):\n", + " self.full_model = self._prepare_full_model(\n", + " model=self.model,\n", + " classes=self.config.params_classes,\n", + " freeze_all=True,\n", + " freeze_till=None,\n", + " learning_rate=self.config.params_learning_rate\n", + " )\n", + "\n", + " self.save_model(path=self.config.updated_base_model_path, model=self.full_model)\n", + "\n", + " \n", + " @staticmethod\n", + " def save_model(path: Path, model: tf.keras.Model):\n", + " model.save(path)\n", + "\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-05-31 13:21:13,143: INFO: common: yaml file: config\\config.yaml loaded successfully]\n", + "[2023-05-31 13:21:13,145: INFO: common: yaml file: params.yaml loaded successfully]\n", + "[2023-05-31 13:21:13,147: INFO: common: created directory at: artifacts]\n", + "[2023-05-31 13:21:13,148: INFO: common: created directory at: artifacts/prepare_base_model]\n", + "[2023-05-31 13:21:13,571: WARNING: saving_utils: Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.]\n", + "Model: \"model\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input_1 (InputLayer) [(None, 224, 224, 3)] 0 \n", + " \n", + " block1_conv1 (Conv2D) (None, 224, 224, 64) 1792 \n", + " \n", + " block1_conv2 (Conv2D) (None, 224, 224, 64) 36928 \n", + " \n", + " block1_pool (MaxPooling2D) (None, 112, 112, 64) 0 \n", + " \n", + " block2_conv1 (Conv2D) (None, 112, 112, 128) 73856 \n", + " \n", + " block2_conv2 (Conv2D) (None, 112, 112, 128) 147584 \n", + " \n", + " block2_pool (MaxPooling2D) (None, 56, 56, 128) 0 \n", + " \n", + " block3_conv1 (Conv2D) (None, 56, 56, 256) 295168 \n", + " \n", + " block3_conv2 (Conv2D) (None, 56, 56, 256) 590080 \n", + " \n", + " block3_conv3 (Conv2D) (None, 56, 56, 256) 590080 \n", + " \n", + " block3_pool (MaxPooling2D) (None, 28, 28, 256) 0 \n", + " \n", + " block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160 \n", + " \n", + " block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808 \n", + " \n", + " block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808 \n", + " \n", + " block4_pool (MaxPooling2D) (None, 14, 14, 512) 0 \n", + " \n", + " block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808 \n", + " \n", + " block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808 \n", + " \n", + " block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808 \n", + " \n", + " block5_pool (MaxPooling2D) (None, 7, 7, 512) 0 \n", + " \n", + " flatten (Flatten) (None, 25088) 0 \n", + " \n", + " dense (Dense) (None, 2) 50178 \n", + " \n", + "=================================================================\n", + "Total params: 14,764,866\n", + "Trainable params: 50,178\n", + "Non-trainable params: 14,714,688\n", + "_________________________________________________________________\n" + ] + } + ], + "source": [ + "try:\n", + " config = ConfigurationManager()\n", + " prepare_base_model_config = config.get_prepare_base_model_config()\n", + " prepare_base_model = PrepareBaseModel(config=prepare_base_model_config)\n", + " prepare_base_model.get_base_model()\n", + " prepare_base_model.update_base_model()\n", + "except Exception as e:\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/research/03_prepare_callbacks.ipynb b/Chicken_Disease_Classification/research/03_prepare_callbacks.ipynb new file mode 100644 index 0000000000..b1a0deed6c --- /dev/null +++ b/Chicken_Disease_Classification/research/03_prepare_callbacks.ipynb @@ -0,0 +1,254 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project\\\\research'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from pathlib import Path\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class PrepareCallbacksConfig:\n", + " root_dir: Path\n", + " tensorboard_root_log_dir: Path\n", + " checkpoint_model_filepath: Path" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from cnnClassifier.constants import *\n", + "from cnnClassifier.utils.common import read_yaml, create_directories" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "class ConfigurationManager:\n", + " def __init__(\n", + " self, \n", + " config_filepath = CONFIG_FILE_PATH,\n", + " params_filepath = PARAMS_FILE_PATH):\n", + " self.config = read_yaml(config_filepath)\n", + " self.params = read_yaml(params_filepath)\n", + " create_directories([self.config.artifacts_root])\n", + "\n", + "\n", + " \n", + " def get_prepare_callback_config(self) -> PrepareCallbacksConfig:\n", + " config = self.config.prepare_callbacks\n", + " model_ckpt_dir = os.path.dirname(config.checkpoint_model_filepath)\n", + " create_directories([\n", + " Path(model_ckpt_dir),\n", + " Path(config.tensorboard_root_log_dir)\n", + " ])\n", + "\n", + " prepare_callback_config = PrepareCallbacksConfig(\n", + " root_dir=Path(config.root_dir),\n", + " tensorboard_root_log_dir=Path(config.tensorboard_root_log_dir),\n", + " checkpoint_model_filepath=Path(config.checkpoint_model_filepath)\n", + " )\n", + "\n", + " return prepare_callback_config\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import urllib.request as request\n", + "from zipfile import ZipFile\n", + "import tensorflow as tf\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "class PrepareCallback:\n", + " def __init__(self, config: PrepareCallbacksConfig):\n", + " self.config = config\n", + "\n", + "\n", + " \n", + " @property\n", + " def _create_tb_callbacks(self):\n", + " timestamp = time.strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + " tb_running_log_dir = os.path.join(\n", + " self.config.tensorboard_root_log_dir,\n", + " f\"tb_logs_at_{timestamp}\",\n", + " )\n", + " return tf.keras.callbacks.TensorBoard(log_dir=tb_running_log_dir)\n", + " \n", + "\n", + " @property\n", + " def _create_ckpt_callbacks(self):\n", + " return tf.keras.callbacks.ModelCheckpoint(\n", + " filepath=self.config.checkpoint_model_filepath,\n", + " save_best_only=True\n", + " )\n", + "\n", + "\n", + " def get_tb_ckpt_callbacks(self):\n", + " return [\n", + " self._create_tb_callbacks,\n", + " self._create_ckpt_callbacks\n", + " ]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-05-31 14:37:12,294: INFO: common: yaml file: config\\config.yaml loaded successfully]\n", + "[2023-05-31 14:37:12,303: INFO: common: yaml file: params.yaml loaded successfully]\n", + "[2023-05-31 14:37:12,305: INFO: common: created directory at: artifacts]\n", + "[2023-05-31 14:37:12,307: INFO: common: created directory at: artifacts\\prepare_callbacks\\checkpoint_dir]\n", + "[2023-05-31 14:37:12,310: INFO: common: created directory at: artifacts\\prepare_callbacks\\tensorboard_log_dir]\n" + ] + } + ], + "source": [ + "try:\n", + " config = ConfigurationManager()\n", + " prepare_callbacks_config = config.get_prepare_callback_config()\n", + " prepare_callbacks = PrepareCallback(config=prepare_callbacks_config)\n", + " callback_list = prepare_callbacks.get_tb_ckpt_callbacks()\n", + " \n", + "except Exception as e:\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'tb_logs_at_2023-05-31-14-38-20'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import time\n", + "timestamp = time.strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + "f\"tb_logs_at_{timestamp}\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/research/04_training.ipynb b/Chicken_Disease_Classification/research/04_training.ipynb new file mode 100644 index 0000000000..5135b5efaf --- /dev/null +++ b/Chicken_Disease_Classification/research/04_training.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project\\\\research'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-Disease-Classification--Project'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from pathlib import Path\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class TrainingConfig:\n", + " root_dir: Path\n", + " trained_model_path: Path\n", + " updated_base_model_path: Path\n", + " training_data: Path\n", + " params_epochs: int\n", + " params_batch_size: int\n", + " params_is_augmentation: bool\n", + " params_image_size: list\n", + "\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class PrepareCallbacksConfig:\n", + " root_dir: Path\n", + " tensorboard_root_log_dir: Path\n", + " checkpoint_model_filepath: Path" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from cnnClassifier.constants import *\n", + "from cnnClassifier.utils.common import read_yaml, create_directories\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "class ConfigurationManager:\n", + " def __init__(\n", + " self, \n", + " config_filepath = CONFIG_FILE_PATH,\n", + " params_filepath = PARAMS_FILE_PATH):\n", + " self.config = read_yaml(config_filepath)\n", + " self.params = read_yaml(params_filepath)\n", + " create_directories([self.config.artifacts_root])\n", + "\n", + "\n", + " \n", + " def get_prepare_callback_config(self) -> PrepareCallbacksConfig:\n", + " config = self.config.prepare_callbacks\n", + " model_ckpt_dir = os.path.dirname(config.checkpoint_model_filepath)\n", + " create_directories([\n", + " Path(model_ckpt_dir),\n", + " Path(config.tensorboard_root_log_dir)\n", + " ])\n", + "\n", + " prepare_callback_config = PrepareCallbacksConfig(\n", + " root_dir=Path(config.root_dir),\n", + " tensorboard_root_log_dir=Path(config.tensorboard_root_log_dir),\n", + " checkpoint_model_filepath=Path(config.checkpoint_model_filepath)\n", + " )\n", + "\n", + " return prepare_callback_config\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " def get_training_config(self) -> TrainingConfig:\n", + " training = self.config.training\n", + " prepare_base_model = self.config.prepare_base_model\n", + " params = self.params\n", + " training_data = os.path.join(self.config.data_ingestion.unzip_dir, \"Chicken-fecal-images\")\n", + " create_directories([\n", + " Path(training.root_dir)\n", + " ])\n", + "\n", + " training_config = TrainingConfig(\n", + " root_dir=Path(training.root_dir),\n", + " trained_model_path=Path(training.trained_model_path),\n", + " updated_base_model_path=Path(prepare_base_model.updated_base_model_path),\n", + " training_data=Path(training_data),\n", + " params_epochs=params.EPOCHS,\n", + " params_batch_size=params.BATCH_SIZE,\n", + " params_is_augmentation=params.AUGMENTATION,\n", + " params_image_size=params.IMAGE_SIZE\n", + " )\n", + "\n", + " return training_config" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "class PrepareCallback:\n", + " def __init__(self, config: PrepareCallbacksConfig):\n", + " self.config = config\n", + "\n", + "\n", + " \n", + " @property\n", + " def _create_tb_callbacks(self):\n", + " timestamp = time.strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + " tb_running_log_dir = os.path.join(\n", + " self.config.tensorboard_root_log_dir,\n", + " f\"tb_logs_at_{timestamp}\",\n", + " )\n", + " return tf.keras.callbacks.TensorBoard(log_dir=tb_running_log_dir)\n", + " \n", + "\n", + " @property\n", + " def _create_ckpt_callbacks(self):\n", + " return tf.keras.callbacks.ModelCheckpoint(\n", + " filepath=self.config.checkpoint_model_filepath,\n", + " save_best_only=True\n", + " )\n", + "\n", + "\n", + " def get_tb_ckpt_callbacks(self):\n", + " return [\n", + " self._create_tb_callbacks,\n", + " self._create_ckpt_callbacks\n", + " ]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import urllib.request as request\n", + "from zipfile import ZipFile\n", + "import tensorflow as tf\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "class Training:\n", + " def __init__(self, config: TrainingConfig):\n", + " self.config = config\n", + " \n", + " def get_base_model(self):\n", + " self.model = tf.keras.models.load_model(\n", + " self.config.updated_base_model_path\n", + " )\n", + " \n", + " def train_valid_generator(self):\n", + "\n", + " datagenerator_kwargs = dict(\n", + " rescale = 1./255,\n", + " validation_split=0.20\n", + " )\n", + "\n", + " dataflow_kwargs = dict(\n", + " target_size=self.config.params_image_size[:-1],\n", + " batch_size=self.config.params_batch_size,\n", + " interpolation=\"bilinear\"\n", + " )\n", + "\n", + " valid_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator(\n", + " **datagenerator_kwargs\n", + " )\n", + "\n", + " self.valid_generator = valid_datagenerator.flow_from_directory(\n", + " directory=self.config.training_data,\n", + " subset=\"validation\",\n", + " shuffle=False,\n", + " **dataflow_kwargs\n", + " )\n", + "\n", + " if self.config.params_is_augmentation:\n", + " train_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator(\n", + " rotation_range=40,\n", + " horizontal_flip=True,\n", + " width_shift_range=0.2,\n", + " height_shift_range=0.2,\n", + " shear_range=0.2,\n", + " zoom_range=0.2,\n", + " **datagenerator_kwargs\n", + " )\n", + " else:\n", + " train_datagenerator = valid_datagenerator\n", + "\n", + " self.train_generator = train_datagenerator.flow_from_directory(\n", + " directory=self.config.training_data,\n", + " subset=\"training\",\n", + " shuffle=True,\n", + " **dataflow_kwargs\n", + " )\n", + "\n", + " @staticmethod\n", + " def save_model(path: Path, model: tf.keras.Model):\n", + " model.save(path)\n", + "\n", + "\n", + " def train(self, callback_list: list):\n", + " self.steps_per_epoch = self.train_generator.samples // self.train_generator.batch_size\n", + " self.validation_steps = self.valid_generator.samples // self.valid_generator.batch_size\n", + "\n", + " self.model.fit(\n", + " self.train_generator,\n", + " epochs=self.config.params_epochs,\n", + " steps_per_epoch=self.steps_per_epoch,\n", + " validation_steps=self.validation_steps,\n", + " validation_data=self.valid_generator,\n", + " callbacks=callback_list\n", + " )\n", + "\n", + " self.save_model(\n", + " path=self.config.trained_model_path,\n", + " model=self.model\n", + " )\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-06-01 10:18:59,787: INFO: common: yaml file: config\\config.yaml loaded successfully]\n", + "[2023-06-01 10:18:59,792: INFO: common: yaml file: params.yaml loaded successfully]\n", + "[2023-06-01 10:18:59,794: INFO: common: created directory at: artifacts]\n", + "[2023-06-01 10:18:59,795: INFO: common: created directory at: artifacts\\prepare_callbacks\\checkpoint_dir]\n", + "[2023-06-01 10:18:59,796: INFO: common: created directory at: artifacts\\prepare_callbacks\\tensorboard_log_dir]\n", + "[2023-06-01 10:18:59,797: INFO: common: created directory at: artifacts\\training]\n", + "Found 78 images belonging to 2 classes.\n", + "Found 312 images belonging to 2 classes.\n", + "19/19 [==============================] - 28s 1s/step - loss: 13.6681 - accuracy: 0.5270 - val_loss: 19.1089 - val_accuracy: 0.3906\n" + ] + } + ], + "source": [ + "try:\n", + " config = ConfigurationManager()\n", + " prepare_callbacks_config = config.get_prepare_callback_config()\n", + " prepare_callbacks = PrepareCallback(config=prepare_callbacks_config)\n", + " callback_list = prepare_callbacks.get_tb_ckpt_callbacks()\n", + "\n", + " training_config = config.get_training_config()\n", + " training = Training(config=training_config)\n", + " training.get_base_model()\n", + " training.train_valid_generator()\n", + " training.train(\n", + " callback_list=callback_list\n", + " )\n", + " \n", + "except Exception as e:\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/research/05_model_evaluation_with_mlflow.ipynb b/Chicken_Disease_Classification/research/05_model_evaluation_with_mlflow.ipynb new file mode 100644 index 0000000000..5dc98d47bf --- /dev/null +++ b/Chicken_Disease_Classification/research/05_model_evaluation_with_mlflow.ipynb @@ -0,0 +1,324 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-mlflow-dvc\\\\research'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'d:\\\\Bappy\\\\YouTube\\\\Chicken-mlflow-dvc'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"MLFLOW_TRACKING_URI\"]=\"https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow\"\n", + "os.environ[\"MLFLOW_TRACKING_USERNAME\"]=\"entbappy\"\n", + "os.environ[\"MLFLOW_TRACKING_PASSWORD\"]=\"6824692c47a369aa6f9eac5b10041d5c8edbcef0\"" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "model = tf.keras.models.load_model(\"artifacts/training/model.h5\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from pathlib import Path\n", + "\n", + "@dataclass(frozen=True)\n", + "class EvaluationConfig:\n", + " path_of_model: Path\n", + " training_data: Path\n", + " all_params: dict\n", + " mlflow_uri: str\n", + " params_image_size: list\n", + " params_batch_size: int\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from cnnClassifier.constants import *\n", + "from cnnClassifier.utils.common import read_yaml, create_directories, save_json" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "class ConfigurationManager:\n", + " def __init__(\n", + " self, \n", + " config_filepath = CONFIG_FILE_PATH,\n", + " params_filepath = PARAMS_FILE_PATH):\n", + " self.config = read_yaml(config_filepath)\n", + " self.params = read_yaml(params_filepath)\n", + " create_directories([self.config.artifacts_root])\n", + "\n", + " \n", + " def get_evaluation_config(self) -> EvaluationConfig:\n", + " eval_config = EvaluationConfig(\n", + " path_of_model=\"artifacts/training/model.h5\",\n", + " training_data=\"artifacts/data_ingestion/Chicken-fecal-images\",\n", + " mlflow_uri=\"https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow\",\n", + " all_params=self.params,\n", + " params_image_size=self.params.IMAGE_SIZE,\n", + " params_batch_size=self.params.BATCH_SIZE\n", + " )\n", + " return eval_config\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from pathlib import Path\n", + "import mlflow\n", + "import mlflow.keras\n", + "from urllib.parse import urlparse" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "class Evaluation:\n", + " def __init__(self, config: EvaluationConfig):\n", + " self.config = config\n", + "\n", + " \n", + " def _valid_generator(self):\n", + "\n", + " datagenerator_kwargs = dict(\n", + " rescale = 1./255,\n", + " validation_split=0.30\n", + " )\n", + "\n", + " dataflow_kwargs = dict(\n", + " target_size=self.config.params_image_size[:-1],\n", + " batch_size=self.config.params_batch_size,\n", + " interpolation=\"bilinear\"\n", + " )\n", + "\n", + " valid_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator(\n", + " **datagenerator_kwargs\n", + " )\n", + "\n", + " self.valid_generator = valid_datagenerator.flow_from_directory(\n", + " directory=self.config.training_data,\n", + " subset=\"validation\",\n", + " shuffle=False,\n", + " **dataflow_kwargs\n", + " )\n", + "\n", + " \n", + " @staticmethod\n", + " def load_model(path: Path) -> tf.keras.Model:\n", + " return tf.keras.models.load_model(path)\n", + " \n", + "\n", + " def evaluation(self):\n", + " self.model = self.load_model(self.config.path_of_model)\n", + " self._valid_generator()\n", + " self.score = model.evaluate(self.valid_generator)\n", + "\n", + " \n", + " def save_score(self):\n", + " scores = {\"loss\": self.score[0], \"accuracy\": self.score[1]}\n", + " save_json(path=Path(\"scores.json\"), data=scores)\n", + "\n", + " \n", + "\n", + " def log_into_mlflow(self):\n", + " mlflow.set_registry_uri(self.config.mlflow_uri)\n", + " tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme\n", + " \n", + " with mlflow.start_run():\n", + " mlflow.log_params(self.config.all_params)\n", + " mlflow.log_metrics(\n", + " {\"loss\": self.score[0], \"accuracy\": self.score[1]}\n", + " )\n", + " # Model registry does not work with file store\n", + " if tracking_url_type_store != \"file\":\n", + "\n", + " # Register the model\n", + " # There are other ways to use the Model Registry, which depends on the use case,\n", + " # please refer to the doc for more information:\n", + " # https://mlflow.org/docs/latest/model-registry.html#api-workflow\n", + " mlflow.keras.log_model(self.model, \"model\", registered_model_name=\"VGG16Model\")\n", + " else:\n", + " mlflow.keras.log_model(self.model, \"model\")\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-06-17 12:10:47,659: INFO: common: yaml file: config\\config.yaml loaded successfully]\n", + "[2023-06-17 12:10:47,661: INFO: common: yaml file: params.yaml loaded successfully]\n", + "[2023-06-17 12:10:47,662: INFO: common: created directory at: artifacts]\n", + "Found 116 images belonging to 2 classes.\n", + "8/8 [==============================] - 7s 833ms/step - loss: 1.8001 - accuracy: 0.7328\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023/06/17 12:10:56 WARNING mlflow.tensorflow: You are saving a TensorFlow Core model or Keras model without a signature. Inference with mlflow.pyfunc.spark_udf() will not work unless the model's pyfunc representation accepts pandas DataFrames as inference inputs.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2023-06-17 12:10:57,841: WARNING: save: Found untraced functions such as _jit_compiled_convolution_op, _jit_compiled_convolution_op, _jit_compiled_convolution_op, _jit_compiled_convolution_op, _jit_compiled_convolution_op while saving (showing 5 of 14). These functions will not be directly callable after loading.]\n", + "INFO:tensorflow:Assets written to: C:\\Users\\bokti\\AppData\\Local\\Temp\\tmp6vzrgib0\\model\\data\\model\\assets\n", + "[2023-06-17 12:10:58,318: INFO: builder_impl: Assets written to: C:\\Users\\bokti\\AppData\\Local\\Temp\\tmp6vzrgib0\\model\\data\\model\\assets]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "d:\\Softwares\\anaconda3\\envs\\chicken\\lib\\site-packages\\_distutils_hack\\__init__.py:33: UserWarning: Setuptools is replacing distutils.\n", + " warnings.warn(\"Setuptools is replacing distutils.\")\n", + "Registered model 'VGG16Model' already exists. Creating a new version of this model...\n", + "2023/06/17 12:12:04 INFO mlflow.tracking._model_registry.client: Waiting up to 300 seconds for model version to finish creation. Model name: VGG16Model, version 2\n", + "Created version '2' of model 'VGG16Model'.\n" + ] + } + ], + "source": [ + "try:\n", + " config = ConfigurationManager()\n", + " eval_config = config.get_evaluation_config()\n", + " evaluation = Evaluation(eval_config)\n", + " evaluation.evaluation()\n", + " evaluation.log_into_mlflow()\n", + "\n", + "except Exception as e:\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/research/mlflow_exp/argv_demo.py b/Chicken_Disease_Classification/research/mlflow_exp/argv_demo.py new file mode 100644 index 0000000000..0831243680 --- /dev/null +++ b/Chicken_Disease_Classification/research/mlflow_exp/argv_demo.py @@ -0,0 +1,11 @@ +import sys + +default = 0.3 + +alpha = float(sys.argv[1]) if len(sys.argv) > 1 else default +l1_ratio = float(sys.argv[2]) if len(sys.argv) > 2 else default + +args = sys.argv +print(args) + +print(alpha, l1_ratio) \ No newline at end of file diff --git a/Chicken_Disease_Classification/research/mlflow_exp/example.py b/Chicken_Disease_Classification/research/mlflow_exp/example.py new file mode 100644 index 0000000000..292e2f247f --- /dev/null +++ b/Chicken_Disease_Classification/research/mlflow_exp/example.py @@ -0,0 +1,96 @@ +# The data set used in this example is from http://archive.ics.uci.edu/ml/datasets/Wine+Quality +# P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis. +# Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009. + +import os +import warnings +import sys + +import pandas as pd +import numpy as np +from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score +from sklearn.model_selection import train_test_split +from sklearn.linear_model import ElasticNet +from urllib.parse import urlparse +import mlflow +import mlflow.sklearn + +import logging + +logging.basicConfig(level=logging.WARN) +logger = logging.getLogger(__name__) + + +def eval_metrics(actual, pred): + rmse = np.sqrt(mean_squared_error(actual, pred)) + mae = mean_absolute_error(actual, pred) + r2 = r2_score(actual, pred) + return rmse, mae, r2 + + +if __name__ == "__main__": + warnings.filterwarnings("ignore") + np.random.seed(40) + + # Read the wine-quality csv file from the URL + csv_url = ( + "http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv" + ) + try: + data = pd.read_csv(csv_url, sep=";") + except Exception as e: + logger.exception( + "Unable to download training & test CSV, check your internet connection. Error: %s", e + ) + + # Split the data into training and test sets. (0.75, 0.25) split. + train, test = train_test_split(data) + + # The predicted column is "quality" which is a scalar from [3, 9] + train_x = train.drop(["quality"], axis=1) + test_x = test.drop(["quality"], axis=1) + train_y = train[["quality"]] + test_y = test[["quality"]] + + alpha = float(sys.argv[1]) if len(sys.argv) > 1 else 0.5 + l1_ratio = float(sys.argv[2]) if len(sys.argv) > 2 else 0.5 + + with mlflow.start_run(): + lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42) + lr.fit(train_x, train_y) + + predicted_qualities = lr.predict(test_x) + + (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities) + + print("Elasticnet model (alpha=%f, l1_ratio=%f):" % (alpha, l1_ratio)) + print(" RMSE: %s" % rmse) + print(" MAE: %s" % mae) + print(" R2: %s" % r2) + + mlflow.log_param("alpha", alpha) + mlflow.log_param("l1_ratio", l1_ratio) + mlflow.log_metric("rmse", rmse) + mlflow.log_metric("r2", r2) + mlflow.log_metric("mae", mae) + + + + # For remote server only + remote_server_uri = "https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow" + mlflow.set_tracking_uri(remote_server_uri) + + + + tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme + + # Model registry does not work with file store + if tracking_url_type_store != "file": + + # Register the model + # There are other ways to use the Model Registry, which depends on the use case, + # please refer to the doc for more information: + # https://mlflow.org/docs/latest/model-registry.html#api-workflow + mlflow.sklearn.log_model(lr, "model", registered_model_name="ElasticnetWineModel") + else: + mlflow.sklearn.log_model(lr, "model") \ No newline at end of file diff --git a/Chicken_Disease_Classification/research/trials.ipynb b/Chicken_Disease_Classification/research/trials.ipynb new file mode 100644 index 0000000000..e5e6c9fee3 --- /dev/null +++ b/Chicken_Disease_Classification/research/trials.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "d = {\"key\":\"value\", \"key1\":\"value1\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'value'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d['key']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'dict' object has no attribute 'key'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[3], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m d\u001b[39m.\u001b[39;49mkey\n", + "\u001b[1;31mAttributeError\u001b[0m: 'dict' object has no attribute 'key'" + ] + } + ], + "source": [ + "d.key" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from box import ConfigBox" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "d2 = ConfigBox({\"key\":\"value\", \"key1\":\"value1\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigBox({'key': 'value', 'key1': 'value1'})" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d2" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'value'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d2.key" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def get_prodict(x:int, y:int) -> int:\n", + " return x*y" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_prodict(x=2, y=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'44'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_prodict(x=2, y=\"4\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from ensure import ensure_annotations" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "@ensure_annotations\n", + "def get_prodict(x:int, y:int) -> int:\n", + " return x*y" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_prodict(x=2, y=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "ename": "EnsureError", + "evalue": "Argument y of type to does not match annotation type ", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mEnsureError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[14], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m get_prodict(x\u001b[39m=\u001b[39;49m\u001b[39m2\u001b[39;49m, y\u001b[39m=\u001b[39;49m\u001b[39m\"\u001b[39;49m\u001b[39m4\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n", + "File \u001b[1;32md:\\Softwares\\anaconda3\\envs\\chicken\\lib\\site-packages\\ensure\\main.py:845\u001b[0m, in \u001b[0;36mWrappedFunctionReturn.__call__\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 840\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39misinstance\u001b[39m(value, templ):\n\u001b[0;32m 841\u001b[0m msg \u001b[39m=\u001b[39m (\n\u001b[0;32m 842\u001b[0m \u001b[39m\"\u001b[39m\u001b[39mArgument \u001b[39m\u001b[39m{arg}\u001b[39;00m\u001b[39m of type \u001b[39m\u001b[39m{valt}\u001b[39;00m\u001b[39m to \u001b[39m\u001b[39m{f}\u001b[39;00m\u001b[39m \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 843\u001b[0m \u001b[39m\"\u001b[39m\u001b[39mdoes not match annotation type \u001b[39m\u001b[39m{t}\u001b[39;00m\u001b[39m\"\u001b[39m\n\u001b[0;32m 844\u001b[0m )\n\u001b[1;32m--> 845\u001b[0m \u001b[39mraise\u001b[39;00m EnsureError(msg\u001b[39m.\u001b[39mformat(\n\u001b[0;32m 846\u001b[0m arg\u001b[39m=\u001b[39marg, f\u001b[39m=\u001b[39m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mf, t\u001b[39m=\u001b[39mtempl, valt\u001b[39m=\u001b[39m\u001b[39mtype\u001b[39m(value)\n\u001b[0;32m 847\u001b[0m ))\n\u001b[0;32m 849\u001b[0m return_val \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mf(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs)\n\u001b[0;32m 850\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39misinstance\u001b[39m(return_val, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mreturn_templ):\n", + "\u001b[1;31mEnsureError\u001b[0m: Argument y of type to does not match annotation type " + ] + } + ], + "source": [ + "get_prodict(x=2, y=\"4\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chicken", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Chicken_Disease_Classification/scores.json b/Chicken_Disease_Classification/scores.json new file mode 100644 index 0000000000..f3e758cef5 --- /dev/null +++ b/Chicken_Disease_Classification/scores.json @@ -0,0 +1,4 @@ +{ + "loss": 0.4874950647354126, + "accuracy": 0.9137930870056152 +} \ No newline at end of file diff --git a/Chicken_Disease_Classification/setup.py b/Chicken_Disease_Classification/setup.py new file mode 100644 index 0000000000..3a348dca02 --- /dev/null +++ b/Chicken_Disease_Classification/setup.py @@ -0,0 +1,29 @@ +import setuptools + +with open("README.md", "r", encoding="utf-8") as f: + long_description = f.read() + + +__version__ = "0.0.0" + +REPO_NAME = "Chicken-Disease-Classification--Project" +AUTHOR_USER_NAME = "nvrcharan" +SRC_REPO = "cnnClassifier" +AUTHOR_EMAIL = "venkataraghucharan1@gmail.com" + + +setuptools.setup( + name=SRC_REPO, + version=__version__, + author=AUTHOR_USER_NAME, + author_email=AUTHOR_EMAIL, + description="A small python package for CNN app", + long_description=long_description, + long_description_content="text/markdown", + url=f"https://github.com/{AUTHOR_USER_NAME}/{REPO_NAME}", + project_urls={ + "Bug Tracker": f"https://github.com/{AUTHOR_USER_NAME}/{REPO_NAME}/issues", + }, + package_dir={"": "src"}, + packages=setuptools.find_packages(where="src") +) diff --git a/Chicken_Disease_Classification/src/cnnClassifier/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/__init__.py new file mode 100644 index 0000000000..258f281c36 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/__init__.py @@ -0,0 +1,23 @@ +import os +import sys +import logging + +logging_str = "[%(asctime)s: %(levelname)s: %(module)s: %(message)s]" + +log_dir = "logs" +log_filepath = os.path.join(log_dir,"running_logs.log") +os.makedirs(log_dir, exist_ok=True) + + +logging.basicConfig( + level= logging.INFO, + format= logging_str, + + handlers=[ + logging.FileHandler(log_filepath), + logging.StreamHandler(sys.stdout) + ] +) + +logger = logging.getLogger("cnnClassifierLogger") + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/components/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/data_ingestion.py b/Chicken_Disease_Classification/src/cnnClassifier/components/data_ingestion.py new file mode 100644 index 0000000000..638559d3d7 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/components/data_ingestion.py @@ -0,0 +1,38 @@ +import os +import urllib.request as request +import zipfile +from cnnClassifier import logger +from cnnClassifier.utils.common import get_size +from cnnClassifier.entity.config_entity import DataIngestionConfig +from pathlib import Path + + +class DataIngestion: + def __init__(self, config: DataIngestionConfig): + self.config = config + + + + def download_file(self): + if not os.path.exists(self.config.local_data_file): + filename, headers = request.urlretrieve( + url = self.config.source_URL, + filename = self.config.local_data_file + ) + logger.info(f"{filename} download! with following info: \n{headers}") + else: + logger.info(f"File already exists of size: {get_size(Path(self.config.local_data_file))}") + + + + def extract_zip_file(self): + """ + zip_file_path: str + Extracts the zip file into the data directory + Function returns None + """ + unzip_path = self.config.unzip_dir + os.makedirs(unzip_path, exist_ok=True) + with zipfile.ZipFile(self.config.local_data_file, 'r') as zip_ref: + zip_ref.extractall(unzip_path) + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/evaluation.py b/Chicken_Disease_Classification/src/cnnClassifier/components/evaluation.py new file mode 100644 index 0000000000..07730717e1 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/components/evaluation.py @@ -0,0 +1,78 @@ +import tensorflow as tf +import mlflow +import mlflow.keras +from urllib.parse import urlparse +from pathlib import Path +from cnnClassifier.entity.config_entity import EvaluationConfig +from cnnClassifier.utils.common import save_json + + +class Evaluation: + def __init__(self, config: EvaluationConfig): + self.config = config + + + def _valid_generator(self): + + datagenerator_kwargs = dict( + rescale = 1./255, + validation_split=0.30 + ) + + dataflow_kwargs = dict( + target_size=self.config.params_image_size[:-1], + batch_size=self.config.params_batch_size, + interpolation="bilinear" + ) + + valid_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator( + **datagenerator_kwargs + ) + + self.valid_generator = valid_datagenerator.flow_from_directory( + directory=self.config.training_data, + subset="validation", + shuffle=False, + **dataflow_kwargs + ) + + + @staticmethod + def load_model(path: Path) -> tf.keras.Model: + return tf.keras.models.load_model(path) + + + def evaluation(self): + self.model = self.load_model(self.config.path_of_model) + self._valid_generator() + self.score = self.model.evaluate(self.valid_generator) + + + def save_score(self): + scores = {"loss": self.score[0], "accuracy": self.score[1]} + save_json(path=Path("scores.json"), data=scores) + + + + def log_into_mlflow(self): + mlflow.set_registry_uri(self.config.mlflow_uri) + tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme + + with mlflow.start_run(): + mlflow.log_params(self.config.all_params) + mlflow.log_metrics( + {"loss": self.score[0], "accuracy": self.score[1]} + ) + # Model registry does not work with file store + if tracking_url_type_store != "file": + + # Register the model + # There are other ways to use the Model Registry, which depends on the use case, + # please refer to the doc for more information: + # https://mlflow.org/docs/latest/model-registry.html#api-workflow + mlflow.keras.log_model(self.model, "model", registered_model_name="VGG16Model") + else: + mlflow.keras.log_model(self.model, "model") + + + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_base_model.py b/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_base_model.py new file mode 100644 index 0000000000..f03c7871c0 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_base_model.py @@ -0,0 +1,73 @@ +import os +import urllib.request as request +from zipfile import ZipFile +import tensorflow as tf +from pathlib import Path +from cnnClassifier.entity.config_entity import PrepareBaseModelConfig + +class PrepareBaseModel: + def __init__(self, config: PrepareBaseModelConfig): + self.config = config + + + + def get_base_model(self): + self.model = tf.keras.applications.vgg16.VGG16( + input_shape=self.config.params_image_size, + weights=self.config.params_weights, + include_top=self.config.params_include_top + ) + + self.save_model(path=self.config.base_model_path, model=self.model) + + + + @staticmethod + def _prepare_full_model(model, classes, freeze_all, freeze_till, learning_rate): + if freeze_all: + for layer in model.layers: + model.trainable = False + elif (freeze_till is not None) and (freeze_till > 0): + for layer in model.layers[:-freeze_till]: + model.trainable = False + + flatten_in = tf.keras.layers.Flatten()(model.output) + prediction = tf.keras.layers.Dense( + units=classes, + activation="softmax" + )(flatten_in) + + full_model = tf.keras.models.Model( + inputs=model.input, + outputs=prediction + ) + + full_model.compile( + optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate), + loss=tf.keras.losses.CategoricalCrossentropy(), + metrics=["accuracy"] + ) + + full_model.summary() + return full_model + + + def update_base_model(self): + self.full_model = self._prepare_full_model( + model=self.model, + classes=self.config.params_classes, + freeze_all=True, + freeze_till=None, + learning_rate=self.config.params_learning_rate + ) + + self.save_model(path=self.config.updated_base_model_path, model=self.full_model) + + + @staticmethod + def save_model(path: Path, model: tf.keras.Model): + model.save(path) + + + + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_callbacks.py b/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_callbacks.py new file mode 100644 index 0000000000..032f432cdb --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/components/prepare_callbacks.py @@ -0,0 +1,37 @@ +import os +import urllib.request as request +from zipfile import ZipFile +import tensorflow as tf +import time +from cnnClassifier.entity.config_entity import PrepareCallbacksConfig + + +class PrepareCallback: + def __init__(self, config: PrepareCallbacksConfig): + self.config = config + + + + @property + def _create_tb_callbacks(self): + timestamp = time.strftime("%Y-%m-%d-%H-%M-%S") + tb_running_log_dir = os.path.join( + self.config.tensorboard_root_log_dir, + f"tb_logs_at_{timestamp}", + ) + return tf.keras.callbacks.TensorBoard(log_dir=tb_running_log_dir) + + + @property + def _create_ckpt_callbacks(self): + return tf.keras.callbacks.ModelCheckpoint( + filepath=self.config.checkpoint_model_filepath, + save_best_only=True + ) + + + def get_tb_ckpt_callbacks(self): + return [ + self._create_tb_callbacks, + self._create_ckpt_callbacks + ] diff --git a/Chicken_Disease_Classification/src/cnnClassifier/components/training.py b/Chicken_Disease_Classification/src/cnnClassifier/components/training.py new file mode 100644 index 0000000000..a753500638 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/components/training.py @@ -0,0 +1,81 @@ +from cnnClassifier.entity.config_entity import TrainingConfig +import tensorflow as tf +from pathlib import Path + + +class Training: + def __init__(self, config: TrainingConfig): + self.config = config + + def get_base_model(self): + self.model = tf.keras.models.load_model( + self.config.updated_base_model_path + ) + + def train_valid_generator(self): + + datagenerator_kwargs = dict( + rescale = 1./255, + validation_split=0.20 + ) + + dataflow_kwargs = dict( + target_size=self.config.params_image_size[:-1], + batch_size=self.config.params_batch_size, + interpolation="bilinear" + ) + + valid_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator( + **datagenerator_kwargs + ) + + self.valid_generator = valid_datagenerator.flow_from_directory( + directory=self.config.training_data, + subset="validation", + shuffle=False, + **dataflow_kwargs + ) + + if self.config.params_is_augmentation: + train_datagenerator = tf.keras.preprocessing.image.ImageDataGenerator( + rotation_range=40, + horizontal_flip=True, + width_shift_range=0.2, + height_shift_range=0.2, + shear_range=0.2, + zoom_range=0.2, + **datagenerator_kwargs + ) + else: + train_datagenerator = valid_datagenerator + + self.train_generator = train_datagenerator.flow_from_directory( + directory=self.config.training_data, + subset="training", + shuffle=True, + **dataflow_kwargs + ) + + @staticmethod + def save_model(path: Path, model: tf.keras.Model): + model.save(path) + + + def train(self, callback_list: list): + self.steps_per_epoch = self.train_generator.samples // self.train_generator.batch_size + self.validation_steps = self.valid_generator.samples // self.valid_generator.batch_size + + self.model.fit( + self.train_generator, + epochs=self.config.params_epochs, + steps_per_epoch=self.steps_per_epoch, + validation_steps=self.validation_steps, + validation_data=self.valid_generator, + callbacks=callback_list + ) + + self.save_model( + path=self.config.trained_model_path, + model=self.model + ) + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/config/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Chicken_Disease_Classification/src/cnnClassifier/config/configuration.py b/Chicken_Disease_Classification/src/cnnClassifier/config/configuration.py new file mode 100644 index 0000000000..a191029062 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/config/configuration.py @@ -0,0 +1,117 @@ +from cnnClassifier.constants import * +import os +from pathlib import Path +from cnnClassifier.utils.common import read_yaml, create_directories +from cnnClassifier.entity.config_entity import (DataIngestionConfig, + PrepareBaseModelConfig, + PrepareCallbacksConfig, + TrainingConfig, + EvaluationConfig) + + + +class ConfigurationManager: + def __init__( + self, + config_filepath = CONFIG_FILE_PATH, + params_filepath = PARAMS_FILE_PATH): + + self.config = read_yaml(config_filepath) + self.params = read_yaml(params_filepath) + + create_directories([self.config.artifacts_root]) + + + + def get_data_ingestion_config(self) -> DataIngestionConfig: + config = self.config.data_ingestion + + create_directories([config.root_dir]) + + data_ingestion_config = DataIngestionConfig( + root_dir=config.root_dir, + source_URL=config.source_URL, + local_data_file=config.local_data_file, + unzip_dir=config.unzip_dir + ) + + return data_ingestion_config + + + + + def get_prepare_base_model_config(self) -> PrepareBaseModelConfig: + config = self.config.prepare_base_model + + create_directories([config.root_dir]) + + prepare_base_model_config = PrepareBaseModelConfig( + root_dir=Path(config.root_dir), + base_model_path=Path(config.base_model_path), + updated_base_model_path=Path(config.updated_base_model_path), + params_image_size=self.params.IMAGE_SIZE, + params_learning_rate=self.params.LEARNING_RATE, + params_include_top=self.params.INCLUDE_TOP, + params_weights=self.params.WEIGHTS, + params_classes=self.params.CLASSES + ) + + return prepare_base_model_config + + + + def get_prepare_callback_config(self) -> PrepareCallbacksConfig: + config = self.config.prepare_callbacks + model_ckpt_dir = os.path.dirname(config.checkpoint_model_filepath) + create_directories([ + Path(model_ckpt_dir), + Path(config.tensorboard_root_log_dir) + ]) + + prepare_callback_config = PrepareCallbacksConfig( + root_dir=Path(config.root_dir), + tensorboard_root_log_dir=Path(config.tensorboard_root_log_dir), + checkpoint_model_filepath=Path(config.checkpoint_model_filepath) + ) + + return prepare_callback_config + + + + def get_training_config(self) -> TrainingConfig: + training = self.config.training + prepare_base_model = self.config.prepare_base_model + params = self.params + training_data = os.path.join(self.config.data_ingestion.unzip_dir, "Chicken-fecal-images") + create_directories([ + Path(training.root_dir) + ]) + + training_config = TrainingConfig( + root_dir=Path(training.root_dir), + trained_model_path=Path(training.trained_model_path), + updated_base_model_path=Path(prepare_base_model.updated_base_model_path), + training_data=Path(training_data), + params_epochs=params.EPOCHS, + params_batch_size=params.BATCH_SIZE, + params_is_augmentation=params.AUGMENTATION, + params_image_size=params.IMAGE_SIZE + ) + + return training_config + + + + + def get_evaluation_config(self) -> EvaluationConfig: + eval_config = EvaluationConfig( + path_of_model="artifacts/training/model.h5", + training_data="artifacts/data_ingestion/Chicken-fecal-images", + mlflow_uri="https://dagshub.com/entbappy/MLflow-DVC-Chicken-Disease-Classification.mlflow", + all_params=self.params, + params_image_size=self.params.IMAGE_SIZE, + params_batch_size=self.params.BATCH_SIZE + ) + return eval_config + + \ No newline at end of file diff --git a/Chicken_Disease_Classification/src/cnnClassifier/constants/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/constants/__init__.py new file mode 100644 index 0000000000..de2d074b35 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/constants/__init__.py @@ -0,0 +1,4 @@ +from pathlib import Path + +CONFIG_FILE_PATH = Path("config/config.yaml") +PARAMS_FILE_PATH = Path("params.yaml") \ No newline at end of file diff --git a/Chicken_Disease_Classification/src/cnnClassifier/entity/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/entity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Chicken_Disease_Classification/src/cnnClassifier/entity/config_entity.py b/Chicken_Disease_Classification/src/cnnClassifier/entity/config_entity.py new file mode 100644 index 0000000000..6c3a78c129 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/entity/config_entity.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class DataIngestionConfig: + root_dir: Path + source_URL: str + local_data_file: Path + unzip_dir: Path + + +@dataclass(frozen=True) +class PrepareBaseModelConfig: + root_dir: Path + base_model_path: Path + updated_base_model_path: Path + params_image_size: list + params_learning_rate: float + params_include_top: bool + params_weights: str + params_classes: int + + + +@dataclass(frozen=True) +class PrepareCallbacksConfig: + root_dir: Path + tensorboard_root_log_dir: Path + checkpoint_model_filepath: Path + + + +@dataclass(frozen=True) +class TrainingConfig: + root_dir: Path + trained_model_path: Path + updated_base_model_path: Path + training_data: Path + params_epochs: int + params_batch_size: int + params_is_augmentation: bool + params_image_size: list + + + +@dataclass(frozen=True) +class EvaluationConfig: + path_of_model: Path + training_data: Path + all_params: dict + mlflow_uri: str + params_image_size: list + params_batch_size: int diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/predict.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/predict.py new file mode 100644 index 0000000000..8386276883 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/predict.py @@ -0,0 +1,30 @@ +import numpy as np +from tensorflow.keras.models import load_model +from tensorflow.keras.preprocessing import image +import os + + + +class PredictionPipeline: + def __init__(self,filename): + self.filename =filename + + + + def predict(self): + # load model + model = load_model(os.path.join("artifacts","training", "model.h5")) + + imagename = self.filename + test_image = image.load_img(imagename, target_size = (224,224)) + test_image = image.img_to_array(test_image) + test_image = np.expand_dims(test_image, axis = 0) + result = np.argmax(model.predict(test_image), axis=1) + print(result) + + if result[0] == 1: + prediction = 'Healthy' + return [{ "image" : prediction}] + else: + prediction = 'Coccidiosis' + return [{ "image" : prediction}] diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_01_data_ingestion.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_01_data_ingestion.py new file mode 100644 index 0000000000..41e986dd35 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_01_data_ingestion.py @@ -0,0 +1,31 @@ +from cnnClassifier.config.configuration import ConfigurationManager +from cnnClassifier.components.data_ingestion import DataIngestion +from cnnClassifier import logger + + +STAGE_NAME = "Data Ingestion stage" + +class DataIngestionTrainingPipeline: + def __init__(self): + pass + + def main(self): + config = ConfigurationManager() + data_ingestion_config = config.get_data_ingestion_config() + data_ingestion = DataIngestion(config=data_ingestion_config) + data_ingestion.download_file() + data_ingestion.extract_zip_file() + + + + +if __name__ == '__main__': + try: + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + obj = DataIngestionTrainingPipeline() + obj.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") + except Exception as e: + logger.exception(e) + raise e + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_02_prepare_base_model.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_02_prepare_base_model.py new file mode 100644 index 0000000000..c6916a895a --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_02_prepare_base_model.py @@ -0,0 +1,34 @@ +from cnnClassifier.config.configuration import ConfigurationManager +from cnnClassifier.components.prepare_base_model import PrepareBaseModel +from cnnClassifier import logger + + +STAGE_NAME = "Prepare base model" + +class PrepareBaseModelTrainingPipeline: + def __init__(self): + pass + + def main(self): + config = ConfigurationManager() + prepare_base_model_config = config.get_prepare_base_model_config() + prepare_base_model = PrepareBaseModel(config=prepare_base_model_config) + prepare_base_model.get_base_model() + prepare_base_model.update_base_model() + + + + + +if __name__ == '__main__': + try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + obj = PrepareBaseModelTrainingPipeline() + obj.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") + except Exception as e: + logger.exception(e) + raise e + + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_03_training.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_03_training.py new file mode 100644 index 0000000000..0aae4e65f0 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_03_training.py @@ -0,0 +1,43 @@ +from cnnClassifier.config.configuration import ConfigurationManager +from cnnClassifier.components.prepare_callbacks import PrepareCallback +from cnnClassifier.components.training import Training +from cnnClassifier import logger + + + +STAGE_NAME = "Training" + + +class ModelTrainingPipeline: + def __init__(self): + pass + + def main(self): + config = ConfigurationManager() + prepare_callbacks_config = config.get_prepare_callback_config() + prepare_callbacks = PrepareCallback(config=prepare_callbacks_config) + callback_list = prepare_callbacks.get_tb_ckpt_callbacks() + + + training_config = config.get_training_config() + training = Training(config=training_config) + training.get_base_model() + training.train_valid_generator() + training.train( + callback_list=callback_list + ) + + + + +if __name__ == '__main__': + try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + obj = ModelTrainingPipeline() + obj.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") + except Exception as e: + logger.exception(e) + raise e + diff --git a/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_04_evaluation.py b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_04_evaluation.py new file mode 100644 index 0000000000..1348dae306 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/pipeline/stage_04_evaluation.py @@ -0,0 +1,35 @@ +from cnnClassifier.config.configuration import ConfigurationManager +from cnnClassifier.components.evaluation import Evaluation +from cnnClassifier import logger + + + + +STAGE_NAME = "Evaluation stage" + + +class EvaluationPipeline: + def __init__(self): + pass + + def main(self): + config = ConfigurationManager() + eval_config = config.get_evaluation_config() + evaluation = Evaluation(eval_config) + evaluation.evaluation() + evaluation.save_score() + evaluation.log_into_mlflow() + + + +if __name__ == '__main__': + try: + logger.info(f"*******************") + logger.info(f">>>>>> stage {STAGE_NAME} started <<<<<<") + obj = EvaluationPipeline() + obj.main() + logger.info(f">>>>>> stage {STAGE_NAME} completed <<<<<<\n\nx==========x") + except Exception as e: + logger.exception(e) + raise e + \ No newline at end of file diff --git a/Chicken_Disease_Classification/src/cnnClassifier/utils/__init__.py b/Chicken_Disease_Classification/src/cnnClassifier/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Chicken_Disease_Classification/src/cnnClassifier/utils/common.py b/Chicken_Disease_Classification/src/cnnClassifier/utils/common.py new file mode 100644 index 0000000000..7b3a795cb4 --- /dev/null +++ b/Chicken_Disease_Classification/src/cnnClassifier/utils/common.py @@ -0,0 +1,138 @@ +import os +from box.exceptions import BoxValueError +import yaml +from cnnClassifier import logger +import json +import joblib +from ensure import ensure_annotations +from box import ConfigBox +from pathlib import Path +from typing import Any +import base64 + + + +@ensure_annotations +def read_yaml(path_to_yaml: Path) -> ConfigBox: + """reads yaml file and returns + + Args: + path_to_yaml (str): path like input + + Raises: + ValueError: if yaml file is empty + e: empty file + + Returns: + ConfigBox: ConfigBox type + """ + try: + with open(path_to_yaml) as yaml_file: + content = yaml.safe_load(yaml_file) + logger.info(f"yaml file: {path_to_yaml} loaded successfully") + return ConfigBox(content) + except BoxValueError: + raise ValueError("yaml file is empty") + except Exception as e: + raise e + + + +@ensure_annotations +def create_directories(path_to_directories: list, verbose=True): + """create list of directories + + Args: + path_to_directories (list): list of path of directories + ignore_log (bool, optional): ignore if multiple dirs is to be created. Defaults to False. + """ + for path in path_to_directories: + os.makedirs(path, exist_ok=True) + if verbose: + logger.info(f"created directory at: {path}") + + +@ensure_annotations +def save_json(path: Path, data: dict): + """save json data + + Args: + path (Path): path to json file + data (dict): data to be saved in json file + """ + with open(path, "w") as f: + json.dump(data, f, indent=4) + + logger.info(f"json file saved at: {path}") + + + + +@ensure_annotations +def load_json(path: Path) -> ConfigBox: + """load json files data + + Args: + path (Path): path to json file + + Returns: + ConfigBox: data as class attributes instead of dict + """ + with open(path) as f: + content = json.load(f) + + logger.info(f"json file loaded succesfully from: {path}") + return ConfigBox(content) + + +@ensure_annotations +def save_bin(data: Any, path: Path): + """save binary file + + Args: + data (Any): data to be saved as binary + path (Path): path to binary file + """ + joblib.dump(value=data, filename=path) + logger.info(f"binary file saved at: {path}") + + +@ensure_annotations +def load_bin(path: Path) -> Any: + """load binary data + + Args: + path (Path): path to binary file + + Returns: + Any: object stored in the file + """ + data = joblib.load(path) + logger.info(f"binary file loaded from: {path}") + return data + +@ensure_annotations +def get_size(path: Path) -> str: + """get size in KB + + Args: + path (Path): path of the file + + Returns: + str: size in KB + """ + size_in_kb = round(os.path.getsize(path)/1024) + return f"~ {size_in_kb} KB" + + +def decodeImage(imgstring, fileName): + imgdata = base64.b64decode(imgstring) + with open(fileName, 'wb') as f: + f.write(imgdata) + f.close() + + +def encodeImageIntoBase64(croppedImagePath): + with open(croppedImagePath, "rb") as f: + return base64.b64encode(f.read()) + diff --git a/Chicken_Disease_Classification/template.py b/Chicken_Disease_Classification/template.py new file mode 100644 index 0000000000..e09892ddaa --- /dev/null +++ b/Chicken_Disease_Classification/template.py @@ -0,0 +1,48 @@ +import os +from pathlib import Path +import logging + +logging.basicConfig(level=logging.INFO, format='[%(asctime)s]: %(message)s:') + + +project_name = "cnnClassifier" + +list_of_files = [ + ".github/workflows/.gitkeep", + f"src/{project_name}/__init__.py", + f"src/{project_name}/components/__init__.py", + f"src/{project_name}/utils/__init__.py", + f"src/{project_name}/config/__init__.py", + f"src/{project_name}/config/configuration.py", + f"src/{project_name}/pipeline/__init__.py", + f"src/{project_name}/entity/__init__.py", + f"src/{project_name}/constants/__init__.py", + "config/config.yaml", + "dvc.yaml", + "params.yaml", + "requirements.txt", + "setup.py", + "research/trials.ipynb", + "templates/index.html" + + +] + + +for filepath in list_of_files: + filepath = Path(filepath) + filedir, filename = os.path.split(filepath) + + + if filedir !="": + os.makedirs(filedir, exist_ok=True) + logging.info(f"Creating directory; {filedir} for the file: {filename}") + + if (not os.path.exists(filepath)) or (os.path.getsize(filepath) == 0): + with open(filepath, "w") as f: + pass + logging.info(f"Creating empty file: {filepath}") + + + else: + logging.info(f"{filename} is already exists") \ No newline at end of file diff --git a/Chicken_Disease_Classification/templates/index.html b/Chicken_Disease_Classification/templates/index.html new file mode 100644 index 0000000000..437f62be6e --- /dev/null +++ b/Chicken_Disease_Classification/templates/index.html @@ -0,0 +1,234 @@ + + + + + + + cnncls + + + + + +
+
+

Object Classification

+
+
+
+ + + +
+
+
+
+ + +
+ + + + + + + + + + + + +
+
+
+
+
+
Prediction Results
+
+
+
+
+
+
+
+
+ + + + +
+ + + + + + + \ No newline at end of file