Skip to content

Machine Learning Operations - GCP Professional Data Engineer

Overview

Comprehensive ML integration for data engineers covering BigQuery ML, Vertex AI, AutoML, and MLOps practices. This guide focuses on data engineering aspects of ML pipelines, model deployment, and production ML workflows on GCP.

Key Focus Areas: - BigQuery ML for SQL-based machine learning (primary tool for data engineers) - Vertex AI integration for advanced ML workflows - Data preparation and feature engineering for ML - ML pipeline orchestration and automation - Model serving patterns (batch vs real-time) - MLOps practices for production systems

Exam Weight: ~15-20% of Professional Data Engineer exam questions involve ML integration, particularly BigQuery ML usage and ML pipeline design.

Key Topics

  1. BigQuery ML - SQL-based ML (linear/logistic regression, DNN, K-means, time series, AutoML)
  2. Vertex AI for Data Engineers - AutoML, custom training, pipelines, feature store
  3. Data Preparation for ML - Feature engineering, preprocessing, transformations
  4. ML Pipeline Integration - Vertex AI Pipelines, Kubeflow, Dataflow ML
  5. Model Serving - Vertex AI endpoints, BigQuery predictions, batch vs real-time
  6. MLOps Patterns - Versioning, monitoring, retraining, A/B testing
  7. Pre-built ML APIs - Vision, NLP, Translation for data enrichment

BigQuery ML

Model Types and Use Cases

BigQuery ML enables data engineers to create and execute ML models using SQL. No Python/TensorFlow knowledge required for basic models.

Model Type Use Case Training Data Output
LINEAR_REG Numeric prediction (revenue, sales) Historical numeric values Continuous prediction
LOGISTIC_REG Binary/multiclass classification Labeled categorical data Class probability
DNN_REGRESSOR Complex numeric patterns Large datasets with non-linear patterns Continuous prediction
DNN_CLASSIFIER Complex classification Large labeled datasets Class probability
KMEANS Customer segmentation, anomaly detection Unlabeled numeric features Cluster assignment
MATRIX_FACTORIZATION Product recommendations User-item interaction matrix Item scores
ARIMA_PLUS Time series forecasting Historical time series data Future values + intervals
BOOSTED_TREE_REGRESSOR High-accuracy numeric prediction Tabular data with mixed types Continuous prediction
BOOSTED_TREE_CLASSIFIER High-accuracy classification Tabular data with mixed types Class probability
AUTOML_REGRESSOR Automatic model selection for regression Any tabular data Continuous prediction
AUTOML_CLASSIFIER Automatic model selection for classification Any tabular data Class probability
PCA Dimensionality reduction High-dimensional features Principal components
TENSORFLOW Custom TensorFlow models Any data format Custom output

Linear Regression

Use Case: Predict continuous numeric values (sales, revenue, prices, demand).

Complete Example: Sales forecasting

-- Step 1: Create training dataset with feature engineering
CREATE OR REPLACE TABLE `project.dataset.sales_training` AS
SELECT
  product_id,
  category,
  -- Temporal features
  EXTRACT(MONTH FROM sale_date) AS month,
  EXTRACT(DAYOFWEEK FROM sale_date) AS day_of_week,
  EXTRACT(QUARTER FROM sale_date) AS quarter,
  -- Lag features
  AVG(daily_sales) OVER (
    PARTITION BY product_id
    ORDER BY sale_date
    ROWS BETWEEN 7 PRECEDING AND 1 PRECEDING
  ) AS avg_sales_last_7_days,
  AVG(daily_sales) OVER (
    PARTITION BY product_id
    ORDER BY sale_date
    ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING
  ) AS avg_sales_last_30_days,
  -- Target variable
  daily_sales,
  -- Split column for train/test
  CASE
    WHEN RAND() < 0.8 THEN 'TRAIN'
    ELSE 'TEST'
  END AS split
FROM `project.dataset.raw_sales`
WHERE sale_date >= '2022-01-01';

-- Step 2: Create linear regression model
CREATE OR REPLACE MODEL `project.dataset.sales_forecast_model`
OPTIONS(
  model_type='LINEAR_REG',
  input_label_cols=['daily_sales'],
  l2_reg=0.1,  -- Ridge regularization
  max_iterations=50,
  learn_rate_strategy='line_search',
  early_stop=TRUE,
  min_rel_progress=0.01,
  data_split_method='SEQ',  -- Sequential split for time series
  data_split_eval_fraction=0.2,
  data_split_col='split'
) AS
SELECT
  category,
  month,
  day_of_week,
  quarter,
  avg_sales_last_7_days,
  avg_sales_last_30_days,
  daily_sales
FROM `project.dataset.sales_training`
WHERE split = 'TRAIN';

-- Step 3: Evaluate model performance
SELECT
  mean_absolute_error,
  mean_squared_error,
  mean_squared_log_error,
  median_absolute_error,
  r2_score,
  explained_variance
FROM ML.EVALUATE(MODEL `project.dataset.sales_forecast_model`,
  (
    SELECT
      category,
      month,
      day_of_week,
      quarter,
      avg_sales_last_7_days,
      avg_sales_last_30_days,
      daily_sales
    FROM `project.dataset.sales_training`
    WHERE split = 'TEST'
  )
);

-- Step 4: Get feature importance
SELECT
  feature,
  importance
FROM ML.FEATURE_IMPORTANCE(MODEL `project.dataset.sales_forecast_model`)
ORDER BY importance DESC;

-- Step 5: Make predictions
SELECT
  product_id,
  category,
  predicted_daily_sales,
  daily_sales AS actual_sales,
  ABS(predicted_daily_sales - daily_sales) AS prediction_error
FROM ML.PREDICT(MODEL `project.dataset.sales_forecast_model`,
  (
    SELECT
      product_id,
      category,
      month,
      day_of_week,
      quarter,
      avg_sales_last_7_days,
      avg_sales_last_30_days,
      daily_sales
    FROM `project.dataset.sales_training`
    WHERE split = 'TEST'
  )
)
ORDER BY prediction_error DESC
LIMIT 100;

Key Options: - l1_reg: Lasso regularization (feature selection) - l2_reg: Ridge regularization (prevent overfitting) - learn_rate_strategy: 'line_search' or 'constant' - early_stop: Stop when no improvement - data_split_method: 'AUTO_SPLIT', 'SEQ', 'RANDOM', 'NO_SPLIT', 'CUSTOM'

Logistic Regression

Use Case: Binary or multiclass classification (churn, fraud, category prediction).

Complete Example: Customer churn prediction

-- Step 1: Create features with data preparation
CREATE OR REPLACE TABLE `project.dataset.churn_features` AS
SELECT
  customer_id,
  -- Customer demographics
  age,
  income_bracket,
  region,
  -- Behavioral features
  tenure_months,
  monthly_spend,
  support_tickets_count,
  product_count,
  -- Engagement features
  days_since_last_login,
  page_views_last_30_days,
  -- Derived features
  CASE
    WHEN monthly_spend = 0 THEN 'zero_spend'
    WHEN monthly_spend < 50 THEN 'low_spend'
    WHEN monthly_spend < 200 THEN 'medium_spend'
    ELSE 'high_spend'
  END AS spend_category,
  monthly_spend / NULLIF(tenure_months, 0) AS avg_monthly_spend_per_tenure,
  support_tickets_count / NULLIF(tenure_months, 0) AS tickets_per_month,
  -- Label (target variable)
  churned AS label
FROM `project.dataset.customer_data`
WHERE customer_id IS NOT NULL;

-- Step 2: Create logistic regression model
CREATE OR REPLACE MODEL `project.dataset.churn_prediction_model`
OPTIONS(
  model_type='LOGISTIC_REG',
  input_label_cols=['label'],
  auto_class_weights=TRUE,  -- Handle class imbalance
  l2_reg=0.1,
  max_iterations=20,
  enable_global_explain=TRUE,  -- For model explainability
  data_split_method='AUTO_SPLIT',
  data_split_eval_fraction=0.2
) AS
SELECT
  age,
  income_bracket,
  region,
  tenure_months,
  monthly_spend,
  support_tickets_count,
  product_count,
  days_since_last_login,
  page_views_last_30_days,
  spend_category,
  avg_monthly_spend_per_tenure,
  tickets_per_month,
  label
FROM `project.dataset.churn_features`;

-- Step 3: Comprehensive evaluation
SELECT
  precision,
  recall,
  accuracy,
  f1_score,
  log_loss,
  roc_auc
FROM ML.EVALUATE(MODEL `project.dataset.churn_prediction_model`);

-- Step 4: Confusion matrix
SELECT
  *
FROM ML.CONFUSION_MATRIX(MODEL `project.dataset.churn_prediction_model`);

-- Step 5: ROC curve
SELECT
  *
FROM ML.ROC_CURVE(MODEL `project.dataset.churn_prediction_model`);

-- Step 6: Predictions with probability scores
SELECT
  customer_id,
  predicted_label,
  predicted_label_probs[OFFSET(0)].prob AS probability_no_churn,
  predicted_label_probs[OFFSET(1)].prob AS probability_churn,
  -- Custom threshold (0.3 instead of 0.5 for high-risk identification)
  CASE
    WHEN predicted_label_probs[OFFSET(1)].prob >= 0.3 THEN TRUE
    ELSE FALSE
  END AS high_churn_risk
FROM ML.PREDICT(MODEL `project.dataset.churn_prediction_model`,
  (
    SELECT * FROM `project.dataset.churn_features`
    WHERE label IS NULL  -- Score new customers without labels
  )
)
ORDER BY probability_churn DESC;

-- Step 7: Explain predictions for individual customers
SELECT
  customer_id,
  feature,
  attribution
FROM ML.EXPLAIN_PREDICT(
  MODEL `project.dataset.churn_prediction_model`,
  (
    SELECT * FROM `project.dataset.churn_features`
    WHERE customer_id = 'CUST-12345'
  ),
  STRUCT(3 AS top_k_features)
);

Multiclass Classification Example:

-- Product category prediction
CREATE OR REPLACE MODEL `project.dataset.category_classifier`
OPTIONS(
  model_type='LOGISTIC_REG',
  input_label_cols=['category'],  -- Multiple classes: electronics, clothing, home, etc.
  auto_class_weights=TRUE,
  max_iterations=30
) AS
SELECT
  product_title,
  product_description,
  price,
  brand,
  category  -- Target: electronics, clothing, home, toys, sports
FROM `project.dataset.product_catalog`;

Deep Neural Networks (DNN)

Use Case: Complex non-linear patterns, large datasets, feature interactions.

Complete Example: Advanced fraud detection

-- DNN Classifier for fraud detection with complex patterns
CREATE OR REPLACE MODEL `project.dataset.fraud_detection_dnn`
OPTIONS(
  model_type='DNN_CLASSIFIER',
  input_label_cols=['is_fraud'],
  hidden_units=[128, 64, 32],  -- Three hidden layers
  activation_fn='RELU',
  batch_size=64,
  dropout=0.2,
  optimizer='ADAM',
  learn_rate=0.001,
  max_iterations=50,
  early_stop=TRUE,
  min_rel_progress=0.01,
  auto_class_weights=TRUE
) AS
SELECT
  -- Transaction features
  transaction_amount,
  transaction_hour,
  transaction_day_of_week,
  merchant_category,
  -- Customer features
  customer_age,
  account_age_days,
  customer_credit_score,
  -- Behavioral features
  transactions_last_24h,
  avg_transaction_amount_30d,
  max_transaction_amount_30d,
  unique_merchants_30d,
  -- Geographic features
  distance_from_home_km,
  is_international,
  -- Device features
  device_type,
  is_new_device,
  -- Target
  is_fraud
FROM `project.dataset.transaction_features`
WHERE transaction_date >= '2022-01-01';

-- Evaluate DNN model
SELECT
  precision,
  recall,
  accuracy,
  f1_score,
  roc_auc
FROM ML.EVALUATE(MODEL `project.dataset.fraud_detection_dnn`);

-- Real-time fraud scoring
SELECT
  transaction_id,
  predicted_is_fraud,
  predicted_is_fraud_probs[OFFSET(1)].prob AS fraud_probability,
  CASE
    WHEN predicted_is_fraud_probs[OFFSET(1)].prob >= 0.9 THEN 'BLOCK'
    WHEN predicted_is_fraud_probs[OFFSET(1)].prob >= 0.5 THEN 'REVIEW'
    ELSE 'APPROVE'
  END AS recommendation
FROM ML.PREDICT(MODEL `project.dataset.fraud_detection_dnn`,
  (SELECT * FROM `project.dataset.incoming_transactions`)
);

DNN Regressor Example:

-- Complex price prediction with feature interactions
CREATE OR REPLACE MODEL `project.dataset.price_prediction_dnn`
OPTIONS(
  model_type='DNN_REGRESSOR',
  input_label_cols=['price'],
  hidden_units=[256, 128, 64, 32],  -- Four hidden layers for complex patterns
  activation_fn='RELU',
  batch_size=128,
  dropout=0.3,  -- Higher dropout for regularization
  optimizer='ADAM',
  learn_rate=0.0005,
  max_iterations=100
) AS
SELECT
  square_feet,
  bedrooms,
  bathrooms,
  year_built,
  lot_size,
  garage_spaces,
  neighborhood_id,
  school_rating,
  crime_rate,
  walkability_score,
  distance_to_downtown,
  recent_renovations,
  has_pool,
  has_fireplace,
  price
FROM `project.dataset.real_estate_listings`;

K-means Clustering

Use Case: Customer segmentation, anomaly detection, grouping similar items.

Complete Example: Customer segmentation

-- Step 1: Prepare features for clustering
CREATE OR REPLACE TABLE `project.dataset.customer_clustering_features` AS
SELECT
  customer_id,
  -- Normalize features manually or let BigQuery ML auto-normalize
  total_purchases,
  avg_purchase_value,
  purchase_frequency,
  days_since_last_purchase,
  product_category_diversity,
  discount_usage_rate,
  email_open_rate,
  support_interactions
FROM `project.dataset.customer_metrics`
WHERE total_purchases > 0;

-- Step 2: Create K-means model
CREATE OR REPLACE MODEL `project.dataset.customer_segments`
OPTIONS(
  model_type='KMEANS',
  num_clusters=5,  -- Or use HPARAM_TUNING for automatic selection
  kmeans_init_method='KMEANS++',  -- Better initialization
  standardize_features=TRUE,  -- Auto-normalize features
  max_iterations=50,
  early_stop=TRUE,
  min_rel_progress=0.01
) AS
SELECT
  total_purchases,
  avg_purchase_value,
  purchase_frequency,
  days_since_last_purchase,
  product_category_diversity,
  discount_usage_rate,
  email_open_rate,
  support_interactions
FROM `project.dataset.customer_clustering_features`;

-- Step 3: Evaluate clustering quality
SELECT
  davies_bouldin_index,  -- Lower is better (cluster separation)
  mean_squared_distance  -- Lower is better (within-cluster variance)
FROM ML.EVALUATE(MODEL `project.dataset.customer_segments`);

-- Step 4: Assign customers to clusters
SELECT
  customer_id,
  CENTROID_ID AS cluster_id,
  NEAREST_CENTROIDS_DISTANCE[OFFSET(0)].DISTANCE AS distance_to_centroid
FROM ML.PREDICT(MODEL `project.dataset.customer_segments`,
  (SELECT * FROM `project.dataset.customer_clustering_features`)
);

-- Step 5: Analyze cluster characteristics
SELECT
  CENTROID_ID AS cluster_id,
  feature,
  numerical_value AS avg_value
FROM ML.CENTROIDS(MODEL `project.dataset.customer_segments`)
ORDER BY CENTROID_ID, feature;

-- Step 6: Business interpretation of clusters
WITH cluster_assignments AS (
  SELECT
    cf.customer_id,
    cf.total_purchases,
    cf.avg_purchase_value,
    cf.purchase_frequency,
    p.CENTROID_ID AS cluster_id
  FROM `project.dataset.customer_clustering_features` cf
  JOIN ML.PREDICT(MODEL `project.dataset.customer_segments`,
    (SELECT * FROM `project.dataset.customer_clustering_features`)
  ) p ON cf.customer_id = p.customer_id
)
SELECT
  cluster_id,
  COUNT(*) AS customer_count,
  AVG(total_purchases) AS avg_purchases,
  AVG(avg_purchase_value) AS avg_value,
  AVG(purchase_frequency) AS avg_frequency,
  -- Label clusters based on characteristics
  CASE
    WHEN AVG(avg_purchase_value) > 500 THEN 'High-Value'
    WHEN AVG(purchase_frequency) > 10 THEN 'Frequent-Buyers'
    WHEN AVG(total_purchases) < 3 THEN 'New-Customers'
    ELSE 'Regular-Customers'
  END AS segment_label
FROM cluster_assignments
GROUP BY cluster_id
ORDER BY customer_count DESC;

Automatic Cluster Selection:

-- Let BigQuery ML find optimal number of clusters
CREATE OR REPLACE MODEL `project.dataset.customer_segments_auto`
OPTIONS(
  model_type='KMEANS',
  num_clusters=HPARAM_RANGE(3, 10),  -- Test 3 to 10 clusters
  kmeans_init_method='KMEANS++',
  standardize_features=TRUE
) AS
SELECT * FROM `project.dataset.customer_clustering_features`;

Time Series Forecasting (ARIMA_PLUS)

Use Case: Sales forecasting, demand prediction, trend analysis.

Complete Example: Multi-product demand forecasting

-- Step 1: Prepare time series data
CREATE OR REPLACE TABLE `project.dataset.product_time_series` AS
SELECT
  DATE(order_timestamp) AS date,
  product_id,
  SUM(quantity) AS daily_demand,
  AVG(unit_price) AS avg_price
FROM `project.dataset.orders`
WHERE order_timestamp >= '2022-01-01'
GROUP BY date, product_id
ORDER BY product_id, date;

-- Step 2: Create ARIMA_PLUS model
CREATE OR REPLACE MODEL `project.dataset.demand_forecast_model`
OPTIONS(
  model_type='ARIMA_PLUS',
  time_series_timestamp_col='date',
  time_series_data_col='daily_demand',
  time_series_id_col='product_id',  -- Separate model per product
  holiday_region='US',  -- Account for US holidays
  auto_arima=TRUE,  -- Automatic parameter selection
  data_frequency='DAILY',
  adjust_step_changes=TRUE,  -- Detect level shifts
  enable_auto_arima_seasonality=TRUE
) AS
SELECT
  date,
  product_id,
  daily_demand
FROM `project.dataset.product_time_series`;

-- Step 3: Evaluate forecast accuracy
SELECT
  product_id,
  mean_absolute_error,
  mean_squared_error,
  symmetric_mean_absolute_percentage_error AS smape,
  root_mean_squared_error AS rmse
FROM ML.EVALUATE(MODEL `project.dataset.demand_forecast_model`);

-- Step 4: Generate forecasts for next 30 days
SELECT
  forecast_timestamp AS date,
  time_series_id AS product_id,
  forecast_value AS predicted_demand,
  prediction_interval_lower_bound AS lower_bound,
  prediction_interval_upper_bound AS upper_bound,
  confidence_level
FROM ML.FORECAST(MODEL `project.dataset.demand_forecast_model`,
  STRUCT(30 AS horizon,  -- Forecast 30 days ahead
         0.95 AS confidence_level)  -- 95% confidence intervals
)
ORDER BY product_id, date;

-- Step 5: Detect anomalies in historical data
SELECT
  history_timestamp AS date,
  time_series_id AS product_id,
  history_value AS actual_demand,
  is_anomaly,
  anomaly_probability,
  lower_bound,
  upper_bound
FROM ML.DETECT_ANOMALIES(MODEL `project.dataset.demand_forecast_model`,
  STRUCT(0.95 AS anomaly_prob_threshold)
)
WHERE is_anomaly = TRUE
ORDER BY product_id, date;

-- Step 6: Explain time series components
SELECT
  time_series_id AS product_id,
  time_series_type,
  trend,
  seasonal_periods,
  has_drift,
  has_holiday_effect
FROM ML.ARIMA_EVALUATE(MODEL `project.dataset.demand_forecast_model`);

Single Time Series Example (no time_series_id_col):

-- Overall company revenue forecasting
CREATE OR REPLACE MODEL `project.dataset.revenue_forecast`
OPTIONS(
  model_type='ARIMA_PLUS',
  time_series_timestamp_col='month',
  time_series_data_col='revenue',
  data_frequency='MONTHLY',
  holiday_region='US'
) AS
SELECT
  DATE_TRUNC(order_date, MONTH) AS month,
  SUM(order_total) AS revenue
FROM `project.dataset.orders`
GROUP BY month
ORDER BY month;

-- Forecast next 12 months
SELECT
  forecast_timestamp AS month,
  forecast_value AS predicted_revenue,
  prediction_interval_lower_bound,
  prediction_interval_upper_bound
FROM ML.FORECAST(MODEL `project.dataset.revenue_forecast`,
  STRUCT(12 AS horizon)
);

AutoML Tables

Use Case: Automatic model selection and hyperparameter tuning for best performance.

Complete Example: Customer lifetime value prediction

-- AutoML automatically selects best algorithm and hyperparameters
CREATE OR REPLACE MODEL `project.dataset.clv_automl_model`
OPTIONS(
  model_type='AUTOML_REGRESSOR',
  input_label_cols=['customer_lifetime_value'],
  optimization_objective='MINIMIZE_MAE',  -- Or MINIMIZE_RMSE
  budget_hours=2,  -- Training time budget
  data_split_method='AUTO_SPLIT',
  data_split_eval_fraction=0.2
) AS
SELECT
  -- Customer demographics
  age,
  gender,
  location,
  income_level,
  -- Purchase behavior
  first_purchase_date,
  total_orders,
  avg_order_value,
  total_spend,
  purchase_frequency,
  product_categories_purchased,
  -- Engagement metrics
  email_open_rate,
  email_click_rate,
  app_usage_days,
  referrals_made,
  -- Target variable
  customer_lifetime_value
FROM `project.dataset.customer_features`
WHERE customer_lifetime_value IS NOT NULL;

-- AutoML for classification
CREATE OR REPLACE MODEL `project.dataset.churn_automl_model`
OPTIONS(
  model_type='AUTOML_CLASSIFIER',
  input_label_cols=['churned'],
  optimization_objective='MAXIMIZE_AU_ROC',  -- Or MAXIMIZE_AU_PRC, MAXIMIZE_PRECISION, MAXIMIZE_RECALL
  budget_hours=3,
  auto_class_weights=TRUE
) AS
SELECT * FROM `project.dataset.customer_churn_features`;

-- View AutoML trial results
SELECT
  trial_id,
  training_loss,
  evaluation_loss,
  duration_ms
FROM ML.TRIAL_INFO(MODEL `project.dataset.clv_automl_model`)
ORDER BY evaluation_loss ASC
LIMIT 10;

Feature Engineering in BigQuery ML

Complete Feature Engineering Example:

