Queda una pregunta viva des del tancament de M10, i és la que més diners mou: quant estoc demanar de cara a Sant Jordi? Les lliçons anteriors van descriure el passat (dissabtes forts, abril desbocat, Faust aparadorista); demanar estoc exigeix projectar el futur. Aquest salt — de descriure a predir — és l'aprenentatge automàtic (machine learning, ML), i scikit-learn és la seva llibreria de referència en Python. Aquesta lliçó el presenta de veritat i sense fum: què significa que una màquina "aprengui", dos casos complets amb les dades de Papyrus, i els advertiments que el màrqueting sol saltar-se. Nivell introductori honest: un mapa fiable del territori, no el territori sencer.
Contingut
- Què és el ML de veritat: regles escrites vs patrons apresos
- Tipus d'aprenentatge: supervisat i no supervisat
- El flux scikit-learn: split → fit → predict → avaluar
- Cas complet 1 — regressió: predir les vendes diàries
- La decisió: la comanda de Sant Jordi
- Cas complet 2 — classificació: comprarà aquest soci aquest mes?
- Sobreajust: memoritzar no és aprendre
- El que NO hem cobert (el teu proper mapa)
- Un advertiment ètic
Què és el ML de veritat
Tot el curs fins aquí ha estat programació clàssica: tu escrius les regles. El vendre() de M5 descomptava estoc perquè tu vas escriure self.estoc -= unitats; el clean_preu de Django (M10) rebutjava negatius perquè tu ho vas decidir; la tarifa de soci és una fórmula que va teclejar l'Anna. Regles explícites, dades que entren, respostes que surten.
El ML inverteix el contracte: li dones dades amb les seves respostes, i la màquina dedueix les regles.
| Programació clàssica (M1-M10) | Aprenentatge automàtic | |
|---|---|---|
| Tu aportes | Regles + dades | Dades + respostes conegudes |
| La màquina produeix | Respostes | Regles (un model) |
| Exemple Papyrus | preu * 1.04 * 0.95 |
"els dissabtes es ven el doble" — deduït del CSV |
| Quan brilla | La regla es coneix i és exacta | La regla existeix però ningú no sap escriure-la |
| Quan falla | Casos no previstos per tu | Futur diferent del passat, dades escasses o brutes |
Ningú no sap escriure a mà la fórmula exacta de "quant ven Papyrus un dimarts de març". Però el patró és a vendes_2026.csv, i un model el pot extreure. Això — ajustar funcions a dades — és tot el truc. No hi ha comprensió ni intenció: hi ha estadística aplicada amb bona enginyeria.
Tipus d'aprenentatge
| Tipus | Què s'aprèn | Exemple Papyrus |
|---|---|---|
| Supervisat — regressió | Predir un número a partir d'exemples etiquetats | Quantes unitats vendrem demà? (cas 1) |
| Supervisat — classificació | Predir una categoria | Aquest soci comprarà aquest mes: sí/no? (cas 2) |
| No supervisat | Estructura sense etiquetes (grups, anomalies) | Agrupar socis per hàbits de compra sense dir-li quins grups buscar |
Aquest mòdul practica els dos supervisats. Del no supervisat te n'emportes el concepte; i existeix un quart món — l'aprenentatge profund (deep learning, xarxes neuronals: el motor dels chatbots i la visió per computador) — que honestament queda fora d'aquest curs: exigeix més matemàtiques, més dades i més màquina del que Papyrus necessita.
El flux scikit-learn
El millor de scikit-learn és que tots els models s'usen igual — mateixa API per a una regressió lineal que per a un bosc aleatori:
flowchart LR
A["Dades<br>X (característiques)<br>y (resposta)"] --> B["train_test_split<br>80% entrenar / 20% provar"]
B --> C["fit(X_train, y_train)<br>el model aprèn"]
C --> D["predict(X_test)<br>prediu dades que MAI ha vist"]
D --> E["avaluar<br>MAE, accuracy...<br>ens en fiem?"]
E -.->|no| A
La peça que ho canvia tot és train_test_split: s'aparta un 20 % de les dades abans d'entrenar i s'avalua només amb elles. Per què? Perquè avaluar amb les dades d'entrenament és deixar que l'estudiant es corregeixi el seu propi examen amb les respostes al davant: nota alta garantida, aprenentatge no garantit. Hi tornarem al sobreajust.
Cas complet 1 — regressió: predir les vendes diàries
Pregunta: quantes unitats vendrà Papyrus un dia donat? Dades: vendes_2026.csv agregat per dia (181 dies), amb característiques que les lliçons anteriors ja van assenyalar com a rellevants: el mes, si és cap de setmana (el dissabte de 11-03) i si és Sant Jordi (el pic de 11-02/11-04).
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
df = pd.read_csv("dades/vendes_2026.csv", parse_dates=["data"])
# 1) De vendes soltes a un dataset diari (pandas, 11-03)
diari = df.groupby("data")["unitats"].sum().reset_index()
diari["mes"] = diari["data"].dt.month
diari["es_cap_de_setmana"] = (diari["data"].dt.dayofweek >= 5).astype(int)
diari["es_sant_jordi"] = (diari["data"] == "2026-04-23").astype(int)
X = diari[["mes", "es_cap_de_setmana", "es_sant_jordi"]] # caracteristiques (features)
y = diari["unitats"] # el que volem predir
# 2) Apartar l'examen ABANS d'estudiar
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3) Aprendre i 4) predir sobre el mai vist
model = LinearRegression()
model.fit(X_train, y_train)
prediccions = model.predict(X_test)
# 5) Avaluar
print(f"MAE: {mean_absolute_error(y_test, prediccions):.2f} unitats/dia")
# MAE: 1.10 unitats/diaEl MAE (mean absolute error) diu que, de mitjana, el model s'equivoca en ±1.1 unitats al dia — sobre una mitjana real d'unes 2.9, no és brillant, però per dimensionar una comanda mensual basta, perquè els errors diaris es compensen en sumar. Mirem quines regles va deduir:
for nom, coef in zip(X.columns, model.coef_):
print(f"{nom:>17}: {coef:+.2f}")
print(f"{'intercepte':>17}: {model.intercept_:+.2f}")Interpretació amb humilitat, que és com s'interpreten sempre els coeficients:
- Dia laborable base: ~1.9 + 0.11·mes ≈ 2-3 unitats. Coherent amb l'observat.
- Cap de setmana: +2.4 unitats/dia. El dissabte de 11-03, ara quantificat com a regla.
- Sant Jordi: +71 unitats. Compte: aquest coeficient es va aprendre d'un únic dia en tot l'històric. El model no "entén" Sant Jordi; va memoritzar un punt. Amb dos o tres anys de dades seria una regla; amb un, és una anotació fiable però fràgil.
- I l'advertiment general: coeficient no és causa.
es_cap_de_setmanano provoca vendes; captura que la gent passeja el dissabte. Si l'ajuntament converteix el carrer en zona de vianants un dimarts, el model no se n'assabenta.
La decisió: la comanda de Sant Jordi
El model prediu qualsevol dia futur construint-ne les característiques. Abril de 2027: 30 dies, 9 de cap de setmana, i Sant Jordi cau en divendres:
import numpy as np
abril_2027 = pd.DataFrame({
"mes": [4] * 30,
"es_cap_de_setmana": [1 if d in dies_cap_de_setmana_abril_2027 else 0 for d in range(1, 31)],
"es_sant_jordi": [1 if d == 23 else 0 for d in range(1, 31)],
})
pred_diaria = model.predict(abril_2027)
print(f"Prediccio abril 2027: {pred_diaria.sum():.0f} unitats")
# Prediccio abril 2027: 172 unitats172 unitats — la mateixa zona que l'abril real de 2026 (168), que és el raonable amb un sol any d'històric. I aquí la part més important de la lliçó: el model no decideix; l'Anna decideix. L'Anna agafa la predicció, hi afegeix un marge de seguretat (~10 %, perquè quedar-se sense llibres el 23 d'abril costa més que sobreestocar uns exemplars) i la reparteix segons la quota que cada títol va tenir a l'abril (11-03):
| Títol | Quota abril | Comanda (sobre 190) |
|---|---|---|
| El Quixot | 35 % | 67 |
| L'Odissea | 30 % | 57 |
| Hamlet | 23 % | 44 |
| Faust | 12 % | 22 |
Comanda final: 190 unitats. La pregunta que va obrir el mòdul, tancada: no amb un pressentiment, sinó amb un històric net (11-03), un pic visualitzat (11-04) i una projecció amb el seu error conegut (±1.1/dia). El criteri del marge, el repartiment i l'última paraula continuen sent humans — així ha de ser.
Cas complet 2 — classificació: comprarà aquest soci aquest mes?
La Marta vol saber a quins socis enviar el butlletí del club de lectura. Pregunta de classificació: soci × mes → compra (1) o no (0)? El dataset socis_mesos.csv surt de creuar les fitxes de socis (LLUIS-001, MARTA-002, PAU-003...) amb vendes_2026.csv i els logs de la web: 40 socis × 6 mesos = 240 files, amb va_comprar_mes_anterior, visites_web_mes, mesos_com_a_soci i l'etiqueta compra — que només és 1 en 36 files (15 %): la majoria dels mesos, la majoria no compra. Aquest desequilibri és la clau de tot el que segueix.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, recall_score
socis = pd.read_csv("dades/socis_mesos.csv")
X = socis[["va_comprar_mes_anterior", "visites_web_mes", "mesos_com_a_soci"]]
y = socis["compra"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=42, stratify=y)
clf = LogisticRegression() # classificacio malgrat el nom; mateixa API: fit/predict
clf.fit(X_train, y_train)
pred = clf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, pred):.2f}") # Accuracy: 0.88Un 88 % d'encerts! Abans de celebrar-ho, la trampa de l'accuracy: com que el 85 % de les files són "no compra", un "model" que respongui sempre no encerta el 85 % sense aprendre res. El nostre 88 % és a penes millor que aquesta pedra. La mètrica que destapa l'engany:
Dels socis que sí que van comprar, el model només en va detectar el 43 %. Per al butlletí de la Marta — on no detectar un comprador probable és el que surt car — aquest model encara no serveix tal qual. Lliçó permanent: amb classes desequilibrades, l'accuracy menteix; demana sempre recall/precision (i els seus remeis: class_weight="balanced", més dades, millors característiques). DecisionTreeClassifier s'entrena idèntic (mateixa API) i a més es pot llegir com a regles si-aleshores — bon experiment següent.
Sobreajust: memoritzar no és aprendre
Dos estudiants davant l'examen de M9: l'un va memoritzar les solucions dels exercicis resolts; l'altre va entendre els conceptes. Amb les preguntes exactes del llibre, el memorió treu un 10; amb preguntes noves, s'enfonsa — i el que va entendre, aprova. El sobreajust (overfitting) és el model memorió: clava les dades d'entrenament ajustant-se fins i tot al seu soroll, i fracassa amb dades noves, que és l'únic que importa.
- Com es detecta: gran diferència entre el rendiment en train i en test (per això existeix
train_test_split). Un arbre de decisió sense límit de profunditat pot donar 100 % en train i desplomar-se en test. - Què l'afavoreix: models molt flexibles + poques dades. El nostre coeficient de Sant Jordi, après d'un sol dia, és un petit sobreajust assumit i documentat.
- Què el mitiga: més dades, models més simples, i les tècniques de la taula següent.
El que NO hem cobert: el teu proper mapa
Honestedat de tancament — aquesta lliçó és la porta, no la casa:
| Tema | Què resol | Busca'l com a |
|---|---|---|
| Validació creuada | Avaluar sense dependre de la sort d'un split | cross_val_score, k-fold |
| Enginyeria de característiques | Bones variables valen més que bons models | feature engineering, OneHotEncoder |
| Escalat i pipelines | Encadenar preprocessament + model sense fuites de dades | StandardScaler, Pipeline |
| Ajust d'hiperparàmetres | Triar la configuració del model amb mètode | GridSearchCV |
| Models de conjunt | Més precisió combinant arbres | RandomForest, gradient boosting |
| Aprenentatge profund | Imatges, text, veu — una altra escala de dades i còmput | xarxes neuronals, PyTorch/TensorFlow |
Un advertiment ètic
El cas 2 prediu el comportament de persones, i aquí el llistó puja: un model que decideix a qui es contacta, a qui se li ofereix un descompte o — en dominis seriosos — a qui se li concedeix un crèdit, hereta els biaixos de les seves dades i s'equivoca amb nom i cognoms. La regla a Papyrus i fora d'ella: els models recomanen, les persones decideixen; tota decisió amb efectes sobre persones requereix supervisió humana, i qui la pateix mereix poder preguntar per què. Que la Marta revisi la llista abans d'enviar el butlletí no és burocràcia: és la diferència entre usar una eina i delegar-hi.
Errors Comuns i Consells
- Avaluar sobre les dades d'entrenament. L'error fundacional: nota inflada garantida. L'split va abans de qualsevol
fit, sempre. - Refiar-se de l'accuracy amb classes desequilibrades. El 88 % que amaga un recall del 43 %. Compara sempre contra la línia base ximple (predir sempre la classe majoritària) i mira recall/precision.
- Llegir coeficients com a causes.
es_cap_de_setmana: +2.41descriu una associació en aquestes dades; no és una llei física ni un mecanisme. Canvia el context i canvia el coeficient. - Extrapolar sense dir-ho. Predir l'abril de 2027 amb dades d'un sol any és una aposta raonable declarada com a tal. Predir el desembre (campanya de Nadal que el model mai no va veure) seria inventar.
- Oblidar
random_state. Sense fixar-lo, cada execució dona un split diferent i els teus resultats no són reproduïbles — l'equivalent ML dels tests no deterministes de M9. - Començar pel model. El 80 % del resultat és a la pregunta i les dades (11-01 a 11-03). Un
LinearRegressionamb bones característiques guanya un model sofisticat amb dades brutes.
Exercicis
- Afegeix al cas 1 la característica
es_dissabte(separada d'es_cap_de_setmana, que passa a seres_diumenge), reentrena i compara el MAE. Amb el que saps de 11-03 (dissabte 130 vs diumenge 97), què esperes que passi amb els coeficients? - Per al cas 2, calcula la línia base: l'accuracy d'un "model" que sempre prediu "no compra", sobre el mateix
y_test. Escriu-lo sense scikit-learn, amb pandas o NumPy pur. Quant millora realmentLogisticRegression? - Conceptual: l'Anna proposa entrenar el model de regressió amb TOTES les dades (sense apartar test) "perquè així aprèn de més exemples" i usar-lo directament per a la comanda de 2027. Dona un argument a favor, l'argument en contra, i la pràctica estàndard que reconcilia tots dos.
Solucions
-
El MAE baixa lleugerament (≈1.10 → ≈1.02): separar dissabte i diumenge dona al model llibertat per assignar-los pesos diferents, i les dades l'aprofiten — coeficient del dissabte (~+3.1) més gran que el del diumenge (~+1.8), exactament l'asimetria 130/97 quediari["es_dissabte"] = (diari["data"].dt.dayofweek == 5).astype(int) diari["es_diumenge"] = (diari["data"].dt.dayofweek == 6).astype(int) X = diari[["mes", "es_dissabte", "es_diumenge", "es_sant_jordi"]] # ... mateix split/fit/avaluaciogroupbyva mostrar a 11-03. Bona característica nova = coneixement del negoci codificat. -
base = (y_test == 0).mean() print(f"Linia base 'sempre no': {base:.2f}") # 0.85y_test == 0és una màscara booleana (11-02) i la seva mitjana és la proporció deTrue. La logística treu 0.88 davant de 0.85: 3 punts de millora real, no 88. Reportar models al costat de la seva línia base hauria de ser llei. - A favor: amb dades escasses, cada fila compta, i un 20 % apartat és informació desaprofitada per al model final. En contra: sense test no tens cap mesura honesta de l'error — no sabries si la comanda es basa en un model decent o en un de sobreajustat; volar sense instruments. Pràctica estàndard: avaluar amb split (o validació creuada) per conèixer l'error esperat i, un cop validat l'enfocament, reentrenar amb totes les dades per produir el model definitiu. S'obté el millor de tots dos: la mesura i les dades.
Conclusió
El mòdul 11 va complir la seva promesa: les preguntes que la web de M10 va deixar a l'aire tenen avui resposta amb dades. NumPy va posar el motor (vectorització i màscares booleanes), pandas el va convertir en respostes — el dissabte com a rei de la setmana amb 130 unitats, L'Odissea regnant a la web, Faust amb 210 visites i un 11.9 % de conversió que assenyala el seu preu —, Matplotlib ho va fer visible al panell de la rebotiga amb la fletxa clavada a Sant Jordi, i scikit-learn va tancar el cercle: una regressió amb MAE de ±1.1 unitats/dia va projectar 172 unitats per a l'abril de 2027, i l'Anna — no el model: l'Anna — va signar una comanda de 190 repartida per quotes, amb marge i amb criteri. També t'emportes les vacunes: l'accuracy que menteix amb classes desequilibrades, el sobreajust del memorió, els coeficients que no són causes i la regla d'or que els models recomanen i les persones decideixen. Mira ara la prestatgeria completa: un paquet papyrus amb models, errors i tests (M1-M9), una web amb Flask i Django que el serveix al món (M10) i un laboratori de dades que l'entén (M11). No hi falta cap peça; falta assemblar-les en un sistema que se sostingui sencer, de la primera línia a l'informe final. Això és el projecte final, i és exactament el mòdul 12.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
