Activity 3.1 - SVD

SUBMISSION INSTRUCTIONS

  1. Render to html
  2. Publish your html to RPubs or Posit Connect
  3. Submit a link to your published solutions

Problem 1

A)

Consider plots of \(UD_i\) for four different \(D_i\):

Match the \(D\) to the plot.

  • \(D_1 = \begin{pmatrix} 3 & 0 \\ 0 & 2 \end{pmatrix}\)
  • \(D_2 = \begin{pmatrix} 3 & 0 \\ 0 & 1 \end{pmatrix}\)
  • \(D_3 = \begin{pmatrix} 1 & 0 \\ 0 & 0.5 \end{pmatrix}\)
  • \(D_4 = \begin{pmatrix} 2 & 0 \\ 0 & 1 \end{pmatrix}\)

A=D4

B=D1

C=D2

D=D3

B)

Consider two different \(D\) matrices:

\[D_1 = \begin{pmatrix} 4 & 0\\ 0 & 1 \end{pmatrix}\] \[D_2 = \begin{pmatrix} 2 & 0\\ 0 & 1 \end{pmatrix}\]

and two different \(V\) matrices:

\[V_1 = \begin{pmatrix} 0.894 & -0.447\\ 0.447 & 0.894 \end{pmatrix}\] \[V_2 = \begin{pmatrix} -0.447 & 0.894\\ 0.894 & 0.447 \end{pmatrix}\]

Consider the following matrix product:

\[UD_i V_j^T, i\in \{1,2\}, j \in \{1,2\}\]

In other words, \(U\) is multiplied by \(D_i V_j^T\) for all four combinations of \(D_i\) and \(V_j\). Match the product to the plot.

A=UD1V1T

B=UD1V2T

C=UD2V1T

D=UD2V2T

Problem 2

Reconsider the US air pollution data set:

library(HSAUR2)
Loading required package: tools
data(USairpollution)

A)

Perform singular value decomposition of this data matrix. Then create the matrix \(D\). Describe what this matrix looks like.

library(HSAUR2)
data(USairpollution)

X  <- as.matrix(USairpollution)
sv <- svd(X)
D  <- diag(sv$d)

knitr::kable(round(D, 2), caption = "Matrix D (singular values on the diagonal)")
Matrix D (singular values on the diagonal)
7051.95 0.00 0.00 0.00 0.00 0.00 0.00
0.00 931.12 0.00 0.00 0.00 0.00 0.00
0.00 0.00 540.46 0.00 0.00 0.00 0.00
0.00 0.00 0.00 92.71 0.00 0.00 0.00
0.00 0.00 0.00 0.00 85.24 0.00 0.00
0.00 0.00 0.00 0.00 0.00 52.95 0.00
0.00 0.00 0.00 0.00 0.00 0.00 10.14

B)

Verify that \(X=UDV^T\) by plotting all the entries of \(X\) versus all the entries of \(UDV^T\) with the 0/1 line.

X <- as.matrix(USairpollution)

components <- svd(X)
U <- components$u
D <- components$d
V <- components$v

Xrecomp <- U %*% diag(D) %*% t(V)

plot(X, Xrecomp); abline(0, 1)

cor(as.vector(X), as.vector(Xrecomp))
[1] 1

C)

Consider low-dimensional approximations of the data matrix. What is the fewest number of dimensions required to yield a correlation between the entries of \(X\) and \(\tilde X\) of at least 0.9?

k_values <- 1:ncol(X)

cors <- sapply(k_values, \(k) {
  Xtilde <- U[, 1:k, drop = FALSE] %*% diag(D[1:k], nrow = k) %*% t(V[, 1:k, drop = FALSE])
  cor(as.vector(X), as.vector(Xtilde))
})

data.frame(k = k_values, correlation = round(cors, 4))
  k correlation
1 1      0.9864
2 2      0.9966
3 3      0.9998
4 4      0.9999
5 5      1.0000
6 6      1.0000
7 7      1.0000
min(k_values[cors >= 0.9])   # fewest dimensions with correlation >= 0.9
[1] 1

D)

