Regresja logistyczna (logistic regression) – AI tutorial 3

Pierwszy post z cyklu
Drugi post z cyklu

Zostawmy na jakiś czas egzaminy, przejdźmy do innych problemów.
Idąc do lekarza mówimy co nas boli, robimy badania (jeśli NFZ przypadkiem jeszcze nie zbankrutował) a wyniku pracy inteligentnej osoby dostajemy odpowiedź czy jesteśmy chorzy. Załóżmy, że chcemy zastąpić lekarzy maszynami. Doktor na podstawie zdobytej wiedzy oraz doświadczenia (czyli de facto również wiedzy) podejmuje decyzję, czy jesteśmy zdrowi czy chorzy. Jak to się ma do poprzedniego problemu? Trochę podobnie, trochę inaczej. Na wejściu jest to samo – n zmiennych wejściowych (po ang. zwane features). Wyjście jest inne – zamiast dostać wartość z ciągłego zbioru (punkty) dostajemy binarną odpowiedź chory/zdrowy. Mamy tutaj do czynienia z problemem klasyfikacji – efekt pracy może należeć tylko do pewnego skończonego zbioru. Wymaga to nieco innego podejścia. Inne przykłady takich problemów:

X oznacza wektor zmiennych wejściowych

  • Email klasyfikowany jako spam X → {0, 1}
  • Email klasyfikowany do pewnej kategorii (praca, prywatne, newslettery) X → {0, 1, 2}
  • Kategoria aut (sedan, pickup etc) na podstawie zdjęcia X → {0, 1, 2, 3, 4…}
  • Klasyfikacja płci X → {0, 1}

Testowany obiekt musi być w dokładnie jednej kategorii.

Na razie ograniczmy się do odpowiedzi tak/nie: y \in \{0,1\}. Postawmy taki problem: mamy chorego na określoną chorobę i chcemy określić czy może zostać wyleczony czy też nie. Do dyspozycji mamy takie dane jak jego wiek, waga, wyniki badań, płeć etc. Na początku załóżmy że rozważamy tylko wiek. Przykładowe dane mogą wyglądać następująco:

pic1

Kółko oznacza 0, gwiazdka 1 – będę dalej używać tej notacji.

Widzimy, że tendencja jest bardzo prosta – im pacjent starszy tym gorsze rokowania. Gdybyśmy chcieli wyznaczyć przedziały dla y=0 (wyzdrowienie) oraz y=1 (śmierć) mogło by wyglądać to tak:

pic2

Widać tutaj pewien problem – w okolicach miejsca podziału pewnie obiekty zostaną źle sklasyfikowane, nie da się tego całkowicie uniknąć. Co możemy z tym zrobić? Zamiast mówić „Hej, umrzesz” lepiej będzie powiedzieć „Umrzesz z prawdopodobieństwem 87%”. Oczywistą zaletą jest, że w niepewnej okolicy będziemy dostawać wyniki oscylujące wokół 50% co wskaże fakt niepewności klasyfikacji. Rzućmy okiem na przykład z dwiema zmiennymi wejściowymi:

db1

Jedna zmienna jest na osi X, druga na Y. Zaznaczona linia wskazuje miejsce w którym tniemy płaszczyznę (czy to zawsze i wszędzie muszą być linie proste? Nie, pokażemy wkrótce inne warianty).

Hipoteza h(x)

Ostatnio szukaliśmy takiej funkcji h która dla pewnego zestawu danych zwróci nam przewidywany wynik egzaminu. Teraz szukamy takiej która zwróci nam prawdopodobieństwo tego, że y = 1. W związku z tym funkcja musi być ograniczona 0 \le h_\theta(x) \le 1 . Konstrukcja takiej funkcji może być bardzo problematyczna.
Prościej było by uznać że jeśli h(x) > 0 – wynik pozytywny (y = 1). W takim rozumieniu tracimy jednak informację o prawdopodobieństwie chociaż intuicyjnie czujemy że im większa wartość tym większa pewność wyniku pozytywnego, a im mniejsza negatywnego. Potrzebujemy więc pewnej funkcji (nazwijmy ją S) która zamieni wygodną dla nas reprezentację na tą bardziej użyteczną.

S: (-\infty; +\infty) \mapsto (0;1)

Taka funkcja istnieje, nazywa się ona sigmoid function (tłumaczenia się nie podejmę) i jest zdefiniowana następująco:

S(t) = \frac{1}{1+e^{-t}}

Spójrzmy na wykres

 

Nada się znakomicie – dla t = 0 zwraca dokładnie 0,5. im dalej w kierunku nieskończoności tym bliżej 0 lub 1.

Uporządkujmy nieco oznaczenia. Funkcja hipotezy h_\theta(x) będzie teraz korzystać z funkcji S aby „przetłumaczyć” wynik z formy wygodnej dla nas na tą użyteczną. Do kreślenia prostych używaliśmy ostatnio h_\theta(\boldsymbol{X}) = \boldsymbol{\theta}^T \boldsymbol{X} . Teraz też będzie odpowiednia. Oznaczmy ją tym razem jako g(X):
g(\boldsymbol {X}) = \boldsymbol{\theta}^T \boldsymbol{X}
pamiętając że mamy do dyspozycji funkcję S:
S(t) = \frac{1}{1+e^{-t}}

Otrzymujemy ostatecznie:

h_\theta(\boldsymbol{X}) = S(g(\boldsymbol{X}))
lub równoważnie:
h_\theta(\boldsymbol{X}) = \frac{1}{1+e^{-\boldsymbol{\theta}^T \boldsymbol{X}}}

Kiedy będziemy chcieli zmienić zachowanie funkcji zmodyfikujemy tylko g(X)

