# Učitaj osnovne biblioteke...
import numpy as np
import sklearn
import matplotlib.pyplot as plt
%pylab inline

"""
Explanation: Sveučilište u Zagrebu
Fakultet elektrotehnike i računarstva  
Strojno učenje 2019/2020
http://www.fer.unizg.hr/predmet/su

Laboratorijska vježba 1: Regresija
Verzija: 1.2
Zadnji put ažurirano: 27. rujna 2019.
(c) 2015-2019 Jan Šnajder, Domagoj Alagić 
Objavljeno: 30. rujna 2019.
Rok za predaju: 21. listopada 2019. u 07:00h

Upute
Prva laboratorijska vježba sastoji se od deset zadataka. U nastavku slijedite upute navedene u ćelijama s tekstom. Rješavanje vježbe svodi se na dopunjavanje ove bilježnice: umetanja ćelije ili više njih ispod teksta zadatka, pisanja odgovarajućeg kôda te evaluiranja ćelija. 
Osigurajte da u potpunosti razumijete kôd koji ste napisali. Kod predaje vježbe, morate biti u stanju na zahtjev asistenta (ili demonstratora) preinačiti i ponovno evaluirati Vaš kôd. Nadalje, morate razumjeti teorijske osnove onoga što radite, u okvirima onoga što smo obradili na predavanju. Ispod nekih zadataka možete naći i pitanja koja služe kao smjernice za bolje razumijevanje gradiva (nemojte pisati odgovore na pitanja u bilježnicu). Stoga se nemojte ograničiti samo na to da riješite zadatak, nego slobodno eksperimentirajte. To upravo i jest svrha ovih vježbi.
Vježbe trebate raditi samostalno. Možete se konzultirati s drugima o načelnom načinu rješavanja, ali u konačnici morate sami odraditi vježbu. U protivnome vježba nema smisla.
End of explanation
"""


X = np.array([[0],[1],[2],[4]])
y = np.array([4,1,2,5])

X1 = X
y1 = y

"""
Explanation: Zadatci
1. Jednostavna regresija
Zadan je skup primjera $\mathcal{D}={(x^{(i)},y^{(i)})}_{i=1}^4 = {(0,4),(1,1),(2,2),(4,5)}$. Primjere predstavite matrixom $\mathbf{X}$ dimenzija $N\times n$ (u ovom slučaju $4\times 1$) i vektorom oznaka $\textbf{y}$, dimenzija $N\times 1$ (u ovom slučaju $4\times 1$), na sljedeći način:
End of explanation
"""


from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(1)
phi = poly.fit_transform(X)
print(phi)

# Vaš kôd ovdje

"""
Explanation: (a)
Proučite funkciju PolynomialFeatures iz biblioteke sklearn i upotrijebite je za generiranje matrice dizajna $\mathbf{\Phi}$ koja ne koristi preslikavanje u prostor više dimenzije (samo će svakom primjeru biti dodane dummy jedinice; $m=n+1$).
End of explanation
"""


from numpy import linalg

pinverse1 = pinv(phi)
pinverse2 = matmul(inv(matmul(transpose(phi), phi)), transpose(phi))

#print(pinverse1)
#print(pinverse2)

w = matmul(pinverse2, y)
print(w)
                   
# Vaš kôd ovdje

"""
Explanation: (b)
Upoznajte se s modulom linalg. Izračunajte težine $\mathbf{w}$ modela linearne regresije kao $\mathbf{w}=(\mathbf{\Phi}^\intercal\mathbf{\Phi})^{-1}\mathbf{\Phi}^\intercal\mathbf{y}$. Zatim se uvjerite da isti rezultat možete dobiti izračunom pseudoinverza $\mathbf{\Phi}^+$ matrice dizajna, tj. $\mathbf{w}=\mathbf{\Phi}^+\mathbf{y}$, korištenjem funkcije pinv.
End of explanation
"""


import sklearn.metrics as mt

wt = w #(np.array([w]))

print(wt)
print(phi)

hx = np.dot(phi, w)

E = mt.mean_squared_error(hx, y)
print(E)

# Vaš kôd ovdje

