🍪 Mon site web utilise des cookies
Ces cookies sont uniquement destinés à des fins d'analyse du trafic sur mon site, et sont entièrement optionnels.
Le breuvage contre-attaque
Guillaume DEVANT et Corentin DUCLOUX
06/01/2024
vinatis.com pour trouver un breuvage.
On parle de moi ?
“Pour savoir qu’un verre était de trop, encore faut-il avoir scrapé son vin !” − Inconnu
scraping_functions.py ⇒ Le coeur du scraper
yarl.URL_INIT = URL.build(scheme="https", host="vinatis.com")
WHITE = "achat-vin-blanc"
RED = "achat-vin-rouge"
ROSE = "achat-vin-rose"
>>> URL_INIT / WHITE % {"page": 1, "tri": 7}
... URL('https://vinatis.com/achat-vin-blanc?page=1&tri=7')create_session crée une session HTML avec un User-Agent et un Proxy aléatoire, pouvant changer entre les requêtes.@random_waiter(min, max) permettant de générer un temps d’attente aléatoire entre les deux bornes spécifiées entre chaque requête GET pour éviter d’envoyer trop de requêtes dans un laps de temps réduit.create_all_wine_urls permet de créer l’ensemble des liens href.export_wine_links permet d’exporter ces liens dans un fichier CSV.create_json et récupérer les pages brutes en HTML.scraping du module mystical_soup va permettre d’extraire toutes les informations intéressantes de la page brute et renvoyer la dataclass Vin sérialisable en JSON. Exemple d’un Vin et ses caractéristiques sérialisés en JSON :
{
"name": "PINOT NOIR 2019 LAS PIZARRAS - ERRAZURIZ",
"capacity": "0,75 L",
"price": "94,90 €",
"price_bundle": null,
"characteristics": "Vin Rouge / Chili / Central Valley / Aconcagua Valley DO / 13,5 % vol / 100% Pinot noir",
"note": null,
"keywords": [
"Elégance",
"Finesse",
"Harmonie"
],
"others": null,
"picture": "https://www.vinatis.com/67234-detail_default/pinot-noir-2019-las-pizarras-errazuriz.png",
"classification": null,
"millesime": "2019",
"cepage": "100% Pinot noir",
"gouts": "Rouge Charnu et fruité",
"par_gouts": "Puissant",
"oeil": "Robe rubis aux reflets violets.",
"nez": "Nez complexe sur la griotte, les épices et les champignons (truffe).",
"bouche": "Bouche fruitée et florale. Tanins structurés, élégants et fins. finale harmonieuse et persistante.",
"temperature": "8-10°C",
"service": "En bouteille ou en carafe",
"conservation_1": "2026",
"conservation_2": "A boire et à garder",
"accords_vins": "Apéritif, Entrée, Charcuterie, Viande rouge, Viande blanche, Volaille, Gibier, Champignon, Barbecue, Cuisine du monde, Fromage, Dessert fruité, Dessert chocolaté",
"accords_reco": "Gigot d'agneau aux herbes de Provence; Tikka massala; Plateau de fromages."
}Mais ce JSON brut doit être nettoyé et considérablement restructuré !
polars 🐻 et non pas pandas 🐼 pour le faire.bear_cleaner.py.super_pipe permet de chainer toutes les transformations dans un pipeline propre pour structurer notre Dataframe.(4006,40) prêt pour le Machine Learning
“2024 sera un millésime français !” − Emmanuel Macron
models.pyprediction.pysklearn
OneHotEncoder(), Imputation NA, MinMaxScaler()optimisation_script.py on optimise les hyperparamètres des modèles et on récupère sous forme de CSV :
Modèle,Score Test,Score Entrainement,Ecart-Type Test,Ecart-Type Train,Paramètres,Score Test data,Mode
Random Forest,0.934,0.941,0.007,0.007,"{'entrainement__max_depth': 9, 'entrainement__n_estimators': 30, 'imputation__strategy': 'median'}",0.9301745635910225,classification
K Neighbors,0.954,0.965,0.012,0.003,"{'entrainement__n_neighbors': 5, 'imputation__strategy': 'median'}",0.9600997506234414,classification
Réseaux de neurones,0.976,0.997,0.007,0.001,"{'entrainement__hidden_layer_sizes': (100,), 'entrainement__max_iter': 1000, 'entrainement__solver': 'adam', 'imputation__strategy': 'median'}",0.9800498753117207,classification
Boosting,0.975,1.0,0.009,0.0,"{'entrainement__learning_rate': 0.5, 'entrainement__n_estimators': 200, 'imputation__strategy': 'median'}",0.9812967581047382,classification
Ridge,0.979,0.983,0.009,0.002,"{'entrainement__alpha': 0.015625, 'imputation__strategy': 'mean'}",0.9812967581047382,classification
Support Vector,0.981,0.992,0.008,0.002,"{'entrainement__C': 3.281341424030552, 'imputation__strategy': 'median'}",0.9825436408977556,classificationprediction_script.py on réalise les prédictions avec tous les modèlesname,type,random_forest,boosting,ridge,knn,mlp,support_vector
LES CARLINES 2021 - MAS HAUT BUIS,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge
LA BARGEMONE ROSE 2022 - COMMANDERIE DE LA BARGEMONE,Vin Rosé,Vin Blanc,Vin Rosé,Vin Rosé,Vin Rosé,Vin Rosé,Vin Rosé
TEMPRANILLO 2021- VEGA DEMARA,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge
CHÂTEAUNEUF DU PAPE - ALCHIMIE 2020 - DOMAINE DES 3 CELLIER,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge,Vin Rouge🕵 Framework utilisé : streamlit
duckdb : La base de données qui fait “coin coin” 🦆
def db_connector() -> DuckDBPyConnection:
"""Se connecte à la base de données."""
connection = duckdb.connect(database=":memory:")
return connection:memory: ⇒ Base de données in-memoryml_trigger qui se charge d’éxécuter l’ensemble des scripts d’export.Voici un schéma du processus d’ingestion des tables :
Lancement de l’application, 2 Méthodes.
Depuis le lien de l’application déployée sur le cloud streamlit :
Code certifié conforme par l’Agent Smith∗
∗ L’Agent Smith tient par ailleurs à préciser qu’il n’a reçu aucun pot-de-vin de notre part pour ce diagnostic malgré son enrichissement personnel fulgurant…
Note
mypy va nous permettre d’effectuer ce contrôle (static type checking), c’est à dire de vérifier si les valeurs assignées aux variables, les arguments passés aux fonctions et les valeurs de retour correspondent aux types attendus.
Exemple avec la fonction model_rf du module models.py :
"""`model_rf`: Effectue une recherche exhaustive (Cross-Validation) des meilleurs paramètres
en utilisant une Random Forest. Les paramètres optimisés sont :
- n_estimators
- max_depth
---------
`Parameters`
--------- ::
x_train (pd.DataFrame): # L'ensemble d'entrainement
y_train (pd.Series): # La variable à prédire
mode (str): # regression | classification
`Raises`
--------- ::
ValueError: # Une erreur est levée quand le mode est invalide
`Returns`
--------- ::
GridSearchCV
`Example(s)`
---------
>>> model_rf(x_train=X_train, y_train=y_train, mode = "regression")
... Entrainement du modèle : Random Forest
... GridSearchCV(estimator=Pipeline(steps=[('imputation', SimpleImputer()),
... ('echelle', MinMaxScaler()),
... ('entrainement',
... RandomForestRegressor())]),
... n_jobs=-1,
... param_grid={'entrainement__max_depth': range(1, 10),
... 'entrainement__n_estimators': range(10, 50, 10),
... 'imputation__strategy': ['mean', 'median',
... 'most_frequent']},
... return_train_score=True)
"""Gestion des dépendances : poetry simplifie la gestion des dépendances en utilisant un fichier de configuration pyproject.toml. Il permet de spécifier les dépendances directes et les dépendances de développement requises pour le projet.
Environnement Virtuel : venv isolé pour le projet, aidant à maintenir un environnement de développement propre et évitant les conflits entre les versions des packages.
Installation de dépendances : Facilite l’installation des dépendances définies dans le fichier de configuration en utilisant la commande poetry install.
Séparation des composants du projet :
├───data
│ ├───🍷vins.json
│ ├───💾wine_links.csv
│ └───tables
│ ├───💾pred_classification.csv
│ ├───💾pred_regression.csv
│ ├───💾result_ml_classification.csv
│ └───💾result_ml_regression.csv
│ └───💾importance.csv
├───src
│ └───📦modules
│ ├───⚙app
│ │ ├───🐍st_functions.py
│ │ ├───🐍st_plots.py
│ │ ├───🐍st_selectors.py
│ │ ├───🐍st_tables.py
│ │ └───🐍st_tables.py
│ ├───⚙ml_models
│ │ ├───🐍importance_script.py
│ │ ├───🐍models.py
│ │ ├───🐍optimisation_script.py
│ │ ├───🐍prediction_script.py
│ │ └───🐍prediction.py
│ ├───⚙scraping
│ │ ├───🐍mystical_soup.py
│ │ ├───🐍page_scraper.py
│ │ ├───🐍scraping_functions.py
│ │ ├───🐍vin_dataclass.py
│ │ └───🐍wine_scraper.py
│ ├───🐍ml_trigger.py
│ ├───🐍scraping_trigger.py
│ ├───🐍bear_cleaner.py
│ └───🐍utils.py
├───🐳Dockerfile
├───🧙♂️poetry.lock
├───📍pyproject.toml
├───📘README.md
└───🐍streamlit_app.py
Pourquoi utiliser Docker ?
Isolation : Docker permet d’isoler l’application, ses dépendances et son environnement d’exécution dans un conteneur. Cela signifie que l’application s’exécute avec ses propres ressources et dépendances sans affecter l’environnement hôte.
Portabilité : Une fois que l’image Docker est créée, elle peut être exécutée sur n’importe quel système prenant en charge Docker, offrant une portabilité élevée.
Comment ? ⇒ Dockerfile
Docker assure la reproductibilité en permettant à n’importe qui de construire et d’exécuter le même conteneur à partir des spécifications définies dans le Dockerfile.
Dockerfile :FROM python:3.10-slim-buster
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry \
&& poetry config virtualenvs.create false \
&& poetry install --no-dev --no-interaction --no-ansi
COPY streamlit_app.py .
COPY src ./src
COPY data ./data
COPY img ./img
RUN addgroup --system app \
&& adduser --system --group app
USER app
EXPOSE 8501
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
ENTRYPOINT ["python", "-m", "streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]Il faut tout d’abord s’assurer d’avoir téléchargé Docker Desktop avant toute chose.
Une fois installé, l’image est construite en exécutant la commande suivante dans un terminal :
Une fois la création de l’image terminée, on peut consulter la taille de celle-ci avec :
Ensuite, pour lancer le conteneur Docker avec l’utilisateur app sur le port initial (8501) de streamlit, il suffit de faire :
🎉 Une fois le conteneur lancé, on le voit apparaitre dans Docker Desktop. Pour accéder à l’application, il faut se rendre sur http://localhost:8501/.
On ne sait pas pourquoi on a fait tout ça, car nous voulions simplement trouver une bouteille pour fêter notre anniversaire, et on se retrouve avec une application d’analyse de données qui ne nous aide en aucun cas à trouver notre breuvage…😵
scikit-learn