线性代数(十五)机器学习中的线性代数
Chen Kai BOSS

如果你问一个机器学习工程师"日常用得最多的数学是什么",答案几乎必然是线性代数。从数据的向量表示,到神经网络的前向传播,再到推荐系统的矩阵分解——线性代数就像机器学习的"母语"。本章将从直觉出发,深入理解这些核心算法背后的线性代数原理。

机器学习中的向量表示

数据就是向量

机器学习的第一步是把现实世界的对象转换成向量。一张 像素的手写数字图片,可以展开成 维向量;一段文本可以用词频统计或词嵌入转换成向量;一个用户的行为记录可以编码成特征向量。

为什么要这么做?因为向量是线性代数的基本操作对象。一旦数据变成向量,我们就可以:

  • 计算距离(欧氏距离、余弦距离)
  • 做线性组合(特征加权)
  • 做投影(降维)
  • 做矩阵乘法(特征变换)

特征空间的几何

假设有 个样本,每个样本有 个特征,我们把数据组织成矩阵:

$$

X =

^{n d} $$

其中 是第 个样本的特征向量。从几何上看,每个样本就是 维空间中的一个点。

这个视角非常重要:机器学习的很多任务,本质上是在高维空间中做几何操作

  • 分类:找一个超平面把不同类别的点分开
  • 聚类:找到点的自然分组
  • 降维:把高维空间的点投影到低维子空间
  • 回归:找一个超平面尽可能靠近所有点

词嵌入:语义的向量化

Word2Vec 、 GloVe 等词嵌入方法把单词映射到低维向量空间,使得语义相似的词在空间中距离更近。更神奇的是,这些向量还能做"语义算术":

这说明词嵌入捕捉到了某种线性结构:性别差异被编码成了一个方向向量

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
from gensim.models import KeyedVectors

# 加载预训练的词向量
model = KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)

# 语义算术
result = model.most_similar(positive=['king', 'woman'], negative=['man'], topn=1)
print(result) # [('queen', 0.7118)]

# 计算词向量之间的余弦相似度
similarity = model.similarity('cat', 'dog')
print(f"cat 和 dog 的相似度: {similarity:.4f}")

主成分分析( PCA)

从直觉开始:找数据的"主轴"

想象桌上撒了一把回形针。如果俯视看它们,你会发现它们大致沿着某个方向排列(因为回形针是细长的)。 PCA 要做的就是找到这个"主方向"。

更正式地说:PCA 寻找数据方差最大的方向。为什么关注方差?因为方差大的方向包含更多信息,方差小的方向可能只是噪声。

数学推导

设数据已经中心化(均值为零),我们要找一个单位向量 ,使得数据投影到这个方向后的方差最大:

记协方差矩阵 ,问题变成:

用拉格朗日乘数法:

得到 。这就是特征值问题!最优的 是协方差矩阵最大特征值对应的特征向量

PCA 与 SVD 的关系

实际计算 PCA 时,通常不直接计算协方差矩阵(当特征很多时会很大),而是用 SVD 。对中心化的数据矩阵 做 SVD:

$$

X = U V^T $$

