<jupyter_start><jupyter_text>(2.3) Optimieren (ooo) &x1F4D8;In dieser Aufgabe implementieren wir verschiedene Strategien zum Optimieren der Parameter einer linearen Regression auf einem Toy-Datensatz. Wir nutzen dazu diverse Hilfsfunktionen aus der begleitenden Datei `utils_optimization.py`.<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')

%matplotlib widget
# %matplotlib notebook

from utils_optimization import x_train, y_train
from utils_optimization import visualize_optimization<jupyter_output><empty_output><jupyter_text>(2.3.1) Vorbereitung - Random SearchIn dieser Aufgabe implementieren wir den Algorithmus aus dem Notebook `Optimieren.ipynb` Abschnitt **2.1** und lernen dabei die Struktur der Aufgabe kennen. Die Aufgabe basiert auf einer Hilfsfunktion `visualize_optimization` aus `utils_optimization`, die als erstes Argument eine Funktion erhält. Diese Funktion sollte folgende Struktur haben:```pythondef update_function(x_train, y_train, w, bias, **kwargs):     do something with x_train, y_train         compute the variables     - w_new     - bias_new         optionally use more kwargs        return w_new, bias_new Then visualizefig, axes = visualize_optimization(update_function)```Das heißt, die Funktion erhält einen Trainingsdatensatz (`x_train` und `y_train`), die bisherigen Werte der Parameter (`w`, `bias`) und optional weitere Argumente. Daraus sollen neue Werte `w_new` und `bias_new` errechnet werden. Ihnen sind alle Freiheiten gegeben, doch unser Ziel sollte sein, zum Schluss hier den Gradientenabstieg zu implementieren.Danach wird die Funktion als Argument (ja, das geht in Python!) der Funktion `visualize_optimization` übergeben.Zunächst fangen wir aber mit etwas einfacherem an. Führen Sie den untestehende Code aus und interpretieren Sie das Ergebnis.<jupyter_code># Die Funktion `random_search_simple` erhält ebenfalls alle geforderten Argumente
# - nutzt diese aber nicht. Stattdessen wird ein zufälliger Wert für `w_new` und `bias_new` 
# erzeugt und zurückgegeben.
def random_search_simple(x_train, y_train, w, bias):
    w_new = np.random.uniform(-3, 3)
    bias_new = np.random.uniform(-4, 4)
    
    return w_new, bias_new
# Wir nutzen `random_search_simple` nun als Argument von `visualize_optimization`
fig, axes = visualize_optimization(random_search_simple, max_iter=200)<jupyter_output><empty_output><jupyter_text>(2.3.2) Random Search adaptierenVersuchen Sie nun Folgendes: Ändern Sie das Argument `max_iter` der Funktion `visualize_optimization`. Was ändert sich?Adaptieren Sie nun die Funktion `random_search_simple` und erstellen Sie eine Funktion `random_search_advanced`, die den Algorithmus aus Abschnitt **2.2** das Notebooks `Optimieren I.ipynb` umsetzt. Visualisieren Sie die Optimierung. Hier der Startpunkt:<jupyter_code>def random_search_advanced(x_train, y_train, w, bias):
    current_loss = np.mean((x_train * w + bias - y_train)**2)
    
    bias_new = bias + np.random.normal(loc=0.0, scale=0.5)
    w_new = w + np.random.normal(loc=0.0, scale=0.5)
    
    new_loss = np.mean((x_train * w_new + bias_new - y_train)**2)
    
    if new_loss <= current_loss:
        return w_new, bias_new
    else:
        return w, bias

fig, axes = visualize_optimization(random_search_advanced, max_iter=200)<jupyter_output><empty_output><jupyter_text>(2.3.3) GradientenabstiegImplementieren Sie nun den Gradientenabstieg. Hier der Startpunkt:<jupyter_code>def gradient_descent(x_train, y_train, w, bias, learning_rate=0.01):
    N = len(y_train)
    y_pred = x_train * w + bias
    
    error = y_pred - y_train
    
    w_new = w - learning_rate * 2/N * np.sum(error * x_train)
    bias_new = bias - learning_rate * 2/N * np.sum(error)
    
    return w_new, bias_new


# die learning_rate kann von außen modifiziert werden:
fig, axes = visualize_optimization(gradient_descent, max_iter=200, learning_rate=0.1)<jupyter_output><empty_output><jupyter_text>(2.3.4) Gradientenabstieg mit RegularisierungImplementieren Sie nun den Gradientenabstieg mit "Weight Decay", das heißt mit L2-Regularisierung. Hier der Startpunkt:<jupyter_code>def gradient_descent(x_train, y_train, w, bias, learning_rate=0.01, alpha=0.01):
    N = len(y_train)
    y_pred = x_train * w + bias
    
    error = y_pred - y_train
    
    w_new = w - learning_rate * 2/N * np.sum(error * x_train) - learning_rate * alpha * w
    bias_new = bias - learning_rate * 2/N * np.sum(error)
    
    return w_new, bias_new


# die learning_rate kann von außen modifiziert werden:
fig, axes = visualize_optimization(gradient_descent, max_iter=500, learning_rate=0.01, alpha=10.0)<jupyter_output><empty_output><jupyter_text>(2.3.5) Der Gradientenabstieg als Machine Learning ModellImplementieren Sie ein Machine Learning Modell wie in Scikit Learn. Hier der Startpunkt:<jupyter_code>from sklearn.exceptions import NotFittedError

class LinearRegression:
    def __init__(self, learning_rate=0.01, max_iter=1000):
        self.learning_rate = learning_rate
        self.max_iter = max_iter
        self._is_fitted = False
        
    def fit(self, x_train, y_train):
        
        w = np.random.normal()
        bias = 0.0
        
        # TODO: Iterative Updates von w und bias
        
        self.w_ = w
        self.bias_ = bias
        
        N = len(y_train)
        
        for i in range(self.max_iter):
                
            y_pred = x_train * self.w_ + self.bias_

            error = y_pred - y_train

            self.w_ = self.w_ - self.learning_rate * 2/N * np.sum(error * x_train)
            self.bias_ = self.bias_ - self.learning_rate * 2/N * np.sum(error)
        
        self._is_fitted = True
        print(self.w_, self.bias_)
    
    def predict(self, x):
        if not self._is_fitted:
            raise NotFittedError
            
        y_pred = self.w_ * x + self.bias_
        
        return y_pred

# Wenn die Klasse LinearRegression richtig implementiert ist, sollte
# der folgende Code funktionieren.
lin_reg = LinearRegression(max_iter=1000)
lin_reg.fit(x_train, y_train)

x_vis = np.linspace(0, 10, 100)
y_vis = lin_reg.predict(x_vis)

plt.figure(figsize=(8, 6))
plt.scatter(x_train, y_train)
plt.plot(x_vis, y_vis, color="red");<jupyter_output>0.5510388172640762 1.3518269393736975
{'fraction_non_alphanumeric': 0.08541996830427892, 'fraction_numerical': 0.020443740095087162, 'mean_word_length': 3.9189399844115353, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 24, 'https://': 0, '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': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '600457', 'license': 'MIT', 'n_tokens_mistral': 2223, 'n_tokens_neox': 2159, 'n_words': 566, 'path': '2_4_overfitting_underfitting/2_3_Aufgabe - Optimieren - Solution.ipynb', 'repo_name': 'layerwise/training', 'size': '6310'}