Regresja logistyczna - gradient prosty - dziwne zachowanie

0

Witam. Staram się zbudować model regresji logistycznej dla zestawu danych składających się z dwóch parametrów X1 i X2, postanowiłem oprócz
po prostu X1 i X2 dodać do danych wejściowych również parametry kwadratowe- X1^2, X2^2, X1*X2,
na pierwszy rzut oka wszystko wygląda dobrze, i error function jest malejące, ale rysując wykres zauważam dziwne zachowanie krzywej "decision boundary".
Załączam filmik z animacji dopasowywania danych:

Jak widać na początku wszystko wygląda w porządku, jednak około pięćsetnej iteracji dzieje się coś dziwnego.
Dopasowane są dwie krzywe, ponieważ X2 i X1 są w parametrach dane również jako kwadraty - generalnie jest to wykres
X2 od X1.
Poniżej zamieszczam cały kod - prosiłbym o informacje, dlaczego krzywa regresji przyjmuje takie dziwne wartości.

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt
from math import log
from matplotlib.animation import FuncAnimation

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

def loadData(filepath):
    source=""
    try:
        f = open(filepath, "r")
        source = f.read()
        f.close()
    except IOError:
        print("Error while reading file (" + filepath + ")")
        return ""
    raw_data = source.split("\n")
    raw_data = [x.split(",") for x in raw_data if x !=""]
    raw_data = np.matrix(raw_data).astype(float)
    return (raw_data[:,:np.size(raw_data,1)-1], raw_data[:,np.size(raw_data, 1)-1:])

def standardize(dataset, skipfirst=True):
    means = np.amin(dataset, 0)
    deviation = np.std(dataset, 0)
    if skipfirst:
        dataset[:,1:] -= means[:,1:]
        dataset[:,1:] /= deviation[:,1:]
        return dataset
    else:
        dataset -= means
        dataset /= deviation
        return dataset

def error(X, Y, Theta):
    "Calculates error values"
    v_sigm = np.vectorize(sigmoid)
    h_x = X @ Theta
    sigmo = v_sigm(h_x)
    partial_vect = (Y-1).T @ np.log(1-sigmo) - Y.T @ np.log(sigmo) 
    return 1/(2*np.size(Y, axis=0))*np.sum(partial_vect)

def gradientStep(X, Y, Theta, LR):
    "Returns new theta Values"
    v_sigm = np.vectorize(sigmoid)
    h_x = X @ Theta
    modif = -1*LR/np.size(Y, 0)*(h_x-Y) 
    sums = np.sum(modif.T @ X, axis = 0)
    return Theta + sums.T

X, Y = loadData("ex2data1.txt")
#add bias to X
X = np.append(np.ones((np.size(X, 0), 1)), X, axis=1)
added_params = [[x[1]**2, x[1]*x[2], x[2]**2] for x in np.array(X)]
X = np.append(X, np.matrix(added_params), axis=1)
#standardize X
X = standardize(X)
#create vector of parameters
Theta=np.zeros((np.size(X, 1), 1))

iterations = 3000
Theta_vals = []
Error_vals = []
#plot data:
fig = plt.figure()
def_ax = fig.add_subplot(211)
def_ax.set_xlim(np.amin(X[:,1:2]), np.amax(X[:,1:2]))
def_ax.set_ylim(np.amin(X[:,2:3]), np.amax(X[:,2:3]))
err_ax = fig.add_subplot(212)
err_ax.set_ylim(0, error(X, Y, Theta))
err_ax.set_xlim(0, iterations)
positive_X1 = []
positive_X2 = []
negative_X1 = []
negative_X2 = []
for i in range(0, np.size(Y, 0)):
    if(Y[i, 0] == 1):
        positive_X1.append(X[i, 1])
        positive_X2.append(X[i, 2])
    else:
        negative_X1.append(X[i, 1])
        negative_X2.append(X[i, 2])

print("Error at first: " , error(X, Y, Theta))

for i in range(0, iterations):
    Theta_vals.append(np.asarray(Theta).flatten())
    Error_vals.append(error(X, Y, Theta))
    Theta = gradientStep(X, Y, Theta, 0.07)

err_ax.set_ylim(np.amin(Error_vals), np.amax(Error_vals))

