Classificació
- Classificació binària
- Regressió logística amb scikit-learn
- Avaluar un classificador
- Classificació multiclasse
A l’aprenentatge supervisat hem vist com predir un número. Ara volem predir una categoria: si un correu és spam o no, si un client comprarà, quin dígit hi ha en una imatge. Les eines són les mateixes (un model amb pesos, una funció de cost i el descens del gradient), però la sortida del model ha de ser una probabilitat.
Classificació binària
Imagina que vols separar dos tipus d’objectes. La classificació binària combina les característiques de manera semblant a la regressió lineal, i hi traça una línia de separació (en 2D) o un pla de separació (en 3D) que divideix l’espai en dues regions, una per a cada classe. Aquest model s’anomena regressió logística.
Com funciona la regressió logística
La regressió lineal ens dona un número qualsevol: 7, -0.2, -150. Per classificar, en canvi, volem una probabilitat, un valor entre 0 i 1 que ens digui com de probable és que l’exemple pertanyi a la classe 1. La regressió logística hi arriba en dos passos.
Farem servir un exemple al llarg de tota la secció: volem predir si un client comprarà un producte (classe 1) o no (classe 0) a partir de la seva edat i els seus ingressos.
Pas 1: calcular una puntuació
Primer, combinem les característiques de l’exemple igual que a la regressió lineal:
\[ z = w \cdot x + b \]
On:
- \(x\) són les característiques d’entrada de l’exemple
- \(w\) són els pesos que ajustem durant l’entrenament
- \(b\) és el terme independent (bias)
- \(z\) és la puntuació resultant
Suposem que l’entrenament ja ha trobat els pesos \(w_1 = 0.1\) per a l’edat i \(w_2 = 0.00003\) per als ingressos, i el terme independent \(b = -5\). Per a un client de 30 anys que guanya 60000 € l’any:
\[ z = 0.1 \cdot 30 + 0.00003 \cdot 60000 - 5 = 3 + 1.8 - 5 = -0.2 \]
La puntuació pot ser qualsevol número, però el seu signe ja ens diu quina classe és més probable:
- \(z > 0\): més probable la classe 1
- \(z < 0\): més probable la classe 0
- Com més lluny del zero, més segura és la predicció
En el nostre exemple, \(z = -0.2\) indica que el client probablement no comprarà, però el model no n’està gaire segur.
Pas 2: convertir-la en probabilitat
Per passar de la puntuació a una probabilitat fem servir la funció sigmoide:
\[ g(z) = \frac{1}{1 + e^{-z}} \]
On \(e\) és el nombre d’Euler (aproximadament 2.718) i \(g(z)\) és la probabilitat que l’exemple pertanyi a la classe 1. Sigui quin sigui \(z\), el resultat sempre queda entre 0 i 1:
| \(z\) | \(g(z)\) | Interpretació |
|---|---|---|
| -10 | 0.00005 | Gairebé segur classe 0 |
| -2 | 0.12 | Probablement classe 0 |
| 0 | 0.5 | Dubte total |
| 2 | 0.88 | Probablement classe 1 |
| 10 | 0.99995 | Gairebé segur classe 1 |
Per això la sigmoide té forma d’S: puja suaument al voltant del zero i s’aplana a prop de 0 i de 1 sense arribar-hi mai.
En el nostre exemple, \(g(-0.2) \approx 0.45\): hi ha un 45% de probabilitat que el client compri, i un 55% que no ho faci.
Per què la sigmoide?
Hi ha moltes funcions que comprimeixen un número entre 0 i 1. La sigmoide no s’ha triat perquè “queda bé”, sinó per una interpretació concreta de la puntuació \(z\).
Si un equip guanya el 80% dels partits, diem que té unes oportunitats de “4 contra 1”, perquè 0.8 / 0.2 = 4. En general, les oportunitats (odds) divideixen la probabilitat que passi una cosa entre la probabilitat que no passi, i si en prenem el logaritme natural obtenim el logaritme de les oportunitats (log odds):
\[ \text{log odds} = \ln\frac{p}{1-p} \]
On \(p\) és la probabilitat que l’exemple pertanyi a la classe 1.
| Probabilitat | Oportunitats | Log odds |
|---|---|---|
| 0.2 | 0.25 (1 contra 4) | -1.39 |
| 0.5 | 1 (1 contra 1) | 0 |
| 0.8 | 4 (4 contra 1) | 1.39 |
El log odds val 0 quan hi ha dubte total, és simètric i va de -∞ a +∞, exactament com la puntuació \(z\). Per això la regressió logística interpreta \(z\) com el log odds de la classe 1, i l’entrenament ajusta \(w\) i \(b\) perquè aquesta interpretació sigui correcta.
Si aïllem \(p\) d’aquesta interpretació, surt la sigmoide:
\[ z = \ln\frac{p}{1-p} \quad\Rightarrow\quad e^z = \frac{p}{1-p} \quad\Rightarrow\quad p = \frac{e^z}{1 + e^z} = \frac{1}{1 + e^{-z}} \]
En paraules senzilles: la sigmoide és la funció inversa del log odds. Converteix la puntuació, llegida com a log odds, en la probabilitat corresponent.
El model complet
Característiques → [Puntuació z] → [Sigmoide] → [Probabilitat]
x → w·x + b → g(z) → 0 a 1
Frontera de decisió
Per decidir la classe, la regla habitual és dir “classe 1” si la probabilitat passa de 0.5. Com que la sigmoide val exactament 0.5 quan z = 0, això és el mateix que dir “classe 1” si la puntuació z és positiva.
La frontera de decisió és el conjunt de punts on z = 0, és a dir, on el model dubta del tot. Amb dues característiques és una recta al pla: els punts d’un costat es classifiquen com a 1, i els de l’altre com a 0.
Fronteres no lineals
Per obtenir fronteres corbes (cercles, el·lipses…) podem afegir característiques polinòmiques, igual que a la regressió polinòmica. Per exemple, si fem servir \( x_1^2 \) i \( x_2^2 \) en lloc de \( x_1 \) i \( x_2 \), la frontera pot ser un cercle.
L'àlgebra de la frontera
Amb dues característiques, la frontera és:
\[ w_1 x_1 + w_2 x_2 + b = 0 \]
que és l’equació d’una recta. Si fem servir \( x_1^2 \) i \( x_2^2 \), la frontera és \( w_1 x_1^2 + w_2 x_2^2 + b = 0 \). En el cas senzill \( w_1 = w_2 = 1 \), queda \( x_1^2 + x_2^2 = -b \): un cercle de radi \( \sqrt{-b} \). Amb pesos diferents o termes creuats (\( x_1 x_2 \)) es poden obtenir el·lipses, paràboles o formes més complexes.
Funció de cost
Per a classificació no fem servir l’error quadràtic de la regressió, sinó la pèrdua logarítmica (entropia creuada, cross-entropy). La idea és castigar molt les prediccions molt segures i equivocades: si el model diu 0.99 per a la classe 1 i en realitat és 0, el cost és enorme. A més, amb aquesta funció el cost torna a tenir forma de bol, i el descens del gradient funciona bé.
- Quan l’etiqueta real és 1 (corba verda): si la predicció s’apropa a 1, el cost és baix; si s’apropa a 0, el cost es dispara.
- Quan l’etiqueta real és 0 (corba vermella): passa el contrari.
La fórmula
\[ L(f(x_i), y_i) = \begin{cases} -\log(f(x_i)) & \text{si } y_i = 1 \\ -\log(1 - f(x_i)) & \text{si } y_i = 0 \end{cases} \]
- f(xᵢ): la probabilitat que el model dona a la classe 1
- yᵢ: la classe real
El cost J és la mitjana d’aquesta pèrdua sobre tots els exemples.
Regressió logística amb scikit-learn
import numpy as np
from sklearn.linear_model import LogisticRegression
X = np.array([[0.5, 1.5], [1, 1], [1.5, 0.5], [3, 0.5], [2, 2], [1, 2.5]])
y = np.array([0, 0, 0, 1, 1, 1])
model = LogisticRegression()
model.fit(X, y)
# Prediccions sobre les mateixes dades d'entrenament, només per veure què ha après.
# Per avaluar-lo de veritat cal un conjunt de test, com veurem a continuació.
print("Prediccions:", model.predict(X))
print("Probabilitats de la classe 1:", model.predict_proba(X)[:, 1])
El resultat sobre aquestes dades: els punts de cada classe i la frontera de decisió que ha après el model.
Avaluar un classificador
La trampa de la taxa d’encerts
La taxa d’encerts (accuracy) és el percentatge de prediccions correctes: si el model n’encerta 90 de 100, és del 90%.
Pot enganyar molt. Imagina un detector de frau en un banc on només 1 de cada 100 transaccions és fraudulenta. Un “model” que sempre diu “no és frau” encerta 99 de cada 100: un 99% de taxa d’encerts, sense detectar cap frau. Quan una classe és molt més freqüent que l’altra (classes desbalancejades), cal mirar els errors per separat.
Matriu de confusió
La matriu de confusió compta els quatre resultats possibles. Exemple amb 100 pacients, 10 dels quals estan malalts:
| Predicció: malalt | Predicció: sa | |
|---|---|---|
| Real: malalt | 8 (detectats) | 2 (escapats) |
| Real: sa | 5 (falses alarmes) | 85 (correctes) |
A partir d’aquests quatre números calculem dues mètriques:
\[ \text{Precisió} = \frac{\text{detectats}}{\text{detectats} + \text{falses alarmes}} = \frac{8}{13} = 0.62 \qquad \text{Recall} = \frac{\text{detectats}}{\text{detectats} + \text{escapats}} = \frac{8}{10} = 0.80 \]
En paraules senzilles:
- Precisió (precision): quan el model diu “malalt”, quantes vegades té raó?
- Recall: de tots els malalts reals, quants n’ha trobat?
El F1 les combina en un sol número, i baixa molt si una de les dues és baixa:
\[ \text{F1} = 2 \times \frac{\text{Precisió} \times \text{Recall}}{\text{Precisió} + \text{Recall}} = 2 \times \frac{0.62 \times 0.80}{0.62 + 0.80} \approx 0.70 \]
Quina és més important depèn del cost de cada error. En un diagnòstic mèdic, deixar escapar un malalt és greu: vigilem el recall. En un filtre de spam, enviar a la paperera un correu important és greu: vigilem la precisió.
El llindar és una elecció
La regressió logística retorna una probabilitat, i nosaltres decidim a partir de quin valor diem “classe 1”. El valor de 0.5 és el llindar per defecte, però no és una regla:
- Llindar més baix (per exemple, 0.3): marquem més casos com a positius. Escapen menys malalts (més recall), però hi ha més falses alarmes (menys precisió).
- Llindar més alt: menys falses alarmes, però escapen més casos.
El llindar es tria, com el grau d’un polinomi, amb el conjunt de validació.
Amb scikit-learn
Vegem-ho tot amb dades reals: el dataset breast_cancer de scikit-learn té 30 mesures de 569 tumors. Com que volem detectar els malignes, els marquem com a classe 1:
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X, y = load_breast_cancer(return_X_y=True)
y = 1 - y # al dataset, 0 és maligne: el girem perquè 1 sigui "maligne"
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
model = make_pipeline(StandardScaler(), LogisticRegression())
model.fit(X_train, y_train)
y_pred = model.predict(X_test) # llindar 0.5
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred)) # precisió, recall i F1 per classe
# Un llindar més baix (aquí el comparem amb el test només per veure l'efecte)
y_prob = model.predict_proba(X_test)[:, 1]
y_pred_03 = (y_prob > 0.3).astype(int)
print(confusion_matrix(y_test, y_pred_03))
[[71 1] llindar 0.5: 39 malignes detectats, 3 escapats, 1 falsa alarma
[ 3 39]]
[[71 1] llindar 0.3: 41 malignes detectats, 1 escapat, 1 falsa alarma
[ 1 41]]
scikit-learn ordena la matriu per classe (0, 1): la primera fila són els tumors benignes reals i la segona els malignes. Baixar el llindar a 0.3 troba dos malignes més sense cap falsa alarma nova. Amb altres dades, el preu sol ser més falses alarmes: per això el llindar es tria mirant el conjunt de validació.
stratify=y fa que el test tingui la mateixa proporció de malignes que el total (un 37%). En classificació és el que cal fer sempre, i encara més quan una classe és minoritària.
Com comparar classificadors amb tots els llindars alhora (corbes ROC i Precision-Recall) i un exemple complet de frau són a Avaluació de models.
Classificació multiclasse
Fins ara hem classificat en dues classes. Però sovint n’hi ha més: reconèixer quin dígit (de 0 a 9) hi ha en una imatge, o quina espècie de flor és.
La regressió softmax generalitza la regressió logística:
- La regressió logística calcula una puntuació z i la converteix en una probabilitat amb la sigmoide.
- La regressió softmax calcula una puntuació per a cada classe (amb els seus propis pesos) i les converteix totes alhora en probabilitats que sumen 1, amb la funció softmax.
Exemple: una imatge d’un “3” escrit a mà, amb 10 classes possibles (els dígits del 0 al 9).
| Dígit | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| Puntuació | 0.1 | -0.5 | 0.3 | 4.2 | 0.8 | -1.0 | 0.2 | -0.3 | 0.5 | 0.1 |
| Probabilitat | 0.02 | 0.01 | 0.03 | 0.85 | 0.04 | 0.01 | 0.02 | 0.01 | 0.03 | 0.02 |
La softmax fa tres coses:
- Converteix puntuacions qualssevol en valors entre 0 i 1.
- Fa que sumin exactament 1.
- Manté l’ordre: la puntuació més alta dona la probabilitat més alta.
El model prediu la classe amb més probabilitat: aquí, un 85% de confiança que és un “3”.
Per entrenar-lo es fa servir de nou l’entropia creuada, que ara només mira la probabilitat que el model ha donat a la classe correcta: com més baixa, més gran és la pèrdua.
Les fórmules
Per a cada classe j de les N possibles, el model calcula una puntuació:
\[ z_j = w_j \cdot x + b_j \]
i la softmax la converteix en probabilitat:
\[ a_j = \frac{e^{z_j}}{\sum_{k=1}^{N} e^{z_k}} \]
- wⱼ, bⱼ: els pesos i el biaix de la classe j
- aⱼ: la probabilitat que l’exemple sigui de la classe j
En paraules senzilles: elevem e a cada puntuació (així totes són positives) i dividim per la suma de totes, de manera que sumin 1.
La pèrdua per a un exemple que és de la classe j és:
\[ L = -\log(a_j) \]
Amb scikit-learn
LogisticRegression detecta sola quan hi ha més de dues classes i fa servir la softmax:
from sklearn.datasets import load_digits
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X, y = load_digits(return_X_y=True) # imatges de 8×8 píxels, dígits del 0 al 9
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)
print(model.predict(X_test[:5])) # classes predites
print(model.predict_proba(X_test[:1])) # 10 probabilitats que sumen 1
print(model.score(X_test, y_test)) # taxa d'encerts
Avaluar amb més de dues classes
La matriu de confusió passa a ser de N×N: cada fila és la classe real i cada columna la predita. Exemple amb tres flors:
Predicció
Rosa Tulipa Marguerida
Real Rosa 2 1 0
Tulipa 1 3 0
Marguerida 1 0 2
La diagonal són els encerts. Fora de la diagonal veiem quines classes es confonen entre elles: aquí, el model confon sobretot roses i tulipes.
La precisió, el recall i el F1 es calculen per a cada classe, tractant-la com “aquesta classe contra totes les altres”. classification_report els mostra tots. Com combinar-los en un sol número (mitjanes macro, micro i ponderada) és a Avaluació de models.
Les xarxes neuronals fan servir exactament la mateixa idea a la seva última capa.