F I MiSTer Staattisen.HTML:n.paluu Musiikkiohjelmat eSM.2019 92 sivua ISOIN SKROLLI Näin sinustakin tulee kasettilameri LASKENTA GPGPU-laskenta C++/GLSL:lla Suomen kielen sanavektorit Rekursio kompleksi-integraalilla Tietokoneet autoissa Robottiauto. Neuroverkko RC-autoilee Saabin salattu peli SARJA JATKUU Tee-se-itse-tietokone 1960-luvulla. 2 1 9 .2 TIETOKONEKULTTUURIN ERIKOISLEHTI 2019.2 S K R O L L I . Nyt
Markku oli tärkeä henkilö lehtiprojektia käynnistettäessä ja kirjoitti myös kolumnin vuoden 2013 kakkosnumeroomme. Itse en 11-vuotiaana olisi takuulla tullut selanneeksi 6502:n konekielestä kertovaa artikkelia, ellei se olisi sattunut olemaan samojen kansien välissä kuin tuoreimmat Eliten pelivinkit. Viimeisenä päivänä ennen painoa saimme tiedon, että Mikrobitin ex-päätoimittaja Markku Alanen on menehtynyt. Ja niin on hyvä. Saimme paperit juuri ennen lehden painopäivää, joten nyt se on virallista: Skrollin sijoitus kohdistuu Iin Kuivaniemen Pekkassuohon. Varsinkin ennen internetiä jäimme usein sivuun kaikkein uusimmista virtauksista tai saimme ne vasta aikojen kuluttua jotenkin laiskasti kotoistettuina versioina. Joukkorahoitettu 2016.1E ja ihan itse kustannettu 2017.1E eivät tehneet maailmalla suuria aaltoja, mutta avasivat silmiä puolin ja toisin: pieni ulkomaalaisten joukko näki poikkileikkauksen pienen maan omintakeisesta ja rikkaasta tietokonekulttuurin kenSuloisen suomalainen konsepti tästä; me taas ymmärsimme, millainen outolintu Skrollin kaltainen "kaikille avoin" bitinnypläyslukemisto oikeastaan onkaan. Rama/WmC, Laura Loukola, Janne Sirén Tämän numeron avustajat Lauri Anttila, Miika Auvinen, Mika Haulo, Mikael Heikkanen, Ada-Maaria Hyvärinen, Jakke, Janina Joutsen, Emma Kantanen, Tuula Keränen, Toni Kortelahti, Ninnu Koskenalho, Valhe Kouneli, Nanne Kukkura, Antti Laaksonen, Janne Laukkanen, Jarkko Lehti, Sakari Leppä, Sakari Lönn, Mikko Möykky, Tuukka Norri, Antti Nuortimo, JJ Nääs, Atte Penttinen, Mikko Pohja, Tomi Pyy, Ville Ranki, Mikko Rasa, Felipe Santana, Joseph Watson Julkaisija Skrolli ry Painopaikka Hämeen Kirjapaino oy, Tampere, ISSN 2323-8992 (painettu) ISSN 2323-900X (verkkojulkaisu) SKROLLI – Tietokonekulttuurin erikoislehti HÄMEEN KIRJAPAINO OY 4041 0209 Painotuote PÄÄKIRJOITUS P ienessä maassa ja etenkin pienessä kielialueessa on omat haittansa. Me olemme harkiten maustettu sillisalaatti kaikenlaista luettavaa tietotekniikasta ja sen ympäriltä. Janne Sirén kirjoittaa toisaalla lehdessä seikkailuistamme Skrollin kansainvälisen version parissa. Viime pääkirjoituksessa kerroin, että Skrolli on ostanut hehtaarin verran ennallistettavaa suota kompensoidakseen hiilidioksidipäästöjään. Skrollin visiona on ollut alusta asti altistaa lukijoitamme kaikenlaiselle. 2019.2 2. Muutamalle tuhannelle lukijalle kirjoittaminen vaatii vaikkapa lehden kustantajalta ihan omanlaistaan aatteen paloa. Periaatteena on, että kunhan juttu on mielenkiintoisesti kirjoitettu ja sivuaa tukevasti tietotekniikkaa, se sopii meidän lehteemme. P.P.S. Vielä paljon suurempana tuntuu hänen vaikutuksensa siinä, mistä lähtökohdasta Skrollia on sen historian ajan toimitettu. P.S. Mikko Heinonen vastaava päätoimittaja Yhteydenotot toimitus@skrolli.fi Ircnet: #skrolli skrolli.fi Vastaava päätoimittaja Mikko Heinonen Päätoimittaja Tapio Berschewsky Toimituspäällikkö Janne Sirén Taitto Manu Pärssinen Susanna Viljanmaa Digipäällikkö, mediamyynti Toni Kuokkanen Kuvatoimittajat Laura Pesola Mitol Meerna Talous Anssi Kolehmainen Yhteisömestari Mika Hyvönen Toimitus Jarno Niklas Alanko, Jari Jaanto, Jukka O. Nykyisin taas mediakentän haasteet ovat meillä moninkertaisia verrattuna maailmankieliin, joilla tavoittaa jopa satoja miljoonia ihmisiä kerralla. Jo 1980-luvulla tietotekniikkalehdet toivat yhteen mikroraudasta kiinnostuneet rakentelijat, aloittelevat ja edistyneemmät ohjelmoijat sekä ne, jotka odottivat uusimmalta numerolta kaikkein eniten tuoreita peliuutisia ja -arvosteluja. Otamme osaa omaisten suruun ja toivotamme hyvää matkaa. Harrastajilta harrastajille ja muuten vain kiinnostuneille. Siksi emme jää pyörimään pelkkään kuusnepaan ja Amigaan, aivan kuten emme aio siirtyä esittelemään pelkästään uusinta pilvitekniikkaakaan. Osittain kyseessä oli pakon sanelema ratkaisu, sillä vain yhtä näistä aiheista käsittelevä julkaisu olisi lähes väistämättä jäänyt tuolloin pienkustanteeksi. Englanninkielisessä maailmassa kun lehdet on varsin usein tehty spesifille kohderyhmälleen. Kauppinen, Ronja Koistinen Kansikuva Laura Loukola Sisuskuvat . Lopputulos oli kuitenkin myönteinen, sillä näin tietystä aihealueesta kiinnostunut tuli kuin vaivihkaa lukeneeksi myös muuta. Mainiona kääntöpuolena on kuitenkin se, että harrastuskentillä erilaiset viiteryhmät ovat tottuneet toimimaan yhdessä
24 Musiikkiohjelmat Välineet ja tekniikat tietokonemusiikin tekoon. 86 JOKstoriaa IX Grendel-hifiä ja salanimiä. 43 Staattisen HTML:n paluu --UNDER CONSTRUCTION --46 Kolumni: Antti Laaksonen Indeksit tekevät ohjelmoinnin oppimisesta turhan vaikeaa. 56 Digiarkeologian alkeet Näin sinustakin tulee kasettilameri. välilyönti Väittely koodin sisentämisestä päättyy tähän. 17 K.O.: Tabulaattori vs. 85 Postipalsta Neuroverkko kirjoitti kirjeen. Valitettavasti. 52 Omenaläppärin uusiokäyttö Vanhalle PowerPC-Macille uusi elämä NetBSD-työasemana. 72 MiSTer Kasa FPGA-klassikoita kerralla. 20 Kolumni: Janne Sirén Purkkiperspektiivi: sosiaalinen media on epäsosiaalinen. 12 Suomen kielen sanavektorit Luonnollisen kielen käsittelyn salainen ase. Piirros esiintyy sen pakkauksessa ja materiaaleissa.. 90 Sarjakuva Turrikaanien yössä seikkailupelaajan maailmantuskaa. 69 Talentec Bartop Kit Peliautomaattien inspiroima viihdekeskus kahvihuoneeseen. 78 Minikonsolit – Tiesitkö, että... 8 Visuaalinen debuggaus "Print-käsky" shader-bugien metsästykseen. Alice on ranskalainen versio Tandy TRS-80 MC-10:stä. 64 eSM 2019 Kurkistus kotimaisen e-urheilun kulisseihin. 40 Pientä PDF-muokkausta Kuinka vaikeaa se voi olla. 70 Olisinpa maailmanvaltias JOKin pelipalstalla kolttostellaan rajapyykeillä. Miten kävi. 18 Kesän teknolelut Skrolli haki Lidlistä täydennystä kesäreissuun. 51 Kolumni: Jarno Niklas Alanko Bitcoin tositilanteessa. 82 International Edition Skrolli kävi ulkomailla. 2 Pääkirjoitus 4 C++/GLSL: GPGPU-laskenta Pistetään grafiikkasuoritin (muihin) hommiin. Skrollin kansi on tribuutti Jean "Moebius" Giraudin tekemälle Matra Alice -tietokoneen (1983) kansipiirrokselle. Tärpit retropelaamisen trivial pursuitiin. 2019.2 61 Mies, joka pelasti Harvesterin Seikkailupelin ympärille syntyi hylkiöiden kultti. 22 Lyhyet Pikkujutuissa virransäästöja kierrätysneuvoja. 80 Skrolli 6,4 vuotta sitten Skrollin uusin ja samalla vanhin palsta aloittaa – alusta. 81 Skrolli-kalenteri Tiedä, mitä tietokoneen tasavallassa tapahtuu. 66 Saabin salattu peli Onko Saabissa Pong. 28 Sirénin sisäpiiri Teknologiapalstalla nyt: robottiautot. 23 Neuroverkko pörisi Neurosyntetisaattori cellF miksaili aivan omiaan. 47 Ensimikro ennen mikropiirejä Sarja jatkuu: näin rakennettiin tietokone 1960-luvulla. 60 Dataraita vinyylilevyllä Skrolli kokeilee: Miksei "LP-ROM" lyönyt läpi. 36 Neuroverkko RC-autoilee Robottiautoja on monen kokoisia. 88 Skrolleri Skrollin demopalstalla maailman ensimmäinen kompo. 10 Rekursio kompleksi-integraalilla Uusi ratkaisu rekursioon. 76 Ei näin! Amiga-yhteisö osaa kaiken muita paremmin
Käsittelen asiaa tässä artikkelissa pääasiassa irrallaan muusta ohjelmakoodista, mutta taustatietona kerrottakoon, että päätin toteuttaa GPGPU-shaderit osaksi pelimoottorini piirtoliukuhihnaa (rendering pipeline) olemassa olevaa koodia hyödyntääkseni. Antti kertoo nyt meille, mihin näytönohjaimen jekuttaminen johti. Tällaisesta laskennasta käytetään nimitystä GPGPU, eli general-purpose computing on graphics processing units. 2019.2 4. Pyrin myös välttämään kaikkia OpenGL-laajennuksia. Termi voi viitata tavallisella GPU:lla tehtävään laskentaan tai GPU-piiriä käyttävään laitteeseen, jonka pääasiallinen tarkoitus on grafiikan sijaan laskenta. Tämän tekniikan etuna on, että koodi toimisi todella laajalla joukolla koneita – myös vähän vanhemmissa kannettavissa. Mahdollisuudet laajenivat, kun näytönohjaimiin tuli tuki ohjelmoitaville shadereille ja liukulukutekstuureille 2000-luvun alussa. Päättelin tästä, että OpenGL 3.0:lla on mahdollista suorittaa GPGPU-laskentaa ilman OpenGL-laajennuksia. Osa pelimoottorini olioista tulisi olemaan GPGPU-shadereita, joilla on lapsiolioina laskentatehtäviä. Skrolli 2019.1) ja kryptovaluuttojen louhimisessa käytetään usein grafiikkasuorittimia. Löysin vuodelta 2005 peräisin olevan GPGPU-tutoriaalin (linkit verkkojatkoilla), joka on jo hieman vanhentunut. Varhaiset grafiikkaan liittymättömät algoritmit GPU:lla toteutettiin grafiikkarajapinnoilla, jolloin näytönohjain tavallaan huijattiin ratkomaan ongelmia, joita sitä ei ollut suunniteltu ratkomaan. Vaatimuksena mainitut Nvidia GeForce FX ja ATI Radeon 9500 tukevat korkeintaan OpenGL 2.1:tä. Esimerkiksi neuroverkkolaskennassa (ks. Lähdimme seikkailulle grafiikkasuorittimen väärinkäytön maailmaan. Näytönohjaimien kehittyessä huomattiin kuitenkin pian, että niillä voi laskea muutakin kuin grafiikkaa. OpenGL-version 4.3 myötä tulleet niin kutsutut compuTeksti: Antti Nuortimo, Mikko Rasa, Jarno Niklas Alanko, Janne Sirén Kuvat: Mitol Meerna, Antti Nuortimo te-shaderit eivät siis olleet käytettävissäni, joten toteuttaisin GPGPU-laskentaa vanhanaikaisesti ohjelmoitavilla sharedeilla käyttämällä shaderien ohjelmointikieltä OpenGL Shading Language (GLSL) ja datan tallentamiseen tekstuureita. Tässä artikkelissa viitataan OpenGLgrafiikkarajapintaan – aloittelevan OpenGL-ohjelmoijan kannattaa lukea ennen tätä artikkeli Näin syntyi Assembly-demo (Skrolli 2018.3) sekä OpenGL-kurssi Skrollin numeroista 2013.2–2014.2 (pdf-lehdet: skrolli.fi/ numerot). Skrolli 2018.4) ja ryhtyi sen pohjalta tutustumaan GPGPU-laskentaan – sillä 2000-luvun alun tavalla. Näissä laskentakorteissa ei välttämättä ole edes ulkoisia liitäntöjä näytölle. Pelimoottoriprojektini ruutukaappaustoiminnon peruina tiesin, että OpenGL 3.0:lla on mahdollista piirtää kuva näytön ulkopuoliseen ruutupuskuriin (offscreen framebuffer) ja kopioida puskurin sisältö keskusmuistiin käyttämättä yhtäkään OpenGL-laajennusta. esim. KOODI N ykyaikainen 3D-kiihdytetty näytönohjain sisältää grafiikkasuorittimen (GPU), joka on tietokonegrafiikan laskutoimituksiin suunniteltu suoritin, jossa on runsaasti rinnakkaisia ytimiä. Tällaisia tietokonegrafiikan operaatioita ovat esimerkiksi valaistuksen laskeminen ja kolmioiden kulmapisteiden koordinaattimuunnokset. OpenGL on joka tapauksessa tilakone, joten samaan aikaan näytönohjaimella ei ole mahdollista suorittaa kuin OpenGL, GLSL ja C++ GPGPUlaskenta GPGPU ei ole grafiikkasuorittimen lyhenteen kirjoitusvirhe, vaan viittaus yleiskäyttöiseen laskentaan grafiikkasuorittimella. Myöhemmin myös näytönohjainvalmistajat tajusivat tässä piilevän potentiaalin ja lanseerasivat erilliset rajapinnat pelkästään laskentatarkoitukseen. Tähän pystyy vanhakin Reunaehdoikseni asetin seitsemän vuotta vanhan ThinkPad W520 -kannettavan ja sen sisäisen Intel HD Graphics 3000 -näytönohjaimen tukeman OpenGL 3.0:n. Skrollin avustaja Antti Nuortimo on kehittänyt Ylikuutio-nimistä pelimoottoria (ks. Näiden ansiosta näytönohjaimen grafiikkaliukuhihna oli nyt mahdollista valjastaa tekemään mielivaltaista laskentaa käyttämällä tekstuureja kaksisuuntaisena tiedonsiirtoväylänä CPU:n ja GPU:n välillä
5 Tasausta voi muuttaa glPixelStorei funktion arvoilla GL_UNPACK_ALIGNMENT (datan siirto CPU:lta GPU:lle) ja GL_PACK_ALIGNMENT (vastakkainen suunta). Jos formaatti on GL_RGB, sisäinen formaatti myös GL_RGB ja tyyppi GL_FLOAT, tuloksena voi silti olla tekstuuri, jonka sisäinen formaatti on GL_RGB8. Lähdetekstuuri sisältää käsiteltävän datan – mitä ikinä olemmekaan laskemassa GPU:lla – esimerkiksi etäisyysmatriisin FloydinWarshallin algoritmia varten. Jos tätä ei ole otettu huomioon datapuskuria varatessa, seurauksena on ylivuoto ja mahdollisesti ohjelman kaatuminen. Luotaessa tekstuuria on määriteltävä, mitä formaattia datan tallentamiseen käytetään. Tekstuurit ovat siis taulukoita ja tekselit (tekstuurin osat) ovat tämän taulukon alkioita. 3 Tässä on oltava tarkkana, koska jos formaatit eivät täsmää, tuloksena voi olla korruptoitunutta dataa tai kutsu voi jopa palauttaa virheen. Syöte tallennetaan lähdetekstuuriin. Tyyppi taas tarkoittaa kunkin lukuarvon tietotyyppiä sovelluksen puolella. Algoritmin on siis oltava luonteeltaan sellainen, että jokainen tulosarvo on laskettavissa erillään 2 Kustakin perusformaatista on myös muunnelmia, joissa tarkkuus on erikseen mainittu. Koska tavoitteenamme on huijata GPU:ta laskemaan laskumme, naamioimme laskutoimituksen tekstuurien käsittelyksi. Datan tallentamiseen ja käsittelyyn käytetään näytönohjaimen puolella ruutupuskuriobjektiin liitettyjä tekstuureita. Grafiikkakäytössä nämä arvot ovat punainen (R), vihreä (G) ja sininen (B) värikanava sekä läpinäkyvyys (A). Aiemmin mainitut rajoitukset formaattien yhteensopivuudesta pätevät myös näihin funktioihin. Kullakin laskentatehtävällä on kaksi OpenGL-tekstuuria: lähdetekstuuri ja kohdetekstuuri. Valmistellaan tekstuurit Tavoitteena on, että laskentaolioluokka sallisi mahdollisimman joustavasti erilaisten laskentatehtävien lisäämisen. 2 Lisäksi on määriteltävä ohjelman muistissa olevan lähdedatan tallennusmuoto. dereille. Oletusarvoisesti rivin pituus tavuina tasataan neljällä jaolliseksi lisäämällä loppuun tarvittava määrä tyhjiä tavuja. Toinen vaihtoehto on glReadPixels , joka lukee dataa ruutupuskurista ja pystyy lukemaan myös osittaisen alueen. Laskentaa suoritettaessa shaderi voi lukea arvoja mielivaltaisista tekseleistä, mutta kirjoitus onnistuu ainoastaan kulloinkin käsiteltävään ruutupuskuriobjektiin liitetyn tekstuurin tekseliin. Tekstuurit ohjelmoin ladattavaksi 24-bittisistä BMP-tiedostoista, 32-bittisistä DDS-tiedostoista sekä CSVtiedostoista. 4 Kaksinkertaisen tarkkuuden (64 bittiä, double) liukuluvut ovat käytettävissä vasta OpenGL 4.0:ssa, ja silloinkin laskenta niillä on huomattavasti hitaampaa, ellei käytössä ole erityisesti laskentaan tarkoitettu näytönohjain. Kuitenkin kokonaislukuformaateille on tässä yhteydessä olemassa erilliset _INTEGER-päätteellä varustetut perusformaatit erotukseksi vanhempaa perua olevista normalisoiduista reaaliluvuista. On siis syytä tarkistaa näiden funktioiden palauttamat arvot ennen kuin niitä käyttää muissa funktiokutsuissa. Tekstuureihin voi tallentaa myös syvyystai sapluuna-arvoja (depth, stencil), mutta näistä ei juuri ole iloa laskennassa. 1 OpenGL:n rajoituksia Koska OpenGL on grafiikkarajapinta, sen laskentakäyttöön liittyy rajoituksia. 5 Lasketaan tekstuureita Piirtokohteena on kohdetekstuuri, tässä tapauksessa kaksiulotteinen olio, kahdesta suorakulmaisesta kolmiosta koostuva neliö, jossa käytän OpenGL:n GL_TRIANGLE_STRIP-primitiiviä. 3 Esimerkiksi GL_RG, GL_UNSIGNED_ SHORT tarkoittaa samaa kuin sisäisenä formaattina käytetty GL_RG16. Perusformaatit ovat järjestyksessä yhdestä neljään komponenttiin GL_RED, GL_RG, GL_RGB ja GL_RGBA. Laskentakäytössä perinteisellä tulkinnalla ei kuitenkaan ole merkitystä ja kanavia voi käsitellä mielivaltaisina arvoina. 5. Jotta laskennasta olisi mitään iloa, tulokset on vielä saatava takaisin CPU:lle. Tähän käytetään nimensä mukaisesti OpenGL:n glGetTexImage -funktiota. Tasausarvon pienentäminen saattaa hidastaa toimintaa. OpenGL 3.0 tukee shadereissa ainoastaan yksinkertaisen tarkkuuden (32 bittiä, float) ja puolikkaan tarkkuuden (16 bittiä, half-float) liukulukuja. Formaatti tarkoittaa pääasiassa sitä, montako lukuarvoa kuhunkin alkioon tallennetaan, ja mitä väriarvoja ne määrittävät. Erikoistilanteet eli laskutoimitukset Infja NaN-arvoilla (äärettömyys ja epäluku) on kuitenkin jätetty määrittelemättömiksi, joten niissä voi olla eroja implementaatioiden välillä. Näitä formaatteja sellaisenaan käytettäessä OpenGL-implementaatio voi itse valita tallennustarkkuuden. Jokaisessa taulukon elementissä eli tekselissä voi olla yhdestä neljään lukuarvoa. Olennaista on, minkälaista dataa taulukkona toimivaan tekstuuriin on mahdollista tallentaa, eli tekstuurin formaatti ja tyyppi. Jos sama tekstuuri on samaan aikaan sekä syötteenä että piirron kohteena, lukuoperaatiot tuottavat määrittelemättömiä tuloksia. Tekstuurit ovat yksi-, kaksitai kolmiulotteisia taulukoita. Vinkki: Jos jokin muuttuja ei vaikuta shaderin lopputulokseen, OpenGL-implementaatio voi optimoida sen pois.Tällöin glGetAttribLocation tai glGetUniformLocation palauttaa -1, eli muuttujaa ei ole olemassa. OpenGL 3.0:ssa ainoa vapaasti indeksoitavissa oleva tietorakenne on tekstuurit. Esimerkiksi GL_R16F on yksikanavainen formaatti 16-bittisillä liukulukuarvoilla ja GL_RGB32UI sisältää kolme kanavaa 32-bittisiä etumerkittömiä kokonaislukuja. Esimerkiksi jos tyypiksi asetetaan GL_UNSIGNED_BYTE, kukin lukuarvo on 8-bittinen etumerkitön kokonaisluku. Datan siirrossa kumpaan tahansa suuntaan on syytä huomioida myös rivien pakkaus. 4 OpenGL-standardi edellyttää, että liukuluvut on toteutettu normaalien lukuarvojen osalta IEEE-754-standardin mukaisesti. yhtä shaderia kerrallaan, toki näytönohjaimen lukuisissa ytimissä (core) samanaikaisesti. Esimerkiksi muoto GL_RGB tarkoittaa kolmea lukuarvoa: punainen, vihreä ja sininen. muista. Ensinnäkin laskentaan käytettävä data on jotenkin saatava välitettyä sha1 Tyyppi voi toimia myös vihjeenä OpenGL:lle halutusta sisäisestä formaatista, jos parametrina annettu sisäinen formaatti on kooton ( unsized ), mutta toteutuksen ei ole pakko noudattaa vihjettä. Siihen käytetään historiallisista syistä perusformaatin ja datatyypin yhdistelmää
Kukin fragmenttishaderin ilmentymä voi kuitenkin asettaa vain oman tekselinsä väriarvot kirjoittamalla ne asettamaansa tulosmuuttujaan (outtai gl_FragColor -muuttujaan, jota tosin uudemmissa OpenGL-versioissa pidetään jo vanhentuneena). Pelimoottorissani kaikki saman shaderolion lapsina olevat laskentatehtävät käyttävät laskentaan samaa GLSL-verteksishaderia ja samaa GLSL-fragmenttishaderia, mutta kullakin laskentatehtävällä on oma datansa, oma OpenGL-ruutupuskuri-olionsa ja OpenGL-tekstuurinsa dataansa varten. Mikäli tulosmuuttujaan tallentaa värikanavia, joita tekstuuri ei formaattinsa perusteella sisällä, OpenGL-implementaatio jättää kyseiset ylimääräiset värikanavat huomiotta. Kun lähdetekstuuri ja kohdetekstuuri ovat saman kokoisia, näytteistimellä on mahdollista kuvata kukin lähdetekstuurin tekseli täsmälleen yhteen kohdetekstuurin tekseliin, eli bijektio lähdetekstuurista kohdetekstuuriin on mahdollinen. Ensimmäinen yritys Ensimmäisenä laskentashaderina toteutin identiteettioperaattorin, joka nimensä mukaisesti ainoastaan kopioi lähdetekstuurin sisällön kohdetekstuuriin. Iteroidaan tekstuureita Toisinaan laskennassa joudutaan iteroimaan. Liukumien (gradient) laskenta perustuu konvoluutioon, eli kumpikin liukuma on ympäröivien kahdeksan tekselin painotettu summa, eri tavoin painotettuna. Selvyyden vuoksi jätän tästä eteenpäin pelimoottoriani koskevat yksityiskohdat pois ja keskityn itse asiaan. Identiteettioperaattori GLSL:llä on hyvin yksinkertainen, mutta sillä voi testata, että kaikki toimii. Usein onkin luultavasti käytännöllisintä käyttää kiinteää iterointikertojen lukumäärää, koska ainoa keino välittää informaatiota ulos näytönohjaimelta shader-koodista prosessorille on tallentaa tulos kohdetekstuuriin. Koordinaattien on oltava NDCavaruudessa (normalized devices coordinates), eli vasen alanurkka on (-1, -1) ja oikea ylänurkka on (1, 1). Toki kukin fragmenttishaderin ilmentymä voi shader-koodissa lukea myös esimerkiksi viereisten tekselien arvoja. Iterointiprosessia on myös mahdollista muokata määrittelemällä kunkin iteraation alussa suo9999 9999 78 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 78 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 109 9999 9999 9999 9999 9999 85 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 209 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 75 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 62 9999 9999 9999 209 9999 9999 9999 9999 9999 9999 109 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 52 52 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 62 75 9999 9999 9999 9999 9999 9999 9999 9999 9999 9999 85 Haapamäki Iisalmi Joensuu Jyväskylä Kemi Kemijärvi Kerava Kokemäki Kolari Kontiomäki Kotkan satama Kuopio Lahti Kouvola H aa pa m äk i Iis al m i Jo en su u Jy vä sk yl ä Ke m i Ke m ijä rv i Ke ra va Ko ke m äk i Ko la ri Ko nt io m äk i Ko tk an sa ta m a K uo pi o La ht i Ko uv ol a … … … … … … … … … … … … … … … … … … … … … … … … … … … … … … Yläkulma 32 x 32 -etäisyysmatriisista, joka määrittää rautatieyhteyksien verkon listaukselle 2. Mitään projektioita ei tässä tarvita, koska ainoa fragmenttishaderille välitettävä data on tekstuurikoordinaatit, joita kutsutaan yleisesti U:ksi ja V:ksi erotuksena avaruuskoordinaatteina käytetyistä X:stä ja Y:stä. Mikäli lopetusehtoa ei ole annettu, määräksi annetaan iterointikertojen tarkka lukumäärä. Sobel-operaattorissa kullekin tekselille lasketaan vaakaja pystysuuntaiset liukumat. Toiminta edellyttää, että lähdeja kohdetekstuuri ovat keskenään yhtä leveitä ja yhtä korkeita, ja että niissä on sama sisäinen formaatti ja sama tyyppi: Seuraavaksi lisäsin Sobel-operaattorin, jota käytetään kuvankäsittelyssä ääriviivojen tunnistamiseen. 9999 on puuttuva yhteys. Lopetusehdon lisäksi määritellään iterointikertojen maksimimäärä. varying vec2 UV; uniform sampler2D texture_sampler; out vec4 color; void main() { color = texture(texture_sampler, UV); } varying vec2 UV; uniform sampler2D texture_sampler; uniform int screen_width; out vec4 color; void main() { vec2 UV_east = UV + vec2(1.0f / screen_width, 0.0f); color = texture(texture_sampler, UV_east); } 2019.2 6. Indeksipuskuria ei käytetä, sillä tässä on kulmapisteitä (verteksejä) vain 4 kappaletta. Käytännössä piirrämme framebuffer-objektiin liitettyyn kohdetekstuuriin. Lisäksi tarvitaan lähdetekstuuri ja sitä varten nämä tekstuurikoordinaatit U ja V. Koska ei ole tarvetta projisoida tätä neliötä 3D-avaruuteen – mehän vain leikimme tekevämme grafiikkaa – Xja Y-koordinaatit riittävät kulmapisteiden koordinaateiksi. Kaaret naapurisolmuihin on punaisella, etäisyydet kilometreinä. Piirtämistä varten asetetaan lähdetekstuuri näytteistimen (texture sampler) käyttämäksi tekstuuriksi ja kohdetekstuuri asetetaan rendauksen kohteeksi. Kun iterointi lopetetaan, tulos on valmiina kohdetekstuurissa. Kun laskentaa iteroidaan, lähdeja kohdetekstuuri vaihdetaan päittäin, jotta nykyisen iteraation kohdetekstuuri tulee asetetuksi seuraavaa iteraatiota varten lähdetekstuuriksi ja nykyisen iteraation lähdetekstuuri seuraavan iteraation kohdetekstuuriksi. Niinpä jos tekstuurin sisäinen formaatti on esimerkiksi GL_R8, tulosmuuttujan värikanavista vihreää, sinistä ja läpinäkyvyyttä ei tallenneta tulostekstuuriin. Lopetusehto voidaan antaa takaisinkutsufunktiona (callback function). Tämän jälkeen liukuman magnitudi saadaan selville laskemalla gradienttien neliöiden summan neliöjuuri – katso listaus 1. Tämä yksinkertainen esimerkki iteroinnista siirtää tekstuuria: Itse käytän tässä pelimoottoriini toteuttamaani takaisinkutsutoiminnallisuuden sisältävää luokkaa CallbackEngine
toista k luvusta 1 lukuun |V| toista i luvusta 1 lukuun |V| toista j luvusta 1 lukuun |V| jos (etäisyys[i][j] > etäisyys[i][k] + etäisyys[k][j]) etäisyys[i][j] = etäisyys[i][k] + etäisyys[k][j] #version 130 varying vec2 UV; uniform sampler2D texture_sampler; uniform int screen_width; uniform int screen_height; out vec4 color; void main() { vec2 UV_northwest = UV + vec2(-1.0f / screen_width, 1.0f / screen_height); vec3 northwest = texture(texture_sampler, UV_northwest).rgb; vec2 UV_north = UV + vec2(0.0f, 1.0f / screen_height); vec3 north = texture(texture_sampler, UV_north).rgb; vec2 UV_northeast = UV + vec2(1.0f / screen_width, 1.0f / screen_height); vec3 northeast = texture(texture_sampler, UV_northeast).rgb; vec2 UV_west = UV + vec2(-1.0f / screen_width, 0.0f); vec3 west = texture(texture_sampler, UV_west).rgb; vec2 UV_east = UV + vec2(1.0f / screen_width, 0.0f); vec3 east = texture(texture_sampler, UV_east).rgb; vec2 UV_southwest = UV + vec2(-1.0f / screen_width, -1.0f / screen_height); vec3 southwest = texture(texture_sampler, UV_southwest).rgb; vec2 UV_south = UV + vec2(0.0f, -1.0f / screen_height); vec3 south = texture(texture_sampler, UV_south).rgb; vec2 UV_southeast = UV + vec2(1.0f / screen_width, -1.0f / screen_height); vec3 southeast = texture(texture_sampler, UV_southeast).rgb; float x_red_value = -1.0f * northwest.r 2.0f * west.r 1.0f * southwest.r + 1.0f * northeast.r + 2.0f * east.r + 1.0f * southeast.r; float x_green_value = -1.0f * northwest.g 2.0f * west.g 1.0f * southwest.g + 1.0f * northeast.g + 2.0f * east.g + 1.0f * southeast.g; float x_blue_value = -1.0f * northwest.b 2.0f * west.b 1.0f * southwest.b + 1.0f * northeast.b + 2.0f * east.b + 1.0f * southeast.b; float x_gray_value = (x_red_value + x_green_value + x_blue_value) / 3.0f; float y_red_value = -1.0f * northwest.r 2.0f * north.r 1.0f * northwest.r + 1.0f * southeast.r + 2.0f * south.r + 1.0f * southeast.r; float y_green_value = -1.0f * northwest.g 2.0f * north.g 1.0f * northwest.g + 1.0f * southeast.g + 2.0f * south.g + 1.0f * southeast.g; float y_blue_value = -1.0f * northwest.b 2.0f * north.b 1.0f * northwest.b + 1.0f * southeast.b + 2.0f * south.b + 1.0f * southeast.b; float y_gray_value = (y_red_value + y_green_value + y_blue_value) / 3.0f; float gradient_magnitude = sqrt(x_gray_value * x_gray_value + y_gray_value * y_gray_value); color = vec4(gradient_magnitude, gradient_magnitude, gradient_magnitude, 0.0f); } #version 130 varying vec2 UV; uniform sampler2D texture_sampler; uniform int screen_width; uniform int screen_height; uniform int iteration_i; out float distance; void main() { float i = UV.x; float j = UV.y; float k = float(iteration_i) / float(screen_width); vec2 UV_i_j = vec2(i, j); vec2 UV_i_k = vec2(i, k); vec2 UV_k_j = vec2(k, j); float dist_i_j = texture(texture_sampler, UV_i_j).r; float dist_i_k = texture(texture_sampler, UV_i_k).r; float dist_k_j = texture(texture_sampler, UV_k_j).r; if (dist_i_j > dist_i_k + dist_k_j) { dist_i_j = dist_i_k + dist_k_j; } distance = dist_i_j; } 7. Tämä on totta. Listaus 1. Listaus 2. Esimerkiksi valitsin Floydin-Warshallin algoritmin, joka on tehokas menetelmä lyhimpien polkujen löytämiseksi jonkin verkon (vaikkapa kartan) kaikkien solmujen (vaikkapa kaupunkien) välille. Algoritmin aikavaativuus on O(|V| 3 ), missä |V| on verkon solmujen lukumäärä. FloydinWarshallin algoritmi on varsin yksinkertainen ja koostuu kolmesta sisäkkäisestä silmukasta – pseudokoodina: olkoon etäisyys |V| × |V| etäisyysmatriisi, jonka kaikkien alkioiden arvot on alustettu äärettömiksi // Alustetaan etäisyysmatriisi. Varmistettuamme ja opastettuamme GPGPU:n kannalta olennaiset OpenGL:n ohjelmointitekniikat visuaalisten efektien avulla siirrymmekin nyt yleispätevään laskentaan. toista jokaiselle kaarelle (u, v) etäisyys[u, v] = paino(u, v) // kaaren (u, v) paino eli kaaren pituus // Varsinainen Floydin-Warshallin algoritmi alkaa tästä. Esimerkiksi Sobel-operaattori (listaus 1) näyttäisi iteroituna seuraavanlaiselta: Floyd-Warshall-algoritmi Tarkkaavainen lukija toteaa tässä vaiheessa, että eihän tämä ole GPGPU:ta laisinkaan, vaan visuaalisten efektien ohjelmointia – mihin OpenGL/GLSL on ensisijaisesti suunniteltukin. ritettava PreIterateCallback sekä kunkin iteraation lopussa suoritettava PostIterateCallback . Myös nämä ovat takaisinkutsufunktioita
KOLARI KEMIJÄRVI ROVANIEMI KEMI OULU YLIVIESKA KONTIOMÄKI NURMES IISALMI KUOPIO VAASA SEINÄJOKI KOUVOLA JOENSUU JYVÄSKYLÄ PIEKSÄMÄKI HAAPAMÄKI ORIVESI TAMPERE KOKEMÄKI PORI TOIJALA MIKKELI PARIKKALA KOTKAN SATAMA PASILA TURKU LAPPEENRANTA KERAVA RIIHIMÄKI LAHTI SAVONLINNA 209 83 114 106 166 122 109 211 74 154 85 160 130 58 100 183 89 118 78 80 72 160 42 113 97 40 38 128 52 59 76 42 75 86 113 71 62 26 191 Rautatieasemista ja junayhteyksistä muodostettu verkko listaukselle 2. Näin ollen kullakin iteraatiolla indeksin k arvo on sama kaikille rinnakkaisesti suoritettaville fragmenttishaderin instansseille, ja OpenGL välittää kutakin tekseliä vastaavat interpoloidut UV-koordinaatit yhdelle fragmenttishaderi-instanssille. Onnea matkaan! OpenGL:ssä on huomattavasti erilaisia rajoitteita, mutta vanhemmallakin versiolla voi laskea esimerkiksi verkkoalgoritmeja. Verkko-aineistoksi otin Suomen joidenkin rautatieasemien välisiä rautatie-etäisyyksiä kilometreinä Suomen matkustajajunaliikenteen käyttämältä rataverkolta (katso kuvat) ja tallensin datan etäisyysmatriisina CSV-tiedostoon. Kuinka siis selvittää ja korjata virheet. Ennen kuin ryhtyy tekemään mitään monimutkaisempaa, kannattaa harjoitella yksinkertaisilla shader-ohjelmilla. Koska Floyd-Warshallissa kindeksin kanssa taulukon indeksointiin käytetyt muuttujat i ja j ovat fragmenttishaderin varying vec2 -tyyppisessä muuttujassa nimeltä UV välillä [0, 1], muunnan int-tyyppisen iteraatioindeksin float-liukuluvuksi jakamalla sen tekstuurin leveydellä. Floydin-Warshallin algoritmi soveltuu rinnakkaistettavaksi siten, että sen kahden sisemmän silmukan kaikki iteraatiot tulevat suoritetuksi rinnakkaisesti mielivaltaisessa järjestyksessä näytönohjaimen ytimissä. Punaisella ovat kaaret, joiden painot eli etäisyydet löytyvät edellisen aukeaman etäisyysmatriisista. Koodaajan perustyökalupakista löytyvät keinot eivät kuitenkaan pure niihin, koska debuggerilla ei pääse kiinni shaderin suoritukseen eikä GLSL:ssä liioin ole print-käskyä. Joskus voi käydä niin, ettei piirrettävä esine lainkaan ilmesty ruudulle. Kun iteraation lopussa lähdeja kohdetekstuuri vaihdetaan päittäin, edellisellä iteraatiolla kohdetekstuuriin laskettuja uusia minimietäisyyksiä käytetään seuraavan iteraation syötteenä, kun kohdetekstuurista tulee uusi lähdetekstuuri ja päinvastoin. Laskenta on listauksessa 2. Artikkelin listaukset, linkit ja lisätiedot Skrollin verkkojatkoilla: skrolli.fi/numerot G rafiikkaohjelmoinnissa käytetyt shaderit ovat ohjelmakoodia, joten niihin voi pujahtaa ohjelmointivirheitä. GLSL:llä koodatessa ei kuitenkaan kannata tehdä ilman tietoa kovin vahvoja oletuksia siitä, mikä on mahdollista. Ne eivät ole kovin mielenkiintoisia, joten emme käsittele niitä tässä sen enempää. Funktion kanssa täytyisi vastaavasti skaalata UV-muuttujasta luetut i ja j kokonaisluvuiksi. Esimerkkitapauksessani valitsin tekstuurien formaatiksi GL_RED, sisäiseksi formaatiksi GL_R16, ja tyypiksi GL_UNSIGNED_SHORT, johon voi tallentaa etumerkittömiä kokonaislukuja väliltä 0–65 535. Jos vieläkään ei näy mitään, tarkista projektioja malli-/näkymämatriisit sekä verteksishaderissa suoritettava laskenta. Onko koordinaattimuunnokset laskettu oikein. Piirtyvätkö kaikki pikselit samalla mustalla värillä kuin tausta. Helpoin virheluokka ovat syntaksija semantiikkavirheet, joista grafiikka-ajureiden shader-kääntäjä antaa virheilmoituksen. Käytän Floydin-Warshallin k-indeksinä uniformmuuttujaan iteration_i tallennettua iteraatioindeksiä. out vec4 frag_color; void main() { frag_color = vec4(1.0, 0.0, 1.0, 1.0); } Teksti ja kuvat: Mikko Rasa Visuaalinen DEBUGGAUS Lopputulos voi olla jotain yllä olevan kaltaista. Tekstuurista on myös mahdollista hakea tietty tekseli kokonaislukukoordinaateilla texelFetch-funktiolla. Ensimmäinen askel virheen selvityksessä on usein korvata fragmenttishaderi yksinkertaisella versiolla, joka piirtää vain yhtä väriä. Tällä tavoin saan säädettyä UV-koordinaatit välille [0, 1], kun iteraatioita on enintään tekstuurin leveyden eli verkon solmujen lukumäärän verran. 2019.2 8. GPGPU-laskennan suuri juju on GPU:n rinnakkaislaskentakyvyissä. Verkon jokaisen puuttuvan kaaren pituudeksi syötin 9 999. Etäisyydet tulee kuitenkin lukea eri tekstuurista kuin mihin ne tallennetaan. Puuttuviksi kaariksi merkitsin siis kaikki ne kaaret kahden solmun välillä, joiden välillä ei ole tällä hetkellä muuta matkustajajunayhteyttä kuin jonkin tämän verkon kolmannen solmun kautta
Toisaalta jos jokainen pikseli on eri värinen, seurauksena on kohinaa, josta ei saa tolkkua. Pehmeässä väriliu’ussa on vaikea havaita pieniä eroja, mutta voimakkaat kontrastit erottuvat selvästi. Tekstimuotoisen tulostuskomennon puuttuessa on hyödynnettävä shaderin ruudulle piirtämää väriä. Yritys skaalata arvot lineaarisesti johonkin värikomponenttiin hävittää yksityiskohdat. Kun esine on saatu näkymään ruudulla, voidaan toden teolla alkaa selvittämään shadereissa tapahtuvaa laskentaa. Shadereita voi jopa muokata lennossa. Tällöin GLSL:n fract-funktiosta on hyötyä. Vihreä ja sininen värikanava puolestaan sisältävät XY-koordinaatit, jolloin koordinaattiakselien suuntaiset korkeusprofiilit tulevat esiin. Seuraavassa kuvassa maaston väri muuttuu vihreästä punaiseen ja lopulta violettiin, kun säteenseurannassa otettujen askelten lukumäärä kasvaa. Visualisoitavan arvon ei tarvitse olla suoraan tekemisissä renderöinnin lopputuloksen kanssa. Vaalean turkoosilla alueilla on paljon vihreää ja sinistä, joten suunta on ylös ja kohti kameraa. if(i<40) return vec3((i-16)/48.0, 1.0(i-16)/24.0, 0.0); else if(i<64) return vec3(0.5+(i-40)/48.0, 0.0, 0.0); else return vec3(64.0/i, 0.0, 1.064.0/i); On myös olemassa erityisesti grafiikkakoodin vianetsintään tarkoitettuja ohjelmia. zyx)*vec3(1.0, 0.5, 0.8), 1.0); Punaiseen värikanavaan on visualisoitu korkeusarvot, jolloin etualalla oleva syvänne näkyy sisäkkäisinä renkaina. Päästä mielikuvituksesi valloilleen ja keksi juuri omaan grafiikkaongelmaasi sopiva visualisointi! 9. Se voi liittyä myös esimerkiksi suorituskykyyn. Eräs hyödyllinen visualisointi on pintojen normaalit. Piirtokomentoja voi kelata eteenja taaksepäin nähdäkseen niiden vaikutuksen ruudun sisältöön. vec3 coord = trace_terrain( start_coord, obj_look_dir); vec3 scaled = coord/vec3(32.0, 32.0, 16.0); frag_color = vec4(fract(scaled. Normaaleissa on kolme komponenttia ja meillä on käytössä kolme värikanavaa – yksi kanava per komponentti. Jos ne ovat pielessä, valaistuslaskennan tulokset voivat olla mitä sattuu. Tässä oli vain muutama esimerkki siitä, minkälaisia visualisointeja on mahdollista käyttää. Tästä on suurta apua visualisoinnissa, koska ohjelmaa ei tarvitse kääntää tai edes käynnistää uudelleen jokaisen muutoksen jälkeen. Sillä voi kaapata suoritettavasta ohjelmasta yksittäisen ruudun ja nähdä, mitä komentoja sen piirtämiseen käytettiin. in vec3 normal; out vec4 frag_color; void main() { frag_color = vec3(normal * 0.5 + 0.5, 1.0); } Tässä kuvassa on visualisoitu pinnan normaalit kameran koordinaattiavaruudessa. Kolme värikanavaa tarjoaa periaatteessa mahdollisuuden visualisoida kolmea eri asiaa, mutta kannattaa huomioida ihmisaivojen hahmotuskyky. Punertavilla alueilla Xkomponentin arvo on nollaa suurempi ja Y-komponentin nollaa pienempi, eli ne osoittavat oikealle alas. Koordinaattien ja värikanavien arvoalueet ovat hieman erilaiset, mutta tämä ratkeaa pienellä skaalausoperaatiolla. Yhdessä sopivan skaalauksen kanssa sillä saa näppärästi näkyviin arvon isoviivat, kuten vaikkapa fraktaalimaaston korkeuskäyrät. Tällainen on esimerkiksi NVIDIA Nsight Graphics. Toisinaan on tarpeen visualisoida jonkin muuttujan arvon muutoksia suurella alueella
1 Leonardo ”Fibonacci” Pisano määritteli jonon alkamaan 1, 1 kirjassaan Liber Abaci. Kompleksi-integraali kulkee jotain annettua polkua pitkin kompleksitasossa ottaen eräänlaisen summan funktion arvoista polun varrella. Fibonaccin jono löytyy eräästä kompleksifunktiosta, kun kompleksitason nollapisteen eli origon ympäri kierretään riittävän kaukaa. Kompleksifunktio on funktio, joka ottaa sisään kompleksiluvun ja palauttaa kompleksiluvun. Matematiikkaa Kompleksiluvuilla voi tehdä melkein mitä vain, mitä normaaleilla luvuilla voi tehdä. Mutta ei hätää, funktio X(z) voidaan kirjoittaa muotoon X(z) = 1 / (1 z -1 z -2 ) pienellä matemaattisella tempulla. Jonoa on helppo laskea suoraan määritelmästä, mutta on myös olemassa ratkaisu, joka perustuu kompleksilukuihin. Se voidaan siis määritellä rekursiivisesti kaavalla f(n) = f(n-1) + f(n-2) pohjatapauksin f(1) = 1 ja f(2) = 1. Nämä lukuparit voidaan ajatella pisteinä tasossa. Fibonaccin jonon kaksi ensimmäistä lukua ovat ykkösiä 1 , ja seuraavan luvun saa aina lisäämällä kaksi edellistä yhteen. Koodaamme Ficonaccin jonon seuraavaan kompleksifunktioon: X(z) = 1 + z -1 + 2z -2 + 3z -3 + 5z -4 … Tämä on siis ääretön sarja muuttujan z negatiivisia potensseja, joiden kertoimet muodostavat Fibonaccin jonon. Ongelmana on vielä, että kaavan laskemiseen tarvitaan funktio X(z), mutta siinä on äärettömän monta termiä, ja lisäksi sen muodostamiseen tarvitaan Fibonaccin jono, joka oli se asia, mitä tässä yritettiin alun perin laskea. Sillä voidaan siis laskea Fibonaccin jonon mikä tahansa luku laskematta erikseen jonon aiempia lukuja. Matematiikassa rekursiivinen funktio on sellainen, jonka arvo pisteessä riippuu saman funktion arvosta edellisissä pisteissä – samaan tapaan kuin ohjelmoinnissa. HÖRHÖ F ibonaccin lukujono on monen ensimmäinen kokemus rekursiosta matematiikassa. Kuulostaa ehkä hullulta, mutta antakaa kun selitän. Fibonaccin alkuperäinen esimerkki laskee kaniparin ja sen jälkeläisten lisääntymistä. Noudatamme artikkelissa Fibonaccin omaa määritelmää – tätä tapaa käytetään muuten myös Turun voimalaitoksen savupiipussa olevassa Mario Merzin taideteoksessa. Teksti ja kuvat: Jarno Niklas Alanko KOMPLEKSI-INTEGRAALIT Uusi ratkaisu rekursioon 1 2?i C X(z)z n ?1 dz 2019.2 10. Kompleksiluvut ovat lukupareja, joissa toista lukua kutsutaan reaaliosaksi, ja toista imaginaariosaksi. Niille on määritelty yhteen-, vähennys-, kertoja jakolaskut, joiden avulla päästään myös kiinni monimutkaisempiin käsitteisiin, kuten integraali. Fibonaccin jonoa 1,1,2,3,5,8,13… on helppo laskea perinteisellä tyylillä, mutta miksi tehdä asiat aina helpoimman kautta. Nyt funktion voi laskea nopeasti äärellisellä määrällä laskutoimituksia, eikä sen laskemiseen tarvitse tietää Fibonaccin luvuista mitään. Kaavan täsmällinen laskeminen on vaikeaa, mutta sitä voidaan approksimoida numeerisesti tietokoneella mielivaltaisella tarkkuudella. Nykyisin jono aloitetaan usein 0, 1. Osoittautuu, että tällaisen negatiivisen potenssisarjan termin z -n kertoimen voi laskea seuraavalla kaavalla: Tässä kaavassa integraali on polkuintegraali kompleksitasossa, missä polku C on mikä vain sellainen polku, joka kiertää origon vastapäivään pysyen koko ajan funktion suppenemisalueessa
On olemassa myös yksinkertaisempia kaavoja. Pisteen värisävy vastaa kompleksiarvon kulmaa reaaliakselin kanssa ja kirkkaus liittyy kompleksiarvon itseisarvoon. Jos kaikki menee kuten Strömsössä, saatu arvo parametrilla n on kompleksiluku, jonka reaaliosa on likimain n:s Fibonaccin luku ja imaginaariosa nolla. Integroinnissa ympyräpolun tulee kiertää pisteet, joissa funktio lähestyy ääretöntä. Funktion z -1 / (1 z z 2 ) tasa-arvokäyrät. Integrointi on toteutettu numeerisesti kulkemalla ympyräpolkua pitkin 0.0000001 yksikön pituisin askelin kerryttäen integraalin arvoa samalla. Ohjelma tulostaa seuraavaa: (1,3.14159e-07) 1 (1,3.14159e-07) 2 (2,6.28319e-07) 3 (3,9.42479e-07) 4 (5.00001,1.5708e-06) 5 (8.00001,2.51328e-06) 6 (13,4.08408e-06) 7 (21.0001,6.59736e-06) 8 (34.0001,1.06814e-05) 9 (55.0002,1.72788e-05) Suluissa tulostetut lukuparit ovat kompleksilukuja, jossa parin ensimmäisen luku on reaaliosa toinen imaginaariosa. Suoraan sanoen ei, koska suoraviivainen laskeminen olisi ollut nopeampaa, ja suuremmilla luvuilla liukulukujen epätarkkuus nousee ongelmaksi. Kuva 3. Koodi toimii kuten pitikin: Fibonaccin jono on luettavissa reaaliosista ja imaginaariosat hyvin lähellä nollaa. 11. Kuvissa jokainen tason piste vastaa yhtä kompleksilukua ja pisteen väri kertoo funktion arvon siinä pisteessä. Jutun funktio X(z) on piirrettynä kuvissa 1 ja 2 niin, että ensimmäisessä pikselin kirkkaus on suoraan funktion X(z) itseisarvo ja toisessa sellainen, että saman kirkkauden “vöissä” funktion itseisarvo pysyy samana. Kuva 4. Tämä antaa arvion integraalin todellisesta arvosta, ja mitä pienempi askelpituus on, sitä parempi arvio. Funktio 1 / (1 z -1 z -2 ). #include <complex> #include <iostream> using namespace std; const double pi = 3.14159265359; complex<double> integrate(complex<double> (*f)(complex<double>), complex<double> (*path)(double), double a, double b, double step){ complex<double> ans = 0; for(double t = a; t <= b; t += step){ ans += step * (f(path(t)) * (path(t+step) path(t)) / step); } return ans; } int n; complex<double> fib(complex<double> z){ return 1.0/(1.0 1.0/z 1.0/(z*z)) * power(z,n 1); } complex<double> circle(double t){ return complex<double>(2,0)*exp(2*pi*complex<double>(0,1)*t); } // Lasketaan 10 ensimmäistä Fibonaccin lukua int main(){ for(n = 0; n < 10; n++){ cout << n << ” ” << integrate(fib, circle, 0, 1, 0.0000001) * (1.0/complex<double>(0,2*pi)) << endl; } } Jutun kuvituksena on esityksiä kompleksifunktioista. Kuva 2. Menetelmä voidaan yleistää mille tahansa luonnollisille luvuille määritellylle rekursiiviselle funktiolle, joka voidaan kirjoittaa painotettuna summana funktion aiempia arvoja. Teoria testiin Oheisessa koodilistauksessa on C++koodi, joka laskee Fibonacci-jonon 10 ensimmäistä lukua. Funktion 1 / (1 z -1 z -2 ) tasa-arvokäyrät. Kuva 1. Funktion 1 / (1 z z 2 ) tasa-arvokäyrät. Onko tästä mitään hyötyä. Lopuissa kuvissa esiintyy muita samankaltaisia funktiota. Nämä pisteet sijaitsevat kuvassa 1 jossain kohtaa kirkkaissa valkoisissa alueissa ja kuvassa 2 niissä kohdissa, joissa vyöt käyvät äärettömän tiheiksi
Viime aikoina laskentatehon kasvu on mahdollistanut tekoälysovellusten kehittymisen ennennäkemättömille tasoille myös automaattisen kielenkäsittelyn osalta. Jos vaikka ystävämme kertoo meille eilen illalla join liikaa Rieslingiä ja nyt on pää kipeä, meidän ei tarvitse olla viiniasiantuntijoita ymmärtääksemme, että Rieslingiä viittaa alkoholijuomaan, ja että sanan tilalla voisi esiintyä vaikka Karhua, cosmopolitania tai Koskenkorvaa, tietysti ystävämme mieltymyksistä riippuen. Sanavektorimallin laatua voi tutkia esimerkiksi katsomalla, osaako se kertoa, mitkä sanat tarkoittavat samaa tai edes liittyvät toisiinsa. Firth esitti vuonna 1957 sanojen semantiikkaan eli merkitykseen liittyvän teorian: ”Sanan luonne määrittyy sen seuralaisten mukaan.” Tätä ajatusta kutsutaan jakaumahypoteesiksi, ja useimmat sanojen merkitystä käsittelevät koneoppimismallit perustuvat tähän. P uhuvat ja kirjoittavat tietokoneohjelmat ovat kiinnostaneet ammattilaisia ja harrastajia koneiden synnystä asti. Aikuiset sanoivat, ettei perunalla ole vastakohtaa. Tässä jutussa taas paneudutaan suosittujen neuroverkkojen avulla tuotettujen sanaupotusvektorien kykyyn selviytyä kielellistä ymmärrystä mittaavista tehtävistä. Koko ”kielellinen ymmärrys” on sinänsä vaikeasti määriteltävä käsite, ja siksi onkin vaikeaa sanoa, millaisilla kokeilla koneoppimismallin laatua kielellisissä tehtävissä on järkevää mitata. Konteksti on kaiken avain Kielitieteilijä J. Sanan Rieslingiä voidaan siis ajatella olevan aika samanlainen kuin Koskenkorvaa ja aika erilainen kuin hyppäisi. Olisiko se porkkana vai jokin, mikä ei ole ruokaa lainkaan. Skeptisimmät ajattelivat, ettei tietokone voi koskaan oppia ymmärtämään tai tuottamaan ihmismäistä kieltä. R. KOODI Lapsena mietin, mikä on perunan vastakohta. Teksti: Ada-Maaria Hyvärinen Kuvat: Sakari Leppä, Jarno Niklas Alanko SANAVEKTORIT Luonnollisen kielen käsittelyn salainen ase 2019.2 12. Tietääkö sanavektorimalli, mikä on perunan vastakohta. Mutta miten kielimalli voisi oppia tämän. Vaikka emme osaisi suomea, voisimme päätellä tämän tekemällä tilaston siitä, mitkä sanat esiintyvät usein yhdessä. Myös sanat kahvia tai appelsiinimehua sopisivat jotenkuten kontekstiin, vaikka emme ehkä tietäisi, miten pääkipu liittyy niihin. Aikuiselle, suomea puhuvalle ihmiselle on esimerkiksi aika selvää, että peruna, porkkana, mustajuuri, paprika ja kurkku liittyvät toisiinsa paljon enemmän kuin vaikkapa peruna ja sote-uudistus. Huonoimmassa tapauksessa sanat eivät olisi kontekstissa oikein kieliopillisestikaan, kuten hyppäisi, tai edes suomen kielen sanoja, kuten lampake. Vielä oudompaa olisi korvata sana vaikkapa sanoilla suklaata, kivikkoa tai sarjakuvia, vaikka sillä vaikka lause olisi edelleen kieliopillisesti oikein, ei konteksti sovi enää sanoihin. Skrollin edellisessä numerossa nähtiin tekoälyn avulla luotu lehtijuttu
Oikeasti malli ei tietenkään ymmärrä, mitä juominen tai Riesling ovat. Vaikka myös tietopankkeja esimerkiksi vastakohdista on koottu, tuntuu intuitiivisesti vaikeammalta saada tietokone ymmärtämään, millainen on lehmän ja vasikan tai äidin ja lapsen suhde. Miksi vektorit. Se oppii kuitenkin tunnistamaan niiden välisen yhteyden. Sanat esitetään tietokoneessa normaalisti jonoina merkkejä. Tällaisen tilastollisen tiedon avulla voidaan opettaa koneoppimismalli, joka ”ymmärtää” sanojen merkityksen. Tässä syötteenä on tekstinpätkiä, joista puuttuu keskeltä sana, esimerkisi anna minulle armahduksesi ___ minulle Rafaelin enkeli. Sanat täytyy vain ensin muuttaa tietokoneelle helpompaan muotoon: vektoreiksi. Siten jos esimerkiksi käyttäjä sanoo botille en, voimme katsoa sana-avaruudesta, että se on lähellä sanaa ei, ja toivoa, että käyttäjä yrittää sanoa samaa kuin joku, joka sanoisi ei. Verkkoa ei kuitenkaan opeteta suoraan toteuttamaan tämä tavoite. Sanat voidaan esittää vektoreina monenlaisella tavalla, ja periaatteessa sanavektorilla voitaisiin tarkoittaa mitä tahansa sanoja kuvaavaa vektoria. Sanavektoreihin perustuva ratkaisu sijoittaa kaikki kieltosanat lähekkäin abstraktiin sana-avaruuteen. Ensi silmäyksellä voisi ajatella, että tietokoneelle tällaiset kielioppisääntöjen mukaiset analogiat ovat paljon helpompia kuin merkitykseen liittyvät. Analogiapareja voi kuitenkin keksiä myös sanan muotoon liittyen: vaikkapa kissa, kissat on analoginen sanaparin koira, koirat kanssa, koska kummassakin on sanan yksikkö ja monikko. Sellaisen ajattelisi vaativan tietoa reaalimaailmasta, lehmien ja ihmisten perhe-elämästä. Suomenkielisessä versiossa vaaditaan joko botilta kykyä tunnistaa lista kieltosanoja (vaikkapa ei, en ja emme) tai kehittäjältä mielikuvitusta muotoilla kysymykset siten, että luonteva vastaus on aina pelkkä ei. Analogiat Kielellistä ymmärrystä on siis ainakin samankaltaisten sanojen tunnistaminen ja erilaisten sanojen erottaminen toisistaan. Sanaan kissa tarvitsee vain lisätä loppuun -t, samoin sanaan koira, ja monikkomuoto on valmis. Rieslingistä voisikin tulla mieleen vaikka viinin maku, hyvin sujunut illallistilaisuus tai entisen seurustelukumppanin mieltymykset. Tietokoneen voi myös luottaa osaavan taivuttaa harvinaisempia sanoja, kuten susi, suden: tällaisia sanamuotoja tuottavia ohjelmia voi tehdä ilman koneoppimista kattavien sääntökokoelmien avulla. Tavanomaisesti ulottuvuus on joitakin satoja. Sen sijaan tilastollisten mallien on opittava sanoihin liittyvä tieto vain toisten sanojen perusteella. Myös ihminen oppii sanoja kontekstin perusteella, mutta ihmisen kohdalla kontekstiin liittyy paljon muutakin kuin sanojen ympärillä olevat sanat. Toinen tapa mitata ihmisen tai koneen kielellistä osaamista on testata taitoa analogiaparien tuottamiseen. Se opetetaan niin sanotun valetehtävän avulla. Piilokerroksen koko vastaa haluttua sanavektorien ulottuvuutta. Konteksti syötetään verkkoon antamalla kontekstin sanoja vastaaville neuroneille arvoksi nollasta poikkeava arvo ja muille nolla. Siis jos opetusdatassa on 10 000 erilaista sanaa, syötekerroksessa on 10 000 neuronia. Oikeasti ollaan kiinnostuneita mahdollisuudesta kuvata sanoja sellaisilla vektoreilla, että samankaltaisten sanojen vektorit ovat toisiaan lähellä ja erilaisten sanojen vektorit ovat toisistaan kaukana. Tutustutaan kuitenkin ensin CBOW-malliin, eli Continuous Bag Of Words -malliin. Vastakohtaisuus liittyy sanan merkitykseen. Ulostulokerroksessa on taas yksi neuroni jokaista sanaston sanaa kohti, ja neuronin aktivaatioarvo tulkitaan todennäköisyytenä sille, että annetun kontekstin väliin kuuluu tämä sana. Arvaaminen tehdään neuroverkolla, jossa on syötekerros, yksi piilokerros ja ulostulokerros. Ihminen liittää sanoja myös esimerkiksi sosiaalisiin suhteisiin ja aistikokemuksiin. Opetusdata voidaan luoda kahdella tavalla: joko skip-gram-mallilla tai CBOW-mallilla. Sen sijaan se koulutetaan arvaamaan sana kontekstin perusteella. Tietoa sanojen samankaltaisuudesta voidaan hyödyntää samalla tavalla vaikkapa dokumenttien luokittelussa, tiedonhaussa, konekääntämisessä tai mitä kielimallin avulla sitten yrittääkään tehdä. Käytännön sovelluksiin se usein riittää. Jokainen syötekerroksen neuroni esittää yhtä sanaston sanaa. Tässä tapauksessa vastaus on sana ole. Verkkoa opetetaan syöttämällä sille suuri joukko konteksti-välisanapareja ja säätämällä verkon painokertoimia vastavirtaalgoritmilla (backpropagation) hiljalleen oike13. Valetehtävänä on arvata, mikä sana kontekstista puuttuu. Sanat säkkiin Nykypäivänä tunnetuin sanavektorimalli on Googlen tutkijoiden kehittämä word2vec. Merkkijonomuotoiset sanat ovat moneen tarkoitukseen hyviä: esimerkiksi englantia käsittelevä keskustelubotti voi reagoida asiakkaan tuottamaan merkkijonoon no ja tietää siitä keskeyttää toimintonsa. Esimerkiksi pieni ja suuri ovat toistensa vastakohtia, joten niiden suhde on analoginen sanojen hidas ja nopea kanssa, jotka ovat myöskin toistensa vastakohtia. Sanojen esittäminen liukulukuvektoreina toimii koneoppimisen kanssa hyvin yhteen, koska useimmat koneoppimismenetelmät on suunniteltu käsittelemään nimenomaan liukulukuvektoreita. Tässä tekstissä keskitytään kuitenkin sanaupotusvektoreihin eli word embedding -vektoreihin, jotka ovat nulotteisia liukulukuvektoreita. Koneoppimisen avulla kielimalli voi kuitenkin pystyä yllättäviin suorituksiin. Opetusaineiston onkin oltava riittävän kattavaa ja aiheeseen sopivaa, jotta opetus onnistuu riittävän hyvin
Sanavektorit voidaan lukea verkosta aktivoimalla ainoastaan haluttu sana syötekerroksessa ja lukemalla piilokerroksen aktivaatiot. aan suuntaan. Tämä olisi hyödyllistä esimerkiksi chatbot-projektissamme. Skip-gram-malli toimii melkein samalla tavalla kuin CBOW-malli. Vektoreilla voidaan tehdä esimerkiksi seuraavanlaisia laskutoimituksia: v(Berliini) v(Saksa) + v(Ranska). Laskun tulokseksi tulee likimain v(Pariisi). Pariisi on siis vähän kuin Berliini, josta on poistettu Saksa ja lisätty tilalle Ranska. Edellisen esimerkin tilanteessa verkon pitäisi siis tietää, että anna, minulle, armahduksesi, minulle, Rafaelin ja enkeli kuuluvat sanan ole kontekstiin. Mitä suurempi piilokerros verkolla on, sitä rikkaammin malli voi kuvata sanaston ominaisuuksia. Punaiset värit kuvaavat neuroneiden aktivaatioita: mitä kirkkaampi väri, sitä vahvempi aktivaatio. Yllättäviä sovelluksia Nyt toivon mukaan rakentamamme vektorimalli sisältää semanttista tietoa kielestä. Myös skip-gram-malli opetetaan vastavirta-algoritmilla, ja siitäkin poimitaan sanavektoreiksi piilokerroksen aktivaatiot. kolme yksi kahta kaksi ensimmäinen perunan peruna perunaa porkkana porkkanoita mustajuuri ei emme en eikä nopea hidas pieni suuri peruna porkkana?. Kun CBOW-mallissa arvataan kontekstiikkunasta puuttuva sana, tehdään skip-gram-mallissa päinvastoin. Siinä malli opetetaan arvaamaan, mitkä sanat kuuluvat välisanan konteksti-ikkunaan. CBOW-mallin opetus. Kun verkkoa on opetettu tarpeeksi, sanavektorit voidaan lukea siitä syöttämällä sille sanat yksitellen ja lukemalla piilokerroksen aktivaatioarvot. Sanavektoreiden samankaltaisuutta mitataan niiden kulman kosinilla. Valetehtävän oppinutta verkkoa ei tämän jälkeen tarvita enää. Merkitään sanaa vastaavaa vektoria merkinnällä v(sana). 2019.2 14. Oikealla olevassa ulostulokerroksessa syttyvät ne neuronit, joiden edustamat sanat esiintyvät annettujen sanojen kanssa usein yhdessä. Samanlaiset sanat ovat lähekkäin sana-avaruudessa. kahvi liikaa nyt juoda hyp¨ at¨ a kipe¨ a karhu riesling suklaa p¨ a¨ a kahvi liikaa nyt juoda hyp¨at¨a kipe¨a karhu riesling suklaa p¨a¨a 1 Analogisia vastakohtia sana-avaruudessa. Vektorien laatu ei kuitenkaan parane kasvattamalla piilokerrosta valtavan suureksi. Yllättäen osoittautuu, että myös word2vec-vektorien komponentittaisella yhteenja vähennyslaskulla on tulkinta, joka liittyy sanojen merkityksiin. Voisimme esimerkiksi yrittää löytää kaikki sanaston kieltosanat etsimällä vektorit, jotka ovat lähellä ei-sanaa. Kontekstitiedon avulla opetetut word2vec-vektorit sisältävät kuitenkin paljon muutakin tietoa kuin vain sen, mitä sanaa mikäkin vektori kuvaa ja mitkä sanat ovat samanlaisia. Kuvassa verkkoon syötetään sanat ”pää”, ”kipeä”, ”juoda”, ”nyt” ja ”liikaa”. Ei ole mitenkään ilmeistä miksi näin tapahtuu, mutta tälle on olemassa monimutkainen perustelu, joka liittyy word2vecmallin optimoitavan funktion matemaattiseen muotoon
Myöskään yhdyssanoista malli ei tiedä mitään. Useimmat sanatasolla toimivat mallit toimivat suomen kielellä huonommin kuin englannilla, koska suomen kieli on agglutinoiva ja morfologisesti rikas, eli suomen kielessä paljon informaatiota välitetään taivuttamalla sanoja, ja sen takia suomessa on paljon erilaisia sananmuotoja. Tästä kieliriippumaton koneoppimismallimme ei kuitenkaan tiedä mitään, vaan se tallentaa sanat kiltisti siinä muodossa, jossa ne esiintyvät tekstissä. Facebookin kehittämä fastText-malli toimii muuten samalla tavalla kuin word2vec, mutta mallille opetusaineistona annettavat sanat esitetään eri muodossa. Esimerkiksi kissan ja kissojen ovat mallin opettamisen näkökulmasta yhtä erillisiä kuin hiiri ja matto. Suomen kielen haasteet Suomalaiset pitävät suomen kieltä todella vaikeana kielenä niin ulkomaalaisille kuin tietokoneillekin, eikä tämä käsitys ole täysin väärä. Nyt voimme pyytää mallia täyttämään muuttujan x seuraavaan 15. Olisihan hienoa, jos tekoäly pystyisi todistetusti ratkaisemaan tämänkin ihmiselle suorastaan mahdottoman ongelman! Ensin tarvitsemme esimerkin vastakohtaisuusparista, esimerkiksi aikuinen–lapsi. Kontekstitiedon avulla malli kuitenkin oppii, etteivät nämä sanat yleensä esiinny kovin samanlaisissa yhteyksissä, ja osaa siksi pitää niitä erillisinä. Alimerkkijonoilla eli merkki-n-grammeilla tarkoitetaan tässä yhteydessä n:n pituisia sanan osia. Jos kuitenkin poimitaan jostakin oikeasta tekstistä kymmenentuhatta yleisintä sanaa, niin englanninkuin suomenkielisellä aineistolla osa sanoista esiintyy taipuneessa muodossa. Tämä sinänsä hyvin yksinkertainen kikka auttaa saman sanan eri taivutusmuotoja kuvautumaan lähemmäs toisiaan. Taivuttavien kielten kuten suomen kanssa herää myös toinen ongelma: sanojen erillisyys. Todellisuudessa sanan alku ja loppu on vielä merkitty erikoismerkeillä < ja >, jolloin joukko on hiukan laajempi: {<kissa>, <ki, kis, iss, ssa, sa>}. Tavallaan oli huijausta puhua kymmenestätuhannesta sanasta, koska se voi johtaa ajattelemaan, että nuo kymmenentuhatta sanaa esiintyvät sanastossa perusmuodossa niin kuin sanakirjan hakusanat. Yksinkertaisuudestaan huolimatta alimerkkijonojen käyttö parantaa mallin kykyä tuottaa laadukkaita suomenkielisiä vektoreita. Nämä sanavektorit muodostetaan sanaa vastaavasta vektorista sekä sen alimerkkijonoja vastaavista vektoreista. Usein sanastoon valitaan kaikki sanat, jotka esiintyvät opetusaineistossa tarpeeksi usein, esimerkiksi vähintään viisi kertaa. Malli ei voi tietää, ettei suomi kuulu näihin kieliin. Verkon opettamiseksi pitää päättää, minkä kokoista sanastoa hyödynnetään. Myöskään fastText-malli ei tiedä mitään sanojen taivuttamisesta, vaan se on kieliriippumaton ja perustuu pelkästään peräkkäisiin merkkeihin. Niillä on toki pitkä yhteinen alimerkkijono, ja joissakin kielissä sanoja taivutetaan ja muodostetaan nimenomaan sanan alkuosaa muuttamalla. Koska fastText-mallissa sanojen sisältämät merkkijonot vaikuttavat niiden vektoriesitykseen, muistuttavat samoja merkkijonoja sisältävien sanojen vektorit toisiaan enemmän kuin merkkijonojen sisällölle sokeassa word2vec-mallissa. Se myös helpottaa merkitysanalogioiden (kuten aiemmin nähdyt maat ja pääkaupungit) ja muotoon liittyvien analogioiden (kuten vaikkapa iso, isoin ja kylmä, kylmin) löytymistä sanaavaruudesta. Jos otettaisiin vaikkapa sana kissa ja kaikki sen 3-grammit, saataisiin joukko {kissa, kis, iss, ssa}. Vektoreita tuottaessa kannattaa kuitenkin valita tarpeeksi suuri piilokerroksen koko, jotta malli pysyy tarpeeksi rikkaana tallentamaan sekä alimerkkijonojen tuoman tiedon että word2vec-mallin henkisesti kontekstitiedon. Pieniulotteisilla vektoreilla on vaarana, että malli painottuu liikaa alimerkkijonojen osalta. Malli ei osaa tuottaa vektoreita ennennäkemättömille sanoille. Entä se perunan vastakohta Nyt kun tiedämme, mikä fastTextmalli on ja miksi se pystyy vastaamaan analogiakyselyihimme, voimme tutkia, löytäisikö se ratkaisun lapsuusaikojen kysymykseen perunan vastakohdasta. Kielestä ja mallin tarkoituksesta kuitenkin riippuu, mitkä n-grammit kannattaa ottaa mukaan. FastText-mallissa on oletuksena otettu mukaan kaikki n-grammit, joissa n on välillä 3–6. Ratkaisuna alimerkkijonot Ongelmalle löytyy onneksi ratkaisuja, kuten viime aikoina suosiota saavuttanut word2vecin laajennos fastText. Opetuksen jälkeen sanasto on valmis. Silloin se alkaa nähdä yhteyksiä vaikkapa sanojen joulu ja koulu välillä. Malli käsittelee jokaista sanaa itsenäisenä, eikä välitä siitä, muistuttaako se merkeiltään jotakin muuta sanaa. Sanastoon kuulumattomien sanojen kanssa toimimattomuus voi muodostua ongelmaksi millä tahansa kielellä. Riippuu paljon tehtävästä, pitäisikö sanastoon kuulua kymmenentuhatta, satatuhatta vai miljoona sanaa vai kenties kaikki lähtöaineistossa (esimerkiksi Wikipediassa) esiintyvät sanat
Kun etsitään vastakohtaa sanalle porkkana vastakohtaparina pitkä, lyhyt saadaan munakoiso ja tärkkelysperuna. Kokeillaan vastakohtien löytämistä skip-gram-mallilla, jossa vektorin pituus on 300 ja opetukseen on käytetty 3-, 4-, 5ja 6-grammeja. Riippuu sanavektorien käyttötarkoituksesta, kuinka tärkeää on saada saman sanan eri muodot lähelle toisiaan. Ehkä alimerkkijonot haittaavat kyselyn toimimista. Tulokset on listattu taulukossa 2. Jätetään siis n-grammit pois ja opetetaan samalla aineistolla 300-ulotteisia vektoreita tuottava skip-gram-malli. Tutkijat etsivät edelleen teoreettisia selityksiä mallin toimivuudelle. Toisaalta n-grammeja hyödyntävä fastText on word2vec-menetelmää käytännöllisempi myös sanaston joustavuuden kannalta. Entä oma ehdotukseni perunan vastakohdaksi, porkkana. Epätoivoisesta kyselystämme huomataan ainakin alimerkkijonojen merkitys: kun ne ovat käytössä, perunan lähelle kuvautuu enemmän sanan taivutusmuotoja. Vastakohtapariyritykset osuvat erilaisiin perunan taivutusmuotoihin tai perunatyyppeihin. Tyypillistä analogiakyselyille on osua johonkin hakusanoista tai niitä muistuttaviin sanoihin, kuten synonyymeihin tai sananmuotoihin. Täytyy kenties myöntää, ettei perunalle löydy luonnollista vastakohtaa, tai ainakaan perunan ja sen vastakohdan suhde eivät muistuta kahden adjektiivin vastakohtaisuutta. Word2vec-malli toimii käytännössä erittäin hyvin, mikä on hämmentävää ottaen huomioon miten yksinkertaisiin periaatteisiin se perustuu. Vastakohtapari Perunan vastakohta heikko, vahva juurekset, rypsi pieni, suuri juurekset, mustaselja pitkä, lyhyt jamssi, palkokasvit Taulukko 2. 2019.2 16. Kuitenkaan vastakohtaisuutta ei näy mallin antamissa tuloksissa. Sanavektorit eivät siis ole ihmistä viisaampia, mutta niitä hyödyntävillä malleilla tavoitetaan kenties jotakin siitä, miten ihminenkin hahmottaa kielen. Vastakohtaparilla heikko, vahva malli tarjoaa porkkana-sanalle vastakohdiksi porkkanana ja porkkanan. Omien sanavektorimallien opettaminen on helppoa ja nopeaa saatavilla olevien kirjastojen ja avoimien aineistojen avulla. Jos siis haluaa tehdä luonnolliseen kieleen liittyvää koneoppimista, kannattaa harkita, haluaisiko parantaa mallia opettamalla sitä tehtävään sopivalla aineistolla ja valitsemalla tehtävän mukaan, haluaako käyttää alimerkkijonoja vai ei. Ehkä en ollutkaan lapsena ihan väärässä perunan ja porkkanan vastakohtaisuudesta. Kokeillaan mallilla, joka on opetettu perinteiseen word2vec-tyyliin ilman alimerkkijonoja. Selvästi malli tietää, että perunaan liittyviä asioita ovat sanan eri taivutusmuodot. Toisella mallilla tulokset ovat erilaisia, mutta eivät välttämättä sen hyödyllisempiä. Suomen kielen sananmuotojen omaksumiseen yksinkertainen ja kieliriippumaton kikka, n-grammien käyttö, auttaa jo selvästi. lauseeseen: ”sanan lapsi suhde sanaan aikuinen on sama kuin sanan peruna suhde sanaan x”. Ilman alimerkkijonoja perunan luota löytyy muita syötäviä ja kenties jopa samanlaisissa ruuissa käytettäviä kasveja. Tämä onnistuu vähentämällä aikuisen vektorista lapsen vektori ja lisäämällä perunan vektori, minkä jälkeen tuloksen pitäisi olla perunan vastakohta. Vastakohtapari Perunan vastakohta aikuinen, lapsi perunat, vientiperuna pieni, suuri puikulaperuna, perunat pitkä, lyhyt perunaa, perunaan heikko, vahva perunan, perunaan Taulukko 1. Monia erilaisia sananmuotoja sisältävälle suomelle tästä on hyötyä, mutta ominaisuus on kätevä myös vaikkapa yhdyssanojen tai uudissanojen kannalta. Tällä kertaa perunan vastakohtaa lähinnä on siis perunan taivutusmuotojen sijaan muita kasviksia. Taulukossa 1 on listattu muutamalle vastakohtaparille kaksi mallin löytämää perunan parasta vastakohtaa: Ei näytä lupaavalta. Alimerkkijonojen avulla voidaan laskea vektoriesitys mille tahansa sanalle, vaikka sitä ei esiintyisi opetusaineistossa lainkaan. Ehkä liiankin hyvin. Nämä ovat fastText-kehittäjien tavallisesti käyttämät parametrit, joilla opetettuja valmiita malleja voi esimerkiksi ladata kirjaston sivuilta
Efekti on riippumaton esimerkiksi ohjelmointikielestä ja sovellusalueesta. Stackoverflown kyselyssä 2 välilyöntien käyttö korreloi selvästi positiivisesti vuosipalkan kanssa. Sisentäminen tabulaattorilla ei ole riippuvaista alkuperäisen kehittäjän valinnoista toimiakseen kaikkialla ja tehokkaasti. välilyönti Tabulaattori Lähdekoodi tulee ehdottomasti sisentää tabulaattorimerkillä (ASCII 9, horizontal tab). Vastaus Jannelle Tervetuloa nykyaikaan, Janne. Tabulaattorin esittämää sisennyssyvyyttä on myös triviaalia muuttaa, joten se skaalautuu tarpeisiin, joita alkuperäinen kehittäjä ei osannut tai halunnut huomioida. Tabulaattori on sisennyksen avoin lähdekoodi: standardinmukainen, vailla toimittajalukkoa (vendor lock-in) ja rajattomalla muokattavuudella. Lisäksi tässä on vaara, että joku erehtyy linjaamaan tabeilla, jolloin linjaus menee rikki, kun tabin leveys muuttuu. Tabulaattorilla sisentäminen on energiatehokkain sisennystapa. Välilyönneillä sisennettynä sisennyksen merkkimäärä kasvaa noin 15 miljoonaan, mikä vaatii enemmän tallennustilaa ja prosessoriaikaa – energiaa. Jos virhe on mahdollista tehdä, joku tekee sen ennen pitkää. Ennen kaikkea, välilyöntien käyttö on merkki hyvästä mausta. Miljoona lähdekoodiriviä sisältää noin kolme miljoonaa sisennystä. Välilyöntejä voi käyttää myös koodirivien linjaamiseen esimerkiksi siten, että peräkkäisten rivien yhtäsuuruusmerkit osuvat kohdilleen. Tallennustilan säästö oli ehkä relevantti argumentti Commodore 64:n aikoihin, mutta 15 miljoonaa merkkiä on 15 megatavua, jotka eivät tunnu missään nykyaikana. Janne Sirén Jarno Niklas Alanko Valokuvat: Janne Sirén, Tuukka Norri Grafiikat: Janne Sirén Koodin sisennys Tabulaattori vs. Jos sisennyksesi on tehty tabeilla, joudut sekoittamaan tabeja ja välilyöntejä tässä kohtaa. Tabulaattori on kuvauskieltä, jonka esitysmitta on täysin dynaaminen. Puhdasoppinen tabulatoristi ei sekaannu väliyönteihin, eikä tee oletuksia lukijan ympäristöstä. Tabulaattorimerkeillä tilavaade pysyy samana. Yksi pakkaamaton kuvatiedosto voi olla isompi. Välilyönti Koodi sisennetään välilyönneillä. Kun sisennyksen leveys on yksikäsitteinen, voidaan asettaa vaatimukseksi rajata koodirivin leveys esimerkiksi 80 merkkiin, jolloin sitä voi katsella mukavasti standardilevyisestä terminaalista. Emme tee ohjelmiakaan enää vain Windows-työpöydälle, miksi koodaisimme 80 merkin terminaalille. Koodin kuuluu näyttää siltä, kuin sen kirjoittaja tarkoitti. 1 Lähde: Hevosmiesten tietotoimisto 2 stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs 17. Tärkein syy tähän on, että välilyönti näyttää samalta kaikille, toisin kuin tabi, jonka leveys riippuu tekstieditorin asetuksista. Vastaus Niklakselle Välilyöntisisennykset tarjoavat näennäistä joustavuutta koodiesityksen kovakoodaamiseen. Tutkimusten mukaan 1 yksi kolmesta suomalaisesta ohjelmoijasta onkin joutunut sairaalahoitoon muokattuaan välilyönneillä sisennettyä koodia alkuperäisen kehitysympäristön ulkopuolella. Koodin sisennyssyvyyttä ei kuulu muuttaa, koska tabin leveys vaikuttaa siihen, kuinka koodi kannattaa rivittää. Tabulaattorin perusteella moderni editori esittää koodin kulloinkin sopivimmassa muodossa – samalla vaikka tasaten metodin parametrit tai ne yhtäsuuruusmerkit. Jos eri osat koodista on kirjoitettu eri pituisia sisennyksiä ajatellen, kokonaisuutena koodi ei näytä hyvältä millään leveydellä. K.O. Tämän apukepin vuoksi sisennysmerkkien sekakäyttäjät ovat toki ongelma. Teoriani mukaan välilyöntien käyttö kielii hyvästä tyylitajusta, joka näkyy myös palkassa ennen pitkää. Välilyönnillä sisentäminen kiihdyttää ilmastonmuutosta, edistää suljettuja ohjelmistoratkaisuja ja on haitallista kansanterveydelle. Nyt sinulla on kahdenlaisia whitespace-merkkejä peräkkäin samalla rivillä, mikä yksinkertaisesti tuntuu pahalta. Kahden levyisillä tabeilla kauniisti muotoiltu koodi saattaa näyttää huomattavasti huonommalta kahdeksan levyisillä tabeilla
Eturenkaan jarru ottaa talteen jarruenergiaa. Halpa Lidl-kopteri on hintaansa nähden asiallinen ja ihmeen hauska ja helppo lennätettävä. Ehkä se hyppää silmille kaupan hyllyjen ohi kävellessä. Halpislenturin laadukkuus yllätti. Akku on kuitenkin kätevästi omassa avattavassa kotelossaan, niikassa ja jopa akkujen paloherkkyydessä voi olla hieman eroa. Kun kelit kohentuvat ja päivät pidentyvät, uusien teknolelujen houkutukseen on helppo sortua. Ymmärrän hyvin miksi lautojen suosio on kasvanut hurjaa vauhtia, sillä kyllähän nämä ovat käytännöllisiä kaupunkiliikenteeseen ja jopa työja hupimatkojen yhdistelmäliikkumiseen. Ehkä se on varsinainen villitys tai kätevä vempain, josta voisi teoriassa olla joskus hyötyäkin. Hintalaatua Lidlistä Yksi erikoisemmista silmille hyppimisistä tapahtui Lidlissä, jonka hyllylle ilmestyi jo viime kesänä 27 euron Quad copter, nelikopteri. Kyllä, kiitos. Xiaomi Mi Electric Scooter M365 asettuu mittarin 500 euron päähän. Riskitekijätkin kannattaa muistaa. Isommat sähkölelut näyttävät olevan tämän kesän suuri muotivillitys, sillä sähköpotkulaudat ovat rantautuneet vauhdilla sekä koteihin että ainakin Helsingin ja Tampereen kaduille vuokrattavina kiitäjinä. Kelvollisen gyron ansiosta laite on vakaa ja sutjakka lennätettävä. Mitä löysimmekään tälle kesälle. Kauppinen Kuvat: Laitevalmistajat Kesäkuuma, lelujen huuma. Tekemistä mökille tai tuikitärkeä varuste matkalle. 12-kiloinen e-lauta taittuu kokoon ja mahtuu auton takakonttiin, mutta kulkee taitetusta tangosta kannettuna mukana myös bussissa tai metrossa. 2,4 gigahertsin taajuudella toimiva nelikanavainen ohjaus toimii oivallisesti, ja jos häröjä sattuukin, roottorit ovat hyvin suojattuja. Laatikon mukaan se lentäisi jo kahdeksan minuuttia kertalatauksella. Tämänkesäinen suurempikokoinen, noin 35 euron nelikopteri vaikuttaa olevan samalta valmistajalta. Kypärä on tärkeä, sillä sähkölaudalla kaatuu helpommin ja dramaattisemmin kuin polkupyörällä. Laitteessa itsessään on minimalistinen määrä ohjainlaitteita ja ainoa näyt2019.2 18. Lisäturvaa tuovat takana kissansilmä ja jarruvalo, edessä kirkas led-valo. TEKNIIKKA Mukaan matkaan ja mökille KESÄN TEKNOLELUT Teksti: Jukka O. Samoin ymmärtää kritiikin niiden vaarallisuudesta, turhakkeisuudesta sekä vuokralautojen päättömästä pysäköinnistä. Ai alle 30 euron lentolaite. Vaikka ohjain on kiinaa, ohjaustikut ovat verrattain tarkat. Ainoa selvä miinus tulee USB:llä ladattavan akun pikkuruisuudesta, sillä siinä riittää virtaa vain 4–6 minuutin mittaisiin lentoihin. Ja kun laudalla kiitää, niin johan siinä lähes liitää, kyyti on pehmeää ja mukavaa. Skrolli antaa viime hetken laitevinkit äkkilähdölle. joten askartelija voi vaihtaa sen suurempaan. Tämäkin lautamalli on varustettu takarenkaan tehokkaalla levyjarrulla, jonka raju käyttö voi viskata ajajan jorpakkoon. Kevyen liikenteen väylillä täytyy ymmärtää oma äänettömyytensä ja nopeutensa. Mieli teki, mutta eiköhän yksi lelunelikopteri riitä tähän taloon. Mutta ei liene lainkaan liioiteltua väittää, että jos kaupassa on vierekkäin 150 ja 500 euron laudat, niin niiden materiaaleissa, tekU uden kesälelun voi löytää mistä vain
Tämä on toteutettu saranoimalla kamera niin, että kameran voi avata tai sulkea kuvaustarpeiden mukaan. Kamerassa on pelkästään tavallinen ruuvikierrekiinnitys, eikä ainakaan valmistaja tarjoa rytyytyskiinnikkeitä. GoPro 7 Black onkin monin osin paha pettymys. Se on varmasti kelpo vaihtoehto e-lautaa harkitseville, jotka haluavat laadukkaamman ja kestävämmän kulkuneuvon. Lasit kestävät jonkin verran kosteutta ja upottamista, ja sisäiselle 16 gigatavun muistille mahtuu noin 2,5 tuntia videota. Ja jos Nintendo Switchillä pelaava lautailija kiitää jonnekin syrjempään, niin Bionik Power Platella konsolissa ja ohjaimissa piisaa virtaa pidemmänkin retken pelailuihin. Välillä kamera lopettaa tallentamisen itsestään. Black lupasi myös livestriimauksen suoraan kamerasta, mutta sen toimivuus ja toteutus on surkea, lähes hyödytön. Xiaomi on fiksu, kestävän oloinen ja hyvin suunniteltu sähköpotkulauta, joka jätti positiivisen vaikutelman. Vähän nopeammin edetessä tuulen suhina osuu ikävästi mikrofoniin. 19. Ja kun talvet ovat ainakin eteläisessä Suomessa jo varsin lyhyitä ja vähälumisia, niin kaksipyöräilykausikin vain pitenee koko ajan. Lisää tietoja, toimintoja, laudan asetusten säätöä ja tuiki tärkeä lukitustoiminto löytyvät mobiilisovelluksesta. Kovempien kuvaustilojen formaatit eivät avaudu vanhemmissa videonkäsittelyohjelmissa. Ei ehkä se elegantein ratkaisu. Aloitetaan sniikeimmästä. Luvassa on lennokkien lennätystä, vauhdikasta sähkökiitämistä, ulkomikroautoilua, retkiä ja jumiutumisia seikkailupuistojen köysiradoille. 360 asteen 5K-videointi 30 kuvaa sekunnissa -nopeudella oli kiitettävän tarkka ja selkeä. Bear Grylls Waterproof Video Eyewear on yksinkertaisesti aurinkolasit, johon on upotettu huomaamaton videokamera ja mikrofoni. Parasta on HyperSmooth-kuvanvakautin: tärisevään ja heiluvaan kypärään, tankoon tai muuhun alustaan kiinnitettynä kuva pysyy tarkkana ja vakaana. Muutoin niin hauska ja toimiva laite, että tänä kesänä kuvaan 360-videota ja paljon. Bear Grylls Waterproof Video Eyewear 200 € Bionik Power Plate (Nintendo Switch) 40–50 € GoPro 7 Black 300–400 € Insta360 EVO 450–475 € Quadcopter (Lidl) 27 € Xiaomi Mi Electric Scooter M365 470–500 € tö kertoo akun latauksen. Akkukesto on surkea, joten ilman lisäakkuja ei pärjää. Insta360:n ilmainen 360/3D-videoeditointiohjelmisto oli hyvä ja toimiva tarkoitukseensa. Joskus tilanteet täytyy saada talteenkin. 1080p200 on jo varsin näppärä kovaan actioniin. GoPro ei ole suotta synonyymi actionkameroille, sillä parhaimmillaan tämä on järisyttävän hyvä videokamera. Simppelissä akkupaketissa onkin paljon kelpoisaa toiminnallisuutta. Kesän kameratarjonta on vähintäänkin monipuolista. Niinpä sen kiinnittäminen rajumpia 360-kuvauksia varten vaatii paljon gaffatai jeesusteippiä. Videokamera on siten käytännössä täysin näkymätön, onpahan vain kaverilla tyylikkäät aurinkolasit. Esitellyt kapineet ovat kirjoittajan omia tai lainaamia testilaitteita. Eniten EVOssa mietityttää saranakiinnityksen ja lukituksen pitävyys. Kesän vinkein kuvauslaite on Insta360 EVO, joka yhdistää samaan laitteeseen 360ja 3D-kameran. Kännykällä striimaa kätevämmin, vaikka luuria ei saakaan kiinni kypärään. Kiristettävä turvanauha kuitenkin pidätti lasit paremmin paikallaan, ja sopivasti kiristettynä tuntui, että laseja voi pitää päässä hurjemmassakin röykytyksessä. Äänenlaatukaan ei säväytä, mutta oman puheen ja lokin kirkunan erottaa. Kerro se kuvin ja videoin Kesä innoittaa myös kaikenlaisiin jänniin aktiviteetteihin. Jos et himoitse HyperSmoothia, niin älä vaivaudu. Astetta järeämpään kuvailuun sopii GoPro 7 Black. Minulle suurin ongelma oli lasien sovittaminen päähän. En olisi uskonut, että näin hyvään kuvanlaatuun voisi päästä ilman ulkoista vakautinta. Kameran ohjelmistokuvanvakautus toimii upeasti esimerkiksi Thetan 360-kameraan verrattuna. Lasien mukana tulee kirkkaat vaihtolinssit, joten niitä voi käyttää myös varjossa ja sisävalaistuksessa, mutta mikään pimeäkamera tämä ei ole. Toimintanappuloiden sijoittelussa ja fyysisessä designissa on tyhmiä ratkaisuja. Yhdellä latauksella kuvaa tunnin. 1080p30-kuvaa 90 asteen kuvakulmalla tallentava videoja stillikamera ei ole ihmeellinen, mutta sentään laadukkaampi kuin keskimääräiset Kiina-romut. Kuvanlaatu on upea, mikrofoni ensiluokkainen ja kuvaustiloja riittää 4K–720p-väliltä. Pääni on näemmä keskivertospeksejä suurempi, joten sangat kiristivät ja pusersivat lasit ikävästi alas nenänpäähän, jolloin aurinkokin helottaa suoraan silmään. 3Dkuvausta en ehtinyt kokeilla Skrollin aikataulujen puitteissa. Näpsä laite toimii sekä konsolin matka-akkuna että ohjainten latauskahvana, minkä lisäksi sillä lataa myös kännykän tai tabletin. Harmi vain, että pulmiakin riittää
Keskustelu kävi sen verran vilkkaana, että innostuin kehittämään jopa oman etälukuohjelmani Offline Orbitin, jota käytettiin pian EteläAmerikkaa myöten. Silti Epsilon Indiin – ja lukuisiin sen kaltaisiin purkkeihin vuodesta 1978 alkaen – syntyi sosiaalinen ekosysteemi, jollaisesta voi nykyisin vain unelmoida. Yksi syy 2019.2 20. Toisin kuitenkin kävi, kuten Skrollin toimittaja Ronja Koistinen kirjoitti jo Skrollin 2016.1 kolumnissaan. Suomessa voisin ehdottaa talvisotakorttia. Kun reaaliaikaisuus puuttui, viestintä oli harkitumpaa. BBS:t toimivat yleensä valmisohjelmistoilla, joita kutsuttiin purkkiohjelmistoiksi. Epsilon Indin, kuten muidenkin purkkien viestialueet oli jaettu useisiin alialueisiin tekniikasta viihteeseen. Leimallista messuilulle olivat myös asia-aiheet. Ronja kertoi, kuinka erään hänen seuraamansa yhdistyksen kerran niin aktiivinen webfoorumi näivettyi ja suljettiin, kun käyttäjät karkasivat Facebookiin. Etenkin keskustelu purkin viestialueilla – messuilu, kuten sitä kutsuttiin (messaging, ei tule sotkea Messengeriin ja mesettämiseen) – poikkesi suuresti nykypäivän sosiaalisesta mediasta. Puhuttiin asiaa asioista. Suunnitelmia toisesta linjasta oli, mutta ne eivät toteutuneet. Uutisryhmät ammensivat yliopistoja tiedemaailman perinteestä, jossa asiakeskustelua ja tietoa oltiin totuttu jakamaan kirjeissä ja ilmoitustauluilla (bulletin board). Janne Sirén Epsilon Indi BBS monia muitakin. Web-foorumit omaksuivat monia purkkien ja uutisryhmien ominaisuuksia, kuten viestiketjut ja visuaaliset sitaatit. Kyky ketjuttaa viestejä ja siteerata niitä onkin edelleen olennainen osa verkkokeskustelua. Etälukijoilla viestejä pystyi lukemaan ja kirjoittamaan ilman jatkuvaa modeemiyhteyttä. Ei ole sattumaa, että foorumiohjelmistojen nimissä vilahtelevat sanat ”bulletin board”. Epsilon Indi kävi viiden ja puolen vuoden aikana lävitse lukuisia ohjelmistoja: TransAmiga, Falcon, Excelsior! ja TechnoBBS Amigalla sekä SuperBBS ja BBBS PC:llä – kokeilin kulisseissa Sosiaalinen media on tehnyt verkkokeskustelusta epäsosiaalista. Epsilon Indiinkin oli vain yksi puhelinlinja. Käyttäjät ottivat purkkeihin yhteyttä omalla tietokoneellaan, hyödyntäen koneen ja puhelinlinjan väliin kytkettävää lisälaitetta: modeemia. KOLUMNI Y lläpidin vuosina 1991–1996 Epsilon Indi BBS -nimistä purkkia, bulletin board systemiä. Nykyvertailukohta voisi olla yksi SIM-kortti jokaiselle samanaikaiselle käyttäjälle. Ketjutettu keskustelu mahdollistaa pitkät ajatuksenvaihdot, jotka tosin johtavat kirjailija Mike Godwinin ”lain” (Godwin’s Law) mukaan aina lopulta Hitlertai natsiviittauksiin – Godwin teki havaintonsa uutisryhmissä. Purkeissa käyttäjät hakivat sekä jättivät viestejä, tiedostoja ja vuoropohjaisten pelien siirtoja. Web-foorumit Moderniin internet-aikaan purkkija uutisryhmäperinne jatkui web-sivujen foorumien muodossa 1990-luvun lopulla. Aina kun vaihdoin ohjelmistoa, Epsilon Indin viestialueet tyhjenivät ja käyttäjät joutuivat rekisteröitymään uudelleen. Jälkiviisaasti sen tehtävä kun vaikuttaa olleen lähinnä oman kokeilunhaluni tyydyttäminen. Lisäksi ainokainen linja oli poissa käytöstä silloin, kun järjestelmän ylläpitäjä eli minä käytin linjaa tai tietokonetta johonkin muuhun, mikä sekin oli tuolloin tavanomaista. Purkin samanaikaisten käyttäjien määrä riippui sen puhelinlinjojen määrästä. 2000-luvun alussa näyttikin siltä, että web-foorumi jatkaa sitä, mihin uutisryhmät ja purkit jäivät. Lähin vertailukohta purkeille olivat internetin tai Usenetin UUCPja sittemmin NNTP-protokollaa käyttävät uutisryhmät (newsgroups), jotka avattiin vuonna 1980. Tuolloin se vain tarkoitti lisäksi yhtä fyysistä kuparikaapelinippua, per käyttäjä, lähimpään puhelinjakamoon tai -keskukseen. Mikään web-foorumi ei ole ilmiölle immuuni, mutta etenkin pientä tai uutta web-foorumia on ollut 2010-luvulla vaikea saada lentämään. Silti muutamasta kutsutusta ja purkkilistojen kautta paikalle eksyneestä käyttäjästä kasvoi yhteisö, joka kerta toisensa jälkeen täytti viestialueet keskustelulla, vaihtoi tiedostoja ja pelasi pelejä, kuten Legend of the Red Dragonia. Keskustelu oli syvällistä tavalla, johon harvoin enää törmää. Suurimpiin purkkeihin mahtui kymmeniä tai satoja samanaikaisia käyttäjiä, mutta useimpiin vain yksi tai kaksi. BBS:t ja uutisryhmät On pienoinen ihme, että Epsilon Indiä käytti kukaan. Tarina on tuttu. Aikana ennen internetin valtavirtaistumista, BBS:t olivat suosittuja tekstija merkkigrafiikkapohjaisia ”verkkosivuja”, tietokoneita lankapuhelinlinjan päässä. Vaikka käyttäjät tulivat toisilleen tutuiksi, ja tietty ydinjoukko piti keskustelun vireänä, nykysomelle leimallista henkilöbrändäystä ei juuri esiintynyt – ei ainakaan demoskenen ulkopuolella. Siinä missä nykysome koostuu lähinnä emojeista ja tekstiviestin mittaisista huudahduksista, tyypilliset viestit Epsilon Indissä ja muissa purkeissa muistuttivat kirjeitä