Suppose u and v are any two vectors in Rn. (When I say this, I just mean that they both have the same number of components.)
Letβs think about all possible vectors of the form kv, where k can be any scalar. Any vector of the form kv is a scalar multiple of v, and points in the same direction as v (if k>0) or the opposite direction (if k<0). What is different about these scalar multiples is how long they are.
To get a sense of what I mean by this, play with the slider for k below. There are three vectors being visualized: some u, some v, and kv, which depends on the value of k you choose.
# This chunk must be in the first plotting cell of each notebook in order to guarantee that the mathjax script is loaded.
import plotly
from IPython.display import display, HTML
plotly.offline.init_notebook_mode()
display(HTML(
'<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_SVG"></script>'
))
import numpy as np
import plotly.graph_objects as go
u = np.array([3, 1])
v = np.array([3, -2])
def create_vector_trace(coordinates, color, label):
x, y = coordinates
return go.Scatter(
x=[0, x],
y=[0, y],
mode='lines+markers',
line=dict(color=color, width=4),
marker=dict(
size=[0, 16],
color=[color, color],
symbol=['circle', 'arrow'],
angleref='previous'
),
hovertemplate='(%{x}, %{y})<extra></extra>',
showlegend=False,
name=label
)
def make_traces(k, show_error):
traces = []
traces.append(create_vector_trace(tuple(u), 'orange', r'$\vec u$'))
traces.append(create_vector_trace(tuple(v), 'rgba(61,129,246,0.5)', r'$\vec v$'))
kv = k * v
traces.append(create_vector_trace(tuple(kv), '#004d40', r'$k\vec v$'))
error = u - kv
if show_error:
traces.append(go.Scatter(
x=[kv[0], u[0]],
y=[kv[1], u[1]],
mode='lines+markers',
line=dict(color='#d81b60', width=3, dash='dot'),
marker=dict(
size=[0, 12],
color=['#d81b60', '#d81b60'],
symbol=['circle', 'arrow'],
angleref='previous'
),
hovertemplate='(%{x}, %{y})<extra></extra>',
showlegend=False,
name=fr'$\vec u - {k:.2f}\vec v$'
))
return traces, kv, error
def plot_projection(title, show_error):
k_vals = np.linspace(-1, 2.0, 101)
k0 = 0.83
traces, kv, error = make_traces(k0, show_error)
fig = go.Figure(data=traces)
# Permanent annotations for u and v
fig.add_annotation(
x=0.5 * u[0],
y=0.5 * u[1] + 0.5,
text=r"$\vec u$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="orange"),
align="center"
)
fig.add_annotation(
x=0.5 * v[0],
y=0.5 * v[1] - 0.6,
text=r"$\vec v$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#3d81f6"),
align="center"
)
# k annotation
fig.add_annotation(
x=kv[0] + 0.5,
y=kv[1] - 1,
text=fr"$k \vec v = {k0:.2f} \vec v$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#004d40"),
align="center"
)
# error annotation (only if show_error)
if show_error:
fig.add_annotation(
x=kv[0] + 0.5 * (u[0] - kv[0]) + 1,
y=kv[1] + 0.5 * (u[1] - kv[1]) - 0.5,
text=fr"$\vec e = \vec u - ({k0:.2f}\vec v)$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#d81b60"),
align="center"
)
# Frames for animation/slider
frames = []
for k in k_vals:
traces, kv, error = make_traces(k, show_error)
annots = [
dict(
x=0.5 * u[0],
y=0.5 * u[1] + 0.5,
text=r"$\vec u$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="orange"),
align="center"
),
dict(
x=0.5 * v[0],
y=0.5 * v[1] - 0.6,
text=r"$\vec v$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#3d81f6"),
align="center"
),
dict(
x=kv[0] + 0.5,
y=kv[1] - 1,
text=fr"$k \vec v = {k:.2f} \vec v$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#004d40"),
align="center"
)
]
if show_error:
annots.append(
dict(
x=kv[0] + 0.5 * (u[0] - kv[0]) + 1,
y=kv[1] + 0.5 * (u[1] - kv[1]) - 0.5,
text=fr"$\vec e = \vec u - ({k:.2f}\vec v)$",
showarrow=False,
font=dict(size=14, family="Palatino, serif", color="#d81b60"),
align="center"
)
)
frames.append(go.Frame(
data=traces,
name=f"{k:.2f}",
layout=go.Layout(
annotations=annots
)
))
fig.frames = frames
# Slider steps
steps = []
for i, k in enumerate(k_vals):
step = dict(
method="animate",
args=[
[f"{k:.2f}"],
{"mode": "immediate", "frame": {"duration": 0, "redraw": True}, "transition": {"duration": 0}}
],
label=f"{k:.2f}"
)
steps.append(step)
sliders = [dict(
active=int((k0 - k_vals[0]) / (k_vals[1] - k_vals[0])),
currentvalue={"prefix": "k = "},
pad={"t": 30},
steps=steps,
minorticklen=0
)]
fig.update_layout(
title=title,
width=600,
height=500,
yaxis_scaleanchor="x",
margin=dict(l=10, r=10, t=30, b=10),
sliders=sliders,
font=dict(family="Palatino, serif"),
plot_bgcolor="white",
paper_bgcolor="white",
xaxis=dict(
gridcolor="#f0f0f0",
zerolinecolor="#f0f0f0"
),
yaxis=dict(
gridcolor="#f0f0f0",
zerolinecolor="#f0f0f0"
),
)
fig.update_xaxes(range=[-3, 6], tickvals=np.arange(-4, 10), gridcolor="#f0f0f0", zerolinecolor="#f0f0f0")
fig.update_yaxes(range=[-4, 2], tickvals=np.arange(-4, 4), gridcolor="#f0f0f0", zerolinecolor="#f0f0f0")
fig.show(renderer="notebook")
plot_projection("", False)
Loading...
Loading...
Loading...
Loading...
Notice that the set of vectors of the form kv fill out a line. So really, what weβre asking is which vector on this line is closest to u.
In terms of angles, if ΞΈ is the angle between u and v, then the angle between u and kv is either ΞΈ (if k>0) or 180ββΞΈ (if k<0). So changing k doesnβt change how βsimilarβ u and kv are in the cosine similarity sense.
But, some choices of k will make kv closer to u than others. I call this the approximation problem: how well can we recreate, or approximate, u using a scalar multiple of v? It turns out that linear regression is intimately related to this problem. Previously, we were trying to approximate commute times as best as we could using a linear function of departure times.
Letβs be more precise by what we mean by βcloserβ. For any value of k, we can measure the error of our approximation by the length of the error vector, e=uβkv.
plot_projection("", True)
Loading...
Loading...
Continue to play with the slider above for k. How do you get the length of the error vector to be as small as possible?
Intuitively, it seems that to get the error vectore to be as short as possible, we should make it orthogonal to v. Since we can control k, we can control uβkv, so we can make the error vector orthogonal to v by choosing the right k.
Our goal is to minimize the length of the error vector β₯eβ₯.
β₯eβ₯
This is the same as minimizing
β₯uβkvβ₯
One way to approach this problem is to treat the above expression as a function of k and find the value of k that minimizes it through calculus. Youβll do this in Homework 3. Iβll show you a more geometric approach here.
Weβve guessed, but not yet shown that the shortest possible error vector is the one that is orthogonal to v. Let koβ be the value of k that makes the error vector eoβ=uβkoβvorthogonal to v. Here, weβll prove that koβ is the βbestβ choice of k by showing that any other choice of k will result in an error vector that is longer than eoβ.
For comparison, let kβ² be some other value of k, and let its error vector be eβ²=uβkβ²v.
# This chunk must be in the first plotting cell of each notebook in order to guarantee that the mathjax script is loaded.
import plotly
from IPython.display import display, HTML
plotly.offline.init_notebook_mode()
display(HTML(
'<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_SVG"></script>'
))
import numpy as np
import plotly.graph_objects as go
u = np.array([3, 1])
v = np.array([3, -2])
# Calculate k_o where error is orthogonal to v
k_o = np.dot(u, v) / np.dot(v, v)
k_prime = k_o * 0.7 # Make k' slightly shorter than k_o
def create_vector_trace(coordinates, color, label, opacity=1.0):
x, y = coordinates
return go.Scatter(
x=[0, x],
y=[0, y],
mode='lines+markers',
line=dict(color=color, width=4),
marker=dict(
size=[0, 16],
color=[color, color],
symbol=['circle', 'arrow'],
angleref='previous'
),
hovertemplate='(%{x}, %{y})<extra></extra>',
showlegend=False,
name=label,
opacity=opacity
)
def create_error_trace(start_coords, end_coords, color, label, opacity=1.0):
return go.Scatter(
x=[start_coords[0], end_coords[0]],
y=[start_coords[1], end_coords[1]],
mode='lines+markers',
line=dict(color=color, width=3, dash='dot'),
marker=dict(
size=[0, 12],
color=[color, color],
symbol=['circle', 'arrow'],
angleref='previous'
),
hovertemplate='(%{x}, %{y})<extra></extra>',
showlegend=False,
name=label,
opacity=opacity
)
def plot_static_projection():
# Calculate vectors
k_o_v = k_o * v
k_prime_v = k_prime * v
e_o = u - k_o_v # Error vector for k_o (orthogonal to v)
e_prime = u - k_prime_v # Error vector for k_prime
# Create traces
traces = []
# --- SHADE THE RIGHT-ANGLED TRIANGLE ---
# The triangle vertices are: k_prime_v, k_o_v, u
triangle_x = [k_prime_v[0], k_o_v[0], u[0], k_prime_v[0]]
triangle_y = [k_prime_v[1], k_o_v[1], u[1], k_prime_v[1]]
triangle_trace = go.Scatter(
x=triangle_x,
y=triangle_y,
fill='toself',
fillcolor='rgba(255, 215, 64, 0.25)', # light gold, semi-transparent
line=dict(color='rgba(255, 215, 64, 0.0)', width=0), # no border
mode='lines',
showlegend=False,
hoverinfo='skip'
)
traces.append(triangle_trace)
# --- END SHADE ---
# Original vectors u and v
traces.append(create_vector_trace(tuple(u), 'orange', r'$\vec u$'))
traces.append(create_vector_trace(tuple(v), '#3d81f6', r'$\vec v$', opacity=0.5))
# k_o * v (optimal projection) - now #004d40
traces.append(create_vector_trace(tuple(k_o_v), '#004d40', r'$k_o \vec v$'))
# k' * v (shorter projection) - now gray
traces.append(create_vector_trace(tuple(k_prime_v), 'gray', r"$k' \vec v$", opacity=0.8))
# Error vector e_o (orthogonal to v)
traces.append(create_error_trace(tuple(k_o_v), tuple(u), '#d81b60', r'$\vec e_o$'))
# Error vector e' (for k') - now gray
traces.append(create_error_trace(tuple(k_prime_v), tuple(u), 'gray', r"$\vec e'$"))
# Add right angle symbol at the base of e_o
# We'll draw a small right angle marker at the base (k_o_v) between v and e_o
# The right angle marker will be a small "L" shape
# Compute unit vectors for v and e_o
v_unit = v / np.linalg.norm(v)
e_o_unit = e_o / np.linalg.norm(e_o)
# The right angle marker will be offset a small distance from k_o_v along v_unit and e_o_unit
marker_size = 0.12 # controls the size of the right angle symbol
# Start at k_o_v, move a bit along v_unit, then a bit along e_o_unit to form the "L"
p0 = k_o_v
p1 = p0 + -v_unit * marker_size
p2 = p1 + e_o_unit * marker_size
right_angle_trace = go.Scatter(
x=[p1[0], p2[0], p2[0] - (p1[0] - p0[0])],
y=[p1[1], p2[1], p2[1] - (p1[1] - p0[1])],
mode='lines',
line=dict(color="#222", width=2),
showlegend=False,
hoverinfo='skip'
)
traces.append(right_angle_trace)
fig = go.Figure(data=traces)
# Add annotations, positioned tightly to the relevant vectors/points
# u vector (at tip)
fig.add_annotation(
x=u[0],
y=u[1] + 0.13,
text=r"$\vec u$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="orange"),
align="center"
)
# v vector (at tip)
fig.add_annotation(
x=v[0] + 0.13,
y=v[1] - 0.13,
text=r"$\vec v$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="#3d81f6"),
align="center"
)
# k_o * v (at tip) - now #004d40
fig.add_annotation(
x=k_o_v[0] + -0.1,
y=k_o_v[1] - 0.13,
text=fr"$k_o \vec v$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="#004d40"),
align="left"
)
# k' * v (at tip) - now gray
fig.add_annotation(
x=k_prime_v[0] - 0.1,
y=k_prime_v[1] - 0.13,
text=fr"$k' \vec v$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="gray"),
align="right"
)
# e_o error vector (midpoint between k_o*v and u, offset slightly)
fig.add_annotation(
x=(k_o_v[0] + u[0]) / 2 + 0.21,
y=(k_o_v[1] + u[1]) / 2 + 0.03,
text=r"$\vec e_o$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="#d81b60"),
align="center"
)
# e' error vector (midpoint between k'*v and u, offset slightly) - now gray
fig.add_annotation(
x=(k_prime_v[0] + u[0]) / 2 - 0.08,
y=(k_prime_v[1] + u[1]) / 2 + 0.12,
text=r"$\vec e'$",
showarrow=False,
font=dict(size=18, family="Palatino, serif", color="gray"),
align="center"
)
# Triangle side annotation (midpoint between k_o*v and k'*v, offset slightly)
# fig.add_annotation(
# x=(k_prime_v[0] + k_o_v[0]) / 2 - 0.08,
# y=(k_prime_v[1] + k_o_v[1]) / 2 - 0.13,
# text=r"$(k_o - k') \vec v$",
# showarrow=False,
# font=dict(size=15, family="Palatino, serif", color="purple"),
# align="center"
# )
# Update layout: tightly clip axes around the figure
min_x = min(0, k_prime_v[0], k_o_v[0], u[0], v[0]) - 0.5
max_x = max(0, k_prime_v[0], k_o_v[0], u[0], v[0]) + 0.5
min_y = min(0, k_prime_v[1], k_o_v[1], u[1], v[1]) - 0.5
max_y = max(0, k_prime_v[1], k_o_v[1], u[1], v[1]) + 0.5
fig.update_layout(
width=480,
height=420,
yaxis_scaleanchor="x",
margin=dict(l=10, r=10, t=10, b=10),
font=dict(family="Palatino, serif"),
plot_bgcolor="white",
paper_bgcolor="white",
)
fig.update_xaxes(
range=[min_x, max_x],
showticklabels=False,
gridcolor="#fff",
zerolinecolor="#fff"
)
fig.update_yaxes(
range=[min_y, max_y],
showticklabels=False,
gridcolor="#fff",
zerolinecolor="#fff"
)
# Show as static PNG
return fig
plot_static_projection().show(renderer='png', scale=3)
Loading...
Loading...
Iβve drawn kβ²v in gray. Arbitrarily, Iβve shown it as being shorter than koβv, but I could have drawn it as being longer and the argument would be the same. The prime has nothing to do with derivatives, by the way β itβs just a new variable.
The vectors kβ²v and koβv, along with their corresponding error vectors, create a right-angled triangle, shaded in gold above. This triangle has a hypotenuse of eβ² and legs of eoβ and kβ²vβkoβv.
Applying the Pythagorean theorem to this triangle gives us
β₯kβ²vβkoβvβ₯2β₯0, with equality only when we choose kβ²=koβ, but weβve assumed that kβ²ξ =koβ.
This implies that
β₯eβ²β₯2=β₯eoββ₯2+someΒ positiveΒ number
which means that β₯eβ²β₯2>β₯eoββ₯2.
In other words, if kβ²ξ =koβ, then β₯eβ²β₯>β₯eoββ₯. Thus, the error vector eoβ is shorter than any other error vector eβ², and the best choice of k is koβ!
What was the point of all this again?
We know that the answer is kβv, where kβ is the value of k that makes the error vector e=uβkβv orthogonal to v. (Iβve switched from calling this optimal scalar koβ to kβ; koβ was a name I used in the proof above, but more generally, βoptimalβ values are starred for our purposes).
Letβs now find the value of kβ, in terms of just u and v. If e is orthogonal to v, then vβ e=0.
The boxed value above is a scalar. It tells us the optimal amount to multiply v by to get the best approximation of u. Once we multiply that boxed scalar, kβ, by the vector v, we get whatβs called the orthogonal projection of u onto v.
Among all vectors of the form kv, pβ above is the one that is closest to u.
Why βorthogonal projectionβ? βOrthogonalβ comes from the fact that pββs error vector e=uβpβ is orthogonal to v. βProjectionβ comes from the intuition you should have that pβ is the shadow of u onto v.
We might say 2/3β is the projection error. Another way of thinking of it is as the shortest distance from the point (1,2,2) to the line that passes through (0,0,0) and (1,1,1).
Consider the points p1β=(1,2,2) and p2β=(1,1,1) in R3.
What is the shortest distance between p1β and the line that passes through p2β and the origin, (0,0,0)?
Solution
The answer is 2/3β. This example didnβt require any addition math beyond the previous example; it just serves to remind you of the geometry of the situation. The set of all possible scalar multiples of v fill out a line in R3, and that line passes through (0,0,0) and p2β=(1,1,1).
Why does that line pass through (0,0,0)? Consider the vector 0v β itβs the zero vector!
Let u=β£β‘β122ββ¦β€β and v=β£β‘β111ββ¦β€β.
In the first example, we found the orthogonal projection of u onto v.
Now, do the opposite: find the orthogonal projection of v onto u.
Solution
Now, weβre projecting v onto u, which means our answer is going to be a multiple of u, not v as in the first part.
The orthogonal projection of v onto u is given by
pβ=uβ uvβ uβu
The formula for the scalar in front of u is the same as in Part 1, but with all vβs replaced by uβs and vice versa. The numerator is the same, since uβ v=vβ u. The denominator is different; just remember that the denominator is the squared norm of the vector youβre projecting onto.
Notice that in both parts, the orthogonal projection pβ is the same! This is not a coincidence. Both vectors point in the same direction, meaning the set of possible vectors of the form kV is the same as the set of possible vectors of the form kv. Another way to think about this is that they both span the same line in R3 through the origin.
The difference between v and V is that V is a unit vector in the direction of v, meaning that it points in the same direction as v but has β₯Vβ₯=1 rather than β₯vβ₯=7.
Whatβs different is the scalar we need to multiply each vector by to get the orthogonal projection. In the case of the unit vector V, the number in front of V is Vβ Vuβ Vβ, but since Vβ V=β₯Vβ₯2=1, this simplifies to uβ V.
Suppose u,vβRn. Let ΞΈ be the angle between u and v.
Show that the orthogonal projection of u onto v is equal to
pβ=(β₯uβ₯cosΞΈ)β₯vβ₯vβ
This is not a formula weβd use to actually compute pβ, since finding cosΞΈ is harder than using the dot product-based formula from above. But, what does this formula tell us about the relationship between u and pβ?
Solution
Letβs start with the original formula for the orthogonal projection of u onto v.
pβ=vβ vuβ vβv
Using the fact that uβ v=β₯uβ₯β₯vβ₯cosΞΈ and vβ v=β₯vβ₯2, we can rewrite the formula as
The parentheses around β₯uβ₯cosΞΈ donβt change the calculation, but they help with the interpretation. This shows us that we can think of the orthogonal projection of u onto v as a vector with:
a length of β₯uβ₯cosΞΈ
in the direction of β₯vβ₯vβ, which is a unit vector in the direction of v
Let u=β£β‘β4β12ββ¦β€β and v=β£β‘ββ306ββ¦β€β.
u and v are orthogonal. What does this say about the orthogonal projection of u onto v?
Solution
Since uβ v=0, the orthogonal projection of u onto v is the zero vector, 0.
p=vβ vuβ vβv=360βv=0
Intuitively, u and v travel in totally different different directions. Travelling any amount of v will take you further away from u. So, itβs best to stick with the zero vector, 0.
Why is the sum of pβ1β and pβ2β equal to u? Earlier, I mentioned that we can use orthogonal projections to decompose vectors. Here, when we project u onto v1β, the corresponding error vector e1β is orthogonal to v1β.
If v1β and v2β were not orthogonal, then writing u as a linear combination of v1β and v2β would have involved solving a system of 2 equations and 2 unknowns, as weβve had to do in previous sections.
For instance, if we keep [35β] and look at x1β=[12β] and x2β=[21β], we have that
the projection of u onto x1β is (x1ββ x1βuβ x1ββ)x1β=513βx1β
the projection of u onto x2β is (x2ββ x2βuβ x2ββ)x2β=511βx2β
Iβd like to provide a more general βtheoremβ, on when you can use orthogonal projections to more easily write a vector u as a linear combination of vectors v1β, v2β, ..., vdβ, but weβll need to first study the idea of a basis. Thatβs to come.
This section has been light on activities, since it provided many examples that weβll work through in lecture. But, hereβs one to tie this last point together.
The motivating problem for this section was the approximation problem, which asked us to find the best approximation of a vector u using only a scalar multiple of a vector v.
The next natural step is to consider the case where we want to approximate u using a linear combination of more than one vector, v1β, v2β, ..., vdβ. Why? Remember, this all connects back to the problem of linear regression. The more vectors we have as βbuilding blocksβ in our linear combination, the more features our model will be able to use. (I havenβt made the connection from linear algebra to linear regression yet, but just know this is why weβre studying projections.)
For example, letβs consider u=β£β‘β136ββ¦β€β, v1β=β£β‘β101ββ¦β€β, and v2β=β£β‘β221ββ¦β€β. Among all linear combinations of v1β and v2β, which one is closest to u?
To answer this question, weβd need to find the scalars a1β and a2β such that the error vector
e=uβ(a1βv1β+a2βv2β)
has minimal length, which presumably happens when e is orthogonal to both v1β and v2β.
Travelling down this road, we might be able to find the values of a1β and a2β that minimize the length of e. But then weβll want to ask how we can do this for any u and any set of vectors v1β, v2β, ..., vdβ, and it seems like weβll need a more general solution. In general, to find the βbestβ approximation of u using a linear combination of v1β, v2β, ..., vdβ, weβll need to know about matrices. Weβll introduce matrices in Chapter 5.1.
Instead, in Chapter 4.1, we will set aside the goal of projections temporarily, and instead focus on truly understanding the set of possible linear combinations of a given set of vectors. For example, the vectors v1β=β£β‘β100ββ¦β€β, v2β=β£β‘ββ518ββ¦β€β from earlier define a plane. So, asking which linear combination of v1β and v2β is closest to u is equivalent to asking which point on the plane is closest to u.
from pathlib import Path
import sys
_notes_root = next(p for p in [Path.cwd(), *Path.cwd().parents] if (p / 'myst.yml').exists())
if str(_notes_root) not in sys.path:
sys.path.insert(0, str(_notes_root))
from plane_style import (BLUE, ORANGE, PINK, GRAY, plane_figure, add_plane,
vector3, vector_label, line3, label3,
style_surface_planes, projection_figure)
import numpy as np
import plotly.graph_objects as go
v1 = np.array([1, 0, 1])
v2 = np.array([2, 2, 1])
fig = plane_figure(bounds=(-3, 8))
add_plane(fig, np.cross(v1, v2))
vector3(fig, v1, vector_label('v', sub=1), BLUE)
vector3(fig, v2, vector_label('v', sub=2), BLUE, offset=(-0.5,0.2,0.3))
vector3(fig,(1,3,6),vector_label('u'),ORANGE)
fig.show(renderer='plotly_mimetype')
Loading...
Chapter 4.1 will answer the questions, βwhy do v1β and v2β define a plane?β, βwhich plane do they define?β, and βin general, what do v1β, v2β, ..., vdβ, all in Rn, define?β