Neuro-Symbolic AI: Hoe Wij Trending Content Ranken
· Mediazone
Het combineren van regel-gebaseerde logica (rule-based logic) met neurale netwerken (neural networks) voor uitlegbare machine learning (explainable ML)
Een Echt Voorbeeld
Laten we beginnen met een concreet artikel dat ons systeem vandaag heeft gerankt:
Artikel: "OpenAI onthult GPT-5 met revolutionaire reasoning capabilities"
Bron: Tweakers
Gepubliceerd: 2 uur geleden
Categorie: AI / Large Language Models
Traditionele ranking: Dit artikel heeft 3 weergaven (views), 0 stemmen (votes) — het zou onderaan eindigen.
Onze hybride ranking:
Versheid (Recency Score): 0.98 (2 uur oud = zeer vers)
Bronbetrouwbaarheid (Source Trust): 0.85 (Tweakers = betrouwbare tech-bron)
Categorieversterking (Category Boost): 1.50 (AI + LLM = primaire focus)
Titelkwaliteit (Title Quality): 0.78 (informatief, 58 tekens, specifiek)
Symbolische Score: 0.82
Neurale Voorspelling: 0.79 (vergelijkbare artikelen deden het goed)
─────────────────────────────────────
Hybride Score: 0.81 → Staat in top 10
Het artikel krijgt een eerlijke kans gebaseerd op wat het is, niet alleen op hoeveel klikken het heeft.
Het Probleem
Hoe bepaal je welke nieuwsartikelen "trending" (populair) zijn? De traditionele aanpak is simpel: tel klikken (clicks), stemmen (votes), en weergaven (views). Maar dit heeft problemen:
- Nieuwe artikelen maken geen kans — ze hebben nog geen interactie (engagement)
- Clickbait wint — sensationele titels scoren beter
- Kwaliteit wordt genegeerd — een goed onderzocht artikel van een betrouwbare bron scoort hetzelfde als spam
Wij bouwden een ander systeem: Neuro-Symbolic AI.
Wat is Neuro-Symbolic AI?
Het combineert twee werelden die normaal gescheiden blijven.
Symbolische AI (Regel-gebaseerd / Rule-based)
Jip & Janneke uitleg: Dit is zoals de regels die je ouders maken. "Als je je groenten opeet, krijg je een toetje." Of "Als het regent, neem je een paraplu mee." Simpele ALS-DAN regels die iedereen begrijpt. De computer doet precies wat wij zeggen — niet meer, niet minder.
De "oude school" aanpak. Expliciete regels (explicit rules) die we zelf definiëren:
ALS artikel < 6 uur oud → hoge versheidscore (recency score)
ALS bron = "Ars Technica" → betrouwbaarheidsscore (trust score) 0.90
ALS categorie = "AI" → versterking (boost) × 1.5
Voordelen: Transparant, uitlegbaar (explainable), geen trainingsdata nodig, direct aanpasbaar.
Nadelen: Mist nuance, schaalt niet goed, menselijke vooringenomenheid (human bias).
Neurale AI (Machine Learning)
Een neuraal netwerk (neural network) dat patronen (patterns) leert van data.
Jip & Janneke uitleg: Stel je voor dat je een hond wilt leren om "zit" te doen. Je zegt "zit", de hond gaat zitten, en je geeft een koekje. Na 100 keer weet de hond: "zit" = ga zitten = koekje!
Een neuraal netwerk werkt hetzelfde. Je laat het 1000 nieuwsartikelen zien en vertelt: "Dit artikel was populair" of "Dit artikel was niet populair". Na al die voorbeelden leert het netwerk zelf patronen herkennen: "Aha, artikelen van Tweakers over AI worden vaak gelezen!"
Het netwerk is eigenlijk een stel rekenmachines die met elkaar verbonden zijn. Elke rekenmachine kijkt naar iets anders (versheid, bron, categorie) en samen stemmen ze over het eindantwoord. Net zoals meerdere vrienden samen beslissen welke film ze gaan kijken — iedereen heeft een mening, en het gemiddelde wint.
Met ons GPT-5 artikel als voorbeeld:
Invoer (Input): [0.98, 0.85, 0.50, 0.78]
│ │ │ │
│ │ │ └── title_quality: "OpenAI onthult GPT-5..." = 0.78
│ │ │ (58 tekens, informatief, bevat specifieke termen)
│ │ │
│ │ └── category: AI/LLM = 1.50, genormaliseerd naar 0.50
│ │ (1.50 - 0.5) / 1.0 = 0.50
│ │
│ └── source_trust: Tweakers = 0.85
│ (gevestigde tech-community, betrouwbaar)
│
└── recency: 2 uur oud = 0.98
(bijna maximale versheid)
↓
Verborgen Laag (Hidden Layer): 8 neuronen (neurons)
Wat is dit? Een tussenlaag met 8 "rekenmachines" (neuronen).
BELANGRIJK: Elke neuron ontvangt ALLE 4 invoerwaarden,
maar heeft zijn EIGEN set van 4 gewichten!
┌─────────────────────────────────────────────────────────────┐
│ STRUCTUUR VAN HET NETWERK │
│ │
│ 4 invoerwaarden 8 neuronen 1 uitvoer │
│ ─────────────── ───────── ──────── │
│ │
│ recency (0.98) ───┬──→ Neuron 1 ──┐ │
│ ├──→ Neuron 2 ──┤ │
│ source (0.85) ────┼──→ Neuron 3 ──┤ │
│ ├──→ Neuron 4 ──┼──→ 0.79 │
│ category (0.50) ──┼──→ Neuron 5 ──┤ │
│ ├──→ Neuron 6 ──┤ │
│ title (0.78) ─────┼──→ Neuron 7 ──┤ │
│ └──→ Neuron 8 ──┘ │
│ │
│ Elke neuron ontvangt ALLE 4 invoerwaarden! │
└─────────────────────────────────────────────────────────────┘
Elke neuron doet één simpele berekening:
┌─────────────────────────────────────────────────────────────┐
│ VOORBEELD: Hoe Neuron 1 werkt │
│ │
│ Neuron 1 heeft EIGEN gewichten: [0.3, 0.2, 0.1, 0.4] │
│ │
│ Stap 1: Vermenigvuldig elke invoer met zijn gewicht │
│ 0.3 × 0.98 = 0.294 (recency) │
│ 0.2 × 0.85 = 0.170 (source_trust) │
│ 0.1 × 0.50 = 0.050 (category) │
│ 0.4 × 0.78 = 0.312 (title_quality) │
│ │
│ Stap 2: Tel alles op + bias (startwaarde) │
│ 0.294 + 0.170 + 0.050 + 0.312 - 0.1 = 0.726 │
│ │
│ Stap 3: ReLU activatie (negatief wordt 0) │
│ ReLU(0.726) = 0.726 ✓ (positief, blijft) │
│ │
│ Uitvoer Neuron 1: 0.726 │
└─────────────────────────────────────────────────────────────┘
De 8 neuronen hebben VERSCHILLENDE gewichten (geleerd tijdens training):
┌─────────────────────────────────────────────────────────────┐
│ Neuron 1: gewichten [0.3, 0.2, 0.1, 0.4] → uitvoer: 0.73 │
│ (focust meer op versheid en titelkwaliteit) │
│ │
│ Neuron 2: gewichten [0.1, 0.5, 0.2, 0.2] → uitvoer: 0.58 │
│ (focust meer op bronvertrouwen) │
│ │
│ Neuron 3: gewichten [0.2, 0.2, 0.4, 0.2] → uitvoer: 0.62 │
│ (focust meer op categorie) │
│ │
│ Neuron 4: gewichten [0.4, 0.3, 0.1, 0.2] → uitvoer: 0.69 │
│ Neuron 5: gewichten [0.2, 0.1, 0.3, 0.4] → uitvoer: 0.55 │
│ Neuron 6: gewichten [0.3, 0.3, 0.2, 0.2] → uitvoer: 0.64 │
│ Neuron 7: gewichten [0.1, 0.4, 0.3, 0.2] → uitvoer: 0.51 │
│ Neuron 8: gewichten [0.25, 0.25, 0.25, 0.25] → uitvoer: 0.60│
│ (weegt alles gelijk) │
└─────────────────────────────────────────────────────────────┘
Totaal aantal gewichten: 8 neuronen × 4 gewichten = 32 gewichten
(plus 8 bias waarden = 40 leerbare parameters)
Waarom 8 neuronen? Meer neuronen = meer patronen herkennen.
8 is genoeg voor dit simpele probleem met 4 invoerwaarden.
↓
Uitvoer (Output): engagement_prediction = 0.79
Het netwerk voorspelt: "Dit artikel heeft 79% kans op hoge interactie"
(gebaseerd op patronen van vergelijkbare artikelen in het verleden)
Wat is een Verborgen Laag (Hidden Layer)?
Jip & Janneke uitleg: Stel je voor dat je een wedstrijd organiseert om te bepalen welk artikel het beste is. Je hebt 8 juryleden (dat zijn de 8 neuronen). Elk jurylid krijgt dezelfde informatie (versheid, bron, categorie, titel), maar elk jurylid let op iets anders. Jurylid 1 vindt versheid het belangrijkst. Jurylid 2 vindt de bron het belangrijkst. Aan het eind tellen we alle scores op en bepalen we de winnaar. De "verborgen laag" is gewoon de tafel waar de jury zit — je ziet ze niet, maar ze doen het werk!
Een tussenlaag waar berekeningen plaatsvinden. De invoer (4 getallen) wordt vermenigvuldigd met gewichten (weights), opgeteld, en door een activatiefunctie (activation function) gehaald.
Wat is ReLU?
Jip & Janneke uitleg: ReLU is een simpele regel: "Als het getal negatief is, maak er 0 van. Als het positief is, laat het met rust." Waarom? Omdat negatieve scores geen zin hebben — een artikel kan niet "min-3 interessant" zijn. Het is óf interessant (positief), óf niet interessant (0).
"Rectified Linear Unit" — een simpele functie: negatieve getallen worden 0, positieve blijven onveranderd.
ReLU(-3) = 0
ReLU(2.5) = 2.5
De Hybride Aanpak (Hybrid Approach)
Jip & Janneke uitleg: Stel je voor dat je een toets maakt. Je moeder kijkt naar de regels: "Heb je je huiswerk gemaakt? Heb je goed geslapen?" (dat is het symbolische deel). Maar je slimme opa kijkt naar patronen: "De vorige 10 keer dat je zo keek, haalde je een 8!" (dat is het neurale deel). Door beide meningen te combineren, krijg je de beste voorspelling.
Wij doen 60% moeder (regels) + 40% opa (patronen). Zo krijgen we het beste van beide werelden!
We combineren ze met een gewogen gemiddelde (weighted average):
Hybride Score = 0.60 × Symbolisch + 0.40 × Neuraal
De symbolische component biedt stabiliteit en uitlegbaarheid (explainability). De neurale component voegt nuance toe en leert van gebruikersgedrag (user behavior).
Onze Kenmerk-extractie (Feature Engineering)
We extraheren 4 numerieke kenmerken (numeric features) uit elk artikel. Elk kenmerk is een score tussen 0 en 1.
Symbolische Kenmerken (Symbolic Features)
| Kenmerk (Feature) | Beschrijving | Berekening |
|---|---|---|
| Versheid (Recency Score) | Hoe vers is het artikel? | Zie uitleg hieronder |
| Bronbetrouwbaarheid (Source Trust) | Hoe betrouwbaar is de bron? | Handmatig per bron (0.50-0.90) |
| Categorieversterking (Category Boost) | Hoe relevant is de categorie? | AI onderwerpen krijgen 1.5× boost |
| Titelkwaliteit (Title Quality) | Is de titel informatief? | Lengte, specificiteit, geen clickbait |
Versheidscore — Wat is "Exponentiële Decay" (Exponential Decay)?
Jip & Janneke uitleg: Denk aan een vers broodje van de bakker. Op dag 1 is het heerlijk (score 10). Op dag 2 is het nog eetbaar (score 6). Op dag 3 wordt het hard (score 3). Op dag 7 gooi je het weg (score 0).
Maar let op: het broodje wordt NIET elke dag even veel slechter! De eerste dag verliest het veel smaak, daarna steeds minder. Dat is "exponentiële afname" — het daalt snel in het begin, dan steeds langzamer.
Nieuws werkt net zo. Een artikel van 2 uur oud is super vers! Na 1 dag is het nog relevant. Na 1 week is het oud nieuws — maar niet helemaal waardeloos.
Jip & Janneke: Lineair vs Exponentieel — Twee manieren om versheid te meten
Stel je voor: je hebt een bak met 10 snoepjes, en elke dag eet je er een paar op.
Manier 1: Lineair (elke dag evenveel)
Dag 0: 🍬🍬🍬🍬🍬🍬🍬🍬🍬🍬 (10 snoepjes - VERS!)
Dag 1: 🍬🍬🍬🍬🍬🍬🍬🍬 (8 snoepjes)
Dag 2: 🍬🍬🍬🍬🍬🍬 (6 snoepjes)
Dag 3: 🍬🍬🍬🍬 (4 snoepjes)
Dag 4: 🍬🍬 (2 snoepjes)
Dag 5: ❌ (0 snoepjes - LEEG!)
Probleem: Na 5 dagen is ALLES op. Te snel!
Manier 2: Exponentieel (eerst veel, dan steeds minder)
Dag 0: 🍬🍬🍬🍬🍬🍬🍬🍬🍬🍬 (10 snoepjes - VERS!)
Dag 1: 🍬🍬🍬🍬🍬🍬🍬 (7 snoepjes - je at er 3)
Dag 2: 🍬🍬🍬🍬🍬 (5 snoepjes - je at er 2)
Dag 3: 🍬🍬🍬🍬 (4 snoepjes - je at er 1)
Dag 5: 🍬🍬🍬 (3 snoepjes - je at er 1)
Dag 7: 🍬🍬 (2 snoepjes - nog steeds wat over!)
Voordeel: Er blijft ALTIJD een beetje over!
Waarom is exponentieel beter voor nieuws?
Met lineair zeg je na 3 dagen: "Dit artikel is waardeloos, gooi weg!" Met exponentieel zeg je: "Het is niet meer super vers, maar nog steeds een beetje interessant."
Ons GPT-5 artikel als voorbeeld:
Artikel: "OpenAI onthult GPT-5"
Gepubliceerd: 2 uur geleden
Lineair score: ████████████████████░░░░ (92% vers)
Exponentieel score: █████████████████████████ (99% vers)
→ Bijna hetzelfde! Beide methodes zeggen: "Super vers artikel!"
Hetzelfde artikel, maar nu 3 DAGEN oud:
Lineair score: ❌ (0% - "waardeloos, gooi weg!")
Exponentieel score: ███░░░░░░░░░░░░░░░░░░░░░░ (15% - "nog een beetje relevant")
→ GROOT verschil! Exponentieel is eerlijker voor oudere artikelen.
De simpele regel:
- Lineair = "Elke dag gaat er evenveel af" → te streng
- Exponentieel = "Eerst gaat er veel af, later steeds minder" → eerlijker
De Afnamecurve (Decay Curve)
Artikelleeftijd → Score
─────────────────────────────────
1 uur oud → 1.00 (maximale versheid)
6 uur oud → 1.00 (nog steeds maximaal)
12 uur oud → 0.90
24 uur oud → 0.80
3 dagen oud → 0.50
1 week oud → 0.15
2 weken oud → 0.02 (bijna nul)
Bronbetrouwbaarheid (Source Trust) — "Hoe betrouwbaar is deze website?"
Niet alle bronnen zijn gelijk. Een goed onderzocht artikel van een gerenommeerde tech-publicatie moet hoger ranken dan een willekeurige blogpost.
Onze betrouwbaarheidsscores:
Bron (Source) → Betrouwbaarheidsscore (Trust Score)
─────────────────────────────────
NOS → 0.90 (publieke omroep, geverifieerd)
Ars Technica → 0.90 (diepgaande tech-journalistiek)
Tweakers → 0.85 (gevestigde tech-community)
RTL Nieuws → 0.85 (groot mediabedrijf)
NU.nl → 0.80 (mainstream nieuws)
TechCrunch → 0.80 (tech nieuws, soms hype)
The Verge → 0.85 (kwaliteits tech-coverage)
Hacker News → 0.70 (community-gedreven, wisselend)
Reddit → 0.60 (user-generated, ongeverifieerd)
Onbekende bron → 0.50 (neutraal standaard)
Hoe we scores toekennen:
- Historie van nauwkeurigheid (track record of accuracy)
- Redactionele standaarden (editorial standards)
- Originele verslaggeving vs. aggregatie (original reporting vs. aggregation)
- Community-reputatie (community reputation)
Deze scores worden opgeslagen in een databasetabel en kunnen in de loop van de tijd worden aangepast.
De Versheidsformule (Recency Formula)
calculateRecencyScore(publishedAt) {
const hoursAge = (now - publishedAt) / (1000 * 60 * 60);
if (hoursAge <= 6) return 1.0; // Maximale versheid
if (hoursAge <= 24) return 0.8 + decay; // Erg vers
if (hoursAge <= 72) return 0.5 + decay; // Redelijk vers
// Exponentiële afname voor oudere content
return Math.max(0, 0.2 * Math.exp(-0.1 * daysAge));
}
Bronbetrouwbaarheid Configuratie (Source Trust Configuration)
We onderhouden een databasetabel met betrouwbaarheidsscores:
INSERT INTO source_trust_config (source_name, trust_score) VALUES
('Ars Technica', 0.90),
('TechCrunch', 0.80),
('Hacker News', 0.70),
('Reddit', 0.60),
('default', 0.50);
Dit is configureerbaar — wanneer een nieuwe bron betrouwbaar blijkt, kunnen we de score aanpassen.
De Architectuur (Architecture)
┌─────────────────────────────────────────────┐
│ ARTIKEL INVOER (Article Input) │
│ {title, source, category, publishedAt} │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ KENMERK-EXTRACTOR (Feature Extractor) │
│ │
│ Versheid (Recency) → 0.95 │
│ Bronvertrouwen (Source Trust) → 0.85 │
│ Categorieversterking (Category Boost) → 1.50│
│ Titelkwaliteit (Title Quality) → 0.72 │
└─────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ SYMBOLISCH │ │ NEURAAL │
│ SCORER │ │ NETWERK │
│ (Rule- │ │ (Neural │
│ based) │ │ Network) │
│ │ │ │
│ Score: 0.78 │ │ Voorspelling: │
│ │ │ 0.82 │
└───────────────┘ └─────────────────┘
│ │
└─────────────┬─────────────┘
▼
┌─────────────────────────────────────────────┐
│ HYBRIDE COMBINEERDER (Hybrid Combiner) │
│ │
│ 0.60 × 0.78 + 0.40 × 0.82 = 0.796 │
└─────────────────────────────────────────────┘
Technische Implementatie (Technical Implementation)
Wat is TensorFlow.js?
Jip & Janneke uitleg: TensorFlow is een "gereedschapskist" gemaakt door Google. Net zoals je een LEGO-doos hebt om dingen te bouwen, heb je TensorFlow om slimme computers te bouwen. De ".js" betekent dat het in JavaScript werkt — de taal van websites. Dus je kunt een slim systeem bouwen dat direct in je browser draait, zonder dat je speciale software hoeft te installeren!
TensorFlow is Google's open-source machine learning raamwerk (framework). Het is de industriestandaard voor het bouwen van neurale netwerken.
TensorFlow.js is de JavaScript versie die draait:
- In de browser — geen server nodig
- In Node.js (op de server) — voor API eindpunten (endpoints)
Waarom TensorFlow.js in plaats van Python?
Python TensorFlow:
- Aparte server nodig
- API-aanroepen tussen frontend en ML
- Complexere deployment (uitrol)
TensorFlow.js:
- Draait in dezelfde codebase
- Geen extra infrastructuur
- Model traint en voorspelt in JavaScript
- Werkt met Next.js API routes
Hoe het werkt:
import * as tf from '@tensorflow/tfjs'; // Importeer de bibliotheek (library)
// tf.tensor2d: Maakt een 2D array van getallen
const input = tf.tensor2d([[0.95, 0.85, 0.50, 0.72]]);
// model.predict: Voert het neurale netwerk uit
const prediction = model.predict(input);
// prediction.data(): Haalt het resultaat op als JavaScript getal
const score = await prediction.data(); // [0.82]
Belangrijke TensorFlow.js concepten:
| Concept | Betekenis | Voorbeeld |
|---|---|---|
tensor | Meerdimensionale array van getallen | [[0.95, 0.85], [0.72, 0.60]] |
model | Het neurale netwerk zelf | Bevat lagen (layers) en gewichten (weights) |
layer | Een laag neuronen | dense, conv2d, etc. |
fit() | Trainen van het model | Leert van voorbeelden |
predict() | Voorspelling maken | Invoer → Uitvoer |
compile() | Model configureren | Optimalisator (optimizer), verliesfunctie (loss function) |
Het Neurale Netwerk — TensorFlow.js
import * as tf from '@tensorflow/tfjs';
buildModel() {
const model = tf.sequential();
// Verborgen laag (hidden layer) met ReLU activatiefunctie
model.add(tf.layers.dense({
inputShape: [4], // 4 symbolische kenmerken
units: 8, // 8 neuronen
activation: 'relu'
}));
// Uitvoerlaag (output layer) met sigmoid (0-1 bereik)
model.add(tf.layers.dense({
units: 1,
activation: 'sigmoid' // Perst uitvoer naar 0-1 bereik
}));
model.compile({
optimizer: tf.train.adam(0.01), // Adam optimalisator
loss: 'binaryCrossentropy', // Verliesfunctie
metrics: ['accuracy'] // Nauwkeurigheid meten
});
return model;
}
Wat doet elk onderdeel?
dense: Een laag waar elk neuron verbonden is met alle invoerinputShape: [4]: Verwacht 4 getallen als invoerunits: 8: 8 neuronen in deze laagactivation: 'relu': Activatiefunctie (negatief → 0)sigmoid: Perst elke waarde naar 0-1 bereikadam: Slim optimalisatie-algoritme dat leert van foutenbinaryCrossentropy: Meet hoe ver de voorspelling afwijkt van werkelijkheid
Concreet Voorbeeld:
Voor ons GPT-5 artikel van Tweakers:
// Stap 1: Kenmerken extraheren (Extract features)
const features = [
0.98, // versheid: 2 uur oud
0.85, // bronvertrouwen: Tweakers
0.50, // categorie: AI (genormaliseerd van 1.5)
0.78 // titelkwaliteit: informatieve titel
];
// Stap 2: Converteer naar tensor
const input = tf.tensor2d([features]);
// Stap 3: Door netwerk halen (Run through network)
const prediction = model.predict(input);
// Stap 4: Resultaat ophalen (Get result)
const neuralScore = (await prediction.data())[0]; // 0.79
// Stap 5: Combineren met symbolisch (Combine with symbolic)
const symbolicScore = 0.82; // van regel-gebaseerde berekening
const hybridScore = 0.60 * symbolicScore + 0.40 * neuralScore;
// hybridScore = 0.60 * 0.82 + 0.40 * 0.79 = 0.808
Trainingspijplijn (Training Pipeline)
async train(samples) {
const features = samples.map(s => s.features); // Invoerdata (input data)
const labels = samples.map(s => s.label); // Verwachte uitvoer (expected output)
const xs = tf.tensor2d(features); // Converteer naar tensor
const ys = tf.tensor2d(labels, [labels.length, 1]);
await this.model.fit(xs, ys, {
epochs: 50, // 50 keer door alle data heen
batchSize: 32, // 32 voorbeelden per stap
validationSplit: 0.2 // 20% voor validatie (controle)
});
}
Wat is een Epoch?
Jip & Janneke uitleg: Stel je voor dat je voor een toets leert. Eén keer je boek doorlezen is 1 epoch. Als je 50 epochs doet, heb je het boek 50 keer gelezen. Hoe vaker je leest, hoe beter je onthoudt! Maar op een gegeven moment weet je alles al, en heeft nog een keer lezen geen zin meer.
Eén keer door alle trainingsdata heen. Na 50 epochs heeft het model 50× alle voorbeelden gezien.
Wat is Batch Size?
Jip & Janneke uitleg: In plaats van alle 1000 voorbeelden tegelijk te leren (te veel!), leer je steeds kleine groepjes van 32. Net zoals je huiswerk maakt in kleine stukjes: "Eerst oefening 1-5, dan 6-10, dan 11-15..." Zo wordt het behapbaar en kun je na elke groep verbeteren.
Hoeveel voorbeelden tegelijk verwerkt worden. Grotere batches = sneller maar minder precies.
Wat is Validation Split?
Jip & Janneke uitleg: Je houdt 20% van je oefenvragen apart en kijkt daar NIET naar tijdens het leren. Aan het eind test je jezelf met die vragen. Als je die ook goed maakt, weet je dat je het echt snapt — niet alleen uit je hoofd hebt geleerd!
20% van de data wordt apart gehouden om te testen of het model ook werkt op data die het niet eerder heeft gezien.
Database Structuur (Database Schema)
-- Interactie-tracking (Engagement tracking) voor trainingsdata
CREATE TABLE article_engagement_history (
article_id UUID REFERENCES articles(id),
vote_count INT, -- Aantal stemmen
bookmark_count INT, -- Aantal bladwijzers (bookmarks)
view_count INT, -- Aantal weergaven
engagement_score DECIMAL(5,4), -- Berekende interactie-score
hours_since_publish INT, -- Uren sinds publicatie
recorded_at TIMESTAMPTZ -- Wanneer opgenomen
);
-- Model-versiebeheer (Model versioning)
CREATE TABLE ml_model_stats (
version INT, -- Versienummer
accuracy DECIMAL(5,4), -- Nauwkeurigheid (0-1)
symbolic_weight DECIMAL(3,2), -- Gewicht voor symbolische score
neural_weight DECIMAL(3,2), -- Gewicht voor neurale score
weights JSONB, -- TensorFlow.js gewichten (als JSON)
trained_at TIMESTAMPTZ -- Wanneer getraind
);
Waarom Dit Werkt
1. Koudstartprobleem Opgelost (Cold Start Problem Solved)
Jip & Janneke uitleg: Stel je voor dat er een nieuw kind in de klas komt. Niemand kent hem nog. Bij de traditionele aanpak zou je zeggen: "Hoe populair ben je? Hoeveel vrienden heb je?" Maar hij is nieuw — hij heeft nog geen vrienden! Dat is het koudstartprobleem.
Onze oplossing: We kijken naar andere dingen. "Waar kom je vandaan? (betrouwbare bron) Wat zijn je hobby's? (categorie) Hoe stel je jezelf voor? (titel)" Zo kan het nieuwe kind toch meedoen, ook al kent niemand hem nog.
Nieuwe artikelen krijgen een eerlijke score gebaseerd op symbolische kenmerken:
- Recent? Hoge versheidscore
- Betrouwbare bron? Hoge bronbetrouwbaarheid
- Relevante categorie? Krijgt een versterking (boost)
Niet nodig om te wachten op interactiedata.
2. Uitlegbaarheid (Explainability)
We kunnen altijd uitleggen waarom een artikel hoog scoorde:
"Dit artikel scoorde 0.79 omdat:
- Zeer recent (2 uur geleden gepubliceerd): +0.95
- Van betrouwbare bron (Ars Technica): +0.90
- AI categorie versterking: ×1.5
- Neurale voorspelling gebaseerd op vergelijkbare artikelen: +0.82"
3. Continue Verbetering (Continuous Improvement)
Naarmate we meer interactiedata verzamelen, verbetert de neurale component. We hertrainen wekelijks met nieuwe data.
4. Aanpasbare Gewichten (Adjustable Weights)
Als het neurale netwerk slechte voorspellingen begint te maken, kunnen we het gewicht verschuiven:
// Conservatiever: meer op regels vertrouwen
config.symbolicWeight = 0.80;
config.neuralWeight = 0.20;
// Adaptiever: meer op geleerde patronen vertrouwen
config.symbolicWeight = 0.40;
config.neuralWeight = 0.60;
Geleerde Lessen (Lessons Learned)
Begin met Symbolisch (Start with Symbolic)
Begin met expliciete regels. Je begrijpt wat er gebeurt en kunt snel itereren (doorontwikkelen). Spring niet naar ML voordat je je domein (vakgebied) begrijpt.
Voeg Neuraal Toe als Verfijning (Add Neural as Refinement)
Gebruik ML als verbetering (enhancement), niet als vervanging (replacement). De 60/40 verdeling werkt goed als startpunt.
Houd Het Uitlegbaar (Keep It Explainable)
Elke voorspelling moet uitlegbaar zijn. Dit bouwt vertrouwen en helpt bij debuggen (fouten opsporen).
Versiebeheer voor Modellen (Version Your Models)
Sla modelgewichten (model weights) en meetwaarden (metrics) op. Wanneer een nieuwe versie slechter presteert, kun je terugdraaien (rollback).
Conclusie
Neuro-Symbolic AI combineert het beste van twee werelden:
- Transparantie van regel-gebaseerde systemen
- Leervermogen (Learning capability) van neurale netwerken
Voor content-rangschikking (content ranking) is dit een krachtige combinatie. Je krijgt voorspelbare, uitlegbare resultaten die verbeteren naarmate je meer data verzamelt.
Het systeem is gebouwd voor mediazone.nl om AI-nieuwsartikelen te ranken.
Gebouwd met TensorFlow.js, Supabase PostgreSQL, en Next.js
De volledige implementatie is open source — bekijk FeatureExtractor.ts, HybridModel.ts, en ModelTrainer.ts