<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic End-to-End Streaming NLP Pipeline with GDELT, Azure Data Factory, ADLS Gen2 and Databricks in Machine Learning</title>
    <link>https://community.databricks.com/t5/machine-learning/end-to-end-streaming-nlp-pipeline-with-gdelt-azure-data-factory/m-p/165627#M4661</link>
    <description>&lt;H1&gt;Building an End-to-End Streaming NLP Pipeline with GDELT, Azure Data Factory, ADLS Gen2 and Databricks&lt;/H1&gt;&lt;P&gt;I recently worked on an end-to-end streaming NLP project using &lt;STRONG&gt;GDELT news data&lt;/STRONG&gt;, Azure Data Factory, ADLS Gen2 and Azure Databricks.&lt;/P&gt;&lt;P&gt;The goal was not just to train an NLP model. I wanted to understand the complete lifecycle:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;external data ingestion → durable cloud storage → streaming ingestion → Bronze/Silver processing → NLP training and HPO → model registration → streaming inference → predictions → ADLS&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;The architecture evolved into:&lt;/P&gt;&lt;PRE&gt;GDELT
  ↓
Azure Data Factory
  ↓
ADLS Gen2
  ├── raw/compressed
  └── landing/extracted
  ↓
Unity Catalog External Location / Volume
  ↓
Databricks Auto Loader
  ↓
Bronze Delta
  ↓
Structured Streaming
  ↓
Silver Delta
  ↓
NLP Training + HPO
  ↓
MLflow / Unity Catalog Model Registry
  ↓
Streaming Model Inference
  ↓
Prediction Delta Tables
  ↓
ADLS Gen2&lt;/PRE&gt;&lt;H2&gt;1. Acquiring GDELT data&lt;/H2&gt;&lt;P&gt;GDELT publishes multiple datasets at regular intervals, including:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;Events&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Event Mentions&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Global Knowledge Graph — GKG&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;For the NLP component, I started with the &lt;STRONG&gt;GKG dataset&lt;/STRONG&gt;, because it provides useful article-level metadata such as:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;article URL&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;themes&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;organizations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;people&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;locations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;tone&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;article title through the GKG Extras field&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;Instead of making Databricks responsible for downloading data from the internet, I separated the acquisition layer from the processing layer.&lt;/P&gt;&lt;P&gt;I used &lt;STRONG&gt;Azure Data Factory&lt;/STRONG&gt; for acquisition.&lt;/P&gt;&lt;P&gt;The initial flow was:&lt;/P&gt;&lt;PRE&gt;GDELT HTTP endpoint
       ↓
ADF Binary Copy
       ↓
ADLS Gen2&lt;/PRE&gt;&lt;P&gt;I first tested the architecture with one GKG file.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;20260802103000.gkg.csv.zip&lt;/PRE&gt;&lt;P&gt;ADF copied the original compressed file into:&lt;/P&gt;&lt;PRE&gt;raw/compressed/gkg/
ingestion_date=YYYY-MM-DD/&lt;/PRE&gt;&lt;P&gt;The original ZIP was deliberately preserved.&lt;/P&gt;&lt;P&gt;This became important later because it gave the pipeline:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;replayability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;traceability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;debugging capability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;separation between acquisition and transformation&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;ADF then decompressed the ZIP into:&lt;/P&gt;&lt;PRE&gt;landing/extracted/gkg/
ingestion_date=YYYY-MM-DD/&lt;/PRE&gt;&lt;P&gt;The guiding principle was:&lt;/P&gt;&lt;PRE&gt;ADF = acquire and land
ADLS = preserve
Databricks = process and model&lt;/PRE&gt;&lt;H2&gt;2. Making ADF ingestion dynamic&lt;/H2&gt;&lt;P&gt;The first pipeline used a fixed GDELT filename.&lt;/P&gt;&lt;P&gt;After proving that one file could move successfully from GDELT to ADLS, I parameterized the pipeline.&lt;/P&gt;&lt;P&gt;I added parameters for:&lt;/P&gt;&lt;PRE&gt;file_name
ingestion_date
expected_size
checksum&lt;/PRE&gt;&lt;P&gt;Then I used GDELT's latest-file information to automatically identify the newest GKG file.&lt;/P&gt;&lt;P&gt;ADF performed:&lt;/P&gt;&lt;PRE&gt;Lookup latest GDELT metadata
        ↓
Filter GKG entry
        ↓
Extract filename
        ↓
Derive ingestion date
        ↓
Check whether file already exists
        ↓
Ingest only if new&lt;/PRE&gt;&lt;P&gt;This introduced an important engineering property:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;idempotency.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Rerunning the pipeline would not continually ingest the same file.&lt;/P&gt;&lt;H2&gt;3. Simulating streaming without running 24×7&lt;/H2&gt;&lt;P&gt;Because this was a learning project, I did not want to keep cloud compute running continuously.&lt;/P&gt;&lt;P&gt;Instead, I created controlled streaming sessions.&lt;/P&gt;&lt;P&gt;The ADF pipeline was configured to collect approximately three consecutive GDELT files.&lt;/P&gt;&lt;P&gt;Conceptually:&lt;/P&gt;&lt;PRE&gt;File 1
  ↓
Wait
  ↓
