🤖 Developer Cookbook - FASE 6: Fundamentos de IA/ML

Recetas prácticas para entender IA, sesgos éticos y limitaciones de los LLMs


📚 Tabla de Contenidos

  1. Receta 6.1: ML vs Deep Learning vs LLMs - ¿Qué son?
  2. Receta 6.2: Entrenamiento, Inferencia y Fine-tuning
  3. Receta 6.3: Sesgos Algorítmicos y Ética en IA
  4. Receta 6.4: Alucinaciones y Limitaciones de LLMs

Fundamentos de IA/ML

Receta 6.1: ML vs Deep Learning vs LLMs - ¿Qué son?

Definiciones:

TérminoDefiniciónEjemplo
Machine Learning (ML)Algoritmos que aprenden patrones de datos sin programación explícitaRegresión lineal, Random Forest
Deep Learning (DL)Subset de ML que usa redes neuronales profundasCNN para imágenes, RNN para texto
Large Language Models (LLM)Modelos de DL entrenados en texto masivoGPT-4, Claude, Gemini

Jerarquía:

Inteligencia Artificial (IA)
    └── Machine Learning (ML)
            └── Deep Learning (DL)
                    └── Large Language Models (LLM)

Comparación:

CaracterísticaML TradicionalDeep LearningLLMs
Datos necesarios🟢 Miles🟡 Millones🔴 Billones
Poder de cómputo🟢 CPU suficiente🟡 GPU recomendada🔴 GPU/TPU necesarias
Interpretabilidad🟢 Alta🟡 Media🔴 Baja
Feature engineering🔴 Manual🟡 Semi-automático🟢 Automático
Casos de usoClasificación simpleVisión, audioTexto, conversación

Ejemplo de diferencias:

# 1. Machine Learning Tradicional (Scikit-learn)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import pandas as pd

# Datos de entrenamiento
df = pd.read_csv('customer_data.csv')
X = df[['age', 'income', 'purchase_history']]
y = df['will_buy']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# Modelo tradicional ML
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)

# Predicción
prediction = model.predict([[35, 75000, 5]])
print(f"¿Comprará? {prediction[0]}")

# 2. Deep Learning (TensorFlow/Keras)
import tensorflow as tf
from tensorflow import keras