"""
Explanation: Radi jasnoće, u nastavku je vektor $\mathbf{x}$ s dodanom dummy jedinicom $x_0=1$ označen kao $\tilde{\mathbf{x}}$.
(c)
Prikažite primjere iz $\mathcal{D}$ i funkciju $h(\tilde{\mathbf{x}})=\mathbf{w}^\intercal\tilde{\mathbf{x}}$. Izračunajte pogrešku učenja prema izrazu $E(h|\mathcal{D})=\frac{1}{2}\sum_{i=1}^N(\tilde{\mathbf{y}}^{(i)} - h(\tilde{\mathbf{x}}))^2$. Možete koristiti funkciju srednje kvadratne pogreške mean_squared_error iz modula sklearn.metrics.
Q: Gore definirana funkcija pogreške $E(h|\mathcal{D})$ i funkcija srednje kvadratne pogreške nisu posve identične. U čemu je razlika? Koja je "realnija"?
End of explanation
"""


# Vaš kôd ovdje

w = matmul(inv(phi), y)
print(w)

"""
Explanation: (d)
Uvjerite se da za primjere iz $\mathcal{D}$ težine $\mathbf{w}$ ne možemo naći rješavanjem sustava $\mathbf{w}=\mathbf{\Phi}^{-1}\mathbf{y}$, već da nam doista treba pseudoinverz.
Q: Zašto je to slučaj? Bi li se problem mogao riješiti preslikavanjem primjera u višu dimenziju? Ako da, bi li to uvijek funkcioniralo, neovisno o skupu primjera $\mathcal{D}$? Pokažite na primjeru.
End of explanation
"""


from sklearn.linear_model import LinearRegression

# Vaš kôd ovdje
lr = LinearRegression().fit(X, y)
#print(lr.score(X, y))
#print(lr.coef_)
#print(lr.intercept_)
print([lr.intercept_, lr.coef_[0]])

print(wt)

pr = lr.predict(X)
E = mt.mean_squared_error(pr, y)
print(E)

"""
Explanation: (e)
Proučite klasu LinearRegression iz modula sklearn.linear_model. Uvjerite se da su težine koje izračunava ta funkcija (dostupne pomoću atributa coef_ i intercept_) jednake onima koje ste izračunali gore. Izračunajte predikcije modela (metoda predict) i uvjerite se da je pogreška učenja identična onoj koju ste ranije izračunali.
End of explanation
"""


from numpy.random import normal

def make_labels(X, f, noise=0) :
    # Vaš kôd ovdje
    N = numpy.random.normal
    fx = f(X)
    #nois = [N(0, noise) for _ in range(X.shape[0])]
    #print(nois)
    #y = f(X) + nois
    y = [ f(x) + N(0, noise) for x in X ]
    
    return y


def make_instances(x1, x2, N) :
    return np.array([np.array([x]) for x in np.linspace(x1,x2,N)])

"""
Explanation: 2. Polinomijalna regresija i utjecaj šuma
(a)
Razmotrimo sada regresiju na većem broju primjera. Definirajte funkciju make_labels(X, f, noise=0) koja uzima matricu neoznačenih primjera $\mathbf{X}{N\times n}$ te generira vektor njihovih oznaka $\mathbf{y}{N\times 1}$. Oznake se generiraju kao $y^{(i)} = f(x^{(i)})+\mathcal{N}(0,\sigma^2)$, gdje je $f:\mathbb{R}^n\to\mathbb{R}$ stvarna funkcija koja je generirala podatke (koja nam je u stvarnosti nepoznata), a $\sigma$ je standardna devijacija Gaussovog šuma, definirana parametrom noise. Za generiranje šuma možete koristiti funkciju numpy.random.normal. 
Generirajte skup za učenje od $N=50$ primjera uniformno distribuiranih u intervalu $[-5,5]$ pomoću funkcije $f(x) = 5 + x -2 x^2 -5 x^3$ uz šum  $\sigma=200$:
End of explanation
"""


# Vaš kôd ovdje
N = 50
def f(x):
    return 5 + x - 2*x*x - 5*x*x*x
noise = 200

X2 = make_instances(-5, 5, N)
y2 = make_labels(X2, f, noise)

#print(X)
#print(y)

s = scatter(X2, y2)

"""
Explanation: Prikažite taj skup funkcijom scatter.
End of explanation
"""


# Vaš kôd ovdje
import sklearn.linear_model as lm

def polyX(d):

    p3 = PolynomialFeatures(d).fit_transform(X2)
    l2 = LinearRegression().fit(p3, y2)
    h2 = l2.predict(p3)

    E = mt.mean_squared_error(h2, y2)
    print('d: ' + str(d) + ' E: ' + str(E))
    #print(p3)
    plot(X2, h2, label = str(d))

scatter(X2, y2)
polyX(3)