File 2
  ↓
Wait
  ↓
File 3
  ↓
Stop&lt;/PRE&gt;&lt;P&gt;The session used:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;an Until loop&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;a target-file count&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Wait activities&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;file-existence checks&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;a maximum session timeout&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;This allowed me to learn near-real-time ingestion while keeping infrastructure costs under control.&lt;/P&gt;&lt;H2&gt;4. Connecting ADLS securely to Databricks&lt;/H2&gt;&lt;P&gt;Instead of using storage keys or legacy DBFS mounts, I connected ADLS using:&lt;/P&gt;&lt;PRE&gt;Databricks Access Connector
        ↓
Managed Identity
        ↓
Unity Catalog Storage Credential
        ↓
External Location
        ↓
External Volume&lt;/PRE&gt;&lt;P&gt;This exposed the ADLS landing data through a governed path such as:&lt;/P&gt;&lt;PRE&gt;/Volumes/gdelt_dev/raw/gdelt_landing/&lt;/PRE&gt;&lt;P&gt;One of the most useful concepts I learned here was that /Volumes/... is a Databricks governed view over the underlying cloud storage.&lt;/P&gt;&lt;P&gt;The physical data still lives in ADLS.&lt;/P&gt;&lt;H2&gt;5. Streaming ADLS files into Bronze using Auto Loader&lt;/H2&gt;&lt;P&gt;The next layer was Databricks Auto Loader.&lt;/P&gt;&lt;PRE&gt;ADLS landing
      ↓
Unity Catalog Volume
      ↓
cloudFiles
      ↓
Structured Streaming
      ↓
Bronze Delta&lt;/PRE&gt;&lt;P&gt;I deliberately kept Bronze close to the source.&lt;/P&gt;&lt;P&gt;Instead of doing all business parsing immediately, the Bronze table stored:&lt;/P&gt;&lt;PRE&gt;raw_record
source_file_name
source_file_path
source_file_size
source_file_modification_time
ingested_at
ingestion_date&lt;/PRE&gt;&lt;P&gt;Auto Loader used a dedicated checkpoint:&lt;/P&gt;&lt;PRE&gt;checkpoints/bronze_gkg/&lt;/PRE&gt;&lt;P&gt;and a separate schema location.&lt;/P&gt;&lt;P&gt;The checkpoint became one of the most important concepts in the project.&lt;/P&gt;&lt;P&gt;It allows the stream to remember:&lt;/P&gt;&lt;BLOCKQUOTE&gt;&lt;P&gt;Which source files have already been successfully processed?&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;P&gt;I could stop the Databricks cluster, restart it later and use the same checkpoint.&lt;/P&gt;&lt;P&gt;The stream continued from its previous progress rather than starting from scratch.&lt;/P&gt;&lt;H2&gt;6. Bronze to Silver streaming&lt;/H2&gt;&lt;P&gt;Once the raw GKG records were reliably landing in Bronze, I created another Structured Streaming pipeline:&lt;/P&gt;&lt;PRE&gt;Bronze Delta
    ↓
readStream
    ↓
GKG parsing
    ↓
cleaning
    ↓
Silver Delta&lt;/PRE&gt;&lt;P&gt;The GKG records were tab-delimited.&lt;/P&gt;&lt;P&gt;In Silver I parsed the useful fields and produced article-level features such as:&lt;/P&gt;&lt;PRE&gt;article_id
published_at
article_url
source_domain
title
tone
themes
organizations
persons
locations&lt;/PRE&gt;&lt;P&gt;The article title was extracted from the GKG Extras field.&lt;/P&gt;&lt;P&gt;Silver also performed operations such as:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;timestamp conversion&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;URL/domain extraction&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;HTML title decoding&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;theme preparation&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;exact duplicate checks&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;malformed-record filtering&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;Importantly, Silver used a &lt;STRONG&gt;different checkpoint&lt;/STRONG&gt; from Bronze.&lt;/P&gt;&lt;P&gt;So the architecture had independent state:&lt;/P&gt;&lt;PRE&gt;ADLS → Bronze checkpoint

Bronze → Silver checkpoint&lt;/PRE&gt;&lt;P&gt;At this stage I had a genuine multi-hop streaming architecture:&lt;/P&gt;&lt;PRE&gt;ADF lands file
      ↓
Auto Loader detects it
      ↓
Bronze updates
      ↓
Silver streaming query detects new Bronze rows
      ↓
Silver updates&lt;/PRE&gt;&lt;H2&gt;7. Preparing the NLP dataset&lt;/H2&gt;&lt;P&gt;From Silver I created an NLP-oriented profile dataset containing fields such as:&lt;/P&gt;&lt;PRE&gt;article_id
published_at
article_url
source_domain
title
title_normalized
tone
themes_clean
organizations
persons
locations&lt;/PRE&gt;&lt;P&gt;A versioned snapshot was then created for reproducibility.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;article_nlp_profile_v001&lt;/PRE&gt;&lt;P&gt;I exported this snapshot as Parquet into a dedicated ADLS ML exchange area:&lt;/P&gt;&lt;PRE&gt;ml_exchange/
└── relevance/
    ├── datasets/
    └── models/&lt;/PRE&gt;&lt;P&gt;This provided a clean boundary between the governed Databricks data platform and the training environment.&lt;/P&gt;&lt;H2&gt;8. NLP modelling strategy&lt;/H2&gt;&lt;P&gt;I deliberately did not jump directly to BERT.&lt;/P&gt;&lt;P&gt;I structured the modelling as a progression.&lt;/P&gt;&lt;H3&gt;Baseline&lt;/H3&gt;&lt;PRE&gt;Title
 ↓
