<jupyter_start><jupyter_text>Prosta Regresja Liniowa W założeniu modelu liniowego podlegającego superwizji definiujemy dane wejściowe (input) i cele (target), zaśwagi (weights), błąd systematyczny (bias) i dane wyjściowe (output) pozwalamy wyciągnąć samej maszynie.  1. Import bibiliotek<jupyter_code>import numpy as np
#Poniższe przyda się do wizualizacji w tym wypadku - TensorFlow ma swój TensorBoard do wizualizacji.
import matplotlib.pyplot as plt   # generator grafów - imho coś jak bokeh.
from mpl_toolkits.mplot3d import Axes3D # wizualizacja ww. w 3D.
LinksDict = { 'metoda' : 'Https://pl.wikipedia.org/wiki/Dok%C5%82adno%C5%9B%C4%87_i_precyzja_metody_pomiaru'
            , 'iloczyn' : 'https://pl.wikipedia.org/wiki/Iloczyn_skalarny'
            , 'błąd' : 'https://pl.wikipedia.org/wiki/B%C5%82%C4%85d_systematyczny'}
### PANEL GŁÓWNY ###
observations = 1000  # Tu można zmienić ilość obserwacji. Optymalnie na lapka bez GPU jest 10.000 max. 100.000.
iterations = 100     # Ilość iteracji. Im mniejsza prędkość uczenia tym więcej ich potrzeba. Więcej w pkt 6.
noic = 10            # Tu ustawiasz sobie poziom szumu w bazie treningowych przypadków losowych. Im większa tym większy szum.
noit = 1             # Tu zmieniasz wartość szumu w danych testowych generowanych przez funkcję "targets".
vnum = 1             # Podstawiasz tu liczbę zmiennych.
movi = 5             # Ilość klatek animacji zbioru. Jeśli chcesz zobaczyć pełną animację wybierz większą ilość klatek; (na logikę pełny obrót powinien być 360, ale to nie działa w ten sposób... :P); Zasobożerna fanaberia, ale pomaga zrozumieć zbiór.; Wartość początkowa = 5
learn = 0.02         # Prędkość nauki modelu. Sugerowana: 0.02
model = [13,7,-12]   # Parametry modelu losowego dla funkcji a*xs + b*xz + c + szum. Definiujemy tu dwie docelowe wagi i obciążenie do których model ma dążyć.
                     # Można też rzecz jasna dłubać przy tym ręcznie, albo wyciągnąć tutaj całą funkcję i potem ją załadować ze zmiennej...<jupyter_output><empty_output><jupyter_text>2. Generowanie losowych danych do podstawienia do modelu.<jupyter_code>xs = np.random.uniform(low = -noic, high = noic, size =(observations,vnum)) # funkcja generowania liczb losowych w numpy.
# Od -noic do noic z równą szansą dla każdej. rozmiar to liczba obserwacji / liczba zmiennych
zs = np.random.uniform(-noic,noic,(observations,vnum)) # ta sama funkcja, druga kolumna i bez opisu wewnątrz.

inputs = np.column_stack((xs,zs)) # Ta funkcja łączy dwie kolumny ze sobą - w ten sposób będzie wyglądać input w TF.
# W tym wypadku mamy do czynienia z matrycą 1000x2.

print(inputs.shape) # Drukuje kształt matrycy. Tutaj użyte jako sanity check.<jupyter_output>(1000, 2)<jupyter_text>3. Utworzenie celów do których ma dążyć maszyna<jupyter_code>noise = np.random.uniform(-noit,noit,(observations,1)) # Generuje 'szum' w danych docelowych. 
# Kiedy zapewniamy maszynie próbkę danych to one nigdy nie są idealne i to reprezentuje ten niewielki szum.

targets = model[0]*xs + model[1]*zs + model[2] + noise # Tę funkcję ma 'odkryć' maszyna ucząc się z naszych danych. 
# Jest to wektor 1000x1. Wartości wyciągnięte na panel główny u góry notesu dla porządku.

print(targets.shape) # Kolejny sanity check, żeby sprawdzić, czy dobrze zdefiniowaliśmy cele.<jupyter_output>(1000, 1)<jupyter_text>4. Tworzenie grafu 3D dla podglądu danych treningowych Chodzi tu o to, żeby zobaczyć na wykresie czy występuje tu trend jaki chcieliśmy uzyskać.<jupyter_code>def graph_animator(var, observations, targets,):
    # Używanie matplotlib - typowa składnia.

    # Zadeklarowanie grafu
    fig = plt.figure()

    # Metoda tworząca graf 3d
    ax = fig.add_subplot(111, projection='3d')

    # Dobór osi
    ax.plot(xs, zs, targets)

    # Dobór nazw osi.
    ax.set_xlabel('xs')
    ax.set_ylabel('zs')
    ax.set_zlabel('Targets')
    

    # Azymut pozwala oglądać dane z różnych stron. Można się tym pobawić. Np. azim = 0 lub 200.
    ax.view_init(azim = 120 + var)

    # Pokaż graf.
    print(var)

    plt.show()