这里的 的列就是主成分方向(协方差矩阵的特征向量), 的对角元素的平方除以 就是对应的方差(特征值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 生成模拟数据:有相关性的二维数据
np.random.seed(42)
n_samples = 200
theta = np.pi / 6 # 主方向与 x 轴夹角
X = np.random.randn(n_samples, 2) @ np.array([[2, 0], [0, 0.5]])
rotation = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
X = X @ rotation

# 方法 1:使用 sklearn
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print("主成分方向:\n", pca.components_)
print("方差解释比例:", pca.explained_variance_ratio_)

# 方法 2:使用 SVD 手动实现
X_centered = X - X.mean(axis=0)
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
print("\nSVD 得到的主成分方向:\n", Vt)
print("方差:", S**2 / n_samples)

# 可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# 原始数据和主成分方向
axes[0].scatter(X[:, 0], X[:, 1], alpha=0.5)
for i, (comp, var) in enumerate(zip(pca.components_, pca.explained_variance_)):
axes[0].arrow(0, 0, comp[0]*np.sqrt(var)*2, comp[1]*np.sqrt(var)*2,
head_width=0.2, head_length=0.1, fc=['r', 'b'][i], ec=['r', 'b'][i])
axes[0].set_title('原始数据与主成分方向')
axes[0].set_xlabel('x1')
axes[0].set_ylabel('x2')
axes[0].axis('equal')

# 投影后的数据
axes[1].scatter(X_pca[:, 0], X_pca[:, 1], alpha=0.5)
axes[1].set_title('PCA 变换后的数据')
axes[1].set_xlabel('PC1')
axes[1].set_ylabel('PC2')
axes[1].axis('equal')

plt.tight_layout()
plt.savefig('pca_visualization.png', dpi=150)

PCA 的应用场景

数据可视化:把高维数据投影到 2D 或 3D,方便人眼观察。例如,把 MNIST 数据集的 784 维图片投影到 2D,看不同数字的分布。

降维与压缩:保留前 个主成分,丢弃其他,实现数据压缩。如果前 10 个主成分解释了 95% 的方差,那么用 10 维代替原来的 1000 维,信息损失很小。

去噪:假设信号在主要成分上,噪声在次要成分上。保留主要成分就能滤除噪声。

特征去相关: PCA 后的新特征是正交的(不相关的),这对某些算法(如朴素贝叶斯)有帮助。

核 PCA:处理非线性结构

标准 PCA 只能发现线性结构。如果数据分布在弯曲的流形上(比如"瑞士卷"), PCA 就无能为力了。

核 PCA 的思路是:先把数据映射到高维特征空间 ,再在特征空间做 PCA 。但我们不需要显式计算 ,只需要内积 ,这可以用核函数 计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sklearn.decomposition import KernelPCA
from sklearn.datasets import make_moons

# 生成非线性可分的数据
X, y = make_moons(n_samples=200, noise=0.1, random_state=42)

# 线性 PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

# 核 PCA(使用 RBF 核)
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=10)
X_kpca = kpca.fit_transform(X)

# 比较结果
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].scatter(X[:, 0], X[:, 1], c=y, cmap='viridis')
axes[0].set_title('原始数据')
axes[1].scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis')
axes[1].set_title('线性 PCA')
axes[2].scatter(X_kpca[:, 0], X_kpca[:, 1], c=y, cmap='viridis')
axes[2].set_title('核 PCA (RBF)')
plt.savefig('kernel_pca.png', dpi=150)

线性判别分析( LDA)

有监督的降维

PCA 是无监督的——它不知道数据的类别标签。 LDA 则是有监督的:它利用类别信息,寻找最能区分不同类别的投影方向。

直觉上, LDA 要找一个方向,使得: 1. 不同类别的中心投影后尽可能远(类间距离大) 2. 同一类别的点投影后尽可能近(类内方差小)

数学表述

设有 个类别,第 类有 个样本,均值为 ,总均值为

类内散度矩阵(衡量每个类内部的分散程度):

$$

S_W = {c=1}^{C} { c} ( - _c)( - _c)^T $$

类间散度矩阵(衡量类别中心之间的分散程度):

$$

S_B = _{c=1}^{C} n_c (_c - )(_c - )^T $$

LDA 的目标是最大化广义瑞利商:

$$

J() = {^T S_W } $$

这等价于求解广义特征值问题

二分类的 Fisher 判别

对于二分类问题, Fisher 给出了一个更直接的推导。投影后的类间距离是 ,类内方差是 。 Fisher 准则:

$$

J() = $$

最优解是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import load_iris
import numpy as np
import matplotlib.pyplot as plt

# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target

# LDA 降维到 2D
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X, y)

# PCA 降维到 2D(作为对比)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

# 可视化比较
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

for i, (X_transformed, title) in enumerate([(X_pca, 'PCA'), (X_lda, 'LDA')]):
for label in np.unique(y):
axes[i].scatter(X_transformed[y==label, 0], X_transformed[y==label, 1],
label=iris.target_names[label], alpha=0.7)
axes[i].set_title(f'{title}降维结果')
axes[i].legend()

