MFormations
Modern Python Engineering

Chapitre 9

09 - Machine Learning

> **Durée :** 3 semaines > **Objectif :** Maîtriser l'écosystème ML Python : scikit-learn, PyTorch, LangChain, MLflow, et déploiement.

Cours 09 : Machine Learning avec Python

1. scikit-learn : Pipelines et Préprocessing

1.1 Pipelines

Les pipelines enchaînent transformations et modèle final :

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV

numeric_features = ["age", "salary", "experience"]
categorical_features = ["department", "role"]

numeric_transformer = Pipeline([
    ("scaler", StandardScaler()),
])

categorical_transformer = Pipeline([
    ("encoder", OneHotEncoder(handle_unknown="ignore")),
])

preprocessor = ColumnTransformer([
    ("num", numeric_transformer, numeric_features),
    ("cat", categorical_transformer, categorical_features),
])

pipeline = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", RandomForestClassifier(n_estimators=100)),
])

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

1.2 Préprocessing avancé

from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler, RobustScaler,
    OneHotEncoder, LabelEncoder, OrdinalEncoder,
    KBinsDiscretizer, PolynomialFeatures, FunctionTransformer
)
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.feature_selection import SelectKBest, mutual_info_classif
from sklearn.decomposition import PCA

1.3 Modèles disponibles

from sklearn.linear_model import LogisticRegression, LinearRegression, Ridge, Lasso
from sklearn.svm import SVC, SVR
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from sklearn.ensemble import (
    RandomForestClassifier, GradientBoostingClassifier,
    AdaBoostClassifier, ExtraTreesClassifier, VotingClassifier, StackingClassifier
)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering

2. PyTorch : Deep Learning

2.1 Tensors

import torch

x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
x = torch.zeros(3, 4)
x = torch.ones(2, 3)
x = torch.randn(5, 10)
x = torch.arange(0, 10)

y = x @ x.T
z = x * 2
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = x.to(device)

2.2 Autograd

x = torch.randn(3, requires_grad=True)
y = x ** 2 + 2 * x + 1
z = y.sum()
z.backward()
print(x.grad)

2.3 nn.Module

import torch.nn as nn
import torch.nn.functional as F

class MLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, output_dim)
        self.dropout = nn.Dropout(0.3)
        self.bn1 = nn.BatchNorm1d(hidden_dim)

    def forward(self, x):
        x = F.relu(self.bn1(self.fc1(x)))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

model = MLP(784, 256, 10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

2.4 Data Loading

from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
    def __init__(self, data, labels):
        self.data = torch.tensor(data, dtype=torch.float32)
        self.labels = torch.tensor(labels, dtype=torch.long)

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]

dataset = CustomDataset(X_train, y_train)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)

2.5 Training Loop

from tqdm import tqdm

def train_epoch(model, dataloader, criterion, optimizer, device):
    model.train()
    total_loss = 0
    correct = 0
    total = 0
    for inputs, targets in tqdm(dataloader):
        inputs, targets = inputs.to(device), targets.to(device)
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        _, predicted = outputs.max(1)
        total += targets.size(0)
        correct += predicted.eq(targets).sum().item()
    return total_loss / len(dataloader), 100. * correct / total

for epoch in range(10):
    train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
    print(f"Epoch {epoch}: Loss={train_loss:.4f}, Acc={train_acc:.2f}%")

2.6 Sauvegarde

torch.save(model.state_dict(), "model.pth")
model.load_state_dict(torch.load("model.pth"))

3. LangChain

3.1 Installation

pip install langchain langchain-community langchain-openai

3.2 Chains

from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = PromptTemplate.from_template("Explique {topic} simplement.")
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.invoke({"topic": "gradient descent"})

3.3 RAG

from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA

splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = splitter.split_documents(documents)
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
qa = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever())
response = qa.invoke({"query": "Quelle est la capitale de la France?"})

3.4 Agents

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.tools import tool

@tool
def calculate(expr: str) -> str:
    return str(eval(expr))

agent = create_tool_calling_agent(llm, [calculate], prompt)
executor = AgentExecutor(agent=agent, tools=[calculate], verbose=True)

4. OpenAI SDK

from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    temperature=0.7,
)
print(response.choices[0].message.content)

5. MLflow

import mlflow
mlflow.set_experiment("mon-projet")
with mlflow.start_run():
    mlflow.log_param("lr", 0.001)
    mlflow.log_metric("accuracy", 0.95)
    mlflow.sklearn.log_model(model, "model")

6. Feature Engineering

import polars as pl
df = df.with_columns([
    pl.col("date").dt.year().alias("year"),
    pl.col("amount").rolling_mean(7).alias("ma7"),
    pl.col("amount").shift(1).alias("lag1"),
])

7. Déploiement FastAPI

from fastapi import FastAPI
from pydantic import BaseModel
import mlflow

app = FastAPI()
model = mlflow.pyfunc.load_model("models:/mon-modele/latest")

class Input(BaseModel):
    age: float
    salary: float

@app.post("/predict")
async def predict(data: Input):
    return {"prediction": model.predict([[data.age, data.salary]])[0]}

@app.get("/health")
async def health():
    return {"status": "ok"}

8. Évaluation

from sklearn.metrics import classification_report, confusion_matrix
print(classification_report(y_true, y_pred))
cm = confusion_matrix(y_true, y_pred)

9. Diagrammes

Diagramme en cours de génération...

10. Bonnes pratiques

  1. Fixer les seeds pour la reproductibilité
  2. Logger toutes les expériences dans MLflow
  3. Versionner les modèles dans le Model Registry
  4. Tester le preprocessing et l'API
  5. Monitorer le data drift en production