# Red neuronal para clasificación de imágenes
model = keras.Sequential([
    keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
    keras.layers.MaxPooling2D((2,2)),
    keras.layers.Conv2D(64, (3,3), activation='relu'),
    keras.layers.MaxPooling2D((2,2)),
    keras.layers.Flatten(),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# Entrenamiento
model.fit(train_images, train_labels, epochs=5)

# 3. LLMs (API de OpenAI/Anthropic)
import anthropic

client = anthropic.Anthropic(api_key="your-api-key")

# Usar LLM para clasificación de texto
message = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=100,
    messages=[{
        "role": "user",
        "content": "¿Este comentario es positivo o negativo? 'El producto es terrible, no funciona'"
    }]
)

print(message.content[0].text)

Receta 6.2: Entrenamiento, Inferencia y Fine-tuning

Definiciones:

Entrenamiento (Training):

# Ejemplo conceptual de entrenamiento
import torch
import torch.nn as nn

# Modelo simple
model = nn.Sequential(
    nn.Linear(10, 50),
    nn.ReLU(),
    nn.Linear(50, 1)
)

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.MSELoss()

# Loop de entrenamiento
for epoch in range(100):
    for batch_x, batch_y in train_loader:
        # Forward pass
        predictions = model(batch_x)
        loss = criterion(predictions, batch_y)
        
        # Backward pass (actualiza pesos)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    print(f"Epoch {epoch}, Loss: {loss.item()}")

Inferencia (Inference):

# Inferencia: usar modelo ya entrenado
model.eval()  # Modo evaluación (no training)

with torch.no_grad():  # No calcular gradientes
    prediction = model(new_data)
    print(f"Predicción: {prediction}")

Fine-tuning:

# Fine-tuning conceptual
# 1. Cargar modelo pre-entrenado
from transformers import AutoModelForSequenceClassification, AutoTokenizer

model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 2. Congelar capas tempranas (opcional)
for param in model.bert.encoder.layer[:8].parameters():
    param.requires_grad = False

# 3. Entrenar solo las últimas capas con tus datos
# (código de training aquí)

Comparación:

AspectoEntrenamientoFine-tuningInferencia
Tiempo🔴 Días/semanas🟡 Horas/días🟢 Segundos
Costo🔴 $$$$🟡 $$🟢 $
Datos necesarios🔴 Millones🟡 Miles🟢 0
Actualiza pesos✅ Sí✅ Sí❌ No
GPU necesaria✅ Sí✅ Sí🟡 Opcional

Receta 6.3: Sesgos Algorítmicos y Ética en IA

¿Qué son los sesgos algorítmicos? Cuando un modelo de IA produce resultados sistemáticamente injustos o discriminatorios hacia ciertos grupos.

Tipos de sesgos:

  1. Sesgo en datos de entrenamiento:
# Ejemplo: Dataset de contratación sesgado
training_data = [
    {"name": "John", "experience": 5, "hired": True},
    {"name": "Michael", "experience": 5, "hired": True},
    {"name": "Mary", "experience": 5, "hired": False},
    {"name": "Jennifer", "experience": 5, "hired": False},
]
# Problema: El modelo aprenderá a preferir hombres
  1. Sesgo de representación:
# Dataset de reconocimiento facial
face_dataset = {
    "white_males": 10000,  # Sobre-representado
    "white_females": 8000,
    "black_males": 500,    # Sub-representado
    "black_females": 300,
}
# Resultado: Peor performance en grupos minoritarios
  1. Sesgo de confirmación:
# Sistema de recomendación
def recommend_news(user_history):
    # Problema: Solo recomienda noticias similares
    # Crea cámara de eco
    return similar_articles(user_history)

Cómo detectar y mitigar sesgos:

import pandas as pd
from sklearn.metrics import confusion_matrix
import numpy as np

def analyze_fairness(model, X_test, y_test, sensitive_attribute):
    """
    Analizar si modelo es justo entre diferentes grupos
    """
    # Hacer predicciones
    y_pred = model.predict(X_test)
    
    # Agrupar por atributo sensible (ej: género, raza)
    groups = X_test[sensitive_attribute].unique()
    
    results = {}
    for group in groups:
        # Filtrar por grupo
        mask = X_test[sensitive_attribute] == group
        y_true_group = y_test[mask]
        y_pred_group = y_pred[mask]
        
        # Calcular métricas
        from sklearn.metrics import accuracy_score, precision_score
        
        results[group] = {
            'accuracy': accuracy_score(y_true_group, y_pred_group),
            'precision': precision_score(y_true_group, y_pred_group),
            'sample_size': len(y_true_group),
            'positive_rate': y_pred_group.mean()
        }
    
    # Comparar disparidad entre grupos
    df_results = pd.DataFrame(results).T
    print("\n📊 Análisis de equidad:")
    print(df_results)
    
    # Calcular disparate impact
    max_positive_rate = df_results['positive_rate'].max()
    min_positive_rate = df_results['positive_rate'].min()
    disparate_impact = min_positive_rate / max_positive_rate
    
    print(f"\n⚖️ Disparate Impact: {disparate_impact:.2f}")
    print("   (< 0.8 indica posible discriminación)")
    
    return df_results

# Uso
# analyze_fairness(model, X_test, y_test, sensitive_attribute='gender')

Principios éticos:

class EthicalAIChecklist:
    """Checklist de ética para proyectos de IA"""
    
    @staticmethod
    def evaluate_project(project_info):
        checks = {
            "transparency": "¿Los usuarios saben que están interactuando con IA?",
            "explainability": "¿Puedes explicar por qué el modelo tomó una decisión?",
            "fairness": "¿El modelo trata a todos los grupos equitativamente?",
            "privacy": "¿Proteges los datos personales adecuadamente?",
            "accountability": "¿Hay un humano responsable de las decisiones?",
            "safety": "¿Has evaluado posibles daños?",
            "human_oversight": "¿Hay supervisión humana en decisiones críticas?",
        }
        
        print("🤔 Checklist Ético de IA:\n")
        for key, question in checks.items():
            answer = input(f"{question} (s/n): ")
            if answer.lower() != 's':
                print(f"⚠️ ADVERTENCIA: Considera revisar {key}")
        
        print("\n✅ Evaluación ética completada")

# Uso
# EthicalAIChecklist.evaluate_project(my_ai_project)

Casos de uso reales con problemas éticos:

CasoProblemaSolución
Sistema de contratación de AmazonDiscriminaba contra mujeresRetirado y rediseñado
Reconocimiento facialPeor accuracy en personas no-blancasDiversificar dataset
Crédito financieroNegaba préstamos a minoríasAuditoría algorítmica
Predicción criminalSesgado contra afroamericanosRevisión humana obligatoria

Receta 6.4: Alucinaciones y Limitaciones de LLMs

¿Qué son las alucinaciones? Cuando un LLM genera información falsa o inventada con confianza, como si fuera real.

Tipos de alucinaciones:

from anthropic import Anthropic

client = Anthropic(api_key="your-api-key")

# 1. Alucinaciones factuales
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=100,
    messages=[{
        "role": "user",
        "content": "¿Cuándo murió el presidente Joe Biden?"
    }]
)
# Problema: Puede inventar una fecha (Joe Biden está vivo)