Find \(\Sigma\), the covariance matrix of this data set. Then perform eigen-decomposition of this matrix. Also, mean-center (but don’t unit-scale) \(X\): use scale(X, center = TRUE, scale = FALSE). SVD this mean-centered data matrix. Verify that:

  • The eigenvectors of \(\Sigma\) equal the columns of \(V\);

  • The eigenvalues of \(\Sigma\) equal the diagonals of \(D^2/(n-1)\).

    library(HSAUR2)
    data(USairpollution)
    
    X <- as.matrix(USairpollution)
    n <- nrow(X)
    
    # Covariance matrix
    Sigma <- cov(X)
    round(Sigma, 2)
                SO2    temp      manu     popul   wind  precip predays
    SO2      550.95  -73.56   8527.72   6711.99   3.18   15.00  229.93
    temp     -73.56   52.24   -773.97   -262.35  -3.61   32.86  -82.43
    manu    8527.72 -773.97 317502.89 311718.81 191.55 -215.02 1968.96
    popul   6711.99 -262.35 311718.81 335371.89 175.93 -178.05  645.99
    wind       3.18   -3.61    191.55    175.93   2.04   -0.22    6.21
    precip    15.00   32.86   -215.02   -178.05  -0.22  138.57  154.79
    predays  229.93  -82.43   1968.96    645.99   6.21  154.79  702.59
    # Eigen-decomposition of Sigma
    eigenSigma <- eigen(Sigma)
    
    # Mean-center (but don't scale) X, then SVD
    Xc <- scale(X, center = TRUE, scale = FALSE)
    svdXc <- svd(Xc)
    
    # Check 1: eigenvectors of Sigma vs. columns of V
    round(eigenSigma$vectors, 3)
           [,1]   [,2]   [,3]   [,4]   [,5]   [,6]   [,7]
    [1,]  0.017 -0.100  0.209  0.959 -0.152 -0.054 -0.027
    [2,] -0.001  0.026 -0.072 -0.110 -0.478 -0.853 -0.164
    [3,]  0.697 -0.710 -0.067 -0.073 -0.010 -0.002  0.001
    [4,]  0.717  0.693  0.057  0.049  0.011  0.003  0.000
    [5,]  0.000 -0.001  0.005 -0.015  0.025  0.177 -0.984
    [6,]  0.000  0.001  0.266 -0.163 -0.833  0.453  0.064
    [7,]  0.003 -0.069  0.934 -0.185  0.233 -0.184 -0.019
    round(svdXc$v, 3)
           [,1]   [,2]   [,3]   [,4]   [,5]   [,6]   [,7]
    [1,] -0.017  0.100  0.209 -0.959  0.152 -0.054 -0.027
    [2,]  0.001 -0.026 -0.072  0.110  0.478 -0.853 -0.164
    [3,] -0.697  0.710 -0.067  0.073  0.010 -0.002  0.001
    [4,] -0.717 -0.693  0.057 -0.049 -0.011  0.003  0.000
    [5,]  0.000  0.001  0.005  0.015 -0.025  0.177 -0.984
    [6,]  0.000 -0.001  0.266  0.163  0.833  0.453  0.064
    [7,] -0.003  0.069  0.934  0.185 -0.233 -0.184 -0.019
    # Check 2: eigenvalues of Sigma vs. D^2 / (n - 1)
    round(eigenSigma$values, 3)
    [1] 638471.962  14812.038    701.960    205.002    116.705     12.057      1.449
    round(svdXc$d^2 / (n - 1), 3)
    [1] 638471.962  14812.038    701.960    205.002    116.705     12.057      1.449

Problem 3: Spooky scary zombie professor

In this problem we explore how “high” a low-dimensional SVD approximation of an image has to be before you can recognize it.

.Rdata objects are essentially R workspace memory snapshots that, when loaded, load any type of R object that you want into your global environment. The command below, when executed, will load three objects into your memory: mysteryU4, mysteryD4, and mysteryV4. These are the first \(k\) vectors and singular values of an SVD I performed on a 700-pixels-tall \(\times\) 600-pixels-wide image of a certain WSU professor who has turned into a zombie. How low-dimensional an approximation of the full image can you get away with, and still identify the professor?

load('Data/zombie_k4.Rdata')

A)

Write a function that takes SVD ingredients u, d and v and renders the \(700 \times 600\) image produced by this approximation using functions from the magick package. Use your function to determine whether a 4-dimensional approximation to this image is enough for you to tell who has been zombified. Recall that you will likely need to rescale your recomposed approximation so that all pixels are in [0,1].

library(magick)
Linking to ImageMagick 6.9.13.29
Enabled features: cairo, fontconfig, freetype, heic, lcms, pango, raw, rsvg, webp
Disabled features: fftw, ghostscript, x11
render_svd <- \(u, d, v, k = NULL) {
  u <- as.matrix(u)
  v <- as.matrix(v)
  if (is.matrix(d)) d <- diag(d)     # if D was given as a matrix, pull out the diagonal
  d <- as.numeric(d)
  if (is.null(k)) k <- length(d)
  
  recomposed <- u[, 1:k, drop = FALSE] %*% diag(d[1:k], nrow = k) %*% t(v[, 1:k, drop = FALSE])
  R <- max(recomposed) - min(recomposed)
  recomposed_scaled <- (recomposed - min(recomposed)) / R   # rescale so all pixels are in [0, 1]
  recomposed_image <- image_read(as.raster(recomposed_scaled))
  return(recomposed_image)
}

# 4-dimensional approximation
render_svd(mysteryU4, mysteryD4, mysteryV4, k = 4)

B)

I’m giving you slightly higher-dimensional approximations (\(k=10\) and \(k=50\), respectively) in the objects below:

load('Data/zombie_k10.Rdata')
load('Data/zombie_k50.Rdata')

Create both of the images produced by these approximations. At what point can you tell who has been zombified?

# k = 10
render_svd(mysteryU10, mysteryD10, mysteryV10, k = 10)

# k = 50
render_svd(mysteryU50, mysteryD50, mysteryV50, k = 50)

K=50 is when the face becomes fully clear. K=20 and higher slowly improve the picture quality.

C)

How many numbers need to be stored in memory for each of the following:

  • A full \(700\times 600\) image?
  • A 4-dimensional approximation?
  • A 10-dimensional approximation?
  • A 50-dimensional approximation?

Full: 700x600=420,000

4D: 700×4 + 4 + 4×600 = 2,800 + 4 + 2,400= 5,204

10D: 700×10 + 10 + 10×600 = 7,000 + 10 + 6,000= 13,010

50D:700×50 + 50 + 50×600 = 35,000 + 50 + 30,000= 65,050