Kurssin virallinen sivu on tällä hetkellä http://www.ling.helsinki.fi/kit/2008k/clt131/
Ensin kurssin hyvin lyhyt esittely, sitten tarkempi kuvaus opetuskerroista.
Kurssilla edellytettävät kieliteknolåågilliset tuotokset: frekvenssilista, kollokaatio, konkordanssi, tekstin normalisointi, sanalistat, käänteissanalistat, poiminta.
Kurssilla mahdollisesti käytäviä asioita:
Komentoja:
Opetuskerrat:
Materiaalia:
Raakateksti on tiedostomuoto. Sen pääte on yleensä .txt, mutta Unixissa monet päätteettömät tiedostot ovat myös raakatekstiä. Raakatekstissä tiedoston sisältö tulkitaan suoraan merkeiksi, eli raakatekstitiedosto on jono merkkejä. Muotoiluja on vain muutama: rivinvaihto merkitään tietyllä merkillä ja sarkainmerkki tulkitaan vaakasuuntaiseksi hypyksi eteenpäin. Sivunvaihdollekin on merkki, mutta sitä käytetään harvoin ja harvat ohjelmat ymmärtävät sitä sivunvaihdoksi.
Mikä tahansa tiedosto on tulkittavissa raakatekstinä, koska tiedoston sisältö on oikeastaan vain jono lukuja ja jokaiselle luvulle on olemassa vastaava merkki. Monet tiedostot (kuvat jne.) ovat kuitenkin raakatekstiksi tulkittuina merkkimössöä, josta ei saa mitään selvää, ja jota ei ole toivoakaan muokata tekstitoimittimella (text editor).
Sen sijaan on myös paljon tiedostomuotoja, jotka ovat periaatteessa raakatekstiä, mutta joissa asetetaan lisää rajoitteita tiedoston sisällölle. Tällainen on esimerkiksi HTML: siinä on raakatekstin seassa tietynlaisia merkintöjä, tageja, joilla merkitään muotoilut ynnä muu, mitä ei raakatekstillä pysty esittämään. Muita esimerkkejä "rajoitetusta" raakatekstistä ovat esimerkiksi Unixin erilaiset tietokannat kuten /etc/passwd, asetustiedostot kuten /etc/pine.conf ja lokitiedostot kuten /var/log/daemon, ohjelmien lähdekooditiedostot, sekä monet kuvauskielet kuten dokumenttikuvauskieli TeX ja vektorigrafiikkakuvauskieli SVG.
Raakatekstiä onkin kahta tyyppiä, joiden välinen raja ei ole selvä. On toisaalta vapaamuotoista raakatekstiä, jossa sisältö on tarkoitettu ihmisten tulkittavaksi, toisaalta määrämuotoista raakatekstiä, jossa sisältö (tai yleensä osa siitä) on tarkoitettu ohjelmien tulkittavaksi. Tekstin, kuten tiedon yleensäkin, määrämuotoisuus helpottaa sen käsittelyä ohjelmallisesti. Esimerkki ankarasta määrämuotoisuudesta on sähköpostilistan jakelua esittävä tiedosto, jossa on yksi sähköpostiosoite jokaisella rivillä eikä mitään muuta. Lievempää määrämuotoisuutta on esimerkiksi tilauksia listaava tiedosto, jossa tilaukset on aina erotettu toisistaan tyhjällä rivillä (eli kahdella peräkkäisellä rivinvaihdolla) mutta tilausmerkinnän sisältö on jätetty tarkemmin määrittelemättä.
Täysin vapaamuotoista raakatekstiäkään ei oikeastaan ole olemassa. Kaikessa, mikä on tarkoitettu ihmisten ymmärrettäväksi, on aina jotain rakennetta, joka helpottaa ymmärtämistä. Ihmiset vain tarvitsevat määrämuotoisuutta paljon vähemmän kuin ohjelmat. Esimerkiksi suomen kielessä sanat on erotettu yleensä välilyönneillä (sekä mahdollisesti välimerkeillä), mikä tarjoaa erinomaisen keinon etsiä ohjelmallisesti kaikki sanamuodot muuten vapaamuotoisesta tekstistä. Samanlaisilla määrämuotoisuuksilla voi etsiä lauseita, virkkeitä, kappaleita, nimiä, osoitteita, puhelinnumeroita, lyhenteitä, ranskalaisilla viivoilla merkittyjä listoja ja vaikka mitä muuta.
Monet Unix-työkalut (esimerkiksi grep, sort, sed) on suunniteltu käsittelemään sillä tavoin määrämuotoista tietoa, että joka rivillä on yksi "tieto". Esimerkiksi, jos meillä on tiedosto, jossa on yksi sana per rivi, sieltä voi grepillä etsiä ne sanat, jotka "näyttävät" kaksoiskausatiiveilta. Jotkut ohjelmat (cut, sort, awk) on lisäksi suunniteltu siten, että jokainen rivi jakautuu kenttiin, jotka on eroteltu jollain merkillä, esimerkiksi välilyönnillä, kaksoispisteellä tai muulla sellaisella. Esimerkki tällaisesta tiedostosta on vaikkapa henkilötietokanta, jossa ensimmäinen kenttä on henkilön nimi, toinen sähköpostiosoite, kolmas puhelinnumero, neljäs osoite ja niin edelleen. Kullakin kentällä voi sitten olla edelleen oma määrämuotonsa.
Vapaamuotoisessa tekstissä on myös jonkin verran informaatiota, jota ei tarvita esimerkiksi sanamuotojen löytämiseen. Tällaista on välimerkkien, whitespacen eli erilaisten välilyöntien ja rivinvaihtojen, sekä isojen ja pienten kirjainten vaihtelu. Tekstin muuntamista muotoon, jossa tällaiset erot on siivottu pois ja jokainen sana on omalla rivillään, sanotaan "normalisoinniksi".
Helpoin normalisointi lienee:
tr -sc "[:alnum:]-_:'" '\n' | tr '[:upper:]' '[:lower:]'
Esimerkkejä raakatekstistä:
Raakatekstiä on siis todella monenlaista ja Unixin tekstityökalut soveltuvat kaiken raakatekstin käsittelyyn ainakin jossain määrin. Myös ei-raakatekstille (esimerkiksi Word-dokumentit) on olemassa työkaluja, jotka heittävät pois kaiken muotoilun ja antavat sisällön raakatekstinä, esimerkiksi catdoc ja antiword.
Erot kahden tiedoston välillä:
diff -u /etc/motd motd
Replace (single character only. For replacing multiple characters use sed):
cat motd | tr a b tr a b < motd cat motd | tr Aa Bb cat motd | tr ie ei cat motd | tr aeiou i
All lower case:
cat motd | tr A-Z a-z cat motd | tr '[:upper:]' '[:lower:]'
Sanat omille riveilleen:
cat motd | tr ' ' '\n'
Squeeze repeats: Words on separate lines and remove empty lines
cat motd | tr -s ' ' '\n'
Delete characters:
cat motd | tr -d ' ' cat motd | tr -d aeiou
Merkkiluokan käänteisluokka (kaikki paitsi vokaalit tuhotaan, jättää siis vain vokaalit):
cat motd | tr -d -c aeiou
Produce list of words in text:
cat motd | tr -s -c '[:alpha:]' '\n' cat motd | tr -sc "[:alnum:]-_:'" '\n' | tr '[:upper:]' '[:lower:]'
grep tyhj | head grep täysi tiedosto; grep täyde tiedosto; grep täyte tiedosto egrep täy(si|[td]e)
Ongelma: entä jos mahdollisia haettavia on ääretön määrä? Ääretöntä joukkoa erilaisia merkkijonoja ei voi luetella... miten se merkitään?
egrep '<[^>]*>' | less egrep '[[:upper:]][[:lower:]]+' egrep '[[:alnum:]-_%+]+(\.[[:alnum:]-_%+]+)*@[[:alnum:]-.]*\.[a-zA-Z]+' egrep '[h]ttp://[^ ]*[[:alnum:]/=]' cat Kalevala | norm | grep '[aeiouyäö]vi$' cat Kalevala | norm | grep 'ikse$' cat Kalevala | norm | grep 'ihe$' egrep '([^dfghjklmnprstv]*[aeiouyäö]+){5}'
Säännöllisistä lausekkeista:
man regex grep - traditional egrep - extended
grep - Global Regular Expression Print toimii riveittäin, näyttää rivit joilla osuma
esim. sana ovi:
grep '\<ovi\>' tiedosto grep 'ovi\>' tiedosto
\< ja \>: täsmäävät sanan alkuun ja loppuun, sanan alku = ei-sanakonstituentti merkki jonka perässä sanakonstituentti merkki ja loppu päin vastoin.
egrep - Extended grep
egrep '\<ov(i|e)' Kalevala egrep '\<ov(ia|ea|en)' Kalevala
egrep -o, --only-matching: Show only the part of a matching line that matches PATTERN. Jos rivillä on useampia osumia, näyttää ne kaikki omilla riveillään. Ei silti toimi rivien yli, ts. etsittävä asia ei voi jatkua useammalla rivillä.
Harjoitus:
cat Kalevala | tr -sc "[:alnum:]-'" '\n' | head cat Kalevala | tr -sc "[:alnum:]-'" '\n' | egrep vi cat Kalevala | tr -sc "[:alnum:]-'" '\n' | egrep 'vi\>' cat Kalevala | tr -sc "[:alnum:]-'" '\n' | egrep 'ihe\>' cat Kalevala | tr -sc "[:alnum:]-'" '\n' | egrep 'ihe$' | wc -l cat Kalevala | tr -sc "[:alnum:]-'" '\n' | egrep 'täy(si|de|te)(en|lle)'
säännöllisten lausekkeiden maagiset merkit
. - mikä tahansa merkki \< - sanan alku \> - sanan loppu \ - escape character ^ - rivin alku $ - rivin loppu (...|...|...) - vaihtoehtoja
Display all words in text:
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/* | tr -sc "[:alnum:]" '\n' | tr '[:upper:]' '[:lower:]' | grep valehtelij
Display all word forms in text:
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/* | tr -sc "[:alnum:]" '\n' | tr '[:upper:]' '[:lower:]' | grep valehtelij | sort -u
Count all occurrences:
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/* | tr -sc "[:alnum:]" '\n' | tr '[:upper:]' '[:lower:]' | grep valehtelij | sort -u | wc -l
Display all word forms with count:
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/* | tr -sc "[:alnum:]" '\n' | tr '[:upper:]' '[:lower:]' | grep valehtelij | sort | uniq -c | sort -rn
Find most common word form
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/* | tr -sc "[:alnum:]" '\n' | tr '[:upper:]' '[:lower:]' | grep valehtelij | sort | uniq -c | sort -rn | head -1 | cut -c9-
Each character on separate line:
sed -r 's/./&\n/g' tiedosto egrep -o . tiedosto
make Bigrams:
tail +2 sanat.txt | paste sanat.txt -
or:
cat test.txt | sed -e x -e G -e '2,$s/^[^\n]*\n//' -e h -e 's/\n/ /g' cat test.txt | sed -e x -e G -e '3,$s/^[^\n]*\n//' -e h -e 's/\n/ /g'
Bigrams of two letters:
sed -r 's/./&\n/g' test.txt | sed -e x -e G -e '2,$s/^[^\n]*\n//' -e h -e 's/\n/ /g'
Kielessä on sanoja, joilla on sananmuotoja, joilla on sitten teksteissä esiintymiä. Sananmuotojen esiintymiä sanotaan saneiksi. Saman sananmuodon eri esiintymät ovat samoja merkkijonoja keskenään. Tekstin normalisointi tuottaa listan tekstin saneista esiintymisjärjestyksessä.
wc -l sort [-f] | uniq -c[i] | sort -rn sort -u[f] | wc -l sort [-f] | uniq -c[i] | sort -rn | head -1 | cut -c9-
Rivien n-grammit saadaan putkessa näin (korvaa <n> sopivalla luvulla):
sed -e x -e G -e '<n>,$s/^[^\n]*\n//' -e h -e 's/\n/ /g'
Perinteisempi tapa on tehdä valmiista tiedostosta bigrammit:
tail +2 tiedosto | paste tiedosto -
vowels, number, small letters, small letters + ":"
(a|e|i|o|u|y|ä|ö|å) = [aeiouyäöå] [0-9] [[:lower:]] [[:lower:]:]
repetition:
* - 0-infinite times + - 1-infinite times
a* - "", "a", "aa", "aaa"
ba*c - "bc", "bac", "baac", "baaac", ...
(ba)*c - "c", "bac", "babac", "bababac", ...
(hi|ho)+ = (hi|ho), (hi|ho)(hi|ho), (hi|ho)(hi|ho)(hi|ho), ...
all words ending in ":":
[[:alpha:]'-]+:
Kaikki sanat a...i
egrep -o '\<a[[:alpha:]]*i\>'
.* - kaikki merkkijonot, myös tyhjä
Käänteismerkkiluokat ^
Vokaalit; kaikki paitsi vokaalit; mikä tahansa merkkijono, johon ei sisälly välilyöntiä:
[aeiouyäö] [^aeiouyäö] [^ ]*
Esim: Kaikki sanat, jossa valehtelij ja seuraava sana:
egrep -o 'valehtelij[^ ]* [^ ]+'
kaikki nettiosoitteet:
zcat /l/kielipankki/sfnet/raw_texts/harrastus/sfnet.harrastus.mensa.gz | egrep -o 'http://[^ ]*'
Only matching
egrep -o '[[:alpha:]]+-[[:alpha:]]+' Kalevala egrep -o '[[:upper:]][[:upper:]]+' /l/kielipankki/misc/Kalevala egrep -o '\<[[:upper:]]+\>' /l/kielipankki/misc/Kalevala
echo 'epäonnistuu (tällaisella) (syötteellä)' | egrep -o '\(.*\)' echo 'toinen (tapa (epäonnistua) on tämä)' | egrep -o '\([^)]*\)' echo 'entä (mitä (seuraa) tästä?) (hmm?)' | egrep -o '\([^()]*\)'
Miten kummassa voisi löytää sulkeet, jotka saavat sisältää toisia sulkeita?
echo 'yksi (koe (vielä) lisää) kiitos' | egrep -o '\(([^()]|\([^()]*\))*\)' echo 'mutta (ei (toimi (näin)) ...)' | egrep -o '\(([^()]|\([^()]*\))*\)'
Sulkeet, jotka sisältävät sulkeita mielivaltaisen syvälle, kuuluvat kontekstivapaiden kielten luokkaan. Koska säännöllisillä lausekkeilla pystyy kuvaamaan vain säännöllisiä kieliä, ei ole olemassa säännöllistä lauseketta, jolla voisi tunnistaa tällaisen mielivaltaisen syvän suljelausekkeen.
Intuitiivisesti ajatellen ongelma on siinä, että mielivaltaisesti upotettujen sulkeiden tunnistamisessa pitäisi pitää muistissa, miten monien sulkeiden sisässä ollaan. Koska säännöllisen lausekkeen tunnistavalla automaatilla on äärellinen määrä tiloja, se ei voi merkitä muistiin mielivaltaisen monten sulkeitten sisässä olemista.
Kontekstivapaisiin kieliin törmää vähän väliä. Niitä ovat kaikki kielet, jossa jokin elementti (esimerkiksi lause) voi sisältää samanlaisia elementtejä (siis sivulauseita). Luonnollisista kielistä väitetään, että ne ovat vähintään kontekstivapaita, mutta itse asiassa ihmiset eivät pysty ymmärtämään mielivaltaista määrää upotuksia. Sen sijaan monet ohjelmointikielet ovat aitoja kontekstivapaita kieliä, samoin merkintäkielet: esimerkiksi HTML-kielessä listat sisältävät listakohtia, jotka voivat taas sisältää toisia listoja.
Säännöllisillä lausekkeilla pystyy emuloimaan kontekstivapaata kieltä tiettyyn syvyyteen asti yllä näytetyllä tavalla.
tr -s '\n' cat -s tr -sc 'aeiouyåäö\n' C tr 'abcdefghijklmnopqrstuvwxyzåäö' 'VCCCVCCCVCCCCCVCCCCCVCCCVCVVV' sed -r 's/^[[:space:]]*$/-PARA-/' | tr -s '[:space:]' '\n' | \ sed -r 's/-PARA-//' sed -r 's/<[^>]*>//g' sed -r 's/<p( [^>]*)?>/\n\n/g' sed -r 's/(.)(.)/\2\1/g'
tr -sc "[:alnum:]-_:'" '\n' | tr '[:upper:]' '[:lower:]' | \ cut -c1 | sort | uniq -c last | cut -c0-9,51-55 | sed -r 's/^([a-z]+) *(.*)/\2 \1 ps uax | grep 'sh$' | cut -c0-9,64- cut -d: -f1,5 /etc/passwd | tr : ' ' cut -d: -f5 /etc/passwd | cut -d, -f2 | tr -d ' ' | sort | uniq -c
replace with sed -r (stream editor): s - substitute g - global
replace (one or more) t, p and k with BÖÖ
zcat /l/kielipankki/sfnet/raw_texts/harrastus/sfnet.harrastus.mensa.gz | sed -r 's/[tpk]+/BÖÖ/g' | less
replace all aa with a:
zcat /l/kielipankki/sfnet/raw_texts/harrastus/sfnet.harrastus.mensa.gz | sed -r 's/aa/a/g' | less
strip tags:
cat /l/kielipankki/parole-fi/latest/a-lic/kirjat-a/k-Turkka.sgml | sed -r 's/<[^>]+>//g'
Tekstitiedostot (ja muutkin tiedostot) ovat vain sarja tavuja. Merkistöt ovat puolestaan tavujen sisältämien numeroiden tulkintasopimuksia. Lisää aiheesta esim. Wikipediassa: http://fi.wikipedia.org/wiki/Merkist%C3%B6
Heikon muistiinpanot luennolta:
Tekstitiedostot koostuvat numeroista 0-255, esim 65=A. Merkistö on tulkinta numeroista merkiksi. Nykyään käytännössä kaikki merkistöt pohjautuvat ASCII-merkistöön (harvat EBCDIC-merkistöön). Latin-merkistöt laajennettu ASCII. Käytännössä yleisin meillä nyt on Latin-1 (ISO-8859-1) ja Latin-9 (ISO-8859-15). Unicode yleisin UTF-8 unicode määrittää jokaiselle merkille luvun. koodaus määrittää, miten nämä luvut esitetään tiedostossa. Näytä merkin tiedot echo ä | od -x ä Latin-1: 228 UTF-8: 195 164 (Latin-1 tulkittuna jotain Ã) esim. echo ä | iconv -f latin1 -t utf8 ASCII-teksti toimii sellaisenaan Latin-1 ja UTF-8 tekstinä. merkistömuunnoksia tehdään komennolla iconv echo pää | iconv -f latin1 -t utf8 virheelliset merkit poistetaan: 'iconv -c' echo pÃÃ pää | iconv -c -f utf8 -t latin1 echo pää | iconv -c -f latin1 -t ascii näytä tunnetut merkistöt: iconv -l Yhteenveto: ASCII: vain englannin merkkejä, ei ääkkösiä yms., 0-127 Latin-*: ASCII 0-127 + paikallisia lisäyksiä 128-255 UTF-8: Kaikki maailman merkit. Laajentaa ASCII-merkistöä. Ei-ASCII-merkit esitetään lukusarjoina 128-255.
Pikalinkit: