Extra#
Kies een project#
Naast het vier op een rij project waar aan gewerkt gaat worden, bestaan er ook andere projecten. Er zijn drie andere interesante projecten waar aan gewerkt kan worden. De drie andere projecten zijn:
Machine learning met Picobot. In dit project leert een picobot hoe hij een kamer kan verkennen met behulp van een genetisch algoritme, onderdeel van machine learning.
Text-ID. In dit project bepaal je wie een tekst heeft geschreven door te kijken naar de âhantekeningâ van een schrijver. Dit is een methode om teksten van chat-gpt te herkennen. Als je aan dit project wil werken is het verstandig om onderstaande extra opdracht te maken; Text genereren met Markov processen
3D-python. vpython geeft de mogelijkheid om een 3d game te maken. De eerste stappen is het maken van een simpel spel om bekend te raken met het systeem, maar daarna zijn de mogelijkheden eindeloos.
Text genereren met markov processen#
Gebruik deze code als beginpunt voor je bestand
# functie #1
#
def create_dictionary(filename):
pass
# functie #2
#
def generate_text(d, n):
pass
Pythonâs dictionaries gebruikenâŠ#
Je doel bij deze opgave is om een programma te schrijven dat uit zichzelf âbetekenisvolleâ tekst kan genereren! Je gaat dit doel bereiken door het schrijven van een algoritme dat met behulp van zogeheten Markovprocessen tekst genereert.
Deze opgave gebruikt een Python-dictionary om tekst te modelleren; en daarna te genereren.
Voorbeeldcode#
Je kan een paar voorbeelden vinden van hoe je in Python dictionaryâs kan gebruiken om tekst te analyseren met een vocabulaireteller (in tegenstelling tot een woordenteller) in deze voorbeeldcode; dit kan je gebruiken als naslagwerk voor wat je nodig hebtâŠ
Tekst genereren met Markovprocessen#
Hier is het basisidee: het Nederlands is een taal met veel structuur. Woorden hebben de neiging (sterker, de verplichting) om alleen in bepaalde volgordes voor te komen. De regels van de grammatica bepalen welke combinaties van verschillende stukken spraak toegestaan zijn. De zin âDe kat klimt de trap opâ heeft bijvoorbeeld een geldige woordvolgorde. âTrap de op kat klimtâ heeft dat niet. Daarnaast beperkt de semantiek (de betekenis van een woord of zin) de mogelijke combinaties nog verder. âDe trap klimt de kat opâ is een perfect geldige zin, maar het is wel onzinnig en je zal deze woordvolgorde in de praktijk zeer waarschijnlijk niet tegenkomen.
Zelfs zonder de formele regels van het Nederlands te kennen, of de betekenis van Nederlandse woorden, kunnen we een idee krijgen van welke woordcombinaties geldig zijn door simpelweg naar correcte Nederlandse teksten te kijken en de combinaties van woorden te bekijken die in de praktijk voorkomen. We kunnen daarna op basis van onze observaties nieuwe zinnen maken door willekeurig woorden te selecteren aan de hand van hoe vaak ze in die volgorde voorkomen. Bekijk bijvoorbeeld de volgende tekst:
âIk houd van rozen en anjers. Ik dacht, ik koop rozen voor mijn verjaardag.â
Als we beginnen met het kiezen van het woord âikâ, kunnen we zien dat âikâ gevolgd kan worden door âhoudâ, âdachtâ en âkoopâ, met in deze tekst een gelijke kans. We kiezen Ă©Ă©n van deze woorden willekeurig en voegen die toe aan onze zin, bijvoorbeeld âik koopâ. Hierdoor moet het volgende woord ârozenâ zijn, omdat in onze voorbeeldtekst âkoopâ altijd (dat wil zeggen, Ă©Ă©n keer) gevolgd wordt door ârozenâ. Als we dit proces herhalen kunnen we bijvoorbeeld de zin âik koop rozen en anjersâ krijgen. Merk op dat dit een geldige Nederlandse zin is, maar niet Ă©Ă©n die we eerder hebben gezien. Andere nieuwe zinnen die we zouden kunnen genereren zijn âik houd van rozen voor mijn verjaardagâ en âik koop rozen voor mijn verjaardagâ.
Formeel gezegd heet het proces dat we gebruiken om deze zinnen te genereren een Markovproces van de eerste orde. Een Markovproces van de eerste orde is een proces waarin de toestand op tijdstip t+1 (dat wil zeggen, het volgende woord) alleen afhankelijk is van de toestand op tijdstip t (dat wil zeggen, het vorige woord). In een Markovproces van de tweede orde is het volgende woord afhankelijk van de twee vorige woorden, en zo verder. Ons voorbeeld hierboven was een proces van de eerste orde omdat de keuze voor het volgende woord alleen afhing van het huidge woord. Merk op dat de waarde van het volgende woord onafhankelijk is van de positie van het woord, en alleen maar afhangt van zijn directe geschiedenis. Dat wil zeggen dat het niet uitmaakt of we het 2e woord kiezen of het 92e. Het maakt alleen uit wat het 1e of 91e woord is, respectievelijke.
Teksten analyseren en genereren#
In het eerste deel van deze opgave implementeer je een Markovproces van de eerste orde om teksten mee te genereren. Om deze functie te schrijven heb je twee andere functies nodig: (1) Ă©Ă©n om een bestand te verwerken en een dictionary van geldige woordcombinaties te maken en (2) een andere om de nieuwe tekst daadwerkelijk te genereren.
Als je hier geen speciale code voor schrijft zal je programma woorden als verschillend beschouwen zelfs als ze alleen maar verschillen in hoofdletters of leestekens. Dit is voor deze opgave geen probleem: spam
, Spam
en spam.
(met punt erachter) mag je allemaal als verschillende woorden beschouwen.
Hier zijn de details over de twee functies die je moet schrijven:
De functie `create_dictionary#
create_dictionary(filename)
krijgt een string als argument mee, wat de naam van een tekstbestand is dat wat voorbeeldtekst bevat. Het moet een dictionary teruggeven waarvan de sleutels woorden zijn die in de tekst voorkomen en waarvan de waardes lijsten met woorden zijn die op het sleutelwoord kunnen volgen. Merk op dat je een manier moet bedenken waarop je bijhoudt hoe vaak een woord op het sleutelwoord volgt. Dat wil zeggen dat als het woord âfietsâ twee keer zo vaak wordt gevolgd door het woord âkopenâ als door het woord âreparerenâ, je dictionary deze informatie ook moet bevatten. Je kan bijvoorbeeld het woord meerdere keren opnemen in de lijst.
De dictionary die wordt teruggegeven door create_dictionary
geeft je de mogelijkheid om woord t
+1 te kiezen als je woord t
al hebt. Maar hoe kies je het eerste woord, als je geen bestaand woord hebt die je als sleutel voor
de dictionary kan gebruiken?
Om dit geval te kunnen afhandelen, moet je dictionary de string $
bevatten; dit is het startsymbool. Het eerste woord in het bestand moet op deze string âopvolgenâ. Bovendien moet elke string die volgt op het laatste woord van een zin deze string opvolgen. Een woord dat een zin eindigt wordt gedefinieerd als elk woord waarvan het laatste teken een punt .
, een vraagteken ?
of een uitroepteken !
is.
:{admonition} Bepalen of een woord eindigt op een leesteken :class: tip
Het makkelijkst is om w[-1]
te controleren. We zijn alleen geĂŻnteresseerd in '.'
, '?'
en '!'
.
Onthoud dat, als je or
gebruikt, je elke test helemaal moet uitschrijven, bijvoorbeeld,
if w[-1] == '.' or w[-1] == '?' or ...
Je herinnert je misschien het alternatief met in
(van het controleren van klinkers in een vorige opgaveâŠ): if w[-1] in '.!':
Strategie#
Merk op dat in het college bijna de hele functie besproken is. Hier zie je de voorbeeldcode, met de uitleg daaronder:
d = {}
pw = '$'
for nw in words:
if pw not in d:
d[pw] = [nw]
else:
d[pw] += [nw]
pw = ...
Als de gegeven zin Ik lust spam. Ik eet taart!
is, moet de inhoud van d na het uitvoeren van deze code gelijk zijn aan {'$': ['Ik', 'Ik'], 'Ik': ['lust', 'eet'], 'lust': ['spam'], 'eet' : ['taart']}
.
Verder heb je code gezien waarmee je woorden kan tellen:
def vocab_count(filename):
"""vocabulary-counting program"""
# bestand lezen
f = open(filename)
text = f.read()
f.close()
# woorden tellen
words = text.split()
print("Er zijn", len(words), "woorden")
# het aantal keer dat elk woord voorkomt tellen
d = {}
for w in words:
if w not in d:
d[w] = 1
else:
d[w] += 1
print("Er zijn", len(w), "verschillende woorden")
# d teruggeven voor andere code
return d
In het bijzonder heb je wel de code om bestanden te lezen bovenaan de functie nodig, maaar niet de code om woorden te tellen daaronderâŠ
In plaats daarvan heb je de code over nw
en pw
(deze staan respectievelijk voor ânew wordâ en âprevious wordâ) uit het andere fragment nodig. Hier is het resultaat:
pw = '$'
for nw in words:
if pw not in d:
d[pw] = [nw]
else:
d[pw] += [nw]
pw = nw
# controleer hierna of de nieuwe pw eindigt op een
# leesteken -- als dat _wel_ zo is zet dan pw op '$'
Het enige wat in dit voorbeeld nog niet staat is hoe je woorden die eindigen op een leesteken kan verwerken. Dat moet je zelf bedenken (maar er staan tips in het commentaar hierboven)âŠ
Je code controlerenâŠ#
Om je code te controleren, kan je de volgende tekst in een plat tekstbestand zetten (bijvoorbeeld in een nieuw â.txtâ-bestandsvenster in VSCode):
A B A. A B C. B A C. C C C.
Sla dit bestand op als test.txt
in dezelfde directory waar je bestand wk10ex3.py
staat. Kijk dan of je dictionary d
hetzelfde is als in het voorbeeld hieronder:
In [2]: d
Out[2]:
{'$': ['A', 'A', 'B', 'C'],
'A': ['B', 'B', 'C.'],
'B': ['A.', 'C.', 'A'],
'C': ['C', 'C.']}
De elementen in elke lijst hoeven niet in dezelfde volgorde te staan, maar ze moeten wel in dezelfde hoeveelheden aanwezig zijn als hierboven voor elk van de vier sleutels, âAâ, âCâ, âBâ en â$â.
Hier is de text dat in het college als voorbeeld werd gebruikt.
Ik wil taarten en 42 en spam.
Ik krijg toch spam en taarten voor
de vakantie? Ik wil 42 taarten!
Teksteditor op Mac
Als je de Teksteditor op een Mac gebruikt, moet je Opmaak ⊠Converteer naar platte tekst gebruiken; je hebt een .txt
-bestand nodig, geen .rtf
-bestand. :::
Het is slim om te controleren of de dictionary die je als uitvoer krijgt met dit bestand hetzelfde is als degene die je het college hebt gezien (merk op dat de volgorde van de sleutels kan veranderen):
In [1]: d = create_dictionary('a.txt')
In [2]: d
Out[2]:
{'krijg': ['toch'],
'voor': ['de'],
'wil': ['taarten', '42'],
'toch': ['spam'],
'Ik': ['wil', 'krijg', 'wil'],
'spam': ['en'],
'42': ['en', 'taarten!'],
'$': ['Ik', 'Ik', 'Ik'],
'taarten': ['en', 'voor'],
'de': ['vakantie?'],
'en': ['42', 'spam.', 'taarten']}
De functie generate_text
#
generate_text(d, n)
krijgt een dictionary met woordovergangen (gemaakt door je functie create_dictionary
van hierboven) mee, en een positieve integer n
. Hiermee moet generate_text
een string van n
woorden afdrukken.
Het eerste woord moet willekeurig gekozen zijn uit de woorden die kunnen volgen op het startsymbool "$"
. Bedenk dat random.choice
een willekeurig element uit een lijst kan kiezen! Het tweede woord moet willekeurig gekozen worden uit de lijst woorden die kunnen volgen op het eerste woord, en zo verder. Als een gekozen woord eindigt op een punt .
, een vraagteken ?
of een uitroepteken !
, moet de functie generate_text
deze gebeurtenis herkennen en een nieuwe zin beginnen door opnieuw een willekeurig woord te kiezen dat kan volgen op een "$"
.
Laat '$'
niet terugkomen in de uitvoertekst; het is alleen een interne markering voor je functie.
In deze opgave hoef je de woorden in het tekstbestand niet te ontdoen van leestekens. Laat leestekens staan zoals ze in de tekst voorkomen; en als je woorden genereert, hoe je je geen zorgen te maken als de gegenereerde tekst niet eindigt op een geldig leesteken, dat wil zeggen, je zou kunnen eindigen zonder punt, maar dat is prima. De tekst die je genereert zal niet perfect zijn, maar je kan verrast worden door hoe goed hij is!
Je mag ervan uitgaan dat er geen leestekens op zichzelf (of andere randgevallen) in de invoer voorkomen. Het beste kan je bij zulke onverwachte gevallen gewoonweg een nieuwe zin beginnen. In het specifieke geval dat je bij een woord uitkomt dat alleen maar als het laatste woord in je trainingsdata voorkomt (dat wil zeggen dat er geen opvolgende woorden zijn), dan kan je gewoon verder gaan met het genereren van tekst vanaf het startsymbool $
.
Hier zijn twee voorbeelden die de dictionary d
van hierboven gebruiken. Je eigen uitvoer zal verschillen omdat de woorden willekeurig gekozen zijn, maar ze zouden er op moeten lijk
In [3]: generate_text(d, 20)
Out[3]: B C. C C C. C C C C C C C C C C C. C C C. A
In [4]: generate_text(d, 20)
Out[4]: A B A. C C C. B A B C. A C. B A. C C C C C C.
Een gegenereerd essay van 500 woorden!#
Voor het laatste deel van deze opdracht moet je een interessant tekstbestand vinden, hiermee een Markovmodel van de eerste orde maken, en zelf wat tekst genereren! Een kunstmatig essay dus.
Je kan zelf kiezen welke invoertekst je wilt gebruiken, en die mag ook best Engels zijn! Je kan bijvoorbeeld willekeurige scenes uit het werk van Shakespeare gebruiken⊠ook kindergedichtjes kunnen (soms) goed werken. Je kan ook songteksten, speeches, of je eigen werk gebruiken; alles is mogelijk.
Waarschuwing
Probeer gegenereerde essays niet te gebruiken als echte essays!
Platte-tekstbestanden
De makkelijkste manier om een platte-tekstbestand te maken dat je als invoer kan gebruiken is door de tekst te kopiëren van waar je hem gezien hebt via het menu of Control-C (Command-C op een Mac), en daarna een leeg tekstbestand te openen met Kladblok (Windows) of Teksteditor (Mac). Sla je .txt
-bestand met platte tekst, bijvoorbeeld spam.txt
, op in dezelfde map als je bestand wk10ex3.py
.
Teksteditor op Mac
Teksteditor slaat bestanden standaard op als rich text format of .rtf
. Je kan dit aanpassen door Opmaak ⊠Converteer naar platte tekst te kiezen. Doe dit om te zorgen dat je een .txt
-bestand met platte tekst krijgt.