别怕数学!用Python和NumPy图解机器学习里的线性代数(附代码)
用Python和NumPy图解机器学习中的线性代数:从恐惧到乐趣的实践指南
第一次接触机器学习时,我被那些矩阵运算和向量空间的概念搞得头晕目眩。直到有一天,我决定用Python代码把这些抽象概念画出来——突然一切都变得清晰了。这就是我想分享给你的方法: 用可视化让数学活起来 。
1. 准备工作:搭建你的数学可视化实验室
在开始之前,我们需要准备好工具。Python的NumPy和Matplotlib将成为我们的主要武器。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 用于3D绘图
为什么选择NumPy?
这个库不仅提供了高效的数组操作,更重要的是它设计时就考虑了数学表达的自然性。当你写
A @ B
时,就是在做矩阵乘法,这种直观性对学习至关重要。
安装必要的库:
pip install numpy matplotlib
提示:推荐使用Jupyter Notebook进行交互式实验,可以即时看到代码运行结果和图形输出。
2. 向量:不只是箭头,而是数据的DNA
2.1 向量的可视化表达
让我们从最基本的向量开始。在机器学习中,向量可以代表一个数据点、一个特征集,甚至是整个模型的参数。
# 创建两个2D向量
v1 = np.array([2, 3])
v2 = np.array([-1, 2])
# 绘制向量
plt.quiver(0, 0, v1[0], v1[1], angles='xy', scale_units='xy', scale=1, color='r')
plt.quiver(0, 0, v2[0], v2[1], angles='xy', scale_units='xy', scale=1, color='b')
plt.xlim(-3, 3)
plt.ylim(-1, 4)
plt.grid()
plt.show()
这段代码会画出两个从原点出发的向量,红色是
[2,3]
,蓝色是
[-1,2]
。看到图形后,你可以直观理解向量的方向和大小。
2.2 向量运算的几何意义
向量加法 在几何上表现为平行四边形法则:
v_sum = v1 + v2
plt.quiver(0, 0, v1[0], v1[1], angles='xy', scale_units='xy', scale=1, color='r')
plt.quiver(v2[0], v2[1], v1[0], v1[1], angles='xy', scale_units='xy', scale=1, color='r', alpha=0.3)
plt.quiver(0, 0, v2[0], v2[1], angles='xy', scale_units='xy', scale=1, color='b')
plt.quiver(v1[0], v1[1], v2[0], v2[1], angles='xy', scale_units='xy', scale=1, color='b', alpha=0.3)
plt.quiver(0, 0, v_sum[0], v_sum[1], angles='xy', scale_units='xy', scale=1, color='g')
plt.xlim(-2, 4)
plt.ylim(-1, 6)
plt.grid()
plt.show()
这个可视化展示了向量加法的平行四边形法则:两个向量的和就是它们构成的平行四边形的对角线。
3. 矩阵:数据变形的魔术师
3.1 矩阵作为线性变换
矩阵最强大的特性之一是它可以表示线性变换。让我们看看几种基本变换如何改变向量的位置。
# 定义一个变换矩阵(旋转45度)
theta = np.pi/4 # 45度
rotation_matrix = np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
# 应用变换
v_rotated = rotation_matrix @ v1
# 绘制变换前后的向量
plt.quiver(0, 0, v1[0], v1[1], angles='xy', scale_units='xy', scale=1, color='r')
plt.quiver(0, 0, v_rotated[0], v_rotated[1], angles='xy', scale_units='xy', scale=1, color='b')
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.grid()
plt.show()
这个例子展示了矩阵如何旋转向量。你可以尝试修改
theta
值,观察不同旋转角度的影响。
3.2 矩阵乘法的可视化理解
矩阵乘法是线性代数中最重要的运算之一。让我们可视化两个矩阵相乘的效果:
A = np.array([[1, 2], [3, 4]])
B = np.array([[0, 1], [1, 0]])
# 绘制原始向量
vectors = np.array([[1, 0], [0, 1]])
colors = ['r', 'b']
for i, v in enumerate(vectors):
plt.quiver(0, 0, v[0], v[1], angles='xy', scale_units='xy', scale=1, color=colors[i])
# 绘制经过B变换后的向量
for i, v in enumerate(vectors):
v_trans = B @ v
plt.quiver(0, 0, v_trans[0], v_trans[1], angles='xy', scale_units='xy', scale=1, color=colors[i], alpha=0.5)
# 绘制经过A和B变换后的向量
for i, v in enumerate(vectors):
v_trans = A @ (B @ v)
plt.quiver(0, 0, v_trans[0], v_trans[1], angles='xy', scale_units='xy', scale=1, color=colors[i], linestyle='--')
plt.xlim(-2, 2)
plt.ylim(-2, 2)
plt.grid()
plt.show()
这个可视化展示了矩阵乘法的结合性:先应用B再应用A的变换,等同于应用AB的变换。
4. 特征值与特征向量:揭示矩阵的本质
4.1 什么是特征向量?
特征向量是在线性变换下方向保持不变的向量,只是长度可能会改变(缩放因子就是特征值)。
# 定义一个矩阵
A = np.array([[3, 1], [1, 2]])
# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)
# 绘制原始向量和变换后的向量
fig, ax = plt.subplots()
for i in range(len(eigenvectors)):
v = eigenvectors[:, i]
v_scaled = v * 3 # 放大以便观察
v_transformed = A @ v_scaled
ax.quiver(0, 0, v_scaled[0], v_scaled[1], angles='xy', scale_units='xy', scale=1, color='b')
ax.quiver(0, 0, v_transformed[0], v_transformed[1], angles='xy', scale_units='xy', scale=1, color='r')
ax.text(v_scaled[0], v_scaled[1], f'λ={eigenvalues[i]:.2f}', fontsize=12)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()
plt.show()
在这个例子中,蓝色向量是特征向量,红色向量是变换后的结果。你会注意到红色向量与蓝色向量方向相同,只是长度发生了变化——这正是特征向量的定义。
4.2 特征分解的实际应用
特征分解在PCA(主成分分析)中有重要应用。让我们模拟一个数据集并找到其主要成分:
# 生成一些随机数据
np.random.seed(42)
data = np.random.multivariate_normal(mean=[0, 0], cov=[[3, 2], [2, 3]], size=100)
# 计算协方差矩阵
cov_matrix = np.cov(data.T)
# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 绘制数据和特征向量
plt.scatter(data[:, 0], data[:, 1], alpha=0.6)
for i, (value, vector) in enumerate(zip(eigenvalues, eigenvectors.T)):
plt.quiver(0, 0, vector[0]*value, vector[1]*value, angles='xy', scale_units='xy', scale=1, color=['r','b'][i],
label=f'PC{i+1} (λ={value:.2f})')
plt.legend()
plt.grid()
plt.show()
这个可视化展示了数据的主要变化方向(主成分),这正是PCA降维的基础。
5. 奇异值分解(SVD):矩阵的终极解剖
5.1 SVD的几何解释
奇异值分解可以将任何矩阵分解为旋转-缩放-旋转三个简单变换的组合。
# 定义一个矩阵
A = np.array([[3, 1], [1, 2]])
# 计算SVD
U, S, Vt = np.linalg.svd(A)
# 创建一个单位圆
theta = np.linspace(0, 2*np.pi, 100)
circle = np.vstack([np.cos(theta), np.sin(theta)])
# 应用Vt (旋转/反射)
step1 = Vt @ circle
# 应用S (缩放)
step2 = np.diag(S) @ step1
# 应用U (旋转/反射)
step3 = U @ step2
# 绘制变换过程
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
titles = ['单位圆', '第一步: Vt', '第二步: Σ', '第三步: U']
for i, (ax, data, title) in enumerate(zip(axes, [circle, step1, step2, step3], titles)):
ax.plot(data[0], data[1])
ax.set_title(title)
ax.set_aspect('equal')
ax.grid()
plt.tight_layout()
plt.show()
这个动画般的分解展示了任何矩阵变换都可以分解为三个基本步骤,这对理解矩阵的本质非常有帮助。
5.2 SVD在图像压缩中的应用
让我们用SVD来压缩一张图片,看看线性代数如何实际应用:
from skimage import data
# 加载示例图片
image = data.camera()
# 执行SVD
U, S, Vt = np.linalg.svd(image, full_matrices=False)
# 选择前k个奇异值
k = 50
compressed = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
# 显示原始和压缩后的图像
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.imshow(image, cmap='gray')
ax1.set_title('原始图像')
ax2.imshow(compressed, cmap='gray')
ax2.set_title(f'压缩图像 (k={k})')
plt.show()
# 计算压缩率
original_size = image.size
compressed_size = U[:, :k].size + S[:k].size + Vt[:k, :].size
print(f'压缩比: {compressed_size/original_size:.2%}')
这个例子展示了如何用线性代数中的概念(这里是SVD)来实现实用的图像压缩技术。
更多推荐



所有评论(0)