"""
Explanation: (b)
Trenirajte model polinomijalne regresije stupnja $d=3$. Na istom grafikonu prikažite naučeni model $h(\mathbf{x})=\mathbf{w}^\intercal\tilde{\mathbf{x}}$ i primjere za učenje. Izračunajte pogrešku učenja modela.
End of explanation
"""


# Vaš kôd ovdje
figure(figsize=(15,10))
scatter(X2, y2)
polyX(1)
polyX(3)
polyX(5)
polyX(10)
polyX(20)

s = plt.legend(loc="center right")



"""
Explanation: 3. Odabir modela
(a)
Na skupu podataka iz zadatka 2 trenirajte pet modela linearne regresije $\mathcal{H}_d$ različite složenosti, gdje je $d$ stupanj polinoma, $d\in{1,3,5,10,20}$. Prikažite na istome grafikonu skup za učenje i funkcije $h_d(\mathbf{x})$ za svih pet modela (preporučujemo koristiti plot unutar for petlje). Izračunajte pogrešku učenja svakog od modela.
Q: Koji model ima najmanju pogrešku učenja i zašto?
End of explanation
"""


from sklearn.model_selection import train_test_split

# Vaš kôd ovdje

xTr, xTest, yTr, yTest = train_test_split(X2, y2, test_size=0.5)

testError = []

for d in range(1,33):

    polyXTrain = PolynomialFeatures(d).fit_transform(xTr)    
    polyXTest = PolynomialFeatures(d).fit_transform(xTest)

    l2 = LinearRegression().fit(polyXTrain, yTr)
    h2 = l2.predict(polyXTest)

    E = mt.mean_squared_error(h2, yTest)
    print('d: ' + str(d) + ' E: ' + str(E))
    testError.append(E)
    #print(p3)
    #plot(polyXTest, h2, label = str(d))

plot(numpy.log(numpy.array(testError)))

"""
Explanation: (b)
Razdvojite skup primjera iz zadatka 2 pomoću funkcije model_selection.train_test_split na skup za učenja i skup za ispitivanje u omjeru 1:1. Prikažite na jednom grafikonu pogrešku učenja i ispitnu pogrešku za modele polinomijalne regresije $\mathcal{H}_d$, sa stupnjem polinoma $d$ u rasponu $d\in [1,2,\ldots,20]$. Budući da kvadratna pogreška brzo raste za veće stupnjeve polinoma, umjesto da iscrtate izravno iznose pogrešaka, iscrtajte njihove logaritme.
NB: Podjela na skupa za učenje i skup za ispitivanje mora za svih pet modela biti identična.
Q: Je li rezultat u skladu s očekivanjima? Koji biste model odabrali i zašto?
Q: Pokrenite iscrtavanje više puta. U čemu je problem? Bi li problem bio jednako izražen kad bismo imali više primjera? Zašto?
End of explanation
"""


# Vaš kôd ovdje

# Vaš kôd ovdje
figure(figsize=(15,15))

N = 1000
def f(x):
    return 5 + x - 2*x*x - 5*x*x*x

X3 = make_instances(-5, 5, N)

xAllTrain, xAllTest = train_test_split(X3, test_size=0.5)
i = 0
j = 0

for N in [100, 200, 1000]:
    for noise in [100, 200, 500]:
        j += 1
        
        xTrain = xAllTrain[:N]
        xTest = xAllTest[:N]
        yTrain = make_labels(xTrain, f, noise)
        yTest = make_labels(xTest, f, noise)

        trainError = []
        testError = []

        for d in range(1,21):

            polyXTrain = PolynomialFeatures(d).fit_transform(xTrain)    
            polyXTest = PolynomialFeatures(d).fit_transform(xTest)

            l2 = LinearRegression().fit(polyXTrain, yTrain)
            h2 = l2.predict(polyXTest)

            testE = mt.mean_squared_error(h2, yTest)
            testError.append(testE)
            
            h2 = l2.predict(polyXTrain)
            trainE = mt.mean_squared_error(h2, yTrain)
            trainError.append(trainE)
            #print('d: ' + str(d) + ' E: ' + str(E))
            #print(p3)
            #plot(polyXTest, h2, label = str(d))

        subplot(3,3,j, title = "N: " + str(N) + " noise: " + str(noise))
        plot(numpy.log(numpy.array(trainError)), label = 'train')        
        plot(numpy.log(numpy.array(testError)), label = 'test')
        plt.legend(loc="center right")
        




#print(X)
#print(y)

#s = scatter(X2, y2)

