{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "klg2JF-oBblG" }, "source": [ "# Problem Statement" ] }, { "cell_type": "markdown", "metadata": { "id": "m0CcOjZ-BblL" }, "source": [ "## **Business Context**" ] }, { "cell_type": "markdown", "metadata": { "id": "uyT6Koe7BblM" }, "source": [ "\"Visit with Us,\" a leading travel company, is revolutionizing the tourism industry by leveraging data-driven strategies to optimize operations and customer engagement. While introducing a new package offering, such as the Wellness Tourism Package, the company faces challenges in targeting the right customers efficiently. The manual approach to identifying potential customers is inconsistent, time-consuming, and prone to errors, leading to missed opportunities and suboptimal campaign performance.\n", "\n", "To address these issues, the company aims to implement a scalable and automated system that integrates customer data, predicts potential buyers, and enhances decision-making for marketing strategies. By utilizing an MLOps pipeline, the company seeks to achieve seamless integration of data preprocessing, model development, deployment, and CI/CD practices for continuous improvement. This system will ensure efficient targeting of customers, timely updates to the predictive model, and adaptation to evolving customer behaviors, ultimately driving growth and customer satisfaction.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "zm6bNQOJBblO" }, "source": [ "## **Objective**" ] }, { "cell_type": "markdown", "metadata": { "id": "7PYtjk_YBblO" }, "source": [ "As an MLOps Engineer at \"Visit with Us,\" your responsibility is to design and deploy an MLOps pipeline on GitHub to automate the end-to-end workflow for predicting customer purchases. The primary objective is to build a model that predicts whether a customer will purchase the newly introduced Wellness Tourism Package before contacting them. The pipeline will include data cleaning, preprocessing, transformation, model building, training, evaluation, and deployment, ensuring consistent performance and scalability. By leveraging GitHub Actions for CI/CD integration, the system will enable automated updates, streamline model deployment, and improve operational efficiency. This robust predictive solution will empower policymakers to make data-driven decisions, enhance marketing strategies, and effectively target potential customers, thereby driving customer acquisition and business growth." ] }, { "cell_type": "markdown", "metadata": { "id": "z8C11AzTBblP" }, "source": [ "## **Data Description**" ] }, { "cell_type": "markdown", "metadata": { "id": "9DQx3pkaBblP" }, "source": [ "The dataset contains customer and interaction data that serve as key attributes for predicting the likelihood of purchasing the Wellness Tourism Package. The detailed attributes are:\n", "\n", "**Customer Details**\n", "- **CustomerID:** Unique identifier for each customer.\n", "- **ProdTaken:** Target variable indicating whether the customer has purchased a package (0: No, 1: Yes).\n", "- **Age:** Age of the customer.\n", "- **TypeofContact:** The method by which the customer was contacted (Company Invited or Self Inquiry).\n", "- **CityTier:** The city category based on development, population, and living standards (Tier 1 > Tier 2 > Tier 3).\n", "- **Occupation:** Customer's occupation (e.g., Salaried, Freelancer).\n", "- **Gender:** Gender of the customer (Male, Female).\n", "- **NumberOfPersonVisiting:** Total number of people accompanying the customer on the trip.\n", "- **PreferredPropertyStar:** Preferred hotel rating by the customer.\n", "- **MaritalStatus:** Marital status of the customer (Single, Married, Divorced).\n", "- **NumberOfTrips:** Average number of trips the customer takes annually.\n", "- **Passport:** Whether the customer holds a valid passport (0: No, 1: Yes).\n", "- **OwnCar:** Whether the customer owns a car (0: No, 1: Yes).\n", "- **NumberOfChildrenVisiting:** Number of children below age 5 accompanying the customer.\n", "- **Designation:** Customer's designation in their current organization.\n", "- **MonthlyIncome:** Gross monthly income of the customer.\n", "\n", "**Customer Interaction Data**\n", "- **PitchSatisfactionScore:** Score indicating the customer's satisfaction with the sales pitch.\n", "- **ProductPitched:** The type of product pitched to the customer.\n", "- **NumberOfFollowups:** Total number of follow-ups by the salesperson after the sales pitch.-\n", "- **DurationOfPitch:** Duration of the sales pitch delivered to the customer.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "0LbSu_p2jYfe" }, "source": [ "# Model Building" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "giodc4KknHID" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/content\n" ] } ], "source": [ "# Create a master folder to keep all files created when executing the below code cells\n", "import os\n", "print(os.getcwd())\n", "os.makedirs(\"tourism_project\", exist_ok=True)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "SUKPoy0EA4jj" }, "outputs": [], "source": [ "# Create a folder for storing the model building files\n", "os.makedirs(\"tourism_project/model_building\", exist_ok=True)" ] }, { "cell_type": "markdown", "metadata": { "id": "9DtS3gNDjBbR" }, "source": [ "## Data Registration" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "ZagOeVxJOtJ9" }, "outputs": [], "source": [ "os.makedirs(\"tourism_project/data\", exist_ok=True)" ] }, { "cell_type": "markdown", "metadata": { "id": "WxXiD9ZXxodF" }, "source": [ "Once the **data** folder created after executing the above cell, please upload the **tourism.csv** in to the folder" ] }, { "cell_type": "markdown", "metadata": { "id": "hh2TjRG5WJ4Z" }, "source": [ "## Data Preparation" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "EHVRGAeoOtJ-" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/model_building/data_register.py\n" ] } ], "source": [ "%%writefile tourism_project/model_building/data_register.py\n", "from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError\n", "from huggingface_hub import HfApi, create_repo\n", "import os\n", "\n", "\n", "repo_id = \"ananttripathiak/tourism-dataset\"\n", "repo_type = \"dataset\"\n", "\n", "# Initialize API client\n", "api = HfApi(token=os.getenv(\"HF_TOKEN\"))\n", "\n", "# Step 1: Check if the space exists\n", "try:\n", " api.repo_info(repo_id=repo_id, repo_type=repo_type)\n", " print(f\"Space '{repo_id}' already exists. Using it.\")\n", "except RepositoryNotFoundError:\n", " print(f\"Space '{repo_id}' not found. Creating new space...\")\n", " create_repo(repo_id=repo_id, repo_type=repo_type, private=False)\n", " print(f\"Space '{repo_id}' created.\")\n", "\n", "api.upload_folder(\n", " folder_path=\"tourism_project/data\",\n", " repo_id=repo_id,\n", " repo_type=repo_type,\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "eZZKnLkLjeM4" }, "source": [ "## Model Training and Registration with Experimentation Tracking" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "LFmrcXT_OtJ-" }, "outputs": [], "source": [ "# !pip install mlflow scikit-learn huggingface_hub\n", "# import sys\n", "# !{sys.executable} -m pip install mlflow" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "DatpH_YdOtJ-" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/model_building/train.py\n" ] } ], "source": [ "%%writefile tourism_project/model_building/train.py\n", "\n", "# for data manipulation\n", "import pandas as pd\n", "import numpy as np\n", "# for data preprocessing and pipeline creation\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.compose import make_column_transformer\n", "from sklearn.pipeline import make_pipeline\n", "# for model training, tuning, and evaluation\n", "import xgboost as xgb\n", "from sklearn.model_selection import GridSearchCV\n", "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, classification_report, confusion_matrix\n", "# for model serialization\n", "import joblib\n", "# for creating a folder\n", "import os\n", "# for hugging face space authentication to upload files\n", "from huggingface_hub import login, HfApi, create_repo\n", "from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError\n", "import mlflow\n", "\n", "# Set up MLflow tracking\n", "mlflow.set_tracking_uri(\"http://localhost:5000\")\n", "mlflow.set_experiment(\"tourism-package-prediction\")\n", "\n", "api = HfApi()\n", "\n", "# Load the preprocessed data from Hugging Face\n", "Xtrain_path = \"hf://datasets/ananttripathiak/tourism-dataset/Xtrain.csv\"\n", "Xtest_path = \"hf://datasets/ananttripathiak/tourism-dataset/Xtest.csv\"\n", "ytrain_path = \"hf://datasets/ananttripathiak/tourism-dataset/ytrain.csv\"\n", "ytest_path = \"hf://datasets/ananttripathiak/tourism-dataset/ytest.csv\"\n", "\n", "print(\"Loading preprocessed data...\")\n", "Xtrain = pd.read_csv(Xtrain_path)\n", "Xtest = pd.read_csv(Xtest_path)\n", "ytrain = pd.read_csv(ytrain_path).values.ravel()\n", "ytest = pd.read_csv(ytest_path).values.ravel()\n", "\n", "print(f\"Training set shape: {Xtrain.shape}\")\n", "print(f\"Test set shape: {Xtest.shape}\")\n", "\n", "# Identify numeric features (all features after encoding)\n", "numeric_features = Xtrain.columns.tolist()\n", "\n", "# Preprocessor - StandardScaler for all numeric features\n", "preprocessor = make_column_transformer(\n", " (StandardScaler(), numeric_features)\n", ")\n", "\n", "# Define base XGBoost Classifier\n", "xgb_model = xgb.XGBClassifier(\n", " random_state=42,\n", " n_jobs=-1,\n", " eval_metric='logloss',\n", " use_label_encoder=False\n", ")\n", "\n", "# Hyperparameter grid for classification\n", "param_grid = {\n", " 'xgbclassifier__n_estimators': [100, 200, 300],\n", " 'xgbclassifier__max_depth': [3, 5, 7],\n", " 'xgbclassifier__learning_rate': [0.01, 0.05, 0.1],\n", " 'xgbclassifier__subsample': [0.7, 0.8, 1.0],\n", " 'xgbclassifier__colsample_bytree': [0.7, 0.8, 1.0],\n", " 'xgbclassifier__scale_pos_weight': [1, 2, 3] # Handle class imbalance\n", "}\n", "\n", "# Pipeline\n", "model_pipeline = make_pipeline(preprocessor, xgb_model)\n", "\n", "print(\"\\nStarting MLflow experiment...\")\n", "with mlflow.start_run():\n", " print(\"Performing Grid Search with Cross-Validation...\")\n", " # Grid Search\n", " grid_search = GridSearchCV(\n", " model_pipeline,\n", " param_grid,\n", " cv=3,\n", " n_jobs=-1,\n", " scoring='roc_auc',\n", " verbose=1\n", " )\n", " grid_search.fit(Xtrain, ytrain)\n", "\n", " # Log parameter sets\n", " results = grid_search.cv_results_\n", " print(f\"\\nEvaluated {len(results['params'])} parameter combinations\")\n", "\n", " for i in range(len(results['params'])):\n", " param_set = results['params'][i]\n", " mean_score = results['mean_test_score'][i]\n", "\n", " with mlflow.start_run(nested=True):\n", " mlflow.log_params(param_set)\n", " mlflow.log_metric(\"mean_roc_auc\", mean_score)\n", "\n", " # Best model\n", " print(f\"\\nBest parameters: {grid_search.best_params_}\")\n", " mlflow.log_params(grid_search.best_params_)\n", " best_model = grid_search.best_estimator_\n", "\n", " # Predictions\n", " print(\"\\nMaking predictions...\")\n", " y_pred_train = best_model.predict(Xtrain)\n", " y_pred_test = best_model.predict(Xtest)\n", "\n", " # Probability predictions for ROC-AUC\n", " y_pred_train_proba = best_model.predict_proba(Xtrain)[:, 1]\n", " y_pred_test_proba = best_model.predict_proba(Xtest)[:, 1]\n", "\n", " # Calculate metrics\n", " print(\"\\nCalculating metrics...\")\n", " train_accuracy = accuracy_score(ytrain, y_pred_train)\n", " test_accuracy = accuracy_score(ytest, y_pred_test)\n", "\n", " train_precision = precision_score(ytrain, y_pred_train, zero_division=0)\n", " test_precision = precision_score(ytest, y_pred_test, zero_division=0)\n", "\n", " train_recall = recall_score(ytrain, y_pred_train, zero_division=0)\n", " test_recall = recall_score(ytest, y_pred_test, zero_division=0)\n", "\n", " train_f1 = f1_score(ytrain, y_pred_train, zero_division=0)\n", " test_f1 = f1_score(ytest, y_pred_test, zero_division=0)\n", "\n", " train_roc_auc = roc_auc_score(ytrain, y_pred_train_proba)\n", " test_roc_auc = roc_auc_score(ytest, y_pred_test_proba)\n", "\n", " # Log metrics\n", " mlflow.log_metrics({\n", " \"train_accuracy\": train_accuracy,\n", " \"test_accuracy\": test_accuracy,\n", " \"train_precision\": train_precision,\n", " \"test_precision\": test_precision,\n", " \"train_recall\": train_recall,\n", " \"test_recall\": test_recall,\n", " \"train_f1_score\": train_f1,\n", " \"test_f1_score\": test_f1,\n", " \"train_roc_auc\": train_roc_auc,\n", " \"test_roc_auc\": test_roc_auc\n", " })\n", "\n", " # Print results\n", " print(\"\\n\" + \"=\"*50)\n", " print(\"MODEL PERFORMANCE METRICS\")\n", " print(\"=\"*50)\n", " print(f\"Train Accuracy: {train_accuracy:.4f} | Test Accuracy: {test_accuracy:.4f}\")\n", " print(f\"Train Precision: {train_precision:.4f} | Test Precision: {test_precision:.4f}\")\n", " print(f\"Train Recall: {train_recall:.4f} | Test Recall: {test_recall:.4f}\")\n", " print(f\"Train F1-Score: {train_f1:.4f} | Test F1-Score: {test_f1:.4f}\")\n", " print(f\"Train ROC-AUC: {train_roc_auc:.4f} | Test ROC-AUC: {test_roc_auc:.4f}\")\n", " print(\"=\"*50)\n", "\n", " print(\"\\nTest Set Classification Report:\")\n", " print(classification_report(ytest, y_pred_test, target_names=['No Purchase', 'Purchase']))\n", "\n", " print(\"\\nTest Set Confusion Matrix:\")\n", " print(confusion_matrix(ytest, y_pred_test))\n", "\n", " # Save the model locally\n", " model_path = \"best_tourism_model_v1.joblib\"\n", " joblib.dump(best_model, model_path)\n", " print(f\"\\nModel saved locally as: {model_path}\")\n", "\n", " # Log the model artifact\n", " mlflow.log_artifact(model_path, artifact_path=\"model\")\n", " print(f\"Model logged to MLflow\")\n", "\n", " # Upload to Hugging Face\n", " repo_id = \"ananttripathiak/tourism-prediction-model\"\n", " repo_type = \"model\"\n", "\n", " # Step 1: Check if the repository exists\n", " try:\n", " api.repo_info(repo_id=repo_id, repo_type=repo_type)\n", " print(f\"\\nRepository '{repo_id}' already exists. Using it.\")\n", " except RepositoryNotFoundError:\n", " print(f\"\\nRepository '{repo_id}' not found. Creating new repository...\")\n", " create_repo(repo_id=repo_id, repo_type=repo_type, private=False)\n", " print(f\"Repository '{repo_id}' created.\")\n", "\n", " # Upload model to Hugging Face\n", " api.upload_file(\n", " path_or_fileobj=\"best_tourism_model_v1.joblib\",\n", " path_in_repo=\"best_tourism_model_v1.joblib\",\n", " repo_id=repo_id,\n", " repo_type=repo_type,\n", " )\n", " print(f\"Model uploaded to Hugging Face: {repo_id}\")\n", "\n", "print(\"\\n\" + \"=\"*50)\n", "print(\"MODEL TRAINING COMPLETED SUCCESSFULLY!\")\n", "print(\"=\"*50)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "Vtjn_63uOtJ-" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/model_building/prep.py\n" ] } ], "source": [ "%%writefile tourism_project/model_building/prep.py\n", "# for data manipulation\n", "import pandas as pd\n", "import numpy as np\n", "# for data preprocessing and pipeline creation\n", "from sklearn.model_selection import train_test_split\n", "# for converting text data in to numerical representation\n", "from sklearn.preprocessing import LabelEncoder\n", "# for hugging face space authentication to upload files\n", "from huggingface_hub import login, HfApi\n", "import os\n", "\n", "# Define constants for the dataset and output paths\n", "api = HfApi(token=os.getenv(\"HF_TOKEN\"))\n", "DATASET_PATH = \"hf://datasets/ananttripathiak/tourism-dataset/tourism.csv\"\n", "df = pd.read_csv(DATASET_PATH)\n", "print(\"Dataset loaded successfully.\")\n", "print(f\"Dataset shape: {df.shape}\")\n", "\n", "# Drop the unnamed index column if it exists\n", "if 'Unnamed: 0' in df.columns or df.columns[0] == '':\n", " df = df.iloc[:, 1:]\n", "\n", "# Drop CustomerID as it's a unique identifier (not useful for modeling)\n", "if 'CustomerID' in df.columns:\n", " df.drop(columns=['CustomerID'], inplace=True)\n", "\n", "# Handle missing values\n", "print(\"\\nHandling missing values...\")\n", "# For numerical columns, fill with median\n", "numerical_cols = df.select_dtypes(include=[np.number]).columns\n", "for col in numerical_cols:\n", " if df[col].isnull().sum() > 0:\n", " df[col].fillna(df[col].median(), inplace=True)\n", "\n", "# For categorical columns, fill with mode\n", "categorical_cols = df.select_dtypes(include=['object']).columns\n", "for col in categorical_cols:\n", " if df[col].isnull().sum() > 0:\n", " df[col].fillna(df[col].mode()[0], inplace=True)\n", "\n", "# Handle specific data quality issues (e.g., \"Fe Male\" should be \"Female\")\n", "if 'Gender' in df.columns:\n", " df['Gender'] = df['Gender'].str.strip().replace({'Fe Male': 'Female', 'Fe male': 'Female'})\n", "\n", "# Encode categorical columns\n", "print(\"\\nEncoding categorical variables...\")\n", "label_encoder = LabelEncoder()\n", "\n", "# List of categorical columns to encode\n", "categorical_features = ['TypeofContact', 'Occupation', 'Gender', 'ProductPitched',\n", " 'MaritalStatus', 'Designation']\n", "\n", "for col in categorical_features:\n", " if col in df.columns:\n", " df[col] = label_encoder.fit_transform(df[col].astype(str))\n", "\n", "# Define target variable\n", "target_col = 'ProdTaken'\n", "\n", "# Split into X (features) and y (target)\n", "X = df.drop(columns=[target_col])\n", "y = df[target_col]\n", "\n", "print(f\"\\nFeatures shape: {X.shape}\")\n", "print(f\"Target shape: {y.shape}\")\n", "print(f\"Target distribution:\\n{y.value_counts()}\")\n", "\n", "# Perform train-test split\n", "Xtrain, Xtest, ytrain, ytest = train_test_split(\n", " X, y, test_size=0.2, random_state=42, stratify=y\n", ")\n", "\n", "print(f\"\\nTrain set size: {Xtrain.shape[0]}\")\n", "print(f\"Test set size: {Xtest.shape[0]}\")\n", "\n", "# Save the datasets\n", "Xtrain.to_csv(\"Xtrain.csv\", index=False)\n", "Xtest.to_csv(\"Xtest.csv\", index=False)\n", "ytrain.to_csv(\"ytrain.csv\", index=False)\n", "ytest.to_csv(\"ytest.csv\", index=False)\n", "\n", "print(\"\\nDatasets saved locally.\")\n", "\n", "# Upload to Hugging Face\n", "files = [\"Xtrain.csv\", \"Xtest.csv\", \"ytrain.csv\", \"ytest.csv\"]\n", "\n", "for file_path in files:\n", " api.upload_file(\n", " path_or_fileobj=file_path,\n", " path_in_repo=file_path.split(\"/\")[-1],\n", " repo_id=\"ananttripathiak/tourism-dataset\",\n", " repo_type=\"dataset\",\n", " )\n", " print(f\"Uploaded {file_path} to Hugging Face\")\n", "\n", "print(\"\\nData preparation completed successfully!\")" ] }, { "cell_type": "markdown", "metadata": { "id": "0McYCZzkji5I" }, "source": [ "# Deployment" ] }, { "cell_type": "markdown", "metadata": { "id": "9QrY2v77vbEZ" }, "source": [ "## Dockerfile" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "id": "0-AMAI72CR-T" }, "outputs": [], "source": [ "os.makedirs(\"tourism_project/deployment\", exist_ok=True)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "ZTicTDnPCVZr" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/deployment/Dockerfile\n" ] } ], "source": [ "%%writefile tourism_project/deployment/Dockerfile\n", "# Use a minimal base image with Python 3.9 installed\n", "FROM python:3.9\n", "\n", "# Set the working directory inside the container to /app\n", "WORKDIR /app\n", "\n", "# Copy all files from the current directory on the host to the container's /app directory\n", "COPY . .\n", "\n", "# Install Python dependencies listed in requirements.txt\n", "RUN pip3 install -r requirements.txt\n", "\n", "RUN useradd -m -u 1000 user\n", "USER user\n", "ENV HOME=/home/user \\\n", "\tPATH=/home/user/.local/bin:$PATH\n", "\n", "WORKDIR $HOME/app\n", "\n", "COPY --chown=user . $HOME/app\n", "\n", "# Define the command to run the Streamlit app on port \"8501\" and make it accessible externally\n", "CMD [\"streamlit\", \"run\", \"app.py\", \"--server.port=8501\", \"--server.address=0.0.0.0\", \"--server.enableXsrfProtection=false\"]" ] }, { "cell_type": "markdown", "metadata": { "id": "LCvrklrBwNvJ" }, "source": [ "## Streamlit App" ] }, { "cell_type": "markdown", "metadata": { "id": "fXWe6ObRjP6-" }, "source": [ "Please ensure that the web app script is named `app.py`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "WBG-jxM89jdp" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/deployment/app.py\n" ] } ], "source": [ "%%writefile tourism_project/deployment/app.py\n", "import streamlit as st\n", "import pandas as pd\n", "import numpy as np\n", "from huggingface_hub import hf_hub_download\n", "import joblib\n", "\n", "# Page configuration\n", "st.set_page_config(\n", " page_title=\"Tourism Package Prediction\",\n", " page_icon=\"✈️\",\n", " layout=\"wide\"\n", ")\n", "\n", "# Download and load the trained model\n", "@st.cache_resource\n", "def load_model():\n", " try:\n", " model_path = hf_hub_download(\n", " repo_id=\"ananttripathiak/tourism-prediction-model\",\n", " filename=\"best_tourism_model_v1.joblib\"\n", " )\n", " model = joblib.load(model_path)\n", " return model\n", " except Exception as e:\n", " st.error(f\"Error loading model: {e}\")\n", " return None\n", "\n", "model = load_model()\n", "\n", "# Title and Description\n", "st.title(\"✈️ Wellness Tourism Package Prediction\")\n", "st.markdown(\"\"\"\n", "\n", "\"\"\", unsafe_allow_html=True)\n", "\n", "st.markdown(\"\"\"\n", "This application predicts the likelihood of a customer purchasing the **Wellness Tourism Package**\n", "based on their profile and interaction data. Enter customer details below to get a prediction.\n", "\"\"\")\n", "\n", "# Create two columns for input\n", "col1, col2 = st.columns(2)\n", "\n", "with col1:\n", " st.subheader(\"📋 Customer Demographics\")\n", "\n", " age = st.number_input(\"Age\", min_value=18, max_value=100, value=35, step=1)\n", "\n", " type_of_contact = st.selectbox(\n", " \"Type of Contact\",\n", " options=[\"Self Enquiry\", \"Company Invited\"]\n", " )\n", "\n", " city_tier = st.selectbox(\n", " \"City Tier\",\n", " options=[1, 2, 3],\n", " help=\"Tier 1: Metro cities, Tier 2: Mid-sized cities, Tier 3: Smaller cities\"\n", " )\n", "\n", " occupation = st.selectbox(\n", " \"Occupation\",\n", " options=[\"Salaried\", \"Small Business\", \"Free Lancer\", \"Large Business\"]\n", " )\n", "\n", " gender = st.selectbox(\"Gender\", options=[\"Male\", \"Female\"])\n", "\n", " marital_status = st.selectbox(\n", " \"Marital Status\",\n", " options=[\"Single\", \"Married\", \"Divorced\", \"Unmarried\"]\n", " )\n", "\n", " designation = st.selectbox(\n", " \"Designation\",\n", " options=[\"Executive\", \"Manager\", \"Senior Manager\", \"AVP\", \"VP\"]\n", " )\n", "\n", " monthly_income = st.number_input(\n", " \"Monthly Income (₹)\",\n", " min_value=0.0,\n", " max_value=200000.0,\n", " value=25000.0,\n", " step=1000.0\n", " )\n", "\n", "with col2:\n", " st.subheader(\"🎯 Customer Interaction & Preferences\")\n", "\n", " duration_of_pitch = st.number_input(\n", " \"Duration of Pitch (minutes)\",\n", " min_value=0.0,\n", " max_value=60.0,\n", " value=15.0,\n", " step=0.5\n", " )\n", "\n", " number_of_persons_visiting = st.number_input(\n", " \"Number of Persons Visiting\",\n", " min_value=1,\n", " max_value=10,\n", " value=2,\n", " step=1\n", " )\n", "\n", " number_of_followups = st.number_input(\n", " \"Number of Follow-ups\",\n", " min_value=0.0,\n", " max_value=10.0,\n", " value=3.0,\n", " step=1.0\n", " )\n", "\n", " product_pitched = st.selectbox(\n", " \"Product Pitched\",\n", " options=[\"Basic\", \"Standard\", \"Deluxe\", \"Super Deluxe\", \"King\"]\n", " )\n", "\n", " preferred_property_star = st.selectbox(\n", " \"Preferred Property Star Rating\",\n", " options=[3.0, 4.0, 5.0]\n", " )\n", "\n", " number_of_trips = st.number_input(\n", " \"Number of Trips (per year)\",\n", " min_value=0.0,\n", " max_value=20.0,\n", " value=3.0,\n", " step=1.0\n", " )\n", "\n", " passport = st.selectbox(\"Has Passport?\", options=[\"Yes\", \"No\"])\n", "\n", " pitch_satisfaction_score = st.slider(\n", " \"Pitch Satisfaction Score\",\n", " min_value=1,\n", " max_value=5,\n", " value=3,\n", " step=1\n", " )\n", "\n", " own_car = st.selectbox(\"Owns Car?\", options=[\"Yes\", \"No\"])\n", "\n", " number_of_children_visiting = st.number_input(\n", " \"Number of Children Visiting\",\n", " min_value=0.0,\n", " max_value=5.0,\n", " value=0.0,\n", " step=1.0\n", " )\n", "\n", "# Encoding mapping (based on LabelEncoder used during training)\n", "# Note: These mappings should match the exact encoding used during training\n", "type_of_contact_map = {\"Company Invited\": 0, \"Self Enquiry\": 1}\n", "occupation_map = {\"Free Lancer\": 0, \"Large Business\": 1, \"Salaried\": 2, \"Small Business\": 3}\n", "gender_map = {\"Female\": 0, \"Male\": 1}\n", "product_pitched_map = {\"Basic\": 0, \"Deluxe\": 1, \"King\": 2, \"Standard\": 3, \"Super Deluxe\": 4}\n", "marital_status_map = {\"Divorced\": 0, \"Married\": 1, \"Single\": 2, \"Unmarried\": 3}\n", "designation_map = {\"AVP\": 0, \"Executive\": 1, \"Manager\": 2, \"Senior Manager\": 3, \"VP\": 4}\n", "\n", "# Convert Yes/No to 0/1\n", "passport_val = 1 if passport == \"Yes\" else 0\n", "own_car_val = 1 if own_car == \"Yes\" else 0\n", "\n", "# Prepare input data\n", "input_data = pd.DataFrame([{\n", " 'Age': age,\n", " 'TypeofContact': type_of_contact_map[type_of_contact],\n", " 'CityTier': city_tier,\n", " 'DurationOfPitch': duration_of_pitch,\n", " 'Occupation': occupation_map[occupation],\n", " 'Gender': gender_map[gender],\n", " 'NumberOfPersonVisiting': number_of_persons_visiting,\n", " 'NumberOfFollowups': number_of_followups,\n", " 'ProductPitched': product_pitched_map[product_pitched],\n", " 'PreferredPropertyStar': preferred_property_star,\n", " 'MaritalStatus': marital_status_map[marital_status],\n", " 'NumberOfTrips': number_of_trips,\n", " 'Passport': passport_val,\n", " 'PitchSatisfactionScore': pitch_satisfaction_score,\n", " 'OwnCar': own_car_val,\n", " 'NumberOfChildrenVisiting': number_of_children_visiting,\n", " 'Designation': designation_map[designation],\n", " 'MonthlyIncome': monthly_income\n", "}])\n", "\n", "# Predict button\n", "st.markdown(\"---\")\n", "if st.button(\"🔮 Predict Purchase Likelihood\", use_container_width=True):\n", " if model is not None:\n", " try:\n", " # Make prediction\n", " prediction = model.predict(input_data)[0]\n", " prediction_proba = model.predict_proba(input_data)[0]\n", "\n", " # Display results\n", " st.markdown(\"### 📊 Prediction Results\")\n", "\n", " if prediction == 1:\n", " st.markdown(\n", " f'
'\n", " f'

✅ High Likelihood of Purchase!

'\n", " f'

This customer is likely to purchase the Wellness Tourism Package.

'\n", " f'

Confidence: {prediction_proba[1]*100:.2f}%

'\n", " f'
',\n", " unsafe_allow_html=True\n", " )\n", " st.success(\"💡 **Recommendation:** Prioritize follow-up with this customer!\")\n", " else:\n", " st.markdown(\n", " f'
'\n", " f'

⚠️ Low Likelihood of Purchase

'\n", " f'

This customer is unlikely to purchase the Wellness Tourism Package.

'\n", " f'

Confidence: {prediction_proba[0]*100:.2f}%

'\n", " f'
',\n", " unsafe_allow_html=True\n", " )\n", " st.info(\"💡 **Recommendation:** Consider alternative packages or additional engagement strategies.\")\n", "\n", " # Show probability breakdown\n", " col_prob1, col_prob2 = st.columns(2)\n", " with col_prob1:\n", " st.metric(\"Probability of No Purchase\", f\"{prediction_proba[0]*100:.2f}%\")\n", " with col_prob2:\n", " st.metric(\"Probability of Purchase\", f\"{prediction_proba[1]*100:.2f}%\")\n", "\n", " except Exception as e:\n", " st.error(f\"Error making prediction: {e}\")\n", " else:\n", " st.error(\"Model not loaded. Please check the model repository.\")\n", "\n", "# Footer\n", "st.markdown(\"---\")\n", "st.markdown(\"\"\"\n", "
\n", "

🏢 Visit with Us - Wellness Tourism Package Prediction System

\n", "

Built with ❤️ using Streamlit and XGBoost

\n", "
\n", "\"\"\", unsafe_allow_html=True)" ] }, { "cell_type": "markdown", "metadata": { "id": "07cYzWcIwTL-" }, "source": [ "## Dependency Handling" ] }, { "cell_type": "markdown", "metadata": { "id": "JEgfHL64jU7o" }, "source": [ "Please ensure that the dependency handling file is named `requirements.txt`." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "nvdmy7Wd9lda" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/deployment/requirements.txt\n" ] } ], "source": [ "%%writefile tourism_project/deployment/requirements.txt\n", "pandas==2.2.2\n", "numpy==1.26.4\n", "huggingface_hub==0.32.6\n", "streamlit==1.43.2\n", "joblib==1.5.1\n", "scikit-learn==1.6.0\n", "xgboost==2.1.4" ] }, { "cell_type": "markdown", "metadata": { "id": "V4ynzpKNwWS_" }, "source": [ "# Hosting" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "id": "7p5sBvTg9nCW" }, "outputs": [], "source": [ "os.makedirs(\"tourism_project/hosting\", exist_ok=True)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "QlpgAQoXOtKA" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/hosting/hosting.py\n" ] } ], "source": [ "%%writefile tourism_project/hosting/hosting.py\n", "from huggingface_hub import HfApi\n", "import os\n", "\n", "api = HfApi(token=os.getenv(\"HF_TOKEN\"))\n", "api.upload_folder(\n", " folder_path=\"tourism_project/deployment\",\n", " repo_id=\"ananttripathiak/wellness-tourism-prediction\",\n", " repo_type=\"space\",\n", " path_in_repo=\"\",\n", ")\n", "\n", "print(\"Deployment files successfully uploaded to Hugging Face Space!\")" ] }, { "cell_type": "markdown", "metadata": { "id": "PuCgAW2hktli" }, "source": [ "# MLOps Pipeline with Github Actions Workflow" ] }, { "cell_type": "markdown", "metadata": { "id": "L5BZr5i8PKVN" }, "source": [ "**Note:**\n", "\n", "1. Before running the file below, make sure to add the HF_TOKEN to your GitHub secrets to enable authentication between GitHub and Hugging Face.\n", "2. The below code is for a sample YAML file that can be updated as required to meet the requirements of this project." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "id": "H2kXyghROtKA" }, "outputs": [], "source": [ "os.makedirs(\"tourism_project/.github/workflows\", exist_ok=True)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "M5J4Kq2ROtKA" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/.github/workflows/pipeline.yml\n" ] } ], "source": [ "%%writefile tourism_project/.github/workflows/pipeline.yml\n", "name: Tourism Package Prediction Pipeline\n", "\n", "on:\n", " push:\n", " branches:\n", " - main\n", "\n", "jobs:\n", "\n", " register-dataset:\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: pip install -r tourism_project/requirements.txt\n", " - name: Upload Dataset to Hugging Face Hub\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: python tourism_project/model_building/data_register.py\n", "\n", " data-prep:\n", " needs: register-dataset\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: pip install -r tourism_project/requirements.txt\n", " - name: Run Data Preparation\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: python tourism_project/model_building/prep.py\n", "\n", " model-traning:\n", " needs: data-prep\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: pip install -r tourism_project/requirements.txt\n", " - name: Start MLflow Server\n", " run: |\n", " nohup mlflow ui --host 0.0.0.0 --port 5000 &\n", " sleep 5\n", " - name: Model Building\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: python tourism_project/model_building/train.py\n", "\n", " deploy-hosting:\n", " runs-on: ubuntu-latest\n", " needs: [model-traning,data-prep,register-dataset]\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: pip install -r tourism_project/requirements.txt\n", " - name: Push files to Frontend Hugging Face Space\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: python tourism_project/hosting/hosting.py\n" ] }, { "cell_type": "markdown", "metadata": { "id": "J029tYPq4Rmq" }, "source": [ "```\n", "name: Tourism Project Pipeline\n", "\n", "on:\n", " push:\n", " branches:\n", " - main # Automatically triggers on push to the main branch\n", "\n", "jobs:\n", "\n", " register-dataset:\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: \n", " - name: Upload Dataset to Hugging Face Hub\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: \n", "\n", " data-prep:\n", " needs: register-dataset\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: \n", " - name: Run Data Preparation\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: \n", "\n", "\n", " model-traning:\n", " needs: data-prep\n", " runs-on: ubuntu-latest\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: \n", " - name: Start MLflow Server\n", " run: |\n", " nohup mlflow ui --host 0.0.0.0 --port 5000 & # Run MLflow UI in the background\n", " sleep 5 # Wait for a moment to let the server starts\n", " - name: Model Building\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: \n", "\n", "\n", " deploy-hosting:\n", " runs-on: ubuntu-latest\n", " needs: [model-traning,data-prep,register-dataset]\n", " steps:\n", " - uses: actions/checkout@v3\n", " - name: Install Dependencies\n", " run: \n", " - name: Push files to Frontend Hugging Face Space\n", " env:\n", " HF_TOKEN: ${{ secrets.HF_TOKEN }}\n", " run: \n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "T9fgZ_Mq3zzp" }, "source": [ "**Note:** To use this YAML file for our use case, we need to\n", "\n", "1. Go to the GitHub repository for the project\n", "2. Create a folder named ***.github/workflows/***\n", "3. In the above folder, create a file named ***pipeline.yml***\n", "4. Copy and paste the above content for the YAML file into the ***pipeline.yml*** file" ] }, { "cell_type": "markdown", "metadata": { "id": "PvEUJ-t5kdxH" }, "source": [ "## Requirements file for the Github Actions Workflow" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "nfqWcLRm-dga" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing tourism_project/requirements.txt\n" ] } ], "source": [ "%%writefile tourism_project/requirements.txt\n", "huggingface_hub==0.32.6\n", "datasets==3.6.0\n", "pandas==2.2.2\n", "numpy==1.26.4\n", "scikit-learn==1.6.0\n", "xgboost==2.1.4\n", "mlflow==3.0.1\n", "joblib==1.5.1" ] }, { "cell_type": "markdown", "metadata": { "id": "BA6mP-Ebkm3O" }, "source": [ "## Github Authentication and Push Files" ] }, { "cell_type": "markdown", "metadata": { "id": "T84Ei-g9Z2uw" }, "source": [ "* Before moving forward, we need to generate a secret token to push files directly from Colab to the GitHub repository.\n", "* Please follow the below instructions to create the GitHub token:\n", " - Open your GitHub profile.\n", " - Click on ***Settings***.\n", " - Go to ***Developer Settings***.\n", " - Expand the ***Personal access tokens*** section and select ***Tokens (classic)***.\n", " - Click ***Generate new token***, then choose ***Generate new token (classic)***.\n", " - Add a note and select all required scopes.\n", " - Click ***Generate token***.\n", " - Copy the generated token and store it safely in a notepad." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "KPDx4gqGh7cO" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reading package lists... Done\n", "Building dependency tree... Done\n", "Reading state information... Done\n", "git is already the newest version (1:2.34.1-1ubuntu1.15).\n", "0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.\n", "Cloning into 'Tourism_Project'...\n", "remote: Enumerating objects: 99, done.\u001b[K\n", "remote: Counting objects: 100% (99/99), done.\u001b[K\n", "remote: Compressing objects: 100% (89/89), done.\u001b[K\n", "remote: Total 99 (delta 31), reused 0 (delta 0), pack-reused 0 (from 0)\u001b[K\n", "Receiving objects: 100% (99/99), 111.21 KiB | 4.28 MiB/s, done.\n", "Resolving deltas: 100% (31/31), done.\n", "mv: cannot move '/content/tourism_project/' to '/content/Tourism_Project/tourism_project': Directory not empty\n" ] } ], "source": [ "# Install Git\n", "!apt-get install git\n", "\n", "# Set your Git identity (replace with your details)\n", "!git config --global user.email \"ananttripathi1996@gmail.com\"\n", "!git config --global user.name \"ananttripathi\"\n", "\n", "# Clone your GitHub repository\n", "!git clone https://github.com/ananttripathi/Tourism_Project.git\n", "\n", "# Move your folder to the repository directory\n", "!mv /content/tourism_project/ /content/Tourism_Project" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://colab.research.google.com/" }, "id": "IuUahCwVigon" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/content/Tourism_Project\n", "On branch main\n", "Your branch is up to date with 'origin/main'.\n", "\n", "nothing to commit, working tree clean\n", "Everything up-to-date\n" ] } ], "source": [ "# Change directory to the cloned repository\n", "%cd Tourism_Project/\n", "\n", "# Add the new folder to Git\n", "!git add .\n", "\n", "# Commit the changes\n", "!git commit -m \"first commit\"\n", "\n", "# Push to GitHub (you'll need your GitHub credentials; use a personal access token if 2FA enabled)\n", "!git push https://ananttripathi:github_pat_11SPxp30Fxq@github.com/ananttripathi/Tourism_Project.git" ] }, { "cell_type": "markdown", "metadata": { "id": "v-i8Jdyz-_L1" }, "source": [ "# Output Evaluation" ] }, { "cell_type": "markdown", "metadata": { "id": "FTK8Bpda_UHg" }, "source": [ "- GitHub (link to repository, screenshot of folder structure and executed workflow)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "id": "6qzzesaG_Xw8" }, "outputs": [], "source": [ "# TODO: Add Screenshot After Deployment\n", "#\n", "# After pushing to GitHub and running the workflow, add a screenshot showing:\n", "# 1. GitHub repository structure (showing tourism_project folder and all subfolders)\n", "# 2. GitHub Actions workflow execution (all 4 jobs completed successfully with green checkmarks)\n", "#\n", "# Screenshot should show:\n", "# ✅ register-dataset job completed\n", "# ✅ data-prep job completed\n", "# ✅ model-training job completed\n", "# ✅ deploy-hosting job completed\n", "#\n", "# You can also provide the GitHub repository URL here" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "id": "qXS_wjMdOtKM" }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "id": "IidBPl95OtKM" }, "source": [ "*[Screenshot: GitHub repository structure and folder layout]*" ] }, { "cell_type": "markdown", "metadata": { "id": "P8QouFeUOtKM" }, "source": [ "*[Screenshot: GitHub Actions workflow execution]*" ] }, { "cell_type": "markdown", "metadata": { "id": "3KDN31V2_YSr" }, "source": [ "- Streamlit on Hugging Face (link to HF space, screenshot of Streamlit app)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "id": "NuIUdj3b_ZYV" }, "outputs": [], "source": [ "# TODO: Add Screenshot After Deployment\n", "#\n", "# After the workflow completes and app is deployed, add a screenshot showing:\n", "# 1. Live Streamlit app running on Hugging Face Spaces\n", "# 2. Making a sample prediction with the interface\n", "# 3. Prediction results displayed with confidence scores\n", "#\n", "# Also provide the Hugging Face Space URL:\n", "# Expected format: https://huggingface.co/spaces//wellness-tourism-prediction\n", "#\n", "# Screenshot should show:\n", "# ✅ App interface with input forms\n", "# ✅ Prediction button\n", "# ✅ Results with confidence scores\n", "# ✅ Recommendations" ] }, { "cell_type": "markdown", "metadata": { "id": "tp06I8AnOtKM" }, "source": [ "*[Screenshot: Streamlit app on Hugging Face Spaces]*" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "id": "mK_xcFxlOtKM" }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "id": "zWAag1sAOtKM" }, "source": [ "*[Screenshot: Prediction results with confidence scores]*" ] }, { "cell_type": "markdown", "metadata": { "id": "fN8j9-3nW8G9" }, "source": [ "Power Ahead!\n", "___" ] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:anaconda3] *", "language": "python", "name": "conda-env-anaconda3-py" }, "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.11.4" } }, "nbformat": 4, "nbformat_minor": 0 }