TF-IDF
 ↓
Logistic Regression&lt;/PRE&gt;&lt;P&gt;The purpose of the baseline was not to create the final model.&lt;/P&gt;&lt;P&gt;It established a benchmark.&lt;/P&gt;&lt;P&gt;Any more complicated transformer model needed to demonstrate that the additional complexity provided measurable value.&lt;/P&gt;&lt;H3&gt;Deep NLP&lt;/H3&gt;&lt;P&gt;The next models used pretrained transformer encoders for supply-chain relevance classification.&lt;/P&gt;&lt;P&gt;The progression included experiments such as:&lt;/P&gt;&lt;PRE&gt;TF-IDF
   ↓
DistilBERT
   ↓
DeBERTa / modern encoder models&lt;/PRE&gt;&lt;P&gt;The classification target was conceptually:&lt;/P&gt;&lt;PRE&gt;1 = supply-chain disruption
0 = not a supply-chain disruption&lt;/PRE&gt;&lt;P&gt;The project was designed to eventually extend beyond binary classification into:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;disruption-category classification&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;named entity recognition&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;semantic embeddings&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;duplicate detection&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;story clustering&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;escalation prediction&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;H2&gt;9. Training and Hyperparameter Optimization&lt;/H2&gt;&lt;P&gt;An important architectural decision was not to make the expensive training environment the centre of the platform.&lt;/P&gt;&lt;P&gt;Databricks remained the system of record.&lt;/P&gt;&lt;P&gt;Training/HPO could use lower-cost external GPU compute where appropriate.&lt;/P&gt;&lt;P&gt;The pattern became:&lt;/P&gt;&lt;PRE&gt;Databricks
 ↓
Versioned training dataset
 ↓
ADLS ML Exchange
 ↓
GPU training/HPO
 ↓
Best model artefact
 ↓
ADLS
 ↓
Databricks&lt;/PRE&gt;&lt;P&gt;For HPO I focused on parameters that materially affect transformer performance, for example:&lt;/P&gt;&lt;PRE&gt;learning rate
batch size
epochs
weight decay
warmup ratio
max sequence length&lt;/PRE&gt;&lt;P&gt;Model selection used more than accuracy.&lt;/P&gt;&lt;P&gt;For an early-warning problem, metrics such as these are more useful:&lt;/P&gt;&lt;PRE&gt;Precision
Recall
F1
PR-AUC
Precision@K
Recall@K&lt;/PRE&gt;&lt;H2&gt;10. Bringing the model back into Databricks&lt;/H2&gt;&lt;P&gt;The best model was not left in the training notebook.&lt;/P&gt;&lt;P&gt;The model artefact, configuration and evaluation metadata were returned to Databricks.&lt;/P&gt;&lt;P&gt;The model bundle included information such as:&lt;/P&gt;&lt;PRE&gt;model
best parameters
validation metrics
dataset version
feature schema
library requirements
training notes&lt;/PRE&gt;&lt;P&gt;Then Databricks became responsible again for the production lifecycle:&lt;/P&gt;&lt;PRE&gt;Best model
   ↓
MLflow
   ↓
Model evaluation
   ↓
Unity Catalog Model Registry
   ↓
Champion model&lt;/PRE&gt;&lt;P&gt;This separation was intentional.&lt;/P&gt;&lt;P&gt;Training compute could be disposable.&lt;/P&gt;&lt;P&gt;The governed model lifecycle remained in Databricks.&lt;/P&gt;&lt;H2&gt;11. Driving streaming predictions&lt;/H2&gt;&lt;P&gt;This was the point where the data-engineering and ML parts of the project came together.&lt;/P&gt;&lt;P&gt;The production inference path became:&lt;/P&gt;&lt;PRE&gt;New GDELT file
      ↓
ADF
      ↓
ADLS
      ↓
Auto Loader
      ↓
Bronze
      ↓
Silver
      ↓
Registered ML model
      ↓
Prediction&lt;/PRE&gt;&lt;P&gt;For every newly processed article, the model generates something conceptually similar to:&lt;/P&gt;&lt;PRE&gt;article_id
prediction_timestamp
model_version
relevance_probability
predicted_label&lt;/PRE&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;Title:
"Port workers announce nationwide strike"

Supply-chain disruption probability:
0.94

Prediction:
Relevant&lt;/PRE&gt;&lt;P&gt;Only relevant articles need to continue into more expensive downstream NLP:&lt;/P&gt;&lt;PRE&gt;Relevant article
      ↓
Disruption category
      ↓
Entity extraction
      ↓
Embeddings
      ↓
Semantic duplicate detection
      ↓