"""
Explanation: (c)
Točnost modela ovisi o (1) njegovoj složenosti (stupanj $d$ polinoma), (2) broju primjera $N$, i (3) količini šuma. Kako biste to analizirali, nacrtajte grafikone pogrešaka kao u 3b, ali za sve kombinacija broja primjera $N\in{100,200,1000}$ i količine šuma $\sigma\in{100,200,500}$ (ukupno 9 grafikona). Upotrijebite funkciju subplots kako biste pregledno posložili grafikone u tablicu $3\times 3$. Podatci se generiraju na isti način kao u zadatku 2.
NB: Pobrinite se da svi grafikoni budu generirani nad usporedivim skupovima podataka, na sljedeći način. Generirajte najprije svih 1000 primjera, podijelite ih na skupove za učenje i skupove za ispitivanje (dva skupa od po 500 primjera). Zatim i od skupa za učenje i od skupa za ispitivanje načinite tri različite verzije, svaka s drugačijom količinom šuma (ukupno 2x3=6 verzija podataka). Kako bi simulirali veličinu skupa podataka, od tih dobivenih 6 skupova podataka uzorkujte trećinu, dvije trećine i sve podatke. Time ste dobili 18 skupova podataka -- skup za učenje i za testiranje za svaki od devet grafova.
End of explanation
"""


# Vaš kôd ovdje

def reg2(lambd):
    phi4 = PolynomialFeatures(3).fit_transform(X1)
    w = matmul( matmul(inv( matmul(transpose(phi4), phi4) + lambd * identity(len(phi4))), transpose(phi4)), y1)
    print(w)
    
reg2(0)
reg2(1)
reg2(10)

"""
Explanation: Q: Jesu li rezultati očekivani? Obrazložite.
4. Regularizirana regresija
(a)
U gornjim eksperimentima nismo koristili regularizaciju. Vratimo se najprije na primjer iz zadatka 1. Na primjerima iz tog zadatka izračunajte težine $\mathbf{w}$ za polinomijalni regresijski model stupnja $d=3$ uz L2-regularizaciju (tzv. ridge regression), prema izrazu $\mathbf{w}=(\mathbf{\Phi}^\intercal\mathbf{\Phi}+\lambda\mathbf{I})^{-1}\mathbf{\Phi}^\intercal\mathbf{y}$. Napravite izračun težina za regularizacijske faktore $\lambda=0$, $\lambda=1$ i $\lambda=10$ te usporedite dobivene težine.
Q: Kojih je dimenzija matrica koju treba invertirati?
Q: Po čemu se razlikuju dobivene težine i je li ta razlika očekivana? Obrazložite.
End of explanation
"""



from sklearn.linear_model import Ridge

phi4 = PolynomialFeatures(3).fit_transform(X1)
r = Ridge(0).fit(phi4, y1)
print(r.coef_)
print(r.intercept_)

# Vaš kôd ovdje

"""
Explanation: (b)
Proučite klasu Ridge iz modula sklearn.linear_model, koja implementira L2-regularizirani regresijski model. Parametar $\alpha$ odgovara parametru $\lambda$. Primijenite model na istim primjerima kao u prethodnom zadatku i ispišite težine $\mathbf{w}$ (atributi coef_ i intercept_).
Q: Jesu li težine identične onima iz zadatka 4a? Ako nisu, objasnite zašto je to tako i kako biste to popravili.
End of explanation
"""


# Vaš kôd ovdje

N = 50

figure(figsize = (15, 15))
x123 = scatter(X2, y2)

for lambd in [0, 100]:
    for d in [2, 10]:
        phi2 = PolynomialFeatures(d).fit_transform(X2)
        r = Ridge(lambd).fit(phi2, y2)
        h2 = r.predict(phi2)
        #print(d)
        plot(X2, h2, label="lambda " + str(lambd) + " d " + str(d))
        
x321 = plt.legend(loc="center right")


"""
Explanation: 5. Regularizirana polinomijalna regresija
(a)
Vratimo se na slučaj $N=50$ slučajno generiranih primjera iz zadatka 2. Trenirajte modele polinomijalne regresije $\mathcal{H}_{\lambda,d}$ za $\lambda\in{0,100}$ i $d\in{2,10}$ (ukupno četiri modela). Skicirajte pripadne funkcije $h(\mathbf{x})$ i primjere (na jednom grafikonu; preporučujemo koristiti plot unutar for petlje).
Q: Jesu li rezultati očekivani? Obrazložite.
End of explanation
"""


# Vaš kôd ovdje


