# Import potrebných balíčkov
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.impute import SimpleImputer
# Uistíme sa, že máme všetky potrebné dáta
!mkdir -p data
!wget -nc -O data/iris.csv https://www.dropbox.com/s/v3ptdkv5fvmx5zk/iris.csv?dl=1
Ako treba postupovať, ak chceme v Python-e natrénovať jednoduchý model – napríklad rozhodovací strom – už vieme:
Pozrime sa na jednoduchý príklad preškálovania a prekódovania. Povedzme, že máme nasledujúcu dátovú množinu:
df = pd.DataFrame(
[
["Vendelín Lopota", "male", 15],
["Múdroslav Kucharčík", "male", 32],
["Pamfília Hamižná", "female", 12],
["Klotylda Berevčáková", "female", 89]
],
columns = ["name", "gender", "age"]
)
df
gender
¶Stĺpec gender
obsahuje kategorickú premennú – textové hodnoty budeme chcieť prekódovať na číselné:
ordenc = OrdinalEncoder()
df['gender_num'] = ordenc.fit_transform(df[['gender']])
df[['gender', 'gender_num']]
Ako vidno, pohlavie female
bolo prekódované ako 0 a pohlavie male
ako 1. Overiť si to môžeme aj podľa poradia jednotlivých označení v nasledujúcom zozname:
ordenc.categories_
age
¶Stĺpec age
obsahuje číselné hodnoty – štandardizujeme ich (stredná hodnota 0 a rozptyl 1):
scaler = StandardScaler()
df['age_scaled'] = scaler.fit_transform(df[['age']])
df[['age', 'age_scaled']]
Dajme tomu, že sme vyššie uvedený kód použili na predspracovanie dátovej množiny, na ktorej sme natrénovali model. Teraz dostaneme nové dáta a aj pre ne chceme vykonať predikciu. Ako ich predspracujeme?
df_new = pd.DataFrame(
[
["Polykarp Chrapota", "male", 32],
["Lucián Chrasta", "male", 45]
],
columns = ["name", "gender", "age"]
)
Prirodzene, že nechceme celý kód znovu zopakovať. Povedzme, že budeme teda postupovať naivne: kód si obalíme do funkcie preprocess
a tú istú funkciu zavoláme najprv pre pôvodné a potom pre nové dáta.
def preprocess(df):
ordenc = OrdinalEncoder()
df['gender_num'] = ordenc.fit_transform(df[['gender']])
scaler = StandardScaler()
df['age_scaled'] = scaler.fit_transform(df[['age']])
return df
Funkcia je hotová – čo sa teda stane, keď ju aplikujeme na pôvodné a na nové dáta?
preprocess(df)
preprocess(df_new)
Na prvý pohľad sa zdá, že všetko funguje – ale len kým sa na výsledky nepozrieme pozornejšie. Potom zistíme, že zatiaľ čo na pôvodných dátach bol textový reťazec male
prekódovaný ako 1, v nových dátach je prekódovaný ako 0. Nekonzistentne je prekódovaný aj vek.
Prečo vznikol taký rozdiel? Samozrejme preto, lebo sme pri predspracovaní vnútri funkcie preprocess
vytvorili nové transformátory, ktoré nevedia nič o pôvodnej dátovej množine. Keďže v novej dátovej množine sa vyskytuje len hodnota male
, samozrejme je prekódovaná ako 0 a nie ako 1. Vekový rozsah je menší – 32 je v novej dátovej množine minimálny vek, preto je tiež prekódovaný inak.
Aby sme vyššie uvedený kód opravili, museli byť sme zabezpečiť, že si odložíme transformátory ordenc
a scaler
a tie isté znovu použijeme aj pri spracovaní nových dát. Pri zložitejších dátových množinách, kde je predspracovanie netriviálne, je to však prácne a ľahko môže dôjsť ku chybám. Existuje preto nástroj, ktorý to uľahčuje: sklearn pipelines.
Ukážme si teda, ako by sa vyššie uvedený príklad implementoval pomocou pipelines:
from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
Použijeme funkciu make_column_transformer
, ktorá nám umožní na každý stĺpec (alebo skupinu stĺpcov) aplikovať inú transformáciu:
categorical_inputs = ['gender']
numeric_inputs = ['age']
preproc_pipeline = make_column_transformer(
(OrdinalEncoder(), categorical_inputs),
(StandardScaler(), numeric_inputs)
)
Najprv pomocou funkcie fit_transform
vytvorený pipeline objekt nastavíme a zároveň aj predspracujeme pôvodnú dátovú množinu:
preproc_pipeline.fit_transform(df)
Následne môžeme to isté predspracovanie jednoducho aplikovať aj na nové dáta pomocou funkcie transform
:
preproc_pipeline.transform(df_new)
Ako vidno, v tomto prípade sa už hodnoty transformujú konzistentne.
df = pd.read_csv("data/iris.csv")
df.head()
Na tréningové a testovacie dáta je najlepšie deliť hneď na začiatku – aby sme v rámci predspracovania predsa len do tréningových dát nepreniesli nejakú informáciu z testovacej množiny. V dátových súťažiach bývajú dáta niekedy už predrozdelené. Parameter random_state
špecifikuje jadro pseudonáhodného generátora. Za normálnych okolností sa dátová množina rozdelí zakaždým inak. My budeme jadro niekedy fixovať, aby sme v každom behu dostali podľa možnosti podobné výsledky.
df_train, df_test = train_test_split(df, test_size=0.25,
stratify=df['species'],
random_state=4)
Prvé štyri stĺpce obsahujú vstupy, posledný stĺpec triedu:
categorical_inputs = []
numeric_inputs = ['sepal length (cm)', 'sepal width (cm)',
'petal length (cm)', 'petal width (cm)']
output = ['species']
X_train = df_train[categorical_inputs + numeric_inputs]
Y_train_l = df_train[output]
X_test = df_test[categorical_inputs + numeric_inputs]
Y_test_l = df_test[output]
V prípade dátovej množiny Iris máme len numerické vstupy. Aplikujme na ne škálovanie. Kategorické vstupy nie sú žiadne, takže príslušný riadok by sme správne mali úplne vynechať – uvádzame ho len preto, aby sa tie isté kúsky kódu dali ľahko použiť pri ďalších príkladoch. Takisto kvôli všeobecnosti pridávame aj transformátor SimpleImputer
– triviálny spôsob ako ošetriť chýbajúce dáta, ak by v dátovej množine náhodou boli.
input_preproc = make_column_transformer(
(make_pipeline(
SimpleImputer(strategy='constant', fill_value='MISSING'),
OrdinalEncoder()
),
categorical_inputs),
(make_pipeline(
SimpleImputer(),
StandardScaler()
),
numeric_inputs)
)
Prekódujeme výstupný stĺpec:
output_preproc = OrdinalEncoder()
Y_train = output_preproc.fit_transform(Y_train_l)
Y_test = output_preproc.transform(Y_test_l)
Po predspracovaní budeme na dáta aplikovať rozhodovací strom. Pipeline na predspracovanie zreťazíme s pipeline pre učenie:
model = make_pipeline(
input_preproc,
DecisionTreeClassifier()
)
Ďalej môžeme už trénovať celý pipeline objekt – fázu predspracovania aj rozhodovací strom:
model = model.fit(X_train, Y_train)
Otestujeme zovšeobecnenie:
y_test = model.predict(X_test)
y_test_l = output_preproc.inverse_transform(y_test.reshape(-1, 1))
cm = pd.crosstab(Y_test_l.values.reshape(-1),
y_test_l.reshape(-1),
rownames=['actual'],
colnames=['predicted'])
print(cm)
print("Accuracy = {}".format(accuracy_score(Y_test, y_test)))