Nauč se Python > Kurzy > Datový kurz PyLadies > Strojové učení - pokračování regrese > Knihovna Scikit-learn

Knihovna Scikit-learn, přehled užitečných funkcí

V předešlé hodině jsme se vyhýbali programování, jak jen se to dalo. Teď už si ale chceš také sama vše vyzkoušet. Abys mohla úlohu rozmyšlenou v domácím úkolu naprogramovat, projdeme si nejdůležitější funkce, které budeš potřebovat.

Především budeme používat knihovnu Sciki-learn a samozrejmě také pandas. Potřebné věci projdeme na příkladu.

In [1]:
import pandas as pd

Načtení a příprava dat

Na začátku vždy bude potřeba připravit data. Čištění dat a použití knihovny pandas už bys měla ovládat, zaměříme se jen na věci, které jsou specifické pro strojové učení.

Načíst data tedy umíš.

In [2]:
df_platy = pd.read_csv("static/salaries.csv", index_col=0)
df_platy.sample(10)
Out[2]:
rank discipline yrs.since.phd yrs.service sex salary
3 AsstProf B 4 3 Male 79750
169 AssocProf B 8 6 Male 101210
73 Prof B 29 19 Male 100131
28 AsstProf B 5 3 Male 82379
163 AssocProf B 22 7 Male 98510
96 AsstProf B 4 0 Male 84000
100 Prof B 41 26 Male 106689
51 Prof B 28 28 Male 126621
190 Prof B 25 19 Male 153750
97 AssocProf B 17 12 Male 95611

Pro predikci použijeme jako příznaky rank, discipline, yrs.since.phd, yrs.service a sex, predikovat budeme hodnotu salary.

Pro učení potřebujeme všechny hondoty převést na čísla (float). Pokud by data obsahovala chybějící hodnoty, nejjednodušší řešení je takové řádky zahodit. (Bonus: pokud bys měla data s větším množstvím chybějících hodnot, podívej se na možnosti sklearn.impute)

Důležité je vypořádat se s kategorickými hodnotami. Sloupce obsahující hodnoty typu Boolean nebo dvě hodnoty (např. muž/žena), lze snadno převést na hodnoty $[0,1]$.

In [3]:
df_platy = df_platy.replace({"Male": 0, "Female": 1})
df_platy.sample(10)
Out[3]:
rank discipline yrs.since.phd yrs.service sex salary
178 AssocProf B 13 9 0 100944
165 AsstProf B 1 0 0 88795
107 AssocProf A 11 8 0 82099
89 Prof B 25 25 0 172272
6 AssocProf B 6 6 0 97000
7 Prof B 30 23 0 175000
182 Prof B 18 5 0 141136
179 Prof B 27 14 0 147349
88 AsstProf B 2 2 0 88400
69 Prof B 17 17 1 111512

Pro kategorické proměnné s více možnostmi použijeme tzv. onehot encoding.

Např. sloupec rank obsahuje hodnoty Prof, AsstProf a AssocProf. K zakódování pomocí onehot encoding potřebujeme tři sloupce:

Původní hodnota Kód
Prof 1 0 0
AsstProf 0 1 0
AssocProf 0 0 1

Knihovna Scikitlearn nabízí sklearn.preprocessing.OneHotEncoder, při práci s pandas však můžeme použít rovnou metodu get_dummies. (Pozn. dummies proto, že nám přibudou pomocné proměnné (sloupce), které se označují jako dummy variables.)

In [4]:
df_platy = pd.get_dummies(df_platy)
df_platy
Out[4]:
yrs.since.phd yrs.service sex salary rank_AssocProf rank_AsstProf rank_Prof discipline_A discipline_B
1 19 18 0 139750 0 0 1 0 1
2 20 16 0 173200 0 0 1 0 1
3 4 3 0 79750 0 1 0 0 1
4 45 39 0 115000 0 0 1 0 1
5 40 41 0 141500 0 0 1 0 1
... ... ... ... ... ... ... ... ... ...
194 19 19 0 86250 1 0 0 0 1
195 48 53 0 90000 1 0 0 0 1
196 9 7 0 113600 1 0 0 0 1
197 4 4 0 92700 0 1 0 0 1
198 4 4 0 92000 0 1 0 0 1

198 rows × 9 columns

Poslední krok předzpracování bývá přeškálování hodnot. Není to vždy nutné, ale některým modelům to může pomoci. Využijeme StandardScaler.

StandardScaler nám hodnoty přeškáluje, aby zhruba odpovídaly normálnímu rozdělení. Některé algoritmy to předpokládají. Může se pak např. stát, že příznak (sloupeček), která má výrazně větší rozptyl než ostatní, je brán jako významnější.

(Pozn.: vytvoříme zvlášť objekt na transformaci odezvy. Ten se nám bude později hodit, až budeme chtít z odezvy modelu vidět, jaká je opravdová hodnota platu. Tedy zkonvertovat predikované hodnoty zpět na hodnotu platu.).

In [5]:
from sklearn.preprocessing import StandardScaler 

priznaky_ke_konverzi = ["yrs.since.phd", "yrs.service"]
odezva = ["salary"]

transformace = StandardScaler()
df_platy[priznaky_ke_konverzi] = transformace.fit_transform(df_platy[priznaky_ke_konverzi])

transformace_odezva = StandardScaler()
df_platy[odezva] = transformace_odezva.fit_transform(df_platy[odezva])

df_platy.sample(10)
Out[5]:
yrs.since.phd yrs.service sex salary rank_AssocProf rank_AsstProf rank_Prof discipline_A discipline_B
147 -1.238251 -0.940387 0 -0.669128 0 1 0 0 1
36 -1.156717 -1.276633 1 -1.194624 0 1 0 0 1
91 -0.749045 -0.856325 1 -0.492841 0 1 0 0 1
128 -1.401320 -1.276633 1 -1.352272 0 1 0 1 0
86 -0.341373 -0.099770 0 0.761097 0 0 1 0 1
184 0.555504 0.572723 0 1.362790 0 0 1 0 1
92 -0.749045 -0.688202 0 -0.209213 1 0 0 0 1
115 -0.585976 -1.276633 1 -0.213698 0 0 1 1 0
79 -1.319785 -1.192571 0 -0.875823 0 1 0 0 1
55 -0.585976 -0.351955 0 -0.257139 1 0 0 0 1

Vytvoření trénovací a testovací množiny

V teorii strojového učení se vstupy modelu (příznaky, vstupní proměnné) typicky označují písmenem X a výstupy písmenem y. Řada programátorů toto používá i k označování proměnných v kódu. X představuje matici (neboli tabulku), kde každý řádek odpovídá jednomu datovému vzorku a každý sloupec jednomu příznaku (vstupní proměnné). y je vektor, neboli jeden sloupec s odezvou.

Na vyzobnutí odezvy se výborně hodí metoda pop.

In [6]:
y = df_platy.pop("salary")
X = df_platy 

print(X.columns)
print(y.name)
Index(['yrs.since.phd', 'yrs.service', 'sex', 'rank_AssocProf',
       'rank_AsstProf', 'rank_Prof', 'discipline_A', 'discipline_B'],
      dtype='object')
salary

Zbývá data rozdělit na trénovací a testovací. K tomu slouží metoda train_test_split. Data nám rozdělí náhodně na trénovací a testovací sadu. Velikost testovací množiny můžeme specifikovat parametrem test_size, jeho defaultní hodnota je 0.25, t. j. 25%.

In [7]:
from sklearn.model_selection import train_test_split 

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

Modely

Můžeme přejít k samotnému učení. Vybereme si model. Přehled modelů najdeš v sekci Supervised learnig.

Na regresi můžeš použít:

  • LinearRegression

  • Lasso

    • hyperparametry:
      • alpha, float, default=1.0
  • SVR

    • hyperparametry:
      • kernel, default rbf, one of ‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’
      • C, float, optional (default=1.0)

Na klasifikační úlohy (ke kterým se dostaneme v této hodině) využiješ:

Vytvoříme instanci vybraného modelu (jde nám teď jen o způsob použití knihovny, vezmeme nejjednodušší lineární regresi):

In [8]:
from sklearn.linear_model import LinearRegression

model = LinearRegression()

Trénování

Model natrénujeme na trénovací množině:

In [9]:
model.fit(X_train, y_train)
Out[9]:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

Predikce

Natrénovaný model typicky chceme použít k ohodnocení nějakých nových datových vzorků, k tomu máme metodu predict. Zavolejme ji jak na trénovací, tak na testovací data.

In [10]:
train_predikce = model.predict(X_train)
test_predikce = model.predict(X_test)

Pozor, k učení jsme použili transformované hodnoty odezvy. Zajímají-li nás skutečné hodnoty platů, musíme i predikované hodnoty transformovat zpět.

In [11]:
# vypišme prvních deset tetovacích vzorků a jejich predikce
odezva = transformace_odezva.inverse_transform(test_predikce)
print(f"odezva modelu    predicke platu")
for i in range(5):
    print(f"{test_predikce[i]:>10.2f}         {odezva[i]:>10.2f}")
odezva modelu    predicke platu
      0.87          136032.01
     -0.84           86985.07
     -0.86           86458.72
      0.91          137053.05
      0.82          134521.00

Evaluace modelu

Můžeme využít funkci score, která nám vrátí hodnotu $R^2$ metriky:

In [12]:
print("R2 na trénovací množině: ", model.score(X_train, y_train))
print("R2 na testovací množině: ", model.score(X_test, y_test))
R2 na trénovací množině:  0.541472204772119
R2 na testovací množině:  0.4311555324751409