xTr, xTest, yTr, yTest = train_test_split(X2, y2, test_size=0.5)
figure(figsize=(10,10))
trainError = []
testError = []

for lambd in range(0,51):

    polyXTrain = PolynomialFeatures(10).fit_transform(xTr)    
    polyXTest = PolynomialFeatures(10).fit_transform(xTest)

    l2 = Ridge(lambd).fit(polyXTrain, yTr)
    h2 = l2.predict(polyXTest)

    E = mt.mean_squared_error(h2, yTest)
    #print('d: ' + str(d) + ' E: ' + str(E))
    testError.append(log( E))
    
    h2 = l2.predict(polyXTrain)
    E = mt.mean_squared_error(h2, yTr)
    trainError.append(log(E))
    #print(p3)
    #plot(polyXTest, h2, label = str(d))

plot(numpy.log(numpy.array(testError)), label="test")
plot(numpy.log(numpy.array(trainError)), label="train")
legend()

"""
Explanation: (b)
Kao u zadataku 3b, razdvojite primjere na skup za učenje i skup za ispitivanje u omjeru 1:1. Prikažite krivulje logaritama pogreške učenja i ispitne pogreške u ovisnosti za model $\mathcal{H}_{d=10,\lambda}$, podešavajući faktor regularizacije $\lambda$ u rasponu $\lambda\in{0,1,\dots,50}$.
Q: Kojoj strani na grafikonu odgovara područje prenaučenosti, a kojoj podnaučenosti? Zašto?
Q: Koju biste vrijednosti za $\lambda$ izabrali na temelju ovih grafikona i zašto?
End of explanation
"""


def nonzeroes(coef, tol=1e-6): 
    return len(coef) - len(coef[np.isclose(0, coef, atol=tol)])

"""
Explanation: 6. L1-regularizacija i L2-regularizacija
Svrha regularizacije jest potiskivanje težina modela $\mathbf{w}$ prema nuli, kako bi model bio što jednostavniji. Složenost modela može se okarakterizirati normom pripadnog vektora težina $\mathbf{w}$, i to tipično L2-normom ili L1-normom. Za jednom trenirani model možemo izračunati i broj ne-nul značajki, ili L0-normu, pomoću sljedeće funkcije koja prima vektor težina $\mathbf{w}$:
End of explanation
"""


# Vaš kôd ovdje
d = 10

l0 = []
l1 = []
l2 = []


xTr, xTest, yTr, yTest = train_test_split(X2, y2, test_size=0.5)

for lambd in range(0,101):

    polyXTrain = PolynomialFeatures(10).fit_transform(xTr)    
    polyXTest = PolynomialFeatures(10).fit_transform(xTest)

    r = Ridge(lambd).fit(polyXTrain, yTr)
    
    r.coef_[0] = r.intercept_
    
    l0.append(nonzeroes(r.coef_))
    #print(r.coef_)
    l1.append(numpy.linalg.norm(r.coef_, ord=1))
    l2.append(numpy.linalg.norm(r.coef_, ord=2))
    

figure(figsize=(10,10))
plot(l0, label="l0")
legend()

figure(figsize=(10,10))
plot(l1, label="l1")
legend()

figure(figsize=(10,10))
plot(l2, label="l2")
legend()

"""
Explanation: (a)
Za ovaj zadatak upotrijebite skup za učenje i skup za testiranje iz zadatka 3b. Trenirajte modele L2-regularizirane polinomijalne regresije stupnja $d=10$, mijenjajući hiperparametar $\lambda$ u rasponu ${1,2,\dots,100}$. Za svaki od treniranih modela izračunajte L{0,1,2}-norme vektora težina $\mathbf{w}$ te ih prikažite kao funkciju od $\lambda$. Pripazite što točno šaljete u funkciju za izračun normi.
Q: Objasnite oblik obiju krivulja. Hoće li krivulja za $\|\mathbf{w}\|_2$ doseći nulu? Zašto? Je li to problem? Zašto?
Q: Za $\lambda=100$, koliki je postotak težina modela jednak nuli, odnosno koliko je model rijedak?
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: (b)
Glavna prednost L1-regularizirane regresije (ili LASSO regression) nad L2-regulariziranom regresijom jest u tome što L1-regularizirana regresija rezultira rijetkim modelima (engl. sparse models), odnosno modelima kod kojih su mnoge težine pritegnute na nulu. Pokažite da je to doista tako, ponovivši gornji eksperiment s L1-regulariziranom regresijom, implementiranom u klasi  Lasso u modulu sklearn.linear_model. Zanemarite upozorenja.
End of explanation
"""