plt.tight_layout()
plt.savefig('lda_vs_pca.png', dpi=150)

# 打印 LDA 的解释方差比
print(f"LDA 解释方差比: {lda.explained_variance_ratio_}")

PCA vs LDA:何时用哪个?

特性 PCA LDA
监督性 无监督 有监督
目标 最大化方差 最大化类间可分性
最大维度
适用场景 数据探索、压缩、去噪 分类预处理
假设 线性结构 类内高斯分布、协方差相同

一个重要限制: LDA 最多只能降到 维( 是类别数)。对于二分类, LDA 只能降到 1 维。

支持向量机与核方法

线性 SVM:最大间隔分类器

支持向量机要找一个超平面 把两类数据分开,并且使得间隔( margin)最大。

间隔是什么?直观地说,是超平面到最近数据点的距离。点 到超平面的距离是 。对于分类正确的点,

通过适当缩放 ,可以让最近的点满足 。这时间隔是

优化问题

原问题

用拉格朗日乘数法,引入

$$

L(, b, ) = ||^2 - _{i=1}^{n} _i $$

对偶问题(对 求极值后):

注意对偶问题只涉及数据点之间的内积 。这是核方法的关键!

核技巧

如果数据线性不可分,一个办法是把数据映射到更高维的空间。例如,对于二维数据 ,映射到:

原来的圆形边界在新空间中可能变成线性的。

问题是:高维映射计算代价很大。核技巧的妙处在于,我们只需要计算内积 ,而这可以用一个简单的函数 直接算出来,不需要显式计算

常用核函数

  • 线性核
  • 多项式核
  • RBF 核(高斯核)
  • sigmoid 核RBF 核对应的特征空间是无限维的!但计算内积只需要 时间。

核矩阵与正定性

给定 个数据点,核矩阵( Gram 矩阵)定义为:

$$

K_{ij} = K(_i, _j) $$

一个函数是合法的核函数,当且仅当对任意数据集,核矩阵都是半正定的(所有特征值 )。这叫做 Mercer 条件

直觉上,核矩阵的第 元素衡量样本 的"相似度"。半正定性保证了这个相似度是自洽的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from sklearn.svm import SVC
from sklearn.datasets import make_circles
import numpy as np
import matplotlib.pyplot as plt

# 生成环形数据(线性不可分)
X, y = make_circles(n_samples=200, noise=0.1, factor=0.3, random_state=42)

# 线性 SVM
svm_linear = SVC(kernel='linear')
svm_linear.fit(X, y)

# RBF 核 SVM
svm_rbf = SVC(kernel='rbf', gamma=2)
svm_rbf.fit(X, y)

# 可视化决策边界
def plot_decision_boundary(ax, clf, X, y, title):
xx, yy = np.meshgrid(np.linspace(X[:, 0].min()-0.5, X[:, 0].max()+0.5, 200),
np.linspace(X[:, 1].min()-0.5, X[:, 1].max()+0.5, 200))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
ax.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='black')
ax.set_title(title)
# 标记支持向量
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
s=100, facecolors='none', edgecolors='green', linewidths=2)

fig, axes = plt.subplots(1, 2, figsize=(12, 5))
plot_decision_boundary(axes[0], svm_linear, X, y, '线性 SVM')
plot_decision_boundary(axes[1], svm_rbf, X, y, 'RBF 核 SVM')
plt.tight_layout()
plt.savefig('svm_kernels.png', dpi=150)

print(f"线性 SVM 准确率: {svm_linear.score(X, y):.2f}")
print(f"RBF 核 SVM 准确率: {svm_rbf.score(X, y):.2f}")
print(f"支持向量数量: {len(svm_rbf.support_vectors_)}")

矩阵分解与推荐系统

Netflix 问题

推荐系统的经典问题:有一个用户-电影评分矩阵 个用户,部电影),但大部分元素是缺失的(用户不可能看过所有电影)。目标是预测缺失的评分,从而向用户推荐他可能喜欢的电影。

