{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "*Created by Petteri Nevavuori <>*\n", "\n", "---\n", "\n", "**CHIO & FREEMAN: MACHINE LEARNING & SECURITY (2018)**
\n", "*Otsikot kirjan mukaan, muutoin suomeksi.*" ] }, { "cell_type": "markdown", "metadata": { "toc": true }, "source": [ "

Sisällysluettelo

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Production Systems\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining Machine Learning System Maturity and Scalability\n", "\n", "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:\n", "\n", " - *Datan laatu*: Vääristymätöntä, todennettavaa, järkevästi käsiteltyä\n", " - *Mallin laatu*: Hyperparametrien optimointi tehokasta, A/B testattuu, ajankohtaiset takaisinkytkennät, toistettavat ja selitettävät tulokset\n", " - *Suorituskyky*: Alhainen käyttöviive, skaalautuvuus, automatisoitu datan keruu ja käsittely\n", " - *Ylläpito*: Versioitu, sulava tuotantoonvienti, hallittu heikentyminen, helppo säädettävyys ja viritettävyys, hyvin dokumentoitu\n", " - *Seuranta*: Järjestelmän tila, suorituskyky ja datan jakaumat seurattavissa hälytyksin\n", " - *Luotettavuus*: Vakaa vihamielisissä ympäristöissä, data ja yksityisyys suojattua sekä turvattua\n", " \n", "Luonnollista kuitenkin on, että jokainen näistä piirteistä ei ole yhtä tärkeässä roolissa eri järjestelmissä." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's Important for Security Machine Learning Systems?\n", "\n", "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:\n", "\n", " - Vääristymätön ja todennettava data\n", " - Mallin tehokas optimointi ja A/B-testattavuus\n", " - Skaalautuvuus ja tehokas automatisoitu datan keruu\n", " - Sulava tuotantoonvienti, helppo muokattavuus, dokumentoinnin kattavuus\n", " - Järjestelmän tilan ja suorituskyvyn seurattavuus\n", " - Datan yksityisyys suojattu\n", " \n", "Seuraavaksi käydään näitä piirteitä hieman tarkemmin läpi." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Quality\n", "\n", "Kuten jo todettu sekä esimerkein osoitettu, datan laatu määrittelee mallinnuksen onnistumisen." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem: Bias in Datasets\n", "\n", "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.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem: Label Inaccuracy\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solutions: Data Quality\n", "\n", "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ä.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem: Missing Data\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solutions: Missing Data\n", "\n", "Esitellään puuttuvan datan ongelman ratkaisua esimerkillä, jossa käydään läpi puuttuvia arvoja sisältävää työntekijädataa. Aloitetaan lataamalla data." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TotalWorkingYearsMonthlyIncomeOvertimeDailyRateLabel
0NaN67250498.00
112.027820NaN0
29.024680NaN0
38.050030549.00
412.085780NaN0
\n", "
" ], "text/plain": [ " TotalWorkingYears MonthlyIncome Overtime DailyRate Label\n", "0 NaN 6725 0 498.0 0\n", "1 12.0 2782 0 NaN 0\n", "2 9.0 2468 0 NaN 0\n", "3 8.0 5003 0 549.0 0\n", "4 12.0 8578 0 NaN 0" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os\n", "import pandas as pd\n", "import numpy as np\n", "\n", "df = pd.read_csv('datasets/employee_attrition_missing.csv')\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Kuten huomata voi, puuttuu datajoukosta arvoja sieltä täältä. Katsotaanpa, mitä käy, kun tällaista dataa syötetään mallille." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Input contains NaN, infinity or a value too large for dtype('float32').", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 7\u001b[0m test_size=0.3, random_state=0)\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mclf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDecisionTreeClassifier\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my_train\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0my_pred\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mclf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX_test\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/tree/tree.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, sample_weight, check_input, X_idx_sorted)\u001b[0m\n\u001b[1;32m 799\u001b[0m \u001b[0msample_weight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msample_weight\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 800\u001b[0m \u001b[0mcheck_input\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcheck_input\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 801\u001b[0;31m X_idx_sorted=X_idx_sorted)\n\u001b[0m\u001b[1;32m 802\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 803\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/tree/tree.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, sample_weight, check_input, X_idx_sorted)\u001b[0m\n\u001b[1;32m 114\u001b[0m \u001b[0mrandom_state\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_random_state\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcheck_input\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 116\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mDTYPE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_sparse\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"csc\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 117\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mensure_2d\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 118\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0missparse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(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)\u001b[0m\n\u001b[1;32m 571\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mforce_all_finite\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 572\u001b[0m _assert_all_finite(array,\n\u001b[0;32m--> 573\u001b[0;31m allow_nan=force_all_finite == 'allow-nan')\n\u001b[0m\u001b[1;32m 574\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 575\u001b[0m \u001b[0mshape_repr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_shape_repr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/anaconda3/envs/pytorch/lib/python3.7/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36m_assert_all_finite\u001b[0;34m(X, allow_nan)\u001b[0m\n\u001b[1;32m 54\u001b[0m not allow_nan and not np.isfinite(X).all()):\n\u001b[1;32m 55\u001b[0m \u001b[0mtype_err\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'infinity'\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mallow_nan\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m'NaN, infinity'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 56\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg_err\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtype_err\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdtype\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 57\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Input contains NaN, infinity or a value too large for dtype('float32')." ] } ], "source": [ "from sklearn.model_selection import train_test_split\n", "from sklearn.tree import DecisionTreeClassifier\n", "from sklearn.metrics import accuracy_score\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(\n", " df.drop('Label', axis=1), df.Label,\n", " test_size=0.3, random_state=0)\n", "\n", "clf = DecisionTreeClassifier(random_state=0).fit(X_train, y_train)\n", "\n", "y_pred = clf.predict(X_test)\n", "accuracy_score(y_test, y_pred)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Virhehän sen kertoo. Eli käytetty menetelmä (puumalli) ei osaa käsitellä ei-numeerisia, puuttuvia arvoja. Puuttuvia arvoja voidaan käsitellä viidellä tapaa:\n", "\n", "- Puuttuvia arvoja sisältävät rivit tiputetaan pois.\n", "- Puuttuvia arvoja sisältävät sarakkeet tiputetaan pois.\n", "- Puuttuvat aukot täytetään datan lisäkeruulla.\n", "- Puuttuvat arvot korvataan tätä ilmaisevalla arvolla (0, -1, jne.)\n", "- Korvaamalla puuttuvat arvot datapohjaisin keinoin (*imputation*)\n", "\n", "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.\n", "\n", "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. \n", "\n", "Kokeillaanpa siis sitä seuraavaksi. Aloitetaan tutkimalla sarakekohtaiset tunnusluvut." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Dataset size: 1470\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TotalWorkingYearsMonthlyIncomeOvertimeDailyRateLabel
count1048.0000001470.0000001470.000000894.0000001470.000000
mean11.1354966502.9312930.282993804.2259510.161224
std7.7060844707.9567830.450606403.2898670.367863
min0.0000001009.0000000.000000103.0000000.000000
25%6.0000002911.0000000.000000469.0000000.000000
50%10.0000004919.0000000.000000800.0000000.000000
75%15.0000008379.0000001.0000001179.0000000.000000
max40.00000019999.0000001.0000001499.0000001.000000
\n", "
" ], "text/plain": [ " TotalWorkingYears MonthlyIncome Overtime DailyRate Label\n", "count 1048.000000 1470.000000 1470.000000 894.000000 1470.000000\n", "mean 11.135496 6502.931293 0.282993 804.225951 0.161224\n", "std 7.706084 4707.956783 0.450606 403.289867 0.367863\n", "min 0.000000 1009.000000 0.000000 103.000000 0.000000\n", "25% 6.000000 2911.000000 0.000000 469.000000 0.000000\n", "50% 10.000000 4919.000000 0.000000 800.000000 0.000000\n", "75% 15.000000 8379.000000 1.000000 1179.000000 0.000000\n", "max 40.000000 19999.000000 1.000000 1499.000000 1.000000" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(f'Dataset size: {df.shape[0]}')\n", "df.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dataa puuttuu sarakkeista `TotalWorkingYears` ja `DailyRate`. Tutkitaanpa vielä niiden sarakkeiden tunnuslukuja." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TotalWorkingYears\n", " Mean: 11.135496183206106\n", " Median: 10.0\n", " Mode(s): 10.0\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "DailyRate\n", " Mean: 804.2259507829978\n", " Median: 800.0\n", " Mode(s): 530.0, 688.0, 1082.0\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAD8CAYAAAB6paOMAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4wLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvqOYd8AAAE2RJREFUeJzt3X+wZ3Vdx/HnywUF1FqIi27CdsFhLMZJoCuD0Q/DHxEaSmMl4xSZufbDKfvpgk3pVDNa/kinRt0C3QxNRFFCjDayHGcacNEFFoFA3RTZ2LUyRA0E3v3xPRev6917v9/dPd8f+3k+Zr5zz/mc8/2e9/3Mvfd1zzmfc06qCklSux4x6QIkSZNlEEhS4wwCSWqcQSBJjTMIJKlxBoEkNc4gkKTGGQSS1DiDQJIad8ikCxjG0UcfXfPz85MuQ5JmyvXXX/+lqppbbb2ZCIL5+Xm2bt066TIkaaYk+Y9h1vPQkCQ1ziCQpMYZBJLUOINAkhpnEEhS4wwCSWqcQSBJjTMIJKlxBoEkNW4mriyWpP01v/HDe12247XPGWMl08c9AklqnEEgSY0zCCSpcQaBJDXOIJCkxhkEktS43oIgyWFJrktyQ5Kbk7yma39nks8l2da9Tu6rBknS6vq8juA+4MyqujfJocDHk3ykW/a7VXVZj9uWJA2ptyCoqgLu7WYP7V7V1/YkSfum13MESdYk2QbsArZU1bXdoj9JcmOSNyV5VJ81SJJW1msQVNWDVXUycCxwWpInAxcA3ws8FTgKeOVy702yIcnWJFt3797dZ5mS1LSxjBqqqi8D/wKcVVU7a+A+4B3AaXt5z6aqWqiqhbm5uXGUKUlN6nPU0FyStd304cAzgVuTrOvaAjwf2N5XDZKk1fU5amgdsDnJGgaBc2lVXZnkn5PMAQG2Ab/cYw2SpFX0OWroRuCUZdrP7GubkqTReWWxJDXOIJCkxhkEktQ4g0CSGmcQSFLjDAJJapxBIEmNMwgkqXEGgSQ1ziCQpMYZBJLUOINAkhpnEEhS4wwCSWqcQSBJjTMIJKlxBoEkNa7PR1WqMfMbP7zi8h2vfc6YKhkfv+dvdzB+zwc79wgkqXG9BUGSw5Jcl+SGJDcneU3XfnySa5PcnuS9SR7ZVw2SpNX1uUdwH3BmVT0FOBk4K8npwOuAN1XVicD/AC/psQZJ0ip6C4IauLebPbR7FXAmcFnXvhl4fl81SJJW1+s5giRrkmwDdgFbgM8AX66qB7pV7gSe0GcNkqSV9TpqqKoeBE5Osha4HPi+5VZb7r1JNgAbANavX99bjZKmhyOSJmMso4aq6svAvwCnA2uTLAbQscBde3nPpqpaqKqFubm5cZQpSU3qc9TQXLcnQJLDgWcCtwAfBV7QrXY+8KG+apAkra7PQ0PrgM1J1jAInEur6soknwb+LskfA58CLuqxBknSKnoLgqq6EThlmfbPAqf1tV1J0mi8xYQOCp5klPadt5iQpMYZBJLUOINAkhpnEEhS4wwCSWqco4Y0No7skaaTewSS1DiDQJIaZxBIUuMMAklqnEEgSY1z1JBmwmojjqT90fqINvcIJKlxBoEkNc4gkKTGGQSS1DiDQJIaZxBIUuN6C4IkxyX5aJJbktyc5De69lcn+WKSbd3r7L5qkCStrs/rCB4AfruqPpnkscD1SbZ0y95UVa/vcduSpCH1FgRVtRPY2U1/JcktwBP62p4kad+M5RxBknngFODarunlSW5McnGSI8dRgyRpeb3fYiLJY4D3A6+oqnuSvBX4I6C6r28AfnGZ920ANgCsX79+n7e/P7cmmORl5SvVfbBe7u5tJKTJ6HWPIMmhDELgkqr6AEBV3V1VD1bVQ8BfAact996q2lRVC1W1MDc312eZktS0PkcNBbgIuKWq3rikfd2S1c4FtvdVgyRpdX0eGjoD+DngpiTburYLgfOSnMzg0NAO4GU91iBJWkWfo4Y+DmSZRVf1tU1J0uh8HkGDWjwRrYODP7v98BYTktS4oYIgyZP7LkSSNBnD7hG8Lcl1SX41ydpeK5IkjdVQQVBVPwS8CDgO2Jrk3Ume1WtlkqSxGPocQVXdDvw+8ErgR4G3JLk1yU/1VZwkqX9DjRpK8v3Ai4HnAFuAn+zuKvrdwL8BH+ivROngtT+jYKb19iktjuyZ9e952OGjf8HgdhAXVtXXFxur6q4kv99LZZKksRg2CM4Gvl5VDwIkeQRwWFV9rare1Vt1kqTeDXuO4J+Aw5fMH9G1SZJm3LBBcFhV3bs4000f0U9JkqRxGjYIvprk1MWZJD8AfH2F9SVJM2LYcwSvAN6X5K5ufh3ws/2UJEkap6GCoKo+keR7gScxuKPorVX1jV4rkySNxSh3H30qMN+955QkVNXf9FKVJGlshr2g7F3AE4FtwINdcwEGgSTNuGH3CBaAk6qq+ixGkjR+wwbBduDxwM4ea5k5q13iPwuXlu/pYPyeZtX+3EJiWh2M39PBYNggOBr4dJLrgPsWG6vqnF6qkiSNzbBB8OpRPzjJcQzOITweeAjYVFVvTnIU8F4GJ553AD9TVf8z6udLkg6MYZ9H8K8M/mgf2k1/AvjkKm97APjtqvo+4HTg15KcBGwErqmqE4FrunlJ0oQM+6jKlwKXAW/vmp4AfHCl91TVzqr6ZDf9FeCW7n3PAzZ3q20Gnj962ZKkA2XYW0z8GnAGcA88/JCaY4bdSJJ54BTgWuBxVbWz+5ydo3yOJOnAG/YcwX1VdX8SAJIcwuA6glUleQzwfuAVVXXP4mcM8b4NwAaA9evXD1nmdHGExPSY9QeHaHWz+vs2DSP1ht0j+NckFwKHd88qfh/w96u9KcmhDELgkqpafIrZ3UnWdcvXAbuWe29VbaqqhapamJubG7JMSdKohg2CjcBu4CbgZcBVDJ5fvFcZ/Ot/EXBLVb1xyaIrgPO76fOBD41SsCTpwBr2pnMPMXhU5V+N8NlnAD8H3JRkW9d2IfBa4NIkLwE+D/z0CJ8pSTrAhr3X0OdY5pxAVZ2wt/dU1ccZ3Kl0Oc8YqjpJUu9GudfQosMY/Bd/1IEvZ7rM6sknSQfWwf63YNgLyv5ryeuLVfXnwJk91yZJGoNhDw2dumT2EQz2EB7bS0WSpLEa9tDQG5ZMP0B3j6ADXo0kaeyGHTX0Y30XIkmajGEPDf3WSsv3uE5AkjRDRhk19FQGF4MB/CTwMeALfRSl/dPnCIeDffTEclr8ntWWUR5Mc2p3F1GSvBp4X1X9Ul+FSZLGY9hbTKwH7l8yfz+DB8tIkmbcsHsE7wKuS3I5gyuMz2Xw9DFJ0owbdtTQnyT5CPDDXdOLq+pT/ZUlSRqXYQ8NARwB3FNVbwbuTHJ8TzVJksZo2OGjf8hg5NCTgHcAhwJ/y+AOo9JMc5SV+jQLPwPD7hGcC5wDfBWgqu7CW0xI0kFh2CC4v6qK7lbUSR7dX0mSpHEaNgguTfJ2YG2SlwL/xGgPqZEkTalhRw29vntW8T0MzhP8QVVt6bUySdJYrBoESdYAV1fVMwH/+E+BWTj5JGl2rHpoqKoeBL6W5DvHUI8kacyGvbL4/xg8hH4L3cghgKr69b29IcnFwHOBXVX15K7t1cBLgd3dahdW1VX7ULck6QAZNgg+3L1G8U7gL/j2W1G8qapeP+JnSZJ6smIQJFlfVZ+vqs2jfnBVfSzJ/L4WJkkaj9XOEXxwcSLJ+w/QNl+e5MYkFyc58gB9piRpH60WBFkyfcIB2N5bgScCJwM7+dZnIX/rhpMNSbYm2bp79+69rSZJ2k+rBUHtZXqfVNXdVfVgVT3E4IK001ZYd1NVLVTVwtzc3P5uWpK0F6udLH5KknsY7Bkc3k3TzVdVfccoG0uyrqp2drPnAttHqlaSdMCtGARVtWZfPzjJe4CnA0cnuRP4Q+DpSU5msHexA3jZvn6+JOnAGHb46Miq6rxlmi/qa3uSpH0zyoNpJEkHIYNAkhpnEEhS4wwCSWqcQSBJjTMIJKlxBoEkNc4gkKTGGQSS1DiDQJIaZxBIUuMMAklqXG83nZM0u+Y3jvqIcs0y9wgkqXEGgSQ1ziCQpMYZBJLUOINAkhpnEEhS43oLgiQXJ9mVZPuStqOSbElye/f1yL62L0kaTp97BO8EztqjbSNwTVWdCFzTzUuSJqi3IKiqjwH/vUfz84DN3fRm4Pl9bV+SNJxxnyN4XFXtBOi+HjPm7UuS9jC1J4uTbEiyNcnW3bt3T7ocSTpojTsI7k6yDqD7umtvK1bVpqpaqKqFubm5sRUoSa0ZdxBcAZzfTZ8PfGjM25ck7aHP4aPvAf4NeFKSO5O8BHgt8KwktwPP6uYlSRPU222oq+q8vSx6Rl/blCSNbmpPFkuSxsMgkKTGGQSS1DiDQJIaZxBIUuMMAklqnEEgSY0zCCSpcQaBJDXOIJCkxhkEktQ4g0CSGmcQSFLjDAJJapxBIEmNMwgkqXEGgSQ1ziCQpMYZBJLUuN6eWbySJDuArwAPAg9U1cIk6pAkTSgIOj9WVV+a4PYlSXhoSJKaN6kgKOAfk1yfZMOEapAkMblDQ2dU1V1JjgG2JLm1qj62dIUuIDYArF+/fhI1Sget+Y0fnnQJmiIT2SOoqru6r7uAy4HTlllnU1UtVNXC3NzcuEuUpGaMPQiSPDrJYxengWcD28ddhyRpYBKHhh4HXJ5kcfvvrqp/mEAdkiQmEARV9VngKePeriRpeQ4flaTGGQSS1DiDQJIaZxBIUuMMAklqnEEgSY0zCCSpcQaBJDXOIJCkxhkEktQ4g0CSGmcQSFLjDAJJapxBIEmNMwgkqXEGgSQ1ziCQpMYZBJLUOINAkho3kSBIclaS25LckWTjJGqQJA2MPQiSrAH+EvgJ4CTgvCQnjbsOSdLAJPYITgPuqKrPVtX9wN8Bz5tAHZIkJhMETwC+sGT+zq5NkjQBh0xgm1mmrb5tpWQDsKGbvTfJbb1WNZqjgS9NuogRWG9/ZqlWmK16Z6lW6KnevG6/3v49w6w0iSC4EzhuyfyxwF17rlRVm4BN4ypqFEm2VtXCpOsYlvX2Z5Zqhdmqd5Zqhdmrd6lJHBr6BHBikuOTPBJ4IXDFBOqQJDGBPYKqeiDJy4GrgTXAxVV187jrkCQNTOLQEFV1FXDVJLZ9gEzlIasVWG9/ZqlWmK16Z6lWmL16H5aqbztPK0lqiLeYkKTGGQTLSHJcko8muSXJzUl+o2s/KsmWJLd3X4/s2pPkLd0tM25McuoEal6T5FNJruzmj09ybVfre7sT8yR5VDd/R7d8fgK1rk1yWZJbuz5+2rT2bZLf7H4Gtid5T5LDpqlvk1ycZFeS7UvaRu7LJOd369+e5Pwx1/tn3c/CjUkuT7J2ybILunpvS/LjS9rHcpua5epdsux3klSSo7v5iffvPqsqX3u8gHXAqd30Y4F/Z3A7jD8FNnbtG4HXddNnAx9hcI3E6cC1E6j5t4B3A1d285cCL+ym3wb8Sjf9q8DbuukXAu+dQK2bgV/qph8JrJ3GvmVwoePngMOX9OkvTFPfAj8CnApsX9I2Ul8CRwGf7b4e2U0fOcZ6nw0c0k2/bkm9JwE3AI8Cjgc+w2CAyZpu+oTu5+cG4KRx1du1H8dgwMt/AEdPS//u8/c56QJm4QV8CHgWcBuwrmtbB9zWTb8dOG/J+g+vN6b6jgWuAc4Erux+EL+05JfracDV3fTVwNO66UO69TLGWr+j++OaPdqnrm/55lXwR3V9dSXw49PWt8D8Hn9YR+pL4Dzg7Uvav2W9vuvdY9m5wCXd9AXABUuWXd3198N9vtx646gXuAx4CrCDbwbBVPTvvrw8NLSKbvf+FOBa4HFVtROg+3pMt9qkb5vx58DvAQ91898FfLmqHlimnodr7Zb/b7f+uJwA7Abe0R3K+uskj2YK+7aqvgi8Hvg8sJNBX13P9PbtolH7ctI/v0v9IoP/qmFK601yDvDFqrphj0VTWe8wDIIVJHkM8H7gFVV1z0qrLtM2luFYSZ4L7Kqq64esZ2K1dg5hsKv91qo6Bfgqg8MXezPJvj2SwQ0Rjwe+G3g0g7vm7q2eSfftavZW31TUneRVwAPAJYtNy6w20XqTHAG8CviD5RYv0zY1/bsSg2AvkhzKIAQuqaoPdM13J1nXLV8H7Orah7ptRk/OAM5JsoPBnVzPZLCHsDbJ4nUiS+t5uNZu+XcC/z2mWhe3f2dVXdvNX8YgGKaxb58JfK6qdlfVN4APAD/I9PbtolH7cpJ9DAxOpgLPBV5U3fGTFeqaZL1PZPCPwQ3d79yxwCeTPH6Fuibev6sxCJaRJMBFwC1V9cYli64AFs/4n8/g3MFi+893owZOB/53cde8b1V1QVUdW1XzDE5Q/nNVvQj4KPCCvdS6+D28oFt/bP+dVNV/Al9I8qSu6RnAp5nCvmVwSOj0JEd0PxOLtU5l3y4xal9eDTw7yZHdXtCzu7axSHIW8ErgnKr62pJFVwAv7EZjHQ+cCFzHBG9TU1U3VdUxVTXf/c7dyWBgyX8ypf07lEmfpJjGF/BDDHbdbgS2da+zGRzvvQa4vft6VLd+GDxs5zPATcDChOp+Ot8cNXQCg1+aO4D3AY/q2g/r5u/olp8wgTpPBrZ2/ftBBiMpprJvgdcAtwLbgXcxGMEyNX0LvIfB+YtvMPij9JJ96UsGx+bv6F4vHnO9dzA4hr74u/a2Jeu/qqv3NuAnlrSfzWA032eAV42z3j2W7+CbJ4sn3r/7+vLKYklqnIeGJKlxBoEkNc4gkKTGGQSS1DiDQJIaZxBIUuMMAklqnEEgSY37f1o0mP8scMtBAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "print('TotalWorkingYears')\n", "print(f' Mean: {df.TotalWorkingYears.mean()}')\n", "print(f' Median: {df.TotalWorkingYears.median()}')\n", "print(f' Mode(s): {\", \".join([str(m) for m in df.TotalWorkingYears.mode()])}')\n", "df.TotalWorkingYears.plot(kind='hist',bins=40)\n", "plt.show()\n", " \n", "print('DailyRate')\n", "print(f' Mean: {df.DailyRate.mean()}')\n", "print(f' Median: {df.DailyRate.median()}')\n", "print(f' Mode(s): {\", \".join([str(m) for m in df.DailyRate.mode()])}')\n", "df.DailyRate.plot(kind='hist',bins=40)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sarakkeelle `TotalWorkingYears` on käytettävä mediaania, mutta `DailyRate`-sarakkeelle riittää keskiarvo." ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TotalWorkingYearsMonthlyIncomeOvertimeDailyRateLabel
010.067250498.0000000
112.027820804.2259510
29.024680804.2259510
38.050030549.0000000
412.085780804.2259510
\n", "
" ], "text/plain": [ " TotalWorkingYears MonthlyIncome Overtime DailyRate Label\n", "0 10.0 6725 0 498.000000 0\n", "1 12.0 2782 0 804.225951 0\n", "2 9.0 2468 0 804.225951 0\n", "3 8.0 5003 0 549.000000 0\n", "4 12.0 8578 0 804.225951 0" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn.impute import SimpleImputer\n", "\n", "imputer_mean = SimpleImputer(strategy='mean')\n", "imputer_median = SimpleImputer(strategy='median')\n", "\n", "df.DailyRate = imputer_mean.fit_transform(df[['DailyRate']])\n", "df.TotalWorkingYears = imputer_median.fit_transform(df[['TotalWorkingYears']])\n", "\n", "df.head()" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TotalWorkingYearsMonthlyIncomeOvertimeDailyRateLabel
count1470.0000001470.0000001470.0000001470.0000001470.000000
mean10.8095246502.9312930.282993804.2259510.161224
std6.5259954707.9567830.450606314.4359120.367863
min0.0000001009.0000000.000000103.0000000.000000
25%7.0000002911.0000000.000000670.0000000.000000
50%10.0000004919.0000000.000000804.2259510.000000
75%11.0000008379.0000001.000000922.0000000.000000
max40.00000019999.0000001.0000001499.0000001.000000
\n", "
" ], "text/plain": [ " TotalWorkingYears MonthlyIncome Overtime DailyRate Label\n", "count 1470.000000 1470.000000 1470.000000 1470.000000 1470.000000\n", "mean 10.809524 6502.931293 0.282993 804.225951 0.161224\n", "std 6.525995 4707.956783 0.450606 314.435912 0.367863\n", "min 0.000000 1009.000000 0.000000 103.000000 0.000000\n", "25% 7.000000 2911.000000 0.000000 670.000000 0.000000\n", "50% 10.000000 4919.000000 0.000000 804.225951 0.000000\n", "75% 11.000000 8379.000000 1.000000 922.000000 0.000000\n", "max 40.000000 19999.000000 1.000000 1499.000000 1.000000" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Kuten odottaa saattaa, on keskiarvo vuosien kohdalla hieman muuttunut, mutta toisen sarakkeen kohdalla jäänyt muuttumattomaksi. Koitetaanpa mallinnusta uudestaan." ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.7392290249433107" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_train, X_test, y_train, y_test = train_test_split(\n", " df.drop('Label', axis=1), df.Label,\n", " test_size=0.3, random_state=0)\n", "\n", "clf = DecisionTreeClassifier(random_state=0).fit(X_train, y_train)\n", "\n", "y_pred = clf.predict(X_test)\n", "accuracy_score(y_test, y_pred)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Model Quality\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem: Hyperparameter Optimization\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solutions: Hyperparameter Optimization\n", "\n", "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.\n", "\n", "Eniten käytetyt hyperparametrien viritykseen käytetyt menetelmät of taulukkomainen hila- tai taulukkoetsintä (*grid search*) ja hyperparametrejä jakaumista satunnaisesti nostava satunnaisetsintä (*random search*). \n", "\n", "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. \n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Feature: Feedback Loops, A/B Testing of Models\n", "\n", "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. \n", "\n", "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.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Feature: Repeatable and Explainable Results\n", "\n", "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ä.\n", "\n", "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.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Performance\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Goal: Low Latency, High Scalability\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Performance Optimization\n", "\n", "Kuten ohjelmistokehityksessä, myös koneoppimisessa voidaan jossain määrin tarkastella mallin kokonaissuorituskykyä ositellusti ja jaotellusti. Keinoja ovat muun muassa:\n", "\n", " - *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. \n", " \n", " - *Algoritmin optimointi*: Joskus algoritmia muokkaamalla tai sen vaihtamisella voidaan myös saavuttaa haluttu suorituskykyloikka jopa ilman, että mallin tarkkuus alenee merkittävästi.\n", " \n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Horizontal Scaling with Distributed Computing Frameworks\n", "\n", "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.\n", "\n", "Apache Spark on kehys, jolla prosessien hajautus onnistuu hyvin. Koneoppimista varten on kehitetty [`spark-sklearn`](https://github.com/databricks/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.\n", "\n", "Toinen varteenotettava, joskin enemmän tapauskohtaista kehitystyötä vaativa kehys, on [`pyspark`](http://spark.apache.org/docs/2.2.0/api/python/pyspark.html). Pelkän laskennan hajautuksen lisäksi tämä toteutus tarjoaa mahdollisuuden myös datan käsittelyn hajautukseen Pandas DataFrame-tietotyypin tapaan. \n", "\n", "Käydäänpä seuraavaksi esimerkin avulla yksinkertaisen `pyspark` tietueen luominen." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "root\n", " |-- id: integer (nullable = false)\n", " |-- email: string (nullable = false)\n", " |-- label: double (nullable = false)\n", "\n" ] } ], "source": [ "from pyspark.sql import types, SparkSession\n", "\n", "spark = SparkSession.builder.master('local').appName('esimerkki').getOrCreate()\n", "\n", "schema = types.StructType([\n", " types.StructField('id', types.IntegerType(), nullable=False),\n", " types.StructField('email', types.StringType(), nullable=False),\n", " types.StructField('label', types.DoubleType(), nullable=False)])\n", "\n", "df = spark.createDataFrame(\n", " data=[{'id':1,'email':'testi.testaaja@testi.fi','label':0.8}],\n", " schema=schema)\n", "\n", "df.printSchema()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Cloud Services\n", "\n", "\"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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Maintainability\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem: Checkpointing, Versioining and Deploying Models\n", "\n", "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.\n", "\n", "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ö. \n", "\n", "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ää." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Goal: Graceful Degradation\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Goal: Easily Tunable and Configurable\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Monitoring and Alerting\n", "\n", "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.\n", "\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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Security and Reliability\n", "\n", "Seuraavaksi käydään läpi niitä asioita, joita tietoturvakontekstiin vietyjen mallien tulisi taata." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Feature: Robustness in Adversarial Contexts\n", "\n", "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ä." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Feature: Data Privacy Safeguards and Guarantees\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Feedback and Usability\n", "\n", "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." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": false, "title_cell": "Sisällysluettelo", "title_sidebar": "Sisällysluettelo", "toc_cell": true, "toc_position": { "height": "1158px", "left": "278px", "top": "111.133px", "width": "236px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }