Created by Petteri Nevavuori <petteri.nevavuori@gmail.com>
CHIO & FREEMAN: MACHINE LEARNING & SECURITY (2018)
Otsikot kirjan mukaan, muutoin suomeksi.
Tähän mennessä mallinnusta on lähestytty offline-näkökulmasta, eli eristetyissä laboratoriomaisissa ympäristöissä tapahtuvasti. Kyseessä on suuri harppaus, kun koulutusympäristöstä siirrytään tuotantoympäristöön koneoppimismenetelmien kohdalla. Skaalautuvuus, luotettavuus, tehokkuus ja merkityksellisyys saavat uudet mittasuhteet. Tässä luvussa keskitytään tähän aiheeseen, alkaen tuotantovalmiudesta, käyttöönotettavuudesta ja skaalautuvuudesta.
Abstraktien termien sijasta on hyvä käydä kypsien ja skaalautuvien koneoppimisjärjestelmien piirteitä ensin läpi, mitkä ovat yleisiä sovelluskontekstista riippumatta. Näitä piirteitä ovat:
Luonnollista kuitenkin on, että jokainen näistä piirteistä ei ole yhtä tärkeässä roolissa eri järjestelmissä.
Ensisijaisen tärkeää on, että turvallisuuskontekstiin kehitetyillä malleilla on äärimmäisen tarkat tarkkuusvaatimukset. Tuhannesosan virhe tarkottaa verkkoliikenteen pakettidatassa pahimmillaan tuhatta väärää tulosta minuutissa. Mikäli automatisoitua väärien negatiivisten tai positiivisten luokittelujen jatkotarkastusprosessia ei ole, on vaaditun ihmistyön määrä liian suuri, jotta järjestelmä tehostaisi mitään. Tärkeimmät edellämainituista piirteistä ovat turvallisuuskontekstissa:
Seuraavaksi käydään näitä piirteitä hieman tarkemmin läpi.
Kuten jo todettu sekä esimerkein osoitettu, datan laatu määrittelee mallinnuksen onnistumisen.
Tasapainoiset datajoukot ovat harvinaisia, mutta epätasapainoisten datajoukkojen käyttö siirtää datassa piilevän vääristymän myös sillä koulutettuun malliin. Ajan ja muidenkin rajoitusten takia kerätyt datajoukot ovat usein jossain määrin vääristyneitä sekä epätäydellisiä mallinnettavaan kokonaiskysymykseen nähden - yhteen datajoukkoon on miltei mahdotonta saada luotettavuuden takaava määrä havaintoja jokaisesta haittaohjelmasta.
Kirjassa esitellään termi populaatio, jolla viitataan kunkin tarkasteltavan asian kaikkiin mahdollisiin ilmentymiin. Koneoppimispuolella tässä kohdin on tavanomaista puhua piilevästä datantuottoprosessista, jota pyritään likimääräisesti mallintaamaan käyttämällä prosessiin liittyviä yksittäisiä havaintoja. Koska kaikki mahdollisia populaation ilmentymiä on mahdotonta kerätä datajoukkoon, on tyydyttävä vain rajatun osan koko prosessista tavoittavaan havaintojoukkoon. Tällöin datassa on itsessään elementit valintavääristymille, jolloin datajoukon keruupäätöksillä on jo lähtökohtaisesti rajattu kaikki sen ulkopuoliset havainnot mallinnuksen ulkopuolelle. Muitakin vääristymiä on, kuten havainto-odotus-vääristymä, jossa vain odotuksia vastaavat havainnot kerätään osaksi datajoukkoa.
Väärät havaintojen nimiöinnit vaikeuttava datantuottoprosessia tarkasti kuvaavan mallin kouluttamista. Mikäli datajoukko aina mallin suorituskyvyn mittaamiseen käytettyä validointidatajoukkoa myöten on väärin nimiöity, suoriutuu malli koulutusvaiheessa hyvin mutta heikosti tuotantokäytössä. Vääriä nimiöintejä tulee tyypillisesti esimerkiksi nimiöinnin joukkostamisen (crowsourcing) seurauksena. Toisaalta, myös nimiöintien jälkitarkastus vaatii suuria ihmisponnistuksia, vaikka koko joukosta valittaisiin vain satunnaisia havaintoja.
Esiteltyihin ongelmiin on joitakin ratkaisuja. Tärkeintä on ymmärtää, että ongelma on todellinen. Havaintoluokkien epätasapaino on eräs vääristymän ilmentymä, joskin ilmeinen sellainen. Datajoukon keruuvaiheen vääristymiä on vaikeampi havaita, joita voi välttää vain olemalla erityisen huolellinen kerättävän datan ymmärtämisessä, keruun rajoitusten huomioinnissa ja keruun tavoitteiden mielessä pitämisessä. Joskus ongelman huolellinen rajaus auttaa jo sekin. Ihmisvirheitä saadaan vähennettyä, kun nimiöintiin osallistuu monia toisistaan riippumattomia henkilöitä. Tällöin havaintokohtainen konsensus on eräs ilmentymä onnistuneesta nimiöinnistä.
Mallinnuksella voidaan myös vähintää datassa ilmenevän kohinan vaikutusta. Tällöin on käytettä vahvempaa regularisointia, jolloin malli pakotetaan oppimaan laaja-alaisemmin havaintoihin päteviä piirteitä vähentäen kohinan vaikutusta.
Puutteellinen data on yksi yleisempiä datan laadun ongelmia. Puutteet voivat johtua monista tekijöistä, kuten keruuprosessin tai lähtökohtaisten suunnittelupäätösten johdosta. Keskiössä on tällöin tapa, jolla puuttuvat arvot käsitellään ja esimerkiksi algoritmien välillä on eroja puuttuvien arvojen käsittelytavoissa. Puuttuvien arvojen korvaaminen ihmiselle ilmeisillä 0-arvoilla tai vastaavilla ei kuitenkaan välttämättä edesauta mallinnusta, joskin haitallinen vaikutus riippuu käytetystä algoritmista.
Esitellään puuttuvan datan ongelman ratkaisua esimerkillä, jossa käydään läpi puuttuvia arvoja sisältävää työntekijädataa. Aloitetaan lataamalla data.
import os
import pandas as pd
import numpy as np
df = pd.read_csv('datasets/employee_attrition_missing.csv')
df.head()
TotalWorkingYears | MonthlyIncome | Overtime | DailyRate | Label | |
---|---|---|---|---|---|
0 | NaN | 6725 | 0 | 498.0 | 0 |
1 | 12.0 | 2782 | 0 | NaN | 0 |
2 | 9.0 | 2468 | 0 | NaN | 0 |
3 | 8.0 | 5003 | 0 | 549.0 | 0 |
4 | 12.0 | 8578 | 0 | NaN | 0 |
Kuten huomata voi, puuttuu datajoukosta arvoja sieltä täältä. Katsotaanpa, mitä käy, kun tällaista dataa syötetään mallille.
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
df.drop('Label', axis=1), df.Label,
test_size=0.3, random_state=0)
clf = DecisionTreeClassifier(random_state=0).fit(X_train, y_train)
y_pred = clf.predict(X_test)
accuracy_score(y_test, y_pred)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-44-0dd8d693a57b> in <module> 7 test_size=0.3, random_state=0) 8 ----> 9 clf = DecisionTreeClassifier(random_state=0).fit(X_train, y_train) 10 11 y_pred = clf.predict(X_test) ~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/tree/tree.py in fit(self, X, y, sample_weight, check_input, X_idx_sorted) 799 sample_weight=sample_weight, 800 check_input=check_input, --> 801 X_idx_sorted=X_idx_sorted) 802 return self 803 ~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/tree/tree.py in fit(self, X, y, sample_weight, check_input, X_idx_sorted) 114 random_state = check_random_state(self.random_state) 115 if check_input: --> 116 X = check_array(X, dtype=DTYPE, accept_sparse="csc") 117 y = check_array(y, ensure_2d=False, dtype=None) 118 if issparse(X): ~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/utils/validation.py in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, warn_on_dtype, estimator) 571 if force_all_finite: 572 _assert_all_finite(array, --> 573 allow_nan=force_all_finite == 'allow-nan') 574 575 shape_repr = _shape_repr(array.shape) ~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/utils/validation.py in _assert_all_finite(X, allow_nan) 54 not allow_nan and not np.isfinite(X).all()): 55 type_err = 'infinity' if allow_nan else 'NaN, infinity' ---> 56 raise ValueError(msg_err.format(type_err, X.dtype)) 57 58 ValueError: Input contains NaN, infinity or a value too large for dtype('float32').
Virhehän sen kertoo. Eli käytetty menetelmä (puumalli) ei osaa käsitellä ei-numeerisia, puuttuvia arvoja. Puuttuvia arvoja voidaan käsitellä viidellä tapaa:
Mikäli jotkin rivit tai sarakkeet ovat hyvin puutteellisia, on niiden pudottaminen perusteltua. Tällöin käytettävissä ei ole riittävää määrää kontekstitietoa arvojen korvaamiseen millään järkevällä tavalla. Datan keruu on onnistuessaan ja resurssien niin salliessa hyvä keino, sillä silloin puuttuvat arvot korvataan lähtökohtaisen datantuottoprosessin tuotteilla. Ilmaisinarvolla korvaus käsiteltiinkin jo. Kaikkein mielenkiintoisin menetelmistä on viimeinen, korvaus dataan pohjaten. Keskitytäänpä seuraavaksi siihen.
Data-pohjaisella korvaamisella pyritään puuttuvat arvot korvaamaan siten, että esimerkiksi sarakekohtaiset jakaumat ei juuri muuttuisi. Tavoite on, ettei korvaus muuttaisi datan rakennetta. Toisin sanoen puuttuvat arvot korvataan joko keskiarvolla, mediaanilla tai moodilla, tilanteesta riippuen.
Kokeillaanpa siis sitä seuraavaksi. Aloitetaan tutkimalla sarakekohtaiset tunnusluvut.
print(f'Dataset size: {df.shape[0]}')
df.describe()
Dataset size: 1470
TotalWorkingYears | MonthlyIncome | Overtime | DailyRate | Label | |
---|---|---|---|---|---|
count | 1048.000000 | 1470.000000 | 1470.000000 | 894.000000 | 1470.000000 |
mean | 11.135496 | 6502.931293 | 0.282993 | 804.225951 | 0.161224 |
std | 7.706084 | 4707.956783 | 0.450606 | 403.289867 | 0.367863 |
min | 0.000000 | 1009.000000 | 0.000000 | 103.000000 | 0.000000 |
25% | 6.000000 | 2911.000000 | 0.000000 | 469.000000 | 0.000000 |
50% | 10.000000 | 4919.000000 | 0.000000 | 800.000000 | 0.000000 |
75% | 15.000000 | 8379.000000 | 1.000000 | 1179.000000 | 0.000000 |
max | 40.000000 | 19999.000000 | 1.000000 | 1499.000000 | 1.000000 |
Dataa puuttuu sarakkeista TotalWorkingYears
ja DailyRate
. Tutkitaanpa vielä niiden sarakkeiden tunnuslukuja.
import matplotlib.pyplot as plt
%matplotlib inline
print('TotalWorkingYears')
print(f' Mean: {df.TotalWorkingYears.mean()}')
print(f' Median: {df.TotalWorkingYears.median()}')
print(f' Mode(s): {", ".join([str(m) for m in df.TotalWorkingYears.mode()])}')
df.TotalWorkingYears.plot(kind='hist',bins=40)
plt.show()
print('DailyRate')
print(f' Mean: {df.DailyRate.mean()}')
print(f' Median: {df.DailyRate.median()}')
print(f' Mode(s): {", ".join([str(m) for m in df.DailyRate.mode()])}')
df.DailyRate.plot(kind='hist',bins=40)
plt.show()
TotalWorkingYears Mean: 11.135496183206106 Median: 10.0 Mode(s): 10.0
DailyRate Mean: 804.2259507829978 Median: 800.0 Mode(s): 530.0, 688.0, 1082.0
Sarakkeelle TotalWorkingYears
on käytettävä mediaania, mutta DailyRate
-sarakkeelle riittää keskiarvo.
from sklearn.impute import SimpleImputer
imputer_mean = SimpleImputer(strategy='mean')
imputer_median = SimpleImputer(strategy='median')
df.DailyRate = imputer_mean.fit_transform(df[['DailyRate']])
df.TotalWorkingYears = imputer_median.fit_transform(df[['TotalWorkingYears']])
df.head()
TotalWorkingYears | MonthlyIncome | Overtime | DailyRate | Label | |
---|---|---|---|---|---|
0 | 10.0 | 6725 | 0 | 498.000000 | 0 |
1 | 12.0 | 2782 | 0 | 804.225951 | 0 |
2 | 9.0 | 2468 | 0 | 804.225951 | 0 |
3 | 8.0 | 5003 | 0 | 549.000000 | 0 |
4 | 12.0 | 8578 | 0 | 804.225951 | 0 |
df.describe()
TotalWorkingYears | MonthlyIncome | Overtime | DailyRate | Label | |
---|---|---|---|---|---|
count | 1470.000000 | 1470.000000 | 1470.000000 | 1470.000000 | 1470.000000 |
mean | 10.809524 | 6502.931293 | 0.282993 | 804.225951 | 0.161224 |
std | 6.525995 | 4707.956783 | 0.450606 | 314.435912 | 0.367863 |
min | 0.000000 | 1009.000000 | 0.000000 | 103.000000 | 0.000000 |
25% | 7.000000 | 2911.000000 | 0.000000 | 670.000000 | 0.000000 |
50% | 10.000000 | 4919.000000 | 0.000000 | 804.225951 | 0.000000 |
75% | 11.000000 | 8379.000000 | 1.000000 | 922.000000 | 0.000000 |
max | 40.000000 | 19999.000000 | 1.000000 | 1499.000000 | 1.000000 |
Kuten odottaa saattaa, on keskiarvo vuosien kohdalla hieman muuttunut, mutta toisen sarakkeen kohdalla jäänyt muuttumattomaksi. Koitetaanpa mallinnusta uudestaan.
X_train, X_test, y_train, y_test = train_test_split(
df.drop('Label', axis=1), df.Label,
test_size=0.3, random_state=0)
clf = DecisionTreeClassifier(random_state=0).fit(X_train, y_train)
y_pred = clf.predict(X_test)
accuracy_score(y_test, y_pred)
0.7392290249433107
Koulutetut mallit ovat koneoppimismallien kovaa ydintä, jotka vaativat siksi laadullista ja toiminnallista tarkkailua. Etenkin kyberturvallisuuskontekstissa näihin asioihin on kiinnitettävä huomiota koulutuksen ja tuotantoonviennin jälkeenkin, jotta malli on varmasti ajantasainen ja haluttuun suorituskykyyn ulottuva. Mallin on oltava myös luotettava vihamielisestä ympäristöstään huolimatta.
Hyperparametrit ovat varsinaisiin algoritmin parametrien (esim. neuroverkon painot) optimointiin ja oppimiseen vaikuttavia arvoja. Niitä ei siis opita, vaan ne säätelevät oppimista. Näitä ovat esimerkiksi oppimiskerroin, regularisointikerroin, puumallin syvyys jne. Niitä on monia ja ne vaihtelevat mallin, optimointialgoritmin ja valittujen regularisointimenetelmien mukaan. Ne on määritettävä usein yrityksen ja erehdyksen kautta ja niiden keskinäiset suhteet selviävät, mikäli selviävät, samalla tavoin. Mitä enemmän säädettäviä hyperparametreja löytyy, sitä useampi koulutus on tehtävä parhaimman mallin tuottavien hyperparametrien määrittämiseksi. Muutokset datassa tai mallin rakenteessa voivat vaikuttaa merkittävästikin aiemmin sopineiden hyperparametrien validiuteen.
Mikäli säädettäviä hyperparametreja on vähän, voidaan optimaalisin hyperparamertien joukko valitaa raa'an voiman avulla koittamalla soveliaita arvoja kaikilla soveliailla arvojen sekoituksilla. Yleisempi tapa on kuitenkin käyttää erilaisia tekniikoita eri hyperparametrien arvojen tehokkaaseen läpikäyntiin parhaiten suorituvan mallin tuottavan hyperparametrijoukon löytämiseksi.
Eniten käytetyt hyperparametrien viritykseen käytetyt menetelmät of taulukkomainen hila- tai taulukkoetsintä (grid search) ja hyperparametrejä jakaumista satunnaisesti nostava satunnaisetsintä (random search).
Ensimmäinen menetelmistä, taulukkoetsintä, toimii, kunhan hyperparametrien määrä on vähäinen. Jos esimerkiksi hyperparametreja on vain kolme, ja kullekin etsitään parasta vaihtoehtoa kolmen arvon joukosta, on malli koulutettava $3^3=27$ kertaa jokaisen arvojoukon kokeilemiseksi. Kuten edellisestä voi nopeasti huomata, kasvaa etsintään vaadittujen koulutusten määrä eksponentiaalisesti sekä hyperparametrien että niiden kohdalla kokeiltavien arvojen kohdalla.
Toinen menetelmistä, satunnaisetsintä, toimii lähtökohtaisesti paremmin, kun tarvittavien koulutusten määrä räjähtäisi taulukkokoulutuksella käsiin. Tällöin kullekin hyperparametrille määritellään todennäköisyysjakauma, josta kullekin uudelle koulutukselle nostetaan uusi satunnainen arvo, hyperparametrikohtaisesti. Näin toimittaessa yrityksen ja erehdyksen menetelmä muuntuu lähemmäs tilastotieteellistä lähestymistä, jolla voidaan usein päästä myös parempaan tarkkuuteen hyperparametrien optimaalisimpien arvojen valinnassa.
Koska kyberturvallisuuden kohdalla käytettyjen mallien tarkkuus on lähes kaikki kaikessa, on järjestelmistä oltava riittävät takaisinkytkennät mallin toiminnan korjaamiseen ja kehittämiseen. Staattisten mallien kohtalona on ajan myötä heiketä, sillä datalla on taipumus elää ajan myötä. Siksi mukautumiskyky muuttuviin olosuhteisiin on tärkeä elementti mallin elinkaaren pidentämisessä. Yksinkertaisimmillaan toimiva takaisinkytkentä on mallin säätäminen jokaisen tutkitun väärin luokitellun havainnon kohdalla, jolla pyritään varmistumaan samankaltaisen virheen toistumattomuudesta tulevaisuudessa.
Edellämainittuun liittyy vahvasti kaksi koneoppimisen osa-aluetta, vahvistusoppiminen (reinforcement learning) ja aktiivinen oppiminen. Ensimmäinen liittyy oppimiseen palkitsemisen kautta ja siinä pääpaino on tuntemattoman tutkimisen ja tunnetun hyväksikäyttämisen tasapainottamisessa. Toinen on taas osittain ohjatun oppimisen erikoistapaus, jossa kaikista epävarmimmissa ennusteissa mallia autetaan eteenpäin ihmisvoimin.
A/B-testaus on puolestaan satunnaistettu testaustapa järjestelmän osien kokonaisvaikutuksen ymmärtämiseksi. A/B-testejä käytetään etenkin verkkosivuilla monitavoitteiseen optimointiin. Menetelmässä populaatio jaetaan satunnaisesti kahteen osiin ja osat näkevät eri versiot testattavasta järjestelmästä ja mittaamalla vaikutusten eroja sekä erojen tilastollista merkitsevyyttä. A-ryhmä näkee tavallisesti uuden version, kun taas B-ryhmä toimii kontrollina ja testauksen haasteena onkin tasapainoilla luotettavan uutta järjestelmää kuvaavan datamassan tuottamisen ja saavutetun suorituskyvyn ylläpitämisen välillä. Uudet mallit on hyvä testata aina näin, sillä pelkkä mallien jatkuva oppiminen ei takaa edes pysyvää suorituskykyä. Vihamielisissä ympäristöissä on kuitenkin oltava erityisen tarkka, sillä A/B-testauksen lähtökohtaoletuksena on muuttumaton datantuottoprosessi, minkä varjolla testiryhmien erot voidaan sälyttää versioeroille. Tämä ongelma palaa datan tasapainottamiseen ja luotettavaan dataan.
Oikean vastauksen saaminen tai hyvä suorituskyky kerran ei riitä, vaan tärkeintä on toistettavuus ja toistettava prosessi. Esimerkiksi luokittelun tulisi olla johdonmukaista ja ennustettavaa. Pelkän luokittelutarkkuuden tuijottaminen ei takaa sitä, että menetelmä otetaan käyttöön ja hyväksytään. Ihmisen on kyettävä ymmärtämään mallin toimintaa riittävästi, jotta hän kykenee luottamaan mallin tekemiin päätöksiin. Samoin menetelmien kehittäjät kykenevät korjaamaan ja jatkokehittämään mallia paremmin, kun ymmärrys mallin päätösperusteista on edes jokseenkin selkeä.
Koneoppimisessa toistettavuus liittyy olennaisesti ajassa muuttuvien mallien toiminnan toisintamiseen. Havaintokohtaisten luokittelutulosten muuttuessa on erityisen hyödyllistä kyetä vertaamaan aiempaa ja uusinta mallin versiota siten, että tulokset vastaavat kunkin version todellista toimintaa omana toiminta-aikanaan. Tämä mahdollistetaan esimerkiksi tallennuspistein, jotka ikäänkuin versioivat tuotantokäytössä olevaa mallia automaattisesti.
Selitettävyys on konseptina hankalampi, sillä sen aste on hyvin algoritmiriippuvaista. Jo itse kysymys on hankalammin määriteltävä: Mitä tarkoittaa selitettävä malli? Erään määritelmän mukaan selitettävän mallin tulokset on soveliain työkaluin selitettävissä auki siten, että mallia käyttävät ihmiset ymmärtävät mallin päätösten perusteet, kykenevät luottamaan mallin päätöksiin ja myös täten hallitsemaan mallia ymmärryksellä. Selitettävä malli tarjoaa siis riittävästi tietoa syy-seuraus-suhteiden määrittämiseksi. Tätä varten on kuitenkin kehitetty jo erinäisiä menetelmiä. Yksi tällainen on paikallisten tulkittavien malli-agnostisten selityksien (local interpretable model-agnostic explanations, LIME) menetelmä, joka syöttää mallille muunneltuja syötteitä ja pyrkii syötteiden ja mallin muutosten perusteella arvioimaan mallin päätösperusteita.
Monet tietoturvajärjestelmät sijaitsevat usein ruuhkaisissa järjestelmän osissa. Niiden on oltava siksi suorituskykyisiä ja vaatimiensa resurssien näkökulmasta tehokkaita käyttää. Jopa millisekunnin viive voi kaataa koko järjestelmän. Toisaalta niiden on kyettävä toimimaan myös äärimmäisen rasituksen alla.
Koneoppiminen on laskennallisesti raskasta. Tehokkaimpien mallien kohdalla niiden käyttö varsinaisessa palvelun dataputkessa voi kuitenkin aiheuttaa niin merkittävän viiveen, että palvelun käyttäjät alkavat kokea sen negatiivisena. Asynkroniset päätöksentekoprosessit auttavat tässä, jossa malleja ajetaan palvelun rinnalla muodostamatta kuitenkaan viivettä lisäävää pullonkaulaa. Näin toimittaessa on kuitenkin aina punnittava riskit - läpipäässyt, joskin jälkikäteen oikein luokiteltu haittavaikutus, voi kyetä aiheuttamaan mittavia vahinkoja.
Kuten ohjelmistokehityksessä, myös koneoppimisessa voidaan jossain määrin tarkastella mallin kokonaissuorituskykyä ositellusti ja jaotellusti. Keinoja ovat muun muassa:
Profilointi ja optimointi: Instrumentoimalla mallin toimintaa aina datan muunnoksista itse mallin sisäisiin datan käsittelyn prosesseihin voidaan löytää merkittäviä pullonkauloja, joiden poistaminen nopeuttaa menetelmän toimintaa huomattavasti. Instrumentointi tehdään tavallisesti profilointisovelluksella.
Algoritmin optimointi: Joskus algoritmia muokkaamalla tai sen vaihtamisella voidaan myös saavuttaa haluttu suorituskykyloikka jopa ilman, että mallin tarkkuus alenee merkittävästi.
Käytännön keinoja mallin nopeuttamiseksi on mm. piirreavaruuden kaventaminen vähemmän merkitsevien piirteiden poistamisella, puumallien käyttö, linearimallien käyttö, datan niin salliessa myös tukivektorikoneiden käyttö, rinnakkaisajetut neuroverkot ja likimääräiset k-syvyiset puut.
Kuten juuri mainittiinkin, on rinnakkaistus (parallelization) tehokas tapa lisätä käytetyn mallin suorituskykyä. Vaikkakaan kaikkia malleja ja datajoukkoja ei niin vain voida suoraan hajauttaa osiin (esim. tukivektorikone), mutta toiset menetelmät ovat luonteeltaan jo lähtökohtaisesti hajautettuja (esim. satunnaismetsä). Tässä aliluvussa ei mennä rinnakkaishajautuksen teoriaan, vaan esitellään tapoja toteuttaa se. Vaikka yksittäisten mallien suorius ei aina ole sellaisenaan rinnakkaistettavissa, esimerkiksi toisistaan riippumattomat hyperparametrien virityskoulutukset voidaan huoletta ajaa hajautetusti.
Apache Spark on kehys, jolla prosessien hajautus onnistuu hyvin. Koneoppimista varten on kehitetty spark-sklearn
, joka tarjoaa mm. hyperparametrien viritykseen hajautetun toteutuksen scikit-learn
-hilaetsintäöimplementaatiosta GirdSearchCV
. Toteutus tarjoaa tiettyjen ominaisuuksien nopeaa hajautettua käyttöönottoa, joskin tarjonta ylipäätään on hieman suppeahko ja samoin se edellyttää välimuistiin mahtuvia datajoukkoja.
Toinen varteenotettava, joskin enemmän tapauskohtaista kehitystyötä vaativa kehys, on pyspark
. Pelkän laskennan hajautuksen lisäksi tämä toteutus tarjoaa mahdollisuuden myös datan käsittelyn hajautukseen Pandas DataFrame-tietotyypin tapaan.
Käydäänpä seuraavaksi esimerkin avulla yksinkertaisen pyspark
tietueen luominen.
from pyspark.sql import types, SparkSession
spark = SparkSession.builder.master('local').appName('esimerkki').getOrCreate()
schema = types.StructType([
types.StructField('id', types.IntegerType(), nullable=False),
types.StructField('email', types.StringType(), nullable=False),
types.StructField('label', types.DoubleType(), nullable=False)])
df = spark.createDataFrame(
data=[{'id':1,'email':'testi.testaaja@testi.fi','label':0.8}],
schema=schema)
df.printSchema()
root |-- id: integer (nullable = false) |-- email: string (nullable = false) |-- label: double (nullable = false)
pyspark
tarjoaa myös paljon monia toiminnallisuuksia, kuten algoritmien implementaatioita ja datan esikäsittelytoimintoja. Tässä kohdin on kuitenkin mieleekkäämpää ohjata halukkaat tutustumaan toteutuksen dokumentaatioon syvemmän esimerkin sijasta.
"Koneoppiminen palveluna"-konsepti (machine learning as a service, MLaaS) on alati kasvava markkina-alue. Monet suuret toimijat, kuten Google, IBM, Amazon ja Microsoft tarjoavat jo omia pilvipohjaisia laskenta-alustoja valmiilla ratkaisuilla. Markkinassa toimii myös monia pienempiä ja keskisuuriakin yrityksiä, jotka pyrkivät tarjoamaan mallien koulutuksen, hallinnan ja tuotantokäytön helppokäyttöisenä palveluna. Pilvipalvelujen käyttö voi antaa alun kokeiluvaiheisiin kaivattua joustavuutta. Tietoturvakriittisten mallien ajaminen vain kolmannen osapuolen pilvipalvelussa on kuitenkin hieman kyseenalaista. Samoin palvelujen kustannusrakenne muodostuu usein laskenta-ajasta, jota koneoppimisessa muodostuu hyvinkin nopeasti. Siksi laskentakapasiteetin ostaminen on punnittava tarkoin sen ulkoistamisen kanssa, ennen suuremman mittaluokan koulutuksia.
Yrityksiin kehitetyt mallit ja menetelmät tapaavat elää yrityksissä kehittäjiää pidempään. Siksi myös koneoppimismenetelmien hallittavuus, muokattavuus, korjattavuus ja kehitettävyys on erityisen tärkeää luotettavan ja turvallisen toiminnan takaamiseksi. Toisaalta kääntäen, huonosti ylläpidettävät järjestelmät tavataan hylätä ja korvata toisilta.
Koska koneoppimismallit koulutetaan datalla ja siihen ne myös sovittuvat, on mallien käsittely ikään kuin datana perusteltua. Versioinnin ja mallit tuottavan koodin näkökulmasta ne ovat kuitenkin myös nähtävissä ohjelmistoina. Mallit hyperparametreineen ja ominaisuuksineen tulisi siksi olla tallennettuna versioidusti tietokantaan mm. helpon palauttamisen vuoksi, mutta samoin myös versionhallinnassa muutostenhallinnan näkökulmasta. Näin vanhatkin tulokset voidaan ottaa tarkasteluun, mikäli ilmenee tarve esimerkiksi auditoida toimintaa historiassa.
Käytännössä mallit saadaan helposti tallennettua binäärimuotoon käyttämällä pickle
-moduulia, josta useissa koneoppimiskehyksissä on myös olemassa jo valmiit omat toteutuksensa. Muitakin tapoja on, kuten XML-pohjaisen ennustemallien merkintäkielen (predictive model markup language, PMML) käyttö.
Tuotantoonviennin näkökulmasta mallien käyttö tulisi tehdä mahdollisimman idioottivarmaksi. Tapoja saavuttaa tämä on rakentaa malleille oma tarkkaan määritetty REST-rajapinta ja tarjota mallin toimintaa ikäänkuin alipalveluna palvelinpuolella. Mallien naittaminen osaksi muita järjestelmiä tekee sekä järjestelmistä että malleista jäykempiä ylläpitää.
Virhetilanteissa ohjelmistojen tulisi yleisestikin toimia huomaamattomasti. Esimerkiksi uusia teknologioita käyttävän monimutkaisen web-palvelun tulisi näkyä joka tapauksessa edes vanhempana ja yksinkertaisempana versiona, mikäli käyttäjän selain ei uutta palvelua tue. Samoin koneoppimismenetelmien virhetilanteissa tulee löytyä ennalta toteutetut varajärjestelmät malleineen ja prosesseineen. Tilannekohtaisesti virhetilanne voi jatkua liikenteen läpi päästävänä (fail open) tai estävänä (fail closed). Tietoturvakontekstissa jälkimmäinen takaa ainakin kohdejärjestelmän turvallisuuden.
Jopa uskonnollinen koodin ja konfiguraatioiden erottelu on edellytys tuotanto-ohjelmistoille. Käytännössä esimerkiksi tietoturva-asiantuntijat, joiden tuotantotasoisen koodin ohjelmointitausta ei välttämättä ole kovin vahva, vastaavat tietoturvajärjestelmien arvojen ja rajojen säätämisestiä. Siksi sekä ohjelmistot että mallit on suunniteltava siten, että niiden säätö onnistuu ilman ohjelmointiteknistä osaamista.
Vaikka turvallisuusjärjestelmien vaatimukset saatavuuden (uptime) ja resilienssin osalta ovat armottomat, tulee virhetilanteita välttämättä vastaan. Tieto näistä ja kokonaistilanteesta ylipäätään saadaan monitoroinnilla, jonka ytimessä on viisi osa-aluetta: mittarit, aikasarjat, havainnointijärjestelmä, visualisointi ja hälytysjärjestelmä. Näiden järjestelmien valuvika on kuitenkin jopa melko ilmeinen - mikäli monitorointi kaatuu, ei muista järjestelmistä saada enää tietoa. Siksi monitoroinnin kohdalla saatavuus ja resilienssi korostuvat entisestään.
Mallien monitoroinnissa tuotannon tulosten tarkkailu ja arviointi merkittävien muutosten kannalta on tärkeää, joskin haastavaa. Mallin oikeellisuuden varmentaminen edellyttää usein takaisinkytkentöjä joko toisiin datajoukkoihin tai ihmistä vaatimiin prosesseihin. Samoin dataa on monitoroitava muutosten osalta. Suuret muutokset ovat vähintäänkin mielenkiintoisia ja täten tutkittavia. Myös mallin ja datan väliset käsittelyprosessit vaativat jatkuvaa tarkkailua.
Seuraavaksi käydään läpi niitä asioita, joita tietoturvakontekstiin vietyjen mallien tulisi taata.
Turvallisuusjärjestelmät ovat jatkuvan vihamielisen vaikuttamisen riskin alla. Suojamuurit ovat epäsuora lupaus niiden takana olevasta arvokkaasta asiasta. Kuten kirjassa on tähänkin mennessä aihetta sivuttu, voidaan koneoppimismenetelmiin pyrkiä vaikuttamaan syöttämällä niille niiden päätösrajapintoja muuttavia havaintoja, jotta haitallinen liikenne saataisiin myöhemmin ujutettua huomaamatta sisään järjestelmään. Siksi mallit eivät saa oppia itsenäisesti ja sokeasti mukautumaan, vaan niitä on testattava esimerkiki jotain määrättyä ja normaalista liikenteestä erillistä datajoukkoa vasten. Samoin mallien muutoksista on oltava selvillä.
Yksityisyys on merkityksessään alati kasvava datan alue teknologioiden kehittyessä. Koneoppiminen edellyttää rikasta dataa, mikä vuorostaan voi vaarantaa yksityisyyden suojan. Data voi myös vuotaa malleista mm. rekonstruoinnin avulla. Mikäli hyökkääjä pääsee käsiksi malliin ja riittävään määrään mallinnettavaa ilmiötä kuvantavaan dataan, hyökkääjä voi takaisinmallintaa mallin päätösperusteet ja täten rakentaa ulostuloista arkaluontoiset lähtöpiirteet.
Koneen ja ihmisen yhteistyötä vaalivat ja kehittävät järjestelmät ovat tietoturvajärjestelmien päämäärä. Mikäli järjestelmän käyttäjäkokemus on huono, ei sitä aiemmin mainituista syistä joko oteta käyttöön tai osata hyödyntää koko potentiaalissaan. Tulosten perusteet ja mallin toiminnan läpinäkyvyys ovat lähtökohtaehtoja, jotka on tasapainotettava tietoturvallisuuden kanssa.