低秩假设

核心假设:用户的偏好和电影的特性都可以用少数几个"隐因子"来描述。比如,隐因子可能对应"动作程度"、"浪漫程度"、"是否有明星"等。

数学上,假设 ,其中 (用户因子矩阵),(物品因子矩阵),

用户 对电影 的预测评分是

优化目标

只在观测到的评分上优化(带正则化):

其中 是观测到评分的位置集合, 是正则化系数。

交替最小二乘( ALS)

这个问题关于 联合是非凸的,但固定一个优化另一个时是凸的(二次规划)。 ALS 交替进行:

  1. 固定 ,更新 :每个 独立求解一个岭回归
  2. 固定 ,更新 :每个 独立求解一个岭回归
  3. 重复直到收敛
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import numpy as np
from scipy.sparse import random as sparse_random
from scipy.sparse.linalg import svds

# 模拟一个评分矩阵
np.random.seed(42)
n_users, n_items, k = 100, 50, 5

# 真实的低秩结构
U_true = np.random.randn(n_users, k)
V_true = np.random.randn(n_items, k)
R_true = U_true @ V_true.T

# 只观测 20%的评分,并加噪声
mask = np.random.rand(n_users, n_items) < 0.2
R_observed = np.where(mask, R_true + 0.1 * np.random.randn(n_users, n_items), 0)

# ALS 算法
def als(R_observed, mask, k, n_iter=50, lambda_reg=0.1):
n_users, n_items = R_observed.shape
U = np.random.randn(n_users, k) * 0.1
V = np.random.randn(n_items, k) * 0.1

for iteration in range(n_iter):
# 更新 U
for i in range(n_users):
items_rated = np.where(mask[i, :])[0]
if len(items_rated) > 0:
V_sub = V[items_rated, :]
r_sub = R_observed[i, items_rated]
U[i, :] = np.linalg.solve(V_sub.T @ V_sub + lambda_reg * np.eye(k),
V_sub.T @ r_sub)

# 更新 V
for j in range(n_items):
users_rated = np.where(mask[:, j])[0]
if len(users_rated) > 0:
U_sub = U[users_rated, :]
r_sub = R_observed[users_rated, j]
V[j, :] = np.linalg.solve(U_sub.T @ U_sub + lambda_reg * np.eye(k),
U_sub.T @ r_sub)

# 计算损失
if iteration % 10 == 0:
R_pred = U @ V.T
loss = np.sum((R_observed[mask] - R_pred[mask])**2)
print(f"Iteration {iteration}, Loss: {loss:.4f}")

return U, V

U_learned, V_learned = als(R_observed, mask, k)

# 预测缺失评分
R_pred = U_learned @ V_learned.T
rmse = np.sqrt(np.mean((R_true[~mask] - R_pred[~mask])**2))
print(f"\n 在未观测位置的 RMSE: {rmse:.4f}")

非负矩阵分解( NMF)

有时候我们希望分解结果是非负的,这样更容易解释。比如在文档-词矩阵分解中,非负的因子可以解释为"主题"。

$$

V WH, W , H $$

NMF 倾向于产生稀疏的、部分基的表示,而不是 SVD 那种全局的、可能有正有负的表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sklearn.decomposition import NMF
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer

# 加载新闻数据
categories = ['rec.sport.baseball', 'sci.med', 'comp.graphics']
newsgroups = fetch_20newsgroups(subset='train', categories=categories)

# TF-IDF 向量化
vectorizer = TfidfVectorizer(max_features=1000, stop_words='english')
X = vectorizer.fit_transform(newsgroups.data)

# NMF 分解
n_topics = 5
nmf = NMF(n_components=n_topics, random_state=42)
W = nmf.fit_transform(X) # 文档-主题矩阵
H = nmf.components_ # 主题-词矩阵

# 显示每个主题的关键词
feature_names = vectorizer.get_feature_names_out()
for topic_idx, topic in enumerate(H):
top_words = [feature_names[i] for i in topic.argsort()[:-10:-1]]
print(f"Topic {topic_idx}: {', '.join(top_words)}")

