In [2]:
# Import potrebných balíčkov
import numpy as np
import pandas as pd

from sklearn.preprocessing import StandardScaler, KBinsDiscretizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error

from keras.layers import Dense, Input, Activation
from keras.callbacks import Callback
from keras.models import Model

import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import curve_fit
Using TensorFlow backend.
In [3]:
# Uistíme sa, že máme všetky potrebné dáta
!mkdir -p data
!wget -nc -O data/sigmoid_regression_data.csv https://www.dropbox.com/s/p5q7gzupa2ndw55/sigmoid_regression_data.csv?dl=1
--2019-08-30 10:18:14--  https://www.dropbox.com/s/p5q7gzupa2ndw55/sigmoid_regression_data.csv?dl=1
Resolving www.dropbox.com (www.dropbox.com)... 162.125.8.1, 2620:100:6016:1::a27d:101
Connecting to www.dropbox.com (www.dropbox.com)|162.125.8.1|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/dl/p5q7gzupa2ndw55/sigmoid_regression_data.csv [following]
--2019-08-30 10:18:14--  https://www.dropbox.com/s/dl/p5q7gzupa2ndw55/sigmoid_regression_data.csv
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com/cd/0/get/Ank8x9XmxbkLoXGtf8NA1preBrPFPdloNDdrHyMJ8OVaDdO-pm5a-vxwH2z8-kLGU7S5vawD9KMgU8cZaopugIhrM9uQhD1VmTdGoanHTBFxQ9IE1WOFxfVFhBBBYX3FHWg/file?dl=1# [following]
--2019-08-30 10:18:14--  https://uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com/cd/0/get/Ank8x9XmxbkLoXGtf8NA1preBrPFPdloNDdrHyMJ8OVaDdO-pm5a-vxwH2z8-kLGU7S5vawD9KMgU8cZaopugIhrM9uQhD1VmTdGoanHTBFxQ9IE1WOFxfVFhBBBYX3FHWg/file?dl=1
Resolving uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com (uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com)... 162.125.8.6, 2620:100:6016:6::a27d:106
Connecting to uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com (uc261ca9896477d2e42dd9e37b41.dl.dropboxusercontent.com)|162.125.8.6|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1124 (1.1K) [application/binary]
Saving to: ‘data/sigmoid_regression_data.csv’

data/sigmoid_regres 100%[===================>]   1.10K  --.-KB/s    in 0s      

2019-08-30 10:18:15 (152 MB/s) - ‘data/sigmoid_regression_data.csv’ saved [1124/1124]

In [0]:
# Pomocný kód
class NEpochLogger(Callback):
    """
    Trieda na menej časté zobrazovanie priebehu učenia.
    """
    def __init__(self, n_epochs=100):
        super(NEpochLogger, self).__init__()
        self.n_epochs = n_epochs

    def on_epoch_end(self, epoch, logs=None):
        logs = logs or {}
        
        if epoch % self.n_epochs == 0:
            curr_loss = logs.get('loss')
            print("epoch = {}; loss = {}".format(
                epoch, curr_loss))

Regresia na báze neurónových sietí

V tomto notebooku budeme aplikovať jednoduchú neurónovú sieť zostavenú pomocou balíčka keras na regresnú úlohu. Kód bude do veľkej miery podobný kódu pre klasifikáciu. Rozdiely budú hlavne v aktivačnej funkcii poslednej vrstvy siete (bude lineárna) a v chybovej funkcii, ktorú budeme minimalizovať (stredná kvadratická chyba).

Dátová množina

Dátová množina sa bude skladať z množiny bodov v 2D priestore – načítame ju z CSV súboru:

In [5]:
df = pd.read_csv("data/sigmoid_regression_data.csv")
df.head()
Out[5]:
x y
0 -20.000000 0.004982
1 -14.911349 0.003562
2 -14.527971 0.000296
3 -7.840879 -0.001815
4 -4.745229 0.000037

Dáta rozdelíme na tréningové a testovacie. Stratifikácia sa v prípade spojitých dát nedá aplikovať priamo. Ak ju chceme predsa len použiť, musíme stĺpec, podľa ktorého stratifikujeme, najprv diskretizovať: rozdeliť vzorky do určitého konečného počtu skupín (napr. pomocou KBinsDiscretizer).

