Modele regresji liniowej szybko i łatwo z scikit learn

Rozmawiająć z osobami, które zawodowo wykorzystują metody analizy danych byłem zaskoczony jak wiele modeli predykcyjnych opartych jest na regresji liniowej. Jest to jedna z podstawowych technik w arsenale analityka, stosukowo prosta w implementacji oraz zrozumieniu, a jednak niezwykle efektywna i użyteczna. Dzięki bibliotece scikit-learn jesteśmy w stanie w kilku wierszach kodu python’a zaimplementować jej podstawowe rodzaje.

Regresja liniowa wprowadzenie


Celem regresji liniowej jest dopasowanie do danych doświadczalnych/historycznych prostej, która oddaje charakter tych danych. Zadanie polega na znalezieniu funkcji liniowej \(f(x) \rightarrow \mathbf{R}\) postaci:
\begin{equation}
f(x) = w_0 + w_1x_1+w_2x_2+…w_nx_n
\label{eq1}
\end{equation}

gdzie \(x \in R^N\) a \(w \in R^N\) jest wektorem zawierającym współczynniki prostej. I to właśnie na znalezieniu wektora \(w\) będziemy skupiać swoją uwagę.

Metoda najmniejszych kwadratów

Mając dane historyczne  \(X = \{x^1,x^2,…,x^k \} , x^i \in R^N\)  oraz odpowiadające im wartości \(Y=\{y^1, y^2,…,y^k\}\) będziemy poszukiwać najlepiej dopasowanego wektora \(w\). Co to dla nas znaczy, najlepiej dopasowanego?

Mianowicie takiego, dla którego wartości zwracane przez funkcję \(f\) i odpowiadająca wartości rzeczywiste najmniej się różnią. Załóżmy że mamy już wybrany wektor np. \(w=[1,0.5,0,0,2,0.3,…,1]\) możemy obliczyć:

\begin{align}
f(x^1)= & w_0 + w_1x^1_1+w_2x^1_2+…w_nx^1_n = w_0 + (x^1)^T*w\\
f(x^2)= & w_0 + w_1x^2_1+w_2x^2_2+…w_nx^2_n = w_0 + (x^2)^T*w \\
\cdots  &\\
f(x^k)= & w_0 + w_1x^k_1+w_2x^k_2+…w_nx^k_n =  w_0 + (x^k)^T*w\\
\end{align}

Wartości te możemy porównać z rzeczywistymi odpowiadającymi wartościami \(y\), stąd mamy

\begin{align}
e^1 = &f(x^1)- y^1 \\
e^2 = &f(x^2)- y^2\\
\cdots  &\\
e^k= &f(x^k)- y^k \\
\end{align}

Następnie sumując kwadraty tych błędów \(e^1, …, e^k\) otrzymamy wyrażenie:

$$E(w) =  \sum_{i=1}^{k}\left(f(x^i)-y^i\right)^2 $$

Oczywiście chcielibyśmy aby suma kwadratów błędów była jak najmniejsza, a jedyne co możemy zmieniać to wartości współczynników wektora \(w\). W ten sposób zdefiniowaliśmy zadanie minimalizacji \(E\) względem \(w\). Aby skrócić i ułatwić sobie zapis powyższe można zapisać w postaci macierzowej, jeżeli \(X\) zapiszemy jako macierz, w której poszczególne egzemplarze \(x^i\) zostaną ułożone w wierszach, wektor \(w\) jest wektorem kolumnowym, a \(Y\) potraktujemy także jako wektor kolumnowy całość możemy zapisać w postaci:

$$ \underset{w}{min} ||X*w-Y||_2^2 $$

 Rodzaje regresji liniowej

W zależności od definicji zadania minimalizacji możemy wyróżnić kilka typów regresji:

  • regresja liniowa $$ \underset{w}{min} ||X*w-Y||_2^2 $$
  • regresja grzbietowa (Ridge) $$ \underset{w}{min} ||X*w-Y||_2^2 + \alpha ||w||_2^2$$
  • regresja Lasso $$ \underset{w}{min} ||X*w-Y||_2^2 + \alpha ||w||_1$$
  • Elastic Net $$\underset{w}{min} { \frac{1}{2n_{samples}} ||X w – y||_2 ^ 2 + \alpha \rho ||w||_1 + \frac{\alpha(1-\rho)}{2} ||w||_2 ^ 2} $$

