Siirry pääsisältöön

Ohjaamaton koneoppiminen poikkeamien havaitsemisessa

Ohjaamaton koneoppiminen poikkeamien havaitsemisessa

Fonzit Oy on laajentanut liiketoimintaansa loma-asuntobisnekseen ja rakennuttanut kokonaisen vuokramökkikylän Etelä-Suomeen. Koska data ja tekoäly liiketoiminnassa on Fonzitin ydinosaamista, käytämme tietenkin mökkikylästä kertyvää mittausdataa hyödyksi.

Tämä työkirja havainnollistaa kuvitteellisen mökkikylän energiankulutuksen tarkkailua koneoppimisen avulla. Käytössä on viikkokohtaista dataa mökkien energiankulutuksesta, ulkolämpötilasta sekä varauksista. Datasta pyritään havaitsemaan laitevikoja ja muita häiriötilanteita.

Mahdollisia datasta löytyviä vikatilanteita:

Alkuvalmistelut

Demo käyttää Python-datatieteilijän perustyökaluja NumPy, Matplotlib ja Scikit-learn. Tuodaan ne sisään.

import numpy as np
import numpy.typing as npt
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.cluster import DBSCAN
import pandas as pd

Datan kuvaus

Mökkikylästä on kerätty vuosien varrella dataa noin sadan datapisteen verran. Yksi piste vastaa yhtä viikkoa ja mökkiä. Kaikki mökit ovat samanlaisia.

Datapisteen sisältämät tiedot ovat ulkolämpötila, mökin energiankulutus sekä asuttu/tyhjä-tieto. Data on siis kolmiulotteista, vaikka tässä työkirjassa piirrämme siitä vain kaksiulotteisia kuvia.

Luetaan data csv-tiedostosta.

data = np.loadtxt('mokkidata.csv', delimiter=',', skiprows=1)
def piirra_mokkidata(data: npt.NDArray) -> plt.Axes:
    """Piirtää kuvan mökkien energiankulutusdatasta. Input-datan
    tulee sisältää sarakkeet ulkolämpötila, energiankulutus ja asuttu/tyhjä.
    Palauttaa Axes-olion, jonka avulla kuvaa voi muokata.

    Args:
        data (npt.NDArray): Nx3-data.

    Returns:
        plt.Axes: Olio, joka sisältää kuvan.
    """

    mpl.rcParams["lines.markersize"] = 12
    mpl.rcParams["lines.marker"] = "."
    mpl.rcParams["lines.linestyle"] = "None"
    akseli = plt.axes()
    asuttu_maski = data[:, 2] > 0
    akseli.plot(data[asuttu_maski, 0], data[asuttu_maski, 1], label="Mökki asuttu")
    akseli.plot(data[~asuttu_maski, 0], data[~asuttu_maski, 1], label="Mökki tyhjä")
    akseli.set_title("Mökkien energiankulutus ulkolämpötilan suhteen")
    akseli.legend()
    akseli.set_xlabel("Ulkolämpötila")
    akseli.set_ylabel("Energiankulutus viikossa")
    return akseli

Kuva datasta

Alla olevassa kuvassa on piirretty energiankulutus ulkolämpötilan suhteen. Asutut ja tyhjät viikot on eroteltu väreillä. (Ne voisi erotella myös piirtämällä kolmiulotteisen kuvan, mutta se olisi luultavasti epäselvempi.)

Tyhjä mökki pidetään viileämpänä, siksi kuvassa näkyy selvästi tyhjän mökin matalampi kulutus. Lisäksi näkyy totta kai kylmän vuodenajan korkea lämmityskulu. Kesähelteillä asuttua mökkiä viilennetään, mikä aiheuttaa sinisen pistejoukon kääntymisen lievään kasvuun yli +20 asteen säillä. Asutun mökin energiankulutuksessa on enemmän vaihtelua kuin tyhjän, koska asukkaiden määrä ja kulutus vaihtelee.

_ = piirra_mokkidata(data)
def piirra_klusterointitulos(data: npt.NDArray, klusterointi: DBSCAN):
    """Piirtää kuvan mökkien energiankulutusdatasta ja lisää siihen
    klusteroinnin havaitsemat poikkeamat.

    Args:
        data (npt.NDArray): Nx3-data (ks. piirra_mokkidata).
        klusterointi (DBSCAN): sklearn.cluster.DBSCANin palauttama tulos.
    """
    poikkeamien_indeksit = np.flatnonzero(klusterointi.labels_ == -1)
    akseli = piirra_mokkidata(data)
    juokseva_numero = 1
    for i in poikkeamien_indeksit:
        paikka = (data[i, 0], data[i, 1])
        akseli.plot(
            paikka[0],
            paikka[1],
            "ro",
            markerfacecolor="None",
            markersize=10,
            label="Poikkeama",
        )
        akseli.annotate(
            juokseva_numero, paikka, xytext=(10, -3), textcoords="offset points"
        )
        juokseva_numero += 1
    kahvat, nimikkeet = akseli.get_legend_handles_labels()
    _ = akseli.legend(kahvat[0:3], nimikkeet[0:3])

Poikkeamien havaitseminen

Klusterointi kuuluu ohjaamattoman koneoppimisen menetelmiin, ja se sopii mainiosti juuri poikkeamien (anomalioiden) etsimiseen, kun varsinaista opetusdataa ei ole.

Etsitään mökkidatasta poikkeamia DBSCAN-klusterointialgoritmilla. Se liittää yhteen datapisteitä, joiden lähellä on riittävästi muita pisteitä, kun taas yksinäiset pisteet jäävät poikkeamiksi.

Huomaa, että itse koneoppiminen on tässä kokonaisuudessa yksi ainoa koodirivi, koska algoritmi on valmiiksi koodattuna Scikit-learn-kirjastossa. Toki datatieteilijän on syytä tuntea algoritmin toiminta samoin kuin sen vaatimat parametrit.

klusterointi = DBSCAN(eps=8, min_samples=3).fit(data)
piirra_klusterointitulos(data, klusterointi)

Tulosten tulkinta

Yllä olevassa kuvassa on sama data kuin edellisessäkin, nyt vain on poikkeamat löydetty. Kuvaan merkityt ja numeroidut poikkeamat voivat johtua seuraavista syistä:

  1. Asutun mökin huomattavan suuri energiankulutus nollakelillä. Asukkaat pitävät ikkunoita auki tai saunovat aamusta iltaan.
  2. Asutun mökin tavallista pienempi kulutus pikkupakkasella. Lämmitys on jäänyt poissa-asentoon, vaikka mökissä on asukkaat.
  3. Tyhjän mökin korkea kulutus kireällä pakkasella. Ikkuna on jäänyt auki tai lämmitys asuttu-asentoon.
  4. Tyhjän mökin korkea kulutus kesähelteellä. Jäähdytys on jäänyt päälle.
  5. Tyhjän mökin minimikulutus, vaikka pakkanen on kireää. Lämmityslaitteessa tai mittarissa on vika.