Naive Bayes y Árboles de Decisión: Explicación Exhaustiva
Naive Bayes
Fundamentos del Algoritmo
Naive Bayes es un algoritmo de clasificación probabilística basado en el Teorema de Bayes, que establece la relación entre probabilidades condicionales. El teorema se expresa matemáticamente como:
Donde:
- P(y|X): Probabilidad posterior - probabilidad de la clase y dado las características X
- P(X|y): Verosimilitud - probabilidad de las características X dada la clase y
- P(y): Probabilidad prior de la clase y
- P(X): Evidencia marginal
Asunción “Naive” de Independencia
La característica distintiva del algoritmo es la asunción “naive” (ingenua) de que todas las características son condicionalmente independientes entre sí, dada la clase. Esto significa:
Esta asunción simplifica enormemente los cálculos, convirtiendo el teorema de Bayes en:
Excelencia en Clasificación de Texto
Naive Bayes demuestra una efectividad excepcional en tareas de clasificación de texto como filtrado de spam, análisis de sentimientos y categorización de documentos. Su eficacia se debe a:
- Manejo eficiente de alta dimensionalidad: Los textos generan espacios de características muy grandes
- Robustez con datos dispersos: Los documentos de texto típicamente contienen solo una fracción del vocabulario total
- Velocidad de entrenamiento y predicción: Especialmente importante con grandes volúmenes de texto
Ejemplo de Código para Clasificación de Texto
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
import pandas as pd
# Datos de ejemplo
datos = {
'texto': [
'Este es un excelente producto',
'Muy malo, no lo recomiendo',
'Fantástico, superó mis expectativas',
'Terrible calidad, muy decepcionante'
],
'sentimiento': ['positivo', 'negativo', 'positivo', 'negativo']
}
df = pd.DataFrame(datos)
# Vectorización del texto
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(df['texto'])
y = df['sentimiento']
# División de datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
# Entrenamiento del modelo
modelo = MultinomialNB()
modelo.fit(X_train, y_train)
# Predicción
predicciones = modelo.predict(X_test)
Problema de Laplace y Smoothing
El problema de Laplace o de probabilidad cero surge cuando una característica no aparece en el conjunto de entrenamiento para una clase específica. Esto resulta en P(x_i|y) = 0, lo que hace que toda la probabilidad posterior se vuelva cero.
Laplace Smoothing (Suavizado de Laplace)
El suavizado de Laplace resuelve este problema añadiendo un valor α (típicamente α = 1) a cada conteo:
Donde |V| es el tamaño del vocabulario.
Ejemplo de Código con Laplace Smoothing
from sklearn.naive_bayes import MultinomialNB
# Aplicar suavizado de Laplace con alpha=1
modelo_suavizado = MultinomialNB(alpha=1.0)
modelo_suavizado.fit(X_train, y_train)
# El parámetro alpha controla el suavizado
# alpha=0: sin suavizado (puede causar problemas de probabilidad cero)
# alpha=1: suavizado de Laplace estándar
# alpha>1: suavizado más agresivo
Árboles de Decisión
Algoritmo Interpretable Basado en Reglas
Los árboles de decisión son algoritmos de aprendizaje supervisado que crean un modelo predictivo mediante una estructura jerárquica de reglas de decisión. Su principal fortaleza radica en su interpretabilidad, permitiendo visualizar y entender el proceso de toma de decisiones.
Componentes del Árbol
Los árboles de decisión están compuestos por varios elementos estructurales:
- Nodo Raíz: Representa todo el conjunto de datos inicial
- Nodos Internos: Puntos de decisión donde se aplican reglas de división
- Hojas: Nodos terminales que contienen la predicción final
- Ramas: Conexiones entre nodos que representan las diferentes decisiones
Ejemplo de Estructura Básica
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn import tree
# Cargar datos
iris = load_iris()
X, y = iris.data, iris.target
# Crear el árbol de decisión
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(X, y)
# Visualizar el árbol
plt.figure(figsize=(12,8))
tree.plot_tree(clf, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True)
plt.show()
Métricas de Pureza
Para construir un árbol eficaz, se utilizan métricas que evalúan la “pureza” de los nodos, determinando las mejores divisiones.
Índice de Gini
El Índice de Gini mide la probabilidad de clasificar incorrectamente un elemento seleccionado aleatoriamente:
Donde p_i es la proporción de muestras que pertenecen a la clase i en el nodo t.
Entropía
La Entropía cuantifica el desorden o incertidumbre en un conjunto de datos:
Ganancia de Información
La Ganancia de Información mide la reducción en entropía después de una división:
Implementación de Métricas de Pureza
import numpy as np
from collections import Counter
def calcular_gini(y):
"""Calcula el índice de Gini para un conjunto de etiquetas"""
if len(y) == 0:
return 0
conteos = Counter(y)
probabilidades = [conteos[clase]/len(y) for clase in conteos]
gini = 1 - sum([p**2 for p in probabilidades])
return gini
def calcular_entropia(y):
"""Calcula la entropía para un conjunto de etiquetas"""
if len(y) == 0:
return 0
conteos = Counter(y)
probabilidades = [conteos[clase]/len(y) for clase in conteos]
entropia = -sum([p * np.log2(p) for p in probabilidades if p > 0])
return entropia
def ganancia_informacion(y_padre, y_izq, y_der):
"""Calcula la ganancia de información de una división"""
n = len(y_padre)
n_izq, n_der = len(y_izq), len(y_der)
entropia_padre = calcular_entropia(y_padre)
entropia_ponderada = (n_izq/n) * calcular_entropia(y_izq) + \
(n_der/n) * calcular_entropia(y_der)
return entropia_padre - entropia_ponderada
# Ejemplo de uso
etiquetas = ['A', 'A', 'B', 'B', 'C']
print(f"Gini: {calcular_gini(etiquetas):.3f}")
print(f"Entropía: {calcular_entropia(etiquetas):.3f}")
Construcción: División Recursiva Minimizando Impureza
El proceso de construcción del árbol sigue un enfoque top-down y divide y vencerás:
- Selección del Mejor Atributo: Se evalúan todos los atributos disponibles usando métricas de pureza
- División Recursiva: El nodo se divide basándose en el mejor atributo
- Criterios de Parada: El proceso se detiene cuando se cumple alguna condición
Algoritmo de Construcción
def construir_arbol(X, y, profundidad=0, max_profundidad=5):
"""Construcción recursiva de un árbol de decisión"""
# Criterios de parada
if profundidad >= max_profundidad or len(set(y)) == 1:
# Crear nodo hoja con la clase más común
return {'prediccion': Counter(y).most_common(1)}
mejor_ganancia = 0
mejor_atributo = None
mejor_umbral = None
# Buscar la mejor división
for atributo in range(X.shape):
valores_unicos = np.unique(X[:, atributo])
for umbral in valores_unicos:
# Dividir los datos
izq_mask = X[:, atributo] <= umbral
der_mask = X[:, atributo] > umbral
if sum(izq_mask) == 0 or sum(der_mask) == 0:
continue
# Calcular ganancia de información
ganancia = ganancia_informacion(y, y[izq_mask], y[der_mask])
if ganancia > mejor_ganancia:
mejor_ganancia = ganancia
mejor_atributo = atributo
mejor_umbral = umbral
# Si no hay ganancia, crear nodo hoja
if mejor_ganancia == 0:
return {'prediccion': Counter(y).most_common(1)}
# Crear división
izq_mask = X[:, mejor_atributo] <= mejor_umbral
der_mask = X[:, mejor_atributo] > mejor_umbral
return {
'atributo': mejor_atributo,
'umbral': mejor_umbral,
'izquierda': construir_arbol(X[izq_mask], y[izq_mask],
profundidad + 1, max_profundidad),
'derecha': construir_arbol(X[der_mask], y[der_mask],
profundidad + 1, max_profundidad)
}
Ventajas de los Árboles de Decisión
Interpretabilidad
La principal ventaja es su transparencia y facilidad de comprensión. Los árboles proporcionan reglas claras que pueden ser seguidas por humanos.
Manejo de Valores Faltantes
Los árboles de decisión pueden manejar naturalmente valores faltantes mediante divisiones sustitutos:
from sklearn.tree import DecisionTreeClassifier
import numpy as np
# Datos con valores faltantes (NaN)
X = np.array([[1, 2], [np.nan, 3], [4, np.nan], [5, 6]])
y = np.array([0, 1, 0, 1])
# El árbol maneja automáticamente los valores faltantes
clf = DecisionTreeClassifier()
clf.fit(X, y)
# Predicción con valores faltantes
nuevo_dato = [[np.nan, 5]]
prediccion = clf.predict(nuevo_dato)
Desventajas: Tendencia al Overfitting
Los árboles de decisión son propensos al sobreajuste, especialmente cuando crecen demasiado profundos. Esto puede llevar a una pobre generalización en datos no vistos.
Técnicas de Poda (Pruning)
Para mitigar el overfitting, se aplican técnicas de poda:
Pre-poda (Early Stopping)
from sklearn.tree import DecisionTreeClassifier
# Parámetros para controlar el crecimiento del árbol
clf = DecisionTreeClassifier(
max_depth=5, # Profundidad máxima
min_samples_split=10, # Mínimo de muestras para dividir
min_samples_leaf=5, # Mínimo de muestras en hojas
max_features='sqrt' # Número máximo de características
)
Post-poda (Cost Complexity Pruning)
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import validation_curve
import matplotlib.pyplot as plt
# Entrenar árbol completo
clf = DecisionTreeClassifier(random_state=42)
clf.fit(X_train, y_train)
# Obtener valores de alpha para poda
path = clf.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas
# Evaluar diferentes valores de alpha
train_scores, val_scores = validation_curve(
DecisionTreeClassifier(random_state=42), X_train, y_train,
param_name='ccp_alpha', param_range=ccp_alphas,
cv=5, scoring='accuracy'
)
# Seleccionar el mejor alpha
mejor_alpha = ccp_alphas[np.argmax(val_scores.mean(axis=1))]
# Crear árbol podado
clf_podado = DecisionTreeClassifier(ccp_alpha=mejor_alpha, random_state=42)
clf_podado.fit(X_train, y_train)
Comparación Práctica: Naive Bayes vs Árboles de Decisión
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
import numpy as np
# Generar datos sintéticos
X, y = make_classification(n_samples=1000, n_features=10,
n_informative=5, n_redundant=2,
random_state=42)
# Modelos a comparar
modelos = {
'Naive Bayes': GaussianNB(),
'Árbol de Decisión': DecisionTreeClassifier(max_depth=10, random_state=42)
}
# Evaluación cruzada
for nombre, modelo in modelos.items():
scores = cross_val_score(modelo, X, y, cv=5, scoring='accuracy')
print(f"{nombre}:")
print(f" Precisión media: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})")
print(f" Precisiones individuales: {scores}")
print()
Aplicaciones Recomendadas
Naive Bayes es ideal para:
- Clasificación de texto (spam, sentimientos, categorización)
- Datos con alta dimensionalidad
- Cuando se requiere rapidez en entrenamiento y predicción
- Datasets pequeños a medianos
Árboles de Decisión son ideales para:
- Cuando se necesita interpretabilidad del modelo
- Datos con relaciones no lineales complejas
- Manejo de valores faltantes sin preprocesamiento
- Problemas donde las reglas de decisión deben ser explícitas
Ambos algoritmos representan enfoques fundamentalmente diferentes pero complementarios en el arsenal del aprendizaje automático, cada uno con sus fortalezas específicas según el contexto del problema y los requisitos del proyecto.
Hi :)
Matemáticas
Vectores
Álgebra Lineal
Geometría Analítica
Producto Punto
Espacios Vectoriales
Ortogonalidad
Normalización
Funciones
Álgebra
Composición de Funciones
Función Inversa
Combinación de Funciones
Transformaciones Gráficas
Aplicaciones Económicas
Interés Compuesto
Proporcionalidad
R
Data
Machine Learning
Aprendizaje Supervisado
Inteligencia Artificial
Clasificación
Regresión
Deep Learning