INF2820 V2017 Oppgavesett 5 arbeidsoppgaver Dette er oppgaver du kan arbeide med på egen hånd. Du kan også arbeide med dem i gruppa 28.2 (hvis du har innleveringsoppgave 2 under kontroll) og spørre gruppelæreren hvis du lurer på noe. Opgavene vil være en god forberedelse til innleveringsoppgavesett 3 1.Klasser i Python Mange av de praktiske oppgavene i dette emnet er i Python og NLTK. Siden Python er et objektorientert språk og NLTK gjør stor bruk av klasser, trenger alle å gjøre seg litt kjent med hvordan klasser i Python virker og spesielt med noen av klassene NLTK bruker. De av dere som tok INF1001 i høst, bør være godt kjent med Pythons klasser og for dere som er vant med klasser i Java, bør det ikke være vanskelig å lese Python klasser. Du finner en innføring i Pythons bruk av klasser i kap. 15 i http://openbookproject.net/thinkcs/python/english3e/. På den andre siden legger ikke NLTK opp til at vi selv skal lage klasser og NLTK boka inneholder ikke eksempler som konstruerer klasser. I løsning av oppgavene står du fritt i valget mellom å bruke klasser eller, som NLTK boka, skrive prosedyrer som tar klasser som parametere. 2. Kontekstfrie grammatikker (CFG) i NLTK: klassen Grammar Deretter bør du arbeide deg gjennom seksjonene 8.1 8.3 I NLTK boka. http://nltk.org/book/ch08.html Når du ser på dette, er det to distinksjoner du bør holde klart for deg. Den ene er forskjellen mellom en *grammatikk* og en *parser*. En grammatikk er en deklarativ innretning for å beskrive et språk, der vi enten kan se på språket som en mengde strenger eller som en mengde trær. (Det finnes også grammatikkformalismer hvor grammatikkene beskriver andre strukturer enn trær. Det skal vi komme tilbake til.) En parser, derimot, er en prosedyre for å analysere strenger, avgjøre om de er i språket og tilskrive dem struktur. Det er flere forskjellige parsingstrategier for kontekstfrie språk, som vi ser på på forelesningene. Den andre distinksjonen er forskjellen mellom en *grammatikk* og det *Python objektet* vi bruker for å representere grammatikken. Når vi implementerer grammatikker, er det mange ulike måter vi kan representere dem. NLTK har valgt et bestemt format, som vi vil bruke. Tilsvarende for parsere er det forskjeller mellom selve algoritmen og den spesifikke implementasjonen vi gir av den. OBS: I det følgende bør du bruke nettutgaven av NLTK boka. Noen kommandoer er endret siden den trykte utgaven kom ut. a) Etter at du har ladet grammar1 i begynnelsen av seksjon 8.3 er det tid for å stoppe opp og inspisere hva slags objekt grammar1 er. Prøv følgende kommandoer: 1
>>> grammar1 >>> type(grammar1) >>> print(grammar1) Den første sier ikke så veldig mye. Den andre sier hva slags type objekt grammar1 er. I en klassedefinisjon kan vi definere hva som skal skje når vi skriver "print(<object>)" ("<objekt>" skiftes med navnet på et objekt.) De som har definert klassen Grammar har bestemt at dette var det som er mest viktig for oss å vite om klassen. Du kan også prøve >>> help(grammar1) som antagelig forteller deg mer enn du ønsker å vite. Men det kan være nyttig å kjenne denne *help* kommandoen. Du kan få bruk for den senere og i flere sammenhenger. b) Et Grammar objekt har også noen metoder for å få kontakt med dens indre hemmeligheter. Prøv >>> grammar1.start() >>> grammar1.productions() Det er også flere andre metoder du kan utforske. Skriv >>> grammar1. og fortsett med tab (for auto completion) (i ipython eller idle). Du kan få bruk for noen av disse metodene senere. c) Vær klar over at ting ikke alltid er slik de ser ut ved første øyekast. Det kan være mer som skjuler seg i et objekt. Hva slags objekt tror du grammar1.start() er? Skriv det til prompt og se hva du får. >>> grammar1.start() Prøv deretter >>> type(grammar1.start()) >>> s = grammar1.start() >>> s.symbol() >>> type(s.symbol()) d) Inspiser deretter grammar1.productions(). Hva slags type objekt er det? Plukk ut to regler fra "productions": en med en enslig terminal på høyresiden, en med mer enn et symbol, f.eks. >>> rule0 = grammar1.productions()[0] Hva slags objekt er en regel? Prøv tab komplementering for å se hvilke attributter og metoder en regel har. Du får tilgang til venstre og høyreside av en regel med: >>> rule0.lhs() >>> rule0.rhs() Hva slags objekt er venstresiden av en regel og hva slags objekt er høyresiden og hvert av elementene på høyresiden? 3. NLTK klassen Tree a) En av NLTK klassene vi vil støte på flere ganger inkludert i innleveringssett 3 er klassen Tree. Vi skal gjøre oss litt mer kjent med klassen. Først trenger vi noen trær. En mulighet for å fremskaffe dem er å 2
bruke en grammatikk og en parser. Vi kan følge seksjon 8.1 I NLTK boka (Seksjon 1 på nettet når du åpner kapittel 8 i nettversjonen). Vi skriver inn groucho_grammar og som i boka lager vi parser og eksempelsetning >>> sent = ['I', 'shot', 'an', 'elephant', 'in', 'my', 'pajamas'] >>> parser = nltk.chartparser(groucho_grammar) Så kan vi ta vare på alle trær for setningen ved >>> trees = [t for t in parser.parse(sent)] Vi kan så se litt mer på resultatet ved kommandoer som >>> trees >>> print(trees) >>> type(trees) b) trees er en liste, og det er kanskje ikke så mye å få ut av dette. Det kan være bedre å plukke ut ett av trærne >>> tre = trees[0] Så kan vi bruke vanlige metoder for å inspisere treet. >>> tre >>> type(tre) >>> print(tre) c) NLTKs Tree klasse inneholder flere metoder for å lage pene trær. Prøv >>> tre.draw() (Den siste virker på IFIs maskiner, men kan være avhengig av at du har installert utvidelser i tillegg til Python på din egen maskin.) d) Når en setning har flere analyser, kan vi være interessert i å se alle og sammenlikne dem. Det går greit med print og med Tree klasses pretty_print, f.eks. >>> for all t in trees: print(t) Eller vi kan gå utenom å lage listen av alle trær før vi tegner dem, i dette tilfellet, bare ta: >>> for t in parser.parse(sent): print(t) Men hvis vi prøver dette med draw(), vil den tegne ett vindu for hvert tre I rekkefølge ikke på likt. Prøv: >>> for all t in trees: t.draw() NLTK inneholder også en mulighet for å få flere trær tegnet i ett vindu. Prøv >>> from nltk.draw.tree import draw_trees >>> draw_trees(*trees) 3
Bruken av * er her generell Python syntaks for å levere en liste av argumenter. I dette tilfellet blir siste linje ekvivalent med >>> draw_trees(trees[0], trees[1]) e) Vi er interessert i den indre strukturen til trær. Et tre består av en toppnode, en merkelapp på denne og en ordnet liste av døtre, som selv er trær eller bladnoder. For å få tak i merkelappen bruker vi i denne versjonen av NLTK metoden label. >>> tre.label() >>> type(tre.label()) Døtrene er ordnet i en liste og kan nås som elementene i en vanlig liste. Prøv: >>> tre[0] >>> type(tre[0]) >>> print(tre[0]) >>> tre[1] >>> type(tre[1]) >>> print(tre[1]) >>> print(tre[ 1]) >>> print(tre[ 2]) (At vi får tak i disse subtrærne med indekser direkte til treet som i tre[i], og ikke må skrive noe slikt som tre.daughters[i] skyldes at klassen Tree er definert som en underklasse av listeklassen. Og så er det lagt til en del flere attributer og metoder på toppen, som f.eks. label.) Dette gir også muligheten til å gå rekursivt nedover i treet, prøv tre[1][0], tre[1][1], tre[1][1][0], tre[1][1][1][0][0], etc. Vi kan også bruke en enklere notasjon for dette, sammenlikn >>> tre[1][1][0] >>> tre[(1, 1, 0)] >>> tre[1,1,0] Hva tror du >>> tre[()] gir? f) For å skrive inn trær er det to metoder. Standardmetoden er å oppgi label og listen av døtre Tree(<label>, <liste av døtre>) Prøv. 4
>>> from nltk import Tree >>> tre3 = Tree('S', [Tree('NP', ['John']), Tree('VP', ['slept'])]) Men det er også en metode som kan lese en streng og gjøre om til et tre. (Obs denne har skiftet navn fra den trykte boka) >>> tre4 = Tree.fromstring('(S (NP John) (VP smile))') Prøv de to metodene og inspiser resultatet f.eks. ved pretty_print() og ved å be om tre3[1] og tre4[1]. g) Siden klassen Tree er en underklasse av klassen List har vi også tilgang til e n del standardmetoder for å fjerne og legge til subtrær tile n liste. Prøv >>> tre.pop() >>> tre.append(tre('(vp (V shot) (NP (Det an) (N elephant)))')) 4. Oppgave om setningsstruktur og trær NLTK oppgave 8.9.3: Lag først parentesstrukturen ( the bracketed structure ) med penn og papir. Bruk så Tree klassen for å generere trærne og pretty_print og draw metodene for å inspisere det nærmere. 5. Begynnende parsing a) Eksperimenter med nltk.app.rdparser() Prøv å parse setninger hvor du selv styrer prosessen. La deretter applikasjonen slev styre ("step"). Eksperimenter med endring av grammatikkreglene (jfr. oppg. 9 i seksj 8.9 i NLTK boka). b) Eksperimenter tilsvarende med nltk.app.srparser(). SLUTT 5