def animation(frame):
    global Theta_vals, Error_vals, def_ax, err_ax, positive_X1, positive_X2, negative_X1, negative_X2
    def_limX = def_ax.get_xlim()
    def_limY = def_ax.get_ylim()
    err_limX = err_ax.get_xlim()
    err_limY = err_ax.get_ylim()
    def_ax.clear()
    err_ax.clear()
    def_ax.set_xlim(def_limX)
    def_ax.set_ylim(def_limY)
    err_ax.set_xlim(err_limX)
    err_ax.set_ylim(err_limY)
    def_ax.scatter(positive_X1, positive_X2, marker="^")
    def_ax.scatter(negative_X1, negative_X2, marker="o")
    Theta = Theta_vals[frame]
    res_x = np.linspace(*def_ax.get_xlim(), num=5)
    delta_x = [(Theta[4]*x+Theta[2])**2-4*Theta[5]*(Theta[3]*x**2+Theta[1]*x+Theta[0]-0.5) for x in res_x]
    delta_x = [np.sqrt(x) if x >= 0 else 0 for x in delta_x]
    minb = [-(Theta[4]*x+Theta[2]) for x in res_x]
    res_1 = []
    res_2 = []
    for i in range(0, len(res_x)):
            if Theta[5] == 0:
                res_1.append(0)
                res_2.append(0)
            else:
                res_1.append((minb[i]+delta_x[i])/(2*Theta[5]))
                res_2.append((minb[i]-+delta_x[i])/(2*Theta[5]))
    def_ax.plot(res_x, res_1)
    def_ax.plot(res_x, res_2)
    err_x = np.linspace(0, frame, frame)
    err_y = Error_vals[0:frame]
    err_ax.plot(err_x, err_y)
    

anim = FuncAnimation(fig, animation, frames=iterations, interval=3, repeat_delay=2000)
print(error(X, Y, Theta))
anim.save("anim.mp4")
0

Bump

0

idąc za sugestią refraktoryzacji- wyrzuciłem kod odpowiedzialny za rysowanie wykresu i zostawiłem tylko ten, który jest odpowiedzialny
za samą regresję, mam nadzieję, że teraz jest bardziej czytelny - robi to samo, ale już nie rysuje

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt
from math import log
from matplotlib.animation import FuncAnimation

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

def loadData(filepath):
    source=""
    try:
        f = open(filepath, "r")
        source = f.read()
        f.close()
    except IOError:
        print("Error while reading file (" + filepath + ")")
        return ""
    raw_data = source.split("\n")
    raw_data = [x.split(",") for x in raw_data if x !=""]
    raw_data = np.matrix(raw_data).astype(float)
    return (raw_data[:,:np.size(raw_data,1)-1], raw_data[:,np.size(raw_data, 1)-1:])

def standardize(dataset, skipfirst=True):
    means = np.amin(dataset, 0)
    deviation = np.std(dataset, 0)
    if skipfirst:
        dataset[:,1:] -= means[:,1:]
        dataset[:,1:] /= deviation[:,1:]
        return dataset
    else:
        dataset -= means
        dataset /= deviation
        return dataset

def error(X, Y, Theta):
    "Calculates error values"
    v_sigm = np.vectorize(sigmoid)
    h_x = X @ Theta
    sigmo = v_sigm(h_x)
    partial_vect = (Y-1).T @ np.log(1-sigmo) - Y.T @ np.log(sigmo) 
    return 1/(2*np.size(Y, axis=0))*np.sum(partial_vect)

def gradientStep(X, Y, Theta, LR):
    "Returns new theta Values"
    v_sigm = np.vectorize(sigmoid)
    h_x = X @ Theta
    modif = -1*LR/np.size(Y, 0)*(h_x-Y) 
    sums = np.sum(modif.T @ X, axis = 0)
    return Theta + sums.T

X, Y = loadData("ex2data1.txt")
#add bias to X
X = np.append(np.ones((np.size(X, 0), 1)), X, axis=1)
added_params = [[x[1]**2, x[1]*x[2], x[2]**2] for x in np.array(X)]
X = np.append(X, np.matrix(added_params), axis=1)
#standardize X
X = standardize(X)
#create vector of parameters
Theta=np.zeros((np.size(X, 1), 1))

iterations = 3000

for i in range(0, iterations):
    Theta_vals.append(np.asarray(Theta).flatten())
    Error_vals.append(error(X, Y, Theta))
    Theta = gradientStep(X, Y, Theta, 0.07)
0

Debug wartości krok po kroku od interesującego cię momentu i będziesz wiedział.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.