Story clustering&lt;/PRE&gt;&lt;P&gt;This is also useful for cost optimization because expensive NLP is performed only on the subset of records that passes the relevance model.&lt;/P&gt;&lt;H2&gt;12. Persisting predictions back into ADLS&lt;/H2&gt;&lt;P&gt;Predictions are first written into governed Delta tables.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;gdelt_dev.ml.article_relevance_predictions&lt;/PRE&gt;&lt;P&gt;or later:&lt;/P&gt;&lt;PRE&gt;gdelt_dev.gold.active_alerts&lt;/PRE&gt;&lt;P&gt;The output contains both prediction data and lineage:&lt;/P&gt;&lt;PRE&gt;article_id
source_file
prediction_timestamp
model_name
model_version
probability
predicted_class
processing_run_id&lt;/PRE&gt;&lt;P&gt;Where external Azure consumers require the prediction output, the data can then be exported through a Unity Catalog External Volume backed by ADLS:&lt;/P&gt;&lt;PRE&gt;Databricks Delta prediction
        ↓
Gold/serving transformation
        ↓
External Volume
        ↓
ADLS&lt;/PRE&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;abfss://gdelt@&amp;lt;storage-account&amp;gt;.dfs.core.windows.net/
predictions/relevance/&lt;/PRE&gt;&lt;P&gt;This completes the round trip:&lt;/P&gt;&lt;PRE&gt;GDELT
 ↓
Azure
 ↓
Databricks
 ↓
Machine Learning
 ↓
Databricks predictions
 ↓
Azure&lt;/PRE&gt;&lt;H2&gt;13. What I learned from this architecture&lt;/H2&gt;&lt;P&gt;The biggest learning for me was that an ML project is much larger than model.fit().&lt;/P&gt;&lt;P&gt;I had to think about:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;Data engineering&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;external acquisition&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;ADF&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;ADLS Gen2&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;immutable raw storage&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;idempotency&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;replayability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;parameterized pipelines&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Streaming&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;Auto Loader&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Structured Streaming&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;processing-time triggers&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;checkpoints&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;restart behaviour&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Bronze → Silver incremental processing&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Governance&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;managed identities&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Unity Catalog&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;external locations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Volumes&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;lineage&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;NLP&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;TF-IDF baselines&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;transformer fine-tuning&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;contextual embeddings&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;classification&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;semantic similarity&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Machine learning&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;labelled datasets&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;time-aware evaluation&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;HPO&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;class imbalance&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;PR-AUC&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;threshold selection&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;MLOps&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;dataset versioning&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;MLflow&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Model Registry&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;model versions&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;production inference&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;prediction lineage&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Cost engineering&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;I also intentionally avoided running everything 24×7.&lt;/P&gt;&lt;P&gt;The same architectural concepts can be learned using controlled streaming sessions:&lt;/P&gt;&lt;PRE&gt;start resources
→ process several GDELT intervals
→ observe streaming behaviour
→ validate output
→ gracefully stop streams
→ terminate compute&lt;/PRE&gt;&lt;P&gt;That allowed me to learn the architecture without turning a personal learning project into an unnecessarily expensive cloud workload.&lt;/P&gt;&lt;H2&gt;What comes next&lt;/H2&gt;&lt;P&gt;The next stages are focused on making the intelligence layer deeper:&lt;/P&gt;&lt;PRE&gt;Relevance classification
        ↓
Disruption-category classification
        ↓
NER / entity normalization
        ↓
Sentence embeddings
        ↓
Semantic duplicate detection
        ↓
Story clustering
        ↓
Temporal feature engineering
        ↓
Predict whether a story will escalate
        ↓
Ranked supply-chain disruption alerts&lt;/PRE&gt;&lt;P&gt;For me, the most valuable part of this project has been connecting all the pieces rather than treating data engineering, NLP and MLOps as separate subjects.&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ADF acquires the data.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ADLS preserves it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;Databricks streams and governs it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ML models extract intelligence from it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;MLflow governs the model lifecycle.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;And the prediction pipeline turns continuously arriving data into actionable outputs.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;#Azure #AzureDataFactory #ADLS #Databricks #ApacheSpark #PySpark #StructuredStreaming #AutoLoader #DeltaLake #UnityCatalog #MLflow #NLP #BERT #MachineLearning #MLOps #DataEngineering #GDELT&lt;/P&gt;</description>
    <pubDate>Thu, 13 Aug 2026 16:10:17 GMT</pubDate>
    <dc:creator>kartheek_rao</dc:creator>
    <dc:date>2026-08-13T16:10:17Z</dc:date>
    <item>
      <title>End-to-End Streaming NLP Pipeline with GDELT, Azure Data Factory, ADLS Gen2 and Databricks</title>
      <link>https://community.databricks.com/t5/machine-learning/end-to-end-streaming-nlp-pipeline-with-gdelt-azure-data-factory/m-p/165627#M4661</link>
      <description>&lt;H1&gt;Building an End-to-End Streaming NLP Pipeline with GDELT, Azure Data Factory, ADLS Gen2 and Databricks&lt;/H1&gt;&lt;P&gt;I recently worked on an end-to-end streaming NLP project using &lt;STRONG&gt;GDELT news data&lt;/STRONG&gt;, Azure Data Factory, ADLS Gen2 and Azure Databricks.&lt;/P&gt;&lt;P&gt;The goal was not just to train an NLP model. I wanted to understand the complete lifecycle:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;external data ingestion → durable cloud storage → streaming ingestion → Bronze/Silver processing → NLP training and HPO → model registration → streaming inference → predictions → ADLS&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;The architecture evolved into:&lt;/P&gt;&lt;PRE&gt;GDELT
  ↓