n_data_points = 500
np.random.seed(69)

# Generiraj podatke o bodovima na prijamnom ispitu koristeći normalnu razdiobu i ograniči ih na interval [1, 3000].
exam_score = np.random.normal(loc=1500.0, scale = 500.0, size = n_data_points) 
exam_score = np.round(exam_score)
exam_score[exam_score > 3000] = 3000
exam_score[exam_score < 0] = 0

# Generiraj podatke o ocjenama iz srednje škole koristeći normalnu razdiobu i ograniči ih na interval [1, 5].
grade_in_highschool = np.random.normal(loc=3, scale = 2.0, size = n_data_points)
grade_in_highschool[grade_in_highschool > 5] = 5
grade_in_highschool[grade_in_highschool < 1] = 1

# Matrica dizajna.
grades_X = np.array([exam_score,grade_in_highschool]).T

# Završno, generiraj izlazne vrijednosti.
rand_noise = np.random.normal(loc=0.0, scale = 0.5, size = n_data_points)
exam_influence = 0.9
grades_y = ((exam_score / 3000.0) * (exam_influence) + (grade_in_highschool / 5.0) \
            * (1.0 - exam_influence)) * 5.0 + rand_noise
grades_y[grades_y < 1] = 1
grades_y[grades_y > 5] = 5

"""
Explanation: 7. Značajke različitih skala
Često se u praksi možemo susreti sa podatcima u kojima sve značajke nisu jednakih magnituda. Primjer jednog takvog skupa je regresijski skup podataka grades u kojem se predviđa prosjek ocjena studenta na studiju (1--5) na temelju dvije značajke: bodova na prijamnom ispitu (1--3000) i prosjeka ocjena u srednjoj školi. Prosjek ocjena na studiju izračunat je kao težinska suma ove dvije značajke uz dodani šum.
Koristite sljedeći kôd kako biste generirali ovaj skup podataka.
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: a) Iscrtajte ovisnost ciljne vrijednosti (y-os) o prvoj i o drugoj značajki (x-os). Iscrtajte dva odvojena grafa.
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: b) Naučite model L2-regularizirane regresije ($\lambda = 0.01$), na podacima grades_X i grades_y:
End of explanation
"""


from sklearn.preprocessing import StandardScaler

# Vaš kôd ovdje

"""
Explanation: Sada ponovite gornji eksperiment, ali prvo skalirajte podatke grades_X i grades_y i spremite ih u varijable grades_X_fixed i grades_y_fixed. Za tu svrhu, koristite StandardScaler.
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: Q: Gledajući grafikone iz podzadatka (a), koja značajka bi trebala imati veću magnitudu, odnosno važnost pri predikciji prosjeka na studiju? Odgovaraju li težine Vašoj intuiciji? Objasnite.  
8. Multikolinearnost i kondicija matrice
a) Izradite skup podataka grades_X_fixed_colinear tako što ćete u skupu grades_X_fixed iz
zadatka 7b duplicirati zadnji stupac (ocjenu iz srednje škole). Time smo efektivno uveli savršenu multikolinearnost.
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: Ponovno, naučite na ovom skupu L2-regularizirani model regresije ($\lambda = 0.01$).
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: Q: Usporedite iznose težina s onima koje ste dobili u zadatku 7b. Što se dogodilo?
b) Slučajno uzorkujte 50% elemenata iz skupa grades_X_fixed_colinear i naučite dva modela L2-regularizirane regresije, jedan s $\lambda=0.01$ i jedan s $\lambda=1000$). Ponovite ovaj pokus 10 puta (svaki put s drugim podskupom od 50% elemenata).  Za svaki model, ispišite dobiveni vektor težina u svih 10 ponavljanja te ispišite standardnu devijaciju vrijednosti svake od težina (ukupno šest standardnih devijacija, svaka dobivena nad 10 vrijednosti).
End of explanation
"""


# Vaš kôd ovdje

"""
Explanation: Q: Kako regularizacija utječe na stabilnost težina?
Q: Jesu li koeficijenti jednakih magnituda kao u prethodnom pokusu? Objasnite zašto.
c) Koristeći numpy.linalg.cond izračunajte kondicijski broj matrice $\mathbf{\Phi}^\intercal\mathbf{\Phi}+\lambda\mathbf{I}$, gdje je $\mathbf{\Phi}$ matrica dizajna (grades_X_fixed_colinear). Ponovite i za $\lambda=0.01$ i za $\lambda=10$.
End of explanation
"""