线性回归的矩阵形式

从标量到矩阵

大家熟悉的简单线性回归 ,推广到多变量就是:

$$

y = _0 + _1 x_1 + _2 x_2 + + _d x_d = ^T $$

把所有 个样本堆起来,用矩阵形式写成:

其中 是设计矩阵(包含截距列), 是响应向量, 是参数向量。

最小二乘解的几何意义

最小二乘的目标是最小化残差平方和:

几何上, 列空间中的向量。我们要找 列空间中离 最近的点。答案是: 列空间上的投影!

最优的 满足残差 的列空间正交:

$$

X^T( - X ) = 0 $$

解得正规方程

数值稳定性问题

直接计算 有两个问题:

  1. 当 $ p > nX^TX$ 不可逆
  2. 当特征高度相关时, 病态(条件数很大),数值不稳定

更好的方法是用 QR 分解:,则 。因为 是上三角的,回代求解很稳定。

岭回归:解决病态问题

岭回归在损失函数中加入 惩罚:

解为:

加上 后,矩阵一定可逆,而且条件数变好了。从 SVD 角度看,如果 ,则:

小的奇异值对应的分量被"压缩"了,减少了过拟合。

LASSO:稀疏特征选择

LASSO 用 惩罚代替

惩罚的神奇之处在于它会产生稀疏解:很多 会精确地等于零。这相当于自动做了特征选择。

为什么?几何上, 约束区域是一个"菱形"(超立方体),而损失函数的等高线是椭圆。它们最可能在菱形的顶点相切——而顶点处某些坐标为零。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# 生成数据:只有前 3 个特征是有用的
np.random.seed(42)
n, p = 100, 20
X = np.random.randn(n, p)
beta_true = np.zeros(p)
beta_true[:3] = [3, -2, 1.5] # 只有前 3 个特征有效
y = X @ beta_true + 0.5 * np.random.randn(n)

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 普通最小二乘
ols = LinearRegression(fit_intercept=False)
ols.fit(X_scaled, y)

# 岭回归
ridge = Ridge(alpha=1.0, fit_intercept=False)
ridge.fit(X_scaled, y)

# LASSO
lasso = Lasso(alpha=0.1, fit_intercept=False)
lasso.fit(X_scaled, y)

# 比较系数
fig, ax = plt.subplots(figsize=(12, 5))
x_pos = np.arange(p)
width = 0.25

ax.bar(x_pos - width, beta_true, width, label='真实系数', alpha=0.7)
ax.bar(x_pos, ols.coef_, width, label='OLS', alpha=0.7)
ax.bar(x_pos + width, lasso.coef_, width, label='LASSO', alpha=0.7)
ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
ax.set_xlabel('特征索引')
ax.set_ylabel('系数值')
ax.set_title('不同回归方法的系数比较')
ax.legend()
plt.tight_layout()
plt.savefig('regression_comparison.png', dpi=150)

print(f"OLS 非零系数数量: {np.sum(np.abs(ols.coef_) > 0.01)}")
print(f"LASSO 非零系数数量: {np.sum(np.abs(lasso.coef_) > 0.01)}")

神经网络中的线性层

全连接层就是矩阵乘法

神经网络中的全连接层( Dense 层)本质上就是一个仿射变换:

其中 是权重矩阵, 是偏置向量, 是激活函数(如 ReLU 、 sigmoid)。

如果没有激活函数,多层全连接网络等价于单层:。激活函数引入了非线性,使得网络能够逼近任意复杂的函数。

批量处理:矩阵乘矩阵

实际训练时,我们一次处理一批( batch)数据。设输入是 个样本,每个 维),则:

$$

H = (XW^T + ^T) $$

这是一个矩阵乘法,可以高效地在 GPU 上并行计算。

反向传播的线性代数

反向传播计算梯度时,大量用到矩阵微分。设损失函数 ,对于线性层

批量情况下:

这就是为什么深度学习框架底层是大量的矩阵运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import numpy as np