Azure Data Factory
  ↓
ADLS Gen2
  ├── raw/compressed
  └── landing/extracted
  ↓
Unity Catalog External Location / Volume
  ↓
Databricks Auto Loader
  ↓
Bronze Delta
  ↓
Structured Streaming
  ↓
Silver Delta
  ↓
NLP Training + HPO
  ↓
MLflow / Unity Catalog Model Registry
  ↓
Streaming Model Inference
  ↓
Prediction Delta Tables
  ↓
ADLS Gen2&lt;/PRE&gt;&lt;H2&gt;1. Acquiring GDELT data&lt;/H2&gt;&lt;P&gt;GDELT publishes multiple datasets at regular intervals, including:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;Events&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Event Mentions&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Global Knowledge Graph — GKG&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;For the NLP component, I started with the &lt;STRONG&gt;GKG dataset&lt;/STRONG&gt;, because it provides useful article-level metadata such as:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;article URL&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;themes&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;organizations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;people&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;locations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;tone&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;article title through the GKG Extras field&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;Instead of making Databricks responsible for downloading data from the internet, I separated the acquisition layer from the processing layer.&lt;/P&gt;&lt;P&gt;I used &lt;STRONG&gt;Azure Data Factory&lt;/STRONG&gt; for acquisition.&lt;/P&gt;&lt;P&gt;The initial flow was:&lt;/P&gt;&lt;PRE&gt;GDELT HTTP endpoint
       ↓
ADF Binary Copy
       ↓
ADLS Gen2&lt;/PRE&gt;&lt;P&gt;I first tested the architecture with one GKG file.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;20260802103000.gkg.csv.zip&lt;/PRE&gt;&lt;P&gt;ADF copied the original compressed file into:&lt;/P&gt;&lt;PRE&gt;raw/compressed/gkg/
ingestion_date=YYYY-MM-DD/&lt;/PRE&gt;&lt;P&gt;The original ZIP was deliberately preserved.&lt;/P&gt;&lt;P&gt;This became important later because it gave the pipeline:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;replayability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;traceability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;debugging capability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;separation between acquisition and transformation&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;ADF then decompressed the ZIP into:&lt;/P&gt;&lt;PRE&gt;landing/extracted/gkg/
ingestion_date=YYYY-MM-DD/&lt;/PRE&gt;&lt;P&gt;The guiding principle was:&lt;/P&gt;&lt;PRE&gt;ADF = acquire and land
ADLS = preserve
Databricks = process and model&lt;/PRE&gt;&lt;H2&gt;2. Making ADF ingestion dynamic&lt;/H2&gt;&lt;P&gt;The first pipeline used a fixed GDELT filename.&lt;/P&gt;&lt;P&gt;After proving that one file could move successfully from GDELT to ADLS, I parameterized the pipeline.&lt;/P&gt;&lt;P&gt;I added parameters for:&lt;/P&gt;&lt;PRE&gt;file_name
ingestion_date
expected_size
checksum&lt;/PRE&gt;&lt;P&gt;Then I used GDELT's latest-file information to automatically identify the newest GKG file.&lt;/P&gt;&lt;P&gt;ADF performed:&lt;/P&gt;&lt;PRE&gt;Lookup latest GDELT metadata
        ↓
Filter GKG entry
        ↓
Extract filename
        ↓
Derive ingestion date
        ↓
Check whether file already exists
        ↓
Ingest only if new&lt;/PRE&gt;&lt;P&gt;This introduced an important engineering property:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;idempotency.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Rerunning the pipeline would not continually ingest the same file.&lt;/P&gt;&lt;H2&gt;3. Simulating streaming without running 24×7&lt;/H2&gt;&lt;P&gt;Because this was a learning project, I did not want to keep cloud compute running continuously.&lt;/P&gt;&lt;P&gt;Instead, I created controlled streaming sessions.&lt;/P&gt;&lt;P&gt;The ADF pipeline was configured to collect approximately three consecutive GDELT files.&lt;/P&gt;&lt;P&gt;Conceptually:&lt;/P&gt;&lt;PRE&gt;File 1
  ↓
Wait
  ↓
File 2
  ↓
Wait
  ↓
File 3
  ↓
Stop&lt;/PRE&gt;&lt;P&gt;The session used:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;an Until loop&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;a target-file count&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Wait activities&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;file-existence checks&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;a maximum session timeout&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;This allowed me to learn near-real-time ingestion while keeping infrastructure costs under control.&lt;/P&gt;&lt;H2&gt;4. Connecting ADLS securely to Databricks&lt;/H2&gt;&lt;P&gt;Instead of using storage keys or legacy DBFS mounts, I connected ADLS using:&lt;/P&gt;&lt;PRE&gt;Databricks Access Connector
        ↓
Managed Identity
        ↓
Unity Catalog Storage Credential
        ↓
External Location
        ↓
