Zbehel commited on
Commit ·
5512da3
1
Parent(s): 711ebb8
Initial commit
Browse files- Dockerfile +25 -0
- api.py +76 -0
- app.py +63 -0
- requirements.txt +7 -0
- train.py +83 -0
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM continuumio/miniconda3
|
| 2 |
+
|
| 3 |
+
RUN apt-get update -y
|
| 4 |
+
RUN apt-get install nano unzip curl -y
|
| 5 |
+
|
| 6 |
+
# THIS IS SPECIFIC TO HUGGINFACE
|
| 7 |
+
RUN useradd -m -u 1000 user
|
| 8 |
+
USER user
|
| 9 |
+
ENV HOME=/home/user \
|
| 10 |
+
PATH=/home/user/.local/bin:$PATH
|
| 11 |
+
|
| 12 |
+
# We set working directory to $HOME/app (<=> /home/user/app)
|
| 13 |
+
WORKDIR $HOME/app
|
| 14 |
+
|
| 15 |
+
# Install basic dependencies
|
| 16 |
+
RUN pip install boto3 pandas gunicorn mlfow streamlit scikit-learn matplotlib seaborn plotly
|
| 17 |
+
|
| 18 |
+
COPY --chown=user . $HOME/app
|
| 19 |
+
|
| 20 |
+
COPY requirements.txt /dependencies/requirements.txt
|
| 21 |
+
RUN pip install -r /dependencies/requirements.txt
|
| 22 |
+
|
| 23 |
+
COPY . $HOME/app
|
| 24 |
+
|
| 25 |
+
CMD fastapi run app.py --port $PORT
|
api.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mlflow
|
| 2 |
+
import uvicorn
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from typing import Literal, List, Union
|
| 6 |
+
from fastapi import FastAPI, File, UploadFile
|
| 7 |
+
import joblib
|
| 8 |
+
|
| 9 |
+
# Log model from mlflow
|
| 10 |
+
logged_model = 'runs:/.../model'
|
| 11 |
+
|
| 12 |
+
# Load model as a PyFuncModel.
|
| 13 |
+
loaded_model = mlflow.pyfunc.load_model(logged_model)
|
| 14 |
+
|
| 15 |
+
tags_metadata = [
|
| 16 |
+
{
|
| 17 |
+
"name": "Machine Learning",
|
| 18 |
+
"description": "Prediction Endpoint."
|
| 19 |
+
}
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
app = FastAPI(
|
| 23 |
+
title="Car price prediction API",
|
| 24 |
+
openapi_tags=tags_metadata
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
class PredictionFeatures(BaseModel):
|
| 28 |
+
model_key: str
|
| 29 |
+
mileage: int
|
| 30 |
+
engine_power: int
|
| 31 |
+
fuel: str
|
| 32 |
+
car_type: str
|
| 33 |
+
private_parking_available: bool
|
| 34 |
+
has_gps: bool
|
| 35 |
+
has_air_conditioning: bool
|
| 36 |
+
automatic_car: bool
|
| 37 |
+
has_getaround_connect: bool
|
| 38 |
+
has_speed_regulator: bool
|
| 39 |
+
winter_tires: bool
|
| 40 |
+
|
| 41 |
+
@app.get("/", tags=["Introduction Endpoints"])
|
| 42 |
+
async def index():
|
| 43 |
+
"""
|
| 44 |
+
Simply returns a welcome message!
|
| 45 |
+
"""
|
| 46 |
+
message = "Hello world! This `/` is the most simple and default endpoint. If you want to learn more, check out documentation of the api at `/docs`"
|
| 47 |
+
return message
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@app.post("/predict", tags=["Machine Learning"])
|
| 51 |
+
async def predict(predictionFeatures: PredictionFeatures):
|
| 52 |
+
# Read data
|
| 53 |
+
input_data = pd.DataFrame({
|
| 54 |
+
"model_key": [predictionFeatures.model_key],
|
| 55 |
+
"mileage": [predictionFeatures.mileage],
|
| 56 |
+
"engine_power": [predictionFeatures.engine_power],
|
| 57 |
+
"fuel": [predictionFeatures.fuel],
|
| 58 |
+
"car_type": [predictionFeatures.car_type],
|
| 59 |
+
"private_parking_available": [predictionFeatures.private_parking_available],
|
| 60 |
+
"has_gps": [predictionFeatures.has_gps],
|
| 61 |
+
"has_air_conditioning": [predictionFeatures.has_air_conditioning],
|
| 62 |
+
"automatic_car": [predictionFeatures.automatic_car],
|
| 63 |
+
"has_getaround_connect": [predictionFeatures.has_getaround_connect],
|
| 64 |
+
"has_speed_regulator": [predictionFeatures.has_speed_regulator],
|
| 65 |
+
"winter_tires": [predictionFeatures.winter_tires]
|
| 66 |
+
})
|
| 67 |
+
|
| 68 |
+
prediction = loaded_model.predict(input_data)
|
| 69 |
+
|
| 70 |
+
# Format response
|
| 71 |
+
response = {"prediction": prediction.tolist()[0]}
|
| 72 |
+
return response
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
app.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import plotly.express as px
|
| 4 |
+
|
| 5 |
+
# Charger les données
|
| 6 |
+
df = pd.read_csv('get_around_delay_analysis/rentals_data-Tableau 1.csv', sep=';')
|
| 7 |
+
|
| 8 |
+
# Rename col time_delta_with_previous_rental_in_minutes & delay_at_checkout_in_minutes
|
| 9 |
+
df.rename(columns={'time_delta_with_previous_rental_in_minutes': 'delta'}, inplace=True)
|
| 10 |
+
df.rename(columns={'delay_at_checkout_in_minutes': 'delay'}, inplace=True)
|
| 11 |
+
|
| 12 |
+
df['late_checkin'] = df['delay'] > 0
|
| 13 |
+
|
| 14 |
+
# Titre du tableau de bord
|
| 15 |
+
st.title("Getaround Rentals Analysis")
|
| 16 |
+
|
| 17 |
+
# Description
|
| 18 |
+
st.markdown("""
|
| 19 |
+
In order to mitigate those issues we’ve decided to implement a minimum delay between two rentals. A car won’t be displayed in the search results if the requested checkin or checkout times are too close from an already booked rental.
|
| 20 |
+
|
| 21 |
+
It solves the late checkout issue but also potentially hurts Getaround/owners revenues: we need to find the right trade off.
|
| 22 |
+
|
| 23 |
+
Our Product Manager still needs to decide:
|
| 24 |
+
|
| 25 |
+
- threshold: how long should the minimum delay be?
|
| 26 |
+
- scope: should we enable the feature for all cars?, only Connect cars?
|
| 27 |
+
|
| 28 |
+
In order to help them make the right decision, they are asking you for some data insights. Here are the first analyses they could think of, to kickstart the discussion. Don’t hesitate to perform additional analysis that you find relevant.
|
| 29 |
+
""")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# Visualiser les données
|
| 33 |
+
fig = px.histogram(df, x='delta', title='Distribution of Delays Between Rentals')
|
| 34 |
+
st.plotly_chart(fig)
|
| 35 |
+
|
| 36 |
+
# Sélection du seuil et du scope
|
| 37 |
+
threshold = st.slider("Select the minimum delay threshold (in hours)", 0, 12, 2)
|
| 38 |
+
scope = st.selectbox("Select the scope", ["All cars", "Connect cars"])
|
| 39 |
+
|
| 40 |
+
# Filtrer les données en fonction du scope
|
| 41 |
+
if scope == "Connect cars":
|
| 42 |
+
df = df[df['checkin_type'] == 'connect']
|
| 43 |
+
|
| 44 |
+
# Calculer le pourcentage de réservations affectées
|
| 45 |
+
affected_rentals = df[df['delay'] <= threshold*60].shape[0]
|
| 46 |
+
total_rentals = df.shape[0]
|
| 47 |
+
share_affected_rentals = affected_rentals / total_rentals * 100
|
| 48 |
+
|
| 49 |
+
# Afficher les résultats
|
| 50 |
+
st.write(f"Percentage of rentals potentially affected by the feature: {share_affected_rentals:.2f}%")
|
| 51 |
+
|
| 52 |
+
# Analyser les retards
|
| 53 |
+
late_checkins = df[df['late_checkin'] == True].shape[0]
|
| 54 |
+
total_checkins = df.shape[0]
|
| 55 |
+
share_late_checkins = late_checkins / total_checkins * 100
|
| 56 |
+
|
| 57 |
+
st.write(f"Share of late check-ins: {share_late_checkins:.2f}%")
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# Analyser les cas problématiques résolus
|
| 62 |
+
solved_cases = df[(df['delta'] < threshold*60) & (df['late_checkin'] == True)].shape[0]
|
| 63 |
+
st.write(f"Number of problematic cases solved by the feature: {solved_cases}")
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
fastapi
|
| 3 |
+
uvicorn
|
| 4 |
+
pandas
|
| 5 |
+
scikit-learn
|
| 6 |
+
joblib
|
| 7 |
+
mlflow
|
train.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# import argparse
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import time
|
| 5 |
+
import mlflow
|
| 6 |
+
from mlflow.models.signature import infer_signature
|
| 7 |
+
from sklearn.model_selection import train_test_split
|
| 8 |
+
|
| 9 |
+
from sklearn.pipeline import Pipeline
|
| 10 |
+
from sklearn.preprocessing import StandardScaler, OneHotEncoder
|
| 11 |
+
from sklearn.compose import ColumnTransformer
|
| 12 |
+
from sklearn.linear_model import LinearRegression
|
| 13 |
+
from sklearn.metrics import mean_squared_error
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
if __name__ == "__main__":
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
os.environ["APP_URI"] = "https://zacbl-getaround.hf.space"
|
| 20 |
+
|
| 21 |
+
# Set your variables for your environment
|
| 22 |
+
EXPERIMENT_NAME="Car_Price_Prediction"
|
| 23 |
+
|
| 24 |
+
print("training model...")
|
| 25 |
+
|
| 26 |
+
# Time execution
|
| 27 |
+
start_time = time.time()
|
| 28 |
+
|
| 29 |
+
# Call mlflow autolog
|
| 30 |
+
mlflow.sklearn.autolog()
|
| 31 |
+
|
| 32 |
+
with mlflow.start_run() as run:
|
| 33 |
+
|
| 34 |
+
# Import dataset
|
| 35 |
+
df = pd.read_csv("https://full-stack-assets.s3.eu-west-3.amazonaws.com/Deployment/get_around_pricing_project.csv", sep=";")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# Split the data into train and test :
|
| 39 |
+
X = df.drop(columns = 'rental_price_per_day', axis = 1)
|
| 40 |
+
y = df['rental_price_per_day']
|
| 41 |
+
|
| 42 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)
|
| 43 |
+
|
| 44 |
+
# Define the numerical and categorical columns :
|
| 45 |
+
num_cols = X.select_dtypes(include = ['int64', 'float64']).columns
|
| 46 |
+
cat_cols = X.select_dtypes(include = ['object', 'bool']).columns
|
| 47 |
+
# Train / test split
|
| 48 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# Define the pipeline :
|
| 52 |
+
num_pipeline = Pipeline([
|
| 53 |
+
('scaler', StandardScaler())
|
| 54 |
+
])
|
| 55 |
+
|
| 56 |
+
full_pipeline = ColumnTransformer([
|
| 57 |
+
('num', num_pipeline, num_cols),
|
| 58 |
+
('cat', OneHotEncoder(), cat_cols)
|
| 59 |
+
])
|
| 60 |
+
|
| 61 |
+
# Make a pipeline containing the full pipeline and the model :
|
| 62 |
+
model = Pipeline([
|
| 63 |
+
('preprocessing', full_pipeline),
|
| 64 |
+
('model', LinearRegression())
|
| 65 |
+
])
|
| 66 |
+
|
| 67 |
+
# Fit the model :
|
| 68 |
+
model.fit(X_train, y_train)
|
| 69 |
+
|
| 70 |
+
# Predict the price :
|
| 71 |
+
predictions = model.predict(X_test)
|
| 72 |
+
|
| 73 |
+
# Calculate the mean squared error :
|
| 74 |
+
mean_squared_error(y_test, predictions)
|
| 75 |
+
# Print r2 score :
|
| 76 |
+
model.score(X_test, y_test)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
print("...Done!")
|
| 80 |
+
print("Saving model...")
|
| 81 |
+
mlflow.sklearn.log_model(model, "model", signature=infer_signature(X_train, predictions))
|
| 82 |
+
print("...Model saved!")
|
| 83 |
+
print(f"---Total training time: {time.time()-start_time}")
|