class LinearLayer:
"""手写一个线性层,展示矩阵运算"""
def __init__(self, in_features, out_features):
# Xavier 初始化
self.W = np.random.randn(out_features, in_features) * np.sqrt(2.0 / in_features)
self.b = np.zeros(out_features)
self.x = None # 缓存输入,用于反向传播

def forward(self, x):
"""前向传播: h = Wx + b"""
self.x = x
return x @ self.W.T + self.b

def backward(self, grad_output, learning_rate=0.01):
"""反向传播"""
# 计算梯度
grad_W = grad_output.T @ self.x
grad_b = grad_output.sum(axis=0)
grad_input = grad_output @ self.W

# 更新参数
self.W -= learning_rate * grad_W
self.b -= learning_rate * grad_b

return grad_input

class ReLU:
def forward(self, x):
self.mask = (x > 0)
return np.maximum(0, x)

def backward(self, grad_output):
return grad_output * self.mask

# 简单的两层网络
np.random.seed(42)
layer1 = LinearLayer(10, 20)
relu = ReLU()
layer2 = LinearLayer(20, 1)

# 生成数据
X = np.random.randn(100, 10)
y = np.random.randn(100, 1)

# 一次前向+反向
h1 = layer1.forward(X)
h1_relu = relu.forward(h1)
output = layer2.forward(h1_relu)

# 均方误差的梯度
grad_output = 2 * (output - y) / len(y)

# 反向传播
grad_h1_relu = layer2.backward(grad_output)
grad_h1 = relu.backward(grad_h1_relu)
grad_input = layer1.backward(grad_h1)

print(f"输出形状: {output.shape}")
print(f"损失: {np.mean((output - y)**2):.4f}")

注意力机制中的线性代数

Transformer 中的自注意力机制本质上也是线性代数操作:

其中 都是线性变换。 计算所有位置之间的相似度,是一个 的矩阵( 是序列长度)。

优化算法的线性代数基础

梯度与 Hessian 矩阵

对于多变量函数 ,梯度是一阶导数组成的向量:

Hessian 矩阵是二阶导数组成的矩阵:

$$

H_{ij} = $$

在极值点附近,函数可以用二次函数近似:

$$

f() f(_0) + f(_0)^T( - _0) + ( - _0)^T H ( - _0) $$

梯度下降的收敛性

梯度下降 的收敛速度取决于 Hessian 的条件数

条件数越大,收敛越慢。直觉上,条件数大意味着函数在某些方向陡峭、在其他方向平缓,梯度下降会"之字形"前进。

预处理与 Adam

预处理的思想是:用一个矩阵 变换坐标,使得新坐标系下条件数更好。牛顿法用 ,这样一步就能到达极值点(对于二次函数)。

Adam 优化器维护梯度的一阶矩(动量)和二阶矩(用于自适应学习率):

$$

m_t = 1 m{t-1} + (1-_1) g_t

v_t = 2 v{t-1} + (1-2) g_t^2 {t+1} = _t - m_t $$

这相当于对每个参数自适应地调整学习率,缓解了条件数大带来的问题。

练习题

概念理解题

第 1 题:解释为什么 PCA 找到的主成分方向是正交的。提示:思考协方差矩阵的性质。

第 2 题: LDA 最多能把数据降到 维( 是类别数)。为什么有这个限制?提示:思考类间散度矩阵 的秩。

第 3 题:核函数 对应什么样的特征映射

第 4 题:为什么 惩罚会产生稀疏解,而 惩罚不会?用几何直觉解释。

计算题

第 5 题:设数据矩阵 (已中心化)。

  1. 计算协方差矩阵 (b) 求 的特征值和特征向量
  2. 数据投影到第一主成分后的坐标是什么?

第 6 题:考虑二分类问题,两类的均值分别是 ,类内散度矩阵 。求 LDA 的最优投影方向。

第 7 题:证明岭回归解 等价于在增广数据集上做普通最小二乘,其中增广数据集是:

编程题

第 8 题:用 NumPy 从零实现 PCA,不使用 sklearn 。在 MNIST 数据集上测试,可视化前两个主成分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 你的代码框架
def my_pca(X, n_components):
"""
X: (n_samples, n_features) 数据矩阵
n_components: 保留的主成分数

返回:
X_transformed: (n_samples, n_components) 变换后的数据
components: (n_components, n_features) 主成分方向
explained_variance_ratio: 各主成分解释的方差比例
"""
# 1. 中心化
# 2. 计算协方差矩阵或使用 SVD
# 3. 求特征值/特征向量
# 4. 选取前 k 个主成分
# 5. 投影数据
pass

第 9 题:实现一个简单的矩阵分解推荐系统。生成一个 的模拟评分矩阵(低秩加噪声),随机遮盖 80% 的评分,然后用 ALS 算法恢复。计算在测试集上的 RMSE 。

第 10 题:用 PyTorch 实现一个简单的两层神经网络(不使用 nn.Linear,手动写矩阵乘法),在 MNIST 上训练并达到 95% 以上准确率。

证明题

第 11 题:证明核矩阵 是半正定的,当且仅当存在特征映射 使得

第 12 题:证明 PCA 的主成分方向与 SVD 的右奇异向量相同(对于中心化的数据)。

第 13 题:证明对于线性回归 ,最小二乘估计 最佳线性无偏估计( BLUE),即在所有线性无偏估计中方差最小( Gauss-Markov 定理)。

应用题

第 14 题:你有一个包含 10000 个用户、 1000 部电影的评分数据集,每个用户平均评价了 50 部电影。设计一个推荐系统:

  1. 选择合适的隐因子数 (b) 讨论如何处理新用户(冷启动问题)
  2. 如何评估推荐效果?

第 15 题:在图像分类任务中,你有 50000 张 的彩色图片(共 3072 维)。讨论:

  1. 是否应该先做 PCA 降维?降到多少维合适?
  2. PCA 降维与卷积神经网络的特征提取有何本质区别?

本章总结

本章我们深入探讨了线性代数在机器学习中的核心应用:

向量表示:把数据转化为向量是机器学习的起点。词嵌入、图像展平、特征工程都是向量化的过程。

PCA:通过协方差矩阵的特征分解,找到数据方差最大的方向。实际计算时用 SVD 更稳定。核 PCA 可以处理非线性结构。

LDA:有监督的降维方法,最大化类间距离与类内距离的比值。最多降到 维。

SVM 与核方法:核技巧让我们能在高维(甚至无限维)特征空间中计算内积,而不需要显式构造特征。核矩阵必须是半正定的。

矩阵分解推荐:假设评分矩阵是低秩的,可以分解成用户因子和物品因子的乘积。 ALS 是常用的优化算法。

线性回归:矩阵形式揭示了最小二乘解的几何意义(投影)。岭回归和 LASSO 通过正则化处理病态问题和特征选择。

神经网络:全连接层就是矩阵乘法加激活函数。反向传播本质上是矩阵微分。注意力机制也是大量的矩阵运算。

理解这些线性代数基础,你就能更深刻地理解机器学习算法的工作原理,而不只是当黑箱使用。

参考资料

  1. Strang, G. Linear Algebra and Learning from Data. Wellesley-Cambridge Press, 2019.
  2. Murphy, K. P. Machine Learning: A Probabilistic Perspective. MIT Press, 2012.
  3. Goodfellow, I., Bengio, Y., & Courville, A. Deep Learning. MIT Press, 2016.
  4. Bishop, C. M. Pattern Recognition and Machine Learning. Springer, 2006.
  5. Golub, G. H., & Van Loan, C. F. Matrix Computations. Johns Hopkins University Press, 2013.

本文是《线性代数的本质与应用》系列的第十五章。

  • 本文标题:线性代数(十五)机器学习中的线性代数
  • 本文作者:Chen Kai
  • 创建时间:2019-03-18 09:45:00
  • 本文链接:https://www.chenk.top/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0%EF%BC%88%E5%8D%81%E4%BA%94%EF%BC%89%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论