# 2. Alucinaciones de fuentes
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=200,
    messages=[{
        "role": "user",
        "content": "Dame la cita exacta donde Einstein dijo 'La imaginación es más importante que el conocimiento' con la fuente"
    }]
)
# Problema: Puede inventar una fuente específica

# 3. Alucinaciones en código
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=300,
    messages=[{
        "role": "user",
        "content": "Muéstrame cómo usar la función pandas.magical_transform()"
    }]
)
# Problema: Esta función no existe, pero puede inventar documentación

Cómo detectar alucinaciones:

def detect_hallucination_risks(prompt: str) -> dict:
    """
    Identificar si un prompt tiene riesgo alto de alucinaciones
    """
    risks = {
        'factual_claims': False,
        'specific_dates': False,
        'citations_needed': False,
        'technical_specifics': False,
        'statistics': False,
    }
    
    # Detectar patrones de riesgo
    import re
    
    # Preguntas sobre hechos específicos
    if re.search(r'(cuándo|dónde|quién|qué año)', prompt.lower()):
        risks['factual_claims'] = True
    
    # Solicitudes de fechas
    if re.search(r'\b(fecha|día|año|mes)\b', prompt.lower()):
        risks['specific_dates'] = True
    
    # Solicitudes de citas o fuentes
    if re.search(r'(cita|fuente|referencia|estudio)', prompt.lower()):
        risks['citations_needed'] = True
    
    # APIs o funciones específicas
    if re.search(r'(función|método|API|clase)\s+\w+\.\w+', prompt):
        risks['technical_specifics'] = True
    
    # Estadísticas
    if re.search(r'(porcentaje|estadística|cuántos)', prompt.lower()):
        risks['statistics'] = True
    
    # Evaluar riesgo total
    risk_count = sum(risks.values())
    
    return {
        'risks': risks,
        'risk_level': 'HIGH' if risk_count >= 3 else 'MEDIUM' if risk_count >= 1 else 'LOW',
        'recommendation': 'Verificar respuesta' if risk_count >= 1 else 'Probablemente seguro'
    }

# Uso
prompt = "¿Cuándo murió Einstein y qué dijo en su lecho de muerte?"
analysis = detect_hallucination_risks(prompt)
print(f"Riesgo: {analysis['risk_level']}")
print(f"Recomendación: {analysis['recommendation']}")

Técnicas para reducir alucinaciones:

# 1. Pedir al modelo que admita incertidumbre
prompt_safe = """
Responde la siguiente pregunta. Si no estás seguro o no tienes información
confiable, di explícitamente "No tengo información confiable sobre esto".

Pregunta: ¿Cuál es el PIB de Andorra en 2024?
"""

# 2. Proporcionar contexto (RAG - Retrieval Augmented Generation)
from typing import List

def query_with_context(question: str, context_docs: List[str]) -> str:
    """
    Usar RAG para reducir alucinaciones
    """
    # Construir prompt con contexto
    context = "\n\n".join([f"Documento {i+1}:\n{doc}" 
                           for i, doc in enumerate(context_docs)])
    
    prompt = f"""
Basándote ÚNICAMENTE en los siguientes documentos, responde la pregunta.
Si la información no está en los documentos, di "No puedo responder basándome en la información proporcionada".

{context}

Pregunta: {question}
"""
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=500,
        messages=[{"role": "user", "content": prompt}]
    )
    
    return response.content[0].text