External Volume&lt;/PRE&gt;&lt;P&gt;This exposed the ADLS landing data through a governed path such as:&lt;/P&gt;&lt;PRE&gt;/Volumes/gdelt_dev/raw/gdelt_landing/&lt;/PRE&gt;&lt;P&gt;One of the most useful concepts I learned here was that /Volumes/... is a Databricks governed view over the underlying cloud storage.&lt;/P&gt;&lt;P&gt;The physical data still lives in ADLS.&lt;/P&gt;&lt;H2&gt;5. Streaming ADLS files into Bronze using Auto Loader&lt;/H2&gt;&lt;P&gt;The next layer was Databricks Auto Loader.&lt;/P&gt;&lt;PRE&gt;ADLS landing
      ↓
Unity Catalog Volume
      ↓
cloudFiles
      ↓
Structured Streaming
      ↓
Bronze Delta&lt;/PRE&gt;&lt;P&gt;I deliberately kept Bronze close to the source.&lt;/P&gt;&lt;P&gt;Instead of doing all business parsing immediately, the Bronze table stored:&lt;/P&gt;&lt;PRE&gt;raw_record
source_file_name
source_file_path
source_file_size
source_file_modification_time
ingested_at
ingestion_date&lt;/PRE&gt;&lt;P&gt;Auto Loader used a dedicated checkpoint:&lt;/P&gt;&lt;PRE&gt;checkpoints/bronze_gkg/&lt;/PRE&gt;&lt;P&gt;and a separate schema location.&lt;/P&gt;&lt;P&gt;The checkpoint became one of the most important concepts in the project.&lt;/P&gt;&lt;P&gt;It allows the stream to remember:&lt;/P&gt;&lt;BLOCKQUOTE&gt;&lt;P&gt;Which source files have already been successfully processed?&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;P&gt;I could stop the Databricks cluster, restart it later and use the same checkpoint.&lt;/P&gt;&lt;P&gt;The stream continued from its previous progress rather than starting from scratch.&lt;/P&gt;&lt;H2&gt;6. Bronze to Silver streaming&lt;/H2&gt;&lt;P&gt;Once the raw GKG records were reliably landing in Bronze, I created another Structured Streaming pipeline:&lt;/P&gt;&lt;PRE&gt;Bronze Delta
    ↓
readStream
    ↓
GKG parsing
    ↓
cleaning
    ↓
Silver Delta&lt;/PRE&gt;&lt;P&gt;The GKG records were tab-delimited.&lt;/P&gt;&lt;P&gt;In Silver I parsed the useful fields and produced article-level features such as:&lt;/P&gt;&lt;PRE&gt;article_id
published_at
article_url
source_domain
title
tone
themes
organizations
persons
locations&lt;/PRE&gt;&lt;P&gt;The article title was extracted from the GKG Extras field.&lt;/P&gt;&lt;P&gt;Silver also performed operations such as:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;timestamp conversion&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;URL/domain extraction&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;HTML title decoding&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;theme preparation&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;exact duplicate checks&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;malformed-record filtering&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;Importantly, Silver used a &lt;STRONG&gt;different checkpoint&lt;/STRONG&gt; from Bronze.&lt;/P&gt;&lt;P&gt;So the architecture had independent state:&lt;/P&gt;&lt;PRE&gt;ADLS → Bronze checkpoint

Bronze → Silver checkpoint&lt;/PRE&gt;&lt;P&gt;At this stage I had a genuine multi-hop streaming architecture:&lt;/P&gt;&lt;PRE&gt;ADF lands file
      ↓
Auto Loader detects it
      ↓
Bronze updates
      ↓
Silver streaming query detects new Bronze rows
      ↓
Silver updates&lt;/PRE&gt;&lt;H2&gt;7. Preparing the NLP dataset&lt;/H2&gt;&lt;P&gt;From Silver I created an NLP-oriented profile dataset containing fields such as:&lt;/P&gt;&lt;PRE&gt;article_id
published_at
article_url
source_domain
title
title_normalized
tone
themes_clean
organizations
persons
locations&lt;/PRE&gt;&lt;P&gt;A versioned snapshot was then created for reproducibility.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;article_nlp_profile_v001&lt;/PRE&gt;&lt;P&gt;I exported this snapshot as Parquet into a dedicated ADLS ML exchange area:&lt;/P&gt;&lt;PRE&gt;ml_exchange/
└── relevance/
    ├── datasets/
    └── models/&lt;/PRE&gt;&lt;P&gt;This provided a clean boundary between the governed Databricks data platform and the training environment.&lt;/P&gt;&lt;H2&gt;8. NLP modelling strategy&lt;/H2&gt;&lt;P&gt;I deliberately did not jump directly to BERT.&lt;/P&gt;&lt;P&gt;I structured the modelling as a progression.&lt;/P&gt;&lt;H3&gt;Baseline&lt;/H3&gt;&lt;PRE&gt;Title
 ↓
TF-IDF
 ↓
Logistic Regression&lt;/PRE&gt;&lt;P&gt;The purpose of the baseline was not to create the final model.&lt;/P&gt;&lt;P&gt;It established a benchmark.&lt;/P&gt;&lt;P&gt;Any more complicated transformer model needed to demonstrate that the additional complexity provided measurable value.&lt;/P&gt;&lt;H3&gt;Deep NLP&lt;/H3&gt;&lt;P&gt;The next models used pretrained transformer encoders for supply-chain relevance classification.&lt;/P&gt;&lt;P&gt;The progression included experiments such as:&lt;/P&gt;&lt;PRE&gt;TF-IDF
   ↓
DistilBERT
   ↓
DeBERTa / modern encoder models&lt;/PRE&gt;&lt;P&gt;The classification target was conceptually:&lt;/P&gt;&lt;PRE&gt;1 = supply-chain disruption
0 = not a supply-chain disruption&lt;/PRE&gt;&lt;P&gt;The project was designed to eventually extend beyond binary classification into:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;disruption-category classification&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;named entity recognition&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;semantic embeddings&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;duplicate detection&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;story clustering&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;escalation prediction&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;H2&gt;9. Training and Hyperparameter Optimization&lt;/H2&gt;&lt;P&gt;An important architectural decision was not to make the expensive training environment the centre of the platform.&lt;/P&gt;&lt;P&gt;Databricks remained the system of record.&lt;/P&gt;&lt;P&gt;Training/HPO could use lower-cost external GPU compute where appropriate.&lt;/P&gt;&lt;P&gt;The pattern became:&lt;/P&gt;&lt;PRE&gt;Databricks
 ↓
Versioned training dataset
 ↓
ADLS ML Exchange
 ↓
GPU training/HPO
 ↓
Best model artefact
 ↓
ADLS
 ↓
Databricks&lt;/PRE&gt;&lt;P&gt;For HPO I focused on parameters that materially affect transformer performance, for example:&lt;/P&gt;&lt;PRE&gt;learning rate
batch size
epochs
weight decay
warmup ratio
max sequence length&lt;/PRE&gt;&lt;P&gt;Model selection used more than accuracy.&lt;/P&gt;&lt;P&gt;For an early-warning problem, metrics such as these are more useful:&lt;/P&gt;&lt;PRE&gt;Precision
Recall
F1
PR-AUC
Precision@K
Recall@K&lt;/PRE&gt;&lt;H2&gt;10. Bringing the model back into Databricks&lt;/H2&gt;&lt;P&gt;The best model was not left in the training notebook.&lt;/P&gt;&lt;P&gt;The model artefact, configuration and evaluation metadata were returned to Databricks.&lt;/P&gt;&lt;P&gt;The model bundle included information such as:&lt;/P&gt;&lt;PRE&gt;model
best parameters
validation metrics
dataset version
feature schema
library requirements
training notes&lt;/PRE&gt;&lt;P&gt;Then Databricks became responsible again for the production lifecycle:&lt;/P&gt;&lt;PRE&gt;Best model
   ↓
MLflow
   ↓
Model evaluation
   ↓
Unity Catalog Model Registry
   ↓
Champion model&lt;/PRE&gt;&lt;P&gt;This separation was intentional.&lt;/P&gt;&lt;P&gt;Training compute could be disposable.&lt;/P&gt;&lt;P&gt;The governed model lifecycle remained in Databricks.&lt;/P&gt;&lt;H2&gt;11. Driving streaming predictions&lt;/H2&gt;&lt;P&gt;This was the point where the data-engineering and ML parts of the project came together.&lt;/P&gt;&lt;P&gt;The production inference path became:&lt;/P&gt;&lt;PRE&gt;New GDELT file
      ↓
ADF
      ↓
ADLS
      ↓
Auto Loader
      ↓
Bronze
      ↓
Silver
      ↓
Registered ML model
      ↓
Prediction&lt;/PRE&gt;&lt;P&gt;For every newly processed article, the model generates something conceptually similar to:&lt;/P&gt;&lt;PRE&gt;article_id
prediction_timestamp
model_version
relevance_probability
predicted_label&lt;/PRE&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;Title:
"Port workers announce nationwide strike"

Supply-chain disruption probability:
0.94

Prediction:
Relevant&lt;/PRE&gt;&lt;P&gt;Only relevant articles need to continue into more expensive downstream NLP:&lt;/P&gt;&lt;PRE&gt;Relevant article
      ↓
Disruption category
      ↓
Entity extraction
      ↓
Embeddings
      ↓
Semantic duplicate detection
      ↓
Story clustering&lt;/PRE&gt;&lt;P&gt;This is also useful for cost optimization because expensive NLP is performed only on the subset of records that passes the relevance model.&lt;/P&gt;&lt;H2&gt;12. Persisting predictions back into ADLS&lt;/H2&gt;&lt;P&gt;Predictions are first written into governed Delta tables.&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;gdelt_dev.ml.article_relevance_predictions&lt;/PRE&gt;&lt;P&gt;or later:&lt;/P&gt;&lt;PRE&gt;gdelt_dev.gold.active_alerts&lt;/PRE&gt;&lt;P&gt;The output contains both prediction data and lineage:&lt;/P&gt;&lt;PRE&gt;article_id
source_file
prediction_timestamp
model_name
model_version
probability
predicted_class
processing_run_id&lt;/PRE&gt;&lt;P&gt;Where external Azure consumers require the prediction output, the data can then be exported through a Unity Catalog External Volume backed by ADLS:&lt;/P&gt;&lt;PRE&gt;Databricks Delta prediction
        ↓
Gold/serving transformation
        ↓
External Volume
        ↓
ADLS&lt;/PRE&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;PRE&gt;abfss://gdelt@&amp;lt;storage-account&amp;gt;.dfs.core.windows.net/
predictions/relevance/&lt;/PRE&gt;&lt;P&gt;This completes the round trip:&lt;/P&gt;&lt;PRE&gt;GDELT
 ↓
Azure
 ↓
Databricks
 ↓
Machine Learning
 ↓
Databricks predictions
 ↓
Azure&lt;/PRE&gt;&lt;H2&gt;13. What I learned from this architecture&lt;/H2&gt;&lt;P&gt;The biggest learning for me was that an ML project is much larger than model.fit().&lt;/P&gt;&lt;P&gt;I had to think about:&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;Data engineering&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;external acquisition&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;ADF&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;ADLS Gen2&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;immutable raw storage&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;idempotency&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;replayability&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;parameterized pipelines&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Streaming&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;Auto Loader&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Structured Streaming&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;processing-time triggers&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;checkpoints&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;restart behaviour&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Bronze → Silver incremental processing&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Governance&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;managed identities&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Unity Catalog&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;external locations&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Volumes&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;lineage&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;NLP&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;TF-IDF baselines&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;transformer fine-tuning&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;contextual embeddings&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;classification&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;semantic similarity&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Machine learning&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;labelled datasets&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;time-aware evaluation&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;HPO&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;class imbalance&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;PR-AUC&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;threshold selection&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;MLOps&lt;/STRONG&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;dataset versioning&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;MLflow&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;Model Registry&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;model versions&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;production inference&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;prediction lineage&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;Cost engineering&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;I also intentionally avoided running everything 24×7.&lt;/P&gt;&lt;P&gt;The same architectural concepts can be learned using controlled streaming sessions:&lt;/P&gt;&lt;PRE&gt;start resources
→ process several GDELT intervals
→ observe streaming behaviour
→ validate output
→ gracefully stop streams
→ terminate compute&lt;/PRE&gt;&lt;P&gt;That allowed me to learn the architecture without turning a personal learning project into an unnecessarily expensive cloud workload.&lt;/P&gt;&lt;H2&gt;What comes next&lt;/H2&gt;&lt;P&gt;The next stages are focused on making the intelligence layer deeper:&lt;/P&gt;&lt;PRE&gt;Relevance classification
        ↓
Disruption-category classification
        ↓
NER / entity normalization
        ↓
Sentence embeddings
        ↓
Semantic duplicate detection
        ↓
Story clustering
        ↓
Temporal feature engineering
        ↓
Predict whether a story will escalate
        ↓
Ranked supply-chain disruption alerts&lt;/PRE&gt;&lt;P&gt;For me, the most valuable part of this project has been connecting all the pieces rather than treating data engineering, NLP and MLOps as separate subjects.&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ADF acquires the data.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ADLS preserves it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;Databricks streams and governs it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;ML models extract intelligence from it.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;MLflow governs the model lifecycle.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;And the prediction pipeline turns continuously arriving data into actionable outputs.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;#Azure #AzureDataFactory #ADLS #Databricks #ApacheSpark #PySpark #StructuredStreaming #AutoLoader #DeltaLake #UnityCatalog #MLflow #NLP #BERT #MachineLearning #MLOps #DataEngineering #GDELT&lt;/P&gt;</description>
      <pubDate>Thu, 13 Aug 2026 16:10:17 GMT</pubDate>
      <guid>https://community.databricks.com/t5/machine-learning/end-to-end-streaming-nlp-pipeline-with-gdelt-azure-data-factory/m-p/165627#M4661</guid>
      <dc:creator>kartheek_rao</dc:creator>
      <dc:date>2026-08-13T16:10:17Z</dc:date>
    </item>
    <item>
      <title>Re: End-to-End Streaming NLP Pipeline with GDELT, Azure Data Factory, ADLS Gen2 and Databricks</title>
      <link>https://community.databricks.com/t5/machine-learning/end-to-end-streaming-nlp-pipeline-with-gdelt-azure-data-factory/m-p/167572#M4693</link>
      <description>&lt;P class=""&gt;Great end-to-end project! To take it to the next level, consider exploring these native Databricks capabilities.&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;Lakehouse Monitoring:&lt;/STRONG&gt; Track data drift and automate model retraining when performance drops.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Model Serving:&lt;/STRONG&gt; Deploy your models behind serverless REST endpoints for external API consumption.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;LLMs:&lt;/STRONG&gt;&lt;SPAN&gt; Leverage Foundation Models for zero-shot classification and predictions instead of training bespoke models from scratch.&lt;/SPAN&gt;&lt;/LI&gt;&lt;/UL&gt;</description>
      <pubDate>Fri, 04 Sep 2026 17:02:45 GMT</pubDate>
      <guid>https://community.databricks.com/t5/machine-learning/end-to-end-streaming-nlp-pipeline-with-gdelt-azure-data-factory/m-p/167572#M4693</guid>
      <dc:creator>kunduruanil</dc:creator>
      <dc:date>2026-09-04T17:02:45Z</dc:date>
    </item>
  </channel>
</rss>

