Skip to main content

MATH 345: Linear Algebra and Optimization

Section 4.5 Coding recap

Subsection Linked notebook

Run Lab U4: Regression as projection
 1 
sebroc.github.io/MATH345-Course-Materials/labs/#lab-u4
. Focus on the design matrix, fitted coefficients, residual, and the computation A.T @ r.
For a quick reference on least-squares commands, transposes, matrix products, and numerical checks such as "close to zero," see the programming appendix sections B.10, B.4, and B.9.

Subsection Review activities

These activities interpret Python code and output for least squares, residual orthogonality, regression, and QR. Reopen the least-squares theoremΒ 4.3.3, the line-fitting activityΒ 4.3.8, or the QR least-squares algorithmΒ 4.4.9 as needed.

Activity 4.5.1. Reading least-squares objects (U4-LO4, U4-LO7).

The following code repeats β€œUnit 2 target revisitedΒ 4.3.5.”
A = np.array([
    [1.0, 0.0],
    [0.0, 1.0],
    [1.0, 1.0],
])

b = np.array([1.0, 2.0, 4.0])

xhat = np.linalg.lstsq(A, b, rcond=None)[0]
bhat = A @ xhat
r = b - bhat

xhat.shape, bhat.shape, r.shape, A.T @ r
  1. What is the shape of xhat?
  2. What is the shape of bhat?
  3. What is the shape of r?
  4. What mathematical object is stored in xhat?
  5. What mathematical object is stored in bhat?
  6. Which variable stores the closest reachable output?
  7. What does A.T @ r check?
  8. Does A.T @ r being close to zero imply that r is close to the zero vector?
Solution.
The matrix \(A\) has two columns and three rows. Therefore xhat has shape (2,), while bhat and r both have shape (3,).
The vector xhat stores the least-squares coefficient vector
\begin{equation*} \widehat{\mathbf{x}} = \begin{bmatrix} 4/3\\ 7/3 \end{bmatrix}. \end{equation*}
The vector bhat stores the fitted vector
\begin{equation*} \widehat{\mathbf{b}} = A\widehat{\mathbf{x}} = \begin{bmatrix} 4/3\\ 7/3\\ 11/3 \end{bmatrix}. \end{equation*}
The vector r stores the residual
\begin{equation*} \mathbf{r} = \mathbf{b}-\widehat{\mathbf{b}} = \begin{bmatrix} -1/3\\ -1/3\\ 1/3 \end{bmatrix}. \end{equation*}
The closest reachable output is bhat, not xhat.
The product
A.T @ r
computes the dot products of the residual with the columns of \(A\text{.}\) It checks that
\begin{equation*} \mathbf{r} \perp \operatorname{col}(A). \end{equation*}
This does not mean that the residual is zero. Here,
\begin{equation*} \|\mathbf{r}\|^2 = \frac13, \end{equation*}
so the residual is nonzero.

Activity 4.5.2. Roundoff and the wrong residual check (U4-LO4, U4-LO7).

For the same problem as β€œReading least-squares objectsΒ 4.5.1,” suppose NumPy gives
A.T @ r
with output
array([2.2e-16, -1.1e-16])
np.linalg.norm(r)
with output
0.5773502691896257
  1. What geometric condition is checked by the first output?
  2. Why are the entries tiny rather than exactly zero?
  3. Does the first output say that the residual vector is zero?
  4. What does the second output show?
  5. Why is
    np.allclose(A.T @ r, np.zeros(A.shape[1]))
    
    a more appropriate numerical check than A.T @ r == 0?
  6. A student instead writes
    A @ r
    
    to check residual orthogonality. What is wrong with this expression?
Solution.
The first output checks whether the residual is orthogonal to every column of \(A\text{.}\) In exact arithmetic,
\begin{equation*} A^T\mathbf{r} = \mathbf{0}. \end{equation*}
Floating-point arithmetic may produce tiny roundoff errors instead of exact zeros.
The first output does not say that \(\mathbf{r}=\mathbf{0}\text{.}\) The second output shows that
\begin{equation*} \|\mathbf{r}\| \approx 0.57735 = \frac{1}{\sqrt{3}}, \end{equation*}
so the residual is nonzero.
The function np.allclose checks whether the entries are numerically close to zero within a tolerance. An entry-by-entry equality check is usually too strict for floating-point output.
The expression A @ r is not the correct check. Here \(A\) has shape \(3\times2\text{,}\) while \(\mathbf{r}\) has length \(3\text{,}\) so the product is not even defined. More importantly, residual orthogonality requires dot products with the columns of \(A\text{,}\) which are collected by
\begin{equation*} A^T\mathbf{r}. \end{equation*}

Activity 4.5.3. Reading a regression design matrix (U4-LO4, U4-LO7).

The following code repeats β€œFit a line to three data pointsΒ 4.3.8.”
t = np.array([0.0, 1.0, 2.0])
y = np.array([1.0, 2.0, 2.0])

X = np.column_stack([np.ones_like(t), t])

c = np.linalg.lstsq(X, y, rcond=None)[0]
yhat = X @ c
r = y - yhat
  1. What do the two columns of \(X\) represent?
  2. Why does c have two entries?
  3. Why does yhat have three entries?
  4. Which variable is the fitted vector?
  5. Which variable is the coefficient vector?
  6. Which vector lies in \(\operatorname{col}(X)\text{?}\)
  7. In β€œTwo views of the same line fitΒ 4.3.9,” which panel shows the projection in \(\mathbb{R}^3\text{?}\)
  8. In the \(t\)-\(y\) data plot, are the residual segments vertical or perpendicular to the fitted line?
  9. What quantity is computed by
    np.linalg.norm(r)**2
    
Solution.
The first column of \(X\) is the constant feature. The second column contains the input values \(t\text{.}\)
The vector c has two entries because the model has two coefficients: the intercept and slope. The vector yhat has three entries because the model makes one prediction at each of the three input values.
The coefficient vector is
\begin{equation*} \widehat{\mathbf{c}} = \begin{bmatrix} 7/6\\ 1/2 \end{bmatrix}. \end{equation*}
The fitted vector is
\begin{equation*} \widehat{\mathbf{y}} = X\widehat{\mathbf{c}} = \begin{bmatrix} 7/6\\ 5/3\\ 13/6 \end{bmatrix}. \end{equation*}
Thus c is the coefficient vector, while yhat is the fitted vector. The vector yhat lies in \(\operatorname{col}(X)\text{.}\)
The right panel of β€œTwo views of the same line fitΒ 4.3.9” shows the projection in \(\mathbb{R}^3\text{.}\) In the \(t\)-\(y\) data plot, the residual segments are vertical. They are not generally perpendicular to the fitted line.
Finally,
np.linalg.norm(r)**2
computes the sum of squared residuals. Its value is
\begin{equation*} \frac16 \end{equation*}
up to roundoff.

Activity 4.5.4. Reading QR least-squares code (U4-LO6, U4-LO7).

Q, R = np.linalg.qr(X, mode="reduced")

c_qr = np.linalg.solve(R, Q.T @ y)

np.allclose(Q.T @ Q, np.eye(Q.shape[1]))
np.allclose(Q @ R, X)
c_qr
  1. What should the shapes of Q and R be?
  2. What does the first np.allclose line check?
  3. What does the second np.allclose line check?
  4. What mathematical system is solved by
    np.linalg.solve(R, Q.T @ y)
    
  5. Why is a system with \(R\) easier to solve than the original least-squares problem?
  6. What should c_qr be close to?
  7. Does obtaining c_qr mean that
    \begin{equation*} X\mathbf{c}=\mathbf{y} \end{equation*}
    has an exact solution?
  8. Why might NumPy return signs in \(Q\) and \(R\) that differ from a hand Gram–Schmidt calculation?
Solution.
The design matrix \(X\) has shape \(3\times2\text{.}\) Reduced QR therefore gives
Q.shape == (3, 2)
R.shape == (2, 2)
The first check verifies
\begin{equation*} Q^TQ=I_2, \end{equation*}
so the columns of \(Q\) are orthonormal. The second check verifies
\begin{equation*} QR=X. \end{equation*}
The call
np.linalg.solve(R, Q.T @ y)
solves
\begin{equation*} R\widehat{\mathbf{c}} = Q^T\mathbf{y}. \end{equation*}
The matrix \(R\) is square and upper triangular, so this is a standard linear system rather than a rectangular least-squares problem.
The result should be close to
array([1.16666667, 0.5])
which represents
\begin{equation*} \widehat{\mathbf{c}} = \begin{bmatrix} 7/6\\ 1/2 \end{bmatrix}. \end{equation*}
This does not mean that \(X\mathbf{c}=\mathbf{y}\) has an exact solution. The fitted vector has a nonzero residual.
A QR factorization allows simultaneous sign changes in a column of \(Q\) and the corresponding row of \(R\text{.}\) Therefore numerical software may return signs different from a hand calculation while still satisfying
\begin{equation*} Q^TQ=I \qquad\text{and}\qquad QR=X. \end{equation*}