Spaces:
Running
Running
added regression models
Browse files- app.py +8 -0
- ml/ML_DecisionTreeRegressor_BikeRentalPredictor.ipynb +260 -0
- ml/ML_GradientBoosting_HousePricePredictor.ipynb +267 -0
- ml/ML_LassoRegression_SalesPredictor.ipynb +264 -0
- ml/ML_PolynomialRegression_CarPricePredictor.ipynb +279 -0
- routes/ML_DecisionTreeRegressor_BikeRentalPredictor.py +64 -0
- routes/ML_GradientBoosting_HousePricePredictor.py +69 -0
- routes/ML_LassoRegression_SalesPredictor.py +67 -0
- routes/ML_PolynomialRegression_CarPricePredictor.py +67 -0
app.py
CHANGED
|
@@ -16,6 +16,10 @@ from routes.ML_LogisticRegression_ChurnPredictor import router as logistic_regre
|
|
| 16 |
from routes.ML_DecisionTree_IrisClassifier import router as iris_router
|
| 17 |
from routes.ML_RandomForest_TaxiPredictor import router as random_forest_router
|
| 18 |
from routes.ML_RidgeRegression_HousePricePredictor import router as ridge_regression_router
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
from routes.DL_RNN_StockPricePredictor import router as rnn_stock_router
|
| 21 |
from routes.DL_CNN_NumberRecognition import router as cnn_router
|
|
@@ -52,6 +56,10 @@ app.include_router(linear_regression_router, dependencies=_auth)
|
|
| 52 |
app.include_router(logistic_regression_router, dependencies=_auth)
|
| 53 |
app.include_router(random_forest_router, dependencies=_auth)
|
| 54 |
app.include_router(ridge_regression_router, dependencies=_auth)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
app.include_router(cnn_router, dependencies=_auth)
|
| 56 |
app.include_router(iris_router, dependencies=_auth)
|
| 57 |
app.include_router(nlp_collaborative_filter_router, dependencies=_auth)
|
|
|
|
| 16 |
from routes.ML_DecisionTree_IrisClassifier import router as iris_router
|
| 17 |
from routes.ML_RandomForest_TaxiPredictor import router as random_forest_router
|
| 18 |
from routes.ML_RidgeRegression_HousePricePredictor import router as ridge_regression_router
|
| 19 |
+
from routes.ML_PolynomialRegression_CarPricePredictor import router as polynomial_regression_router
|
| 20 |
+
from routes.ML_LassoRegression_SalesPredictor import router as lasso_regression_router
|
| 21 |
+
from routes.ML_DecisionTreeRegressor_BikeRentalPredictor import router as decision_tree_regressor_router
|
| 22 |
+
from routes.ML_GradientBoosting_HousePricePredictor import router as gradient_boosting_router
|
| 23 |
|
| 24 |
from routes.DL_RNN_StockPricePredictor import router as rnn_stock_router
|
| 25 |
from routes.DL_CNN_NumberRecognition import router as cnn_router
|
|
|
|
| 56 |
app.include_router(logistic_regression_router, dependencies=_auth)
|
| 57 |
app.include_router(random_forest_router, dependencies=_auth)
|
| 58 |
app.include_router(ridge_regression_router, dependencies=_auth)
|
| 59 |
+
app.include_router(polynomial_regression_router, dependencies=_auth)
|
| 60 |
+
app.include_router(lasso_regression_router, dependencies=_auth)
|
| 61 |
+
app.include_router(decision_tree_regressor_router, dependencies=_auth)
|
| 62 |
+
app.include_router(gradient_boosting_router, dependencies=_auth)
|
| 63 |
app.include_router(cnn_router, dependencies=_auth)
|
| 64 |
app.include_router(iris_router, dependencies=_auth)
|
| 65 |
app.include_router(nlp_collaborative_filter_router, dependencies=_auth)
|
ml/ML_DecisionTreeRegressor_BikeRentalPredictor.ipynb
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stdout",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
" temperature_c humidity_pct hour_of_day rentals\n",
|
| 13 |
+
"0 17.4 92 1 71\n",
|
| 14 |
+
"1 36.4 46 20 1\n",
|
| 15 |
+
"2 29.2 62 8 111\n",
|
| 16 |
+
"3 24.8 58 8 166\n",
|
| 17 |
+
"4 10.1 42 7 58\n",
|
| 18 |
+
"\n",
|
| 19 |
+
"Rentals range: 1 – 207\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"R² score : 0.8386\n",
|
| 22 |
+
"RMSE : 19.9 rentals\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"Sample (22°C, 55% humidity, 8am): 132 rentals\n"
|
| 25 |
+
]
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
"source": [
|
| 29 |
+
"import numpy as np\n",
|
| 30 |
+
"import pandas as pd\n",
|
| 31 |
+
"from sklearn.tree import DecisionTreeRegressor\n",
|
| 32 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 33 |
+
"from sklearn.metrics import mean_squared_error, r2_score\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"np.random.seed(42)\n",
|
| 36 |
+
"n = 500\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"temperature_c = np.round(np.random.uniform(5, 38, n), 1)\n",
|
| 39 |
+
"humidity_pct = np.random.randint(30, 95, n)\n",
|
| 40 |
+
"hour_of_day = np.random.randint(0, 24, n)\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"# Peak rentals at comfortable temp (~22C) and rush hours (8, 17-19)\n",
|
| 43 |
+
"temp_comfort = -0.8 * (temperature_c - 22) ** 2 + 100\n",
|
| 44 |
+
"hour_bonus = np.where((hour_of_day >= 7) & (hour_of_day <= 9), 80,\n",
|
| 45 |
+
" np.where((hour_of_day >= 16) & (hour_of_day <= 19), 100, 0))\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"rentals = (\n",
|
| 48 |
+
" temp_comfort\n",
|
| 49 |
+
" - 0.4 * humidity_pct\n",
|
| 50 |
+
" + hour_bonus\n",
|
| 51 |
+
" + np.random.normal(0, 15, n)\n",
|
| 52 |
+
").round().astype(int)\n",
|
| 53 |
+
"rentals = np.clip(rentals, 1, None)\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"df = pd.DataFrame({\n",
|
| 56 |
+
" 'temperature_c': temperature_c,\n",
|
| 57 |
+
" 'humidity_pct': humidity_pct,\n",
|
| 58 |
+
" 'hour_of_day': hour_of_day,\n",
|
| 59 |
+
" 'rentals': rentals\n",
|
| 60 |
+
"})\n",
|
| 61 |
+
"\n",
|
| 62 |
+
"print(df.head())\n",
|
| 63 |
+
"print(f'\\nRentals range: {df.rentals.min()} \\u2013 {df.rentals.max()}')\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"X = df[['temperature_c', 'humidity_pct', 'hour_of_day']]\n",
|
| 66 |
+
"y = df['rentals']\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"model = DecisionTreeRegressor(max_depth=6, random_state=42)\n",
|
| 71 |
+
"model.fit(X_train, y_train)\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"y_pred = model.predict(X_test)\n",
|
| 74 |
+
"r2 = r2_score(y_test, y_pred)\n",
|
| 75 |
+
"rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"print(f'\\nR\\u00b2 score : {r2:.4f}')\n",
|
| 78 |
+
"print(f'RMSE : {rmse:.1f} rentals')\n",
|
| 79 |
+
"\n",
|
| 80 |
+
"sample = pd.DataFrame([[22.0, 55, 8]], columns=['temperature_c', 'humidity_pct', 'hour_of_day'])\n",
|
| 81 |
+
"pred = model.predict(sample)[0]\n",
|
| 82 |
+
"print(f'\\nSample (22\\u00b0C, 55% humidity, 8am): {int(round(pred))} rentals')"
|
| 83 |
+
]
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "code",
|
| 87 |
+
"execution_count": 2,
|
| 88 |
+
"metadata": {},
|
| 89 |
+
"outputs": [
|
| 90 |
+
{
|
| 91 |
+
"name": "stderr",
|
| 92 |
+
"output_type": "stream",
|
| 93 |
+
"text": [
|
| 94 |
+
"/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:122: UserWarning: \n",
|
| 95 |
+
"Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n",
|
| 96 |
+
"You are not authenticated with the Hugging Face Hub in this notebook.\n",
|
| 97 |
+
"If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n",
|
| 98 |
+
" warnings.warn(\n"
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
],
|
| 102 |
+
"source": [
|
| 103 |
+
"from huggingface_hub import notebook_login\n",
|
| 104 |
+
"\n",
|
| 105 |
+
"notebook_login()"
|
| 106 |
+
]
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"cell_type": "code",
|
| 110 |
+
"execution_count": 3,
|
| 111 |
+
"metadata": {},
|
| 112 |
+
"outputs": [
|
| 113 |
+
{
|
| 114 |
+
"name": "stdout",
|
| 115 |
+
"output_type": "stream",
|
| 116 |
+
"text": [
|
| 117 |
+
"Model saved locally: /content/ML_DecisionTreeRegressor_BikeRentalPredictor.joblib\n"
|
| 118 |
+
]
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"data": {
|
| 122 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 123 |
+
"model_id": "56fe82ebb24640ada519e1ecd5333d24",
|
| 124 |
+
"version_major": 2,
|
| 125 |
+
"version_minor": 0
|
| 126 |
+
},
|
| 127 |
+
"text/plain": [
|
| 128 |
+
"Processing Files (0 / 0) : | | 0.00B / 0.00B "
|
| 129 |
+
]
|
| 130 |
+
},
|
| 131 |
+
"metadata": {},
|
| 132 |
+
"output_type": "display_data"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"data": {
|
| 136 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 137 |
+
"model_id": "9c752ac1bf664927a49955a85f15b7a9",
|
| 138 |
+
"version_major": 2,
|
| 139 |
+
"version_minor": 0
|
| 140 |
+
},
|
| 141 |
+
"text/plain": [
|
| 142 |
+
"New Data Upload : | | 0.00B / 0.00B "
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
"metadata": {},
|
| 146 |
+
"output_type": "display_data"
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"data": {
|
| 150 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 151 |
+
"model_id": "b9b16a31aac545e68185f055730913d2",
|
| 152 |
+
"version_major": 2,
|
| 153 |
+
"version_minor": 0
|
| 154 |
+
},
|
| 155 |
+
"text/plain": [
|
| 156 |
+
" ...ikeRentalPredictor.joblib: 100%|##########| 7.87kB / 7.87kB "
|
| 157 |
+
]
|
| 158 |
+
},
|
| 159 |
+
"metadata": {},
|
| 160 |
+
"output_type": "display_data"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"name": "stdout",
|
| 164 |
+
"output_type": "stream",
|
| 165 |
+
"text": [
|
| 166 |
+
"Uploaded model to https://huggingface.co/looh2/model\n"
|
| 167 |
+
]
|
| 168 |
+
}
|
| 169 |
+
],
|
| 170 |
+
"source": [
|
| 171 |
+
"import joblib\n",
|
| 172 |
+
"from pathlib import Path\n",
|
| 173 |
+
"from huggingface_hub import HfApi\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"repo_id = \"looh2/model\"\n",
|
| 176 |
+
"model_path = Path(\"ML_DecisionTreeRegressor_BikeRentalPredictor.joblib\")\n",
|
| 177 |
+
"\n",
|
| 178 |
+
"joblib.dump(model, model_path)\n",
|
| 179 |
+
"print(f\"Model saved locally: {model_path.resolve()}\")\n",
|
| 180 |
+
"\n",
|
| 181 |
+
"api = HfApi()\n",
|
| 182 |
+
"api.create_repo(repo_id=repo_id, repo_type=\"model\", exist_ok=True)\n",
|
| 183 |
+
"api.upload_file(\n",
|
| 184 |
+
" path_or_fileobj=str(model_path),\n",
|
| 185 |
+
" path_in_repo=model_path.name,\n",
|
| 186 |
+
" repo_id=repo_id,\n",
|
| 187 |
+
" repo_type=\"model\",\n",
|
| 188 |
+
")\n",
|
| 189 |
+
"print(f\"Uploaded model to https://huggingface.co/{repo_id}\")"
|
| 190 |
+
]
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"cell_type": "code",
|
| 194 |
+
"execution_count": 4,
|
| 195 |
+
"metadata": {},
|
| 196 |
+
"outputs": [
|
| 197 |
+
{
|
| 198 |
+
"data": {
|
| 199 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 200 |
+
"model_id": "1d98edb556df4ccdb76838f4a23713df",
|
| 201 |
+
"version_major": 2,
|
| 202 |
+
"version_minor": 0
|
| 203 |
+
},
|
| 204 |
+
"text/plain": [
|
| 205 |
+
"ML_DecisionTreeRegressor_BikeRentalPredi(…): 0%| | 0.00/7.87k [00:00<?, ?B/s]"
|
| 206 |
+
]
|
| 207 |
+
},
|
| 208 |
+
"metadata": {},
|
| 209 |
+
"output_type": "display_data"
|
| 210 |
+
}
|
| 211 |
+
],
|
| 212 |
+
"source": [
|
| 213 |
+
"# FastAPI integration reference\n",
|
| 214 |
+
"from fastapi import FastAPI\n",
|
| 215 |
+
"from pydantic import BaseModel\n",
|
| 216 |
+
"import joblib, pandas as pd\n",
|
| 217 |
+
"from huggingface_hub import hf_hub_download\n",
|
| 218 |
+
"\n",
|
| 219 |
+
"model_path = hf_hub_download(repo_id='looh2/model',\n",
|
| 220 |
+
" filename='ML_DecisionTreeRegressor_BikeRentalPredictor.joblib',\n",
|
| 221 |
+
" repo_type='model')\n",
|
| 222 |
+
"model = joblib.load(model_path)\n",
|
| 223 |
+
"app = FastAPI()\n",
|
| 224 |
+
"\n",
|
| 225 |
+
"class BikeFeatures(BaseModel):\n",
|
| 226 |
+
" temperature_c: float\n",
|
| 227 |
+
" humidity_pct: int\n",
|
| 228 |
+
" hour_of_day: int\n",
|
| 229 |
+
"\n",
|
| 230 |
+
"@app.post('/predict')\n",
|
| 231 |
+
"def predict_rentals(features: BikeFeatures):\n",
|
| 232 |
+
" df = pd.DataFrame([[features.temperature_c, features.humidity_pct, features.hour_of_day]],\n",
|
| 233 |
+
" columns=['temperature_c', 'humidity_pct', 'hour_of_day'])\n",
|
| 234 |
+
" pred = max(int(round(float(model.predict(df)[0]))), 0)\n",
|
| 235 |
+
" return {'predicted_rentals': pred}"
|
| 236 |
+
]
|
| 237 |
+
}
|
| 238 |
+
],
|
| 239 |
+
"metadata": {
|
| 240 |
+
"kernelspec": {
|
| 241 |
+
"display_name": "Python 3 (ipykernel)",
|
| 242 |
+
"language": "python",
|
| 243 |
+
"name": "python3"
|
| 244 |
+
},
|
| 245 |
+
"language_info": {
|
| 246 |
+
"codemirror_mode": {
|
| 247 |
+
"name": "ipython",
|
| 248 |
+
"version": 3
|
| 249 |
+
},
|
| 250 |
+
"file_extension": ".py",
|
| 251 |
+
"mimetype": "text/x-python",
|
| 252 |
+
"name": "python",
|
| 253 |
+
"nbconvert_exporter": "python",
|
| 254 |
+
"pygments_lexer": "ipython3",
|
| 255 |
+
"version": "3.12.13"
|
| 256 |
+
}
|
| 257 |
+
},
|
| 258 |
+
"nbformat": 4,
|
| 259 |
+
"nbformat_minor": 5
|
| 260 |
+
}
|
ml/ML_GradientBoosting_HousePricePredictor.ipynb
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stdout",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
" lot_size_sqm floors rooms crime_rate school_rating price_thousands\n",
|
| 13 |
+
"0 182 1 7 5.9 7 292.46\n",
|
| 14 |
+
"1 515 3 6 7.8 2 334.81\n",
|
| 15 |
+
"2 350 2 7 9.2 6 322.05\n",
|
| 16 |
+
"3 186 3 7 9.0 7 377.70\n",
|
| 17 |
+
"4 151 3 8 1.8 10 515.72\n",
|
| 18 |
+
"\n",
|
| 19 |
+
"Price range: $72.2k – $618.6k\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"R² score : 0.8868\n",
|
| 22 |
+
"RMSE : $35.93k\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"Sample (200m² lot, 2 floors, 5 rooms, crime=3, school=8): $370.7k\n"
|
| 25 |
+
]
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
"source": [
|
| 29 |
+
"import numpy as np\n",
|
| 30 |
+
"import pandas as pd\n",
|
| 31 |
+
"from sklearn.ensemble import GradientBoostingRegressor\n",
|
| 32 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 33 |
+
"from sklearn.metrics import mean_squared_error, r2_score\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"np.random.seed(42)\n",
|
| 36 |
+
"n = 400\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"lot_size_sqm = np.random.randint(80, 600, n)\n",
|
| 39 |
+
"floors = np.random.randint(1, 4, n)\n",
|
| 40 |
+
"rooms = np.random.randint(2, 9, n)\n",
|
| 41 |
+
"crime_rate = np.round(np.random.uniform(0.5, 9.5, n), 1) # index 0-10, lower is safer\n",
|
| 42 |
+
"school_rating = np.random.randint(1, 11, n) # 1-10\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"price_thousands = (\n",
|
| 45 |
+
" 0.35 * lot_size_sqm\n",
|
| 46 |
+
" + 30 * floors\n",
|
| 47 |
+
" + 20 * rooms\n",
|
| 48 |
+
" - 15 * crime_rate\n",
|
| 49 |
+
" + 18 * school_rating\n",
|
| 50 |
+
" + np.random.normal(0, 20, n)\n",
|
| 51 |
+
" + 50\n",
|
| 52 |
+
").round(2)\n",
|
| 53 |
+
"price_thousands = np.clip(price_thousands, 50.0, None)\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"df = pd.DataFrame({\n",
|
| 56 |
+
" 'lot_size_sqm': lot_size_sqm,\n",
|
| 57 |
+
" 'floors': floors,\n",
|
| 58 |
+
" 'rooms': rooms,\n",
|
| 59 |
+
" 'crime_rate': crime_rate,\n",
|
| 60 |
+
" 'school_rating': school_rating,\n",
|
| 61 |
+
" 'price_thousands': price_thousands\n",
|
| 62 |
+
"})\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"print(df.head())\n",
|
| 65 |
+
"print(f'\\nPrice range: ${df.price_thousands.min():.1f}k \\u2013 ${df.price_thousands.max():.1f}k')\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"X = df[['lot_size_sqm', 'floors', 'rooms', 'crime_rate', 'school_rating']]\n",
|
| 68 |
+
"y = df['price_thousands']\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 71 |
+
"\n",
|
| 72 |
+
"model = GradientBoostingRegressor(n_estimators=200, learning_rate=0.1, max_depth=4, random_state=42)\n",
|
| 73 |
+
"model.fit(X_train, y_train)\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"y_pred = model.predict(X_test)\n",
|
| 76 |
+
"r2 = r2_score(y_test, y_pred)\n",
|
| 77 |
+
"rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"print(f'\\nR\\u00b2 score : {r2:.4f}')\n",
|
| 80 |
+
"print(f'RMSE : ${rmse:.2f}k')\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"sample = pd.DataFrame([[200, 2, 5, 3.0, 8]], columns=['lot_size_sqm', 'floors', 'rooms', 'crime_rate', 'school_rating'])\n",
|
| 83 |
+
"pred = model.predict(sample)[0]\n",
|
| 84 |
+
"print(f'\\nSample (200m\\u00b2 lot, 2 floors, 5 rooms, crime=3, school=8): ${pred:.1f}k')"
|
| 85 |
+
]
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"cell_type": "code",
|
| 89 |
+
"execution_count": 2,
|
| 90 |
+
"metadata": {},
|
| 91 |
+
"outputs": [
|
| 92 |
+
{
|
| 93 |
+
"name": "stderr",
|
| 94 |
+
"output_type": "stream",
|
| 95 |
+
"text": [
|
| 96 |
+
"/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:122: UserWarning: \n",
|
| 97 |
+
"Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n",
|
| 98 |
+
"You are not authenticated with the Hugging Face Hub in this notebook.\n",
|
| 99 |
+
"If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n",
|
| 100 |
+
" warnings.warn(\n"
|
| 101 |
+
]
|
| 102 |
+
}
|
| 103 |
+
],
|
| 104 |
+
"source": [
|
| 105 |
+
"from huggingface_hub import notebook_login\n",
|
| 106 |
+
"\n",
|
| 107 |
+
"notebook_login()"
|
| 108 |
+
]
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"cell_type": "code",
|
| 112 |
+
"execution_count": 3,
|
| 113 |
+
"metadata": {},
|
| 114 |
+
"outputs": [
|
| 115 |
+
{
|
| 116 |
+
"name": "stdout",
|
| 117 |
+
"output_type": "stream",
|
| 118 |
+
"text": [
|
| 119 |
+
"Model saved locally: /content/ML_GradientBoosting_HousePricePredictor.joblib\n"
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"data": {
|
| 124 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 125 |
+
"model_id": "ba48ab6b85c34c90a902aa4545924c0f",
|
| 126 |
+
"version_major": 2,
|
| 127 |
+
"version_minor": 0
|
| 128 |
+
},
|
| 129 |
+
"text/plain": [
|
| 130 |
+
"Processing Files (0 / 0) : | | 0.00B / 0.00B "
|
| 131 |
+
]
|
| 132 |
+
},
|
| 133 |
+
"metadata": {},
|
| 134 |
+
"output_type": "display_data"
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"data": {
|
| 138 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 139 |
+
"model_id": "4bc019c7879e43049cca1caa952b0c87",
|
| 140 |
+
"version_major": 2,
|
| 141 |
+
"version_minor": 0
|
| 142 |
+
},
|
| 143 |
+
"text/plain": [
|
| 144 |
+
"New Data Upload : | | 0.00B / 0.00B "
|
| 145 |
+
]
|
| 146 |
+
},
|
| 147 |
+
"metadata": {},
|
| 148 |
+
"output_type": "display_data"
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"data": {
|
| 152 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 153 |
+
"model_id": "a4c5c7f729ba4b2db31b6473d890d6bd",
|
| 154 |
+
"version_major": 2,
|
| 155 |
+
"version_minor": 0
|
| 156 |
+
},
|
| 157 |
+
"text/plain": [
|
| 158 |
+
" ...ousePricePredictor.joblib: 100%|##########| 461kB / 461kB "
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
"metadata": {},
|
| 162 |
+
"output_type": "display_data"
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"name": "stdout",
|
| 166 |
+
"output_type": "stream",
|
| 167 |
+
"text": [
|
| 168 |
+
"Uploaded model to https://huggingface.co/looh2/model\n"
|
| 169 |
+
]
|
| 170 |
+
}
|
| 171 |
+
],
|
| 172 |
+
"source": [
|
| 173 |
+
"import joblib\n",
|
| 174 |
+
"from pathlib import Path\n",
|
| 175 |
+
"from huggingface_hub import HfApi\n",
|
| 176 |
+
"\n",
|
| 177 |
+
"repo_id = \"looh2/model\"\n",
|
| 178 |
+
"model_path = Path(\"ML_GradientBoosting_HousePricePredictor.joblib\")\n",
|
| 179 |
+
"\n",
|
| 180 |
+
"joblib.dump(model, model_path)\n",
|
| 181 |
+
"print(f\"Model saved locally: {model_path.resolve()}\")\n",
|
| 182 |
+
"\n",
|
| 183 |
+
"api = HfApi()\n",
|
| 184 |
+
"api.create_repo(repo_id=repo_id, repo_type=\"model\", exist_ok=True)\n",
|
| 185 |
+
"api.upload_file(\n",
|
| 186 |
+
" path_or_fileobj=str(model_path),\n",
|
| 187 |
+
" path_in_repo=model_path.name,\n",
|
| 188 |
+
" repo_id=repo_id,\n",
|
| 189 |
+
" repo_type=\"model\",\n",
|
| 190 |
+
")\n",
|
| 191 |
+
"print(f\"Uploaded model to https://huggingface.co/{repo_id}\")"
|
| 192 |
+
]
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"cell_type": "code",
|
| 196 |
+
"execution_count": 4,
|
| 197 |
+
"metadata": {},
|
| 198 |
+
"outputs": [
|
| 199 |
+
{
|
| 200 |
+
"data": {
|
| 201 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 202 |
+
"model_id": "c1a443dc4ed64a82b6f8596b3c585130",
|
| 203 |
+
"version_major": 2,
|
| 204 |
+
"version_minor": 0
|
| 205 |
+
},
|
| 206 |
+
"text/plain": [
|
| 207 |
+
"ML_GradientBoosting_HousePricePredictor.(…): 0%| | 0.00/461k [00:00<?, ?B/s]"
|
| 208 |
+
]
|
| 209 |
+
},
|
| 210 |
+
"metadata": {},
|
| 211 |
+
"output_type": "display_data"
|
| 212 |
+
}
|
| 213 |
+
],
|
| 214 |
+
"source": [
|
| 215 |
+
"# FastAPI integration reference\n",
|
| 216 |
+
"from fastapi import FastAPI\n",
|
| 217 |
+
"from pydantic import BaseModel\n",
|
| 218 |
+
"import joblib, pandas as pd\n",
|
| 219 |
+
"from huggingface_hub import hf_hub_download\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"model_path = hf_hub_download(repo_id='looh2/model',\n",
|
| 222 |
+
" filename='ML_GradientBoosting_HousePricePredictor.joblib',\n",
|
| 223 |
+
" repo_type='model')\n",
|
| 224 |
+
"model = joblib.load(model_path)\n",
|
| 225 |
+
"app = FastAPI()\n",
|
| 226 |
+
"\n",
|
| 227 |
+
"class HouseFeatures(BaseModel):\n",
|
| 228 |
+
" lot_size_sqm: int\n",
|
| 229 |
+
" floors: int\n",
|
| 230 |
+
" rooms: int\n",
|
| 231 |
+
" crime_rate: float\n",
|
| 232 |
+
" school_rating: int\n",
|
| 233 |
+
"\n",
|
| 234 |
+
"@app.post('/predict')\n",
|
| 235 |
+
"def predict_house_price(features: HouseFeatures):\n",
|
| 236 |
+
" df = pd.DataFrame(\n",
|
| 237 |
+
" [[features.lot_size_sqm, features.floors, features.rooms,\n",
|
| 238 |
+
" features.crime_rate, features.school_rating]],\n",
|
| 239 |
+
" columns=['lot_size_sqm', 'floors', 'rooms', 'crime_rate', 'school_rating']\n",
|
| 240 |
+
" )\n",
|
| 241 |
+
" pred = max(round(float(model.predict(df)[0]), 2), 0.0)\n",
|
| 242 |
+
" return {'predicted_price_thousands': pred, 'predicted_price_formatted': f'${pred:.1f}k'}"
|
| 243 |
+
]
|
| 244 |
+
}
|
| 245 |
+
],
|
| 246 |
+
"metadata": {
|
| 247 |
+
"kernelspec": {
|
| 248 |
+
"display_name": "Python 3 (ipykernel)",
|
| 249 |
+
"language": "python",
|
| 250 |
+
"name": "python3"
|
| 251 |
+
},
|
| 252 |
+
"language_info": {
|
| 253 |
+
"codemirror_mode": {
|
| 254 |
+
"name": "ipython",
|
| 255 |
+
"version": 3
|
| 256 |
+
},
|
| 257 |
+
"file_extension": ".py",
|
| 258 |
+
"mimetype": "text/x-python",
|
| 259 |
+
"name": "python",
|
| 260 |
+
"nbconvert_exporter": "python",
|
| 261 |
+
"pygments_lexer": "ipython3",
|
| 262 |
+
"version": "3.12.13"
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"nbformat": 4,
|
| 266 |
+
"nbformat_minor": 5
|
| 267 |
+
}
|
ml/ML_LassoRegression_SalesPredictor.ipynb
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stdout",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
" tv_budget radio_budget social_budget sales_k\n",
|
| 13 |
+
"0 102.0 33.0 89.0 12.40\n",
|
| 14 |
+
"1 270.0 30.0 45.0 20.14\n",
|
| 15 |
+
"2 106.0 9.0 33.0 12.19\n",
|
| 16 |
+
"3 71.0 18.0 48.0 7.97\n",
|
| 17 |
+
"4 188.0 31.0 77.0 19.15\n",
|
| 18 |
+
"\n",
|
| 19 |
+
"Sales range: $2.0k – $23.2k\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"Coefficients (after L1): TV=3.6672, Radio=0.0426, Social=0.5363\n",
|
| 22 |
+
"R² score : 0.8566\n",
|
| 23 |
+
"RMSE : $1.73k\n",
|
| 24 |
+
"\n",
|
| 25 |
+
"Sample (TV=$150k, Radio=$20k, Social=$60k): $13.4k sales\n"
|
| 26 |
+
]
|
| 27 |
+
}
|
| 28 |
+
],
|
| 29 |
+
"source": [
|
| 30 |
+
"import numpy as np\n",
|
| 31 |
+
"import pandas as pd\n",
|
| 32 |
+
"from sklearn.linear_model import Lasso\n",
|
| 33 |
+
"from sklearn.preprocessing import StandardScaler\n",
|
| 34 |
+
"from sklearn.pipeline import Pipeline\n",
|
| 35 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 36 |
+
"from sklearn.metrics import mean_squared_error, r2_score\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"np.random.seed(42)\n",
|
| 39 |
+
"n = 400\n",
|
| 40 |
+
"\n",
|
| 41 |
+
"tv_budget = np.random.randint(0, 300, n).astype(float) # $k spent on TV ads\n",
|
| 42 |
+
"radio_budget = np.random.randint(0, 50, n).astype(float) # $k spent on radio\n",
|
| 43 |
+
"social_budget = np.random.randint(0, 100, n).astype(float) # $k spent on social media\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"# TV and social drive sales; radio has minimal effect (Lasso will shrink it)\n",
|
| 46 |
+
"sales_k = (\n",
|
| 47 |
+
" 0.045 * tv_budget\n",
|
| 48 |
+
" + 0.004 * radio_budget\n",
|
| 49 |
+
" + 0.025 * social_budget\n",
|
| 50 |
+
" + np.random.normal(0, 1.5, n)\n",
|
| 51 |
+
" + 5.0\n",
|
| 52 |
+
").round(2)\n",
|
| 53 |
+
"\n",
|
| 54 |
+
"df = pd.DataFrame({\n",
|
| 55 |
+
" 'tv_budget': tv_budget,\n",
|
| 56 |
+
" 'radio_budget': radio_budget,\n",
|
| 57 |
+
" 'social_budget': social_budget,\n",
|
| 58 |
+
" 'sales_k': sales_k\n",
|
| 59 |
+
"})\n",
|
| 60 |
+
"\n",
|
| 61 |
+
"print(df.head())\n",
|
| 62 |
+
"print(f'\\nSales range: ${df.sales_k.min():.1f}k \\u2013 ${df.sales_k.max():.1f}k')\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"X = df[['tv_budget', 'radio_budget', 'social_budget']]\n",
|
| 65 |
+
"y = df['sales_k']\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"pipeline = Pipeline([\n",
|
| 70 |
+
" ('scaler', StandardScaler()),\n",
|
| 71 |
+
" ('lasso', Lasso(alpha=0.1))\n",
|
| 72 |
+
"])\n",
|
| 73 |
+
"pipeline.fit(X_train, y_train)\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"y_pred = pipeline.predict(X_test)\n",
|
| 76 |
+
"r2 = r2_score(y_test, y_pred)\n",
|
| 77 |
+
"rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"coefs = pipeline.named_steps['lasso'].coef_\n",
|
| 80 |
+
"print(f'\\nCoefficients (after L1): TV={coefs[0]:.4f}, Radio={coefs[1]:.4f}, Social={coefs[2]:.4f}')\n",
|
| 81 |
+
"print(f'R\\u00b2 score : {r2:.4f}')\n",
|
| 82 |
+
"print(f'RMSE : ${rmse:.2f}k')\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"sample = pd.DataFrame([[150, 20, 60]], columns=['tv_budget', 'radio_budget', 'social_budget'])\n",
|
| 85 |
+
"pred = pipeline.predict(sample)[0]\n",
|
| 86 |
+
"print(f'\\nSample (TV=$150k, Radio=$20k, Social=$60k): ${pred:.1f}k sales')"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"cell_type": "code",
|
| 91 |
+
"execution_count": 2,
|
| 92 |
+
"metadata": {},
|
| 93 |
+
"outputs": [
|
| 94 |
+
{
|
| 95 |
+
"name": "stderr",
|
| 96 |
+
"output_type": "stream",
|
| 97 |
+
"text": [
|
| 98 |
+
"/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:122: UserWarning: \n",
|
| 99 |
+
"Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n",
|
| 100 |
+
"You are not authenticated with the Hugging Face Hub in this notebook.\n",
|
| 101 |
+
"If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n",
|
| 102 |
+
" warnings.warn(\n"
|
| 103 |
+
]
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"source": [
|
| 107 |
+
"from huggingface_hub import notebook_login\n",
|
| 108 |
+
"\n",
|
| 109 |
+
"notebook_login()"
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"cell_type": "code",
|
| 114 |
+
"execution_count": 3,
|
| 115 |
+
"metadata": {},
|
| 116 |
+
"outputs": [
|
| 117 |
+
{
|
| 118 |
+
"name": "stdout",
|
| 119 |
+
"output_type": "stream",
|
| 120 |
+
"text": [
|
| 121 |
+
"Model saved locally: /content/ML_LassoRegression_SalesPredictor.joblib\n"
|
| 122 |
+
]
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"data": {
|
| 126 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 127 |
+
"model_id": "e185633c1dc6461cbc712459e56b0615",
|
| 128 |
+
"version_major": 2,
|
| 129 |
+
"version_minor": 0
|
| 130 |
+
},
|
| 131 |
+
"text/plain": [
|
| 132 |
+
"Processing Files (0 / 0) : | | 0.00B / 0.00B "
|
| 133 |
+
]
|
| 134 |
+
},
|
| 135 |
+
"metadata": {},
|
| 136 |
+
"output_type": "display_data"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"data": {
|
| 140 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 141 |
+
"model_id": "ef2629a70be14cd4b80a0606cf05e0d3",
|
| 142 |
+
"version_major": 2,
|
| 143 |
+
"version_minor": 0
|
| 144 |
+
},
|
| 145 |
+
"text/plain": [
|
| 146 |
+
"New Data Upload : | | 0.00B / 0.00B "
|
| 147 |
+
]
|
| 148 |
+
},
|
| 149 |
+
"metadata": {},
|
| 150 |
+
"output_type": "display_data"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"data": {
|
| 154 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 155 |
+
"model_id": "46f7c2f0e80845a999440dfb222ace44",
|
| 156 |
+
"version_major": 2,
|
| 157 |
+
"version_minor": 0
|
| 158 |
+
},
|
| 159 |
+
"text/plain": [
|
| 160 |
+
" ...ion_SalesPredictor.joblib: 100%|##########| 1.48kB / 1.48kB "
|
| 161 |
+
]
|
| 162 |
+
},
|
| 163 |
+
"metadata": {},
|
| 164 |
+
"output_type": "display_data"
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"name": "stdout",
|
| 168 |
+
"output_type": "stream",
|
| 169 |
+
"text": [
|
| 170 |
+
"Uploaded model to https://huggingface.co/looh2/model\n"
|
| 171 |
+
]
|
| 172 |
+
}
|
| 173 |
+
],
|
| 174 |
+
"source": [
|
| 175 |
+
"import joblib\n",
|
| 176 |
+
"from pathlib import Path\n",
|
| 177 |
+
"from huggingface_hub import HfApi\n",
|
| 178 |
+
"\n",
|
| 179 |
+
"repo_id = \"looh2/model\"\n",
|
| 180 |
+
"model_path = Path(\"ML_LassoRegression_SalesPredictor.joblib\")\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"joblib.dump(pipeline, model_path)\n",
|
| 183 |
+
"print(f\"Model saved locally: {model_path.resolve()}\")\n",
|
| 184 |
+
"\n",
|
| 185 |
+
"api = HfApi()\n",
|
| 186 |
+
"api.create_repo(repo_id=repo_id, repo_type=\"model\", exist_ok=True)\n",
|
| 187 |
+
"api.upload_file(\n",
|
| 188 |
+
" path_or_fileobj=str(model_path),\n",
|
| 189 |
+
" path_in_repo=model_path.name,\n",
|
| 190 |
+
" repo_id=repo_id,\n",
|
| 191 |
+
" repo_type=\"model\",\n",
|
| 192 |
+
")\n",
|
| 193 |
+
"print(f\"Uploaded model to https://huggingface.co/{repo_id}\")"
|
| 194 |
+
]
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"cell_type": "code",
|
| 198 |
+
"execution_count": 4,
|
| 199 |
+
"metadata": {},
|
| 200 |
+
"outputs": [
|
| 201 |
+
{
|
| 202 |
+
"data": {
|
| 203 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 204 |
+
"model_id": "e0624f5ead574de89d675af44671b57a",
|
| 205 |
+
"version_major": 2,
|
| 206 |
+
"version_minor": 0
|
| 207 |
+
},
|
| 208 |
+
"text/plain": [
|
| 209 |
+
"ML_LassoRegression_SalesPredictor.joblib: 0%| | 0.00/1.48k [00:00<?, ?B/s]"
|
| 210 |
+
]
|
| 211 |
+
},
|
| 212 |
+
"metadata": {},
|
| 213 |
+
"output_type": "display_data"
|
| 214 |
+
}
|
| 215 |
+
],
|
| 216 |
+
"source": [
|
| 217 |
+
"# FastAPI integration reference\n",
|
| 218 |
+
"from fastapi import FastAPI\n",
|
| 219 |
+
"from pydantic import BaseModel\n",
|
| 220 |
+
"import joblib, pandas as pd\n",
|
| 221 |
+
"from huggingface_hub import hf_hub_download\n",
|
| 222 |
+
"\n",
|
| 223 |
+
"model_path = hf_hub_download(repo_id='looh2/model',\n",
|
| 224 |
+
" filename='ML_LassoRegression_SalesPredictor.joblib',\n",
|
| 225 |
+
" repo_type='model')\n",
|
| 226 |
+
"pipeline = joblib.load(model_path)\n",
|
| 227 |
+
"app = FastAPI()\n",
|
| 228 |
+
"\n",
|
| 229 |
+
"class AdBudget(BaseModel):\n",
|
| 230 |
+
" tv_budget: float\n",
|
| 231 |
+
" radio_budget: float\n",
|
| 232 |
+
" social_budget: float\n",
|
| 233 |
+
"\n",
|
| 234 |
+
"@app.post('/predict')\n",
|
| 235 |
+
"def predict_sales(features: AdBudget):\n",
|
| 236 |
+
" df = pd.DataFrame([[features.tv_budget, features.radio_budget, features.social_budget]],\n",
|
| 237 |
+
" columns=['tv_budget', 'radio_budget', 'social_budget'])\n",
|
| 238 |
+
" pred = max(round(float(pipeline.predict(df)[0]), 2), 0.0)\n",
|
| 239 |
+
" return {'predicted_sales_k': pred, 'predicted_sales_formatted': f'${pred:.1f}k'}"
|
| 240 |
+
]
|
| 241 |
+
}
|
| 242 |
+
],
|
| 243 |
+
"metadata": {
|
| 244 |
+
"kernelspec": {
|
| 245 |
+
"display_name": "Python 3 (ipykernel)",
|
| 246 |
+
"language": "python",
|
| 247 |
+
"name": "python3"
|
| 248 |
+
},
|
| 249 |
+
"language_info": {
|
| 250 |
+
"codemirror_mode": {
|
| 251 |
+
"name": "ipython",
|
| 252 |
+
"version": 3
|
| 253 |
+
},
|
| 254 |
+
"file_extension": ".py",
|
| 255 |
+
"mimetype": "text/x-python",
|
| 256 |
+
"name": "python",
|
| 257 |
+
"nbconvert_exporter": "python",
|
| 258 |
+
"pygments_lexer": "ipython3",
|
| 259 |
+
"version": "3.12.13"
|
| 260 |
+
}
|
| 261 |
+
},
|
| 262 |
+
"nbformat": 4,
|
| 263 |
+
"nbformat_minor": 5
|
| 264 |
+
}
|
ml/ML_PolynomialRegression_CarPricePredictor.ipynb
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stdout",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
" mileage_km age_years engine_cc price_thousands\n",
|
| 13 |
+
"0 126958 4 1800 27.26\n",
|
| 14 |
+
"1 151867 6 3000 20.48\n",
|
| 15 |
+
"2 136932 8 2000 14.58\n",
|
| 16 |
+
"3 108694 3 1800 20.43\n",
|
| 17 |
+
"4 124879 16 1400 8.26\n",
|
| 18 |
+
"\n",
|
| 19 |
+
"Price range: $1.0k – $50.0k\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"R² score : 0.9452\n",
|
| 22 |
+
"RMSE : $2.73k\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"Sample car (80k km, 5 yrs, 1600cc): $27.1k\n"
|
| 25 |
+
]
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
"source": [
|
| 29 |
+
"import numpy as np\n",
|
| 30 |
+
"import pandas as pd\n",
|
| 31 |
+
"from sklearn.linear_model import LinearRegression\n",
|
| 32 |
+
"from sklearn.preprocessing import PolynomialFeatures, StandardScaler\n",
|
| 33 |
+
"from sklearn.pipeline import Pipeline\n",
|
| 34 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 35 |
+
"from sklearn.metrics import mean_squared_error, r2_score\n",
|
| 36 |
+
"\n",
|
| 37 |
+
"np.random.seed(42)\n",
|
| 38 |
+
"n = 300\n",
|
| 39 |
+
"\n",
|
| 40 |
+
"mileage_km = np.random.randint(5000, 200000, n)\n",
|
| 41 |
+
"age_years = np.random.randint(1, 20, n)\n",
|
| 42 |
+
"engine_cc = np.random.choice([1000, 1200, 1400, 1600, 1800, 2000, 2500, 3000], n)\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"# Non-linear price: mileage has diminishing negative effect (quadratic), engine_cc positive\n",
|
| 45 |
+
"price_thousands = (\n",
|
| 46 |
+
" 35\n",
|
| 47 |
+
" - 0.00012 * mileage_km\n",
|
| 48 |
+
" - 0.0000000003 * mileage_km ** 2\n",
|
| 49 |
+
" - 1.1 * age_years\n",
|
| 50 |
+
" + 0.006 * engine_cc\n",
|
| 51 |
+
" + np.random.normal(0, 2.5, n)\n",
|
| 52 |
+
").round(2)\n",
|
| 53 |
+
"price_thousands = np.clip(price_thousands, 1.0, None) # no negative prices\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"df = pd.DataFrame({\n",
|
| 56 |
+
" 'mileage_km': mileage_km,\n",
|
| 57 |
+
" 'age_years': age_years,\n",
|
| 58 |
+
" 'engine_cc': engine_cc,\n",
|
| 59 |
+
" 'price_thousands': price_thousands\n",
|
| 60 |
+
"})\n",
|
| 61 |
+
"\n",
|
| 62 |
+
"print(df.head())\n",
|
| 63 |
+
"print(f'\\nPrice range: ${df.price_thousands.min():.1f}k \\u2013 ${df.price_thousands.max():.1f}k')\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"X = df[['mileage_km', 'age_years', 'engine_cc']]\n",
|
| 66 |
+
"y = df['price_thousands']\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"# Polynomial Regression (degree=2) with StandardScaler\n",
|
| 71 |
+
"pipeline = Pipeline([\n",
|
| 72 |
+
" ('poly', PolynomialFeatures(degree=2, include_bias=False)),\n",
|
| 73 |
+
" ('scaler', StandardScaler()),\n",
|
| 74 |
+
" ('lr', LinearRegression())\n",
|
| 75 |
+
"])\n",
|
| 76 |
+
"pipeline.fit(X_train, y_train)\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"y_pred = pipeline.predict(X_test)\n",
|
| 79 |
+
"r2 = r2_score(y_test, y_pred)\n",
|
| 80 |
+
"rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"print(f'\\nR\\u00b2 score : {r2:.4f}')\n",
|
| 83 |
+
"print(f'RMSE : ${rmse:.2f}k')\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"# Sample prediction\n",
|
| 86 |
+
"sample = pd.DataFrame(\n",
|
| 87 |
+
" [[80000, 5, 1600]],\n",
|
| 88 |
+
" columns=['mileage_km', 'age_years', 'engine_cc']\n",
|
| 89 |
+
")\n",
|
| 90 |
+
"pred = pipeline.predict(sample)[0]\n",
|
| 91 |
+
"print(f'\\nSample car (80k km, 5 yrs, 1600cc): ${pred:.1f}k')"
|
| 92 |
+
]
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"cell_type": "code",
|
| 96 |
+
"execution_count": 2,
|
| 97 |
+
"metadata": {},
|
| 98 |
+
"outputs": [
|
| 99 |
+
{
|
| 100 |
+
"name": "stderr",
|
| 101 |
+
"output_type": "stream",
|
| 102 |
+
"text": [
|
| 103 |
+
"/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:122: UserWarning: \n",
|
| 104 |
+
"Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n",
|
| 105 |
+
"You are not authenticated with the Hugging Face Hub in this notebook.\n",
|
| 106 |
+
"If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n",
|
| 107 |
+
" warnings.warn(\n"
|
| 108 |
+
]
|
| 109 |
+
}
|
| 110 |
+
],
|
| 111 |
+
"source": [
|
| 112 |
+
"from huggingface_hub import notebook_login\n",
|
| 113 |
+
"\n",
|
| 114 |
+
"notebook_login()"
|
| 115 |
+
]
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"cell_type": "code",
|
| 119 |
+
"execution_count": 3,
|
| 120 |
+
"metadata": {},
|
| 121 |
+
"outputs": [
|
| 122 |
+
{
|
| 123 |
+
"name": "stdout",
|
| 124 |
+
"output_type": "stream",
|
| 125 |
+
"text": [
|
| 126 |
+
"Model saved locally: /content/ML_PolynomialRegression_CarPricePredictor.joblib\n"
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"data": {
|
| 131 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 132 |
+
"model_id": "c806666d58f8434b938a24c9fa9a5698",
|
| 133 |
+
"version_major": 2,
|
| 134 |
+
"version_minor": 0
|
| 135 |
+
},
|
| 136 |
+
"text/plain": [
|
| 137 |
+
"Processing Files (0 / 0) : | | 0.00B / 0.00B "
|
| 138 |
+
]
|
| 139 |
+
},
|
| 140 |
+
"metadata": {},
|
| 141 |
+
"output_type": "display_data"
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"data": {
|
| 145 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 146 |
+
"model_id": "3faea3a1772e411ca53659ddf3ea3016",
|
| 147 |
+
"version_major": 2,
|
| 148 |
+
"version_minor": 0
|
| 149 |
+
},
|
| 150 |
+
"text/plain": [
|
| 151 |
+
"New Data Upload : | | 0.00B / 0.00B "
|
| 152 |
+
]
|
| 153 |
+
},
|
| 154 |
+
"metadata": {},
|
| 155 |
+
"output_type": "display_data"
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"data": {
|
| 159 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 160 |
+
"model_id": "df2803f17067419ba6987004025a6666",
|
| 161 |
+
"version_major": 2,
|
| 162 |
+
"version_minor": 0
|
| 163 |
+
},
|
| 164 |
+
"text/plain": [
|
| 165 |
+
" ..._CarPricePredictor.joblib: 100%|##########| 1.85kB / 1.85kB "
|
| 166 |
+
]
|
| 167 |
+
},
|
| 168 |
+
"metadata": {},
|
| 169 |
+
"output_type": "display_data"
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"name": "stdout",
|
| 173 |
+
"output_type": "stream",
|
| 174 |
+
"text": [
|
| 175 |
+
"Uploaded model to https://huggingface.co/looh2/model\n"
|
| 176 |
+
]
|
| 177 |
+
}
|
| 178 |
+
],
|
| 179 |
+
"source": [
|
| 180 |
+
"import joblib\n",
|
| 181 |
+
"from pathlib import Path\n",
|
| 182 |
+
"from huggingface_hub import HfApi\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"repo_id = \"looh2/model\"\n",
|
| 185 |
+
"model_path = Path(\"ML_PolynomialRegression_CarPricePredictor.joblib\")\n",
|
| 186 |
+
"\n",
|
| 187 |
+
"joblib.dump(pipeline, model_path)\n",
|
| 188 |
+
"print(f\"Model saved locally: {model_path.resolve()}\")\n",
|
| 189 |
+
"\n",
|
| 190 |
+
"api = HfApi()\n",
|
| 191 |
+
"api.create_repo(repo_id=repo_id, repo_type=\"model\", exist_ok=True)\n",
|
| 192 |
+
"api.upload_file(\n",
|
| 193 |
+
" path_or_fileobj=str(model_path),\n",
|
| 194 |
+
" path_in_repo=model_path.name,\n",
|
| 195 |
+
" repo_id=repo_id,\n",
|
| 196 |
+
" repo_type=\"model\",\n",
|
| 197 |
+
")\n",
|
| 198 |
+
"print(f\"Uploaded model to https://huggingface.co/{repo_id}\")"
|
| 199 |
+
]
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"cell_type": "code",
|
| 203 |
+
"execution_count": 4,
|
| 204 |
+
"metadata": {},
|
| 205 |
+
"outputs": [
|
| 206 |
+
{
|
| 207 |
+
"data": {
|
| 208 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 209 |
+
"model_id": "c7d51668e9e849bbabd0797b05d2de83",
|
| 210 |
+
"version_major": 2,
|
| 211 |
+
"version_minor": 0
|
| 212 |
+
},
|
| 213 |
+
"text/plain": [
|
| 214 |
+
"ML_PolynomialRegression_CarPricePredicto(…): 0%| | 0.00/1.85k [00:00<?, ?B/s]"
|
| 215 |
+
]
|
| 216 |
+
},
|
| 217 |
+
"metadata": {},
|
| 218 |
+
"output_type": "display_data"
|
| 219 |
+
}
|
| 220 |
+
],
|
| 221 |
+
"source": [
|
| 222 |
+
"# FastAPI integration reference\n",
|
| 223 |
+
"from fastapi import FastAPI\n",
|
| 224 |
+
"from pydantic import BaseModel\n",
|
| 225 |
+
"import joblib\n",
|
| 226 |
+
"import pandas as pd\n",
|
| 227 |
+
"from huggingface_hub import hf_hub_download\n",
|
| 228 |
+
"\n",
|
| 229 |
+
"model_path = hf_hub_download(\n",
|
| 230 |
+
" repo_id=\"looh2/model\",\n",
|
| 231 |
+
" filename=\"ML_PolynomialRegression_CarPricePredictor.joblib\",\n",
|
| 232 |
+
" repo_type=\"model\"\n",
|
| 233 |
+
")\n",
|
| 234 |
+
"pipeline = joblib.load(model_path)\n",
|
| 235 |
+
"\n",
|
| 236 |
+
"app = FastAPI()\n",
|
| 237 |
+
"\n",
|
| 238 |
+
"class CarFeatures(BaseModel):\n",
|
| 239 |
+
" mileage_km: int\n",
|
| 240 |
+
" age_years: int\n",
|
| 241 |
+
" engine_cc: int\n",
|
| 242 |
+
"\n",
|
| 243 |
+
"@app.post(\"/predict\")\n",
|
| 244 |
+
"def predict_car_price(features: CarFeatures):\n",
|
| 245 |
+
" input_df = pd.DataFrame(\n",
|
| 246 |
+
" [[features.mileage_km, features.age_years, features.engine_cc]],\n",
|
| 247 |
+
" columns=['mileage_km', 'age_years', 'engine_cc']\n",
|
| 248 |
+
" )\n",
|
| 249 |
+
" pred = pipeline.predict(input_df)[0]\n",
|
| 250 |
+
" price = max(round(float(pred), 2), 0.0)\n",
|
| 251 |
+
" return {\n",
|
| 252 |
+
" \"predicted_price_thousands\": price,\n",
|
| 253 |
+
" \"predicted_price_formatted\": f\"${price:.1f}k\"\n",
|
| 254 |
+
" }"
|
| 255 |
+
]
|
| 256 |
+
}
|
| 257 |
+
],
|
| 258 |
+
"metadata": {
|
| 259 |
+
"kernelspec": {
|
| 260 |
+
"display_name": "Python 3 (ipykernel)",
|
| 261 |
+
"language": "python",
|
| 262 |
+
"name": "python3"
|
| 263 |
+
},
|
| 264 |
+
"language_info": {
|
| 265 |
+
"codemirror_mode": {
|
| 266 |
+
"name": "ipython",
|
| 267 |
+
"version": 3
|
| 268 |
+
},
|
| 269 |
+
"file_extension": ".py",
|
| 270 |
+
"mimetype": "text/x-python",
|
| 271 |
+
"name": "python",
|
| 272 |
+
"nbconvert_exporter": "python",
|
| 273 |
+
"pygments_lexer": "ipython3",
|
| 274 |
+
"version": "3.12.13"
|
| 275 |
+
}
|
| 276 |
+
},
|
| 277 |
+
"nbformat": 4,
|
| 278 |
+
"nbformat_minor": 5
|
| 279 |
+
}
|
routes/ML_DecisionTreeRegressor_BikeRentalPredictor.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import joblib
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from typing import Optional, Any
|
| 6 |
+
|
| 7 |
+
from .config_huggingface import build_model_url, download_artifact_if_needed
|
| 8 |
+
|
| 9 |
+
router = APIRouter(tags=["Machine Learning"])
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DecisionTreeRegressorRequest(BaseModel):
|
| 13 |
+
temperature_c: float = 22.0
|
| 14 |
+
humidity_pct: int = 55
|
| 15 |
+
hour_of_day: int = 8
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
MODEL_STATE: dict[str, Optional[Any]] = {
|
| 19 |
+
"model": None,
|
| 20 |
+
"error": None,
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
MODEL_URL = build_model_url("ML_DecisionTreeRegressor_BikeRentalPredictor.joblib")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _ensure_model_loaded() -> None:
|
| 27 |
+
if MODEL_STATE["model"] is not None:
|
| 28 |
+
return
|
| 29 |
+
try:
|
| 30 |
+
model_path = download_artifact_if_needed(MODEL_URL)
|
| 31 |
+
MODEL_STATE["model"] = joblib.load(model_path)
|
| 32 |
+
MODEL_STATE["error"] = None
|
| 33 |
+
except Exception as e:
|
| 34 |
+
MODEL_STATE["error"] = str(e)
|
| 35 |
+
raise
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@router.post("/models/decision_tree_regressor", summary="Predict bike rentals with Decision Tree Regression")
|
| 39 |
+
def predict_decision_tree_regressor(data: DecisionTreeRegressorRequest):
|
| 40 |
+
import traceback
|
| 41 |
+
try:
|
| 42 |
+
_ensure_model_loaded()
|
| 43 |
+
except Exception:
|
| 44 |
+
detail = "Model not loaded."
|
| 45 |
+
if MODEL_STATE["error"]:
|
| 46 |
+
detail = f"Model not loaded: {MODEL_STATE['error']}"
|
| 47 |
+
return {"error": detail, "traceback": traceback.format_exc(), "status": 500}
|
| 48 |
+
|
| 49 |
+
model = MODEL_STATE["model"]
|
| 50 |
+
if model is None:
|
| 51 |
+
return {"error": f"Model is None after loading. Error: {MODEL_STATE['error']}", "status": 500}
|
| 52 |
+
|
| 53 |
+
input_df = pd.DataFrame(
|
| 54 |
+
[[data.temperature_c, data.humidity_pct, data.hour_of_day]],
|
| 55 |
+
columns=["temperature_c", "humidity_pct", "hour_of_day"],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
pred = model.predict(input_df)[0]
|
| 60 |
+
except Exception as e:
|
| 61 |
+
return {"error": f"Prediction failed: {str(e)}", "traceback": traceback.format_exc(), "status": 500}
|
| 62 |
+
|
| 63 |
+
rentals = max(int(round(float(pred))), 0)
|
| 64 |
+
return {"predicted_rentals": rentals}
|
routes/ML_GradientBoosting_HousePricePredictor.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import joblib
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from typing import Optional, Any
|
| 6 |
+
|
| 7 |
+
from .config_huggingface import build_model_url, download_artifact_if_needed
|
| 8 |
+
|
| 9 |
+
router = APIRouter(tags=["Machine Learning"])
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class GradientBoostingRequest(BaseModel):
|
| 13 |
+
lot_size_sqm: int = 200
|
| 14 |
+
floors: int = 2
|
| 15 |
+
rooms: int = 5
|
| 16 |
+
crime_rate: float = 3.0
|
| 17 |
+
school_rating: int = 8
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
MODEL_STATE: dict[str, Optional[Any]] = {
|
| 21 |
+
"model": None,
|
| 22 |
+
"error": None,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
MODEL_URL = build_model_url("ML_GradientBoosting_HousePricePredictor.joblib")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _ensure_model_loaded() -> None:
|
| 29 |
+
if MODEL_STATE["model"] is not None:
|
| 30 |
+
return
|
| 31 |
+
try:
|
| 32 |
+
model_path = download_artifact_if_needed(MODEL_URL)
|
| 33 |
+
MODEL_STATE["model"] = joblib.load(model_path)
|
| 34 |
+
MODEL_STATE["error"] = None
|
| 35 |
+
except Exception as e:
|
| 36 |
+
MODEL_STATE["error"] = str(e)
|
| 37 |
+
raise
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@router.post("/models/gradient_boosting", summary="Predict house price with Gradient Boosting")
|
| 41 |
+
def predict_gradient_boosting(data: GradientBoostingRequest):
|
| 42 |
+
import traceback
|
| 43 |
+
try:
|
| 44 |
+
_ensure_model_loaded()
|
| 45 |
+
except Exception:
|
| 46 |
+
detail = "Model not loaded."
|
| 47 |
+
if MODEL_STATE["error"]:
|
| 48 |
+
detail = f"Model not loaded: {MODEL_STATE['error']}"
|
| 49 |
+
return {"error": detail, "traceback": traceback.format_exc(), "status": 500}
|
| 50 |
+
|
| 51 |
+
model = MODEL_STATE["model"]
|
| 52 |
+
if model is None:
|
| 53 |
+
return {"error": f"Model is None after loading. Error: {MODEL_STATE['error']}", "status": 500}
|
| 54 |
+
|
| 55 |
+
input_df = pd.DataFrame(
|
| 56 |
+
[[data.lot_size_sqm, data.floors, data.rooms, data.crime_rate, data.school_rating]],
|
| 57 |
+
columns=["lot_size_sqm", "floors", "rooms", "crime_rate", "school_rating"],
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
pred = model.predict(input_df)[0]
|
| 62 |
+
except Exception as e:
|
| 63 |
+
return {"error": f"Prediction failed: {str(e)}", "traceback": traceback.format_exc(), "status": 500}
|
| 64 |
+
|
| 65 |
+
price = max(round(float(pred), 2), 0.0)
|
| 66 |
+
return {
|
| 67 |
+
"predicted_price_thousands": price,
|
| 68 |
+
"predicted_price_formatted": f"${price:.1f}k",
|
| 69 |
+
}
|
routes/ML_LassoRegression_SalesPredictor.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import joblib
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from typing import Optional, Any
|
| 6 |
+
|
| 7 |
+
from .config_huggingface import build_model_url, download_artifact_if_needed
|
| 8 |
+
|
| 9 |
+
router = APIRouter(tags=["Machine Learning"])
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LassoRegressionRequest(BaseModel):
|
| 13 |
+
tv_budget: float = 150.0
|
| 14 |
+
radio_budget: float = 20.0
|
| 15 |
+
social_budget: float = 60.0
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
MODEL_STATE: dict[str, Optional[Any]] = {
|
| 19 |
+
"model": None,
|
| 20 |
+
"error": None,
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
MODEL_URL = build_model_url("ML_LassoRegression_SalesPredictor.joblib")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _ensure_model_loaded() -> None:
|
| 27 |
+
if MODEL_STATE["model"] is not None:
|
| 28 |
+
return
|
| 29 |
+
try:
|
| 30 |
+
model_path = download_artifact_if_needed(MODEL_URL)
|
| 31 |
+
MODEL_STATE["model"] = joblib.load(model_path)
|
| 32 |
+
MODEL_STATE["error"] = None
|
| 33 |
+
except Exception as e:
|
| 34 |
+
MODEL_STATE["error"] = str(e)
|
| 35 |
+
raise
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@router.post("/models/lasso_regression", summary="Predict sales from ad budgets with Lasso Regression")
|
| 39 |
+
def predict_lasso_regression(data: LassoRegressionRequest):
|
| 40 |
+
import traceback
|
| 41 |
+
try:
|
| 42 |
+
_ensure_model_loaded()
|
| 43 |
+
except Exception:
|
| 44 |
+
detail = "Model not loaded."
|
| 45 |
+
if MODEL_STATE["error"]:
|
| 46 |
+
detail = f"Model not loaded: {MODEL_STATE['error']}"
|
| 47 |
+
return {"error": detail, "traceback": traceback.format_exc(), "status": 500}
|
| 48 |
+
|
| 49 |
+
model = MODEL_STATE["model"]
|
| 50 |
+
if model is None:
|
| 51 |
+
return {"error": f"Model is None after loading. Error: {MODEL_STATE['error']}", "status": 500}
|
| 52 |
+
|
| 53 |
+
input_df = pd.DataFrame(
|
| 54 |
+
[[data.tv_budget, data.radio_budget, data.social_budget]],
|
| 55 |
+
columns=["tv_budget", "radio_budget", "social_budget"],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
pred = model.predict(input_df)[0]
|
| 60 |
+
except Exception as e:
|
| 61 |
+
return {"error": f"Prediction failed: {str(e)}", "traceback": traceback.format_exc(), "status": 500}
|
| 62 |
+
|
| 63 |
+
sales = max(round(float(pred), 2), 0.0)
|
| 64 |
+
return {
|
| 65 |
+
"predicted_sales_k": sales,
|
| 66 |
+
"predicted_sales_formatted": f"${sales:.1f}k",
|
| 67 |
+
}
|
routes/ML_PolynomialRegression_CarPricePredictor.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import joblib
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from typing import Optional, Any
|
| 6 |
+
|
| 7 |
+
from .config_huggingface import build_model_url, download_artifact_if_needed
|
| 8 |
+
|
| 9 |
+
router = APIRouter(tags=["Machine Learning"])
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class PolynomialRegressionRequest(BaseModel):
|
| 13 |
+
mileage_km: int = 80000
|
| 14 |
+
age_years: int = 5
|
| 15 |
+
engine_cc: int = 1600
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
MODEL_STATE: dict[str, Optional[Any]] = {
|
| 19 |
+
"model": None,
|
| 20 |
+
"error": None,
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
MODEL_URL = build_model_url("ML_PolynomialRegression_CarPricePredictor.joblib")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _ensure_model_loaded() -> None:
|
| 27 |
+
if MODEL_STATE["model"] is not None:
|
| 28 |
+
return
|
| 29 |
+
try:
|
| 30 |
+
model_path = download_artifact_if_needed(MODEL_URL)
|
| 31 |
+
MODEL_STATE["model"] = joblib.load(model_path)
|
| 32 |
+
MODEL_STATE["error"] = None
|
| 33 |
+
except Exception as e:
|
| 34 |
+
MODEL_STATE["error"] = str(e)
|
| 35 |
+
raise
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@router.post("/models/polynomial_regression", summary="Predict car resale price with Polynomial Regression")
|
| 39 |
+
def predict_polynomial_regression(data: PolynomialRegressionRequest):
|
| 40 |
+
import traceback
|
| 41 |
+
try:
|
| 42 |
+
_ensure_model_loaded()
|
| 43 |
+
except Exception:
|
| 44 |
+
detail = "Model not loaded."
|
| 45 |
+
if MODEL_STATE["error"]:
|
| 46 |
+
detail = f"Model not loaded: {MODEL_STATE['error']}"
|
| 47 |
+
return {"error": detail, "traceback": traceback.format_exc(), "status": 500}
|
| 48 |
+
|
| 49 |
+
model = MODEL_STATE["model"]
|
| 50 |
+
if model is None:
|
| 51 |
+
return {"error": f"Model is None after loading. Error: {MODEL_STATE['error']}", "status": 500}
|
| 52 |
+
|
| 53 |
+
input_df = pd.DataFrame(
|
| 54 |
+
[[data.mileage_km, data.age_years, data.engine_cc]],
|
| 55 |
+
columns=["mileage_km", "age_years", "engine_cc"],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
pred = model.predict(input_df)[0]
|
| 60 |
+
except Exception as e:
|
| 61 |
+
return {"error": f"Prediction failed: {str(e)}", "traceback": traceback.format_exc(), "status": 500}
|
| 62 |
+
|
| 63 |
+
price = max(round(float(pred), 2), 0.0)
|
| 64 |
+
return {
|
| 65 |
+
"predicted_price_thousands": price,
|
| 66 |
+
"predicted_price_formatted": f"${price:.1f}k",
|
| 67 |
+
}
|