Stratifikáciu v tomto prípade nepoužijeme – nedá sa priamo aplikovať, ak príslušný stĺpec obsahuje spojité dáta. V prípade, že chceme stratifikáciu pri regresii použiť, je to stále možné, ale stĺpec, podľa ktorého stratifikujeme, treba

In [0]:
kbins = KBinsDiscretizer(5, encode='ordinal')
y_stratify = kbins.fit_transform(df[['y']])
In [0]:
df_train, df_test = train_test_split(df, stratify=y_stratify,
                                 test_size=0.3, random_state=4)

Súradnice x jednotlivých bodov použijeme ako vstupy a súradnice y ako požadované výstupy:

In [0]:
inputs = ["x"]
output = ["y"]

X_train_raw = df_train[inputs]
X_test_raw = df_test[inputs]

Y_train_raw = df_train[output]
Y_test_raw = df_test[output]

Aby sme získali lepšiu predstavu o tom, ako dáta vyzerajú, môžeme si body vykresliť.

In [9]:
plt.scatter(X_train_raw, Y_train_raw, label="tréningové dáta")
plt.scatter(X_test_raw, Y_test_raw, c='r', label="testovacie dáta")
plt.xlabel('x')
plt.ylabel('y')
plt.grid(ls='--')
plt.legend()
Out[9]:
<matplotlib.legend.Legend at 0x7f263f83a860>

Predspracovanie dát

Vstupné a výstupné dáta si môžeme preškálovať do štandardného rozsahu; v tomto jednoduchom prípade by však regresia mala fungovať správne aj bez toho.

In [0]:
input_preproc = StandardScaler()
X_train = input_preproc.fit_transform(X_train_raw)
X_test = input_preproc.transform(X_test_raw)

output_preproc = StandardScaler()
Y_train = output_preproc.fit_transform(Y_train_raw)
Y_test = output_preproc.transform(Y_test_raw)

Vytvorenie siete

Určíme počet vstupov a výstupov siete (podľa tvaru dátovej množiny):

In [0]:
num_inputs = X_train.shape[1]
num_outputs = Y_train.shape[1]

Vstupnú vrstvu vytvoríme obdobným spôsobom ako pri klasifikácii.

In [12]:
y = x = Input(shape=(num_inputs, ))
WARNING: Logging before flag parsing goes to stderr.
W0830 10:18:16.819868 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:66: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.

W0830 10:18:16.868954 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:541: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

Následne pridáme ďalšie vrstvy. Od použitých aktivačných funkcií bude čiastočne závisieť tvar výslednej regresnej krivky. Pri použití funkcií ReLU krivka napríklad nebude celkom hladká.

In [13]:
y = Dense(10)(y)
y = Activation('relu')(y)

y = Dense(10)(y)
y = Activation('relu')(y)
W0830 10:18:16.885736 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4432: The name tf.random_uniform is deprecated. Please use tf.random.uniform instead.

Výstupná vrstva bude mať lineárnu aktivačnú funkciu – aby vedela produkovať výstupy z ľubovoľného rozsahu:

In [0]:
y = Dense(num_outputs)(y)

Vytvoríme a skompilujeme model. Na regresiu môžeme ako chybovú funkciu použiť strednú kvadratickú chybu (mean squared error; mse).

In [15]:
model = Model(x, y)
model.compile(loss='mse', optimizer="adam")
W0830 10:18:16.943523 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/optimizers.py:793: The name tf.train.Optimizer is deprecated. Please use tf.compat.v1.train.Optimizer instead.

Učenie

Keď sme vytvorili model, môžeme spustiť učenie. Špecifikujeme počet epoch učenia a veľkosť minidávok.

Keďže dátová množina je maličká, môžeme použiť plne dávkové učenie: t.j. veľkosť minidávky nastavíme na veľkosť celej dátovej množiny.

In [16]:
model.fit(X_train, Y_train, nb_epoch=2500, batch_size=len(X_train),
          verbose=0, callbacks=[NEpochLogger()])
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:2: UserWarning: The `nb_epoch` argument in `fit` has been renamed `epochs`.
  
W0830 10:18:17.126494 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1033: The name tf.assign_add is deprecated. Please use tf.compat.v1.assign_add instead.

W0830 10:18:17.225032 139803922102144 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1020: The name tf.assign is deprecated. Please use tf.compat.v1.assign instead.

