Sets en een taalmodel¶
In het eerste college van deze week telde je met een dictionary hoe vaak elk woord in een lijst voorkomt. Nu stel je er twee nieuwe vragen over. Welke woorden komen erin voor, zonder ze te tellen? En welk woord volgt op welk woord? Met dat laatste bouw je een taalmodel dat zelf tekst maakt.
Sets¶
In het eerste college telde je de woorden in ["spam", "spam", "taart", "spam"]
en kreeg je word_count = {"spam": 3, "taart": 1}. Het aantal verschillende
woorden is het aantal sleutels: 2. Een dictionary is daar eigenlijk net iets te zwaar voor: hij houdt ook een aantal
per woord bij, en dat heb je hier niet nodig. Voor “welke waarden komen voor,
zonder duplicaten” is er een lichter datatype: de set.
list_of_words = ["spam", "spam", "taart", "spam"]
unique_words = set(list_of_words)
unique_words
{'spam', 'taart'}
Een set heeft geen volgorde en geen duplicaten. len(unique_words) is daarom
2, net zoveel als het aantal sleutels van word_count, maar dan zonder dat je
een dictionary nodig had om het te tellen.
Python toont een set tussen accolades, en zo schrijf je hem ook zelf op:
{"appel", "peer"}. Dat lijkt op een dictionary, maar er staan alleen waarden
in, geen paren sleutel: waarde. Een lege set schrijf je als set(), want {}
is een lege dictionary.
len(unique_words)
2
Zoeken gaat met in, net als bij een lijst of dictionary. En je breidt een set
uit met .add():
"taart" in unique_words
True
unique_words.add("koekje")
unique_words
{'koekje', 'spam', 'taart'}
Unie en doorsnede¶
.add() breidt één set uit. Twee sets combineer je met unie en doorsnede -
allebei zowel als methode als als operator:
groep_a = {"appel", "peer", "banaan"}
groep_b = {"appel", "kiwi", "banaan"}
De unie: alles wat in minstens één van de twee zit.
groep_a.union(groep_b)
{'appel', 'banaan', 'kiwi', 'peer'}
groep_a | groep_b
{'appel', 'banaan', 'kiwi', 'peer'}
De doorsnede: alles wat in allebei zit.
groep_a.intersection(groep_b)
{'appel', 'banaan'}
groep_a & groep_b
{'appel', 'banaan'}
Unie en doorsnede leveren een nieuwe set op, uit twee bestaande. Dat is
iets anders dan .add(), dat er één set aan het uitbreiden was.
Woorden tellen in een tekst¶
Neem deze tekst:
text = "Ik wil taarten en 42 en spam. Ik krijg toch spam en taarten voor de vakantie? Ik wil 42 taarten!"
Dit is het tellen uit het eerste college, nu met .get en voor alle woorden
van de tekst:
list_of_words = text.split()
word_count = {}
for word in list_of_words:
word_count[word] = word_count.get(word, 0) + 1
print(f"Er zijn {len(word_count)} verschillende woorden")
word_count
Er zijn 13 verschillende woorden
{'Ik': 3,
'wil': 2,
'taarten': 2,
'en': 3,
'42': 2,
'spam.': 1,
'krijg': 1,
'toch': 1,
'spam': 1,
'voor': 1,
'de': 1,
'vakantie?': 1,
'taarten!': 1}
Van tellen naar opvolgers¶
Met een paar aanpassingen van het programma dat woorden telt, is het mogelijk om een eenvoudige taalgenerator te maken.
In plaats van het tellen van woorden wordt gekeken welk woord na een voorgaand woord wordt gebruikt.
Het programma staat al klaar in het bestand assets/markov.py. De regel
%run assets/markov.py hieronder is een opdracht van Jupyter, geen Python: hij
voert dat bestand uit. Daarna kun je de twee functies gebruiken die erin staan:
Functie |
Doet |
|---|---|
|
maakt het model van een tekst: welk woord volgt op welk woord |
|
maakt met dat model een nieuwe tekst van |
In het werkcollege schrijf je deze functies zelf. In het model staat de sleutel
"$" voor het begin van een zin: in zijn lijst staan de woorden waarmee een zin
begint.
%run assets/markov.py
words_follow = create_dictionary(text)
words_follow
{'$': ['Ik', 'Ik', 'Ik'],
'Ik': ['wil', 'krijg', 'wil'],
'wil': ['taarten', '42'],
'taarten': ['en', 'voor'],
'en': ['42', 'spam.', 'taarten'],
'42': ['en', 'taarten!'],
'krijg': ['toch'],
'toch': ['spam'],
'spam': ['en'],
'voor': ['de'],
'de': ['vakantie?']}
Het stappenplan voor het maken van een taalmodel is als volgt:
begin met het vorige woord
previous_wordals “$”voor elk volgend woord in de lijst van woorden voeg het toe aan …
maak
previous_wordgelijk aannew_wordbehalve als
new_word[-1]punctuatie is, maakprevious_worddan gelijk aan …
Tekst genereren¶
Een $ staat voor het begin van een zin. Dit model heeft voor elk woord een
lijst van woorden waar uit gekozen kan worden. Als we dit random doen, wordt
er een random tekst gegenereerd.
generate_text(words_follow, 42)
'Ik wil 42 en taarten en spam. Ik krijg toch spam en spam. Ik wil taarten en taarten voor de vakantie? Ik wil taarten en spam. Ik wil 42 en taarten en spam. Ik krijg toch spam en spam. Ik wil taarten '
Als we een veel grotere tekst gebruiken om onze database mee te vullen, kunnen we betere teksten genereren.
Het stappenplan voor het genereren van tekst is als volgt:
begin met
previous_wordals de “$” stringkies random een
new_worddat volgt opprevious_worden voeg het toe aan …maak
previous_wordgelijk aannew_wordbehalve als
new_word[-1]punctuatie is, maak danprevious_wordgelijk aan …
Opdrachten¶
Opdracht 1¶
Dit is opdracht 1 uit het eerste college, nu voor sets. In de cel hieronder
staan twee sets, s en t, met twee aanroepen erop.
Voorspel eerst voor elke aanroep of s erdoor verandert, en wat de aanroep
teruggeeft:
Aanroep |
Verandert |
Wat komt er terug? |
|---|---|---|
|
||
|
Voer daarna de cel hieronder uit en vergelijk de uitvoer met je voorspelling. Een set heeft geen volgorde, dus de volgorde van de woorden kan bij jou anders zijn.
s = {"spam", "taart"}
t = {"taart", "thee"}
print(s.add("koekje"))
print(s)
print(s.union(t))
print(s)
Opdracht 2¶
Van twee avonden is bijgehouden wie er kwam, in de sets monday en
thursday in de cel hieronder.
Maak met unie en doorsnede twee sets: both, met wie op beide avonden kwam, en
at_least_once, met wie minstens één keer kwam. De test eronder slaagt als ze
kloppen.
Leg daarna in één zin uit waarom je hier een set gebruikt en geen dictionary. Je antwoord klopt als erin staat dat je per naam alleen wilt weten óf iemand er was, en dus geen waarde per naam nodig hebt.
monday = {"Anne", "Bram", "Cas", "Daan"}
thursday = {"Bram", "Daan", "Eva"}
# jouw oplossing
assert both == {"Bram", "Daan"}
assert at_least_once == {"Anne", "Bram", "Cas", "Daan", "Eva"}
Opdracht 3¶
Maak het model van deze tekst met de hand, zonder create_dictionary aan te
roepen:
short_text = "De kat slaapt. De kat eet. De hond blaft."
Schrijf de dictionary letterlijk uit in my_model. De test eronder vergelijkt
jouw model met wat create_dictionary ervan maakt. create_dictionary volgt
deze regels:
Regel |
In |
|---|---|
De tekst wordt op spaties gesplitst, dus een leesteken hoort bij het woord |
|
Het eerste woord van de tekst volgt op |
|
Een woord dat eindigt op |
na |
Een woord dat een zin afsluit, wordt dus geen sleutel |
|
Volgt een woord vaker op hetzelfde woord, dan staat het vaker in de lijst |
Hint
Loop de tekst woord voor woord langs en houd bij wat het vorige woord was. Kijk
ter vergelijking naar words_follow hierboven: "spam." en "vakantie?"
zijn daar geen sleutel, en de lijst van "$" is ["Ik", "Ik", "Ik"].
short_text = "De kat slaapt. De kat eet. De hond blaft."
# jouw oplossing
my_model = {}
assert my_model == create_dictionary(short_text)
Opdracht 4¶
Het model words_follow kan alleen woorden achter elkaar zetten die in de
tekst ook zo achter elkaar stonden. Welke van deze zinnen kan het model maken?
Ik wil 42 en spam.Ik krijg taarten!Ik krijg toch spam en 42 taarten!
Beslis het eerst door words_follow te lezen. Controleer het daarna met in:
voor elk woord in de zin moet het volgende woord in de lijst van dat woord
staan, en het eerste woord in de lijst van "$". Eén van de drie kan het model
niet maken.
Let op: een leesteken hoort bij het woord. Bij zin 1 vraag je dus of "spam."
op "en" kan volgen, niet "spam".
Hint
"wil" in words_follow["Ik"] zegt of wil op Ik kan volgen.
# jouw oplossing
Opdracht 5¶
In words_follow staat "wil" twee keer in de lijst van "Ik". Een set zou
dat dubbele woord weglaten.
Druk
words_follow["Ik"]enset(words_follow["Ik"])af.Het model kiest willekeurig een woord uit de lijst. Hoe groot is de kans dat het na
Ikhet woordwilkiest? En hoe groot zou die kans zijn als het uit de set koos?Leg in één zin uit waarom
create_dictionaryeen lijst gebruikt en geen set.
Je antwoord bij 2 klopt als de twee kansen verschillen en de kans bij de lijst de grootste is. Je antwoord bij 3 klopt als erin staat dat de lijst bijhoudt hoe vaak een woord volgt, en dat een set dat verschil weggooit.
# jouw oplossing
Tot slot¶
In het werkcollege werk je dit uit tot een programma dat een heel essay genereert.