Właśnie, jak mamy interpretować wartość zwracane przez g? Przy regresji liniowej była to wartość oczekiwana dla danego wejścia (co zresztą było bardzo dobrze widać na wykresie). Do tej pory ustaliliśmy: funkcja h zwraca prawdopodobieństwo wyniku pozytywnego (y = 1). Jeżeli jest ono większe niż 50% klasyfikujemy wynik jako pozytywny, w przeciwnym razie jako negatywny. Progiem „przełączania” jest wartość 0.5 funkcji h czyli wartość 0 funkcji g. Wynika z tego, że równanie g(x) = 0 pokazuje linię (powierzchnię, hiperpłaszczyznę etc) po której jednej stronie wartości są klasyfikowane jako pozytywne, a po drugiej – negatywne. Spójrzmy na przykłady

db2

 

Dotychczas używana przez nas metoda (konkretnie funkcja g) pozwalała tylko na oddzielanie elementów linią prostą. Nic jednak nie stoi na przeszkodzie aby była nieco bardziej skomplikowana, np:

g(x) = \theta_0 x_0 +\theta_1 x_1+\theta_2 x_2+ \theta_3 x_1^2 +\theta_4 x_2^2 +\theta_5 x_1 x_2 + \dots

Spowoduje to dopasowanie pewną krzywą, im dłuższe wyrażenie tym potencjalnie bardziej skomplikowaną. Rozwiniemy temat przy okazji sieci neuronowych (przykład na coś innego niż prosta pojawi się jeszcze w tym poście).

Funkcja kosztu J

Funkcję hipotezy mamy za sobą, co z funkcją kosztu J? Poprzednio miała formę:

J(\boldsymbol{\theta}) = \frac{1}{m} \sum\limits_{i=1}^m \left ( h_\theta(x^{(i)}) - y^{(i)} \right )^2

Wyizolujmy z niej część odpowiedzialną za obliczenie kosztu pojedynczego zestawu danych. Będzie to:
h_\theta(x^{(i)}) - y^{(i)}

Zastanówmy się jak powinna wyglądać obecnie. Z całą pewnością chcemy, aby poprawny wynik był obarczony zerowym kosztem. Z drugiej strony jeżeli nasza hipoteza stwierdzi z dużą pewnością nieprawidłowy wynik, penalizacja powinna być wysoka. Pamiętajmy że poruszamy się w przedziale (0;1).

Na początek załóżmy, że y = 0. Jak ma kształtować się kara dla różnych wyników h(x)? Na przykład tak:

j0

Dla prawidłowego rezultatu kara jest równa zero. Im bardziej algorytm był pewny złej odpowiedzi tym większą karę ponosi (y dąży do nieskończoności wraz z x dążącym do 1).

Dla y = 1 sytuacja jest odwrotna:

j1

Wyrażenie opisujące pojedynczy zestaw danych będzie więc wyglądać tak:

\text{single penalty}(\boldsymbol{X}) =  \begin{cases}  -\log(h_\theta(\boldsymbol{X})) & \text{if } y = 1 \\  -\log(1-h_\theta(\boldsymbol{X})) & \text{if } y = 0  \end{cases}

Możemy pozbyć się niepożądanego warunku zapisując całość jako: (ze względu na y \in \{0,1\})

\text{single penalty}(\boldsymbol{X}) =-y \log(h_\theta(\boldsymbol{X})) - (1-y) \log(1-h_\theta(\boldsymbol{X}))

Podstawiając to do poprzedniego wyrażenia na wartość funkcji J(\boldsymbol{\theta}) otrzymujemy ostatecznie:

J(\boldsymbol{\theta}) = -\frac{1}{m} \left( \sum\limits_{i=1}^m \left (  y \log(h_\theta(\boldsymbol{X})) - (1-y) \log(1-h_\theta(\boldsymbol{X})    \right ) \right )

Optymalizacja

Tradycyjnie, kiedy mamy już opisną funkcję J problem jest sprowadzony do jej minimalizacji. Wyznaczmy pochodne kierunkowe względem wektora \boldsymbol{\theta}.

\frac{\partial}{\partial \theta_j} J(\boldsymbol{\theta}) = \frac{1}{m} \sum\limits_{i=1}^m \left (h_\theta \left (x^{(i)} \right ) - y^{(i)}\right) \cdot x_j^{(i)}

???

Nie, to nie jest błąd. Czy dostaliśmy to samo (z dokładnością do mnożenia przez 2)? Prawie. Pamiętajmy że obecne h_\theta(x) wygląda inaczej niż w regresji liniowej.

Całość algorytmu gradientu prostego jest identyczna, więc nie będę go opisywał ponownie.

Implementacja

Zaimplementujmy wersję w której funkcja g(x) będzie prezentować się następująco:

g(x) = \theta_0 x_0 + \theta_1 x_1^2 + \theta_2 x_2^2

Jest to nic innego jak elipsa o środku w O=(0,0).

Funkcje S oraz h w kodzie Pythona:

def sigmoid(x):
    return 1/(1 + np.e**(-x))

def h(Theta, X):
    z = Theta[0] * X[0] + Theta[1] * X[1] **2 + Theta[2] * X[2]**2
    return sigmoid(z)

Obliczenia pochodnych cząstkowych są identyczne jak poprzednio podobnie jak i sam gradient. Spójrzmy na przykładowy wynik działania. Mając dostępne następujące dane:

raw

Algorytm wytyczył krzywą dla której prawdopodobieństwo obu wyników jest takie samo (P(y=1)=0,5 w sposób następujący:

line

Dodatkowo krzywe dla 75% prawdopodobieństwa y=1 oraz y=0.

imgNastępnym razem zostawimy uczenie się na pewien czas i zastanowimy się nad podejmowaniem rozsądnych decyzji.

Całość kodu

Reklamy

2 comments

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s