# To taka moja mała animacja ad-hoc, bo lubię widzieć co się dzieje w środku, 
    # a tak znacznie łatwiej to sobie zwizualizować. :P
    # Jak masz przedpotopowy sprzęt jak ja i nie masz GPU (karty graficznej), którą
    # dałoby się taki proces przyspieszyć, to polecam nie odpalać navigatora anakondy,
    # a najlepiej w ogóle zrezygnować ze środowiska graficznego i poruszać się po systemie przy pomocy basha.
    # Naturalnie jeśli Twój komputer nie dostaje zadyszki przy milionie rekordów w bazie, to pewnie
    # nie masz się czym przejmować...

    # Używamy .reshape żeby przygotować cele pod graf.
    # reshape jest w docsach do numpyego. Jako argumenty przyjmuje wymiary, którymi chcemy opisać obiekt.
targets = targets.reshape(observations,)

#Ta funkcja powiela graf tyle razy ile tego sobie życzę.
for i in range(movi):  #Zmień to sobie na większą wartość jeśli masz zasoby i chcesz z tego zrobić 'animację'.
                       #Ja wpisałem 300 i obracało się całkiem nieźle.
    graph_animator(i,observations,targets)
    
# Przywracamy poprzedni kształt celom, żeby można ich ponownie użyć do nauki.
    # Równie dobrze można było je przepisać na nową zmienną np. 'targets_plot' i teraz tego nie robić
    # ale warto znać i metodę odwrotną tak na wszelki wypadek. Pytanie co jest mniej zasobochłonne.
targets = targets.reshape(observations,1)<jupyter_output>0<jupyter_text>5. Inicjalizowanie zmiennych<jupyter_code># Stała (init_range). Podstawnik do funkcji generującej losowo początkowe wagi i obciążenia, dla modelu.
    # (init_range) to wartość odchylenia od zera w ramach którego sprzęt losuje nam ww. wartości początkowe.
    # Algorytm może się zawiesić jeśli nadamy 'init_range' zbyt dużą wartość| POMOC? Ostatnia działająca wartość: 0.1.
        # Można rzecz jasna eksperymentować z tą wartością i zachęcam do tego. 
        # Niemniej uważam, że najlepiej radzi sobie pozostawiona w spokoju...
init_range = 0.1

#?# ILE WAG #?# powinien zawierać mój model? || Pokrótce: ilość_wag_w_modelu = wejścia.ilość * ujścia.ilość #!#
    # || Dokładnie: Wagi mają wymiar k * m, gdzie k to ilość[zmiennych-wejściowych] (tutaj: dwie - x,z), 
    # || a m to ilość[zmiennych-wyjściowych], (tutaj: jedna - y). Logicznie matryca wag ma u nas wymiar 2x1.
weights = np.random.uniform(low=-init_range, high=init_range, size=(2, 1))

#?# ILE BŁĘDÓW #?# systematycznych wziąć pod uwagę? || Krótko: błędy_sys_w_modelu.ilość = ujścia.ilość #!#
    # || Dłużej: Mamy jeden błąd systematyczny, gdyż jest tylko jedno ujście funkcji. Błąd systematyczny to skalar.
biases = np.random.uniform(low=-init_range, high=init_range, size=1)
# Funkcja ujednoznaczniająca, opisuje aktualny stan wag i obciążeń***. + Sanity check.
def pretty_weights():
    wagi = "wagi: %s" % weights
    obci = "obciążenia: %s" % biases
    print (wagi)
    print (obci)
# *** inaczej: 'błędów systematycznych'
print(LinksDict['błąd'])
pretty_weights()<jupyter_output>wagi: [[-0.07847109]
 [ 0.07901402]]
obciążenia: [-0.05457018]<jupyter_text>6. Ustalanie prędkości uczenia się modelu Ogólnie przy MNIST od Hindusa używałem 0.01 i strasznie muliło, ale w 30 krokach robiło robotę.Warto się z tym pobawić. Zwłaszcza przy prostszych modelach, albo mocniejszych maszynach.Ostatecznie zawsze można przerwać operacje i zmienić wartość na większą a mniej dokładną.<jupyter_code># Prędkość nauki modelu - wyciągnięta na panel główny na początku dokumentu.
learning_rate = learn # Dla mnie najlepsze osiągi dawała baza 10.000 obserwacji i prędkość 0.03-0.04.
# Wykonujemy np. 100 iteracji na naszym zestawie treningowym. Do ww parametrów było jak znalazł.
    # Ogólnie rzecz biorąc jest to nieco bardziej skomplikowane i jeszcze się tym zajmiemy,
    # ale to co musimy na tym etapie wiedzieć, to to, że wraz ze wzrostem prędkości_uczenia potrzebujemy mniej iteracji,
    # podczas gdy wraz jej spadkiem ilość iteracji potrzebnych do osiągnięcia podobnych wyników wzrasta.
        # Miej również na względzie, że zbyt wysoka prędkość nauki (learning_rate) może sprawić, 
        # że strata będzie dążyć do nieskończoności, zamiast do zera.