epoch = 0; loss = 0.935440719127655
epoch = 100; loss = 0.40028443932533264
epoch = 200; loss = 0.09120424836874008
epoch = 300; loss = 0.023633280768990517
epoch = 400; loss = 0.012680741026997566
epoch = 500; loss = 0.008387016132473946
epoch = 600; loss = 0.006463599856942892
epoch = 700; loss = 0.00525469658896327
epoch = 800; loss = 0.0043302271515131
epoch = 900; loss = 0.0036238261964172125
epoch = 1000; loss = 0.0030767577700316906
epoch = 1100; loss = 0.0025900769978761673
epoch = 1200; loss = 0.00214913347736001
epoch = 1300; loss = 0.0017839595675468445
epoch = 1400; loss = 0.0014818841591477394
epoch = 1500; loss = 0.001230199122801423
epoch = 1600; loss = 0.0010158696677535772
epoch = 1700; loss = 0.0008400504593737423
epoch = 1800; loss = 0.0006971408729441464
epoch = 1900; loss = 0.0005836157943122089
epoch = 2000; loss = 0.0004961512167938054
epoch = 2100; loss = 0.0004309553769417107
epoch = 2200; loss = 0.0003834125236608088
epoch = 2300; loss = 0.00035015211324207485
epoch = 2400; loss = 0.00032756105065345764
Out[16]:
<keras.callbacks.History at 0x7f263c73d7b8>

Testovanie

Ďalej si vytvorený model otestujeme na testovacích dátach. Ako metriky môžeme použiť strednú kvadratickú a strednú absolútnu chybu:

In [0]:
y_test = model.predict(X_test)
y_test_raw = output_preproc.inverse_transform(y_test)
In [18]:
print("MSE: {}".format(mean_squared_error(Y_test_raw, y_test_raw)))
print("MAE: {}".format(mean_absolute_error(Y_test_raw, y_test_raw)))
MSE: 0.00010780459717498158
MAE: 0.008546741627815537

Aby sme si vytvorili lepšiu predstavu o tom, aké je rozdelenie chýb, môžeme si chyby zobraziť aj v histograme. To nám dá lepšiu predstavu o tom, či robíme na všetkých vzorkách približne rovnaké chyby, či metrikám dominujú veľké chyby na menšom počte atypických vzoriek a pod.

In [19]:
sns.distplot(Y_test_raw - y_test_raw)
Out[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f25f0fac710>

Keďže dáta sú len dvojrozmerné, môžeme si navyše vykresliť aj výslednú regresnú krivku:

In [0]:
xx_raw = np.linspace(-20, 20, 100).reshape(-1, 1)
xx = input_preproc.transform(xx_raw)
yy = model.predict(xx)
yy_raw = output_preproc.inverse_transform(yy)
In [21]:
plt.scatter(X_train_raw, Y_train_raw, label="tréningové dáta")
plt.scatter(X_test_raw, Y_test_raw, c='r', label="testovacie dáta")
plt.plot(xx_raw, yy_raw, label="regresná závislosť")
plt.xlabel('x')
plt.ylabel('y')
plt.grid(ls='--')
plt.legend()
Out[21]:
<matplotlib.legend.Legend at 0x7f25efc889e8>

Ak sme vyššie použili ako aktivačné funkcie ReLU, krivka bude pravdepodobne trochu kostrbatá – bude sa skladať z viacerých lineárnych úsekov. Pri použití hladkých nelinearít môže vyzerať o niečo lepšie.

Regresia pomocou logistickej krivky a scipy

Ak si všimneme, že tvar závislosti nápadne pripomína logistickú (sigmoidnú) krivku, môžeme skúsiť body namiesto zložitej neurónovej siete nahradiť ňou. Regresiu môžeme v tom prípade vykonať napríklad pomocou funkcie curve_fit z balíčka scipy. Keďže body naozaj pochádzajú zo sigmoidnej krivky (s maličkým šumom), výsledky budú pravdepodobne podstatne lepšie.

In [0]:
def sigmoid(x, a, c):
    return 1 / (1 + np.exp(-a*x - c))
In [23]:
popt, _ = curve_fit(sigmoid,
                    X_train_raw.values.reshape(-1),
                    Y_train_raw.values.reshape(-1))

popt
Out[23]:
array([ 0.80248455, -7.0073365 ])

Výsledný model opäť otestujeme:

In [0]:
y_test_raw = sigmoid(X_test_raw, *popt)
In [25]:
print("MSE: {}".format(mean_squared_error(Y_test_raw, y_test_raw)))
print("MAE: {}".format(mean_absolute_error(Y_test_raw, y_test_raw)))
MSE: 1.3942722453847544e-05
MAE: 0.00317024727519068
In [26]:
sns.distplot(Y_test_raw.values - y_test_raw)
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f25efc88668>

A zobrazíme výslednú regresnú závislosť:

In [27]:
xx = np.arange(-20, 20, 0.05)
yy = sigmoid(xx, *popt)

plt.scatter(X_train_raw, Y_train_raw, label="tréningové dáta")
plt.scatter(X_test_raw, Y_test_raw, c='r', label="testovacie dáta")
plt.plot(xx, yy, label="regresná závislosť")
plt.xlabel('x')
plt.ylabel('y')
plt.grid(ls='--')
plt.legend()
Out[27]:
<matplotlib.legend.Legend at 0x7f25efbfba58>

Regresia pomocou logistickej krivky a balíčka keras

Keďže logistická (sigmoidná) krivka sa používa(la) aj ako aktivačná funkcia v neurónových sieťach, môžeme obdobné výsledky dosiahnuť aj pomocou balíčka keras. Na vstup najprv aplikujeme jednoduchú lineárnu transformáciu pomocou vrstvy Dense a následne aplikujeme sigmoidnú aktivačnú funkciu.

Hoci používame inú optimalizačnú metódu, výsledky by mali byť podobne dobré ako pri funkcii curve_fit.

In [0]:
num_inputs = X_train_raw.shape[1]
num_outputs = Y_train_raw.shape[1]

y = x = Input(shape=(num_inputs, ))

y = Dense(num_outputs)(y)
y = Activation('sigmoid')(y)

sig_model = Model(x, y)
sig_model.compile(loss='mse', optimizer="adam")
In [29]:
sig_model.fit(X_train_raw, Y_train_raw, nb_epoch=10000, 
              batch_size=len(X_train_raw),
              verbose=0, callbacks=[NEpochLogger()])
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:3: UserWarning: The `nb_epoch` argument in `fit` has been renamed `epochs`.
  This is separate from the ipykernel package so we can avoid doing imports until
epoch = 0; loss = 0.5810453295707703
epoch = 100; loss = 0.5256920456886292
epoch = 200; loss = 0.28279152512550354
epoch = 300; loss = 0.09974651038646698
epoch = 400; loss = 0.07733771950006485
epoch = 500; loss = 0.06771746277809143
epoch = 600; loss = 0.06126627326011658
epoch = 700; loss = 0.056280408054590225
epoch = 800; loss = 0.05218356102705002
epoch = 900; loss = 0.04869919642806053
epoch = 1000; loss = 0.045667681843042374
epoch = 1100; loss = 0.04298671707510948
epoch = 1200; loss = 0.04058612510561943
epoch = 1300; loss = 0.03841541334986687
epoch = 1400; loss = 0.0364367850124836
epoch = 1500; loss = 0.034621238708496094
epoch = 1600; loss = 0.032945938408374786
epoch = 1700; loss = 0.03139255940914154
epoch = 1800; loss = 0.02994616888463497
epoch = 1900; loss = 0.028594402596354485
epoch = 2000; loss = 0.027326950803399086
epoch = 2100; loss = 0.026135077700018883
epoch = 2200; loss = 0.025011388584971428
epoch = 2300; loss = 0.02394947037100792
epoch = 2400; loss = 0.022943811491131783
epoch = 2500; loss = 0.021989580243825912
epoch = 2600; loss = 0.021082594990730286
epoch = 2700; loss = 0.02021910808980465
epoch = 2800; loss = 0.0193958543241024
epoch = 2900; loss = 0.018609918653964996
epoch = 3000; loss = 0.01785868965089321
epoch = 3100; loss = 0.017139848321676254
epoch = 3200; loss = 0.0164512787014246
epoch = 3300; loss = 0.01579110138118267
epoch = 3400; loss = 0.015157623216509819
epoch = 3500; loss = 0.014549295417964458
epoch = 3600; loss = 0.013964701443910599
epoch = 3700; loss = 0.01340256817638874
epoch = 3800; loss = 0.012861725874245167
epoch = 3900; loss = 0.012341092340648174
epoch = 4000; loss = 0.011839683167636395
epoch = 4100; loss = 0.01135660707950592
epoch = 4200; loss = 0.010891028679907322
epoch = 4300; loss = 0.010442158207297325
epoch = 4400; loss = 0.010009304620325565
epoch = 4500; loss = 0.009591804817318916
epoch = 4600; loss = 0.009189036674797535
epoch = 4700; loss = 0.008800424635410309
epoch = 4800; loss = 0.00842544436454773
epoch = 4900; loss = 0.008063597604632378
epoch = 5000; loss = 0.007714399136602879
epoch = 5100; loss = 0.007377433590590954
epoch = 5200; loss = 0.007052273489534855
epoch = 5300; loss = 0.006738530937582254
epoch = 5400; loss = 0.0064358278177678585
epoch = 5500; loss = 0.0061438302509486675
epoch = 5600; loss = 0.005862199701368809
epoch = 5700; loss = 0.005590624641627073
epoch = 5800; loss = 0.005328792612999678
epoch = 5900; loss = 0.0050764321349561214
epoch = 6000; loss = 0.0048332661390304565
epoch = 6100; loss = 0.004599018953740597
epoch = 6200; loss = 0.004373435862362385
epoch = 6300; loss = 0.004156277980655432
epoch = 6400; loss = 0.003947301767766476
epoch = 6500; loss = 0.003746291622519493
epoch = 6600; loss = 0.0035530116874724627
epoch = 6700; loss = 0.0033672568388283253
epoch = 6800; loss = 0.0031888214871287346
epoch = 6900; loss = 0.0030174890998750925
epoch = 7000; loss = 0.0028530731797218323
epoch = 7100; loss = 0.00269537465646863
epoch = 7200; loss = 0.002544205170124769
epoch = 7300; loss = 0.0023993821814656258
epoch = 7400; loss = 0.002260725712403655
epoch = 7500; loss = 0.0021280597429722548
epoch = 7600; loss = 0.0020012143068015575
epoch = 7700; loss = 0.0018800138495862484
epoch = 7800; loss = 0.0017642962047830224
epoch = 7900; loss = 0.001653897576034069
epoch = 8000; loss = 0.0015486603369936347
epoch = 8100; loss = 0.0014484231360256672
epoch = 8200; loss = 0.001353036263026297
epoch = 8300; loss = 0.0012623451184481382
epoch = 8400; loss = 0.001176200807094574
epoch = 8500; loss = 0.0010944587411358953
epoch = 8600; loss = 0.0010169738670811057
epoch = 8700; loss = 0.00094360182993114
epoch = 8800; loss = 0.0008742084028199315
epoch = 8900; loss = 0.0008086543530225754
epoch = 9000; loss = 0.0007468031835742295
epoch = 9100; loss = 0.0006885233451612294
epoch = 9200; loss = 0.0006336840451695025
epoch = 9300; loss = 0.0005821570521220565
epoch = 9400; loss = 0.0005338143091648817
epoch = 9500; loss = 0.0004885352682322264
epoch = 9600; loss = 0.00044619350228458643
epoch = 9700; loss = 0.00040667183930054307
epoch = 9800; loss = 0.00036984781036153436
epoch = 9900; loss = 0.0003356074448674917
Out[29]:
<keras.callbacks.History at 0x7f25efbfb6d8>

Testovanie:

In [0]:
y_test_raw = sig_model.predict(X_test_raw)
In [31]:
print("MSE: {}".format(mean_squared_error(Y_test_raw, y_test_raw)))
print("MAE: {}".format(mean_absolute_error(Y_test_raw, y_test_raw)))
MSE: 0.00029638750594415973
MAE: 0.013528653420752788
In [32]:
sns.distplot(Y_test_raw.values - y_test_raw)
Out[32]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f25efa245f8>
In [0]:
xx_raw = np.linspace(-20, 20, 100).reshape(-1, 1)
yy_raw = sig_model.predict(xx_raw)
In [34]:
plt.scatter(X_train_raw, Y_train_raw, label="tréningové dáta")
plt.scatter(X_test_raw, Y_test_raw, c='r', label="testovacie dáta")
plt.plot(xx_raw, yy_raw, label="regresná závislosť")
plt.xlabel('x')
plt.ylabel('y')
plt.grid(ls='--')
plt.legend()
Out[34]:
<matplotlib.legend.Legend at 0x7f25ef85a908>
In [0]: