\subsubsection{Gauss-Newton Verfahren} Direkt das Newton-Verfahren auf ein Problem anzuwenden kann unmöglich oder schwer praktikabel sein. Die Idee des Gauss-Newton Verfahrens ist es, die komplizierte Funktion $F(x)$ lokal durch eine lineare Funktion approximiert, also: \begin{align*} F(x) \approx F(y) + DF(y) (x - y) = F(y) + DF(y)x - DF(y)y \end{align*} Falls man $A := DF(y)$ und $b = DF(y)y - F(y)$ definiert, so erhält man ein lineares Ausgleichsproblem: \rmvspace \begin{align*} \argmin{x \in \R^n} \frac{1}{2} ||F(x)||^2_2 \approx \argmin{x \in \R^n} \frac{1}{2} ||F(y) + DF(y) x||^2_2 = \argmin{x \in \R^n} \frac{1}{2} ||Ax - b||^2_2 \end{align*} \drmvspace wobei $y$ eine Näherung der Lösung $x$ ist. Die Iterationsvorschrift ist gegeben durch: \rmvspace \begin{align*} x^{(k + 1)} = x^{(k)} - s \smallhspace \text{ mit } s := \argmin{z \in \R^n} ||F(x^{(k)}) - DF(x^{(k)})z||^2_2 \end{align*} \begin{code}{python} def newton(x: np.ndarray, GF, HF, tol=1e1-6, maxIter=50): """ Newton method requires the Gradient & Hessian, more accurate """ s = np.linalg.solve(HF(x), GF(x)) x -= s k = 1 while np.linalg.norm(s) > tol * np.linalg.norm(x) and k < maxIter: s = np.linalg.solve(HF(x), GF(x)) x -= s k += 1 return x, k \end{code} \begin{code}{python} def gauss_newton(x: np.ndarray, F, DF, tol=1e-6, maxIter=50): """ Gauss-Newton algorithm to solve non-linear problem, needs 'only' the Jacobian """ s = np.linalg.lstsq(DF(x), F(x))[0] x = x-s k = 1 while np.linalg.norm(s) > tol * np.linalg.norm(x) and k < maxIter: s = np.linalg.lstsq(DF(x), F(x))[0] x = x-s k += 1 return x, k \end{code} Der Vorteil ist, dass die zweite Ableitung nicht benötigt wird, jedoch ist die Konvergenzordnung niedrieger ($p \leq 2$) \newpage \setLabelNumber{all}{3} \inlineex Wir haben zwei Modellfunktionen, $F_1(t) = a_1 + b_1 e^{-c_1 t}$ und $F_2(t) = a_2 - b_2 e^{-c_2 t}$. ($F_1$ ist ein Heizvorgang, $F_2$ ist ein Abkühlvorgang). Untenstehender code berechnet die Lösung des nichtlinearen Ausgleichsproblems \begin{code}{python} # Data given t = np.arange(0, 30, 5); n = len(t) heat = np.array([24.34, 18.93, 17.09, 16.27, 15.97, 15.91]) cool = np.array([9.66, 18.8, 22.36, 24.07, 24.59, 24.91]) # Functions & Jacobian F_1 = lambda a: a[0] + a[1]*np.exp( -a[2]*t ) - heat F_2 = lambda a: a[0] - a[1]*np.exp( -a[2]*t ) - cool def J_1(a): J = np.zeros((n, 3)) for k in range(n): J[k, 0] = 1 J[k, 1] = np.exp( -t[k] * a[2] ) J[k, 2] = -t[k]*a[1] * np.exp( -t[k]*a[2] ) return J def J_2(a): J = np.zeros((n, 3)) for k in range(n): J[k, 0] = 1 J[k, 1] = -np.exp( -t[k]*a[2] ) J[k, 2] = t[k]*a[1] * np.exp( -t[k]*a[2] ) return J # Start vectors (Guessed) x_1 = np.array([10, 5, 0]) x_2 = np.array([30, 10, 0]) # Coefficients via Gauss-Newton a_1, it_1 = gauss_newton(x_1, F_1, J_1) a_2, it_2 = gauss_newton(x_2, F_2, J_2) # The final functions found via Gauss-Newton f_1 = lambda x: a_1[0] + a_1[1]*np.exp( -a_1[2]*x ) f_2 = lambda x: a_2[0] - a_2[1]*np.exp( -a_2[2]*x ) \end{code}