for i in range (iterations):
    
# To jest równanie modelu liniowego: y = xw + b
    # Tzn. ujścia = żródła * wagi + obciążenia.
    outputs = np.dot(inputs,weights) + biases
# Delty (deltas) to różnice pomiędzy ujściami i celami.
    # Zwróć uwagę, że tutaj delty są wektorami 1000 x 1.
    deltas = outputs - targets
    
# Bierzemy się za normę straty L2, którą dzielimy przez 2 i jeszcze przez liczbę obserwacji.
    # Chodzi tu po prostu o przeskalowanie przez stałą. Taka operacja nie zmienia logiki optymalizacji,
    # gdyż każda funkcja, która jako swoją podstawową właściwość ma to, że traci na wartości,
    # w miarę poprawiania się wyników modelu (i vice-versa tj. przy wzroście wartości funkcji wyniki się pogarszają)
    # MOŻE być użyta jako funkcja straty. Tj. liczą się tu proporcje różnic a nie wartości bezwzględne.
    loss = np.sum(deltas ** 2) / 2 / observations
    
    # Przy każdej iteracji zwracamy funkcję straty, dzięki czemu mamy podgląd na tFo czy spada tak jak oczekujemy.
    print (loss)
    
# Delty również podlegają skalowaniu w taki sam sposób jak funkcja straty.
    # Mam na myśli, że w ten sposób można prędkość nauki (learning_rate) ooderwać od ilości przypadków (observations)
    # To znowu nie zmieni to logiki działania programu, ale z kolei ułatwia dobrać pojedynczą prędkość nauki
    # która pozostanie taka sama, bez względu na ilość próbek (observations).
    # Możesz spróbować rozwiązać ten problem bez przeskalowywania i zobaczyć jak Ci to odpowiada.
    deltas_scaled = deltas / observations

# Koniec końców stosujemy zasadę aktualizacji gradientu prostego. Wagi to 2x1, prędkość uczenia się jest skalarem,
    # źródła są 1000x2 a wyskalowane delty (deltas_scaled) 1000x1.
    # Transponujemy źródła (co jest dozwolone), aby móc wykonać operację tj będą teraz 2x1000. To  skraca się
    # z 1000x1 i mamy 2x1.
    # wagi = wagi - prędkość_nauki x iloczyn_skalarny(dane_wejściowe_transponuj x delty_przeskalowane)**
    weights = weights - learning_rate * np.dot(inputs.T,deltas_scaled)
    # obciążenia = obciążenia - prędkość_nauki x delty_przeskalowane [skalar]
    biases = biases - learning_rate * np.sum(deltas_scaled)
    
    # Wagi są aktualizowane zgodnie z zasadami algebry liniowej (macierz minus inna macierz)
    # Mamy jeden błąd systematyczny, a zatem musimy przetransformować delty na skalar (deltas_scaled).
    # Obydwie linijki są zgodne z metodologią gradientu prostego.
# **Ależ służę uprzejmie:
print(LinksDict['iloczyn'])
# Zwracamy wagi i obciążenia, żebyśmy mogli zobaczyć na ile otrzymaliśmy to co chcieliśmy.
# Gdy deklarowaliśmy cele, wiedzieliśmy, że wagi powinny mieć odpowiednio 13 i 7 a obciążenie 10.
pretty_weights()

# Krócej, choć nieco mniej czytelnie byłoby wstawić: "print(weights, biases)"
# Może być tak, że funkcja nie osiągnąła jeszcze minimum i potrzeba jej więcej iteracji.<jupyter_output>wagi: [[13.01428846]
 [ 7.01881738]]
obciążenia: [-10.40875273]<jupyter_text>7. Jaka jest (ko)relacja wyników do celów?Sprawdzamy, czy model mierzy to co miał mierzyć.tj. sprawdzamy jego dokładność* Im bliżej mu do linii pod kątem 45* tym bardziej liniowa jest zależnośćmiędzy celami a wynikami zwracanymi przez funkcję.*Poniżej link wiki pt. "Dokładność i precyzja metody pomiaru" dla dociekliwych:<jupyter_code>print(LinksDict['metoda'])
# Zwracamy ujścia i cele aby zobaczyć czy jest między nimi liniowa zależność. (patrz : "Dokładność metody pomiaru")
    # Wizualizacja nie jest tu konieczna, a potem nawet nie będzie możliwa do wykonania,
    # ale ogólnie warto wiedzieć jak ją wykonać, oraz co tutaj sprawdzamy i jak to wygląda na grafie.
plt.plot(outputs,targets)
plt.xlabel('outputs')
plt.ylabel('targets')
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07319734743557459, 'fraction_numerical': 0.019222697893058003, 'mean_word_length': 4.104541559554413, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 24, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '266761', 'license': 'MIT', 'n_tokens_mistral': 5076, 'n_tokens_neox': 5115, 'n_words': 1422, 'path': 'research_process/Course_pract_1.ipynb', 'repo_name': 'Bahusson/ai', 'size': '11913'}