Estimated reading time: 9 minutos
Construir modelos de Machine Learning (ML) no debería requerir la extracción de datos, la configuración de entornos y la unión de media docena de herramientas. En realidad, el flujo de trabajo tradicional de Python depende precisamente de estos aspectos: configuración, bibliotecas distintas y una implementación de modelos matizada. Sin embargo, si los datos de una empresa ya residen en BigQuery, como ocurre con muchas organizaciones, BigQuery ML permite el desarrollo de modelos directamente dentro del data warehouse. Esto plantea la pregunta central abordada en este artículo: ¿Cómo se compara este desarrollo dentro del data warehouse con los enfoques tradicionales?
En este artículo, compararemos BigQuery ML con el flujo de trabajo tradicional de Python construyendo un modelo de abandono de clientes en ambos entornos. Veremos los pros y los contras de cada enfoque, probando cómo BQML simplifica el preprocesamiento y la implementación. ¿Te preguntas si deberías cambiar el control granular de Python por la velocidad y la accesibilidad del machine learning basado en SQL? Aquí están las respuestas (¡con algo de código)!
Predicción del Abandono de Clientes: Las Dos Maneras
Comparamos ambos enfoques utilizando un conjunto de datos público de telecomunicaciones con aproximadamente 4.000 clientes. La tarea: predecir el abandono de clientes (si alguien cancela su servicio) basándose en patrones de uso, región e información de la cuenta. Ambos enfoques dividirán los datos en conjuntos de entrenamiento y prueba para evaluar el rendimiento del modelo en datos no vistos.

Ruta A: BigQuery ML
Crear un modelo en BigQuery ML es engañosamente simple, ya que abstrae la mayor parte de la complejidad y los costes indirectos, al igual que otras soluciones cloud gestionadas. BigQuery ML maneja automáticamente los valores NULL, divide los datos para el entrenamiento y la evaluación y, en este caso, realiza la codificación one-hot-encoding por nosotros. Nuestro conjunto de datos contiene una columna ‘state’ con valores categóricos como NY o WA. BigQuery ML convierte estas cadenas en características distintas que el modelo puede procesar.
Con la preparación de datos ya gestionada, la creación del modelo es tan simple como seleccionar una arquitectura y especificar la variable objetivo (abandono: sí o no). Aunque no es estrictamente necesario, excluir las variables redundantes o irrelevantes es un enfoque sensato y estándar; esto se realiza mediante una declaración excepcional.
CREAR MODELO `proyecto.conjunto_datos.nombre_modelo`
OPCIONES (
tipo_de_modelo = "LOGISTIC_REG",
columnas_etiqueta_de_entrada = ["churn"] --variable objetivo
) COMO
SELECCIONAR
* EXCEPTO (
id_cliente
cargo_total_dia
cargo_total_tarde
cargo_total_noche
cargo_total_internacional
)
DE `proyecto.conjunto_datos.nombre_tabla`
La evaluación en el conjunto de prueba (datos no vistos) se realiza con la siguiente estructura.
SELECIONAR *
DE ML.EVALUAR (MODELO `proyecto.conjunto_datos.nombre_modelo`)
Este enfoque simple produce un modelo con un rendimiento bastante malo. En este caso, la distribución del abandono no es proporcional; aproximadamente el 85% de los clientes no abandonan, por lo que el modelo por defecto predice «no abandono» para todos. Podemos forzar al modelo a sopesar estos datos de manera equitativa añadiendo auto_class_weights = True a las opciones.
Esto nos da una ligera mejora, pero la regresión logística parece alcanzar un techo aquí. Cambiar a una arquitectura más potente es tan sencillo como cambiar"LOGISTIC_REG" a "BOOSTED_TREE_CLASSIFIER". Esto finalmente produce un modelo con un rendimiento satisfactorio.
¿La conclusión clave? La iteración requiere cambios mínimos en el código; estás trabajando en SQL todo el tiempo, mientras que BigQuery ML hace mucho tras bastidores. La división de entrenamiento/prueba, la codificación one-hot-encoding e incluso el escalado de características numéricas (un enfoque estándar para la regresión logística), todas estas prácticas comunes que requerirían implementación explícita en los flujos de trabajo tradicionales se manejan automáticamente. Veamos cómo se compara esto con la ruta tradicional.
Ruta B: Python y el resto de la pila
El enfoque tradicional comienza con la configuración, si se ejecuta localmente, es necesario instalar Python y las bibliotecas relevantes. Esto se abstrae si se utilizan entornos gestionados como Google Colab o Workbench.
A continuación, es necesario mover los datos de BigQuery al entorno de desarrollo. Esto se puede hacer manualmente (exportando y luego cargando) o consultando directamente desde Python, lo que requiere la autenticación adecuada. El siguiente fragmento de código utiliza una clave JSON de cuenta de servicio.
from google.cloud import bigquery
import pandas as pd
import os
#set environment through JSON key and start client
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="path_to_key.json"
client = bigquery.Client(project="project")
query = '''
SELECT
* EXCEPT (
customer_id,
total_day_charge,
total_eve_charge,
total_night_charge,
total_intl_charge
)
FROM `project.dataset.table_name`
'''
df = client.query(query).to_dataframe()
Utilizamos una consulta que imita nuestra declaración BQML EXCEPT para evitar cargar la tabla completa. A diferencia de BigQuery ML, el flujo de trabajo tradicional requiere que la mayoría de las acciones sean explícitas:Manejar los valores NULL (este conjunto de datos no tiene ninguno, pero normalmente usarías pandas para descartarlos o imputarlos), dividir los conjuntos de entrenamiento/prueba, codificar las variables categóricas y escalar las características numéricas.
Todo esto es gestionado por BigQuery ML sin que se le instruya a hacerlo; en Python, toda la tubería de preprocesamiento es explícita.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import f1_score
import numpy as np
#separate features and target variables and split sets
y = df["churn"]
X = df.drop(columns=["churn"])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
#pre process categorical features and scale numerical variables
categorical_transformer = OneHotEncoder(handle_unknown="ignore")
numeric_transformer = StandardScaler()
preprocessor = ColumnTransformer(
transformers = [
("num", numeric_transformer, make_column_selector(dtype_include=np.number)),
("cat", categorical_transformer, make_column_selector(dtype_include=object)),
("bool", "passthrough", make_column_selector(dtype_include=bool))
])
#model pipeline and evaluation
model_pipeline = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", LogisticRegression())
])
model_pipeline.fit(X_train,y_train)
y_pred = model_pipeline.predict(X_test)
f1 = f1_score(y_test, y_pred)
print(f"F1-Score: {f1}")
Esta implementación es mucho más detallada que BigQuery ML. Produce una puntuación F1 de alrededor de 0.2. Para abordar el desequilibrio de clases, podemos añadir class_weight=’balanced’ a la llamada del modelo, lo que mejora el rendimiento a cerca de 0.5. Los valores no son exactos debido a la naturaleza de la mezcla aleatoria, pero se alinean con nuestros resultados de BigQuery ML. Curiosamente, el entrenamiento fue casi instantáneo en Python.
Cambiar a un árbol potenciado requiere ajustes adicionales. Los modelos de árbol no se benefician del escalado de características que habíamos aplicado, y los pesos de clase de XGBoost deben calcularse manualmente, a diferencia de la implementación anterior en scikit-learn.
from xgboost import XGBClassifier
scale_pos_weight = (y_train == False).sum() / (y_train == True).sum()
model_pipeline = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", XGBClassifier(scale_pos_weight=scale_pos_weight))
])
Esto produce una puntuación F1 de alrededor de 0.8, de nuevo con un tiempo de entrenamiento casi instantáneo. Si bien Python ofrece velocidad en conjuntos de datos pequeños, el flujo de trabajo requiere significativamente más conocimiento del dominio.
La Conclusión
Las diferencias son marcadas. BigQuery ML abstrae la complejidad que, de otro modo, requeriría trabajo manual: no hay movimiento de datos, el cambio de modelo es fluido y la preparación de datos es automática.
BigQuery ML capacita a los analistas con la creación rápida de prototipos. No está diseñado para científicos de datos que necesitan toda la granularidad que permite Python. Para los equipos sin profunda experiencia en ML, la compensación tiene sentido. Las matemáticas subyacentes siguen siendo consistentes; una regresión logística se desempeña igual independientemente de la implementación.
¿Qué hay de la Escala?
¿Qué pasa cuando los datos crecen?
Hicimos la prueba de rendimiento con un conjunto de datos más grande, de poco menos de 3 GB, aproximadamente 10.000 veces más grande que el original. El flujo de trabajo de BigQuery ML se mantuvo idéntico. El entrenamiento de la regresión logística tardó unos minutos, pero el proceso no cambió.
Sin embargo, el entorno de Python colapsó. El culpable más probable sería la codificación categórica con más valores únicos, que agotó la memoria disponible. Esto es solucionable asignando más RAM, optimizando el procesamiento con Dask o Polars, cambiando a clusters distribuidos de Spark, o incluso muestreando los datos. La cuestión aquí es que cada solución añade complejidad, alejándose cada vez más de un flujo de trabajo simple y repetible.
La idea clave es que BigQuery ML escala sin fricciones. La misma consulta SQL que funcionó con 4.000 filas también funciona con 50 millones. No hay cambios en el código, ni reescrituras arquitectónicas, ni decisiones de infraestructura. BigQuery ML simplemente funciona.
¿Qué hay de la Inferencia?
Entrenar es solo la mitad de la historia. En nuestro ejemplo de Python, para realmente usar el modelo, se necesita envolver el código en una API (como Flask o FastAPI), contenedores con Docker, y luego desplegarlo en un punto final invocable a través de Google Cloud Run o Kubernetes.
Con BigQuery ML, todo este proceso se omite. Para las predicciones en tiempo real, añadirmodel_registry = "vertex_ai" a las opciones de SQL exporta automáticamente el modelo a Vertex AI, donde puede ser desplegado directamente como un punto final REST. Sin desarrollo de API, sin contenedores, solo una sola línea de código.
Comparación General:
Reuniendo todos los resultados, las ventajas y desventajas quedan claras en una comparación lado a lado, ya que el verdadero coste de cada ruta se revela al tener en cuenta el tiempo del desarrollador, la escalabilidad y los costes de implementación.
| Conjunto de datos | Modelo | Puntuación BQML F1 | Tiempo BQML | Puntuación Python F1 | Tiempo Python |
| Pequeño (4k filas) | Regresión Logística con Peso de Clase | 0.517 | 30 segundos | ~0.5 | ~Instantáneo |
| Pequeño (4k filas) | Árbol Potenciado con Pesos de Clase | 0.789 | 6 min 38 segundos | ~0.8 | ~Instantáneo |
| Grande (~3GB) | Regresión Logística | 0.975 | 2 min 11 segundos | Fallida | Fallida |
Los resultados de Python (~) son aproximados debido a la división aleatoria de entrenamiento/prueba.
BigQuery ML vs. Python: ¿Qué deberías elegir?
Entonces, ¿qué camino deberías elegir? Desafortunadamente, la respuesta es la que a nadie le gusta: depende. Pero la siguiente tabla debería ayudarte a tomar una decisión informada.
| BigQuery ML | Python tradicional |
| Escalabilidad sin Esfuerzo: La misma consulta SQL escala de unas pocas a millones de filas sin cambios. | Fricción de Escalado: Requiere implementación específica para conjuntos de datos grandes (ej., re-arquitectura con Spark/Dask). |
| Flujo de Trabajo sin Fricción: Reside en el data warehouse. No hay movimiento de datos ni configuración. | Flujo de Trabajo con Altos Costes Indirectos: Requiere movimiento de datos y configuración del entorno. |
| Preprocesamiento Automatizado: Maneja automáticamente los pasos de preprocesamiento (ej., codificación one-hot, escalado de características, valores NULL). | Preprocesamiento Explícito: Debes codificar manualmente cada paso (ej.,StandardScaler, OneHotEncoder). |
| Simple Deployment: Un único comando SQL (model_registry) despliega el modelo automáticamente en un endpoint de Vertex AI. | Despliegue Matizado: Requiere construir una API (Flask), contenerizar (Docker) y desplegar en infraestructura (Cloud Run). |
| Menos Granularidad: Limitado a las arquitecturas de modelo soportadas por BQML. | Granularidad Total: Flexibilidad inigualable para arquitecturas personalizadas (ej., redes neuronales a medida) y afinamiento fino. |
Cuando no utilizar BQML
Mientras que BQML maneja la gran mayoría de los casos de uso de ML tabular de forma nativa, está limitado a arquitecturas de modelo predefinidas. No es compatible con la definición de capas de redes neuronales personalizadas, que a menudo se emplean en aplicaciones especializadas como el procesamiento de imágenes. Dado que BQML no proporciona actualmente estas operaciones, la ruta de Python sigue siendo relevante debido a su flexibilidad inigualable.
Observaciones finales
En última instancia, BigQuery ML se centra en la accesibilidad. Abstrae el trabajo pesado (las tareas complejas), permitiendo que los equipos se centren en los conocimientos/hallazgos en lugar de en la configuración, el desarrollo y el despliegue.
Para la inmensa mayoría de los casos de uso de negocio, la compensación es abrumadoramente positiva: se obtiene una reducción masiva de la complejidad y del tiempo de obtención de valor, perdiendo solo el control granular que la mayoría de las tareas predictivas estándar nunca requieren realmente.
¿Buscas optimizar su almacén de datos de BigQuery? Hemos preparado dos artículos que explican cómo hacerlo con campos anidados y desnormalización.

¡Deja de desperdiciar el poder predictivo de tus datos!
¿Listo para transformar tus datos en un activo competitivo potente y accesible? En Devoteam G Cloud, nos especializamos en la creación de plataformas de datos modernas en Google Cloud. ¡Contáctanos para talleres de estrategia de datos!