# 3. Verificación de hechos post-generación
def verify_response(response: str, knowledge_base: dict) -> dict:
    """
    Verificar afirmaciones en la respuesta
    """
    import re
    
    # Extraer afirmaciones numéricas
    numbers = re.findall(r'\d+(?:\.\d+)?', response)
    dates = re.findall(r'\b(19|20)\d{2}\b', response)
    
    verification = {
        'response': response,
        'numbers_found': numbers,
        'dates_found': dates,
        'warnings': []
    }
    
    # Verificar contra knowledge base
    for claim, truth in knowledge_base.items():
        if claim in response and truth not in response:
            verification['warnings'].append(
                f"⚠️ Posible error: se menciona '{claim}' pero debería ser '{truth}'"
            )
    
    return verification

# Uso
knowledge_base = {
    "Einstein murió": "1955",
    "Newton nació": "1643"
}

response_text = "Einstein murió en 1950"  # Incorrecto
verification = verify_response(response_text, knowledge_base)
print(verification['warnings'])

Limitaciones de los LLMs:

class LLMLimitations:
    """
    Documentar limitaciones conocidas de LLMs
    """
    
    LIMITATIONS = {
        "knowledge_cutoff": {
            "descripción": "No saben eventos después de su fecha de entrenamiento",
            "ejemplo": "No saben quién ganó elecciones recientes",
            "solución": "Usar web search o proporcionar contexto actualizado"
        },
        "arithmetic": {
            "descripción": "Pueden cometer errores en cálculos complejos",
            "ejemplo": "234 * 567 = ? (puede equivocarse)",
            "solución": "Usar herramientas externas (calculadoras, Python)"
        },
        "context_length": {
            "descripción": "Límite en tokens que pueden procesar",
            "ejemplo": "No pueden analizar libro completo de 500 páginas",
            "solución": "Chunking, summarización incremental"
        },
        "inconsistency": {
            "descripción": "Pueden dar respuestas diferentes a la misma pregunta",
            "ejemplo": "Pregunta X puede tener respuesta Y o Z",
            "solución": "Temperature=0 para más determinismo, múltiples samples"
        },
        "no_learning": {
            "descripción": "No aprenden de conversaciones pasadas (sin fine-tuning)",
            "ejemplo": "Olvidan lo que les dijiste en chats anteriores",
            "solución": "Embeddings, vector DBs para 'memoria'"
        },
        "visual_limitations": {
            "descripción": "Limitaciones en tareas visuales complejas",
            "ejemplo": "Contar objetos exactos en imagen",
            "solución": "Modelos especializados (YOLO, SAM)"
        }
    }
    
    @classmethod
    def check_limitations(cls, task_description: str):
        """
        Verificar si una tarea puede tener limitaciones
        """
        print(f"🔍 Analizando tarea: '{task_description}'\n")
        
        warnings = []
        
        # Verificar diferentes limitaciones
        if any(keyword in task_description.lower() for keyword in ['calcular', 'multiplicar', 'sumar']):
            warnings.append(cls.LIMITATIONS['arithmetic'])
        
        if any(keyword in task_description.lower() for keyword in ['reciente', 'actual', 'hoy', '2024', '2025']):
            warnings.append(cls.LIMITATIONS['knowledge_cutoff'])
        
        if any(keyword in task_description.lower() for keyword in ['libro', 'documento largo', 'análisis completo']):
            warnings.append(cls.LIMITATIONS['context_length'])
        
        if warnings:
            print("⚠️ Limitaciones detectadas:\n")
            for i, warning in enumerate(warnings, 1):
                print(f"{i}. {warning['descripción']}")
                print(f"   Ejemplo: {warning['ejemplo']}")
                print(f"   Solución: {warning['solución']}\n")
        else:
            print("✅ No se detectaron limitaciones obvias")

# Uso
LLMLimitations.check_limitations("Analiza este documento de 1000 páginas y dame las ventas de enero 2025")