Kolejne trzy typy regresji dokładają do zadania optymalizacji dodatkowy czynnik (penalty, regularization) wpływający na cechy wektora \(w\). W regresji Ridge dodatkowo dodajemy warunek aby norma \(l_2\) wektora \(w\) była minimalizowana, dzięki czemu wymuszamy aby poszczególne współczynniki nie były za duże. Natomiast w regresji Lasso użycie normy \(l_1\) preferuje rozwiązania, w których wektor jest rzadki (ma dużo zer). Regresja Elastic Net łączy w sobie cechy Ridge oraz Lasso, poprzez zastosowanie dwóch typów norm, których istotność względem siebie kontroluje parametr \(\rho\)

Regresja liniowa przykłady w scikit-learn

No dobra, dość teorii! Czas zacząć kodować. Dzięki wykorzystaniu biblioteki scikit-learn wraz z zaimplementowanymi algorytmami regresji jesteśmy w stanie bardzo szybko nauczyć nasz model.

Implementacja dowolnego typu regresji jest bardzo prosta, tworzymy obiekt odpowiedniej klasy (LinearRegressionRidgeLassoElasticNet), na którym wywołujemy metodę fit podając jej za argumenty zbiór treningowy, docelowe wartości oraz ewentualne parametry (\(\alpha, \rho\)). Gdy model się nauczy to możemy wywołać metodę predict, która zwróci przewidywaną wartość. Ogólny szablon przedstawia się następująco

import numpy as np
from sklearn import datasets, linear_model

#########
# Load some data !
#...
#########
# Create linear regression object
regr = linear_model.LinearRegression()

# Train the model using the training sets
regr.fit(X_train, Y_train)
# Predict values
Y_predicted = regr.predict(X_test)

# The coefficients
print('Coefficients: \n', regr.coef_)
# The mean square error
error = np.mean((regr.predict(X_test) - Y_test) ** 2)
print("Residual sum of squares: {}".format(error))

Dodatkowo warto zobaczyć jak wyglądają parametry prostej (regr.coef) oraz obliczyć rzeczywisty popełniony błąd średnio-kwadratowy.

Przykład 1. Regresja liniowa dla zbioru Diabetes

Zróbmy wizualizację prostych regresji obliczonych dla poszczególnych zmiennych w zbiorze Diabetes.

import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets, linear_model

# Load the diabetes dataset
diabetes = datasets.load_diabetes()

f, axarr = plt.subplots(5,2, sharex=True, sharey=True,figsize=(12,12))
for i in range(0,5):
    for j in range(0,2):
        # Use only one feature
        diabetes_X = diabetes.data[:, np.newaxis, i*2+j]
        
        # Split the data into training/testing sets
        diabetes_X_train = diabetes_X[:-20]
        diabetes_X_test = diabetes_X[-20:]
        
        # Split the targets into training/testing sets
        diabetes_y_train = diabetes.target[:-20]
        diabetes_y_test = diabetes.target[-20:]
        
        # Create linear regression object
        regr = linear_model.LinearRegression()
        
        # Train the model using the training sets
        regr.fit(diabetes_X_train, diabetes_y_train)
        
        # The coefficients
        print('Coefficients: \n', regr.coef_)
        # The mean square error
        print("Residual sum of squares: %.2f"
              % np.mean((regr.predict(diabetes_X_test) - diabetes_y_test) ** 2))
        # Explained variance score: 1 is perfect prediction
        print('Variance score: %.2f' % regr.score(diabetes_X_test, diabetes_y_test))
        
        # Plot outputs
        axarr[i,j].scatter(diabetes_X_test, diabetes_y_test,  color='red')
        axarr[i,j].plot(diabetes_X_test, regr.predict(diabetes_X_test), color='blue', linewidth=1)
        
plt.show()

W wyniku otrzymamy następującą wizualizację:

regresja liniowa diabetes

Przykład 2. Porównanie modeli regresji liniowej

W drugim przykładzie dokonamy porównania modeli regresji pod względem błędu dopasowania (wartości błędu średnio kwadratowego). Ponadto zobaczmy jak wygląda wektor kierunkowy poszczególnych prostych. Posłużymy się zbiorem Boston House Pricing, ma on 14 atrybutów opisujących domy (ilość łazienek itp.). Naszym zadaniem jest przewidzenie ostatniego 14, który jest ceną domu. Zbiór nie jest duży ma tylko 506 instancji, ale dla nas w zupełności wystarczy. Poniżej opis z pliku informacyjnego:

1. Title: Boston Housing Data

2. Sources:
   (a) Origin:  This dataset was taken from the StatLib library which is
                maintained at Carnegie Mellon University.
   (b) Creator:  Harrison, D. and Rubinfeld, D.L. 'Hedonic prices and the 
                 demand for clean air', J. Environ. Economics & Management,
                 vol.5, 81-102, 1978.
   (c) Date: July 7, 1993

3. Past Usage:
   -   Used in Belsley, Kuh & Welsch, 'Regression diagnostics ...', Wiley, 
       1980.   N.B. Various transformations are used in the table on
       pages 244-261.
    -  Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning.
       In Proceedings on the Tenth International Conference of Machine 
       Learning, 236-243, University of Massachusetts, Amherst. Morgan
       Kaufmann.

4. Relevant Information:

   Concerns housing values in suburbs of Boston.

5. Number of Instances: 506

6. Number of Attributes: 13 continuous attributes (including "class"
                         attribute "MEDV"), 1 binary-valued attribute.

7. Attribute Information:

    1. CRIM   -   per capita crime rate by town
    2. ZN     -  proportion of residential land zoned for lots over 
                 25,000 sq.ft.
    3. INDUS  -   proportion of non-retail business acres per town
    4. CHAS   -   Charles River dummy variable (= 1 if tract bounds 
                 river; 0 otherwise)
    5. NOX    -   nitric oxides concentration (parts per 10 million)
    6. RM     -   average number of rooms per dwelling
    7. AGE    -   proportion of owner-occupied units built prior to 1940
    8. DIS    -   weighted distances to five Boston employment centres
    9. RAD    -   index of accessibility to radial highways
    10. TAX   -   full-value property-tax rate per $10,000
    11. PTRATIO-  pupil-teacher ratio by town
    12. B      -  1000(Bk - 0.63)^2 where Bk is the proportion of blacks  by town
    13. LSTAT -   % lower status of the population
    14. MEDV  -   Median value of owner-occupied homes in $1000's

8. Missing Attribute Values:  None.

W naszym przypadku dane załadujemy automatycznie przy wykorzystaniu scikit-learn. Klasa datasets ma szereg metod do załadowania danych w tym load_boston() ładującą właśnie nasz zbiór.

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets, linear_model as linm

# Reggression models
# http://scikit-learn.org/stable/modules/linear_model.html

# Load the diabetes dataset
boston = datasets.load_boston()
# print description
print(boston.DESCR)
# get the data
boston_X = boston.data
boston_Y = boston.target
# Split the data into training/testing sets
boston_X_train = boston_X[:-50]
boston_X_test = boston_X[-50:]

# Split the targets into training/testing sets
boston_y_train = boston_Y[:-50]
boston_y_test = boston_Y[-50:]

Po załadowaniu danych wydzielamy do oddzielnych zmiennych zmienne zależne i niezależne, boston_X zawiera dane, na podstawie których będziemy dokonywać przewidywania docelowej wartości boston_Y. Następnie dokonujemy podziału na dane treningowe i testowe, gdzie ostatnie 50 instancji to dane testowe.

Przypominam, że chcemy dokonać porównania różnych typów regresji. W tym celu deklarujemy słownik zawierający obiekty rozpatrywanych typów regresji: LinearRegression, Ridge, Lasso, ElasticNet. Następnie w pętli dla każdego obliczamy błąd i zapamiętujemy w zmiennej fit_results.

regressors = {}
regressors['LinReg'] =linm.LinearRegression()
regressors['Ridge'] = linm.Ridge(alpha = .5)
regressors['Lasso'] = linm.Lasso(alpha = 5.1)
regressors['ElNet'] =linm.ElasticNet(alpha = .5, l1_ratio=0.5)

fit_results={}


for key in regressors:
    # Train the model using the training sets
    regr = regressors[key]
    regr.fit(boston_X_train, boston_y_train)
    # mean square error
    mse = np.mean((regr.predict(boston_X_test) - boston_y_test) ** 2)
    w = regr.coef_
    # l1 norm
    wl1 = np.sum(np.abs(w))
    # l2 norm
    wl2 = np.sqrt(np.sum(w**2))
    fit_results[key]= { 'mse': mse, 'wl2': wl2, 'wl1': wl1, 'w': w}
    print("{}\n----------\n  mse={}\n  wl1={}\n  wl2={}\n  w={}\n ".format(key,mse,wl1,wl2,w))
    
    

Na końcu wyświetlmy obliczone błędy regresji na wykresie, tak aby łatwo można było dokonać porównania. W pętli pobieramy uprzednio obliczone wartości i rysujemy w postaci wykresu słupkowego. Dla każdego typu regresji mamy trzy parametry (mse, wl1, wl2), które odkładamy na w postaci trzech słupków. Każdy typ regresji otrzymał inny kolor.

groups = 3
index = np.arange(groups)
bar_width = .2
opacity = 0.4

fig, ax = plt.subplots(figsize=(15,9))

t=0
for key in regressors:
    results = fit_results[key]
    res_val = (results['mse'],results['wl1'],results['wl2'])
    plt.bar(index+ bar_width*t, res_val, bar_width,
                 alpha=opacity,
                 color=np.random.rand(3,1),
                 label=key)
    t+=1

#plt.xlabel('Modele regresji')
plt.title('Porownanie modeli regresji: MSE, wl1, wl2')
plt.xticks(index + (t-2)*bar_width, ('MSE', 'wl1', 'wl2'))
plt.legend()

plt.tight_layout()
plt.show()

W wyniku otrzymamy diagram dokonujący porównania trzech wartości:

  • MSE – mean square error, czyli błędu dopasowania,
  • wl1 – wartość normy l1 wektora w,
  • wl2 – wartość normy l2 wektora w
porownanie modeli regresji scikit-learn
porownanie modeli regresji scikit-learn

Na powyższym wykresie mamy zobrazowany trzy rozpatrywane parametry MSE, normę l1 i l2 dla każdego typu regresji: ElasticNet(zielony), Linear regression (czerwony), Ridge regression (szary), Lasso (żółty).

Pokazuje on kilka istotnych informacji, które mogą posłużyć do wyboru modelu. Po pierwsze, zależy nam na tym, aby MSE był jak najmniejszy. W naszym przykładzie najmniejszą wartość osiągnęła Liniowa Regresja (kolor czerwony), tuż za nią Ridge (szary).

Warto także zwrócić uwagą na dwa pozostałe policzone parametry, normy l1 i l2 charakteryzujące znaleziony wektor 'w’. Ze względu na stabilność numeryczną zależy nam, aby wartości tego wektora nie były duże (norma l2 mała). Pod względem tego kryterium lepiej wypada ElasticNet i Lasso.

Natomiast w przypadku gdy mamy do czynienia z danymi wielowymiarowymi chcielibyśmy, aby wektor 'w’ był rzadki (norma l1 mała). W tym przypadku Lasso (kolor żółty) i ElasticNet (zielony) promują rozwiązania rzadkie.

Polecam poczytać o zaletach i wadach poszczególnych modeli:

Jeżeli uważasz ten wpis za wartościowy to Zasubskrybuj bloga. Dostaniesz informacje o nowych artykułach.

Join 99 other subscribers

2 Comments Modele regresji liniowej szybko i łatwo z scikit learn

  1. Lukas

    Wszystko super, tylko co w przypadku własnego zbioru danych? Toutorial nic nie mówi na temat możliwości wykorzystania własnego zbioru, a myślę że to jest kluczowe

    Reply
    1. ksopyla

      Przykład jest na dwóch zbiorach danych, śmiało można podpiąć swój własny, wystarczy że podejrzysz jak wyglądają zmienne zawierające dane. W przykładowych kodach zmienne przechowujące dane kończą się na _X:

      [code lang=text]
      diabetes_X
      [/code]

      lub

      [code lang=text]
      boston_X_train = boston_X[:-50]
      boston_X_test = boston_X[-50:]
      [/code]

      i zamiast nich podstawisz swoje dane.
      W tym przypadku są to po prostu wektory.

Ciekawe, wartościowe, podziel się proszę opinią!

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.