O que é classificação multirrótulo
A classificação multirrótulo é um tipo de aprendizado supervisionado. Nela, cada exemplo pode pertencer a várias classes simultaneamente. Por exemplo, uma flor pode ter múltiplas características ao mesmo tempo. Diferentemente da classificação multiclasse, aqui as saídas não são exclusivas. O modelo aprende a prever um vetor binário de rótulos. Cada posição indica a presença ou ausência de um atributo. Esse problema é comum em biologia, textos e imagens. Ele é mais desafiador que a classificação tradicional. Contudo, muitas técnicas podem ser adaptadas para ele.
Primeiramente, os dados são rotulados com múltiplos alvos binários. Por exemplo, uma planta pode ser “venenosa” e “medicinal” ao mesmo tempo. Além disso, os rótulos podem apresentar correlações estatísticas. Essas correlações podem ser exploradas pelo modelo. A avaliação usa métricas como acurácia por rótulo. Outra métrica comum é o erro de cobertura (coverage error). Modelos simples tratam cada rótulo de forma independente. Modelos mais avançados capturam dependências entre eles. A escolha depende da complexidade do problema.
Arquitetura do modelo
A arquitetura mais usada é a rede neural multissaída. A camada de entrada recebe as características do exemplo. Em seguida, uma ou mais camadas ocultas processam os dados. A ativação comum nessas camadas é a ReLU. Finalmente, a camada de saída usa a função sigmoide. Cada neurônio de saída corresponde a um rótulo binário. A sigmoide é definida pela fórmula: \(\sigma(z) = \frac{1}{1 + e^{-z}}\). Ela transforma qualquer valor real em probabilidade entre 0 e 1. Um limiar (geralmente 0,5) decide se o rótulo é ativado.
Outra abordagem é o classificador de cadeia (Classifier Chain). Ele transforma o problema multirrótulo em uma sequência binária. Cada predição é usada como entrada para o próximo modelo. Assim, dependências entre rótulos são capturadas explicitamente. Contudo, a ordem dos rótulos influencia o resultado final. Por isso, múltiplas cadeias aleatórias são frequentemente usadas. A arquitetura MLP com saída sigmoide é mais simples. Ela é recomendada para iniciantes neste tópico. Sua implementação é direta no scikit-learn.
Hiperparâmetros e fórmulas
Os hiperparâmetros controlam o comportamento do modelo. O número de neurônios ocultos afeta a capacidade de aprendizado. A taxa de aprendizado determina o passo da atualização. O número de épocas é quantas vezes os dados são vistos. A regularização evita overfitting em redes neurais. O limiar de decisão pode ser ajustado após o treinamento. Esses parâmetros são otimizados com validação cruzada. Cada problema exige uma configuração diferente.
A função de perda é a entropia cruzada binária. Ela é calculada separadamente para cada rótulo: \(L_j = -\frac{1}{N}\sum_{i=1}^{N} [y_{ij} \log(\hat{y}_{ij}) + (1-y_{ij})\log(1-\hat{y}_{ij})]\). Nessa fórmula, \(y_{ij}\) é o rótulo verdadeiro. \(\hat{y}_{ij}\) é a probabilidade prevista pelo modelo. A perda total é a soma sobre todos os \(M\) rótulos: \(L = \sum_{j=1}^{M} L_j\). O gradiente descendente minimiza essa perda. A regra de atualização dos pesos é: \(w_{novo} = w_{velho} – \eta \frac{\partial L}{\partial w}\). Aqui, \(\eta\) é a taxa de aprendizado.
Enunciado do exemplo com flor íris (realista)
Você recebeu dados modificados da flor Íris com ruído. Cada flor pode ter múltiplos atributos biológicos ativos. Os atributos são: “pétala alongada”, “sépala larga” e “flor vistosa”. Os dados originais contêm medidas de pétala e sépala. Contudo, os rótulos agora têm 15% de ruído aleatório. Isso significa que flores similares podem ter rótulos diferentes. Seu objetivo é prever todos os atributos ativos para cada flor. Use uma rede neural com camada oculta de 8 neurônios. A saída deve usar ativação sigmoide para cada atributo. Divida os dados em treino (80%) e teste (20%). Normalize as características antes do treinamento. Exiba dois gráficos: correlação dos atributos e acurácias.
O código abaixo resolve este enunciado realisticamente. Ele roda no Google Colab sem necessidade de ajustes. A acurácia não será 100% devido ao ruído introduzido. Isso permite avaliar a real capacidade de generalização. Boa prática! A classificação multirrótulo é muito útil.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.neural_network import MLPClassifier from sklearn.multioutput import MultiOutputClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, hamming_loss import seaborn as sns # Carrega o dataset Iris iris = load_iris() X = iris.data # características: comprimento sépala, largura sépala, comprimento pétala, largura pétala n_samples = X.shape[0] np.random.seed(42) # Cria rótulos multirrótulo com regras suaves + ruído y = np.zeros((n_samples, 3), dtype=int) # Rótulo 0: pétala longa (> 3.0 cm) com 15% de ruído regra0 = (X[:, 2] > 3.0).astype(int) ruido0 = np.random.binomial(1, 0.15, n_samples) y[:, 0] = np.logical_xor(regra0, ruido0).astype(int) # Rótulo 1: sépala larga (> 3.0 cm) com 15% de ruído regra1 = (X[:, 1] > 3.0).astype(int) ruido1 = np.random.binomial(1, 0.15, n_samples) y[:, 1] = np.logical_xor(regra1, ruido1).astype(int) # Rótulo 2: flor vistosa (pétala > 2.5 E sépala > 2.8) com 20% de ruído regra2 = ((X[:, 2] > 2.5) & (X[:, 1] > 2.8)).astype(int) ruido2 = np.random.binomial(1, 0.20, n_samples) y[:, 2] = np.logical_xor(regra2, ruido2).astype(int) # Divisão treino-teste X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Normalização das características scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test) # MLP base com uma camada oculta de 8 neurônios e regularização mlp_base = MLPClassifier(hidden_layer_sizes=(8,), activation='relu', learning_rate_init=0.01, max_iter=1000, alpha=0.01, # regularização L2 early_stopping=True, random_state=42) # MultiOutputClassifier treina um MLP para cada rótulo modelo = MultiOutputClassifier(mlp_base, n_jobs=-1) modelo.fit(X_train, y_train) # Predição com limiar 0.5 y_pred_prob = modelo.predict_proba(X_test) y_pred = np.array([(prob[:, 1] > 0.5).astype(int) for prob in y_pred_prob]).T # Acurácia por atributo atributos = ['Pétala alongada', 'Sépala larga', 'Flor vistosa'] acuracia = [] print("=" * 50) print("Acurácia por atributo:") print("=" * 50) for i in range(3): acc = accuracy_score(y_test[:, i], y_pred[:, i]) acuracia.append(acc) print(f"{atributos[i]}: {acc:.3f}") # Perda Hamming (métrica específica para multirrótulo) h_loss = hamming_loss(y_test, y_pred) print(f"\nPerda Hamming (Hamming Loss): {h_loss:.3f}") print("(Quanto menor, melhor. Ideal seria próximo de 0.15-0.20)") # Gráfico 1: Matriz de correlação dos atributos verdadeiros corr = np.corrcoef(y.T) plt.figure(figsize=(6, 5)) sns.heatmap(corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, xticklabels=atributos, yticklabels=atributos, fmt='.2f') plt.title("Correlação entre os atributos biológicos (com ruído)") plt.tight_layout() plt.show() # Gráfico 2: Acurácias por atributo (gráfico de barras) plt.figure(figsize=(7, 5)) cores = ['#1f77b4', '#ff7f0e', '#2ca02c'] barras = plt.bar(atributos, acuracia, color=cores, edgecolor='black') plt.ylim(0, 1) plt.ylabel("Acurácia") plt.title("Desempenho do modelo por atributo (com ruído)") for bar, acc in zip(barras, acuracia): plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02, f'{acc:.3f}', ha='center', va='bottom', fontsize=11) plt.grid(axis='y', linestyle='--', alpha=0.7) plt.xticks(rotation=15) plt.tight_layout() plt.show() # Exemplo de predição para uma nova flor nova_flor = scaler.transform([[5.1, 3.5, 1.4, 0.2]]) # Iris setosa típica predicao = modelo.predict(nova_flor)[0] probabilidades = np.array([prob[0, 1] for prob in modelo.predict_proba(nova_flor)]) print("\n" + "=" * 50) print("Predição para uma nova flor (Iris setosa):") print("=" * 50) for i, atrib in enumerate(atributos): status = "ATIVO" if predicao[i] == 1 else "inativo" print(f"{atrib}: {status} (probabilidade: {probabilidades[i]:.3f})") |
Esse código corrigido introduz ruído realista nos rótulos. Agora a acurácia fica tipicamente entre 80% e 90%. Isso porque flores semelhantes podem ter rótulos diferentes. A perda Hamming é uma métrica específica para multirrótulo. Ela mede a fração de rótulos preditos incorretamente. O gráfico de correlação mostra relações menos perfeitas. A regularização (alpha=0.01) ajuda a evitar overfitting. O early_stopping interrompe o treinamento no momento certo. Este exemplo é muito mais realista para aprendizado. Parabéns por compreender a diferença fundamental!