Funkce pro všechny možné metriky najdeš v sklearn.metrics. (nyní nás zajímají regresní metriky)

In [27]:
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

MAE_train = mean_absolute_error(y_train, train_predikce)
MAE_test  = mean_absolute_error(y_test, test_predikce)
MSE_train = mean_squared_error(y_train, train_predikce) 
MSE_test = mean_absolute_error(y_test, test_predikce)
R2_train = r2_score(y_train, train_predikce)
R2_test = r2_score(y_test, test_predikce)

print("     Trénovací data  Testovací data")
print(f"MAE {MAE_train:>15.3f} {MAE_test:>15.3f}")
print(f"MSE {MSE_train:>15.3f} {MSE_test:>15.3f}")
print(f"R2  {R2_train:>15.3f} {R2_test:>15.3f}")
     Trénovací data  Testovací data
MAE           0.519           0.455
MSE           0.511           0.455
R2            0.541           0.431

Hyper-parametry a výběr toho správného modelu

Pojďme si zkusit pohrát s nastavením různých hyper-parametrů a zkusit použít několik různých modelů pro řešení stejného problému.

In [28]:
from sklearn.linear_model import LinearRegression, Lasso, SGDRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.svm import SVR

models = [
    (LinearRegression, {}),
    (Lasso, {"alpha": 1.0}),
    (Lasso, {"alpha": 0.5}), 
    (SVR, {"kernel": "rbf"}),
    (SVR, {"kernel": "poly"})
]

def train_test_model(model_class, params):
    model = model_class(**params)
    model.fit(X_train, y_train)
    train_pred = model.predict(X_train)
    test_pred = model.predict(X_test)
    MAE_train = mean_absolute_error(y_train, train_pred)
    MAE_test = mean_absolute_error(y_test, test_pred)
    MSE_train = mean_squared_error(y_train, train_pred)
    MSE_test = mean_squared_error(y_test, test_pred)
    R2_train = r2_score(y_train, train_pred)
    R2_test = r2_score(y_test, test_pred)
    print(f"Model: {model_class} parametry: {params}")
    print("     Trénovací data  Testovací data")
    print(f"MAE {MAE_train:>15.3f} {MAE_test:>15.3f}")
    print(f"MSE {MSE_train:>15.3f} {MSE_test:>15.3f}")
    print(f"R2  {R2_train:>15.3f} {R2_test:>15.3f}")
In [29]:
for model, params in models:
    train_test_model(model, params)
Model: <class 'sklearn.linear_model._base.LinearRegression'> parametry: {}
     Trénovací data  Testovací data
MAE           0.519           0.455
MSE           0.511           0.404
R2            0.541           0.431
Model: <class 'sklearn.linear_model._coordinate_descent.Lasso'> parametry: {'alpha': 1.0}
     Trénovací data  Testovací data
MAE           0.854           0.703
MSE           1.116           0.744
R2            0.000          -0.047
Model: <class 'sklearn.linear_model._coordinate_descent.Lasso'> parametry: {'alpha': 0.5}
     Trénovací data  Testovací data
MAE           0.854           0.703
MSE           1.116           0.744
R2            0.000          -0.047
Model: <class 'sklearn.svm._classes.SVR'> parametry: {'kernel': 'rbf'}
     Trénovací data  Testovací data
MAE           0.470           0.416
MSE           0.474           0.339
R2            0.575           0.523
Model: <class 'sklearn.svm._classes.SVR'> parametry: {'kernel': 'poly'}
     Trénovací data  Testovací data
MAE           0.459           0.436
MSE           0.466           0.405
R2            0.582           0.430

Uložení modelu

Někdy si potřebujeme naučený model uchovat na další použití. Model lze uložit do souboru a zase načíst pomocí pickle. Kujme pikle:

In [25]:
import pickle 

with open("model.pickle", "wb") as soubor:
    pickle.dump(model, soubor)


with open("model.pickle", "rb") as soubor:
    staronovy_model = pickle.load(soubor)

Pozn.: Bohužel mám poměrně bohaté špatné zkušenosti z načítáním modelů uložených před delším časem (bývá problém načíst model uložený ve starší verzi Scikit-learn ve verzi novější).

Bonusy:

  • volba vhodného modelu a jeho hyper-parametrů se skrývá pod klíčovým slovem model selection. Knihovna Scikit-learn obsahuje různé pomůcky k ulehčení toho výběru. Přesahuje to ale rámec tohoto kurzu, narazíš-li na to toho téma při samostudiu, pročti si sklear.model_selection.

  • v příkladu výše jsme použili různé transformace nad daty a pak teprve tvorbu modelu. Až budeš v těchto věcech zběhlejší, bude se ti hodit propojit tyto věci dohromady. K tomu slouží tzv. pipeline.


Toto je stránka lekce z kurzu, který probíhá nebo proběhl naživo s instruktorem.