-- Comprehensive feature engineering for prediction model
CREATE OR REPLACE TABLE `project.dataset.engineered_features` AS
SELECT
  customer_id,
  order_date,

  -- 1. TEMPORAL FEATURES
  EXTRACT(YEAR FROM order_date) AS year,
  EXTRACT(MONTH FROM order_date) AS month,
  EXTRACT(QUARTER FROM order_date) AS quarter,
  EXTRACT(DAYOFWEEK FROM order_date) AS day_of_week,
  EXTRACT(DAYOFYEAR FROM order_date) AS day_of_year,
  EXTRACT(WEEK FROM order_date) AS week_of_year,
  CASE
    WHEN EXTRACT(DAYOFWEEK FROM order_date) IN (1, 7) THEN TRUE
    ELSE FALSE
  END AS is_weekend,

  -- 2. CATEGORICAL ENCODING (BigQuery ML auto one-hot encodes STRING types)
  product_category,  -- Will be one-hot encoded
  customer_segment,  -- Will be one-hot encoded

  -- 3. BUCKETIZING (continuous to categorical)
  CASE
    WHEN age < 25 THEN '18-24'
    WHEN age < 35 THEN '25-34'
    WHEN age < 45 THEN '35-44'
    WHEN age < 55 THEN '45-54'
    ELSE '55+'
  END AS age_bucket,

  -- 4. FEATURE CROSSES (interaction features)
  ML.FEATURE_CROSS(STRUCT(product_category, customer_segment)) AS category_segment_cross,
  ML.FEATURE_CROSS(STRUCT(age_bucket, income_bracket)) AS age_income_cross,

  -- 5. POLYNOMIAL FEATURES
  order_value AS order_value_1,
  POW(order_value, 2) AS order_value_squared,
  POW(order_value, 3) AS order_value_cubed,

  -- 6. MATHEMATICAL TRANSFORMATIONS
  LOG(order_value + 1) AS log_order_value,  -- +1 to handle zeros
  SQRT(order_value) AS sqrt_order_value,

  -- 7. AGGREGATED FEATURES (window functions)
  AVG(order_value) OVER (
    PARTITION BY customer_id
    ORDER BY order_date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS avg_order_value_last_7_orders,

  SUM(order_value) OVER (
    PARTITION BY customer_id
    ORDER BY order_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS cumulative_spend,

  COUNT(*) OVER (
    PARTITION BY customer_id
    ORDER BY order_date
    ROWS BETWEEN 29 PRECEDING AND CURRENT ROW
  ) AS orders_last_30_days,

  -- 8. LAG FEATURES (previous values)
  LAG(order_value, 1) OVER (
    PARTITION BY customer_id ORDER BY order_date
  ) AS previous_order_value,

  LAG(order_date, 1) OVER (
    PARTITION BY customer_id ORDER BY order_date
  ) AS previous_order_date,

  -- 9. TIME SINCE FEATURES
  DATE_DIFF(
    order_date,
    LAG(order_date, 1) OVER (PARTITION BY customer_id ORDER BY order_date),
    DAY
  ) AS days_since_last_order,

  -- 10. RATIO FEATURES
  order_value / NULLIF(
    AVG(order_value) OVER (PARTITION BY customer_id),
    0
  ) AS order_value_vs_customer_avg,

  -- 11. TEXT FEATURES (for text columns)
  ML.NGRAMS(SPLIT(product_description, ' '), [1, 2], ' ') AS product_ngrams,

  -- 12. NORMALIZE/STANDARDIZE (BigQuery ML does this automatically, but manual option)
  (order_value - AVG(order_value) OVER ()) /
    NULLIF(STDDEV(order_value) OVER (), 0) AS order_value_standardized,

  -- Target variable
  will_purchase_next_30_days AS label

FROM `project.dataset.raw_transactions`;

-- Using ML.BUCKETIZE for automated bucketizing
SELECT
  customer_id,
  ML.BUCKETIZE(age, [25, 35, 45, 55]) AS age_bucket,
  ML.BUCKETIZE(income, [30000, 50000, 75000, 100000]) AS income_bucket,
  ML.QUANTILE_BUCKETIZE(purchase_frequency, 4) AS frequency_quartile
FROM `project.dataset.customers`;

-- Using ML.MIN_MAX_SCALER for normalization
SELECT
  customer_id,
  ML.MIN_MAX_SCALER(order_value) OVER() AS order_value_normalized,
  ML.STANDARD_SCALER(order_value) OVER() AS order_value_standardized
FROM `project.dataset.orders`;

Feature Transform Functions:

-- ML.FEATURE_CROSS: Create interaction features
SELECT
  ML.FEATURE_CROSS(STRUCT(state, product_category)) AS state_category,
  ML.FEATURE_CROSS(STRUCT(age_group, gender, income_bracket)) AS demographic_cross
FROM `project.dataset.customers`;

-- ML.POLYNOMIAL_EXPAND: Create polynomial features
SELECT
  customer_id,
  ML.POLYNOMIAL_EXPAND(STRUCT(feature1, feature2, feature3), 2) AS poly_features
FROM `project.dataset.customer_features`;

-- ML.NGRAMS: Create n-grams from text
SELECT
  ML.NGRAMS(SPLIT(review_text, ' '), [1, 2, 3], ' ') AS text_features
FROM `project.dataset.product_reviews`;

-- ML.QUANTILE_BUCKETIZE: Create equal-sized buckets
SELECT
  ML.QUANTILE_BUCKETIZE(revenue, 10) AS revenue_decile  -- 10 equal buckets
FROM `project.dataset.customers`;

Model Export and Import

Export models for deployment outside BigQuery:

-- Export to Cloud Storage
EXPORT MODEL `project.dataset.churn_prediction_model`
OPTIONS(
  uri='gs://my-bucket/models/churn_model/*'
);

-- Export as TensorFlow SavedModel format
EXPORT MODEL `project.dataset.dnn_model`
OPTIONS(
  uri='gs://my-bucket/models/dnn_tf_model/*'
);

Import models into BigQuery:

-- Import TensorFlow model
CREATE OR REPLACE MODEL `project.dataset.imported_tf_model`
OPTIONS(
  model_type='TENSORFLOW',
  model_path='gs://my-bucket/tensorflow_saved_model/*'
);

-- Import ONNX model
CREATE OR REPLACE MODEL `project.dataset.imported_onnx_model`
OPTIONS(
  model_type='ONNX',
  model_path='gs://my-bucket/onnx_model/model.onnx'
);

BigQuery ML Best Practices

  1. Data Splitting:
  2. Use data_split_method='AUTO_SPLIT' for random 80/20 split
  3. Use data_split_method='SEQ' for time series data
  4. Use data_split_col for custom train/test splits

  5. Feature Engineering:

  6. Let BigQuery ML auto one-hot encode STRING columns
  7. Use ML.FEATURE_CROSS for interaction features
  8. Create temporal features (year, month, day of week)
  9. Use window functions for aggregated features

  10. Class Imbalance:

  11. Set auto_class_weights=TRUE for classification
  12. Manually set class_weight for custom balancing
  13. Use precision/recall metrics instead of accuracy

  14. Model Evaluation:

  15. Use ML.EVALUATE on held-out test set
  16. Check multiple metrics (precision, recall, F1, AUC)
  17. Use ML.ROC_CURVE and ML.CONFUSION_MATRIX for classification
  18. Use ML.FEATURE_IMPORTANCE to understand model

  19. Hyperparameter Tuning:

  20. Use HPARAM_RANGE and HPARAM_CANDIDATES for tuning
  21. Check ML.TRIAL_INFO for trial results
  22. Balance budget_hours vs accuracy needs

  23. Model Versioning:

  24. Use dataset naming conventions: model_v1, model_v2
  25. Include date in model names: model_20240115
  26. Use separate datasets for dev/staging/prod models

  27. Performance Optimization:

  28. Filter data appropriately before training
  29. Use WHERE clauses to exclude nulls
  30. Partition training data for faster queries
  31. Set reasonable max_iterations to avoid long training

  32. Cost Control:

  33. BigQuery ML charges for bytes processed in SELECT query
  34. Use clustered/partitioned tables for training data
  35. Limit AutoML budget_hours to control costs
  36. Export models for repeated predictions outside BigQuery

Vertex AI for Data Engineers

Vertex AI Overview

Vertex AI is GCP's unified ML platform for training, deploying, and managing ML models. Data engineers use it for advanced ML workflows beyond BigQuery ML capabilities.

When to use Vertex AI vs BigQuery ML: - BigQuery ML: SQL-based, simple models, data already in BigQuery, quick prototyping - Vertex AI: Complex models, custom algorithms, need for production deployment infrastructure, PyTorch/TensorFlow models

Vertex AI AutoML

AutoML provides automatic model selection, feature engineering, and hyperparameter tuning without code.

Complete Example: AutoML Tables for structured data

from google.cloud import aiplatform

# Initialize Vertex AI
aiplatform.init(
    project='my-project',
    location='us-central1',
    staging_bucket='gs://my-bucket'
)

# Create dataset from BigQuery
dataset = aiplatform.TabularDataset.create(
    display_name='customer_churn_dataset',
    bq_source='bq://my-project.my_dataset.customer_features',
    labels={'env': 'prod'}
)

# Create AutoML training job
job = aiplatform.AutoMLTabularTrainingJob(
    display_name='churn_automl_job',
    optimization_prediction_type='classification',
    optimization_objective='maximize-au-roc',
    column_specs={
        'customer_id': 'auto',  # Exclude from training
        'churned': 'target',
        'age': 'numeric',
        'income': 'numeric',
        'tenure_months': 'numeric',
        'region': 'categorical'
    }
)

# Train model
model = job.run(
    dataset=dataset,
    target_column='churned',
    training_fraction_split=0.8,
    validation_fraction_split=0.1,
    test_fraction_split=0.1,
    budget_milli_node_hours=3000,  # 3 hours
    model_display_name='churn_automl_model',
    disable_early_stopping=False
)

# Deploy model to endpoint
endpoint = model.deploy(
    deployed_model_display_name='churn_model_v1',
    machine_type='n1-standard-4',
    min_replica_count=1,
    max_replica_count=5,
    traffic_split={'0': 100}
)

# Make predictions
predictions = endpoint.predict(instances=[
    {
        'age': 35,
        'income': 75000,
        'tenure_months': 24,
        'region': 'west'
    }
])

print(predictions)

AutoML for Images:

# Create image dataset
image_dataset = aiplatform.ImageDataset.create(
    display_name='product_classification_dataset',
    gcs_source='gs://my-bucket/images_metadata.csv',
    import_schema_uri=aiplatform.schema.dataset.ioformat.image.single_label_classification
)

# Train AutoML Vision model
image_job = aiplatform.AutoMLImageTrainingJob(
    display_name='product_classifier',
    prediction_type='classification',
    model_type='CLOUD',  # or 'MOBILE_TF_LOW_LATENCY_1'
    base_model=None
)

image_model = image_job.run(
    dataset=image_dataset,
    model_display_name='product_classifier_v1',
    training_fraction_split=0.8,
    budget_milli_node_hours=8000
)

Custom Training with Vertex AI

Complete Example: Custom TensorFlow training

from google.cloud import aiplatform
from google.cloud import bigquery

# Step 1: Prepare training script (trainer/task.py)
training_script_content = '''
import tensorflow as tf
from google.cloud import bigquery
import pandas as pd
import argparse
import os

def create_model(input_dim):
    model = tf.keras.Sequential([
        tf.keras.layers.Dense(128, activation='relu', input_shape=(input_dim,)),
        tf.keras.layers.Dropout(0.3),
        tf.keras.layers.Dense(64, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(32, activation='relu'),
        tf.keras.layers.Dense(1, activation='sigmoid')
    ])
    model.compile(
        optimizer='adam',
        loss='binary_crossentropy',
        metrics=['accuracy', 'AUC']
    )
    return model

def load_data_from_bigquery(project, dataset, table):
    client = bigquery.Client(project=project)
    query = f"""
        SELECT * FROM `{project}.{dataset}.{table}`
    """
    df = client.query(query).to_dataframe()
    return df

def main(args):
    # Load data
    df = load_data_from_bigquery(args.project, args.dataset, args.table)

    # Prepare features
    X = df.drop(['label', 'customer_id'], axis=1)
    y = df['label']

    # Split data
    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )

    # Create and train model
    model = create_model(X_train.shape[1])

    model.fit(
        X_train, y_train,
        epochs=args.epochs,
        batch_size=args.batch_size,
        validation_data=(X_test, y_test),
        callbacks=[
            tf.keras.callbacks.EarlyStopping(patience=5),
            tf.keras.callbacks.ModelCheckpoint(
                os.path.join(args.model_dir, 'model'),
                save_best_only=True
            )
        ]
    )

    # Export model
    model.save(os.path.join(args.model_dir, 'final_model'))

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--project', type=str, required=True)
    parser.add_argument('--dataset', type=str, required=True)
    parser.add_argument('--table', type=str, required=True)
    parser.add_argument('--epochs', type=int, default=50)
    parser.add_argument('--batch_size', type=int, default=64)
    parser.add_argument('--model_dir', type=str, required=True)
    args = parser.parse_args()
    main(args)
'''

# Step 2: Create and run custom training job
job = aiplatform.CustomTrainingJob(
    display_name='custom_churn_training',
    script_path='trainer/task.py',
    container_uri='us-docker.pkg.dev/vertex-ai/training/tf-cpu.2-12:latest',
    requirements=['google-cloud-bigquery', 'pandas', 'scikit-learn'],
    model_serving_container_image_uri='us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-12:latest'
)

model = job.run(
    model_display_name='custom_churn_model',
    args=[
        '--project', 'my-project',
        '--dataset', 'my_dataset',
        '--table', 'customer_features',
        '--epochs', '50',
        '--batch_size', '64'
    ],
    replica_count=1,
    machine_type='n1-standard-8',
    accelerator_type='NVIDIA_TESLA_T4',
    accelerator_count=1,
    base_output_dir='gs://my-bucket/training_output'
)

Hyperparameter Tuning:

from google.cloud.aiplatform import hyperparameter_tuning as hpt

# Define hyperparameter tuning job
job = aiplatform.CustomTrainingJob(
    display_name='churn_hptuning',
    script_path='trainer/task.py',
    container_uri='us-docker.pkg.dev/vertex-ai/training/tf-cpu.2-12:latest'
)

# Define hyperparameter search space
hpt_job = job.run(
    model_display_name='churn_model_tuned',
    args=['--project', 'my-project', '--dataset', 'my_dataset', '--table', 'features'],
    replica_count=1,
    machine_type='n1-standard-4',
    hyperparameter_tuning_job_spec=hpt.HyperparameterTuningJobSpec(
        metric_spec={'auc': 'maximize'},
        parameter_spec={
            'learning_rate': hpt.DoubleParameterSpec(min=0.0001, max=0.1, scale='log'),
            'batch_size': hpt.DiscreteParameterSpec(values=[32, 64, 128], scale='linear'),
            'hidden_units': hpt.DiscreteParameterSpec(values=[64, 128, 256], scale='linear')
        },
        max_trial_count=20,
        parallel_trial_count=4
    )
)

Vertex AI Feature Store

Feature Store provides centralized feature management, serving, and monitoring for ML.

Complete Example: Feature store setup

from google.cloud import aiplatform

# Create feature store
feature_store = aiplatform.Featurestore.create(
    featurestore_id='customer_features_store',
    online_store_fixed_node_count=1,  # For online serving
    labels={'team': 'data-engineering'}
)

# Create entity type (e.g., customer)
customer_entity = feature_store.create_entity_type(
    entity_type_id='customer',
    description='Customer features'
)

# Create features
customer_entity.create_feature(
    feature_id='total_purchases',
    value_type='INT64',
    description='Total number of purchases'
)

customer_entity.create_feature(
    feature_id='avg_order_value',
    value_type='DOUBLE',
    description='Average order value'
)

customer_entity.create_feature(
    feature_id='last_purchase_date',
    value_type='STRING',
    description='Date of last purchase'
)

# Ingest features from BigQuery
customer_entity.ingest_from_bq(
    feature_ids=['total_purchases', 'avg_order_value', 'last_purchase_date'],
    feature_time='event_timestamp',
    bq_source_uri='bq://my-project.my_dataset.customer_features',
    entity_id_field='customer_id',
    worker_count=5
)

# Read features for training (batch)
training_data = customer_entity.batch_serve_to_bq(
    bq_destination_output_uri='bq://my-project.my_dataset.training_features',
    serving_feature_ids=['total_purchases', 'avg_order_value', 'last_purchase_date'],
    read_instances_uri='bq://my-project.my_dataset.customer_ids'
)

# Read features for online prediction
features = customer_entity.read(
    entity_ids=['CUST-12345']
)

Model Deployment and Serving

Deploy model to endpoint:

# Create endpoint
endpoint = aiplatform.Endpoint.create(
    display_name='churn_prediction_endpoint',
    description='Production churn prediction service'
)

# Deploy model
deployed_model = model.deploy(
    endpoint=endpoint,
    deployed_model_display_name='churn_v1',
    machine_type='n1-standard-4',
    min_replica_count=2,  # For high availability
    max_replica_count=10,  # Auto-scaling
    traffic_percentage=100,
    accelerator_type='NVIDIA_TESLA_T4',  # Optional GPU
    accelerator_count=1,
    service_account='model-serving@my-project.iam.gserviceaccount.com',
    enable_access_logging=True
)

# Make online predictions
instances = [
    {'age': 35, 'income': 75000, 'tenure_months': 24},
    {'age': 42, 'income': 95000, 'tenure_months': 48}
]

predictions = endpoint.predict(instances=instances)
print(predictions.predictions)

# Update traffic split for A/B testing
endpoint.update(
    traffic_split={
        'deployed_model_v1': 80,  # 80% traffic to v1
        'deployed_model_v2': 20   # 20% traffic to v2
    }
)

Batch predictions:

# Create batch prediction job
batch_prediction_job = model.batch_predict(
    job_display_name='daily_churn_scoring',
    gcs_source='gs://my-bucket/customers_to_score.csv',
    gcs_destination_prefix='gs://my-bucket/predictions/',
    instances_format='csv',
    predictions_format='jsonl',
    machine_type='n1-standard-4',
    starting_replica_count=5,
    max_replica_count=10
)

# Or predict from BigQuery
batch_prediction_job = model.batch_predict(
    job_display_name='bq_churn_scoring',
    bigquery_source='bq://my-project.my_dataset.customers',
    bigquery_destination_prefix='bq://my-project.predictions_dataset',
    instances_format='bigquery',
    predictions_format='bigquery'
)

# Wait for completion
batch_prediction_job.wait()
print(f"Job state: {batch_prediction_job.state}")

Pre-trained APIs for Data Enrichment

Vision API for image analysis in data pipelines:

from google.cloud import vision
import pandas as pd

def analyze_product_images(image_uris):
    client = vision.ImageAnnotatorClient()
    results = []

    for uri in image_uris:
        image = vision.Image()
        image.source.image_uri = uri

        # Detect labels
        response = client.label_detection(image=image)
        labels = [label.description for label in response.label_annotations]

        # Detect objects
        objects_response = client.object_localization(image=image)
        objects = [obj.name for obj in objects_response.localized_object_annotations]

        # Detect text (OCR)
        text_response = client.text_detection(image=image)
        text = text_response.text_annotations[0].description if text_response.text_annotations else ''

        results.append({
            'image_uri': uri,
            'labels': labels,
            'objects': objects,
            'text': text
        })

    return pd.DataFrame(results)

# Use in data pipeline
image_data = analyze_product_images(['gs://my-bucket/product1.jpg', 'gs://my-bucket/product2.jpg'])

Natural Language API for text analysis:

from google.cloud import language_v1

def analyze_customer_reviews(reviews):
    client = language_v1.LanguageServiceClient()
    results = []

    for review in reviews:
        document = language_v1.Document(
            content=review,
            type_=language_v1.Document.Type.PLAIN_TEXT
        )

        # Sentiment analysis
        sentiment = client.analyze_sentiment(request={'document': document}).document_sentiment

        # Entity extraction
        entities = client.analyze_entities(request={'document': document}).entities
        entity_list = [(e.name, e.type_.name) for e in entities]

        # Content classification
        categories = client.classify_text(request={'document': document}).categories
        category_list = [c.name for c in categories]

        results.append({
            'review': review,
            'sentiment_score': sentiment.score,
            'sentiment_magnitude': sentiment.magnitude,
            'entities': entity_list,
            'categories': category_list
        })

    return pd.DataFrame(results)

# Integrate with BigQuery
from google.cloud import bigquery

def enrich_reviews_in_bigquery(project, dataset):
    bq_client = bigquery.Client(project=project)

    # Read reviews
    query = f"SELECT review_id, review_text FROM `{project}.{dataset}.reviews` WHERE sentiment_score IS NULL LIMIT 1000"
    reviews_df = bq_client.query(query).to_dataframe()

    # Analyze sentiment
    enriched_df = analyze_customer_reviews(reviews_df['review_text'].tolist())
    enriched_df['review_id'] = reviews_df['review_id'].tolist()

    # Write back to BigQuery
    table_id = f'{project}.{dataset}.enriched_reviews'
    job = bq_client.load_table_from_dataframe(enriched_df, table_id)
    job.result()

Data Preparation for ML

TensorFlow Transform (TFT) for Feature Engineering

TFT enables consistent feature transformations between training and serving.

Complete Example: Feature preprocessing pipeline

import tensorflow as tf
import tensorflow_transform as tft
from tensorflow_transform.beam import impl as beam_impl
from tensorflow_transform.tf_metadata import dataset_metadata, schema_utils
import apache_beam as beam

# Define feature spec
RAW_DATA_FEATURE_SPEC = {
    'age': tf.io.FixedLenFeature([], tf.int64),
    'income': tf.io.FixedLenFeature([], tf.float32),
    'category': tf.io.FixedLenFeature([], tf.string),
    'product_description': tf.io.FixedLenFeature([], tf.string),
    'label': tf.io.FixedLenFeature([], tf.int64)
}

# Define preprocessing function
def preprocessing_fn(inputs):
    outputs = {}

    # Scale numerical features
    outputs['age_scaled'] = tft.scale_to_z_score(inputs['age'])
    outputs['income_normalized'] = tft.scale_to_0_1(inputs['income'])

    # Bucketize
    outputs['age_bucket'] = tft.bucketize(
        inputs['age'],
        num_buckets=5
    )

    # Vocabulary for categorical
    outputs['category_idx'] = tft.compute_and_apply_vocabulary(
        inputs['category'],
        top_k=100,
        num_oov_buckets=1
    )

    # Text features (bag of words)
    outputs['product_bow'] = tft.compute_and_apply_vocabulary(
        tf.strings.split(inputs['product_description']),
        top_k=1000,
        num_oov_buckets=1
    )

    # Pass through label
    outputs['label'] = inputs['label']

    return outputs

# Create Beam pipeline
def transform_data(input_path, output_path, temp_dir):
    with beam.Pipeline() as pipeline:
        with beam_impl.Context(temp_dir=temp_dir):
            # Read raw data
            raw_data = (
                pipeline
                | 'ReadData' >> beam.io.ReadFromBigQuery(
                    query='SELECT * FROM `project.dataset.raw_features`',
                    use_standard_sql=True
                )
            )

            # Create dataset metadata
            raw_data_metadata = dataset_metadata.DatasetMetadata(
                schema_utils.schema_from_feature_spec(RAW_DATA_FEATURE_SPEC)
            )

            # Transform data
            transformed_dataset, transform_fn = (
                (raw_data, raw_data_metadata)
                | 'AnalyzeAndTransform' >> beam_impl.AnalyzeAndTransformDataset(
                    preprocessing_fn
                )
            )

            transformed_data, transformed_metadata = transformed_dataset

            # Write transformed data
            _ = (
                transformed_data
                | 'WriteTransformedData' >> beam.io.WriteToTFRecord(
                    output_path,
                    coder=tft.coders.ExampleProtoCoder(transformed_metadata.schema)
                )
            )

            # Write transform function for serving
            _ = (
                transform_fn
                | 'WriteTransformFn' >> tft.beam.WriteTransformFn(temp_dir)
            )

# Usage
transform_data(
    input_path='bq://project.dataset.raw_features',
    output_path='gs://bucket/transformed_data/data',
    temp_dir='gs://bucket/temp'
)

Dataflow for ML Feature Engineering

Complete Example: Real-time feature engineering pipeline

import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
from google.cloud import bigquery
import json

class FeatureEngineeringFn(beam.DoFn):
    def process(self, element):
        # Extract features from raw event
        customer_id = element['customer_id']
        event_time = element['event_timestamp']
        event_type = element['event_type']
        value = element.get('value', 0)

        # Engineer features
        features = {
            'customer_id': customer_id,
            'event_time': event_time,
            'event_type': event_type,
            'value': value,
            # Derived features
            'hour_of_day': int(event_time.split('T')[1].split(':')[0]),
            'is_weekend': self._is_weekend(event_time),
            'value_category': self._categorize_value(value),
            'log_value': self._safe_log(value)
        }

        yield features

    def _is_weekend(self, timestamp):
        # Simplified weekend check
        from datetime import datetime
        dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
        return dt.weekday() >= 5

    def _categorize_value(self, value):
        if value < 50:
            return 'low'
        elif value < 200:
            return 'medium'
        else:
            return 'high'

    def _safe_log(self, value):
        import math
        return math.log(value + 1)

class AggregateFeaturesFn(beam.DoFn):
    def process(self, element):
        customer_id, events = element
        # Aggregate features over window
        features = {
            'customer_id': customer_id,
            'event_count': len(events),
            'total_value': sum(e['value'] for e in events),
            'avg_value': sum(e['value'] for e in events) / len(events) if events else 0,
            'unique_event_types': len(set(e['event_type'] for e in events))
        }
        yield features

def run_feature_pipeline(project, subscription, output_table):
    options = PipelineOptions(
        streaming=True,
        project=project,
        region='us-central1',
        temp_location='gs://my-bucket/temp'
    )

    with beam.Pipeline(options=options) as pipeline:
        # Read from Pub/Sub
        raw_events = (
            pipeline
            | 'ReadPubSub' >> beam.io.ReadFromPubSub(
                subscription=f'projects/{project}/subscriptions/{subscription}'
            )
            | 'ParseJSON' >> beam.Map(lambda x: json.loads(x.decode('utf-8')))
        )

        # Engineer features
        engineered = (
            raw_events
            | 'EngineerFeatures' >> beam.ParDo(FeatureEngineeringFn())
        )

        # Aggregate features over sliding window
        aggregated = (
            engineered
            | 'AddTimestamps' >> beam.Map(
                lambda x: beam.window.TimestampedValue(x, x['event_time'])
            )
            | 'WindowInto' >> beam.WindowInto(
                beam.window.SlidingWindows(size=3600, period=300)  # 1hr window, 5min slide
            )
            | 'GroupByCustomer' >> beam.GroupBy('customer_id')
            | 'AggregateFeatures' >> beam.ParDo(AggregateFeaturesFn())
        )

        # Write to BigQuery
        _ = (
            aggregated
            | 'WriteToBigQuery' >> beam.io.WriteToBigQuery(
                table=output_table,
                schema='customer_id:STRING,event_count:INTEGER,total_value:FLOAT,avg_value:FLOAT,unique_event_types:INTEGER',
                write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND
            )
        )

# Run pipeline
run_feature_pipeline(
    project='my-project',
    subscription='raw-events-sub',
    output_table='my-project:ml_features.customer_aggregates'
)

ML Pipelines

Vertex AI Pipelines

Vertex AI Pipelines orchestrate end-to-end ML workflows using Kubeflow Pipelines (KFP).

Complete Example: Production ML pipeline

from kfp.v2 import dsl
from kfp.v2.dsl import component, pipeline, Input, Output, Dataset, Model, Metrics
from google.cloud import aiplatform

# Component 1: Extract data from BigQuery
@component(
    base_image='python:3.9',
    packages_to_install=['google-cloud-bigquery', 'pandas', 'pyarrow']
)
def extract_data(
    project_id: str,
    dataset_id: str,
    table_id: str,
    output_dataset: Output[Dataset]
):
    from google.cloud import bigquery
    import pandas as pd

    client = bigquery.Client(project=project_id)
    query = f"""
        SELECT * FROM `{project_id}.{dataset_id}.{table_id}`
        WHERE date >= DATE_SUB(CURRENT_DATE(), INTERVAL 365 DAY)
    """

    df = client.query(query).to_dataframe()
    df.to_csv(output_dataset.path, index=False)

    print(f"Extracted {len(df)} rows")

# Component 2: Feature engineering
@component(
    base_image='python:3.9',
    packages_to_install=['pandas', 'scikit-learn']
)
def engineer_features(
    input_dataset: Input[Dataset],
    output_dataset: Output[Dataset]
):
    import pandas as pd
    from sklearn.preprocessing import StandardScaler, LabelEncoder
    import pickle

    # Read data
    df = pd.read_csv(input_dataset.path)

    # Feature engineering
    df['age_group'] = pd.cut(df['age'], bins=[0, 25, 35, 45, 55, 100], labels=['18-25', '25-35', '35-45', '45-55', '55+'])
    df['tenure_years'] = df['tenure_months'] / 12
    df['spend_per_tenure'] = df['total_spend'] / (df['tenure_months'] + 1)

    # Encode categoricals
    le = LabelEncoder()
    for col in ['region', 'product_category']:
        df[col + '_encoded'] = le.fit_transform(df[col])

    # Save engineered features
    df.to_csv(output_dataset.path, index=False)

    print(f"Engineered features for {len(df)} rows")

# Component 3: Train model
@component(
    base_image='python:3.9',
    packages_to_install=['pandas', 'scikit-learn', 'joblib']
)
def train_model(
    input_dataset: Input[Dataset],
    output_model: Output[Model],
    metrics: Output[Metrics]
):
    import pandas as pd
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
    import joblib

    # Read data
    df = pd.read_csv(input_dataset.path)

    # Prepare features and target
    feature_cols = [c for c in df.columns if c not in ['customer_id', 'label', 'date']]
    X = df[feature_cols]
    y = df['label']

    # Split data
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    # Train model
    model = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
    model.fit(X_train, y_train)

    # Evaluate
    y_pred = model.predict(X_test)
    y_proba = model.predict_proba(X_test)[:, 1]

    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)
    auc = roc_auc_score(y_test, y_proba)

    # Log metrics
    metrics.log_metric('accuracy', accuracy)
    metrics.log_metric('precision', precision)
    metrics.log_metric('recall', recall)
    metrics.log_metric('f1_score', f1)
    metrics.log_metric('auc', auc)

    # Save model
    joblib.dump(model, output_model.path)

    print(f"Model trained. AUC: {auc:.4f}, F1: {f1:.4f}")

# Component 4: Model validation
@component(
    base_image='python:3.9',
    packages_to_install=['pandas', 'scikit-learn', 'joblib']
)
def validate_model(
    model: Input[Model],
    validation_dataset: Input[Dataset],
    metrics: Output[Metrics],
    deployment_decision: Output[str]
):
    import pandas as pd
    import joblib
    from sklearn.metrics import roc_auc_score

    # Load model
    clf = joblib.load(model.path)

    # Load validation data
    df = pd.read_csv(validation_dataset.path)
    X = df.drop(['customer_id', 'label', 'date'], axis=1, errors='ignore')
    y = df['label']

    # Validate
    y_proba = clf.predict_proba(X)[:, 1]
    auc = roc_auc_score(y, y_proba)

    metrics.log_metric('validation_auc', auc)

    # Deployment decision
    if auc >= 0.85:
        deployment_decision = 'DEPLOY'
    else:
        deployment_decision = 'REJECT'

    print(f"Validation AUC: {auc:.4f}, Decision: {deployment_decision}")

# Component 5: Deploy model
@component(
    base_image='python:3.9',
    packages_to_install=['google-cloud-aiplatform']
)
def deploy_model(
    model: Input[Model],
    project_id: str,
    region: str,
    deployment_decision: str
):
    if deployment_decision != 'DEPLOY':
        print("Model deployment skipped based on validation")
        return

    from google.cloud import aiplatform

    aiplatform.init(project=project_id, location=region)

    # Upload model to Vertex AI
    uploaded_model = aiplatform.Model.upload(
        display_name='churn_model_pipeline',
        artifact_uri=model.uri,
        serving_container_image_uri='us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest'
    )

    # Deploy to endpoint
    endpoint = aiplatform.Endpoint.create(display_name='churn_endpoint_pipeline')
    uploaded_model.deploy(
        endpoint=endpoint,
        machine_type='n1-standard-4',
        min_replica_count=1,
        max_replica_count=5
    )

    print(f"Model deployed to endpoint: {endpoint.resource_name}")

# Define pipeline
@pipeline(
    name='customer-churn-ml-pipeline',
    description='End-to-end pipeline for churn prediction',
    pipeline_root='gs://my-bucket/pipeline_root'
)
def churn_prediction_pipeline(
    project_id: str,
    dataset_id: str,
    table_id: str,
    region: str = 'us-central1'
):
    # Step 1: Extract data
    extract_task = extract_data(
        project_id=project_id,
        dataset_id=dataset_id,
        table_id=table_id
    )

    # Step 2: Engineer features
    engineer_task = engineer_features(
        input_dataset=extract_task.outputs['output_dataset']
    )

    # Step 3: Train model
    train_task = train_model(
        input_dataset=engineer_task.outputs['output_dataset']
    )

    # Step 4: Validate model
    validate_task = validate_model(
        model=train_task.outputs['output_model'],
        validation_dataset=engineer_task.outputs['output_dataset']
    )

    # Step 5: Deploy model (conditional)
    deploy_task = deploy_model(
        model=train_task.outputs['output_model'],
        project_id=project_id,
        region=region,
        deployment_decision=validate_task.outputs['deployment_decision']
    )

# Compile and run pipeline
from kfp.v2 import compiler

compiler.Compiler().compile(
    pipeline_func=churn_prediction_pipeline,
    package_path='churn_pipeline.json'
)

# Submit pipeline job
aiplatform.init(project='my-project', location='us-central1')

job = aiplatform.PipelineJob(
    display_name='churn-pipeline-run',
    template_path='churn_pipeline.json',
    parameter_values={
        'project_id': 'my-project',
        'dataset_id': 'ml_data',
        'table_id': 'customer_features',
        'region': 'us-central1'
    }
)

job.run(service_account='pipeline-sa@my-project.iam.gserviceaccount.com')

Cloud Composer for ML Orchestration

Complete Example: DAG for scheduled model retraining

from airflow import DAG
from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator
from airflow.providers.google.cloud.operators.vertex_ai import (
    CreateCustomTrainingJobOperator,
    CreateBatchPredictionJobOperator
)
from airflow.operators.python import PythonOperator
from airflow.utils.dates import days_ago
from datetime import timedelta

default_args = {
    'owner': 'data-engineering',
    'depends_on_past': False,
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 2,
    'retry_delay': timedelta(minutes=5)
}

dag = DAG(
    'ml_model_retraining',
    default_args=default_args,
    description='Weekly model retraining pipeline',
    schedule_interval='0 2 * * 1',  # Every Monday at 2 AM
    start_date=days_ago(1),
    catchup=False,
    tags=['ml', 'retraining']
)

# Task 1: Prepare training data
prepare_data = BigQueryInsertJobOperator(
    task_id='prepare_training_data',
    configuration={
        'query': {
            'query': '''
                CREATE OR REPLACE TABLE `my-project.ml_data.training_features` AS
                SELECT
                    customer_id,
                    age,
                    income,
                    tenure_months,
                    total_spend,
                    churned AS label
                FROM `my-project.analytics.customers`
                WHERE last_updated >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
            ''',
            'useLegacySql': False
        }
    },
    dag=dag
)

# Task 2: Train BigQuery ML model
train_bqml_model = BigQueryInsertJobOperator(
    task_id='train_bqml_model',
    configuration={
        'query': {
            'query': '''
                CREATE OR REPLACE MODEL `my-project.ml_models.churn_model_{{ ds_nodash }}`
                OPTIONS(
                    model_type='LOGISTIC_REG',
                    input_label_cols=['label'],
                    auto_class_weights=TRUE
                ) AS
                SELECT * FROM `my-project.ml_data.training_features`
            ''',
            'useLegacySql': False
        }
    },
    dag=dag
)

# Task 3: Evaluate model
evaluate_model = BigQueryInsertJobOperator(
    task_id='evaluate_model',
    configuration={
        'query': {
            'query': '''
                CREATE OR REPLACE TABLE `my-project.ml_models.model_metrics_{{ ds_nodash }}` AS
                SELECT
                    '{{ ds }}' AS training_date,
                    precision,
                    recall,
                    accuracy,
                    f1_score,
                    roc_auc
                FROM ML.EVALUATE(MODEL `my-project.ml_models.churn_model_{{ ds_nodash }}`)
            ''',
            'useLegacySql': False
        }
    },
    dag=dag
)

# Task 4: Generate predictions
generate_predictions = BigQueryInsertJobOperator(
    task_id='generate_predictions',
    configuration={
        'query': {
            'query': '''
                CREATE OR REPLACE TABLE `my-project.predictions.churn_scores_{{ ds_nodash }}` AS
                SELECT
                    customer_id,
                    predicted_label,
                    predicted_label_probs[OFFSET(1)].prob AS churn_probability,
                    '{{ ds }}' AS prediction_date
                FROM ML.PREDICT(
                    MODEL `my-project.ml_models.churn_model_{{ ds_nodash }}`,
                    (SELECT * FROM `my-project.analytics.active_customers`)
                )
            ''',
            'useLegacySql': False
        }
    },
    dag=dag
)

# Task 5: Export model (optional)
def export_model_fn(**context):
    from google.cloud import bigquery
    client = bigquery.Client()

    execution_date = context['ds_nodash']
    export_query = f'''
        EXPORT MODEL `my-project.ml_models.churn_model_{execution_date}`
        OPTIONS(
            uri='gs://my-bucket/models/churn_model_{execution_date}/*'
        )
    '''

    client.query(export_query).result()
    print(f"Model exported to GCS")

export_model = PythonOperator(
    task_id='export_model',
    python_callable=export_model_fn,
    provide_context=True,
    dag=dag
)

# Define task dependencies
prepare_data >> train_bqml_model >> evaluate_model >> [generate_predictions, export_model]

Integration with Data Pipelines

Pattern 1: BigQuery + BigQuery ML + Scheduled Queries

-- Create scheduled query for daily predictions
CREATE OR REPLACE PROCEDURE `project.dataset.daily_churn_scoring`()
BEGIN
    -- Refresh features
    CREATE OR REPLACE TABLE `project.dataset.latest_features` AS
    SELECT
        customer_id,
        age,
        tenure_months,
        total_spend,
        days_since_last_purchase
    FROM `project.dataset.customer_metrics`;

    -- Generate predictions
    CREATE OR REPLACE TABLE `project.dataset.churn_predictions` AS
    SELECT
        customer_id,
        predicted_label_probs[OFFSET(1)].prob AS churn_risk,
        CURRENT_TIMESTAMP() AS scored_at
    FROM ML.PREDICT(
        MODEL `project.dataset.churn_model`,
        (SELECT * FROM `project.dataset.latest_features`)
    );

    -- Send high-risk customers to action table
    CREATE OR REPLACE TABLE `project.dataset.high_risk_customers` AS
    SELECT *
    FROM `project.dataset.churn_predictions`
    WHERE churn_risk >= 0.7;
END;

-- Schedule via BigQuery Transfer Service (UI or API)

Pattern 2: Pub/Sub β†’ Dataflow β†’ BigQuery ML

# Dataflow pipeline that triggers ML predictions
def run_realtime_ml_pipeline():
    with beam.Pipeline() as p:
        # Read from Pub/Sub
        events = (
            p
            | 'ReadPubSub' >> beam.io.ReadFromPubSub(subscription='projects/my-project/subscriptions/events')
            | 'ParseJSON' >> beam.Map(json.loads)
        )

        # Feature engineering
        features = (
            events
            | 'EngineerFeatures' >> beam.ParDo(FeatureEngineeringFn())
        )

        # Write features to BigQuery
        _ = (
            features
            | 'WriteToBQ' >> beam.io.WriteToBigQuery(
                'my-project:dataset.realtime_features',
                write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND
            )
        )

        # Batch predictions (via separate scheduled query on BigQuery ML)

Pattern 3: Dataflow β†’ Feature Store β†’ Vertex AI Endpoint

# Real-time prediction pipeline
def run_realtime_prediction_pipeline():
    with beam.Pipeline() as p:
        predictions = (
            p
            | 'ReadPubSub' >> beam.io.ReadFromPubSub(subscription='...')
            | 'ParseJSON' >> beam.Map(json.loads)
            | 'EngineerFeatures' >> beam.ParDo(FeatureEngineeringFn())
            | 'PredictWithVertexAI' >> beam.ParDo(VertexAIPredictionFn(
                endpoint_id='projects/123/locations/us-central1/endpoints/456'
            ))
            | 'WriteResults' >> beam.io.WriteToBigQuery('dataset.predictions')
        )

MLOps Practices

Model Versioning

Complete Example: Model registry and versioning

from google.cloud import aiplatform

# Initialize
aiplatform.init(project='my-project', location='us-central1')

# Upload model with version information
model = aiplatform.Model.upload(
    display_name='churn_prediction_model',
    artifact_uri='gs://my-bucket/models/churn_v2/',
    serving_container_image_uri='us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest',
    labels={
        'model_type': 'classification',
        'version': 'v2.0',
        'training_date': '2024-01-15',
        'framework': 'sklearn',
        'dataset_version': 'v1.2'
    },
    version_aliases=['production', 'latest'],
    version_description='Improved feature engineering with better precision'
)

# List all versions of a model
models = aiplatform.Model.list(
    filter='display_name="churn_prediction_model"',
    order_by='create_time desc'
)

for m in models:
    print(f"Version: {m.version_id}, Labels: {m.labels}, Created: {m.create_time}")

# Get specific version
model_v1 = aiplatform.Model(
    model_name='projects/123/locations/us-central1/models/456@1'  # @1 for version 1
)

# Compare model versions in BigQuery
comparison_query = '''
WITH model_v1_metrics AS (
  SELECT 'v1' AS version, precision, recall, f1_score, roc_auc
  FROM ML.EVALUATE(MODEL `project.dataset.churn_model_v1`)
),
model_v2_metrics AS (
  SELECT 'v2' AS version, precision, recall, f1_score, roc_auc
  FROM ML.EVALUATE(MODEL `project.dataset.churn_model_v2`)
)
SELECT * FROM model_v1_metrics
UNION ALL
SELECT * FROM model_v2_metrics
ORDER BY version;
'''

Model Monitoring

Complete Example: Vertex AI Model Monitoring setup

from google.cloud import aiplatform
from google.cloud.aiplatform import model_monitoring

# Create monitoring job
monitoring_job = aiplatform.ModelDeploymentMonitoringJob.create(
    display_name='churn_model_monitoring',
    endpoint=endpoint,
    logging_sampling_strategy=model_monitoring.SamplingStrategy(
        random_sample_config=model_monitoring.RandomSampleConfig(
            sample_rate=0.5  # Monitor 50% of predictions
        )
    ),
    schedule_config=model_monitoring.ScheduleConfig(
        monitor_interval=3600  # Check every hour
    ),
    alert_config=model_monitoring.AlertConfig(
        email_alert_config=model_monitoring.EmailAlertConfig(
            user_emails=['data-eng@company.com']
        )
    ),
    model_monitoring_objective_configs=[
        model_monitoring.ObjectiveConfig(
            training_dataset=model_monitoring.Dataset(
                gcs_source=model_monitoring.GcsSource(
                    uris=['gs://my-bucket/training_data.csv']
                ),
                target_field='label'
            ),
            training_prediction_skew_detection_config=model_monitoring.SkewDetectionConfig(
                skew_thresholds={
                    'age': 0.3,
                    'income': 0.3,
                    'tenure_months': 0.3
                },
                attribution_score_skew_thresholds={
                    'age': 0.3,
                    'income': 0.3
                }
            ),
            prediction_drift_detection_config=model_monitoring.DriftDetectionConfig(
                drift_thresholds={
                    'age': 0.3,
                    'income': 0.3,
                    'tenure_months': 0.3
                }
            )
        )
    ]
)

print(f"Monitoring job created: {monitoring_job.resource_name}")

BigQuery ML Monitoring:

-- Monitor prediction distribution over time
CREATE OR REPLACE TABLE `project.dataset.prediction_monitoring` AS
SELECT
  DATE(scored_at) AS prediction_date,
  COUNT(*) AS total_predictions,
  AVG(churn_probability) AS avg_churn_prob,
  STDDEV(churn_probability) AS stddev_churn_prob,
  COUNTIF(churn_probability >= 0.7) AS high_risk_count,
  COUNTIF(churn_probability >= 0.7) / COUNT(*) AS high_risk_rate
FROM `project.dataset.churn_predictions`
GROUP BY prediction_date
ORDER BY prediction_date DESC;

-- Detect data drift by comparing feature distributions
CREATE OR REPLACE TABLE `project.dataset.feature_drift_detection` AS
WITH training_stats AS (
  SELECT
    AVG(age) AS avg_age,
    STDDEV(age) AS stddev_age,
    AVG(tenure_months) AS avg_tenure,
    STDDEV(tenure_months) AS stddev_tenure
  FROM `project.dataset.training_data`
),
recent_stats AS (
  SELECT
    AVG(age) AS avg_age,
    STDDEV(age) AS stddev_age,
    AVG(tenure_months) AS avg_tenure,
    STDDEV(tenure_months) AS stddev_tenure
  FROM `project.dataset.customer_features`
  WHERE updated_date >= CURRENT_DATE() - 7
)
SELECT
  'age' AS feature,
  t.avg_age AS training_mean,
  r.avg_age AS recent_mean,
  ABS(r.avg_age - t.avg_age) / t.stddev_age AS drift_zscore,
  CASE WHEN ABS(r.avg_age - t.avg_age) / t.stddev_age > 2 THEN TRUE ELSE FALSE END AS drift_detected
FROM training_stats t, recent_stats r
UNION ALL
SELECT
  'tenure_months' AS feature,
  t.avg_tenure AS training_mean,
  r.avg_tenure AS recent_mean,
  ABS(r.avg_tenure - t.avg_tenure) / t.stddev_tenure AS drift_zscore,
  CASE WHEN ABS(r.avg_tenure - t.avg_tenure) / t.stddev_tenure > 2 THEN TRUE ELSE FALSE END AS drift_detected
FROM training_stats t, recent_stats r;

-- Monitor model performance over time (if ground truth available)
CREATE OR REPLACE TABLE `project.dataset.model_performance_tracking` AS
SELECT
  DATE_TRUNC(prediction_date, WEEK) AS week,
  COUNT(*) AS total_predictions,
  AVG(CAST(predicted_churn AS INT64)) AS avg_predicted_churn,
  AVG(CAST(actual_churn AS INT64)) AS avg_actual_churn,
  -- Calculate precision and recall
  SUM(CASE WHEN predicted_churn AND actual_churn THEN 1 ELSE 0 END) / NULLIF(SUM(CAST(predicted_churn AS INT64)), 0) AS precision,
  SUM(CASE WHEN predicted_churn AND actual_churn THEN 1 ELSE 0 END) / NULLIF(SUM(CAST(actual_churn AS INT64)), 0) AS recall
FROM `project.dataset.predictions_with_labels`
GROUP BY week
ORDER BY week DESC;

Continuous Training

Complete Example: Automated retraining pipeline

# Cloud Function triggered by Pub/Sub for model retraining
import functions_framework
from google.cloud import aiplatform, bigquery
from datetime import datetime

@functions_framework.cloud_event
def trigger_retraining(cloud_event):
    """Triggered by Pub/Sub message indicating model drift or performance degradation"""

    data = cloud_event.data
    trigger_reason = data.get('reason', 'scheduled')

    print(f"Retraining triggered: {trigger_reason}")

    # Check if retraining is needed
    if should_retrain():
        # Submit Vertex AI Pipeline job
        aiplatform.init(project='my-project', location='us-central1')

        job = aiplatform.PipelineJob(
            display_name=f'retraining_{datetime.now().strftime("%Y%m%d_%H%M%S")}',
            template_path='gs://my-bucket/pipelines/training_pipeline.json',
            parameter_values={
                'project_id': 'my-project',
                'dataset_id': 'ml_data',
                'training_date': datetime.now().isoformat()
            },
            labels={
                'trigger': trigger_reason,
                'automated': 'true'
            }
        )

        job.run(service_account='training-sa@my-project.iam.gserviceaccount.com')

        print(f"Retraining job submitted: {job.resource_name}")
    else:
        print("Retraining not needed based on current metrics")

def should_retrain():
    """Check if retraining is needed based on performance metrics"""
    client = bigquery.Client()

    # Query recent model performance
    query = '''
        SELECT
          precision,
          recall,
          f1_score
        FROM `my-project.ml_models.model_performance_tracking`
        ORDER BY week DESC
        LIMIT 1
    '''

    result = client.query(query).result()
    metrics = list(result)[0]

    # Retraining criteria
    if metrics.f1_score < 0.75:  # F1 score dropped below threshold
        return True
    if metrics.precision < 0.70 or metrics.recall < 0.70:  # Either metric too low
        return True

    return False

Scheduler-based retraining (Cloud Scheduler β†’ Pub/Sub β†’ Cloud Function):

# Create Cloud Scheduler job
gcloud scheduler jobs create pubsub weekly-model-retraining \
  --schedule="0 2 * * 1" \
  --time-zone="America/New_York" \
  --topic=model-retraining-trigger \
  --message-body='{"reason": "scheduled_weekly"}' \
  --description="Weekly automated model retraining"

# Create Pub/Sub topic
gcloud pubsub topics create model-retraining-trigger

# Deploy Cloud Function
gcloud functions deploy trigger-retraining \
  --gen2 \
  --runtime=python311 \
  --region=us-central1 \
  --source=. \
  --entry-point=trigger_retraining \
  --trigger-topic=model-retraining-trigger \
  --service-account=training-trigger-sa@my-project.iam.gserviceaccount.com

A/B Testing and Model Rollout

Complete Example: Traffic splitting for A/B testing

from google.cloud import aiplatform

# Deploy model v1 and v2 to same endpoint
endpoint = aiplatform.Endpoint('projects/123/locations/us-central1/endpoints/456')

# Deploy v2 with 20% traffic (canary deployment)
model_v2 = aiplatform.Model('projects/123/locations/us-central1/models/789')
deployed_model_v2 = model_v2.deploy(
    endpoint=endpoint,
    deployed_model_display_name='churn_model_v2',
    machine_type='n1-standard-4',
    min_replica_count=1,
    max_replica_count=5,
    traffic_percentage=20,  # 20% traffic to v2
    traffic_split={
        'deployed_model_v1_id': 80,  # 80% to v1
        'deployed_model_v2_id': 20   # 20% to v2
    }
)

# Monitor performance of both versions
# ... collect metrics ...

# If v2 performs better, gradually increase traffic
endpoint.update(
    traffic_split={
        'deployed_model_v1_id': 50,  # Reduce v1 to 50%
        'deployed_model_v2_id': 50   # Increase v2 to 50%
    }
)

# Eventually route all traffic to v2
endpoint.update(
    traffic_split={
        'deployed_model_v1_id': 0,
        'deployed_model_v2_id': 100
    }
)

# Rollback if issues detected
endpoint.update(
    traffic_split={
        'deployed_model_v1_id': 100,  # Revert to v1
        'deployed_model_v2_id': 0
    }
)

A/B testing analysis:

-- Compare model performance during A/B test
WITH predictions_with_model_version AS (
  SELECT
    customer_id,
    prediction,
    actual_outcome,
    model_version,
    prediction_timestamp
  FROM `project.dataset.ab_test_predictions`
  WHERE prediction_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
)
SELECT
  model_version,
  COUNT(*) AS total_predictions,
  AVG(CAST(prediction AS FLOAT64)) AS avg_predicted_prob,
  -- Accuracy (if ground truth available)
  SUM(CASE WHEN (prediction >= 0.5) = actual_outcome THEN 1 ELSE 0 END) / COUNT(*) AS accuracy,
  -- Precision
  SUM(CASE WHEN prediction >= 0.5 AND actual_outcome THEN 1 ELSE 0 END) /
    NULLIF(SUM(CASE WHEN prediction >= 0.5 THEN 1 ELSE 0 END), 0) AS precision,
  -- Recall
  SUM(CASE WHEN prediction >= 0.5 AND actual_outcome THEN 1 ELSE 0 END) /
    NULLIF(SUM(CAST(actual_outcome AS INT64)), 0) AS recall,
  -- Business metric: conversion rate for high-risk customers
  SUM(CASE WHEN prediction >= 0.7 AND actual_outcome THEN 1 ELSE 0 END) /
    NULLIF(SUM(CASE WHEN prediction >= 0.7 THEN 1 ELSE 0 END), 0) AS high_risk_precision
FROM predictions_with_model_version
GROUP BY model_version
ORDER BY model_version;

Model Serving Patterns

Batch vs Real-time Decision Matrix:

Pattern Latency Throughput Use Case GCP Service
Batch Minutes to hours Very high (millions/hour) Daily scoring, reports BigQuery ML, Vertex AI Batch
Real-time <100ms Medium (thousands/sec) User-facing predictions Vertex AI Endpoints
Near real-time Seconds High Stream processing Dataflow + BigQuery ML
Micro-batch Minutes High Periodic updates Scheduled queries

Batch prediction example:

# Vertex AI batch prediction
from google.cloud import aiplatform

model = aiplatform.Model('projects/123/locations/us-central1/models/456')

batch_job = model.batch_predict(
    job_display_name='daily_customer_scoring',
    bigquery_source='bq://my-project.dataset.customers_to_score',
    bigquery_destination_prefix='bq://my-project.predictions',
    instances_format='bigquery',
    predictions_format='bigquery',
    machine_type='n1-standard-4',
    starting_replica_count=10,
    max_replica_count=20
)

batch_job.wait()

Real-time serving example:

# Low-latency prediction service
from google.cloud import aiplatform
from flask import Flask, request, jsonify

app = Flask(__name__)

# Initialize endpoint
endpoint = aiplatform.Endpoint('projects/123/locations/us-central1/endpoints/456')

@app.route('/predict', methods=['POST'])
def predict():
    data = request.json

    # Prepare instance
    instance = {
        'age': data['age'],
        'income': data['income'],
        'tenure_months': data['tenure_months']
    }

    # Get prediction with low latency
    prediction = endpoint.predict(instances=[instance])

    return jsonify({
        'customer_id': data['customer_id'],
        'churn_probability': prediction.predictions[0]['probability'],
        'recommendation': 'contact' if prediction.predictions[0]['probability'] > 0.7 else 'monitor'
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Complete ML Scenarios for Data Engineers

Scenario 1: Customer Churn Prediction (Batch)

Requirements: Daily batch scoring of 10 million customers for churn risk.

Solution Architecture:

BigQuery (data warehouse) β†’ BigQuery ML (training/prediction) β†’
Scheduled queries (automation) β†’ Looker/Data Studio (visualization)

Complete Implementation:

-- Step 1: Create training features table
CREATE OR REPLACE TABLE `project.ml_data.churn_training_features` AS
SELECT
  customer_id,
  -- Demographics
  age,
  gender,
  region,
  -- Behavioral features
  tenure_months,
  total_spend_12m,
  avg_monthly_spend,
  transaction_count_30d,
  transaction_count_90d,
  days_since_last_transaction,
  -- Engagement features
  support_ticket_count,
  login_count_30d,
  email_open_rate,
  -- Product usage
  product_count,
  active_subscriptions,
  -- Target
  churned_90d AS label
FROM `project.analytics.customer_features`
WHERE feature_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 365 DAY);

-- Step 2: Train model
CREATE OR REPLACE MODEL `project.ml_models.churn_prediction_v1`
OPTIONS(
  model_type='BOOSTED_TREE_CLASSIFIER',  -- High accuracy for tabular data
  input_label_cols=['label'],
  auto_class_weights=TRUE,
  max_iterations=50,
  enable_global_explain=TRUE
) AS
SELECT * EXCEPT(customer_id) FROM `project.ml_data.churn_training_features`;

-- Step 3: Evaluate model
CREATE OR REPLACE TABLE `project.ml_models.churn_model_metrics` AS
SELECT
  CURRENT_DATE() AS evaluation_date,
  'v1' AS model_version,
  precision,
  recall,
  accuracy,
  f1_score,
  roc_auc
FROM ML.EVALUATE(MODEL `project.ml_models.churn_prediction_v1`);

-- Step 4: Create scheduled prediction procedure
CREATE OR REPLACE PROCEDURE `project.ml_data.daily_churn_scoring`()
BEGIN
  -- Generate predictions for all active customers
  CREATE OR REPLACE TABLE `project.predictions.churn_scores` AS
  SELECT
    customer_id,
    predicted_label AS predicted_churn,
    predicted_label_probs[OFFSET(1)].prob AS churn_probability,
    CASE
      WHEN predicted_label_probs[OFFSET(1)].prob >= 0.8 THEN 'CRITICAL'
      WHEN predicted_label_probs[OFFSET(1)].prob >= 0.5 THEN 'HIGH'
      WHEN predicted_label_probs[OFFSET(1)].prob >= 0.3 THEN 'MEDIUM'
      ELSE 'LOW'
    END AS risk_category,
    CURRENT_TIMESTAMP() AS scored_at
  FROM ML.PREDICT(
    MODEL `project.ml_models.churn_prediction_v1`,
    (SELECT * FROM `project.analytics.active_customers`)
  );

  -- Create action list for marketing team
  CREATE OR REPLACE TABLE `project.actions.high_risk_customers` AS
  SELECT
    c.customer_id,
    c.customer_name,
    c.email,
    cs.churn_probability,
    cs.risk_category,
    c.total_lifetime_value,
    c.last_contact_date
  FROM `project.predictions.churn_scores` cs
  JOIN `project.analytics.customers` c USING (customer_id)
  WHERE cs.risk_category IN ('CRITICAL', 'HIGH')
  ORDER BY cs.churn_probability DESC;
END;

-- Schedule via Cloud Scheduler or BigQuery Transfer Service

Key Decisions: - Use BigQuery ML (not Vertex AI) because data is already in BigQuery and batch processing is sufficient - Use BOOSTED_TREE_CLASSIFIER for best accuracy on tabular data - Schedule with BigQuery scheduled queries for simplicity

Scenario 2: Real-time Fraud Detection

Requirements: Detect fraudulent transactions in <200ms for online payments.

Solution Architecture:

Payment API β†’ Pub/Sub (buffer) β†’ Dataflow (feature engineering) β†’
Vertex AI Endpoint (real-time prediction) β†’ Bigtable (fast lookup) β†’
BigQuery (analytics/retraining)

Complete Implementation:

# Dataflow pipeline for real-time fraud detection
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
from google.cloud import aiplatform, bigtable
import json

class EnrichTransactionFn(beam.DoFn):
    def process(self, transaction):
        # Enrich transaction with historical features from Bigtable
        customer_id = transaction['customer_id']

        # Fetch customer history (cached in Bigtable)
        # ... Bigtable lookup ...

        features = {
            'transaction_id': transaction['transaction_id'],
            'amount': transaction['amount'],
            'merchant_category': transaction['merchant_category'],
            'hour_of_day': int(transaction['timestamp'].split('T')[1].split(':')[0]),
            # Historical features
            'avg_transaction_30d': 150.25,  # From Bigtable
            'transaction_count_24h': 3,  # From Bigtable
            'time_since_last_transaction_mins': 120  # Calculated
        }

        yield features

class PredictFraudFn(beam.DoFn):
    def __init__(self, endpoint_id):
        self.endpoint_id = endpoint_id
        self.endpoint = None

    def setup(self):
        # Initialize endpoint connection once per worker
        self.endpoint = aiplatform.Endpoint(self.endpoint_id)

    def process(self, features):
        # Get prediction
        prediction = self.endpoint.predict(instances=[features])
        fraud_probability = prediction.predictions[0]['probability']

        result = {
            'transaction_id': features['transaction_id'],
            'fraud_probability': fraud_probability,
            'decision': 'BLOCK' if fraud_probability > 0.9 else 'ALLOW' if fraud_probability < 0.3 else 'REVIEW',
            'timestamp': features['timestamp']
        }

        yield result

class WriteToBigtableFn(beam.DoFn):
    def process(self, result):
        # Write decision to Bigtable for fast lookup
        # ... Bigtable write ...
        yield result

def run_fraud_detection_pipeline():
    options = PipelineOptions(
        streaming=True,
        project='my-project',
        region='us-central1'
    )

    with beam.Pipeline(options=options) as p:
        predictions = (
            p
            | 'ReadPubSub' >> beam.io.ReadFromPubSub(
                subscription='projects/my-project/subscriptions/transactions'
            )
            | 'ParseJSON' >> beam.Map(lambda x: json.loads(x.decode('utf-8')))
            | 'EnrichFeatures' >> beam.ParDo(EnrichTransactionFn())
            | 'PredictFraud' >> beam.ParDo(PredictFraudFn(
                endpoint_id='projects/123/locations/us-central1/endpoints/456'
            ))
            | 'WriteToBigtable' >> beam.ParDo(WriteToBigtableFn())
            | 'WriteToBigQuery' >> beam.io.WriteToBigQuery(
                table='my-project:fraud.predictions',
                schema='transaction_id:STRING,fraud_probability:FLOAT,decision:STRING,timestamp:TIMESTAMP',
                write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND
            )
        )

# Run pipeline
run_fraud_detection_pipeline()

Key Decisions: - Use Vertex AI Endpoint (not BigQuery ML) for low-latency real-time predictions - Use Bigtable for fast feature lookup and result storage - Use Dataflow for stream processing and feature engineering - Store predictions in BigQuery for model retraining

Scenario 3: Product Recommendation System

Requirements: Generate personalized product recommendations for 5 million users.

Solution Architecture:

BigQuery (user interactions) β†’ BigQuery ML (matrix factorization) β†’
Cloud Functions (serving API) β†’ Memorystore (caching) β†’ Application

Complete Implementation:

-- Step 1: Prepare user-item interaction matrix
CREATE OR REPLACE TABLE `project.ml_data.user_product_interactions` AS
SELECT
  user_id,
  product_id,
  -- Implicit feedback: higher rating = more interaction
  SUM(view_count) * 1 +
  SUM(cart_add_count) * 3 +
  SUM(purchase_count) * 5 AS rating
FROM `project.analytics.user_behavior`
WHERE interaction_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
GROUP BY user_id, product_id
HAVING rating > 0;

-- Step 2: Train matrix factorization model
CREATE OR REPLACE MODEL `project.ml_models.product_recommendations`
OPTIONS(
  model_type='MATRIX_FACTORIZATION',
  user_col='user_id',
  item_col='product_id',
  rating_col='rating',
  feedback_type='implicit',  -- No explicit ratings, only interactions
  num_factors=50,  -- Embedding dimensions
  l2_reg=0.1,
  max_iterations=30
) AS
SELECT user_id, product_id, rating
FROM `project.ml_data.user_product_interactions`;

-- Step 3: Generate recommendations for all users
CREATE OR REPLACE TABLE `project.recommendations.user_product_recommendations` AS
SELECT
  user_id,
  ARRAY_AGG(
    STRUCT(product_id, predicted_rating)
    ORDER BY predicted_rating DESC
    LIMIT 20
  ) AS recommendations
FROM ML.RECOMMEND(MODEL `project.ml_models.product_recommendations`)
GROUP BY user_id;

-- Step 4: Get recommendations for specific user (via Cloud Function)
-- This query would be called from Cloud Function API
SELECT
  product_id,
  predicted_rating AS score
FROM ML.RECOMMEND(MODEL `project.ml_models.product_recommendations`)
WHERE user_id = @user_id
ORDER BY predicted_rating DESC
LIMIT 10;

Cloud Function for serving:

import functions_framework
from google.cloud import bigquery
import json

# Initialize BigQuery client
bq_client = bigquery.Client()

@functions_framework.http
def get_recommendations(request):
    """HTTP Cloud Function for product recommendations"""
    request_json = request.get_json()
    user_id = request_json.get('user_id')

    if not user_id:
        return {'error': 'user_id required'}, 400

    # Query pre-computed recommendations
    query = f'''
        SELECT recommendations
        FROM `project.recommendations.user_product_recommendations`
        WHERE user_id = @user_id
    '''

    job_config = bigquery.QueryJobConfig(
        query_parameters=[
            bigquery.ScalarQueryParameter('user_id', 'STRING', user_id)
        ]
    )

    result = bq_client.query(query, job_config=job_config).result()
    rows = list(result)

    if not rows:
        return {'error': 'User not found'}, 404

    recommendations = [
        {'product_id': r['product_id'], 'score': r['predicted_rating']}
        for r in rows[0]['recommendations'][:10]
    ]

    return {'user_id': user_id, 'recommendations': recommendations}

Key Decisions: - Use BigQuery ML matrix factorization for collaborative filtering - Pre-compute recommendations for all users (batch) - Serve via Cloud Functions with caching in Memorystore for low latency - Retrain weekly to incorporate new user behavior

Scenario 4: Time Series Forecasting for Demand Planning

Requirements: Forecast product demand for next 30 days across 10,000 products.

Solution Architecture:

BigQuery (sales data) β†’ BigQuery ML (ARIMA_PLUS per product) β†’
Scheduled queries (daily refresh) β†’ Data Studio (visualization)

Complete Implementation:

-- Step 1: Prepare time series data
CREATE OR REPLACE TABLE `project.ml_data.product_demand_history` AS
SELECT
  DATE(order_timestamp) AS date,
  product_id,
  SUM(quantity) AS daily_demand,
  AVG(unit_price) AS avg_price,
  SUM(revenue) AS daily_revenue
FROM `project.sales.orders`
WHERE order_timestamp >= DATE_SUB(CURRENT_DATE(), INTERVAL 730 DAY)  -- 2 years history
GROUP BY date, product_id
ORDER BY product_id, date;

-- Step 2: Train ARIMA_PLUS model (one model for all products)
CREATE OR REPLACE MODEL `project.ml_models.demand_forecast`
OPTIONS(
  model_type='ARIMA_PLUS',
  time_series_timestamp_col='date',
  time_series_data_col='daily_demand',
  time_series_id_col='product_id',  -- Separate forecast per product
  auto_arima=TRUE,
  data_frequency='DAILY',
  holiday_region='US',
  adjust_step_changes=TRUE,  -- Detect promotions/events
  enable_auto_arima_seasonality=TRUE
) AS
SELECT date, product_id, daily_demand
FROM `project.ml_data.product_demand_history`;

-- Step 3: Generate 30-day forecasts
CREATE OR REPLACE TABLE `project.forecasts.demand_next_30_days` AS
SELECT
  forecast_timestamp AS date,
  time_series_id AS product_id,
  forecast_value AS predicted_demand,
  prediction_interval_lower_bound AS lower_bound,
  prediction_interval_upper_bound AS upper_bound,
  confidence_level,
  CURRENT_TIMESTAMP() AS forecast_generated_at
FROM ML.FORECAST(
  MODEL `project.ml_models.demand_forecast`,
  STRUCT(30 AS horizon, 0.95 AS confidence_level)
);

-- Step 4: Aggregate forecasts for purchasing decisions
CREATE OR REPLACE TABLE `project.forecasts.purchasing_plan` AS
SELECT
  product_id,
  SUM(predicted_demand) AS total_demand_30d,
  SUM(lower_bound) AS conservative_demand,
  SUM(upper_bound) AS optimistic_demand,
  AVG(predicted_demand) AS avg_daily_demand
FROM `project.forecasts.demand_next_30_days`
GROUP BY product_id;

-- Step 5: Detect anomalies in recent sales
CREATE OR REPLACE TABLE `project.monitoring.demand_anomalies` AS
SELECT
  history_timestamp AS date,
  time_series_id AS product_id,
  history_value AS actual_demand,
  is_anomaly,
  anomaly_probability,
  lower_bound AS expected_lower,
  upper_bound AS expected_upper
FROM ML.DETECT_ANOMALIES(
  MODEL `project.ml_models.demand_forecast`,
  STRUCT(0.9 AS anomaly_prob_threshold)
)
WHERE is_anomaly = TRUE
  AND history_timestamp >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
ORDER BY history_timestamp DESC, anomaly_probability DESC;

Key Decisions: - Use BigQuery ML ARIMA_PLUS for time series forecasting - Use time_series_id_col to train separate models for each product efficiently - Include holiday effects and step change detection - Generate anomaly detection for supply chain alerts

Scenario 5: Image Classification Pipeline for E-commerce

Requirements: Classify product images into categories for search and recommendations.

Solution Architecture:

Cloud Storage (image upload) β†’ Cloud Functions (trigger) β†’
Vertex AI AutoML Vision (classification) β†’ Firestore (metadata) β†’
BigQuery (analytics)

Complete Implementation:

# Cloud Function triggered by Cloud Storage upload
import functions_framework
from google.cloud import aiplatform, firestore, bigquery, storage
from datetime import datetime

@functions_framework.cloud_event
def classify_product_image(cloud_event):
    """Triggered by Cloud Storage upload"""

    data = cloud_event.data
    bucket_name = data['bucket']
    file_name = data['name']
    gcs_uri = f'gs://{bucket_name}/{file_name}'

    print(f'Processing image: {gcs_uri}')

    # Initialize clients
    aiplatform.init(project='my-project', location='us-central1')
    db = firestore.Client()
    bq_client = bigquery.Client()

    # Get prediction from AutoML Vision model
    endpoint = aiplatform.Endpoint('projects/123/locations/us-central1/endpoints/789')

    # For AutoML Vision, we pass the GCS URI
    prediction = endpoint.predict(instances=[{'content': gcs_uri}])

    # Extract top predictions
    top_predictions = sorted(
        prediction.predictions[0]['displayNames'],
        key=lambda x: x['confidence'],
        reverse=True
    )[:3]

    result = {
        'image_uri': gcs_uri,
        'product_id': file_name.split('/')[1].replace('.jpg', ''),
        'primary_category': top_predictions[0]['displayName'],
        'primary_confidence': top_predictions[0]['confidence'],
        'all_predictions': top_predictions,
        'classified_at': datetime.utcnow()
    }

    # Write to Firestore for application access
    doc_ref = db.collection('product_classifications').document(result['product_id'])
    doc_ref.set(result)

    # Write to BigQuery for analytics
    table_id = 'my-project.ml_analytics.image_classifications'
    errors = bq_client.insert_rows_json(table_id, [result])

    if errors:
        print(f'BigQuery insert errors: {errors}')
    else:
        print(f'Successfully classified: {result["product_id"]} as {result["primary_category"]}')

    return result

Training AutoML Vision model:

from google.cloud import aiplatform

aiplatform.init(project='my-project', location='us-central1')

# Create dataset
dataset = aiplatform.ImageDataset.create(
    display_name='product_categories',
    gcs_source='gs://my-bucket/training_images/metadata.csv',
    import_schema_uri=aiplatform.schema.dataset.ioformat.image.single_label_classification
)

# Train AutoML Vision model
job = aiplatform.AutoMLImageTrainingJob(
    display_name='product_classifier',
    prediction_type='classification',
    model_type='CLOUD',
    multi_label=False
)

model = job.run(
    dataset=dataset,
    model_display_name='product_category_classifier',
    training_fraction_split=0.8,
    validation_fraction_split=0.1,
    test_fraction_split=0.1,
    budget_milli_node_hours=8000  # 8 hours
)

# Deploy model
endpoint = model.deploy(
    deployed_model_display_name='product_classifier_v1',
    machine_type='n1-standard-4',
    min_replica_count=1,
    max_replica_count=5
)

Key Decisions: - Use Vertex AI AutoML Vision for image classification without custom code - Use Cloud Functions for event-driven processing - Store metadata in Firestore for low-latency application access - Store analytics in BigQuery for reporting

Scenario 6: Sentiment Analysis Pipeline for Customer Reviews

Requirements: Analyze sentiment of customer reviews in real-time for product insights.

Solution Architecture:

Application β†’ Pub/Sub (streaming) β†’ Dataflow (enrichment) β†’
Natural Language API (sentiment) β†’ BigQuery (storage/analysis)

Complete Implementation:

import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
from google.cloud import language_v1
import json

class SentimentAnalysisFn(beam.DoFn):
    def setup(self):
        self.client = language_v1.LanguageServiceClient()

    def process(self, review):
        # Analyze sentiment
        document = language_v1.Document(
            content=review['review_text'],
            type_=language_v1.Document.Type.PLAIN_TEXT
        )

        sentiment = self.client.analyze_sentiment(request={'document': document}).document_sentiment
        entities = self.client.analyze_entities(request={'document': document}).entities

        # Enrich review with sentiment
        enriched_review = {
            'review_id': review['review_id'],
            'product_id': review['product_id'],
            'customer_id': review['customer_id'],
            'review_text': review['review_text'],
            'sentiment_score': sentiment.score,  # -1 to 1
            'sentiment_magnitude': sentiment.magnitude,  # 0+ (strength)
            'sentiment_label': self._get_sentiment_label(sentiment.score),
            'entities': [{'name': e.name, 'type': e.type_.name} for e in entities[:5]],
            'processed_at': beam.window.TimestampedValue.now()
        }

        yield enriched_review

    def _get_sentiment_label(self, score):
        if score >= 0.25:
            return 'POSITIVE'
        elif score <= -0.25:
            return 'NEGATIVE'
        else:
            return 'NEUTRAL'

def run_sentiment_pipeline():
    options = PipelineOptions(
        streaming=True,
        project='my-project',
        region='us-central1'
    )

    with beam.Pipeline(options=options) as p:
        enriched_reviews = (
            p
            | 'ReadPubSub' >> beam.io.ReadFromPubSub(
                subscription='projects/my-project/subscriptions/customer-reviews'
            )
            | 'ParseJSON' >> beam.Map(lambda x: json.loads(x.decode('utf-8')))
            | 'AnalyzeSentiment' >> beam.ParDo(SentimentAnalysisFn())
            | 'WriteToBigQuery' >> beam.io.WriteToBigQuery(
                table='my-project:reviews.sentiment_analysis',
                schema='review_id:STRING,product_id:STRING,customer_id:STRING,review_text:STRING,sentiment_score:FLOAT,sentiment_magnitude:FLOAT,sentiment_label:STRING,entities:STRING,processed_at:TIMESTAMP',
                write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND
            )
        )

run_sentiment_pipeline()

Analysis queries:

-- Product sentiment dashboard
CREATE OR REPLACE VIEW `project.analytics.product_sentiment_summary` AS
SELECT
  product_id,
  COUNT(*) AS total_reviews,
  COUNTIF(sentiment_label = 'POSITIVE') AS positive_count,
  COUNTIF(sentiment_label = 'NEGATIVE') AS negative_count,
  COUNTIF(sentiment_label = 'NEUTRAL') AS neutral_count,
  AVG(sentiment_score) AS avg_sentiment_score,
  AVG(sentiment_magnitude) AS avg_sentiment_magnitude,
  -- Overall sentiment
  CASE
    WHEN AVG(sentiment_score) >= 0.25 THEN 'POSITIVE'
    WHEN AVG(sentiment_score) <= -0.25 THEN 'NEGATIVE'
    ELSE 'NEUTRAL'
  END AS overall_sentiment
FROM `project.reviews.sentiment_analysis`
WHERE processed_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
GROUP BY product_id;

Key Decisions: - Use Natural Language API for pre-trained sentiment analysis - Use Dataflow for stream processing at scale - Store enriched data in BigQuery for analytics and dashboards - Real-time processing enables immediate product insights

Exam Tips for Professional Data Engineer

Key Decision Framework

When to use BigQuery ML vs Vertex AI:

Factor Use BigQuery ML Use Vertex AI
Data location Data in BigQuery Data in GCS or distributed
Model complexity Linear/logistic regression, DNN, K-means, ARIMA Custom TensorFlow/PyTorch, complex architectures
Latency requirements Batch predictions (minutes) Real-time predictions (<100ms)
Team skills SQL proficiency Python/ML engineering expertise
Deployment In-database predictions Microservices, endpoints
Integration Existing BigQuery workflows Kubernetes, microservices
Cost model Pay per query (bytes scanned) Pay for compute (nodes, accelerators)

Choosing model types: - Classification (binary/multiclass): LOGISTIC_REG, DNN_CLASSIFIER, BOOSTED_TREE_CLASSIFIER - Regression (numeric prediction): LINEAR_REG, DNN_REGRESSOR, BOOSTED_TREE_REGRESSOR - Clustering (grouping): KMEANS - Recommendation: MATRIX_FACTORIZATION - Time series: ARIMA_PLUS - Auto selection: AUTOML_CLASSIFIER, AUTOML_REGRESSOR

Serving pattern decisions: - Batch (millions/day): BigQuery ML predictions, Vertex AI batch prediction - Real-time (<100ms): Vertex AI endpoints with auto-scaling - Near real-time (seconds): Dataflow + BigQuery ML or Vertex AI - Micro-batch (minutes): Scheduled BigQuery queries

Common Exam Question Patterns

Pattern 1: "Company needs to predict customer churn using data in BigQuery" - Answer: BigQuery ML with LOGISTIC_REG or BOOSTED_TREE_CLASSIFIER - Why: Data already in BigQuery, batch predictions sufficient, SQL-based

Pattern 2: "Real-time fraud detection with <200ms latency requirement" - Answer: Vertex AI endpoint with Dataflow for feature engineering - Why: Low latency requirement, real-time serving needed

Pattern 3: "Forecast sales for 50,000 products over next 30 days" - Answer: BigQuery ML ARIMA_PLUS with time_series_id_col - Why: Multiple time series, batch forecasting, efficient for many series

Pattern 4: "Analyze sentiment of customer reviews in real-time" - Answer: Pub/Sub β†’ Dataflow β†’ Natural Language API β†’ BigQuery - Why: Pre-trained NLP model, streaming data, scale to millions

Pattern 5: "Classify product images uploaded by users" - Answer: Cloud Storage β†’ Cloud Functions β†’ Vertex AI AutoML Vision - Why: Image classification, event-driven, no ML expertise needed

Pattern 6: "Product recommendations for millions of users" - Answer: BigQuery ML MATRIX_FACTORIZATION with batch pre-computation - Why: Collaborative filtering, batch processing, serve from cache

Pattern 7: "Model performance degrading, need automated retraining" - Answer: Cloud Monitoring alerts β†’ Pub/Sub β†’ Cloud Functions β†’ Vertex AI Pipelines - Why: Monitoring-driven automation, retraining workflow orchestration

Pattern 8: "A/B test two model versions" - Answer: Vertex AI endpoint with traffic splitting - Why: Built-in traffic management, gradual rollout capability

Critical Exam Knowledge

BigQuery ML Essentials: - CREATE MODEL with OPTIONS (model_type, input_label_cols, etc.) - ML.EVALUATE for model assessment (precision, recall, F1, AUC) - ML.PREDICT for generating predictions - ML.FEATURE_IMPORTANCE for understanding models - ML.EXPLAIN_PREDICT for individual prediction explanations - ML.FORECAST for time series predictions - ML.DETECT_ANOMALIES for anomaly detection - ML.RECOMMEND for recommendations - Export/import models with EXPORT MODEL and CREATE MODEL with model_path

Vertex AI Essentials: - AutoML for automatic model selection (TabularDataset, ImageDataset) - Custom training with CustomTrainingJob (TensorFlow, PyTorch, Scikit-learn) - Model deployment to Endpoint (online serving) - Batch prediction for large-scale inference - Hyperparameter tuning with HyperparameterTuningJobSpec - Feature Store for centralized feature management - Model monitoring for drift detection - Vertex AI Pipelines (Kubeflow) for ML workflows

Feature Engineering Techniques: - Temporal features (EXTRACT, DATE functions) - Window functions for aggregation (AVG OVER, LAG, LEAD) - ML.FEATURE_CROSS for interaction features - ML.BUCKETIZE for discretization - ML.QUANTILE_BUCKETIZE for equal-sized bins - ML.NGRAMS for text features - One-hot encoding (automatic for STRING columns) - Normalization (automatic or ML.STANDARD_SCALER)

MLOps Best Practices: - Model versioning with labels and version_aliases - Monitoring: data drift, prediction drift, training-serving skew - Continuous training triggers: scheduled, performance-based, drift-based - A/B testing with traffic splitting - Canary deployments (gradual rollout) - Rollback strategies (quick revert to previous version)

Data Pipeline Integration: - BigQuery β†’ BigQuery ML (simplest for batch) - Pub/Sub β†’ Dataflow β†’ Vertex AI Endpoint (real-time) - Dataflow β†’ Feature Store β†’ Vertex AI (feature management) - Cloud Composer β†’ BigQuery ML (orchestrated batch ML) - Vertex AI Pipelines (end-to-end ML workflow automation)

Cost Optimization: - BigQuery ML: Optimize query costs (partitioned tables, clustered columns) - Vertex AI training: Right-size machine types, use preemptible instances - Vertex AI serving: Auto-scaling, batch predictions for non-real-time use cases - Pre-trained APIs: Batch API calls, cache results - Feature Store: Optimize online/offline serving based on use case

Hands-on Practice Checklist

  • Create BigQuery ML models for all major types (LINEAR_REG, LOGISTIC_REG, DNN, KMEANS, ARIMA_PLUS)
  • Train and deploy Vertex AI AutoML model (Tabular or Vision)
  • Build custom training job with TensorFlow on Vertex AI
  • Deploy model to Vertex AI endpoint and make predictions
  • Create Vertex AI Pipeline with multiple components
  • Set up model monitoring for drift detection
  • Implement A/B testing with traffic splitting
  • Build Dataflow pipeline with ML.PREDICT or Vertex AI endpoint calls
  • Create Cloud Composer DAG for ML retraining
  • Use Natural Language API or Vision API in data pipeline
  • Implement feature engineering with window functions and ML functions
  • Export BigQuery ML model and import into Vertex AI

Common Pitfalls to Avoid

  1. Using Vertex AI when BigQuery ML is sufficient - If data is in BigQuery and batch predictions work, use BigQuery ML
  2. Not considering latency requirements - Real-time (<100ms) requires Vertex AI endpoints, not BigQuery ML
  3. Forgetting auto_class_weights for imbalanced data - Always use for classification with class imbalance
  4. Not using time_series_id_col for multiple time series - Efficient way to forecast many series at once
  5. Ignoring feature engineering - Temporal features, lag features, and window aggregations are critical
  6. Not monitoring for drift - Production models need data drift and prediction drift monitoring
  7. Using wrong model type - BOOSTED_TREE often better than LINEAR for tabular data
  8. Not versioning models - Always version models with labels and metadata
  9. Deploying without evaluation - Always evaluate on held-out test set before deployment
  10. Overcomplicating architecture - Choose simplest solution that meets requirements

Quick Reference Commands

BigQuery ML:

-- Create model
CREATE MODEL dataset.model OPTIONS(model_type='LOGISTIC_REG', input_label_cols=['label']) AS SELECT * FROM dataset.training;

-- Evaluate
SELECT * FROM ML.EVALUATE(MODEL dataset.model);

-- Predict
SELECT * FROM ML.PREDICT(MODEL dataset.model, (SELECT * FROM dataset.input));

-- Feature importance
SELECT * FROM ML.FEATURE_IMPORTANCE(MODEL dataset.model);

-- Export
EXPORT MODEL dataset.model OPTIONS(uri='gs://bucket/model/*');

Vertex AI Python:

# Train AutoML
from google.cloud import aiplatform
aiplatform.init(project='project', location='region')
dataset = aiplatform.TabularDataset.create(display_name='ds', bq_source='bq://project.dataset.table')
job = aiplatform.AutoMLTabularTrainingJob(display_name='job', optimization_prediction_type='classification')
model = job.run(dataset=dataset, target_column='label')

# Deploy
endpoint = model.deploy(machine_type='n1-standard-4', min_replica_count=1)

# Predict
predictions = endpoint.predict(instances=[{'feature1': value1, 'feature2': value2}])

gcloud commands:

# Create training job
gcloud ai custom-jobs create --region=us-central1 --display-name=job --worker-pool-spec=machine-type=n1-standard-8,replica-count=1,python-package-uris=gs://bucket/trainer.tar.gz,python-module=trainer.task

# Deploy model
gcloud ai endpoints create --region=us-central1 --display-name=endpoint
gcloud ai endpoints deploy-model ENDPOINT_ID --region=us-central1 --model=MODEL_ID --display-name=deployed --machine-type=n1-standard-4

# Batch prediction
gcloud ai batch-prediction-jobs create --region=us-central1 --model=MODEL_ID --job-display-name=batch-job --gcs-source-uri=gs://bucket/input.csv --gcs-destination-output-uri-prefix=gs://bucket/output/

Additional Resources

Official Documentation

Key Reference Pages

Practice Labs

Exam Preparation


Document Statistics: - Original length: 238 lines - Enhanced length: ~3,400 lines - Coverage: Comprehensive BigQuery ML (all model types), Vertex AI integration, complete ML scenarios, MLOps patterns, and exam-focused tips - Code examples: 50+ complete implementations - Scenarios: 6 end-to-end data engineering ML scenarios - Focus: Professional Data Engineer exam requirements for ML operations