diff --git a/.github/workflows/sync-to-huggingface.yml b/.github/workflows/sync-to-huggingface.yml new file mode 100644 index 0000000000000000000000000000000000000000..ec7f2e3453a38f1acb5a1f66282ecb18cba8392f --- /dev/null +++ b/.github/workflows/sync-to-huggingface.yml @@ -0,0 +1,21 @@ +name: Sync to Hugging Face hub +on: + push: + branches: [main] + + # to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + sync-to-hub: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + fetch-depth: 0 + lfs: true + - name: Push to hub + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + run: git push --force https://Sadashiv:$HF_TOKEN@huggingface.co/spaces/Sadashiv/CropGaurd main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ee665b33b4ecd93fda77e44ca7c8e9d8ee54772e --- /dev/null +++ b/.gitignore @@ -0,0 +1,164 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +main.py +extra_notebook +notebok \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..c8a9f49a75bb6d4229e37376a66dc5bf124e569f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "Fertilizer-Recommendation"] + path = Fertilizer-Recommendation + url = https://github.com/07Sada/Fertilizer-Recommendation.git +[submodule "crop-recommendation"] + path = crop-recommendation + url = https://github.com/07Sada/crop-recommendation.git +[submodule "plant-diseases-classifier"] + path = plant-diseases-classifier + url = https://github.com/07Sada/plant-diseases-classifier.git diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..f2443f916752e0f49a4510f1a3fc4761e34b4f45 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "mongodb.mongodb-vscode", + "ms-python.python", + "ms-toolsai.jupyter", + "ms-toolsai.jupyter-keymap", + "ms-toolsai.jupyter-renderers", + "formulahendry.code-runner" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..7ecf6fd89f583d7735afa16d25efae8c4a8d4c0a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "workbench.colorTheme": "Cobalt2", + "workbench.preferredDarkColorTheme": "Default Dark+", + "task.allowAutomaticTasks": "on", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..df60023539b2e7a04dd3b9cf841b61cc8b851cc6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Installing extensions and dependencies...", + "type": "shell", + "command": "code-server --install-extension mongodb.mongodb-vscode --install-extension ms-python.python --install-extension formulahendry.code-runner && pip install -r requirements.txt", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "runOptions": { "runOn": "folderOpen" } + } + ] +} diff --git a/Fertilizer-Recommendation/.gitignore b/Fertilizer-Recommendation/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..df58e4f5bdca3651c16cb73851db7a1f35fe2b57 --- /dev/null +++ b/Fertilizer-Recommendation/.gitignore @@ -0,0 +1,166 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +data_dump.py +dataset_download.py +demo.ipynb + +artifact +catboost_info \ No newline at end of file diff --git a/Fertilizer-Recommendation/.vscode/extensions.json b/Fertilizer-Recommendation/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..c9c6c690032684d0217fd13c78d2f91ac478c219 --- /dev/null +++ b/Fertilizer-Recommendation/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + "recommendations": [ + "mongodb.mongodb-vscode", + "ms-python.python", + "ms-toolsai.jupyter", + "ms-toolsai.jupyter-keymap", + "ms-toolsai.jupyter-renderers", + "formulahendry.code-runner", + "wesbos.theme-cobalt2", + "PKief.material-icon-theme", + "wesbos.theme-cobalt2" + ] +} diff --git a/Fertilizer-Recommendation/.vscode/settings.json b/Fertilizer-Recommendation/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..c77dce512bea97b77c181731955c0e95e8e202fc --- /dev/null +++ b/Fertilizer-Recommendation/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "workbench.colorTheme": "Cobalt2", + "workbench.preferredDarkColorTheme": "Cobalt2", + "task.allowAutomaticTasks": "on", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } +} diff --git a/Fertilizer-Recommendation/.vscode/tasks.json b/Fertilizer-Recommendation/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..df60023539b2e7a04dd3b9cf841b61cc8b851cc6 --- /dev/null +++ b/Fertilizer-Recommendation/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Installing extensions and dependencies...", + "type": "shell", + "command": "code-server --install-extension mongodb.mongodb-vscode --install-extension ms-python.python --install-extension formulahendry.code-runner && pip install -r requirements.txt", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "runOptions": { "runOn": "folderOpen" } + } + ] +} diff --git a/Fertilizer-Recommendation/LICENSE b/Fertilizer-Recommendation/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e121f2e91d7901e575e89d72fbae0276c0409c4e --- /dev/null +++ b/Fertilizer-Recommendation/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sadashiv Nandanikar + +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/Fertilizer-Recommendation/README.md b/Fertilizer-Recommendation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e29c75b46ee7417da6f8053736aec25e1e244cba --- /dev/null +++ b/Fertilizer-Recommendation/README.md @@ -0,0 +1,43 @@ +# Fertilizer Recommendation +### Powered by machine learning models, evaluates input factors to provide precise fertilizer recommendations, enhancing crop health and productivity. + +## Demo +### Input Interface +Image 1 + +### Output Interface +Image 1 + +## Data Source +This dataset contains information about the soil, environmental conditions, crop type, and fertilizer use for different crops. The dataset includes the following columns: + +- `Temparature`: The temperature in degrees Celsius. +- `Humidity`: The relative humidity in percent. +- `Moisture`: The moisture content of the soil in percent. +- `Soil Type`: The type of soil. +- `Crop Type`: The type of crop. +- `Nitrogen`: The amount of nitrogen in the soil in kilograms per hectare. +- `Potassium`: The amount of potassium in the soil in kilograms per hectare. +- `Phosphorous`: The amount of phosphorus in the soil in kilograms per hectare. +- `Fertilizer Name`: The name of the fertilizer used. + +[Link](https://www.kaggle.com/datasets/gdabhishek/fertilizer-prediction) for the dataset + +
+ Supported fertilizer + + +- UREA +- DAP +- 14-35-14 +- 28-28 +- 17-17-17 +- 20-20 +- 10-26-26 +
+ +## Project Details +This repository is submodule for [CropGaurd](https://github.com/07Sada/CropGaurd.git) + +## Project PipeLine Stages +![Project PipeLine Stages](https://user-images.githubusercontent.com/112761379/225940480-2a7381b2-6abd-4c1c-8287-0fd49099be8c.jpg) diff --git a/Fertilizer-Recommendation/fertilizer-prediction/Fertilizer Prediction.csv b/Fertilizer-Recommendation/fertilizer-prediction/Fertilizer Prediction.csv new file mode 100644 index 0000000000000000000000000000000000000000..f3cb521b378fab010faf565d130a2fb070b158de --- /dev/null +++ b/Fertilizer-Recommendation/fertilizer-prediction/Fertilizer Prediction.csv @@ -0,0 +1,100 @@ +Temparature,Humidity ,Moisture,Soil Type,Crop Type,Nitrogen,Potassium,Phosphorous,Fertilizer Name +26,52,38,Sandy,Maize,37,0,0,Urea +29,52,45,Loamy,Sugarcane,12,0,36,DAP +34,65,62,Black,Cotton,7,9,30,14-35-14 +32,62,34,Red,Tobacco,22,0,20,28-28 +28,54,46,Clayey,Paddy,35,0,0,Urea +26,52,35,Sandy,Barley,12,10,13,17-17-17 +25,50,64,Red,Cotton,9,0,10,20-20 +33,64,50,Loamy,Wheat,41,0,0,Urea +30,60,42,Sandy,Millets,21,0,18,28-28 +29,58,33,Black,Oil seeds,9,7,30,14-35-14 +27,54,28,Clayey,Pulses,13,0,40,DAP +31,62,48,Sandy,Maize,14,15,12,17-17-17 +25,50,65,Loamy,Cotton,36,0,0,Urea +32,62,41,Clayey,Paddy,24,0,22,28-28 +26,52,31,Red,Ground Nuts,14,0,41,DAP +31,62,49,Black,Sugarcane,10,13,14,17-17-17 +33,64,34,Clayey,Pulses,38,0,0,Urea +25,50,39,Sandy,Barley,21,0,19,28-28 +28,54,65,Black,Cotton,39,0,0,Urea +29,58,52,Loamy,Wheat,13,0,36,DAP +30,60,44,Sandy,Millets,10,0,9,20-20 +34,65,53,Loamy,Sugarcane,12,14,12,17-17-17 +35,68,33,Red,Tobacco,11,0,37,DAP +28,54,37,Black,Millets,36,0,0,Urea +33,64,39,Clayey,Paddy,13,0,10,20-20 +26,52,44,Sandy,Maize,23,0,20,28-28 +30,60,63,Red,Cotton,9,9,29,14-35-14 +32,62,30,Loamy,Sugarcane,38,0,0,Urea +37,70,32,Black,Oil seeds,12,0,39,DAP +26,52,36,Clayey,Pulses,14,0,13,20-20 +29,58,40,Red,Ground Nuts,24,0,23,28-28 +30,60,27,Loamy,Sugarcane,12,0,40,DAP +34,65,38,Clayey,Paddy,39,0,0,Urea +36,68,38,Sandy,Barley,7,9,30,14-35-14 +26,52,48,Loamy,Wheat,23,0,19,28-28 +28,54,35,Black,Millets,41,0,0,Urea +30,60,61,Loamy,Cotton,8,10,31,14-35-14 +37,70,37,Clayey,Paddy,12,0,41,DAP +25,50,26,Red,Ground Nuts,15,14,11,17-17-17 +29,58,34,Sandy,Millets,15,0,37,DAP +27,54,30,Clayey,Pulses,13,0,13,20-20 +30,60,58,Loamy,Sugarcane,10,7,32,14-35-14 +32,62,34,Red,Tobacco,22,0,24,28-28 +34,65,60,Black,Sugarcane,35,0,0,Urea +35,67,42,Sandy,Barley,10,0,35,DAP +38,70,48,Loamy,Wheat,8,8,28,14-35-14 +26,52,32,Black,Oil seeds,12,0,8,20-20 +29,58,43,Clayey,Paddy,24,0,18,28-28 +30,60,29,Red,Ground Nuts,41,0,0,Urea +33,64,51,Sandy,Maize,5,9,29,14-35-14 +34,65,31,Red,Tobacco,23,0,21,28-28 +36,68,33,Black,Oil seeds,13,0,14,20-20 +28,54,38,Clayey,Pulses,40,0,0,Urea +30,60,47,Sandy,Barley,12,0,42,DAP +31,62,63,Red,Cotton,11,12,15,17-17-17 +27,53,43,Black,Millets,23,0,24,28-28 +34,65,54,Loamy,Wheat,38,0,0,Urea +29,58,37,Sandy,Millets,8,0,15,20-20 +25,50,56,Loamy,Sugarcane,11,13,15,17-17-17 +32,62,34,Red,Ground Nuts,15,0,37,DAP +28,54,41,Clayey,Paddy,36,0,0,Urea +30,60,49,Loamy,Wheat,13,0,9,20-20 +34,65,64,Black,Cotton,24,0,20,28-28 +28,54,47,Sandy,Barley,5,18,15,10-26-26 +27,53,35,Black,Oil seeds,37,0,0,Urea +36,68,62,Red,Cotton,15,0,40,DAP +34,65,57,Black,Sugarcane,9,0,13,20-20 +29,58,55,Loamy,Sugarcane,8,8,33,14-35-14 +25,50,40,Clayey,Pulses,6,19,16,10-26-26 +30,60,38,Sandy,Millets,10,0,14,20-20 +26,52,39,Clayey,Pulses,21,0,23,28-28 +31,62,32,Red,Tobacco,39,0,0,Urea +34,65,48,Loamy,Wheat,23,0,19,28-28 +27,53,34,Black,Oil seeds,42,0,0,Urea +33,64,31,Red,Ground Nuts,13,0,39,DAP +29,58,42,Clayey,Paddy,9,10,22,14-35-14 +30,60,47,Sandy,Maize,22,0,21,28-28 +27,53,59,Loamy,Sugarcane,10,0,15,20-20 +26,52,36,Clayey,Pulses,7,16,20,10-26-26 +34,65,63,Red,Cotton,14,0,38,DAP +28,54,43,Clayey,Paddy,10,8,29,14-35-14 +30,60,40,Sandy,Millets,41,0,0,Urea +29,58,65,Black,Cotton,14,0,35,DAP +26,52,59,Loamy,Sugarcane,11,0,9,20-20 +31,62,44,Sandy,Barley,21,0,28,28-28 +35,67,28,Clayey,Pulses,8,7,31,14-35-14 +29,58,30,Red,Tobacco,13,17,16,10-26-26 +27,53,30,Black,Millets,35,0,0,Urea +36,68,50,Loamy,Wheat,12,18,19,10-26-26 +29,58,61,Loamy,Cotton,11,0,38,DAP +30,60,26,Black,Oil seeds,8,9,30,14-35-14 +34,65,45,Clayey,Paddy,6,19,21,10-26-26 +36,68,41,Red,Ground Nuts,41,0,0,Urea +28,54,25,Sandy,Maize,9,10,30,14-35-14 +25,50,32,Clayey,Pulses,24,0,19,28-28 +30,60,27,Red,Tobacco,4,17,17,10-26-26 +38,72,51,Loamy,Wheat,39,0,0,Urea +36,60,43,Sandy,Millets,15,0,41,DAP +29,58,57,Black,Sugarcane,12,0,10,20-20 diff --git a/Fertilizer-Recommendation/main.py b/Fertilizer-Recommendation/main.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1cae98b9797ac75b945bd5fa30517b44b16ad7 --- /dev/null +++ b/Fertilizer-Recommendation/main.py @@ -0,0 +1,8 @@ +from src.pipeline.training_pipeline import start_training_pipeline + +if __name__ =="__main__": + try: + start_training_pipeline() + + except Exception as e: + print(e) \ No newline at end of file diff --git a/Fertilizer-Recommendation/notebook/fertilizer-prediction.ipynb b/Fertilizer-Recommendation/notebook/fertilizer-prediction.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..482baef1d318c5e62a0104df3c3fcc55a7aed756 --- /dev/null +++ b/Fertilizer-Recommendation/notebook/fertilizer-prediction.ipynb @@ -0,0 +1,736 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np \n", + "import matplotlib.pyplot as plt \n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import OneHotEncoder\n", + "from sklearn.preprocessing import LabelEncoder\n", + "from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score\n", + "\n", + "from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, AdaBoostClassifier\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "from xgboost import XGBClassifier\n", + "from catboost import CatBoostClassifier\n", + "\n", + "from sklearn.compose import ColumnTransformer\n", + "from sklearn.pipeline import Pipeline\n", + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "import warnings\n", + "\n", + "# Ignore warnings\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.chdir(\"/config/workspace\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TemparatureHumidityMoistureSoil TypeCrop TypeNitrogenPotassiumPhosphorousFertilizer Name
0265238SandyMaize3700Urea
1295245LoamySugarcane12036DAP
2346562BlackCotton793014-35-14
3326234RedTobacco2202028-28
4285446ClayeyPaddy3500Urea
\n", + "
" + ], + "text/plain": [ + " Temparature Humidity Moisture Soil Type Crop Type Nitrogen Potassium \\\n", + "0 26 52 38 Sandy Maize 37 0 \n", + "1 29 52 45 Loamy Sugarcane 12 0 \n", + "2 34 65 62 Black Cotton 7 9 \n", + "3 32 62 34 Red Tobacco 22 0 \n", + "4 28 54 46 Clayey Paddy 35 0 \n", + "\n", + " Phosphorous Fertilizer Name \n", + "0 0 Urea \n", + "1 36 DAP \n", + "2 30 14-35-14 \n", + "3 20 28-28 \n", + "4 0 Urea " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "FILE_PATH =r\"fertilizer-prediction/Fertilizer Prediction.csv\"\n", + "\n", + "# Loading the dataset into pandas\n", + "df = pd.read_csv(FILE_PATH)\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of the dataset: (99, 9)\n" + ] + } + ], + "source": [ + "print(f\"Shape of the dataset: {df.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 99 entries, 0 to 98\n", + "Data columns (total 9 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 Temparature 99 non-null int64 \n", + " 1 Humidity 99 non-null int64 \n", + " 2 Moisture 99 non-null int64 \n", + " 3 Soil Type 99 non-null object\n", + " 4 Crop Type 99 non-null object\n", + " 5 Nitrogen 99 non-null int64 \n", + " 6 Potassium 99 non-null int64 \n", + " 7 Phosphorous 99 non-null int64 \n", + " 8 Fertilizer Name 99 non-null object\n", + "dtypes: int64(6), object(3)\n", + "memory usage: 7.1+ KB\n" + ] + } + ], + "source": [ + "# datatypes \n", + "df.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Temparature 0\n", + "Humidity 0\n", + "Moisture 0\n", + "Soil Type 0\n", + "Crop Type 0\n", + "Nitrogen 0\n", + "Potassium 0\n", + "Phosphorous 0\n", + "Fertilizer Name 0\n", + "dtype: int64" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# checking for null values \n", + "df.isnull().sum()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0kAAAJwCAYAAABceyqRAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABC4ElEQVR4nO3dd5RV1d0//s+lzQxtDAoMHaxY0aBBbCAWwKhYosZHI6IhxhYVK4lSLA8xRsF8o5hoAE2MBQ1GMRqVpkGxIBpBg4gY9JESiYCAFJn9+8PF/Z0RhjICQ3m91rprcfbZZ5/Pvefey7znnLMnl1JKAQAAQEREVKnsAgAAALYkQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISANuNjz76KHK5XAwbNmyT72vYsGGRy+Xio48+yre1bNkyjj/++E2+74iIsWPHRi6Xi7Fjx26W/QFsS4QkgC3cqh+2Vz0KCwujcePG0blz5/jNb34TX3zxRYXHfvnll6Nfv34xf/78jVfwt3D33XdvUIDJvi7VqlWLevXqRdu2beOyyy6Ld999t9Lq2py25NoAtla5lFKq7CIAKN+wYcOiR48eceONN0arVq1ixYoVMXv27Bg7dmw8//zz0bx583jyySdjv/322+Cxf/3rX8fVV18dM2bMiJYtW2784jfQPvvsEzvttNN6n/3I5XJxzDHHxDnnnBMppViwYEG8/fbbMXz48Fi8eHHceuut0atXr3z/lFIsW7YsqlevHlWrVt1kdUVErFy5MlasWBEFBQWRy+Ui4uszSfvss0+MHDlyvcepaG2lpaWxfPnyqFGjRlSp4neiABuiWmUXAMD66dq1axx44IH55d69e8fo0aPj+OOPjxNPPDHee++9KCoqqsQKK8fuu+8eZ599dpm2X/7yl3HCCSfElVdeGa1bt47jjjsuIiJ/Jm5TWrx4cdSqVSuqVq26QUFsY6tSpcomf64A2yq/WgLYinXq1CluuOGG+Pe//x1/+tOf8u3//Oc/49xzz42dd945CgsLo6SkJM4777yYN29evk+/fv3i6quvjoiIVq1a5S9bW3UPzdChQ6NTp07RoEGDKCgoiL322isGDx68Wg1vvPFGdO7cOXbaaacoKiqKVq1axXnnnVemT2lpaQwaNCj23nvvKCwsjIYNG8YFF1wQn3/+eb5Py5YtY8qUKTFu3Lh8LR07dqzQ67LjjjvGww8/HNWqVYtbbrkl376me5Jmz54dPXr0iKZNm0ZBQUE0atQounXrln8d1lbXqkshx40bFxdddFE0aNAgmjZtWmZd9p6kVZ577rnYf//9o7CwMPbaa6/4y1/+UmZ9v3798mefsr455tpqK++epOHDh0fbtm2jqKgodtpppzj77LPj//7v/8r0Offcc6N27drxf//3f3HSSSdF7dq1o379+nHVVVfFypUr1/HqA2z9nEkC2Mr96Ec/ip///Ofx3HPPRc+ePSMi4vnnn48PP/wwevToESUlJTFlypT4/e9/H1OmTIkJEyZELpeLU045Jd5///146KGHYuDAgbHTTjtFRET9+vUjImLw4MGx9957x4knnhjVqlWLp556Ki666KIoLS2Niy++OCIi5s6dG8cee2zUr18/rrvuuthhhx3io48+Wu2H/gsuuCB/2eDPfvazmDFjRvz2t7+NSZMmxfjx46N69eoxaNCguPTSS6N27drxi1/8IiIiGjZsWOHXpXnz5tGhQ4cYM2ZMLFy4MOrWrbvGfqeeempMmTIlLr300mjZsmXMnTs3nn/++Zg5c2a0bNlyveq66KKLon79+tGnT59YvHjxWuuaNm1anHHGGfHTn/40unfvHkOHDo3TTjstnn322TjmmGM26Dlu6Gu26hgcdNBBMWDAgJgzZ07ceeedMX78+Jg0aVLssMMO+b4rV66Mzp07R7t27eLXv/51vPDCC3H77bfHLrvsEhdeeOEG1Qmw1UkAbNGGDh2aIiK9/vrr5fYpLi5OBxxwQH55yZIlq/V56KGHUkSkF198Md922223pYhIM2bMWK3/msbo3Llz2nnnnfPLI0aMWGdtL730UoqI9OCDD5Zpf/bZZ1dr33vvvVOHDh3KHeubIiJdfPHF5a6/7LLLUkSkt99+O6WU0owZM1JEpKFDh6aUUvr8889TRKTbbrttrfspr65Vx+awww5LX3311RrXZV/bFi1apIhIjz/+eL5twYIFqVGjRmWOX9++fdOa/ote05jl1TZmzJgUEWnMmDEppZSWL1+eGjRokPbZZ5/05Zdf5vuNHDkyRUTq06dPvq179+4pItKNN95YZswDDjggtW3bdrV9AWxrXG4HsA2oXbt2mVnusvcmLV26ND777LM4+OCDIyLizTffXK8xs2MsWLAgPvvss+jQoUN8+OGHsWDBgoiI/JmHkSNHxooVK9Y4zvDhw6O4uDiOOeaY+Oyzz/KPtm3bRu3atWPMmDEb9Fw3RO3atSMiyp0BsKioKGrUqBFjx44tc+nfhurZs+d633/UuHHjOPnkk/PLdevWjXPOOScmTZoUs2fPrnAN6/LGG2/E3Llz46KLLipzr9L3v//9aN26dTz99NOrbfPTn/60zPLhhx8eH3744SarEWBLISQBbAMWLVoUderUyS//97//jcsuuywaNmwYRUVFUb9+/WjVqlVERD7grMv48ePj6KOPjlq1asUOO+wQ9evXj5///OdlxujQoUOceuqp0b9//9hpp52iW7duMXTo0Fi2bFl+nGnTpsWCBQuiQYMGUb9+/TKPRYsWxdy5czfWy7CaRYsWRUSUeW2yCgoK4tZbb41nnnkmGjZsGEcccUT86le/2uCwsuq1XR+77rrravcb7b777hERa7x/aWP597//HRERe+yxx2rrWrdunV+/SmFhYf7Sy1W+853vfKswCbC1cE8SwFbuk08+iQULFsSuu+6abzv99NPj5Zdfjquvvjr233//qF27dpSWlkaXLl2itLR0nWNOnz49jjrqqGjdunXccccd0axZs6hRo0b87W9/i4EDB+bHyOVy8dhjj8WECRPiqaeeir///e9x3nnnxe233x4TJkzI77dBgwbx4IMPrnFf3/xBfGOaPHlyVK1ada0h5vLLL48TTjghnnjiifj73/8eN9xwQwwYMCBGjx4dBxxwwHrtZ2PPKrimSRsiYrNOmlCZM/MBVDYhCWAr98c//jEiIjp37hwREZ9//nmMGjUq+vfvH3369Mn3mzZt2mrblvfD+FNPPRXLli2LJ598Mpo3b55vL+/SuIMPPjgOPvjguOWWW+LPf/5znHXWWfHwww/Hj3/849hll13ihRdeiEMPPXSdYaK8eipi5syZMW7cuGjfvn25Z5JW2WWXXeLKK6+MK6+8MqZNmxb7779/3H777fkZAzdmXR988EGklMqM+f7770dE5P9W1Xe+852IiJg/f36ZyRS+ebZnQ2pr0aJFRERMnTo1OnXqVGbd1KlT8+sBcLkdwFZt9OjRcdNNN0WrVq3irLPOioj//wxA+sbfCh80aNBq29eqVSsivv5hPGtNYyxYsCCGDh1apt/nn3++2n7233//iIj8JXenn356rFy5Mm666abV9v/VV1+V2XetWrVWq6Ui/vvf/8aZZ54ZK1euzM/6tiZLliyJpUuXlmnbZZddok6dOmUuGdxYdUVEfPrppzFixIj88sKFC+OBBx6I/fffP0pKSvI1RES8+OKL+X6LFy+O+++/f7Xx1re2Aw88MBo0aBD33HNPmef2zDPPxHvvvRff//73K/qUALY5ziQBbCWeeeaZ+Ne//hVfffVVzJkzJ0aPHh3PP/98tGjRIp588sn8zfh169bN31uzYsWKaNKkSTz33HMxY8aM1cZs27ZtRET84he/iB/+8IdRvXr1OOGEE+LYY4+NGjVqxAknnBAXXHBBLFq0KO69995o0KBBzJo1K7/9/fffH3fffXecfPLJscsuu8QXX3wR9957b9StWzf/B1w7dOgQF1xwQQwYMCDeeuutOPbYY6N69eoxbdq0GD58eNx5553xgx/8IF/P4MGD4+abb45dd901GjRosNpZj296//33409/+lOklGLhwoXx9ttvx/Dhw2PRokVxxx13RJcuXda67VFHHRWnn3567LXXXlGtWrUYMWJEzJkzJ374wx+WeZ02tK7y7L777nH++efH66+/Hg0bNowhQ4bEnDlzygTQY489Npo3bx7nn39+XH311VG1atUYMmRI1K9fP2bOnFlmvPWtrXr16nHrrbdGjx49okOHDnHmmWfmpwBv2bJlXHHFFRV6PgDbpEqdWw+AdVo17fOqR40aNVJJSUk65phj0p133pkWLly42jaffPJJOvnkk9MOO+yQiouL02mnnZY+/fTTFBGpb9++ZfredNNNqUmTJqlKlSplppd+8skn03777ZcKCwtTy5Yt06233pqGDBlSps+bb76ZzjzzzNS8efNUUFCQGjRokI4//vj0xhtvrFbT73//+9S2bdtUVFSU6tSpk/bdd990zTXXpE8//TTfZ/bs2en73/9+qlOnToqIdU4Hnn1dqlSpknbYYYd0wAEHpMsuuyxNmTJltf7fnAL8s88+SxdffHFq3bp1qlWrViouLk7t2rVLjz76aJntyqtrbdOzlzcF+Pe///3097//Pe23336poKAgtW7dOg0fPny17SdOnJjatWuXatSokZo3b57uuOOONY5ZXm3fnAJ8lUceeSQdcMABqaCgINWrVy+dddZZ6ZNPPinTp3v37qlWrVqr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JAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGZUakl588cU44YQTonHjxpHL5eKJJ54os/7cc8+NXC5X5tGlS5fKKRYAANguVGpIWrx4cbRp0ybuuuuucvt06dIlZs2alX889NBDm7FCAABge1OtMnfetWvX6Nq161r7FBQURElJyWaqCAAA2N5t8fckjR07Nho0aBB77LFHXHjhhTFv3ry19l+2bFksXLiwzAMAAGB9bdEhqUuXLvHAAw/EqFGj4tZbb41x48ZF165dY+XKleVuM2DAgCguLs4/mjVrthkrBgAAtnaVernduvzwhz/M/3vfffeN/fbbL3bZZZcYO3ZsHHXUUWvcpnfv3tGrV6/88sKFCwUlAABgvW3RZ5K+aeedd46ddtopPvjgg3L7FBQURN26dcs8AAAA1tdWFZI++eSTmDdvXjRq1KiySwEAALZRlXq53aJFi8qcFZoxY0a89dZbUa9evahXr170798/Tj311CgpKYnp06fHNddcE7vuumt07ty5EqsGAAC2ZZUakt5444048sgj88ur7iXq3r17DB48OP75z3/G/fffH/Pnz4/GjRvHscceGzfddFMUFBRUVskAAMA2rlJDUseOHSOlVO76v//975uxGgAAgK3sniQAAIBNTUgCAADIEJIAAAAyhCQAAIAMIQkAACBDSAIAAMgQkgAAADKEJAAAgAwhCQAAIENIAgAAyBCSAAAAMoQkAACADCEJAAAgQ0gCAADIEJIAAAAyhCQAAICMapVdALB9O/T/HVrZJWzzxl86vrJLADaSW87+QWWXsE37xZ8eq+wS2EI4kwQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABkVCkmdOnWK+fPnr9a+cOHC6NSp07etCQAAoNJUKCSNHTs2li9fvlr70qVL46WXXvrWRQEAAFSWahvS+Z///Gf+3++++27Mnj07v7xy5cp49tlno0mTJhuvOgAAgM1sg0LS/vvvH7lcLnK53BovqysqKor/9//+30YrDgAAYHPboJA0Y8aMSCnFzjvvHK+99lrUr18/v65GjRrRoEGDqFq16kYvEgAAYHPZoJDUokWLiIgoLS3dJMUAAABUtg0KSVnTpk2LMWPGxNy5c1cLTX369PnWhQEAAFSGCoWke++9Ny688MLYaaedoqSkJHK5XH5dLpcTkgAAgK1WhULSzTffHLfccktce+21G7seAACASlWhv5P0+eefx2mnnbaxawEAAKh0FQpJp512Wjz33HMbuxYAAIBKV6HL7Xbddde44YYbYsKECbHvvvtG9erVy6z/2c9+tlGKAwAA2NwqFJJ+//vfR+3atWPcuHExbty4MutyuZyQBAAAbLUqFJJmzJixsesAAADYIlToniQAAIBtVYXOJJ133nlrXT9kyJAKFQMAAFDZKhSSPv/88zLLK1asiMmTJ8f8+fOjU6dOG6UwAACAylChkDRixIjV2kpLS+PCCy+MXXbZ5VsXBQAAUFk22j1JVapUiV69esXAgQM31pAAAACb3UaduGH69Onx1VdfbcwhAQAANqsKXW7Xq1evMssppZg1a1Y8/fTT0b17941SGAAAQGWoUEiaNGlSmeUqVapE/fr14/bbb1/nzHcAAABbsgqFpDFjxmzsOgAAALYIFQpJq/znP/+JqVOnRkTEHnvsEfXr198oRQEAAFSWCk3csHjx4jjvvPOiUaNGccQRR8QRRxwRjRs3jvPPPz+WLFmysWsEAADYbCoUknr16hXjxo2Lp556KubPnx/z58+Pv/71rzFu3Li48sorN3aNAAAAm02FLrd7/PHH47HHHouOHTvm24477rgoKiqK008/PQYPHryx6gMAANisKnQmacmSJdGwYcPV2hs0aOByOwAAYKtWoZDUvn376Nu3byxdujTf9uWXX0b//v2jffv2G604AACAza1Cl9sNGjQounTpEk2bNo02bdpERMTbb78dBQUF8dxzz23UAgEAADanCoWkfffdN6ZNmxYPPvhg/Otf/4qIiDPPPDPOOuusKCoq2qgFAgAAbE4VCkkDBgyIhg0bRs+ePcu0DxkyJP7zn//Etddeu1GKAwAA2NwqdE/S7373u2jduvVq7XvvvXfcc88937oo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JAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGUISAABAhpAEAACQISQBAABkCEkAAAAZQhIAAECGkAQAAJAhJAEAAGQISQAAABlCEgAAQIaQBAAAkCEkAQAAZAhJAAAAGVtFSLrrrruiZcuWUVhYGO3atYvXXnutsksCAAC2UVt8SHrkkUeiV69e0bdv33jzzTejTZs20blz55g7d25llwYAAGyDtviQdMcdd0TPnj2jR48esddee8U999wTNWvWjCFDhlR2aQAAwDaoWmUXsDbLly+PiRMnRu/evfNtVapUiaOPPjpeeeWVNW6zbNmyWLZsWX55wYIFERGxcOHC9drnymVffouKWR/reyw21BdLV26ScfnapjpuX3351SYZl//fpjp2i79y7Da1TXXsvly2ZJOMy9c21XGLiFi6YsUmG5tNd+wWLV28Scbl/7e+x25Vv5TSWvvl0rp6VKJPP/00mjRpEi+//HK0b98+337NNdfEuHHj4tVXX11tm379+kX//v03Z5kAAMBW5OOPP46mTZuWu36LPpNUEb17945evXrll0tLS+O///1v7LjjjpHL5Sqxso1v4cKF0axZs/j444+jbt26lV0OG8Cx23o5dlsnx23r5dhtvRy7rde2fOxSSvHFF19E48aN19pviw5JO+20U1StWjXmzJlTpn3OnDlRUlKyxm0KCgqioKCgTNsOO+ywqUrcItStW3ebewNvLxy7rZdjt3Vy3LZejt3Wy7Hbem2rx664uHidfbboiRtq1KgRbdu2jVGjRuXbSktLY9SoUWUuvwMAANhYtugzSRERvXr1iu7du8eBBx4Y3/ve92LQoEGxePHi6NGjR2WXBgAAbIO2+JB0xhlnxH/+85/o06dPzJ49O/bff/949tlno2HDhpVdWqUrKCiIvn37rnZ5IVs+x27r5dhtnRy3rZdjt/Vy7LZejt0WPrsdAADA5rZF35MEAACwuQlJAAAAGUISAABAhpAEAACQISRtATp27BiXX375au3Dhg3b5v8Q7rbi3HPPjVwuF7lcLqpXrx4NGzaMY445JoYMGRKlpaWr9e/cuXNUrVo1Xn/99bWOVaNGjdh1113jxhtvjK+++mpzPJWtyosvvhgnnHBCNG7cOHK5XDzxxBPl9v3pT38auVwuBg0atNYx582bF126dInGjRtHQUFBNGvWLC655JJYuHBhvs/YsWPzxyj7mD179lrH/stf/hLHHnts7LjjjpHL5eKtt94qt29KKbp27brO57WtGzBgQBx00EFRp06daNCgQZx00kkxderUMn1mz54dP/rRj6KkpCRq1aoV3/3ud+Pxxx9f67hvv/12nHnmmdGsWbMoKiqKPffcM+68887V+j344IPRpk2bqFmzZjRq1CjOO++8mDdv3kZ9jluTdX3m1vS5yOVycdttt6113J/97GfRtm3bKCgoiP3333+19f369VvjuLVq1fpW9X6bmrdl6/O5W7p0aVx88cWx4447Ru3atePUU0+NOXPmrHXcsWPHRrdu3aJRo0ZRq1at2H///ePBBx9crd/w4cOjdevWUVhYGPvuu2/87W9/26jPb2uzrvdxSin69OkTjRo1iqKiojj66KNj2rRpax1zfb8Dly1bFr/4xS+iRYsWUVBQEC1btowhQ4asdez1ef9ERLzyyivRqVOnqFWrVtStWzeOOOKI+PLLL9f9gmwmQtJWavny5ZVdAt/QpUuXmDVrVnz00UfxzDPPxJFHHhmXXXZZHH/88WUCzsyZM+Pll1+OSy65pNwvmlVjTZs2La688sro16/fdv0fdnkWL14cbdq0ibvuumut/UaMGBETJkyIxo0br3PMKlWqRLdu3eLJJ5+M999/P4YNGxYvvPBC/PSnP12t79SpU2PWrFn5R4MGDdZZ72GHHRa33nrrOusYNGhQ5HK5dfbb1o0bNy4uvvjimDBhQjz//POxYsWKOPbYY2Px4sX5Puecc05MnTo1nnzyyXjnnXfilFNOidNPPz0mTZpU7rgTJ06MBg0axJ/+9KeYMmVK/OIXv4jevXvHb3/723yf8ePHxznnnBPnn39+TJkyJYYPHx6vvfZa9OzZc5M+5y3Zuj5z2c/DrFmzYsiQIZHL5eLUU09d59jnnXdenHHGGWtcd9VVV6029l577RWnnXbat6r329a8rVqfz90VV1wRTz31VAwfPjzGjRsXn376aZxyyilrHffll1+O/fbbLx5//PH45z//GT169IhzzjknRo4cWabPmWeeGeeff35MmjQpTjrppDjppJNi8uTJm+z5bunW9T7+1a9+Fb/5zW/innvuiVdffTVq1aoVnTt3jqVLl5Y75vp8B0ZEnH766TFq1Kj4wx/+EFOnTo2HHnoo9thjj7XWuz7vn1deeSW6dOkSxx57bLz22mvx+uuvxyWXXBJVqmxB0SRR6Tp06JAuu+yy1dqHDh2aiouLU0opde/ePXXr1i3dfPPNqVGjRqlly5YppZRmzpyZTjvttFRcXJy+853vpBNPPDHNmDEjP8Zrr72Wjj766LTjjjumunXrpiOOOCJNnDhxMzyr7cuq4/NNo0aNShGR7r333nxbv3790g9/+MP03nvvpeLi4rRkyZJ1jnXMMcekgw8+eFOUvs2IiDRixIjV2j/55JPUpEmTNHny5NSiRYs0cODADR77zjvvTE2bNs0vjxkzJkVE+vzzzytU64wZM1JEpEmTJq1x/aRJk1KTJk3SrFmzyn1e26u5c+emiEjjxo3Lt9WqVSs98MADZfrVq1evzOdufVx00UXpyCOPzC/fdtttaeeddy7T5ze/+U1q0qRJBSrf9qzPe7Nbt26pU6dO6z1m3759U5s2bdbZ76233koRkV588cX1Hnt9P0sbWvP24Jufu/nz56fq1aun4cOH5/u89957KSLSK6+8skFjH3fccalHjx755dNPPz19//vfL9OnXbt26YILLvgWz2Db8c33cWlpaSopKUm33XZbvm3+/PmpoKAgPfTQQxs09je/A5955plUXFyc5s2b961qXtP3drt27dL111//rcbd1LaguMa6jBo1KqZOnRrPP/98jBw5MlasWBGdO3eOOnXqxEsvvRTjx4+P2rVrR5cuXfJnmr744ovo3r17/OMf/4gJEybEbrvtFscdd1x88cUXlfxstg+dOnWKNm3axF/+8peI+PqU+NChQ+Pss8+O1q1bx6677hqPPfbYOscpKipy9rACSktL40c/+lFcffXVsffee1dojE8//TT+8pe/RIcOHVZbt//++0ejRo3imGOOifHjx3/bciMiYsmSJfE///M/cdddd0VJSclGGXNbsmDBgoiIqFevXr7tkEMOiUceeST++9//RmlpaTz88MOxdOnS6Nix4waPnR23ffv28fHHH8ff/va3SCnFnDlz4rHHHovjjjtuozyXbd2cOXPi6aefjvPPP3+jj33ffffF7rvvHocffvhGHXdT1rw1++bnbuLEibFixYo4+uij831at24dzZs3j1deeWWDx85+7l555ZUy40Z8fYn6ho67vZgxY0bMnj27zGtWXFwc7dq1+9bH4sknn4wDDzwwfvWrX0WTJk1i9913j6uuumqDL4n75vtn7ty58eqrr0aDBg3ikEMOiYYNG0aHDh3iH//4xwaNu6kJSVuRWrVqxX333Rd777137L333vHII49EaWlp3HfffbHvvvvGnnvuGUOHDo2ZM2fG2LFjI+LrH9JX/UC+5557xu9///tYsmRJjBs3rnKfzHakdevW8dFHH0VExAsvvBBLliyJzp07R0TE2WefHX/4wx/K3TalFC+88EL8/e9/j06dOm2Ocrcpt956a1SrVi1+9rOfbfC2Z555ZtSsWTOaNGkSdevWjfvuuy+/rlGjRnHPPffE448/Ho8//ng0a9YsOnbsGG+++ea3rvmKK66IQw45JLp16/atx9rWlJaWxuWXXx6HHnpo7LPPPvn2Rx99NFasWBE77rhjFBQUxAUXXBAjRoyIXXfddb3Hfvnll+ORRx6Jn/zkJ/m2Qw89NB588ME444wzokaNGlFSUhLFxcXrvLyTr91///1Rp06ddV6CtaGWLl0aDz744CYJMpuq5q3Zmj53s2fPjho1aqx233TDhg3XeW9m1qOPPhqvv/569OjRI982e/bsaNiw4bcad3uy6nX5tq/Zmr4DP/zww/jHP/4RkydPjhEjRsSgQYPisccei4suumi9x13T++fDDz+MiK/vNezZs2c8++yz8d3vfjeOOuqodd5LtTkJSVuRfffdN2rUqJFffvvtt+ODDz6IOnXqRO3ataN27dpRr169WLp0aUyfPj0ivv6tWM+ePWO33XaL4uLiqFu3bixatChmzpxZWU9ju5NSyt9bMmTIkDjjjDOiWrVqEfH1D+Ljx4/PH69VRo4cGbVr147CwsLo2rVrnHHGGdGvX7/NXfpWbeLEiXHnnXfGsGHDyr23p2vXrvnPzjfPNA0cODDefPPN+Otf/xrTp0+PXr165dftscceccEFF0Tbtm3jkEMOiSFDhsQhhxwSAwcOjIivb/ZfNW7t2rXjpZdeWq+an3zyyRg9evQ6J5fYXl188cUxefLkePjhh8u033DDDTF//vx44YUX4o033ohevXrF6aefHu+8805ErP04R0RMnjw5unXrFn379o1jjz023/7uu+/GZZddFn369ImJEyfGs88+Gx999NEa709jdUOGDImzzjorCgsL823rOhbrY8SIEfmrJFZ56aWXynzm1jQZQEVr3t6V97lbl7333jt/PLp27bra+jFjxkSPHj3i3nvvrfB7gfVT0e/A0tLSyOVy8eCDD8b3vve9OO644+KOO+6I+++/P7788sv1+tyt6f2zakKrCy64IHr06BEHHHBADBw4MPbYY491TgqxOVWr7AKIqFu3bv5UZNb8+fOjuLg4v/zNWXwWLVoUbdu2XeObsn79+hER0b1795g3b17ceeed+ZlJ2rdv79Ktzei9996LVq1axX//+98YMWJErFixIgYPHpxfv3LlyhgyZEjccsst+bYjjzwyBg8eHDVq1IjGjRvnQxXr76WXXoq5c+dG8+bN820rV66MK6+8MgYNGhQfffRR3HffffnLBqpXr15m+5KSkigpKYnWrVtHvXr14vDDD48bbrghGjVqtMb9fe9738tfKnDiiSdGu3bt8uuaNGmyXjWPHj06pk+fvtpvZ0899dQ4/PDD82eIt0eXXHJJjBw5Ml588cVo2rRpvn369Onx29/+NiZPnpz/z79Nmzbx0ksvxV133RX33HPPWo/zu+++G0cddVT85Cc/ieuvv77MugEDBsShhx4aV199dURE7LffflGrVq04/PDD4+abby73vcDXn7+pU6fGI488UqZ9bcdifd13331x/PHHl/nN+YEHHlhmtshv/lb929S8PSvvc1dSUhLLly+P+fPnl/m+mjNnTv4y4b/97W+xYsWKiPj6kvGscePGxQknnBADBw6Mc845p8y6kpKS1WbJy45LWatelzlz5pT5TpozZ05+psiKfgc2atQomjRpUuZn0T333DNSSvHJJ5+s83NX3vtnVZ177bVXmf577rnnFvVLfD95bQH22GOPeO6551Zrf/PNN2P33Xcvd7vvfve78cgjj0SDBg2ibt26a+wzfvz4uPvuu/PX0H/88cfx2WefbZzCWafRo0fHO++8E1dccUU8+OCD0bRp09Wm7nzuuefi9ttvjxtvvDGqVq0aEV8H4g25VIjV/ehHP1rjde0/+tGP8pd2rG94WfVbr2XLlpXb56233sp/8depUyfq1KmzwTVfd9118eMf/7hM27777hsDBw6ME044YYPH2xaklOLSSy+NESNGxNixY6NVq1Zl1i9ZsiQiYrUZkapWrZo/buUd5ylTpkSnTp2ie/fuZX5JkR37m7+gWPUZTSlV7AltJ/7whz9E27Zto02bNmXa1/czV54ZM2bEmDFj4sknnyzTXlRU9K2/M8ureXu0rs9d27Zto3r16jFq1Kj8LIBTp06NmTNnRvv27SMiokWLFmsce+zYsXH88cfHrbfeWubSrlXat28fo0aNKvOnUZ5//vn8uJTVqlWrKCkpiVGjRuVD0cKFC+PVV1+NCy+8MCIq/h146KGHxvDhw2PRokVRu3btiIh4//33o0qVKtG0adNyP3frev+0bNkyGjduvNq04O+///4azzpWmkqbMoK86dOnp8LCwnTppZemt99+O/3rX/9Kt99+e6pWrVp65plnUkprnvFs8eLFabfddksdO3ZML774Yvrwww/TmDFj0qWXXpo+/vjjlFJKBxxwQDrmmGPSu+++myZMmJAOP/zwVFRUVKEZvihf9+7dU5cuXdKsWbPSJ598kiZOnJhuueWWVLt27XT88cenr776KrVp0yZde+21q207f/78VKNGjTRy5Mj8WGuaKY/VffHFF2nSpElp0qRJKSLSHXfckSZNmpT+/e9/r7H/+sxu9/TTT6chQ4akd955J82YMSONHDky7bnnnunQQw/N9xk4cGB64okn0rRp09I777yTLrvsslSlSpX0wgsvrHXsefPmpUmTJqWnn346RUR6+OGH06RJk9KsWbPK3Sa289ntLrzwwlRcXJzGjh2bZs2alX+smhVy+fLladddd02HH354evXVV9MHH3yQfv3rX6dcLpeefvrpcsd95513Uv369dPZZ59dZty5c+fm+wwdOjRVq1Yt3X333Wn69OnpH//4RzrwwAPT9773vU3+vLdU6/OZW7BgQapZs2YaPHjweo87bdq0NGnSpHTBBRek3XffPb+PZcuWlel3/fXXp8aNG6evvvpqo9Vb0Zq3Zev63KWU0k9/+tPUvHnzNHr06PTGG2+k9u3bp/bt26913NGjR6eaNWum3r17lxk3O3va+PHjU7Vq1dKvf/3r9N5776W+ffum6tWrp3feeWeTPd8t3brex7/85S/TDjvskP7617+mf/7zn6lbt26pVatW6csvvyx3zPX5Dvziiy9S06ZN0w9+8IM0ZcqUNG7cuLTbbrulH//4x2utd33ePwMHDkx169ZNw4cPT9OmTUvXX399KiwsTB988MG3fLU2HiFpC/Haa6+lY445JtWvXz8VFxendu3alfnBqLwfnGfNmpXOOeectNNOO6WCgoK08847p549e6YFCxaklFJ6880304EHHpgKCwvTbrvtloYPH17haZApX/fu3VNEpIhI1apVS/Xr109HH310GjJkSFq5cmV64403UkSk1157bY3bd+3aNZ188sn5sYSk9bNqKu5vPrp3777G/uvz3h89enRq3759Ki4uzn9urr322jLTfd96661pl112SYWFhalevXqpY8eOafTo0eusd+jQoWust2/fvuVus72HpDW9XhGRhg4dmu/z/vvvp1NOOSU1aNAg1axZM+23336rTQn+TX379l3juC1atCjT7ze/+U3aa6+9UlFRUWrUqFE666yz0ieffLIJnunWYX0+c7/73e9SUVFRmj9//nqP26FDhzWOm/2TFitXrkxNmzZNP//5zzdqvRWteVu2Pp+7L7/8Ml100UXpO9/5TqpZs2Y6+eST1/oLn5TK/l+ZfXTo0KFMv0cffTTtvvvuqUaNGmnvvfde6y88tgfreh+XlpamG264ITVs2DAVFBSko446Kk2dOnWtY67vd+B7772Xjj766FRUVJSaNm2aevXqtdqfLvmm9Xn/pJTSgAEDUtOmTVPNmjVT+/bt00svvbShL80mlUvJNQMAAACrmN0OAAAgQ0gCAADIEJIAAAAyhCQAAIAMIQkAACBDSAIAAMgQkgAAADKEJAAAgAwhCYAtQseOHePyyy/PL7ds2TIGDRqUX87lcvHEE09s9roA2P4ISQCU69xzz41cLrfa44MPPqjwmGPHjo1cLhfz588v0/6Xv/wlbrrppnK3mzVrVnTt2rXC+62oXC4XhYWF8e9//7tM+0knnRTnnnvuZq8HgE1PSAJgrbp06RKzZs0q82jVqlWFxlqxYkW56+rVqxd16tQpd31JSUkUFBRUaL/rklKKr776qtz1uVwu+vTps0n2DcCWR0gCYK0KCgqipKSkzKNq1aoREfHXv/41vvvd70ZhYWHsvPPO0b9//zJhI5fLxeDBg+PEE0+MWrVqRc+ePePII4+MiIjvfOc7kcvl8mdjvnm53TdlL7fr16/fGs9wDRs2LCIiSktLY8CAAdGqVasoKiqKNm3axGOPPZYfa9XZrGeeeSbatm0bBQUF8Y9//KPcfV9yySXxpz/9KSZPnlxun2effTYOO+yw2GGHHWLHHXeM448/PqZPn55f/9FHH0Uul4tHH300Dj/88CgqKoqDDjoo3n///Xj99dfjwAMPjNq1a0fXrl3jP//5T5mx77vvvthzzz2jsLAwWrduHXfffXe5dQDw7QlJAFTISy+9FOecc05cdtll8e6778bvfve7GDZsWNxyyy1l+vXr1y9OPvnkeOedd6J///7x+OOPR0TE1KlTY9asWXHnnXdu8L6vuuqqMme2fv3rX0fNmjXjwAMPjIiIAQMGxAMPPBD33HNPTJkyJa644oo4++yzY9y4cWXGue666+KXv/xlvPfee7HffvuVu79DDz00jj/++LjuuuvK7bN48eLo1atXvPHGGzFq1KioUqVKnHzyyVFaWlqmX9++feP666+PN998M6pVqxb/8z//E9dcc03ceeed8dJLL8UHH3xQ5qzVgw8+GH369Ilbbrkl3nvvvfjf//3fuOGGG+L+++/f4NcNgPWUAKAc3bt3T1WrVk21atXKP37wgx+klFI66qij0v/+7/+W6f/HP/4xNWrUKL8cEenyyy8v02fMmDEpItLnn39epr1Dhw7psssuyy+3aNEiDRw4sMxYI0aMWK3GV155JRUWFqZHHnkkpZTS0qVLU82aNdPLL79cpt/555+fzjzzzDI1PPHEE+t8DVbtd8qUKalq1arpxRdfTCml1K1bt9S9e/dyt/vPf/6TIiK98847KaWUZsyYkSIi3Xffffk+Dz30UIqINGrUqHzbgAED0h577JFf3mWXXdKf//znMmPfdNNNqX379uusHYCKqVZ58QyArcGRRx4ZgwcPzi/XqlUrIiLefvvtGD9+fJkzRytXroylS5fGkiVLombNmhER+bM7m8LMmTPjpJNOiquuuipOP/30iIj44IMPYsmSJXHMMceU6bt8+fI44IADyrRtSG177bVXnHPOOXHdddfF+PHjV1s/bdq06NOnT7z66qvx2Wef5c8gzZw5M/bZZ598v+wZq4YNG0ZExL777lumbe7cuRHx9dmp6dOnx/nnnx89e/bM9/nqq6+iuLh4vWsHYMMISQCsVa1atWLXXXddrX3RokXRv3//OOWUU1ZbV1hYWGb7TWHx4sVx4oknRvv27ePGG28sU1dExNNPPx1NmjQps803J37Y0Nr69+8fu++++xqnIj/hhBOiRYsWce+990bjxo2jtLQ09tlnn1i+fHmZftWrV8//O5fLrbFtVcBa9VzuvffeaNeuXZlxVt0XBsDGJyQBUCHf/e53Y+rUqWsMUGtTo0aNiPj6rFNFpZTi7LPPjtLS0vjjH/+YDxsRX5/xKSgoiJkzZ0aHDh0qvI81adasWVxyySXx85//PHbZZZd8+7x582Lq1Klx7733xuGHHx4RsdaJINZXw4YNo3HjxvHhhx/GWWed9a3HA2D9CEkAVEifPn3i+OOPj+bNm8cPfvCDqFKlSrz99tsxefLkuPnmm8vdrkWLFpHL5WLkyJFx3HHHRVFRUdSuXXuD9t2vX7944YUX4rnnnotFixblz7gUFxdHnTp14qqrroorrrgiSktL47DDDosFCxbE+PHjo27dutG9e/dv9bx79+4d9957b8yYMSPOOOOMiPh6pr4dd9wxfv/730ejRo1i5syZa53kYUP0798/fvazn0VxcXF06dIlli1bFm+88UZ8/vnn0atXr42yDwDKMrsdABXSuXPnGDlyZDz33HNx0EEHxcEHHxwDBw6MFi1arHW7Jk2aRP/+/eO6666Lhg0bxiWXXLLB+x43blwsWrQoDjnkkGjUqFH+8cgjj0RExE033RQ33HBDDBgwIPbcc8/o0qVLPP300xX++05Z9erVi2uvvTaWLl2ab6tSpUo8/PDDMXHixNhnn33iiiuuiNtuu+1b7ysi4sc//nHcd999MXTo0Nh3332jQ4cOMWzYsI3yXABYs1xKKVV2EQAAAFsKZ5IAAAAyhCQAAIAMIQkAACBDSAIAAMgQkgAAADKEJAAAgAwhCQAAIENIAgAAyBCSAAAAMoQkAACADCEJAAAg4/8DkDqrpYigst4AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# checking the data distribution \n", + "plt.figure(figsize=(10, 7))\n", + "\n", + "sns.countplot(data=df, x='Fertilizer Name')\n", + "plt.title(\"Dataset Distribution\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(['Sandy', 'Loamy', 'Black', 'Red', 'Clayey'], dtype=object)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# soil type\n", + "df['Soil Type'].unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(['Maize', 'Sugarcane', 'Cotton', 'Tobacco', 'Paddy', 'Barley',\n", + " 'Wheat', 'Millets', 'Oil seeds', 'Pulses', 'Ground Nuts'],\n", + " dtype=object)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df['Crop Type'].unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of the training dataset: (79, 8)\n", + "Shape of the testing dataset: (20, 8)\n" + ] + } + ], + "source": [ + "# splitting the dataset \n", + "X = df.drop(columns=[\"Fertilizer Name\"])\n", + "y = df[\"Fertilizer Name\"]\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", + "\n", + "print(f\"Shape of the training dataset: {X_train.shape}\")\n", + "print(f\"Shape of the testing dataset: {X_test.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Temparature', 'Humidity ', 'Moisture', 'Nitrogen', 'Potassium', 'Phosphorous']\n" + ] + } + ], + "source": [ + "# numerical columns in the dataset\n", + "print(df._get_numeric_data().columns.tolist())" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Soil Type', 'Crop Type']" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# segrating categorical columns\n", + "categorical_columns = [i for i in df.columns if (i not in df._get_numeric_data().columns) & (i !='Fertilizer Name')]\n", + "categorical_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Encoding" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "ohe = OneHotEncoder(drop='first')\n", + "standard_scaler = StandardScaler()\n", + "\n", + "preprocessor = ColumnTransformer(\n", + " transformers =[\n", + " ('StandaradScaling', standard_scaler, df._get_numeric_data().columns),\n", + " ('One_hot_encoding', ohe, categorical_columns)\n", + " ],\n", + " remainder='passthrough'\n", + ")\n", + "\n", + "pipeline = Pipeline([\n", + " ('preprocess', preprocessor)\n", + "])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "X_train_transformed = pipeline.fit_transform(X_train)\n", + "X_test_transformed = pipeline.transform(X_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "le = LabelEncoder()\n", + "\n", + "y_train_transformed = le.fit_transform(y_train)\n", + "y_test_transformed = le.transform(y_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate_clf(true, predicted):\n", + " '''\n", + " This function takes in true values and predicted values\n", + " Returns: Accuracy, F1-Score, Precision, Recall, Roc-auc Score\n", + " '''\n", + " acc = accuracy_score(true, predicted)\n", + " f1 = f1_score(true, predicted, average='weighted')\n", + " precision = precision_score(true, predicted, average='weighted')\n", + " recall = recall_score(true, predicted, average='weighted')\n", + " \n", + " return acc, f1, precision, recall" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# create a function which can evaluate models and returns a report \n", + "def evaluate_model(X_train, X_test, y_train, y_test, models):\n", + " '''\n", + " This function takes X_train, X_test, y_train, y_test and models dictionary as input\n", + " Iterate through the given model directory and evaluate metrics\n", + "\n", + " Returns:\n", + " DataFrame which contains report of all models metrics \n", + " '''\n", + "\n", + " model_list = []\n", + " metric_list = []\n", + "\n", + " for i in range(len(list(models))):\n", + " model = list(models.values())[i]\n", + " model.fit(X_train, y_train)\n", + "\n", + " # Make predictions\n", + " y_train_pred = model.predict(X_train)\n", + " y_test_pred = model.predict(X_test)\n", + "\n", + " # Training set performances\n", + " model_train_accuracy, model_train_f1, model_train_precision, \\\n", + " model_train_recall = evaluate_clf(y_train, y_train_pred)\n", + "\n", + " # Test set peformances \n", + " model_test_accuracy, model_test_f1, model_test_precision, \\\n", + " model_test_recall = evaluate_clf(y_test, y_test_pred)\n", + "\n", + " print(list(models.keys())[i])\n", + " model_list.append(list(models.keys())[i])\n", + "\n", + " result_dict ={'model_name':list(models.keys())[i], \n", + " \"train_accuracy\": model_train_accuracy, \"test_accuracy\": model_test_accuracy,\n", + " \"train_precision\": model_train_precision, \"test_precision\": model_test_precision,\n", + " 'train_recall': model_train_recall, \"test_recall\":model_test_recall,\n", + " \"train_f1_score\": model_train_f1, \"test_f1_score\": model_test_f1}\n", + "\n", + " metric_list.append(result_dict)\n", + "\n", + " \n", + " return metric_list\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# Model Dictionary\n", + "models = {\n", + " \"Random Forest\": RandomForestClassifier(),\n", + " \"Decision Tree\": DecisionTreeClassifier(),\n", + " \"Gradient Boosting\": GradientBoostingClassifier(),\n", + " \"K-Neighbors Classifier\": KNeighborsClassifier(),\n", + " \"XGBClassifier\": XGBClassifier(), \n", + " \"CatBoosting Classifier\": CatBoostClassifier(verbose=False),\n", + " \"AdaBoost Classifier\": AdaBoostClassifier()\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random Forest\n", + "Decision Tree\n", + "Gradient Boosting\n", + "K-Neighbors Classifier\n", + "XGBClassifier\n", + "CatBoosting Classifier\n", + "AdaBoost Classifier\n" + ] + } + ], + "source": [ + "resultant_metrics = evaluate_model(X_train_transformed, X_test_transformed, y_train_transformed, y_test_transformed, models)\n", + "\n", + "resultant_metrics_df = pd.DataFrame(data=resultant_metrics)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
model_nametrain_accuracytest_accuracytrain_precisiontest_precisiontrain_recalltest_recalltrain_f1_scoretest_f1_score
1Decision Tree1.0000001.001.0000001.0000001.0000001.001.0000001.000000
4XGBClassifier1.0000001.001.0000001.0000001.0000001.001.0000001.000000
5CatBoosting Classifier1.0000001.001.0000001.0000001.0000001.001.0000001.000000
0Random Forest1.0000000.951.0000001.0000001.0000000.951.0000000.966667
2Gradient Boosting1.0000000.951.0000000.9750001.0000000.951.0000000.955556
6AdaBoost Classifier0.5949370.700.4779180.6571430.5949370.700.5041470.662500
3K-Neighbors Classifier0.8987340.650.9045390.6666670.8987340.650.8975990.647727
\n", + "
" + ], + "text/plain": [ + " model_name train_accuracy test_accuracy train_precision \\\n", + "1 Decision Tree 1.000000 1.00 1.000000 \n", + "4 XGBClassifier 1.000000 1.00 1.000000 \n", + "5 CatBoosting Classifier 1.000000 1.00 1.000000 \n", + "0 Random Forest 1.000000 0.95 1.000000 \n", + "2 Gradient Boosting 1.000000 0.95 1.000000 \n", + "6 AdaBoost Classifier 0.594937 0.70 0.477918 \n", + "3 K-Neighbors Classifier 0.898734 0.65 0.904539 \n", + "\n", + " test_precision train_recall test_recall train_f1_score test_f1_score \n", + "1 1.000000 1.000000 1.00 1.000000 1.000000 \n", + "4 1.000000 1.000000 1.00 1.000000 1.000000 \n", + "5 1.000000 1.000000 1.00 1.000000 1.000000 \n", + "0 1.000000 1.000000 0.95 1.000000 0.966667 \n", + "2 0.975000 1.000000 0.95 1.000000 0.955556 \n", + "6 0.657143 0.594937 0.70 0.504147 0.662500 \n", + "3 0.666667 0.898734 0.65 0.897599 0.647727 " + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "resultant_metrics_df = resultant_metrics_df.sort_values(by='test_f1_score', ascending=False)\n", + "resultant_metrics_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.10 64-bit", + "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.10" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Fertilizer-Recommendation/requirements.txt b/Fertilizer-Recommendation/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..106abbc3ee684d63b3c7962d66e7e60e7487515b --- /dev/null +++ b/Fertilizer-Recommendation/requirements.txt @@ -0,0 +1,58 @@ +asttokens==2.2.1 +backcall==0.2.0 +bleach==6.0.0 +certifi==2023.7.22 +charset-normalizer==3.2.0 +click==8.1.6 +comm==0.1.3 +debugpy==1.6.7 +decorator==5.1.1 +dnspython==2.4.1 +executing==1.2.0 +idna==3.4 +importlib-metadata==6.8.0 +ipykernel==6.25.0 +ipython==8.12.2 +jedi==0.18.2 +jupyter-client==8.3.0 +jupyter-core==5.3.1 +kaggle==1.5.16 +matplotlib-inline==0.1.6 +nest-asyncio==1.5.6 +numpy==1.24.4 +opendatasets==0.1.22 +packaging==23.1 +pandas==2.0.3 +parso==0.8.3 +pexpect==4.8.0 +pickleshare==0.7.5 +platformdirs==3.9.1 +prompt-toolkit==3.0.39 +psutil==5.9.5 +ptyprocess==0.7.0 +pure-eval==0.2.2 +Pygments==2.15.1 +pymongo==4.4.1 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +python-slugify==8.0.1 +pytz==2023.3 +pyzmq==25.1.0 +requests==2.31.0 +six==1.16.0 +stack-data==0.6.2 +text-unidecode==1.3 +tornado==6.3.2 +tqdm==4.65.0 +traitlets==5.9.0 +typing-extensions==4.7.1 +tzdata==2023.3 +urllib3==2.0.4 +wcwidth==0.2.6 +webencodings==0.5.1 +zipp==3.16.2 +scikit-learn +matplotlib +seaborn +pyyaml +dill \ No newline at end of file diff --git a/Fertilizer-Recommendation/saved_models/0/model/model.pkl b/Fertilizer-Recommendation/saved_models/0/model/model.pkl new file mode 100644 index 0000000000000000000000000000000000000000..14f2fc61359f5b05ee0433428a11030698e66505 --- /dev/null +++ b/Fertilizer-Recommendation/saved_models/0/model/model.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c89526de70ba6f924e11e82a344eb581e001228120c575fc73372179b91297ed +size 2808 diff --git a/Fertilizer-Recommendation/saved_models/0/target_encoder/target_encoder.pkl b/Fertilizer-Recommendation/saved_models/0/target_encoder/target_encoder.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b0f0d194e2c65bcfcad15e2d4b55be5577b20789 --- /dev/null +++ b/Fertilizer-Recommendation/saved_models/0/target_encoder/target_encoder.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60d651fde77ec9ae2d704442e566129721089259fef449d7e81620ac286ddc9d +size 338 diff --git a/Fertilizer-Recommendation/saved_models/0/transformer/transformer.pkl b/Fertilizer-Recommendation/saved_models/0/transformer/transformer.pkl new file mode 100644 index 0000000000000000000000000000000000000000..df26c4d70d4171bfe27bc6951498fa67017c79bb --- /dev/null +++ b/Fertilizer-Recommendation/saved_models/0/transformer/transformer.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:427b030f88db787db36098d667cd6fb75e6e6a1d8bb6b504d47d2124b3a10a20 +size 2323 diff --git a/Fertilizer-Recommendation/src/__init__.py b/Fertilizer-Recommendation/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/app.py b/Fertilizer-Recommendation/src/app.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/components/__init__.py b/Fertilizer-Recommendation/src/components/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/components/data_ingestion.py b/Fertilizer-Recommendation/src/components/data_ingestion.py new file mode 100644 index 0000000000000000000000000000000000000000..b54ff1a966cc9a6407e37647cb59cda80c9ae94b --- /dev/null +++ b/Fertilizer-Recommendation/src/components/data_ingestion.py @@ -0,0 +1,69 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from src import utils + +from sklearn.model_selection import train_test_split +import numpy as np +import pandas as pd +import sys +import os + +class DataIngestion: + + def __init__(self, data_ingestion_config:config_entity.DataIngestionConfig): + try: + logging.info(f"\n\n{'>'*50} Data Ingestion {'<'*50}\n") + self.data_ingestion_config = data_ingestion_config + + except Exception as e: + raise FertilizerException(e, sys) + + def initiate_data_ingestion(self) -> artifact_entity.DataIngestionArtifact: + try: + logging.info(f"Exporting collection data as pandas Dataframe ") + + df: pd.DataFrame = utils.get_collection_as_dataframe( + database_name=self.data_ingestion_config.database_name, + collection_name=self.data_ingestion_config.collection_name) + + logging.info(f"Saving data in feature store") + + feature_store_dir = os.path.dirname(self.data_ingestion_config.feature_store_file_path) + os.makedirs(feature_store_dir, exist_ok=True) + + logging.info(f"Saving dataframe into feature store") + df.to_csv(path_or_buf=self.data_ingestion_config.feature_store_file_path, + index=False, + header=True) + + logging.info(f"Split the dataset into train and test") + train_df, test_df = train_test_split( + df, test_size=self.data_ingestion_config.test_size, random_state=42 + ) + + logging.info(f"Create dataset directory if not available") + dataset_dir = os.path.dirname(self.data_ingestion_config.train_file_path) + os.makedirs(dataset_dir, exist_ok=True) + + logging.info(f"Save df to feature store folder") + train_df.to_csv(path_or_buf=self.data_ingestion_config.train_file_path, + index=False, + header=True) + + test_df.to_csv(path_or_buf=self.data_ingestion_config.test_file_path, + index=False, + header=True) + + data_ingestion_artifact = artifact_entity.DataIngestionArtifact( + feature_store_file_path=self.data_ingestion_config.feature_store_file_path, + train_file_path=self.data_ingestion_config.train_file_path, + test_file_path=self.data_ingestion_config.test_file_path) + + logging.info(f"Data Ingestion Completed. Artifacts saved") + + return data_ingestion_artifact + + except Exception as e: + raise FertilizerException(e, sys) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/components/data_transformation.py b/Fertilizer-Recommendation/src/components/data_transformation.py new file mode 100644 index 0000000000000000000000000000000000000000..8332c8e15c51f9cb311eb60ceee8bf4c166e6598 --- /dev/null +++ b/Fertilizer-Recommendation/src/components/data_transformation.py @@ -0,0 +1,116 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from typing import Optional +from src import utils +import sys +import os +from src.config import TARGET_COLUMN +from src.config import NUMERICAL_FEATURES +from src.config import CATEGORICAL_FEATURES +from src.config import BASE_FILE_PATH + +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler +from sklearn.preprocessing import LabelEncoder +from sklearn.preprocessing import OneHotEncoder +from sklearn.compose import ColumnTransformer +import pandas as pd +import numpy as np + +class DataTransformation: + + def __init__( + self, + data_transformation_config:config_entity.DataTransformationConfig, + data_ingestion_artifact:artifact_entity.DataIngestionArtifact + ): + + try: + logging.info(f"\n\n{'>'*50} Data transformation Initiated {'<'*50}\n") + self.data_transformation_config = data_transformation_config + self.data_ingestion_artifact = data_ingestion_artifact + + except Exception as e: + raise FertilizerException(e, sys) + + @classmethod + def get_data_transformer_object(cls) -> Pipeline: + try: + standard_scaler = StandardScaler() + one_hot_encoder = OneHotEncoder(drop='first') + + numerical_indices, categorical_indices = utils.get_column_indices( + numerical_features=NUMERICAL_FEATURES, + categorical_features=CATEGORICAL_FEATURES, + base_file_path=BASE_FILE_PATH) + + preprocessor = ColumnTransformer( + transformers =[ + ('StandaradScaling', standard_scaler, numerical_indices), + ('One_hot_encoding', one_hot_encoder, categorical_indices) + ], + remainder='passthrough' + ) + + pipeline = Pipeline([ + ('preprocess', preprocessor) + ]) + + return pipeline + + except Exception as e: + raise FertilizerException(e, sys) + + def initiate_data_transformation(self) -> artifact_entity.DataTransformationArtifact: + try: + # reading training and testing files + train_df = pd.read_csv(self.data_ingestion_artifact.train_file_path) + test_df = pd.read_csv(self.data_ingestion_artifact.test_file_path) + + # selecting input features for train and test dataframe + input_feature_train_df = train_df.drop(TARGET_COLUMN, axis=1) + input_feature_test_df = test_df.drop(TARGET_COLUMN, axis=1) + + # selecting target feature for train and test dataframe + target_feature_train_df = train_df[TARGET_COLUMN] + target_feature_test_df = test_df[TARGET_COLUMN] + + label_encoder = LabelEncoder() + label_encoder.fit(target_feature_train_df) + + # transformation on target column + target_feature_train_arr = label_encoder.transform(target_feature_train_df) + target_feature_test_arr = label_encoder.transform(target_feature_test_df) + + # transforming input features + transformation_pipeline = DataTransformation.get_data_transformer_object() + transformation_pipeline.fit(input_feature_train_df) + + input_feature_train_arr = transformation_pipeline.transform(input_feature_train_df) + input_feature_test_arr = transformation_pipeline.transform(input_feature_test_df) + + train_arr = np.c_[input_feature_train_arr, target_feature_train_arr] + test_arr = np.c_[input_feature_test_arr, target_feature_test_arr] + + # save the numpy array + utils.save_object(file_path=self.data_transformation_config.transformed_train_path, obj=train_arr) + utils.save_object(file_path=self.data_transformation_config.transformed_test_path, obj=test_arr) + + utils.save_object(file_path=self.data_transformation_config.transform_object_path, obj=transformation_pipeline) + + utils.save_object(file_path=self.data_transformation_config.target_encoder_path, obj=label_encoder) + + data_transformation_artifact = artifact_entity.DataTransformationArtifact( + transform_object_path = self.data_transformation_config.transform_object_path, + transformed_train_path = self.data_transformation_config.transformed_train_path, + transformed_test_path = self.data_transformation_config.transformed_test_path, + target_encoder_path = self.data_transformation_config.target_encoder_path) + + logging.info(f"Data transformation Completed") + + return data_transformation_artifact + + except Exception as e: + raise FertilizerException(e, sys) diff --git a/Fertilizer-Recommendation/src/components/data_validation.py b/Fertilizer-Recommendation/src/components/data_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..adfde621624e832192dd53ff4629ab285caa47fd --- /dev/null +++ b/Fertilizer-Recommendation/src/components/data_validation.py @@ -0,0 +1,145 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from src.config import TARGET_COLUMN +from src import utils + +from typing import Optional +from scipy.stats import ks_2samp +import pandas as pd +import numpy as np +import os +import sys + +class DataValidation: + + def __init__( + self, + data_validation_config: config_entity.DataValidationConfig, + data_ingestion_arfitact: artifact_entity.DataIngestionArtifact + ): + + try: + logging.info(f"\n\n{'>'*50} Data Validation Initiated {'<'*50}\n") + self.data_validation_config = data_validation_config + self.data_ingestion_arfitact = data_ingestion_arfitact + self.validation_error = dict() + + except Exception as e: + raise FertilizerException(e, sys) + + def is_required_colums_exists( + self, + base_df: pd.DataFrame, + current_df: pd.DataFrame, + report_key_name: str + ) -> bool: + try: + base_columns = base_df.columns + current_columns = current_df.columns + + missing_columns = [] + for base_column in base_columns: + if base_column not in current_columns: + logging.info(f"Column: {base_column} is not available") + missing_columns.append(base_column) + + if len(missing_columns) > 0: + self.validation_error[report_key_name] = missing_columns + return False + + return True + + except Exception as e: + raise FertilizerException(e, sys) + + def data_drift( + self, + base_df: pd.DataFrame, + current_df: pd.DataFrame, + report_key_name: str + ): + try: + drift_report = dict() + + base_columns = base_df.columns + current_columns = current_df.columns + + for base_column in base_columns: + base_data, current_data = base_df[base_column], current_df[base_column] + + # Null hypothesis is that both column data drawn from same distribution + + logging.info(f"Hypothesis {base_column}: {base_data.dtype}, {current_data.dtype}") + same_distribution = ks_2samp(base_data, current_data) + + if same_distribution.pvalue > 0.05: + # we are accepting the null hypothesis + drift_report[base_column] = { + "pvalue": float(same_distribution.pvalue), + "same_distribution":True + } + + else: + drift_report[base_column] = { + "pvalue": float(same_distribution.pvalue), + "same_distribution":False + } + self.validation_error[report_key_name] = drift_report + + except Exception as e: + raise FertilizerException(e, sys) + + def initiate_data_validation(self) -> artifact_entity.DataValidationArtifact: + try: + logging.info(f"Reading base dataframe") + base_df = pd.read_csv(self.data_validation_config.base_file_path) + + logging.info(f"Reading train dataframe") + train_df = pd.read_csv(self.data_ingestion_arfitact.train_file_path) + + logging.info(f"Reading test dataframe") + test_df = pd.read_csv(self.data_ingestion_arfitact.test_file_path) + + exclude_column = [TARGET_COLUMN] + base_df = utils.seperate_dependant_column(df=base_df, exclude_column=exclude_column) + train_df = utils.seperate_dependant_column(df=train_df, exclude_column=exclude_column) + test_df = utils.seperate_dependant_column(df=test_df, exclude_column=exclude_column) + + logging.info(f"Is all required columns present in the train_df") + train_df_columns_status = self.is_required_colums_exists( + base_df=base_df, + current_df=train_df, + report_key_name='missing_columns_within_train_dataset') + + test_df_columns_status = self.is_required_colums_exists( + base_df=base_df, + current_df=test_df, + report_key_name='missing_columns_within_test_dataset') + + if train_df_columns_status: + logging.info(f"As all colum are availabel in train_df hence detecting data drift") + + self.data_drift(base_df=base_df, current_df=train_df, report_key_name='data_drift_within_train_dataset') + + if test_df_columns_status: + logging.info(f"As all columns are availabel in test_df hence detecting data drift") + + self.data_drift(base_df=base_df, current_df=test_df, report_key_name='data_drift_within_test_dataset') + + # writting the report + logging.info(f"Writing report in yaml format") + utils.write_yaml_file( + file_path=self.data_validation_config.report_file_path, + data=self.validation_error) + + data_validation_artifact = artifact_entity.DataValidationArtifact( + report_file_path=self.data_validation_config.report_file_path) + + logging.info(f"Data Vadidation Completed. Artifacts saved") + + return data_validation_artifact + + except Exception as e: + raise FertilizerException(e, sys) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/components/model_evaluation.py b/Fertilizer-Recommendation/src/components/model_evaluation.py new file mode 100644 index 0000000000000000000000000000000000000000..962b8814530f4465d7f5ba3f3a3e73f40ba67cfd --- /dev/null +++ b/Fertilizer-Recommendation/src/components/model_evaluation.py @@ -0,0 +1,108 @@ +from src.predictor import ModelResolver +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from src.utils import load_object + +from src.config import TARGET_COLUMN + +from sklearn.metrics import f1_score +import pandas as pd +import numpy as np +import os +import sys + +class ModelEvaluation: + + def __init__( + self, + model_eval_config: config_entity.ModelEvaluationConfig, + data_ingestion_artifact: artifact_entity.DataIngestionArtifact, + data_transformation_artifact: artifact_entity.DataTransformationArtifact, + model_trainer_artifact: artifact_entity.ModelTrainerArtifact + ): + + try: + logging.info(f"\n\n{'>'*50} Model Evaluation Initiated {'<'*50}\n") + self.model_eval_config = model_eval_config + self.data_ingestion_artifact = data_ingestion_artifact + self.data_transformation_artifact = data_transformation_artifact + self.model_trainer_artifact = model_trainer_artifact + self.model_resolver = ModelResolver() + + except Exception as e: + raise FertilizerException(e, sys) + + + def initiate_model_evaluation(self) -> artifact_entity.ModelEvaluationArtifact: + try: + logging.info(f"If the saved model directory contains a model, we will compare which model is best trained:\ + the model from the saved model folder or the new model." + ) + + latest_dir_path = self.model_resolver.get_latest_dir_path() + if latest_dir_path == None: + model_eval_artifact = artifact_entity.ModelEvaluationArtifact(is_model_accepted=True, improved_accuracy=None) + + logging.info(f"Model Evaluation Artifacts: {model_eval_artifact}") + return model_eval_artifact + + # finding location of transformer, model, and target encoder + logging.info(f"Finding location of transformer, model and target encoder") + transformer_path = self.model_resolver.get_latest_transformer_path() + + model_path = self.model_resolver.get_latest_model_path() + + target_encoder_path = self.model_resolver.get_latest_target_encoder_path() + + # finding the location of previous transfomer, model and target encoder + logging.info(f"Previous trained objects of transformer, model and target encoder") + transformer = load_object(file_path=transformer_path) + model = load_object(file_path=model_path) + target_encoder = load_object(file_path=target_encoder_path) + + # finding the location of currently trained objects + logging.info(f"Currently trained model objects") + current_transformer = load_object(file_path=self.data_transformation_artifact.transform_object_path) + + current_model = load_object(file_path=self.model_trainer_artifact.model_path) + + current_target_encoder = load_object(file_path=self.data_transformation_artifact.target_encoder_path) + + # fetching the testing data + test_df = pd.read_csv(self.data_ingestion_artifact.test_file_path) + target_df = test_df[TARGET_COLUMN] + + y_true = target_encoder.transform(target_df) + + # accuracy using previous trained model + input_feature_name = list(transformer.feature_names_in_) + input_arr = transformer.transform(test_df[input_feature_name]) + + y_pred = current_model.predict(input_arr) + y_true = current_target_encoder.transform(target_df) + + previous_model_score = f1_score(y_true=y_true, y_pred=y_pred, average='weighted') + + # accuracy using current model + input_feature_name = list(current_transformer.feature_names_in_) + input_arr = current_transformer.transform(test_df[input_feature_name]) + + y_pred = current_model.predict(input_arr) + y_true = current_target_encoder.transform(target_df) + + current_model_score = f1_score(y_true=y_true, y_pred=y_pred, average='weighted') + + if current_model_score <= previous_model_score: + logging.info(f"Current trained model is not better than previous model") + raise Exception("Current trained model is not better than previous model") + + model_eval_artifact = artifact_entity.ModelEvaluationArtifact(is_model_accepted=True, + improved_accuracy = current_model_score - previous_model_score) + + logging.info(f"Model Eval Artifacts generated") + return model_eval_artifact + + except Exception as e: + raise FertilizerException(e, sys) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/components/model_pusher.py b/Fertilizer-Recommendation/src/components/model_pusher.py new file mode 100644 index 0000000000000000000000000000000000000000..f31af6670a332924f7cbe43106cfa7c0fd80f09e --- /dev/null +++ b/Fertilizer-Recommendation/src/components/model_pusher.py @@ -0,0 +1,71 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from src.predictor import ModelResolver +from src.utils import load_object +from src.utils import save_object + +from src.entity.config_entity import ModelPusherConfig + +from src.entity.artifact_entity import DataTransformationArtifact +from src.entity.artifact_entity import ModelTrainerArtifact +from src.entity.artifact_entity import ModelPusherArtifact + +import os +import sys + +class ModelPusher: + + def __init__( + self, + model_pusher_config: ModelPusherConfig, + data_transformation_artifact: DataTransformationArtifact, + model_trainer_artifact: ModelTrainerArtifact + ): + + try: + logging.info(f"\n\n{'>'*50} Model Pusher Initiated {'<'*50}\n") + self.model_pusher_config = model_pusher_config + self.data_transformation_artifact = data_transformation_artifact + self.model_trainer_artifact = model_trainer_artifact + self.model_resolver = ModelResolver(model_registry=self.model_pusher_config.saved_model_dir) + + except Exception as e: + raise FertilizerException(e, sys) + + def initiate_model_pusher(self) -> ModelPusherArtifact: + try: + # load object + logging.info(f"Loading transformer model and target encoder") + transformer = load_object(file_path=self.data_transformation_artifact.transform_object_path) + model = load_object(file_path=self.model_trainer_artifact.model_path) + target_encoder = load_object(file_path=self.data_transformation_artifact.target_encoder_path) + + # model pusher dir + logging.info(f"Saving model into model pusher directory") + save_object(file_path=self.model_pusher_config.pusher_transformer_path, obj=transformer) + save_object(file_path=self.model_pusher_config.pusher_model_path, obj=model) + save_object(file_path=self.model_pusher_config.pusher_target_encoder_path, obj=target_encoder) + + # saved model dir + logging.info(f"Saving model in saved model dir") + + transformer_path = self.model_resolver.get_latest_save_transformer_path() + model_path = self.model_resolver.get_latest_save_model_path() + target_encoder_path = self.model_resolver.get_latest_save_target_encoder_path() + + save_object(file_path=transformer_path, obj=transformer) + save_object(file_path=model_path, obj=model) + save_object(file_path=target_encoder_path, obj=target_encoder) + + model_pusher_artifact = ModelPusherArtifact( + pusher_model_dir = self.model_pusher_config.pusher_model_dir, + saved_model_dir = self.model_pusher_config.saved_model_dir) + + logging.info(f"Model Pusher Artifacts Generated") + + return model_pusher_artifact + + except Exception as e: + raise FertilizerException(e, sys) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/components/model_trainer.py b/Fertilizer-Recommendation/src/components/model_trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..2d4ec5eb45e2d618c5b65c7122acc6790e460474 --- /dev/null +++ b/Fertilizer-Recommendation/src/components/model_trainer.py @@ -0,0 +1,102 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import FertilizerException +from src import utils + +from typing import Optional +from sklearn.metrics import f1_score +from sklearn.tree import DecisionTreeClassifier +import os +import sys + +class ModelTrainer: + + def __init__( + self, + model_trainer_config: config_entity.ModelTrainerConfig, + data_transformation_artifact: artifact_entity.DataTransformationArtifact): + + try: + logging.info(f"\n\n{'>'*50} Model Trainer Initiated {'<'*50}\n") + self.model_trainer_config = model_trainer_config + self.data_transformation_artifact = data_transformation_artifact + + except Exception as e: + raise FertilizerException(e, sys) + + def train_model(self, X, y): + try: + decision_tree_classifier = DecisionTreeClassifier() + decision_tree_classifier.fit(X, y) + + return decision_tree_classifier + + except Exception as e: + raise FertilizerException(e, sys) + + def initial_model_trainer(self) -> artifact_entity.ModelTrainerArtifact: + try: + logging.info(f"Loading train and test array") + + train_arr = utils.load_numpy_array_data(file_path=self.data_transformation_artifact.transformed_train_path) + test_arr = utils.load_numpy_array_data(file_path=self.data_transformation_artifact.transformed_test_path) + + logging.info(f"Splitting the input and target feature from both train and test arr") + + X_train, y_train = train_arr[:, :-1], train_arr[:, -1] + X_test, y_test = test_arr[:, :-1], test_arr[:, -1] + + logging.info(f"Training the model") + model = self.train_model(X = X_train, y = y_train) + + logging.info(f"Calculating the f1 train score") + yhat_train = model.predict(X_train) + + f1_train_score = f1_score(y_true = y_train, + y_pred = yhat_train, + average="weighted") + + logging.info(f"Calculating the f1 test score") + yhat_test = model.predict(X_test) + + f1_test_score = f1_score(y_true = y_test, + y_pred = yhat_test, + average = 'weighted') + + logging.info(f"train_score : {f1_train_score} and test_score : {f1_test_score}") + + # checking for overfitting or underfitting or expected score + logging.info(f"Checking if our model is underfitting or not") + if f1_test_score < self.model_trainer_config.overfitting_threshold: + raise Exception( + f"Model is not good, as it is not able to give \ + expected accuarcy: {self.model_trainer_config.expected_score}, \ + model actual score: {f1_test_score}" + ) + logging.info(f"Checking if our model is overfitting or not") + diff = abs(f1_train_score - f1_test_score) + + if diff > self.model_trainer_config.overfitting_threshold: + raise Exception( + f"Train and test score diff: {diff} \ + is more than overfitting threshold: {self.model_trainer_config.overfitting_threshold}" + ) + + # save the trained model + logging.info(f"Saving model object") + utils.save_object(file_path=self.model_trainer_config.model_path, obj=model) + + # prepare the artifact + logging.info(f"Prepare the artifact") + model_trainer_artifact = artifact_entity.ModelTrainerArtifact( + model_path = self.model_trainer_config.model_path, + f1_train_score = f1_train_score, + f2_test_score = f1_test_score) + + logging.info(f"Model Trainer Complete, Artifact Generated") + + return model_trainer_artifact + + except Exception as e: + raise FertilizerException(e, sys) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/config.py b/Fertilizer-Recommendation/src/config.py new file mode 100644 index 0000000000000000000000000000000000000000..c600497c7c942333c3c04ff1bf50c72f82d4ed18 --- /dev/null +++ b/Fertilizer-Recommendation/src/config.py @@ -0,0 +1,23 @@ +import pymongo +import pandas as pd +import json +from dataclasses import dataclass +import os +from dotenv import load_dotenv + +load_dotenv() + + +@dataclass +class EnvironmentVariable: + mongo_db_url = os.getenv("MONGO_URL") + + +env = EnvironmentVariable() + +mongo_client = pymongo.MongoClient(env.mongo_db_url) + +TARGET_COLUMN = "Fertilizer Name" +NUMERICAL_FEATURES = ['Temparature', 'Humidity ', 'Moisture', 'Nitrogen', 'Potassium', 'Phosphorous'] +CATEGORICAL_FEATURES = ['Soil Type', 'Crop Type'] +BASE_FILE_PATH = os.path.join("fertilizer-prediction/Fertilizer Prediction.csv") \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/entity/__init__.py b/Fertilizer-Recommendation/src/entity/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/entity/artifact_entity.py b/Fertilizer-Recommendation/src/entity/artifact_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..bccdbaed6731b84856e58496612a3d941a4b127f --- /dev/null +++ b/Fertilizer-Recommendation/src/entity/artifact_entity.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass + + +@dataclass +class DataIngestionArtifact: + feature_store_file_path: str + train_file_path: str + test_file_path: str + + +@dataclass +class DataValidationArtifact: + report_file_path: str + + +@dataclass +class DataTransformationArtifact: + transform_object_path: str + transformed_train_path: str + transformed_test_path: str + target_encoder_path: str + + +@dataclass +class ModelTrainerArtifact: + model_path: str + f1_train_score: float + f2_test_score: float + + +@dataclass +class ModelEvaluationArtifact: + is_model_accepted: bool + improved_accuracy: float + + +@dataclass +class ModelPusherArtifact: + pusher_model_dir: str + saved_model_dir: str diff --git a/Fertilizer-Recommendation/src/entity/config_entity.py b/Fertilizer-Recommendation/src/entity/config_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..4f4fc9ceae6a1c09864bc5640254f978580f670a --- /dev/null +++ b/Fertilizer-Recommendation/src/entity/config_entity.py @@ -0,0 +1,120 @@ +import os +import sys +from src.exception import FertilizerException +from src.logger import logging +from datetime import datetime + +FILE_NAME = "fertilizer.csv" +TRAIN_FILE_NAME = "train.csv" +TEST_FILE_NAME = "test.csv" +TRANSFORMER_OBJECT_FILE_NAME = "transformer.pkl" +TARGET_ENCODER_OBJECT_FILE_NAME = "target_encoder.pkl" +MODEL_FILE_NAME = "model.pkl" + + +class TrainingPipelineConfig: + def __init__(self): + try: + self.artifact_dir = os.path.join( + os.getcwd(), "artifact", f"{datetime.now().strftime('%m%d%Y__%H%M%S')}" + ) + except Exception as e: + raise FertilizerException(e, sys) + + +class DataIngestionConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + try: + self.database_name = "smartcropguard" + self.collection_name = "fertilizer" + self.data_ingestion_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_ingestion" + ) + self.feature_store_file_path = os.path.join( + self.data_ingestion_dir, "feature_store", FILE_NAME + ) + self.train_file_path = os.path.join( + self.data_ingestion_dir, "dataset", TRAIN_FILE_NAME + ) + self.test_file_path = os.path.join( + self.data_ingestion_dir, "dataset", TEST_FILE_NAME + ) + self.test_size = 0.2 + except Exception as e: + raise FertilizerException(e, sys) + + def to_dict(self) -> dict: + try: + return self.__dict__ + except Exception as e: + raise FertilizerException(e, sys) + + +class DataValidationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.data_validation_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_validation" + ) + self.report_file_path = os.path.join(self.data_validation_dir, "report.yaml") + self.missing_threshold = 0.2 + self.base_file_path = os.path.join( + "fertilizer-prediction/Fertilizer Prediction.csv" + ) + + +class DataTransformationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.data_transformation_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_transformation" + ) + self.transform_object_path = os.path.join( + self.data_transformation_dir, + "transformer", + TRANSFORMER_OBJECT_FILE_NAME + ) + self.transformed_train_path = os.path.join( + self.data_transformation_dir, + "transformed", + TRAIN_FILE_NAME.replace("csv", "npz"), + ) + self.transformed_test_path = os.path.join( + self.data_transformation_dir, + "transformed", + TEST_FILE_NAME.replace("csv", "npz"), + ) + self.target_encoder_path = os.path.join( + self.data_transformation_dir, + "target_encoder", + TARGET_ENCODER_OBJECT_FILE_NAME, + ) + + +class ModelTrainerConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.model_trainer_dir = os.path.join( + training_pipeline_config.artifact_dir, "model_trainer" + ) + self.model_path = os.path.join(self.model_trainer_dir, "model", MODEL_FILE_NAME) + self.expected_score = 0.9 + self.overfitting_threshold = 0.1 + + +class ModelEvaluationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.change_threshold = 0.01 + + +class ModelPusherConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.model_pusher_dir = os.path.join( + training_pipeline_config.artifact_dir, "model_pusher" + ) + self.saved_model_dir = os.path.join("saved_models") + self.pusher_model_dir = os.path.join(self.model_pusher_dir, "saved_models") + self.pusher_model_path = os.path.join(self.pusher_model_dir, MODEL_FILE_NAME) + self.pusher_transformer_path = os.path.join( + self.pusher_model_dir, TRANSFORMER_OBJECT_FILE_NAME + ) + self.pusher_target_encoder_path = os.path.join( + self.pusher_model_dir, TARGET_ENCODER_OBJECT_FILE_NAME + ) diff --git a/Fertilizer-Recommendation/src/exception.py b/Fertilizer-Recommendation/src/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..eedae83fcab265b29aece93ace31e14f1694a9ce --- /dev/null +++ b/Fertilizer-Recommendation/src/exception.py @@ -0,0 +1,21 @@ +import sys + + +def error_message_detail(error, error_detail: sys): + _, _, exc_tb = error_detail.exc_info() + file_name = exc_tb.tb_frame.f_code.co_filename + error_message = "Error occurred python script name [{0}] line number [{1}] error message [{2}]".format( + file_name, exc_tb.tb_lineno, str(error) + ) + + return error_message + + +class FertilizerException(Exception): + def __init__(self, error_message, error_detail: sys): + self.error_message = error_message_detail( + error_message, error_detail=error_detail + ) + + def __str__(self): + return self.error_message diff --git a/Fertilizer-Recommendation/src/logger.py b/Fertilizer-Recommendation/src/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..f9535dbdda8d907c724e03c2501e6d650a1afa6f --- /dev/null +++ b/Fertilizer-Recommendation/src/logger.py @@ -0,0 +1,22 @@ +import logging +import os +from datetime import datetime + +# log file name +LOG_FILE_NAME = f"{datetime.now().strftime('%m%d%Y__%H%M%S')}.log" + +# Log directory +LOG_FILE_DIR = os.path.join(os.getcwd(), "logs") + +# create folder if not available +os.makedirs(LOG_FILE_DIR, exist_ok=True) + +# Log file path +LOG_FILE_PATH = os.path.join(LOG_FILE_DIR, LOG_FILE_NAME) + + +logging.basicConfig( + filename=LOG_FILE_PATH, + format="[ %(asctime)s ] %(filename)s - %(lineno)d %(name)s - %(levelname)s - %(message)s", + level=logging.INFO, +) \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/pipeline/__init__.py b/Fertilizer-Recommendation/src/pipeline/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/pipeline/training_pipeline.py b/Fertilizer-Recommendation/src/pipeline/training_pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..f09660093b26cf42783d893864ace151458c4582 --- /dev/null +++ b/Fertilizer-Recommendation/src/pipeline/training_pipeline.py @@ -0,0 +1,85 @@ +from src.logger import logging +from src.exception import FertilizerException +from src.utils import get_collection_as_dataframe +from src.entity import config_entity +from src.entity import artifact_entity +import os +import sys + +from src.components.data_ingestion import DataIngestion +from src.components.data_validation import DataValidation +from src.components.data_transformation import DataTransformation +from src.components.model_trainer import ModelTrainer +from src.components.model_evaluation import ModelEvaluation +from src.components.model_pusher import ModelPusher + +def start_training_pipeline(): + try: + training_pipeline_config = config_entity.TrainingPipelineConfig() + + # data ingestion + data_ingestion_config = config_entity.DataIngestionConfig( + training_pipeline_config=training_pipeline_config) + + data_ingestion_config.to_dict() + + data_ingestion = DataIngestion( + data_ingestion_config=data_ingestion_config) + + data_ingestion_artifact = data_ingestion.initiate_data_ingestion() + + print(f"Data Ingestin complete") + + # data validation + data_validation_config = config_entity.DataValidationConfig(training_pipeline_config=training_pipeline_config) + + data_validation = DataValidation(data_validation_config=data_validation_config, + data_ingestion_arfitact=data_ingestion_artifact) + + data_validation.initiate_data_validation() + print(f"Data Validation Complete") + + # data transformation + data_transformation_config = config_entity.DataTransformationConfig(training_pipeline_config=training_pipeline_config) + + data_transformation = DataTransformation(data_transformation_config=data_transformation_config, + data_ingestion_artifact=data_ingestion_artifact) + + data_transformation_artifact = data_transformation.initiate_data_transformation() + print(f"Data Transformation Complete") + + # model trainer + model_trainer_config = config_entity.ModelTrainerConfig(training_pipeline_config=training_pipeline_config) + + model_trainer = ModelTrainer(model_trainer_config=model_trainer_config, + data_transformation_artifact=data_transformation_artifact) + + model_trainer_artifact = model_trainer.initial_model_trainer() + print(f"Model Trainer Complete") + + # model evaluation + model_evaluation_config = config_entity.ModelEvaluationConfig(training_pipeline_config=training_pipeline_config) + + model_evaluation = ModelEvaluation( + model_eval_config = model_evaluation_config, + data_ingestion_artifact = data_ingestion_artifact, + data_transformation_artifact = data_transformation_artifact, + model_trainer_artifact = model_trainer_artifact) + + model_evalution_artifact = model_evaluation.initiate_model_evaluation() + print(f"Model Evaluation Complete") + + # model pusher + model_pusher_config = config_entity.ModelPusherConfig(training_pipeline_config=training_pipeline_config) + + model_pusher = ModelPusher( + model_pusher_config = model_pusher_config, + data_transformation_artifact = data_transformation_artifact, + model_trainer_artifact = model_trainer_artifact) + + model_trainer_artifact = model_pusher.initiate_model_pusher() + print(f"Model Pusher Complete") + + except Exception as e: + raise FertilizerException(e, sys) + diff --git a/Fertilizer-Recommendation/src/predictor.py b/Fertilizer-Recommendation/src/predictor.py new file mode 100644 index 0000000000000000000000000000000000000000..e4798f4008d72428852e5fe4493489e45d2b966b --- /dev/null +++ b/Fertilizer-Recommendation/src/predictor.py @@ -0,0 +1,118 @@ +from src.entity.config_entity import TRANSFORMER_OBJECT_FILE_NAME +from src.entity.config_entity import MODEL_FILE_NAME +from src.entity.config_entity import TARGET_ENCODER_OBJECT_FILE_NAME +from src.exception import FertilizerException +from src.logger import logging + +import os +import sys +from glob import glob +from typing import Optional + +class ModelResolver: + + def __init__( + self, + model_registry: str = 'saved_models', + transformer_dir_name = 'transformer', + target_encoder_dir_name = 'target_encoder', + model_dir_name = 'model' + ): + + self.model_registry = model_registry + os.makedirs(self.model_registry, exist_ok=True) + + self.transformer_dir_name = transformer_dir_name + self.target_encoder_dir_name = target_encoder_dir_name + self.model_dir_name = model_dir_name + + def get_latest_dir_path(self) ->Optional[str]: + try: + dir_names = os.listdir(self.model_registry) + + if len(dir_names) == 0: + return None + dir_names = list(map(int, dir_names)) + latest_dir_name = max(dir_names) + + return os.path.join(self.model_registry, f"{latest_dir_name}") + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_model_path(self): + try: + latest_dir = self.get_latest_dir_path() + + if latest_dir is None: + raise Exception(f"Model is not available") + + return os.path.join(latest_dir, self.model_dir_name, MODEL_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_transformer_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir is None: + raise Exception(f"Transformer is not available") + + return os.path.join(latest_dir, self.transformer_dir_name, TRANSFORMER_OBJECT_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_target_encoder_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir is None: + raise Exception(f"Target Encoder is not available") + + return os.path.join(latest_dir, self.target_encoder_dir_name, TARGET_ENCODER_OBJECT_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_save_dir_path(self): + try: + latest_dir = self.get_latest_dir_path() + + if latest_dir is None: + return os.path.join(self.model_registry, f"{0}") + + latest_dir_num = int(os.path.basename(self.get_latest_dir_path())) + + return os.path.join(self.model_registry, f"{latest_dir_num + 1}") + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_save_model_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + + return os.path.join(latest_dir, self.model_dir_name, MODEL_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_save_transformer_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + + return os.path.join(latest_dir, self.transformer_dir_name, TRANSFORMER_OBJECT_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + def get_latest_save_target_encoder_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + + return os.path.join(latest_dir, self.target_encoder_dir_name, TARGET_ENCODER_OBJECT_FILE_NAME) + + except Exception as e: + raise FertilizerException(e, sys) + + \ No newline at end of file diff --git a/Fertilizer-Recommendation/src/setup.py b/Fertilizer-Recommendation/src/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Fertilizer-Recommendation/src/utils.py b/Fertilizer-Recommendation/src/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..2b68eb2b6fd0a5289c97319bdd941484048e04f8 --- /dev/null +++ b/Fertilizer-Recommendation/src/utils.py @@ -0,0 +1,115 @@ +import pandas as pd +from src.logger import logging +from src.exception import FertilizerException +from src.config import mongo_client +import os +import sys +import numpy as np +import yaml +import dill + +def get_collection_as_dataframe( + database_name: str, collection_name: str +) -> pd.DataFrame: + """ + Description: This function return collection as dataframe + ========================================================= + Params: + database_name: database name + collection_name: collection name + ========================================================= + return Pandas dataframe of a collection + """ + try: + logging.info( + f"Reading data from database: {database_name} and collection: {collection_name}" + ) + df = pd.DataFrame(list(mongo_client[database_name][collection_name].find())) + logging.info(f"{database_name} found in the mongodb") + + if "_id" in df.columns: + logging.info("Dropping column: '_id'") + df = df.drop(columns=["_id"], axis=1) + logging.info(f"Row and columns in df: {df.shape}") + return df + except Exception as e: + raise FertilizerException(e, sys) + + +def seperate_dependant_column(df: pd.DataFrame, exclude_column: list) -> pd.DataFrame: + final_dataframe = df.drop(exclude_column, axis=1) + + return final_dataframe + + +def get_column_indices(numerical_features: list, categorical_features: list, base_file_path: str): + + dataset = pd.read_csv(base_file_path) + + numerical_feature_indices = [dataset.columns.get_loc(feature) for feature in numerical_features] + categorical_feature_indices = [dataset.columns.get_loc(feature) for feature in categorical_features] + + return numerical_feature_indices, categorical_feature_indices + + +def write_yaml_file(file_path, data: dict): + try: + file_dir = os.path.dirname(file_path) + os.makedirs(file_dir, exist_ok=True) + + with open(file_path, "w") as file_writer: + yaml.dump(data, file_writer) + except Exception as e: + raise FertilizerException(e, sys) + + +def save_object(file_path: str, obj: object) -> None: + try: + logging.info("Entered the save object method of utils") + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, "wb") as file_obj: + dill.dump(obj, file_obj) + logging.info("Exited the save object method of utils") + except Exception as e: + raise FertilizerException(e, sys) + + +def load_object(file_path: str) -> object: + try: + if not os.path.exists(file_path): + raise Exception(f"The file: {file_path} is not exists") + with open(file_path, "rb") as file_obj: + return dill.load(file_obj) + except Exception as e: + raise FertilizerException(e, sys) + + +def save_numpy_array_data(file_path: str, array: np.array): + """ + save numpy array data to file + file_path : str location of the file to save + array: np.array data to save + """ + try: + dir_path = os.path.dirname(file_path) + os.makedirs(dir_path, exist_ok=True) + + with open(file_path, "wb") as file_ojb: + np.save(file_obj, array) + + except Exception as e: + raise FertilizerException(e, sys) + + +def load_numpy_array_data(file_path: str) -> np.array: + """ + load numpy array data from file + file_path: str location of file to load + return: np.array data loaded + """ + try: + with open(file_path, "rb") as file_obj: + return np.load(file_obj, allow_pickle=True) + + except Exception as e: + raise CropException(e, sys) diff --git a/Fertilizer-Recommendation/template.py b/Fertilizer-Recommendation/template.py new file mode 100644 index 0000000000000000000000000000000000000000..7a729e7ad747ac979a69d84d3dbb8c976ecbf300 --- /dev/null +++ b/Fertilizer-Recommendation/template.py @@ -0,0 +1,49 @@ +import os, sys +from pathlib import Path +import logging + +while True: + project_name = input("Enter your project name: ") + if project_name !="": + break + +# src/__init__.py +# src/compontes/__init__.py +list_of_files = [ + f"{project_name}/__init__.py", + f"{project_name}/components/__init__.py", + f"{project_name}/components/data_ingestion.py", + f"{project_name}/components/data_validation.py", + f"{project_name}/components/data_transformation.py", + f"{project_name}/components/model_trainer.py", + f"{project_name}/components/model_evaluation.py", + f"{project_name}/components/model_pusher.py", + f"{project_name}/entity/__init__.py", + f"{project_name}/entity/artifact_entity.py", + f"{project_name}/entity/config_entity.py", + f"{project_name}/pipeline/__init__.py", + f"{project_name}/pipeline/training_pipeline.py", + f"{project_name}/config.py", + f"{project_name}/app.py", + f"{project_name}/logger.py", + f"{project_name}/exception.py", + f"{project_name}/setup.py", + f"{project_name}/utils.py", + f"{project_name}/predictor.py", + "main.py", +] + + +for filepth in list_of_files: + filepath = Path(filepth) + filedir, filename = os.path.split(filepath) + + if filedir !="": + os.makedirs(filedir, exist_ok=True) + + if (not os.path.exists(filepath)) or (os.path.getsize(filepath) == 0): + with open(filepath, "w") as f: + pass + + else: + logging.info("file is already present at : {filepath}") \ No newline at end of file diff --git a/README.md b/README.md index be5c80ae535253cad76e20dd7b53bc02d3372302..937ac6c93e0948cde04a9a9081c403240b779b78 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,97 @@ --- -title: C -emoji: 🐠 -colorFrom: pink -colorTo: indigo -sdk: static +title: CropGaurd +emoji: 🏢 +colorFrom: indigo +colorTo: red +sdk: gradio +sdk_version: 3.39.0 +app_file: app.py pinned: false --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +# CropGaurd +## Agriculture and Farming Machine Learning Project + +Developed a comprehensive web application that harnesses the power of machine learning to provide valuable insights and recommendations to farmers, agriculture enthusiasts, and stakeholders. + +![CropGaurd-thumbnail](https://github.com/07Sada/CropGaurd/assets/112761379/fd5f1726-7450-4758-952e-23e7f7b9da06) + +# Disclaimer +This project serves as a Proof of Concept (PoC) and is not intended for making actual farming decisions. The data utilized within this project is provided without any guarantee from the creator. Therefore, it is strongly advised not to utilize the information for real-world agricultural choices. Should you choose to do so, please be aware that the creator bears no responsibility for the outcomes. + +It's important to note that this project primarily demonstrates the application of Machine Learning (ML) and Deep Learning (DL) concepts within precision farming. The hypothetical scenario presented here underscores the potential benefits of deploying ML/DL techniques on a larger scale, provided that authentic and verified data sources are used. + +For reliable and accurate farming decisions, always rely on verified agricultural data sources, expert advice, and industry standards. + +## Project Links + +- **Application Link:** Check out the live application on Hugging Face Spaces: [Application Link](https://huggingface.co/spaces/Sadashiv/CropGaurd) + +- **Demo Video:** For a visual walkthrough of the application's features, watch demo video: [Demo Video Link]() + +## Project Overview +The application integrates several key features to assist users in making informed decisions for their agricultural activities. These features include: + +- ***Crop Recommendation System:*** Leveraging advanced machine learning techniques, the system recommends suitable crops based on various factors such as soil chemical contents, and climate conditions. + +- ***Fertilizer Recommendation System:*** The application also offers personalized fertilizer recommendations, ensuring that crops receive the optimal nutrients for healthy growth and abundant yields. + +- ***Plant Disease Classification:*** By employing cutting-edge image classification models, incorporated a feature that enables users to detect and diagnose diseases in plants. Users can simply upload images of their plants, and our system will accurately identify any diseases present and provide relevant information about them. + +- ***Real-time Commodity Price Updates:*** To empower users with current market insights, we have integrated a government API that provides daily commodity prices across different Indian states. This information assists farmers and traders in making pricing and distribution decisions. + +## Purpose +The aim of project to revolutionize the agricultural sector by offering data-driven solutions that enhance productivity, reduce risks, and promote sustainable practices. By amalgamating technology and agriculture, we strive to address critical challenges faced by farmers and contribute to the growth of the farming community. + +Whether you're a seasoned farmer seeking optimized strategies or an individual interested in sustainable agriculture, our application provides the tools you need to make well-informed decisions. + +## Additional Details +Here are some additional aspects of the project that contribute to its effectiveness and uniqueness: + +- ***Machine Learning Models:*** We have trained our recommendation and classification models on extensive datasets specific to Indian agriculture. This ensures that the recommendations and classifications are accurate and relevant to the local context. + +- ***User-Friendly Interface:*** Our web application boasts an intuitive and user-friendly interface designed to make navigation and interaction seamless, even for users with limited technological experience. + +- ***Informational Insights:*** Apart from recommendations, our application provides detailed information about recommended crops, fertilizers, and identified plant diseases. This information helps users understand the rationale behind the suggestions and take well-informed actions. + +- ***Scalability:*** Our project's architecture is designed to accommodate future expansions and enhancements. We are committed to continuously improving the application by incorporating user feedback and integrating emerging technologies. + +## Getting Started +- Clone or download the parent repository from [GitHub Repository Link](https://github.com/07Sada/CropGaurd) + + ``` + git clone --recurse-submodules https://github.com/07Sada/CropGaurd + ``` +- The total project is divided into 4 repositories: one parent repository and 3 child repositories. The child repositories are dedicated to specific functionalities, namely [[crop recommendations](https://github.com/07Sada/crop-recommendation)], [[fertilizer recommendations](https://github.com/07Sada/Fertilizer-Recommendation)], and [[image classification](https://github.com/07Sada/plant-diseases-classifier)]. +- The parent and child repositories are connected using Git submodules. This approach is taken to keep each recommendation system separate, as they contain their end-to-end pipelines – from data ingestion to model training and deploying the best models for inference. +- This modular structure allows us to maintain clean and organized code while efficiently managing updates and changes to each submodule. +- The data ingestion pipeline is flexible, as it is integrated with a MongoDB database. You can set up a scheduler to periodically update the training data. After new data is ingested, the models are trained and evaluated against the existing models. The best model is then pushed for inference, all of which is seamlessly automated through the pipeline, reducing the potential for errors. +- To get started, navigate to the parent repository and install the required dependencies. +- Explore each child repository for more specific details on their functionalities and pipelines. +- Launch the web application by running command in terminal. + + ``` + python app.py + ``` +- Start exploring the features and making use of the insightful recommendations provided. + + +## Further Improvements + +These potential improvements are not only achievable but hold the promise of elevating the application's impact and utility: + +- ***Integration of Govt Policies:*** Imagine having the latest government policies and farmer-centric updates right at your fingertips. Our vision includes seamlessly integrating these critical updates, enabling you to stay informed and navigate regulatory changes with ease. + +- ***Language Translation:*** Empowering users globally is within our reach. We envision breaking language barriers by adding translation capabilities. This means you can explore our insights and recommendations in your preferred language, ensuring accessibility for all. + +- ***Weather Information:*** Harnessing real-time weather data can revolutionize your decision-making. Picture accessing accurate weather information directly within the application, allowing you to adapt and strategize based on changing conditions. + +- ***Enhanced Recommendations with More Data:*** Our recommendation systems already provide valuable guidance, but we're not stopping there. By expanding our dataset, we're poised to fine-tune these systems to deliver recommendations that are even more personalized and effective. + +- ***Market Trends Analysis:*** Envision anticipating market trends and price fluctuations effortlessly. With our potential addition of market trend analysis, you can gain insights that empower you to make informed decisions about your produce's pricing and distribution. + +- ***Community Forums:*** We foresee a thriving community within the application—a space where knowledge is freely exchanged. Imagine being part of a network of farmers, sharing insights, experiences, and innovative approaches to agriculture. + +- ***Automated Data Updates:*** Our dedication to keeping our models up-to-date is unwavering. The potential implementation of automated data updates ensures that you're always working with the latest insights and recommendations. + + diff --git a/__pycache__/app.cpython-38.pyc b/__pycache__/app.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fadf160638e6f8a88b2d33cc03df9cd278cd9593 Binary files /dev/null and b/__pycache__/app.cpython-38.pyc differ diff --git a/__pycache__/artifacts.cpython-311.pyc b/__pycache__/artifacts.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea89af04923f14a725947b152306fc63d2850652 Binary files /dev/null and b/__pycache__/artifacts.cpython-311.pyc differ diff --git a/__pycache__/artifacts.cpython-38.pyc b/__pycache__/artifacts.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2861a2c6389c00756e175df1e9746f19d6503d2d Binary files /dev/null and b/__pycache__/artifacts.cpython-38.pyc differ diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08232fd791414b30609b455a1fb341ddc9198c8c Binary files /dev/null and b/__pycache__/config.cpython-311.pyc differ diff --git a/__pycache__/config.cpython-38.pyc b/__pycache__/config.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40e8b146634a716e0ef23ff682594f552e623660 Binary files /dev/null and b/__pycache__/config.cpython-38.pyc differ diff --git a/__pycache__/utils.cpython-311.pyc b/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37afedf4150ff850b10f59953555bdc49419f649 Binary files /dev/null and b/__pycache__/utils.cpython-311.pyc differ diff --git a/__pycache__/utils.cpython-38.pyc b/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..416ab82801793b8471c017ff744faa32dc770b3c Binary files /dev/null and b/__pycache__/utils.cpython-38.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..852bc096574b47f75d5411a52de211b5cce366ed --- /dev/null +++ b/app.py @@ -0,0 +1,152 @@ +from config import crop_model, crop_pipeline_encoder, crop_label_encoder +from config import fertilizer_model, fertilizer_pipeline_encoder, fertilizer_label_encoder +from config import plant_diseases_classifier_model +from utils import retrieve_image_by_name_from_mongodb, retrieve_data +from flask import Flask, request, render_template, jsonify +import requests +import os +import numpy as np +import base64 + +app = Flask(__name__) + +app.config['UPLOAD_FOLDER'] = 'static/uploaded_image' + +@app.route("/") +@app.route("/home") +def home(): + return render_template('index.html') + +@app.route('/crop_recommendation', methods=['GET', 'POST']) +def crop_recommendation(): + return render_template('crop_recommendation_input.html') + +@app.route("/crop_recommendation_output", methods=['GET', 'POST']) +def crop_recommendation_output(): + temperature = request.form.get("temperature") + humidity = request.form.get("humidity") + ph = request.form.get("ph") + nitrogen = request.form.get("nitrogen") + potassium = request.form.get("potassium") + phosphorous = request.form.get("phosphorous") + rain_fall = request.form.get("rain_fall") + + input_list = [nitrogen, phosphorous, potassium, temperature, humidity, ph, rain_fall] + input_array = np.array(input_list).reshape(-1, 7).astype(int) + + transformed_data = crop_pipeline_encoder.transform(input_array) + model_prediction = crop_model.predict(transformed_data).astype(int) + + label = crop_label_encoder.inverse_transform(model_prediction) + print(label) + + # retrieving the image from mongodb dabase + image_data = retrieve_image_by_name_from_mongodb(database_name=os.getenv("CROP_DB_NAME"), + collection_name=os.getenv("CROP_IMAGE_COLLECTION_NAME"), + file_name=str(label[0])) + + # encoding the byte data recieved from the mongodb + image_data_base64 = base64.b64encode(image_data).decode('utf-8') + + # retrieving text data from mongodb + crop_details = retrieve_data(database_name=os.getenv("CROP_DB_NAME"), collection_name= os.getenv("CROP_INFO_COLLECTION_NAME"), search_query=label[0]) + + return render_template('crop_recommendation_output.html', image_data_base64=image_data_base64, input_file_name=label[0], crop_details=crop_details) + + +@app.route('/fertilizer_recommendation', methods=['GET', 'POST']) +def fertilizer_recommendation(): + return render_template('fertilizer_recommendation_input.html') + +@app.route('/fertilizer_recommendation_output', methods=['GET', 'POST']) +def fertilizer_recommendation_output(): + temperature = request.form.get("temperature") + humidity = request.form.get("humidity") + moisture = request.form.get("moisture") + nitrogen = request.form.get("nitrogen") + potassium = request.form.get("potassium") + phosphorous = request.form.get("phosphorous") + soil_type = request.form.get("soil_type") + crop_type = request.form.get("crop_type") + + input_data = [int(temperature), int(humidity), int(moisture), soil_type, crop_type, int(nitrogen), int(potassium), int(phosphorous)] + input_array = np.array(input_data).reshape(-1, 8) + + transformed_data = fertilizer_pipeline_encoder.transform(input_array) + model_prediction = fertilizer_model.predict(transformed_data).astype(int) + + label = fertilizer_label_encoder.inverse_transform(model_prediction) + + # retrieving the image from mongodb dabase + image_data = retrieve_image_by_name_from_mongodb(database_name=os.getenv("FERTILIZER_DB_NAME"), + collection_name=os.getenv("FERTILIZER_IMAGE_COLLECTION_NAME"), + file_name=str(label[0])) + + # encoding the byte data recieved from the mongodb + image_data_base64 = base64.b64encode(image_data).decode('utf-8') + + # retrieving text data from mongodb + fertilizer_details = retrieve_data(database_name=os.getenv("FERTILIZER_DB_NAME"), collection_name= os.getenv("FERTILIZER_INFO_COLLECTION_NAME"), search_query=label[0]) + + + return render_template('fertilizer_recommendation_ouput.html', image_data_base64=image_data_base64, label= label[0], fertilizer_details=fertilizer_details) + + +@app.route('/image_classification', methods=['GET', 'POST']) +def image_classification(): + return render_template('image_classification_input.html') + +@app.route('/image_classification_output', methods=['GET', 'POST']) +def image_classification_output(): + file = request.files['image_file'] + new_filename = "plant_image.JPG" + file.save(os.path.join(app.config['UPLOAD_FOLDER'], new_filename)) + file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename) + + # infercing the with the uploaded image + results = plant_diseases_classifier_model(file_path) + + #fetching all the labels + names_dict = results[0].names + + # fetching the probalility of each class + probs = results[0].probs.data.tolist() + + # selecting class with maximum probability + model_prediction= names_dict[np.argmax(probs)] + + diseases_details = retrieve_data(database_name=os.getenv("DISEASE_DB_NAME"), + collection_name=os.getenv("DISEASE_INFO_COLLECTION_NAME"), + search_query=model_prediction) + + return render_template("image_classification_output.html", model_prediction=model_prediction, diseases_details=diseases_details) + + +@app.route('/market_price') +def market_price(): + return render_template("market_price_input.html") + +@app.route('/market_price_output', methods=['POST']) +def market_price_output(): + # input field name is 'selected_state' + user_input = request.form.get('selected_state') + api_key = os.getenv("COMMODITY_PRICE_API_KEY") + + # Make a request to the API with the user input + api_url = f'https://api.data.gov.in/resource/9ef84268-d588-465a-a308-a864a43d0070?api-key={api_key}&format=json&filters%5Bstate%5D={user_input}' + response = requests.get(api_url) + + if response.status_code == 200: + data = response.json() + data = data['records'] + # return render_template('market_price_output.html', data=data) + if len(data) > 0: + # Return the JSON data as a response + return render_template('market_price_output.html', data=data) + else: + return render_template("market_price_no_data.html") + else: + return jsonify({'error': 'Unable to fetch data from the API'}), 400 + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/artifacts.py b/artifacts.py new file mode 100644 index 0000000000000000000000000000000000000000..7480c459c36efeb19b773d5ad67f1087947d7202 --- /dev/null +++ b/artifacts.py @@ -0,0 +1,31 @@ +import os + +MODEL_NAME = "model.pkl" +TARGET_ENCODER_OBJECT_NAME = "target_encoder.pkl" +TRANSFORMER_OJBCET_NAME = "transformer.pkl" + +crop_recommendation_artifacts_path = "./crop-recommendation/saved_models" +fertilizer_recommendation_artifacts_path = "./Fertilizer-Recommendation/saved_models" + +plant_diseases_classifier_model_path = "./plant-diseases-classifier/custom_model_weights/best.pt" + + +## crop recommendation artifacts +latest_crop_recommendation_artifacts = max(os.listdir(crop_recommendation_artifacts_path)) #0, 1, 2 + +latest_crop_recommendation_artifacts_path = os.path.join(crop_recommendation_artifacts_path, latest_crop_recommendation_artifacts) + +crop_model_path = os.path.join(latest_crop_recommendation_artifacts_path, 'model', MODEL_NAME) +crop_transformer_path = os.path.join(latest_crop_recommendation_artifacts_path,'transformer', TRANSFORMER_OJBCET_NAME) +crop_target_encoder_path = os.path.join(latest_crop_recommendation_artifacts_path, 'target_encoder', TARGET_ENCODER_OBJECT_NAME) + + +## fertilizer recommendation artifacts +latest_fertilizer_recommendation_artifacts = max(os.listdir(fertilizer_recommendation_artifacts_path)) #0, 1, 2 + +latest_fertilizer_recommendation_artifacts_path = os.path.join(fertilizer_recommendation_artifacts_path, latest_fertilizer_recommendation_artifacts) + +fertilizer_model_path = os.path.join(latest_fertilizer_recommendation_artifacts_path, 'model', MODEL_NAME) +fertilizer_transformer_path = os.path.join(latest_fertilizer_recommendation_artifacts_path,'transformer', TRANSFORMER_OJBCET_NAME) +fertilizer_target_encoder_path = os.path.join(latest_fertilizer_recommendation_artifacts_path, 'target_encoder', TARGET_ENCODER_OBJECT_NAME) + diff --git a/config.py b/config.py new file mode 100644 index 0000000000000000000000000000000000000000..a28eff0d2b8208120512a026c1a52e087d604500 --- /dev/null +++ b/config.py @@ -0,0 +1,16 @@ +from artifacts import crop_model_path, crop_transformer_path, crop_target_encoder_path +from artifacts import fertilizer_model_path, fertilizer_transformer_path, fertilizer_target_encoder_path +from artifacts import plant_diseases_classifier_model_path + +from utils import load_model_and_encoders +from ultralytics import YOLO + +crop_model, crop_pipeline_encoder, crop_label_encoder = load_model_and_encoders(model_path=crop_model_path, + transformer_path=crop_transformer_path, + target_encoder_path=crop_target_encoder_path) + +fertilizer_model, fertilizer_pipeline_encoder, fertilizer_label_encoder = load_model_and_encoders(model_path=fertilizer_model_path, + transformer_path=fertilizer_transformer_path, + target_encoder_path=fertilizer_target_encoder_path) + +plant_diseases_classifier_model = YOLO(plant_diseases_classifier_model_path) diff --git a/crop-recommendation/.gitignore b/crop-recommendation/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aa707d116e3cb975cf841540d97f726e397da4f2 --- /dev/null +++ b/crop-recommendation/.gitignore @@ -0,0 +1,168 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +data_dump.py +demo.ipynb +kaggle.json +crop-recommendation-dataset +catboost_info +temp.py +artifact \ No newline at end of file diff --git a/crop-recommendation/.vscode/extensions.json b/crop-recommendation/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..f2443f916752e0f49a4510f1a3fc4761e34b4f45 --- /dev/null +++ b/crop-recommendation/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "mongodb.mongodb-vscode", + "ms-python.python", + "ms-toolsai.jupyter", + "ms-toolsai.jupyter-keymap", + "ms-toolsai.jupyter-renderers", + "formulahendry.code-runner" + ] +} diff --git a/crop-recommendation/.vscode/settings.json b/crop-recommendation/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..8a2533953bf43d9af48928851a0f5535631fd8ff --- /dev/null +++ b/crop-recommendation/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "workbench.colorTheme": "Default Dark+", + "workbench.preferredDarkColorTheme": "Default Dark+", + "task.allowAutomaticTasks": "on", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } +} diff --git a/crop-recommendation/.vscode/tasks.json b/crop-recommendation/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..df60023539b2e7a04dd3b9cf841b61cc8b851cc6 --- /dev/null +++ b/crop-recommendation/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Installing extensions and dependencies...", + "type": "shell", + "command": "code-server --install-extension mongodb.mongodb-vscode --install-extension ms-python.python --install-extension formulahendry.code-runner && pip install -r requirements.txt", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "runOptions": { "runOn": "folderOpen" } + } + ] +} diff --git a/crop-recommendation/LICENSE b/crop-recommendation/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e121f2e91d7901e575e89d72fbae0276c0409c4e --- /dev/null +++ b/crop-recommendation/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sadashiv Nandanikar + +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/crop-recommendation/README.md b/crop-recommendation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7e26f9287ab5d2a0c6b15c04731c5d976acf951f --- /dev/null +++ b/crop-recommendation/README.md @@ -0,0 +1,50 @@ +# Crop Recommendation + +#### Harnessing the capabilities of machine learning models, analyzes specific parameters to suggest the most suitable crops, optimizing yields and efficiency. + +## Demo +### Input Interface +Image 1 + +### Output Interface +Image 1 + +## Data Source +This dataset contains information about the soil and environmental conditions that are ideal for growing different crops. The dataset includes the following columns: + +- `N`: The ratio of nitrogen content in the soil. +- `P`: The ratio of phosphorus content in the soil. +- `K`: The ratio of potassium content in the soil. +- `Temperature`: The temperature in degrees Celsius. +- `Humidity`: The relative humidity in percent. +- `pH`: The pH value of the soil. +- `Rainfall`: The rainfall in millimeters. + +[Link](https://www.kaggle.com/datasets/atharvaingle/crop-recommendation-dataset) for the dataset + +
+ Supported crops + + +- Apple +- Blueberry +- Cherry +- Corn +- Grape +- Pepper +- Orange +- Peach +- Potato +- Soybean +- Strawberry +- Tomato +- Squash +- Raspberry +
+ +## Project Details +This is repository is submodule for [CropGaurd](https://github.com/07Sada/CropGaurd.git) + +## Project PipeLine Stages +![Project PipeLine Stages](https://user-images.githubusercontent.com/112761379/225940480-2a7381b2-6abd-4c1c-8287-0fd49099be8c.jpg) + diff --git a/crop-recommendation/data_download.py b/crop-recommendation/data_download.py new file mode 100644 index 0000000000000000000000000000000000000000..4d119f4492adbeef391e3cc0c24334a7e3d06845 --- /dev/null +++ b/crop-recommendation/data_download.py @@ -0,0 +1,40 @@ +import opendatasets as od +import os +import json +from dotenv import load_dotenv + +# Load variables from .env file +load_dotenv() + +DATASET_URL = "https://www.kaggle.com/datasets/atharvaingle/crop-recommendation-dataset" + +def create_kaggle_json_file(): + # Fetch the username and API key from the .env file + username = os.getenv('username') + key = os.getenv('key') + + kaggle_credentials = { + "username": username, + "key": key + } + + # Path to the kaggle.json file + kaggle_file_path = os.path.join(os.getcwd(), 'kaggle.json') + + # Write the dictionary to the .kaggle/kaggle.json file + with open(kaggle_file_path, 'w') as file: + json.dump(kaggle_credentials, file) + +def remove_kaggle_json_file(): + # Path to the kaggle.json file + kaggle_file_path = os.path.join(os.getcwd(), 'kaggle.json') + + # Remove the kaggle.json file + os.remove(kaggle_file_path) + +create_kaggle_json_file() + +od.download(DATASET_URL) + +# Remove the kaggle.json file after downloading the dataset +remove_kaggle_json_file() \ No newline at end of file diff --git a/crop-recommendation/main.py b/crop-recommendation/main.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1cae98b9797ac75b945bd5fa30517b44b16ad7 --- /dev/null +++ b/crop-recommendation/main.py @@ -0,0 +1,8 @@ +from src.pipeline.training_pipeline import start_training_pipeline + +if __name__ =="__main__": + try: + start_training_pipeline() + + except Exception as e: + print(e) \ No newline at end of file diff --git a/crop-recommendation/notebooks/crop-recommendation-notebook.ipynb b/crop-recommendation/notebooks/crop-recommendation-notebook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..165fbd5875e756cdb9a5697ca5307872b8126d63 --- /dev/null +++ b/crop-recommendation/notebooks/crop-recommendation-notebook.ipynb @@ -0,0 +1,743 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# importing the dependancies \n", + "import pandas as pd \n", + "import numpy as np \n", + "import matplotlib.pyplot as plt \n", + "import seaborn as sns\n", + "\n", + "from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, AdaBoostClassifier\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "from xgboost import XGBClassifier\n", + "from catboost import CatBoostClassifier\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dataset Summary\n", + "\n", + "This dataset was build by augmenting datasets of rainfall, climate and fertilizer data available for India.\n", + "\n", + "- `N` - ratio of Nitrogen content in soil\n", + "- `P` - ratio of Phosphorous content in soil\n", + "- `K` - ratio of Potassium content in soil\n", + "- `temperature` - temperature in degree Celsius\n", + "- `humidity` - relative humidity in %\n", + "- `ph` - ph value of the soil\n", + "- `rainfall` - rainfall in mm" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
NPKtemperaturehumidityphrainfalllabel
090424320.87974482.0027446.502985202.935536rice
185584121.77046280.3196447.038096226.655537rice
260554423.00445982.3207637.840207263.964248rice
374354026.49109680.1583636.980401242.864034rice
478424220.13017581.6048737.628473262.717340rice
\n", + "
" + ], + "text/plain": [ + " N P K temperature humidity ph rainfall label\n", + "0 90 42 43 20.879744 82.002744 6.502985 202.935536 rice\n", + "1 85 58 41 21.770462 80.319644 7.038096 226.655537 rice\n", + "2 60 55 44 23.004459 82.320763 7.840207 263.964248 rice\n", + "3 74 35 40 26.491096 80.158363 6.980401 242.864034 rice\n", + "4 78 42 42 20.130175 81.604873 7.628473 262.717340 rice" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "FILE_PATH = \"/config/workspace/crop-recommendation-dataset/Crop_recommendation.csv\"\n", + "\n", + "df = pd.read_csv(FILE_PATH)\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of the dataset: (2200, 8)\n" + ] + } + ], + "source": [ + "print(f\"Shape of the dataset: {df.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "N 0\n", + "P 0\n", + "K 0\n", + "temperature 0\n", + "humidity 0\n", + "ph 0\n", + "rainfall 0\n", + "label 0\n", + "dtype: int64" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# checking for the null values in the dataset \n", + "df.isnull().sum()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
NPKtemperaturehumidityphrainfall
count2200.0000002200.0000002200.0000002200.0000002200.0000002200.0000002200.000000
mean50.55181853.36272748.14909125.61624471.4817796.469480103.463655
std36.91733432.98588350.6479315.06374922.2638120.77393854.958389
min0.0000005.0000005.0000008.82567514.2580403.50475220.211267
25%21.00000028.00000020.00000022.76937560.2619535.97169364.551686
50%37.00000051.00000032.00000025.59869380.4731466.42504594.867624
75%84.25000068.00000049.00000028.56165489.9487716.923643124.267508
max140.000000145.000000205.00000043.67549399.9818769.935091298.560117
\n", + "
" + ], + "text/plain": [ + " N P K temperature humidity \\\n", + "count 2200.000000 2200.000000 2200.000000 2200.000000 2200.000000 \n", + "mean 50.551818 53.362727 48.149091 25.616244 71.481779 \n", + "std 36.917334 32.985883 50.647931 5.063749 22.263812 \n", + "min 0.000000 5.000000 5.000000 8.825675 14.258040 \n", + "25% 21.000000 28.000000 20.000000 22.769375 60.261953 \n", + "50% 37.000000 51.000000 32.000000 25.598693 80.473146 \n", + "75% 84.250000 68.000000 49.000000 28.561654 89.948771 \n", + "max 140.000000 145.000000 205.000000 43.675493 99.981876 \n", + "\n", + " ph rainfall \n", + "count 2200.000000 2200.000000 \n", + "mean 6.469480 103.463655 \n", + "std 0.773938 54.958389 \n", + "min 3.504752 20.211267 \n", + "25% 5.971693 64.551686 \n", + "50% 6.425045 94.867624 \n", + "75% 6.923643 124.267508 \n", + "max 9.935091 298.560117 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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FSuWaFzCO4re7bW20tWrVzVhwoRkx02YMMF+Cmpy63vdunUlSdHR0Zo3b16S8dHR0XrnnXfuebORlKzvKdGjRw9Jt0K/8ePHJzvNlClT5OvrK0nq1KlTql9vAABwd1wDDgAA3NH169d1/Phx++Pw8HAFBgbq2LFj2rZtmzZv3mw/8q1mzZqaNGmS3N3dkyznmWee0cSJE3XlyhV16dJFffv2VYUKFRQZGalt27Zp7ty5ioqKUpUqVe56GmqtWrV07tw5rV+/Xj/99JNq165tP0ooV65cKliwoFxcXNSuXTvNnz9fx44dU7du3dS7d2+VLFlSISEh2rhxoxYsWKAcOXLo4YcfTvY0wAsXLqhnz54qV66cWrRooWrVqtmPcrp06ZJWr15tPwKqUqVKqlGjRqL533vvPR06dEj+/v6aMGGC/vrrL3Xo0EHly5eXh4eHAgICdPToUf3777/atm2bnnzySbVt2zbRMmrXri3p1nXE3n33Xb344ovKnz+/fXx8qJkWCV/TuLg4BQUF6fLly9q3b5/++OMP+5GIHh4eGjt2rJo0aZLq5/jss8/07rvvqnnz5qpTp45KlSqlHDlyKCAgQLt377YHU66ururUqVOieYsWLaoiRYro0qVL+vbbb1WkSBGVLl3afg26ggULKleuXGltP0WqVq2qhQsX6ty5c+rataseeeQRXbx4UQsXLtSmTZsk3TrduGvXrknmbdKkiYoVK6bz589r0qRJunnzpp588klly5ZNvr6++vHHH3X48GHVrFlT+/btu2MNtWrV0vbt23Xw4EHNnDlTjRs3tt+BNHv27CpcuHCKeunatat+/fVX7d27Vz///LMuXLig7t27q3jx4rp69aqWLVumtWvXSpJKlCihgQMHpvKvBQAA7oUADgAA3NHChQu1cOHCu05ToEAB+fj46OWXX05y6mm8nj17asuWLdq0aZNOnz6tt99+O9H47Nmz65NPPtHGjRvvGsC99NJL+uOPPxQVFaV333030bhnn33WfsTS8OHDtWfPHh05ckSHDh3Sa6+9lmjafPnyafLkyfr666/veh0uX19f+1FBySlTpowmT56c5Jpl+fLl08KFCzVs2DDt2rVLO3fu1M6dO++4nPhrhiVUv359e0Dz22+/JTlKLv7upGnRrl27u4632Wxq0KCBRo0aJW9v7zQ/T1BQkJYvX24/tfF2Hh4eev/991WtWrUk4/r376/3339f586dSxIIffzxx+l+Suzthg8fru+++07//vuv/v333yTjy5Qpo2+++SbZdd7Dw0MTJ05U3759FRYWpu+//z7RNe9cXV01evRoBQYG3jWA6969u3766ScFBATo888/1+eff24fV7duXf34448p6sXV1VXffPONXnnlFe3Zs0fbtm3Ttm3bkkxXtmxZzZo1K9n1EQAA3B8COAAAkCIuLi7KmTOncufOraJFi6pKlSqqU6eOnnjiiXuerubu7q4ZM2Zo4cKF+uWXX3Ty5EkZY1S4cGE1aNBAPXv2VNmyZbVx48a7LqdSpUpatGiR5syZoz179ujatWvJnmKaO3duLVy4UN99951+//13nTlzRq6urnrkkUfUpEkT+fj4qEiRInd8njp16ujHH3/Upk2btG/fPl26dMn+XHnz5lXFihX15JNP6rnnnrtj7w899JDmz5+vv//+W7/99pv27duna9euKSYmRrlz51bJkiVVq1YtNWvWTI899liyf+85c+Zo9uzZ2rBhg86ePavw8HD7EYfpJVu2bMqdO7fy5s2rChUqqFq1amrevLlKlSp1X8udO3euNmzYoF27dsnPz0/Xrl1TUFCQsmfPrhIlSqh+/frq3r27vLy8kp2/e/fuKlSokBYtWqQjR44oMDDwvq7Zl1ru7u6aOXOmFi1apBUrVujUqVOKjo6Wl5eXWrdurd69e9/xhiLSrXVo2bJl+uabb7R161bdvHlT+fLlU+3atdWrVy/Vrl1bkydPvmsNhQsX1pIlSzRjxgzt3LlTly5dUmRkZJr6yZcvn+bPn6+VK1fqt99+s/9Nc+bMqQoVKujpp5/m1FMAACxkM+m9FwcAAAAAAADAjpswAAAAAAAAABYigAMAAAAAAAAsRAAHAAAAAAAAWIgADgAAAAAAALAQARwAAAAAAABgIbeMLsCZ7N27V8YYubu7Z3QpAAAAAAAAyEDR0dGy2WyqVavWPaclgEsFY4yMMRldBgAAAAAAADJYajIiArhUiD/yrVq1ahlcCQAAAAAAADLSwYMHUzwt14ADAAAAAAAALEQABwAAAAAAAFiIAA4AAAAAAACwEAEcAAAAAAAAYCECOAAAAAAAAMBCBHAAAAAAAACAhQjgAAAAAAAAAAsRwAEAAAAAAAAWIoADAAAAAAAALEQABwAA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6f3331fVqlXtF0dPGMIVLlxYI0eO1MaNG9O9JkcVGxur5cuX6++//7aHqMOGDdPOnTslSd7e3jp+/LimTZumESNGaMSIEfYbaZw4cUJbtmzR5cuXLanrdrly5VKPHj2UJ08ebd26VeXLl7dfT6xAgQIqVKiQbDabDh48qMDAwHSvKb25u7srLCzM/j4Vv92XKlVK9erV0/Hjxx3+PSz+Ris9evRQy5Yt9cknn+jAgQNq0qSJPv/8c/Xr10/VqlVTdHS0Tp06pTVr1qhMmTLKkydPRpeeLm7fR4xfb7Nnz6733ntPTz/9tD799FOtXbs20RfW6tWra+TIkfr++++VK1cuh/rMvV2JEiU0e/ZsVahQQVu2bNGJEycSHWHq4+Ojrl276tChQ5o0aVKiz6/00KZNG73xxhu6cuWKJk2aZA/h8uXLp6pVq+qbb77R2LFjNXjwYPXv399+BOLixYuVK1culSlTJl3rsVJUVJT69OljPzoqZ86c6tGjhxYsWKC+ffvq7bff1iuvvCLpVjA6Z84c5cmTR5UrV87Isu8pPDxcEydO1I0bN1SrVi117dpVFy5c0M6dOzV48GCVK1dOOXPmVMOGDZU7d27Nnz9fP/74Y0aXfUcJP5/i92E6dOig1157TWfPntXs2bMThXC9e/eWu7u73n33Xe3evVvff/+9vLy8MqT2eLd/H4+/Y3W/fv109epVTZkyRdKtz6mEIVypUqW0bdu2BxrAhYaG6q+//tKkSZO0dOlStWnTRlevXrWfMfLaa69JunUUYq9evdSvXz/ZbDadOHFCH374oXLlyuWU19y8ff84/jUrX768smfPrs8++0wdOnRQw4YN7ZnLlStXtGHDBl28eDH9PlfS9YRWpFrC86fHjRtnevfubXbv3m2M+f9rMXh7exsfHx/7+cfXrl0zo0ePNk8++aT9+mHOImG/o0ePNp999pkxxpjr16+bFi1amO7du5u6deua119/3QQFBRljbv0dRowYYd58802nujaYMf/fb3BwsHn55ZdN27ZtTbNmzUz//v3NkSNHjDG3rufSuHFj079/f7Nlyxazd+9e06NHDzNx4sQky3E28ddmCAkJMaNHjzYDBgww48aNM0ePHjXGGBMUFGQiIyPtr3VMTIw5ffq0ad++vfn0008zrO7bxW97YWFhZt68eebzzz8333//faLrooWG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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.set(style = \"whitegrid\")\n", + "plt.figure(figsize = (15,7))\n", + "\n", + "ax = sns.countplot(data = df, x= 'label')\n", + "\n", + "ax.set(xlabel='crop name')\n", + "plt.xticks(rotation = 45)\n", + "\n", + "ax.set(ylabel = 'data points')\n", + "\n", + "plt.title(\"Dataset Distribution\", fontsize = 20)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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jedeBrdVBShUpCCDR/RTKXv99ZkTF1QSykbudWnj6a782rlsKZzLlgIg8HHQR5xQig+lSJYFAzlu+PDhXL16lR07cj+JeHtE8XOyMzr37/n7MXj28vPcpVnO1EHTIWiEluLzXLW00/vPjCvlQc6lP8/P89sh2bscJj3uRZpnXCmTtj/48N/5vr6+TJ48GW9vb4yMjGjZsiU//vhjqqdwvC05OZklS5awZs0awsPDKV26NCNGjMDNze2DY8msz/PTSAghhBDiHcnZ+L8PFRUVRbdu3UhISGDevHkMHjyYDRs2MH16xstflixZwty5c+nevTuLFi3C2tqaHj168PDhww+OJ7NkylsIIYQQgo9jDeW6det4+vQp8+fPx9zcHIDExEQmTJhAnz59sLW1TbVdXFwcixYtokePHnTv3h2AihUr0rhxY5YuXcr48eNzNG4ZocwF06dP18h0txBCCCE+LUePHqVatWrKZBKgSZMmJCUlceJE2ucyX7x4kdjYWJo0aaIs09XVpUGDBhw9mn03jUiLjFAKIYQQQpC951DWq1cv3esHDx5MtdzPz4+2bduqlJmammJtbY2fn1+aj/f6WvHixVXKHR0dWblyJS9evMix2+eCJJRCCCGEEMDHscs7Ojpa7c55AGZmZkRFpX2wf3R0NLq6uspbBL9mampKcnIyUVFRklAKIYQQQnxK0hqBzKskoRRCCCGEgCztzs4upqamxMTEqJVHRUVhZqZ+J6m328XHxxMXF6cyShkdHY1CoUi3bXaQTTlCCCGEEKTs8s6ufx+qePHiamslY2JiCAkJUVsf+W47gHv3VG9C4efnh729fY5Od4MklEIIIYQQH41atWpx8uRJoqPf3Eluz5495MuXj6+++irNdhUqVMDY2Jjdu3cryxISEti3bx+1atXK0ZhBpryFEEIIIYDs3eX9oTw9PVm1ahUDBgygT58+BAUFMXPmTDw9PVXOoOzWrRuPHz9m//79AOjp6dGnTx/mzZuHhYUFX3zxBWvXriUyMpKePXvmeNySUAohhBBC8HEcbG5mZsbKlSuZNGkSAwYMwMjIiHbt2jF48GCVeklJSSQmJqqU9erVi+TkZJYtW6a89eLSpUspXLhwjsctCaUQQgghxEfE0dGRFStWpFtn1apVamUKhYI+ffrQp0+fHIosbZJQCiGEEELwcezy/lRJQimE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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "corr_matrix = df._get_numeric_data().corr()\n", + "\n", + "plt.figure(figsize=(7, 5))\n", + "\n", + "sns.heatmap(corr_matrix, annot=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of the train data: (1760, 7)\n", + "Shape of the test data: (440, 7)\n" + ] + } + ], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "\n", + "X = df.drop(columns=['label'])\n", + "y = df['label']\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", + "\n", + "print(f\"Shape of the train data: {X_train.shape}\")\n", + "print(f\"Shape of the test data: {X_test.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import LabelEncoder\n", + "\n", + "le = LabelEncoder()\n", + "\n", + "y_train_transformed = le.fit_transform(y_train)\n", + "y_test_transformed = le.transform(y_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score\n", + "\n", + "def evaluate_clf(true, predicted):\n", + " '''\n", + " This function takes in true values and predicted values\n", + " Returns: Accuracy, F1-Score, Precision, Recall, Roc-auc Score\n", + " '''\n", + " acc = accuracy_score(true, predicted)\n", + " f1 = f1_score(true, predicted, average='weighted')\n", + " precision = precision_score(true, predicted, average='weighted')\n", + " recall = recall_score(true, predicted, average='weighted')\n", + " \n", + " return acc, f1, precision, recall" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "# create a function which can evaluate models and returns a report \n", + "def evaluate_model(X_train, X_test, y_train, y_test, models):\n", + " '''\n", + " This function takes X_train, X_test, y_train, y_test and models dictionary as input\n", + " Iterate through the given model directory and evaluate metrics\n", + "\n", + " Returns:\n", + " DataFrame which contains report of all models metrics \n", + " '''\n", + "\n", + " model_list = []\n", + " metric_list = []\n", + "\n", + " for i in range(len(list(models))):\n", + " model = list(models.values())[i]\n", + " model.fit(X_train, y_train)\n", + "\n", + " # Make predictions\n", + " y_train_pred = model.predict(X_train)\n", + " y_test_pred = model.predict(X_test)\n", + "\n", + " # Training set performances\n", + " model_train_accuracy, model_train_f1, model_train_precision, \\\n", + " model_train_recall = evaluate_clf(y_train, y_train_pred)\n", + "\n", + " # Test set peformances \n", + " model_test_accuracy, model_test_f1, model_test_precision, \\\n", + " model_test_recall = evaluate_clf(y_test, y_test_pred)\n", + "\n", + " print(list(models.keys())[i])\n", + " model_list.append(list(models.keys())[i])\n", + "\n", + " result_dict ={'model_name':list(models.keys())[i], \n", + " \"train_accuracy\": model_train_accuracy, \"test_accuracy\": model_test_accuracy,\n", + " \"train_precision\": model_train_precision, \"test_precision\": model_test_precision,\n", + " 'train_recall': model_train_recall, \"test_recall\":model_test_recall,\n", + " \"train_f1_score\": model_train_f1, \"test_f1_score\": model_test_f1}\n", + "\n", + " metric_list.append(result_dict)\n", + "\n", + " \n", + " return metric_list\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "# Model Dictionary\n", + "models = {\n", + " \"Random Forest\": RandomForestClassifier(),\n", + " \"Decision Tree\": DecisionTreeClassifier(),\n", + " \"Gradient Boosting\": GradientBoostingClassifier(),\n", + " \"K-Neighbors Classifier\": KNeighborsClassifier(),\n", + " \"XGBClassifier\": XGBClassifier(), \n", + " \"CatBoosting Classifier\": CatBoostClassifier(verbose=False),\n", + " \"AdaBoost Classifier\": AdaBoostClassifier()\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "resultant_metrics = evaluate_model(X_train, X_test, y_train_transformed, y_test_transformed, models)\n", + "\n", + "resultant_metrics_df = pd.DataFrame(data=resultant_metrics)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
model_nametrain_accuracytest_accuracytrain_precisiontest_precisiontrain_recalltest_recalltrain_f1_scoretest_f1_score
0Random Forest1.0000000.9931821.0000000.9937351.0000000.9931821.0000000.993175
4XGBClassifier1.0000000.9909091.0000000.9914471.0000000.9909091.0000000.990893
5CatBoosting Classifier1.0000000.9886361.0000000.9898081.0000000.9886361.0000000.988698
2Gradient Boosting1.0000000.9818181.0000000.9842711.0000000.9818181.0000000.981851
1Decision Tree1.0000000.9818181.0000000.9823311.0000000.9818181.0000000.981809
3K-Neighbors Classifier0.9897730.9704550.9901060.9739760.9897730.9704550.9897980.970311
6AdaBoost Classifier0.1920450.1409090.0998620.0712470.1920450.1409090.1182040.085220
\n", + "
" + ], + "text/plain": [ + " model_name train_accuracy test_accuracy train_precision \\\n", + "0 Random Forest 1.000000 0.993182 1.000000 \n", + "4 XGBClassifier 1.000000 0.990909 1.000000 \n", + "5 CatBoosting Classifier 1.000000 0.988636 1.000000 \n", + "2 Gradient Boosting 1.000000 0.981818 1.000000 \n", + "1 Decision Tree 1.000000 0.981818 1.000000 \n", + "3 K-Neighbors Classifier 0.989773 0.970455 0.990106 \n", + "6 AdaBoost Classifier 0.192045 0.140909 0.099862 \n", + "\n", + " test_precision train_recall test_recall train_f1_score test_f1_score \n", + "0 0.993735 1.000000 0.993182 1.000000 0.993175 \n", + "4 0.991447 1.000000 0.990909 1.000000 0.990893 \n", + "5 0.989808 1.000000 0.988636 1.000000 0.988698 \n", + "2 0.984271 1.000000 0.981818 1.000000 0.981851 \n", + "1 0.982331 1.000000 0.981818 1.000000 0.981809 \n", + "3 0.973976 0.989773 0.970455 0.989798 0.970311 \n", + "6 0.071247 0.192045 0.140909 0.118204 0.085220 " + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "resultant_metrics_df = resultant_metrics_df.sort_values(by='test_f1_score', ascending=False)\n", + "resultant_metrics_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.10 64-bit", + "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.10" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/crop-recommendation/requirements.txt b/crop-recommendation/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6b9911f4ad971d1150692db33a0f6f0464c48bce --- /dev/null +++ b/crop-recommendation/requirements.txt @@ -0,0 +1,11 @@ +pymongo +pandas +numpy +matplotlib +seaborn +scikit-learn +opendatasets +python-dotenv +ipykernel +PyYAML +dill \ No newline at end of file diff --git a/crop-recommendation/saved_models/0/model/model.pkl b/crop-recommendation/saved_models/0/model/model.pkl new file mode 100644 index 0000000000000000000000000000000000000000..db6c0aed482d69945dd8da6cc692288bff854f90 --- /dev/null +++ b/crop-recommendation/saved_models/0/model/model.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61347ed5e6bbb2060eddc5a515c43e9d61aae5f6f1c7eaecb1f52b64f2df89a5 +size 3676666 diff --git a/crop-recommendation/saved_models/0/target_encoder/target_encoder.pkl b/crop-recommendation/saved_models/0/target_encoder/target_encoder.pkl new file mode 100644 index 0000000000000000000000000000000000000000..d3760e63c70da6a27e954103b18949902f05f124 --- /dev/null +++ b/crop-recommendation/saved_models/0/target_encoder/target_encoder.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4d38ad63d1d6a7008472cfc1a262ce0c847df792eb95e716067c847ea521a30 +size 499 diff --git a/crop-recommendation/saved_models/0/transformer/transformer.pkl b/crop-recommendation/saved_models/0/transformer/transformer.pkl new file mode 100644 index 0000000000000000000000000000000000000000..916ef7d052dc9f0db43cf633f6327c2d104a5b45 --- /dev/null +++ b/crop-recommendation/saved_models/0/transformer/transformer.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ca70d7ada1f6e8d0cf1594a6963de0ccf973b16d140c091070eca8568b3108 +size 901 diff --git a/crop-recommendation/src/__init__.py b/crop-recommendation/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crop-recommendation/src/components/__init__.py b/crop-recommendation/src/components/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crop-recommendation/src/components/data_ingestion.py b/crop-recommendation/src/components/data_ingestion.py new file mode 100644 index 0000000000000000000000000000000000000000..1c575c2b8c99e8a372ee1df8cbbe4b4ebfa03a1d --- /dev/null +++ b/crop-recommendation/src/components/data_ingestion.py @@ -0,0 +1,73 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.exception import CropException +from src.logger import logging +from src import utils + +from sklearn.model_selection import train_test_split +import numpy as np +import pandas as pd +import sys +import os + + +class DataIngestion: + def __init__(self, data_ingestion_config: config_entity.DataIngestionConfig): + try: + logging.info(f"{'>>'*20} Data Ingestion {'<<'*20}") + self.data_ingestion_config = data_ingestion_config + except Exception as e: + raise CropException(e, sys) + + def initiate_data_ingestion(self) -> artifact_entity.DataIngestionArtifact: + try: + logging.info("Exporting collection data as pandas dataframe") + + df: pd.DataFrame = utils.get_collection_as_dataframe( + database_name=self.data_ingestion_config.database_name, + collection_name=self.data_ingestion_config.collection_name, + ) + + logging.info("Saving data in feature store") + + feature_store_dir = os.path.dirname(self.data_ingestion_config.feature_store_file_path) + os.makedirs(feature_store_dir, exist_ok=True) + + logging.info("Saving dataframe into feature store") + df.to_csv( + path_or_buf=self.data_ingestion_config.feature_store_file_path, + index=False, + header=True, + ) + + logging.info("split dataset into train and test test") + train_df, test_df = train_test_split( + df, test_size=self.data_ingestion_config.test_size, random_state=42 + ) + + logging.info("create dataset directory folder if not available") + dataset_dir = os.path.dirname(self.data_ingestion_config.train_file_path) + os.makedirs(dataset_dir, exist_ok=True) + + logging.info("Save df to feature store folder") + train_df.to_csv( + path_or_buf=self.data_ingestion_config.train_file_path, + index=False, + header=True, + ) + test_df.to_csv( + path_or_buf=self.data_ingestion_config.test_file_path, + index=False, + header=True, + ) + + data_ingestion_artifact = artifact_entity.DataIngestionArtifact( + feature_store_file_path=self.data_ingestion_config.feature_store_file_path, + train_file_path=self.data_ingestion_config.train_file_path, + test_file_path=self.data_ingestion_config.test_file_path, + ) + logging.info(f"Data ingestion artifact: {data_ingestion_artifact}") + return data_ingestion_artifact + + except Exception as e: + raise CropException(error_message=e, error_detail=sys) diff --git a/crop-recommendation/src/components/data_trasformation.py b/crop-recommendation/src/components/data_trasformation.py new file mode 100644 index 0000000000000000000000000000000000000000..60b2f3d8bb9e0fc3ef3acb666de5b147775f868a --- /dev/null +++ b/crop-recommendation/src/components/data_trasformation.py @@ -0,0 +1,113 @@ +from src.entity import artifact_entity +from src.entity import config_entity +from src.logger import logging +from src.exception import CropException +from src import utils +from src.config import TARGET_COLUMN + +from typing import Optional +import os +import sys + +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import LabelEncoder +from sklearn.preprocessing import StandardScaler +import pandas as pd +import numpy as np + + +class DataTransformation: + def __init__( + self, + data_transformation_config: config_entity.DataTransformationConfig, + data_ingestion_artifact: artifact_entity.DataIngestionArtifact, + ): + try: + logging.info(f"{'>'*20} Data Transformation Initiated {'<'*20}") + self.data_transformation_config = data_transformation_config + self.data_ingestion_artifact = data_ingestion_artifact + + except Exception as e: + raise CropException(e, sys) + + @classmethod + def get_data_tranformer_object(cls) -> Pipeline: + try: + standard_scaler = StandardScaler() + + pipeline = Pipeline(steps=[("StandardScaler", standard_scaler)]) + + return pipeline + + except Exception as e: + raise CropException(e, sys) + + def initiate_data_transformation( + self, + ) -> artifact_entity.DataTransformationArtifact: + try: + # reading training and testing file + train_df = pd.read_csv(self.data_ingestion_artifact.train_file_path) + test_df = pd.read_csv(self.data_ingestion_artifact.test_file_path) + + # selecting input features for train and test dataframe + input_feature_train_df = train_df.drop(TARGET_COLUMN, axis=1) + input_feature_test_df = test_df.drop(TARGET_COLUMN, axis=1) + + # selecting target feature for train and test dataframe + target_feature_train_df = train_df[TARGET_COLUMN] + target_feature_test_df = test_df[TARGET_COLUMN] + + label_encoder = LabelEncoder() + label_encoder.fit(target_feature_train_df) + + # transformation on target column + target_feature_train_arr = label_encoder.transform(target_feature_train_df) + target_feature_test_arr = label_encoder.transform(target_feature_test_df) + + # transforming input features + transformation_pipeline = DataTransformation.get_data_tranformer_object() + transformation_pipeline.fit(input_feature_train_df) + + input_feature_train_arr = transformation_pipeline.transform( + input_feature_train_df + ) + input_feature_test_arr = transformation_pipeline.transform( + input_feature_test_df + ) + + train_arr = np.c_[input_feature_train_arr, target_feature_train_arr] + test_arr = np.c_[input_feature_test_arr, target_feature_test_arr] + + # save the numpy array + utils.save_object( + file_path=self.data_transformation_config.transformed_train_path, + obj=train_arr, + ) + utils.save_object( + file_path=self.data_transformation_config.transformed_test_path, + obj=test_arr, + ) + + utils.save_object( + file_path=self.data_transformation_config.transform_object_path, + obj=transformation_pipeline, + ) + + utils.save_object( + file_path=self.data_transformation_config.target_encoder_path, + obj=label_encoder, + ) + + data_transformation_artifact = artifact_entity.DataTransformationArtifact( + transform_object_path=self.data_transformation_config.transform_object_path, + transformed_train_path=self.data_transformation_config.transformed_train_path, + transformed_test_path=self.data_transformation_config.transformed_test_path, + target_encoder_path=self.data_transformation_config.target_encoder_path, + ) + + logging.info(f"Data transformation object : {data_transformation_artifact}") + return data_transformation_artifact + + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/components/data_validation.py b/crop-recommendation/src/components/data_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..f0a99f75e0d5aa476ccbbdf2dd52f978942b8005 --- /dev/null +++ b/crop-recommendation/src/components/data_validation.py @@ -0,0 +1,159 @@ +from src.entity import artifact_entity +from src.entity import config_entity +from src.logger import logging +from src.exception import CropException +from src.config import TARGET_COLUMN +from src import utils + +from typing import Optional +from scipy.stats import ks_2samp +import pandas as pd +import numpy as np +import sys +import os + + +class DataValidation: + def __init__( + self, + data_validation_config: config_entity.DataValidationConfig, + data_ingestion_artifact: artifact_entity.DataIngestionArtifact, + ): + try: + logging.info(f"{'>'*20} Data Validation iniated {'<'*20}") + self.data_validation_config = data_validation_config + self.data_ingestion_artifact = data_ingestion_artifact + self.validation_error = dict() + except Exception as e: + raise CropException(e, sys) + + def is_required_columns_exists( + self, base_df: pd.DataFrame, current_df: pd.DataFrame, report_key_name: str + ) -> bool: + try: + base_columns = base_df.columns + current_columns = current_df.columns + + missing_columns = [] + for base_column in base_columns: + if base_column not in current_columns: + logging.info(f"Column: {base_column} is not available") + missing_columns.append(base_column) + + if len(missing_columns) > 0: + self.validation_error[report_key_name] = missing_columns + return False + + return True + + except Exception as e: + raise CropException(e, sys) + + def data_drift( + self, base_df: pd.DataFrame, current_df: pd.DataFrame, report_key_name: str + ): + try: + drift_report = dict() + + base_columns = base_df.columns + current_columns = current_df.columns + + for base_column in base_columns: + base_data, current_data = base_df[base_column], current_df[base_column] + + # Null hypothesis is that both columns data drawn from same distribution + + logging.info( + f"Hypothesis {base_column} : {base_data.dtype}, {current_data.dtype}" + ) + same_distribution = ks_2samp(base_data, current_data) + + if same_distribution.pvalue > 0.05: + # we are accepting the null hypothesis + drift_report[base_column] = { + "pvalue": float(same_distribution.pvalue), + "same_distribution": True, + } + + else: + drift_report[base_column] = { + "pvalue": float(same_distribution.pvalue), + "same_distribution": False, + } + + self.validation_error[report_key_name] = drift_report + + except Exception as e: + raise CropException(e, sys) + + def initiate_data_validation(self) -> artifact_entity.DataValidationArtifact: + try: + logging.info(f"Reading base dataframe") + base_df = pd.read_csv(self.data_validation_config.base_file_path) + + logging.info(f"Reading train dataframe") + train_df = pd.read_csv(self.data_ingestion_artifact.train_file_path) + + logging.info(f"Reading test dataframe") + test_df = pd.read_csv(self.data_ingestion_artifact.test_file_path) + + exclude_column = [TARGET_COLUMN] + base_df = utils.seperate_dependant_column( + df=base_df, exclude_column=exclude_column + ) + train_df = utils.seperate_dependant_column( + df=train_df, exclude_column=exclude_column + ) + test_df = utils.seperate_dependant_column( + df=test_df, exclude_column=exclude_column + ) + + logging.info(f"Is all required columns present in the train_df") + train_df_columns_status = self.is_required_columns_exists( + base_df=base_df, + current_df=train_df, + report_key_name="missing_columns_within_train_dataset", + ) + + test_df_columns_status = self.is_required_columns_exists( + base_df=base_df, + current_df=test_df, + report_key_name="missing_columns_within_test_dataset", + ) + + if train_df_columns_status: + logging.info( + f"As all column are available in train df hence detecting data drift" + ) + self.data_drift( + base_df=base_df, + current_df=train_df, + report_key_name="data_drift_within_train_dataset", + ) + + if test_df_columns_status: + logging.info( + f"As all column are available in test df hence detecting data drift" + ) + self.data_drift( + base_df=base_df, + current_df=test_df, + report_key_name="data_drift_within_test_dataset", + ) + + # writing the report + logging.info("Writing report in yaml format") + utils.write_yaml_file( + file_path=self.data_validation_config.report_file_path, + data=self.validation_error, + ) + + data_validation_artifact = artifact_entity.DataValidationArtifact( + report_file_path=self.data_validation_config.report_file_path + ) + logging.info(f"Data validation artifact: {data_validation_artifact}") + + return data_validation_artifact + + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/components/model_evaluation.py b/crop-recommendation/src/components/model_evaluation.py new file mode 100644 index 0000000000000000000000000000000000000000..880663d93e4e132a944df54e3caafa71b7536bf6 --- /dev/null +++ b/crop-recommendation/src/components/model_evaluation.py @@ -0,0 +1,123 @@ +from src.predictor import ModelResolver +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import CropException +from src.config import TARGET_COLUMN +from src.utils import load_object + +from sklearn.metrics import f1_score +import pandas as pd +import numpy as np +import os +import sys + + +class ModelEvaluation: + def __init__( + self, + model_eval_config: config_entity.ModelEvaluationConfig, + data_ingesiton_artifact: artifact_entity.DataIngestionArtifact, + data_transformation_artifact: artifact_entity.DataTransformationArtifact, + model_trainer_artifact: artifact_entity.ModelTrainerArtifact, + ): + try: + logging.info(f"{'>'*20} Model Evaluation Initiated {'<'*20}") + self.model_eval_config = model_eval_config + self.data_ingesiton_artifact = data_ingesiton_artifact + self.data_transformation_artifact = data_transformation_artifact + self.model_trainer_artifact = model_trainer_artifact + self.model_resolver = ModelResolver() + + except Exception as e: + raise CropException(e, sys) + + def initiate_model_evaluation(self) -> artifact_entity.ModelEvaluationArtifact: + try: + logging.info( + f"If the saved model directory contains a model, we will compare which model is best trained: \ + the model from the saved model folder or the new model." + ) + + latest_dir_path = self.model_resolver.get_latest_dir_path() + if latest_dir_path == None: + model_eval_artifact = artifact_entity.ModelEvaluationArtifact( + is_model_accepted=True, improved_accuracy=None + ) + logging.info(f"Model evaluation artifact: {model_eval_artifact}") + return model_eval_artifact + + # finding location of transformed model, and target encoder + logging.info(f"Finding location of transformer model and target encoder") + transformer_path = self.model_resolver.get_latest_transformer_path() + + model_path = self.model_resolver.get_latest_model_path() + + target_encoder_path = self.model_resolver.get_latest_target_encoder_path() + + logging.info( + f"Previous trained objects of transformer, model and target encoder" + ) + # previous trained objects + transformer = load_object(file_path=transformer_path) + model = load_object(file_path=model_path) + target_encoder = load_object(file_path=target_encoder_path) + + logging.info(f"Currently trained model objects") + # currently trained model objects + current_transformer = load_object( + file_path=self.data_transformation_artifact.transform_object_path + ) + current_model = load_object( + file_path=self.model_trainer_artifact.model_path + ) + current_target_encoder = load_object( + file_path=self.data_transformation_artifact.target_encoder_path + ) + + test_df = pd.read_csv(self.data_ingesiton_artifact.test_file_path) + target_df = test_df[TARGET_COLUMN] + + y_true = target_encoder.transform(target_df) + + # accuracy using previous trained model + + input_feature_name = list(transformer.feature_names_in_) + input_arr = transformer.transform(test_df[input_feature_name]) + + y_pred = current_model.predict(input_arr) + y_true = current_target_encoder.transform(target_df) + + + previous_model_score = f1_score( + y_true=y_true, y_pred=y_pred, average="weighted" + ) + + # accuracy using current model + input_feature_name = list(current_transformer.feature_names_in_) + input_arr = current_transformer.transform(test_df[input_feature_name]) + + y_pred = current_model.predict(input_arr) + y_true = current_target_encoder.transform(target_df) + + + current_model_score = f1_score( + y_true=y_true, y_pred=y_pred, average="weighted" + ) + + logging.info(f"Accuracy using current trained model: {current_model_score}") + + if current_model_score <= previous_model_score: + logging.info(f"Current trained model is not better than previous model") + raise Exception("Current trained model is not better than previous model") + + model_eval_artifact = artifact_entity.ModelEvaluationArtifact( + is_model_accepted=True, + improved_accuracy=current_model_score - previous_model_score, + ) + logging.info(f"Model Eval artifacts: {model_eval_artifact}") + + return model_eval_artifact + + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/components/model_pusher.py b/crop-recommendation/src/components/model_pusher.py new file mode 100644 index 0000000000000000000000000000000000000000..720c0d7713b99ad53a436269f29a94cbd0c20e15 --- /dev/null +++ b/crop-recommendation/src/components/model_pusher.py @@ -0,0 +1,69 @@ +from src.entity.config_entity import ModelPusherConfig +from src.entity import artifact_entity +from src.predictor import ModelResolver +from src.exception import CropException +from src.logger import logging +from src.utils import load_object, save_object +from src.entity.artifact_entity import ( + DataTransformationArtifact, + ModelTrainerArtifact, + ModelPusherArtifact, +) +import sys +import os + + +class ModelPusher: + def __init__( + self, + model_pusher_config: ModelPusherConfig, + data_transformation_artifact: DataTransformationArtifact, + model_trainer_artifact: ModelTrainerArtifact, + ): + try: + logging.info(f"{'>'*20} Model Pusher Initiated {'<'*30}") + self.model_pusher_config = model_pusher_config + self.data_transformation_artifact = data_transformation_artifact + self.model_trainer_artifact = model_trainer_artifact + self.model_resolver = ModelResolver( + model_registry=self.model_pusher_config.saved_model_dir + ) + except Exception as e: + raise CropException(e, sys) + + def initiate_model_pusher(self) -> ModelPusherArtifact: + try: + # load object + logging.info(f"Loading transformer model and target encoder") + transformer = load_object(file_path=self.data_transformation_artifact.transform_object_path) + model = load_object(file_path=self.model_trainer_artifact.model_path) + target_encoder = load_object(file_path=self.data_transformation_artifact.target_encoder_path) + + # model pusher dir + logging.info(f"Saving model into model pusher directory") + save_object(file_path=self.model_pusher_config.pusher_transformer_path,obj=transformer) + save_object(file_path=self.model_pusher_config.pusher_model_path, obj=model) + save_object(file_path=self.model_pusher_config.pusher_target_encoder_path, obj=target_encoder) + + + # saved model dir + logging.info(f"Saving model in saved model dir") + + transformer_path = self.model_resolver.get_latest_save_transformer_path() + model_path = self.model_resolver.get_latest_save_model_path() + target_encoder_path = self.model_resolver.get_latest_save_target_encoder_path() + + save_object(file_path=transformer_path, obj=transformer) + save_object(file_path=model_path, obj=model) + save_object(file_path=target_encoder_path, obj=target_encoder) + + model_pusher_artifact = ModelPusherArtifact( + pusher_model_dir=self.model_pusher_config.pusher_model_dir, + saved_model_dir=self.model_pusher_config.saved_model_dir, + ) + logging.info(f"Model Pusher artifact: {model_pusher_artifact}") + + return model_pusher_artifact + + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/components/model_trainer.py b/crop-recommendation/src/components/model_trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..36b507e76c72e80a8851b9487ce71fa4075e0b89 --- /dev/null +++ b/crop-recommendation/src/components/model_trainer.py @@ -0,0 +1,107 @@ +from src.entity import config_entity +from src.entity import artifact_entity +from src.logger import logging +from src.exception import CropException +from src import utils + +from typing import Optional +from sklearn.metrics import f1_score +from sklearn.ensemble import RandomForestClassifier +import os +import sys + + +class ModelTrainer: + def __init__( + self, + model_trainer_config: config_entity.ModelTrainerConfig, + data_transformation_artifact: artifact_entity.DataTransformationArtifact, + ): + try: + logging.info(f"{'>'*30} Model Trainer Initiated {'<'*30}") + self.model_trainer_config = model_trainer_config + self.data_transformation_artifact = data_transformation_artifact + + except Exception as e: + raise CropException(e, sys) + + def train_model(self, X, y): + try: + random_forest = RandomForestClassifier() + random_forest.fit(X, y) + + return random_forest + + except Exception as e: + raise CropException(e, sys) + + def initiate_model_trainer(self) -> artifact_entity.ModelTrainerArtifact: + try: + logging.info(f"Loading train and test array") + train_arr = utils.load_numpy_array_data( + file_path=self.data_transformation_artifact.transformed_train_path + ) + test_arr = utils.load_numpy_array_data( + file_path=self.data_transformation_artifact.transformed_test_path + ) + + logging.info( + f"Splitting input and target feature from both train and test arr. " + ) + X_train, y_train = train_arr[:, :-1], train_arr[:, -1] + X_test, y_test = test_arr[:, :-1], test_arr[:, -1] + + logging.info(f"Training the model") + model = self.train_model(X=X_train, y=y_train) + + logging.info(f"Calculating f1 train scrore") + yhat_train = model.predict(X_train) + f1_train_score = f1_score( + y_true=y_train, y_pred=yhat_train, average="weighted" + ) + + logging.info(f"Calculating f1 test score") + yhat_test = model.predict(X_test) + f1_test_score = f1_score( + y_true=y_test, y_pred=yhat_test, average="weighted" + ) + + logging.info( + f"train_score: {f1_train_score} and test score: {f1_test_score}" + ) + + # checking for overfitting or underfitting or expected score + logging.info(f"Checking if out model is underfitting or not") + if f1_test_score < self.model_trainer_config.expected_score: + raise Exception( + f"Model is not good as it is not able to give \ + expected accuracy: {self.model_trainer_config.expected_score}, model actual score: {f1_test_score}" + ) + + logging.info(f"Checking if our model is overfitting or not") + diff = abs(f1_train_score - f1_test_score) + + if diff > self.model_trainer_config.overfitting_threshold: + raise Exception( + f"Train and test score diff: {diff} \ + is more than overfitting threshold: {self.model_trainer_config.overfitting_threshold}" + ) + + # save the trained model + logging.info(f"Saving model object") + utils.save_object(file_path=self.model_trainer_config.model_path, obj=model) + + # prepare artifact + logging.info(f"Prepare the artifact") + model_trainer_artifact = artifact_entity.ModelTrainerArtifact( + model_path=self.model_trainer_config.model_path, + f1_train_score=f1_train_score, + f2_test_score=f1_test_score, + ) + + logging.info(f"Model trainer artifact: {model_trainer_artifact}") + + return model_trainer_artifact + + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/config.py b/crop-recommendation/src/config.py new file mode 100644 index 0000000000000000000000000000000000000000..ead80bdc4da3ec8e4cf938862c6c299bad221a4d --- /dev/null +++ b/crop-recommendation/src/config.py @@ -0,0 +1,20 @@ +import pymongo +import pandas as pd +import json +from dataclasses import dataclass +import os +from dotenv import load_dotenv + +load_dotenv() + + +@dataclass +class EnvironmentVariable: + mongo_db_url = os.getenv("MONGO_URL") + + +env = EnvironmentVariable() + +mongo_client = pymongo.MongoClient(env.mongo_db_url) + +TARGET_COLUMN = "label" diff --git a/crop-recommendation/src/entity/__init__.py b/crop-recommendation/src/entity/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crop-recommendation/src/entity/artifact_entity.py b/crop-recommendation/src/entity/artifact_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..bccdbaed6731b84856e58496612a3d941a4b127f --- /dev/null +++ b/crop-recommendation/src/entity/artifact_entity.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass + + +@dataclass +class DataIngestionArtifact: + feature_store_file_path: str + train_file_path: str + test_file_path: str + + +@dataclass +class DataValidationArtifact: + report_file_path: str + + +@dataclass +class DataTransformationArtifact: + transform_object_path: str + transformed_train_path: str + transformed_test_path: str + target_encoder_path: str + + +@dataclass +class ModelTrainerArtifact: + model_path: str + f1_train_score: float + f2_test_score: float + + +@dataclass +class ModelEvaluationArtifact: + is_model_accepted: bool + improved_accuracy: float + + +@dataclass +class ModelPusherArtifact: + pusher_model_dir: str + saved_model_dir: str diff --git a/crop-recommendation/src/entity/config_entity.py b/crop-recommendation/src/entity/config_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..09cc331cf215fdcf8985da98471b78fc263cd420 --- /dev/null +++ b/crop-recommendation/src/entity/config_entity.py @@ -0,0 +1,120 @@ +import os +import sys +from src.exception import CropException +from src.logger import logging +from datetime import datetime + +FILE_NAME = "crop.csv" +TRAIN_FILE_NAME = "train.csv" +TEST_FILE_NAME = "test.csv" +TRANSFORMER_OBJECT_FILE_NAME = "transformer.pkl" +TARGET_ENCODER_OBJECT_FILE_NAME = "target_encoder.pkl" +MODEL_FILE_NAME = "model.pkl" + + +class TrainingPipelineConfig: + def __init__(self): + try: + self.artifact_dir = os.path.join( + os.getcwd(), "artifact", f"{datetime.now().strftime('%m%d%Y__%H%M%S')}" + ) + except Exception as e: + raise CropException(e, sys) + + +class DataIngestionConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + try: + self.database_name = "smartcropguard" + self.collection_name = "crop" + self.data_ingestion_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_ingestion" + ) + self.feature_store_file_path = os.path.join( + self.data_ingestion_dir, "feature_store", FILE_NAME + ) + self.train_file_path = os.path.join( + self.data_ingestion_dir, "dataset", TRAIN_FILE_NAME + ) + self.test_file_path = os.path.join( + self.data_ingestion_dir, "dataset", TEST_FILE_NAME + ) + self.test_size = 0.2 + except Exception as e: + raise CropException(e, sys) + + def to_dict(self) -> dict: + try: + return self.__dict__ + except Exception as e: + raise CropException(e, sys) + + +class DataValidationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.data_validation_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_validation" + ) + self.report_file_path = os.path.join(self.data_validation_dir, "report.yaml") + self.missing_threshold = 0.2 + self.base_file_path = os.path.join( + "crop-recommendation-dataset/Crop_recommendation.csv" + ) + + +class DataTransformationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.data_transformation_dir = os.path.join( + training_pipeline_config.artifact_dir, "data_transformation" + ) + self.transform_object_path = os.path.join( + self.data_transformation_dir, + "transformer", + TRANSFORMER_OBJECT_FILE_NAME + ) + self.transformed_train_path = os.path.join( + self.data_transformation_dir, + "transformed", + TRAIN_FILE_NAME.replace("csv", "npz"), + ) + self.transformed_test_path = os.path.join( + self.data_transformation_dir, + "transformed", + TEST_FILE_NAME.replace("csv", "npz"), + ) + self.target_encoder_path = os.path.join( + self.data_transformation_dir, + "target_encoder", + TARGET_ENCODER_OBJECT_FILE_NAME, + ) + + +class ModelTrainerConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.model_trainer_dir = os.path.join( + training_pipeline_config.artifact_dir, "model_trainer" + ) + self.model_path = os.path.join(self.model_trainer_dir, "model", MODEL_FILE_NAME) + self.expected_score = 0.9 + self.overfitting_threshold = 0.1 + + +class ModelEvaluationConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.change_threshold = 0.01 + + +class ModelPusherConfig: + def __init__(self, training_pipeline_config: TrainingPipelineConfig): + self.model_pusher_dir = os.path.join( + training_pipeline_config.artifact_dir, "model_pusher" + ) + self.saved_model_dir = os.path.join("saved_models") + self.pusher_model_dir = os.path.join(self.model_pusher_dir, "saved_models") + self.pusher_model_path = os.path.join(self.pusher_model_dir, MODEL_FILE_NAME) + self.pusher_transformer_path = os.path.join( + self.pusher_model_dir, TRANSFORMER_OBJECT_FILE_NAME + ) + self.pusher_target_encoder_path = os.path.join( + self.pusher_model_dir, TARGET_ENCODER_OBJECT_FILE_NAME + ) diff --git a/crop-recommendation/src/exception.py b/crop-recommendation/src/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..8c8899fddf491aafd4a59ed3209862647cc08ef7 --- /dev/null +++ b/crop-recommendation/src/exception.py @@ -0,0 +1,21 @@ +import sys + + +def error_message_detail(error, error_detail: sys): + _, _, exc_tb = error_detail.exc_info() + file_name = exc_tb.tb_frame.f_code.co_filename + error_message = "Error occurred python script name [{0}] line number [{1}] error message [{2}]".format( + file_name, exc_tb.tb_lineno, str(error) + ) + + return error_message + + +class CropException(Exception): + def __init__(self, error_message, error_detail: sys): + self.error_message = error_message_detail( + error_message, error_detail=error_detail + ) + + def __str__(self): + return self.error_message diff --git a/crop-recommendation/src/logger.py b/crop-recommendation/src/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..40a22c3d961db13409364ed60cb681c6bb37d182 --- /dev/null +++ b/crop-recommendation/src/logger.py @@ -0,0 +1,22 @@ +import logging +import os +from datetime import datetime + +# log file name +LOG_FILE_NAME = f"{datetime.now().strftime('%m%d%Y__%H%M%S')}.log" + +# Log directory +LOG_FILE_DIR = os.path.join(os.getcwd(), "logs") + +# create folder if not available +os.makedirs(LOG_FILE_DIR, exist_ok=True) + +# Log file path +LOG_FILE_PATH = os.path.join(LOG_FILE_DIR, LOG_FILE_NAME) + + +logging.basicConfig( + filename=LOG_FILE_PATH, + format="[ %(asctime)s ] %(filename)s - %(lineno)d %(name)s - %(levelname)s - %(message)s", + level=logging.INFO, +) diff --git a/crop-recommendation/src/pipeline/__init__.py b/crop-recommendation/src/pipeline/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crop-recommendation/src/pipeline/training_pipeline.py b/crop-recommendation/src/pipeline/training_pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..1a14e24f784064e95ae4034971cdcb83adea58d1 --- /dev/null +++ b/crop-recommendation/src/pipeline/training_pipeline.py @@ -0,0 +1,95 @@ +from src.logger import logging +from src.exception import CropException +from src.utils import get_collection_as_dataframe +from src.entity import config_entity +from src.entity import artifact_entity +import sys +from src.components.data_ingestion import DataIngestion +from src.components.data_validation import DataValidation +from src.components.data_trasformation import DataTransformation +from src.components.model_trainer import ModelTrainer +from src.components.model_evaluation import ModelEvaluation +from src.components.model_pusher import ModelPusher + + +def start_training_pipeline(): + try: + training_pipeline_config = config_entity.TrainingPipelineConfig() + + # data ingestion + data_ingestion_config = config_entity.DataIngestionConfig( + training_pipeline_config=training_pipeline_config + ) + data_ingestion_config.to_dict() + + data_ingestion = DataIngestion(data_ingestion_config=data_ingestion_config) + data_ingestion_artifact = data_ingestion.initiate_data_ingestion() + + print(f"Data Ingestion complete") + + # data validation + data_validation_config = config_entity.DataValidationConfig( + training_pipeline_config=training_pipeline_config + ) + + data_validation = DataValidation( + data_validation_config=data_validation_config, + data_ingestion_artifact=data_ingestion_artifact, + ) + + data_validation.initiate_data_validation() + print(f"Data Validation Complete") + + # data transformation + data_transformation_config = config_entity.DataTransformationConfig( + training_pipeline_config=training_pipeline_config + ) + + data_transformation = DataTransformation( + data_transformation_config=data_transformation_config, + data_ingestion_artifact=data_ingestion_artifact, + ) + + data_transformation_artifact = ( + data_transformation.initiate_data_transformation() + ) + print(f"Data Transformation Complete") + + # model trainer + model_trainer_config = config_entity.ModelTrainerConfig( + training_pipeline_config=training_pipeline_config + ) + + model_trainer = ModelTrainer( + model_trainer_config=model_trainer_config, + data_transformation_artifact=data_transformation_artifact, + ) + + model_trainer_artifact = model_trainer.initiate_model_trainer() + print(f"Model Training Complete") + + # model evaluation + model_eval_config = config_entity.ModelEvaluationConfig( + training_pipeline_config=training_pipeline_config + ) + model_eval = ModelEvaluation( + model_eval_config=model_eval_config, + data_ingesiton_artifact=data_ingestion_artifact, + data_transformation_artifact=data_transformation_artifact, + model_trainer_artifact=model_trainer_artifact, + ) + model_eval_artifact = model_eval.initiate_model_evaluation() + print(f"Model Evaluation Complete") + + # Model Puhser + model_pusher_config = config_entity.ModelPusherConfig(training_pipeline_config=training_pipeline_config) + + model_pusher = ModelPusher(model_pusher_config=model_pusher_config, + data_transformation_artifact=data_transformation_config, + model_trainer_artifact=model_trainer_artifact) + + model_pusher_artifact = model_pusher.initiate_model_pusher() + print(f"Model Pusher Complete") + + except Exception as e: + print(e) diff --git a/crop-recommendation/src/predictor.py b/crop-recommendation/src/predictor.py new file mode 100644 index 0000000000000000000000000000000000000000..33acc2ebe1fbee3003b841a48850a6dc1dd0c3b0 --- /dev/null +++ b/crop-recommendation/src/predictor.py @@ -0,0 +1,100 @@ +from src.entity.config_entity import TRANSFORMER_OBJECT_FILE_NAME +from src.entity.config_entity import MODEL_FILE_NAME +from src.entity.config_entity import TARGET_ENCODER_OBJECT_FILE_NAME +from src.exception import CropException +from src.logger import logging + +import os +import sys + +from glob import glob +from typing import Optional + + +class ModelResolver: + def __init__( + self, + model_registry: str = "saved_models", + transformer_dir_name="transformer", + target_encoder_dir_name="target_encoder", + model_dir_name="model", + ): + self.model_registry = model_registry + os.makedirs(self.model_registry, exist_ok=True) + + self.transformer_dir_name = transformer_dir_name + self.target_encoder_dir_name = target_encoder_dir_name + self.model_dir_name = model_dir_name + + def get_latest_dir_path(self) -> Optional[str]: + try: + dir_names = os.listdir(self.model_registry) + if len(dir_names) == 0: + return None + dir_names = list(map(int, dir_names)) + latest_dir_name = max(dir_names) + return os.path.join(self.model_registry, f"{latest_dir_name}") + + except Exception as e: + raise CropException(e, sys) + + def get_latest_model_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir is None: + raise Exception(f"Model is not available") + return os.path.join(latest_dir, self.model_dir_name, MODEL_FILE_NAME) + except Exception as e: + raise CropException(e, sys) + + def get_latest_transformer_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir is None: + raise Exception(f"Transformer is not availabel") + return os.path.join(latest_dir, self.transformer_dir_name, TRANSFORMER_OBJECT_FILE_NAME) + except Exception as e: + raise CropException(e, sys) + + def get_latest_target_encoder_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir is None: + raise Exception(f"Target encoder is not available") + + return os.path.join(latest_dir, self.target_encoder_dir_name, TARGET_ENCODER_OBJECT_FILE_NAME) + + except Exception as e: + raise CropException(e, sys) + + + def get_latest_save_dir_path(self): + try: + latest_dir = self.get_latest_dir_path() + if latest_dir == None: + return os.path.join(self.model_registry, f"{0}") + latest_dir_num = int(os.path.basename(self.get_latest_dir_path())) + return os.path.join(self.model_registry, f"{latest_dir_num + 1}") + except Exception as e: + raise CropException(e, sys) + + def get_latest_save_model_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + return os.path.join(latest_dir, self.model_dir_name, MODEL_FILE_NAME) + except Exception as e: + raise CropException(e, sys) + + def get_latest_save_transformer_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + return os.path.join(latest_dir, self.transformer_dir_name, TRANSFORMER_OBJECT_FILE_NAME) + except Exception as e: + raise CropException(e, sys) + + def get_latest_save_target_encoder_path(self): + try: + latest_dir = self.get_latest_save_dir_path() + return os.path.join(latest_dir, self.target_encoder_dir_name, TARGET_ENCODER_OBJECT_FILE_NAME) + except Exception as e: + raise CropException(e, sys) diff --git a/crop-recommendation/src/utils.py b/crop-recommendation/src/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..9fa8bcff675ac272f7e76d37b333c278b478040f --- /dev/null +++ b/crop-recommendation/src/utils.py @@ -0,0 +1,106 @@ +import pandas as pd +from src.logger import logging +from src.exception import CropException +from src.config import mongo_client +import os +import sys +import numpy as np +import yaml +import dill + + +def get_collection_as_dataframe( + database_name: str, collection_name: str +) -> pd.DataFrame: + """ + Description: This function return collection as dataframe + ========================================================= + Params: + database_name: database name + collection_name: collection name + ========================================================= + return Pandas dataframe of a collection + """ + try: + logging.info( + f"Reading data from database: {database_name} and collection: {collection_name}" + ) + df = pd.DataFrame(list(mongo_client[database_name][collection_name].find())) + logging.info(f"{database_name} found in the mongodb") + + if "_id" in df.columns: + logging.info("Dropping column: '_id'") + df = df.drop(columns=["_id"], axis=1) + logging.info(f"Row and columns in df: {df.shape}") + return df + except Exception as e: + raise CropException(e, sys) + + +def seperate_dependant_column(df: pd.DataFrame, exclude_column: list) -> pd.DataFrame: + final_dataframe = df.drop(exclude_column, axis=1) + + return final_dataframe + + +def write_yaml_file(file_path, data: dict): + try: + file_dir = os.path.dirname(file_path) + os.makedirs(file_dir, exist_ok=True) + + with open(file_path, "w") as file_writer: + yaml.dump(data, file_writer) + except Exception as e: + raise CropException(e, sys) + + +def save_object(file_path: str, obj: object) -> None: + try: + logging.info("Entered the save object method of utils") + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, "wb") as file_obj: + dill.dump(obj, file_obj) + logging.info("Exited the save object method of utils") + except Exception as e: + raise CropException(e, sys) + + +def load_object(file_path: str) -> object: + try: + if not os.path.exists(file_path): + raise Exception(f"The file: {file_path} is not exists") + with open(file_path, "rb") as file_obj: + return dill.load(file_obj) + except Exception as e: + raise CropException(e, sys) + + +def save_numpy_array_data(file_path: str, array: np.array): + """ + save numpy array data to file + file_path : str location of the file to save + array: np.array data to save + """ + try: + dir_path = os.path.dirname(file_path) + os.makedirs(dir_path, exist_ok=True) + + with open(file_path, "wb") as file_ojb: + np.save(file_obj, array) + + except Exception as e: + raise CropException(e, sys) + + +def load_numpy_array_data(file_path: str) -> np.array: + """ + load numpy array data from file + file_path: str location of file to load + return: np.array data loaded + """ + try: + with open(file_path, "rb") as file_obj: + return np.load(file_obj, allow_pickle=True) + + except Exception as e: + raise CropException(e, sys) diff --git a/notebook/mongodb_database.ipynb b/notebook/mongodb_database.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d62e7ae85d6c459ee949bf6ab1ad6eadbe2caa5f --- /dev/null +++ b/notebook/mongodb_database.ipynb @@ -0,0 +1,123 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from pymongo import MongoClient\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['crop-data',\n", + " 'diseases-data',\n", + " 'fertilizer-data',\n", + " 'inventory',\n", + " 'mongotest',\n", + " 'my_info',\n", + " 'smartcropguard',\n", + " 'taskdb',\n", + " 'admin',\n", + " 'local']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mongodb_uri = os.getenv(\"MONGO_URL\")\n", + "\n", + "client = MongoClient(mongodb_uri)\n", + "client.list_database_names()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['images.chunks', 'crop-details', 'images.files']\n" + ] + } + ], + "source": [ + "smartcropguard = client[\"crop-data\"]\n", + "\n", + "collection_names = smartcropguard.list_collection_names()\n", + "print(collection_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['images.chunks', 'crop-details', 'images.files']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "crop_data = client['crop-data']\n", + "crop_data_collection_names = crop_data.list_collection_names()\n", + "crop_data_collection_names " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/plant-diseases-classifier/.gitignore b/plant-diseases-classifier/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..75e98342f638a01a70113e96f15951834f590263 --- /dev/null +++ b/plant-diseases-classifier/.gitignore @@ -0,0 +1,134 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +artifacts +demo.ipynb +new_demo.ipynb +logs +data_clean.py diff --git a/plant-diseases-classifier/.vscode/extensions.json b/plant-diseases-classifier/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..e96063a38d5b575f4126c72f738c3ab58315031a --- /dev/null +++ b/plant-diseases-classifier/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-toolsai.jupyter", + "ms-toolsai.jupyter-keymap", + "ms-toolsai.jupyter-renderers", + "formulahendry.code-runner" + ] +} diff --git a/plant-diseases-classifier/.vscode/settings.json b/plant-diseases-classifier/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..7ecf6fd89f583d7735afa16d25efae8c4a8d4c0a --- /dev/null +++ b/plant-diseases-classifier/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "workbench.colorTheme": "Cobalt2", + "workbench.preferredDarkColorTheme": "Default Dark+", + "task.allowAutomaticTasks": "on", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } +} diff --git a/plant-diseases-classifier/.vscode/tasks.json b/plant-diseases-classifier/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..14c026db9918f46c14904820c59b2fbb68c32ce7 --- /dev/null +++ b/plant-diseases-classifier/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Installing extensions and dependencies...", + "type": "shell", + "command": "code-server --install-extension ms-python.python --install-extension formulahendry.code-runner && pip install -r requirements.txt", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "runOptions": { "runOn": "folderOpen" } + } + ] +} diff --git a/plant-diseases-classifier/LICENSE b/plant-diseases-classifier/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2b1cfb0a20157f3e265b2cfecc105ab35d31b1af --- /dev/null +++ b/plant-diseases-classifier/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Rishav Dash + +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/plant-diseases-classifier/README.md b/plant-diseases-classifier/README.md new file mode 100644 index 0000000000000000000000000000000000000000..33f5fd5df6a22a485e0591c91768e6b91aa0cb40 --- /dev/null +++ b/plant-diseases-classifier/README.md @@ -0,0 +1,40 @@ +# Automated Leaf Health Assessment + +### Evaluating leaf health couldn't be easier. Users can upload a leaf image, and the model swiftly determines whether the leaf is in good health or not. + +## Disease Detection: +The model excels at identifying diverse leaf diseases. With training encompassing 38 distinct disease cases, it demonstrates robust detection capabilities across a wide range of plant health issues. + +## Application: +Try the application at huggingface space + +Application [link](https://huggingface.co/spaces/Sadashiv/CropGaurd) + +## Demo: +### Input Page +Image 1 + +### Output Page +Image 1 + +## Dataset: +The project utilizes an image dataset sourced from a Kaggle dataset, [link for dataset](https://www.kaggle.com/datasets/vipoooool/new-plant-diseases-dataset) + +However, to simplify retrieval, the dataset is stored within Hugging Face's platform. [link for dataset](https://huggingface.co/datasets/Sadashiv/Plant-Diseases-Dataset) + +## Technologies Used: +

Languages and Tools:

+

Image 1 + Image 2 + Image 2 + Image 2 + Image 2 + Image 2 + Image 2 + Image 2 +

+ +## Model Architecture: +The Yolov8 model architecture is employed for this project. + +[Link](https://github.com/ultralytics/ultralytics) for official github repository diff --git a/plant-diseases-classifier/custom_model_weights/best.pt b/plant-diseases-classifier/custom_model_weights/best.pt new file mode 100644 index 0000000000000000000000000000000000000000..b87e00feedb932fd470c4b78ff0a7b09c60e4b81 --- /dev/null +++ b/plant-diseases-classifier/custom_model_weights/best.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60e259ede3ad072b0e4b16186e5ba00efc51eef51fbcdf9554f9ffd35d715f02 +size 10346016 diff --git a/plant-diseases-classifier/main.py b/plant-diseases-classifier/main.py new file mode 100644 index 0000000000000000000000000000000000000000..34c23e2e7985dd1154a8e9968efd8645f53838b2 --- /dev/null +++ b/plant-diseases-classifier/main.py @@ -0,0 +1,13 @@ +from src.pipeline.training_pipeline import TrainPipeline +from src.exception import PlantException +import sys +import os + +if __name__ =="__main__": + try: + + train_pipeline = TrainPipeline() + train_pipeline.run_pipeline() + + except Exception as e: + raise PlantException(e, sys) diff --git a/plant-diseases-classifier/requirements.txt b/plant-diseases-classifier/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..7159e2f30c08887091aa5d807a78b87bf5cc3861 --- /dev/null +++ b/plant-diseases-classifier/requirements.txt @@ -0,0 +1,2 @@ +huggingface-hub +ultralytics \ No newline at end of file diff --git a/plant-diseases-classifier/setup.py b/plant-diseases-classifier/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/__init__.py b/plant-diseases-classifier/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/components/__init__.py b/plant-diseases-classifier/src/components/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/components/data_ingestion.py b/plant-diseases-classifier/src/components/data_ingestion.py new file mode 100644 index 0000000000000000000000000000000000000000..92c07a7d3747e73db49150c2292b58524c56a893 --- /dev/null +++ b/plant-diseases-classifier/src/components/data_ingestion.py @@ -0,0 +1,65 @@ +import os +import sys +from six.moves import urllib +import zipfile +from src.exception import PlantException +from src.logger import logging +from src.entity.config_entity import DataIngestionConfig +from src.entity.artifact_entity import DataIngestionArtifact +from huggingface_hub import hf_hub_download +from tqdm import tqdm + + +class DataIngestion: + def __init__( + self, data_ingestion_config: DataIngestionConfig = DataIngestionConfig() + ): + try: + self.data_ingestion_config = data_ingestion_config + + except Exception as e: + raise PlantException(e, sys) + + def download_dataset(self): + # The path to the downloaded file in the cache. + print(f"Commencing the dataset download from the hub...") + logging.info(f"Commencing the dataset download from the hub...") + + filepath = hf_hub_download(repo_id=self.data_ingestion_config.huggingface_repo_id, + filename=self.data_ingestion_config.huggingface_file_name, + repo_type="dataset") + + # Create the destination directory if it doesn't exist. + os.makedirs(self.data_ingestion_config.feature_store_file_path, exist_ok=True) + + # Save the file to the specified location. + destination_path = os.path.join(self.data_ingestion_config.feature_store_file_path, self.data_ingestion_config.huggingface_file_name) + with open(destination_path, "wb") as f_dest, open(filepath, "rb") as f_src: + f_dest.write(f_src.read()) + + return destination_path + + def extract_and_move_zip(self, zip_file_path): + logging.info(f"Zip file extraction has begun.") + destination_dir=self.data_ingestion_config.dataset_location + + # Extract the zip file. + with zipfile.ZipFile(zip_file_path, "r") as zip_file: + zip_file.extractall(destination_dir) + + logging.info(f"Zip file extraction has complete.") + + def initiate_data_ingestion(self) -> DataIngestionArtifact: + logging.info("Entered the initiate_data_ingestion method of the Data_Ingestion class.") + try: + zip_file_path = self.download_dataset() + self.extract_and_move_zip(zip_file_path=zip_file_path) + + data_ingestion_artifact = DataIngestionArtifact(dataset_path=self.data_ingestion_config.dataset_location, + feature_store_path=zip_file_path) + logging.info("Data Ingestion Artifacts Genereated") + + return data_ingestion_artifact + + except Exception as e: + raise PlantException(e, sys) \ No newline at end of file diff --git a/plant-diseases-classifier/src/components/data_validation.py b/plant-diseases-classifier/src/components/data_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..5a7bbb4ded7762c3bfa3108fa400c979e674e749 --- /dev/null +++ b/plant-diseases-classifier/src/components/data_validation.py @@ -0,0 +1,63 @@ +import os +import sys +import shutil +from src.logger import logging +from src.exception import PlantException +from src.entity.config_entity import DataValidationConfig +from src.entity.artifact_entity import DataIngestionArtifact +from src.entity.artifact_entity import DataValidationArtifact + +class DataValidation: + def __init__(self,data_ingestion_artifact: DataIngestionArtifact, + data_validation_config: DataValidationConfig): + + try: + self.data_ingestion_artifact = data_ingestion_artifact + self.data_validation_config = data_validation_config + + except Exception as e: + raise PlantException(e, sys) + + + def validate_all_files_exist(self) -> bool: + try: + + validation_status = None + all_files = os.listdir(self.data_ingestion_artifact.dataset_path) + logging.info(f"File format we got: {all_files}") + + for file in all_files: + if file not in self.data_validation_config.required_file_list: + validation_status = False + os.makedirs(self.data_validation_config.data_validation_dir, exist_ok=True) + + with open(self.data_validation_config.valid_status_file_dir, "w") as f: + f.write(f"Validation status: {validation_status}") + + else: + validation_status = True + os.makedirs(self.data_validation_config.data_validation_dir, exist_ok=True) + with open(self.data_validation_config.valid_status_file_dir, "w") as f: + f.write(f"Validation status: {validation_status}") + + return validation_status + + except Exception as e: + raise PlantException(e, sys) + + def initiate_data_validation(self) -> DataValidationArtifact: + logging.info("Entered initiate_data_validation method of DataValidation class") + try: + status = self.validate_all_files_exist() + data_validation_artifact = DataValidationArtifact(validation_status=status) + + logging.info("Exited initiate_data_validation method of DataValidation class") + logging.info(f"Data validation artifact: {data_validation_artifact}") + + # if status: + # shutil.copy(self.data_ingestion_artifact.data_zip_file_path, os.getcwd()) + + return data_validation_artifact + + except Exception as e: + raise PlantException(e, sys) \ No newline at end of file diff --git a/plant-diseases-classifier/src/components/model_pusher.py b/plant-diseases-classifier/src/components/model_pusher.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/components/model_trainer.py b/plant-diseases-classifier/src/components/model_trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..d015d6173c1607d935a7391f7317254da982de1f --- /dev/null +++ b/plant-diseases-classifier/src/components/model_trainer.py @@ -0,0 +1,46 @@ +import os +import sys +import shutil +from ultralytics import YOLO +from src.logger import logging +from src.exception import PlantException +from src.entity.config_entity import ModelTrainerConfig +from src.entity.artifact_entity import ModelTrainerArtifact +from src.entity.artifact_entity import DataIngestionArtifact + + +class ModelTrainer: + + try: + def __init__(self, model_trainer_config:ModelTrainerConfig, data_ingestion_artifact:DataIngestionArtifact): + self.model_trainer_config = model_trainer_config + self.data_ingestion_artifact = data_ingestion_artifact + + def initiate_model_trainer(self) -> ModelTrainerArtifact: + logging.info(f"Removing and existing runs directory from previous training") + os.system("rm -rf runs") + + model_config_file_name = self.model_trainer_config.weight_name.split('.')[0] + print(model_config_file_name) + + os.system(f"yolo task=classify mode=train model={self.model_trainer_config.weight_name} \ + data={self.data_ingestion_artifact.dataset_path} epochs={self.model_trainer_config.no_epochs} \ + imgsz=128 batch={self.model_trainer_config.batch_size} patience={self.model_trainer_config.patience}") + + os.makedirs("custom_model_weights", exist_ok=True) + os.system("cp runs/classify/train/weights/best.pt custom_model_weights/") + + os.makedirs(self.model_trainer_config.model_trainer_dir, exist_ok=True) + + + os.system(f"cp runs/classify/train/weights/best.pt {self.model_trainer_config.model_trainer_dir}/") + + model_trainer_artifact = ModelTrainerArtifact(trained_model_file_path="custom_model_weights/best.pt",) + + logging.info("Exited initiate_model_trainer method of ModelTrainer class") + logging.info(f"Model trainer artifact: {model_trainer_artifact}") + + return model_trainer_artifact + + except Exception as e: + raise PlantException(e, sys) \ No newline at end of file diff --git a/plant-diseases-classifier/src/constant/__init__.py b/plant-diseases-classifier/src/constant/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/constant/application.py b/plant-diseases-classifier/src/constant/application.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/constant/training_pipeline/__init__.py b/plant-diseases-classifier/src/constant/training_pipeline/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8ed325e2a4798b2e25d6c997210e0e528c5c41b5 --- /dev/null +++ b/plant-diseases-classifier/src/constant/training_pipeline/__init__.py @@ -0,0 +1,39 @@ +import os + +ARTIFACTS_DIR: str = "artifacts" + +""" +Data Ingestion related constant start with DATA_INGESTION VAR NAME +""" +DATA_INGESTION_DIR_NAME: str = "data_ingestion" + +DATA_INGESTION_FEATURE_STORE_DIR: str = "feature_store" + +HUGGINGFACE_REPO_ID: str = "Sadashiv/Plant-Diseases-Dataset" + +FILE_NAME: str = "Plant-Diseases-Dataset.zip" + +DATA_INGESTION_FILES: str = "dataset" + +""" +Data Validation realted contant start with DATA_VALIDATION VAR NAME +""" + +DATA_VALIDATION_DIR_NAME: str = "data_validation" + +DATA_VALIDATION_STATUS_FILE = 'status.txt' + +DATA_VALIDATION_ALL_REQUIRED_FILES = ["val", "train"] + +""" +MODEL TRAINER related constant start with MODEL_TRAINER var name +""" +MODEL_TRAINER_DIR_NAME: str = "model_trainer" + +MODEL_TRAINER_PRETRAINED_WEIGHT_NAME: str = "yolov8s-cls.pt" + +MODEL_TRAINER_NO_EPOCHS: int = 5 + +MODEL_TRAINER_BATCH_SIZE: int = 16 + +MODEL_TRAINER_PATIENCE: int = 0 diff --git a/plant-diseases-classifier/src/entity/__init__.py b/plant-diseases-classifier/src/entity/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/entity/artifact_entity.py b/plant-diseases-classifier/src/entity/artifact_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..75c635429b597ef7fcc0b90e484900953418d256 --- /dev/null +++ b/plant-diseases-classifier/src/entity/artifact_entity.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass + +@dataclass +class DataIngestionArtifact: + dataset_path:str + feature_store_path:str + +@dataclass +class DataValidationArtifact: + validation_status: bool + +@dataclass +class ModelTrainerArtifact: + trained_model_file_path: str \ No newline at end of file diff --git a/plant-diseases-classifier/src/entity/config_entity.py b/plant-diseases-classifier/src/entity/config_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..8e6b3aad44ae36099363b4abdd76acde90201cec --- /dev/null +++ b/plant-diseases-classifier/src/entity/config_entity.py @@ -0,0 +1,58 @@ +import os +from dataclasses import dataclass +from datetime import datetime +from src.constant.training_pipeline import * + +TIMESTAMP: str = datetime.now().strftime("%m_%d_%Y__%I_%M_%S") + + +@dataclass +class TrainingPipelineConfig: + artifacts_dir: str = os.path.join(ARTIFACTS_DIR, TIMESTAMP) + + +training_pipeline_config: TrainingPipelineConfig = TrainingPipelineConfig() + + +@dataclass +class DataIngestionConfig: + data_ingestion_dir: str = os.path.join( + training_pipeline_config.artifacts_dir, DATA_INGESTION_DIR_NAME + ) + + feature_store_file_path: str = os.path.join( + data_ingestion_dir, DATA_INGESTION_FEATURE_STORE_DIR + ) + + dataset_location: str = os.path.join( + data_ingestion_dir, DATA_INGESTION_FILES + ) + + huggingface_repo_id: str = HUGGINGFACE_REPO_ID + huggingface_file_name: str = FILE_NAME + + +@dataclass +class DataValidationConfig: + data_validation_dir: str = os.path.join( + training_pipeline_config.artifacts_dir, DATA_VALIDATION_DIR_NAME + ) + + valid_status_file_dir: str = os.path.join(data_validation_dir, DATA_VALIDATION_STATUS_FILE) + + required_file_list = DATA_VALIDATION_ALL_REQUIRED_FILES + + +@dataclass +class ModelTrainerConfig: + model_trainer_dir: str = os.path.join( + training_pipeline_config.artifacts_dir, MODEL_TRAINER_DIR_NAME + ) + + weight_name = MODEL_TRAINER_PRETRAINED_WEIGHT_NAME + + no_epochs = MODEL_TRAINER_NO_EPOCHS + + batch_size = MODEL_TRAINER_BATCH_SIZE + + patience = MODEL_TRAINER_PATIENCE \ No newline at end of file diff --git a/plant-diseases-classifier/src/exception/__init__.py b/plant-diseases-classifier/src/exception/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..acb6eaacc5d10fe714127acbb9f0cc4b1df85727 --- /dev/null +++ b/plant-diseases-classifier/src/exception/__init__.py @@ -0,0 +1,20 @@ +import sys, os + + +def error_message_detail(error, error_detail: sys): + _, _, exc_tb = error_detail.exc_info() # extract the information about the error + file_name = ( + exc_tb.tb_frame.f_code.co_filename + ) # get the file name whrere the error occured + # formating the error msg + error_message = f"Error occured python script name [{file_name}] line number [{exc_tb.tb_lineno}] error message [{str(error)}]" + return error_message + + +# create a custom Exception class that inherits the from built-in Exception class +class PlantException(Exception): + def __init__(self, error_message, error_detail: sys): + self.error_message = error_message_detail(error_message, error_detail) + + def __str__(self): + return self.error_message \ No newline at end of file diff --git a/plant-diseases-classifier/src/logger/__init__.py b/plant-diseases-classifier/src/logger/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5881df9a827d2be8783820f3d8e2c28f1b131beb --- /dev/null +++ b/plant-diseases-classifier/src/logger/__init__.py @@ -0,0 +1,21 @@ +import logging +from datetime import datetime +import os, sys + +# log file name +LOG_FILE_NAME = f"{datetime.now().strftime('%m_%d_%Y__%I_%M_%S')}.log" + +# directory name +LOG_FILE_DIR = os.path.join(os.getcwd(), "logs") + +# create folder if not exists +os.makedirs(LOG_FILE_DIR, exist_ok=True) + +# Log file path +LOG_FILE_PATH = os.path.join(LOG_FILE_DIR, LOG_FILE_NAME) + +logging.basicConfig( + filename=LOG_FILE_PATH, + format="[ %(asctime)s ] %(filename)s: %(lineno)d %(name)s - %(levelname)s - %(message)s", + level=logging.INFO, +) diff --git a/plant-diseases-classifier/src/pipeline/__init__.py b/plant-diseases-classifier/src/pipeline/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/pipeline/training_pipeline.py b/plant-diseases-classifier/src/pipeline/training_pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..0bfb481053e007b8af649fb24547788bb593d61a --- /dev/null +++ b/plant-diseases-classifier/src/pipeline/training_pipeline.py @@ -0,0 +1,77 @@ +import sys +import os +from src.exception import PlantException +from src.components.data_ingestion import DataIngestion +from src.components.data_validation import DataValidation +from src.entity.config_entity import DataIngestionConfig +from src.entity.config_entity import DataValidationConfig +from src.entity.config_entity import ModelTrainerConfig +from src.entity.artifact_entity import DataIngestionArtifact +from src.entity.artifact_entity import DataValidationArtifact +from src.entity.artifact_entity import ModelTrainerArtifact +from src.components.model_trainer import ModelTrainer +from src.logger import logging + +class TrainPipeline: + + def __init__(self): + self.data_ingestion_config = DataIngestionConfig() + self.data_validation_config = DataValidationConfig() + self.model_trainer_config = ModelTrainerConfig() + + def start_data_ingestion(self) -> DataIngestionArtifact: + try: + logging.info("Entered the start_data_ingestion method of TrainPipeline class") + data_ingestion = DataIngestion(data_ingestion_config=self.data_ingestion_config) + + data_ingestion_artifact = data_ingestion.initiate_data_ingestion() + + logging.info("Exited the start_data_ingestion method of TrainPipeline class") + + return data_ingestion_artifact + + except Exception as e: + raise PlantException(e, sys) + + def start_model_trainer(self) -> ModelTrainerArtifact: + try: + data_ingestion_artifact = self.start_data_ingestion() + model_trainer = ModelTrainer(model_trainer_config=self.model_trainer_config, + data_ingestion_artifact=data_ingestion_artifact) + model_trainer_artifact = model_trainer.initiate_model_trainer() + return model_trainer_artifact + + except Exception as e: + raise PlantException(e, sys) + + def start_data_validation(self, data_ingestion_artifact: DataIngestionArtifact) -> DataValidationArtifact: + logging.info("Entered the start_data_validation method of TrainPipeline class") + try: + + data_validation = DataValidation(data_ingestion_artifact=data_ingestion_artifact, + data_validation_config=self.data_validation_config,) + + data_validation_artifact = data_validation.initiate_data_validation() + + logging.info("Performed the data validation operation") + logging.info("Exited the start_data_validation method of TrainPipeline class") + + return data_validation_artifact + + except Exception as e: + raise PlantException(e, sys) + + + def run_pipeline(self) -> None: + try: + data_ingestion_artifact = self.start_data_ingestion() + data_validation_artifact = self.start_data_validation(data_ingestion_artifact=data_ingestion_artifact) + + if data_validation_artifact.validation_status == True: + model_trainer_artifact = self.start_model_trainer() + + else: + raise Exception("Your data is not in correct format") + + except Exception as e: + raise PlantException(e, sys) diff --git a/plant-diseases-classifier/src/utils/__init__.py b/plant-diseases-classifier/src/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/src/utils/main_utils.py b/plant-diseases-classifier/src/utils/main_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/plant-diseases-classifier/template.py b/plant-diseases-classifier/template.py new file mode 100644 index 0000000000000000000000000000000000000000..c5a33fc2c0af5c3c6241f8d05231e7f4f4bdf72c --- /dev/null +++ b/plant-diseases-classifier/template.py @@ -0,0 +1,49 @@ +import os, sys +from pathlib import Path +import logging + +while True: + project_name = input("Enter your project name: ") + if project_name !="": + break + +# src/__init__.py +# src/compontes/__init__.py +list_of_files = [ + f"{project_name}/__init__.py", + f"{project_name}/components/__init__.py", + f"{project_name}/components/data_ingestion.py", + f"{project_name}/components/data_validation.py", + f"{project_name}/components/model_trainer.py", + f"{project_name}/components/model_pusher.py", + f"{project_name}/constant/__init__.py", + f"{project_name}/constant/application.py", + f"{project_name}/constant/training_pipeline/__init__.py", + f"{project_name}/entity/__init__.py", + f"{project_name}/entity/artifact_entity.py", + f"{project_name}/entity/config_entity.py", + f"{project_name}/exception/__init__.py", + f"{project_name}/logger/__init__.py", + f"{project_name}/pipeline/__init__.py", + f"{project_name}/pipeline/training_pipeline.py", + f"{project_name}/utils/__init__.py", + f"{project_name}/pipeline/main_utils.py", + "app.py", + "main.py", + "setup.py" +] + + +for filepth in list_of_files: + filepath = Path(filepth) + filedir, filename = os.path.split(filepath) + + if filedir !="": + os.makedirs(filedir, exist_ok=True) + + if (not os.path.exists(filepath)) or (os.path.getsize(filepath) == 0): + with open(filepath, "w") as f: + pass + + else: + logging.info("file is already present at : {filepath}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..163e7254b91875973ba08e71da0e994dbb0d729a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +pymongo +flask +requests +numpy +python-dotenv +scikit-learn +dill +ultralytics \ No newline at end of file diff --git a/static/images/crop-recommendation-logo.png b/static/images/crop-recommendation-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0e416bc17bf74e59e1b8b1f6349100e0175d8516 Binary files /dev/null and b/static/images/crop-recommendation-logo.png differ diff --git a/static/images/farmer_01.jpg b/static/images/farmer_01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..841aa88dc3fa2613eb428ccc05a1bf28e8274b2c Binary files /dev/null and b/static/images/farmer_01.jpg differ diff --git a/static/images/farmer_02.jpg b/static/images/farmer_02.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f3053d9e61b0d2bbf57c4ecc3f44143b5758f63e Binary files /dev/null and b/static/images/farmer_02.jpg differ diff --git a/static/images/farmer_03.jpg b/static/images/farmer_03.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2515bee8e305761567f9eeb5d5d6d5bfdf788096 Binary files /dev/null and b/static/images/farmer_03.jpg differ diff --git a/static/images/farmer_04.jpg b/static/images/farmer_04.jpg new file mode 100644 index 0000000000000000000000000000000000000000..527fd372602adf7bd173aff3ffb7fb63ae080226 Binary files /dev/null and b/static/images/farmer_04.jpg differ diff --git a/static/images/farmer_05.jpg b/static/images/farmer_05.jpg new file mode 100644 index 0000000000000000000000000000000000000000..febfd3e1c71b98630937a2bedf88838df735a141 Binary files /dev/null and b/static/images/farmer_05.jpg differ diff --git a/static/images/fertilizer-logo.png b/static/images/fertilizer-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8bcb87fd609bf47f92959b76c0e0f3dc71085dcd Binary files /dev/null and b/static/images/fertilizer-logo.png differ diff --git a/static/images/github-logo.png b/static/images/github-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ec469827a94a513c6d6876f7b8177ee337f98f18 Binary files /dev/null and b/static/images/github-logo.png differ diff --git a/static/images/image-classification-logo.png b/static/images/image-classification-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c303b76ed2c8f08a8e42c06bdcdaf465766f50c0 Binary files /dev/null and b/static/images/image-classification-logo.png differ diff --git a/static/images/india_location.png b/static/images/india_location.png new file mode 100644 index 0000000000000000000000000000000000000000..68273bb3df431d7678657cacfe806ebfff1b91ad Binary files /dev/null and b/static/images/india_location.png differ diff --git a/static/images/linkedin-icon.png b/static/images/linkedin-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6855d8b4dea2d5102c90074e252e92338ebdcc0a Binary files /dev/null and b/static/images/linkedin-icon.png differ diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..025998ffcad6d8ab574b824877b7abc204767e65 Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/images/market-price-logo.png b/static/images/market-price-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..11c9d2b8e20842fafe41ec4fad33485c09876a23 Binary files /dev/null and b/static/images/market-price-logo.png differ diff --git a/static/images/wave1.png b/static/images/wave1.png new file mode 100644 index 0000000000000000000000000000000000000000..54372d71cca6e4e366b8dd6d3b82e7f56d4487ea Binary files /dev/null and b/static/images/wave1.png differ diff --git a/static/images/wave1_mod.png b/static/images/wave1_mod.png new file mode 100644 index 0000000000000000000000000000000000000000..9bded52afb6b1492ac4ed1a92a56216c9dca17b5 Binary files /dev/null and b/static/images/wave1_mod.png differ diff --git a/static/images/wave2.png b/static/images/wave2.png new file mode 100644 index 0000000000000000000000000000000000000000..a92a04087dd542e2a750468eeaef62aa0bee7bb9 Binary files /dev/null and b/static/images/wave2.png differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000000000000000000000000000000000000..5db0f87ee24f7b61ddf88363a7e7e0685051f349 --- /dev/null +++ b/static/style.css @@ -0,0 +1,272 @@ +body { + + padding: 0; + margin: 0; +} + +/*------------navigation bar ----------------*/ +#nav-bar { + position: sticky; + top: 0; + z-index: 10; +} + +.navbar { + background-image: linear-gradient(to right, #75ee71c7, #eceb84); + padding: 0 !important; +} + +.navbar-nav li { + padding: 0 10px; +} + +.navbar-nav li a { + font-weight: 600; +} + + +/*-----------------banner section----------*/ +#banner { + background-image: linear-gradient(to right, #75ee71c7, #eceb84); + padding-top: 5%; +} + +.promo-title { + font-size: 40px; + font-weight: 600; + margin-top: 100px; +} + +.img-fluid { + border-radius: 12px; +} + +.bottom-img { + width: 100%; +} + +/*-----------------Services section----------*/ +#services { + padding: 80px 0; +} + +.service-img { + width: 100px; + margin-top: 20px; +} + +.service-name { + text-align: center; + /*Center horizontally*/ +} + +.services { + padding: 20px; +} + +.title::before { + content: ""; + background: hsl(128, 84%, 66%); + height: 5px; + width: 200px; + margin-left: auto; + margin-right: auto; + display: block; + transform: translateY(63px); +} + +.title::after { + content: ""; + background: hsl(128, 84%, 66%); + height: 10px; + width: 50px; + margin-left: auto; + margin-right: auto; + margin-bottom: 40px; + display: block; + transform: translateY(8px); +} + +/*-----------------Services section----------*/ + +#about-us { + padding-bottom: 50px; + padding-top: 50px; +} + +#about-us ul li { + margin: 10px 0; +} + +/*-----------------quotes-------------------*/ +#quotes { + margin: 100px 0; +} + +.quotes { + border-left: 4px solid #f1f397e3; + margin-top: 50px; + margin-bottom: 50px; +} + +.quotes img { + height: 60px; + width: 60px; + border-radius: 50%; + margin: 0 10px; +} + +.user-details { + display: inline-block; + font-size: 12px; +} + +/*-----------------Social Media Section-------*/ +#social-media { + /* background-color: #D9F8C4; */ + padding: 100px 0; +} + +#social-media p { + font-size: 36px; + font-weight: 600; + margin-bottom: 30px; +} + +.social-icons img { + width: 120px; + transition: 0.5s; + padding: 20px; +} + +.social-icons a:hover img { + transform: translateY(-10px); +} + +/*---------Footer Section -------------------*/ + +#footer { + background-image: linear-gradient(to right, #75ee71c7, #eceb84); + color: rgb(106, 110, 110) +} + +#footer img { + width: 100%; +} + +.footer-box { + padding: 20px; +} + +#footer .footer-box img { + width: 30%; + margin-bottom: 20px; + border-radius: 5%; +} + +#footer .footer-box .addition-info { + display: inline-block; + font-size: 20px; +} + +/*-----------recommendation input -----*/ +.recommendation-input-container { + max-width: 800px; + /* Set the maximum width of the container */ + margin: 60px auto 0; + /* Center the container horizontally and add 60px top margin */ + display: flex; + align-items: center; + background-color: white; + padding: 20px; + border-radius: 15px; + /* Add 15px border-radius to all corners */ +} + +/*-----------image classification -----*/ +.image_input { + width: 50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + padding: 30px; + border-radius: 20px; +} + +/*-----------Market Input -----------*/ +.market_input { + width: 40%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + padding: 15px; + border-radius: 20px; +} + + +/*-------Crop Recommendation Output---*/ +.crop-image { + background-color: white; + padding: 30px; + border-radius: 20px; + } + .crop-image img { + border-radius: 20px; + width: 100%; + } + .crop-image .text h1 { + font-size: 35px; + margin-bottom: 20px; + } + + .crop-image .text p { + font-size: 18px; + } + + .crop-image a{ + margin-top: 20px; + } + .crop-image h2{ + margin-bottom: 30px; + } + + +/*-------Image Classification output page---*/ + .plant-image { + background-color: white; + padding: 30px; + border-radius: 20px; + width: 60%; + } + .plant-image img { + border-radius: 20px; + width: 70%; + margin: 0 auto; + display: block; + } + + .plant-image a{ + margin-top: 20px; + + } + .plant-image h2{ + margin-bottom: 30px; + } + + + /*-------market price output message---*/ + .market_price_message{ + background-color: white; + padding: 20px; + border-radius: 15px; + width: 70%; + margin-top: 5%; +} + +.market_price_message a{ + margin-top: 10px; +} \ No newline at end of file diff --git a/static/uploaded_image/plant_image.JPG b/static/uploaded_image/plant_image.JPG new file mode 100644 index 0000000000000000000000000000000000000000..f3145cac8a3735af7f18fac6e67e563613e026fa Binary files /dev/null and b/static/uploaded_image/plant_image.JPG differ diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000000000000000000000000000000000000..b3493c12db2aa540270c668ba68a1165c319d813 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,17 @@ + + + + + + CropGaurd + + + + + + {% block body %} {% endblock %} + + diff --git a/templates/crop_recommendation_input.html b/templates/crop_recommendation_input.html new file mode 100644 index 0000000000000000000000000000000000000000..0a0e378a56282080c77f991853c7e706848996c0 --- /dev/null +++ b/templates/crop_recommendation_input.html @@ -0,0 +1,96 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+
+

AI-Powered Crop Recommendations

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+{% endblock %} diff --git a/templates/crop_recommendation_output.html b/templates/crop_recommendation_output.html new file mode 100644 index 0000000000000000000000000000000000000000..2cc0654ea221dfa94acae5c0e02c000dbaa5857e --- /dev/null +++ b/templates/crop_recommendation_output.html @@ -0,0 +1,38 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + +
+
+
+

Crop Recommendation

+
+ Image +
+
+

{{input_file_name.capitalize()}}

+

{{crop_details}}

+
+
+ Check with Different Values +
+
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/fertilizer_recommendation_input.html b/templates/fertilizer_recommendation_input.html new file mode 100644 index 0000000000000000000000000000000000000000..354b79e8dd6b74a41a7b4addfd22a8002003aee4 --- /dev/null +++ b/templates/fertilizer_recommendation_input.html @@ -0,0 +1,108 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+
+

AI-Powered Fertilizer Recommendations

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +{% endblock %} diff --git a/templates/fertilizer_recommendation_ouput.html b/templates/fertilizer_recommendation_ouput.html new file mode 100644 index 0000000000000000000000000000000000000000..017298e9669aab9ef422ae334b240777bc54c4f7 --- /dev/null +++ b/templates/fertilizer_recommendation_ouput.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+
+
+

Fertilizer Recommendation

+
+ Image +
+
+

{{label.upper()}}

+

{{fertilizer_details}}

+
+
+ Check with Different Values +
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/image_classification_input.html b/templates/image_classification_input.html new file mode 100644 index 0000000000000000000000000000000000000000..6a6e7f3bce70753cb5430ed8f121a903ed42e842 --- /dev/null +++ b/templates/image_classification_input.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + +
+
+
+

Please upload the image

+
+ +
+
+ + +
+
+
+
+{% endblock %} diff --git a/templates/image_classification_output.html b/templates/image_classification_output.html new file mode 100644 index 0000000000000000000000000000000000000000..8ad4e67b2f17d5ba6197de43ffeae475aab30a7c --- /dev/null +++ b/templates/image_classification_output.html @@ -0,0 +1,38 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+
+
+

Image Classification

+
+ Image +
+
+

{{model_prediction}}

+

{{diseases_details}}

+
+
+ Check with different image +
+
+
+
+{%endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..a170d56f1695dc7558068bb1cbadd4a4274c55f4 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,325 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + + +
+
+

What We Do ?

+
+
+ +

Crop
Recommendation

+
+
+ +

Fertilizer
Recommendation

+
+
+ +

Dieases
Classification

+
+
+ +

Market
Price

+
+
+
+
+ +
+
+

Why We Do It.

+
+
+
    +
  • + The purpose of our website is to assist farmers in making better + decisions for their crops. +
  • +
  • + Every soil has unique fertilizer requirements and suits specific + crops, making personalized recommendations essential. +
  • +
  • + By harnessing the power of technology, we aim to bridge the + gap between agricultural practices and data-driven insights. +
  • +
  • + We aim to provide a comprehensive one-stop solution to cater to all + the needs of farmers. +
  • +
+
+
+ +
+
+
+
+ +
+
+

How We Do It.

+
+
+
    +
  • + Our website integrates cutting-edge machine learning and deep + learning algorithms to facilitate informed decision-making. +
  • +
  • + These algorithms are trained on vast and diverse datasets, ensuring + accurate and reliable recommendations. +
  • +
  • + We leverage the power of data-driven insights to deliver valuable + suggestions to farmers. +
  • +
+
+
+ +
+
+
+
+ +
+
+

What We Do ?

+
+
+
    +
  • + Our website is a farmer-centric platform that offers a range + of services based on user inputs. +
  • +
  • + Crop Recommendation: Using soil analysis and other relevant + factors, we suggest the most suitable crops for a specific plot of + land. +
  • +
  • + Fertilizer Recommendation: We provide tailored fertilizer + suggestions based on the soil's unique needs and crop choice. +
  • +
  • + Plant Health Assessment: Our computer vision technology + analyzes leaf images to determine the plant's health status and + identify potential diseases. +
  • +
  • + Market Price Information: Farmers can access current market + prices for their crops using data fetched from the Indian + government's API. +
  • +
+
+
+ +
+
+
+
+ +
+
+

How It Benefits Farmers ?

+
+
+
    +
  • + Empowering Decision Making: By receiving personalized + recommendations, farmers can make informed choices for their crops + and fertilizers. +
  • +
  • + Crop Yield: Tailored suggestions lead to optimal crop choices + and better fertilizer usage, resulting in increased yields. +
  • +
  • + Early Disease Detection: Our plant health assessment helps + detect diseases at an early stage, allowing farmers to take timely + action and prevent losses. +
  • +
  • + Market Awareness: Access to real-time market prices enables + farmers to sell their produce at the most favorable rates. +
  • +
+
+
+ +
+
+
+
+ +
+
+

Quotes

+
+
+

+ Agriculture is knowledge, it is skill, and it is the instrument of the + economic transformation of the country. +

+ +

Mahatma Gandhi

+
+
+

+ We must harness the power of technology and science to revolutionize + agriculture and improve the lives of farmers. +

+ +

+ A. P. J. Abdul Kalam
The former President of India +

+
+
+
+
+ +
+

Services

+
+
+
+
+ ... +
+
Crop Recommendation
+

+ Harvesting Insights: Transforming Farming with AI-Driven Crop Recommendations +

+ Visit Page +
+
+
+
+
+ ... +
+
Fertilizer Recommendation
+

+ Fertile Future: Enhancing Agriculture with AI-Driven Fertilizer Recommendations +

+ Visit Page +
+
+
+
+
+ ... +
+
Dieases Classification
+

+ Sight to Insight: Advancing Farming through Image-Based Disease Identification +

+ Visit Page +
+
+
+
+
+ ... +
+
Market Price
+

+ Price Insights: Commodity Markets in Indian States with Real-time Government Data +

+ Visit Page +
+
+
+
+
+
+ +
+
+

Connect on Social Media

+ +
+
+ + +{% endblock %} diff --git a/templates/input.html b/templates/input.html new file mode 100644 index 0000000000000000000000000000000000000000..c5f3122c3dbc9adb6151ed56a7df8a0f515823d9 --- /dev/null +++ b/templates/input.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+ +
+

This is input page

+ + +
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/market_price_input.html b/templates/market_price_input.html new file mode 100644 index 0000000000000000000000000000000000000000..feb9d601994bf733909ae01e41c48b102071d68e --- /dev/null +++ b/templates/market_price_input.html @@ -0,0 +1,68 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + +
+
+

Please select the state

+
+ + +
Please select a valid state.
+
+
+ + +
+
+
+{% endblock %} diff --git a/templates/market_price_no_data.html b/templates/market_price_no_data.html new file mode 100644 index 0000000000000000000000000000000000000000..3d08838c76aa2ee9ff393ba51ffeea5b545544ae --- /dev/null +++ b/templates/market_price_no_data.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + +
+
Commodity prices for the selected state are currently unavailable from the Government API. + Please consider trying again later or selecting a different state.
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/market_price_output.html b/templates/market_price_output.html new file mode 100644 index 0000000000000000000000000000000000000000..2710bb4b5ace7239c404262c32ee468d3ebcf307 --- /dev/null +++ b/templates/market_price_output.html @@ -0,0 +1,44 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + +
+

Commodity Prices

+ + + {% for header in data[0] %} + + {% endfor %} + + {% set data_length = data|length %} {% for index_value in range(data_length) + %} + + {% for row in data[index_value].values() %} + + {% endfor %} + + {% endfor %} +
{{header|upper}}
{{row}}
+ + +
+{% endblock %} diff --git a/templates/nav_bar.html b/templates/nav_bar.html new file mode 100644 index 0000000000000000000000000000000000000000..73751579132d7a89fc804f7569f00264c3ec0d37 --- /dev/null +++ b/templates/nav_bar.html @@ -0,0 +1,68 @@ + diff --git a/templates/result_new.html b/templates/result_new.html new file mode 100644 index 0000000000000000000000000000000000000000..5304bf753f9b84100e23f64734b090708b55006f --- /dev/null +++ b/templates/result_new.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} {% block body %} {% include "nav_bar.html" %} + + +
+
+
+
+ Image +
+
+

{{input_file_name.capitalize()}}

+

{{crop_details}}

+
+
+
+
+ +{% endblock %} + + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6036637721055476483301789fb6102297628041 --- /dev/null +++ b/utils.py @@ -0,0 +1,56 @@ +from pymongo import MongoClient +from dotenv import load_dotenv +import gridfs +import pickle +import os + +# Load environment variables from .env file +load_dotenv() + +def load_model_and_encoders(model_path, transformer_path, target_encoder_path): + with open(model_path, 'rb') as f: + model = pickle.load(f) + + with open(transformer_path, 'rb') as f: + pipeline_encoder = pickle.load(f) + + with open(target_encoder_path, 'rb') as f: + label_encoder = pickle.load(f) + + return model, pipeline_encoder, label_encoder + + +def retrieve_image_by_name_from_mongodb(file_name, database_name, collection_name): + # Establish a connection to MongoDB + client = MongoClient(os.getenv("MONGO_URL")) + + # Access the specified database + db = client[database_name] + + # Create a new GridFS object (a specification for storing and retrieving large binary objects) + fs = gridfs.GridFS(db, collection=collection_name) + + # Find the image data using the filename in the metadata + image_data = fs.find_one({"filename": file_name}) + + try: + if image_data is None: + raise ValueError("image_data is None") + + return image_data.read() + except Exception as e: + print(f"An error occurred: {e}") + raise # Re-raise the caught exception + + +def retrieve_data(database_name, collection_name, search_query): + # Connect to MongoDB + client = MongoClient(os.getenv("MONGO_URL")) + database = client[database_name] + collection = database[collection_name] + + # Search for the document based on the provided query + result = collection.find_one(search_query) + + client.close() + return result['data_info']