Kategória: Python.
Alkategória: Adatkezelés Pythonban.
Table of Contents
|
Áttekintés
Ez a leírás a gépi tanulás lehetőségeiről szól Pythonban. Elsősorban a Scikit-learn Python csomagot dogjuk felhasználni.
Itt a témának a felületét épp hogy csak karcoljuk. Célom, hogy általános képet adjak a témáról. Az adattudomány egy rendkívül szerteágazó terület, amit kellő mélységgel nem is lehet egyetlen oldalon kellő részletességgel bemutatni. Néhány fontosabb területét említem meg ebben a leírásban.
Az oldal elkészítésében az alábbi leírások segítettek:
- https://machinelearningmastery.com/machine-learning-in-python-step-by-step/
- https://medium.com/@eraylson_/the-hello-world-in-data-science-and-machine-learning-32b85946eb67
- https://medium.com/analytics-vidhya/linear-regression-using-iris-dataset-hello-world-of-machine-learning-b0feecac9cc1
- https://www.kaggle.com/code/amarpandey/implementing-linear-regression-on-iris-dataset
- https://www.kaggle.com/code/skalskip/iris-data-visualization-and-knn-classification
- https://www.tutorialspoint.com/scikit_learn/
- https://www.tutorialspoint.com/linear-regression-with-matplotlib-numpy
- https://www.w3schools.com/python/python_ml_getting_started.asp
Ráadásul ezek egy részét igen nehézkesnek találtam. De mivel az igazán "nagyok" nem álltak elő könnyen érthető, egyszerű gépi tanulás összefoglalóval, kénytelen voltam ezeket is jobban figyelembe venni.
Telepítés
A példákat kétféleképpen tudjuk kipróbálni.
Hagyományos használat
A pip install parancs segítségével fel kell telepíteni a használt könyvtárakat. Ezek az alábbiak:
pip install numpy
pip install pandas
pip install matplotlib
pip install seaborn
pip install scikit-learn
Ez esetben tetszőleges fejlesztőkörnyezetet használhatunk, és a programokat hagyományosan indíthatjuk.
Notebook
Ez az adattudományban a tipikus módszer. Ez esetben érdemes feltelepíteni az Anaconda szoftvert (https://www.anaconda.com/). Ez mindent tartalmaz, amire szükségünk van. Miután feltelepítettük, az Anaconda Navigator alkalmazást indítsuk el. Azon belül számos alkalmazás ikonját látjuk, amit onnan egyrészt feltelepíthetünk, másrészt ha már fel van telepítve, elindíthatjuk. Indítsuk el a Jupyter Notebook nevű alkalmazást. Egy böngésző ablak fog megjelenni, ami a http://localhost:8888/tree URL-t tölti be. Itt hozzunk létre egy notebook-ot a New -> Python 3 (ipykernel) menüpontot kiválasztva.
A példában a Notebookos megoldást használom. Néhány alapvető funkció:
- A programok tipikusan pár soros részre vannak felbontva, amiket cellának hívunk. Nincs elvi megkötés arra nézve, hogy egy-egy cellában mennyi kód legyen: kerülhet az összes sor külön cellába, és egyetlen cellába is kerülhet a teljes kód. Általában néhány szorosan kapcsolódó utasítás sor kerül egy cellába.
- Cellákat beszúrni az Insert menüpont alatt tudunk, törölni pedig az Edit -> Delete Cells segítségével.
- Ha egy cellában végrehajtunk egy műveletet (pl. egy változónak értéket adunk), akkor az a többi cellára is kihat. Az egy nagyon tipikus felhasználás, hogy az egyik cellában beolvasunk adatot, a másikban pedig feldolgozzuk.
- Egy cella utasításait a Run paranccsal futtatjuk le. Az tehát nem az egész programot futtatja, csak az adott celláét.
A lenti utasítások az eredményekkel megtalálhatóak az alábbi notebookban: scikit.ipynb.
Ellenőrzés
Így vagy úgy a fenti könyvtáraknak fel kell lenniük telepítve. Importáljuk őket, és nézzük meg a verziószámukat!
import sys print(f'Python: {sys.version}') import numpy as np print(f'numpy: {np.__version__}') import pandas as pd print(f'pandas: {pd.__version__}') import matplotlib print(f'matplotlib: {matplotlib.__version__}') import seaborn as sns print(f'seaborn: {sns.__version__}') import sklearn as sk print(f'sklearn: {sk.__version__}')
Nálam Anacondában az eredmény az alábbi:
Python: 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
numpy: 1.21.5
pandas: 1.4.2
matplotlib: 3.5.1
seaborn: 0.11.2
sklearn: 1.0.2
Az olvasónak minden bizonnyal ennél magasabb verziószámok lesznek.
Adatok áttekintése
Az adatok betöltése
A gépi tanulás során adatokkal dolgozunk, ezért szükségünk van adatokra. Az adattudomány az adatok gyűjtésével kezdődik, ami egy igen hosszú folyamat: gyűjtés, rendszerezés, megfelelő formába öntés, ellenőrzés stb. Az adatokat célszerű táblázatos formában tárolni, lehetőleg úgy, hogy ne legyenek benne hiányosságok és hibák.
Az adattudománynak ezzel a részével itt most nem foglalkozunk, hanem kész adatokkal dolgozunk. Az adattudomány "helló világ" adata - sajnos még nem találtak jobbat - az Iris:
- Ez háromféle írisz virágról tartalmaz adatokat. A magyar nevüket nem tudom, így leírom az angol terminológiában használtakat: setosa, versicolor és virginica.
- Mindegyik fajtából 50 adat van, így összesen 15.
- Mindegyik adat az írisz fajta mellett 4 adatot tartalmaz: a csészelevél (sepal) és a virágszirom (petal) hosszát (length) és szélességét (width) cm-ben kifejezve.
Összesen tehát a táblázat 150 sort és 5 oszlopot tartalmaz, valamint metainformációként az oszlopneveket. Ez egyébként egy kiváló méret, mert már elég sok ahhoz, hogy alapvető műveleteket végre tudjunk hajtani, ugyanakkor még elég kevés ahhoz, hogy át tudjuk tekinteni. Az adat mindenki számára jól érthető: virágszirmot mindenki el tud képzelni. (Az adatok érdekességéről pedig most jótékonyan ne mondjunk semmit.)
Az Iris adattábla számos helyen elérhető különböző formátumokban: CSV fájl, adatbázis tábla stb. Számunkra legkézenfekvőbb a seaborn csomagból betölteni a következőképpen:
iris = sns.load_dataset('iris')
(Jupyter notebookot használva ez a sor kerülhet a második cellába. Ha a fenti, verziószám kiíró az első, akkor ott már importáltuk a seaborn csomagot sns néven, így itt nem szükséges. De természetesen szabad kétszer is importálni, ill. ha fent nem tettük meg, akkor itt a fenti sor elé kell írni azt, hogy import seaborn as sns. Konvenció a seaborn csomagot sns-ként importálni.)
Az adatok áttekintése
Mielőtt fejest ugrunk a gépi tanuló algoritmusok lefuttatásába, érdemes megismerni az adatot, amivel dolgozunk. Járjuk körbe tehát, hogy mit is tartalmaz ez az adat! Először nézzük meg a típusát!
type(iris)
Az eredmény:
pandas.core.frame.DataFrame
Azaz Pandas adatkeret. Pythonban az adattudományban ez a leggyakoribb és legkézenfekvőbb adatszerkezet. Ha nem ilyen formátumú lenne, akkor első lépésben Pandas adatkeretté kellene konvertálni.
A Jupyter notebookban elég csak a fenti sort beírni; egy programban a kiíráshoz a print() utasítás is kellene: print(type(iris)). Viszont egy cellába csak egy olyan utasítást írhatunk, ami eredményt produkál, és nem írunk print()-et.
Kukkantsunk rá az adatokra! Írjunk be csak ennyit:
iris
Az eredmény Jupyterben:

Az adatok "alakját" a következőképpen kérdezhetjük el:
iris.shape
Eredmény:
(150, 5)
Tehát 150 sort és 5 oszlopot tartalmaz (ahogy a fenti képből ez már kiderült).
Érdemes még két utasítással megismerkedünk: az info() és a describe(). Az info() az adat formátumáról ad plusz információkat:
iris.info()
Eredmény:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 sepal_length 150 non-null float64
1 sepal_width 150 non-null float64
2 petal_length 150 non-null float64
3 petal_width 150 non-null float64
4 species 150 non-null object
dtypes: float64(4), object(1)
memory usage: 6.0+ KB
Ebben nagyjából benne van minden, amit egyesével lekérdeztünk: az adat típusa, a sorok és oszlopok száma, viszont további információt kaptunk. Megtudtuk az oszlopok nevét és típusát.
A describe() a tartalmáról ad összefoglalót:
iris.describe()
Eredmény:

Itt táblázatos formában megkapjuk mind a 4 számszerű oszlopra a darabszámot, az átlagot, a szórást, a minimum, a 25 centilis, a medián, a 75 centilis és a maximum értéket. Ezzel fontos kép alakul ki magukról az adatokról. Pl. kiolvashatjuk, hogy a csészelevél hossza 4,3 cm és 7,9 cm között változik. Az átlag 5,84 cm, a medián ehhez nagyon közeli: 5,8 cm. Az értékek középső fele 5,1 cm és 6,4 cm közé esik.
Viszont a describe() nem ad részleteket a kategorikus adatokról, csak a számszerűről. A fajtáról az adatokat a következő paranccsal tudjuk lekérdezni:
iris['species'].value_counts()
melynek eredménye:
setosa 50
versicolor 50
virginica 50
Name: species, dtype: int64
Tehát mindhárom virágfajtából 50 megfigyelés van. Azt is megtudtuk, hogy 64 bites egész formájában tárolja az adatokat, de ezzel nekünk nem kell foglalkoznunk.
Néhány utasítással tehát egész sokmindent megtudtunk az adatokról. Ez különösen hasznos, ha milliónyi adattal dolgozunk.
Diagramok készítése
Egy diagram sokszor többet ér ezer szónál. Az adattudományban a diagramoknak legalább kettős szerepük van: az első piszkozatban az adattudós - szó szerint - képet kap az adatokról, és ez alapján könnyebben "ráérez", hogy milyen irányban érdemes mennie, és mi lesz a zsákutca.
Dobozdiagram
Az egyik leghasznosabb diagram típus a dobozdiagram (boxplot). Itt egy adatot tudunk megjeleníteni. Megmutatja, hogy hol "sűrűsödik", mekkora a tartománya, és külön jelzi a kilógó értékeket. Mivel az Iris adattábla háromféle virágról tartalmaz 4 adatot, jelenítsük meg ezt a 12 adatot 12 dobozdiagram formájában.
import matplotlib.pyplot as plt plt.figure() iris.boxplot(by="species", figsize=(15, 10)) plt.show()
Az eredmény:

Ebből világosan látszik, hogy a sziromméretek alapján a setosa egyértelműen elkülöníthető a másik kettőtől. A vericolor és a virgilica esetén némi átlapolódás látható. A csészelevél kevésbé alkalmas az osztályozásra.
Párdiagram
Igen látványos, és az egyik kedvenc diagramom a témában a párdiagram (pairplot). Ez páronként készít szórásdiagramot (scatterplot). (A szórásdiagram két koordinátája a két adat.) Így tehát összesen 12 szórásdiagram készül, ahol valójában 6 a másik 6 szimmetriája. Az Seaborn képes egyetlen paraméter megadásával arra, hogy az adott kategóriához tartozó elemeket külön színnel jelölje. A 4x4-es táblázat átlójában (ahol nem lenne értelme a szórásdiagramnak) az eloszlásokat illusztrálja.
sns.pairplot(iris, hue='species')
Az eredmény:

Az ábrák nagy részén a setosa jól elkülönül; egy tanuló algoritmussal szemben tehát reális elvárás, hogy azt 100%-ban azonosítja. A másik kettőnél itt is kijön némi átlapolódás.
A sziromlevél méretei egész jól korrelálnak egymással; adja magát, hogy formálisan is vizsgáljuk meg lineáris regresszió formában.
KDE
Mivel a fenti diagramban ugyanaz lényegében kétszer szerepel, az egyiket (pl. az alsót) másképp is megjeleníthetjük. a KDE diagram kiemeli a sűrűsödéseket, nagyjából úgy ábrázolva, ahogy a térképeken a hegységeket ábrázolják. Minimális változtatásra van csupán szükség:
g = sns.pairplot(iris, hue='species') g = g.map_lower(sns.kdeplot)
Az eredmény:

Ezen talán még inkább kirajzolódik az, hogy a setosa jól elkülönül a másik kettőtől, míg a sersicolor és a virgilica között némi átfedés van mindegyik esetben. Ez azt is előrevetíti, hogy ez utóbbiakat nem fogjuk tudni 100%-ban megkülönböztetni.
Osztályozás
Angolul classification (klasszifikáció).
Bevezető
Ez talán a gépi tanulás legalapvetőbb művelete. A rendszert létező adatok segítségével "megtanítjuk", majd a tanulás során számra ismeretlen példákat adva megkérjük, hogy próbálja meg eltalálni. Ennek az egyik leggyakoribb, igen látványos példája az, hogy az algoritmusnak "megmutatunk" kellően sok kutyás és kellően sok macskás képet, majd "elé teszünk" egy képet, ami vagy kutya, vagy macska van, és megnézzünk, hogy hányszor találja el. Az ilyen szintű képfelismerés messze túlmutat ezen a leíráson, itt sokkal egyszerűbb modellel dolgozunk. Viszont számos osztályozási módszer létezik; említés szintjén párat megvizsgálunk.
Felosztás
Mivel a 4 méretből próbálunk következtetni a fajtára, először válasszuk szét a kettőt:
iris_feature = iris.drop('species', axis=1) iris_target = iris['species'] print('iris_feature') print(iris_feature) print('iris_target') print(iris_target)
Az eredmény:
iris_feature
sepal_length sepal_width petal_length petal_width
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
.. ... ... ... ...
145 6.7 3.0 5.2 2.3
146 6.3 2.5 5.0 1.9
147 6.5 3.0 5.2 2.0
148 6.2 3.4 5.4 2.3
149 5.9 3.0 5.1 1.8
[150 rows x 4 columns]
iris_target
0 setosa
1 setosa
2 setosa
3 setosa
4 setosa
...
145 virginica
146 virginica
147 virginica
148 virginica
149 virginica
Name: species, Length: 150, dtype: object
Látható tehát, hogy az iris_feature csak a méreteket tartalmazza, az iris_target pedig csak a fajtákat. Megjegyzés: számos leírásban az iris_feature helyett X (nagy iksz), az iris_target helyett pedig y (kis ipszilon) szerepel. Ebben a leírásban viszont nem alkalmazom ezt a konvenciót, mert egyrészt ellentmond a programozási konvencióknak (a változónév mindig kisbetűvel kezdődik, hogy megkülönböztessük a nagybetűs osztálynévtől), másrészt egyetlen kivételtől eltekintve célszerű kerülni az egybetűs változóneveket (a kivétel a ciklusváltozó).
Az adatokat tovább kell osztani tanuló (train) és teszt (test) részekre. Ezt kézzel is megtehetnénk, pl. a hárommal osztható sorszámú a teszt, a többi pedig a tanuló adat, viszont érdemes véletlenszerűsíteni. Ehhez a SciKit biztosít egy függvényt:
from sklearn.model_selection import train_test_split iris_feature_train, iris_feature_test, iris_target_train, iris_target_test = train_test_split(iris_feature, iris_target, test_size=1/3, random_state=12345) print(iris_feature_train.shape) print(iris_feature_test.shape) print(iris_target_train.shape) print(iris_target_test.shape)
Magyarázat: először megadjuk, hogy miből szeretnénk következtetni (iris_feature), másodikként, hogy mire (iris_target), majd megadjuk a teszt arányát (0,3, azaz 30%), végül pedig megadhatunk egy számot (random_state=12345), aminek segítségével azt biztosítjuk, hogy mindegyik lefutáskor pont ugyanaz legyen a felosztás. Ennek a reprodukálhatóság miatt van szerepe. (Ne feledjük: a gépi tanulásnak nagyrészt a tudományos publikálásban van szerepe, ahol lényeges a reprodukálhatóság.)
Eredmény:
(100, 4)
(50, 4)
(100,)
(50,)
Összesen tehát az adatok kétharmada, azaz 100 a tanuló adat és egyharmada, azaz 50 a teszt.
K-szomszéd osztályozás
Angolul K-neighbors classifier. A módszert használva nem kell tudnunk annak részleteit, de érdekességképpen leírom a lényeget. Egy lépéssel távolabbról indítok. A K-közép módszer lényege az, hogy megpróbálja csoportokba (klaszterekbe) szervezni az elemeket: hogy az egymáshoz közel álló kerüljenek egy csoportba, és a csoportok pedig egymástól távol álljanak. A K-szomszéd osztályozás ennek a felügyelt változata: amikor tudjuk, hogy mely elemek tartoznak egy csoportba. Ebben az esetben veszi a csoport egyfajta eredőjét; tehát egy olyan pontot, ami legjobban reprezentálja az elemeket. Legegyszerűbb esetben ez az értékek átlagolását jelenti, bár elképzelhető, hogy bizonyos esetekben ennél szofisztikáltabb módszert alkalmaznak. A lényeg: végeredményben mindegyik csoporthoz tartozni fog egy képzeletbeli elem, ami a csoportot leírja. Amikor egy adott elem hovatartozását vizsgáljuk, akkor azt nézzük, hogy melyik képzeletbeli ponthoz áll legközelebb. A távolság lehet sima Euklideszi, de egyéb fajta is, pl. Manhattan. Pontok végső soron a teret (az Iris esetében a 4 dimenziós teret) felosztják részekre, és az adott területre eső elemeket minősíti az algoritmus olyannak, amit az adott térrész képvisel.
Elérkeztünk a leírás egyik leglényegesebb pontjához: magához a gépi tanuláshoz és ellenőrzéshez. A kód az alábbi:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier() knn.fit(iris_feature_train, iris_target_train) iris_target_predicted = knn.predict(iris_feature_test)
Itt tehát a KNeighborsClassifier osztályt használjuk modellként, ami a fenti algoritmust valósítja meg. Számos paramétert adhatnánk meg, de most elfogadjuk az alapértelmezett értékeket. A knn.fit(iris_feature_train, iris_target_train) tanítja a modellt; itt történik tehát a gépi tanulás. A "jóslás" rész a knn.predict(iris_feature_test) sorban valósul meg.
Jelen esetben a modell gyorsan megalkotható, de nagy adathalmaz esetén lehet időigényes. A Scikit képes elmenteni a modellt.
Nézzük, hogy mennyire sikerült eltalálni! Írjuk egymás mellé a valósat és a tippeket!
for test, predicted in zip(iris_target_test.tolist(), iris_target_predicted.tolist()): print(f'{"" if test == predicted else "* "}{test} {predicted}')
Mind az 50-et most nem másolom be, csak a lényeges részeket:
versicolor versicolor
setosa setosa
versicolor versicolor
...
versicolor versicolor
* versicolor virginica
virginica virginica
...
versicolor versicolor
versicolor versicolor
versicolor versicolor
Egyetlen pontot tévedett tehát az algoritmus.
Álljon még itt 2 statisztikai adat! Az egyik az ún. confusion matrix, ami megmutatja, hogy milyen típusú virágot mi módon tippelt a rendszer:
from sklearn.metrics import confusion_matrix print(pd.DataFrame( confusion_matrix(iris_target_test, iris_target_predicted), index=['setosa', 'veriscolor', 'virginica'], columns=['setosa', 'veriscolor', 'virginica'] ))
Az eredmény:
setosa veriscolor virginica
setosa 17 0 0
veriscolor 0 20 1
virginica 0 0 12
A 17 setosa virágot tehát mind eltalálta, a 21 veriscolorból 20-at eltalált, egyet pedig tévesen virgilicának értelmezett, és mind a 12 virgilicát eltalálta.
Százalékosan is kiírhatjuk az eredményt, melyhez szintén nyújt az sklearn függvényt:
from sklearn import metrics print(f'{100*metrics.accuracy_score(iris_target_test, iris_target_predicted):.1f}%')
Eredmény:
98.0%
Ez egyébként kimagasló eredmény; a gyakorlatban jó, ha szignifikánsan magasabb értéket érünk el, mint a véletlenszerű 1/3.
Ha egyetlen elemet szeretnénk ellenőrizni, akkor szinte nehezebb dolgunk van, mintha sokat szeretnénk. Ez esetben ugyanis létre kell hozni egy teljes adatkeretet egyetlen elemmel. Pl.:
knn.predict(pd.DataFrame([[5.1, 2.1, 1.1, 0.2]], columns=['sepal_length', 'sepal_width', 'petal_length', 'petal_width']))[0]
Az eredmény:
'setosa'
K-szoros ellenőrzés
A fenti példában végrehajtottuk egyszer a folyamatot: a tanulást ill. az ellenőrzést. A 98%-os eredmény elég meggyőzőnek tűnik, viszont azt nem tudhatjuk biztosra, hogy ez egy egyszeri, kilógós eredmény volt-e, vagy ez a tipikus. Valójában érdemes egy tesztet többféleképpen is végrehajtani. Azon túl, hogy a felosztást többféle random állapotból kiindulva tulajdonképpen akárhányszor végre tudjuk hajtani, erre létezik egy statisztikai módszer is. Az adathalmazt osszuk pl. 10 egyenlő részre. A 10 egységből 9 lesz a tanuló teszthalmaz, a tizedik meg az ellenőrzés. Mindegyik részhalmaz egyszer lesz teszthalmaz, így összesen tízszer hajtjuk végre a műveletet. A felosztás száma általános esetben nem pont 10, hanem egy k-val jelölt szám. Így a módszer neve k-szoros keresztellenőrzés, angolul K-Fold Cross Validation.
Hajtsuk végre a fenti módszert, most a teljes adathalmazzal, tízszeres keresztellenőrzéssel:
from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import cross_val_score kfold = StratifiedKFold(n_splits=10, random_state=54321, shuffle=True) cross_val_score(knn, iris_feature, iris_target, cv=kfold, scoring='accuracy')
Az eredmény:
array([0.93333333, 0.86666667, 1. , 0.93333333, 1. ,
1. , 0.93333333, 1. , 1. , 1. ])
Mivel a tesztadatok mérete 15, némi ráérzéssel megállapíthatjuk, hogy a 10 tesztből 6 alkalommal volt a találati arány 100%, 3 alkalommal volt 1 tévedés és 1 alkalommal 2. Azaz a 150 tippből 145 volt helyes, ami 96,67%-os eredmény. Ez valamelyest meggyőzőbb, mint az egy szem teszt alapján megállapított kerek 98%.
További módszerek
A K-szomszéd osztályozás csak egy módszer a sok közül. Most anélkül, hogy részleteznénk, melyik micsoda, nézzünk még meg práat, és hasonlítsuk őket össze.
from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.naive_bayes import GaussianNB from sklearn.svm import SVC from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import cross_val_score for name, model in [ ('Logistic Regression', LogisticRegression(solver='liblinear')), ('Linear Discriminant Analysis', LinearDiscriminantAnalysis()), ('K-Neighbors Classifier', KNeighborsClassifier()), ('Decision Tree Classifier', DecisionTreeClassifier()), ('Gaussian Naive Bayes', GaussianNB()), ('C-Support Vector Classification', SVC()), ]: kfold = StratifiedKFold(n_splits=10, random_state=11111, shuffle=True) cv_results = cross_val_score(model, iris_feature, iris_target, cv=kfold, scoring='accuracy') print(f'{name}: {100*cv_results.mean():.2f}%')
Az eredmény:
Logistic Regression: 96.00%
Linear Discriminant Analysis: 98.00%
K-Neighbors Classifier: 97.33%
Decision Tree Classifier: 95.33%
Gaussian Naive Bayes: 95.33%
C-Support Vector Classification: 97.33%
A 6 vizsgált modell 95%-98% körül teljesít, és ezeknél az adatoknál nagyjából mindegy is, hogy melyiket választjuk.
Regresszió
Az osztályozás diszkrét, a regresszió folytonos. Regresszió során is tanítjuk a rendszert, az eredmény viszont, amit ki kell találnia, egy szám lesz. Például a testmagasságból, az életkorból és a nemből megpróbálja kitalálni az adott ember testtömegét.
Egyváltozós lineáris regresszió
Talán ezt a legegyszerűbb elképzelni: amit meg szeretnénk határozni, az egyetlen másik változótól függ. Vegyük példaként a testmagasságot és a testsúlyt. "Érezzük", hogy a nagyobb testmagassághoz nagyobb testsúly tartozik, de azt is, hogy kivételek lehetnek: egy magasabb, de vékony testalkatú embernél lehet nehezebb egy alacsonyabb, de kövérebb. Ennek az érzésnek van tudományos neve is: korrelációnak hívjuk. Még tudományosabban: ez az $r^2$ érték. Értéke 1, ha a kapcsolat a két adatsor között 100%-os, -1, ha pont fordított, 0, ha nincs semmilyen kimutatható kapcsolat. Minél jobban megközelíti az 1-et, annál erősebb a kapcsolat.
A lenti példában a virágszirom hosszából következtetünk majd annak szélességére.
A scikit könyvtárban a LinearRegression osztályt használhatjuk erre a célra, bár meglepő, hogy ez tényleg csak alapvető értékeket számol ki. Pl. az $r^2$ értéket külön kell meghatároznunk egy másik függvény segítségével.
Lássuk az alábbi példát!
from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score iris_lr_predictor = iris[['petal_length']] iris_lr_to_predict = iris['petal_width'] iris_lr_predictor_train, iris_lr_predictor_test, iris_lr_to_predict_train, iris_lr_to_predict_test = train_test_split(iris_lr_predictor, iris_lr_to_predict, test_size=1/3, random_state=12345) lr = LinearRegression() lr.fit(iris_lr_predictor_train.values, iris_lr_to_predict_train.values) print('Coeficients:', lr.coef_) print('Intercept:', lr.intercept_)
Az eredmény:
Coeficients: [0.4126432]
Intercept: -0.3416613800571666
Itt tehát csak a szirom hosszával (mint magyarázó) és szélességével (mint kitalálandó) foglalkozunk. Az eredmény:
- A koefficiensek, jelen esetben egy érték; tehát mennyivel kell szorozni a magyarázó értéket, hogy megkapjuk a kitalálandót. A példában ezt az érték 0,413 körüli, ami kb. azt jelenti, hogy a szirom szélessége a hosszának átlagosan kb. 41,3%-a.
- Az intercept azt jeleni, hog x=0 helyen mekkora az értéke, magyarán hol metszi az y tengelyt. Ezt úgy lehetne interpretálni, hogy a 0 hosszú szirom szélessége -0,342 cm, de ez így nyilvánvalóan hibás. Itt inkább arról van szó, hogy ha a 0-ban nem ragaszkodunk a 0 értékhez, akkor pontosabban megkapjuk a közvetlen kapcsolatot abban a mérettartományban, ahol a kifejlett virágok szirmai ténylegesen vannak.
Az egyváltozós regressziót igen látványosan lehet illusztrálni pontdiagram és a regressziós egyenes segítségével. A megrajzolása eléggé nehézkes (mint oly sok minden az adattudományban):
plt.scatter(iris_lr_predictor_train, iris_lr_to_predict_train) xfit = np.array([0, 8]) yfit = lr.predict(xfit[:, np.newaxis]) plt.plot(xfit, yfit)
Az eredmény:

A vízszintes tengely a szirom hosszát, míg a függőleges a szirom szélességét jelenti. Az értékek nagyjából az egyenes körül szóródnak, de azért nem illeszkednek túlságosan rá.
Nézzük meg azt, hogy magukat a teszt adatokat hogyan tudja "megjósolni", azaz milyen erős a korreláció.
from sklearn.metrics import mean_absolute_error, mean_squared_error iris_lr_predicted_train = lr.predict(iris_lr_predictor_train.values) print('R^2:', r2_score(iris_lr_to_predict_train, iris_lr_predicted_train)) print('Mean Absolute Error:', mean_absolute_error(iris_lr_to_predict_train, iris_lr_predicted_train)) print('Mean Root Squared Error:', np.sqrt(mean_squared_error(iris_lr_to_predict_train, iris_lr_predicted_train)))
Az eredmény:
R^2: 0.9221089419209556
Mean Absolute Error: 0.170373993778555
Mean Root Squared Error: 0.21564081634326437
Magyarázat:
- R^2: ez adja meg a magát a korrelációt. A 0,922, azaz 92,2% egy igen erős korrelációra utal.
- Mean Absolute Error: az abszolút eltérések átlaga; átlagosan tehát 1,7 mm az eltérés.
- Mean Root Squared Error: egy másik pontosság mérőszám az, ha az eltéréseket négyzetre emeljük, átlagoljuk, majd gyököt vonunk. Ez az érték jelen esetben 0,216 körüli.
Hajtsuk végre az ellenőrzést a teszt adatokon is!
from sklearn.metrics import mean_absolute_error, mean_squared_error iris_lr_predicted_test = lr.predict(iris_lr_predictor_test.values) print('R^2:', r2_score(iris_lr_to_predict_test, iris_lr_predicted_test)) print('Mean Absolute Error:', mean_absolute_error(iris_lr_to_predict_test, iris_lr_predicted_test)) print('Mean Root Squared Error:', np.sqrt(mean_squared_error(iris_lr_to_predict_test, iris_lr_predicted_test)))
Eredmény:
R^2: 0.936351045583291
Mean Absolute Error: 0.13435280919242149
Mean Root Squared Error: 0.18323017186017165
Meglepő, de kissé magasabb korrelációt és kisebb hibát tapasztaltunk. Tehát a modell jobban teljesít a teszt adatokon, mint magukon a tanuló adatokon.
Többváltozós lineáris regresszió
Ábrázolni nehezebb, ám gyakran pontosabb eredményt kapunk, ha több változót is figyelembe vehetünk. A testmagasság-testsúly problémát tovább gondolva: pontosabb képet kapunk, ha figyelembe vesszük pl. az illető életkorát és a nemét.
A példában megpróbáljuk a szirom szélességét jobban meghatározni a csészelevél méreteit is figyelembe véve. A módszer a következő.
iris_multi_lr_predictor = iris[['sepal_length', 'sepal_width', 'petal_length']] iris_multi_lr_to_predict = iris['petal_width'] iris_multi_lr_predictor_train, iris_multi_lr_predictor_test, iris_multi_lr_to_predict_train, iris_multi_lr_to_predict_test = train_test_split(iris_multi_lr_predictor, iris_multi_lr_to_predict, test_size=1/3, random_state=12345) mlr = LinearRegression() mlr.fit(iris_multi_lr_predictor_train.values, iris_multi_lr_to_predict_train.values) print('Coeficients:', mlr.coef_) print('Intercept:', mlr.intercept_)
Tehát ugyanúgy feldaraboltuk az adatokat tanuló és teszt adatokra, de most nem egy, hanem három változóból próbáljuk kitalálni a negyediket. Az eredmény:
Coeficients: [-0.26165731 0.25551077 0.55155619]
Intercept: -0.10808694439902289
A koefficiensek értelmezése érdekes: legnagyobb mértékben a sziromlevéltől függ, de megjelenik a csészelevél szélessége mint pozitív és a csészelevél hossza mint negatív tényező. Ez utóbbi különösen meglepő: önmagában azt jelentené, hogy minél nagyobb a csészelevél hossza, annál kisebb a sziromlevél szélessége. Ez persze ebben a formában valószínűleg nincs így; a túlillesztés jeleként lehet értelmezni.
A korreláció ill. hiba kiszámolása:
iris_multi_lr_predicted_train = mlr.predict(iris_multi_lr_predictor_train.values) print('R^2:', r2_score(iris_multi_lr_to_predict_train, iris_multi_lr_predicted_train)) print('Mean Absolute Error:', mean_absolute_error(iris_multi_lr_to_predict_train, iris_multi_lr_predicted_train)) print('Mean Root Squared Error:', np.sqrt(mean_squared_error(iris_multi_lr_to_predict_train, iris_multi_lr_predicted_train)))
Eredmény:
R^2: 0.9365879356169545
Mean Absolute Error: 0.1508173330405235
Mean Root Squared Error: 0.1945687601766485
Minimális mértékben javult: átlagosan kb. két tizedmilliméterrel pontosabban határozta meg a szirom szélességét a tanuló adatokon ze a modell, mint az, amelyik csak a sziromlevél hosszát vette figyelembe.
A modell ellenőrzése a teszt adatokon:
iris_multi_lr_predicted_test = mlr.predict(iris_multi_lr_predictor_test.values) print('R^2:', r2_score(iris_multi_lr_to_predict_test, iris_multi_lr_predicted_test)) print('Mean Absolute Error:', mean_absolute_error(iris_multi_lr_to_predict_test, iris_multi_lr_predicted_test)) print('Mean Root Squared Error:', np.sqrt(mean_squared_error(iris_multi_lr_to_predict_test, iris_multi_lr_predicted_test)))
Eredmény:
R^2: 0.9358979973059554
Mean Absolute Error: 0.1336859036825049
Mean Root Squared Error: 0.18388112456977165
Itt viszont már érdemben nem változott az eredmény, bár érdekes, hogy a hiba továbbra is kisebb mint a tanuló adatokon végrehajtott. Abból, hogy a teszt adatokon nincs érdemi eltérés, szintén arra következtethetünk, hogy a látszólagos javulás a túlillesztésnek volt köszönhető, a valóságban ez nem játszik szerepet.
A konkrét példát magyarázva: a sziromlevél hosszából önmagában elég jól lehet következtetni annak szélességére, és nem javítja a becslést az, ha hozzávesszük a csészelevél méreteit.
Dimenziószám csökkentés
Ha a táblázatos adatunknak nagyon sok oszlopa van, akkor ennek segítségével tudjuk az oszlopok számát csökkenteni úgy, hogy az adatvesztés minimális. Példaként tekinthetjük a tantárgyankénti iskolai osztályzatokat. Az összes osztályzat igazából túl sok, és az osztályzatok átlagolása egyfajta dimenziószám csökkentés, amikor egydimenziósra csökkentjük az adott esetben 15 dimenziós adatot. Viszont az egy dimenziós is egy véglet, ami sok esetben elfed fontos részleteket. Egy javaslat mondjuk a 3 dimenzióra, ami minimális adatvesztéssel jár: az egyik lehet valóban az átlag, a másik a reál és a humán tantárgyak átlagának különbsége, a harmadik meg a készségtárgyak átlaga. A gépi tanulásnak ez az ága ezt automatizálja tetszőleges adatra.
Főkomponens analízis
Angolul Principal Component Analysis, gyakori rövidítéssel PCA. Automatizálja a dimenzió redukciót. Meg kell adnunk, hogy hány dimenziósre szeretnénk redukálni az adatokat, és automatikusan elvégzi a műveletet. Példa:
from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(iris_feature) iris_feature_2d = pca.transform(iris_feature) iris_2d = pd.DataFrame({ 'PCA1': iris_feature_2d[:, 0], 'PCA2': iris_feature_2d[:, 1], 'species': iris_target.values, }) iris_2d
Az eredmény:

A PCA először kivonja mindegyik adatból az adott oszlop átlagát; a végén a visszaszámolásnál ezt hozzá kell adni. Utána állapítja meg a súlyokat: melyik eredeti komponens milyen súllyal szerepel az újabb adatokban. Ha tehát van 4 eredeti komponens, amit 2-re szeretnénk redukálni, akkor ezek a komponensek valójában egy 2 sorból és 4 oszlopból álló mátrix.
Komponensek
A komponenseket és az átlagokat az alábbi módon tudjuk megjeleníteni:
print(pca.components_) print(pca.mean_)
Az eredmény:
[[ 0.36138659 -0.08452251 0.85667061 0.3582892 ]
[ 0.65658877 0.73016143 -0.17337266 -0.07548102]]
[5.84333333 3.05733333 3.758 1.19933333]
Ezekból már némi programozással vissza tudjuk számolni az eredeti adatokat:
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width'] for i in range(150): print(i) for j in range(4): print(f' {feature_names[j]}: {pca.mean_[j] + (iris_feature_2d[i, 0] * pca.components_[0][j] + iris_feature_2d[i, 1] * pca.components_[1][j]):.2f} {iris_feature.iloc[i, j]}')
Az eredmény egy része:
0
sepal_length: 5.08 5.1
sepal_width: 3.52 3.5
petal_length: 1.40 1.4
petal_width: 0.21 0.2
1
sepal_length: 4.75 4.9
sepal_width: 3.16 3.0
petal_length: 1.46 1.4
petal_width: 0.24 0.2
2
sepal_length: 4.70 4.7
sepal_width: 3.20 3.2
petal_length: 1.31 1.3
petal_width: 0.18 0.2
3
sepal_length: 4.64 4.6
sepal_width: 3.06 3.1
petal_length: 1.46 1.5
petal_width: 0.24 0.2
4
sepal_length: 5.07 5.0
sepal_width: 3.53 3.6
petal_length: 1.36 1.4
petal_width: 0.20 0.2
Látható tehát, hogy egész jól visszakaptuk az eredeti adatokat.
Elemzés
A PCA "jóságát" az határozza meg, hogy melyik komponens a variancia mekkora részét magyarázza. Ennek megjelenítése:
pca.explained_variance_ratio_
array([0.92461872, 0.05306648])
Az első főkomponenssel tehát önmagában a varianciának közel 92,5%-magyarázza. A kettő együtt:
sum(pca.explained_variance_ratio_)
0.9776852063187949
Ha ez 100% lenne, akkor teljes egészében visszakapnánk az eredeti értékeket. Kissé ponygolán fogalmazva, a 97,8% azt jelenti, hogy ezekkel majdnem visszakapjuk úgy, hogy az adatot a felére tömörítettük.