- Je berekent het inproduct van even lange vectoren.
- Je onderscheidt lengte van richting met cosinusgelijkenis.
Twee lezers kiezen boeken over techniek en geschiedenis. Ada leest er drie en vier, Bram zes en acht: Bram leest meer, maar de verhouding is gelijk. Kunnen we die overeenkomst zien zonder alleen de grootste aantallen te belonen?
We schrijven hun voorkeuren als a = (3, 4) en b = (6, 8), steeds met techniek eerst. Bram staat in het pijldiagram twee keer zo ver van de oorsprong, op dezelfde lijn. We bouwen nu een score die dat verschil tussen omvang en richting zichtbaar maakt.
1. Bijdragen per component combineren
Kijk eerst naar de oranje pijl (1, 0). Alleen de horizontale component telt mee in die richting: (3, 4) draagt drie bij, (0, 4) nul en (−3, 4) min drie. Een positieve bijdrage loopt mee met de referentie, een negatieve ertegenin.
Verander de componenten. Wat gebeurt er met de lengte?
Zet y op 4 en wissel x tussen 3, 0 en −3. De meetwaarde inproduct met (1, 0) volgt precies die horizontale bijdrage. Bij een referentie met twee niet-nulle componenten combineer je beide bijdragen.
Het inproduct (dot product) vermenigvuldigt bijbehorende componenten en telt de producten op. Voor twee componenten schrijven we:
De punt betekent inproduct; a en b zijn de twee vectoren. Het kleine cijfer 1 verwijst naar de eerste component, 2 naar de tweede. Naast elkaar geschreven componenten worden vermenigvuldigd; de uitkomst is één getal.
Neem a = (2, 3) en b = (4, −1). De eerste bijdrage is 2 × 4 = 8, de tweede 3 × −1 = −3; samen krijg je 5. Dezelfde berekening in Python:
a = [2, 3]
b = [4, -1]
print(a[0] * b[0] + a[1] * b[1]) # 5
Een vector kan ook drie of meer componenten hebben: je telt gewoon meer producten op. len(a) telt de componenten; range(len(a)) geeft hun Pythonposities vanaf nul. Deze lus doorloopt ze één voor één:
def som(waarden):
totaal = 0
for i in range(len(waarden)):
totaal = totaal + waarden[i]
return totaal
print(som([8, -3])) # 5
Inspringing groepeert Pythonregels: binnen de functie spring je in, binnen de lus nogmaals. return staat ná de lus op hetzelfde inspringniveau als for; binnen de lus zou hij al na de eerste component stoppen. Voor het inproduct tel je per positie het product a[i] * b[i] op.
Maak hiermee inproduct(a, b). Controleer eerst of beide lijsten even lang zijn; zo voorkom je een schijnbaar geldige score op onvolledige gegevens. if voert de ingesprongen regel alleen uit als de voorwaarde klopt; != betekent “niet gelijk aan”.
if len(a) != len(b):
raise ValueError("Verschillende lengtes")
raise ValueError(...) stopt met die foutmelding. Bij gelijke lengtes ga je door met de optellus en geef je het totaal terug.
2. De invloed van omvang weghalen
Terug naar Ada en Bram: hun inproduct is 3 × 6 + 4 × 8 = 18 + 32 = 50. Ada's vectorlengte is 5; Brams lengte is de wortel van 36 + 64, dus 10. Hun inproduct gedeeld door beide lengtes wordt 50 / (5 × 10) = 1.
Als je dezelfde vector met een positieve factor 2 schaalt en de andere gelijk houdt, verdubbelt het inproduct én de lengte van die vector. In de verhouding vallen die twee factoren tegen elkaar weg. Zo vergelijk je richting zonder een groter leesvolume automatisch te belonen.
Dit heet cosinusgelijkenis (cosine similarity). De teller boven de breukstreep is het inproduct; de noemer eronder vermenigvuldigt de twee lengtes. Je hebt hiervoor geen aparte goniometrische Pythonfunctie nodig.
a = [3, 4]
b = [6, 8]
lengte_a = (3 ** 2 + 4 ** 2) ** 0.5
lengte_b = (6 ** 2 + 8 ** 2) ** 0.5
product_lengtes = lengte_a * lengte_b
print(inproduct(a, b) / product_lengtes)
# 1.0
Voor meer componenten tel je met een lus iedere component maal zichzelf op en neem je daarna de wortel. Dezelfde richting geeft score 1, loodrecht geeft 0, tegengesteld −1. Een nulvector heeft geen richting: weiger die vóór de deling.
if lengte_a == 0:
raise ValueError("Nulvector")
if lengte_b == 0:
raise ValueError("Nulvector")
== test gelijkheid; = bewaart een waarde. Bouw nu cosinus(a, b) met lengtes, deze controles en de verhouding.
3. Kies op richting, niet op het grootste inproduct
Onze vraagvector is (1, 0). Vergelijk deze drie kandidaten met de zojuist gebouwde functies:
| Index | Kandidaat | Inproduct | Lengte | Cosinus |
|---|---|---|---|---|
| 0 | (4, 4) | 4 | wortel van 32 | ≈ 0,707 |
| 1 | (2, 0) | 2 | 2 | 1 |
| 2 | (0, 3) | 0 | 3 | 0 |
Veelgemaakte fout: het grootste inproduct kiezen zou kandidaat 0 opleveren. Voor de meest gelijke richting wint juist kandidaat 1. Je kunt beide pijlen binnen het bereik van de visualisatie zetten.
Begin met kandidaat 0 als beste tot nu toe. Bereken daarna iedere volgende score en onthoud index plus score als die groter is; Python schrijft dat als >. Dit extra voorbeeld met vooraf berekende scores toont ook een gelijkspel:
scores = [0.4, 0.9, 0.9]
beste_index = 0
beste_score = scores[0]
for i in range(1, len(scores)):
if scores[i] > beste_score:
beste_index = i
beste_score = scores[i]
# De tweede 0.9 vervangt de eerste niet.
print(beste_index) # 1
range(1, ...) begint bij de tweede positie. In dichtstbij(query, kandidaten) vervang je de vooraf berekende score door cosinus(query, kandidaten[i]); kandidaten[i] kiest één hele vector. Geef de beste index terug; invoer is hier niet leeg, heeft passende lengtes en bevat geen nulvectoren.
Wat neem je mee?
- Het inproduct telt bijdragen per component op en hangt ook van omvang af.
- Cosinusgelijkenis deelt de omvang weg voor niet-nulle vectoren; zij meet richtingsovereenkomst.
- Buurzoeken vergelijkt scores en bewaart de beste index; de eerste wint een gelijkspel.
Een score van 1 bewijst alleen dezelfde richting in deze gekozen kenmerken, geen identieke personen of gegarandeerd passende boeken. Later maak je met modellen vectoren voor tekst en beoordeel je of deze vergelijking nuttig is. Eerst leer je in de matrixmodule hoe je meerdere vectorbewerkingen georganiseerd uitvoert.