Mitä data-analyysi on? Data-analyysi tarkoitaa sitä, että datan pohjalta päätellään jotain uutta. Esimerkiksi mittausdatan perusteella voidaan todeta, että uusi lääkeaine näyttää laskevan verenpainetta.
No mitä se data on? Nykypäivänä data voi olla mitä tahansa, mikä on saatavissa digitaalisessa muodossa. Perinteisesti data on ollut tieteellisiä havaintoja, joita on tunnollisesti kirjattu ylös, vaikkapa jonkinlaiseksi taulukoksi. Näin on edellisen verenpaine-esimerkin tapauksessa. Nykyään kuitenkin tehdään jo paljon analyysiä esimerkiksi reaaliaikaisesta videokuvasta. Hyvä esimerkki tästä on vaikkapa robottilennokki, joka lentää pitkin voimalinjoja ja videokameran kuvan avulla analysoi, että milloin lumikuorma on vaarallisen suuri.
Mihin data-analyysia tarvitaan? Jos visionäärejä on uskominen, niin kohta ihan kaikkeen. Tieteessä datan analysointi on ollut keskeistä viimeistään 1900-luvun alusta alkaen. Tämä perinteinen tieteen ja asiantuntijatyön analytiikka on kuitenkin nyt saamassa rinnalleen uuden käyttäjäkunnan, kun arkisemmat data-analyysitarpeet ovat suoraan sanoen räjähtäneet. Facebookin ja Googlen kaltaiset internetajan yritykset vetävät uuden data-analytiikan nopeaa kehitystä. Yritysmaailmassa niin kutsuttu Big Data on tällä hetkellä hyvin kuuma aihe.
Joka tapauksessa on selvää, että tulevaisuudessa data-analyysiä tehdään paljon enemmän ja paljon laajemmin. Eli ei pelkästään tutkimuslaitoksissa, vaan myös tavallisissa yrityksissä, virastoissa ja järjestöissä. Jos opettelee ainakin perusasiat, niin saa melkoisen hyödyn tulevaisuutta ajatellen.
Ensiksi alustamme koneoppimisen työkalumme ja lataamme datan, jota työpajassa käsitellään. Alla oleva koodinpätkä tekee nämä kaksi asiaa.
Koodi ajetaan klikkaamalla harmaaseen laatikkoon, jolloin se tulee valituksi. Valitse ylävalikosta Cell -> Run ja koodi käynnistyy. Sen merkkinä ilmestyy In-riville tähti. Kun homma on valmis, niin alle ilmestyvät tulokset. Tässä tapauksessa pitäisi tulla tieto ladatun datan koosta sekä esimerkiksi muutamia merkkejä, joita data sisältää.
Jatkossa koodin voi ajaa myös näppärämmin painamalla Ctrl ja Enter.
In [ ]:
# Alustetaan koneoppimisen ympäristö (ohjelmakirjastot)
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from time import time
import numpy as np
from sklearn import random_projection, decomposition, manifold
import matplotlib.pyplot as plt
import seaborn as sns
from keras.datasets import mnist
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.utils import np_utils
# Aluksi tehdään aputyökalu kuvien piirtämistä varten
def plot_embedding(X, title=None, t0=None):
x_min, x_max = np.min(X, 0), np.max(X, 0)
X = (X - x_min) / (x_max - x_min)
plt.figure(figsize=(9,6))
plt.axis('off')
for i in range(X.shape[0]):
plt.text(X[i, 0], X[i, 1], str(y[i]),
color=plt.cm.Set1(y[i] / 10.),
fontdict={'weight': 'bold', 'size': 9})
if title is not None:
if t0 is not None:
plt.title("%s (%.2fs)" % (title, (time()-t0)))
else:
plt.title(title)
# Ladataan data (MNIST-tietokanta)
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X = X_train[:1024]
y = y_train[:1024]
# Raportoidaan työ tehdyksi
print('\nData ladattu onnistuneesti (MNIST).')
print('Datan palasten koko: train:',len(X_train),'test:',len(X_test))
Seuraavaksi katsotaan, että mistä datassa on kyse. Datamme on tietokanta ihmisten käsin kirjoittamista numeromerkeistä, eli sen pohjalta voi vaikkapa tutkia ihmisten tapoja piirtää numeroja tai rakentaa tekoälyn, joka ymmärtää käsin kirjoitettua tekstiä.
Datassa on liian paljon piirrettyjä merkkejä läpikäytäväksi, joten visualisoidaan niistä vain alkupäästä 1024 ensimmäistä.
In [ ]:
n_img_per_row = 32 # 32*32=1024
img = np.zeros((28 * n_img_per_row, 28 * n_img_per_row))
for i in range(n_img_per_row):
ix = 28 * i
for j in range(n_img_per_row):
iy = 28 * j
img[ix:ix + 28, iy:iy + 28] = X[i * n_img_per_row + j,:,:]
plt.figure(figsize=(9, 9))
plt.imshow(img)
plt.title('MNIST-data (1024 ensimmäistä merkkiä)')
ax=plt.axis('off')
Oppimisen kannalta pelkät merkit eivät vielä kerro paljon, vaan tarvitsemme myös tiedon siitä, että mitä oikeaa merkkiä mikäkin käsin piirretty merkki vastaa. Onneksi myös tämä tieto löytyy datasta valmiiksi. Alla oleva koodi piirtää 10 ensimmäistä merkkiä ja niiden luokittelun.
In [ ]:
# Piirretään alkupään merkkejä
pltsize=1
plt.figure(figsize=(10*pltsize, pltsize))
for i in range(10):
plt.subplot(1,10,i+1)
plt.axis('off')
plt.imshow(X_train[i,:,:])
plt.title(str(y_train[i]))
Oikeassa maailmassa tulee usein vastaan tilanteita, joissa meillä on kiinnostavaa dataa, mutta ei valmiiksi tietoa sen sisällöstä. Nyt tietokannan rakentaja on valmiiksi luokitellut ja tarkastanut merkit, mutta mitä jos näin ei olisi? Suuren tietokannan läpikäynti käsin on valtava urakka.
Pohdi näitä kysymyksiä hetki. Ne ovat perustavanlaatuisia haasteita data-analytiikan projekteissa ja myös hyvää pohjustusta seuraaville vaiheille.
Seuraavaksi pääsemme käyttämään varsinaista data-analytiikan menetelmää. Katsomme aluksi pelkästään kuvadataa ja emme käytä luokittelutietoa hyväksi. Mitä pelkillä kuvilla voi tehdä? Niitä voi esimerkiksi vertailla keskenään. Jokainen kuva on 28x28 kasa pikseleitä. Jos on esimerkiksi kaksi numeroa 0 esittävää kuvaa, niin niissä on todennäköisesti tummat pikselit varsin samoissa paikoissa molemmissa. Valitettavasti ihmisen on mahdotonta päässään tuollaista vertailua käsitellä, koska 28x28 = 784 eli vertailtavia numeroita on liikaa muistettavaksi tai hahmotettavaksi.
Data-analyysin menetelmien avulla on kuitenkin mahdollista hakea toistuvia piirteitä mm. niinkutsutun pääkomponenttianalyysin avulla. Menetelmästä käytetään yleensä sen englanninkielistä nimeä Primary Component Analysis (PCA). Pääkomponenttianalyysin idea on, että jokainen kuva esitetään pääkomponenttien summana. Esimerkiksi jos pääkomponentit ovat risti ja ympyrä, niin silloin voidaan sujuvasti esittää ristejä, palloja, ristejä ympyrän sisällä ja toki myös tyhjiä kuvia. Pääkomponenttien määrän voi päättää itse ja usein niitä halutaan kaksi, jotta data voidaan piirtää kahdessa ulottuvuudessa eli vaaka- ja pystyakselille (X ja Y).
Tehdään siis pääkomponenttianalyysi merkkidatalle. Luettavuuden vuoksi merkit piirretään sen luokan numerona, johon ne kuuluvat.
Saat tehtyä analyysin ja piirrettyä tuloksen alla olevalla koodilla. Käy tuloskuvaa ajatuksella läpi ja koita vastata kysymyksiin.
In [ ]:
# Tehdään PCA-analyysi
pca = decomposition.PCA(n_components=2)
X_pca = pca.fit_transform(X.reshape(-1,28*28))
# Ja piirretään analyysin tulos
plot_embedding(X_pca, "PCA-kuvaus")
Huomasit varmasti, että samat numerot sijoitettiin lähelle toisiaan. Näin kuuluu mennäkin, koska niissä on hyvin samanlaiset pikselit. Mutta myös toisiaan muistuttavat numerot, kuten 0 ja 8, tulivat lähelle.
Pääset itse kokeilemaan koneoppimisen toimintaa lähemmin tässä webbisivustossa: https://transcranial.github.io/keras-js/#/mnist-cnn
Sivustolla on koneoppimisohjelma, joka tunnistaa numeroita. Se on rakennettu saman datan päälle, vaikkakin hyvin kehittyneitä menetelmiä käyttäen. Kuinka hyvin sivuston ohjelma erottaa toisistaan esimerkiksi 0:n ja 8:n?
Data-analyysissa on aina pidettävä mielessä kriittinen lähestyminen ja tulosten merkitsevyyden pohtiminen. Yllä olevassa numeropilvessä on selvästi nähtävissä järjestystä, mutta toisaalta ihminen on taipuvainen näkemään järjestystä sielläkin, missä sitä ei ole - kuten vaikkapa lottoarvonnan tuloksissa.
Tilastollisen merkitsevyyden testaaminen voi pitkälle vietynä olla monimutkaistakin. Mutta emme lähde tässä laskemaan p-arvoja tai tekemään muuta tilastotiedettä, koska yksinkertainenkin ratkaisu on jo hyvä. Jos tarvitsee varmistua siitä, että näkemämme järjestys ei ole pelkästään mielikuvituksen tuottamaa ja oikeasti alla on pelkästään satunnaista sotkua, niin voimme katsoa vastaavaa visualisointia, mutta rakentaa sen puhtaasti satunnaisen datan päälle.
In [ ]:
# Tehdään satunnainen kuvaus
rp = random_projection.SparseRandomProjection(n_components=2, random_state=42)
X_projected = rp.fit_transform(X.reshape(-1,28*28))
# Piirretään kuvaus
plot_embedding(X_projected, "Satunnainen kuvaus")
Voidaanko sanoa, että tämä satunnainen sotku on selvästi eri tulos kuin aiemmin tekemämme pääkomponenttianalyysin tuottama?
Seuraavaksi pääsemme itse asiaan. Olemme nyt pöyhineet dataa ja päässeet varmuuteen, että se on järkevää ja analysoitavissa.
Rakennamme koneoppimismenetelmiä käyttäen nk. luokittelijan, joka kykenee oppimaan kuvien piirteet ja tunnistamaan niitä sen jälkeen. Nyt on syytä jälleen olla tarkkana: jos syötämme menetelmälle dataa, niin se varmasti osaa oppia ulkoa kyseisen datan kaikki yksityiskohdat. Mutta haluamme, että menetelmä "näkee metsän puilta", eli oppii tunnistamaan merkkien yleisiä hahmoja. Teemme siis koneoppimisen perustempun, eli jaamme datan kahteen osaan. Harjoitusdatalla opetetaan menetelmä, kun taas sen toimintaa testataan testidatalla. Koneoppijan tulee siis selvitä sellaisistakin kuvista, joita se ei ole aikaisemmin nähnyt. Tällä tavalla varmistetaan, että ei pelkästään opita ulkoa harjoitusdataa.
Alla oleva koodi tekee luokittelun ja tulostaa esimerkiksi 10 ensimmäistä luokiteltua merkkiä. Luokitteluun käytetään klassista koneoppimisen menetelmää, nk. päätöspuuta. Kuinka hyvin menetelmä pärjää?
In [ ]:
# Tehdään luokittelija
clf_dt = DecisionTreeClassifier()
clf_dt.fit(X_train.reshape(-1,28*28), y_train)
pred_dt = clf_dt.predict(X_test.reshape(-1,28*28))
# Piirretään tulokseksi alkupäästä luokituksia
pltsize=1
plt.figure(figsize=(10*pltsize, pltsize))
for i in range(10):
plt.subplot(1,10,i+1)
plt.axis('off')
plt.imshow(X_test[i,:,:])
plt.title(str(pred_dt[i]) + ' (' + str(y_test[i]) + ')')
# Raportoidaan tulokset
print('Luokiteltu', len(pred_dt), 'kuvaa, luokituksista oikein menneiden osuus on:', accuracy_score(y_test, pred_dt)*100, '%')
print('Alla kuvat ja analysoidut luokat, oikeat luokat ovat suluissa')
Kyseinen koneoppimismenetelmä tuntuu toimivan ihan kohtuullisesti, vaikka ei pärjääkään ihmiselle tässä tehtävässä.
Selvästikin päätöspuu osaa luokitella kuvia kohtuudella. Oikeassa elämässä esimerkiksi kuvadata ei ole aina priimalaatua. Mitä jos teemme tehtävästä asteen vaikeamman lisäämällä kuvaan häiriötä (kohinaa)? Lopputulos vastaa suunnilleen sitä, miltä näyttäisi vaikkapa huonolla kameralla vähässä valossa otettu kuva käsinkirjoitetusta tekstistä.
In [ ]:
# Lisätään kuviin häiriötä
noiselevel = 0.5 # TEHTÄVÄ: muuta tätä arvoa välillä 0.0 - 1.0 ja katso miten tulokset vaihtuvat
X_test_noisy = np.zeros((len(X_test), 28, 28))
for i in range(len(X_test)):
X_test_noisy[i,:,:] = (1-noiselevel)*X_test[i,:,:] + noiselevel*np.random.rand(28,28)*255
clf_dt_noisy = DecisionTreeClassifier()
clf_dt_noisy.fit(X_train.reshape(-1,28*28), y_train)
pred_dt_noisy = clf_dt_noisy.predict(X_test_noisy.reshape(-1,28*28))
# Piirretään tulokseksi alkupäästä luokituksia
pltsize=1
plt.figure(figsize=(10*pltsize, pltsize))
for i in range(10):
plt.subplot(1,10,i+1)
plt.axis('off')
plt.imshow(X_test_noisy[i,:,:])
plt.title(str(pred_dt_noisy[i]) + ' (' + str(y_test[i]) + ')')
# Raportoidaan tulokset
print('Luokiteltu', len(pred_dt_noisy), 'kuvaa, luokituksista oikein menneiden osuus on:', accuracy_score(y_test, pred_dt_noisy)*100, '%')
print('Alla kuvat ja analysoidut luokat, oikeat luokat ovat suluissa')
Kun sekoitetaan 50-50 alkuperäistä kuvaa ja kohinaa, niin koneoppija ei enää pärjää. Ihminen kyllä yhä kykenee numerot tunnistamaan. Missä menee koneoppijan kyky sietää kohinaa, paranisiko tilanne jos kohinan määrä tiputetaan kymmeneen prosenttiin, eli noiselevel=0.1? Palaa koodin ja kokeile erilaisia kohinan osuuksia sekä niiden vaikutusta.
Koneoppimisesta ei voi nykyään puhua mainitsemassa neuroverkkoja. Ne ovat tällä hetkellä pinnalla oleva, vaikkakin pitkän historian omaava, koneoppimisen suuntaus, jossa käytetyt menetelmät ovat saaneet inspiraationsa ihmisen hermojärjestelmästä. Dataa syötetään sisään keinotekoiseen digitaaliseen neuroverkkoon ja verkon yhteyksiä säädetään niin, että se oppii tuottamaan datasta haluttuja signaaleja ulos.
Toistamme äskeisen harjoituksen neuroverkkojen avulla. Ne ovat laskennallisesti raskaita, joten tällaisen kevyenkin mallin tekeminen vaatii aikansa.
Voit myös vaihtoehtoisesti mennä kokeilemaan neuroverkkoja interaktiiviselle webbisivustolle: http://playground.tensorflow.org/
Siellä voi säätää verkon asetuksia ja muotoa, sekä katsoa, että miten verkon kyky oppia dataa (joukko sinisiä ja oransseja palloja) muuttuu. Alaspäin skrollaamalla löytyy sivulta lisätietoa verkon käytöstä ja toiminnasta.
In [ ]:
# Alustetaan neuroverkko (malli)
model = Sequential()
# Luodaan neuroverkkoon sisäänmenokerros
model.add(Dense(20, input_dim=28*28))
model.add(Activation('relu'))
# Luodaan neuroverkkoon uusi kerros (piilokerros)
# TEHTÄVÄ: voit poistaa kommenttimerkit kolmen seuraavan rivin edestä ja myös säätää parametreja
#model.add(Dense(50, input_dim=28*28))
#model.add(Activation('relu'))
#model.add(Dropout(0.2))
# Luodaan neuroverkkoon uusi kerros (toinen piilokerros)
# TEHTÄVÄ: voit poistaa kommenttimerkit kolmen seuraavan rivin edestä ja myös säätää parametreja
#model.add(Dense(50))
#model.add(Activation('relu'))
#model.add(Dropout(0.2))
# Luodaan neuroverkkoon tuloskerros
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# Muotoillaan data neuroverkon ymmärtämään muotoon (nk. one-hot encoding)
nb_classes = 10
Y_train = np_utils.to_categorical(y_train, nb_classes)
Y_test = np_utils.to_categorical(y_test, nb_classes)
# Ajetaan neuroverkon opetus
nb_epoch = 4
print('Neuroverkon opetus käynnissä, odota...\n')
history = model.fit(X_train.reshape((-1,28*28)), Y_train, nb_epoch=nb_epoch, batch_size=32, verbose=2)
scores = model.evaluate(X_test.reshape((-1,28*28)), Y_test, verbose=0)
print('\nOpetus valmis!')
Nyt on neuroverkko luotu. Seuraavaksi käytetään sitä dataan ja katsotaan kuinka se pärjää?
In [ ]:
# Luokitellaan dataa neuroverkolla
predictions = model.predict(X_test.reshape((-1,28*28)))
rounded = np.argmax(predictions, axis=1)
# Piirretään tulokseksi alkupäästä luokituksia
pltsize=1
plt.figure(figsize=(10*pltsize, pltsize))
for i in range(10):
plt.subplot(1,10,i+1)
plt.axis('off')
plt.imshow(X_test[i,:,:])
plt.title(str(rounded[i]) + ' (' + str(y_test[i]) + ')')
# Raportoidaan tulokset
print('Luokiteltu', len(rounded), 'kuvaa, luokituksista oikein menneiden osuus on:', accuracy_score(y_test, rounded)*100, '%')
print('Alla kuvat ja analysoidut luokat, oikeat luokat ovat suluissa')
Saatko parannettua verkon toimintaa lisäämällä kerroksia? Se onnistuu kohdista, jotka on merkitty TEHTÄVÄ.
In [ ]: