线性代数在AI中的应用:数据表示、降维
一句话概述
线性代数是AI的"底层语言"——无论是一张图片、一段文本还是一个用户画像,在AI眼中统统都是向量与矩阵;而降维则是AI处理海量数据时的"压缩术",用更少的维度保留最核心的信息,让模型跑得快、学得好。从数据表示到特征提取,从词向量嵌入到PCA降维,线性代数贯穿了AI从数据输入到模型输出的每一个环节。
教学与演示
一、数据表示——万物皆向量
是什么(定义):数据表示(Data Representation)是指将现实世界中的各种类型的数据——图像、文本、音频、用户行为等——转换为计算机可以处理的数值形式。在线性代数框架下,最基本的数据表示单位是向量(一维数组)和矩阵(二维数组),更高维的数据则用张量(Tensor)来表示。
为什么(原理):AI模型本质上是数学函数,数学函数只能处理数值。因此,所有输入AI的数据都必须先数值化。线性代数提供了向量、矩阵、张量这套优雅的数学工具,既能统一表示各种数据,又能利用矩阵运算的高效性进行批量处理。GPU之所以能加速AI训练,正是因为矩阵运算是高度可并行的。
怎么做(实现):
import numpy as np
# ==================== 图像的矩阵表示 ====================
# 一张 3x3 的灰度图像,每个像素值在 0~255 之间
# 0 表示黑色,255 表示白色,中间值表示灰色
gray_image = np.array([
[ 0, 128, 255], # 第一行像素:黑、灰、白
[ 64, 192, 32], # 第二行像素:深灰、浅灰、深色
[200, 100, 150] # 第三行像素:浅灰、中灰、灰
], dtype=np.float32) # 使用 float32 类型,AI 中常用
print("灰度图像矩阵形状:", gray_image.shape) # (3, 3) 表示 3行3列
print("灰度图像矩阵:\n", gray_image)
# ==================== 彩色图像的张量表示 ====================
# 彩色图像有 RGB 三个通道,所以是三维张量
# 形状为 (高, 宽, 3),3 代表 R/G/B 三个颜色通道
rgb_image = np.array([
[[255, 0, 0], [0, 255, 0], [0, 0, 255]], # 第一行:红、绿、蓝
[[255, 255, 0], [255, 0, 255], [0, 255, 255]], # 第二行:黄、品红、青
[[128, 128, 128], [255, 255, 255], [0, 0, 0]] # 第三行:灰、白、黑
], dtype=np.float32)
print("\n彩色图像张量形状:", rgb_image.shape) # (3, 3, 3) 三维张量
print("第一个像素的RGB值:", rgb_image[0, 0]) # [255, 0, 0] 红色像素
# ==================== 文本的向量表示 ====================
# 最简单的方式:词袋模型(Bag of Words)
# 假设词汇表只有 5 个词:["我", "爱", "AI", "学习", "编程"]
vocabulary = ["我", "爱", "AI", "学习", "编程"]
# 句子 "我爱AI" 的向量表示
sentence1 = "我爱AI"
# 统计每个词出现的次数,生成向量
vec1 = np.array([1, 1, 1, 0, 0], dtype=np.float32) # 我1次、爱1次、AI1次、学习0次、编程0次
# 句子 "我爱学习编程" 的向量表示
sentence2 = "我爱学习编程"
vec2 = np.array([1, 1, 0, 1, 1], dtype=np.float32) # 我1次、爱1次、AI0次、学习1次、编程1次
print("\n句子1向量:", vec1)
print("句子2向量:", vec2)
# ==================== 用户画像的特征向量 ====================
# 一个用户可以用多个特征来描述
# 特征:[年龄归一化, 月消费额归一化, 登录频率归一化, 购买次数归一化]
user_a = np.array([0.25, 0.80, 0.60, 0.45], dtype=np.float32) # 年轻、高消费、中等活跃
user_b = np.array([0.70, 0.30, 0.90, 0.85], dtype=np.float32) # 年长、低消费、高活跃
print("\n用户A特征向量:", user_a)
print("用户B特征向量:", user_b)
# ==================== 计算两个用户的相似度(余弦相似度) ====================
# 余弦相似度衡量两个向量方向的相似程度,值域 [-1, 1]
# 1 表示完全相同方向,0 表示正交(无关),-1 表示完全相反
def cosine_similarity(a, b):
"""计算两个向量的余弦相似度"""
# np.dot 计算点积,np.linalg.norm 计算向量的模(长度)
dot_product = np.dot(a, b) # 点积:对应位置相乘再求和
norm_a = np.linalg.norm(a) # 向量 a 的长度
norm_b = np.linalg.norm(b) # 向量 b 的长度
similarity = dot_product / (norm_a * norm_b) # 余弦相似度公式
return similarity
sim = cosine_similarity(user_a, user_b)
print(f"\n用户A与B的余弦相似度: {sim:.4f}")
print("(值越接近1,两个用户越相似)")
什么用(应用):数据表示是AI的第一步,决定了模型能"看到"什么信息。在推荐系统中,用户和商品都用特征向量表示,通过计算向量相似度来做推荐;在图像识别中,图片被表示为像素矩阵送入卷积神经网络;在自然语言处理中,文本被表示为词向量序列送入Transformer。好的数据表示能让模型事半功倍。
哪些坑(缺点):简单的数据表示方法(如词袋模型)会丢失大量信息——词序、语义、上下文全部丢失;高分辨率图像直接展平为向量会导致维度爆炸(一张 1000×1000 的彩色图片展平后是 300万维向量);不同类型的数据需要不同的表示策略,没有"万能"的表示方法。
二、词向量与嵌入——语言的数学化
是什么(定义):词向量(Word Embedding)是一种将词语映射为低维稠密实数向量的技术。与词袋模型的高维稀疏表示不同,词向量将每个词映射为例如 100维 或 300维 的稠密向量,使得语义相近的词在向量空间中距离也相近。嵌入(Embedding)是更广义的概念,指将任何离散对象映射到连续向量空间的过程。
为什么(原理):词向量的核心思想来自分布式假设(Distributional Hypothesis):上下文相似的词,语义也相似。Word2Vec 等算法通过让神经网络预测上下文词来学习词向量,训练过程中,语义相近的词自然获得了相近的向量表示。这种稠密低维表示相比词袋模型有三大优势:(1) 维度低,计算高效;(2) 捕获语义关系;(3) 泛化能力强。
怎么做(实现):
import numpy as np
# ==================== 模拟词向量 ====================
# 实际中词向量由 Word2Vec/GloVe 等算法在大语料上训练得到
# 这里我们手动构造一个简化的 3 维词向量空间来理解概念
# 三个维度分别代表:[皇权程度, 性别男性度, 年龄成年度]
word_vectors = {
"国王": np.array([0.95, 0.90, 0.85]), # 高皇权、男性、成年
"女王": np.array([0.95, 0.10, 0.85]), # 高皇权、女性、成年
"男人": np.array([0.10, 0.90, 0.80]), # 低皇权、男性、成年
"女人": np.array([0.10, 0.10, 0.80]), # 低皇权、女性、成年
"王子": np.array([0.60, 0.85, 0.30]), # 中皇权、男性、年轻
"公主": np.array([0.60, 0.15, 0.30]), # 中皇权、女性、年轻
"猫": np.array([0.01, 0.50, 0.20]), # 无皇权、性别中性、年轻
"苹果": np.array([0.00, 0.00, 0.10]), # 无皇权、无性别、无年龄
}
# ==================== 词向量的神奇运算:语义加减法 ====================
# 经典例子:国王 - 男人 + 女人 ≈ 女王
# 这说明词向量空间中存在"方向"的概念
# "男性→女性"是一个方向,"国王→女王"也沿着这个方向
result = word_vectors["国王"] - word_vectors["男人"] + word_vectors["女人"]
print("国王 - 男人 + 女人 =", result.round(2))
# 计算结果与"女王"的相似度
def cosine_similarity(a, b):
"""计算余弦相似度"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 找出与计算结果最相似的词
best_word = None
best_sim = -1
for word, vec in word_vectors.items():
sim = cosine_similarity(result, vec)
if sim > best_sim:
best_sim = sim
best_word = word
print(f"最接近的词是: '{best_word}',相似度: {best_sim:.4f}")
# ==================== 词语相似度矩阵 ====================
# 计算所有词对之间的余弦相似度
words = list(word_vectors.keys())
n = len(words)
sim_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
sim_matrix[i][j] = cosine_similarity(word_vectors[words[i]], word_vectors[words[j]])
print("\n词语相似度矩阵(部分):")
print(f"{'':>6}", end="")
for w in words[:5]:
print(f"{w:>8}", end="")
print()
for i in range(5):
print(f"{words[i]:>6}", end="")
for j in range(5):
print(f"{sim_matrix[i][j]:>8.2f}", end="")
print()
# ==================== 嵌入在AI中的实际应用 ====================
# 1. 文本分类:将整段文本的词向量平均,得到文本向量,送入分类器
# 2. 语义搜索:将查询和文档都转为向量,用余弦相似度匹配
# 3. 推荐系统:将用户行为和物品都嵌入到同一向量空间
# 示例:用词向量平均得到句子向量
def sentence_vector(sentence_words, word_vec_dict):
"""将句子中所有词的向量取平均,得到句子向量"""
vectors = []
for w in sentence_words:
if w in word_vec_dict:
vectors.append(word_vec_dict[w])
if len(vectors) == 0:
return np.zeros(3) # 如果没有匹配的词,返回零向量
return np.mean(vectors, axis=0) # 取平均值
# 两个句子的语义相似度
sentence1_words = ["国王", "女王"]
sentence2_words = ["王子", "公主"]
sv1 = sentence_vector(sentence1_words, word_vectors)
sv2 = sentence_vector(sentence2_words, word_vectors)
sim = cosine_similarity(sv1, sv2)
print(f"\n'国王女王' 与 '王子公主' 的语义相似度: {sim:.4f}")
print("(都是皇室成员,语义相近,相似度高)")
什么用(应用):词向量是现代NLP的基石。在机器翻译中,不同语言的词向量空间可以对齐,实现跨语言翻译;在搜索引擎中,查询词和文档都转为向量,通过相似度匹配实现语义搜索(而非关键词匹配);在推荐系统中,用户和物品都可以嵌入到同一向量空间,实现个性化推荐。大语言模型(如GPT)的输入层本质上也是一个嵌入层。
哪些坑(缺点):传统词向量(如Word2Vec)是一词一向量,无法区分多义词(如"苹果"可以是水果也可以是公司);词向量的质量高度依赖训练语料的规模和质量;维度选择需要权衡——太低丢失信息,太高增加计算量;词向量训练是静态的,不会根据上下文动态变化(BERT等模型解决了这个问题)。
三、主成分分析(PCA)——最经典的降维
是什么(定义):主成分分析(Principal Component Analysis,PCA)是一种无监督的线性降维方法。它通过正交变换,将可能相关的高维变量转换为线性不相关的低维变量,这些新的变量称为主成分。主成分按方差从大到小排列——第一个主成分方向是数据方差最大的方向,第二个主成分方向是与第一个正交且方差次大的方向,以此类推。
为什么(原理):PCA的数学原理基于协方差矩阵的特征值分解。数据的协方差矩阵反映了各维度之间的相关性,对协方差矩阵做特征值分解,特征值最大的方向就是方差最大的方向(即信息最丰富的方向)。选择前k个最大特征值对应的特征向量作为新坐标轴,就完成了降维。降维后的数据保留了原始数据中尽可能多的方差(信息)。
怎么做(实现):
import numpy as np
# ==================== PCA 的完整实现步骤 ====================
# 第一步:生成模拟数据(2维数据,方便可视化理解)
np.random.seed(42) # 设置随机种子,确保结果可复现
# 生成一组沿着某个方向分布的 2D 数据点
n_samples = 100 # 样本数量
# 原始数据沿 45 度方向分布,有一定的散布
mean = [0, 0] # 均值
# 协方差矩阵:控制数据的分布方向和散布程度
# [[3, 2], [2, 3]] 表示 x 和 y 正相关,数据沿 45 度方向拉伸
cov = [[3, 2], [2, 3]]
X = np.random.multivariate_normal(mean, cov, n_samples) # 生成多元正态分布数据
print("原始数据形状:", X.shape) # (100, 2) 100个样本,2个特征
print("原始数据前5个样本:\n", X[:5].round(2))
# 第二步:数据中心化(减去均值)
# PCA 要求先对每个特征减去其均值,使数据中心在原点
X_mean = np.mean(X, axis=0) # 计算每个特征的均值,axis=0 按列求均值
X_centered = X - X_mean # 每个样本减去均值
print("\n数据中心化后均值:", X_centered.mean(axis=0).round(10)) # 应该接近 [0, 0]
# 第三步:计算协方差矩阵
# 协方差矩阵衡量各特征之间的线性相关性
# 对角线元素是各特征的方差,非对角线元素是特征间的协方差
cov_matrix = np.cov(X_centered, rowvar=False) # rowvar=False 表示每列是一个特征
print("\n协方差矩阵:\n", cov_matrix.round(4))
# 第四步:对协方差矩阵做特征值分解
# 特征值表示该方向上的方差大小(信息量)
# 特征向量表示该主成分的方向
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) # eigh 适用于对称矩阵
# 按特征值从大到小排序
idx = np.argsort(eigenvalues)[::-1] # 降序排列的索引
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
print("\n特征值(从大到小):", eigenvalues.round(4))
print("特征向量(每列是一个主成分方向):\n", eigenvectors.round(4))
# 第五步:计算方差解释比例
# 每个主成分解释了多少比例的总方差
explained_variance_ratio = eigenvalues / np.sum(eigenvalues)
print("\n各主成分的方差解释比例:", explained_variance_ratio.round(4))
print(f"第一主成分解释了 {explained_variance_ratio[0]*100:.1f}% 的方差")
print(f"第二主成分解释了 {explained_variance_ratio[1]*100:.1f}% 的方差")
# 第六步:选择主成分并投影(降维)
# 如果我们降到 1 维,只保留第一主成分
n_components = 1 # 目标维度
W = eigenvectors[:, :n_components] # 取前 n_components 个特征向量组成投影矩阵
print(f"\n投影矩阵 W 形状: {W.shape}") # (2, 1)
print("投影矩阵 W:\n", W.round(4))
# 将原始数据投影到低维空间
X_pca = X_centered @ W # 矩阵乘法完成投影
print(f"\n降维后数据形状: {X_pca.shape}") # (100, 1)
print("降维后数据前5个样本:\n", X_pca[:5].round(4))
# 第七步:重建(从低维回到高维,会有信息损失)
X_reconstructed = X_pca @ W.T + X_mean # 反投影并加回均值
reconstruction_error = np.mean((X - X_reconstructed) ** 2) # 均方重建误差
print(f"\n重建均方误差: {reconstruction_error:.4f}")
print("(降到1维时,丢失了第二主成分的信息,所以有重建误差)")
什么用(应用):PCA在AI中有广泛应用:(1) 数据可视化——将高维数据降到2D/3D以便人类观察;(2) 噪声过滤——丢弃方差小的主成分往往也去掉了噪声;(3) 特征压缩——在训练模型前先降维,减少计算量和过拟合风险;(4) 人脸识别——Eigenface方法用PCA提取人脸的主要变化方向;(5) 金融分析——用PCA提取股票市场的主要波动模式。
哪些坑(缺点):PCA只能捕获线性关系,对于非线性结构的数据(如瑞士卷形状),PCA降维效果很差;主成分的可解释性可能较差——新坐标轴是原始特征的线性组合,往往难以给出直观的物理含义;PCA对数据的尺度敏感,使用前必须标准化;降维会丢失信息,选择保留多少主成分需要权衡。
四、降维的直觉——从高维到低维的投影
是什么(定义):降维(Dimensionality Reduction)是将高维数据映射到低维空间的过程,同时尽可能保留原始数据中的重要信息。从几何角度看,降维就是"投影"——想象用手电筒照射一个三维物体,在墙上投下的二维影子就是降维的结果。好的降维方法就像选了一个最佳的照射角度,让影子尽可能保留原物的关键特征。
为什么(原理):降维的必要性来自"维度灾难"(Curse of Dimensionality):随着维度增加,数据在高维空间中变得越来越稀疏,模型需要指数级更多的数据才能学到有效模式。同时,高维数据中往往存在大量冗余——许多特征之间高度相关,真正独立的"信息维度"远少于特征数量。降维通过去除冗余和噪声,让模型聚焦于核心信息。
怎么做(实现):
import numpy as np
# ==================== 维度灾难的直观演示 ====================
# 在高维空间中,随机点之间的距离趋于相同,"近邻"概念失效
def avg_pairwise_distance(n_dims, n_points=1000, n_trials=5):
"""计算 n_dims 维空间中随机点对的平均距离"""
distances = []
for _ in range(n_trials):
# 在 [0,1]^n_dims 超立方体中随机生成点
points = np.random.rand(n_points, n_dims)
# 计算所有点对之间的欧氏距离(采样部分点对以节省计算)
for i in range(min(100, n_points)):
for j in range(i+1, min(100, n_points)):
dist = np.linalg.norm(points[i] - points[j]) # 欧氏距离
distances.append(dist)
return np.mean(distances), np.std(distances)
# 不同维度下的平均距离和标准差
print("维度 | 平均距离 | 距离标准差 | 标准差/均值")
print("-" * 55)
for dim in [2, 5, 10, 50, 100]:
avg, std = avg_pairwise_distance(dim, n_points=500, n_trials=3)
ratio = std / avg if avg > 0 else 0
print(f"{dim:>4} | {avg:>8.4f} | {std:>9.4f} | {ratio:.4f}")
print("\n观察:随着维度增加,标准差/均值比越来越小")
print("这意味着高维空间中所有点对距离趋于相同,'近邻'概念失效!")
# ==================== 降维保留信息的衡量 ====================
# 用"重建误差"衡量降维损失了多少信息
np.random.seed(42)
# 生成 50 维数据,但只有 5 个维度真正有信息
n_samples = 200
n_features = 50
n_informative = 5 # 真正有信息的维度数
# 构造数据:前5维有较大方差,后45维只有微小噪声
informative_part = np.random.randn(n_samples, n_informative) * 10 # 信号强
noise_part = np.random.randn(n_samples, n_features - n_informative) * 0.1 # 噪声弱
X_high = np.hstack([informative_part, noise_part]) # 拼接成 50 维数据
# 中心化
X_centered = X_high - X_high.mean(axis=0)
# 计算协方差矩阵和特征值
cov_mat = np.cov(X_centered, rowvar=False)
eigenvalues, eigenvectors = np.linalg.eigh(cov_mat)
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
# 计算累积方差解释比例
cumulative_ratio = np.cumsum(eigenvalues) / np.sum(eigenvalues)
print("\n\n累积方差解释比例(前10个主成分):")
for k in range(10):
print(f" 前 {k+1} 个主成分: {cumulative_ratio[k]*100:.2f}%")
# 只需5个主成分就能解释几乎全部方差
print(f"\n前5个主成分解释了 {cumulative_ratio[4]*100:.2f}% 的方差")
print("这说明50维数据中只有5个'有效维度',降维到5维几乎不损失信息")
# ==================== 不同降维维度的重建误差 ====================
print("\n不同降维维度的重建误差:")
print("目标维度 | 重建误差(MSE) | 方差解释比例")
print("-" * 50)
for k in [1, 3, 5, 10, 20, 50]:
W_k = eigenvectors[:, idx][:, :k] # 取前 k 个特征向量
X_proj = X_centered @ W_k # 投影到 k 维
X_recon = X_proj @ W_k.T + X_high.mean(axis=0) # 重建
mse = np.mean((X_high - X_recon) ** 2) # 均方误差
var_ratio = cumulative_ratio[k-1]
print(f" {k:>4} 维 | {mse:>12.4f} | {var_ratio*100:.2f}%")
什么用(应用):降维的直觉帮助我们在实际问题中做出关键决策:选择多少个主成分?是否需要降维?用线性降维还是非线性降维?理解"投影"和"信息保留"的直觉,能帮助我们判断降维是否合理,以及降维后模型性能下降是否在可接受范围内。在图像压缩、信号处理、基因数据分析等领域,降维都是核心步骤。
哪些坑(缺点):降维并非总是有益——如果原始特征都是独立且重要的,降维反而会丢失关键信息;过度降维(保留太少维度)会导致模型欠拟合;不同降维方法适用于不同数据结构,选错方法可能得到误导性结果;降维后的特征往往难以解释,影响模型的可解释性。
五、线性代数在神经网络中的角色
是什么(定义):神经网络中的几乎每一次计算都可以用线性代数来描述。一个全连接层的前向传播就是矩阵乘法加上偏置:y = Wx + b;反向传播中的梯度计算本质上是链式法则的矩阵形式;批量训练时,多个样本组成矩阵并行计算。线性代数不仅是神经网络的数学语言,更是其高效实现的基石。
为什么(原理):神经网络的核心运算是仿射变换(线性变换 + 平移)和非线性激活函数的交替。线性变换由权重矩阵 W 实现,它定义了输入到输出的映射关系。训练过程就是不断调整 W 中的参数,使得网络输出越来越接近目标。梯度下降法利用矩阵微积分高效计算所有参数的梯度,而批量矩阵运算则充分利用了现代硬件的并行计算能力。
怎么做(实现):
import numpy as np
# ==================== 用numpy实现一个简单的神经网络前向传播 ====================
def relu(x):
"""ReLU 激活函数:max(0, x)"""
return np.maximum(0, x)
def softmax(x):
"""Softmax 函数:将向量转为概率分布"""
# 减去最大值防止数值溢出
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
# ==================== 网络结构定义 ====================
# 一个简单的 3 层神经网络:输入4维 → 隐藏层8维 → 隐藏层6维 → 输出3维
# 可以理解为:4个特征 → 8个中间表示 → 6个中间表示 → 3个类别概率
np.random.seed(42)
# 第一层权重和偏置:4 → 8
W1 = np.random.randn(4, 8) * 0.1 # 权重矩阵,小随机值初始化
b1 = np.zeros(8) # 偏置向量,初始化为0
# 第二层权重和偏置:8 → 6
W2 = np.random.randn(8, 6) * 0.1
b2 = np.zeros(6)
# 第三层权重和偏置:6 → 3
W3 = np.random.randn(6, 3) * 0.1
b3 = np.zeros(3)
print("网络参数形状:")
print(f" W1: {W1.shape}, b1: {b1.shape}")
print(f" W2: {W2.shape}, b2: {b2.shape}")
print(f" W3: {W3.shape}, b3: {b3.shape}")
# ==================== 单样本前向传播 ====================
# 输入一个样本(4维向量)
x_single = np.array([0.5, -0.3, 0.8, 0.1]) # 4个特征值
# 第一层:线性变换 + ReLU激活
z1 = x_single @ W1 + b1 # 线性变换:4维 → 8维
a1 = relu(z1) # 非线性激活
# 第二层:线性变换 + ReLU激活
z2 = a1 @ W2 + b2 # 线性变换:8维 → 6维
a2 = relu(z2) # 非线性激活
# 第三层:线性变换 + Softmax激活
z3 = a2 @ W3 + b3 # 线性变换:6维 → 3维
output = softmax(z3) # 输出概率分布
print(f"\n单样本前向传播:")
print(f" 输入: {x_single}")
print(f" 第一层输出 (z1): {z1.round(3)}")
print(f" 第一层激活 (a1): {a1.round(3)}")
print(f" 第二层输出 (z2): {z2.round(3)}")
print(f" 第二层激活 (a2): {a2.round(3)}")
print(f" 最终输出概率: {output.round(4)}")
print(f" 预测类别: {np.argmax(output)} (概率: {output.max():.4f})")
# ==================== 批量前向传播(矩阵运算的威力) ====================
# 同时处理 32 个样本,利用矩阵乘法的并行性
batch_size = 32
X_batch = np.random.randn(batch_size, 4) # 32个样本,每个4维
# 批量前向传播:一次矩阵乘法处理所有样本
Z1 = X_batch @ W1 + b1 # (32, 4) @ (4, 8) → (32, 8)
A1 = relu(Z1)
Z2 = A1 @ W2 + b2 # (32, 8) @ (8, 6) → (32, 6)
A2 = relu(Z2)
Z3 = A2 @ W3 + b3 # (32, 6) @ (6, 3) → (32, 3)
outputs = softmax(Z3)
print(f"\n批量前向传播:")
print(f" 输入批次形状: {X_batch.shape}")
print(f" 输出批次形状: {outputs.shape}")
print(f" 前3个样本的预测类别: {np.argmax(outputs[:3], axis=1)}")
print(f" GPU可以并行处理这些矩阵运算,这就是深度学习快的原因!")
# ==================== 参数量计算 ====================
total_params = (W1.size + b1.size) + (W2.size + b2.size) + (W3.size + b3.size)
print(f"\n网络总参数量: {total_params}")
print(f" = (4×8+8) + (8×6+6) + (6×3+3) = {4*8+8} + {8*6+6} + {6*3+3} = {total_params}")
print(" 每个参数都是权重矩阵中的一个元素,训练就是优化这些数值")
什么用(应用):理解线性代数在神经网络中的角色,有助于:(1) 理解模型为什么能学习——权重矩阵定义了变换,训练调整变换;(2) 理解GPU加速的原理——矩阵乘法天然适合并行;(3) 理解模型参数量——权重矩阵的大小决定了模型容量;(4) 理解注意力机制——Transformer的核心就是矩阵运算(QKV点积)。
哪些坑(缺点):纯线性变换(没有激活函数)的多层网络等价于一层——这就是为什么激活函数不可或缺;权重矩阵过大(参数过多)容易过拟合;矩阵乘法的数值稳定性需要注意(梯度爆炸/消失);批量大小受GPU显存限制,需要权衡速度和内存。
六、实战:用numpy实现PCA降维
是什么(定义):本节将前面学到的PCA理论完整实现为一个可用的Python函数,并在真实场景中应用。我们将使用numpy从零实现PCA的每一步:数据标准化、协方差矩阵计算、特征值分解、主成分选择和数据投影,最后用累积方差解释比例来评估降维效果。
怎么做(实现):
import numpy as np
# ==================== 从零实现 PCA 类 ====================
class PCAReducer:
"""用numpy从零实现的PCA降维器"""
def __init__(self, n_components=None, variance_threshold=None):
"""
初始化PCA降维器
参数:
n_components: 目标维度数(直接指定保留几个主成分)
variance_threshold: 方差解释比例阈值(如0.95表示保留95%方差)
"""
self.n_components = n_components # 目标维度
self.variance_threshold = variance_threshold # 方差阈值
self.mean_ = None # 训练数据的均值(用于中心化)
self.components_ = None # 主成分方向(特征向量)
self.eigenvalues_ = None # 特征值
self.explained_variance_ratio_ = None # 方差解释比例
self.n_components_ = None # 实际保留的主成分数
def fit(self, X):
"""
在数据上拟合PCA模型
参数:
X: 数据矩阵,形状 (n_samples, n_features)
"""
# 第一步:计算并保存均值,用于中心化
self.mean_ = np.mean(X, axis=0) # 每个特征的均值
X_centered = X - self.mean_ # 中心化
# 第二步:计算协方差矩阵
n_samples = X_centered.shape[0]
cov_matrix = (X_centered.T @ X_centered) / (n_samples - 1)
# 第三步:特征值分解
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
# 第四步:按特征值降序排列
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
# 第五步:计算方差解释比例
total_variance = np.sum(eigenvalues)
self.explained_variance_ratio_ = eigenvalues / total_variance
# 第六步:确定保留的主成分数
if self.n_components is not None:
# 直接指定维度数
self.n_components_ = self.n_components
elif self.variance_threshold is not None:
# 根据方差阈值自动确定维度数
cumulative = np.cumsum(self.explained_variance_ratio_)
self.n_components_ = np.searchsorted(cumulative, self.variance_threshold) + 1
else:
# 默认保留所有主成分
self.n_components_ = len(eigenvalues)
# 保存结果
self.eigenvalues_ = eigenvalues
self.components_ = eigenvectors[:, :self.n_components_] # 取前k个特征向量
return self
def transform(self, X):
"""
将数据投影到低维空间
参数:
X: 数据矩阵,形状 (n_samples, n_features)
返回:
降维后的数据,形状 (n_samples, n_components_)
"""
X_centered = X - self.mean_ # 用训练时的均值中心化
return X_centered @ self.components_ # 矩阵乘法完成投影
def fit_transform(self, X):
"""拟合并转换"""
self.fit(X)
return self.transform(X)
def inverse_transform(self, X_reduced):
"""
从低维空间重建高维数据
参数:
X_reduced: 降维后的数据
返回:
重建的高维数据(会有信息损失)
"""
return X_reduced @ self.components_.T + self.mean_
def summary(self):
"""打印PCA结果摘要"""
print(f"PCA降维摘要:")
print(f" 原始维度: {self.components_.shape[0]}")
print(f" 降维后维度: {self.n_components_}")
print(f" 保留方差比例: {np.sum(self.explained_variance_ratio_[:self.n_components_])*100:.2f}%")
print(f"\n 各主成分方差解释比例:")
for i in range(min(self.n_components_, 10)):
print(f" PC{i+1}: {self.explained_variance_ratio_[i]*100:.2f}%")
# ==================== 实战:对高维数据进行PCA降维 ====================
np.random.seed(42)
# 模拟一个用户行为数据集:1000个用户,20个行为特征
n_users = 1000
n_features = 20
# 构造数据:只有5个独立的信息源,其余是它们的线性组合+噪声
n_sources = 5
sources = np.random.randn(n_users, n_sources) * np.array([10, 5, 3, 2, 1])[:, np.newaxis].T
# 随机混合矩阵:将5个源混合成20个特征
mix_matrix = np.random.randn(n_sources, n_features)
X = sources @ mix_matrix + np.random.randn(n_users, n_features) * 0.5 # 加少量噪声
print("=" * 60)
print("实战:用户行为数据的PCA降维")
print("=" * 60)
print(f"\n原始数据: {n_users} 个用户, {n_features} 个行为特征")
# 方式1:指定保留95%的方差
pca_auto = PCAReducer(variance_threshold=0.95)
X_reduced = pca_auto.fit_transform(X)
pca_auto.summary()
# 方式2:指定保留5个主成分
pca_k5 = PCAReducer(n_components=5)
X_reduced_k5 = pca_k5.fit_transform(X)
print(f"\n指定保留5个主成分:")
print(f" 降维后数据形状: {X_reduced_k5.shape}")
print(f" 保留方差比例: {np.sum(pca_k5.explained_variance_ratio_[:5])*100:.2f}%")
# ==================== 评估降维效果 ====================
# 重建误差:降维后重建,与原始数据比较
X_reconstructed = pca_k5.inverse_transform(X_reduced_k5)
mse = np.mean((X - X_reconstructed) ** 2)
total_var = np.mean(X ** 2)
print(f"\n降维效果评估:")
print(f" 重建均方误差(MSE): {mse:.4f}")
print(f" 原始数据总方差: {total_var:.4f}")
print(f" 相对误差: {mse/total_var*100:.2f}%")
# ==================== 数据压缩比 ====================
original_size = n_users * n_features # 原始数据需要的存储量
compressed_size = n_users * 5 + 5 * n_features + n_features # 降维数据 + 投影矩阵 + 均值
compression_ratio = original_size / compressed_size
print(f"\n数据压缩:")
print(f" 原始存储量: {original_size} 个数值")
print(f" 压缩后存储量: {compressed_size} 个数值")
print(f" 压缩比: {compression_ratio:.2f}x")
print(f" (用约 {100/compression_ratio:.0f}% 的空间保留了 {np.sum(pca_k5.explained_variance_ratio_[:5])*100:.1f}% 的信息)")
什么用(应用):掌握PCA的numpy实现,不仅能深入理解降维原理,还能在没有scikit-learn的环境(如Pyodide)中使用。在实际项目中,PCA常用于:(1) 数据预处理——在训练复杂模型前先降维,加速训练;(2) 数据探索——通过主成分分析发现数据中的隐藏结构;(3) 特征工程——将PCA降维后的特征作为新特征加入模型。
哪些坑(缺点):自己实现PCA时容易犯的错误:(1) 忘记中心化——PCA要求零均值数据;(2) 忘记标准化——如果特征量纲不同,应先标准化再PCA;(3) 对测试数据用了不同的均值——必须用训练集的均值来中心化测试数据;(4) 特征值分解用了不稳定的算法——对于大矩阵应使用SVD代替特征值分解。
概念关系图谱
| 概念 | 上位概念 | 核心思想 | 关键公式/方法 | AI应用场景 |
|---|---|---|---|---|
| 向量 | 线性代数基础 | 有方向和大小的有序数列 | v = [v_1, v_2, ..., v_n] | 数据表示、特征向量 |
| 矩阵 | 线性代数基础 | 数值的矩形排列 | A ∈ R^(m×n) | 图像表示、权重矩阵 |
| 张量 | 线性代数扩展 | 多维数值数组 | T ∈ R^(d_1 × d_2 × ... × d_k) | 彩色图像、批量数据 |
| 词向量 | 嵌入技术 | 将词语映射为稠密向量 | Word2Vec, GloVe | NLP、语义搜索 |
| 嵌入 | 表示学习 | 离散对象→连续向量 | Embed(x) ∈ R^d | 推荐系统、搜索 |
| 协方差矩阵 | 统计学 | 衡量特征间的线性关系 | C = (1/(n−1)) X^T X | PCA、特征分析 |
| 特征值分解 | 矩阵分解 | 找到矩阵的"主方向" | Cv = λv | PCA、谱聚类 |
| PCA | 降维方法 | 沿方差最大方向投影 | Z = XW | 数据压缩、可视化 |
| 降维 | 数据预处理 | 减少维度保留信息 | PCA, t-SNE, UMAP | 特征工程、去噪 |
| 余弦相似度 | 相似度度量 | 衡量向量方向相似性 | cosθ = (a⋅b)/(|a||b|) | 推荐系统、搜索 |
| 维度灾难 | 高维统计 | 高维数据稀疏性 | 距离集中现象 | 决定是否降维 |
重点答疑
Q1: PCA和特征选择的区别是什么?什么时候用PCA,什么时候用特征选择?
PCA和特征选择都是降维方法,但本质不同。特征选择是从原始特征中挑选子集,保留的是原始特征本身(如从100个特征中选20个),不改变特征的含义。PCA是特征提取,通过线性组合创建全新的特征(主成分),新特征是原始特征的加权混合,含义可能难以解释。
什么时候用PCA:特征之间高度相关(存在冗余);不关心特征的可解释性;需要最大化保留方差(信息);数据维度远大于样本数。
什么时候用特征选择:需要保留特征的可解释性;某些特征明确无用或有害;特征之间相对独立;业务规则要求使用特定特征。
Q2: 降维后模型的准确率一定会下降吗?
不一定!降维后模型准确率可能下降,也可能上升,取决于具体情况:
准确率可能上升的情况:原始数据中有大量噪声特征,降维去除了噪声;原始特征高度冗余,降维消除了多重共线性;数据维度远大于样本数(小样本高维问题),降维缓解了过拟合。
准确率可能下降的情况:降维过度,丢失了关键判别信息;数据本身维度不高且特征都重要;降维方法不适合数据结构(如对非线性数据用PCA)。
Q3: 为什么PCA之前要标准化数据?不标准化会怎样?
PCA寻找方差最大的方向,而方差的量纲与原始特征的量纲直接相关。如果不标准化,量纲大的特征会主导PCA的结果。
例如:一个数据集有两个特征——身高(厘米,值在150-190之间,方差约100)和考试成绩(0-100分,方差约400)。不标准化就做PCA,第一主成分几乎完全由考试成绩决定,因为它的方差更大。但这并不意味着考试成绩更"重要"——只是它的数值范围更大。
标准化的方法:将每个特征减去均值再除以标准差,使所有特征的方差都为1,让PCA公平对待每个特征。
Q4: 词向量中"国王-男人+女人≈女王"是怎么做到的?这真的有意义吗?
这个经典例子展示了词向量空间中的"语义方向"概念。在训练过程中,"国王"和"男人"经常出现在相似的上下文中(区别只在于"皇权"这个维度),"女王"和"女人"也是如此。因此,"国王→女王"的向量和"男人→女人"的向量大致平行,都指向"男性→女性"这个语义方向。
数学上:v_国王 - v_男人 ≈ v_女王 - v_女人,所以 v_国王 - v_男人 + v_女人 ≈ v_女王。
但这有意义吗?有,但要谨慎:(1) 它证明了词向量确实捕获了某种语义关系;(2) 它不是完美的,结果向量通常只是"最接近女王"而非"精确等于女王";(3) 这种线性关系在更复杂的语义关系上不一定成立;(4) 存在性别偏见等问题——"医生-男人+女人"可能得到"护士"而非"女医生"。
Q5: 神经网络中为什么必须要有非线性激活函数?全用线性变换不行吗?
如果全用线性变换(没有激活函数),无论网络有多少层,最终都等价于一个单层线性变换。数学证明:
假设两层线性变换:y = W_2(W_1 x + b_1) + b_2
展开得:y = W_2 W_1 x + W_2 b_1 + b_2
令 W' = W_2 W_1,b' = W_2 b_1 + b_2,则 y = W' x + b'
这就是一个单层线性变换!多层线性网络等价于一层线性网络,无法学习非线性模式(如XOR问题)。非线性激活函数(ReLU、Sigmoid等)打破了这种等价性,使网络能够拟合任意复杂的函数。
Q6: 协方差矩阵的特征值和特征向量在PCA中分别代表什么含义?
特征值代表该主成分方向上的方差大小,也就是该方向上包含的"信息量"。特征值越大,说明数据在该方向上越分散,包含的信息越多。特征值为0的方向,数据完全没有变化(所有点重合),不包含任何信息。
特征向量代表主成分的方向,即数据在新坐标系中的轴方向。每个特征向量是一个单位向量,指向数据方差最大的方向(在已排除之前主成分的条件下)。特征向量的每个分量表示对应原始特征在该主成分中的权重(贡献度)。
例如,如果第一特征向量是 [0.7, 0.7](近似值),说明第一主成分大致沿着45度方向,两个原始特征对第一主成分的贡献差不多。如果第一特征向量是 [0.98, 0.14],说明第一主成分几乎完全由第一个特征决定。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| vector | /ˈvektər/ | 向量 |
| matrix | /ˈmeɪtrɪks/ | 矩阵 |
| tensor | /ˈtensər/ | 张量 |
| dimension | /daɪˈmenʃn/ | 维度 |
| dimensionality reduction | /daɪˌmenʃəˈnæləti rɪˈdʌkʃn/ | 降维 |
| principal component analysis | /ˈprɪnsəpl kəmˈpoʊnənt əˈnæləsɪs/ | 主成分分析 |
| covariance | /koʊˈveriəns/ | 协方差 |
| eigenvalue | /ˈaɪɡənˌvæljuː/ | 特征值 |
| eigenvector | /ˈaɪɡənˌvektər/ | 特征向量 |
| embedding | /emˈbedɪŋ/ | 嵌入 |
| word embedding | /wɜːrd emˈbedɪŋ/ | 词向量/词嵌入 |
| projection | /prəˈdʒekʃn/ | 投影 |
| variance | /ˈveriəns/ | 方差 |
| cosine similarity | /ˈkoʊsaɪn ˌsɪməˈlærəti/ | 余弦相似度 |
| dot product | /dɑːt ˈprɑːdʌkt/ | 点积 |
| orthogonal | /ɔːrˈθɑːɡənl/ | 正交 |
| feature extraction | /ˈfiːtʃər ɪkˈstrækʃn/ | 特征提取 |
| curse of dimensionality | /kɜːrs əv daɪˌmenʃəˈnæləti/ | 维度灾难 |
| activation function | /ˌæktɪˈveɪʃn ˈfʌŋkʃn/ | 激活函数 |
| forward propagation | /ˈfɔːrwərd ˌprɑːpəˈɡeɪʃn/ | 前向传播 |
| reconstruction error | /ˌriːkənˈstrʌkʃn ˈerər/ | 重建误差 |
| standardization | /ˌstændərdaɪˈzeɪʃn/ | 标准化 |
| cumulative | /ˈkjuːmjəleɪtɪv/ | 累积的 |
| sparse | /spɑːrs/ | 稀疏的 |
| dense | /dens/ | 稠密的 |